From 47c2cbb0fa93e143c6add4cf1eb4012bbc37f7b2 Mon Sep 17 00:00:00 2001 From: jesopo Date: Fri, 22 Feb 2019 11:23:36 +0000 Subject: [PATCH] Split line_handler.py out in to different files for different categories --- modules/line_handler.py | 783 ------------------------------- modules/line_handler/__init__.py | 257 ++++++++++ modules/line_handler/channel.py | 159 +++++++ modules/line_handler/core.py | 134 ++++++ modules/line_handler/ircv3.py | 74 +++ modules/line_handler/message.py | 168 +++++++ modules/line_handler/user.py | 89 ++++ src/utils/__init__.py | 6 +- 8 files changed, 886 insertions(+), 784 deletions(-) delete mode 100644 modules/line_handler.py create mode 100644 modules/line_handler/__init__.py create mode 100644 modules/line_handler/channel.py create mode 100644 modules/line_handler/core.py create mode 100644 modules/line_handler/ircv3.py create mode 100644 modules/line_handler/message.py create mode 100644 modules/line_handler/user.py 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"]) diff --git a/src/utils/__init__.py b/src/utils/__init__.py index 9cb99e93..90f3e6ba 100644 --- a/src/utils/__init__.py +++ b/src/utils/__init__.py @@ -1,6 +1,10 @@ -import datetime, decimal, io, ipaddress, re, typing +import datetime, decimal, enum, io, ipaddress, re, typing from src.utils import cli, consts, irc, http, parse, security +class Direction(enum.Enum): + SEND = 0 + RECV = 1 + ISO8601_PARSE = "%Y-%m-%dT%H:%M:%S%z" def iso8601_format(dt: datetime.datetime) -> str: