Get rid of client.loop

Instead, use the asyncio-intended way of implicit loop.
This commit is contained in:
Lonami Exo 2022-01-16 13:51:23 +01:00
parent 6eadc8aed8
commit a62627534e
19 changed files with 140 additions and 177 deletions

View File

@ -8,14 +8,15 @@ use these if possible.
.. code-block:: python .. code-block:: python
import asyncio
from telethon import TelegramClient from telethon import TelegramClient
# Remember to use your own values from my.telegram.org! # Remember to use your own values from my.telegram.org!
api_id = 12345 api_id = 12345
api_hash = '0123456789abcdef0123456789abcdef' api_hash = '0123456789abcdef0123456789abcdef'
client = TelegramClient('anon', api_id, api_hash)
async def main(): async def main():
async with TelegramClient('anon', api_id, api_hash).start() as client:
# Getting information about yourself # Getting information about yourself
me = await client.get_me() me = await client.get_me()
@ -70,8 +71,7 @@ use these if possible.
path = await message.download_media() path = await message.download_media()
print('File saved to', path) # printed after download is done print('File saved to', path) # printed after download is done
with client: asyncio.run(main())
client.loop.run_until_complete(main())
Here, we show how to sign in, get information about yourself, send Here, we show how to sign in, get information about yourself, send
@ -100,8 +100,8 @@ proceeding. We will see all the available methods later on.
# Most of your code should go here. # Most of your code should go here.
# You can of course make and use your own async def (do_something). # 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. # They only need to be async if they need to await things.
async with client: async with client.start():
me = await client.get_me() me = await client.get_me()
await do_something(me) await do_something(me)
client.loop.run_until_complete(main()) asyncio.run(main())

View File

@ -49,6 +49,7 @@ We can finally write some code to log into our account!
.. code-block:: python .. code-block:: python
import asyncio
from telethon import TelegramClient from telethon import TelegramClient
# Use your own values from my.telegram.org # Use your own values from my.telegram.org
@ -57,10 +58,10 @@ We can finally write some code to log into our account!
async def main(): async def main():
# The first parameter is the .session file name (absolute paths allowed) # The first parameter is the .session file name (absolute paths allowed)
async with TelegramClient('anon', api_id, api_hash) as client: async with TelegramClient('anon', api_id, api_hash).start() as client:
await client.send_message('me', 'Hello, myself!') await client.send_message('me', 'Hello, myself!')
client.loop.run_until_complete(main()) asyncio.run(main())
In the first line, we import the class name so we can create an instance In the first line, we import the class name so we can create an instance
@ -98,21 +99,19 @@ You will still need an API ID and hash, but the process is very similar:
.. code-block:: python .. code-block:: python
import asyncio
from telethon import TelegramClient from telethon import TelegramClient
api_id = 12345 api_id = 12345
api_hash = '0123456789abcdef0123456789abcdef' api_hash = '0123456789abcdef0123456789abcdef'
bot_token = '12345:0123456789abcdef0123456789abcdef' bot_token = '12345:0123456789abcdef0123456789abcdef'
# We have to manually call "start" if we want an explicit bot token
bot = TelegramClient('bot', api_id, api_hash).start(bot_token=bot_token)
async def main(): async def main():
# But then we can use the client instance as usual # But then we can use the client instance as usual
async with bot: async with TelegramClient('bot', api_id, api_hash).start(bot_token=bot_token) as bot:
... ... # bot is your client
client.loop.run_until_complete(main()) asyncio.run(main())
To get a bot account, you need to talk To get a bot account, you need to talk

View File

@ -74,7 +74,7 @@ Or we call `client.get_input_entity()
async def main(): async def main():
peer = await client.get_input_entity('someone') peer = await client.get_input_entity('someone')
client.loop.run_until_complete(main()) asyncio.run(main())
.. note:: .. note::

View File

@ -156,8 +156,8 @@ you can save it in a variable directly:
.. code-block:: python .. code-block:: python
string = '1aaNk8EX-YRfwoRsebUkugFvht6DUPi_Q25UOCzOAqzc...' string = '1aaNk8EX-YRfwoRsebUkugFvht6DUPi_Q25UOCzOAqzc...'
with TelegramClient(StringSession(string), api_id, api_hash) as client: async with TelegramClient(StringSession(string), api_id, api_hash).start() as client:
client.loop.run_until_complete(client.send_message('me', 'Hi')) await client.send_message('me', 'Hi')
These strings are really convenient for using in places like Heroku since These strings are really convenient for using in places like Heroku since

View File

@ -759,3 +759,5 @@ they are meant to work on lists.
also mark read only supports single now. a list would just be max anyway. also mark read only supports single now. a list would just be max anyway.
removed max id since it's not really of much use. removed max id since it's not really of much use.
client loop has been removed. embrace implicit loop as asyncio does now

View File

@ -20,10 +20,10 @@ Each mixin has its own methods, which you all can use.
async def main(): async def main():
# Now you can use all client methods listed below, like for example... # Now you can use all client methods listed below, like for example...
async with client.start():
await client.send_message('me', 'Hello to myself!') await client.send_message('me', 'Hello to myself!')
with client: asyncio.run(main())
client.loop.run_until_complete(main())
You **don't** need to import these `AuthMethods`, `MessageMethods`, etc. You **don't** need to import these `AuthMethods`, `MessageMethods`, etc.

View File

@ -40,7 +40,7 @@ class _ChatAction:
self._chat, self._action) self._chat, self._action)
self._running = True self._running = True
self._task = self._client.loop.create_task(self._update()) self._task = asyncio.create_task(self._update())
return self return self
async def __aexit__(self, *args): async def __aexit__(self, *args):

View File

@ -88,7 +88,6 @@ def init(
app_version: str = None, app_version: str = None,
lang_code: str = 'en', lang_code: str = 'en',
system_lang_code: str = 'en', system_lang_code: str = 'en',
loop: asyncio.AbstractEventLoop = None,
base_logger: typing.Union[str, logging.Logger] = None, base_logger: typing.Union[str, logging.Logger] = None,
receive_updates: bool = True receive_updates: bool = True
): ):
@ -153,24 +152,6 @@ def init(
self.api_id = int(api_id) self.api_id = int(api_id)
self.api_hash = api_hash self.api_hash = api_hash
# Current proxy implementation requires `sock_connect`, and some
# event loops lack this method. If the current loop is missing it,
# bail out early and suggest an alternative.
#
# TODO A better fix is obviously avoiding the use of `sock_connect`
#
# See https://github.com/LonamiWebs/Telethon/issues/1337 for details.
if not callable(getattr(self.loop, 'sock_connect', None)):
raise TypeError(
'Event loop of type {} lacks `sock_connect`, which is needed to use proxies.\n\n'
'Change the event loop in use to use proxies:\n'
'# https://github.com/LonamiWebs/Telethon/issues/1337\n'
'import asyncio\n'
'asyncio.set_event_loop(asyncio.SelectorEventLoop())'.format(
self.loop.__class__.__name__
)
)
if local_addr is not None: if local_addr is not None:
if use_ipv6 is False and ':' in local_addr: if use_ipv6 is False and ':' in local_addr:
raise TypeError( raise TypeError(
@ -283,10 +264,6 @@ def init(
# A place to store if channels are a megagroup or not (see `edit_admin`) # A place to store if channels are a megagroup or not (see `edit_admin`)
self._megagroup_cache = {} self._megagroup_cache = {}
def get_loop(self: 'TelegramClient') -> asyncio.AbstractEventLoop:
return asyncio.get_event_loop()
def get_flood_sleep_threshold(self): def get_flood_sleep_threshold(self):
return self._flood_sleep_threshold return self._flood_sleep_threshold
@ -382,7 +359,7 @@ async def connect(self: 'TelegramClient') -> None:
await self.session.save() await self.session.save()
self._updates_handle = self.loop.create_task(self._update_loop()) self._updates_handle = asyncio.create_task(self._update_loop())
def is_connected(self: 'TelegramClient') -> bool: def is_connected(self: 'TelegramClient') -> bool:
sender = getattr(self, '_sender', None) sender = getattr(self, '_sender', None)

View File

@ -148,10 +148,6 @@ class TelegramClient:
"System lang code" to be sent when creating the initial connection. "System lang code" to be sent when creating the initial connection.
Defaults to `lang_code`. Defaults to `lang_code`.
loop (`asyncio.AbstractEventLoop`, optional):
Asyncio event loop to use. Defaults to `asyncio.get_event_loop()`.
This argument is ignored.
base_logger (`str` | `logging.Logger`, optional): base_logger (`str` | `logging.Logger`, optional):
Base logger name or instance to use. Base logger name or instance to use.
If a `str` is given, it'll be passed to `logging.getLogger()`. If a If a `str` is given, it'll be passed to `logging.getLogger()`. If a
@ -2666,31 +2662,11 @@ class TelegramClient:
app_version: str = None, app_version: str = None,
lang_code: str = 'en', lang_code: str = 'en',
system_lang_code: str = 'en', system_lang_code: str = 'en',
loop: asyncio.AbstractEventLoop = None,
base_logger: typing.Union[str, logging.Logger] = None, base_logger: typing.Union[str, logging.Logger] = None,
receive_updates: bool = True receive_updates: bool = True
): ):
telegrambaseclient.init(**locals()) telegrambaseclient.init(**locals())
@property
def loop(self: 'TelegramClient') -> asyncio.AbstractEventLoop:
"""
Property with the ``asyncio`` event loop used by this client.
Example
.. code-block:: python
# Download media in the background
task = client.loop.create_task(message.download_media())
# Do some work
...
# Join the task (wait for it to complete)
await task
"""
return telegrambaseclient.get_loop(**locals())
@property @property
def flood_sleep_threshold(self): def flood_sleep_threshold(self):
return telegrambaseclient.get_flood_sleep_threshold(**locals()) return telegrambaseclient.get_flood_sleep_threshold(**locals())

View File

@ -159,14 +159,14 @@ def _process_update(self: 'TelegramClient', update, entities, others):
channel_id = self._state_cache.get_channel_id(update) channel_id = self._state_cache.get_channel_id(update)
args = (update, entities, others, channel_id, self._state_cache[channel_id]) args = (update, entities, others, channel_id, self._state_cache[channel_id])
if self._dispatching_updates_queue is None: if self._dispatching_updates_queue is None:
task = self.loop.create_task(_dispatch_update(self, *args)) task = asyncio.create_task(_dispatch_update(self, *args))
self._updates_queue.add(task) self._updates_queue.add(task)
task.add_done_callback(lambda _: self._updates_queue.discard(task)) task.add_done_callback(lambda _: self._updates_queue.discard(task))
else: else:
self._updates_queue.put_nowait(args) self._updates_queue.put_nowait(args)
if not self._dispatching_updates_queue.is_set(): if not self._dispatching_updates_queue.is_set():
self._dispatching_updates_queue.set() self._dispatching_updates_queue.set()
self.loop.create_task(_dispatch_queue_updates(self)) asyncio.create_task(_dispatch_queue_updates(self))
self._state_cache.update(update) self._state_cache.update(update)

View File

@ -37,15 +37,15 @@ class AlbumHack:
# very short-lived but might as well try to do "the right thing". # very short-lived but might as well try to do "the right thing".
self._client = weakref.ref(client) self._client = weakref.ref(client)
self._event = event # parent event self._event = event # parent event
self._due = client.loop.time() + _HACK_DELAY self._due = asyncio.get_running_loop().time() + _HACK_DELAY
client.loop.create_task(self.deliver_event()) asyncio.create_task(self.deliver_event())
def extend(self, messages): def extend(self, messages):
client = self._client() client = self._client()
if client: # weakref may be dead if client: # weakref may be dead
self._event.messages.extend(messages) self._event.messages.extend(messages)
self._due = client.loop.time() + _HACK_DELAY self._due = asyncio.get_running_loop().time() + _HACK_DELAY
async def deliver_event(self): async def deliver_event(self):
while True: while True:
@ -53,7 +53,7 @@ class AlbumHack:
if client is None: if client is None:
return # weakref is dead, nothing to deliver return # weakref is dead, nothing to deliver
diff = self._due - client.loop.time() diff = self._due - asyncio.get_running_loop().time()
if diff <= 0: if diff <= 0:
# We've hit our due time, deliver event. It won't respect # We've hit our due time, deliver event. It won't respect
# sequential updates but fixing that would just worsen this. # sequential updates but fixing that would just worsen this.

View File

@ -1,5 +1,7 @@
import re import re
import struct import struct
import asyncio
import functools
from .common import EventBuilder, EventCommon, name_inner_event from .common import EventBuilder, EventCommon, name_inner_event
from .._misc import utils from .._misc import utils
@ -7,6 +9,20 @@ from .. import _tl
from ..types import _custom from ..types import _custom
def auto_answer(func):
@functools.wraps(func)
async def wrapped(self, *args, **kwargs):
if self._answered:
return await func(*args, **kwargs)
else:
return (await asyncio.gather(
self._answer(),
func(*args, **kwargs),
))[1]
return wrapped
@name_inner_event @name_inner_event
class CallbackQuery(EventBuilder): class CallbackQuery(EventBuilder):
""" """
@ -240,16 +256,15 @@ class CallbackQuery(EventBuilder):
if self._answered: if self._answered:
return return
self._answered = True res = await self._client(_tl.fn.messages.SetBotCallbackAnswer(
return await self._client(
_tl.fn.messages.SetBotCallbackAnswer(
query_id=self.query.query_id, query_id=self.query.query_id,
cache_time=cache_time, cache_time=cache_time,
alert=alert, alert=alert,
message=message, message=message,
url=url url=url,
) ))
) self._answered = True
return res
@property @property
def via_inline(self): def via_inline(self):
@ -266,35 +281,36 @@ class CallbackQuery(EventBuilder):
""" """
return isinstance(self.query, _tl.UpdateInlineBotCallbackQuery) return isinstance(self.query, _tl.UpdateInlineBotCallbackQuery)
@auto_answer
async def respond(self, *args, **kwargs): async def respond(self, *args, **kwargs):
""" """
Responds to the message (not as a reply). Shorthand for Responds to the message (not as a reply). Shorthand for
`telethon.client.messages.MessageMethods.send_message` with `telethon.client.messages.MessageMethods.send_message` with
``entity`` already set. ``entity`` already set.
This method also creates a task to `answer` the callback. This method will also `answer` the callback if necessary.
This method will likely fail if `via_inline` is `True`. This method will likely fail if `via_inline` is `True`.
""" """
self._client.loop.create_task(self.answer())
return await self._client.send_message( return await self._client.send_message(
await self.get_input_chat(), *args, **kwargs) await self.get_input_chat(), *args, **kwargs)
@auto_answer
async def reply(self, *args, **kwargs): async def reply(self, *args, **kwargs):
""" """
Replies to the message (as a reply). Shorthand for Replies to the message (as a reply). Shorthand for
`telethon.client.messages.MessageMethods.send_message` with `telethon.client.messages.MessageMethods.send_message` with
both ``entity`` and ``reply_to`` already set. both ``entity`` and ``reply_to`` already set.
This method also creates a task to `answer` the callback. This method will also `answer` the callback if necessary.
This method will likely fail if `via_inline` is `True`. This method will likely fail if `via_inline` is `True`.
""" """
self._client.loop.create_task(self.answer())
kwargs['reply_to'] = self.query.msg_id kwargs['reply_to'] = self.query.msg_id
return await self._client.send_message( return await self._client.send_message(
await self.get_input_chat(), *args, **kwargs) await self.get_input_chat(), *args, **kwargs)
@auto_answer
async def edit(self, *args, **kwargs): async def edit(self, *args, **kwargs):
""" """
Edits the message. Shorthand for Edits the message. Shorthand for
@ -303,7 +319,7 @@ class CallbackQuery(EventBuilder):
Returns `True` if the edit was successful. Returns `True` if the edit was successful.
This method also creates a task to `answer` the callback. This method will also `answer` the callback if necessary.
.. note:: .. note::
@ -311,7 +327,6 @@ class CallbackQuery(EventBuilder):
`Message.edit <telethon.tl._custom.message.Message.edit>`, `Message.edit <telethon.tl._custom.message.Message.edit>`,
since the message object is normally not present. since the message object is normally not present.
""" """
self._client.loop.create_task(self.answer())
if isinstance(self.query.msg_id, _tl.InputBotInlineMessageID): if isinstance(self.query.msg_id, _tl.InputBotInlineMessageID):
return await self._client.edit_message( return await self._client.edit_message(
None, self.query.msg_id, *args, **kwargs None, self.query.msg_id, *args, **kwargs
@ -322,6 +337,7 @@ class CallbackQuery(EventBuilder):
*args, **kwargs *args, **kwargs
) )
@auto_answer
async def delete(self, *args, **kwargs): async def delete(self, *args, **kwargs):
""" """
Deletes the message. Shorthand for Deletes the message. Shorthand for
@ -332,11 +348,10 @@ class CallbackQuery(EventBuilder):
this `delete` method. Use a this `delete` method. Use a
`telethon.client.telegramclient.TelegramClient` instance directly. `telethon.client.telegramclient.TelegramClient` instance directly.
This method also creates a task to `answer` the callback. This method will also `answer` the callback if necessary.
This method will likely fail if `via_inline` is `True`. This method will likely fail if `via_inline` is `True`.
""" """
self._client.loop.create_task(self.answer())
return await self._client.delete_messages( return await self._client.delete_messages(
await self.get_input_chat(), [self.query.msg_id], await self.get_input_chat(), [self.query.msg_id],
*args, **kwargs *args, **kwargs

View File

@ -242,6 +242,6 @@ class InlineQuery(EventBuilder):
if inspect.isawaitable(obj): if inspect.isawaitable(obj):
return asyncio.ensure_future(obj) return asyncio.ensure_future(obj)
f = asyncio.get_event_loop().create_future() f = asyncio.get_running_loop().create_future()
f.set_result(obj) f.set_result(obj)
return f return f

View File

@ -23,13 +23,15 @@ class Connection:
""" """
Establishes a connection with the server. Establishes a connection with the server.
""" """
loop = asyncio.get_event_loop() loop = asyncio.get_running_loop()
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setblocking(False) sock.setblocking(False)
if self._local_addr: if self._local_addr:
sock.bind(self._local_addr) sock.bind(self._local_addr)
# TODO https://github.com/LonamiWebs/Telethon/issues/1337 may be an issue again
# perhaps we just need to ignore async connect on windows and block?
await asyncio.wait_for(loop.sock_connect(sock, (self._ip, self._port)), timeout) await asyncio.wait_for(loop.sock_connect(sock, (self._ip, self._port)), timeout)
self._sock = sock self._sock = sock
@ -41,14 +43,14 @@ class Connection:
if not self._sock: if not self._sock:
raise ConnectionError('not connected') raise ConnectionError('not connected')
loop = asyncio.get_event_loop() loop = asyncio.get_running_loop()
await loop.sock_sendall(self._sock, self._transport.pack(data)) await loop.sock_sendall(self._sock, self._transport.pack(data))
async def recv(self): async def recv(self):
if not self._sock: if not self._sock:
raise ConnectionError('not connected') raise ConnectionError('not connected')
loop = asyncio.get_event_loop() loop = asyncio.get_running_loop()
while True: while True:
try: try:
length, body = self._transport.unpack(self._in_buffer) length, body = self._transport.unpack(self._in_buffer)

View File

@ -58,7 +58,7 @@ class MTProtoSender:
# pending futures should be cancelled. # pending futures should be cancelled.
self._user_connected = False self._user_connected = False
self._reconnecting = False self._reconnecting = False
self._disconnected = asyncio.get_event_loop().create_future() self._disconnected = asyncio.get_running_loop().create_future()
self._disconnected.set_result(None) self._disconnected.set_result(None)
# We need to join the loops upon disconnection # We need to join the loops upon disconnection
@ -248,18 +248,17 @@ class MTProtoSender:
await self._disconnect(error=e) await self._disconnect(error=e)
raise e raise e
loop = asyncio.get_event_loop()
self._log.debug('Starting send loop') self._log.debug('Starting send loop')
self._send_loop_handle = loop.create_task(self._send_loop()) self._send_loop_handle = asyncio.create_task(self._send_loop())
self._log.debug('Starting receive loop') self._log.debug('Starting receive loop')
self._recv_loop_handle = loop.create_task(self._recv_loop()) self._recv_loop_handle = asyncio.create_task(self._recv_loop())
# _disconnected only completes after manual disconnection # _disconnected only completes after manual disconnection
# or errors after which the sender cannot continue such # or errors after which the sender cannot continue such
# as failing to reconnect or any unexpected error. # as failing to reconnect or any unexpected error.
if self._disconnected.done(): if self._disconnected.done():
self._disconnected = loop.create_future() self._disconnected = asyncio.get_running_loop().create_future()
self._log.info('Connection to %s complete!', self._connection) self._log.info('Connection to %s complete!', self._connection)
@ -381,7 +380,7 @@ class MTProtoSender:
self._pending_state.clear() self._pending_state.clear()
if self._auto_reconnect_callback: if self._auto_reconnect_callback:
asyncio.get_event_loop().create_task(self._auto_reconnect_callback()) asyncio.create_task(self._auto_reconnect_callback())
break break
else: else:
@ -406,7 +405,7 @@ class MTProtoSender:
# gets stuck. # gets stuck.
# TODO It still gets stuck? Investigate where and why. # TODO It still gets stuck? Investigate where and why.
self._reconnecting = True self._reconnecting = True
asyncio.get_event_loop().create_task(self._reconnect(error)) asyncio.create_task(self._reconnect(error))
def _keepalive_ping(self, rnd_id): def _keepalive_ping(self, rnd_id):
""" """

View File

@ -132,7 +132,7 @@ class App(tkinter.Tk):
command=self.send_message).grid(row=3, column=2) command=self.send_message).grid(row=3, column=2)
# Post-init (async, connect client) # Post-init (async, connect client)
self.cl.loop.create_task(self.post_init()) asyncio.create_task(self.post_init())
async def post_init(self): async def post_init(self):
""" """
@ -369,10 +369,4 @@ async def main(interval=0.05):
if __name__ == "__main__": if __name__ == "__main__":
# Some boilerplate code to set up the main method asyncio.run(main())
aio_loop = asyncio.get_event_loop()
try:
aio_loop.run_until_complete(main())
finally:
if not aio_loop.is_closed():
aio_loop.close()

View File

@ -9,9 +9,6 @@ from telethon.errors import SessionPasswordNeededError
from telethon.network import ConnectionTcpAbridged from telethon.network import ConnectionTcpAbridged
from telethon.utils import get_display_name from telethon.utils import get_display_name
# Create a global variable to hold the loop we will be using
loop = asyncio.get_event_loop()
def sprint(string, *args, **kwargs): def sprint(string, *args, **kwargs):
"""Safe Print (handle UnicodeEncodeErrors on some terminals)""" """Safe Print (handle UnicodeEncodeErrors on some terminals)"""
@ -50,7 +47,7 @@ async def async_input(prompt):
let the loop run while we wait for input. let the loop run while we wait for input.
""" """
print(prompt, end='', flush=True) print(prompt, end='', flush=True)
return (await loop.run_in_executor(None, sys.stdin.readline)).rstrip() return (await asyncio.get_running_loop().run_in_executor(None, sys.stdin.readline)).rstrip()
def get_env(name, message, cast=str): def get_env(name, message, cast=str):
@ -109,34 +106,34 @@ class InteractiveTelegramClient(TelegramClient):
# media known the message ID, for every message having media. # media known the message ID, for every message having media.
self.found_media = {} self.found_media = {}
async def init(self):
# Calling .connect() may raise a connection error False, so you need # Calling .connect() may raise a connection error False, so you need
# to except those before continuing. Otherwise you may want to retry # to except those before continuing. Otherwise you may want to retry
# as done here. # as done here.
print('Connecting to Telegram servers...') print('Connecting to Telegram servers...')
try: try:
loop.run_until_complete(self.connect()) await self.connect()
except IOError: except IOError:
# We handle IOError and not ConnectionError because # We handle IOError and not ConnectionError because
# PySocks' errors do not subclass ConnectionError # PySocks' errors do not subclass ConnectionError
# (so this will work with and without proxies). # (so this will work with and without proxies).
print('Initial connection failed. Retrying...') print('Initial connection failed. Retrying...')
loop.run_until_complete(self.connect()) await self.connect()
# If the user hasn't called .sign_in() or .sign_up() yet, they won't # If the user hasn't called .sign_in() or .sign_up() yet, they won't
# be authorized. The first thing you must do is authorize. Calling # be authorized. The first thing you must do is authorize. Calling
# .sign_in() should only be done once as the information is saved on # .sign_in() should only be done once as the information is saved on
# the *.session file so you don't need to enter the code every time. # the *.session file so you don't need to enter the code every time.
if not loop.run_until_complete(self.is_user_authorized()): if not await self.is_user_authorized():
print('First run. Sending code request...') print('First run. Sending code request...')
user_phone = input('Enter your phone: ') user_phone = input('Enter your phone: ')
loop.run_until_complete(self.sign_in(user_phone)) await self.sign_in(user_phone)
self_user = None self_user = None
while self_user is None: while self_user is None:
code = input('Enter the code you just received: ') code = input('Enter the code you just received: ')
try: try:
self_user =\ self_user = await self.sign_in(code=code)
loop.run_until_complete(self.sign_in(code=code))
# Two-step verification may be enabled, and .sign_in will # Two-step verification may be enabled, and .sign_in will
# raise this error. If that's the case ask for the password. # raise this error. If that's the case ask for the password.
@ -146,8 +143,7 @@ class InteractiveTelegramClient(TelegramClient):
pw = getpass('Two step verification is enabled. ' pw = getpass('Two step verification is enabled. '
'Please enter your password: ') 'Please enter your password: ')
self_user =\ self_user = await self.sign_in(password=pw)
loop.run_until_complete(self.sign_in(password=pw))
async def run(self): async def run(self):
"""Main loop of the TelegramClient, will wait for user action""" """Main loop of the TelegramClient, will wait for user action"""
@ -397,9 +393,13 @@ class InteractiveTelegramClient(TelegramClient):
)) ))
if __name__ == '__main__': async def main():
SESSION = os.environ.get('TG_SESSION', 'interactive') SESSION = os.environ.get('TG_SESSION', 'interactive')
API_ID = get_env('TG_API_ID', 'Enter your API ID: ', int) API_ID = get_env('TG_API_ID', 'Enter your API ID: ', int)
API_HASH = get_env('TG_API_HASH', 'Enter your API hash: ') API_HASH = get_env('TG_API_HASH', 'Enter your API hash: ')
client = InteractiveTelegramClient(SESSION, API_ID, API_HASH) client = await InteractiveTelegramClient(SESSION, API_ID, API_HASH).init()
loop.run_until_complete(client.run()) await client.run()
if __name__ == '__main__':
asyncio.run(main())

View File

@ -7,8 +7,6 @@ import os
import time import time
import sys import sys
loop = asyncio.get_event_loop()
""" """
Provider token can be obtained via @BotFather. more info at https://core.telegram.org/bots/payments#getting-a-token Provider token can be obtained via @BotFather. more info at https://core.telegram.org/bots/payments#getting-a-token
@ -180,4 +178,4 @@ if __name__ == '__main__':
if not provider_token: if not provider_token:
logger.error("No provider token supplied.") logger.error("No provider token supplied.")
exit(1) exit(1)
loop.run_until_complete(main()) asyncio.run(main())

View File

@ -134,12 +134,13 @@ async def main():
# By default, `Quart.run` uses `asyncio.run()`, which creates a new asyncio # By default, `Quart.run` uses `asyncio.run()`, which creates a new asyncio
# event loop. If we create the `TelegramClient` before, `telethon` will # event loop. Instead, we use `asyncio.run()` manually in order to make this
# use `asyncio.get_event_loop()`, which is the implicit loop in the main # explicit, as the client cannot be "transferred" between loops while
# thread. These two loops are different, and it won't work. # connected due to the need to schedule work within an event loop.
# #
# So, we have to manually pass the same `loop` to both applications to # In essence one needs to be careful to avoid mixing event loops, but this is
# make 100% sure it works and to avoid headaches. # simple, as `asyncio.run` is generally only used in the entry-point of the
# program.
# #
# To run Quart inside `async def`, we must use `hypercorn.asyncio.serve()` # To run Quart inside `async def`, we must use `hypercorn.asyncio.serve()`
# directly. # directly.
@ -149,4 +150,4 @@ async def main():
# won't have to worry about any of this, but it's still good to be # won't have to worry about any of this, but it's still good to be
# explicit about the event loop. # explicit about the event loop.
if __name__ == '__main__': if __name__ == '__main__':
client.loop.run_until_complete(main()) asyncio.run(main())