Java tutorial
/* * Copyright (c) <2013> <Jim Johnson jimj@jimj.net> * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package net.jimj.automaton; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.eventbus.EventBus; import com.google.common.eventbus.Subscribe; import com.mongodb.DB; import com.mongodb.MongoException; import net.jimj.automaton.commands.Command; import net.jimj.automaton.commands.HeadCommand; import net.jimj.automaton.commands.HelpCommand; import net.jimj.automaton.commands.KarmaCommand; import net.jimj.automaton.commands.NoteCommand; import net.jimj.automaton.commands.PrefCommand; import net.jimj.automaton.commands.Processor; import net.jimj.automaton.commands.QuoteCommand; import net.jimj.automaton.commands.UserCommand; import net.jimj.automaton.commands.WeatherCommand; import net.jimj.automaton.commands.YourMomCommand; import net.jimj.automaton.commands.ZingCommand; import net.jimj.automaton.events.BehaviorEvent; import net.jimj.automaton.events.DataStoredEvent; import net.jimj.automaton.events.HelpEvent; import net.jimj.automaton.events.ReplyEvent; import net.jimj.automaton.events.UnknownUserEvent; import net.jimj.automaton.irc.CommandResponse; import net.jimj.automaton.irc.IRCClient; import net.jimj.automaton.irc.IRCConnection; import net.jimj.automaton.irc.Message; import net.jimj.automaton.model.Config; import net.jimj.automaton.model.Stats; import net.jimj.automaton.model.User; import org.apache.commons.lang3.StringUtils; import org.apache.http.conn.ssl.SSLContextBuilder; import org.apache.http.conn.ssl.SSLContexts; import org.mongojack.JacksonDBCollection; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.net.ssl.SSLContext; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.security.KeyStore; import java.util.ArrayList; import java.util.HashMap; import java.util.concurrent.TimeUnit; public class Bot { private static final Logger LOGGER = LoggerFactory.getLogger(Bot.class); private static final int END_OF_MOTD = 376; private ObjectMapper objectMapper; private HashMap<String, Command> commandMap = new HashMap<>(); private ArrayList<Processor> processors = new ArrayList<>(); private IRCClient ircClient; private UserAccess userAccess; private Config config; private DB db; private StatsCollector statsCollector; private EventBus eventBus; public Bot(DB db) { this.eventBus = new EventBus(); eventBus.register(this); this.db = db; this.statsCollector = new StatsCollector(); this.objectMapper = new ObjectMapper(); this.userAccess = new UserAccess(db.getCollection("users")); loadConfig(); loadCommands(); } public static InputStream getConfigAsStream(String propName, String defaultLocation) throws FileNotFoundException { InputStream inputStream; String location = System.getProperty(propName); if (location != null) { inputStream = new FileInputStream(location); } else { inputStream = Bot.class.getResourceAsStream(defaultLocation); } return inputStream; } public void go() { LOGGER.debug("Creating statsCollector thread"); Runnable collector = new Runnable() { @Override public void run() { JacksonDBCollection<Stats, String> statsCollection = JacksonDBCollection .wrap(db.getCollection("botstats"), Stats.class, String.class); Stats lastStats = null; while (true) { try { Stats currentStats = statsCollector.toStats(); //If the current stats are different or the last stats are over an hour old if (!currentStats.equals(lastStats) || (currentStats.getRuntimeMillis() - lastStats.getRuntimeMillis()) > TimeUnit.HOURS .toMillis(1)) { statsCollection.insert(currentStats); lastStats = currentStats; } Thread.sleep(TimeUnit.SECONDS.toMillis(15)); } catch (InterruptedException e) { //Shutting down. statsCollection.insert(statsCollector.toStats()); break; } catch (MongoException me) { LOGGER.error("Error in stats collection", me); } } } }; new Thread(collector).start(); connect(); } @Subscribe public void execute(ReplyEvent event) { ircClient.sendPrivMsg(event.getTarget(), event.getMessage()); } @Subscribe public void execute(HelpEvent event) { String cmdName = event.getCommandName(); Command cmd = commandMap.get(cmdName); if (cmdName == null) { StringBuilder commandList = new StringBuilder(); for (String command : commandMap.keySet()) { commandList.append(command).append(", "); } commandList.replace(commandList.length() - 2, commandList.length(), ""); ircClient.sendPrivMsg(event.getTarget(), "Command list: " + commandList.toString()); } else if (cmd == null) { ircClient.sendPrivMsg(event.getTarget(), "Unknown command: " + cmdName); } else { cmd.help(event.getUser()); } } @Subscribe public void execute(UnknownUserEvent event) { if (event.isSelf()) { ircClient.sendPrivMsg(event.getTarget(), "Sorry " + event.getUser().getNick() + " but I don't know who you are."); } else { ircClient.sendPrivMsg(event.getTarget(), "Sorry " + event.getUser().getNick() + " but I don't know who " + event.getUnknownNick() + " is."); } } @Subscribe public void execute(DataStoredEvent event) { ircClient.sendPrivMsg(event.getTarget(), event.getMessage()); } @Subscribe public void execute(BehaviorEvent event) { ircClient.sendPrivMsg(event.getTarget(), event.getResponse()); } private void connect() { LOGGER.debug("Connecting..."); try { SSLContext ctx = null; if (StringUtils.isNotBlank(config.getIrcKeystoreLocation())) { SSLContextBuilder ctxBuilder = SSLContexts.custom(); KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); try (FileInputStream keyInStream = new FileInputStream( new File(config.getIrcKeystoreLocation()));) { trustStore.load(keyInStream, config.getIrcKeystorePassword().toCharArray()); } ctxBuilder.loadTrustMaterial(trustStore); ctx = ctxBuilder.build(); } IRCConnection.Builder connectionBuilder = IRCConnection.builder().withHost(config.getServer()) .withNick(config.getNick()); if (config.getPort() != -1) { connectionBuilder = connectionBuilder.withPort(config.getPort()).withSSLContext(ctx); } IRCConnection ircConnection = connectionBuilder.withListener(this).build(); ircConnection.connect(); ircClient = new IRCClient(ircConnection); } catch (Exception e) { LOGGER.error("Exception in go method ", e); } } @Subscribe public void onCommandResponse(CommandResponse response) { if (response.getCode() == END_OF_MOTD) { //End of MOTD. for (String channel : config.getChannels()) { ircClient.joinChannel(channel); } } } @Subscribe public void onMessage(Message message) { if (message.getType().equals("PRIVMSG")) { String from = message.getFrom(); //Extract irc username if (from.contains("!")) { from = from.substring(0, from.indexOf("!")); } if (from.startsWith(":")) { from = from.substring(1); } User user = userAccess.findUser(from); String channel = null; String messageText = message.getMessage(); //Extract the channel, if present. Also chop off the leading : from the message. if (!messageText.startsWith(":")) { channel = messageText.substring(0, messageText.indexOf(":")).trim(); messageText = messageText.substring(messageText.indexOf(":") + 1); } else { messageText = messageText.substring(1); } user.setChannel(channel); processMessage(user, messageText); } else { LOGGER.warn( "Not sure what to do with message type " + message.getType() + " from " + message.getFrom()); } } protected void processMessage(User user, String message) { //Ignore self for any processing. if (user.getNick().equals(config.getNick())) { return; } statsCollector.incMessageCount(); if (message.startsWith(config.getCommandChar())) { statsCollector.incCommandCount(); String commandName = message; //Try to chop off the first word int commandNameEnd = message.indexOf(" "); String args = null; //Strip off the command character for looking up the command if (commandNameEnd != -1) { commandName = message.substring(config.getCommandChar().length(), commandNameEnd); args = StringUtils .strip(message.substring(config.getCommandChar().length() + commandName.length())); } else { commandName = commandName.substring(config.getCommandChar().length()); } try { fireCommand(user, commandName, args); } catch (Exception e) { eventBus.post(new BehaviorEvent(user, BehaviorEvent.Behavior.SASSY)); LOGGER.error("Caused by " + user.getNick() + " with message " + message, e); } } else { for (Processor processor : processors) { if (processor.shouldProcess(message)) { statsCollector.incProcessCount(); processor.process(user, message); } } } } protected void fireCommand(User user, String commandName, String args) { LOGGER.debug("Firing command " + commandName); Command commandObj = commandMap.get(commandName); if (commandObj != null) { commandObj.execute(user, args); } else { eventBus.post(new BehaviorEvent(user, BehaviorEvent.Behavior.CONFUSED, BehaviorEvent.Behavior.SASSY)); } } private void loadCommands() { loadCommand(new QuoteCommand(db.getCollection("quotes"))); loadCommand(new HeadCommand()); loadCommand(new KarmaCommand(db.getCollection("karma"))); loadCommand(new YourMomCommand(db.getCollection("yourmom"))); loadCommand(new NoteCommand(db.getCollection("notes"))); loadCommand(new HelpCommand()); loadCommand(new UserCommand(userAccess)); loadCommand(new PrefCommand(userAccess)); loadCommand(new WeatherCommand()); loadCommand(new ZingCommand()); } private void loadCommand(Command command) { command.setEventBus(eventBus); commandMap.put(command.getCommandName(), command); if (command instanceof Processor) { processors.add((Processor) command); } } private void loadConfig() { try { InputStream configStream = getConfigAsStream("bot.config", "/META-INF/config.json"); config = objectMapper.readValue(configStream, Config.class); if (LOGGER.isDebugEnabled()) { LOGGER.debug(objectMapper.writeValueAsString(config)); } } catch (IOException e) { System.err.println("Error loading configuration"); e.printStackTrace(System.err); throw new IllegalStateException("Error loading configuration"); } } private void messageOwner(String message) { String owner = userAccess.getOwnerNick(); if (owner != null) { ircClient.sendPrivMsg(owner, message); } } }