diff options
Diffstat (limited to 'modules')
43 files changed, 0 insertions, 4235 deletions
diff --git a/modules/admin.py b/modules/admin.py deleted file mode 100644 index 364380d4..00000000 --- a/modules/admin.py +++ /dev/null @@ -1,208 +0,0 @@ -#--depends-on commands -#--depends-on permissions - -from src import IRCLine, ModuleManager, utils - -class Module(ModuleManager.BaseModule): - @utils.hook("received.command.nick", min_args=1) - def change_nickname(self, event): - """ - :help: Change my nickname - :usage: <nickname> - :permission: changenickname - """ - nickname = event["args_split"][0] - event["server"].send_nick(nickname) - - @utils.hook("received.command.raw", min_args=1) - def raw(self, event): - """ - :help: Send a line of raw IRC data - :usage: <raw line> - :permission: raw - """ - if IRCLine.is_human(event["args"]): - line = IRCLine.parse_human(event["args"]) - else: - line = IRCLine.parse_line(event["args"]) - line = event["server"].send(line) - - if not line == None: - event["stdout"].write("Sent: %s" % line.parsed_line.format()) - else: - event["stderr"].write("Line was filtered") - - @utils.hook("received.command.part") - def part(self, event): - """ - :help: Part from the current or given channel - :usage: [channel] - :permission: part - """ - if event["args"]: - target = event["args_split"][0] - elif event["is_channel"]: - target = event["target"].name - else: - event["stderr"].write("No channel provided") - event["server"].send_part(target) - - def _id_from_alias(self, alias): - return self.bot.database.servers.get_by_alias(alias) - def _server_from_alias(self, alias): - id, server = self._both_from_alias(alias) - return server - def _both_from_alias(self, alias): - id = self._id_from_alias(alias) - if id == None: - raise utils.EventError("Unknown server alias") - return id, self.bot.get_server_by_id(id) - - @utils.hook("received.command.reconnect") - def reconnect(self, event): - """ - :help: Reconnect to the current network - :permission: reconnect - """ - server = event["server"] - alias = str(event["server"]) - if event["args"]: - alias = event["args_split"][0] - server = self._server_from_alias(alias) - - if server: - line = server.send_quit("Reconnecting") - line.events.on("send").hook(lambda e: self.bot.reconnect( - server.id, server.connection_params)) - if not server == event["server"]: - event["stdout"].write("Reconnecting to %s" % alias) - else: - event["stdout"].write("Not connected to %s" % alias) - - @utils.hook("received.command.connect", min_args=1) - def connect(self, event): - """ - :help: Connect to a network - :usage: <server id> - :permission: connect - """ - alias = event["args_split"][0] - server = self._server_from_alias(alias) - if server: - raise utils.EventError("Already connected to %s" % str(server)) - - server = self.bot.add_server(self._id_from_alias(alias)) - event["stdout"].write("Connecting to %s" % str(server)) - - @utils.hook("received.command.disconnect") - def disconnect(self, event): - """ - :help: Disconnect from a server - :usage: [server id] - :permission: disconnect - """ - server = event["server"] - id = -1 - alias = str(event["server"]) - if event["args"]: - alias = event["args_split"][0] - id, server = self._both_from_alias(alias) - - if not server == None: - alias = str(server) - server.disconnect() - self.bot.disconnect(server) - elif id in self.bot.reconnections: - self.bot.reconnections[id].cancel() - del self.bot.reconnections[id] - else: - raise utils.EventError("Server not connected") - - event["stdout"].write("Disconnected from %s" % alias) - - @utils.hook("received.command.shutdown") - def shutdown(self, event): - """ - :help: Shutdown bot - :usage: [reason] - :permission: shutdown - """ - reason = event["args"] or "" - for server in self.bot.servers.values(): - line = server.send_quit(reason) - line.events.on("send").hook(self._shutdown_hook(server)) - def _shutdown_hook(self, server): - def shutdown(e): - server.disconnect() - self.bot.disconnect(server) - return shutdown - - @utils.hook("received.command.addserver", min_args=3) - def add_server(self, event): - """ - :help: Add a new server - :usage: <alias> <hostname>:[+]<port> <nickname>!<username>[@<bindhost>] - :permission: addserver - """ - alias = event["args_split"][0] - hostname, sep, port = event["args_split"][1].partition(":") - tls = port.startswith("+") - port = port.lstrip("+") - - if not hostname or not port or not port.isdigit(): - raise utils.EventError("Please provide <hostname>:[+]<port>") - port = int(port) - - hostmask = IRCLine.parse_hostmask(event["args_split"][2]) - nickname = hostmask.nickname - username = hostmask.username or nickname - realname = nickname - bindhost = hostmask.hostname or None - - try: - server_id = self.bot.database.servers.add(alias, hostname, port, "", - tls, bindhost, nickname, username, realname) - except Exception as e: - event["stderr"].write("Failed to add server") - self.log.error("failed to add server \"%s\"", [alias], - exc_info=True) - return - event["stdout"].write("Added server '%s'" % alias) - - @utils.hook("received.command.editserver") - @utils.kwarg("min_args", 3) - @utils.kwarg("help", "Edit server details") - @utils.kwarg("usage", "<alias> <option> <value>") - @utils.kwarg("permission", "editserver") - def edit_server(self, event): - alias = event["args_split"][0] - server_id = self._id_from_alias(alias) - if server_id == None: - raise utils.EventError("Unknown server '%s'" % alias) - - option = event["args_split"][1].lower() - value = " ".join(event["args_split"][2:]) - value_parsed = None - - if option == "hostname": - value_parsed = value - elif option == "port": - if not value.isdigit(): - raise utils.EventError("Invalid port") - value_parsed = int(value.lstrip("0")) - elif option == "tls": - value_lower = value.lower() - if not value_lower in ["yes", "no"]: - raise utils.EventError("TLS should be either 'yes' or 'no'") - value_parsed = value_lower == "yes" - elif option == "password": - value_parsed = value - elif option == "bindhost": - value_parsed = value - elif option in ["nickname", "username", "realname"]: - value_parsed = value - else: - raise utils.EventError("Unknown option '%s'" % option) - - self.bot.database.servers.edit(server_id, option, value_parsed) - event["stdout"].write("Set %s for %s" % (option, alias)) diff --git a/modules/channel_access.py b/modules/channel_access.py deleted file mode 100644 index 5502db9f..00000000 --- a/modules/channel_access.py +++ /dev/null @@ -1,91 +0,0 @@ -#--depends-on check_mode -#--depends-on commands -#--depends-on permissions - -from src import ModuleManager, utils - -class Module(ModuleManager.BaseModule): - _name = "ChanAccess" - - def _has_channel_access(self, target, user, require_access): - access = target.get_user_setting(user.get_id(), "access", []) - identified = self.exports.get_one("is-identified")(user) - - return (require_access in access or "*" in access) and identified - - def _command_check(self, event, target, require_access): - if event["is_channel"]: - if require_access: - if self._has_channel_access(target, event["user"], - require_access): - return utils.consts.PERMISSION_FORCE_SUCCESS, None - else: - return (utils.consts.PERMISSION_ERROR, - "You do not have permission to do this") - - @utils.hook("preprocess.command") - def preprocess_command(self, event): - require_access = event["hook"].get_kwarg("require_access") - if require_access: - return self._command_check(event, event["target"], require_access) - - @utils.hook("check.command.channel-access") - def check_command(self, event): - target = event["target"] - access = event["request_args"][0] - if len(event["request_args"]) > 1: - target = event["request_args"][0] - access = event["request_args"][1] - - return self._command_check(event, target, access) - - @utils.hook("received.command.access", min_args=1, channel_only=True) - def access(self, event): - """ - :help: Show/modify channel access for a user - :usage: list <nickname> - :usage: add <nickname> <permission1 permission2 ...> - :usage: remove <nickname> <permission1 permission2 ...> - :usage: set <nickname> <permission1 permission2 ...> - :require_mode: high - """ - subcommand = event["args_split"][0].lower() - target = event["server"].get_user(event["args_split"][1]) - access = event["target"].get_user_setting(target.get_id(), "access", []) - - if subcommand == "list": - event["stdout"].write("Access for %s: %s" % (target.nickname, - " ".join(access))) - elif subcommand == "set": - if not len(event["args_split"]) > 2: - raise utils.EventError("Please provide a list of permissions") - event["target"].set_user_setting(target.get_id(), "access", - event["args_split"][2:]) - elif subcommand == "add": - if not len(event["args_split"]) > 2: - raise utils.EventError("Please provide a list of permissions") - for acc in event["args_split"][2:]: - if acc in access: - raise utils.EventError("%s already has '%s' permission" % ( - target.nickname, acc)) - access.append(acc) - event["target"].set_user_setting(target.get_id(), "access", access) - event["stdout"].write("Added permission to %s: %s" % ( - target.nickname, " ".join(event["args_split"][2:]))) - elif subcommand == "remove": - if not len(event["args_split"]) > 2: - raise utils.EventError("Please provide a list of permissions") - for acc in event["args_split"][2:]: - if not acc in access: - raise utils.EventError("%s does not have '%s' permission" % - (target.nickname, acc)) - access.remove(acc) - if access: - event["target"].set_user_setting(target.get_id(), "access", - access) - else: - event["target"].del_user_setting(target.get_id(), "access") - event["stdout"].write("Removed permission from %s: %s" % ( - target.nickname, " ".join(event["args_split"][2:]))) - else: - event["stderr"].write("Unknown command '%s'" % subcommand) diff --git a/modules/channel_blacklist.py b/modules/channel_blacklist.py deleted file mode 100644 index d151bad8..00000000 --- a/modules/channel_blacklist.py +++ /dev/null @@ -1,40 +0,0 @@ -from src import EventManager, ModuleManager, utils - -@utils.export("channelset", utils.BoolSetting("blacklist", - "Refuse to join a given channel")) -class Module(ModuleManager.BaseModule): - @utils.hook("preprocess.send.join") - @utils.kwarg("priority", EventManager.PRIORITY_HIGH) - def preprocess_send_join(self, event): - if event["line"].args: - channels = event["line"].args[0].split(",") - keys = event["line"].args[1:] - - changed = False - channels_out = [] - for channel_name in filter(None, channels): - id = event["server"].channels.get_id(channel_name, create=False) - if not id == None and self.bot.database.channel_settings.get( - id, "blacklist", False): - changed = True - if keys: - keys.pop(0) - else: - key = None - if keys: - key = keys.pop(0) - channels_out.append([channel_name, key]) - - if changed: - if not channels_out: - event["line"].invalidate() - else: - channels = [c[0] for c in channels_out] - keys = [c[1] for c in channels_out if c[1]] - event["line"].args[0] = ",".join(channels) - event["line"].args[1:] = keys - - @utils.hook("received.join") - def on_join(self, event): - if event["channel"].get_setting("blacklist", False): - event["channel"].send_part() diff --git a/modules/channel_keys.py b/modules/channel_keys.py deleted file mode 100644 index 01e3c38f..00000000 --- a/modules/channel_keys.py +++ /dev/null @@ -1,55 +0,0 @@ -from src import ModuleManager, utils - -@utils.export("channelset", utils.Setting("key", "Channel key (password)", - example="hunter2")) -class Module(ModuleManager.BaseModule): - def _get_key(self, server, channel_name): - channel_id = server.channels.get_id(channel_name) - return self.bot.database.channel_settings.get(channel_id, "key", None) - def _set_key(self, channel, key): - channel.set_setting("key", key) - def _unset_key(self, channel): - channel.del_setting("key") - - @utils.hook("preprocess.send.join") - def preprocess_send_join(self, event): - if event["line"].args: - channels = event["line"].args[0].split(",") - - init_keys = False - if len(event["line"].args) > 1: - init_keys = True - keys = event["line"].args[1].split(",") - else: - keys = [] - - with_keys = {} - for channel in channels: - if keys: - with_keys[channel] = keys.pop(0) - else: - with_keys[channel] = self._get_key(event["server"], channel) - - channels_out = [] - keys_out = [] - - # sort such that channels with keys are at the start - for channel_name, key in sorted(with_keys.items(), - key=lambda item: not bool(item[1])): - channels_out.append(channel_name) - if key: - keys_out.append(key) - - event["line"].args[0] = ",".join(channels_out) - if not init_keys: - event["line"].args.append(None) - event["line"].args[1] = ",".join(keys_out) - - @utils.hook("received.324") - @utils.hook("received.mode.channel") - def on_modes(self, event): - for mode, arg in event["modes"]: - if mode == "+k": - self._set_key(event["channel"], arg) - elif mode == "-k": - self._unset_key(event["channel"]) diff --git a/modules/check_mode.py b/modules/check_mode.py deleted file mode 100644 index 9fe3f464..00000000 --- a/modules/check_mode.py +++ /dev/null @@ -1,43 +0,0 @@ -#--depends-on commands - -from src import ModuleManager, utils - -LOWHIGH = { - "low": "v", - "high": "o" -} - -@utils.export("channelset", utils.Setting("mode-low", - "Set which channel mode is considered to be 'low' access", example="v")) -@utils.export("channelset", utils.Setting("mode-high", - "Set which channel mode is considered to be 'high' access", example="o")) -class Module(ModuleManager.BaseModule): - def _check_command(self, event, channel, require_mode): - if event["is_channel"] and require_mode: - if require_mode.lower() in LOWHIGH: - require_mode = event["target"].get_setting( - "mode-%s" % require_mode.lower(), - LOWHIGH[require_mode.lower()]) - - if not event["target"].mode_or_above(event["user"], - require_mode): - return (utils.consts.PERMISSION_ERROR, - "You do not have permission to do this") - else: - return utils.consts.PERMISSION_FORCE_SUCCESS, None - - @utils.hook("preprocess.command") - def preprocess_command(self, event): - require_mode = event["hook"].get_kwarg("require_mode") - if not require_mode == None: - return self._check_command(event, event["target"], require_mode) - - @utils.hook("check.command.channel-mode") - def check_command(self, event): - target = event["target"] - mode = event["request_args"][0] - if len(event["request_args"]) > 1: - target = event["request_args"][0] - mode = event["request_args"][1] - - return self._check_command(event, target, mode) diff --git a/modules/commands/__init__.py b/modules/commands/__init__.py deleted file mode 100644 index 5a750a9b..00000000 --- a/modules/commands/__init__.py +++ /dev/null @@ -1,424 +0,0 @@ -#--depends-on config -#--depends-on permissions - -import enum, re, shlex, string, traceback, typing -from src import EventManager, IRCLine, ModuleManager, utils -from . import outs - -COMMAND_METHOD = "command-method" -COMMAND_METHODS = ["PRIVMSG", "NOTICE"] - -STR_MORE = " (more...)" -STR_MORE_LEN = len(STR_MORE.encode("utf8")) -STR_CONTINUED = "(...continued)" -WORD_BOUNDARIES = [" "] - -NON_ALPHANUMERIC = [char for char in string.printable if not char.isalnum()] - -class OutType(enum.Enum): - OUT = 1 - ERR = 2 - -class BadContextException(Exception): - def __init__(self, required_context): - self.required_context = required_context - Exception.__init__(self) - -class CommandEvent(object): - def __init__(self, command, args): - self.command = command - self.args = args - -SETTING_COMMANDMETHOD = utils.OptionsSetting(COMMAND_METHODS, COMMAND_METHOD, - "Set the method used to respond to commands") - -@utils.export("channelset", utils.Setting("command-prefix", - "Set the command prefix used in this channel", example="!")) -@utils.export("serverset", utils.Setting("command-prefix", - "Set the command prefix used on this server", example="!")) -@utils.export("serverset", SETTING_COMMANDMETHOD) -@utils.export("channelset", SETTING_COMMANDMETHOD) -@utils.export("botset", SETTING_COMMANDMETHOD) -@utils.export("channelset", utils.BoolSetting("hide-prefix", - "Disable/enable hiding prefix in command reponses")) -@utils.export("channelset", utils.BoolSetting("commands", - "Disable/enable responding to commands in-channel")) -@utils.export("channelset", utils.BoolSetting("prefixed-commands", - "Disable/enable responding to prefixed commands in-channel")) -class Module(ModuleManager.BaseModule): - @utils.hook("new.user") - @utils.hook("new.channel") - def new(self, event): - if "user" in event: - target = event["user"] - else: - target = event["channel"] - - def has_command(self, command): - return command.lower() in self.events.on("received").on( - "command").get_children() - def get_hooks(self, command): - return self.events.on("received.command").on(command - ).get_hooks() - - def is_highlight(self, server, s): - if s and s[-1] in [":", ","]: - return server.is_own_nickname(s[:-1]) - - def _command_method(self, server, target): - return target.get_setting(COMMAND_METHOD, - server.get_setting(COMMAND_METHOD, - self.bot.get_setting(COMMAND_METHOD, "PRIVMSG"))).upper() - - def _find_command_hook(self, server, target, is_channel, command, args): - if not self.has_command(command): - command_event = CommandEvent(command, args) - self.events.on("get.command").call(command=command_event, - server=server, target=target, is_channel=is_channel) - - command = command_event.command - args = command_event.args - - hook = None - args_split = [] - channel_skip = False - private_skip = False - if self.has_command(command): - for potential_hook in self.get_hooks(command): - alias_of = self._get_alias_of(potential_hook) - if alias_of: - if self.has_command(alias_of): - potential_hook = self.get_hooks(alias_of)[0] - else: - raise ValueError( - "'%s' is an alias of unknown command '%s'" - % (command.lower(), alias_of.lower())) - - if not is_channel and potential_hook.get_kwarg("channel_only", - False): - channel_skip = True - continue - if is_channel and potential_hook.get_kwarg("private_only", - False): - private_skip = True - continue - - hook = potential_hook - - if args: - argparse = hook.get_kwarg("argparse", "plain") - if argparse == "shlex": - args_split = shlex.split(args) - elif argparse == "plain": - args_split = args.split(" ") - - break - - if not hook and (private_skip or channel_skip): - raise BadContextException("channel" if channel_skip else "private") - - return hook, command, args_split - - def _check(self, context, kwargs, requests=[]): - event_hook = self.events.on(context).on("command") - - returns = [] - if requests: - for request, request_args in requests: - returns.append(event_hook.on(request).call_for_result_unsafe( - **kwargs, request_args=request_args)) - else: - returns = event_hook.call_unsafe(**kwargs) - - hard_fail = False - force_success = False - error = None - for returned in returns: - if returned: - type, message = returned - if type == utils.consts.PERMISSION_HARD_FAIL: - error = message - hard_fail = True - break - elif type == utils.consts.PERMISSION_FORCE_SUCCESS: - force_success = True - break - elif type == utils.consts.PERMISSION_ERROR: - error = message - - if hard_fail: - return False, error - elif not force_success and error: - return False, error - else: - return True, None - - - def _check_assert(self, check_kwargs, user, - check: typing.Union[utils.Check, utils.MultiCheck]): - checks = check.to_multi() # both Check and MultiCheck has this func - is_success, message = self._check("check", check_kwargs, - checks.requests()) - if not is_success: - raise utils.EventError("%s: %s" % (user.nickname, message)) - - def command(self, server, target, target_str, is_channel, user, command, - args_split, line, hook, **kwargs): - module_name = (self._get_prefix(hook) or - self.bot.modules.from_context(hook.context).title) - - stdout = outs.StdOut(module_name) - stderr = outs.StdOut(module_name) - - ret = False - has_out = False - - if hook.get_kwarg("remove_empty", True): - args_split = list(filter(None, args_split)) - - event_kwargs = {"hook": hook, "user": user, "server": server, - "target": target, "target_str": target_str, - "is_channel": is_channel, "line": line, "args_split": args_split, - "command": command, "args": " ".join(args_split), "stdout": stdout, - "stderr": stderr, "tags": {}} - event_kwargs.update(kwargs) - - check_assert = lambda check: self._check_assert(event_kwargs, user, - check) - event_kwargs["check_assert"] = check_assert - - eaten = False - - check_success, check_message = self._check("preprocess", event_kwargs) - if check_success: - new_event = self.events.on(hook.event_name).make_event(**event_kwargs) - self.log.trace("calling command '%s': %s", [command, new_event.kwargs]) - - try: - hook.call(new_event) - except utils.EventError as e: - stderr.write(str(e)) - eaten = new_event.eaten - else: - if check_message: - stderr.write("%s: %s" % (user.nickname, check_message)) - - self._check("postprocess", event_kwargs) - # postprocess - send stdout/stderr and typing tag - - return eaten - - @utils.hook("postprocess.command") - @utils.kwarg("priority", EventManager.PRIORITY_LOW) - def postprocess(self, event): - type = None - obj = None - if event["stdout"].has_text(): - type = OutType.OUT - obj = event["stdout"] - elif event["stderr"].has_text(): - type = OutType.ERR - obj = event["stderr"] - else: - return - self._out(event["server"], event["target"], event["target_str"], obj, - type, event["tags"]) - - def _out(self, server, target, target_str, obj, type, tags): - if type == OutType.OUT: - color = utils.consts.GREEN - else: - color = utils.consts.RED - - line_str = obj.pop() - if obj.prefix: - line_str = "[%s] %s" % ( - utils.irc.color(obj.prefix, color), line_str) - method = self._command_method(server, target) - - if not method in ["PRIVMSG", "NOTICE"]: - raise ValueError("Unknown command-method '%s'" % method) - - line = IRCLine.ParsedLine(method, [target_str, line_str], - tags=tags) - valid, trunc = line.truncate(server.hostmask(), - margin=STR_MORE_LEN) - - if trunc: - if not trunc[0] in WORD_BOUNDARIES: - for boundary in WORD_BOUNDARIES: - left, *right = valid.rsplit(boundary, 1) - if right: - valid = left - trunc = right[0]+trunc - obj.insert("%s %s" % (STR_CONTINUED, trunc)) - valid = valid+STR_MORE - line = IRCLine.parse_line(valid) - server.send(line) - - @utils.hook("preprocess.command") - def _check_min_args(self, event): - min_args = event["hook"].get_kwarg("min_args") - if min_args and len(event["args_split"]) < min_args: - usage = self._get_usage(event["hook"], event["command"], - event["command_prefix"]) - error = None - if usage: - error = "Not enough arguments, usage: %s" % usage - else: - error = "Not enough arguments (minimum: %d)" % min_args - return utils.consts.PERMISSION_HARD_FAIL, error - - def _command_prefix(self, server, channel): - return channel.get_setting("command-prefix", - server.get_setting("command-prefix", "!")) - - @utils.hook("received.message.channel", priority=EventManager.PRIORITY_LOW) - def channel_message(self, event): - commands_enabled = event["channel"].get_setting("commands", True) - if not commands_enabled: - return - - command_prefix = self._command_prefix(event["server"], event["channel"]) - command = None - args = "" - if event["message_split"][0].startswith(command_prefix): - if not event["channel"].get_setting("prefixed-commands",True): - return - command = event["message_split"][0].replace( - command_prefix, "", 1).lower() - if " " in event["message"]: - args = event["message"].split(" ", 1)[1] - elif len(event["message_split"]) > 1 and self.is_highlight( - event["server"], event["message_split"][0]): - command = event["message_split"][1].lower() - if event["message"].count(" ") > 1: - args = event["message"].split(" ", 2)[2] - - hook = None - args_split = [] - if command: - try: - hook, command, args_split = self._find_command_hook( - event["server"], event["channel"], True, command, args) - except BadContextException: - event["channel"].send_message( - "%s: That command is not valid in a channel" % - event["user"].nickname) - return - - if hook: - if event["action"]: - return - - if hook: - self.command(event["server"], event["channel"], - event["target_str"], True, event["user"], command, - args_split, event["line"], hook, - command_prefix=command_prefix, - buffer_line=event["buffer_line"]) - else: - self.events.on("unknown.command").call(server=event["server"], - target=event["channel"], user=event["user"], - command=command, command_prefix=command_prefix, - is_channel=True) - else: - regex_hooks = self.events.on("command.regex").get_hooks() - for hook in regex_hooks: - if event["action"] and hook.get_kwarg("ignore_action", True): - continue - - pattern = hook.get_kwarg("pattern", None) - if pattern: - match = re.search(pattern, event["message"]) - if match: - command = hook.get_kwarg("command", "") - res = self.command(event["server"], event["channel"], - event["target_str"], True, event["user"], command, - "", event["line"], hook, match=match, - message=event["message"], command_prefix="", - action=event["action"], - buffer_line=event["buffer_line"]) - - if res: - break - - @utils.hook("received.message.private", priority=EventManager.PRIORITY_LOW) - def private_message(self, event): - if event["message_split"] and not event["action"]: - command = event["message_split"][0].lower() - - # this should help catch commands when people try to do prefixed - # commands ('!help' rather than 'help') in PM - command = command.lstrip("".join(NON_ALPHANUMERIC)) - - args = "" - if " " in event["message"]: - args = event["message"].split(" ", 1)[1] - - try: - hook, command, args_split = self._find_command_hook( - event["server"], event["user"], False, command, args) - except BadContextException: - event["user"].send_message( - "That command is not valid in a PM") - return - - if hook: - self.command(event["server"], event["user"], - event["user"].nickname, False, event["user"], command, - args_split, event["line"], hook, command_prefix="", - buffer_line=event["buffer_line"]) - else: - self.events.on("unknown.command").call(server=event["server"], - target=event["user"], user=event["user"], command=command, - command_prefix="", is_channel=False) - - def _get_usage(self, hook, command, command_prefix=""): - command = "%s%s" % (command_prefix, command) - usages = hook.get_kwargs("usage") - - if usages: - return " | ".join( - "%s %s" % (command, usage) for usage in usages) - return None - - def _get_prefix(self, hook): - return hook.get_kwarg("prefix", None) - def _get_alias_of(self, hook): - return hook.get_kwarg("alias_of", None) - - @utils.hook("send.stdout") - def _stdout(self, event): - self._send_out(event, OutType.OUT) - @utils.hook("send.stderr") - def _stderr(self, event): - self._send_out(event, OutType.ERR) - - def _send_out(self, event, type): - target = event["target"] - stdout = outs.StdOut(event["module_name"]) - stdout.write(event["message"]) - if event.get("hide_prefix", False): - stdout.prefix = None - - target_str = event.get("target_str", target.name) - self._out(event["server"], target, target_str, stdout, - type, {}) - - @utils.hook("check.command.self") - def check_command_self(self, event): - if event["server"].irc_lower(event["request_args"][0] - ) == event["user"].name: - return utils.consts.PERMISSION_FORCE_SUCCESS, None - else: - return (utils.consts.PERMISSION_ERROR, - "You do not have permission to do this") - - @utils.hook("check.command.is-channel") - def check_command_is_channel(self, event): - if event["is_channel"]: - return utils.consts.PERMISSION_FORCE_SUCCESS, None - else: - return (utils.consts.PERMISSION_ERROR, - "This command can only be used in-channel") diff --git a/modules/commands/outs.py b/modules/commands/outs.py deleted file mode 100644 index e82ceefd..00000000 --- a/modules/commands/outs.py +++ /dev/null @@ -1,28 +0,0 @@ -import re -from src import IRCLine, utils - -class StdOut(object): - def __init__(self, prefix): - self.prefix = prefix - self._lines = [] - self._assured = False - - def assure(self): - self._assured = True - - def write(self, text): - self.write_lines( - text.replace("\r", "").replace("\n\n", "\n").split("\n")) - def write_lines(self, lines): - self._lines += list(filter(None, lines)) - - def get_all(self): - return self._lines.copy() - def pop(self): - return self._lines.pop(0) - def insert(self, text): - self._lines.insert(0, text) - - def has_text(self): - return bool(self._lines) - diff --git a/modules/config.py b/modules/config.py deleted file mode 100644 index 710a5dd6..00000000 --- a/modules/config.py +++ /dev/null @@ -1,244 +0,0 @@ -#--depends-on channel_access -#--depends-on check_mode -#--depends-on commands -#--depends-on permissions - -import enum -from src import ModuleManager, utils - -class ConfigInvalidValue(Exception): - def __init__(self, message: str=None): - self.message = message -class ConfigSettingInexistent(Exception): - pass - -class ConfigResults(enum.Enum): - Changed = 1 - Retrieved = 2 - Removed = 3 - Unchanged = 4 - -class ConfigResult(object): - def __init__(self, result, data=None): - self.result = result - self.data = data - -class ConfigChannelTarget(object): - def __init__(self, bot, server, channel_name): - self._bot = bot - self._server = server - self._channel_name = channel_name - def _get_id(self): - return self._server.channels.get_id(self._channel_name) - def set_setting(self, setting, value): - channel_id = self._get_id() - self._bot.database.channel_settings.set(channel_id, setting, value) - def get_setting(self, setting, default=None): - channel_id = self._get_id() - return self._bot.database.channel_settings.get(channel_id, setting, - default) - def del_setting(self, setting): - channel_id = self._get_id() - self._bot.database.channel_settings.delete(channel_id, setting) - - def get_user_setting(self, user_id, setting, default=None): - return self._bot.database.user_channel_settings.get(user_id, - self._get_id(), setting, default) - -class Module(ModuleManager.BaseModule): - def _to_context(self, server, channel, user, context_desc): - context_desc_lower = context_desc.lower() - - if context_desc == "*": - if channel == user: - # we're in PM - return user, "set", None - else: - #we're in a channel - return channel, "channelset", None - elif server.is_channel(context_desc): - return context_desc, "channelset", context_desc - elif server.irc_lower(context_desc) == user.nickname_lower: - return user, "set", None - elif "user".startswith(context_desc_lower): - return user, "set", None - elif "channel".startswith(context_desc_lower): - return channel, "channelset", None - elif "server".startswith(context_desc_lower): - return server, "serverset", None - elif "bot".startswith(context_desc_lower): - return self.bot, "botset", None - else: - raise ValueError() - - @utils.hook("preprocess.command") - def preprocess_command(self, event): - require_setting = event["hook"].get_kwarg("require_setting", None) - if not require_setting == None: - require_setting_unless = event["hook"].get_kwarg( - "require_setting_unless", None) - if not require_setting_unless == None: - require_setting_unless = int(require_setting_unless) - if len(event["args_split"]) >= require_setting_unless: - return - - context, _, require_setting = require_setting.rpartition(":") - require_setting = require_setting.lower() - channel = None - if event["is_channel"]: - channel = event["target"] - - context = context or "user" - target, setting_context, _ = self._to_context(event["server"], - channel, event["user"], context) - - export_settings = self._get_export_setting(setting_context) - setting_info = export_settings.get(require_setting, None) - if setting_info: - value = target.get_setting(require_setting, None) - if value == None: - example = setting_info.example or "<value>" - if context == "user": - context = event["user"].nickname - elif context == "channel" and not channel == None: - context = channel.name - else: - context = context[0] - - error = "Please set %s, e.g.: %sconfig %s %s %s" % ( - require_setting, event["command_prefix"], context, - require_setting, example) - return utils.consts.PERMISSION_ERROR, error - - def _get_export_setting(self, context): - settings = self.exports.get_all(context) - return {setting.name.lower(): setting for setting in settings} - - def _config(self, export_settings, target, setting, value=None): - if not value == None: - setting_object = export_settings[setting] - try: - validated_value = setting_object.parse(value) - except utils.settings.SettingParseException as e: - raise ConfigInvalidValue(str(e)) - - if not validated_value == None: - existing_value = target.get_setting(setting, None) - if existing_value == validated_value: - return ConfigResult(ConfigResults.Unchanged) - else: - target.set_setting(setting, validated_value) - formatted_value = setting_object.format(validated_value) - return ConfigResult(ConfigResults.Changed, formatted_value) - else: - raise ConfigInvalidValue() - else: - unset = False - if setting.startswith("-"): - setting = setting[1:] - unset = True - - existing_value = target.get_setting(setting, None) - if not existing_value == None: - if unset: - target.del_setting(setting) - return ConfigResult(ConfigResults.Removed) - else: - formatted = export_settings[setting].format(existing_value) - return ConfigResult(ConfigResults.Retrieved, formatted) - else: - raise ConfigSettingInexistent() - - @utils.hook("received.command.c", alias_of="config") - @utils.hook("received.command.config") - @utils.kwarg("min_args", 1) - @utils.kwarg("help", "Change config options") - @utils.kwarg("usage", "<context>[:name] [-][setting [value]]") - def config(self, event): - arg_count = len(event["args_split"]) - context_desc, _, name = event["args_split"][0].partition(":") - - setting = None - value = None - if arg_count > 1: - setting = event["args_split"][1].lower() - if arg_count > 2: - value = " ".join(event["args_split"][2:]) - - try: - target, context, name_override = self._to_context(event["server"], - event["target"], event["user"], context_desc) - except ValueError: - raise utils.EventError( - "Unknown context '%s'. Please provide " - "'user', 'channel', 'server' or 'bot'" % context_desc) - - name = name_override or name - - permission_check = utils.Check("permission", "config") - - if context == "set": - if name: - event["check_assert"]( - utils.Check("self", name)|permission_check) - target = event["server"].get_user(name) - else: - target = event["user"] - elif context == "channelset": - if name: - if name in event["server"].channels: - target = event["server"].channels.get(name) - else: - target = ConfigChannelTarget(self.bot, event["server"], - name) - else: - if event["is_channel"]: - target = event["target"] - else: - raise utils.EventError( - "Cannot change config for current channel when in " - "private message") - event["check_assert"](permission_check| - utils.Check("channel-access", target, "config")| - utils.Check("channel-mode", target, "o")) - elif context == "serverset" or context == "botset": - event["check_assert"](permission_check) - - export_settings = self._get_export_setting(context) - if not setting == None: - if not setting.lstrip("-") in export_settings: - raise utils.EventError("Setting not found") - - try: - result = self._config(export_settings, target, setting, value) - except ConfigInvalidValue as e: - if not e.message == None: - raise utils.EventError("Invalid value: %s" % e.message) - - example = export_settings[setting].get_example() - if not example == None: - raise utils.EventError("Invalid value. %s" % - example) - else: - raise utils.EventError("Invalid value") - except ConfigSettingInexistent: - raise utils.EventError("Setting not set") - - for_str = "" - if name_override: - for_str = " for %s" % name_override - if result.result == ConfigResults.Changed: - event["stdout"].write("Config '%s'%s set to %s" % - (setting, for_str, result.data)) - elif result.result == ConfigResults.Retrieved: - event["stdout"].write("%s%s: %s" % (setting, for_str, - result.data)) - elif result.result == ConfigResults.Removed: - event["stdout"].write("Unset setting '%s'%s" % - (setting.lstrip("-"), for_str)) - elif result.result == ConfigResults.Unchanged: - event["stdout"].write("Config '%s'%s unchanged" % - (setting, for_str)) - else: - event["stdout"].write("Available config: %s" % - ", ".join(export_settings.keys())) diff --git a/modules/ctcp.py b/modules/ctcp.py deleted file mode 100644 index 678cf833..00000000 --- a/modules/ctcp.py +++ /dev/null @@ -1,29 +0,0 @@ -#--depends-on config - -import datetime -from src import IRCBot, ModuleManager, utils - - -@utils.export("serverset", utils.BoolSetting("ctcp-responses", - "Set whether I respond to CTCPs on this server")) -class Module(ModuleManager.BaseModule): - @utils.hook("received.ctcp.request.version") - def ctcp_version(self, event): - default = "BitBot %s (%s)" % (IRCBot.VERSION, IRCBot.SOURCE) - - event["user"].send_ctcp_response("VERSION", - self.bot.config.get("ctcp-version", default)) - - @utils.hook("received.ctcp.request.source") - def ctcp_source(self, event): - event["user"].send_ctcp_response("SOURCE", - self.bot.config.get("ctcp-source", IRCBot.SOURCE)) - - @utils.hook("received.ctcp.request.ping") - def ctcp_ping(self, event): - event["user"].send_ctcp_response("PING", event["message"]) - - @utils.hook("received.ctcp.request.time") - def ctcp_time(self, event): - event["user"].send_ctcp_response("TIME", - datetime.datetime.now().strftime("%c")) diff --git a/modules/deferred_read.py b/modules/deferred_read.py deleted file mode 100644 index c891e860..00000000 --- a/modules/deferred_read.py +++ /dev/null @@ -1,23 +0,0 @@ -from src import EventManager, ModuleManager, utils - -# postpone parsing SOME lines until after 001 - -class Module(ModuleManager.BaseModule): - @utils.hook("new.server") - def new_server(self, event): - event["server"]._deferred_read = [] - - @utils.hook("raw.received.001", priority=EventManager.PRIORITY_LOW) - def on_001(self, event): - lines = event["server"]._deferred_read[:] - event["server"]._deferred_read.clear() - for line in lines: - self.events.on("raw.received").call(line=line, - server=event["server"]) - - @utils.hook("raw.received.mode", priority=EventManager.PRIORITY_HIGH) - def defer(self, event): - if not event["server"].connected: - event.eat() - event["server"]._deferred_read.append(event["line"]) - diff --git a/modules/fake_echo.py b/modules/fake_echo.py deleted file mode 100644 index bb7fbf43..00000000 --- a/modules/fake_echo.py +++ /dev/null @@ -1,13 +0,0 @@ -from src import EventManager, IRCLine, ModuleManager, utils - -class Module(ModuleManager.BaseModule): - @utils.hook("raw.send.privmsg", priority=EventManager.PRIORITY_MONITOR) - @utils.hook("raw.send.notice", priority=EventManager.PRIORITY_MONITOR) - def send_message(self, event): - our_hostmask = IRCLine.parse_hostmask(event["server"].hostmask()) - - echo = IRCLine.ParsedLine(event["line"].command, event["line"].args, - source=our_hostmask, tags=event["line"].tags) - echo.id = event["line"].id - - self.events.on("raw.received").call(line=echo, server=event["server"]) diff --git a/modules/format_activity.py b/modules/format_activity.py deleted file mode 100644 index 1d93eb94..00000000 --- a/modules/format_activity.py +++ /dev/null @@ -1,285 +0,0 @@ -import datetime -from src import EventManager, ModuleManager, utils - -class Module(ModuleManager.BaseModule): - def _color(self, nickname): - return utils.irc.hash_colorize(nickname) - - def _event(self, type, server, line, context, minimal=None, pretty=None, - channel=None, user=None, **kwargs): - self.events.on("formatted").on(type).call(server=server, - context=context, line=line, channel=channel, user=user, - minimal=minimal, pretty=pretty, **kwargs) - - def _mode_symbols(self, user, channel, server): - modes = list(channel.get_user_modes(user)) - if modes: - modes = [mode for mode in modes if mode in server.prefix_modes] - modes.sort(key=lambda x: list(server.prefix_modes.keys()).index(x)) - return server.prefix_modes[modes[0]] - return "" - - def _privmsg(self, event, channel, user): - symbols = "" - if channel: - symbols = self._mode_symbols(user, channel, event["server"]) - - if event["action"]: - format = "* %s%s %s" - else: - format = "<%s%s> %s" - - minimal = format % ("", user.nickname, event["message"]) - normal = format % (symbols, user.nickname, event["message"]) - pretty = format % (symbols, self._color(user.nickname), - event["message"]) - - return minimal, normal, pretty - - @utils.hook("send.message.channel") - @utils.hook("received.message.channel") - def channel_message(self, event): - minimal, normal, pretty = self._privmsg(event, event["channel"], - event["user"]) - - self._event("message.channel", event["server"], normal, - event["channel"].name, channel=event["channel"], user=event["user"], - parsed_line=event["line"], minimal=minimal, pretty=pretty) - - def _on_notice(self, event, user, channel): - symbols = "" - if channel: - symbols = self._mode_symbols(user, channel, event["server"]) - - format = "-%s%s- %s" - minimal = format % ("", user.nickname, event["message"]) - normal = format % (symbols, user.nickname, event["message"]) - pretty = format % (symbols, self._color(user.nickname), - event["message"]) - - return minimal, normal, pretty - def _channel_notice(self, event, user, channel): - minimal, normal, pretty = self._on_notice(event, user, channel) - self._event("notice.channel", event["server"], normal, - event["channel"].name, parsed_line=event["line"], channel=channel, - user=event["user"], minimal=minimal, pretty=pretty) - - @utils.hook("received.notice.channel") - @utils.hook("send.notice.channel") - def channel_notice(self, event): - self._channel_notice(event, event["user"], event["channel"]) - - @utils.hook("received.notice.private") - @utils.hook("send.notice.private") - def private_notice(self, event): - minimal, normal, pretty = self._on_notice(event, event["user"], None) - self._event("notice.private", event["server"], normal, - event["target"].nickname, parsed_line=event["line"], - user=event["user"], minimal=minimal, pretty=pretty) - - def _on_join(self, event, user): - channel_name = event["channel"].name - - minimal = "%s joined %s" % (user.nickname, channel_name) - - normal_format = "- %s (%s) joined %s" - normal = normal_format % (user.nickname, user.userhost(), channel_name) - pretty = normal_format % (self._color(user.nickname), user.userhost(), - channel_name) - - self._event("join", event["server"], normal, event["channel"].name, - channel=event["channel"], user=user, minimal=minimal, - pretty=pretty) - @utils.hook("received.join") - def join(self, event): - self._on_join(event, event["user"]) - @utils.hook("self.join") - def self_join(self, event): - self._on_join(event, event["server"].get_user(event["server"].nickname)) - - @utils.hook("received.chghost") - def _on_chghost(self, event): - username = event["username"] - hostname = event["hostname"] - - format = "%s changed host to %s@%s" - minimal = format % (event["user"].nickname, username, hostname) - - normal_format = "- %s" % format - normal = normal_format % (event["user"].nickname, username, hostname) - pretty = normal_format % (self._color(event["user"].nickname), username, - hostname) - - self._event("chghost", event["server"], normal, None, - user=event["user"], minimal=minimal, pretty=pretty) - - def _on_part(self, event, user): - channel_name = event["channel"].name - reason = event["reason"] - reason = "" if not reason else " (%s)" % reason - - format = "%s left %s%s" - minimal = format % (user.nickname, channel_name, reason) - - normal_format = "- %s" % format - normal = normal_format % (user.nickname, channel_name, reason) - pretty = normal_format % (self._color(user.nickname), channel_name, - reason) - - self._event("part", event["server"], normal, event["channel"].name, - channel=event["channel"], user=user, minimal=minimal, pretty=pretty) - @utils.hook("received.part") - def part(self, event): - self._on_part(event, event["user"]) - @utils.hook("self.part") - def self_part(self, event): - self._on_part(event, event["server"].get_user(event["server"].nickname)) - - def _on_nick(self, event, user): - old_nickname = event["old_nickname"] - new_nickname = event["new_nickname"] - - format = "%s changed nickname to %s" - minimal = format % (old_nickname, new_nickname) - - normal_format = "- %s" % format - normal = normal_format % (old_nickname, new_nickname) - pretty = normal_format % ( - self._color(old_nickname), self._color(new_nickname)) - - self._event("nick", event["server"], normal, None, user=user, - minimal=minimal, pretty=pretty) - @utils.hook("received.nick") - def nick(self, event): - self._on_nick(event, event["user"]) - @utils.hook("self.nick") - def self_nick(self, event): - self._on_nick(event, event["server"].get_user(event["server"].nickname)) - - @utils.hook("received.server-notice") - def server_notice(self, event): - line = "(server notice) %s" % event["message"] - self._event("server-notice", event["server"], line, None) - - @utils.hook("received.invite") - def invite(self, event): - format = "%s invited %s to %s" - - sender = event["user"].nickname - target = event["target_user"].nickname - channel_name = event["target_channel"] - - minimal = format % (sender, target, channel_name) - normal = "- %s" % minimal - pretty = format % (self._color(sender), target, channel_name) - - self._event("invite", event["server"], normal, event["target_channel"], - minimal=minimal, pretty=pretty) - - @utils.hook("received.mode.channel") - def mode(self, event): - modes = "".join(event["modes_str"]) - args = " ".join(event["args_str"]) - if args: - args = " %s" % args - - format = "%s set mode %s%s" - minimal = format % (event["user"].nickname, modes, args) - - normal_format = "- %s" % format - normal = normal_format % (event["user"].nickname, modes, args) - pretty = normal_format % (self._color(event["user"].nickname), modes, - args) - - self._event("mode.channel", event["server"], normal, - event["channel"].name, channel=event["channel"], user=event["user"], - minimal=minimal, pretty=pretty) - - def _on_topic(self, event, nickname, action, topic): - format = "topic %s by %s: %s" - minimal = format % (action, nickname, topic) - - normal_format = "- %s" % format - normal = normal_format % (action, nickname, topic) - pretty = normal_format % (action, self._color(nickname), topic) - - self._event("topic", event["server"], normal, event["channel"].name, - channel=event["channel"], user=event.get("user", None), - minimal=minimal, pretty=pretty) - @utils.hook("received.topic") - def on_topic(self, event): - self._on_topic(event, event["user"].nickname, "changed", - event["topic"]) - @utils.hook("received.333") - def on_333(self, event): - self._on_topic(event, event["setter"].nickname, "set", - event["channel"].topic) - - dt = utils.datetime.iso8601_format( - utils.datetime.datetime_timestamp(event["set_at"])) - - minimal = "topic set at %s" % dt - normal = "- %s" % minimal - - self._event("topic-timestamp", event["server"], normal, - event["channel"].name, channel=event["channel"], minimal=minimal) - - def _on_kick(self, event, kicked_nickname): - sender_nickname = event["user"].nickname - channel_name = event["channel"].name - - reason = "" - if event["reason"]: - reason = " (%s)" % event["reason"] - - format = "%s kicked %s from %s%s" - minimal = format % (sender_nickname, kicked_nickname, channel_name, - reason) - - normal_format = "- %s" % format - normal = normal_format % (sender_nickname, kicked_nickname, - channel_name, reason) - pretty = normal_format % (self._color(sender_nickname), - self._color(kicked_nickname), channel_name, reason) - - self._event("kick", event["server"], normal, event["channel"].name, - channel=event["channel"], user=event.get("user", None), - minimal=minimal, pretty=pretty) - @utils.hook("received.kick") - def kick(self, event): - self._on_kick(event, event["target_user"].nickname) - @utils.hook("self.kick") - def self_kick(self, event): - self._on_kick(event, event["server"].nickname) - - def _quit(self, event, user, reason): - reason = "" if not reason else " (%s)" % reason - - format = "%s quit%s" - minimal = format % (user.nickname, reason) - - normal_format = "- %s" % format - normal = normal_format % (user.nickname, reason) - pretty = normal_format % (self._color(user.nickname), reason) - - self._event("quit", event["server"], normal, None, user=user, - minimal=minimal, pretty=pretty) - @utils.hook("received.quit") - def on_quit(self, event): - self._quit(event, event["user"], event["reason"]) - @utils.hook("send.quit") - def send_quit(self, event): - self._quit(event, event["server"].get_user(event["server"].nickname), - event["reason"]) - - @utils.hook("received.rename") - def rename(self, event): - line = "%s was renamed to %s" % (event["old_name"], event["new_name"]) - self._event("rename", event["server"], line, event["old_name"], - channel=event["channel"]) - - @utils.hook("received.376") - def motd_end(self, event): - for line in event["server"].motd_lines: - line = "[MOTD] %s" % line - self._event("motd", event["server"], line, None) diff --git a/modules/help.py b/modules/help.py deleted file mode 100644 index 58659d9d..00000000 --- a/modules/help.py +++ /dev/null @@ -1,124 +0,0 @@ -#--depends-on commands -from src import IRCBot, ModuleManager, utils - -class Module(ModuleManager.BaseModule): - def _get_help(self, hook): - return hook.get_kwarg("help", None) or hook.docstring.description - def _get_usage(self, hook, command, command_prefix=""): - command = "%s%s" % (command_prefix, command) - usage = hook.get_kwarg("usage", None) - if usage: - usages = [usage] - else: - usages = hook.docstring.var_items.get("usage", None) - - if usages: - return " | ".join( - "%s %s" % (command, usage) for usage in usages) - return usage - - def _get_hook(self, command): - hooks = self.events.on("received.command").on(command).get_hooks() - if hooks: - return hooks[0] - else: - return None - - @utils.hook("received.command.help") - def help(self, event): - if event["args"]: - command = event["args_split"][0].lower() - hook = self._get_hook(command) - - if hook == None: - raise utils.EventError("Unknown command '%s'" % command) - help = self._get_help(hook) - usage = self._get_usage(hook, command, event["command_prefix"]) - - out = help - if usage: - out += ". Usage: %s" % usage - - if out: - event["stdout"].write("%s: %s" % (command, out)) - else: - event["stderr"].write("No help for %s" % command) - else: - modules_command = utils.irc.bold( - "%smodules" % event["command_prefix"]) - commands_command = utils.irc.bold( - "%scommands <module>" % event["command_prefix"]) - help_command = utils.irc.bold( - "%shelp <command>" % event["command_prefix"]) - - event["stdout"].write("I'm %s. use '%s' to list modules, " - "'%s' to list commands and " - "'%s' to see help text for a command" % - (IRCBot.URL, modules_command, commands_command, help_command)) - - def _all_command_hooks(self): - all_hooks = {} - for child_name in self.events.on("received.command").get_children(): - hooks = self.events.on("received.command").on(child_name - ).get_hooks() - if hooks: - all_hooks[child_name.lower()] = hooks[0] - return all_hooks - - @utils.hook("received.command.modules") - def modules(self, event): - contexts = {} - for command, command_hook in self._all_command_hooks().items(): - if not command_hook.context in contexts: - module = self.bot.modules.from_context(command_hook.context) - contexts[module.context] = module.name - - modules_available = sorted(contexts.values()) - event["stdout"].write("Modules: %s" % ", ".join(modules_available)) - - @utils.hook("received.command.commands", min_args=1) - def commands(self, event): - module_name = event["args_split"][0] - module = self.bot.modules.from_name(module_name) - if module == None: - raise utils.EventError("No such module '%s'" % module_name) - - commands = [] - for command, command_hook in self._all_command_hooks().items(): - if command_hook.context == module.context: - commands.append(command) - - event["stdout"].write("Commands for %s module: %s" % ( - module.name, ", ".join(commands))) - - @utils.hook("received.command.which") - @utils.kwarg("min_args", 1) - @utils.kwarg("help", "Find where a command is provided") - @utils.kwarg("usage", "<command>") - def which(self, event): - command = event["args_split"][0].lower() - hooks = self.events.on("received.command").on(command).get_hooks() - if not hooks: - raise utils.EventError("Unknown command '%s'" % command) - - hook = hooks[0] - module = self.bot.modules.from_context(hook.context) - event["stdout"].write("%s%s is provided by %s.%s" % ( - event["command_prefix"], command, module.name, - hook.function.__name__)) - - @utils.hook("received.command.apropos") - @utils.kwarg("min_args", 1) - @utils.kwarg("help", "Show commands with a given string in them") - @utils.kwarg("usage", "<query>") - def apropos(self, event): - query = event["args_split"][0] - query_lower = query.lower() - - commands = [] - for command, hook in self._all_command_hooks().items(): - if query_lower in command.lower(): - commands.append("%s%s" % (event["command_prefix"], command)) - if commands: - event["stdout"].write("Apropos of '%s': %s" % - (query, ", ".join(commands))) diff --git a/modules/ignore.py b/modules/ignore.py deleted file mode 100644 index 11ad58f3..00000000 --- a/modules/ignore.py +++ /dev/null @@ -1,163 +0,0 @@ -#--depends-on commands -#--depends-on permissions - -from src import EventManager, ModuleManager, utils - -class Module(ModuleManager.BaseModule): - def _user_ignored(self, user): - return user.get_setting("ignore", False) - def _user_command_ignored(self, user, command): - return user.get_setting("ignore-%s" % command, False) - def _user_channel_ignored(self, channel, user): - return channel.get_user_setting(user.get_id(), "ignore", False) - def _server_command_ignored(self, server, command): - return server.get_setting("ignore-%s" % command, False) - - def _is_command_ignored(self, server, user, command): - if self._user_command_ignored(user, command): - return True - elif self._server_command_ignored(server, command): - return True - - @utils.hook("received.message.private") - @utils.hook("received.message.channel") - @utils.hook("received.notice.private") - @utils.hook("received.notice.channel") - @utils.kwarg("priority", EventManager.PRIORITY_HIGH) - def message(self, event): - if self._user_ignored(event["user"]): - event.eat() - elif event["is_channel"] and self._user_channel_ignored(event["target"], - event["user"]): - event.eat() - - @utils.hook("preprocess.command") - def preprocess_command(self, event): - if self._user_ignored(event["user"]): - return utils.consts.PERMISSION_HARD_FAIL, None - elif event["is_channel"] and self._user_channel_ignored(event["target"], - event["user"]): - return utils.consts.PERMISSION_HARD_FAIL, None - elif self._is_command_ignored(event["server"], event["user"], - event["command"]): - return utils.consts.PERMISSION_HARD_FAIL, None - - @utils.hook("received.command.ignore", min_args=1) - def ignore(self, event): - """ - :help: Ignore commands from a given user - :usage: <nickname> [command] - :permission: ignore - """ - time, args = utils.parse.timed_args(event["args_split"], 1) - - setting = "ignore" - for_str = "" - if len(args) > 1: - command = args[1].lower() - setting = "ignore-%s" % command - for_str = " for '%s'" % command - - user = event["server"].get_user(args[0]) - if user.get_setting(setting, False): - event["stderr"].write("I'm already ignoring '%s'%s" % - (user.nickname, for_str)) - else: - user.set_setting(setting, True) - event["stdout"].write("Now ignoring '%s'%s" % - (user.nickname, for_str)) - - if not time == None: - self.timers.add_persistent("unignore", time, - user_id=user.get_id(), setting=setting) - @utils.hook("timer.unignore") - def _timer_unignore(self, event): - self.bot.database.user_settings.delete( - event["user_id"], event["setting"]) - - @utils.hook("received.command.unignore", min_args=1) - def unignore(self, event): - """ - :help: Unignore commands from a given user - :usage: <nickname> [command] - :permission: unignore - """ - setting = "ignore" - for_str = "" - if len(event["args_split"]) > 1: - command = event["args_split"][1].lower() - setting = "ignore-%s" % command - for_str = " for '%s'" % command - - user = event["server"].get_user(event["args_split"][0]) - if not user.get_setting(setting, False): - event["stderr"].write("I'm not ignoring '%s'%s" % - (user.nickname, for_str)) - else: - user.del_setting(setting) - event["stdout"].write("Removed ignore for '%s'%s" % - (user.nickname, for_str)) - - @utils.hook("received.command.cignore", - help="Ignore a user in this channel") - @utils.hook("received.command.cunignore", - help="Unignore a user in this channel") - @utils.kwarg("channel_only", True) - @utils.kwarg("min_args", 1) - @utils.kwarg("usage", "<nickname>") - @utils.kwarg("permission", "cignore") - @utils.kwarg("require_mode", "o") - @utils.kwarg("require_access", "cignore") - def cignore(self, event): - remove = event["command"] == "cunignore" - - target_user = event["server"].get_user(event["args_split"][0]) - is_ignored = event["target"].get_user_setting(target_user.get_id(), - "ignore", False) - - if remove: - if not is_ignored: - raise utils.EventError("I'm not ignoring %s in this channel" % - target_user.nickname) - event["target"].del_user_setting(target_user.get_id(), "ignore") - event["stdout"].write("Unignored %s" % target_user.nickname) - else: - if is_ignored: - raise utils.EventError("I'm already ignoring %s in this channel" - % target_user.nickname) - event["target"].set_user_setting(target_user.get_id(), "ignore", - True) - event["stdout"].write("Ignoring %s" % target_user.nickname) - - @utils.hook("received.command.serverignore", min_args=1) - def server_ignore(self, event): - """ - :permission: server-ignore - """ - command = event["args_split"][0].lower() - setting = "ignore-%s" % command - - if event["server"].get_setting(setting, False): - event["stderr"].write("I'm already ignoring '%s' for %s" % - (command, str(event["server"]))) - else: - event["server"].set_setting(setting, True) - event["stdout"].write("Now ignoring '%s' for %s" % - (command, str(event["server"]))) - - @utils.hook("received.command.serverunignore", min_args=1) - def server_unignore(self, event): - """ - :permission: server-unignore - """ - command = event["args_split"][0].lower() - setting = "ignore-%s" % command - - if not event["server"].get_setting(setting, False): - event["stderr"].write("I'm not ignoring '%s' for %s" % - (command, str(event["server"]))) - else: - event["server"].del_setting(setting) - event["stdout"].write("No longer ignoring '%s' for %s" % - (command, str(event["server"]))) - diff --git a/modules/ircv3_chathistory.py b/modules/ircv3_chathistory.py deleted file mode 100644 index e540673a..00000000 --- a/modules/ircv3_chathistory.py +++ /dev/null @@ -1,36 +0,0 @@ -#--depends-on ircv3_msgid - -from src import ModuleManager, utils - -TAG = utils.irc.MessageTag("msgid", "draft/msgid") -CHATHISTORY_BATCH = utils.irc.BatchType("chathistory") - -EVENTPLAYBACK_CAP = utils.irc.Capability(None, "draft/event-playback", - alias="event-playback") -HISTORY_BATCH = utils.irc.BatchType("history") - -@utils.export("cap", EVENTPLAYBACK_CAP) -class Module(ModuleManager.BaseModule): - @utils.hook("received.batch.end") - def batch_end(self, event): - if (CHATHISTORY_BATCH.match(event["batch"].type) or - HISTORY_BATCH.match(event["batch"].type)): - target_name = event["batch"].args[0] - if target_name in event["server"].channels: - target = event["server"].channels.get(target_name) - else: - target = event["server"].get_user(target_name) - - last_msgid = target.get_setting("last-msgid", None) - if not last_msgid == None: - lines = event["batch"].get_lines() - stop_index = -1 - - for i, line in enumerate(lines): - msgid = TAG.get_value(line.tags) - if msgid == last_msgid: - stop_index = i - break - - if not stop_index == -1: - return lines[stop_index+1:] diff --git a/modules/ircv3_echo_message.py b/modules/ircv3_echo_message.py deleted file mode 100644 index 276ac554..00000000 --- a/modules/ircv3_echo_message.py +++ /dev/null @@ -1,21 +0,0 @@ -from src import EventManager, ModuleManager, utils - -CAP = utils.irc.Capability("echo-message", depends_on=["labeled-response"]) - -@utils.export("cap", CAP) -class Module(ModuleManager.BaseModule): - @utils.hook("raw.send.privmsg", priority=EventManager.PRIORITY_LOW) - @utils.hook("raw.send.notice", priority=EventManager.PRIORITY_LOW) - def send_message(self, event): - if event["server"].has_capability(CAP): - event.eat() - - @utils.hook("preprocess.send.privmsg") - @utils.hook("preprocess.send.notice") - @utils.hook("preprocess.send.tagmsg") - def preprocess_send(self, event): - if event["server"].has_capability(CAP): - event["events"].on("labeled-response").hook(self.on_echo) - - def on_echo(self, event): - event["responses"][0].id = event["line"].id diff --git a/modules/ircv3_labeled_responses.py b/modules/ircv3_labeled_responses.py deleted file mode 100644 index 7dd04b5c..00000000 --- a/modules/ircv3_labeled_responses.py +++ /dev/null @@ -1,67 +0,0 @@ -import uuid -from src import ModuleManager, utils - -CAP = utils.irc.Capability(None, "draft/labeled-response-0.2", - alias="labeled-response", depends_on=["batch"]) -TAG = utils.irc.MessageTag(None, "draft/label") -BATCH = utils.irc.BatchType(None, "draft/labeled-response") - -CAP_TO_TAG = { - "draft/labeled-response-0.2": "draft/label" -} - -class WaitingForLabel(object): - def __init__(self, line, events): - self.line = line - self.events = events - self.labels_since = 0 - -@utils.export("cap", CAP) -class Module(ModuleManager.BaseModule): - @utils.hook("new.server") - def new_server(self, event): - event["server"]._label_cache = {} - - @utils.hook("preprocess.send") - def raw_send(self, event): - available_cap = event["server"].available_capability(CAP) - - if available_cap: - label = TAG.get_value(event["line"].tags) - if label == None: - tag_key = CAP_TO_TAG[available_cap] - label = str(uuid.uuid4()) - event["line"].tags[tag_key] = label - - event["server"]._label_cache[label] = WaitingForLabel(event["line"], - event["events"]) - - @utils.hook("raw.received") - def raw_recv(self, event): - if not event["line"].command == "BATCH": - label = TAG.get_value(event["line"].tags) - if not label == None: - self._recv(event["server"], label, [event["line"]]) - - @utils.hook("received.batch.end") - def batch_end(self, event): - if BATCH.match(event["batch"].type): - label = TAG.get_value(event["batch"].tags) - self._recv(event["server"], label, event["batch"].get_lines()) - - def _recv(self, server, label, lines): - if not label in server._label_cache: - self.log.debug("unknown label received on %s: %s", - [str(server), label]) - return - - cached = server._label_cache.pop(label) - cached.events.on("labeled-response").call(line=cached.line, - responses=lines) - - for label, other_cached in server._label_cache.items(): - other_cached.labels_since += 1 - if other_cached.labels_since == 10: - self.log.debug( - "%d labels seen while waiting for response to %s on %s", - [other_cached.labels_since, label, str(server)]) diff --git a/modules/ircv3_message_tracking.py b/modules/ircv3_message_tracking.py deleted file mode 100644 index 3f4ad88c..00000000 --- a/modules/ircv3_message_tracking.py +++ /dev/null @@ -1,17 +0,0 @@ -from src import ModuleManager, utils - -MSGID_TAG = "draft/msgid" -READ_TAG = "+draft/read" -DELIVERED_TAG = "+draft/delivered" -MESSAGE_TAG_CAPS = set(["draft/message-tags-0.2", "message-tags"]) - -class Module(ModuleManager.BaseModule): - @utils.hook("received.message.private") - @utils.hook("received.notice.private") - def privmsg(self, event): - if MSGID_TAG in event["tags"] and ( - event["server"].agreed_capabilities & MESSAGE_TAG_CAPS): - target = event.get("channel", event["user"]) - msgid = event["tags"][MSGID_TAG] - tags = {DELIVERED_TAG: msgid, READ_TAG: msgid} - target.send_tagmsg(tags) diff --git a/modules/ircv3_metadata.py b/modules/ircv3_metadata.py deleted file mode 100644 index e0e6d387..00000000 --- a/modules/ircv3_metadata.py +++ /dev/null @@ -1,16 +0,0 @@ -from src import IRCBot, ModuleManager, utils - -CAP = utils.irc.Capability(None, "draft/metadata", alias="metadata") - -class Module(ModuleManager.BaseModule): - @utils.hook("received.cap.new") - @utils.hook("received.cap.ls") - def on_cap(self, event): - cap = CAP.copy() - cap.on_ack(lambda: self._ack(event["server"])) - return cap - - def _ack(self, server): - url = self.bot.get_setting("bot-url", IRCBot.SOURCE) - server.send_raw("METADATA * SET bot BitBot") - server.send_raw("METADATA * SET homepage :%s" % url) diff --git a/modules/ircv3_msgid.py b/modules/ircv3_msgid.py deleted file mode 100644 index f95f9fd4..00000000 --- a/modules/ircv3_msgid.py +++ /dev/null @@ -1,31 +0,0 @@ -from src import ModuleManager, utils - -TAG = utils.irc.MessageTag("msgid", "draft/msgid") - -class Module(ModuleManager.BaseModule): - def _on_channel(self, channel, tags): - msgid = TAG.get_value(tags) - if not msgid == None: - channel.set_setting("last-msgid", msgid) - - @utils.hook("received.message.channel") - @utils.hook("send.message.channel") - @utils.hook("received.notice.channel") - @utils.hook("send.notice.channel") - @utils.hook("received.tagmsg.channel") - @utils.hook("send.tagmsg.channel") - def on_channel(self, event): - self._on_channel(event["channel"], event["tags"]) - - @utils.hook("received.ctcp.request") - @utils.hook("received.ctcp.response") - def ctcp(self, event): - if event["is_channel"]: - self._on_channel(event["target"], event["tags"]) - - @utils.hook("postprocess.command") - def postprocess_command(self, event): - msgid = TAG.get_value(event["line"].tags) - if msgid: - event["tags"]["+draft/reply"] = msgid - event["tags"]["+draft/reply"] = msgid diff --git a/modules/ircv3_sasl/README.md b/modules/ircv3_sasl/README.md deleted file mode 100644 index 30a51e08..00000000 --- a/modules/ircv3_sasl/README.md +++ /dev/null @@ -1,46 +0,0 @@ -# Configuring SASL - -You can either configure SASL through `!serverset sasl` from an registered and identified admin account or directly through sqlite. - -## USERPASS Mechanism - -BitBot supports a special SASL mechanism name: `USERPASS`. This internally -represents "pick the strongest username:password algorithm" - -## !serverset sasl - -These commands are to be executed from a registered admin account - -#### USERPASS -> !serverset sasl userpass <username>:<password> - -#### PLAIN -> !serverset sasl plain <username>:<password> - -#### SCRAM-SHA-1 -> !serverset sasl scram-sha-1 <username>:<password> - -#### SCRAM-SHA-256 -> !serverset sasl scram-sha-256 <username>:<password> - -#### EXTERNAL -> !serverset sasl external - -## sqlite - -Execute these against the current bot database file (e.g. `$ sqlite3 databases/bot.db`) - -#### USERPASS -> INSERT INTO server_settings (<serverid>, 'sasl', '{"mechanism": "userpass", "args": "<username>:<password>"}'); - -#### PLAIN -> INSERT INTO server_settings (<serverid>, 'sasl', '{"mechanism": "plain", "args": "<username>:<password>"}'); - -#### SCRAM-SHA-1 -> INSERT INTO server_settings (<serverid>, 'sasl', '{"mechanism": "scram-sha-1", "args": "<username>:<password>"}'); - -#### SCRAM-SHA-256 -> INSERT INTO server_settings (<serverid>, 'sasl', '{"mechanism": "scram-sha-256", "args": "<username>:<password>"}'); - -#### external -> INSERT INTO server_settings (<serverid>, 'sasl', '{"mechanism": "external"}'); diff --git a/modules/ircv3_sasl/__init__.py b/modules/ircv3_sasl/__init__.py deleted file mode 100644 index 9f7fac5f..00000000 --- a/modules/ircv3_sasl/__init__.py +++ /dev/null @@ -1,195 +0,0 @@ -#--depends-on config - -import base64, hashlib, hmac, typing, uuid -from src import ModuleManager, utils -from . import scram - -CAP = utils.irc.Capability("sasl") - -USERPASS_MECHANISMS = [ - "SCRAM-SHA-512", - "SCRAM-SHA-256", - "SCRAM-SHA-1", - "PLAIN" -] -ALL_MECHANISMS = USERPASS_MECHANISMS+["EXTERNAL"] - -def _parse(value): - mechanism, _, arguments = value.partition(" ") - mechanism = mechanism.upper() - - if mechanism in ALL_MECHANISMS: - return {"mechanism": mechanism.upper(), "args": arguments} - else: - raise utils.settings.SettingParseException( - "Unknown SASL mechanism '%s'" % mechanism) - -SASL_TIMEOUT = 15 # 15 seconds - -HARDFAIL = utils.BoolSetting("sasl-hard-fail", - "Set whether a SASL failure should cause a disconnect") - -@utils.export("serverset", utils.FunctionSetting(_parse, "sasl", - "Set the sasl username/password for this server", - example="PLAIN BitBot:hunter2", format=utils.sensitive_format)) -@utils.export("serverset", HARDFAIL) -@utils.export("botset", HARDFAIL) -class Module(ModuleManager.BaseModule): - @utils.hook("new.server") - def new_server(self, event): - event["server"]._sasl_timeout = None - event["server"]._sasl_retry = False - - def _best_userpass_mechanism(self, mechanisms): - for potential_mechanism in USERPASS_MECHANISMS: - if potential_mechanism in mechanisms: - return potential_mechanism - - def _mech_match(self, server, server_mechanisms): - our_sasl = server.get_setting("sasl", None) - if not our_sasl: - return None - - our_mechanism = our_sasl["mechanism"].upper() - - if not server_mechanisms and our_mechanism in ALL_MECHANISMS: - return our_mechanism - elif our_mechanism in server_mechanisms: - return our_mechanism - elif our_mechanism == "USERPASS": - if server_mechanisms: - return self._best_userpass_mechanism(server_mechanisms) - else: - return USERPASS_MECHANISMS[0] - return None - - @utils.hook("received.cap.new") - @utils.hook("received.cap.ls") - def on_cap(self, event): - has_sasl = "sasl" in event["capabilities"] - if has_sasl: - server_mechanisms = event["capabilities"]["sasl"] - if server_mechanisms: - server_mechanisms = server_mechanisms.split(",") - else: - server_mechanisms = [] - - mechanism = self._mech_match(event["server"], server_mechanisms) - - if mechanism: - cap = CAP.copy() - cap.on_ack( - lambda: self._sasl_ack(event["server"], mechanism)) - return cap - - def _sasl_ack(self, server, mechanism): - server.send_authenticate(mechanism) - server._sasl_timeout = self.timers.add("sasl-timeout", - self._sasl_timeout, SASL_TIMEOUT, server=server) - server._sasl_mechanism = mechanism - - server.wait_for_capability("sasl") - - def _sasl_timeout(self, timer): - server = timer.kwargs["server"] - self._panic(server, "SASL handshake timed out") - - @utils.hook("received.authenticate") - def on_authenticate(self, event): - sasl = event["server"].get_setting("sasl") - mechanism = event["server"]._sasl_mechanism - - auth_text = None - if mechanism == "PLAIN": - if event["message"] != "+": - event["server"].send_authenticate("*") - else: - sasl_username, sasl_password = sasl["args"].split(":", 1) - auth_text = ("%s\0%s\0%s" % ( - sasl_username, sasl_username, sasl_password)).encode("utf8") - - elif mechanism == "EXTERNAL": - if event["message"] != "+": - event["server"].send_authenticate("*") - else: - auth_text = "+" - - elif mechanism.startswith("SCRAM-"): - - if event["message"] == "+": - # start SCRAM handshake - - # create SCRAM helper - sasl_username, sasl_password = sasl["args"].split(":", 1) - algo = mechanism.split("SCRAM-", 1)[1] - event["server"]._scram = scram.SCRAM( - algo, sasl_username, sasl_password) - - # generate client-first-message - auth_text = event["server"]._scram.client_first() - else: - current_scram = event["server"]._scram - data = base64.b64decode(event["message"]) - if current_scram.state == scram.SCRAMState.ClientFirst: - # use server-first-message to generate client-final-message - auth_text = current_scram.server_first(data) - elif current_scram.state == scram.SCRAMState.ClientFinal: - # use server-final-message to check server proof - verified = current_scram.server_final(data) - del event["server"]._scram - - if verified: - auth_text = "+" - else: - if current_scram.state == scram.SCRAMState.VerifyFailed: - # server gave a bad verification so we should panic - self._panic(event["server"], "SCRAM VerifyFailed") - - else: - raise ValueError("unknown sasl mechanism '%s'" % mechanism) - - if not auth_text == None: - if not auth_text == "+": - auth_text = base64.b64encode(auth_text) - auth_text = auth_text.decode("utf8") - event["server"].send_authenticate(auth_text) - - def _end_sasl(self, server): - server.capability_done("sasl") - if not server._sasl_timeout == None: - server._sasl_timeout.cancel() - server._sasl_timeout = None - - @utils.hook("received.908") - def sasl_mechanisms(self, event): - server_mechanisms = event["line"].args[1].split(",") - mechanism = self._mech_match(event["server"], server_mechanisms) - if mechanism: - event["server"]._sasl_mechanism = mechanism - event["server"].send_authenticate(mechanism) - event["server"]._sasl_retry = True - - @utils.hook("received.903") - def sasl_success(self, event): - self._end_sasl(event["server"]) - @utils.hook("received.904") - def sasl_failure(self, event): - if not event["server"]._sasl_retry: - self._panic(event["server"], "ERR_SASLFAIL (%s)" % - event["line"].args[1]) - else: - event["server"]._sasl_retry = False - - @utils.hook("received.907") - def sasl_already(self, event): - self._end_sasl(event["server"]) - - def _panic(self, server, message): - if server.get_setting("sasl-hard-fail", - self.bot.get_setting("sasl-hard-fail", False)): - message = "SASL panic for %s: %s" % (str(server), message) - self.log.error(message) - self.bot.disconnect(server) - else: - self.log.warn("SASL failure for %s: %s" % (str(server), message)) - self._end_sasl(server) diff --git a/modules/ircv3_sasl/scram.py b/modules/ircv3_sasl/scram.py deleted file mode 100644 index f243d1e6..00000000 --- a/modules/ircv3_sasl/scram.py +++ /dev/null @@ -1,130 +0,0 @@ -import base64, enum, hashlib, hmac, os, typing - -# IANA Hash Function Textual Names -# https://tools.ietf.org/html/rfc5802#section-4 -# https://www.iana.org/assignments/hash-function-text-names/ -# MD2 has been removed as it's unacceptably weak -ALGORITHMS = [ - "MD5", "SHA-1", "SHA-224", "SHA-256", "SHA-384", "SHA-512"] - -SCRAM_ERRORS = [ - "invalid-encoding", - "extensions-not-supported", # unrecognized 'm' value - "invalid-proof", - "channel-bindings-dont-match", - "server-does-support-channel-binding", - "channel-binding-not-supported", - "unsupported-channel-binding-type", - "unknown-user", - "invalid-username-encoding", # invalid utf8 or bad SASLprep - "no-resources" -] - -def _scram_nonce() -> bytes: - return base64.b64encode(os.urandom(32)) -def _scram_escape(s: bytes) -> bytes: - return s.replace(b"=", b"=3D").replace(b",", b"=2C") -def _scram_unescape(s: bytes) -> bytes: - return s.replace(b"=3D", b"=").replace(b"=2C", b",") -def _scram_xor(s1: bytes, s2: bytes) -> bytes: - return bytes(a ^ b for a, b in zip(s1, s2)) - -class SCRAMState(enum.Enum): - Uninitialised = 0 - ClientFirst = 1 - ClientFinal = 2 - Success = 3 - Failed = 4 - VerifyFailed = 5 - -class SCRAMError(Exception): - pass - -class SCRAM(object): - def __init__(self, algo: str, username: str, password: str): - if not algo in ALGORITHMS: - raise ValueError("Unknown SCRAM algorithm '%s'" % algo) - - self._algo = algo.replace("-", "") # SHA-1 -> SHA1 - self._username = username.encode("utf8") - self._password = password.encode("utf8") - - self.state = SCRAMState.Uninitialised - self.error = "" - self.raw_error = "" - - self._client_first = b"" - self._salted_password = b"" - self._auth_message = b"" - - def _get_pieces(self, data: bytes) -> typing.Dict[bytes, bytes]: - pieces = (piece.split(b"=", 1) for piece in data.split(b",")) - return dict((piece[0], piece[1]) for piece in pieces) - - def _hmac(self, key: bytes, msg: bytes) -> bytes: - return hmac.new(key, msg, self._algo).digest() - def _hash(self, msg: bytes) -> bytes: - return hashlib.new(self._algo, msg).digest() - - def _constant_time_compare(self, b1: bytes, b2: bytes): - return hmac.compare_digest(b1, b2) - - def client_first(self) -> bytes: - self.state = SCRAMState.ClientFirst - self._client_first = b"n=%s,r=%s" % ( - _scram_escape(self._username), _scram_nonce()) - - # n,,n=<username>,r=<nonce> - return b"n,,%s" % self._client_first - - def server_first(self, data: bytes) -> bytes: - self.state = SCRAMState.ClientFinal - - pieces = self._get_pieces(data) - nonce = pieces[b"r"] # server combines your nonce with it's own - salt = base64.b64decode(pieces[b"s"]) # salt is b64encoded - iterations = int(pieces[b"i"]) - - salted_password = hashlib.pbkdf2_hmac(self._algo, self._password, - salt, iterations, dklen=None) - self._salted_password = salted_password - - client_key = self._hmac(salted_password, b"Client Key") - stored_key = self._hash(client_key) - - channel = base64.b64encode(b"n,,") - auth_noproof = b"c=%s,r=%s" % (channel, nonce) - auth_message = b"%s,%s,%s" % (self._client_first, data, auth_noproof) - self._auth_message = auth_message - - client_signature = self._hmac(stored_key, auth_message) - client_proof_xor = _scram_xor(client_key, client_signature) - client_proof = base64.b64encode(client_proof_xor) - - # c=<b64encode("n,,")>,r=<nonce>,p=<proof> - return b"%s,p=%s" % (auth_noproof, client_proof) - - def server_final(self, data: bytes) -> bool: - pieces = self._get_pieces(data) - if b"e" in pieces: - error = pieces[b"e"].decode("utf8") - self.raw_error = error - if error in SCRAM_ERRORS: - self.error = error - else: - self.error = "other-error" - - self.state = SCRAMState.Failed - return False - - verifier = base64.b64decode(pieces[b"v"]) - - server_key = self._hmac(self._salted_password, b"Server Key") - server_signature = self._hmac(server_key, self._auth_message) - - if server_signature == verifier: - self.state = SCRAMState.Success - return True - else: - self.state = SCRAMState.VerifyFailed - return False diff --git a/modules/ircv3_server_time.py b/modules/ircv3_server_time.py deleted file mode 100644 index c9790d95..00000000 --- a/modules/ircv3_server_time.py +++ /dev/null @@ -1,12 +0,0 @@ -from src import ModuleManager, utils - -CAP = utils.irc.Capability("server-time") -TAG = utils.irc.MessageTag("time") - -@utils.export("cap", CAP) -class Module(ModuleManager.BaseModule): - @utils.hook("raw.received") - def raw_recv(self, event): - server_time = TAG.get_value(event["line"].tags) - if not server_time == None: - event["server"].set_setting("last-server-time", server_time) diff --git a/modules/ircv3_sts.py b/modules/ircv3_sts.py deleted file mode 100644 index aeeac1f1..00000000 --- a/modules/ircv3_sts.py +++ /dev/null @@ -1,70 +0,0 @@ -import time -from src import ModuleManager, utils - -CAP = utils.irc.Capability("sts", "draft/sts") - -class Module(ModuleManager.BaseModule): - def _get_policy(self, server): - return server.get_setting("sts-policy", None) - def _set_policy(self, server, policy): - self.log.info("Setting STS policy for '%s': %s", [str(server), policy]) - server.set_setting("sts-policy", policy) - def _remove_policy(self, server): - server.del_setting("sts-policy") - - def set_policy(self, server, port, duration): - expiration = None - self._set_policy(server, { - "port": port, - "from": time.time(), - "duration": duration}) - def change_duration(self, server, info): - duration = int(info["duration"]) - if duration == 0: - self._remove_policy(server) - else: - port = server.connection_params.port - if "port" in info: - port = int(info["port"]) - self.set_policy(server, port, duration) - - @utils.hook("received.cap.ls") - def on_cap_ls(self, event): - sts = CAP.available(event["capabilities"]) - if sts: - info = utils.parse.keyvalue(event["capabilities"][sts], - delimiter=",") - if not event["server"].connection_params.tls: - if "port" in info: - self.set_policy(event["server"], int(info["port"]), None) - event["server"].disconnect() - self.bot.reconnect(event["server"].id, - event["server"].connection_params) - else: - self.change_duration(event["server"], info) - - @utils.hook("received.cap.new") - def on_cap_new(self, event): - if CAP.available(event["capabilities"] - ) and event["server"].connection_params.tls: - info = utils.parse.keyvalue(sts, delimiter=",") - self.change_duration(event["server"], info) - - @utils.hook("new.server") - def new_server(self, event): - sts_policy = self._get_policy(event["server"]) - if sts_policy: - if not event["server"].connection_params.tls: - if not sts_policy["duration"] or time.time() <= ( - sts_policy["from"]+sts_policy["duration"]): - self.log.info("Applying STS policy for '%s'", - [str(event["server"])]) - event["server"].connection_params.tls = True - event["server"].connection_params.port = sts_policy["port"] - - @utils.hook("server.disconnect") - def on_disconnect(self, event): - sts_policy = self._get_policy(event["server"]) - if sts_policy and sts_policy["duration"]: - sts_policy["from"] = time.time() - self._set_policy(event["server"], sts_policy) diff --git a/modules/line_handler/__init__.py b/modules/line_handler/__init__.py deleted file mode 100644 index ddea6fdc..00000000 --- a/modules/line_handler/__init__.py +++ /dev/null @@ -1,260 +0,0 @@ -import enum -from src import EventManager, IRCLine, ModuleManager, utils -from . import channel, core, ircv3, message, user - -class Module(ModuleManager.BaseModule): - def _handle(self, server, line): - hooks = self.events.on("raw.received").on(line.command).get_hooks() - default_events = [] - for hook in hooks: - default_events.append(hook.get_kwarg("default_event", False)) - - kwargs = {"server": server, "line": line, - "direction": utils.Direction.Recv} - - self.events.on("raw.received").on(line.command).call_unsafe(**kwargs) - if any(default_events) or not hooks: - self.events.on("received").on(line.command).call(**kwargs) - - @utils.hook("raw.received") - def handle_raw(self, event): - if ("batch" in event["line"].tags and - event["line"].tags["batch"] in event["server"].batches): - event["server"].batches[event["line"].tags["batch"]].add_line( - event["line"]) - else: - self._handle(event["server"], event["line"]) - - @utils.hook("raw.send") - def handle_send(self, event): - self.events.on("raw.send").on(event["line"].command).call_unsafe( - server=event["server"], direction=utils.Direction.Send, - line=event["line"]) - - # ping from the server - @utils.hook("raw.received.ping") - def ping(self, event): - core.ping(event) - - @utils.hook("raw.received.error") - def error(self, event): - self.log.error("ERROR received from %s: %s", - [str(event["server"]), event["line"].args[0]]) - @utils.hook("raw.received.fail") - def fail(self, event): - command = event["line"].args[0] - error_code = event["line"].args[1] - context = event["line"].args[2:-1] - description = event["line"].args[-1] - - self.log.warn("FAIL (%s %s) received on %s: %s", - [command, error_code, str(event["server"]), description]) - self.events.on("received.fail").on(command).call(error_code=error_code, - context=context, description=description, server=event["server"]) - - # first numeric line the server sends - @utils.hook("raw.received.001", default_event=True) - def handle_001(self, event): - core.handle_001(event) - - # server telling us what it supports - @utils.hook("raw.received.005") - def handle_005(self, event): - core.handle_005(self.events, event) - - # RPL_MYINFO - @utils.hook("raw.received.004") - def handle_004(self, event): - core.handle_004(event) - - # whois respose (nickname, username, realname, hostname) - @utils.hook("raw.received.311", default_event=True) - def handle_311(self, event): - user.handle_311(event) - - # on-join channel topic line - @utils.hook("raw.received.332") - def handle_332(self, event): - channel.handle_332(self.events, event) - - # channel topic changed - @utils.hook("raw.received.topic") - def topic(self, event): - channel.topic(self.events, event) - - # on-join channel topic set by/at - @utils.hook("raw.received.333") - def handle_333(self, event): - channel.handle_333(self.events, event) - - # /names response, also on-join user list - @utils.hook("raw.received.353", default_event=True) - def handle_353(self, event): - channel.handle_353(event) - - # on-join user list has finished - @utils.hook("raw.received.366", default_event=True) - def handle_366(self, event): - channel.handle_366(event) - - @utils.hook("raw.received.375", priority=EventManager.PRIORITY_HIGH) - def motd_start(self, event): - core.motd_start(event) - - @utils.hook("raw.received.372") - @utils.hook("raw.received.375") - def motd_line(self, event): - core.motd_line(event) - - # on user joining channel - @utils.hook("raw.received.join") - def join(self, event): - channel.join(self.events, event) - - # on user parting channel - @utils.hook("raw.received.part") - def part(self, event): - channel.part(self.events, event) - - # unknown command sent by us, oops! - @utils.hook("raw.received.421", default_event=True) - def handle_421(self, event): - self.bot.log.warn("We sent an unknown command to %s: %s", - [str(event["server"]), event["line"].args[1]]) - - # a user has disconnected! - @utils.hook("raw.received.quit") - @utils.hook("raw.send.quit") - def quit(self, event): - user.quit(self.events, event) - - # the server is telling us about its capabilities! - @utils.hook("raw.received.cap") - def cap(self, event): - ircv3.cap(self.exports, self.events, event) - - # the server is asking for authentication - @utils.hook("raw.received.authenticate") - def authenticate(self, event): - ircv3.authenticate(self.events, event) - - # someone has changed their nickname - @utils.hook("raw.received.nick") - def nick(self, event): - user.nick(self.events, event) - - # something's mode has changed - @utils.hook("raw.received.mode") - def mode(self, event): - core.mode(self.events, event) - # server telling us our own modes - @utils.hook("raw.received.221") - def umodeis(self, event): - core.handle_221(event) - - # someone (maybe me!) has been invited somewhere - @utils.hook("raw.received.invite") - def invite(self, event): - core.invite(self.events, event) - - # we've received/sent a PRIVMSG, NOTICE or TAGMSG - @utils.hook("raw.received.privmsg") - @utils.hook("raw.received.notice") - @utils.hook("raw.received.tagmsg") - def message(self, event): - message.message(self.events, event) - - # IRCv3 AWAY, used to notify us that a client we can see has changed /away - @utils.hook("raw.received.away") - def away(self, event): - user.away(self.events, event) - - @utils.hook("raw.received.batch") - def batch(self, event): - identifier = event["line"].args[0] - modifier, identifier = identifier[0], identifier[1:] - - if modifier == "+": - batch_type = event["line"].args[1] - args = event["line"].args[2:] - - batch = IRCLine.IRCBatch(identifier, batch_type, args, - event["line"].tags, source=event["line"].source) - event["server"].batches[identifier] = batch - - self.events.on("received.batch.start").call(batch=batch, - server=event["server"]) - else: - batch = event["server"].batches[identifier] - del event["server"].batches[identifier] - - lines = batch.get_lines() - - results = self.events.on("received.batch.end").call(batch=batch, - server=event["server"]) - - for result in results: - if not result == None: - lines = result - break - - for line in lines: - self._handle(event["server"], line) - - # IRCv3 CHGHOST, a user's username and/or hostname has changed - @utils.hook("raw.received.chghost") - def chghost(self, event): - user.chghost(self.events, event) - - # IRCv3 SETNAME, to change a user's realname - @utils.hook("raw.received.setname") - def setname(self, event): - user.setname(event) - - @utils.hook("raw.received.account") - def account(self, event): - user.account(self.events, event) - - # response to a WHO command for user information - @utils.hook("raw.received.352", default_event=True) - def handle_352(self, event): - core.handle_352(self.events, event) - - # response to a WHOX command for user information, including account name - @utils.hook("raw.received.354", default_event=True) - def handle_354(self, event): - core.handle_354(self.events, event) - - # response to an empty mode command - @utils.hook("raw.received.324") - def handle_324(self, event): - channel.handle_324(self.events, event) - - # channel creation unix timestamp - @utils.hook("raw.received.329", default_event=True) - def handle_329(self, event): - channel.handle_329(event) - - # nickname already in use - @utils.hook("raw.received.433", default_event=True) - def handle_433(self, event): - core.handle_433(event) - # nickname/channel is temporarily unavailable - @utils.hook("raw.received.437") - def handle_437(self, event): - core.handle_437(event) - - # we need a registered nickname for this channel - @utils.hook("raw.received.477", default_event=True) - def handle_477(self, event): - channel.handle_477(self.timers, event) - - # someone's been kicked from a channel - @utils.hook("raw.received.kick") - def kick(self, event): - channel.kick(self.events, event) - - # a channel has been renamed - @utils.hook("raw.received.rename") - def rename(self, event): - channel.rename(self.events, event) diff --git a/modules/line_handler/channel.py b/modules/line_handler/channel.py deleted file mode 100644 index 91150839..00000000 --- a/modules/line_handler/channel.py +++ /dev/null @@ -1,160 +0,0 @@ -from src import IRCLine, utils - -def handle_332(events, event): - channel = event["server"].channels.get(event["line"].args[1]) - topic = event["line"].args.get(2) - channel.set_topic(topic) - events.on("received.332").call(channel=channel, server=event["server"], - topic=topic) - -def topic(events, event): - user = event["server"].get_user(event["line"].source.nickname) - channel = event["server"].channels.get(event["line"].args[0]) - topic = event["line"].args.get(1) - channel.set_topic(topic) - events.on("received.topic").call(channel=channel, server=event["server"], - topic=topic, user=user) - -def handle_333(events, event): - channel = event["server"].channels.get(event["line"].args[1]) - - topic_setter = IRCLine.parse_hostmask(event["line"].args[2]) - topic_time = int(event["line"].args[3]) - - channel.set_topic_setter(topic_setter) - channel.set_topic_time(topic_time) - events.on("received.333").call(channel=channel, - setter=topic_setter, set_at=topic_time, server=event["server"]) - -def handle_353(event): - channel = event["server"].channels.get(event["line"].args[2]) - nicknames = event["line"].args.get(3).split(" ") - - # there can sometimes be a dangling space at the end of a 353 - if nicknames and not nicknames[-1]: - nicknames.pop(-1) - - for nickname in nicknames: - modes = set([]) - - while nickname[0] in event["server"].prefix_symbols: - modes.add(event["server"].prefix_symbols[nickname[0]]) - nickname = nickname[1:] - - if event["server"].has_capability_str("userhost-in-names"): - hostmask = IRCLine.parse_hostmask(nickname) - nickname = hostmask.nickname - user = event["server"].get_user(hostmask.nickname, - username=hostmask.username, hostname=hostmask.hostname) - else: - user = event["server"].get_user(nickname) - user.join_channel(channel) - channel.add_user(user) - - for mode in modes: - channel.add_mode(mode, nickname) - -def handle_366(event): - event["server"].send_whox(event["line"].args[1], "n", "ahnrtu", "111") - -def join(events, event): - account = None - realname = None - channel_name = event["line"].args[0] - - if len(event["line"].args) == 3: - if not event["line"].args[1] == "*": - account = event["line"].args[1] - realname = event["line"].args[2] - - user = event["server"].get_user(event["line"].source.nickname, - username=event["line"].source.username, - hostname=event["line"].source.hostname) - - if account: - user.account = account - if realname: - user.realname = realname - - is_self = event["server"].is_own_nickname(event["line"].source.nickname) - if is_self: - channel = event["server"].channels.add(channel_name) - else: - channel = event["server"].channels.get(channel_name) - - - channel.add_user(user) - user.join_channel(channel) - - if is_self: - events.on("self.join").call(channel=channel, server=event["server"], - account=account, realname=realname) - channel.send_mode() - else: - events.on("received.join").call(channel=channel, user=user, - server=event["server"], account=account, realname=realname) - -def part(events, event): - channel = event["server"].channels.get(event["line"].args[0]) - user = event["server"].get_user(event["line"].source.nickname) - reason = event["line"].args.get(1) - - channel.remove_user(user) - user.part_channel(channel) - if not len(user.channels): - event["server"].remove_user(user) - - if not event["server"].is_own_nickname(event["line"].source.nickname): - events.on("received.part").call(channel=channel, reason=reason, - user=user, server=event["server"]) - else: - event["server"].channels.remove(channel) - events.on("self.part").call(channel=channel, reason=reason, - server=event["server"]) - -def handle_324(events, event): - if event["line"].args[1] in event["server"].channels: - channel = event["server"].channels.get(event["line"].args[1]) - modes = event["line"].args[2] - args = event["line"].args[3:] - new_modes = channel.parse_modes(modes, args[:]) - events.on("received.324").call(modes=new_modes, - channel=channel, server=event["server"], mode_str=modes, - args_str=args) - -def handle_329(event): - channel = event["server"].channels.get(event["line"].args[1]) - channel.creation_timestamp = int(event["line"].args[2]) - -def handle_477(timers, event): - pass - -def kick(events, event): - user = event["server"].get_user(event["line"].source.nickname) - target = event["line"].args[1] - channel = event["server"].channels.get(event["line"].args[0]) - reason = event["line"].args.get(2) - target_user = event["server"].get_user(target) - - if not event["server"].is_own_nickname(target): - events.on("received.kick").call(channel=channel, reason=reason, - target_user=target_user, user=user, server=event["server"]) - else: - event["server"].channels.remove(channel) - events.on("self.kick").call(channel=channel, reason=reason, user=user, - server=event["server"]) - - channel.remove_user(target_user) - target_user.part_channel(channel) - if not len(target_user.channels): - event["server"].remove_user(target_user) - -def rename(events, event): - old_name = event["line"].args[0] - new_name = event["line"].args[1] - channel = event["server"].channels.get(old_name) - - event["server"].channels.rename(old_name, new_name) - events.on("received.rename").call(channel=channel, old_name=old_name, - new_name=new_name, reason=event["line"].args.get(2), - server=event["server"]) diff --git a/modules/line_handler/core.py b/modules/line_handler/core.py deleted file mode 100644 index d72bf223..00000000 --- a/modules/line_handler/core.py +++ /dev/null @@ -1,154 +0,0 @@ -import codecs, re - -RE_ISUPPORT_ESCAPE = re.compile(r"\\x(\d\d)", re.I) -RE_MODES = re.compile(r"[-+]\w+") - -def ping(event): - event["server"].send_pong(event["line"].args[0]) - -def handle_001(event): - event["server"].socket.enable_write_throttle() - event["server"].name = event["line"].source.hostmask - event["server"].set_own_nickname(event["line"].args[0]) - event["server"].send_whois(event["server"].nickname) - event["server"].send_mode(event["server"].nickname) - event["server"].connected = True - -def handle_005(events, event): - isupport_list = event["line"].args[1:-1] - isupport = {} - - for i, item in enumerate(isupport_list): - key, sep, value = item.partition("=") - if value: - for match in RE_ISUPPORT_ESCAPE.finditer(value): - char = codecs.decode(match.group(1), "hex").decode("ascii") - value.replace(match.group(0), char) - - if sep: - isupport[key] = value - else: - isupport[key] = None - event["server"].isupport.update(isupport) - - if "NAMESX" in isupport and not event["server"].has_capability_str( - "multi-prefix"): - event["server"].send_raw("PROTOCTL NAMESX") - - if "PREFIX" in isupport: - modes, symbols = isupport["PREFIX"][1:].split(")", 1) - event["server"].prefix_symbols.clear() - event["server"].prefix_modes.clear() - for symbol, mode in zip(symbols, modes): - event["server"].prefix_symbols[symbol] = mode - event["server"].prefix_modes[mode] = symbol - - if "CHANMODES" in isupport: - modes = isupport["CHANMODES"].split(",", 3) - event["server"].channel_list_modes = list(modes[0]) - event["server"].channel_parametered_modes = list(modes[1]) - event["server"].channel_setting_modes = list(modes[2]) - event["server"].channel_modes = list(modes[3]) - if "CHANTYPES" in isupport: - event["server"].channel_types = list(isupport["CHANTYPES"]) - if "CASEMAPPING" in isupport: - event["server"].case_mapping = isupport["CASEMAPPING"] - if "STATUSMSG" in isupport: - event["server"].statusmsg = list(isupport["STATUSMSG"]) - - events.on("received.005").call(isupport=isupport, - server=event["server"]) - -def handle_004(event): - event["server"].version = event["line"].args[2] - -def motd_start(event): - event["server"].motd_lines.clear() -def motd_line(event): - event["server"].motd_lines.append(event["line"].args[1]) - -def _own_modes(server, modes): - mode_chunks = RE_MODES.findall(modes) - for chunk in mode_chunks: - remove = chunk[0] == "-" - for mode in chunk[1:]: - server.change_own_mode(remove, mode) - -def mode(events, event): - user = event["server"].get_user(event["line"].source.nickname) - target = event["line"].args[0] - is_channel = event["server"].is_channel(target) - if is_channel: - channel = event["server"].channels.get(target) - modes = event["line"].args[1] - args = event["line"].args[2:] - - new_modes = channel.parse_modes(modes, args[:]) - - events.on("received.mode.channel").call(modes=new_modes, - channel=channel, server=event["server"], user=user, modes_str=modes, - args_str=args) - elif event["server"].is_own_nickname(target): - modes = event["line"].args[1] - _own_modes(event["server"], modes) - - events.on("self.mode").call(modes=modes, server=event["server"]) - event["server"].send_who(event["server"].nickname) - -def handle_221(event): - _own_modes(event["server"], event["line"].args[1]) - -def invite(events, event): - target_channel = event["line"].args[1] - user = event["server"].get_user(event["line"].source.nickname) - target_user = event["server"].get_user(event["line"].args[0]) - events.on("received.invite").call(user=user, target_channel=target_channel, - server=event["server"], target_user=target_user) - -def handle_352(events, event): - nickname = event["line"].args[5] - username = event["line"].args[2] - hostname = event["line"].args[3] - - if event["server"].is_own_nickname(nickname): - event["server"].username = username - event["server"].hostname = hostname - - target = event["server"].get_user(nickname) - target.username = username - target.hostname = hostname - events.on("received.who").call(server=event["server"], - user=target) - -def handle_354(events, event): - if event["line"].args[1] == "111": - nickname = event["line"].args[4] - username = event["line"].args[2] - hostname = event["line"].args[3] - realname = event["line"].args[6] - account = event["line"].args[5] - - if event["server"].is_own_nickname(nickname): - event["server"].username = username - event["server"].hostname = hostname - event["server"].realname = realname - - target = event["server"].get_user(nickname) - target.username = username - target.hostname = hostname - target.realname = realname - if not account == "0": - target.account = account - else: - target.account = None - events.on("received.whox").call(server=event["server"], - user=target) - -def _nick_in_use(server): - new_nick = "%s|" % server.connection_params.nickname - server.send_nick(new_nick) - -def handle_433(event): - _nick_in_use(event["server"]) -def handle_437(event): - _nick_in_use(event["server"]) diff --git a/modules/line_handler/ircv3.py b/modules/line_handler/ircv3.py deleted file mode 100644 index a9d740ed..00000000 --- a/modules/line_handler/ircv3.py +++ /dev/null @@ -1,138 +0,0 @@ -from src import utils - -CAPABILITIES = [ - utils.irc.Capability("multi-prefix"), - utils.irc.Capability("chghost"), - utils.irc.Capability("invite-notify"), - utils.irc.Capability("account-tag"), - utils.irc.Capability("account-notify"), - utils.irc.Capability("extended-join"), - utils.irc.Capability("away-notify"), - utils.irc.Capability("userhost-in-names"), - utils.irc.Capability("message-tags", "draft/message-tags-0.2"), - utils.irc.Capability("cap-notify"), - utils.irc.Capability("batch"), - utils.irc.Capability(None, "draft/rename", alias="rename"), - utils.irc.Capability(None, "draft/setname", alias="setname") -] - -def _cap_depend_sort(caps, server_caps): - sorted_caps = [] - - caps_copy = {alias: cap.copy() for alias, cap in caps.items()} - - for cap in caps.values(): - if not cap.available(server_caps): - del caps_copy[cap.alias] - - while True: - remove = [] - for alias, cap in caps_copy.items(): - for depend_alias in cap.depends_on: - if not depend_alias in caps_copy: - remove.append(alias) - if remove: - for alias in remove: - del caps_copy[alias] - else: - break - - while caps_copy: - fulfilled = [] - for cap in caps_copy.values(): - remove = [] - for depend_alias in cap.depends_on: - if depend_alias in sorted_caps: - remove.append(depend_alias) - for remove_cap in remove: - cap.depends_on.remove(remove_cap) - - if not cap.depends_on: - fulfilled.append(cap.alias) - for fulfilled_cap in fulfilled: - del caps_copy[fulfilled_cap] - sorted_caps.append(fulfilled_cap) - return [caps[alias] for alias in sorted_caps] - -def _cap_match(server, caps): - matched_caps = {} - blacklist = server.get_setting("blacklisted-caps", []) - - cap_aliases = {} - for cap in caps: - if not cap.alias in blacklist: - cap_aliases[cap.alias] = cap - - sorted_caps = _cap_depend_sort(cap_aliases, server.server_capabilities) - - for cap in sorted_caps: - available = cap.available(server.server_capabilities) - if available and not server.has_capability(cap): - matched_caps[available] = cap - return matched_caps - -def cap(exports, events, event): - capabilities = utils.parse.keyvalue(event["line"].args[-1]) - subcommand = event["line"].args[1].upper() - is_multiline = len(event["line"].args) > 3 and event["line"].args[2] == "*" - - if subcommand == "DEL": - for capability in capabilities.keys(): - event["server"].agreed_capabilities.discard(capability) - if capability in event["server"].server_capabilities: - del event["server"].server_capabilities[capability] - - events.on("received.cap.del").call(server=event["server"], - capabilities=capabilities) - elif subcommand == "ACK": - for cap_name, cap_args in capabilities.items(): - if cap_name[0] == "-": - event["server"].agreed_capabilities.discard(cap_name[1:]) - else: - event["server"].agreed_capabilities.add(cap_name) - - events.on("received.cap.ack").call(capabilities=capabilities, - server=event["server"]) - - if subcommand == "LS" or subcommand == "NEW": - event["server"].server_capabilities.update(capabilities) - if not is_multiline: - server_caps = list(event["server"].server_capabilities.keys()) - all_caps = CAPABILITIES[:] - - export_caps = [cap.copy() for cap in exports.get_all("cap")] - all_caps.extend(export_caps) - - module_caps = events.on("received.cap.ls").call( - capabilities=event["server"].server_capabilities, - server=event["server"]) - module_caps = list(filter(None, module_caps)) - all_caps.extend(module_caps) - - matched_caps = _cap_match(event["server"], all_caps) - event["server"].capability_queue.update(matched_caps) - - if event["server"].capability_queue: - event["server"].send_capability_queue() - else: - event["server"].send_capability_end() - - - if subcommand == "ACK" or subcommand == "NAK": - ack = subcommand == "ACK" - for capability in capabilities: - if capability in event["server"].capabilities_requested: - cap_obj = event["server"].capabilities_requested[capability] - del event["server"].capabilities_requested[capability] - if ack: - cap_obj.ack() - else: - cap_obj.nak() - - if (not event["server"].capabilities_requested and - not event["server"].waiting_for_capabilities()): - event["server"].send_capability_end() - -def authenticate(events, event): - events.on("received.authenticate").call(message=event["line"].args[0], - server=event["server"]) diff --git a/modules/line_handler/message.py b/modules/line_handler/message.py deleted file mode 100644 index fa36dbc2..00000000 --- a/modules/line_handler/message.py +++ /dev/null @@ -1,109 +0,0 @@ -from src import IRCBuffer, utils - -def _from_self(server, source): - if source: - return server.is_own_nickname(source.nickname) - else: - return False - -def message(events, event): - from_self = _from_self(event["server"], event["line"].source) - if from_self == None: - return - - direction = "send" if from_self else "received" - - target_str = event["line"].args[0] - - message = None - if len(event["line"].args) > 1: - message = event["line"].args[1] - - if not from_self and ( - not event["line"].source or - not event["server"].name or - event["line"].source.hostmask == event["server"].name or - target_str == "*"): - if event["line"].source: - event["server"].name = event["line"].source.hostmask - - events.on("received.server-notice").call(message=message, - message_split=message.split(" "), server=event["server"]) - return - - if from_self: - user = event["server"].get_user(event["server"].nickname) - else: - user = event["server"].get_user(event["line"].source.nickname, - username=event["line"].source.username, - hostname=event["line"].source.hostname) - - # strip prefix_symbols from the start of target, for when people use - # e.g. 'PRIVMSG +#channel :hi' which would send a message to only - # voiced-or-above users - target = target_str.lstrip("".join(event["server"].statusmsg)) - - is_channel = event["server"].is_channel(target) - - if is_channel: - if not target in event["server"].channels: - return - target_obj = event["server"].channels.get(target) - else: - target_obj = event["server"].get_user(target) - - kwargs = {"server": event["server"], "target": target_obj, - "target_str": target_str, "user": user, "tags": event["line"].tags, - "is_channel": is_channel, "from_self": from_self, "line": event["line"]} - - action = False - - if message: - ctcp_message = utils.irc.parse_ctcp(message) - - if ctcp_message: - if (not ctcp_message.command == "ACTION" or not - event["line"].command == "PRIVMSG"): - if event["line"].command == "PRIVMSG": - ctcp_action = "request" - else: - ctcp_action = "response" - events.on(direction).on("ctcp").on(ctcp_action).call( - message=ctcp_message.message, **kwargs) - events.on(direction).on("ctcp").on(ctcp_action).on( - ctcp_message.command).call(message=ctcp_message.message, - **kwargs) - return - else: - message = ctcp_message.message - action = True - - if not message == None: - kwargs["message"] = message - kwargs["message_split"] = message.split(" ") - kwargs["action"] = action - - event_type = event["line"].command.lower() - if event_type == "privmsg": - event_type = "message" - - context = "channel" if is_channel else "private" - hook = events.on(direction).on(event_type).on(context) - - buffer_line = None - if message: - buffer_line = IRCBuffer.BufferLine(user.nickname, message, action, - event["line"].tags, from_self, event["line"].command) - - buffer_obj = target_obj - if is_channel: - hook.call(channel=target_obj, buffer_line=buffer_line, **kwargs) - else: - buffer_obj = target_obj - if not from_self: - buffer_obj = user - - hook.call(buffer_line=buffer_line, **kwargs) - - if buffer_line: - buffer_obj.buffer.add(buffer_line) diff --git a/modules/line_handler/user.py b/modules/line_handler/user.py deleted file mode 100644 index d1592cd7..00000000 --- a/modules/line_handler/user.py +++ /dev/null @@ -1,102 +0,0 @@ -from src import utils - -def handle_311(event): - nickname = event["line"].args[1] - username = event["line"].args[2] - hostname = event["line"].args[3] - realname = event["line"].args[4] - - if event["server"].is_own_nickname(nickname): - event["server"].username = username - event["server"].hostname = hostname - event["server"].realname = realname - - target = event["server"].get_user(nickname) - target.username = username - target.hostname = hostname - target.realname = realname - -def quit(events, event): - nickname = None - if event["direction"] == utils.Direction.Recv: - nickname = event["line"].source.nickname - reason = event["line"].args.get(0) - - if event["direction"] == utils.Direction.Recv: - nickname = event["line"].source.nickname - if (not event["server"].is_own_nickname(nickname) and - not event["line"].source.hostmask == "*"): - user = event["server"].get_user(nickname) - events.on("received.quit").call(reason=reason, user=user, - server=event["server"]) - event["server"].remove_user(user) - else: - event["server"].disconnect() - else: - events.on("send.quit").call(reason=reason, server=event["server"]) - -def nick(events, event): - new_nickname = event["line"].args.get(0) - user = event["server"].get_user(event["line"].source.nickname) - old_nickname = user.nickname - user.set_nickname(new_nickname) - event["server"].change_user_nickname(old_nickname, new_nickname) - - if not event["server"].is_own_nickname(event["line"].source.nickname): - events.on("received.nick").call(new_nickname=new_nickname, - old_nickname=old_nickname, user=user, server=event["server"]) - else: - events.on("self.nick").call(server=event["server"], - new_nickname=new_nickname, old_nickname=old_nickname) - event["server"].set_own_nickname(new_nickname) - -def away(events, event): - user = event["server"].get_user(event["line"].source.nickname) - message = event["line"].args.get(0) - if message: - user.away = True - user.away_message = message - events.on("received.away.on").call(user=user, server=event["server"], - message=message) - else: - user.away = False - user.away_message = None - events.on("received.away.off").call(user=user, server=event["server"]) - -def chghost(events, event): - nickname = event["line"].source.nickname - username = event["line"].args[0] - hostname = event["line"].args[1] - - if event["server"].is_own_nickname(nickname): - event["server"].username = username - event["server"].hostname = hostname - - target = event["server"].get_user(nickname) - events.on("received.chghost").call(user=target, server=event["server"], - username=username, hostname=hostname) - - target.username = username - target.hostname = hostname - -def setname(event): - nickname = event["line"].source.nickname - realname = event["line"].args[0] - - user = event["server"].get_user(nickname) - user.realname = realname - - if event["server"].is_own_nickname(nickname): - event["server"].realname = realname - -def account(events, event): - user = event["server"].get_user(event["line"].source.nickname) - - if not event["line"].args[0] == "*": - user.account = event["line"].args[0] - events.on("received.account.login").call(user=user, - server=event["server"], account=event["line"].args[0]) - else: - user.account = None - events.on("received.account.logout").call(user=user, - server=event["server"]) diff --git a/modules/modules.py b/modules/modules.py deleted file mode 100644 index a93afaea..00000000 --- a/modules/modules.py +++ /dev/null @@ -1,122 +0,0 @@ -#--depends-on commands -#--depends-on permissions - -from src import ModuleManager, utils - -class Module(ModuleManager.BaseModule): - def _catch(self, name, func): - try: - return func() - except ModuleManager.ModuleNotFoundException: - raise utils.EventError("Module '%s' not found" % name) - except ModuleManager.ModuleNotLoadedException: - raise utils.EventError("Module '%s' isn't loaded" % name) - except ModuleManager.ModuleWarning as warning: - raise utils.EventError("Module '%s' not loaded: %s" % ( - name, str(warning))) - except Exception as e: - raise utils.EventError("Failed to reload module '%s': %s" % ( - name, str(e))) - - @utils.hook("received.command.loadmodule", min_args=1) - def load(self, event): - """ - :help: Load a module - :usage: <module name> - :permission: load-module - """ - name = event["args_split"][0].lower() - if name in self.bot.modules.modules: - raise utils.EventError("Module '%s' is already loaded" % name) - definition = self._catch(name, - lambda: self.bot.modules.find_module(name)) - - self._catch(name, lambda: self.bot.modules.load_module(self.bot, definition)) - event["stdout"].write("Loaded '%s'" % name) - - @utils.hook("received.command.unloadmodule", min_args=1) - def unload(self, event): - """ - :help: Unload a module - :usage: <module name> - :permission: unload-module - """ - name = event["args_split"][0].lower() - if not name in self.bot.modules.modules: - raise utils.EventError("Module '%s' isn't loaded" % name) - - self._catch(name, lambda: self.bot.modules.unload_module(name)) - event["stdout"].write("Unloaded '%s'" % name) - - def _reload(self, name): - self.bot.modules.unload_module(name) - definition = self._catch(name, - lambda: self.bot.modules.find_module(name)) - self.bot.modules.load_module(self.bot, definition) - @utils.hook("received.command.reloadmodule", min_args=1) - def reload(self, event): - """ - :help: Reload a module - :usage: <module name> - :permission: reload-module - """ - name = event["args_split"][0].lower() - - self._catch(name, lambda: self._reload(name)) - event["stdout"].write("Reloaded '%s'" % name) - - @utils.hook("received.command.reloadallmodules") - def reload_all(self, event): - """ - :help: Reload all modules - :permission: reload-all-modules - """ - result = self.bot.try_reload_modules() - if result.success: - event["stdout"].write(result.message) - else: - event["stderr"].write(result.message) - - def _get_blacklist(self): - return self.bot.config.get_list("module-blacklist") - def _save_blacklist(self, modules): - self.bot.config.set_list("module-blacklist", modules) - self.bot.config.save() - - @utils.hook("received.command.enablemodule") - @utils.kwarg("min_args", 1) - @utils.kwarg("help", "Remove a module from the module blacklist") - @utils.kwarg("usage", "<module>") - @utils.kwarg("permission", "enable-module") - def enable(self, event): - name = event["args_split"][0].lower() - - blacklist = self._get_blacklist() - if not name in blacklist: - raise utils.EventError("Module '%s' isn't disabled" % name) - - blacklist.remove(name) - self._save_blacklist(blacklist) - event["stdout"].write("Module '%s' has been enabled and can now " - "be loaded" % name) - - @utils.hook("received.command.disablemodule") - @utils.kwarg("min_args", 1) - @utils.kwarg("help", "Add a module to the module blacklist") - @utils.kwarg("usage", "<module>") - @utils.kwarg("permission", "disable-module") - def disable(self, event): - name = event["args_split"][0].lower() - and_unloaded = "" - if name in self.bot.modules.modules: - self.bot.modules.unload_module(name) - and_unloaded = " and unloaded" - - blacklist = self._get_blacklist() - if name in blacklist: - raise utils.EventError("Module '%s' is already disabled" % name) - - blacklist.append(name) - self._save_blacklist(blacklist) - event["stdout"].write("Module '%s' has been disabled%s" % ( - name, and_unloaded)) diff --git a/modules/more.py b/modules/more.py deleted file mode 100644 index 52849938..00000000 --- a/modules/more.py +++ /dev/null @@ -1,23 +0,0 @@ -from src import EventManager, ModuleManager, utils - -class Module(ModuleManager.BaseModule): - @utils.hook("new.user") - @utils.hook("new.channel") - def new(self, event): - obj = event.get("user", event.get("channel", None)) - obj._last_stdout = None - obj._last_stderr = None - - @utils.hook("postprocess.command") - @utils.kwarg("priority", EventManager.PRIORITY_MONITOR) - def postprocess(self, event): - if event["stdout"].has_text(): - event["target"]._last_stdout = event["stdout"] - if event["stderr"].has_text(): - event["target"]._last_stderr = event["stderr"] - - @utils.hook("received.command.more") - def more(self, event): - last_stdout = event["target"]._last_stdout - if last_stdout and last_stdout.has_text(): - event["stdout"].write_lines(last_stdout.get_all()) diff --git a/modules/nick_regain.py b/modules/nick_regain.py deleted file mode 100644 index cf1dfa48..00000000 --- a/modules/nick_regain.py +++ /dev/null @@ -1,48 +0,0 @@ -from src import ModuleManager, utils - -class Module(ModuleManager.BaseModule): - def _done_connecting(self, server): - target_nick = server.connection_params.nickname - if not server.irc_equals(server.nickname, target_nick): - if "MONITOR" in server.isupport: - server.send_raw("MONITOR + %s" % target_nick) - else: - self.timers.add("ison-check", self._ison_check, 30, - server=server) - - @utils.hook("received.376") - def end_of_motd(self, event): - self._done_connecting(event["server"]) - @utils.hook("received.422") - def no_motd(self, event): - self._done_connecting(event["server"]) - - @utils.hook("self.nick") - def self_nick(self, event): - target_nick = event["server"].connection_params.nickname - if event["server"].irc_equals(event["new_nickname"], target_nick): - if "MONITOR" in event["server"].isupport: - event["server"].send_raw("MONITOR - %s " % target_nick) - - @utils.hook("received.731") - def mon_offline(self, event): - target_nick = event["server"].connection_params.nickname - nicks = event["line"].args[1].split(",") - nicks = [event["server"].irc_lower(n) for n in nicks] - if event["server"].irc_lower(target_nick) in nicks: - event["server"].send_nick(target_nick) - - def _ison_check(self, timer): - server = timer.kwargs["server"] - target_nick = server.connection_params.nickname - if not server.irc_equals(server.nickname, target_nick): - server.send_raw("ISON %s" % target_nick) - timer.redo() - - @utils.hook("received.303") - def ison_response(self, event): - target_nick = event["server"].connection_params.nickname - if not event["line"].args[1] and not event["server"].irc_equals( - event["server"].nickname, target_nick): - event["server"].send_nick(target_nick) - diff --git a/modules/perform.py b/modules/perform.py deleted file mode 100644 index 832cab54..00000000 --- a/modules/perform.py +++ /dev/null @@ -1,76 +0,0 @@ -#--depends-on commands -#--depends-on permissions - -from src import EventManager, IRCLine, ModuleManager, utils - -class Module(ModuleManager.BaseModule): - def _execute(self, server, commands, **kwargs): - for command in commands: - line = command.format(**kwargs) - if IRCLine.is_human(line): - line = IRCLine.parse_human(line) - else: - line = IRCLine.parse_line(line) - server.send(line) - - @utils.hook("received.001", priority=EventManager.PRIORITY_URGENT) - def on_connect(self, event): - commands = event["server"].get_setting("perform", []) - self._execute(event["server"], commands, NICK=event["server"].nickname) - - @utils.hook("self.join", priority=EventManager.PRIORITY_URGENT) - def on_join(self, event): - commands = event["channel"].get_setting("perform", []) - self._execute(event["server"], commands, NICK=event["server"].nickname, - CHAN=event["channel"].name) - - def _perform(self, target, args_split): - subcommand = args_split[0].lower() - current_perform = target.get_setting("perform", []) - if subcommand == "list": - return "Configured commands: %s" % ", ".join(current_perform) - - message = None - if subcommand == "add": - if not len(args_split) > 1: - raise utils.EventError("Please provide a raw command to add") - current_perform.append(" ".join(args_split[1:])) - message = "Added command" - elif subcommand == "remove": - if not len(args_split) > 1: - raise utils.EventError("Please provide an index to remove") - if not args_split[1].isdigit(): - raise utils.EventError("Please provide a number") - index = int(args_split[1]) - if not index < len(current_perform): - raise utils.EventError("Index out of bounds") - current_perform.pop(index) - message = "Removed command" - else: - raise utils.EventError("Unknown subcommand '%s'" % subcommand) - - target.set_setting("perform", current_perform) - return message - - @utils.hook("received.command.perform", min_args=1) - @utils.kwarg("min_args", 1) - @utils.kwarg("help", "Edit on-connect command configuration") - @utils.kwarg("usage", "list") - @utils.kwarg("usage", "add <raw command>") - @utils.kwarg("usage", "remove <index>") - @utils.kwarg("permission", "perform") - def perform(self, event): - event["stdout"].write(self._perform(event["server"], - event["args_split"])) - - @utils.hook("received.command.cperform", min_args=1) - @utils.kwarg("min_args", 1) - @utils.kwarg("channel_only", True) - @utils.kwarg("help", "Edit channel on-join command configuration") - @utils.kwarg("usage", "list") - @utils.kwarg("usage", "add <raw command>") - @utils.kwarg("usage", "remove <index>") - @utils.kwarg("permission", "cperform") - def cperform(self, event): - event["stdout"].write(self._perform(event["target"], - event["args_split"])) diff --git a/modules/permissions/__init__.py b/modules/permissions/__init__.py deleted file mode 100644 index 0559774c..00000000 --- a/modules/permissions/__init__.py +++ /dev/null @@ -1,357 +0,0 @@ -#--depends-on commands - -import base64, binascii, os -import scrypt -from src import ModuleManager, utils - -HOSTMASKS_SETTING = "hostmask-account" -NO_PERMISSION = "You do not have permission to do that" - -class Module(ModuleManager.BaseModule): - def on_load(self): - self.exports.add("is-identified", self._is_identified) - self.exports.add("account-name", self._account_name) - - @utils.hook("new.server") - def new_server(self, event): - event["server"]._hostmasks = {} - - for account, user_hostmasks in event["server"].get_all_user_settings( - HOSTMASKS_SETTING): - for hostmask in user_hostmasks: - self._add_hostmask(event["server"], - utils.irc.hostmask_parse(hostmask), account) - - def _add_hostmask(self, server, hostmask, account): - server._hostmasks[hostmask.original] = (hostmask, account) - def _remove_hostmask(self, server, hostmask): - if hostmask in server._hostmasks: - del server._hostmasks[hostmask] - - def _make_salt(self): - return base64.b64encode(os.urandom(64)).decode("utf8") - - def _random_password(self): - return binascii.hexlify(os.urandom(32)).decode("utf8") - - def _make_hash(self, password, salt=None): - salt = salt or self._make_salt() - hash = base64.b64encode(scrypt.hash(password, salt)).decode("utf8") - return hash, salt - - def _get_hash(self, server, account): - hash, salt = server.get_user(account).get_setting("authentication", - (None, None)) - return hash, salt - - def _master_password(self): - master_password = self._random_password() - hash, salt = self._make_hash(master_password) - self.bot.set_setting("master-password", [hash, salt]) - return master_password - @utils.hook("control.master-password") - def command_line(self, event): - master_password = self._master_password() - return "One-time master password: %s" % master_password - - def _has_identified(self, server, user, account): - user._id_override = server.get_user_id(account) - def _is_identified(self, user): - return not user._id_override == None - def _signout(self, user): - user._id_override = None - - def _find_hostmask(self, server, user): - user_hostmask = user.hostmask() - for hostmask, (hostmask_pattern, account) in server._hostmasks.items(): - if utils.irc.hostmask_match(user_hostmask, hostmask_pattern): - return (hostmask, account) - def _specific_hostmask(self, server, hostmask, account): - for user in server.users.values(): - if utils.irc.hostmask_match(user.hostmask(), hostmask): - if account == None: - user._hostmask_account = None - self._signout(user) - else: - user._hostmask_account = (hostmask, account) - self._has_identified(server, user, account) - - def _account_name(self, user): - if not user.account == None: - return user.account - elif not user._account_override == None: - return user._account_override - elif not user._hostmask_account == None: - return user._hostmask_account[1] - - @utils.hook("new.user") - def new_user(self, event): - event["user"]._hostmask_account = None - event["user"]._account_override = None - event["user"]._master_admin = False - - def _set_hostmask(self, server, user): - account = self._find_hostmask(server, user) - if not account == None: - hostmask, account = account - user._hostmask_account = (hostmask, account) - self._has_identified(server, user, account) - - @utils.hook("received.chghost") - @utils.hook("received.nick") - @utils.hook("received.who") - @utils.hook("received.whox") - @utils.hook("received.message.private") - def chghost(self, event): - if not self._is_identified(event["user"]): - self._set_hostmask(event["server"], event["user"]) - @utils.hook("received.whox") - @utils.hook("received.account") - @utils.hook("received.account.login") - @utils.hook("received.account.logout") - @utils.hook("received.join") - def check_account(self, event): - if not self._is_identified(event["user"]): - if event["user"].account: - self._has_identified(event["server"], event["user"], - event["user"].account) - else: - self._set_hostmask(event["server"], event["user"]) - - def _get_permissions(self, user): - if self._is_identified(user): - return user.get_setting("permissions", []) - return [] - - def _has_permission(self, user, permission): - if user._master_admin: - return True - - permissions = self._get_permissions(user) - if permission in permissions: - return True - else: - permission_parts = permission.split(".") - for user_permission in permissions: - user_permission_parts = user_permission.split(".") - for i, part in enumerate(permission_parts): - last = i==(len(permission_parts)-1) - user_last = i==(len(user_permission_parts)-1) - if not permission_parts[i] == user_permission_parts[i]: - if user_permission_parts[i] == "*" and user_last: - return True - else: - break - else: - if last and user_last: - return True - return False - - @utils.hook("received.command.masterlogin") - @utils.kwarg("min_args", 1) - @utils.kwarg("private_only", True) - def master_login(self, event): - saved_hash, saved_salt = self.bot.get_setting("master-password", - (None, None)) - if saved_hash and saved_salt: - given_hash, _ = self._make_hash(event["args"], saved_salt) - if utils.security.constant_time_compare(given_hash, saved_hash): - self.bot.del_setting("master-password") - event["user"]._master_admin = True - event["stdout"].write("Master login successful") - return - event["stderr"].write("Master login failed") - - @utils.hook("received.command.mypermissions") - @utils.kwarg("authenticated", True) - def my_permissions(self, event): - """ - :help: Show your permissions - """ - permissions = event["user"].get_setting("permissions", []) - event["stdout"].write("Your permissions: %s" % ", ".join(permissions)) - - - @utils.hook("received.command.register", private_only=True, min_args=1) - @utils.kwarg("min_args", 1) - @utils.kwarg("private_only", True) - @utils.kwarg("help", "Register your nickname") - @utils.kwarg("usage", "<password>") - def register(self, event): - hash, salt = self._get_hash(event["server"], event["user"].nickname) - if not hash and not salt: - password = event["args"] - hash, salt = self._make_hash(password) - event["user"].set_setting("authentication", [hash, salt]) - - event["user"]._account_override = event["user"].nickname - self._has_identified(event["server"], event["user"], - event["user"].nickname) - - event["stdout"].write("Nickname registered successfully") - else: - event["stderr"].write("This nickname is already registered") - - @utils.hook("received.command.identify", private_only=True, min_args=1) - @utils.kwarg("min_args", 1) - @utils.kwarg("private_only", True) - @utils.kwarg("help", "Identify for your current nickname") - @utils.kwarg("usage", "[account] <password>") - def identify(self, event): - if not event["user"].channels: - raise utils.EventError("You must share at least one channel " - "with me before you can identify") - - if not self._is_identified(event["user"]): - if len(event["args_split"]) > 1: - account = event["args_split"][0] - password = " ".join(event["args_split"][1:]) - else: - account = event["user"].nickname - password = event["args"] - - hash, salt = self._get_hash(event["server"], account) - if hash and salt: - attempt, _ = self._make_hash(password, salt) - if utils.security.constant_time_compare(attempt, hash): - event["user"]._account_override = account - self._has_identified(event["server"], event["user"], account) - - event["stdout"].write("Correct password, you have " - "been identified as %s." % account) - self.events.on("internal.identified").call( - user=event["user"]) - else: - event["stderr"].write("Incorrect password for '%s'" % - account) - else: - event["stderr"].write("Account '%s' is not registered" % - account) - else: - event["stderr"].write("You are already identified as %s" % - self._account_name(event["user"])) - - @utils.hook("received.command.permission") - @utils.kwarg("min_args", 2) - @utils.kwarg("usage", "list <account>") - @utils.kwarg("usage", "clear <account>") - @utils.kwarg("usage", "add <account> <permission>") - @utils.kwarg("usage", "remove <account> <permission>") - @utils.kwarg("permission", "permissions.change") - def permission(self, event): - subcommand = event["args_split"][0].lower() - account = event["args_split"][1] - target_user = event["server"].get_user(account) - - if subcommand == "list": - event["stdout"].write("Permissions for %s: %s" % ( - account, ", ".join(self._get_permissions(target_user)))) - elif subcommand == "clear": - if not self._get_permissions(target_user): - raise utils.EventError("%s has no permissions" % account) - target_user.del_setting("permissions") - event["stdout"].write("Cleared permissions for %s" % account) - else: - permissions = event["args_split"][2:] - if not permissions: - raise utils.EventError("Please provide at least 1 permission") - user_permissions = self._get_permissions(target_user) - - if subcommand == "add": - new = list(set(permissions)-set(user_permissions)) - if not new: - raise utils.EventError("No new permissions to give") - target_user.set_setting("permissions", user_permissions+new) - event["stdout"].write("Gave %s new permissions: %s" % - (account, ", ".join(new))) - elif subcommand == "remove": - permissions_set = set(permissions) - user_permissions_set = set(user_permissions) - removed = list(user_permissions_set&permissions_set) - if not (user_permissions_set & permissions_set): - raise utils.EventError("New permissions to remove") - change = list(user_permissions_set - permissions_set) - - if not change: - target_user.del_setting("permissions") - else: - target_user.set_setting("permissions", change) - event["stdout"].write("Removed permissions from %s: %s" % - (account, ", ".join(change))) - else: - raise utils.EventError("Unknown subcommand %s" % subcommand) - - @utils.hook("received.command.hostmask") - @utils.kwarg("min_args", 1) - @utils.kwarg("authenticated", True) - @utils.kwarg("usage", "list") - @utils.kwarg("usage", "add [hostmask]") - @utils.kwarg("usage", "remove [hostmask]") - def hostmask(self, event): - subcommand = event["args_split"][0].lower() - hostmasks = event["user"].get_setting(HOSTMASKS_SETTING, []) - - if subcommand == "list": - event["stdout"].write("Your hostmasks: %s" % ", ".join(hostmasks)) - else: - if event["args_split"][1:]: - hostmask = event["args_split"][1] - else: - hostmask = "*!%s" % event["user"].userhost() - account = self._account_name(event["user"]) - - if subcommand == "add": - if hostmask in hostmasks: - raise utils.EventError( - "Hostmask %s is already on your account" % hostmask) - hostmasks.append(hostmask) - event["user"].set_setting(HOSTMASKS_SETTING, hostmasks) - - hostmask_obj = utils.irc.hostmask_parse(hostmaks) - self._specific_hostmask(event["server"], hostmask_obj, account) - self._add_hostmask(event["server"], hostmask_obj, account) - - event["stdout"].write("Added %s to your hostmasks" % hostmask) - elif subcommand == "remove": - if not hostmask in hostmasks: - raise utils.EventError("Hostmask %s is not on your account" - % hostmask) - while hostmask in hostmasks: - hostmasks.remove(hostmask) - event["user"].set_setting(HOSTMASKS_SETTING, hostmasks) - - self._specific_hostmask(event["server"], hostmask, None) - self._remove_hostmask(event["server"], hostmask) - - event["stdout"].write("Removed %s from your hostmasks" - % hostmask) - else: - raise utils.EventError("Unknown subcommand %s" % subcommand) - - def _assert(self, allowed): - if allowed: - return utils.consts.PERMISSION_FORCE_SUCCESS, None - else: - return utils.consts.PERMISSION_ERROR, NO_PERMISSION - - @utils.hook("preprocess.command") - def preprocess_command(self, event): - allowed = None - permission = event["hook"].get_kwarg("permission", None) - authenticated = event["hook"].get_kwarg("authenticated", False) - if not permission == None: - allowed = self._has_permission(event["user"], permission) - elif authenticated: - allowed = self._is_identified(event["user"]) - else: - return - - return self._assert(allowed) - - @utils.hook("check.command.permission") - def check_permission(self, event): - return self._assert( - self._has_permission(event["user"], event["request_args"][0])) - @utils.hook("check.command.authenticated") - def check_authenticated(self, event): - return self._assert(self._is_identified(event["user"])) diff --git a/modules/print_activity.py b/modules/print_activity.py deleted file mode 100644 index e6a34992..00000000 --- a/modules/print_activity.py +++ /dev/null @@ -1,48 +0,0 @@ -#--depends-on config -#--depends-on format_activity - -import datetime -from src import EventManager, ModuleManager, utils - -@utils.export("botset", - utils.BoolSetting("print-motd", "Set whether I print /motd")) -@utils.export("botset", utils.BoolSetting("pretty-activity", - "Whether or not to pretty print activity")) -@utils.export("channelset", utils.BoolSetting("print", - "Whether or not to print activity a channel to logs")) -class Module(ModuleManager.BaseModule): - def _print(self, event): - if (event["channel"] and - not event["channel"].get_setting("print", True)): - return - - line = event["line"] - if event["pretty"] and self.bot.get_setting("pretty-activity", False): - line = event["pretty"] - - context = (":%s" % event["context"]) if event["context"] else "" - self.bot.log.info("%s%s | %s", [ - str(event["server"]), context, utils.irc.parse_format(line)]) - - @utils.hook("formatted.message.channel") - @utils.hook("formatted.notice.channel") - @utils.hook("formatted.notice.private") - @utils.hook("formatted.join") - @utils.hook("formatted.part") - @utils.hook("formatted.nick") - @utils.hook("formatted.server-notice") - @utils.hook("formatted.invite") - @utils.hook("formatted.mode.channel") - @utils.hook("formatted.topic") - @utils.hook("formatted.topic-timestamp") - @utils.hook("formatted.kick") - @utils.hook("formatted.quit") - @utils.hook("formatted.rename") - @utils.hook("formatted.chghost") - def formatted(self, event): - self._print(event) - - @utils.hook("formatted.motd") - def motd(self, event): - if self.bot.get_setting("print-motd", True): - self._print(event) diff --git a/modules/proxy.py b/modules/proxy.py deleted file mode 100644 index 1bcaebd1..00000000 --- a/modules/proxy.py +++ /dev/null @@ -1,38 +0,0 @@ -import typing, urllib.parse -import socks -from src import ModuleManager, utils - -TYPES = { - "socks4": socks.SOCKS4, - "socks5": socks.SOCKS5, - "http": socks.HTTP -} - -def _parse(value): - parsed = urllib.parse.urlparse(value) - if parsed.scheme in TYPES and parsed.hostname: - return value - -@utils.export("serverset", utils.FunctionSetting(_parse, "proxy", - "Proxy configuration for the current server", - example="socks5://localhost:9050")) -class Module(ModuleManager.BaseModule): - @utils.hook("preprocess.connect") - def new_server(self, event): - proxy = event["server"].get_setting("proxy", None) - if proxy: - proxy_parsed = urllib.parse.urlparse(proxy) - type = TYPES.get(proxy_parsed.scheme) - - if type == None: - raise ValueError("Invalid proxy type '%s' for '%s'" % - (proxy_parsed.scheme, str(event["server"]))) - - event["server"].socket._make_socket = self._socket_factory( - type, proxy_parsed.hostname, proxy_parsed.port) - - def _socket_factory(self, ptype, phost, pport): - def _(host, port, bind, timeout): - return socks.create_connection((host, port), timeout, bind, - ptype, phost, pport) - return _ diff --git a/modules/signals.py b/modules/signals.py deleted file mode 100644 index 921b483c..00000000 --- a/modules/signals.py +++ /dev/null @@ -1,65 +0,0 @@ -import signal, sys -from src import Config, IRCLine, ModuleManager, utils - -class Module(ModuleManager.BaseModule): - def on_load(self): - self._exited = False - signal.signal(signal.SIGINT, self.SIGINT) - signal.signal(signal.SIGUSR1, self.SIGUSR1) - signal.signal(signal.SIGHUP, self.SIGHUP) - - def SIGINT(self, signum, frame): - print() - self.bot.trigger(lambda: self._kill(signum)) - - def _kill(self, signum): - if self._exited: - return - self._exited = True - - self.events.on("signal.interrupt").call(signum=signum) - - written = False - for server in list(self.bot.servers.values()): - if server.connected: - server.socket.clear_send_buffer() - - line = IRCLine.ParsedLine("QUIT", ["Shutting down"]) - sent_line = server.send(line, immediate=True) - sent_line.events.on("send").hook(self._make_hook(server)) - - server.send_enabled = False - written = True - - if not written: - sys.exit() - - def _make_hook(self, server): - return lambda e: self._disconnect_hook(server) - def _disconnect_hook(self, server): - self.bot.disconnect(server) - if not self.bot.servers: - sys.exit() - - def SIGUSR1(self, signum, frame): - self.bot.trigger(self._reload_config) - - def SIGHUP(self, signum, frame): - self.bot.trigger(self._SIGHUP) - def _SIGHUP(self): - self._reload_config() - self._reload_modules() - - def _reload_config(self): - self.bot.log.info("Reloading config file") - self.bot.config.load() - self.bot.log.info("Reloaded config file") - - def _reload_modules(self): - self.bot.log.info("Reloading modules") - - result = self.bot.try_reload_modules() - if result.success: - self.bot.log.info(result.message) - else: - self.bot.log.warn(result.message) diff --git a/modules/silence.py b/modules/silence.py deleted file mode 100644 index 42990921..00000000 --- a/modules/silence.py +++ /dev/null @@ -1,70 +0,0 @@ -#--depends-on commands -#--depends-on permissions - -import time -from src import EventManager, ModuleManager, utils - -SILENCE_TIME = 60*5 # 5 minutes - -class Module(ModuleManager.BaseModule): - def on_load(self): - self.exports.add("is-silenced", self._is_silenced) - - def _is_silenced(self, target): - silence_until = target.get_setting("silence-until", None) - if not silence_until == None: - if time.time()<silence_until: - return True - else: - target.del_setting("silence-until") - return False - - @utils.hook("received.command.silence") - @utils.kwarg("channel_only", True) - @utils.kwarg("help", "Prevent me saying anything for a period of time " - "(default: 5 minutes)") - @utils.kwarg("usage", "[+time]") - @utils.kwarg("require_mode", "high") - @utils.kwarg("require_access", "silence") - @utils.kwarg("permission", "silence") - def silence(self, event): - duration = SILENCE_TIME - if event["args"] and event["args_split"][0].startswith("+"): - duration = utils.datetime.from_pretty_time( - event["args_split"][0][1:]) - if duration == None: - raise utils.EventError("Invalid duration provided") - - silence_until = time.time()+duration - event["target"].set_setting("silence-until", silence_until) - event["stdout"].write("Ok, I'll be back") - - @utils.hook("received.command.unsilence") - @utils.kwarg("help", "Unsilence me") - @utils.kwarg("unsilence", True) - @utils.kwarg("channel_only", True) - @utils.kwarg("require_mode", "high") - @utils.kwarg("require_access", "unsilence") - @utils.kwarg("permission", "unsilence") - def unsiltence(self, event): - silence_until = event["target"].get_setting("silence-until", None) - if not silence_until == None: - event["target"].del_setting("silence-until") - event["stdout"].write("Ok. I've been unsilenced") - else: - event["stderr"].write("I am not silenced") - - @utils.hook("preprocess.command", priority=EventManager.PRIORITY_HIGH) - def preprocess_command(self, event): - if event["is_channel"] and not event["hook"].get_kwarg( - "unsilence", False): - silence_until = event["target"].get_setting("silence-until", None) - if silence_until: - if self._is_silenced(event["target"]): - return utils.consts.PERMISSION_HARD_FAIL, None - - @utils.hook("unknown.command") - @utils.kwarg("priority", EventManager.PRIORITY_HIGH) - def unknown_command(self, event): - if event["is_channel"] and self._is_silenced(event["target"]): - event.eat() diff --git a/modules/strip_color.py b/modules/strip_color.py deleted file mode 100644 index 736d066e..00000000 --- a/modules/strip_color.py +++ /dev/null @@ -1,22 +0,0 @@ -#--depends-on config - -from src import ModuleManager, utils - -@utils.export("serverset", utils.BoolSetting("strip-color", - "Set whether I strip colors from my messages on this server")) -@utils.export("channelset", utils.BoolSetting("strip-color", - "Set whether I strip colors from my messages on in this channel")) -class Module(ModuleManager.BaseModule): - @utils.hook("preprocess.send.privmsg") - @utils.hook("preprocess.send.notice") - def preprocess(self, event): - if len(event["line"].args) > 1: - strip_color = event["server"].get_setting("strip-color", False) - target = event["line"].args[0] - if not strip_color and target in event["server"].channels: - channel = event["server"].channels.get(target) - strip_color = channel.get_setting("strip-color", False) - - if strip_color: - message = event["line"].args[1] - event["line"].args[1] = utils.irc.strip_font(message) diff --git a/modules/strip_otr.py b/modules/strip_otr.py deleted file mode 100644 index bdb273a5..00000000 --- a/modules/strip_otr.py +++ /dev/null @@ -1,15 +0,0 @@ -from src import EventManager, ModuleManager, utils - -# Strip magic whitespace string from the end of messages. -# OTR uses this string to advertise, over plaintext, that the sending user -# supports OTR. - -MAGIC = " \t \t\t\t\t \t \t \t \t\t \t \t" - -class Module(ModuleManager.BaseModule): - @utils.hook("raw.received.privmsg") - @utils.kwarg("priority", EventManager.PRIORITY_HIGH) - def on_message(self, event): - message = event["line"].args.get(1) - if message.endswith(MAGIC): - event["line"].args[1] = message.rsplit(MAGIC, 1)[0] diff --git a/modules/throttle.py b/modules/throttle.py deleted file mode 100644 index e204cc34..00000000 --- a/modules/throttle.py +++ /dev/null @@ -1,17 +0,0 @@ -from src import ModuleManager, utils - -def _parse(value): - lines, _, seconds = value.partition(":") - if lines.isdigit() and seconds.isdigit(): - return [int(lines), int(seconds)] - return None - -@utils.export("serverset", utils.FunctionSetting(_parse, "throttle", - "Configure lines:seconds throttle for the current server", example="4:2")) -class Module(ModuleManager.BaseModule): - @utils.hook("received.001") - def connect(self, event): - throttle = event["server"].get_setting("throttle", None) - if throttle: - lines, seconds = throttle - event["server"].socket.set_throttle(lines, seconds) |
