python telegram bot ignoring messages - python

From time to time my telegram bot seems to ignore messages. Unfortunately it never happens to me but to other users and only when they are asked to upload a photo or pdf. Sometimes the corresponding handler is not called. The problem persists even when the MessageHandler has no filters at all. I am using python-telegram-bot v13.7.
Please find below a minimal sample. It should print "receiving something" whenever the handler is called but sometimes it doesn't.
Is there anything I can do?
EDIT: modified the sample to be a MWE (you have to provide a valid telegram bot ID). Most of the time it works just fine but sometimes it will fail and not print "receiving something" although the user uploaded some document (mostly images).
from telegram import Update
from telegram.ext import (
Updater,
CommandHandler,
MessageHandler,
Filters,
ConversationHandler,
CallbackContext,
)
UPLOAD = 0
def welcome(update: Update, context: CallbackContext) -> int:
update.message.reply_text('Available commands: /upload')
return ConversationHandler.END
def upload(update: Update, context: CallbackContext) -> int:
update.message.reply_text('Please upload document.')
return UPLOAD
def receive(update: Update, context: CallbackContext) -> int:
print('receiving something...')
update.message.reply_text('Thank you.')
return ConversationHandler.END
def cancel(*args) -> int:
print('cancelled')
return welcome(*args)
def handle(*args, **options):
updater = Updater(" ...... ")
dispatcher = updater.dispatcher
conv_handler = ConversationHandler(
entry_points=[CommandHandler('upload', upload),
MessageHandler(Filters.text, welcome)
],
states={
# UPLOAD: [CommandHandler('cancel', cancel), MessageHandler(Filters.photo | Filters.document.pdf, receive)],
UPLOAD: [CommandHandler('cancel', cancel), MessageHandler(Filters.all, receive)],
},
fallbacks=[CommandHandler('cancel', cancel)],
conversation_timeout=60
)
dispatcher.add_handler(conv_handler)
updater.start_polling()
updater.idle()
handle()

One situation where such a behaviour can occure is a timeout. You have defined a timeout of 60 seconds and if the time is over, the bot silently does nothing and can not react to your uploaded file, because it's not anymore in the correct state for that. If this is you problem you can easily check this by defining a function triggering in the timeout-state.
I changed 2 things of your code:
add a timeout function:
def timeout(*args):
print("timeout")
add function to timeout state (ConversationHandler.TIMEOUT)
...
conv_handler = ConversationHandler(
entry_points=[CommandHandler('upload', upload),
MessageHandler(Filters.text, welcome)],
states={
UPLOAD: [CommandHandler('cancel', cancel), MessageHandler(Filters.all, receive)],
ConversationHandler.TIMEOUT: [MessageHandler(Filters.all, timeout)]
},
fallbacks=[CommandHandler('cancel', cancel)],
conversation_timeout=2,
)
...
(3. Reduce timeout for easier testing :))
From the documentation of conversation_timeout:
When this handler is inactive more than this timeout (in seconds), it will be automatically ended. If this value is 0 or None (default), there will be no timeout. The last received update and the corresponding context will be handled by ALL the handler’s who’s check_update method returns True that are in the state ConversationHandler.TIMEOUT.
A second situation where a bot CAN ignore messages is, when multiple users use the bot simultaneously. As far as i know CAN this lead to a situation where a user "hijacks" anothers state, so that the other users isn't anymore in the state of "uploading", so that this users can not upload a file until he "starts" the upload state.
(I am not 100% shure about this, but you should give it a try)

Related

Telegram python check key value from JSON

i'm creating a telegram bot using pyhton, specifically the following module
https://github.com/python-telegram-bot/python-telegram-bot
What i want to do is:
send input to the bot
read the update object to analyze each field
check if 'text' key is present
do something if yes
My current python implementation:
def echo(update: Update, context: CallbackContext) -> None:
if 'text' in update.message:
update.message.reply_text('I found your key value you are looking for')
else:
update.message.reply_text('Key not found')
def main():
"""Start the bot."""
# Create the Updater and pass it your bot's token.
updater = Updater(MY_TOKEN)
# Get the dispatcher to register handlers
dispatcher = updater.dispatcher
dispatcher.add_handler(MessageHandler(Filters.text & ~Filters.command, echo))
updater.start_polling()
updater.idle()
if __name__ == '__main__':
main()
Structure of update object:
{
"update_id":id_update,
"message":{
"message_id":1579,
"date":1615193338,
"chat":{
"id":id_chat,
"type":"private",
"username":"XXX",
"first_name":"XXX"
},
"text":"Hello Bot",
"entities":[
],
"caption_entities":[
],
"photo":[
],
"new_chat_members":[
],
"new_chat_photo":[
],
"delete_chat_photo":false,
"group_chat_created":false,
"supergroup_chat_created":false,
"channel_chat_created":false,
"from":{
"id":id_chat,
"first_name":"xxx",
"is_bot":false,
"username":"xxx",
"language_code":"it"
}
}
}
When i test it i didn't get any output from the bot, it seems like it is ignoring the if/else condition.
If i print the update.message.text i see correctly the input sent to the bot.
Thank you all
EDIT
I found the solution, i had to change the filter passed to MessageHandler in this way
dispatcher.add_handler(MessageHandler(Filters.all, echo))
Thanks anyway for the help
Your edit is very likely not the actual solution. Using Filters.all instead of Filters.text & ~Filters.command just says that the MessageHandler will catch any message and not just messages that contain text and don't start with a botcommand.
The problem rather is that 'text' in update.message can't work as update.message is a telegram.Message object and not iterable. Therefore 'text' in update.message will probably throw an error, which you don't see as you have neither logging enabled nor an error handler registered (see the PTB readme and wiki, respectively for info on logging & error handlers).
My guess is that changing to 'text' in update.message.text should do the trick.

Best practice in sending message in python telegram bot from a 3d party listener

I have a custom code that does its routine and I want to send a message to myself in Telegram if something goes wrong. In my case I use python-telegram-bot library along with apscheduler and its listeners, where certain events could be catched.
I came up with such working code, but my question is: is it possible to make it better, namely without using global variable? This was done to overcome the problem that listeners do not accept arguments needed for bot to send a message.
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.events import EVENT_JOB_EXECUTED, EVENT_JOB_ERROR
from telegram.ext import Updater, CommandHandler
import copy
import my_custom_library
saved_update = None
def my_listener(event): # not related with bot
if event.exception:
if saved_update is not None:
alert(saved_update, 'Scheduler threw event.exception.') # should have bot related args
else:
record = event.retval # get returned value from my_custom_library.repetitive_function
try:
processed_record = my_custom_library.my_unsafe_business_logic(record) # something might go wrong here
my_custom_library.add_to_db(processed_record) # and here
except Exception as e:
if saved_update is not None:
alert(saved_update, e) # should have bot related args
def start(update, context):
global saved_update
saved_update = copy.deepcopy(update) # this is what I don't like
update.message.reply_text('You have subscribed for notifications.')
def alert(update, reason):
update.message.reply_text('Something went wrong: {}'.format(reason))
def main():
scheduler = BackgroundScheduler()
scheduler.add_listener(my_listener, EVENT_JOB_EXECUTED | EVENT_JOB_ERROR)
scheduler.add_job(my_custom_library.repetitive_function, args=(my_args,), trigger='interval', minutes=1)
scheduler.start()
# bot
updater = Updater(TOKEN, use_context=True)
dp = updater.dispatcher
dp.add_handler(CommandHandler("start", callback=start))
updater.start_polling()
updater.idle()
if __name__ == '__main__':
main()
The Telegram Bot API is fairly simple, you just ned to send an HTTP GET Request to this URL:
https://api.telegram.org/bot_token_/sendMessage?chat_id=123&text=Hello%20World!
Just create a bot with Botfather and send the Bot a message.
With the specified Token from Botfather and this URL:
https://api.telegram.org/bot_token_/getUpdates
You can get the messages which were sent to the Bot and the chat_id.
The simplest way would be to use the requests module and send the output of the updater to the first URL as the text parameter.
In my scripts I often use callbacks. It's a clean solution that provides separation between the two scripts. The send_message function can accept kwargs, so you can essentially create a dictionary, and update it when sending the message. The first part (channel ID) is something you know on the bot-side, and the second part (the text itself) is something you know one the third-party side.
In your third-party, provide a set_cb function that accepts a callback and a dictionary. Like so:
def set_cb(self, callback, params):
self.callback = callback
self.callback_params = params
In your bot script, set the callback before updater.idle()
# Start the Bot
updater = Updater("TOKEN")
updater.start_polling()
# Set the callback
r.set_cb(updater.bot.send_message, {"chat_id": 123456})
Then, in your third-party, once you want to send a message, simply add the message text call the following:
self.callback_params.update({"text": text})
self.callback(**self.callback_params) # converts the dict to kwargs

Best practice for a back button implementation in telegram conversation bot

I'm trying to create a telegram conversation bot using python-telegram-bot package and I wondered what is the best practice for implementing a "back to main menu" button for each state in the conversation.
For some reason, I feels like i'm doing it the wrong way. The back to main menu button actually does nothing except to drawing the main menu buttons which should be immediate, but still it's usually takes 0.5-3 seconds and sometimes even longer (Only one user at a time talking to the bot).
I'm using CallbackQueryHandler(back_to_main_menu, pattern='^main_menu$') for each state basically which I'm quite sure it's not the right way to do it although I couldn't find any other better solution so far.
Notice: the example below is just a little example of how I use the CallbackQueryHandler. My state machine got 13 states, in 10 of them I got a back to main menu button.
A short example of what I need:
import os
from telegram.ext import Updater, ConversationHandler, CommandHandler, CallbackQueryHandler
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, ReplyKeyboardRemove
FIRST, SECOND, MAIN_MENU = range(3)
def get_main_menu():
return [[InlineKeyboardButton("First", callback_data='first')],
[InlineKeyboardButton("Second", callback_data='second')]]
def add_main_menu_button(bot, update, message, menu):
query = update.callback_query
menu.append([InlineKeyboardButton("Back to main menu", callback_data="main_menu")])
reply_markup = InlineKeyboardMarkup(menu)
bot.edit_message_text(message, chat_id=query.message.chat_id, message_id=query.message.message_id,
reply_markup=reply_markup)
def back_to_main_menu(bot, update):
query = update.callback_query
reply_markup = InlineKeyboardMarkup(get_main_menu())
bot.edit_message_text("Example example 2", chat_id=query.message.chat_id, message_id=query.message.message_id, reply_markup=reply_markup)
return MAIN_MENU
def second(bot, update):
add_main_menu_button(bot, update, "E e", list())
def first(bot, update):
add_main_menu_button(bot, update, "T t", list())
def start(bot, update):
reply_markup = InlineKeyboardMarkup(get_main_menu())
update.message.reply_text("Example example 1", reply_markup=reply_markup)
return MAIN_MENU
def cancel(bot, update):
update.message.reply_text('Bye! I hope we can talk again some day.', reply_markup=ReplyKeyboardRemove())
return ConversationHandler.END
def main():
updater = Updater(os.environ["BOT_TOKEN"])
conversation_handler = ConversationHandler(entry_points=[CommandHandler('start', start)],
states={FIRST: [CallbackQueryHandler(back_to_main_menu, pattern='^main_menu$')],
SECOND: [CallbackQueryHandler(back_to_main_menu, pattern='^main_menu$')],
MAIN_MENU: [CallbackQueryHandler(first, pattern='first'),
CallbackQueryHandler(second)]},
fallbacks=[CommandHandler('cancel', cancel)],
allow_reentry=True
)
updater.dispatcher.add_handler(conversation_handler)
updater.start_polling()
updater.idle()
In addition, when I use multiple CallbackQueryHandler in a conversation I get the following warning:
WARNING:root:If 'per_message=False', 'CallbackQueryHandler' will not be tracked for every message
which makes me think I'm doing something wrong.
Thanks.
For each conversation you only need to specify one CallbackQueryHandler as a fallback. For example:
START = range(1)
start_conv_handler = ConversationHandler(entry_points=[CommandHandler('start', start)],
states={
START: [CommandHandler('start', start)]
},
fallbacks= CallbackQueryHandler(main_callback_handler)]
)
And then within that single CallbackQueryHandler you can plan all the neceesary actions you want your buttons to perform. Since you can add whatever information you need to the query that the handler receive, you can play with more complicated scenarios by putting return CallbackQueryHandler at the end of each function that describes a conversation step (start in my example).
So it would look like:
def start(bot, update):
... your code here
return CallbackQueryHandler

Callbackquery handler issue in python telegram bot

In my code I'm facing an issue with callbackquery handler, when I'm hit /start command Next button appear and when I'm hitting on that button it gives me reply as hi, till this output is correct. Then when I'm hitting another command /help then help button appears, when I hit that help button then it gives me same reply for next button is hi.
Conclusion: Is there is way to kill old callbackquery handler. I found way is return Conversationhandler.END from callbackquery handler function but it limits my functionality had googled for it but no found expected output.
Here is my code:
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import Updater, CommandHandler, CallbackQueryHandler, ConversationHandler
TELEGRAM_HTTP_API_TOKEN = 'token'
FIRST, SECOND, HELP = range(3)
def start(bot, update):
keyboard = [
[InlineKeyboardButton(u"Next", callback_data=str(FIRST))]
]
reply_markup = InlineKeyboardMarkup(keyboard)
update.message.reply_text(
u"Start handler, Press next",
reply_markup=reply_markup
)
return FIRST
def first(bot, update):
query = update.callback_query
#reply_markup = InlineKeyboardMarkup(keyboard)
bot.send_message(chat_id=query.message.chat_id,
text='hi')
def help(bot,update):
keyboard = [
[InlineKeyboardButton(u"HELP", callback_data=str(HELP))]
]
reply_markup = InlineKeyboardMarkup(keyboard)
update.message.reply_text(
u"Help handler, Press button",
reply_markup=reply_markup
)
return HELP
def myhelp(bot,update):
query = update.callback_query
bot.send_message(chat_id=query.message.chat_id,
text='help')
updater = Updater(TELEGRAM_HTTP_API_TOKEN)
conv_handler = ConversationHandler(
entry_points=[CommandHandler('start', start)],
states={
FIRST: [CallbackQueryHandler(first)]
},
fallbacks=[CommandHandler('start', start)]
)
conv_handler1=ConversationHandler(
entry_points=[CommandHandler('help',help)],
states={
HELP: [CallbackQueryHandler(myhelp)]
},
fallbacks=[CommandHandler('help',help)]
)
updater.dispatcher.add_handler(conv_handler)
updater.dispatcher.add_handler(conv_handler1)
updater.start_polling()
updater.idle()
This is code screenshot output for more detail
Any kind of help is welcome.
You need to make your bot persistent (so state don't get lost), add error handler (to know if something failed) and fallback route (if there is not route matching).
In that case you'll know what's going wrong and where.

How to receive file_id through python-telegram-bot?

I'm making a Telegram bot using python-telegram-bot, and I need some way to receive voice messages. For that, I need to download them, and to do that, I have to get their file_ids. However, the MessageHandler handles... well, messages, and Handler gives me a NotImplementedError. Is there a way to get the file_id?
I know this question is old but I was facing a problem with this in the latest version (12+)
So it appears that the bot- pass_user_data in the callback function is deprecated and from now on you should use context based callbacks.
CallbackContext is an object that contains all the extra context
information regarding an Update, error or Job.
to the new style using CallbackContext:
def voice_handler(update: Update, context: CallbackContext):
file = context.bot.getFile(update.message.audio.file_id)
file.download('./voice.ogg')
You can read more in the Transition-guide-to-Version-12.0
The easiest way to download voice messages is to register a MessageHandler with a voice filter. The Docs provide more information on Filters and the voice module.
import telegram
from telegram.ext import Updater
def voice_handler(bot, update):
file = bot.getFile(update.message.voice.file_id)
print ("file_id: " + str(update.message.voice.file_id))
file.download('voice.ogg')
updater = Updater(token='TOKEN')
dispatcher = updater.dispatcher
dispatcher.add_handler(MessageHandler(Filters.voice, voice_handler))
In version 13+, you need to use update.message.voice.file_id instead of update.message.audio.file_id. So the code will be:
def voice_handler(update: Update, context: CallbackContext):
file = context.bot.getFile(update.message.voice.file_id)
file.download('./voice.ogg')
I'll show you an example with a photo file, but it works for any file (you'll just need to change the parameters)
from telegram.ext import Updater, CommandHandler
from telegram.ext.callbackcontext import CallbackContext
from telegram.update import Update
def start (update: Update, context: CallbackContext):
# getting chat_id:
chatID = update.effective_chat.id
# sending the photo to discover its file_id:
photo1 = context.bot.send_photo(chat_id=chatID, photo=open('photo1.jpg','rb'))
photo1_fileID = photo1.photo[-1].file_id
context.bot.send_message(chat_id=update.effective_chat.id, text=('file_id photo1.jpg = ' + photo1_fileID))
def main():
updater = Updater(token='TOKEN', use_context=True)
dispatcher = updater.dispatcher
dispatcher.add_handler(CommandHandler('start', start))
updater.start_polling()
updater.idle()
if __name__ == '__main__':
main()

Categories