X-Git-Url: https://code.delx.au/pymsnt/blobdiff_plain/c82ae883c8c1a236e80013c734fb56725b3c535c..HEAD:/src/legacy/glue.py diff --git a/src/legacy/glue.py b/src/legacy/glue.py index 1747492..cc3b30c 100644 --- a/src/legacy/glue.py +++ b/src/legacy/glue.py @@ -1,29 +1,44 @@ -# Copyright 2004 James Bunton +# Copyright 2004-2005 James Bunton # Licensed for distribution under the GPL version 2, check COPYING for details -import utils -from twisted.internet import task -if(utils.checkTwisted()): - from twisted.xish.domish import Element -else: - from tlib.domish import Element -from tlib import msn +import os.path +from twisted.internet import task, error +from twisted.words.xish.domish import Element + +from debug import LogEvent, INFO, WARN, ERROR +from legacy import msn +import disco import groupchat -import msnw +import ft +import avatar import config -import debug import lang -name = "MSN Transport" # The name of the transport -version = "0.9.5" # The transport version -mangle = True # XDB '@' -> '%' mangling -id = "msn" # The transport identifier +url = "http://delx.net.au/projects/pymsnt" +version = "0.11.3" # The transport version +mangle = True # XDB '@' -> '%' mangling +id = "msn" # The transport identifier + + +# Load the default avatars +f = open(os.path.join("data", "defaultJabberAvatar.png"), "rb") +defaultJabberAvatarData = f.read() +f.close() +f = open(os.path.join("data", "defaultMSNAvatar.png"), "rb") +defaultAvatarData = f.read() +f.close() +defaultAvatar = avatar.AvatarCache().setAvatar(defaultAvatarData) +def reloadConfig(): + msn.MSNConnection.GETALLAVATARS = config.getAllAvatars + msn.MSNConnection.BINDADDRESS = config.host + msn.setDebug(config._debugLevel >= 4) + def isGroupJID(jid): """ Returns True if the JID passed is a valid groupchat JID (for MSN, does not contain '%') """ return (jid.find('%') == -1) @@ -34,7 +49,7 @@ def isGroupJID(jid): namespace = "jabber:iq:register" -def formRegEntry(username, password, nickname): +def formRegEntry(username, password): """ Returns a domish.Element representation of the data passed. This element will be written to the XDB spool file """ reginfo = Element((None, "query")) reginfo.attributes["xmlns"] = "jabber:iq:register" @@ -45,9 +60,6 @@ def formRegEntry(username, password, nickname): passEl = reginfo.addElement("password") passEl.addContent(password) - nickEl = reginfo.addElement("nick") - if(nickname): nickEl.addContent(nickname) - return reginfo @@ -55,123 +67,202 @@ def formRegEntry(username, password, nickname): def getAttributes(base): """ This function should, given a spool domish.Element, pull the username, password, - and nickname out of it and return them """ + and out of it and return them """ username = "" password = "" - nickname = "" for child in base.elements(): try: - if(child.name == "username"): + if child.name == "username": username = child.__str__() - elif(child.name == "password"): + elif child.name == "password": password = child.__str__() - elif(child.name == "nick"): - nickname = child.__str__() except AttributeError: continue - return username, password, nickname + return username, password +def startStats(statistics): + stats = statistics.stats + stats["MessageCount"] = 0 + stats["FailedMessageCount"] = 0 + stats["AvatarCount"] = 0 + stats["FailedAvatarCount"] = 0 +def updateStats(statistics): + stats = statistics.stats + # FIXME + #stats["AvatarCount"] = msnp2p.MSNP2P_Avatar.TRANSFER_COUNT + #stats["FailedAvatarCount"] = msnp2p.MSNP2P_Avatar.ERROR_COUNT -def msn2jid(msnid): +msn2jid_cache = {} +def msn2jid(msnid, withResource): """ Converts a MSN passport into a JID representation to be used with the transport """ - return msnid.replace('@', '%') + "@" + config.jid - -translateAccount = msn2jid # Marks this as the function to be used in jabber:iq:gateway (Service ID Translation) - + global msn2jid_cache + global jid2msn_cache + + if msn2jid_cache.has_key(msnid): + jid = msn2jid_cache[msnid] + if withResource: + jid += "/msn" + return jid + else: + if msnid.startswith("tel:+"): + msnid = msnid.replace("tel:+", "") + "%tel" + jid = msnid.replace('@', '%') + "@" + config.jid + msn2jid_cache[msnid] = jid + jid2msn_cache[jid] = msnid + return msn2jid(msnid, withResource) + +# Marks this as the function to be used in jabber:iq:gateway (Service ID Translation) +def translateAccount(msnid): + return msn2jid(msnid, False) + +jid2msn_cache = {} def jid2msn(jid): """ Converts a JID representation of a MSN passport into the original MSN passport """ - return unicode(jid[:jid.find('@')].replace('%', '@')) + global jid2msn_cache + global msn2jid_cache + + if jid2msn_cache.has_key(jid): + msnid = jid2msn_cache[jid] + return msnid + else: + if jid.find("%tel@") > 0: + jid = "tel:+" + jid.replace("%tel@", "@") + msnid = unicode(jid[:jid.find('@')].replace('%', '@')).split("/")[0] + jid2msn_cache[jid] = msnid + msn2jid_cache[msnid] = jid + return msnid def presence2state(show, ptype): """ Converts a Jabber presence into an MSN status code """ - if(ptype == "unavailable"): + if ptype == "unavailable": return msn.STATUS_OFFLINE - elif(show in [None, "online", "chat"]): + elif not show or show == "online" or show == "chat": return msn.STATUS_ONLINE - elif(show in ["dnd"]): + elif show == "dnd": return msn.STATUS_BUSY - elif(show in ["away", "xa"]): + elif show == "away" or show == "xa": return msn.STATUS_AWAY + return msn.STATUS_ONLINE def state2presence(state): """ Converts a MSN status code into a Jabber presence """ - if(state == msn.STATUS_ONLINE): + if state == msn.STATUS_ONLINE: return (None, None) - elif(state == msn.STATUS_BUSY): + elif state == msn.STATUS_BUSY: return ("dnd", None) - elif(state == msn.STATUS_AWAY): + elif state == msn.STATUS_AWAY: return ("away", None) - elif(state == msn.STATUS_IDLE): + elif state == msn.STATUS_IDLE: return ("away", None) - elif(state == msn.STATUS_BRB): + elif state == msn.STATUS_BRB: return ("away", None) - elif(state == msn.STATUS_PHONE): + elif state == msn.STATUS_PHONE: return ("dnd", None) - elif(state == msn.STATUS_LUNCH): + elif state == msn.STATUS_LUNCH: return ("away", None) else: return (None, "unavailable") +def getGroupNames(msnContact, msnContactList): + """ Gets a list of groups that this contact is in """ + groups = [] + for groupGUID in msnContact.groups: + try: + groups.append(msnContactList.groups[groupGUID]) + except KeyError: + pass + return groups + +def msnlist2jabsub(lists): + """ Converts MSN contact lists ORed together into the corresponding Jabber subscription state """ + if lists & msn.FORWARD_LIST and lists & msn.REVERSE_LIST: + return "both" + elif lists & msn.REVERSE_LIST: + return "from" + elif lists & msn.FORWARD_LIST: + return "to" + else: + return "none" + + +def jabsub2msnlist(sub): + """ Converts a Jabber subscription state into the corresponding MSN contact lists ORed together """ + if sub == "to": + return msn.FORWARD_LIST + elif sub == "from": + return msn.REVERSE_LIST + elif sub == "both": + return (msn.FORWARD_LIST | msn.REVERSE_LIST) + else: + return 0 + + + # This class handles groupchats with the legacy protocol class LegacyGroupchat(groupchat.BaseGroupchat): - def __init__(self, session, resource, ID=None, existing=False, switchboardSession=None): + def __init__(self, session, resource=None, ID=None, switchboardSession=None): """ Possible entry points for groupchat - User starts an empty switchboard session by sending presence to a blank room - An existing switchboard session is joined by another MSN user - User invited to an existing switchboard session with more than one user """ groupchat.BaseGroupchat.__init__(self, session, resource, ID) - if(not existing): - self.switchboardSession = msnw.GroupchatSwitchboardSession(self, makeSwitchboard=True) - else: + if switchboardSession: self.switchboardSession = switchboardSession + else: + self.switchboardSession = msn.MultiSwitchboardSession(self.session.legacycon) + self.switchboardSession.groupchat = self - assert(self.switchboardSession != None) - - debug.log("LegacyGroupchat: \"%s\" created" % (self.roomJID())) + LogEvent(INFO, self.roomJID()) def removeMe(self): - self.switchboardSession.removeMe() - self.switchboardSession = None + if self.switchboardSession.transport: + self.switchboardSession.transport.loseConnection() + self.switchboardSession.groupchat = None + del self.switchboardSession groupchat.BaseGroupchat.removeMe(self) - debug.log("LegacyGroupchat: \"%s\" destroyed" % (self.roomJID())) - utils.mutilateMe(self) + LogEvent(INFO, self.roomJID()) def sendLegacyMessage(self, message, noerror): - debug.log("LegacyGroupchat: \"%s\" sendLegacyMessage(\"%s\")" % (self.roomJID(), message)) - self.switchboardSession.sendMessage(message, noerror) + LogEvent(INFO, self.roomJID()) + self.switchboardSession.sendMessage(message.replace("\n", "\r\n"), noerror) def sendContactInvite(self, contactJID): - debug.log("LegacyGroupchat: \"%s\" sendContactInvite(\"%s\")" % (self.roomJID(), contactJID)) + LogEvent(INFO, self.roomJID()) userHandle = jid2msn(contactJID) self.switchboardSession.inviteUser(userHandle) + + def gotMessage(self, userHandle, text): + LogEvent(INFO, self.roomJID()) + self.messageReceived(userHandle, text) # This class handles most interaction with the legacy protocol -class LegacyConnection(msnw.MSNConnection): +class LegacyConnection(msn.MSNConnection): """ A glue class that connects to the legacy network """ def __init__(self, username, password, session): + self.jabberID = session.jabberID + self.session = session self.listSynced = False self.initialListVersion = 0 - # Get the latest listVersion to pass to MSNConnection - result = self.session.pytrans.xdb.request(self.session.jabberID, "msn:listVersion") - if(result): - self.initialListVersion = int(str(result)) + self.remoteShow = "" + self.remoteStatus = "" + self.remoteNick = "" # Init the MSN bits - msnw.MSNConnection.__init__(self, username, password) + msn.MSNConnection.__init__(self, username, password, self.jabberID) # User typing notification stuff self.userTyping = dict() # Indexed by contact MSN ID, stores whether the user is typing to this contact @@ -181,235 +272,419 @@ class LegacyConnection(msnw.MSNConnection): self.userTypingSend = task.LoopingCall(self.sendTypingNotifications) self.userTypingSend.start(5.0) - import subscription # Is in here to prevent an ImportError loop - self.subscriptions = subscription.SubscriptionManager(self.session) + self.legacyList = LegacyList(self.session) - debug.log("LegacyConnection: \"%s\" - created" % (self.session.jabberID)) + LogEvent(INFO, self.jabberID) def removeMe(self): - debug.log("LegacyConnection: \"%s\" - being deleted" % (self.session.jabberID)) + LogEvent(INFO, self.jabberID) self.userTypingSend.stop() - if(self.getContacts()): - for userHandle in self.getContacts().getContacts(): - msnContact = self.getContacts().getContact(userHandle) - if(msnContact.status and msnContact.status != msn.STATUS_OFFLINE): - msnContact.status = msn.STATUS_OFFLINE - self.sendMSNContactPresence(msnContact) - -# msnw.MSNConnection.changeStatus(self, msn.STATUS_OFFLINE, self.session.nickname) # Change our nickname on the MSN network (stops users from appearing offline with a nickname "james - Online") - - # Save to XDB the current list version (for fast logins) - if(self.notificationFactory and self.notificationFactory.contacts): - listVersion = self.notificationFactory.contacts.version - el = Element((None, "query")) - el.addContent(str(listVersion)) - self.session.pytrans.xdb.set(self.session.jabberID, "msn:listVersion", el) - - msnw.MSNConnection.removeMe(self) - self.subscriptions.removeMe() - self.subscriptions = None + self.legacyList.removeMe() + self.legacyList = None self.session = None - - utils.mutilateMe(self) + self.logOut() - def jidRes(self, resource): - to = self.session.jabberID - if(resource): - to += "/" + resource - return to + # Implemented from baseproto + def sendShowStatus(self, jid=None): + if not self.session: return + source = config.jid + if not jid: + jid = self.jabberID + self.session.sendPresence(to=jid, fro=source, show=self.remoteShow, status=self.remoteStatus, nickname=self.remoteNick) + def resourceOffline(self, resource): + pass + def highestResource(self): """ Returns highest priority resource """ return self.session.highestResource() def sendMessage(self, dest, resource, body, noerror): dest = jid2msn(dest) - if(self.userTyping.has_key(dest)): + if self.userTyping.has_key(dest): del self.userTyping[dest] - msnw.MSNConnection.sendMessage(self, dest, resource, body, noerror) + try: + msn.MSNConnection.sendMessage(self, dest, body, noerror) + self.session.pytrans.statistics.stats["MessageCount"] += 1 + except: + self.failedMessage(dest, body) + raise - def buildFriendly(self, status): - """ Constructs a friendly name from the user's registered nick, and their status message """ - - if(not config.fancyFriendly): - if(self.session.nickname and len(self.session.nickname) > 0): - return self.session.nickname - else: - return self.session.jabberID[:self.session.jabberID.find('@')] - - if(self.session.nickname and len(self.session.nickname) > 0): - friendly = self.session.nickname - else: - friendly = self.session.jabberID[:self.session.jabberID.find('@')] - if(status and len(status) > 0): - friendly += " - " - friendly += status - if(len(friendly) > 127): - friendly = friendly[:124] + "..." - debug.log("LegacyConnection: buildFriendly(%s) returning \"%s\"" % (self.session.jabberID, friendly)) - return friendly - - def msnAlert(self, text, actionurl, subscrurl): - el = Element((None, "message")) - el.attributes["to"] = self.session.jabberID - el.attributes["from"] = config.jid - el.attributes["type"] = "headline" - body = el.addElement("body") - body.addContent(text) - - x = el.addElement("x") - x.attributes["xmlns"] = "jabber:x:oob" - x.addElement("desc").addContent("More information on this notice.") - x.addElement("url").addContent(actionurl) - - x = el.addElement("x") - x.attributes["xmlns"] = "jabber:x:oob" - x.addElement("desc").addContent("Manage subscriptions to alerts.") - x.addElement("url").addContent(subscrurl) + def sendFile(self, dest, ftSend): + dest = jid2msn(dest) + def continueSendFile1((msnFileSend, d)): + def continueSendFile2((success, )): + if success: + ftSend.accept(msnFileSend) + else: + sendFileFail() + d.addCallbacks(continueSendFile2, sendFileFail) + + def sendFileFail(): + ftSend.reject() - self.session.pytrans.send(el) + d = msn.MSNConnection.sendFile(self, dest, ftSend.filename, ftSend.filesize) + d.addCallbacks(continueSendFile1, sendFileFail) - def setStatus(self, show, status): + def setStatus(self, nickname, show, status): statusCode = presence2state(show, None) - msnw.MSNConnection.changeStatus(self, statusCode, self.buildFriendly(status)) + msn.MSNConnection.changeStatus(self, statusCode, nickname, status) - def newResourceOnline(self, resource): - self.sendLists(resource) - - def jabberSubscriptionReceived(self, source, subtype): - self.subscriptions.jabberSubscriptionReceived(source, subtype) + def updateAvatar(self, av=None): + global defaultJabberAvatarData + + if av: + msn.MSNConnection.changeAvatar(self, av.getImageData) + else: + msn.MSNConnection.changeAvatar(self, lambda: defaultJabberAvatarData) def sendTypingNotifications(self): + if not self.session: return + # Send any typing notification messages to the user's contacts for contact in self.userTyping.keys(): - if(self.userTyping[contact]): + if self.userTyping[contact]: self.sendTypingToContact(contact) # Send any typing notification messages from contacts to the user - for contact, resource in self.contactTyping.keys(): - self.contactTyping[(contact, resource)] += 1 - if(self.contactTyping[(contact, resource)] >= 3): - self.session.sendTypingNotification(self.jidRes(resource), msn2jid(contact), False) - del self.contactTyping[(contact, resource)] - - def gotContactTyping(self, contact, resource): - # Check if the contact has only just started typing - if(not self.contactTyping.has_key((contact, resource))): - self.session.sendTypingNotification(self.jidRes(resource), msn2jid(contact), True) - - # Reset the counter - self.contactTyping[(contact, resource)] = 0 + for contact in self.contactTyping.keys(): + self.contactTyping[contact] += 1 + if self.contactTyping[contact] >= 3: + self.session.sendTypingNotification(self.jabberID, msn2jid(contact, True), False) + del self.contactTyping[contact] def userTypingNotification(self, dest, resource, composing): + if not self.session: return dest = jid2msn(dest) self.userTyping[dest] = composing - if(composing): # Make it instant + if composing: # Make it instant self.sendTypingToContact(dest) + + + # Implement callbacks from msn.MSNConnection + def connectionFailed(self, reason): + LogEvent(INFO, self.jabberID) + text = lang.get(self.session.lang).msnConnectFailed % reason + self.session.sendMessage(to=self.jabberID, fro=config.jid, body=text) + self.session.removeMe() + + def loginFailed(self, reason): + LogEvent(INFO, self.jabberID) + text = lang.get(self.session.lang).msnLoginFailure % (self.session.username) + self.session.sendErrorMessage(to=self.jabberID, fro=config.jid, etype="auth", condition="not-authorized", explanation=text, body="Login Failure") + self.session.removeMe() - def sendMSNContactPresence(self, msnContact, to=None): - if(not to): - to = self.session.jabberID - source = msn2jid(msnContact.userHandle) - show, ptype = state2presence(msnContact.status) - status = msnContact.screenName.decode("utf-8") - self.session.sendPresence(to=to, fro=source, show=show, status=status, ptype=ptype) - - def sendMSNUserPresence(self, userHandle, to=None): - msnContact = self.getContacts().getContact(userHandle) - if(msnContact): - self.sendMSNContactPresence(msnContact, to) - - def sendLists(self, resource): - """ Sends a copy of the MSN contact presences to this resource """ - debug.log("LegacyConnection: \"%s\" - sendLists(\"%s\")" % (self.session.jabberID, resource)) - fulljid = self.session.jabberID - if(resource): - fulljid += "/" + resource - if(self.getContacts()): - for userHandle in self.getContacts().getContacts(): - self.sendMSNUserPresence(userHandle, fulljid) - - def listSynchronized(self): - if(self.session): - self.session.sendPresence(to=self.session.jabberID, fro=config.jid) - self.subscriptions.syncJabberLegacyLists() - self.listSynced = True - self.subscriptions.flushSubscriptionBuffer() - - def gotMessage(self, remoteUser, resource, text): - source = msn2jid(remoteUser) - self.session.sendMessage(self.jidRes(resource), fro=source, body=text, mtype="chat") + def connectionLost(self, reason): + LogEvent(INFO, self.jabberID) + if reason.type != error.ConnectionDone: + text = lang.get(self.session.lang).msnDisconnected % reason + self.session.sendMessage(to=self.jabberID, fro=config.jid, body=text) + self.session.removeMe() # Tear down the session + + def multipleLogin(self): + LogEvent(INFO, self.jabberID) + self.session.sendMessage(to=self.jabberID, fro=config.jid, body=lang.get(self.session.lang).msnMultipleLogin) + self.session.removeMe() - def loggedIn(self): - if(self.session): - debug.log("LegacyConnection: \"%s\" - loggedIn()" % (self.session.jabberID)) - self.session.ready = True + def serverGoingDown(self): + LogEvent(INFO, self.jabberID) + self.session.sendMessage(to=self.jabberID, fro=config.jid, body=lang.get(self.session.lang).msnMaintenance) - def contactStatusChanged(self, remoteUser): - if(self.session): # Make sure the transport isn't shutting down - debug.log("LegacyConnection: \"%s\" - contactStatusChanged(\"%s\")" % (self.session.jabberID, remoteUser)) - self.sendMSNUserPresence(remoteUser) - - def ourStatusChanged(self, statusCode): - # Send out a new presence packet to the Jabber user so that the MSN-t icon changes - if(self.session): - source = config.jid - to = self.session.jabberID - show, ptype = state2presence(statusCode) - debug.log("LegacyConnection: \"%s\" - ourStatusChanged(\"%s\")" % (self.session.jabberID, statusCode)) - self.session.sendPresence(to=to, fro=source, show=show) + def accountNotVerified(self): + LogEvent(INFO, self.jabberID) + text = lang.get(self.session.lang).msnNotVerified % (self.session.username) + self.session.sendMessage(to=self.jabberID, fro=config.jid, body=text) def userMapping(self, passport, jid): + LogEvent(INFO, self.jabberID) text = lang.get(self.session.lang).userMapping % (passport, jid) - self.session.sendMessage(to=self.session.jabberID, fro=msn2jid(passport), body=text) + self.session.sendMessage(to=self.jabberID, fro=msn2jid(passport, True), body=text) - def userAddedMe(self, userHandle): - self.subscriptions.msnContactAddedMe(userHandle) - - def userRemovedMe(self, userHandle): - self.subscriptions.msnContactRemovedMe(userHandle) + def loggedIn(self): + LogEvent(INFO, self.jabberID) - def serverGoingDown(self): - if(self.session): - self.session.sendMessage(to=self.session.jabberID, fro=config.jid, body=lang.get(self.session.lang).msnMaintenance) + def listSynchronized(self): + LogEvent(INFO, self.jabberID) + self.session.sendPresence(to=self.jabberID, fro=config.jid) + self.legacyList.syncJabberLegacyLists() + self.listSynced = True + self.session.ready = True + #self.legacyList.flushSubscriptionBuffer() + + def ourStatusChanged(self, statusCode, screenName, personal): + # Send out a new presence packet to the Jabber user so that the transport icon changes + LogEvent(INFO, self.jabberID) + self.remoteShow, ptype = state2presence(statusCode) + self.remoteStatus = personal + self.remoteNick = screenName + self.sendShowStatus() + + def gotMessage(self, remoteUser, text): + LogEvent(INFO, self.jabberID) + source = msn2jid(remoteUser, True) + if self.contactTyping.has_key(remoteUser): + del self.contactTyping[remoteUser] + self.session.sendMessage(self.jabberID, fro=source, body=text, mtype="chat") + self.session.pytrans.statistics.stats["MessageCount"] += 1 + + def gotGroupchat(self, msnGroupchat, userHandle): + LogEvent(INFO, self.jabberID) + msnGroupchat.groupchat = LegacyGroupchat(self.session, switchboardSession=msnGroupchat) + msnGroupchat.groupchat.sendUserInvite(msn2jid(userHandle, True)) + + def gotContactTyping(self, contact): + LogEvent(INFO, self.jabberID) + # Check if the contact has only just started typing + if not self.contactTyping.has_key(contact): + self.session.sendTypingNotification(self.jabberID, msn2jid(contact, True), True) + + # Reset the counter + self.contactTyping[contact] = 0 - def multipleLogin(self): - if(self.session): - self.session.sendMessage(to=self.session.jabberID, fro=config.jid, body=lang.get(self.session.lang).msnMultipleLogin) - self.session.removeMe() + def failedMessage(self, remoteUser, message): + LogEvent(INFO, self.jabberID) + self.session.pytrans.statistics.stats["FailedMessageCount"] += 1 + fro = msn2jid(remoteUser, True) + self.session.sendErrorMessage(to=self.jabberID, fro=fro, etype="wait", condition="recipient-unavailable", explanation=lang.get(self.session.lang).msnFailedMessage, body=message) + + def contactAvatarChanged(self, userHandle, hash): + LogEvent(INFO, self.jabberID) + jid = msn2jid(userHandle, False) + c = self.session.contactList.findContact(jid) + if not c: return + + if hash: + # New avatar + av = self.session.pytrans.avatarCache.getAvatar(hash) + if av: + msnContact = self.getContacts().getContact(userHandle) + msnContact.msnobjGot = True + c.updateAvatar(av) + else: + def updateAvatarCB((imageData,)): + av = self.session.pytrans.avatarCache.setAvatar(imageData) + c.updateAvatar(av) + d = self.sendAvatarRequest(userHandle) + if d: + d.addCallback(updateAvatarCB) + else: + # They've turned off their avatar + global defaultAvatar + c.updateAvatar(defaultAvatar) - def accountNotVerified(self): - if(self.session): - text = lang.get(self.session.lang).msnNotVerified % (self.session.username) - self.session.sendMessage(to=self.session.jabberID, fro=config.jid, body=text) + def contactStatusChanged(self, remoteUser): + LogEvent(INFO, self.jabberID) + + msnContact = self.getContacts().getContact(remoteUser) + c = self.session.contactList.findContact(msn2jid(remoteUser, False)) + if not (c and msnContact): return + + show, ptype = state2presence(msnContact.status) + status = msnContact.personal.decode("utf-8", "replace") + screenName = msnContact.screenName.decode("utf-8", "replace") + + c.updateNickname(screenName, push=False) + c.updatePresence(show, status, ptype, force=True) + + def gotFileReceive(self, fileReceive): + LogEvent(INFO, self.jabberID) + # FIXME + ft.FTReceive(self.session, msn2jid(fileReceive.userHandle, True), fileReceive) + + def contactAddedMe(self, userHandle): + LogEvent(INFO, self.jabberID) + self.session.contactList.getContact(msn2jid(userHandle, False)).contactRequestsAuth() + + def contactRemovedMe(self, userHandle): + LogEvent(INFO, self.jabberID) + c = self.session.contactList.getContact(msn2jid(userHandle, True)) + c.contactDerequestsAuth() + c.contactRemovesAuth() + + def gotInitialEmailNotification(self, inboxunread, foldersunread): + if config.mailNotifications: + LogEvent(INFO, self.jabberID) + text = lang.get(self.session.lang).msnInitialMail % (inboxunread, foldersunread) + self.session.sendMessage(to=self.jabberID, fro=config.jid, body=text, mtype="headline") + + def gotRealtimeEmailNotification(self, mailfrom, fromaddr, subject): + if config.mailNotifications: + LogEvent(INFO, self.jabberID) + text = lang.get(self.session.lang).msnRealtimeMail % (mailfrom, fromaddr, subject) + self.session.sendMessage(to=self.jabberID, fro=config.jid, body=text, mtype="headline") + + def gotMSNAlert(self, text, actionurl, subscrurl): + LogEvent(INFO, self.jabberID) + + el = Element((None, "message")) + el.attributes["to"] = self.jabberID + el.attributes["from"] = config.jid + el.attributes["type"] = "headline" + body = el.addElement("body") + body.addContent(text) + + x = el.addElement("x") + x.attributes["xmlns"] = "jabber:x:oob" + x.addElement("desc").addContent("More information on this notice.") + x.addElement("url").addContent(actionurl) + + x = el.addElement("x") + x.attributes["xmlns"] = "jabber:x:oob" + x.addElement("desc").addContent("Manage subscriptions to alerts.") + x.addElement("url").addContent(subscrurl) + + self.session.pytrans.send(el) - def loginFailure(self, message): - if(self.session): - text = lang.get(self.session.lang).msnLoginFailure % (self.session.username) - self.session.sendErrorMessage(to=self.session.jabberID, fro=config.jid, etype="auth", condition="not-authorized", explanation=text, body="Login Failure") + def gotAvatarImageData(self, userHandle, imageData): + LogEvent(INFO, self.jabberID) + av = self.session.pytrans.avatarCache.setAvatar(imageData) + jid = msn2jid(userHandle, False) + c = self.session.contactList.findContact(jid) + c.updateAvatar(av) - def failedMessage(self, remoteUser, message): - if(self.session): - fro = msn2jid(remoteUser) - self.session.sendErrorMessage(to=self.session.jabberID, fro=fro, etype="wait", condition="recipient-unavailable", explanation=lang.get(self.session.lang).msnFailedMessage, body=message) - def initialEmailNotification(self, inboxunread, foldersunread): - text = lang.get(self.session.lang).msnInitialMail % (inboxunread, foldersunread) - self.session.sendMessage(to=self.session.jabberID, fro=config.jid, body=text, mtype="headline") + + +class LegacyList: + def __init__(self, session): + self.jabberID = session.jabberID + self.session = session - def realtimeEmailNotification(self, mailfrom, fromaddr, subject): - text = lang.get(self.session.lang).msnRealtimeMail % (mailfrom, fromaddr, subject) - self.session.sendMessage(to=self.session.jabberID, fro=config.jid, body=text, mtype="headline") + def removeMe(self): + self.session = None + + def addContact(self, jid): + LogEvent(INFO, self.jabberID) + userHandle = jid2msn(jid) + self.session.legacycon.addContact(msn.FORWARD_LIST, userHandle) + + # Handle adding a contact that has previously been removed + msnContact = self.session.legacycon.getContacts().getContact(userHandle) + if msnContact and msnContact.lists & msn.REVERSE_LIST: + self.session.legacycon.contactAddedMe(userHandle) + self.authContact(jid) + self.session.contactList.getContact(jid).contactGrantsAuth() + + def removeContact(self, jid): + LogEvent(INFO, self.jabberID) + jid = jid2msn(jid) + self.session.legacycon.remContact(msn.FORWARD_LIST, jid) + + + def authContact(self, jid): + LogEvent(INFO, self.jabberID) + userHandle = jid2msn(jid) + d = self.session.legacycon.remContact(msn.PENDING_LIST, userHandle) + if d: + self.session.legacycon.addContact(msn.REVERSE_LIST, userHandle) + self.session.legacycon.remContact(msn.BLOCK_LIST, userHandle) + self.session.legacycon.addContact(msn.ALLOW_LIST, userHandle) + + def deauthContact(self, jid): + LogEvent(INFO, self.jabberID) + jid = jid2msn(jid) + self.session.legacycon.remContact(msn.ALLOW_LIST, jid) + self.session.legacycon.remContact(msn.PENDING_LIST, jid) + self.session.legacycon.addContact(msn.BLOCK_LIST, jid) + + + + def syncJabberLegacyLists(self): + """ Synchronises the MSN contact list on server with the Jabber contact list """ + + global defaultAvatar + + # We have to make an MSNContactList from the XDB data, then compare it with the one the server sent + # Any subscription changes must be sent to the client, as well as changed in the XDB + LogEvent(INFO, self.jabberID, "Start.") + result = self.session.pytrans.xdb.request(self.jabberID, disco.IQROSTER) + oldContactList = msn.MSNContactList() + if result: + for item in result.elements(): + user = item.getAttribute("jid") + sub = item.getAttribute("subscription") + lists = item.getAttribute("lists") + if not lists: + lists = jabsub2msnlist(sub) # Backwards compatible + lists = int(lists) + contact = msn.MSNContact(userHandle=user, screenName="", lists=lists) + oldContactList.addContact(contact) - def connectionLost(self, reason): - if(self.session): - debug.log("LegacyConnection: \"%s\" - connectionLost(\"%s\")" % (self.session.jabberID, reason)) - text = lang.get(self.session.lang).msnDisconnected % ("Error") # FIXME, a better error would be nice =P - self.session.sendMessage(to=self.session.jabberID, fro=config.jid, body=text) - self.session.removeMe() # Tear down the session + newXDB = Element((None, "query")) + newXDB.attributes["xmlns"] = disco.IQROSTER + + contactList = self.session.legacycon.getContacts() + + # Convienence functions + def addedToList(num): + return (not (oldLists & num) and (lists & num)) + def removedFromList(num): + return ((oldLists & num) and not (lists & num)) + + for contact in contactList.contacts.values(): + # Compare with the XDB entry + oldContact = oldContactList.getContact(contact.userHandle) + if oldContact == None: + oldLists = 0 + else: + oldLists = oldContact.lists + lists = contact.lists + + # Create the Jabber representation of the + # contact base on the old list data and then + # sync it with current + jabContact = self.session.contactList.createContact(msn2jid(contact.userHandle, False), msnlist2jabsub(oldLists)) + jabContact.updateAvatar(defaultAvatar, push=False) + + if addedToList(msn.FORWARD_LIST): + jabContact.syncGroups(getGroupNames(contact, contactList), push=False) + jabContact.syncContactGrantedAuth() + + if removedFromList(msn.FORWARD_LIST): + jabContact.syncContactRemovedAuth() + + if addedToList(msn.ALLOW_LIST): + jabContact.syncUserGrantedAuth() + + if addedToList(msn.BLOCK_LIST) or removedFromList(msn.ALLOW_LIST): + jabContact.syncUserRemovedAuth() + + if (not (lists & msn.ALLOW_LIST) and not (lists & msn.BLOCK_LIST) and (lists & msn.REVERSE_LIST)) or (lists & msn.PENDING_LIST): + jabContact.contactRequestsAuth() + + if removedFromList(msn.REVERSE_LIST): + jabContact.contactDerequestsAuth() + + jabContact.syncRoster() + + item = newXDB.addElement("item") + item.attributes["jid"] = contact.userHandle + item.attributes["subscription"] = msnlist2jabsub(lists) + item.attributes["lists"] = str(lists) + + # Update the XDB + self.session.pytrans.xdb.set(self.jabberID, disco.IQROSTER, newXDB) + LogEvent(INFO, self.jabberID, "End.") + + def saveLegacyList(self): + contactList = self.session.legacycon.getContacts() + if not contactList: return + + newXDB = Element((None, "query")) + newXDB.attributes["xmlns"] = disco.IQROSTER + + for contact in contactList.contacts.values(): + item = newXDB.addElement("item") + item.attributes["jid"] = contact.userHandle + item.attributes["subscription"] = msnlist2jabsub(contact.lists) # Backwards compat + item.attributes["lists"] = str(contact.lists) + + self.session.pytrans.xdb.set(self.jabberID, disco.IQROSTER, newXDB) + LogEvent(INFO, self.jabberID, "Finished saving list.") +