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()