diff --git a/readthedocs/concepts/strings.rst b/readthedocs/concepts/strings.rst index dee48c80..a696b684 100644 --- a/readthedocs/concepts/strings.rst +++ b/readthedocs/concepts/strings.rst @@ -3,7 +3,7 @@ String-based Debugging ====================== Debugging is *really* important. Telegram's API is really big and there -is a lot of things that you should know. Such as, what attributes or fields +are a lot of things that you should know. Such as, what attributes or fields does a result have? Well, the easiest thing to do is printing it: .. code-block:: python @@ -32,7 +32,7 @@ Can we get better than the shown string, though? Yes! print(entity.stringify()) -Will show a much better: +Will show a much better representation: .. code-block:: python diff --git a/telethon/client/telegrambaseclient.py b/telethon/client/telegrambaseclient.py index 62faecae..ce80211b 100644 --- a/telethon/client/telegrambaseclient.py +++ b/telethon/client/telegrambaseclient.py @@ -1,4 +1,5 @@ import abc +import re import asyncio import collections import logging @@ -161,11 +162,11 @@ class TelegramBaseClient(abc.ABC): device_model (`str`, optional): "Device model" to be sent when creating the initial connection. - Defaults to ``platform.node()``. + Defaults to 'PC (n)bit' derived from ``platform.uname().machine``, or its direct value if unknown. system_version (`str`, optional): "System version" to be sent when creating the initial connection. - Defaults to ``platform.system()``. + Defaults to ``platform.uname().release`` stripped of everything ahead of -. app_version (`str`, optional): "App version" to be sent when creating the initial connection. @@ -320,11 +321,19 @@ class TelegramBaseClient(abc.ABC): # Used on connection. Capture the variables in a lambda since # exporting clients need to create this InvokeWithLayerRequest. system = platform.uname() + + if system.machine in ('x86_64', 'AMD64'): + default_device_model = 'PC 64bit' + elif system.machine in ('i386','i686','x86'): + default_device_model = 'PC 32bit' + else: + default_device_model = system.machine + default_system_version = re.sub(r'-.+','',system.release) self._init_with = lambda x: functions.InvokeWithLayerRequest( LAYER, functions.InitConnectionRequest( api_id=self.api_id, - device_model=device_model or system.system or 'Unknown', - system_version=system_version or system.release or '1.0', + device_model=device_model or default_device_model or 'Unknown', + system_version=system_version or default_system_version or '1.0', app_version=app_version or self.__version__, lang_code=lang_code, system_lang_code=system_lang_code, diff --git a/telethon/client/updates.py b/telethon/client/updates.py index e609f9ab..de051454 100644 --- a/telethon/client/updates.py +++ b/telethon/client/updates.py @@ -467,6 +467,8 @@ class UpdateMethods: # the name of speed; we don't want to make it worse for all updates # just because albums may need it. for builder, callback in self._event_builders: + if isinstance(builder, events.Raw): + continue if not isinstance(event, builder.Event): continue diff --git a/telethon/utils.py b/telethon/utils.py index f74ef880..86a8661e 100644 --- a/telethon/utils.py +++ b/telethon/utils.py @@ -601,16 +601,52 @@ def get_message_id(message): def _get_metadata(file): - # `hachoir` only deals with paths to in-disk files, while - # `_get_extension` supports a few other things. The parser - # may also fail in any case and we don't want to crash if + if not hachoir: + return + + stream = None + close_stream = True + seekable = True + + # The parser may fail and we don't want to crash if # the extraction process fails. - if hachoir and isinstance(file, str) and os.path.isfile(file): - try: - with hachoir.parser.createParser(file) as parser: - return hachoir.metadata.extractMetadata(parser) - except Exception as e: - _log.warning('Failed to analyze %s: %s %s', file, e.__class__, e) + try: + # Note: aiofiles are intentionally left out for simplicity + if isinstance(file, str): + stream = open(file, 'rb') + elif isinstance(file, bytes): + stream = io.BytesIO(file) + else: + stream = file + close_stream = False + if getattr(file, 'seekable', None): + seekable = file.seekable() + else: + seekable = False + + if not seekable: + return None + + pos = stream.tell() + filename = getattr(file, 'name', '') + + parser = hachoir.parser.guess.guessParser(hachoir.stream.InputIOStream( + stream, + source='file:' + filename, + tags=[], + filename=filename + )) + + return hachoir.metadata.extractMetadata(parser) + + except Exception as e: + _log.warning('Failed to analyze %s: %s %s', file, e.__class__, e) + + finally: + if stream and close_stream: + stream.close() + elif stream and seekable: + stream.seek(pos) def get_attributes(file, *, attributes=None, mime_type=None, @@ -803,15 +839,31 @@ def is_gif(file): def is_audio(file): - """Returns `True` if the file extension looks like an audio file.""" - file = 'a' + _get_extension(file) - return (mimetypes.guess_type(file)[0] or '').startswith('audio/') + """Returns `True` if the file has an audio mime type.""" + ext = _get_extension(file) + if not ext: + metadata = _get_metadata(file) + if metadata and metadata.has('mime_type'): + return metadata.get('mime_type').startswith('audio/') + else: + return False + else: + file = 'a' + ext + return (mimetypes.guess_type(file)[0] or '').startswith('audio/') def is_video(file): - """Returns `True` if the file extension looks like a video file.""" - file = 'a' + _get_extension(file) - return (mimetypes.guess_type(file)[0] or '').startswith('video/') + """Returns `True` if the file has a video mime type.""" + ext = _get_extension(file) + if not ext: + metadata = _get_metadata(file) + if metadata and metadata.has('mime_type'): + return metadata.get('mime_type').startswith('video/') + else: + return False + else: + file = 'a' + ext + return (mimetypes.guess_type(file)[0] or '').startswith('video/') def is_list_like(obj):