]> code.delx.au - pymsnt/blobdiff - src/legacy/msnw.py
Fixed a silly bug in cosmetic changes.
[pymsnt] / src / legacy / msnw.py
index 85b9256f4c45ca980b1cb891c4e9384cb49f5db5..89471af38ea9e41ed97834bea065c7710c8fc032 100644 (file)
@@ -1,14 +1,20 @@
-# Copyright 2004 James Bunton <james@delx.cjb.net>
+# Copyright 2004-2005 James Bunton <james@delx.cjb.net>
 # Licensed for distribution under the GPL version 2, check COPYING for details
 
 from twisted.internet import reactor
 from twisted.internet.protocol import ClientFactory
+from twisted.python import log
+from debug import LogEvent, INFO, WARN, ERROR
 from tlib import msn
+import math
+import base64
+import binascii
+import math
 import config
 import utils
-import debug
-
+import lang
 
+MAXMESSAGESIZE = 1400
 
 
 class MSNConnection:
@@ -19,10 +25,10 @@ class MSNConnection:
                self.inited = False
                self.tries = 0
                self.initMe()
-               debug.log("MSNConnection: \"%s\" created" % (self.username))
+               LogEvent(INFO, self.session.jabberID)
                
        def initMe(self):
-               if(self.inited):
+               if self.inited:
                        MSNConnection.removeMe(self)
                
                self.switchboardSessions = {}
@@ -36,48 +42,61 @@ class MSNConnection:
                self.notificationFactory.initialListVersion = self.initialListVersion
                self.notificationProtocol = None
                
-               self.savedStatus = None
+               self.savedEvents = SavedEvents()
                
                self.inited = True
                
-               debug.log("MSNConnection: \"%s\" initialised" % (self.username))
+               LogEvent(INFO, self.session.jabberID)
        
        def removeMe(self):
-               debug.log("MSNConnection: \"%s\" destroyed" % (self.username))
-               if(self.notificationProtocol):
+               LogEvent(INFO, self.session.jabberID)
+               if self.notificationProtocol:
                        self.notificationProtocol.removeMe()
-               if(self.notificationFactory):
+               if self.notificationFactory:
                        self.notificationFactory.msncon = None
                self.notificationFactory = None
                self.notificationProtocol = None
-               for userHandle in utils.copyDict(self.switchboardSessions):
+               self.savedEvents = SavedEvents()
+               for userHandle in self.switchboardSessions.copy():
                        self.switchboardSessions[userHandle].removeMe()
                self.switchboardSessions = {}
        
        def resourceOffline(self, offlineResource):
                for contact in self.switchboardSessions.keys():
-                       if(self.switchboardSessions[contact].resource == offlineResource):
+                       if self.switchboardSessions[contact].resource == offlineResource:
                                self.switchboardSessions[contact].resource = self.highestResource()
        
        def getContacts(self):
-               if(self.notificationFactory):
+               if self.notificationFactory:
                        return self.notificationFactory.contacts
                else:
                        return None
                
        
        def sendMessage(self, remoteUser, resource, text, noerror):
-               debug.log("MSNConnection: \"%s\" sendMessage(\"%s\", \"%s\")" % (self.username, remoteUser, text))
-               if(self.notificationProtocol):
-                       if(not self.switchboardSessions.has_key(remoteUser)):
+               LogEvent(INFO, self.session.jabberID)
+               if self.notificationProtocol:
+                       if not self.switchboardSessions.has_key(remoteUser):
                                self.switchboardSessions[remoteUser] = SwitchboardSession(self, remoteUser, resource)
                        self.switchboardSessions[remoteUser].resource = resource
                        self.switchboardSessions[remoteUser].sendMessage(text.replace("\n", "\r\n"), noerror)
-               elif(not noerror):
+               elif not noerror:
                        self.failedMessage(remoteUser, text)
        
+       def requestAvatar(self, userHandle):
+               LogEvent(INFO, self.session.jabberID)
+               resource = self.session.highestResource()
+               if config.getAllAvatars:
+                       if not self.switchboardSessions.has_key(userHandle):
+                               self.switchboardSessions[userHandle] = SwitchboardSession(self, userHandle, resource)
+                       else:
+                               self.switchboardSessions[userHandle].requestAvatar()
+               else:
+                       if self.switchboardSessions.has_key(userHandle): # Only request avatars for open switchboards
+                               self.switchboardSessions[userHandle].requestAvatar()
+       
        def sendTypingToContact(self, remoteUser):
-               if(self.switchboardSessions.has_key(remoteUser)):
+               if self.switchboardSessions.has_key(remoteUser):
                        self.switchboardSessions[remoteUser].sendTypingNofication()
        
        def notificationProtocolReady(self, notificationProtocol):
@@ -85,42 +104,61 @@ class MSNConnection:
                self.loggedIn()
                self.tries = 0
        
-       def sendSavedStatus(self):
-               # Hack for initial status
-               if(self.savedStatus):
-                       statusCode, screenName = self.savedStatus
-                       self.savedStatus = None
-                       self.changeStatus(statusCode, screenName)
+       def sendSavedEvents(self):
+               # Hack for events sent before we're logged in
+               self.savedEvents.send(self)
+               self.savedEvents = None
        
-       def changeStatus(self, statusCode, screenName):
-               if(self.notificationProtocol):
+       def changeAvatar(self, imageData):
+               if self.notificationProtocol:
+                       self.notificationProtocol.changeAvatar(imageData, push=True)
+               else:
+                       self.savedEvents.avatarImageData = imageData
+       
+       def changeStatus(self, statusCode, screenName, personal):
+               if self.notificationProtocol:
                        def cb1(arg):
                                self.ourStatusChanged(arg[0])
                        def cb2(arg):
                                self.ourNickChanged(arg[0])
-                       debug.log("MSNConnection: \"%s\" - changing status and screenName (\"%s\", \"%s\")" % (self.username, statusCode, screenName))
-                       if(statusCode):
+                       def cb3(arg):
+                               self.ourPersonalChanged(personal)
+                       LogEvent(INFO, self.session.jabberID)
+                       if statusCode:
                                statusCode = str(statusCode.encode("utf-8"))
                                self.notificationProtocol.changeStatus(statusCode).addCallback(cb1)
-                       if(screenName):
+                       if screenName:
                                screenName = str(screenName.encode("utf-8"))
                                self.notificationProtocol.changeScreenName(screenName).addCallback(cb2)
+                       if personal:
+                               personal = str(personal.encode("utf-8"))
+                       else:
+                               personal = ""
+                       self.notificationProtocol.changePersonalMessage(personal).addCallback(cb3)
                else:
-                       self.savedStatus = (statusCode, screenName)
+                       self.savedEvents.statusCode = statusCode
+                       self.savedEvents.screenName = screenName
+                       self.savedEvents.personal = personal
        
        def connectionLostBase(self, reason):
                # Attempts to reconnect
-               if(self.tries < 5 and self.session):
+               if self.tries < 5 and self.session:
                        reactor.callLater(2 ** self.tries, self.initMe)
                        self.tries += 1
                else:
                        self.connectionLost(self)
        
        def addContact(self, listType, userHandle):
-               return self.notificationProtocol.addContact(listType, str(userHandle))
+               if self.notificationProtocol:
+                       return self.notificationProtocol.addContact(listType, str(userHandle))
+               else:
+                       self.savedEvents.addContacts.append((listType, str(userHandle)))
        
        def remContact(self, listType, userHandle, groupID=0):
-               return self.notificationProtocol.remContact(listType, str(userHandle))
+               if self.notificationProtocol:
+                       return self.notificationProtocol.remContact(listType, str(userHandle))
+               else:
+                       self.savedEvents.remContacts.append((listType, str(userHandle)))
        
        
        
@@ -142,6 +180,15 @@ class MSNConnection:
        def gotMessage(self, remoteUser, resource, text):
                pass
        
+       def avatarHashChanged(self, userHandle, hash):
+               pass
+       
+       def gotAvatarImage(self, to, image):
+               pass
+       
+       def gotSendRequest(self, fileReceive):
+               pass
+       
        def listSynchronized(self):
                pass
        
@@ -151,13 +198,16 @@ class MSNConnection:
        def ourStatusChanged(self, statusCode):
                pass
        
-       def userMapping(self, passport, jid):
+       def ourNickChanged(self, nick):
                pass
        
-       def gotContactTyping(self, remoteUser, resource):
+       def ourPersonalChanged(self, personal):
                pass
        
-       def ourNickChanged(self, arg):
+       def userMapping(self, passport, jid):
+               pass
+       
+       def gotContactTyping(self, remoteUser, resource):
                pass
        
        def serverGoingDown(self):
@@ -180,6 +230,26 @@ class MSNConnection:
 
 
 
+class SavedEvents:
+       def __init__(self):
+               self.nickname = ""
+               self.statusCode = ""
+               self.personal = ""
+               self.avatarImageData = ""
+               self.addContacts = []
+               self.remContacts = []
+       
+       def send(self, msncon):
+               if self.avatarImageData:
+                       msncon.notificationProtocol.changeAvatar(self.avatarImageData, push=False)
+               if self.nickname or self.statusCode or self.personal:
+                       msncon.changeStatus(self.statusCode, self.nickname, self.personal)
+               for listType, userHandle in self.addContacts:
+                       msncon.addContact(listType, userHandle)
+               for listType, userHandle in self.remContacts:
+                       msncon.remContact(listType, userHandle)
+                       
+
 
 def switchToGroupchat(switchboardSession, user1, user2):
        gcsbs = GroupchatSwitchboardSession()
@@ -194,43 +264,110 @@ def switchToGroupchat(switchboardSession, user1, user2):
        gcsbs.userJoined(user2)
        groupchat.sendUserInvite(msn2jid(switchboardSession.remoteUser))
        switchboardSession.removeMe(False)
-       debug.log("GroupchatSwitchboardSession: \"%s\" \"%s\" created by conversion" % (gcsbs.groupchat.roomJID(), gcsbs))
+       LogEvent(INFO, gcsbs.msncon.session.jabberID)
        return gcsbs
 
 
-class GroupchatSwitchboardSession:
+class SwitchboardSessionBase:
+       def sendMessage(self, message, noerror):
+               if self.ready:
+                       def failedMessage(ignored):
+                               if isinstance(self, GroupchatSwitchboardSession):
+                                       tempmsncon.failedMessage(self.groupchat.roomJID(), message)
+                               else:
+                                       tempmsncon.failedMessage(self.remoteUser, message)
+                                       
+                       tempmsncon = self.msncon # In case MSN tells us the message failed after removeMe()
+
+                       LogEvent(INFO, self.ident)
+                       message = str(message.encode("utf-8"))
+                       
+                       if len(message) < MAXMESSAGESIZE:
+                               msnmessage = msn.MSNMessage(message=message)
+                               msnmessage.setHeader("Content-Type", "text/plain; charset=UTF-8")
+                               msnmessage.ack = msn.MSNMessage.MESSAGE_NACK
+
+                               d = self.switchboard.sendMessage(msnmessage)
+                               if not noerror:
+                                       d.addCallback(failedMessage)
+                       else:
+                               chunks = int(math.ceil(len(message) / float(MAXMESSAGESIZE)))
+                               chunk = 0
+                               guid = utils.random_guid()
+                               while chunk < chunks:
+                                       offset = chunk * MAXMESSAGESIZE
+                                       text = message[offset : offset + MAXMESSAGESIZE]
+
+                                       msnmessage = msn.MSNMessage(message=text)
+                                       msnmessage.setHeader("Message-ID", guid)
+                                       if chunk == 0:
+                                               msnmessage.setHeader("Content-Type", "text/plain; charset=UTF-8")
+                                               msnmessage.setHeader("Chunks", str(chunks))
+                                       else:
+                                               msnmessage.setHeader("Chunk", str(chunk))
+                                       msnmessage.ack = msn.MSNMessage.MESSAGE_NACK
+
+                                       d = self.switchboard.sendMessage(msnmessage)
+                                       if not noerror:
+                                               d.addCallback(failedMessage)
+                                       chunk += 1
+
+                       self.resetTimer()
+               else:
+                       self.messageBuffer.append((message, noerror))
+       
+       def gotAvatarImage(self, to, image):
+               self.msncon.gotAvatarImage(to, image)
+       
+       def gotSendRequest(self, fileReceive):
+               self.msncon.gotSendRequest(fileReceive)
+
+       def switchboardReady(self, switchboard):
+               LogEvent(INFO, self.ident)
+               self.ready = True
+               self.switchboard = switchboard
+               self.flushBuffer()
+       
+       def resetTimer(self):
+               pass
+
+
+class GroupchatSwitchboardSession(SwitchboardSessionBase):
        def __init__(self, groupchat=None, makeSwitchboard=False):
                self.removed = False
 
                self.msncon = None
-               self.groupchat = None
-               if(groupchat):
+               if groupchat:
+                       self.ident = groupchat.roomJID()
                        self.groupchat = groupchat
                        self.msncon = self.groupchat.session.legacycon
+               else:
+                       self.ident = str(self)
+                       self.groupchat = None
                self.switchboard = None
                self.ready = False
                self.messageBuffer = []
                self.invitedUsers = []
                self.oneUserHasJoined = False
                
-               if(makeSwitchboard and groupchat):
-                       debug.log("GroupchatSwitchboardSession: \"%s\" \"%s\" requesting a switchboard session" % (self.groupchat.roomJID(), self))
+               if makeSwitchboard and groupchat:
+                       LogEvent(INFO, self.ident, "Requesting switchboard.")
                        d = self.msncon.notificationProtocol.requestSwitchboardServer()
                        d.addCallback(self.sbRequestAccepted)
                        d.addErrback(self.removeMe)
                
-               if(self.msncon):
-                       debug.log("GroupchatSwitchboardSession: \"%s\" \"%s\" created" % (self.msncon.username, self))
+               if self.msncon:
+                       LogEvent(INFO, self.ident, "Created groupchat for " + self.msncon.username)
        
        def removeMe(self):
-               if(self.removed):
-                       debug.log("GroupchatSwitchboardSession: removeMe called more than once! Traceback!")
+               if self.removed:
+                       log.err("removeMe called more than once!")
                        return
                self.removed = True
 
-               debug.log("GroupchatSwitchboardSession: \"%s\" \"%s\" destroyed" % (self.groupchat.roomJID(), self))
+               LogEvent(INFO, self.ident)
                self.msncon = None
-               if(self.switchboard):
+               if self.switchboard:
                        self.switchboard.removeMe()
                self.switchboard = None
                self.groupchat = None
@@ -240,28 +377,19 @@ class GroupchatSwitchboardSession:
        
        def sbRequestAccepted(self, (host, port, key)):
                # Connect to the switchboard server
-               debug.log("GroupchatSwitchboardSession: \"%s\" \"%s\" sbRequestAccepted()" % (self.msncon.username, self))
+               LogEvent(INFO, self.ident)
                reactor.connectTCP(host, port, SwitchboardFactory(self, key))
        
        def sendMessage(self, message, noerror):
-               if(self.ready and self.oneUserHasJoined):
-                       def failedMessage(ignored=None):
-                               tempmsncon.failedMessage(self.groupchat.roomJID(), message)
-                       message = str(message.encode("utf-8"))
-                       msnmessage = msn.MSNMessage(message=message)
-                       msnmessage.setHeader("Content-Type", "text/plain; charset=UTF-8")
-                       msnmessage.ack = msn.MSNMessage.MESSAGE_NACK
-                       tempmsncon = self.msncon # In case MSN tells us the message failed after removeMe()
-                       d = self.switchboard.sendMessage(msnmessage)
-                       if(not noerror):
-                               d.addCallback(failedMessage)
+               if self.oneUserHasJoined:
+                       SwitchboardSessionBase.sendMessage(self, message, noerror)
                else:
-                       self.messageBuffer.append(message)
+                       self.messageBuffer.append((message, noerror))
        
        def inviteUser(self, userHandle):
                userHandle = str(userHandle)
-               if(self.ready):
-                       debug.log("GroupchatSwitchboardSession: \"%s\" \"%s\" inviting %s" % (self.msncon.username, self, userHandle))
+               if self.ready:
+                       LogEvent(INFO, self.ident)
                        self.switchboard.inviteUser(userHandle)
                else:
                        self.invitedUsers.append(userHandle)
@@ -270,30 +398,31 @@ class GroupchatSwitchboardSession:
                self.groupchat.messageReceived(message.userHandle, message.getMessage())
        
        def flushBuffer(self):
-               for m in utils.copyList(self.messageBuffer):
-                       self.messageBuffer.remove(m)
-                       self.sendMessage(m, True)
+               for m, noerror in self.messageBuffer[:]:
+                       self.messageBuffer.remove((m, noerror))
+                       self.sendMessage(m, noerror)
                
-               for i in utils.copyList(self.invitedUsers):
+               for i in self.invitedUsers[:]:
                        self.invitedUsers.remove(i)
                        self.inviteUser(i)
        
        def userJoined(self, userHandle):
-               debug.log("GroupchatSwitchboardSession: \"%s\" \"%s\" userJoined(\"%s\")" % (self.msncon.username, self, userHandle))
+               LogEvent(INFO, self.ident)
                self.oneUserHasJoined = True
                self.flushBuffer()
                self.groupchat.contactJoined(userHandle)
        
        def userLeft(self, userHandle):
-               debug.log("GroupchatSwitchboardSession: \"%s\" \"%s\" userLeft(\"%s\")" % (self.msncon.username, self, userHandle))
+               LogEvent(INFO, self.ident)
                self.groupchat.contactLeft(userHandle)
 
 
 
-class SwitchboardSession:
+class SwitchboardSession(SwitchboardSessionBase):
        def __init__(self, msncon, remoteUser, resource, reply=False, host=None, port=None, key=None, sessionID=None):
                self.removed = False
 
+               self.ident = (msncon.session.jabberID, remoteUser)
                self.msncon = msncon
                self.remoteUser = str(remoteUser)
                self.resource = str(resource)
@@ -304,7 +433,7 @@ class SwitchboardSession:
                self.messageBuffer = [] # Any messages sent before the switchboard is ready are buffered
                self.ready = False # Is True when we are connected to the switchboard, and the remote user has accepted our invite
                
-               if(not reply):
+               if not reply:
                        # Request a switchboard
                        d = self.msncon.notificationProtocol.requestSwitchboardServer()
                        d.addCallback(self.sbRequestAccepted)
@@ -312,27 +441,27 @@ class SwitchboardSession:
                else:
                        reactor.connectTCP(host, port, SwitchboardFactory(self, key, sessionID, reply))
                
-               debug.log("SwitchboardSession: \"%s\" \"%s\" \"%s\" created" % (self.msncon.username, self.remoteUser, self.resource))
+               LogEvent(INFO, self.ident)
        
        def removeMe(self, sbflag=True):
-               if(self.removed):
-                       debug.log("SwitchboardSession: removeMe called more than once! Traceback!")
+               if self.removed:
+                       log.err("removeMe called more than once!")
                        return
                self.removed = True
 
-               debug.log("SwitchboardSession: \"%s\" \"%s\" \"%s\" destroyed" % (self.msncon.username, self.remoteUser, self.resource))
+               LogEvent(INFO, self.ident)
                for message, noerror in self.messageBuffer:
-                       if(not noerror):
+                       if not noerror:
                                self.msncon.failedMessage(self.remoteUser, message)
                self.messageBuffer = []
 
                del self.msncon.switchboardSessions[self.remoteUser]
                self.msncon = None
-               if(sbflag and self.switchboard):
+               if sbflag and self.switchboard:
                        self.switchboard.removeMe()
                self.switchboard = None
                self.ready = False
-               if(self.killTimer and not self.killTimer.called):
+               if self.killTimer and not self.killTimer.called:
                        self.killTimer.cancel()
                self.killTimer = None
 
@@ -347,32 +476,15 @@ class SwitchboardSession:
                # Connect to the switchboard server
                reactor.connectTCP(host, port, SwitchboardFactory(self, key))
        
-       def sendMessage(self, message, noerror):
-               if(self.ready):
-                       debug.log("SwitchboardSession: \"%s\" \"%s\" sending message \"%s\"" % (self.msncon.username, self.remoteUser, message))
-                       message = str(message.encode("utf-8"))
-                       msnmessage = msn.MSNMessage(message=message)
-                       msnmessage.setHeader("Content-Type", "text/plain; charset=UTF-8")
-                       msnmessage.ack = msn.MSNMessage.MESSAGE_NACK
-                       def failedMessage(ignored):
-                               tempmsncon.failedMessage(self.remoteUser, message)
-                       d = self.switchboard.sendMessage(msnmessage)
-                       tempmsncon = self.msncon # In case MSN tells us the message failed after removeMe()
-                       if(not noerror):
-                               d.addCallback(failedMessage)
-                       self.resetTimer()
-               else:
-                       self.messageBuffer.append((message, noerror))
-       
        def sendTypingNofication(self):
-               if(self.ready):
+               if self.ready:
                        self.switchboard.sendTypingNotification()
        
        def contactTyping(self):
                self.msncon.gotContactTyping(self.remoteUser, self.resource)
        
        def flushBuffer(self):
-               for m, noerror in utils.copyList(self.messageBuffer):
+               for m, noerror in self.messageBuffer[:]:
                        self.messageBuffer.remove((m, noerror))
                        self.sendMessage(m, noerror)
        
@@ -380,20 +492,30 @@ class SwitchboardSession:
                self.msncon.gotMessage(self.remoteUser, self.resource, message.getMessage())
                self.resetTimer()
        
+       CAPS = msn.MSNContact.MSNC1 | msn.MSNContact.MSNC2 | msn.MSNContact.MSNC3 | msn.MSNContact.MSNC4
+       def requestAvatar(self):
+               if not self.switchboard: return
+               msnContacts = self.msncon.getContacts()
+               if not msnContacts: return
+               msnContact = msnContacts.getContact(self.remoteUser)
+               if not (msnContact and msnContact.caps & self.CAPS and msnContact.msnobj): return
+               if msnContact.msnobjGot: return
+               msnContact.msnobjGot = True # This is deliberately set before we get the avatar. So that we don't try to reget failed avatars over & over
+               self.switchboard.sendAvatarRequest(self.remoteUser, msnContact.msnobj)
+       
        def userJoined(self, userHandle):
-               if(userHandle != self.remoteUser):
+               if userHandle != self.remoteUser:
                        # Another user has joined, so we now have three participants (these two and ourself)
                        switchToGroupchat(self, self.remoteUser, userHandle)
+               else:
+                       self.requestAvatar()
        
        def userLeft(self, userHandle):
-               if(userHandle == self.remoteUser):
+               if userHandle == self.remoteUser:
                        self.removeMe()
 
 
 
-
-
-
 class DispatchFactory(ClientFactory):
        def __init__(self, msncon):
                self.msncon = msncon
@@ -402,7 +524,10 @@ class DispatchFactory(ClientFactory):
                p = Dispatch(self.msncon)
                del self.msncon # No longer needed
                return p
-
+       
+       def clientConnectionFailed(self, connector, reason):
+               self.msncon.connectionLostBase(reason)
+       
 
 class Dispatch(msn.DispatchClient):
        def __init__(self, msncon):
@@ -416,7 +541,7 @@ class Dispatch(msn.DispatchClient):
        
        def gotNotificationReferral(self, host, port):
                self.transport.loseConnection()
-               if(self.msncon and self.msncon.session and self.msncon.session.alive):
+               if self.msncon and self.msncon.session and self.msncon.session.alive:
                        reactor.connectTCP(host, port, self.msncon.notificationFactory)
 
 
@@ -425,17 +550,17 @@ class Notification(msn.NotificationClient):
        def __init__(self):
                self.removed = False
 
-               msn.NotificationClient.__init__(self, proxy=config.proxyServer, proxyport=config.proxyPort)
+               msn.NotificationClient.__init__(self)
 
        def removeMe(self):
-               if(self.removed):
-                       debug.log("Notification: removeMe called more than once! Traceback!")
+               if self.removed:
+                       log.err("removeMe called more than once!")
                        return
                self.removed = True
 
                self.logOut()
                self.transport.loseConnection()
-               if(self.factory.msncon):
+               if self.factory.msncon:
                        self.factory.msncon.notificationProtocol = None
                        self.factory.msncon = None
                self.factory = None
@@ -443,167 +568,105 @@ class Notification(msn.NotificationClient):
                utils.mutilateMe(self)
        
        def badConditions(self):
-               if(not (self.factory and self.factory.msncon and self.factory.msncon.session and self.factory.msncon.session.alive)):
-                       if(not self.removed):
+               if not (self.factory and self.factory.msncon and self.factory.msncon.session and self.factory.msncon.session.alive):
+                       if not self.removed:
                                self.removeMe()
                        return True
                return False
                
        
        def loginFailure(self, message):
-               if(self.badConditions()): return
+               if self.badConditions(): return
                self.factory.msncon.loginFailure(message)
        
-       def loggedIn(self, userHandle, screenName, verified):
-               if(self.badConditions()): return
+       def loggedIn(self, userHandle, verified):
+               if self.badConditions(): return
 
                self.factory.msncon.notificationProtocolReady(self)
-               if(not verified):
+               if not verified:
                        self.factory.msncon.accountNotVerified()
                
-               msn.NotificationClient.loggedIn(self, userHandle, screenName, verified)
+               msn.NotificationClient.loggedIn(self, userHandle, verified)
                
-               debug.log("NotificationClient: \"%s\" authenticated with MSN servers" % (self.factory.msncon.username))
-       
-       def gotMessage(self, msnmessage):
-               if(self.badConditions()): return
-               debug.log("NotificationClient: \"%s\" gotMessage()" % (self.factory.msncon.username))
-       
-               cTypes = [s.lstrip() for s in msnmessage.getHeader("Content-Type").split(';')]
-               def getFields():
-                       fields = msnmessage.getMessage().strip().split('\n')
-                       values = {}
-                       for i in fields:
-                               a = i.split(':')
-                               if(len(a) != 2): continue
-                               f, v = a
-                               f = f.strip()
-                               v = v.strip()
-                               values[f] = v
-                       return values
-               
-               if("text/x-msmsgsinitialemailnotification" in cTypes and config.mailNotifications):
-                       values = getFields()
-                       try:
-                               inboxunread = int(values["Inbox-Unread"])
-                               foldersunread = int(values["Folders-Unread"])
-                       except KeyError:
-                               return
-                       if(foldersunread + inboxunread == 0): return # For some reason MSN sends notifications about empty inboxes sometimes?
-                       debug.log("NotificationClient: \"%s\" Initial hotmail notification" % (self.factory.msncon.username))
-                       self.factory.msncon.initialEmailNotification(inboxunread, foldersunread)
-               
-               elif("text/x-msmsgsemailnotification" in cTypes and config.mailNotifications):
-                       values = getFields()
-                       try:
-                               mailfrom = values["From"]
-                               fromaddr = values["From-Addr"]
-                               subject = values["Subject"]
-                               junkbeginning = "=?\"us-ascii\"?Q?"
-                               junkend = "?="
-                               subject = subject.replace(junkbeginning, "").replace(junkend, "").replace("_", " ")
-                       except KeyError:
-                               # If any of the fields weren't found then it's not a big problem. We just ignore the message
-                               return
-                       debug.log("NotificationClient: \"%s\" Live hotmail notification" % (self.factory.msncon.username))
-                       self.factory.msncon.realtimeEmailNotification(mailfrom, fromaddr, subject)
-
-               elif("NOTIFICATION" == msnmessage.userHandle):
-                       notification = utils.parseText(msnmessage.message)
-                       siteurl = notification.getAttribute("siteurl")
-                       notid = notification.getAttribute("id")
-
-                       msg = None
-                       for e in notification.elements():
-                               if(e.name == "MSG"):
-                                       msg = e
-                                       break
-                       else: return
-
-                       msgid = msg.getAttribute("id")
-
-                       action = None
-                       subscr = None
-                       bodytext = None
-                       for e in msg.elements():
-                               if(e.name == "ACTION"):
-                                       action = e.getAttribute("url")
-                               if(e.name == "SUBSCR"):
-                                       subscr = e.getAttribute("url")
-                               if(e.name == "BODY"):
-                                       for e2 in e.elements():
-                                               if(e2.name == "TEXT"):
-                                                       bodytext = e2.__str__()
-                       if(not (action and subscr and bodytext)): return
-
-
-                       actionurl = "%s&notification_id=%s&message_id=%s&agent=messenger" % (action, notid, msgid)
-                       subscrurl = "%s&notification_id=%s&message_id=%s&agent=messenger" % (subscr, notid, msgid)
-                       
-                       self.factory.msncon.msnAlert(bodytext, actionurl, subscrurl)
-
+               LogEvent(INFO, self.factory.msncon.session.jabberID)
        
+       def msnAlertReceived(self, body, action, subscr):
+               if self.badConditions(): return
+               self.factory.msncon.msnAlert(body, action, subscr)
+
+       def initialEmailNotification(self, inboxunread, foldersunread):
+               if self.badConditions() or not config.mailNotifications: return
+               self.factory.msncon.initialEmailNotification(inboxunread, foldersunread)
+
+       def realtimeEmailNotification(self, mailfrom, fromaddr, subject):
+               if self.badConditions() or not config.mailNotifications: return
+               self.factory.msncon.realtimeEmailNotification(mailfrom, fromaddr, subject)
        
        def connectionLost(self, reason):
-               if(self.badConditions()): return
+               if self.badConditions(): return
                def wait():
-                       debug.log("NotificationClient: \"%s\" lost connection with MSN servers" % (self.factory.userHandle))
+                       LogEvent(INFO, self.factory.msncon.session.jabberID)
                        msn.NotificationClient.connectionLost(self, reason)
                        self.factory.msncon.connectionLostBase(reason)
                # Make sure this event is handled after any others
                reactor.callLater(0, wait)
        
        def listSynchronized(self, *args):
-               if(self.badConditions()): return
-               debug.log("NotificationClient: \"%s\" MSN contact lists synchronised" % (self.factory.userHandle))
+               if self.badConditions(): return
+               LogEvent(INFO, self.factory.msncon.session.jabberID)
                self.factory.msncon.listSynchronized()
-               if(self.badConditions()): return # Just in case the session is deregistered
-               self.factory.msncon.sendSavedStatus()
+               if self.badConditions(): return # Just in case the session is deregistered
+               self.factory.msncon.sendSavedEvents()
+               self.setPrivacyMode(False)
        
        def gotSwitchboardInvitation(self, sessionID, host, port, key, remoteUser, screenName):
-               if(self.badConditions()): return
-               debug.log("NotificationClient: \"%s\" gotSwitchboardInvitation(\"%s\")" % (self.factory.userHandle, remoteUser))
+               if self.badConditions(): return
+               LogEvent(INFO, self.factory.msncon.session.jabberID)
                sbs = SwitchboardSession(self.factory.msncon, remoteUser, self.factory.msncon.session.highestResource(), True, host, port, key, sessionID)
-               if(self.factory.msncon.switchboardSessions.has_key(remoteUser)):
+               if self.factory.msncon.switchboardSessions.has_key(remoteUser):
                        self.factory.msncon.switchboardSessions[remoteUser].removeMe()
                self.factory.msncon.switchboardSessions[remoteUser] = sbs
        
+       def avatarHashChanged(self, userHandle, hash):
+               if self.badConditions(): return
+               LogEvent(INFO, self.factory.msncon.session.jabberID)
+               hash = base64.decodestring(hash)
+               hash = binascii.hexlify(hash)
+               self.factory.msncon.avatarHashChanged(userHandle, hash)
+       
        def contactStatusChanged(self, statusCode, userHandle, screenName):
-               if(self.badConditions()): return
-               debug.log("NotificationClient: \"%s\" contactStatusChanged(\"%s\", \"%s\")" % (self.factory.userHandle, statusCode, userHandle))
-               msn.NotificationClient.contactStatusChanged(self, statusCode, userHandle, screenName)
+               if self.badConditions(): return
+               LogEvent(INFO, self.factory.msncon.session.jabberID)
                
                self.factory.msncon.contactStatusChanged(userHandle)
        
-       def gotContactStatus(self, statusCode, userHandle, screenName):
-               if(self.badConditions()): return
-               msn.NotificationClient.gotContactStatus(self, statusCode, userHandle, screenName)
-               debug.log("NotificationClient: \"%s\" gotContactStatus(\"%s\", \"%s\")" % (self.factory.userHandle, statusCode, userHandle))
-               
+       def contactPersonalChanged(self, userHandle, personal):
+               if self.badConditions(): return
+               msn.NotificationClient.contactPersonalChanged(self, userHandle, personal)
                self.factory.msncon.contactStatusChanged(userHandle)
        
        def contactOffline(self, userHandle):
-               if(self.badConditions()): return
-               debug.log("NotificationClient: \"%s\" contactOffline(\"%s\")" % (self.factory.userHandle, userHandle))
+               if self.badConditions(): return
+               LogEvent(INFO, self.factory.msncon.session.jabberID)
                msn.NotificationClient.contactOffline(self, userHandle)
                
                self.factory.msncon.contactStatusChanged(userHandle)
        
-       def userAddedMe(self, userHandle, screenName, listVersion):
-               if(self.badConditions()): return
-               debug.log("NotificationClient: \"%s\" userAddedMe(\"%s\", \"%s\")" % (self.factory.userHandle, userHandle, listVersion))
-               msn.NotificationClient.userAddedMe(self, userHandle, screenName, listVersion)
+       def userAddedMe(self, userGuid, userHandle, screenName):
+               if self.badConditions(): return
+               LogEvent(INFO, self.factory.msncon.session.jabberID)
+               msn.NotificationClient.userAddedMe(self, userGuid, userHandle, screenName)
                self.factory.msncon.userAddedMe(userHandle)
        
-       def userRemovedMe(self, userHandle, listVersion):
-               if(self.badConditions()): return
-               debug.log("NotificationClient: \"%s\" userRemovedMe(\"%s\", \"%s\")" % (self.factory.userHandle, userHandle, listVersion))
-               msn.NotificationClient.userRemovedMe(self, userHandle, listVersion)
+       def userRemovedMe(self, userGuid, userHandle):
+               if self.badConditions(): return
+               LogEvent(INFO, self.factory.msncon.session.jabberID)
+               msn.NotificationClient.userRemovedMe(self, userGuid, userHandle)
                self.factory.msncon.userRemovedMe(userHandle)
        
        def multipleLogin(self):
-               if(self.badConditions()): return
-               debug.log("NotificationClient: \"%s\" multiple logins" % (self.factory.msncon.username))
+               if self.badConditions(): return
+               LogEvent(INFO, self.factory.msncon.session.jabberID)
                self.factory.msncon.multipleLogin()
 
 
@@ -617,7 +680,7 @@ class SwitchboardFactory(ClientFactory):
        
        def buildProtocol(self, addr):
                p = Switchboard(self.switchboardSession)
-               if(p.badConditions()): return p
+               if p.badConditions(): return p
                p.key = self.key
                p.sessionID = self.sessionID
                p.reply = self.reply
@@ -629,65 +692,62 @@ class Switchboard(msn.SwitchboardClient):
        def __init__(self, switchboardSession):
                self.removed = False
 
-               msn.SwitchboardClient.__init__(self)
                self.switchboardSession = switchboardSession
                self.chattingUsers = []
                self.callid = None
-               if(self.badConditions()): return
-               debug.log("SwitchboardClient: \"%s\" \"%s\" - created" % (self.switchboardSession.msncon.username, self.switchboardSession))
+               msn.SwitchboardClient.__init__(self)
+               if self.badConditions(): return
+               self.msnobj = self.switchboardSession.msncon.notificationProtocol.msnobj
+               LogEvent(INFO, self.switchboardSession.ident)
        
        def removeMe(self):
-               if(self.removed):
-                       debug.log("Switchboard: removeMe called more than once! Traceback!")
+               if self.removed:
+                       log.err("removeMe called more than once!")
                        return
                self.removed = True
 
                self.transport.loseConnection()
-               debug.log("SwitchboardClient: \"%s\" - destroyed" % (self.switchboardSession))
+               LogEvent(INFO, self.switchboardSession.ident)
                self.switchboardSession = None
                self.factory.switchboardSession = None
                self.factory = None
 
-               if(self.callid and not self.callid.called):
+               if self.callid and not self.callid.called:
                        self.callid.cancel() # Cancel the invite fail message
                self.callid = None
 
                utils.mutilateMe(self)
        
        def badConditions(self):
-               if(not (self.switchboardSession and self.switchboardSession.msncon and self.switchboardSession.msncon.session and self.switchboardSession.msncon.session.alive)):
-                       if(self.switchboardSession):
-                               if(not self.switchboardSession.removed):
+               if not (self.switchboardSession and self.switchboardSession.msncon and self.switchboardSession.msncon.session and self.switchboardSession.msncon.session.alive):
+                       if self.switchboardSession:
+                               if not self.switchboardSession.removed:
                                        self.switchboardSession.removeMe()
-                       elif(not self.removed):
+                       elif not self.removed:
                                self.removeMe()
                        return True
                return False
        
        def loggedIn(self):
-               if(self.badConditions()): return
-               if((not self.reply) and self.switchboardSession.__class__ == SwitchboardSession):
+               if self.badConditions(): return
+               if (not self.reply) and isinstance(self.switchboardSession, SwitchboardSession):
                        def failCB(arg=None):
-                               debug.log(templogmessage)
+                               LogEvent(INFO, ident, "User has not joined after 30 seconds.")
                                self.switchboardSession.removeMe()
                        d = self.inviteUser(self.switchboardSession.remoteUser)
                        d.addErrback(failCB)
-                       templogmessage = "SwitchboardClient: \"%s\" \"%s\" - user has NOT joined after 30 seconds" % (self.switchboardSession.msncon.username, self.switchboardSession.remoteUser)
+                       ident = self.switchboardSession.ident
                        # If the user doesn't join then we want to tear down the SwitchboardSession
                        self.callid = reactor.callLater(30.0, failCB)
                
                else:
                        self.readySwitchboardSession()
        
-       def readySwitchboardSession(self, ignored=None):
-               if(self.badConditions()): return
-               debug.log("SwitchboardClient: \"%s\" \"%s\" - ready for use" % (self.switchboardSession.msncon.username, self.switchboardSession))
-               self.switchboardSession.ready = True
-               self.switchboardSession.switchboard = self
-               self.switchboardSession.flushBuffer()
+       def readySwitchboardSession(self):
+               self.switchboardSession.switchboardReady(self)
                for user in self.chattingUsers:
                        self.switchboardSession.userJoined(user)
-               if(self.callid and not self.callid.called):
+               if self.callid and not self.callid.called:
                        self.callid.cancel() # Cancel the invite fail message (only applies if we needed to invite the user)
                self.callid = None
        
@@ -696,43 +756,49 @@ class Switchboard(msn.SwitchboardClient):
                        self.chattingUsers.append(user)
        
        def userJoined(self, userHandle, screenName):
-               if(self.badConditions()): return
-               if((not self.reply) and self.switchboardSession.__class__ == SwitchboardSession):
+               if self.badConditions(): return
+               # FIXME - check this is correct
+               if (not self.reply) and isinstance(self.switchboardSession, SwitchboardSession):
                        self.readySwitchboardSession()
-               debug.log("SwitchboardClient: \"%s\" \"%s\" - userJoined(\"%s\")" % (self.switchboardSession.msncon.username, self.switchboardSession, userHandle))
+               LogEvent(INFO, self.switchboardSession.ident)
                self.switchboardSession.userJoined(userHandle)
-               self.sendClientCaps()
        
        def userLeft(self, userHandle):
-               if(self.badConditions()): return
-               debug.log("SwitchboardClient: \"%s\" \"%s\" - userLeft(\"%s\")" % (self.switchboardSession.msncon.username, self.switchboardSession, userHandle))
+               if self.badConditions(): return
+               LogEvent(INFO, self.switchboardSession.ident)
                def wait():
+                       if self.badConditions(): return
                        self.switchboardSession.userLeft(userHandle)
                # Make sure this event is handled after any others (eg, gotMessage)
                reactor.callLater(0, wait)
        
        def gotMessage(self, message):
-               if(self.badConditions()):
-                       debug.log("SwitchboardClient: gotMessage called too late! Traceback!")
+               if self.badConditions():
+                       LogEvent(WARN, self.switchboardSession.ident, "gotMessage() called too late. Dropped a message!")
                        return
-               debug.log("SwitchboardClient: \"%s\" \"%s\" gotMessage(\"%s\")" % (self.switchboardSession.msncon.username, message.userHandle, message.getMessage()))
+
+               LogEvent(INFO, self.switchboardSession.ident)
                cTypes = [s.lstrip() for s in message.getHeader("Content-Type").split(';')]
-               if("text/plain" in cTypes):
-                       if(len(cTypes) > 1 and cTypes[1].find("UTF-8") >= 0):
-                               message.message = message.message.decode("utf-8")
-                       self.switchboardSession.gotMessage(message)
+               if "text/plain" in cTypes:
+                       try:
+                               if len(cTypes) > 1 and cTypes[1].find("UTF-8") >= 0:
+                                       message.message = message.message.decode("utf-8")
+                               self.switchboardSession.gotMessage(message)
+                       except:
+                               self.switchboardSession.gotMessage(lang.get(self.switchboardSession.msncon.session.lang).msnDroppedMessage) # FIXME, this is a little deep
+                               raise
                        return
-               if("text/x-clientcaps" in cTypes):
-                       if(message.hasHeader("JabberID")):
+               if "text/x-clientcaps" in cTypes:
+                       if message.hasHeader("JabberID"):
                                jid = message.getHeader("JabberID")
                                self.switchboardSession.msncon.userMapping(message.userHandle, jid)
                        return
-               debug.log("Discarding unknown message type: %s" % (message.getMessage()))
+               LogEvent(INFO, self.switchboardSession.ident, "Discarding unknown message type.")
        
        def userTyping(self, message):
-               if(self.badConditions()): return
-               if(self.switchboardSession.__class__ == SwitchboardSession): # Ignore typing in groupchats
-                       if(message.userHandle == self.switchboardSession.remoteUser):
+               if self.badConditions(): return
+               if isinstance(self.switchboardSession, SwitchboardSession): # Ignore typing in groupchats
+                       if message.userHandle == self.switchboardSession.remoteUser:
                                self.switchboardSession.contactTyping()
        
        def sendClientCaps(self):
@@ -741,5 +807,26 @@ class Switchboard(msn.SwitchboardClient):
                message.setHeader("Client-Name", "PyMSNt")
                message.setHeader("JabberID", str(self.switchboardSession.msncon.session.jabberID)) # FIXME, this is a little deep
                self.sendMessage(message)
+       
+       def sendMessage(self, message):
+               # A little bit of fancyness to make sure that clientcaps
+               # only gets sent after the first text message.
+               if message.getHeader("Content-Type").startswith("text"):
+                       self.sendMessage = type(self.sendMessage)(msn.SwitchboardClient.sendMessage, self, Switchboard)
+                       self.sendClientCaps()
+                       return self.sendMessage(message)
+               else:
+                       return msn.SwitchboardClient.sendMessage(self, message)
+       
+       def gotAvatarImage(self, to, image):
+               if self.badConditions(): return
+               self.switchboardSession.gotAvatarImage(to, image)
+       
+       def gotSendRequest(self, fileReceive):
+               if self.badConditions():
+                       fileReceive.accept(False)
+                       return
+               LogEvent(INFO, self.switchboardSession.ident)
+               self.switchboardSession.gotSendRequest(fileReceive)