|
|
|
@ -2,11 +2,15 @@
|
|
|
|
|
from twisted.internet import reactor
|
|
|
|
|
from twisted.internet.ssl import DefaultOpenSSLContextFactory
|
|
|
|
|
from twisted.internet.protocol import Protocol, Factory
|
|
|
|
|
from twisted.internet.endpoints import SSL4ClientEndpoint, TCP4ClientEndpoint, connectProtocol
|
|
|
|
|
from twisted.words.protocols.irc import IRCClient
|
|
|
|
|
|
|
|
|
|
from json import load, dump, loads
|
|
|
|
|
from sys import exit
|
|
|
|
|
|
|
|
|
|
listener = None
|
|
|
|
|
connections = {}
|
|
|
|
|
IRCPool = {}
|
|
|
|
|
|
|
|
|
|
def log(data):
|
|
|
|
|
print("[LOG]", data)
|
|
|
|
@ -33,6 +37,136 @@ def sendFailure(addr, data):
|
|
|
|
|
def sendInfo(addr, data):
|
|
|
|
|
sendData(addr, "[i] " + data)
|
|
|
|
|
|
|
|
|
|
class IRCBot(IRCClient):
|
|
|
|
|
def __init__(self, name):
|
|
|
|
|
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._who = {}
|
|
|
|
|
self._getWho = {}
|
|
|
|
|
self.wholist = {}
|
|
|
|
|
|
|
|
|
|
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[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 sanitize(self, user):
|
|
|
|
|
user = user.replace("!", "")
|
|
|
|
|
user = user.replace("~", "")
|
|
|
|
|
user = user.replace("&", "")
|
|
|
|
|
user = user.replace("@", "")
|
|
|
|
|
user = user.replace("%", "")
|
|
|
|
|
user = user.replace("+", "")
|
|
|
|
|
return user
|
|
|
|
|
|
|
|
|
|
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):
|
|
|
|
|
ircLog(self.ident, "Password mismatch")
|
|
|
|
|
manager.handleWrongPassword(self.ident, prefix, params)
|
|
|
|
|
|
|
|
|
|
def irc_RPL_NAMREPLY(self, prefix, params):
|
|
|
|
|
channel = params[2]
|
|
|
|
|
nicklist = params[3].split(' ')
|
|
|
|
|
|
|
|
|
|
if channel not in self._names:
|
|
|
|
|
return
|
|
|
|
|
n = self._names[channel][1]
|
|
|
|
|
n += nicklist
|
|
|
|
|
|
|
|
|
|
def irc_RPL_ENDOFNAMES(self, prefix, params):
|
|
|
|
|
channel = params[1]
|
|
|
|
|
if channel not in self._names:
|
|
|
|
|
return
|
|
|
|
|
callbacks, namelist = self._names[channel]
|
|
|
|
|
for cb in callbacks:
|
|
|
|
|
cb.callback((channel, namelist))
|
|
|
|
|
del self._names[channel]
|
|
|
|
|
|
|
|
|
|
def got_names(self, nicklist):
|
|
|
|
|
ncklist = []
|
|
|
|
|
for i in nicklist[1]:
|
|
|
|
|
ncklist.append(self.sanitize(i))
|
|
|
|
|
self.nameslist[nicklist[0]] = ncklist
|
|
|
|
|
|
|
|
|
|
def who(self, channel):
|
|
|
|
|
channel = channel
|
|
|
|
|
d = defer.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):
|
|
|
|
|
self.wholist[whoinfo[0]] = whoinfo[1]
|
|
|
|
|
|
|
|
|
|
def signedOn(self):
|
|
|
|
|
if self.authtype == "ns":
|
|
|
|
|
self.msg(self.authentity, "IDENTIFY %s" % self.nspass)
|
|
|
|
|
|
|
|
|
|
def joined(self, channel):
|
|
|
|
|
self.who(channel).addCallback(self.got_who)
|
|
|
|
|
|
|
|
|
|
class Base(Protocol):
|
|
|
|
|
def __init__(self, addr):
|
|
|
|
|
self.addr = addr
|
|
|
|
@ -124,6 +258,37 @@ class Helper(object):
|
|
|
|
|
sendFailure(addr, "Usage: " + help[mode])
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
def addBot(self, name):
|
|
|
|
|
global IRCPool
|
|
|
|
|
instance = pool[name]
|
|
|
|
|
if instance["protocol"] == "plain":
|
|
|
|
|
if instance["bind"] == None:
|
|
|
|
|
point = TCP4ClientEndpoint(reactor, instance["host"], int(instance["port"]), timeout=int(instance["timeout"]))
|
|
|
|
|
bot = IRCBot(name)
|
|
|
|
|
IRCPool[name] = bot
|
|
|
|
|
d = connectProtocol(point, bot)
|
|
|
|
|
return
|
|
|
|
|
else:
|
|
|
|
|
point = TCP4ClientEndpoint(reactor, instance["host"], int(instance["port"]), timeout=int(instance["timeout"]), bindAddress=instance["bind"])
|
|
|
|
|
bot = IRCBot(name)
|
|
|
|
|
IRCPool[name] = bot
|
|
|
|
|
d = connectProtocol(point, bot)
|
|
|
|
|
return
|
|
|
|
|
elif instance["protocol"] == "ssl":
|
|
|
|
|
contextFactory = DefaultOpenSSLContextFactory(instance["key"].encode("utf-8", "replace"), instance["certificate"].encode("utf-8", "replace"))
|
|
|
|
|
if instance["bind"] == None:
|
|
|
|
|
point = SSL4ClientEndpoint(reactor, instance["host"], int(instance["port"]), contextFactory, timeout=int(instance["timeout"]))
|
|
|
|
|
bot = IRCBot(name)
|
|
|
|
|
IRCPool[name] = bot
|
|
|
|
|
d = connectProtocol(point, bot)
|
|
|
|
|
return
|
|
|
|
|
else:
|
|
|
|
|
point = SSL4ClientEndpoint(reactor, instance["host"], int(instance["port"]), contextFactory, timeout=int(instance["timeout"]), bindAddress=instance["bind"])
|
|
|
|
|
bot = IRCBot(name)
|
|
|
|
|
IRCPool[name] = bot
|
|
|
|
|
d = connectProtocol(point, bot)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
def parseCommand(self, addr, authed, data):
|
|
|
|
|
global pool
|
|
|
|
|
data = data.strip()
|
|
|
|
@ -174,6 +339,33 @@ class Helper(object):
|
|
|
|
|
info("\n".join(poolMap))
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
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.addBot(spl[1])
|
|
|
|
|
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
|
|
|
|
|
if spl[1] in IRCPool.keys():
|
|
|
|
|
IRCPool[spl[1]].transport.loseConnection()
|
|
|
|
|
success("Successfully disabled bot %s" % spl[1])
|
|
|
|
|
return
|
|
|
|
|
else:
|
|
|
|
|
incUsage("disable")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
elif cmd == "add":
|
|
|
|
|
if length == 6:
|
|
|
|
|
|
|
|
|
@ -193,9 +385,11 @@ class Helper(object):
|
|
|
|
|
failure("Port must be an integer, not %s" % spl[3])
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
pool[spl[1]] = { "address": spl[2],
|
|
|
|
|
pool[spl[1]] = { "host": spl[2],
|
|
|
|
|
"port": spl[3],
|
|
|
|
|
"protocol": protocol,
|
|
|
|
|
"bind": None,
|
|
|
|
|
"timeout": 30,
|
|
|
|
|
"nickname": spl[5],
|
|
|
|
|
"username": None,
|
|
|
|
|
"realname": None,
|
|
|
|
@ -205,10 +399,13 @@ class Helper(object):
|
|
|
|
|
"source": None,
|
|
|
|
|
"authtype": None,
|
|
|
|
|
"password": None,
|
|
|
|
|
"authentity": None,
|
|
|
|
|
"key": None,
|
|
|
|
|
"certificate": None,
|
|
|
|
|
"authentity": "NickServ",
|
|
|
|
|
"key": config["ListenerKey"],
|
|
|
|
|
"certificate": config["ListenerCertificate"],
|
|
|
|
|
"enabled": config["ConnectOnCreate"],
|
|
|
|
|
}
|
|
|
|
|
if config["ConnectOnCreate"] == True:
|
|
|
|
|
self.addBot(spl[1])
|
|
|
|
|
success("Successfully created bot")
|
|
|
|
|
self.savePool()
|
|
|
|
|
return
|
|
|
|
@ -223,6 +420,9 @@ class Helper(object):
|
|
|
|
|
failure("Name does not exist: %s" % spl[1])
|
|
|
|
|
return
|
|
|
|
|
del pool[spl[1]]
|
|
|
|
|
if spl[1] in IRCPool.keys():
|
|
|
|
|
IRCPool[spl[1]].transport.loseConnection()
|
|
|
|
|
del IRCPool[spl[1]]
|
|
|
|
|
success("Successfully removed bot")
|
|
|
|
|
self.savePool()
|
|
|
|
|
return
|
|
|
|
@ -231,6 +431,7 @@ class Helper(object):
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
elif cmd == "mod":
|
|
|
|
|
toUnset = False
|
|
|
|
|
if length == 2:
|
|
|
|
|
if not spl[1] in pool.keys():
|
|
|
|
|
failure("Name does not exist: %s" % spl[1])
|
|
|
|
@ -258,11 +459,11 @@ class Helper(object):
|
|
|
|
|
if not spl[2] in pool[spl[1]].keys():
|
|
|
|
|
failure("No such key: %s" % spl[2])
|
|
|
|
|
return
|
|
|
|
|
if spl[2] == "port":
|
|
|
|
|
if spl[2] in ["port", "timeout"]:
|
|
|
|
|
try:
|
|
|
|
|
int(spl[3])
|
|
|
|
|
except:
|
|
|
|
|
failure("Port must be an integer, not %s" % spl[3])
|
|
|
|
|
failure("Value must be an integer, not %s" % spl[3])
|
|
|
|
|
return
|
|
|
|
|
if spl[2] == "protocol":
|
|
|
|
|
if not spl[3] in ["ssl", "plain"]:
|
|
|
|
@ -271,8 +472,25 @@ class Helper(object):
|
|
|
|
|
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] == "authtype":
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
pool[spl[1]][spl[2]] = spl[3]
|
|
|
|
|
success("Successfully set key %s to %s on %s" % (spl[2], spl[3], spl[1]))
|
|
|
|
|
self.savePool()
|
|
|
|
|
if toUnset:
|
|
|
|
|
success("Successfully unset key %s on %s" % (spl[2], spl[3], spl[1]))
|
|
|
|
|
else:
|
|
|
|
|
success("Successfully set key %s to %s on %s" % (spl[2], spl[3], spl[1]))
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
@ -300,6 +518,9 @@ if __name__ == "__main__":
|
|
|
|
|
config = helper.getConfig()
|
|
|
|
|
pool = helper.getPool()
|
|
|
|
|
help = helper.getHelp()
|
|
|
|
|
for i in pool.keys():
|
|
|
|
|
if pool[i]["enabled"] == True:
|
|
|
|
|
helper.addBot(i)
|
|
|
|
|
|
|
|
|
|
listener = BaseFactory()
|
|
|
|
|
if config["UseSSL"] == True:
|
|
|
|
|