From 2a7d4317bd20c171a36abe93aac417a460643c99 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Thu, 27 Jun 2019 13:55:54 +0200 Subject: [PATCH] Add quart-based example (#1207) --- readthedocs/quick-references/faq.rst | 3 + telethon_examples/README.md | 25 ++++++ telethon_examples/quart_login.py | 120 +++++++++++++++++++++++++++ 3 files changed, 148 insertions(+) create mode 100644 telethon_examples/quart_login.py diff --git a/readthedocs/quick-references/faq.rst b/readthedocs/quick-references/faq.rst index 70ab9bfa..f452b9ea 100644 --- a/readthedocs/quick-references/faq.rst +++ b/readthedocs/quick-references/faq.rst @@ -213,6 +213,9 @@ lot of headaches to get threads and asyncio to work together. Instead, consider using `Quart `_, an asyncio-based alternative to `Flask `_. +Check out `quart_login.py`_ for an example web-application based on Quart. + .. _logging: https://docs.python.org/3/library/logging.html .. _@SpamBot: https://t.me/SpamBot .. _issue 297: https://github.com/LonamiWebs/Telethon/issues/297 +.. _quart_login.py: https://github.com/LonamiWebs/Telethon/tree/master/telethon_examples#quart_loginpy diff --git a/telethon_examples/README.md b/telethon_examples/README.md index 3cab7322..29af0e4d 100644 --- a/telethon_examples/README.md +++ b/telethon_examples/README.md @@ -90,6 +90,30 @@ send messages, delete them, and download media. The code is a bit long which may make it harder to follow, and requires saving some state in order for downloads to work later. +### [`quart_login.py`] + +* Usable as: **user**. +* Difficulty: **medium**. + +Web-based application using [Quart](https://pgjones.gitlab.io/quart/index.html) +(an `asyncio` alternative to [Flask](http://flask.pocoo.org/)) and Telethon +together. + +The example should work as a base for Quart applications *with a single +global client*, and it should be easy to adapt for multiple clients by +following the comments in the code. + +It showcases how to login manually (ask for phone, code, and login), +and once the user is logged in, some messages and photos will be shown +in the page. + +There is nothing special about Quart. It was chosen because it's a +drop-in replacement for Flask, the most popular option for web-apps. +You can use any `asyncio` library with Telethon just as well, +like [Sanic](https://sanic.readthedocs.io/en/latest/index.html) or +[aiohttp](https://docs.aiohttp.org/en/stable/). You can even use Flask, +if you learn how to use `threading` and `asyncio` together. + ### [`gui.py`] * Usable as: **user and bot**. @@ -111,6 +135,7 @@ assumes some [`asyncio`] knowledge, but otherwise is easy to follow. [CC0 License]: https://github.com/LonamiWebs/Telethon/blob/master/telethon_examples/LICENSE [@BotFather]: https://t.me/BotFather [`assistant.py`]: https://raw.githubusercontent.com/LonamiWebs/Telethon/master/telethon_examples/assistant.py +[`quart_login.py`]: https://raw.githubusercontent.com/LonamiWebs/Telethon/master/telethon_examples/quart_login.py [`gui.py`]: https://raw.githubusercontent.com/LonamiWebs/Telethon/master/telethon_examples/gui.py [`interactive_telegram_client.py`]: https://raw.githubusercontent.com/LonamiWebs/Telethon/master/telethon_examples/interactive_telegram_client.py [`print_messages.py`]: https://raw.githubusercontent.com/LonamiWebs/Telethon/master/telethon_examples/print_messages.py diff --git a/telethon_examples/quart_login.py b/telethon_examples/quart_login.py new file mode 100644 index 00000000..b4715c66 --- /dev/null +++ b/telethon_examples/quart_login.py @@ -0,0 +1,120 @@ +import base64 +import os + +from quart import Quart, request + +from telethon import TelegramClient, utils + + +def get_env(name, message): + if name in os.environ: + return os.environ[name] + return input(message) + + +# Session name, API ID and hash to use; loaded from environmental variables +SESSION = os.environ.get('TG_SESSION', 'quart') +API_ID = int(get_env('TG_API_ID', 'Enter your API ID: ')) +API_HASH = get_env('TG_API_HASH', 'Enter your API hash: ') + + +# Helper method to add the HTML head/body +def html(inner): + return ''' + + + + + Telethon + Quart + + {} + +'''.format(inner) + + +# Helper method to format messages nicely +async def format_message(message): + if message.photo: + content = '{}'.format( + base64.b64encode(await message.download_media(bytes)).decode(), + message.raw_text + ) + else: + # client.parse_mode = 'html', so bold etc. will work! + content = (message.text or '(action message)').replace('\n', '
') + + return '

{}: {}{}

'.format( + utils.get_display_name(message.sender), + content, + message.date + ) + + +# Define the global phone and Quart app variables +phone = None +app = Quart(__name__) + + +# Quart handlers +@app.route('/', methods=['GET', 'POST']) +async def root(): + # Connect if we aren't yet + if not client.is_connected(): + await client.connect() + + # We want to update the global phone variable to remember it + global phone + + # Check form parameters (phone/code) + form = await request.form + if 'phone' in form: + phone = form['phone'] + await client.send_code_request(phone) + + if 'code' in form: + await client.sign_in(code=form['code']) + + # If we're logged in, show them some messages from their first dialog + if await client.is_user_authorized(): + # They are logged in, show them some messages from their first dialog + dialog = (await client.get_dialogs())[0] + result = '

{}

'.format(dialog.title) + async for m in client.iter_messages(dialog, 10): + result += await(format_message(m)) + + return html(result) + + # Ask for the phone if we don't know it yet + if phone is None: + return html(''' +
+ Phone (international format): + +
''') + + # We have the phone, but we're not logged in, so ask for the code + return html(''' +
+ Telegram code: + +
''') + + +# By default, `Quart.run` uses `asyncio.run()`, which creates a new asyncio +# event loop. If we create the `TelegramClient` before, `telethon` will +# use `asyncio.get_event_loop()`, which is the implicit loop in the main +# thread. These two loops are different, and it won't work. +# +# So, we have to manually pass the same `loop` to both applications to +# make 100% sure it works and to avoid headaches. +# +# Quart doesn't seem to offer a way to run inside `async def` +# (see https://gitlab.com/pgjones/quart/issues/146) so we must +# run and block on it last. +# +# This example creates a global client outside of Quart handlers. +# If you create the client inside the handlers (common case), you +# won't have to worry about any of this. +client = TelegramClient(SESSION, API_ID, API_HASH) +client.parse_mode = 'html' # <- render things nicely +app.run(loop=client.loop) # <- same event loop as telethon