55import logging
66import os
77import re
8+ from collections import defaultdict
9+ from operator import attrgetter
810
911import sqlalchemy
1012
@@ -18,7 +20,7 @@ def find_hooks(parent, module):
1820 """
1921 :type parent: Plugin
2022 :type module: object
21- :rtype: (list[CommandHook], list[RegexHook], list[RawHook], list[SieveHook], List[EventHook], List[PeriodicHook], list[OnStartHook], List[OnStopHook])
23+ :rtype: (list[CommandHook], list[RegexHook], list[RawHook], list[SieveHook], List[EventHook], List[PeriodicHook], list[OnStartHook], List[OnStopHook], list[OnCapAckHook], list[OnCapAvailableHook], list[OnConnectHook] )
2224 """
2325 # set the loaded flag
2426 module ._cloudbot_loaded = True
@@ -30,8 +32,12 @@ def find_hooks(parent, module):
3032 periodic = []
3133 on_start = []
3234 on_stop = []
35+ on_cap_ack = []
36+ on_cap_available = []
37+ on_connect = []
3338 type_lists = {"command" : command , "regex" : regex , "irc_raw" : raw , "sieve" : sieve , "event" : event ,
34- "periodic" : periodic , "on_start" : on_start , "on_stop" : on_stop }
39+ "periodic" : periodic , "on_start" : on_start , "on_stop" : on_stop , "on_cap_ack" : on_cap_ack ,
40+ "on_cap_available" : on_cap_available , "on_connect" : on_connect }
3541 for name , func in module .__dict__ .items ():
3642 if hasattr (func , "_cloudbot_hook" ):
3743 # if it has cloudbot hook
@@ -43,7 +49,7 @@ def find_hooks(parent, module):
4349 # delete the hook to free memory
4450 del func ._cloudbot_hook
4551
46- return command , regex , raw , sieve , event , periodic , on_start , on_stop
52+ return command , regex , raw , sieve , event , periodic , on_start , on_stop , on_cap_ack , on_cap_available , on_connect
4753
4854
4955def find_tables (code ):
@@ -98,6 +104,8 @@ def __init__(self, bot):
98104 self .event_type_hooks = {}
99105 self .regex_hooks = []
100106 self .sieves = []
107+ self .cap_hooks = {"on_available" : defaultdict (list ), "on_ack" : defaultdict (list )}
108+ self .connect_hooks = []
101109 self ._hook_waiting_queues = {}
102110
103111 @asyncio .coroutine
@@ -179,6 +187,16 @@ def load_plugin(self, path):
179187
180188 self .plugins [plugin .file_name ] = plugin
181189
190+ for on_cap_available_hook in plugin .on_cap_available :
191+ for cap in on_cap_available_hook .caps :
192+ self .cap_hooks ["on_available" ][cap .casefold ()].append (on_cap_available_hook )
193+ self ._log_hook (on_cap_available_hook )
194+
195+ for on_cap_ack_hook in plugin .on_cap_ack :
196+ for cap in on_cap_ack_hook .caps :
197+ self .cap_hooks ["on_ack" ][cap .casefold ()].append (on_cap_ack_hook )
198+ self ._log_hook (on_cap_ack_hook )
199+
182200 for periodic_hook in plugin .periodic :
183201 task = asyncio .async (self ._start_periodic (periodic_hook ))
184202 plugin .tasks .append (task )
@@ -227,8 +245,14 @@ def load_plugin(self, path):
227245 self .sieves .append (sieve_hook )
228246 self ._log_hook (sieve_hook )
229247
248+ # register connect hooks
249+ for connect_hook in plugin .connect_hooks :
250+ self .connect_hooks .append (connect_hook )
251+ self ._log_hook (connect_hook )
252+
230253 # sort sieve hooks by priority
231254 self .sieves .sort (key = lambda x : x .priority )
255+ self .connect_hooks .sort (key = attrgetter ("priority" ))
232256
233257 # we don't need this anymore
234258 del plugin .run_on_start
@@ -259,6 +283,22 @@ def unload_plugin(self, path):
259283 for task in plugin .tasks :
260284 task .cancel ()
261285
286+ for on_cap_available_hook in plugin .on_cap_available :
287+ available_hooks = self .cap_hooks ["on_available" ]
288+ for cap in on_cap_available_hook .caps :
289+ cap_cf = cap .casefold ()
290+ available_hooks [cap_cf ].remove (on_cap_available_hook )
291+ if not available_hooks [cap_cf ]:
292+ del available_hooks [cap_cf ]
293+
294+ for on_cap_ack in plugin .on_cap_ack :
295+ ack_hooks = self .cap_hooks ["on_ack" ]
296+ for cap in on_cap_ack .caps :
297+ cap_cf = cap .casefold ()
298+ ack_hooks [cap_cf ].remove (on_cap_ack )
299+ if not ack_hooks [cap_cf ]:
300+ del ack_hooks [cap_cf ]
301+
262302 # unregister commands
263303 for command_hook in plugin .commands :
264304 for alias in command_hook .aliases :
@@ -294,6 +334,10 @@ def unload_plugin(self, path):
294334 for sieve_hook in plugin .sieves :
295335 self .sieves .remove (sieve_hook )
296336
337+ # unregister connect hooks
338+ for connect_hook in plugin .connect_hooks :
339+ self .connect_hooks .remove (connect_hook )
340+
297341 # Run on_stop hooks
298342 for on_stop_hook in plugin .run_on_stop :
299343 event = Event (bot = self .bot , hook = on_stop_hook )
@@ -521,7 +565,12 @@ def __init__(self, filepath, filename, title, code):
521565 self .file_path = filepath
522566 self .file_name = filename
523567 self .title = title
524- self .commands , self .regexes , self .raw_hooks , self .sieves , self .events , self .periodic , self .run_on_start , self .run_on_stop = find_hooks (self , code )
568+ # TODO clean up hook lists
569+ hooks = find_hooks (self , code )
570+ self .commands , self .regexes , self .raw_hooks , * hooks = hooks
571+ self .sieves , self .events , self .periodic , * hooks = hooks
572+ self .run_on_start , self .run_on_stop , self .on_cap_ack , * hooks = hooks
573+ self .on_cap_available , self .connect_hooks , * hooks = hooks
525574 # we need to find tables for each plugin so that they can be unloaded from the global metadata when the
526575 # plugin is reloaded
527576 self .tables = find_tables (code )
@@ -776,6 +825,45 @@ def __str__(self):
776825 return "on_stop {} from {}" .format (self .function_name , self .plugin .file_name )
777826
778827
828+ class CapHook (Hook ):
829+ def __init__ (self , _type , plugin , base_hook ):
830+ self .caps = base_hook .caps
831+ super ().__init__ ("on_cap_{}" .format (_type ), plugin , base_hook )
832+
833+ def __repr__ (self ):
834+ return "{name}[{caps} {base!r}]" .format (name = self .type , caps = self .caps , base = super ())
835+
836+ def __str__ (self ):
837+ return "{name} {func} from {file}" .format (name = self .type , func = self .function_name , file = self .plugin .file_name )
838+
839+
840+ class OnCapAvaliableHook (CapHook ):
841+ def __init__ (self , plugin , base_hook ):
842+ super ().__init__ ("available" , plugin , base_hook )
843+
844+
845+ class OnCapAckHook (CapHook ):
846+ def __init__ (self , plugin , base_hook ):
847+ super ().__init__ ("ack" , plugin , base_hook )
848+
849+
850+ class OnConnectHook (Hook ):
851+ def __init__ (self , plugin , sieve_hook ):
852+ """
853+ :type plugin: Plugin
854+ :type sieve_hook: cloudbot.util.hook._Hook
855+ """
856+
857+ self .priority = sieve_hook .kwargs .pop ("priority" , 100 )
858+ super ().__init__ ("on_connect" , plugin , sieve_hook )
859+
860+ def __repr__ (self ):
861+ return "{name}[{base!r}]" .format (name = self .type , base = super ())
862+
863+ def __str__ (self ):
864+ return "{name} {func} from {file}" .format (name = self .type , func = self .function_name , file = self .plugin .file_name )
865+
866+
779867_hook_name_to_plugin = {
780868 "command" : CommandHook ,
781869 "regex" : RegexHook ,
@@ -785,4 +873,7 @@ def __str__(self):
785873 "periodic" : PeriodicHook ,
786874 "on_start" : OnStartHook ,
787875 "on_stop" : OnStopHook ,
876+ "on_cap_available" : OnCapAvaliableHook ,
877+ "on_cap_ack" : OnCapAckHook ,
878+ "on_connect" : OnConnectHook ,
788879}
0 commit comments