return page.render(request)
oobSite = server.Site(FileTransfer())
-reactor.listenTCP(int(config.ftOOBPort), oobSite)
+# FIXME
+#reactor.listenTCP(int(config.ftOOBPort), oobSite)
import utils
from twisted.internet import task
from tlib.xmlw import Element
-from tlib import msn, msnp2p
+from tlib import msn
from debug import LogEvent, INFO, WARN, ERROR
import sha
import groupchat
def updateStats(statistics):
stats = statistics.stats
- stats["AvatarCount"] = msnp2p.MSNP2P_Avatar.TRANSFER_COUNT
- stats["FailedAvatarCount"] = msnp2p.MSNP2P_Avatar.ERROR_COUNT
+ # FIXME
+ #stats["AvatarCount"] = msnp2p.MSNP2P_Avatar.TRANSFER_COUNT
+ #stats["FailedAvatarCount"] = msnp2p.MSNP2P_Avatar.ERROR_COUNT
def msn2jid(msnid):
+++ /dev/null
-# 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 lang
-
-MAXMESSAGESIZE = 1400
-
-
-class MSNConnection:
- """ Manages all the Twisted factories, etc """
- def __init__(self, username, password):
- self.username = username
- self.password = password
- self.inited = False
- self.tries = 0
- self.initMe()
- LogEvent(INFO, self.session.jabberID)
-
- def initMe(self):
- if self.inited:
- MSNConnection.removeMe(self)
-
- self.switchboardSessions = {}
- dispatchFactory = DispatchFactory(self)
- reactor.connectTCP('messenger.hotmail.com', 1863, dispatchFactory)
- self.notificationFactory = msn.NotificationFactory()
- self.notificationFactory.userHandle = self.username
- self.notificationFactory.password = self.password
- self.notificationFactory.msncon = self
- self.notificationFactory.protocol = Notification
- self.notificationFactory.initialListVersion = self.initialListVersion
- self.notificationProtocol = None
-
- self.savedEvents = SavedEvents()
-
- self.inited = True
-
- LogEvent(INFO, self.session.jabberID)
-
- def removeMe(self):
- LogEvent(INFO, self.session.jabberID)
- if self.notificationProtocol:
- self.notificationProtocol.removeMe()
- if self.notificationFactory:
- self.notificationFactory.msncon = None
- self.notificationFactory = None
- self.notificationProtocol = None
- 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:
- self.switchboardSessions[contact].resource = self.highestResource()
-
- def getContacts(self):
- if self.notificationFactory:
- return self.notificationFactory.contacts
- else:
- return None
-
-
- def sendMessage(self, remoteUser, resource, text, noerror):
- 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:
- 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):
- self.switchboardSessions[remoteUser].sendTypingNofication()
-
- def notificationProtocolReady(self, notificationProtocol):
- self.notificationProtocol = notificationProtocol
- self.loggedIn()
- self.tries = 0
-
- def sendSavedEvents(self):
- # Hack for events sent before we're logged in
- self.savedEvents.send(self)
- self.savedEvents = None
-
- 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])
- 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:
- 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.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:
- reactor.callLater(2 ** self.tries, self.initMe)
- self.tries += 1
- else:
- self.connectionLost(self)
-
- def addContact(self, listType, 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):
- if self.notificationProtocol:
- return self.notificationProtocol.remContact(listType, str(userHandle))
- else:
- self.savedEvents.remContacts.append((listType, str(userHandle)))
-
-
-
- def initialEmailNotification(self, inboxunread, foldersunread):
- pass
-
- def realtimeEmailNotification(self, mailfrom, fromaddr, subject):
- pass
-
- def loggedIn(self):
- pass
-
- def loginFailure(self, message):
- pass
-
- def multipleLogin(self):
- pass
-
- 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
-
- def contactStatusChanged(self, remoteUser):
- pass
-
- def ourStatusChanged(self, statusCode):
- pass
-
- def ourNickChanged(self, nick):
- pass
-
- def ourPersonalChanged(self, personal):
- pass
-
- def userMapping(self, passport, jid):
- pass
-
- def gotContactTyping(self, remoteUser, resource):
- pass
-
- def serverGoingDown(self):
- pass
-
- def accountNotVerified(self):
- pass
-
- def userAddedMe(self, userHandle):
- pass
-
- def userRemovedMe(self, userHandle):
- pass
-
- def failedMessage(self, remoteUser, message):
- pass
-
- def connectionLost(self):
- pass
-
-
-
-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()
- from glue import LegacyGroupchat, msn2jid
- groupchat = LegacyGroupchat(session=switchboardSession.msncon.session, resource=None, existing=True, switchboardSession=gcsbs)
- gcsbs.groupchat = groupchat
- gcsbs.msncon = switchboardSession.msncon
- gcsbs.switchboard = switchboardSession.switchboard
- gcsbs.switchboard.switchboardSession = gcsbs
- gcsbs.ready = True
- gcsbs.userJoined(user1)
- gcsbs.userJoined(user2)
- groupchat.sendUserInvite(msn2jid(switchboardSession.remoteUser))
- switchboardSession.removeMe(False)
- LogEvent(INFO, gcsbs.msncon.session.jabberID)
- return gcsbs
-
-
-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 = msn.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
- 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:
- LogEvent(INFO, self.ident, "Requesting switchboard.")
- d = self.msncon.notificationProtocol.requestSwitchboardServer()
- d.addCallback(self.sbRequestAccepted)
- d.addErrback(self.removeMe)
-
- if self.msncon:
- LogEvent(INFO, self.ident, "Created groupchat for " + self.msncon.username)
-
- def removeMe(self):
- if self.removed:
- log.err("removeMe called more than once!")
- return
- self.removed = True
-
- LogEvent(INFO, self.ident)
- self.msncon = None
- if self.switchboard:
- self.switchboard.removeMe()
- self.switchboard = None
- self.groupchat = None
- self.ready = False
-
- def sbRequestAccepted(self, (host, port, key)):
- # Connect to the switchboard server
- LogEvent(INFO, self.ident)
- reactor.connectTCP(host, port, SwitchboardFactory(self, key))
-
- def sendMessage(self, message, noerror):
- if self.oneUserHasJoined:
- SwitchboardSessionBase.sendMessage(self, message, noerror)
- else:
- self.messageBuffer.append((message, noerror))
-
- def inviteUser(self, userHandle):
- userHandle = str(userHandle)
- if self.ready:
- LogEvent(INFO, self.ident)
- self.switchboard.inviteUser(userHandle)
- else:
- self.invitedUsers.append(userHandle)
-
- def gotMessage(self, message):
- self.groupchat.messageReceived(message.userHandle, message.getMessage())
-
- def flushBuffer(self):
- for m, noerror in self.messageBuffer[:]:
- self.messageBuffer.remove((m, noerror))
- self.sendMessage(m, noerror)
-
- for i in self.invitedUsers[:]:
- self.invitedUsers.remove(i)
- self.inviteUser(i)
-
- def userJoined(self, userHandle):
- LogEvent(INFO, self.ident)
- self.oneUserHasJoined = True
- self.flushBuffer()
- self.groupchat.contactJoined(userHandle)
-
- def userLeft(self, userHandle):
- LogEvent(INFO, self.ident)
- self.groupchat.contactLeft(userHandle)
-
-
-
-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)
-
- self.killTimer = reactor.callLater(30.0*60.0, self.removeMe)
-
- self.switchboard = None # The SwitchboardClient class
- 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:
- # Request a switchboard
- d = self.msncon.notificationProtocol.requestSwitchboardServer()
- d.addCallback(self.sbRequestAccepted)
- d.addErrback(self.removeMe)
- else:
- reactor.connectTCP(host, port, SwitchboardFactory(self, key, sessionID, reply))
-
- LogEvent(INFO, self.ident)
-
- def removeMe(self, sbflag=True):
- if self.removed:
- log.err("removeMe called more than once!")
- return
- self.removed = True
-
- LogEvent(INFO, self.ident)
- for message, noerror in self.messageBuffer:
- 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:
- self.switchboard.removeMe()
- self.switchboard = None
- self.ready = False
- if self.killTimer and not self.killTimer.called:
- self.killTimer.cancel()
- self.killTimer = None
-
- def resetTimer(self):
- # Sets a count down timer to kill this switchboard session in 30 minutes
- self.killTimer.cancel()
- self.killTimer = reactor.callLater(30.0*60.0, self.removeMe)
-
- def sbRequestAccepted(self, (host, port, key)):
- # Connect to the switchboard server
- reactor.connectTCP(host, port, SwitchboardFactory(self, key))
-
- def sendTypingNofication(self):
- if self.ready:
- self.switchboard.sendTypingNotification()
-
- def contactTyping(self):
- self.msncon.gotContactTyping(self.remoteUser, self.resource)
-
- def flushBuffer(self):
- for m, noerror in self.messageBuffer[:]:
- self.messageBuffer.remove((m, noerror))
- self.sendMessage(m, noerror)
-
- def gotMessage(self, message):
- 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:
- # 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:
- self.removeMe()
-
-
-
-class DispatchFactory(ClientFactory):
- def __init__(self, msncon):
- self.msncon = msncon
-
- def buildProtocol(self, addr):
- 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):
- msn.DispatchClient.__init__(self)
- self.msncon = msncon
- self.userHandle = self.msncon.username
-
- def __del__(self):
- self.factory = None
- self.msncon = None
-
- def gotNotificationReferral(self, host, port):
- self.transport.loseConnection()
- if self.msncon and self.msncon.session and self.msncon.session.alive:
- reactor.connectTCP(host, port, self.msncon.notificationFactory)
-
-
-
-class Notification(msn.NotificationClient):
- def __init__(self):
- self.removed = False
-
- msn.NotificationClient.__init__(self)
-
- def removeMe(self):
- if self.removed:
- log.err("removeMe called more than once!")
- return
- self.removed = True
-
- self.logOut()
- self.transport.loseConnection()
- if self.factory.msncon:
- self.factory.msncon.notificationProtocol = None
- self.factory.msncon = None
- self.factory = None
-
- 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:
- self.removeMe()
- return True
- return False
-
-
- def loginFailure(self, message):
- if self.badConditions(): return
- self.factory.msncon.loginFailure(message)
-
- def loggedIn(self, userHandle, verified):
- if self.badConditions(): return
-
- self.factory.msncon.notificationProtocolReady(self)
- if not verified:
- self.factory.msncon.accountNotVerified()
-
- msn.NotificationClient.loggedIn(self, userHandle, verified)
-
- 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
- def wait():
- 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
- 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.sendSavedEvents()
- self.setPrivacyMode(False)
-
- def gotSwitchboardInvitation(self, sessionID, host, port, key, remoteUser, screenName):
- 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):
- 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
- LogEvent(INFO, self.factory.msncon.session.jabberID)
-
- self.factory.msncon.contactStatusChanged(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
- LogEvent(INFO, self.factory.msncon.session.jabberID)
- msn.NotificationClient.contactOffline(self, userHandle)
-
- self.factory.msncon.contactStatusChanged(userHandle)
-
- 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, 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
- LogEvent(INFO, self.factory.msncon.session.jabberID)
- self.factory.msncon.multipleLogin()
-
-
-
-class SwitchboardFactory(ClientFactory):
- def __init__(self, switchboardSession, key, sessionID=None, reply=False):
- self.switchboardSession = switchboardSession
- self.key = key
- self.sessionID = sessionID
- self.reply = reply
-
- def buildProtocol(self, addr):
- p = Switchboard(self.switchboardSession)
- if p.badConditions(): return p
- p.key = self.key
- p.sessionID = self.sessionID
- p.reply = self.reply
- p.userHandle = self.switchboardSession.msncon.username
- p.factory = self
- return p
-
-class Switchboard(msn.SwitchboardClient):
- def __init__(self, switchboardSession):
- self.removed = False
-
- self.switchboardSession = switchboardSession
- self.chattingUsers = []
- self.callid = None
- 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:
- log.err("removeMe called more than once!")
- return
- self.removed = True
-
- self.transport.loseConnection()
- LogEvent(INFO, self.switchboardSession.ident)
- self.switchboardSession = None
- self.factory.switchboardSession = None
- self.factory = None
-
- if self.callid and not self.callid.called:
- self.callid.cancel() # Cancel the invite fail message
- self.callid = None
-
- 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:
- self.switchboardSession.removeMe()
- elif not self.removed:
- self.removeMe()
- return True
- return False
-
- def loggedIn(self):
- if self.badConditions(): return
- if (not self.reply) and isinstance(self.switchboardSession, SwitchboardSession):
- def failCB(arg=None):
- LogEvent(INFO, ident, "User has not joined after 30 seconds.")
- self.switchboardSession.removeMe()
- d = self.inviteUser(self.switchboardSession.remoteUser)
- d.addErrback(failCB)
- 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):
- self.switchboardSession.switchboardReady(self)
- for user in self.chattingUsers:
- self.switchboardSession.userJoined(user)
- 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
-
- def gotChattingUsers(self, users):
- for user in users:
- self.chattingUsers.append(user)
-
- def userJoined(self, userHandle, screenName):
- if self.badConditions(): return
- # FIXME - check this is correct
- if (not self.reply) and isinstance(self.switchboardSession, SwitchboardSession):
- self.readySwitchboardSession()
- LogEvent(INFO, self.switchboardSession.ident)
- self.switchboardSession.userJoined(userHandle)
-
- def userLeft(self, 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():
- LogEvent(WARN, self.switchboardSession.ident, "gotMessage() called too late. Dropped a message!")
- return
-
- LogEvent(INFO, self.switchboardSession.ident)
- cTypes = [s.lstrip() for s in message.getHeader("Content-Type").split(';')]
- 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"):
- jid = message.getHeader("JabberID")
- self.switchboardSession.msncon.userMapping(message.userHandle, jid)
- return
- LogEvent(INFO, self.switchboardSession.ident, "Discarding unknown message type.")
-
- def userTyping(self, message):
- 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):
- message = msn.MSNMessage()
- message.setHeader("Content-Type", "text/x-clientcaps")
- 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)
-
-
-from msn import *
-from msnft import *
+from msnw import MSNConnection
+import msn
PINGSPEED = 50.0
DEBUGALL = False
-LINEDEBUG = False
+LINEDEBUG = True
MESSAGEDEBUG = False
MSNP2PDEBUG = False
"""
log.msg('Error %s' % (errorCodes[errorCode]))
+
class DispatchClient(MSNEventBase):
"""
This class provides support for clients connecting to the dispatch server
@ivar userHandle: your user handle (passport) needed before connecting.
"""
- # eventually this may become an attribute of the
- # factory.
- userHandle = ""
-
def connectionMade(self):
MSNEventBase.connectionMade(self)
self.sendLine('VER %s %s' % (self._nextTransactionID(), MSN_PROTOCOL_VERSION))
self.transport.loseConnection()
raise MSNProtocolError, "Invalid version response"
id = self._nextTransactionID()
- self.sendLine("CVR %s %s %s" % (id, MSN_CVR_STR, self.userHandle))
+ self.sendLine("CVR %s %s %s" % (id, MSN_CVR_STR, self.factory.userHandle))
def handle_CVR(self, params):
- self.sendLine("USR %s TWN I %s" % (self._nextTransactionID(), self.userHandle))
+ self.sendLine("USR %s TWN I %s" % (self._nextTransactionID(), self.factory.userHandle))
def handle_XFR(self, params):
if len(params) < 4: raise MSNProtocolError, "Invalid number of parameters for XFR"
pass
+class DispatchFactory(ClientFactory):
+ """
+ This class keeps the state for the DispatchClient.
+
+ @ivar userHandle: the userHandle to request a notification
+ server for.
+ """
+ protocol = DispatchClient
+ userHandle = ""
+
+
+
class NotificationClient(MSNEventBase):
"""
This class provides support for clients connecting
self.handleAvatarHelper(msnContact, params[5])
else:
self.handleAvatarGoneHelper(msnContact)
- self.gotContactStatus(params[1], params[2], unquote(params[3]))
+ self.gotContactStatus(params[2], params[1], unquote(params[3]))
def handleAvatarGoneHelper(self, msnContact):
if msnContact.msnobj:
self.handleAvatarHelper(msnContact, params[4])
else:
self.handleAvatarGoneHelper(msnContact)
- self.contactStatusChanged(params[0], params[1], unquote(params[2]))
+ self.contactStatusChanged(params[1], params[0], unquote(params[2]))
def handle_FLN(self, params):
checkParamLen(len(params), 1, 'FLN')
"""
pass
- def gotContactStatus(self, statusCode, userHandle, screenName):
+ def gotContactStatus(self, userHandle, statusCode, screenName):
"""
Called when we receive a list of statuses upon login.
- @param statusCode: 3-letter status code
@param userHandle: the contact's user handle (passport)
+ @param statusCode: 3-letter status code
@param screenName: the contact's screen name
"""
pass
- def contactStatusChanged(self, statusCode, userHandle, screenName):
+ def contactStatusChanged(self, userHandle, statusCode, screenName):
"""
Called when we're notified that a contact's status has changed.
- @param statusCode: 3-letter status code
@param userHandle: the contact's user handle (passport)
+ @param statusCode: 3-letter status code
@param screenName: the contact's screen name
"""
pass
self.pingCheckTask.stop()
self.pingCheckTask = None
self.sendLine("OUT")
+ self.transport.loseConnection()
class NotificationFactory(ClientFactory):
"""
to a switchboard invitation
@ivar reply: set this to 1 in connectionMade or before to signifiy
that you are replying to a switchboard invitation.
- @ivar msnobj: the MSNObject for the user. So that the switchboard can distribute it.
+ @ivar msnobj: the MSNObject for the user's avatar. So that the
+ switchboard can distribute it to anyone who asks.
"""
key = 0
def _checkTyping(self, message, cTypes):
""" helper method for checkMessage """
if 'text/x-msmsgscontrol' in cTypes and message.hasHeader('TypingUser'):
- self.userTyping(message)
+ self.gotContactTyping(message)
return 1
def _checkFileInvitation(self, message, info):
"""
pass
- def userTyping(self, message):
+ def gotContactTyping(self, message):
"""
called when we receive the special type of message notifying
- us that a user is typing a message.
+ us that a contact is typing a message.
@param message: the associated MSNMessage object
"""
--- /dev/null
+# Copyright 2004-2005 James Bunton <james@delx.cjb.net>
+# Licensed for distribution under the GPL version 2, check COPYING for details
+
+# Twisted imports
+from twisted.internet import reactor
+from twisted.internet.defer import Deferred
+from twisted.internet.protocol import ClientFactory
+
+# System imports
+import math, base64, binascii, math
+
+# 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.
+You should not directly instantiate any objects of other classes.
+"""
+
+class MSNConnection:
+ """ Manages all the Twisted factories, etc """
+ def __init__(self, username, password, ident):
+ """ Connects to the MSN servers.
+ @param username: the MSN passport to connect with.
+ @param password: the password for this account.
+ @param ident: a unique identifier to use in logging.
+ """
+ self.username = username
+ self.password = password
+ self.ident = ident
+ self.timeout = None
+ self.connect()
+ LogEvent(INFO, self.ident)
+
+ def connect(self):
+ """ Automatically called by the constructor """
+ self.connectors = []
+ self.switchboardSessions = {}
+ self.savedEvents = SavedEvents() # Save any events that occur before connect
+ self._getNotificationReferral()
+
+ def _getNotificationReferral(self):
+ def timeout():
+ if not d.called: d.errback()
+ self.timeout = reactor.callLater(30, timeout)
+ dispatchFactory = msn.DispatchFactory()
+ dispatchFactory.userHandle = self.username
+ dispatchFactory.protocol = DispatchClient
+ d = Deferred()
+ dispatchFactory.d = d
+ d.addCallbacks(self._gotNotificationReferral, self.connectionFailed)
+ self.connectors.append(reactor.connectTCP("messenger.hotmail.com", 1863, dispatchFactory))
+ LogEvent(INFO, self.ident)
+
+ def _gotNotificationReferral(self, (host, port)):
+ self.timeout.cancel()
+ # Create the NotificationClient
+ self.notificationFactory = msn.NotificationFactory()
+ self.notificationFactory.userHandle = self.username
+ self.notificationFactory.password = self.password
+ self.notificationFactory.msncon = self
+ self.notificationFactory.protocol = NotificationClient
+ self.connectors.append(reactor.connectTCP(host, port, self.notificationFactory))
+ LogEvent(INFO, self.ident)
+
+ def _sendSavedEvents(self):
+ self.savedEvents.send(self)
+ self.savedEvents = None
+
+ def _notificationClientReady(self, notificationClient):
+ self.notificationClient = notificationClient
+
+ def _ensureSwitchboardSession(self, userHandle):
+ if not self.switchboardSessions.has_key(userHandle):
+ sb = OneSwitchboardSession(self, userHandle)
+ sb.connect()
+ self.switchboardSessions[userHandle] = sb
+
+
+
+ # API calls
+ def getContacts(self):
+ """ Gets the contact list.
+
+ @return an instance of MSNContactList (do not modify) if connected,
+ or None if not.
+ """
+ if self.notificationFactory:
+ return self.notificationFactory.contacts
+ else:
+ return None
+
+ def sendMessage(self, userHandle, text, noerror=False):
+ """
+ Sends a message to a contact. Can only be called after listSynchronized().
+
+ @param userHandle: the contact's MSN passport.
+ @param text: the text to send.
+ @param noerror: Set this to True if you don't want failed messages to bounce.
+ """
+ LogEvent(INFO, self.ident)
+ if self.notificationClient:
+ self._ensureSwitchboardSession(userHandle)
+ self.switchboardSessions[userHandle].sendMessage(text, noerror)
+ elif not noerror:
+ self.failedMessage(userHandle, text)
+
+ def requestAvatar(self, userHandle):
+ """
+ Requests the avatar of a contact.
+
+ @param userHandle: the contact to request an avatar from.
+ @return: a Deferred() if the avatar can be fetched at this time.
+ This will fire with an argument of a tuple with the PNG
+ image data as the only element.
+ Otherwise returns None
+ """
+
+ LogEvent(INFO, self.ident)
+ if not self.notificationClient: return
+ if GETALLAVATARS:
+ self._ensureSwitchboardSession(userHandle)
+ sb = self.switchboardSessions.get(userHandle)
+ if sb: return sb.sendAvatarRequest()
+
+ def sendFile(self, userHandle, filename, filesize):
+ """
+ Used to send a file to a contact.
+
+ @param username: the passport of the contact to send a file to.
+ @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,
+ 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()
+ when the file is done.
+ NOTE: You MUST write() exactly as much as you
+ declare in filesize.
+ """
+ raise NotImplementedError # May have to wait for the switchboardSession to exist
+
+ def sendTypingToContact(self, userHandle):
+ """
+ Sends typing notification to a contact.
+ @param userHandle: the contact to notify of our typing.
+ """
+
+ sb = self.switchboardSessions.get(userHandle)
+ if sb: return sb.sendTypingNotification()
+
+ def changeAvatar(self, imageData):
+ """
+ Changes the user's avatar.
+ @param imageData: the new PNG avatar image data.
+ """
+ if self.notificationClient:
+ LogEvent(INFO, self.ident)
+ self.notificationClient.changeAvatar(imageData, push=True)
+ else:
+ self.savedEvents.avatarImageData = imageData
+
+ def changeStatus(self, statusCode, screenName, personal):
+ """
+ Changes your status details. All details must be given with
+ each call. This can be called before connection if you wish.
+
+ @param statusCode: the user's new status (look in msn.statusCodes).
+ @param screenName: the user's new screenName (up to 127 characters).
+ @param personal: the user's new personal message.
+ """
+
+ if self.notificationClient:
+ count = 0
+ def cb():
+ if count == 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
+
+ def addContact(self, listType, userHandle):
+ """ See msn.NotificationClient.addContact """
+ if self.notificationClient:
+ return self.notificationClient.addContact(listType, str(userHandle))
+ else:
+ self.savedEvents.addContacts.append((listType, str(userHandle)))
+
+ def remContact(self, listType, userHandle):
+ """ See msn.NotificationClient.remContact """
+ if self.notificationClient:
+ return self.notificationClient.remContact(listType, str(userHandle))
+ else:
+ self.savedEvents.remContacts.append((listType, str(userHandle)))
+
+ def logOut(self):
+ """ Shuts down the whole connection. Don't try to call any
+ other methods after this one. """
+ if self.notificationClient:
+ self.notificationClient.logOut()
+ for c in self.connectors:
+ c.disconnect()
+ if self.notificationFactory:
+ self.notificationFactory.msncon = None
+ LogEvent(INFO, self.ident)
+
+
+ # Reimplement these!
+ def connectionFailed(self, reason=''):
+ """ Called when the connection to the server failed. """
+
+ def connectionLost(self, reason=''):
+ """ Called when we are disconnected. """
+
+ def multipleLogin(self):
+ """ Called when the server says there has been another login
+ for this account. """
+
+ def serverGoingDown(self):
+ """ Called when the server says that it will be going down. """
+
+ def accountNotVerified(self):
+ """ Called if this passport has not been verified. Certain
+ functions are not available. """
+
+ def userMapping(self, passport, jid):
+ """ Called when it is brought to our attention that one of the
+ MSN contacts has a Jabber ID. You should communicate with Jabber. """
+
+ def loggedIn(self):
+ """ Called when we have authenticated, but before we receive
+ the contact list. """
+
+ def listSynchronized(self):
+ """ Called when we have received the contact list. All methods
+ in this class are now valid. """
+
+ def ourStatusChanged(self, statusCode, screenName, personal):
+ """ Called when the user's status has changed. """
+
+ def gotMessage(self, userHandle, text):
+ """ Called when a contact sends us a message """
+
+ 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(). """
+
+ def contactStatusChanged(self, userHandle, statusCode, screenName):
+ """ 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. """
+
+ def contactAddedMe(self, userHandle):
+ """ Called when a contact adds the user to their list. """
+
+ def contactRemovedMe(self, userHandle):
+ """ Called when a contact removes the user from their list. """
+
+
+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.notificationClient.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)
+
+
+
+class DispatchClient(msn.DispatchClient):
+ def gotNotificationReferral(self, host, port):
+ if self.factory.d.called: return # Too slow! We've already timed out
+ self.factory.d.callback((host, port))
+
+
+class NotificationClient(msn.NotificationClient):
+ def loginFailure(self, message):
+ self.factory.msncon.connectionFailed(message)
+
+ def loggedIn(self, userHandle, verified):
+ LogEvent(INFO, self.factory.msncon.ident)
+ msn.NotificationClient.loggedIn(self, userHandle, verified)
+ self.factory.msncon._notificationClientReady(self)
+ if not verified:
+ self.factory.msncon.accountNotVerified()
+
+ def logOut(self):
+ msn.NotificationClient.logOut(self)
+
+ def connectionLost(self, reason):
+ if not self.factory.msncon: return # If we called logOut
+ def wait():
+ LogEvent(INFO, self.factory.msncon.ident)
+ msn.NotificationClient.connectionLost(self, reason)
+ self.factory.msncon.connectionLost(reason)
+ # Make sure this event is handled after any others
+ reactor.callLater(0, wait)
+
+ def gotMSNAlert(self, body, action, subscr):
+ LogEvent(INFO, self.factory.msncon.ident)
+ self.factory.msncon.gotMSNAlert(body, action, subscr)
+
+ def gotInitialEmailNotification(self, inboxunread, foldersunread):
+ LogEvent(INFO, self.factory.msncon.ident)
+ self.factory.msncon.gotInitialEmailNotification(inboxunread, foldersunread)
+
+ def gotRealtimeEmailNotification(self, mailfrom, fromaddr, subject):
+ LogEvent(INFO, self.factory.msncon.ident)
+ self.factory.msncon.gotRealtimeEmailNotification(mailfrom, fromaddr, subject)
+
+ def userAddedMe(self, userGuid, userHandle, screenName):
+ LogEvent(INFO, self.factory.msncon.ident)
+ self.factory.msncon.contactAddedMe(userHandle)
+
+ def userRemovedMe(self, userHandle):
+ LogEvent(INFO, self.factory.msncon.ident)
+ self.factory.msncon.contactRemovedMe(userHandle)
+
+ def listSynchronized(self, *args):
+ LogEvent(INFO, self.factory.msncon.ident)
+ self.factory.msncon._sendSavedEvents()
+ self.factory.msncon.listSynchronized()
+
+ def contactAvatarChanged(self, userHandle, hash):
+ LogEvent(INFO, self.factory.msncon.ident)
+ self.factory.msncon.contactAvatarChanged(userHandle, hash)
+
+ def gotContactStatus(self, userHandle, statusCode, screenName):
+ LogEvent(INFO, self.factory.msncon.ident)
+ self.factory.msncon.contactStatusChanged(userHandle, statusCode, screenName)
+
+ def contactStatusChanged(self, userHandle, statusCode, screenName):
+ LogEvent(INFO, self.factory.msncon.ident)
+ self.factory.msncon.contactStatusChanged(userHandle, statusCode, screenName)
+
+ def contactOffline(self, userHandle):
+ LogEvent(INFO, self.factory.msncon.ident)
+ self.factory.msncon.contactStatusChanged(userHandle, msn.STATUS_OFFLINE, "")
+
+ def gotSwitchboardInvitation(self, sessionID, host, port, key, userHandle, screenName):
+ LogEvent(INFO, self.factory.msncon.ident)
+ sb = SwitchboardSession(self.factory.msncon, userHandle, 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)
+ self.factory.msncon.multipleLogin()
+
+ def serverGoingDown(self):
+ LogEvent(INFO, self.factory.msncon.ident)
+ self.factory.msncon.serverGoingDown()
+
+
+
+def switchToGroupchat(*args):
+ raise NotImplementedError
+
+
+class SwitchboardSessionBase:
+ def __init__(self, msncon):
+ self.msncon = msncon
+ self.ident = (msncon.ident, "INVALID!!")
+ self.messageBuffer = []
+ self.ready = False
+ self.killTimer = reactor.callLater(SWITCHBOARDTIMEOUT, self.transport.loseConnection)
+
+ def __del__(self):
+ LogEvent(INFO, self.ident)
+ del self.msncon
+ self.transport.disconnect()
+
+ for message, noerror in self.messageBuffer:
+ if not noerror:
+ self.msncon.failedMessage(self.remoteUser, message)
+
+ if not self.killTimer.called:
+ self.killTimer.cancel()
+ del self.killTimer
+
+ def connect(self):
+ LogEvent(INFO, self.ident)
+ def sbRequestAccepted((host, port, key)):
+ LogEvent(INFO, self.ident)
+ self.key = key
+ factory = ClientFactory()
+ factory.buildProtocol = lambda: self
+ reactor.connectTCP(host, port, factory)
+ def sbRequestFailed(ignored=None):
+ LogEvent(INFO, self.ident)
+ del self.msncon.switchboardSessions[self.remoteUser]
+ d = self.msncon.notificationClient.requestSwitchboardServer()
+ d.addCallbacks(sbRequestAccepted, sbRequestFailed)
+
+ def flushBuffer(self):
+ for message, noerror in self.messageBuffer[:]:
+ self.messageBuffer.remove((m, noerror))
+ self.sendMessage(m, noerror)
+
+ def resetTimer(self):
+ self.killTimer.cancel()
+ self.killTimer = reactor.callLater(SWITCHBOARDTIMEOUT, self.transport.loseConnection)
+
+ def failedMessage(self, ignored):
+ raise NotImplementedError
+
+ def sendMessage(text, noerror):
+ if not self.ready:
+ self.messageBuffer.append((message, noerror))
+ else:
+ LogEvent(INFO, self.ident)
+ def failedMessage(ignored):
+ if not noerror:
+ self.failedMessage(text)
+
+ if len(message) < MAXMESSAGESIZE:
+ message = msn.MSNMessage(message=str(text.replace("\n", "\r\n").encode("utf-8")))
+ message.setHeader("Content-Type", "text/plain; charset=UTF-8")
+ message.ack = msn.MSNMessage.MESSAGE_NACK
+
+ d = msn.SwitchboardClient.sendMessage(self, message)
+ if not noerror:
+ d.addCallback(failedMessage)
+
+ else:
+ chunks = int(math.ceil(len(text) / float(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")))
+ message.ack = msn.MSNMessage.MESSAGE_NACK
+ if chunk == 0:
+ message.setHeader("Content-Type", "text/plain; charset=UTF-8")
+ message.setHeader("Chunks", str(chunks))
+ else:
+ message.setHeader("Chunk", str(chunk))
+
+ d = msn.SwitchboardClient.sendMessage(self, message)
+ if not noerror:
+ d.addCallback(failedMessage)
+
+ chunk += 1
+
+ self.resetTimer()
+
+ def gotFileReceive(self, fileReceive):
+ LogEvent(INFO, self.ident)
+ self.msncon.gotFileReceive(fileReceive)
+
+
+
+class OneSwitchboardSession(SwitchboardSessionBase, msn.SwitchboardClient):
+ def __init__(self, msncon, remoteUser):
+ SwitchboardSessionBase.__init__(self, msncon)
+ msn.SwitchboardClient.__init__(self)
+ self.remoteUser = remoteUser
+ self.ident = (self.msncon, self.remoteUser)
+ self.chattingUsers = []
+
+ def _ready(self):
+ LogEvent(INFO, self.ident)
+ self.ready = True
+ for user in self.chattingUsers:
+ self.userJoined(user)
+ if not self.timeout.called:
+ self.timeout.cancel()
+ del self.timeout
+ self.flushBuffer()
+
+ def failedMessage(self, text):
+ self.msncon.failedMessage(self.remoteUser, text)
+
+ # Callbacks
+ def loggedIn(self):
+ LogEvent(INFO, self.ident)
+ if not self.reply:
+ def failCB(arg=None):
+ LogEvent(INFO, ident, "User has not joined after 30 seconds.")
+ del self.msncon.switchboardSessions[self.remoteUser]
+ d = self.inviteUser(self.remoteUser)
+ d.addErrback(failCB)
+ self.timeout = reactor.callLater(30.0, failCB)
+ else:
+ self._ready()
+
+ def gotChattingUsers(self, users):
+ for userHandle in users.keys():
+ self.chattingUsers.append(userHandle)
+
+ def userJoined(self, userHandle, screenName):
+ LogEvent(INFO, self.ident)
+ if not self.reply:
+ self._ready()
+ if userHandle != self.remoteUser:
+ # Another user has joined, so we now have three participants.
+ switchToGroupchat(self, self.remoteUser, userHandle)
+ else:
+ self.requestAvatar()
+
+ def userLeft(self, userHandle):
+ def wait():
+ if userHandle == 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())
+
+ def gotFileReceive(self, fileReceive):
+ LogEvent(INFO, self.ident)
+ self.msncon.gotFileReceive(fileReceive)
+
+ def gotContactTyping(self, message):
+ LogEvent(INFO, self.ident)
+ self.msncon.gotContactTyping(message.userHandle)
+
+ def sendTypingNotification(self):
+ LogEvent(INFO, self.ident)
+ if self.ready:
+ self.sendTypingNotification()
+
+ CAPS = msn.MSNContact.MSNC1 | msn.MSNContact.MSNC2 | msn.MSNContact.MSNC3 | msn.MSNContact.MSNC4
+ def requestAvatar(self):
+ if not self.ready: 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.sendAvatarRequest(self.remoteUser, msnContact.msnobj)
+
+
+
def gotProfile(self, message):
self.state = 'PROFILE'
- def gotContactStatus(self, code, userHandle, screenName):
+ def gotContactStatus(self, userHandle, code, screenName):
if code == msn.STATUS_AWAY and userHandle == "foo@bar.com" and screenName == "Test Screen Name":
c = self.factory.contacts.getContact(userHandle)
if c.caps & msn.MSNContact.MSNC1 and c.msnobj:
self.state = 'INITSTATUS'
- def contactStatusChanged(self, code, userHandle, screenName):
+ def contactStatusChanged(self, userHandle, code, screenName):
if code == msn.STATUS_LUNCH and userHandle == "foo@bar.com" and screenName == "Test Name":
self.state = 'NEWSTATUS'
if userHandle == "friend@hotmail.com":
self.state = 'USERLEFT'
- def userTyping(self, message):
+ def gotContactTyping(self, message):
if message.userHandle == 'foo@bar.com':
self.state = 'USERTYPING'
--- /dev/null
+# Copyright 2005 James Bunton <james@delx.cjb.net>
+# Licensed for distribution under the GPL version 2, check COPYING for details
+
+"""
+Test cases for msnw (MSN Wrapper)
+"""
+
+# Twisted imports
+from twisted.internet.defer import Deferred
+from twisted.internet import reactor, error
+from twisted.trial import unittest
+from twisted.python import log
+
+# System imports
+import sys
+
+# Local imports
+import msnw
+
+
+# Settings
+TIMEOUT = 30.0 # Connection timeout in seconds
+LOGGING = True
+USER1 = "messengertest1@hotmail.com"
+PASS1 = "hellohello"
+USER2 = "messengertest2@hotmail.com"
+PASS2 = "hellohello"
+
+
+
+if LOGGING:
+ log.startLogging(sys.stdout)
+
+
+checkCount = 0 # Uck!
+def clearAccount(msncon):
+ """ Clears the contact list of the given MSNConnection. Returns a
+ Deferred which fires when the task is complete.
+ """
+ d = Deferred()
+ count = 0
+ global checkCount
+ checkCount = 0
+ def cb(ignored=None):
+ global checkCount
+ checkCount += 1
+ if checkCount == count:
+ d.callback(None)
+
+ for msnContact in msncon.getContacts().contacts.values():
+ for list in [msnw.FORWARD_LIST, msnw.BLOCK_LIST, msnw.ALLOW_LIST, msnw.PENDING_LIST]:
+ if msnContact.lists & list:
+ msncon.remContact(list, msnContact.userHandle).addCallback(cb)
+ count += 1
+
+ if count == 0:
+ reactor.callLater(0, d.callback, None)
+ return d
+
+
+####################
+# Basic connection #
+####################
+
+class MSNConnection(msnw.MSNConnection):
+ def __init__(self, username, password, ident, testCase):
+ msnw.MSNConnection.__init__(self, username, password, ident)
+ self.testCase = testCase
+
+ def listSynchronized(self):
+ # Now we're fully connected
+ self.testCase.done = "SYNCED"
+
+ def gotMessage(self, userHandle, text):
+ self.testCase.done = "GOTMESSAGE"
+ self.message = (userHandle, text)
+
+ def contactAddedMe(self, userHandle):
+ print "CONTACT ADDED ME"
+ self.contactAdded = userHandle
+
+
+class BasicConnection(unittest.TestCase):
+ def failed(self, why):
+ self.done = True
+
+ def testConnect(self):
+ self.done = False
+ self.timeout = reactor.callLater(TIMEOUT, self.failed)
+ self.user = MSNConnection(USER1, PASS1, "user", self)
+ while not self.done:
+ reactor.iterate(0.1)
+ self.failUnless((self.done == "SYNCED"), "Failed to connect to MSN servers.")
+ self.user.logOut()
+ reactor.iterate(0.1)
+ try:
+ self.timeout.cancel()
+ except (error.AlreadyCancelled, error.AlreadyCalled):
+ pass
+
+class BasicTests(unittest.TestCase):
+ def setUp(self):
+ self.failure = None
+ self.timeout = None
+ self.done = False
+
+ # Login both accounts
+ self.user1 = MSNConnection(USER1, PASS1, "user1", self)
+ self.loop("Logging in.", cond="SYNCED")
+ self.user2 = MSNConnection(USER2, PASS2, "user2", self)
+ self.loop("Logging in.", cond="SYNCED")
+
+ # Purge both contact lists
+ clearAccount(self.user1).addCallback(self.cb)
+ self.loop("Purging user1 contact list.")
+ clearAccount(self.user2).addCallback(self.cb)
+ self.loop("Purging user2 contact list.")
+
+ # Adding users to each other's lists
+ self.user1.addContact(msnw.FORWARD_LIST, USER2).addCallback(self.cb)
+ self.loop("Adding user2 to user1's forward list.")
+ self.user1.addContact(msnw.ALLOW_LIST, USER2).addCallback(self.cb)
+ self.loop("Adding user2 to user1's allow list.")
+ self.user2.addContact(msnw.FORWARD_LIST, USER1).addCallback(self.cb)
+ self.loop("Adding user1 to user2's forward list.")
+ self.user2.addContact(msnw.ALLOW_LIST, USER1).addCallback(self.cb)
+ self.loop("Adding user1 to user2's allow list.")
+
+ # Check the contacts have seen each other
+ self.loop("Waiting...")
+ self.failUnless((self.user1.contactAdded == USER2 and self.user2.contactAdded == USER1), "Contacts can't see each other.")
+
+
+ def tearDown(self):
+ self.user1.logOut()
+ self.user2.logOut()
+ reactor.iterate(0.1)
+
+ def cb(self, ignored=None):
+ self.done = True
+
+ def loop(self, failMsg, cond=None):
+ # Loops with a timeout
+ self.done = False
+ self.timeout = reactor.callLater(TIMEOUT, self.failed, "Timeout: " + failMsg)
+ while not self.done:
+ reactor.iterate(0.1)
+ try:
+ self.timeout.cancel()
+ except (error.AlreadyCancelled, error.AlreadyCalled):
+ pass
+ if self.failure:
+ self.fail(self.failure)
+ if cond:
+ self.failUnless((self.done == cond), "Failed: " + failMsg)
+
+ def failed(self, why):
+ self.failure = why
+ self.done = True
+
+ # The tests!
+ def testMessageExchange(self):
+ self.user1.sendMessage(USER2, "Hi user2")
+ self.loop("Timeout exchanging message 1.", cond="GOTMESSAGE")
+ self.failUnless((self.user1.message == (USER2, "Hi user1")), "Failed to transfer message 1.")
+
+