Skip to content

Commit 940e70e

Browse files
authored
Merge pull request CloudBotIRC#70 from linuxdaemon/gonzobot+regex-priorities
Add hook halting system and priorities
2 parents 95a438c + 4c6cdd7 commit 940e70e

4 files changed

Lines changed: 89 additions & 25 deletions

File tree

cloudbot/bot.py

Lines changed: 48 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,17 @@
55
import re
66
import os
77
import gc
8+
from operator import attrgetter
9+
810
from sqlalchemy import create_engine
911

1012
from sqlalchemy.orm import scoped_session, sessionmaker
1113
from sqlalchemy.ext.declarative import declarative_base
1214
from sqlalchemy.schema import MetaData
1315

14-
import cloudbot
1516
from cloudbot.client import Client
1617
from cloudbot.config import Config
18+
from cloudbot.hook import Action
1719
from cloudbot.reloader import PluginReloader
1820
from cloudbot.plugin import PluginManager
1921
from cloudbot.event import Event, CommandEvent, RegexEvent, EventType
@@ -221,23 +223,46 @@ def process(self, event):
221223
run_before_tasks = []
222224
tasks = []
223225
command_prefix = event.conn.config.get('command_prefix', '.')
226+
halted = False
227+
228+
def add_hook(hook, _event, _run_before=False):
229+
nonlocal halted
230+
if halted:
231+
return False
232+
233+
coro = self.plugin_manager.launch(hook, _event)
234+
if _run_before:
235+
run_before_tasks.append(coro)
236+
else:
237+
tasks.append(coro)
238+
239+
if hook.action is Action.HALTALL:
240+
halted = True
241+
return False
242+
elif hook.action is Action.HALTTYPE:
243+
return False
244+
return True
224245

225246
# Raw IRC hook
226247
for raw_hook in self.plugin_manager.catch_all_triggers:
227248
# run catch-all coroutine hooks before all others - TODO: Make this a plugin argument
228-
if not raw_hook.threaded:
229-
run_before_tasks.append(
230-
self.plugin_manager.launch(raw_hook, Event(hook=raw_hook, base_event=event)))
231-
else:
232-
tasks.append(self.plugin_manager.launch(raw_hook, Event(hook=raw_hook, base_event=event)))
249+
run_before = not raw_hook.threaded
250+
if not add_hook(raw_hook, Event(hook=raw_hook, base_event=event), _run_before=run_before):
251+
# The hook has an action of Action.HALT* so stop adding new tasks
252+
break
253+
233254
if event.irc_command in self.plugin_manager.raw_triggers:
234255
for raw_hook in self.plugin_manager.raw_triggers[event.irc_command]:
235-
tasks.append(self.plugin_manager.launch(raw_hook, Event(hook=raw_hook, base_event=event)))
256+
if not add_hook(raw_hook, Event(hook=raw_hook, base_event=event)):
257+
# The hook has an action of Action.HALT* so stop adding new tasks
258+
break
236259

237260
# Event hooks
238261
if event.type in self.plugin_manager.event_type_hooks:
239262
for event_hook in self.plugin_manager.event_type_hooks[event.type]:
240-
tasks.append(self.plugin_manager.launch(event_hook, Event(hook=event_hook, base_event=event)))
263+
if not add_hook(event_hook, Event(hook=event_hook, base_event=event)):
264+
# The hook has an action of Action.HALT* so stop adding new tasks
265+
break
241266

242267
if event.type is EventType.message:
243268
# Commands
@@ -258,7 +283,7 @@ def process(self, event):
258283
command_hook = self.plugin_manager.commands[command]
259284
command_event = CommandEvent(hook=command_hook, text=text,
260285
triggered_command=command, base_event=event)
261-
tasks.append(self.plugin_manager.launch(command_hook, command_event))
286+
add_hook(command_hook, command_event)
262287
else:
263288
potential_matches = []
264289
for potential_match, plugin in self.plugin_manager.commands.items():
@@ -269,20 +294,27 @@ def process(self, event):
269294
command_hook = potential_matches[0][1]
270295
command_event = CommandEvent(hook=command_hook, text=text,
271296
triggered_command=command, base_event=event)
272-
tasks.append(self.plugin_manager.launch(command_hook, command_event))
297+
add_hook(command_hook, command_event)
273298
else:
274299
event.notice("Possible matches: {}".format(
275300
formatting.get_text_list([command for command, plugin in potential_matches])))
276301

277302
# Regex hooks
303+
regex_matched = False
278304
for regex, regex_hook in self.plugin_manager.regex_hooks:
279305
if not regex_hook.run_on_cmd and cmd_match:
280-
pass
281-
else:
282-
regex_match = regex.search(event.content)
283-
if regex_match:
284-
regex_event = RegexEvent(hook=regex_hook, match=regex_match, base_event=event)
285-
tasks.append(self.plugin_manager.launch(regex_hook, regex_event))
306+
continue
307+
308+
if regex_hook.only_no_match and regex_matched:
309+
continue
310+
311+
regex_match = regex.search(event.content)
312+
if regex_match:
313+
regex_matched = True
314+
regex_event = RegexEvent(hook=regex_hook, match=regex_match, base_event=event)
315+
if not add_hook(regex_hook, regex_event):
316+
# The hook has an action of Action.HALT* so stop adding new tasks
317+
break
286318

287319
# Run the tasks
288320
yield from asyncio.gather(*run_before_tasks, loop=self.loop)

cloudbot/hook.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,31 @@
11
import inspect
22
import re
33
import collections
4+
from enum import Enum, unique, IntEnum
45

56
from cloudbot.event import EventType
67

78
valid_command_re = re.compile(r"^\w+$")
89

910

11+
@unique
12+
class Priority(IntEnum):
13+
# Reversed to maintain compatibility with sieve hooks numeric priority
14+
LOWEST = 127
15+
LOW = 63
16+
NORMAL = 0
17+
HIGH = -64
18+
HIGHEST = -128
19+
20+
21+
@unique
22+
class Action(Enum):
23+
"""Defines the action to take after executing a hook"""
24+
HALTTYPE = 0 # Once this hook executes, no other hook of that type should run
25+
HALTALL = 1 # Once this hook executes, No other hook should run
26+
CONTINUE = 2 # Normal execution of all hooks
27+
28+
1029
class _Hook:
1130
"""
1231
:type function: function

cloudbot/plugin.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55
import logging
66
import os
77
import re
8+
from itertools import chain
89

910
import sqlalchemy
1011

1112
from cloudbot.event import Event
13+
from cloudbot.hook import Priority, Action
1214
from cloudbot.util import database
1315

1416
logger = logging.getLogger("cloudbot")
@@ -227,8 +229,14 @@ def load_plugin(self, path):
227229
self.sieves.append(sieve_hook)
228230
self._log_hook(sieve_hook)
229231

230-
# sort sieve hooks by priority
231-
self.sieves.sort(key=lambda x: x.priority)
232+
# Sort hooks
233+
self.regex_hooks.sort(key=lambda x: x[1].priority)
234+
dicts_of_lists_of_hooks = (self.event_type_hooks, self.raw_triggers)
235+
lists_of_hooks = [self.catch_all_triggers, self.sieves]
236+
lists_of_hooks.extend(chain.from_iterable(d.values() for d in dicts_of_lists_of_hooks))
237+
238+
for lst in lists_of_hooks:
239+
lst.sort(key=lambda x: x.priority)
232240

233241
# we don't need this anymore
234242
del plugin.run_on_start
@@ -594,6 +602,8 @@ def __init__(self, _type, plugin, func_hook):
594602

595603
self.permissions = func_hook.kwargs.pop("permissions", [])
596604
self.single_thread = func_hook.kwargs.pop("singlethread", False)
605+
self.action = func_hook.kwargs.pop("action", Action.CONTINUE)
606+
self.priority = func_hook.kwargs.pop("priority", Priority.NORMAL)
597607

598608
if func_hook.kwargs:
599609
# we should have popped all the args, so warn if there are any left
@@ -650,6 +660,7 @@ def __init__(self, plugin, regex_hook):
650660
:type regex_hook: cloudbot.util.hook._RegexHook
651661
"""
652662
self.run_on_cmd = regex_hook.kwargs.pop("run_on_cmd", False)
663+
self.only_no_match = regex_hook.kwargs.pop("only_no_match", False)
653664

654665
self.regexes = regex_hook.regexes
655666

plugins/link_announcer.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,18 @@
44
from contextlib import closing
55
from cloudbot import hook
66

7-
# This will match any URL except the patterns defined in blacklist.
8-
blacklist = '.*(reddit\.com|redd\.it|youtube\.com|youtu\.be|imdb\.com|spotify\.com|twitter\.com|twitch\.tv|amazon\.co|xkcd\.com|amzn\.co|steamcommunity\.com|steampowered\.com|newegg\.com|soundcloud\.com|vimeo\.com|speedtest\.net).*'
9-
url_re = re.compile('(?!{})http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+~]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+'.format(blacklist), re.I)
7+
from cloudbot.hook import Priority, Action
8+
9+
# This will match any URL, blacklist removed and abstracted to a priority/halting system
10+
url_re = re.compile(r'https?://(?:[a-zA-Z]|[0-9]|[$-_@.&+~]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', re.I)
1011

1112
opt_out = []
1213

1314
traditional = [
1415
(1024 ** 5, 'PB'),
15-
(1024 ** 4, 'TB'),
16-
(1024 ** 3, 'GB'),
17-
(1024 ** 2, 'MB'),
16+
(1024 ** 4, 'TB'),
17+
(1024 ** 3, 'GB'),
18+
(1024 ** 2, 'MB'),
1819
(1024 ** 1, 'KB'),
1920
(1024 ** 0, 'B'),
2021
]
@@ -29,7 +30,8 @@ def bytesto(bytes, system = traditional):
2930
amount = int(bytes/factor)
3031
return str(amount) + suffix
3132

32-
@hook.regex(url_re)
33+
34+
@hook.regex(url_re, priority=Priority.LOW, action=Action.HALTTYPE, only_no_match=True)
3335
def print_url_title(message, match, chan):
3436
if chan in opt_out:
3537
return

0 commit comments

Comments
 (0)