diff --git a/IRCBuffer.py b/IRCBuffer.py index 6e689d30..619f9061 100644 --- a/IRCBuffer.py +++ b/IRCBuffer.py @@ -1,4 +1,5 @@ import re +import Utils class BufferLine(object): def __init__(self, sender, message, action, from_self): @@ -28,7 +29,7 @@ class Buffer(object): def find(self, pattern, **kwargs): from_self = kwargs.get("from_self", True) for_user = kwargs.get("for_user", "") - for_user = for_user.lower() if for_user else None + for_user = Utils.irc_lower(for_user) if for_user else None not_pattern = kwargs.get("not_pattern", None) for line in self.lines: if line.from_self and not from_self: @@ -36,7 +37,7 @@ class Buffer(object): elif re.search(pattern, line.message): if not_pattern and re.search(not_pattern, line.message): continue - if for_user and not line.sender.lower() == for_user: + if for_user and not Utils.irc_lower(line.sender) == for_user: continue return line def skip_next(self): diff --git a/IRCChannel.py b/IRCChannel.py index 0dfd6884..00407772 100644 --- a/IRCChannel.py +++ b/IRCChannel.py @@ -1,9 +1,9 @@ import uuid -import IRCBuffer +import IRCBuffer, Utils class Channel(object): def __init__(self, name, id, server, bot): - self.name = name.lower() + self.name = Utils.irc_lower(name) self.id = id self.server = server self.bot = bot diff --git a/IRCLineHandler.py b/IRCLineHandler.py index 4a19082c..d7d47f54 100644 --- a/IRCLineHandler.py +++ b/IRCLineHandler.py @@ -5,6 +5,7 @@ RE_PREFIXES = re.compile(r"\bPREFIX=\((\w+)\)(\W+)(?:\b|$)") RE_CHANMODES = re.compile( r"\bCHANMODES=(\w*),(\w*),(\w*),(\w*)(?:\b|$)") RE_CHANTYPES = re.compile(r"\bCHANTYPES=(\W+)(?:\b|$)") +RE_CASEMAPPING = re.compile(r"\bCASEMAPPING=(\S+)") RE_MODES = re.compile(r"[-+]\w+") CAPABILITIES = {"multi-prefix", "chghost", "invite-notify", "account-tag", @@ -136,6 +137,11 @@ class LineHandler(object): match = re.search(RE_CHANTYPES, isupport_line) if match: event["server"].channel_types = list(match.group(1)) + + match = re.search(RE_CASEMAPPING, isupport_line) + if match: + event["server"].case_mapping = match.group(1) + self.events.on("received.numeric.005").call( isupport=isupport_line, server=event["server"]) @@ -554,10 +560,11 @@ class LineHandler(object): # we need a registered nickname for this channel def handle_477(self, event): - if event["args"][1].lower() in event["server"].attempted_join: + channel_name = Utils.irc_lower(event["args"][1]) + if channel_name in event["server"].attempted_join: self.bot.add_timer("rejoin", 5, channel_name=event["args"][1], - key=event["server"].attempted_join[event["args"][1].lower()], + key=event["server"].attempted_join[channel_name], server_id=event["server"].id) # someone's been kicked from a channel diff --git a/IRCServer.py b/IRCServer.py index a1e3a489..8b674711 100644 --- a/IRCServer.py +++ b/IRCServer.py @@ -47,6 +47,7 @@ class Server(object): {"@": "o", "+": "v"}) self.channel_modes = [] self.channel_types = [] + self.case_mapping = "rfc1459" self.last_read = time.monotonic() self.last_send = None @@ -130,9 +131,9 @@ class Server(object): def set_own_nickname(self, nickname): self.nickname = nickname - self.nickname_lower = nickname.lower() + self.nickname_lower = Utils.irc_lower(nickname) def is_own_nickname(self, nickname): - return nickname.lower() == self.nickname_lower + return Utils.irc_equals(nickname, self.nickname) def add_own_mode(self, mode, arg=None): self.own_modes[mode] = arg @@ -145,7 +146,7 @@ class Server(object): self.add_own_mode(mode, arg) def has_user(self, nickname): - return nickname.lower() in self.users + return Utils.irc_lower(nickname) in self.users def get_user(self, nickname): if not self.has_user(nickname): user_id = self.get_user_id(nickname) @@ -154,7 +155,7 @@ class Server(object): user=new_user, server=self) self.users[new_user.nickname_lower] = new_user self.new_users.add(new_user) - return self.users[nickname.lower()] + return self.users[Utils.irc_lower(nickname)] def get_user_id(self, nickname): self.bot.database.users.add(self.id, nickname) return self.bot.database.users.get_id(self.id, nickname) @@ -164,12 +165,12 @@ class Server(object): channel.remove_user(user) def change_user_nickname(self, old_nickname, new_nickname): - user = self.users.pop(old_nickname.lower()) + user = self.users.pop(Utils.irc_lower(old_nickname)) user._id = self.get_user_id(new_nickname) - self.users[new_nickname.lower()] = user + self.users[Utils.irc_lower(new_nickname)] = user def has_channel(self, channel_name): - return channel_name[0] in self.channel_types and channel_name.lower( - ) in self.channels + return channel_name[0] in self.channel_types and Utils.irc_lower( + channel_name) in self.channels def get_channel(self, channel_name): if not self.has_channel(channel_name): channel_id = self.get_channel_id(channel_name) @@ -178,7 +179,7 @@ class Server(object): self.events.on("new").on("channel").call( channel=new_channel, server=self) self.channels[new_channel.name] = new_channel - return self.channels[channel_name.lower()] + return self.channels[Utils.irc_lower(channel_name)] def get_channel_id(self, channel_name): self.bot.database.channels.add(self.id, channel_name) return self.bot.database.channels.get_id(self.id, channel_name) diff --git a/IRCUser.py b/IRCUser.py index 1bb7ad13..d336ecf1 100644 --- a/IRCUser.py +++ b/IRCUser.py @@ -1,5 +1,5 @@ import uuid -import IRCBuffer +import IRCBuffer, Utils class User(object): def __init__(self, nickname, id, server, bot): @@ -31,7 +31,7 @@ class User(object): def set_nickname(self, nickname): self.nickname = nickname - self.nickname_lower = nickname.lower() + self.nickname_lower = Utils.irc_lower(nickname) self.name = self.nickname_lower def join_channel(self, channel): self.channels.add(channel) diff --git a/Utils.py b/Utils.py index 0aa407d4..ac39825c 100644 --- a/Utils.py +++ b/Utils.py @@ -1,10 +1,14 @@ import json, re, traceback, urllib.request, urllib.parse, urllib.error, ssl +import string import bs4 USER_AGENT = ("Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 " "(KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36") REGEX_HTTP = re.compile("https?://", re.I) +RFC1459_UPPER = r'\[]~' +RFC1459_UPPER = r'|{}^' + def remove_colon(s): if s.startswith(":"): s = s[1:] @@ -13,6 +17,21 @@ def remove_colon(s): def arbitrary(s, n): return remove_colon(" ".join(s[n:])) +def _rfc1459_lower(s): + for upper, lower in zip(RFC1459_UPPER, RFC1459_LOWER): + s = s.replace(upper, lower) + return s.lower() +def irc_lower(server, s): + if server.case_mapping == "ascii": + return s.lower() + elif server.case_mapping == "rfc1459": + return _rfc1459_lower(s) + else: + raise ValueError("unknown casemapping '%s'" % server.case_mapping) + +def irc_equals(server, s1, s2): + return irc_lower(server, s1) == irc_lower(server, s2) + class IRCHostmask(object): def __init__(self, nickname, username, hostname, hostmask): self.nickname = nickname