/* * 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.io.IOException; import java.util.Arrays; import java.util.Date; import net.sf.jml.DisplayPictureListener; import net.sf.jml.MsnContact; import net.sf.jml.MsnContactPending; import net.sf.jml.MsnGroup; import net.sf.jml.MsnList; import net.sf.jml.MsnMessenger; import net.sf.jml.MsnObject; import net.sf.jml.MsnSwitchboard; import net.sf.jml.event.MsnContactListListener; import net.sf.jml.event.MsnEmailListener; import net.sf.jml.event.MsnMessageListener; import net.sf.jml.event.MsnMessengerListener; import net.sf.jml.event.MsnSwitchboardListener; import net.sf.jml.exception.IncorrectPasswordException; import net.sf.jml.exception.LoginException; import net.sf.jml.exception.MsgNotSendException; import net.sf.jml.exception.MsnProtocolException; import net.sf.jml.exception.UnknownMessageException; import net.sf.jml.exception.UnsupportedProtocolException; import net.sf.jml.message.MsnControlMessage; import net.sf.jml.message.MsnDatacastMessage; import net.sf.jml.message.MsnEmailActivityMessage; import net.sf.jml.message.MsnEmailInitEmailData; import net.sf.jml.message.MsnEmailInitMessage; import net.sf.jml.message.MsnEmailNotifyMessage; import net.sf.jml.message.MsnInstantMessage; import net.sf.jml.message.MsnSystemMessage; import net.sf.jml.message.MsnUnknownMessage; import net.sf.jml.message.p2p.DisplayPictureRetrieveWorker; import net.sf.jml.message.p2p.MsnP2PMessage; import net.sf.jml.net.Session; import net.sf.jml.net.SessionListener; import net.sf.kraken.avatars.Avatar; import net.sf.kraken.type.ConnectionFailureReason; import net.sf.kraken.type.TransportLoginStatus; import net.sf.kraken.util.chatstate.ChatStateEventSource; import org.apache.log4j.Logger; 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; import org.xmpp.packet.Presence; /** * MSN Listener Interface. * * This handles real interaction with MSN, but mostly is a listener for * incoming events from MSN. * * @author Daniel Henninger */ public class MSNListener implements MsnContactListListener, MsnMessageListener, MsnMessengerListener, MsnSwitchboardListener, MsnEmailListener, SessionListener { static Logger Log = Logger.getLogger(MSNListener.class); /** * Creates the MSN Listener instance. * * @param session Session this listener is associated with. */ public MSNListener(MSNSession session) { //this.msnSessionRef = new WeakReference<MSNSession>(session); this.msnSession = session; } /** * The session this listener is associated with. */ //public WeakReference<MSNSession> msnSessionRef = null; public MSNSession msnSession = null; /** * Returns the MSN session this listener is attached to. * * @return MSN session we are attached to. */ public MSNSession getSession() { //return msnSessionRef.get(); return msnSession; } /** * Handles incoming messages from MSN users. */ public void instantMessageReceived(MsnSwitchboard switchboard, MsnInstantMessage message, MsnContact friend) { getSession().getTransport().sendMessage( getSession().getJID(), getSession().getTransport().convertIDToJID(friend.getEmail().toString()), message.getContent() ); // TODO: this will cause a duplicate chat state message to be sent. Prevent this! final JID to = getSession().getJID(); final JID from = getSession().getTransport().convertIDToJID(friend.getEmail().toString()); getSession().getTransport().getChatStateEventSource().isActive(from, to); } /** * Handles incoming system messages from MSN. * * @param messenger Messenger session the message is associated with. * @param message MSN message. */ public void systemMessageReceived(MsnMessenger messenger, MsnSystemMessage message) { getSession().getTransport().sendMessage( getSession().getJID(), getSession().getTransport().getJID(), message.getContent() ); } /** * Handles incoming control messages from MSN. */ public void controlMessageReceived(MsnSwitchboard switchboard, MsnControlMessage message, MsnContact friend) { if (message.getTypingUser() != null) { final JID to = getSession().getJID(); final JID from = getSession().getTransport().convertIDToJID(friend.getEmail().toString()); getSession().getTransport().getChatStateEventSource().isComposing(from, to); } else { Log.debug("MSN: Received unknown control msg to " + switchboard + " from " + friend + ": " + message); } } /** * Handles incoming datacast messages from MSN. */ public void datacastMessageReceived(MsnSwitchboard switchboard, MsnDatacastMessage message, MsnContact friend) { final JID to = getSession().getJID(); final JID from = getSession().getTransport().convertIDToJID(friend.getEmail().toString()); if (message.getId() == 1) { final String msg = LocaleUtils.getLocalizedString("gateway.msn.nudge", "kraken"); getSession().getTransport().sendAttentionNotification(to, from, msg); } else if (message.getId() == 2) { final String msg = LocaleUtils.getLocalizedString("gateway.msn.wink", "kraken"); getSession().getTransport().sendAttentionNotification(to, from, msg); } else { Log.debug("MSN: Received unknown datacast message to " + switchboard + " from " + friend + ": " + message); } } /** * Handles incoming unknown messages from MSN. */ public void unknownMessageReceived(MsnSwitchboard switchboard, MsnUnknownMessage message, MsnContact friend) { Log.debug("MSN: Received unknown message to " + switchboard + " from " + friend + ": " + message); } public void initialEmailNotificationReceived(MsnSwitchboard switchboard, MsnEmailInitMessage message, MsnContact contact) { Log.debug("MSN: Got init email notify "+message.getInboxUnread()+" unread message(s)"); if (JiveGlobals.getBooleanProperty("plugin.gateway.msn.mailnotifications", true) && message.getInboxUnread() > 0) { getSession().getTransport().sendMessage( getSession().getJID(), getSession().getTransport().getJID(), LocaleUtils.getLocalizedString("gateway.msn.initialmail", "kraken", Arrays.asList(message.getInboxUnread())), Message.Type.headline ); } } public void initialEmailDataReceived(MsnSwitchboard switchboard, MsnEmailInitEmailData message, MsnContact contact) { Log.debug("MSN: Got init email data "+message.getInboxUnread()+" unread message(s)"); if (JiveGlobals.getBooleanProperty("plugin.gateway.msn.mailnotifications", true) && message.getInboxUnread() > 0) { getSession().getTransport().sendMessage( getSession().getJID(), getSession().getTransport().getJID(), LocaleUtils.getLocalizedString("gateway.msn.initialmail", "kraken", Arrays.asList(message.getInboxUnread())), Message.Type.headline ); } } public void newEmailNotificationReceived(MsnSwitchboard switchboard, MsnEmailNotifyMessage message, MsnContact contact) { Log.debug("MSN: Got new email notification from "+message.getFrom()+" <"+message.getFromAddr()+">"); if (JiveGlobals.getBooleanProperty("plugin.gateway.msn.mailnotifications", true)) { getSession().getTransport().sendMessage( getSession().getJID(), getSession().getTransport().getJID(), LocaleUtils.getLocalizedString("gateway.msn.mail", "kraken", Arrays.asList(message.getFrom(), message.getFromAddr(), message.getSubject())), Message.Type.headline ); } } public void activityEmailNotificationReceived(MsnSwitchboard switchboard, MsnEmailActivityMessage message, MsnContact contact) { Log.debug("MSN: Got email activity notification "+message.getSrcFolder()+" to "+message.getDestFolder()); } /** * The user's login has completed and was accepted. */ public void loginCompleted(MsnMessenger messenger) { Log.debug("MSN: Login completed for "+messenger.getOwner().getEmail()); getSession().setLoginStatus(TransportLoginStatus.LOGGED_IN); } /** * Contact list has been synced. */ public void contactListSyncCompleted(MsnMessenger messenger) { Log.debug("MSN: Contact list sync for "+messenger.getOwner().getEmail()); } /** * Contact list initialization has completed. */ public void contactListInitCompleted(MsnMessenger messenger) { for (MsnGroup msnGroup : messenger.getContactList().getGroups()) { Log.debug("MSN: Got group "+msnGroup); getSession().storeGroup(msnGroup); } for (MsnContact msnContact : messenger.getContactList().getContacts()) { Log.debug("MSN: Got contact "+msnContact); if (msnContact.isInList(MsnList.FL) && msnContact.getEmail() != null) { final MSNBuddy buddy = new MSNBuddy(getSession().getBuddyManager(), msnContact); getSession().getBuddyManager().storeBuddy(buddy); if (JiveGlobals.getBooleanProperty("plugin.gateway.msn.avatars", true)) { final MsnObject msnAvatar = msnContact.getAvatar(); if (msnAvatar != null && (buddy.getAvatar() == null || !buddy.getAvatar().getLegacyIdentifier().equals(msnAvatar.getSha1c()))) { try { messenger.retrieveDisplayPicture(msnAvatar, new DisplayPictureListener() { public void notifyMsnObjectRetrieval( MsnMessenger messenger, DisplayPictureRetrieveWorker worker, MsnObject msnObject, ResultStatus result, byte[] resultBytes, Object context) { Log.debug("MSN: Got avatar retrieval result: "+result); // Check for the value if (result == ResultStatus.GOOD) { try { Log.debug("MSN: Found avatar of length "+resultBytes.length); Avatar avatar = new Avatar(buddy.getJID(), msnAvatar.getSha1c(), resultBytes); buddy.setAvatar(avatar); } catch (IllegalArgumentException e) { Log.debug("MSN: Got null avatar, ignoring."); } } } }); } catch (Exception e) { Log.debug("MSN: Unable to retrieve MSN avatar: ", e); } } else if (buddy.getAvatar() != null && msnAvatar == null) { buddy.setAvatar(null); } } } } getSession().syncUsers(); } /** * A friend for this user has changed status. */ public void contactStatusChanged(MsnMessenger messenger, MsnContact friend) { if (!friend.isInList(MsnList.FL) || friend.getEmail() == null) { // Not in our buddy list, don't care, or null email address. We need that. return; } if (getSession().getBuddyManager().isActivated()) { try { final MSNBuddy buddy = getSession().getBuddyManager().getBuddy(getSession().getTransport().convertIDToJID(friend.getEmail().toString())); buddy.setPresenceAndStatus(((MSNTransport)getSession().getTransport()).convertMSNStatusToXMPP(friend.getStatus()), friend.getPersonalMessage()); buddy.setMsnContact(friend); if (JiveGlobals.getBooleanProperty("plugin.gateway.msn.avatars", true)) { final MsnObject msnAvatar = friend.getAvatar(); if (msnAvatar != null && (buddy.getAvatar() == null || !buddy.getAvatar().getLegacyIdentifier().equals(msnAvatar.getSha1c()))) { try { messenger.retrieveDisplayPicture(msnAvatar, new DisplayPictureListener() { public void notifyMsnObjectRetrieval( MsnMessenger messenger, DisplayPictureRetrieveWorker worker, MsnObject msnObject, ResultStatus result, byte[] resultBytes, Object context) { Log.debug("MSN: Got avatar retrieval result: "+result); // Check for the value if (result == ResultStatus.GOOD) { try { Log.debug("MSN: Found avatar of length "+resultBytes.length); Avatar avatar = new Avatar(buddy.getJID(), msnAvatar.getSha1c(), resultBytes); buddy.setAvatar(avatar); } catch (IllegalArgumentException e) { Log.debug("MSN: Got null avatar, ignoring."); } } } }); } catch (Exception e) { Log.debug("MSN: Unable to retrieve MSN avatar: ", e); } } else if (buddy.getAvatar() != null && msnAvatar == null) { buddy.setAvatar(null); } } } catch (NotFoundException e) { // Not in our contact list. Ignore. Log.debug("MSN: Received presense notification for contact we don't care about: "+friend.getEmail().toString()); } } else { getSession().getBuddyManager().storePendingStatus(getSession().getTransport().convertIDToJID(friend.getEmail().toString()), ((MSNTransport)getSession().getTransport()).convertMSNStatusToXMPP(friend.getStatus()), friend.getPersonalMessage()); } } /** * Someone added us to their contact list. */ public void contactAddedMe(MsnMessenger messenger, MsnContact friend) { Log.debug("MSN: Contact added me: "+ friend.getFriendlyName()); final JID from = getSession().getTransport().convertIDToJID(friend.getEmail().toString()); final Presence p = new Presence(); p.setType(Presence.Type.subscribe); p.setTo(getSession().getJID()); p.setFrom(from); getSession().getTransport().sendPacket(p); } /** * Someone removed us from their contact list. */ public void contactRemovedMe(MsnMessenger messenger, MsnContact friend) { Log.debug("MSN: Contact removed me: "+ friend.getFriendlyName()); Presence p = new Presence(); p.setType(Presence.Type.unsubscribe); p.setTo(getSession().getJID()); p.setFrom(getSession().getTransport().convertIDToJID(friend.getEmail().toString())); getSession().getTransport().sendPacket(p); } /** * A contact we added has been added to the server. */ public void contactAddCompleted(MsnMessenger messenger, MsnContact contact, MsnList list) { Log.debug("MSN: Contact add completed: "+contact); // Presence p = new Presence(); // p.setType(Presence.Type.subscribed); // p.setTo(getSession().getJID()); // p.setFrom(getSession().getTransport().convertIDToJID(contact.getEmail().toString())); // getSession().getTransport().sendPacket(p); getSession().completedPendingContactAdd(contact); } /** * A contact we removed has been removed from the server. */ public void contactRemoveCompleted(MsnMessenger messenger, MsnContact contact, MsnList list) { Log.debug("MSN: Contact remove completed: "+contact); // Presence p = new Presence(); // p.setType(Presence.Type.unsubscribed); // p.setTo(getSession().getJID()); // p.setFrom(getSession().getTransport().convertIDToJID(contact.getEmail().toString())); // getSession().getTransport().sendPacket(p); } /** * A group we added has been added to the server. */ public void groupAddCompleted(MsnMessenger messenger, MsnGroup group) { Log.debug("MSN: Group add completed: "+group); getSession().storeGroup(group); getSession().completedPendingGroupAdd(group); } /** * A group we removed has been removed from the server. */ public void groupRemoveCompleted(MsnMessenger messenger, MsnGroup group) { Log.debug("MSN: Group remove completed: "+group); getSession().unstoreGroup(group); } /** * Owner status has changed. */ public void ownerStatusChanged(MsnMessenger messenger) { //getSession().setPresenceAndStatus(((MSNTransport)getSession().getTransport()).convertMSNStatusToXMPP(messenger.getOwner().getStatus()), messenger.getOwner().getPersonalMessage()); } /** * Catches MSN exceptions. */ public void exceptionCaught(MsnMessenger messenger, Throwable throwable) { Log.debug("MSN: Exception occurred for "+messenger.getOwner().getEmail()+" : "+throwable); if (throwable instanceof IncorrectPasswordException) { getSession().setLoginStatus(TransportLoginStatus.DISCONNECTED); getSession().setFailureStatus(ConnectionFailureReason.USERNAME_OR_PASSWORD_INCORRECT); getSession().sessionDisconnectedNoReconnect(LocaleUtils.getLocalizedString("gateway.msn.passwordincorrect", "kraken")); } else if (throwable instanceof LoginException) { // This can be a number of things, but generally it's a failed username and password. getSession().setLoginStatus(TransportLoginStatus.DISCONNECTED); getSession().setFailureStatus(ConnectionFailureReason.USERNAME_OR_PASSWORD_INCORRECT); getSession().sessionDisconnectedNoReconnect(LocaleUtils.getLocalizedString("gateway.msn.passwordincorrect", "kraken")); } else if (throwable instanceof MsnProtocolException) { Log.debug("MSN: Protocol exception: "+throwable.toString()); } else if (throwable instanceof MsgNotSendException) { getSession().getTransport().sendMessage( getSession().getJID(), getSession().getTransport().getJID(), LocaleUtils.getLocalizedString("gateway.msn.sendmsgfailed", "kraken")+" "+throwable.toString(), Message.Type.error ); } else if (throwable instanceof UnknownMessageException) { Log.debug("MSN: Unknown message: "+throwable.toString()); } else if (throwable instanceof UnsupportedProtocolException) { Log.debug("MSN: Protocol error: "+throwable.toString()); } else if (throwable instanceof IOException) { Log.debug("MSN: IO error: "+throwable.toString()); getSession().setLoginStatus(TransportLoginStatus.DISCONNECTED); getSession().setFailureStatus(ConnectionFailureReason.CAN_NOT_CONNECT); getSession().sessionDisconnected(LocaleUtils.getLocalizedString("gateway.msn.disconnect", "kraken")); } else { Log.debug("MSN: Unknown error: "+throwable.toString(), throwable); } } public void contactAddInGroupCompleted(MsnMessenger arg0, MsnContact arg1, MsnGroup arg2) { // TODO Auto-generated method stub } public void contactAddedMe(MsnMessenger arg0, MsnContactPending[] arg1) { // TODO Auto-generated method stub } public void contactPersonalMessageChanged(MsnMessenger arg0, MsnContact arg1) { // TODO Auto-generated method stub } public void contactRemoveFromGroupCompleted(MsnMessenger arg0, MsnContact arg1, MsnGroup arg2) { // TODO Auto-generated method stub } public void ownerDisplayNameChanged(MsnMessenger arg0) { // TODO Auto-generated method stub } public void offlineMessageReceived(String arg0, String arg1, String arg2, MsnContact arg3) { // TODO Auto-generated method stub } public void p2pMessageReceived(MsnSwitchboard arg0, MsnP2PMessage arg1, MsnContact arg2) { // TODO Auto-generated method stub } public void logout(MsnMessenger arg0) { // TODO Auto-generated method stub } public void contactJoinSwitchboard(MsnSwitchboard arg0, MsnContact arg1) { // TODO Auto-generated method stub } public void contactLeaveSwitchboard(MsnSwitchboard arg0, MsnContact arg1) { final JID to = getSession().getJID(); final JID from = getSession().getTransport().convertIDToJID(arg1.getEmail().toString()); getSession().getTransport().getChatStateEventSource().isGone(from, to); } public void switchboardClosed(MsnSwitchboard arg0) { // TODO Auto-generated method stub } public void switchboardStarted(MsnSwitchboard arg0) { // TODO Auto-generated method stub } public void exceptionCaught(Session arg0, Throwable t) throws Exception{ Log.debug("MSN: Session exceptionCaught for "+getSession().getRegistration().getUsername()+" : "+t); } public void messageReceived(Session arg0, net.sf.jml.net.Message message) throws Exception { Log.debug("MSN: Session messageReceived for "+getSession().getRegistration().getUsername()+" : "+message); // TODO: Kinda hacky, would like to improve on this later. if (message.toString().startsWith("OUT OTH")) { // Forced disconnect because account logged in elsewhere getSession().setLoginStatus(TransportLoginStatus.DISCONNECTED); getSession().setFailureStatus(ConnectionFailureReason.LOCKED_OUT); getSession().sessionDisconnectedNoReconnect(LocaleUtils.getLocalizedString("gateway.msn.otherloggedin", "kraken")); } else if (message.toString().startsWith("OUT SDH")) { // Forced disconnect from server for maintenance getSession().setLoginStatus(TransportLoginStatus.DISCONNECTED); getSession().setFailureStatus(ConnectionFailureReason.LOCKED_OUT); getSession().sessionDisconnectedNoReconnect(LocaleUtils.getLocalizedString("gateway.msn.disconnect", "kraken")); } } public void messageSent(Session arg0, net.sf.jml.net.Message message) throws Exception { Log.debug("MSN: Session messageSent for "+getSession().getRegistration().getUsername()+" : "+message); } public void sessionIdle(Session session) throws Exception { } public void sessionEstablished(Session session) { Log.debug("MSN: Session established for "+getSession().getRegistration().getUsername()); } public void sessionTimeout(Session session) { // This is used to handle regular pings to the MSN server. No need to mention it. } public void sessionClosed(Session session) { Log.debug("MSN: Session closed for "+getSession().getRegistration().getUsername()); } }