Is it possible to force one command handler to run another in the same way as if the user did it him/herself, the example below is taken from the repo over at github for ``python-telegram-bot```
The goal is to trigger the /help command when the user chooses a any button from start
"""
Basic example for a bot that uses inline keyboards. For an in-depth explanation, check out
https://git.io/JOmFw.
"""
import logging
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
from telegram.ext import Updater, CommandHandler, CallbackQueryHandler, CallbackContext
logging.basicConfig(
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
)
logger = logging.getLogger(__name__)
def start(update: Update, context: CallbackContext) -> None:
"""Sends a message with three inline buttons attached."""
keyboard = [
[
InlineKeyboardButton("Option 1", callback_data='1'),
InlineKeyboardButton("Option 2", callback_data='2'),
],
[InlineKeyboardButton("Option 3", callback_data='3')],
]
reply_markup = InlineKeyboardMarkup(keyboard)
update.message.reply_text('Please choose:', reply_markup=reply_markup)
def button(update: Update, context: CallbackContext) -> None:
"""Parses the CallbackQuery and updates the message text."""
query = update.callback_query
# CallbackQueries need to be answered, even if no notification to the user is needed
# Some clients may have trouble otherwise. See https://core.telegram.org/bots/api#callbackquery
query.answer()
query.edit_message_text(text=f"Selected option: {query.data}")
def help_command(update: Update, context: CallbackContext) -> None:
"""Displays info on how to use the bot."""
update.message.reply_text("Use /start to test this bot.")
def main() -> None:
"""Run the bot."""
# Create the Updater and pass it your bot's token.
updater = Updater("TOKEN")
updater.dispatcher.add_handler(CommandHandler('start', start))
updater.dispatcher.add_handler(CallbackQueryHandler(button))
updater.dispatcher.add_handler(CommandHandler('help', help_command))
# Start the Bot
updater.start_polling()
# Run the bot until the user presses Ctrl-C or the process receives SIGINT,
# SIGTERM or SIGABRT
updater.idle()
if __name__ == '__main__':
main()
I looked everywhere and search but no one seems to have a clear and straight forward explanation if this is doable or not
Yes, you can directly call another function with the update and context from the function the program is in. To demonstrate I added a line in your button function. Also mind the rewrites necessary in help_command since it wants to reply to a message. There is no message when this function is called from button:
def button(update: Update, context: CallbackContext) -> None:
"""Parses the CallbackQuery and updates the message text."""
query = update.callback_query
# CallbackQueries need to be answered, even if no notification to the user is needed
# Some clients may have trouble otherwise. See https://core.telegram.org/bots/api#callbackquery
query.answer()
query.edit_message_text(text=f"Selected option: {query.data}")
help_command(update, context)
def help_command(update: Update, context: CallbackContext) -> None:
"""Displays info on how to use the bot."""
# update.message.reply_text("Use /start to test this bot.")
context.bot.send_message(update.effective_user.id, "Use /start to test this bot.")
If for some reason you'd insist on using the reply_text function, you could rewrite help_command like this:
def help_command(update: Update, context: CallbackContext) -> None:
"""Displays info on how to use the bot."""
if hasattr(update.message, "reply_text"):
update.message.reply_text("Use /start to test this bot.")
else:
context.bot.send_message(update.effective_user.id, "Use /start to test this bot.")
Hope this helps.
Related
Is it possible to have my InlineKeyboardButton callback function be the input to the next state? as if the user entered the text
for example if this is my callback:
async def button(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Parses the CallbackQuery and updates the message text."""
query = update.callback_query
await query.answer()
await update.message.reply_text(text=query.data)
return REPLY
and this is my conversation handler:
conv_handler = ConversationHandler(
entry_points=[CommandHandler('start', start)],
states={
REPLY: [
CallbackQueryHandler(button),
MessageHandler(
filters.TEXT & ~filters.COMMAND,
reply,
)
],
},
fallbacks=[CommandHandler("done", start)],
)
After clicking on an inline button, created at start function, and calling button callback, I would want to call the MessageHandler's reply function with an input I decide in the button callback
Thanks!
The user interacts with my Telegram bot using an Inline Keyboard. Some buttons execute functions that are sensitive and I would like to ask the user's confirmation before proceeding.
For example : in the main menu, the user chooses between two buttons Option 1 and Option 2. If clicked, the sensitive functions do_action_1 and do_action_2 are executed respectively.
A minimal working example is below :
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
from telegram.ext import Updater, CommandHandler, CallbackQueryHandler, CallbackContext
def start(update: Update, context: CallbackContext) -> None:
update.message.reply_text('Please choose:',
reply_markup = keyboard_main_menu())
def main_menu(update: Update, context: CallbackContext) -> None:
""" Displays the main menu keyboard when called. """
query = update.callback_query
query.answer()
query.edit_message_text(text = 'Please choose an option:',
reply_markup = keyboard_main_menu())
def keyboard_main_menu():
""" Creates the main menu keyboard """
keyboard = [
[InlineKeyboardButton("Option 1", callback_data='1'),
InlineKeyboardButton("Option 2", callback_data='2'),],
]
return InlineKeyboardMarkup(keyboard)
def do_action_1(update: Update, context: CallbackContext) -> None:
keyboard = [[InlineKeyboardButton("Main menu", callback_data='main')]]
reply_markup = InlineKeyboardMarkup(keyboard)
query = update.callback_query
query.answer()
query.edit_message_text(text=f"Selected option {query.data}\n"
f"Executed action 1.",
reply_markup=reply_markup)
def do_action_2(update: Update, context: CallbackContext) -> None:
keyboard = [[InlineKeyboardButton("Main menu", callback_data='main')]]
reply_markup = InlineKeyboardMarkup(keyboard)
query = update.callback_query
query.answer()
query.edit_message_text(text=f"Selected option {query.data}\n"
f"Executed action 2.",
reply_markup=reply_markup)
def main() -> None:
"""Run the bot."""
updater = Updater("TOKEN")
updater.dispatcher.add_handler(CommandHandler('start', start))
updater.dispatcher.add_handler(CallbackQueryHandler(main_menu, pattern='main'))
updater.dispatcher.add_handler(CallbackQueryHandler(do_action_1, pattern='1'))
updater.dispatcher.add_handler(CallbackQueryHandler(do_action_2, pattern='2'))
# Start the Bot
updater.start_polling()
print('started')
if __name__ == '__main__':
main()
Now, I would like to insert an intermediate step after clicking Option 1 or Option 2, displaying a keyboard "Are you sure ?" with two buttons Yes and No. Clicking Yes executes do_action_1 or do_action_2 based on the user's choice at the previous keyboard. Clicking No would bring the user back to the main menu.
How can I do that ?
You can display a keyboard with the options YES and NO and handle the input like you handled the input for the other buttons. To keep track of which action to take when the user presses YES, you can either encode that action in the callback_data or store it temporarily. See also this wiki page an Storing Data.
Disclaimer: I'm currently the maintainer of python-telegram-bot
Thanks to #CallMeStag's answer, I managed to get a mwe :
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
from telegram.ext import Updater, CommandHandler, CallbackQueryHandler, CallbackContext
def start(update: Update, context: CallbackContext) -> None:
"""Sends a message with three inline buttons attached."""
update.message.reply_text('Please choose:',
reply_markup = keyboard_main_menu())
def main_menu(update: Update, context: CallbackContext) -> None:
""" Displays the main menu keyboard when called. """
query = update.callback_query
query.answer()
query.edit_message_text(text = 'Please choose:',
reply_markup = keyboard_main_menu())
def keyboard_main_menu():
""" Creates the main menu keyboard """
keyboard = [
[InlineKeyboardButton("Option 1", callback_data='1'),
InlineKeyboardButton("Option 2", callback_data='2'),],
]
return InlineKeyboardMarkup(keyboard)
def confirm(update: Update, context: CallbackContext) -> None:
"""Parses the CallbackQuery and updates the message text."""
query = update.callback_query
query.answer()
keyboard = [
[InlineKeyboardButton("Yes", callback_data=f'YES{query.data}'),
InlineKeyboardButton("No", callback_data='main'),],
]
reply_markup = InlineKeyboardMarkup(keyboard)
query.edit_message_text(text=f"Selected option {query.data}."
f"Are you sure ? ",
reply_markup=reply_markup)
def do_action_1(update: Update, context: CallbackContext) -> None:
keyboard = [[InlineKeyboardButton("Main menu", callback_data='main')]]
reply_markup = InlineKeyboardMarkup(keyboard)
query = update.callback_query
query.answer()
query.edit_message_text(text=f"Selected option {query.data}\n"
f"Executed action 1.",
reply_markup=reply_markup)
def do_action_2(update: Update, context: CallbackContext) -> None:
keyboard = [[InlineKeyboardButton("Main menu", callback_data='main')]]
reply_markup = InlineKeyboardMarkup(keyboard)
query = update.callback_query
query.answer()
query.edit_message_text(text=f"Selected option {query.data}\n"
f"Executed action 2.",
reply_markup=reply_markup)
def main() -> None:
"""Run the bot."""
# Create the Updater and pass it your bot's token.
updater = Updater("TOKEN")
updater.dispatcher.add_handler(CommandHandler('start', start))
updater.dispatcher.add_handler(CallbackQueryHandler(main_menu, pattern='main'))
updater.dispatcher.add_handler(CallbackQueryHandler(confirm, pattern='^(|1|2)$'))
updater.dispatcher.add_handler(CallbackQueryHandler(do_action_1, pattern='YES1'))
updater.dispatcher.add_handler(CallbackQueryHandler(do_action_2, pattern='YES2'))
# Start the Bot
updater.start_polling()
print('started')
if __name__ == '__main__':
main()
The bot now correctly asks for the user's confirmation before executing do_action_1 and do_action_2. Thanks a lot !
I'm here to ask you a way to ignore incoming messages from a user in python-telegram-bot.
I'm just specifying my situation to let you aware about what the purpose of the code i'm looking for.
So this is a code for a robot, the user sends the command /order and the bot asks which drink the user wants to order.
At this point, in order to avoid useless overlflow, i want to block the specific user (about 5 minutes) whom have just ordered for the sake of leaving the bot "free" for the others users.
Years ago, i looked at the specific option telegram had for groups, such as a timer chat, isn't it?
But i think is no possible at all in the private chat with the bot.
This is a simplified version of the code i'm working on.
from telegram import ReplyKeyboardMarkup, ReplyKeyboardRemove, Update
from telegram.ext import (
Updater,
CommandHandler,
MessageHandler,
Filters,
ConversationHandler,
CallbackContext,
)
WORK = range(1)
def cancel(update: Update, context: CallbackContext) -> int:
"""Cancels and ends the conversation."""
user = update.message.from_user
update.message.reply_text(
'Bye! I hope we can talk again some day.', reply_markup=ReplyKeyboardRemove()
)
return ConversationHandler.END
def order(update: Update, context: CallbackContext) -> int:
update.message.reply_text("please choose a drink")
update.message.reply_text("Sangria | Martini | Analcolico")
return WORK
def work(update: Update, context: CallbackContext)-> int:
ord_drink= update.message.text
update.message.reply_text("Okay i've just received your drink, you can order your drink in 5 MINUTES")
return ConversationHandler.END
def main() -> None:
"""Run the bot."""
# Create the Updater and pass it your bot's token.
updater = Updater("TOKEN")
# Get the dispatcher to register handlers
dispatcher = updater.dispatcher
#blocks the user after the first order for n minutes
conv_handler2 = ConversationHandler(
entry_points=[CommandHandler('order', order)],
states={
WORK : [MessageHandler(Filters.text & ~Filters.command, work)],
},
fallbacks=[CommandHandler('cancel', cancel)],
)
dispatcher.add_handler(conv_handler2)
# Start the Bot
updater.start_polling()
#updater.stop()
# 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()
LOOK AT IT:
The blocking function for the user must be called after his order, obviously, but the bot should be free for other users
If this bot is running locally you can create a temporary array in which you store the id of the user that just ordered.
At the same time, you start a Job that will execute 5 minutes later and in this Job you pop the id from the array.
# on top of the file
silented_user = []
# in the main function
j = updater.job_queue
# before the order is sent
if update.effective_user.id not in silented_user:
...
# after the order is sent
silented_user.append(update.effective_user.id)
j.run_one(pop_user, 5*60, context={'cid_to_pop':update.effective_user.id})
def pop_user(context):
silented_user.pop(context.job.context['cid_to_pop'])
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 use python-telegram-bot
this is my main.py
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
from telegram.ext import Updater, CommandHandler, CallbackQueryHandler, CallbackContext
def start(update: Update, context: CallbackContext) -> None:
"""Sends a message with inline buttons attached."""
keyboard = [
[
InlineKeyboardButton("Connect wallet", callback_data='connectwallet'),
],
update.message.reply_text('Please choose:', reply_markup=reply_markup)
def button(update: Update, context: CallbackContext) -> None:
"""Parses the CallbackQuery and updates the message text."""
query = update.callback_query
query.answer()
if query.data == 'connectwallet':
context.bot.send_message(chat_id=chat_id, text=f'This is test msg')
def main() -> None:
"""Run the bot."""
# Create the Updater and pass it your bot's token.
updater = Updater('180666756:AAGX__token__WdNO_YOVa7nA35EBXc')
updater.dispatcher.add_handler(CommandHandler('start', start))
updater.dispatcher.add_handler(CallbackQueryHandler(button))
updater.dispatcher.add_handler(CommandHandler('help', help_command))
# Start the Bot
updater.start_polling( )
# timeout=300
# Run the bot until the user presses Ctrl-C or the process receives SIGINT,
# SIGTERM or SIGABRT
updater.idle()
if __name__ == '__main__':
main()
But whenever I shut down main.py, change some code and restart it( Using ctrl+c and then python3 main.py)
Telegram bot freezes and stops responding to user commands. Sometimes it get's back to life, sometimes it doesn't and i need to restart bot from telegram.
I've tried to search a solution but didn't find it.
Any help would be appreciated.
That's because of telegram api limitations and if you are in local host maybe your ram and internet be slow. use heroku for deploying. Hope it will work also if you know threading you can use it it's good with its issue. Also make sure to enable logging if you need codes ask me I will give.