@spencer_spfl

Как отправлять сообщения по расписанию с aiogram3?

Я перерыл кучу ресурсов, на которых используют aiogram3 и при этом отправляют отложенные сообщения. У меня не чистый код, разбит на пару файлов, но вот его основная часть.
Что можно почитать или посмотреть, чтобы понять как реализовать отправку сообщений по расписанию?

Вот моя попытка реализовать это через aioschedule и вызове его в main()

Код:

import asyncio
import logging
import aioschedule

from aiogram import Bot, Dispatcher, types, F
from config import TOKEN
from schedule import auto_posting

bot = Bot(token=TOKEN)
dp = Dispatcher()

#Здесь обработка сообщений, всё работает стабильно через @dp хэндлеры

async def scheduler():
    aioschedule.every().day.at("23:51").do(asyncio.create_task(auto_posting(bot)))


async def on_startup():
    await asyncio.create_task(scheduler())


async def main():
    logging.basicConfig(level=logging.INFO)
    await asyncio.gather(
        dp.start_polling(bot),
        on_startup(),
    )


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

Вот код функций из других файлов:

async def auto_posting(bot: Bot):
    await database.connect_db()
    data = await database.get_one_post()
    text = str(data[0])
    photo = str(data[1])
    kb = data[2]
    print(text, photo, kb)
    if photo != 'NULL':
        await bot.send_photo(channel_id,
                             photo=types.FSInputFile(path=photo),
                             caption=text,
                             reply_markup=kb,
                             parse_mode='HTML')
    else:
        await bot.send_message(channel_id,
                               text=text,
                               reply_markup=kb,
                               parse_mode='HTML')


async def get_one_post():
    data = cur.execute('SELECT text, media_path, inline_urls FROM posts WHERE status = ?', (0,)).fetchone()
    text = data[0]
    photo = data[1]
    urls_temp = data[2]
    if urls_temp is not 'NULL':
        urls = []
        for x in urls_temp:
            urls.append(x.split(' - '))
        buttons = []
        for x in urls:
            buttons.append([types.InlineKeyboardButton(text=x[0], url=x[1])])
        kb = types.InlineKeyboardMarkup(inline_keyboard=buttons)
    else:
        kb = None
    return text, photo, kb

Я готов тратить время на решение этого вопроса без полного переписывания бота в модульную форму.

Читал его про telethon, но это либо переписывать бота полностью, либо писать второго исключительно для работы с бд, а хотелось бы всё сделать с aiogram.

Вариант с loop.time() + delay тоже не подходит ибо нужно высчитывать время отправки, а при любом сбое серверов менять эти значения.
  • Вопрос задан
  • 2511 просмотров
Решения вопроса 1
@spencer_spfl Автор вопроса
Решение:
Создать отдельный процесс

async def scheduler():
    posting_time = ['10:00',
                    '18:00']

    while True:
        if datetime.datetime.now().strftime('%H:%M') in posting_time:
            await auto_posting()
        await asyncio.sleep(60)


def worker():
    asyncio.run((scheduler()))


async def main():
    process = Process(target=worker)
    process.start()
    logging.basicConfig(level=logging.INFO)
    await dp.start_polling(bot)
    process.join()


if __name__ == '__main__':
    asyncio.run(main())
Ответ написан
Комментировать
Пригласить эксперта
Ответы на вопрос 1
Vindicar
@Vindicar
RTFM!
Открываем страницу пакета, читаем пример.
В конце видим рабочий цикл aioschedule, где периодически вызывается корутина aioschedule.run_pending().
У тебя этого нет. Т.е. ты планируешь вызов своей корутины, но не выполняешь код, который этот вызов сделает.
Поскольку у тебя scheduler() вызывается через create_task(), то можно прямо в тело scheduler() дописать что-то типа
while True:
    await aioschedule.run_pending()
    await asyncio.sleep(0.1)

Вот только зачем ты делаешь await create_teask()? Это убивает смысл create_task(), так как ты ждёшь завершения созданной фоновой задачи. Вместо этого сохрани таск в глобальную переменную - так он точно не будет собран сборщиком мусора, и ты сможешь при необходимости вызвать метод cancel(), чтобы прервать цикл внутри scheduler().
Ответ написан
Ваш ответ на вопрос

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

Похожие вопросы