55import logging
66import os
77import re
8+ from collections import defaultdict
89
910import sqlalchemy
1011
@@ -18,7 +19,7 @@ def find_hooks(parent, module):
1819 """
1920 :type parent: Plugin
2021 :type module: object
21- :rtype: (list[CommandHook], list[RegexHook], list[RawHook], list[SieveHook], List[EventHook], List[PeriodicHook], list[OnStartHook], List[OnStopHook])
22+ :rtype: (list[CommandHook], list[RegexHook], list[RawHook], list[SieveHook], List[EventHook], List[PeriodicHook], list[OnStartHook], List[OnStopHook], list[OnCapAckHook], list[OnCapAvailableHook] )
2223 """
2324 # set the loaded flag
2425 module ._cloudbot_loaded = True
@@ -30,8 +31,11 @@ def find_hooks(parent, module):
3031 periodic = []
3132 on_start = []
3233 on_stop = []
34+ on_cap_ack = []
35+ on_cap_available = []
3336 type_lists = {"command" : command , "regex" : regex , "irc_raw" : raw , "sieve" : sieve , "event" : event ,
34- "periodic" : periodic , "on_start" : on_start , "on_stop" : on_stop }
37+ "periodic" : periodic , "on_start" : on_start , "on_stop" : on_stop , "on_cap_ack" : on_cap_ack ,
38+ "on_cap_available" : on_cap_available }
3539 for name , func in module .__dict__ .items ():
3640 if hasattr (func , "_cloudbot_hook" ):
3741 # if it has cloudbot hook
@@ -43,7 +47,7 @@ def find_hooks(parent, module):
4347 # delete the hook to free memory
4448 del func ._cloudbot_hook
4549
46- return command , regex , raw , sieve , event , periodic , on_start , on_stop
50+ return command , regex , raw , sieve , event , periodic , on_start , on_stop , on_cap_ack , on_cap_available
4751
4852
4953def find_tables (code ):
@@ -98,6 +102,7 @@ def __init__(self, bot):
98102 self .event_type_hooks = {}
99103 self .regex_hooks = []
100104 self .sieves = []
105+ self .cap_hooks = {"on_available" : defaultdict (list ), "on_ack" : defaultdict (list )}
101106 self ._hook_waiting_queues = {}
102107
103108 @asyncio .coroutine
@@ -179,6 +184,16 @@ def load_plugin(self, path):
179184
180185 self .plugins [plugin .file_name ] = plugin
181186
187+ for on_cap_available_hook in plugin .on_cap_available :
188+ for cap in on_cap_available_hook .caps :
189+ self .cap_hooks ["on_available" ][cap .casefold ()].append (on_cap_available_hook )
190+ self ._log_hook (on_cap_available_hook )
191+
192+ for on_cap_ack_hook in plugin .on_cap_ack :
193+ for cap in on_cap_ack_hook .caps :
194+ self .cap_hooks ["on_ack" ][cap .casefold ()].append (on_cap_ack_hook )
195+ self ._log_hook (on_cap_ack_hook )
196+
182197 for periodic_hook in plugin .periodic :
183198 task = asyncio .async (self ._start_periodic (periodic_hook ))
184199 plugin .tasks .append (task )
@@ -259,6 +274,22 @@ def unload_plugin(self, path):
259274 for task in plugin .tasks :
260275 task .cancel ()
261276
277+ for on_cap_available_hook in plugin .on_cap_available :
278+ available_hooks = self .cap_hooks ["on_available" ]
279+ for cap in on_cap_available_hook .caps :
280+ cap_cf = cap .casefold ()
281+ available_hooks [cap_cf ].remove (on_cap_available_hook )
282+ if not available_hooks [cap_cf ]:
283+ del available_hooks [cap_cf ]
284+
285+ for on_cap_ack in plugin .on_cap_ack :
286+ ack_hooks = self .cap_hooks ["on_ack" ]
287+ for cap in on_cap_ack .caps :
288+ cap_cf = cap .casefold ()
289+ ack_hooks [cap_cf ].remove (on_cap_ack )
290+ if not ack_hooks [cap_cf ]:
291+ del ack_hooks [cap_cf ]
292+
262293 # unregister commands
263294 for command_hook in plugin .commands :
264295 for alias in command_hook .aliases :
@@ -521,7 +552,8 @@ def __init__(self, filepath, filename, title, code):
521552 self .file_path = filepath
522553 self .file_name = filename
523554 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 )
555+ self .commands , self .regexes , self .raw_hooks , self .sieves , self .events , self .periodic , self .run_on_start , self .run_on_stop , self .on_cap_ack , self .on_cap_available = find_hooks (
556+ self , code )
525557 # we need to find tables for each plugin so that they can be unloaded from the global metadata when the
526558 # plugin is reloaded
527559 self .tables = find_tables (code )
@@ -776,6 +808,28 @@ def __str__(self):
776808 return "on_stop {} from {}" .format (self .function_name , self .plugin .file_name )
777809
778810
811+ class CapHook (Hook ):
812+ def __init__ (self , _type , plugin , base_hook ):
813+ self .caps = base_hook .caps
814+ super ().__init__ ("on_cap_{}" .format (_type ), plugin , base_hook )
815+
816+ def __repr__ (self ):
817+ return "{name}[{caps} {base!r}]" .format (name = self .type , caps = self .caps , base = super ())
818+
819+ def __str__ (self ):
820+ return "{name} {func} from {file}" .format (name = self .type , func = self .function_name , file = self .plugin .file_name )
821+
822+
823+ class OnCapAvaliableHook (CapHook ):
824+ def __init__ (self , plugin , base_hook ):
825+ super ().__init__ ("available" , plugin , base_hook )
826+
827+
828+ class OnCapAckHook (CapHook ):
829+ def __init__ (self , plugin , base_hook ):
830+ super ().__init__ ("ack" , plugin , base_hook )
831+
832+
779833_hook_name_to_plugin = {
780834 "command" : CommandHook ,
781835 "regex" : RegexHook ,
@@ -785,4 +839,6 @@ def __str__(self):
785839 "periodic" : PeriodicHook ,
786840 "on_start" : OnStartHook ,
787841 "on_stop" : OnStopHook ,
842+ "on_cap_available" : OnCapAvaliableHook ,
843+ "on_cap_ack" : OnCapAckHook ,
788844}
0 commit comments