Skip to content

Commit 0dde883

Browse files
authored
Merge pull request CloudBotIRC#179 from linuxdaemon/gonzobot+op-tracking
Add channel op tracking
2 parents ce231cf + 7da2fba commit 0dde883

9 files changed

Lines changed: 514 additions & 56 deletions

File tree

cloudbot/hook.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,16 @@ def add_hook(self, caps, kwargs):
207207
self.caps.update(caps)
208208

209209

210+
class _PermissionHook(_Hook):
211+
def __init__(self, func):
212+
super().__init__(func, "perm_check")
213+
self.perms = set()
214+
215+
def add_hook(self, perms, kwargs):
216+
self._add_hook(kwargs)
217+
self.perms.update(perms)
218+
219+
210220
def _add_hook(func, hook):
211221
if not hasattr(func, "_cloudbot_hook"):
212222
func._cloudbot_hook = {}
@@ -439,3 +449,18 @@ def _on_connect_hook(func):
439449

440450

441451
connect = on_connect
452+
453+
454+
def permission(*perms, **kwargs):
455+
def _perm_hook(func):
456+
assert len(inspect.getfullargspec(func).args) == 3, \
457+
"Permission hook has incorrect argument count. Needs params: bot, event, hook"
458+
hook = _get_hook(func, "perm_check")
459+
if hook is None:
460+
hook = _PermissionHook(func)
461+
_add_hook(func, hook)
462+
463+
hook.add_hook(perms, kwargs)
464+
return func
465+
466+
return lambda func: _perm_hook(func)

cloudbot/plugin.py

Lines changed: 51 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -26,36 +26,23 @@ def find_hooks(parent, module):
2626
"""
2727
:type parent: Plugin
2828
:type module: object
29-
:rtype: (list[CommandHook], list[RegexHook], list[RawHook], list[SieveHook], List[EventHook], List[PeriodicHook], list[OnStartHook], List[OnStopHook], list[OnCapAckHook], list[OnCapAvailableHook], list[OnConnectHook])
29+
:rtype: dict
3030
"""
3131
# set the loaded flag
3232
module._cloudbot_loaded = True
33-
command = []
34-
regex = []
35-
raw = []
36-
sieve = []
37-
event = []
38-
periodic = []
39-
on_start = []
40-
on_stop = []
41-
on_cap_ack = []
42-
on_cap_available = []
43-
on_connect = []
44-
type_lists = {"command": command, "regex": regex, "irc_raw": raw, "sieve": sieve, "event": event,
45-
"periodic": periodic, "on_start": on_start, "on_stop": on_stop, "on_cap_ack": on_cap_ack,
46-
"on_cap_available": on_cap_available, "on_connect": on_connect}
33+
hooks = defaultdict(list)
4734
for name, func in module.__dict__.items():
4835
if hasattr(func, "_cloudbot_hook"):
4936
# if it has cloudbot hook
5037
func_hooks = func._cloudbot_hook
5138

5239
for hook_type, func_hook in func_hooks.items():
53-
type_lists[hook_type].append(_hook_name_to_plugin[hook_type](parent, func_hook))
40+
hooks[hook_type].append(_hook_name_to_plugin[hook_type](parent, func_hook))
5441

5542
# delete the hook to free memory
5643
del func._cloudbot_hook
5744

58-
return command, regex, raw, sieve, event, periodic, on_start, on_stop, on_cap_ack, on_cap_available, on_connect
45+
return hooks
5946

6047

6148
def find_tables(code):
@@ -112,6 +99,7 @@ def __init__(self, bot):
11299
self.sieves = []
113100
self.cap_hooks = {"on_available": defaultdict(list), "on_ack": defaultdict(list)}
114101
self.connect_hooks = []
102+
self.perm_hooks = defaultdict(list)
115103
self._hook_waiting_queues = {}
116104

117105
@asyncio.coroutine
@@ -182,7 +170,7 @@ def load_plugin(self, path):
182170
yield from plugin.create_tables(self.bot)
183171

184172
# run on_start hooks
185-
for on_start_hook in plugin.run_on_start:
173+
for on_start_hook in plugin.hooks["on_start"]:
186174
success = yield from self.launch(on_start_hook, Event(bot=self.bot, hook=on_start_hook))
187175
if not success:
188176
logger.warning("Not registering hooks from plugin {}: on_start hook errored".format(plugin.title))
@@ -193,23 +181,23 @@ def load_plugin(self, path):
193181

194182
self.plugins[plugin.file_name] = plugin
195183

196-
for on_cap_available_hook in plugin.on_cap_available:
184+
for on_cap_available_hook in plugin.hooks["on_cap_available"]:
197185
for cap in on_cap_available_hook.caps:
198186
self.cap_hooks["on_available"][cap.casefold()].append(on_cap_available_hook)
199187
self._log_hook(on_cap_available_hook)
200188

201-
for on_cap_ack_hook in plugin.on_cap_ack:
189+
for on_cap_ack_hook in plugin.hooks["on_cap_ack"]:
202190
for cap in on_cap_ack_hook.caps:
203191
self.cap_hooks["on_ack"][cap.casefold()].append(on_cap_ack_hook)
204192
self._log_hook(on_cap_ack_hook)
205193

206-
for periodic_hook in plugin.periodic:
194+
for periodic_hook in plugin.hooks["periodic"]:
207195
task = async_util.wrap_future(self._start_periodic(periodic_hook))
208196
plugin.tasks.append(task)
209197
self._log_hook(periodic_hook)
210198

211199
# register commands
212-
for command_hook in plugin.commands:
200+
for command_hook in plugin.hooks["command"]:
213201
for alias in command_hook.aliases:
214202
if alias in self.commands:
215203
logger.warning(
@@ -220,7 +208,7 @@ def load_plugin(self, path):
220208
self._log_hook(command_hook)
221209

222210
# register raw hooks
223-
for raw_hook in plugin.raw_hooks:
211+
for raw_hook in plugin.hooks["irc_raw"]:
224212
if raw_hook.is_catch_all():
225213
self.catch_all_triggers.append(raw_hook)
226214
else:
@@ -232,7 +220,7 @@ def load_plugin(self, path):
232220
self._log_hook(raw_hook)
233221

234222
# register events
235-
for event_hook in plugin.events:
223+
for event_hook in plugin.hooks["event"]:
236224
for event_type in event_hook.types:
237225
if event_type in self.event_type_hooks:
238226
self.event_type_hooks[event_type].append(event_hook)
@@ -241,36 +229,42 @@ def load_plugin(self, path):
241229
self._log_hook(event_hook)
242230

243231
# register regexps
244-
for regex_hook in plugin.regexes:
232+
for regex_hook in plugin.hooks["regex"]:
245233
for regex_match in regex_hook.regexes:
246234
self.regex_hooks.append((regex_match, regex_hook))
247235
self._log_hook(regex_hook)
248236

249237
# register sieves
250-
for sieve_hook in plugin.sieves:
238+
for sieve_hook in plugin.hooks["sieve"]:
251239
self.sieves.append(sieve_hook)
252240
self._log_hook(sieve_hook)
253241

254242
# register connect hooks
255-
for connect_hook in plugin.connect_hooks:
243+
for connect_hook in plugin.hooks["on_connect"]:
256244
self.connect_hooks.append(connect_hook)
257245
self._log_hook(connect_hook)
258246

247+
for perm_hook in plugin.hooks["perm_check"]:
248+
for perm in perm_hook.perms:
249+
self.perm_hooks[perm].append(perm_hook)
250+
251+
self._log_hook(perm_hook)
252+
259253
# sort sieve hooks by priority
260254
self.sieves.sort(key=lambda x: x.priority)
261255
self.connect_hooks.sort(key=attrgetter("priority"))
262256

263257
# Sort hooks
264258
self.regex_hooks.sort(key=lambda x: x[1].priority)
265-
dicts_of_lists_of_hooks = (self.event_type_hooks, self.raw_triggers)
259+
dicts_of_lists_of_hooks = (self.event_type_hooks, self.raw_triggers, self.perm_hooks)
266260
lists_of_hooks = [self.catch_all_triggers, self.sieves]
267261
lists_of_hooks.extend(chain.from_iterable(d.values() for d in dicts_of_lists_of_hooks))
268262

269263
for lst in lists_of_hooks:
270264
lst.sort(key=lambda x: x.priority)
271265

272266
# we don't need this anymore
273-
del plugin.run_on_start
267+
del plugin.hooks["on_start"]
274268

275269
@asyncio.coroutine
276270
def unload_plugin(self, path):
@@ -298,15 +292,15 @@ def unload_plugin(self, path):
298292
for task in plugin.tasks:
299293
task.cancel()
300294

301-
for on_cap_available_hook in plugin.on_cap_available:
295+
for on_cap_available_hook in plugin.hooks["on_cap_available"]:
302296
available_hooks = self.cap_hooks["on_available"]
303297
for cap in on_cap_available_hook.caps:
304298
cap_cf = cap.casefold()
305299
available_hooks[cap_cf].remove(on_cap_available_hook)
306300
if not available_hooks[cap_cf]:
307301
del available_hooks[cap_cf]
308302

309-
for on_cap_ack in plugin.on_cap_ack:
303+
for on_cap_ack in plugin.hooks["on_cap_ack"]:
310304
ack_hooks = self.cap_hooks["on_ack"]
311305
for cap in on_cap_ack.caps:
312306
cap_cf = cap.casefold()
@@ -315,14 +309,14 @@ def unload_plugin(self, path):
315309
del ack_hooks[cap_cf]
316310

317311
# unregister commands
318-
for command_hook in plugin.commands:
312+
for command_hook in plugin.hooks["command"]:
319313
for alias in command_hook.aliases:
320314
if alias in self.commands and self.commands[alias] == command_hook:
321315
# we need to make sure that there wasn't a conflict, so we don't delete another plugin's command
322316
del self.commands[alias]
323317

324318
# unregister raw hooks
325-
for raw_hook in plugin.raw_hooks:
319+
for raw_hook in plugin.hooks["irc_raw"]:
326320
if raw_hook.is_catch_all():
327321
self.catch_all_triggers.remove(raw_hook)
328322
else:
@@ -333,28 +327,32 @@ def unload_plugin(self, path):
333327
del self.raw_triggers[trigger]
334328

335329
# unregister events
336-
for event_hook in plugin.events:
330+
for event_hook in plugin.hooks["event"]:
337331
for event_type in event_hook.types:
338332
assert event_type in self.event_type_hooks # this can't be not true
339333
self.event_type_hooks[event_type].remove(event_hook)
340334
if not self.event_type_hooks[event_type]: # if that was the last hook for this event type
341335
del self.event_type_hooks[event_type]
342336

343337
# unregister regexps
344-
for regex_hook in plugin.regexes:
338+
for regex_hook in plugin.hooks["regex"]:
345339
for regex_match in regex_hook.regexes:
346340
self.regex_hooks.remove((regex_match, regex_hook))
347341

348342
# unregister sieves
349-
for sieve_hook in plugin.sieves:
343+
for sieve_hook in plugin.hooks["sieve"]:
350344
self.sieves.remove(sieve_hook)
351345

352346
# unregister connect hooks
353-
for connect_hook in plugin.connect_hooks:
347+
for connect_hook in plugin.hooks["on_connect"]:
354348
self.connect_hooks.remove(connect_hook)
355349

350+
for perm_hook in plugin.hooks["perm_check"]:
351+
for perm in perm_hook.perms:
352+
self.perm_hooks[perm].remove(perm_hook)
353+
356354
# Run on_stop hooks
357-
for on_stop_hook in plugin.run_on_stop:
355+
for on_stop_hook in plugin.hooks["on_stop"]:
358356
event = Event(bot=self.bot, hook=on_stop_hook)
359357
yield from self.launch(on_stop_hook, event)
360358

@@ -562,11 +560,7 @@ class Plugin:
562560
:type file_path: str
563561
:type file_name: str
564562
:type title: str
565-
:type commands: list[CommandHook]
566-
:type regexes: list[RegexHook]
567-
:type raw_hooks: list[RawHook]
568-
:type sieves: list[SieveHook]
569-
:type events: list[EventHook]
563+
:type hooks: dict
570564
:type tables: list[sqlalchemy.Table]
571565
"""
572566

@@ -580,12 +574,7 @@ def __init__(self, filepath, filename, title, code):
580574
self.file_path = filepath
581575
self.file_name = filename
582576
self.title = title
583-
# TODO clean up hook lists
584-
hooks = find_hooks(self, code)
585-
self.commands, self.regexes, self.raw_hooks, *hooks = hooks
586-
self.sieves, self.events, self.periodic, *hooks = hooks
587-
self.run_on_start, self.run_on_stop, self.on_cap_ack, *hooks = hooks
588-
self.on_cap_available, self.connect_hooks, *hooks = hooks
577+
self.hooks = find_hooks(self, code)
589578
# we need to find tables for each plugin so that they can be unloaded from the global metadata when the
590579
# plugin is reloaded
591580
self.tables = find_tables(code)
@@ -888,6 +877,18 @@ def __str__(self):
888877
return "{name} {func} from {file}".format(name=self.type, func=self.function_name, file=self.plugin.file_name)
889878

890879

880+
class PermHook(Hook):
881+
def __init__(self, plugin, perm_hook):
882+
self.perms = perm_hook.perms
883+
super().__init__("perm_check", plugin, perm_hook)
884+
885+
def __repr__(self):
886+
return "PermHook[{}]".format(Hook.__repr__(self))
887+
888+
def __str__(self):
889+
return "perm hook {} from {}".format(self.function_name, self.plugin.file_name)
890+
891+
891892
_hook_name_to_plugin = {
892893
"command": CommandHook,
893894
"regex": RegexHook,
@@ -900,4 +901,5 @@ def __str__(self):
900901
"on_cap_available": OnCapAvaliableHook,
901902
"on_cap_ack": OnCapAckHook,
902903
"on_connect": OnConnectHook,
904+
"perm_check": PermHook,
903905
}

cloudbot/util/async_util.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import asyncio
66

77
import sys
8+
from functools import partial
89

910

1011
def wrap_future(fut, *, loop=None):
@@ -18,3 +19,12 @@ def wrap_future(fut, *, loop=None):
1819
return asyncio.async(fut, loop=loop)
1920

2021
return asyncio.ensure_future(fut, loop=loop)
22+
23+
24+
@asyncio.coroutine
25+
def run_func(loop, func, *args, **kwargs):
26+
part = partial(func, *args, **kwargs)
27+
if asyncio.iscoroutine(func) or asyncio.iscoroutinefunction(func):
28+
return (yield from part())
29+
else:
30+
return (yield from loop.run_in_executor(None, part))

0 commit comments

Comments
 (0)