/*
* Hamsam - Instant Messaging API
* Copyright (C) 2003 Raghu K
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package hamsam.protocol.yahoo;
import hamsam.api.Buddy;
import hamsam.api.Conference;
import hamsam.api.IMListener;
import hamsam.api.Message;
import hamsam.api.Response;
import hamsam.api.SmileyComponent;
import hamsam.exception.IllegalArgumentException;
import hamsam.exception.IllegalStateException;
import hamsam.exception.UnsupportedOperationException;
import hamsam.net.Connection;
import hamsam.net.ProxyInfo;
import hamsam.protocol.Protocol;
import java.io.IOException;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Hashtable;
import java.util.StringTokenizer;
import java.util.Vector;
/**
* This is the implementation of Yahoo instant messaging protocol. This
* runs as a thread which will scan all incoming packets from Yahoo and
* notify the registered event listener.
*
* @author Raghu
*/
public class YahooProtocol extends Thread implements Protocol, Constants
{
/**
* The connection used by Yahoo protocol.
*/
private Connection conn;
/**
* This is the status of the user.
*/
private long currentStatus;
/**
* The session ID used for Yahoo session management.
*/
private long sessionID;
/**
* Listener for Yahoo IM services.
*/
private IMListener listener;
/**
* Yahoo ID of the current user.
*/
private String yahooID;
/**
* Password of the current user.
*/
private String password;
/**
* This maps a conference object to its room name.
*/
private Hashtable confRoomMap;
/**
* This maps a room name to the corresponding conference object.
*/
private Hashtable roomConfMap;
/**
* This is the number of conferences already you have participated.
*/
private long conferenceCount;
/**
* This thread checks for incoming packets from Yahoo.
*/
private ReaderThread reader;
/**
* This thread handles all outgoing packets to Yahoo.
*/
private WriterThread writer;
/**
* This is a buffer for holding all incoming packets. Access
* to this buffer must be synchronized.
*/
private Vector readBuffer;
/**
* This is a buffer for holding all outgoing packets. Access
* to this buffer must be synchronized.
*/
private Vector writeBuffer;
/**
* Indicates whether we are connected to a Yahoo Server.
*/
private boolean connected;
/**
* The I/O error received from the reader / writer threads.
*/
private IOException IOError;
/**
* All smileys supported by Yahoo.
*/
private SmileyComponent[] smileys;
/**
* Default constructor.
*/
public YahooProtocol()
{
confRoomMap = new Hashtable();
roomConfMap = new Hashtable();
readBuffer = new Vector();
writeBuffer = new Vector();
smileys = Util.loadSmileys();
connected = false;
IOError = null;
setName("hamsam.protocol.yahoo.YahooProtocol");
setDaemon(true);
this.start();
}
/**
* Returns the name of this protocol. This method is used to
* distinguish each of the protocols supported by the API.
*
* <p>
* This method always returns the string "Yahoo"
*
* @return the identifying name of this protocol.
*/
public String getProtocolName()
{
return "Yahoo";
}
/**
* Connect to the Yahoo instant messaging service. This is equivalent
* to the logging in to the service.
*
* @param username the user id to log in.
* @param password the password for authentication.
* @param info the proxy information explaining how to connect.
*
* @throws IllegalStateException if no listener is set using the
* {@link #setListener(IMListener) setListener} method.
*/
public void connect(String username, String password, ProxyInfo info) throws IllegalStateException
{
if(listener == null)
throw new IllegalStateException("Listener not set");
listener.connecting(this);
this.yahooID = username.toLowerCase();
this.password = password;
// Create a connection object
try
{
this.conn = info.getConnection("scs.msg.yahoo.com", 5050);
}
catch(IOException e)
{
listener.connectFailed(this, "Unable to contact Yahoo! server");
return;
}
/* start threads for I/O with yahoo */
if(reader == null)
{
reader = new ReaderThread(this, conn, readBuffer);
reader.start();
}
else
reader.changeConnection(conn);
if(writer == null)
{
writer = new WriterThread(this, conn, writeBuffer);
writer.start();
}
else
writer.changeConnection(conn);
/* send the initial packet */
Packet pack = new Packet(SERVICE_VERIFY, STATUS_AVAILABLE, 0);
sendToWriterThread(pack);
}
/**
* Logout from Yahoo instant messaging service.
*
* @throws IllegalStateException if the protocol is not connected.
*/
public void disconnect() throws IllegalStateException
{
if(reader == null || writer == null)
throw new IllegalStateException("Not yet connected to Yahoo");
// send the logoff packet
Packet pack = new Packet(SERVICE_LOGOFF, STATUS_AVAILABLE, sessionID);
sendToWriterThread(pack);
// wait for the writer thread to pick this packet up.
// I know this is bad programming - this is just a quick fix,
// must find a better way of doing this.
try
{
sleep(1000);
}
catch(InterruptedException e)
{
}
reader.stopReading();
writer.stopWriting();
try
{
conn.close();
}
catch(IOException e)
{
// just ignore this.
}
reader = null;
writer = null;
listener.disconnected(this);
}
/**
* Determines whether this protocol will notify you if
* a user attempts to add you to his / her buddy list.
*
* @return always returns true.
*/
public boolean isBuddyAddRequestSupported()
{
return true;
}
/**
* Determines whether this protocol supports arranging buddies in
* different groups.
*
* @return always returns true.
*/
public boolean isBuddyGroupSupported()
{
return true;
}
/**
* Determines whether this protocol supports ignoring
* buddies. Ignored buddies can not send messages to
* the person who ignored them.
*
* @return always returns true.
*/
public boolean isIgnoreSupported()
{
return true;
}
/**
* Determines whether this protocol supports sending and
* receiving offline messages. Offline messages are messages
* which are sent when the receipient is not connected to
* the instant messaging system. These messages are stored
* by the instant messaging server and later delivered to the
* receipient when he / she connects to the system.
*
* @return always returns true.
*/
public boolean isOfflineMessageSupported()
{
return true;
}
/**
* Determines whether this protocol supports typing notifications.
* In an instant messaging session, some protocols inform one user
* that the other person starts or stops typing.
*
* @return always returns true.
*/
public boolean isTypingNotifySupported()
{
return true;
}
/**
* Determines whether this protocol supports conferences.
*
* @return always returns true.
* @see hamsam.api.Conference Conference
*/
public boolean isConferenceSupported()
{
return true;
}
/**
* Determines whether this protocol supports e-mail alerts. Many protcols have
* associated e-mail accounts. When an e-mail arrives in that account, a
* notification is sent.
*
* @return always returns true.
*/
public boolean isMailNotifySupported()
{
return true;
}
/**
* Determines whether this protocol supports setting the status of the
* user to invisible. Invisible users appear as offline to other users
* although they can send and receive messages and participate in any
* other operations.
*
* @return always returns true.
*/
public boolean isInvisibleSupported()
{
return true;
}
/**
* Sets the listener for Yahoo protocol. This listener will be notified for
* any instant message events that originates from Yahoo.
*
* @param listener the listener for Yahoo protocol, pass <code>null</code>
* to unregister the current listener.
*/
public void setListener(IMListener listener)
{
this.listener = listener;
}
/**
* Change the status for the current user logged in to Yahoo.
*
* @param status the status message for the user.
*/
public void changeStatus(String status)
{
String statMsg = "";
long[] indicators = {
STATUS_AVAILABLE, STATUS_BRB, STATUS_BUSY, STATUS_NOTATHOME,
STATUS_NOTATDESK, STATUS_NOTINOFFICE, STATUS_ONPHONE,
STATUS_ONVACATION, STATUS_OUTTOLUNCH, STATUS_STEPPEDOUT
};
String[] messages = {
"I'm Available", "Be Right Back", "Busy", "Not At Home",
"Not At My Desk", "Not In The Office", "On The Phone",
"On Vacation", "Out To Lunch", "Stepped Out"
};
if(status == null)
{
currentStatus = STATUS_INVISIBLE;
statMsg = null;
}
else
{
for(int i = 0; i < messages.length; i++)
if(status.equals(messages[i]))
{
currentStatus = indicators[i];
statMsg = null;
}
if(statMsg != null)
{
currentStatus = STATUS_CUSTOM;
statMsg = status;
}
}
int service;
if(currentStatus == STATUS_AVAILABLE)
service = SERVICE_ISBACK;
else
service = SERVICE_ISAWAY;
Packet pack = new Packet(service, currentStatus, sessionID);
pack.putData(10, String.valueOf(currentStatus));
if(currentStatus == STATUS_CUSTOM)
{
pack.putData(19, statMsg);
pack.putData(47, "0");
}
sendToWriterThread(pack);
}
/**
* Start a new conference by inviting some buddies.
*
* @param conf the conference which is to be started.
* @param message the invitation message to be sent.
* @throws IllegalStateException if the protocol is not yet connected.
*/
public void startConference(Conference conf, String message) throws IllegalStateException
{
Packet pack = new Packet(SERVICE_CONFINVITE, STATUS_AVAILABLE, sessionID);
pack.putData(1, yahooID);
pack.putData(13, "0");
pack.putData(50, yahooID);
Buddy[] buddies = conf.getParticipants();
for(int i = 0; i < buddies.length; i++)
{
String user = buddies[i].getUsername();
if(!user.equals(yahooID))
pack.putData(52, user);
}
String confRoom = yahooID + " - " + ++conferenceCount;
pack.putData(57, confRoom);
pack.putData(58, message);
sendToWriterThread(pack);
confRoomMap.put(conf, confRoom);
roomConfMap.put(confRoom, conf);
}
/**
* Disconnect the current user from a conference.
*
* @param conf the conference from which the user has to disconnect.
* @throws IllegalStateException if the protocol is not yet connected.
*/
public void quitConference(Conference conf) throws IllegalStateException
{
checkConnected();
String room = (String) confRoomMap.get(conf);
if(room == null)
return;
Packet pack = new Packet(SERVICE_CONFLOGOFF, STATUS_AVAILABLE, sessionID);
pack.putData(1, yahooID);
pack.putData(57, room);
Buddy[] buddies = conf.getParticipants();
for(int i = 0; i < buddies.length; i++)
pack.putData(3, buddies[i].getUsername());
sendToWriterThread(pack);
confRoomMap.remove(conf);
roomConfMap.remove(room);
}
/**
* Send a conference message.
*
* @param conf the conference to which the message is to be sent.
* @param message the instant message to be sent.
*
* @throws IllegalStateException if the protocol is not yet connected.
*/
public void sendConferenceMessage(Conference conf, Message message) throws IllegalStateException
{
checkConnected();
String room = (String) confRoomMap.get(conf);
if(room == null)
return;
Packet pack = new Packet(SERVICE_CONFMSG, STATUS_AVAILABLE, sessionID);
pack.putData(1, yahooID);
pack.putData(57, room);
pack.putData(14, Util.messageToYahooString(message));
Buddy[] buddies = conf.getParticipants();
for(int i = 0; i < buddies.length; i++)
pack.putData(53, buddies[i].getUsername());
sendToWriterThread(pack);
}
/**
* Add a buddy to your buddy list. The result of this operation
* will be notified to the registered <code>IMListener</code>.
*
* @param buddy the buddy to be added.
*
* @see IMListener#buddyAddRequest(Buddy,Buddy,String) IMListener.buddyAddRequest
* @see IMListener#buddyAdded(Buddy) IMListener.buddyAdded
* @see IMListener#buddyAddFailed(Buddy,String) IMListener.buddyAddFailed
*
* @throws IllegalArgumentException if the group of the buddy is not specified.
* @throws IllegalStateException if the protocol is not connected to Yahoo yet.
*/
public void addToBuddyList(Buddy buddy) throws IllegalArgumentException, IllegalStateException
{
checkConnected();
Packet pack = new Packet(SERVICE_ADDBUDDY, STATUS_AVAILABLE, sessionID);
pack.putData(1, yahooID);
pack.putData(7, buddy.getUsername());
String group = buddy.getGroup();
if(group == null)
throw new IllegalArgumentException("Buddy's group is unknown.");
pack.putData(65, group);
sendToWriterThread(pack);
}
/**
* Delete a buddy from your buddy list. The result of this operation
* will be notified to the registered <code>IMListener</code>.
*
* @param buddy the buddy to be deleted.
*
* @see IMListener#buddyDeleted(Buddy) IMListener.buddyDeleted
* @see IMListener#buddyDeleteFailed(Buddy,String) IMListener.buddyDeleteFailed
*
* @throws IllegalArgumentException if the group of the buddy is not specified.
* @throws IllegalStateException if the protocol is not connected to Yahoo yet.
*/
public void deleteFromBuddyList(Buddy buddy) throws IllegalArgumentException, IllegalStateException
{
Packet pack = new Packet(SERVICE_REMBUDDY, STATUS_AVAILABLE, sessionID);
pack.putData(1, yahooID);
pack.putData(7, buddy.getUsername());
String group = buddy.getGroup();
if(group == null)
throw new IllegalArgumentException("Buddy's group is unknown.");
pack.putData(65, group);
sendToWriterThread(pack);
}
/**
* Prevent a buddy from sending you messages. The result of this
* operation will be notified to the registered <code>IMListener</code>.
*
* @param buddy the buddy to be ignored.
*
* @see IMListener#buddyIgnored(Buddy) IMListener.buddyIgnored
* @see IMListener#buddyIgnoreFailed(Buddy,String) IMListener.buddyIgnoreFailed
*
* @throws IllegalStateException if the protocol is not connected to Yahoo yet.
*/
public void ignoreBuddy(Buddy buddy) throws IllegalStateException
{
checkConnected();
Packet pack = new Packet(SERVICE_IGNOREBUDDY, STATUS_AVAILABLE, sessionID);
pack.putData(1, yahooID);
pack.putData(7, buddy.getUsername());
pack.putData(13, "1");
sendToWriterThread(pack);
}
/**
* Undo a previous ignore operation for a buddy. The result of this
* operation will be notified to the registered <code>IMListener</code>.
*
* @param buddy the buddy to be ignored.
*
* @see IMListener#buddyUnignored(Buddy) IMListener.buddyUnignored
* @see IMListener#buddyUnignoreFailed(Buddy,String) IMListener.buddyUnignoreFailed
*
* @throws IllegalStateException if the protocol is not connected to Yahoo yet.
*/
public void unignoreBuddy(Buddy buddy) throws IllegalStateException
{
checkConnected();
Packet pack = new Packet(SERVICE_IGNOREBUDDY, STATUS_AVAILABLE, sessionID);
pack.putData(1, yahooID);
pack.putData(7, buddy.getUsername());
pack.putData(13, "2");
sendToWriterThread(pack);
}
/**
* Send an instant message to a buddy.
*
* @param buddy the buddy to whom the message should be sent.
* @param message the message to be sent.
*/
public void sendInstantMessage(Buddy buddy, Message message)
{
Packet pack = new Packet(SERVICE_MESSAGE, STATUS_OFFLINE, sessionID);
pack.putData(0, yahooID);
pack.putData(1, yahooID);
pack.putData(5, buddy.getUsername());
pack.putData(14, Util.messageToYahooString(message));
sendToWriterThread(pack);
}
/**
* Notify this buddy that you have started typing.
*
* @param buddy the buddy to whom the typing notification is to be sent.
*/
public void typingStarted(Buddy buddy)
{
Packet pack = new Packet(SERVICE_NOTIFY, STATUS_TYPING, sessionID);
pack.putData(4, yahooID);
pack.putData(5, buddy.getUsername());
pack.putData(13, "1");
pack.putData(14, " ");
pack.putData(49, "TYPING");
sendToWriterThread(pack);
}
/**
* Notify this buddy that you have stopped typing.
*
* @param buddy the buddy to whom the typing notification is to be sent.
*/
public void typingStopped(Buddy buddy)
{
Packet pack = new Packet(SERVICE_NOTIFY, STATUS_TYPING, sessionID);
pack.putData(4, yahooID);
pack.putData(5, buddy.getUsername());
pack.putData(13, "0");
pack.putData(14, " ");
pack.putData(49, "TYPING");
sendToWriterThread(pack);
}
/**
* Returns an array of all SmileyComponents supported by this protocol.
*
* @return an array of all SmileyComponents supported by this protocol.
*/
public SmileyComponent[] getSupportedSmileys()
{
return smileys;
}
/**
* Returns an array of status messages supported by this protocol. Since Yahoo
* supports any status message, this method always returns null.
*
* @return <code>null</code>
*/
public String[] getSupportedStatusMessages()
{
return null;
}
/**
* This thread starts here.
*/
public void run()
{
GregorianCalendar prevKeepAliveTime = new GregorianCalendar();
while(true)
{
if(listener == null)
{
try
{
Thread.sleep(200);
}
catch (InterruptedException e)
{
}
continue;
}
// check for I/O errors
if(IOError != null)
{
if(reader != null)
reader.stopReading();
if(writer != null)
writer.stopWriting();
try
{
conn.close();
}
catch(IOException e)
{
}
IOError = null;
reader = null;
writer = null;
listener.disconnected(this);
}
// we are sending keep alive packets once in a minute
// to keep the Yahoo connection up.
GregorianCalendar now = new GregorianCalendar();
prevKeepAliveTime.add(GregorianCalendar.SECOND, 59);
if(now.after(prevKeepAliveTime))
{
Packet pack = new Packet(SERVICE_PING, STATUS_AVAILABLE, sessionID);
sendToWriterThread(pack);
prevKeepAliveTime = now;
}
else
prevKeepAliveTime.add(GregorianCalendar.SECOND, -59);
Packet pack = null;
synchronized(readBuffer)
{
try
{
if(readBuffer.size() > 0)
pack = (Packet) readBuffer.remove(0);
else
readBuffer.wait(15000);
}
catch(InterruptedException e)
{
/* nothing to do here */
}
}
if(pack == null)
continue;
switch(pack.getService())
{
case SERVICE_USERSTAT:
case SERVICE_LOGON:
case SERVICE_LOGOFF:
case SERVICE_ISAWAY:
case SERVICE_ISBACK:
case SERVICE_IDACT:
case SERVICE_IDDEACT:
processStatus(pack);
break;
case SERVICE_NOTIFY:
processNotify(pack);
break;
case SERVICE_SYSMESSAGE:
case SERVICE_MESSAGE:
processMessage(pack);
break;
case SERVICE_NEWMAIL:
processMail(pack);
break;
case SERVICE_NEWCONTACT:
processContact(pack);
break;
case SERVICE_LIST:
processList(pack);
break;
case SERVICE_VERIFY:
processVerification(pack);
break;
case SERVICE_AUTH:
processAuthentication(pack);
break;
case SERVICE_AUTHRESP:
processAuthenticationResponse(pack);
break;
case SERVICE_CONFINVITE:
case SERVICE_CONFADDINVITE:
case SERVICE_CONFDECLINE:
case SERVICE_CONFLOGON:
case SERVICE_CONFLOGOFF:
case SERVICE_CONFMSG:
processConference(pack);
break;
/*case SERVICE_P2PFILEXFER:
case SERVICE_FILETRANSFER:
yahoo_process_filetransfer(pack);
break;*/
case SERVICE_ADDBUDDY:
processBuddyAdd(pack);
break;
case SERVICE_REMBUDDY:
processBuddyDelete(pack);
break;
case SERVICE_IGNOREBUDDY:
processBuddyIgnore(pack);
break;
}
}
}
/**
* This method is invoked by the reader and writer threads to indicate
* an I/O error.
*
* <p>
* All operations will be stopped and the service will be shut down
* once this method is invoked.
*/
void shutdown(IOException e)
{
IOError = e;
}
/**
* Given a yahoo status code and custom status message, create
* a status string.
*
* @param status yahoo status code.
* @param message custom status message.
* @return the status string.
*/
private String parseYahooStatus(long status, String message)
{
switch((int)status)
{
case (int)STATUS_AVAILABLE:
return "I'm available";
case (int)STATUS_BRB:
return "Be Right Back";
case (int)STATUS_BUSY:
return "Busy";
case (int)STATUS_NOTATHOME:
return "Not At Home";
case (int)STATUS_NOTATDESK:
return "Not At My Desk";
case (int)STATUS_NOTINOFFICE:
return "Not In The Office";
case (int)STATUS_ONPHONE:
return "On The Phone";
case (int)STATUS_ONVACATION:
return "On Vacation";
case (int)STATUS_OUTTOLUNCH:
return "Out To Lunch";
case (int)STATUS_STEPPEDOUT:
return "Stepped Out";
case (int)STATUS_OFFLINE:
return null;
case (int)STATUS_CUSTOM:
return message;
}
return null;
}
/**
* Parse a string of Yahoo IDs and build an array of buddy objects from it.
* This is used for processing buddy list and ignore list.
*
* <p>
* The format of the input string is as follows. Each line in the string,
* separated by new line character, represents a group. The line starts
* with the group name, followed by a colon. The remaining part of the
* line is the Yahoo IDs belonging to that group, separated by commas.
*
* @param list the list of Yahoo IDs to be parsed.
* @return an array of buddy objects.
*/
private Buddy[] parseYahooList(String list)
{
String[] lines;
String[] split = new String[2];
Vector buddyVector = new Vector();
StringTokenizer tok = new StringTokenizer(list, "\n");
lines = new String[tok.countTokens()];
for(int i = 0; tok.hasMoreTokens(); i++)
lines[i] = tok.nextToken();
for(int i = 0; i < lines.length; i++)
{
int index = lines[i].indexOf(':');
if(index == -1)
continue;
try
{
split[0] = lines[i].substring(0, index);
split[1] = lines[i].substring(index + 1);
}
catch(IndexOutOfBoundsException e)
{
continue;
}
tok = new StringTokenizer(split[1], ",");
for(int j = 0; tok.hasMoreTokens(); j++)
buddyVector.add(new Buddy(this, tok.nextToken(), split[0]));
}
return (Buddy[])buddyVector.toArray(new Buddy[0]);
}
/**
* Process the change in status of a user.
*
* @param pack the packet containing info.
*/
private void processStatus(Packet pack)
{
String name = null;
int state = 0;
String msg = null;
if(pack.getService() == SERVICE_LOGOFF && pack.getStatus() == 0xffffffffL)
{
/* you are disconnected because of a duplicate login */
connected = false;
listener.disconnected(this);
return;
}
int size = pack.getDataSize();
for(int i = 0; i < size; i++)
{
int key = pack.getKey(i);
String value = pack.getValue(i);
switch(key)
{
case 1: /* we don't get the full buddy list here. */
connected = true;
listener.connected(this);
break;
case 7: /* the current buddy */
name = value;
break;
case 10: /* state */
state = Integer.parseInt(value);
break;
case 19: /* custom status message */
msg = value;
break;
case 13:
if(pack.getService() == SERVICE_LOGOFF || Integer.parseInt(value) == 0)
{
Buddy buddy = new Buddy(this, name);
buddy.setStatus(parseYahooStatus(STATUS_OFFLINE, null));
listener.buddyStatusChanged(buddy);
break;
}
Buddy buddy = new Buddy(this, name);
buddy.setStatus(parseYahooStatus(state, msg));
listener.buddyStatusChanged(buddy);
break;
case 16: /* Custom error message */
listener.protocolMessageReceived(this, Util.parseYahooMessage(value));
break;
}
}
}
/**
* Process a notify packet received from Yahoo.
*
* @param pack the packet received from Yahoo.
*/
private void processNotify(Packet pack)
{
String msg = null;
String from = null;
int stat = 0;
int size = pack.getDataSize();
for(int i = 0; i < size; i++)
{
int key = pack.getKey(i);
String value = pack.getValue(i);
if(key == 4)
from = value;
if(key == 49)
msg = value;
if(key == 13)
stat = Integer.parseInt(value);
}
if(msg == null || from == null)
return;
if(msg.equalsIgnoreCase("TYPING"))
{
Buddy buddy = new Buddy(this, from);
if(stat == 1)
listener.typingStarted(buddy);
else if(stat == 0)
listener.typingStopped(buddy);
}
}
/**
* Process an instant message or system message received from Yahoo.
*
* @param pack the received packet.
*/
private void processMessage(Packet pack)
{
String msg = null;
String from = null;
long time = -1;
int size = pack.getDataSize();
for(int i = 0; i < size; i++)
{
int key = pack.getKey(i);
String value = pack.getValue(i);
if(key == 1)
from = value;
else if(key == 4)
from = value;
else if(key == 15)
time = Long.parseLong(value) * 1000;
else if(key == 14 || key == 16)
{
// user message or system message
msg = value;
if(pack.getService() == SERVICE_SYSMESSAGE)
listener.protocolMessageReceived(this, Util.parseYahooMessage(msg));
else if(pack.getStatus() <= 1 || pack.getStatus() == 5)
{
Buddy buddy = new Buddy(this, from);
if(time != -1)
listener.offlineMessageReceived(buddy, new Date(time),
Util.parseYahooMessage(msg));
else
listener.instantMessageReceived(buddy, Util.parseYahooMessage(msg));
}
else if(pack.getStatus() == 2)
{
/* this indicates an error in sending message to the buddy */
/*TODO*/
}
else if(pack.getStatus() == 0xffffffff)
{
/* this is an error message from Yahoo */
/*TODO*/
//listener.protocolMessageReceived(this, Util.parseYahooMessage(msg));
}
time = -1;
msg = from = null;
}
}
}
/**
* Process a new mail notification from Yahoo.
*
* @param pack the received packet.
*/
private void processMail(Packet pack)
{
String who = null;
String mailID = null;
String subject = null;
int count = 0;
int size = pack.getDataSize();
for(int i = 0; i < size; i++)
{
int key = pack.getKey(i);
String value = pack.getValue(i);
if(key == 9)
count = Integer.parseInt(value);
else if(key == 43)
who = value;
else if(key == 42)
mailID = value;
else if(key == 18)
subject = value;
}
if(who != null && mailID != null && subject != null)
{
String[] fromArr = new String[1];
fromArr[0] = who + " <" + mailID + ">";
String[] subjectArr = new String[1];
subjectArr[0] = subject;
listener.mailNotificationReceived(this, 1, fromArr, subjectArr);
}
else
listener.mailNotificationReceived(this, count, null, null);
}
/**
* Process a new contact notification from Yahoo.
*
* @param pack the received packet.
*/
private void processContact(Packet pack)
{
String myid = null;
String who = null;
String msg = null;
String name = null;
long state = STATUS_AVAILABLE;
int away = 0;
int size = pack.getDataSize();
for(int i = 0; i < size; i++)
{
int key = pack.getKey(i);
String value = pack.getValue(i);
if(key == 1)
myid = value;
else if(key == 3)
who = value;
else if(key == 14)
msg = value;
else if(key == 7)
name = value;
else if(key == 10)
state = Integer.parseInt(value);
else if(key == 47)
away = Integer.parseInt(value);
}
if(myid != null)
{
Response resp = listener.buddyAddRequest(new Buddy(this, who), new Buddy(this, myid), msg);
if(!resp.isAccepted())
{
Packet retPack = new Packet(SERVICE_REJECTCONTACT, STATUS_AVAILABLE, sessionID);
retPack.putData(1, yahooID);
retPack.putData(7, who);
retPack.putData(14, resp.getMessage());
sendToWriterThread(retPack);
}
}
else if(name != null)
{
Buddy buddy = new Buddy(this, name);
buddy.setStatus(parseYahooStatus(state, msg));
listener.buddyStatusChanged(buddy);
}
else if(pack.getStatus() == 0x07)
{
Buddy buddy = new Buddy(this, who);
listener.buddyAddRejected(buddy, msg);
}
}
/**
* Process the buddy and ignore lists received from Yahoo.
*
* @param pack the received packet.
*/
private void processList(Packet pack)
{
String rawBuddyList = null;
String ignoreList = null;
int size = pack.getDataSize();
for(int i = 0; i < size; i++)
{
int key = pack.getKey(i);
String value = pack.getValue(i);
switch(key)
{
case 87: /* buddies */
if(rawBuddyList == null)
rawBuddyList = value;
else
rawBuddyList += value;
break;
case 88: /* ignore list */
if(ignoreList == null)
ignoreList = "Ignore:";
ignoreList += value;
break;
case 59: /* cookies add C cookie */
if(ignoreList != null)
{
Buddy[] ignore = parseYahooList(ignoreList);
ignoreList = null;
listener.ignoreListReceived(this, ignore);
}
if(rawBuddyList != null)
{
Buddy[] buddies = parseYahooList(rawBuddyList);
rawBuddyList = null;
listener.buddyListReceived(this, buddies);
}
/*TODO I shall handle cookies and file transfer together
if(pair->value[0]=='Y')
{
FREE(yd->cookie_y);
FREE(yd->login_cookie);
yd->cookie_y = getcookie(pair->value);
yd->login_cookie = getlcookie(yd->cookie_y);
}
else if(pair->value[0]=='T')
{
FREE(yd->cookie_t);
yd->cookie_t = getcookie(pair->value);
}
else if(pair->value[0]=='C')
{
FREE(yd->cookie_c);
yd->cookie_c = getcookie(pair->value);
}*/
break;
}
}
}
/**
* Process the initial verification of the Yahoo server.
*
* @param pack the packet containing the verification info.
*/
private void processVerification(Packet pack)
{
if(pack.getStatus() == 1)
{
pack = new Packet(SERVICE_AUTH, STATUS_AVAILABLE, 0);
pack.putData(1, yahooID);
sendToWriterThread(pack);
}
}
/**
* Process an authentication request from Yahoo.
*
* @param pack the packet containing authentication request.
*/
private void processAuthentication(Packet pack)
{
sessionID = pack.getSessionID();
String seed = null;
String sn = null;
int method = 0;
int size = pack.getDataSize();
for(int i = 0; i < size; i++)
{
int key = pack.getKey(i);
String value = pack.getValue(i);
if(key == 94)
seed = value;
if(key == 1)
sn = value;
if(key == 13)
method = Integer.parseInt(value);
}
if(seed == null || sn == null)
return;
Crypt crypt = new Crypt(yahooID, password, seed);
crypt.setMethod(method);
String[] response = crypt.doEncrypt();
Packet retPack = new Packet(SERVICE_AUTHRESP, STATUS_AVAILABLE, 0);
retPack.putData(0, yahooID);
retPack.putData(1, yahooID);
retPack.putData(6, response[0]);
retPack.putData(96, response[1]);
sendToWriterThread(retPack);
}
private void processAuthenticationResponse(Packet pack)
{
String url = null;
int loginStatus = 0;
int size = pack.getDataSize();
for(int i = 0; i < size; i++)
{
int key = pack.getKey(i);
String value = pack.getValue(i);
if(key == 20)
url = value;
else if(key == 66)
loginStatus = Integer.parseInt(value);
}
if(pack.getStatus() == 0xffffffffL)
{
if(loginStatus == 13 || loginStatus == 3)
listener.connectFailed(this, "Invalid username and/or password.");
else if(loginStatus == 14)
listener.connectFailed(this, "Your account is locked. Visit " + url + " to reactivate it.");
currentStatus = loginStatus;
}
}
/**
* Process a packet related to conferences.
*
* @param pack the packet containing the data.
*/
private void processConference(Packet pack)
{
String msg = null;
Buddy host = null;
Buddy who = null;
String room = null;
String id = null;
Vector members = new Vector();
int size = pack.getDataSize();
for(int i = 0; i < size; i++)
{
int key = pack.getKey(i);
String value = pack.getValue(i);
if(key == 50)
host = new Buddy(this, value);
if(key == 52) // invite
members.add(new Buddy(this, value));
if(key == 53) // logon
who = new Buddy(this, value);
if(key == 54) // decline
who = new Buddy(this, value);
if(key == 55) // unavailable (status == 2)
who = new Buddy(this, value);
if(key == 56) // logoff
who = new Buddy(this, value);
if(key == 57)
room = value;
if(key == 58) // join message
msg = value;
if(key == 14) // decline/conf message
msg = value;
if (key == 16) // error
msg = value;
if(key == 1) // my id
id = value;
if(key == 3) // message sender
who = new Buddy(this, value);
}
if(room == null)
return;
if(host != null && !members.contains(host))
members.add(host);
// invite, decline, join, left, message -> status == 1
switch(pack.getService())
{
case SERVICE_CONFINVITE:
if(pack.getStatus() == 2)
;
else if(!members.isEmpty())
{
try
{
Buddy[] buddies = (Buddy[]) members.toArray(new Buddy[0]);
Conference conf = new Conference(this, host, buddies);
Response resp = listener.conferenceInvitationReceived(conf, msg);
processConferenceResponse(resp, buddies, room);
}
catch(UnsupportedOperationException e)
{
// this exception will never be thrown
}
catch(IllegalArgumentException e)
{
// this exception will never be thrown
}
}
else if(msg != null)
{
listener.protocolMessageReceived(this, Util.parseYahooMessage(msg));
}
break;
case SERVICE_CONFADDINVITE:
if(pack.getStatus() == 2)
;
else
{
try
{
Buddy[] buddies = (Buddy[]) members.toArray(new Buddy[0]);
Conference conf = new Conference(this, host, buddies);
Response resp = listener.conferenceInvitationReceived(conf, msg);
processConferenceResponse(resp, buddies, room);
}
catch(UnsupportedOperationException e)
{
// this exception will never be thrown
}
catch(IllegalArgumentException e)
{
// this exception will never be thrown
}
}
break;
case SERVICE_CONFDECLINE:
if(who != null)
{
Conference conf = (Conference) roomConfMap.get(room);
if(conf != null)
listener.conferenceInvitationDeclined(conf, who, msg);
}
break;
case SERVICE_CONFLOGON:
if(who != null)
{
Conference conf = (Conference) roomConfMap.get(room);
if(conf != null)
listener.conferenceInvitationAccepted(conf, who);
}
break;
case SERVICE_CONFLOGOFF:
if(who != null)
{
Conference conf = (Conference) roomConfMap.get(room);
if(conf != null)
{
conf.removeParticipant(who);
listener.conferenceParticipantLeft(conf, who);
}
}
break;
case SERVICE_CONFMSG:
if(who != null)
{
Conference conf = (Conference) roomConfMap.get(room);
if(conf != null)
listener.conferenceMessageReceived(conf, who, Util.parseYahooMessage(msg));
}
break;
}
}
/**
* Process a responce from Yahoo for buddy add.
*
* @param pack the response packet from Yahoo.
*/
private void processBuddyAdd(Packet pack)
{
String who = null;
String where = null;
int status = 0;
int size = pack.getDataSize();
for(int i = 0; i < size; i++)
{
int key = pack.getKey(i);
String value = pack.getValue(i);
if(key == 7)
who = value;
if(key == 65)
where = value;
if(key == 66)
status = Integer.parseInt(value);
}
if(who == null)
return;
Buddy buddy = new Buddy(this, who, where);
if(status == 0)
listener.buddyAdded(buddy);
else if(status == 2)
listener.buddyAddFailed(buddy, "This user is already present in your buddy list");
else if(status == 3)
listener.buddyAddFailed(buddy, "This user does not exist");
else
listener.buddyAddFailed(buddy, "Operation failed: error code = " + status);
}
/**
* Process a response from Yahoo for buddy delete.
*
* @param pack the resonse from Yahoo.
*/
private void processBuddyDelete(Packet pack)
{
String who = null;
String where = null;
boolean success = false;
int size = pack.getDataSize();
for(int i = 0; i < size; i++)
{
int key = pack.getKey(i);
String value = pack.getValue(i);
if(key == 7)
who = value;
if(key == 65)
where = value;
if(key == 66)
success = Integer.parseInt(value) == 0;
}
if(who == null)
return;
Buddy buddy = new Buddy(this, who, where);
if(success)
listener.buddyDeleted(buddy);
else
listener.buddyDeleteFailed(buddy, "This user is not in your buddy list");
}
/**
* Process a response from Yahoo for buddy ignore.
*
* @param pack the resonse from Yahoo.
*/
private void processBuddyIgnore(Packet pack)
{
String who = null;
int status = 0;
int operation = 0;
int size = pack.getDataSize();
for(int i = 0; i < size; i++)
{
int key = pack.getKey(i);
String value = pack.getValue(i);
if(key == 0)
who = value;
if(key == 13) /* 1 == ignore, 2 == unignore */
operation = Integer.parseInt(value);
if(key == 66)
status = Integer.parseInt(value);
}
if(who == null)
return;
Buddy buddy = new Buddy(this, who);
/*
* status
* 0 - ok
* 2 - already in ignore list, could not add
* 3 - not in ignore list, could not delete
* 12 - is a buddy, could not add
*/
switch(status)
{
case 0:
if(operation == 1) // ignore
listener.buddyIgnored(buddy);
else if(operation == 2) // unignore
listener.buddyUnignored(buddy);
break;
case 2:
listener.buddyIgnoreFailed(buddy, "This buddy is already ignored.");
break;
case 3:
listener.buddyUnignoreFailed(buddy, "This buddy wasn't ignored.");
break;
case 12:
listener.buddyIgnoreFailed(buddy, "Please remove this buddy from your buddy list and then try again.");
break;
}
}
/**
* Notify Yahoo with a response from the user for a conference invitation.
*
* @param resp the response from the user.
* @param buddies all buddies part of this conference.
* @param room the conference room name.
*/
private void processConferenceResponse(Response resp, Buddy[] buddies, String room)
{
if(resp.isAccepted())
{
Packet retPack = new Packet(SERVICE_CONFLOGON, STATUS_AVAILABLE, sessionID);
retPack.putData(1, yahooID);
for(int i = 0; i < buddies.length; i++)
retPack.putData(3, buddies[i].getUsername());
retPack.putData(57, room);
sendToWriterThread(retPack);
}
else
{
Packet retPack = new Packet(SERVICE_CONFDECLINE, STATUS_AVAILABLE, sessionID);
retPack.putData(1, yahooID);
for(int i = 0; i < buddies.length; i++)
retPack.putData(3, buddies[i].getUsername());
retPack.putData(57, room);
retPack.putData(14, resp.getMessage());
sendToWriterThread(retPack);
}
}
/**
* Send a packet to the writer thread for despatch to Yahoo server.
*
* @param pack the packet to be sent.
*/
private void sendToWriterThread(Packet pack)
{
synchronized(writeBuffer)
{
writeBuffer.add(pack);
writeBuffer.notify();
}
}
/**
* Verify that we are logged in to Yahoo server, otherwise throw an exception.
*
* @throws IllegalStateException if not logged in to Yahoo
*/
private void checkConnected() throws IllegalStateException
{
if(!connected)
throw new IllegalStateException("Not connected to Yahoo yet");
}
/**
* Checks whether this protocol supports buddy aliases.
*
* @return <code>false</code>
*/
public boolean isBuddyNameAliasSupported()
{
return false;
}
/**
* Yahoo module currently does not support aliases. This method
* throws an UnSupportedOperationException.
*/
public void changeBuddyAlias(Buddy buddy, String alias) throws UnsupportedOperationException
{
throw new UnsupportedOperationException("Yahoo module does not support buddy aliases.");
}
}