Fix various bugs uncovered by the LIST system
* Work around Twisted's broken handling of spaces * Work around Twisted's broken line decoding * Don't run signedOn twice for relays * Improved detection of whether the endpoint is connected to ZNC * Delay a LIST until all configured relays are online * Discard a LIST if there are no callbacks for it * Get rid of some double-negative ternary blocks
This commit is contained in:
parent
b4fa747853
commit
7ffb6125aa
163
core/bot.py
163
core/bot.py
|
@ -3,7 +3,9 @@ from twisted.words.protocols.irc import IRCClient
|
||||||
from twisted.internet.defer import Deferred
|
from twisted.internet.defer import Deferred
|
||||||
from twisted.internet.task import LoopingCall
|
from twisted.internet.task import LoopingCall
|
||||||
from twisted.internet import reactor, task
|
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 string import digits
|
||||||
from random import randint
|
from random import randint
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
@ -36,9 +38,33 @@ def deliverRelayCommands(num, relayCommands, user=None, stage2=None):
|
||||||
port,
|
port,
|
||||||
bot, contextFactory)
|
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):
|
class IRCRelay(IRCClient):
|
||||||
def __init__(self, num, relayCommands, user, stage2):
|
def __init__(self, num, relayCommands, user, stage2):
|
||||||
self.connected = False
|
self.isconnected = False
|
||||||
self.buffer = ""
|
self.buffer = ""
|
||||||
if user == None:
|
if user == None:
|
||||||
self.user = main.config["Relay"]["User"]
|
self.user = main.config["Relay"]["User"]
|
||||||
|
@ -78,23 +104,24 @@ class IRCRelay(IRCClient):
|
||||||
deliverRelayCommands(self.num, commands, user, self.stage2)
|
deliverRelayCommands(self.num, commands, user, self.stage2)
|
||||||
|
|
||||||
def signedOn(self):
|
def signedOn(self):
|
||||||
self.connected = True
|
if not self.isconnected:
|
||||||
log("signed on as a relay: %s" % self.num)
|
self.isconnected = True
|
||||||
#sendRelayNotification("Relay", {"type": "conn", "status": "connected"}) nobody actually cares
|
log("signed on as a relay: %s" % self.num)
|
||||||
sleeptime = 0
|
#sendRelayNotification("Relay", {"type": "conn", "status": "connected"}) nobody actually cares
|
||||||
increment = 0.8
|
sleeptime = 0
|
||||||
for i in self.relayCommands.keys():
|
increment = 0.8
|
||||||
for x in self.relayCommands[i]:
|
for i in self.relayCommands.keys():
|
||||||
reactor.callLater(sleeptime, self.msg, main.config["Tweaks"]["ZNC"]["Prefix"]+i, x)
|
for x in self.relayCommands[i]:
|
||||||
sleeptime += increment
|
reactor.callLater(sleeptime, self.msg, main.config["Tweaks"]["ZNC"]["Prefix"]+i, x)
|
||||||
increment += 0.8
|
sleeptime += increment
|
||||||
reactor.callLater(sleeptime, self.sendStage2)
|
increment += 0.8
|
||||||
reactor.callLater(sleeptime+5, self.transport.loseConnection)
|
reactor.callLater(sleeptime, self.sendStage2)
|
||||||
return
|
reactor.callLater(sleeptime+5, self.transport.loseConnection)
|
||||||
|
return
|
||||||
|
|
||||||
class IRCBot(IRCClient):
|
class IRCBot(IRCClient):
|
||||||
def __init__(self, net, num):
|
def __init__(self, net, num):
|
||||||
self.connected = False
|
self.isconnected = False
|
||||||
self.channels = []
|
self.channels = []
|
||||||
self.net = net
|
self.net = net
|
||||||
self.num = num
|
self.num = num
|
||||||
|
@ -121,12 +148,26 @@ class IRCBot(IRCClient):
|
||||||
self.listOngoing = False # we are currently receiving a LIST
|
self.listOngoing = False # we are currently receiving a LIST
|
||||||
self.listRetried = False # we asked and got nothing so asked again
|
self.listRetried = False # we asked and got nothing so asked again
|
||||||
self.listAttempted = False # we asked for a list
|
self.listAttempted = False # we asked for a list
|
||||||
self.listSimple = False # after asking again we got the list, so
|
self.listSimple = False # after asking again we got the list, so use the simple
|
||||||
# use the simple syntax from now on
|
# syntax from now on
|
||||||
|
self.wantList = False # we want to send a LIST, but not all relays are active yet
|
||||||
self.chanlimit = 0
|
self.chanlimit = 0
|
||||||
self.servername = None
|
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):
|
def joinChannels(self, channels):
|
||||||
sleeptime = 0.0
|
sleeptime = 0.0
|
||||||
increment = 0.8
|
increment = 0.8
|
||||||
|
@ -172,8 +213,11 @@ class IRCBot(IRCClient):
|
||||||
del cast["host"]
|
del cast["host"]
|
||||||
del cast["channel"]
|
del cast["channel"]
|
||||||
if "Disconnected from IRC" in cast["msg"]:
|
if "Disconnected from IRC" in cast["msg"]:
|
||||||
log("ZNC disconnected on %s - %i" (self.net, self.num))
|
log("ZNC disconnected on %s - %i" % (self.net, self.num))
|
||||||
self.connected = False
|
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 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 "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
|
if cast["channel"].lower() == self.nickname.lower(): # as they are channel == nickname
|
||||||
|
@ -339,28 +383,38 @@ class IRCBot(IRCClient):
|
||||||
self._tempList[0].append(d)
|
self._tempList[0].append(d)
|
||||||
if self.listSimple:
|
if self.listSimple:
|
||||||
self.sendLine("LIST")
|
self.sendLine("LIST")
|
||||||
return d
|
return d # return early if we know what to do
|
||||||
|
|
||||||
if noargs:
|
if noargs:
|
||||||
self.sendLine("LIST")
|
self.sendLine("LIST")
|
||||||
else:
|
else:
|
||||||
self.sendLine("LIST >0")
|
self.sendLine("LIST >0")
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def list(self, noargs=False):
|
def list(self, noargs=False, nocheck=False):
|
||||||
if not self.listAttempted:
|
if self.listAttempted:
|
||||||
self.listAttempted = True
|
|
||||||
else:
|
|
||||||
debug("List request dropped, already asked for LIST - %s - %i" % (self.net, self.num))
|
debug("List request dropped, already asked for LIST - %s - %i" % (self.net, self.num))
|
||||||
return
|
return
|
||||||
if not self.listOngoing:
|
|
||||||
self._list(noargs).addCallback(self.got_list)
|
|
||||||
else:
|
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
|
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):
|
def irc_RPL_LISTSTART(self, prefix, params):
|
||||||
self.listAttempted = False
|
self.listAttempted = False
|
||||||
self.listOngoing = True
|
self.listOngoing = True
|
||||||
|
self.wantList = False
|
||||||
|
|
||||||
def irc_RPL_LIST(self, prefix, params):
|
def irc_RPL_LIST(self, prefix, params):
|
||||||
channel = params[1]
|
channel = params[1]
|
||||||
|
@ -369,6 +423,11 @@ class IRCBot(IRCClient):
|
||||||
self._tempList[1].append([channel, users, topic])
|
self._tempList[1].append([channel, users, topic])
|
||||||
|
|
||||||
def irc_RPL_LISTEND(self, prefix, params):
|
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
|
callbacks, info = self._tempList
|
||||||
self.listOngoing = False
|
self.listOngoing = False
|
||||||
for cb in callbacks:
|
for cb in callbacks:
|
||||||
|
@ -379,13 +438,13 @@ class IRCBot(IRCClient):
|
||||||
self._tempList[0].clear()
|
self._tempList[0].clear()
|
||||||
self._tempList[1].clear()
|
self._tempList[1].clear()
|
||||||
if noResults:
|
if noResults:
|
||||||
if not self.listRetried:
|
if self.listRetried:
|
||||||
self.list(True)
|
warn("LIST still empty after retry: %s - %i" % (net, num))
|
||||||
self.listRetried = True
|
|
||||||
else:
|
|
||||||
warn("List still empty after retry: %s - %i" % (net, num))
|
|
||||||
self.listRetried = False
|
self.listRetried = False
|
||||||
return
|
return
|
||||||
|
else:
|
||||||
|
self.list(True)
|
||||||
|
self.listRetried = True
|
||||||
else:
|
else:
|
||||||
if self.listRetried:
|
if self.listRetried:
|
||||||
self.listRetried = False
|
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))):
|
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
|
return # check if any of interested is in any of options, some networks
|
||||||
chanlimit = None # call isupport() more than once, so discard swiftly anything
|
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 i.startswith("CHANLIMIT"):
|
||||||
if ":" in i:
|
if ":" in i:
|
||||||
split = i.split(":")
|
split = i.split(":")
|
||||||
|
@ -415,14 +477,28 @@ class IRCBot(IRCClient):
|
||||||
if len(split) == 2:
|
if len(split) == 2:
|
||||||
chanlimit = split[1]
|
chanlimit = split[1]
|
||||||
break
|
break
|
||||||
if chanlimit:
|
print("chanlimit", chanlimit)
|
||||||
try:
|
try:
|
||||||
self.chanlimit = int(chanlimit)
|
self.chanlimit = int(chanlimit)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
warn("Invalid chanlimit: %s" % i)
|
warn("Invalid chanlimit: %s" % i)
|
||||||
if self.num == 1: # Only one instance should do a list, so
|
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:
|
if self.chanlimit:
|
||||||
self.list() # why not this one? :P
|
if allRelays:
|
||||||
|
self.list()
|
||||||
|
else:
|
||||||
|
self.wantList = True
|
||||||
else:
|
else:
|
||||||
debug("Aborting LIST due to bad chanlimit")
|
debug("Aborting LIST due to bad chanlimit")
|
||||||
self.checkChannels()
|
self.checkChannels()
|
||||||
|
@ -496,7 +572,6 @@ class IRCBot(IRCClient):
|
||||||
#END hacks
|
#END hacks
|
||||||
|
|
||||||
def signedOn(self):
|
def signedOn(self):
|
||||||
self.connected = True
|
|
||||||
log("signed on: %s - %i" % (self.net, self.num))
|
log("signed on: %s - %i" % (self.net, self.num))
|
||||||
#self.event(type="conn", status="connected")
|
#self.event(type="conn", status="connected")
|
||||||
sendRelayNotification({"type": "conn", "net": self.net, "num": self.num, "status": "signedon"})
|
sendRelayNotification({"type": "conn", "net": self.net, "num": self.num, "status": "signedon"})
|
||||||
|
@ -607,7 +682,7 @@ class IRCBotFactory(ReconnectingClientFactory):
|
||||||
if not self.relay:
|
if not self.relay:
|
||||||
userinfo.delChannels(self.net, self.client.channels)
|
userinfo.delChannels(self.net, self.client.channels)
|
||||||
if not self.client == None:
|
if not self.client == None:
|
||||||
self.client.connected = False
|
self.client.isconnected = False
|
||||||
self.client.channels = []
|
self.client.channels = []
|
||||||
error = reason.getErrorMessage()
|
error = reason.getErrorMessage()
|
||||||
log("%s - %i: connection lost: %s" % (self.net, self.num, error))
|
log("%s - %i: connection lost: %s" % (self.net, self.num, error))
|
||||||
|
@ -619,7 +694,7 @@ class IRCBotFactory(ReconnectingClientFactory):
|
||||||
|
|
||||||
def clientConnectionFailed(self, connector, reason):
|
def clientConnectionFailed(self, connector, reason):
|
||||||
if not self.client == None:
|
if not self.client == None:
|
||||||
self.client.connected = False
|
self.client.isconnected = False
|
||||||
self.client.channels = []
|
self.client.channels = []
|
||||||
error = reason.getErrorMessage()
|
error = reason.getErrorMessage()
|
||||||
log("%s - %i: connection failed: %s" % (self.net, self.num, error))
|
log("%s - %i: connection failed: %s" % (self.net, self.num, error))
|
||||||
|
|
|
@ -12,7 +12,7 @@ def allRelaysActive(net):
|
||||||
for i in main.network[net].relays.keys():
|
for i in main.network[net].relays.keys():
|
||||||
name = net+str(i)
|
name = net+str(i)
|
||||||
if name in main.IRCPool.keys():
|
if name in main.IRCPool.keys():
|
||||||
if main.IRCPool[name].connected:
|
if main.IRCPool[name].isconnected:
|
||||||
existNum += 1
|
existNum += 1
|
||||||
if existNum == relayNum:
|
if existNum == relayNum:
|
||||||
return True
|
return True
|
||||||
|
@ -28,7 +28,8 @@ def getChanFree(net, new):
|
||||||
chanfree[i] = main.IRCPool[name].chanlimit-len(main.IRCPool[name].channels)
|
chanfree[i] = main.IRCPool[name].chanlimit-len(main.IRCPool[name].channels)
|
||||||
chanlimits.add(main.IRCPool[name].chanlimit)
|
chanlimits.add(main.IRCPool[name].chanlimit)
|
||||||
if not len(chanlimits) == 1:
|
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 False
|
||||||
return (chanfree, chanlimits.pop())
|
return (chanfree, chanlimits.pop())
|
||||||
|
|
||||||
|
@ -72,7 +73,7 @@ def notifyJoin(net):
|
||||||
|
|
||||||
def minifyChans(net, listinfo):
|
def minifyChans(net, listinfo):
|
||||||
if not allRelaysActive(net):
|
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
|
return False
|
||||||
for i in main.network[net].relays.keys():
|
for i in main.network[net].relays.keys():
|
||||||
name = net+str(i)
|
name = net+str(i)
|
||||||
|
|
Loading…
Reference in New Issue