monolith/core/bot.py

422 lines
16 KiB
Python
Raw Normal View History

from twisted.internet.protocol import ReconnectingClientFactory
from twisted.words.protocols.irc import IRCClient
from twisted.internet.defer import Deferred
from twisted.internet.task import LoopingCall
2018-08-27 19:42:49 +00:00
from string import digits
from random import randint
import modules.keyword as keyword
import modules.userinfo as userinfo
import modules.counters as count
import modules.monitor as monitor
import main
from utils.logging.log import *
from utils.logging.send import *
class IRCBot(IRCClient):
def __init__(self, name):
self.connected = False
self.channels = []
self.net = "".join([x for x in name if not x in digits])
2018-08-27 19:42:49 +00:00
if self.net == "":
error("Network with all numbers: %s" % name)
self.buffer = ""
self.name = name
instance = main.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 = {}
2018-08-27 19:42:49 +00:00
self._names = {}
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 = main.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)
2018-08-27 19:42:49 +00:00
userinfo.editUser(self.net, channel, nick, user)
count.event(self.name, "privmsg")
keyword.actKeyword(user, channel, msg, self.nickname, "MSG", self.name)
monitor.event(self.net, channel, {"type": "msg", "exact": user, "nick": nick, "ident": ident, "host": host, "message": msg})
def noticed(self, user, channel, msg):
nick, ident, host = self.parsen(user)
2018-08-27 19:42:49 +00:00
userinfo.editUser(self.net, channel, nick, user)
count.event(self.name, "notice")
keyword.actKeyword(user, channel, msg, self.nickname, "NOTICE", self.name)
monitor.event(self.net, channel, {"type": "notice", "exact": user, "nick": nick, "ident": ident, "host": host, "message": msg})
def action(self, user, channel, msg):
nick, ident, host = self.parsen(user)
2018-08-27 19:42:49 +00:00
userinfo.editUser(self.net, channel, nick, user)
count.event(self.name, "action")
keyword.actKeyword(user, channel, msg, self.nickname, "ACTION", self.name)
monitor.event(self.net, channel, {"type": "action", "exact": user, "nick": nick, "ident": ident, "host": host, "message": msg})
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
2018-08-27 19:42:49 +00:00
def nickChanged(self, olduser, newnick):
oldnick, ident, host = self.parsen(olduser)
userinfo.renameUser(self.net, oldnick, olduser, newnick, newnick+"!"+ident+"@"+host)
self.nickname = newnick
count.event(self.name, "selfnick")
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):
d = Deferred()
if channel not in self._who:
2018-08-27 19:42:49 +00:00
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]
2018-08-27 19:42:49 +00:00
n.append([nick, user, host, server, status, realname])
count.event(self.name, "whoreply")
monitor.event(self.net, channel, {"type": "who", "exact": nick+"!"+user+"@"+host, "nick": nick, "ident": user, "host": host, "realname": realname, "server": server, "status": status})
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):
2018-08-27 19:42:49 +00:00
userinfo.initialUsers(self.net, whoinfo[0], whoinfo[1])
def sanit(self, data):
if len(data) >= 1:
if data[0] in ["!", "~", "&", "@", "%", "+"]:
data = data[1:]
return data
return data
else:
return False
def names(self, channel):
d = Deferred()
if channel not in self._names:
self._names[channel] = ([], [])
self._names[channel][0].append(d)
self.sendLine("NAMES %s" % channel)
return d
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.append(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):
newNicklist = []
for i in nicklist[1]:
for x in i:
f = self.sanit(x)
if f:
newNicklist.append(f)
userinfo.initialNames(self.net, nicklist[0], newNicklist)
#twisted sucks so i have to do this to actually get the user info
def irc_JOIN(self, prefix, params):
"""
Called when a user joins a channel.
"""
nick = prefix.split('!')[0]
channel = params[-1]
if nick == self.nickname:
self.joined(channel)
else:
self.userJoined(prefix, channel)
def irc_PART(self, prefix, params):
"""
Called when a user leaves a channel.
"""
nick = prefix.split('!')[0]
channel = params[0]
if len(params) >= 2:
message = params[1]
else:
message = None
if nick == self.nickname:
self.left(channel, message)
else:
self.userLeft(prefix, channel, message)
def irc_QUIT(self, prefix, params):
"""
Called when a user has quit.
"""
#nick = prefix.split('!')[0]
self.userQuit(prefix, params[0])
def irc_NICK(self, prefix, params):
"""
Called when a user changes their nickname.
"""
nick = prefix.split('!', 1)[0]
if nick == self.nickname:
2018-08-27 19:42:49 +00:00
self.nickChanged(prefix, params[0])
else:
self.userRenamed(prefix, params[0])
def irc_KICK(self, prefix, params):
"""
Called when a user is kicked from a channel.
"""
#kicker = prefix.split('!')[0]
channel = params[0]
kicked = params[1]
message = params[-1]
if kicked.lower() == self.nickname.lower():
# Yikes!
self.kickedFrom(channel, prefix, message)
else:
self.userKicked(kicked, channel, prefix, message)
def irc_TOPIC(self, prefix, params):
"""
Someone in the channel set the topic.
"""
#user = prefix.split('!')[0]
channel = params[0]
newtopic = params[1]
self.topicUpdated(prefix, channel, newtopic)
#END hacks
def signedOn(self):
self.connected = True
log("signed on: %s" % self.name)
if main.config["Notifications"]["Connection"]:
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)
count.event(self.name, "signedon")
def joined(self, channel):
if not channel in self.channels:
self.channels.append(channel)
2018-08-27 19:42:49 +00:00
self.names(channel).addCallback(self.got_names)
self.who(channel).addCallback(self.got_who)
count.event(self.name, "selfjoin")
if self.name == main.config["Master"][0] and channel == main.config["Master"][1]:
for i in range(len(main.masterbuf)):
self.msg(channel, main.masterbuf.pop(0))
main.saveConf("masterbuf")
2018-08-27 19:42:49 +00:00
lc = LoopingCall(self.who, channel)
self._getWho[channel] = lc
intrange = main.config["Tweaks"]["Delays"]["WhoRange"]
minint = main.config["Tweaks"]["Delays"]["WhoLoop"]
interval = randint(minint, minint+intrange)
lc.start(interval)
2018-08-27 19:42:49 +00:00
def botLeft(self, channel):
if channel in self.channels:
self.channels.remove(channel)
2018-08-27 19:42:49 +00:00
if channel in self._getWho.keys():
lc = self._getWho[channel]
lc.stop()
del self._getWho[channel]
userinfo.delChannel(self.net, channel)
def left(self, channel, message):
keyword.actKeyword(self.nickname, channel, message, self.nickname, "SELFPART", self.name)
count.event(self.name, "selfpart")
monitor.event(self.net, channel, {"type": "part", "message": message})
2018-08-27 19:42:49 +00:00
self.botLeft(channel)
def kickedFrom(self, channel, kicker, message):
nick, ident, host = self.parsen(kicker)
if channel in self.channels:
self.channels.remove(channel)
keyword.sendMaster("KICK %s: (%s/%s) %s" % (self.name, kicker, channel, message))
count.event(self.name, "selfkick")
monitor.event(self.net, channel, {"type": "kick", "exact": kicker, "nick": nick, "ident": ident, "host": host, "message": message})
2018-08-27 19:42:49 +00:00
self.botLeft(channel)
def userJoined(self, user, channel):
nick, ident, host = self.parsen(user)
2018-08-27 19:42:49 +00:00
userinfo.addUser(self.net, channel, nick, user)
count.event(self.name, "join")
monitor.event(self.net, channel, {"type": "join", "exact": user, "nick": nick, "ident": ident, "host": host})
def userLeft(self, user, channel, message):
nick, ident, host = self.parsen(user)
2018-08-27 19:42:49 +00:00
userinfo.delUser(self.net, channel, nick, user)
keyword.actKeyword(user, channel, message, self.nickname, "PART", self.name)
count.event(self.name, "part")
monitor.event(self.net, channel, {"type": "part", "exact": user, "nick": nick, "ident": ident, "host": host, "message": message})
def userQuit(self, user, quitMessage):
nick, ident, host = self.parsen(user)
2018-08-27 19:42:49 +00:00
userinfo.delUserByNetwork(self.net, nick, user)
count.event(self.name, "quit")
keyword.actKeyword(user, None, quitMessage, self.nickname, "QUIT", self.name)
monitor.event(self.net, None, {"type": "quit", "exact": user, "nick": nick, "ident": ident, "host": host, "message": quitMessage})
def userKicked(self, kickee, channel, kicker, message):
nick, ident, host = self.parsen(kicker)
2018-08-27 19:42:49 +00:00
userinfo.editUser(self.net, channel, nick, kicker)
userinfo.delUserByNick(self.net, channel, kickee)
count.event(self.name, "kick")
keyword.actKeyword(kicker, channel, message, self.nickname, "KICK", self.name)
monitor.event(self.net, channel, {"type": "kick", "exact": kicker, "nick": nick, "ident": ident, "host": host, "message": message, "user": kickee})
def userRenamed(self, oldname, newname):
nick, ident, host = self.parsen(oldname)
2018-08-27 19:42:49 +00:00
userinfo.renameUser(self.net, nick, oldname, newname, newname+"!"+ident+"@"+host)
count.event(self.name, "nick")
monitor.event(self.net, None, {"type": "nick", "exact": oldname, "nick": nick, "ident": ident, "host": host, "user": newname})
def topicUpdated(self, user, channel, newTopic):
nick, ident, host = self.parsen(user)
2018-08-27 19:42:49 +00:00
userinfo.editUser(self.net, channel, nick, user)
count.event(self.name, "topic")
keyword.actKeyword(user, channel, newTopic, self.nickname, "TOPIC", self.name)
monitor.event(self.net, channel, {"type": "topic", "exact": user, "nick": nick, "ident": ident, "host": host, "message": newTopic})
def modeChanged(self, user, channel, toset, modes, args):
nick, ident, host = self.parsen(user)
2018-08-27 19:42:49 +00:00
userinfo.editUser(self.net, channel, nick, user)
count.event(self.name, "mode")
argList = list(args)
modeList = [i for i in modes]
for a, m in zip(argList, modeList):
monitor.event(self.net, channel, {"type": "mode", "exact": user, "nick": nick, "ident": ident, "host": host, "modes": m, "modeargs": a})
class IRCBotFactory(ReconnectingClientFactory):
def __init__(self, name):
self.instance = main.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)
main.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 main.config["Notifications"]["Connection"]:
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 main.config["Notifications"]["Connection"]:
keyword.sendMaster("CONNFAIL %s: %s" % (self.name, error))
self.retry(connector)
#ReconnectingClientFactory.clientConnectionFailed(self, connector, reason)