package net.matrix_rad.yabba; import java.lang.*; // Always needed, usually imported by default. import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Enumeration; import java.util.Hashtable; // For talking to Jabber servers. import org.jabber.jabberbeans.*; import org.jabber.jabberbeans.util.JID; import org.jabber.jabberbeans.Extension.*; // Import the only GUI stuff we need. import net.matrix_rad.skang.*; /* A quick Skang tutorial. For those that don't know anything about Skang (most of you I guess), some explaination is needed. Skang stands for SKin lANGuage, and is my effort at an intelligent skin language. Skang is part of my matrix-DFS project. Currently it only really supports AWT, but that will change, my own widget set is next, followed by swing. Skang will do a whole bunch of things automatically, read properties files; load properties, command line args, and applet parameters into the proper fields; load the skang file, etc. This leaves Yabba.java to deal almost exclusively with JabberBeans stuff, making it good for example code. Obviuously some GUI stuff still needs to be done, but all through Skang. Most of the unindented code in here is to support things I will soon put into Skang, so expect it to go away, probably before this gets released properly. Yabba.skang is the actual skin, reasonably self explanatory. Right clicking on any widget will bring up the developers menu, which includes an item to reload the skin file, making experiments quick and easy. Just keep Yabba.skang in a text editor, change it and relaod. The first non comment line in Yabba.skang loads the AWT module, which provides most of the commands used in the rest of the skang file. Yabba_small.skang is an alternate smaller skin. All the "CLASS_" fields are used by Skang for various reasons. "USAGE" is a list of Yabba's parameters. "SKANG" is a list of Yabba's extensions to the Skang language itself, bind these to widgets with the "action" command. Error reporting for "SKANG" commands is currently in a state of flux, I'm in the middle of changing over to Exceptions, which is what SkangException is all about. Skang has minimal support for a state machine. setState() sets the state machine to a particular state, and nextState() increments the state field, which is "currentState". main(), Yabba(), and Yabba(Boolean, Boolean, String) must be that way to keep Skang happy. Not much point changing them. When Skang is loading things as modules, the GUI is created later by the main class. An example of this is Matrix.class, which loads Yabba as a module, but Yabba.class loads itself directly. initGUI() is called when the GUI is ready. runBit() is a callback that Skang calls from its run() method very many times a second (too many times for this application). It needs to be quick. setThing(), appendThing() and getThing() are the main interfaces to Skangs widget and field managing code. setThing() changes the text of a Thing, either a widget or a field (or both). appendThing() adds text to the end of the Thing, and getThing() gets the Object of a thing. ERRLN() and OUTLN() are output commands, either to the console or the standard Skang output widgets. pause() is a wrapper around Thread.sleep if you just want a delay. pendingDoThing() is the correct way to call any SKANG command, especially from some one elses context, it adds the command to the list of things to do just before runBit() is called. The popup command creates a non-modal OK/Cancel dialog with SKANG commands for each button. Hope I didn't leave any out. */ /** Yabba is a Jabber chat client based on the JabberBeans Java API. * It is designed as both a part of matrix-DFS and as example code * to hand back to the JabberBeans project. It uses Skang for all * it's GUI stuff. * * @.requires Java 1.1.5. * @author David Seikel * @.copyright 2000 David Seikel * @version 0.5 alpha 2002-12-12 14:56:00 * @.concurrency All matrix-DFS modules run concurrently with each other. * @.priority Portability - This will need to run on version 4 browsers. * @.priority Simplicity - All software should be as simple to use as possible for the target audience. * @.priority Robustness - Must never crash, and must warn the user if the various connections time out. * @.priority Safety - The main users of this software are school children, effort could be spent on security issues. * @.priority Reusability - Attempting to provide both an open source GUI and a propietry GUI. * @.priority Testability - This is a client / server type setup, and I am writing the client. Should be able to get them to run on the same computer to test. * @.priority Maintainability - Schoolsnet will no doubt have others working on this one day. Since we are open sourcing this, the whole world needs to understand it * @.priority Speed - There is no need for speed, only a need to keep CPU usage to a minimum while it is just ticking over. For the GUI, responsiveness is a major issue. End to end and back speed is important, but some parts are out of our hands. * @.priority Size - All else being equal, smaller is better. * @.fyi Coding standards used. The AmbySoft Inc. coding standards for Java are usually used by the matrix-DFS project, but this is designed to be an example for the JabberBeans project, so their coding standards take precedence. The major difference is in the order of declerations, not a major problem really. Since part of the AmbySoft standard is to document differences from the standard and their reasons, this is still following AmbySoft B-). One difference from both standards is temporary code, debugging stuff and other things that are intended to go away once I fix / implement something better. All such code is not indented to make it easy to find later. The JabberBeans coding style says to keep methods below two pages long, without defining how long a page is. The example he gives is a good example of different page lengths. On my monitor, pages are 110 lines long. I have disagreed long and hard with the "Keep functions one/two page long" crowd. Obvious exceptions are lengthy switch's (runBit() is one such example). In the case of Java, breaking long methods into smaller methods THAT ARE NOT CALLED ANYWHERE ELSE will just bloat your class and slow things down with unneeded method call overhead. If Java had an inline method modifier, then I would use it just to keep David happy. In Skang, runBit() is called VERY often, the quicker it is, the better. Later versions of Skang will have more support for state machines, and long switch's will go away from here. Coding styles that insist on - if (someBoolean) { singleLineStatement(); } instead of - if (someBoolean) singleLineStatement(); obviuously result in methods that take up more space, you can't have both David B-). BTW, I have no objection to - if (someBoolean) { firstStatement(); secondStatement(); } it's how I prefer it anyway (K&R style sucks). * */ public final class Yabba extends Skang implements ConnectionListener, PacketListener, RosterListener { // All current public fields have to be public, cause Skang needs it that // way. All else is private. // Skang provides an IDLE = 0; // The order of these is important, this is the state machine. private final static short DISCONNECTED = 1; // A do nothing state. private final static short CONNECT = 2; private final static short WAIT_CONNECT = 3; // A do nothing state. private final static short AUTH = 4; private final static short WAIT_AUTH = 5; // A do nothing state. private final static short ROSTER = 6; private final static short WAIT_ROSTER = 7; // A do nothing state. private final static short PRESENCE = 8; private final static short CONNECTED = 9; // A do nothing state. private final static short REGISTER = 10; private final static short WAIT_REGISTER = 11; // A do nothing state. private final static short DISCONNECT = 12; private final static short WAIT_DISCONNECT = 13; // A do nothing state. //********************************************************************** /** User logon name. * * @.skangarg user * @.required */ public static String user = null; /** Jabber server. * * @.skangarg server * @.default "localhost" */ public static String server = null; /** Jabber resource. * * @.skangarg resource * @.default "Yabba" */ public static String resource = null; /** Users jabber password. * * @.skangarg jabberpassword * @.required */ public static String jabberPassword = null; /** Users buddy list. * * @.skangarg buddies */ public static String buddies = null; private static boolean isConnected = false; private static boolean doingRoster = false; private static String authID = null; private static String regID = null; private static String oldServer = null; private static ConnectionBean jabberBean = new ConnectionBean(); private static MessengerBean mBean = new MessengerBean(jabberBean); private static IQBean iBean = new IQBean(jabberBean); private static RosterBean rBean = new RosterBean(); //private static int count = 0; //********************************************************************** // The JabberBeans standard says to put constructors first, and main() is // not really a constructor. In matrix-DFS main() is only ever used as an // alternate entry point for applications that constructs the class as an // applet and returns. Besides, I prefer to list my entry points first, // in called order. /** The main entry point for this class when run as an application. * * @param String someArguments[] - The command line arguments. * @.modifies Creates the rest of the class as an applet. * @.concurrency Since this method is a wrapper around an applet, it has to start the applet as a thread. * @.example main({"user=dvs1", "server=localhost"}); */ public static void main(String someArguments[]) { Skang.main(someArguments); } /** Do our one time setup stuff. * init() sometimes has a limited time to run before the browser complains. */ public void init() { super.init(); // Call superclass init(). // What.appCodeBase is a Skang field, similar to applet.appCodeBase, but // applies to applications as well. if ((server == null) || (server == "") || !areWe.standAlone) { server = What.appCodeBase.getHost(); } // One time Jabber init. jabberBean.addConnectionListener(this); mBean.addPacketListener(this); iBean.addPacketListener(this); rBean.setIQBean(iBean); rBean.addRosterListener(this); } public void initGUI() { super.initGUI(); try { appendThing("Buddy", buddies); } catch (SkangException e) { ; } setThing("JID", user + ":" + jabberPassword + "@" + server + "/" + resource); } /** Skang will try to call this often during it's loop. * * @.modifies The state machine lives here, so of course everything gets modified. */ public void runBit() { // The Jabber state machine. switch (currentState) { case IDLE : // Skang sets this as the inital state, but { // leaves it up to the module to change it. // We wait for the user to enter their JID. } break; case CONNECT : { if (isConnected) { setState(AUTH); break; } // Blank the Reply widget, coz we are starting from scratch. setThing("Reply", ""); try { jabberBean.connect(InetAddress.getByName(server)); oldServer = server; nextState(); } catch (UnknownHostException e) { //from InetAddress.getByName() ERRLN("DNS error finding your server:" + e.getMessage()); break; } catch (IOException e) { //from connect ERRLN("IO error while attempting to connect to server:" + e.getMessage()); break; } } break; case AUTH : { addReply("AUTHORIZING " + user + "@" + server + "/" + resource); // We construct an InfoQuery packet with auth data InfoQueryBuilder iqb = new InfoQueryBuilder(); //and the auth data builder IQAuthBuilder iqAuthb = new IQAuthBuilder(); //we are setting info iqb.setType("set"); iqAuthb.setUsername(user); iqAuthb.setPassword(jabberPassword); iqAuthb.setResource(resource); //build the Auth data try { iqb.addExtension(iqAuthb.build()); } catch (InstantiationException e) { //building failed ? ERRLN("Fatal Error on Auth object build:" + e.getMessage()); break; } // Build and send the InfoQuery packet to the server to log in try { InfoQuery iq = (InfoQuery) iqb.build(); authID = iq.getIdentifier(); jabberBean.send(iq); OUTLN("Login request sent."); nextState(); } catch (InstantiationException e) { //building failed ? ERRLN("Fatal Error on IQ object build:" + e.getMessage()); break; } } break; case ROSTER : { addReply("REQUESTING ROSTER FOR " + user); try { rBean.refreshRoster(); nextState(); } catch (InstantiationException e) { ERRLN("Fatal Error on refreshRoster:" + e.getMessage()); break; } } break; case WAIT_ROSTER : { // count++; // if (count > 500) // { // addReply("NO ROSTER FOR " + user + "???"); // nextState(); // count = 0; // } } break; case PRESENCE : { addReply("SENDING PRESENCE FOR " + user); // Send presence. try { PresenceBuilder pb = new PresenceBuilder(); Presence pkt = (Presence) pb.build(); jabberBean.send(pkt); ERRLN("SENDING PRESENCE: " + pkt.toString()); nextState(); } catch (InstantiationException e) { //building failed ? ERRLN("Fatal Error on Presence object build:" + e.getMessage()); break; } } break; case REGISTER : { addReply("REGISTERING " + user); // We construct a InfoQuery packet with auth data InfoQueryBuilder iqb = new InfoQueryBuilder(); //and the registration data builder IQRegisterBuilder iqReg = new IQRegisterBuilder(); /* Should actually ask the server for instructions. Client request for registration information to a server service (service.denmark): Server response with registration fields required: Choose a username and password to register with this server. 106c0a7b5510f192a408a1d054150ed1065e255a Client request to register for an account: hamlet hamlet@denmark gertrude 106c0a7b5510f192a408a1d054150ed1065e255a Successful registration: Failed registration: Not Acceptable */ iqb.setType("set"); iqReg.set("name", user); // Fake it. iqReg.set("username", user); iqReg.set("email", user + "@" + server); // Fake it. iqReg.set("password", jabberPassword); //build the Auth data try { iqb.addExtension(iqReg.build()); } catch (InstantiationException e) { //building failed ? ERRLN("Fatal Error on Register object build:" + e.getMessage()); break; } // Build and send the InfoQuery packet to the server to create the account try { InfoQuery iq = (InfoQuery) iqb.build(); regID = iq.getIdentifier(); jabberBean.send(iq); nextState(); } catch (InstantiationException e) { //building failed ? ERRLN("Fatal Error on IQ object build:" + e.getMessage()); break; } } break; case DISCONNECT : { addReply("DISCONNECTING FROM " + server); // This kills our module, dunno why. jabberBean.disconnect(); setState(WAIT_DISCONNECT); } break; } } /** * Add a string to the Replyt widget, without having to deal with exceptions. * * @param String aReply - the aded string. * * @.modifies Changes the Reply details as displayed to the user. */ private String addReply(String aReply) { try { //OUTLN("addReply(" + aReply + ")"); appendThing("Reply", aReply + "\n"); } catch (SkangException e) { ; } return aReply; } /** Select the buddy you want to chat to. * A dummy function for unimplemeted commands. * We need this to get Skang to do the correct thing * when showing help and copyright messages. * * @.skang buddy data */ public void buddy(String anArgument) { ; } /** Do what we need to do when we get a new roster. * * @param Roster r - The new roster details. * @.modifies Changes the roster details as displayed to the user. */ private void doRoster(Roster r) { if (doingRoster) return ; doingRoster = true; Hashtable buds = new Hashtable(); int j = 0; for (int i = 0; i < buddies.length(); i++) { if ((i == (buddies.length() - 1)) || (buddies.charAt(i) == ',')) { if (buddies.charAt(j) == ' ') j++; if (i == (buddies.length() - 1)) i++; String text = buddies.substring(j + 1, i - 1); // Strip <> from ends. buds.put(text, text); ERRLN("BUDDY: " + text); j = i + 1; } } if (r != null) { for (Enumeration buddies = r.items(); buddies.hasMoreElements(); ) { RosterItem roster = (RosterItem) buddies.nextElement(); String type = roster.getSubscriptionType(); if ((type != null) && ((type.equals("from")) || (type.equals("both")))) { String bud = roster.getJID().toString(); buds.put(bud, bud); try { appendThing("Buddy", roster.getFriendlyName() + " <" + bud + ">"); } catch (SkangException e) { ERRLN(e.getMessage()); } } } } for (Enumeration buddies = rBean.entries(); buddies.hasMoreElements(); ) { RosterItem roster = (RosterItem) buddies.nextElement(); String bud = "NULL"; String waiting = "NULL"; String type = "NULL"; if (roster.getSubscriptionType() != null) { type = roster.getSubscriptionType(); } if ((roster.getJID() != null)) { bud = roster.getJID().toString(); // Already in our roster, so no need to subscribe. if (buds.remove(bud) == null) ; // subscribe("unsubscribe", bud); // User is no longer our buddy. } if (roster.getWaitingStateType() != null) // What WE are waiting for. { waiting = roster.getWaitingStateType().toString(); buds.remove(bud); } ERRLN("ROSTER: " + " " + waiting + " " + type + " " + roster.getFriendlyName() + " " + bud ); } pause(10); // To avoid a race condition, we must lose the race B-). if (currentState == WAIT_ROSTER) { // Try to subscribe to users in the buddy list but not currently in the roster. for (Enumeration buddies = buds.elements(); buddies.hasMoreElements(); ) { String bud = (String) buddies.nextElement(); // Request a subscription from your buddy. subscribe("subscribe", bud); } setState(PRESENCE); } doingRoster = false; } /** Login as this user. Split up a user:password@server/resource into seperate * strings and reconnect as new user. * * @.skang newjid data */ public void newJID(String aValue) { pause(10); // To avoid a race condition, we must lose the race B-). int pos = 0; if ((pos = aValue.indexOf('/')) != -1) { setThing("resource", aValue.substring(pos + 1)); aValue = aValue.substring(0, pos); } if ((pos = aValue.indexOf('@')) != -1) { setThing("server", aValue.substring(pos + 1)); aValue = aValue.substring(0, pos); } if ((pos = aValue.indexOf(':')) != -1) { setThing("jabberpassword", aValue.substring(pos + 1)); aValue = aValue.substring(0, pos); } setThing("user", aValue); setThing("JID", user + ":" + jabberPassword + "@" + server + "/" + resource); pause(10); // To avoid a race condition, we must lose the race B-). if (isConnected) { if (server.equals(oldServer)) { setState(AUTH); } else { addReply("DISCONNECTING FROM " + server); // This kills our module, dunno why. jabberBean.disconnect(); setState(CONNECT); } } else { setState(CONNECT); } } /** Change user name. * * @.skang newuser data */ public void newUser(String aValue) { pause(10); // To avoid a race condition, we must lose the race B-). user = aValue; setThing("JID", user + ":" + jabberPassword + "@" + server + "/" + resource); } /** Change user password. * * @.skang newjabberpassword data */ public void newJabberPassword(String aValue) { pause(10); // To avoid a race condition, we must lose the race B-). jabberPassword = aValue; setThing("JID", user + ":" + jabberPassword + "@" + server + "/" + resource); } /** Change server. * * @.skang newserver data */ public void newServer(String aValue) { pause(10); // To avoid a race condition, we must lose the race B-). server = aValue; setThing("JID", user + ":" + jabberPassword + "@" + server + "/" + resource); } /** Change resource name. * * @.skang newresource data */ public void newResource(String aValue) { pause(10); // To avoid a race condition, we must lose the race B-). resource = aValue; setThing("JID", user + ":" + jabberPassword + "@" + server + "/" + resource); } /** Register the current user. * * @.skang register */ public void register() { pause(10); // To avoid a race condition, we must lose the race B-). setState(REGISTER); } /** The conversation and progress messages. * A dummy function for unimplemeted commands. * We need this to get Skang to do the correct thing * when showing help and copyright messages. * * @.skang reply data */ public void reply(String anArgument) { ; } /** Subscribe to a user. * * @.skang subscribe name,data */ public void subscribe(String aType, String aBuddy) { PresenceBuilder pb = new PresenceBuilder(); pb.setType(aType); pb.setFromAddress(JID.fromString(user + "@" + server + "/" + resource)); pb.setToAddress(JID.fromString(aBuddy)); try { ERRLN( addReply(aType + "ING TO <" + aBuddy + ">") ); Presence pkt = (Presence) pb.build(); jabberBean.send(pkt); } catch (InstantiationException e) { ERRLN("Fatal Error on Presence object build:" + e.getMessage()); } } /** Quit Yabba. * * @.modifies Changes everything B-). */ public void quit() { //ERRLN("QUIT (Yabba) FOR - " + this.getClass().toString()); if (isConnected) { // This hangs our module, dunno why. We want to quit, not hang. //jabberBean.disconnect(); } //ERRLN("QUIT (Yabba) FOR - " + this.getClass().toString() + " ENDED"); } /** Send a Jabber message to the local Jabber server. * * @.skang jabber data * @param String aUI - the Jabber message to send. * @.modifies adds the message to the Reply widget. */ public void sendJab(String aUI) throws SkangException { OUTLN("Sending " + aUI); ERRLN("Contacting Jabber."); MessageBuilder mb = new MessageBuilder(); Message msg; //set destination user. String destination = getThing("Buddy"); destination = destination.substring(destination.indexOf('<') + 1, destination.indexOf('>')); mb.setToAddress(JID.fromString(destination)); mb.setBody(aUI); try { msg = (Message)mb.build(); } catch (InstantiationException e) { //build failed ? SkangException ex = new SkangException("error creating message packet:" + e.getMessage()); ERRLN(ex.getMessage()); throw(ex); } //send message jabberBean.send(msg); OUTLN("Message sent to " + destination); ERRLN(""); appendThing("Reply", "<" + user + "> " + aUI + "\n"); } // The methods that follow are a bunch of JabberBean callbacks, I won't javadoc them just yet. public void changedRoster(Roster r) { ERRLN("CHANGEROSTER();"); addReply("GOT ROSTER UPDATE FOR " + user); doRoster(r); } public void connectionChanged(ConnectionEvent c) { ConnectionEvent.EState oldState = c.getOldState(); ConnectionEvent.EReason reason = c.getReason(); ConnectionEvent.EState newState = c.getState(); // David, a switch is preferable here, but EState is not castable to an int! if (newState == ConnectionEvent.STATE_UNKNOWN) { addReply("CONNECTION STATE UNKNOWN FOR " + server); } else if (newState == ConnectionEvent.STATE_CONNECTING) { addReply("CONNECTING TO " + server); } else if (newState == ConnectionEvent.STATE_CONNECTED) { addReply("CONNECTED TO " + server); pause(10); // To avoid a race condition, we must lose the race B-). isConnected = true; setState(AUTH); } else if (newState == ConnectionEvent.STATE_DISCONNECTED) { addReply("DISCONNECTED FROM " + server); pause(10); // To avoid a race condition, we must lose the race B-). isConnected = false; oldServer = null; setState(DISCONNECTED); } } public void receivedPacket(PacketEvent anEvent) { // Presence packets seem to bypass this. String type = "UNKNOWN"; Packet pkt = anEvent.getPacket(); ContentPacket cpkt = (ContentPacket) pkt; ERRLN("RECEIVED " + pkt.getClass().getName() + ": " + pkt.toString()); /* Error codes 400 - Bad request 401 - Unauthorized 402 - Payment required 403 - Forbidden 404 - Not found 405 - Not Allowed 406 - Not Acceptable 407 - Registration required 408 - Request timeout 409 - Username not available 500 - Internal server error 501 - Not implemented 502 - Remote server error 503 - Service unavailable 504 - Remote server timeout */ if (pkt instanceof InfoQuery) { type = "INFOQUERY"; if ((authID != null) && (currentState == WAIT_AUTH)) // Are we waiting for auth? { if (authID.equals(cpkt.getIdentifier())) // Is this the auth reply? { if (cpkt.getErrorCode() == null) // Was there any errors? { addReply("AUTHORIZED " + user + "@" + server + "/" + resource); authID = null; pause(10); // To avoid a race condition, we must lose the race B-). setState(ROSTER); } else { ERRLN(addReply("AUTHENTICATION PROBLEM " + cpkt.getType() + ":" + cpkt.getErrorCode() + " " + cpkt.getErrorText())); authID = null; if (cpkt.getErrorCode().equals("407") || cpkt.getErrorCode().equals("401")) // Ask the user if they want to create the account. { pendingDoThing("popup 'WARNING', 'This user may not be known to Jabber!', 'Do you wish to register a new user?', 'register', ''"); } else { setState(IDLE); } } } } else if ((regID != null) && (currentState == WAIT_REGISTER)) // Are we waiting for registration? { if (regID.equals(cpkt.getIdentifier())) // Is this the reg reply? { if (cpkt.getErrorCode() == null) // Was there any errors? { addReply("REGISTERED " + user + "@" + server + "/" + resource); regID = null; pause(10); // To avoid a race condition, we must lose the race B-). setState(AUTH); } else { ERRLN("REGISTRATION PROBLEM " + cpkt.getType() + ":" + cpkt.getErrorCode() + " " + cpkt.getErrorText()); regID = null; pause(2000); setState(IDLE); } } } } else if (pkt instanceof Presence) { type = "PRESENCE"; Presence prsnc = (Presence) pkt; String from = prsnc.getToAddress().toString(); ERRLN("PRESENCE TO: " + prsnc.getToAddress().toString() + " FROM: " + from + " TYPE: " + prsnc.getType() + " PRIORITY: " + prsnc.getPriority() + " STATE: " + prsnc.getStateShow() + " STATUS: " + prsnc.getStatus() ); /* jane@city wants to add george@jungle to her buddy list. Jane updates her roster: lovers. Jane sends: George is offline, so his server caches the request. When George connects and his client (jabdrum) fetches the roster, the server forwards the presence subscribe request. Upon receiving this request it says "George, this jane@city person added you to their roster, do you want to allow her to see your presence?" If so, it sends a and it also asks: "Do you want to add her to your roster?" If so, it sends a . The server updates the roster and pushes: nonapes Back in the city, Jane is still connected so her client gets these presence packets and asks her: "George accepted, and wants to subscribe to you, too, is this OK?". If so, it sends back a and her server sends a roster push: lovers Georges client gets it and tells George that Jane accepted, and receives the roster push nonapes. But then George realizes he has enough friends to keep him company, so his client sends . His server then removes the entry and sends a and . Jane just got told by her client "you've been unsubscribed from George's presence, and George doesn't want to know about yours either anymore" and her server returns a . */ if (prsnc.getType().equals("subscribe")) { pendingDoThing("popup '" + from + "', " + "'wishes to subscribe to your presence.', " + "'Will you allow this?', " + "'subscribe \"subscribed\", \"" + from + "\"', " + "'subscribe \"unsubscribed\", \"" + from + "\"'"); } } else if (pkt instanceof Message) { type = "MESSAGE"; Message msg = (Message) pkt; String subType = msg.getType(); if ((subType != null) && (subType.equals("error"))) { ERRLN("RECEIVED ERROR: " + pkt.toString()); } else { OUTLN("Message received from " + msg.getFromAddress()); addReply("<" + msg.getFromAddress().getUsername() + "> " + msg.getBody()); } } else { ERRLN("RECEIVED " + type + " " + pkt.getClass().getName() + ": " + pkt.toString() + "||||" + cpkt.getType() + "||||" + cpkt.getIdentifier() + "||||" + cpkt.getErrorCode() + "||||" + cpkt.getErrorText()); return ; } } public void replacedRoster(Roster r) { ERRLN("REPLACEDROSTER();"); addReply("GOT ROSTER FOR " + user); // setThing("Buddy", ""); doRoster(r); } public void sendFailed(PacketEvent anEvent) { ERRLN(addReply("SEND FAILED: " + anEvent.getPacket().toString())); } public void sentPacket(PacketEvent anEvent) { // Presence packets seem to bypass this. ERRLN("SENT: " + anEvent.getPacket().toString()); } /* A more advanced example that includes adding users to the roster, two new users coming online, a user changing status, and a client version request. SEND: RECV: SEND: SEND: SEND: hamlet SEND: gertrude SEND: Courtyard SEND: SEND: RECV: SEND: SEND: SEND: RECV: RECV: RECV: RECV: RECV: Friends RECV: RECV: RECV: SEND: RECV: RECV: chat RECV: RECV: RECV: away RECV: Busy with project. RECV: RECV: RECV: SEND: SEND: RECV: RECV: RECV: RECV: RECV: RECV: RECV: RECV: RECV: RECV: RECV: RECV: SEND: SEND: Well, God-a-mercy. SEND: RECV: RECV: My honoured lord! RECV: RECV: RECV: My most dear lord! RECV: SEND: SEND: SEND: RECV: RECV: RECV: Gabber RECV: 0.6.1 RECV: Debian GNU/Linux 2.2.16 RECV: RECV: SEND: RECV: */ }