--- a/Network/IRC/IrcWidget.py Fri Sep 28 20:07:25 2018 +0200 +++ b/Network/IRC/IrcWidget.py Sat Sep 29 19:32:33 2018 +0200 @@ -16,7 +16,8 @@ import re import logging -from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QByteArray, QTimer +from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QByteArray, QTimer, \ + QDateTime from PyQt5.QtWidgets import QWidget, QToolButton, QLabel, QTabWidget from PyQt5.QtNetwork import QTcpSocket, QAbstractSocket try: @@ -127,7 +128,7 @@ self.networkWidget.initialize(self.__ircNetworkManager) self.networkWidget.connectNetwork.connect(self.__connectNetwork) self.networkWidget.editNetwork.connect(self.__editNetwork) - self.networkWidget.joinChannel.connect(self.__joinChannel) + self.networkWidget.joinChannel.connect(self.joinChannel) self.networkWidget.nickChanged.connect(self.__changeNick) self.networkWidget.sendData.connect(self.__send) self.networkWidget.away.connect(self.__away) @@ -288,9 +289,9 @@ for channel in self.__channelList: channel.setPartMessage(partMsg) - def __joinChannel(self, name, key=""): + def joinChannel(self, name, key=""): """ - Private slot to join a channel. + Public slot to join a channel. @param name name of the channel (string) @param key key of the channel (string) @@ -310,9 +311,13 @@ channel.initAutoWho() channel.sendData.connect(self.__send) + channel.sendCtcpRequest.connect(self.__sendCtcpRequest) channel.sendCtcpReply.connect(self.__sendCtcpReply) channel.channelClosed.connect(self.__closeChannel) channel.openPrivateChat.connect(self.__openPrivate) + channel.awayCommand.connect(self.networkWidget.handleAwayCommand) + channel.leaveChannels.connect(self.__leaveChannels) + channel.leaveAllChannels.connect(self.__leaveAllChannels) self.channelsWidget.addTab(channel, name) self.__channelList.append(channel) @@ -371,8 +376,12 @@ channel.addUsers([name, self.__nickName]) channel.sendData.connect(self.__send) + channel.sendCtcpRequest.connect(self.__sendCtcpRequest) channel.sendCtcpReply.connect(self.__sendCtcpReply) channel.channelClosed.connect(self.__closeChannel) + channel.awayCommand.connect(self.networkWidget.handleAwayCommand) + channel.leaveChannels.connect(self.__leaveChannels) + channel.leaveAllChannels.connect(self.__leaveAllChannels) self.channelsWidget.addTab(channel, name) self.__channelList.append(channel) @@ -386,6 +395,29 @@ channel = self.channelsWidget.currentWidget() channel.requestLeave() + @pyqtSlot(list) + def __leaveChannels(self, channelNames): + """ + Private slot to leave a list of channels and close their associated + tabs. + + @param channelNames list of channels to leave + @type list of str + """ + for channelName in channelNames: + for channel in self.__channelList: + if channel.name() == channelName: + channel.leaveChannel() + + @pyqtSlot() + def __leaveAllChannels(self): + """ + Private slot to leave all channels and close their tabs. + """ + while self.__channelList: + channel = self.__channelList[0] + channel.leaveChannel() + def __closeAllChannels(self): """ Private method to close all channels. @@ -441,12 +473,33 @@ self.__socket.write( QByteArray("{0}\r\n".format(data).encode("utf-8"))) + def __sendCtcpRequest(self, receiver, request, arguments): + """ + Private slot to send a CTCP request. + + @param receiver nick name of the receiver + @type str + @param request CTCP request to be sent + @type str + @param arguments arguments to be sent + @type str + """ + request = request.upper() + if request == "PING": + arguments = "Eric IRC {0}".format( + QDateTime.currentMSecsSinceEpoch()) + + self.__send("PRIVMSG {0} :\x01{1} {2}\x01".format( + receiver, request, arguments)) + def __sendCtcpReply(self, receiver, text): """ Private slot to send a CTCP reply. - @param receiver nick name of the receiver (string) - @param text text to be sent (string) + @param receiver nick name of the receiver + @type str + @param text text to be sent + @type str """ self.__send("NOTICE {0} :\x01{1}\x01".format(receiver, text)) @@ -551,6 +604,42 @@ self.__updateUsersCount() self.__buffer = "" + def __handleCtcpReply(self, match): + """ + Private method to handle a server message containing a CTCP reply. + + @param match reference to the match object + """ + if "!" in match.group(1): + sender = match.group(1).split("!", 1)[0] + + try: + ctcpCommand = match.group(3).split(":", 1)[1] + except IndexError: + ctcpCommand = match.group(3) + ctcpCommand = ctcpCommand[1:].split("\x01", 1)[0] + if " " in ctcpCommand: + ctcpReply, ctcpArg = ctcpCommand.split(" ", 1) + else: + ctcpReply, ctcpArg = ctcpCommand, "" + ctcpReply = ctcpReply.upper() + + if ctcpReply == "PING" and ctcpArg.startswith("Eric IRC "): + # it is a response to a ping request + pingDateTime = int(ctcpArg.split()[-1]) + latency = QDateTime.currentMSecsSinceEpoch() - pingDateTime + self.networkWidget.addServerMessage( + self.tr("CTCP"), + self.tr( + "Received CTCP-PING response from {0} with latency" + " of {1} ms.").format(sender, latency)) + else: + self.networkWidget.addServerMessage( + self.tr("CTCP"), + self.tr( + "Received unknown CTCP-{0} response from {1}.") + .format(ctcpReply, sender)) + def __handleNamedMessage(self, match): """ Private method to handle a server message containing a message name. @@ -564,6 +653,11 @@ msg = match.group(3).split(":", 1)[1] except IndexError: msg = match.group(3) + + if msg.startswith("\x01"): + self.__handleCtcpReply(match) + return True + if "!" in match.group(1): name = match.group(1).split("!", 1)[0] msg = "-{0}- {1}".format(name, msg) @@ -612,6 +706,11 @@ self.tr("User {0} is now known as {1}.").format( oldNick, newNick)) return True + elif name == "PONG": + nick = match.group(3).split(":", 1)[1] + self.networkWidget.addMessage( + self.tr("Received PONG from {0}").format(nick)) + return True elif name == "ERROR": self.networkWidget.addErrorMessage( self.tr("Server Error"), match.group(3).split(":", 1)[1]) @@ -749,7 +848,7 @@ if channel.autoJoin(): name = channel.getName() key = channel.getKey() - self.__joinChannel(name, key) + self.joinChannel(name, key) def __tcpError(self, error): """