Skip to content

Commit 664034d

Browse files
author
Pietro Albini
committed
Merge branch 'feature/edited-messages'
2 parents 7046c49 + 2ff823d commit 664034d

File tree

10 files changed

+157
-20
lines changed

10 files changed

+157
-20
lines changed

botogram/bot.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from . import frozenbot
2222
from . import shared
2323
from . import tasks
24+
from . import messages
2425

2526

2627
class Bot(frozenbot.FrozenBot):
@@ -63,6 +64,12 @@ def __init__(self, api_connection):
6364
# Setup the scheduler
6465
self._scheduler = tasks.Scheduler()
6566

67+
# Initialize the list of update processors
68+
self._update_processors = {}
69+
self.register_update_processor("message", messages.process_message)
70+
self.register_update_processor("edited_message",
71+
messages.process_edited_message)
72+
6673
self._bot_id = str(uuid.uuid4())
6774

6875
self.use(defaults.DefaultComponent())
@@ -130,6 +137,11 @@ def __(func):
130137
return func
131138
return __
132139

140+
def message_edited(self, func):
141+
"""Add a message edited hook"""
142+
self._main_component.add_message_edited_hook(func)
143+
return func
144+
133145
def command(self, name, hidden=False):
134146
"""Register a new command"""
135147
def __(func):
@@ -191,6 +203,14 @@ def run(self, workers=2):
191203
inst = runner.BotogramRunner(self, workers=workers)
192204
inst.run()
193205

206+
def register_update_processor(self, kind, processor):
207+
"""Register a new update processor"""
208+
if kind in self._update_processors:
209+
raise NameError("An update processor for \"%s\" updates is "
210+
"already registered" % kind)
211+
212+
self._update_processors[kind] = processor
213+
194214
def freeze(self):
195215
"""Return a frozen instance of the bot"""
196216
chains = components.merge_chains(self._main_component,
@@ -202,7 +222,8 @@ def freeze(self):
202222
self.lang, self.itself, self._commands_re,
203223
self._commands, chains, self._scheduler,
204224
self._main_component._component_id,
205-
self._bot_id, self._shared_memory)
225+
self._bot_id, self._shared_memory,
226+
self._update_processors)
206227

207228
@property
208229
def lang(self):

botogram/components.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ def __new__(cls, *args, **kwargs):
3131
self.__memory_preparers = []
3232
self.__timers = []
3333
self.__chat_unavailable_hooks = []
34+
self.__messages_edited_hooks = []
3435

3536
self._component_id = str(uuid.uuid4())
3637

@@ -148,6 +149,14 @@ def add_chat_unavailable_hook(self, func):
148149
hook = hooks.ChatUnavailableHook(func, self)
149150
self.__chat_unavailable_hooks.append(hook)
150151

152+
def add_message_edited_hook(self, func):
153+
"""Add a new edited message hook"""
154+
if not callable(func):
155+
raise ValueError("A message edited hook must be callable")
156+
157+
hook = hooks.MessageEditedHook(func, self)
158+
self.__messages_edited_hooks.append(hook)
159+
151160
def _add_no_commands_hook(self, func):
152161
"""Register an hook which will be executed when no commands matches"""
153162
if not callable(func):
@@ -170,6 +179,7 @@ def _get_chains(self):
170179
"memory_preparers": [self.__memory_preparers],
171180
"tasks": [self.__timers],
172181
"chat_unavalable_hooks": [self.__chat_unavailable_hooks],
182+
"messages_edited": [self.__messages_edited_hooks],
173183
}
174184

175185
def _get_commands(self):

botogram/frozenbot.py

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class FrozenBot:
2323
def __init__(self, api, about, owner, hide_commands, before_help,
2424
after_help, process_backlog, lang, itself, commands_re,
2525
commands, chains, scheduler, main_component_id, bot_id,
26-
shared_memory):
26+
shared_memory, update_processors):
2727
# This attribute should be added with the default setattr, because is
2828
# needed by the custom setattr
2929
object.__setattr__(self, "_frozen", False)
@@ -43,6 +43,7 @@ def __init__(self, api, about, owner, hide_commands, before_help,
4343
self._shared_memory = shared_memory
4444
self._scheduler = scheduler
4545
self._chains = chains
46+
self._update_processors = update_processors
4647
self._commands = {name: command.for_bot(self)
4748
for name, command in commands.items()}
4849

@@ -65,7 +66,7 @@ def __reduce__(self):
6566
self.before_help, self.after_help, self.process_backlog,
6667
self.lang, self.itself, self._commands_re, self._commands,
6768
self._chains, self._scheduler, self._main_component_id,
68-
self._bot_id, self._shared_memory,
69+
self._bot_id, self._shared_memory, self._update_processors,
6970
)
7071
return restore, args
7172

@@ -171,21 +172,13 @@ def process(self, update):
171172
update.set_api(self.api) # Be sure to use the correct API object
172173

173174
try:
174-
for hook in self._chains["messages"]:
175-
# Get the correct name of the hook
176-
name = hook.name
177-
self.logger.debug("Processing update #%s with the %s hook..." %
178-
(update.update_id, name))
179-
180-
result = hook.call(self, update)
181-
if result is True:
182-
self.logger.debug("Update #%s was just processed by the "
183-
"%s hook." % (update.update_id, name))
184-
return True
185-
186-
self.logger.debug("No hook actually processed the #%s update." %
187-
update.update_id)
175+
for kind, processor in self._update_processors.items():
176+
# Call the processor of the right kind
177+
if getattr(update, kind) is None:
178+
continue
188179

180+
processor(self, self._chains, update)
181+
break
189182
except api_module.ChatUnavailableError as e:
190183
# Do some sane logging
191184
self.logger.warning("Chat %s is not available to your bot:" %
@@ -199,8 +192,6 @@ def process(self, update):
199192
e.chat_id))
200193
hook.call(self, e.chat_id, e.reason)
201194

202-
return False
203-
204195
def scheduled_tasks(self, current_time=None, wrap=True):
205196
"""Return a list of tasks scheduled for now"""
206197
# This provides a better API for the users of the method
@@ -215,6 +206,10 @@ def process():
215206
return [wrapper(job) for job in tasks]
216207
return list(tasks)
217208

209+
def register_update_processor(self, kind, processor):
210+
"""Register a new update processor"""
211+
raise FrozenBotError("Can't register new update processors at runtime")
212+
218213
# This helper manages the translation
219214

220215
def _(self, message, **args):

botogram/hooks.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,15 @@ def call(self, bot, chat_id, reason):
200200
reason=reason)
201201

202202

203+
class MessageEditedHook(Hook):
204+
"""Underlying hook for @bot.message_edited"""
205+
206+
def _call(self, bot, update):
207+
message = update.edited_message
208+
return bot._call(self.func, self.component_id, chat=message.chat,
209+
message=message)
210+
211+
203212
class TimerHook(Hook):
204213
"""Underlying hook for a timer"""
205214

botogram/messages.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
"""
2+
botogram.messages
3+
Logic for processing messages sent to your bot
4+
5+
Copyright (c) 2016 Pietro Albini <[email protected]>
6+
Released under the MIT license
7+
"""
8+
9+
10+
def process_message(bot, chains, update):
11+
"""Process a message sent to the bot"""
12+
for hook in chains["messages"]:
13+
bot.logger.debug("Processing update #%s with the hook %s..." %
14+
(update.update_id, hook.name))
15+
16+
result = hook.call(bot, update)
17+
if result is True:
18+
bot.logger.debug("Update #%s was just processed by the %s hook." %
19+
(update.update_id, hook.name))
20+
return
21+
22+
bot.logger.debug("No hook actually processed the #%s update." %
23+
update.update_id)
24+
25+
26+
def process_edited_message(bot, chains, update):
27+
"""Process an edited message"""
28+
for hook in chains["messages_edited"]:
29+
bot.logger.debug("Processing edited message in update #%s with the "
30+
"hook %s..." % (update.update_id, hook.name))
31+
32+
result = hook.call(bot, update)
33+
if result is True:
34+
bot.logger.debug("Update %s was just processed by the %s hook." %
35+
(update.update_id, hook.name))
36+
return
37+
38+
bot.logger.debug("No hook actually processed the #%s update." %
39+
update.update_id)

botogram/objects/messages.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,7 @@ def from_(self):
319319
"migrate_to_chat_id": int,
320320
"migrate_from_chat_id": int,
321321
"pinned_message": _itself,
322+
"edit_date": int,
322323
}
323324
replace_keys = {
324325
"from": "sender",

botogram/objects/updates.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ class Update(BaseObject):
2121
"update_id": int,
2222
}
2323
optional = {
24-
"message": Message
24+
"message": Message,
25+
"edited_message": Message,
2526
}
2627
_check_equality_ = "update_id"
2728

docs/api/bot.rst

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,31 @@ components.
163163
:param bool multiple: If the function should be called multiple times on
164164
multiple matches.
165165

166+
.. py:decoratormethod:: message_edited
167+
168+
All the functions decorated with this method will be called when an user
169+
edits a message the bot knows about. This allows you, for example, to
170+
update the preview of a message if the user edits the request, or to
171+
enforce a no-edits policy on groups by banning whoever edits a message.
172+
173+
You can :ref:`request the following arguments <bot-structure-hooks-args>`
174+
in the decorated functions:
175+
176+
* **chat**: the chat in which the message was originally sent (instance
177+
of :py:class:`~botogram.Chat`)
178+
179+
* **message**: the edited message (instance of
180+
:py:class:`~botogram.Message`)
181+
182+
.. code-block:: python
183+
184+
@bot.message_edited
185+
def no_edits(chat, message):
186+
message.reply("You can't edit messages! Bye.")
187+
chat.ban(message.sender)
188+
189+
.. versionadded:: 0.3
190+
166191
.. py:decoratormethod:: command(name, [hidden=False])
167192
168193
This decorator register a new command, and calls the decorated function

docs/api/components.rst

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,37 @@ about how to create them in the ":ref:`custom-components`" chapter.
127127
:param bool multiple: If the function should be called multiple times on
128128
multiple matches.
129129

130+
.. py:method:: add_message_edited_hook(func)
131+
132+
All the functions provided to this method will be called when an user
133+
edits a message the bot knows about. This allows you, for example, to
134+
update the preview of a message if the user edits the request, or to
135+
enforce a no-edits policy on groups by banning whoever edits a message.
136+
137+
You can :ref:`request the following arguments <bot-structure-hooks-args>`
138+
in the provided functions:
139+
140+
* **chat**: the chat in which the message was orignally sent (instance of
141+
:py:class:`~botogram.Chat`)
142+
* **message**: the edited message (instance of
143+
:py:class:`~botogram.Message`)
144+
145+
.. code-block:: python
146+
147+
class NoEditsComponent(botogram.Component):
148+
component_name = "noedits"
149+
150+
def __init__(self):
151+
self.add_message_edited_hook(self.no_edits)
152+
153+
def no_edits(self, chat, message):
154+
message.reply("You can't edit messages! Bye.")
155+
chat.ban(message.sender)
156+
157+
:param callable func: The function you want to use.
158+
159+
.. versionadded:: 0.3
160+
130161
.. py:method:: add_command(name, func, [hidden=False])
131162
132163
This function registers a new command, and calls the provided function

docs/changelog.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ New features
4545
* New argument ``hidden`` on :py:meth:`botogram.Bot.command`
4646
* New argument ``hidden`` on :py:meth:`botogram.Component.add_command`
4747

48+
* Add support for processing edited messages:
49+
50+
* New decorator :py:meth:`botogram.Bot.message_edited`
51+
* New method :py:meth:`botogram.Component.add_message_edited_hook`
52+
4853
* Added new attribute :py:attr:`botogram.Message.pinned_message`
4954
* Added new attribute :py:attr:`botogram.Sticker.emoji`
5055
* Added new attribute :py:attr:`botogram.Chat.admins`

0 commit comments

Comments
 (0)