11import asyncio
2- import logging
2+ import inspect
33from functools import partial
44
55from cloudbot import hook
66from cloudbot .event import CapEvent
7+ from cloudbot .util import async_util
8+ from cloudbot .util .parsers .irc import CapList
79
8- logger = logging .getLogger ("cloudbot" )
910
10-
11- @asyncio .coroutine
1211@hook .connect (priority = - 10 )
1312def send_cap_ls (conn ):
1413 conn .cmd ("CAP" , "LS" , "302" )
14+ conn .memory .setdefault ("available_caps" , set ()).clear ()
15+ conn .memory .setdefault ("cap_queue" , {}).clear ()
1516
1617
1718@asyncio .coroutine
1819def handle_available_caps (conn , caplist , event , irc_paramlist , bot ):
19- available_caps = conn .memory .setdefault ("available_caps" , set ())
20- caps = [tuple (cap .split ('=' , 1 )) for cap in caplist ]
21- available_caps .update (caps )
22- cap_queue = conn .memory .setdefault ("cap_queue" , {})
23- for cap , * param in caps :
24- cap_event = partial (CapEvent , base_event = event , cap = cap , cap_param = param [0 ] if param else None )
20+ available_caps = conn .memory ["available_caps" ]
21+ available_caps .update (caplist )
22+ cap_queue = conn .memory ["cap_queue" ]
23+ for cap in caplist :
24+ name = cap .name
25+ name_cf = name .casefold ()
26+ cap_event = partial (CapEvent , base_event = event , cap = name , cap_param = cap .value )
2527 tasks = [
26- bot .plugin_manager .launch (_hook , cap_event (hook = _hook ))
27- for _hook in bot .plugin_manager .cap_hooks ["on_available" ][cap . casefold () ]
28+ bot .plugin_manager .internal_launch (_hook , cap_event (hook = _hook ))
29+ for _hook in bot .plugin_manager .cap_hooks ["on_available" ][name_cf ]
2830 ]
2931 results = yield from asyncio .gather (* tasks )
30- if any (results ):
31- cap_queue [cap . casefold () ] = conn .loop .create_future ()
32+ if any (ok and ( res or res is None ) for ok , res in results ):
33+ cap_queue [name_cf ] = conn .loop .create_future ()
3234 conn .cmd ("CAP" , "REQ" , cap )
3335
3436 if irc_paramlist [2 ] != '+' :
@@ -37,46 +39,111 @@ def handle_available_caps(conn, caplist, event, irc_paramlist, bot):
3739 conn .send ("CAP END" )
3840
3941
42+ HANDLERS = {}
43+
44+
45+ def _subcmd_handler (* types ):
46+ def _decorate (func ):
47+ for subcmd in types :
48+ HANDLERS [subcmd .upper ()] = func
49+
50+ return func
51+
52+ return lambda func : _decorate (func )
53+
54+
55+ @asyncio .coroutine
56+ def _launch_handler (subcmd , event , ** kwargs ):
57+ subcmd = subcmd .upper ()
58+ kwargs ["subcmd" ] = subcmd
59+ handler = HANDLERS .get (subcmd )
60+ if handler :
61+ sig = inspect .signature (handler )
62+ args = []
63+
64+ for arg in sig .parameters .keys ():
65+ if arg in kwargs :
66+ args .append (kwargs [arg ])
67+ else :
68+ try :
69+ value = getattr (event , arg )
70+ except AttributeError :
71+ event .logger .warning ("CAP subcommand handler requested unknown argument: %s" , arg )
72+ return
73+ else :
74+ args .append (value )
75+
76+ yield from async_util .run_func (event .loop , handler , * args )
77+
78+
79+ @asyncio .coroutine
80+ @_subcmd_handler ("LS" )
81+ def cap_ls (conn , caplist , event , irc_paramlist , bot , logger ):
82+ logger .info ("Available capabilities: %s" , caplist )
83+ yield from handle_available_caps (conn , caplist , event , irc_paramlist , bot )
84+
85+
86+ @asyncio .coroutine
87+ def handle_req_resp (enabled , conn , caplist , event , bot ):
88+ server_caps = conn .memory .setdefault ('server_caps' , {})
89+ cap_queue = conn .memory .get ("cap_queue" , {})
90+ caps = (cap .name .casefold () for cap in caplist )
91+ for cap in caps :
92+ server_caps [cap ] = enabled
93+ if enabled :
94+ cap_event = partial (CapEvent , base_event = event , cap = cap )
95+ tasks = [
96+ bot .plugin_manager .launch (_hook , cap_event (hook = _hook ))
97+ for _hook in bot .plugin_manager .cap_hooks ["on_ack" ][cap ]
98+ ]
99+ yield from asyncio .gather (* tasks )
100+
101+ if cap in cap_queue :
102+ cap_queue [cap ].set_result (enabled )
103+
104+
105+ @asyncio .coroutine
106+ @_subcmd_handler ("ACK" )
107+ def cap_ack_nak (conn , caplist , event , bot ):
108+ yield from handle_req_resp (True , conn , caplist , event , bot )
109+
110+
111+ @asyncio .coroutine
112+ @_subcmd_handler ("NAK" )
113+ def cap_nak (conn , caplist , event , bot ):
114+ yield from handle_req_resp (False , conn , caplist , event , bot )
115+
116+
117+ @_subcmd_handler ("LIST" )
118+ def cap_list (logger , caplist ):
119+ logger .info ("Enabled Capabilities: %s" , caplist )
120+
121+
122+ @asyncio .coroutine
123+ @_subcmd_handler ("NEW" )
124+ def cap_new (logger , caplist , conn , event , bot , irc_paramlist ):
125+ logger .info ("New capabilities advertised: %s" , caplist )
126+ yield from handle_available_caps (conn , caplist , event , irc_paramlist , bot )
127+
128+
129+ @_subcmd_handler ("DEL" )
130+ def cap_del (logger , conn , caplist ):
131+ # TODO add hooks for CAP removal
132+ logger .info ("Capabilities removed by server: %s" , caplist )
133+ server_caps = conn .memory .setdefault ('server_caps' , {})
134+ for cap in caplist :
135+ server_caps [cap .name .casefold ()] = False
136+
137+
40138@asyncio .coroutine
41139@hook .irc_raw ("CAP" )
42- def on_cap (irc_paramlist , conn , bot , event ):
43- caplist = []
140+ def on_cap (irc_paramlist , event ):
141+ args = {}
44142 if len (irc_paramlist ) > 2 :
45143 capstr = irc_paramlist [- 1 ].strip ()
46144 if capstr [0 ] == ':' :
47145 capstr = capstr [1 :]
48146
49- caplist = capstr .split ()
50- subcmd = irc_paramlist [1 ].upper ()
51- if subcmd == "LS" :
52- yield from handle_available_caps (conn , caplist , event , irc_paramlist , bot )
53-
54- elif subcmd in ('ACK' , 'NAK' ):
55- enabled = subcmd == 'ACK'
56- server_caps = conn .memory .setdefault ('server_caps' , {})
57- cap_queue = conn .memory .get ("cap_queue" , {})
58- caps = [cap .casefold () for cap in caplist ]
59- for cap in caps :
60- server_caps [cap ] = enabled
61- if enabled :
62- cap_event = partial (CapEvent , base_event = event , cap = cap )
63- tasks = [
64- bot .plugin_manager .launch (_hook , cap_event (hook = _hook ))
65- for _hook in bot .plugin_manager .cap_hooks ["on_ack" ][cap ]
66- ]
67- yield from asyncio .gather (* tasks )
68-
69- if cap in cap_queue :
70- cap_queue [cap ].set_result (enabled )
71-
72- elif subcmd == 'LIST' :
73- logger .info ("Enabled Capabilities: %s" , irc_paramlist [- 1 ])
74- elif subcmd == 'NEW' :
75- logger .info ("New capabilities advertised: %s" , irc_paramlist [- 1 ])
76- yield from handle_available_caps (conn , caplist , event , irc_paramlist , bot )
77- elif subcmd == 'DEL' :
78- # TODO add hooks for CAP removal
79- logger .info ("Capabilities removed by server: %s" , irc_paramlist [- 1 ])
80- server_caps = conn .memory .setdefault ('server_caps' , {})
81- for cap in caplist :
82- server_caps [cap ] = False
147+ args ["caplist" ] = CapList .parse (capstr )
148+
149+ yield from _launch_handler (irc_paramlist [1 ], event , ** args )
0 commit comments