IRClib tutorial

This tutorial explains how to use the IRClib.

Introduction
1.1. Should I read the introduction?
1.2. What is IRC and what is a library?
1.3. Why I wrote IRClib

The classes and their jobs
2.1. IRCConnection
2.2. IRCEventListener
2.3. IRCEventAdapter
2.4. IRCModeParser
2.5. IRCParser
2.6. IRCUser
2.7. IRCUtil
2.8. SSLIRCConnection
2.9. SSLDefaultTrustManager

How to create a complete IRC connection
3.1. Create an IRCConnection instance with listener
3.2. Write a complete IRCEventListener

All about Secure Sockets Layer (SSL)
4.1. How to use the SSLIRCConnection class
4.2. Write your own com.sun.net.ssl.X509TrustManager

Example
5.1. Write a small command-line IRC client






1.1. Should I read the introduction? (Top)
No.

1.2. What is IRC and what is a library? (Top)
First, if you cannot answer these two questions, I'm sure you're wrong here.
IRC is the chat-protocol of the internet. See the moepII FAQs for more information.
A library is not an application. That means, a normal user cannot start it or so. Application programmers can use this library as a rather thin abstraction layer of the IRC protocol.

1.3. Why I wrote IRClib (Top)
I wrote it just for fun. Then I started the moepII IRC client, because otherwise the library would not have had much sense for me.




2.1. IRCConnection (Top)
The IRCConnection class is the beginning of everything. It is intantiated to create a connection to an IRC server.

2.2. IRCEventListener (Top)
The IRCEventListener is a listener-interface. A class which implements IRCEventListener can be set as listener of an IRCConnection instance with the IRCConnection.addIRCEventListener method. If you do not want to define all event-methods, you can extend the IRCEventAdapter class.

2.3. IRCEventAdapter (Top)
This adapts the IRCEventListener interface for listener-classes which do not define all methods of the IRCEventListener.

2.4. IRCModeParser (Top)
An instance of this class is given as argument in the IRCEventListener.onMode method for channel-mode-events. It contains the parsed modes so that you can easily access the modes with their operators (+ or -) and their optional arguments.

2.5. IRCParser (Top)
This class is used by the IRCConnection to analyze incoming lines. It parses the prefix, command and the parameters. The parameters are divided into the middle part and the trailing part. They also can be accessed by their index. Nevertheless, this class is unimportant for you. You can ignore it.

2.6. IRCUser (Top)
IRCUser objects are arguments of many event-methods of the IRCEventListener interface. Such objects contain the active user's nickname, username and host. Thus an IRCUser object provides all available information about an IRC user.

2.7. IRCUtil (Top)
The IRCUtil class provides some static utilities. The most important for you are the constant field values. There are all IRC numeric errors and numeric replies. Such numbers are given as arguments in the IRCEventListener.onReply and IRCEventListener.onError methods.

2.8. SSLIRCConnection (Top)
The SSLIRCConnection class extends the IRCConnection and provides secure sockets. It's used same as the IRCConnection with the only difference that the server must provide SSL connections.

2.9. SSLDefaultTrustManager (Top)
It's the default TrustManager of the SSLIRCConnection if no other is set by you with the SSLIRCConnection.addTrustManager method. This SSLDefaultTrustManager trusts automatically the X509 certificate, which is used by all public IRC servers which support SSL. It does NOT ask the user whether he accepts the certificate. This can be changed easily as you can read below in the SSL section.




3.1. Create an IRCConnection instance with listener (Top)
The following code of a class which imports org.schwering.irc.lib.* prepares an IRC connection and then tries to establish the connection.
The server is irc.somenetwork.com, the default portrange (6667 and 6669) is set, no password is used (null). The nickname is Foo and the realname is Mr. Foobar. The username foobar.
Because of setDaemon(true), the JVM exits even if this thread is running.
An instance of the class MyListener which must implement IRCActionListener is set as event-listener for the connection. You get more information about the listener in the next section.
The connection is told to parse out mIRC color codes and to enable automatic PING? PONG! replies.
import org.schwering.irc.lib.*;

public class Test {
  public static void main(String[] args) {
    IRCConnection conn = new IRCConnection(
                               "irc.somenetwork.com", 
                               new int[] { 6667, 6668, 6669 },
                               null, 
                               "Foo", 
                               "Mr. Foobar", 
                               "foo@bar.com" 
                             ); 
    
    conn.addIRCEventListener(new MyListener()); // see next section
    conn.setDaemon(true);
    conn.setColors(false); 
    conn.setPong(true); 
       
    try {
      conn.connect(); // Connect! (Don't forget this!!)
    } catch (IOException ioexc) {
      ioexc.printStackTrace(); // Connection failed
    }
  }
}


3.2. Write a complete IRCEventListener (Top)
In the above example which creates a new IRCConnection, an instance of the MyListener class is set as IRCEventListener of the instance with IRCConnection.addIRCEventListener.
Every listener must implement the IRCEventListener interface. Therefore it also must define all methods of the interface. To avoid this, you can either define empty methods for those where you want to do nothing or you can extend IRCEventAdapter. The IRCEventAdapter class defines all methods which are defined in the IRCEventListener.
A listener can treat the following events: Our listener will print some information about all events. But it will not define the onPing method, because with IRCConnection.setPong(true) we enabled automatic PING? PONG!. Additionally, we ignore all NOTICEs (the onNotice event) and user-MODEs (the onMode method, in which the modes are given as String argument) and all commands the IRClib doesn't know (the onOther method). Of course, ignoring these events makes no sense in reality, but here we do it for testing purposes. This is the code for our listener-class:
import org.schwering.irc.lib.*;

public class MyListener extends IRCEventAdapter 
                        implements IRCEventListener {

  public void onConnect() {
    System.out.println("Connected successfully.");
  }

  public void onDisconnected() {
    System.out.println("Disconnected.");
  }

  public void onError(String msg) {
    System.out.println("ERROR: "+ msg);
  }

  public void onError(int num, String msg) {
    System.out.println("Error #"+ num +": "+ 
        msg);
  }

  public void onInvite(String chan, IRCUser user, String nickPass) {
    System.out.println("INVITE: "+ user.getNick() 
        +" invites "+ nickPass +" to "+ chan);
  }

  public void onJoin(String chan, IRCUser user) {
    System.out.println("JOIN: "+ user.getNick() 
        +" joins "+ chan);
    // add the nickname to the nickname-table
  }

  public void onKick(String chan, IRCUser user, String nickPass, String msg) {
    System.out.println("KICK: "+ user.getNick() 
        +" kicks "+ nickPass +"("+ msg +")");
    // remove the nickname from the nickname-table
  }

  public void onMode(String chan, IRCUser user, IRCModeParser modeParser) {
    System.out.println("MODE: "+ user.getNick() 
        +" changes modes in "+ chan +": "+ modeParser.getLine());
    // some operations with the modes
  }

  public void onNick(IRCUser user, String nickNew) {
    System.out.println("NICK: "+ user.getNick() 
        +" is now known as "+ nickNew);
    // update the nickname in the nickname-table
  }

  public void onPart(String chan, IRCUser user, String msg) {
    System.out.println("PART: "+ user.getNick() 
        +" parts from "+ chan +"("+ msg +")");
    // remove the nickname from the nickname-table
  }

  public void onPrivmsg(String target, IRCUser user, String msg) {
    System.out.println("PRIVMSG: "+ user.getNick() 
        +" to "+ target +": "+ msg);
  }

  public void onQuit(IRCUser user, String msg) {
    System.out.println("QUIT: "+ user.getNick() +" ("+ 
        user.getUsername() +"@"+ user.getHost() +") ("+ msg +")");
    // remove the nickname from the nickname-table
  }

  public void onReply(int num, String value, String msg) {
    System.out.println("Reply #"+ num +": Message: "+ 
        msg +" | Value: "+ value);
  }
  
  public void onTopic(String chan, IRCUser user, String topic) {
    System.out.println("TOPIC: "+ user.getNick() 
        +" changes topic of "+ chan +" into: "+ topic);
  }

}

Here we did not define any constructor, therefore the only way to get an instance of the class is new MyListener(). However, it's often useful to define a constructor with some arguments. For example, you could transmit a reference to the object which creates the new listener to the listener-object as owner (that would look like conn.addIRCEventListener(new MyListener(this))). Then the event-methods could easily add, remove or modify the nicklists and other fields. You could also write the listener as nested class in the class which creates the IRCConnection instance.




4.1. How to use the SSLIRCConnection class (Top)
That's quite easy. I'll take the code from above where we wrote a class which creates an IRCConnection and mark the changes with bold font.
SSL IRC servers listen don't have a common port. They usually listen to 994, 6000, 6697 or 7000.
import org.schwering.irc.lib.*;

public class Test {
  public static void main(String[] args) {
    SSLIRCConnection conn = new SSLIRCConnection(
                                  "ssl.somenetwork.com",
                                  new int[] { 994, 443, 6000, 6697 },
                                  null,
                                  "Foo",
                                  "Mr. Foobar",
                                  "foo@bar.com"
                                );

    conn.addTrustManager(new MyTrustManager()); // see next section

    conn.addIRCEventListener(new MyListener());
    conn.setDaemon(true);
    conn.setColors(false);
    conn.setPong(true);

    try {
      conn.connect(); // Connect! (Don't forget this!!)
    } catch (IOException ioexc) {
      ioexc.printStackTrace(); // Connection failed
    }
  }
}

You can add as many TrustManagers as you want, from ± 0 to + ∞. If you add no TrustManager until you invoke the connect method, the SSLIRCConnection automatically uses an instance of SSLDefaultTrustManager.

4.2. Write your own com.sun.net.ssl.X509TrustManager (Top)
IRClib comes with a default trustmanager, the SSLDefaultTrustManager class. It is set by the SSLIRCConnection as the none trustmanager, if no others are set until the connect method is invoked. The SSLDefaultTrustManager accepts the X509 certificate automatically.
Of course, you also can write your own TrustManager. Here I'll only describe how to modify the SSLDefaultTrustManager so that it asks the user whether he, the user, accepts the certificate. If you want to write a TrustManager for other certificates, your class must implement the com.sun.net.ssl.TrustManager interface (not javax.net.ssl.TrustManager since we use JSSE and not the Java 1.4 API for compliance with older JREs).

So, if you write a client for human users with a nice Swing GUI, you might want let the human user himself decide whether he accepts the server's X509 certificate or not. The easiest way to do so is to extend the SSLDefaultTrustManager and override the isServerTrusted method. Let's make an example which shows the user a dialog and asks for a OK or a Cancel.
import javax.security.cert.X509Certificate;
import com.sun.net.ssl.X509TrustManager;
import org.schwering.irc.lib.*;

public class MyTrustManager extends SSLDefaultTrustManager implements X509TrustManager {
  public boolean isServerTrusted(X509Certificate chain[]) {
    boolean accept = false;
    for (int i = 0; !accept && i < chain.length; i++)
      accept = confirmDialog("Do you want to accept this server's X509 "+
          "certificate?", "SSL X509 Certificate");
    return accept;
  }

  private static boolean confirmDialog(String msg, String title) {
    return (JOptionPane.showConfirmDialog(null, msg, title,
        JOptionPane.YES_NO_OPTION) == 0);
  }
}


The confirmDialog is just a helper-method which shows a small dialog with a Yes and a No button. The method uses the javax.swing.JOptionPane class.
The real important method is the isServerTrusted method, of course. It asks the user for every certificate (generally it's just one) whether he accepts it or not. If the user accepts it, the method is happy, returns true, the server is trusted and the connection can start. Otherwise, if the user rejected all certificates, the method returns false. Thus, the SSLIRCConnection cannot be established and is still disconnected.




5.1. Write a small command-line IRC client (Top)
At the ending of this tutorial we want to write a small IRC client with IRClib. It's a console application. The information like server, portrange, nickname, username, realname etc. are transmitted as parameters in the command-line.
You can also download the following source with bytecode: IRC.zip.
Run the class with java IRC -server <server>:<port> -nick <nickname>. Additionally, you can set the parameters -pass <server-password>, -user <username>, -name <realname> and -ssl without a value to use secure sockets.
import org.schwering.irc.lib.*;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.util.Hashtable;

/**
 * A simple command line client based on IRClib.
 */
public class IRC extends Thread {

  /** Reads input from the console. */
  private BufferedReader in;

  /** The IRC connection. */
  private IRCConnection conn;

  /** The current default target of PRIVMSGs (a channel or nickname). */
  private String target;

  /**
   * Parses the arguments and starts the client.
   */
  public static void main(String[] args) {
    Hashtable ht = null;
    try {
      ht = getHashtable(args);
    } catch (IllegalArgumentException exc) {
      printHelp();
      return;
    }
    String host = (String)ht.get("host");
    int port = new Integer((String)ht.get("port")).intValue();
    String pass = (String)ht.get("pass");
    String nick = (String)ht.get("nick");
    String user = (String)ht.get("user");
    String name = (String)ht.get("name");
    boolean ssl = ((Boolean)ht.get("ssl")).booleanValue();
    try {
      new IRC(host, port, pass, nick, user, name, ssl);
    } catch (IOException exc) {
      printHelp();
    }
  }

  /**
   * Returns a hashtable with settings like host, port, nick etc..
   */
  private static Hashtable getHashtable(String[] args) {
    Hashtable ht = new Hashtable();
    String serverPort = (String)getParam(args, "server");
    int colon = serverPort.indexOf(':');
    ht.put("host", serverPort.substring(0, colon));
    ht.put("port", serverPort.substring(colon + 1));
    ht.put("pass", getParam(args, "pass", ""));
    ht.put("nick", getParam(args, "nick"));
    ht.put("user", getParam(args, "user", ht.get("nick")));
    ht.put("name", getParam(args, "name", ht.get("user")));
    ht.put("ssl", getParam(args, "ssl", new Boolean(false)));
    return ht;
  }

  /**
   * Returns a value of a key in the arguments.
   */
  private static Object getParam(String[] args, Object key) {
    return getParam(args, key, null);
  }

  /**
   * Returns a value of a key in the arguments. If a key without a value is
   * found, a Boolean object with true is returned. If no key is found, the
   * default value is returned.
   */
  private static Object getParam(String[] args, Object key, Object def) {
    for (int i = 0; i < args.length; i++) {
      if (args[i].equalsIgnoreCase("-"+ key)) {
        if (i + 1 < args.length) {
          String value = args[i + 1];
          if (value.charAt(0) == '-')
            return new Boolean(true);
          else 
            return value;
        } else {
          return new Boolean(true);
        }
      } 
    }
    if (def != null)
      return def;
    else 
      throw new IllegalArgumentException("No value for "+ key +" found.");
  }

  /**
   * Prints some help.
   */
  private static void printHelp() {
    print("A simple command-line IRC client based on IRClib.");
    print("");
    print("Use it as follows:");
    print("java IRC -server <server:port> [-pass <server-password<] -nick "+
        "<nickname> [-user <username>] [-name <realname>] [-ssl]");
    print("");
    print("Note that you need the IRClib classes in your classpath.");
    print("You can get IRClib from http://moepii.sourceforge.net.");
    print("");
    print("Copyright (C) 2003, 2004, 2005, 2006 Christoph Schwering");
  }

  /**
   * A shorthand for the System.out.println method.
   */
  private static void print(Object o) {
    System.out.println(o);
  }

  /**
   * Checks wether a string starts with another string (case insensitive).
   */
  private static boolean startsWith(String s1, String s2) {
    return (s1.length() >= s2.length()) ? 
        s1.substring(0, s2.length()).equalsIgnoreCase(s2) : false;
  }

  /**
   * Creates a new IRCConnection instance and starts the thread.
   * 
   * If you get confused by the two setDaemon()s: The conn.setDaemon(false) marks the 
   * IRCConnection thread as user thread and the setDaemon(true) marks this class's thread
   * (which just listens for keyboard input) as daemon thread. Thus, if the IRCConnection 
   * breaks, this console application shuts down, because due to the setDaemon(true) it 
   * will no longer wait for keyboard input (no input would make sense without being 
   * connected to a server).
   */
  public IRC(String host, int port, String pass, String nick, String user, 
      String name, boolean ssl) throws IOException {
    in = new BufferedReader(new InputStreamReader(System.in));
    if (!ssl) {
      conn = new IRCConnection(host, new int[] { port }, pass, nick, user, 
          name);
    } else {
      conn = new SSLIRCConnection(host, new int[] { port }, pass, nick, user,
          name);
      ((SSLIRCConnection)conn).addTrustManager(new SSLDefaultTrustManager());
    }
    conn.addIRCEventListener(new Listener());
    conn.setPong(true);
    conn.setDaemon(false);
    conn.setColors(false);
    conn.connect();
    setDaemon(true);
    start();
  }

  /**
   * The thread waits for input.
   */
  public void run() {
    while (true) {
      try {
        shipInput();
      } catch (Exception exc) { 
        exc.printStackTrace();
      }
    }
  }

  /**
   * Parses the input and sends it to the IRC server.
   */
  public void shipInput() throws Exception {
    String input = in.readLine();
    if (input == null || input.length() == 0)
      return;
    
    if (input.charAt(0) == '/') {
      if (startsWith(input, "/TARGET")) {
        target = input.substring(8);
        return;
      } else if (startsWith(input, "/JOIN")) {
        target = input.substring(6);
      }
      input = input.substring(1);
      print("Exec: "+ input);
      conn.send(input);
    } else {
      conn.doPrivmsg(target, input);
      print(target +"> "+ input);
    }
  }

  /**
   * Treats IRC events. The most of them are just printed.
   */
  public class Listener extends IRCEventAdapter implements IRCEventListener {

    public void onRegistered() {
      print("Connected");
    }
    
    public void onDisconnected() {
      print("Disconnected");
    }

    public void onError(String msg) {
      print("Error: "+ msg);
    }
    
    public void onError(int num, String msg) {
      print("Error #"+ num +": "+ msg);
    }

    public void onInvite(String chan, IRCUser u, String nickPass) {
      print(chan +"> "+ u.getNick() +" invites "+ nickPass);
    }

    public void onJoin(String chan, IRCUser u) {
      print(chan +"> "+ u.getNick() +" joins");
    }
    
    public void onKick(String chan, IRCUser u, String nickPass, String msg) {
      print(chan +"> "+ u.getNick() +" kicks "+ nickPass);
    }

    public void onMode(IRCUser u, String nickPass, String mode) {
      print("Mode: "+ u.getNick() +" sets modes "+ mode +" "+ 
          nickPass);
    }

    public void onMode(IRCUser u, String chan, IRCModeParser mp) {
      print(chan +"> "+ u.getNick() +" sets mode: "+ mp.getLine());
    }

    public void onNick(IRCUser u, String nickNew) {
      print("Nick: "+ u.getNick() +" is now known as "+ nickNew);
    }

    public void onNotice(String target, IRCUser u, String msg) {
      print(target +"> "+ u.getNick() +" (notice): "+ msg);
    }

    public void onPart(String chan, IRCUser u, String msg) {
      print(chan +"> "+ u.getNick() +" parts");
    }
    
    public void onPrivmsg(String chan, IRCUser u, String msg) {
      print(chan +"> "+ u.getNick() +": "+ msg);
    }

    public void onQuit(IRCUser u, String msg) {
      print("Quit: "+ u.getNick());
    }

    public void onReply(int num, String value, String msg) {
      print("Reply #"+ num +": "+ value +" "+ msg);
    }

    public void onTopic(String chan, IRCUser u, String topic) {
      print(chan +"> "+ u.getNick() +" changes topic into: "+ topic);
    }

  }

}

Back to main page SourceForge.net Logo