aboutsummaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/alias_variables.py4
-rw-r--r--modules/autojoin.py (renamed from modules/channel_save.py)14
-rw-r--r--modules/badwords.py5
-rw-r--r--modules/ban_enforce.py27
-rw-r--r--modules/channel_op.py103
-rw-r--r--modules/dnsbl/__init__.py4
-rw-r--r--modules/dnsbl/lists.py12
-rw-r--r--modules/fediverse/__init__.py2
-rw-r--r--modules/fediverse/ap_server.py4
-rw-r--r--modules/fediverse/ap_utils.py15
-rw-r--r--modules/git_webhooks/__init__.py2
-rw-r--r--modules/git_webhooks/github.py2
-rw-r--r--modules/ids.py2
-rw-r--r--modules/inactive_channels.py31
-rw-r--r--modules/ircv3_editmsg.py2
-rw-r--r--modules/ircv3_typing.py5
-rw-r--r--modules/lastfm.py15
-rw-r--r--modules/message_filter.py19
-rw-r--r--modules/quotes.py36
-rw-r--r--modules/rss.py33
-rw-r--r--modules/seen.py4
-rw-r--r--modules/shorturl.py32
-rw-r--r--modules/title.py12
-rw-r--r--modules/tweets/format.py2
-rw-r--r--modules/user_time.py2
-rw-r--r--modules/weather.py6
-rw-r--r--modules/words.py38
-rw-r--r--modules/yourls.py34
-rw-r--r--modules/youtube.py12
29 files changed, 343 insertions, 136 deletions
diff --git a/modules/alias_variables.py b/modules/alias_variables.py
index 6f2499a3..b618a7c6 100644
--- a/modules/alias_variables.py
+++ b/modules/alias_variables.py
@@ -5,7 +5,11 @@ class Module(ModuleManager.BaseModule):
@utils.hook("get.command")
@utils.kwarg("priority", EventManager.PRIORITY_HIGH)
def get_command(self, event):
+ event["kwargs"]["CTRIGGER"] = event["command_prefix"]
+
+ event["kwargs"]["BNICK"] = event["server"].nickname
event["kwargs"]["NICK"] = event["user"].nickname
+
if event["is_channel"]:
event["kwargs"]["CHAN"] = event["target"].name
random_user = random.choice(list(event["target"].users))
diff --git a/modules/channel_save.py b/modules/autojoin.py
index d5b69638..bbd08dfb 100644
--- a/modules/channel_save.py
+++ b/modules/autojoin.py
@@ -25,6 +25,9 @@ class Module(ModuleManager.BaseModule):
if channel_name in channels:
channels.remove(channel_name)
server.set_setting("autojoin", channels)
+ return True
+ else:
+ return False
@utils.hook("self.part")
def on_part(self, event):
@@ -33,3 +36,14 @@ class Module(ModuleManager.BaseModule):
@utils.hook("self.kick")
def on_kick(self, event):
self._remove_channel(event["server"], event["channel"].name)
+
+ @utils.hook("raw.received.470")
+ def on_linkchannel(self, event):
+ initial = event["line"].args[1]
+ initial_lower = event["server"].irc_lower(initial)
+ linked = event["line"].args[2]
+
+ if self._remove_channel(event["server"], initial_lower):
+ self.log.warn(f"{str(event['server'])} "
+ f"channel {initial} linked to {linked} "
+ "- removed from autojoin")
diff --git a/modules/badwords.py b/modules/badwords.py
index d23a4137..db5ed24c 100644
--- a/modules/badwords.py
+++ b/modules/badwords.py
@@ -80,9 +80,8 @@ class Module(ModuleManager.BaseModule):
ban = True
if ban:
- event["channel"].send_ban("*!%s@%s" % (
- event["user"].username,
- event["user"].realname))
+ event["channel"].send_ban(self.exports.get("ban-mask")(
+ event["server"], event["channel"], event["user"]))
if kick:
event["channel"].send_kick(event["user"].nickname,
"You said a badword!")
diff --git a/modules/ban_enforce.py b/modules/ban_enforce.py
new file mode 100644
index 00000000..4e40bb84
--- /dev/null
+++ b/modules/ban_enforce.py
@@ -0,0 +1,27 @@
+from src import ModuleManager, utils
+
+REASON = "User is banned from this channel"
+
+@utils.export("channelset", utils.BoolSetting("ban-enforce",
+ "Whether or not to parse new bans and kick who they affect"))
+class Module(ModuleManager.BaseModule):
+ @utils.hook("received.mode.channel")
+ def on_mode(self, event):
+ if event["channel"].get_setting("ban-enforce", False):
+ bans = []
+ kicks = set([])
+ for mode, arg in event["modes"]:
+ if mode[0] == "+" and mode[1] == "b":
+ bans.append(arg)
+
+ if bans:
+ umasks = {u.hostmask(): u for u in event["channel"].users}
+ for ban in bans:
+ mask = utils.irc.hostmask_parse(ban)
+ matches = list(utils.irc.hostmask_match_many(
+ umasks.keys(), mask))
+ for match in matches:
+ kicks.add(umasks[match])
+ if kicks:
+ nicks = [u.nickname for u in kicks]
+ event["channel"].send_kicks(sorted(nicks), REASON)
diff --git a/modules/channel_op.py b/modules/channel_op.py
index e7ab7bc9..d37e27b4 100644
--- a/modules/channel_op.py
+++ b/modules/channel_op.py
@@ -26,6 +26,28 @@ class TargetType(enum.Enum):
MASK = 2
ACCOUNT = 3
+def _mlock(s):
+ modes, *args = s.split(" ")
+
+ adds = ""
+ removes = ""
+ add = True
+ for c in modes:
+ if c == "+":
+ add = True
+ elif c == "-":
+ add = False
+ elif add:
+ adds += c
+ else:
+ removes += c
+
+ return (
+ "" if not adds else f"+{''.join(adds)}",
+ "" if not removes else f"-{''.join(removes)}",
+ args
+ )
+
KICK_REASON_SETTING = utils.Setting("default-kick-reason",
"Set the default kick reason", example="have a nice trip")
@@ -33,10 +55,6 @@ BAN_FORMATTING = "${n} = nick, ${u} = username, ${h} = hostname, ${a} = account"
@utils.export("channelset", utils.Setting("ban-format",
"Set ban format (%s)" % BAN_FORMATTING, example="*!${u}@${h}"))
-@utils.export("channelset", utils.Setting("ban-format-account",
- "Set ban format for users with accounts (%s)" % BAN_FORMATTING,
- example="~a:${a}"))
-
@utils.export("serverset", utils.OptionsSetting(
list(QUIET_METHODS.keys()), "quiet-method",
"Set this server's method of muting users"))
@@ -47,6 +65,10 @@ BAN_FORMATTING = "${n} = nick, ${u} = username, ${h} = hostname, ${a} = account"
@utils.export("botset", KICK_REASON_SETTING)
@utils.export("serverset", KICK_REASON_SETTING)
@utils.export("channelset", KICK_REASON_SETTING)
+
+@utils.export("channelset", utils.FunctionSetting(_mlock, "mlock",
+ "Set which modes are locked on and off for the current channel",
+ example="+mnt-z"))
class Module(ModuleManager.BaseModule):
_name = "ChanOp"
@@ -75,23 +97,6 @@ class Module(ModuleManager.BaseModule):
reason = reason or self._kick_reason(server, channel)
channel.send_kicks(nicknames, reason)
- def _format_hostmask(self, user, s):
- vars = {}
- vars["n"] = vars["nickname"] = user.nickname
- vars["u"] = vars["username"] = user.username
- vars["h"] = vars["hostname"] = user.hostname
- vars["a"] = vars["account"] = user.account or ""
- return utils.parse.format_token_replace(s, vars)
- def _get_hostmask(self, channel, user):
- 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)
-
- format = channel.get_setting("ban-format", "*!${u}@${h}")
- return self._format_hostmask(user, format)
-
-
@utils.hook("received.command.topic")
@utils.kwarg("require_mode", "o")
@utils.kwarg("require_access", "low,topic")
@@ -209,7 +214,8 @@ class Module(ModuleManager.BaseModule):
elif flag == "V" and identified:
modes.append(("v", user.nickname))
elif flag == "b":
- modes.append(("b", self._get_hostmask(channel, user)))
+ mask = self.exports.get("ban-mask")(server, channel, user)
+ modes.append(("b", mask))
kick_reason = "User is banned from this channel"
new_modes = []
@@ -394,15 +400,16 @@ class Module(ModuleManager.BaseModule):
elif spec[2][0] in ["user", "cuser"]:
users = [spec[2][1]]
elif spec[2][0] == "word":
- masks = [spec[2][1]]
+ args = [spec[2][1]]
target_type, mode, prefix = self._find_mode(type, server)
if users:
if target_type == TargetType.MASK:
- args = [self._get_hostmask(spec[0], u) for u in users]
+ mask_f = self.exports.get("ban-mask")
+ args = [mask_f(server, spec[0], u) for u in users]
elif target_type == TargetType.NICKNAME:
args = [
- u.nickname for u in users if not spec[0].has_mode(u, mode)]
+ u.nickname for u in users if not spec[0].has_umode(u, mode)]
elif target_type == TargetType.ACCOUNT:
args = [u.account for u in users if not u.account == None]
@@ -428,7 +435,9 @@ class Module(ModuleManager.BaseModule):
users = args = []
if event["spec"][1][0] == "user":
- masks = [self._get_hostmask(event["spec"][0], event["spec"][1][1])]
+ mask_f = self.exports.get("ban-mask")
+ masks = [
+ mask_f(event["server"], event["spec"][0], event["spec"][1][1])]
elif event["spec"][1][0] == "word":
masks = self._list_query_event(event["spec"][0],
event["spec"][1][1], mode, prefix)
@@ -457,7 +466,7 @@ class Module(ModuleManager.BaseModule):
_, mode, _ = self._find_mode(
event["hook"].get_kwarg("type"), event["server"])
valid_nicks = [
- u.nickname for u in users if event["spec"][0].has_mode(u, mode)]
+ u.nickname for u in users if event["spec"][0].has_umode(u, mode)]
if valid_nicks:
event["spec"][0].send_modes([(mode, a) for a in valid_nicks], False)
@@ -489,3 +498,43 @@ class Module(ModuleManager.BaseModule):
if modes:
event["spec"][0].send_modes(modes, False)
+
+ @utils.hook("received.324")
+ @utils.hook("received.mode.channel")
+ def on_modes(self, event):
+ mlock = event["channel"].get_setting("mlock", None)
+ if mlock:
+ changes_adds = ""
+ changes_removes = ""
+ changes_args = []
+ adds, removes, args = mlock
+ args = args.copy() # cached settings objects are mutable
+
+ for mode in adds[1:]:
+ if not event["channel"].has_mode(mode):
+ if (mode in event["server"].channel_list_modes or
+ mode in event["server"].channel_parametered_modes or
+ mode in event["server"].channel_setting_modes):
+ changes_adds += mode
+ changes_args.append(args.pop(0))
+ elif mode in event["server"].channel_modes:
+ changes_adds += mode
+
+ for mode in removes[1:]:
+ if event["channel"].has_mode(mode):
+ if (mode in event["server"].channel_list_modes or
+ mode in event["server"].channel_parametered_modes):
+ changes_removes += mode
+ changes_args.append(args.pop(0))
+ elif (mode in event["server"].channel_setting_modes or
+ mode in event["server"].channel_modes):
+ changes_removes += mode
+
+ out = ""
+ if changes_adds:
+ out += f"+{changes_adds}"
+ if changes_removes:
+ out += f"-{changes_removes}"
+
+ if out:
+ event["channel"].send_mode(out, changes_args)
diff --git a/modules/dnsbl/__init__.py b/modules/dnsbl/__init__.py
index 495cbd8c..2b3daf35 100644
--- a/modules/dnsbl/__init__.py
+++ b/modules/dnsbl/__init__.py
@@ -14,11 +14,11 @@ class Module(ModuleManager.BaseModule):
lists = []
for i, arg in reversed(list(enumerate(args))):
if arg[0] == "@":
- hostname = args.pop(i)
+ hostname = args.pop(i)[1:]
if hostname in default_lists:
lists.insert(0, default_lists[hostname])
else:
- lists.insert(0, lists.DNSBL(hostname))
+ lists.insert(0, _lists.DNSBL(hostname))
lists = lists or list(default_lists.values())
diff --git a/modules/dnsbl/lists.py b/modules/dnsbl/lists.py
index ce2b6404..b84628ea 100644
--- a/modules/dnsbl/lists.py
+++ b/modules/dnsbl/lists.py
@@ -6,7 +6,7 @@ class DNSBL(object):
self.hostname = hostname
def process(self, result: str):
- return "unknown"
+ return result
class ZenSpamhaus(DNSBL):
hostname = "zen.spamhaus.org"
@@ -40,10 +40,18 @@ class DroneBL(DNSBL):
elif result in ["12", "13", "15", "16"]:
return "exploits"
+class AbuseAtCBL(DNSBL):
+ hostname = "cbl.abuseat.org"
+ def process(self, result):
+ result = result.rsplit(".", 1)[1]
+ if result == "2":
+ return "abuse"
+
DEFAULT_LISTS = [
ZenSpamhaus(),
EFNetRBL(),
- DroneBL()
+ DroneBL(),
+ AbuseAtCBL()
]
def default_lists():
diff --git a/modules/fediverse/__init__.py b/modules/fediverse/__init__.py
index c1279eb7..aefd966e 100644
--- a/modules/fediverse/__init__.py
+++ b/modules/fediverse/__init__.py
@@ -95,7 +95,7 @@ class Module(ModuleManager.BaseModule):
note = note["object"]
cw, author, content, url = ap_utils.parse_note(actor, note, type)
- shorturl = self.exports.get_one("shorturl")(event["server"], url,
+ shorturl = self.exports.get("shorturl")(event["server"], url,
context=event["target"])
if cw:
diff --git a/modules/fediverse/ap_server.py b/modules/fediverse/ap_server.py
index e1b5cece..1fb98760 100644
--- a/modules/fediverse/ap_server.py
+++ b/modules/fediverse/ap_server.py
@@ -12,7 +12,7 @@ class Server(object):
self.username = username
self.instance = instance
- url_for = self.exports.get_one("url-for")
+ url_for = self.exports.get("url-for")
key_id = self._ap_keyid_url(url_for)
private_key = ap_security.PrivateKey(self.bot.config["tls-key"], key_id)
@@ -61,7 +61,7 @@ class Server(object):
def _toot(self, activity_id):
content, timestamp = self.bot.get_setting(
"ap-activity-%s" % activity_id)
- url_for = self.exports.get_one("url-for")
+ url_for = self.exports.get("url-for")
self_id = self._ap_self_url(url_for)
activity_url = self._ap_activity_url(url_for, activity_id)
diff --git a/modules/fediverse/ap_utils.py b/modules/fediverse/ap_utils.py
index 0d44d523..9174648c 100644
--- a/modules/fediverse/ap_utils.py
+++ b/modules/fediverse/ap_utils.py
@@ -39,15 +39,17 @@ class FindActorException(Exception):
pass
def find_actor(username, instance):
- hostmeta = HOSTMETA_TEMPLATE % instance
- hostmeta_request = utils.http.Request(HOSTMETA_TEMPLATE % instance)
+ hostmeta_url = HOSTMETA_TEMPLATE % instance
+ hostmeta_request = utils.http.Request(hostmeta_url)
try:
hostmeta = utils.http.request(hostmeta_request)
except:
- raise FindActorException("Failed to get host-meta for %s" % instance)
+ # failed to GET hostmeta; this is an optional step for servers that do
+ # not host their webfinger at the usual URL (see WEBFINGER_TEMPLATE)
+ hostmeta = None
webfinger_url = None
- if hostmeta.code == 200:
+ if hostmeta and hostmeta.code == 200:
for item in hostmeta.soup().find_all("link"):
if item["rel"] and item["rel"][0] == "lrdd":
webfinger_url = item["template"]
@@ -60,8 +62,9 @@ def find_actor(username, instance):
try:
webfinger = activity_request(webfinger_url, type=JRD_TYPE)
- except:
- raise FindActorException("Failed to get webfinger for %s" % instance)
+ except Exception as e:
+ raise FindActorException("Failed to get webfinger for %s: %s" %
+ (instance, str(e)))
actor_url = None
if webfinger.code == 200:
diff --git a/modules/git_webhooks/__init__.py b/modules/git_webhooks/__init__.py
index b87f0665..44d9b5dc 100644
--- a/modules/git_webhooks/__init__.py
+++ b/modules/git_webhooks/__init__.py
@@ -138,7 +138,7 @@ class Module(ModuleManager.BaseModule):
if url:
if channel.get_setting("git-shorten-urls", False):
- url = self.exports.get_one("shorturl")(server, url,
+ url = self.exports.get("shorturl")(server, url,
context=channel) or url
output = "%s - %s" % (output, url)
diff --git a/modules/git_webhooks/github.py b/modules/git_webhooks/github.py
index 21b672ce..8bf61114 100644
--- a/modules/git_webhooks/github.py
+++ b/modules/git_webhooks/github.py
@@ -441,7 +441,7 @@ class GitHub(object):
url = ""
if data["check_run"]["details_url"]:
url = data["check_run"]["details_url"]
- url = " - %s" % self.exports.get_one("shorturl-any")(url)
+ url = " - %s" % self.exports.get("shorturl-any")(url)
duration = ""
if data["check_run"]["completed_at"]:
diff --git a/modules/ids.py b/modules/ids.py
index 1738c549..2e056892 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,
- self.exports.get_one("account-name")(event["user"])))
+ self.exports.get("account-name")(event["user"])))
@utils.hook("received.command.channelid", channel_only=True)
def channel_id(self, event):
diff --git a/modules/inactive_channels.py b/modules/inactive_channels.py
index 5959d432..78f724c7 100644
--- a/modules/inactive_channels.py
+++ b/modules/inactive_channels.py
@@ -1,21 +1,23 @@
import datetime
from src import ModuleManager, utils
-PRUNE_TIMEDELTA = datetime.timedelta(weeks=2)
+PRUNE_TIMEDELTA = datetime.timedelta(weeks=4)
-SETTING_NAME = "inactive-channels"
-SETTING = utils.BoolSetting(SETTING_NAME,
- "Whether or not to leave inactive channels after 2 weeks")
+SETTING_NAME = "inactive-prune"
+SETTING = utils.IntRangeSetting(0, None, SETTING_NAME,
+ "Amount of days of inactivity before we leave a channel")
-MODE_SETTING_NAME = "inactive-channel-modes"
+MODE_SETTING_NAME = "inactive-prune-modes"
MODE_SETTING = utils.BoolSetting(MODE_SETTING_NAME,
"Whether or not we will leave inactive channels that we have a mode in")
@utils.export("botset", SETTING)
@utils.export("serverset", SETTING)
-@utils.export("channelset", SETTING)
@utils.export("serverset", MODE_SETTING)
@utils.export("channelset", MODE_SETTING)
+
+@utils.export("channelset", utils.BoolSetting(SETTING_NAME,
+ "Whether or not to leave this channel when it is inactive"))
class Module(ModuleManager.BaseModule):
def _get_timestamp(self, channel):
return channel.get_setting("last-message", None)
@@ -35,29 +37,27 @@ class Module(ModuleManager.BaseModule):
def hourly(self, event):
parts = []
now = utils.datetime.utcnow()
- botwide_setting = self.bot.get_setting(SETTING_NAME, False)
+ botwide_days = self.bot.get_setting(SETTING_NAME, None)
botwide_mode_setting = self.bot.get_setting(MODE_SETTING_NAME, False)
for server in self.bot.servers.values():
- serverwide_setting = server.get_setting(
- SETTING_NAME, botwide_setting)
- if not serverwide_setting:
+ serverwide_days = server.get_setting(SETTING_NAME, botwide_days)
+ if serverwide_days == None:
continue
mode_setting = server.get_setting(
MODE_SETTING_NAME, botwide_mode_setting)
-
our_user = server.get_user(server.nickname)
+
for channel in server.channels:
- if not channel.get_setting(SETTING_NAME, serverwide_setting):
- continue
- if not mode_setting and channel.get_user_modes(our_user):
+ if (not channel.get_setting(SETTING_NAME, True) or
+ not mode_setting and channel.get_user_modes(our_user)):
continue
timestamp = self._get_timestamp(channel)
if timestamp:
dt = utils.datetime.parse.iso8601(timestamp)
- if (now-dt) >= PRUNE_TIMEDELTA:
+ if (now-dt).days >= serverwide_days:
parts.append([server, channel])
for server, channel in parts:
@@ -66,6 +66,7 @@ class Module(ModuleManager.BaseModule):
channel.send_part("Channel inactive")
self._del_timestamp(channel)
+ @utils.hook("send.message.channel")
@utils.hook("received.message.channel")
def channel_message(self, event):
self._set_timestamp(event["channel"])
diff --git a/modules/ircv3_editmsg.py b/modules/ircv3_editmsg.py
index a7b5b2b0..15163053 100644
--- a/modules/ircv3_editmsg.py
+++ b/modules/ircv3_editmsg.py
@@ -18,7 +18,7 @@ class Module(ModuleManager.BaseModule):
timestamp, line.message)
line = "- %s" % minimal
- self.exports.get_one("format")("delete", event["server"], line,
+ self.exports.get("format")("delete", event["server"], line,
event["target_str"], minimal=minimal, channel=channel,
user=event["user"])
diff --git a/modules/ircv3_typing.py b/modules/ircv3_typing.py
index 3c8c9c6d..05de5162 100644
--- a/modules/ircv3_typing.py
+++ b/modules/ircv3_typing.py
@@ -5,7 +5,10 @@ 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})
+ tags={
+ "+typing": state,
+ "+draft/typing": state
+ })
def _has_tags(self, server):
return server.has_capability(CAP)
diff --git a/modules/lastfm.py b/modules/lastfm.py
index 14e9128a..6931e0c0 100644
--- a/modules/lastfm.py
+++ b/modules/lastfm.py
@@ -7,8 +7,13 @@ from src import ModuleManager, utils
URL_SCROBBLER = "http://ws.audioscrobbler.com/2.0/"
+SETTING_YT = utils.BoolSetting("lastfm-youtube",
+ "Whether or not to search last.fm now-playing results on youtube")
+
@utils.export("set", utils.Setting("lastfm", "Set last.fm username",
example="jesopo"))
+@utils.export("botset", SETTING_YT)
+@utils.export("serverset", SETTING_YT)
class Module(ModuleManager.BaseModule):
_name = "last.fm"
@@ -59,11 +64,13 @@ class Module(ModuleManager.BaseModule):
time_language = "is listening to" if np else "last listened to"
- yt_url = self.exports.get_one("search-youtube")(
- "%s - %s" % (artist, track_name))
yt_url_str = ""
- if yt_url:
- yt_url_str = " - %s" % yt_url
+ if event["server"].get_setting("lastfm-youtube",
+ self.bot.get_setting("lastfm-youtube", False)):
+ yt_url = self.exports.get("search-youtube")(
+ "%s - %s" % (artist, track_name))
+ if yt_url:
+ yt_url_str = " - %s" % yt_url
info_page = utils.http.request(URL_SCROBBLER, get_params={
"method": "track.getInfo", "artist": artist,
diff --git a/modules/message_filter.py b/modules/message_filter.py
index a6191a66..f773ad36 100644
--- a/modules/message_filter.py
+++ b/modules/message_filter.py
@@ -31,20 +31,23 @@ class Module(ModuleManager.BaseModule):
filters = self._get_filters(event["server"], target)
for filter in filters:
sed = utils.parse.sed.parse(filter)
- type, out = utils.parse.sed.sed(sed, message)
- if type == "m" and out:
- self.log.info("Message matched filter, dropping: %s"
- % event["line"].format())
- event["line"].invalidate()
- return
- elif type == "s":
+ if sed.type == "m":
+ out = utils.parse.sed.sed(sed, message_plain)
+ if out:
+ self.log.info("Message matched filter, dropping: %s"
+ % event["line"].format())
+ event["line"].invalidate()
+ return
+ elif sed.type == "s":
+ out = utils.parse.sed.sed(sed, message)
message = out
if not message == original_message:
event["line"].args[1] = message
- @utils.hook("received.command.cfilter", channel_only=True)
+ @utils.hook("received.command.cfilter", channel_only=True,
+ require_access="high,filter", require_mode="o")
@utils.hook("received.command.filter")
@utils.hook("received.command.bfilter")
@utils.kwarg("help", "Add a message filter for the current channel")
diff --git a/modules/quotes.py b/modules/quotes.py
index a843bdfb..e323cc9b 100644
--- a/modules/quotes.py
+++ b/modules/quotes.py
@@ -63,22 +63,30 @@ class Module(ModuleManager.BaseModule):
category)
found_target = None
+ found_quote = None
if not remove_quote == None:
- remove_quote_lower = remove_quote.lower()
+ remove_quote_lower = remove_quote.lower().strip()
for nickname, time_added, quote, target in quotes[:]:
- if quote.lower() == remove_quote_lower:
- quotes.remove([nickname, time_added, quote])
+ if remove_quote_lower in quote.lower().strip():
found_target = target
+ found_quote = [nickname, time_added, quote]
message = "Removed quote from '%s'"
break
else:
if quotes:
- quote = quotes.pop(-1)
- found_target = quote[-1]
+ nickname, time_added, quote, target = quotes.pop(-1)
+
+ found_target = target
+ found_quote = [nickname, time_added, quote]
message = "Removed last '%s' quote"
if not message == None:
- self._set_quotes(found_target, category, quotes)
+ target_quotes = self._get_quotes(found_target, category)
+ target_quotes.remove(found_quote)
+ self._set_quotes(found_target, category, target_quotes)
+
+ _, _, quote = found_quote
+ message = f"{message} ({quote})"
event["stdout"].write(message % category)
else:
event["stderr"].write("Quote not found")
@@ -89,6 +97,10 @@ class Module(ModuleManager.BaseModule):
@utils.kwarg("usage", "<category> [= <search>]")
def quote(self, event):
category, search = self.category_and_quote(event["args"])
+ if event["server"].has_user(category):
+ category = event["server"].get_user_nickname(
+ event["server"].get_user(category).get_id())
+
quotes = event["server"].get_setting("quotes-%s" % category, [])
if event["is_channel"]:
quotes += self._get_quotes(event["target"], category)
@@ -122,28 +134,26 @@ class Module(ModuleManager.BaseModule):
raise utils.EventError(
"Please provide a number between 1 and 3")
- target = event["args_split"][0]
- lines = event["target"].buffer.find_many_from(target, line_count)
+ target_user = event["args_split"][0]
+ lines = event["target"].buffer.find_many_from(target_user, line_count)
if lines:
lines.reverse()
target = event["server"]
if event["target"].get_setting("channel-quotes", False):
target = event["target"]
- quotes = self._get_quotes(target, target)
-
lines_str = []
for line in lines:
lines_str.append(line.format())
text = " ".join(lines_str)
- quotes.append([event["user"].name, int(time.time()), text])
-
quote_category = line.sender
if event["server"].has_user(quote_category):
- account = event["server"].get_user_nickname(
+ quote_category = event["server"].get_user_nickname(
event["server"].get_user(quote_category).get_id())
+ quotes = self._get_quotes(target, quote_category)
+ quotes.append([event["user"].name, int(time.time()), text])
self._set_quotes(target, quote_category, quotes)
event["stdout"].write("Quote added")
diff --git a/modules/rss.py b/modules/rss.py
index 07916861..9ba23db4 100644
--- a/modules/rss.py
+++ b/modules/rss.py
@@ -7,10 +7,15 @@ import feedparser
RSS_INTERVAL = 60 # 1 minute
+SETTING_BIND = utils.Setting("rss-bindhost",
+ "Which local address to bind to for RSS requests", example="127.0.0.1")
+
@utils.export("botset", utils.IntSetting("rss-interval",
"Interval (in seconds) between RSS polls", example="120"))
@utils.export("channelset", utils.BoolSetting("rss-shorten",
"Whether or not to shorten RSS urls"))
+@utils.export("serverset", SETTING_BIND)
+@utils.export("channelset", SETTING_BIND)
class Module(ModuleManager.BaseModule):
_name = "RSS"
def on_load(self):
@@ -27,7 +32,7 @@ class Module(ModuleManager.BaseModule):
link = entry.get("link", None)
if shorten:
try:
- link = self.exports.get_one("shorturl")(server, link)
+ link = self.exports.get("shorturl")(server, link)
except:
pass
link = " - %s" % link if link else ""
@@ -49,26 +54,36 @@ class Module(ModuleManager.BaseModule):
if server and channel_name in server.channels:
channel = server.channels.get(channel_name)
for url in urls:
- if not url in hooks:
- hooks[url] = []
- hooks[url].append((server, channel))
+ bindhost = channel.get_setting("rss-bindhost",
+ server.get_setting("rss-bindhost", None))
+
+ if url.startswith("www."):
+ url = url.replace("www.", "", 1)
+
+ key = (url, bindhost)
+ if not key in hooks:
+ hooks[key] = []
+
+ hooks[key].append((server, channel))
if not hooks:
return
requests = []
- for url in hooks.keys():
- requests.append(utils.http.Request(url, id=url))
+ for url, bindhost in hooks.keys():
+ requests.append(utils.http.Request(url, id=f"{url} {bindhost}",
+ bindhost=bindhost))
pages = utils.http.request_many(requests)
- for url, channels in hooks.items():
- if not url in pages:
+ for (url, bindhost), channels in hooks.items():
+ key = f"{url} {bindhost}"
+ if not key in pages:
# async url get failed
continue
try:
- data = pages[url].decode()
+ data = pages[key].decode()
except Exception as e:
self.log.error("Failed to decode rss URL %s", [url],
exc_info=True)
diff --git a/modules/seen.py b/modules/seen.py
index b19dce61..7eb037d8 100644
--- a/modules/seen.py
+++ b/modules/seen.py
@@ -36,7 +36,7 @@ class Module(ModuleManager.BaseModule):
since = utils.datetime.format.to_pretty_since(
time.time()-seen_seconds, max_units=2)
event["stdout"].write("%s was last seen %s ago%s" % (
- event["args_split"][0], since, seen_info or ""))
+ user.nickname, since, seen_info or ""))
else:
event["stderr"].write("I have never seen %s before." % (
- event["args_split"][0]))
+ user.nickname))
diff --git a/modules/shorturl.py b/modules/shorturl.py
index afc1b50c..345d2183 100644
--- a/modules/shorturl.py
+++ b/modules/shorturl.py
@@ -16,36 +16,48 @@ class Module(ModuleManager.BaseModule):
self.exports.add("botset", setting)
def _shorturl_options_factory(self):
- shorteners = self.exports.find("shorturl-s-")
- return [s.replace("shorturl-s-", "", 1) for s in shorteners]
+ shorteners = set(self.exports.find("shorturl-s-"))
+ shorteners.update(self.exports.find("shorturl-x-"))
+ return sorted(s.split("-", 2)[-1] for s in shorteners)
def _get_shortener(self, name):
- return self.exports.get_one("shorturl-s-%s" % name, None)
- def _call_shortener(self, shortener_name, url):
- shortener = self._get_shortener(shortener_name)
+ extended = self.exports.get("shorturl-x-%s" % name, None)
+ if not extended == None:
+ return True, extended
+ return False, self.exports.get("shorturl-s-%s" % name, None)
+ def _call_shortener(self, server, context, shortener_name, url):
+ extended, shortener = self._get_shortener(shortener_name)
if shortener == None:
return None
- short_url = shortener(url)
+
+ if extended:
+ short_url = shortener(server, context, url)
+ else:
+ short_url = shortener(url)
+
if short_url == None:
return None
return short_url
@utils.export("shorturl-any")
def _shorturl_any(self, url):
- return self._call_shortener("bitly", url) or url
+ return self._call_shortener(server, None, "bitly", url) or url
@utils.export("shorturl")
def _shorturl(self, server, url, context=None):
shortener_name = None
if context:
shortener_name = context.get_setting("url-shortener",
- server.get_setting("url-shortener", "bitly"))
+ server.get_setting("url-shortener",
+ self.bot.get_setting("url-shortener", "bitly")))
else:
- shortener_name = server.get_setting("url-shortener", "bitly")
+ shortener_name = server.get_setting("url-shortener",
+ self.bot.get_setting("url-shortener", "bitly"))
if shortener_name == None:
return url
- return self._call_shortener(shortener_name, url) or url
+ return self._call_shortener(
+ server, context, shortener_name, url) or url
@utils.export("shorturl-s-bitly")
def _bitly(self, url):
diff --git a/modules/title.py b/modules/title.py
index df05546e..eba18566 100644
--- a/modules/title.py
+++ b/modules/title.py
@@ -27,7 +27,7 @@ class Module(ModuleManager.BaseModule):
for title_word in RE_WORDSPLIT.split(title):
if len(title_word) > 1 or title_word.isalpha():
title_word = title_word.lower()
- title_words.append(title_word.strip("'\"<>()"))
+ title_words.append(title_word.strip("'\"<>(),:"))
if title_words:
present = 0
@@ -45,13 +45,9 @@ class Module(ModuleManager.BaseModule):
if not urllib.parse.urlparse(url).scheme:
url = "http://%s" % url
- hostname = urllib.parse.urlparse(url).hostname
- if not utils.http.host_permitted(hostname):
- self.log.warn("Attempted to get forbidden host: %s", [url])
- return -1, None
-
+ request = utils.http.Request(url, check_hostname=True)
try:
- page = utils.http.request(url)
+ page = utils.http.request(request)
except Exception as e:
self.log.error("failed to get URL title for %s: %s", [url, str(e)])
return -1, None
@@ -71,7 +67,7 @@ class Module(ModuleManager.BaseModule):
return -2, title
if channel.get_setting("title-shorten", False):
- short_url = self.exports.get_one("shorturl")(server, url,
+ short_url = self.exports.get("shorturl")(server, url,
context=channel)
return page.code, "%s - %s" % (title, short_url)
return page.code, title
diff --git a/modules/tweets/format.py b/modules/tweets/format.py
index 540e7638..9648dc51 100644
--- a/modules/tweets/format.py
+++ b/modules/tweets/format.py
@@ -23,7 +23,7 @@ def _tweet(exports, server, tweet, from_url):
short_url = ""
if not from_url:
- short_url = exports.get_one("shorturl")(server, tweet_link)
+ short_url = exports.get("shorturl")(server, tweet_link)
short_url = " - %s" % short_url if short_url else ""
created_at = _timestamp(tweet.created_at)
diff --git a/modules/user_time.py b/modules/user_time.py
index 762a9440..8d0701e6 100644
--- a/modules/user_time.py
+++ b/modules/user_time.py
@@ -34,7 +34,7 @@ class Module(ModuleManager.BaseModule):
location["timezone"])
if query:
- location = self.exports.get_one("get-location")(query)
+ location = self.exports.get("get-location")(query)
if location:
return (LocationType.NAME, location["name"],
location["timezone"])
diff --git a/modules/weather.py b/modules/weather.py
index 1677f990..bd6b5e8b 100644
--- a/modules/weather.py
+++ b/modules/weather.py
@@ -45,7 +45,7 @@ class Module(ModuleManager.BaseModule):
if location == None and query:
- location_info = self.exports.get_one("get-location")(query)
+ location_info = self.exports.get("get-location")(query)
if not location_info == None:
location = [location_info["lat"], location_info["lon"],
location_info.get("name", None)]
@@ -74,8 +74,8 @@ class Module(ModuleManager.BaseModule):
# wind speed is in metres per second - 3.6* for KMh
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)
+ wind_speed_k = "%skm/h" % round(wind_speed, 1)
+ wind_speed_m = "%smi/h" % round(0.6214*wind_speed, 1)
if not nickname == None:
location_str = "(%s) %s" % (nickname, location_str)
diff --git a/modules/words.py b/modules/words.py
index 88dbc6cc..59b9ef92 100644
--- a/modules/words.py
+++ b/modules/words.py
@@ -66,7 +66,7 @@ class Module(ModuleManager.BaseModule):
if event["channel"].get_setting("word-tracking-registered",
event["server"].get_setting("word-tracking-registered", False)):
- if not self.exports.get_one("is-identified")(event["user"]):
+ if not self.exports.get("is-identified")(event["user"]):
return
if user.get_setting("first-words", None) == None:
@@ -105,19 +105,24 @@ class Module(ModuleManager.BaseModule):
self._channel_message(event["server"].get_user(
event["server"].nickname), event)
- @utils.hook("received.command.words", channel_only=True)
+ @utils.hook("received.command.words")
@utils.kwarg("help",
"See how many words you or the given nickname have used")
- @utils.spec("!-channelonly ?<nickname>ouser")
+ @utils.spec("?<nickname>ouser")
def words(self, event):
- target_user = event["spec"][0] or event["user"]
+ if event["spec"][0] and event["is_channel"]:
+ target_user = event["spec"][0]
+ else:
+ target_user = event["user"]
- words = dict(self._user_all(target_user))
- this_channel = words.get(event["target"].id, 0)
+ word_items = self._user_all(target_user)
- total = 0
- for channel_id in words:
- total += words[channel_id]
+ words = {}
+ for channel_id, count in word_items:
+ if not channel_id in words:
+ words[channel_id] = 0
+ words[channel_id] += count
+ total = sum(words.values())
since = ""
first_words = target_user.get_setting("first-words", None)
@@ -125,9 +130,14 @@ class Module(ModuleManager.BaseModule):
since = " since %s" % utils.datetime.format.date_human(
utils.datetime.timestamp(first_words))
- event["stdout"].write("%s has used %d words (%d in %s)%s" % (
- target_user.nickname, total, this_channel, event["target"].name,
- since))
+ if event["is_channel"]:
+ this_channel = words.get(event["target"].id, 0)
+ event["stdout"].write("%s has used %d words (%d in %s)%s" % (
+ target_user.nickname, total, this_channel, event["target"].name,
+ since))
+ else:
+ event["stdout"].write("%s has used %d words%s" % (
+ target_user.nickname, total, since))
@utils.hook("received.command.trackword")
@utils.kwarg("help", "Start tracking a word")
@@ -193,7 +203,9 @@ class Module(ModuleManager.BaseModule):
user_words = {}
for user_id, word_count in words:
_, nickname = self.bot.database.users.by_id(user_id)
- user_words[nickname] = word_count
+ if not nickname in user_words:
+ user_words[nickname] = 0
+ user_words[nickname] += word_count
top_10 = utils.top_10(user_words,
convert_key=lambda nickname: self._get_nickname(
diff --git a/modules/yourls.py b/modules/yourls.py
new file mode 100644
index 00000000..3d0fa6e1
--- /dev/null
+++ b/modules/yourls.py
@@ -0,0 +1,34 @@
+import urllib.parse
+from src import ModuleManager, utils
+
+def _parse(s):
+ parsed = urllib.parse.urlparse(s)
+ return urllib.parse.urljoin(s, parsed.path), parsed.query
+
+SETTING = utils.FunctionSetting(_parse, "yourls",
+ "Set YOURLS server (and token) to use for URL shortening",
+ example="https://bitbot.dev/yourls-api.php?1002a612b4",
+ format=utils.sensitive_format)
+
+@utils.export("botset", SETTING)
+@utils.export("serverset", SETTING)
+@utils.export("channelset", SETTING)
+class Module(ModuleManager.BaseModule):
+ @utils.export("shorturl-x-yourls")
+ def _shorturl(self, server, context, url):
+ setting = server.get_setting("yourls",
+ self.bot.get_setting("yourls", None))
+ if context:
+ setting = context.get_setting("yourls", setting)
+
+ if not setting == None:
+ shortener_url, token = setting
+
+ page = utils.http.request(shortener_url, get_params={
+ "signature": token,
+ "action": "shorturl",
+ "url": url,
+ "format": "json"}).json()
+ if page:
+ return page["shorturl"]
+ return None
diff --git a/modules/youtube.py b/modules/youtube.py
index f5710199..7699fdd6 100644
--- a/modules/youtube.py
+++ b/modules/youtube.py
@@ -24,6 +24,8 @@ ARROW_DOWN = "↓"
"Turn safe search off/on"))
class Module(ModuleManager.BaseModule):
def get_video_page(self, video_id):
+ self.log.debug("youtube API request: "
+ "videos.list [contentDetails,snippet,statistics]")
return utils.http.request(URL_YOUTUBEVIDEO, get_params={
"part": "contentDetails,snippet,statistics",
"id": video_id, "key": self.bot.config["google-api-key"]}).json()
@@ -76,7 +78,10 @@ class Module(ModuleManager.BaseModule):
return None
def get_playlist_page(self, playlist_id):
- return utils.http.request(URL_YOUTUBEPLAYLIST, get_params={
+ self.log.debug("youtube API request: "
+ "playlists.list [contentDetails,snippet]")
+
+ return utils.http.request(URL_YOUTUBEPLAYLIST, get_params={
"part": "contentDetails,snippet", "id": playlist_id,
"key": self.bot.config["google-api-key"]}).json()
def playlist_details(self, playlist_id):
@@ -109,6 +114,8 @@ class Module(ModuleManager.BaseModule):
def _search_youtube(self, query):
video_id = ""
+ self.log.debug("youtube API request: search.list (A) [snippet]")
+
search_page = utils.http.request(URL_YOUTUBESEARCH,
get_params={"q": query, "part": "snippet",
"maxResults": "1", "type": "video",
@@ -144,6 +151,9 @@ class Module(ModuleManager.BaseModule):
if not url:
safe_setting = event["target"].get_setting("youtube-safesearch", True)
safe = "moderate" if safe_setting else "none"
+
+ self.log.debug("youtube API request: search.list (B) [snippet]")
+
search_page = utils.http.request(URL_YOUTUBESEARCH,
get_params={"q": search, "part": "snippet", "maxResults": "1",
"type": "video", "key": self.bot.config["google-api-key"],