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.
Related
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)
I'm having a problem with handling incoming messages. I have a main menu in my bot with one InlineKeyboardMarkup. When the button is pressed, the bot should wait for user input and then send a message. But in my case, it sends the message, then it waits for user input and then it sends again the message. And that's a big problem, because after the message it sends, it should go back to main menu.
I'm using pythonTelegramBotAPI (telebot)
That's the code:
#bot.callback_query_handler(func = lambda call: True)
def query_handler(call):
bot.answer_callback_query(callback_query_id = call.id, text = '')
cid = call.message.chat.id
mid = call.message.message_id
msg = call.message
if call.data == 'request':
bot.edit_message_text('Test - Answer to request', cid, mid, reply_markup = markup)
request_handler(msg)
################# Keyboard sections #################
#bot.message_handler()
def request_handler(msg):
if msg.content_type == 'text':
bot.send_message(msg.chat.id, 'Request accepted')
# and here the code to go back, that I didn't do yet
Perhaps this conversational bot example will help you to understand how to build dialog with user.
Correct answer
The decorator #bot.messge_handler() is used to tell to the function that it must be handle incoming messages. So, when there's a new message, the function will do the code inside it automatically. This means that the function must not be called manually as is not required. Calling the function will, in fact, run the code inside it, instead of initializing it and waiting for an input.
And here's a tip: if the user send a message, the function is automatically called. But what happens when the bot send a message? The function is also called, because it doesn't make difference about who sent the message. So, to avoid using the function also for bot messages, just put an if in like this:
if msg.from_user.is_bot == False:
# Here put what the function should do
The if, as you can see, check if the message is coming from the bot.
I am currently using python-telegram-bot library to make a telegram bot. My problem is I am trying to have my bot respond back when using the inline command. So when a user sends the bot #botname 'text', I want the to store the 'text' as a string and then have my bot send something back with that variable.
For some reason I can not get this to work. I tried the code below, but it doesn't work...I also posted the example from the github that works but not in the way i want.
My code
def inlinequery(update, context):
"""Handle the inline query."""
query = update.inline_query.query
text = query.message_text
print(text)
update.message.reply_text(text)
Example Code
#Sends message when #botname is used
def inlinequery(update, context):
"""Handle the inline query."""
query = update.inline_query.query
results = [
InlineQueryResultArticle(
id=uuid4(),
title="Caps",
input_message_content=InputTextMessageContent(
query.upper())),
InlineQueryResultArticle(
id=uuid4(),
title="Bold",
input_message_content=InputTextMessageContent(
"*{}*".format(escape_markdown(query)),
parse_mode=ParseMode.MARKDOWN)),
InlineQueryResultArticle(
id=uuid4(),
title="Italic",
input_message_content=InputTextMessageContent(
"_{}_".format(escape_markdown(query)),
parse_mode=ParseMode.MARKDOWN))]
update.inline_query.answer(results)
def main():
# Get the dispatcher to register handlers
dp = updater.dispatcher
dp.add_handler(InlineQueryHandler(inlinequery))
# Start the Bot
updater.start_polling()
if __name__ == '__main__':
main()
You can use the User object of the inline query to send them a message. Keep in mind that the user has to have started a private chat with the bot before the bot can send them messages.
I modified your attempt. It should work, but i have not tested it:
def inlinequery(update, context):
"""Handle the inline query."""
query = update.inline_query
text = query.query
print(text)
query.from_user.send_message(text)
Related docs:
InlineQuery.user
User.send_message
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
I created a bot in Lex and then created two intents within same bot - intent1 and intent2 with utterance get me a taxi now and I wan a taxi to {Location} on {TravelDate} at {TaxiTime} respectively(first one in intent1 and second one in intent2). Both the intents call different lambda function and inside lambda functions I access RDS to add the booking info for taxi. When I test from Lex console by saying either of the two utterances the lambda function executes completely as I can see database record updated but on Lex bot test console I see Reached second execution of fulfillment lambda on same utterance Lex error. In my code I have this line:
def delegate(session_attributes, slots):
return {
'sessionAttributes': session_attributes,
'dialogAction': {
'type': 'Delegate',
'slots': slots
}
}
def book_taxi(intent_request):
confirmation_status = intent_request['currentIntent']['confirmationStatus']
#bunch of other processing code
logger.debug('Confirmation = {}'.format(confirmation_status))
if confirmation_status == 'Confirmed':
try_ex(lambda: session_attributes.pop('confirmationContext'))
logger.debug('confirmation_context = {}'.format(confirmation_context))
if confirmation_context == 'AutoPopulate':
return elicit_slot(
session_attributes,
intent_request['currentIntent']['name'],
intent_request['currentIntent']['slots']
)
return delegate(session_attributes, intent_request['currentIntent']['slots'])
logger.debug('Booked Taxi at={}'.format(reservation))
My guess is that the delegate() call in code above is causing the issue because in my log files i can see the first two debug logs as Confirmed and None values but the last logger.debug() is not in the log file which means delegate() got called and thats is causing error on Lex console.
What could be the possible issue for this error?
Your Utterance May contains the text that invoke a more than one intent.
that's why the problem raised.
you chack the uttrances in both the intents and remove one from two similar type utterances.