Skip to content

Commit 1e6c615

Browse files
authored
Support for polls (#126)
* Add support and documentation for polls * Add changelog * Add @bot.process_poll hook * Add @bot.process_poll documentation * Add dummy method comment in documentation
2 parents 6f1046f + d8f2c1f commit 1e6c615

File tree

14 files changed

+290
-11
lines changed

14 files changed

+290
-11
lines changed

botogram/bot.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ def __init__(self, api_connection):
9292
self.register_update_processor("edited_channel_post",
9393
messages.process_channel_post_edited)
9494
self.register_update_processor("callback_query", callbacks.process)
95+
self.register_update_processor("poll", messages.process_poll_update)
9596

9697
self._bot_id = str(uuid.uuid4())
9798

@@ -142,6 +143,11 @@ def process_message(self, func):
142143
self._main_component.add_process_message_hook(func)
143144
return func
144145

146+
def poll_update(self, func):
147+
"""Add a poll update hook"""
148+
self._main_component.add_poll_update_hook(func)
149+
return func
150+
145151
def message_equals(self, string, ignore_case=True):
146152
"""Add a message equals hook"""
147153
def __(func):

botogram/components.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ def __new__(cls, *args, **kwargs):
4747
self.__messages_edited_hooks = []
4848
self.__channel_post_hooks = []
4949
self.__channel_post_edited_hooks = []
50+
self.__poll_update_hooks = []
5051

5152
self._component_id = str(uuid.uuid4())
5253

@@ -75,6 +76,14 @@ def add_process_message_hook(self, func):
7576
hook = hooks.ProcessMessageHook(func, self)
7677
self.__processors.append(hook)
7778

79+
def add_poll_update_hook(self, func):
80+
"""Add a poll update hook"""
81+
if not callable(func):
82+
raise ValueError("A poll update hook must be callable")
83+
84+
hook = hooks.PollUpdateHook(func, self)
85+
self.__poll_update_hooks.append(hook)
86+
7887
def add_message_equals_hook(self, string, func, ignore_case=True):
7988
"""Add a message equals hook"""
8089
if not callable(func):
@@ -220,6 +229,7 @@ def _get_chains(self):
220229
]
221230
return {
222231
"messages": messages,
232+
"poll_updates": [self.__poll_update_hooks],
223233
"memory_preparers": [self.__memory_preparers],
224234
"tasks": [self.__timers],
225235
"chat_unavalable_hooks": [self.__chat_unavailable_hooks],

botogram/frozenbot.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,10 @@ def process_message(self, func):
111111
"""Register a message processor hook"""
112112
raise FrozenBotError("Can't add hooks to a bot at runtime")
113113

114+
def poll_update(self, func):
115+
"""Register a poll update hook"""
116+
raise FrozenBotError("Can't add hooks to a bot at runtime")
117+
114118
def message_equals(self, string, ignore_case=True):
115119
"""Add a message equals hook"""
116120
raise FrozenBotError("Can't add hooks to a bot at runtime")

botogram/hooks.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,13 @@ class ProcessMessageHook(Hook):
8181
pass
8282

8383

84+
class PollUpdateHook(Hook):
85+
"""Underlying hook for @bot.poll_update"""
86+
87+
def _call(self, bot, update):
88+
return bot._call(self.func, self.component_id, poll=update.poll)
89+
90+
8491
class MemoryPreparerHook(Hook):
8592
"""Underlying hook for @bot.prepare_memory"""
8693

botogram/messages.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,19 @@ def process_channel_post_edited(bot, chains, update):
8181

8282
bot.logger.debug("No hook actually processed the #%s update." %
8383
update.update_id)
84+
85+
86+
def process_poll_update(bot, chains, update):
87+
"""Process a poll update"""
88+
for hook in chains["poll_updates"]:
89+
bot.logger.debug("Processing poll update in update #%s with"
90+
"the hook %s..." % (update.update_id, hook.name))
91+
92+
result = hook.call(bot, update)
93+
if result is True:
94+
bot.logger.debug("Update %s was just processed by the %s hook." %
95+
(update.update_id, hook.name))
96+
return
97+
98+
bot.logger.debug("No hook actually processed the #%s update." %
99+
update.update_id)

botogram/objects/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
Video, VideoNote, Contact, Location, Venue
2626
from .messages import Message
2727
from .markup import ReplyKeyboardMarkup, ReplyKeyboardHide, ForceReply
28+
from .polls import Poll, PollOption
2829
from .updates import Update, Updates
2930
from .mixins import Album
3031

@@ -56,6 +57,10 @@
5657
"ReplyKeyboardHide",
5758
"ForceReply",
5859

60+
# Polls-related objects
61+
"Poll",
62+
"PollOption",
63+
5964
# Updates-related objects
6065
"Update",
6166
"Updates",

botogram/objects/messages.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from .chats import User, Chat
2727
from .media import Audio, Voice, Document, Photo, Sticker, Video, VideoNote, \
2828
Contact, Location, Venue
29+
from .polls import Poll
2930

3031

3132
_url_protocol_re = re.compile(r"^https?:\/\/|s?ftp:\/\/|mailto:", re.I)
@@ -350,6 +351,7 @@ def from_(self):
350351
"contact": Contact,
351352
"location": Location,
352353
"venue": Venue,
354+
"poll": Poll,
353355
"new_chat_member": User,
354356
"left_chat_member": User,
355357
"new_chat_title": str,

botogram/objects/mixins.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,16 @@ def send_contact(self, phone, first_name, last_name=None, *, reply_to=None,
336336

337337
return self._api.call("sendContact", args, expect=_objects().Message)
338338

339+
@_require_api
340+
def send_poll(self, question, *kargs, reply_to=None, extra=None,
341+
attach=None, notify=True):
342+
"""Send a poll"""
343+
args = self._get_call_args(reply_to, extra, attach, notify)
344+
args["question"] = question
345+
args["options"] = json.dumps(list(kargs))
346+
347+
return self._api.call("sendPoll", args, expect=_objects().Message)
348+
339349
@_require_api
340350
def delete_message(self, message):
341351
"""Delete a message from chat"""
@@ -543,6 +553,11 @@ def reply_with_album(self, *args, **kwargs):
543553
"""Reply with an album to the current message"""
544554
return self.chat.send_album(*args, reply_to=self, **kwargs)
545555

556+
@_require_api
557+
def reply_with_poll(self, *args, **kwargs):
558+
"""Reply with a poll to the current message"""
559+
return self.chat.send_poll(*args, reply_to=self, **kwargs)
560+
546561
@_require_api
547562
def delete(self):
548563
"""Delete the message"""
@@ -551,6 +566,27 @@ def delete(self):
551566
"message_id": self.id,
552567
})
553568

569+
@_require_api
570+
def stop_poll(self, extra=None, attach=None):
571+
"""Stops a poll"""
572+
args = dict()
573+
args["chat_id"] = self.chat.id
574+
args["message_id"] = self.id
575+
576+
if extra is not None:
577+
_deprecated_message(
578+
"The extra parameter", "1.0", "use the attach parameter", -3
579+
)
580+
args["reply_markup"] = json.dumps(extra.serialize())
581+
if attach is not None:
582+
if not hasattr(attach, "_serialize_attachment"):
583+
raise ValueError("%s is not an attachment" % attach)
584+
args["reply_markup"] = json.dumps(attach._serialize_attachment(
585+
self.chat
586+
))
587+
return self._api.call("stopPoll", args,
588+
expect=_objects().Poll)
589+
554590

555591
class FileMixin:
556592
"""Add some methods for files"""

botogram/objects/polls.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Copyright (c) 2015-2019 The Botogram Authors (see AUTHORS)
2+
#
3+
# Permission is hereby granted, free of charge, to any person obtaining a copy
4+
# of this software and associated documentation files (the "Software"), to deal
5+
# in the Software without restriction, including without limitation the rights
6+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
# copies of the Software, and to permit persons to whom the Software is
8+
# furnished to do so, subject to the following conditions:
9+
#
10+
# The above copyright notice and this permission notice shall be included in
11+
# all copies or substantial portions of the Software.
12+
#
13+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18+
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19+
# DEALINGS IN THE SOFTWARE.
20+
21+
from .base import BaseObject, multiple
22+
23+
24+
class PollOption(BaseObject):
25+
required = {
26+
"text": str,
27+
"voter_count": int,
28+
}
29+
30+
31+
class Poll(BaseObject):
32+
required = {
33+
"id": str,
34+
"question": str,
35+
"options": multiple(PollOption),
36+
"is_closed": bool,
37+
}

botogram/objects/updates.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
from .callbacks import CallbackQuery
2424
from .messages import Message
25+
from .polls import Poll
2526

2627

2728
class Update(BaseObject):
@@ -41,6 +42,7 @@ class Update(BaseObject):
4142
"channel_post": Message,
4243
"edited_channel_post": Message,
4344
"callback_query": CallbackQuery,
45+
"poll": Poll,
4446
}
4547
_check_equality_ = "update_id"
4648

0 commit comments

Comments
 (0)