diff --git a/commands/__init__.py b/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/commands/add.py b/commands/add.py new file mode 100644 index 0000000..b003c10 --- /dev/null +++ b/commands/add.py @@ -0,0 +1,110 @@ +from core.main import * +import core.helper as helper + +class Add: + def __init__(self, register): + register("add", self.add) + + def add(self, addr, authed, data, obj, spl, success, failure, info, incUsage, length): + if authed: + if length > 6: + failure("Too many arguments") + return + + if length > 1: + name = spl[1] + else: + incUsage("add") + return + if length > 2: + host = spl[2] + if length > 3: + port = spl[3] + if length > 4: + protocol = spl[4] + if length > 5: + nickname = spl[5] + + toFail = False + if length < 6: + if config["Default"]["nickname"] == None: + failure("Choose a nickname, or configure one in defaults") + toFail = True + else: + nickname = config["Default"]["nickname"] + + if length < 5: + if config["Default"]["protocol"] == None: + failure("Choose a protocol, or configure one in defaults") + toFail = True + else: + protocol = config["Default"]["protocol"] + + if length < 4: + if config["Default"]["port"] == None: + failure("Choose a port, or configure one in defaults") + toFail = True + else: + port = config["Default"]["port"] + + if length < 3: + if config["Default"]["host"] == None: + failure("Choose a host, or configure one in defaults") + toFail = True + else: + host = config["Default"]["host"] + if toFail: + failure("Stopping due to previous error(s)") + return + + if length < 2: + incUsage("add") + return + + if name in pool.keys(): + failure("Name already exists: %s" % name) + return + + protocol = protocol.lower() + + if not protocol in ["ssl", "plain"]: + failure("Protocol must be ssl or plain, not %s" % protocol) + return + + try: + int(port) + except: + failure("Port must be an integer, not %s" % port) + return + + pool[name] = { "host": host, + "port": port, + "protocol": protocol, + "bind": config["Default"]["bind"], + "timeout": config["Default"]["timeout"], + "maxdelay": config["Default"]["maxdelay"], + "initialdelay": config["Default"]["initialdelay"], + "factor": config["Default"]["factor"], + "jitter": config["Default"]["jitter"], + "nickname": nickname, + "username": config["Default"]["username"], + "realname": config["Default"]["realname"], + "userinfo": config["Default"]["userinfo"], + "finger": config["Default"]["finger"], + "version": config["Default"]["version"], + "source": config["Default"]["source"], + "autojoin": config["Default"]["autojoin"], + "authtype": config["Default"]["authtype"], + "password": config["Default"]["password"], + "authentity": config["Default"]["authentity"], + "key": config["Default"]["key"], + "certificate": config["Default"]["certificate"], + "enabled": config["ConnectOnCreate"], + } + if config["ConnectOnCreate"] == True: + helper.addBot(name) + success("Successfully created bot") + saveConf("pool") + return + else: + incUsage(None) diff --git a/commands/default.py b/commands/default.py new file mode 100644 index 0000000..7e5f2a5 --- /dev/null +++ b/commands/default.py @@ -0,0 +1,80 @@ +from core.main import * + +class Default: + def __init__(self, register): + register("default", self.default) + + def default(self, addr, authed, data, obj, spl, success, failure, info, incUsage, length): + if authed: + toUnset = False + if length == 1: + optionMap = ["Viewing defaults"] + for i in config["Default"].keys(): + optionMap.append("%s: %s" % (i, config["Default"][i])) + info("\n".join(optionMap)) + return + elif length == 2: + if not spl[1] in config["Default"].keys(): + failure("No such key: %s" % spl[1]) + return + info("%s: %s" % (spl[1], config["Default"][spl[1]])) + return + elif length == 3: + if not spl[1] in config["Default"].keys(): + failure("No such key: %s" % spl[1]) + return + + if spl[2].lower() in ["none", "nil"]: + spl[2] = None + toUnset = True + + if spl[1] in ["port", "timeout", "maxdelay"]: + try: + spl[2] = int(spl[2]) + except: + failure("Value must be an integer, not %s" % spl[2]) + return + + if spl[2] in ["initialdelay", "factor", "jitter"]: + try: + spl[3] = float(spl[3]) + except: + failure("Value must be a floating point integer, not %s" % spl[3]) + return + + if spl[1] == "protocol": + if not toUnset: + if not spl[2] in ["ssl", "plain"]: + failure("Protocol must be ssl or plain, not %s" % spl[2]) + return + + if spl[2] == config["Default"][spl[1]]: + failure("Value already exists: %s" % spl[2]) + return + + if spl[1] == "authtype": + if not toUnset: + if not spl[2] in ["sp", "ns"]: + failure("Authtype must be sp or ns, not %s" % spl[2]) + return + if spl[1] == "enabled": + failure("Use the ConnectOnCreate config parameter to set this") + return + if spl[1] == "autojoin": + if not toUnset: + spl[2] = spl[2].split(",") + else: + spl[2] = [] + + config["Default"][spl[1]] = spl[2] + saveConf("config") + if toUnset: + success("Successfully unset key %s" % spl[1]) + else: + success("Successfully set key %s to %s" % (spl[1], spl[2])) + return + else: + incUsage("default") + return + else: + incUsage(None) diff --git a/commands/delete.py b/commands/delete.py new file mode 100644 index 0000000..f47958a --- /dev/null +++ b/commands/delete.py @@ -0,0 +1,29 @@ +from core.main import * + +class Delete: + def __init__(self, register): + register("del", self.delete) + + def delete(self, addr, authed, data, obj, spl, success, failure, info, incUsage, length): + if authed: + if length == 2: + if not spl[1] in pool.keys(): + failure("Name does not exist: %s" % spl[1]) + return + del pool[spl[1]] + if spl[1] in ReactorPool.keys(): + if spl[1] in FactoryPool.keys(): + FactoryPool[spl[1]].stopTrying() + ReactorPool[spl[1]].disconnect() + if spl[1] in IRCPool.keys(): + del IRCPool[spl[1]] + del ReactorPool[spl[1]] + del FactoryPool[spl[1]] + success("Successfully removed bot") + saveConf("pool") + return + else: + incUsage("del") + return + else: + incUsage(None) diff --git a/commands/disable.py b/commands/disable.py new file mode 100644 index 0000000..f5b8b39 --- /dev/null +++ b/commands/disable.py @@ -0,0 +1,29 @@ +from core.main import * + +class Disable: + def __init__(self, register): + register("disable", self.disable) + + def disable(self, addr, authed, data, obj, spl, success, failure, info, incUsage, length): + if authed: + if length == 2: + if not spl[1] in pool.keys(): + failure("Name does not exist: %s" % spl[1]) + return + pool[spl[1]]["enabled"] = False + saveConf("pool") + if spl[1] in ReactorPool.keys(): + if spl[1] in FactoryPool.keys(): + FactoryPool[spl[1]].stopTrying() + ReactorPool[spl[1]].disconnect() + if spl[1] in IRCPool.keys(): + del IRCPool[spl[1]] + del ReactorPool[spl[1]] + del FactoryPool[spl[1]] + success("Successfully disabled bot %s" % spl[1]) + return + else: + incUsage("disable") + return + else: + incUsage(None) diff --git a/commands/dist.py b/commands/dist.py new file mode 100644 index 0000000..0d4c325 --- /dev/null +++ b/commands/dist.py @@ -0,0 +1,20 @@ +from core.main import * +from subprocess import run, PIPE + +class Dist: + def __init__(self, register): + register("dist", self.dist) + + def dist(self, addr, authed, data, obj, spl, success, failure, info, incUsage, length): + if authed: + if config["Dist"]["Enabled"]: + rtrn = run([config["Dist"]["File"]], shell=True, stdout=PIPE) + if config["Dist"]["SendOutput"]: + info("Exit code: %s -- Stdout: %s" % (rtrn.returncode, rtrn.stdout)) + else: + info("Exit code: %s" % rtrn.returncode) + else: + failure("The dist command is not enabled") + return + else: + incUsage(None) diff --git a/commands/enable.py b/commands/enable.py new file mode 100644 index 0000000..43ffba1 --- /dev/null +++ b/commands/enable.py @@ -0,0 +1,26 @@ +from core.main import * +import core.helper as helper + +class Enable: + def __init__(self, register): + register("enable", self.enable) + + def enable(self, addr, authed, data, obj, spl, success, failure, info, incUsage, length): + if authed: + if length == 2: + if not spl[1] in pool.keys(): + failure("Name does not exist: %s" % spl[1]) + return + pool[spl[1]]["enabled"] = True + saveConf("pool") + if not spl[1] in IRCPool.keys(): + helper.addBot(spl[1]) + else: + pass + success("Successfully enabled bot %s" % spl[1]) + return + else: + incUsage("enable") + return + else: + incUsage(None) diff --git a/commands/get.py b/commands/get.py new file mode 100644 index 0000000..1f44169 --- /dev/null +++ b/commands/get.py @@ -0,0 +1,22 @@ +from core.main import * + +class Get: + def __init__(self, register): + register("get", self.get) + + def get(self, addr, authed, data, obj, spl, success, failure, info, incUsage, length): + if authed: + if length == 3: + if not spl[1] in pool.keys(): + failure("Name does not exist: %s" % spl[1]) + return + if not spl[1] in IRCPool.keys(): + failure("Name has no instance: %s" % spl[1]) + return + info(str(IRCPool[spl[1]].get(spl[2]))) + return + else: + incUsage("get") + return + else: + incUsage(None) diff --git a/commands/help.py b/commands/help.py new file mode 100644 index 0000000..e07b828 --- /dev/null +++ b/commands/help.py @@ -0,0 +1,15 @@ +from core.main import * + +class Help: + def __init__(self, register): + register("help", self.help) + + def help(self, addr, authed, data, obj, spl, success, failure, info, incUsage, length): + if authed: + helpMap = [] + for i in help.keys(): + helpMap.append("%s: %s" % (i, help[i])) + info("\n".join(helpMap)) + return + else: + incUsage(None) diff --git a/commands/join.py b/commands/join.py new file mode 100644 index 0000000..b68fcfd --- /dev/null +++ b/commands/join.py @@ -0,0 +1,33 @@ +from core.main import * + +class Join: + def __init__(self, register): + register("join", self.join) + + def join(self, addr, authed, data, obj, spl, success, failure, info, incUsage, length): + if authed: + if length == 3: + if not spl[1] in pool.keys(): + failure("Name does not exist: %s" % spl[1]) + return + if not spl[1] in IRCPool.keys(): + failure("Name has no instance: %s" % spl[1]) + return + IRCPool[spl[1]].join(spl[2]) + success("Joined %s" % spl[2]) + return + elif length == 4: + if not spl[1] in pool.keys(): + failure("Name does not exist: %s" % spl[1]) + return + if not spl[1] in IRCPool.keys(): + failure("Name has no instance: %s" % spl[1]) + return + IRCPool[spl[1]].join(spl[2], spl[3]) + success("Joined %s with key %s" % (spl[2], spl[3])) + return + else: + incUsage("join") + return + else: + incUsage(None) diff --git a/commands/key.py b/commands/key.py new file mode 100644 index 0000000..e523097 --- /dev/null +++ b/commands/key.py @@ -0,0 +1,149 @@ +from core.main import * +import modules.keyword as keyword + +class Key: + def __init__(self, register): + register("key", self.key) + + def key(self, addr, authed, data, obj, spl, success, failure, info, incUsage, length): + if authed: + if data.startswith("key add"): + if not data in ["key add ", "key add"] and data[3] == " ": + keywordsToAdd = data[8:] + keywords = keywordsToAdd.split(",") + for i in keywords: + rtrn = keyword.addKeyword(i) + if rtrn == "EXISTS": + failure("Keyword already exists: %s" % i) + elif rtrn == "ISIN": + failure("Keyword already matched: %s" % i) + elif rtrn == True: + success("Keyword added: %s" % i) + return + else: + incUsage("key") + return + elif data.startswith("key del"): + if not data in ["key del ", "key del"] and data[3] == " ": + keywordsToDel = data[8:] + keywords = keywordsToDel.split(",") + for i in keywords: + rtrn = keyword.delKeyword(i) + if rtrn == "NOKEY": + failure("Keyword does not exist: %s" % i) + elif rtrn == True: + success("Keyword deleted: %s" % i) + return + else: + incUsage("key") + return + if length == 4: + if spl[1] == "except": + if not spl[2] in keyconf["Keywords"]: + failure("No such keyword: %s" % spl[2]) + return + if spl[2] in keyconf["KeywordsExcept"].keys(): + if spl[3] in keyconf["KeywordsExcept"][spl[2]]: + failure("Exception exists: %s" % spl[3]) + return + else: + if not spl[2] in spl[3]: + failure("Keyword %s not in exception %s. This won't work" % (spl[2], spl[3])) + return + keyconf["KeywordsExcept"][spl[2]] = [] + + keyconf["KeywordsExcept"][spl[2]].append(spl[3]) + saveConf("keyconf") + success("Successfully added exception %s for keyword %s" % (spl[3], spl[2])) + return + elif spl[1] == "master": + if not spl[2] in pool.keys(): + failure("Name does not exist: %s" % spl[2]) + return + if spl[2] in IRCPool.keys(): + if not spl[3] in IRCPool[spl[2]].channels: + info("Bot not on channel: %s" % spl[3]) + config["Master"] = [spl[2], spl[3]] + saveConf("config") + success("Master set to %s on %s" % (spl[3], spl[2])) + return + elif spl[1] == "unexcept": + if not spl[2] in keyconf["KeywordsExcept"].keys(): + failure("No such exception: %s" % spl[2]) + return + if not spl[3] in keyconf["KeywordsExcept"][spl[2]]: + failure("Exception %s has no attribute %s" % (spl[2], spl[3])) + return + keyconf["KeywordsExcept"][spl[2]].remove(spl[3]) + if keyconf["KeywordsExcept"][spl[2]] == []: + del keyconf["KeywordsExcept"][spl[2]] + saveConf("keyconf") + success("Successfully removed exception %s for keyword %s" % (spl[3], spl[2])) + return + else: + incUsage("key") + return + elif length == 3: + if spl[1] == "unexcept": + if not spl[2] in keyconf["KeywordsExcept"].keys(): + failure("No such exception: %s" % spl[2]) + return + del keyconf["KeywordsExcept"][spl[2]] + saveConf("keyconf") + success("Successfully removed exception list of %s" % spl[2]) + return + elif spl[1] == "monitor": + if spl[2] == "on": + if not obj.addr in MonitorPool: + MonitorPool.append(obj.addr) + success("Keyword monitoring enabled") + return + else: + failure("Keyword monitoring is already enabled") + return + elif spl[2] == "off": + if obj.addr in MonitorPool: + MonitorPool.remove(obj.addr) + success("Keyword monitoring disabled") + return + else: + failure("Keyword monitoring is already disabled") + return + else: + incUsage("key") + return + else: + incUsage("key") + return + elif length == 2: + if spl[1] == "show": + info(",".join(keyconf["Keywords"])) + return + + elif spl[1] == "showexcept": + exceptMap = [] + for i in keyconf["KeywordsExcept"].keys(): + exceptMap.append("Key: %s" % i) + exceptMap.append("%s: %s" % (i, ",".join(keyconf["KeywordsExcept"][i]))) + exceptMap.append("\n") + info("\n".join(exceptMap)) + return + elif spl[1] == "master": + info(" - ".join(config["Master"])) + return + elif spl[1] == "monitor": + if obj.addr in MonitorPool: + info("Keyword monitoring is enabled on this connection") + return + else: + info("Keyword monitoring is disabled on this connection") + return + else: + incUsage("key") + return + + else: + incUsage("key") + return + else: + incUsage(None) diff --git a/commands/list.py b/commands/list.py new file mode 100644 index 0000000..e39ee95 --- /dev/null +++ b/commands/list.py @@ -0,0 +1,18 @@ +from core.main import * + +class List: + def __init__(self, register): + register("list", self.list) + + def list(self, addr, authed, data, obj, spl, success, failure, info, incUsage, length): + if authed: + poolMap = [] + for i in pool.keys(): + poolMap.append("Server: %s" % i) + for x in pool[i].keys(): + poolMap.append("%s: %s" % (x, pool[i][x])) + poolMap.append("\n") + info("\n".join(poolMap)) + return + else: + incUsage(None) diff --git a/commands/load.py b/commands/load.py new file mode 100644 index 0000000..ccb1428 --- /dev/null +++ b/commands/load.py @@ -0,0 +1,26 @@ +from core.main import * + +class Load: + def __init__(self, register): + register("load", self.load) + + def load(self, addr, authed, data, obj, spl, success, failure, info, incUsage, length): + if authed: + if length == 2: + if spl[1] in filemap.keys(): + loadConf(spl[1]) + success("Loaded %s from %s" % (spl[1], filemap[spl[1]][0])) + return + elif spl[1] == "all": + for i in filemap.keys(): + loadConf(i) + success("Loaded %s from %s" % (i, filemap[i][0])) + return + else: + incUsage("load") + return + else: + incUsage("load") + return + else: + incUsage(None) diff --git a/commands/logout.py b/commands/logout.py new file mode 100644 index 0000000..f95a256 --- /dev/null +++ b/commands/logout.py @@ -0,0 +1,15 @@ +from core.main import * + +class Logout: + def __init__(self, register): + register("logout", self.logout) + + def logout(self, addr, authed, data, obj, spl, success, failure, info, incUsage, length): + if authed: + obj.authed = False + if obj.addr in MonitorPool: + MonitorPool.remove(obj.addr) + success("Logged out") + return + else: + incUsage(None) diff --git a/commands/mod.py b/commands/mod.py new file mode 100644 index 0000000..8ffd9ad --- /dev/null +++ b/commands/mod.py @@ -0,0 +1,89 @@ +from core.main import * + +class Mod: + def __init__(self, register): + register("mod", self.mod) + + def mod(self, addr, authed, data, obj, spl, success, failure, info, incUsage, length): + if authed: + toUnset = False + if length == 2: + if not spl[1] in pool.keys(): + failure("Name does not exist: %s" % spl[1]) + return + optionMap = ["Viewing options for %s" % spl[1]] + for i in pool[spl[1]].keys(): + optionMap.append("%s: %s" % (i, pool[spl[1]][i])) + info("\n".join(optionMap)) + return + + elif length == 3: + if not spl[1] in pool.keys(): + failure("Name does not exist: %s" % spl[1]) + return + if not spl[2] in pool[spl[1]].keys(): + failure("No such key: %s" % spl[2]) + return + info("%s: %s" % (spl[2], pool[spl[1]][spl[2]])) + return + + elif length == 4: + if not spl[1] in pool.keys(): + failure("Name does not exist: %s" % spl[1]) + return + if not spl[2] in pool[spl[1]].keys(): + failure("No such key: %s" % spl[2]) + return + + if spl[2] == "protocol": + if not spl[3] in ["ssl", "plain"]: + failure("Protocol must be ssl or plain, not %s" % spl[3]) + return + if spl[3] == pool[spl[1]][spl[2]]: + failure("Value already exists: %s" % spl[3]) + return + + if spl[3].lower() in ["none", "nil"]: + spl[3] = None + toUnset = True + + if spl[2] in ["port", "timeout", "maxdelay"]: + try: + spl[3] = int(spl[3]) + except: + failure("Value must be an integer, not %s" % spl[3]) + return + + if spl[2] in ["initialdelay", "factor", "jitter"]: + try: + spl[3] = float(spl[3]) + except: + failure("Value must be a floating point integer, not %s" % spl[3]) + return + + if spl[2] == "authtype": + if not toUnset: + if not spl[3] in ["sp", "ns"]: + failure("Authtype must be sp or ns, not %s" % spl[3]) + return + if spl[2] == "enabled": + failure("Use the enable and disable commands to manage this") + return + if spl[2] == "autojoin": + spl[3] = spl[3].split(",") + + pool[spl[1]][spl[2]] = spl[3] + if spl[1] in IRCPool.keys(): + IRCPool[spl[1]].refresh() + saveConf("pool") + if toUnset: + success("Successfully unset key %s on %s" % (spl[2], spl[1])) + else: + success("Successfully set key %s to %s on %s" % (spl[2], spl[3], spl[1])) + return + + else: + incUsage("mod") + return + else: + incUsage(None) diff --git a/commands/part.py b/commands/part.py new file mode 100644 index 0000000..0b145a9 --- /dev/null +++ b/commands/part.py @@ -0,0 +1,23 @@ +from core.main import * + +class Part: + def __init__(self, register): + register("part", self.part) + + def part(self, addr, authed, data, obj, spl, success, failure, info, incUsage, length): + if authed: + if length == 3: + if not spl[1] in pool.keys(): + failure("Name does not exist: %s" % spl[1]) + return + if not spl[1] in IRCPool.keys(): + failure("Name has no instance: %s" % spl[1]) + return + IRCPool[spl[1]].part(spl[2]) + success("Left %s" % spl[2]) + return + else: + incUsage("part") + return + else: + incUsage(None) diff --git a/commands/password.py b/commands/password.py new file mode 100644 index 0000000..b4b449e --- /dev/null +++ b/commands/password.py @@ -0,0 +1,22 @@ +from core.main import * + +class Password: + def __init__(self, register): + register("pass", self.password) + + def password(self, addr, authed, data, obj, spl, success, failure, info, incUsage, length): + if authed: + info("You are already authenticated") + return + else: + if length == 2: + if spl[1] == config["Password"]: + success("Authenticated successfully") + obj.authed = True + return + else: + failure("Password incorrect") + obj.transport.loseConnection() + return + else: + incUsage("pass") diff --git a/commands/save.py b/commands/save.py new file mode 100644 index 0000000..64c4cbf --- /dev/null +++ b/commands/save.py @@ -0,0 +1,26 @@ +from core.main import * + +class Save: + def __init__(self, register): + register("save", self.save) + + def save(self, addr, authed, data, obj, spl, success, failure, info, incUsage, length): + if authed: + if length == 2: + if spl[1] in filemap.keys(): + saveConf(spl[1]) + success("Saved %s to %s" % (spl[1], filemap[spl[1]][0])) + return + elif spl[1] == "all": + for i in filemap.keys(): + saveConf(i) + success("Saved %s from %s" % (i, filemap[i][0])) + return + else: + incUsage("save") + return + else: + incUsage("save") + return + else: + incUsage(None) diff --git a/commands/stats.py b/commands/stats.py new file mode 100644 index 0000000..9b1f76f --- /dev/null +++ b/commands/stats.py @@ -0,0 +1,49 @@ +from core.main import * + +class Stats: + def __init__(self, register): + register("stats", self.stats) + + def stats(self, addr, authed, data, obj, spl, success, failure, info, incUsage, length): + if authed: + if length == 1: + stats = [] + numChannels = 0 + numWhoEntries = 0 + for i in IRCPool.keys(): + numChannels += len(IRCPool[i].channels) + for i in wholist.keys(): + numWhoEntries += len(wholist[i].keys()) + stats.append("Servers: %s" % len(IRCPool.keys())) + stats.append("Channels: %s" % numChannels) + stats.append("User records: %s" % numWhoEntries) + info("\n".join(stats)) + return + + elif length == 2: + stats = [] + numChannels = 0 + numWhoEntries = 0 + + failures = 0 + if spl[1] in IRCPool.keys(): + numChannels += len(IRCPool[spl[1]].channels) + else: + failure("Name does not exist: %s" % spl[1]) + failures += 1 + if spl[1] in wholist.keys(): + numWhoEntries += len(wholist[spl[1]].keys()) + else: + failure("Who entry does not exist: %s" % spl[1]) + failures += 1 + if failures == 2: + failure("No information found, aborting") + return + stats.append("Channels: %s" % numChannels) + stats.append("User records: %s" % numWhoEntries) + info("\n".join(stats)) + return + else: + incUsage("stats") + else: + incUsage(None) diff --git a/commands/who.py b/commands/who.py new file mode 100644 index 0000000..9a8256b --- /dev/null +++ b/commands/who.py @@ -0,0 +1,26 @@ +from core.main import * +import modules.userinfo as userinfo + +class Who: + def __init__(self, register): + register("who", self.who) + + def who(self, addr, authed, data, obj, spl, success, failure, info, incUsage, length): + if authed: + if length == 2: + result = userinfo.getWho(spl[1]) + rtrn = "" + for i in result.keys(): + rtrn += "Matches from: %s" % i + rtrn += "\n" + for x in result[i]: + x = [y for y in x if not y == None] + rtrn += str((", ".join(x))) + rtrn += "\n" + info(rtrn) + return + else: + incUsage("who") + return + else: + incUsage(None) diff --git a/core/bot.py b/core/bot.py new file mode 100644 index 0000000..ec0894c --- /dev/null +++ b/core/bot.py @@ -0,0 +1,240 @@ +from twisted.internet.protocol import ReconnectingClientFactory +from twisted.words.protocols.irc import IRCClient +from twisted.internet.defer import Deferred + +import modules.keyword as keyword +import modules.userinfo as userinfo + +from core.main import * +from utils.logging.log import * +from utils.logging.send import * + +class IRCBot(IRCClient): + def __init__(self, name): + self.connected = False + self.channels = [] + + self.name = name + instance = pool[name] + + self.nickname = instance["nickname"] + self.realname = instance["realname"] + self.username = instance["username"] + self.userinfo = instance["userinfo"] + self.fingerReply = instance["finger"] + self.versionName = instance["version"] + self.versionNum = None + self.versionEnv = None + self.sourceURL = instance["source"] + self.autojoin = instance["autojoin"] + + self._who = {} + self._getWho = {} + + self.authtype = instance["authtype"] + if self.authtype == "ns": + self.authpass = instance["password"] + self.authentity = instance["authentity"] + else: + self.password = instance["password"] + + def refresh(self): + instance = pool[self.name] + if not instance["nickname"] == self.nickname: + self.nickname = instance["nickname"] + self.setNick(self.nickname) + + self.userinfo = instance["userinfo"] + self.fingerReply = instance["finger"] + self.versionName = instance["version"] + self.versionNum = None + self.versionEnv = None + self.sourceURL = instance["source"] + + def parsen(self, user): + step = user.split("!") + nick = step[0] + if len(step) == 2: + step2 = step[1].split("@") + ident, host = step2 + else: + ident = nick + host = nick + + return [nick, ident, host] + + def privmsg(self, user, channel, msg): + nick, ident, host = self.parsen(user) + userinfo.setWhoSingle(self.name, nick, ident, host) + + keyword.actKeyword(user, channel, msg, self.nickname, "PRV", self.name) + + def noticed(self, user, channel, msg): + nick, ident, host = self.parsen(user) + userinfo.setWhoSingle(self.name, nick, ident, host) + + keyword.actKeyword(user, channel, msg, self.nickname, "NOT", self.name) + + def action(self, user, channel, msg): + nick, ident, host = self.parsen(user) + userinfo.setWhoSingle(self.name, nick, ident, host) + + keyword.actKeyword(user, channel, msg, self.nickname, "ACT", self.name) + + def get(self, var): + try: + result = getattr(self, var) + except AttributeError: + result = None + return result + + def setNick(self, nickname): + self._attemptedNick = nickname + self.sendLine("NICK %s" % nickname) + self.nickname = nickname + + def alterCollidedNick(self, nickname): + newnick = nickname + "_" + return newnick + + def irc_ERR_NICKNAMEINUSE(self, prefix, params): + self._attemptedNick = self.alterCollidedNick(self._attemptedNick) + self.setNick(self._attemptedNick) + + def irc_ERR_PASSWDMISMATCH(self, prefix, params): + log("%s: password mismatch" % self.name) + sendAll("%s: password mismatch" % self.name) + + def who(self, channel): + channel = channel + d = Deferred() + if channel not in self._who: + self._who[channel] = ([], {}) + self._who[channel][0].append(d) + self.sendLine("WHO %s" % channel) + return d + + def irc_RPL_WHOREPLY(self, prefix, params): + channel = params[1] + user = params[2] + host = params[3] + server = params[4] + nick = params[5] + status = params[6] + realname = params[7] + if channel not in self._who: + return + n = self._who[channel][1] + n[nick] = [nick, user, host, server, status, realname] + + def irc_RPL_ENDOFWHO(self, prefix, params): + channel = params[1] + if channel not in self._who: + return + callbacks, info = self._who[channel] + for cb in callbacks: + cb.callback((channel, info)) + del self._who[channel] + + def got_who(self, whoinfo): + userinfo.setWho(self.name, whoinfo[1]) + + def signedOn(self): + self.connected = True + log("signed on: %s" % self.name) + if config["ConnectionNotifications"]: + keyword.sendMaster("SIGNON: %s" % self.name) + if self.authtype == "ns": + self.msg(self.authentity, "IDENTIFY %s" % self.nspass) + for i in self.autojoin: + self.join(i) + + def joined(self, channel): + if not channel in self.channels: + self.channels.append(channel) + self.who(channel).addCallback(self.got_who) + + def left(self, channel): + if channel in self.channels: + self.channels.remove(channel) + + def kickedFrom(self, channel, kicker, message): + if channel in self.channels: + self.channels.remove(channel) + keyword.sendMaster("KICK %s: (%s/%s) %s" % (self.name, kicker, channel, message)) + + def userJoined(self, user, channel): + nick, ident, host = self.parsen(user) + userinfo.setWhoSingle(self.name, nick, ident, host) + + def userLeft(self, user, channel): + nick, ident, host = self.parsen(user) + userinfo.setWhoSingle(self.name, nick, ident, host) + + def userQuit(self, user, quitMessage): + nick, ident, host = self.parsen(user) + userinfo.setWhoSingle(self.name, nick, ident, host) + + keyword.actKeyword(user, None, quitMessage, self.nickname, "QUT", self.name) + + def userKicked(self, kickee, channel, kicker, message): + nick, ident, host = self.parsen(kicker) + userinfo.setWhoSingle(self.name, nick, ident, host) + + keyword.actKeyword(kicker, channel, message, self.nickname, "KCK", self.name) + + def userRenamed(self, oldname, newname): + nick, ident, host = self.parsen(oldname) + userinfo.setWhoSingle(self.name, nick, ident, host) + userinfo.setWhoSingle(self.name, newname, ident, host) + + def topicUpdated(self, user, channel, newTopic): + nick, ident, host = self.parsen(user) + userinfo.setWhoSingle(self.name, nick, ident, host) + + keyword.actKeyword(user, channel, newTopic, self.nickname, "TOP", self.name) + + def modeChanged(self, user, channel, toset, modes, args): + nick, ident, host = self.parsen(user) + userinfo.setWhoSingle(self.name, nick, ident, host) + +class IRCBotFactory(ReconnectingClientFactory): + def __init__(self, name): + self.instance = pool[name] + self.name = name + self.client = None + self.maxDelay = self.instance["maxdelay"] + self.initialDelay = self.instance["initialdelay"] + self.factor = self.instance["factor"] + self.jitter = self.instance["jitter"] + + def buildProtocol(self, addr): + entry = IRCBot(self.name) + IRCPool[self.name] = entry + self.client = entry + return entry + + def clientConnectionLost(self, connector, reason): + if not self.client == None: + self.client.connected = False + self.client.channels = [] + error = reason.getErrorMessage() + log("%s: connection lost: %s" % (self.name, error)) + sendAll("%s: connection lost: %s" % (self.name, error)) + if config["ConnectionNotifications"]: + keyword.sendMaster("CONNLOST %s: %s" % (self.name, error)) + self.retry(connector) + #ReconnectingClientFactory.clientConnectionLost(self, connector, reason) + + def clientConnectionFailed(self, connector, reason): + if not self.client == None: + self.client.connected = False + self.client.channels = [] + error = reason.getErrorMessage() + log("%s: connection failed: %s" % (self.name, error)) + sendAll("%s: connection failed: %s" % (self.name, error)) + if config["ConnectionNotifications"]: + keyword.sendMaster("CONNFAIL %s: %s" % (self.name, error)) + self.retry(connector) + #ReconnectingClientFactory.clientConnectionFailed(self, connector, reason) + diff --git a/core/helper.py b/core/helper.py new file mode 100644 index 0000000..06a4c80 --- /dev/null +++ b/core/helper.py @@ -0,0 +1,44 @@ +from twisted.internet import reactor +from twisted.internet.ssl import DefaultOpenSSLContextFactory + +from core.bot import IRCBot, IRCBotFactory +from core.main import * +from utils.logging.log import * + +def addBot(name): + instance = pool[name] + log("Started bot %s to %s:%s protocol %s nickname %s" % (name, instance["host"], instance["port"], instance["protocol"], instance["nickname"])) + if instance["protocol"] == "plain": + if instance["bind"] == None: + bot = IRCBotFactory(name) + rct = reactor.connectTCP(instance["host"], instance["port"], bot, timeout=int(instance["timeout"])) + + ReactorPool[name] = rct + FactoryPool[name] = bot + return + else: + bot = IRCBotFactory(name) + rct = reactor.connectTCP(instance["host"], instance["port"], bot, timeout=int(instance["timeout"]), bindAddress=instance["bind"]) + + ReactorPool[name] = rct + FactoryPool[name] = bot + return + elif instance["protocol"] == "ssl": + keyFN = certPath+instance["key"] + certFN = certPath+instance["certificate"] + contextFactory = DefaultOpenSSLContextFactory(keyFN.encode("utf-8", "replace"), certFN.encode("utf-8", "replace")) + if instance["bind"] == None: + bot = IRCBotFactory(name) + rct = reactor.connectSSL(instance["host"], int(instance["port"]), bot, contextFactory) + + ReactorPool[name] = rct + FactoryPool[name] = bot + return + else: + + bot = IRCBotFactory(name) + rct = reactor.connectSSL(instance["host"], int(instance["port"]), bot, contextFactory, bindAddress=instance["bind"]) + + ReactorPool[name] = rct + FactoryPool[name] = bot + return diff --git a/core/main.py b/core/main.py new file mode 100644 index 0000000..c732c09 --- /dev/null +++ b/core/main.py @@ -0,0 +1,50 @@ +from json import load, dump, loads +from utils.loaders.command_loader import loadCommands +from utils.logging.log import * + +configPath = "conf/" +certPath = "cert/" + +filemap = { + "config": ["config.json", "configuration"], + "keyconf": ["keyword.json", "keyword lists"], + "pool": ["pool.json", "pool"], + "help": ["help.json", "command help"], + "wholist": ["wholist.json", "WHO lists"], +} + +numbers = "0123456789" + +listener = None +connections = {} +IRCPool = {} +ReactorPool = {} +FactoryPool = {} + +MonitorPool = [] + +CommandMap = {} + +def register(command, function): + if not command in CommandMap: + CommandMap[command] = function + log("Registering command %s" % command) + else: + error("Duplicate command: %s" % (command)) + +def saveConf(var): + with open(configPath+filemap[var][0], "w") as f: + dump(globals()[var], f, indent=4) + return + +def loadConf(var): + with open(configPath+filemap[var][0], "r") as f: + globals()[var] = load(f) + +def initConf(): + for i in filemap.keys(): + loadConf(i) + +def initMain(): + initConf() + loadCommands(register) diff --git a/core/parser.py b/core/parser.py new file mode 100644 index 0000000..2845829 --- /dev/null +++ b/core/parser.py @@ -0,0 +1,30 @@ +from core.main import * +from utils.logging.log import * +from utils.logging.send import * + +def parseCommand(addr, authed, data): + #call command modules with: (addr, authed, data, spl, success, failure, info, incUsage, length) + spl = data.split() + if addr in connections.keys(): + obj = connections[addr] + else: + warn("Got connection object with no instance in the address pool") + return + + success = lambda data: sendSuccess(addr, data) + failure = lambda data: sendFailure(addr, data) + info = lambda data: sendInfo(addr, data) + + incUsage = lambda mode: incorrectUsage(addr, mode) + length = len(spl) + if len(spl) > 0: + cmd = spl[0] + else: + failure("No text was sent") + return + for i in CommandMap.keys(): + if data.startswith(i): + CommandMap[i](addr, authed, data, obj, spl, success, failure, info, incUsage, length) + return + incUsage(None) + return diff --git a/modules/keyword.py b/modules/keyword.py new file mode 100644 index 0000000..43547f6 --- /dev/null +++ b/modules/keyword.py @@ -0,0 +1,74 @@ +from core.main import * +from utils.logging.log import * + +def sendMaster(data): + if config["Master"][0] in IRCPool.keys(): + IRCPool[config["Master"][0]].msg(config["Master"][1], data) + else: + warn("Master with no IRC instance defined") + for i in MonitorPool: + connections[i].send(data) + +def isKeyword(msg): + message = msg.lower() + messageDuplicate = message + toUndo = False + uniqueNum = 0 + totalNum = 0 + for i in keyconf["Keywords"]: + if i in message: + if i in keyconf["KeywordsExcept"].keys(): + for x in keyconf["KeywordsExcept"][i]: + if x in message: + toUndo = True + messageDuplicate = messageDuplicate.replace(x, "\0\r\n\n\0") + for y in keyconf["Keywords"]: + if i in messageDuplicate: + totalNum += messageDuplicate.count(i) + message = messageDuplicate.replace(i, "{"+i+"}") + message = message.replace("\0\r\n\n\0", x) + uniqueNum += 1 + + if toUndo == False: + totalNum += message.count(i) + message = message.replace(i, "{"+i+"}") + uniqueNum += 1 + + toUndo = False + + if totalNum == 0: + return False + else: + return [message, uniqueNum, totalNum] + +def actKeyword(user, channel, message, nickname, actType, name): + toSend = isKeyword(message) + if name == config["Master"][0] and channel == config["Master"][1]: + pass + else: + if config["HighlightNotifications"]: + msgLower = message.lower() + nickLower = nickname.lower() + if nickLower in msgLower: + msgLower = msgLower.replace(nickLower, "{"+nickLower+"}") + sendMaster("NICK %s %s (T:%s): (%s/%s) %s" % (actType, name, msgLower.count(nickLower), user, channel, msgLower)) + if toSend: + sendMaster("MATCH %s %s (U:%s T:%s): (%s/%s) %s" % (actType, name, toSend[1], toSend[2], user, channel, toSend[0])) + +def addKeyword(keyword): + if keyword in keyconf["Keywords"]: + return "EXISTS" + else: + for i in keyconf["Keywords"]: + if i in keyword or keyword in i: + return "ISIN" + keyconf["Keywords"].append(keyword) + saveConf("keyconf") + return True + +def delKeyword(keyword): + if not keyword in keyconf["Keywords"]: + return "NOKEY" + keyconf["Keywords"].remove(keyword) + saveConf("keyconf") + return True diff --git a/modules/userinfo.py b/modules/userinfo.py new file mode 100644 index 0000000..f0403fe --- /dev/null +++ b/modules/userinfo.py @@ -0,0 +1,31 @@ +from core.main import * +#from utils.logging.log import * + +def setWho(network, newObjects): + network = "".join([x for x in network if not x in numbers]) + if not network in wholist.keys(): + wholist[network] = {} + for i in newObjects.keys(): + wholist[network][i] = newObjects[i] + + return + +def setWhoSingle(network, nick, ident, host): + network = "".join([x for x in network if not x in numbers]) + + if network in wholist.keys(): + if nick in wholist[network].keys(): + wholist[network][nick][1] = ident + wholist[network][nick][2] = host + else: + wholist[network][nick] = [nick, ident, host, None, None, None] + +def getWho(nick): + result = {} + for i in wholist.keys(): + for x in wholist[i].keys(): + if nick.lower() == x.lower(): + if not i in result.keys(): + result[i] = [] + result[i].append(wholist[i][x]) + return result diff --git a/threshold b/threshold index bc41686..9524f5a 100755 --- a/threshold +++ b/threshold @@ -1,283 +1,40 @@ #!/usr/bin/env python from twisted.internet import reactor -from twisted.internet.defer import Deferred from twisted.internet.ssl import DefaultOpenSSLContextFactory -from twisted.internet.protocol import Protocol, Factory, ClientFactory, ReconnectingClientFactory -from twisted.words.protocols.irc import IRCClient +from twisted.internet.protocol import Protocol, Factory, ClientFactory #from twisted.python import log #from sys import stdout #log.startLogging(stdout) -from json import load, dump, loads -from sys import exit -from subprocess import run, PIPE +from core.main import ( + numbers, + configPath, + certPath, + listener, + connections, + IRCPool, + ReactorPool, + FactoryPool, + MonitorPool, + saveConf, + loadConf, + initMain, +) +initMain() + +from core.main import ( + config, + keyconf, + pool, + help, + wholist, +) from utils.logging.log import * - -numbers = "0123456789" - -listener = None -connections = {} -IRCPool = {} -ReactorPool = {} -FactoryPool = {} - -MonitorPool = [] - -configPath = "conf/" -certPath = "cert/" - -filemap = { - "config": ["config.json", "configuration"], - "keyconf": ["keyword.json", "keyword lists"], - "pool": ["pool.json", "pool"], - "help": ["help.json", "command help"], - "wholist": ["wholist.json", "WHO lists"], -} - -def sendData(addr, data): - connections[addr].send(data) - -def sendSuccess(addr, data): - sendData(addr, "[y] " + data) - -def sendFailure(addr, data): - sendData(addr, "[n] " + data) - -def sendInfo(addr, data): - sendData(addr, "[i] " + data) - -class IRCBot(IRCClient): - def __init__(self, name): - self.connected = False - self.channels = [] - - self.name = name - instance = pool[name] - - self.nickname = instance["nickname"] - self.realname = instance["realname"] - self.username = instance["username"] - self.userinfo = instance["userinfo"] - self.fingerReply = instance["finger"] - self.versionName = instance["version"] - self.versionNum = None - self.versionEnv = None - self.sourceURL = instance["source"] - self.autojoin = instance["autojoin"] - - self._who = {} - self._getWho = {} - - self.authtype = instance["authtype"] - if self.authtype == "ns": - self.authpass = instance["password"] - self.authentity = instance["authentity"] - else: - self.password = instance["password"] - - def refresh(self): - instance = pool[self.name] - if not instance["nickname"] == self.nickname: - self.nickname = instance["nickname"] - self.setNick(self.nickname) - - self.userinfo = instance["userinfo"] - self.fingerReply = instance["finger"] - self.versionName = instance["version"] - self.versionNum = None - self.versionEnv = None - self.sourceURL = instance["source"] - - def parsen(self, user): - step = user.split("!") - nick = step[0] - if len(step) == 2: - step2 = step[1].split("@") - ident, host = step2 - else: - ident = nick - host = nick - - return [nick, ident, host] - - def privmsg(self, user, channel, msg): - nick, ident, host = self.parsen(user) - helper.setWhoSingle(self.name, nick, ident, host) - - helper.actKeyword(user, channel, msg, self.nickname, "PRV", self.name) - - def noticed(self, user, channel, msg): - nick, ident, host = self.parsen(user) - helper.setWhoSingle(self.name, nick, ident, host) - - helper.actKeyword(user, channel, msg, self.nickname, "NOT", self.name) - - def action(self, user, channel, msg): - nick, ident, host = self.parsen(user) - helper.setWhoSingle(self.name, nick, ident, host) - - helper.actKeyword(user, channel, msg, self.nickname, "ACT", self.name) - - def get(self, var): - try: - result = getattr(self, var) - except AttributeError: - result = None - return result - - def setNick(self, nickname): - self._attemptedNick = nickname - self.sendLine("NICK %s" % nickname) - self.nickname = nickname - - def alterCollidedNick(self, nickname): - newnick = nickname + "_" - return newnick - - def irc_ERR_NICKNAMEINUSE(self, prefix, params): - self._attemptedNick = self.alterCollidedNick(self._attemptedNick) - self.setNick(self._attemptedNick) - - def irc_ERR_PASSWDMISMATCH(self, prefix, params): - log("%s: password mismatch" % self.name) - helper.sendAll("%s: password mismatch" % self.name) - - def who(self, channel): - channel = channel - d = Deferred() - if channel not in self._who: - self._who[channel] = ([], {}) - self._who[channel][0].append(d) - self.sendLine("WHO %s" % channel) - return d - - def irc_RPL_WHOREPLY(self, prefix, params): - channel = params[1] - user = params[2] - host = params[3] - server = params[4] - nick = params[5] - status = params[6] - realname = params[7] - if channel not in self._who: - return - n = self._who[channel][1] - n[nick] = [nick, user, host, server, status, realname] - - def irc_RPL_ENDOFWHO(self, prefix, params): - channel = params[1] - if channel not in self._who: - return - callbacks, info = self._who[channel] - for cb in callbacks: - cb.callback((channel, info)) - del self._who[channel] - - def got_who(self, whoinfo): - global wholist - helper.setWho(self.name, whoinfo[1]) - - def signedOn(self): - self.connected = True - log("signed on: %s" % self.name) - if config["ConnectionNotifications"]: - helper.sendMaster("SIGNON: %s" % self.name) - if self.authtype == "ns": - self.msg(self.authentity, "IDENTIFY %s" % self.nspass) - for i in self.autojoin: - self.join(i) - - def joined(self, channel): - if not channel in self.channels: - self.channels.append(channel) - self.who(channel).addCallback(self.got_who) - - def left(self, channel): - if channel in self.channels: - self.channels.remove(channel) - - def kickedFrom(self, channel, kicker, message): - if channel in self.channels: - self.channels.remove(channel) - helper.sendMaster("KICK %s: (%s/%s) %s" % (self.name, kicker, channel, message)) - - def userJoined(self, user, channel): - nick, ident, host = self.parsen(user) - helper.setWhoSingle(self.name, nick, ident, host) - - def userLeft(self, user, channel): - nick, ident, host = self.parsen(user) - helper.setWhoSingle(self.name, nick, ident, host) - - def userQuit(self, user, quitMessage): - nick, ident, host = self.parsen(user) - helper.setWhoSingle(self.name, nick, ident, host) - - helper.actKeyword(user, None, quitMessage, self.nickname, "QUT", self.name) - - def userKicked(self, kickee, channel, kicker, message): - nick, ident, host = self.parsen(kicker) - helper.setWhoSingle(self.name, nick, ident, host) - - helper.actKeyword(kicker, channel, message, self.nickname, "KCK", self.name) - - def userRenamed(self, oldname, newname): - nick, ident, host = self.parsen(oldname) - helper.setWhoSingle(self.name, nick, ident, host) - helper.setWhoSingle(self.name, newname, ident, host) - - def topicUpdated(self, user, channel, newTopic): - nick, ident, host = self.parsen(user) - helper.setWhoSingle(self.name, nick, ident, host) - - helper.actKeyword(user, channel, newTopic, self.nickname, "TOP", self.name) - - def modeChanged(self, user, channel, toset, modes, args): - nick, ident, host = self.parsen(user) - helper.setWhoSingle(self.name, nick, ident, host) - -class IRCBotFactory(ReconnectingClientFactory): - def __init__(self, name): - self.instance = pool[name] - self.name = name - self.client = None - self.maxDelay = self.instance["maxdelay"] - self.initialDelay = self.instance["initialdelay"] - self.factor = self.instance["factor"] - self.jitter = self.instance["jitter"] - - def buildProtocol(self, addr): - global IRCPool - entry = IRCBot(self.name) - IRCPool[self.name] = entry - self.client = entry - return entry - - def clientConnectionLost(self, connector, reason): - if not self.client == None: - self.client.connected = False - self.client.channels = [] - error = reason.getErrorMessage() - log("%s: connection lost: %s" % (self.name, error)) - helper.sendAll("%s: connection lost: %s" % (self.name, error)) - if config["ConnectionNotifications"]: - helper.sendMaster("CONNLOST %s: %s" % (self.name, error)) - self.retry(connector) - #ReconnectingClientFactory.clientConnectionLost(self, connector, reason) - - def clientConnectionFailed(self, connector, reason): - if not self.client == None: - self.client.connected = False - self.client.channels = [] - error = reason.getErrorMessage() - log("%s: connection failed: %s" % (self.name, error)) - helper.sendAll("%s: connection failed: %s" % (self.name, error)) - if config["ConnectionNotifications"]: - helper.sendMaster("CONNFAIL %s: %s" % (self.name, error)) - self.retry(connector) - #ReconnectingClientFactory.clientConnectionFailed(self, connector, reason) +import modules.userinfo as userinfo +import core.helper as helper +from core.parser import parseCommand class Base(Protocol): def __init__(self, addr): @@ -298,16 +55,15 @@ class Base(Protocol): splitData = [x for x in data.split("\n") if x] if "\n" in data: for i in splitData: - helper.parseCommand(self.addr, self.authed, i) + parseCommand(self.addr, self.authed, i) return - helper.parseCommand(self.addr, self.authed, data) + parseCommand(self.addr, self.authed, data) def connectionMade(self): log("Connection from %s:%s" % (self.addr.host, self.addr.port)) self.send("Hello.") def connectionLost(self, reason): - global connections, MonitorPool self.authed = False log("Connection lost from %s:%s -- %s" % (self.addr.host, self.addr.port, reason.getErrorMessage())) if not listener == None: @@ -322,867 +78,18 @@ class Base(Protocol): class BaseFactory(Factory): def buildProtocol(self, addr): - global connections entry = Base(addr) connections[addr] = entry return entry def send(self, addr, data): - global connections if addr in connections.keys(): connection = connections[addr] connection.send(data) else: return -class Helper(object): - def setWho(self, network, newObjects): - global wholist - network = "".join([x for x in network if not x in numbers]) - if not network in wholist.keys(): - wholist[network] = {} - for i in newObjects.keys(): - wholist[network][i] = newObjects[i] - - return - - def setWhoSingle(self, network, nick, ident, host): - global wholist - network = "".join([x for x in network if not x in numbers]) - - if network in wholist.keys(): - if nick in wholist[network].keys(): - wholist[network][nick][1] = ident - wholist[network][nick][2] = host - else: - wholist[network][nick] = [nick, ident, host, None, None, None] - - def getWho(self, nick): - result = {} - for i in wholist.keys(): - for x in wholist[i].keys(): - if nick.lower() == x.lower(): - if not i in result.keys(): - result[i] = [] - result[i].append(wholist[i][x]) - return result - - def save(self, var): - with open(configPath+filemap[var][0], "w") as f: - dump(globals()[var], f, indent=4) - return - - def load(self, var): - with open(configPath+filemap[var][0], "r") as f: - globals()[var] = load(f) - - def incorrectUsage(self, addr, mode): - if mode == None: - sendFailure(addr, "Incorrect usage") - return - if mode in help.keys(): - sendFailure(addr, "Usage: " + help[mode]) - return - - def sendAll(self, data): - global connections - for i in connections: - connections[i].send(data) - return - - def sendMaster(self, data): - if config["Master"][0] in IRCPool.keys(): - IRCPool[config["Master"][0]].msg(config["Master"][1], data) - else: - warn("Master with no IRC instance defined") - for i in MonitorPool: - connections[i].send(data) - - def isKeyword(self, msg): - message = msg.lower() - messageDuplicate = message - toUndo = False - uniqueNum = 0 - totalNum = 0 - for i in keyconf["Keywords"]: - if i in message: - if i in keyconf["KeywordsExcept"].keys(): - for x in keyconf["KeywordsExcept"][i]: - if x in message: - toUndo = True - messageDuplicate = messageDuplicate.replace(x, "\0\r\n\n\0") - for y in keyconf["Keywords"]: - if i in messageDuplicate: - totalNum += messageDuplicate.count(i) - message = messageDuplicate.replace(i, "{"+i+"}") - message = message.replace("\0\r\n\n\0", x) - uniqueNum += 1 - - if toUndo == False: - totalNum += message.count(i) - message = message.replace(i, "{"+i+"}") - uniqueNum += 1 - - toUndo = False - - if totalNum == 0: - return False - else: - return [message, uniqueNum, totalNum] - - def actKeyword(self, user, channel, message, nickname, actType, name): - toSend = self.isKeyword(message) - if name == config["Master"][0] and channel == config["Master"][1]: - pass - else: - if config["HighlightNotifications"]: - msgLower = message.lower() - nickLower = nickname.lower() - if nickLower in msgLower: - msgLower = msgLower.replace(nickLower, "{"+nickLower+"}") - self.sendMaster("NICK %s %s (T:%s): (%s/%s) %s" % (actType, name, msgLower.count(nickLower), user, channel, msgLower)) - if toSend: - helper.sendMaster("MATCH %s %s (U:%s T:%s): (%s/%s) %s" % (actType, name, toSend[1], toSend[2], user, channel, toSend[0])) - - def addBot(self, name): - global IRCPool, certPath - instance = pool[name] - log("Started bot %s to %s:%s protocol %s nickname %s" % (name, instance["host"], instance["port"], instance["protocol"], instance["nickname"])) - if instance["protocol"] == "plain": - if instance["bind"] == None: - bot = IRCBotFactory(name) - rct = reactor.connectTCP(instance["host"], instance["port"], bot, timeout=int(instance["timeout"])) - - ReactorPool[name] = rct - FactoryPool[name] = bot - return - else: - bot = IRCBotFactory(name) - rct = reactor.connectTCP(instance["host"], instance["port"], bot, timeout=int(instance["timeout"]), bindAddress=instance["bind"]) - - ReactorPool[name] = rct - FactoryPool[name] = bot - return - elif instance["protocol"] == "ssl": - keyFN = certPath+instance["key"] - certFN = certPath+instance["certificate"] - contextFactory = DefaultOpenSSLContextFactory(keyFN.encode("utf-8", "replace"), certFN.encode("utf-8", "replace")) - if instance["bind"] == None: - bot = IRCBotFactory(name) - rct = reactor.connectSSL(instance["host"], int(instance["port"]), bot, contextFactory) - - ReactorPool[name] = rct - FactoryPool[name] = bot - return - else: - - bot = IRCBotFactory(name) - rct = reactor.connectSSL(instance["host"], int(instance["port"]), bot, contextFactory, bindAddress=instance["bind"]) - - ReactorPool[name] = rct - FactoryPool[name] = bot - return - - def addKeyword(self, keyword): - if keyword in keyconf["Keywords"]: - return "EXISTS" - else: - for i in keyconf["Keywords"]: - if i in keyword or keyword in i: - return "ISIN" - keyconf["Keywords"].append(keyword) - self.save("keyconf") - return True - - def delKeyword(self, keyword): - if not keyword in keyconf["Keywords"]: - return "NOKEY" - keyconf["Keywords"].remove(keyword) - self.save("keyconf") - return True - - def parseCommand(self, addr, authed, data): - global pool, MonitorPool, wholist - spl = data.split() - if addr in connections.keys(): - obj = connections[addr] - else: - warn("Got connection object with no instance in the address pool") - return - - success = lambda data: sendSuccess(addr, data) - failure = lambda data: sendFailure(addr, data) - info = lambda data: sendInfo(addr, data) - - incUsage = lambda mode: self.incorrectUsage(addr, mode) - length = len(spl) - if len(spl) > 0: - cmd = spl[0] - else: - failure("No text was sent") - return - - if authed == True: - if cmd == "help": - helpMap = [] - for i in help.keys(): - helpMap.append("%s: %s" % (i, help[i])) - info("\n".join(helpMap)) - return - - elif cmd == "save": - if length == 2: - if spl[1] in filemap.keys(): - self.save(spl[1]) - success("Saved %s to %s" % (spl[1], filemap[spl[1]][0])) - return - elif spl[1] == "all": - for i in filemap.keys(): - helper.save(i) - success("Saved %s from %s" % (i, filemap[i][0])) - return - else: - incUsage("save") - return - else: - incUsage("save") - return - - elif cmd == "load": - if length == 2: - if spl[1] in filemap.keys(): - self.load(spl[1]) - success("Loaded %s from %s" % (spl[1], filemap[spl[1]][0])) - return - elif spl[1] == "all": - for i in filemap.keys(): - helper.load(i) - success("Loaded %s from %s" % (i, filemap[i][0])) - return - else: - incUsage("load") - return - else: - incUsage("load") - return - - elif cmd == "dist": - if config["Dist"]["Enabled"]: - rtrn = run([config["Dist"]["File"]], shell=True, stdout=PIPE) - if config["Dist"]["SendOutput"]: - info("Exit code: %s -- Stdout: %s" % (rtrn.returncode, rtrn.stdout)) - else: - info("Exit code: %s" % rtrn.returncode) - else: - failure("The dist command is not enabled") - return - - elif cmd == "pass": - info("You are already authenticated") - return - - elif cmd == "logout": - obj.authed = False - success("Logged out") - return - - elif cmd == "list": - poolMap = [] - for i in pool.keys(): - poolMap.append("Server: %s" % i) - for x in pool[i].keys(): - poolMap.append("%s: %s" % (x, pool[i][x])) - poolMap.append("\n") - info("\n".join(poolMap)) - return - - elif cmd == "stats": - if length == 1: - stats = [] - numChannels = 0 - numWhoEntries = 0 - for i in IRCPool.keys(): - numChannels += len(IRCPool[i].channels) - for i in wholist.keys(): - numWhoEntries += len(wholist[i].keys()) - stats.append("Servers: %s" % len(IRCPool.keys())) - stats.append("Channels: %s" % numChannels) - stats.append("User records: %s" % numWhoEntries) - info("\n".join(stats)) - return - - elif length == 2: - stats = [] - numChannels = 0 - numWhoEntries = 0 - - failures = 0 - if spl[1] in IRCPool.keys(): - numChannels += len(IRCPool[spl[1]].channels) - else: - failure("Name does not exist: %s" % spl[1]) - failures += 1 - if spl[1] in wholist.keys(): - numWhoEntries += len(wholist[spl[1]].keys()) - else: - failure("Who entry does not exist: %s" % spl[1]) - failures += 1 - if failures == 2: - failure("No information found, aborting") - return - stats.append("Channels: %s" % numChannels) - stats.append("User records: %s" % numWhoEntries) - info("\n".join(stats)) - return - else: - incUsage("stats") - - elif cmd == "enable": - if length == 2: - if not spl[1] in pool.keys(): - failure("Name does not exist: %s" % spl[1]) - return - pool[spl[1]]["enabled"] = True - self.save("pool") - if not spl[1] in IRCPool.keys(): - self.addBot(spl[1]) - else: - pass - success("Successfully enabled bot %s" % spl[1]) - return - else: - incUsage("enable") - return - - elif cmd == "disable": - if length == 2: - if not spl[1] in pool.keys(): - failure("Name does not exist: %s" % spl[1]) - return - pool[spl[1]]["enabled"] = False - self.save("pool") - if spl[1] in ReactorPool.keys(): - if spl[1] in FactoryPool.keys(): - FactoryPool[spl[1]].stopTrying() - ReactorPool[spl[1]].disconnect() - if spl[1] in IRCPool.keys(): - del IRCPool[spl[1]] - del ReactorPool[spl[1]] - del FactoryPool[spl[1]] - success("Successfully disabled bot %s" % spl[1]) - return - else: - incUsage("disable") - return - - elif cmd == "join": - if length == 3: - if not spl[1] in pool.keys(): - failure("Name does not exist: %s" % spl[1]) - return - if not spl[1] in IRCPool.keys(): - failure("Name has no instance: %s" % spl[1]) - return - IRCPool[spl[1]].join(spl[2]) - success("Joined %s" % spl[2]) - return - elif length == 4: - if not spl[1] in pool.keys(): - failure("Name does not exist: %s" % spl[1]) - return - if not spl[1] in IRCPool.keys(): - failure("Name has no instance: %s" % spl[1]) - return - IRCPool[spl[1]].join(spl[2], spl[3]) - success("Joined %s with key %s" % (spl[2], spl[3])) - return - else: - incUsage("join") - return - - elif cmd == "part": - if length == 3: - if not spl[1] in pool.keys(): - failure("Name does not exist: %s" % spl[1]) - return - if not spl[1] in IRCPool.keys(): - failure("Name has no instance: %s" % spl[1]) - return - IRCPool[spl[1]].part(spl[2]) - success("Left %s" % spl[2]) - return - else: - incUsage("part") - return - - elif cmd == "get": - if length == 3: - if not spl[1] in pool.keys(): - failure("Name does not exist: %s" % spl[1]) - return - if not spl[1] in IRCPool.keys(): - failure("Name has no instance: %s" % spl[1]) - return - info(str(IRCPool[spl[1]].get(spl[2]))) - return - else: - incUsage("get") - return - - elif cmd == "who": - if length == 2: - result = self.getWho(spl[1]) - rtrn = "" - for i in result.keys(): - rtrn += "Matches from: %s" % i - rtrn += "\n" - for x in result[i]: - x = [y for y in x if not y == None] - rtrn += str((", ".join(x))) - rtrn += "\n" - info(rtrn) - return - else: - incUsage("who") - return - - elif cmd == "key": - if data.startswith("key add"): - if not data in ["key add ", "key add"] and data[3] == " ": - keywordsToAdd = data[8:] - keywords = keywordsToAdd.split(",") - for keyword in keywords: - rtrn = self.addKeyword(keyword) - if rtrn == "EXISTS": - failure("Keyword already exists: %s" % keyword) - elif rtrn == "ISIN": - failure("Keyword already matched: %s" % keyword) - elif rtrn == True: - success("Keyword added: %s" % keyword) - return - else: - incUsage("key") - return - elif data.startswith("key del"): - if not data in ["key del ", "key del"] and data[3] == " ": - keywordsToDel = data[8:] - keywords = keywordsToDel.split(",") - for keyword in keywords: - rtrn = self.delKeyword(keyword) - if rtrn == "NOKEY": - failure("Keyword does not exist: %s" % keyword) - elif rtrn == True: - success("Keyword deleted: %s" % keyword) - return - else: - incUsage("key") - return - if length == 4: - if spl[1] == "except": - if not spl[2] in keyconf["Keywords"]: - failure("No such keyword: %s" % spl[2]) - return - if spl[2] in keyconf["KeywordsExcept"].keys(): - if spl[3] in keyconf["KeywordsExcept"][spl[2]]: - failure("Exception exists: %s" % spl[3]) - return - else: - if not spl[2] in spl[3]: - failure("Keyword %s not in exception %s. This won't work" % (spl[2], spl[3])) - return - keyconf["KeywordsExcept"][spl[2]] = [] - - keyconf["KeywordsExcept"][spl[2]].append(spl[3]) - self.save("keyconf") - success("Successfully added exception %s for keyword %s" % (spl[3], spl[2])) - return - elif spl[1] == "master": - if not spl[2] in pool.keys(): - failure("Name does not exist: %s" % spl[2]) - return - if spl[2] in IRCPool.keys(): - if not spl[3] in IRCPool[spl[2]].channels: - info("Bot not on channel: %s" % spl[3]) - config["Master"] = [spl[2], spl[3]] - self.save("config") - success("Master set to %s on %s" % (spl[3], spl[2])) - return - elif spl[1] == "unexcept": - if not spl[2] in keyconf["KeywordsExcept"].keys(): - failure("No such exception: %s" % spl[2]) - return - if not spl[3] in keyconf["KeywordsExcept"][spl[2]]: - failure("Exception %s has no attribute %s" % (spl[2], spl[3])) - return - keyconf["KeywordsExcept"][spl[2]].remove(spl[3]) - if keyconf["KeywordsExcept"][spl[2]] == []: - del keyconf["KeywordsExcept"][spl[2]] - self.save("keyconf") - success("Successfully removed exception %s for keyword %s" % (spl[3], spl[2])) - return - else: - incUsage("key") - return - elif length == 3: - if spl[1] == "unexcept": - if not spl[2] in keyconf["KeywordsExcept"].keys(): - failure("No such exception: %s" % spl[2]) - return - del keyconf["KeywordsExcept"][spl[2]] - self.save("keyconf") - success("Successfully removed exception list of %s" % spl[2]) - return - elif spl[1] == "monitor": - if spl[2] == "on": - if not obj.addr in MonitorPool: - MonitorPool.append(obj.addr) - success("Keyword monitoring enabled") - return - else: - failure("Keyword monitoring is already enabled") - return - elif spl[2] == "off": - if obj.addr in MonitorPool: - MonitorPool.remove(obj.addr) - success("Keyword monitoring disabled") - return - else: - failure("Keyword monitoring is already disabled") - return - else: - incUsage("key") - return - else: - incUsage("key") - return - elif length == 2: - if spl[1] == "show": - info(",".join(keyconf["Keywords"])) - return - - elif spl[1] == "showexcept": - exceptMap = [] - for i in keyconf["KeywordsExcept"].keys(): - exceptMap.append("Key: %s" % i) - exceptMap.append("%s: %s" % (i, ",".join(keyconf["KeywordsExcept"][i]))) - exceptMap.append("\n") - info("\n".join(exceptMap)) - return - elif spl[1] == "master": - info(" - ".join(config["Master"])) - return - elif spl[1] == "monitor": - if obj.addr in MonitorPool: - info("Keyword monitoring is enabled on this connection") - return - else: - info("Keyword monitoring is disabled on this connection") - return - else: - incUsage("key") - return - - else: - incUsage("key") - return - - elif cmd == "add": - if length > 6: - failure("Too many arguments") - return - - if length > 1: - name = spl[1] - else: - incUsage("add") - return - if length > 2: - host = spl[2] - if length > 3: - port = spl[3] - if length > 4: - protocol = spl[4] - if length > 5: - nickname = spl[5] - - toFail = False - if length < 6: - if config["Default"]["nickname"] == None: - failure("Choose a nickname, or configure one in defaults") - toFail = True - else: - nickname = config["Default"]["nickname"] - - if length < 5: - if config["Default"]["protocol"] == None: - failure("Choose a protocol, or configure one in defaults") - toFail = True - else: - protocol = config["Default"]["protocol"] - - if length < 4: - if config["Default"]["port"] == None: - failure("Choose a port, or configure one in defaults") - toFail = True - else: - port = config["Default"]["port"] - - if length < 3: - if config["Default"]["host"] == None: - failure("Choose a host, or configure one in defaults") - toFail = True - else: - host = config["Default"]["host"] - if toFail: - failure("Stopping due to previous error(s)") - return - - if length < 2: - incUsage("add") - return - - if name in pool.keys(): - failure("Name already exists: %s" % name) - return - - protocol = protocol.lower() - - if not protocol in ["ssl", "plain"]: - failure("Protocol must be ssl or plain, not %s" % protocol) - return - - try: - int(port) - except: - failure("Port must be an integer, not %s" % port) - return - - pool[name] = { "host": host, - "port": port, - "protocol": protocol, - "bind": config["Default"]["bind"], - "timeout": config["Default"]["timeout"], - "maxdelay": config["Default"]["maxdelay"], - "initialdelay": config["Default"]["initialdelay"], - "factor": config["Default"]["factor"], - "jitter": config["Default"]["jitter"], - "nickname": nickname, - "username": config["Default"]["username"], - "realname": config["Default"]["realname"], - "userinfo": config["Default"]["userinfo"], - "finger": config["Default"]["finger"], - "version": config["Default"]["version"], - "source": config["Default"]["source"], - "autojoin": config["Default"]["autojoin"], - "authtype": config["Default"]["authtype"], - "password": config["Default"]["password"], - "authentity": config["Default"]["authentity"], - "key": config["Default"]["key"], - "certificate": config["Default"]["certificate"], - "enabled": config["ConnectOnCreate"], - } - if config["ConnectOnCreate"] == True: - self.addBot(name) - success("Successfully created bot") - self.save("pool") - return - - elif cmd == "del": - if length == 2: - - if not spl[1] in pool.keys(): - failure("Name does not exist: %s" % spl[1]) - return - del pool[spl[1]] - if spl[1] in ReactorPool.keys(): - if spl[1] in FactoryPool.keys(): - FactoryPool[spl[1]].stopTrying() - ReactorPool[spl[1]].disconnect() - if spl[1] in IRCPool.keys(): - del IRCPool[spl[1]] - del ReactorPool[spl[1]] - del FactoryPool[spl[1]] - success("Successfully removed bot") - self.save("pool") - return - else: - incUsage("del") - return - elif cmd == "default": - toUnset = False - if length == 1: - optionMap = ["Viewing defaults"] - for i in config["Default"].keys(): - optionMap.append("%s: %s" % (i, config["Default"][i])) - info("\n".join(optionMap)) - return - elif length == 2: - if not spl[1] in config["Default"].keys(): - failure("No such key: %s" % spl[1]) - return - info("%s: %s" % (spl[1], config["Default"][spl[1]])) - return - elif length == 3: - if not spl[1] in config["Default"].keys(): - failure("No such key: %s" % spl[1]) - return - - if spl[2].lower() in ["none", "nil"]: - spl[2] = None - toUnset = True - - if spl[1] in ["port", "timeout", "maxdelay"]: - try: - spl[2] = int(spl[2]) - except: - failure("Value must be an integer, not %s" % spl[2]) - return - - if spl[2] in ["initialdelay", "factor", "jitter"]: - try: - spl[3] = float(spl[3]) - except: - failure("Value must be a floating point integer, not %s" % spl[3]) - return - - if spl[1] == "protocol": - if not toUnset: - if not spl[2] in ["ssl", "plain"]: - failure("Protocol must be ssl or plain, not %s" % spl[2]) - return - - if spl[2] == config["Default"][spl[1]]: - failure("Value already exists: %s" % spl[2]) - return - - if spl[1] == "authtype": - if not toUnset: - if not spl[2] in ["sp", "ns"]: - failure("Authtype must be sp or ns, not %s" % spl[2]) - return - if spl[1] == "enabled": - failure("Use the ConnectOnCreate config parameter to set this") - return - if spl[1] == "autojoin": - if not toUnset: - spl[2] = spl[2].split(",") - else: - spl[2] = [] - - config["Default"][spl[1]] = spl[2] - self.save("config") - if toUnset: - success("Successfully unset key %s" % spl[1]) - else: - success("Successfully set key %s to %s" % (spl[1], spl[2])) - return - else: - incUsage("default") - return - - elif cmd == "mod": - toUnset = False - if length == 2: - if not spl[1] in pool.keys(): - failure("Name does not exist: %s" % spl[1]) - return - optionMap = ["Viewing options for %s" % spl[1]] - for i in pool[spl[1]].keys(): - optionMap.append("%s: %s" % (i, pool[spl[1]][i])) - info("\n".join(optionMap)) - return - - elif length == 3: - if not spl[1] in pool.keys(): - failure("Name does not exist: %s" % spl[1]) - return - if not spl[2] in pool[spl[1]].keys(): - failure("No such key: %s" % spl[2]) - return - info("%s: %s" % (spl[2], pool[spl[1]][spl[2]])) - return - - elif length == 4: - if not spl[1] in pool.keys(): - failure("Name does not exist: %s" % spl[1]) - return - if not spl[2] in pool[spl[1]].keys(): - failure("No such key: %s" % spl[2]) - return - - if spl[2] == "protocol": - if not spl[3] in ["ssl", "plain"]: - failure("Protocol must be ssl or plain, not %s" % spl[3]) - return - if spl[3] == pool[spl[1]][spl[2]]: - failure("Value already exists: %s" % spl[3]) - return - - if spl[3].lower() in ["none", "nil"]: - spl[3] = None - toUnset = True - - if spl[2] in ["port", "timeout", "maxdelay"]: - try: - spl[3] = int(spl[3]) - except: - failure("Value must be an integer, not %s" % spl[3]) - return - - if spl[2] in ["initialdelay", "factor", "jitter"]: - try: - spl[3] = float(spl[3]) - except: - failure("Value must be a floating point integer, not %s" % spl[3]) - return - - if spl[2] == "authtype": - if not toUnset: - if not spl[3] in ["sp", "ns"]: - failure("Authtype must be sp or ns, not %s" % spl[3]) - return - if spl[2] == "enabled": - failure("Use the enable and disable commands to manage this") - return - if spl[2] == "autojoin": - spl[3] = spl[3].split(",") - - pool[spl[1]][spl[2]] = spl[3] - if spl[1] in IRCPool.keys(): - IRCPool[spl[1]].refresh() - self.save("pool") - if toUnset: - success("Successfully unset key %s on %s" % (spl[2], spl[1])) - else: - success("Successfully set key %s to %s on %s" % (spl[2], spl[3], spl[1])) - return - - else: - incUsage("mod") - return - - else: - incUsage(None) - return - else: - if cmd == "pass" and length == 2: - if spl[1] == config["Password"]: - success("Authenticated successfully") - obj.authed = True - return - else: - failure("Password incorrect") - obj.transport.loseConnection() - return - else: - incUsage(None) - return - if __name__ == "__main__": - helper = Helper() - for i in filemap.keys(): - helper.load(i) - for i in pool.keys(): if pool[i]["enabled"] == True: helper.addBot(i) diff --git a/utils/loaders/command_loader.py b/utils/loaders/command_loader.py new file mode 100644 index 0000000..ce65a9c --- /dev/null +++ b/utils/loaders/command_loader.py @@ -0,0 +1,15 @@ +from os import listdir +from core.main import * +from utils.logging.log import * +import commands + +def loadCommands(func): + for filename in listdir('commands'): + if filename.endswith('.py') and filename != "__init__.py": + commandName = filename[0:-3] + className = commandName.capitalize() + #try: + __import__('commands.%s' % commandName) + eval('commands.%s.%s(func)' % (commandName, className)) + #except Exception as err: + # error("Exception while loading command %s:\n%s" % (commandName, err)) diff --git a/utils/logging/log.py b/utils/logging/log.py index 87d0ed5..5e1141f 100644 --- a/utils/logging/log.py +++ b/utils/logging/log.py @@ -9,4 +9,3 @@ def warn(data): def error(data): print("[ERROR]", data) - exit(1) diff --git a/utils/logging/send.py b/utils/logging/send.py new file mode 100644 index 0000000..2f894f1 --- /dev/null +++ b/utils/logging/send.py @@ -0,0 +1,26 @@ +from core.main import connections, help + +def sendData(addr, data): + connections[addr].send(data) + +def sendSuccess(addr, data): + sendData(addr, "[y] " + data) + +def sendFailure(addr, data): + sendData(addr, "[n] " + data) + +def sendInfo(addr, data): + sendData(addr, "[i] " + data) + +def sendAll(data): + for i in connections: + connections[i].send(data) + return + +def incorrectUsage(addr, mode): + if mode == None: + sendFailure(addr, "Incorrect usage") + return + if mode in help.keys(): + sendFailure(addr, "Usage: " + help[mode]) + return