diff --git a/_trash/2026-05-07T19-39-23-130Z/src/main/java/net/viper/status/modules/chat/bridge/TelegramBridge.java b/_trash/2026-05-07T19-39-23-130Z/src/main/java/net/viper/status/modules/chat/bridge/TelegramBridge.java
new file mode 100644
index 0000000..e4657ec
--- /dev/null
+++ b/_trash/2026-05-07T19-39-23-130Z/src/main/java/net/viper/status/modules/chat/bridge/TelegramBridge.java
@@ -0,0 +1,399 @@
+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