diff options
Diffstat (limited to 'modules')
58 files changed, 915 insertions, 846 deletions
diff --git a/modules/acronym.py b/modules/acronym.py index 212465d5..116317a1 100644 --- a/modules/acronym.py +++ b/modules/acronym.py @@ -9,11 +9,13 @@ class Module(ModuleManager.BaseModule): @utils.kwarg("usage", "<acronym>") def acronym(self, event): query = event["args_split"][0].upper() - response = utils.http.request(API % query, parse=True) - if response.data: - acronyms = [] - for element in response.data.find_all("acro"): - acronyms.append(element.expan.string) + response = utils.http.request(API % query) + + acronyms = [] + for element in response.soup().find_all("acro"): + acronyms.append(element.expan.string) + + if acronyms: event["stdout"].write("%s: %s" % (query, ", ".join(acronyms))) else: raise utils.EventResultsError() diff --git a/modules/aliases.py b/modules/aliases.py new file mode 100644 index 00000000..3b120370 --- /dev/null +++ b/modules/aliases.py @@ -0,0 +1,120 @@ +#--depends-on commands +import re +from src import EventManager, ModuleManager, utils + +REGEX_ARG_NUMBER = re.compile(r"\$(?:(\d+)(-?)|(-))") +SETTING_PREFIX = "command-alias-" + +class Module(ModuleManager.BaseModule): + def _arg_replace(self, s, args_split): + parts = s.split("$$") + for i, part in enumerate(parts): + for match in REGEX_ARG_NUMBER.finditer(s): + if match.group(1): + index = int(match.group(1)) + continuous = match.group(2) == "-" + if index >= len(args_split): + raise IndexError("Unknown alias arg index") + else: + index = 0 + continuous = True + + if continuous: + replace = " ".join(args_split[index:]) + else: + replace = args_split[index] + parts[i] = part.replace(match.group(0), replace) + return "$".join(parts) + + def _get_alias(self, server, target, command): + setting = "%s%s" % (SETTING_PREFIX, command) + command = self.bot.get_setting(setting, + server.get_setting(setting, + target.get_setting(setting, None))) + if not command == None: + command, _, args = command.partition(" ") + return command, args + return None + def _get_aliases(self, targets): + alias_list = [] + for target in targets: + alias_list += target.find_settings(prefix=SETTING_PREFIX) + + aliases = {} + for alias, command in alias_list: + alias = alias.replace(SETTING_PREFIX, "", 1) + if not alias in aliases: + aliases[alias] = command + return aliases + + @utils.hook("get.command") + @utils.kwarg("priority", EventManager.PRIORITY_URGENT) + def get_command(self, event): + alias = self._get_alias(event["server"], event["target"], + event["command"].command) + if not alias == None: + alias, alias_args = alias + event["command"].command = alias + event["command"].args = self._arg_replace(alias_args, + event["command"].args.split(" ")) + + @utils.hook("received.command.alias") + @utils.hook("received.command.calias", + require_mode="o", require_access="alias") + @utils.kwarg("min_args", 1) + @utils.kwarg("permission", "alias") + @utils.kwarg("usage", "list") + @utils.kwarg("usage", "add <alias> <command> [arg1 [arg2 ...]]") + @utils.kwarg("usage", "remove <alias>") + @utils.kwarg("remove_empty", False) + def alias(self, event): + target = event["server"] + if event["command"] == "calias": + if not event["is_channel"]: + raise utils.EventError("%scalias can only be used in-channel" + % event["command_prefix"]) + target = event["target"] + + subcommand = event["args_split"][0].lower() + if subcommand == "list": + aliases = self._get_aliases([target]) + event["stdout"].write("Available aliases: %s" % + ", ".join(sorted(aliases.keys()))) + + elif subcommand == "show": + if not len(event["args_split"]) > 1: + raise utils.EventError("Please provide an alias to remove") + + alias = event["args_split"][1].lower() + setting = target.get_setting("%s%s" % (SETTING_PREFIX, alias), None) + + if setting == None: + raise utils.EventError("I don't have an '%s' alias" % alias) + prefix = event["command_prefix"] + event["stdout"].write(f"{prefix}{alias}: {prefix}{setting}") + + elif subcommand == "add": + if not len(event["args_split"]) > 2: + raise utils.EventError("Please provide an alias and a command") + + alias = event["args_split"][1].lower() + command = event["args_split"][2].lower() + command = " ".join([command]+event["args_split"][3:]) + target.set_setting("%s%s" % (SETTING_PREFIX, alias), command) + + event["stdout"].write("Added '%s' alias" % alias) + + elif subcommand == "remove": + if not len(event["args_split"]) > 1: + raise utils.EventError("Please provide an alias to remove") + + alias = event["args_split"][1].lower() + setting = "%s%s" % (SETTING_PREFIX, alias) + if target.get_setting(setting, None) == None: + raise utils.EventError("I don't have an '%s' alias" % alias) + + target.del_setting(setting) + event["stdout"].write("Removed '%s' alias" % alias) + + else: + raise utils.EventError("Unknown subcommand '%s'" % subcommand) diff --git a/modules/badges.py b/modules/badges.py index 631e7ccd..5aa16015 100644 --- a/modules/badges.py +++ b/modules/badges.py @@ -9,7 +9,7 @@ HUMAN_FORMAT_HELP = "year-month-day (e.g. 2018-12-29)" class Module(ModuleManager.BaseModule): def _parse_date(self, dt: str): if dt.lower() == "today": - return utils.datetime.datetime_utcnow() + return utils.datetime.utcnow() else: match = RE_HUMAN_FORMAT.match(dt) if not match: @@ -52,7 +52,7 @@ class Module(ModuleManager.BaseModule): badge_lower = badge.lower() badges = self._get_badges(event["user"]) - now = self._round_up_day(utils.datetime.datetime_utcnow()) + now = self._round_up_day(utils.datetime.utcnow()) found_badge = self._find_badge(badges, badge) @@ -74,7 +74,7 @@ class Module(ModuleManager.BaseModule): if event["args"]: user = event["server"].get_user(event["args_split"][0]) - now = self._round_up_day(utils.datetime.datetime_utcnow()) + now = self._round_up_day(utils.datetime.utcnow()) badges = [] for badge, date in self._get_badges(user).items(): days_since = self._days_since(now, diff --git a/modules/bitcoin.py b/modules/bitcoin.py index 91fd84fb..c130d20d 100644 --- a/modules/bitcoin.py +++ b/modules/bitcoin.py @@ -12,16 +12,15 @@ class Module(ModuleManager.BaseModule): :usage: [currency] """ currency = (event["args"] or "USD").upper() - page = utils.http.request("https://blockchain.info/ticker", - json=True) + page = utils.http.request("https://blockchain.info/ticker").json() if page: - if currency in page.data: - conversion = page.data[currency] + if currency in page: + conversion = page[currency] buy, sell = conversion["buy"], conversion["sell"] event["stdout"].write("1 BTC = %.2f %s (buy) %.2f %s " "(sell)" % (buy, currency, sell, currency)) else: event["stderr"].write("Unknown currency, available " - "currencies: %s" % ", ".join(page.data.keys())) + "currencies: %s" % ", ".join(page.keys())) else: raise utils.EventResultsError() diff --git a/modules/channel_access.py b/modules/channel_access.py index 100b22e0..a7d75ac9 100644 --- a/modules/channel_access.py +++ b/modules/channel_access.py @@ -1,5 +1,6 @@ #--depends-on check_mode #--depends-on commands +#--depends-on permissions from src import ModuleManager, utils @@ -8,7 +9,7 @@ class Module(ModuleManager.BaseModule): def _has_channel_access(self, target, user, require_access): access = target.get_user_setting(user.get_id(), "access", []) - identified_account = user.get_identified_account() + identified = self.exports.get_one("is-identified")(user) return ((require_access in access or "*" in access ) and identified_account) diff --git a/modules/channel_op.py b/modules/channel_op.py index 9d6d08d9..bc307778 100644 --- a/modules/channel_op.py +++ b/modules/channel_op.py @@ -27,16 +27,6 @@ KICK_REASON_SETTING = utils.Setting("default-kick-reason", class Module(ModuleManager.BaseModule): _name = "ChanOp" - def _parse_time(self, args, min_args): - if args and args[0][0] == "+": - if len(args[1:]) < min_args: - raise utils.EventError("Not enough arguments") - time = utils.datetime.from_pretty_time(args[0][1:]) - if time == None: - raise utils.EventError("Invalid timeframe") - return time, args[1:] - return None, args - def _kick_reason(self, server, channel): return channel.get_setting("default-kick-reason", server.get_setting("default-kick-reason", @@ -66,10 +56,10 @@ class Module(ModuleManager.BaseModule): mask_split[i] = (mask_part.replace("$n", user.nickname) .replace("$u", user.username) .replace("$h", user.hostname) - .replace("$a", user.get_identified_account() or "")) + .replace("$a", user.account or "")) return "$".join(mask_split) def _get_hostmask(self, channel, user): - if not user.get_identified_account() == None: + if not user.account == None: account_format = channel.get_setting("ban-format-account", None) if not account_format == None: return self._format_hostmask(user, account_format) @@ -109,7 +99,7 @@ class Module(ModuleManager.BaseModule): @utils.kwarg("require_access", "ban") @utils.kwarg("usage", "[+time] <target>") def ban(self, event): - time, args = self._parse_time(event["args_split"], 1) + time, args = utils.parse.timed_args(event["args_split"], 1) self._ban(event["server"], event["target"], args[0], True, time, True) @utils.hook("received.command.unban") @@ -128,7 +118,7 @@ class Module(ModuleManager.BaseModule): @utils.kwarg("require_access", "kickban") @utils.kwarg("usage", "[+time] <nickname> [reason]") def kickban(self, event): - time, args = self._parse_time(event["args_split"], 1) + time, args = utils.parse.timed_args(event["args_split"], 1) self._ban(event["server"], event["target"], args[0], False, time, True) self._kick(event["server"], event["target"], args[0], args[1:]) @@ -199,7 +189,7 @@ class Module(ModuleManager.BaseModule): @utils.kwarg("help", "Mute a given user") def _mute(self, event): add = event["command"] == "mute" - time, args = self._parse_time(event["args_split"], 1) + time, args = utils.parse.timed_args(event["args_split"], 1) target_name = args[0] if not event["server"].has_user(target_name): @@ -315,7 +305,7 @@ class Module(ModuleManager.BaseModule): flags = channel.get_user_setting(user.get_id(), "flags", "") if flags: - identified = not user.get_identified_account() == None + identified = not user._id_override == None current_modes = channel.get_user_modes(user) modes = [] @@ -349,7 +339,7 @@ class Module(ModuleManager.BaseModule): @utils.kwarg("usage", "[+time]") @utils.kwarg("help", "Mute the current channel") def cmute(self, event): - time, args = self._parse_time(event["args_split"], 0) + time, args = utils.parse.timed_args(event["args_split"], 0) event["target"].send_mode("+m") if time: diff --git a/modules/commands/__init__.py b/modules/commands/__init__.py index d48a58a8..71013c38 100644 --- a/modules/commands/__init__.py +++ b/modules/commands/__init__.py @@ -1,26 +1,34 @@ #--depends-on config #--depends-on permissions -import re, shlex, string, traceback, typing +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"] -REGEX_ARG_NUMBER = re.compile(r"\$(?:(\d+)(-?)|(-))") - -MESSAGE_TAGS_CAP = utils.irc.Capability("message-tags", - "draft/message-tags-0.2") -MSGID_TAG = utils.irc.MessageTag("msgid", "draft/msgid") +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") @@ -45,8 +53,6 @@ class Module(ModuleManager.BaseModule): target = event["user"] else: target = event["channel"] - target.last_stdout = None - target.last_stderr = None def has_command(self, command): return command.lower() in self.events.on("received").on( @@ -59,44 +65,19 @@ class Module(ModuleManager.BaseModule): if s and s[-1] in [":", ","]: return server.is_own_nickname(s[:-1]) - def _get_aliases(self, server): - return server.get_setting("command-aliases", {}) - def _set_aliases(self, server, aliases): - server.set_setting("command-aliases", aliases) - - def _alias_arg_replace(self, s, args_split): - for match in REGEX_ARG_NUMBER.finditer(s): - if match.group(1): - index = int(match.group(1)) - continuous = match.group(2) == "-" - if index >= len(args_split): - raise IndexError("Unknown alias arg index") - else: - index = 0 - continuous = True - - if continuous: - replace = " ".join(args_split[index:]) - else: - replace = args_split[index] - s = s.replace(match.group(0), replace) - return s - - def _command_method(self, target, server): + def _command_method(self, server, target): return target.get_setting(COMMAND_METHOD, server.get_setting(COMMAND_METHOD, - self.bot.get_setting(COMMAND_METHOD, "PRIVMSG"))) + self.bot.get_setting(COMMAND_METHOD, "PRIVMSG"))).upper() - def _find_command_hook(self, server, command, is_channel, args): + def _find_command_hook(self, server, target, is_channel, command, args): if not self.has_command(command): - aliases = self._get_aliases(server) - if command.lower() in aliases: - command, _, new_args = aliases[command.lower()].partition(" ") + command_event = CommandEvent(command, args) + self.events.on("get.command").call(command=command_event, + server=server, target=target, is_channel=is_channel) - try: - args = self._alias_arg_replace(new_args, shlex.split(args)) - except IndexError: - return None, None, None + command = command_event.command + args = command_event.args hook = None args_split = [] @@ -124,11 +105,12 @@ class Module(ModuleManager.BaseModule): hook = potential_hook - argparse = hook.get_kwarg("argparse", "plain") - if argparse == "shlex": - args_split = shlex.split(args) - elif argparse == "plain": - args_split = args.split(" ") + if args: + argparse = hook.get_kwarg("argparse", "plain") + if argparse == "shlex": + args_split = shlex.split(args) + elif argparse == "plain": + args_split = args.split(" ") break @@ -180,31 +162,13 @@ class Module(ModuleManager.BaseModule): if not is_success: raise utils.EventError("%s: %s" % (user.nickname, message)) - def _tagmsg(self, target, tags): - return IRCLine.ParsedLine("TAGMSG", [target], tags=tags) - def command(self, server, target, target_str, is_channel, user, command, - args_split, tags, hook, **kwargs): - message_tags = server.has_capability(MESSAGE_TAGS_CAP) - expect_output = hook.get_kwarg("expect_output", True) + args_split, line, hook, **kwargs): + module_name = (self._get_prefix(hook) or + self.bot.modules.from_context(hook.context).title) - module_name = self._get_prefix(hook) or "" - if not module_name and hasattr(hook.function, "__self__"): - module_name = hook.function.__self__._name - - send_tags = {} - if message_tags: - msgid = MSGID_TAG.get_value(tags) - if msgid: - send_tags["+draft/reply"] = msgid - - if expect_output: - line = self._tagmsg(target_str, {"+draft/typing": "active"}) - server.send(line, immediate=True) - - stdout = outs.StdOut(server, module_name, target, target_str, send_tags) - stderr = outs.StdErr(server, module_name, target, target_str, send_tags) - command_method = self._command_method(target, server) + stdout = outs.StdOut(module_name) + stderr = outs.StdOut(module_name) ret = False has_out = False @@ -213,48 +177,84 @@ class Module(ModuleManager.BaseModule): args_split = list(filter(None, args_split)) event_kwargs = {"hook": hook, "user": user, "server": server, - "target": target, "is_channel": is_channel, "tags": tags, - "args_split": args_split, "command": command, - "args": " ".join(args_split), "stdout": stdout, - "stderr": stderr} + "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 not check_success: + 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) - ).send(command_method) - return True + stderr.write("%s: %s" % (user.nickname, check_message)) - new_event = self.events.on(hook.event_name).make_event(**event_kwargs) + self._check("postprocess", event_kwargs) + # postprocess - send stdout/stderr and typing tag - self.log.trace("calling command '%s': %s", [command, new_event.kwargs]) + return eaten - try: - hook.call(new_event) - except utils.EventError as e: - stderr.write(str(e)).send(command_method) - return True + @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 hook.get_kwarg("skip_out", False): - has_out = stdout.has_text() or stderr.has_text() - if has_out: - command_method = self._command_method(target, server) - stdout.send(command_method) - stderr.send(command_method) - target.last_stdout = stdout - target.last_stderr = stderr - ret = new_event.eaten + if not method in ["PRIVMSG", "NOTICE"]: + raise ValueError("Unknown command-method '%s'" % method) - if expect_output and message_tags and not has_out: - line = self._tagmsg(target_str, {"+draft/typing": "done"}) - server.send(line, immediate=True) + line = IRCLine.ParsedLine(method, [target_str, line_str], + tags=tags) + valid, trunc = line.truncate(server.hostmask(), + margin=STR_MORE_LEN) - return ret + 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): @@ -300,7 +300,7 @@ class Module(ModuleManager.BaseModule): if command: try: hook, command, args_split = self._find_command_hook( - event["server"], command, True, args) + event["server"], event["channel"], True, command, args) except BadContextException: event["channel"].send_message( "%s: That command is not valid in a channel" % @@ -314,7 +314,7 @@ class Module(ModuleManager.BaseModule): if hook: self.command(event["server"], event["channel"], event["target_str"], True, event["user"], command, - args_split, event["tags"], hook, + args_split, event["line"], hook, command_prefix=command_prefix) else: self.events.on("unknown.command").call(server=event["server"], @@ -328,16 +328,13 @@ class Module(ModuleManager.BaseModule): continue pattern = hook.get_kwarg("pattern", None) - if not pattern and hook.get_kwarg("pattern-url", None) == "1": - pattern = utils.http.REGEX_URL - 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["tags"], hook, match=match, + "", event["line"], hook, match=match, message=event["message"], command_prefix="", action=event["action"]) @@ -359,7 +356,7 @@ class Module(ModuleManager.BaseModule): try: hook, command, args_split = self._find_command_hook( - event["server"], command, False, args) + event["server"], event["user"], False, command, args) except BadContextException: event["user"].send_message( "That command is not valid in a PM") @@ -368,7 +365,7 @@ class Module(ModuleManager.BaseModule): if hook: self.command(event["server"], event["user"], event["user"].nickname, False, event["user"], command, - args_split, event["tags"], hook, command_prefix="") + args_split, event["line"], hook, command_prefix="") else: self.events.on("unknown.command").call(server=event["server"], target=event["user"], user=event["user"], command=command, @@ -388,72 +385,23 @@ class Module(ModuleManager.BaseModule): def _get_alias_of(self, hook): return hook.get_kwarg("alias_of", None) - @utils.hook("received.command.more", skip_out=True) - def more(self, event): - """ - :help: Show more output from the last command - """ - if event["target"].last_stdout and event["target"].last_stdout.has_text(): - event["target"].last_stdout.send( - self._command_method(event["target"], event["server"])) - @utils.hook("send.stdout") - def send_stdout(self, event): - target = event["target"] - stdout = outs.StdOut(event["server"], event["module_name"], - target, event.get("target_str", target.name), {}) - - if event.get("hide_prefix", False): - stdout.hide_prefix() - - stdout.write(event["message"]).send( - self._command_method(event["target"], event["server"])) - if stdout.has_text(): - event["target"].last_stdout = stdout + def _stdout(self, event): + self._send_out(event, OutType.OUT) @utils.hook("send.stderr") - def send_stderr(self, event): - target = event["target"] - stderr = outs.StdErr(event["server"], event["module_name"], - target, event.get("target_str", target.name), {}) + 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): - stderr.hide_prefix() - - stderr.write(event["message"]).send( - self._command_method(event["target"], event["server"])) - if stderr.has_text(): - event["target"].last_stderr = stderr - - @utils.hook("received.command.alias", min_args=2) - def add_alias(self, event): - """ - :help: Add a command alias - :usage: <alias> <command> <args...> - :permission: command-alias - """ - alias = event["args_split"][0].lower() - command = " ".join(event["args_split"][1:]) - aliases = self._get_aliases(event["server"]) - aliases[alias] = command - self._set_aliases(event["server"], aliases) - event["stdout"].write("Added '%s' alias" % alias) - - @utils.hook("received.command.removealias", min_args=1) - def remove_alias(self, event): - """ - :help: Remove a command alias - :usage: <alias> - :permission: command-alias - """ - alias = event["args_split"][0].lower() - aliases = self._get_aliases(event["server"]) - - if not alias in aliases: - raise utils.EventError("No '%s' alias" % alias) + stdout.prefix = None - del aliases[alias] - self._set_aliases(event["server"], aliases) - event["stdout"].write("Removed '%s' alias" % alias) + 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): diff --git a/modules/commands/outs.py b/modules/commands/outs.py index 41528da3..e82ceefd 100644 --- a/modules/commands/outs.py +++ b/modules/commands/outs.py @@ -1,98 +1,28 @@ import re from src import IRCLine, utils -STR_MORE = " (more...)" -STR_MORE_LEN = len(STR_MORE.encode("utf8")) -STR_CONTINUED = "(...continued) " -WORD_BOUNDARY = ' ' - -def _message_factory(command): - if not command in ["PRIVMSG", "NOTICE"]: - raise ValueError("Unknown command method '%s'" % method) - - def _(target, message, tags): - return IRCLine.ParsedLine(command, [target, message], tags=tags) - return _ - -class Out(object): - def __init__(self, server, module_name, target, target_str, tags): - self.server = server - self._prefix = self._default_prefix(module_name) - self._hide_prefix = False - self.target = target - self._target_str = target_str - self._text = "" - self.written = False - self._tags = tags +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._text += text - self.written = True - return self - def writeline(self, line): - self._text += "%s\n" % line - - def send(self, method): - if self.has_text(): - prefix = "" - if not self._hide_prefix: - prefix = utils.consts.RESET + "[%s] " % self._prefix - - text = self._text[:].replace("\r", "") - while "\n\n" in text: - text = text.replace("\n\n", "\n") - - full_text = "%s%s" % (prefix, text) - message_factory = _message_factory(method) - - line = message_factory(self._target_str, full_text, tags=self._tags) - if self._assured: - line.assure() - - valid, truncated = line.truncate(self.server.hostmask(), - margin=STR_MORE_LEN) + self.write_lines( + text.replace("\r", "").replace("\n\n", "\n").split("\n")) + def write_lines(self, lines): + self._lines += list(filter(None, lines)) - if truncated: - valid, truncated = self._adjust_to_word_boundaries(valid, truncated) - - line = IRCLine.parse_line(valid+STR_MORE) - self._text = "%s%s" % (STR_CONTINUED, truncated) - else: - self._text = "" - - sent_line = self.server.send(line) - - def _adjust_to_word_boundaries(self, left, right): - if right[0] == WORD_BOUNDARY: - return left, right - - parts = left.rsplit(WORD_BOUNDARY, 1) - - if len(parts) != 2: - return left, right - - return parts[0], parts[1] + right - - def _default_prefix(self, s: str): - return s - def set_prefix(self, prefix): - self._prefix = self._default_prefix(prefix) - def append_prefix(self, s: str): - self._prefix = "%s%s" % (self._prefix, s) - def hide_prefix(self): - self._hide_prefix = True + 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._text) - -class StdOut(Out): - def _default_prefix(self, s: str): - return utils.irc.color(s, utils.consts.GREEN) -class StdErr(Out): - def _default_prefix(self, s: str): - return utils.irc.color(s, utils.consts.RED) + return bool(self._lines) diff --git a/modules/config.py b/modules/config.py index 0a24e139..48e3eb62 100644 --- a/modules/config.py +++ b/modules/config.py @@ -150,13 +150,11 @@ class Module(ModuleManager.BaseModule): raise ConfigSettingInexistent() @utils.hook("received.command.c", alias_of="config") - @utils.hook("received.command.config", min_args=1) + @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): - """ - :help: Change config options - :usage: <context>[:name] [-][setting [value]] - """ - arg_count = len(event["args_split"]) context_desc, _, name = event["args_split"][0].partition(":") diff --git a/modules/define.py b/modules/define.py index 144ec227..4e83c65c 100644 --- a/modules/define.py +++ b/modules/define.py @@ -15,12 +15,12 @@ class Module(ModuleManager.BaseModule): def _get_definition(self, word): page = utils.http.request(URL_WORDNIK % word, get_params={ "useCanonical": "true", "limit": 1, - "sourceDictionaries": "wiktionary", "api_key": self.bot.config[ - "wordnik-api-key"]}, json=True) + "sourceDictionaries": "wiktionary", + "api_key": self.bot.config["wordnik-api-key"]}) if page: if page.code == 200: - return True, page.data[0] + return True, page.json()[0] else: return True, None else: @@ -58,16 +58,16 @@ class Module(ModuleManager.BaseModule): self._last_called = time.time() page = utils.http.request(URL_WORDNIK_RANDOM, get_params={ - "api_key":self.bot.config["wordnik-api-key"], - "min_dictionary_count":1},json=True) - if page and len(page.data): - success, definition = self._get_definition(page.data["word"]) + "api_key": self.bot.config["wordnik-api-key"], + "min_dictionary_count": 1}).json() + if page: + success, definition = self._get_definition(page["word"]) if not success: raise utils.EventError("Try again in a couple of seconds") text = utils.http.strip_html(definition["text"]) event["stdout"].write("Random Word: %s - Definition: %s" % ( - page.data["word"], text)) + page["word"], text)) else: raise utils.EventResultsError() else: diff --git a/modules/duckduckgo.py b/modules/duckduckgo.py index 6660d158..f43d2376 100644 --- a/modules/duckduckgo.py +++ b/modules/duckduckgo.py @@ -18,9 +18,9 @@ class Module(ModuleManager.BaseModule): if phrase: page = utils.http.request(URL_DDG, get_params={ "q": phrase, "format": "json", "no_html": "1", - "no_redirect": "1"}, json=True) + "no_redirect": "1"}).json() - if page and page.data["AbstractURL"]: - event["stdout"].write(page.data["AbstractURL"]) + if page and page["AbstractURL"]: + event["stdout"].write(page["AbstractURL"]) else: event["stderr"].write("No results found") diff --git a/modules/echo.py b/modules/echo.py index cfdb2084..1cbf7532 100644 --- a/modules/echo.py +++ b/modules/echo.py @@ -5,7 +5,6 @@ from src import ModuleManager, utils class Module(ModuleManager.BaseModule): @utils.hook("received.command.echo") @utils.kwarg("min_args", 1) - @utils.kwarg("argparse", "shlex") @utils.kwarg("remove_empty", False) @utils.kwarg("help", "Echo a string back") def echo(self, event): @@ -15,7 +14,6 @@ class Module(ModuleManager.BaseModule): @utils.kwarg("min_args", 1) @utils.kwarg("expect_output", False) @utils.kwarg("remove_empty", False) - @utils.kwarg("argparse", "shlex") @utils.kwarg("help", "Make the bot send a /me") def action(self, event): event["target"].send_message("\x01ACTION %s\x01" % event["args"]) @@ -24,7 +22,6 @@ class Module(ModuleManager.BaseModule): @utils.kwarg("min_args", 2) @utils.kwarg("permission", "say") @utils.kwarg("remove_empty", False) - @utils.kwarg("argparse", "shlex") @utils.kwarg("help", "Send a message to a target") def msg(self, event): event["server"].send_message(event["args_split"][0], diff --git a/modules/eval_lua.py b/modules/eval_lua.py index 102aadc7..6b34c7c2 100644 --- a/modules/eval_lua.py +++ b/modules/eval_lua.py @@ -10,15 +10,14 @@ class Module(ModuleManager.BaseModule): @utils.hook("received.command.lua", min_args=1) def eval(self, event): try: - page = utils.http.request(EVAL_URL, - post_data={"input": event["args"]}, - method="POST", parse=True) + page = utils.http.request(EVAL_URL, post_data= + {"input": event["args"]}, method="POST") except socket.timeout: raise utils.EventError("%s: eval timed out" % event["user"].nickname) if page: - textareas = page.data.find_all("textarea") + textareas = page.soup().find_all("textarea") if len(textareas) > 1: out = textareas[1].text.strip("\n") event["stdout"].write("%s: %s" % (event["user"].nickname, out)) diff --git a/modules/eval_python.py b/modules/eval_python.py index b16671bc..5454b7d3 100644 --- a/modules/eval_python.py +++ b/modules/eval_python.py @@ -21,6 +21,6 @@ class Module(ModuleManager.BaseModule): if page and page.data: event["stdout"].write("%s: %s" % (event["user"].nickname, - page.data.rstrip("\n"))) + page.decode().rstrip("\n"))) else: event["stderr"].write("%s: failed to eval" % event["user"].nickname) diff --git a/modules/fediverse/__init__.py b/modules/fediverse/__init__.py index 362caf57..f80607d3 100644 --- a/modules/fediverse/__init__.py +++ b/modules/fediverse/__init__.py @@ -117,13 +117,13 @@ class Module(ModuleManager.BaseModule): except ap_utils.FindActorException as e: raise utils.EventError(str(e)) - actor = ap_actor.Actor(actor_url) if not actor.load(): raise utils.EventError("Failed to load user") items = actor.outbox.load() nonreply = [actor.followers] + first_item = None for item in items: if item["type"] == "Announce" or item["object"]["cc"] == nonreply: first_item = item diff --git a/modules/fediverse/ap_actor.py b/modules/fediverse/ap_actor.py index a2557e9b..f87069a5 100644 --- a/modules/fediverse/ap_actor.py +++ b/modules/fediverse/ap_actor.py @@ -14,10 +14,11 @@ class Actor(object): def load(self): response = ap_utils.activity_request(self.url) if response.code == 200: - self.username = response.data["preferredUsername"] - self.inbox = Inbox(response.data["inbox"]) - self.outbox = Outbox(response.data["outbox"]) - self.followers = response.data["followers"] + response = response.json() + self.username = response["preferredUsername"] + self.inbox = Inbox(response["inbox"]) + self.outbox = Outbox(response["outbox"]) + self.followers = response["followers"] return True return False @@ -26,19 +27,19 @@ class Outbox(object): self._url = url def load(self): - outbox = ap_utils.activity_request(self._url) + outbox = ap_utils.activity_request(self._url).json() items = None - if "first" in outbox.data: - if type(outbox.data["first"]) == dict: + if "first" in outbox: + if type(outbox["first"]) == dict: # pleroma - items = outbox.data["first"]["orderedItems"] + items = outbox["first"]["orderedItems"] else: # mastodon - first = ap_utils.activity_request(outbox.data["first"]) - items = first.data["orderedItems"] + first = ap_utils.activity_request(outbox["first"]).json() + items = first["orderedItems"] else: - items = outbox.data["orderedItems"] + items = outbox["orderedItems"] return items class Inbox(object): @@ -58,5 +59,5 @@ class Inbox(object): headers.append(["signature", signature]) return ap_utils.activity_request(self._url, activity.format(sender), - method="POST", headers=dict(headers)).data + method="POST", headers=dict(headers)).json() diff --git a/modules/fediverse/ap_utils.py b/modules/fediverse/ap_utils.py index bd0cacf4..686b8850 100644 --- a/modules/fediverse/ap_utils.py +++ b/modules/fediverse/ap_utils.py @@ -7,7 +7,6 @@ LD_TYPE = ("application/ld+json; " "profile=\"https://www.w3.org/ns/activitystreams\"") JRD_TYPE = "application/jrd+json" ACTIVITY_TYPE = "application/activity+json" -USERAGENT = "BitBot (%s) Fediverse" % IRCBot.VERSION def split_username(s): if s[0] == "@": @@ -26,8 +25,8 @@ def activity_request(url, data=None, method="GET", type=ACTIVITY_TYPE, else: headers = {"Accept": type} - request = utils.http.Request(url, headers=headers, useragent=USERAGENT, - content_type=content_type, post_data=data, method=method, json=True, + request = utils.http.Request(url, headers=headers, + content_type=content_type, post_data=data, method=method, json_body=True, fallback_encoding="utf8") return utils.http.request(request) @@ -39,8 +38,7 @@ class FindActorException(Exception): def find_actor(username, instance): hostmeta = HOSTMETA_TEMPLATE % instance - hostmeta_request = utils.http.Request(HOSTMETA_TEMPLATE % instance, - useragent=USERAGENT, parse=True, check_content_type=False) + hostmeta_request = utils.http.Request(HOSTMETA_TEMPLATE % instance) try: hostmeta = utils.http.request(hostmeta_request) except: @@ -48,7 +46,7 @@ def find_actor(username, instance): webfinger_url = None if hostmeta.code == 200: - for item in hostmeta.data.find_all("link"): + for item in hostmeta.soup().find_all("link"): if item["rel"] and item["rel"][0] == "lrdd": webfinger_url = item["template"] break @@ -65,7 +63,7 @@ def find_actor(username, instance): actor_url = None if webfinger.code == 200: - for link in webfinger.data["links"]: + for link in webfinger.json()["links"]: if link["type"] == ACTIVITY_TYPE: return link["href"] else: @@ -129,15 +127,15 @@ def format_note(actor, note, type="Create"): if type == "Announce": retoot_url = note retoot_instance = urllib.parse.urlparse(retoot_url).hostname - retoot = activity_request(retoot_url) - retoot_url = retoot.data.get("url", retoot.data["id"]) + retoot = activity_request(retoot_url).json() + retoot_url = retoot.get("url", retoot["id"]) - original_tooter = ap_actor.Actor(retoot.data["attributedTo"]) + original_tooter = ap_actor.Actor(retoot["attributedTo"]) original_tooter.load() retooted_user = "@%s@%s" % (original_tooter.username, retoot_instance) - retoot_content = _content(retoot.data) + retoot_content = _content(retoot) - return (retoot.data.get("summary", None), "%s (boost %s): %s" % ( + return (retoot.get("summary", None), "%s (boost %s): %s" % ( actor.username, retooted_user, retoot_content), retoot_url) elif type == "Create": diff --git a/modules/format_activity.py b/modules/format_activity.py index 47f9bb7a..1d93eb94 100644 --- a/modules/format_activity.py +++ b/modules/format_activity.py @@ -64,12 +64,6 @@ class Module(ModuleManager.BaseModule): event["channel"].name, parsed_line=event["line"], channel=channel, user=event["user"], minimal=minimal, pretty=pretty) - def _private_notice(self, event, user): - minimal, normal, pretty = self._on_notice(event, user, None) - self._event("notice.private", event["server"], normal, None, - parsed_line=event["line"], user=event["user"], minimal=minimal, - pretty=pretty) - @utils.hook("received.notice.channel") @utils.hook("send.notice.channel") def channel_notice(self, event): @@ -78,7 +72,10 @@ class Module(ModuleManager.BaseModule): @utils.hook("received.notice.private") @utils.hook("send.notice.private") def private_notice(self, event): - self._private_notice(event, event["user"]) + 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 diff --git a/modules/git_webhooks/github.py b/modules/git_webhooks/github.py index cdf9016d..b7f13805 100644 --- a/modules/git_webhooks/github.py +++ b/modules/git_webhooks/github.py @@ -71,6 +71,7 @@ COMMENT_ACTIONS = { "edited": "edited a comment", "deleted": "deleted a comment" } +COMMENT_MAX = 100 CHECK_RUN_CONCLUSION = { "success": "passed", @@ -170,7 +171,7 @@ class GitHub(object): self.log.debug("git.io shortening: %s" % url) try: page = utils.http.request("https://git.io", method="POST", - post_data={"url": url}, detect_encoding=False) + post_data={"url": url}) return page.headers["Location"] except utils.http.HTTPTimeoutException: self.log.warn( @@ -243,6 +244,16 @@ class GitHub(object): return outputs + def _comment(self, s): + s_line = utils.parse.line_normalise(s) + left, right = s_line[:COMMENT_MAX], s_line[COMMENT_MAX:] + if not right: + return left + else: + if " " in left: + left = left.rsplit(" ", 1)[0] + return "%s[...]" % left + def commit_comment(self, full_name, data): action = data["action"] commit = self._short_hash(data["comment"]["commit_id"]) @@ -253,13 +264,17 @@ class GitHub(object): def pull_request(self, full_name, data): raw_number = data["pull_request"]["number"] + branch = data["pull_request"]["base"]["ref"] + colored_branch = utils.irc.color(branch, colors.COLOR_BRANCH) + sender = utils.irc.bold(data["sender"]["login"]) + + author = utils.irc.bold(data["pull_request"]["user"]["login"]) number = utils.irc.color("#%s" % data["pull_request"]["number"], colors.COLOR_ID) + identifier = "%s by %s" % (number, author) + action = data["action"] - action_desc = "%s %s" % (action, number) - branch = data["pull_request"]["base"]["ref"] - colored_branch = utils.irc.color(branch, colors.COLOR_BRANCH) - author = utils.irc.bold(data["sender"]["login"]) + action_desc = "%s %s" % (action, identifier) if action == "opened": action_desc = "requested %s merge into %s" % (number, @@ -267,22 +282,23 @@ class GitHub(object): elif action == "closed": if data["pull_request"]["merged"]: action_desc = "%s %s into %s" % ( - utils.irc.color("merged", colors.COLOR_POSITIVE), number, - colored_branch) + utils.irc.color("merged", colors.COLOR_POSITIVE), + identifier, colored_branch) else: action_desc = "%s %s" % ( - utils.irc.color("closed", colors.COLOR_NEGATIVE), number) + utils.irc.color("closed", colors.COLOR_NEGATIVE), + identifier) elif action == "ready_for_review": action_desc = "marked %s ready for review" % number elif action == "synchronize": action_desc = "committed to %s" % number commits_url = data["pull_request"]["commits_url"] - commits = utils.http.request(commits_url, json=True) + commits = utils.http.request(commits_url).json() if commits: seen_before = False new_commits = [] - for commit in commits.data: + for commit in commits: if seen_before: new_commits.append({"id": commit["sha"], "message": commit["commit"]["message"]}) @@ -299,12 +315,15 @@ class GitHub(object): outputs[i] = "[PR] %s" % output return outputs elif action == "labeled": - action_desc = "labled %s as '%s'" % (number, data["label"]["name"]) + action_desc = "labled %s as '%s'" % ( + identifier, data["label"]["name"]) + elif action == "edited" and "title" in data["changes"]: + action_desc = "renamed %s" % identifier pr_title = data["pull_request"]["title"] url = self._short_url(data["pull_request"]["html_url"]) return ["[PR] %s %s: %s - %s" % ( - author, action_desc, pr_title, url)] + sender, action_desc, pr_title, url)] def pull_request_review(self, full_name, data): if not data["action"] == "submitted": @@ -352,6 +371,8 @@ class GitHub(object): action_str = "%s %s" % (action, number) if action == "labeled": action_str = "labeled %s as '%s'" % (number, data["label"]["name"]) + elif action == "edited" and "title" in data["changes"]: + action_str = "renamed %s" % number issue_title = data["issue"]["title"] author = utils.irc.bold(data["sender"]["login"]) @@ -370,9 +391,13 @@ class GitHub(object): type = "PR" if "pull_request" in data["issue"] else "issue" commenter = utils.irc.bold(data["sender"]["login"]) url = self._short_url(data["comment"]["html_url"]) - return ["[%s] %s %s on %s: %s - %s" % - (type, commenter, COMMENT_ACTIONS[action], number, issue_title, - url)] + + body = "" + if not action == "deleted": + body = ": %s" % self._comment(data["comment"]["body"]) + + return ["[%s] %s %s on %s%s - %s" % + (type, commenter, COMMENT_ACTIONS[action], number, body, url)] def create(self, full_name, data): ref = data["ref"] diff --git a/modules/github.py b/modules/github.py index c5e9b18e..b528ad2b 100644 --- a/modules/github.py +++ b/modules/github.py @@ -70,17 +70,18 @@ class Module(ModuleManager.BaseModule): headers = {} if not oauth2_token == None: headers["Authorization"] = "token %s" % oauth2_token - request = utils.http.Request(url, headers=headers, json=True) + request = utils.http.Request(url, headers=headers) return utils.http.request(request) def _commit(self, username, repository, commit): page = self._get(API_COMMIT_URL % (username, repository, commit)) if page and page.code == 200: + page = page.json() repo = utils.irc.color("%s/%s" % (username, repository), COLOR_REPO) - sha = utils.irc.color(page.data["sha"][:8], COLOR_ID) + sha = utils.irc.color(page["sha"][:8], COLOR_ID) return "(%s@%s) %s - %s %s" % (repo, sha, - page.data["author"]["login"], page.data["commit"]["message"], - self._short_url(page.data["html_url"])) + page["author"]["login"], page["commit"]["message"], + self._short_url(page["html_url"])) def _parse_commit(self, target, ref): username, repository, commit = self._parse_ref(target, ref, "@") return self._commit(username, repository, commit) @@ -116,29 +117,29 @@ class Module(ModuleManager.BaseModule): def _parse_issue(self, page, username, repository, number): repo = utils.irc.color("%s/%s" % (username, repository), COLOR_REPO) number = utils.irc.color("#%s" % number, COLOR_ID) - labels = [label["name"] for label in page.data["labels"]] + labels = [label["name"] for label in page["labels"]] labels_str = "" if labels: labels_str = "[%s] " % ", ".join(labels) - url = self._short_url(page.data["html_url"]) + url = self._short_url(page["html_url"]) - state = page.data["state"] + state = page["state"] if state == "open": state = utils.irc.color("open", COLOR_NEUTRAL) elif state == "closed": state = utils.irc.color("closed", COLOR_NEGATIVE) return "(%s issue%s, %s) %s %s%s" % ( - repo, number, state, page.data["title"], labels_str, url) + repo, number, state, page["title"], labels_str, url) def _get_issue(self, username, repository, number): return self._get(API_ISSUE_URL % (username, repository, number)) @utils.hook("received.command.ghissue", min_args=1) def github_issue(self, event): if event["target"].get_setting("github-hide-prefix", False): - event["stdout"].hide_prefix() - event["stderr"].hide_prefix() + event["stdout"].prefix = None + event["stderr"].prefix = None username, repository, number = self._parse_ref( event["target"], event["args_split"][0], "#") @@ -147,21 +148,21 @@ class Module(ModuleManager.BaseModule): page = self._get_issue(username, repository, number) if page and page.code == 200: - self._parse_issue(page, username, repository, number) + self._parse_issue(page.json(), username, repository, number) else: event["stderr"].write("Could not find issue") def _parse_pull(self, page, username, repository, number): repo = utils.irc.color("%s/%s" % (username, repository), COLOR_REPO) number = utils.irc.color("#%s" % number, COLOR_ID) - branch_from = page.data["head"]["label"] - branch_to = page.data["base"]["label"] - added = self._added(page.data["additions"]) - removed = self._removed(page.data["deletions"]) - url = self._short_url(page.data["html_url"]) + branch_from = page["head"]["label"] + branch_to = page["base"]["label"] + added = self._added(page["additions"]) + removed = self._removed(page["deletions"]) + url = self._short_url(page["html_url"]) - state = page.data["state"] - if page.data["merged"]: + state = page["state"] + if page["merged"]: state = utils.irc.color("merged", COLOR_POSITIVE) elif state == "open": state = utils.irc.color("open", COLOR_NEUTRAL) @@ -170,14 +171,14 @@ class Module(ModuleManager.BaseModule): return "(%s PR%s, %s) %s → %s [%s/%s] %s %s" % ( repo, number, state, branch_from, branch_to, added, removed, - page.data["title"], url) + page["title"], url) def _get_pull(self, username, repository, number): return self._get(API_PULL_URL % (username, repository, number)) @utils.hook("received.command.ghpull", min_args=1) def github_pull(self, event): if event["target"].get_setting("github-hide-prefix", False): - event["stdout"].hide_prefix() - event["stderr"].hide_prefix() + event["stdout"].prefix = None + event["stderr"].prefix = None username, repository, number = self._parse_ref( event["target"], event["args_split"][0], "#") @@ -187,7 +188,7 @@ class Module(ModuleManager.BaseModule): page = self._get_pull(username, repository, number) if page and page.code == 200: - self._parse_pull(page, username, repository, number) + self._parse_pull(page.json(), username, repository, number) else: event["stderr"].write("Could not find pull request") @@ -198,9 +199,11 @@ class Module(ModuleManager.BaseModule): page = self._get_issue(username, repository, number) if page and page.code == 200: - if "pull_request" in page.data: + page = page.json() + if "pull_request" in page: pull = self._get_pull(username, repository, number) - return self._parse_pull(pull, username, repository, number) + return self._parse_pull(pull.json(), username, repository, + number) else: return self._parse_issue(page, username, repository, number) else: @@ -210,8 +213,8 @@ class Module(ModuleManager.BaseModule): @utils.hook("received.command.github", min_args=1) def github(self, event): if event["target"].get_setting("github-hide-prefix", False): - event["stdout"].hide_prefix() - event["stderr"].hide_prefix() + event["stdout"].prefix = None + event["stderr"].prefix = None result = self._get_info(event["target"], event["args_split"][0]) if not result == None: event["stdout"].write(result) @@ -248,7 +251,7 @@ class Module(ModuleManager.BaseModule): return if result: if event["target"].get_setting("github-hide-prefix", False): - event["stdout"].hide_prefix() + event["stdout"].prefix = None event["stdout"].write(result) @utils.hook("command.regex") @@ -267,5 +270,5 @@ class Module(ModuleManager.BaseModule): return if result: if event["target"].get_setting("github-hide-prefix", False): - event["stdout"].hide_prefix() + event["stdout"].prefix = None event["stdout"].write(result) diff --git a/modules/google.py b/modules/google.py index 699e8ef0..6cddb8a3 100644 --- a/modules/google.py +++ b/modules/google.py @@ -27,14 +27,17 @@ class Module(ModuleManager.BaseModule): safe = "active" if safe_setting else "off" page = utils.http.request(URL_GOOGLESEARCH, get_params={ - "q": phrase, "key": self.bot.config[ - "google-api-key"], "cx": self.bot.config[ - "google-search-id"], "prettyPrint": "true", - "num": 1, "gl": "gb", "safe": safe}, json=True) + "q": phrase, "prettyPrint": "true", "num": 1, "gl": "gb", + "key": self.bot.config["google-api-key"], + "cx": self.bot.config["google-search-id"], + "safe": safe}).json() if page: - if "items" in page.data and len(page.data["items"]): + if "items" in page and len(page["items"]): + item = page["items"][0] + link = item["link"] + title = utils.parse.line_normalise(item["title"]) event["stdout"].write( - "(%s) %s" % (phrase, page.data["items"][0]["link"])) + "(%s) %s - %s" % (phrase, title, link)) else: event["stderr"].write("No results found") else: @@ -51,10 +54,11 @@ class Module(ModuleManager.BaseModule): phrase = event["args"] or event["target"].buffer.get() if phrase: page = utils.http.request(URL_GOOGLESUGGEST, get_params={ - "output": "json", "client": "hp", "gl": "gb", "q": phrase}) + "output": "json", "client": "hp", "gl": "gb", "q": phrase} + ).json() if page: # google gives us jsonp, so we need to unwrap it. - page = page.data.split("(", 1)[1][:-1] + page = page.split("(", 1)[1][:-1] page = json.loads(page) suggestions = page[1] suggestions = [utils.http.strip_html(s[0]) for s in suggestions] diff --git a/modules/help.py b/modules/help.py index c2bb0f12..58659d9d 100644 --- a/modules/help.py +++ b/modules/help.py @@ -44,11 +44,17 @@ class Module(ModuleManager.BaseModule): else: event["stderr"].write("No help for %s" % command) else: - event["stdout"].write("I'm %s. use '%smodules' to list modules, " - "'%scommands <module>' to list commands and " - "'%shelp <command>' to see help text for a command" % - (IRCBot.URL, event["command_prefix"], event["command_prefix"], - event["command_prefix"])) + 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 = {} diff --git a/modules/ids.py b/modules/ids.py index 20a641b7..1738c549 100644 --- a/modules/ids.py +++ b/modules/ids.py @@ -16,7 +16,7 @@ class Module(ModuleManager.BaseModule): @utils.kwarg("help", "Show what I think your account name is") def account(self, event): event["stdout"].write("%s: %s" % (event["user"].nickname, - event["user"].get_identified_account())) + self.exports.get_one("account-name")(event["user"]))) @utils.hook("received.command.channelid", channel_only=True) def channel_id(self, event): diff --git a/modules/ignore.py b/modules/ignore.py index 3108ea71..11ad58f3 100644 --- a/modules/ignore.py +++ b/modules/ignore.py @@ -49,14 +49,16 @@ class Module(ModuleManager.BaseModule): :usage: <nickname> [command] :permission: ignore """ + time, args = utils.parse.timed_args(event["args_split"], 1) + setting = "ignore" for_str = "" - if len(event["args_split"]) > 1: - command = event["args_split"][1].lower() + if len(args) > 1: + command = args[1].lower() setting = "ignore-%s" % command for_str = " for '%s'" % command - user = event["server"].get_user(event["args_split"][0]) + 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)) @@ -65,6 +67,14 @@ class Module(ModuleManager.BaseModule): 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): """ diff --git a/modules/imdb.py b/modules/imdb.py index d6b0a487..9436768e 100644 --- a/modules/imdb.py +++ b/modules/imdb.py @@ -17,15 +17,13 @@ class Module(ModuleManager.BaseModule): :usage: <movie/tv title> """ page = utils.http.request(URL_OMDB, get_params={ - "t": event["args"], - "apikey": self.bot.config["omdbapi-api-key"]}, - json=True) + "apikey": self.bot.config["omdbapi-api-key"], + "t": event["args"]}).json() if page: - if "Title" in page.data: + if "Title" in page: event["stdout"].write("%s, %s (%s) %s (%s/10.0) %s" % ( - page.data["Title"], page.data["Year"], page.data["Runtime"], - page.data["Plot"], page.data["imdbRating"], - URL_IMDBTITLE % page.data["imdbID"])) + page["Title"], page["Year"], page["Runtime"], page["Plot"], + page["imdbRating"], URL_IMDBTITLE % page["imdbID"])) else: event["stderr"].write("Title not found") else: diff --git a/modules/imgur.py b/modules/imgur.py index 30208c36..35bb6329 100644 --- a/modules/imgur.py +++ b/modules/imgur.py @@ -48,11 +48,10 @@ class Module(ModuleManager.BaseModule): def _parse_gallery(self, hash): api_key = self.bot.config["imgur-api-key"] result = utils.http.request(URL_GALLERY % hash, - headers={"Authorization": "Client-ID %s" % api_key}, - json=True) + headers={"Authorization": "Client-ID %s" % api_key}).json() - if result and result.data["success"]: - data = result.data["data"] + if result and result["success"]: + data = result["data"] text = "" nsfw = utils.irc.bold(NSFW_TEXT) + " " if data["nsfw"] else "" @@ -75,11 +74,10 @@ class Module(ModuleManager.BaseModule): def _parse_image(self, hash): api_key = self.bot.config["imgur-api-key"] result = utils.http.request(URL_IMAGE % hash, - headers={"Authorization": "Client-ID %s" % api_key}, - json=True) + headers={"Authorization": "Client-ID %s" % api_key}).json() - if result and result.data["success"]: - data = result.data["data"] + if result and result["success"]: + data = result["data"] text = "" nsfw = utils.irc.bold(NSFW_TEXT) + " " if data["nsfw"] else "" @@ -102,11 +100,10 @@ class Module(ModuleManager.BaseModule): def _image_info(self, hash): api_key = self.bot.config["imgur-api-key"] result = utils.http.request(URL_IMAGE % hash, - headers={"Authorization": "Client-ID %s" % api_key}, - json=True) + headers={"Authorization": "Client-ID %s" % api_key}).json() - if result and result.data["success"]: - data = result.data["data"] + if result and result["success"]: + data = result["data"] text = self._prefix(data) text += "(%s %dx%d, %d views)" % (data["type"], data["width"], @@ -120,11 +117,10 @@ class Module(ModuleManager.BaseModule): def _gallery_info(self, hash): api_key = self.bot.config["imgur-api-key"] result = utils.http.request(URL_GALLERY % hash, - headers={"Authorization": "Client-ID %s" % api_key}, - json=True) + headers={"Authorization": "Client-ID %s" % api_key}).json() - if result and result.data["success"]: - data = result.data["data"] + if result and result["success"]: + data = result["data"] text = self._prefix(data) text += "(%d views, %d▲▼%d)" % (data["views"], data["ups"], data["downs"]) diff --git a/modules/ip_addresses.py b/modules/ip_addresses.py index 744900f6..73ec061f 100644 --- a/modules/ip_addresses.py +++ b/modules/ip_addresses.py @@ -85,19 +85,17 @@ class Module(ModuleManager.BaseModule): :usage: <IP> :prefix: GeoIP """ - page = utils.http.request(URL_GEOIP % event["args_split"][0], - json=True) + page = utils.http.request(URL_GEOIP % event["args_split"][0]).json() if page: - if page.data["status"] == "success": - data = page.data["query"] - data += " | Organisation: %s" % page.data["org"] - data += " | City: %s" % page.data["city"] - data += " | Region: %s (%s)" % (page.data["regionName"], - page.data["countryCode"]) - data += " | ISP: %s" % page.data["isp"] - data += " | Lon/Lat: %s/%s" % (page.data["lon"], - page.data["lat"]) - data += " | Timezone: %s" % page.data["timezone"] + if page["status"] == "success": + data = page["query"] + data += " | Organisation: %s" % page["org"] + data += " | City: %s" % page["city"] + data += " | Region: %s (%s)" % ( + page["regionName"], page["countryCode"]) + data += " | ISP: %s" % page["isp"] + data += " | Lon/Lat: %s/%s" % (page["lon"], page["lat"]) + data += " | Timezone: %s" % page["timezone"] event["stdout"].write(data) else: event["stderr"].write("No geoip data found") diff --git a/modules/ircv3_msgid.py b/modules/ircv3_msgid.py index d5690286..f95f9fd4 100644 --- a/modules/ircv3_msgid.py +++ b/modules/ircv3_msgid.py @@ -22,3 +22,10 @@ class Module(ModuleManager.BaseModule): 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_typing.py b/modules/ircv3_typing.py new file mode 100644 index 00000000..ca2fce2b --- /dev/null +++ b/modules/ircv3_typing.py @@ -0,0 +1,27 @@ +from src import IRCLine, ModuleManager, utils + +CAP = utils.irc.Capability("message-tags", "draft/message-tags-0.2") + +class Module(ModuleManager.BaseModule): + def _tagmsg(self, target, state): + return IRCLine.ParsedLine("TAGMSG", [target], + tags={"+draft/typing": state}) + def _has_tags(self, server): + return server.has_capability(CAP) + + @utils.hook("preprocess.command") + def preprocess(self, event): + if (self._has_tags(event["server"]) and + event["hook"].get_kwarg("expect_output", True)): + event["target"]._typing = True + event["server"].send(self._tagmsg(event["target_str"], "active"), + immediate=True) + else: + event["target"]._typing = False + + @utils.hook("postprocess.command") + def postprocess(self, event): + if (event["target"]._typing and + not event["stdout"].has_text() and + not event["stderr"].has_text()): + event["server"].send(self._tagmsg(event["target_str"], "done")) diff --git a/modules/lastfm.py b/modules/lastfm.py index 756f1f49..14e9128a 100644 --- a/modules/lastfm.py +++ b/modules/lastfm.py @@ -40,11 +40,10 @@ class Module(ModuleManager.BaseModule): page = utils.http.request(URL_SCROBBLER, get_params={ "method": "user.getrecenttracks", "user": lastfm_username, "api_key": self.bot.config["lastfm-api-key"], - "format": "json", "limit": "1"}, json=True) + "format": "json", "limit": "1"}).json() if page: - if ("recenttracks" in page.data and - len(page.data["recenttracks"]["track"])): - now_playing = page.data["recenttracks"]["track"] + if "recenttracks" in page and len(page["recenttracks"]["track"]): + now_playing = page["recenttracks"]["track"] if type(now_playing) == list: now_playing = now_playing[0] @@ -55,7 +54,7 @@ class Module(ModuleManager.BaseModule): np = True else: played = int(now_playing["date"]["uts"]) - dt = utils.datetime.datetime_utcnow() + dt = utils.datetime.utcnow() np = bool((dt.timestamp()-played) < 120) time_language = "is listening to" if np else "last listened to" @@ -70,12 +69,12 @@ class Module(ModuleManager.BaseModule): "method": "track.getInfo", "artist": artist, "track": track_name, "autocorrect": "1", "api_key": self.bot.config["lastfm-api-key"], - "user": lastfm_username, "format": "json"}, json=True) + "user": lastfm_username, "format": "json"}).json() - track = info_page.data.get("track", {}) + track = info_page.get("track", {}) tags_str = "" - if "toptags" in track: + if "toptags" in track and track["toptags"]["tag"]: tags = [t["name"] for t in track["toptags"]["tag"]] tags_str = " [%s]" % ", ".join(tags) diff --git a/modules/line_handler/__init__.py b/modules/line_handler/__init__.py index 5001266c..ddea6fdc 100644 --- a/modules/line_handler/__init__.py +++ b/modules/line_handler/__init__.py @@ -218,12 +218,12 @@ class Module(ModuleManager.BaseModule): # response to a WHO command for user information @utils.hook("raw.received.352", default_event=True) def handle_352(self, event): - core.handle_352(event) + 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(event) + core.handle_354(self.events, event) # response to an empty mode command @utils.hook("raw.received.324") diff --git a/modules/line_handler/channel.py b/modules/line_handler/channel.py index 385bf6b2..91150839 100644 --- a/modules/line_handler/channel.py +++ b/modules/line_handler/channel.py @@ -72,8 +72,7 @@ def join(events, event): hostname=event["line"].source.hostname) if account: - user.identified_account = account - user.identified_account_id = event["server"].get_user(account).get_id() + user.account = account if realname: user.realname = realname diff --git a/modules/line_handler/core.py b/modules/line_handler/core.py index 607c8dd4..d72bf223 100644 --- a/modules/line_handler/core.py +++ b/modules/line_handler/core.py @@ -105,7 +105,7 @@ def invite(events, event): events.on("received.invite").call(user=user, target_channel=target_channel, server=event["server"], target_user=target_user) -def handle_352(event): +def handle_352(events, event): nickname = event["line"].args[5] username = event["line"].args[2] hostname = event["line"].args[3] @@ -117,8 +117,10 @@ def handle_352(event): 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(event): +def handle_354(events, event): if event["line"].args[1] == "111": nickname = event["line"].args[4] username = event["line"].args[2] @@ -136,11 +138,11 @@ def handle_354(event): target.hostname = hostname target.realname = realname if not account == "0": - target.identified_account = account - target.identified_account_id = event["server"].get_user(account - ).get_id() + target.account = account else: - target.identified_account = None + 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 diff --git a/modules/line_handler/user.py b/modules/line_handler/user.py index 20521675..d1592cd7 100644 --- a/modules/line_handler/user.py +++ b/modules/line_handler/user.py @@ -39,6 +39,8 @@ 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, @@ -48,9 +50,6 @@ def nick(events, event): new_nickname=new_nickname, old_nickname=old_nickname) event["server"].set_own_nickname(new_nickname) - user.set_nickname(new_nickname) - event["server"].change_user_nickname(old_nickname, new_nickname) - def away(events, event): user = event["server"].get_user(event["line"].source.nickname) message = event["line"].args.get(0) @@ -94,13 +93,10 @@ def account(events, event): user = event["server"].get_user(event["line"].source.nickname) if not event["line"].args[0] == "*": - user.identified_account = event["line"].args[0] - user.identified_account_id = event["server"].get_user( - event["line"].args[0]).get_id() + user.account = event["line"].args[0] events.on("received.account.login").call(user=user, server=event["server"], account=event["line"].args[0]) else: - user.identified_account = None - user.identified_account_id = None + user.account = None events.on("received.account.logout").call(user=user, server=event["server"]) diff --git a/modules/location.py b/modules/location.py index 1858a915..dfef9244 100644 --- a/modules/location.py +++ b/modules/location.py @@ -14,11 +14,10 @@ class Module(ModuleManager.BaseModule): self.exports.add("get-location", self._get_location) def _get_location(self, s): - page = utils.http.request(URL_OPENCAGE, get_params={ - "q": s, "key": self.bot.config["opencagedata-api-key"], "limit": "1" - }, json=True) - if page and page.data["results"]: - result = page.data["results"][0] + page = utils.http.request(URL_OPENCAGE, get_params={"limit": "1", + "q": s, "key": self.bot.config["opencagedata-api-key"]}).json() + if page and page["results"]: + result = page["results"][0] timezone = result["annotations"]["timezone"]["name"] lat = result["geometry"]["lat"] lon = result["geometry"]["lng"] diff --git a/modules/markov.py b/modules/markov.py index c2d4a792..9ba9d31f 100644 --- a/modules/markov.py +++ b/modules/markov.py @@ -39,7 +39,7 @@ class Module(ModuleManager.BaseModule): if page.code == 200: event["stdout"].write("Importing...") self._load_thread = threading.Thread(target=self._load_loop, - args=[event["target"].id, page.data]) + args=[event["target"].id, page.decode()]) self._load_thread.daemon = True self._load_thread.start() else: diff --git a/modules/more.py b/modules/more.py new file mode 100644 index 00000000..52849938 --- /dev/null +++ b/modules/more.py @@ -0,0 +1,23 @@ +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/nr.py b/modules/nr.py index c7f988d6..7c7303d0 100644 --- a/modules/nr.py +++ b/modules/nr.py @@ -126,8 +126,6 @@ class Module(ModuleManager.BaseModule): client = self.client colours = self.COLOURS - eagle_key = self.bot.config.get("eagle-api-key", None) - eagle_url = self.bot.config.get("eagle-api-url", None) schedule = {} location_code = event["args_split"][0].upper() @@ -237,16 +235,6 @@ class Module(ModuleManager.BaseModule): trains.append(parsed) - if eagle_url: - summary_query = utils.http.request("%s/json/summaries/%s?uids=%s" % (eagle_url, now.date().isoformat(), "%20".join([a["uid"] for a in trains])), json=True, headers={"x-eagle-key": self.bot.config["eagle-api-key"]}) - if summary_query: - for t in trains: - summary = summary_query.data.get(t["uid"]) - if summary: - t.update(summary) - summary_plat = summary.get("platforms", {}).get(query["crs"]) - if summary_plat and t["platform"]=="?": - t["platform"], t["platform_prefix"] = summary_plat, "s" for t in trains: t["dest_summary"] = "/".join(["%s%s" %(a["code"]*filter["crs"] or a["name"], " " + a["via"] @@ -316,8 +304,6 @@ class Module(ModuleManager.BaseModule): "S": "ship", "T": "trip", "1": "train", "2": "freight", "3": "trip", "4": "ship", "5": "bus"} - eagle_key = self.bot.config.get("eagle-api-key", None) - eagle_url = self.bot.config.get("eagle-api-url", None) schedule = {} sources = [] @@ -336,10 +322,6 @@ class Module(ModuleManager.BaseModule): if len(service_id) <= 8: query = client.service.QueryServices(service_id, datetime.utcnow().date().isoformat(), datetime.utcnow().time().strftime("%H:%M:%S+0000")) - if eagle_url: - schedule_query = utils.http.request("%s/json/schedule/%s/%s" % (eagle_url, service_id, datetime.now().date().isoformat()), json=True, headers={"x-eagle-key": eagle_key}) - if schedule_query: - schedule = schedule_query.data["current"] if not query and not schedule: return event["stdout"].write("No service information is available for this identifier.") diff --git a/modules/onionoo.py b/modules/onionoo.py index 427cc487..bfd882cd 100644 --- a/modules/onionoo.py +++ b/modules/onionoo.py @@ -11,9 +11,9 @@ URL_RELAY_SEARCH_DETAILS = "https://metrics.torproject.org/rs.html#details/" def _get_relays_details(search): page = utils.http.request( - URL_ONIONOO_DETAILS, get_params={"search": search}, json=True) - if page and "relays" in page.data: - return page.data["relays"] + URL_ONIONOO_DETAILS, get_params={"search": search}).json() + if page and "relays" in page: + return page["relays"] raise utils.EventResultsError() def _format_relay_summary_message(relays, search): diff --git a/modules/permissions/README.md b/modules/permissions/README.md deleted file mode 100644 index c18dcc8b..00000000 --- a/modules/permissions/README.md +++ /dev/null @@ -1,38 +0,0 @@ -# Permissions - -## Adding an admin user - -This is a little complex at the moment but it will get easier some time soon. - -### Registering user - -Join a channel that BitBot is in (he'll automatically join #bitbot with default -configuration) and then type - -> /msg <botnick> register <password> - -### Give * permission - -The `*` permission is a special permission that gives you completely unfettered -access to all of BitBot's functions. - -On IRC, send this to BitBot and take note of the ID response - -> /msg <botnick> myid - -Then take that ID and open the database in sqlite3 (default database location is -`databases/bot.db` - -> $ sqlite3 databases/bot.db - -And then insert your `*` permission - -> INSERT INTO user_settings VALUES (<id>, 'permissions', '["*"]'); - -(where `<id>` is the response from the `myid` command) - -### Authenticating - -To authenticate yourself as your admin user, use the following command - -> /msg <botnick> identify <password> diff --git a/modules/permissions/__init__.py b/modules/permissions/__init__.py index 0311dfa3..70cfdf40 100644 --- a/modules/permissions/__init__.py +++ b/modules/permissions/__init__.py @@ -1,54 +1,24 @@ -#--depends-on commands -#--depends-on config - import base64, binascii, os import scrypt from src import ModuleManager, utils -REQUIRES_IDENTIFY = "You need to be identified to use that command" -REQUIRES_IDENTIFY_INTERNAL = ("You need to be identified to use that command " - "(/msg %s register | /msg %s identify)") +HOSTMASKS_SETTING = "hostmask-account" +NO_PERMISSION = "You do not have permission to do that" -@utils.export("serverset", utils.OptionsSetting(["internal", "ircv3-account"], - "identity-mechanism", "Set the identity mechanism for this server")) class Module(ModuleManager.BaseModule): - @utils.hook("new.user") - def new_user(self, event): - self._logout(event["user"]) - event["user"].admin_master = False + def on_load(self): + self.exports.add("is-identified", self._is_identified) + self.exports.add("account-name", self._account_name) - 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("new.server") + def new_server(self, event): + hostmasks = {} - def command_line(self, args: str): - if args == "master-password": - master_password = self._master_password() - print("one-time master password: %s" % master_password) - else: - raise ValueError("Unknown command-line argument") - @utils.hook("received.command.masterpassword", private_only=True) - def master_password(self, event): - """ - :permission: master-password - """ - master_password = self._master_password() - event["stdout"].write("One-time master password: %s" % - master_password) - - @utils.hook("received.part") - def on_part(self, event): - if len(event["user"].channels) == 0 and event["user" - ].identified_account_override: - event["user"].send_notice("You no longer share any channels " - "with me so you have been signed out") - - def _get_hash(self, server, account): - hash, salt = server.get_user(account).get_setting("authentication", - (None, None)) - return hash, salt + for account, user_hostmasks in event["server"].get_all_user_settings( + HOSTMASKS_SETTING): + for hostmask in user_hostmasks: + hostmasks[hostmask] = account + event["server"]._hostmasks = hostmasks def _make_salt(self): return base64.b64encode(os.urandom(64)).decode("utf8") @@ -61,46 +31,169 @@ class Module(ModuleManager.BaseModule): hash = base64.b64encode(scrypt.hash(password, salt)).decode("utf8") return hash, salt - def _identified(self, server, user, account): - user.identified_account_override = account - user.identified_account_id_override = server.get_user(account).get_id() + 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 in server._hostmasks.keys(): + if utils.irc.hostmask_match(user_hostmask, hostmask): + return (hostmask, server._hostmasks[hostmask]) + 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) - def _logout(self, user): - user.identified_account_override = None - user.identified_account_id_override = None + @utils.hook("received.chghost") + @utils.hook("received.nick") + @utils.hook("received.who") + @utils.hook("received.whox") + 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", private_only=True, min_args=1) + @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"].admin_master = True + event["user"]._master_admin = True event["stdout"].write("Master login successful") return event["stderr"].write("Master login failed") - - @utils.hook("received.command.identify", private_only=True, min_args=1) - def identify(self, event): + @utils.hook("received.command.mypermissions") + @utils.kwarg("authenticated", True) + def my_permissions(self, event): """ - :help: Identify yourself - :usage: [account] <password> + :help: Show your permissions """ - identity_mechanism = event["server"].get_setting("identity-mechanism", - "internal") - if not identity_mechanism == "internal": - raise utils.EventError("The 'identify' command isn't available " - "on this network") + 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 event["user"].identified_account_override: + if not self._is_identified(event["user"]): if len(event["args_split"]) > 1: account = event["args_split"][0] password = " ".join(event["args_split"][1:]) @@ -112,9 +205,11 @@ class Module(ModuleManager.BaseModule): if hash and salt: attempt, _ = self._make_hash(password, salt) if utils.security.constant_time_compare(attempt, hash): - self._identified(event["server"], event["user"], account) + 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) + "been identified as %s." % account) self.events.on("internal.identified").call( user=event["user"]) else: @@ -124,179 +219,130 @@ class Module(ModuleManager.BaseModule): event["stderr"].write("Account '%s' is not registered" % account) else: - event["stderr"].write("You are already identified") + event["stderr"].write("You are already identified as %s" % + self._account_name(event["user"])) - @utils.hook("received.command.register", private_only=True, min_args=1) - def register(self, event): - """ - :help: Register yourself - :usage: <password> - """ - identity_mechanism = event["server"].get_setting("identity-mechanism", - "internal") - if not identity_mechanism == "internal": - raise utils.EventError("The 'identify' command isn't available " - "on this network") + @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) - 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]) - self._identified(event["server"], event["user"], - event["user"].nickname) - event["stdout"].write("Nickname registered successfully") + 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: - event["stderr"].write("This nickname is already registered") + permissions = event["args_split"][2:] + if not permissions: + raise utils.EventError("Please provide at least 1 permission") + user_permissions = self._get_permissions(target_user) - @utils.hook("received.command.setpassword", authenticated=True, min_args=1) - def set_password(self, event): - """ - :help: Change your password - :usage: <password> - """ - hash, salt = self._make_hash(event["args"]) - event["user"].set_setting("authentication", [hash, salt]) - event["stdout"].write("Set your password") + 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) - @utils.hook("received.command.logout", private_only=True) - def logout(self, event): - """ - :help: Logout from your identified account - """ - if event["user"].identified_account_override: - self._logout(event["user"]) - event["stdout"].write("You have been logged out") - else: - event["stderr"].write("You are not logged in") + 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.resetpassword", private_only=True, - min_args=2) - def reset_password(self, event): - """ - :help: Reset a given user's password - :usage: <nickname> <password> - :permission: resetpassword - """ - target = event["server"].get_user(event["args_split"][0]) - password = " ".join(event["args_split"][1:]) - registered = target.get_setting("authentication", None) + @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 registered == None: - event["stderr"].write("'%s' isn't registered" % target.nickname) + if subcommand == "list": + event["stdout"].write("Your hostmasks: %s" % ", ".join(hostmasks)) else: - hash, salt = self._make_hash(password) - target.set_setting("authentication", [hash, salt]) - event["stdout"].write("Reset password for '%s'" % - target.nickname) + if event["args_split"][1:]: + hostmask = event["args_split"][1] + else: + hostmask = "*!%s" % event["user"].userhost() + account = self._account_name(event["user"]) - def _check_command(self, event, permission, authenticated): - if event["user"].admin_master and (permission or authenticated): - return utils.consts.PERMISSION_FORCE_SUCCESS, None + 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) - identity_mechanism = event["server"].get_setting("identity-mechanism", - "internal") - identified_account = None - if identity_mechanism == "internal": - identified_account = event["user"].identified_account_override - elif identity_mechanism == "ircv3-account": - identified_account = (event["user"].identified_account or - event["tags"].get("account", None)) + self._specific_hostmask(event["server"], hostmask, account) + event["server"]._hostmasks[hostmask] = account - identified_user = None - permissions = [] - if identified_account: - identified_user = event["server"].get_user(identified_account) - permissions = identified_user.get_setting("permissions", []) + 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) - if permission: - has_permission = permission and ( - permission in permissions or "*" in permissions) - if not identified_account or not has_permission: - return (utils.consts.PERMISSION_ERROR, - "You do not have permission to do that") - else: - return utils.consts.PERMISSION_FORCE_SUCCESS, None - elif authenticated: - if not identified_account: - error = None - if identity_mechanism == "internal": - error = REQUIRES_IDENTIFY_INTERNAL % ( - event["server"].nickname, event["server"].nickname) - else: - error = REQUIRES_IDENTIFY - return utils.consts.PERMISSION_ERROR, error + self._specific_hostmask(event["server"], hostmask, None) + if hostmask in event["server"]._hostmasks: + del event["server"]._hostmasks[hostmask] + + event["stdout"].write("Removed %s from your hostmasks" + % hostmask) else: - return utils.consts.PERMISSION_FORCE_SUCCESS, None + 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) - return self._check_command(event, permission, authenticated) - - @utils.hook("check.command.permission") - def check_command(self, event): - return self._check_command(event, event["request_args"][0], False) - - @utils.hook("received.command.mypermissions", 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)) - - def _get_user_details(self, server, nickname): - target = server.get_user(nickname) - registered = bool(target.get_setting("authentication", None)) - permissions = target.get_setting("permissions", []) - return [target, registered, permissions] - - @utils.hook("received.command.givepermission", min_args=2) - def give_permission(self, event): - """ - :help: Give a given permission to a given user - :usage: <nickname> <permission> - :permission: givepermission - """ - permission = event["args_split"][1].lower() - target, registered, permissions = self._get_user_details( - event["server"], event["args_split"][0]) - - if target.get_identified_account() == None: - raise utils.EventError("%s isn't registered" % target.nickname) - - if permission in permissions: - event["stderr"].write("%s already has permission '%s'" % ( - target.nickname, permission)) + if not permission == None: + allowed = self._has_permission(event["user"], permission) + elif authenticated: + allowed = self._is_identified(event["user"]) else: - permissions.append(permission) - target.set_setting("permissions", permissions) - event["stdout"].write("Gave permission '%s' to %s" % ( - permission, target.nickname)) - @utils.hook("received.command.removepermission", min_args=2) - def remove_permission(self, event): - """ - :help: Remove a given permission from a given user - :usage: <nickname> <permission> - :permission: removepermission - """ - permission = event["args_split"][1].lower() - target, registered, permissions = self._get_user_details( - event["server"], event["args_split"][0]) + return - if target.identified_account == None: - raise utils.EventError("%s isn't registered" % target.nickname) + return self._assert(allowed) - if permission not in permissions: - event["stderr"].write("%s doesn't have permission '%s'" % ( - target.nickname, permission)) - else: - permissions.remove(permission) - if not permissions: - target.del_setting("permissions") - else: - target.set_setting("permissions", permissions) - event["stdout"].write("Removed permission '%s' from %s" % ( - permission, target.nickname)) + @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 index e2c953e9..e6a34992 100644 --- a/modules/print_activity.py +++ b/modules/print_activity.py @@ -20,9 +20,9 @@ class Module(ModuleManager.BaseModule): 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"]), event["context"] or "", - utils.irc.parse_format(line)]) + str(event["server"]), context, utils.irc.parse_format(line)]) @utils.hook("formatted.message.channel") @utils.hook("formatted.notice.channel") diff --git a/modules/rss.py b/modules/rss.py index e8cba9e8..cb68a304 100644 --- a/modules/rss.py +++ b/modules/rss.py @@ -67,7 +67,7 @@ class Module(ModuleManager.BaseModule): # async url get failed continue - feed = feedparser.parse(pages[url].data) + feed = feedparser.parse(pages[url].decode()) feed_title = feed["feed"].get("title", None) max_ids = len(feed["entries"])*10 @@ -105,8 +105,7 @@ class Module(ModuleManager.BaseModule): def _get_entries(self, url, max: int=None): try: - data = utils.http.request(url) - feed = feedparser.parse(data.data) + feed = feedparser.parse(utils.http.request(url).data) except Exception as e: self.log.warn("failed to parse RSS %s", [url], exc_info=True) feed = None diff --git a/modules/rust.py b/modules/rust.py index 2e74d945..2b4a2061 100644 --- a/modules/rust.py +++ b/modules/rust.py @@ -35,14 +35,14 @@ class Module(ModuleManager.BaseModule): args["code"] = FN_TEMPLATE % event["args"] try: page = utils.http.request(EVAL_URL, post_data=args, - method="POST", json=True, content_type="application/json") + method="POST", content_type="application/json").json() except socket.timeout: raise utils.EventError("%s: eval timed out" % event["user"].nickname) - err_or_out = "stdout" if page.data["success"] else "stderr" + err_or_out = "stdout" if page["success"] else "stderr" event[err_or_out].write("%s: %s" % (event["user"].nickname, - page.data[err_or_out].strip("\n"))) + page[err_or_out].strip("\n"))) @utils.hook("received.command.crate") @utils.kwarg("min_args", 1) @@ -50,10 +50,10 @@ class Module(ModuleManager.BaseModule): @utils.kwarg("usage", "<crate-name>") def crate(self, event): query = event["args_split"][0] - request = utils.http.Request(API_CRATE % query, json=True) + request = utils.http.Request(API_CRATE % query) response = utils.http.request(request) if response.code == 200: - crate = response.data["crate"] + crate = response.json()["crate"] name = crate["id"] url = URL_CRATE % name event["stdout"].write("%s %s: %s - %s" % ( diff --git a/modules/scripts/__init__.py b/modules/scripts/__init__.py index 6f939366..39a489a0 100644 --- a/modules/scripts/__init__.py +++ b/modules/scripts/__init__.py @@ -60,9 +60,9 @@ class Module(ModuleManager.BaseModule): if out: if proc.returncode == 0: if "stdout" in event: - event["stdout"].set_prefix(name) + event["stdout"].prefix = name event["stdout"].write(out) else: if "stderr" in event: - event["stderr"].set_prefix(name) + event["stderr"].prefix = name event["stderr"].write(out) diff --git a/modules/shorturl.py b/modules/shorturl.py index e18cdcfd..10bd9dd3 100644 --- a/modules/shorturl.py +++ b/modules/shorturl.py @@ -57,10 +57,10 @@ class Module(ModuleManager.BaseModule): access_token = self.bot.config.get("bitly-api-key", None) if access_token: page = utils.http.request(URL_BITLYSHORTEN, get_params={ - "access_token": access_token, "longUrl": url}, json=True) + "access_token": access_token, "longUrl": url}).json() - if page and page.data["data"]: - return page.data["data"]["url"] + if page["data"]: + return page["data"]["url"] return None def _find_url(self, target, args): diff --git a/modules/soundcloud.py b/modules/soundcloud.py index 74b57efc..9355caef 100644 --- a/modules/soundcloud.py +++ b/modules/soundcloud.py @@ -45,11 +45,11 @@ class Module(ModuleManager.BaseModule): page = utils.http.request( URL_SOUNDCLOUD_TRACK if has_query else URL_SOUNDCLOUD_RESOLVE, - get_params=get_params, json=True) + get_params=get_params).json() if page: - if len(page.data): - page = page.data[0] if has_query else page + if len(page): + page = page[0] if has_query else page title = page["title"] user = page["user"]["username"] duration = time.strftime("%H:%M:%S", time.gmtime(page[ diff --git a/modules/spotify.py b/modules/spotify.py index 962b0a62..9438b149 100644 --- a/modules/spotify.py +++ b/modules/spotify.py @@ -24,12 +24,11 @@ class Module(ModuleManager.BaseModule): page = utils.http.request(URL_TOKEN, method="POST", headers={"Authorization": "Basic %s" % bearer}, - post_data={"grant_type": "client_credentials"}, - json=True) + post_data={"grant_type": "client_credentials"}).json() - token = page.data["access_token"] + token = page["access_token"] self._token = token - self._token_expires = time.time()+page.data["expires_in"] + self._token_expires = time.time()+page["expires_in"] return token @utils.hook("received.command.sp", alias_of="spotify") @@ -42,11 +41,10 @@ class Module(ModuleManager.BaseModule): token = self._get_token() page = utils.http.request(URL_SEARCH, get_params={"type": "track", "limit": 1, "q": event["args"]}, - headers={"Authorization": "Bearer %s" % token}, - json=True) + headers={"Authorization": "Bearer %s" % token}).json() if page: - if len(page.data["tracks"]["items"]): - item = page.data["tracks"]["items"][0] + if len(page["tracks"]["items"]): + item = page["tracks"]["items"][0] title = item["name"] artist_name = item["artists"][0]["name"] url = item["external_urls"]["spotify"] diff --git a/modules/to.py b/modules/tell.py index eb0ad03a..93aaba0c 100644 --- a/modules/to.py +++ b/modules/tell.py @@ -15,13 +15,14 @@ class Module(ModuleManager.BaseModule): if messages: event["channel"].del_user_setting(event["user"].get_id(), "to") - @utils.hook("received.command.to", min_args=2, channel_only=True) - def to(self, event): - """ - :help: Relay a message to a user the next time they talk in this - channel - :usage: <nickname> <message> - """ + @utils.hook("received.command.to", alias_of="tell") + @utils.hook("received.command.tell") + @utils.kwarg("min_args", 2) + @utils.kwarg("channel_only", True) + @utils.kwarg("help", + "Relay a message to a user the next time they talk in this channel") + @utils.kwarg("usage", "<nickname> <message>") + def tell(self, event): target_name = event["args_split"][0] if not event["server"].has_user_id(target_name): raise utils.EventError("I've never seen %s before" % target_name) diff --git a/modules/thesaurus.py b/modules/thesaurus.py index df988c9e..bd30e67e 100644 --- a/modules/thesaurus.py +++ b/modules/thesaurus.py @@ -14,17 +14,18 @@ class Module(ModuleManager.BaseModule): :usage: <word> [type] """ phrase = event["args_split"][0] - page = utils.http.request(URL_THESAURUS % (self.bot.config[ - "bighugethesaurus-api-key"], phrase), json=True) + page = utils.http.request(URL_THESAURUS % ( + self.bot.config["bighugethesaurus-api-key"], phrase)) syn_ant = event["command"][:3] if page: if page.code == 404: raise utils.EventError("Word not found") + page = page.json() if not len(event["args_split"]) > 1: word_types = [] - for word_type in page.data.keys(): - if syn_ant in page.data[word_type]: + for word_type in page.keys(): + if syn_ant in page[word_type]: word_types.append(word_type) if word_types: word_types = sorted(word_types) @@ -35,11 +36,11 @@ class Module(ModuleManager.BaseModule): event["stderr"].write("No categories available") else: category = event["args_split"][1].lower() - if category in page.data: - if syn_ant in page.data[category]: + if category in page: + if syn_ant in page[category]: event["stdout"].write("%ss for %s: %s" % ( event["command"].title(), phrase, ", ".join( - page.data[category][syn_ant]))) + page[category][syn_ant]))) else: event["stderr"].write("No %ss for %s" % ( event["command"], phrase)) diff --git a/modules/title.py b/modules/title.py index 96a9ee0b..309b9b00 100644 --- a/modules/title.py +++ b/modules/title.py @@ -50,15 +50,17 @@ class Module(ModuleManager.BaseModule): return -1, None try: - page = utils.http.request(url, parse=True) - except utils.http.HTTPWrongContentTypeException: - return -1, None + page = utils.http.request(url) except Exception as e: self.log.error("failed to get URL title for %s: %s", [url, str(e)]) return -1, None - if page.data.title: - title = utils.parse.line_normalise(page.data.title.text) + if not page.content_type in utils.http.SOUP_CONTENT_TYPES: + return -1, None + page = page.soup() + + if page.title: + title = utils.parse.line_normalise(page.title.text) if not title: return -3, None @@ -135,3 +137,8 @@ class Module(ModuleManager.BaseModule): event["stdout"].write(title) else: event["stderr"].write("Failed to get title") + + @utils.hook("received.command.notitle") + @utils.kwarg("expect_output", False) + def notitle(self, event): + pass diff --git a/modules/trakt.py b/modules/trakt.py index ad7a6644..dc8596c0 100644 --- a/modules/trakt.py +++ b/modules/trakt.py @@ -25,26 +25,26 @@ class Module(ModuleManager.BaseModule): page = utils.http.request(URL_TRAKT % username, headers={ "Content-Type": "application/json", "trakt-api-version": "2", "trakt-api-key": - self.bot.config["trakt-api-key"]}, json=True, - code=True) + self.bot.config["trakt-api-key"]}) if page: if page.code == 200: - type = page.data["type"] + page = page.json() + type = page["type"] if type == "movie": - title = page.data["movie"]["title"] - year = page.data["movie"]["year"] - slug = page.data["movie"]["ids"]["slug"] + title = page["movie"]["title"] + year = page["movie"]["year"] + slug = page["movie"]["ids"]["slug"] event["stdout"].write( "%s is now watching %s (%s) %s" % ( username, title, year, URL_TRAKTSLUG % ("movie", slug))) elif type == "episode": - season = page.data["episode"]["season"] - episode_number = page.data["episode"]["number"] - episode_title = page.data["episode"]["title"] - show_title = page.data["show"]["title"] - show_year = page.data["show"]["year"] - slug = page.data["show"]["ids"]["slug"] + season = page["episode"]["season"] + episode_number = page["episode"]["number"] + episode_title = page["episode"]["title"] + show_title = page["show"]["title"] + show_year = page["show"]["year"] + slug = page["show"]["ids"]["slug"] event["stdout"].write( "%s is now watching %s s%se%s - %s %s" % ( username, show_title, str(season).zfill(2), diff --git a/modules/translate.py b/modules/translate.py index b12f0d0a..e11de291 100644 --- a/modules/translate.py +++ b/modules/translate.py @@ -35,11 +35,11 @@ class Module(ModuleManager.BaseModule): phrase = phrase.split(" ", 1)[1] page = utils.http.request(URL_TRANSLATE, get_params={ - "client": "gtx", "sl": source_language, - "tl": target_language, "dt": "t", "q": phrase}) + "client": "gtx", "dt": "t", "q": phrase, + "sl": source_language, "tl": target_language}) if page and not page.data.startswith(b"[null,null,"): - data = page.data.decode("utf8") + data = page.decode("utf8") while ",," in data: data = data.replace(",,", ",null,") data = data.replace("[,", "[null,") diff --git a/modules/urbandictionary.py b/modules/urbandictionary.py index b71dc45b..a5098c3f 100644 --- a/modules/urbandictionary.py +++ b/modules/urbandictionary.py @@ -24,11 +24,11 @@ class Module(ModuleManager.BaseModule): term = " ".join(term) page = utils.http.request(URL_URBANDICTIONARY, - get_params={"term": term}, json=True) + get_params={"term": term}).json() if page: - if len(page.data["list"]): - if number > 0 and len(page.data["list"]) > number-1: - definition = page.data["list"][number-1] + if len(page["list"]): + if number > 0 and len(page["list"]) > number-1: + definition = page["list"][number-1] event["stdout"].write("%s: %s" % (definition["word"], definition["definition"].replace("\n", " ").replace( "\r", "").replace(" ", " "))) diff --git a/modules/weather.py b/modules/weather.py index fe2c20bf..1677f990 100644 --- a/modules/weather.py +++ b/modules/weather.py @@ -56,31 +56,32 @@ class Module(ModuleManager.BaseModule): args["lat"] = lat args["lon"] = lon - page = utils.http.request(URL_WEATHER, get_params=args, json=True) + page = utils.http.request(URL_WEATHER, get_params=args).json() if page: - if "weather" in page.data: + if "weather" in page: if location_name: location_str = location_name else: - location_parts = [page.data["name"]] - if "country" in page.data["sys"]: - location_parts.append(page.data["sys"]["country"]) + location_parts = [page["name"]] + if "country" in page["sys"]: + location_parts.append(page["sys"]["country"]) location_str = ", ".join(location_parts) - celsius = "%dC" % page.data["main"]["temp"] - fahrenheit = "%dF" % ((page.data["main"]["temp"]*(9/5))+32) - description = page.data["weather"][0]["description"].title() - humidity = "%s%%" % page.data["main"]["humidity"] + celsius = "%dC" % page["main"]["temp"] + fahrenheit = "%dF" % ((page["main"]["temp"]*(9/5))+32) + description = page["weather"][0]["description"].title() + humidity = "%s%%" % page["main"]["humidity"] # wind speed is in metres per second - 3.6* for KMh - wind_speed = 3.6*page.data["wind"]["speed"] + wind_speed = 3.6*page["wind"]["speed"] wind_speed_k = "%sKMh" % round(wind_speed, 1) wind_speed_m = "%sMPh" % round(0.6214*wind_speed, 1) if not nickname == None: - event["stdout"].append_prefix("|%s" % nickname) + location_str = "(%s) %s" % (nickname, location_str) + event["stdout"].write( - "(%s) %s/%s | %s | Humidity: %s | Wind: %s/%s" % ( + "%s | %s/%s | %s | Humidity: %s | Wind: %s/%s" % ( location_str, celsius, fahrenheit, description, humidity, wind_speed_k, wind_speed_m)) else: diff --git a/modules/wikipedia.py b/modules/wikipedia.py index a94109ed..3eae02b6 100644 --- a/modules/wikipedia.py +++ b/modules/wikipedia.py @@ -14,17 +14,20 @@ class Module(ModuleManager.BaseModule): :usage: <term> """ page = utils.http.request(URL_WIKIPEDIA, get_params={ - "action": "query", "prop": "extracts", - "titles": event["args"], "exintro": "", - "explaintext": "", "exchars": "500", - "redirects": "", "format": "json"}, json=True) + "action": "query", "prop": "extracts|info", "inprop": "url", + "titles": event["args"], "exintro": "", "explaintext": "", + "exchars": "500", "redirects": "", "format": "json"}).json() + if page: - pages = page.data["query"]["pages"] + pages = page["query"]["pages"] article = list(pages.items())[0][1] if not "missing" in article: title, info = article["title"], article["extract"] - info = info.replace("\n\n", " ").split("\n")[0] - event["stdout"].write("%s: %s" % (title, info)) + title = article["title"] + info = utils.parse.line_normalise(article["extract"]) + url = article["fullurl"] + + event["stdout"].write("%s: %s - %s" % (title, info, url)) else: event["stderr"].write("No results found") else: diff --git a/modules/wolframalpha.py b/modules/wolframalpha.py index 968b05ae..2b5c36fe 100644 --- a/modules/wolframalpha.py +++ b/modules/wolframalpha.py @@ -20,13 +20,13 @@ class Module(ModuleManager.BaseModule): page = utils.http.request(URL_WA, get_params={"i": event["args"], "appid": self.bot.config["wolframalpha-api-key"], - "reinterpret": "true", "units": "metric"}, code=True) + "reinterpret": "true", "units": "metric"}) except utils.http.HTTPTimeoutException: page = None if page: if page.code == 200: - event["stdout"].write("%s: %s" % (event["args"], page.data)) + event["stdout"].write("%s: %s" % (event["args"], page.decode())) else: event["stdout"].write("No results") else: diff --git a/modules/youtube.py b/modules/youtube.py index 30848f4a..35fe65dd 100644 --- a/modules/youtube.py +++ b/modules/youtube.py @@ -30,8 +30,7 @@ class Module(ModuleManager.BaseModule): def get_video_page(self, video_id): return utils.http.request(URL_YOUTUBEVIDEO, get_params={ "part": "contentDetails,snippet,statistics", - "id": video_id, "key": self.bot.config["google-api-key"]}, - json=True) + "id": video_id, "key": self.bot.config["google-api-key"]}).json() def _number(self, n): if n: @@ -39,8 +38,8 @@ class Module(ModuleManager.BaseModule): def video_details(self, video_id): page = self.get_video_page(video_id) - if page.data["items"]: - item = page.data["items"][0] + if page["items"]: + item = page["items"][0] snippet = item["snippet"] statistics = item["statistics"] content = item["contentDetails"] @@ -67,7 +66,7 @@ class Module(ModuleManager.BaseModule): video_views_str = "" if video_views: - video_views_str = ", %s views " % video_views + video_views_str = ", %s views" % video_views match = re.match(REGEX_ISO8601, video_duration) video_duration = "" @@ -88,11 +87,11 @@ class Module(ModuleManager.BaseModule): def get_playlist_page(self, playlist_id): return utils.http.request(URL_YOUTUBEPLAYLIST, get_params={ "part": "contentDetails,snippet", "id": playlist_id, - "key": self.bot.config["google-api-key"]}, json=True) + "key": self.bot.config["google-api-key"]}).json() def playlist_details(self, playlist_id): page = self.get_playlist_page(playlist_id) - if page.data["items"]: - item = page.data["items"][0] + if page["items"]: + item = page["items"][0] snippet = item["snippet"] content = item["contentDetails"] @@ -121,12 +120,11 @@ class Module(ModuleManager.BaseModule): search_page = utils.http.request(URL_YOUTUBESEARCH, get_params={"q": query, "part": "snippet", "maxResults": "1", "type": "video", - "key": self.bot.config["google-api-key"]}, - json=True) + "key": self.bot.config["google-api-key"]}).json() if search_page: - if search_page.data["pageInfo"]["totalResults"] > 0: - video_id = search_page.data["items"][0]["id"]["videoId"] + if search_page["pageInfo"]["totalResults"] > 0: + video_id = search_page["items"][0]["id"]["videoId"] return "https://youtu.be/%s" % video_id @utils.hook("received.command.yt", alias_of="youtube") @@ -157,11 +155,10 @@ class Module(ModuleManager.BaseModule): search_page = utils.http.request(URL_YOUTUBESEARCH, get_params={"q": search, "part": "snippet", "maxResults": "1", "type": "video", "key": self.bot.config["google-api-key"], - "safeSearch": safe}, json=True) + "safeSearch": safe}).json() if search_page: - if search_page.data["pageInfo"]["totalResults"] > 0: - url = URL_VIDEO % search_page.data[ - "items"][0]["id"]["videoId"] + if search_page["pageInfo"]["totalResults"] > 0: + url = URL_VIDEO % search_page["items"][0]["id"]["videoId"] else: raise utils.EventError("No videos found") else: |
