0

I'm trying to run Quart+Telethon with multiple clients. This example shows one global client. I have this working.

Now I need my app to handle multiple users simultaneously logging in and doing stuff. This post suggests using asyncio.gather.

How do I need to change my code such that I can have multiple people logging in?

Here I placed the bulk of Quart functionality into the work() function (wherever client is referenced). In the def main() I start the app and then invoke asyncio.gather.

When running the app with two work() functions (two clients) in asyncio.gather I get error "AssertionError: Handler is overwriting existing for endpoint phone_form".

And if I run only with one work() function I get a different error: "ConnectionError('Cannot send requests while disconnected')"

What am I missing? Thx

import os
import asyncio

from dotenv import load_dotenv
from quart import Quart, render_template_string, request, render_template
from telethon import TelegramClient


load_dotenv('.env')

API_ID = int(os.getenv('API_ID'))
API_HASH = str(os.getenv('API_HASH'))


app = Quart(__name__)


async def work(client):
    async with client:

        @app.route("/")
        async def phone_form():
            return await render_template('phone_form.html')

        @app.route("/validation_form", methods=['POST'])
        async def validation_form():
            """ Ask the user for the confirmation code they just got. """
            global phone

            # Check form parameters (phone/code)
            form = await request.form
            if 'phone' in form:
                phone = form['phone']
                await client.send_code_request(phone)

            return await render_template('validation_form.html', phone_nr=phone)

async def main():
    import hypercorn.asyncio

    # create task so that starting hypercorn server is no blocking functions that come after it
    server = asyncio.create_task(hypercorn.asyncio.serve(app, hypercorn.Config()))

    # have many clients here, using the app asynchronously
    await asyncio.gather(
        work(TelegramClient('user1', API_ID_, API_HASH)),
        work(TelegramClient('user2', API_ID, API_HASH)),
    )
    # this is to start blocking - means after this subsequent functions will need to wait until hypercorn is finished (hypercorn runs forever!)
    # this await also lets server run indefinitely
    await server

if __name__ == '__main__':
    asyncio.run(main())
user1552294
  • 125
  • 1
  • 10

1 Answers1

0

The code is trying to set N function handlers to the same routes, that's what causing the error here. I believe you need a different approach here - make adding sessions a continuous process with routes dealing with the last (new) session.

I'm not sure how hypercorn server should be started so using it's part from your example.

eg.

API_ID = int(os.getenv('API_ID'))
API_HASH = str(os.getenv('API_HASH'))

app = Quart(__name__)

clients = []

async def work():
    new_cli = None
    phone = None
    
    @app.route("/")
    async def index():
        """ Some page containing 'new session' button redirecting to /phone_form """
        new_cli = None
        phone = None
        return render_template('index.html')
        
    @app.route("/phone_form")
    async def phone_form():
        num = len(clients) + 1
        new_cli = TelegramClient(f'user{num}', API_ID_, API_HASH)
        await new_cli.connect()
        return await render_template('phone_form.html')

    @app.route("/validation_form", methods=['POST'])
    async def validation_form():
        """ Ask the user for the confirmation code they just got. """

        # Check form parameters (phone/code)
        form = await request.form
        if 'phone' in form:
            phone = form['phone']
            await client.send_code_request(phone)

        return await render_template('validation_form.html', phone_nr=phone)

    @app.route("/confirm_code", methods=['POST'])
    async def confirm_code():
        """ Finish auth process. """

        form = await request.form
        if 'code' in form:
            await new_cli.sign_in(phone, form['code'])
        clients.append(new_cli)
        new_cli = None
        phone = None

async def main():
    import hypercorn.asyncio

    server = asyncio.create_task(hypercorn.asyncio.serve(app, hypercorn.Config()))
    await work()
    await server

if __name__ == '__main__':
    asyncio.run(main())

You may also want to add checkers if new client session is present. Resulted sessions can be used later.

Kiril
  • 1