From dc9ac7eddd94b26312cedd135796e3a74ed1aac8 Mon Sep 17 00:00:00 2001 From: Sandy Mossgrave Date: Sat, 5 Oct 2024 11:45:41 -0700 Subject: [PATCH] Added an example chat input interaction (/bean). Streamlined the messagehandler functions Changed the callback help/usage paradigm to use something that may actually work (I don't know whether it does) Rationalized the way that configuration files are located and loaded, guarded against TOCTOU --- .gitignore | 1 + pom.xml | 207 ++++++++++-------- .../com/tinyplantnews/priestess/Command.java | 4 +- .../priestess/Configuration.java | 126 ++++++++--- .../tinyplantnews/priestess/Priestess.java | 74 +++++-- 5 files changed, 264 insertions(+), 148 deletions(-) diff --git a/.gitignore b/.gitignore index 75a8b40..b009cc1 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ dependency-reduced-pom.xml buildNumber.properties .classpath .project +.bin/ .settings/ diff --git a/pom.xml b/pom.xml index 78166da..5d77a14 100644 --- a/pom.xml +++ b/pom.xml @@ -1,98 +1,113 @@ - - 4.0.0 - com.tinyplantnews.priestess - Priestess - Priestesss of the Bean Goose Cult - 0.2 - jar - - scm:git:https://tinyplantnews.com/git/Priestess.git - scm:git:https://twi@10.0.0.1:/srv/git/Priestess.git - - - - - org.codehaus.mojo - buildnumber-maven-plugin - 1.4 - - - buildnumber - validate - - create - - - - - {0,number} - - buildNumber - - false - false - unknownbuild - - - - org.apache.maven.plugins - maven-shade-plugin - 3.4.1 - - - package - - shade - - - - - com.tinyplantnews.priestess.Priestess - - - - - - - - ${project.artifactId}-${project.version}.${buildNumber} - - - src/main/java/com/tinyplantnews/priestess/version - true - - - - - - com.discord4j - discord4j-core - 3.2.3 - - - org.junit.jupiter - junit-jupiter-api - 5.6.0 - test - - - org.junit.jupiter - junit-jupiter-params - 5.6.0 - test - - - org.junit.jupiter - junit-jupiter-engine - 5.6.0 - test - - - - UTF-8 - 18 - 18 - com.tinyplantnews.priestess.Priestess - + + 4.0.0 + com.tinyplantnews.priestess + Priestess + Priestesss of the Bean Goose Cult + 0.2 + jar + + scm:git:https://tinyplantnews.com/git/Priestess.git + scm:git:https://twi@10.0.0.1:/srv/git/Priestess.git + + + + + org.codehaus.mojo + exec-maven-plugin + 3.4.1 + + java + + + com.tinyplantnews.priestess.Priestess + + + + org.codehaus.mojo + buildnumber-maven-plugin + 1.4 + + + buildnumber + validate + + create + + + + + {0,number} + + buildNumber + + false + false + unknownbuild + + + + org.apache.maven.plugins + maven-shade-plugin + 3.4.1 + + + package + + shade + + + + + + com.tinyplantnews.priestess.Priestess + + + + + + + + ${project.artifactId}-${project.version}.${buildNumber} + + + src/main/java/com/tinyplantnews/priestess/version + true + + + + + + com.discord4j + discord4j-core + 3.2.3 + + + org.junit.jupiter + junit-jupiter-api + 5.6.0 + test + + + org.junit.jupiter + junit-jupiter-params + 5.6.0 + test + + + org.junit.jupiter + junit-jupiter-engine + 5.6.0 + test + + + + UTF-8 + 18 + 18 + com.tinyplantnews.priestess.Priestess + diff --git a/src/main/java/com/tinyplantnews/priestess/Command.java b/src/main/java/com/tinyplantnews/priestess/Command.java index 4943c72..655b92a 100644 --- a/src/main/java/com/tinyplantnews/priestess/Command.java +++ b/src/main/java/com/tinyplantnews/priestess/Command.java @@ -37,7 +37,7 @@ public class Command { static void tryAll(String commandline, Message m, int permissionLevel) { for (String s : commands.keySet()) { if (commandline.startsWith(s)) { - log.log(Level.INFO, "Matched command " + s); + log.log(Level.FINE, "Matched command " + s); switch (commands.get(s).tryExecuteCommand(commandline, permissionLevel, m)) { case SUCCESS: return; @@ -123,7 +123,7 @@ public class Command { public String getHelp() { return callback.getHelp(this.name); } - + public static Command commandThatJustRepliesWith(String name, String reply) { Command c = new Command(name, 1); c.setCallback(new Callback() { diff --git a/src/main/java/com/tinyplantnews/priestess/Configuration.java b/src/main/java/com/tinyplantnews/priestess/Configuration.java index 6a9ce7a..e636c0f 100644 --- a/src/main/java/com/tinyplantnews/priestess/Configuration.java +++ b/src/main/java/com/tinyplantnews/priestess/Configuration.java @@ -51,27 +51,14 @@ public class Configuration { if (getCONFIG_FILE_LOCATION() != null) { return getCONFIG_FILE_LOCATION(); } - setCONFIG_FILE_LOCATION(setConfigurationFilePath(), false); + setCONFIG_FILE_LOCATION(findOrCreateConfigurationFile(), false); return getCONFIG_FILE_LOCATION(); } - private static String setConfigurationFilePath() { - String s = System.getenv(CONFIG_FILE_KEY); - if (s != null && !s.isBlank()) { - return s; - } else if (props.containsKey(CONFIG_FILE_KEY)) { - return props.getProperty(CONFIG_FILE_KEY); - } - s = prefs.get(CONFIG_FILE_KEY, ""); - if (s != null && !s.isBlank()) { - return s; - } - if (Files.exists(Paths.get(DEFAULT_CONFIG_FILE_NAME), LinkOption.NOFOLLOW_LINKS)) { - return DEFAULT_CONFIG_FILE_NAME; - } - // JDialog jd = new JDialog(); - // jd.add(new JLabel("Choose where to put Priestess Working Directory")); - // jd.setVisible(true); + private static String findOrCreateConfigurationFile() { + /* + * Try the environment variable + */ JOptionPane.showConfirmDialog(null, "Select where cult files will be stored."); JFileChooser jfc = new JFileChooser("Config File for Discord Cult Priestess"); jfc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); @@ -82,6 +69,12 @@ public class Configuration { if (rv == JFileChooser.APPROVE_OPTION) { File f = jfc.getSelectedFile(); if (f.isDirectory() && f.canRead() && f.canWrite()) { + prefs.put(CONFIG_FILE_KEY, f.getAbsolutePath()); + try { + prefs.flush(); + } catch (BackingStoreException e) { + log.log(Level.WARNING, "Failed to flush preferences to backing store"); + } return f.getAbsolutePath(); } } else if (rv == JFileChooser.CANCEL_OPTION) { @@ -92,18 +85,66 @@ public class Configuration { return ""; } + public static final String FILE_MARKER = "(file)"; + + public static String resolveIndirectConfigurationValue(String v) { + log.log(Level.INFO, "Index of <" + FILE_MARKER + "> in <" + v + "> is " + v.indexOf(FILE_MARKER)); + if (v.indexOf(FILE_MARKER) == 0) { + String relpath = v.substring(FILE_MARKER.length()).trim(); + + File configFile = Paths.get(CONFIG_FILE_LOCATION).toFile(); + String configDir; + if (configFile.isDirectory()) { + configDir = configFile.getPath(); + } else { + configDir = configFile.getParent(); + } + + Path indirectValuePath = Paths.get(configDir, relpath); + try (BufferedReader br = new BufferedReader(new FileReader(indirectValuePath.toFile()));) { + final StringBuilder sb = new StringBuilder(); + br.lines().forEach((s) -> { + sb.append(s); + }); + v = sb.toString(); + } catch (FileNotFoundException ex) { + + Logger.getLogger(Configuration.class.getName()).log(Level.SEVERE, + "Error loading indirect configuration value from (" + indirectValuePath.toString() + ") {0}", + ex); + } catch (IOException e) { + Logger.getLogger(Configuration.class.getName()).log(Level.SEVERE, + "Error loading indirect configuration value from (" + indirectValuePath.toString() + ") {0}", + e); + } + } + return v; + } + private static final String delimiter = ": "; - public static void getConfigurationFromFile() { - if (CONFIG_FILE_LOCATION == null) { - setConfigurationFilePath(); + private static File resolveDirectoryToConfigFile(File f) { + + if (f.isDirectory()) { + f = Paths.get(f.getPath(), Configuration.DEFAULT_CONFIG_FILE_NAME).toFile(); } - try { - if (Files.isDirectory(Paths.get(CONFIG_FILE_LOCATION), LinkOption.NOFOLLOW_LINKS)) { - CONFIG_FILE_LOCATION = CONFIG_FILE_LOCATION + System.getProperty("file.separator") - + DEFAULT_CONFIG_FILE_NAME; - } - BufferedReader br = new BufferedReader(new FileReader(Paths.get(CONFIG_FILE_LOCATION).toFile())); + return f; + } + + /** + * + */ + public static boolean readConfigurationFromFile(String path) throws IOException { + if (path == null || path.isBlank()) { + return false; + } + File f = Paths.get(path).toFile(); + if (CONFIG_FILE_LOCATION == null) { + findOrCreateConfigurationFile(); + } + f = resolveDirectoryToConfigFile(f); + try (BufferedReader br = new BufferedReader(new FileReader(f))) { + br.lines().forEach((s) -> { String key = s.substring(0, s.indexOf(delimiter)); if (s.length() == s.indexOf(delimiter) + delimiter.length()) { @@ -111,16 +152,35 @@ public class Configuration { configuration.put(key, ""); } else { String value = s.substring(s.indexOf(delimiter) + delimiter.length()); + value = resolveIndirectConfigurationValue(value); configuration.put(key, value); } }); - } catch (FileNotFoundException ex) { - - Logger.getLogger(Configuration.class.getName()).log(Level.SEVERE, - "Error loading configuration file from (" + CONFIG_FILE_LOCATION + ") {0}", ex); + CONFIG_FILE_LOCATION = f.toString(); + return true; + } catch (FileNotFoundException fnfe) { + return false; } } + public static void findAndReadConfigurationFile() { + try { + if (readConfigurationFromFile(System.getenv(CONFIG_FILE_KEY))) { + return; + } else if (readConfigurationFromFile(props.getProperty(CONFIG_FILE_KEY, ""))) { + return; + } else if (readConfigurationFromFile(prefs.get(CONFIG_FILE_KEY, ""))) { + return; + } else if (readConfigurationFromFile(findOrCreateConfigurationFile())) { + } else { + log.log(Level.WARNING, "Could not resolve a configuration file path, using defaults."); + } + } catch (IOException ioe) { + log.log(Level.SEVERE, "Could not read configuration file {0}", ioe.toString()); + } + + } + /** * @return the CONFIG_FILE_LOCATION */ @@ -180,7 +240,9 @@ public class Configuration { "Making backup configuration file failed. Exiting without committing configuration to file"); return; } - try (BufferedWriter br = new BufferedWriter(new FileWriter(Paths.get(CONFIG_FILE_LOCATION).toFile(), false))) { + File f = Paths.get(CONFIG_FILE_LOCATION).toFile(); + f = resolveDirectoryToConfigFile(f); + try (BufferedWriter br = new BufferedWriter(new FileWriter(f, false))) { configuration.forEach((key, value) -> { try { br.append(key + delimiter + value + "\n"); diff --git a/src/main/java/com/tinyplantnews/priestess/Priestess.java b/src/main/java/com/tinyplantnews/priestess/Priestess.java index 3954388..83ca79c 100644 --- a/src/main/java/com/tinyplantnews/priestess/Priestess.java +++ b/src/main/java/com/tinyplantnews/priestess/Priestess.java @@ -22,6 +22,8 @@ import discord4j.core.object.presence.ClientPresence; import discord4j.core.object.presence.Status; import discord4j.core.object.reaction.ReactionEmoji; import discord4j.core.spec.MessageCreateSpec; +import discord4j.discordjson.json.ApplicationCommandRequest; + import java.io.BufferedReader; import java.io.File; import java.io.IOException; @@ -248,6 +250,14 @@ public class Priestess { return Mono.empty(); }; + Function> messagehandler = (MessageCreateEvent t) -> { + if (t.getGuild().blockOptional().isEmpty()) { + return directmessagehandler.apply(t); + } else { + return guildedmessagehandler.apply(t); + } + }; + private static final String alphabet = "abcdefghijklmnopqrstuvwxyz"; private String randomLetter() { @@ -266,6 +276,8 @@ public class Priestess { if (botToken.isBlank()) { log.log(Level.SEVERE, "bot token is null. aborting."); return; + } else { + log.log(Level.FINE, "bot token set."); } SysInListener sil = new SysInListener(); Thread t = new Thread(sil); @@ -327,12 +339,23 @@ public class Priestess { dc.on(ChatInputInteractionEvent.class).flatMap(event -> { return event.reply().withContent("agreed"); }).subscribe(); + long applicationId = dc.getRestClient().getApplicationId().block(); + + ApplicationCommandRequest acr = ApplicationCommandRequest.builder().name("bean").description("beans") + .build(); + + dc.getGuilds().flatMap((g) -> { + log.log(Level.INFO, "Registering command with server {0}", g.getName()); + dc.getRestClient().getApplicationService() + .createGuildApplicationCommand(applicationId, g.getId().asLong(), acr).block(); + return Mono.empty(); + }).subscribe(); + + //dc.on(MessageCreateEvent.class).filter(event -> !event.getMessage().getAuthor().get().isBot()) + //.filter(g -> !g.getGuild().blockOptional().isEmpty()).flatMap(guildedmessagehandler).subscribe(); dc.on(MessageCreateEvent.class).filter(event -> !event.getMessage().getAuthor().get().isBot()) - .filter(g -> !g.getGuild().blockOptional().isEmpty()).flatMap(guildedmessagehandler).subscribe(); - - dc.on(MessageCreateEvent.class).filter(event -> !event.getMessage().getAuthor().get().isBot()) - .filter(g -> g.getGuild().blockOptional().isEmpty()).flatMap(directmessagehandler).blockLast(); + /*.filter(g -> g.getGuild().blockOptional().isEmpty())*/.flatMap(messagehandler).blockLast(); } } @@ -340,7 +363,7 @@ public class Priestess { private void setup() { Configuration.findConfigurationFilePath(); - Configuration.getConfigurationFromFile(); + Configuration.findAndReadConfigurationFile(); botToken = Configuration.getConfigurationParameter(TOKEN_KEY); @@ -380,7 +403,9 @@ public class Priestess { private void shutdown() { replcontinue = false; - dc.logout().subscribe(); + if (dc != null) { + dc.logout().subscribe(); + } System.out.println("Logged out"); try { user_reader.close(); @@ -392,10 +417,10 @@ public class Priestess { } int invocationsTotal = invocationsBeforeThisSession + invocationsThisSession; - System.out.println("Bean Goose has been invoked " + invocationsTotal + " times so far."); + log.log(Level.INFO, "Bean Goose has been invoked " + invocationsTotal + " times so far."); Configuration.setConfigurationParameter(INVOCATIONS_KEY, Integer.toString(invocationsTotal)); Configuration.commitConfigurationToFile(); - System.out.println("Configuration committed to file."); + log.log(Level.INFO, "Configuration committed to file."); } private void invokeDeity(Guild g, Message m) { @@ -420,7 +445,7 @@ public class Priestess { private int resolveAuthorPermissionLevel(MessageCreateEvent t, Message m) { int level = 0; - Optional gio = t.getGuildId(); + Optional gio = t.getGuildId(); if (gio.isPresent()) { Member a = m.getAuthorAsMember().block(); for (Snowflake snowflake : (Snowflake[]) a.getRoleIds().toArray(new Snowflake[3])) { @@ -454,7 +479,7 @@ public class Priestess { if (ao.isPresent()) { for (String s1 : s.split(" ")) { if (s1.equals(ao.get().getId().asString())) { - System.out.println("Literally catluck detected."); + log.log(Level.FINE, "Literally catluck detected."); return true; } } @@ -614,17 +639,31 @@ public class Priestess { String[] usages = { "Undefined command usage" }; String help = "${usage}"; + public Callback() { + super(); + } + + public Callback(String[] usages1, String help1) { + super(); + if (usages1 != null) { + usages = usages1; + } + if (help1 != null) { + help = help1; + } + } + @Override public Publisher apply(CommandArgs t) { throw new UnsupportedOperationException("Not supported yet."); } public String getHelp(String commandName) { - String s = getUsage(commandName); - return help/* .replaceAll("\\$\\{usage\\}", s) */.replaceAll("\\$\\{commandname\\}", commandName); + String s = getUsage(commandName, usages); + return help.replaceAll("\\$\\{usage\\}", s).replaceAll("\\$\\{commandname\\}", commandName); } - public String getUsage(String commandName) { + public String getUsage(String commandName, String[] usages) { StringBuilder u = new StringBuilder(); for (int i = 0; i < usages.length; i++) { String s = usages[i]; @@ -639,10 +678,8 @@ public class Priestess { } - public final Callback registerEmojiCommand = new Callback() { - - String usage = "{commandname} EMOJI"; - String help = "register emoji for use in basic invocations." + "" + "" + "" + ""; + public final Callback registerEmojiCommand = new Callback(new String[] { "${commandname} EMOJI" }, + "register emoji for use in basic invocations.") { @Override public Publisher apply(CommandArgs o) { @@ -675,7 +712,8 @@ public class Priestess { } }; - public final Callback commitConfigurationCommand = new Callback() { + public final Callback commitConfigurationCommand = new Callback(new String[] { "${commandname}" }, + "commit configuration to the configuration file.") { @Override public Publisher apply(CommandArgs o) { System.out.println("Committing configuration to file.");