]> code.delx.au - pymsnt/blobdiff - src/tlib/msn/msnw.py
Data received after the connection has been cleaned up is now ignored (instead of...
[pymsnt] / src / tlib / msn / msnw.py
index 66d2105c2f71a74b3d5f1a1eaf6fb43d0689363a..2241c22aab20b3c885233d305f26a95e7d0e04e0 100644 (file)
@@ -7,28 +7,25 @@ from twisted.internet.defer import Deferred
 from twisted.internet.protocol import ClientFactory
 
 # System imports
-import math, base64, binascii, math
+import math, base64, binascii
 
 # Local imports
 from debug import LogEvent, INFO, WARN, ERROR
 from tlib.msn import msn
 
-# Imports from msn
-from msn import FORWARD_LIST, ALLOW_LIST, BLOCK_LIST, REVERSE_LIST, PENDING_LIST
-from msn import STATUS_ONLINE, STATUS_OFFLINE, STATUS_HIDDEN, STATUS_IDLE, STATUS_AWAY, STATUS_BUSY, STATUS_BRB, STATUS_PHONE, STATUS_LUNCH
-
-MAXMESSAGESIZE     = 1400
-SWITCHBOARDTIMEOUT = 30.0*60.0
-GETALLAVATARS      = True
 
 
 """
-All interaction should be with the MSNConnection class.
+All interaction should be with the MSNConnection and MultiSwitchboardSession classes.
 You should not directly instantiate any objects of other classes.
 """
 
 class MSNConnection:
        """ Manages all the Twisted factories, etc """
+       MAXMESSAGESIZE     = 1400
+       SWITCHBOARDTIMEOUT = 30.0*60.0
+       GETALLAVATARS      = False
+
        def __init__(self, username, password, ident):
                """ Connects to the MSN servers.
                @param username: the MSN passport to connect with.
@@ -39,6 +36,8 @@ class MSNConnection:
                self.password = password
                self.ident = ident
                self.timeout = None
+               self.notificationFactory = None
+               self.notificationClient = None
                self.connect()
                LogEvent(INFO, self.ident)
        
@@ -51,7 +50,10 @@ class MSNConnection:
 
        def _getNotificationReferral(self):
                def timeout():
-                       if not d.called: d.errback()
+                       self.timeout = None
+                       if not d.called:
+                               d.errback(Exception("Timeout"))
+                               self.logOut() # Clean up everything
                self.timeout = reactor.callLater(30, timeout)
                dispatchFactory = msn.DispatchFactory()
                dispatchFactory.userHandle = self.username
@@ -64,6 +66,7 @@ class MSNConnection:
        
        def _gotNotificationReferral(self, (host, port)):
                self.timeout.cancel()
+               self.timeout = None
                # Create the NotificationClient
                self.notificationFactory = msn.NotificationFactory()
                self.notificationFactory.userHandle = self.username
@@ -75,7 +78,6 @@ class MSNConnection:
        
        def _sendSavedEvents(self):
                self.savedEvents.send(self)
-               self.savedEvents = None
        
        def _notificationClientReady(self, notificationClient):
                self.notificationClient = notificationClient
@@ -115,7 +117,7 @@ class MSNConnection:
                elif not noerror:
                        self.failedMessage(userHandle, text)
 
-       def requestAvatar(self, userHandle):
+       def sendAvatarRequest(self, userHandle):
                """
                Requests the avatar of a contact.
 
@@ -128,7 +130,7 @@ class MSNConnection:
 
                LogEvent(INFO, self.ident)
                if not self.notificationClient: return
-               if GETALLAVATARS:
+               if MSNConnection.GETALLAVATARS:
                        self._ensureSwitchboardSession(userHandle)
                sb = self.switchboardSessions.get(userHandle)
                if sb: return sb.sendAvatarRequest()
@@ -141,8 +143,9 @@ class MSNConnection:
                @param filename: the name of the file to send.
                @param filesize: the size of the file to send.
         
-               @return: (fileSend, d) A FileSend object and a Deferred.
-                        The Deferred will pass one argument in a tuple,
+               @return: A Deferred, which will fire with an argument of:
+                        (fileSend, d) A FileSend object and a Deferred.
+                        The new Deferred will pass one argument in a tuple,
                         whether or not the transfer is accepted. If you
                         receive a True, then you can call write() on the
                         fileSend object to send your file. Call close()
@@ -150,11 +153,15 @@ class MSNConnection:
                         NOTE: You MUST write() exactly as much as you
                         declare in filesize.
                """
-               raise NotImplementedError # May have to wait for the switchboardSession to exist
+               msnContact = self.getContacts().getContact(userHandle)
+               if not msnContact:
+                       raise ValueError, "Contact not found"
+               self._ensureSwitchboardSession(userHandle)
+               return self.switchboardSessions[userHandle].sendFile(msnContact, filename, filesize)
 
        def sendTypingToContact(self, userHandle):
                """
-               Sends typing notification to a contact.
+               Sends typing notification to a contact. Should send every 5secs.
                @param userHandle: the contact to notify of our typing.
                """
 
@@ -169,8 +176,8 @@ class MSNConnection:
                if self.notificationClient:
                        LogEvent(INFO, self.ident)
                        self.notificationClient.changeAvatar(imageData, push=True)
-               else:
-                       self.savedEvents.avatarImageData = imageData
+               # Save the avatar for reuse on disconnection
+               self.savedEvents.avatarImageData = imageData
        
        def changeStatus(self, statusCode, screenName, personal):
                """
@@ -182,20 +189,23 @@ class MSNConnection:
                @param personal: the user's new personal message.
                """
 
+               if not screenName: screenName = self.username
+               if not statusCode: statusCode = msn.STATUS_ONLINE
+               if not personal: personal = ""
                if self.notificationClient:
-                       count = 0
-                       def cb():
-                               if count == 3:
+                       changeCount = [0] # Hack
+                       def cb(ignored=None):
+                               changeCount[0] += 1
+                               if changeCount[0] == 3:
                                        self.ourStatusChanged(statusCode, screenName, personal)
                        LogEvent(INFO, self.ident)
                        self.notificationClient.changeStatus(statusCode.encode("utf-8")).addCallback(cb)
                        self.notificationClient.changeScreenName(screenName.encode("utf-8")).addCallback(cb)
-                       if not personal: personal = ""
-                       self.notificationClient.changePersonalMessage(personal.encode("utf-8"))
-               else:
-                       self.savedEvents.statusCode = statusCode
-                       self.savedEvents.screenName = screenName
-                       self.savedEvents.personal = personal
+                       self.notificationClient.changePersonalMessage(personal.encode("utf-8")).addCallback(cb)
+               # Remember the saved status
+               self.savedEvents.statusCode = statusCode
+               self.savedEvents.screenName = screenName
+               self.savedEvents.personal = personal
                                
        def addContact(self, listType, userHandle):
                """ See msn.NotificationClient.addContact """
@@ -213,7 +223,7 @@ class MSNConnection:
        
        def logOut(self):
                """ Shuts down the whole connection. Don't try to call any
-               other methods after this one. """
+               other methods after this one. Except maybe connect() """
                if self.notificationClient:
                        self.notificationClient.logOut()
                for c in self.connectors:
@@ -221,7 +231,13 @@ class MSNConnection:
                if self.notificationFactory:
                        self.notificationFactory.msncon = None
                self.connectors = []
+               for sbs in self.switchboardSessions.values():
+                       if hasattr(sbs, "transport") and sbs.transport:
+                               sbs.transport.loseConnection()
                self.switchboardSessions = {}
+               if self.timeout:
+                       self.timeout.cancel()
+                       self.timeout = None
                LogEvent(INFO, self.ident)
                
        
@@ -229,6 +245,9 @@ class MSNConnection:
        def connectionFailed(self, reason=''):
                """ Called when the connection to the server failed. """
        
+       def loginFailed(self, reason=''):
+               """ Called when the account could not be logged in. """
+       
        def connectionLost(self, reason=''):
                """ Called when we are disconnected. """
        
@@ -261,19 +280,32 @@ class MSNConnection:
        def gotMessage(self, userHandle, text):
                """ Called when a contact sends us a message """
        
+       def gotGroupchat(self, msnGroupchat, userHandle):
+               """ Called when a conversation with more than one contact begins.
+               userHandle is the person who invited us.
+               The overriding method is expected to set msnGroupchat.groupchat to an object
+               that implements the following methods:
+                       contactJoined(userHandle)
+                       contactLeft(userHandle)
+                       gotMessage(userHandle, text)
+
+               The object received as 'msnGroupchat' is an instance of MultiSwitchboardSession.
+               """
+       
+       def gotContactTyping(self, userHandle):
+               """ Called when a contact sends typing notification.
+                   Will be called once every 5 seconds. """
+       
        def failedMessage(self, userHandle, text):
                """ Called when a message we sent has been bounced back. """
 
        def contactAvatarChanged(self, userHandle, hash):
                """ Called when we receive a changed avatar hash for a contact.
-               You should call requestAvatar(). """
+               You should call sendAvatarRequest(). """
        
-       def contactStatusChanged(self, userHandle, statusCode, screenName):
+       def contactStatusChanged(self, userHandle):
                """ Called when we receive status information for a contact. """
        
-       def contactPersonalChanged(self, personal):
-               """ Called when we receive a new personal message for a contact. """
-       
        def gotFileReceive(self, fileReceive):
                """ Called when a contact sends the user a file.
                Call accept(fileHandle) or reject() on the object. """
@@ -283,11 +315,26 @@ class MSNConnection:
        
        def contactRemovedMe(self, userHandle):
                """ Called when a contact removes the user from their list. """
+       
+       def gotInitialEmailNotification(self, inboxunread, foldersunread):
+               """ Received at login to tell about the user's Hotmail status """
+       
+       def gotRealtimeEmailNotification(self, mailfrom, fromaddr, subject):
+               """ Received in realtime whenever an email comes into the hotmail account """
+       
+       def gotMSNAlert(self, body, action, subscr):
+               """ An MSN Alert (http://alerts.msn.com) was received. Body is the
+               text of the alert. 'action' is a url for more information,
+               'subscr' is a url to modify your your alerts subscriptions. """
+       
+       def gotAvatarImageData(self, userHandle, imageData):
+               """ An contact's avatar has been received because a switchboard
+               session with them was started. """
 
 
 class SavedEvents:
        def __init__(self):
-               self.nickname = ""
+               self.screenName = ""
                self.statusCode = ""
                self.personal = ""
                self.avatarImageData = ""
@@ -297,8 +344,8 @@ class SavedEvents:
        def send(self, msncon):
                if self.avatarImageData:
                        msncon.notificationClient.changeAvatar(self.avatarImageData, push=False)
-               if self.nickname or self.statusCode or self.personal:
-                       msncon.changeStatus(self.statusCode, self.nickname, self.personal)
+               if self.screenName or self.statusCode or self.personal:
+                       msncon.changeStatus(self.statusCode, self.screenName, self.personal)
                for listType, userHandle in self.addContacts:
                        msncon.addContact(listType, userHandle)
                for listType, userHandle in self.remContacts:
@@ -314,12 +361,13 @@ class DispatchClient(msn.DispatchClient):
 
 class NotificationClient(msn.NotificationClient):
        def loginFailure(self, message):
-               self.factory.msncon.connectionFailed(message)
+               self.factory.msncon.loginFailed(message)
        
        def loggedIn(self, userHandle, verified):
                LogEvent(INFO, self.factory.msncon.ident)
                msn.NotificationClient.loggedIn(self, userHandle, verified)
                self.factory.msncon._notificationClientReady(self)
+               self.factory.msncon.loggedIn()
                if not verified:
                        self.factory.msncon.accountNotVerified()
        
@@ -331,7 +379,8 @@ class NotificationClient(msn.NotificationClient):
                def wait():
                        LogEvent(INFO, self.factory.msncon.ident)
                        msn.NotificationClient.connectionLost(self, reason)
-                       self.factory.msncon.connectionLost(reason)
+                       if self.factory.maxRetries >= self.factory.retries:
+                               self.factory.msncon.connectionLost(reason)
                # Make sure this event is handled after any others
                reactor.callLater(0, wait)
        
@@ -366,24 +415,29 @@ class NotificationClient(msn.NotificationClient):
        
        def gotContactStatus(self, userHandle, statusCode, screenName): 
                LogEvent(INFO, self.factory.msncon.ident)
-               self.factory.msncon.contactStatusChanged(userHandle, statusCode, screenName)
+               self.factory.msncon.contactStatusChanged(userHandle)
        
        def contactStatusChanged(self, userHandle, statusCode, screenName):
                LogEvent(INFO, self.factory.msncon.ident)
-               self.factory.msncon.contactStatusChanged(userHandle, statusCode, screenName)
+               self.factory.msncon.contactStatusChanged(userHandle)
        
-       def contactOffline(self, userHandle):
+       def contactPersonalChanged(self, userHandle, personal):
                LogEvent(INFO, self.factory.msncon.ident)
-               self.factory.msncon.contactStatusChanged(userHandle, msn.STATUS_OFFLINE, "")
+               self.factory.msncon.contactStatusChanged(userHandle)
 
+       def contactOffline(self, userHandle):
+               LogEvent(INFO, self.factory.msncon.ident)
+               self.factory.msncon.contactStatusChanged(userHandle)
+       
        def gotSwitchboardInvitation(self, sessionID, host, port, key, userHandle, screenName):
                LogEvent(INFO, self.factory.msncon.ident)
-               sb = OneSwitchboardSession(self.factory.msncon, userHandle)
+               sb = self.factory.msncon.switchboardSessions.get(userHandle)
+               if sb and sb.transport:
+                       sb.transport.loseConnection()
+               else:
+                       sb = OneSwitchboardSession(self.factory.msncon, userHandle)
+                       self.factory.msncon.switchboardSessions[userHandle] = sb
                sb.connectReply(host, port, key, sessionID)
-               sbOld = self.factory.msncon.switchboardSessions.get(userHandle)
-               if sbOld:
-                       sbOld.disconnect()
-               self.factory.msncon.switchboardSessions[userHandle] = sb
        
        def multipleLogin(self):
                LogEvent(INFO, self.factory.msncon.ident)
@@ -395,29 +449,35 @@ class NotificationClient(msn.NotificationClient):
 
        
 
-def switchToGroupchat(*args):
-       raise NotImplementedError
-
-
-class SwitchboardSessionBase:
+class SwitchboardSessionBase(msn.SwitchboardClient):
        def __init__(self, msncon):
+               msn.SwitchboardClient.__init__(self)
                self.msncon = msncon
+               self.msnobj = msncon.notificationClient.msnobj
                self.userHandle = msncon.username
                self.ident = (msncon.ident, "INVALID!!")
                self.messageBuffer = []
+               self.funcBuffer = []
                self.ready = False
 
        def __del__(self):
                LogEvent(INFO, self.ident)
                del self.msncon
                self.transport.disconnect()
+       
+       def connectionLost(self, reason):
+               self.msncon = None
+               self.msnobj = None
+               self.ident = (self.ident[0], self.ident[1] + " Disconnected!")
 
-               for message, noerror in self.messageBuffer:
-                       if not noerror:
-                               self.msncon.failedMessage(self.remoteUser, message)
+       def loggedIn(self):
+               LogEvent(INFO, self.ident)
+               self.ready = True
+               self.flushBuffer()
 
        def connect(self):
                LogEvent(INFO, self.ident)
+               self.ready = False
                def sbRequestAccepted((host, port, key)):
                        LogEvent(INFO, self.ident)
                        self.key = key
@@ -433,6 +493,7 @@ class SwitchboardSessionBase:
        
        def connectReply(self, host, port, key, sessionID):
                LogEvent(INFO, self.ident)
+               self.ready = False
                self.key = key
                self.sessionID = sessionID
                self.reply = 1
@@ -444,22 +505,46 @@ class SwitchboardSessionBase:
                for message, noerror in self.messageBuffer[:]:
                        self.messageBuffer.remove((message, noerror))
                        self.sendMessage(message, noerror)
+               for f in self.funcBuffer[:]:
+                       self.funcBuffer.remove(f)
+                       f()
 
-       def failedMessage(self, ignored):
+       def failedMessage(self, *ignored):
                raise NotImplementedError
 
-       def sendMessage(self, text, noerror):
+       def sendClientCaps(self):
+               message = msn.MSNMessage()
+               message.setHeader("Content-Type", "text/x-clientcaps")
+               message.setHeader("Client-Name", "PyMSNt")
+               if hasattr(self.msncon, "jabberID"):
+                       message.setHeader("JabberID", str(self.msncon.jabberID))
+               self.sendMessage(message)
+       
+       def sendMessage(self, message, noerror=False):
+               # Check to make sure that clientcaps only gets sent after
+               # the first text type message.
+               if isinstance(message, msn.MSNMessage) and message.getHeader("Content-Type").startswith("text"):
+                       self.sendMessage = self.sendMessageReal
+                       self.sendClientCaps()
+                       return self.sendMessage(message, noerror)
+               else:
+                       return self.sendMessageReal(message, noerror)
+       
+       def sendMessageReal(self, text, noerror=False):
+               if not isinstance(text, basestring):
+                       msn.SwitchboardClient.sendMessage(self, text)
+                       return
                if not self.ready:
                        self.messageBuffer.append((text, noerror))
                else:
                        LogEvent(INFO, self.ident)
+                       text = str(text.replace("\n", "\r\n").encode("utf-8"))
                        def failedMessage(ignored):
                                if not noerror:
                                        self.failedMessage(text)
 
-                       if len(text) < MAXMESSAGESIZE:
-                               message = msn.MSNMessage(message=str(text.replace("\n", "\r\n").encode("utf-8")))
-                               message.setHeader("Content-Type", "text/plain; charset=UTF-8")
+                       if len(text) < MSNConnection.MAXMESSAGESIZE:
+                               message = msn.MSNMessage(message=text)
                                message.ack = msn.MSNMessage.MESSAGE_NACK
 
                                d = msn.SwitchboardClient.sendMessage(self, message)
@@ -467,18 +552,19 @@ class SwitchboardSessionBase:
                                        d.addCallback(failedMessage)
 
                        else:
-                               chunks = int(math.ceil(len(text) / float(MAXMESSAGESIZE)))
+                               chunks = int(math.ceil(len(text) / float(MSNConnection.MAXMESSAGESIZE)))
                                chunk = 0
                                guid = msn.random_guid()
                                while chunk < chunks:
-                                       offset = chunk * MAXMESSAGESIZE
-                                       text = message[offset : offset + MAXMESSAGESIZE]
-                                       message = msn.MSNMessage(message=str(text.replace("\n", "\r\n").encode("utf-8")))
+                                       offset = chunk * MSNConnection.MAXMESSAGESIZE
+                                       message = msn.MSNMessage(message=text[offset : offset + MSNConnection.MAXMESSAGESIZE])
                                        message.ack = msn.MSNMessage.MESSAGE_NACK
+                                       message.setHeader("Message-ID", guid)
                                        if chunk == 0:
-                                               message.setHeader("Content-Type", "text/plain; charset=UTF-8")
                                                message.setHeader("Chunks", str(chunks))
                                        else:
+                                               message.delHeader("MIME-Version")
+                                               message.delHeader("Content-Type")
                                                message.setHeader("Chunk", str(chunk))
 
                                        d = msn.SwitchboardClient.sendMessage(self, message)
@@ -487,21 +573,72 @@ class SwitchboardSessionBase:
 
                                        chunk += 1
 
-       def gotFileReceive(self, fileReceive):
-               LogEvent(INFO, self.ident)
-               self.msncon.gotFileReceive(fileReceive)
+
+class MultiSwitchboardSession(SwitchboardSessionBase):
+       """ Create one of me to chat to multiple contacts """
+
+       def __init__(self, msncon):
+               """ Automatically creates a new switchboard connection to the server """
+               SwitchboardSessionBase.__init__(self, msncon)
+               self.ident = (self.msncon.ident, self)
+               self.contactCount = 0
+               self.groupchat = None
+               self.connect()
+       
+       def failedMessage(self, text):
+               self.groupchat.gotMessage("BOUNCE", text)
        
+       def sendMessage(self, text, noerror=False):
+               """ Used to send a mesage to the groupchat. Can be called immediately
+               after instantiation. """
+               if self.contactCount > 0:
+                       SwitchboardSessionBase.sendMessage(self, text, noerror)
+               else:
+                       #self.messageBuffer.append((message, noerror))
+                       pass # They're sending messages to an empty room. Ignore.
+       
+       def inviteUser(self, userHandle):
+               """ Used to invite a contact to the groupchat. Can be called immediately
+               after instantiation. """
+               userHandle = str(userHandle)
+               if self.ready:
+                       LogEvent(INFO, self.ident, "immediate")
+                       msn.SwitchboardClient.inviteUser(self, userHandle)
+               else:
+                       LogEvent(INFO, self.ident, "pending")
+                       self.funcBuffer.append(lambda: msn.SwitchboardClient.inviteUser(self, userHandle))
+       
+       def gotMessage(self, message):
+               self.groupchat.gotMessage(message.userHandle, message.getMessage())
+
+       def userJoined(self, userHandle, screenName=''):
+               LogEvent(INFO, self.ident)
+               self.contactCount += 1
+               self.groupchat.contactJoined(userHandle)
+
+       def userLeft(self, userHandle):
+               LogEvent(INFO, self.ident)
+               self.contactCount -= 1
+               self.groupchat.contactLeft(userHandle)
+
 
 
-class OneSwitchboardSession(SwitchboardSessionBase, msn.SwitchboardClient):
+class OneSwitchboardSession(SwitchboardSessionBase):
        def __init__(self, msncon, remoteUser):
                SwitchboardSessionBase.__init__(self, msncon)
-               msn.SwitchboardClient.__init__(self)
-               self.remoteUser = remoteUser
-               self.ident = (self.msncon, self.remoteUser)
+               self.remoteUser = str(remoteUser)
+               self.ident = (self.msncon.ident, self.remoteUser)
                self.chattingUsers = []
                self.timeout = None
        
+       def __del__(self):
+               if self.timeout:
+                       self.timeout.cancel()
+               self.timeout = None
+               for message, noerror in self.messageBuffer:
+                       if not noerror:
+                               self.failedMessage(message)
+
        def _ready(self):
                LogEvent(INFO, self.ident)
                self.ready = True
@@ -509,8 +646,17 @@ class OneSwitchboardSession(SwitchboardSessionBase, msn.SwitchboardClient):
                        self.userJoined(user)
                if self.timeout:
                        self.timeout.cancel()
-               del self.timeout
+               self.timeout = None
                self.flushBuffer()
+
+       def _switchToMulti(self, userHandle):
+               LogEvent(INFO, self.ident)
+               del self.msncon.switchboardSessions[self.remoteUser]
+               self.__class__ = MultiSwitchboardSession
+               del self.remoteUser
+               self.contactCount = 0
+               self.msncon.gotGroupchat(self, userHandle)
+               assert self.groupchat
        
        def failedMessage(self, text):
                self.msncon.failedMessage(self.remoteUser, text)
@@ -520,8 +666,11 @@ class OneSwitchboardSession(SwitchboardSessionBase, msn.SwitchboardClient):
                LogEvent(INFO, self.ident)
                if not self.reply:
                        def failCB(arg=None):
-                               LogEvent(INFO, ident, "User has not joined after 30 seconds.")
+                               if not (self.msncon and self.msncon.switchboardSessions.has_key(self.remoteUser)):
+                                       return
+                               LogEvent(INFO, self.ident, "User has not joined after 30 seconds.")
                                del self.msncon.switchboardSessions[self.remoteUser]
+                               self.timeout = None
                        d = self.inviteUser(self.remoteUser)
                        d.addErrback(failCB)
                        self.timeout = reactor.callLater(30.0, failCB)
@@ -538,19 +687,44 @@ class OneSwitchboardSession(SwitchboardSessionBase, msn.SwitchboardClient):
                        self._ready()
                if userHandle != self.remoteUser:
                        # Another user has joined, so we now have three participants.
-                       switchToGroupchat(self, self.remoteUser, userHandle)
+                       remoteUser = self.remoteUser
+                       self._switchToMulti(remoteUser)
+                       self.userJoined(remoteUser)
+                       self.userJoined(userHandle)
                else:
-                       self.requestAvatar()
+                       def updateAvatarCB((imageData, )):
+                               if self.msncon:
+                                       self.msncon.gotAvatarImageData(self.remoteUser, imageData)
+                       d = self.sendAvatarRequest()
+                       if d:
+                               d.addCallback(updateAvatarCB)
 
        def userLeft(self, userHandle):
                def wait():
                        if userHandle == self.remoteUser:
-                               del self.msncon.switchboardSessions[self.remoteUser]
+                               if self.msncon and self.msncon.switchboardSessions.has_key(self.remoteUser):
+                                       del self.msncon.switchboardSessions[self.remoteUser]
                reactor.callLater(0, wait) # Make sure this is handled after everything else
 
        def gotMessage(self, message):
                LogEvent(INFO, self.ident)
-               self.msncon.gotMessage(self.remoteUser, message.getMessage())
+               cTypes = [s.strip() for s in message.getHeader("Content-Type").split(';')]
+               if "text/plain" == cTypes[0]:
+                       try:
+                               if len(cTypes) > 1 and cTypes[1].lower().find("utf-8") >= 0:
+                                       text = message.getMessage().decode("utf-8")
+                               else:
+                                       text = message.getMessage()
+                               self.msncon.gotMessage(self.remoteUser, text)
+                       except:
+                               self.msncon.gotMessage(self.remoteUser, "A message was lost.")
+                               raise
+               elif "text/x-clientcaps" == cTypes[0]:
+                       if message.hasHeader("JabberID"):
+                               jid = message.getHeader("JabberID")
+                               self.msncon.userMapping(message.userHandle, jid)
+               else:
+                       LogEvent(INFO, self.ident, "Discarding unknown message type.")
        
        def gotFileReceive(self, fileReceive):
                LogEvent(INFO, self.ident)
@@ -563,10 +737,10 @@ class OneSwitchboardSession(SwitchboardSessionBase, msn.SwitchboardClient):
        def sendTypingNotification(self):
                LogEvent(INFO, self.ident)
                if self.ready:
-                       self.sendTypingNotification()
+                       msn.SwitchboardClient.sendTypingNotification(self)
        
        CAPS = msn.MSNContact.MSNC1 | msn.MSNContact.MSNC2 | msn.MSNContact.MSNC3 | msn.MSNContact.MSNC4
-       def requestAvatar(self):
+       def sendAvatarRequest(self):
                if not self.ready: return
                msnContacts = self.msncon.getContacts()
                if not msnContacts: return
@@ -574,7 +748,15 @@ class OneSwitchboardSession(SwitchboardSessionBase, msn.SwitchboardClient):
                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.sendAvatarRequest(self.remoteUser, msnContact.msnobj)
+               return msn.SwitchboardClient.sendAvatarRequest(self, msnContact)
        
+       def sendFile(self, msnContact, filename, filesize):
+               def doSendFile(ignored=None):
+                       d.callback(msn.SwitchboardClient.sendFile(self, msnContact, filename, filesize))
+               d = Deferred()
+               if self.ready:
+                       reactor.callLater(0, doSendFile)
+               else:
+                       self.funcBuffer.append(doSendFile)
+               return d
        
-