pollbot.pyΒΆ

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