All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.pircbotx.PircBotX Maven / Gradle / Ivy

// Generated by delombok at Sun Jan 24 05:06:42 EST 2016
/**
 * Copyright (C) 2010-2014 Leon Blakey 
 *
 * This file is part of PircBotX.
 *
 * PircBotX is free software: you can redistribute it and/or modify it under the
 * terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 *
 * PircBotX is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * PircBotX. If not, see .
 */
package org.pircbotx;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.primitives.Ints;
import java.io.BufferedReader;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.InterruptedIOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.lang.ref.WeakReference;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import lombok.NonNull;
import org.apache.commons.lang3.StringUtils;
import org.pircbotx.dcc.DccHandler;
import org.pircbotx.exception.IrcException;
import org.pircbotx.hooks.ListenerAdapter;
import org.pircbotx.hooks.events.*;
import org.pircbotx.output.OutputCAP;
import org.pircbotx.output.OutputDCC;
import org.pircbotx.output.OutputIRC;
import org.pircbotx.output.OutputRaw;
import org.pircbotx.snapshot.UserChannelDaoSnapshot;

/**
 * PircBotX is a Java framework for writing IRC bots quickly and easily.
 * 

* It provides an event-driven architecture to handle common IRC events, flood * protection, DCC support, ident support, and more. The comprehensive logfile * format is suitable for use with pisg to generate channel statistics. *

* Methods of the PircBotX class can be called to send events to the IRC server * that it connects to. For example, calling the sendMessage method will send a * message to a channel or user on the IRC server. Multiple servers can be * supported using multiple instances of PircBotX. *

* To perform an action when the PircBotX receives a normal message from the IRC * server, you would listen for the MessageEvent in your listener (see * {@link ListenerAdapter}). Many other events are dispatched as well for other * incoming lines * * @author Origionally by: * Paul James Mutton for PircBot *

* Forked and Maintained by Leon Blakey in PircBotX */ public class PircBotX implements Comparable, Closeable { @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(PircBotX.class); //THIS LINE IS AUTOGENERATED, DO NOT EDIT /** * The definitive version number of this release of PircBotX. */ public static final String VERSION = "2.1"; protected static final AtomicInteger BOT_COUNT = new AtomicInteger(); /** * Unique number for this bot */ protected final int botId; //Utility objects /** * Configuration used for this bot */ protected final Configuration configuration; protected final InputParser inputParser; /** * User-Channel mapper */ protected final UserChannelDao userChannelDao; protected final DccHandler dccHandler; protected final ServerInfo serverInfo; //Connection stuff. protected Socket socket; protected BufferedReader inputReader; protected Writer outputWriter; protected final OutputRaw outputRaw; protected final OutputIRC outputIRC; protected final OutputCAP outputCAP; protected final OutputDCC outputDCC; /** * Enabled CAP features */ protected List enabledCapabilities = new ArrayList(); protected String nick; protected boolean loggedIn = false; protected Thread shutdownHook; protected volatile boolean reconnectStopped = false; protected ImmutableMap reconnectChannels; private State state = State.INIT; protected final Object stateLock = new Object(); protected Exception disconnectException; protected String serverHostname; protected int serverPort; /** */ protected boolean nickservIdentified = false; private int connectAttempts = 0; private int connectAttemptTotal = 0; /** * Constructs a PircBotX with the provided configuration. * * @param configuration Fully built Configuration */ @SuppressWarnings("unchecked") public PircBotX(@NonNull Configuration configuration) { if (configuration == null) { throw new java.lang.NullPointerException("configuration"); } botId = BOT_COUNT.getAndIncrement(); this.configuration = configuration; this.nick = configuration.getName(); //Pre-insert an initial User representing the bot itself this.userChannelDao = configuration.getBotFactory().createUserChannelDao(this); UserHostmask botHostmask = configuration.getBotFactory().createUserHostmask(this, null, configuration.getName(), configuration.getLogin(), null); getUserChannelDao().createUser(botHostmask); this.serverInfo = configuration.getBotFactory().createServerInfo(this); this.outputRaw = configuration.getBotFactory().createOutputRaw(this); this.outputIRC = configuration.getBotFactory().createOutputIRC(this); this.outputCAP = configuration.getBotFactory().createOutputCAP(this); this.outputDCC = configuration.getBotFactory().createOutputDCC(this); this.dccHandler = configuration.getBotFactory().createDccHandler(this); this.inputParser = configuration.getBotFactory().createInputParser(this); } /** * Start the bot by connecting to the server. If * {@link Configuration#isAutoReconnect()} is true this will continuously * reconnect to the server until {@link #stopBotReconnect() } is called or * an exception is thrown from connecting * * @throws IOException if it was not possible to connect to the server. * @throws IrcException */ public void startBot() throws IOException, IrcException { //Begin magic reconnectStopped = false; do { //Try to connect to the server, grabbing any exceptions LinkedHashMap connectExceptions = Maps.newLinkedHashMap(); try { connectAttemptTotal++; connectAttempts++; connectExceptions.putAll(connect()); } catch (Exception e) { //Initial connect exceptions are returned in the map, this is a more serious error log.error("Exception encountered during connect", e); connectExceptions.put(new InetSocketAddress(serverHostname, serverPort), e); if (!configuration.isAutoReconnect()) throw new RuntimeException("Exception encountered during connect", e); } finally { if (!connectExceptions.isEmpty()) Utils.dispatchEvent(this, new ConnectAttemptFailedEvent(this, configuration.getAutoReconnectAttempts() - connectAttempts, ImmutableMap.copyOf(connectExceptions))); //Cleanup if not already called synchronized (stateLock) { if (state != State.DISCONNECTED) shutdown(); } } //No longer connected to the server if (!configuration.isAutoReconnect()) return; if (reconnectStopped) { log.debug("stopBotReconnect() called, exiting reconnect loop"); return; } if (connectAttempts == configuration.getAutoReconnectAttempts()) { throw new IOException("Failed to connect to IRC server(s) after " + connectAttempts + " attempts"); } //Optionally pause between attempts, useful if network is temporarily down if (configuration.getAutoReconnectDelay() > 0) try { log.debug("Pausing for {} milliseconds before connecting again", configuration.getAutoReconnectDelay()); Thread.sleep(configuration.getAutoReconnectDelay()); } catch (InterruptedException e) { throw new RuntimeException("Interrupted while pausing before the next connect attempt", e); } } while (connectAttempts < configuration.getAutoReconnectAttempts()); } /** * Do not try connecting again in the future. */ public void stopBotReconnect() { reconnectStopped = true; } /** * Attempt to connect to the specified IRC server using the supplied port * number, password, and socketFactory. On success a {@link ConnectEvent} * will be dispatched * * @throws IOException if it was not possible to connect to the server. * @throws IrcException if the server would not let us join it. */ protected ImmutableMap connect() throws IOException, IrcException { synchronized (stateLock) { //Server id Utils.addBotToMDC(this); if (isConnected()) throw new IrcException(IrcException.Reason.AlreadyConnected, "Must disconnect from server before connecting again"); if (getState() == State.CONNECTED) throw new RuntimeException("Bot is not connected but state is State.CONNECTED. This shouldn\'t happen"); if (configuration.isIdentServerEnabled() && IdentServer.getServer() == null) throw new RuntimeException("UseIdentServer is enabled but no IdentServer has been started"); //Reset capabilities enabledCapabilities = new ArrayList(); //Pre-insert an initial User representing the bot itself getUserChannelDao().close(); UserHostmask botHostmask = configuration.getBotFactory().createUserHostmask(this, null, configuration.getName(), configuration.getLogin(), null); getUserChannelDao().createUser(botHostmask); //On each server the user gives us, try to connect to all the IP addresses ImmutableMap.Builder connectExceptions = ImmutableMap.builder(); int serverEntryCounter = 0; ServerEntryLoop: for (Configuration.ServerEntry curServerEntry : configuration.getServers()) { serverEntryCounter++; serverHostname = curServerEntry.getHostname(); //Hostname and port Utils.addBotToMDC(this); log.info("---Starting Connect attempt {}/{}", connectAttempts, configuration.getAutoReconnectAttempts() + "---"); int serverAddressCounter = 0; InetAddress[] serverAddresses = InetAddress.getAllByName(serverHostname); for (InetAddress curAddress : serverAddresses) { serverAddressCounter++; String debug = Utils.format("[{}/{} address left from {}, {}/{} hostnames left] ", String.valueOf(serverAddresses.length - serverAddressCounter), String.valueOf(serverAddresses.length), serverHostname, String.valueOf(configuration.getServers().size() - serverEntryCounter), String.valueOf(configuration.getServers().size())); log.debug("{}Atempting to connect to {} on port {}", debug, curAddress, curServerEntry.getPort()); try { socket = configuration.getSocketFactory().createSocket(curAddress, curServerEntry.getPort(), configuration.getLocalAddress(), 0); //No exception, assume successful serverPort = curServerEntry.getPort(); break ServerEntryLoop; } catch (Exception e) { connectExceptions.put(new InetSocketAddress(curAddress, curServerEntry.getPort()), e); log.warn("{}Failed to connect to {} on port {}", debug, curAddress, curServerEntry.getPort(), e); } } } //Make sure were connected if (socket == null || (socket != null && !socket.isConnected())) { return connectExceptions.build(); } state = State.CONNECTED; socket.setSoTimeout(configuration.getSocketTimeout()); log.info("Connected to server."); changeSocket(socket); } configuration.getListenerManager().onEvent(new SocketConnectEvent(this)); if (configuration.isIdentServerEnabled()) IdentServer.getServer().addIdentEntry(socket.getInetAddress(), socket.getPort(), socket.getLocalPort(), configuration.getLogin()); if (configuration.isCapEnabled()) // Attempt to initiate a CAP transaction. sendCAP().getSupported(); // Attempt to join the server. if (configuration.isWebIrcEnabled()) sendRaw().rawLineNow("WEBIRC " + configuration.getWebIrcPassword() + " " + configuration.getWebIrcUsername() + " " + configuration.getWebIrcHostname() + " " + configuration.getWebIrcAddress().getHostAddress()); if (StringUtils.isNotBlank(configuration.getServerPassword())) sendRaw().rawLineNow("PASS " + configuration.getServerPassword()); sendRaw().rawLineNow("NICK " + configuration.getName()); sendRaw().rawLineNow("USER " + configuration.getLogin() + " 8 * :" + configuration.getRealName()); //Start input to start accepting lines startLineProcessing(); return ImmutableMap.of(); } protected void changeSocket(Socket socket) throws IOException { this.socket = socket; this.inputReader = new BufferedReader(new InputStreamReader(socket.getInputStream(), configuration.getEncoding())); this.outputWriter = new OutputStreamWriter(socket.getOutputStream(), configuration.getEncoding()); } protected void startLineProcessing() { while (processNextLine()) { //see processNextLine } //Now that the socket is definitely closed call event, log, and kill the OutputThread shutdown(); } /** * @return true to continue, false to end */ protected boolean processNextLine() { //Get line from the server String line; try { line = inputReader.readLine(); } catch (InterruptedIOException iioe) { // This will happen if we haven't received anything from the server for a while. // So we shall send it a ping to check that we are still connected. sendRaw().rawLine("PING " + (System.currentTimeMillis() / 1000)); // Now we go back to listening for stuff from the server... return true; } catch (Exception e) { if (Thread.interrupted()) { log.error("--- PircBotX interrupted during read, aborting reconnect loop and shutting down ---"); stopBotReconnect(); return false; } else if (socket.isClosed()) { log.info("Socket is closed, stopping read loop and shutting down"); return false; } else { disconnectException = e; //Something is wrong. Assume its bad and begin disconnect String debug = "Exception encountered when reading next line from server"; log.error(debug, e); Utils.dispatchEvent(this, new ExceptionEvent(this, e, debug)); line = null; } } if (Thread.interrupted()) { log.error("--- PircBotX interrupted during read, aborting reconnect loop and shutting down ---"); stopBotReconnect(); return false; } //End the loop if the line is null if (line == null) return false; //Start acting the line try { inputParser.handleLine(line); } catch (Exception e) { //Exception in client code. Just log and continue String debug = "Exception encountered when parsing line " + line; log.error(debug, e); Utils.dispatchEvent(this, new ExceptionEvent(this, e, debug)); } if (Thread.interrupted()) { log.error("--- PircBotX interrupted during parsing, aborting reconnect loop and shutting down ---"); stopBotReconnect(); return false; } return true; } /** * Actually sends the raw line to the server. This method is NOT * SYNCHRONIZED since it's only called from methods that handle locking * * @param line * @throws java.io.IOException */ protected void sendRawLineToServer(String line) throws IOException { if (line.length() > configuration.getMaxLineLength() - 2) line = line.substring(0, configuration.getMaxLineLength() - 2); outputWriter.write(line + "\r\n"); outputWriter.flush(); List lineParts = Utils.tokenizeLine(line); getConfiguration().getListenerManager().onEvent(new OutputEvent(this, line, lineParts)); } protected void onLoggedIn(String nick) { this.loggedIn = true; setNick(nick); //Were probably connected to the server at this point this.connectAttempts = 0; if (configuration.isShutdownHookEnabled()) Runtime.getRuntime().addShutdownHook(shutdownHook = new PircBotX.BotShutdownHook(this)); } public OutputRaw sendRaw() { return outputRaw; } public OutputIRC sendIRC() { return outputIRC; } public OutputIRC send() { return outputIRC; } public OutputCAP sendCAP() { return outputCAP; } public OutputDCC sendDCC() { return outputDCC; } /** * Sets the internal nick of the bot. This is only to be called by the * PircBotX class in response to notification of nick changes that apply to * us. * * @param nick The new nick. */ protected void setNick(String nick) { this.nick = nick; } /** * 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. * * @since PircBot 1.0.0 * * @return The current nick of the bot. */ public String getNick() { return nick; } /** * Returns whether or not the PircBotX 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 PircBotX is currently connected to a * server. */ public boolean isConnected() { synchronized (this.stateLock) { return socket != null && !socket.isClosed(); } } /** * Returns a String representation of this object. You may find this useful * for debugging purposes, particularly if you are using more than one * PircBotX instance to achieve multiple server connectivity. The format of * this String may change between different versions of PircBotX but is * currently something of the form * Version{PircBotX 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. */ @Override public String toString() { return "Version{" + configuration.getVersion() + "}" + " Connected{" + isConnected() + "}" + " Server{" + getServerHostname() + "}" + " Port{" + getServerPort() + "}"; } /** * Gets the bots own user object. * * @return The user object representing this bot * @see UserChannelDao#getUserBot() */ public User getUserBot() { return userChannelDao.getUser(getNick()); } /** * @return the serverInfo */ public ServerInfo getServerInfo() { return serverInfo; } public InetAddress getLocalAddress() { return socket.getLocalAddress(); } public int getConnectionId() { return connectAttemptTotal; } /** * Get the auto reconnect channels and clear local copy * * @return */ protected ImmutableMap reconnectChannels() { ImmutableMap reconnectChannelsLocal = reconnectChannels; reconnectChannels = null; return reconnectChannelsLocal; } /** * If for some reason you absolutely need to stop PircBotX now instead of * gracefully closing with {@link OutputIRC#quitServer() }, this will close * the socket which causes read loop to terminate which will shutdown * PircBotX shortly. * * @see OutputIRC#quitServer() */ public void close() { try { socket.close(); } catch (Exception e) { log.error("Can\'t close socket", e); } } /** * Fully shutdown the bot and all internal resources. This will close the * connections to the server, kill background threads, clear server specific * state, and dispatch a DisconnectedEvent */ protected void shutdown() { UserChannelDaoSnapshot daoSnapshot; synchronized (stateLock) { log.debug("---PircBotX shutdown started---"); if (state == State.DISCONNECTED) throw new RuntimeException("Cannot call shutdown twice"); state = State.DISCONNECTED; if (configuration.isIdentServerEnabled()) IdentServer.getServer().removeIdentEntry(socket.getInetAddress(), socket.getPort(), socket.getLocalPort(), configuration.getLogin()); //Close the socket from here and let the threads die if (socket != null && !socket.isClosed()) try { socket.close(); } catch (Exception e) { log.error("Cannot close socket", e); } //Cache channels for possible next reconnect ImmutableMap.Builder reconnectChannelsBuilder = ImmutableMap.builder(); for (Channel curChannel : userChannelDao.getAllChannels()) { String key = (curChannel.getChannelKey() == null) ? "" : curChannel.getChannelKey(); reconnectChannelsBuilder.put(curChannel.getName(), key); } reconnectChannels = reconnectChannelsBuilder.build(); //Clear relevant variables of information loggedIn = false; daoSnapshot = (configuration.isSnapshotsEnabled()) ? userChannelDao.createSnapshot() : null; userChannelDao.close(); inputParser.close(); dccHandler.close(); } //Dispatch event configuration.getListenerManager().onEvent(new DisconnectEvent(this, daoSnapshot, disconnectException)); disconnectException = null; log.debug("Disconnected."); //Shutdown listener manager configuration.getListenerManager().shutdown(this); } /** * Compare {@link #getBotId() bot id's}. This is useful for sorting lists of * Channel objects. * * @param other Other channel to compare to * @return the result of calling compareToIgnoreCase on channel names. */ public int compareTo(PircBotX other) { return Ints.compare(getBotId(), other.getBotId()); } /** * @return the state */ public State getState() { synchronized (this.stateLock) { return state; } } protected static class BotShutdownHook extends Thread { protected final WeakReference thisBotRef; public BotShutdownHook(PircBotX bot) { this.thisBotRef = new WeakReference(bot); setName("bot" + BOT_COUNT + "-shutdownhook"); } @Override public void run() { PircBotX thisBot = thisBotRef.get(); if (thisBot != null && thisBot.getState() != PircBotX.State.DISCONNECTED) { thisBot.stopBotReconnect(); thisBot.sendIRC().quitServer(); try { if (thisBot.isConnected()) thisBot.socket.close(); } catch (IOException ex) { log.debug("Unabloe to forcibly close socket", ex); } } } } public static enum State { INIT, CONNECTED, DISCONNECTED; } @java.lang.Override @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") public boolean equals(final java.lang.Object o) { if (o == this) return true; if (!(o instanceof PircBotX)) return false; final PircBotX other = (PircBotX)o; if (!other.canEqual((java.lang.Object)this)) return false; if (this.getBotId() != other.getBotId()) return false; return true; } @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") protected boolean canEqual(final java.lang.Object other) { return other instanceof PircBotX; } @java.lang.Override @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") public int hashCode() { final int PRIME = 59; int result = 1; result = result * PRIME + this.getBotId(); return result; } /** * Unique number for this bot */ @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") public int getBotId() { return this.botId; } /** * Configuration used for this bot */ @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") public Configuration getConfiguration() { return this.configuration; } @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") public InputParser getInputParser() { return this.inputParser; } /** * User-Channel mapper */ @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") public UserChannelDao getUserChannelDao() { return this.userChannelDao; } @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") public DccHandler getDccHandler() { return this.dccHandler; } @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") protected Socket getSocket() { return this.socket; } /** * Enabled CAP features */ @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") public List getEnabledCapabilities() { return this.enabledCapabilities; } @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") public String getServerHostname() { return this.serverHostname; } @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") public int getServerPort() { return this.serverPort; } /** */ @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") public boolean isNickservIdentified() { return this.nickservIdentified; } /** */ @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") protected void setNickservIdentified(final boolean nickservIdentified) { this.nickservIdentified = nickservIdentified; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy