diff --git a/src/main/java/com/gmail/inverseconduit/BotConfig.java b/src/main/java/com/gmail/inverseconduit/BotConfig.java index 4b59537..cf3c84f 100644 --- a/src/main/java/com/gmail/inverseconduit/BotConfig.java +++ b/src/main/java/com/gmail/inverseconduit/BotConfig.java @@ -8,6 +8,7 @@ import java.util.Properties; import java.util.logging.Level; import java.util.logging.Logger; + import com.gmail.inverseconduit.datatype.CredentialsProvider; /** @@ -76,6 +77,7 @@ public String getLoginPassword() { /** * Gets the string sequence that triggers the bot. * + * @return the string considered the trigger * @deprecated This constant is only here for legacy purposes. Use * @ListenerProperty to specify the command invocation * sequence(s). diff --git a/src/main/java/com/gmail/inverseconduit/bot/AbstractBot.java b/src/main/java/com/gmail/inverseconduit/bot/AbstractBot.java new file mode 100644 index 0000000..23aac84 --- /dev/null +++ b/src/main/java/com/gmail/inverseconduit/bot/AbstractBot.java @@ -0,0 +1,37 @@ +package com.gmail.inverseconduit.bot; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import com.gmail.inverseconduit.chat.ChatWorker; +import com.gmail.inverseconduit.datatype.ChatMessage; + +public abstract class AbstractBot implements ChatWorker { + + protected final ScheduledExecutorService executor = Executors + .newSingleThreadScheduledExecutor(); + + protected final ExecutorService processingThread = Executors + .newSingleThreadExecutor(); + + protected final BlockingQueue messageQueue = new LinkedBlockingQueue<>(); + + @Override + public final synchronized boolean enqueueMessage(ChatMessage chatMessage) + throws InterruptedException { + return messageQueue.offer(chatMessage, 200, TimeUnit.MILLISECONDS); + } + + @Override + public abstract void start(); + + @Override + protected void finalize() { + executor.shutdownNow(); + } + +} diff --git a/src/main/java/com/gmail/inverseconduit/bot/DefaultBot.java b/src/main/java/com/gmail/inverseconduit/bot/DefaultBot.java index 522bb40..b6faf20 100644 --- a/src/main/java/com/gmail/inverseconduit/bot/DefaultBot.java +++ b/src/main/java/com/gmail/inverseconduit/bot/DefaultBot.java @@ -1,23 +1,22 @@ package com.gmail.inverseconduit.bot; +import java.util.Collections; import java.util.HashSet; import java.util.Set; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.Executors; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; -import com.gmail.inverseconduit.chat.ChatWorker; +import com.gmail.inverseconduit.AppContext; +import com.gmail.inverseconduit.BotConfig; +import com.gmail.inverseconduit.chat.ChatInterface; import com.gmail.inverseconduit.chat.Subscribable; import com.gmail.inverseconduit.commands.CommandHandle; import com.gmail.inverseconduit.datatype.ChatMessage; +import com.gmail.inverseconduit.datatype.SeChatDescriptor; /** * Defines bot core functionality. A bot manages {@link CommandHandle - * CommandHandles}. - * Additionally messages should be enqueued to him, using + * CommandHandles}. Additionally messages should be enqueued to him, using * {@link DefaultBot#enqueueMessage(ChatMessage) enqueueMessage}.
*
* These messages will get preprocessed and then passed to their respective @@ -27,21 +26,16 @@ * >vincentyification@gmail.com> * @author Vogel612<vogel612@gmx.de> */ -public class DefaultBot implements Subscribable, ChatWorker { +public class DefaultBot extends AbstractBot implements Subscribable { - private final Logger LOGGER = Logger.getLogger(DefaultBot.class.getName()); + private final Logger LOGGER = Logger.getLogger(DefaultBot.class.getName()); - protected final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); + protected final ChatInterface chatInterface; - protected final BlockingQueue messageQueue = new LinkedBlockingQueue<>(); + protected final Set commands = new HashSet<>(); - protected final Set commands = new HashSet<>(); - - public DefaultBot() {} - - @Override - public synchronized boolean enqueueMessage(ChatMessage chatMessage) throws InterruptedException { - return messageQueue.offer(chatMessage, 200, TimeUnit.MILLISECONDS); + public DefaultBot(ChatInterface chatInterface) { + this.chatInterface = chatInterface; } @Override @@ -51,14 +45,22 @@ public void start() { private void processMessageQueue() { while (messageQueue.peek() != null) { - LOGGER.info("processing message from queue"); - processMessage(); + LOGGER.finest("processing message from queue"); + processingThread.submit(() -> processMessage(messageQueue.poll())); } } - private void processMessage() { - final ChatMessage message = messageQueue.poll(); - commands.stream().filter(c -> c.matchesSyntax(message.getMessage())).findFirst().ifPresent(c -> c.execute(message)); + private void processMessage(final ChatMessage chatMessage) { + final String trigger = AppContext.INSTANCE.get(BotConfig.class).getTrigger(); + if ( !chatMessage.getMessage().startsWith(trigger)) { return; } + + commands.stream() + // FIXME: make the trigger removal for call-by-name better!! + .filter(c -> c.getName().equalsIgnoreCase(chatMessage.getMessage().replace(trigger, ""))).findFirst().map(c -> c.execute(chatMessage)).ifPresent(result -> chatInterface.sendMessage(SeChatDescriptor.buildSeChatDescriptorFrom(chatMessage), result)); + } + + public Set getCommands() { + return Collections.unmodifiableSet(commands); } @Override @@ -71,8 +73,8 @@ public void unSubscribe(CommandHandle subscriber) { commands.remove(subscriber); } - @Override - protected void finalize() { - executor.shutdownNow(); + public void shutdown() { + executor.shutdown(); } + } diff --git a/src/main/java/com/gmail/inverseconduit/bot/InteractionBot.java b/src/main/java/com/gmail/inverseconduit/bot/InteractionBot.java new file mode 100644 index 0000000..72718f4 --- /dev/null +++ b/src/main/java/com/gmail/inverseconduit/bot/InteractionBot.java @@ -0,0 +1,59 @@ +package com.gmail.inverseconduit.bot; + +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import com.gmail.inverseconduit.bot.interactions.Interaction; +import com.gmail.inverseconduit.bot.interactions.Interactions; +import com.gmail.inverseconduit.chat.ChatInterface; +import com.gmail.inverseconduit.chat.Subscribable; +import com.gmail.inverseconduit.datatype.ChatMessage; +import com.gmail.inverseconduit.datatype.SeChatDescriptor; + +public class InteractionBot extends AbstractBot implements + Subscribable { + + private final ChatInterface chatInterface; + + protected final Set interactions = new HashSet<>(); + + public InteractionBot(ChatInterface chatInterface) { + this.chatInterface = chatInterface; + Interactions.getPerminteractions().forEach(interactions::add); + } + + @Override + public void start() { + executor.scheduleAtFixedRate(this::processInteractions, 200, 700, + TimeUnit.MILLISECONDS); + } + + private void processInteractions() { + if (messageQueue.peek() != null) { + processingThread.submit(() -> interact(messageQueue.poll())); + } + } + + private void interact(ChatMessage message) { + interactions + .stream() + .filter(interaction -> interaction.getCondition().test( + message.getMessage())) + .findFirst() + .ifPresent( + action -> chatInterface.sendMessage(SeChatDescriptor + .buildSeChatDescriptorFrom(message), action + .getResponse())); + } + + @Override + public void subscribe(Interaction subscriber) { + interactions.add(subscriber); + } + + @Override + public void unSubscribe(Interaction subscriber) { + interactions.remove(subscriber); + } +} diff --git a/src/main/java/com/gmail/inverseconduit/bot/Program.java b/src/main/java/com/gmail/inverseconduit/bot/Program.java index 7d0fe89..11994ea 100644 --- a/src/main/java/com/gmail/inverseconduit/bot/Program.java +++ b/src/main/java/com/gmail/inverseconduit/bot/Program.java @@ -14,12 +14,10 @@ import com.gmail.inverseconduit.SESite; import com.gmail.inverseconduit.chat.ChatInterface; import com.gmail.inverseconduit.chat.StackExchangeChat; -import com.gmail.inverseconduit.chat.commands.ChatCommands; import com.gmail.inverseconduit.commands.CommandHandle; +import com.gmail.inverseconduit.commands.sets.CoreBotCommands; import com.gmail.inverseconduit.datatype.SeChatDescriptor; import com.gmail.inverseconduit.javadoc.JavaDocAccessor; -import com.gmail.inverseconduit.scripts.ScriptRunner; -import com.gmail.inverseconduit.scripts.ScriptRunnerCommands; /** * Class to contain the program, to be started from main. This class is @@ -27,167 +25,104 @@ * * @author vogel612<vogel612@gmx.de> */ -@SuppressWarnings("deprecation") public class Program { - private static final Logger LOGGER = Logger.getLogger(Program.class - .getName()); - - private static final ScheduledExecutorService executor = Executors - .newSingleThreadScheduledExecutor(); - - private static final BotConfig config = AppContext.INSTANCE - .get(BotConfig.class); - - private final DefaultBot bot; - - private final ChatInterface chatInterface; - - private final ScriptRunner scriptRunner; - - private final JavaDocAccessor javaDocAccessor; - - private static final Pattern javadocPattern = Pattern.compile( - "^" + Pattern.quote(config.getTrigger()) + "javadoc:(.*)", - Pattern.DOTALL); - - /** - * @throws IOException - * if there's a problem loading the Javadocs - */ - public Program() throws IOException { - LOGGER.finest("Instantiating Program"); - chatInterface = new StackExchangeChat(); - bot = new DefaultBot(); - - chatInterface.subscribe(bot); - - javaDocAccessor = new JavaDocAccessor(chatInterface, - config.getJavadocsDir()); - scriptRunner = new ScriptRunner(chatInterface); - LOGGER.info("Basic component setup complete"); - } - - /** - * This is where the beef happens. Glue all the stuff together here - */ - public void startup() { - LOGGER.info("Beginning startup process"); - bindDefaultCommands(); - login(); - for (Integer room : config.getRooms()) { - chatInterface.joinChat(new SeChatDescriptor.DescriptorBuilder( - SESite.STACK_OVERFLOW).setRoom(() -> room).build()); - } - scheduleQueryingThread(); - bot.start(); - LOGGER.info("Startup completed."); - } - - private void scheduleQueryingThread() { - executor.scheduleAtFixedRate( - () -> { - try { - chatInterface.queryMessages(); - } catch (RuntimeException | Error e) { - Logger.getAnonymousLogger().log(Level.SEVERE, - "Throwable occurred in querying thread", e); - throw e; - } catch (Exception e) { - Logger.getAnonymousLogger().log(Level.WARNING, - "Exception occured in querying thread:", e); - } - }, 5, 3, TimeUnit.SECONDS); - Logger.getAnonymousLogger().info("querying thread started"); - } - - private void login() { - boolean loggedIn = chatInterface.login(SESite.STACK_OVERFLOW, config); - if (!loggedIn) { - Logger.getAnonymousLogger().severe("Login failed!"); - System.exit(2); - } - } - - private void bindDefaultCommands() { - bindHelpCommand(); - bindShutdownCommand(); - bindEvalCommand(); - bindLoadCommand(); - bindJavaDocCommand(); - bindTestCommand(); - bindSummonCommand(); - bindUnsummonCommand(); - } - - private void bindUnsummonCommand() { - CommandHandle unsummon = ChatCommands.unsummonCommand(chatInterface); - bot.subscribe(unsummon); - } - - private void bindSummonCommand() { - CommandHandle summon = ChatCommands.summonCommand(chatInterface); - bot.subscribe(summon); - } - - private void bindEvalCommand() { - CommandHandle eval = ScriptRunnerCommands.evalCommand(scriptRunner); - bot.subscribe(eval); - } - - private void bindLoadCommand() { - CommandHandle load = ScriptRunnerCommands.loadCommand(scriptRunner); - bot.subscribe(load); - } - - private void bindHelpCommand() { - CommandHandle help = new CommandHandle.Builder( - "help", - s -> { - return s.trim().startsWith(config.getTrigger() + "help"); - }, - message -> { - chatInterface.sendMessage( - SeChatDescriptor.buildSeChatDescriptorFrom(message), - String.format( - "@%s I am JavaBot, maintained by Uni, Vogel, and a few others. You can find me on http://github.com/Vincentyification/JavaBot", - message.getUsername())); - }).build(); - bot.subscribe(help); - } - - private void bindJavaDocCommand() { - CommandHandle javaDoc = new CommandHandle.Builder("javadoc", - javadocPattern.asPredicate(), message -> { - Matcher matcher = javadocPattern.matcher(message - .getMessage()); - matcher.find(); - javaDocAccessor.javadoc(message, matcher.group(1).trim()); - }).build(); - bot.subscribe(javaDoc); - } - - private void bindShutdownCommand() { - CommandHandle shutdown = new CommandHandle.Builder("shutdown", s -> { - return s.trim().startsWith(config.getTrigger() + "shutdown"); - }, message -> { - // FIXME: Require permissions for this - chatInterface.broadcast("*~going down*"); - executor.shutdownNow(); - System.exit(0); - }).build(); - bot.subscribe(shutdown); - } - - private void bindTestCommand() { - CommandHandle test = new CommandHandle.Builder( - "test", - s -> s.equals("test"), - message -> { - chatInterface.sendMessage( - SeChatDescriptor.buildSeChatDescriptorFrom(message), - "*~response*"); - }).build(); - bot.subscribe(test); - } + private static final Logger LOGGER = Logger.getLogger(Program.class.getName()); + + private static final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); + + private static final BotConfig config = AppContext.INSTANCE.get(BotConfig.class); + + private final DefaultBot bot; + + private final InteractionBot interactionBot; + + private final ChatInterface chatInterface = new StackExchangeChat(); + + private final JavaDocAccessor javaDocAccessor; + + private static final Pattern javadocPattern = Pattern.compile("^" + Pattern.quote(config.getTrigger()) + "javadoc:(.*)", Pattern.DOTALL); + + /** + * @throws IOException + * if there's a problem loading the Javadocs + */ + // TODO: get the chatInterface solved via Dependency Injection instead. + // This would greatly improve testability and ease of switching + // implementations + public Program() throws IOException { + LOGGER.finest("Instantiating Program"); + bot = new DefaultBot(chatInterface); + interactionBot = new InteractionBot(chatInterface); + + //better not get ExceptionInInitializerError + javaDocAccessor = new JavaDocAccessor(config.getJavadocsDir()); + chatInterface.subscribe(bot); + chatInterface.subscribe(interactionBot); + LOGGER.info("Basic component setup complete"); + } + + /** + * This is where the beef happens. Glue all the stuff together here + */ + public void startup() { + LOGGER.info("Beginning startup process"); + bindDefaultCommands(); + login(); + for (Integer room : config.getRooms()) { + chatInterface.joinChat(new SeChatDescriptor.DescriptorBuilder(SESite.STACK_OVERFLOW).setRoom(() -> room).build()); + } + scheduleQueryingThread(); + bot.start(); + interactionBot.start(); + LOGGER.info("Startup completed."); + } + + private void scheduleQueryingThread() { + executor.scheduleAtFixedRate(() -> { + try { + chatInterface.queryMessages(); + } catch(RuntimeException | Error e) { + Logger.getAnonymousLogger().log(Level.SEVERE, "Runtime Exception or Error occurred in querying thread", e); + throw e; + } catch(Exception e) { + Logger.getAnonymousLogger().log(Level.WARNING, "Exception occured in querying thread:", e); + } + }, 5, 3, TimeUnit.SECONDS); + Logger.getAnonymousLogger().info("querying thread started"); + } + + private void login() { + boolean loggedIn = chatInterface.login(SESite.STACK_OVERFLOW, config); + if ( !loggedIn) { + Logger.getAnonymousLogger().severe("Login failed!"); + System.exit(2); + } + } + + private void bindDefaultCommands() { + bindShutdownCommand(); + bindJavaDocCommand(); + new CoreBotCommands(chatInterface).allCommands().forEach(bot::subscribe); + } + + private void bindJavaDocCommand() { + CommandHandle javaDoc = new CommandHandle.Builder("javadoc", message -> { + Matcher matcher = javadocPattern.matcher(message.getMessage()); + matcher.find(); + return javaDocAccessor.javadoc(message, matcher.group(1).trim()); + }).build(); + bot.subscribe(javaDoc); + } + + private void bindShutdownCommand() { + CommandHandle shutdown = new CommandHandle.Builder("shutdown", message -> { + // FIXME: Require permissions for this + chatInterface.broadcast("*~going down*"); + executor.shutdownNow(); + System.exit(0); + return ""; + }).build(); + bot.subscribe(shutdown); + } } diff --git a/src/main/java/com/gmail/inverseconduit/bot/interactions/Interaction.java b/src/main/java/com/gmail/inverseconduit/bot/interactions/Interaction.java new file mode 100644 index 0000000..a1ae7c2 --- /dev/null +++ b/src/main/java/com/gmail/inverseconduit/bot/interactions/Interaction.java @@ -0,0 +1,24 @@ +package com.gmail.inverseconduit.bot.interactions; + +import java.util.function.Predicate; + +public class Interaction { + + private final Predicate condition; + + private final String response; + + public Interaction(Predicate condition, String response) { + this.condition = condition; + this.response = response; + } + + public Predicate getCondition() { + return condition; + } + + public String getResponse() { + return response; + } + +} diff --git a/src/main/java/com/gmail/inverseconduit/bot/interactions/Interactions.java b/src/main/java/com/gmail/inverseconduit/bot/interactions/Interactions.java new file mode 100644 index 0000000..1873cc8 --- /dev/null +++ b/src/main/java/com/gmail/inverseconduit/bot/interactions/Interactions.java @@ -0,0 +1,29 @@ +package com.gmail.inverseconduit.bot.interactions; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +public final class Interactions { + + private static final Set permInteractions = new HashSet<>(); + + static { + permInteractions.add(howDoI()); + permInteractions.add(thatWord()); + } + + public static Set getPerminteractions() { + return Collections.unmodifiableSet(permInteractions); + } + + private static Interaction howDoI() { + return new Interaction((s) -> { + return s.startsWith("how do I") || s.startsWith("how do i"); + }, "~ Write **code**"); + } + + private static Interaction thatWord() { + return new Interaction((s) -> s.contains("you keep using that word"), "https://www.youtube.com/watch?v=G2y8Sx4B2Sk"); + } +} diff --git a/src/main/java/com/gmail/inverseconduit/chat/ChatInterface.java b/src/main/java/com/gmail/inverseconduit/chat/ChatInterface.java index d512678..6be81af 100644 --- a/src/main/java/com/gmail/inverseconduit/chat/ChatInterface.java +++ b/src/main/java/com/gmail/inverseconduit/chat/ChatInterface.java @@ -19,7 +19,7 @@ * @author Vogel612<vogel612@gmx.de> */ @ThreadSafe -public interface ChatInterface extends Subscribable { +public interface ChatInterface extends Subscribable, AutoCloseable { /** * Queries the messages of the chat. This method is designed to be called @@ -89,4 +89,9 @@ public interface ChatInterface extends Subscribable { * the broadcast message */ void broadcast(String message); + + @Override + default void close() throws Exception { + + } } diff --git a/src/main/java/com/gmail/inverseconduit/chat/StackExchangeChat.java b/src/main/java/com/gmail/inverseconduit/chat/StackExchangeChat.java index 1ffb2b5..bc540f1 100644 --- a/src/main/java/com/gmail/inverseconduit/chat/StackExchangeChat.java +++ b/src/main/java/com/gmail/inverseconduit/chat/StackExchangeChat.java @@ -326,4 +326,12 @@ public void unSubscribe(final ChatWorker subscriber) { subscribers.remove(subscriber); } + @Override + public void close() throws Exception { + subscribers.clear(); + chatMap.clear(); + sender.shutdown(); + webClient.closeAllWindows(); + } + } diff --git a/src/main/java/com/gmail/inverseconduit/chat/Subscribable.java b/src/main/java/com/gmail/inverseconduit/chat/Subscribable.java index 7a5f2bc..0443052 100644 --- a/src/main/java/com/gmail/inverseconduit/chat/Subscribable.java +++ b/src/main/java/com/gmail/inverseconduit/chat/Subscribable.java @@ -6,6 +6,8 @@ * One can subscribe and unSubscribe instances of T to recieve produced U's * * @author Vogel612<vogel612@gmx.de> + * @param + * The Class of Subscriber instances, that can handle instances of U */ public interface Subscribable { diff --git a/src/main/java/com/gmail/inverseconduit/commands/CommandHandle.java b/src/main/java/com/gmail/inverseconduit/commands/CommandHandle.java index 5ce05b4..05db77c 100644 --- a/src/main/java/com/gmail/inverseconduit/commands/CommandHandle.java +++ b/src/main/java/com/gmail/inverseconduit/commands/CommandHandle.java @@ -1,55 +1,54 @@ package com.gmail.inverseconduit.commands; import java.util.function.Consumer; +import java.util.function.Function; import java.util.function.Predicate; import com.gmail.inverseconduit.datatype.ChatMessage; /** * Simple handle for a Command. Consists of a {@link Predicate} to match - * messages (aka. invocations) against, a helpText, - * an infoText and a {@link Consumer Consumer} for {@link ChatMessage - * ChatMessages} + * messages (aka. invocations) against, a helpText, an infoText and a + * {@link Consumer Consumer} for {@link ChatMessage ChatMessages} * * @author vogel612<vogel612@gmx.de> */ public class CommandHandle { - private final Predicate matchesSyntax; + private final String name; - private final String name; + private final String helpText; - private final String helpText; + private final String infoText; - private final String infoText; - - private final Consumer consumer; + private final Function consumer; /** * Command Builder for assembling commands. The command builder is not - * intended - * to be threadsafe or reusable. - * After building a command it should be disposed of, or undefined results - * may - * occur + * intended to be threadsafe or reusable. After building a command it should + * be disposed of, or undefined results may occur * * @author vogel612<vogel612@gmx.de> */ public static class Builder { - private Predicate matchesSyntax; + private String name; - private String name; + private String helpText = ""; - private String helpText = ""; + private String infoText = ""; - private String infoText = ""; + private Function consumer; - private Consumer consumer; + @Deprecated + @SuppressWarnings("unused") + public Builder(String name, Predicate matchesSyntax, Function consumer) { + this.name = name; + this.consumer = consumer; + } - public Builder(String name, Predicate matchesSyntax, Consumer consumer) { + public Builder(String name, Function consumer) { this.name = name; - this.matchesSyntax = matchesSyntax; this.consumer = consumer; } @@ -57,8 +56,7 @@ public Builder(String name, Predicate matchesSyntax, Consumer { - return s.trim().equals(config.getTrigger() + "unsummon"); - }, message -> { + return new CommandHandle.Builder("unsummon", message -> { SeChatDescriptor descriptor = SeChatDescriptor.buildSeChatDescriptorFrom(message); - chatInterface.sendMessage(descriptor, "*~bye, bye*"); chatInterface.leaveChat(descriptor); + return "*~bye, bye*"; }).build(); } public static CommandHandle summonCommand(ChatInterface chatInterface) { - return new CommandHandle.Builder("summon", s -> { - return s.trim().startsWith(config.getTrigger()) && s.trim().matches(".*summon (stack(overflow|exchange)|meta) [0-9]{1,6}"); - }, message -> { + return new CommandHandle.Builder("summon", message -> { Logger.getAnonymousLogger().info("Actually invoking summon command"); - SeChatDescriptor callingRoomDescriptor = SeChatDescriptor.buildSeChatDescriptorFrom(message); String[] args = message.getMessage().trim().split(" "); final SESite targetSite; switch (args[1].toLowerCase()) { @@ -42,16 +33,14 @@ public static CommandHandle summonCommand(ChatInterface chatInterface) { targetSite = SESite.META_STACK_EXCHANGE; break; default: - chatInterface.sendMessage(callingRoomDescriptor, "The given site was not one of stackoverflow, stackexchange or meta"); - return; + return "The given site was not one of stackoverflow, stackexchange or meta"; } try { final int targetRoom = Integer.parseInt(args[2]); - if ( !chatInterface.joinChat(new SeChatDescriptor.DescriptorBuilder(targetSite).setRoom(() -> targetRoom).build())) { - chatInterface.sendMessage(callingRoomDescriptor, "Could not join room."); - } + if ( !chatInterface.joinChat(new SeChatDescriptor.DescriptorBuilder(targetSite).setRoom(() -> targetRoom).build())) { return "Could not join room."; } + return "Successfully joined room"; } catch(NumberFormatException ex) { - chatInterface.sendMessage(callingRoomDescriptor, "Could not determine roomnumber."); + return "Could not determine roomnumber."; } }).build(); } diff --git a/src/main/java/com/gmail/inverseconduit/commands/sets/CoreBotCommands.java b/src/main/java/com/gmail/inverseconduit/commands/sets/CoreBotCommands.java new file mode 100644 index 0000000..b17a541 --- /dev/null +++ b/src/main/java/com/gmail/inverseconduit/commands/sets/CoreBotCommands.java @@ -0,0 +1,87 @@ +package com.gmail.inverseconduit.commands.sets; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +import com.gmail.inverseconduit.AppContext; +import com.gmail.inverseconduit.BotConfig; +import com.gmail.inverseconduit.chat.ChatInterface; +import com.gmail.inverseconduit.commands.CommandHandle; +import com.gmail.inverseconduit.scripts.ScriptRunner; + +public final class CoreBotCommands { + + private static final BotConfig BOT_CONFIG = AppContext.INSTANCE.get(BotConfig.class); + + final Set allCommands = new HashSet<>(); + + public CoreBotCommands(ChatInterface chatInterface) { + createSummonCommands(chatInterface); + createGroovyCommands(); + createHelpCommand(); + createAboutCommand(); + createTestCommand(); + } + + public Set allCommands() { + return Collections.unmodifiableSet(allCommands); + } + + private void createSummonCommands(ChatInterface chatInterface) { + allCommands.add(ChatCommands.unsummonCommand(chatInterface)); + allCommands.add(ChatCommands.summonCommand(chatInterface)); + } + + private void createGroovyCommands() { + ScriptRunner runner = new ScriptRunner(); + allCommands.add(ScriptRunnerCommands.evalCommand(runner)); + allCommands.add(ScriptRunnerCommands.loadCommand(runner)); + } + + private void createAboutCommand() { + CommandHandle about = + new CommandHandle.Builder( + "about", + message -> { + return String.format("@%s I am JavaBot, maintained by Uni, Vogel, and a few others. You can find me on http://github.com/Vincentyification/JavaBot", message.getUsername()); + }).build(); + allCommands.add(about); + } + + private void createHelpCommand() { + CommandHandle help = new CommandHandle.Builder("help", message -> { + String[] parts = message.getMessage().split(" "); + String commandName = parts[parts.length - 1]; + Optional helpText = allCommands.stream().filter(c -> c.getName().equals(commandName)).findFirst().map(c -> c.getHelpText()); + if (helpText.isPresent()) { return helpText.get(); } + return "help command: Get additional info about a command of your choice, syntax:" + BOT_CONFIG.getTrigger() + "help [commandName]"; + }).setHelpText("help command: Get additional info about a command of your choice, syntax:" + BOT_CONFIG.getTrigger() + "help [commandName]").build(); + allCommands.add(help); + } + + // FIXME: Javadoc accessor needs configuration + /* + * private void bindJavaDocCommand() { CommandHandle javaDoc = new + * CommandHandle.Builder("javadoc", message -> { Matcher matcher = + * javadocPattern.matcher(message .getMessage()); matcher.find(); return + * javaDocAccessor.javadoc(message, matcher.group(1) .trim()); }).build(); + * allCommands.add(javaDoc); } + */ + + /* + * private void bindShutdownCommand() { CommandHandle shutdown = new + * CommandHandle.Builder("shutdown", message -> { // FIXME: Require + * permissions for this chatInterface.broadcast("*~going down*"); // FIXME: + * needs to be solved differently executor.shutdownNow(); System.exit(0); + * return ""; }).build(); allCommands.add(shutdown); } + */ + + private void createTestCommand() { + CommandHandle test = new CommandHandle.Builder("test", message -> { + return "*~response*"; + }).build(); + allCommands.add(test); + } +} diff --git a/src/main/java/com/gmail/inverseconduit/scripts/ScriptRunnerCommands.java b/src/main/java/com/gmail/inverseconduit/commands/sets/ScriptRunnerCommands.java similarity index 59% rename from src/main/java/com/gmail/inverseconduit/scripts/ScriptRunnerCommands.java rename to src/main/java/com/gmail/inverseconduit/commands/sets/ScriptRunnerCommands.java index 80e963c..abac312 100644 --- a/src/main/java/com/gmail/inverseconduit/scripts/ScriptRunnerCommands.java +++ b/src/main/java/com/gmail/inverseconduit/commands/sets/ScriptRunnerCommands.java @@ -1,4 +1,4 @@ -package com.gmail.inverseconduit.scripts; +package com.gmail.inverseconduit.commands.sets; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -6,28 +6,30 @@ import com.gmail.inverseconduit.AppContext; import com.gmail.inverseconduit.BotConfig; import com.gmail.inverseconduit.commands.CommandHandle; +import com.gmail.inverseconduit.scripts.ScriptRunner; public final class ScriptRunnerCommands { private static final BotConfig config = AppContext.INSTANCE.get(BotConfig.class); - private static final Pattern evalPattern = Pattern.compile("^" + Pattern.quote(config.getTrigger()) + "eval:(.*)", Pattern.DOTALL); + private static final Pattern evalPattern = Pattern.compile("^" + Pattern.quote(config.getTrigger()) + "eval (.*)", Pattern.DOTALL); - private static final Pattern loadPattern = Pattern.compile("^" + Pattern.quote(config.getTrigger()) + "load:(.*)", Pattern.DOTALL); + private static final Pattern loadPattern = Pattern.compile("^" + Pattern.quote(config.getTrigger()) + "load (.*)", Pattern.DOTALL); public static CommandHandle evalCommand(ScriptRunner scriptRunner) { - return new CommandHandle.Builder("eval", evalPattern.asPredicate(), message -> { + return new CommandHandle.Builder("eval", message -> { Matcher matcher = evalPattern.matcher(message.getMessage()); matcher.find(); - scriptRunner.evaluateGroovy(message, matcher.group(1)); - }).setHelpText("Evaluates a given groovy script. Syntax: '{trigger}eval:{groovy}'").setInfoText("GroovyScript evalutation").build(); + return scriptRunner.evaluateGroovy(message, matcher.group(1).trim()); + }).setHelpText("Evaluates a given groovy script. Syntax: '" + config.getTrigger() + "eval {groovy}'").setInfoText("GroovyScript evaluation").build(); } public static CommandHandle loadCommand(ScriptRunner scriptRunner) { - return new CommandHandle.Builder("load", loadPattern.asPredicate(), message -> { + return new CommandHandle.Builder("load", message -> { Matcher matcher = loadPattern.matcher(message.getMessage()); matcher.find(); - scriptRunner.evaluateAndCache(message, matcher.group(1)); + scriptRunner.evaluateAndCache(matcher.group(1)); + return "Thanks, I'll remember that"; }).build(); } } diff --git a/src/main/java/com/gmail/inverseconduit/javadoc/JavaDocAccessor.java b/src/main/java/com/gmail/inverseconduit/javadoc/JavaDocAccessor.java index 7429fea..d1e0c62 100644 --- a/src/main/java/com/gmail/inverseconduit/javadoc/JavaDocAccessor.java +++ b/src/main/java/com/gmail/inverseconduit/javadoc/JavaDocAccessor.java @@ -4,13 +4,10 @@ import java.nio.file.Files; import java.nio.file.Path; -import com.gmail.inverseconduit.chat.ChatInterface; import com.gmail.inverseconduit.datatype.ChatMessage; -import com.gmail.inverseconduit.datatype.SeChatDescriptor; public class JavaDocAccessor { - private final ChatInterface chatInterface; private final JavadocDao dao; @@ -22,9 +19,7 @@ public class JavaDocAccessor { * @throws IOException * if there's a problem reading a Javadoc file */ - public JavaDocAccessor(ChatInterface chatInterface, Path dir) throws IOException { - this.chatInterface = chatInterface; - + public JavaDocAccessor(Path dir) throws IOException { dao = new JavadocDao(); Path java8Api = dir.resolve("java8.zip"); @@ -45,7 +40,7 @@ public JavaDocAccessor(ChatInterface chatInterface, Path dir) throws IOException } } - public void javadoc(ChatMessage chatMessage, String commandText) { + public String javadoc(ChatMessage chatMessage, String commandText) { String response; try { response = generateResponse(commandText); @@ -53,8 +48,7 @@ public void javadoc(ChatMessage chatMessage, String commandText) { throw new RuntimeException("Problem getting Javadoc info.", e); } - response = ":" + chatMessage.getMessageId() + " " + response; - chatInterface.sendMessage(SeChatDescriptor.buildSeChatDescriptorFrom(chatMessage), response); + return ":" + chatMessage.getMessageId() + " " + response; } private String generateResponse(String commandText) throws IOException { diff --git a/src/main/java/com/gmail/inverseconduit/scripts/ScriptRunner.java b/src/main/java/com/gmail/inverseconduit/scripts/ScriptRunner.java index 2daa390..840ffb1 100644 --- a/src/main/java/com/gmail/inverseconduit/scripts/ScriptRunner.java +++ b/src/main/java/com/gmail/inverseconduit/scripts/ScriptRunner.java @@ -7,12 +7,11 @@ import java.util.logging.Logger; +import org.codehaus.groovy.control.CompilationFailedException; import org.codehaus.groovy.control.CompilerConfiguration; import com.gmail.inverseconduit.ScriptBase; -import com.gmail.inverseconduit.chat.ChatInterface; import com.gmail.inverseconduit.datatype.ChatMessage; -import com.gmail.inverseconduit.datatype.SeChatDescriptor; /** * Class to run chat Code. The relevant commands submit code from the chat to @@ -34,32 +33,36 @@ public class ScriptRunner { private final GroovyClassLoader groovyLoader; - private final ChatInterface chatInterface; - - public ScriptRunner(ChatInterface chatInterface) { + public ScriptRunner() { // Groovy groovyConfig = new CompilerConfiguration(); groovyConfig.setScriptBaseClass(ScriptBase.class.getName()); - scriptBinding.setVariable("javaBot", chatInterface); + // scriptBinding.setVariable("javaBot", null); + //FIXME: we could use JavaBot for this groovyLoader = new GroovyClassLoader(this.getClass().getClassLoader(), groovyConfig); groovyShell = new GroovyShell(this.getClass().getClassLoader(), scriptBinding, groovyConfig); - this.chatInterface = chatInterface; } - public void evaluateGroovy(ChatMessage msg, String commandText) { - LOGGER.finest("Evaluating Groovy Script"); - - Object result = groovyShell.evaluate(createCodeSource(commandText)); - chatInterface.sendMessage(SeChatDescriptor.buildSeChatDescriptorFrom(msg), result == null - ? "[tag:groovy]: no result" - : "[tag:groovy]: " + result.toString()); + public String evaluateGroovy(ChatMessage msg, String commandText) { + LOGGER.info("Evaluating Groovy Script"); + Object result; + try { + result = groovyShell.evaluate(createCodeSource(commandText)); + } catch(CompilationFailedException ex) { + result = "compilation failed with error " + ex.getMessage(); + } catch(Exception ex) { + result = "undefined execution error: " + ex.getMessage(); + } + LOGGER.info("Result:" + result); + return result == null + ? String.format(":%d [tag:groovy]: no result", msg.getMessageId()) + : String.format(":%d [tag:groovy]: %s", msg.getMessageId(), result.toString()); } - public void evaluateAndCache(ChatMessage msg, String commandText) { + public void evaluateAndCache(String commandText) { LOGGER.finest("Compiling class to cache it"); groovyLoader.parseClass(createCodeSource(commandText), true); - chatInterface.sendMessage(SeChatDescriptor.buildSeChatDescriptorFrom(msg), "Thanks, I'll remember that"); } private GroovyCodeSource createCodeSource(String commandText) { diff --git a/src/test/java/com/gmail/inverseconduit/utils/PrintUtilTest.java b/src/test/java/com/gmail/inverseconduit/utils/PrintUtilTest.java index 7747435..5f04756 100644 --- a/src/test/java/com/gmail/inverseconduit/utils/PrintUtilTest.java +++ b/src/test/java/com/gmail/inverseconduit/utils/PrintUtilTest.java @@ -5,7 +5,6 @@ import java.util.Arrays; import java.util.List; -import org.junit.Ignore; import org.junit.Test; public class PrintUtilTest { @@ -95,8 +94,6 @@ public void sophisticatedLink() { final List parts = PrintUtils.splitUsefully(testString); - System.out.println(parts.toString()); - assertEquals("[a very sophisticated link](https://chat.meta.stackexchange.com/rooms/89 \"with a title text\")", parts.get(1)); assertEquals(5, parts.size()); }