From c61a5c8fc6cc82f207efe7ac3b998d32f2c54940 Mon Sep 17 00:00:00 2001 From: pgjones Date: Tue, 9 Jul 2019 20:05:17 +0100 Subject: [PATCH] Improve Quart example As the client seems tied to a specific login, I've changed it from being a global to a local in the request handler. This should prevent one user logging in and another unrelated user having access (due to a global shared client). I've then made use of session storage to store the phone and code (session storage is per user and deleted when the browser tab is closed). I wasn't sure if the code was sensitive, if so it is potentially problematic to store in the default session system (cookies) - a server storage solution (e.g. redis or in memory) would be better. Note that if the client has to be global the Quart before/after serving functions should be used to connect and disconnect, as so, client = ... @app.before_serving async def startup(): await client.connect() @app.after_serving async def cleanup(): await client.disconnect() as this creates the client within the event-loop managed by Quart. Alternatively if the event-loop must be managed outside of Quart the following should be used, from hypercorn.asyncio import serve loop.run_until_complete(serve(app)) instead of `app.run()`. I've then made a number of more minor changes to use the template rendering system within Quart (more features, better example) and to clean up the code. --- telethon_examples/quart_login.py | 125 +++++++++++++------------------ 1 file changed, 52 insertions(+), 73 deletions(-) diff --git a/telethon_examples/quart_login.py b/telethon_examples/quart_login.py index b4715c66..bd6fcf83 100644 --- a/telethon_examples/quart_login.py +++ b/telethon_examples/quart_login.py @@ -1,10 +1,38 @@ import base64 import os -from quart import Quart, request +from quart import Quart, render_template_string, request, session from telethon import TelegramClient, utils +BASE_TEMPLATE = """ + + + + + Telethon + Quart + + {{ content | safe }} + +""" + +PHONE_FORM = """ +
+ Phone (international format): + +
+""" + +CODE_FORM = """ +
+ Telegram code: + +
+""" + +app = Quart(__name__) +app.secret_key = "CHANGE THIS TO SOMETHING SECRET" + def get_env(name, message): if name in os.environ: @@ -18,20 +46,6 @@ 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: @@ -50,71 +64,36 @@ async def format_message(message): ) -# 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() + client = TelegramClient(SESSION, API_ID, API_HASH) + await client.connect() - # We want to update the global phone variable to remember it - global phone + if request.method == "POST": + form = await request.form + if 'phone' in form: + session["phone"] = form["phone"] + await client.send_code_request(form["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: + session["code"] = form["code"] - if 'code' in form: - await client.sign_in(code=form['code']) + if "phone" not in session: + return await render_template_string(BASE_TEMPLATE, content=PHONE_FORM) - # 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)) + if "code" not in session: + return await render_template_string(BASE_TEMPLATE, content=CODE_FORM) - return html(result) + await client.sign_in(code=session["code"]) + # 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)) - # 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: - -
''') + await client.disconnect() + return await render_template_string(BASE_TEMPLATE, content=result) -# 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 +if __name__ == "__main__": + app.run()