From 6fc4d6f60e21f4502ae6a01e5140509cd4cbebe5 Mon Sep 17 00:00:00 2001 From: Skylar Date: Mon, 6 Jul 2026 22:34:29 -0400 Subject: [PATCH 01/11] Stop tracking Intellij files --- .gitignore | 2 ++ .idea/vcs.xml | 7 ------- 2 files changed, 2 insertions(+), 7 deletions(-) delete mode 100644 .idea/vcs.xml diff --git a/.gitignore b/.gitignore index e165b2c..2556638 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,5 @@ gradle-app.setting .project # JDT-specific (Eclipse Java Development Tools) .classpath + +.idea/ diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 8306744..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file From 404f2df5adfe06d743a2df0fcad361f925c5837e Mon Sep 17 00:00:00 2001 From: Skylar Date: Mon, 6 Jul 2026 22:34:47 -0400 Subject: [PATCH 02/11] Update Gradle --- gradle/wrapper/gradle-wrapper.jar | Bin 56172 -> 61624 bytes gradle/wrapper/gradle-wrapper.properties | 3 +- gradlew | 269 ++++++++++++++--------- gradlew.bat | 15 +- 4 files changed, 175 insertions(+), 112 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 28861d273a5d270fd8f65dd74570c17c9c507736..afba109285af78dbd2a1d187e33ac4f87c76e392 100644 GIT binary patch delta 53505 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ΞqF<%)ziFk}}sDd&vjl$rJwVa6jdiocIMT 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 delta 48341 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

o4Bq6MuTcS;QD|W*%7B zAX6rDm+__;ltNNBXJ1>wMg~RE=o-qQ+nAdcGpINgAf1yz2r=H_g*A$@iJ|L*ybvaI zL~W?J4&w$*LlxZ?36YR#7jVpES=VTk!J$CDpogq4$?aln`Lw!$#KbrHO*2skzhzCa za%s>6TN^`?s$^Osw8ua1)L8G_-TTC)`)gtC7B-({iX&2ItJ2{0Ni=EB4%b>PBN-Da z7j?N9z)!U-6l+b#8)Xbpkh`aGk8w8|pnkKB5a=W**;Ugyyd)qG*V|&Q!?5Wt(g2(nweDwv=@yoWN(%0T_+uYvlPSftDR&l*c2U3JKhMd) z_h`{MTrzkpk_Wht>G51(&_qk+SSwS(p*8Y+TdiAn7(Y3sd2LbEzD02*uIZ&259`pX zF~fl^0M1hM({*?_Rn=zP5lu+ZU2j9(IXY!HGTYmmyNWoe7i(vM8tYxho>PZQeL;O5 zJxWCoHA8S`#0J7&`a<1?F&P9Xq@eOXZXKcd=U)N47A&T~$w2|1VPQdzeZpYKKf5(S z|AB$@2ry#M>;K&R7>sQ!fz1d+Haq_cQwXB{9}fimr{I8&j9e{kOc>W&0}vb$416~(2dH=Fw}BPvay4KHtt|xAwxTi{%%)E7W@?k7zPj=5fd1* z3a|5b#q#26!VJ<15=?&q{^_(*=1rmy2&E2s{i@`Dg$8u^4^3WJL0UpwRgF zLDm(2lyuPXFaO3r5ab|F{Xa_kUx>dvApZa&qyGT|13Li4(8j0w)uX=#iIiKOjuT{* z!~_ot5B~y601iZTWTOA~iD?iO_vV9uv!EB*3qlq&h=2)P8 z;}yW6flSA^e{v`{`$4I5LAHz_$pdZo{!voN{^DQ*PuHMcfrLut_*bVF8G*vZSip&_ z*Z=7--!c_H0HjKDp@4yr{!tYeST_@hL? zVl=ifbawuG(2zQCf#oN#pgx?){_fl83F@EKy+}0oqPld2-}n!wNI$v_SfYD%?Z@Ve|`nFZ27_=88lPCr5mLqVJngFX##%r30Ogn4`kkk`jhh_X2A=*cnjNcuC+Fx zH{E@40xt{*U*NlPe-F^IDC)oa^umVh1qZj_H-}k*=--?dwl*&~n#CXvDeM2Yz`k6x z_>=ec^Y0~$?|TTp?!ygZTp-H+uPM{N3-meLug{eCf8Cpfy+C|({(tVvmfs(G_5)8X z__+W5Ed94;^S}1w24CnobnAGUpojL~n$4e@$uG_3e \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -106,80 +140,105 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=`expr $i + 1` + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 107acd3..93e3f59 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal From f3c41b7c7072baaae7fd77614a46e5a6d9a1ac39 Mon Sep 17 00:00:00 2001 From: Skylar Date: Wed, 8 Jul 2026 00:25:45 -0400 Subject: [PATCH 03/11] Setup Doom Handling --- src/main/java/configs/RemoteDataSettings.java | 23 ++ src/main/java/configs/Settings.java | 14 +- src/main/java/discord/TwoDee.java | 3 + src/main/java/doom/ConfigDoom.java | 44 ++++ src/main/java/doom/Doom.java | 13 + src/main/java/doom/DoomChange.java | 7 + src/main/java/doom/DoomHandler.java | 241 ++++++++++++++---- src/main/java/doom/RemoteDoom.java | 32 +++ src/main/java/doom/RemoteDoomRequest.java | 7 + .../java/listeners/DoomPoolAutocomplete.java | 6 +- .../RollComponentInteractionListener.java | 10 +- src/main/java/logic/DoomLogic.java | 49 ++-- src/main/java/util/CachedValue.java | 42 +++ 13 files changed, 403 insertions(+), 88 deletions(-) create mode 100644 src/main/java/configs/RemoteDataSettings.java create mode 100644 src/main/java/doom/ConfigDoom.java create mode 100644 src/main/java/doom/Doom.java create mode 100644 src/main/java/doom/DoomChange.java create mode 100644 src/main/java/doom/RemoteDoom.java create mode 100644 src/main/java/doom/RemoteDoomRequest.java create mode 100644 src/main/java/util/CachedValue.java diff --git a/src/main/java/configs/RemoteDataSettings.java b/src/main/java/configs/RemoteDataSettings.java new file mode 100644 index 0000000..196d9f7 --- /dev/null +++ b/src/main/java/configs/RemoteDataSettings.java @@ -0,0 +1,23 @@ +package configs; + +public class RemoteDataSettings { + private final String url; + private final String token; + + public RemoteDataSettings() { + this("", ""); + } + + public RemoteDataSettings(String url, String token) { + this.url = url; + this.token = token; + } + + public String getUrl() { + return url; + } + + public String getToken() { + return token; + } +} diff --git a/src/main/java/configs/Settings.java b/src/main/java/configs/Settings.java index d1705f6..bb9c3f9 100644 --- a/src/main/java/configs/Settings.java +++ b/src/main/java/configs/Settings.java @@ -48,6 +48,10 @@ public static Quotes getQuotes() { return instance.quotes; } + public static RemoteDataSettings getRemoteDataSettings() { + return getSettingsInstance().getRemoteDataSettings(); + } + private static SettingsInstance getSettingsInstance() { return instance.settingsInstance; } @@ -61,7 +65,7 @@ public static void serializePersonalSettings() { new GsonBuilder().setPrettyPrinting().create().toJson(Settings.getSettingsInstance(), writer); writer.close(); } catch (IOException e) { - e.printStackTrace(); + LOGGER.error("Unable to serialize settings file!", e); } } @@ -71,7 +75,7 @@ protected static void serializeQuotes() { new GsonBuilder().setPrettyPrinting().create().toJson(Settings.getQuotes(), writer); writer.close(); } catch (IOException e) { - e.printStackTrace(); + LOGGER.error("Unable to serialize quotes file!", e); } } @@ -93,12 +97,14 @@ public static void removeQuote(String quote) { private static class SettingsInstance { private final DoomSettings doom; private final DiscordSettings discordSettings; + private final RemoteDataSettings remoteDataSettings; private final List players; public SettingsInstance() { doom = new DoomSettings(); discordSettings = new DiscordSettings(); players = new ArrayList<>(); + this.remoteDataSettings = new RemoteDataSettings(); } public DoomSettings getDoom() { @@ -112,5 +118,9 @@ public DiscordSettings getDiscordSettings() { public List getPlayers() { return players; } + + public RemoteDataSettings getRemoteDataSettings() { + return remoteDataSettings; + } } } diff --git a/src/main/java/discord/TwoDee.java b/src/main/java/discord/TwoDee.java index be1705a..d7d4e80 100644 --- a/src/main/java/discord/TwoDee.java +++ b/src/main/java/discord/TwoDee.java @@ -1,6 +1,7 @@ package discord; import configs.Settings; +import doom.DoomHandler; import language.LanguageLogic; import listeners.DoomPoolAutocomplete; import listeners.LanguageAutocompleteListener; @@ -22,6 +23,8 @@ public class TwoDee { private static final Logger LOGGER = LogManager.getLogger(TwoDee.class); public static void main(String[] args) { + + DoomHandler.setupRemoteDoom(); String token = Settings.getDiscordSettings().getToken(); LanguageLogic languageLogic = LanguageLogic.of(new File("resources/languages.json")) diff --git a/src/main/java/doom/ConfigDoom.java b/src/main/java/doom/ConfigDoom.java new file mode 100644 index 0000000..08ef295 --- /dev/null +++ b/src/main/java/doom/ConfigDoom.java @@ -0,0 +1,44 @@ +package doom; + +import configs.Settings; + +import java.util.concurrent.CompletableFuture; + +public record ConfigDoom( + String name, + int doom +) implements Doom { + + @Override + public String getName() { + return this.name(); + } + + @Override + public CompletableFuture getDoom() { + return CompletableFuture.completedFuture(this.doom()); + } + + @Override + public CompletableFuture changeDoom(int amount) { + int old = this.doom(); + int current = this.doom() + amount; + DoomHandler.getInstance().doomSettings.getDoomPools() + .put(this.name(), current); + return CompletableFuture.completedFuture(new DoomChange(old, current)); + } + + @Override + public CompletableFuture delete() { + Integer removed = DoomHandler.getInstance() + .doomSettings + .getDoomPools() + .remove(this.getName()); + if (removed == null) { + return CompletableFuture.completedFuture(false); + } else { + Settings.serializePersonalSettings(); + return CompletableFuture.completedFuture(true); + } + } +} diff --git a/src/main/java/doom/Doom.java b/src/main/java/doom/Doom.java new file mode 100644 index 0000000..b8098a4 --- /dev/null +++ b/src/main/java/doom/Doom.java @@ -0,0 +1,13 @@ +package doom; + +import java.util.concurrent.CompletableFuture; + +public interface Doom { + String getName(); + + CompletableFuture getDoom(); + + CompletableFuture changeDoom(int amount); + + CompletableFuture delete(); +} diff --git a/src/main/java/doom/DoomChange.java b/src/main/java/doom/DoomChange.java new file mode 100644 index 0000000..85a2055 --- /dev/null +++ b/src/main/java/doom/DoomChange.java @@ -0,0 +1,7 @@ +package doom; + +public record DoomChange( + int old, + int current +) { +} diff --git a/src/main/java/doom/DoomHandler.java b/src/main/java/doom/DoomHandler.java index 5fb5a7c..60a9b7f 100644 --- a/src/main/java/doom/DoomHandler.java +++ b/src/main/java/doom/DoomHandler.java @@ -1,6 +1,8 @@ package doom; import com.google.common.collect.Lists; +import com.google.common.reflect.TypeToken; +import com.google.gson.Gson; import configs.DoomSettings; import configs.Settings; import org.apache.logging.log4j.LogManager; @@ -10,25 +12,94 @@ import roles.Player; import roles.PlayerHandler; import roles.Storytellers; +import util.CachedValue; import util.DamerauLevenshtein; import javax.annotation.Nullable; import java.awt.*; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; import java.text.MessageFormat; +import java.time.Duration; +import java.util.*; import java.util.List; -import java.util.Map; -import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; public final class DoomHandler { private static final Logger LOGGER = LogManager.getLogger(DoomHandler.class); public static final String DOOM = "Doom!"; + private static final Gson GSON = new Gson(); private static final DoomHandler instance = new DoomHandler(); - private final DoomSettings doomSettings; + public final DoomSettings doomSettings; + + private final CachedValue> remoteDooms; private DoomHandler() { doomSettings = Settings.getDoom(); + remoteDooms = new CachedValue<>(Duration.ofHours(4).toMillis(), this::getRemoteDoom); + } + + public static void setupRemoteDoom() { + if (!getDoomPools().isEmpty()) { + instance.doomSettings + .getDoomPools() + .entrySet() + .removeIf(doomPool -> createDoomPool(doomPool.getKey(), doomPool.getValue())); + Settings.serializePersonalSettings(); + } + + instance.remoteDooms.initialize(); + } + + private static boolean createDoomPool(String name, int count) { + HttpClient httpClient = HttpClient.newHttpClient(); + + String body = GSON.toJson(new RemoteDoomRequest(name, count)); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(Settings.getRemoteDataSettings().getUrl() + "/doompool")) + .header("Authorization", "Bearer " + Settings.getRemoteDataSettings().getToken()) + .POST(HttpRequest.BodyPublishers.ofString(body)) + .build(); + + try { + HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + LOGGER.info("Wrote Remote Doom: {}", response.body()); + if (response.statusCode() == 200) { + return true; + } else { + LOGGER.error("Received {}, {}", response.statusCode(), response.body()); + return false; + } + } catch (Exception e) { + LOGGER.error("Failed to create doom pool", e); + return false; + } + + } + + private List getRemoteDoom() { + try { + HttpClient httpClient = HttpClient.newHttpClient(); + + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(Settings.getRemoteDataSettings().getUrl() + "/doompool")) + .header("Authorization", "Bearer " + Settings.getRemoteDataSettings().getToken()) + .GET() + .build(); + + HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + + return GSON.fromJson(response.body(), new TypeToken>() { + }.getType()); + } catch (Exception e) { + LOGGER.error("Failed to read Remote Doom", e); + return Collections.emptyList(); + } } /** @@ -40,15 +111,24 @@ private DoomHandler() { * @return An embed that shows the change in doom points for the specified doom pool */ public static EmbedBuilder addDoom(String pool, int doomVal) { - final int oldDoom = getDoom(pool); - if (oldDoom == 0) { - return new EmbedBuilder() - .setTitle("Error") - .setDescription("No Doom Pool with Name ''**" + pool + "**'' exists."); - } - final int newDoom = getDoomPools().compute(pool, (s, integer) -> integer != null ? integer + doomVal : doomVal); - Settings.serializePersonalSettings(); - return generateDoomEmbed(pool, oldDoom, newDoom); + return getDoomPool(pool) + .map(doomPool -> { + try { + DoomChange doomChange = doomPool.changeDoom(doomVal).get(); + return generateDoomEmbed(pool, doomChange.old(), doomChange.current()); + } catch (InterruptedException | ExecutionException e) { + LOGGER.error("Failed to update doom", e); + return new EmbedBuilder() + .setTitle("Error") + .setDescription("Failed to update doom: " + e.getMessage()); + } + }) + .orElseGet(() -> new EmbedBuilder() + .setTitle("Error") + .setDescription("No Doom Pool with Name ''**" + pool + "**'' exists.") + ); + + } /** @@ -72,8 +152,7 @@ public static int addDoom(int doomVal) { public static int addDoomOnOpportunity(User user, int count) { if (Storytellers.isUserStoryteller(user)) { return getDoom(); - } - else { + } else { final String doomPool = PlayerHandler.getPlayerFromUser(user).map(Player::getDoomPool).orElse(getActivePool()); addDoom(doomPool, count); return getDoom(doomPool); @@ -87,7 +166,42 @@ public static int addDoomOnOpportunity(User user, int count) { * @return The number of doom points in that pool */ public static int getDoom(String pool) { - return getDoomPools().getOrDefault(pool, 0); + return getDoomPool(pool) + .flatMap(doomPool -> { + try { + return Optional.of(doomPool.getDoom().get()); + } catch (InterruptedException | ExecutionException e) { + LOGGER.error("Failed to call getDoom(String pool)", e); + return Optional.empty(); + } + }) + .orElse(0); + } + + public static Optional getDoomPool(String pool) { + return getDoomPools() + .stream() + .filter(doomPool -> doomPool.getName().equalsIgnoreCase(pool)) + .findFirst(); + } + + public static List getRemoteDoomPools() { + return instance.remoteDooms.get(); + } + + public static List getOldDoomPools() { + return instance.doomSettings.getDoomPools() + .entrySet() + .stream() + .map(entry -> new ConfigDoom(entry.getKey(), entry.getValue())) + .toList(); + } + + public static List getDoomPools() { + ArrayList dooms = new ArrayList<>(); + dooms.addAll(getRemoteDoomPools()); + dooms.addAll(getOldDoomPools()); + return dooms; } /** @@ -105,23 +219,34 @@ public static int getDoom() { * @param newDoom The new value of the doom pool * @return The embed with a new doom value */ - public static EmbedBuilder setDoom(String pool, int newDoom) { - int oldDoom = getDoom(pool); - if (oldDoom == 0) { + public static EmbedBuilder setDoom(String poolName, int newDoom) { + try { + return setDoomAsync(poolName, newDoom).get(); + } catch (InterruptedException | ExecutionException e) { + LOGGER.error("Failed to set doom", e); return new EmbedBuilder() .setTitle("Error") - .setDescription("No Doom Pool with Name ''**" + pool + "**'' exists."); + .setDescription("Failed to set doom for ''**" + poolName + "**'':" + e.getMessage()); } - getDoomPools().put(pool, newDoom); - Settings.serializePersonalSettings(); - return generateDoomEmbed(pool, oldDoom, newDoom); + } + + public static CompletableFuture setDoomAsync(String poolName, int newDoom) { + return getDoomPool(poolName) + .map(pool -> pool.getDoom() + .thenCompose(doom -> pool.changeDoom(newDoom - doom)) + .thenApply(doomChange -> generateDoomEmbed(pool.getName(), doomChange.old(), doomChange.current())) + ) + .orElseGet(() -> CompletableFuture.completedFuture(new EmbedBuilder() + .setTitle("Error") + .setDescription("No Doom Pool with Name ''**" + poolName + "**'' exists.") + )); } /** * Generates an embed that shows the amount of doom points in the specified doom pool. * * @param pool The name of the doom pool to check - * @return The an embed containing the value of the doom pool + * @return The embed containing the value of the doom pool */ public static EmbedBuilder generateDoomEmbed(String pool) { final int doom = getDoom(pool); @@ -154,30 +279,37 @@ public static EmbedBuilder generateDoomEmbed(String pool, int oldDoom, int newDo * @return An embed containing information about the deleted doom pool and the number of doom points it had */ public static EmbedBuilder deletePool(String pool) { - final Integer removedDoom = getDoomPools().remove(pool); - final String description = Optional.ofNullable(removedDoom).map(doom -> MessageFormat.format("I''ve removed the ''**{0}**'' doom pool, which contained {1} doom points.", pool, doom)).orElseGet(() -> MessageFormat.format("I was unable to find the ''**{0}**'' doom pool", pool)); - Settings.serializePersonalSettings(); + String description = getDoomPool(pool) + .map(doom -> MessageFormat.format("I''ve removed the ''**{0}**'' doom pool, which contained {1} doom points.", pool, doom)) + .orElseGet(() -> MessageFormat.format("I was unable to find the ''**{0}**'' doom pool", pool)); + return new EmbedBuilder() .setTitle(DOOM) .setDescription(description); } /** - * Generates an embed that provides a summary of all of the doom pools. + * Generates an embed that provides a summary of the doom pools. * * @return Am embed with information on the names and values of all available doom pools and the name and value of * the active doom pool */ public static EmbedBuilder generateDoomEmbed() { - final EmbedBuilder embedBuilder = new EmbedBuilder().setTitle(DOOM).setDescription(MessageFormat.format("Here are the values of all doom pools.\nThe current active doom pool is ''**{0}**'' with {1} doom points", getActivePool(), getDoom(getActivePool()))).setColor(new Color((int) (getDoom() % 101 * (2.55)))); - getDoomPools().forEach((key, value) -> embedBuilder.addField(key, String.valueOf(value))); + final EmbedBuilder embedBuilder = new EmbedBuilder() + .setTitle(DOOM) + .setDescription(MessageFormat.format( + "Here are the values of all doom pools.\nThe current active doom pool is ''**{0}**'' with {1} doom points", + getActivePool(), + getDoom(getActivePool()) + )) + .setColor(new Color((int) (getDoom() % 101 * (2.55)))); + getDoomPools().forEach(doomPool -> embedBuilder.addField( + doomPool.getName(), + String.valueOf(doomPool.getDoom()) + )); return embedBuilder; } - public static Map getDoomPools() { - return instance.doomSettings.getDoomPools(); - } - /** * Getter for the value of the active pool * @@ -208,49 +340,52 @@ public static String getDoomPoolOrDefault(User user) { } public static EmbedBuilder createPool(String poolName, int count) { - getDoomPools().put(poolName, count); - Settings.serializePersonalSettings(); - return new EmbedBuilder() - .setTitle(DOOM) - .setDescription(MessageFormat.format("I''ve created the doom pool ''**{0}**'', which contains {1} doom points.", poolName, count)); + if (createDoomPool(poolName, count)) { + return new EmbedBuilder() + .setTitle(DOOM) + .setDescription(MessageFormat.format("I''ve created the doom pool ''**{0}**'', which contains {1} doom points.", poolName, count)); + } else { + return new EmbedBuilder() + .setTitle(DOOM) + .setDescription(MessageFormat.format("Failed to create doom pool ''**{0}**''.", poolName)); + } } @Nullable public static String findPool(String poolName) { - if (getDoomPools().containsKey(poolName)) { + if (getDoomPools().stream().anyMatch(pool -> pool.getName().equalsIgnoreCase(poolName))) { return poolName; - } - else { + } else { List potentialPoolNames = Lists.newArrayList(); int currentDistance = Integer.MAX_VALUE; - for (String existingPool : getDoomPools().keySet()) { - int distance = DamerauLevenshtein.calculateDistance(poolName, existingPool); + for (Doom existingPool : getDoomPools()) { + int distance = DamerauLevenshtein.calculateDistance(poolName, existingPool.getName()); if (distance < currentDistance) { potentialPoolNames.clear(); - potentialPoolNames.add(existingPool); + potentialPoolNames.add(existingPool.getName()); currentDistance = distance; - } - else if (distance == currentDistance) { - potentialPoolNames.add(existingPool); + } else if (distance == currentDistance) { + potentialPoolNames.add(existingPool.getName()); } } if (potentialPoolNames.isEmpty()) { return null; - } - else if (potentialPoolNames.size() == 1) { + } else if (potentialPoolNames.size() == 1) { if (currentDistance <= 2) { return potentialPoolNames.get(0); - } - else { + } else { return null; } - } - else { + } else { LOGGER.warn("Found multiple doom pool names with same level of similarity"); return null; } } } + public static DoomHandler getInstance() { + return instance; + } + } \ No newline at end of file diff --git a/src/main/java/doom/RemoteDoom.java b/src/main/java/doom/RemoteDoom.java new file mode 100644 index 0000000..fa5f7c8 --- /dev/null +++ b/src/main/java/doom/RemoteDoom.java @@ -0,0 +1,32 @@ +package doom; + +import java.util.concurrent.CompletableFuture; + +public record RemoteDoom( + long id, + String name, + int doom +) implements Doom { + @Override + public String getName() { + return this.name(); + } + + @Override + public CompletableFuture getDoom() { + //Todo Implement + return CompletableFuture.completedFuture(0); + } + + @Override + public CompletableFuture changeDoom(int amount) { + //Todo Implement + return CompletableFuture.completedFuture(new DoomChange(0, amount)); + } + + @Override + public CompletableFuture delete() { + //Todo Implement + return CompletableFuture.completedFuture(false); + } +} diff --git a/src/main/java/doom/RemoteDoomRequest.java b/src/main/java/doom/RemoteDoomRequest.java new file mode 100644 index 0000000..b39d99e --- /dev/null +++ b/src/main/java/doom/RemoteDoomRequest.java @@ -0,0 +1,7 @@ +package doom; + +public record RemoteDoomRequest( + String name, + int doom +) { +} diff --git a/src/main/java/listeners/DoomPoolAutocomplete.java b/src/main/java/listeners/DoomPoolAutocomplete.java index bfcd535..43bb0ec 100644 --- a/src/main/java/listeners/DoomPoolAutocomplete.java +++ b/src/main/java/listeners/DoomPoolAutocomplete.java @@ -15,9 +15,9 @@ public class DoomPoolAutocomplete implements AutocompleteCreateListener { public void onAutocompleteCreate(AutocompleteCreateEvent event) { if (event.getAutocompleteInteraction().getFocusedOption().getName().equals(DoomLogic.POOL_NAME)) { final String enteredValue = event.getAutocompleteInteraction().getFocusedOption().getStringValue().orElse(""); - final List choices = DoomHandler.getDoomPools().keySet().stream() - .filter(poolName -> UtilFunctions.containsIgnoreCase(poolName, enteredValue)) - .map(s -> SlashCommandOptionChoice.create(s, s)) + final List choices = DoomHandler.getDoomPools().stream() + .filter(pool -> UtilFunctions.containsIgnoreCase(pool.getName(), enteredValue)) + .map(s -> SlashCommandOptionChoice.create(s.getName(), s.getName())) .collect(Collectors.toList()); event.getAutocompleteInteraction().respondWithChoices(choices); } diff --git a/src/main/java/listeners/RollComponentInteractionListener.java b/src/main/java/listeners/RollComponentInteractionListener.java index 3e4b31d..e406feb 100644 --- a/src/main/java/listeners/RollComponentInteractionListener.java +++ b/src/main/java/listeners/RollComponentInteractionListener.java @@ -202,8 +202,14 @@ private void handleReroll(ButtonInteraction interaction, Message interactionMess * @return A completable future indicating that the plot points and doom points have been rolled back */ private CompletableFuture rollBackChanges() { - PlayerHandler.getPlayerFromUser(user).map(Player::getDoomPool).ifPresent(s -> DoomHandler.setDoom(s, originalDoomPoints)); - return SheetsHandler.setPlotPoints(user, originalPlotPoints).thenAccept(integer -> {}); + return CompletableFuture.allOf( + PlayerHandler.getPlayerFromUser(user) + .map(Player::getDoomPool) + .map(s -> DoomHandler.setDoomAsync(s, originalDoomPoints)) + .orElseGet(() -> CompletableFuture.completedFuture(null)), + SheetsHandler.setPlotPoints(user, originalPlotPoints) + .thenAccept(integer -> {}) + ); } /** diff --git a/src/main/java/logic/DoomLogic.java b/src/main/java/logic/DoomLogic.java index 6d41eff..35cf9b9 100644 --- a/src/main/java/logic/DoomLogic.java +++ b/src/main/java/logic/DoomLogic.java @@ -20,6 +20,7 @@ import java.util.Arrays; import java.util.List; import java.util.Optional; +import java.util.concurrent.CompletableFuture; public class DoomLogic implements VelenHybridHandler { @@ -73,38 +74,29 @@ private static SlashCommandOptionBuilder getCountOption() { .setRequired(false); } - private EmbedBuilder handleCommand(String mode, String poolName, int count) { + private CompletableFuture handleCommand(String mode, String poolName, int count) { String actualPoolName = DoomHandler.findPool(poolName); if (actualPoolName == null) { if (mode == null || mode.isEmpty() || mode.equalsIgnoreCase("list")) { - return DoomHandler.generateDoomEmbed(); - } - else { - return new EmbedBuilder() + return CompletableFuture.completedFuture(DoomHandler.generateDoomEmbed()); + } else { + return CompletableFuture.completedFuture(new EmbedBuilder() .setTitle("Error") - .setDescription("No Doom Pool with Name ''**" + poolName + "**'' exists."); - } - } - else { - switch (mode) { - case "add": - return DoomHandler.addDoom(actualPoolName, count); - case "sub": - return DoomHandler.addDoom(actualPoolName, count * -1); - case "select": - return DoomHandler.setActivePool(actualPoolName); - case "set": - return DoomHandler.setDoom(actualPoolName, count); - case "delete": - return DoomHandler.deletePool(actualPoolName); - case "create": - return DoomHandler.createPool(actualPoolName, count); - case "list": - return DoomHandler.generateDoomEmbed(); - case "query": - default: - return actualPoolName.equals("") ? DoomHandler.generateDoomEmbed() : DoomHandler.generateDoomEmbed(actualPoolName); + .setDescription("No Doom Pool with Name ''**" + poolName + "**'' exists.") + ); } + } else { + return switch (mode) { + case "add" -> CompletableFuture.completedFuture(DoomHandler.addDoom(actualPoolName, count)); + case "sub" -> CompletableFuture.completedFuture(DoomHandler.addDoom(actualPoolName, count * -1)); + case "select" -> CompletableFuture.completedFuture(DoomHandler.setActivePool(actualPoolName)); + case "set" -> CompletableFuture.completedFuture(DoomHandler.setDoom(actualPoolName, count)); + case "delete" -> CompletableFuture.completedFuture(DoomHandler.deletePool(actualPoolName)); + case "create" -> CompletableFuture.completedFuture(DoomHandler.createPool(actualPoolName, count)); + case "list" -> CompletableFuture.completedFuture(DoomHandler.generateDoomEmbed()); + default -> + CompletableFuture.completedFuture(actualPoolName.isEmpty() ? DoomHandler.generateDoomEmbed() : DoomHandler.generateDoomEmbed(actualPoolName)); + }; } } @@ -120,7 +112,8 @@ public void onEvent(VelenGeneralEvent event, VelenGeneralResponder responder, Us final String name = subcommand.flatMap(velenSubcommand -> velenSubcommand.withName(POOL_NAME)) .flatMap(VelenOption::asString) .orElse(DoomHandler.getActivePool()); - responder.addEmbed(handleCommand(subcommandName, name, count)).respond(); + handleCommand(subcommandName, name, count) + .thenCompose(embed -> responder.addEmbed(embed).respond()); } } } diff --git a/src/main/java/util/CachedValue.java b/src/main/java/util/CachedValue.java new file mode 100644 index 0000000..a6d217a --- /dev/null +++ b/src/main/java/util/CachedValue.java @@ -0,0 +1,42 @@ +package util; + +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; + +public class CachedValue { + private final long ttlMillis; + private final Supplier refresh; + + private final AtomicLong expiryTime; + private final AtomicReference value; + + public CachedValue(long ttlMillis, Supplier refresh) { + this.ttlMillis = ttlMillis; + this.refresh = refresh; + this.value = new AtomicReference<>(); + this.expiryTime = new AtomicLong(); + } + + public T get() { + if (expiryTime.get() < System.currentTimeMillis()) { + this.value.set(null); + } + + T currentValue = this.value.get(); + if (currentValue == null) { + currentValue = refresh.get(); + this.value.set(currentValue); + this.expiryTime.set(System.currentTimeMillis() + ttlMillis); + } + return currentValue; + } + + public void initialize() { + this.get(); + } + + public void forceRefresh() { + this.value.set(null); + } +} From 2572ecff62d7dfb978a639f1e9a3461ff3fa37fb Mon Sep 17 00:00:00 2001 From: Skylar Date: Wed, 8 Jul 2026 20:35:22 -0400 Subject: [PATCH 04/11] Allow changing Settings location via ENV --- .gitignore | 1 + resources/settings.json | 4 +++ src/main/java/configs/Settings.java | 36 +++++++++++++++---- .../RollComponentInteractionListener.java | 1 - 4 files changed, 35 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 2556638..f1cd263 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,4 @@ gradle-app.setting .classpath .idea/ +/tmp/ diff --git a/resources/settings.json b/resources/settings.json index 32d6a14..a8e7448 100644 --- a/resources/settings.json +++ b/resources/settings.json @@ -10,5 +10,9 @@ "token": "", "storytellerRoles": [] }, + "remoteDataSettings": { + "url": "http://localhost:8080", + "token": "" + }, "players": [] } \ No newline at end of file diff --git a/src/main/java/configs/Settings.java b/src/main/java/configs/Settings.java index bb9c3f9..4cc5fdb 100644 --- a/src/main/java/configs/Settings.java +++ b/src/main/java/configs/Settings.java @@ -11,6 +11,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; public class Settings { @@ -20,14 +21,37 @@ public class Settings { private Quotes quotes; private Settings() { - try { - settingsInstance = new Gson().fromJson(new BufferedReader(new FileReader("resources/settings.json")), new TypeToken() { - }.getType()); - quotes = new Gson().fromJson(new BufferedReader(new FileReader("resources/quotes.json")), new TypeToken() { - }.getType()); - } catch (FileNotFoundException e) { + String settingsFileLocation = Optional.ofNullable(System.getenv("SETTINGS_FILE")) + .orElse("resources/settings.json"); + Gson gson = new GsonBuilder() + .setPrettyPrinting() + .create(); + try ( + FileReader fileReader = new FileReader(settingsFileLocation); + BufferedReader bufferedReader = new BufferedReader(fileReader) + ) { + settingsInstance = gson.fromJson( + bufferedReader, + new TypeToken() { + }.getType() + ); + } catch (IOException e) { LOGGER.error("Unable to find settings file!"); settingsInstance = new SettingsInstance(); + } + String quotesFileLocation = Optional.ofNullable(System.getenv("QUOTES_FILE")) + .orElse("resources/quotes.json"); + try ( + FileReader fileReader = new FileReader(quotesFileLocation); + BufferedReader bufferedReader = new BufferedReader(fileReader) + ) { + quotes = gson.fromJson( + bufferedReader, + new TypeToken() { + }.getType() + ); + } catch (IOException e) { + LOGGER.error("Unable to find quotes file!"); quotes = new Quotes(); } } diff --git a/src/main/java/listeners/RollComponentInteractionListener.java b/src/main/java/listeners/RollComponentInteractionListener.java index e406feb..8bcd688 100644 --- a/src/main/java/listeners/RollComponentInteractionListener.java +++ b/src/main/java/listeners/RollComponentInteractionListener.java @@ -208,7 +208,6 @@ private CompletableFuture rollBackChanges() { .map(s -> DoomHandler.setDoomAsync(s, originalDoomPoints)) .orElseGet(() -> CompletableFuture.completedFuture(null)), SheetsHandler.setPlotPoints(user, originalPlotPoints) - .thenAccept(integer -> {}) ); } From 7c298e9c62d46cb62fd6ba79a51da4089c840913 Mon Sep 17 00:00:00 2001 From: Skylar Date: Fri, 17 Jul 2026 00:42:20 -0400 Subject: [PATCH 05/11] Working on setting up RemoteDoom calls --- src/main/java/doom/DoomHandler.java | 8 +++--- src/main/java/doom/RemoteDoom.java | 38 +++++++++++++++++++++++-- src/main/java/sheets/SheetsHandler.java | 19 ++++--------- 3 files changed, 46 insertions(+), 19 deletions(-) diff --git a/src/main/java/doom/DoomHandler.java b/src/main/java/doom/DoomHandler.java index 60a9b7f..6a6514b 100644 --- a/src/main/java/doom/DoomHandler.java +++ b/src/main/java/doom/DoomHandler.java @@ -1,7 +1,6 @@ package doom; import com.google.common.collect.Lists; -import com.google.common.reflect.TypeToken; import com.google.gson.Gson; import configs.DoomSettings; import configs.Settings; @@ -23,8 +22,10 @@ import java.net.http.HttpResponse; import java.text.MessageFormat; import java.time.Duration; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; @@ -94,8 +95,7 @@ private List getRemoteDoom() { HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); - return GSON.fromJson(response.body(), new TypeToken>() { - }.getType()); + return GSON.fromJson(response.body(), RemoteDoom.LIST_TYPE_TOKEN.getType()); } catch (Exception e) { LOGGER.error("Failed to read Remote Doom", e); return Collections.emptyList(); diff --git a/src/main/java/doom/RemoteDoom.java b/src/main/java/doom/RemoteDoom.java index fa5f7c8..93c9e4d 100644 --- a/src/main/java/doom/RemoteDoom.java +++ b/src/main/java/doom/RemoteDoom.java @@ -1,5 +1,14 @@ package doom; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import configs.Settings; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse.BodyHandlers; +import java.util.List; import java.util.concurrent.CompletableFuture; public record RemoteDoom( @@ -7,6 +16,12 @@ public record RemoteDoom( String name, int doom ) implements Doom { + public static final TypeToken> LIST_TYPE_TOKEN = new TypeToken<>() { + }; + + private static final Gson GSON = new Gson(); + private static final HttpClient httpClient = HttpClient.newHttpClient(); + @Override public String getName() { return this.name(); @@ -14,8 +29,27 @@ public String getName() { @Override public CompletableFuture getDoom() { - //Todo Implement - return CompletableFuture.completedFuture(0); + return httpClient.sendAsync( + HttpRequest.newBuilder() + .GET() + .uri(URI.create(Settings.getRemoteDataSettings().getUrl() + "/doompool/" + this.id())) + .header("Authorization", "Bearer " + Settings.getRemoteDataSettings().getToken()) + .build(), + BodyHandlers.ofString() + ).thenComposeAsync(httpResponse -> { + if (httpResponse.statusCode() == 200) { + try { + RemoteDoom doomPool = GSON.fromJson(httpResponse.body(), RemoteDoom.class); + return CompletableFuture.completedFuture(doomPool.doom()); + } catch (Exception e) { + return CompletableFuture.failedFuture(e); + } + } else { + return CompletableFuture.failedFuture( + new Exception("Received %s for Http Response for getDoom".formatted(httpResponse.statusCode())) + ); + } + }); } @Override diff --git a/src/main/java/sheets/SheetsHandler.java b/src/main/java/sheets/SheetsHandler.java index 80f6e1e..875e6b6 100644 --- a/src/main/java/sheets/SheetsHandler.java +++ b/src/main/java/sheets/SheetsHandler.java @@ -19,6 +19,8 @@ import org.apache.commons.lang3.tuple.Pair; import org.javacord.api.entity.user.User; import org.javacord.api.interaction.SlashCommandOptionChoice; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import roles.Player; import roles.PlayerHandler; @@ -33,6 +35,7 @@ import java.util.stream.Collectors; public class SheetsHandler { + private static final Logger LOGGER = LoggerFactory.getLogger(SheetsHandler.class); private static final String APPLICATION_NAME = "Skill Lookup"; private static final JsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance(); private static final String TOKENS_DIRECTORY_PATH = "tokens"; @@ -117,7 +120,7 @@ private static Optional getPlotPointRange(User user) { try { return Optional.of(instance.service.spreadsheets().values().get(spreadsheetForUser.get(), "PlotPoints").execute()); } catch (IOException e) { - e.printStackTrace(); + LOGGER.error("Error while reading plot point range for user {}", spreadsheetForUser, e); } } return Optional.empty(); @@ -159,7 +162,7 @@ public static CompletableFuture> setPlotPoints(User user, int final int updatedPlotPointCount = Integer.parseInt((String) plotPointsCellUpdateRequest.getUpdatedData().getValues().get(0).get(0)); return Optional.of(updatedPlotPointCount); } catch (IOException e) { - e.printStackTrace(); + LOGGER.error("Error while setting plot points for user {}", user, e); } return Optional.empty(); } @@ -167,16 +170,6 @@ public static CompletableFuture> setPlotPoints(User user, int }); } - /** - * A functon used to throw an exception. Used in completable futures to allow exceptions to be handled in exceptonally - * - * @throws T The exception to throw - */ - @SuppressWarnings("all") - public static R sneakyThrow(Throwable t) throws T { - throw (T) t; - } - /** * Gets the current bleed value of a player @@ -190,7 +183,7 @@ public static Optional getPlayerBleed(User user) { try { return Optional.of(instance.service.spreadsheets().values().get(spreadsheetForUser.get(), "PlotPointBleed").execute()).map(valueRange -> Integer.parseInt((String) valueRange.getValues().get(0).get(0))); } catch (IOException e) { - e.printStackTrace(); + LOGGER.error("Error while reading player bleed for user {}", user, e); } } return Optional.empty(); From dcb757f0bd7784601538d78219e4c0201ae60d1a Mon Sep 17 00:00:00 2001 From: Skylar Date: Fri, 17 Jul 2026 01:17:40 -0400 Subject: [PATCH 06/11] Upload of Old Doom Pools now works properly --- build.gradle | 2 +- src/main/java/configs/Settings.java | 32 ++++++++----- src/main/java/doom/ConfigDoom.java | 2 +- src/main/java/doom/DoomHandler.java | 47 +++++++++++-------- .../java/doom/{Doom.java => DoomPool.java} | 2 +- .../{RemoteDoom.java => RemoteDoomPool.java} | 32 ++++++++++--- src/main/java/logic/DoomLogic.java | 6 +-- 7 files changed, 79 insertions(+), 44 deletions(-) rename src/main/java/doom/{Doom.java => DoomPool.java} (89%) rename src/main/java/doom/{RemoteDoom.java => RemoteDoomPool.java} (56%) diff --git a/build.gradle b/build.gradle index 3e30b74..1d027a4 100644 --- a/build.gradle +++ b/build.gradle @@ -30,7 +30,7 @@ dependencies { implementation group: 'com.vdurmont', name: 'emoji-java', version: '5.1.1' implementation group: 'org.apache.commons', name: 'commons-math3', version: '3.6.1' implementation 'io.vavr:vavr:0.10.4' - implementation 'com.google.code.gson:gson:2.9.0' + implementation 'com.google.code.gson:gson:2.14.0' implementation 'com.github.Alan19:Velen:3e73a4b331' implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.17.2' implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.17.2' diff --git a/src/main/java/configs/Settings.java b/src/main/java/configs/Settings.java index 4cc5fdb..094e908 100644 --- a/src/main/java/configs/Settings.java +++ b/src/main/java/configs/Settings.java @@ -15,6 +15,9 @@ import java.util.stream.Collectors; public class Settings { + private static final Gson GSON = new GsonBuilder() + .setPrettyPrinting() + .create(); public static final Settings instance = new Settings(); private static final Logger LOGGER = LogManager.getLogger(Settings.class); private SettingsInstance settingsInstance; @@ -23,14 +26,11 @@ public class Settings { private Settings() { String settingsFileLocation = Optional.ofNullable(System.getenv("SETTINGS_FILE")) .orElse("resources/settings.json"); - Gson gson = new GsonBuilder() - .setPrettyPrinting() - .create(); try ( FileReader fileReader = new FileReader(settingsFileLocation); BufferedReader bufferedReader = new BufferedReader(fileReader) ) { - settingsInstance = gson.fromJson( + settingsInstance = GSON.fromJson( bufferedReader, new TypeToken() { }.getType() @@ -45,7 +45,7 @@ private Settings() { FileReader fileReader = new FileReader(quotesFileLocation); BufferedReader bufferedReader = new BufferedReader(fileReader) ) { - quotes = gson.fromJson( + quotes = GSON.fromJson( bufferedReader, new TypeToken() { }.getType() @@ -84,20 +84,26 @@ private static SettingsInstance getSettingsInstance() { * Serializes the values of the settings and writes it to settings.json. Generally used to update doom pools. */ public static void serializePersonalSettings() { - try { - final BufferedWriter writer = new BufferedWriter(new FileWriter("resources/settings.json")); - new GsonBuilder().setPrettyPrinting().create().toJson(Settings.getSettingsInstance(), writer); - writer.close(); + String settingsFileLocation = Optional.ofNullable(System.getenv("SETTINGS_FILE")) + .orElse("resources/settings.json"); + try ( + FileWriter fileWriter = new FileWriter(settingsFileLocation); + BufferedWriter writer = new BufferedWriter(fileWriter) + ) { + GSON.toJson(Settings.getSettingsInstance(), writer); } catch (IOException e) { LOGGER.error("Unable to serialize settings file!", e); } } protected static void serializeQuotes() { - try { - final BufferedWriter writer = new BufferedWriter(new FileWriter("resources/quotes.json")); - new GsonBuilder().setPrettyPrinting().create().toJson(Settings.getQuotes(), writer); - writer.close(); + String quotesFileLocation = Optional.ofNullable(System.getenv("QUOTES_FILE")) + .orElse("resources/quotes.json"); + try ( + FileWriter fileWriter = new FileWriter(quotesFileLocation); + BufferedWriter writer = new BufferedWriter(fileWriter) + ) { + GSON.toJson(Settings.getQuotes(), writer); } catch (IOException e) { LOGGER.error("Unable to serialize quotes file!", e); } diff --git a/src/main/java/doom/ConfigDoom.java b/src/main/java/doom/ConfigDoom.java index 08ef295..29f6d52 100644 --- a/src/main/java/doom/ConfigDoom.java +++ b/src/main/java/doom/ConfigDoom.java @@ -7,7 +7,7 @@ public record ConfigDoom( String name, int doom -) implements Doom { +) implements DoomPool { @Override public String getName() { diff --git a/src/main/java/doom/DoomHandler.java b/src/main/java/doom/DoomHandler.java index 6a6514b..a75a194 100644 --- a/src/main/java/doom/DoomHandler.java +++ b/src/main/java/doom/DoomHandler.java @@ -4,6 +4,7 @@ import com.google.gson.Gson; import configs.DoomSettings; import configs.Settings; +import org.apache.commons.lang3.tuple.Pair; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.javacord.api.entity.message.embed.EmbedBuilder; @@ -38,7 +39,7 @@ public final class DoomHandler { private static final DoomHandler instance = new DoomHandler(); public final DoomSettings doomSettings; - private final CachedValue> remoteDooms; + private final CachedValue> remoteDooms; private DoomHandler() { doomSettings = Settings.getDoom(); @@ -46,7 +47,7 @@ private DoomHandler() { } public static void setupRemoteDoom() { - if (!getDoomPools().isEmpty()) { + if (!getOldDoomPools().isEmpty()) { instance.doomSettings .getDoomPools() .entrySet() @@ -83,7 +84,7 @@ private static boolean createDoomPool(String name, int count) { } - private List getRemoteDoom() { + private List getRemoteDoom() { try { HttpClient httpClient = HttpClient.newHttpClient(); @@ -95,7 +96,7 @@ private List getRemoteDoom() { HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); - return GSON.fromJson(response.body(), RemoteDoom.LIST_TYPE_TOKEN.getType()); + return GSON.fromJson(response.body(), RemoteDoomPool.LIST_TYPE_TOKEN.getType()); } catch (Exception e) { LOGGER.error("Failed to read Remote Doom", e); return Collections.emptyList(); @@ -178,14 +179,14 @@ public static int getDoom(String pool) { .orElse(0); } - public static Optional getDoomPool(String pool) { + public static Optional getDoomPool(String pool) { return getDoomPools() .stream() .filter(doomPool -> doomPool.getName().equalsIgnoreCase(pool)) .findFirst(); } - public static List getRemoteDoomPools() { + public static List getRemoteDoomPools() { return instance.remoteDooms.get(); } @@ -197,11 +198,11 @@ public static List getOldDoomPools() { .toList(); } - public static List getDoomPools() { - ArrayList dooms = new ArrayList<>(); - dooms.addAll(getRemoteDoomPools()); - dooms.addAll(getOldDoomPools()); - return dooms; + public static List getDoomPools() { + ArrayList doomPools = new ArrayList<>(); + doomPools.addAll(getRemoteDoomPools()); + doomPools.addAll(getOldDoomPools()); + return doomPools; } /** @@ -280,7 +281,7 @@ public static EmbedBuilder generateDoomEmbed(String pool, int oldDoom, int newDo */ public static EmbedBuilder deletePool(String pool) { String description = getDoomPool(pool) - .map(doom -> MessageFormat.format("I''ve removed the ''**{0}**'' doom pool, which contained {1} doom points.", pool, doom)) + .map(doomPool -> MessageFormat.format("I''ve removed the ''**{0}**'' doom pool, which contained {1} doom points.", pool, doomPool)) .orElseGet(() -> MessageFormat.format("I was unable to find the ''**{0}**'' doom pool", pool)); return new EmbedBuilder() @@ -294,7 +295,7 @@ public static EmbedBuilder deletePool(String pool) { * @return Am embed with information on the names and values of all available doom pools and the name and value of * the active doom pool */ - public static EmbedBuilder generateDoomEmbed() { + public static CompletableFuture generateDoomEmbed() { final EmbedBuilder embedBuilder = new EmbedBuilder() .setTitle(DOOM) .setDescription(MessageFormat.format( @@ -303,11 +304,19 @@ public static EmbedBuilder generateDoomEmbed() { getDoom(getActivePool()) )) .setColor(new Color((int) (getDoom() % 101 * (2.55)))); - getDoomPools().forEach(doomPool -> embedBuilder.addField( - doomPool.getName(), - String.valueOf(doomPool.getDoom()) - )); - return embedBuilder; + List>> futures = new ArrayList<>(); + for (DoomPool doomPool : getDoomPools()) { + futures.add(doomPool.getDoom().thenApply(doom -> Pair.of(doomPool.getName(), doom))); + } + return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) + .thenApply(v -> { + futures.forEach(pairFuture -> { + Pair pair = pairFuture.join(); + embedBuilder.addField(pair.getLeft(), String.valueOf(pair.getRight())); + }); + + return embedBuilder; + }); } /** @@ -358,7 +367,7 @@ public static String findPool(String poolName) { } else { List potentialPoolNames = Lists.newArrayList(); int currentDistance = Integer.MAX_VALUE; - for (Doom existingPool : getDoomPools()) { + for (DoomPool existingPool : getDoomPools()) { int distance = DamerauLevenshtein.calculateDistance(poolName, existingPool.getName()); if (distance < currentDistance) { potentialPoolNames.clear(); diff --git a/src/main/java/doom/Doom.java b/src/main/java/doom/DoomPool.java similarity index 89% rename from src/main/java/doom/Doom.java rename to src/main/java/doom/DoomPool.java index b8098a4..8e28183 100644 --- a/src/main/java/doom/Doom.java +++ b/src/main/java/doom/DoomPool.java @@ -2,7 +2,7 @@ import java.util.concurrent.CompletableFuture; -public interface Doom { +public interface DoomPool { String getName(); CompletableFuture getDoom(); diff --git a/src/main/java/doom/RemoteDoom.java b/src/main/java/doom/RemoteDoomPool.java similarity index 56% rename from src/main/java/doom/RemoteDoom.java rename to src/main/java/doom/RemoteDoomPool.java index 93c9e4d..ec786f4 100644 --- a/src/main/java/doom/RemoteDoom.java +++ b/src/main/java/doom/RemoteDoomPool.java @@ -9,14 +9,15 @@ import java.net.http.HttpRequest; import java.net.http.HttpResponse.BodyHandlers; import java.util.List; +import java.util.Map; import java.util.concurrent.CompletableFuture; -public record RemoteDoom( +public record RemoteDoomPool( long id, String name, int doom -) implements Doom { - public static final TypeToken> LIST_TYPE_TOKEN = new TypeToken<>() { +) implements DoomPool { + public static final TypeToken> LIST_TYPE_TOKEN = new TypeToken<>() { }; private static final Gson GSON = new Gson(); @@ -39,7 +40,7 @@ public CompletableFuture getDoom() { ).thenComposeAsync(httpResponse -> { if (httpResponse.statusCode() == 200) { try { - RemoteDoom doomPool = GSON.fromJson(httpResponse.body(), RemoteDoom.class); + RemoteDoomPool doomPool = GSON.fromJson(httpResponse.body(), RemoteDoomPool.class); return CompletableFuture.completedFuture(doomPool.doom()); } catch (Exception e) { return CompletableFuture.failedFuture(e); @@ -54,8 +55,27 @@ public CompletableFuture getDoom() { @Override public CompletableFuture changeDoom(int amount) { - //Todo Implement - return CompletableFuture.completedFuture(new DoomChange(0, amount)); + return httpClient.sendAsync( + HttpRequest.newBuilder() + .POST(HttpRequest.BodyPublishers.ofString(GSON.toJson(Map.of("amount", amount)))) + .uri(URI.create(Settings.getRemoteDataSettings().getUrl() + "/doompool/" + this.id() + "/doom")) + .header("Authorization", "Bearer " + Settings.getRemoteDataSettings().getToken()) + .build(), + BodyHandlers.ofString() + ).thenComposeAsync(httpResponse -> { + if (httpResponse.statusCode() == 200) { + try { + DoomChange doomChange = GSON.fromJson(httpResponse.body(), DoomChange.class); + return CompletableFuture.completedFuture(doomChange); + } catch (Exception e) { + return CompletableFuture.failedFuture(e); + } + } else { + return CompletableFuture.failedFuture( + new Exception("Received %s for Http Response for getDoom".formatted(httpResponse.statusCode())) + ); + } + }); } @Override diff --git a/src/main/java/logic/DoomLogic.java b/src/main/java/logic/DoomLogic.java index 35cf9b9..ce56e05 100644 --- a/src/main/java/logic/DoomLogic.java +++ b/src/main/java/logic/DoomLogic.java @@ -78,7 +78,7 @@ private CompletableFuture handleCommand(String mode, String poolNa String actualPoolName = DoomHandler.findPool(poolName); if (actualPoolName == null) { if (mode == null || mode.isEmpty() || mode.equalsIgnoreCase("list")) { - return CompletableFuture.completedFuture(DoomHandler.generateDoomEmbed()); + return DoomHandler.generateDoomEmbed(); } else { return CompletableFuture.completedFuture(new EmbedBuilder() .setTitle("Error") @@ -93,9 +93,9 @@ private CompletableFuture handleCommand(String mode, String poolNa case "set" -> CompletableFuture.completedFuture(DoomHandler.setDoom(actualPoolName, count)); case "delete" -> CompletableFuture.completedFuture(DoomHandler.deletePool(actualPoolName)); case "create" -> CompletableFuture.completedFuture(DoomHandler.createPool(actualPoolName, count)); - case "list" -> CompletableFuture.completedFuture(DoomHandler.generateDoomEmbed()); + case "list" -> DoomHandler.generateDoomEmbed(); default -> - CompletableFuture.completedFuture(actualPoolName.isEmpty() ? DoomHandler.generateDoomEmbed() : DoomHandler.generateDoomEmbed(actualPoolName)); + actualPoolName.isEmpty() ? DoomHandler.generateDoomEmbed() : CompletableFuture.completedFuture(DoomHandler.generateDoomEmbed(actualPoolName)); }; } } From 403f573c3489145d106f9c28382fcbd5dbde4fcb Mon Sep 17 00:00:00 2001 From: Skylar Date: Sun, 19 Jul 2026 14:49:55 -0400 Subject: [PATCH 07/11] Fixes languages.json --- resources/languages.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/languages.json b/resources/languages.json index 1fd0747..6606d66 100644 --- a/resources/languages.json +++ b/resources/languages.json @@ -453,7 +453,7 @@ "to": "Cosma" }, { - "from": "Coimperial", + "from": "Corimperial", "to": "Cosma" }, { @@ -466,7 +466,7 @@ }, { "from": "Imperial English", - "to": "Coimperial" + "to": "Corimperial" }, { "from": "Starword", From 773b97f5546ef946159af677dfce5c5f9d17d271 Mon Sep 17 00:00:00 2001 From: Skylar Date: Sun, 19 Jul 2026 14:50:02 -0400 Subject: [PATCH 08/11] Fix some logging --- src/main/java/discord/TwoDee.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/discord/TwoDee.java b/src/main/java/discord/TwoDee.java index d7d4e80..3a1b529 100644 --- a/src/main/java/discord/TwoDee.java +++ b/src/main/java/discord/TwoDee.java @@ -35,7 +35,7 @@ public static void main(String[] args) { final Velen velen = SlashCommandRegister.setupVelen(languageLogic); new DiscordApiBuilder().setToken(token).setAllIntentsExcept(Intent.GUILD_PRESENCES).setUserCacheEnabled(true).addListener(velen).login().thenAccept(api -> { - System.out.println("You can invite the bot by using the following url: " + api.createBotInvite() + "&scope=bot%20applications.commands"); + LOGGER.info("You can invite the bot by using the following url: {}&scope=bot%20applications.commands", api.createBotInvite()); velen.registerAllSlashCommands(api); //Send startup message Settings.getDiscordSettings().getAnnouncementChannels().forEach(id -> { @@ -44,7 +44,7 @@ public static void main(String[] args) { channel.get().sendMessage(Settings.getQuotes().getRandomStartupQuote()); } else { - LOGGER.error("Failed to find channel for ID: %d".formatted(id)); + LOGGER.error("Failed to find channel for ID: {}", id); } }); AwardContextMenu.setupContextMenu(api); From 66f60fca80accc85d44aec301c51acfd26362ed2 Mon Sep 17 00:00:00 2001 From: Skylar Date: Sun, 19 Jul 2026 23:32:55 -0400 Subject: [PATCH 09/11] Swap to full slf4j logging --- build.gradle | 4 ++-- .../java/calculation/outputs/BigQueryOutput.java | 6 +++--- src/main/java/calculation/outputs/SQLOutput.java | 6 +++--- src/main/java/configs/Settings.java | 6 +++--- src/main/java/discord/TwoDee.java | 6 +++--- src/main/java/doom/DoomHandler.java | 6 +++--- src/main/java/language/LanguageLogic.java | 6 +++--- src/main/java/logic/CalculationLogic.java | 6 +++--- src/main/java/util/DamerauLevenshtein.java | 7 +++---- src/main/resources/log4j2.xml | 12 ------------ 10 files changed, 26 insertions(+), 39 deletions(-) delete mode 100644 src/main/resources/log4j2.xml diff --git a/build.gradle b/build.gradle index 1d027a4..830f1fc 100644 --- a/build.gradle +++ b/build.gradle @@ -32,11 +32,11 @@ dependencies { implementation 'io.vavr:vavr:0.10.4' implementation 'com.google.code.gson:gson:2.14.0' implementation 'com.github.Alan19:Velen:3e73a4b331' - implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.17.2' - implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.17.2' implementation group: 'org.xerial', name: 'sqlite-jdbc', version: '3.36.0.3' implementation 'org.jgrapht:jgrapht-jdk1.5:0.7.3' implementation 'com.io7m.jgrapht:jgrapht-guava:1.2.1-MODULARIZED' + implementation 'org.slf4j:slf4j-simple:2.0.18' + implementation 'org.apache.logging.log4j:log4j-to-slf4j:2.26.1' } distZip { diff --git a/src/main/java/calculation/outputs/BigQueryOutput.java b/src/main/java/calculation/outputs/BigQueryOutput.java index c7c8889..9967ad6 100644 --- a/src/main/java/calculation/outputs/BigQueryOutput.java +++ b/src/main/java/calculation/outputs/BigQueryOutput.java @@ -4,8 +4,8 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import io.vavr.control.Try; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.annotation.Nullable; import java.io.BufferedWriter; @@ -19,7 +19,7 @@ * Output class for output a json new line delimited form for Google's Big Query. Dice end up being an array object in the Roll Object */ public class BigQueryOutput implements IOutput { - private final static Logger LOGGER = LogManager.getLogger(BigQueryOutput.class); + private final static Logger LOGGER = LoggerFactory.getLogger(BigQueryOutput.class); private final static Gson GSON = new GsonBuilder() .create(); diff --git a/src/main/java/calculation/outputs/SQLOutput.java b/src/main/java/calculation/outputs/SQLOutput.java index 53a0474..ef08626 100644 --- a/src/main/java/calculation/outputs/SQLOutput.java +++ b/src/main/java/calculation/outputs/SQLOutput.java @@ -5,8 +5,8 @@ import calculation.models.RollInfo; import io.vavr.control.Try; import org.apache.commons.lang3.tuple.Pair; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import rolling.Roller; import javax.annotation.Nullable; @@ -24,7 +24,7 @@ * Output that creates a SQLite Database with two Tables: rolls and dice with rolls being the parent table */ public class SQLOutput implements IOutput, AutoCloseable { - private static final Logger LOGGER = LogManager.getLogger(SQLOutput.class); + private static final Logger LOGGER = LoggerFactory.getLogger(SQLOutput.class); private final Connection connection; private final String fileLocation; diff --git a/src/main/java/configs/Settings.java b/src/main/java/configs/Settings.java index 094e908..ae1df3f 100644 --- a/src/main/java/configs/Settings.java +++ b/src/main/java/configs/Settings.java @@ -3,8 +3,8 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import roles.Player; import java.io.*; @@ -19,7 +19,7 @@ public class Settings { .setPrettyPrinting() .create(); public static final Settings instance = new Settings(); - private static final Logger LOGGER = LogManager.getLogger(Settings.class); + private static final Logger LOGGER = LoggerFactory.getLogger(Settings.class); private SettingsInstance settingsInstance; private Quotes quotes; diff --git a/src/main/java/discord/TwoDee.java b/src/main/java/discord/TwoDee.java index 3a1b529..06576da 100644 --- a/src/main/java/discord/TwoDee.java +++ b/src/main/java/discord/TwoDee.java @@ -7,11 +7,11 @@ import listeners.LanguageAutocompleteListener; import listeners.PoolAutocompleteListener; import logic.AwardContextMenu; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import org.javacord.api.DiscordApiBuilder; import org.javacord.api.entity.intent.Intent; import org.javacord.api.util.logging.ExceptionLogger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import pw.mihou.velen.interfaces.Velen; import pw.mihou.velen.internals.observer.VelenObserver; import pw.mihou.velen.internals.observer.modes.ObserverMode; @@ -20,7 +20,7 @@ import java.io.File; public class TwoDee { - private static final Logger LOGGER = LogManager.getLogger(TwoDee.class); + private static final Logger LOGGER = LoggerFactory.getLogger(TwoDee.class); public static void main(String[] args) { diff --git a/src/main/java/doom/DoomHandler.java b/src/main/java/doom/DoomHandler.java index a75a194..96d4c22 100644 --- a/src/main/java/doom/DoomHandler.java +++ b/src/main/java/doom/DoomHandler.java @@ -5,10 +5,10 @@ import configs.DoomSettings; import configs.Settings; import org.apache.commons.lang3.tuple.Pair; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import org.javacord.api.entity.message.embed.EmbedBuilder; import org.javacord.api.entity.user.User; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import roles.Player; import roles.PlayerHandler; import roles.Storytellers; @@ -32,7 +32,7 @@ public final class DoomHandler { - private static final Logger LOGGER = LogManager.getLogger(DoomHandler.class); + private static final Logger LOGGER = LoggerFactory.getLogger(DoomHandler.class); public static final String DOOM = "Doom!"; private static final Gson GSON = new Gson(); diff --git a/src/main/java/language/LanguageLogic.java b/src/main/java/language/LanguageLogic.java index bd03fd5..296d863 100644 --- a/src/main/java/language/LanguageLogic.java +++ b/src/main/java/language/LanguageLogic.java @@ -8,11 +8,11 @@ import com.google.gson.*; import io.vavr.control.Try; import org.apache.commons.lang3.tuple.Pair; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import org.jgrapht.Graph; import org.jgrapht.alg.DijkstraShortestPath; import org.jgrapht.graph.guava.MutableGraphAdapter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import util.DamerauLevenshtein; import util.GsonHelper; import util.Match; @@ -30,7 +30,7 @@ @SuppressWarnings("UnstableApiUsage") public class LanguageLogic { - private static final Logger LOGGER = LogManager.getLogger(LanguageLogic.class); + private static final Logger LOGGER = LoggerFactory.getLogger(LanguageLogic.class); private static final Gson GSON = new GsonBuilder() .setPrettyPrinting() .create(); diff --git a/src/main/java/logic/CalculationLogic.java b/src/main/java/logic/CalculationLogic.java index 731519b..dd66563 100644 --- a/src/main/java/logic/CalculationLogic.java +++ b/src/main/java/logic/CalculationLogic.java @@ -9,8 +9,6 @@ import exceptions.UserException; import io.vavr.Tuple; import io.vavr.control.Try; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import org.javacord.api.entity.channel.Channel; import org.javacord.api.entity.channel.ChannelType; import org.javacord.api.entity.channel.TextChannel; @@ -20,6 +18,8 @@ import org.javacord.api.event.message.MessageCreateEvent; import org.javacord.api.interaction.*; import org.javacord.api.interaction.callback.InteractionImmediateResponseBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import pw.mihou.velen.interfaces.*; import pw.mihou.velen.interfaces.routed.VelenRoutedOptions; @@ -34,7 +34,7 @@ * Command Class for Calculation. Handles IO, as well as streaming all messages into the Collector */ public class CalculationLogic implements VelenEvent, VelenSlashEvent { - private final static Logger LOGGER = LogManager.getLogger(CalculationLogic.class); + private final static Logger LOGGER = LoggerFactory.getLogger(CalculationLogic.class); @SuppressWarnings("ResultOfMethodCallIgnored") @Override diff --git a/src/main/java/util/DamerauLevenshtein.java b/src/main/java/util/DamerauLevenshtein.java index 79a3f75..df46874 100644 --- a/src/main/java/util/DamerauLevenshtein.java +++ b/src/main/java/util/DamerauLevenshtein.java @@ -1,16 +1,15 @@ package util; import com.google.common.collect.Lists; -import doom.DoomHandler; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.Collection; import java.util.List; import java.util.Optional; public class DamerauLevenshtein { - private static final Logger LOGGER = LogManager.getLogger(DamerauLevenshtein.class); + private static final Logger LOGGER = LoggerFactory.getLogger(DamerauLevenshtein.class); /** * Calculates the string distance between source and target strings using diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml deleted file mode 100644 index 4a20803..0000000 --- a/src/main/resources/log4j2.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file From 052b225dc44bc5a8afc33d661c26693b2e0f9cdb Mon Sep 17 00:00:00 2001 From: Skylar Date: Sun, 19 Jul 2026 23:33:06 -0400 Subject: [PATCH 10/11] Properly create users/characters --- src/main/java/discord/TwoDee.java | 13 ++- src/main/java/roles/PlayerHandler.java | 138 +++++++++++++++++++++-- src/main/java/roles/RemoteCharacter.java | 18 +++ src/main/java/roles/RemoteUser.java | 22 ++++ src/main/java/sheets/SheetsHandler.java | 17 +++ 5 files changed, 197 insertions(+), 11 deletions(-) create mode 100644 src/main/java/roles/RemoteCharacter.java create mode 100644 src/main/java/roles/RemoteUser.java diff --git a/src/main/java/discord/TwoDee.java b/src/main/java/discord/TwoDee.java index 06576da..e3dd248 100644 --- a/src/main/java/discord/TwoDee.java +++ b/src/main/java/discord/TwoDee.java @@ -15,6 +15,7 @@ import pw.mihou.velen.interfaces.Velen; import pw.mihou.velen.internals.observer.VelenObserver; import pw.mihou.velen.internals.observer.modes.ObserverMode; +import roles.PlayerHandler; import slashcommands.SlashCommandRegister; import java.io.File; @@ -34,7 +35,14 @@ public static void main(String[] args) { ); final Velen velen = SlashCommandRegister.setupVelen(languageLogic); - new DiscordApiBuilder().setToken(token).setAllIntentsExcept(Intent.GUILD_PRESENCES).setUserCacheEnabled(true).addListener(velen).login().thenAccept(api -> { + new DiscordApiBuilder() + .setToken(token) + .setAllIntentsExcept(Intent.GUILD_PRESENCES) + .setUserCacheEnabled(true) + .addListener(velen) + .login() + .thenCompose(PlayerHandler.getInstance()::uploadPlayersToRemote) + .thenAccept(api -> { LOGGER.info("You can invite the bot by using the following url: {}&scope=bot%20applications.commands", api.createBotInvite()); velen.registerAllSlashCommands(api); //Send startup message @@ -42,8 +50,7 @@ public static void main(String[] args) { var channel = api.getTextChannelById(id); if (channel.isPresent()) { channel.get().sendMessage(Settings.getQuotes().getRandomStartupQuote()); - } - else { + } else { LOGGER.error("Failed to find channel for ID: {}", id); } }); diff --git a/src/main/java/roles/PlayerHandler.java b/src/main/java/roles/PlayerHandler.java index 779b6a0..2e12f37 100644 --- a/src/main/java/roles/PlayerHandler.java +++ b/src/main/java/roles/PlayerHandler.java @@ -1,18 +1,31 @@ package roles; +import com.google.gson.Gson; import configs.Settings; +import doom.DoomHandler; +import doom.RemoteDoomPool; import org.apache.commons.lang3.tuple.Pair; +import org.javacord.api.DiscordApi; import org.javacord.api.entity.Mentionable; import org.javacord.api.entity.permission.Role; import org.javacord.api.entity.user.User; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sheets.SheetsHandler; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Optional; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import java.util.stream.Collectors; public class PlayerHandler { + private static final Logger LOGGER = LoggerFactory.getLogger(PlayerHandler.class); + private static final HttpClient HTTP_CLIENT = HttpClient.newHttpClient(); + private static final Gson GSON = new Gson(); private static final PlayerHandler instance = new PlayerHandler(); private final List players; @@ -40,14 +53,12 @@ public static Collection> getPlayersFromMentionable(Mentionab ) .flatMap(Optional::stream) .collect(Collectors.toSet()); - } - else if (mentionable instanceof User) { + } else if (mentionable instanceof User) { return getPlayerFromUser((User) mentionable) .map(player -> Pair.of((User) mentionable, player)) .map(Collections::singleton) .orElse(Collections.emptySet()); - } - else { + } else { return Collections.emptyList(); } } @@ -55,4 +66,115 @@ else if (mentionable instanceof User) { public List getPlayers() { return players; } + + public CompletableFuture uploadPlayersToRemote(DiscordApi discordApi) { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(Settings.getRemoteDataSettings().getUrl() + "/character")) + .header("Authorization", "Bearer " + Settings.getRemoteDataSettings().getToken()) + .GET() + .build(); + + return HTTP_CLIENT.sendAsync(request, HttpResponse.BodyHandlers.ofString()) + .>thenApply(response -> GSON.fromJson( + response.body(), + RemoteCharacter.LIST_TYPE_TOKEN.getType() + )) + .thenApply(this::getPlayersWithoutRemoteCharacters) + .thenCompose(players -> CompletableFuture.allOf(players.stream() + .map(player -> createCharacterFor(discordApi, player)) + .toArray(CompletableFuture[]::new) + )) + .thenApply(ignore -> discordApi); + } + + private List getPlayersWithoutRemoteCharacters(List remoteCharacters) { + List existingSheets = remoteCharacters.stream() + .map(RemoteCharacter::sheet) + .toList(); + + return players.stream() + .filter(player -> !existingSheets.contains(player.getSheetId())) + .toList(); + } + + private CompletableFuture createCharacterFor(DiscordApi discordApi, Player player) { + return this.findUserForPlayer(Long.toString(player.getDiscordId())) + .thenCompose(existingUser -> existingUser.map(CompletableFuture::completedFuture) + .orElseGet(() -> this.createUserForPlayer(discordApi, player)) + ) + .thenCompose(remoteUser -> createCharacterFor(remoteUser, player)) + .thenAccept(remoteCharacter -> LOGGER.info("Created new Remote Character {}", remoteCharacter.name())); + } + + private CompletableFuture createCharacterFor(RemoteUser remoteUser, Player player) { + LOGGER.info("Creating new character for user {}", player.getDiscordId()); + return SheetsHandler.getName(player.getSheetId()) + .thenCompose(name -> { + if (!name.isEmpty()) { + Map map = new HashMap<>(); + + map.put("name", name); + map.put("sheet", player.getSheetId()); + map.put("ownerId", remoteUser.id()); + + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(Settings.getRemoteDataSettings().getUrl() + "/character")) + .header("Authorization", "Bearer " + Settings.getRemoteDataSettings().getToken()) + .POST(HttpRequest.BodyPublishers.ofString(GSON.toJson(map))) + .build(); + + return HTTP_CLIENT.sendAsync(request, HttpResponse.BodyHandlers.ofString()) + .thenApply(response -> GSON.fromJson(response.body(), RemoteCharacter.class)); + } else { + throw new CompletionException(new IllegalStateException("Failed to get name for " + player.getSheetId())); + } + }); + } + + private CompletableFuture> findUserForPlayer(String discordId) { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(Settings.getRemoteDataSettings().getUrl() + "/user?external_id=" + discordId)) + .header("Authorization", "Bearer " + Settings.getRemoteDataSettings().getToken()) + .GET() + .build(); + + return HTTP_CLIENT.sendAsync(request, HttpResponse.BodyHandlers.ofString()) + .>thenApply(response -> GSON.fromJson(response.body(), RemoteUser.LIST_TYPE_TOKEN.getType())) + .thenApply(existingUsers -> existingUsers.stream() + .filter(user -> Optional.ofNullable(user.externalId()) + .filter(discordId::equals) + .isPresent() + ) + .findFirst() + ); + } + + private CompletableFuture createUserForPlayer(DiscordApi discordApi, Player player) { + Optional doomPoolId = DoomHandler.getDoomPool(player.getDoomPool()) + .flatMap(doomPool -> { + if (doomPool instanceof RemoteDoomPool remoteDoomPool) { + return Optional.of(remoteDoomPool.id()); + } else { + return Optional.empty(); + } + }); + LOGGER.info("Creating new user for user {}", player.getDiscordId()); + return discordApi.getUserById(player.getDiscordId()) + .thenCompose(user -> { + Map requestValues = new HashMap<>(); + requestValues.put("name", user.getName()); + requestValues.put("externalId", Long.toString(player.getDiscordId())); + requestValues.put("notes", "Created by TwoDee"); + doomPoolId.ifPresent(id -> requestValues.put("doomPermission", id.toString())); + + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(Settings.getRemoteDataSettings().getUrl() + "/user")) + .header("Authorization", "Bearer " + Settings.getRemoteDataSettings().getToken()) + .POST(HttpRequest.BodyPublishers.ofString(GSON.toJson(requestValues))) + .build(); + + return HTTP_CLIENT.sendAsync(request, HttpResponse.BodyHandlers.ofString()); + }) + .thenApply(response -> GSON.fromJson(response.body(), RemoteUser.class)); + } } diff --git a/src/main/java/roles/RemoteCharacter.java b/src/main/java/roles/RemoteCharacter.java new file mode 100644 index 0000000..e125c89 --- /dev/null +++ b/src/main/java/roles/RemoteCharacter.java @@ -0,0 +1,18 @@ +package roles; + +import com.google.gson.reflect.TypeToken; + +import java.util.List; +import java.util.Map; + +public record RemoteCharacter( + long id, + String name, + String sheet, + long ownerId, + int plotPoints, + Map skills +) { + public static final TypeToken> LIST_TYPE_TOKEN = new TypeToken<>() { + }; +} diff --git a/src/main/java/roles/RemoteUser.java b/src/main/java/roles/RemoteUser.java new file mode 100644 index 0000000..22510c6 --- /dev/null +++ b/src/main/java/roles/RemoteUser.java @@ -0,0 +1,22 @@ +package roles; + +import com.google.gson.reflect.TypeToken; + +import javax.annotation.Nullable; +import java.util.List; + +public record RemoteUser( + long id, + String name, + String characterPermission, + String doomPermission, + String userPermission, + String tokenPermission, + @Nullable String externalId, + String notes, + boolean active, + @Nullable Long createdBy +) { + public static final TypeToken> LIST_TYPE_TOKEN = new TypeToken<>() { + }; +} diff --git a/src/main/java/sheets/SheetsHandler.java b/src/main/java/sheets/SheetsHandler.java index 875e6b6..8b1f51d 100644 --- a/src/main/java/sheets/SheetsHandler.java +++ b/src/main/java/sheets/SheetsHandler.java @@ -32,6 +32,7 @@ import java.text.MessageFormat; import java.util.*; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import java.util.stream.Collectors; public class SheetsHandler { @@ -293,4 +294,20 @@ private static ValueRange getRange(String spreadsheetID, String range) throws IO .get(spreadsheetID, range) .execute(); } + + public static CompletableFuture getName(String spreadsheetId) { + return CompletableFuture.supplyAsync(() -> { + try { + ValueRange valueRange = getRange(spreadsheetId, "B1:L2"); + return valueRange.getValues() + .stream() + .flatMap(List::stream) + .findFirst() + .map(Object::toString) + .orElse(""); + } catch (IOException e) { + throw new CompletionException(e); + } + }); + } } From 3b4099718a1acbbaa35885f501cbb31583ec8a44 Mon Sep 17 00:00:00 2001 From: Skylar Date: Mon, 20 Jul 2026 00:01:41 -0400 Subject: [PATCH 11/11] Fix todo for doom --- src/main/java/doom/RemoteDoomPool.java | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/main/java/doom/RemoteDoomPool.java b/src/main/java/doom/RemoteDoomPool.java index ec786f4..450b04f 100644 --- a/src/main/java/doom/RemoteDoomPool.java +++ b/src/main/java/doom/RemoteDoomPool.java @@ -80,7 +80,21 @@ public CompletableFuture changeDoom(int amount) { @Override public CompletableFuture delete() { - //Todo Implement - return CompletableFuture.completedFuture(false); + return httpClient.sendAsync( + HttpRequest.newBuilder() + .DELETE() + .uri(URI.create(Settings.getRemoteDataSettings().getUrl() + "/doompool/" + this.id())) + .header("Authorization", "Bearer " + Settings.getRemoteDataSettings().getToken()) + .build(), + BodyHandlers.ofString() + ).thenComposeAsync(httpResponse -> { + if (httpResponse.statusCode() == 200) { + return CompletableFuture.completedFuture(true); + } else { + return CompletableFuture.failedFuture( + new Exception("Received %s for Http Response for getDoom".formatted(httpResponse.statusCode())) + ); + } + }); } }