diff --git a/core/bot.py b/core/bot.py index e2df83e..1b37f7c 100644 --- a/core/bot.py +++ b/core/bot.py @@ -3,7 +3,9 @@ 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 @@ -36,9 +38,33 @@ def deliverRelayCommands(num, relayCommands, user=None, stage2=None): port, bot, contextFactory) +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(' ') + args.append(trailing) + else: + args = s.split(' ') + command = args.pop(0) + return prefix, command, args + class IRCRelay(IRCClient): def __init__(self, num, relayCommands, user, stage2): - self.connected = False + self.isconnected = False self.buffer = "" if user == None: self.user = main.config["Relay"]["User"] @@ -78,23 +104,24 @@ class IRCRelay(IRCClient): deliverRelayCommands(self.num, commands, user, self.stage2) def signedOn(self): - self.connected = True - log("signed on as a relay: %s" % self.num) - #sendRelayNotification("Relay", {"type": "conn", "status": "connected"}) nobody actually cares - 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 - reactor.callLater(sleeptime, self.sendStage2) - reactor.callLater(sleeptime+5, self.transport.loseConnection) - return + if not self.isconnected: + self.isconnected = True + log("signed on as a relay: %s" % self.num) + #sendRelayNotification("Relay", {"type": "conn", "status": "connected"}) nobody actually cares + 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 + reactor.callLater(sleeptime, self.sendStage2) + reactor.callLater(sleeptime+5, self.transport.loseConnection) + return class IRCBot(IRCClient): def __init__(self, net, num): - self.connected = False + self.isconnected = False self.channels = [] self.net = net self.num = num @@ -121,12 +148,26 @@ class IRCBot(IRCClient): 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.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 + 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 @@ -172,8 +213,11 @@ class IRCBot(IRCClient): del cast["host"] del cast["channel"] if "Disconnected from IRC" in cast["msg"]: - log("ZNC disconnected on %s - %i" (self.net, self.num)) - self.connected = False + 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 if not cast["type"] in ["query", "self", "highlight", "znc", "who"]: if "channel" in cast.keys() and not cast["type"] == "mode": # don't handle modes here if cast["channel"].lower() == self.nickname.lower(): # as they are channel == nickname @@ -339,28 +383,38 @@ class IRCBot(IRCClient): self._tempList[0].append(d) if self.listSimple: self.sendLine("LIST") - return d + 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): - if not self.listAttempted: - self.listAttempted = True - else: + def list(self, noargs=False, nocheck=False): + if self.listAttempted: debug("List request dropped, already asked for LIST - %s - %i" % (self.net, self.num)) return - if not self.listOngoing: - self._list(noargs).addCallback(self.got_list) else: - debug("List request dropped, already ongoing - %s - %i" % (self.net, self.num)) + 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] @@ -369,6 +423,11 @@ class IRCBot(IRCClient): 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: @@ -379,13 +438,13 @@ class IRCBot(IRCClient): self._tempList[0].clear() self._tempList[1].clear() if noResults: - if not self.listRetried: - self.list(True) - self.listRetried = True - else: - warn("List still empty after retry: %s - %i" % (net, num)) + if self.listRetried: + warn("LIST still empty after retry: %s - %i" % (net, num)) self.listRetried = False return + else: + self.list(True) + self.listRetried = True else: if self.listRetried: self.listRetried = False @@ -402,7 +461,10 @@ class IRCBot(IRCClient): if not any((x for x in options if any(y in x for y in interested))): return # check if any of interested is in any of options, some networks chanlimit = None # call isupport() more than once, so discard swiftly anything - for i in options: # we don't care about + if not self.isconnected: # we don't care about + log("endpoint connected: %s - %i" % (self.net, self.num)) + self.isconnected = True + for i in options: if i.startswith("CHANLIMIT"): if ":" in i: split = i.split(":") @@ -415,14 +477,28 @@ class IRCBot(IRCClient): if len(split) == 2: chanlimit = split[1] break - if chanlimit: - try: - self.chanlimit = int(chanlimit) - except TypeError: - warn("Invalid chanlimit: %s" % i) - if self.num == 1: # Only one instance should do a list, so + print("chanlimit", chanlimit) + 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 unlimited + allRelays = chankeep.allRelaysActive(self.net) + print(self.net, self.num, allRelays) + if allRelays: + for i in main.network.keys(): + for x in main.network[i].relays.keys(): + name = i+str(x) + 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: - self.list() # why not this one? :P + if allRelays: + self.list() + else: + self.wantList = True else: debug("Aborting LIST due to bad chanlimit") self.checkChannels() @@ -496,7 +572,6 @@ class IRCBot(IRCClient): #END hacks def signedOn(self): - self.connected = True log("signed on: %s - %i" % (self.net, self.num)) #self.event(type="conn", status="connected") sendRelayNotification({"type": "conn", "net": self.net, "num": self.num, "status": "signedon"}) @@ -607,7 +682,7 @@ class IRCBotFactory(ReconnectingClientFactory): if not self.relay: userinfo.delChannels(self.net, self.client.channels) if not self.client == None: - self.client.connected = False + self.client.isconnected = False self.client.channels = [] error = reason.getErrorMessage() log("%s - %i: connection lost: %s" % (self.net, self.num, error)) @@ -619,7 +694,7 @@ class IRCBotFactory(ReconnectingClientFactory): def clientConnectionFailed(self, connector, reason): if not self.client == None: - self.client.connected = False + self.client.isconnected = False self.client.channels = [] error = reason.getErrorMessage() log("%s - %i: connection failed: %s" % (self.net, self.num, error)) diff --git a/modules/chankeep.py b/modules/chankeep.py index d9dd94c..6e174ca 100644 --- a/modules/chankeep.py +++ b/modules/chankeep.py @@ -12,7 +12,7 @@ def allRelaysActive(net): for i in main.network[net].relays.keys(): name = net+str(i) if name in main.IRCPool.keys(): - if main.IRCPool[name].connected: + if main.IRCPool[name].isconnected: existNum += 1 if existNum == relayNum: return True @@ -28,7 +28,8 @@ def getChanFree(net, new): chanfree[i] = main.IRCPool[name].chanlimit-len(main.IRCPool[name].channels) chanlimits.add(main.IRCPool[name].chanlimit) if not len(chanlimits) == 1: - error("Network %s has servers with different CHANMAX values" % net) + error("Network %s has servers with different CHANLIMIT values" % net) + print(chanlimits) return False return (chanfree, chanlimits.pop()) @@ -72,7 +73,7 @@ def notifyJoin(net): def minifyChans(net, listinfo): if not allRelaysActive(net): - error("All relays for %s are not active, cannot minify list") + error("All relays for %s are not active, cannot minify list" % net) return False for i in main.network[net].relays.keys(): name = net+str(i)