I'm looking for a way for a bot to wait for a reply from a user after a command. For example, you first type "/ask", then the bot waits for a plain message (not a command) from the user and after the user replies is stores his/her reply in a variable
I'm sure this is quite simple, but all the tutorials I've seen are in Russian and the documentation for python-telegram-api is very chaotic and I'm not the most advanced
If I'm dumb, sorry, just please help a fellow beginner out
Okay, this was pointless. I thought you couldn't use arguments, but the post I read was 5 years old so... I'm stupid. I just used arguments instead, thanks for the help tho, really appreciate it
here's the code that takes user's input and stores it in the "store_user_input" variable.
import logging
from telegram.ext import Updater,
CommandHandler, MessageHandler, Filters,
CallbackContext
from telegram import Update
logging.basicConfig(
format='%(asctime)s - %(name)s - %
(levelname)s - %(message)s',
level=logging.INFO
)
logger = logging.getLogger(__name__)
# function to handle the /start command
def startcommand(update:Update, context:
CallbackContext) -> None:
first_name = update.message.chat.first_name
update.message.reply_text\
(
f'Welcome to the bot, {first_name},
what are you interested in?'
)
# function to handle and store user input
def text(update:Update, context:
CallbackContext) -> None:
text_received = update.message.text
store_user_input = text_received
# function to handle the /help command
def helpcommand(update:Update, context:
CallbackContext) -> None:
update.message.reply_text('Here is a list of
all available commands:\n '
'/start - start the
bot\n'
'/help - get all
available commands\n')
# function to handle errors occured in the
dispatcher
def errormsg(update:Update, context:
CallbackContext) -> None:
update.message.reply_text('An error
occured.')
# main function
def main():
# "bot_data is your .txt file with bot API token"
fo = open("bot_data")
fr = fo.read()
updater = Updater(fr)
dispatcher = updater.dispatcher
# add handlers for start and help commands
dispatcher.add_handler(CommandHandler("start", startcommand))
dispatcher.add_handler(CommandHandler("help", helpcommand))
# add an handler for normal text (not commands)
dispatcher.add_handler(MessageHandler(Filters.text, text))
# add an handler for errors
dispatcher.add_error_handler(errormsg)
# start bot
updater.start_polling()
# run the bot
updater.idle()
if __name__ == '__main__':
main()
You can check the value by adding:
print("store_user_input")
after store_user_input line.
Related
I've created a bot to gather information in which users will forward messages to the bot from any other telegram chat/channel/group.
The forwarded messages can be of types video,photo,audio and url or any combination of these.
Below is where I am looking at:
https://core.telegram.org/bots/api#available-types
Right now I have (on Python)—>
#bot.message_handler(content_types=["video", "photo", "audio", "link"])
def send_welcome(message):
bot.reply_to(message, "got it")
BUT since there is no link content type, when the user forwards only a link, this doesn't work.
I'm looking for some property like forward_status = True if it exists.
According to the line #bot.message_handler(content_types=["video", "photo", "audio", "link"]) I suppose you are using pyTelegramBotAPI
This package allows you to create custom filters. Here you can find examples of how to create your own filters and how to register them. Actually, the package already has such filter - telebot.custom_filters.ForwardFilter (docs, source code)
Instancemessage.message_id is used to store a specific message ID in a certain user chat, then your bot should forward any message content of media, link, text or another available type, you could try whatever the bot configuration have to integrate these functions just as a simple demostration of your problem and different options from message, check message test for additional information.
image
import logging
from telegram import __version__ as TG_VER
try:
from telegram import __version_info__
except ImportError:
__version_info__ = (0, 0, 0, 0, 0) # type: ignore[assignment]
if __version_info__ < (20, 0, 0, "alpha", 1):
raise RuntimeError(
f"This example is not compatible with your current PTB version {TG_VER}. To view the "
f"{TG_VER} version of this example, "
f"visit https://docs.python-telegram-bot.org/en/v{TG_VER}/examples.html"
)
from telegram import ForceReply, Update
from telegram.ext import Application, CommandHandler, ContextTypes, MessageHandler, filters
# Enable logging
logging.basicConfig(
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO
)
logger = logging.getLogger(__name__)
# Define a few command handlers. These usually take the two arguments update and
# context.
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Send a message when the command /start is issued."""
user = update.effective_user
await update.message.reply_html(
rf"Hi {user.mention_html()}!",
reply_markup=ForceReply(selective=True),
)
async def forward_msg(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
await context.bot.forward_message(chat_id=update.effective_chat.id,
from_chat_id=update.effective_chat.id,
message_id=update.message.message_id)
def main() -> None:
"""Start the bot."""
# Create the Application and pass it your bot's token.
application = Application.builder().token("ID").build()
# on different commands - answer in Telegram
application.add_handler(CommandHandler("start", start))
messages_handler = MessageHandler(filters.TEXT, forward_msg)
application.add_handler(messages_handler)
# Run the bot until the user presses Ctrl-C
application.run_polling()
if __name__ == "__main__":
main()
I have found a workaround since I've decided to collect forwarded messages in text format as well.
#dp.message_handler(content_types = ['text'], is_forwarded = True)
async def check(message):
await message.reply("yay")
This will identify any forwarded messages in the text format.
I am not a professional programmer but I'm trying to build a python-telegram-bot for work using ConversationHandlers. Basically, I offer users a menu of options, summarized as:
Complete Survey
EXIT
If "Complete Survey" is selected, the bot then asks for the user ID. Depending on the user ID I assign the user 1 of 30+ different surveys (I'm trying to use child conversations). Over time this list of surveys will grow and each survey has unique questions and steps to it.
Given the number of surveys, I thought of managing each survey as a child conversation with its own ConversationHandler, and running it from a separate file/module (to keep things dynamic and not have one HUGE file with n+ variables to consider).
The thing is, how can I continue the child conversation from a separate file? Is there another way to approach this? I understand that the bot is still running from the main file and checking for updates. I would like to run each survey and, once finished, return to the INITIAL bot menu (parent conversation).
I found this previous discussion but my knowledge barely goes beyond the python-telegram-bot examples so I'm having a hard time following along: https://github.com/python-telegram-bot/python-telegram-bot/issues/2388
Here is an example summarized code of what I'm trying to do:
main_file.py
from telegram import ReplyKeyboardMarkup, ReplyKeyboardRemove, InlineKeyboardMarkup, InlineKeyboardButton, Update, KeyboardButton, Bot, InputMediaPhoto
from telegram.ext import Updater, CommandHandler, MessageHandler, Filters, ConversationHandler, CallbackQueryHandler, CallbackContext
import surveys # this file contains each survey as a function with its own ConversationHandler
token = ''
MENU, USER, CHAT_ID, USER_ID, FINISHED = map(chr, range(1,6))
END = ConversationHandler.END
def start(update: Update, context: CallbackContext) -> int:
"""Initialize the bot"""
context.user_data[CHAT_ID] = update.message.chat_id
text = 'Select an option:'
reply_keyboard = [
['Complete Survey'],
['EXIT'],
]
context.bot.send_message(
context.user_data[CHAT_ID],
text=text,
reply_markup=ReplyKeyboardMarkup(reply_keyboard, one_time_keyboard=True)
)
return MENU
def exit(update:Update, context:CallbackContext) -> None:
"""Exit from the main menu"""
context.bot.send_message(
context.user_data[CHAT_ID],
text='OK bye!',
reply_markup=ReplyKeyboardRemove()
)
return END
def abrupt_exit(update:Update, context:CallbackContext) -> None:
"""Exit the main conversation to enter the survey conversation"""
return END
def survey_start(update:Update, context:CallbackContext) -> None:
"""Asks for the user_id in order to determine which survey to offer"""
text = 'Please type in your company ID'
context.bot.send_message(
context.user_data[CHAT_ID],
text=text,
reply_markup=ReplyKeyboardRemove()
)
return USER
def survey_select(update:Update, context:CallbackContext) -> None:
"""Search database to find next survey to complete"""
user = str(update.message.text)
chat_id = context.user_data[CHAT_ID]
context.user_data[USER_ID] = user
"""Search database with user_id and return survey to complete"""
survey = 'survey_a' # this value is obtained from the database
runSurvey = getattr(surveys, survey) # I used getattr to load functions in a different module
runSurvey(Update, CallbackContext, user, chat_id, token)
return FINISHED
def main() -> None:
updater = Updater(token, use_context=True)
# Get the dispatcher to register handlers
dispatcher = updater.dispatcher
# Survey conversation
survey_handler = ConversationHandler(
entry_points=[
MessageHandler(Filters.regex('^Complete Survey$'), survey_start),
],
states={
USER: [
MessageHandler(Filters.text, survey_select),
],
FINISHED: [
# I'm guessing here I should add something to exit the survey ConversationHandler
],
},
fallbacks=[
CommandHandler('stop', exit),
],
)
# Initial conversation
conv_handler=ConversationHandler(
entry_points=[
CommandHandler('start', start),
],
states={
MENU: [
MessageHandler(Filters.regex('^Complete Survey$'), abrupt_exit),
MessageHandler(Filters.regex('^EXIT$'), exit),
],
},
allow_reentry=True,
fallbacks=[
CommandHandler('stop', exit),
],
)
dispatcher.add_handler(conv_handler, group=0) # I used separate groups because I tried ending
dispatcher.add_handler(survey_handler, group=1) # the initial conversation and starting the other
# Start the Bot
updater.start_polling()
updater.idle()
if __name__ == '__main__':
main()
surveys.py This is where each survey is with its own conversation and functions to call. Basically I enter survey_A (previously selected) and am trying to use it as the main()
from telegram import ReplyKeyboardMarkup, ReplyKeyboardRemove, InlineKeyboardMarkup, InlineKeyboardButton, Update, \
KeyboardButton, Bot, InputMediaPhoto
from telegram.ext import Updater, CommandHandler, MessageHandler, Filters, ConversationHandler, CallbackQueryHandler, \
CallbackContext
NEXT_QUESTION, LAST_QUESTION, CHAT_ID = map(chr, range(1,4))
END = ConversationHandler.END
def exit(update:Update, context:CallbackContext) -> None:
"""Exit from the main menu"""
context.bot.send_message(
context.user_data[CHAT_ID],
text='OK bye!',
reply_markup=ReplyKeyboardRemove()
)
return END
def first_q(update:Update, context:CallbackContext, chat_id:str) -> None:
"""First survey_A question"""
context.bot.send_message(
chat_id,
text='What is your name?',
reply_markup=ReplyKeyboardRemove()
)
return NEXT_QUESTION
def last_q(update: Update, context: CallbackContext) -> None:
"""Last survey_A question"""
update.message.reply_text(
'How old are you?', reply_markup=ReplyKeyboardRemove()
)
return LAST_QUESTION
def survey_a(update:Update, context:CallbackContext, user:str, chat_id: str, token:str) -> None:
"""This function acts like the main() for the survey A conversation"""
print(f'{user} will now respond survey_a')
CHAT_ID = chat_id # identify the chat_id to use
updater = Updater(token, use_context=True) # here I thought of calling the Updater once more
survey_a_handler = ConversationHandler(
entry_points=[
MessageHandler(Filters.text, first_q),
],
states={
NEXT_QUESTION: [
MessageHandler(Filters.text, last_q),
],
LAST_QUESTION: [
MessageHandler(Filters.text, exit),
],
},
allow_reentry=True,
fallbacks=[
CommandHandler('stop', exit),
],
)
updater.dispatcher.add_handler(survey_a_handler, group=0) # I only want to add the corresponding
# survey conversation handler
first_q(Update, CallbackContext, CHAT_ID)
I run the code and it breaks at surveys.py line 23, in first_q:
context.bot.send_message(
AttributeError: 'property' object has no attribute 'send_message'
I assume my logic with the conversation handler is way off.
I appreciate any help
I have been developing telegram bots for about a year now, and I hope the best approach is to structure your project first. Let me explain that all in detail.
"Foldering"
Folder structure
Basically, all the code is in the src folder of the project. Inside the src folder there is another sub-folder called components which includes all the different sections of your bot you want to work on (i.e your quiz_1, quiz_2, ...) and main.py file which includes the 'core' of the bot. However in the root directory of the project (which is just your project folder) you can see bot.py file which serves just as a runner file. So nothing more in there except just:
import src.main from main
if '__name__' == '__main__':
main()
Tips
So regarding your questionnaire:
I would recommend using just strings as keys for the states instead of mapping them to random values. Basically you can do just like "MAIN_MENU", "STATE_ONE" , "STATE_TWO" and so on, but be sure to return the same string in the callback function!
The overall logic of the PTB library is like:
Telegram API server -> PTB Updater() class -> Dispatcher() (which is updater.dispatcher in your code) -> Handlers -> callback function -> <- user.
The reason arrows point to user and back to callback function is because there is an interaction of your bot's logic and user, so that user's response goes back to your callback function code.
I recommend not choosing callback function names as like 'first_question' or 'second_question'. Instead name it like get_question() use that function to retrieve question data from other source so that it can be dynamic. So for example, you will have a dictionary of different questions with keys of question number - simple, right? And then you will write a function that will send user a question according to its state and picking the right question with the right key from the dictionary. By this you can add more questions to your dictionary and no need to change the code in the function because it will be dynamic (as long as you write the correct function that will work).
In your main.py file have only one main() function which will hold the Updater() with the given token, because you cannot have more than one Updater() with the same token. It's like one bot can be accessed by only and only one app that is polling at a time. Polling - visit here.
🎉 Great news!
To support your bot development and follow the structured project creation, I have created a repo on GitHub that holds almost the same project structure as I tried to explain to you today. Feel free to check it out, clone it and play around. Just add your token to .env file and run the bot.
More resources
Check out these projects as well:
https://github.com/ez-developers/traderbot/tree/master/bot
https://github.com/ez-developers/equip.uz/tree/main/bot
As you will see in there main.py contains all the handlers and src folder contains all the different 'components' which are more of a like different parts of the bot.
If you need any help, I am here and more than happy to answer any of your questions.
The logic is the following:
With the /start command the bot shows the Main Menu with buttons (each button represents a file the user wants to get access to);
When any button is pressed, the conversation starts where the bot asks for a gmail address;
The user sends their gmail address, the bot checks it, if the address format is correct then the bot grants the permission to view the file and posts the link to the chat.
I used these examples as my starting point:
https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/conversationbot.py
https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/conversationbot2.py
My code is this one:
from telegram import (
Bot,
Update,
InlineKeyboardMarkup,
InlineKeyboardButton,
)
from telegram.ext import (
Updater,
CommandHandler,
MessageHandler,
Filters,
CallbackContext,
CallbackQueryHandler,
ConversationHandler,
)
def startCommand(update: Update, context: CallbackContext):
keyboardMarkup = InlineKeyboardMarkup(
[[InlineKeyboardButton('Share File 1', callback_data='sharingFile1')]]
)
update.message.reply_text(f'Howdy, {update.effective_user.first_name}.\nThis is the Main Menu.',
reply_markup=keyboardMarkup)
def convGetGMailAddr(update: Update, context: CallbackContext):
update.message.reply_text('Waiting for your gmail address.\n\nSend /end and I\'ll stop waiting.')
return convEmailAddr
def convMismatch(update: Update, context: CallbackContext):
text = f"""Sorry, I don't understand this gmail address.
Please, send me your gmail address again.\n\nSend /end and I\'ll stop waiting.
"""
update.message.reply_text(text)
return convEmailAddr
def convGiveLink(update: Update, context: CallbackContext):
link = 'https://docs.google.com/spreadsheets/d/1ZP1xZ0WaH8w2yaQTSx99gafNZWawQabcdVW5DSngavQ'
update.message.reply_text(f'Thank you! Here\'s your link to the shared file:\n{link}')
return ConversationHandler.END
def convEnd(update: Update, context: CallbackContext):
update.message.reply_text('I\'ve stopped waiting.\n\nSend /start to go to the Main Menu.')
return ConversationHandler.END
def sharingFileHandler(update: Update, context: CallbackContext):
if update.callback_query.data == 'sharingFile1':
update.callback_query.edit_message_text(
update.effective_message.text,
reply_markup=InlineKeyboardMarkup([])
)
conv_sharing = ConversationHandler(
entry_points=[MessageHandler(Filters.regex('.*[File 1]*.*'), convGetGMailAddr)],
states={
convEmailAddr: [
MessageHandler(~Filters.regex('.*#gmail.com$') & ~Filters.command, convMismatch),
MessageHandler(Filters.regex('.*#gmail.com$'), convGiveLink),
],
},
fallbacks=[CommandHandler('end', convEnd)],
)
disp.add_handler(conv_sharing)
bot.send_message(update.effective_chat.id, 'I\'ll share the File 1 with you.')
bot_token = 'abcd1234'
bot = Bot(bot_token)
updater = Updater(bot_token, use_context=True)
convEmailAddr = ''
disp = updater.dispatcher
disp.add_handler(CommandHandler('start', startCommand))
disp.add_handler(CallbackQueryHandler(sharingFileHandler))
updater.start_polling(drop_pending_updates=True)
updater.idle()
The issue is that the bot doesn't read it's own reply in the function sharingFileHandler to start the conversation handler. The entry point of the conversation is posting the string "File 1" and when I send something like "asdklhasdlkh file 1 asdaskldha" then everything works fine.
Another question is is it possible for the bot to listen to email addresses only inside of the conversation? Right now the function convGetGMailAddr starts at any moment.
Update 1 (2021-10-20)
Based on the CallMeStag's answer I changed my code.
Deleted the function convGetGMailAddr and modified the function sharingFileHandler:
def sharingFileHandler(update: Update, context: CallbackContext):
if update.callback_query.data == 'sharingFile1':
update.callback_query.edit_message_text(
update.effective_message.text,
reply_markup=InlineKeyboardMarkup([])
)
text = f"""I\'ll share the File 1 with you to your Google account.
Please, send me your gmail address.\n\nSend /end and I\'ll stop waiting."""
bot.send_message(update.effective_chat.id, text)
return convEmailAddr
bot_token = '1234abcd'
bot = Bot(bot_token)
updater = Updater(bot_token, use_context=True)
convEmailAddr = ''
disp = updater.dispatcher
disp.add_handler(CommandHandler('start', startCommand))
conv_sharing = ConversationHandler(
entry_points=[CallbackQueryHandler(sharingFileHandler)],
states={
convEmailAddr: [
MessageHandler(~Filters.regex('.*#gmail.com$') & ~Filters.command, convMismatch),
MessageHandler(Filters.regex('.*#gmail.com$'), convGiveLink),
],
},
fallbacks=[CommandHandler('end', convEnd)],
)
disp.add_handler(conv_sharing)
updater.start_polling(drop_pending_updates=True)
updater.idle()
Now my bot does exactly what I want and it stopped doing what I wanted it to stop doing. 🙂
Thank you, CallMeStag!
You're building a new conversationhandler & adding it to the dispatcher every time sharingFileHandler is called. that's surely not what you want. You should instead build it only once and add it to the dispatcher also only once right where you add the other handlers (at the very end of your snippet).
You should then make CallbackQueryHandler(sharingFileHandler) the entry point of that conversation. this will automatically solve your second problem.
Disclaimer: I'm currently the maintainer of python-telegram-bot.
im creating a im creating a telegram bot using python and i have created a function that just gets the orginal messag's users id by a reply of another user...
let me explain,
incase there are A & B in a telegram group,
A : "hi"
B : tags/replies to A's "hi" by "/info"
in this case i need how does the bot in the group get A's info by the "/info" given (replied) out by B?
so i wrote a piece of code to check that :
import logging
from telegram.ext import Updater, CommandHandler, MessageHandler, Filters
from typing import Optional, List
from telegram import Message, Chat, Update, Bot, User
from telegram.error import BadRequest
from telegram.ext.dispatcher import run_async
from telegram.utils.helpers import mention_html
updater = Updater("1262215479:AAHtwK5J-6lP8iw9b7uRjcOaazelRkHgq3s", use_context=True)
dp = updater.dispatcher
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.INFO)
logger = logging.getLogger(__name__)
def mute(bot: Bot, update: Update, args):
chat = update.effective_chat # type: Optional[Chat]
user = update.effective_user # type: Optional[User]
message = update.effective_message # type: Optional[Message]
user_id = extract_user(message, args)
member = chat.get_member(int(user_id))
print("hey1")
def main():
dp.add_handler(CommandHandler("promote", mute))
updater.start_polling()
updater.idle()
if __name__ == '__main__':
main()
and i get 'TypeError: mute() missing 1 required positional argument: 'args'' error
The reason you get an error is that args needs an argument but what you mean is
*args with an asterisk.
I'm working on my wetherstation which collects temperature and humidity every minute. To this project I would like to add a Telegram bot which sends me a message if the data record gets stopped.
I downloaded the Telegram bot library and made some tests with my Telegram bot.
For now, my bot is able to answer if I request, for example, the current humidity by sending /humidity to my bot.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Simple Bot to reply to Telegram messages
# This program is dedicated to the public domain under the CC0 license.
"""
This Bot uses the Updater class to handle the bot.
First, a few handler functions are defined. Then, those functions are passed to
the Dispatcher and registered at their respective places.
Then, the bot is started and runs until we press Ctrl-C on the command line.
Usage:
Basic Echobot example, repeats messages.
Press Ctrl-C on the command line or send a signal to the process to stop the
bot.
"""
from telegram.ext import Updater, CommandHandler, MessageHandler, Filters
import logging
import read_from_database
# Enable logging
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.INFO)
logger = logging.getLogger(__name__)
# Define a few command handlers. These usually take the two arguments bot and
# update. Error handlers also receive the raised TelegramError object in error.
def start(bot, update):
update.message.reply_text('Hi!')
def status(bot, update):
zeitstempel = read_from_database.get_timestamp()
update.message.reply_text('letzter Eintrag: {:%d.%m.%Y %H:%M}'.format(zeitste$
def temperatur(bot, update):
temperatur = read_from_database.get_temperature()
# update.message.reply_text('letzter Temperatureintrag: %s ' % temperatur + u'$
update.message.reply_text('letzter Temperatureintrag: %s C' % temperatur)
def feuchtigkeit(bot, update):
feuchtigkeit = read_from_database.get_humidity()
update.message.reply_text('letzter Feuchtigkeitseintrag: %s %%' % feuchtigkei$
def echo(bot, update):
update.message.reply_text(update.message.text)
def error(bot, update, error):
logger.warn('Update "%s" caused error "%s"' % (update, error))
def main():
# Create the EventHandler and pass it your bot's token.
updater = Updater("TOKEN")
# 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("status", status))
dp.add_handler(CommandHandler("feuchtigkeit", feuchtigkeit))
dp.add_handler(CommandHandler("temperatur", temperatur))
# on noncommand i.e message - echo the message on Telegram
#dp.add_handler(MessageHandler(Filters.text, echo))
# log all errors
dp.add_error_handler(error)
# Start the Bot
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()
But I have no idea how to send out an alarm when my python script runs into an exception during the record. Is there a possibility to send a message from my python script without sending a request to my Telegram bot?
use updater.bot.send_message to send message