aboutsummaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/line_handler.py783
-rw-r--r--modules/line_handler/__init__.py257
-rw-r--r--modules/line_handler/channel.py159
-rw-r--r--modules/line_handler/core.py134
-rw-r--r--modules/line_handler/ircv3.py74
-rw-r--r--modules/line_handler/message.py168
-rw-r--r--modules/line_handler/user.py89
7 files changed, 881 insertions, 783 deletions
diff --git a/modules/line_handler.py b/modules/line_handler.py
deleted file mode 100644
index 242eb035..00000000
--- a/modules/line_handler.py
+++ /dev/null
@@ -1,783 +0,0 @@
-import codecs, enum, re
-from src import ModuleManager, utils
-
-RE_ISUPPORT_ESCAPE = re.compile(r"\\x(\d\d)", re.I)
-RE_MODES = re.compile(r"[-+]\w+")
-
-CAPABILITIES = {"multi-prefix", "chghost", "invite-notify", "account-tag",
- "account-notify", "extended-join", "away-notify", "userhost-in-names",
- "draft/message-tags-0.2", "message-tags", "server-time", "cap-notify",
- "batch", "draft/labeled-response", "draft/rename", "echo-message",
- "draft/setname"}
-
-LABELED_BATCH = {
- "labeled-response": "label",
- "draft/labeled-response": "draft/label"
-}
-
-class Direction(enum.Enum):
- SEND = 0
- RECV = 1
-
-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.kwargs.get("default_event", False))
- default_event = any(default_events)
-
- kwargs = {"args": line.args, "tags": line.tags, "server": server,
- "prefix": line.prefix, "direction": Direction.RECV}
-
- self.events.on("raw.received").on(line.command).call_unsafe(**kwargs)
- if default_event or not hooks:
- self.events.on("received").on(line.command).call(**kwargs)
-
- @utils.hook("raw.received")
- def handle_raw(self, event):
- line = utils.irc.parse_line(event["line"])
- if "batch" in line.tags and line.tags["batch"] in event[
- "server"].batches:
- server.batches[tag["batch"]].lines.append(line)
- else:
- self._handle(event["server"], line)
-
- @utils.hook("raw.send")
- def handle_send(self, event):
- line = utils.irc.parse_line(event["line"])
- self.events.on("raw.send").on(line.command).call_unsafe(
- args=line.args, tags=line.tags, server=event["server"],
- direction=Direction.SEND)
-
- def _event(self, event, event_name: str, **kwargs: dict):
- direction = event["direction"]
- if direction == Direction.RECV:
- root_event = self.events.on("received")
- elif direction == Direction.SEND:
- root_event = self.events.on("send")
- root_event.on(event_name).call(**kwargs)
-
- # ping from the server
- @utils.hook("raw.received.ping")
- def ping(self, event):
- event["server"].send_pong(event["args"].get(0))
-
- @utils.hook("raw.received.error")
- def error(self, event):
- self.log.error("ERROR received: %s", [event["args"][0]])
- @utils.hook("raw.received.fail")
- def fail(self, event):
- command = event["args"][0]
- error_code = event["args"][1]
- context = event["args"][2:-1]
- description = event["args"][-1]
-
- self.log.warn("FAIL (%s %s) received: %s" %
- (command, error_code, description))
- self.events.on("received.fail").call(command=command,
- error_code=error_code, context=context, description=description)
-
- # first numeric line the server sends
- @utils.hook("raw.received.001", default_event=True)
- def handle_001(self, event):
- event["server"].socket.set_write_throttling(True)
- event["server"].name = event["prefix"].hostmask
- event["server"].set_own_nickname(event["args"][0])
- event["server"].send_whois(event["server"].nickname)
-
- # server telling us what it supports
- @utils.hook("raw.received.005")
- def handle_005(self, event):
- isupport_list = event["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 "multi-prefix" in event[
- "server"].agreed_capabilities:
- event["server"].send("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_paramatered_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"]
-
- self._event(event, "numeric.005", isupport=isupport,
- server=event["server"])
-
- # whois respose (nickname, username, realname, hostname)
- @utils.hook("raw.received.311", default_event=True)
- def handle_311(self, event):
- nickname = event["args"][1]
- if event["server"].is_own_nickname(nickname):
- target = event["server"]
- else:
- target = event["server"].get_user(nickname)
- target.username = event["args"][2]
- target.hostname = event["args"][3]
- target.realname = event["args"][4]
-
- # on-join channel topic line
- @utils.hook("raw.received.332")
- def handle_332(self, event):
- channel = event["server"].channels.get(event["args"][1])
- topic = event["args"].get(2)
- channel.set_topic(topic)
- self._event(event, "numeric.332", channel=channel,
- server=event["server"], topic=topic)
-
- # channel topic changed
- @utils.hook("raw.received.topic")
- def topic(self, event):
- user = event["server"].get_user(event["prefix"].nickname)
- channel = event["server"].channels.get(event["args"][0])
- topic = event["args"].get(1)
- channel.set_topic(topic)
- self._event(event, "topic", channel=channel, server=event["server"],
- topic=topic, user=user)
-
- # on-join channel topic set by/at
- @utils.hook("raw.received.333")
- def handle_333(self, event):
- channel = event["server"].channels.get(event["args"][1])
-
- topic_setter_hostmask = event["args"][2]
- topic_setter = utils.irc.seperate_hostmask(topic_setter_hostmask)
- topic_time = int(event["args"][3]) if event["args"][3].isdigit(
- ) else None
-
- channel.set_topic_setter(topic_setter.nickname, topic_setter.username,
- topic_setter.hostname)
- channel.set_topic_time(topic_time)
- self._event(event, "numeric.333", channel=channel,
- setter=topic_setter.nickname, set_at=topic_time,
- server=event["server"])
-
- # /names response, also on-join user list
- @utils.hook("raw.received.353", default_event=True)
- def handle_353(self, event):
- channel = event["server"].channels.get(event["args"][2])
- nicknames = event["args"].get(3).split()
- 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 "userhost-in-names" in event["server"].agreed_capabilities:
- hostmask = utils.irc.seperate_hostmask(nickname)
- nickname = hostmask.nickname
- user = event["server"].get_user(hostmask.nickname)
- user.username = hostmask.username
- user.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)
-
- # on-join user list has finished
- @utils.hook("raw.received.366", default_event=True)
- def handle_366(self, event):
- event["server"].send_whox(event["args"][1], "n", "ahnrtu", "111")
-
- @utils.hook("raw.received.375")
- def motd_start(self, event):
- event["server"].motd_lines.clear()
- event["server"].motd_lines.append(event["args"][1])
-
- @utils.hook("raw.received.372")
- def motd_line(self, event):
- event["server"].motd_lines.append(event["args"][1])
-
- # on user joining channel
- @utils.hook("raw.received.join")
- def join(self, event):
- account = None
- realname = None
- channel_name = event["args"][0]
-
- if len(event["args"]) == 3:
- if not event["args"][1] == "*":
- account = event["args"][1]
- realname = event["args"][2]
-
- if not event["server"].is_own_nickname(event["prefix"].nickname):
- channel = event["server"].channels.get(channel_name)
- user = event["server"].get_user(event["prefix"].nickname)
- if not user.username and not user.hostname:
- user.username = event["prefix"].username
- user.hostname = event["prefix"].hostname
-
- if account:
- user.identified_account = account
- user.identified_account_id = event["server"].get_user(
- account).get_id()
- if realname:
- user.realname = realname
-
- channel.add_user(user)
- user.join_channel(channel)
- self._event(event, "join", channel=channel, user=user,
- server=event["server"], account=account, realname=realname)
- else:
- channel = event["server"].channels.add(channel_name)
- if channel.name in event["server"].attempted_join:
- del event["server"].attempted_join[channel.name]
- self.events.on("self.join").call(channel=channel,
- server=event["server"], account=account, realname=realname)
- channel.send_mode()
-
- # on user parting channel
- @utils.hook("raw.received.part")
- def part(self, event):
- channel = event["server"].channels.get(event["args"][0])
- reason = event["args"].get(1)
-
- if not event["server"].is_own_nickname(event["prefix"].nickname):
- user = event["server"].get_user(event["prefix"].nickname)
- self._event(event, "part", channel=channel, reason=reason,
- user=user, server=event["server"])
- channel.remove_user(user)
- user.part_channel(channel)
- if not len(user.channels):
- event["server"].remove_user(user)
- else:
- self.events.on("self.part").call(channel=channel, reason=reason,
- server=event["server"])
- event["server"].channels.remove(channel)
-
- # 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: %s", [event["args"][1]])
-
- # a user has disconnected!
- @utils.hook("raw.received.quit")
- @utils.hook("raw.send.quit")
- def quit(self, event):
- nickname = None
- if event["direction"] == Direction.RECV:
- nickname = event["prefix"].nickname
- reason = event["args"].get(0)
-
- if event["direction"] == Direction.RECV:
- nickname = event["prefix"].nickname
- if (not event["server"].is_own_nickname(nickname) and
- not event["prefix"].hostmask == "*"):
- user = event["server"].get_user(nickname)
- event["server"].remove_user(user)
- self.events.on("received.quit").call(reason=reason, user=user,
- server=event["server"])
- else:
- event["server"].disconnect()
- else:
- self.events.on("send.quit").call(reason=reason,
- server=event["server"])
-
- def _match_caps(self, capabilities):
- return set(capabilities) & CAPABILITIES
-
- # the server is telling us about its capabilities!
- @utils.hook("raw.received.cap")
- def cap(self, event):
- capabilities = utils.parse.keyvalue(event["args"][-1])
- subcommand = event["args"][1].lower()
- is_multiline = len(event["args"]) > 3 and event["args"][2] == "*"
-
- if subcommand == "ls":
- event["server"].cap_started = True
- event["server"].server_capabilities.update(capabilities)
- if not is_multiline:
- matched_caps = self._match_caps(
- list(event["server"].server_capabilities.keys()))
- blacklisted_caps = event["server"].get_setting(
- "blacklisted-caps", [])
- matched_caps = list(
- set(matched_caps)-set(blacklisted_caps))
-
- event["server"].queue_capabilities(matched_caps)
-
- self._event(event, "cap.ls",
- capabilities=event["server"].server_capabilities,
- server=event["server"])
-
- if event["server"].has_capability_queue():
- event["server"].send_capability_queue()
- else:
- event["server"].send_capability_end()
- elif subcommand == "new":
- capabilities_keys = capabilities.keys()
- event["server"].server_capabilities.update(capabilities)
-
- matched_caps = self._match_caps(list(capabilities_keys))
- event["server"].queue_capabilities(matched_caps)
-
- self._event(event, "cap.new", server=event["server"],
- capabilities=capabilities)
-
- if event["server"].has_capability_queue():
- event["server"].send_capability_queue()
- elif subcommand == "del":
- for capability in capabilities.keys():
- event["server"].agreed_capabilities.discard(capability)
- del event["server"].server_capabilities[capability]
-
- self._event(event, "cap.del", server=event["server"],
- capabilities=capabilities)
- elif subcommand == "ack":
- event["server"].agreed_capabilities.update(capabilities)
- self._event(event, "cap.ack", capabilities=capabilities,
- server=event["server"])
-
- if subcommand == "ack" or subcommand == "nak":
- for capability in capabilities:
- event["server"].requested_capabilities.remove(capability)
-
- if (event["server"].cap_started and
- not event["server"].requested_capabilities and
- not event["server"].waiting_for_capabilities()):
- event["server"].cap_started = False
- event["server"].send_capability_end()
-
- # the server is asking for authentication
- @utils.hook("raw.received.authenticate")
- def authenticate(self, event):
- self._event(event, "authenticate", message=event["args"][0],
- server=event["server"])
-
- # someone has changed their nickname
- @utils.hook("raw.received.nick")
- def nick(self, event):
- new_nickname = event["args"].get(0)
- if not event["server"].is_own_nickname(event["prefix"].nickname):
- user = event["server"].get_user(event["prefix"].nickname)
- old_nickname = user.nickname
- user.set_nickname(new_nickname)
- event["server"].change_user_nickname(old_nickname, new_nickname)
-
- self._event(event, "nick", new_nickname=new_nickname,
- old_nickname=old_nickname, user=user, server=event["server"])
- else:
- old_nickname = event["server"].nickname
- event["server"].set_own_nickname(new_nickname)
-
- self.events.on("self.nick").call(server=event["server"],
- new_nickname=new_nickname, old_nickname=old_nickname)
-
- # something's mode has changed
- @utils.hook("raw.received.mode")
- def mode(self, event):
- user = event["server"].get_user(event["prefix"].nickname)
- target = event["args"][0]
- is_channel = target[0] in event["server"].channel_types
- if is_channel:
- channel = event["server"].channels.get(target)
- remove = False
- args = event["args"][2:]
- _args = args[:]
- modes = RE_MODES.findall(event["args"][1])
- for chunk in modes:
- remove = chunk[0] == "-"
- for mode in chunk[1:]:
- if mode in event["server"].channel_modes:
- channel.change_mode(remove, mode)
- elif mode in event["server"].prefix_modes and len(args):
- channel.change_mode(remove, mode, args.pop(0))
- elif (mode in event["server"].channel_list_modes or
- mode in event["server"].channel_paramatered_modes):
- args.pop(0)
- elif not remove:
- args.pop(0)
- self._event(event, "mode.channel", modes=modes, mode_args=_args,
- channel=channel, server=event["server"], user=user)
- elif event["server"].is_own_nickname(target):
- modes = RE_MODES.findall(event["args"][1])
- for chunk in modes:
- remove = chunk[0] == "-"
- for mode in chunk[1:]:
- event["server"].change_own_mode(remove, mode)
- self.events.on("self.mode").call(modes=modes,
- server=event["server"])
- event["server"].send_who(event["server"].nickname)
-
- # someone (maybe me!) has been invited somewhere
- @utils.hook("raw.received.invite")
- def invite(self, event):
- target_channel = event["args"][1]
- user = event["server"].get_user(event["prefix"].nickname)
- target_user = event["server"].get_user(event["args"][0])
- self._event(event, "invite", user=user, target_channel=target_channel,
- server=event["server"], target_user=target_user)
-
- def _from_self(self, server, direction, prefix):
- if direction == Direction.SEND:
- if "echo-message" in server.agreed_capabilities:
- return None
- else:
- return True
- else:
- if prefix:
- return server.is_own_nickname(prefix.nickname)
- else:
- return False
-
- # we've received/sent a message
- @utils.hook("raw.received.privmsg")
- @utils.hook("raw.send.privmsg")
- def privmsg(self, event):
- from_self = self._from_self(event["server"], event["direction"],
- event.get("prefix", None))
- if from_self == None:
- return
-
- user = None
- if "prefix" in event and not from_self:
- user = event["server"].get_user(event["prefix"].nickname)
-
- message = event["args"][1]
- target = event["args"][0]
-
- # 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
- statusmsg = []
- while target[0] in event["server"].prefix_symbols.keys():
- statusmsg.append(target[0])
- target = target[1:]
-
- channel = None
- if target[0] in event["server"].channel_types:
- if not target in event["server"].channels:
- return
- channel = event["server"].channels.get(target)
-
- action = False
- event_type = "message"
- ctcp_message = utils.irc.parse_ctcp(message)
- if ctcp_message:
- message = ctcp_message.message
- event_type = "ctcp.%s" % ctcp_message.command
- if ctcp_message.command == "ACTION":
- action = True
- message = ctcp_message.message
-
- if user and "account" in event["tags"]:
- user.identified_account = event["tags"]["account"]
- user.identified_account_id = event["server"].get_user(
- event["tags"]["account"]).get_id()
-
- kwargs = {"message": message, "message_split": message.split(),
- "server": event["server"], "tags": event["tags"],
- "action": action}
-
- direction = "send" if from_self else "received"
- context = "channel" if channel else "private"
- hook = self.events.on(direction).on(event_type).on(context)
-
- user_nickname = None
- if user:
- user_nickname = None if from_self else user.nickname
-
- if channel:
- hook.call(user=user, channel=channel, statusmsg=statusmsg, **kwargs)
- channel.buffer.add_message(user_nickname, message, action,
- event["tags"], user==None)
- elif event["server"].is_own_nickname(target):
- hook.call(user=user, **kwargs)
- user.buffer.add_message(user_nickname, message, action,
- event["tags"], False)
- elif from_self:
- # a message we've sent to a user
- user = event["server"].get_user(target)
- hook.call(user=user, **kwargs)
- user.buffer.add_message(user_nickname, message, action,
- event["tags"], True)
-
- # we've received/sent a notice
- @utils.hook("raw.received.notice")
- @utils.hook("raw.send.notice")
- def notice(self, event):
- from_self = self._from_self(event["server"], event["direction"],
- event.get("prefix", None))
- if from_self == None:
- return
-
- message = event["args"][1]
- message_split = message.split(" ")
- target = event["args"][0]
-
- if "prefix" in event and (
- not event["prefix"] or
- not event["server"].name or
- event["prefix"].hostmask == event["server"].name or
- target == "*"):
- if event["prefix"]:
- event["server"].name = event["prefix"].hostmask
-
- self._event(event, "server-notice", message=message,
- message_split=message_split, server=event["server"])
- else:
- user = None
- if "prefix" in event and not from_self:
- user = event["server"].get_user(event["prefix"].nickname)
-
- channel = None
- if target[0] in event["server"].channel_types:
- channel = event["server"].channels.get(target)
-
- direction = "send" if from_self else "received"
- context = "channel" if channel else "private"
- hook = self.events.on(direction).on("notice").on(context)
-
- user_nickname = None
- if user:
- user_nickname = None if from_self else user.nickname
-
- kwargs = {"message": message, "message_split": message_split,
- "server": event["server"], "tags": event["tags"]}
-
- if channel:
- hook.call(user=user, channel=channel, **kwargs)
- channel.buffer.add_notice(user_nickname, message, event["tags"],
- user==None)
- elif event["server"].is_own_nickname(target):
- hook.call(user=user, **kwargs)
- user.buffer.add_notice(user_nickname, message, event["tags"],
- False)
- elif from_self:
- # a notice we've sent to a user
- user = event["server"].get_user(target)
- hook.call(user=user, **kwargs)
- user.buffer.add_notice(user_nickname, message, event["tags"],
- True)
-
- # IRCv3 TAGMSG, used to send tags without any other information
- @utils.hook("raw.received.tagmsg")
- def tagmsg(self, event):
- from_self = self._from_self(event["server"], event["direction"],
- event.get("prefix", None))
- if from_self == None:
- return
-
- user = None
- if "prefix" in event and not from_self:
- user = event["server"].get_user(event["prefix"].nickname)
-
- target = event["args"][0]
- channel = None
- if target[0] in event["server"].channel_types:
- channel = event["server"].channels.get(target)
-
- direction = "send" if from_self else "received"
- context = "channel" if channel else "private"
- hook = self.events.on(direction).on("tagmsg").on(context)
-
- kwargs = {"server": event["server"], "tags": event["tags"]}
-
- if channel:
- hook.call(user=user, channel=channel, **kwargs)
- elif event["server"].is_own_nickname(taget):
- hook.call(user=user, **kwargs)
- elif from_self:
- user = event["server"].get_user(target)
- hook.call(user=user, **kwargs)
-
- # 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 = event["server"].get_user(event["prefix"].nickname)
- message = event["args"].get(0)
- if message:
- user.away = True
- self._event(event, "away.on", user=user, server=event["server"],
- message=message)
- else:
- user.away = False
- self._event(event, "away.off", user=user, server=event["server"])
-
- @utils.hook("raw.received.batch")
- def batch(self, event):
- identifier = event["args"][0]
- modifier, identifier = identifier[0], identifier[1:]
- batch_type = event["args"][1]
-
- if modifier == "+":
- event["server"].batches[identifier] = utils.irc.IRCRecvBatch(
- identifier, batch_type, event["tags"])
- else:
- batch = event["server"].batches[identifier]
- del event["server"].batches[identifier]
-
- add_tags = {}
- if batch.type in LABELED_BATCH.keys():
- tag_name = LABELED_BATCH[batch.type]
- add_tags[tag_name] = batch.tags[tag_name]
-
- for line in batch.lines:
- if add_tags:
- line.tags.update(add_tags)
- self._handle(line)
-
- # IRCv3 CHGHOST, a user's username and/or hostname has changed
- @utils.hook("raw.received.chghost")
- def chghost(self, event):
- nickname = event["prefix"].nickname
- username = event["args"][0]
- hostname = event["args"][1]
-
- if not event["server"].is_own_nickname(nickname):
- target = event["server"].get_user(nickname)
- else:
- target = event["server"]
- target.username = username
- target.hostname = hostname
-
- # IRCv3 SETNAME, to change a user's realname
- @utils.hook("raw.received.setname")
- def setname(self, event):
- user = event["server"].get_user(event["prefix"].nickname)
- user.realname = event["args"][0]
-
- @utils.hook("raw.received.account")
- def account(self, event):
- user = event["server"].get_user(event["prefix"].nickname)
-
- if not event["args"][0] == "*":
- user.identified_account = event["args"][0]
- user.identified_account_id = event["server"].get_user(
- event["args"][0]).get_id()
- self._event(event, "account.login", user=user,
- server=event["server"], account=event["args"][0])
- else:
- user.identified_account = None
- user.identified_account_id = None
- self._event(event, "account.logout", user=user,
- server=event["server"])
-
- # response to a WHO command for user information
- @utils.hook("raw.received.352", default_event=True)
- def handle_352(self, event):
- nickname = event["args"][5]
- if not event["server"].is_own_nickname(nickname):
- target = event["server"].get_user(nickname)
- else:
- target = event["server"]
- target.username = event["args"][2]
- target.hostname = event["args"][3]
-
- # response to a WHOX command for user information, including account name
- @utils.hook("raw.received.354", default_event=True)
- def handle_354(self, event):
- if event["args"][1] == "111":
- nickname = event["args"][4]
-
- if not event["server"].is_own_nickname(nickname):
- target = event["server"].get_user(nickname)
-
- account = event["args"][5]
- if not account == "0":
- target.identified_account = account
- else:
- target.identified_account = None
- else:
- target = event["server"]
-
- target.username = event["args"][2]
- target.hostname = event["args"][3]
- target.realname = event["args"][6]
-
-
- # response to an empty mode command
- @utils.hook("raw.received.324", default_event=True)
- def handle_324(self, event):
- channel = event["server"].channels.get(event["args"][1])
- modes = event["args"][2]
- if modes[0] == "+" and modes[1:]:
- for mode in modes[1:]:
- if mode in event["server"].channel_modes:
- channel.add_mode(mode)
-
- # channel creation unix timestamp
- @utils.hook("raw.received.329", default_event=True)
- def handle_329(self, event):
- channel = event["server"].channels.get(event["args"][1])
- channel.creation_timestamp = int(event["args"][2])
-
- # nickname already in use
- @utils.hook("raw.received.433", default_event=True)
- def handle_433(self, event):
- new_nick = "%s|" % event["server"].connection_params.nickname
- event["server"].send_nick(new_nick)
-
- # we need a registered nickname for this channel
- @utils.hook("raw.received.477", default_event=True)
- def handle_477(self, event):
- channel_name = event["server"].irc_lower(event["args"][1])
- if channel_name in event["server"].channels:
- key = event["server"].attempted_join[channel_name]
- self.timers.add("rejoin", 5, channel_name=channe_name, key=key,
- server_id=event["server"].id)
-
- # someone's been kicked from a channel
- @utils.hook("raw.received.kick")
- def kick(self, event):
- user = event["server"].get_user(event["prefix"].nickname)
- target = event["args"][1]
- channel = event["server"].channels.get(event["args"][0])
- reason = event["args"].get(2)
-
- if not event["server"].is_own_nickname(target):
- target_user = event["server"].get_user(target)
- self._event(event, "kick", channel=channel, reason=reason,
- target_user=target_user, 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)
- else:
- event["server"].channels.remove(channel)
- self.events.on("self.kick").call(channel=channel, reason=reason,
- user=user, server=event["server"])
-
- # a channel has been renamed
- @utils.hook("raw.received.rename")
- def rename(self, event):
- old_name = event["args"][0]
- new_name = event["args"][1]
- channel = event["server"].channels.get(old_name)
-
- event["server"].channels.rename(old_name, new_name)
- self._event(event, "rename", channel=channel, old_name=old_name,
- new_name=new_name, reason=event["args"].get(2),
- server=event["server"])
diff --git a/modules/line_handler/__init__.py b/modules/line_handler/__init__.py
new file mode 100644
index 00000000..ef4e617b
--- /dev/null
+++ b/modules/line_handler/__init__.py
@@ -0,0 +1,257 @@
+import enum
+from src import ModuleManager, utils
+from . import channel, core, ircv3, message, user
+
+LABELED_BATCH = {
+ "labeled-response": "label",
+ "draft/labeled-response": "draft/label"
+}
+
+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.kwargs.get("default_event", False))
+ default_event = any(default_events)
+
+ kwargs = {"args": line.args, "tags": line.tags, "server": server,
+ "prefix": line.prefix, "direction": utils.Direction.RECV}
+
+ self.events.on("raw.received").on(line.command).call_unsafe(**kwargs)
+ if default_event or not hooks:
+ self.events.on("received").on(line.command).call(**kwargs)
+
+ @utils.hook("raw.received")
+ def handle_raw(self, event):
+ line = utils.irc.parse_line(event["line"])
+ if "batch" in line.tags and line.tags["batch"] in event[
+ "server"].batches:
+ server.batches[tag["batch"]].lines.append(line)
+ else:
+ self._handle(event["server"], line)
+
+ @utils.hook("raw.send")
+ def handle_send(self, event):
+ line = utils.irc.parse_line(event["line"])
+ self.events.on("raw.send").on(line.command).call_unsafe(
+ args=line.args, tags=line.tags, server=event["server"],
+ direction=utils.Direction.SEND)
+
+ # 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: %s", [event["args"][0]])
+ @utils.hook("raw.received.fail")
+ def fail(self, event):
+ command = event["args"][0]
+ error_code = event["args"][1]
+ context = event["args"][2:-1]
+ description = event["args"][-1]
+
+ self.log.warn("FAIL (%s %s) received: %s" %
+ (command, error_code, description))
+ self.events.on("received.fail").call(command=command,
+ error_code=error_code, context=context, description=description)
+
+ # 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)
+
+ # 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):
+ channels.handle_332(self.events, event)
+
+ # channel topic changed
+ @utils.hook("raw.received.topic")
+ def topic(self, event):
+ channels.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")
+ def motd_start(self, event):
+ core.motd_start(event)
+
+ @utils.hook("raw.received.372")
+ 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: %s", [event["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.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)
+
+ # 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 message
+ @utils.hook("raw.received.privmsg")
+ @utils.hook("raw.send.privmsg")
+ def privmsg(self, event):
+ message.privmsg(self.events, event)
+
+ # we've received/sent a notice
+ @utils.hook("raw.received.notice")
+ @utils.hook("raw.send.notice")
+ def notice(self, event):
+ message.notice(self.events, event)
+
+ # IRCv3 TAGMSG, used to send tags without any other information
+ @utils.hook("raw.received.tagmsg")
+ def tagmsg(self, event):
+ message.tagmsg(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["args"][0]
+ modifier, identifier = identifier[0], identifier[1:]
+ batch_type = event["args"][1]
+
+ if modifier == "+":
+ event["server"].batches[identifier] = utils.irc.IRCRecvBatch(
+ identifier, batch_type, event["tags"])
+ else:
+ batch = event["server"].batches[identifier]
+ del event["server"].batches[identifier]
+
+ add_tags = {}
+ if batch.type in LABELED_BATCH.keys():
+ tag_name = LABELED_BATCH[batch.type]
+ add_tags[tag_name] = batch.tags[tag_name]
+
+ for line in batch.lines:
+ if add_tags:
+ line.tags.update(add_tags)
+ self._handle(line)
+
+ # IRCv3 CHGHOST, a user's username and/or hostname has changed
+ @utils.hook("raw.received.chghost")
+ def chghost(self, event):
+ user.chghost(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(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(event)
+
+
+ # response to an empty mode command
+ @utils.hook("raw.received.324", default_event=True)
+ def handle_324(self, event):
+ channel.handle_324(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)
+
+ # we need a registered nickname for this channel
+ @utils.hook("raw.received.477", default_event=True)
+ def handle_477(self, event):
+ channel_name = event["server"].irc_lower(event["args"][1])
+ if channel_name in event["server"].channels:
+ key = event["server"].attempted_join[channel_name]
+ self.timers.add("rejoin", 5, channel_name=channe_name, key=key,
+ server_id=event["server"].id)
+
+ # 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/modules/line_handler/channel.py b/modules/line_handler/channel.py
new file mode 100644
index 00000000..80f42aaa
--- /dev/null
+++ b/modules/line_handler/channel.py
@@ -0,0 +1,159 @@
+from src import utils
+
+def handle_332(events, event):
+ channel = event["server"].channels.get(event["args"][1])
+ topic = event["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["prefix"].nickname)
+ channel = event["server"].channels.get(event["args"][0])
+ topic = event["args"].get(1)
+ channel.set_topic(topic)
+ events.on("received.topic", channel=channel, server=event["server"],
+ topic=topic, user=user)
+
+def handle_333(events, event):
+ channel = event["server"].channels.get(event["args"][1])
+
+ topic_setter_hostmask = event["args"][2]
+ topic_setter = utils.irc.seperate_hostmask(topic_setter_hostmask)
+ topic_time = int(event["args"][3]) if event["args"][3].isdigit() else None
+
+ channel.set_topic_setter(topic_setter.nickname, topic_setter.username,
+ topic_setter.hostname)
+ channel.set_topic_time(topic_time)
+ events.on("received.333", channel=channel, setter=topic_setter.nickname,
+ set_at=topic_time, server=event["server"])
+
+def handle_353(event):
+ channel = event["server"].channels.get(event["args"][2])
+ nicknames = event["args"].get(3).split()
+ 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 "userhost-in-names" in event["server"].agreed_capabilities:
+ hostmask = utils.irc.seperate_hostmask(nickname)
+ nickname = hostmask.nickname
+ user = event["server"].get_user(hostmask.nickname)
+ user.username = hostmask.username
+ user.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["args"][1], "n", "ahnrtu", "111")
+
+def join(events, event):
+ account = None
+ realname = None
+ channel_name = event["args"][0]
+
+ if len(event["args"]) == 3:
+ if not event["args"][1] == "*":
+ account = event["args"][1]
+ realname = event["args"][2]
+
+ if not event["server"].is_own_nickname(event["prefix"].nickname):
+ channel = event["server"].channels.get(channel_name)
+ user = event["server"].get_user(event["prefix"].nickname)
+ if not user.username and not user.hostname:
+ user.username = event["prefix"].username
+ user.hostname = event["prefix"].hostname
+
+ if account:
+ user.identified_account = account
+ user.identified_account_id = event["server"].get_user(
+ account).get_id()
+ if realname:
+ user.realname = realname
+
+ channel.add_user(user)
+ user.join_channel(channel)
+ events.on("received.join", channel=channel, user=user,
+ server=event["server"], account=account, realname=realname)
+ else:
+ channel = event["server"].channels.add(channel_name)
+ if channel.name in event["server"].attempted_join:
+ del event["server"].attempted_join[channel.name]
+ events.on("self.join").call(channel=channel, server=event["server"],
+ account=account, realname=realname)
+ channel.send_mode()
+
+def part(events, event):
+ channel = event["server"].channels.get(event["args"][0])
+ reason = event["args"].get(1)
+
+ if not event["server"].is_own_nickname(event["prefix"].nickname):
+ user = event["server"].get_user(event["prefix"].nickname)
+
+ events.on("received.part", channel=channel, reason=reason, user=user,
+ server=event["server"])
+
+ channel.remove_user(user)
+ user.part_channel(channel)
+ if not len(user.channels):
+ event["server"].remove_user(user)
+ else:
+ events.on("self.part").call(channel=channel, reason=reason,
+ server=event["server"])
+ event["server"].channels.remove(channel)
+
+def handle_324(event):
+ channel = event["server"].channels.get(event["args"][1])
+ modes = event["args"][2]
+ if modes[0] == "+" and modes[1:]:
+ for mode in modes[1:]:
+ if mode in event["server"].channel_modes:
+ channel.add_mode(mode)
+
+def handle_329(event):
+ channel = event["server"].channels.get(event["args"][1])
+ channel.creation_timestamp = int(event["args"][2])
+
+def handle_477(timers, event):
+ channel_name = event["server"].irc_lower(event["args"][1])
+ if channel_name in event["server"].channels:
+ key = event["server"].attempted_join[channel_name]
+ timers.add("rejoin", 5, channel_name=channe_name, key=key,
+ server_id=event["server"].id)
+
+def kick(events, event):
+ user = event["server"].get_user(event["prefix"].nickname)
+ target = event["args"][1]
+ channel = event["server"].channels.get(event["args"][0])
+ reason = event["args"].get(2)
+
+ if not event["server"].is_own_nickname(target):
+ target_user = event["server"].get_user(target)
+ events.on("received.kick").call(channel=channel, reason=reason,
+ target_user=target_user, 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)
+ else:
+ event["server"].channels.remove(channel)
+ events.on("self.kick").call(channel=channel, reason=reason, user=user,
+ server=event["server"])
+
+def rename(events, event):
+ old_name = event["args"][0]
+ new_name = event["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["args"].get(2), server=event["server"])
diff --git a/modules/line_handler/core.py b/modules/line_handler/core.py
new file mode 100644
index 00000000..78a0c479
--- /dev/null
+++ b/modules/line_handler/core.py
@@ -0,0 +1,134 @@
+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["args"][0])
+
+def handle_001(event):
+ event["server"].socket.set_write_throttling(True)
+ event["server"].name = event["prefix"].hostmask
+ event["server"].set_own_nickname(event["args"][0])
+ event["server"].send_whois(event["server"].nickname)
+
+def handle_005(events, event):
+ isupport_list = event["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 "multi-prefix" in event[
+ "server"].agreed_capabilities:
+ event["server"].send("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_paramatered_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"]
+
+ events.on("received.005").call(isupport=isupport,
+ server=event["server"])
+
+def motd_start(event):
+ event["server"].motd_lines.clear()
+ event["server"].motd_lines.append(event["args"][1])
+def motd_line(event):
+ event["server"].motd_lines.append(event["args"][1])
+
+def mode(events, event):
+ user = event["server"].get_user(event["prefix"].nickname)
+ target = event["args"][0]
+ is_channel = target[0] in event["server"].channel_types
+ if is_channel:
+ channel = event["server"].channels.get(target)
+ remove = False
+ args = event["args"][2:]
+ _args = args[:]
+ modes = RE_MODES.findall(event["args"][1])
+ for chunk in modes:
+ remove = chunk[0] == "-"
+ for mode in chunk[1:]:
+ if mode in event["server"].channel_modes:
+ channel.change_mode(remove, mode)
+ elif mode in event["server"].prefix_modes and len(args):
+ channel.change_mode(remove, mode, args.pop(0))
+ elif (mode in event["server"].channel_list_modes or
+ mode in event["server"].channel_paramatered_modes):
+ args.pop(0)
+ elif not remove:
+ args.pop(0)
+ events.on("received.mode.channel").call(modes=modes, mode_args=_args,
+ channel=channel, server=event["server"], user=user)
+ elif event["server"].is_own_nickname(target):
+ modes = RE_MODES.findall(event["args"][1])
+ for chunk in modes:
+ remove = chunk[0] == "-"
+ for mode in chunk[1:]:
+ event["server"].change_own_mode(remove, mode)
+ events.on("self.mode").call(modes=modes, server=event["server"])
+ event["server"].send_who(event["server"].nickname)
+
+def invite(events, event):
+ target_channel = event["args"][1]
+ user = event["server"].get_user(event["prefix"].nickname)
+ target_user = event["server"].get_user(event["args"][0])
+ events.on("received.invite").call(user=user, target_channel=target_channel,
+ server=event["server"], target_user=target_user)
+
+def handle_352(event):
+ nickname = event["args"][5]
+ if not event["server"].is_own_nickname(nickname):
+ target = event["server"].get_user(nickname)
+ else:
+ target = event["server"]
+ target.username = event["args"][2]
+ target.hostname = event["args"][3]
+
+def handle_354(event):
+ if event["args"][1] == "111":
+ nickname = event["args"][4]
+
+ if not event["server"].is_own_nickname(nickname):
+ target = event["server"].get_user(nickname)
+
+ account = event["args"][5]
+ if not account == "0":
+ target.identified_account = account
+ else:
+ target.identified_account = None
+ else:
+ target = event["server"]
+
+ target.username = event["args"][2]
+ target.hostname = event["args"][3]
+ target.realname = event["args"][6]
+
+def handle_433(event):
+ new_nick = "%s|" % event["server"].connection_params.nickname
+ event["server"].send_nick(new_nick)
diff --git a/modules/line_handler/ircv3.py b/modules/line_handler/ircv3.py
new file mode 100644
index 00000000..077b6ec3
--- /dev/null
+++ b/modules/line_handler/ircv3.py
@@ -0,0 +1,74 @@
+from src import utils
+
+CAPABILITIES = {"multi-prefix", "chghost", "invite-notify", "account-tag",
+ "account-notify", "extended-join", "away-notify", "userhost-in-names",
+ "draft/message-tags-0.2", "message-tags", "server-time", "cap-notify",
+ "batch", "draft/labeled-response", "draft/rename", "echo-message",
+ "draft/setname"}
+
+def _match_caps(capabilities):
+ return set(capabilities) & CAPABILITIES
+
+def cap(events, event):
+ capabilities = utils.parse.keyvalue(event["args"][-1])
+ subcommand = event["args"][1].lower()
+ is_multiline = len(event["args"]) > 3 and event["args"][2] == "*"
+
+ if subcommand == "ls":
+ event["server"].cap_started = True
+ event["server"].server_capabilities.update(capabilities)
+ if not is_multiline:
+ matched_caps = _match_caps(
+ list(event["server"].server_capabilities.keys()))
+ blacklisted_caps = event["server"].get_setting(
+ "blacklisted-caps", [])
+ matched_caps = list(
+ set(matched_caps)-set(blacklisted_caps))
+
+ event["server"].queue_capabilities(matched_caps)
+
+ events.on("received.cap.ls").call(
+ capabilities=event["server"].server_capabilities,
+ server=event["server"])
+
+ if event["server"].has_capability_queue():
+ event["server"].send_capability_queue()
+ else:
+ event["server"].send_capability_end()
+ elif subcommand == "new":
+ capabilities_keys = capabilities.keys()
+ event["server"].server_capabilities.update(capabilities)
+
+ matched_caps = _match_caps(list(capabilities_keys))
+ event["server"].queue_capabilities(matched_caps)
+
+ events.on("received.cap.new").call(server=event["server"],
+ capabilities=capabilities)
+
+ if event["server"].has_capability_queue():
+ event["server"].send_capability_queue()
+ elif subcommand == "del":
+ for capability in capabilities.keys():
+ event["server"].agreed_capabilities.discard(capability)
+ del event["server"].server_capabilities[capability]
+
+ events.on("received.cap.del").call(server=event["server"],
+ capabilities=capabilities)
+ elif subcommand == "ack":
+ event["server"].agreed_capabilities.update(capabilities)
+ events.on("received.cap.ack").call(capabilities=capabilities,
+ server=event["server"])
+
+ if subcommand == "ack" or subcommand == "nak":
+ for capability in capabilities:
+ event["server"].requested_capabilities.remove(capability)
+
+ if (event["server"].cap_started and
+ not event["server"].requested_capabilities and
+ not event["server"].waiting_for_capabilities()):
+ event["server"].cap_started = False
+ event["server"].send_capability_end()
+
+def authenticate(events, event):
+ events.on("received.authenticate").call(message=event["args"][0],
+ server=event["server"])
diff --git a/modules/line_handler/message.py b/modules/line_handler/message.py
new file mode 100644
index 00000000..cdea31c0
--- /dev/null
+++ b/modules/line_handler/message.py
@@ -0,0 +1,168 @@
+from src import utils
+
+def _from_self(server, direction, prefix):
+ if direction == utils.Direction.SEND:
+ if "echo-message" in server.agreed_capabilities:
+ return None
+ else:
+ return True
+ else:
+ if prefix:
+ return server.is_own_nickname(prefix.nickname)
+ else:
+ return False
+
+def privmsg(events, event):
+ from_self = _from_self(event["server"], event["direction"],
+ event.get("prefix", None))
+ if from_self == None:
+ return
+
+ user = None
+ if "prefix" in event and not from_self:
+ user = event["server"].get_user(event["prefix"].nickname)
+
+ message = event["args"][1]
+ target = event["args"][0]
+
+ # 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
+ statusmsg = []
+ while target[0] in event["server"].prefix_symbols.keys():
+ statusmsg.append(target[0])
+ target = target[1:]
+
+ channel = None
+ if target[0] in event["server"].channel_types:
+ if not target in event["server"].channels:
+ return
+ channel = event["server"].channels.get(target)
+
+ action = False
+ event_type = "message"
+ ctcp_message = utils.irc.parse_ctcp(message)
+ if ctcp_message:
+ message = ctcp_message.message
+ event_type = "ctcp.%s" % ctcp_message.command
+ if ctcp_message.command == "ACTION":
+ action = True
+ message = ctcp_message.message
+
+ if user and "account" in event["tags"]:
+ user.identified_account = event["tags"]["account"]
+ user.identified_account_id = event["server"].get_user(
+ event["tags"]["account"]).get_id()
+
+ kwargs = {"message": message, "message_split": message.split(),
+ "server": event["server"], "tags": event["tags"],
+ "action": action}
+
+ direction = "send" if from_self else "received"
+ context = "channel" if channel else "private"
+ hook = events.on(direction).on(event_type).on(context)
+
+ user_nickname = None
+ if user:
+ user_nickname = None if from_self else user.nickname
+
+ if channel:
+ hook.call(user=user, channel=channel, statusmsg=statusmsg, **kwargs)
+ channel.buffer.add_message(user_nickname, message, action,
+ event["tags"], user==None)
+ elif event["server"].is_own_nickname(target):
+ hook.call(user=user, **kwargs)
+ user.buffer.add_message(user_nickname, message, action,
+ event["tags"], False)
+ elif from_self:
+ # a message we've sent to a user
+ user = event["server"].get_user(target)
+ hook.call(user=user, **kwargs)
+ user.buffer.add_message(user_nickname, message, action,
+ event["tags"], True)
+
+def notice(events, event):
+ from_self = _from_self(event["server"], event["direction"],
+ event.get("prefix", None))
+ if from_self == None:
+ return
+
+ message = event["args"][1]
+ message_split = message.split(" ")
+ target = event["args"][0]
+
+ if "prefix" in event and (
+ not event["prefix"] or
+ not event["server"].name or
+ event["prefix"].hostmask == event["server"].name or
+ target == "*"):
+ if event["prefix"]:
+ event["server"].name = event["prefix"].hostmask
+
+ events.on("received.server-notice").call(message=message,
+ message_split=message_split, server=event["server"])
+ else:
+ user = None
+ if "prefix" in event and not from_self:
+ user = event["server"].get_user(event["prefix"].nickname)
+
+ channel = None
+ if target[0] in event["server"].channel_types:
+ channel = event["server"].channels.get(target)
+
+ direction = "send" if from_self else "received"
+ context = "channel" if channel else "private"
+ hook = events.on(direction).on("notice").on(context)
+
+ user_nickname = None
+ if user:
+ user_nickname = None if from_self else user.nickname
+
+ kwargs = {"message": message, "message_split": message_split,
+ "server": event["server"], "tags": event["tags"]}
+
+ if channel:
+ hook.call(user=user, channel=channel, **kwargs)
+ channel.buffer.add_notice(user_nickname, message, event["tags"],
+ user==None)
+ elif event["server"].is_own_nickname(target):
+ hook.call(user=user, **kwargs)
+ user.buffer.add_notice(user_nickname, message, event["tags"],
+ False)
+ elif from_self:
+ # a notice we've sent to a user
+ user = event["server"].get_user(target)
+ hook.call(user=user, **kwargs)
+ user.buffer.add_notice(user_nickname, message, event["tags"],
+ True)
+
+# IRCv3 TAGMSG, used to send tags without any other information
+@utils.hook("raw.received.tagmsg")
+def tagmsg(events, event):
+ from_self = _from_self(event["server"], event["direction"],
+ event.get("prefix", None))
+ if from_self == None:
+ return
+
+ user = None
+ if "prefix" in event and not from_self:
+ user = event["server"].get_user(event["prefix"].nickname)
+
+ target = event["args"][0]
+ channel = None
+ if target[0] in event["server"].channel_types:
+ channel = event["server"].channels.get(target)
+
+ direction = "send" if from_self else "received"
+ context = "channel" if channel else "private"
+ hook = events.on(direction).on("tagmsg").on(context)
+
+ kwargs = {"server": event["server"], "tags": event["tags"]}
+
+ if channel:
+ hook.call(user=user, channel=channel, **kwargs)
+ elif event["server"].is_own_nickname(taget):
+ hook.call(user=user, **kwargs)
+ elif from_self:
+ user = event["server"].get_user(target)
+ hook.call(user=user, **kwargs)
diff --git a/modules/line_handler/user.py b/modules/line_handler/user.py
new file mode 100644
index 00000000..f9f14af6
--- /dev/null
+++ b/modules/line_handler/user.py
@@ -0,0 +1,89 @@
+from src import utils
+
+def handle_311(event):
+ nickname = event["args"][1]
+ if event["server"].is_own_nickname(nickname):
+ target = event["server"]
+ else:
+ target = event["server"].get_user(nickname)
+ target.username = event["args"][2]
+ target.hostname = event["args"][3]
+ target.realname = event["args"][4]
+
+def quit(events, event):
+ nickname = None
+ if event["direction"] == utils.Direction.RECV:
+ nickname = event["prefix"].nickname
+ reason = event["args"].get(0)
+
+ if event["direction"] == utils.Direction.RECV:
+ nickname = event["prefix"].nickname
+ if (not event["server"].is_own_nickname(nickname) and
+ not event["prefix"].hostmask == "*"):
+ user = event["server"].get_user(nickname)
+ event["server"].remove_user(user)
+ events.on("received.quit").call(reason=reason, user=user,
+ server=event["server"])
+ else:
+ event["server"].disconnect()
+ else:
+ events.on("send.quit").call(reason=reason, server=event["server"])
+
+def nick(events, event):
+ new_nickname = event["args"].get(0)
+ if not event["server"].is_own_nickname(event["prefix"].nickname):
+ user = event["server"].get_user(event["prefix"].nickname)
+ old_nickname = user.nickname
+ user.set_nickname(new_nickname)
+ event["server"].change_user_nickname(old_nickname, new_nickname)
+
+ events.on("received.nick").call(new_nickname=new_nickname,
+ old_nickname=old_nickname, user=user, server=event["server"])
+ else:
+ old_nickname = event["server"].nickname
+ event["server"].set_own_nickname(new_nickname)
+
+ events.on("self.nick").call(server=event["server"],
+ new_nickname=new_nickname, old_nickname=old_nickname)
+
+def away(events, event):
+ user = event["server"].get_user(event["prefix"].nickname)
+ message = event["args"].get(0)
+ if message:
+ user.away = True
+ events.on("received.away.on").call(user=user, server=event["server"],
+ message=message)
+ else:
+ user.away = False
+ events.on("received.away.off").call(user=user, server=event["server"])
+
+def chghost(event):
+ nickname = event["prefix"].nickname
+ username = event["args"][0]
+ hostname = event["args"][1]
+
+ if not event["server"].is_own_nickname(nickname):
+ target = event["server"].get_user(nickname)
+ else:
+ target = event["server"]
+ target.username = username
+ target.hostname = hostname
+
+def setname(event):
+ user = event["server"].get_user(event["prefix"].nickname)
+ user.realname = event["args"][0]
+
+def account(events, event):
+ user = event["server"].get_user(event["prefix"].nickname)
+
+ if not event["args"][0] == "*":
+ user.identified_account = event["args"][0]
+ user.identified_account_id = event["server"].get_user(
+ event["args"][0]).get_id()
+ events.on("received.account.login").call(user=user,
+ server=event["server"], account=event["args"][0])
+ else:
+ user.identified_account = None
+ user.identified_account_id = None
+ events.on("received.account.logout").call(user=user,
+ server=event["server"])