Hide navigation sidebar
Hide table of contents sidebar
PTB has undergone significant changes in v20. Please read the documentation carefully and also check out the transition guide in the wiki.
Back to top
Toggle table of contents sidebar

# `pollbot.py`

      1#!/usr/bin/env python
      2# pylint: disable=unused-argument
      3# This program is dedicated to the public domain under the CC0 license.
      4
      5"""
      6Basic example for a bot that works with polls. Only 3 people are allowed to interact with each
      7poll/quiz the bot generates. The preview command generates a closed poll/quiz, exactly like the
      8one the user sends the bot
      9"""
     10import logging
     11
     12from telegram import (
     13    KeyboardButton,
     14    KeyboardButtonPollType,
     15    Poll,
     16    ReplyKeyboardMarkup,
     17    ReplyKeyboardRemove,
     18    Update,
     19)
     20from telegram.constants import ParseMode
     21from telegram.ext import (
     22    Application,
     23    CommandHandler,
     24    ContextTypes,
     25    MessageHandler,
     26    PollAnswerHandler,
     27    PollHandler,
     28    filters,
     29)
     30
     31# Enable logging
     32logging.basicConfig(
     33    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO
     34)
     35# set higher logging level for httpx to avoid all GET and POST requests being logged
     36logging.getLogger("httpx").setLevel(logging.WARNING)
     37
     38logger = logging.getLogger(__name__)
     39
     40
     41TOTAL_VOTER_COUNT = 3
     42
     43
     44async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
     45    """Inform user about what this bot can do"""
     46    await update.message.reply_text(
     47        "Please select /poll to get a Poll, /quiz to get a Quiz or /preview"
     48        " to generate a preview for your poll"
     49    )
     50
     51
     52async def poll(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
     53    """Sends a predefined poll"""
     54    questions = ["Good", "Really good", "Fantastic", "Great"]
     55    message = await context.bot.send_poll(
     56        update.effective_chat.id,
     57        "How are you?",
     58        questions,
     59        is_anonymous=False,
     60        allows_multiple_answers=True,
     61    )
     62    # Save some info about the poll the bot_data for later use in receive_poll_answer
     63    payload = {
     64        message.poll.id: {
     65            "questions": questions,
     66            "message_id": message.message_id,
     67            "chat_id": update.effective_chat.id,
     68            "answers": 0,
     69        }
     70    }
     71    context.bot_data.update(payload)
     72
     73
     74async def receive_poll_answer(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
     75    """Summarize a users poll vote"""
     76    answer = update.poll_answer
     77    answered_poll = context.bot_data[answer.poll_id]
     78    try:
     79        questions = answered_poll["questions"]
     80    # this means this poll answer update is from an old poll, we can't do our answering then
     81    except KeyError:
     82        return
     83    selected_options = answer.option_ids
     84    answer_string = ""
     85    for question_id in selected_options:
     86        if question_id != selected_options[-1]:
     87            answer_string += questions[question_id] + " and "
     88        else:
     89            answer_string += questions[question_id]
     90    await context.bot.send_message(
     91        answered_poll["chat_id"],
     92        f"{update.effective_user.mention_html()} feels {answer_string}!",
     93        parse_mode=ParseMode.HTML,
     94    )
     95    answered_poll["answers"] += 1
     96    # Close poll after three participants voted
     97    if answered_poll["answers"] == TOTAL_VOTER_COUNT:
     98        await context.bot.stop_poll(answered_poll["chat_id"], answered_poll["message_id"])
     99
    100
    101async def quiz(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    102    """Send a predefined poll"""
    103    questions = ["1", "2", "4", "20"]
    104    message = await update.effective_message.reply_poll(
    105        "How many eggs do you need for a cake?", questions, type=Poll.QUIZ, correct_option_id=2
    106    )
    107    # Save some info about the poll the bot_data for later use in receive_quiz_answer
    108    payload = {
    109        message.poll.id: {"chat_id": update.effective_chat.id, "message_id": message.message_id}
    110    }
    111    context.bot_data.update(payload)
    112
    113
    114async def receive_quiz_answer(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    115    """Close quiz after three participants took it"""
    116    # the bot can receive closed poll updates we don't care about
    117    if update.poll.is_closed:
    118        return
    119    if update.poll.total_voter_count == TOTAL_VOTER_COUNT:
    120        try:
    121            quiz_data = context.bot_data[update.poll.id]
    122        # this means this poll answer update is from an old poll, we can't stop it then
    123        except KeyError:
    124            return
    125        await context.bot.stop_poll(quiz_data["chat_id"], quiz_data["message_id"])
    126
    127
    128async def preview(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    129    """Ask user to create a poll and display a preview of it"""
    130    # using this without a type lets the user chooses what he wants (quiz or poll)
    131    button = [[KeyboardButton("Press me!", request_poll=KeyboardButtonPollType())]]
    132    message = "Press the button to let the bot generate a preview for your poll"
    133    # using one_time_keyboard to hide the keyboard
    134    await update.effective_message.reply_text(
    135        message, reply_markup=ReplyKeyboardMarkup(button, one_time_keyboard=True)
    136    )
    137
    138
    139async def receive_poll(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    140    """On receiving polls, reply to it by a closed poll copying the received poll"""
    141    actual_poll = update.effective_message.poll
    142    # Only need to set the question and options, since all other parameters don't matter for
    143    # a closed poll
    144    await update.effective_message.reply_poll(
    145        question=actual_poll.question,
    146        options=[o.text for o in actual_poll.options],
    147        # with is_closed true, the poll/quiz is immediately closed
    148        is_closed=True,
    149        reply_markup=ReplyKeyboardRemove(),
    150    )
    151
    152
    153async def help_handler(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    154    """Display a help message"""
    155    await update.message.reply_text("Use /quiz, /poll or /preview to test this bot.")
    156
    157
    158def main() -> None:
    159    """Run bot."""
    160    # Create the Application and pass it your bot's token.
    161    application = Application.builder().token("TOKEN").build()
    162    application.add_handler(CommandHandler("start", start))
    163    application.add_handler(CommandHandler("poll", poll))
    164    application.add_handler(CommandHandler("quiz", quiz))
    165    application.add_handler(CommandHandler("preview", preview))
    166    application.add_handler(CommandHandler("help", help_handler))
    167    application.add_handler(MessageHandler(filters.POLL, receive_poll))
    168    application.add_handler(PollAnswerHandler(receive_poll_answer))
    169    application.add_handler(PollHandler(receive_quiz_answer))
    170
    171    # Run the bot until the user presses Ctrl-C
    172    application.run_polling(allowed_updates=Update.ALL_TYPES)
    173
    174
    175if __name__ == "__main__":
    176    main()