Update examples to be async

This commit is contained in:
Lonami Exo
2018-06-24 12:04:23 +02:00
parent 58031b3adf
commit 026c0c4f9d
5 changed files with 134 additions and 116 deletions

View File

@@ -1,4 +1,6 @@
import os
import sys
import asyncio
from getpass import getpass
from telethon.utils import get_display_name
@@ -6,9 +8,10 @@ from telethon.utils import get_display_name
from telethon import TelegramClient, events
from telethon.network import ConnectionTcpAbridged
from telethon.errors import SessionPasswordNeededError
from telethon.tl.types import (
PeerChat, UpdateShortChatMessage, UpdateShortMessage
)
# Create a global variable to hold the loop we will be using
loop = asyncio.get_event_loop()
def sprint(string, *args, **kwargs):
@@ -41,6 +44,16 @@ def bytes_to_string(byte_count):
)
async def async_input(prompt):
"""
Python's ``input()`` is blocking, which means the event loop we set
above can't be running while we're blocking there. This method will
let the loop run while we wait for input.
"""
print(prompt, end='', flush=True)
return (await loop.run_in_executor(None, sys.stdin.readline)).rstrip()
class InteractiveTelegramClient(TelegramClient):
"""Full featured Telegram client, meant to be used on an interactive
session to see what Telethon is capable off -
@@ -78,41 +91,37 @@ class InteractiveTelegramClient(TelegramClient):
connection=ConnectionTcpAbridged,
# If you're using a proxy, set it here.
proxy=proxy,
# If you want to receive updates, you need to start one or more
# "update workers" which are background threads that will allow
# you to run things when your update handlers (callbacks) are
# called with an Update object.
update_workers=1
proxy=proxy
)
# Store {message.id: message} map here so that we can download
# media known the message ID, for every message having media.
self.found_media = {}
# Calling .connect() may return False, so you need to assert it's
# True before continuing. Otherwise you may want to retry as done here.
# Calling .connect() may raise a connection error False, so you need
# to except those before continuing. Otherwise you may want to retry
# as done here.
print('Connecting to Telegram servers...')
if not self.connect():
try:
loop.run_until_complete(self.connect())
except ConnectionError:
print('Initial connection failed. Retrying...')
if not self.connect():
print('Could not connect to Telegram servers.')
return
loop.run_until_complete(self.connect())
# 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
# .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.
if not self.is_user_authorized():
if not loop.run_until_complete(self.is_user_authorized()):
print('First run. Sending code request...')
self.sign_in(user_phone)
loop.run_until_complete(self.sign_in(user_phone))
self_user = None
while self_user is None:
code = input('Enter the code you just received: ')
try:
self_user = self.sign_in(code=code)
self_user =\
loop.run_until_complete(self.sign_in(code=code))
# Two-step verification may be enabled, and .sign_in will
# raise this error. If that's the case ask for the password.
@@ -122,9 +131,10 @@ class InteractiveTelegramClient(TelegramClient):
pw = getpass('Two step verification is enabled. '
'Please enter your password: ')
self_user = self.sign_in(password=pw)
self_user =\
loop.run_until_complete(self.sign_in(password=pw))
def run(self):
async def run(self):
"""Main loop of the TelegramClient, will wait for user action"""
# Once everything is ready, we can add an event handler.
@@ -142,7 +152,7 @@ class InteractiveTelegramClient(TelegramClient):
# Entities represent the user, chat or channel
# corresponding to the dialog on the same index.
dialogs = self.get_dialogs(limit=dialog_count)
dialogs = await self.get_dialogs(limit=dialog_count)
i = None
while i is None:
@@ -159,7 +169,7 @@ class InteractiveTelegramClient(TelegramClient):
print(' !q: Quits the dialogs window and exits.')
print(' !l: Logs out, terminating this session.')
print()
i = input('Enter dialog ID or a command: ')
i = await async_input('Enter dialog ID or a command: ')
if i == '!q':
return
if i == '!l':
@@ -169,7 +179,7 @@ class InteractiveTelegramClient(TelegramClient):
#
# This is not the same as simply calling .disconnect(),
# which simply shuts down everything gracefully.
self.log_out()
await self.log_out()
return
try:
@@ -199,7 +209,7 @@ class InteractiveTelegramClient(TelegramClient):
# And start a while loop to chat
while True:
msg = input('Enter a message: ')
msg = await async_input('Enter a message: ')
# Quit
if msg == '!q':
break
@@ -209,16 +219,16 @@ class InteractiveTelegramClient(TelegramClient):
# History
elif msg == '!h':
# First retrieve the messages and some information
messages = self.get_messages(entity, limit=10)
messages = await self.get_messages(entity, limit=10)
# Iterate over all (in reverse order so the latest appear
# the last in the console) and print them with format:
# "[hh:mm] Sender: Message"
for msg in reversed(messages):
# Note that the .sender attribute is only there for
# convenience, the API returns it differently. But
# this shouldn't concern us. See the documentation
# for .iter_messages() for more information.
# Note how we access .sender here. Since we made an
# API call using the self client, it will always have
# information about the sender. This is different to
# events, where Telegram may not always send the user.
name = get_display_name(msg.sender)
# Format the message content
@@ -242,29 +252,33 @@ class InteractiveTelegramClient(TelegramClient):
# Send photo
elif msg.startswith('!up '):
# Slice the message to get the path
self.send_photo(path=msg[len('!up '):], entity=entity)
path = msg[len('!up '):]
await self.send_photo(path=path, entity=entity)
# Send file (document)
elif msg.startswith('!uf '):
# Slice the message to get the path
self.send_document(path=msg[len('!uf '):], entity=entity)
path = msg[len('!uf '):]
await self.send_document(path=path, entity=entity)
# Delete messages
elif msg.startswith('!d '):
# Slice the message to get message ID
deleted_msg = self.delete_messages(entity, msg[len('!d '):])
msg = msg[len('!d '):]
deleted_msg = await self.delete_messages(entity, msg)
print('Deleted {}'.format(deleted_msg))
# Download media
elif msg.startswith('!dm '):
# Slice the message to get message ID
self.download_media_by_id(msg[len('!dm '):])
await self.download_media_by_id(msg[len('!dm '):])
# Download profile photo
elif msg == '!dp':
print('Downloading profile picture to usermedia/...')
os.makedirs('usermedia', exist_ok=True)
output = self.download_profile_photo(entity, 'usermedia')
output = await self.download_profile_photo(entity,
'usermedia')
if output:
print('Profile picture downloaded to', output)
else:
@@ -278,26 +292,26 @@ class InteractiveTelegramClient(TelegramClient):
# Send chat message (if any)
elif msg:
self.send_message(entity, msg, link_preview=False)
await self.send_message(entity, msg, link_preview=False)
def send_photo(self, path, entity):
async def send_photo(self, path, entity):
"""Sends the file located at path to the desired entity as a photo"""
self.send_file(
await self.send_file(
entity, path,
progress_callback=self.upload_progress_callback
)
print('Photo sent!')
def send_document(self, path, entity):
async def send_document(self, path, entity):
"""Sends the file located at path to the desired entity as a document"""
self.send_file(
await self.send_file(
entity, path,
force_document=True,
progress_callback=self.upload_progress_callback
)
print('Document sent!')
def download_media_by_id(self, media_id):
async def download_media_by_id(self, media_id):
"""Given a message ID, finds the media this message contained and
downloads it.
"""
@@ -310,7 +324,7 @@ class InteractiveTelegramClient(TelegramClient):
print('Downloading media to usermedia/...')
os.makedirs('usermedia', exist_ok=True)
output = self.download_media(
output = await self.download_media(
msg.media,
file='usermedia/',
progress_callback=self.download_progress_callback
@@ -336,29 +350,33 @@ class InteractiveTelegramClient(TelegramClient):
bytes_to_string(total_bytes), downloaded_bytes / total_bytes)
)
def message_handler(self, event):
async def message_handler(self, event):
"""Callback method for received events.NewMessage"""
# Note that accessing ``.sender`` and ``.chat`` may be slow since
# these are not cached and must be queried always! However it lets
# us access the chat title and user name.
# Note that message_handler is called when a Telegram update occurs
# and an event is created. Telegram may not always send information
# about the ``.sender`` or the ``.chat``, so if you *really* want it
# you should use ``get_chat()`` and ``get_sender()`` while working
# with events. Since they are methods, you know they may make an API
# call, which can be expensive.
chat = await event.get_chat()
if event.is_group:
if event.out:
sprint('>> sent "{}" to chat {}'.format(
event.text, get_display_name(event.chat)
event.text, get_display_name(chat)
))
else:
sprint('<< {} @ {} sent "{}"'.format(
get_display_name(event.sender),
get_display_name(event.chat),
get_display_name(await event.get_sender()),
get_display_name(chat),
event.text
))
else:
if event.out:
sprint('>> "{}" to user {}'.format(
event.text, get_display_name(event.chat)
event.text, get_display_name(chat)
))
else:
sprint('<< {} sent "{}"'.format(
get_display_name(event.chat), event.text
get_display_name(chat), event.text
))