diff --git a/pom.xml b/pom.xml
index 9dd6e31..369ed4d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -55,7 +55,21 @@
json
20220924
-
+
+ com.github.jinahya
+ random-org-json-rpc
+ 0.0.1
+
+
+ com.google.code.gson
+ gson
+ 2.10.1
+
+
+ commons-codec
+ commons-codec
+ 1.15
+
diff --git a/src/main/java/wtf/beatrice/hidekobot/Cache.java b/src/main/java/wtf/beatrice/hidekobot/Cache.java
index 841c5e0..1a0a640 100644
--- a/src/main/java/wtf/beatrice/hidekobot/Cache.java
+++ b/src/main/java/wtf/beatrice/hidekobot/Cache.java
@@ -1,6 +1,7 @@
package wtf.beatrice.hidekobot;
import org.jetbrains.annotations.Nullable;
+import org.random.util.RandomOrgRandom;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import wtf.beatrice.hidekobot.datasources.ConfigurationEntry;
@@ -31,7 +32,7 @@ public class Cache
// the Random instance that we should always use when looking for an RNG based thing.
// the seed is updated periodically.
- private static final SecureRandom randomInstance = new SecureRandom();
+ private static Random randomInstance = new SecureRandom();
// map to store results of "love calculator", to avoid people re-running the same command until
// they get what they wanted.
@@ -86,8 +87,22 @@ public class Cache
return randomInstance;
}
- public static void setRandomSeed(long seed) {
- randomInstance.setSeed(seed);
+ public static void initRandomOrg(String apiKey)
+ {
+ /* we use the random.org instance to generate 160 random bytes.
+ then, we're feeding those 160 bytes as a seed for a SecureRandom.
+
+ this is preferred to calling the RandomOrgRandom directly every time,
+ because it has to query the api and (1) takes a long time, especially with big
+ dice rolls, and (2) you'd run in the limits extremely quickly if the bot
+ was run publicly for everyone to use.
+ */
+
+ RandomOrgRandom randomOrg = new RandomOrgRandom(apiKey);
+ byte[] randomBytes = new byte[160];
+ randomOrg.nextBytes(randomBytes);
+
+ randomInstance = new SecureRandom(randomBytes);
}
/**
@@ -143,6 +158,10 @@ public class Cache
return configurationSource == null ? null : (String) configurationSource.getConfigValue(ConfigurationEntry.BOT_TOKEN);
}
+ public static String getRandomOrgApiKey() {
+ return configurationSource == null ? null : (String) configurationSource.getConfigValue(ConfigurationEntry.RANDOM_ORG_API_KEY);
+ }
+
/**
* Get the bot maintainer's profile id.
*
diff --git a/src/main/java/wtf/beatrice/hidekobot/HidekoBot.java b/src/main/java/wtf/beatrice/hidekobot/HidekoBot.java
index 8f5233a..7832377 100644
--- a/src/main/java/wtf/beatrice/hidekobot/HidekoBot.java
+++ b/src/main/java/wtf/beatrice/hidekobot/HidekoBot.java
@@ -10,12 +10,14 @@ import sun.misc.Signal;
import wtf.beatrice.hidekobot.commands.completer.ProfileImageCommandCompleter;
import wtf.beatrice.hidekobot.commands.message.HelloCommand;
import wtf.beatrice.hidekobot.commands.slash.*;
+import wtf.beatrice.hidekobot.datasources.ConfigurationEntry;
import wtf.beatrice.hidekobot.datasources.ConfigurationSource;
import wtf.beatrice.hidekobot.datasources.DatabaseSource;
import wtf.beatrice.hidekobot.datasources.PropertiesSource;
import wtf.beatrice.hidekobot.listeners.*;
import wtf.beatrice.hidekobot.runnables.ExpiredMessageTask;
import wtf.beatrice.hidekobot.runnables.HeartBeatTask;
+import wtf.beatrice.hidekobot.runnables.RandomOrgSeedTask;
import wtf.beatrice.hidekobot.runnables.StatusUpdateTask;
import wtf.beatrice.hidekobot.util.CommandUtil;
import wtf.beatrice.hidekobot.util.FormatUtil;
@@ -107,6 +109,22 @@ public class HidekoBot
}
+
+ boolean enableRandomSeedUpdaterTask = false;
+ // initialize random.org object if API key is provided
+ {
+ String apiKey = Cache.getRandomOrgApiKey();
+ if(apiKey != null &&
+ !apiKey.isEmpty() &&
+ !apiKey.equals(ConfigurationEntry.RANDOM_ORG_API_KEY.getDefaultValue()))
+ {
+ LOGGER.info("Enabling Random.org integration... This might take a while!");
+ Cache.initRandomOrg(apiKey);
+ enableRandomSeedUpdaterTask = true;
+ LOGGER.info("Random.org integration enabled!");
+ }
+ }
+
// register slash commands and completers
SlashCommandListener slashCommandListener = new SlashCommandListener();
SlashCommandCompletionListener slashCommandCompletionListener = new SlashCommandCompletionListener();
@@ -198,6 +216,11 @@ public class HidekoBot
scheduler.scheduleAtFixedRate(heartBeatTask, 10L, 30L, TimeUnit.SECONDS); //every 30 seconds
StatusUpdateTask statusUpdateTask = new StatusUpdateTask();
scheduler.scheduleAtFixedRate(statusUpdateTask, 0L, 60L * 5L, TimeUnit.SECONDS); // every 5 minutes
+ if(enableRandomSeedUpdaterTask)
+ {
+ RandomOrgSeedTask randomSeedTask = new RandomOrgSeedTask();
+ scheduler.scheduleAtFixedRate(randomSeedTask, 15L, 15L, TimeUnit.MINUTES); // every 15 minutes
+ }
// register shutdown interrupt signal listener for proper shutdown.
Signal.handle(new Signal("INT"), signal -> shutdown());
diff --git a/src/main/java/wtf/beatrice/hidekobot/commands/base/DiceRoll.java b/src/main/java/wtf/beatrice/hidekobot/commands/base/DiceRoll.java
index 9726716..414d159 100644
--- a/src/main/java/wtf/beatrice/hidekobot/commands/base/DiceRoll.java
+++ b/src/main/java/wtf/beatrice/hidekobot/commands/base/DiceRoll.java
@@ -2,6 +2,7 @@ package wtf.beatrice.hidekobot.commands.base;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.User;
+import org.random.util.RandomOrgRandom;
import wtf.beatrice.hidekobot.Cache;
import wtf.beatrice.hidekobot.objects.MessageResponse;
import wtf.beatrice.hidekobot.objects.fun.Dice;
@@ -125,6 +126,9 @@ public class DiceRoll
embedBuilder.setAuthor(author.getAsTag(), null, author.getAvatarUrl());
embedBuilder.setTitle("Dice Roll");
+ if(Cache.getRandom() instanceof RandomOrgRandom)
+ embedBuilder.setFooter("Seed provided by Random.org");
+
StringBuilder message = new StringBuilder();
int total = 0;
diff --git a/src/main/java/wtf/beatrice/hidekobot/datasources/ConfigurationEntry.java b/src/main/java/wtf/beatrice/hidekobot/datasources/ConfigurationEntry.java
index 997958a..21cb2c6 100644
--- a/src/main/java/wtf/beatrice/hidekobot/datasources/ConfigurationEntry.java
+++ b/src/main/java/wtf/beatrice/hidekobot/datasources/ConfigurationEntry.java
@@ -7,6 +7,7 @@ public enum ConfigurationEntry
BOT_OWNER_ID("bot-owner-id", 100000000000000000L),
BOT_COLOR("bot-color", "PINK"),
HEARTBEAT_LINK("heartbeat-link", "https://your-heartbeat-api.com/api/push/apikey?status=up&msg=OK&ping="),
+ RANDOM_ORG_API_KEY("random-org-api-key", "00000000-0000-0000-0000-000000000000"),
;
diff --git a/src/main/java/wtf/beatrice/hidekobot/runnables/RandomOrgSeedTask.java b/src/main/java/wtf/beatrice/hidekobot/runnables/RandomOrgSeedTask.java
new file mode 100644
index 0000000..786f8aa
--- /dev/null
+++ b/src/main/java/wtf/beatrice/hidekobot/runnables/RandomOrgSeedTask.java
@@ -0,0 +1,33 @@
+package wtf.beatrice.hidekobot.runnables;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import wtf.beatrice.hidekobot.Cache;
+import wtf.beatrice.hidekobot.datasources.ConfigurationEntry;
+
+/**
+ * This runnable pulls a random seed from random.org and used it to feed a SecureRandom,
+ * if the integration is enabled.
+ *
+ * This is necessary since we are not directly accessing random.org for each random number we
+ * need, and thus, only the seed is random - not the algorithm applied to it to compute the numbers.
+ */
+public class RandomOrgSeedTask implements Runnable
+{
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(RandomOrgSeedTask.class);
+
+ @Override
+ public void run()
+ {
+ String apiKey = Cache.getRandomOrgApiKey();
+ if(apiKey != null &&
+ !apiKey.isEmpty() &&
+ !apiKey.equals(ConfigurationEntry.RANDOM_ORG_API_KEY.getDefaultValue()))
+ {
+ if(Cache.isVerbose()) LOGGER.info("Updating Random seed from random.org...");
+ Cache.initRandomOrg(Cache.getRandomOrgApiKey());
+ if(Cache.isVerbose()) LOGGER.info("Random.org seed updated!");
+ }
+ }
+}
\ No newline at end of file