diff --git a/README.md b/README.md index f99f78c..3850a60 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # LiveMotdManager -LiveMotdManager is a multi-platform Minecraft plugin providing a dynamic server list MOTD. It supports Spigot/Paper/Purpur/Folia servers and BungeeCord/Waterfall (1.16+) / Velocity proxies. The plugin changes MOTD based on time, player count, TPS and more. Weather and Discord integrations are included. +LiveMotdManager is a multi-platform Minecraft plugin that keeps your server list message dynamic. +It supports Spigot/Paper/Purpur/Folia servers and BungeeCord/Waterfall (1.16+) or Velocity proxies. +The MOTD can react to time of day, player counts, TPS, real world weather and Discord activity. ## Building @@ -10,19 +12,23 @@ Requirements: Java 17+ and Maven. mvn package ``` -Resulting jars will be in `spigot/target`, `bungee/target` and `velocity/target`. +Resulting jars are placed in `spigot/target`, `bungee/target` and `velocity/target` named like +`livemotdmanager-spigot-1.0.0.jar`. ## Installation 1. Place the jar for your platform into the plugins folder. -2. Start the server/proxy to generate the config file. -3. Edit `config.yml` as needed and run `/motd reload` to apply changes. +2. Start the server/proxy to generate `config.yml`. +3. Edit the configuration and run `/motd reload` to apply changes. ## Commands +Run `/motd help` in game for usage. + - `/motd reload` – reload configuration. - `/motd set ` – set temporary MOTD until restart. - `/motd info` – show active template and debug info. +- `/motd force ` – force a configured template regardless of conditions. ## Configuration @@ -30,11 +36,15 @@ See `config.yml` for an example configuration with multiple templates and integr ## Weather -Uses [open-meteo.com](https://open-meteo.com/) APIs with no key required. +Uses [open-meteo.com](https://open-meteo.com/) APIs with no key required. The plugin performs an +initial API test on startup and logs the result to the console. ## Discord -Optional integration with DiscordSRV. Placeholder `%discord_online%` shows number of connected Discord users. +Optional integration with DiscordSRV. Placeholder `%discord_online%` shows number of connected +Discord users. + +Additional documentation is available in the [wiki](wiki/Home.md). ## License diff --git a/bungee/pom.xml b/bungee/pom.xml index e050137..4407f0a 100644 --- a/bungee/pom.xml +++ b/bungee/pom.xml @@ -3,7 +3,7 @@ com.livemotdmanager livemotdmanager-parent - 1.0-SNAPSHOT + 1.0.0 bungee jar @@ -11,7 +11,7 @@ com.livemotdmanager core - 1.0-SNAPSHOT + 1.0.0 net.md-5 @@ -22,6 +22,7 @@ + livemotdmanager-bungee-${project.version} org.apache.maven.plugins diff --git a/bungee/src/main/java/com/livemotdmanager/bungee/LiveMotdBungee.java b/bungee/src/main/java/com/livemotdmanager/bungee/LiveMotdBungee.java index 1817f4c..a1edc3d 100644 --- a/bungee/src/main/java/com/livemotdmanager/bungee/LiveMotdBungee.java +++ b/bungee/src/main/java/com/livemotdmanager/bungee/LiveMotdBungee.java @@ -107,10 +107,16 @@ public class MotdCommand extends Command { @Override public void execute(CommandSender sender, String[] args) { if (args.length == 0) { - sender.sendMessage(new TextComponent("/motd reload|set|info")); + sender.sendMessage(new TextComponent("/motd help")); return; } switch (args[0].toLowerCase()) { + case "help": + sender.sendMessage(new TextComponent("/motd reload - reload configuration")); + sender.sendMessage(new TextComponent("/motd set - set temporary MOTD")); + sender.sendMessage(new TextComponent("/motd info - show debug info")); + sender.sendMessage(new TextComponent("/motd force - force a template")); + break; case "reload": loadConfig(); sender.sendMessage(new TextComponent("Config reloaded.")); @@ -129,8 +135,21 @@ public void execute(CommandSender sender, String[] args) { sender.sendMessage(new TextComponent("Weather: " + weather.getCachedWeather())); sender.sendMessage(new TextComponent("Discord online: " + discord.getOnlineUsers())); break; + case "force": + if (args.length < 2) { + sender.sendMessage(new TextComponent("Usage: /motd force ")); + break; + } + if (args[1].equalsIgnoreCase("off")) { + manager.clearForcedTemplate(); + sender.sendMessage(new TextComponent("Forced template cleared.")); + } else { + manager.setForcedTemplate(args[1]); + sender.sendMessage(new TextComponent("Forced template set to " + args[1] + ".")); + } + break; default: - sender.sendMessage(new TextComponent("Unknown subcommand.")); + sender.sendMessage(new TextComponent("Unknown subcommand. Use /motd help")); } } } diff --git a/core/pom.xml b/core/pom.xml index 31b3932..9aac95f 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -3,7 +3,7 @@ com.livemotdmanager livemotdmanager-parent - 1.0-SNAPSHOT + 1.0.0 core diff --git a/core/src/main/java/com/livemotdmanager/core/MotdManager.java b/core/src/main/java/com/livemotdmanager/core/MotdManager.java index c727a7f..12ba8a5 100644 --- a/core/src/main/java/com/livemotdmanager/core/MotdManager.java +++ b/core/src/main/java/com/livemotdmanager/core/MotdManager.java @@ -24,6 +24,7 @@ public class MotdManager { private String currentTemplate = ""; private final long cacheIntervalMs; private String temporaryMotd = null; + private String forcedTemplate = null; public MotdManager(MotdConfig config, WeatherService weather, DiscordService discord, ServerInfoProvider infoProvider) { this.config = config; @@ -48,6 +49,15 @@ public Component provide() { if (temporaryMotd != null) { return MiniMessage.miniMessage().deserialize(applyPlaceholders(temporaryMotd, ctx)); } + if (forcedTemplate != null) { + for (MotdConfig.MotdRule rule : config.motd) { + if (rule.when.equalsIgnoreCase(forcedTemplate)) { + currentTemplate = rule.when; + String txt = applyPlaceholders(rule.text, ctx); + return MiniMessage.miniMessage().deserialize(txt); + } + } + } if (now - lastUpdate > cacheIntervalMs) { rebuild(ctx); lastUpdate = now; @@ -78,6 +88,14 @@ public String applyPlaceholders(String text, MotdContext ctx) { return out; } + public void setForcedTemplate(String when) { + this.forcedTemplate = when; + } + + public void clearForcedTemplate() { + this.forcedTemplate = null; + } + private String defaultPlaceholders(String text, MotdContext ctx) { String out = text; out = out.replace("%online%", Integer.toString(ctx.online)); diff --git a/core/src/main/java/com/livemotdmanager/core/WeatherService.java b/core/src/main/java/com/livemotdmanager/core/WeatherService.java index 200352a..2d2c3f5 100644 --- a/core/src/main/java/com/livemotdmanager/core/WeatherService.java +++ b/core/src/main/java/com/livemotdmanager/core/WeatherService.java @@ -4,11 +4,12 @@ import com.google.gson.JsonParser; import java.net.URI; +import java.net.URLEncoder; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; import java.time.Duration; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @@ -32,7 +33,10 @@ public WeatherService(MotdConfig.WeatherSettings settings) { public void start() { if (!enabled) return; - scheduler.scheduleAtFixedRate(this::update, 0, updateIntervalMinutes, TimeUnit.MINUTES); + // Initial fetch for startup verification + update(); + System.out.println("[LiveMotdManager] Weather API test: " + (cached.isEmpty() ? "unavailable" : cached)); + scheduler.scheduleAtFixedRate(this::update, updateIntervalMinutes, updateIntervalMinutes, TimeUnit.MINUTES); } public void stop() { @@ -46,8 +50,9 @@ public String getCachedWeather() { private void update() { if (!enabled || city == null || city.isEmpty()) return; try { - // Geocode city - String geocodeUrl = String.format("https://geocoding-api.open-meteo.com/v1/search?count=1&name=%s", city); + // Geocode city (encode to handle spaces and special characters) + String encodedCity = URLEncoder.encode(city, StandardCharsets.UTF_8); + String geocodeUrl = String.format("https://geocoding-api.open-meteo.com/v1/search?count=1&name=%s", encodedCity); HttpRequest geoReq = HttpRequest.newBuilder().uri(URI.create(geocodeUrl)).timeout(Duration.ofSeconds(10)).build(); HttpResponse geoResp = http.send(geoReq, HttpResponse.BodyHandlers.ofString()); JsonObject geoJson = JsonParser.parseString(geoResp.body()).getAsJsonObject(); diff --git a/pom.xml b/pom.xml index d77c999..bee5bb1 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 com.livemotdmanager livemotdmanager-parent - 1.0-SNAPSHOT + 1.0.0 pom core diff --git a/spigot/pom.xml b/spigot/pom.xml index 72e46b3..94aab69 100644 --- a/spigot/pom.xml +++ b/spigot/pom.xml @@ -3,7 +3,7 @@ com.livemotdmanager livemotdmanager-parent - 1.0-SNAPSHOT + 1.0.0 spigot jar @@ -11,7 +11,7 @@ com.livemotdmanager core - 1.0-SNAPSHOT + 1.0.0 net.kyori @@ -26,6 +26,7 @@ + livemotdmanager-spigot-${project.version} org.apache.maven.plugins diff --git a/spigot/src/main/java/com/livemotdmanager/spigot/LiveMotdSpigot.java b/spigot/src/main/java/com/livemotdmanager/spigot/LiveMotdSpigot.java index f69dcc1..85dbabb 100644 --- a/spigot/src/main/java/com/livemotdmanager/spigot/LiveMotdSpigot.java +++ b/spigot/src/main/java/com/livemotdmanager/spigot/LiveMotdSpigot.java @@ -116,10 +116,16 @@ public double tps() { @Override public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { if (args.length == 0) { - sender.sendMessage("/motd reload|set|info"); + sender.sendMessage("/motd help"); return true; } switch (args[0].toLowerCase()) { + case "help": + sender.sendMessage("/motd reload - reload configuration"); + sender.sendMessage("/motd set - set temporary MOTD"); + sender.sendMessage("/motd info - show debug info"); + sender.sendMessage("/motd force - force a template"); + break; case "reload": loadConfig(); sender.sendMessage("MOTD config reloaded."); @@ -138,15 +144,28 @@ public boolean onCommand(CommandSender sender, Command command, String label, St sender.sendMessage("Weather: " + weather.getCachedWeather()); sender.sendMessage("Discord online: " + discord.getOnlineUsers()); break; + case "force": + if (args.length < 2) { + sender.sendMessage("Usage: /motd force "); + break; + } + if (args[1].equalsIgnoreCase("off")) { + manager.clearForcedTemplate(); + sender.sendMessage("Forced template cleared."); + } else { + manager.setForcedTemplate(args[1]); + sender.sendMessage("Forced template set to " + args[1] + "."); + } + break; default: - sender.sendMessage("Unknown subcommand."); + sender.sendMessage("Unknown subcommand. Use /motd help"); } return true; } @Override public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { - if (args.length == 1) return Arrays.asList("reload", "set", "info"); + if (args.length == 1) return Arrays.asList("help", "reload", "set", "info", "force"); return List.of(); } } diff --git a/velocity/pom.xml b/velocity/pom.xml index 9d5743a..1a77831 100644 --- a/velocity/pom.xml +++ b/velocity/pom.xml @@ -3,14 +3,14 @@ com.livemotdmanager livemotdmanager-parent - 1.0-SNAPSHOT + 1.0.0 velocity com.livemotdmanager core - 1.0-SNAPSHOT + 1.0.0 com.velocitypowered @@ -20,6 +20,7 @@ + livemotdmanager-velocity-${project.version} org.apache.maven.plugins diff --git a/velocity/src/main/java/com/livemotdmanager/velocity/LiveMotdVelocity.java b/velocity/src/main/java/com/livemotdmanager/velocity/LiveMotdVelocity.java index 162cb9b..242b40c 100644 --- a/velocity/src/main/java/com/livemotdmanager/velocity/LiveMotdVelocity.java +++ b/velocity/src/main/java/com/livemotdmanager/velocity/LiveMotdVelocity.java @@ -100,10 +100,16 @@ public class MotdCommand implements SimpleCommand { public void execute(Invocation invocation) { String[] args = invocation.arguments(); if (args.length == 0) { - invocation.source().sendMessage(Component.text("/motd reload|set|info")); + invocation.source().sendMessage(Component.text("/motd help")); return; } switch (args[0].toLowerCase()) { + case "help": + invocation.source().sendMessage(Component.text("/motd reload - reload configuration")); + invocation.source().sendMessage(Component.text("/motd set - set temporary MOTD")); + invocation.source().sendMessage(Component.text("/motd info - show debug info")); + invocation.source().sendMessage(Component.text("/motd force - force a template")); + break; case "reload": loadConfig(); invocation.source().sendMessage(Component.text("Config reloaded.")); @@ -122,8 +128,21 @@ public void execute(Invocation invocation) { invocation.source().sendMessage(Component.text("Weather: " + weather.getCachedWeather())); invocation.source().sendMessage(Component.text("Discord online: " + discord.getOnlineUsers())); break; + case "force": + if (args.length < 2) { + invocation.source().sendMessage(Component.text("Usage: /motd force ")); + break; + } + if (args[1].equalsIgnoreCase("off")) { + manager.clearForcedTemplate(); + invocation.source().sendMessage(Component.text("Forced template cleared.")); + } else { + manager.setForcedTemplate(args[1]); + invocation.source().sendMessage(Component.text("Forced template set to " + args[1] + ".")); + } + break; default: - invocation.source().sendMessage(Component.text("Unknown subcommand.")); + invocation.source().sendMessage(Component.text("Unknown subcommand. Use /motd help")); } } } diff --git a/wiki/Home.md b/wiki/Home.md new file mode 100644 index 0000000..07e7893 --- /dev/null +++ b/wiki/Home.md @@ -0,0 +1,39 @@ +# LiveMotdManager Wiki + +Welcome to the LiveMotdManager wiki. This documentation expands on configuration and usage. + +## Features + +- Dynamic MOTD templates based on time, player count and TPS +- Real world weather integration via [open-meteo.com](https://open-meteo.com/) +- DiscordSRV integration +- Works on Spigot/Paper/Folia servers and BungeeCord/Waterfall/Velocity proxies + +## Configuration + +See the included `config.yml` for a starting point. Each `motd` entry contains a `when` rule +and the MiniMessage formatted `text` to display. + +### Weather + +Set `weather.enable` to `true` and specify `city`. Use `%weather_city%` or `%weather_%` +placeholders in your templates. + +### Discord + +If DiscordSRV is installed you can show online Discord users with `%discord_online%`. + +## Commands + +Run `/motd help` in game for the complete list. + +- `/motd reload` – reload configuration +- `/motd set ` – set a temporary MOTD until restart +- `/motd info` – show debug information +- `/motd force ` – force a configured template + +## Building + +Run `mvn package` to build. Jars are created in each module's `target` directory with names like +`livemotdmanager-spigot-1.0.0.jar`. +