sampleuser
sampleuser

Reputation: 320

Python Telegram API -- how do I use ForceReply?

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

Answers (2)

dmitry_romanov
dmitry_romanov

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.

Dialog will look like enter image description here

Upvotes: 0

Oleg
Oleg

Reputation: 629

aiogram framework already solved your task

Every step is a state of user.

It's called FSM (finite state machine).

You don't even need to do something with ForceReply.

Example

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

Related Questions