Skip to content

Commit 982dcf9

Browse files
committed
Expand chan parameter configs
1 parent 1ec40e1 commit 982dcf9

File tree

5 files changed

+324
-47
lines changed

5 files changed

+324
-47
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2525
- Fixed handling of colons in core IRC parser
2626
- Fix FML random URL
2727
- Update tvdb.py to v3 TVDB API
28+
- Fix channel parameter handling in IRC client
2829
### Removed
2930
- twitch.py removed due to outdated API and lack of maintainer
3031

cloudbot/clients/irc.py

Lines changed: 85 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -11,28 +11,30 @@
1111

1212
from irclib.parser import Message
1313

14-
from cloudbot.client import Client, client, ClientConnectError
14+
from cloudbot.client import Client, ClientConnectError, client
1515
from cloudbot.event import Event, EventType, IrcOutEvent
1616
from cloudbot.util import async_util
1717

1818
logger = logging.getLogger("cloudbot")
1919

20-
irc_nick_re = re.compile(r'[A-Za-z0-9^{\}\[\]\-`_|\\]+')
20+
irc_nick_re = re.compile(r"[A-Za-z0-9^{\}\[\]\-`_|\\]+")
2121

22-
irc_bad_chars = ''.join([chr(x) for x in list(range(0, 1)) + list(range(4, 32)) + list(range(127, 160))])
23-
irc_clean_re = re.compile('[{}]'.format(re.escape(irc_bad_chars)))
22+
irc_bad_chars = "".join(
23+
[chr(x) for x in list(range(0, 1)) + list(range(4, 32)) + list(range(127, 160))]
24+
)
25+
irc_clean_re = re.compile("[{}]".format(re.escape(irc_bad_chars)))
2426

2527

2628
def irc_clean(dirty):
27-
return irc_clean_re.sub('', dirty)
29+
return irc_clean_re.sub("", dirty)
2830

2931

3032
irc_command_to_event_type = {
3133
"PRIVMSG": EventType.message,
3234
"JOIN": EventType.join,
3335
"PART": EventType.part,
3436
"KICK": EventType.kick,
35-
"NOTICE": EventType.notice
37+
"NOTICE": EventType.notice,
3638
}
3739

3840
content_params = {
@@ -57,6 +59,14 @@ def irc_clean(dirty):
5759
"353": 2,
5860
"366": 1,
5961
"324": 1,
62+
"329": 1,
63+
"332": 1,
64+
"333": 1,
65+
# WHOIS
66+
"310": 1,
67+
"311": 1,
68+
"312": 1,
69+
"318": 1,
6070
}
6171

6272
target_params = {
@@ -69,13 +79,17 @@ def irc_clean(dirty):
6979
def decode(bytestring):
7080
"""
7181
Tries to decode a bytestring using multiple encoding formats
82+
83+
>>> decode(bytes([0x80, 0xbf, 0x81]) + '\u200b'.encode())
84+
'\u200b'
7285
"""
73-
for codec in ('utf-8', 'iso-8859-1', 'shift_jis', 'cp1252'):
86+
for codec in ("utf-8", "shift_jis", "cp1252"):
7487
try:
7588
return bytestring.decode(codec)
7689
except UnicodeDecodeError:
7790
continue
78-
return bytestring.decode('utf-8', errors='ignore')
91+
92+
return bytestring.decode("utf-8", errors="ignore")
7993

8094

8195
def _get_param(msg: Message, index_map: Mapping[str, int]) -> Optional[str]:
@@ -108,16 +122,16 @@ def __init__(self, bot, _type, name, nick, *, channels=None, config=None):
108122
super().__init__(bot, _type, name, nick, channels=channels, config=config)
109123

110124
self.target_nick = nick
111-
conn_config = config['connection']
112-
self.use_ssl = conn_config.get('ssl', False)
113-
self._ignore_cert_errors = conn_config.get('ignore_cert', False)
114-
self._timeout = conn_config.get('timeout', 300)
115-
self.server = conn_config['server']
116-
self.port = conn_config.get('port', 6667)
125+
conn_config = config["connection"]
126+
self.use_ssl = conn_config.get("ssl", False)
127+
self._ignore_cert_errors = conn_config.get("ignore_cert", False)
128+
self._timeout = conn_config.get("timeout", 300)
129+
self.server = conn_config["server"]
130+
self.port = conn_config.get("port", 6667)
117131

118132
local_bind = (
119-
conn_config.get('bind_addr', False),
120-
conn_config.get('bind_port', 0),
133+
conn_config.get("bind_addr", False),
134+
conn_config.get("bind_port", 0),
121135
)
122136
if local_bind[0] is False:
123137
local_bind = False
@@ -135,7 +149,7 @@ def __init__(self, bot, _type, name, nick, *, channels=None, config=None):
135149
def make_ssl_context(self, conn_config):
136150
if self.use_ssl:
137151
ssl_context = ssl.create_default_context()
138-
client_cert = conn_config.get('client_cert')
152+
client_cert = conn_config.get("client_cert")
139153
if client_cert:
140154
path = Path(client_cert)
141155
if path.exists():
@@ -177,12 +191,17 @@ async def try_connect(self):
177191
try:
178192
await self.connect(self._timeout)
179193
except (TimeoutError, asyncio.TimeoutError):
180-
logger.error("[%s] Timeout occurred while connecting to %s", self.name, self.describe_server())
194+
logger.error(
195+
"[%s] Timeout occurred while connecting to %s",
196+
self.name,
197+
self.describe_server(),
198+
)
181199
except (socket.error, socket.gaierror, OSError, ssl.SSLError):
182200
logger.error(
183201
"[%s] Error occurred while connecting to %s (%s)",
184-
self.name, self.describe_server(),
185-
traceback.format_exc().splitlines()[-1]
202+
self.name,
203+
self.describe_server(),
204+
traceback.format_exc().splitlines()[-1],
186205
)
187206
except Exception as e:
188207
raise ClientConnectError(self.name, self.describe_server()) from e
@@ -192,18 +211,18 @@ async def try_connect(self):
192211
sleep_time = random.randrange(self._timeout)
193212
canceller = asyncio.shield(self.cancelled_future)
194213
try:
195-
await asyncio.wait_for(
196-
canceller, timeout=sleep_time
197-
)
198-
except asyncio.CancelledError:
214+
await asyncio.wait_for(canceller, timeout=sleep_time)
215+
except (asyncio.CancelledError, asyncio.TimeoutError):
199216
pass
200217

201218
async def connect(self, timeout=None):
202219
"""
203220
Connects to the IRC server, or reconnects if already connected.
204221
"""
205222
if self._connecting:
206-
raise ValueError("Attempted to connect while another connect attempt is happening")
223+
raise ValueError(
224+
"Attempted to connect while another connect attempt is happening"
225+
)
207226

208227
self._connecting = True
209228
try:
@@ -226,7 +245,11 @@ async def _connect(self, timeout=None):
226245
optional_params["local_addr"] = self.local_bind
227246

228247
coro = self.loop.create_connection(
229-
partial(_IrcProtocol, self), host=self.server, port=self.port, ssl=self.ssl_context, **optional_params
248+
partial(_IrcProtocol, self),
249+
host=self.server,
250+
port=self.port,
251+
ssl=self.ssl_context,
252+
**optional_params,
230253
)
231254

232255
if timeout is not None:
@@ -235,7 +258,9 @@ async def _connect(self, timeout=None):
235258
self._transport, self._protocol = await coro
236259

237260
tasks = [
238-
self.bot.plugin_manager.launch(hook, Event(bot=self.bot, conn=self, hook=hook))
261+
self.bot.plugin_manager.launch(
262+
hook, Event(bot=self.bot, conn=self, hook=hook)
263+
)
239264
for hook in self.bot.plugin_manager.connect_hooks
240265
if not hook.clients or self.type in hook.clients
241266
]
@@ -421,9 +446,13 @@ async def send(self, line, log=True):
421446
bot=self.bot, hook=out_sieve, conn=self.conn, irc_raw=line
422447
)
423448

424-
ok, new_line = await self.bot.plugin_manager.internal_launch(out_sieve, event)
449+
ok, new_line = await self.bot.plugin_manager.internal_launch(
450+
out_sieve, event
451+
)
425452
if not ok:
426-
logger.warning("Error occurred in outgoing sieve, falling back to old behavior")
453+
logger.warning(
454+
"Error occurred in outgoing sieve, falling back to old behavior"
455+
)
427456
logger.debug("Line was: %s", line)
428457
filtered = False
429458
break
@@ -461,7 +490,9 @@ def data_received(self, data):
461490
except Exception:
462491
logger.exception(
463492
"[%s] Error occurred while parsing IRC line '%s' from %s",
464-
self.conn.name, line, self.conn.describe_server()
493+
self.conn.name,
494+
line,
495+
self.conn.describe_server(),
465496
)
466497
else:
467498
# handle the message, async
@@ -485,22 +516,20 @@ def parse_line(self, line: str) -> Event:
485516
content = None
486517

487518
# Event type
488-
event_type = irc_command_to_event_type.get(
489-
command, EventType.other
490-
)
519+
event_type = irc_command_to_event_type.get(command, EventType.other)
491520
target = _get_param(message, target_params)
492521

493522
# Parse for CTCP
494523
if event_type is EventType.message and content_raw.startswith("\x01"):
495524
possible_ctcp = content_raw[1:]
496-
if content_raw.endswith('\x01'):
525+
if content_raw.endswith("\x01"):
497526
possible_ctcp = possible_ctcp[:-1]
498527

499-
if '\x01' in possible_ctcp:
528+
if "\x01" in possible_ctcp:
500529
logger.debug(
501530
"[%s] Invalid CTCP message received, "
502531
"treating it as a mornal message",
503-
self.conn.name
532+
self.conn.name,
504533
)
505534
ctcp_text = None
506535
else:
@@ -518,6 +547,7 @@ def parse_line(self, line: str) -> Event:
518547

519548
# Channel
520549
channel = _get_param(message, chan_params)
550+
521551
prefix = message.prefix
522552
if prefix is None:
523553
nick = None
@@ -539,13 +569,29 @@ def parse_line(self, line: str) -> Event:
539569
# Channel for a PM is the sending user
540570
if channel == self.conn.nick.lower():
541571
channel = nick.lower()
572+
else:
573+
# If the channel isn't set, it's the sending user/server
574+
channel = nick.lower() if nick else nick
542575

543576
# Set up parsed message
544577
# TODO: Do we really want to send the raw `prefix` and `command_params` here?
545578
event = Event(
546-
bot=self.bot, conn=self.conn, event_type=event_type, content_raw=content_raw, content=content,
547-
target=target, channel=channel, nick=nick, user=user, host=host, mask=mask, irc_raw=line,
548-
irc_prefix=mask, irc_command=command, irc_paramlist=command_params, irc_ctcp_text=ctcp_text
579+
bot=self.bot,
580+
conn=self.conn,
581+
event_type=event_type,
582+
content_raw=content_raw,
583+
content=content,
584+
target=target,
585+
channel=channel,
586+
nick=nick,
587+
user=user,
588+
host=host,
589+
mask=mask,
590+
irc_raw=line,
591+
irc_prefix=mask,
592+
irc_command=command,
593+
irc_paramlist=command_params,
594+
irc_ctcp_text=ctcp_text,
549595
)
550596
return event
551597

plugins/core/core_sieve.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,16 @@ def check_acls(bot: CloudBot, event: Event, _hook: Hook) -> Optional[Event]:
2626
"""
2727
Handle config ACLs
2828
"""
29+
if event.chan is None:
30+
return event
31+
2932
conn = event.conn
3033

3134
# check acls
3235
acl = conn.config.get("acls", {}).get(_hook.function_name, {})
3336
allowlist = acl.get("deny-except")
3437
denylist = acl.get("allow-except")
38+
3539
chan = event.chan.lower()
3640
if allowlist is not None:
3741
allowed_channels = list(map(str.lower, allowlist))

0 commit comments

Comments
 (0)