import java.math.*; import javax.imageio.*; import java.awt.image.*; import java.awt.event.*; import java.awt.*; import java.security.spec.*; import java.security.*; import java.lang.management.*; import java.lang.ref.*; import java.lang.reflect.*; import java.net.*; import java.io.*; import javax.swing.table.*; import javax.swing.text.*; import javax.swing.event.*; import javax.swing.*; import java.util.concurrent.atomic.*; import java.util.concurrent.*; import java.util.regex.*; import java.util.List; import java.util.zip.*; import java.util.*; import org.jibble.pircbot.*; public class main { public static String unquote(String s) { if (s.startsWith("[")) { int i = 1; while (i < s.length() && s.charAt(i) == '=') ++i; if (i < s.length() && s.charAt(i) == '[') { String m = s.substring(1, i); if (s.endsWith("]" + m + "]")) return s.substring(i+1, s.length()-i-1); } } if (s.startsWith("\"") && s.endsWith("\"") && s.length() > 1) { String st = s.substring(1, s.length()-1); StringBuilder sb = new StringBuilder(st.length()); for (int i = 0; i < st.length(); i++) { char ch = st.charAt(i); if (ch == '\\') { char nextChar = (i == st.length() - 1) ? '\\' : st .charAt(i + 1); // Octal escape? if (nextChar >= '0' && nextChar <= '7') { String code = "" + nextChar; i++; if ((i < st.length() - 1) && st.charAt(i + 1) >= '0' && st.charAt(i + 1) <= '7') { code += st.charAt(i + 1); i++; if ((i < st.length() - 1) && st.charAt(i + 1) >= '0' && st.charAt(i + 1) <= '7') { code += st.charAt(i + 1); i++; } } sb.append((char) Integer.parseInt(code, 8)); continue; } switch (nextChar) { case '\\': ch = '\\'; break; case 'b': ch = '\b'; break; case 'f': ch = '\f'; break; case 'n': ch = '\n'; break; case 'r': ch = '\r'; break; case 't': ch = '\t'; break; case '\"': ch = '\"'; break; case '\'': ch = '\''; break; // Hex Unicode: u???? case 'u': if (i >= st.length() - 5) { ch = 'u'; break; } int code = Integer.parseInt( "" + st.charAt(i + 2) + st.charAt(i + 3) + st.charAt(i + 4) + st.charAt(i + 5), 16); sb.append(Character.toChars(code)); i += 5; continue; default: ch = nextChar; // added by Stefan } i++; } sb.append(ch); } return sb.toString(); } else return s; // return original } // a persistent list that only grows (or is clear()ed) // Note: don't put in static initializer (programID not set yet) static class PersistentLog extends AbstractList { List l = new ArrayList(); File file; PersistentLog(String fileName) { this(getProgramFile(fileName)); } PersistentLog(String progID, String fileName) { this(getProgramFile(progID, fileName)); } PersistentLog(File file) { this.file = file; for (String s : scanLog(file)) try { l.add((A) unstructure(s)); } catch (Throwable __e) { printStackTrace(__e); } } public int size() { return l.size(); } public A get(int i) { return l.get(i); } public boolean add(A a) { l.add(a); logQuoted(file, structure(a)); return true; } String fileContents() { return loadTextFile(file); } public void clear() { l.clear(); file.delete(); } } static class Matches { String[] m; String get(int i) { return m[i]; } String unq(int i) { return unquote(m[i]); } String fsi(int i) { return formatSnippetID(unq(i)); } boolean bool(int i) { return "true".equals(unq(i)); } String rest() { return m[m.length-1]; } // for matchStart int psi(int i) { return Integer.parseInt(unq(i)); } } static abstract class PircBot implements ReplyConstants { /** * The definitive version number of this release of PircBot. * (Note: Change this before automatically building releases) */ public static final String VERSION = "1.5.0+"; private static final int OP_ADD = 1; private static final int OP_REMOVE = 2; private static final int VOICE_ADD = 3; private static final int VOICE_REMOVE = 4; /** * Constructs a PircBot with the default settings. Your own constructors * in classes which extend the PircBot abstract class should be responsible * for changing the default settings if required. */ public PircBot() {} /** * Attempt to connect to the specified IRC server. * The onConnect method is called upon success. * * @param hostname The hostname of the server to connect to. * * @throws IOException if it was not possible to connect to the server. * @throws IrcException if the server would not let us join it. * @throws NickAlreadyInUseException if our nick is already in use on the server. */ public final synchronized void connect(String hostname) throws IOException, IrcException, NickAlreadyInUseException { this.connect(hostname, 6667, null); } /** * Attempt to connect to the specified IRC server and port number. * The onConnect method is called upon success. * * @param hostname The hostname of the server to connect to. * @param port The port number to connect to on the server. * * @throws IOException if it was not possible to connect to the server. * @throws IrcException if the server would not let us join it. * @throws NickAlreadyInUseException if our nick is already in use on the server. */ public final synchronized void connect(String hostname, int port) throws IOException, IrcException, NickAlreadyInUseException { this.connect(hostname, port, null); } /** * Attempt to connect to the specified IRC server using the supplied * password. * The onConnect method is called upon success. * * @param hostname The hostname of the server to connect to. * @param port The port number to connect to on the server. * @param password The password to use to join the server. * * @throws IOException if it was not possible to connect to the server. * @throws IrcException if the server would not let us join it. * @throws NickAlreadyInUseException if our nick is already in use on the server. */ public final synchronized void connect(String hostname, int port, String password) throws IOException, IrcException, NickAlreadyInUseException { _server = hostname; _port = port; _password = password; if (isConnected()) { throw new IOException("The PircBot is already connected to an IRC server. Disconnect first."); } // Don't clear the outqueue - there might be something important in it! // Clear everything we may have know about channels. this.removeAllChannels(); // Connect to the server. Socket socket = new Socket(hostname, port); this.log("*** Connected to server."); _inetAddress = socket.getLocalAddress(); InputStreamReader inputStreamReader = null; OutputStreamWriter outputStreamWriter = null; if (getEncoding() != null) { // Assume the specified encoding is valid for this JVM. inputStreamReader = new InputStreamReader(socket.getInputStream(), getEncoding()); outputStreamWriter = new OutputStreamWriter(socket.getOutputStream(), getEncoding()); } else { // Otherwise, just use the JVM's default encoding. inputStreamReader = new InputStreamReader(socket.getInputStream()); outputStreamWriter = new OutputStreamWriter(socket.getOutputStream()); } BufferedReader breader = new BufferedReader(inputStreamReader); BufferedWriter bwriter = new BufferedWriter(outputStreamWriter); // Attempt to join the server. if (password != null && !password.equals("")) { OutputThread.sendRawLine(this, bwriter, "PASS " + password); } String nick = this.getName(); OutputThread.sendRawLine(this, bwriter, "NICK " + nick); OutputThread.sendRawLine(this, bwriter, "USER " + this.getLogin() + " 8 * :" + this.getVersion()); _inputThread = new InputThread(this, socket, breader, bwriter); // Read stuff back from the server to see if we connected. String line = null; int tries = 1; while ((line = breader.readLine()) != null) { this.handleLine(line); int firstSpace = line.indexOf(" "); int secondSpace = line.indexOf(" ", firstSpace + 1); if (secondSpace >= 0) { String code = line.substring(firstSpace + 1, secondSpace); if (code.equals("004")) { // We're connected to the server. break; } else if (code.equals("433")) { if (_autoNickChange) { tries++; nick = getName() + tries; OutputThread.sendRawLine(this, bwriter, "NICK " + nick); } else { socket.close(); _inputThread = null; throw new NickAlreadyInUseException(line); } } else if (code.equals("439")) { // No action required. } else if (code.startsWith("5") || code.startsWith("4")) { socket.close(); _inputThread = null; throw new IrcException("Could not log into the IRC server: " + line); } } this.setNick(nick); } this.log("*** Logged onto server."); // This makes the socket timeout on read operations after 5 minutes. // Maybe in some future version I will let the user change this at runtime. socket.setSoTimeout(5 * 60 * 1000); // Now start the InputThread to read all other lines from the server. _inputThread.start(); // Now start the outputThread that will be used to send all messages. if (_outputThread == null) { _outputThread = new OutputThread(this, _outQueue); _outputThread.start(); } this.onConnect(); } /** * Reconnects to the IRC server that we were previously connected to. * If necessary, the appropriate port number and password will be used. * This method will throw an IrcException if we have never connected * to an IRC server previously. * * @since PircBot 0.9.9 * * @throws IOException if it was not possible to connect to the server. * @throws IrcException if the server would not let us join it. * @throws NickAlreadyInUseException if our nick is already in use on the server. */ public final synchronized void reconnect() throws IOException, IrcException, NickAlreadyInUseException{ if (getServer() == null) { throw new IrcException("Cannot reconnect to an IRC server because we were never connected to one previously!"); } connect(getServer(), getPort(), getPassword()); } /** * This method disconnects from the server cleanly by calling the * quitServer() method. Providing the PircBot was connected to an * IRC server, the onDisconnect() will be called as soon as the * disconnection is made by the server. * * @see #quitServer() quitServer * @see #quitServer(String) quitServer */ public final synchronized void disconnect() { this.quitServer(); } /** * When you connect to a server and your nick is already in use and * this is set to true, a new nick will be automatically chosen. * This is done by adding numbers to the end of the nick until an * available nick is found. * * @param autoNickChange Set to true if you want automatic nick changes * during connection. */ public void setAutoNickChange(boolean autoNickChange) { _autoNickChange = autoNickChange; } /** * Starts an ident server (Identification Protocol Server, RFC 1413). *

* Most IRC servers attempt to contact the ident server on connecting * hosts in order to determine the user's identity. A few IRC servers * will not allow you to connect unless this information is provided. *

* So when a PircBot is run on a machine that does not run an ident server, * it may be necessary to call this method to start one up. *

* Calling this method starts up an ident server which will respond with * the login provided by calling getLogin() and then shut down immediately. * It will also be shut down if it has not been contacted within 60 seconds * of creation. *

* If you require an ident response, then the correct procedure is to start * the ident server and then connect to the IRC server. The IRC server may * then contact the ident server to get the information it needs. *

* The ident server will fail to start if there is already an ident server * running on port 113, or if you are running as an unprivileged user who * is unable to create a server socket on that port number. *

* If it is essential for you to use an ident server when connecting to an * IRC server, then make sure that port 113 on your machine is visible to * the IRC server so that it may contact the ident server. * * @since PircBot 0.9c */ public final void startIdentServer() { new IdentServer(this, getLogin()); } /** * Joins a channel. * * @param channel The name of the channel to join (eg "#cs"). */ public final void joinChannel(String channel) { this.sendRawLine("JOIN " + channel); } /** * Joins a channel with a key. * * @param channel The name of the channel to join (eg "#cs"). * @param key The key that will be used to join the channel. */ public final void joinChannel(String channel, String key) { this.joinChannel(channel + " " + key); } /** * Parts a channel. * * @param channel The name of the channel to leave. */ public final void partChannel(String channel) { this.sendRawLine("PART " + channel); } /** * Parts a channel, giving a reason. * * @param channel The name of the channel to leave. * @param reason The reason for parting the channel. */ public final void partChannel(String channel, String reason) { this.sendRawLine("PART " + channel + " :" + reason); } /** * Quits from the IRC server. * Providing we are actually connected to an IRC server, the * onDisconnect() method will be called as soon as the IRC server * disconnects us. */ public final void quitServer() { this.quitServer(""); } /** * Quits from the IRC server with a reason. * Providing we are actually connected to an IRC server, the * onDisconnect() method will be called as soon as the IRC server * disconnects us. * * @param reason The reason for quitting the server. */ public final void quitServer(String reason) { this.sendRawLine("QUIT :" + reason); } /** * Sends a raw line to the IRC server as soon as possible, bypassing the * outgoing message queue. * * @param line The raw line to send to the IRC server. */ public final synchronized void sendRawLine(String line) { if (isConnected()) { _inputThread.sendRawLine(line); } } /** * Sends a raw line through the outgoing message queue. * * @param line The raw line to send to the IRC server. */ public final synchronized void sendRawLineViaQueue(String line) { if (line == null) { throw new NullPointerException("Cannot send null messages to server"); } if (isConnected()) { _outQueue.add(line); } } /** * Sends a message to a channel or a private message to a user. These * messages are added to the outgoing message queue and sent at the * earliest possible opportunity. *

* Some examples: - *

    // Send the message "Hello!" to the channel #cs.
     *    sendMessage("#cs", "Hello!");
     *    
     *    // Send a private message to Paul that says "Hi".
     *    sendMessage("Paul", "Hi");
* * You may optionally apply colours, boldness, underlining, etc to * the message by using the Colors class. * * @param target The name of the channel or user nick to send to. * @param message The message to send. * * @see Colors */ public final void sendMessage(String target, String message) { _outQueue.add("PRIVMSG " + target + " :" + message); } /** * Sends an action to the channel or to a user. * * @param target The name of the channel or user nick to send to. * @param action The action to send. * * @see Colors */ public final void sendAction(String target, String action) { sendCTCPCommand(target, "ACTION " + action); } /** * Sends a notice to the channel or to a user. * * @param target The name of the channel or user nick to send to. * @param notice The notice to send. */ public final void sendNotice(String target, String notice) { _outQueue.add("NOTICE " + target + " :" + notice); } /** * Sends a CTCP command to a channel or user. (Client to client protocol). * Examples of such commands are "PING ", "FINGER", "VERSION", etc. * For example, if you wish to request the version of a user called "Dave", * then you would call sendCTCPCommand("Dave", "VERSION");. * The type of response to such commands is largely dependant on the target * client software. * * @since PircBot 0.9.5 * * @param target The name of the channel or user to send the CTCP message to. * @param command The CTCP command to send. */ public final void sendCTCPCommand(String target, String command) { _outQueue.add("PRIVMSG " + target + " :\u0001" + command + "\u0001"); } /** * Attempt to change the current nick (nickname) of the bot when it * is connected to an IRC server. * After confirmation of a successful nick change, the getNick method * will return the new nick. * * @param newNick The new nick to use. */ public final void changeNick(String newNick) { this.sendRawLine("NICK " + newNick); } /** * Identify the bot with NickServ, supplying the appropriate password. * Some IRC Networks (such as freenode) require users to register and * identify with NickServ before they are able to send private messages * to other users, thus reducing the amount of spam. If you are using * an IRC network where this kind of policy is enforced, you will need * to make your bot identify itself to NickServ before you can send * private messages. Assuming you have already registered your bot's * nick with NickServ, this method can be used to identify with * the supplied password. It usually makes sense to identify with NickServ * immediately after connecting to a server. *

* This method issues a raw NICKSERV command to the server, and is therefore * safer than the alternative approach of sending a private message to * NickServ. The latter approach is considered dangerous, as it may cause * you to inadvertently transmit your password to an untrusted party if you * connect to a network which does not run a NickServ service and where the * untrusted party has assumed the nick "NickServ". However, if your IRC * network is only compatible with the private message approach, you may * typically identify like so: *

sendMessage("NickServ", "identify PASSWORD");
* * @param password The password which will be used to identify with NickServ. */ public final void identify(String password) { this.sendRawLine("NICKSERV IDENTIFY " + password); } /** * Set the mode of a channel. * This method attempts to set the mode of a channel. This * may require the bot to have operator status on the channel. * For example, if the bot has operator status, we can grant * operator status to "Dave" on the #cs channel * by calling setMode("#cs", "+o Dave"); * An alternative way of doing this would be to use the op method. * * @param channel The channel on which to perform the mode change. * @param mode The new mode to apply to the channel. This may include * zero or more arguments if necessary. * * @see #op(String,String) op */ public final void setMode(String channel, String mode) { this.sendRawLine("MODE " + channel + " " + mode); } /** * Sends an invitation to join a channel. Some channels can be marked * as "invite-only", so it may be useful to allow a bot to invite people * into it. * * @param nick The nick of the user to invite * @param channel The channel you are inviting the user to join. * */ public final void sendInvite(String nick, String channel) { this.sendRawLine("INVITE " + nick + " :" + channel); } /** * Bans a user from a channel. An example of a valid hostmask is * "*!*compu@*.18hp.net". This may be used in conjunction with the * kick method to permanently remove a user from a channel. * Successful use of this method may require the bot to have operator * status itself. * * @param channel The channel to ban the user from. * @param hostmask A hostmask representing the user we're banning. */ public final void ban(String channel, String hostmask) { this.sendRawLine("MODE " + channel + " +b " + hostmask); } /** * Unbans a user from a channel. An example of a valid hostmask is * "*!*compu@*.18hp.net". * Successful use of this method may require the bot to have operator * status itself. * * @param channel The channel to unban the user from. * @param hostmask A hostmask representing the user we're unbanning. */ public final void unBan(String channel, String hostmask) { this.sendRawLine("MODE " + channel + " -b " + hostmask); } /** * Grants operator privilidges to a user on a channel. * Successful use of this method may require the bot to have operator * status itself. * * @param channel The channel we're opping the user on. * @param nick The nick of the user we are opping. */ public final void op(String channel, String nick) { this.setMode(channel, "+o " + nick); } /** * Removes operator privilidges from a user on a channel. * Successful use of this method may require the bot to have operator * status itself. * * @param channel The channel we're deopping the user on. * @param nick The nick of the user we are deopping. */ public final void deOp(String channel, String nick) { this.setMode(channel, "-o " + nick); } /** * Grants voice privilidges to a user on a channel. * Successful use of this method may require the bot to have operator * status itself. * * @param channel The channel we're voicing the user on. * @param nick The nick of the user we are voicing. */ public final void voice(String channel, String nick) { this.setMode(channel, "+v " + nick); } /** * Removes voice privilidges from a user on a channel. * Successful use of this method may require the bot to have operator * status itself. * * @param channel The channel we're devoicing the user on. * @param nick The nick of the user we are devoicing. */ public final void deVoice(String channel, String nick) { this.setMode(channel, "-v " + nick); } /** * Set the topic for a channel. * This method attempts to set the topic of a channel. This * may require the bot to have operator status if the topic * is protected. * * @param channel The channel on which to perform the mode change. * @param topic The new topic for the channel. * */ public final void setTopic(String channel, String topic) { this.sendRawLine("TOPIC " + channel + " :" + topic); } /** * Kicks a user from a channel. * This method attempts to kick a user from a channel and * may require the bot to have operator status in the channel. * * @param channel The channel to kick the user from. * @param nick The nick of the user to kick. */ public final void kick(String channel, String nick) { this.kick(channel, nick, ""); } /** * Kicks a user from a channel, giving a reason. * This method attempts to kick a user from a channel and * may require the bot to have operator status in the channel. * * @param channel The channel to kick the user from. * @param nick The nick of the user to kick. * @param reason A description of the reason for kicking a user. */ public final void kick(String channel, String nick, String reason) { this.sendRawLine("KICK " + channel + " " + nick + " :" + reason); } /** * Issues a request for a list of all channels on the IRC server. * When the PircBot receives information for each channel, it will * call the onChannelInfo method, which you will need to override * if you want it to do anything useful. * * @see #onChannelInfo(String,int,String) onChannelInfo */ public final void listChannels() { this.listChannels(null); } /** * Issues a request for a list of all channels on the IRC server. * When the PircBot receives information for each channel, it will * call the onChannelInfo method, which you will need to override * if you want it to do anything useful. *

* Some IRC servers support certain parameters for LIST requests. * One example is a parameter of ">10" to list only those channels * that have more than 10 users in them. Whether these parameters * are supported or not will depend on the IRC server software. * * @param parameters The parameters to supply when requesting the * list. * * @see #onChannelInfo(String,int,String) onChannelInfo */ public final void listChannels(String parameters) { if (parameters == null) { this.sendRawLine("LIST"); } else { this.sendRawLine("LIST " + parameters); } } /** * Sends a file to another user. Resuming is supported. * The other user must be able to connect directly to your bot to be * able to receive the file. *

* You may throttle the speed of this file transfer by calling the * setPacketDelay method on the DccFileTransfer that is returned. *

* This method may not be overridden. * * @since 0.9c * * @param file The file to send. * @param nick The user to whom the file is to be sent. * @param timeout The number of milliseconds to wait for the recipient to * acccept the file (we recommend about 120000). * * @return The DccFileTransfer that can be used to monitor this transfer. * * @see DccFileTransfer * */ public final DccFileTransfer dccSendFile(File file, String nick, int timeout) { DccFileTransfer transfer = new DccFileTransfer(this, _dccManager, file, nick, timeout); transfer.doSend(true); return transfer; } /** * Receives a file that is being sent to us by a DCC SEND request. * Please use the onIncomingFileTransfer method to receive files. * * @deprecated As of PircBot 1.2.0, use {@link #onIncomingFileTransfer(DccFileTransfer)} */ protected final void dccReceiveFile(File file, long address, int port, int size) { throw new RuntimeException("dccReceiveFile is deprecated, please use sendFile"); } /** * Attempts to establish a DCC CHAT session with a client. This method * issues the connection request to the client and then waits for the * client to respond. If the connection is successfully made, then a * DccChat object is returned by this method. If the connection is not * made within the time limit specified by the timeout value, then null * is returned. *

* It is strongly recommended that you call this method within a new * Thread, as it may take a long time to return. *

* This method may not be overridden. * * @since PircBot 0.9.8 * * @param nick The nick of the user we are trying to establish a chat with. * @param timeout The number of milliseconds to wait for the recipient to * accept the chat connection (we recommend about 120000). * * @return a DccChat object that can be used to send and recieve lines of * text. Returns null if the connection could not be made. * * @see DccChat */ public final DccChat dccSendChatRequest(String nick, int timeout) { DccChat chat = null; try { ServerSocket ss = null; int[] ports = getDccPorts(); if (ports == null) { // Use any free port. ss = new ServerSocket(0); } else { for (int i = 0; i < ports.length; i++) { try { ss = new ServerSocket(ports[i]); // Found a port number we could use. break; } catch (Exception e) { // Do nothing; go round and try another port. } } if (ss == null) { // No ports could be used. throw new IOException("All ports returned by getDccPorts() are in use."); } } ss.setSoTimeout(timeout); int port = ss.getLocalPort(); InetAddress inetAddress = getDccInetAddress(); if (inetAddress == null) { inetAddress = getInetAddress(); } byte[] ip = inetAddress.getAddress(); long ipNum = ipToLong(ip); sendCTCPCommand(nick, "DCC CHAT chat " + ipNum + " " + port); // The client may now connect to us to chat. Socket socket = ss.accept(); // Close the server socket now that we've finished with it. ss.close(); chat = new DccChat(this, nick, socket); } catch (Exception e) { // Do nothing. } return chat; } /** * Attempts to accept a DCC CHAT request by a client. * Please use the onIncomingChatRequest method to receive files. * * @deprecated As of PircBot 1.2.0, use {@link #onIncomingChatRequest(DccChat)} */ protected final DccChat dccAcceptChatRequest(String sourceNick, long address, int port) { throw new RuntimeException("dccAcceptChatRequest is deprecated, please use onIncomingChatRequest"); } /** * Adds a line to the log. This log is currently output to the standard * output and is in the correct format for use by tools such as pisg, the * Perl IRC Statistics Generator. You may override this method if you wish * to do something else with log entries. * Each line in the log begins with a number which * represents the logging time (as the number of milliseconds since the * epoch). This timestamp and the following log entry are separated by * a single space character, " ". Outgoing messages are distinguishable * by a log entry that has ">>>" immediately following the space character * after the timestamp. DCC events use "+++" and warnings about unhandled * Exceptions and Errors use "###". *

* This implementation of the method will only cause log entries to be * output if the PircBot has had its verbose mode turned on by calling * setVerbose(true); * * @param line The line to add to the log. */ public void log(String line) { if (_verbose) { System.out.println(System.currentTimeMillis() + " " + line); } } /** * This method handles events when any line of text arrives from the server, * then calling the appropriate method in the PircBot. This method is * protected and only called by the InputThread for this instance. *

* This method may not be overridden! * * @param line The raw line of text from the server. */ protected void handleLine(String line) { this.log(line); // Check for server pings. if (line.startsWith("PING ")) { // Respond to the ping and return immediately. this.onServerPing(line.substring(5)); return; } String sourceNick = ""; String sourceLogin = ""; String sourceHostname = ""; StringTokenizer tokenizer = new StringTokenizer(line); String senderInfo = tokenizer.nextToken(); String command = tokenizer.nextToken(); String target = null; int exclamation = senderInfo.indexOf("!"); int at = senderInfo.indexOf("@"); if (senderInfo.startsWith(":")) { if (exclamation > 0 && at > 0 && exclamation < at) { sourceNick = senderInfo.substring(1, exclamation); sourceLogin = senderInfo.substring(exclamation + 1, at); sourceHostname = senderInfo.substring(at + 1); } else { if (tokenizer.hasMoreTokens()) { String token = command; int code = -1; try { code = Integer.parseInt(token); } catch (NumberFormatException e) { // Keep the existing value. } if (code != -1) { String errorStr = token; String response = line.substring(line.indexOf(errorStr, senderInfo.length()) + 4, line.length()); this.processServerResponse(code, response); // Return from the method. return; } else { // This is not a server response. // It must be a nick without login and hostname. // (or maybe a NOTICE or suchlike from the server) sourceNick = senderInfo; target = token; } } else { // We don't know what this line means. this.onUnknown(line); // Return from the method; return; } } } command = command.toUpperCase(); if (sourceNick.startsWith(":")) { sourceNick = sourceNick.substring(1); } if (target == null) { target = tokenizer.nextToken(); } if (target.startsWith(":")) { target = target.substring(1); } // Check for CTCP requests. if (command.equals("PRIVMSG") && line.indexOf(":\u0001") > 0 && line.endsWith("\u0001")) { String request = line.substring(line.indexOf(":\u0001") + 2, line.length() - 1); if (request.equals("VERSION")) { // VERSION request this.onVersion(sourceNick, sourceLogin, sourceHostname, target); } else if (request.startsWith("ACTION ")) { // ACTION request this.onAction(sourceNick, sourceLogin, sourceHostname, target, request.substring(7)); } else if (request.startsWith("PING ")) { // PING request this.onPing(sourceNick, sourceLogin, sourceHostname, target, request.substring(5)); } else if (request.equals("TIME")) { // TIME request this.onTime(sourceNick, sourceLogin, sourceHostname, target); } else if (request.equals("FINGER")) { // FINGER request this.onFinger(sourceNick, sourceLogin, sourceHostname, target); } else if ((tokenizer = new StringTokenizer(request)).countTokens() >= 5 && tokenizer.nextToken().equals("DCC")) { // This is a DCC request. boolean success = _dccManager.processRequest(sourceNick, sourceLogin, sourceHostname, request); if (!success) { // The DccManager didn't know what to do with the line. this.onUnknown(line); } } else { // An unknown CTCP message - ignore it. this.onUnknown(line); } } else if (command.equals("PRIVMSG") && _channelPrefixes.indexOf(target.charAt(0)) >= 0) { // This is a normal message to a channel. this.onMessage(target, sourceNick, sourceLogin, sourceHostname, line.substring(line.indexOf(" :") + 2)); } else if (command.equals("PRIVMSG")) { // This is a private message to us. this.onPrivateMessage(sourceNick, sourceLogin, sourceHostname, line.substring(line.indexOf(" :") + 2)); } else if (command.equals("JOIN")) { // Someone is joining a channel. String channel = target; this.addUser(channel, new User("", sourceNick)); this.onJoin(channel, sourceNick, sourceLogin, sourceHostname); } else if (command.equals("PART")) { // Someone is parting from a channel. this.removeUser(target, sourceNick); if (sourceNick.equals(this.getNick())) { this.removeChannel(target); } this.onPart(target, sourceNick, sourceLogin, sourceHostname); } else if (command.equals("NICK")) { // Somebody is changing their nick. String newNick = target; this.renameUser(sourceNick, newNick); if (sourceNick.equals(this.getNick())) { // Update our nick if it was us that changed nick. this.setNick(newNick); } this.onNickChange(sourceNick, sourceLogin, sourceHostname, newNick); } else if (command.equals("NOTICE")) { // Someone is sending a notice. this.onNotice(sourceNick, sourceLogin, sourceHostname, target, line.substring(line.indexOf(" :") + 2)); } else if (command.equals("QUIT")) { // Someone has quit from the IRC server. if (sourceNick.equals(this.getNick())) { this.removeAllChannels(); } else { this.removeUser(sourceNick); } this.onQuit(sourceNick, sourceLogin, sourceHostname, line.substring(line.indexOf(" :") + 2)); } else if (command.equals("KICK")) { // Somebody has been kicked from a channel. String recipient = tokenizer.nextToken(); if (recipient.equals(this.getNick())) { this.removeChannel(target); } this.removeUser(target, recipient); this.onKick(target, sourceNick, sourceLogin, sourceHostname, recipient, line.substring(line.indexOf(" :") + 2)); } else if (command.equals("MODE")) { // Somebody is changing the mode on a channel or user. String mode = line.substring(line.indexOf(target, 2) + target.length() + 1); if (mode.startsWith(":")) { mode = mode.substring(1); } this.processMode(target, sourceNick, sourceLogin, sourceHostname, mode); } else if (command.equals("TOPIC")) { // Someone is changing the topic. this.onTopic(target, line.substring(line.indexOf(" :") + 2), sourceNick, System.currentTimeMillis(), true); } else if (command.equals("INVITE")) { // Somebody is inviting somebody else into a channel. this.onInvite(target, sourceNick, sourceLogin, sourceHostname, line.substring(line.indexOf(" :") + 2)); } else { // If we reach this point, then we've found something that the PircBot // Doesn't currently deal with. this.onUnknown(line); } } /** * This method is called once the PircBot has successfully connected to * the IRC server. *

* The implementation of this method in the PircBot abstract class * performs no actions and may be overridden as required. * * @since PircBot 0.9.6 */ protected void onConnect() {} /** * This method carries out the actions to be performed when the PircBot * gets disconnected. This may happen if the PircBot quits from the * server, or if the connection is unexpectedly lost. *

* Disconnection from the IRC server is detected immediately if either * we or the server close the connection normally. If the connection to * the server is lost, but neither we nor the server have explicitly closed * the connection, then it may take a few minutes to detect (this is * commonly referred to as a "ping timeout"). *

* If you wish to get your IRC bot to automatically rejoin a server after * the connection has been lost, then this is probably the ideal method to * override to implement such functionality. *

* The implementation of this method in the PircBot abstract class * performs no actions and may be overridden as required. */ protected void onDisconnect() {} /** * This method is called by the PircBot when a numeric response * is received from the IRC server. We use this method to * allow PircBot to process various responses from the server * before then passing them on to the onServerResponse method. *

* Note that this method is private and should not appear in any * of the javadoc generated documenation. * * @param code The three-digit numerical code for the response. * @param response The full response from the IRC server. */ private final void processServerResponse(int code, String response) { if (code == RPL_LIST) { // This is a bit of information about a channel. int firstSpace = response.indexOf(' '); int secondSpace = response.indexOf(' ', firstSpace + 1); int thirdSpace = response.indexOf(' ', secondSpace + 1); int colon = response.indexOf(':'); String channel = response.substring(firstSpace + 1, secondSpace); int userCount = 0; try { userCount = Integer.parseInt(response.substring(secondSpace + 1, thirdSpace)); } catch (NumberFormatException e) { // Stick with the value of zero. } String topic = response.substring(colon + 1); this.onChannelInfo(channel, userCount, topic); } else if (code == RPL_TOPIC) { // This is topic information about a channel we've just joined. int firstSpace = response.indexOf(' '); int secondSpace = response.indexOf(' ', firstSpace + 1); int colon = response.indexOf(':'); String channel = response.substring(firstSpace + 1, secondSpace); String topic = response.substring(colon + 1); _topics.put(channel, topic); // For backwards compatibility only - this onTopic method is deprecated. this.onTopic(channel, topic); } else if (code == RPL_TOPICINFO) { StringTokenizer tokenizer = new StringTokenizer(response); tokenizer.nextToken(); String channel = tokenizer.nextToken(); String setBy = tokenizer.nextToken(); long date = 0; try { date = Long.parseLong(tokenizer.nextToken()) * 1000; } catch (NumberFormatException e) { // Stick with the default value of zero. } String topic = (String) _topics.get(channel); _topics.remove(channel); this.onTopic(channel, topic, setBy, date, false); } else if (code == RPL_NAMREPLY) { // This is a list of nicks in a channel that we've just joined. int channelEndIndex = response.indexOf(" :"); String channel = response.substring(response.lastIndexOf(' ', channelEndIndex - 1) + 1, channelEndIndex); StringTokenizer tokenizer = new StringTokenizer(response.substring(response.indexOf(" :") + 2)); while (tokenizer.hasMoreTokens()) { String nick = tokenizer.nextToken(); String prefix = ""; if (nick.startsWith("@")) { // User is an operator in this channel. prefix = "@"; } else if (nick.startsWith("+")) { // User is voiced in this channel. prefix = "+"; } else if (nick.startsWith(".")) { // Some wibbly status I've never seen before... prefix = "."; } nick = nick.substring(prefix.length()); this.addUser(channel, new User(prefix, nick)); } } else if (code == RPL_ENDOFNAMES) { // This is the end of a NAMES list, so we know that we've got // the full list of users in the channel that we just joined. String channel = response.substring(response.indexOf(' ') + 1, response.indexOf(" :")); User[] users = this.getUsers(channel); this.onUserList(channel, users); } this.onServerResponse(code, response); } /** * This method is called when we receive a numeric response from the * IRC server. *

* Numerics in the range from 001 to 099 are used for client-server * connections only and should never travel between servers. Replies * generated in response to commands are found in the range from 200 * to 399. Error replies are found in the range from 400 to 599. *

* For example, we can use this method to discover the topic of a * channel when we join it. If we join the channel #test which * has a topic of "I am King of Test" then the response * will be "PircBot #test :I Am King of Test" * with a code of 332 to signify that this is a topic. * (This is just an example - note that overriding the * onTopic method is an easier way of finding the * topic for a channel). Check the IRC RFC for the full list of other * command response codes. *

* PircBot implements the interface ReplyConstants, which contains * contstants that you may find useful here. *

* The implementation of this method in the PircBot abstract class * performs no actions and may be overridden as required. * * @param code The three-digit numerical code for the response. * @param response The full response from the IRC server. * * @see ReplyConstants */ protected void onServerResponse(int code, String response) {} /** * This method is called when we receive a user list from the server * after joining a channel. *

* Shortly after joining a channel, the IRC server sends a list of all * users in that channel. The PircBot collects this information and * calls this method as soon as it has the full list. *

* To obtain the nick of each user in the channel, call the getNick() * method on each User object in the array. *

* At a later time, you may call the getUsers method to obtain an * up to date list of the users in the channel. *

* The implementation of this method in the PircBot abstract class * performs no actions and may be overridden as required. * * @since PircBot 1.0.0 * * @param channel The name of the channel. * @param users An array of User objects belonging to this channel. * * @see User */ protected void onUserList(String channel, User[] users) {} /** * This method is called whenever a message is sent to a channel. *

* The implementation of this method in the PircBot abstract class * performs no actions and may be overridden as required. * * @param channel The channel to which the message was sent. * @param sender The nick of the person who sent the message. * @param login The login of the person who sent the message. * @param hostname The hostname of the person who sent the message. * @param message The actual message sent to the channel. */ protected void onMessage(String channel, String sender, String login, String hostname, String message) {} /** * This method is called whenever a private message is sent to the PircBot. *

* The implementation of this method in the PircBot abstract class * performs no actions and may be overridden as required. * * @param sender The nick of the person who sent the private message. * @param login The login of the person who sent the private message. * @param hostname The hostname of the person who sent the private message. * @param message The actual message. */ protected void onPrivateMessage(String sender, String login, String hostname, String message) {} /** * This method is called whenever an ACTION is sent from a user. E.g. * such events generated by typing "/me goes shopping" in most IRC clients. *

* The implementation of this method in the PircBot abstract class * performs no actions and may be overridden as required. * * @param sender The nick of the user that sent the action. * @param login The login of the user that sent the action. * @param hostname The hostname of the user that sent the action. * @param target The target of the action, be it a channel or our nick. * @param action The action carried out by the user. */ protected void onAction(String sender, String login, String hostname, String target, String action) {} /** * This method is called whenever we receive a notice. *

* The implementation of this method in the PircBot abstract class * performs no actions and may be overridden as required. * * @param sourceNick The nick of the user that sent the notice. * @param sourceLogin The login of the user that sent the notice. * @param sourceHostname The hostname of the user that sent the notice. * @param target The target of the notice, be it our nick or a channel name. * @param notice The notice message. */ protected void onNotice(String sourceNick, String sourceLogin, String sourceHostname, String target, String notice) {} /** * This method is called whenever someone (possibly us) joins a channel * which we are on. *

* The implementation of this method in the PircBot abstract class * performs no actions and may be overridden as required. * * @param channel The channel which somebody joined. * @param sender The nick of the user who joined the channel. * @param login The login of the user who joined the channel. * @param hostname The hostname of the user who joined the channel. */ protected void onJoin(String channel, String sender, String login, String hostname) {} /** * This method is called whenever someone (possibly us) parts a channel * which we are on. *

* The implementation of this method in the PircBot abstract class * performs no actions and may be overridden as required. * * @param channel The channel which somebody parted from. * @param sender The nick of the user who parted from the channel. * @param login The login of the user who parted from the channel. * @param hostname The hostname of the user who parted from the channel. */ protected void onPart(String channel, String sender, String login, String hostname) {} /** * This method is called whenever someone (possibly us) changes nick on any * of the channels that we are on. *

* The implementation of this method in the PircBot abstract class * performs no actions and may be overridden as required. * * @param oldNick The old nick. * @param login The login of the user. * @param hostname The hostname of the user. * @param newNick The new nick. */ protected void onNickChange(String oldNick, String login, String hostname, String newNick) {} /** * This method is called whenever someone (possibly us) is kicked from * any of the channels that we are in. *

* The implementation of this method in the PircBot abstract class * performs no actions and may be overridden as required. * * @param channel The channel from which the recipient was kicked. * @param kickerNick The nick of the user who performed the kick. * @param kickerLogin The login of the user who performed the kick. * @param kickerHostname The hostname of the user who performed the kick. * @param recipientNick The unfortunate recipient of the kick. * @param reason The reason given by the user who performed the kick. */ protected void onKick(String channel, String kickerNick, String kickerLogin, String kickerHostname, String recipientNick, String reason) {} /** * This method is called whenever someone (possibly us) quits from the * server. We will only observe this if the user was in one of the * channels to which we are connected. *

* The implementation of this method in the PircBot abstract class * performs no actions and may be overridden as required. * * @param sourceNick The nick of the user that quit from the server. * @param sourceLogin The login of the user that quit from the server. * @param sourceHostname The hostname of the user that quit from the server. * @param reason The reason given for quitting the server. */ protected void onQuit(String sourceNick, String sourceLogin, String sourceHostname, String reason) {} /** * This method is called whenever a user sets the topic, or when * PircBot joins a new channel and discovers its topic. *

* The implementation of this method in the PircBot abstract class * performs no actions and may be overridden as required. * * @param channel The channel that the topic belongs to. * @param topic The topic for the channel. * * @deprecated As of 1.2.0, replaced by {@link #onTopic(String,String,String,long,boolean)} */ protected void onTopic(String channel, String topic) {} /** * This method is called whenever a user sets the topic, or when * PircBot joins a new channel and discovers its topic. *

* The implementation of this method in the PircBot abstract class * performs no actions and may be overridden as required. * * @param channel The channel that the topic belongs to. * @param topic The topic for the channel. * @param setBy The nick of the user that set the topic. * @param date When the topic was set (milliseconds since the epoch). * @param changed True if the topic has just been changed, false if * the topic was already there. * */ protected void onTopic(String channel, String topic, String setBy, long date, boolean changed) {} /** * After calling the listChannels() method in PircBot, the server * will start to send us information about each channel on the * server. You may override this method in order to receive the * information about each channel as soon as it is received. *

* Note that certain channels, such as those marked as hidden, * may not appear in channel listings. *

* The implementation of this method in the PircBot abstract class * performs no actions and may be overridden as required. * * @param channel The name of the channel. * @param userCount The number of users visible in this channel. * @param topic The topic for this channel. * * @see #listChannels() listChannels */ protected void onChannelInfo(String channel, int userCount, String topic) {} /** * Called when the mode of a channel is set. We process this in * order to call the appropriate onOp, onDeop, etc method before * finally calling the override-able onMode method. *

* Note that this method is private and is not intended to appear * in the javadoc generated documentation. * * @param target The channel or nick that the mode operation applies to. * @param sourceNick The nick of the user that set the mode. * @param sourceLogin The login of the user that set the mode. * @param sourceHostname The hostname of the user that set the mode. * @param mode The mode that has been set. */ private final void processMode(String target, String sourceNick, String sourceLogin, String sourceHostname, String mode) { if (_channelPrefixes.indexOf(target.charAt(0)) >= 0) { // The mode of a channel is being changed. String channel = target; StringTokenizer tok = new StringTokenizer(mode); String[] params = new String[tok.countTokens()]; int t = 0; while (tok.hasMoreTokens()) { params[t] = tok.nextToken(); t++; } char pn = ' '; int p = 1; // All of this is very large and ugly, but it's the only way of providing // what the users want :-/ for (int i = 0; i < params[0].length(); i++) { char atPos = params[0].charAt(i); if (atPos == '+' || atPos == '-') { pn = atPos; } else if (atPos == 'o') { if (pn == '+') { this.updateUser(channel, OP_ADD, params[p]); onOp(channel, sourceNick, sourceLogin, sourceHostname, params[p]); } else { this.updateUser(channel, OP_REMOVE, params[p]); onDeop(channel, sourceNick, sourceLogin, sourceHostname, params[p]); } p++; } else if (atPos == 'v') { if (pn == '+') { this.updateUser(channel, VOICE_ADD, params[p]); onVoice(channel, sourceNick, sourceLogin, sourceHostname, params[p]); } else { this.updateUser(channel, VOICE_REMOVE, params[p]); onDeVoice(channel, sourceNick, sourceLogin, sourceHostname, params[p]); } p++; } else if (atPos == 'k') { if (pn == '+') { onSetChannelKey(channel, sourceNick, sourceLogin, sourceHostname, params[p]); } else { onRemoveChannelKey(channel, sourceNick, sourceLogin, sourceHostname, params[p]); } p++; } else if (atPos == 'l') { if (pn == '+') { onSetChannelLimit(channel, sourceNick, sourceLogin, sourceHostname, Integer.parseInt(params[p])); p++; } else { onRemoveChannelLimit(channel, sourceNick, sourceLogin, sourceHostname); } } else if (atPos == 'b') { if (pn == '+') { onSetChannelBan(channel, sourceNick, sourceLogin, sourceHostname,params[p]); } else { onRemoveChannelBan(channel, sourceNick, sourceLogin, sourceHostname, params[p]); } p++; } else if (atPos == 't') { if (pn == '+') { onSetTopicProtection(channel, sourceNick, sourceLogin, sourceHostname); } else { onRemoveTopicProtection(channel, sourceNick, sourceLogin, sourceHostname); } } else if (atPos == 'n') { if (pn == '+') { onSetNoExternalMessages(channel, sourceNick, sourceLogin, sourceHostname); } else { onRemoveNoExternalMessages(channel, sourceNick, sourceLogin, sourceHostname); } } else if (atPos == 'i') { if (pn == '+') { onSetInviteOnly(channel, sourceNick, sourceLogin, sourceHostname); } else { onRemoveInviteOnly(channel, sourceNick, sourceLogin, sourceHostname); } } else if (atPos == 'm') { if (pn == '+') { onSetModerated(channel, sourceNick, sourceLogin, sourceHostname); } else { onRemoveModerated(channel, sourceNick, sourceLogin, sourceHostname); } } else if (atPos == 'p') { if (pn == '+') { onSetPrivate(channel, sourceNick, sourceLogin, sourceHostname); } else { onRemovePrivate(channel, sourceNick, sourceLogin, sourceHostname); } } else if (atPos == 's') { if (pn == '+') { onSetSecret(channel, sourceNick, sourceLogin, sourceHostname); } else { onRemoveSecret(channel, sourceNick, sourceLogin, sourceHostname); } } } this.onMode(channel, sourceNick, sourceLogin, sourceHostname, mode); } else { // The mode of a user is being changed. String nick = target; this.onUserMode(nick, sourceNick, sourceLogin, sourceHostname, mode); } } /** * Called when the mode of a channel is set. *

* You may find it more convenient to decode the meaning of the mode * string by overriding the onOp, onDeOp, onVoice, onDeVoice, * onChannelKey, onDeChannelKey, onChannelLimit, onDeChannelLimit, * onChannelBan or onDeChannelBan methods as appropriate. *

* The implementation of this method in the PircBot abstract class * performs no actions and may be overridden as required. * * @param channel The channel that the mode operation applies to. * @param sourceNick The nick of the user that set the mode. * @param sourceLogin The login of the user that set the mode. * @param sourceHostname The hostname of the user that set the mode. * @param mode The mode that has been set. * */ protected void onMode(String channel, String sourceNick, String sourceLogin, String sourceHostname, String mode) {} /** * Called when the mode of a user is set. *

* The implementation of this method in the PircBot abstract class * performs no actions and may be overridden as required. * * @since PircBot 1.2.0 * * @param targetNick The nick that the mode operation applies to. * @param sourceNick The nick of the user that set the mode. * @param sourceLogin The login of the user that set the mode. * @param sourceHostname The hostname of the user that set the mode. * @param mode The mode that has been set. * */ protected void onUserMode(String targetNick, String sourceNick, String sourceLogin, String sourceHostname, String mode) {} /** * Called when a user (possibly us) gets granted operator status for a channel. *

* This is a type of mode change and is also passed to the onMode * method in the PircBot class. *

* The implementation of this method in the PircBot abstract class * performs no actions and may be overridden as required. * * @since PircBot 0.9.5 * * @param channel The channel in which the mode change took place. * @param sourceNick The nick of the user that performed the mode change. * @param sourceLogin The login of the user that performed the mode change. * @param sourceHostname The hostname of the user that performed the mode change. * @param recipient The nick of the user that got 'opped'. */ protected void onOp(String channel, String sourceNick, String sourceLogin, String sourceHostname, String recipient) {} /** * Called when a user (possibly us) gets operator status taken away. *

* This is a type of mode change and is also passed to the onMode * method in the PircBot class. *

* The implementation of this method in the PircBot abstract class * performs no actions and may be overridden as required. * * @since PircBot 0.9.5 * * @param channel The channel in which the mode change took place. * @param sourceNick The nick of the user that performed the mode change. * @param sourceLogin The login of the user that performed the mode change. * @param sourceHostname The hostname of the user that performed the mode change. * @param recipient The nick of the user that got 'deopped'. */ protected void onDeop(String channel, String sourceNick, String sourceLogin, String sourceHostname, String recipient) {} /** * Called when a user (possibly us) gets voice status granted in a channel. *

* This is a type of mode change and is also passed to the onMode * method in the PircBot class. *

* The implementation of this method in the PircBot abstract class * performs no actions and may be overridden as required. * * @since PircBot 0.9.5 * * @param channel The channel in which the mode change took place. * @param sourceNick The nick of the user that performed the mode change. * @param sourceLogin The login of the user that performed the mode change. * @param sourceHostname The hostname of the user that performed the mode change. * @param recipient The nick of the user that got 'voiced'. */ protected void onVoice(String channel, String sourceNick, String sourceLogin, String sourceHostname, String recipient) {} /** * Called when a user (possibly us) gets voice status removed. *

* This is a type of mode change and is also passed to the onMode * method in the PircBot class. *

* The implementation of this method in the PircBot abstract class * performs no actions and may be overridden as required. * * @since PircBot 0.9.5 * * @param channel The channel in which the mode change took place. * @param sourceNick The nick of the user that performed the mode change. * @param sourceLogin The login of the user that performed the mode change. * @param sourceHostname The hostname of the user that performed the mode change. * @param recipient The nick of the user that got 'devoiced'. */ protected void onDeVoice(String channel, String sourceNick, String sourceLogin, String sourceHostname, String recipient) {} /** * Called when a channel key is set. When the channel key has been set, * other users may only join that channel if they know the key. Channel keys * are sometimes referred to as passwords. *

* This is a type of mode change and is also passed to the onMode * method in the PircBot class. *

* The implementation of this method in the PircBot abstract class * performs no actions and may be overridden as required. * * @since PircBot 0.9.5 * * @param channel The channel in which the mode change took place. * @param sourceNick The nick of the user that performed the mode change. * @param sourceLogin The login of the user that performed the mode change. * @param sourceHostname The hostname of the user that performed the mode change. * @param key The new key for the channel. */ protected void onSetChannelKey(String channel, String sourceNick, String sourceLogin, String sourceHostname, String key) {} /** * Called when a channel key is removed. *

* This is a type of mode change and is also passed to the onMode * method in the PircBot class. *

* The implementation of this method in the PircBot abstract class * performs no actions and may be overridden as required. * * @since PircBot 0.9.5 * * @param channel The channel in which the mode change took place. * @param sourceNick The nick of the user that performed the mode change. * @param sourceLogin The login of the user that performed the mode change. * @param sourceHostname The hostname of the user that performed the mode change. * @param key The key that was in use before the channel key was removed. */ protected void onRemoveChannelKey(String channel, String sourceNick, String sourceLogin, String sourceHostname, String key) {} /** * Called when a user limit is set for a channel. The number of users in * the channel cannot exceed this limit. *

* This is a type of mode change and is also passed to the onMode * method in the PircBot class. *

* The implementation of this method in the PircBot abstract class * performs no actions and may be overridden as required. * * @since PircBot 0.9.5 * * @param channel The channel in which the mode change took place. * @param sourceNick The nick of the user that performed the mode change. * @param sourceLogin The login of the user that performed the mode change. * @param sourceHostname The hostname of the user that performed the mode change. * @param limit The maximum number of users that may be in this channel at the same time. */ protected void onSetChannelLimit(String channel, String sourceNick, String sourceLogin, String sourceHostname, int limit) {} /** * Called when the user limit is removed for a channel. *

* This is a type of mode change and is also passed to the onMode * method in the PircBot class. *

* The implementation of this method in the PircBot abstract class * performs no actions and may be overridden as required. * * @since PircBot 0.9.5 * * @param channel The channel in which the mode change took place. * @param sourceNick The nick of the user that performed the mode change. * @param sourceLogin The login of the user that performed the mode change. * @param sourceHostname The hostname of the user that performed the mode change. */ protected void onRemoveChannelLimit(String channel, String sourceNick, String sourceLogin, String sourceHostname) {} /** * Called when a user (possibly us) gets banned from a channel. Being * banned from a channel prevents any user with a matching hostmask from * joining the channel. For this reason, most bans are usually directly * followed by the user being kicked :-) *

* This is a type of mode change and is also passed to the onMode * method in the PircBot class. *

* The implementation of this method in the PircBot abstract class * performs no actions and may be overridden as required. * * @since PircBot 0.9.5 * * @param channel The channel in which the mode change took place. * @param sourceNick The nick of the user that performed the mode change. * @param sourceLogin The login of the user that performed the mode change. * @param sourceHostname The hostname of the user that performed the mode change. * @param hostmask The hostmask of the user that has been banned. */ protected void onSetChannelBan(String channel, String sourceNick, String sourceLogin, String sourceHostname, String hostmask) {} /** * Called when a hostmask ban is removed from a channel. *

* This is a type of mode change and is also passed to the onMode * method in the PircBot class. *

* The implementation of this method in the PircBot abstract class * performs no actions and may be overridden as required. * * @since PircBot 0.9.5 * * @param channel The channel in which the mode change took place. * @param sourceNick The nick of the user that performed the mode change. * @param sourceLogin The login of the user that performed the mode change. * @param sourceHostname The hostname of the user that performed the mode change. * @param hostmask */ protected void onRemoveChannelBan(String channel, String sourceNick, String sourceLogin, String sourceHostname, String hostmask) {} /** * Called when topic protection is enabled for a channel. Topic protection * means that only operators in a channel may change the topic. *

* This is a type of mode change and is also passed to the onMode * method in the PircBot class. *

* The implementation of this method in the PircBot abstract class * performs no actions and may be overridden as required. * * @since PircBot 0.9.5 * * @param channel The channel in which the mode change took place. * @param sourceNick The nick of the user that performed the mode change. * @param sourceLogin The login of the user that performed the mode change. * @param sourceHostname The hostname of the user that performed the mode change. */ protected void onSetTopicProtection(String channel, String sourceNick, String sourceLogin, String sourceHostname) {} /** * Called when topic protection is removed for a channel. *

* This is a type of mode change and is also passed to the onMode * method in the PircBot class. *

* The implementation of this method in the PircBot abstract class * performs no actions and may be overridden as required. * * @since PircBot 0.9.5 * * @param channel The channel in which the mode change took place. * @param sourceNick The nick of the user that performed the mode change. * @param sourceLogin The login of the user that performed the mode change. * @param sourceHostname The hostname of the user that performed the mode change. */ protected void onRemoveTopicProtection(String channel, String sourceNick, String sourceLogin, String sourceHostname) {} /** * Called when a channel is set to only allow messages from users that * are in the channel. *

* This is a type of mode change and is also passed to the onMode * method in the PircBot class. *

* The implementation of this method in the PircBot abstract class * performs no actions and may be overridden as required. * * @since PircBot 0.9.5 * * @param channel The channel in which the mode change took place. * @param sourceNick The nick of the user that performed the mode change. * @param sourceLogin The login of the user that performed the mode change. * @param sourceHostname The hostname of the user that performed the mode change. */ protected void onSetNoExternalMessages(String channel, String sourceNick, String sourceLogin, String sourceHostname) {} /** * Called when a channel is set to allow messages from any user, even * if they are not actually in the channel. *

* This is a type of mode change and is also passed to the onMode * method in the PircBot class. *

* The implementation of this method in the PircBot abstract class * performs no actions and may be overridden as required. * * @since PircBot 0.9.5 * * @param channel The channel in which the mode change took place. * @param sourceNick The nick of the user that performed the mode change. * @param sourceLogin The login of the user that performed the mode change. * @param sourceHostname The hostname of the user that performed the mode change. */ protected void onRemoveNoExternalMessages(String channel, String sourceNick, String sourceLogin, String sourceHostname) {} /** * Called when a channel is set to 'invite only' mode. A user may only * join the channel if they are invited by someone who is already in the * channel. *

* This is a type of mode change and is also passed to the onMode * method in the PircBot class. *

* The implementation of this method in the PircBot abstract class * performs no actions and may be overridden as required. * * @since PircBot 0.9.5 * * @param channel The channel in which the mode change took place. * @param sourceNick The nick of the user that performed the mode change. * @param sourceLogin The login of the user that performed the mode change. * @param sourceHostname The hostname of the user that performed the mode change. */ protected void onSetInviteOnly(String channel, String sourceNick, String sourceLogin, String sourceHostname) {} /** * Called when a channel has 'invite only' removed. *

* This is a type of mode change and is also passed to the onMode * method in the PircBot class. *

* The implementation of this method in the PircBot abstract class * performs no actions and may be overridden as required. * * @since PircBot 0.9.5 * * @param channel The channel in which the mode change took place. * @param sourceNick The nick of the user that performed the mode change. * @param sourceLogin The login of the user that performed the mode change. * @param sourceHostname The hostname of the user that performed the mode change. */ protected void onRemoveInviteOnly(String channel, String sourceNick, String sourceLogin, String sourceHostname) {} /** * Called when a channel is set to 'moderated' mode. If a channel is * moderated, then only users who have been 'voiced' or 'opped' may speak * or change their nicks. *

* This is a type of mode change and is also passed to the onMode * method in the PircBot class. *

* The implementation of this method in the PircBot abstract class * performs no actions and may be overridden as required. * * @since PircBot 0.9.5 * * @param channel The channel in which the mode change took place. * @param sourceNick The nick of the user that performed the mode change. * @param sourceLogin The login of the user that performed the mode change. * @param sourceHostname The hostname of the user that performed the mode change. */ protected void onSetModerated(String channel, String sourceNick, String sourceLogin, String sourceHostname) {} /** * Called when a channel has moderated mode removed. *

* This is a type of mode change and is also passed to the onMode * method in the PircBot class. *

* The implementation of this method in the PircBot abstract class * performs no actions and may be overridden as required. * * @since PircBot 0.9.5 * * @param channel The channel in which the mode change took place. * @param sourceNick The nick of the user that performed the mode change. * @param sourceLogin The login of the user that performed the mode change. * @param sourceHostname The hostname of the user that performed the mode change. */ protected void onRemoveModerated(String channel, String sourceNick, String sourceLogin, String sourceHostname) {} /** * Called when a channel is marked as being in private mode. *

* This is a type of mode change and is also passed to the onMode * method in the PircBot class. *

* The implementation of this method in the PircBot abstract class * performs no actions and may be overridden as required. * * @since PircBot 0.9.5 * * @param channel The channel in which the mode change took place. * @param sourceNick The nick of the user that performed the mode change. * @param sourceLogin The login of the user that performed the mode change. * @param sourceHostname The hostname of the user that performed the mode change. */ protected void onSetPrivate(String channel, String sourceNick, String sourceLogin, String sourceHostname) {} /** * Called when a channel is marked as not being in private mode. *

* This is a type of mode change and is also passed to the onMode * method in the PircBot class. *

* The implementation of this method in the PircBot abstract class * performs no actions and may be overridden as required. * * @since PircBot 0.9.5 * * @param channel The channel in which the mode change took place. * @param sourceNick The nick of the user that performed the mode change. * @param sourceLogin The login of the user that performed the mode change. * @param sourceHostname The hostname of the user that performed the mode change. */ protected void onRemovePrivate(String channel, String sourceNick, String sourceLogin, String sourceHostname) {} /** * Called when a channel is set to be in 'secret' mode. Such channels * typically do not appear on a server's channel listing. *

* This is a type of mode change and is also passed to the onMode * method in the PircBot class. *

* The implementation of this method in the PircBot abstract class * performs no actions and may be overridden as required. * * @since PircBot 0.9.5 * * @param channel The channel in which the mode change took place. * @param sourceNick The nick of the user that performed the mode change. * @param sourceLogin The login of the user that performed the mode change. * @param sourceHostname The hostname of the user that performed the mode change. */ protected void onSetSecret(String channel, String sourceNick, String sourceLogin, String sourceHostname) {} /** * Called when a channel has 'secret' mode removed. *

* This is a type of mode change and is also passed to the onMode * method in the PircBot class. *

* The implementation of this method in the PircBot abstract class * performs no actions and may be overridden as required. * * @since PircBot 0.9.5 * * @param channel The channel in which the mode change took place. * @param sourceNick The nick of the user that performed the mode change. * @param sourceLogin The login of the user that performed the mode change. * @param sourceHostname The hostname of the user that performed the mode change. */ protected void onRemoveSecret(String channel, String sourceNick, String sourceLogin, String sourceHostname) {} /** * Called when we are invited to a channel by a user. *

* The implementation of this method in the PircBot abstract class * performs no actions and may be overridden as required. * * @since PircBot 0.9.5 * * @param targetNick The nick of the user being invited - should be us! * @param sourceNick The nick of the user that sent the invitation. * @param sourceLogin The login of the user that sent the invitation. * @param sourceHostname The hostname of the user that sent the invitation. * @param channel The channel that we're being invited to. */ protected void onInvite(String targetNick, String sourceNick, String sourceLogin, String sourceHostname, String channel) {} /** * This method used to be called when a DCC SEND request was sent to the PircBot. * Please use the onIncomingFileTransfer method to receive files, as it * has better functionality and supports resuming. * * @deprecated As of PircBot 1.2.0, use {@link #onIncomingFileTransfer(DccFileTransfer)} */ protected void onDccSendRequest(String sourceNick, String sourceLogin, String sourceHostname, String filename, long address, int port, int size) {} /** * This method used to be called when a DCC CHAT request was sent to the PircBot. * Please use the onIncomingChatRequest method to accept chats, as it * has better functionality. * * @deprecated As of PircBot 1.2.0, use {@link #onIncomingChatRequest(DccChat)} */ protected void onDccChatRequest(String sourceNick, String sourceLogin, String sourceHostname, long address, int port) {} /** * This method is called whenever a DCC SEND request is sent to the PircBot. * This means that a client has requested to send a file to us. * This abstract implementation performs no action, which means that all * DCC SEND requests will be ignored by default. If you wish to receive * the file, then you may override this method and call the receive method * on the DccFileTransfer object, which connects to the sender and downloads * the file. *

* Example: *

 public void onIncomingFileTransfer(DccFileTransfer transfer) {
     *     // Use the suggested file name.
     *     File file = transfer.getFile();
     *     // Receive the transfer and save it to the file, allowing resuming.
     *     transfer.receive(file, true);
     * }
*

* Warning: Receiving an incoming file transfer will cause a file * to be written to disk. Please ensure that you make adequate security * checks so that this file does not overwrite anything important! *

* Each time a file is received, it happens within a new Thread * in order to allow multiple files to be downloaded by the PircBot * at the same time. *

* If you allow resuming and the file already partly exists, it will * be appended to instead of overwritten. If resuming is not enabled, * the file will be overwritten if it already exists. *

* You can throttle the speed of the transfer by calling the setPacketDelay * method on the DccFileTransfer object, either before you receive the * file or at any moment during the transfer. *

* The implementation of this method in the PircBot abstract class * performs no actions and may be overridden as required. * * @since PircBot 1.2.0 * * @param transfer The DcccFileTransfer that you may accept. * * @see DccFileTransfer * */ protected void onIncomingFileTransfer(DccFileTransfer transfer) {} /** * This method gets called when a DccFileTransfer has finished. * If there was a problem, the Exception will say what went wrong. * If the file was sent successfully, the Exception will be null. *

* Both incoming and outgoing file transfers are passed to this method. * You can determine the type by calling the isIncoming or isOutgoing * methods on the DccFileTransfer object. * * @since PircBot 1.2.0 * * @param transfer The DccFileTransfer that has finished. * @param e null if the file was transfered successfully, otherwise this * will report what went wrong. * * @see DccFileTransfer * */ protected void onFileTransferFinished(DccFileTransfer transfer, Exception e) {} /** * This method will be called whenever a DCC Chat request is received. * This means that a client has requested to chat to us directly rather * than via the IRC server. This is useful for sending many lines of text * to and from the bot without having to worry about flooding the server * or any operators of the server being able to "spy" on what is being * said. This abstract implementation performs no action, which means * that all DCC CHAT requests will be ignored by default. *

* If you wish to accept the connection, then you may override this * method and call the accept() method on the DccChat object, which * connects to the sender of the chat request and allows lines to be * sent to and from the bot. *

* Your bot must be able to connect directly to the user that sent the * request. *

* Example: *

 public void onIncomingChatRequest(DccChat chat) {
     *     try {
     *         // Accept all chat, whoever it's from.
     *         chat.accept();
     *         chat.sendLine("Hello");
     *         String response = chat.readLine();
     *         chat.close();
     *     }
     *     catch (IOException e) {}
     * }
* * Each time this method is called, it is called from within a new Thread * so that multiple DCC CHAT sessions can run concurrently. *

* The implementation of this method in the PircBot abstract class * performs no actions and may be overridden as required. * * @since PircBot 1.2.0 * * @param chat A DccChat object that represents the incoming chat request. * * @see DccChat * */ protected void onIncomingChatRequest(DccChat chat) {} /** * This method is called whenever we receive a VERSION request. * This abstract implementation responds with the PircBot's _version string, * so if you override this method, be sure to either mimic its functionality * or to call super.onVersion(...); * * @param sourceNick The nick of the user that sent the VERSION request. * @param sourceLogin The login of the user that sent the VERSION request. * @param sourceHostname The hostname of the user that sent the VERSION request. * @param target The target of the VERSION request, be it our nick or a channel name. */ protected void onVersion(String sourceNick, String sourceLogin, String sourceHostname, String target) { this.sendRawLine("NOTICE " + sourceNick + " :\u0001VERSION " + _version + "\u0001"); } /** * This method is called whenever we receive a PING request from another * user. *

* This abstract implementation responds correctly, so if you override this * method, be sure to either mimic its functionality or to call * super.onPing(...); * * @param sourceNick The nick of the user that sent the PING request. * @param sourceLogin The login of the user that sent the PING request. * @param sourceHostname The hostname of the user that sent the PING request. * @param target The target of the PING request, be it our nick or a channel name. * @param pingValue The value that was supplied as an argument to the PING command. */ protected void onPing(String sourceNick, String sourceLogin, String sourceHostname, String target, String pingValue) { this.sendRawLine("NOTICE " + sourceNick + " :\u0001PING " + pingValue + "\u0001"); } /** * The actions to perform when a PING request comes from the server. *

* This sends back a correct response, so if you override this method, * be sure to either mimic its functionality or to call * super.onServerPing(response); * * @param response The response that should be given back in your PONG. */ protected void onServerPing(String response) { this.sendRawLine("PONG " + response); } /** * This method is called whenever we receive a TIME request. *

* This abstract implementation responds correctly, so if you override this * method, be sure to either mimic its functionality or to call * super.onTime(...); * * @param sourceNick The nick of the user that sent the TIME request. * @param sourceLogin The login of the user that sent the TIME request. * @param sourceHostname The hostname of the user that sent the TIME request. * @param target The target of the TIME request, be it our nick or a channel name. */ protected void onTime(String sourceNick, String sourceLogin, String sourceHostname, String target) { this.sendRawLine("NOTICE " + sourceNick + " :\u0001TIME " + new Date().toString() + "\u0001"); } /** * This method is called whenever we receive a FINGER request. *

* This abstract implementation responds correctly, so if you override this * method, be sure to either mimic its functionality or to call * super.onFinger(...); * * @param sourceNick The nick of the user that sent the FINGER request. * @param sourceLogin The login of the user that sent the FINGER request. * @param sourceHostname The hostname of the user that sent the FINGER request. * @param target The target of the FINGER request, be it our nick or a channel name. */ protected void onFinger(String sourceNick, String sourceLogin, String sourceHostname, String target) { this.sendRawLine("NOTICE " + sourceNick + " :\u0001FINGER " + _finger + "\u0001"); } /** * This method is called whenever we receive a line from the server that * the PircBot has not been programmed to recognise. *

* The implementation of this method in the PircBot abstract class * performs no actions and may be overridden as required. * * @param line The raw line that was received from the server. */ protected void onUnknown(String line) { // And then there were none :) } /** * Sets the verbose mode. If verbose mode is set to true, then log entries * will be printed to the standard output. The default value is false and * will result in no output. For general development, we strongly recommend * setting the verbose mode to true. * * @param verbose true if verbose mode is to be used. Default is false. */ public final void setVerbose(boolean verbose) { _verbose = verbose; } /** * Sets the name of the bot, which will be used as its nick when it * tries to join an IRC server. This should be set before joining * any servers, otherwise the default nick will be used. You would * typically call this method from the constructor of the class that * extends PircBot. *

* The changeNick method should be used if you wish to change your nick * when you are connected to a server. * * @param name The new name of the Bot. */ protected final void setName(String name) { _name = name; } /** * Sets the internal nick of the bot. This is only to be called by the * PircBot class in response to notification of nick changes that apply * to us. * * @param nick The new nick. */ private final void setNick(String nick) { _nick = nick; } /** * Sets the internal login of the Bot. This should be set before joining * any servers. * * @param login The new login of the Bot. */ protected final void setLogin(String login) { _login = login; } /** * Sets the internal version of the Bot. This should be set before joining * any servers. * * @param version The new version of the Bot. */ protected final void setVersion(String version) { _version = version; } /** * Sets the interal finger message. This should be set before joining * any servers. * * @param finger The new finger message for the Bot. */ protected final void setFinger(String finger) { _finger = finger; } /** * Gets the name of the PircBot. This is the name that will be used as * as a nick when we try to join servers. * * @return The name of the PircBot. */ public final String getName() { return _name; } /** * Returns the current nick of the bot. Note that if you have just changed * your nick, this method will still return the old nick until confirmation * of the nick change is received from the server. *

* The nick returned by this method is maintained only by the PircBot * class and is guaranteed to be correct in the context of the IRC server. * * @since PircBot 1.0.0 * * @return The current nick of the bot. */ public String getNick() { return _nick; } /** * Gets the internal login of the PircBot. * * @return The login of the PircBot. */ public final String getLogin() { return _login; } /** * Gets the internal version of the PircBot. * * @return The version of the PircBot. */ public final String getVersion() { return _version; } /** * Gets the internal finger message of the PircBot. * * @return The finger message of the PircBot. */ public final String getFinger() { return _finger; } /** * Returns whether or not the PircBot is currently connected to a server. * The result of this method should only act as a rough guide, * as the result may not be valid by the time you act upon it. * * @return True if and only if the PircBot is currently connected to a server. */ public final synchronized boolean isConnected() { return _inputThread != null && _inputThread.isConnected(); } /** * Sets the number of milliseconds to delay between consecutive * messages when there are multiple messages waiting in the * outgoing message queue. This has a default value of 1000ms. * It is a good idea to stick to this default value, as it will * prevent your bot from spamming servers and facing the subsequent * wrath! However, if you do need to change this delay value (not * recommended), then this is the method to use. * * @param delay The number of milliseconds between each outgoing message. * */ public final void setMessageDelay(long delay) { if (delay < 0) { throw new IllegalArgumentException("Cannot have a negative time."); } _messageDelay = delay; } /** * Returns the number of milliseconds that will be used to separate * consecutive messages to the server from the outgoing message queue. * * @return Number of milliseconds. */ public final long getMessageDelay() { return _messageDelay; } /** * Gets the maximum length of any line that is sent via the IRC protocol. * The IRC RFC specifies that line lengths, including the trailing \r\n * must not exceed 512 bytes. Hence, there is currently no option to * change this value in PircBot. All lines greater than this length * will be truncated before being sent to the IRC server. * * @return The maximum line length (currently fixed at 512) */ public final int getMaxLineLength() { return InputThread.MAX_LINE_LENGTH; } /** * Gets the number of lines currently waiting in the outgoing message Queue. * If this returns 0, then the Queue is empty and any new message is likely * to be sent to the IRC server immediately. * * @since PircBot 0.9.9 * * @return The number of lines in the outgoing message Queue. */ public final int getOutgoingQueueSize() { return _outQueue.size(); } /** * Returns the name of the last IRC server the PircBot tried to connect to. * This does not imply that the connection attempt to the server was * successful (we suggest you look at the onConnect method). * A value of null is returned if the PircBot has never tried to connect * to a server. * * @return The name of the last machine we tried to connect to. Returns * null if no connection attempts have ever been made. */ public final String getServer() { return _server; } /** * Returns the port number of the last IRC server that the PircBot tried * to connect to. * This does not imply that the connection attempt to the server was * successful (we suggest you look at the onConnect method). * A value of -1 is returned if the PircBot has never tried to connect * to a server. * * @since PircBot 0.9.9 * * @return The port number of the last IRC server we connected to. * Returns -1 if no connection attempts have ever been made. */ public final int getPort() { return _port; } /** * Returns the last password that we used when connecting to an IRC server. * This does not imply that the connection attempt to the server was * successful (we suggest you look at the onConnect method). * A value of null is returned if the PircBot has never tried to connect * to a server using a password. * * @since PircBot 0.9.9 * * @return The last password that we used when connecting to an IRC server. * Returns null if we have not previously connected using a password. */ public final String getPassword() { return _password; } /** * A convenient method that accepts an IP address represented as a * long and returns an integer array of size 4 representing the same * IP address. * * @since PircBot 0.9.4 * * @param address the long value representing the IP address. * * @return An int[] of size 4. */ public int[] longToIp(long address) { int[] ip = new int[4]; for (int i = 3; i >= 0; i--) { ip[i] = (int) (address % 256); address = address / 256; } return ip; } /** * A convenient method that accepts an IP address represented by a byte[] * of size 4 and returns this as a long representation of the same IP * address. * * @since PircBot 0.9.4 * * @param address the byte[] of size 4 representing the IP address. * * @return a long representation of the IP address. */ public long ipToLong(byte[] address) { if (address.length != 4) { throw new IllegalArgumentException("byte array must be of length 4"); } long ipNum = 0; long multiplier = 1; for (int i = 3; i >= 0; i--) { int byteVal = (address[i] + 256) % 256; ipNum += byteVal*multiplier; multiplier *= 256; } return ipNum; } /** * Sets the encoding charset to be used when sending or receiving lines * from the IRC server. If set to null, then the platform's default * charset is used. You should only use this method if you are * trying to send text to an IRC server in a different charset, e.g. * "GB2312" for Chinese encoding. If a PircBot is currently connected * to a server, then it must reconnect before this change takes effect. * * @since PircBot 1.0.4 * * @param charset The new encoding charset to be used by PircBot. * * @throws UnsupportedEncodingException If the named charset is not * supported. */ public void setEncoding(String charset) throws UnsupportedEncodingException { // Just try to see if the charset is supported first... "".getBytes(charset); _charset = charset; } /** * Returns the encoding used to send and receive lines from * the IRC server, or null if not set. Use the setEncoding * method to change the encoding charset. * * @since PircBot 1.0.4 * * @return The encoding used to send outgoing messages, or * null if not set. */ public String getEncoding() { return _charset; } /** * Returns the InetAddress used by the PircBot. * This can be used to find the I.P. address from which the PircBot is * connected to a server. * * @since PircBot 1.4.4 * * @return The current local InetAddress, or null if never connected. */ public InetAddress getInetAddress() { return _inetAddress; } /** * Sets the InetAddress to be used when sending DCC chat or file transfers. * This can be very useful when you are running a bot on a machine which * is behind a firewall and you need to tell receiving clients to connect * to a NAT/router, which then forwards the connection. * * @since PircBot 1.4.4 * * @param dccInetAddress The new InetAddress(), or null to use the default. */ public void setDccInetAddress(InetAddress dccInetAddress) { _dccInetAddress = dccInetAddress; } /** * Returns the InetAddress used when sending DCC chat or file transfers. * If this is null, the default InetAddress will be used. * * @since PircBot 1.4.4 * * @return The current DCC InetAddress, or null if left as default. */ public InetAddress getDccInetAddress() { return _dccInetAddress; } /** * Returns the set of port numbers to be used when sending a DCC chat * or file transfer. This is useful when you are behind a firewall and * need to set up port forwarding. The array of port numbers is traversed * in sequence until a free port is found to listen on. A DCC tranfer will * fail if all ports are already in use. * If set to null, any free port number will be used. * * @since PircBot 1.4.4 * * @return An array of port numbers that PircBot can use to send DCC * transfers, or null if any port is allowed. */ public int[] getDccPorts() { if (_dccPorts == null || _dccPorts.length == 0) { return null; } // Clone the array to prevent external modification. return (int[]) _dccPorts.clone(); } /** * Sets the choice of port numbers that can be used when sending a DCC chat * or file transfer. This is useful when you are behind a firewall and * need to set up port forwarding. The array of port numbers is traversed * in sequence until a free port is found to listen on. A DCC tranfer will * fail if all ports are already in use. * If set to null, any free port number will be used. * * @since PircBot 1.4.4 * * @param ports The set of port numbers that PircBot may use for DCC * transfers, or null to let it use any free port (default). * */ public void setDccPorts(int[] ports) { if (ports == null || ports.length == 0) { _dccPorts = null; } else { // Clone the array to prevent external modification. _dccPorts = (int[]) ports.clone(); } } /** * Returns true if and only if the object being compared is the exact * same instance as this PircBot. This may be useful if you are writing * a multiple server IRC bot that uses more than one instance of PircBot. * * @since PircBot 0.9.9 * * @return true if and only if Object o is a PircBot and equal to this. */ public boolean equals(Object o) { // This probably has the same effect as Object.equals, but that may change... if (o instanceof PircBot) { PircBot other = (PircBot) o; return other == this; } return false; } /** * Returns the hashCode of this PircBot. This method can be called by hashed * collection classes and is useful for managing multiple instances of * PircBots in such collections. * * @since PircBot 0.9.9 * * @return the hash code for this instance of PircBot. */ public int hashCode() { return super.hashCode(); } /** * Returns a String representation of this object. * You may find this useful for debugging purposes, particularly * if you are using more than one PircBot instance to achieve * multiple server connectivity. The format of * this String may change between different versions of PircBot * but is currently something of the form * * Version{PircBot x.y.z Java IRC Bot - www.jibble.org} * Connected{true} * Server{irc.dal.net} * Port{6667} * Password{} * * * @since PircBot 0.9.10 * * @return a String representation of this object. */ public String toString() { return "Version{" + _version + "}" + " Connected{" + isConnected() + "}" + " Server{" + _server + "}" + " Port{" + _port + "}" + " Password{" + _password + "}"; } /** * Returns an array of all users in the specified channel. *

* There are some important things to note about this method:- *

* * @since PircBot 1.0.0 * * @param channel The name of the channel to list. * * @return An array of User objects. This array is empty if we are not * in the channel. * * @see #onUserList(String,User[]) onUserList */ public final User[] getUsers(String channel) { channel = channel.toLowerCase(); User[] userArray = new User[0]; synchronized (_channels) { Hashtable users = (Hashtable) _channels.get(channel); if (users != null) { userArray = new User[users.size()]; Enumeration enumeration = users.elements(); for (int i = 0; i < userArray.length; i++) { User user = (User) enumeration.nextElement(); userArray[i] = user; } } } return userArray; } /** * Returns an array of all channels that we are in. Note that if you * call this method immediately after joining a new channel(), the new * channel may not appear in this array as it is not possible to tell * if the join was successful until a response is received from the * IRC server. * * @since PircBot 1.0.0 * * @return A String array containing the names of all channels that we * are in. */ public final String[] getChannels() { String[] channels = new String[0]; synchronized (_channels) { channels = new String[_channels.size()]; Enumeration enumeration = _channels.keys(); for (int i = 0; i < channels.length; i++) { channels[i] = (String) enumeration.nextElement(); } } return channels; } /** * Disposes of all thread resources used by this PircBot. This may be * useful when writing bots or clients that use multiple servers (and * therefore multiple PircBot instances) or when integrating a PircBot * with an existing program. *

* Each PircBot runs its own threads for dispatching messages from its * outgoing message queue and receiving messages from the server. * Calling dispose() ensures that these threads are * stopped, thus freeing up system resources and allowing the PircBot * object to be garbage collected if there are no other references to * it. *

* Once a PircBot object has been disposed, it should not be used again. * Attempting to use a PircBot that has been disposed may result in * unpredictable behaviour. * * @since 1.2.2 */ public synchronized void dispose() { //System.out.println("disposing..."); _outputThread.interrupt(); _inputThread.dispose(); } /** * Add a user to the specified channel in our memory. * Overwrite the existing entry if it exists. */ private final void addUser(String channel, User user) { channel = channel.toLowerCase(); synchronized (_channels) { Hashtable users = (Hashtable) _channels.get(channel); if (users == null) { users = new Hashtable(); _channels.put(channel, users); } users.put(user, user); } } /** * Remove a user from the specified channel in our memory. */ private final User removeUser(String channel, String nick) { channel = channel.toLowerCase(); User user = new User("", nick); synchronized (_channels) { Hashtable users = (Hashtable) _channels.get(channel); if (users != null) { return (User) users.remove(user); } } return null; } /** * Remove a user from all channels in our memory. */ private final void removeUser(String nick) { synchronized (_channels) { Enumeration enumeration = _channels.keys(); while (enumeration.hasMoreElements()) { String channel = (String) enumeration.nextElement(); this.removeUser(channel, nick); } } } /** * Rename a user if they appear in any of the channels we know about. */ private final void renameUser(String oldNick, String newNick) { synchronized (_channels) { Enumeration enumeration = _channels.keys(); while (enumeration.hasMoreElements()) { String channel = (String) enumeration.nextElement(); User user = this.removeUser(channel, oldNick); if (user != null) { user = new User(user.getPrefix(), newNick); this.addUser(channel, user); } } } } /** * Removes an entire channel from our memory of users. */ private final void removeChannel(String channel) { channel = channel.toLowerCase(); synchronized (_channels) { _channels.remove(channel); } } /** * Removes all channels from our memory of users. */ private final void removeAllChannels() { synchronized(_channels) { _channels = new Hashtable(); } } private final void updateUser(String channel, int userMode, String nick) { channel = channel.toLowerCase(); synchronized (_channels) { Hashtable users = (Hashtable) _channels.get(channel); User newUser = null; if (users != null) { Enumeration enumeration = users.elements(); while(enumeration.hasMoreElements()) { User userObj = (User) enumeration.nextElement(); if (userObj.getNick().equalsIgnoreCase(nick)) { if (userMode == OP_ADD) { if (userObj.hasVoice()) { newUser = new User("@+", nick); } else { newUser = new User("@", nick); } } else if (userMode == OP_REMOVE) { if(userObj.hasVoice()) { newUser = new User("+", nick); } else { newUser = new User("", nick); } } else if (userMode == VOICE_ADD) { if(userObj.isOp()) { newUser = new User("@+", nick); } else { newUser = new User("+", nick); } } else if (userMode == VOICE_REMOVE) { if(userObj.isOp()) { newUser = new User("@", nick); } else { newUser = new User("", nick); } } } } } if (newUser != null) { users.put(newUser, newUser); } else { // just in case ... newUser = new User("", nick); users.put(newUser, newUser); } } } // Connection stuff. private InputThread _inputThread = null; private OutputThread _outputThread = null; private String _charset = null; private InetAddress _inetAddress = null; // Details about the last server that we connected to. private String _server = null; private int _port = -1; private String _password = null; // Outgoing message stuff. private Queue _outQueue = new Queue(); private long _messageDelay = 1000; // A Hashtable of channels that points to a selfreferential Hashtable of // User objects (used to remember which users are in which channels). private Hashtable _channels = new Hashtable(); // A Hashtable to temporarily store channel topics when we join them // until we find out who set that topic. private Hashtable _topics = new Hashtable(); // DccManager to process and handle all DCC events. private DccManager _dccManager = new DccManager(this); private int[] _dccPorts = null; private InetAddress _dccInetAddress = null; // Default settings for the PircBot. private boolean _autoNickChange = false; private boolean _verbose = false; private String _name = "PircBot"; private String _nick = _name; private String _login = "PircBot"; private String _version = "PircBot " + VERSION + " Java IRC Bot - www.jibble.org"; private String _finger = "You ought to be arrested for fingering a bot!"; private String _channelPrefixes = "#&+!"; } // modified PircBot static boolean useBouncer = true; static String server = useBouncer ? "second.tinybrain.de" : "irc.freenode.net"; static String channel = /*"##linux"*/"#pircbot"; static String name = "Lookie"; static int reconnectTimeout1 = 60; // seconds after which to send a ping to server static int reconnectTimeout2 = 80; // seconds after which we try to reconnect static Map> bulk = new TreeMap(); // key = channel (with "#") static MyBot bot; public static void main(String[] args) throws Exception { Thread _t_0 = new Thread("IRC Log-In") { public void run() { try { bot = new MyBot(); bot.setVerbose(true); print("PircBot connecting to " + server); bot.connect(server); print("PircBot connect issued"); autoReconnect(bot); } catch (Exception _e) { throw _e instanceof RuntimeException ? (RuntimeException) _e : new RuntimeException(_e); } } }; _t_0.start(); } static class MyBot extends PircBot { long lastReceived; MyBot() { setName(name); //setRealName(name); // newer version of PircBot only setLogin(name); setVersion(name); setFinger(""); setAutoNickChange(true); } public void handleLine(String line) { getLog(channel).add(litmap("time", smartNow(), "line", line)); super.handleLine(line); lastReceived = now(); } public void onConnect() { print("PircBot onConnect useBouncer=" + useBouncer); if (useBouncer) { String bouncerLogin = loadSecretTextFileMandatory("bouncer-login").trim(); print("Logging in to bouncer"); bot.sendMessage("root", bouncerLogin.trim()); // session should already be there, so resume it print("Resuming bouncer session"); bot.sendMessage("root", "connect freenode"); } bot.joinChannel(channel); } } static void autoReconnect(final MyBot bot) { Thread _t_1 = new Thread("PircBot Auto-Reconnect") { public void run() { try { while (licensed()) { sleepSeconds(10); if (now() >= bot.lastReceived + reconnectTimeout2*1000) try { print("PircBot: Trying to reconnect..."); hardClose(bot); sleepSeconds(1); // allow for input thread to halt bot.reconnect(); } catch (Exception e) { print("Reconnect fail: " + e); // Couldn't reconnect! } else if (now() >= bot.lastReceived + reconnectTimeout1*1000) bot.sendRawLine("TIME"); // send something } } catch (Exception _e) { throw _e instanceof RuntimeException ? (RuntimeException) _e : new RuntimeException(_e); } } }; _t_1.start(); } static Socket getSocket(PircBot bot) { return (Socket) get(get(bot, "_inputThread"), "_socket"); } static String answer(String s) { Matches m = new Matches(); if (match("irc log size *", s, m)) try { return lstr(getLog(m.unq(0))); } catch (Throwable __e) { return exceptionToUser(__e); } return null; } static synchronized PersistentLog getLog(String channelName) { checkChannelName(channelName); PersistentLog log = bulk.get(channelName); if (log == null) bulk.put(channelName, log = new PersistentLog(channelName + ".log")); return log; } static String checkChannelName(String s) { assertTrue(nempty(s)); for (int i = 0; i < l(s); i++) if (!( "#!".indexOf(s.charAt(i)) >= 0 || Character.isLetterOrDigit(s.charAt(i)))) fail(s); return s; } static void hardClose(PircBot bot) { if (bot == null) return; try { //bot.disconnect(); getSocket(bot).close(); } catch (Exception e) { print(str(e)); } } static void cleanMeUp() { hardClose(bot); } static long smartNow_last; static long smartNow() { return smartNow_last = max(smartNow_last + 1, now()); } static volatile boolean licensed_yes = true; static boolean licensed() { return licensed_yes; } static void licensed_off() { licensed_yes = false; } static void sleepSeconds(long s) { if (s > 0) sleep(s*1000); } static int max(int a, int b) { return Math.max(a, b); } static long max(int a, long b) { return Math.max((long) a, b); } static long max(long a, long b) { return Math.max(a, b); } static double max(int a, double b) { return Math.max((double) a, b); } static int max(Collection c) { int x = Integer.MIN_VALUE; for (int i : c) x = max(x, i); return x; } static double max(double[] c) { if (c.length == 0) return Double.MIN_VALUE; double x = c[0]; for (int i = 1; i < c.length; i++) x = Math.max(x, c[i]); return x; } static boolean nempty(Collection c) { return !isEmpty(c); } static boolean nempty(String s) { return !isEmpty(s); } static Map litmap(Object... x) { TreeMap map = new TreeMap(); for (int i = 0; i < x.length-1; i += 2) if (x[i+1] != null) map.put(x[i], x[i+1]); return map; } static int l(Object[] array) { return array == null ? 0 : array.length; } static int l(byte[] array) { return array == null ? 0 : array.length; } static int l(int[] array) { return array == null ? 0 : array.length; } static int l(char[] array) { return array == null ? 0 : array.length; } static int l(Collection c) { return c == null ? 0 : c.size(); } static int l(Map m) { return m == null ? 0 : m.size(); } static int l(String s) { return s == null ? 0 : s.length(); } static String str(Object o) { return String.valueOf(o); } static RuntimeException fail() { throw new RuntimeException("fail"); } static RuntimeException fail(Object msg) { throw new RuntimeException(String.valueOf(msg)); } static RuntimeException fail(String msg) { throw new RuntimeException(unnull(msg)); } static RuntimeException fail(String msg, Object... args) { throw new RuntimeException(format(msg, args)); } static StringBuffer print_log; static void print() { print(""); } static void print(Object o) { String s = String.valueOf(o) + "\n"; synchronized(StringBuffer.class) { if (print_log == null) print_log = new StringBuffer(); } print_log.append(s); System.out.print(s); } static void print(long l) { print(String.valueOf(l)); } static String loadSecretTextFileMandatory(String name) { return loadTextFileMandatory(new File(getSecretProgramDir(), name)); } static String loadSecretTextFileMandatory(String progID, String name) { return loadTextFileMandatory(new File(getSecretProgramDir(progID), name)); } static void assertTrue(Object o) { assertEquals(true, o); } static boolean assertTrue(String msg, boolean b) { if (!b) fail(msg); return b; } static boolean assertTrue(boolean b) { if (!b) fail("oops"); return b; } static boolean equals(Object a, Object b) { return a == null ? b == null : a.equals(b); } static long now_virtualTime; static long now() { return now_virtualTime != 0 ? now_virtualTime : System.currentTimeMillis(); } // get purpose 1: access a list/array (safer version of x.get(y)) static A get(List l, int idx) { return idx >= 0 && idx < l(l) ? l.get(idx) : null; } static A get(A[] l, int idx) { return idx >= 0 && idx < l(l) ? l[idx] : null; } // get purpose 2: access a field by reflection static Object get(Object o, String field) { if (o instanceof Class) return get((Class) o, field); if (o.getClass().getName().equals("main$DynamicObject")) return call(get_raw(o, "fieldValues"), "get", field); return get_raw(o, field); } static Object get_raw(Object o, String field) { try { Field f = get_findField(o.getClass(), field); f.setAccessible(true); return f.get(o); } catch (Exception e) { throw new RuntimeException(e); } } static Object get(Class c, String field) { try { Field f = get_findStaticField(c, field); f.setAccessible(true); return f.get(null); } catch (Exception e) { throw new RuntimeException(e); } } static Field get_findStaticField(Class c, String field) { Class _c = c; do { for (Field f : _c.getDeclaredFields()) if (f.getName().equals(field) && (f.getModifiers() & Modifier.STATIC) != 0) return f; _c = _c.getSuperclass(); } while (_c != null); throw new RuntimeException("Static field '" + field + "' not found in " + c.getName()); } static Field get_findField(Class c, String field) { Class _c = c; do { for (Field f : _c.getDeclaredFields()) if (f.getName().equals(field)) return f; _c = _c.getSuperclass(); } while (_c != null); throw new RuntimeException("Field '" + field + "' not found in " + c.getName()); } static String lstr(Map map) { return str(l(map)); } static String lstr(List l) { return str(l(l)); } static String loadTextFileMandatory(File file) { String contents = loadTextFile(file); if (contents == null) fail("File not found: " + file.getPath()); return contents; } static String unnull(String s) { return s == null ? "" : s; } static List unnull(List l) { return l == null ? emptyList() : l; } static void sleep(long ms) { try { Thread.sleep(ms); } catch (Exception e) { throw new RuntimeException(e); } } static void sleep() { try { print("Sleeping."); synchronized(main.class) { main.class.wait(); } } catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }} static File getSecretProgramDir() { return getSecretProgramDir(getProgramID()); } static File getSecretProgramDir(String snippetID) { return new File(userHome(), "JavaX-Secret/" + formatSnippetID(snippetID)); } static boolean isEmpty(Collection c) { return c == null || c.isEmpty(); } static boolean isEmpty(String s) { return s == null || s.length() == 0; } static String format(String pat, Object... args) { return format3(pat, args); } static void assertEquals(Object x, Object y) { assertEquals(null, x, y); } static void assertEquals(String msg, Object x, Object y) { if (!(x == null ? y == null : x.equals(y))) fail((msg != null ? msg + ": " : "") + structure(x) + " != " + structure(y)); } // varargs assignment fixer for a single string array argument static Object call(Object o, String method, String[] arg) { return call(o, method, new Object[] {arg}); } static Object call(Object o, String method, Object... args) { try { if (o instanceof Class) { Method m = call_findStaticMethod((Class) o, method, args, false); m.setAccessible(true); return m.invoke(null, args); } else { Method m = call_findMethod(o, method, args, false); m.setAccessible(true); return m.invoke(o, args); } } catch (Exception e) { throw e instanceof RuntimeException ? (RuntimeException) e : new RuntimeException(e); } } static Method call_findStaticMethod(Class c, String method, Object[] args, boolean debug) { Class _c = c; while (c != null) { for (Method m : c.getDeclaredMethods()) { if (debug) System.out.println("Checking method " + m.getName() + " with " + m.getParameterTypes().length + " parameters");; if (!m.getName().equals(method)) { if (debug) System.out.println("Method name mismatch: " + method); continue; } if ((m.getModifiers() & Modifier.STATIC) == 0 || !call_checkArgs(m, args, debug)) continue; return m; } c = c.getSuperclass(); } throw new RuntimeException("Method '" + method + "' (static) with " + args.length + " parameter(s) not found in " + _c.getName()); } static Method call_findMethod(Object o, String method, Object[] args, boolean debug) { Class c = o.getClass(); while (c != null) { for (Method m : c.getDeclaredMethods()) { if (debug) System.out.println("Checking method " + m.getName() + " with " + m.getParameterTypes().length + " parameters");; if (m.getName().equals(method) && call_checkArgs(m, args, debug)) return m; } c = c.getSuperclass(); } throw new RuntimeException("Method '" + method + "' (non-static) with " + args.length + " parameter(s) not found in " + o.getClass().getName()); } private static boolean call_checkArgs(Method m, Object[] args, boolean debug) { Class[] types = m.getParameterTypes(); if (types.length != args.length) { if (debug) System.out.println("Bad parameter length: " + args.length + " vs " + types.length); return false; } for (int i = 0; i < types.length; i++) if (!(args[i] == null || isInstanceX(types[i], args[i]))) { if (debug) System.out.println("Bad parameter " + i + ": " + args[i] + " vs " + types[i]); return false; } return true; } static String format3(String pat, Object... args) { if (args.length == 0) return pat; List tok = javaTokPlusPeriod(pat); int argidx = 0; for (int i = 1; i < tok.size(); i += 2) if (tok.get(i).equals("*")) tok.set(i, format3_formatArg(argidx < args.length ? args[argidx++] : "null")); return join(tok); } static String format3_formatArg(Object arg) { if (arg == null) return "null"; if (arg instanceof String) { String s = (String) arg; return isIdentifier(s) || isNonNegativeInteger(s) ? s : quote(s); } if (arg instanceof Integer || arg instanceof Long) return String.valueOf(arg); return quote(structure(arg)); } static String programID; static String getProgramID() { return programID; } // TODO: ask JavaX instead static String getProgramID(Class c) { return or((String) getOpt(c, "programID"), "?"); } static List emptyList() { return Collections.emptyList(); } static String _userHome; static String userHome() { if (_userHome == null) { if (isAndroid()) _userHome = "/storage/sdcard0/"; else _userHome = System.getProperty("user.home"); //System.out.println("userHome: " + _userHome); } return _userHome; } static String formatSnippetID(String id) { return "#" + parseSnippetID(id); } static String formatSnippetID(long id) { return "#" + id; } // extended over Class.isInstance() to handle primitive types static boolean isInstanceX(Class type, Object arg) { if (type == boolean.class) return arg instanceof Boolean; if (type == int.class) return arg instanceof Integer; if (type == long.class) return arg instanceof Long; if (type == float.class) return arg instanceof Float; if (type == short.class) return arg instanceof Short; if (type == char.class) return arg instanceof Character; if (type == byte.class) return arg instanceof Byte; if (type == double.class) return arg instanceof Double; return type.isInstance(arg); } public static String loadTextFile(String fileName) { try { return loadTextFile(fileName, null); } catch (IOException e) { throw new RuntimeException(e); } } public static String loadTextFile(String fileName, String defaultContents) throws IOException { if (!new File(fileName).exists()) return defaultContents; FileInputStream fileInputStream = new FileInputStream(fileName); InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream, "UTF-8"); return loadTextFile(inputStreamReader); } public static String loadTextFile(File fileName) { try { return loadTextFile(fileName, null); } catch (IOException e) { throw new RuntimeException(e); } } public static String loadTextFile(File fileName, String defaultContents) throws IOException { try { return loadTextFile(fileName.getPath(), defaultContents); } catch (IOException e) { throw new RuntimeException(e); } } public static String loadTextFile(Reader reader) throws IOException { StringBuilder builder = new StringBuilder(); try { char[] buffer = new char[1024]; int n; while (-1 != (n = reader.read(buffer))) builder.append(buffer, 0, n); } finally { reader.close(); } return builder.toString(); } static String structure(Object o) { return structure(o, 0); } // leave to false, unless unstructure() breaks static boolean structure_allowShortening = false; static String structure(Object o, int stringSizeLimit) { if (o == null) return "null"; String name = o.getClass().getName(); StringBuilder buf = new StringBuilder(); if (o instanceof Collection) { // TODO: store the type (e.g. HashSet/TreeSet) for (Object x : (Collection) o) { if (buf.length() != 0) buf.append(", "); buf.append(structure(x, stringSizeLimit)); } return "[" + buf + "]"; } if (o instanceof Map) { for (Object e : ((Map) o).entrySet()) { if (buf.length() != 0) buf.append(", "); buf.append(structure(((Map.Entry) e).getKey(), stringSizeLimit)); buf.append("="); buf.append(structure(((Map.Entry) e).getValue(), stringSizeLimit)); } return (o instanceof HashMap ? "hashmap" : "") + "{" + buf + "}"; } if (o.getClass().isArray()) { int n = Array.getLength(o); for (int i = 0; i < n; i++) { if (buf.length() != 0) buf.append(", "); buf.append(structure(Array.get(o, i), stringSizeLimit)); } return "array{" + buf + "}"; } if (o instanceof String) return quote(stringSizeLimit != 0 ? shorten((String) o, stringSizeLimit) : (String) o); if (o instanceof Class) return "class(" + quote(((Class) o).getName()) + ")"; if (o instanceof Throwable) return "exception(" + quote(((Throwable) o).getMessage()) + ")"; if (o instanceof BigInteger) return "bigint(" + o + ")"; if (o instanceof Double) return "d(" + quote(str(o)) + ")"; if (o instanceof Long) return o + "L"; // Need more cases? This should cover all library classes... if (name.startsWith("java.") || name.startsWith("javax.")) return String.valueOf(o); String shortName = o.getClass().getName().replaceAll("^main\\$", ""); int numFields = 0; String fieldName = ""; if (shortName.equals("DynamicObject")) { shortName = (String) get(o, "className"); Map fieldValues = (Map) get(o, "fieldValues"); for (String _fieldName : fieldValues.keySet()) { fieldName = _fieldName; Object value = fieldValues.get(fieldName); if (value != null) { if (buf.length() != 0) buf.append(", "); buf.append(fieldName + "=" + structure(value, stringSizeLimit)); } ++numFields; } } else { // regular class // TODO: go to superclasses too Field[] fields = o.getClass().getDeclaredFields(); for (Field field : fields) { if ((field.getModifiers() & Modifier.STATIC) != 0) continue; Object value; try { field.setAccessible(true); value = field.get(o); } catch (Exception e) { value = "?"; } fieldName = field.getName(); // put special cases here... if (value != null) { if (buf.length() != 0) buf.append(", "); buf.append(fieldName + "=" + structure(value, stringSizeLimit)); } ++numFields; } } String b = buf.toString(); if (numFields == 1 && structure_allowShortening) b = b.replaceAll("^" + fieldName + "=", ""); // drop field name if only one String s = shortName; if (buf.length() != 0) s += "(" + b + ")"; return s; } static boolean isIdentifier(String s) { return isJavaIdentifier(s); } public static long parseSnippetID(String snippetID) { long id = Long.parseLong(shortenSnippetID(snippetID)); if (id == 0) fail("0 is not a snippet ID"); return id; } static String quote(String s) { if (s == null) return "null"; return "\"" + s.replace("\\", "\\\\").replace("\"", "\\\"").replace("\r", "\\r").replace("\n", "\\n") + "\""; } static String quote(long l) { return quote("" + l); } static String shorten(String s, int max) { if (s == null) return ""; return s.length() <= max ? s : s.substring(0, Math.min(s.length(), max)) + "..."; } public static String join(String glue, Iterable strings) { StringBuilder buf = new StringBuilder(); Iterator i = strings.iterator(); if (i.hasNext()) { buf.append(i.next()); while (i.hasNext()) buf.append(glue).append(i.next()); } return buf.toString(); } public static String join(String glue, String[] strings) { return join(glue, Arrays.asList(strings)); } public static String join(Iterable strings) { return join("", strings); } public static String join(String[] strings) { return join("", strings); } static boolean isNonNegativeInteger(String s) { return s != null && Pattern.matches("\\d+", s); } static A or(A a, A b) { return a != null ? a : b; } static Object getOpt(Object o, String field) { if (o instanceof String) o = getBot ((String) o); if (o == null) return null; if (o instanceof Class) return getOpt((Class) o, field); if (o.getClass().getName().equals("main$DynamicObject")) return call(getOpt_raw(o, "fieldValues"), "get", field); if (o instanceof Map) return ((Map) o).get(field); return getOpt_raw(o, field); } static Object getOpt_raw(Object o, String field) { try { Field f = getOpt_findField(o.getClass(), field); if (f == null) return null; f.setAccessible(true); return f.get(o); } catch (Exception e) { throw new RuntimeException(e); } } static Object getOpt(Class c, String field) { try { Field f = getOpt_findStaticField(c, field); if (f == null) return null; f.setAccessible(true); return f.get(null); } catch (Exception e) { throw new RuntimeException(e); } } static Field getOpt_findStaticField(Class c, String field) { Class _c = c; do { for (Field f : _c.getDeclaredFields()) if (f.getName().equals(field) && (f.getModifiers() & Modifier.STATIC) != 0) return f; _c = _c.getSuperclass(); } while (_c != null); return null; } static Field getOpt_findField(Class c, String field) { Class _c = c; do { for (Field f : _c.getDeclaredFields()) if (f.getName().equals(field)) return f; _c = _c.getSuperclass(); } while (_c != null); return null; } // This is made for NL parsing. // It's javaTok extended with "..." token, "$n" and "#n" and // special quotes (which are converted to normal ones). static List javaTokPlusPeriod(String s) { List tok = new ArrayList(); int l = s.length(); int i = 0; while (i < l) { int j = i; char c; String cc; // scan for whitespace while (j < l) { c = s.charAt(j); cc = s.substring(j, Math.min(j+2, l)); if (c == ' ' || c == '\t' || c == '\r' || c == '\n') ++j; else if (cc.equals("/*")) { do ++j; while (j < l && !s.substring(j, Math.min(j+2, l)).equals("*/")); j = Math.min(j+2, l); } else if (cc.equals("//")) { do ++j; while (j < l && "\r\n".indexOf(s.charAt(j)) < 0); } else break; } tok.add(s.substring(i, j)); i = j; if (i >= l) break; c = s.charAt(i); cc = s.substring(i, Math.min(i+2, l)); // scan for non-whitespace if (c == '\u201C' || c == '\u201D') c = '"'; // normalize quotes if (c == '\'' || c == '"') { char opener = c; ++j; while (j < l) { char _c = s.charAt(j); if (_c == '\u201C' || _c == '\u201D') _c = '"'; // normalize quotes if (_c == opener) { ++j; break; } else if (s.charAt(j) == '\\' && j+1 < l) j += 2; else ++j; } if (j-1 >= i+1) { tok.add(opener + s.substring(i+1, j-1) + opener); i = j; continue; } } else if (Character.isJavaIdentifierStart(c)) do ++j; while (j < l && (Character.isJavaIdentifierPart(s.charAt(j)) || s.charAt(j) == '\'')); // for things like "this one's" else if (Character.isDigit(c)) do ++j; while (j < l && Character.isDigit(s.charAt(j))); else if (cc.equals("[[")) { do ++j; while (j+1 < l && !s.substring(j, j+2).equals("]]")); j = Math.min(j+2, l); } else if (s.substring(j, Math.min(j+3, l)).equals("...")) j += 3; else if (c == '$' || c == '#') do ++j; while (j < l && Character.isDigit(s.charAt(j))); else ++j; tok.add(s.substring(i, j)); i = j; } if ((tok.size() % 2) == 0) tok.add(""); return tok; } static boolean isAndroid() { return System.getProperty("java.vendor").toLowerCase().indexOf("android") >= 0; } static Object getBot(String botID) { return call(getMainBot(), "getBot", botID); } static String shortenSnippetID(String snippetID) { if (snippetID.startsWith("#")) snippetID = snippetID.substring(1); String httpBlaBla = "http://tinybrain.de/"; if (snippetID.startsWith(httpBlaBla)) snippetID = snippetID.substring(httpBlaBla.length()); return "" + parseLong(snippetID); } static boolean isJavaIdentifier(String s) { if (s.length() == 0 || !Character.isJavaIdentifierStart(s.charAt(0))) return false; for (int i = 1; i < s.length(); i++) if (!Character.isJavaIdentifierPart(s.charAt(i))) return false; return true; } static Object mainBot; static Object getMainBot() { return mainBot; } static long parseLong(String s) { return Long.parseLong(s); } static Class run(String progID) { Class main = hotwire(progID); callMain(main); return main; } static void printStackTrace(Throwable e) { // we go to system.out now - system.err is nonsense print(getStackTrace(e)); } static class DynamicObject { String className; Map fieldValues = new TreeMap(); } static Object unstructure(String text) { return unstructure(text, false); } // actually it's now almost the same as jsonDecode :) static Object unstructure(String text, final boolean allDynamic) { final List tok = javaTok(text); class X { int i = 1; Object parse() { String t = tok.get(i); if (t.startsWith("\"")) { String s = unquote(tok.get(i)); i += 2; return s; } if (t.equals("hashmap")) return parseHashMap(); if (t.equals("{")) return parseMap(); if (t.equals("[")) return parseList(); if (t.equals("array")) return parseArray(); if (t.equals("class")) return parseClass(); if (t.equals("bigint")) return parseBigInt(); if (t.equals("d")) return parseDouble(); if (t.equals("null")) { i += 2; return null; } if (t.equals("false")) { i += 2; return false; } if (t.equals("true")) { i += 2; return true; } if (t.equals("-")) { t = tok.get(i+2); i += 4; t = dropSuffix("L", t); long l = -Long.parseLong(t); return l == (int) l ? (int) l : l; } if (isInteger(t) || isLongConstant(t)) { i += 2; t = dropSuffix("L", t); long l = Long.parseLong(t); return l == (int) l ? (int) l : l; } if (isJavaIdentifier(t)) { Class c = allDynamic ? null : findClass(t); DynamicObject dO = null; Object o = null; if (c != null) o = nuObject(c); else { dO = new DynamicObject(); dO.className = t; } i += 2; if (i < tok.size() && tok.get(i).equals("(")) { consume("("); while (!tok.get(i).equals(")")) { // It's like parsing a map. //Object key = parse(); //if (tok.get(i).equals(")")) // key = onlyField(); String key = unquote(tok.get(i)); i += 2; consume("="); Object value = parse(); if (o != null) setOpt(o, key, value); else dO.fieldValues.put(key, value); if (tok.get(i).equals(",")) i += 2; } consume(")"); } return o != null ? o : dO; } throw new RuntimeException("Unknown token " + (i+1) + ": " + t); } Object parseList() { consume("["); List list = new ArrayList(); while (!tok.get(i).equals("]")) { list.add(parse()); if (tok.get(i).equals(",")) i += 2; } consume("]"); return list; } Object parseArray() { consume("array"); consume("{"); List list = new ArrayList(); while (!tok.get(i).equals("}")) { list.add(parse()); if (tok.get(i).equals(",")) i += 2; } consume("}"); return list.toArray(); } Object parseClass() { consume("class"); consume("("); String name = tok.get(i); i += 2; consume(")"); Class c = allDynamic ? null : findClass(name); if (c != null) return c; DynamicObject dO = new DynamicObject(); dO.className = "java.lang.Class"; dO.fieldValues.put("name", name); return dO; } Object parseBigInt() { consume("bigint"); consume("("); String val = tok.get(i); i += 2; if (eq(val, "-")) { val = "-" + tok.get(i); i += 2; } consume(")"); return new BigInteger(val); } Object parseDouble() { consume("d"); consume("("); String val = unquote(tok.get(i)); i += 2; consume(")"); return Double.parseDouble(val); } Object parseHashMap() { consume("hashmap"); return parseMap(new HashMap()); } Object parseMap() { return parseMap(new TreeMap()); } Object parseMap(Map map) { consume("{"); while (!tok.get(i).equals("}")) { Object key = unstructure(tok.get(i)); i += 2; consume("="); Object value = parse(); map.put(key, value); if (tok.get(i).equals(",")) i += 2; } consume("}"); return map; } void consume(String s) { if (!tok.get(i).equals(s)) { String prevToken = i-2 >= 0 ? tok.get(i-2) : ""; String nextTokens = join(tok.subList(i, Math.min(i+4, tok.size()))); fail(quote(s) + " expected: " + prevToken + " " + nextTokens + " (" + i + "/" + tok.size() + ")"); } i += 2; } } return new X().parse(); } static void logQuoted(String logFile, String line) { logQuoted(getProgramFile(logFile), line); } static void logQuoted(File logFile, String line) { appendToFile(logFile, quote(line) + "\n"); } // also logs full exception to console static String exceptionToUser(Throwable e) { return throwableToUser(e); } static File getProgramFile(String progID, String fileName) { return new File(getProgramDir(progID), fileName); } static File getProgramFile(String fileName) { return getProgramFile(getProgramID(), fileName); } static List scanLog(String progID, String fileName) { return scanLog(getProgramFile(progID, fileName)); } static List scanLog(String fileName) { return scanLog(getProgramFile(fileName)); } static List scanLog(File file) { List l = new ArrayList(); for (String s : toLines(file)) if (isQuoted(s)) l.add(unquote(s)); return l; } static boolean match(String pat, String s) { return match3(pat, s); } static boolean match(String pat, String s, Matches matches) { return match3(pat, s, matches); } static String unq(String s) { return unquote(s); } // class Matches is added by #752 static boolean match3(String pat, String s) { return match3(pat, s, null); } static boolean match3(String pat, String s, Matches matches) { if (s == null) return false; return match3(pat, parse3(s), matches); } static boolean match3(String pat, List toks, Matches matches) { List tokpat = parse3(pat); return match3(tokpat,toks,matches); } static boolean match3(List tokpat, List toks, Matches matches) { String[] m = match2(tokpat, toks); //print(structure(tokpat) + " on " + structure(toks) + " => " + structure(m)); if (m == null) return false; else { if (matches != null) matches.m = m; return true; } } static void callMain(Object c, String... args) { callOpt(c, "main", new Object[] {args}); } static boolean isInteger(String s) { return s != null && Pattern.matches("\\-?\\d+", s); } static String getStackTrace(Throwable throwable) { StringWriter writer = new StringWriter(); throwable.printStackTrace(new PrintWriter(writer)); return writer.toString(); } // Let's just generally synchronize this to be safe. static synchronized void appendToFile(String path, String s) { try { new File(path).getParentFile().mkdirs(); //print("[Logging to " + path + "]"); Writer writer = new BufferedWriter(new OutputStreamWriter( new FileOutputStream(path, true), "UTF-8")); writer.write(s); writer.close(); } catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }} static void appendToFile(File path, String s) { appendToFile(path.getPath(), s); } // supports the usual quotings (', ", variable length double brackets) static boolean isQuoted(String s) { if (s.startsWith("'") || s.startsWith("\"")) return true; if (!s.startsWith("[")) return false; int i = 1; while (i < s.length() && s.charAt(i) == '=') ++i; return i < s.length() && s.charAt(i) == '['; //return Pattern.compile("^\\[=*\\[").matcher(s).find(); } // currently finds only inner classes of class "main" // returns null on not found // this is the simple version that is not case-tolerant static Class findClass(String name) { try { return Class.forName("main$" + name); } catch (ClassNotFoundException e) { return null; } } static boolean eq(Object a, Object b) { if (a == null) return b == null; if (a.equals(b)) return true; if (a instanceof BigInteger) { if (b instanceof Integer) return a.equals(BigInteger.valueOf((Integer) b)); if (b instanceof Long) return a.equals(BigInteger.valueOf((Long) b)); } return false; } static Object nuObject(Class c, Object... args) { try { Constructor m = nuObject_findConstructor(c, args); m.setAccessible(true); return m.newInstance(args); } catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }} static Constructor nuObject_findConstructor(Class c, Object... args) { for (Constructor m : c.getDeclaredConstructors()) { if (!nuObject_checkArgs(m.getParameterTypes(), args, false)) continue; return m; } throw new RuntimeException("Constructor with " + args.length + " matching parameter(s) not found in " + c.getName()); } static boolean nuObject_checkArgs(Class[] types, Object[] args, boolean debug) { if (types.length != args.length) { if (debug) System.out.println("Bad parameter length: " + args.length + " vs " + types.length); return false; } for (int i = 0; i < types.length; i++) if (!(args[i] == null || isInstanceX(types[i], args[i]))) { if (debug) System.out.println("Bad parameter " + i + ": " + args[i] + " vs " + types[i]); return false; } return true; } // replacement for class JavaTok // maybe incomplete, might want to add floating point numbers // todo also: extended multi-line strings static List javaTok(String s) { List tok = new ArrayList(); int l = s.length(); int i = 0; while (i < l) { int j = i; char c; String cc; // scan for whitespace while (j < l) { c = s.charAt(j); cc = s.substring(j, Math.min(j+2, l)); if (c == ' ' || c == '\t' || c == '\r' || c == '\n') ++j; else if (cc.equals("/*")) { do ++j; while (j < l && !s.substring(j, Math.min(j+2, l)).equals("*/")); j = Math.min(j+2, l); } else if (cc.equals("//")) { do ++j; while (j < l && "\r\n".indexOf(s.charAt(j)) < 0); } else break; } tok.add(s.substring(i, j)); i = j; if (i >= l) break; c = s.charAt(i); // cc is not needed in rest of loop body cc = s.substring(i, Math.min(i+2, l)); // scan for non-whitespace if (c == '\'' || c == '"') { char opener = c; ++j; while (j < l) { if (s.charAt(j) == opener || s.charAt(j) == '\n') { // end at \n to not propagate unclosed string literal errors ++j; break; } else if (s.charAt(j) == '\\' && j+1 < l) j += 2; else ++j; } } else if (Character.isJavaIdentifierStart(c)) do ++j; while (j < l && (Character.isJavaIdentifierPart(s.charAt(j)) || "'".indexOf(s.charAt(j)) >= 0)); // for stuff like "don't" else if (Character.isDigit(c)) { do ++j; while (j < l && Character.isDigit(s.charAt(j))); if (j < l && s.charAt(j) == 'L') ++j; // Long constants like 1L } else if (cc.equals("[[")) { do ++j; while (j+1 < l && !s.substring(j, j+2).equals("]]")); j = Math.min(j+2, l); } else ++j; tok.add(s.substring(i, j)); i = j; } if ((tok.size() % 2) == 0) tok.add(""); return tok; } static List javaTok(List tok) { return javaTok(join(tok)); } static double parseDouble(String s) { return Double.parseDouble(s); } static boolean isLongConstant(String s) { if (!s.endsWith("L")) return false; s = s.substring(0, l(s)-1); return isInteger(s); } static List toLines(File f) { return toLines(loadTextFile(f)); } public static List toLines(String s) { List lines = new ArrayList(); if (s == null) return lines; int start = 0; while (true) { int i = toLines_nextLineBreak(s, start); if (i < 0) { if (s.length() > start) lines.add(s.substring(start)); break; } lines.add(s.substring(start, i)); if (s.charAt(i) == '\r' && i+1 < s.length() && s.charAt(i+1) == '\n') i += 2; else ++i; start = i; } return lines; } private static int toLines_nextLineBreak(String s, int start) { for (int i = start; i < s.length(); i++) { char c = s.charAt(i); if (c == '\r' || c == '\n') return i; } return -1; } // compile JavaX source, load classes & return main class // src can be a snippet ID or actual source code // TODO: record injection? static Class hotwire(String src) { try { Class j = getJavaX(); synchronized(j) { // hopefully this goes well... List libraries = new ArrayList(); File srcDir = (File) call(j, "transpileMain", src, libraries); if (srcDir == null) fail("transpileMain returned null (src=" + quote(src) + ")"); Object androidContext = get(j, "androidContext"); if (androidContext != null) return (Class) call(j, "loadx2android", srcDir, src); File classesDir = (File) call(j, "TempDirMaker_make"); String javacOutput = (String) call(j, "compileJava", srcDir, libraries, classesDir); System.out.println(javacOutput); URL[] urls = new URL[libraries.size()+1]; urls[0] = classesDir.toURI().toURL(); for (int i = 0; i < libraries.size(); i++) urls[i+1] = libraries.get(i).toURI().toURL(); // make class loader URLClassLoader classLoader = new URLClassLoader(urls); // load & return main class Class theClass = classLoader.loadClass("main"); callOpt(j, "registerSourceCode", theClass, loadTextFile(new File(srcDir, "main.java"))); call(j, "setVars", theClass, isSnippetID(src) ? src: null); if (isSnippetID(src)) callOpt(j, "addInstance", src, theClass); return theClass; } } catch (Exception e) { throw e instanceof RuntimeException ? (RuntimeException) e : new RuntimeException(e); } } static String dropSuffix(String suffix, String s) { return s.endsWith(suffix) ? s.substring(0, l(s)-l(suffix)) : s; } // also logs full exception to console static String throwableToUser(Throwable e) { return formatExceptionForUser(e); } static File getProgramDir() { return programDir(); } static File getProgramDir(String snippetID) { return programDir(snippetID); } static void setOpt(Object o, String field, Object value) { if (o instanceof Class) setOpt((Class) o, field, value); else try { Field f = setOpt_findField(o.getClass(), field); if (f != null) smartSet(f, o, value); } catch (Exception e) { throw new RuntimeException(e); } } static void setOpt(Class c, String field, Object value) { try { Field f = setOpt_findStaticField(c, field); if (f != null) smartSet(f, null, value); } catch (Exception e) { throw new RuntimeException(e); } } static Field setOpt_findField(Class c, String field) { for (Field f : c.getDeclaredFields()) if (f.getName().equals(field)) return f; return null; } static Field setOpt_findStaticField(Class c, String field) { for (Field f : c.getDeclaredFields()) if (f.getName().equals(field) && (f.getModifiers() & Modifier.STATIC) != 0) return f; return null; } // match2 matches multiple "*" (matches a single token) wildcards and zero or one "..." wildcards (matches multiple tokens) static String[] match2(List pat, List tok) { // standard case (no ...) int i = pat.indexOf("..."); if (i < 0) return match2_match(pat, tok); pat = new ArrayList(pat); // We're modifying it, so copy first pat.set(i, "*"); while (pat.size() < tok.size()) { pat.add(i, "*"); pat.add(i+1, ""); // doesn't matter } return match2_match(pat, tok); } static String[] match2_match(List pat, List tok) { List result = new ArrayList(); if (pat.size() != tok.size()) { /*if (debug) print("Size mismatch: " + structure(pat) + " vs " + structure(tok));*/ return null; } for (int i = 1; i < pat.size(); i += 2) { String p = pat.get(i), t = tok.get(i); /*if (debug) print("Checking " + p + " against " + t);*/ if (eq(p, "*")) result.add(t); else if (!equalsIgnoreCase(unquote(p), unquote(t))) // bold change - match quoted and unquoted now return null; } return result.toArray(new String[result.size()]); } static File programDir() { return programDir(getProgramID()); } static File programDir(String snippetID) { return new File(javaxDataDir(), formatSnippetID(snippetID)); } // also logs full exception to console static String formatExceptionForUser(Throwable e) { printStackTrace(e); String msg = e.getMessage(); if (empty(msg)) return str(e); else return msg; } static Object callOpt(Object o, String method, Object... args) { try { if (o == null) return null; if (o instanceof Class) { Method m = callOpt_findStaticMethod((Class) o, method, args, false); if (m == null) return null; m.setAccessible(true); return m.invoke(null, args); } else { Method m = callOpt_findMethod(o, method, args, false); if (m == null) return null; m.setAccessible(true); return m.invoke(o, args); } } catch (Exception e) { throw new RuntimeException(e); } } static Method callOpt_findStaticMethod(Class c, String method, Object[] args, boolean debug) { Class _c = c; while (c != null) { for (Method m : c.getDeclaredMethods()) { if (debug) System.out.println("Checking method " + m.getName() + " with " + m.getParameterTypes().length + " parameters");; if (!m.getName().equals(method)) { if (debug) System.out.println("Method name mismatch: " + method); continue; } if ((m.getModifiers() & Modifier.STATIC) == 0 || !callOpt_checkArgs(m, args, debug)) continue; return m; } c = c.getSuperclass(); } return null; } static Method callOpt_findMethod(Object o, String method, Object[] args, boolean debug) { Class c = o.getClass(); while (c != null) { for (Method m : c.getDeclaredMethods()) { if (debug) System.out.println("Checking method " + m.getName() + " with " + m.getParameterTypes().length + " parameters");; if (m.getName().equals(method) && callOpt_checkArgs(m, args, debug)) return m; } c = c.getSuperclass(); } return null; } private static boolean callOpt_checkArgs(Method m, Object[] args, boolean debug) { Class[] types = m.getParameterTypes(); if (types.length != args.length) { if (debug) System.out.println("Bad parameter length: " + args.length + " vs " + types.length); return false; } for (int i = 0; i < types.length; i++) if (!(args[i] == null || isInstanceX(types[i], args[i]))) { if (debug) System.out.println("Bad parameter " + i + ": " + args[i] + " vs " + types[i]); return false; } return true; } static List parse3(String s) { return dropPunctuation(javaTokPlusPeriod(s)); } public static boolean isSnippetID(String s) { try { parseSnippetID(s); return true; } catch (RuntimeException e) { return false; } } static Class __javax; static Class getJavaX() { return __javax; } static void smartSet(Field f, Object o, Object value) throws Exception { f.setAccessible(true); // take care of common case (long to int) if (f.getType() == int.class && value instanceof Long) value = ((Long) value).intValue(); f.set(o, value); } static boolean empty(Collection c) { return isEmpty(c); } static boolean empty(String s) { return isEmpty(s); } static boolean empty(Map map) { return map == null || map.isEmpty(); } static boolean equalsIgnoreCase(String a, String b) { return a == null ? b == null : a.equalsIgnoreCase(b); } static File javaxDataDir_dir; // can be set to work on different base dir static File javaxDataDir() { return javaxDataDir_dir != null ? javaxDataDir_dir : new File(userHome(), "JavaX-Data"); } static List dropPunctuation_keep = litlist("*", "<", ">"); static List dropPunctuation(List tok) { tok = new ArrayList(tok); for (int i = 1; i < tok.size(); i += 2) { String t = tok.get(i); if (t.length() == 1 && !Character.isLetter(t.charAt(0)) && !Character.isDigit(t.charAt(0)) && !dropPunctuation_keep.contains(t)) { tok.set(i-1, tok.get(i-1) + tok.get(i+1)); tok.remove(i); tok.remove(i); i -= 2; } } return tok; } static String dropPunctuation(String s) { return join(dropPunctuation(nlTok(s))); } static ArrayList litlist(A... a) { return new ArrayList(Arrays.asList(a)); } static List nlTok(String s) { return javaTokPlusPeriod(s); } }