793 lines
31 KiB
Python
793 lines
31 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.lower()
|
|
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"].lower()+"/"+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.prefix = {}
|
|
self.servername = None
|
|
|
|
self._regAttempt = None
|
|
self._negativePass = 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
|
|
self.authenticated = 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 as %s" % (self.net, self.num, self.username))
|
|
sendAll("%s - %i: password mismatch as %s" % (self.net, self.num, self.username))
|
|
|
|
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 self.prefix.keys():
|
|
return (self.prefix[data[0]], data[1:]) # would use a set but it's possible these are the same
|
|
return (None, data)
|
|
else:
|
|
return (None, 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:
|
|
mode, nick = self.sanit(x)
|
|
if nick:
|
|
newNicklist.append((mode, nick))
|
|
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):
|
|
allRelays = chankeep.allRelaysActive(self.net)
|
|
if allRelays:
|
|
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):
|
|
prefix = prefix.replace(")", "")
|
|
prefix = prefix.replace("(", "")
|
|
length = len(prefix)
|
|
half = int(length/2)
|
|
prefixToMode = dict(zip(prefix[half:], prefix[:half]))
|
|
self.prefix = prefixToMode
|
|
|
|
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 regPing(self, negativepass=None):
|
|
if self.authenticated:
|
|
return
|
|
sinst = regproc.substitute(self.net, self.num)
|
|
if not self._negativePass == True:
|
|
if negativepass == False:
|
|
self._negativePass = False
|
|
return
|
|
if negativepass == True:
|
|
if self._negativePass == None:
|
|
self._negativePass = True
|
|
debug("Positive registration check - %s - %i" % (self.net, self.num))
|
|
if sinst["ping"]:
|
|
debug("Sending ping - %s - %i" % (self.net, self.num))
|
|
self.msg(sinst["entity"], sinst["pingmsg"])
|
|
return
|
|
else:
|
|
debug("Negative registration for %s - %i" % (self.net, self.num))
|
|
return
|
|
|
|
if sinst["check"]:
|
|
if sinst["negative"]:
|
|
self._negativePass = None
|
|
self.msg(sinst["entity"], sinst["negativemsg"])
|
|
return
|
|
else:
|
|
self._negativePass = True
|
|
if sinst["ping"]:
|
|
self.msg(sinst["entity"], sinst["pingmsg"])
|
|
return
|
|
else:
|
|
self.authenticated = True
|
|
warn("Won't send registration ping for %s - %i" % (self.net, self.num))
|
|
|
|
def signedOn(self):
|
|
log("signed on: %s - %i" % (self.net, self.num))
|
|
ctime = str(datetime.now().isoformat())
|
|
sendRelayNotification({"type": "conn", "net": self.net, "num": self.num, "status": "signedon", "time": ctime})
|
|
if not self.authenticated:
|
|
reactor.callLater(10, self.regPing)
|
|
|
|
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["time"] = str(datetime.now().isoformat())
|
|
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))
|
|
ctime = str(datetime.now().isoformat())
|
|
sendRelayNotification({"type": "conn", "net": self.net, "num": self.num, "status": "lost", "message": error, "time": ctime})
|
|
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))
|
|
ctime = str(datetime.now().isoformat())
|
|
sendRelayNotification({"type": "conn", "net": self.net, "num": self.num, "status": "failed", "message": error, "time": ctime})
|
|
self.retry(connector)
|
|
#ReconnectingClientFactory.clientConnectionFailed(self, connector, reason)
|
|
|