/* * Copyright 2006-2010 Daniel Henninger. All rights reserved. * * This software is published under the terms of the GNU Public License (GPL), * a copy of which is included in this distribution. */ package net.sf.kraken.protocols.msn; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import net.sf.jml.Email; import net.sf.jml.MsnContact; import net.sf.jml.MsnGroup; import net.sf.jml.MsnMessenger; import net.sf.jml.MsnObject; import net.sf.jml.MsnProtocol; import net.sf.jml.MsnSwitchboard; import net.sf.jml.impl.BasicMessenger; import net.sf.jml.impl.MsnMessengerFactory; import net.sf.jml.message.MsnControlMessage; import net.sf.jml.message.MsnDatacastMessage; import net.sf.kraken.registration.Registration; import net.sf.kraken.roster.TransportBuddyManager; import net.sf.kraken.session.TransportSession; import net.sf.kraken.type.ChatStateType; import net.sf.kraken.type.ConnectionFailureReason; import net.sf.kraken.type.PresenceType; import net.sf.kraken.type.SupportedFeature; import org.apache.log4j.Logger; import org.jivesoftware.openfire.roster.Roster; import org.jivesoftware.openfire.roster.RosterItem; import org.jivesoftware.openfire.user.UserNotFoundException; import org.jivesoftware.util.Base64; import org.jivesoftware.util.JiveGlobals; import org.jivesoftware.util.LocaleUtils; import org.jivesoftware.util.NotFoundException; import org.xmpp.packet.JID; import org.xmpp.packet.Message; /** * Represents a MSN session. * * This is the interface with which the base transport functionality will * communicate with MSN. * * @author Daniel Henninger */ public class MSNSession extends TransportSession<MSNBuddy> { static Logger Log = Logger.getLogger(MSNSession.class); /** * Create a MSN Session instance. * * @param registration Registration information used for logging in. * @param jid JID associated with this session. * @param transport Transport instance associated with this session. * @param priority Priority of this session. */ public MSNSession(Registration registration, JID jid, MSNTransport transport, Integer priority) { super(registration, jid, transport, priority); setSupportedFeature(SupportedFeature.chatstates); setSupportedFeature(SupportedFeature.attention); if (Email.parseStr(registration.getUsername()) == null) { Message m = new Message(); m.setType(Message.Type.error); m.setTo(getJID()); m.setFrom(getTransport().getJID()); m.setBody(LocaleUtils.getLocalizedString("gateway.msn.illegalaccount", "kraken")+" "+registration.getUsername()); getTransport().sendPacket(m); // TODO: this should probably be generic and done within base transport for -all- transports // TODO: Also, this Email.parseStr could be used in the "is this a valid username" check } } /** * MSN session */ private MsnMessenger msnMessenger = null; /** * MSN listener */ private MSNListener msnListener = null; /** * MSN groups. */ private ConcurrentHashMap<String,MsnGroup> msnGroups = new ConcurrentHashMap<String,MsnGroup>(); /** * Pending MSN groups and contact to be added. */ private ConcurrentHashMap<String,ArrayList<Email>> msnPendingGroups = new ConcurrentHashMap<String,ArrayList<Email>>(); /** * @see net.sf.kraken.session.TransportSession#logIn(net.sf.kraken.type.PresenceType, String) */ @Override public void logIn(PresenceType presenceType, String verboseStatus) { if (!isLoggedIn()) { Log.debug("Creating MSN session for " + registration.getUsername()); setPendingPresenceAndStatus(presenceType, verboseStatus); msnMessenger = MsnMessengerFactory.createMsnMessenger(registration.getUsername(), registration.getPassword()); msnListener = new MSNListener(this); ((BasicMessenger)msnMessenger).addSessionListener(msnListener); if (JiveGlobals.getBooleanProperty("plugin.gateway.msn.uselegacyprotocol", true)) { msnMessenger.setSupportedProtocol(new MsnProtocol[] { MsnProtocol.MSNP11 }); } else { msnMessenger.setSupportedProtocol(new MsnProtocol[] { MsnProtocol.MSNP15 }); } if (JiveGlobals.getBooleanProperty("plugin.gateway.msn.avatars", true) && getAvatar() != null) { try { msnMessenger.getOwner().setInitDisplayPicture(MsnObject.getInstance( msnMessenger.getOwner().getEmail().getEmailAddress(), Base64.decode(getAvatar().getImageData()) )); getAvatar().setLegacyIdentifier(msnMessenger.getOwner().getDisplayPicture().getSha1c()); } catch (NotFoundException e) { // Allllrighty then. no avatar for us. } } try { Log.debug("Logging in to MSN session for " + msnMessenger.getOwner().getEmail()); msnMessenger.getOwner().setInitStatus(((MSNTransport)getTransport()).convertXMPPStatusToMSN(presenceType)); msnMessenger.getOwner().setInitPersonalMessage(verboseStatus); msnMessenger.setLogIncoming(false); msnMessenger.setLogOutgoing(false); msnMessenger.addContactListListener(msnListener); msnMessenger.addEmailListener(msnListener); msnMessenger.addMessageListener(msnListener); msnMessenger.addMessengerListener(msnListener); msnMessenger.addSwitchboardListener(msnListener); ((BasicMessenger)msnMessenger).login( JiveGlobals.getProperty("plugin.gateway.msn.connecthost", "messenger.hotmail.com"), JiveGlobals.getIntProperty("plugin.gateway.msn.connectport", 1863)); } catch (Exception e) { Log.debug("MSN user is not able to log in: " + msnMessenger.getOwner().getEmail(), e); setFailureStatus(ConnectionFailureReason.UNKNOWN); sessionDisconnected("Unable to log in."); } } } /** * @see net.sf.kraken.session.TransportSession#logOut() */ @Override public void logOut() { cleanUp(); sessionDisconnectedNoReconnect(null); } /** * @see net.sf.kraken.session.TransportSession#cleanUp() */ @Override public void cleanUp() { if (msnMessenger != null) { if (msnListener != null) { ((BasicMessenger)msnMessenger).removeSessionListener(msnListener); msnMessenger.removeContactListListener(msnListener); msnMessenger.removeEmailListener(msnListener); msnMessenger.removeMessageListener(msnListener); msnMessenger.removeMessengerListener(msnListener); msnMessenger.removeSwitchboardListener(msnListener); } try { msnMessenger.getOwner().setPersonalMessage(""); } catch (Exception e) { // Ignore then. } msnMessenger.logout(); } } /** * Retrieves the manager for this session. * * @return Messenger instance the session is associated with. */ public MsnMessenger getManager() { return msnMessenger; } /** * Records information about a group on the user's contact list. * * @param msnGroup MSN group we are storing a copy of. */ public void storeGroup(MsnGroup msnGroup) { msnGroups.put(msnGroup.getGroupName(), msnGroup); } /** * Removes information about a group from the user's contact list. * * @param msnGroup MSN group we are removing a copy of. */ public void unstoreGroup(MsnGroup msnGroup) { msnGroups.remove(msnGroup.getGroupName()); } /** * Records a member of a pending new group that will be added later. * * @param groupName Name of group to be stored. * @param member Email address of member to be added. */ public void storePendingGroup(String groupName, Email member) { if (!msnPendingGroups.containsKey(groupName)) { ArrayList<Email> newList = new ArrayList<Email>(); newList.add(member); msnPendingGroups.put(groupName, newList); } else { ArrayList<Email> list = msnPendingGroups.get(groupName); list.add(member); msnPendingGroups.put(groupName, list); } } /** * Completes the addition of groups to a new contact after the contact has been created. * * @param msnContact Contact that was added. */ public void completedPendingContactAdd(MsnContact msnContact) { try { Roster roster = getTransport().getRosterManager().getRoster(getJID().getNode()); Email contact = msnContact.getEmail(); JID contactJID = getTransport().convertIDToJID(contact.toString()); RosterItem item = roster.getRosterItem(contactJID); getBuddyManager().storeBuddy(new MSNBuddy(getBuddyManager(), msnContact)); syncContactGroups(contact, item.getGroups()); } catch (UserNotFoundException e) { Log.debug("MSN: Unable to find roster when adding pendingcontact for "+getJID()); } } /** * Completes the addition of a contact to a new group after the group has been created. * * @param msnGroup Group that was added. */ public void completedPendingGroupAdd(MsnGroup msnGroup) { if (!msnPendingGroups.containsKey(msnGroup.getGroupName())) { // Nothing to do, no pending. return; } try { Roster roster = getTransport().getRosterManager().getRoster(getJID().getNode()); for (Email contact : msnPendingGroups.get(msnGroup.getGroupName())) { JID contactJID = getTransport().convertIDToJID(contact.toString()); RosterItem item = roster.getRosterItem(contactJID); syncContactGroups(contact, item.getGroups()); } } catch (UserNotFoundException e) { Log.debug("MSN: Unable to find roster when adding pending group contacts for "+getJID()); } } /** * Syncs up the MSN roster with the jabber roster. */ public void syncUsers() { try { getTransport().syncLegacyRoster(getJID(), buddyManager.getBuddies()); } catch (UserNotFoundException e) { Log.debug("Unable to sync MSN contact list for " + getJID(), e); } buddyManager.activate(); } /** * @see net.sf.kraken.session.TransportSession#addContact(org.xmpp.packet.JID, String, java.util.ArrayList) */ @Override public void addContact(JID jid, String nickname, ArrayList<String> groups) { Email contact = Email.parseStr(getTransport().convertJIDToID(jid)); if (contact == null) { Log.debug("MSN: Unable to add illegal contact "+jid); return; } msnMessenger.addFriend(contact, nickname); } /** * @see net.sf.kraken.session.TransportSession#removeContact(net.sf.kraken.roster.TransportBuddy) */ @Override public void removeContact(MSNBuddy contact) { Email email = Email.parseStr(getTransport().convertJIDToID(contact.getJID())); if (email == null) { Log.debug("MSN: Unable to remove illegal contact "+contact.getJID()); return; } msnMessenger.removeFriend(email, false); } /** * @see net.sf.kraken.session.TransportSession#updateContact(net.sf.kraken.roster.TransportBuddy) */ @Override public void updateContact(MSNBuddy contact) { Email email = Email.parseStr(getTransport().convertJIDToID(contact.getJID())); if (email == null) { Log.debug("MSN: Unable to update illegal contact "+contact.getJID()); return; } String nickname = getTransport().convertJIDToID(contact.getJID()); if (contact.getNickname() != null && !contact.getNickname().equals("")) { nickname = contact.getNickname(); } try { MSNBuddy msnBuddy = getBuddyManager().getBuddy(contact.getJID()); if (msnBuddy.msnContact == null) { MsnContact msnContact = msnMessenger.getContactList().getContactByEmail(email); if (msnContact == null) { Log.debug("MSN: Contact updated but doesn't exist? Adding."); addContact(contact.getJID(), nickname, (ArrayList<String>)contact.getGroups()); return; } else { msnBuddy.setMsnContact(msnContact); } } if (!msnBuddy.msnContact.getFriendlyName().equals(nickname)) { msnMessenger.renameFriend(email, nickname); } syncContactGroups(email, (List<String>)contact.getGroups()); } catch (NotFoundException e) { Log.debug("MSN: Newly added buddy not found in buddy manager: "+email.getEmailAddress()); } } /** * @see net.sf.kraken.session.TransportSession#acceptAddContact(JID) */ @Override public void acceptAddContact(JID jid) { final String userID = getTransport().convertJIDToID(jid); Log.debug("MSN: accept-adding " + userID); // According to a packet dump made with Wireshark, 'accepting' a // contact-add is done by adding the contact yourself (using an outgoing // ADL). final Email email = Email.parseStr(userID); if (email == null) { Log.warn("MSN: Unable to accept-add this illegal contact "+userID); return; } final TransportBuddyManager<MSNBuddy> manager = this.getBuddyManager(); final String nickname; if (manager.hasBuddy(jid)) { try { final MSNBuddy buddy = manager.getBuddy(jid); nickname = buddy.getNickname(); } catch (NotFoundException ex) { throw new RuntimeException("Buddy does not exist although manager.getBuddy() returns true: " + jid); } } else { nickname = null; } msnMessenger.addFriend(email, nickname); } /** * Given a legacy contact and a list of groups, makes sure that the list is in sync with * the actual group list. * * @param contact Email address of contact. * @param groups List of groups contact should be in. */ public void syncContactGroups(Email contact, List<String> groups) { MsnContact msnContact = null; try { MSNBuddy msnBuddy = getBuddyManager().getBuddy(getTransport().convertIDToJID(contact.getEmailAddress())); msnContact = msnBuddy.getMsnContact(); } catch (NotFoundException e) { Log.debug("MSN: Buddy not found in buddy manager: "+contact.getEmailAddress()); } if (msnContact == null) { return; } if (groups != null && !groups.isEmpty()) { // Create groups that do not currently exist. for (String group : groups) { if (!msnGroups.containsKey(group)) { Log.debug("MSN: Group "+group+" is a new group, creating."); msnMessenger.addGroup(group); // Ok, short circuit here, we need to wait for this group to be added. We'll be back. storePendingGroup(group, contact); return; } } // Make sure contact belongs to groups that we want. for (String group : groups) { Log.debug("MSN: Found "+contact+" should belong to group "+group); MsnGroup msnGroup = msnGroups.get(group); if (msnGroup != null && !msnContact.belongGroup(msnGroup)) { Log.debug("MSN: "+contact+" does not belong to "+group+", copying."); msnMessenger.copyFriend(contact, msnGroup.getGroupId()); } } // Now we will clean up groups that we should no longer belong to. for (MsnGroup msnGroup : msnContact.getBelongGroups()) { Log.debug("MSN: Found "+contact+" belongs to group "+msnGroup.getGroupName()); if (!groups.contains(msnGroup.getGroupName())) { Log.debug("MSN: "+contact+" should not belong to "+msnGroup.getGroupName()+", removing."); msnMessenger.removeFriend(contact, msnGroup.getGroupId()); } } } } /** * @see net.sf.kraken.session.TransportSession#sendMessage(org.xmpp.packet.JID, String) */ @Override public void sendMessage(JID jid, String message) { msnMessenger.sendText(Email.parseStr(getTransport().convertJIDToID(jid)), message.replaceAll("\n", "\r\n")); } /** * @see net.sf.kraken.session.TransportSession#sendChatState(org.xmpp.packet.JID,net.sf.kraken.type.ChatStateType) */ @Override public void sendChatState(JID jid, ChatStateType chatState) { if (chatState.equals(ChatStateType.composing)) { Email jidEmail = Email.parseStr(getTransport().convertJIDToID(jid)); MsnControlMessage mcm = new MsnControlMessage(); mcm.setTypingUser(msnMessenger.getOwner().getEmail().getEmailAddress()); for (MsnSwitchboard sb : msnMessenger.getActiveSwitchboards()) { if (sb.containContact(jidEmail)) { sb.sendMessage(mcm, true); } } } } /** * @see net.sf.kraken.session.TransportSession#sendBuzzNotification(org.xmpp.packet.JID, String) */ @Override public void sendBuzzNotification(JID jid, String message) { final MsnDatacastMessage nudge = new MsnDatacastMessage(); nudge.setId(1); // 1=nudge, 2=wink final Email jidEmail = Email.parseStr(getTransport().convertJIDToID(jid)); for (MsnSwitchboard sb : msnMessenger.getActiveSwitchboards()) { if (sb.containContact(jidEmail)) { sb.sendMessage(nudge, false); } } } /** * @see net.sf.kraken.session.TransportSession#updateLegacyAvatar(String, byte[]) */ @Override public void updateLegacyAvatar(String type, byte[] data) { msnMessenger.getOwner().setDisplayPicture(MsnObject.getInstance( msnMessenger.getOwner().getEmail().getEmailAddress(), data )); getAvatar().setLegacyIdentifier(msnMessenger.getOwner().getDisplayPicture().getSha1c()); } /** * @see net.sf.kraken.session.TransportSession#updateStatus(net.sf.kraken.type.PresenceType, String) */ @Override public void updateStatus(PresenceType presenceType, String verboseStatus) { if (isLoggedIn()) { try { msnMessenger.getOwner().setPersonalMessage(verboseStatus); msnMessenger.getOwner().setStatus(((MSNTransport)getTransport()).convertXMPPStatusToMSN(presenceType)); setPresenceAndStatus(presenceType, verboseStatus); } catch (IllegalStateException e) { // // Hrm, not logged in? Lets fix that. // msnMessenger.getOwner().setInitStatus(((MSNTransport)getTransport()).convertJabStatusToMSN(presenceType)); // msnMessenger.login(); } } else { // // Hrm, not logged in? Lets fix that. // msnMessenger.getOwner().setInitStatus(((MSNTransport)getTransport()).convertJabStatusToMSN(presenceType)); // msnMessenger.login(); } } }