Delete src/main/java/net/viper/status/modules/chat/bridge/TelegramBridge.java via Git Manager GUI

This commit is contained in:
2026-05-07 19:49:42 +00:00
parent 8a603cd4c8
commit c38efbe7f9

View File

@@ -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 = "<b>" + escapeHtml(header) + "</b>\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<TelegramUpdate> 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<TelegramUpdate> 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 <TOKEN> ──
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: <b>"
+ escapeHtml(acc.minecraftName) + "</b>");
} 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<TelegramUpdate> parseUpdates(String json) {
java.util.List<TelegramUpdate> 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("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;");
}
}