aboutsummaryrefslogtreecommitdiff
path: root/src/core_modules/line_handler
diff options
context:
space:
mode:
Diffstat (limited to 'src/core_modules/line_handler')
-rw-r--r--src/core_modules/line_handler/__init__.py260
-rw-r--r--src/core_modules/line_handler/channel.py160
-rw-r--r--src/core_modules/line_handler/core.py154
-rw-r--r--src/core_modules/line_handler/ircv3.py138
-rw-r--r--src/core_modules/line_handler/message.py109
-rw-r--r--src/core_modules/line_handler/user.py102
6 files changed, 923 insertions, 0 deletions
diff --git a/src/core_modules/line_handler/__init__.py b/src/core_modules/line_handler/__init__.py
new file mode 100644
index 00000000..ddea6fdc
--- /dev/null
+++ b/src/core_modules/line_handler/__init__.py
@@ -0,0 +1,260 @@
+import enum
+from src import EventManager, IRCLine, ModuleManager, utils
+from . import channel, core, ircv3, message, user
+
+class Module(ModuleManager.BaseModule):
+ def _handle(self, server, line):
+ hooks = self.events.on("raw.received").on(line.command).get_hooks()
+ default_events = []
+ for hook in hooks:
+ default_events.append(hook.get_kwarg("default_event", False))
+
+ kwargs = {"server": server, "line": line,
+ "direction": utils.Direction.Recv}
+
+ self.events.on("raw.received").on(line.command).call_unsafe(**kwargs)
+ if any(default_events) or not hooks:
+ self.events.on("received").on(line.command).call(**kwargs)
+
+ @utils.hook("raw.received")
+ def handle_raw(self, event):
+ if ("batch" in event["line"].tags and
+ event["line"].tags["batch"] in event["server"].batches):
+ event["server"].batches[event["line"].tags["batch"]].add_line(
+ event["line"])
+ else:
+ self._handle(event["server"], event["line"])
+
+ @utils.hook("raw.send")
+ def handle_send(self, event):
+ self.events.on("raw.send").on(event["line"].command).call_unsafe(
+ server=event["server"], direction=utils.Direction.Send,
+ line=event["line"])
+
+ # ping from the server
+ @utils.hook("raw.received.ping")
+ def ping(self, event):
+ core.ping(event)
+
+ @utils.hook("raw.received.error")
+ def error(self, event):
+ self.log.error("ERROR received from %s: %s",
+ [str(event["server"]), event["line"].args[0]])
+ @utils.hook("raw.received.fail")
+ def fail(self, event):
+ command = event["line"].args[0]
+ error_code = event["line"].args[1]
+ context = event["line"].args[2:-1]
+ description = event["line"].args[-1]
+
+ self.log.warn("FAIL (%s %s) received on %s: %s",
+ [command, error_code, str(event["server"]), description])
+ self.events.on("received.fail").on(command).call(error_code=error_code,
+ context=context, description=description, server=event["server"])
+
+ # first numeric line the server sends
+ @utils.hook("raw.received.001", default_event=True)
+ def handle_001(self, event):
+ core.handle_001(event)
+
+ # server telling us what it supports
+ @utils.hook("raw.received.005")
+ def handle_005(self, event):
+ core.handle_005(self.events, event)
+
+ # RPL_MYINFO
+ @utils.hook("raw.received.004")
+ def handle_004(self, event):
+ core.handle_004(event)
+
+ # whois respose (nickname, username, realname, hostname)
+ @utils.hook("raw.received.311", default_event=True)
+ def handle_311(self, event):
+ user.handle_311(event)
+
+ # on-join channel topic line
+ @utils.hook("raw.received.332")
+ def handle_332(self, event):
+ channel.handle_332(self.events, event)
+
+ # channel topic changed
+ @utils.hook("raw.received.topic")
+ def topic(self, event):
+ channel.topic(self.events, event)
+
+ # on-join channel topic set by/at
+ @utils.hook("raw.received.333")
+ def handle_333(self, event):
+ channel.handle_333(self.events, event)
+
+ # /names response, also on-join user list
+ @utils.hook("raw.received.353", default_event=True)
+ def handle_353(self, event):
+ channel.handle_353(event)
+
+ # on-join user list has finished
+ @utils.hook("raw.received.366", default_event=True)
+ def handle_366(self, event):
+ channel.handle_366(event)
+
+ @utils.hook("raw.received.375", priority=EventManager.PRIORITY_HIGH)
+ def motd_start(self, event):
+ core.motd_start(event)
+
+ @utils.hook("raw.received.372")
+ @utils.hook("raw.received.375")
+ def motd_line(self, event):
+ core.motd_line(event)
+
+ # on user joining channel
+ @utils.hook("raw.received.join")
+ def join(self, event):
+ channel.join(self.events, event)
+
+ # on user parting channel
+ @utils.hook("raw.received.part")
+ def part(self, event):
+ channel.part(self.events, event)
+
+ # unknown command sent by us, oops!
+ @utils.hook("raw.received.421", default_event=True)
+ def handle_421(self, event):
+ self.bot.log.warn("We sent an unknown command to %s: %s",
+ [str(event["server"]), event["line"].args[1]])
+
+ # a user has disconnected!
+ @utils.hook("raw.received.quit")
+ @utils.hook("raw.send.quit")
+ def quit(self, event):
+ user.quit(self.events, event)
+
+ # the server is telling us about its capabilities!
+ @utils.hook("raw.received.cap")
+ def cap(self, event):
+ ircv3.cap(self.exports, self.events, event)
+
+ # the server is asking for authentication
+ @utils.hook("raw.received.authenticate")
+ def authenticate(self, event):
+ ircv3.authenticate(self.events, event)
+
+ # someone has changed their nickname
+ @utils.hook("raw.received.nick")
+ def nick(self, event):
+ user.nick(self.events, event)
+
+ # something's mode has changed
+ @utils.hook("raw.received.mode")
+ def mode(self, event):
+ core.mode(self.events, event)
+ # server telling us our own modes
+ @utils.hook("raw.received.221")
+ def umodeis(self, event):
+ core.handle_221(event)
+
+ # someone (maybe me!) has been invited somewhere
+ @utils.hook("raw.received.invite")
+ def invite(self, event):
+ core.invite(self.events, event)
+
+ # we've received/sent a PRIVMSG, NOTICE or TAGMSG
+ @utils.hook("raw.received.privmsg")
+ @utils.hook("raw.received.notice")
+ @utils.hook("raw.received.tagmsg")
+ def message(self, event):
+ message.message(self.events, event)
+
+ # IRCv3 AWAY, used to notify us that a client we can see has changed /away
+ @utils.hook("raw.received.away")
+ def away(self, event):
+ user.away(self.events, event)
+
+ @utils.hook("raw.received.batch")
+ def batch(self, event):
+ identifier = event["line"].args[0]
+ modifier, identifier = identifier[0], identifier[1:]
+
+ if modifier == "+":
+ batch_type = event["line"].args[1]
+ args = event["line"].args[2:]
+
+ batch = IRCLine.IRCBatch(identifier, batch_type, args,
+ event["line"].tags, source=event["line"].source)
+ event["server"].batches[identifier] = batch
+
+ self.events.on("received.batch.start").call(batch=batch,
+ server=event["server"])
+ else:
+ batch = event["server"].batches[identifier]
+ del event["server"].batches[identifier]
+
+ lines = batch.get_lines()
+
+ results = self.events.on("received.batch.end").call(batch=batch,
+ server=event["server"])
+
+ for result in results:
+ if not result == None:
+ lines = result
+ break
+
+ for line in lines:
+ self._handle(event["server"], line)
+
+ # IRCv3 CHGHOST, a user's username and/or hostname has changed
+ @utils.hook("raw.received.chghost")
+ def chghost(self, event):
+ user.chghost(self.events, event)
+
+ # IRCv3 SETNAME, to change a user's realname
+ @utils.hook("raw.received.setname")
+ def setname(self, event):
+ user.setname(event)
+
+ @utils.hook("raw.received.account")
+ def account(self, event):
+ user.account(self.events, event)
+
+ # response to a WHO command for user information
+ @utils.hook("raw.received.352", default_event=True)
+ def handle_352(self, event):
+ core.handle_352(self.events, event)
+
+ # response to a WHOX command for user information, including account name
+ @utils.hook("raw.received.354", default_event=True)
+ def handle_354(self, event):
+ core.handle_354(self.events, event)
+
+ # response to an empty mode command
+ @utils.hook("raw.received.324")
+ def handle_324(self, event):
+ channel.handle_324(self.events, event)
+
+ # channel creation unix timestamp
+ @utils.hook("raw.received.329", default_event=True)
+ def handle_329(self, event):
+ channel.handle_329(event)
+
+ # nickname already in use
+ @utils.hook("raw.received.433", default_event=True)
+ def handle_433(self, event):
+ core.handle_433(event)
+ # nickname/channel is temporarily unavailable
+ @utils.hook("raw.received.437")
+ def handle_437(self, event):
+ core.handle_437(event)
+
+ # we need a registered nickname for this channel
+ @utils.hook("raw.received.477", default_event=True)
+ def handle_477(self, event):
+ channel.handle_477(self.timers, event)
+
+ # someone's been kicked from a channel
+ @utils.hook("raw.received.kick")
+ def kick(self, event):
+ channel.kick(self.events, event)
+
+ # a channel has been renamed
+ @utils.hook("raw.received.rename")
+ def rename(self, event):
+ channel.rename(self.events, event)
diff --git a/src/core_modules/line_handler/channel.py b/src/core_modules/line_handler/channel.py
new file mode 100644
index 00000000..91150839
--- /dev/null
+++ b/src/core_modules/line_handler/channel.py
@@ -0,0 +1,160 @@
+from src import IRCLine, utils
+
+def handle_332(events, event):
+ channel = event["server"].channels.get(event["line"].args[1])
+ topic = event["line"].args.get(2)
+ channel.set_topic(topic)
+ events.on("received.332").call(channel=channel, server=event["server"],
+ topic=topic)
+
+def topic(events, event):
+ user = event["server"].get_user(event["line"].source.nickname)
+ channel = event["server"].channels.get(event["line"].args[0])
+ topic = event["line"].args.get(1)
+ channel.set_topic(topic)
+ events.on("received.topic").call(channel=channel, server=event["server"],
+ topic=topic, user=user)
+
+def handle_333(events, event):
+ channel = event["server"].channels.get(event["line"].args[1])
+
+ topic_setter = IRCLine.parse_hostmask(event["line"].args[2])
+ topic_time = int(event["line"].args[3])
+
+ channel.set_topic_setter(topic_setter)
+ channel.set_topic_time(topic_time)
+ events.on("received.333").call(channel=channel,
+ setter=topic_setter, set_at=topic_time, server=event["server"])
+
+def handle_353(event):
+ channel = event["server"].channels.get(event["line"].args[2])
+ nicknames = event["line"].args.get(3).split(" ")
+
+ # there can sometimes be a dangling space at the end of a 353
+ if nicknames and not nicknames[-1]:
+ nicknames.pop(-1)
+
+ for nickname in nicknames:
+ modes = set([])
+
+ while nickname[0] in event["server"].prefix_symbols:
+ modes.add(event["server"].prefix_symbols[nickname[0]])
+ nickname = nickname[1:]
+
+ if event["server"].has_capability_str("userhost-in-names"):
+ hostmask = IRCLine.parse_hostmask(nickname)
+ nickname = hostmask.nickname
+ user = event["server"].get_user(hostmask.nickname,
+ username=hostmask.username, hostname=hostmask.hostname)
+ else:
+ user = event["server"].get_user(nickname)
+ user.join_channel(channel)
+ channel.add_user(user)
+
+ for mode in modes:
+ channel.add_mode(mode, nickname)
+
+def handle_366(event):
+ event["server"].send_whox(event["line"].args[1], "n", "ahnrtu", "111")
+
+def join(events, event):
+ account = None
+ realname = None
+ channel_name = event["line"].args[0]
+
+ if len(event["line"].args) == 3:
+ if not event["line"].args[1] == "*":
+ account = event["line"].args[1]
+ realname = event["line"].args[2]
+
+ user = event["server"].get_user(event["line"].source.nickname,
+ username=event["line"].source.username,
+ hostname=event["line"].source.hostname)
+
+ if account:
+ user.account = account
+ if realname:
+ user.realname = realname
+
+ is_self = event["server"].is_own_nickname(event["line"].source.nickname)
+ if is_self:
+ channel = event["server"].channels.add(channel_name)
+ else:
+ channel = event["server"].channels.get(channel_name)
+
+
+ channel.add_user(user)
+ user.join_channel(channel)
+
+ if is_self:
+ events.on("self.join").call(channel=channel, server=event["server"],
+ account=account, realname=realname)
+ channel.send_mode()
+ else:
+ events.on("received.join").call(channel=channel, user=user,
+ server=event["server"], account=account, realname=realname)
+
+def part(events, event):
+ channel = event["server"].channels.get(event["line"].args[0])
+ user = event["server"].get_user(event["line"].source.nickname)
+ reason = event["line"].args.get(1)
+
+ channel.remove_user(user)
+ user.part_channel(channel)
+ if not len(user.channels):
+ event["server"].remove_user(user)
+
+ if not event["server"].is_own_nickname(event["line"].source.nickname):
+ events.on("received.part").call(channel=channel, reason=reason,
+ user=user, server=event["server"])
+ else:
+ event["server"].channels.remove(channel)
+ events.on("self.part").call(channel=channel, reason=reason,
+ server=event["server"])
+
+def handle_324(events, event):
+ if event["line"].args[1] in event["server"].channels:
+ channel = event["server"].channels.get(event["line"].args[1])
+ modes = event["line"].args[2]
+ args = event["line"].args[3:]
+ new_modes = channel.parse_modes(modes, args[:])
+ events.on("received.324").call(modes=new_modes,
+ channel=channel, server=event["server"], mode_str=modes,
+ args_str=args)
+
+def handle_329(event):
+ channel = event["server"].channels.get(event["line"].args[1])
+ channel.creation_timestamp = int(event["line"].args[2])
+
+def handle_477(timers, event):
+ pass
+
+def kick(events, event):
+ user = event["server"].get_user(event["line"].source.nickname)
+ target = event["line"].args[1]
+ channel = event["server"].channels.get(event["line"].args[0])
+ reason = event["line"].args.get(2)
+ target_user = event["server"].get_user(target)
+
+ if not event["server"].is_own_nickname(target):
+ events.on("received.kick").call(channel=channel, reason=reason,
+ target_user=target_user, user=user, server=event["server"])
+ else:
+ event["server"].channels.remove(channel)
+ events.on("self.kick").call(channel=channel, reason=reason, user=user,
+ server=event["server"])
+
+ channel.remove_user(target_user)
+ target_user.part_channel(channel)
+ if not len(target_user.channels):
+ event["server"].remove_user(target_user)
+
+def rename(events, event):
+ old_name = event["line"].args[0]
+ new_name = event["line"].args[1]
+ channel = event["server"].channels.get(old_name)
+
+ event["server"].channels.rename(old_name, new_name)
+ events.on("received.rename").call(channel=channel, old_name=old_name,
+ new_name=new_name, reason=event["line"].args.get(2),
+ server=event["server"])
diff --git a/src/core_modules/line_handler/core.py b/src/core_modules/line_handler/core.py
new file mode 100644
index 00000000..d72bf223
--- /dev/null
+++ b/src/core_modules/line_handler/core.py
@@ -0,0 +1,154 @@
+import codecs, re
+
+RE_ISUPPORT_ESCAPE = re.compile(r"\\x(\d\d)", re.I)
+RE_MODES = re.compile(r"[-+]\w+")
+
+def ping(event):
+ event["server"].send_pong(event["line"].args[0])
+
+def handle_001(event):
+ event["server"].socket.enable_write_throttle()
+ event["server"].name = event["line"].source.hostmask
+ event["server"].set_own_nickname(event["line"].args[0])
+ event["server"].send_whois(event["server"].nickname)
+ event["server"].send_mode(event["server"].nickname)
+ event["server"].connected = True
+
+def handle_005(events, event):
+ isupport_list = event["line"].args[1:-1]
+ isupport = {}
+
+ for i, item in enumerate(isupport_list):
+ key, sep, value = item.partition("=")
+ if value:
+ for match in RE_ISUPPORT_ESCAPE.finditer(value):
+ char = codecs.decode(match.group(1), "hex").decode("ascii")
+ value.replace(match.group(0), char)
+
+ if sep:
+ isupport[key] = value
+ else:
+ isupport[key] = None
+ event["server"].isupport.update(isupport)
+
+ if "NAMESX" in isupport and not event["server"].has_capability_str(
+ "multi-prefix"):
+ event["server"].send_raw("PROTOCTL NAMESX")
+
+ if "PREFIX" in isupport:
+ modes, symbols = isupport["PREFIX"][1:].split(")", 1)
+ event["server"].prefix_symbols.clear()
+ event["server"].prefix_modes.clear()
+ for symbol, mode in zip(symbols, modes):
+ event["server"].prefix_symbols[symbol] = mode
+ event["server"].prefix_modes[mode] = symbol
+
+ if "CHANMODES" in isupport:
+ modes = isupport["CHANMODES"].split(",", 3)
+ event["server"].channel_list_modes = list(modes[0])
+ event["server"].channel_parametered_modes = list(modes[1])
+ event["server"].channel_setting_modes = list(modes[2])
+ event["server"].channel_modes = list(modes[3])
+ if "CHANTYPES" in isupport:
+ event["server"].channel_types = list(isupport["CHANTYPES"])
+ if "CASEMAPPING" in isupport:
+ event["server"].case_mapping = isupport["CASEMAPPING"]
+ if "STATUSMSG" in isupport:
+ event["server"].statusmsg = list(isupport["STATUSMSG"])
+
+ events.on("received.005").call(isupport=isupport,
+ server=event["server"])
+
+def handle_004(event):
+ event["server"].version = event["line"].args[2]
+
+def motd_start(event):
+ event["server"].motd_lines.clear()
+def motd_line(event):
+ event["server"].motd_lines.append(event["line"].args[1])
+
+def _own_modes(server, modes):
+ mode_chunks = RE_MODES.findall(modes)
+ for chunk in mode_chunks:
+ remove = chunk[0] == "-"
+ for mode in chunk[1:]:
+ server.change_own_mode(remove, mode)
+
+def mode(events, event):
+ user = event["server"].get_user(event["line"].source.nickname)
+ target = event["line"].args[0]
+ is_channel = event["server"].is_channel(target)
+ if is_channel:
+ channel = event["server"].channels.get(target)
+ modes = event["line"].args[1]
+ args = event["line"].args[2:]
+
+ new_modes = channel.parse_modes(modes, args[:])
+
+ events.on("received.mode.channel").call(modes=new_modes,
+ channel=channel, server=event["server"], user=user, modes_str=modes,
+ args_str=args)
+ elif event["server"].is_own_nickname(target):
+ modes = event["line"].args[1]
+ _own_modes(event["server"], modes)
+
+ events.on("self.mode").call(modes=modes, server=event["server"])
+ event["server"].send_who(event["server"].nickname)
+
+def handle_221(event):
+ _own_modes(event["server"], event["line"].args[1])
+
+def invite(events, event):
+ target_channel = event["line"].args[1]
+ user = event["server"].get_user(event["line"].source.nickname)
+ target_user = event["server"].get_user(event["line"].args[0])
+ events.on("received.invite").call(user=user, target_channel=target_channel,
+ server=event["server"], target_user=target_user)
+
+def handle_352(events, event):
+ nickname = event["line"].args[5]
+ username = event["line"].args[2]
+ hostname = event["line"].args[3]
+
+ if event["server"].is_own_nickname(nickname):
+ event["server"].username = username
+ event["server"].hostname = hostname
+
+ target = event["server"].get_user(nickname)
+ target.username = username
+ target.hostname = hostname
+ events.on("received.who").call(server=event["server"],
+ user=target)
+
+def handle_354(events, event):
+ if event["line"].args[1] == "111":
+ nickname = event["line"].args[4]
+ username = event["line"].args[2]
+ hostname = event["line"].args[3]
+ realname = event["line"].args[6]
+ account = event["line"].args[5]
+
+ if event["server"].is_own_nickname(nickname):
+ event["server"].username = username
+ event["server"].hostname = hostname
+ event["server"].realname = realname
+
+ target = event["server"].get_user(nickname)
+ target.username = username
+ target.hostname = hostname
+ target.realname = realname
+ if not account == "0":
+ target.account = account
+ else:
+ target.account = None
+ events.on("received.whox").call(server=event["server"],
+ user=target)
+
+def _nick_in_use(server):
+ new_nick = "%s|" % server.connection_params.nickname
+ server.send_nick(new_nick)
+
+def handle_433(event):
+ _nick_in_use(event["server"])
+def handle_437(event):
+ _nick_in_use(event["server"])
diff --git a/src/core_modules/line_handler/ircv3.py b/src/core_modules/line_handler/ircv3.py
new file mode 100644
index 00000000..a9d740ed
--- /dev/null
+++ b/src/core_modules/line_handler/ircv3.py
@@ -0,0 +1,138 @@
+from src import utils
+
+CAPABILITIES = [
+ utils.irc.Capability("multi-prefix"),
+ utils.irc.Capability("chghost"),
+ utils.irc.Capability("invite-notify"),
+ utils.irc.Capability("account-tag"),
+ utils.irc.Capability("account-notify"),
+ utils.irc.Capability("extended-join"),
+ utils.irc.Capability("away-notify"),
+ utils.irc.Capability("userhost-in-names"),
+ utils.irc.Capability("message-tags", "draft/message-tags-0.2"),
+ utils.irc.Capability("cap-notify"),
+ utils.irc.Capability("batch"),
+ utils.irc.Capability(None, "draft/rename", alias="rename"),
+ utils.irc.Capability(None, "draft/setname", alias="setname")
+]
+
+def _cap_depend_sort(caps, server_caps):
+ sorted_caps = []
+
+ caps_copy = {alias: cap.copy() for alias, cap in caps.items()}
+
+ for cap in caps.values():
+ if not cap.available(server_caps):
+ del caps_copy[cap.alias]
+
+ while True:
+ remove = []
+ for alias, cap in caps_copy.items():
+ for depend_alias in cap.depends_on:
+ if not depend_alias in caps_copy:
+ remove.append(alias)
+ if remove:
+ for alias in remove:
+ del caps_copy[alias]
+ else:
+ break
+
+ while caps_copy:
+ fulfilled = []
+ for cap in caps_copy.values():
+ remove = []
+ for depend_alias in cap.depends_on:
+ if depend_alias in sorted_caps:
+ remove.append(depend_alias)
+ for remove_cap in remove:
+ cap.depends_on.remove(remove_cap)
+
+ if not cap.depends_on:
+ fulfilled.append(cap.alias)
+ for fulfilled_cap in fulfilled:
+ del caps_copy[fulfilled_cap]
+ sorted_caps.append(fulfilled_cap)
+ return [caps[alias] for alias in sorted_caps]
+
+def _cap_match(server, caps):
+ matched_caps = {}
+ blacklist = server.get_setting("blacklisted-caps", [])
+
+ cap_aliases = {}
+ for cap in caps:
+ if not cap.alias in blacklist:
+ cap_aliases[cap.alias] = cap
+
+ sorted_caps = _cap_depend_sort(cap_aliases, server.server_capabilities)
+
+ for cap in sorted_caps:
+ available = cap.available(server.server_capabilities)
+ if available and not server.has_capability(cap):
+ matched_caps[available] = cap
+ return matched_caps
+
+def cap(exports, events, event):
+ capabilities = utils.parse.keyvalue(event["line"].args[-1])
+ subcommand = event["line"].args[1].upper()
+ is_multiline = len(event["line"].args) > 3 and event["line"].args[2] == "*"
+
+ if subcommand == "DEL":
+ for capability in capabilities.keys():
+ event["server"].agreed_capabilities.discard(capability)
+ if capability in event["server"].server_capabilities:
+ del event["server"].server_capabilities[capability]
+
+ events.on("received.cap.del").call(server=event["server"],
+ capabilities=capabilities)
+ elif subcommand == "ACK":
+ for cap_name, cap_args in capabilities.items():
+ if cap_name[0] == "-":
+ event["server"].agreed_capabilities.discard(cap_name[1:])
+ else:
+ event["server"].agreed_capabilities.add(cap_name)
+
+ events.on("received.cap.ack").call(capabilities=capabilities,
+ server=event["server"])
+
+ if subcommand == "LS" or subcommand == "NEW":
+ event["server"].server_capabilities.update(capabilities)
+ if not is_multiline:
+ server_caps = list(event["server"].server_capabilities.keys())
+ all_caps = CAPABILITIES[:]
+
+ export_caps = [cap.copy() for cap in exports.get_all("cap")]
+ all_caps.extend(export_caps)
+
+ module_caps = events.on("received.cap.ls").call(
+ capabilities=event["server"].server_capabilities,
+ server=event["server"])
+ module_caps = list(filter(None, module_caps))
+ all_caps.extend(module_caps)
+
+ matched_caps = _cap_match(event["server"], all_caps)
+ event["server"].capability_queue.update(matched_caps)
+
+ if event["server"].capability_queue:
+ event["server"].send_capability_queue()
+ else:
+ event["server"].send_capability_end()
+
+
+ if subcommand == "ACK" or subcommand == "NAK":
+ ack = subcommand == "ACK"
+ for capability in capabilities:
+ if capability in event["server"].capabilities_requested:
+ cap_obj = event["server"].capabilities_requested[capability]
+ del event["server"].capabilities_requested[capability]
+ if ack:
+ cap_obj.ack()
+ else:
+ cap_obj.nak()
+
+ if (not event["server"].capabilities_requested and
+ not event["server"].waiting_for_capabilities()):
+ event["server"].send_capability_end()
+
+def authenticate(events, event):
+ events.on("received.authenticate").call(message=event["line"].args[0],
+ server=event["server"])
diff --git a/src/core_modules/line_handler/message.py b/src/core_modules/line_handler/message.py
new file mode 100644
index 00000000..fa36dbc2
--- /dev/null
+++ b/src/core_modules/line_handler/message.py
@@ -0,0 +1,109 @@
+from src import IRCBuffer, utils
+
+def _from_self(server, source):
+ if source:
+ return server.is_own_nickname(source.nickname)
+ else:
+ return False
+
+def message(events, event):
+ from_self = _from_self(event["server"], event["line"].source)
+ if from_self == None:
+ return
+
+ direction = "send" if from_self else "received"
+
+ target_str = event["line"].args[0]
+
+ message = None
+ if len(event["line"].args) > 1:
+ message = event["line"].args[1]
+
+ if not from_self and (
+ not event["line"].source or
+ not event["server"].name or
+ event["line"].source.hostmask == event["server"].name or
+ target_str == "*"):
+ if event["line"].source:
+ event["server"].name = event["line"].source.hostmask
+
+ events.on("received.server-notice").call(message=message,
+ message_split=message.split(" "), server=event["server"])
+ return
+
+ if from_self:
+ user = event["server"].get_user(event["server"].nickname)
+ else:
+ user = event["server"].get_user(event["line"].source.nickname,
+ username=event["line"].source.username,
+ hostname=event["line"].source.hostname)
+
+ # strip prefix_symbols from the start of target, for when people use
+ # e.g. 'PRIVMSG +#channel :hi' which would send a message to only
+ # voiced-or-above users
+ target = target_str.lstrip("".join(event["server"].statusmsg))
+
+ is_channel = event["server"].is_channel(target)
+
+ if is_channel:
+ if not target in event["server"].channels:
+ return
+ target_obj = event["server"].channels.get(target)
+ else:
+ target_obj = event["server"].get_user(target)
+
+ kwargs = {"server": event["server"], "target": target_obj,
+ "target_str": target_str, "user": user, "tags": event["line"].tags,
+ "is_channel": is_channel, "from_self": from_self, "line": event["line"]}
+
+ action = False
+
+ if message:
+ ctcp_message = utils.irc.parse_ctcp(message)
+
+ if ctcp_message:
+ if (not ctcp_message.command == "ACTION" or not
+ event["line"].command == "PRIVMSG"):
+ if event["line"].command == "PRIVMSG":
+ ctcp_action = "request"
+ else:
+ ctcp_action = "response"
+ events.on(direction).on("ctcp").on(ctcp_action).call(
+ message=ctcp_message.message, **kwargs)
+ events.on(direction).on("ctcp").on(ctcp_action).on(
+ ctcp_message.command).call(message=ctcp_message.message,
+ **kwargs)
+ return
+ else:
+ message = ctcp_message.message
+ action = True
+
+ if not message == None:
+ kwargs["message"] = message
+ kwargs["message_split"] = message.split(" ")
+ kwargs["action"] = action
+
+ event_type = event["line"].command.lower()
+ if event_type == "privmsg":
+ event_type = "message"
+
+ context = "channel" if is_channel else "private"
+ hook = events.on(direction).on(event_type).on(context)
+
+ buffer_line = None
+ if message:
+ buffer_line = IRCBuffer.BufferLine(user.nickname, message, action,
+ event["line"].tags, from_self, event["line"].command)
+
+ buffer_obj = target_obj
+ if is_channel:
+ hook.call(channel=target_obj, buffer_line=buffer_line, **kwargs)
+ else:
+ buffer_obj = target_obj
+ if not from_self:
+ buffer_obj = user
+
+ hook.call(buffer_line=buffer_line, **kwargs)
+
+ if buffer_line:
+ buffer_obj.buffer.add(buffer_line)
diff --git a/src/core_modules/line_handler/user.py b/src/core_modules/line_handler/user.py
new file mode 100644
index 00000000..d1592cd7
--- /dev/null
+++ b/src/core_modules/line_handler/user.py
@@ -0,0 +1,102 @@
+from src import utils
+
+def handle_311(event):
+ nickname = event["line"].args[1]
+ username = event["line"].args[2]
+ hostname = event["line"].args[3]
+ realname = event["line"].args[4]
+
+ if event["server"].is_own_nickname(nickname):
+ event["server"].username = username
+ event["server"].hostname = hostname
+ event["server"].realname = realname
+
+ target = event["server"].get_user(nickname)
+ target.username = username
+ target.hostname = hostname
+ target.realname = realname
+
+def quit(events, event):
+ nickname = None
+ if event["direction"] == utils.Direction.Recv:
+ nickname = event["line"].source.nickname
+ reason = event["line"].args.get(0)
+
+ if event["direction"] == utils.Direction.Recv:
+ nickname = event["line"].source.nickname
+ if (not event["server"].is_own_nickname(nickname) and
+ not event["line"].source.hostmask == "*"):
+ user = event["server"].get_user(nickname)
+ events.on("received.quit").call(reason=reason, user=user,
+ server=event["server"])
+ event["server"].remove_user(user)
+ else:
+ event["server"].disconnect()
+ else:
+ events.on("send.quit").call(reason=reason, server=event["server"])
+
+def nick(events, event):
+ new_nickname = event["line"].args.get(0)
+ user = event["server"].get_user(event["line"].source.nickname)
+ old_nickname = user.nickname
+ user.set_nickname(new_nickname)
+ event["server"].change_user_nickname(old_nickname, new_nickname)
+
+ if not event["server"].is_own_nickname(event["line"].source.nickname):
+ events.on("received.nick").call(new_nickname=new_nickname,
+ old_nickname=old_nickname, user=user, server=event["server"])
+ else:
+ events.on("self.nick").call(server=event["server"],
+ new_nickname=new_nickname, old_nickname=old_nickname)
+ event["server"].set_own_nickname(new_nickname)
+
+def away(events, event):
+ user = event["server"].get_user(event["line"].source.nickname)
+ message = event["line"].args.get(0)
+ if message:
+ user.away = True
+ user.away_message = message
+ events.on("received.away.on").call(user=user, server=event["server"],
+ message=message)
+ else:
+ user.away = False
+ user.away_message = None
+ events.on("received.away.off").call(user=user, server=event["server"])
+
+def chghost(events, event):
+ nickname = event["line"].source.nickname
+ username = event["line"].args[0]
+ hostname = event["line"].args[1]
+
+ if event["server"].is_own_nickname(nickname):
+ event["server"].username = username
+ event["server"].hostname = hostname
+
+ target = event["server"].get_user(nickname)
+ events.on("received.chghost").call(user=target, server=event["server"],
+ username=username, hostname=hostname)
+
+ target.username = username
+ target.hostname = hostname
+
+def setname(event):
+ nickname = event["line"].source.nickname
+ realname = event["line"].args[0]
+
+ user = event["server"].get_user(nickname)
+ user.realname = realname
+
+ if event["server"].is_own_nickname(nickname):
+ event["server"].realname = realname
+
+def account(events, event):
+ user = event["server"].get_user(event["line"].source.nickname)
+
+ if not event["line"].args[0] == "*":
+ user.account = event["line"].args[0]
+ events.on("received.account.login").call(user=user,
+ server=event["server"], account=event["line"].args[0])
+ else:
+ user.account = None
+ events.on("received.account.logout").call(user=user,
+ server=event["server"])