Skip to content

Commit 6b3822e

Browse files
authored
Merge pull request CloudBotIRC#231 from linuxdaemon/gonzobot+cap-hook-cleanup
CAP hook cleanup
2 parents 5175e2c + 27a21c3 commit 6b3822e

4 files changed

Lines changed: 128 additions & 60 deletions

File tree

cloudbot/clients/irc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ def cmd(self, command, *params):
202202
:type command: str
203203
:type params: (str)
204204
"""
205-
params = list(params) # turn the tuple of parameters into a list
205+
params = list(map(str, params)) # turn the tuple of parameters into a list
206206
if params:
207207
params[-1] = ':' + params[-1]
208208
self.send("{} {}".format(command, ' '.join(params)))

cloudbot/util/parsers/irc.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,11 @@ def __eq__(self, other):
7070

7171
return NotImplemented
7272

73+
def __hash__(self):
74+
return hash((self.name, self.value))
75+
7376
@staticmethod
74-
def parse(text: str):
77+
def parse(text):
7578
"""Parse a CAP entity from a string"""
7679
name, _, value = text.partition(CAP_VALUE_SEP)
7780
return Cap(name, value)
@@ -81,7 +84,7 @@ class CapList(Parseable, list):
8184
"""Represents a list of CAP entities"""
8285

8386
def __str__(self):
84-
return CAP_SEP.join(self)
87+
return CAP_SEP.join(map(str, self))
8588

8689
@staticmethod
8790
def parse(text):

plugins/core/cap.py

Lines changed: 117 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,36 @@
11
import asyncio
2-
import logging
2+
import inspect
33
from functools import partial
44

55
from cloudbot import hook
66
from 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)
1312
def 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
1819
def 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("[%s|cap] Available capabilities: %s", conn.name, 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("[%s|cap] Enabled Capabilities: %s", conn.name, caplist)
120+
121+
122+
@asyncio.coroutine
123+
@_subcmd_handler("NEW")
124+
def cap_new(logger, caplist, conn, event, bot, irc_paramlist):
125+
logger.info("[%s|cap] New capabilities advertised: %s", conn.name, 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("[%s|cap] Capabilities removed by server: %s", conn.name, 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)

plugins/sasl.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,20 @@
11
import asyncio
22
import base64
3-
import logging
43

54
from cloudbot import hook
65

7-
logger = logging.getLogger("cloudbot")
8-
96

107
@hook.on_cap_available("sasl")
11-
def sasl_available():
12-
pass
8+
def sasl_available(conn):
9+
sasl_conf = conn.config.get('sasl')
10+
return bool(sasl_conf and sasl_conf.get('enabled', True))
1311

1412

1513
@asyncio.coroutine
1614
@hook.on_cap_ack("sasl")
17-
def sasl_ack(conn):
15+
def sasl_ack(conn, logger):
1816
sasl_auth = conn.config.get('sasl')
19-
if sasl_auth:
17+
if sasl_auth and sasl_auth.get('enabled', True):
2018
sasl_mech = sasl_auth.get("mechanism", "PLAIN").upper()
2119
auth_fut = conn.loop.create_future()
2220
conn.memory["sasl_auth_future"] = auth_fut

0 commit comments

Comments
 (0)