aboutsummaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/acronym.py12
-rw-r--r--modules/aliases.py120
-rw-r--r--modules/badges.py6
-rw-r--r--modules/bitcoin.py9
-rw-r--r--modules/channel_access.py3
-rw-r--r--modules/channel_op.py24
-rw-r--r--modules/commands/__init__.py280
-rw-r--r--modules/commands/outs.py100
-rw-r--r--modules/config.py10
-rw-r--r--modules/define.py16
-rw-r--r--modules/duckduckgo.py6
-rw-r--r--modules/echo.py3
-rw-r--r--modules/eval_lua.py7
-rw-r--r--modules/eval_python.py2
-rw-r--r--modules/fediverse/__init__.py2
-rw-r--r--modules/fediverse/ap_actor.py25
-rw-r--r--modules/fediverse/ap_utils.py22
-rw-r--r--modules/format_activity.py11
-rw-r--r--modules/git_webhooks/github.py55
-rw-r--r--modules/github.py59
-rw-r--r--modules/google.py20
-rw-r--r--modules/help.py16
-rw-r--r--modules/ids.py2
-rw-r--r--modules/ignore.py16
-rw-r--r--modules/imdb.py12
-rw-r--r--modules/imgur.py28
-rw-r--r--modules/ip_addresses.py22
-rw-r--r--modules/ircv3_msgid.py7
-rw-r--r--modules/ircv3_typing.py27
-rw-r--r--modules/lastfm.py15
-rw-r--r--modules/line_handler/__init__.py4
-rw-r--r--modules/line_handler/channel.py3
-rw-r--r--modules/line_handler/core.py14
-rw-r--r--modules/line_handler/user.py12
-rw-r--r--modules/location.py9
-rw-r--r--modules/markov.py2
-rw-r--r--modules/more.py23
-rw-r--r--modules/nr.py18
-rw-r--r--modules/onionoo.py6
-rw-r--r--modules/permissions/README.md38
-rw-r--r--modules/permissions/__init__.py486
-rw-r--r--modules/print_activity.py4
-rw-r--r--modules/rss.py5
-rw-r--r--modules/rust.py10
-rw-r--r--modules/scripts/__init__.py4
-rw-r--r--modules/shorturl.py6
-rw-r--r--modules/soundcloud.py6
-rw-r--r--modules/spotify.py14
-rw-r--r--modules/tell.py (renamed from modules/to.py)15
-rw-r--r--modules/thesaurus.py15
-rw-r--r--modules/title.py17
-rw-r--r--modules/trakt.py24
-rw-r--r--modules/translate.py6
-rw-r--r--modules/urbandictionary.py8
-rw-r--r--modules/weather.py25
-rw-r--r--modules/wikipedia.py17
-rw-r--r--modules/wolframalpha.py4
-rw-r--r--modules/youtube.py29
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 &lt;botnick> register &lt;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 &lt;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 (&lt;id>, 'permissions', '["*"]');
-
-(where `<id>` is the response from the `myid` command)
-
-### Authenticating
-
-To authenticate yourself as your admin user, use the following command
-
-> /msg &lt;botnick> identify &lt;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: