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