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 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]) 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 = {} 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) 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) 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) 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 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: 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.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): 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: 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) 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") 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) def botLeft(self, channel): if channel in self.channels: self.channels.remove(channel) 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}) 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}) self.botLeft(channel) def userJoined(self, user, channel): nick, ident, host = self.parsen(user) 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) 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) 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) 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) 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) 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) 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)