/*
* 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();
}
}
}