/* * bogobot - A simple join/part stats logger bot for I2P IRC. * * Bogobot.java * 2004 The I2P Project * http://www.i2p.net * This code is public domain. */ import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.Properties; import java.util.Timer; import java.util.TimerTask; import org.apache.log4j.DailyRollingFileAppender; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.apache.log4j.PatternLayout; import org.jibble.pircbot.IrcException; import org.jibble.pircbot.NickAlreadyInUseException; import org.jibble.pircbot.PircBot; import org.jibble.pircbot.User; /** * TODO 0.5 Add multi-server capability. * * @author hypercubus, oOo * @version 0.4 */ public class Bogobot extends PircBot { private static final String INTERVAL_DAILY = "daily"; private static final String INTERVAL_MONTHLY = "monthly"; private static final String INTERVAL_WEEKLY = "weekly"; private boolean _isIntentionalDisconnect = false; private long _lastUserlistCommandTimestamp = 0; private Logger _logger = Logger.getLogger(Bogobot.class); private int _currentAutoRoundTripTag = 0; private long _lastAutoRoundTripSentTime = 0; private Timer _tickTimer; private String _configFile; private String _botPrimaryNick; private String _botSecondaryNick; private String _botNickservPassword; private String _botUsername; private String _ownerPrimaryNick; private String _ownerSecondaryNick; private String _botShutdownPassword; private String _ircChannel; private String _ircServer; private int _ircServerPort; private boolean _isLoggerEnabled; private String _loggedHostnamePattern; private boolean _isUserlistCommandEnabled; private String _logFilePrefix; private String _logFileRotationInterval; private long _commandAntiFloodInterval; private String _userlistCommandTrigger; private boolean _isRoundTripDelayEnabled; private int _roundTripDelayPeriod; class BogobotTickTask extends TimerTask { private Bogobot _caller; public BogobotTickTask(Bogobot caller) { _caller = caller; } public void run() { _caller.onTick(); } } private void loadConfigFile(String configFileName) { _configFile = configFileName; Properties config = new Properties(); FileInputStream fis = null; try { fis = new FileInputStream(configFileName); config.load(fis); } catch (IOException ioe) { System.err.println("Error loading configuration file"); System.exit(2); } finally { if (fis != null) try { fis.close(); } catch (IOException ioe) { // nop } } _botPrimaryNick = config.getProperty("botPrimaryNick", "somebot"); _botSecondaryNick = config.getProperty("botSecondaryNick", "somebot_"); _botNickservPassword = config.getProperty("botNickservPassword", ""); _botUsername = config.getProperty("botUsername", "somebot"); _ownerPrimaryNick = config.getProperty("ownerPrimaryNick", "somenick"); _ownerSecondaryNick = config.getProperty("ownerSecondaryNick", "somenick_"); _botShutdownPassword = config.getProperty("botShutdownPassword", "take off eh"); _ircChannel = config.getProperty("ircChannel", "#i2p-chat"); _ircServer = config.getProperty("ircServer", "irc.postman.i2p"); _ircServerPort = Integer.parseInt(config.getProperty("ircServerPort", "6668")); _isLoggerEnabled = Boolean.valueOf(config.getProperty("isLoggerEnabled", "true")).booleanValue(); _loggedHostnamePattern = config.getProperty("loggedHostnamePattern", ""); _logFilePrefix = config.getProperty("logFilePrefix", "irc.postman.i2p.i2p-chat"); _logFileRotationInterval = config.getProperty("logFileRotationInterval", INTERVAL_DAILY); _isRoundTripDelayEnabled = Boolean.valueOf(config.getProperty("isRoundTripDelayEnabled", "false")).booleanValue(); _roundTripDelayPeriod = Integer.parseInt(config.getProperty("roundTripDelayPeriod", "300")); _isUserlistCommandEnabled = Boolean.valueOf(config.getProperty("isUserlistCommandEnabled", "true")).booleanValue(); _userlistCommandTrigger = config.getProperty("userlistCommandTrigger", "!who"); _commandAntiFloodInterval = Long.parseLong(config.getProperty("commandAntiFloodInterval", "60")); } public Bogobot(String configFileName) { loadConfigFile(configFileName); this.setName(_botPrimaryNick); this.setLogin(_botUsername); _tickTimer = new Timer(); _tickTimer.scheduleAtFixedRate(new BogobotTickTask(this), 1000, 10 * 1000); } public static void main(String[] args) { Bogobot bogobot; if (args.length > 1) { System.err.println("Too many arguments, the only allowed parameter is configuration file name"); System.exit(3); } if (args.length == 1) { bogobot = new Bogobot(args[0]); } else { bogobot = new Bogobot("bogobot.config"); } bogobot.setVerbose(true); if (bogobot._isLoggerEnabled) bogobot.initLogger(); bogobot.connectToServer(); } protected void onTick() { // Tick about once every ten seconds if (this.isConnected() && _isRoundTripDelayEnabled) { if( ( (System.currentTimeMillis() - _lastAutoRoundTripSentTime) >= (_roundTripDelayPeriod * 1000) ) && (this.getOutgoingQueueSize() == 0) ) { // Connected, sending queue is empty and last RoundTrip is more then 5 minutes old -> Send a new one _currentAutoRoundTripTag ++; _lastAutoRoundTripSentTime = System.currentTimeMillis(); sendNotice(this.getNick(),"ROUNDTRIP " + _currentAutoRoundTripTag); } } } protected void onDisconnect() { if (_isIntentionalDisconnect) System.exit(0); if (_isLoggerEnabled) _logger.info(System.currentTimeMillis() + " quits *** " + this.getName() + " *** (Lost connection)"); try { Thread.sleep(60000); } catch (InterruptedException e) { // No worries. } connectToServer(); } protected void onJoin(String channel, String sender, String login, String hostname) { if (_isLoggerEnabled) { if (sender.equals(this.getName())) { _logger.info(System.currentTimeMillis() + " joins *** " + _botPrimaryNick + " ***"); } else { String prependedHostname = "@" + hostname; if (prependedHostname.endsWith(_loggedHostnamePattern)) { _logger.info(System.currentTimeMillis() + " joins " + sender); } } } } protected void onMessage(String channel, String sender, String login, String hostname, String message) { message = message.replaceFirst("<.+?> ", ""); if (_isUserlistCommandEnabled && message.equals(_userlistCommandTrigger)) { if (System.currentTimeMillis() - _lastUserlistCommandTimestamp < _commandAntiFloodInterval * 1000) return; Object[] users = getUsers(_ircChannel); String output = "Userlist for " + _ircChannel + ": "; for (int i = 0; i < users.length; i++) output += "[" + ((User) users[i]).getNick() + "] "; sendMessage(_ircChannel, output); _lastUserlistCommandTimestamp = System.currentTimeMillis(); } } protected void onPart(String channel, String sender, String login, String hostname) { if (_isLoggerEnabled) { if (sender.equals(this.getName())) { _logger.info(System.currentTimeMillis() + " parts *** " + _botPrimaryNick + " ***"); } else { String prependedHostname = "@" + hostname; if (prependedHostname.endsWith(_loggedHostnamePattern)) { _logger.info(System.currentTimeMillis() + " parts " + sender); } } } } protected void onPrivateMessage(String sender, String login, String hostname, String message) { /* * Nobody else except the bot's owner can shut it down, unless of * course the owner's nick isn't registered and someone's spoofing it. */ if ((sender.equals(_ownerPrimaryNick) || sender.equals(_ownerSecondaryNick)) && message.equals(_botShutdownPassword)) { if (_isLoggerEnabled) _logger.info(System.currentTimeMillis() + " quits *** " + this.getName() + " ***"); _isIntentionalDisconnect = true; disconnect(); } } protected void onQuit(String sourceNick, String sourceLogin, String sourceHostname, String reason) { String prependedHostname = "@" + sourceHostname; if (sourceNick.equals(_botPrimaryNick)) changeNick(_botPrimaryNick); if (_isLoggerEnabled) { if (prependedHostname.endsWith(_loggedHostnamePattern)) { _logger.info(System.currentTimeMillis() + " quits " + sourceNick + " " + reason); } } } private void connectToServer() { int loginAttempts = 0; while (true) { try { connect(_ircServer, _ircServerPort); break; } catch (NickAlreadyInUseException e) { if (loginAttempts == 1) { System.out.println("Sorry, the primary and secondary bot nicks are already taken. Exiting."); System.exit(1); } loginAttempts++; try { Thread.sleep(5000); } catch (InterruptedException e1) { // Hmph. } if (getName().equals(_botPrimaryNick)) setName(_botSecondaryNick); else setName(_botPrimaryNick); continue; } catch (IOException e) { System.out.println("Error during login: "); e.printStackTrace(); System.exit(1); } catch (IrcException e) { System.out.println("Error during login: "); e.printStackTrace(); System.exit(1); } } joinChannel(_ircChannel); } protected void onNotice(String sourceNick, String sourceLogin, String sourceHostname, String target, String notice) { if (sourceNick.equals("NickServ") && (notice.indexOf("/msg NickServ IDENTIFY") >= 0) && (_botNickservPassword != "")) { sendRawLineViaQueue("NICKSERV IDENTIFY " + _botNickservPassword); } if (sourceNick.equals(getNick()) && notice.equals( "ROUNDTRIP " + _currentAutoRoundTripTag)) { int delay = (int)((System.currentTimeMillis() - _lastAutoRoundTripSentTime) / 100); // sendMessage(_ircChannel, "Round-trip delay = " + (delay / 10.0f) + " seconds"); if (_isLoggerEnabled) _logger.info(System.currentTimeMillis() + " roundtrip " + delay); } } private void initLogger() { String logFilePath = "logs" + File.separator + _logFilePrefix; DailyRollingFileAppender rollingFileAppender = null; if (!(new File("logs").exists())) (new File("logs")).mkdirs(); try { if (_logFileRotationInterval.equals("monthly")) rollingFileAppender = new DailyRollingFileAppender(new PatternLayout("%m%n"), logFilePath, "'.'yyyy-MM'.log'"); else if (_logFileRotationInterval.equals("weekly")) rollingFileAppender = new DailyRollingFileAppender(new PatternLayout("%m%n"), logFilePath, "'.'yyyy-ww'.log'"); else rollingFileAppender = new DailyRollingFileAppender(new PatternLayout("%m%n"), logFilePath, "'.'yyyy-MM-dd'.log'"); rollingFileAppender.setThreshold(Level.INFO); _logger.addAppender(rollingFileAppender); } catch (IOException ex) { System.out.println("Error: Couldn't create or open an existing log file. Exiting."); System.exit(1); } } }