diff --git a/services/discord-rules/1.16.5/.gradle/7.6.4/checksums/checksums.lock b/services/discord-rules/1.16.5/.gradle/7.6.4/checksums/checksums.lock new file mode 100644 index 0000000..5f6e62b Binary files /dev/null and b/services/discord-rules/1.16.5/.gradle/7.6.4/checksums/checksums.lock differ diff --git a/services/discord-rules/1.16.5/.gradle/7.6.4/checksums/md5-checksums.bin b/services/discord-rules/1.16.5/.gradle/7.6.4/checksums/md5-checksums.bin new file mode 100644 index 0000000..09d1160 Binary files /dev/null and b/services/discord-rules/1.16.5/.gradle/7.6.4/checksums/md5-checksums.bin differ diff --git a/services/discord-rules/1.16.5/.gradle/7.6.4/checksums/sha1-checksums.bin b/services/discord-rules/1.16.5/.gradle/7.6.4/checksums/sha1-checksums.bin new file mode 100644 index 0000000..9ce5b8d Binary files /dev/null and b/services/discord-rules/1.16.5/.gradle/7.6.4/checksums/sha1-checksums.bin differ diff --git a/services/discord-rules/1.16.5/.gradle/7.6.4/dependencies-accessors/dependencies-accessors.lock b/services/discord-rules/1.16.5/.gradle/7.6.4/dependencies-accessors/dependencies-accessors.lock new file mode 100644 index 0000000..78b61fe Binary files /dev/null and b/services/discord-rules/1.16.5/.gradle/7.6.4/dependencies-accessors/dependencies-accessors.lock differ diff --git a/services/discord-rules/1.16.5/.gradle/7.6.4/dependencies-accessors/gc.properties b/services/discord-rules/1.16.5/.gradle/7.6.4/dependencies-accessors/gc.properties new file mode 100644 index 0000000..e69de29 diff --git a/services/discord-rules/1.16.5/.gradle/7.6.4/executionHistory/executionHistory.bin b/services/discord-rules/1.16.5/.gradle/7.6.4/executionHistory/executionHistory.bin new file mode 100644 index 0000000..f899704 Binary files /dev/null and b/services/discord-rules/1.16.5/.gradle/7.6.4/executionHistory/executionHistory.bin differ diff --git a/services/discord-rules/1.16.5/.gradle/7.6.4/executionHistory/executionHistory.lock b/services/discord-rules/1.16.5/.gradle/7.6.4/executionHistory/executionHistory.lock new file mode 100644 index 0000000..c352856 Binary files /dev/null and b/services/discord-rules/1.16.5/.gradle/7.6.4/executionHistory/executionHistory.lock differ diff --git a/services/discord-rules/1.16.5/.gradle/7.6.4/fileChanges/last-build.bin b/services/discord-rules/1.16.5/.gradle/7.6.4/fileChanges/last-build.bin new file mode 100644 index 0000000..f76dd23 Binary files /dev/null and b/services/discord-rules/1.16.5/.gradle/7.6.4/fileChanges/last-build.bin differ diff --git a/services/discord-rules/1.16.5/.gradle/7.6.4/fileHashes/fileHashes.bin b/services/discord-rules/1.16.5/.gradle/7.6.4/fileHashes/fileHashes.bin new file mode 100644 index 0000000..9ea7522 Binary files /dev/null and b/services/discord-rules/1.16.5/.gradle/7.6.4/fileHashes/fileHashes.bin differ diff --git a/services/discord-rules/1.16.5/.gradle/7.6.4/fileHashes/fileHashes.lock b/services/discord-rules/1.16.5/.gradle/7.6.4/fileHashes/fileHashes.lock new file mode 100644 index 0000000..f9cf0c0 Binary files /dev/null and b/services/discord-rules/1.16.5/.gradle/7.6.4/fileHashes/fileHashes.lock differ diff --git a/services/discord-rules/1.16.5/.gradle/7.6.4/fileHashes/resourceHashesCache.bin b/services/discord-rules/1.16.5/.gradle/7.6.4/fileHashes/resourceHashesCache.bin new file mode 100644 index 0000000..ceaa296 Binary files /dev/null and b/services/discord-rules/1.16.5/.gradle/7.6.4/fileHashes/resourceHashesCache.bin differ diff --git a/services/discord-rules/1.16.5/.gradle/7.6.4/gc.properties b/services/discord-rules/1.16.5/.gradle/7.6.4/gc.properties new file mode 100644 index 0000000..e69de29 diff --git a/services/discord-rules/1.16.5/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/services/discord-rules/1.16.5/.gradle/buildOutputCleanup/buildOutputCleanup.lock new file mode 100644 index 0000000..1c78d02 Binary files /dev/null and b/services/discord-rules/1.16.5/.gradle/buildOutputCleanup/buildOutputCleanup.lock differ diff --git a/services/discord-rules/1.16.5/.gradle/buildOutputCleanup/cache.properties b/services/discord-rules/1.16.5/.gradle/buildOutputCleanup/cache.properties new file mode 100644 index 0000000..39a80c1 --- /dev/null +++ b/services/discord-rules/1.16.5/.gradle/buildOutputCleanup/cache.properties @@ -0,0 +1,2 @@ +#Sun Apr 12 15:36:24 CDT 2026 +gradle.version=7.6.4 diff --git a/services/discord-rules/1.16.5/.gradle/buildOutputCleanup/outputFiles.bin b/services/discord-rules/1.16.5/.gradle/buildOutputCleanup/outputFiles.bin new file mode 100644 index 0000000..13da7c3 Binary files /dev/null and b/services/discord-rules/1.16.5/.gradle/buildOutputCleanup/outputFiles.bin differ diff --git a/services/discord-rules/1.16.5/.gradle/file-system.probe b/services/discord-rules/1.16.5/.gradle/file-system.probe new file mode 100644 index 0000000..0c9517f Binary files /dev/null and b/services/discord-rules/1.16.5/.gradle/file-system.probe differ diff --git a/services/discord-rules/1.16.5/.gradle/vcs-1/gc.properties b/services/discord-rules/1.16.5/.gradle/vcs-1/gc.properties new file mode 100644 index 0000000..e69de29 diff --git a/services/discord-rules/1.16.5/build.gradle b/services/discord-rules/1.16.5/build.gradle new file mode 100644 index 0000000..9b4ab00 --- /dev/null +++ b/services/discord-rules/1.16.5/build.gradle @@ -0,0 +1,45 @@ +buildscript { + repositories { + maven { url = 'https://maven.minecraftforge.net/' } + mavenCentral() + } + dependencies { + classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '5.1.+' + } +} + +apply plugin: 'net.minecraftforge.gradle' + +version = '1.0.0' +group = 'com.discordrules' +archivesBaseName = 'discordrules' + +java.toolchain.languageVersion = JavaLanguageVersion.of(8) + +minecraft { + mappings channel: 'official', version: '1.16.5' + runs { + server { + workingDirectory project.file('run') + mods { + discordrules { + source sourceSets.main + } + } + } + } +} + +dependencies { + minecraft 'net.minecraftforge:forge:1.16.5-36.2.39' +} + +jar { + manifest { + attributes(["Implementation-Title": "Discord Rules", "Implementation-Version": project.version]) + } +} + +tasks.withType(JavaCompile).configureEach { + options.encoding = 'UTF-8' +} diff --git a/services/discord-rules/1.16.5/gradle.properties b/services/discord-rules/1.16.5/gradle.properties new file mode 100644 index 0000000..29b2ac4 --- /dev/null +++ b/services/discord-rules/1.16.5/gradle.properties @@ -0,0 +1,2 @@ +org.gradle.jvmargs=-Xmx3G +org.gradle.daemon=false diff --git a/services/discord-rules/1.16.5/settings.gradle b/services/discord-rules/1.16.5/settings.gradle new file mode 100644 index 0000000..c38c5e3 --- /dev/null +++ b/services/discord-rules/1.16.5/settings.gradle @@ -0,0 +1,7 @@ +pluginManagement { + repositories { + gradlePluginPortal() + maven { url = 'https://maven.minecraftforge.net/' } + } +} +rootProject.name = 'discordrules' diff --git a/services/discord-rules/1.16.5/src/main/java/com/discordrules/CooldownManager.java b/services/discord-rules/1.16.5/src/main/java/com/discordrules/CooldownManager.java new file mode 100644 index 0000000..98918f3 --- /dev/null +++ b/services/discord-rules/1.16.5/src/main/java/com/discordrules/CooldownManager.java @@ -0,0 +1,31 @@ +package com.discordrules; + +import net.minecraft.util.text.StringTextComponent; +import net.minecraft.entity.player.ServerPlayerEntity; +import java.time.Duration; +import java.time.Instant; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class CooldownManager { + private static final ConcurrentHashMap COOLDOWNS = new ConcurrentHashMap<>(); + + public static boolean checkAndUpdateCooldown(ServerPlayerEntity player) { + UUID playerId = player.getUUID(); + Instant now = Instant.now(); + int cooldownSeconds = ServerRulesConfig.COOLDOWN_SECONDS.get(); + Instant lastUsed = COOLDOWNS.get(playerId); + if (lastUsed != null) { + long secondsSinceLastUse = Duration.between(lastUsed, now).getSeconds(); + if (secondsSinceLastUse < cooldownSeconds) { + long remaining = cooldownSeconds - secondsSinceLastUse; + player.sendMessage(new StringTextComponent("\u00A7cPlease wait " + remaining + " seconds before checking the rules again."), player.getUUID()); + return false; + } + } + COOLDOWNS.put(playerId, now); + return true; + } + + public static void removePlayer(UUID playerId) { COOLDOWNS.remove(playerId); } +} diff --git a/services/discord-rules/1.16.5/src/main/java/com/discordrules/DiscordFetcher.java b/services/discord-rules/1.16.5/src/main/java/com/discordrules/DiscordFetcher.java new file mode 100644 index 0000000..d45c346 --- /dev/null +++ b/services/discord-rules/1.16.5/src/main/java/com/discordrules/DiscordFetcher.java @@ -0,0 +1,52 @@ +package com.discordrules; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.concurrent.CompletableFuture; + +public class DiscordFetcher { + private static final Logger LOGGER = LogManager.getLogger(DiscordFetcher.class); + + public static CompletableFuture fetchRulesAsync() { + if (!ServerRulesConfig.isMessageIdValid()) { + LOGGER.error("Invalid Discord Message ID in config."); + return CompletableFuture.completedFuture(null); + } + return CompletableFuture.supplyAsync(() -> { + try { + String token = ServerRulesConfig.BOT_TOKEN.get(); + String channelId = ServerRulesConfig.CHANNEL_ID.get(); + String messageId = ServerRulesConfig.MESSAGE_ID.get(); + URL url = new URL("https://discord.com/api/v10/channels/" + channelId + "/messages/" + messageId); + HttpURLConnection con = (HttpURLConnection) url.openConnection(); + con.setRequestMethod("GET"); + con.setRequestProperty("Authorization", "Bot " + token); + con.setRequestProperty("Accept", "application/json"); + con.setConnectTimeout(10000); + con.setReadTimeout(10000); + int status = con.getResponseCode(); + if (status == 200) { + BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream())); + StringBuilder content = new StringBuilder(); + String inputLine; + while ((inputLine = in.readLine()) != null) { content.append(inputLine); } + in.close(); + JsonObject json = new JsonParser().parse(content.toString()).getAsJsonObject(); + return json.get("content").getAsString(); + } else { + LOGGER.error("Discord API returned status: {}", status); + return null; + } + } catch (Exception ex) { + LOGGER.error("Network error while fetching Discord rules", ex); + return null; + } + }); + } +} diff --git a/services/discord-rules/1.16.5/src/main/java/com/discordrules/DiscordFormatter.java b/services/discord-rules/1.16.5/src/main/java/com/discordrules/DiscordFormatter.java new file mode 100644 index 0000000..13d71c1 --- /dev/null +++ b/services/discord-rules/1.16.5/src/main/java/com/discordrules/DiscordFormatter.java @@ -0,0 +1,45 @@ +package com.discordrules; + +import net.minecraft.util.text.StringTextComponent; +import net.minecraft.util.text.TextFormatting; +import net.minecraft.util.text.IFormattableTextComponent; + +public class DiscordFormatter { + public static IFormattableTextComponent formatRules(String rawDiscordText) { + String processedText = ServerRulesConfig.STRIP_EMOJIS.get() + ? stripEmojis(rawDiscordText) : rawDiscordText; + TextFormatting headerColor = parseColor(ServerRulesConfig.HEADER_COLOR.get(), TextFormatting.GOLD); + TextFormatting bodyColor = parseColor(ServerRulesConfig.BODY_COLOR.get(), TextFormatting.YELLOW); + IFormattableTextComponent rootComponent = new StringTextComponent(""); + String[] lines = processedText.split("\n"); + for (String line : lines) { + IFormattableTextComponent lineComponent; + if (line.startsWith("**") && line.endsWith("**")) { + String cleanLine = line.replace("**", ""); + lineComponent = new StringTextComponent(cleanLine); + lineComponent.withStyle(headerColor, TextFormatting.BOLD); + } else if (line.trim().startsWith("-") || line.trim().startsWith("\u2022")) { + lineComponent = new StringTextComponent(" " + line.trim()); + lineComponent.withStyle(bodyColor); + } else { + lineComponent = new StringTextComponent(line); + lineComponent.withStyle(bodyColor); + } + rootComponent.append(lineComponent).append(new StringTextComponent("\n")); + } + return rootComponent; + } + + private static String stripEmojis(String text) { + if (text == null) return ""; + return text.replaceAll("[\\x{1F300}-\\x{1F9FF}]", ""); + } + + private static TextFormatting parseColor(String name, TextFormatting fallback) { + try { + return TextFormatting.valueOf(name.toUpperCase()); + } catch (IllegalArgumentException e) { + return fallback; + } + } +} diff --git a/services/discord-rules/1.16.5/src/main/java/com/discordrules/RulesCache.java b/services/discord-rules/1.16.5/src/main/java/com/discordrules/RulesCache.java new file mode 100644 index 0000000..6ffab0b --- /dev/null +++ b/services/discord-rules/1.16.5/src/main/java/com/discordrules/RulesCache.java @@ -0,0 +1,26 @@ +package com.discordrules; + +import java.time.Instant; + +public class RulesCache { + private static String cachedRules = null; + private static Instant lastFetchTime = Instant.MIN; + private static final String FALLBACK_RULES = + "Server Rules\n1. Be respectful to all players.\n2. No griefing or cheating.\n3. Follow staff instructions.\nPlease check Discord for the full rules list."; + + public static boolean isCacheValid() { + if (cachedRules == null) return false; + long cacheMinutes = ServerRulesConfig.CACHE_MINUTES.get(); + return Instant.now().isBefore(lastFetchTime.plusSeconds(cacheMinutes * 60)); + } + + public static void updateCache(String newRules) { + if (newRules != null && !newRules.trim().isEmpty()) { + cachedRules = newRules; + lastFetchTime = Instant.now(); + } + } + + public static String getRules() { return cachedRules != null ? cachedRules : FALLBACK_RULES; } + public static void invalidate() { cachedRules = null; lastFetchTime = Instant.MIN; } +} diff --git a/services/discord-rules/1.16.5/src/main/java/com/discordrules/RulesCommand.java b/services/discord-rules/1.16.5/src/main/java/com/discordrules/RulesCommand.java new file mode 100644 index 0000000..17fbaf5 --- /dev/null +++ b/services/discord-rules/1.16.5/src/main/java/com/discordrules/RulesCommand.java @@ -0,0 +1,44 @@ +package com.discordrules; + +import com.mojang.brigadier.CommandDispatcher; +import net.minecraft.command.CommandSource; +import net.minecraft.command.Commands; +import net.minecraft.entity.player.ServerPlayerEntity; +import net.minecraft.util.text.StringTextComponent; +import net.minecraft.util.text.IFormattableTextComponent; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class RulesCommand { + private static final Logger LOGGER = LogManager.getLogger(RulesCommand.class); + + public static void register(CommandDispatcher dispatcher) { + dispatcher.register(Commands.literal("rules").executes(context -> { + CommandSource source = context.getSource(); + if (!(source.getEntity() instanceof ServerPlayerEntity)) { + source.sendSuccess(DiscordFormatter.formatRules(RulesCache.getRules()), false); + return 1; + } + ServerPlayerEntity player = (ServerPlayerEntity) source.getEntity(); + if (!CooldownManager.checkAndUpdateCooldown(player)) return 0; + if (RulesCache.isCacheValid()) { + player.sendMessage(DiscordFormatter.formatRules(RulesCache.getRules()), player.getUUID()); + return 1; + } + player.sendMessage(new StringTextComponent("\u00A77Fetching latest rules..."), player.getUUID()); + DiscordFetcher.fetchRulesAsync().thenAccept(fetchedRules -> { + String rulesText; + if (fetchedRules != null) { + RulesCache.updateCache(fetchedRules); + rulesText = fetchedRules; + } else { + LOGGER.warn("Discord fetch failed. Falling back to cache for {}", player.getName().getString()); + rulesText = RulesCache.getRules(); + } + IFormattableTextComponent formattedRules = DiscordFormatter.formatRules(rulesText); + source.getServer().execute(() -> player.sendMessage(formattedRules, player.getUUID())); + }); + return 1; + })); + } +} diff --git a/services/discord-rules/1.16.5/src/main/java/com/discordrules/ServerRules.java b/services/discord-rules/1.16.5/src/main/java/com/discordrules/ServerRules.java new file mode 100644 index 0000000..1a0dcc0 --- /dev/null +++ b/services/discord-rules/1.16.5/src/main/java/com/discordrules/ServerRules.java @@ -0,0 +1,34 @@ +package com.discordrules; + +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.RegisterCommandsEvent; +import net.minecraftforge.event.entity.player.PlayerEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.ModLoadingContext; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.config.ModConfig; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +@Mod("discordrules") +public class ServerRules { + private static final Logger LOGGER = LogManager.getLogger(ServerRules.class); + + public ServerRules() { + ModLoadingContext.get().registerConfig(ModConfig.Type.SERVER, ServerRulesConfig.SPEC); + MinecraftForge.EVENT_BUS.register(this); + LOGGER.info("Discord Rules Mod Initialized."); + } + + @SubscribeEvent + public void onRegisterCommands(RegisterCommandsEvent event) { + RulesCommand.register(event.getDispatcher()); + LOGGER.info("Registered /rules command."); + } + + @SubscribeEvent + public void onPlayerLogout(PlayerEvent.PlayerLoggedOutEvent event) { + CooldownManager.removePlayer(event.getEntity().getUUID()); + } +} diff --git a/services/discord-rules/1.16.5/src/main/java/com/discordrules/ServerRulesConfig.java b/services/discord-rules/1.16.5/src/main/java/com/discordrules/ServerRulesConfig.java new file mode 100644 index 0000000..62b38fb --- /dev/null +++ b/services/discord-rules/1.16.5/src/main/java/com/discordrules/ServerRulesConfig.java @@ -0,0 +1,40 @@ +package com.discordrules; + +import net.minecraftforge.common.ForgeConfigSpec; +import org.apache.commons.lang3.StringUtils; + +public class ServerRulesConfig { + public static final ForgeConfigSpec SPEC; + public static final ForgeConfigSpec.ConfigValue BOT_TOKEN; + public static final ForgeConfigSpec.ConfigValue CHANNEL_ID; + public static final ForgeConfigSpec.ConfigValue MESSAGE_ID; + public static final ForgeConfigSpec.IntValue COOLDOWN_SECONDS; + public static final ForgeConfigSpec.IntValue CACHE_MINUTES; + public static final ForgeConfigSpec.ConfigValue HEADER_COLOR; + public static final ForgeConfigSpec.ConfigValue BODY_COLOR; + public static final ForgeConfigSpec.BooleanValue STRIP_EMOJIS; + + static { + ForgeConfigSpec.Builder builder = new ForgeConfigSpec.Builder(); + builder.push("discord"); + BOT_TOKEN = builder.comment("Discord Bot Token").define("bot_token", "YOUR_TOKEN_HERE"); + CHANNEL_ID = builder.comment("Discord Channel ID").define("channel_id", "1234567890123456789"); + MESSAGE_ID = builder.comment("Discord Message ID").define("message_id", "1234567890123456789"); + builder.pop(); + builder.push("display"); + HEADER_COLOR = builder.comment("Header color (bold lines). Valid values: BLACK, DARK_BLUE, DARK_GREEN, DARK_AQUA, DARK_RED, DARK_PURPLE, GOLD, GRAY, DARK_GRAY, BLUE, GREEN, AQUA, RED, LIGHT_PURPLE, YELLOW, WHITE").define("header_color", "GOLD"); + BODY_COLOR = builder.comment("Body color (regular lines and bullet points)").define("body_color", "YELLOW"); + STRIP_EMOJIS = builder.comment("Strip emojis that Minecraft can't render (recommended: true)").define("strip_emojis", true); + builder.pop(); + builder.push("performance"); + COOLDOWN_SECONDS = builder.comment("Per-player cooldown in seconds").defineInRange("cooldown_seconds", 60, 0, 3600); + CACHE_MINUTES = builder.comment("Cache duration in minutes").defineInRange("cache_minutes", 30, 1, 1440); + builder.pop(); + SPEC = builder.build(); + } + + public static boolean isMessageIdValid() { + String id = MESSAGE_ID.get(); + return StringUtils.isNotBlank(id) && id.matches("^\\d{17,20}$"); + } +} diff --git a/services/discord-rules/1.16.5/src/main/resources/META-INF/mods.toml b/services/discord-rules/1.16.5/src/main/resources/META-INF/mods.toml new file mode 100644 index 0000000..ae133ea --- /dev/null +++ b/services/discord-rules/1.16.5/src/main/resources/META-INF/mods.toml @@ -0,0 +1,24 @@ +modLoader="javafml" +loaderVersion="[36,)" +license="MIT" + +[[mods]] +modId="discordrules" +version="${file.jarVersion}" +displayName="Discord Rules" +authors="FirefrostGaming" +description='''Displays server rules in-game via /rules, fetched live from a Discord message. Fully configurable colors, cooldowns, and caching.''' + +[[dependencies.discordrules]] +modId="forge" +mandatory=true +versionRange="[36,)" +ordering="NONE" +side="SERVER" + +[[dependencies.discordrules]] +modId="minecraft" +mandatory=true +versionRange="[1.16.5,1.17)" +ordering="NONE" +side="SERVER" diff --git a/services/discord-rules/1.20.1/.gradle/8.8/checksums/checksums.lock b/services/discord-rules/1.20.1/.gradle/8.8/checksums/checksums.lock new file mode 100644 index 0000000..add0705 Binary files /dev/null and b/services/discord-rules/1.20.1/.gradle/8.8/checksums/checksums.lock differ diff --git a/services/discord-rules/1.20.1/.gradle/8.8/checksums/md5-checksums.bin b/services/discord-rules/1.20.1/.gradle/8.8/checksums/md5-checksums.bin new file mode 100644 index 0000000..cd96c19 Binary files /dev/null and b/services/discord-rules/1.20.1/.gradle/8.8/checksums/md5-checksums.bin differ diff --git a/services/discord-rules/1.20.1/.gradle/8.8/checksums/sha1-checksums.bin b/services/discord-rules/1.20.1/.gradle/8.8/checksums/sha1-checksums.bin new file mode 100644 index 0000000..23ce1a7 Binary files /dev/null and b/services/discord-rules/1.20.1/.gradle/8.8/checksums/sha1-checksums.bin differ diff --git a/services/discord-rules/1.20.1/.gradle/8.8/dependencies-accessors/gc.properties b/services/discord-rules/1.20.1/.gradle/8.8/dependencies-accessors/gc.properties new file mode 100644 index 0000000..e69de29 diff --git a/services/discord-rules/1.20.1/.gradle/8.8/executionHistory/executionHistory.bin b/services/discord-rules/1.20.1/.gradle/8.8/executionHistory/executionHistory.bin new file mode 100644 index 0000000..1825f2e Binary files /dev/null and b/services/discord-rules/1.20.1/.gradle/8.8/executionHistory/executionHistory.bin differ diff --git a/services/discord-rules/1.20.1/.gradle/8.8/executionHistory/executionHistory.lock b/services/discord-rules/1.20.1/.gradle/8.8/executionHistory/executionHistory.lock new file mode 100644 index 0000000..8a6964c Binary files /dev/null and b/services/discord-rules/1.20.1/.gradle/8.8/executionHistory/executionHistory.lock differ diff --git a/services/discord-rules/1.20.1/.gradle/8.8/expanded/expanded.lock b/services/discord-rules/1.20.1/.gradle/8.8/expanded/expanded.lock new file mode 100644 index 0000000..35bc03f Binary files /dev/null and b/services/discord-rules/1.20.1/.gradle/8.8/expanded/expanded.lock differ diff --git a/services/discord-rules/1.20.1/.gradle/8.8/fileChanges/last-build.bin b/services/discord-rules/1.20.1/.gradle/8.8/fileChanges/last-build.bin new file mode 100644 index 0000000..f76dd23 Binary files /dev/null and b/services/discord-rules/1.20.1/.gradle/8.8/fileChanges/last-build.bin differ diff --git a/services/discord-rules/1.20.1/.gradle/8.8/fileHashes/fileHashes.bin b/services/discord-rules/1.20.1/.gradle/8.8/fileHashes/fileHashes.bin new file mode 100644 index 0000000..a1fff82 Binary files /dev/null and b/services/discord-rules/1.20.1/.gradle/8.8/fileHashes/fileHashes.bin differ diff --git a/services/discord-rules/1.20.1/.gradle/8.8/fileHashes/fileHashes.lock b/services/discord-rules/1.20.1/.gradle/8.8/fileHashes/fileHashes.lock new file mode 100644 index 0000000..af47946 Binary files /dev/null and b/services/discord-rules/1.20.1/.gradle/8.8/fileHashes/fileHashes.lock differ diff --git a/services/discord-rules/1.20.1/.gradle/8.8/fileHashes/resourceHashesCache.bin b/services/discord-rules/1.20.1/.gradle/8.8/fileHashes/resourceHashesCache.bin new file mode 100644 index 0000000..ce57976 Binary files /dev/null and b/services/discord-rules/1.20.1/.gradle/8.8/fileHashes/resourceHashesCache.bin differ diff --git a/services/discord-rules/1.20.1/.gradle/8.8/gc.properties b/services/discord-rules/1.20.1/.gradle/8.8/gc.properties new file mode 100644 index 0000000..e69de29 diff --git a/services/discord-rules/1.20.1/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/services/discord-rules/1.20.1/.gradle/buildOutputCleanup/buildOutputCleanup.lock new file mode 100644 index 0000000..7bd0c36 Binary files /dev/null and b/services/discord-rules/1.20.1/.gradle/buildOutputCleanup/buildOutputCleanup.lock differ diff --git a/services/discord-rules/1.20.1/.gradle/buildOutputCleanup/cache.properties b/services/discord-rules/1.20.1/.gradle/buildOutputCleanup/cache.properties new file mode 100644 index 0000000..919adbd --- /dev/null +++ b/services/discord-rules/1.20.1/.gradle/buildOutputCleanup/cache.properties @@ -0,0 +1,2 @@ +#Sun Apr 12 15:34:40 CDT 2026 +gradle.version=8.8 diff --git a/services/discord-rules/1.20.1/.gradle/buildOutputCleanup/outputFiles.bin b/services/discord-rules/1.20.1/.gradle/buildOutputCleanup/outputFiles.bin new file mode 100644 index 0000000..05031ec Binary files /dev/null and b/services/discord-rules/1.20.1/.gradle/buildOutputCleanup/outputFiles.bin differ diff --git a/services/discord-rules/1.20.1/.gradle/file-system.probe b/services/discord-rules/1.20.1/.gradle/file-system.probe new file mode 100644 index 0000000..03939c9 Binary files /dev/null and b/services/discord-rules/1.20.1/.gradle/file-system.probe differ diff --git a/services/discord-rules/1.20.1/.gradle/vcs-1/gc.properties b/services/discord-rules/1.20.1/.gradle/vcs-1/gc.properties new file mode 100644 index 0000000..e69de29 diff --git a/services/discord-rules/1.20.1/build.gradle b/services/discord-rules/1.20.1/build.gradle new file mode 100755 index 0000000..3bb3e72 --- /dev/null +++ b/services/discord-rules/1.20.1/build.gradle @@ -0,0 +1,50 @@ +buildscript { + repositories { + maven { url = 'https://maven.minecraftforge.net/' } + mavenCentral() + } + dependencies { + classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '6.0.+' + } +} + +apply plugin: 'net.minecraftforge.gradle' + +version = '1.0.0' +group = 'com.discordrules' +archivesBaseName = 'discordrules' + +java.toolchain.languageVersion = JavaLanguageVersion.of(17) + +minecraft { + mappings channel: 'official', version: '1.20.1' + runs { + server { + workingDirectory project.file('run') + property 'forge.logging.markers', 'REGISTRIES' + property 'forge.logging.console.level', 'debug' + mods { + discordrules { + source sourceSets.main + } + } + } + } +} + +dependencies { + minecraft 'net.minecraftforge:forge:1.20.1-47.3.0' +} + +jar { + manifest { + attributes([ + "Implementation-Title": "Discord Rules", + "Implementation-Version": project.version, + ]) + } +} + +tasks.withType(JavaCompile).configureEach { + options.encoding = 'UTF-8' +} diff --git a/services/discord-rules/1.20.1/gradle.properties b/services/discord-rules/1.20.1/gradle.properties new file mode 100755 index 0000000..29b2ac4 --- /dev/null +++ b/services/discord-rules/1.20.1/gradle.properties @@ -0,0 +1,2 @@ +org.gradle.jvmargs=-Xmx3G +org.gradle.daemon=false diff --git a/services/discord-rules/1.20.1/settings.gradle b/services/discord-rules/1.20.1/settings.gradle new file mode 100755 index 0000000..428f51d --- /dev/null +++ b/services/discord-rules/1.20.1/settings.gradle @@ -0,0 +1,8 @@ +pluginManagement { + repositories { + gradlePluginPortal() + maven { url = 'https://maven.minecraftforge.net/' } + } +} + +rootProject.name = 'discordrules' diff --git a/services/discord-rules/1.20.1/src/main/java/com/discordrules/CooldownManager.java b/services/discord-rules/1.20.1/src/main/java/com/discordrules/CooldownManager.java new file mode 100755 index 0000000..be1ba3a --- /dev/null +++ b/services/discord-rules/1.20.1/src/main/java/com/discordrules/CooldownManager.java @@ -0,0 +1,31 @@ +package com.discordrules; + +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import java.time.Duration; +import java.time.Instant; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class CooldownManager { + private static final ConcurrentHashMap COOLDOWNS = new ConcurrentHashMap<>(); + + public static boolean checkAndUpdateCooldown(ServerPlayer player) { + UUID playerId = player.getUUID(); + Instant now = Instant.now(); + int cooldownSeconds = ServerRulesConfig.COOLDOWN_SECONDS.get(); + Instant lastUsed = COOLDOWNS.get(playerId); + if (lastUsed != null) { + long secondsSinceLastUse = Duration.between(lastUsed, now).getSeconds(); + if (secondsSinceLastUse < cooldownSeconds) { + long remaining = cooldownSeconds - secondsSinceLastUse; + player.sendSystemMessage(Component.literal("\u00A7cPlease wait " + remaining + " seconds before checking the rules again.")); + return false; + } + } + COOLDOWNS.put(playerId, now); + return true; + } + + public static void removePlayer(UUID playerId) { COOLDOWNS.remove(playerId); } +} diff --git a/services/discord-rules/1.20.1/src/main/java/com/discordrules/DiscordFetcher.java b/services/discord-rules/1.20.1/src/main/java/com/discordrules/DiscordFetcher.java new file mode 100755 index 0000000..9f35c3a --- /dev/null +++ b/services/discord-rules/1.20.1/src/main/java/com/discordrules/DiscordFetcher.java @@ -0,0 +1,45 @@ +package com.discordrules; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; +import java.util.concurrent.CompletableFuture; + +public class DiscordFetcher { + private static final Logger LOGGER = LoggerFactory.getLogger(DiscordFetcher.class); + private static final HttpClient CLIENT = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build(); + + public static CompletableFuture fetchRulesAsync() { + if (!ServerRulesConfig.isMessageIdValid()) { + LOGGER.error("Invalid Discord Message ID in config."); + return CompletableFuture.completedFuture(null); + } + String token = ServerRulesConfig.BOT_TOKEN.get(); + String channelId = ServerRulesConfig.CHANNEL_ID.get(); + String messageId = ServerRulesConfig.MESSAGE_ID.get(); + URI uri = URI.create("https://discord.com/api/v10/channels/" + channelId + "/messages/" + messageId); + HttpRequest request = HttpRequest.newBuilder().uri(uri) + .header("Authorization", "Bot " + token) + .header("Accept", "application/json").GET().build(); + return CLIENT.sendAsync(request, HttpResponse.BodyHandlers.ofString()) + .thenApply(response -> { + if (response.statusCode() == 200) { + JsonObject json = JsonParser.parseString(response.body()).getAsJsonObject(); + return json.get("content").getAsString(); + } else { + LOGGER.error("Discord API returned status: {}", response.statusCode()); + return null; + } + }) + .exceptionally(ex -> { + LOGGER.error("Network error while fetching Discord rules", ex); + return null; + }); + } +} diff --git a/services/discord-rules/1.20.1/src/main/java/com/discordrules/DiscordFormatter.java b/services/discord-rules/1.20.1/src/main/java/com/discordrules/DiscordFormatter.java new file mode 100755 index 0000000..d0ace99 --- /dev/null +++ b/services/discord-rules/1.20.1/src/main/java/com/discordrules/DiscordFormatter.java @@ -0,0 +1,42 @@ +package com.discordrules; + +import net.minecraft.ChatFormatting; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; + +public class DiscordFormatter { + public static MutableComponent formatRules(String rawDiscordText) { + String processedText = ServerRulesConfig.STRIP_EMOJIS.get() + ? stripEmojis(rawDiscordText) : rawDiscordText; + ChatFormatting headerColor = parseColor(ServerRulesConfig.HEADER_COLOR.get(), ChatFormatting.GOLD); + ChatFormatting bodyColor = parseColor(ServerRulesConfig.BODY_COLOR.get(), ChatFormatting.YELLOW); + MutableComponent rootComponent = Component.empty(); + String[] lines = processedText.split("\n"); + for (String line : lines) { + MutableComponent lineComponent; + if (line.startsWith("**") && line.endsWith("**")) { + String cleanLine = line.replace("**", ""); + lineComponent = Component.literal(cleanLine).withStyle(headerColor, ChatFormatting.BOLD); + } else if (line.trim().startsWith("-") || line.trim().startsWith("\u2022")) { + lineComponent = Component.literal(" " + line.trim()).withStyle(bodyColor); + } else { + lineComponent = Component.literal(line).withStyle(bodyColor); + } + rootComponent.append(lineComponent).append(Component.literal("\n")); + } + return rootComponent; + } + + private static String stripEmojis(String text) { + if (text == null) return ""; + return text.replaceAll("[\\x{1F300}-\\x{1F9FF}]", ""); + } + + private static ChatFormatting parseColor(String name, ChatFormatting fallback) { + try { + return ChatFormatting.valueOf(name.toUpperCase()); + } catch (IllegalArgumentException e) { + return fallback; + } + } +} diff --git a/services/discord-rules/1.20.1/src/main/java/com/discordrules/RulesCache.java b/services/discord-rules/1.20.1/src/main/java/com/discordrules/RulesCache.java new file mode 100755 index 0000000..6ffab0b --- /dev/null +++ b/services/discord-rules/1.20.1/src/main/java/com/discordrules/RulesCache.java @@ -0,0 +1,26 @@ +package com.discordrules; + +import java.time.Instant; + +public class RulesCache { + private static String cachedRules = null; + private static Instant lastFetchTime = Instant.MIN; + private static final String FALLBACK_RULES = + "Server Rules\n1. Be respectful to all players.\n2. No griefing or cheating.\n3. Follow staff instructions.\nPlease check Discord for the full rules list."; + + public static boolean isCacheValid() { + if (cachedRules == null) return false; + long cacheMinutes = ServerRulesConfig.CACHE_MINUTES.get(); + return Instant.now().isBefore(lastFetchTime.plusSeconds(cacheMinutes * 60)); + } + + public static void updateCache(String newRules) { + if (newRules != null && !newRules.trim().isEmpty()) { + cachedRules = newRules; + lastFetchTime = Instant.now(); + } + } + + public static String getRules() { return cachedRules != null ? cachedRules : FALLBACK_RULES; } + public static void invalidate() { cachedRules = null; lastFetchTime = Instant.MIN; } +} diff --git a/services/discord-rules/1.20.1/src/main/java/com/discordrules/RulesCommand.java b/services/discord-rules/1.20.1/src/main/java/com/discordrules/RulesCommand.java new file mode 100755 index 0000000..02907dc --- /dev/null +++ b/services/discord-rules/1.20.1/src/main/java/com/discordrules/RulesCommand.java @@ -0,0 +1,44 @@ +package com.discordrules; + +import com.mojang.brigadier.CommandDispatcher; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.server.level.ServerPlayer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class RulesCommand { + private static final Logger LOGGER = LoggerFactory.getLogger(RulesCommand.class); + + public static void register(CommandDispatcher dispatcher) { + dispatcher.register(Commands.literal("rules").executes(context -> { + CommandSourceStack source = context.getSource(); + if (source.getEntity() == null || !(source.getEntity() instanceof ServerPlayer)) { + source.sendSystemMessage(DiscordFormatter.formatRules(RulesCache.getRules())); + return 1; + } + ServerPlayer player = (ServerPlayer) source.getEntity(); + if (!CooldownManager.checkAndUpdateCooldown(player)) return 0; + if (RulesCache.isCacheValid()) { + player.sendSystemMessage(DiscordFormatter.formatRules(RulesCache.getRules())); + return 1; + } + player.sendSystemMessage(Component.literal("\u00A77Fetching latest rules...")); + DiscordFetcher.fetchRulesAsync().thenAccept(fetchedRules -> { + String rulesText; + if (fetchedRules != null) { + RulesCache.updateCache(fetchedRules); + rulesText = fetchedRules; + } else { + LOGGER.warn("Discord fetch failed. Falling back to cached rules for {}", player.getName().getString()); + rulesText = RulesCache.getRules(); + } + MutableComponent formattedRules = DiscordFormatter.formatRules(rulesText); + source.getServer().execute(() -> player.sendSystemMessage(formattedRules)); + }); + return 1; + })); + } +} diff --git a/services/discord-rules/1.20.1/src/main/java/com/discordrules/ServerRules.java b/services/discord-rules/1.20.1/src/main/java/com/discordrules/ServerRules.java new file mode 100755 index 0000000..ce1f082 --- /dev/null +++ b/services/discord-rules/1.20.1/src/main/java/com/discordrules/ServerRules.java @@ -0,0 +1,40 @@ +package com.discordrules; + +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.RegisterCommandsEvent; +import net.minecraftforge.event.entity.player.PlayerEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.ModLoadingContext; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.config.ModConfig; +import net.minecraftforge.fml.event.config.ModConfigEvent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Mod("discordrules") +public class ServerRules { + private static final Logger LOGGER = LoggerFactory.getLogger(ServerRules.class); + + public ServerRules() { + ModLoadingContext.get().registerConfig(ModConfig.Type.SERVER, ServerRulesConfig.SPEC); + MinecraftForge.EVENT_BUS.register(this); + LOGGER.info("Discord Rules Mod Initialized."); + } + + @SubscribeEvent + public void onRegisterCommands(RegisterCommandsEvent event) { + RulesCommand.register(event.getDispatcher()); + LOGGER.info("Registered /rules command."); + } + + @SubscribeEvent + public void onPlayerLogout(PlayerEvent.PlayerLoggedOutEvent event) { + CooldownManager.removePlayer(event.getEntity().getUUID()); + } + + @SubscribeEvent + public void onConfigReload(ModConfigEvent.Reloading event) { + LOGGER.info("Rules configuration reloaded! Invalidating cache."); + RulesCache.invalidate(); + } +} diff --git a/services/discord-rules/1.20.1/src/main/java/com/discordrules/ServerRulesConfig.java b/services/discord-rules/1.20.1/src/main/java/com/discordrules/ServerRulesConfig.java new file mode 100755 index 0000000..62b38fb --- /dev/null +++ b/services/discord-rules/1.20.1/src/main/java/com/discordrules/ServerRulesConfig.java @@ -0,0 +1,40 @@ +package com.discordrules; + +import net.minecraftforge.common.ForgeConfigSpec; +import org.apache.commons.lang3.StringUtils; + +public class ServerRulesConfig { + public static final ForgeConfigSpec SPEC; + public static final ForgeConfigSpec.ConfigValue BOT_TOKEN; + public static final ForgeConfigSpec.ConfigValue CHANNEL_ID; + public static final ForgeConfigSpec.ConfigValue MESSAGE_ID; + public static final ForgeConfigSpec.IntValue COOLDOWN_SECONDS; + public static final ForgeConfigSpec.IntValue CACHE_MINUTES; + public static final ForgeConfigSpec.ConfigValue HEADER_COLOR; + public static final ForgeConfigSpec.ConfigValue BODY_COLOR; + public static final ForgeConfigSpec.BooleanValue STRIP_EMOJIS; + + static { + ForgeConfigSpec.Builder builder = new ForgeConfigSpec.Builder(); + builder.push("discord"); + BOT_TOKEN = builder.comment("Discord Bot Token").define("bot_token", "YOUR_TOKEN_HERE"); + CHANNEL_ID = builder.comment("Discord Channel ID").define("channel_id", "1234567890123456789"); + MESSAGE_ID = builder.comment("Discord Message ID").define("message_id", "1234567890123456789"); + builder.pop(); + builder.push("display"); + HEADER_COLOR = builder.comment("Header color (bold lines). Valid values: BLACK, DARK_BLUE, DARK_GREEN, DARK_AQUA, DARK_RED, DARK_PURPLE, GOLD, GRAY, DARK_GRAY, BLUE, GREEN, AQUA, RED, LIGHT_PURPLE, YELLOW, WHITE").define("header_color", "GOLD"); + BODY_COLOR = builder.comment("Body color (regular lines and bullet points)").define("body_color", "YELLOW"); + STRIP_EMOJIS = builder.comment("Strip emojis that Minecraft can't render (recommended: true)").define("strip_emojis", true); + builder.pop(); + builder.push("performance"); + COOLDOWN_SECONDS = builder.comment("Per-player cooldown in seconds").defineInRange("cooldown_seconds", 60, 0, 3600); + CACHE_MINUTES = builder.comment("Cache duration in minutes").defineInRange("cache_minutes", 30, 1, 1440); + builder.pop(); + SPEC = builder.build(); + } + + public static boolean isMessageIdValid() { + String id = MESSAGE_ID.get(); + return StringUtils.isNotBlank(id) && id.matches("^\\d{17,20}$"); + } +} diff --git a/services/discord-rules/1.20.1/src/main/resources/META-INF/mods.toml b/services/discord-rules/1.20.1/src/main/resources/META-INF/mods.toml new file mode 100755 index 0000000..3cec631 --- /dev/null +++ b/services/discord-rules/1.20.1/src/main/resources/META-INF/mods.toml @@ -0,0 +1,27 @@ +modLoader="javafml" +loaderVersion="[47,)" +license="MIT" + +[[mods]] +modId="discordrules" +version="${file.jarVersion}" +displayName="Discord Rules" +authors="FirefrostGaming" +description=''' +Displays server rules in-game via /rules, fetched live from a Discord message. +Fully configurable colors, cooldowns, and caching. +''' + +[[dependencies.discordrules]] +modId="forge" +mandatory=true +versionRange="[47,)" +ordering="NONE" +side="SERVER" + +[[dependencies.discordrules]] +modId="minecraft" +mandatory=true +versionRange="[1.20.1,1.21)" +ordering="NONE" +side="SERVER" diff --git a/services/discord-rules/1.21.1/.gradle/8.8/checksums/checksums.lock b/services/discord-rules/1.21.1/.gradle/8.8/checksums/checksums.lock new file mode 100644 index 0000000..0828389 Binary files /dev/null and b/services/discord-rules/1.21.1/.gradle/8.8/checksums/checksums.lock differ diff --git a/services/discord-rules/1.21.1/.gradle/8.8/checksums/md5-checksums.bin b/services/discord-rules/1.21.1/.gradle/8.8/checksums/md5-checksums.bin new file mode 100644 index 0000000..5e601ff Binary files /dev/null and b/services/discord-rules/1.21.1/.gradle/8.8/checksums/md5-checksums.bin differ diff --git a/services/discord-rules/1.21.1/.gradle/8.8/checksums/sha1-checksums.bin b/services/discord-rules/1.21.1/.gradle/8.8/checksums/sha1-checksums.bin new file mode 100644 index 0000000..6440322 Binary files /dev/null and b/services/discord-rules/1.21.1/.gradle/8.8/checksums/sha1-checksums.bin differ diff --git a/services/discord-rules/1.21.1/.gradle/8.8/dependencies-accessors/gc.properties b/services/discord-rules/1.21.1/.gradle/8.8/dependencies-accessors/gc.properties new file mode 100644 index 0000000..e69de29 diff --git a/services/discord-rules/1.21.1/.gradle/8.8/executionHistory/executionHistory.bin b/services/discord-rules/1.21.1/.gradle/8.8/executionHistory/executionHistory.bin new file mode 100644 index 0000000..5122ce4 Binary files /dev/null and b/services/discord-rules/1.21.1/.gradle/8.8/executionHistory/executionHistory.bin differ diff --git a/services/discord-rules/1.21.1/.gradle/8.8/executionHistory/executionHistory.lock b/services/discord-rules/1.21.1/.gradle/8.8/executionHistory/executionHistory.lock new file mode 100644 index 0000000..27c1f70 Binary files /dev/null and b/services/discord-rules/1.21.1/.gradle/8.8/executionHistory/executionHistory.lock differ diff --git a/services/discord-rules/1.21.1/.gradle/8.8/fileChanges/last-build.bin b/services/discord-rules/1.21.1/.gradle/8.8/fileChanges/last-build.bin new file mode 100644 index 0000000..f76dd23 Binary files /dev/null and b/services/discord-rules/1.21.1/.gradle/8.8/fileChanges/last-build.bin differ diff --git a/services/discord-rules/1.21.1/.gradle/8.8/fileHashes/fileHashes.bin b/services/discord-rules/1.21.1/.gradle/8.8/fileHashes/fileHashes.bin new file mode 100644 index 0000000..4d9002b Binary files /dev/null and b/services/discord-rules/1.21.1/.gradle/8.8/fileHashes/fileHashes.bin differ diff --git a/services/discord-rules/1.21.1/.gradle/8.8/fileHashes/fileHashes.lock b/services/discord-rules/1.21.1/.gradle/8.8/fileHashes/fileHashes.lock new file mode 100644 index 0000000..a68c06d Binary files /dev/null and b/services/discord-rules/1.21.1/.gradle/8.8/fileHashes/fileHashes.lock differ diff --git a/services/discord-rules/1.21.1/.gradle/8.8/gc.properties b/services/discord-rules/1.21.1/.gradle/8.8/gc.properties new file mode 100644 index 0000000..e69de29 diff --git a/services/discord-rules/1.21.1/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/services/discord-rules/1.21.1/.gradle/buildOutputCleanup/buildOutputCleanup.lock new file mode 100644 index 0000000..2322949 Binary files /dev/null and b/services/discord-rules/1.21.1/.gradle/buildOutputCleanup/buildOutputCleanup.lock differ diff --git a/services/discord-rules/1.21.1/.gradle/buildOutputCleanup/cache.properties b/services/discord-rules/1.21.1/.gradle/buildOutputCleanup/cache.properties new file mode 100644 index 0000000..22cb890 --- /dev/null +++ b/services/discord-rules/1.21.1/.gradle/buildOutputCleanup/cache.properties @@ -0,0 +1,2 @@ +#Sun Apr 12 15:23:51 CDT 2026 +gradle.version=8.8 diff --git a/services/discord-rules/1.21.1/.gradle/buildOutputCleanup/outputFiles.bin b/services/discord-rules/1.21.1/.gradle/buildOutputCleanup/outputFiles.bin new file mode 100644 index 0000000..31dbde6 Binary files /dev/null and b/services/discord-rules/1.21.1/.gradle/buildOutputCleanup/outputFiles.bin differ diff --git a/services/discord-rules/1.21.1/.gradle/file-system.probe b/services/discord-rules/1.21.1/.gradle/file-system.probe new file mode 100644 index 0000000..bb04347 Binary files /dev/null and b/services/discord-rules/1.21.1/.gradle/file-system.probe differ diff --git a/services/discord-rules/1.21.1/.gradle/vcs-1/gc.properties b/services/discord-rules/1.21.1/.gradle/vcs-1/gc.properties new file mode 100644 index 0000000..e69de29 diff --git a/services/discord-rules/1.21.1/build.gradle b/services/discord-rules/1.21.1/build.gradle new file mode 100644 index 0000000..a0334f7 --- /dev/null +++ b/services/discord-rules/1.21.1/build.gradle @@ -0,0 +1,35 @@ +plugins { + id 'java-library' + id 'net.neoforged.moddev' version '2.0.141' +} + +version = mod_version +group = mod_group_id + +repositories { + mavenCentral() +} + +dependencies { +} + +neoForge { + version = neo_version + + runs { + server { + server() + } + } + + mods { + "${mod_id}" { + sourceSet sourceSets.main + } + } +} + +tasks.withType(JavaCompile).configureEach { + options.encoding = 'UTF-8' + options.release.set(21) +} diff --git a/services/discord-rules/1.21.1/gradle.properties b/services/discord-rules/1.21.1/gradle.properties new file mode 100644 index 0000000..ce7412d --- /dev/null +++ b/services/discord-rules/1.21.1/gradle.properties @@ -0,0 +1,11 @@ +org.gradle.jvmargs=-Xmx1G -XX:+UseG1GC +net.neoforged.moddev.decompiler.maxMemory=2G +org.gradle.daemon=false + +minecraft_version=1.21.1 +neo_version=21.1.65 + +mod_id=discordrules +mod_name=Discord Rules +mod_version=1.0.0 +mod_group_id=com.discordrules diff --git a/services/discord-rules/1.21.1/gradle/wrapper/gradle-wrapper.jar b/services/discord-rules/1.21.1/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..e644113 Binary files /dev/null and b/services/discord-rules/1.21.1/gradle/wrapper/gradle-wrapper.jar differ diff --git a/services/discord-rules/1.21.1/gradle/wrapper/gradle-wrapper.properties b/services/discord-rules/1.21.1/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..a441313 --- /dev/null +++ b/services/discord-rules/1.21.1/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/services/discord-rules/1.21.1/gradlew b/services/discord-rules/1.21.1/gradlew new file mode 100755 index 0000000..b740cf1 --- /dev/null +++ b/services/discord-rules/1.21.1/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +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 + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +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 ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +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 + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + 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 +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +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=SC2039,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=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +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" || "$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 + 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 + # 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 +fi + + +# 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"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +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. +# + +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/services/discord-rules/1.21.1/gradlew.bat b/services/discord-rules/1.21.1/gradlew.bat new file mode 100644 index 0000000..7101f8e --- /dev/null +++ b/services/discord-rules/1.21.1/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +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! +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 + +:omega diff --git a/services/discord-rules/1.21.1/settings.gradle b/services/discord-rules/1.21.1/settings.gradle new file mode 100644 index 0000000..6035e27 --- /dev/null +++ b/services/discord-rules/1.21.1/settings.gradle @@ -0,0 +1,12 @@ +pluginManagement { + repositories { + gradlePluginPortal() + maven { url = 'https://maven.neoforged.net/releases' } + } +} + +plugins { + id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0' +} + +rootProject.name = 'discordrules' diff --git a/services/discord-rules/1.21.1/src/main/java/com/discordrules/CooldownManager.java b/services/discord-rules/1.21.1/src/main/java/com/discordrules/CooldownManager.java new file mode 100644 index 0000000..be1ba3a --- /dev/null +++ b/services/discord-rules/1.21.1/src/main/java/com/discordrules/CooldownManager.java @@ -0,0 +1,31 @@ +package com.discordrules; + +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import java.time.Duration; +import java.time.Instant; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class CooldownManager { + private static final ConcurrentHashMap COOLDOWNS = new ConcurrentHashMap<>(); + + public static boolean checkAndUpdateCooldown(ServerPlayer player) { + UUID playerId = player.getUUID(); + Instant now = Instant.now(); + int cooldownSeconds = ServerRulesConfig.COOLDOWN_SECONDS.get(); + Instant lastUsed = COOLDOWNS.get(playerId); + if (lastUsed != null) { + long secondsSinceLastUse = Duration.between(lastUsed, now).getSeconds(); + if (secondsSinceLastUse < cooldownSeconds) { + long remaining = cooldownSeconds - secondsSinceLastUse; + player.sendSystemMessage(Component.literal("\u00A7cPlease wait " + remaining + " seconds before checking the rules again.")); + return false; + } + } + COOLDOWNS.put(playerId, now); + return true; + } + + public static void removePlayer(UUID playerId) { COOLDOWNS.remove(playerId); } +} diff --git a/services/discord-rules/1.21.1/src/main/java/com/discordrules/DiscordFetcher.java b/services/discord-rules/1.21.1/src/main/java/com/discordrules/DiscordFetcher.java new file mode 100644 index 0000000..9f35c3a --- /dev/null +++ b/services/discord-rules/1.21.1/src/main/java/com/discordrules/DiscordFetcher.java @@ -0,0 +1,45 @@ +package com.discordrules; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; +import java.util.concurrent.CompletableFuture; + +public class DiscordFetcher { + private static final Logger LOGGER = LoggerFactory.getLogger(DiscordFetcher.class); + private static final HttpClient CLIENT = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build(); + + public static CompletableFuture fetchRulesAsync() { + if (!ServerRulesConfig.isMessageIdValid()) { + LOGGER.error("Invalid Discord Message ID in config."); + return CompletableFuture.completedFuture(null); + } + String token = ServerRulesConfig.BOT_TOKEN.get(); + String channelId = ServerRulesConfig.CHANNEL_ID.get(); + String messageId = ServerRulesConfig.MESSAGE_ID.get(); + URI uri = URI.create("https://discord.com/api/v10/channels/" + channelId + "/messages/" + messageId); + HttpRequest request = HttpRequest.newBuilder().uri(uri) + .header("Authorization", "Bot " + token) + .header("Accept", "application/json").GET().build(); + return CLIENT.sendAsync(request, HttpResponse.BodyHandlers.ofString()) + .thenApply(response -> { + if (response.statusCode() == 200) { + JsonObject json = JsonParser.parseString(response.body()).getAsJsonObject(); + return json.get("content").getAsString(); + } else { + LOGGER.error("Discord API returned status: {}", response.statusCode()); + return null; + } + }) + .exceptionally(ex -> { + LOGGER.error("Network error while fetching Discord rules", ex); + return null; + }); + } +} diff --git a/services/discord-rules/1.21.1/src/main/java/com/discordrules/DiscordFormatter.java b/services/discord-rules/1.21.1/src/main/java/com/discordrules/DiscordFormatter.java new file mode 100644 index 0000000..d0ace99 --- /dev/null +++ b/services/discord-rules/1.21.1/src/main/java/com/discordrules/DiscordFormatter.java @@ -0,0 +1,42 @@ +package com.discordrules; + +import net.minecraft.ChatFormatting; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; + +public class DiscordFormatter { + public static MutableComponent formatRules(String rawDiscordText) { + String processedText = ServerRulesConfig.STRIP_EMOJIS.get() + ? stripEmojis(rawDiscordText) : rawDiscordText; + ChatFormatting headerColor = parseColor(ServerRulesConfig.HEADER_COLOR.get(), ChatFormatting.GOLD); + ChatFormatting bodyColor = parseColor(ServerRulesConfig.BODY_COLOR.get(), ChatFormatting.YELLOW); + MutableComponent rootComponent = Component.empty(); + String[] lines = processedText.split("\n"); + for (String line : lines) { + MutableComponent lineComponent; + if (line.startsWith("**") && line.endsWith("**")) { + String cleanLine = line.replace("**", ""); + lineComponent = Component.literal(cleanLine).withStyle(headerColor, ChatFormatting.BOLD); + } else if (line.trim().startsWith("-") || line.trim().startsWith("\u2022")) { + lineComponent = Component.literal(" " + line.trim()).withStyle(bodyColor); + } else { + lineComponent = Component.literal(line).withStyle(bodyColor); + } + rootComponent.append(lineComponent).append(Component.literal("\n")); + } + return rootComponent; + } + + private static String stripEmojis(String text) { + if (text == null) return ""; + return text.replaceAll("[\\x{1F300}-\\x{1F9FF}]", ""); + } + + private static ChatFormatting parseColor(String name, ChatFormatting fallback) { + try { + return ChatFormatting.valueOf(name.toUpperCase()); + } catch (IllegalArgumentException e) { + return fallback; + } + } +} diff --git a/services/discord-rules/1.21.1/src/main/java/com/discordrules/RulesCache.java b/services/discord-rules/1.21.1/src/main/java/com/discordrules/RulesCache.java new file mode 100644 index 0000000..6ffab0b --- /dev/null +++ b/services/discord-rules/1.21.1/src/main/java/com/discordrules/RulesCache.java @@ -0,0 +1,26 @@ +package com.discordrules; + +import java.time.Instant; + +public class RulesCache { + private static String cachedRules = null; + private static Instant lastFetchTime = Instant.MIN; + private static final String FALLBACK_RULES = + "Server Rules\n1. Be respectful to all players.\n2. No griefing or cheating.\n3. Follow staff instructions.\nPlease check Discord for the full rules list."; + + public static boolean isCacheValid() { + if (cachedRules == null) return false; + long cacheMinutes = ServerRulesConfig.CACHE_MINUTES.get(); + return Instant.now().isBefore(lastFetchTime.plusSeconds(cacheMinutes * 60)); + } + + public static void updateCache(String newRules) { + if (newRules != null && !newRules.trim().isEmpty()) { + cachedRules = newRules; + lastFetchTime = Instant.now(); + } + } + + public static String getRules() { return cachedRules != null ? cachedRules : FALLBACK_RULES; } + public static void invalidate() { cachedRules = null; lastFetchTime = Instant.MIN; } +} diff --git a/services/discord-rules/1.21.1/src/main/java/com/discordrules/RulesCommand.java b/services/discord-rules/1.21.1/src/main/java/com/discordrules/RulesCommand.java new file mode 100644 index 0000000..997562b --- /dev/null +++ b/services/discord-rules/1.21.1/src/main/java/com/discordrules/RulesCommand.java @@ -0,0 +1,44 @@ +package com.discordrules; + +import com.mojang.brigadier.CommandDispatcher; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.server.level.ServerPlayer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class RulesCommand { + private static final Logger LOGGER = LoggerFactory.getLogger(RulesCommand.class); + + public static void register(CommandDispatcher dispatcher) { + dispatcher.register(Commands.literal("rules").executes(context -> { + CommandSourceStack source = context.getSource(); + if (!source.isPlayer()) { + source.sendSystemMessage(DiscordFormatter.formatRules(RulesCache.getRules())); + return 1; + } + ServerPlayer player = source.getPlayer(); + if (!CooldownManager.checkAndUpdateCooldown(player)) return 0; + if (RulesCache.isCacheValid()) { + player.sendSystemMessage(DiscordFormatter.formatRules(RulesCache.getRules())); + return 1; + } + player.sendSystemMessage(Component.literal("\u00A77Fetching latest rules...")); + DiscordFetcher.fetchRulesAsync().thenAccept(fetchedRules -> { + String rulesText; + if (fetchedRules != null) { + RulesCache.updateCache(fetchedRules); + rulesText = fetchedRules; + } else { + LOGGER.warn("Discord fetch failed. Falling back to cached rules for {}", player.getName().getString()); + rulesText = RulesCache.getRules(); + } + MutableComponent formattedRules = DiscordFormatter.formatRules(rulesText); + source.getServer().execute(() -> player.sendSystemMessage(formattedRules)); + }); + return 1; + })); + } +} diff --git a/services/discord-rules/1.21.1/src/main/java/com/discordrules/ServerRules.java b/services/discord-rules/1.21.1/src/main/java/com/discordrules/ServerRules.java new file mode 100644 index 0000000..a32ae1c --- /dev/null +++ b/services/discord-rules/1.21.1/src/main/java/com/discordrules/ServerRules.java @@ -0,0 +1,41 @@ +package com.discordrules; + +import net.neoforged.bus.api.IEventBus; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.ModContainer; +import net.neoforged.fml.common.Mod; +import net.neoforged.fml.config.ModConfig; +import net.neoforged.fml.event.config.ModConfigEvent; +import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.event.RegisterCommandsEvent; +import net.neoforged.neoforge.event.entity.player.PlayerEvent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Mod("discordrules") +public class ServerRules { + private static final Logger LOGGER = LoggerFactory.getLogger(ServerRules.class); + + public ServerRules(IEventBus modEventBus, ModContainer modContainer) { + modContainer.registerConfig(ModConfig.Type.SERVER, ServerRulesConfig.SPEC); + modEventBus.addListener(this::onConfigReload); + NeoForge.EVENT_BUS.register(this); + LOGGER.info("Discord Rules Mod Initialized."); + } + + @SubscribeEvent + public void onRegisterCommands(RegisterCommandsEvent event) { + RulesCommand.register(event.getDispatcher()); + LOGGER.info("Registered /rules command."); + } + + @SubscribeEvent + public void onPlayerLogout(PlayerEvent.PlayerLoggedOutEvent event) { + CooldownManager.removePlayer(event.getEntity().getUUID()); + } + + private void onConfigReload(ModConfigEvent.Reloading event) { + LOGGER.info("Rules configuration reloaded! Invalidating cache."); + RulesCache.invalidate(); + } +} diff --git a/services/discord-rules/1.21.1/src/main/java/com/discordrules/ServerRulesConfig.java b/services/discord-rules/1.21.1/src/main/java/com/discordrules/ServerRulesConfig.java new file mode 100644 index 0000000..3a270f7 --- /dev/null +++ b/services/discord-rules/1.21.1/src/main/java/com/discordrules/ServerRulesConfig.java @@ -0,0 +1,40 @@ +package com.discordrules; + +import net.neoforged.neoforge.common.ModConfigSpec; +import org.apache.commons.lang3.StringUtils; + +public class ServerRulesConfig { + public static final ModConfigSpec SPEC; + public static final ModConfigSpec.ConfigValue BOT_TOKEN; + public static final ModConfigSpec.ConfigValue CHANNEL_ID; + public static final ModConfigSpec.ConfigValue MESSAGE_ID; + public static final ModConfigSpec.IntValue COOLDOWN_SECONDS; + public static final ModConfigSpec.IntValue CACHE_MINUTES; + public static final ModConfigSpec.ConfigValue HEADER_COLOR; + public static final ModConfigSpec.ConfigValue BODY_COLOR; + public static final ModConfigSpec.BooleanValue STRIP_EMOJIS; + + static { + ModConfigSpec.Builder builder = new ModConfigSpec.Builder(); + builder.push("discord"); + BOT_TOKEN = builder.comment("Discord Bot Token").define("bot_token", "YOUR_TOKEN_HERE"); + CHANNEL_ID = builder.comment("Discord Channel ID").define("channel_id", "1234567890123456789"); + MESSAGE_ID = builder.comment("Discord Message ID").define("message_id", "1234567890123456789"); + builder.pop(); + builder.push("display"); + HEADER_COLOR = builder.comment("Header color (bold lines). Valid values: BLACK, DARK_BLUE, DARK_GREEN, DARK_AQUA, DARK_RED, DARK_PURPLE, GOLD, GRAY, DARK_GRAY, BLUE, GREEN, AQUA, RED, LIGHT_PURPLE, YELLOW, WHITE").define("header_color", "GOLD"); + BODY_COLOR = builder.comment("Body color (regular lines and bullet points)").define("body_color", "YELLOW"); + STRIP_EMOJIS = builder.comment("Strip emojis that Minecraft can't render (recommended: true)").define("strip_emojis", true); + builder.pop(); + builder.push("performance"); + COOLDOWN_SECONDS = builder.comment("Per-player cooldown in seconds").defineInRange("cooldown_seconds", 60, 0, 3600); + CACHE_MINUTES = builder.comment("Cache duration in minutes").defineInRange("cache_minutes", 30, 1, 1440); + builder.pop(); + SPEC = builder.build(); + } + + public static boolean isMessageIdValid() { + String id = MESSAGE_ID.get(); + return StringUtils.isNotBlank(id) && id.matches("^\\d{17,20}$"); + } +} diff --git a/services/discord-rules/1.21.1/src/main/resources/META-INF/neoforge.mods.toml b/services/discord-rules/1.21.1/src/main/resources/META-INF/neoforge.mods.toml new file mode 100644 index 0000000..9e5a148 --- /dev/null +++ b/services/discord-rules/1.21.1/src/main/resources/META-INF/neoforge.mods.toml @@ -0,0 +1,28 @@ +modLoader="javafml" +loaderVersion="[4,)" +license="MIT" +showAsResourcePack=false + +[[mods]] +modId="discordrules" +version="${file.jarVersion}" +displayName="Discord Rules" +authors="FirefrostGaming" +description=''' +Displays server rules in-game via /rules, fetched live from a Discord message. +Fully configurable colors, cooldowns, and caching. +''' + +[[dependencies.discordrules]] +modId="neoforge" +type="required" +versionRange="[21.1,)" +ordering="NONE" +side="SERVER" + +[[dependencies.discordrules]] +modId="minecraft" +type="required" +versionRange="[1.21.1,1.22)" +ordering="NONE" +side="SERVER"