/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.java.sip.communicator.slick.protocol.icq; import java.beans.*; import java.io.*; import java.util.*; import net.java.sip.communicator.service.protocol.icqconstants.*; import net.java.sip.communicator.util.*; import net.kano.joscar.*; import net.kano.joscar.flapcmd.*; import net.kano.joscar.snac.*; import net.kano.joscar.snaccmd.*; import net.kano.joscar.snaccmd.conn.*; import net.kano.joscar.snaccmd.error.*; import net.kano.joscar.snaccmd.icbm.*; import net.kano.joscar.snaccmd.loc.*; import net.kano.joscar.snaccmd.ssi.*; import net.kano.joscar.tlv.*; import net.kano.joustsim.*; import net.kano.joustsim.oscar.*; import net.kano.joustsim.oscar.oscar.service.bos.*; import net.kano.joustsim.oscar.oscar.service.buddy.*; import net.kano.joustsim.oscar.oscar.service.icbm.*; import net.kano.joustsim.oscar.oscar.service.ssi.*; /** * An utility that we use to test AIM/ICQ implementations of the * ProtocolProviderService. This class implements functionality such as * verifying whether a particular user is currently on-line, single message * reception and single message sending, and other features that help us verify * that icq implementations behave properly. * * @author Emil Ivov * @author Damian Minkov */ public class IcqTesterAgent { private static final Logger logger = Logger.getLogger(IcqTesterAgent.class); /** * We use this field to determine whether registration has gone ok. */ private IcbmService icbmService = null; /** * The AimConnection that the IcqEchoTest user has established with the icq * service. */ private AimConnection conn = null; /** * We use it to wait for registration completion. */ private Object connectionLock = new Object(); /** * This one should actually be in joscar. But since it isn't we might as * well define it here. */ public static final long ICQ_ONLINE_MASK = 0x01000000L; /** * Indicates whether the tester agent is registered (signed on) the icq * service. */ private boolean registered = false; /** * The icqUIN (or AIM screenname) that the tester agent should use to log * onto the aim service. */ private Screenname icqUIN = null; private AuthCmdFactory authCmdFactory = new AuthCmdFactory(); /** * The IcqTesterAgent constructor that would create a tester agent instance, * prepared to sign on line with the specified icq uin. * * @param icqUinString the icq uin that the tester agent should use when * signing online. */ IcqTesterAgent(String icqUinString) { this.icqUIN = new Screenname(icqUinString); } /** * Registers the echo test user on icq so that it could receive test * messages from icq/aim tested stacks. * * @param password the password corresponding to the icq uin specified in * the constructor. * @return true if registration was successful and false otherwise. */ public boolean register(String password) { if(registered || IcqSlickFixture.onlineTestingDisabled) return true; DefaultAppSession session = new DefaultAppSession(); AimSession aimSession = session.openAimSession(icqUIN); aimSession.openConnection( new AimConnectionProperties( icqUIN, password)); conn = aimSession.getConnection(); conn.addStateListener(new AimConnStateListener()); conn.getBuddyInfoManager().addGlobalBuddyInfoListener(new GlobalBuddyListener()); conn.connect(); synchronized(connectionLock){ try{connectionLock.wait(10000);}catch(InterruptedException ex){} } if (icbmService == null){ //maybe throw an exception here return (registered = false); } //conn.getSsiService() // .getBuddyList().addRetroactiveLayoutListener(new RetroListener()); conn.getBuddyService().addBuddyListener(new BuddyListener()); return (registered = true ); } /** * The method would delete all existing buddies on its current server stored * contact list and fill it in again as specified by the listContents * hashtable. IMPORTANT - Note that this method would completely ERASE * any existing contacts in the account currently used by the tester agent * to log in. * * @param listContents a Hashtable that must contain a description of the * contact list such as the caller would like it to be (groupnNawhere copy * of the initialized contact list would be stored, mapping group names to * lists of contact identifiers * (screennames). */ public void initializeBuddyList(Hashtable<String, List<String>> listContents) { logger.debug("Will Create the following contact list:\n"+ listContents); MutableBuddyList joustSimBuddyList = conn.getSsiService().getBuddyList(); //First empty the existing contact list. List<? extends Group> groups = joustSimBuddyList.getGroups(); Iterator<? extends Group> groupsIter = groups.iterator(); while (groupsIter.hasNext()) { Group group = groupsIter.next(); joustSimBuddyList.deleteGroupAndBuddies(group); } //Now insert all items from the listContents hashtable if they're not //already there. Enumeration<String> newGroupsEnum = listContents.keys(); LayoutEventCollector evtCollector = new LayoutEventCollector(); //go over all groups in the contactsToAdd table while (newGroupsEnum.hasMoreElements()) { String groupName = newGroupsEnum.nextElement(); logger.debug("Will add group " + groupName); //first clear any previously registered groups and then add the //layout listenet to the buddy list. evtCollector.addedGroups.removeAllElements(); joustSimBuddyList.addLayoutListener(evtCollector); joustSimBuddyList.addGroup(groupName); //wait for a notification from the aim server that the group has //been added evtCollector.waitForANewGroup(10000); joustSimBuddyList.removeLayoutListener(evtCollector); //now see if it all worked ok and if yes get a ref to the newly //added group. MutableGroup newlyCreatedGroup = null; if (evtCollector.addedGroups.size() == 0 || (newlyCreatedGroup = (MutableGroup)evtCollector.addedGroups.get(0)) == null) throw new NullPointerException( "Couldn't create group " + groupName); Iterator<String> contactsToAddToThisGroup = listContents.get(groupName).iterator(); while (contactsToAddToThisGroup.hasNext()) { String screenname = contactsToAddToThisGroup.next(); //remove all buddies captured by the event collector so far //then register it as a listener evtCollector.addedBuddies.removeAllElements(); joustSimBuddyList.addLayoutListener(evtCollector); logger.debug("Will add buddy " + screenname); newlyCreatedGroup.addBuddy( screenname); //wait for a notification from the aim server that the buddy has //been added evtCollector.waitForANewBuddy(10000); joustSimBuddyList.removeLayoutListener(evtCollector); //now see if it all worked ok and if yes get a ref to the newly //added group. if (evtCollector.addedBuddies.size() == 0 || evtCollector.addedBuddies.get(0) == null) { //We didn't get an event ... let's see that the new buddy //is really not there and if that is the case throw an exs if(findBuddyInBuddyList(joustSimBuddyList, screenname) == null) throw new NullPointerException( "Couldn't add buddy " + screenname); } } } } /** * Sends to <tt>buddy</tt> a notification that our typing state has now * changed to indicated by <tt>notif</tt>. * @param buddy the screenname of the budy that we'd like to notify. * @param state the typing state that we'd like to send to the specified * buddy. */ public void sendTypingNotification(String buddy, TypingState state) { conn.getIcbmService().getImConversation(new Screenname(buddy)) .setTypingState(state); } /** * Adds a typing listener that would receive joust sim based on typing * notifications received from <tt>buddy</tt> * @param buddy the screenname of the buddy that we'd like to receive * notifications from. * @param l the <tt>ConversationListener</tt> (which also needs to be a * TypingListener) that would be registered for typing notifications * @throws ClassCastException if <tt>l</tt> is only an instance of * <tt>ConversationListener</tt> without implementing * <tt>TypingListener</tt> */ public void addTypingStateInfoListenerForBuddy( String buddy, ConversationListener l) throws ClassCastException { if (! (l instanceof TypingListener)) throw new ClassCastException( "In order to receive typing notifications a typing listener " +"needs to also implement " + TypingListener.class.getName()); conn.getIcbmService().getImConversation(new Screenname(buddy)) .addConversationListener(l); } /** * Removes <tt>l</tt> so that it won't receive further typing events for * <tt>buddy</tt>. * @param buddy the screenname of the buddy that we'd like to stop receiving * notifications from. * @param l the <tt>ConversationListener</tt> to remove */ public void removeTypingStateInfoListenerForBuddy( String buddy, ConversationListener l) { conn.getIcbmService().getImConversation(new Screenname(buddy)) .removeConversationListener(l); } /** * Sends <tt>body</tt> to <tt>buddy</tt> as an instant message * @param buddy the screenname of the budy that we'd like to send our msg to. * @param body the content of the message to send. */ public void sendMessage(String buddy, String body) { conn.getIcbmService().getImConversation(new Screenname(buddy)) .sendMessage(new SimpleMessage(body)); //the aim server doesn't like fast consecutice messages try{Thread.sleep(600);}catch (InterruptedException ex){} } /** * Registers <tt>listener</tt> as an ImConversationListener so that it would * receive messages coming from <tt>buddy</tt>. * @param buddy the screenname of the buddy that we'd like to listen to. * @param listener the <tt>ImConversationListener</tt> to register. */ public void addConversationListener(String buddy, ConversationListener listener) { conn.getIcbmService().getImConversation(new Screenname(buddy)) .addConversationListener(listener); } /** * Removes <tt>listener</tt> as an ImConversationListener so that it won't * receive further messages coming from <tt>buddy</tt>. * @param buddy the screenname of the buddy that we'd like to unregister * from. * @param listener the <tt>ImConversationListener</tt> to remove. */ public void removeConversationListener(String buddy, ConversationListener listener) { conn.getIcbmService().getImConversation(new Screenname(buddy)) .removeConversationListener(listener); } /** * Tries to find the buddy with screenname screenname in the given buddy * list * @param list the BuddyList where to look for the buddy * @param screenname the screen name of the buddy we're looking for * @return a ref to the Buddy we're looking for or null if no such buddy * was found. */ private Buddy findBuddyInBuddyList(BuddyList list, String screenname) { Iterator<? extends Group> groups = list.getGroups().iterator(); while (groups.hasNext()) { Group group = groups.next(); for (Buddy buddy : group.getBuddiesCopy()) { if(buddy.getScreenname().getFormatted().equals(screenname)) return buddy; } } return null; } /** * Unregisters from (signs off) the ICQ service. */ public void unregister() { if(!registered) return; conn.disconnect(true); registered = false; } /** * All this listener does is wait for an event coming from oscar.jar and * indicating that registration has been successful. */ private class AimConnStateListener implements StateListener { public synchronized void handleStateChange(StateEvent event) { synchronized( connectionLock ) { AimConnection conn = event.getAimConnection(); logger.debug("EchoUser change state from:" + event.getOldState() + " to " + event.getNewState()); if (event.getNewState() == State.ONLINE) { icbmService = conn.getIcbmService(); connectionLock.notifyAll(); icbmService.getOscarConnection().getSnacProcessor(). getCmdFactoryMgr().getDefaultFactoryList(). registerAll(authCmdFactory); icbmService.getOscarConnection().getSnacProcessor(). getCmdFactoryMgr().getDefaultFactoryList(). registerAll(FullUserInfoCmd.getCommandFactory()); icbmService.getOscarConnection().getSnacProcessor(). addGlobalResponseListener(authCmdFactory); } else if (event.getNewState() == State.FAILED || event.getNewState() == State.DISCONNECTED) { logger.error("AIM Connection DISCONNECTED for " + getIcqUIN() + "!"); connectionLock.notifyAll(); } } } } /** * Returns the on-line status of the user with the specified screenname. * @param screenname the screenname of the user whose status we're * interested in. * @return a PresenceStatus (one of the IcqStatusEnum static fields) * indicating the the status of the specified buddy. * * @throws java.lang.IllegalStateException if the method is called before * the IcqTesterAgent has been registered with (signed on) the * IcqService. */ public IcqStatusEnum getBuddyStatus(String screenname) throws IllegalStateException { if ( !registered ) throw new IllegalStateException( "You need to register before querying a buddy's status"); StatusResponseRetriever responseRetriever = new StatusResponseRetriever(); GetInfoCmd getInfoCmd = new GetInfoCmd(GetInfoCmd.CMD_NEW_GET_INFO | GetInfoCmd.FLAG_AWAYMSG | GetInfoCmd.FLAG_INFO, new Screenname(screenname).getFormatted()); conn.getInfoService().getOscarConnection() .sendSnacRequest(getInfoCmd, responseRetriever); synchronized(responseRetriever.waitingForResponseLock) { try{ logger.debug("waiting to receive status for " + screenname); responseRetriever.waitingForResponseLock.wait(100000); } catch (InterruptedException ex){ logger.debug("Couldn't wait upon a response retriver", ex); } } logger.debug("Done. we'll return status " + responseRetriever.status); return responseRetriever.status == null ? IcqStatusEnum. OFFLINE : responseRetriever.status; } /** * Converts the specified icqstatus to on of the ICQ_STATUS string fields * of this class * @param icqStatus the icqStatus as retured in FullUserInfo by the joscar * stack * @param returnOnMinus1 specifies the value that should be returned if * icqStatus is on its default joust sim value "-1". * @return the IcqStatusEnum instance that best corresponds to the "long" * icqStatus parameter. */ private static IcqStatusEnum icqStatusLongToString(long icqStatus, IcqStatusEnum returnOnMinus1) { if (icqStatus == -1 ) { return returnOnMinus1; } else if ( (icqStatus & FullUserInfo.ICQSTATUS_AWAY ) != 0) { return IcqStatusEnum.AWAY; } else if ( (icqStatus & FullUserInfo.ICQSTATUS_DND ) != 0) { return IcqStatusEnum.DO_NOT_DISTURB; } else if ( (icqStatus & FullUserInfo.ICQSTATUS_FFC ) != 0) { return IcqStatusEnum.FREE_FOR_CHAT; } else if ( (icqStatus & FullUserInfo.ICQSTATUS_INVISIBLE ) != 0) { return IcqStatusEnum.INVISIBLE; } else if ( (icqStatus & FullUserInfo.ICQSTATUS_NA ) != 0) { return IcqStatusEnum.NOT_AVAILABLE; } else if ( (icqStatus & FullUserInfo.ICQSTATUS_OCCUPIED ) != 0) { return IcqStatusEnum.OCCUPIED; } else if ((icqStatus & ICQ_ONLINE_MASK) == 0 ) { return IcqStatusEnum.OFFLINE; } return IcqStatusEnum.ONLINE; } /** * The StatusResponseRetriever is used as a one time handler for responses * to requests sent through the sendSnacRequest method of one of joustsim's * Services. The StatusResponseRetriever would ignore everything apart from * the first response, which will be stored in the status field. In the * case of a timeout, the status would remain null. Both a response and * a timeout would make the StatusResponseRetriever call its notifyAll * method so that those that are waiting on it be notified. */ private static class StatusResponseRetriever extends SnacRequestAdapter { private boolean ran = false; private IcqStatusEnum status = null; public Object waitingForResponseLock = new Object(); @Override public void handleResponse(SnacResponseEvent e) { SnacCommand snac = e.getSnacCommand(); logger.debug("Received a response to our status request: " + snac); synchronized(this) { if (ran) return; ran = true; } if (snac instanceof UserInfoCmd) { UserInfoCmd uic = (UserInfoCmd) snac; FullUserInfo userInfo = uic.getUserInfo(); if (userInfo != null) { //if we got a UserInfoCmd and not a SnacError then we //are certain the user is not offline and we specify //the second (defaultReturn) param accordingly. this.status = icqStatusLongToString(userInfo.getIcqStatus(), IcqStatusEnum.ONLINE); logger.debug("status is " + status +"=" + userInfo.getIcqStatus()); List<ExtraInfoBlock> eInfoBlocks = userInfo.getExtraInfoBlocks(); if(eInfoBlocks != null){ System.out.println("printing extra info blocks (" + eInfoBlocks.size() + ")"); for (ExtraInfoBlock block : eInfoBlocks) { System.out.println( "block.toString()=" + block.toString()); } } else System.out.println("no extra info."); synchronized(waitingForResponseLock){ waitingForResponseLock.notifyAll(); } } } else if( snac instanceof SnacError) { //this is most probably a CODE_USER_UNAVAILABLE, but //whatever it is it means that to us the buddy in question //is as good as offline so leave status at -1 and notify. this.status = IcqStatusEnum.OFFLINE; logger.debug("status is" + status); synchronized(waitingForResponseLock){ waitingForResponseLock.notifyAll(); } } } @Override public void handleTimeout(SnacRequestTimeoutEvent event) { synchronized(this) { if (ran) return; ran = true; } synchronized(waitingForResponseLock) { waitingForResponseLock.notifyAll(); } } } /** * Returns a string representation of the UIN used by the tester agent to * signon on the icq network. * @return a String containing the icq UIN used by the tester agent to * signon on the AIM network. */ public String getIcqUIN() { return icqUIN.getFormatted(); } /** * Causes the tester agent to enter the specified icq status and returns * only after receiving a notification from tha AIM server that the new * status has been successfully published. * @param icqStatus the icq status to enter * @return true if the status change has succeeded and the corresponding bos * event was received and false otherwise. */ public boolean enterStatus(long icqStatus) { //first init the guy that'll tell us that it's ok. BosEventNotifier bosEventNotifier = new BosEventNotifier(); conn.getBosService().addMainBosServiceListener(bosEventNotifier); //do the state switch synchronized(bosEventNotifier.infoLock ){ conn.getBosService().getOscarConnection().sendSnac(new SetExtraInfoCmd(icqStatus)); try{bosEventNotifier.infoLock.wait(10000);} catch (InterruptedException ex){logger.debug("Strange!");} conn.getBosService().removeMainBosServiceListener(bosEventNotifier); if(bosEventNotifier.lastUserInfo == null){ logger.debug("Status change was not confirmed by AIM server."); return false; } return true; } } /** * Queries the AIM server for our own status and returns accordingly. * @return the IcqStatusEnum instance corresponding to our own status. */ public IcqStatusEnum getPresneceStatus() { return getBuddyStatus(getIcqUIN()); } public net.java.sip.communicator.slick.protocol.icq.IcqTesterAgent. AuthCmdFactory getAuthCmdFactory() { return authCmdFactory; } /** * A bos listener is the way of receiving notifications of changes in our * own state or info. This class allows others to wait() for Bos events. * The class defines an extraInfoLock and an infoLock both of which are * notifyAll()ed whenever a corresponding event is received */ private static class BosEventNotifier implements MainBosServiceListener { public Object extraInfoLock = new Object(); public Object infoLock = new Object(); public FullUserInfo lastUserInfo = null; /** * Saves the extraInfos list and calls a notifyAll on the extraInfoLock * @param extraInfos the list of extraInfos that the AIM server sent */ public void handleYourExtraInfo(List<ExtraInfoBlock> extraInfos) { logger.debug("Bosiat.extrainfo=" + extraInfos); synchronized(extraInfoLock){ extraInfoLock.notifyAll(); } } /** * Saves the full user info and calls a notifyAll on the infoLock * @param service the source bos service * @param userInfo the FullUserInfo as received from the aim server. */ public void handleYourInfo(MainBosService service, FullUserInfo userInfo) { logger.debug("Bosiat.yourinfo=" + userInfo); synchronized(infoLock){ lastUserInfo = userInfo; infoLock.notifyAll(); } } } /** * We use this class to collect and/or wait for events generated upon * modification of a server stored contact list. */ private class LayoutEventCollector implements BuddyListLayoutListener { public Vector<Group> addedGroups = new Vector<Group>(); public Vector<Buddy> addedBuddies = new Vector<Buddy>(); public Vector<Buddy> removedBuddies = new Vector<Buddy>(); /** * The method would wait until at least one new buddy is collected by * this collector or the specified number of milliseconds have passed. * @param milliseconds the maximum number of millisseconds to wait for * a new buddy before simply bailing out. */ public void waitForANewBuddy(int milliseconds) { synchronized (this.addedBuddies){ if ( !addedBuddies.isEmpty()){ return; } try{ this.addedBuddies.wait(milliseconds); } catch (InterruptedException ex){ logger.warn("A strange thing happened while waiting", ex); } } } /** * The method would wait until at least one new group is collected by * this collector or the specified number of milliseconds have passed. * @param milliseconds the maximum number of millisseconds to wait for * a new group before simply bailing out. */ public void waitForANewGroup(int milliseconds) { synchronized (this.addedGroups){ if ( !addedGroups.isEmpty()){ return; } try{ this.addedGroups.wait(milliseconds); } catch (InterruptedException ex){ logger.warn("A strange thing happened while waiting", ex); } } } public void waitForRemovedBuddy(int milliseconds) { synchronized (this.removedBuddies) { if (!removedBuddies.isEmpty()) { return; } try { this.removedBuddies.wait(milliseconds); } catch (InterruptedException ex) { logger.warn("A strange thing happened while waiting", ex); } } } /** * Registers a reference to the group that has just been created and * call a notifyAll() on this. * @param list the BuddyList where this is happening (unused). * @param oldItems List (unused) * @param newItems List (unused) * @param group a reference to the Group that has just been created. * @param buddies a List of the buddies created by this group (unused). */ public void groupAdded(BuddyList list, List<? extends Group> oldItems, List<? extends Group> newItems, Group group, List<? extends Buddy> buddies) { logger.debug("A group was added gname is=" + group.getName()); synchronized(this.addedGroups){ this.addedGroups.add(group); this.addedGroups.notifyAll(); } } /** * Registers a reference to the newly added buddy and calls a * notifyAll() on this. * @param list the BuddyList where this is happening (unused). * @param group the Group where the buddy was added (unused). * @param oldItems List (unused) * @param newItems List (unused) * @param buddy a reference to the newly added Buddy. */ public void buddyAdded(BuddyList list, Group group, List<? extends Buddy> oldItems, List<? extends Buddy> newItems, Buddy buddy) { logger.debug("A buddy ("+buddy.getScreenname() +")was added to group " + group.getName()); synchronized(this.addedBuddies){ this.addedBuddies.add(buddy); this.addedBuddies.notifyAll(); } } //we don't use this one so far. public void groupsReordered( BuddyList list, List<? extends Group> oldOrder, List<? extends Group> newOrder) { logger.debug("groupsReordered"); } //we don't use this one so far. public void groupRemoved( BuddyList list, List<? extends Group> oldItems, List<? extends Group> newItems, Group group) { logger.debug("removedGroup="+group.getName()); } // we don't use this one so far. public void buddyRemoved(BuddyList list, Group group, List<? extends Buddy> oldItems, List<? extends Buddy> newItems, Buddy buddy) { logger.debug("removed buddy=" + buddy); } //we don't use this one public void buddiesReordered(BuddyList list, Group group, List<? extends Buddy> oldBuddies, List<? extends Buddy> newBuddies) { logger.debug("buddiesReordered in group " + group.getName()); } } //------------- other utility stuff that is not really very used --------------- private class BuddyListener implements BuddyServiceListener{ public void gotBuddyStatus(BuddyService service, Screenname buddy, FullUserInfo info) { System.out.println("BuddyListener.gotBuddyStatus " + buddy.toString() +" and status is : " + info.getIcqStatus()); List<ExtraInfoBlock> eInfoBlocks = info.getExtraInfoBlocks(); if (eInfoBlocks != null) { System.out.println("printing extra info blocks (" + eInfoBlocks.size() + ")"); for (ExtraInfoBlock block : eInfoBlocks) System.out.println("block.toString()=" + block); } else logger.trace("no extra info."); } public void buddyOffline(BuddyService service, Screenname buddy) { System.out.println("BuddyListener.buddyOffline " + buddy.toString()); } } private class GlobalBuddyListener implements GlobalBuddyInfoListener { public void buddyInfoChanged(BuddyInfoManager manager, Screenname buddy, BuddyInfo info, PropertyChangeEvent event) { System.out.println("GlobalBuddyListener.buddyInfoChanged: " + "propN= " + event.getPropertyName() + " for buddy: " + buddy.toString() + " info.isOnline()= " + info.isOnline() + " info.statusMessage=" + info.getStatusMessage() + " info.statusMessage=" + info.getAwayMessage() ); System.out.println("info=" + info); } public void newBuddyInfo(BuddyInfoManager manager, Screenname buddy, BuddyInfo info) { System.out.println( "GlobalBuddyListener.newBuddyInfo: " + buddy.toString() + " info.isOnline()= " + info.isOnline() + " info.statusMessage=" + info.getStatusMessage() + " info.statusMessage=" + info.getAwayMessage() ); System.out.println("info=" + info); } public void receivedStatusUpdate(BuddyInfoManager manager, Screenname buddy, BuddyInfo info) { System.out.println("GlobalBuddyListener.receivedStatusUpdate " + buddy.toString() + " info.isOnline()= " + info.isOnline() + " info.statusMessage=" + info.getStatusMessage() + " info.statusMessage=" + info.getAwayMessage() ); System.out.println("info=" + info); } } // private class ServiceListener // implements OpenedServiceListener // { // public void closedServices(AimConnection conn, Collection services) // { // } // // public void openedServices(AimConnection conn, Collection services) // { // conn.getBuddyInfoManager() // .addGlobalBuddyInfoListener(new GlobalBuddyListener()); // conn.getBuddyService().addBuddyListener(new BuddyListener()); // } // } private class RetroListener implements BuddyListLayoutListener, GroupListener { public void groupsReordered(BuddyList list, List<? extends Group> oldOrder, List<? extends Group> newOrder) { System.out.println(" RetroListener.groupReordered"); } public void groupAdded(BuddyList list, List<? extends Group> oldItems, List<? extends Group> newItems, Group group, List<? extends Buddy> buddies) { System.out.println("RetroListener.groupAdded"); System.out.println(" group.name is="+group.getName()); System.out.println("index="+newItems.indexOf(group)); for (int i = 0; i < buddies.size(); i++){ System.out.println(" buddy is=" +((Buddy)buddies.get(i)) .getScreenname().getFormatted()); Buddy b = buddies.get(i); conn.getBuddyInfoTracker().addTracker(b.getScreenname(), new BuddyInfoTrackerListener(){}); } group.addGroupListener(this); } public void groupRemoved(BuddyList list, List<? extends Group> oldItems, List<? extends Group> newItems, Group group) { System.out.println(" RetroListener.groupRemoved"); } public void buddyAdded(BuddyList list, Group group, List<? extends Buddy> oldItems, List<? extends Buddy> newItems, Buddy buddy) { System.out.println(" RetroListener.buddyAdded="+buddy); } public void buddyRemoved(BuddyList list, Group group, List<? extends Buddy> oldItems, List<? extends Buddy> newItems, Buddy buddy) { System.out.println(" RetroListener.buddyRemoved"+buddy); } public void buddiesReordered(BuddyList list, Group group, List<? extends Buddy> oldBuddies, List<? extends Buddy> newBuddies) { System.out.println(" RetroListener.buddiesReordered"); } public void groupNameChanged(Group group, String oldName, String newName) { System.out.println( " RetroListener.GroupListener.groupNameChanged. old is=" + oldName + " new is=" + newName + " group is="+group.getName()); System.out.println("GroupContains="+group.getBuddiesCopy()); } } ////////////////////////// ugly unused testing code ////////////////////////// private RetroListener rl = new RetroListener(); public static void main(String[] args) throws Throwable { java.util.logging.Logger.getLogger("net.kano") .setLevel(java.util.logging.Level.FINEST); IcqTesterAgent icqtests = new IcqTesterAgent("319305099"); if (!icqtests.register("6pC0mmtt")) { System.out.println("registration failed"); ; return; } Thread.sleep(1000); icqtests.conn.getSsiService() .getBuddyList().addRetroactiveLayoutListener(icqtests.rl); Thread.sleep(1000); System.out.println("\n\nr u ready?"); Thread.sleep(3000); java.util.logging.Logger.getLogger("net.kano").setLevel(java.util.logging.Level.FINEST); MutableBuddyList list = icqtests.conn.getSsiService().getBuddyList(); MutableGroup grpGroup = null; Buddy buddyToMove = null; for (Group group : list.getGroups()) { if (group.getName().equals("grp")) grpGroup = (MutableGroup) group; List<? extends Buddy> buddies = group.getBuddiesCopy(); System.out.println("Printing buddies for group " + group.getName()); Thread.sleep(1000); for (Buddy buddy : buddies) { System.out.println(buddy.getScreenname()); if (buddy.getScreenname().getFormatted().equals("201345337")) buddyToMove = buddy; } } System.out.println();System.out.println();System.out.println();System.out.println();System.out.println();System.out.println();System.out.println();System.out.println();System.out.println(); System.out.println("will move buddyyyyyyyyyy"); Thread.sleep(5000); List<Buddy> listToMove = new ArrayList<Buddy>(); listToMove.add(buddyToMove); list.moveBuddies(listToMove, grpGroup); System.out.println("MOved i sega triabva da doidat eventi "); Thread.sleep(50000); //find the buddy again. Buddy movedBuddy = null; for (Group group : list.getGroups()) { List<? extends Buddy> buddies = group.getBuddiesCopy(); for (Buddy buddy : buddies) { if (buddy.getScreenname().getFormatted().equals("201345337")) movedBuddy = buddy; } } if (buddyToMove == movedBuddy) System.out.println("hahaha"); } public void deleteBuddy(String screenname) { logger.debug("Will delete buddy : " + screenname); MutableBuddyList joustSimBuddyList = conn.getSsiService().getBuddyList(); LayoutEventCollector evtCollector = new LayoutEventCollector(); joustSimBuddyList.addLayoutListener(evtCollector); List<? extends Group> grList = joustSimBuddyList.getGroups(); boolean isDeleted = false; Iterator<? extends Group> iter = grList.iterator(); while (iter.hasNext()) { MutableGroup item = (MutableGroup) iter.next(); List<? extends Buddy> bs = item.getBuddiesCopy(); Iterator<? extends Buddy> iter1 = bs.iterator(); while (iter1.hasNext()) { Buddy b = iter1.next(); if(b.getScreenname().getFormatted().equals(screenname)) { item.deleteBuddy(b); isDeleted = true; } } if(isDeleted) break; } if(isDeleted) evtCollector.waitForRemovedBuddy(10000); joustSimBuddyList.removeLayoutListener(evtCollector); } public void addBuddy(String screenname) { logger.debug("Will add buddy : " + screenname); MutableBuddyList joustSimBuddyList = conn.getSsiService().getBuddyList(); List<? extends Group> grList = joustSimBuddyList.getGroups(); Iterator<? extends Group> iter = grList.iterator(); while (iter.hasNext()) { MutableGroup item = (MutableGroup) iter.next(); logger.debug("group : " + item); List<? extends Buddy> bs = item.getBuddiesCopy(); Iterator<? extends Buddy> iter1 = bs.iterator(); while (iter1.hasNext()) { Object b = iter1.next(); logger.debug("buddy : " + b); } } MutableGroup targetGroup = null; if(grList.size() < 1) { logger.debug("No groups! Will stop now"); LayoutEventCollector evtCollector = new LayoutEventCollector(); String groupName = "test-group"; logger.debug("Will add group " + groupName); joustSimBuddyList.addLayoutListener(evtCollector); joustSimBuddyList.addGroup(groupName); //wait for a notification from the aim server that the group has //been added evtCollector.waitForANewGroup(10000); joustSimBuddyList.removeLayoutListener(evtCollector); //now see if it all worked ok and if yes get a ref to the newly //added group. if (evtCollector.addedGroups.size() == 0 || (targetGroup = (MutableGroup)evtCollector.addedGroups.get(0)) == null) throw new NullPointerException("Couldn't create group " + groupName); } else { targetGroup = (MutableGroup)grList.get(0); } targetGroup.addBuddy(screenname); Object lock = new Object(); synchronized(lock){ try{ lock.wait(5000); } catch (Exception ex){} } } /** * Sends <tt>body</tt> to <tt>buddy</tt> as an offline instant message * @param buddy the screenname of the budy that we'd like to send our msg to. * @param body the content of the message to send. */ public void sendOfflineMessage(String buddy, String body) { conn.sendSnac(new OfflineSnacCmd(buddy, body)); } void sendAuthorizationReplay(String uin, String reasonStr, boolean isAccpeted) { conn.sendSnac(new AuthReplyCmd(uin, reasonStr, isAccpeted)); } private class OfflineSnacCmd extends SendImIcbm { private static final int TYPE_OFFLINE = 0x0006; protected OfflineSnacCmd(String sn, String message) { super(sn, message); } @Override protected void writeChannelData(OutputStream out) throws IOException { super.writeChannelData(out); new Tlv(TYPE_OFFLINE).write(out); } } private class AuthReplyCmd extends SsiCommand { private int FLAG_AUTH_ACCEPTED = 1; private int FLAG_AUTH_DECLINED = 0; private String uin = null; private String reason = null; private boolean accepted = false; public AuthReplyCmd(SnacPacket packet) { super(0x001b); ByteBlock messageData = packet.getData(); // parse data int offset = 0; short uinLen = BinaryTools.getUByte(messageData, offset++); uin = OscarTools.getString(messageData.subBlock(offset, uinLen), "US-ASCII"); offset += uinLen; accepted = BinaryTools.getUByte(messageData, offset++) == 1; int reasonLen = BinaryTools.getUShort(messageData, offset); offset += 2; reason = OscarTools.getString(messageData.subBlock(offset, reasonLen), "US-ASCII"); } public AuthReplyCmd(String uin, String reason, boolean accepted) { super(0x001a); this.uin = uin; this.reason = reason; this.accepted = accepted; } @Override public void writeData(OutputStream out) throws IOException { byte[] uinBytes = BinaryTools.getAsciiBytes(uin); BinaryTools.writeUByte(out, uinBytes.length); out.write(uinBytes); if(accepted) { BinaryTools.writeUByte(out, FLAG_AUTH_ACCEPTED); } else { BinaryTools.writeUByte(out, FLAG_AUTH_DECLINED); } if(reason == null) reason = ""; byte[] reasonBytes = BinaryTools.getAsciiBytes(reason); BinaryTools.writeUShort(out, reasonBytes.length); out.write(reasonBytes); } } public class AuthCmdFactory extends ServerSsiCmdFactory implements SnacResponseListener { List<CmdType> SUPPORTED_TYPES = null; public String responseReasonStr = null; public String requestReasonStr = null; public boolean ACCEPT = false; public boolean isErrorAddingReceived = false; public boolean isRequestAccepted = false; public AuthCmdFactory() { List<CmdType> types = super.getSupportedTypes(); ArrayList<CmdType> tempTypes = new ArrayList<CmdType>(types); tempTypes.add(new CmdType(SsiCommand.FAMILY_SSI, 0x001b)); // 1b auth request reply tempTypes.add(new CmdType(SsiCommand.FAMILY_SSI, 0x0019)); // 19 auth request this.SUPPORTED_TYPES = DefensiveTools.getUnmodifiable(tempTypes); } @Override public List<CmdType> getSupportedTypes() {return SUPPORTED_TYPES;} @Override public SnacCommand genSnacCommand(SnacPacket packet) { int command = packet.getCommand(); // auth request if (command == 25) { RequestAuthCmd cmd = new RequestAuthCmd(packet); requestReasonStr = cmd.reason; // will wait as a normal user Object lock = new Object(); synchronized(lock){ try{ lock.wait(2000); } catch (Exception ex){} } logger.trace("sending authorization " + ACCEPT); sendAuthorizationReplay( String.valueOf(cmd.uin), responseReasonStr, ACCEPT); return cmd; } else if (command == 27) // auth reply { AuthReplyCmd cmd = new AuthReplyCmd(packet); isRequestAccepted = cmd.accepted; responseReasonStr = cmd.reason; return cmd; } return super.genSnacCommand(packet); } public void handleResponse(SnacResponseEvent e) { if (e.getSnacCommand() instanceof SsiDataModResponse) { SsiDataModResponse dataModResponse = (SsiDataModResponse) e.getSnacCommand(); int[] results = dataModResponse.getResults(); List<SsiItem> items = ( (ItemsCmd) e.getRequest().getCommand()). getItems(); items = new LinkedList<SsiItem>(items); for (int i = 0; i < results.length; i++) { int result = results[i]; if (result == SsiDataModResponse.RESULT_ICQ_AUTH_REQUIRED) { isErrorAddingReceived = true; // authorisation required for user SsiItem buddyItem = items.get(i); String uinToAskForAuth = buddyItem.getName(); Vector<SsiItem> buddiesToBeAdded = new Vector<SsiItem>(); BuddyAwaitingAuth newBuddy = new BuddyAwaitingAuth( buddyItem); buddiesToBeAdded.add(newBuddy); CreateItemsCmd addCMD = new CreateItemsCmd(buddiesToBeAdded); logger.trace("Adding buddy as awaiting authorization " + uinToAskForAuth); MutableBuddyList joustSimBuddyList = conn.getSsiService().getBuddyList(); LayoutEventCollector evtCollector = new LayoutEventCollector(); joustSimBuddyList.addLayoutListener(evtCollector); conn.getSsiService().getOscarConnection().sendSnac(addCMD); evtCollector.waitForANewBuddy(20000); joustSimBuddyList.removeLayoutListener(evtCollector); logger.trace("Finished - Adding buddy as awaiting authorization"); //SNAC(13,18) send authorization request conn.getSsiService().getOscarConnection().sendSnac( new RequestAuthCmd( uinToAskForAuth, requestReasonStr)); } } } } } private class RequestAuthCmd extends SsiCommand { String uin; String reason; public RequestAuthCmd(String uin, String reason) { super(0x0018); this.uin = uin; this.reason = reason; } public RequestAuthCmd(SnacPacket packet) { super(0x0019); ByteBlock messageData = packet.getData(); // parse data int offset = 0; short uinLen = BinaryTools.getUByte(messageData, offset); offset++; uin = OscarTools.getString(messageData.subBlock(offset, uinLen),"US-ASCII"); offset += uinLen; int reasonLen = BinaryTools.getUShort(messageData, offset); offset+=2; reason = OscarTools.getString(messageData.subBlock(offset, reasonLen), "US-ASCII"); } @Override public void writeData(OutputStream out) throws IOException { byte[] uinBytes = BinaryTools.getAsciiBytes(uin); BinaryTools.writeUByte(out, uinBytes.length); out.write(uinBytes); if (reason == null) { reason = ""; } byte[] reasonBytes = BinaryTools.getAsciiBytes(reason); BinaryTools.writeUShort(out, reasonBytes.length); out.write(reasonBytes); } } public void setAuthorizationRequired() { logger.debug("sending auth required"); FullUserInfoCmd cmd = new FullUserInfoCmd(getIcqUIN()); cmd.writeOutByte(0x030c, 0); // 0x030C User authorization permissions cmd.writeOutByte(0x02F8, 0); // 0x02F8 User 'show web status' permissions conn.getSsiService().getOscarConnection().sendSnac(cmd); } public Hashtable<String, Object> getUserInfo(String uin) { UserInfoResponse response = new UserInfoResponse(); conn.getInfoService().getOscarConnection().sendSnacRequest( FullUserInfoCmd.getFullInfoRequestCommand(getIcqUIN(), uin), response); synchronized(response) { try{response.wait(5000);} catch (InterruptedException ex){} } return response.info; } public void setUserInfoLastName(String lastName) { FullUserInfoCmd cmd = new FullUserInfoCmd(getIcqUIN()); cmd.writeOutString(0x014A, lastName); conn.getSsiService().getOscarConnection().sendSnac(cmd); } public void setUserInfoPhoneNumber(String phone) { FullUserInfoCmd cmd = new FullUserInfoCmd(getIcqUIN()); cmd.writeOutString(0x0276, phone); conn.getSsiService().getOscarConnection().sendSnac(cmd); } public void setUserInfoLanguage(int language1, int language2, int language3) { FullUserInfoCmd cmd = new FullUserInfoCmd(getIcqUIN()); cmd.writeOutShort(0x0186, language1); cmd.writeOutShort(0x0186, language2); cmd.writeOutShort(0x0186, language3); conn.getSsiService().getOscarConnection().sendSnac(cmd); } public void setUserInfoHomeCountry(int countryCode) { FullUserInfoCmd cmd = new FullUserInfoCmd(getIcqUIN()); cmd.writeOutShort(0x01A4, countryCode); conn.getSsiService().getOscarConnection().sendSnac(cmd); } private class UserInfoResponse extends SnacRequestAdapter { Hashtable<String, Object> info = null; @Override public void handleResponse(SnacResponseEvent e) { if(e.getSnacCommand() instanceof FullUserInfoCmd) { FullUserInfoCmd cmd = (FullUserInfoCmd)e.getSnacCommand(); if(cmd.lastOfSequences) { info = cmd.getInfo(); synchronized(this) {notifyAll();} } } } } private static class BuddyAwaitingAuth extends SsiItem { private SsiItem originalItem = null; public BuddyAwaitingAuth(SsiItem originalItem) { super( originalItem.getName(), originalItem.getParentId(), originalItem.getId(), originalItem.getItemType(), getSpecTlvData()); this.originalItem = originalItem; } @Override public void write(OutputStream out) throws IOException { byte[] namebytes = BinaryTools.getAsciiBytes(originalItem.getName()); BinaryTools.writeUShort(out, namebytes.length); out.write(namebytes); BinaryTools.writeUShort(out, originalItem.getParentId()); BinaryTools.writeUShort(out, originalItem.getId()); BinaryTools.writeUShort(out, originalItem.getItemType()); ByteBlock data = getData(); // here we are nice and let data be null int len = data == null ? 0 : data.getLength(); BinaryTools.writeUShort(out, len); if (data != null) { data.write(out); } } private static ByteBlock getSpecTlvData() { try { ByteArrayOutputStream o = new ByteArrayOutputStream(); new Tlv(0x0066).write(o); ByteBlock block = ByteBlock.wrap(o.toByteArray()); return block; } catch (IOException ex) { logger.error("Error creating buddy awaiting auth tlv", ex); return null; } } } }