Telethon leads to `RuntimeWarning: coroutine 'MessageMethods.send_message' was never awaited`
Asked Answered
H

2

9

I'm trying to run this first code snippet provided by the Telethon documentation. But, after multiple problems (here and here), I ended up with this modified version:

import os
import sys
from telethon.sync import TelegramClient, events

# import nest_asyncio
# nest_asyncio.apply()

session_name = "<session_name>"
api_id = <api_id>
api_hash = "<api_hash>"


os.chdir(sys.path[0])

if f"{session_name}.session" in os.listdir():
    os.remove(f"{session_name}.session")

async with TelegramClient(session_name, api_id, api_hash) as client:
   client.send_message('me', 'Hello, myself!')
   print(client.download_profile_photo('me'))

   @client.on(events.NewMessage(pattern='(?i).*Hello'))
   async def handler(event):
      await event.reply('Hey!')

   client.run_until_disconnected()

However now I'm getting these warnings:

usr/local/lib/python3.7/site-packages/ipykernel_launcher.py:23: RuntimeWarning: coroutine 'MessageMethods.send_message' was never awaited
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
/usr/local/lib/python3.7/site-packages/ipykernel_launcher.py:24: RuntimeWarning: coroutine 'DownloadMethods.download_profile_photo' was never awaited
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
/usr/local/lib/python3.7/site-packages/ipykernel_launcher.py:30: RuntimeWarning: coroutine 'UpdateMethods._run_until_disconnected' was never awaited
RuntimeWarning: Enable tracemalloc to get the object allocation traceback

when running the code on Jupyter. Now here are my questions:

  • what those warning messages mean and how should I address them?
  • what is the expected result of this code if working properly? Should I receive a message in Telegram or something? Because I don't recive any messages other than the signin code.
  • What does the @ symbol at the beginning of the @client.on... line mean? what does that line is supposed to do? From this line onwards I do not understand the code. Would appreciate if you could help me understand it.
Habilitate answered 2/4, 2020 at 10:26 Comment(3)
When you run the code in Jupyter, Jupyter runs the asyncio event loop for you, which enables you to use await outside of async def (this is some Jupyter-specific magic).Walkabout
@Walkabout so how would you change the code above to work properly in both Python and Jupyter/IPython?Habilitate
See my answer below.Walkabout
H
7

Just add await the client.send_message('me', 'Hello, myself!') to solve that error and print afterdownload_profile_photo has done its work downloads an image to localhost so that may be why you don't see anything. You should read telethon documentation thoroughly and also how to use photo downloads correctly

All the calls to the client have a delay and should always be awaited so that your code doesn't get blocked. You should read the asyncio tutorial The correct code would be:

async with TelegramClient(session_name, api_id, api_hash) as client:
   await client.send_message('me', 'Hello, myself!')
   print(await client.download_profile_photo('me'))

   @client.on(events.NewMessage(pattern='(?i).*Hello'))
   async def handler(event):
      await event.reply('Hey!')

   #await client.run_until_disconnected()

The @ is a decorator and you should read the PEP related to decorators, but in short words, they execute a function before yours.

In this case @client.on(events.NewMessage means:

When there is a new event that happens to be a message that matches the pattern specified handle it with this function called handler

Hepzi answered 2/4, 2020 at 10:45 Comment(8)
the first three warning messages are gone except the last one RuntimeWarning: Enable tracemalloc to get the object allocation tracebackHabilitate
It's because of what Lonami told you. await client.run_until_disconnected() , i just fixed itHepzi
Thanks. I actually did try that. Although it resolves the warning message, now the interpreter goes to an infinite loop and never finishes.Habilitate
Maybe if you need just to execute it once, just remove the run_until_disconnected lineHepzi
OK, the infinite loop problem is now solved. But I'm not receiving any Hello, myself! messages anywhere. what should I expect to see? How do I know my code worked correctly?Habilitate
It should send a message to your Saved Messages in Telegram. Check it, it doesn't pop out a notification.Hepzi
OMG, yes it is there. Thanks a lot. I whish these explenations would be added here.Habilitate
Well, it's not explicit but if you are interacting with client it means you will send a message to 'me' which means that is "your personal inbox", it's not a print, there isn't any other chat that is only for you so the logical conclusion is that it's sent there. I send messages to 'me' when i retrieve Telegram ids for example :)Hepzi
W
3

Jupyter will run the asyncio event loop so that you can use async for / with / await outside of an async def. This conflicts with Telethon's .sync magic which you should try to avoid when using Jupyter, IPython, or similar.

To fix your code:

from telethon import TelegramClient, events
#            ^ note no .sync

session_name = "<session_name>"
api_id = <api_id>
api_hash = "<api_hash>"

async with TelegramClient(session_name, api_id, api_hash) as client:
    await client.send_message('me', 'Hello, myself!')
    # ^ note you need to use `await` in Jupyter
    # we are avoiding the `.sync` magic so it needs to be done by yourself

    print(await client.download_profile_photo('me'))
    #     ^ same here, needs await

    @client.on(events.NewMessage(pattern='(?i).*Hello'))
    async def handler(event):
        await event.reply('Hey!')

    await client.run_until_disconnected()
    # ^ once again needs await

If you want code to run anywhere (Jupyter, Python shell, normal run), just be sure to do everything inside async def:

import asyncio

from telethon import TelegramClient, events

session_name = "<session_name>"
api_id = <api_id>
api_hash = "<api_hash>"

async def main():
    async with TelegramClient(session_name, api_id, api_hash) as client:
       await client.send_message('me', 'Hello, myself!')
       print(await client.download_profile_photo('me'))

       @client.on(events.NewMessage(pattern='(?i).*Hello'))
       async def handler(event):
           await event.reply('Hey!')

       await client.run_until_disconnected()

# Only this line changes, the rest will work anywhere.
# Jupyter
await main()

# Otherwise
asyncio.run(main())
Walkabout answered 2/4, 2020 at 10:32 Comment(6)
I will check this. but is there a way to have a code which works both in Python shell and Jupyter/IPython similarly?Habilitate
I have updated my answer to reflect your new concerns.Walkabout
I ran the second code block and got RuntimeError: asyncio.run() cannot be called from a running event loopHabilitate
Then I'm afraid the code in Jupyter has to be slightly different, and use await main() instead.Walkabout
I actually did try that but I get lots of warning messages and the compiler never finishes!Habilitate
removing the line await client.run_until_disconnected() solved the problem.Habilitate

© 2022 - 2024 — McMap. All rights reserved.