aboutsummaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/admin.py3
-rw-r--r--modules/ctcp.py8
-rw-r--r--modules/github/__init__.py3
-rw-r--r--modules/ip_addresses.py2
-rw-r--r--modules/ircv3_chathistory.py29
-rw-r--r--modules/ircv3_labeled_responses.py49
-rw-r--r--modules/ircv3_message_tracking.py (renamed from modules/message_tracking.py)0
-rw-r--r--modules/ircv3_metadata.py (renamed from modules/metadata.py)3
-rw-r--r--modules/ircv3_msgid.py21
-rw-r--r--modules/ircv3_resume.py (renamed from modules/resume.py)10
-rw-r--r--modules/ircv3_sasl/README.md (renamed from modules/sasl/README.md)0
-rw-r--r--modules/ircv3_sasl/__init__.py (renamed from modules/sasl/__init__.py)0
-rw-r--r--modules/ircv3_sasl/scram.py (renamed from modules/sasl/scram.py)0
-rw-r--r--modules/ircv3_server_time.py17
-rw-r--r--modules/ircv3_sts.py (renamed from modules/sts.py)0
-rw-r--r--modules/lastfm.py22
-rw-r--r--modules/line_handler/__init__.py58
-rw-r--r--modules/line_handler/channel.py16
-rw-r--r--modules/line_handler/core.py9
-rw-r--r--modules/line_handler/ircv3.py2
-rw-r--r--modules/line_handler/message.py200
-rw-r--r--modules/line_handler/user.py18
-rw-r--r--modules/modules.py28
-rw-r--r--modules/permissions/__init__.py10
-rw-r--r--modules/signals.py19
-rw-r--r--modules/tweets.py122
-rw-r--r--modules/tweets/__init__.py165
-rw-r--r--modules/tweets/format.py35
28 files changed, 503 insertions, 346 deletions
diff --git a/modules/admin.py b/modules/admin.py
index 37d61384..765758ca 100644
--- a/modules/admin.py
+++ b/modules/admin.py
@@ -81,10 +81,11 @@ class Module(ModuleManager.BaseModule):
id = self.bot.database.servers.get_by_alias(alias)
if id == None:
raise utils.EventError("Unknown server alias")
+
server = self.bot.get_server_by_id(id)
server.disconnect()
self.bot.disconnect(server)
- event["stdout"].write("Disconnected from %s" % alias)
+ event["stdout"].write("Disconnected from %s" % str(server))
@utils.hook("received.command.shutdown")
def shutdown(self, event):
diff --git a/modules/ctcp.py b/modules/ctcp.py
index 6c973356..58f8d7e5 100644
--- a/modules/ctcp.py
+++ b/modules/ctcp.py
@@ -8,23 +8,23 @@ from src import IRCBot, ModuleManager, utils
"help": "Set whether I respond to CTCPs on this server",
"validate": utils.bool_or_none, "example": "on"})
class Module(ModuleManager.BaseModule):
- @utils.hook("received.ctcp.version.private")
+ @utils.hook("received.ctcp.request.version")
def ctcp_version(self, event):
default = "BitBot %s (%s)" % (IRCBot.VERSION, IRCBot.SOURCE)
event["user"].send_ctcp_response("VERSION",
self.bot.config.get("ctcp-version", default))
- @utils.hook("received.ctcp.source.private")
+ @utils.hook("received.ctcp.request.source")
def ctcp_source(self, event):
event["user"].send_ctcp_response("SOURCE",
self.bot.config.get("ctcp-source", IRCBot.SOURCE))
- @utils.hook("received.ctcp.ping.private")
+ @utils.hook("received.ctcp.request.ping")
def ctcp_ping(self, event):
event["user"].send_ctcp_response("PING", event["message"])
- @utils.hook("received.ctcp.time.private")
+ @utils.hook("received.ctcp.request.time")
def ctcp_time(self, event):
event["user"].send_ctcp_response("TIME",
datetime.datetime.now().strftime("%c"))
diff --git a/modules/github/__init__.py b/modules/github/__init__.py
index b2a382b3..63d774a8 100644
--- a/modules/github/__init__.py
+++ b/modules/github/__init__.py
@@ -485,7 +485,8 @@ class Module(ModuleManager.BaseModule):
continue
regex = re.compile(r"(.)\b(%s)(%s)" % (
- user.nickname[0], user.nickname[1:]), re.I)
+ re.escape(user.nickname[0]), re.escape(user.nickname[1:])),
+ re.I)
s = regex.sub("\\1\\2\u200c\\3", s)
return s
diff --git a/modules/ip_addresses.py b/modules/ip_addresses.py
index 07f067fb..88bd9a56 100644
--- a/modules/ip_addresses.py
+++ b/modules/ip_addresses.py
@@ -6,7 +6,7 @@ from src import ModuleManager, utils
URL_GEOIP = "http://ip-api.com/json/%s"
REGEX_IPv6 = r"(?:(?:[a-f0-9]{1,4}:){2,}|[a-f0-9:]*::)[a-f0-9:]*"
REGEX_IPv4 = r"(?:\d{1,3}\.){3}\d{1,3}"
-REGEX_IP = re.compile("%s|%s" % (REGEX_IPv4, REGEX_IPv6), re.I)
+REGEX_IP = re.compile("(%s)|(%s)" % (REGEX_IPv4, REGEX_IPv6), re.I)
class Module(ModuleManager.BaseModule):
@utils.hook("received.command.dns", min_args=1)
diff --git a/modules/ircv3_chathistory.py b/modules/ircv3_chathistory.py
new file mode 100644
index 00000000..1607263c
--- /dev/null
+++ b/modules/ircv3_chathistory.py
@@ -0,0 +1,29 @@
+#--depends-on ircv3_msgid
+
+from src import ModuleManager, utils
+
+TAG = utils.irc.MessageTag("msgid", "draft/msgid")
+
+class Module(ModuleManager.BaseModule):
+ @utils.hook("received.batch.end")
+ def batch_end(self, event):
+ if event["batch"].type == "chathistory":
+ target_name = event["batch"].args[0]
+ if target_name in event["server"].channels:
+ target = event["server"].channels.get(target_name)
+ else:
+ target = event["server"].get_user(target_name)
+
+ last_msgid = target.get_setting("last-msgid", None)
+ if not last_msgid == None:
+ lines = event["batch"].get_lines()
+ stop_index = -1
+
+ for i, line in enumerate(lines):
+ msgid = TAG.get_value(line.tags)
+ if msgid == last_msgid:
+ stop_index = i
+ break
+
+ if not stop_index == -1:
+ return lines[stop_index+1:]
diff --git a/modules/ircv3_labeled_responses.py b/modules/ircv3_labeled_responses.py
new file mode 100644
index 00000000..eee53c58
--- /dev/null
+++ b/modules/ircv3_labeled_responses.py
@@ -0,0 +1,49 @@
+import uuid
+from src import ModuleManager, utils
+
+CAP = utils.irc.Capability(None, "draft/labeled-response-0.2")
+TAG = utils.irc.MessageTag(None, "draft/label")
+
+CAP_TO_TAG = {
+ "draft/labeled-response-0.2": "draft/label"
+}
+
+class Module(ModuleManager.BaseModule):
+ @utils.hook("new.server")
+ def new_server(self, event):
+ event["server"]._label_cache = {}
+
+ @utils.hook("received.cap.ls")
+ @utils.hook("received.cap.new")
+ def on_cap(self, event):
+ if CAP.available(event["capabilities"]):
+ return CAP.copy()
+
+ @utils.hook("preprocess.send")
+ def raw_send(self, event):
+ available_cap = event["server"].available_capability(CAP)
+
+ if available_cap:
+ label = TAG.get_value(event["line"].tags)
+ if label == None:
+ tag_key = CAP_TO_TAG[available_cap]
+ label = str(uuid.uuid4())
+ event["line"].tags[tag_key] = label
+
+ event["server"]._label_cache[label] = event["line"]
+
+ @utils.hook("raw.received")
+ def raw_recv(self, event):
+ if not event["line"].command == "BATCH":
+ label = TAG.get_value(event["line"].tags)
+ if not label == None:
+ self._recv(event["server"], label, event["line"])
+
+ @utils.hook("received.batch.end")
+ def batch_end(self, event):
+ if TAG.match(event["batch"].type):
+ self._recv(event["server"], event["batch"].identifier, None)
+
+ def _recv(self, server, label, line):
+ cached_line = server._label_cache.pop(label)
+ # do something with the line!
diff --git a/modules/message_tracking.py b/modules/ircv3_message_tracking.py
index 3f4ad88c..3f4ad88c 100644
--- a/modules/message_tracking.py
+++ b/modules/ircv3_message_tracking.py
diff --git a/modules/metadata.py b/modules/ircv3_metadata.py
index 03fcc45a..55193dce 100644
--- a/modules/metadata.py
+++ b/modules/ircv3_metadata.py
@@ -13,4 +13,5 @@ class Module(ModuleManager.BaseModule):
def _ack(self, server):
url = self.bot.get_setting("bot-url", IRCBot.SOURCE)
- server.send_raw("METADATA * SET bot-url :%s" % url)
+ server.send_raw("METADATA * SET bot BitBot")
+ server.send_raw("METADATA * SET homepage :%s" % url)
diff --git a/modules/ircv3_msgid.py b/modules/ircv3_msgid.py
new file mode 100644
index 00000000..520198d6
--- /dev/null
+++ b/modules/ircv3_msgid.py
@@ -0,0 +1,21 @@
+from src import ModuleManager, utils
+
+TAG = utils.irc.MessageTag("msgid", "draft/msgid")
+
+class Module(ModuleManager.BaseModule):
+ def _on_channel(self, channel, tags):
+ msgid = TAG.get_value(tags)
+ if not msgid == None:
+ channel.set_setting("last-msgid", msgid)
+
+ @utils.hook("received.message.channel")
+ @utils.hook("received.notice.channel")
+ @utils.hook("received.tagmsg.channel")
+ def on_channel(self, event):
+ self._on_channel(event["channel"], event["tags"])
+
+ @utils.hook("received.ctcp.request")
+ @utils.hook("received.ctcp.response")
+ def ctcp(self, event):
+ if event["is_channel"]:
+ self._on_channel(event["target"], event["tags"])
diff --git a/modules/resume.py b/modules/ircv3_resume.py
index c3283bde..3184aa35 100644
--- a/modules/resume.py
+++ b/modules/ircv3_resume.py
@@ -1,3 +1,5 @@
+#--depends-on ircv3_server_time
+
from src import ModuleManager, utils
CAP = utils.irc.Capability(None, "draft/resume-0.4")
@@ -12,13 +14,13 @@ class Module(ModuleManager.BaseModule):
def _del_token(self, server, new=False):
server.del_setting(self._setting(new))
- def _get_timestamp(self, server):
- return server.get_setting("last-read", None)
@utils.hook("new.server")
def new_server(self, event):
- resume_timestamp = self._get_timestamp(event["server"])
- event["server"]._resume_timestamp = resume_timestamp
+ # we need to pull this before any data has been exchanged - to make sure
+ # it's not overwritten from the last connection
+ event["server"]._resume_timestamp = event["server"].get_setting(
+ "last-server-time", None)
@utils.hook("received.cap.ls")
def on_cap_ls(self, event):
diff --git a/modules/sasl/README.md b/modules/ircv3_sasl/README.md
index 30a51e08..30a51e08 100644
--- a/modules/sasl/README.md
+++ b/modules/ircv3_sasl/README.md
diff --git a/modules/sasl/__init__.py b/modules/ircv3_sasl/__init__.py
index b62309a6..b62309a6 100644
--- a/modules/sasl/__init__.py
+++ b/modules/ircv3_sasl/__init__.py
diff --git a/modules/sasl/scram.py b/modules/ircv3_sasl/scram.py
index f243d1e6..f243d1e6 100644
--- a/modules/sasl/scram.py
+++ b/modules/ircv3_sasl/scram.py
diff --git a/modules/ircv3_server_time.py b/modules/ircv3_server_time.py
new file mode 100644
index 00000000..e363b341
--- /dev/null
+++ b/modules/ircv3_server_time.py
@@ -0,0 +1,17 @@
+from src import ModuleManager, utils
+
+CAP = utils.irc.Capability("server-time")
+TAG = utils.irc.MessageTag("time")
+
+class Module(ModuleManager.BaseModule):
+ @utils.hook("received.cap.ls")
+ @utils.hook("received.cap.new")
+ def on_cap(self, event):
+ if CAP.available(event["capabilities"]):
+ return CAP.copy()
+
+ @utils.hook("raw.received")
+ def raw_recv(self, event):
+ server_time = TAG.get_value(event["line"].tags)
+ if not server_time == None:
+ event["server"].set_setting("last-server-time", server_time)
diff --git a/modules/sts.py b/modules/ircv3_sts.py
index 09ecf523..09ecf523 100644
--- a/modules/sts.py
+++ b/modules/ircv3_sts.py
diff --git a/modules/lastfm.py b/modules/lastfm.py
index 19dd69a7..2e08bdab 100644
--- a/modules/lastfm.py
+++ b/modules/lastfm.py
@@ -20,13 +20,23 @@ class Module(ModuleManager.BaseModule):
:help: Get the last listened to track from a user
:usage: [username]
"""
- if event["args_split"]:
- lastfm_username = event["args_split"][0]
- shown_username = lastfm_username
+ user = None
+ lastfm_username = None
+ shown_username = None
+
+ if event["args"]:
+ arg_username = event["args_split"][0]
+ if event["server"].has_user_id(arg_username):
+ user = event["server"].get_user(event["args_split"][0])
+ else:
+ lastfm_username = shown_username = arg_username
else:
- lastfm_username = event["user"].get_setting("lastfm",
- event["user"].nickname)
- shown_username = event["user"].nickname
+ user = event["user"]
+
+ if user:
+ lastfm_username = user.get_setting("lastfm", user.nickname)
+ shown_username = user.nickname
+
page = utils.http.request(URL_SCROBBLER, get_params={
"method": "user.getrecenttracks", "user": lastfm_username,
"api_key": self.bot.config["lastfm-api-key"],
diff --git a/modules/line_handler/__init__.py b/modules/line_handler/__init__.py
index a11fdec5..19e1902b 100644
--- a/modules/line_handler/__init__.py
+++ b/modules/line_handler/__init__.py
@@ -2,11 +2,6 @@ import enum
from src import EventManager, ModuleManager, utils
from . import channel, core, ircv3, message, user
-LABELED_BATCH = {
- "labeled-response": "label",
- "draft/labeled-response": "draft/label"
-}
-
class Module(ModuleManager.BaseModule):
def _handle(self, server, line):
hooks = self.events.on("raw.received").on(line.command).get_hooks()
@@ -15,8 +10,9 @@ class Module(ModuleManager.BaseModule):
default_events.append(hook.kwargs.get("default_event", False))
default_event = any(default_events)
- kwargs = {"args": line.args, "tags": line.tags, "server": server,
- "prefix": line.prefix, "direction": utils.Direction.Recv}
+ kwargs = {"command": line.command, "args": line.args, "tags": line.tags,
+ "server": server, "source": line.source,
+ "direction": utils.Direction.Recv}
self.events.on("raw.received").on(line.command).call_unsafe(**kwargs)
if default_event or not hooks:
@@ -26,7 +22,7 @@ class Module(ModuleManager.BaseModule):
def handle_raw(self, event):
if ("batch" in event["line"].tags and
event["line"].tags["batch"] in event["server"].batches):
- event["server"].batches[event["line"].tags["batch"]].lines.append(
+ event["server"].batches[event["line"].tags["batch"]].add_line(
event["line"])
else:
self._handle(event["server"], event["line"])
@@ -34,8 +30,9 @@ class Module(ModuleManager.BaseModule):
@utils.hook("raw.send")
def handle_send(self, event):
self.events.on("raw.send").on(event["line"].command).call_unsafe(
- args=event["line"].args, tags=event["line"].tags,
- server=event["server"], direction=utils.Direction.Send)
+ command=event["line"].command, args=event["line"].args,
+ tags=event["line"].tags, server=event["server"],
+ direction=utils.Direction.Send)
# ping from the server
@utils.hook("raw.received.ping")
@@ -158,22 +155,14 @@ class Module(ModuleManager.BaseModule):
def invite(self, event):
core.invite(self.events, event)
- # we've received/sent a message
+ # we've received/sent a PRIVMSG, NOTICE or TAGMSG
@utils.hook("raw.received.privmsg")
@utils.hook("raw.send.privmsg")
- def privmsg(self, event):
- message.privmsg(self.events, event)
-
- # we've received/sent a notice
@utils.hook("raw.received.notice")
@utils.hook("raw.send.notice")
- def notice(self, event):
- message.notice(self.events, event)
-
- # IRCv3 TAGMSG, used to send tags without any other information
@utils.hook("raw.received.tagmsg")
- def tagmsg(self, event):
- message.tagmsg(self.events, event)
+ def message(self, event):
+ message.message(self.events, event)
# IRCv3 AWAY, used to notify us that a client we can see has changed /away
@utils.hook("raw.received.away")
@@ -187,20 +176,29 @@ class Module(ModuleManager.BaseModule):
if modifier == "+":
batch_type = event["args"][1]
- event["server"].batches[identifier] = utils.irc.IRCRecvBatch(
- identifier, batch_type, event["tags"])
+ args = event["args"][2:]
+
+ batch = utils.irc.IRCBatch(identifier, batch_type, args,
+ event["tags"])
+ event["server"].batches[identifier] = batch
+
+ self.events.on("received.batch.start").call(batch=batch,
+ server=event["server"])
else:
batch = event["server"].batches[identifier]
del event["server"].batches[identifier]
- add_tags = {}
- if batch.type in LABELED_BATCH.keys():
- tag_name = LABELED_BATCH[batch.type]
- add_tags[tag_name] = batch.tags[tag_name]
+ lines = batch.get_lines()
+
+ results = self.events.on("received.batch.end").call(batch=batch,
+ server=event["server"])
+
+ for result in results:
+ if not result == None:
+ lines = result
+ break
- for line in batch.lines:
- if add_tags:
- line.tags.update(add_tags)
+ for line in lines:
self._handle(event["server"], line)
# IRCv3 CHGHOST, a user's username and/or hostname has changed
diff --git a/modules/line_handler/channel.py b/modules/line_handler/channel.py
index 5b877764..759fdf45 100644
--- a/modules/line_handler/channel.py
+++ b/modules/line_handler/channel.py
@@ -8,7 +8,7 @@ def handle_332(events, event):
topic=topic)
def topic(events, event):
- user = event["server"].get_user(event["prefix"].nickname)
+ user = event["server"].get_user(event["source"].nickname)
channel = event["server"].channels.get(event["args"][0])
topic = event["args"].get(1)
channel.set_topic(topic)
@@ -69,17 +69,17 @@ def join(events, event):
account = event["args"][1]
realname = event["args"][2]
- user = event["server"].get_user(event["prefix"].nickname)
+ user = event["server"].get_user(event["source"].nickname)
- user.username = event["prefix"].username
- user.hostname = event["prefix"].hostname
+ user.username = event["source"].username
+ user.hostname = event["source"].hostname
if account:
user.identified_account = account
user.identified_account_id = event["server"].get_user(account).get_id()
if realname:
user.realname = realname
- is_self = event["server"].is_own_nickname(event["prefix"].nickname)
+ is_self = event["server"].is_own_nickname(event["source"].nickname)
if is_self:
channel = event["server"].channels.add(channel_name)
else:
@@ -101,7 +101,7 @@ def join(events, event):
def part(events, event):
channel = event["server"].channels.get(event["args"][0])
- user = event["server"].get_user(event["prefix"].nickname)
+ user = event["server"].get_user(event["source"].nickname)
reason = event["args"].get(1)
channel.remove_user(user)
@@ -109,7 +109,7 @@ def part(events, event):
if not len(user.channels):
event["server"].remove_user(user)
- if not event["server"].is_own_nickname(event["prefix"].nickname):
+ if not event["server"].is_own_nickname(event["source"].nickname):
events.on("received.part").call(channel=channel, reason=reason,
user=user, server=event["server"])
else:
@@ -137,7 +137,7 @@ def handle_477(timers, event):
server_id=event["server"].id)
def kick(events, event):
- user = event["server"].get_user(event["prefix"].nickname)
+ user = event["server"].get_user(event["source"].nickname)
target = event["args"][1]
channel = event["server"].channels.get(event["args"][0])
reason = event["args"].get(2)
diff --git a/modules/line_handler/core.py b/modules/line_handler/core.py
index 88544a08..3d656b2f 100644
--- a/modules/line_handler/core.py
+++ b/modules/line_handler/core.py
@@ -7,10 +7,11 @@ def ping(event):
event["server"].send_pong(event["args"][0])
def handle_001(event):
- event["server"].socket.set_write_throttling(True)
- event["server"].name = event["prefix"].hostmask
+ event["server"].socket.enable_write_throttle()
+ event["server"].name = event["source"].hostmask
event["server"].set_own_nickname(event["args"][0])
event["server"].send_whois(event["server"].nickname)
+ event["server"].connected = True
def handle_005(events, event):
isupport_list = event["args"][1:-1]
@@ -64,7 +65,7 @@ def motd_line(event):
event["server"].motd_lines.append(event["args"][1])
def mode(events, event):
- user = event["server"].get_user(event["prefix"].nickname)
+ user = event["server"].get_user(event["source"].nickname)
target = event["args"][0]
is_channel = target[0] in event["server"].channel_types
if is_channel:
@@ -97,7 +98,7 @@ def mode(events, event):
def invite(events, event):
target_channel = event["args"][1]
- user = event["server"].get_user(event["prefix"].nickname)
+ user = event["server"].get_user(event["source"].nickname)
target_user = event["server"].get_user(event["args"][0])
events.on("received.invite").call(user=user, target_channel=target_channel,
server=event["server"], target_user=target_user)
diff --git a/modules/line_handler/ircv3.py b/modules/line_handler/ircv3.py
index 42afece7..d9f83b75 100644
--- a/modules/line_handler/ircv3.py
+++ b/modules/line_handler/ircv3.py
@@ -10,11 +10,9 @@ CAPABILITIES = [
utils.irc.Capability("away-notify"),
utils.irc.Capability("userhost-in-names"),
utils.irc.Capability("message-tags", "draft/message-tags-0.2"),
- utils.irc.Capability("server-time"),
utils.irc.Capability("cap-notify"),
utils.irc.Capability("batch"),
utils.irc.Capability("echo-message"),
- utils.irc.Capability(None, "draft/labeled-response"),
utils.irc.Capability(None, "draft/rename"),
utils.irc.Capability(None, "draft/setname")
]
diff --git a/modules/line_handler/message.py b/modules/line_handler/message.py
index 4fb507ca..9157af66 100644
--- a/modules/line_handler/message.py
+++ b/modules/line_handler/message.py
@@ -1,163 +1,111 @@
from src import utils
-def _from_self(server, direction, prefix):
+def _from_self(server, direction, source):
if direction == utils.Direction.Send:
if server.has_capability_str("echo-message"):
return None
else:
return True
else:
- if prefix:
- return server.is_own_nickname(prefix.nickname)
+ if source:
+ return server.is_own_nickname(source.nickname)
else:
return False
-def privmsg(events, event):
+def message(events, event):
from_self = _from_self(event["server"], event["direction"],
- event.get("prefix", None))
+ event.get("source", None))
if from_self == None:
return
- user = None
- if "prefix" in event and not from_self:
- user = event["server"].get_user(event["prefix"].nickname)
+ direction = "send" if from_self else "received"
- message = event["args"][1]
target_str = event["args"][0]
+ message = None
+ if len(event["args"]) > 1:
+ message = event["args"][1]
+
+ if not from_self and (
+ not event["source"] or
+ not event["server"].name or
+ event["source"].hostmask == event["server"].name or
+ target_str == "*"):
+ if event["source"]:
+ event["server"].name = event["source"].hostmask
+
+ events.on("received.server-notice").call(message=message,
+ message_split=message.split(" "), server=event["server"])
+ return
+
+ if from_self:
+ user = event["server"].get_user(event["server"].nickname)
+ else:
+ user = event["server"].get_user(event["source"].nickname)
+
# strip prefix_symbols from the start of target, for when people use
# e.g. 'PRIVMSG +#channel :hi' which would send a message to only
# voiced-or-above users
target = target_str.lstrip("".join(event["server"].prefix_symbols.keys()))
- channel = None
+ is_channel = False
+
if target[0] in event["server"].channel_types:
+ is_channel = True
if not target in event["server"].channels:
return
- channel = event["server"].channels.get(target)
-
- action = False
- event_type = "message"
- ctcp_message = utils.irc.parse_ctcp(message)
- if ctcp_message:
- message = ctcp_message.message
- if ctcp_message.command == "ACTION":
- action = True
- message = ctcp_message.message
- else:
- event_type = "ctcp.%s" % ctcp_message.command
-
- if user and "account" in event["tags"]:
- user.identified_account = event["tags"]["account"]
- user.identified_account_id = event["server"].get_user(
- event["tags"]["account"]).get_id()
-
- kwargs = {"message": message, "message_split": message.split(" "),
- "server": event["server"], "tags": event["tags"],
- "action": action, "target_str": target_str}
-
- direction = "send" if from_self else "received"
- context = "channel" if channel else "private"
- hook = events.on(direction).on(event_type).on(context)
-
- user_nickname = None
- if user:
- user_nickname = None if from_self else user.nickname
-
- if channel:
- hook.call(user=user, channel=channel, **kwargs)
- channel.buffer.add_message(user_nickname, message, action,
- event["tags"], user==None)
- elif from_self:
- # a message we've sent to a user
- user = event["server"].get_user(target)
- hook.call(user=user, **kwargs)
- user.buffer.add_message(user_nickname, message, action,
- event["tags"], True)
- elif event["server"].is_own_nickname(target):
- hook.call(user=user, **kwargs)
- user.buffer.add_message(user_nickname, message, action,
- event["tags"], False)
-
-def notice(events, event):
- from_self = _from_self(event["server"], event["direction"],
- event.get("prefix", None))
- if from_self == None:
- return
-
- message = event["args"][1]
- target = event["args"][0]
-
- if "prefix" in event and (
- not event["prefix"] or
- not event["server"].name or
- event["prefix"].hostmask == event["server"].name or
- target == "*"):
- if event["prefix"]:
- event["server"].name = event["prefix"].hostmask
-
- events.on("received.server-notice").call(message=message,
- message_split=message.split(" "), server=event["server"])
+ target_obj = event["server"].channels.get(target)
else:
- user = None
- if "prefix" in event and not from_self:
- user = event["server"].get_user(event["prefix"].nickname)
-
- channel = None
- if target[0] in event["server"].channel_types:
- channel = event["server"].channels.get(target)
+ target_obj = event["server"].get_user(target)
- direction = "send" if from_self else "received"
- context = "channel" if channel else "private"
- hook = events.on(direction).on("notice").on(context)
+ kwargs = {"server": event["server"], "target": target_obj,
+ "target_str": target_str, "user": user, "tags": event["tags"],
+ "is_channel": is_channel}
- user_nickname = None
- if user:
- user_nickname = None if from_self else user.nickname
+ action = False
- kwargs = {"message": message, "message_split": message.split(" "),
- "server": event["server"], "tags": event["tags"]}
+ if message:
+ ctcp_message = utils.irc.parse_ctcp(message)
- if channel:
- hook.call(user=user, channel=channel, **kwargs)
- channel.buffer.add_notice(user_nickname, message, event["tags"],
- user==None)
- elif from_self:
- # a notice we've sent to a user
- user = event["server"].get_user(target)
- hook.call(user=user, **kwargs)
- user.buffer.add_notice(user_nickname, message, event["tags"],
- True)
- elif event["server"].is_own_nickname(target):
- hook.call(user=user, **kwargs)
- user.buffer.add_notice(user_nickname, message, event["tags"],
- False)
+ if ctcp_message:
+ if not ctcp_message.command == "ACTION" or not event["command"
+ ] == "PRIVMSG":
+ if event["command"] == "PRIVMSG":
+ ctcp_action = "request"
+ else:
+ ctcp_action = "response"
+ events.on(direction).on("ctcp").on(ctcp_action).call(
+ message=ctcp_message.message, **kwargs)
+ events.on(direction).on("ctcp").on(ctcp_action).on(
+ ctcp_message.command).call(message=ctcp_message.message,
+ **kwargs)
+ return
+ else:
+ message = ctcp_message.message
+ action = True
-def tagmsg(events, event):
- from_self = _from_self(event["server"], event["direction"],
- event.get("prefix", None))
- if from_self == None:
- return
+ if not message == None:
+ kwargs["message"] = message
+ kwargs["message_split"] = message.split(" ")
+ kwargs["action"] = action
- user = None
- if "prefix" in event and not from_self:
- user = event["server"].get_user(event["prefix"].nickname)
+ event_type = event["command"].lower()
+ if event["command"] == "PRIVMSG":
+ event_type = "message"
- target = event["args"][0]
- channel = None
- if target[0] in event["server"].channel_types:
- channel = event["server"].channels.get(target)
+ context = "channel" if is_channel else "private"
+ hook = events.on(direction).on(event_type).on(context)
- direction = "send" if from_self else "received"
- context = "channel" if channel else "private"
- hook = events.on(direction).on("tagmsg").on(context)
+ if is_channel:
+ hook.call(channel=target_obj, **kwargs)
+ target_obj.buffer.add_message(user.nickname, message, action,
+ event["tags"], from_self)
+ else:
+ hook.call(**kwargs)
- kwargs = {"server": event["server"], "tags": event["tags"]}
+ buffer_obj = target_obj
+ if not from_self:
+ buffer_obj = user
- if channel:
- hook.call(user=user, channel=channel, **kwargs)
- elif event["server"].is_own_nickname(target):
- hook.call(user=user, **kwargs)
- elif from_self:
- user = event["server"].get_user(target)
- hook.call(user=user, **kwargs)
+ buffer_obj.buffer.add_message(user.nickname, message, action,
+ event["tags"], from_self)
diff --git a/modules/line_handler/user.py b/modules/line_handler/user.py
index f95ce4f4..c31a3b52 100644
--- a/modules/line_handler/user.py
+++ b/modules/line_handler/user.py
@@ -19,13 +19,13 @@ def handle_311(event):
def quit(events, event):
nickname = None
if event["direction"] == utils.Direction.Recv:
- nickname = event["prefix"].nickname
+ nickname = event["source"].nickname
reason = event["args"].get(0)
if event["direction"] == utils.Direction.Recv:
- nickname = event["prefix"].nickname
+ nickname = event["source"].nickname
if (not event["server"].is_own_nickname(nickname) and
- not event["prefix"].hostmask == "*"):
+ not event["source"].hostmask == "*"):
user = event["server"].get_user(nickname)
event["server"].remove_user(user)
events.on("received.quit").call(reason=reason, user=user,
@@ -37,10 +37,10 @@ def quit(events, event):
def nick(events, event):
new_nickname = event["args"].get(0)
- user = event["server"].get_user(event["prefix"].nickname)
+ user = event["server"].get_user(event["source"].nickname)
old_nickname = user.nickname
- if not event["server"].is_own_nickname(event["prefix"].nickname):
+ if not event["server"].is_own_nickname(event["source"].nickname):
events.on("received.nick").call(new_nickname=new_nickname,
old_nickname=old_nickname, user=user, server=event["server"])
else:
@@ -52,7 +52,7 @@ def nick(events, event):
event["server"].change_user_nickname(old_nickname, new_nickname)
def away(events, event):
- user = event["server"].get_user(event["prefix"].nickname)
+ user = event["server"].get_user(event["source"].nickname)
message = event["args"].get(0)
if message:
user.away = True
@@ -65,7 +65,7 @@ def away(events, event):
events.on("received.away.off").call(user=user, server=event["server"])
def chghost(event):
- nickname = event["prefix"].nickname
+ nickname = event["source"].nickname
username = event["args"][0]
hostname = event["args"][1]
@@ -78,7 +78,7 @@ def chghost(event):
target.hostname = hostname
def setname(event):
- nickname = event["prefix"].nickname
+ nickname = event["source"].nickname
realname = event["args"][0]
user = event["server"].get_user(nickname)
@@ -88,7 +88,7 @@ def setname(event):
event["server"].realname = realname
def account(events, event):
- user = event["server"].get_user(event["prefix"].nickname)
+ user = event["server"].get_user(event["source"].nickname)
if not event["args"][0] == "*":
user.identified_account = event["args"][0]
diff --git a/modules/modules.py b/modules/modules.py
index 12023cc4..557e82ba 100644
--- a/modules/modules.py
+++ b/modules/modules.py
@@ -26,8 +26,9 @@ class Module(ModuleManager.BaseModule):
name = event["args_split"][0].lower()
if name in self.bot.modules.modules:
raise utils.EventError("Module '%s' is already loaded" % name)
+ definition = self.bot.modules.find_module(name)
- self._catch(name, lambda: self.bot.modules.load_module(self.bot, name))
+ self._catch(name, lambda: self.bot.modules.load_module(self.bot, definition))
event["stdout"].write("Loaded '%s'" % name)
@utils.hook("received.command.unloadmodule", min_args=1)
@@ -46,8 +47,8 @@ class Module(ModuleManager.BaseModule):
def _reload(self, name):
self.bot.modules.unload_module(name)
- self.bot.modules.load_module(self.bot, name)
-
+ definition = self.bot.modules.find_module(name)
+ self.bot.modules.load_module(self.bot, definition)
@utils.hook("received.command.reloadmodule", min_args=1)
def reload(self, event):
"""
@@ -66,25 +67,26 @@ class Module(ModuleManager.BaseModule):
:help: Reload all modules
:permission: reload-all-modules
"""
- reloaded = []
- failed = []
+ success = []
+ fail = []
for name in list(self.bot.modules.modules.keys()):
try:
- self._reload(name)
+ self.bot.modules.unload_module(name)
except ModuleManager.ModuleWarning:
continue
except:
- failed.append(name)
- continue
- reloaded.append(name)
+ fail.append(name)
+ load_success, load_fail = self.bot.load_modules(safe=True)
+ success.extend(load_success)
+ fail.extend(load_fail)
- if reloaded and failed:
+ if success and fail:
event["stdout"].write("Reloaded %d modules, %d failed" % (
- len(reloaded), len(failed)))
- elif failed:
+ len(success), len(fail)))
+ elif fail:
event["stdout"].write("Failed to reload all modules")
else:
- event["stdout"].write("Reloaded %d modules" % len(reloaded))
+ event["stdout"].write("Reloaded %d modules" % len(success))
@utils.hook("received.command.enablemodule", min_args=1)
def enable(self, event):
diff --git a/modules/permissions/__init__.py b/modules/permissions/__init__.py
index 3075af6a..3e2ce3f1 100644
--- a/modules/permissions/__init__.py
+++ b/modules/permissions/__init__.py
@@ -1,7 +1,7 @@
#--depends-on commands
#--depends-on config
-import base64, os
+import base64, binascii, os
import scrypt
from src import ModuleManager, utils
@@ -51,13 +51,11 @@ class Module(ModuleManager.BaseModule):
(None, None))
return hash, salt
- def _random_string(self, n):
- return base64.b64encode(os.urandom(n)).decode("utf8")
-
def _make_salt(self):
- return self._random_string(64)
+ return base64.b64encode(os.urandom(64)).decode("utf8")
+
def _random_password(self):
- return self._random_string(32)
+ return binascii.hexlify(os.urandom(32)).decode("utf8")
def _make_hash(self, password, salt=None):
salt = salt or self._make_salt()
diff --git a/modules/signals.py b/modules/signals.py
index 4cdefc1d..522179c5 100644
--- a/modules/signals.py
+++ b/modules/signals.py
@@ -1,4 +1,4 @@
-import signal
+import signal, sys
from src import Config, ModuleManager, utils
class Module(ModuleManager.BaseModule):
@@ -19,11 +19,17 @@ class Module(ModuleManager.BaseModule):
self.events.on("signal.interrupt").call(signum=signum)
+ written = False
for server in self.bot.servers.values():
- server.socket.clear_send_buffer()
- line = server.send_quit("Shutting down")
- server.send_enabled = False
- line.on_send(self._make_hook(server))
+ if server.connected:
+ server.socket.clear_send_buffer()
+ line = server.send_quit("Shutting down")
+ server.send_enabled = False
+ line.on_send(self._make_hook(server))
+ written = True
+
+ if not written:
+ sys.exit()
def _make_hook(self, server):
return lambda: self.bot.disconnect(server)
@@ -42,9 +48,6 @@ class Module(ModuleManager.BaseModule):
self.bot.config.load()
self.bot.log.info("Reloaded config file", [])
- def _reload(self, name):
- self.bot.modules.unload_module(name)
- self.bot.modules.load_module(self.bot, name)
def _reload_modules(self):
self.bot.log.info("Reloading modules", [])
diff --git a/modules/tweets.py b/modules/tweets.py
deleted file mode 100644
index d100bb47..00000000
--- a/modules/tweets.py
+++ /dev/null
@@ -1,122 +0,0 @@
-#--depends-on commands
-#--depends-on config
-
-#--require-config twitter-api-key
-#--require-config twitter-api-secret
-#--require-config twitter-access-token
-#--require-config twitter-access-secret
-
-import datetime, html, re, time, traceback
-import twitter
-from src import ModuleManager, utils
-
-REGEX_TWITTERURL = re.compile(
- "https?://(?:www\.)?twitter.com/[^/]+/status/(\d+)", re.I)
-
-@utils.export("channelset", {"setting": "auto-tweet",
- "help": "Enable/disable automatically getting tweet info",
- "validate": utils.bool_or_none, "example": "on"})
-class Module(ModuleManager.BaseModule):
- _name = "Twitter"
-
- def make_timestamp(self, s):
- seconds_since = time.time() - datetime.datetime.strptime(s,
- "%a %b %d %H:%M:%S %z %Y").timestamp()
- since, unit = utils.time_unit(seconds_since)
- return "%s %s ago" % (since, unit)
-
- def _get_api(self):
- api_key = self.bot.config["twitter-api-key"]
- api_secret = self.bot.config["twitter-api-secret"]
- access_token = self.bot.config["twitter-access-token"]
- access_secret = self.bot.config["twitter-access-secret"]
- return twitter.Twitter(auth=twitter.OAuth(
- access_token, access_secret, api_key, api_secret))
-
- def _from_id(self, tweet_id):
- api = self._get_api()
- try:
- return api.statuses.show(id=tweet_id)
- except:
- traceback.print_exc()
-
- def _format_tweet(self, tweet):
- linked_id = tweet["id"]
- username = tweet["user"]["screen_name"]
-
- verified = ""
- if tweet["user"]["verified"]:
- verified = " %s" % utils.irc.color("✓", utils.consts.LIGHTBLUE)
-
- tweet_link = "https://twitter.com/%s/status/%s" % (username,
- linked_id)
-
- short_url = self.exports.get_one("shortlink")(tweet_link)
- short_url = " - %s" % short_url if short_url else ""
-
- if "retweeted_status" in tweet:
- original_username = "@%s" % tweet["retweeted_status"
- ]["user"]["screen_name"]
- original_text = tweet["retweeted_status"]["text"]
- retweet_timestamp = self.make_timestamp(tweet[
- "created_at"])
- original_timestamp = self.make_timestamp(tweet[
- "retweeted_status"]["created_at"])
- return "(@%s%s (%s) retweeted %s (%s)) %s%s" % (
- username, verified, retweet_timestamp, original_username,
- original_timestamp, html.unescape(original_text),
- short_url)
- else:
- return "(@%s%s, %s) %s%s" % (username, verified,
- self.make_timestamp(tweet["created_at"]),
- html.unescape(tweet["text"]), short_url)
-
- @utils.hook("received.command.tw", alias_of="tweet")
- @utils.hook("received.command.tweet")
- def tweet(self, event):
- """
- :help: Get/find a tweet
- :usage: [@username/URL/ID]
- """
-
- if event["args"]:
- target = event["args"]
- else:
- target = event["target"].buffer.find(REGEX_TWITTERURL)
- if target:
- target = target.message
- if target:
- url_match = re.search(REGEX_TWITTERURL, target)
- if url_match or target.isdigit():
- tweet_id = url_match.group(1) if url_match else target
- tweet = self._from_id(tweet_id)
- else:
- if target.startswith("@"):
- target = target[1:]
- api = self._get_api()
- try:
- tweet = api.statuses.user_timeline(
- screen_name=target, count=1)[0]
- except:
- traceback.print_exc()
- tweet = None
- if tweet:
- tweet_str = self._format_tweet(tweet)
- event["stdout"].write(tweet_str)
- else:
- event["stderr"].write("Invalid tweet identifiers provided")
- else:
- event["stderr"].write("No tweet provided to get information about")
-
- @utils.hook("command.regex", pattern=REGEX_TWITTERURL)
- def regex(self, event):
- """
- :command: tweet
- """
- if event["target"].get_setting("auto-tweet", False):
- event.eat()
- tweet_id = event["match"].group(1)
- tweet = self._from_id(tweet_id)
- if tweet:
- tweet_str = self._format_tweet(tweet)
- event["stdout"].write(tweet_str)
diff --git a/modules/tweets/__init__.py b/modules/tweets/__init__.py
new file mode 100644
index 00000000..b0af2bd9
--- /dev/null
+++ b/modules/tweets/__init__.py
@@ -0,0 +1,165 @@
+#--depends-on commands
+#--depends-on permissions
+#--require-config twitter-api-key
+#--require-config twitter-api-secret
+#--require-config twitter-access-token
+#--require-config twitter-access-secret
+
+import re
+from src import ModuleManager, utils
+from . import format
+import tweepy
+
+_bot = None
+_events = None
+_exports = None
+
+REGEX_TWITTERURL = re.compile(
+ "https?://(?:www\.)?twitter.com/[^/]+/status/(\d+)", re.I)
+
+def _get_follows():
+ return _bot.database.channel_settings.find_by_setting("twitter-follow")
+
+class BitBotStreamListener(tweepy.StreamListener):
+ def on_status(self, status):
+ data = json.loads(status)
+ username = data["user"]["screen_name"].lower()
+
+ follows = []
+ for server_id, channel_name, value in _get_follows():
+ if value.lower() == username:
+ server = _bot.get_server_by_id(server_id)
+ if server and channel_name in server.channels:
+ hooks.append([server, server.channels.get(channel_name)])
+
+ tweet = format._tweet(_exports, data)
+ for server, channel in follows:
+ self.events.on("send.stdout").call(target=channel,
+ module_name="Tweets", server=server, message=tweet)
+
+@utils.export("channelset", {"setting": "auto-tweet",
+ "help": "Enable/disable automatically getting tweet info",
+ "validate": utils.bool_or_none, "example": "on"})
+class Module(ModuleManager.BaseModule):
+ _stream = None
+ def on_load(self):
+ global _bot
+ global _events
+ global _exports
+ _bot = self.bot
+ _events = self.events
+ _exports = self.exports
+ def unload(self):
+ self._dispose_stream()
+
+ def _dispose_stream(self):
+ if not self._stream == None:
+ self._stream.disconnect()
+
+ def _get_auth(self):
+ auth = tweepy.OAuthHandler(self.bot.config["twitter-api-key"],
+ self.bot.config["twitter-api-secret"])
+ auth.set_access_token(self.bot.config["twitter-access-token"],
+ self.bot.config["twitter-access-secret"])
+ return auth
+ def _get_api(self, auth):
+ return tweepy.API(auth)
+
+ def _from_id(self, tweet_id):
+ return self._get_api(self._get_auth()).get_status(tweet_id)
+ def _from_username(self, username):
+ return self._get_api(self._get_auth()).user_timeline(
+ screen_name=username, count=1)[0]
+
+ def _start_stream(self):
+ self._dispose_stream()
+
+ usernames = set([])
+ for server_id, channel_name, value in _get_follows():
+ usernames.add(value)
+ if not usernames:
+ return False
+
+ auth = self._get_auth()
+ self._stream = tweepy.Stream(auth=auth, listener=BitBotStreamListener)
+
+ self._stream.filter(follow=list(usernames), is_async=True)
+ return True
+
+ @utils.hook("received.command.tfollow", min_args=2, channel_only=True)
+ def tfollow(self, event):
+ """
+ :help: Stream tweets from a given account to the current channel
+ :usage: add|remove @<username>
+ :permission: twitter-follow
+ """
+ username = event["args_split"][0]
+ if username.startswith("@"):
+ username = username[1:]
+
+ subcommand = event["args_split"][0].lower()
+ follows = event["target"].get_setting("twitter-follow", [])
+ action = None
+
+ if subcommand == "add":
+ action = "followed"
+ if username in follows:
+ raise utils.EventError("Already following %s" % username)
+ follows.append(username)
+ elif subcommand == "remove":
+ action = "unfollowed"
+ if not username in follows:
+ raise utils.EventError("Not following %s" % username)
+ follows.remove(username)
+ else:
+ raise utils.EventError("Unknown subcommand")
+
+ event["target"].set_setting("twitter-follow", follows)
+ self._start_stream()
+ event["stdout"].write("%s @%s" % (action.title(), username))
+
+ @utils.hook("received.command.tw", alias_of="tweet")
+ @utils.hook("received.command.tweet")
+ def tweet(self, event):
+ """
+ :help: Get/find a tweet
+ :usage: [@username/URL/ID]
+ """
+
+ if event["args"]:
+ target = event["args"]
+ else:
+ target = event["target"].buffer.find(REGEX_TWITTERURL)
+ if target:
+ target = target.message
+ if target:
+ url_match = re.search(REGEX_TWITTERURL, target)
+ if url_match or target.isdigit():
+ tweet_id = url_match.group(1) if url_match else target
+ tweet = self._from_id(tweet_id)
+ else:
+ if target.startswith("@"):
+ target = target[1:]
+ tweet = self._from_username(target)
+
+ if tweet:
+ tweet_str = format._tweet(self.exports, tweet)
+ event["stdout"].write(tweet_str)
+ else:
+ event["stderr"].write("Invalid tweet identifiers provided")
+ else:
+ event["stderr"].write("No tweet provided to get information about")
+
+ @utils.hook("command.regex", pattern=REGEX_TWITTERURL)
+ def regex(self, event):
+ """
+ :command: tweet
+ """
+ if event["target"].get_setting("auto-tweet", False):
+ event.eat()
+ tweet_id = event["match"].group(1)
+ tweet = self._from_id(tweet_id)
+ if tweet:
+ tweet_str = format._tweet(self.exports, tweet)
+ event["stdout"].write(tweet_str)
+
diff --git a/modules/tweets/format.py b/modules/tweets/format.py
new file mode 100644
index 00000000..c41fde17
--- /dev/null
+++ b/modules/tweets/format.py
@@ -0,0 +1,35 @@
+import datetime, html, time
+from src import utils
+
+def _timestamp(dt):
+ seconds_since = time.time()-dt.timestamp()
+ since, unit = utils.time_unit(seconds_since)
+ return "%s %s ago" % (since, unit)
+
+def _tweet(exports, tweet):
+ linked_id = tweet.id
+ username = tweet.user.screen_name
+
+ verified = ""
+ if tweet.user.verified:
+ verified = " %s" % utils.irc.color("✓", utils.consts.LIGHTBLUE)
+
+ tweet_link = "https://twitter.com/%s/status/%s" % (username,
+ linked_id)
+
+ short_url = exports.get_one("shortlink")(tweet_link)
+ short_url = " - %s" % short_url if short_url else ""
+ created_at = _timestamp(tweet.created_at)
+
+ # having to use hasattr here is nasty.
+ if hasattr(tweet, "retweeted_status"):
+ original_username = tweet.retweeted_status.user.screen_name
+ original_text = tweet.retweeted_status.text
+ original_timestamp = _timestamp(tweet.retweeted_status.created_at)
+ return "(@%s%s (%s) retweeted @%s (%s)) %s%s" % (username, verified,
+ created_at, original_username, original_timestamp,
+ html.unescape(original_text), short_url)
+ else:
+ return "(@%s%s, %s) %s%s" % (username, verified, created_at,
+ html.unescape(tweet.text), short_url)
+