aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--IRCLineHandler.py88
-rw-r--r--IRCServer.py14
-rw-r--r--ModuleManager.py5
-rw-r--r--modules/channel_save.py37
-rw-r--r--modules/nickserv.py38
5 files changed, 140 insertions, 42 deletions
diff --git a/IRCLineHandler.py b/IRCLineHandler.py
index 602b39b0..fbb153ad 100644
--- a/IRCLineHandler.py
+++ b/IRCLineHandler.py
@@ -8,32 +8,43 @@ RE_CHANTYPES = re.compile(r"\bCHANTYPES=(\W+)(?:\b|$)")
handlers = {}
descriptions = {}
+default_events = {}
current_description = None
+current_default_event = False
handle_lock = threading.Lock()
line, line_split, bot, server = None, None, None, None
-def handler(f=None, description=None):
- global current_description
- if description:
+def handler(f=None, description=None, default_event=False):
+ global current_description, current_default_event
+ if not f:
current_description = description
+ current_default_event = default_event
return handler
name = f.__name__.split("handle_")[1].upper()
handlers[name] = f
- if current_description:
- descriptions[name] = current_description
- current_description = None
+
+ descriptions[name] = current_description
+ default_events[name] = current_default_event
+ current_description, current_default_event = None, False
def handle(_line, _line_split, _bot, _server):
global line, line_split, bot, server
handler_function = None
+
if len(_line_split) > 1:
+ name = _line_split[1]
if _line_split[0][0] == ":":
- if _line_split[1] in handlers:
- handler_function = handlers[_line_split[1]]
- elif _line_split[1].isdigit():
- _bot.events.on("received").on("numeric").on(
- _line_split[1]).call(line=_line,
- line_split=_line_split, server=_server,
- number=_line_split[1])
+ if name in handlers:
+ handler_function = handlers[name]
+ if default_events.get(name, False) or not name in handlers:
+ if name.isdigit():
+ _bot.events.on("received").on("numeric").on(
+ name).call(line=_line,
+ line_split=_line_split, server=_server,
+ number=name)
+ else:
+ _bot.events.on("received").on(name).call(
+ line=_line, line_split=_line_split, server=_server,
+ command=name)
elif _line_split[0] in handlers:
handler_function = handlers[_line_split[0]]
if handler_function:
@@ -48,12 +59,10 @@ def handle_PING():
server.send_pong(Utils.remove_colon(line_split[1]))
bot.events.on("received").on("ping").call(line=line,
line_split=line_split, server=server, nonce=nonce)
-@handler(description="the first line sent to a registered client")
+@handler(description="the first line sent to a registered client", default_event=True)
def handle_001():
server.set_own_nickname(line_split[2])
server.send_whois(server.nickname)
- bot.events.on("received").on("numeric").on("001").call(
- line=line, line_split=line_split, server=server)
@handler(description="the extra supported things line")
def handle_005():
isupport_line = Utils.arbitrary(line_split, 3)
@@ -74,8 +83,8 @@ def handle_005():
server.channel_types = list(match.group(1))
bot.events.on("received").on("numeric").on("005").call(
line=line, line_split=line_split, server=server,
- isupport=isupport_line)
-@handler(description="whois respose (nickname, username, realname, hostname)")
+ isupport=isupport_line, number="005")
+@handler(description="whois respose (nickname, username, realname, hostname)", default_event=True)
def handle_311():
nickname = line_split[3]
if server.is_own_nickname(nickname):
@@ -86,12 +95,12 @@ def handle_311():
hostname = line_split[5]
target.username = username
target.hostname = hostname
-@handler(description="on-join channel topic line")
+@handler(description="on-join channel topic line", default_event=True)
def handle_332():
channel = server.get_channel(line_split[3])
topic = Utils.arbitrary(line_split, 4)
channel.set_topic(topic)
-@handler(description="on-join channel topic set by/at")
+@handler(description="on-join channel topic set by/at", default_event=True)
def handle_333():
channel = server.get_channel(line_split[3])
topic_setter_hostmask = line_split[4]
@@ -101,7 +110,7 @@ def handle_333():
) else None
channel.set_topic_setter(nickname, username, hostname)
channel.set_topic_time(topic_time)
-@handler(description="on-join user list with status symbols")
+@handler(description="on-join user list with status symbols", default_event=True)
def handle_353():
channel = server.get_channel(line_split[4])
nicknames = line_split[5:]
@@ -157,7 +166,7 @@ def handle_PART():
bot.events.on("self").on("part").call(line=line,
line_split=line_split, server=server, channel=channel,
reason=reason)
-@handler(description="unknown command sent by us, oops!")
+@handler(description="unknown command sent by us, oops!", default_event=True)
def handle_421():
print("warning: unknown command '%s'." % line_split[3])
@handler(description="a user has disconnected!")
@@ -172,6 +181,31 @@ def handle_QUIT():
user=user)
else:
server.disconnect()
+
+@handler(description="The server is telling us about its capabilities!")
+def handle_CAP():
+ _line = line
+ _line_split = line_split
+ if line.startswith(":"):
+ _line = " ".join(line_split[1:])
+ _line_split = _line.split()
+
+ capability_list = Utils.arbitrary(_line_split, 3).split()
+ bot.events.on("received").on("cap").call(line=line,
+ line_split=line_split, server=server,
+ nickname=_line_split[1], subcommand=_line_split[2],
+ capabilities=capability_list)
+
+@handler(description="The server is asking for authentication")
+def handle_AUTHENTICATE():
+ _line_split = line_split
+ if line.startswith(":"):
+ _line_split = line_split[1:]
+ bot.events.on("received").on("authenticate").call(line=line,
+ line_split=line_split, server=server,
+ message=Utils.arbitrary(_line_split, 1)
+ )
+
@handler(description="someone has changed their nickname")
def handle_NICK():
nickname, username, hostname = Utils.seperate_hostmask(line_split[0])
@@ -269,12 +303,12 @@ def handle_PRIVMSG():
user=user, message=message, message_split=message_split,
action=action)
user.log.add_line(user.nickname, message, action)
-@handler(description="response to a WHO command for user information")
+@handler(description="response to a WHO command for user information", default_event=True)
def handle_352():
user = server.get_user(line_split[7])
user.username = line_split[4]
user.hostname = line_split[5]
-@handler(description="response to an empty mode command")
+@handler(description="response to an empty mode command", default_event=True)
def handle_324():
channel = server.get_channel(line_split[3])
modes = line_split[4]
@@ -282,14 +316,14 @@ def handle_324():
for mode in modes[1:]:
if mode in server.channel_modes:
channel.add_mode(mode)
-@handler(description="channel creation unix timestamp")
+@handler(description="channel creation unix timestamp", default_event=True)
def handle_329():
channel = server.get_channel(line_split[3])
channel.creation_timestamp = int(line_split[4])
-@handler(description="nickname already in use")
+@handler(description="nickname already in use", default_event=True)
def handle_433():
pass
-@handler(description="we need a registered nickname for this channel")
+@handler(description="we need a registered nickname for this channel", default_event=True)
def handle_477():
bot.add_timer("rejoin", 5, channel_name=line_split[3],
key=server.attempted_join[line_split[3].lower()],
diff --git a/IRCServer.py b/IRCServer.py
index 7154a326..16659381 100644
--- a/IRCServer.py
+++ b/IRCServer.py
@@ -52,8 +52,14 @@ class Server(object):
return self.cached_fileno if fileno == -1 else fileno
def connect(self):
self.socket.connect((self.target_hostname, self.port))
+
if self.password:
self.send_pass(self.password)
+ # In principle, this belongs in the NS module. In reality, it's more practical to put this
+ # One-off case here for SASL
+ if "Nickserv" in self.bot.modules.modules and self.get_setting("nickserv-password"):
+ self.send_capability_request("sasl")
+
self.send_user(self.original_username, self.original_realname)
self.send_nick(self.original_nickname)
self.connected = True
@@ -172,6 +178,14 @@ class Server(object):
self.send("USER %s - - :%s" % (username, realname))
def send_nick(self, nickname):
self.send("NICK %s" % nickname)
+
+ def send_capability_request(self, capname):
+ self.send("CAP REQ :%s" % capname)
+ def send_capability_end(self):
+ self.send("CAP END")
+ def send_authenticate(self, text):
+ self.send("AUTHENTICATE %s" % text)
+
def send_pass(self, password):
self.send("PASS %s" % password)
def send_ping(self, nonce="hello"):
diff --git a/ModuleManager.py b/ModuleManager.py
index 65ea8cbe..3de549d1 100644
--- a/ModuleManager.py
+++ b/ModuleManager.py
@@ -14,6 +14,10 @@ class ModuleManager(object):
def _load_module(self, filename):
name = self.module_name(filename)
+
+ whitelist = self.bot.config.get("module_whitelist", [])
+ if whitelist and name not in whitelist: return
+
with open(filename) as module_file:
while True:
line = module_file.readline().strip()
@@ -61,6 +65,7 @@ class ModuleManager(object):
if name in self.waiting_requirement:
for filename in self.waiting_requirement:
self.load_module(filename)
+ sys.stderr.write("module '%s' loaded.\n" % filename)
else:
sys.stderr.write("module '%s' not loaded.\n" % filename)
def load_modules(self):
diff --git a/modules/channel_save.py b/modules/channel_save.py
index cb950ed2..713a7cb5 100644
--- a/modules/channel_save.py
+++ b/modules/channel_save.py
@@ -1,18 +1,29 @@
class Module(object):
- def __init__(self, bot):
- bot.events.on("self").on("join").hook(self.on_join)
- bot.events.on("received").on("numeric").on("366").hook(
- self.on_connect)
+ def __init__(self, bot):
+ bot.events.on("self").on("part").hook(self.on_self_part)
+ bot.events.on("self").on("join").hook(self.on_join)
+ bot.events.on("received").on("numeric").on("366").hook(
+ self.on_identify_trigger)
+ bot.events.on("received").on("numeric").on("001").hook(
+ self.on_identify_trigger)
- def on_join(self, event):
- channels = set(event["server"].get_setting("autojoin", []))
- channels.add(event["channel"].name)
- event["server"].set_setting("autojoin", list(channels))
+ def on_self_part(self, event):
+ pass
- def on_connect(self, event):
- if event["line_split"][3].lower() == "#bitbot":
- channels = event["server"].get_setting("autojoin", [])
- for channel in channels:
- event["server"].send_join(channel)
+ def on_join(self, event):
+ channels = set(event["server"].get_setting("autojoin", []))
+ channels.add(event["channel"].name)
+ event["server"].set_setting("autojoin", list(channels))
+
+ def on_identify_trigger(self, event):
+ if event["number"]=="001" and not event["server"].sasl_success: return
+ if event["line_split"][3].lower() == "#bitbot" or event["number"]=="001":
+ channels = event["server"].get_setting("autojoin", [])
+ chan_keys = event["server"].get_setting("channel_keys", {})
+ for channel in channels:
+ if channel in chan_keys:
+ event["server"].send_join(channel, key=chan_keys[channel])
+ else:
+ event["server"].send_join(channel)
diff --git a/modules/nickserv.py b/modules/nickserv.py
index 6f1b87b3..149dc961 100644
--- a/modules/nickserv.py
+++ b/modules/nickserv.py
@@ -1,18 +1,28 @@
-
+import base64
class Module(object):
def __init__(self, bot):
+ bot.events.on("new").on("server").hook(self.on_new_server)
bot.events.on("received").on("numeric").on("001"
).hook(self.on_connect)
bot.events.on("received").on("command").on("setnickserv"
).hook(self.set_nickserv, min_args=1, permission="setnickserv",
help="Set bot's nickserv password", usage="<password>",
private_only=True)
+ bot.events.on("received").on("cap").hook(self.on_cap)
+ bot.events.on("received").on("authenticate").hook(self.on_authenticate)
+ for code in ["902", "903", "904", "905", "906", "907", "908"]:
+ bot.events.on("received").on("numeric").on(code).hook(self.on_90x)
+
+ def on_new_server(self, event):
+ event["server"].attempted_auth = False
+ event["server"].sasl_success = False
def on_connect(self, event):
nickserv_password = event["server"].get_setting(
"nickserv-password")
- if nickserv_password:
+ if nickserv_password and not event["server"].sasl_success:
+ event["server"].attempted_auth = True
event["server"].send_message("nickserv",
"identify %s" % nickserv_password)
@@ -20,3 +30,27 @@ class Module(object):
nickserv_password = event["args"]
event["server"].set_setting("nickserv-password", nickserv_password)
event["stdout"].write("Nickserv password saved")
+
+ def on_cap(self, event):
+ if event["subcommand"] == "NAK":
+ event["server"].send_capability_end()
+ elif event["subcommand"] == "ACK":
+ event["server"].send_authenticate("PLAIN")
+ else:
+ pass
+
+ def on_authenticate(self, event):
+ if event["message"] != "+":
+ event["server"].send_authenticate("*")
+ else:
+ nick = event["server"].original_nickname
+ password = event["server"].get_setting("nickserv-password")
+ event["server"].attempted_auth = True
+ event["server"].send_authenticate(
+ base64.b64encode(("%s\0%s\0%s" % (nick, nick, password)).encode("utf8")).decode("utf8")
+ )
+
+ def on_90x(self, event):
+ if event["number"]=="903":
+ event["server"].sasl_success = True
+ event["server"].send_capability_end()