diff --git a/src/main/java/net/viper/status/FileDownloader.java b/src/main/java/net/viper/status/FileDownloader.java deleted file mode 100644 index da566c8..0000000 --- a/src/main/java/net/viper/status/FileDownloader.java +++ /dev/null @@ -1,42 +0,0 @@ -package net.viper.status; - -import net.md_5.bungee.api.plugin.Plugin; - -import java.io.BufferedInputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.net.URL; -import java.util.function.Consumer; - -public class FileDownloader { - private final Plugin plugin; - - public FileDownloader(Plugin plugin) { this.plugin = plugin; } - - public void downloadFile(String urlString, File destination, Runnable onSuccess) { - plugin.getProxy().getScheduler().runAsync(plugin, () -> { - BufferedInputStream bufferedInputStream = null; - FileOutputStream fileOutputStream = null; - try { - URL url = new URL(urlString); - bufferedInputStream = new BufferedInputStream(url.openStream()); - fileOutputStream = new FileOutputStream(destination); - byte[] buffer = new byte[1024]; - int count; - while ((count = bufferedInputStream.read(buffer, 0, 1024)) != -1) { - fileOutputStream.write(buffer, 0, count); - } - fileOutputStream.close(); - bufferedInputStream.close(); - plugin.getProxy().getScheduler().schedule(plugin, onSuccess, 1, java.util.concurrent.TimeUnit.MILLISECONDS); - } catch (Throwable e) { - plugin.getLogger().warning("Download fehlgeschlagen: " + e.getMessage()); - if (destination.exists()) destination.delete(); - } finally { - if (fileOutputStream != null) try { fileOutputStream.close(); } catch (IOException ignored) {} - if (bufferedInputStream != null) try { bufferedInputStream.close(); } catch (IOException ignored) {} - } - }); - } -} \ No newline at end of file diff --git a/src/main/java/net/viper/status/StatusAPI.java b/src/main/java/net/viper/status/StatusAPI.java index e2a0833..5913cba 100644 --- a/src/main/java/net/viper/status/StatusAPI.java +++ b/src/main/java/net/viper/status/StatusAPI.java @@ -1,8 +1,14 @@ package net.viper.status; import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.chat.ClickEvent; +import net.md_5.bungee.api.chat.ComponentBuilder; +import net.md_5.bungee.api.chat.HoverEvent; +import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.config.ListenerInfo; import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.event.PostLoginEvent; +import net.md_5.bungee.api.plugin.Listener; import net.md_5.bungee.api.plugin.Plugin; import net.viper.status.module.ModuleManager; import net.viper.status.stats.PlayerStats; @@ -12,15 +18,15 @@ import net.viper.status.modules.globalchat.GlobalChatModule; import net.viper.status.modules.navigation.NavigationModule; import net.viper.status.modules.commandblocker.CommandBlockerModule; import net.viper.status.modules.broadcast.BroadcastModule; +import net.viper.status.modules.AutoMessage.AutoMessageModule; +import net.viper.status.modules.customcommands.CustomCommandModule; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; -import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.StringReader; import java.net.ServerSocket; @@ -29,24 +35,31 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.*; import java.util.concurrent.TimeUnit; +import net.md_5.bungee.event.EventHandler; /** * StatusAPI - zentraler Bungee HTTP-Status- und Broadcast-Endpunkt */ -public class StatusAPI extends Plugin implements Runnable { +public class StatusAPI extends Plugin implements Runnable, Listener { private Thread thread; private int port = 9191; private ModuleManager moduleManager; - private UpdateChecker updateChecker; - private FileDownloader fileDownloader; private Properties verifyProperties; + // Speicher für Updates beim Join + private boolean updatePending = false; + private String latestVersionString = ""; + private final Set informedAdmins = new HashSet<>(); + @Override public void onEnable() { getLogger().info("StatusAPI Core wird initialisiert..."); + // Event Listener registrieren + getProxy().getPluginManager().registerListener(this, this); + if (!getDataFolder().exists()) { getDataFolder().mkdirs(); } @@ -63,6 +76,16 @@ public class StatusAPI extends Plugin implements Runnable { moduleManager.registerModule(new NavigationModule()); moduleManager.registerModule(new BroadcastModule()); moduleManager.registerModule(new CommandBlockerModule()); + moduleManager.registerModule(new AutoMessageModule()); + + // CustomCommandModule nur laden, wenn aktiviert + boolean customCommandsEnabled = Boolean.parseBoolean(getVerifyProperties().getProperty("customcommands.enabled", "false")); + if (customCommandsEnabled) { + moduleManager.registerModule(new CustomCommandModule()); // Korrigiert: Kein Argument im Konstruktor + getLogger().info("CustomCommandModule aktiviert."); + } else { + getLogger().info("CustomCommandModule deaktiviert (siehe verify.properties)."); + } moduleManager.enableAll(this); @@ -71,22 +94,114 @@ public class StatusAPI extends Plugin implements Runnable { thread = new Thread(this, "StatusAPI-HTTP-Server"); thread.start(); - // Update System - String currentVersion = getDescription() != null ? getDescription().getVersion() : "0.0.0"; - updateChecker = new UpdateChecker(this, currentVersion, 6); - fileDownloader = new FileDownloader(this); + // Einfacher Update-Check (Nur Benachrichtigung für Admins beim Start oder beim Join) + checkForUpdatesAndNotify(); + } - File pluginFile = getFile(); - File backupFile = new File(pluginFile.getParentFile(), "StatusAPI.jar.bak"); - - if (backupFile.exists()) { - ProxyServer.getInstance().getScheduler().schedule(this, () -> { - if (backupFile.exists()) backupFile.delete(); - },1, TimeUnit.MINUTES); + @EventHandler + public void onPlayerJoin(PostLoginEvent event) { + ProxiedPlayer player = event.getPlayer(); + + // Wir prüfen hier nur, ob ein Update aussteht. Der Rest wird im Scheduler verzögert geprüft. + if (updatePending && isAdmin(player)) { + // Verzögerung um 2 Sekunden + getProxy().getScheduler().schedule(this, () -> { + // Prüfen, ob Spieler noch online ist und noch nicht informiert wurde + if (player.isConnected() && !informedAdmins.contains(player.getUniqueId()) && isAdmin(player)) { + sendStyledUpdateMessage(player); + informedAdmins.add(player.getUniqueId()); + getLogger().info("Admin " + player.getName() + " wurde über das neue Update informiert."); + } + }, 2, TimeUnit.SECONDS); } + } - checkAndMaybeUpdate(); - ProxyServer.getInstance().getScheduler().schedule(this, this::checkAndMaybeUpdate, 6, 6, TimeUnit.HOURS); + /** + * Sendet eine formatierte Nachricht mit klickbarem Link. + */ + private void sendStyledUpdateMessage(ProxiedPlayer player) { + String currentVersion = getDescription() != null ? getDescription().getVersion() : "Unbekannt"; + String downloadUrl = "https://git.viper.ipv64.net/M_Viper/StatusAPI/releases"; + + ComponentBuilder builder = new ComponentBuilder(""); + + // Zeile 1: Update Info + // Wir konvertieren Legacy-Strings zu BaseComponents und fügen sie an den Builder an + builder.append(TextComponent.fromLegacyText("§a[StatusAPI] ")) + .append(TextComponent.fromLegacyText("§eEine neue Version ist verfügbar: ")) + .append(TextComponent.fromLegacyText("§c" + latestVersionString)) + .append(TextComponent.fromLegacyText(" §7(Deine Version: ")) + .append(TextComponent.fromLegacyText("§c" + currentVersion)) + .append(TextComponent.fromLegacyText("§7)\n")); + + // Zeile 2: Download + builder.append(TextComponent.fromLegacyText("§7Download: ")); + + // Der Klickbare Teil + TextComponent link = new TextComponent("§e§nLink"); + link.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, downloadUrl)); + link.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextComponent.fromLegacyText("§aKlicke hier um die Release-Seite zu öffnen"))); + + builder.append(link); + + // Korrektur: Nur die Components senden, nicht den Spieler als Argument + player.sendMessage(builder.create()); + } + + /** + * Überprüft auf Updates und benachrichtigt Spieler mit Admin-Rechten (OP/Owner). + */ + private void checkForUpdatesAndNotify() { + String currentVersion = getDescription() != null ? getDescription().getVersion() : "0.0.0"; + // Der UpdateChecker wird nur lokal instanziiert für den Check + UpdateChecker checker = new UpdateChecker(this, currentVersion, 6); + + try { + checker.checkNow(); + + if (checker.isUpdateAvailable(currentVersion)) { + String newVersion = checker.getLatestVersion(); + String url = checker.getLatestUrl(); + + getLogger().warning("------------------------------------------------"); + getLogger().warning("Neue Version verfügbar: " + newVersion); + getLogger().warning("Aktuelle Version: " + currentVersion); + getLogger().warning("Download: " + url); + getLogger().warning("------------------------------------------------"); + + // Info speichern (nur Version, URL ist statisch für den Chat-Link) + this.updatePending = true; + this.latestVersionString = newVersion; + + // Benachrichtigung an online Admins / OPs (mit Verzögerung, um konsistent mit Join zu sein) + boolean notifiedSomeone = false; + for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) { + if (isAdmin(p)) { + final ProxiedPlayer player = p; + getProxy().getScheduler().schedule(this, () -> { + if (player.isConnected() && !informedAdmins.contains(player.getUniqueId())) { + sendStyledUpdateMessage(player); + informedAdmins.add(player.getUniqueId()); + } + }, 2, TimeUnit.SECONDS); + notifiedSomeone = true; + } + } + + if (!notifiedSomeone) { + getLogger().info("Keine Admins waren online, um benachrichtigt zu werden. Sie werden beim Join informiert."); + } + } else { + getLogger().info("Keine Updates verfügbar."); + } + } catch (Exception e) { + getLogger().severe("Fehler beim Update-Check: " + e.getMessage()); + } + } + + // Hilfsmethode zum Prüfen von Admin-Rechten + private boolean isAdmin(ProxiedPlayer player) { + return player.hasPermission("statusapi.admin") || player.hasPermission("bungeecord.command.server"); } @Override @@ -190,8 +305,6 @@ public class StatusAPI extends Plugin implements Runnable { "", "# Beispiel: Deinen UUID hier einfügen", "override.uuid-hier-einfügen = Owner", - "override.uuid-hier-einfügen-2 = Admin", - "override.uuid-hier-einfügen-3 = Developer", "", "# ===========================", "# Chat-Formate für Gruppen", @@ -209,10 +322,33 @@ public class StatusAPI extends Plugin implements Runnable { "groupformat.spieler=&f[Spieler] || &7 || &8", "", "# ===========================", + "# AUTOMESSAGE", + "# ===========================", + "# Aktiviert den automatischen Nachrichten-Rundruf", + "automessage.enabled=false", + "", + "# Zeitintervall in Sekunden (Standard: 300 = 5 Minuten)", + "automessage.interval=300", + "", + "# Optional: Ein Prefix, das VOR jede Nachricht aus der Datei gesetzt wird.", + "# Wenn du das Prefix bereits IN der messages.txt hast, lass dieses Feld einfach leer.", + "automessage.prefix=", + "", + "# Der Name der Datei, in der die Nachrichten stehen (liegt im Plugin-Ordner)", + "automessage.file=messages.txt", + "", + "# ===========================", "# COMMAND BLOCKER", "# ===========================", "commandblocker.enabled=true", - "commandblocker.bypass.permission=commandblocker.bypass" + "commandblocker.bypass.permission=commandblocker.bypass", + "", + "# ===========================", + "# CUSTOM COMMANDS", + "# ===========================", + "# Aktiviert das Modul für benutzerdefinierte Befehle und Aliases.", + "# Wenn aktiviert, wird die Datei 'customcommands.yml' geladen.", + "customcommands.enabled=true" ); // 2. Existierende Werte einlesen @@ -321,69 +457,6 @@ public class StatusAPI extends Plugin implements Runnable { } } - // --- Update Logik --- - private void checkAndMaybeUpdate() { - try { - updateChecker.checkNow(); - String currentVersion = getDescription() != null ? getDescription().getVersion() : "0.0.0"; - - if (updateChecker.isUpdateAvailable(currentVersion)) { - String newVersion = updateChecker.getLatestVersion(); - String url = updateChecker.getLatestUrl(); - getLogger().warning("----------------------------------------"); - getLogger().warning("Neue Version verfügbar: " + newVersion); - getLogger().warning("Starte automatisches Update..."); - getLogger().warning("----------------------------------------"); - - File pluginFile = getFile(); - File newFile = new File(pluginFile.getParentFile(), "StatusAPI.jar.new"); - - fileDownloader.downloadFile(url, newFile, () -> triggerUpdateScript(pluginFile, newFile)); - } - } catch (Exception e) { - getLogger().severe("Fehler beim Update-Check: " + e.getMessage()); - } - } - - private void triggerUpdateScript(File currentFile, File newFile) { - try { - File pluginsFolder = currentFile.getParentFile(); - File rootFolder = pluginsFolder.getParentFile(); - File batFile = new File(rootFolder, "StatusAPI_Update_" + System.currentTimeMillis() + ".bat"); - - String batContent = "@echo off\n" + - "echo Bitte warten, der Server fährt herunter...\n" + - "timeout /t 5 /nobreak >nul\n" + - "cd /d \"" + pluginsFolder.getAbsolutePath().replace("\\", "/") + "\"\n" + - "echo Fuehre Datei-Tausch durch...\n" + - "if exist StatusAPI.jar.bak del StatusAPI.jar.bak\n" + - "if exist StatusAPI.jar (\n" + - " ren StatusAPI.jar StatusAPI.jar.bak\n" + - ")\n" + - "if exist StatusAPI.new.jar (\n" + - " ren StatusAPI.new.jar StatusAPI.jar\n" + - " echo Update erfolgreich!\n" + - ") else (\n" + - " echo FEHLER: StatusAPI.new.jar nicht gefunden!\n" + - " pause\n" + - ")\n" + - "del \"%~f0\""; - - try (PrintWriter out = new PrintWriter(batFile)) { out.println(batContent); } - - for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) { - p.disconnect("§cServer fährt für ein Update neu herunter. Bitte etwas warten."); - } - - getLogger().info("Starte Update-Skript..."); - Runtime.getRuntime().exec("cmd /c start \"Update_Proc\" \"" + batFile.getAbsolutePath() + "\""); - ProxyServer.getInstance().stop(); - - } catch (Exception e) { - getLogger().severe("Fehler beim Vorbereiten des Updates: " + e.getMessage()); - } - } - // --- WebServer & JSON --- @Override public void run() { diff --git a/src/main/java/net/viper/status/modules/AutoMessage/AutoMessageModule.java b/src/main/java/net/viper/status/modules/AutoMessage/AutoMessageModule.java new file mode 100644 index 0000000..9c36b5d --- /dev/null +++ b/src/main/java/net/viper/status/modules/AutoMessage/AutoMessageModule.java @@ -0,0 +1,115 @@ +package net.viper.status.modules.AutoMessage; + +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.chat.TextComponent; +import net.md_5.bungee.api.plugin.Plugin; +import net.viper.status.StatusAPI; +import net.viper.status.module.Module; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.List; +import java.util.Properties; +import java.util.concurrent.TimeUnit; + +public class AutoMessageModule implements Module { + + private int taskId = -1; + + // Diese Methode fehlte bisher und ist zwingend für das Interface + @Override + public String getName() { + return "AutoMessage"; + } + + @Override + public void onEnable(Plugin plugin) { + // Hier casten wir das Plugin-Objekt zu StatusAPI, um an spezifische Methoden zu kommen + StatusAPI api = (StatusAPI) plugin; + + // Konfiguration aus der zentralen verify.properties laden + Properties props = api.getVerifyProperties(); + + boolean enabled = Boolean.parseBoolean(props.getProperty("automessage.enabled", "false")); + + if (!enabled) { + api.getLogger().info("AutoMessage-Modul ist deaktiviert."); + return; + } + + // Interval in Sekunden einlesen + int intervalSeconds; + try { + intervalSeconds = Integer.parseInt(props.getProperty("automessage.interval", "300")); + } catch (NumberFormatException e) { + api.getLogger().warning("Ungültiges Intervall für AutoMessage! Nutze Standard (300s)."); + intervalSeconds = 300; + } + + // Dateiname einlesen (Standard: messages.txt) + String fileName = props.getProperty("automessage.file", "messages.txt"); + File messageFile = new File(api.getDataFolder(), fileName); + + if (!messageFile.exists()) { + api.getLogger().warning("Die Datei '" + fileName + "' wurde nicht gefunden (" + messageFile.getAbsolutePath() + ")!"); + api.getLogger().info("Erstelle eine leere Datei '" + fileName + "' als Vorlage..."); + try { + messageFile.createNewFile(); + } catch (IOException e) { + api.getLogger().severe("Konnte Datei nicht erstellen: " + e.getMessage()); + } + return; + } + + // Nachrichten aus der Datei lesen + List messages; + try { + messages = Files.readAllLines(messageFile.toPath(), StandardCharsets.UTF_8); + } catch (IOException e) { + api.getLogger().severe("Fehler beim Lesen von '" + fileName + "': " + e.getMessage()); + return; + } + + // Leere Zeilen und Kommentare herausfiltern + messages.removeIf(line -> line.trim().isEmpty() || line.trim().startsWith("#")); + + if (messages.isEmpty()) { + api.getLogger().warning("Die Datei '" + fileName + "' enthält keine gültigen Nachrichten!"); + return; + } + + // Optional: Prefix aus Config lesen + String prefixRaw = props.getProperty("automessage.prefix", ""); + String prefix = ChatColor.translateAlternateColorCodes('&', prefixRaw); + + api.getLogger().info("Starte AutoMessage-Task (" + messages.size() + " Nachrichten aus " + fileName + ")"); + + // Finaler Index für den Lambda-Ausdruck + final int[] currentIndex = {0}; + + // Task planen + taskId = ProxyServer.getInstance().getScheduler().schedule(plugin, () -> { + String msg = messages.get(currentIndex[0]); + + String finalMessage = (prefix.isEmpty() ? "" : prefix + " ") + msg; + + // Nachricht an alle auf dem Proxy senden + ProxyServer.getInstance().broadcast(TextComponent.fromLegacy(finalMessage)); + + // Index erhöhen und Loop starten + currentIndex[0] = (currentIndex[0] + 1) % messages.size(); + }, intervalSeconds, intervalSeconds, TimeUnit.SECONDS).getId(); + } + + @Override + public void onDisable(Plugin plugin) { + if (taskId != -1) { + ProxyServer.getInstance().getScheduler().cancel(taskId); + taskId = -1; + plugin.getLogger().info("AutoMessage-Task gestoppt."); + } + } +} \ No newline at end of file diff --git a/src/main/java/net/viper/status/modules/customcommands/CustomCommandModule.java b/src/main/java/net/viper/status/modules/customcommands/CustomCommandModule.java new file mode 100644 index 0000000..926f5dc --- /dev/null +++ b/src/main/java/net/viper/status/modules/customcommands/CustomCommandModule.java @@ -0,0 +1,222 @@ +package net.viper.status.modules.customcommands; + +import java.io.File; +import java.io.IOException; +import java.nio.file.CopyOption; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.chat.TextComponent; +import net.md_5.bungee.api.config.ServerInfo; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.event.ChatEvent; +import net.md_5.bungee.api.plugin.Command; +import net.md_5.bungee.api.plugin.Listener; +import net.md_5.bungee.api.plugin.Plugin; // Import für das Interface Argument +import net.md_5.bungee.config.Configuration; +import net.md_5.bungee.config.ConfigurationProvider; +import net.md_5.bungee.config.YamlConfiguration; +import net.md_5.bungee.event.EventHandler; +import net.viper.status.StatusAPI; +import net.viper.status.module.Module; + +public class CustomCommandModule implements Module, Listener { + + private StatusAPI plugin; + private Configuration config; + private Command chatCommand; + + public CustomCommandModule() { + // Leerer Konstruktor + } + + @Override + public String getName() { + return "CustomCommandModule"; + } + + @Override + public void onEnable(Plugin plugin) { + // Hier casten wir 'Plugin' zu 'StatusAPI', da wir wissen, dass es das ist + this.plugin = (StatusAPI) plugin; + + this.plugin.getLogger().info("Lade CustomCommandModule..."); + reloadConfig(); + + // /bcmds Reload Befehl registrieren + this.plugin.getProxy().getPluginManager().registerCommand(this.plugin, new Command("bcmds") { + @Override + public void execute(CommandSender sender, String[] args) { + if (!sender.hasPermission("statusapi.bcmds")) { + sender.sendMessage(new TextComponent(ChatColor.RED + "You don't have permission.")); + } else { + reloadConfig(); + sender.sendMessage(new TextComponent(ChatColor.GREEN + "Config reloaded.")); + } + } + }); + + // /chat Befehl registrieren (falls aktiviert) + if (config.getBoolean("chat-command", true)) { + chatCommand = new Command("chat") { + @Override + public void execute(CommandSender sender, String[] args) { + if (sender instanceof ProxiedPlayer) { + ProxiedPlayer player = (ProxiedPlayer) sender; + String msg = String.join(" ", args); + ChatEvent e = new ChatEvent(player, player.getServer(), msg); + ProxyServer.getInstance().getPluginManager().callEvent(e); + if (!e.isCancelled()) { + if (!e.isCommand() || !ProxyServer.getInstance().getPluginManager().dispatchCommand(sender, msg.substring(1))) { + player.chat(msg); + } + } + } else { + String msg = String.join(" ", args); + if(msg.startsWith("/")) { + ProxyServer.getInstance().getPluginManager().dispatchCommand(sender, msg.substring(1)); + } else { + sender.sendMessage(new TextComponent("Console cannot send chat messages via /chat usually.")); + } + } + } + }; + this.plugin.getProxy().getPluginManager().registerCommand(this.plugin, chatCommand); + } + + this.plugin.getProxy().getPluginManager().registerListener(this.plugin, this); + } + + @Override + public void onDisable(Plugin plugin) { + // Optional: Cleanup logic, falls nötig. + // Wir nutzen hier das übergebene 'plugin' Argument (oder this.plugin, ist egal) + // Listener und Commands werden automatisch entfernt, wenn das Plugin stoppt. + } + + public void reloadConfig() { + try { + if (!this.plugin.getDataFolder().exists()) { + this.plugin.getDataFolder().mkdirs(); + } + File file = new File(this.plugin.getDataFolder(), "customcommands.yml"); + if (!file.exists()) { + // Kopieren aus Resources + Files.copy(this.plugin.getResourceAsStream("customcommands.yml"), file.toPath(), new CopyOption[0]); + } + this.config = ConfigurationProvider.getProvider(YamlConfiguration.class).load(file); + } catch (IOException e) { + e.printStackTrace(); + this.plugin.getLogger().severe("Konnte customcommands.yml nicht laden!"); + } + } + + @EventHandler(priority = 64) + public void onCommand(ChatEvent e) { + if (!e.isCommand()) return; + if (!(e.getSender() instanceof ProxiedPlayer)) return; + + final ProxiedPlayer player = (ProxiedPlayer) e.getSender(); + String[] split = e.getMessage().split(" "); + String label = split[0].substring(1); + + final List args = new ArrayList<>(Arrays.asList(split)); + args.remove(0); + + Configuration cmds = config.getSection("commands"); + if (cmds == null) return; + + Configuration section = null; + String foundKey = null; + + for (String key : cmds.getKeys()) { + Configuration cmdSection = cmds.getSection(key); + if (key.equalsIgnoreCase(label)) { + section = cmdSection; + foundKey = key; + break; + } + for (String alias : cmdSection.getStringList("aliases")) { + if (alias.equalsIgnoreCase(label)) { + section = cmdSection; + foundKey = key; + break; + } + } + if (section != null) break; + } + + if (section == null) return; + + String type = section.getString("type", "line"); + String sendertype = section.getString("sender", "default"); + String permission = section.getString("permission", ""); + final List commands = section.getStringList("commands"); + + if (!permission.isEmpty() && !player.hasPermission(permission)) { + player.sendMessage(new TextComponent(ChatColor.RED + "You don't have permission.")); + e.setCancelled(true); + return; + } + + e.setCancelled(true); + + final CommandSender target; + if (sendertype.equals("default")) { + target = player; + } else if (sendertype.equals("admin")) { + target = new ForwardSender(player, true); + } else if (sendertype.equals("console")) { + target = ProxyServer.getInstance().getConsole(); + } else { + ProxiedPlayer targetPlayer = ProxyServer.getInstance().getPlayer(sendertype); + if (targetPlayer == null || !targetPlayer.isConnected()) { + player.sendMessage(new TextComponent(ChatColor.RED + "Player " + sendertype + " is not online.")); + return; + } + target = targetPlayer; + } + + String argsString = args.size() >= 1 ? String.join(" ", args) : ""; + final String finalArgs = argsString; + final String senderName = player.getName(); + + if (type.equals("random")) { + int randomIndex = new Random().nextInt(commands.size()); + String rawCommand = commands.get(randomIndex); + executeCommand(target, rawCommand, finalArgs, senderName); + } else if (type.equals("line")) { + ProxyServer.getInstance().getScheduler().runAsync(this.plugin, new Runnable() { + @Override + public void run() { + for (String rawCommand : commands) { + executeCommand(target, rawCommand, finalArgs, senderName); + try { + Thread.sleep(100L); + } catch (InterruptedException ex) { + ex.printStackTrace(); + } + } + } + }); + } else { + this.plugin.getLogger().warning("Unknown type '" + type + "' for command " + foundKey); + } + } + + private void executeCommand(CommandSender sender, String rawCommand, String args, String playerName) { + String parsed = rawCommand + .replaceAll("%args%", args) + .replaceAll("%sender%", playerName); + + String commandToDispatch = parsed.startsWith("/") ? parsed.substring(1) : parsed; + ProxyServer.getInstance().getPluginManager().dispatchCommand(sender, commandToDispatch); + } +} \ No newline at end of file diff --git a/src/main/java/net/viper/status/modules/customcommands/ForwardSender.java b/src/main/java/net/viper/status/modules/customcommands/ForwardSender.java new file mode 100644 index 0000000..fe5796c --- /dev/null +++ b/src/main/java/net/viper/status/modules/customcommands/ForwardSender.java @@ -0,0 +1,118 @@ +package net.viper.status.modules.customcommands; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.Collection; +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.connection.Connection; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.connection.Connection.Unsafe; + +public class ForwardSender implements CommandSender, Connection { + private ProxiedPlayer target; + private Boolean admin; + + public ForwardSender(ProxiedPlayer sender, Boolean admin) { + this.target = sender; + this.admin = admin; + } + + public ProxiedPlayer target() { + return this.target; + } + + @Override + public String getName() { + return this.target.getName(); + } + + @Override + public void sendMessage(String message) { + this.target.sendMessage(message); + } + + @Override + public void sendMessages(String... messages) { + this.target.sendMessages(messages); + } + + @Override + public void sendMessage(BaseComponent... message) { + this.target.sendMessage(message); + } + + @Override + public void sendMessage(BaseComponent message) { + this.target.sendMessage(message); + } + + @Override + public Collection getGroups() { + return this.target.getGroups(); + } + + @Override + public void addGroups(String... groups) { + this.target.addGroups(groups); + } + + @Override + public void removeGroups(String... groups) { + this.target.removeGroups(groups); + } + + @Override + public boolean hasPermission(String permission) { + return this.admin ? true : this.target.hasPermission(permission); + } + + @Override + public void setPermission(String permission, boolean value) { + this.target.setPermission(permission, value); + } + + @Override + public Collection getPermissions() { + Collection perms = this.target.getPermissions(); + if (this.admin) { + perms.add("*"); + } + return perms; + } + + @Override + public InetSocketAddress getAddress() { + return this.target.getAddress(); + } + + @Override + public SocketAddress getSocketAddress() { + return this.target.getSocketAddress(); + } + + @Override + public void disconnect(String reason) { + this.target.disconnect(reason); + } + + @Override + public void disconnect(BaseComponent... reason) { + this.target.disconnect(reason); + } + + @Override + public void disconnect(BaseComponent reason) { + this.target.disconnect(reason); + } + + @Override + public boolean isConnected() { + return this.target.isConnected(); + } + + @Override + public Unsafe unsafe() { + return this.target.unsafe(); + } +} \ No newline at end of file diff --git a/src/main/resources/customcommands.yml b/src/main/resources/customcommands.yml new file mode 100644 index 0000000..286c67c --- /dev/null +++ b/src/main/resources/customcommands.yml @@ -0,0 +1,41 @@ +# If you want to enable /chat +chat-command: true +# The commands +commands: + test: + aliases: + - test2 + permission: "" + type: random + sender: default + commands: + - "alert Das ist ein Random Test Befehl!" + - "glist" + anothercommand: + aliases: + - thisisacommand + permission: "myplugin.mypermission" + type: line + sender: default + commands: + - "alert Hello World!" + - "alert How're you?" + - "alert I'd like to tell you that %args%" + # - "test" # Vorsicht: Rekursion, falls 'test' in dieser Liste steht und 'test' selbst definiert ist + sudo: + aliases: + - runasadmin + permission: "commands.sudo" + type: line + sender: admin + commands: + - "alert Running as Admin: %args%" + - "%args%" + something: + aliases: + - anything + permission: "some.permission" + type: line + sender: console + commands: + - "alert %sender% would like to say that %args%" \ No newline at end of file diff --git a/src/main/resources/messages.txt b/src/main/resources/messages.txt new file mode 100644 index 0000000..8c1f95a --- /dev/null +++ b/src/main/resources/messages.txt @@ -0,0 +1,18 @@ +§8[§2Viper-Netzwerk§8] §7Der Server läuft 24/7 – also keine Hektik beim Spielen :) +§8[§2Viper-Netzwerk§8] §7Dies ist ein privater Server – hier zählt der Zusammenhalt. +§8[§dTipp§8] §7Wenn du denkst, du bist sicher… schau nochmal nach. Creeper machen keine Geräusche beim Tippen. +§8[§2Viper-Netzwerk§8] §7Wähle einen Server, leg los – der Rest ergibt sich. Oder explodiert. +§8[§2Viper-Netzwerk§8] §7Mehr Server. Mehr Blöcke. Mehr Unfälle. Willkommen! +§8[§dTipp§8] §7Halte eine Spitzhacke mit Glück bereit. Man weiß nie, wann das nächste Erz kommt. +§8[§dTipp§8] §7Mit §e/home§7 kannst du dich jederzeit nach Hause teleportieren. +§8[§2Viper-Netzwerk§8] §7Das wichtigste Plugin? Du selbst. Spiel fair, sei kreativ! +§8[§2Viper-Netzwerk§8] §7Redstone ist keine Magie – aber fast. +§8[§dTipp§8] §7Schilde sind cool. Besonders wenn Skelette zielen. +§8[§2Viper-Netzwerk§8] §7Wenn du in Lava fällst, bist du nicht der Erste. Nur der Nächste. +§8[§dTipp§8] §7Villager sind nicht dumm – nur sehr… eigen. +§8[§2Viper-Netzwerk§8] §7Bau groß, bau sicher – oder bau eine Treppe zur Nachbarschaftsklage. +§8[§2Viper-Netzwerk§8] §7Gras wächst. Spieler auch. Gib jedem eine Chance! +§8[§2Viper-Netzwerk§8] §7Ein Creeper ist keine Begrüßung. Es sei denn, du willst es spannend machen. +§8[§dTipp§8] §7Ein voller Magen ist halbe Miete. Farmen lohnt sich! +§8[§2Viper-Netzwerk§8] §7Wir haben keine Probleme – nur Redstone-Schaltungen mit Charakter. +§8[§dTipp§8] §7Markiere dein Grundstück mit §e/p claim§7, bevor es jemand anderes tut! \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 4f06ec1..5800aaa 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,6 +1,6 @@ name: StatusAPI main: net.viper.status.StatusAPI -version: 4.0.6 +version: 4.0.8 author: M_Viper description: StatusAPI für BungeeCord inkl. Update-Checker und Modul-System diff --git a/src/main/resources/verify.properties b/src/main/resources/verify.properties index 0fe7ece..5830a0b 100644 --- a/src/main/resources/verify.properties +++ b/src/main/resources/verify.properties @@ -64,8 +64,6 @@ server.skyblock.secret=GeheimesWortFuerSkyBlock789 # Beispiel: Deinen UUID hier einfügen override.uuid-hier-einfügen = Owner -override.uuid-hier-einfügen = Admin -override.uuid-hier-einfügen = Developer # =========================== # Chat-Formate für Gruppen @@ -82,8 +80,31 @@ groupformat.developer=&b[Dev] || &3 || &a groupformat.premium=&6[Premium] || &e || &7 groupformat.spieler=&f[Spieler] || &7 || &8 +# =========================== +# AUTOMESSAGE +# =========================== +# Aktiviert den automatischen Nachrichten-Rundruf +automessage.enabled=false + +# Zeitintervall in Sekunden (Standard: 300 = 5 Minuten) +automessage.interval=300 + +# Optional: Ein Prefix, das VOR jede Nachricht aus der Datei gesetzt wird. +# Wenn du das Prefix bereits IN der messages.txt hast, lass dieses Feld einfach leer. +automessage.prefix= + +# Der Name der Datei, in der die Nachrichten stehen (liegt im Plugin-Ordner) +automessage.file=messages.txt + # =========================== # COMMAND BLOCKER # =========================== commandblocker.enabled=true commandblocker.bypass.permission=commandblocker.bypass + +# =========================== +# CUSTOM COMMANDS +# =========================== +# Aktiviert das Modul für benutzerdefinierte Befehle und Aliases. +# Wenn aktiviert, wird die Datei 'customcommands.yml' geladen. +customcommands.enabled=true \ No newline at end of file diff --git a/src/main/resources/verify.template.properties b/src/main/resources/verify.template.properties index d40348c..13a72b2 100644 --- a/src/main/resources/verify.template.properties +++ b/src/main/resources/verify.template.properties @@ -63,7 +63,7 @@ server.skyblock.secret=GeheimesWortFuerSkyBlock789 # WICHTIG: Die Gruppe (z.B. Owner) muss unten bei groupformat definiert sein! # Beispiel: Deinen UUID hier einfügen -# override.uuid-hier-einfügen = Owner +override.uuid-hier-einfügen = Owner # ===========================