Skip to content

Commit f0ad524

Browse files
committed
Add post_hook hooks and migrate hook specific logic out to plugins
1 parent 6ba818d commit f0ad524

4 files changed

Lines changed: 102 additions & 26 deletions

File tree

cloudbot/event.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,3 +444,12 @@ def prepare_threaded(self):
444444
@property
445445
def line(self):
446446
return str(self.irc_raw)
447+
448+
449+
class PostHookEvent(Event):
450+
def __init__(self, *args, launched_hook=None, launched_event=None, result=None, error=None, **kwargs):
451+
super().__init__(*args, **kwargs)
452+
self.launched_hook = launched_hook
453+
self.launched_event = launched_event
454+
self.result = result
455+
self.error = error

cloudbot/hook.py

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import collections
22
import inspect
33
import re
4-
import collections
54
from enum import Enum, unique, IntEnum
65

76
from cloudbot.event import EventType
@@ -374,18 +373,21 @@ def on_stop(param=None, **kwargs):
374373
"""External on_stop decorator. Can be used directly as a decorator, or with args to return a decorator
375374
:type param: function | None
376375
"""
376+
377377
def _on_stop_hook(func):
378378
hook = _get_hook(func, "on_stop")
379379
if hook is None:
380380
hook = _Hook(func, "on_stop")
381381
_add_hook(func, hook)
382382
hook._add_hook(kwargs)
383383
return func
384+
384385
if callable(param):
385386
return _on_stop_hook(param)
386387
else:
387388
return lambda func: _on_stop_hook(func)
388389

390+
389391
on_unload = on_stop
390392

391393

@@ -442,7 +444,7 @@ def _on_connect_hook(func):
442444

443445

444446
def irc_out(param=None, **kwargs):
445-
def _on_connect_hook(func):
447+
def _decorate(func):
446448
hook = _get_hook(func, "irc_out")
447449
if hook is None:
448450
hook = _Hook(func, "irc_out")
@@ -452,6 +454,26 @@ def _on_connect_hook(func):
452454
return func
453455

454456
if callable(param):
455-
return _on_connect_hook(param)
457+
return _decorate(param)
456458
else:
457-
return lambda func: _on_connect_hook(func)
459+
return lambda func: _decorate(func)
460+
461+
462+
def post_hook(param=None, **kwargs):
463+
"""
464+
This hook will be fired just after a hook finishes executing
465+
"""
466+
467+
def _decorate(func):
468+
hook = _get_hook(func, "post_hook")
469+
if hook is None:
470+
hook = _Hook(func, "post_hook")
471+
_add_hook(func, hook)
472+
473+
hook._add_hook(kwargs)
474+
return func
475+
476+
if callable(param):
477+
return _decorate(param)
478+
else:
479+
return lambda func: _decorate(func)

cloudbot/plugin.py

Lines changed: 46 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import re
88
import warnings
99
from collections import defaultdict
10+
from functools import partial
1011
from itertools import chain
1112
from operator import attrgetter
1213

@@ -15,7 +16,7 @@
1516

1617
import time
1718

18-
from cloudbot.event import Event
19+
from cloudbot.event import Event, PostHookEvent
1920
from cloudbot.hook import Priority, Action
2021
from cloudbot.util import database, async_util
2122

@@ -42,9 +43,11 @@ def find_hooks(parent, module):
4243
on_cap_available = []
4344
on_connect = []
4445
out_sieve = []
46+
post_hooks = []
4547
type_lists = {"command": command, "regex": regex, "irc_raw": raw, "sieve": sieve, "event": event,
4648
"periodic": periodic, "on_start": on_start, "on_stop": on_stop, "on_cap_ack": on_cap_ack,
47-
"on_cap_available": on_cap_available, "on_connect": on_connect, "irc_out": out_sieve}
49+
"on_cap_available": on_cap_available, "on_connect": on_connect, "irc_out": out_sieve,
50+
"post_hook": post_hooks}
4851
for name, func in module.__dict__.items():
4952
if hasattr(func, "_cloudbot_hook"):
5053
# if it has cloudbot hook
@@ -56,7 +59,7 @@ def find_hooks(parent, module):
5659
# delete the hook to free memory
5760
del func._cloudbot_hook
5861

59-
return command, regex, raw, sieve, event, periodic, on_start, on_stop, on_cap_ack, on_cap_available, on_connect, out_sieve
62+
return command, regex, raw, sieve, event, periodic, on_start, on_stop, on_cap_ack, on_cap_available, on_connect, out_sieve, post_hooks
6063

6164

6265
def find_tables(code):
@@ -114,6 +117,7 @@ def __init__(self, bot):
114117
self.cap_hooks = {"on_available": defaultdict(list), "on_ack": defaultdict(list)}
115118
self.connect_hooks = []
116119
self.out_sieves = []
120+
self.hook_hooks = defaultdict(list)
117121
self._hook_waiting_queues = {}
118122

119123
@asyncio.coroutine
@@ -262,9 +266,13 @@ def load_plugin(self, path):
262266
self.out_sieves.append(out_hook)
263267
self._log_hook(out_hook)
264268

269+
for post_hook in plugin.post_hook_hooks:
270+
self.hook_hooks["post"].append(post_hook)
271+
self._log_hook(post_hook)
272+
265273
# Sort hooks
266274
self.regex_hooks.sort(key=lambda x: x[1].priority)
267-
dicts_of_lists_of_hooks = (self.event_type_hooks, self.raw_triggers)
275+
dicts_of_lists_of_hooks = (self.event_type_hooks, self.raw_triggers, self.hook_hooks)
268276
lists_of_hooks = [self.catch_all_triggers, self.sieves, self.connect_hooks, self.out_sieves]
269277
lists_of_hooks.extend(chain.from_iterable(d.values() for d in dicts_of_lists_of_hooks))
270278

@@ -358,6 +366,9 @@ def unload_plugin(self, path):
358366
for out_hook in plugin.irc_out_hooks:
359367
self.out_sieves.remove(out_hook)
360368

369+
for post_hook in plugin.post_hook_hooks:
370+
self.hook_hooks["post"].remove(post_hook)
371+
361372
# Run on_stop hooks
362373
for on_stop_hook in plugin.run_on_stop:
363374
event = Event(bot=self.bot, hook=on_stop_hook)
@@ -450,9 +461,9 @@ def internal_launch(self, hook, event):
450461
out = yield from self.bot.loop.run_in_executor(None, self._execute_hook_threaded, hook, event)
451462
else:
452463
out = yield from self._execute_hook_sync(hook, event)
453-
except Exception:
464+
except Exception as e:
454465
logger.exception("Error in hook {}".format(hook.description))
455-
return False, None
466+
return False, e
456467

457468
return True, out
458469

@@ -468,13 +479,22 @@ def _execute_hook(self, hook, event):
468479
:rtype: bool
469480
"""
470481
ok, out = yield from self.internal_launch(hook, event)
471-
if out is not None:
472-
if isinstance(out, (list, tuple)):
473-
# if there are multiple items in the response, return them on multiple lines
474-
event.reply(*out)
475-
else:
476-
event.reply(*str(out).split('\n'))
477-
return True
482+
result, error = None, None
483+
if ok is True:
484+
result = out
485+
else:
486+
error = out
487+
488+
post_event = partial(
489+
PostHookEvent, launched_hook=hook, launched_event=event, bot=event.bot,
490+
conn=event.conn, result=result, error=error
491+
)
492+
for post_hook in self.hook_hooks["post"]:
493+
success, res = yield from self.internal_launch(post_hook, post_event(hook=post_hook))
494+
if success and res is False:
495+
break
496+
497+
return ok
478498

479499
@asyncio.coroutine
480500
def _sieve(self, sieve, event, hook):
@@ -524,10 +544,6 @@ def launch(self, hook, event):
524544
if event is None:
525545
return False
526546

527-
if hook.type == "command" and hook.auto_help and not event.text and hook.doc is not None:
528-
event.notice_doc()
529-
return False
530-
531547
if hook.single_thread:
532548
# There should only be one running instance of this hook, so let's wait for the last event to be processed
533549
# before starting this one.
@@ -600,6 +616,7 @@ def __init__(self, filepath, filename, title, code):
600616
self.sieves, self.events, self.periodic, *hooks = hooks
601617
self.run_on_start, self.run_on_stop, self.on_cap_ack, *hooks = hooks
602618
self.on_cap_available, self.connect_hooks, self.irc_out_hooks, *hooks = hooks
619+
self.post_hook_hooks, *hooks = hooks
603620
# we need to find tables for each plugin so that they can be unloaded from the global metadata when the
604621
# plugin is reloaded
605622
self.tables = find_tables(code)
@@ -803,9 +820,6 @@ def __init__(self, plugin, sieve_hook):
803820
:type plugin: Plugin
804821
:type sieve_hook: cloudbot.util.hook._SieveHook
805822
"""
806-
807-
self.priority = sieve_hook.kwargs.pop("priority", 100)
808-
# We don't want to thread sieves by default - this is retaining old behavior for compatibility
809823
super().__init__("sieve", plugin, sieve_hook)
810824

811825
def __repr__(self):
@@ -891,8 +905,6 @@ def __init__(self, plugin, sieve_hook):
891905
:type plugin: Plugin
892906
:type sieve_hook: cloudbot.util.hook._Hook
893907
"""
894-
895-
self.priority = sieve_hook.kwargs.pop("priority", 100)
896908
super().__init__("on_connect", plugin, sieve_hook)
897909

898910
def __repr__(self):
@@ -913,6 +925,17 @@ def __str__(self):
913925
return "irc_out {} from {}".format(self.function_name, self.plugin.file_name)
914926

915927

928+
class PostHookHook(Hook):
929+
def __init__(self, plugin, out_hook):
930+
super().__init__("post_hook", plugin, out_hook)
931+
932+
def __repr__(self):
933+
return "Post_hook[{}]".format(Hook.__repr__(self))
934+
935+
def __str__(self):
936+
return "post_hook {} from {}".format(self.function_name, self.plugin.file_name)
937+
938+
916939
_hook_name_to_plugin = {
917940
"command": CommandHook,
918941
"regex": RegexHook,
@@ -926,4 +949,5 @@ def __str__(self):
926949
"on_cap_ack": OnCapAckHook,
927950
"on_connect": OnConnectHook,
928951
"irc_out": IrcOutHook,
952+
"post_hook": PostHookHook,
929953
}

plugins/core_hooks.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from cloudbot import hook
2+
from cloudbot.hook import Priority
3+
4+
5+
@hook.sieve(priority=Priority.LOWEST)
6+
def cmd_autohelp(bot, event, _hook):
7+
if _hook.type == "command" and _hook.auto_help and not event.text and _hook.doc is not None:
8+
event.notice_doc()
9+
return None
10+
11+
return event
12+
13+
14+
@hook.post_hook(priority=Priority.LOWEST)
15+
def do_reply(result, error, launched_event):
16+
if error is None and result is not None:
17+
if isinstance(result, (list, tuple)):
18+
# if there are multiple items in the response, return them on multiple lines
19+
launched_event.reply(*result)
20+
else:
21+
launched_event.reply(*str(result).split('\n'))

0 commit comments

Comments
 (0)