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

@ -20,7 +20,7 @@ its own methods, which you all can use.
# 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()
asyncio.get_event_loop().run_until_complete(main())
You **don't** need to import these `AuthMethods`, `MessageMethods`, etc.

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
))

View File

@ -5,6 +5,7 @@
# your environment variables. This is a good way to use these private
# values. See https://superuser.com/q/284342.
import asyncio
from os import environ
# environ is used to get API information from environment variables
@ -13,28 +14,27 @@ from os import environ
from telethon import TelegramClient
def main():
async def main():
session_name = environ.get('TG_SESSION', 'session')
client = TelegramClient(session_name,
int(environ['TG_API_ID']),
environ['TG_API_HASH'],
proxy=None,
update_workers=4,
spawn_read_thread=False)
proxy=None)
if 'TG_PHONE' in environ:
client.start(phone=environ['TG_PHONE'])
await client.start(phone=environ['TG_PHONE'])
else:
client.start()
await client.start()
client.add_event_handler(update_handler)
print('(Press Ctrl+C to stop this)')
client.idle()
await client.disconnected
def update_handler(update):
async def update_handler(update):
print(update)
if __name__ == '__main__':
main()
loop = asyncio.get_event_loop()
loop.run_until_complete(main())

View File

@ -10,11 +10,12 @@ This script assumes that you have certain files on the working directory,
such as "xfiles.m4a" or "anytime.png" for some of the automated replies.
"""
import re
import asyncio
from collections import defaultdict
from datetime import datetime, timedelta
from os import environ
from telethon import TelegramClient, events, utils
from telethon import TelegramClient, events
"""Uncomment this for debugging
import logging
@ -30,57 +31,54 @@ REACTS = {'emacs': 'Needs more vim',
recent_reacts = defaultdict(list)
# TG_API_ID and TG_API_HASH *must* exist or this won't run!
session_name = environ.get('TG_SESSION', 'session')
client = TelegramClient(
session_name, int(environ['TG_API_ID']), environ['TG_API_HASH'],
proxy=None
)
@client.on(events.NewMessage)
async def my_handler(event: events.NewMessage.Event):
global recent_reacts
# Through event.raw_text we access the text of messages without format
words = re.split('\W+', event.raw_text)
# Try to match some reaction
for trigger, response in REACTS.items():
if len(recent_reacts[event.chat_id]) > 3:
# Silently ignore triggers if we've recently sent 3 reactions
break
if trigger in words:
# Remove recent replies older than 10 minutes
recent_reacts[event.chat_id] = [
a for a in recent_reacts[event.chat_id] if
datetime.now() - a < timedelta(minutes=10)
]
# Send a reaction as a reply (otherwise, event.respond())
await event.reply(response)
# Add this reaction to the list of recent actions
recent_reacts[event.chat_id].append(datetime.now())
# Automatically send relevant media when we say certain things
# When invoking requests, get_input_entity needs to be called manually
if event.out:
chat = await event.get_input_chat()
if event.raw_text.lower() == 'x files theme':
await client.send_file(chat, 'xfiles.m4a',
reply_to=event.message.id, voice_note=True)
if event.raw_text.lower() == 'anytime':
await client.send_file(chat, 'anytime.png',
reply_to=event.message.id)
if '.shrug' in event.text:
await event.edit(event.text.replace('.shrug', r'¯\_(ツ)_/¯'))
if __name__ == '__main__':
# TG_API_ID and TG_API_HASH *must* exist or this won't run!
session_name = environ.get('TG_SESSION', 'session')
client = TelegramClient(
session_name, int(environ['TG_API_ID']), environ['TG_API_HASH'],
spawn_read_thread=False, proxy=None, update_workers=4
)
@client.on(events.NewMessage)
def my_handler(event: events.NewMessage.Event):
global recent_reacts
# This utils function gets the unique identifier from peers (to_id)
to_id = utils.get_peer_id(event.message.to_id)
# Through event.raw_text we access the text of messages without format
words = re.split('\W+', event.raw_text)
# Try to match some reaction
for trigger, response in REACTS.items():
if len(recent_reacts[to_id]) > 3:
# Silently ignore triggers if we've recently sent 3 reactions
break
if trigger in words:
# Remove recent replies older than 10 minutes
recent_reacts[to_id] = [
a for a in recent_reacts[to_id] if
datetime.now() - a < timedelta(minutes=10)
]
# Send a reaction as a reply (otherwise, event.respond())
event.reply(response)
# Add this reaction to the list of recent actions
recent_reacts[to_id].append(datetime.now())
# Automatically send relevant media when we say certain things
# When invoking requests, get_input_entity needs to be called manually
if event.out:
if event.raw_text.lower() == 'x files theme':
client.send_voice_note(event.message.to_id, 'xfiles.m4a',
reply_to=event.message.id)
if event.raw_text.lower() == 'anytime':
client.send_file(event.message.to_id, 'anytime.png',
reply_to=event.message.id)
if '.shrug' in event.text:
event.edit(event.text.replace('.shrug', r'¯\_(ツ)_/¯'))
if 'TG_PHONE' in environ:
client.start(phone=environ['TG_PHONE'])
else:
client.start()
loop = asyncio.get_event_loop()
loop.run_until_complete(client.start(phone=environ.get('TG_PHONE')))
print('(Press Ctrl+C to stop this)')
client.idle()
client.run_until_disconnected()

View File

@ -1,4 +1,5 @@
#!/usr/bin/env python3
import asyncio
import traceback
from telethon_examples.interactive_telegram_client \
@ -38,14 +39,15 @@ if __name__ == '__main__':
**kwargs)
print('Initialization done!')
loop = asyncio.get_event_loop()
try:
client.run()
loop.run_until_complete(client.run())
except Exception as e:
print('Unexpected error ({}): {} at\n{}'.format(
type(e), e, traceback.format_exc()))
finally:
client.disconnect()
loop.run_until_complete(client.disconnect())
print('Thanks for trying the interactive example! Exiting...')