E
E
Elvis2021-07-03 12:59:14
Python
Elvis, 2021-07-03 12:59:14

How to fix a bot with multiple threads?

Hey!
I am writing a bot for a cart and I need to implement the following functionality: the bot parses a certain URL every 10 minutes (for example), and if there were changes from the previous visit, I wrote to our chat.
Since the bot is also doing other things, I decided to loop the parsing in a function with sleep at the end. If there are changes, I try to send a message to the chat, but then a problem occurs.
Since a fortunate combination of circumstances does not arise from an event in a chat, I cannot pull out an entity from an event for the send_message function. therefore, it is necessary to get it using the get_entity function and a link to the chat as a parameter, but for some reason it does not work from another thread. Below is the simplified code:

import threading
import asyncio
from telethon.sync import TelegramClient, events
import config as cfg

bot = TelegramClient('Bot', cfg.api_id, cfg.api_hash)


@bot.on(events.NewMessage(pattern=r'^(?i)(idchat){1}$'))
async def echoidchat(event):
    # просто команда чтобы видеть что бот в принципе работает
    # я знаю что можно вытащить entity из event, но это работает если из чата пришло событие,
    # но не работает если бот просто должен отправить в чат сообщение сам
    # поэтому вытаскиваю entity из ссылки на чат
    # тут всё проходит отлично
    channel = await bot.get_entity('https://t.me/elvistest')
    await bot.send_message(channel, 'ответ')


async def parseurls():
    # а вот тут уже не работает.
    # на get_entity выдает RuntimeWarning: coroutine 'UserMethods.get_input_entity' was never awaited
    # и не возвращает entity
    while True:
        channel = await bot.get_entity('https://t.me/elvistest')
        await bot.send_message(channel, 'ответ из другого потока')
        asyncio.sleep(5)

if __name__ == '__main__':
    bot.start(bot_token=cfg.bot_token)
    my_thread_flights = threading.Thread(target=asyncio.run, args=(parseurls(),))
    my_thread_flights.start()
    bot.run_until_disconnected()


Actually, how can I send a message to the chat in this version?

Answer the question

In order to leave comments, you need to log in

2 answer(s)
E
Elvis, 2021-07-04
@Dr_Elvis

I solved my question. this code works exactly as i need.
parseurls - works separately and from time to time pulls sendmsg to send a message to the chat
The bot itself works in parallel and responds to the command regardless of the "sleep" of parseurls.

import random
import asyncio
from telethon import TelegramClient, events
import config as cfg

bot = TelegramClient('Bot', cfg.api_id, cfg.api_hash)

@bot.on(events.NewMessage(pattern=r'^(?i)(idchat){1}$'))
async def echoidchat(event):
    await bot.send_message(event.chat, 'ответ')


async def parseurls():
    while True:
        ts = abs(int(random.random()*10))
        print(f'parseurls({ts})')
        await sendmsg(ts)
        await asyncio.sleep(ts)


async def sendmsg(msg):
    print(f'sendmsg({msg}) - start')
    channel = await bot.get_entity('https://t.me/elvistest')
    await bot.send_message(channel, f'ответ из другого потока {msg}')
    print(f'sendmsg({msg}) - done')


def main():
    bot.start(bot_token=cfg.bot_token)
    loop = asyncio.get_event_loop()
    tasks = [
        loop.create_task(parseurls()),
        loop.create_task(bot.run_until_disconnected()),
    ]
    loop.run_until_complete(asyncio.wait(tasks))
    loop.close()

if __name__ == '__main__':
    main()

V
Vindicar, 2021-07-03
@Vindicar

I would suggest using asyncio.Queue , but there are subtleties in dealing with multiple threads.
Let the parser thread not deal with issues of working with the bot at all, but simply parse periodically, and if there is news, put their description in the output queue. It must do this with call_soon_threadsafe() as described here , since the asyncio.Queue class is not thread safe.
But the bot in the main thread can simply await the get () method of the queue in the loop, and send messages when the next object is received.
An alternative is to use the classic thread-safe queue .
This will make it easier to put the parsing results into it, but harder to retrieve them - you will have to periodically (like once every 1-5 seconds) call the get_nowait() method to find out if there is something in the queue. A blocking get() will hang the bot.

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question