* don't check authentication if the network doesn't need to register * don't pass through muser for ZNC type messages * avoid duplicate message for queries containing highlights * make a copy of the cast for metadata analysis to avoid poisoning it * set up callback for when the instance is authenticated, so we can request a LIST immediately if so desired * separate out seeding functions to populate CHANLIMIT to ease future work involving other options, such as PREFIX
748 lines
29 KiB
Python
748 lines
29 KiB
Python
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 twisted.internet import reactor, task
|
|
from twisted.words.protocols.irc import symbolic_to_numeric, numeric_to_symbolic, lowDequote, IRCBadMessage
|
|
|
|
import sys
|
|
from string import digits
|
|
from random import randint
|
|
from copy import deepcopy
|
|
from datetime import datetime
|
|
|
|
from modules import userinfo
|
|
from modules import counters
|
|
from modules import monitor
|
|
from modules import chankeep
|
|
from modules import regproc
|
|
|
|
from core.relay import sendRelayNotification
|
|
from utils.dedup import dedup
|
|
from utils.get import getRelay
|
|
|
|
import main
|
|
from utils.logging.log import *
|
|
from utils.logging.debug import *
|
|
from utils.logging.send import *
|
|
from utils.parsing import parsen
|
|
|
|
from twisted.internet.ssl import DefaultOpenSSLContextFactory
|
|
|
|
def deliverRelayCommands(num, relayCommands, user=None, stage2=None):
|
|
keyFN = main.certPath+main.config["Key"]
|
|
certFN = main.certPath+main.config["Certificate"]
|
|
contextFactory = DefaultOpenSSLContextFactory(keyFN.encode("utf-8", "replace"),
|
|
certFN.encode("utf-8", "replace"))
|
|
bot = IRCBotFactory(net=None, num=num, relayCommands=relayCommands, user=user, stage2=stage2)
|
|
host, port = getRelay(num)
|
|
rct = reactor.connectSSL(host,
|
|
port,
|
|
bot, contextFactory)
|
|
|
|
# Copied from the Twisted source so we can fix a bug
|
|
def parsemsg(s):
|
|
"""
|
|
Breaks a message from an IRC server into its prefix, command, and
|
|
arguments.
|
|
@param s: The message to break.
|
|
@type s: L{bytes}
|
|
@return: A tuple of (prefix, command, args).
|
|
@rtype: L{tuple}
|
|
"""
|
|
prefix = ''
|
|
trailing = []
|
|
if not s:
|
|
raise IRCBadMessage("Empty line.")
|
|
if s[0:1] == ':':
|
|
prefix, s = s[1:].split(' ', 1)
|
|
if s.find(' :') != -1:
|
|
s, trailing = s.split(' :', 1)
|
|
args = s.split(' ') # Twisted bug fixed by adding an argument to split()
|
|
args.append(trailing)
|
|
else:
|
|
args = s.split(' ') # And again
|
|
command = args.pop(0)
|
|
return prefix, command, args
|
|
|
|
class IRCRelay(IRCClient):
|
|
def __init__(self, num, relayCommands, user, stage2):
|
|
self.isconnected = False
|
|
self.buffer = ""
|
|
if user == None:
|
|
self.user = main.config["Relay"]["User"]
|
|
else:
|
|
self.user = user
|
|
password = main.config["Relay"]["Password"]
|
|
self.nickname = "relay"
|
|
self.realname = "relay"
|
|
self.username = self.user
|
|
self.password = self.user+":"+password
|
|
|
|
self.relayCommands = relayCommands
|
|
self.num = num
|
|
self.stage2 = stage2
|
|
self.loop = None
|
|
|
|
def privmsg(self, user, channel, msg):
|
|
nick, ident, host = parsen(user)
|
|
for i in main.ZNCErrors:
|
|
if i in msg:
|
|
error("ZNC issue:", msg)
|
|
if nick[0] == main.config["Tweaks"]["ZNC"]["Prefix"]:
|
|
nick = nick[1:]
|
|
if nick in self.relayCommands.keys():
|
|
sendAll("[%s] %s -> %s" % (self.num, nick, msg))
|
|
|
|
def irc_ERR_PASSWDMISMATCH(self, prefix, params):
|
|
log("%s: relay password mismatch" % self.num)
|
|
sendAll("%s: relay password mismatch" % self.num)
|
|
|
|
def sendStage2(self):
|
|
# [["user", {"sasl": ["message1", "message2"]}], []]
|
|
if not len(self.stage2) == 0:
|
|
user = self.stage2[0].pop(0)
|
|
commands = self.stage2[0].pop(0)
|
|
del self.stage2[0]
|
|
deliverRelayCommands(self.num, commands, user, self.stage2)
|
|
|
|
def signedOn(self):
|
|
if not self.isconnected:
|
|
self.isconnected = True
|
|
#log("signed on as a relay: %s" % self.num)
|
|
sleeptime = 0
|
|
increment = 0.8
|
|
for i in self.relayCommands.keys():
|
|
for x in self.relayCommands[i]:
|
|
reactor.callLater(sleeptime, self.msg, main.config["Tweaks"]["ZNC"]["Prefix"]+i, x)
|
|
sleeptime += increment
|
|
increment += 0.8
|
|
if not self.stage2 == None:
|
|
reactor.callLater(sleeptime, self.sendStage2)
|
|
reactor.callLater(sleeptime+5, self.transport.loseConnection)
|
|
return
|
|
|
|
class IRCBot(IRCClient):
|
|
def __init__(self, net, num):
|
|
self.isconnected = False
|
|
self.channels = []
|
|
self.net = net
|
|
self.authenticated = not regproc.needToRegister(self.net)
|
|
self.num = num
|
|
self.buffer = ""
|
|
self.name = net + str(num)
|
|
alias = main.alias[num]
|
|
relay = main.network[self.net].relays[num]
|
|
self.nickname = alias["nick"]
|
|
self.realname = alias["realname"]
|
|
self.username = alias["nick"]+"/"+relay["net"]
|
|
self.password = main.config["Relay"]["Password"]
|
|
self.userinfo = None #
|
|
self.fingerReply = None #
|
|
self.versionName = None # Don't give out information
|
|
self.versionNum = None #
|
|
self.versionEnv = None #
|
|
self.sourceURL = None #
|
|
|
|
self._getWho = {} # LoopingCall objects -- needed to be able to stop them
|
|
|
|
self._tempWho = {} # temporary storage for gathering WHO info
|
|
self._tempNames = {} # temporary storage for gathering NAMES info
|
|
self._tempList = ([], []) # temporary storage for gathering LIST info
|
|
self.listOngoing = False # we are currently receiving a LIST
|
|
self.listRetried = False # we asked and got nothing so asked again
|
|
self.listAttempted = False # we asked for a list
|
|
self.listSimple = False # after asking again we got the list, so use the simple
|
|
# syntax from now on
|
|
self.wantList = False # we want to send a LIST, but not all relays are active yet
|
|
self.chanlimit = 0
|
|
self.servername = None
|
|
|
|
self._regAttempt = None
|
|
|
|
def lineReceived(self, line):
|
|
if bytes != str and isinstance(line, bytes):
|
|
# decode bytes from transport to unicode
|
|
line = line.decode("utf-8", "replace")
|
|
|
|
line = lowDequote(line)
|
|
try:
|
|
prefix, command, params = parsemsg(line)
|
|
if command in numeric_to_symbolic:
|
|
command = numeric_to_symbolic[command]
|
|
self.handleCommand(command, prefix, params)
|
|
except IRCBadMessage:
|
|
self.badMessage(line, *sys.exc_info())
|
|
|
|
def joinChannels(self, channels):
|
|
sleeptime = 0.0
|
|
increment = 0.8
|
|
for i in channels:
|
|
if not i in self.channels:
|
|
debug(self.net, "-", self.num, ": joining", i, "in", sleeptime, "seconds")
|
|
reactor.callLater(sleeptime, self.join, i)
|
|
sleeptime += increment
|
|
if sleeptime == 10:
|
|
sleeptime = 0.0
|
|
increment = 0.7
|
|
increment += 0.1
|
|
else:
|
|
error("%s - Cannot join channel we are already on: %s - %i" % (i, self.net, self.num))
|
|
|
|
def checkChannels(self):
|
|
if not chankeep.allRelaysActive(self.net):
|
|
debug("Skipping channel check as we have inactive relays: %s - %i" % (self.net, self.num))
|
|
return
|
|
if self.net in main.TempChan.keys():
|
|
if self.num in main.TempChan[self.net].keys():
|
|
self.joinChannels(main.TempChan[self.net][self.num])
|
|
del main.TempChan[self.net][self.num]
|
|
if not main.TempChan[self.net]:
|
|
del main.TempChan[self.net]
|
|
|
|
def event(self, **cast):
|
|
if not "time" in cast.keys():
|
|
cast["time"] = str(datetime.now().isoformat())
|
|
|
|
# remove odd stuff
|
|
for i in list(cast.keys()): # Make a copy of the .keys() as Python 3 cannot handle iterating over
|
|
if cast[i] == "": # a dictionary that changes length with each iteration
|
|
del cast[i]
|
|
# remove server stuff
|
|
if "muser" in cast.keys():
|
|
if cast["muser"] == self.servername:
|
|
return
|
|
if "channel" in cast.keys():
|
|
if cast["channel"] == "*":
|
|
return
|
|
##
|
|
|
|
# expand out the hostmask
|
|
if not {"nick", "ident", "host"}.issubset(set(cast.keys())):
|
|
cast["nick"], cast["ident"], cast["host"] = parsen(cast["muser"])
|
|
|
|
# handle ZNC stuff
|
|
if {"nick", "ident", "host", "msg"}.issubset(set(cast)):
|
|
if cast["ident"] == "znc" and cast["host"] == "znc.in":
|
|
cast["type"] = "znc"
|
|
cast["num"] = self.num
|
|
del cast["nick"]
|
|
del cast["ident"]
|
|
del cast["host"]
|
|
del cast["channel"]
|
|
del cast["muser"]
|
|
if "Disconnected from IRC" in cast["msg"]:
|
|
log("ZNC disconnected on %s - %i" % (self.net, self.num))
|
|
self.isconnected = False
|
|
if "Connected!" in cast["msg"]:
|
|
log("ZNC connected on %s - %i" % (self.net, self.num))
|
|
self.isconnected = True
|
|
#
|
|
|
|
# don't reprocess the same message twice
|
|
# if the type is in that list, it's already been here, don't run it again
|
|
if not cast["type"] in {"query", "self", "highlight", "znc", "who"}:
|
|
cast["num"] = self.num
|
|
if "channel" in cast.keys():
|
|
if cast["type"] == "mode":
|
|
if cast["channel"].lower() == self.nickname.lower():
|
|
#castDup = deepcopy(cast)
|
|
cast["mtype"] = cast["type"]
|
|
cast["type"] = "self"
|
|
#self.event(**castDup)
|
|
if cast["modearg"]: # check if modearg is non-NoneType
|
|
if self.nickname.lower() == cast["modearg"].lower():
|
|
castDup = deepcopy(cast)
|
|
castDup["mtype"] = cast["type"]
|
|
castDup["type"] = "highlight"
|
|
self.event(**castDup)
|
|
else:
|
|
if cast["channel"].lower() == self.nickname.lower():
|
|
cast["mtype"] = cast["type"]
|
|
cast["type"] = "query"
|
|
#self.event(**castDup)
|
|
# Don't call self.event for this one because queries are not events on a
|
|
# channel, but we still want to see them
|
|
|
|
# TODO: better way to do this
|
|
# as we changed the types above, check again
|
|
if not cast["type"] in {"query", "self", "highlight", "znc", "who"}:
|
|
# we have been kicked
|
|
if "user" in cast.keys():
|
|
if cast["user"].lower() == self.nickname.lower():
|
|
castDup = deepcopy(cast)
|
|
castDup["mtype"] = cast["type"]
|
|
castDup["type"] = "self"
|
|
self.event(**castDup)
|
|
|
|
# we sent a message/left/joined/kick someone/quit
|
|
if "nick" in cast.keys():
|
|
if cast["nick"].lower() == self.nickname.lower():
|
|
castDup = deepcopy(cast)
|
|
castDup["mtype"] = cast["type"]
|
|
castDup["type"] = "self"
|
|
|
|
# we have been mentioned in a msg/notice/action/part/quit/topic message
|
|
if "msg" in cast.keys(): # Don't highlight queries
|
|
if not cast["msg"] == None:
|
|
if self.nickname.lower() in cast["msg"].lower():
|
|
castDup = deepcopy(cast)
|
|
castDup["mtype"] = cast["type"]
|
|
castDup["type"] = "highlight"
|
|
self.event(**castDup)
|
|
|
|
if not "net" in cast.keys():
|
|
cast["net"] = self.net
|
|
if not "num" in cast.keys():
|
|
cast["num"] = self.num
|
|
if not self.authenticated:
|
|
regproc.registerTest(cast)
|
|
counters.event(self.net, cast["type"])
|
|
monitor.event(self.net, cast)
|
|
|
|
def privmsg(self, user, channel, msg):
|
|
self.event(type="msg", muser=user, channel=channel, msg=msg)
|
|
|
|
def noticed(self, user, channel, msg):
|
|
self.event(type="notice", muser=user, channel=channel, msg=msg)
|
|
|
|
def action(self, user, channel, msg):
|
|
self.event(type="action", muser=user, channel=channel, msg=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):
|
|
self.nickname = newnick
|
|
self.event(type="self", mtype="nick", muser=olduser, user=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 - %i: password mismatch" % (self.net, self.num))
|
|
sendAll("%s - %i: password mismatch" % (self.net, self.num))
|
|
|
|
def _who(self, channel):
|
|
d = Deferred()
|
|
if channel not in self._tempWho:
|
|
self._tempWho[channel] = ([], [])
|
|
self._tempWho[channel][0].append(d)
|
|
self.sendLine("WHO %s" % channel)
|
|
return d
|
|
|
|
def who(self, channel):
|
|
self._who(channel).addCallback(self.got_who)
|
|
|
|
def irc_RPL_WHOREPLY(self, prefix, params):
|
|
channel = params[1]
|
|
ident = params[2]
|
|
host = params[3]
|
|
server = params[4]
|
|
nick = params[5]
|
|
status = params[6]
|
|
realname = params[7]
|
|
if channel not in self._tempWho:
|
|
return
|
|
n = self._tempWho[channel][1]
|
|
n.append([nick, nick, host, server, status, realname])
|
|
self.event(type="who", nick=nick, ident=ident, host=host, realname=realname, channel=channel, server=server, status=status)
|
|
|
|
def irc_RPL_ENDOFWHO(self, prefix, params):
|
|
channel = params[1]
|
|
if channel not in self._tempWho:
|
|
return
|
|
callbacks, info = self._tempWho[channel]
|
|
for cb in callbacks:
|
|
cb.callback((channel, info))
|
|
del self._tempWho[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._tempNames:
|
|
self._tempNames[channel] = ([], [])
|
|
self._tempNames[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._tempNames:
|
|
return
|
|
n = self._tempNames[channel][1]
|
|
n.append(nicklist)
|
|
|
|
def irc_RPL_ENDOFNAMES(self, prefix, params):
|
|
channel = params[1]
|
|
if channel not in self._tempNames:
|
|
return
|
|
callbacks, namelist = self._tempNames[channel]
|
|
for cb in callbacks:
|
|
cb.callback((channel, namelist))
|
|
del self._tempNames[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)
|
|
|
|
def myInfo(self, servername, version, umodes, cmodes):
|
|
self.servername = servername
|
|
|
|
def _list(self, noargs):
|
|
d = Deferred()
|
|
self._tempList = ([], [])
|
|
self._tempList[0].append(d)
|
|
if self.listSimple:
|
|
self.sendLine("LIST")
|
|
return d # return early if we know what to do
|
|
|
|
if noargs:
|
|
self.sendLine("LIST")
|
|
else:
|
|
self.sendLine("LIST >0")
|
|
return d
|
|
|
|
def list(self, noargs=False, nocheck=False):
|
|
if not self.authenticated:
|
|
debug("Will not send LIST, unauthenticated: %s - %i" % (self.net, self.num))
|
|
return
|
|
if self.listAttempted:
|
|
debug("List request dropped, already asked for LIST - %s - %i" % (self.net, self.num))
|
|
return
|
|
else:
|
|
self.listAttempted = True
|
|
if self.listOngoing:
|
|
debug("LIST request dropped, already ongoing - %s - %i" % (self.net, self.num))
|
|
return
|
|
else:
|
|
if nocheck:
|
|
allRelays = True # override the system - if this is
|
|
else: # specified, we already did this
|
|
allRelays = chankeep.allRelaysActive(self.net)
|
|
if not allRelays:
|
|
self.wantList = True
|
|
debug("Not all relays were active for LIST request")
|
|
return
|
|
self._list(noargs).addCallback(self.got_list)
|
|
|
|
def irc_RPL_LISTSTART(self, prefix, params):
|
|
self.listAttempted = False
|
|
self.listOngoing = True
|
|
self.wantList = False
|
|
|
|
def irc_RPL_LIST(self, prefix, params):
|
|
channel = params[1]
|
|
users = params[2]
|
|
topic = params[3]
|
|
self._tempList[1].append([channel, users, topic])
|
|
|
|
def irc_RPL_LISTEND(self, prefix, params):
|
|
if not len(self._tempList[0]) > 0: # there are no callbacks, can't do anything there
|
|
debug("We didn't ask for this LIST, discarding")
|
|
self._tempList[0].clear()
|
|
self._tempList[1].clear()
|
|
return
|
|
callbacks, info = self._tempList
|
|
self.listOngoing = False
|
|
for cb in callbacks:
|
|
cb.callback((info))
|
|
noResults = False
|
|
if len(self._tempList[1]) == 0:
|
|
noResults = True
|
|
self._tempList[0].clear()
|
|
self._tempList[1].clear()
|
|
if noResults:
|
|
if self.listRetried:
|
|
warn("LIST still empty after retry: %s - %i" % (self.net, self.num))
|
|
self.listRetried = False
|
|
return
|
|
else:
|
|
self.list(True)
|
|
self.listRetried = True
|
|
else:
|
|
if self.listRetried:
|
|
self.listRetried = False
|
|
debug("List received after retry - defaulting to simple list syntax: %s - %i" % (self.net, self.num))
|
|
self.listSimple = True
|
|
|
|
def got_list(self, listinfo):
|
|
if len(listinfo) == 0: # probably ngircd not supporting LIST >0
|
|
return
|
|
chankeep.initialList(self.net, self.num, listinfo, self.chanlimit)
|
|
|
|
def recheckList(self):
|
|
print("list being rechecked")
|
|
allRelays = chankeep.allRelaysActive(self.net)
|
|
if allRelays:
|
|
print("allrelays now passed")
|
|
name = self.net+"1"
|
|
if main.IRCPool[name].wantList == True:
|
|
main.IRCPool[name].list(nocheck=True)
|
|
debug("Asking for a list for %s after final relay %i connected" % (self.net, self.num))
|
|
if self.num == 1: # Only one instance should do a list
|
|
if self.chanlimit:
|
|
if allRelays:
|
|
self.list()
|
|
else:
|
|
self.wantList = True
|
|
else:
|
|
debug("Aborting LIST due to bad chanlimit")
|
|
self.checkChannels()
|
|
|
|
def seed_chanlimit(self, chanlimit):
|
|
if not main.network[self.net].relays[self.num]["registered"]: #TODO: add check for register request sent, only send it once
|
|
if main.config["AutoReg"]:
|
|
self._regAttempt = reactor.callLater(5, regproc.registerAccount, self.net, self.num)
|
|
#regproc.registerAccount(self.net, self.num)
|
|
try:
|
|
self.chanlimit = int(chanlimit)
|
|
except TypeError:
|
|
warn("Invalid chanlimit: %s" % i)
|
|
if self.chanlimit == 0:
|
|
self.chanlimit = 200 # don't take the piss if it's not limited
|
|
if not regproc.needToRegister(self.net): # if we need to register, only recheck on auth confirmation
|
|
self.recheckList()
|
|
|
|
def seed_prefix(self, prefix):
|
|
print("PREFIX", prefix)
|
|
|
|
def isupport(self, options):
|
|
interested = {"CHANLIMIT", "MAXCHANNELS", "PREFIX"}
|
|
newOptions = {x for x in options if any(y in x for y in interested)}
|
|
if len(newOptions) == 0:
|
|
return
|
|
if not self.isconnected:
|
|
log("endpoint connected: %s - %i" % (self.net, self.num))
|
|
self.isconnected = True
|
|
for i in newOptions:
|
|
if i.startswith("PREFIX"):
|
|
if "=" in i:
|
|
split = i.split("=")
|
|
if len(split) == 2:
|
|
prefix = split[1]
|
|
self.seed_prefix(prefix)
|
|
elif i.startswith("CHANLIMIT"):
|
|
if ":" in i:
|
|
split = i.split(":")
|
|
if len(split) >= 2:
|
|
chanlimit = split[1]
|
|
self.seed_chanlimit(chanlimit)
|
|
elif i.startswith("MAXCHANNELS"):
|
|
if "=" in i:
|
|
split = i.split("=")
|
|
if len(split) == 2:
|
|
chanlimit = split[1]
|
|
self.seed_chanlimit(chanlimit)
|
|
|
|
# We need to override these functions as Twisted discards
|
|
# the hostname and other useful information in the functions
|
|
# that these call by default
|
|
def irc_JOIN(self, prefix, params):
|
|
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):
|
|
nick = prefix.split('!')[0]
|
|
channel = params[0]
|
|
if len(params) >= 2:
|
|
message = params[1]
|
|
else:
|
|
message = None
|
|
if nick == self.nickname:
|
|
self.left(prefix, channel, message)
|
|
else:
|
|
self.userLeft(prefix, channel, message)
|
|
|
|
def irc_QUIT(self, prefix, params):
|
|
nick = prefix.split('!')[0]
|
|
self.userQuit(prefix, params[0])
|
|
|
|
def irc_NICK(self, prefix, params):
|
|
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):
|
|
channel = params[0]
|
|
kicked = params[1]
|
|
message = params[-1]
|
|
# Checks on whether it was us that was kicked are done in userKicked
|
|
self.userKicked(kicked, channel, prefix, message)
|
|
|
|
def irc_TOPIC(self, prefix, params):
|
|
channel = params[0]
|
|
newtopic = params[1]
|
|
self.topicUpdated(prefix, channel, newtopic)
|
|
# End of Twisted hackery
|
|
|
|
def signedOn(self):
|
|
log("signed on: %s - %i" % (self.net, self.num))
|
|
sendRelayNotification({"type": "conn", "net": self.net, "num": self.num, "status": "signedon"})
|
|
|
|
def joined(self, channel):
|
|
if not channel in self.channels:
|
|
self.channels.append(channel)
|
|
self.names(channel).addCallback(self.got_names)
|
|
if main.config["Toggles"]["Who"]:
|
|
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.delChannels(self.net, [channel]) # < we do not need to deduplicate this
|
|
#log("Can no longer cover %s, removing records" % channel)# as it will only be matched once --
|
|
# other bots have different nicknames so
|
|
def left(self, user, channel, message): # even if they saw it, they wouldn't react
|
|
self.event(type="part", muser=user, channel=channel, msg=message)
|
|
self.botLeft(channel)
|
|
|
|
def userJoined(self, user, channel):
|
|
self.event(type="join", muser=user, channel=channel)
|
|
|
|
def userLeft(self, user, channel, message):
|
|
self.event(type="part", muser=user, channel=channel, msg=message)
|
|
|
|
def userQuit(self, user, quitMessage):
|
|
self.chanlessEvent({"type": "quit", "muser": user, "msg": quitMessage})
|
|
|
|
def userKicked(self, kickee, channel, kicker, message):
|
|
if kickee.lower() == self.nickname.lower():
|
|
self.botLeft(channel)
|
|
self.event(type="kick", muser=kicker, channel=channel, msg=message, user=kickee)
|
|
|
|
def chanlessEvent(self, cast):
|
|
cast["nick"], cast["ident"], cast["host"] = parsen(cast["muser"])
|
|
if dedup(self.name, cast): # Needs to be kept self.name until the dedup
|
|
# function is converted to the new net, num
|
|
# format
|
|
return # stop right there sir!
|
|
chans = userinfo.getChanList(self.net, cast["nick"])
|
|
if chans == None:
|
|
error("No channels returned for chanless event: %s" % cast)
|
|
# self.event(**cast) -- no, should NEVER happen
|
|
return
|
|
# getChansSingle returns all channels of the user, we only want to use
|
|
# ones we have common with them
|
|
realChans = set(chans).intersection(set(self.channels))
|
|
for i in realChans:
|
|
cast["channel"] = i
|
|
self.event(**cast)
|
|
|
|
def userRenamed(self, oldname, newname):
|
|
self.chanlessEvent({"type": "nick", "muser": oldname, "user": newname})
|
|
|
|
def topicUpdated(self, user, channel, newTopic):
|
|
self.event(type="topic", muser=user, channel=channel, msg=newTopic)
|
|
|
|
def modeChanged(self, user, channel, toset, modes, args):
|
|
argList = list(args)
|
|
modeList = [i for i in modes]
|
|
for a, m in zip(argList, modeList):
|
|
self.event(type="mode", muser=user, channel=channel, mode=m, status=toset, modearg=a)
|
|
|
|
class IRCBotFactory(ReconnectingClientFactory):
|
|
def __init__(self, net, num=None, relayCommands=None, user=None, stage2=None):
|
|
if net == None:
|
|
self.num = num
|
|
self.net = None
|
|
self.name = "relay - %i" % num
|
|
self.relay = True
|
|
else:
|
|
self.name = net+str(num)
|
|
self.num = num
|
|
self.net = net
|
|
self.relay = False
|
|
self.client = None
|
|
self.maxDelay = main.config["Tweaks"]["Delays"]["MaxDelay"]
|
|
self.initialDelay = main.config["Tweaks"]["Delays"]["InitialDelay"]
|
|
self.factor = main.config["Tweaks"]["Delays"]["Factor"]
|
|
self.jitter = main.config["Tweaks"]["Delays"]["Jitter"]
|
|
|
|
self.relayCommands, self.user, self.stage2 = relayCommands, user, stage2
|
|
|
|
def buildProtocol(self, addr):
|
|
if self.relay == False:
|
|
entry = IRCBot(self.net, self.num)
|
|
main.IRCPool[self.name] = entry
|
|
else:
|
|
entry = IRCRelay(self.num, self.relayCommands, self.user, self.stage2)
|
|
|
|
self.client = entry
|
|
return entry
|
|
|
|
def clientConnectionLost(self, connector, reason):
|
|
if not self.relay:
|
|
userinfo.delChannels(self.net, self.client.channels)
|
|
if not self.client == None:
|
|
self.client.isconnected = False
|
|
self.client.authenticated = False
|
|
self.client.channels = []
|
|
error = reason.getErrorMessage()
|
|
if not self.relay:
|
|
log("%s - %i: connection lost: %s" % (self.net, self.num, error))
|
|
sendAll("%s - %i: connection lost: %s" % (self.net, self.num, error))
|
|
sendRelayNotification({"type": "conn", "net": self.net, "num": self.num, "status": "lost", "message": error})
|
|
self.retry(connector)
|
|
#ReconnectingClientFactory.clientConnectionLost(self, connector, reason)
|
|
|
|
def clientConnectionFailed(self, connector, reason):
|
|
if not self.client == None:
|
|
self.client.isconnected = False
|
|
self.client.authenticated = False
|
|
self.client.channels = []
|
|
error = reason.getErrorMessage()
|
|
log("%s - %i: connection failed: %s" % (self.net, self.num, error))
|
|
if not self.relay:
|
|
sendAll("%s - %s: connection failed: %s" % (self.net, self.num, error))
|
|
sendRelayNotification({"type": "conn", "net": self.net, "num": self.num, "status": "failed", "message": error})
|
|
self.retry(connector)
|
|
#ReconnectingClientFactory.clientConnectionFailed(self, connector, reason)
|
|
|