From e1905d0d7ad3014e02bda9d5d468f669c21a7353 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Tue, 13 Aug 2019 23:33:39 +0200 Subject: [PATCH] Avoid using telethon.sync in the examples --- readthedocs/basic/quick-start.rst | 60 ++++++++++++---- readthedocs/basic/signing-in.rst | 14 +++- readthedocs/concepts/asyncio.rst | 6 +- readthedocs/concepts/entities.rst | 47 ++++++------ readthedocs/concepts/errors.rst | 3 +- readthedocs/concepts/full-api.rst | 29 +++++--- readthedocs/concepts/sessions.rst | 2 +- readthedocs/concepts/strings.rst | 2 +- readthedocs/examples/chats-and-channels.rst | 20 +++--- readthedocs/examples/users.rst | 10 +-- .../examples/working-with-messages.rst | 6 +- readthedocs/modules/client.rst | 7 +- readthedocs/quick-references/faq.rst | 6 +- telethon/client/account.py | 10 +-- telethon/client/auth.py | 20 +++--- telethon/client/bots.py | 4 +- telethon/client/buttons.py | 2 +- telethon/client/chats.py | 40 +++++------ telethon/client/dialogs.py | 42 +++++------ telethon/client/downloads.py | 17 +++-- telethon/client/messages.py | 72 +++++++++---------- telethon/client/telegrambaseclient.py | 4 +- telethon/client/updates.py | 4 +- telethon/client/uploads.py | 28 ++++---- telethon/client/users.py | 27 +++---- telethon/helpers.py | 2 +- telethon/tl/custom/message.py | 12 ++-- telethon/utils.py | 4 +- 28 files changed, 283 insertions(+), 217 deletions(-) diff --git a/readthedocs/basic/quick-start.rst b/readthedocs/basic/quick-start.rst index 40e99f6b..30cf0ea4 100644 --- a/readthedocs/basic/quick-start.rst +++ b/readthedocs/basic/quick-start.rst @@ -8,15 +8,16 @@ use these if possible. .. code-block:: python - from telethon.sync import TelegramClient + from telethon import TelegramClient # Remember to use your own values from my.telegram.org! api_id = 12345 api_hash = '0123456789abcdef0123456789abcdef' + client = TelegramClient('anon', api_id, api_hash) - with TelegramClient('anon', api_id, api_hash) as client: + async def main(): # Getting information about yourself - me = client.get_me() + me = await client.get_me() # "me" is an User object. You can pretty-print # any Telegram object with the "stringify" method: @@ -30,20 +31,20 @@ use these if possible. print(me.phone) # You can print all the dialogs/conversations that you are part of: - for dialog in client.iter_dialogs(): + async for dialog in client.iter_dialogs(): print(dialog.name, 'has ID', dialog.id) # You can send messages to yourself... - client.send_message('me', 'Hello, myself!') + await client.send_message('me', 'Hello, myself!') # ...to some chat ID - client.send_message(-100123456, 'Hello, group!') + await client.send_message(-100123456, 'Hello, group!') # ...to your contacts - client.send_message('+34600123123', 'Hello, friend!') + await client.send_message('+34600123123', 'Hello, friend!') # ...or even to any username - client.send_message('TelethonChat', 'Hello, Telethon!') + await client.send_message('TelethonChat', 'Hello, Telethon!') # You can, of course, use markdown in your messages: - message = client.send_message( + message = await client.send_message( 'me', 'This message has **bold**, `code`, __italics__ and ' 'a [nice website](https://lonamiwebs.github.io)!', @@ -54,20 +55,23 @@ use these if possible. print(message.raw_text) # You can reply to messages directly if you have a message object - message.reply('Cool!') + await message.reply('Cool!') # Or send files, songs, documents, albums... - client.send_file('me', '/home/me/Pictures/holidays.jpg') + await client.send_file('me', '/home/me/Pictures/holidays.jpg') # You can print the message history of any chat: - for message in client.iter_messages('me'): + async for message in client.iter_messages('me'): print(message.id, message.text) # You can download media from messages, too! # The method will return the path where the file was saved. if message.photo: - path = message.download_media() - print('File saved to', path) + path = await message.download_media() + print('File saved to', path) # printed after download is done + + with client: + client.loop.run_until_complete(main()) Here, we show how to sign in, get information about yourself, send @@ -77,3 +81,31 @@ files. You should make sure that you understand what the code shown here does, take note on how methods are called and used and so on before proceeding. We will see all the available methods later on. + +.. important:: + + Note that Telethon is an asynchronous library, and as such, you should + get used to it and learn a bit of basic `asyncio`. This will help a lot. + As a quick start, this means you generally want to write all your code + inside some ``async def`` like so: + + .. code-block:: python + + client = ... + + async def do_something(me): + ... + + async def main(): + # Most of your code should go here. + # You can of course make and use your own async def (do_something). + # They only need to be async if they need to await things. + me = await client.get_me() + await do_something(me) + + with client: + client.loop.run_until_complete(main()) + + After you understand this, you may use the ``telethon.sync`` hack if you + want do so (see :ref:`compatibility-and-convenience`), but note you may + run into other issues (iPython, Anaconda, etc. have some issues with it). diff --git a/readthedocs/basic/signing-in.rst b/readthedocs/basic/signing-in.rst index 9614b623..453d54fa 100644 --- a/readthedocs/basic/signing-in.rst +++ b/readthedocs/basic/signing-in.rst @@ -49,7 +49,7 @@ We can finally write some code to log into our account! .. code-block:: python - from telethon.sync import TelegramClient + from telethon import TelegramClient # Use your own values from my.telegram.org api_id = 12345 @@ -57,7 +57,7 @@ We can finally write some code to log into our account! # The first parameter is the .session file name (absolute paths allowed) with TelegramClient('anon', api_id, api_hash) as client: - client.send_message('me', 'Hello, myself!') + client.loop.run_until_complete(client.send_message('me', 'Hello, myself!')) In the first line, we import the class name so we can create an instance @@ -68,6 +68,16 @@ At last, we create a new `TelegramClient ` the client, logging or signing up if necessary. diff --git a/readthedocs/concepts/asyncio.rst b/readthedocs/concepts/asyncio.rst index 8500e55e..4a617080 100644 --- a/readthedocs/concepts/asyncio.rst +++ b/readthedocs/concepts/asyncio.rst @@ -96,8 +96,12 @@ Instead of this: .. code-block:: python + me = client.loop.run_until_complete(client.get_me()) + print(me.username) + + # or, using asyncio's default loop (it's the same) import asyncio - loop = asyncio.get_event_loop() + loop = asyncio.get_event_loop() # == client.loop me = loop.run_until_complete(client.get_me()) print(me.username) diff --git a/readthedocs/concepts/entities.rst b/readthedocs/concepts/entities.rst index a92d36ba..f51cdfe3 100644 --- a/readthedocs/concepts/entities.rst +++ b/readthedocs/concepts/entities.rst @@ -102,33 +102,35 @@ you're able to just do this: .. code-block:: python + # (These examples assume you are inside an "async def") + # # Dialogs are the "conversations you have open". # This method returns a list of Dialog, which # has the .entity attribute and other information. # # This part is IMPORTANT, because it feels the entity cache. - dialogs = client.get_dialogs() + dialogs = await client.get_dialogs() # All of these work and do the same. - lonami = client.get_entity('lonami') - lonami = client.get_entity('t.me/lonami') - lonami = client.get_entity('https://telegram.dog/lonami') + lonami = await client.get_entity('lonami') + lonami = await client.get_entity('t.me/lonami') + lonami = await client.get_entity('https://telegram.dog/lonami') # Other kind of entities. - channel = client.get_entity('telegram.me/joinchat/AAAAAEkk2WdoDrB4-Q8-gg') - contact = client.get_entity('+34xxxxxxxxx') - friend = client.get_entity(friend_id) + channel = await client.get_entity('telegram.me/joinchat/AAAAAEkk2WdoDrB4-Q8-gg') + contact = await client.get_entity('+34xxxxxxxxx') + friend = await client.get_entity(friend_id) # Getting entities through their ID (User, Chat or Channel) - entity = client.get_entity(some_id) + entity = await client.get_entity(some_id) # You can be more explicit about the type for said ID by wrapping # it inside a Peer instance. This is recommended but not necessary. from telethon.tl.types import PeerUser, PeerChat, PeerChannel - my_user = client.get_entity(PeerUser(some_id)) - my_chat = client.get_entity(PeerChat(some_id)) - my_channel = client.get_entity(PeerChannel(some_id)) + my_user = await client.get_entity(PeerUser(some_id)) + my_chat = await client.get_entity(PeerChat(some_id)) + my_channel = await client.get_entity(PeerChannel(some_id)) .. note:: @@ -212,7 +214,7 @@ wherever needed, so you can even do things like: .. code-block:: python - client(SendMessageRequest('username', 'hello')) + await client(SendMessageRequest('username', 'hello')) The library will call the ``.resolve()`` method of the request, which will resolve ``'username'`` with the appropriated :tl:`InputPeer`. Don't worry if @@ -258,7 +260,7 @@ That means you can do this: message.is_private message.chat_id - message.get_chat() + await message.get_chat() # ...etc `SenderGetter ` is similar: @@ -266,7 +268,7 @@ That means you can do this: .. code-block:: python message.user_id - message.get_input_user() + await message.get_input_user() message.user # ...etc @@ -285,22 +287,25 @@ applications"? Now do the same with the library. Use what applies: .. code-block:: python - with client: + # (These examples assume you are inside an "async def") + async with client: # Does it have an username? Use it! - entity = client.get_entity(username) + entity = await client.get_entity(username) # Do you have a conversation open with them? Get dialogs. - client.get_dialogs() + await client.get_dialogs() # Are they participant of some group? Get them. - client.get_participants('TelethonChat') + await client.get_participants('TelethonChat') # Is the entity the original sender of a forwarded message? Get it. - client.get_messages('TelethonChat', 100) + await client.get_messages('TelethonChat', 100) # NOW you can use the ID, anywhere! - entity = client.get_entity(123456) - client.send_message(123456, 'Hi!') + await client.send_message(123456, 'Hi!') + + entity = await client.get_entity(123456) + print(entity) Once the library has "seen" the entity, you can use their **integer** ID. You can't use entities from IDs the library hasn't seen. You must make the diff --git a/readthedocs/concepts/errors.rst b/readthedocs/concepts/errors.rst index 429c5697..dc8d584c 100644 --- a/readthedocs/concepts/errors.rst +++ b/readthedocs/concepts/errors.rst @@ -19,7 +19,8 @@ available in :ref:`telethon-errors`, but some examples are: from telethon import errors try: - print(client.get_messages(chat)[0].text) + messages = await client.get_messages(chat) + print(messages[0].text) except errors.FloodWaitError as e: print('Have to sleep', e.seconds, 'seconds') time.sleep(e.seconds) diff --git a/readthedocs/concepts/full-api.rst b/readthedocs/concepts/full-api.rst index 0c3119fb..f0084a13 100644 --- a/readthedocs/concepts/full-api.rst +++ b/readthedocs/concepts/full-api.rst @@ -78,8 +78,17 @@ Or we call `client.get_input_entity() .. code-block:: python - import telethon.sync - peer = client.get_input_entity('someone') + import telethon + + async def main(): + peer = await client.get_input_entity('someone') + + client.loop.run_until_complete(main()) + +.. note:: + + Remember that ``await`` must occur inside an ``async def``. + Every full API example assumes you already know and do this. When you're going to invoke an API method, most require you to pass an @@ -92,7 +101,7 @@ instead: .. code-block:: python - entity = client.get_entity('someone') + entity = await client.get_entity('someone') In the later case, when you use the entity, the library will cast it to its "input" version for you. If you already have the complete user and @@ -120,7 +129,7 @@ request we do: .. code-block:: python - result = client(SendMessageRequest(peer, 'Hello there!')) + result = await client(SendMessageRequest(peer, 'Hello there!')) Message sent! Of course, this is only an example. There are over 250 methods available as of layer 80, and you can use every single of them @@ -128,8 +137,8 @@ as you wish. Remember to use the right types! To sum up: .. code-block:: python - result = client(SendMessageRequest( - client.get_input_entity('username'), 'Hello there!' + result = await client(SendMessageRequest( + await client.get_input_entity('username'), 'Hello there!' )) @@ -137,9 +146,9 @@ This can further be simplified to: .. code-block:: python - result = client(SendMessageRequest('username', 'Hello there!')) + result = await client(SendMessageRequest('username', 'Hello there!')) # Or even - result = client(SendMessageRequest(PeerChannel(id), 'Hello there!')) + result = await client(SendMessageRequest(PeerChannel(id), 'Hello there!')) .. note:: @@ -195,7 +204,7 @@ knows all requests directly: .. code-block:: python - client([ + await client([ SendMessageRequest('me', 'Hello'), SendMessageRequest('me', ', '), SendMessageRequest('me', 'World'), @@ -212,7 +221,7 @@ and still access the successful results: from telethon.errors import MultiError try: - client([ + await client([ SendMessageRequest('me', 'Hello'), SendMessageRequest('me', ''), SendMessageRequest('me', 'World') diff --git a/readthedocs/concepts/sessions.rst b/readthedocs/concepts/sessions.rst index d46996bb..03c3cbf0 100644 --- a/readthedocs/concepts/sessions.rst +++ b/readthedocs/concepts/sessions.rst @@ -154,7 +154,7 @@ you can save it in a variable directly: string = '1aaNk8EX-YRfwoRsebUkugFvht6DUPi_Q25UOCzOAqzc...' with TelegramClient(StringSession(string), api_id, api_hash) as client: - client.send_message('me', 'Hi') + client.loop.run_until_complete(client.send_message('me', 'Hi')) These strings are really convenient for using in places like Heroku since diff --git a/readthedocs/concepts/strings.rst b/readthedocs/concepts/strings.rst index c88d6940..c0687fd9 100644 --- a/readthedocs/concepts/strings.rst +++ b/readthedocs/concepts/strings.rst @@ -8,7 +8,7 @@ does a result have? Well, the easiest thing to do is printing it: .. code-block:: python - user = client.get_entity('Lonami') + user = await client.get_entity('Lonami') print(user) That will show a huge **string** similar to the following: diff --git a/readthedocs/examples/chats-and-channels.rst b/readthedocs/examples/chats-and-channels.rst index 047ec8af..5d9de180 100644 --- a/readthedocs/examples/chats-and-channels.rst +++ b/readthedocs/examples/chats-and-channels.rst @@ -27,11 +27,11 @@ to, you can make use of the :tl:`JoinChannelRequest` to join such channel: .. code-block:: python from telethon.tl.functions.channels import JoinChannelRequest - client(JoinChannelRequest(channel)) + await client(JoinChannelRequest(channel)) # In the same way, you can also leave such channel from telethon.tl.functions.channels import LeaveChannelRequest - client(LeaveChannelRequest(input_channel)) + await client(LeaveChannelRequest(input_channel)) For more on channels, check the `channels namespace`__. @@ -53,7 +53,7 @@ example, is the ``hash`` of the chat or channel. Now you can use .. code-block:: python from telethon.tl.functions.messages import ImportChatInviteRequest - updates = client(ImportChatInviteRequest('AAAAAEHbEkejzxUjAUCfYg')) + updates = await client(ImportChatInviteRequest('AAAAAEHbEkejzxUjAUCfYg')) Adding someone else to such chat or channel @@ -70,7 +70,7 @@ use is very straightforward, or :tl:`InviteToChannelRequest` for channels: # Note that ``user_to_add`` is NOT the name of the parameter. # It's the user you want to add (``user_id=user_to_add``). - client(AddChatUserRequest( + await client(AddChatUserRequest( chat_id, user_to_add, fwd_limit=10 # Allow the user to see the 10 last messages @@ -79,7 +79,7 @@ use is very straightforward, or :tl:`InviteToChannelRequest` for channels: # For channels (which includes megagroups) from telethon.tl.functions.channels import InviteToChannelRequest - client(InviteToChannelRequest( + await client(InviteToChannelRequest( channel, [users_to_add] )) @@ -127,7 +127,7 @@ Giving or revoking admin permissions can be done with the :tl:`EditAdminRequest` # ) # Once you have a ChatAdminRights, invoke it - client(EditAdminRequest(channel, user, rights)) + await client(EditAdminRequest(channel, user, rights)) # User will now be able to change group info, delete other people's # messages and pin messages. @@ -135,7 +135,7 @@ Giving or revoking admin permissions can be done with the :tl:`EditAdminRequest` # In a normal chat, you should do this instead: from telethon.tl.functions.messages import EditChatAdminRequest - client(EditChatAdminRequest(chat_id, user, is_admin=True)) + await client(EditChatAdminRequest(chat_id, user, is_admin=True)) @@ -192,7 +192,7 @@ banned rights of a user through :tl:`EditBannedRequest` and its parameter embed_links=True ) - client(EditBannedRequest(channel, user, rights)) + await client(EditBannedRequest(channel, user, rights)) You can use a `datetime.datetime` object for ``until_date=``, @@ -214,7 +214,7 @@ is enough: from telethon.tl.functions.channels import EditBannedRequest from telethon.tl.types import ChatBannedRights - client(EditBannedRequest( + await client(EditBannedRequest( channel, user, ChatBannedRights( until_date=None, view_messages=True @@ -240,7 +240,7 @@ use :tl:`GetMessagesViewsRequest`, setting ``increment=True``: # Obtain `channel' through dialogs or through client.get_entity() or anyhow. # Obtain `msg_ids' through `.get_messages()` or anyhow. Must be a list. - client(GetMessagesViewsRequest( + await client(GetMessagesViewsRequest( peer=channel, id=msg_ids, increment=True diff --git a/readthedocs/examples/users.rst b/readthedocs/examples/users.rst index 130bd7ae..89c7bd58 100644 --- a/readthedocs/examples/users.rst +++ b/readthedocs/examples/users.rst @@ -21,9 +21,9 @@ you should use :tl:`GetFullUser`: from telethon.tl.functions.users import GetFullUserRequest - full = client(GetFullUserRequest(user)) + full = await client(GetFullUserRequest(user)) # or even - full = client(GetFullUserRequest('username')) + full = await client(GetFullUserRequest('username')) bio = full.about @@ -41,7 +41,7 @@ request. Omitted fields won't change after invoking :tl:`UpdateProfile`: from telethon.tl.functions.account import UpdateProfileRequest - client(UpdateProfileRequest( + await client(UpdateProfileRequest( about='This is a test from Telethon' )) @@ -55,7 +55,7 @@ You need to use :tl:`account.UpdateUsername`: from telethon.tl.functions.account import UpdateUsernameRequest - client(UpdateUsernameRequest('new_username')) + await client(UpdateUsernameRequest('new_username')) Updating your profile photo @@ -69,6 +69,6 @@ through :tl:`UploadProfilePhoto`: from telethon.tl.functions.photos import UploadProfilePhotoRequest - client(UploadProfilePhotoRequest( + await client(UploadProfilePhotoRequest( client.upload_file('/path/to/some/file') ))) diff --git a/readthedocs/examples/working-with-messages.rst b/readthedocs/examples/working-with-messages.rst index e08e4cf0..bf51f260 100644 --- a/readthedocs/examples/working-with-messages.rst +++ b/readthedocs/examples/working-with-messages.rst @@ -24,7 +24,7 @@ send yourself the very first sticker you have: # Get all the sticker sets this user has from telethon.tl.functions.messages import GetAllStickersRequest - sticker_sets = client(GetAllStickersRequest(0)) + sticker_sets = await client(GetAllStickersRequest(0)) # Choose a sticker set from telethon.tl.functions.messages import GetStickerSetRequest @@ -32,14 +32,14 @@ send yourself the very first sticker you have: sticker_set = sticker_sets.sets[0] # Get the stickers for this sticker set - stickers = client(GetStickerSetRequest( + stickers = await client(GetStickerSetRequest( stickerset=InputStickerSetID( id=sticker_set.id, access_hash=sticker_set.access_hash ) )) # Stickers are nothing more than files, so send that - client.send_file('me', stickers.documents[0]) + await client.send_file('me', stickers.documents[0]) .. _issues: https://github.com/LonamiWebs/Telethon/issues/215 diff --git a/readthedocs/modules/client.rst b/readthedocs/modules/client.rst index 6639200f..de5502c9 100644 --- a/readthedocs/modules/client.rst +++ b/readthedocs/modules/client.rst @@ -14,15 +14,16 @@ Each mixin has its own methods, which you all can use. .. code-block:: python - import asyncio from telethon import TelegramClient + client = TelegramClient(name, api_id, api_hash) + async def main(): - client = await TelegramClient(name, api_id, api_hash).start() # Now you can use all client methods listed below, like for example... await client.send_message('me', 'Hello to myself!') - asyncio.get_event_loop().run_until_complete(main()) + with client: + client.loop.run_until_complete(main()) You **don't** need to import these `AuthMethods`, `MessageMethods`, etc. diff --git a/readthedocs/quick-references/faq.rst b/readthedocs/quick-references/faq.rst index 37221127..df267b86 100644 --- a/readthedocs/quick-references/faq.rst +++ b/readthedocs/quick-references/faq.rst @@ -50,7 +50,7 @@ And except them as such: .. code-block:: python try: - client.send_message(chat, 'Hi') + await client.send_message(chat, 'Hi') except errors.FloodWaitError as e: # e.seconds is how many seconds you have # to wait before making the request again. @@ -98,11 +98,11 @@ This is basic Python knowledge. You should use the dot operator: .. code-block:: python - me = client.get_me() + me = await client.get_me() print(me.username) # ^ we used the dot operator to access the username attribute - result = client(functions.photos.GetUserPhotosRequest( + result = await client(functions.photos.GetUserPhotosRequest( user_id='me', offset=0, max_id=0, diff --git a/telethon/client/account.py b/telethon/client/account.py index e21974c1..d82235b6 100644 --- a/telethon/client/account.py +++ b/telethon/client/account.py @@ -190,11 +190,11 @@ class AccountMethods: from telethon import errors try: - with client.takeout() as takeout: - client.get_messages('me') # normal call - takeout.get_messages('me') # wrapped through takeout (less limits) + async with client.takeout() as takeout: + await client.get_messages('me') # normal call + await takeout.get_messages('me') # wrapped through takeout (less limits) - for message in takeout.iter_messages(chat, wait_time=0): + async for message in takeout.iter_messages(chat, wait_time=0): ... # Do something with the message except errors.TakeoutInitDelayError as e: @@ -233,7 +233,7 @@ class AccountMethods: Example .. code-block:: python - client.end_takeout(success=False) + await client.end_takeout(success=False) """ try: async with _TakeoutClient(True, self, None) as takeout: diff --git a/telethon/client/auth.py b/telethon/client/auth.py index 0ba7225d..53d7200b 100644 --- a/telethon/client/auth.py +++ b/telethon/client/auth.py @@ -89,10 +89,10 @@ class AuthMethods: client = TelegramClient('anon', api_id, api_hash) # Starting as a bot account - client.start(bot_token=bot_token) + await client.start(bot_token=bot_token) # Starting as an user account - client.start(phone) + await client.start(phone) # Please enter the code you received: 12345 # Please enter your password: ******* # (You are now logged in) @@ -306,10 +306,10 @@ class AuthMethods: .. code-block:: python phone = '+34 123 123 123' - client.sign_in(phone) # send code + await client.sign_in(phone) # send code code = input('enter code: ') - client.sign_in(phone, code) + await client.sign_in(phone, code) """ me = await self.get_me() if me: @@ -388,10 +388,10 @@ class AuthMethods: .. code-block:: python phone = '+34 123 123 123' - client.send_code_request(phone) + await client.send_code_request(phone) code = input('enter code: ') - client.sign_up(code, first_name='Anna', last_name='Banana') + await client.sign_up(code, first_name='Anna', last_name='Banana') """ me = await self.get_me() if me: @@ -456,7 +456,7 @@ class AuthMethods: .. code-block:: python phone = '+34 123 123 123' - sent = client.send_code_request(phone) + sent = await client.send_code_request(phone) print(sent) if sent.phone_registered: @@ -501,7 +501,7 @@ class AuthMethods: .. code-block:: python # Note: you will need to login again! - client.log_out() + await client.log_out() """ try: await self(functions.auth.LogOutRequest()) @@ -573,10 +573,10 @@ class AuthMethods: .. code-block:: python # Setting a password for your account which didn't have - client.edit_2fa(new_password='I_<3_Telethon') + await client.edit_2fa(new_password='I_<3_Telethon') # Removing the password - client.edit_2fa(current_password='I_<3_Telethon') + await client.edit_2fa(current_password='I_<3_Telethon') """ if new_password is None and current_password is None: return False diff --git a/telethon/client/bots.py b/telethon/client/bots.py index 045825b3..8345e48f 100644 --- a/telethon/client/bots.py +++ b/telethon/client/bots.py @@ -40,10 +40,10 @@ class BotMethods: .. code-block:: python # Make an inline query to @like - results = client.inline_query('like', 'Do you like Telethon?') + results = await client.inline_query('like', 'Do you like Telethon?') # Send the first result to some chat - message = results[0].click('TelethonOffTopic') + message = await results[0].click('TelethonOffTopic') """ bot = await self.get_input_entity(bot) result = await self(functions.messages.GetInlineBotResultsRequest( diff --git a/telethon/client/buttons.py b/telethon/client/buttons.py index aa6b7fa1..c134e838 100644 --- a/telethon/client/buttons.py +++ b/telethon/client/buttons.py @@ -35,7 +35,7 @@ class ButtonMethods: from telethon import Button markup = client.build_reply_markup(Button.inline('hi')) - client.send_message('click me', buttons=markup) + await client.send_message('click me', buttons=markup) """ if buttons is None: return None diff --git a/telethon/client/chats.py b/telethon/client/chats.py index d2313eee..16d6976d 100644 --- a/telethon/client/chats.py +++ b/telethon/client/chats.py @@ -406,16 +406,16 @@ class ChatMethods: .. code-block:: python # Show all user IDs in a chat - for user in client.iter_participants(chat): + async for user in client.iter_participants(chat): print(user.id) # Search by name - for user in client.iter_participants(chat, search='name'): + async for user in client.iter_participants(chat, search='name'): print(user.username) # Filter by admins from telethon.tl.types import ChannelParticipantsAdmins - for user in client.iter_participants(chat, filter=ChannelParticipantsAdmins): + async for user in client.iter_participants(chat, filter=ChannelParticipantsAdmins): print(user.first_name) """ return _ParticipantsIter( @@ -438,7 +438,7 @@ class ChatMethods: Example .. code-block:: python - users = client.get_participants(chat) + users = await client.get_participants(chat) print(users[0].first_name) for user in users: @@ -562,7 +562,7 @@ class ChatMethods: Example .. code-block:: python - for event in client.iter_admin_log(channel): + async for event in client.iter_admin_log(channel): if event.changed_title: print('The title changed from', event.old, 'to', event.new) """ @@ -601,7 +601,7 @@ class ChatMethods: .. code-block:: python # Get a list of deleted message events which said "heck" - events = client.get_admin_log(channel, search='heck', delete=True) + events = await client.get_admin_log(channel, search='heck', delete=True) # Print the old message before it was deleted print(events[0].old) @@ -643,8 +643,8 @@ class ChatMethods: .. code-block:: python # Download all the profile photos of some user - for photo in client.iter_profile_photos(user): - client.download_media(photo) + async for photo in client.iter_profile_photos(user): + await client.download_media(photo) """ return _ProfilePhotoIter( self, @@ -666,10 +666,10 @@ class ChatMethods: .. code-block:: python # Get the photos of a channel - photos = client.get_profile_photos(channel) + photos = await client.get_profile_photos(channel) # Download the oldest photo - client.download_media(photos[-1]) + await client.download_media(photos[-1]) """ return await self.iter_profile_photos(*args, **kwargs).collect() @@ -746,7 +746,7 @@ class ChatMethods: # Upload a document, showing its progress (most clients ignore this) async with client.action(chat, 'document') as action: - client.send_file(chat, zip_file, progress_callback=action.progress) + await client.send_file(chat, zip_file, progress_callback=action.progress) """ if isinstance(action, str): try: @@ -841,10 +841,10 @@ class ChatMethods: .. code-block:: python # Allowing `user` to pin messages in `chat` - client.edit_admin(chat, user, pin_messages=True) + await client.edit_admin(chat, user, pin_messages=True) # Granting all permissions except for `add_admins` - client.edit_admin(chat, user, is_admin=True, add_admins=False) + await client.edit_admin(chat, user, is_admin=True, add_admins=False) """ entity = await self.get_input_entity(entity) user = await self.get_input_entity(user) @@ -978,15 +978,15 @@ class ChatMethods: from datetime import timedelta # Banning `user` from `chat` for 1 minute - client.edit_permissions(chat, user, timedelta(minutes=1), - view_messages=False) + await client.edit_permissions(chat, user, timedelta(minutes=1), + view_messages=False) # Banning `user` from `chat` forever - client.edit_permissions(chat, user, view_messages=False) + await client.edit_permissions(chat, user, view_messages=False) # Kicking someone (ban + un-ban) - client.edit_permissions(chat, user, view_messages=False) - client.edit_permissions(chat, user) + await client.edit_permissions(chat, user, view_messages=False) + await client.edit_permissions(chat, user) """ entity = await self.get_input_entity(entity) if not isinstance(entity, types.InputPeerChannel): @@ -1053,10 +1053,10 @@ class ChatMethods: .. code-block:: python # Kick some user from some chat - client.kick_participant(chat, user) + await client.kick_participant(chat, user) # Leaving chat - client.kick_participant(chat, 'me') + await client.kick_participant(chat, 'me') """ entity = await self.get_input_entity(entity) user = await self.get_input_entity(user) diff --git a/telethon/client/dialogs.py b/telethon/client/dialogs.py index 9a209ec2..d7aa5d16 100644 --- a/telethon/client/dialogs.py +++ b/telethon/client/dialogs.py @@ -205,7 +205,7 @@ class DialogMethods: .. code-block:: python # Print all dialog IDs and the title, nicely formatted - for dialog in client.iter_dialogs(): + async for dialog in client.iter_dialogs(): print('{:>14}: {}'.format(dialog.id, dialog.title)) """ if archived is not None: @@ -231,20 +231,20 @@ class DialogMethods: .. code-block:: python # Get all open conversation, print the title of the first - dialogs = client.get_dialogs() + dialogs = await client.get_dialogs() first = dialogs[0] print(first.title) # Use the dialog somewhere else - client.send_message(first, 'hi') + await client.send_message(first, 'hi') # Getting only non-archived dialogs (both equivalent) - non_archived = client.get_dialogs(folder=0) - non_archived = client.get_dialogs(archived=False) + non_archived = await client.get_dialogs(folder=0) + non_archived = await client.get_dialogs(archived=False) # Getting only archived dialogs (both equivalent) - archived = client.get_dialogs(folder=1) - non_archived = client.get_dialogs(archived=True) + archived = await client.get_dialogs(folder=1) + non_archived = await client.get_dialogs(archived=True) """ return await self.iter_dialogs(*args, **kwargs).collect() @@ -269,11 +269,11 @@ class DialogMethods: .. code-block:: python # Clear all drafts - for draft in client.get_drafts(): - draft.delete() + async for draft in client.get_drafts(): + await draft.delete() # Getting the drafts with 'bot1' and 'bot2' - for draft in client.iter_drafts(['bot1', 'bot2']): + async for draft in client.iter_drafts(['bot1', 'bot2']): print(draft.text) """ if entity and not utils.is_list_like(entity): @@ -293,11 +293,11 @@ class DialogMethods: .. code-block:: python # Get drafts, print the text of the first - drafts = client.get_drafts() + drafts = await client.get_drafts() print(drafts[0].text) # Get the draft in your chat - draft = client.get_drafts('me') + draft = await client.get_drafts('me') print(drafts.text) """ items = await self.iter_drafts(entity).collect() @@ -350,18 +350,18 @@ class DialogMethods: .. code-block:: python # Archiving the first 5 dialogs - dialogs = client.get_dialogs(5) - client.edit_folder(dialogs, 1) + dialogs = await client.get_dialogs(5) + await client.edit_folder(dialogs, 1) # Un-archiving the third dialog (archiving to folder 0) - client.edit_folder(dialog[2], 0) + await client.edit_folder(dialog[2], 0) # Moving the first dialog to folder 0 and the second to 1 - dialogs = client.get_dialogs(2) - client.edit_folder(dialogs, [0, 1]) + dialogs = await client.get_dialogs(2) + await client.edit_folder(dialogs, [0, 1]) # Un-archiving all dialogs - client.archive(unpack=1) + await client.archive(unpack=1) """ if (entity is None) == (unpack is None): raise ValueError('You can only set either entities or unpack, not both') @@ -419,11 +419,11 @@ class DialogMethods: .. code-block:: python # Deleting the first dialog - dialogs = client.get_dialogs(5) - client.delete_dialog(dialogs[0]) + dialogs = await client.get_dialogs(5) + await client.delete_dialog(dialogs[0]) # Leaving a channel by username - client.delete_dialog('username') + await client.delete_dialog('username') """ entity = await self.get_input_entity(entity) if isinstance(entity, types.InputPeerChannel): diff --git a/telethon/client/downloads.py b/telethon/client/downloads.py index 5b73cedb..47ca3fc1 100644 --- a/telethon/client/downloads.py +++ b/telethon/client/downloads.py @@ -196,7 +196,7 @@ class DownloadMethods: .. code-block:: python # Download your own profile photo - path = client.download_profile_photo('me') + path = await client.download_profile_photo('me') print(path) """ # hex(crc32(x.encode('ascii'))) for x in @@ -322,11 +322,11 @@ class DownloadMethods: Example .. code-block:: python - path = client.download_media(message) - client.download_media(message, filename) + path = await client.download_media(message) + await client.download_media(message, filename) # or - path = message.download_media() - message.download_media(filename) + path = await message.download_media() + await message.download_media(filename) """ # TODO This won't work for messageService if isinstance(message, types.Message): @@ -406,7 +406,7 @@ class DownloadMethods: .. code-block:: python # Download a file and print its header - data = client.download_file(input_file, bytes) + data = await client.download_file(input_file, bytes) print(data[:16]) """ if not part_size_kb: @@ -531,11 +531,14 @@ class DownloadMethods: # Streaming `media` to an output file # After the iteration ends, the sender is cleaned up with open('photo.jpg', 'wb') as fd: - for chunk client.iter_download(media): + async for chunk client.iter_download(media): fd.write(chunk) # Fetching only the header of a file (32 bytes) # You should manually close the iterator in this case. + # + # telethon.sync must be imported for this to work, + # and you must not be inside an "async def". stream = client.iter_download(media, request_size=32) header = next(stream) stream.close() diff --git a/telethon/client/messages.py b/telethon/client/messages.py index 6c8d740c..29f1713a 100644 --- a/telethon/client/messages.py +++ b/telethon/client/messages.py @@ -421,24 +421,24 @@ class MessageMethods: .. code-block:: python # From most-recent to oldest - for message in client.iter_messages(chat): + async for message in client.iter_messages(chat): print(message.id, message.text) # From oldest to most-recent - for message in client.iter_messages(chat, reverse=True): + async for message in client.iter_messages(chat, reverse=True): print(message.id, message.text) # Filter by sender - for message in client.iter_messages(chat, from_user='me'): + async for message in client.iter_messages(chat, from_user='me'): print(message.text) # Server-side search with fuzzy text - for message in client.iter_messages(chat, search='hello'): + async for message in client.iter_messages(chat, search='hello'): print(message.id) # Filter by message type: from telethon.tl.types import InputMessagesFilterPhotos - for message in client.iter_messages(chat, filter=InputMessagesFilterPhotos): + async for message in client.iter_messages(chat, filter=InputMessagesFilterPhotos): print(message.photo) """ if ids is not None: @@ -482,14 +482,14 @@ class MessageMethods: # Get 0 photos and print the total to show how many photos there are from telethon.tl.types import InputMessagesFilterPhotos - photos = client.get_messages(chat, 0, filter=InputMessagesFilterPhotos) + photos = await client.get_messages(chat, 0, filter=InputMessagesFilterPhotos) print(photos.total) # Get all the photos - photos = client.get_messages(chat, None, filter=InputMessagesFilterPhotos) + photos = await client.get_messages(chat, None, filter=InputMessagesFilterPhotos) # Get messages by ID: - message_1337 = client.get_messages(chats, ids=1337) + message_1337 = await client.get_messages(chats, ids=1337) """ if len(args) == 1 and 'limit' not in kwargs: if 'min_id' in kwargs and 'max_id' in kwargs: @@ -605,25 +605,25 @@ class MessageMethods: .. code-block:: python # Markdown is the default - client.send_message('lonami', 'Thanks for the **Telethon** library!') + await client.send_message('lonami', 'Thanks for the **Telethon** library!') # Default to another parse mode client.parse_mode = 'html' - client.send_message('me', 'Some bold and italic text') - client.send_message('me', 'An URL') + await client.send_message('me', 'Some bold and italic text') + await client.send_message('me', 'An URL') # code and pre tags also work, but those break the documentation :) - client.send_message('me', 'Mentions') + await client.send_message('me', 'Mentions') # Explicit parse mode # No parse mode by default client.parse_mode = None # ...but here I want markdown - client.send_message('me', 'Hello, **world**!', parse_mode='md') + await client.send_message('me', 'Hello, **world**!', parse_mode='md') # ...and here I need HTML - client.send_message('me', 'Hello, world!', parse_mode='html') + await client.send_message('me', 'Hello, world!', parse_mode='html') # If you logged in as a bot account, you can send buttons from telethon import events, Button @@ -633,25 +633,25 @@ class MessageMethods: await event.edit('Thank you for clicking {}!'.format(event.data)) # Single inline button - client.send_message(chat, 'A single button, with "clk1" as data', - buttons=Button.inline('Click me', b'clk1')) + await client.send_message(chat, 'A single button, with "clk1" as data', + buttons=Button.inline('Click me', b'clk1')) # Matrix of inline buttons - client.send_message(chat, 'Pick one from this grid', buttons=[ + await client.send_message(chat, 'Pick one from this grid', buttons=[ [Button.inline('Left'), Button.inline('Right')], [Button.url('Check this site!', 'https://lonamiwebs.github.io')] ]) # Reply keyboard - client.send_message(chat, 'Welcome', buttons=[ + await client.send_message(chat, 'Welcome', buttons=[ Button.text('Thanks!', resize=True, single_use=True), Button.request_phone('Send phone'), Button.request_location('Send location') ]) # Forcing replies or clearing buttons. - client.send_message(chat, 'Reply to me', buttons=Button.force_reply()) - client.send_message(chat, 'Bye Keyboard!', buttons=Button.clear()) + await client.send_message(chat, 'Reply to me', buttons=Button.force_reply()) + await client.send_message(chat, 'Bye Keyboard!', buttons=Button.clear()) """ if file is not None: return await self.send_file( @@ -788,19 +788,19 @@ class MessageMethods: .. code-block:: python # a single one - client.forward_messages(chat, message) + await client.forward_messages(chat, message) # or - client.forward_messages(chat, message_id, from_chat) + await client.forward_messages(chat, message_id, from_chat) # or - message.forward_to(chat) + await message.forward_to(chat) # multiple - client.forward_messages(chat, messages) + await client.forward_messages(chat, messages) # or - client.forward_messages(chat, message_ids, from_chat) + await client.forward_messages(chat, message_ids, from_chat) # Forwarding as a copy - client.send_message(chat, message) + await client.send_message(chat, message) """ single = not utils.is_list_like(messages) if single: @@ -945,13 +945,13 @@ class MessageMethods: Example .. code-block:: python - message = client.send_message(chat, 'hello') + message = await client.send_message(chat, 'hello') - client.edit_message(chat, message, 'hello!') + await client.edit_message(chat, message, 'hello!') # or - client.edit_message(chat, message.id, 'hello!!') + await client.edit_message(chat, message.id, 'hello!!') # or - client.edit_message(message, 'hello!!!') + await client.edit_message(message, 'hello!!!') """ if isinstance(entity, types.InputBotInlineMessageID): text = message @@ -1036,7 +1036,7 @@ class MessageMethods: Example .. code-block:: python - client.delete_messages(chat, messages) + await client.delete_messages(chat, messages) """ if not utils.is_list_like(message_ids): message_ids = (message_ids,) @@ -1097,11 +1097,11 @@ class MessageMethods: .. code-block:: python # using a Message object - client.send_read_acknowledge(chat, message) + await client.send_read_acknowledge(chat, message) # ...or using the int ID of a Message - client.send_read_acknowledge(chat, message_id) + await client.send_read_acknowledge(chat, message_id) # ...or passing a list of messages to mark as read - client.send_read_acknowledge(chat, messages) + await client.send_read_acknowledge(chat, messages) """ if max_id is None: if not message: @@ -1158,8 +1158,8 @@ class MessageMethods: .. code-block:: python # Send and pin a message to annoy everyone - message = client.send_message(chat, 'Pinotifying is fun!') - client.pin_message(chat, message, notify=True) + message = await client.send_message(chat, 'Pinotifying is fun!') + await client.pin_message(chat, message, notify=True) """ if not message: message = 0 diff --git a/telethon/client/telegrambaseclient.py b/telethon/client/telegrambaseclient.py index 6d09344d..d3abbc3c 100644 --- a/telethon/client/telegrambaseclient.py +++ b/telethon/client/telegrambaseclient.py @@ -401,7 +401,7 @@ class TelegramBaseClient(abc.ABC): .. code-block:: python try: - client.connect() + await client.connect() except OSError: print('Failed to connect') """ @@ -451,7 +451,7 @@ class TelegramBaseClient(abc.ABC): .. code-block:: python # You don't need to use this if you used "with client" - client.disconnect() + await client.disconnect() """ if self._loop.is_running(): return self._disconnect_coro() diff --git a/telethon/client/updates.py b/telethon/client/updates.py index 1d8d0637..2a9dee95 100644 --- a/telethon/client/updates.py +++ b/telethon/client/updates.py @@ -59,7 +59,7 @@ class UpdateMethods: # # You will still receive updates, since this prevents the # script from exiting. - client.run_until_disconnected() + await client.run_until_disconnected() """ if self.loop.is_running(): return self._run_until_disconnected() @@ -218,7 +218,7 @@ class UpdateMethods: Example .. code-block:: python - client.catch_up() + await client.catch_up() """ pts, date = self._state_cache[None] if not pts: diff --git a/telethon/client/uploads.py b/telethon/client/uploads.py index 00d932c9..ed5ad343 100644 --- a/telethon/client/uploads.py +++ b/telethon/client/uploads.py @@ -237,22 +237,22 @@ class UploadMethods: .. code-block:: python # Normal files like photos - client.send_file(chat, '/my/photos/me.jpg', caption="It's me!") + await client.send_file(chat, '/my/photos/me.jpg', caption="It's me!") # or - client.send_message(chat, "It's me!", file='/my/photos/me.jpg') + await client.send_message(chat, "It's me!", file='/my/photos/me.jpg') # Voice notes or round videos - client.send_file(chat, '/my/songs/song.mp3', voice_note=True) - client.send_file(chat, '/my/videos/video.mp4', video_note=True) + await client.send_file(chat, '/my/songs/song.mp3', voice_note=True) + await client.send_file(chat, '/my/videos/video.mp4', video_note=True) # Custom thumbnails - client.send_file(chat, '/my/documents/doc.txt', thumb='photo.jpg') + await client.send_file(chat, '/my/documents/doc.txt', thumb='photo.jpg') # Only documents - client.send_file(chat, '/my/photos/photo.png', force_document=True) + await client.send_file(chat, '/my/photos/photo.png', force_document=True) # Albums - client.send_file(chat, [ + await client.send_file(chat, [ '/my/photos/holiday1.jpg', '/my/photos/holiday2.jpg', '/my/drawings/portrait.png' @@ -467,17 +467,17 @@ class UploadMethods: .. code-block:: python # Photos as photo and document - file = client.upload_file('photo.jpg') - client.send_file(chat, file) # sends as photo - client.send_file(chat, file, force_document=True) # sends as document + file = await client.upload_file('photo.jpg') + await client.send_file(chat, file) # sends as photo + await client.send_file(chat, file, force_document=True) # sends as document file.name = 'not a photo.jpg' - client.send_file(chat, file, force_document=True) # document, new name + await client.send_file(chat, file, force_document=True) # document, new name # As song or as voice note - file = client.upload_file('song.ogg') - client.send_file(chat, file) # sends as song - client.send_file(chat, file, voice_note=True) # sends as voice note + file = await client.upload_file('song.ogg') + await client.send_file(chat, file) # sends as song + await client.send_file(chat, file, voice_note=True) # sends as voice note """ if isinstance(file, (types.InputFile, types.InputFileBig)): return file # Already uploaded diff --git a/telethon/client/users.py b/telethon/client/users.py index 6630518f..685f6134 100644 --- a/telethon/client/users.py +++ b/telethon/client/users.py @@ -128,7 +128,8 @@ class UserMethods: Example .. code-block:: python - print(client.get_me().username) + me = await client.get_me() + print(me.username) """ if input_peer and self._self_input_peer: return self._self_input_peer @@ -154,7 +155,7 @@ class UserMethods: Example .. code-block:: python - if client.is_bot(): + if await client.is_bot(): print('Beep') else: print('Hello') @@ -171,10 +172,10 @@ class UserMethods: Example .. code-block:: python - if not client.is_user_authorized(): - client.send_code_request(phone) + if not await client.is_user_authorized(): + await client.send_code_request(phone) code = input('enter code: ') - client.sign_in(phone, code) + await client.sign_in(phone, code) """ if self._authorized is None: try: @@ -227,20 +228,20 @@ class UserMethods: from telethon import utils - me = client.get_entity('me') + me = await client.get_entity('me') print(utils.get_display_name(me)) - chat = client.get_input_entity('username') - for message in client.iter_messages(chat): + chat = await client.get_input_entity('username') + async for message in client.iter_messages(chat): ... # Note that you could have used the username directly, but it's # good to use get_input_entity if you will reuse it a lot. - for message in client.iter_messages('username'): + async for message in client.iter_messages('username'): ... # Note that for this to work the phone number must be in your contacts - some_id = client.get_peer_id('+34123456789') + some_id = await client.get_peer_id('+34123456789') """ single = not utils.is_list_like(entity) if single: @@ -360,10 +361,10 @@ class UserMethods: # If you're going to use "username" often in your code # (make a lot of calls), consider getting its input entity # once, and then using the "user" everywhere instead. - user = client.get_input_entity('username') + user = await client.get_input_entity('username') # The same applies to IDs, chats or channels. - chat = client.get_input_entity(-123456789) + chat = await client.get_input_entity(-123456789) """ # Short-circuit if the input parameter directly maps to an InputPeer try: @@ -444,7 +445,7 @@ class UserMethods: Example .. code-block:: python - print(client.get_peer_id('me')) + print(await client.get_peer_id('me')) """ if isinstance(peer, int): return utils.get_peer_id(peer, add_mark=add_mark) diff --git a/telethon/helpers.py b/telethon/helpers.py index f3f8c044..0fdafbf9 100644 --- a/telethon/helpers.py +++ b/telethon/helpers.py @@ -167,7 +167,7 @@ class TotalList(list): # Telethon returns these lists in some cases (for example, # only when a chunk is returned, but the "total" count # is available). - result = client.get_messages(chat, limit=10) + result = await client.get_messages(chat, limit=10) print(result.total) # large number print(len(result)) # 10 diff --git a/telethon/tl/custom/message.py b/telethon/tl/custom/message.py index 9e9ba2f9..e15b36a7 100644 --- a/telethon/tl/custom/message.py +++ b/telethon/tl/custom/message.py @@ -762,7 +762,7 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC): >>> # [button1] [button2] >>> # [ button3 ] >>> # [button4] [button5] - >>> message.click(2) # index + >>> await message.click(2) # index j (`int`): Clicks the button at position (i, j), these being the @@ -772,7 +772,7 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC): >>> # [button1] [button2] >>> # [ button3 ] >>> # [button4] [button5] - >>> message.click(0, 1) # (row, column) + >>> await message.click(0, 1) # (row, column) This is equivalent to ``message.buttons[0][1].click()``. @@ -799,16 +799,16 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC): .. code-block:: python # Click the first button - message.click(0) + await message.click(0) # Click some row/column - message.click(row, column) + await message.click(row, column) # Click by text - message.click(text='👍') + await message.click(text='👍') # Click by data - message.click(data=b'payload') + await message.click(data=b'payload') """ if not self._client: return diff --git a/telethon/utils.py b/telethon/utils.py index b1b27410..834ee699 100644 --- a/telethon/utils.py +++ b/telethon/utils.py @@ -1177,14 +1177,14 @@ def encode_waveform(waveform): file = 'my.ogg' # Send 'my.ogg' with a ascending-triangle waveform - client.send_file(chat, file, attributes=[types.DocumentAttributeAudio( + await client.send_file(chat, file, attributes=[types.DocumentAttributeAudio( duration=7, voice=True, waveform=utils.encode_waveform(bytes(range(2 ** 5)) # 2**5 because 5-bit )] # Send 'my.ogg' with a square waveform - client.send_file(chat, file, attributes=[types.DocumentAttributeAudio( + await client.send_file(chat, file, attributes=[types.DocumentAttributeAudio( duration=7, voice=True, waveform=utils.encode_waveform(bytes((31, 31, 15, 15, 15, 15, 31, 31)) * 4)