Skip to content

Commit 55e3ecf

Browse files
PoolitzerBibo-Joshisharunkumar
authored
* First take on 4.6 support * improved docs * Minor doc formattings * added poll and poll_answer to filters * added tests, fixed mentioned issues * added poll_answer + poll filter tests * Update docs according to official API docs * introducing pollhandler and pollanswerhandler * First take on 4.6 support * improved docs * Minor doc formattings * added poll and poll_answer to filters * added tests, fixed mentioned issues * added poll_answer + poll filter tests * Update docs according to official API docs * introducing pollhandler and pollanswerhandler * correct_option_id validated with None when trying to send a poll with correct option id 0 it was failing. Now None check is done so that even when 0 is passed it is assigned. * improving example * improving code * adding poll filter example to the pollbot.py * Update Readme * simplify pollbot.py and add some comments * add tests for Poll(Answer)Handler * We just want Filters.poll, not Filters.update.poll * Make test_official fail again * Handle ME.language in M._parse_* Co-authored-by: Hinrich Mahler <[email protected]> Co-authored-by: Sharun Kumar <[email protected]>
1 parent 8d2c7af commit 55e3ecf

28 files changed

Lines changed: 1068 additions & 54 deletions

.github/workflows/test.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,6 @@ jobs:
9090
run: |
9191
pytest -v tests/test_official.py
9292
exit $?
93-
continue-on-error: True
9493
env:
9594
TEST_OFFICIAL: "true"
9695
shell: bash --noprofile --norc {0}

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ make the development of bots easy and straightforward. These classes are contain
9393
Telegram API support
9494
====================
9595

96-
All types and methods of the Telegram Bot API **4.5** are supported.
96+
All types and methods of the Telegram Bot API **4.6** are supported.
9797

9898
==========
9999
Installing
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
telegram.KeyboardButtonPollType
2+
===============================
3+
4+
.. autoclass:: telegram.KeyboardButtonPollType
5+
:members:
6+
:show-inheritance:
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
telegram.PollAnswer
2+
===================
3+
4+
.. autoclass:: telegram.PollAnswer
5+
:members:
6+
:show-inheritance:

docs/source/telegram.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,15 @@ telegram package
3131
telegram.inputmediaphoto
3232
telegram.inputmediavideo
3333
telegram.keyboardbutton
34+
telegram.keyboardbuttonpolltype
3435
telegram.location
3536
telegram.loginurl
3637
telegram.message
3738
telegram.messageentity
3839
telegram.parsemode
3940
telegram.photosize
4041
telegram.poll
42+
telegram.pollanswer
4143
telegram.polloption
4244
telegram.replykeyboardremove
4345
telegram.replykeyboardmarkup

examples/pollbot.py

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
# This program is dedicated to the public domain under the CC0 license.
4+
5+
"""
6+
Basic example for a bot that works with polls. Only 3 people are allowed to interact with each
7+
poll/quiz the bot generates. The preview command generates a closed poll/quiz, excatly like the
8+
one the user sends the bot
9+
"""
10+
import logging
11+
12+
from telegram import (Poll, ParseMode, KeyboardButton, KeyboardButtonPollType,
13+
ReplyKeyboardMarkup, ReplyKeyboardRemove)
14+
from telegram.ext import (Updater, CommandHandler, PollAnswerHandler, PollHandler, MessageHandler,
15+
Filters)
16+
from telegram.utils.helpers import mention_html
17+
18+
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
19+
level=logging.INFO)
20+
logger = logging.getLogger(__name__)
21+
22+
23+
def start(update, context):
24+
"""Inform user about what this bot can do"""
25+
update.message.reply_text('Please select /poll to get a Poll, /quiz to get a Quiz or /preview'
26+
' to generate a preview for your poll')
27+
28+
29+
def poll(update, context):
30+
"""Sends a predefined poll"""
31+
questions = ["Good", "Really good", "Fantastic", "Great"]
32+
message = context.bot.send_poll(update.effective_user.id, "How are you?", questions,
33+
is_anonymous=False, allows_multiple_answers=True)
34+
# Save some info about the poll the bot_data for later use in receive_poll_answer
35+
payload = {message.poll.id: {"questions": questions, "message_id": message.message_id,
36+
"chat_id": update.effective_chat.id, "answers": 0}}
37+
context.bot_data.update(payload)
38+
39+
40+
def receive_poll_answer(update, context):
41+
"""Summarize a users poll vote"""
42+
answer = update.poll_answer
43+
poll_id = answer.poll_id
44+
try:
45+
questions = context.bot_data[poll_id]["questions"]
46+
# this means this poll answer update is from an old poll, we can't do our answering then
47+
except KeyError:
48+
return
49+
selected_options = answer.option_ids
50+
answer_string = ""
51+
for question_id in selected_options:
52+
if question_id != selected_options[-1]:
53+
answer_string += questions[question_id] + " and "
54+
else:
55+
answer_string += questions[question_id]
56+
user_mention = mention_html(update.effective_user.id, update.effective_user.full_name)
57+
context.bot.send_message(context.bot_data[poll_id]["chat_id"],
58+
"{} feels {}!".format(user_mention, answer_string),
59+
parse_mode=ParseMode.HTML)
60+
context.bot_data[poll_id]["answers"] += 1
61+
# Close poll after three participants voted
62+
if context.bot_data[poll_id]["answers"] == 3:
63+
context.bot.stop_poll(context.bot_data[poll_id]["chat_id"],
64+
context.bot_data[poll_id]["message_id"])
65+
66+
67+
def quiz(update, context):
68+
"""Send a predefined poll"""
69+
questions = ["1", "2", "4", "20"]
70+
message = update.effective_message.reply_poll("How many eggs do you need for a cake?",
71+
questions, type=Poll.QUIZ, correct_option_id=2)
72+
# Save some info about the poll the bot_data for later use in receive_quiz_answer
73+
payload = {message.poll.id: {"chat_id": update.effective_chat.id,
74+
"message_id": message.message_id}}
75+
context.bot_data.update(payload)
76+
77+
78+
def receive_quiz_answer(update, context):
79+
"""Close quiz after three participants took it"""
80+
# the bot can receive closed poll updates we don't care about
81+
if update.poll.is_closed:
82+
return
83+
if update.poll.total_voter_count == 3:
84+
try:
85+
quiz_data = context.bot_data[update.poll.id]
86+
# this means this poll answer update is from an old poll, we can't stop it then
87+
except KeyError:
88+
return
89+
context.bot.stop_poll(quiz_data["chat_id"], quiz_data["message_id"])
90+
91+
92+
def preview(update, context):
93+
"""Ask user to create a poll and display a preview of it"""
94+
# using this without a type lets the user chooses what he wants (quiz or poll)
95+
button = [[KeyboardButton("Press me!", request_poll=KeyboardButtonPollType())]]
96+
message = "Press the button to let the bot generate a preview for your poll"
97+
# using one_time_keyboard to hide the keyboard
98+
update.effective_message.reply_text(message,
99+
reply_markup=ReplyKeyboardMarkup(button,
100+
one_time_keyboard=True))
101+
102+
103+
def receive_poll(update, context):
104+
"""On receiving polls, reply to it by a closed poll copying the received poll"""
105+
actual_poll = update.effective_message.poll
106+
# Only need to set the question and options, since all other parameters don't matter for
107+
# a closed poll
108+
update.effective_message.reply_poll(
109+
question=actual_poll.question,
110+
options=[o.text for o in actual_poll.options],
111+
# with is_closed true, the poll/quiz is immediately closed
112+
is_closed=True,
113+
reply_markup=ReplyKeyboardRemove()
114+
)
115+
116+
117+
def help_handler(update, context):
118+
"""Display a help message"""
119+
update.message.reply_text("Use /quiz, /poll or /preview to test this "
120+
"bot.")
121+
122+
123+
def main():
124+
# Create the Updater and pass it your bot's token.
125+
# Make sure to set use_context=True to use the new context based callbacks
126+
# Post version 12 this will no longer be necessary
127+
updater = Updater("TOKEN", use_context=True)
128+
dp = updater.dispatcher
129+
dp.add_handler(CommandHandler('start', start))
130+
dp.add_handler(CommandHandler('poll', poll))
131+
dp.add_handler(PollAnswerHandler(receive_poll_answer))
132+
dp.add_handler(CommandHandler('quiz', quiz))
133+
dp.add_handler(PollHandler(receive_quiz_answer))
134+
dp.add_handler(CommandHandler('preview', preview))
135+
dp.add_handler(MessageHandler(Filters.poll, receive_poll))
136+
dp.add_handler(CommandHandler('help', help_handler))
137+
138+
# Start the Bot
139+
updater.start_polling()
140+
141+
# Run the bot until the user presses Ctrl-C or the process receives SIGINT,
142+
# SIGTERM or SIGABRT
143+
updater.idle()
144+
145+
146+
if __name__ == '__main__':
147+
main()

telegram/__init__.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
from .chataction import ChatAction
3939
from .userprofilephotos import UserProfilePhotos
4040
from .keyboardbutton import KeyboardButton
41+
from .keyboardbuttonpolltype import KeyboardButtonPollType
4142
from .replymarkup import ReplyMarkup
4243
from .replykeyboardmarkup import ReplyKeyboardMarkup
4344
from .replykeyboardremove import ReplyKeyboardRemove
@@ -48,7 +49,7 @@
4849
from .parsemode import ParseMode
4950
from .messageentity import MessageEntity
5051
from .games.game import Game
51-
from .poll import Poll, PollOption
52+
from .poll import Poll, PollOption, PollAnswer
5253
from .loginurl import LoginUrl
5354
from .games.callbackgame import CallbackGame
5455
from .payment.shippingaddress import ShippingAddress
@@ -138,7 +139,7 @@
138139
'InlineQueryResultPhoto', 'InlineQueryResultVenue', 'InlineQueryResultVideo',
139140
'InlineQueryResultVoice', 'InlineQueryResultGame', 'InputContactMessageContent', 'InputFile',
140141
'InputLocationMessageContent', 'InputMessageContent', 'InputTextMessageContent',
141-
'InputVenueMessageContent', 'KeyboardButton', 'Location', 'EncryptedCredentials',
142+
'InputVenueMessageContent', 'Location', 'EncryptedCredentials',
142143
'PassportFile', 'EncryptedPassportElement', 'PassportData', 'Message', 'MessageEntity',
143144
'ParseMode', 'PhotoSize', 'ReplyKeyboardRemove', 'ReplyKeyboardMarkup', 'ReplyMarkup',
144145
'Sticker', 'TelegramError', 'TelegramObject', 'Update', 'User', 'UserProfilePhotos', 'Venue',
@@ -156,5 +157,5 @@
156157
'InputMediaAudio', 'InputMediaDocument', 'TelegramDecryptionError',
157158
'PassportElementErrorSelfie', 'PassportElementErrorTranslationFile',
158159
'PassportElementErrorTranslationFiles', 'PassportElementErrorUnspecified', 'Poll',
159-
'PollOption', 'LoginUrl'
160+
'PollOption', 'PollAnswer', 'LoginUrl', 'KeyboardButton', 'KeyboardButtonPollType',
160161
]

telegram/bot.py

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,27 @@ def link(self):
230230

231231
return "https://t.me/{}".format(self.username)
232232

233+
@property
234+
@info
235+
def can_join_groups(self):
236+
""":obj:`str`: Bot's can_join_groups attribute."""
237+
238+
return self.bot.can_join_groups
239+
240+
@property
241+
@info
242+
def can_read_all_group_messages(self):
243+
""":obj:`str`: Bot's can_read_all_group_messages attribute."""
244+
245+
return self.bot.can_read_all_group_messages
246+
247+
@property
248+
@info
249+
def supports_inline_queries(self):
250+
""":obj:`str`: Bot's supports_inline_queries attribute."""
251+
252+
return self.bot.supports_inline_queries
253+
233254
@property
234255
def name(self):
235256
""":obj:`str`: Bot's @username."""
@@ -3492,18 +3513,33 @@ def send_poll(self,
34923513
chat_id,
34933514
question,
34943515
options,
3516+
is_anonymous=True,
3517+
type=Poll.REGULAR,
3518+
allows_multiple_answers=False,
3519+
correct_option_id=None,
3520+
is_closed=None,
34953521
disable_notification=None,
34963522
reply_to_message_id=None,
34973523
reply_markup=None,
34983524
timeout=None,
34993525
**kwargs):
35003526
"""
3501-
Use this method to send a native poll. A native poll can't be sent to a private chat.
3527+
Use this method to send a native poll.
35023528
35033529
Args:
35043530
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target private chat.
35053531
question (:obj:`str`): Poll question, 1-255 characters.
35063532
options (List[:obj:`str`]): List of answer options, 2-10 strings 1-100 characters each.
3533+
is_anonymous (:obj:`bool`, optional): True, if the poll needs to be anonymous,
3534+
defaults to True.
3535+
type (:obj:`str`, optional): Poll type, :attr:`telegram.Poll.QUIZ` or
3536+
:attr:`telegram.Poll.REGULAR`, defaults to :attr:`telegram.Poll.REGULAR`.
3537+
allows_multiple_answers (:obj:`bool`, optional): True, if the poll allows multiple
3538+
answers, ignored for polls in quiz mode, defaults to False
3539+
correct_option_id (:obj:`int`, optional): 0-based identifier of the correct answer
3540+
option, required for polls in quiz mode
3541+
is_closed (:obj:`bool`, optional): Pass True, if the poll needs to be immediately
3542+
closed. This can be useful for poll preview.
35073543
disable_notification (:obj:`bool`, optional): Sends the message silently. Users will
35083544
receive a notification with no sound.
35093545
reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the
@@ -3531,6 +3567,17 @@ def send_poll(self,
35313567
'options': options
35323568
}
35333569

3570+
if not is_anonymous:
3571+
data['is_anonymous'] = is_anonymous
3572+
if type:
3573+
data['type'] = type
3574+
if allows_multiple_answers:
3575+
data['allows_multiple_answers'] = allows_multiple_answers
3576+
if correct_option_id is not None:
3577+
data['correct_option_id'] = correct_option_id
3578+
if is_closed:
3579+
data['is_closed'] = is_closed
3580+
35343581
return self._message(url, data, timeout=timeout, disable_notification=disable_notification,
35353582
reply_to_message_id=reply_to_message_id, reply_markup=reply_markup,
35363583
**kwargs)

telegram/ext/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141
from .shippingqueryhandler import ShippingQueryHandler
4242
from .messagequeue import MessageQueue
4343
from .messagequeue import DelayQueue
44+
from .pollanswerhandler import PollAnswerHandler
45+
from .pollhandler import PollHandler
4446
from .defaults import Defaults
4547

4648
__all__ = ('Dispatcher', 'JobQueue', 'Job', 'Updater', 'CallbackQueryHandler',
@@ -49,4 +51,5 @@
4951
'StringRegexHandler', 'TypeHandler', 'ConversationHandler',
5052
'PreCheckoutQueryHandler', 'ShippingQueryHandler', 'MessageQueue', 'DelayQueue',
5153
'DispatcherHandlerStop', 'run_async', 'CallbackContext', 'BasePersistence',
52-
'PicklePersistence', 'DictPersistence', 'PrefixHandler', 'Defaults')
54+
'PicklePersistence', 'DictPersistence', 'PrefixHandler', 'PollAnswerHandler',
55+
'PollHandler', 'Defaults')

0 commit comments

Comments
 (0)