Why does the line `await asyncio.create_task(scheduler())` never run? - python

I am trying to get my telegram bot to send messages at a certain time but it doesn't work. The bot launches fine, everything works as intended except for the scheduled messages. I suspect the line await asyncio.create_task(scheduler()) never runs but I don't know why or how to fix it/get it to work. Any help is appreciated.
import asyncio
import logging
import aioschedule as schedule
from aiogram import Bot, Dispatcher
from aiogram.types import BotCommand
from aiogram.contrib.fsm_storage.memory import MemoryStorage
from app.config_reader import load_config
from app.handlers.feedback import register_handlers_feedback
from app.handlers.food import register_handlers_food
from app.handlers.common import register_handlers_common
from app.handlers.database import register_handlers_db
from app.handlers.mailing import mail_one, mail_two, mail_three
logger = logging.getLogger(__name__)
async def set_commands(bot: Bot):
commands = [
BotCommand(command="/feedback", description="Take a poll"),
BotCommand(command="/db", description="Add your info to our database"),
BotCommand(command="/cancel", description="Cancel")
]
await bot.set_my_commands(commands)
async def main():
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(name)s - %(message)s",
)
logger.error("Starting bot")
# Parsing the config file
config = load_config("config/bot.ini")
# Instantiating Bot and Dispatcher objects
bot = Bot(token=config.tg_bot.token)
dp = Dispatcher(bot, storage=MemoryStorage())
async def scheduler():
# this line is here temporarily for debugging/checking if it works
schedule.every(5).seconds.do(lambda: mail_one(bot))
schedule.every().day.at('10:00').do(lambda: mail_one(bot))
schedule.every().day.at('10:00').do(lambda: mail_two(bot))
schedule.every().day.at('10:00').do(lambda: mail_three(bot))
while True:
await schedule.run_pending()
await asyncio.sleep(1)
await set_commands(bot)
# Registering handlers
register_handlers_common(dp, config.tg_bot.admin_id)
register_handlers_feedback(dp)
register_handlers_db(dp)
# optional: await dp.skip_updates()
await dp.start_polling()
await asyncio.create_task(scheduler())
if __name__ == '__main__':
asyncio.run(main())```

If the line await asyncio.create_task(scheduler()) never runs, it is because the line before it, await dp.start_pooling(), is blocking it. If you look inside aiogram.Dispacher's start_pooling(), there is a while loop. If execution order doesn't matter, you can try putting await dp.start_polling() inside asyncio.create_task().

Related

How to make Dispatcher work for all aiogram tokens

import logging
from aiogram import Bot, types
from aiogram.contrib.middlewares.logging import LoggingMiddleware
from aiogram.dispatcher import Dispatcher
from aiogram.dispatcher.webhook import SendMessage
from aiogram.utils.executor import start_webhook
API_TOKEN = ''
# webhook settings
WEBHOOK_HOST = 'https://xxxxx211.eu.ngrok.io'
WEBHOOK_PATH = ''
WEBHOOK_URL = f"{WEBHOOK_HOST}{WEBHOOK_PATH}"
# webserver settings
WEBAPP_HOST = 'localhost' # or ip
WEBAPP_PORT = 5000
logging.basicConfig(level=logging.INFO)
bot = Bot(token=API_TOKEN)
dp = Dispatcher(bot)
dp.middleware.setup(LoggingMiddleware())
#dp.message_handler()
async def echo(message: types.Message):
# Regular request
# await bot.send_message(message.chat.id, message.text)
# or reply INTO webhook
return SendMessage(message.chat.id, message.text)
async def on_startup(dp):
await bot.set_webhook(WEBHOOK_URL.format(token=API_TOKEN))
with bot.with_token("token"):
await bot.set_webhook(WEBHOOK_URL.format(token="token"))
with bot.with_token("token2"):
await bot.set_webhook(WEBHOOK_URL.format(token="token2"))
async def on_shutdown(dp):
logging.warning('Shutting down..')
# insert code here to run it before shutdown
# Remove webhook (not acceptable in some cases)
await bot.delete_webhook()
with bot.with_token("token"):
await bot.delete_webhook()
with bot.with_token("token2"):
await bot.delete_webhook()
# Close DB connection (if used)
await dp.storage.close()
await dp.storage.wait_closed()
logging.warning('Bye!')
if __name__ == '__main__':
start_webhook(
dispatcher=dp,
webhook_path=WEBHOOK_PATH,
on_startup=on_startup,
on_shutdown=on_shutdown,
host=WEBAPP_HOST,
port=WEBAPP_PORT,
)
Here I took the code for multibot from the FAQ aiogram gabbhack repository But there is a problem with the Dispatcher there is a concept
Major token and minor
Because of this, when sending code using await message.answer and the like on the aiogram dock using the second bot, the answer comes to the first bot
And the problem is solved with the help of return SendMessage but this is not an option because 2 requests or more cannot be sent and asynchrony is lost
How can I make it so that the Dispatcher is for each token separately (tokens in an unlimited number)
So that you can turn to #dp and await message.answer and the answer comes to this very bot

Why telegram-bot on Python with Webhooks can't process messages from many users simultaneously unlike a bot with Long Polling?

I use aiogram. Logic of my bot is very simple - he receive messages from user and send echo-message after 10 seconds. This is a test bot, but in general, I want to make a bot for buying movies with very big database of users. So, my bot must be able to process messages from many users simultaneously and must receive messages using Webhooks. Here are two python scripts:
Telegram-bot on Long Polling:
import asyncio
import logging
from aiogram import Bot, Dispatcher, executor, types
from bot_files.config import *
# Configure logging
logging.basicConfig(level=logging.INFO)
# Initialize bot and dispatcher
bot = Bot(token=bot_token)
dp = Dispatcher(bot)
#dp.message_handler()
async def echo(message: types.Message):
await asyncio.sleep(10)
await message.answer(message.text)
if __name__ == '__main__':
executor.start_polling(dp, skip_updates=True)
Telegram-bot on Webhooks:
import asyncio
import logging
from aiogram import Bot, Dispatcher, executor, types
from bot_files.config import *
# Configure logging
logging.basicConfig(level=logging.INFO)
# Initialize bot and dispatcher
bot = Bot(token=bot_token)
dp = Dispatcher(bot)
WEBHOOK_HOST = f'https://7417-176-8-60-184.ngrok.io'
WEBHOOK_PATH = f'/webhook/{bot_token}'
WEBHOOK_URL = f'{WEBHOOK_HOST}{WEBHOOK_PATH}'
# webserver settings
WEBAPP_HOST = '0.0.0.0'
WEBAPP_PORT = os.getenv('PORT', default=5000)
async def on_startup(dispatcher):
await bot.set_webhook(WEBHOOK_URL, drop_pending_updates=True)
async def on_shutdown(dispatcher):
await bot.delete_webhook()
#dp.message_handler()
async def echo(message: types.Message):
await asyncio.sleep(10)
await message.answer(message.text)
if __name__ == '__main__':
executor.start_webhook(
dispatcher=dp,
webhook_path=WEBHOOK_PATH,
skip_updates=True,
on_startup=on_startup,
on_shutdown=on_shutdown,
host=WEBAPP_HOST,
port=WEBAPP_PORT
)
In the first case, if two users send messages simultaneously, both of messages are processed also simultaneously(acynchrony) - 10 seconds. In the second case messages are processed linearly(not asynchrony) - one of two users must wait 20 seconds. Why telegram-bot on Python with Webhooks can't process messages from many users simultaneously unlike a bot with Long Polling?
Actually telegram-bot on Python with Webhooks can process messages from many users simultaneously. You need just to put #dp.async_task after handler
#dp.message_handler()
#dp.async_task
async def echo(message: types.Message):
await asyncio.sleep(10)
await message.answer(message.text)

How to run asynchronously python telegram bot and while true loop

I'm using python-telegram-bot library
I'm trying to create telegram bot that works like anki. It add word and translation from user to database and later ask user to write translation of the word. I'm trying to send the user words for which he should write a translation. I have problems with this.
In main.py I can't run asynchronously application.run_polling() that run telegram bot and send_messages.setup(application.bot) from send_messages.py that run while true loop for sending words.
I was trying different ways to solve my problem like asyncio event loop or asyncio.create_task() but that don't work.
The code below only runs send_messages.setup(application.bot) with while true loop and prevents application.run_polling() from executing
main.py
import logging
import os
import telegram.ext as tg_ext
from bot import handlers, send_messages
TOKEN = os.getenv('TOKEN')
logging.basicConfig(
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.INFO,
)
logger = logging.getLogger(__name__)
def main() -> None:
application = tg_ext.ApplicationBuilder().token(TOKEN).build()
handlers.setup_handlers(application)
send_messages.setup(application.bot)
application.run_polling()
if __name__ == '__main__':
main()
send_messages.py
import telegram.ext as tg_ext
import asyncio
from database import db
async def send_words(bot: tg_ext.Application.bot) -> None:
"""Sends words to the user to write a translation."""
while True:
words = db.get_words_to_remember()
for word in words:
word_eng = word[0].word
user = word[1]
await bot.send_message(
user.chat_id, f'Enter translation: {word_eng}')
db.change_is_waiting(user)
await asyncio.sleep(60)
def setup(bot: tg_ext.Application.bot) -> None:
asyncio.run(send_words(bot))
It could be solved by telegram.ext.JobQueue that allow to perform tasks periodically with a running telegram bot
Corrected code:
main.py
import logging
import os
import telegram.ext as tg_ext
from bot import handlers, send_messages
TOKEN = os.getenv('TOKEN')
logging.basicConfig(
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.INFO,
)
logger = logging.getLogger(__name__)
def main() -> None:
application = tg_ext.ApplicationBuilder().token(TOKEN).build()
job_queue = application.job_queue
job_queue.run_repeating(send_messages.setup, interval=60, first=10)
handlers.setup_handlers(application)
application.run_polling()
if __name__ == '__main__':
main()
send_messages.py
import telegram.ext as tg_ext
from database import db
async def send_words(bot: tg_ext.Application.bot) -> None:
"""Sends words to the user to write a translation."""
words = db.get_words_to_remember()
for word in words:
word_eng = word[0].word
user = word[1]
await bot.send_message(user.chat_id, f'Enter translation: {word_eng}')
db.change_is_waiting(user)
async def setup(context: tg_ext.CallbackContext) -> None:
await send_words(context.bot)
It is not possible to run a function which is not async concurrently in the same thread. You need to either use a different thread to run application.run_polling() or use a ThreadPoolExecutor, which in turn will use a different thread under the hood.
See https://docs.python.org/3/library/threading.html for python threading docs and examples.
See https://stackoverflow.com/a/51051980/4542928 for an example of ThreadPoolExecutor.
IMO the overall design looks like it could be improved. Specifically I'm thinking of ConversationHandler - see also the example. This should allow you to send words to the user, wait for the translation, then send the next word etc.

Telegram bot: getting 'The following arguments have not been supplied' error

I am trying to add to a Telegram bot a timer which runs and sends a message every x time.
Getting always the error: x argument have not been supplied from the callback function, even though I am putting those arguments in the context argument when calling run_repeating.
The call to run_repeating:
context.job_queue.run_repeating(stupid_hello,
interval=30,
context={'bot': context.bot,'chat_id':update.message.chat_id},
first=datetime.time(hour=8),
last=datetime.time(hour=22))
callback function:
def stupid_hello(bot, chat_id):
bot.send_message(chat_id=chat_id ,text='Hello World')
And this is how I set the handler:
dp.add_handler(CommandHandler("start", start, pass_job_queue=True))
The run_repeating function is part of a "start" function.
--- EDIT ---
Adding code to reproduce it:
import logging
from re import sub
from typing import Set
import praw
from collections import Counter
from praw.models import MoreComments
import os
import datetime
from telegram import Update
from telegram.ext import Updater, CommandHandler, CallbackContext
import config
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.INFO)
logger = logging.getLogger(__name__)
def stupid_hello(bot, chat_id):
bot.send_message(chat_id=chat_id ,text='Hello World')
def start(update: Update, context: CallbackContext):
context.job_queue.run_repeating(stupid_hello, interval=30,
context={'bot': context.bot, 'chat_id':update.message.chat_id},
first=datetime.time(hour=8),
last=datetime.time(hour=22))
def help(update, context):
"""Send a message when the command /help is issued."""
update.message.reply_text('/start, /top_ten_satoshi')
def error(update, context):
"""Log Errors caused by Updates."""
logger.warning('Update "%s" caused error "%s"', update, context.error)
def main():
"""Start the bot."""
# Create the Updater and pass it your bot's token.
# Make sure to set use_context=True to use the new context based callbacks
# Post version 12 this will no longer be necessary
updater = Updater(config.telegram_token, use_context=True)
# Get the dispatcher to register handlers
dp = updater.dispatcher
# on different commands - answer in Telegram
dp.add_handler(CommandHandler("start", start, pass_job_queue=True))
dp.add_handler(CommandHandler("help", help))
# log all errors
dp.add_error_handler(error)
# Start bot for local usage
updater.start_polling()
# Run the bot until you press Ctrl-C or the process receives SIGINT,
# SIGTERM or SIGABRT. This should be used most of the time, since
# start_polling() is non-blocking and will stop the bot gracefully.
updater.idle()
if __name__ == '__main__':
main()
If you want to reproduce it you will need to add a config.py with your own telegram bot token and send /start to the bot from telegram
I found the solution in Python Telegram Bot, unable to pass job queue?
I thought the problem was related with the run_repeating function when it was with the job_queue.
Hope this helps others having the same issue.
from telegram.ext import Updater, CommandHandler, MessageHandler, Filters, InlineQueryHandler
def sayhi(context):
context.bot.send_message(context.job.context, text="hi")
def time(update, context):
context.job_queue.run_repeating(sayhi, 5, context=update.message.chat_id)
def main():
updater = Updater('Token', use_context=True)
dp = updater.dispatcher
dp.add_handler(MessageHandler(Filters.text , time))
updater.start_polling()
updater.idle()
main()
As you can see the context keyword argument in run_repeating is referenced in the callback function as context.job.context which is the part I was missing

How to start a thread in telethon correctly?

I am writing a bot and I need to implement the following functionality: the bot once every 10 minutes(for example) parse a certain URL and if there were changes from the previous call, writes to the chat.
Since the bot is also engaged in other things, I decided to loop the parsing in the function with sleep at the end. If there are changes, I try to send a message to the chat, but then a problem happens.
Since a successful combination of circumstances does not arise from an event in the chat, I can't pull the "entity" from the "event" for the "send_message" function. therefore, we have to get through the "get_entity" function and links to the chat as a parameter, but for some reason this does not work from another stream. below is a simplified code:
import threading, queue
from time import sleep
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):
channelaa = await bot.get_entity('https://t.me/elvistest')
await bot.send_message(channelaa, 'ответ')
def parseurls():
for x in range(10):
q.put(x)
pass
async def pre_sendmsg():
while True:
try:
msg = q.get_nowait()
except Exception as e:
await asyncio.sleep(1.0)
else:
await sendmsg(msg)
q.task_done()
async def sendmsg(msg):
channel = await bot.get_entity('https://t.me/elvistest')
await bot.send_message(channel, f'ответ из другого потока {msg}')
if __name__ == '__main__':
q = queue.Queue()
parseurls()
bot.start(bot_token=cfg.bot_token)
threading.Thread(target=asyncio.run, daemon=True, args=(pre_sendmsg(),)).start()
bot.run_until_disconnected()
The thing is that on the line " boot.get_entity" nothing happens. The script execution is lost somewhere and does not go further, that is, the next line with "bot. send_message" is simply not executed. however, "def echoidchat" is working at this time.
Well done!
This is work like I want.
import random
import threading, queue
from time import sleep
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()

Categories

Resources