Skip to content

Commit 207a9f0

Browse files
authored
Merge branch 'gonzobot' into gonzobot+outgoing-sieves
2 parents f0ad524 + 4c53fc2 commit 207a9f0

21 files changed

Lines changed: 721 additions & 155 deletions

cloudbot/client.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,14 @@ def part(self, channel):
122122
"""
123123
raise NotImplementedError
124124

125+
def is_nick_valid(self, nick):
126+
"""
127+
Determines if a nick is valid for this connection
128+
:param nick: The nick to check
129+
:return: True if it is valid, otherwise false
130+
"""
131+
raise NotImplementedError
132+
125133
@property
126134
def connected(self):
127135
raise NotImplementedError

cloudbot/clients/irc.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
irc_netmask_re = re.compile(r"([^!@]*)!([^@]*)@(.*)")
1717
irc_param_re = re.compile(r"(?:^|(?<= ))(:.*|[^ ]+)")
1818

19+
irc_nick_re = re.compile(r'[A-Za-z0-9^{\}\[\]\-`_|\\]')
20+
1921
irc_bad_chars = ''.join([chr(x) for x in list(range(0, 1)) + list(range(4, 32)) + list(range(127, 160))])
2022
irc_clean_re = re.compile('[{}]'.format(re.escape(irc_bad_chars)))
2123

@@ -227,6 +229,9 @@ def _send(self, line):
227229
def connected(self):
228230
return self._connected
229231

232+
def is_nick_valid(self, nick):
233+
return bool(irc_nick_re.fullmatch(nick))
234+
230235

231236
class _IrcProtocol(asyncio.Protocol):
232237
"""

cloudbot/event.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,14 @@ def async_call(self, func, *args, **kwargs):
326326
result = yield from self.loop.run_in_executor(executor, part)
327327
return result
328328

329+
def is_nick_valid(self, nick):
330+
"""
331+
Returns whether a nick is valid for a given connection
332+
:param nick: The nick to check
333+
:return: Whether or not it is valid
334+
"""
335+
return self.conn.is_nick_valid(nick)
336+
329337
if sys.version_info < (3, 7, 0):
330338
# noinspection PyCompatibility
331339
@asyncio.coroutine

cloudbot/hook.py

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

208208

209+
class _PermissionHook(_Hook):
210+
def __init__(self, func):
211+
super().__init__(func, "perm_check")
212+
self.perms = set()
213+
214+
def add_hook(self, perms, kwargs):
215+
self._add_hook(kwargs)
216+
self.perms.update(perms)
217+
218+
209219
def _add_hook(func, hook):
210220
if not hasattr(func, "_cloudbot_hook"):
211221
func._cloudbot_hook = {}
@@ -477,3 +487,18 @@ def _decorate(func):
477487
return _decorate(param)
478488
else:
479489
return lambda func: _decorate(func)
490+
491+
492+
def permission(*perms, **kwargs):
493+
def _perm_hook(func):
494+
assert len(inspect.getfullargspec(func).args) == 3, \
495+
"Permission hook has incorrect argument count. Needs params: bot, event, hook"
496+
hook = _get_hook(func, "perm_check")
497+
if hook is None:
498+
hook = _PermissionHook(func)
499+
_add_hook(func, hook)
500+
501+
hook.add_hook(perms, kwargs)
502+
return func
503+
504+
return lambda func: _perm_hook(func)

cloudbot/plugin.py

Lines changed: 59 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -27,39 +27,23 @@ def find_hooks(parent, module):
2727
"""
2828
:type parent: Plugin
2929
:type module: object
30-
:rtype: (list[CommandHook], list[RegexHook], list[RawHook], list[SieveHook], List[EventHook], List[PeriodicHook], list[OnStartHook], List[OnStopHook], list[OnCapAckHook], list[OnCapAvailableHook], list[OnConnectHook])
30+
:rtype: dict
3131
"""
3232
# set the loaded flag
3333
module._cloudbot_loaded = True
34-
command = []
35-
regex = []
36-
raw = []
37-
sieve = []
38-
event = []
39-
periodic = []
40-
on_start = []
41-
on_stop = []
42-
on_cap_ack = []
43-
on_cap_available = []
44-
on_connect = []
45-
out_sieve = []
46-
post_hooks = []
47-
type_lists = {"command": command, "regex": regex, "irc_raw": raw, "sieve": sieve, "event": event,
48-
"periodic": periodic, "on_start": on_start, "on_stop": on_stop, "on_cap_ack": on_cap_ack,
49-
"on_cap_available": on_cap_available, "on_connect": on_connect, "irc_out": out_sieve,
50-
"post_hook": post_hooks}
34+
hooks = defaultdict(list)
5135
for name, func in module.__dict__.items():
5236
if hasattr(func, "_cloudbot_hook"):
5337
# if it has cloudbot hook
5438
func_hooks = func._cloudbot_hook
5539

5640
for hook_type, func_hook in func_hooks.items():
57-
type_lists[hook_type].append(_hook_name_to_plugin[hook_type](parent, func_hook))
41+
hooks[hook_type].append(_hook_name_to_plugin[hook_type](parent, func_hook))
5842

5943
# delete the hook to free memory
6044
del func._cloudbot_hook
6145

62-
return command, regex, raw, sieve, event, periodic, on_start, on_stop, on_cap_ack, on_cap_available, on_connect, out_sieve, post_hooks
46+
return hooks
6347

6448

6549
def find_tables(code):
@@ -118,6 +102,7 @@ def __init__(self, bot):
118102
self.connect_hooks = []
119103
self.out_sieves = []
120104
self.hook_hooks = defaultdict(list)
105+
self.perm_hooks = defaultdict(list)
121106
self._hook_waiting_queues = {}
122107

123108
@asyncio.coroutine
@@ -188,7 +173,7 @@ def load_plugin(self, path):
188173
yield from plugin.create_tables(self.bot)
189174

190175
# run on_start hooks
191-
for on_start_hook in plugin.run_on_start:
176+
for on_start_hook in plugin.hooks["on_start"]:
192177
success = yield from self.launch(on_start_hook, Event(bot=self.bot, hook=on_start_hook))
193178
if not success:
194179
logger.warning("Not registering hooks from plugin {}: on_start hook errored".format(plugin.title))
@@ -199,23 +184,23 @@ def load_plugin(self, path):
199184

200185
self.plugins[plugin.file_name] = plugin
201186

202-
for on_cap_available_hook in plugin.on_cap_available:
187+
for on_cap_available_hook in plugin.hooks["on_cap_available"]:
203188
for cap in on_cap_available_hook.caps:
204189
self.cap_hooks["on_available"][cap.casefold()].append(on_cap_available_hook)
205190
self._log_hook(on_cap_available_hook)
206191

207-
for on_cap_ack_hook in plugin.on_cap_ack:
192+
for on_cap_ack_hook in plugin.hooks["on_cap_ack"]:
208193
for cap in on_cap_ack_hook.caps:
209194
self.cap_hooks["on_ack"][cap.casefold()].append(on_cap_ack_hook)
210195
self._log_hook(on_cap_ack_hook)
211196

212-
for periodic_hook in plugin.periodic:
197+
for periodic_hook in plugin.hooks["periodic"]:
213198
task = async_util.wrap_future(self._start_periodic(periodic_hook))
214199
plugin.tasks.append(task)
215200
self._log_hook(periodic_hook)
216201

217202
# register commands
218-
for command_hook in plugin.commands:
203+
for command_hook in plugin.hooks["command"]:
219204
for alias in command_hook.aliases:
220205
if alias in self.commands:
221206
logger.warning(
@@ -226,7 +211,7 @@ def load_plugin(self, path):
226211
self._log_hook(command_hook)
227212

228213
# register raw hooks
229-
for raw_hook in plugin.raw_hooks:
214+
for raw_hook in plugin.hooks["irc_raw"]:
230215
if raw_hook.is_catch_all():
231216
self.catch_all_triggers.append(raw_hook)
232217
else:
@@ -238,7 +223,7 @@ def load_plugin(self, path):
238223
self._log_hook(raw_hook)
239224

240225
# register events
241-
for event_hook in plugin.events:
226+
for event_hook in plugin.hooks["event"]:
242227
for event_type in event_hook.types:
243228
if event_type in self.event_type_hooks:
244229
self.event_type_hooks[event_type].append(event_hook)
@@ -247,40 +232,50 @@ def load_plugin(self, path):
247232
self._log_hook(event_hook)
248233

249234
# register regexps
250-
for regex_hook in plugin.regexes:
235+
for regex_hook in plugin.hooks["regex"]:
251236
for regex_match in regex_hook.regexes:
252237
self.regex_hooks.append((regex_match, regex_hook))
253238
self._log_hook(regex_hook)
254239

255240
# register sieves
256-
for sieve_hook in plugin.sieves:
241+
for sieve_hook in plugin.hooks["sieve"]:
257242
self.sieves.append(sieve_hook)
258243
self._log_hook(sieve_hook)
259244

260245
# register connect hooks
261-
for connect_hook in plugin.connect_hooks:
246+
for connect_hook in plugin.hooks["on_connect"]:
262247
self.connect_hooks.append(connect_hook)
263248
self._log_hook(connect_hook)
264249

265-
for out_hook in plugin.irc_out_hooks:
250+
for out_hook in plugin.hooks["irc_out"]:
266251
self.out_sieves.append(out_hook)
267252
self._log_hook(out_hook)
268253

269-
for post_hook in plugin.post_hook_hooks:
254+
for post_hook in plugin.hooks["post_hook"]:
270255
self.hook_hooks["post"].append(post_hook)
271256
self._log_hook(post_hook)
272257

258+
for perm_hook in plugin.hooks["perm_check"]:
259+
for perm in perm_hook.perms:
260+
self.perm_hooks[perm].append(perm_hook)
261+
262+
self._log_hook(perm_hook)
263+
264+
# sort sieve hooks by priority
265+
self.sieves.sort(key=lambda x: x.priority)
266+
self.connect_hooks.sort(key=attrgetter("priority"))
267+
273268
# Sort hooks
274269
self.regex_hooks.sort(key=lambda x: x[1].priority)
275-
dicts_of_lists_of_hooks = (self.event_type_hooks, self.raw_triggers, self.hook_hooks)
270+
dicts_of_lists_of_hooks = (self.event_type_hooks, self.raw_triggers, self.perm_hooks, self.hook_hooks)
276271
lists_of_hooks = [self.catch_all_triggers, self.sieves, self.connect_hooks, self.out_sieves]
277272
lists_of_hooks.extend(chain.from_iterable(d.values() for d in dicts_of_lists_of_hooks))
278273

279274
for lst in lists_of_hooks:
280275
lst.sort(key=attrgetter("priority"))
281276

282277
# we don't need this anymore
283-
del plugin.run_on_start
278+
del plugin.hooks["on_start"]
284279

285280
@asyncio.coroutine
286281
def unload_plugin(self, path):
@@ -308,15 +303,15 @@ def unload_plugin(self, path):
308303
for task in plugin.tasks:
309304
task.cancel()
310305

311-
for on_cap_available_hook in plugin.on_cap_available:
306+
for on_cap_available_hook in plugin.hooks["on_cap_available"]:
312307
available_hooks = self.cap_hooks["on_available"]
313308
for cap in on_cap_available_hook.caps:
314309
cap_cf = cap.casefold()
315310
available_hooks[cap_cf].remove(on_cap_available_hook)
316311
if not available_hooks[cap_cf]:
317312
del available_hooks[cap_cf]
318313

319-
for on_cap_ack in plugin.on_cap_ack:
314+
for on_cap_ack in plugin.hooks["on_cap_ack"]:
320315
ack_hooks = self.cap_hooks["on_ack"]
321316
for cap in on_cap_ack.caps:
322317
cap_cf = cap.casefold()
@@ -325,14 +320,14 @@ def unload_plugin(self, path):
325320
del ack_hooks[cap_cf]
326321

327322
# unregister commands
328-
for command_hook in plugin.commands:
323+
for command_hook in plugin.hooks["command"]:
329324
for alias in command_hook.aliases:
330325
if alias in self.commands and self.commands[alias] == command_hook:
331326
# we need to make sure that there wasn't a conflict, so we don't delete another plugin's command
332327
del self.commands[alias]
333328

334329
# unregister raw hooks
335-
for raw_hook in plugin.raw_hooks:
330+
for raw_hook in plugin.hooks["irc_raw"]:
336331
if raw_hook.is_catch_all():
337332
self.catch_all_triggers.remove(raw_hook)
338333
else:
@@ -343,34 +338,38 @@ def unload_plugin(self, path):
343338
del self.raw_triggers[trigger]
344339

345340
# unregister events
346-
for event_hook in plugin.events:
341+
for event_hook in plugin.hooks["event"]:
347342
for event_type in event_hook.types:
348343
assert event_type in self.event_type_hooks # this can't be not true
349344
self.event_type_hooks[event_type].remove(event_hook)
350345
if not self.event_type_hooks[event_type]: # if that was the last hook for this event type
351346
del self.event_type_hooks[event_type]
352347

353348
# unregister regexps
354-
for regex_hook in plugin.regexes:
349+
for regex_hook in plugin.hooks["regex"]:
355350
for regex_match in regex_hook.regexes:
356351
self.regex_hooks.remove((regex_match, regex_hook))
357352

358353
# unregister sieves
359-
for sieve_hook in plugin.sieves:
354+
for sieve_hook in plugin.hooks["sieve"]:
360355
self.sieves.remove(sieve_hook)
361356

362357
# unregister connect hooks
363-
for connect_hook in plugin.connect_hooks:
358+
for connect_hook in plugin.hooks["on_connect"]:
364359
self.connect_hooks.remove(connect_hook)
365360

366-
for out_hook in plugin.irc_out_hooks:
361+
for out_hook in plugin.hooks["irc_out"]:
367362
self.out_sieves.remove(out_hook)
368363

369-
for post_hook in plugin.post_hook_hooks:
364+
for post_hook in plugin.hooks["post_hook"]:
370365
self.hook_hooks["post"].remove(post_hook)
371366

367+
for perm_hook in plugin.hooks["perm_check"]:
368+
for perm in perm_hook.perms:
369+
self.perm_hooks[perm].remove(perm_hook)
370+
372371
# Run on_stop hooks
373-
for on_stop_hook in plugin.run_on_stop:
372+
for on_stop_hook in plugin.hooks["on_stop"]:
374373
event = Event(bot=self.bot, hook=on_stop_hook)
375374
yield from self.launch(on_stop_hook, event)
376375

@@ -592,11 +591,7 @@ class Plugin:
592591
:type file_path: str
593592
:type file_name: str
594593
:type title: str
595-
:type commands: list[CommandHook]
596-
:type regexes: list[RegexHook]
597-
:type raw_hooks: list[RawHook]
598-
:type sieves: list[SieveHook]
599-
:type events: list[EventHook]
594+
:type hooks: dict
600595
:type tables: list[sqlalchemy.Table]
601596
"""
602597

@@ -610,13 +605,7 @@ def __init__(self, filepath, filename, title, code):
610605
self.file_path = filepath
611606
self.file_name = filename
612607
self.title = title
613-
# TODO clean up hook lists
614-
hooks = find_hooks(self, code)
615-
self.commands, self.regexes, self.raw_hooks, *hooks = hooks
616-
self.sieves, self.events, self.periodic, *hooks = hooks
617-
self.run_on_start, self.run_on_stop, self.on_cap_ack, *hooks = hooks
618-
self.on_cap_available, self.connect_hooks, self.irc_out_hooks, *hooks = hooks
619-
self.post_hook_hooks, *hooks = hooks
608+
self.hooks = find_hooks(self, code)
620609
# we need to find tables for each plugin so that they can be unloaded from the global metadata when the
621610
# plugin is reloaded
622611
self.tables = find_tables(code)
@@ -936,6 +925,18 @@ def __str__(self):
936925
return "post_hook {} from {}".format(self.function_name, self.plugin.file_name)
937926

938927

928+
class PermHook(Hook):
929+
def __init__(self, plugin, perm_hook):
930+
self.perms = perm_hook.perms
931+
super().__init__("perm_check", plugin, perm_hook)
932+
933+
def __repr__(self):
934+
return "PermHook[{}]".format(Hook.__repr__(self))
935+
936+
def __str__(self):
937+
return "perm hook {} from {}".format(self.function_name, self.plugin.file_name)
938+
939+
939940
_hook_name_to_plugin = {
940941
"command": CommandHook,
941942
"regex": RegexHook,
@@ -950,4 +951,5 @@ def __str__(self):
950951
"on_connect": OnConnectHook,
951952
"irc_out": IrcOutHook,
952953
"post_hook": PostHookHook,
954+
"perm_check": PermHook,
953955
}

0 commit comments

Comments
 (0)