diff --git a/README.rst b/README.rst index 7e3fd3cd..60fb0e74 100755 --- a/README.rst +++ b/README.rst @@ -26,7 +26,7 @@ Installing .. code:: sh - pip3 install telethon-aio + pip3 install telethon Creating a client diff --git a/readthedocs/extra/basic/asyncio-magic.rst b/readthedocs/extra/basic/asyncio-magic.rst index 48ab9943..c4129b9e 100644 --- a/readthedocs/extra/basic/asyncio-magic.rst +++ b/readthedocs/extra/basic/asyncio-magic.rst @@ -13,9 +13,9 @@ Magic with asyncio import telethon.sync - At the beginning of your main script and you will be good. If you - do use updates or events, keep reading, or use `a simpler version - `_ (discouraged). + At the beginning of your main script and you will be good. If you do use + updates or events, keep reading, or install the latest version using + threads and Python 3.4 support with ``pip install telethon==0.19.1.6``. You might also want to check the :ref:`changelog`. @@ -120,7 +120,7 @@ way, since your event handlers will be ``async def`` too. There are two exceptions. Both `client.run_until_disconnected() ` and - `client.start() ` work in + `client.start() ` work in and outside of ``async def`` for convenience without importing the magic module. The rest of methods remain ``async`` unless you import it. diff --git a/readthedocs/extra/changelog.rst b/readthedocs/extra/changelog.rst index 5536702b..96f59b47 100644 --- a/readthedocs/extra/changelog.rst +++ b/readthedocs/extra/changelog.rst @@ -14,6 +14,131 @@ it can take advantage of new goodies! .. contents:: List of All Versions +Synchronous magic (v1.0) +======================== + +.. important:: + + If you come from Telethon pre-1.0 you **really** want to read + :ref:`asyncio-magic` to port your scripts to the new version. + + If you're not ready for this, you can ``pip install telethon==0.19.1.6``. + It's the latest version of the library using threads for Python 3.4+. + + If you're interested in maintaining a Telethon version that supports + Python 3.4 and uses threads, please open an issue and let me know. + +The library has been around for well over a year. A lot of improvements have +been made, a lot of user complaints have been fixed, and a lot of user desires +have been implemented. It's time to consider the public API as stable, and +remove some of the old methods that were around until now for compatibility +reasons. But there's one more surprise! + +There is a new magic ``telethon.sync`` module to let you use **all** the +methods in the :ref:`TelegramClient ` (and the types returned +from its functions) in a synchronous way, while using ``asyncio`` behind +the scenes! This means you're now able to do both of the following: + +.. code-block:: python + + import asyncio + + async def main(): + await client.send_message('me', 'Hello!') + + asyncio.get_event_loop().run_until_complete(main()) + + # ...can be rewritten as: + + from telethon import sync + client.send_message('me', 'Hello!') + +Both ways can coexist (you need to ``await`` if the loop is running). + +You can also use the magic ``sync`` module in your own classes, and call +``sync.syncify(cls)`` to convert all their ``async def`` into magic variants. + + + +Breaking Changes +~~~~~~~~~~~~~~~~ + +- ``message.get_fwd_sender`` is now in `message.forward + `. +- ``client.add_update_handler`` is now `client.add_event_handler + ` +- ``client.remove_update_handler`` is now `client.remove_event_handler + ` +- ``client.list_update_handlers`` is now `client.list_event_handlers + ` +- ``client.get_message_history`` is now `client.get_messages + ` +- ``client.send_voice_note`` is now `client.send_file + ` with ``is_voice=True``. +- ``client.invoke()`` is now ``client(...)``. +- ``report_errors`` has been removed since it's currently not used, + and ``flood_sleep_threshold`` is now part of the client. +- Methods with a lot of arguments can no longer be used without specifying + their argument. Instead you need to use named arguments. This improves + readability and not needing to learn the order of the arguments, which + can also change. + + +Additions +~~~~~~~~~ + +- `client.send_file ` now + accepts external ``http://`` and ``https://`` URLs. +- You can use the :ref:`TelegramClient ` inside of ``with`` + blocks, which will `client.start() ` + and `disconnect() ` + the client for you: + + .. code-block:: python + + from telethon import TelegramClient, sync + + with TelegramClient(name, api_id, api_hash) as client: + client.send_message('me', 'Hello!') + + Convenience at its maximum! You can even chain the `.start() + ` method since + it returns the instance of the client: + + .. code-block:: python + + with TelegramClient(name, api_id, api_hash).star(bot_token=token) as bot: + bot.send_message(chat, 'Hello!') + + +Bug fixes +~~~~~~~~~ + +- There were some ``@property async def`` left, and some ``await property``. +- "User joined" event was being treated as "User was invited". +- SQLite's cursor should not be closed properly after usage. +- ``await`` the updates task upon disconnection. +- Some bug in Python 3.5.2's ``asyncio`` causing 100% CPU load if you + forgot to call `client.disconnect() + `. + The method is called for you on object destruction, but you still should + disconnect manually or use a ``with`` block. +- Some fixes regarding disconnecting on client deletion and properly + saving the authorization key. +- Passing a class to `message.get_entities_text + ` now works properly. +- Iterating messages from a specific user in private messages now works. + +Enhancements +~~~~~~~~~~~~ + +- Both `client.start() ` and + `client.run_until_disconnected() + ` can + be ran in both a synchronous way (without starting the loop manually) + or from an ``async def`` where they need to have an ``await``. + + Core Rewrite in asyncio (v1.0-rc1) ================================== diff --git a/setup.py b/setup.py index 970e7cd0..43cb66e2 100755 --- a/setup.py +++ b/setup.py @@ -183,7 +183,7 @@ def main(): version = re.search(r"^__version__\s*=\s*'(.*)'.*$", f.read(), flags=re.MULTILINE).group(1) setup( - name='Telethon-aio', + name='Telethon', version=version, description="Full-featured Telegram client library for Python 3", long_description=long_description, @@ -206,7 +206,7 @@ def main(): # 3 - Alpha # 4 - Beta # 5 - Production/Stable - 'Development Status :: 3 - Alpha', + 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'Topic :: Communications :: Chat', diff --git a/telethon/client/auth.py b/telethon/client/auth.py index 15e8d4ac..033132be 100644 --- a/telethon/client/auth.py +++ b/telethon/client/auth.py @@ -17,6 +17,7 @@ class AuthMethods(MessageParseMethods, UserMethods): self, phone=lambda: input('Please enter your phone: '), password=lambda: getpass.getpass('Please enter your password: '), + *, bot_token=None, force_sms=False, code_callback=None, first_name='New User', last_name='', max_attempts=3): """ @@ -193,7 +194,7 @@ class AuthMethods(MessageParseMethods, UserMethods): return self async def sign_in( - self, phone=None, code=None, password=None, + self, phone=None, *, code=None, password=None, bot_token=None, phone_code_hash=None): """ Starts or completes the sign in process with the given phone number @@ -325,7 +326,7 @@ class AuthMethods(MessageParseMethods, UserMethods): self._authorized = True return result.user - async def send_code_request(self, phone, force_sms=False): + async def send_code_request(self, phone, *, force_sms=False): """ Sends a code request to the specified phone number. @@ -382,8 +383,8 @@ class AuthMethods(MessageParseMethods, UserMethods): return True async def edit_2fa( - self, current_password=None, new_password=None, hint='', - email=None): + self, current_password=None, new_password=None, + *, hint='', email=None): """ Changes the 2FA settings of the logged in user, according to the passed parameters. Take note of the parameter explanations. diff --git a/telethon/client/chats.py b/telethon/client/chats.py index 02188e73..a80bc3f4 100644 --- a/telethon/client/chats.py +++ b/telethon/client/chats.py @@ -13,7 +13,7 @@ class ChatMethods(UserMethods): @async_generator async def iter_participants( - self, entity, limit=None, search='', + self, entity, limit=None, *, search='', filter=None, aggressive=False, _total=None): """ Iterator over the participants belonging to the specified chat. diff --git a/telethon/client/dialogs.py b/telethon/client/dialogs.py index b726e364..fa388732 100644 --- a/telethon/client/dialogs.py +++ b/telethon/client/dialogs.py @@ -14,7 +14,7 @@ class DialogMethods(UserMethods): @async_generator async def iter_dialogs( - self, limit=None, offset_date=None, offset_id=0, + self, limit=None, *, offset_date=None, offset_id=0, offset_peer=types.InputPeerEmpty(), ignore_migrated=False, _total=None): """ diff --git a/telethon/client/downloads.py b/telethon/client/downloads.py index 731a308c..318692b9 100644 --- a/telethon/client/downloads.py +++ b/telethon/client/downloads.py @@ -16,7 +16,7 @@ class DownloadMethods(UserMethods): # region Public methods async def download_profile_photo( - self, entity, file=None, download_big=True): + self, entity, file=None, *, download_big=True): """ Downloads the profile photo of the given entity (user/chat/channel). @@ -91,7 +91,8 @@ class DownloadMethods(UserMethods): # Until there's a report for chats, no need to. return None - async def download_media(self, message, file=None, progress_callback=None): + async def download_media(self, message, file=None, + *, progress_callback=None): """ Downloads the given media, or the media from a specified Message. @@ -141,7 +142,7 @@ class DownloadMethods(UserMethods): ) async def download_file( - self, input_location, file=None, part_size_kb=None, + self, input_location, file=None, *, part_size_kb=None, file_size=None, progress_callback=None): """ Downloads the given input location to a file. diff --git a/telethon/client/messages.py b/telethon/client/messages.py index 6341e219..1c84be36 100644 --- a/telethon/client/messages.py +++ b/telethon/client/messages.py @@ -22,7 +22,7 @@ class MessageMethods(UploadMethods, MessageParseMethods): @async_generator async def iter_messages( - self, entity, limit=None, offset_date=None, offset_id=0, + self, entity, limit=None, *, offset_date=None, offset_id=0, max_id=0, min_id=0, add_offset=0, search=None, filter=None, from_user=None, batch_size=100, wait_time=None, ids=None, _total=None): @@ -285,7 +285,7 @@ class MessageMethods(UploadMethods, MessageParseMethods): # region Message sending/editing/deleting async def send_message( - self, entity, message='', reply_to=None, + self, entity, message='', *, reply_to=None, parse_mode=utils.Default, link_preview=True, file=None, force_document=False, clear_draft=False): """ @@ -403,7 +403,7 @@ class MessageMethods(UploadMethods, MessageParseMethods): return self._get_response_message(request, result, entity) - async def forward_messages(self, entity, messages, from_peer=None): + async def forward_messages(self, entity, messages, *, from_peer=None): """ Forwards the given message(s) to the specified entity. @@ -469,8 +469,8 @@ class MessageMethods(UploadMethods, MessageParseMethods): return result[0] if single else result async def edit_message( - self, entity, message=None, text=None, parse_mode=utils.Default, - link_preview=True, file=None): + self, entity, message=None, text=None, + *, parse_mode=utils.Default, link_preview=True, file=None): """ Edits the given message ID (to change its contents or disable preview). @@ -542,7 +542,7 @@ class MessageMethods(UploadMethods, MessageParseMethods): self._cache_media(msg, file, file_handle) return msg - async def delete_messages(self, entity, message_ids, revoke=True): + async def delete_messages(self, entity, message_ids, *, revoke=True): """ Deletes a message from a chat, optionally "for everyone". @@ -586,8 +586,8 @@ class MessageMethods(UploadMethods, MessageParseMethods): # region Miscellaneous - async def send_read_acknowledge(self, entity, message=None, max_id=None, - clear_mentions=False): + async def send_read_acknowledge( + self, entity, message=None, *, max_id=None, clear_mentions=False): """ Sends a "read acknowledge" (i.e., notifying the given peer that we've read their messages, also known as the "double check"). diff --git a/telethon/client/uploads.py b/telethon/client/uploads.py index 473c8c9d..cd38d024 100644 --- a/telethon/client/uploads.py +++ b/telethon/client/uploads.py @@ -27,7 +27,7 @@ class UploadMethods(MessageParseMethods, UserMethods): # region Public methods async def send_file( - self, entity, file, caption='', force_document=False, + self, entity, file, *, caption='', force_document=False, progress_callback=None, reply_to=None, attributes=None, thumb=None, allow_cache=True, parse_mode=utils.Default, voice_note=False, video_note=False, **kwargs): @@ -221,7 +221,7 @@ class UploadMethods(MessageParseMethods, UserMethods): ] async def upload_file( - self, file, part_size_kb=None, file_name=None, use_cache=None, + self, file, *, part_size_kb=None, file_name=None, use_cache=None, progress_callback=None): """ Uploads the specified file and returns a handle (an instance of diff --git a/telethon/events/messageread.py b/telethon/events/messageread.py index 8e634cee..6e781fa2 100644 --- a/telethon/events/messageread.py +++ b/telethon/events/messageread.py @@ -14,7 +14,7 @@ class MessageRead(EventBuilder): messages the event will be fired. By default (``False``) only when messages you sent are read by someone else will fire it. """ - def __init__(self, inbox=False, chats=None, blacklist_chats=None): + def __init__(self, chats=None, *, blacklist_chats=None, inbox=False): super().__init__(chats, blacklist_chats) self.inbox = inbox diff --git a/telethon/events/userupdate.py b/telethon/events/userupdate.py index 518edd9a..ad2d198e 100644 --- a/telethon/events/userupdate.py +++ b/telethon/events/userupdate.py @@ -82,7 +82,7 @@ class UserUpdate(EventBuilder): contact (`bool`): ``True`` if what's being uploaded (selected) is a contact. """ - def __init__(self, user_id, status=None, typing=None): + def __init__(self, user_id, *, status=None, typing=None): super().__init__(types.PeerUser(user_id)) self.online = None if status is None else \ diff --git a/telethon/version.py b/telethon/version.py index dfd1fc23..22b1312a 100644 --- a/telethon/version.py +++ b/telethon/version.py @@ -1,3 +1,3 @@ # Versions should comply with PEP440. # This line is parsed in setup.py: -__version__ = '1.0-rc1' +__version__ = '1.0'