Implemented support for the WHO command and the auto user status update function.

Sun, 09 Dec 2012 12:19:58 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sun, 09 Dec 2012 12:19:58 +0100
changeset 2253
7ba2af1ff785
parent 2252
1fc32bd13be3
child 2254
14f923d13971

Implemented support for the WHO command and the auto user status update function.

Implemented nice messages for changes of the channel modes.

Network/IRC/IrcChannelWidget.py file | annotate | diff | comparison | revisions
Network/IRC/IrcNetworkWidget.py file | annotate | diff | comparison | revisions
Network/IRC/IrcUtilities.py file | annotate | diff | comparison | revisions
Network/IRC/IrcWidget.py file | annotate | diff | comparison | revisions
--- a/Network/IRC/IrcChannelWidget.py	Fri Dec 07 19:48:23 2012 +0100
+++ b/Network/IRC/IrcChannelWidget.py	Sun Dec 09 12:19:58 2012 +0100
@@ -9,7 +9,7 @@
 
 import re
 
-from PyQt4.QtCore import pyqtSlot, pyqtSignal, QDateTime, QPoint, QFileInfo
+from PyQt4.QtCore import pyqtSlot, pyqtSignal, QDateTime, QPoint, QFileInfo, QTimer
 from PyQt4.QtGui import QWidget, QListWidgetItem, QIcon, QPainter, QMenu, QApplication
 
 from E5Gui import E5MessageBox, E5FileDialog
@@ -134,6 +134,30 @@
         painter.drawPixmap(0, 0, pix2)
         painter.end()
         return QIcon(pix1)
+    
+    def parseWhoFlags(self, flags):
+        """
+        Public method to parse the user flags reported by a WHO command.
+        
+        @param flags user flags as reported by WHO (string)
+        """
+        # H The user is not away.
+        # G The user is set away.
+        # * The user is an IRC operator.
+        # @ The user is a channel op in the channel listed in the first field.
+        # + The user is voiced in the channel listed.
+        if flags.endswith("@"):
+            privilege = IrcUserItem.Operator
+        elif flags.endswith("+"):
+            privilege = IrcUserItem.Voice
+        else:
+            privilege = IrcUserItem.Normal
+        if "*" in flags:
+            privilege = IrcUserItem.Admin
+        if flags.startswith("G"):
+            privilege |= IrcUserItem.Away
+        self.__privilege = privilege
+        self.__setIcon()
 
 
 class IrcChannelWidget(QWidget, Ui_IrcChannelWidget):
@@ -155,17 +179,6 @@
     LeaveIndicator = "&lt;--"
     MessageIndicator = "***"
     
-    # TODO: add context menu to users list with these entries:
-    #       Whois
-    #       Private Message
-    # TODO: add "Auto WHO" functionality (WHO <channel> %nf)
-    #       The possible combinations for this field are listed below:
-    #       H The user is not away.
-    #       G The user is set away.
-    #       * The user is an IRC operator.
-    #       @ The user is a channel op in the channel listed in the first field.
-    #       + The user is voiced in the channel listed.
-    # TODO: Check away indication in the user list
     def __init__(self, parent=None):
         """
         Constructor
@@ -194,7 +207,6 @@
             # :foo_!n=foo@foohost.bar.net PRIVMSG #eric-ide :some long message
             (re.compile(r":([^!]+).*\sPRIVMSG\s([^ ]+)\s:(.*)"), self.__message),
             # :foo_!n=foo@foohost.bar.net JOIN :#eric-ide
-            # :detlev_!~detlev@mnch-5d876cfa.pool.mediaWays.net JOIN #eric-ide
             (re.compile(r":([^!]+)!([^ ]+)\sJOIN\s:?([^ ]+)"), self.__userJoin),
             # :foo_!n=foo@foohost.bar.net PART #eric-ide :part message
             (re.compile(r":([^!]+).*\sPART\s([^ ]+)\s:(.*)"), self.__userPart),
@@ -206,11 +218,15 @@
             (re.compile(r":([^!]+).*\sQUIT\s*"), self.__userQuit),
             # :foo_!n=foo@foohost.bar.net NICK :newnick
             (re.compile(r":([^!]+).*\sNICK\s:(.*)"), self.__userNickChange),
-            # :barty!n=foo@foohost.bar.net MODE #eric-ide +o foo_
-            (re.compile(r":([^!]+).*\sMODE\s([^ ]+)\s([^ ]+)\s([^ ]+).*"),
+            # :foo_!n=foo@foohost.bar.net MODE #eric-ide +o foo_
+            (re.compile(r":([^!]+).*\sMODE\s([^ ]+)\s([+-][ovO]+)\s([^ ]+).*"),
                 self.__setUserPrivilege),
+            # :cameron.freenode.net MODE #testeric +ns
+            (re.compile(r":([^ ]+)\sMODE\s([^ ]+)\s(.+)"), self.__updateChannelModes),
             # :sturgeon.freenode.net 301 foo_ bar :Gone away for now
             (re.compile(r":.*\s301\s([^ ]+)\s([^ ]+)\s:(.+)"), self.__userAway),
+            # :sturgeon.freenode.net 315 foo_ #eric-ide :End of /WHO list.
+            (re.compile(r":.*\s315\s[^ ]+\s([^ ]+)\s:(.*)"), self.__whoEnd),
             # :zelazny.freenode.net 324 foo_ #eric-ide +cnt
             (re.compile(r":.*\s324\s.*\s([^ ]+)\s(.+)"), self.__channelModes),
             # :zelazny.freenode.net 328 foo_ #eric-ide :http://www.buggeroff.com/
@@ -221,13 +237,24 @@
             (re.compile(r":.*\s332\s.*\s([^ ]+)\s:(.*)"), self.__setTopic),
             # :zelazny.freenode.net foo_ 333 #eric-ide foo 1353089020
             (re.compile(r":.*\s333\s.*\s([^ ]+)\s([^ ]+)\s(\d+)"), self.__topicCreated), 
+            # :cameron.freenode.net 352 detlev_ #eric-ide ~foo foohost.bar.net cameron.freenode.net foo_ H :0 Foo Bar
+            (re.compile(r":.*\s352\s[^ ]+\s([^ ]+)\s([^ ]+)\s([^ ]+)\s[^ ]+\s([^ ]+)"
+                        r"\s([^ ]+)\s:\d+\s(.*)"), self.__whoEntry),
             # :zelazny.freenode.net 353 foo_ @ #eric-ide :@user1 +user2 user3
             (re.compile(r":.*\s353\s.*\s.\s([^ ]+)\s:(.*)"), self.__userList),
+            # :sturgeon.freenode.net 354 foo_ 42 ChanServ H@
+            (re.compile(r":.*\s354\s[^ ]+\s42\s([^ ]+)\s(.*)"), self.__autoWhoEntry),
             # :zelazny.freenode.net 366 foo_ #eric-ide :End of /NAMES list.
             (re.compile(r":.*\s366\s.*\s([^ ]+)\s:(.*)"), self.__ignore),
             # :sturgeon.freenode.net 704 foo_ index :Help topics available to users:
             (re.compile(r":.*\s70[456]\s[^ ]+\s([^ ]+)\s:(.*)"), self.__help),
         ]
+        
+        self.__autoWhoTemplate = "WHO {0} %tnf,42"
+        self.__autoWhoTimer = QTimer()
+        self.__autoWhoTimer.setSingleShot(True)
+        self.__autoWhoTimer.timeout.connect(self.__sendAutoWhoCommand)
+        self.__autoWhoRequested = False
     
     @pyqtSlot()
     def on_messageEdit_returnPressed(self):
@@ -261,6 +288,7 @@
                 else:
                     self.sendData.emit("PRIVMSG " + self.__name + " :" + msg)
             self.messageEdit.clear()
+            self.unsetMarkerLine()
     
     def requestLeave(self):
         """
@@ -629,6 +657,151 @@
         
         return False
     
+    def __updateChannelModes(self, match):
+        """
+        Private method to handle a message reporting the channel modes.
+        
+        @param match match object that matched the pattern
+        @return flag indicating whether the message was handled (boolean)
+        """
+        # group(1)  user or server
+        # group(2)  channel
+        # group(3)  modes and parameter list
+        if match.group(2).lower() == self.__name:
+            nick = match.group(1)
+            modesParameters = match.group(3).split()
+            modeString = modesParameters.pop(0)
+            isPlus = True
+            message = ""
+            for mode in modeString:
+                if mode == "+":
+                    isPlus = True
+                    continue
+                elif mode == "-":
+                    isPlus = False
+                    continue
+                elif mode == "a":
+                    if isPlus:
+                        message = self.trUtf8(
+                            "{0} sets the channel mode to 'anonymous'.").format(nick)
+                    else:
+                        message = self.trUtf8(
+                            "{0} removes the 'anonymous' mode from the channel.").format(
+                            nick)
+                elif mode == "b":
+                    if isPlus:
+                        message = self.trUtf8(
+                            "{0} sets a ban on {1}.").format(
+                            nick, modesParameters.pop(0))
+                    else:
+                        message = self.trUtf8(
+                            "{0} removes the ban on {1}.").format(
+                            nick, modesParameters.pop(0))
+                elif mode == "c":
+                    if isPlus:
+                        message = self.trUtf8(
+                            "{0} sets the channel mode to 'no colors allowed'.").format(
+                            nick)
+                    else:
+                        message = self.trUtf8(
+                            "{0} sets the channel mode to 'allow color codes'.").format(
+                            nick)
+                elif mode == "e":
+                    if isPlus:
+                        message = self.trUtf8(
+                            "{0} sets a ban exception on {1}.").format(
+                            nick, modesParameters.pop(0))
+                    else:
+                        message = self.trUtf8(
+                            "{0} removes the ban exception on {1}.").format(
+                            nick, modesParameters.pop(0))
+                elif mode == "i":
+                    if isPlus:
+                        message = self.trUtf8(
+                            "{0} sets the channel mode to 'invite only'.").format(nick)
+                    else:
+                        message = self.trUtf8(
+                            "{0} removes the 'invite only' mode from the channel."
+                            ).format(nick)
+                elif mode == "k":
+                    if isPlus:
+                        message = self.trUtf8(
+                            "{0} sets the channel key to '{1}'.").format(
+                            nick, modesParameters.pop(0))
+                    else:
+                        message = self.trUtf8(
+                            "{0} removes the channel key.").format(nick)
+                elif mode == "l":
+                    if isPlus:
+                        message = self.trUtf8(
+                            "{0} sets the channel limit to %n nick(s).", "",
+                            int(modesParameters.pop(0))).format(nick)
+                    else:
+                        message = self.trUtf8(
+                            "{0} removes the channel limit.").format(nick)
+                elif mode == "m":
+                    if isPlus:
+                        message = self.trUtf8(
+                            "{0} sets the channel mode to 'moderated'.").format(nick)
+                    else:
+                        message = self.trUtf8(
+                            "{0} sets the channel mode to 'unmoderated'.").format(nick)
+                elif mode == "n":
+                    if isPlus:
+                        message = self.trUtf8(
+                            "{0} sets the channel mode to 'no messages from outside'."
+                            ).format(nick)
+                    else:
+                        message = self.trUtf8(
+                            "{0} sets the channel mode to 'allow messages from outside'."
+                            ).format(nick)
+                elif mode == "p":
+                    if isPlus:
+                        message = self.trUtf8(
+                            "{0} sets the channel mode to 'private'.").format(nick)
+                    else:
+                        message = self.trUtf8(
+                            "{0} sets the channel mode to 'public'.").format(nick)
+                elif mode == "q":
+                    if isPlus:
+                        message = self.trUtf8(
+                            "{0} sets the channel mode to 'quiet'.").format(nick)
+                    else:
+                        message = self.trUtf8(
+                            "{0} removes the 'quiet' mode from the channel.").format(
+                            nick)
+                elif mode == "r":
+                    continue
+                elif mode == "s":
+                    if isPlus:
+                        message = self.trUtf8(
+                            "{0} sets the channel mode to 'secret'.").format(nick)
+                    else:
+                        message = self.trUtf8(
+                            "{0} sets the channel mode to 'visible'.").format(nick)
+                elif mode == "t":
+                    if isPlus:
+                        message = self.trUtf8(
+                            "{0} switches on 'topic protection'.").format(nick)
+                    else:
+                        message = self.trUtf8(
+                            "{0} switches off 'topic protection'.").format(nick)
+                elif mode == "I":
+                    if isPlus:
+                        message = self.trUtf8(
+                            "{0} sets invitation mask {1}.").format(
+                            nick, modesParameters.pop(0))
+                    else:
+                        message = self.trUtf8(
+                            "{0} removes the invitation mask {1}.").format(
+                            nick, modesParameters.pop(0))
+                
+                self.__addManagementMessage(self.trUtf8("Mode"), message)
+            
+            return True
+        
+        return False
+    
     def __setUserPrivilege(self, match):
         """
         Private method to handle a change of user privileges for the channel.
@@ -640,9 +813,9 @@
             itm = self.__findUser(match.group(4))
             if itm:
                 itm.changePrivilege(match.group(3))
-                self.__addManagementMessage(IrcChannelWidget.MessageIndicator,
-                    self.trUtf8("{0} sets mode for {1}: {2}.").format(
-                        match.group(1), match.group(4), match.group(3)))
+            self.__addManagementMessage(IrcChannelWidget.MessageIndicator,
+                self.trUtf8("{0} sets mode for {1}: {2}.").format(
+                    match.group(1), match.group(4), match.group(3)))
             return True
         
         return False
@@ -970,3 +1143,81 @@
         @param evt reference to the show event (QShowEvent)
         """
         self.__hidden = False
+    
+    def initAutoWho(self):
+        """
+        Public method to initialize the Auto Who system.
+        """
+        if Preferences.getIrc("AutoUserInfoLookup"):
+            self.__autoWhoTimer.setInterval(
+                Preferences.getIrc("AutoUserInfoInterval") * 1000)
+            self.__autoWhoTimer.start()
+    
+    @pyqtSlot()
+    def __sendAutoWhoCommand(self):
+        """
+        Private slot to send the WHO command to update the users list.
+        """
+        if self.usersList.count() <= Preferences.getIrc("AutoUserInfoMax"):
+            self.__autoWhoRequested = True
+            self.sendData.emit(self.__autoWhoTemplate.format(self.__name))
+    
+    def __autoWhoEntry(self, match):
+        """
+        Private method to handle a WHO entry returned by the server as requested
+        automatically.
+        
+        @param match match object that matched the pattern
+        @return flag indicating whether the message was handled (boolean)
+        """
+        # group(1)  nick
+        # group(2)  user flags
+        if self.__autoWhoRequested:
+            itm = self.__findUser(match.group(1))
+            if itm:
+                itm.parseWhoFlags(match.group(2))
+            return True
+        
+        return False
+    
+    def __whoEnd(self, match):
+        """
+        Private method to handle the end of the WHO list.
+        
+        @param match match object that matched the pattern
+        @return flag indicating whether the message was handled (boolean)
+        """
+        if match.group(1).lower() == self.__name:
+            if self.__autoWhoRequested:
+                self.__autoWhoRequested = False
+                self.initAutoWho()
+            else:
+                self.__addManagementMessage(self.trUtf8("Who"),
+                    self.trUtf8("End of /WHO list for {0}.").format(match.group(1)))
+            return True
+        
+        return False
+    
+    def __whoEntry(self, match):
+        """
+        Private method to handle a WHO entry returned by the server as requested
+        manually.
+        
+        @param match match object that matched the pattern
+        @return flag indicating whether the message was handled (boolean)
+        """
+        # group(1)  channel
+        # group(2)  user
+        # group(3)  host
+        # group(4)  nick
+        # group(5)  user flags
+        # group(6)  real name
+        if match.group(2).lower() == self.__name:
+            away = self.trUtf8(" (Away)") if match.group(5).startswith("G") else ""
+            self.__addManagementMessage(self.trUtf8("Who"),
+                self.trUtf8("{0} is {1}@{2} ({3}){4}").format(
+                    match.group(4), match.group(2), match.group(3), match.group(6), away))
+            return True
+        
+        return False
+
--- a/Network/IRC/IrcNetworkWidget.py	Fri Dec 07 19:48:23 2012 +0100
+++ b/Network/IRC/IrcNetworkWidget.py	Sun Dec 09 12:19:58 2012 +0100
@@ -39,7 +39,6 @@
     sendData = pyqtSignal(str)
     away = pyqtSignal(bool)
     
-    
     def __init__(self, parent=None):
         """
         Constructor
@@ -58,6 +57,10 @@
         self.nickCombo.setEnabled(False)
         self.awayButton.setEnabled(False)
         
+        self.channelCombo.lineEdit().returnPressed.connect(self.on_joinButton_clicked)
+        self.nickCombo.lineEdit().returnPressed.connect(
+            self.on_nickCombo_currentIndexChanged)
+        
         self.__initMessagesMenu()
         
         self.__manager = None
@@ -191,7 +194,7 @@
         return network.getChannels()
     
     @pyqtSlot(str)
-    def on_nickCombo_currentIndexChanged(self, nick):
+    def on_nickCombo_currentIndexChanged(self, nick=""):
         """
         Private slot to use another nick name.
         
--- a/Network/IRC/IrcUtilities.py	Fri Dec 07 19:48:23 2012 +0100
+++ b/Network/IRC/IrcUtilities.py	Sun Dec 09 12:19:58 2012 +0100
@@ -146,16 +146,21 @@
     global __channelModesDict
     
     modesDict = {
-        "t": QCoreApplication.translate("IrcUtilities", "topic protection"),
+        "a": QCoreApplication.translate("IrcUtilities", "anonymous"),
+        "b": QCoreApplication.translate("IrcUtilities", "ban mask"),
+        "c": QCoreApplication.translate("IrcUtilities", "no colors allowed"),
+        "e": QCoreApplication.translate("IrcUtilities", "ban exception mask"),
+        "i": QCoreApplication.translate("IrcUtilities", "invite only"),
+        "k": QCoreApplication.translate("IrcUtilities", "password protected"),
+        "l": QCoreApplication.translate("IrcUtilities", "user limit"),
+        "m": QCoreApplication.translate("IrcUtilities", "moderated"),
         "n": QCoreApplication.translate("IrcUtilities", "no messages from outside"),
-        "s": QCoreApplication.translate("IrcUtilities", "secret"),
-        "i": QCoreApplication.translate("IrcUtilities", "invite only"),
         "p": QCoreApplication.translate("IrcUtilities", "private"),
-        "m": QCoreApplication.translate("IrcUtilities", "moderated"),
-        "k": QCoreApplication.translate("IrcUtilities", "password protected"),
-        "a": QCoreApplication.translate("IrcUtilities", "anonymous"),
-        "c": QCoreApplication.translate("IrcUtilities", "no colors allowed"),
-        "l": QCoreApplication.translate("IrcUtilities", "user limit"),
+        "q": QCoreApplication.translate("IrcUtilities", "quit"),
+        "r": QCoreApplication.translate("IrcUtilities", "reop channel"),
+        "s": QCoreApplication.translate("IrcUtilities", "secret"),
+        "t": QCoreApplication.translate("IrcUtilities", "topic protection"),
+        "I": QCoreApplication.translate("IrcUtilities", "invitation mask"),
     }
     __channelModesDict = modesDict
 
--- a/Network/IRC/IrcWidget.py	Fri Dec 07 19:48:23 2012 +0100
+++ b/Network/IRC/IrcWidget.py	Sun Dec 09 12:19:58 2012 +0100
@@ -49,7 +49,6 @@
         self.setupUi(self)
         
         self.__ircNetworkManager = IrcNetworkManager(self)
-        self.__ircNetworkManager.dataChanged.connect(self.__networkDataChanged)
         
         self.__leaveButton = QToolButton(self)
         self.__leaveButton.setIcon(UI.PixmapCache.getIcon("ircCloseChannel.png"))
@@ -59,14 +58,6 @@
         self.channelsWidget.setCornerWidget(self.__leaveButton, Qt.BottomRightCorner)
         self.channelsWidget.setTabsClosable(False)
         
-        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.__channelList = []
         self.__channelTypePrefixes = ""
         self.__userName = ""
@@ -102,6 +93,15 @@
         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)
     
     def shutdown(self):
         """
@@ -245,6 +245,7 @@
         identity = self.__ircNetworkManager.getIdentity(self.__identityName)
         channel.setPartMessage(identity.getPartMessage())
         channel.setUserPrivilegePrefix(self.__userPrefix)
+        channel.initAutoWho()
         
         channel.sendData.connect(self.__send)
         channel.channelClosed.connect(self.__closeChannel)
@@ -252,6 +253,7 @@
         
         self.channelsWidget.addTab(channel, name)
         self.__channelList.append(channel)
+        self.channelsWidget.setCurrentWidget(channel)
         
         joinCommand = ["JOIN", name]
         if key:
@@ -286,6 +288,7 @@
         
         self.channelsWidget.addTab(channel, name)
         self.__channelList.append(channel)
+        self.channelsWidget.setCurrentWidget(channel)
     
     @pyqtSlot()
     def __leaveChannel(self):

eric ide

mercurial