Package hamsam.protocol.yahoo

Source Code of hamsam.protocol.yahoo.YahooProtocol

/*
* 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.");
  }
}
TOP

Related Classes of hamsam.protocol.yahoo.YahooProtocol

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.