Dr_Elvis
@Dr_Elvis
В гугле забанен

Как исправить бота с несколькими потоками?

Привет!
Пишу бота для телеги и мне нужно реализовать следующий функционал: бот раз в 10 минут(к примеру) парсит определенный URL и если с предыдущего захода были изменения - писал в наш чат.
Так как бот занимается еще и другими делами, то решил зациклить выполнение парсинга в функции с sleep в конце. Если есть изменения - пытаюсь отправить в чат сообщение, но тут и случается проблема.
Так как удачное стечение обстоятельств не возникает от ивента в чате - я не могу из event вытащить entity для функции send_message. поэтому приходится доставать посредством функции get_entity и ссылки на чат как параметр, но почему то из другого потока это не получается. ниже приведу код упрощенный:
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()


Собственно как мне в таком варианте отправить сообщение в чат?
  • Вопрос задан
  • 306 просмотров
Решения вопроса 1
Dr_Elvis
@Dr_Elvis Автор вопроса, куратор тега Python
В гугле забанен
Решил я свой вопрос. вот этот код работает ровно так как мне нужно.
parseurls - работает отдельно и время от времени дергает sendmsg для отправки сообщения в чат
Сам бот работает параллельно и отвечает на команду вне зависимости от "сна" 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()
Ответ написан
Комментировать
Пригласить эксперта
Ответы на вопрос 1
Vindicar
@Vindicar
RTFM!
Я бы посоветовал использовать asyncio.Queue, но там есть тонкости при работе с несколькими потоками.
Пусть поток-парсер вообще не занимается вопросами работы с ботом, а просто периодически производит парсинг, и если есть новости - кладёт их описание в выходную очередь. Он должен делать это с помощью call_soon_threadsafe(), как описано тут, так как класс asyncio.Queue не является потокобезопасным.
Зато бот в главном потоке может просто в цикле await'ить метод get() очереди, и отправлять сообщения по получению очередного объекта.

Альтернатива - использовать классический потокобезопасный queue.
Так будет проще помещать в него результаты парсинга, но сложнее их извлекать - придётся периодически (типа раз в 1-5 секунд) вызывать метод get_nowait(), чтобы узнать, есть ли что-то в очереди. Блокирующий get() подвесит бота.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы