Reputation: 320
I would like to have a little Telegram bot in python where a command is issued by the user, the bot asks a question and reacts depending on the answer. The official Telegram API mentions that this is possible using ForceReply(), e.g. for creating polls step by step, see here Official Telegram API # ForceReply.
I wrote the following code:
def test_function(update: Update, context: CallbackContext) -> None:
msg = context.bot.send_message(chat_id=update.message.chat_id,
text="Please enter some text.",
reply_markup=telegram.ForceReply(True))
if __name__ == '__main__':
dispatcher.add_handler(CommandHandler("test", test_function))
updater.start_polling()
updater.idle()
So when /test
is issued by the user, he is asked to enter some text and due to the ForceReply(True)
, the user is forced to reply to the message. But how do I get the result of this reply, i.e. the text replied by the user? There is no hint in the API docs and I also spent some time searching on the internet and on SO but didn't find anything. Therefore, most likely the answer is simple and clear but I just don't get it at the moment.
Any kind of help is appreciated.
Upvotes: 3
Views: 5710
Reputation: 5435
You receive the answer as a general message (see any demo on echo-bot), and that test if it is a reply to your message. If it is, you can process the answer and proceed further.
Example:
# Mock database :-)
question_id = None
# This filter allows to catch only a reply-to messages.
@dp.message_handler(aiogram.dispatcher.filters.IsReplyFilter(True))
async def echo_task(message: types.Message):
"""Here I catch all replies to the messages to treat them as answers."""
if message.reply_to_message.message_id == question_id:
await bot.send_message(message.chat.id,
f"Welcome, Mr(s) {message.text}")
else:
await bot.send_message(message.chat.id,
"You'd better come to desk 11, sir(madam)")
await bot.unpin_chat_message(message.chat.id, question_id)
# Here we catch any general messages user typed.
@dp.message_handler(aiogram.dispatcher.filters.IsReplyFilter(False))
async def echo_task(message: types.Message):
"""Global receiver of the data we didn't handle yet."""
global question_id
bot_message = await bot.send_message(message.chat.id,
"May I ask your name, please?",
reply_markup=aiogram.types.ForceReply())
question_id = bot_message.message_id
await bot.pin_chat_message(message.chat.id, question_id)
I pinned and unpinned questions as a part of an experiment - just drop it, if not needed.
Upvotes: 0
Reputation: 629
Every step is a state of user.
It's called FSM
(finite state machine
).
You don't even need to do something with ForceReply
.
import logging
import aiogram.utils.markdown as md
from aiogram import Bot, Dispatcher, types
from aiogram.contrib.fsm_storage.memory import MemoryStorage
from aiogram.dispatcher import FSMContext
from aiogram.dispatcher.filters import Text
from aiogram.dispatcher.filters.state import State, StatesGroup
from aiogram.types import ParseMode
from aiogram.utils import executor
bot = Bot(token='your:token')
storage = MemoryStorage() # external storage is supported!
dp = Dispatcher(bot, storage=storage)
# Defining available states
class Form(StatesGroup):
name = State()
age = State()
gender = State()
@dp.message_handler(commands='start')
async def cmd_start(message: types.Message):
"""
Conversation's entry point
"""
# Set state
await Form.name.set()
await message.reply("Hi there! What's your name?")
# You can use state '*' if you need to handle all states
@dp.message_handler(state='*', commands='cancel')
@dp.message_handler(Text(equals='cancel', ignore_case=True), state='*')
async def cancel_handler(message: types.Message, state: FSMContext):
"""
Allow user to cancel any action
"""
current_state = await state.get_state()
if current_state is None:
return
# Cancel state and inform user about it
await state.finish()
# And remove keyboard (just in case)
await message.reply('Cancelled.', reply_markup=types.ReplyKeyboardRemove())
@dp.message_handler(state=Form.name)
async def process_name(message: types.Message, state: FSMContext):
"""
Process user name
"""
async with state.proxy() as data:
data['name'] = message.text
await Form.next()
await message.reply("How old are you?")
# Check age. Age gotta be digit
@dp.message_handler(lambda message: not message.text.isdigit(), state=Form.age)
async def process_age_invalid(message: types.Message):
"""
If age is invalid
"""
return await message.reply("Age gotta be a number.\nHow old are you? (digits only)")
@dp.message_handler(lambda message: message.text.isdigit(), state=Form.age)
async def process_age(message: types.Message, state: FSMContext):
# Update state and data
await Form.next()
await state.update_data(age=int(message.text))
# Configure ReplyKeyboardMarkup
markup = types.ReplyKeyboardMarkup(resize_keyboard=True, selective=True)
markup.add("Male", "Female")
markup.add("Other")
await message.reply("What is your gender?", reply_markup=markup)
@dp.message_handler(lambda message: message.text not in ["Male", "Female", "Other"], state=Form.gender)
async def process_gender_invalid(message: types.Message):
"""
In this example gender has to be one of: Male, Female, Other.
"""
return await message.reply("Bad gender name. Choose your gender from the keyboard.")
@dp.message_handler(state=Form.gender)
async def process_gender(message: types.Message, state: FSMContext):
async with state.proxy() as data:
data['gender'] = message.text
# Remove keyboard
markup = types.ReplyKeyboardRemove()
# And send message
await bot.send_message(
message.chat.id,
md.text(
md.text('Hi! Nice to meet you,', md.bold(data['name'])),
md.text('Age:', md.code(data['age'])),
md.text('Gender:', data['gender']),
sep='\n',
),
reply_markup=markup,
parse_mode=ParseMode.MARKDOWN,
)
# Finish conversation
await state.finish()
if __name__ == '__main__':
executor.start_polling(dp, skip_updates=True)
Upvotes: 3