eric6/Network/IRC/IrcWidget.py

Sat, 21 Sep 2019 18:30:02 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sat, 21 Sep 2019 18:30:02 +0200
changeset 7255
d595f6f9cbf8
parent 7229
53054eb5b15a
child 7360
9190402e4505
permissions
-rw-r--r--

Continued to resolve code style issue M841.

# -*- coding: utf-8 -*-

# Copyright (c) 2012 - 2019 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
Module implementing the IRC window.
"""


import re
import logging

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:
    from PyQt5.QtNetwork import QSslSocket, QSslConfiguration
    from E5Network.E5SslErrorHandler import E5SslErrorHandler
    SSL_AVAILABLE = True
except ImportError:
    SSL_AVAILABLE = False

from E5Gui import E5MessageBox

from .Ui_IrcWidget import Ui_IrcWidget

import Preferences
import UI.PixmapCache

from Globals import isMacPlatform

from UI.Info import Version, Copyright


class IrcWidget(QWidget, Ui_IrcWidget):
    """
    Class implementing the IRC window.
    
    @signal autoConnected() emitted after an automatic connection was initiated
    """
    autoConnected = pyqtSignal()
    
    ServerDisconnected = 1
    ServerConnected = 2
    ServerConnecting = 3
    
    def __init__(self, parent=None):
        """
        Constructor
        
        @param parent reference to the parent widget (QWidget)
        """
        super(IrcWidget, self).__init__(parent)
        self.setupUi(self)
        
        from .IrcNetworkManager import IrcNetworkManager
        self.__ircNetworkManager = IrcNetworkManager(self)
        
        self.__leaveButton = QToolButton(self)
        self.__leaveButton.setIcon(
            UI.PixmapCache.getIcon("ircCloseChannel.png"))
        self.__leaveButton.setToolTip(
            self.tr("Press to leave the current channel"))
        self.__leaveButton.clicked.connect(self.__leaveChannel)
        self.__leaveButton.setEnabled(False)
        self.channelsWidget.setCornerWidget(
            self.__leaveButton, Qt.BottomRightCorner)
        self.channelsWidget.setTabsClosable(False)
        if not isMacPlatform():
            self.channelsWidget.setTabPosition(QTabWidget.South)
        
        height = self.height()
        self.splitter.setSizes([height * 0.6, height * 0.4])
        
        self.__channelList = []
        self.__channelTypePrefixes = ""
        self.__userName = ""
        self.__identityName = ""
        self.__quitMessage = ""
        self.__nickIndex = -1
        self.__nickName = ""
        self.__server = None
        self.__registering = False
        
        self.__connectionState = IrcWidget.ServerDisconnected
        self.__sslErrorLock = False
        
        self.__buffer = ""
        self.__userPrefix = {}
        
        self.__socket = None
        if SSL_AVAILABLE:
            self.__sslErrorHandler = E5SslErrorHandler(self)
        else:
            self.__sslErrorHandler = None
        
        self.__patterns = [
            # :foo_!n=foo@foohost.bar.net PRIVMSG bar_ :some long message
            (re.compile(r":([^!]+)!([^ ]+)\sPRIVMSG\s([^ ]+)\s:(.*)"),
             self.__query),
            # :foo.bar.net COMMAND some message
            (re.compile(r""":([^ ]+)\s+([A-Z]+)\s+(.+)"""),
             self.__handleNamedMessage),
            # :foo.bar.net 123 * :info
            (re.compile(r""":([^ ]+)\s+(\d{3})\s+(.+)"""),
             self.__handleNumericMessage),
            # PING :ping message
            (re.compile(r"""PING\s+:(.*)"""), self.__ping),
        ]
        self.__prefixRe = re.compile(r""".*\sPREFIX=\((.*)\)([^ ]+).*""")
        self.__chanTypesRe = re.compile(r""".*\sCHANTYPES=([^ ]+).*""")
        
        ircPic = UI.PixmapCache.getPixmap("irc128.png")
        self.__emptyLabel = QLabel()
        self.__emptyLabel.setPixmap(ircPic)
        self.__emptyLabel.setAlignment(Qt.AlignVCenter | Qt.AlignHCenter)
        self.channelsWidget.addTab(self.__emptyLabel, "")
        
        # all initialized, do connections now
        self.__ircNetworkManager.dataChanged.connect(self.__networkDataChanged)
        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.nickChanged.connect(self.__changeNick)
        self.networkWidget.sendData.connect(self.__send)
        self.networkWidget.away.connect(self.__away)
        self.networkWidget.autoConnected.connect(self.autoConnected)
    
    def shutdown(self):
        """
        Public method to shut down the widget.
        
        @return flag indicating successful shutdown (boolean)
        """
        if self.__server:
            if Preferences.getIrc("AskOnShutdown"):
                ok = E5MessageBox.yesNo(
                    self,
                    self.tr("Disconnect from Server"),
                    self.tr(
                        """<p>Do you really want to disconnect from"""
                        """ <b>{0}</b>?</p><p>All channels will be closed."""
                        """</p>""").format(self.__server.getName()))
            else:
                ok = True
            if ok:
                self.__connectNetwork("", False, True)
        else:
            ok = True
        
        if ok:
            self.__ircNetworkManager.close()
        
        return ok
    
    def autoConnect(self):
        """
        Public method to initiate the IRC auto connection.
        """
        self.networkWidget.autoConnect()

    def __connectNetwork(self, name, connect, silent):
        """
        Private slot to connect to or disconnect from the given network.
        
        @param name name of the network to connect to (string)
        @param connect flag indicating to connect (boolean)
        @param silent flag indicating a silent connect/disconnect (boolean)
        """
        if connect:
            network = self.__ircNetworkManager.getNetwork(name)
            if network:
                self.__server = network.getServer()
                self.__identityName = network.getIdentityName()
                identity = self.__ircNetworkManager.getIdentity(
                    self.__identityName)
                if identity:
                    self.__userName = identity.getIdent()
                    self.__quitMessage = identity.getQuitMessage()
                    if self.__server:
                        useSSL = self.__server.useSSL()
                        if useSSL and not SSL_AVAILABLE:
                            E5MessageBox.critical(
                                self,
                                self.tr("SSL Connection"),
                                self.tr(
                                    """An encrypted connection to the IRC"""
                                    """ network was requested but SSL is not"""
                                    """ available. Please change the server"""
                                    """ configuration."""))
                            return
                        
                        if useSSL:
                            # create SSL socket
                            self.__socket = QSslSocket(self)
                            self.__socket.encrypted.connect(
                                self.__hostConnected)
                            self.__socket.sslErrors.connect(
                                self.__sslErrors)
                        else:
                            # create TCP socket
                            self.__socket = QTcpSocket(self)
                            self.__socket.connected.connect(
                                self.__hostConnected)
                        self.__socket.hostFound.connect(
                            self.__hostFound)
                        self.__socket.disconnected.connect(
                            self.__hostDisconnected)
                        self.__socket.readyRead.connect(
                            self.__readyRead)
                        self.__socket.error.connect(
                            self.__tcpError)
                        
                        self.__connectionState = IrcWidget.ServerConnecting
                        if useSSL:
                            self.networkWidget.addServerMessage(
                                self.tr("Info"),
                                self.tr("Looking for server {0} (port {1})"
                                        " using an SSL encrypted connection"
                                        "...").format(self.__server.getName(),
                                                      self.__server.getPort()))
                            self.__socket.connectToHostEncrypted(
                                self.__server.getName(),
                                self.__server.getPort()
                            )
                        else:
                            self.networkWidget.addServerMessage(
                                self.tr("Info"),
                                self.tr(
                                    "Looking for server {0} (port {1})...")
                                .format(
                                    self.__server.getName(),
                                    self.__server.getPort()))
                            self.__socket.connectToHost(
                                self.__server.getName(),
                                self.__server.getPort())
        else:
            if silent:
                ok = True
            else:
                ok = E5MessageBox.yesNo(
                    self,
                    self.tr("Disconnect from Server"),
                    self.tr("""<p>Do you really want to disconnect from"""
                            """ <b>{0}</b>?</p><p>All channels will be"""
                            """ closed.</p>""")
                    .format(self.__server.getName()))
            if ok:
                if self.__server is not None:
                    self.networkWidget.addServerMessage(
                        self.tr("Info"),
                        self.tr("Disconnecting from server {0}...").format(
                            self.__server.getName()))
                elif name:
                    self.networkWidget.addServerMessage(
                        self.tr("Info"),
                        self.tr("Disconnecting from network {0}...").format(
                            name))
                else:
                    self.networkWidget.addServerMessage(
                        self.tr("Info"),
                        self.tr("Disconnecting from server."))
                self.__closeAllChannels()
                self.__send("QUIT :" + self.__quitMessage)
                if self.__socket:
                    self.__socket.flush()
                    self.__socket.close()
                    if self.__socket:
                        # socket is still existing
                        self.__socket.deleteLater()
                        self.__socket = None
                self.__userName = ""
                self.__identityName = ""
                self.__quitMessage = ""
    
    def __editNetwork(self, name):
        """
        Private slot to edit the network configuration.
        
        @param name name of the network to edit (string)
        """
        from .IrcNetworkListDialog import IrcNetworkListDialog
        dlg = IrcNetworkListDialog(self.__ircNetworkManager, self)
        dlg.exec_()
    
    def __networkDataChanged(self):
        """
        Private slot handling changes of the network and identity definitions.
        """
        identity = self.__ircNetworkManager.getIdentity(self.__identityName)
        if identity:
            partMsg = identity.getPartMessage()
            for channel in self.__channelList:
                channel.setPartMessage(partMsg)
    
    def joinChannel(self, name, key=""):
        """
        Public slot to join a channel.
        
        @param name name of the channel (string)
        @param key key of the channel (string)
        """
        # step 1: check, if this channel is already joined
        for channel in self.__channelList:
            if channel.name() == name:
                return
        
        from .IrcChannelWidget import IrcChannelWidget
        channel = IrcChannelWidget(self)
        channel.setName(name)
        channel.setUserName(self.__nickName)
        identity = self.__ircNetworkManager.getIdentity(self.__identityName)
        if identity:
            channel.setPartMessage(identity.getPartMessage())
        channel.setUserPrivilegePrefix(self.__userPrefix)
        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)
        self.channelsWidget.setCurrentWidget(channel)
        
        joinCommand = ["JOIN", name]
        if key:
            joinCommand.append(key)
        self.__send(" ".join(joinCommand))
        self.__send("MODE " + name)
        
        emptyIndex = self.channelsWidget.indexOf(self.__emptyLabel)
        if emptyIndex > -1:
            self.channelsWidget.removeTab(emptyIndex)
            self.__leaveButton.setEnabled(True)
        self.channelsWidget.setTabsClosable(True)
    
    def __query(self, match):
        """
        Private method to handle a new private connection.
        
        @param match reference to the match object
        @return flag indicating, if the message was handled (boolean)
        """
        # group(1)   sender user name
        # group(2)   sender user@host
        # group(3)   target nick
        # group(4)   message
        if match.group(4).startswith("\x01"):
            return self.__handleCtcp(match)
        
        self.__openPrivate(match.group(1))
        # the above call sets the new channel as the current widget
        channel = self.channelsWidget.currentWidget()
        channel.addMessage(match.group(1), match.group(4))
        channel.setPrivateInfo(
            "{0} - {1}".format(match.group(1), match.group(2)))
        
        return True
    
    @pyqtSlot(str)
    def __openPrivate(self, name):
        """
        Private slot to open a private chat with the given user.
        
        @param name name of the user (string)
        """
        from .IrcChannelWidget import IrcChannelWidget
        channel = IrcChannelWidget(self)
        channel.setName(self.__nickName)
        channel.setUserName(self.__nickName)
        identity = self.__ircNetworkManager.getIdentity(self.__identityName)
        if identity:
            channel.setPartMessage(identity.getPartMessage())
        channel.setUserPrivilegePrefix(self.__userPrefix)
        channel.setPrivate(True, name)
        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)
        self.channelsWidget.setCurrentWidget(channel)
    
    @pyqtSlot()
    def __leaveChannel(self):
        """
        Private slot to leave a channel and close the associated tab.
        """
        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.
        """
        while self.__channelList:
            channel = self.__channelList.pop()
            self.channelsWidget.removeTab(self.channelsWidget.indexOf(channel))
            channel.deleteLater()
            channel = None
        
        self.channelsWidget.addTab(self.__emptyLabel, "")
        self.__emptyLabel.show()
        self.__leaveButton.setEnabled(False)
        self.channelsWidget.setTabsClosable(False)
    
    def __closeChannel(self, name):
        """
        Private slot handling the closing of a channel.
        
        @param name name of the closed channel (string)
        """
        for channel in self.__channelList:
            if channel.name() == name:
                self.channelsWidget.removeTab(
                    self.channelsWidget.indexOf(channel))
                self.__channelList.remove(channel)
                channel.deleteLater()
        
        if self.channelsWidget.count() == 0:
            self.channelsWidget.addTab(self.__emptyLabel, "")
            self.__emptyLabel.show()
            self.__leaveButton.setEnabled(False)
            self.channelsWidget.setTabsClosable(False)
    
    @pyqtSlot(int)
    def on_channelsWidget_tabCloseRequested(self, index):
        """
        Private slot to close a channel by pressing the close button of
        the channels widget.
        
        @param index index of the tab to be closed (integer)
        """
        channel = self.channelsWidget.widget(index)
        channel.requestLeave()
    
    def __send(self, data):
        """
        Private slot to send data to the IRC server.
        
        @param data data to be sent (string)
        """
        if self.__socket:
            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
        @type str
        @param text text to be sent
        @type str
        """
        self.__send("NOTICE {0} :\x01{1}\x01".format(receiver, text))
    
    def __hostFound(self):
        """
        Private slot to indicate the host was found.
        """
        self.networkWidget.addServerMessage(
            self.tr("Info"),
            self.tr("Server found,connecting..."))
    
    def __hostConnected(self):
        """
        Private slot to log in to the server after the connection was
        established.
        """
        self.networkWidget.addServerMessage(
            self.tr("Info"),
            self.tr("Connected,logging in..."))
        self.networkWidget.setConnected(True)
        
        self.__registering = True
        serverPassword = self.__server.getPassword()
        if serverPassword:
            self.__send("PASS " + serverPassword)
        
        identity = self.__ircNetworkManager.getIdentity(
            self.__identityName)
        nick = self.networkWidget.getNickname()
        if not nick and identity:
            self.__nickIndex = 0
            try:
                nick = identity.getNickNames()[self.__nickIndex]
            except IndexError:
                nick = ""
        if not nick:
            nick = self.__userName
        self.__nickName = nick
        self.networkWidget.setNickName(nick)
        if identity:
            realName = identity.getRealName()
            if not realName:
                realName = "eric IDE chat"
            self.__send("NICK " + nick)
            self.__send("USER " + self.__userName + " 0 * :" + realName)
    
    def __hostDisconnected(self):
        """
        Private slot to indicate the host was disconnected.
        """
        if self.networkWidget.isConnected():
            self.__closeAllChannels()
            self.networkWidget.addServerMessage(
                self.tr("Info"),
                self.tr("Server disconnected."))
            self.networkWidget.setRegistered(False)
            self.networkWidget.setConnected(False)
            self.__server = None
            self.__nickName = ""
            self.__nickIndex = -1
            self.__channelTypePrefixes = ""
            
            if self.__socket:
                self.__socket.deleteLater()
            self.__socket = None
            
            self.__connectionState = IrcWidget.ServerDisconnected
            self.__sslErrorLock = False
    
    def __readyRead(self):
        """
        Private slot to read data from the socket.
        """
        if self.__socket:
            self.__buffer += str(
                self.__socket.readAll(),
                Preferences.getSystem("IOEncoding"),
                'replace')
            if self.__buffer.endswith("\r\n"):
                for line in self.__buffer.splitlines():
                    line = line.strip()
                    if line:
                        logging.debug("<IRC> %s", line)
                        handled = False
                        # step 1: give channels a chance to handle the message
                        for channel in self.__channelList:
                            handled = channel.handleMessage(line)
                            if handled:
                                break
                        else:
                            # step 2: try to process the message ourselves
                            for patternRe, patternFunc in self.__patterns:
                                match = patternRe.match(line)
                                if match is not None:
                                    if patternFunc(match):
                                        break
                            else:
                                # Oops, the message wasn't handled
                                self.networkWidget.addErrorMessage(
                                    self.tr("Message Error"),
                                    self.tr(
                                        "Unknown message received from server:"
                                        "<br/>{0}").format(line))
                
                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.
        
        @param match reference to the match object
        @return flag indicating, if the message was handled (boolean)
        """
        name = match.group(2)
        if name == "NOTICE":
            try:
                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)
            self.networkWidget.addServerMessage(self.tr("Notice"), msg)
            return True
        elif name == "MODE":
            self.__registering = False
            if ":" in match.group(3):
                # :foo MODE foo :+i
                name, modes = match.group(3).split(" :")
                sourceNick = match.group(1)
                if not self.isChannelName(name):
                    if name == self.__nickName:
                        if sourceNick == self.__nickName:
                            msg = self.tr(
                                "You have set your personal modes to"
                                " <b>[{0}]</b>.").format(modes)
                        else:
                            msg = self.tr(
                                "{0} has changed your personal modes to"
                                " <b>[{1}]</b>.").format(sourceNick, modes)
                        self.networkWidget.addServerMessage(
                            self.tr("Mode"), msg, filterMsg=False)
                        return True
        elif name == "PART":
            nick = match.group(1).split("!", 1)[0]
            if nick == self.__nickName:
                channel = match.group(3).split(None, 1)[0]
                self.networkWidget.addMessage(
                    self.tr("You have left channel {0}.").format(channel))
                return True
        elif name == "QUIT":
            # don't do anything with it here
            return True
        elif name == "NICK":
            # :foo_!n=foo@foohost.bar.net NICK :newnick
            oldNick = match.group(1).split("!", 1)[0]
            newNick = match.group(3).split(":", 1)[1]
            if oldNick == self.__nickName:
                self.networkWidget.addMessage(
                    self.tr("You are now known as {0}.").format(newNick))
                self.__nickName = newNick
                self.networkWidget.setNickName(newNick)
            else:
                self.networkWidget.addMessage(
                    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])
            return True
        
        return False
    
    def __handleNumericMessage(self, match):
        """
        Private method to handle a server message containing a numeric code.
        
        @param match reference to the match object
        @return flag indicating, if the message was handled (boolean)
        """
        code = int(match.group(2))
        if code < 400:
            return self.__handleServerReply(
                code, match.group(1), match.group(3))
        else:
            return self.__handleServerError(
                code, match.group(1), match.group(3))
    
    def __handleServerError(self, code, server, message):
        """
        Private slot to handle a server error reply.
        
        @param code numerical code sent by the server (integer)
        @param server name of the server (string)
        @param message message sent by the server (string)
        @return flag indicating, if the message was handled (boolean)
        """
        if code == 433:
            if self.__registering:
                self.__handleNickInUseLogin()
            else:
                self.__handleNickInUse()
        else:
            self.networkWidget.addServerMessage(self.tr("Error"), message)
        
        return True
    
    def __handleServerReply(self, code, server, message):
        """
        Private slot to handle a server reply.
        
        @param code numerical code sent by the server (integer)
        @param server name of the server (string)
        @param message message sent by the server (string)
        @return flag indicating, if the message was handled (boolean)
        """
        # determine message type
        if code in [1, 2, 3, 4]:
            msgType = self.tr("Welcome")
        elif code == 5:
            msgType = self.tr("Support")
        elif code in [250, 251, 252, 253, 254, 255, 265, 266]:
            msgType = self.tr("User")
        elif code in [372, 375, 376]:
            msgType = self.tr("MOTD")
        elif code in [305, 306]:
            msgType = self.tr("Away")
        else:
            msgType = self.tr("Info ({0})").format(code)
        
        # special treatment for some messages
        if code == 375:
            message = self.tr("Message of the day")
        elif code == 376:
            message = self.tr("End of message of the day")
        elif code == 4:
            parts = message.strip().split()
            message = self.tr(
                "Server {0} (Version {1}), User-Modes: {2},"
                " Channel-Modes: {3}"
            ).format(parts[1], parts[2], parts[3], parts[4])
        elif code == 265:
            parts = message.strip().split()
            message = self.tr(
                "Current users on {0}: {1}, max. {2}").format(
                server, parts[1], parts[2])
        elif code == 266:
            parts = message.strip().split()
            message = self.tr(
                "Current users on the network: {0}, max. {1}").format(
                parts[1], parts[2])
        elif code == 305:
            message = self.tr("You are no longer marked as being away.")
        elif code == 306:
            message = self.tr("You have been marked as being away.")
        else:
            first, message = message.split(None, 1)
            if message.startswith(":"):
                message = message[1:]
            else:
                message = message.replace(":", "", 1)
        
        self.networkWidget.addServerMessage(msgType, message)
        
        if code == 1:
            # register with services after the welcome message
            self.__connectionState = IrcWidget.ServerConnected
            self.__registerWithServices()
            self.networkWidget.setRegistered(True)
            QTimer.singleShot(1000, self.__autoJoinChannels)
        elif code == 5:
            # extract the user privilege prefixes
            # ... PREFIX=(ov)@+ ...
            m = self.__prefixRe.match(message)
            if m:
                self.__setUserPrivilegePrefix(m.group(1), m.group(2))
            # extract the channel type prefixes
            # ... CHANTYPES=# ...
            m = self.__chanTypesRe.match(message)
            if m:
                self.__setChannelTypePrefixes(m.group(1))
        
        return True
    
    def __registerWithServices(self):
        """
        Private method to register to services.
        """
        identity = self.__ircNetworkManager.getIdentity(self.__identityName)
        if identity:
            service = identity.getServiceName()
            password = identity.getPassword()
            if service and password:
                self.__send("PRIVMSG " + service + " :identify " + password)
    
    def __autoJoinChannels(self):
        """
        Private slot to join channels automatically once a server got
        connected.
        """
        for channel in self.networkWidget.getNetworkChannels():
            if channel.autoJoin():
                name = channel.getName()
                key = channel.getKey()
                self.joinChannel(name, key)
    
    def __tcpError(self, error):
        """
        Private slot to handle errors reported by the TCP socket.
        
        @param error error code reported by the socket
            (QAbstractSocket.SocketError)
        """
        if error == QAbstractSocket.RemoteHostClosedError:
            # ignore this one, it's a disconnect
            if self.__sslErrorLock:
                self.networkWidget.addErrorMessage(
                    self.tr("SSL Error"),
                    self.tr(
                        """Connection to server {0} (port {1}) lost while"""
                        """ waiting for user response to an SSL error.""")
                    .format(self.__server.getName(), self.__server.getPort()))
                self.__connectionState = IrcWidget.ServerDisconnected
        elif error == QAbstractSocket.HostNotFoundError:
            self.networkWidget.addErrorMessage(
                self.tr("Socket Error"),
                self.tr(
                    "The host was not found. Please check the host name"
                    " and port settings."))
        elif error == QAbstractSocket.ConnectionRefusedError:
            self.networkWidget.addErrorMessage(
                self.tr("Socket Error"),
                self.tr(
                    "The connection was refused by the peer. Please check the"
                    " host name and port settings."))
        elif error == QAbstractSocket.SslHandshakeFailedError:
            self.networkWidget.addErrorMessage(
                self.tr("Socket Error"),
                self.tr("The SSL handshake failed."))
        else:
            if self.__socket:
                self.networkWidget.addErrorMessage(
                    self.tr("Socket Error"),
                    self.tr(
                        "The following network error occurred:<br/>{0}")
                    .format(self.__socket.errorString()))
            else:
                self.networkWidget.addErrorMessage(
                    self.tr("Socket Error"),
                    self.tr("A network error occurred."))
    
    def __sslErrors(self, errors):
        """
        Private slot to handle SSL errors.
        
        @param errors list of SSL errors (list of QSslError)
        """
        ignored, defaultChanged = self.__sslErrorHandler.sslErrors(
            errors, self.__server.getName(), self.__server.getPort())
        if ignored == E5SslErrorHandler.NotIgnored:
            self.networkWidget.addErrorMessage(
                self.tr("SSL Error"),
                self.tr(
                    """Could not connect to {0} (port {1}) using an SSL"""
                    """ encrypted connection. Either the server does not"""
                    """ support SSL (did you use the correct port?) or"""
                    """ you rejected the certificate.""")
                .format(self.__server.getName(), self.__server.getPort()))
            self.__socket.close()
        else:
            if defaultChanged:
                self.__socket.setSslConfiguration(
                    QSslConfiguration.defaultConfiguration())
            if ignored == E5SslErrorHandler.UserIgnored:
                self.networkWidget.addErrorMessage(
                    self.tr("SSL Error"),
                    self.tr(
                        """The SSL certificate for the server {0} (port {1})"""
                        """ failed the authenticity check. SSL errors"""
                        """ were accepted by you.""")
                    .format(self.__server.getName(), self.__server.getPort()))
            if self.__connectionState == IrcWidget.ServerConnecting:
                self.__socket.ignoreSslErrors()
    
    def __setUserPrivilegePrefix(self, prefix1, prefix2):
        """
        Private method to set the user privilege prefix.
        
        @param prefix1 first part of the prefix (string)
        @param prefix2 indictors the first part gets mapped to (string)
        """
        # PREFIX=(ov)@+
        # o = @ -> @ircbot , channel operator
        # v = + -> +userName , voice operator
        for i in range(len(prefix1)):
            self.__userPrefix["+" + prefix1[i]] = prefix2[i]
            self.__userPrefix["-" + prefix1[i]] = ""
    
    def __ping(self, match):
        """
        Private method to handle a PING message.
        
        @param match reference to the match object
        @return flag indicating, if the message was handled (boolean)
        """
        self.__send("PONG " + match.group(1))
        return True
    
    def __handleCtcp(self, match):
        """
        Private method to handle a CTCP command.
        
        @param match reference to the match object
        @return flag indicating, if the message was handled (boolean)
        """
        # group(1)   sender user name
        # group(2)   sender user@host
        # group(3)   target nick
        # group(4)   message
        if match.group(4).startswith("\x01"):
            ctcpCommand = match.group(4)[1:].split("\x01", 1)[0]
            if " " in ctcpCommand:
                ctcpRequest, ctcpArg = ctcpCommand.split(" ", 1)
            else:
                ctcpRequest, ctcpArg = ctcpCommand, ""
            ctcpRequest = ctcpRequest.lower()
            if ctcpRequest == "version":
                if Version.startswith("@@"):
                    vers = ""
                else:
                    vers = " " + Version
                msg = "Eric IRC client{0}, {1}".format(vers, Copyright)
                self.networkWidget.addServerMessage(
                    self.tr("CTCP"),
                    self.tr("Received Version request from {0}.").format(
                        match.group(1)))
                self.__sendCtcpReply(match.group(1), "VERSION " + msg)
            elif ctcpRequest == "ping":
                self.networkWidget.addServerMessage(
                    self.tr("CTCP"),
                    self.tr(
                        "Received CTCP-PING request from {0},"
                        " sending answer.").format(match.group(1)))
                self.__sendCtcpReply(
                    match.group(1), "PING {0}".format(ctcpArg))
            elif ctcpRequest == "clientinfo":
                self.networkWidget.addServerMessage(
                    self.tr("CTCP"),
                    self.tr(
                        "Received CTCP-CLIENTINFO request from {0},"
                        " sending answer.").format(match.group(1)))
                self.__sendCtcpReply(
                    match.group(1),
                    "CLIENTINFO CLIENTINFO PING VERSION")
            else:
                self.networkWidget.addServerMessage(
                    self.tr("CTCP"),
                    self.tr(
                        "Received unknown CTCP-{0} request from {1}.")
                    .format(ctcpRequest, match.group(1)))
            return True
        
        return False
    
    def __updateUsersCount(self):
        """
        Private method to update the users count on the channel tabs.
        """
        for channel in self.__channelList:
            index = self.channelsWidget.indexOf(channel)
            self.channelsWidget.setTabText(
                index,
                self.tr("{0} ({1})", "channel name, users count").format(
                    channel.name(), channel.getUsersCount()))
    
    def __handleNickInUseLogin(self):
        """
        Private method to handle a 443 server error at login.
        """
        self.__nickIndex += 1
        try:
            identity = self.__ircNetworkManager.getIdentity(
                self.__identityName)
            if identity:
                nick = identity.getNickNames()[self.__nickIndex]
                self.__nickName = nick
            else:
                self.__connectNetwork("", False, True)
                self.__nickName = ""
                self.__nickIndex = -1
                return
        except IndexError:
            self.networkWidget.addServerMessage(
                self.tr("Critical"),
                self.tr(
                    "No nickname acceptable to the server configured"
                    " for <b>{0}</b>. Disconnecting...")
                .format(self.__userName),
                filterMsg=False)
            self.__connectNetwork("", False, True)
            self.__nickName = ""
            self.__nickIndex = -1
            return
        
        self.networkWidget.setNickName(nick)
        self.__send("NICK " + nick)
    
    def __handleNickInUse(self):
        """
        Private method to handle a 443 server error.
        """
        self.networkWidget.addServerMessage(
            self.tr("Critical"),
            self.tr("The given nickname is already in use."))
    
    def __changeNick(self, nick):
        """
        Private slot to use a new nick name.
        
        @param nick nick name to use (str)
        """
        if nick and nick != self.__nickName:
            self.__send("NICK " + nick)
    
    def __setChannelTypePrefixes(self, prefixes):
        """
        Private method to set the channel type prefixes.
        
        @param prefixes channel prefix characters (string)
        """
        self.__channelTypePrefixes = prefixes
    
    def isChannelName(self, name):
        """
        Public method to check, if the given name is a channel name.
        
        @param name name to check (string)
        @return flag indicating a channel name (boolean)
        """
        if not name:
            return False
        
        if self.__channelTypePrefixes:
            return name[0] in self.__channelTypePrefixes
        else:
            return name[0] in "#&"
    
    def __away(self, isAway):
        """
        Private slot handling the change of the away state.
        
        @param isAway flag indicating the current away state (boolean)
        """
        if isAway and self.__identityName:
            identity = self.__ircNetworkManager.getIdentity(
                self.__identityName)
            if identity and identity.rememberAwayPosition():
                for channel in self.__channelList:
                    channel.setMarkerLine()

eric ide

mercurial