diff --git a/MiRCARTools/IrcClient.py b/MiRCARTools/IrcClient.py index ba1d2ec..64778af 100644 --- a/MiRCARTools/IrcClient.py +++ b/MiRCARTools/IrcClient.py @@ -38,12 +38,15 @@ class IrcClient: self.clientSocket.close() self.clientSocket = self.clientSocketFile = None; # }}} - # {{{ connect(self, preferFamily=socket.AF_INET, timeout=None): Connect to server and register w/ optional timeout - def connect(self, preferFamily=socket.AF_INET, timeout=None): + # {{{ connect(self, localAddr=None, preferFamily=socket.AF_INET, timeout=None): Connect to server and register w/ optional timeout + def connect(self, localAddr=None, preferFamily=socket.AF_INET, timeout=None): gaiInfo = socket.getaddrinfo(self.serverHname, self.serverPort, preferFamily, socket.SOCK_STREAM, socket.IPPROTO_TCP) self.clientSocket = socket.socket(*gaiInfo[0][:3]) self.clientSocket.setblocking(0) + if localAddr != None: + gaiInfo_ = socket.getaddrinfo(localAddr, None, preferFamily, socket.SOCK_STREAM, socket.IPPROTO_TCP) + self.clientSocket.bind(gaiInfo_[0][4]) try: self.clientSocket.connect(gaiInfo[0][4]) except BlockingIOError: @@ -60,16 +63,21 @@ class IrcClient: self.queue("USER", self.clientIdent, "0", "0", self.clientGecos) return True # }}} - # {{{ readline(self): Read and parse single line from server into canonicalised list, honouring timers - def readline(self): + # {{{ readline(self, timeout=30): Read and parse single line from server into canonicalised list, honouring timers + def readline(self, timeout=30): if self.clientNextTimeout: timeNow = time.time() if self.clientNextTimeout <= timeNow: return "" else: readySet = select.select([self.clientSocket.fileno()], [], [], self.clientNextTimeout - timeNow) + if len(readySet[0]) == 0 \ + and (time.time() - timeNow) >= timeout: + return "" else: - readySet = select.select([self.clientSocket.fileno()], [], []) + readySet = select.select([self.clientSocket.fileno()], [], [], timeout) + if len(readySet[0]) == 0: + return "" msg = self.clientSocketFile.readline() if len(msg): msg = msg.rstrip("\r\n") @@ -99,24 +107,31 @@ class IrcClient: msg += args[argNum] + " " self.clientQueue.append((msg + "\r\n").encode()) # }}} - # {{{ unqueue(self): Send all queued lines to server, honouring timers - def unqueue(self): + # {{{ unqueue(self, timeout=15): Send all queued lines to server, honouring timers + def unqueue(self, timeout=15): while self.clientQueue: msg = self.clientQueue[0]; msgLen = len(msg); msgBytesSent = 0; while msgBytesSent < msgLen: if self.clientNextTimeout: timeNow = time.time() if self.clientNextTimeout <= timeNow: - self.clientQueue[0] = msg; return; + self.clientQueue[0] = msg; return True; else: - readySet = select.select([], [self.clientSocket.fileno()], [], self.clientNextTimeout - timeNow) + readySet = select.select([], [self.clientSocket.fileno()], [], min(self.clientNextTimeout - timeNow, timeout)) if len(readySet[1]) == 0: - self.clientQueue[0] = msg; return; + timeNow_ = time.time() + if (timeNow_ - timeNow) >= timeout: + return False + else: + self.clientQueue[0] = msg; return True; else: - readySet = select.select([], [self.clientSocket.fileno()], []) + readySet = select.select([], [self.clientSocket.fileno()], [], timeout) + if len(readySet[1]) == 0: + return False msgBytesSent = self.clientSocket.send(msg) msg = msg[msgBytesSent:]; msgLen -= msgBytesSent; del self.clientQueue[0] + return True # }}} # diff --git a/MiRCARTools/IrcMiRCARTBot.py b/MiRCARTools/IrcMiRCARTBot.py index 1c1f7e3..78e54cb 100755 --- a/MiRCARTools/IrcMiRCARTBot.py +++ b/MiRCARTools/IrcMiRCARTBot.py @@ -34,8 +34,6 @@ from MiRCARTToPngFile import MiRCARTToPngFile class IrcMiRCARTBot(IrcClient.IrcClient): """IRC<->MiRC2png bot""" - clientChannelLastMessage = clientChannelOps = clientChannel = None - clientChannelRejoin = None imgurApiKey = MiRCARTImgurApiKey.imgurApiKey # {{{ ContentTooLargeException(Exception): Raised by _urlretrieveReportHook() given download size > 1 MB @@ -111,7 +109,7 @@ class IrcMiRCARTBot(IrcClient.IrcClient): def _dispatchPrivmsg(self, message): if message[2].lower() == self.clientChannel.lower() \ and message[3].startswith("!pngbot "): - if (int(time.time()) - self.clientLastMessage) < 5: + if (int(time.time()) - self.clientChannelLastMessage) < 5: self._log("Ignoring request on {} from {} due to rate limit: {}".format(message[2].lower(), message[0], message[3])) return elif message[0].split("!")[0].lower() not in self.clientChannelOps: @@ -161,7 +159,7 @@ class IrcMiRCARTBot(IrcClient.IrcClient): if imgurResponse[0] == 200: self._log("Uploaded as: {}".format(imgurResponse[1])) self.queue("PRIVMSG", message[2], "8/!\\ Uploaded as: {}".format(imgurResponse[1])) - self.clientLastMessage = int(time.time()) + self.clientChannelLastMessage = int(time.time()) else: self._log("Upload failed with HTTP status code {}".format(imgurResponse[0])) self._log("Message from website: {}".format(imgurResponse[1])) @@ -207,14 +205,15 @@ class IrcMiRCARTBot(IrcClient.IrcClient): if (totalSize > pow(2,20)): raise IrcMiRCARTBot.ContentTooLargeException # }}} - # {{{ connect(self, preferFamily=0, timeout=None): Connect to server and (re)initialise w/ optional timeout - def connect(self, preferFamily=0, timeout=None): + # {{{ connect(self, localAddr=None, preferFamily=0, timeout=None): Connect to server and (re)initialise w/ optional timeout + def connect(self, localAddr=None, preferFamily=0, timeout=None): self._log("Connecting to {}:{}...".format(self.serverHname, self.serverPort)) - if super().connect(preferFamily=preferFamily, timeout=timeout): + if super().connect(localAddr=localAddr, preferFamily=preferFamily, timeout=timeout): self._log("Connected to {}:{}.".format(self.serverHname, self.serverPort)) self._log("Registering on {}:{} as {}, {}, {}...".format(self.serverHname, self.serverPort, self.clientNick, self.clientIdent, self.clientGecos)) - self.clientLastMessage = 0; self.clientChannelOps = []; + self.clientChannelLastMessage = 0; self.clientChannelOps = []; self.clientChannelRejoin = False + self.clientHasPing = False return True else: return False @@ -226,13 +225,21 @@ class IrcMiRCARTBot(IrcClient.IrcClient): timeNow = time.time() if self.clientNextTimeout <= timeNow: self._dispatchTimer() - self.unqueue() - serverMessage = self.readline() - if serverMessage == None: + if self.unqueue() == False: self._dispatchNone(); break; - elif serverMessage == "": - continue - elif serverMessage[1] == "001": + else: + serverMessage = self.readline() + if serverMessage == None: + self._dispatchNone(); break; + elif serverMessage == "": + if self.clientHasPing: + self._dispatchNone(); break; + else: + self.clientHasPing = True + self.queue("PING", str(time.time())) + self._log("Ping...") + continue + if serverMessage[1] == "001": self._dispatch001(serverMessage) elif serverMessage[1] == "353": self._dispatch353(serverMessage) @@ -244,6 +251,9 @@ class IrcMiRCARTBot(IrcClient.IrcClient): self._dispatchMode(serverMessage) elif serverMessage[1] == "PING": self._dispatchPing(serverMessage) + elif serverMessage[1] == "PONG": + self._log("Pong.") + self.clientHasPing = False elif serverMessage[1] == "PRIVMSG": self._dispatchPrivmsg(serverMessage) # }}} @@ -259,22 +269,26 @@ class IrcMiRCARTBot(IrcClient.IrcClient): def main(optdict, *argv): _IrcMiRCARTBot = IrcMiRCARTBot(*argv) while True: + if "-l" in optdict: + localAddr = optdict["-l"] + else: + localAddr = None if "-4" in optdict: preferFamily = socket.AF_INET elif "-6" in optdict: preferFamily = socket.AF_INET6 else: preferFamily = 0 - if _IrcMiRCARTBot.connect(preferFamily=preferFamily, timeout=15): + if _IrcMiRCARTBot.connect(localAddr=localAddr, preferFamily=preferFamily, timeout=15): _IrcMiRCARTBot.dispatch() _IrcMiRCARTBot.close() time.sleep(15) if __name__ == "__main__": - optlist, argv = getopt(sys.argv[1:], "46") + optlist, argv = getopt(sys.argv[1:], "46l:") optdict = dict(optlist) if len(argv) < 1 or len(argv) > 4: - print("usage: {} [-4|-6] " \ + print("usage: {} [-4|-6] [-l ]" \ " " \ "[] " \ "[] " \