Hide navigation sidebar
Hide table of contents sidebar
Skip to content
Back to top
View this page
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()