diff --git a/src/main/java/net/viper/status/modules/chat/bridge/TelegramBridge.java b/src/main/java/net/viper/status/modules/chat/bridge/TelegramBridge.java deleted file mode 100644 index 856ef95..0000000 --- a/src/main/java/net/viper/status/modules/chat/bridge/TelegramBridge.java +++ /dev/null @@ -1,399 +0,0 @@ -package net.viper.status.modules.chat.bridge; - -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.modules.chat.AccountLinkManager; -import net.viper.status.modules.chat.ChatConfig; - -import java.io.*; -import java.net.HttpURLConnection; -import java.net.URL; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicLong; -import java.util.logging.Logger; - -/** - * Telegram-Brücke für bidirektionale Kommunikation. - * - * Minecraft → Telegram: Via Bot API (sendMessage) - * Telegram → Minecraft: Via Long-Polling (getUpdates) - * - * Voraussetzungen: - * - Telegram Bot via @BotFather erstellen - * - Bot-Token in chat.yml eintragen - * - Bot in die gewünschten Gruppen/Kanäle einladen - * - Bot zu Admin machen (für Gruppen-Nachrichten empfangen) - */ -public class TelegramBridge { - - private static final String API_BASE = "https://api.telegram.org/bot"; - - private final Plugin plugin; - private final ChatConfig config; - private final Logger logger; - private AccountLinkManager linkManager; // wird nach dem Start gesetzt - - // Letztes verarbeitetes Update-ID (für getUpdates Offset) - private final AtomicLong lastUpdateId = new AtomicLong(0L); - - private volatile boolean running = false; - - public TelegramBridge(Plugin plugin, ChatConfig config) { - this.plugin = plugin; - this.config = config; - this.logger = plugin.getLogger(); - } - - /** Setzt den AccountLinkManager – muss vor start() aufgerufen werden. */ - public void setLinkManager(AccountLinkManager linkManager) { - this.linkManager = linkManager; - } - - public void start() { - if (!config.isTelegramEnabled() - || config.getTelegramBotToken().isEmpty() - || config.getTelegramBotToken().equals("YOUR_TELEGRAM_BOT_TOKEN")) { - logger.warning("[ChatModule-Telegram] Bot-Token nicht konfiguriert. Telegram-Empfang deaktiviert."); - return; - } - - running = true; - int interval = Math.max(2, config.getTelegramPollInterval()); - - plugin.getProxy().getScheduler().schedule(plugin, this::pollUpdates, - interval, interval, TimeUnit.SECONDS); - - logger.info("[ChatModule-Telegram] Brücke gestartet (Poll-Intervall: " + interval + "s)."); - } - - public void stop() { - running = false; - } - - // ===== Minecraft → Telegram ===== - - /** - * Sendet eine Nachricht an eine Telegram-Chat-ID. - * Unterstützt Themen-Gruppen via message_thread_id. - */ - public void sendToTelegram(String chatId, String message) { - sendToTelegram(chatId, 0, message); - } - - public void sendToTelegram(String chatId, int threadId, String message) { - if (chatId == null || chatId.isEmpty()) return; - - plugin.getProxy().getScheduler().runAsync(plugin, () -> { - try { - String cleanMessage = ChatColor.stripColor( - ChatColor.translateAlternateColorCodes('&', message)); - - String url = API_BASE + config.getTelegramBotToken() - + "/sendMessage?chat_id=" + URLEncoder.encode(chatId, "UTF-8") - + "&text=" + URLEncoder.encode(cleanMessage, "UTF-8") - + "&parse_mode=HTML" - + (threadId > 0 ? "&message_thread_id=" + threadId : ""); - - getJson(url); - } catch (Exception e) { - logger.warning("[ChatModule-Telegram] Sende-Fehler: " + e.getMessage()); - } - }); - } - - /** - * Sendet eine formatierte HelpOp/Broadcast-Nachricht an Telegram. - * Unterstützt Themen-Gruppen via message_thread_id. - */ - public void sendFormattedToTelegram(String chatId, String header, String content) { - sendFormattedToTelegram(chatId, 0, header, content); - } - - public void sendFormattedToTelegram(String chatId, int threadId, String header, String content) { - if (chatId == null || chatId.isEmpty()) return; - String text = "" + escapeHtml(header) + "\n" + escapeHtml(content); - - plugin.getProxy().getScheduler().runAsync(plugin, () -> { - try { - String url = API_BASE + config.getTelegramBotToken() - + "/sendMessage?chat_id=" + URLEncoder.encode(chatId, "UTF-8") - + "&text=" + URLEncoder.encode(text, "UTF-8") - + "&parse_mode=HTML" - + (threadId > 0 ? "&message_thread_id=" + threadId : ""); - getJson(url); - } catch (Exception e) { - logger.warning("[ChatModule-Telegram] Format-Sende-Fehler: " + e.getMessage()); - } - }); - } - - // ===== Telegram → Minecraft (Polling) ===== - - private void pollUpdates() { - if (!running) return; - try { - // Beim ersten Poll: nur den aktuellen Offset holen, keine alten Updates verarbeiten - if (lastUpdateId.get() == 0L) { - String initUrl = API_BASE + config.getTelegramBotToken() - + "/getUpdates?limit=1&offset=-1"; - String initResp = getJson(initUrl); - if (initResp != null && initResp.contains("\"ok\":true")) { - java.util.List initUpdates = parseUpdates(initResp); - if (!initUpdates.isEmpty()) { - lastUpdateId.set(initUpdates.get(initUpdates.size() - 1).updateId); - } - } - return; // Erster Poll nur zum Initialisieren - } - - long offset = lastUpdateId.get() + 1; - String url = API_BASE + config.getTelegramBotToken() - + "/getUpdates?timeout=2&limit=10" - + (offset > 0 ? "&offset=" + offset : ""); - - String response = getJson(url); - if (response == null || !response.contains("\"ok\":true")) return; - - java.util.List updates = parseUpdates(response); - - for (TelegramUpdate update : updates) { - if (update.updateId > lastUpdateId.get()) { - lastUpdateId.set(update.updateId); - } - if (update.text == null || update.text.isEmpty()) continue; - if (update.isBot) continue; - - // ── Token-Einlösung: /link ── - if (update.text.startsWith("/link ") || update.text.startsWith("/link@")) { - String[] parts = update.text.split("\\s+", 2); - if (parts.length == 2 && linkManager != null) { - String token = parts[1].trim().toUpperCase(); - AccountLinkManager.LinkedAccount acc = - linkManager.redeemTelegram(token, update.fromId, update.fromName); - if (acc != null) { - sendToTelegram(update.chatId, update.threadId, - "✅ Verknüpfung erfolgreich! Minecraft-Account: " - + escapeHtml(acc.minecraftName) + ""); - } else { - sendToTelegram(update.chatId, update.threadId, - "❌ Ungültiger oder abgelaufener Token. Bitte /telegramlink im Spiel erneut ausführen."); - } - } - continue; // Nicht als Chat-Nachricht weiterleiten - } - - // Bot-Befehle ignorieren - if (update.text.startsWith("/")) continue; - - // ── Account-Name auflösen ── - String displayName = (linkManager != null) - ? linkManager.resolveTelegramName(update.fromId, update.fromName) - : update.fromName; - - // Welchem Minecraft-Kanal gehört diese Telegram-Chat-ID + Thread? - final boolean isAdminChat = update.chatId.equals(config.getTelegramAdminChatId()) - && (config.getTelegramAdminTopicId() == 0 - || config.getTelegramAdminTopicId() == update.threadId); - - // Prüfen ob die Nachricht zu einem konfigurierten Kanal-Thema gehört - final boolean matchesChannel = isAdminChat || matchesTelegramChannel(update); - - if (!matchesChannel && !isAdminChat) continue; - - final String format = config.getTelegramFromFormat(); - final String finalDisplay = displayName; - final String formatted = ChatColor.translateAlternateColorCodes('&', - format.replace("{user}", finalDisplay) - .replace("{message}", update.text)); - - ProxyServer.getInstance().getScheduler().runAsync(plugin, () -> { - if (isAdminChat) { - for (net.md_5.bungee.api.connection.ProxiedPlayer p : - ProxyServer.getInstance().getPlayers()) { - if (p.hasPermission("chat.admin.bypass")) { - p.sendMessage(new TextComponent(formatted)); - } - } - } else { - ProxyServer.getInstance().broadcast(new TextComponent(formatted)); - } - }); - } - } catch (Exception e) { - logger.fine("[ChatModule-Telegram] Poll-Fehler: " + e.getMessage()); - } - } - - // ===== HTTP-Hilfsmethoden ===== - - private String getJson(String urlStr) throws Exception { - HttpURLConnection conn = (HttpURLConnection) new URL(urlStr).openConnection(); - conn.setRequestMethod("GET"); - conn.setConnectTimeout(6000); - conn.setReadTimeout(10000); - conn.setRequestProperty("User-Agent", "StatusAPI-ChatModule/1.0"); - int code = conn.getResponseCode(); - String result = readStream(code == 200 ? conn.getInputStream() : conn.getErrorStream()); - conn.disconnect(); - return result; - } - - private String readStream(InputStream in) throws IOException { - if (in == null) return ""; - try (BufferedReader br = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) { - StringBuilder sb = new StringBuilder(); - String line; - while ((line = br.readLine()) != null) sb.append(line); - return sb.toString(); - } - } - - // ===== JSON Mini-Parser ===== - - private static class TelegramUpdate { - long updateId; - String chatId = ""; - String fromId = ""; // Telegram User-ID (für Account-Link) - String fromName = ""; - String text = ""; - boolean isBot = false; - int threadId = 0; // message_thread_id für Themen-Gruppen (0 = kein Thema) - } - - private java.util.List parseUpdates(String json) { - java.util.List result = new java.util.ArrayList<>(); - // Suche nach "result":[...] - int resultStart = json.indexOf("\"result\":["); - if (resultStart < 0) return result; - - // Extrahiere alle Update-Objekte - int depth = 0, start = -1; - boolean inResult = false; - for (int i = resultStart + 10; i < json.length(); i++) { - char c = json.charAt(i); - if (c == '[' && !inResult) { inResult = true; continue; } - if (!inResult) continue; - if (c == '{') { if (depth++ == 0) start = i; } - else if (c == '}') { - if (--depth == 0 && start >= 0) { - TelegramUpdate upd = parseUpdate(json.substring(start, i + 1)); - if (upd != null) result.add(upd); - start = -1; - } - } else if (c == ']' && depth == 0) break; - } - return result; - } - - /** Prüft ob ein Update zu einem konfigurierten Kanal-Thema gehört. */ - private boolean matchesTelegramChannel(TelegramUpdate update) { - for (net.viper.status.modules.chat.ChatChannel ch : config.getChannels().values()) { - if (!ch.getTelegramChatId().equals(update.chatId)) continue; - // Thema konfiguriert? → Thread-ID muss übereinstimmen - if (ch.getTelegramThreadId() > 0 && ch.getTelegramThreadId() != update.threadId) continue; - return true; - } - return false; - } - - private TelegramUpdate parseUpdate(String obj) { - try { - TelegramUpdate upd = new TelegramUpdate(); - upd.updateId = Long.parseLong(extractValue(obj, "update_id")); - - // message-Block - int msgIdx = obj.indexOf("\"message\""); - if (msgIdx < 0) return null; - String msgBlock = extractObject(obj, msgIdx); - - upd.text = unescapeJson(extractString(msgBlock, "text")); - - // message_thread_id (Themen-Gruppen) - String threadIdStr = extractValue(msgBlock, "message_thread_id"); - if (!threadIdStr.isEmpty()) { - try { upd.threadId = Integer.parseInt(threadIdStr); } catch (Exception ignored) {} - } - - // from-Block (Absender) - int fromIdx = msgBlock.indexOf("\"from\""); - if (fromIdx >= 0) { - String fromBlock = extractObject(msgBlock, fromIdx); - String firstName = unescapeJson(extractString(fromBlock, "first_name")); - String lastName = unescapeJson(extractString(fromBlock, "last_name")); - String username = unescapeJson(extractString(fromBlock, "username")); - upd.fromId = extractValue(fromBlock, "id"); - upd.fromName = !username.isEmpty() ? "@" + username - : (firstName + (lastName.isEmpty() ? "" : " " + lastName)).trim(); - String botFlag = extractValue(fromBlock, "is_bot"); - upd.isBot = "true".equals(botFlag); - } - - // chat-Block (Chat-ID) - int chatIdx = msgBlock.indexOf("\"chat\""); - if (chatIdx >= 0) { - String chatBlock = extractObject(msgBlock, chatIdx); - upd.chatId = extractValue(chatBlock, "id"); - } - - return upd; - } catch (Exception e) { - return null; - } - } - - private String extractValue(String json, String key) { - String fullKey = "\"" + key + "\""; - int idx = json.indexOf(fullKey); - if (idx < 0) return ""; - int colon = json.indexOf(':', idx + fullKey.length()); - if (colon < 0) return ""; - int valStart = colon + 1; - while (valStart < json.length() && json.charAt(valStart) == ' ') valStart++; - if (valStart >= json.length()) return ""; - char first = json.charAt(valStart); - if (first == '"') { - return extractString(json.substring(valStart - 1 - key.length()), key); - } - int end = valStart; - while (end < json.length() && ",}\n".indexOf(json.charAt(end)) < 0) end++; - return json.substring(valStart, end).trim(); - } - - private String extractString(String json, String key) { - String fullKey = "\"" + key + "\":\""; - int idx = json.indexOf(fullKey); - if (idx < 0) return ""; - int start = idx + fullKey.length(); - int end = start; - while (end < json.length()) { - if (json.charAt(end) == '"' && json.charAt(end - 1) != '\\') break; - end++; - } - return json.substring(start, end); - } - - private String extractObject(String json, int fromIndex) { - int depth = 0, start = -1; - for (int i = fromIndex; i < json.length(); i++) { - char c = json.charAt(i); - if (c == '{') { if (depth++ == 0) start = i; } - else if (c == '}') { if (--depth == 0 && start >= 0) return json.substring(start, i + 1); } - } - return ""; - } - - private static String unescapeJson(String s) { - if (s == null) return ""; - return s.replace("\\\"", "\"").replace("\\n", "\n") - .replace("\\r", "\r").replace("\\\\", "\\"); - } - - private static String escapeHtml(String s) { - if (s == null) return ""; - return s.replace("&", "&").replace("<", "<").replace(">", ">"); - } -} \ No newline at end of file