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.
Related
Is there a way to send a message (call a function), without a command from the bot's user? For example when a certain condition is met?
Currently I have only been able to call a function with a command.
I have been trying the following:
from telegram.ext import Updater, CommandHandler, CallbackContext
from telegram import Update
from datetime import datetime
import telebot as tb
import logging
bot = tb.TeleBot(API_KEY)
logging.basicConfig(
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
)
logger = logging.getLogger(__name__)
def messager(update: Update, context: CallbackContext) -> None:
"""this function messages at a specific time"""
update.message.reply_text("message back")
if __name__ == "__main__":
updater = Updater(API_KEY)
dispatcher = updater.dispatcher
dispatcher.add_handler(CommandHandler("message", messager))
condition = False #I have been changing this when testing
if (condition == True):
messager() # how to call messager here? how to pass update and context arguments?
updater.start_polling()
updater.idle()
Any possible way to achieve this would be great.
You cannot call a function using update and context since there is no update from the telegram servers to act on. In other words there is no event to perform a callback on.
Depending on what you want to do you could use a MessageHandler instead of a CommandHandler to reply to any message with Filters to filter out which ones to reply to
or if you want to send a message every time you start your bot and you know who to send a message to (i.e you have their chat_id) you can do
if condition:
updater.bot.send_message(chat_id=CHAT_ID, text="message back")
or if you just want to send a message every time some user starts a chat with your bot you could use the start command which is sent by default to start a chat with a bot
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
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().
I'm currently making a Telegram using Python. I was wondering how to make a functional buttons like this
https://i.stack.imgur.com/e3eMb.png
I would like to make /command command to have button that says "This is a button". How do I make it? Here's my code to help :
# Modules needed
import time
from time import sleep
import random
import logging
from telegram.ext import Updater, CommandHandler, MessageHandler, Filters
# Enable logging
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.INFO)
logger = logging.getLogger(__name__)
# /command
def command(update, context):
update.message.reply_text("Reply here")
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.
updater = Updater("[TOKEN]", use_context=True)
# Get the dispatcher to register handlers
dp = updater.dispatcher
# on different commands - answer in Telegram
dp.add_handler(CommandHandler("command", command))
# log all errors
dp.add_error_handler(error)
# Start the Bot
updater.start_polling()
updater.idle()
if __name__ == '__main__':
main()
What you are looking for are so called inline buttons. Check out the official docs and this python-telegram-bot example.
Disclaimer: I'm currently the maintainer of python-telegram-bot.
I'm trying to make a telegram bot with https://github.com/python-telegram-bot/python-telegram-bot library in python3.
I want to make a message sender bot. This is my code now:
#!/usr/bin/env python
"""#==============================# Imports #==============================#"""
import logging, time, telegram
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import Updater, CommandHandler, MessageHandler, Filters
from datetime import datetime
"""#==============================# Enable logging #==============================#"""
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.WARN)#DEBUG)
logger = logging.getLogger(__name__)
"""#==============================# Log error #==============================#"""
def error(update, context):
#Log Errors caused by Updates.
logger.warning("Update '%s' caused error '%s'", update, context.error)
"""#==============================# Bot commands #==============================#"""
def start(update, context):
update.message.reply_text("Bot joined the conversation!")
def get_additional_updates(update, message):
***My Problem***
"""#==============================# MAIN #==============================#"""
def main():
updater = Updater("<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)
dp.add_handler(CommandHandler("send", get_additional_updates)
# log all errors
dp.add_error_handler(error)
# Start the Bot
updater.start_polling()
updater.idle()
if __name__ == '__main__':
main()
I don't know what should I do to get the updates inside the 'get_additional_updates' function.
What I want to do, is: I type /send, after this the bot waits for my message, I type my message in and send it. The problem is, that I can't figure it out how to get the second message (the message itself) to the 'get_additional_updates' function.
I can't find it in the documentation, and I'm very new to programming as well.
Please help me with the code I need to type there in order to get the additional messages.
Let me know if you can't understand what is my question, I'll try to explain better.
Thanks a lot!
P.S.:Sorry, if my english is bad, I'm trying to upgrade that as well.
You should use conversation bot ,Check the example here