Delete _trash/2026-05-07T19-39-34-009Z/_trash/2026-05-07T19-39-23-130Z/src/main/java/net/viper/status/modules/chat/AccountLinkManager.java via Git Manager GUI

This commit is contained in:
2026-05-07 19:49:22 +00:00
parent c0550435bf
commit abbec8b1c6

View File

@@ -1,318 +0,0 @@
package net.viper.status.modules.chat;
import java.io.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
/**
* Verwaltet die Verknüpfung von Minecraft-Accounts mit Discord/Telegram.
*
* Ablauf:
* 1. Spieler tippt /linkdiscord oder /linktelegram → Token wird generiert
* 2. Spieler schickt Token an den Bot
* 3. Bot-Polling erkennt Token → Verknüpfung wird gespeichert
*
* Speicherformat (chat_links.dat):
* minecraft:<uuid>|name:<spielername>|discord:<discord-user-id>|telegram:<telegram-user-id>
*/
public class AccountLinkManager {
private final File file;
private final Logger logger;
// UUID → verknüpfte Accounts
private final ConcurrentHashMap<UUID, LinkedAccount> links = new ConcurrentHashMap<>();
// Ausstehende Token: token → UUID (läuft nach 10 Min ab)
private final ConcurrentHashMap<String, PendingToken> pendingTokens = new ConcurrentHashMap<>();
public AccountLinkManager(File dataFolder, Logger logger) {
this.file = new File(dataFolder, "chat_links.dat");
this.logger = logger;
}
// ===== Datenklassen =====
public static class LinkedAccount {
public UUID minecraftUUID;
public String minecraftName;
public String discordUserId = ""; // leer = nicht verknüpft
public String telegramUserId = ""; // leer = nicht verknüpft
public String telegramUsername = ""; // @username für Anzeige
public String discordUsername = ""; // für Anzeige
}
private static class PendingToken {
UUID uuid;
String playerName;
String type; // "discord" oder "telegram"
long expiresAt; // Unix-Millis
PendingToken(UUID uuid, String playerName, String type) {
this.uuid = uuid;
this.playerName = playerName;
this.type = type;
this.expiresAt = System.currentTimeMillis() + (10 * 60 * 1000L); // 10 Min
}
boolean isExpired() {
return System.currentTimeMillis() > expiresAt;
}
}
// ===== Token-Generierung =====
/**
* Generiert einen neuen Verknüpfungs-Token für einen Spieler.
* Bestehende Token für denselben Spieler+Typ werden überschrieben.
*
* @param uuid UUID des Spielers
* @param playerName Anzeigename
* @param type "discord" oder "telegram"
* @return 6-stelliger alphanumerischer Token (z.B. "A3F9K2")
*/
public String generateToken(UUID uuid, String playerName, String type) {
// Alte Token für diesen Spieler+Typ entfernen
pendingTokens.entrySet().removeIf(e ->
e.getValue().uuid.equals(uuid) && e.getValue().type.equals(type));
// Abgelaufene Token bereinigen
pendingTokens.entrySet().removeIf(e -> e.getValue().isExpired());
String token;
do {
token = generateRandomToken();
} while (pendingTokens.containsKey(token));
pendingTokens.put(token, new PendingToken(uuid, playerName, type));
return token;
}
private String generateRandomToken() {
String chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; // ohne I,O,0,1 (Verwechslungsgefahr)
Random rnd = new Random();
StringBuilder sb = new StringBuilder(6);
for (int i = 0; i < 6; i++) sb.append(chars.charAt(rnd.nextInt(chars.length())));
return sb.toString();
}
// ===== Token einlösen =====
/**
* Versucht einen Token einzulösen (aufgerufen wenn Bot eine Nachricht empfängt).
*
* @param token Der eingesendete Token
* @param externalId Discord User-ID oder Telegram User-ID (als String)
* @param externalName Discord-Username oder Telegram-@username
* @return LinkedAccount wenn erfolgreich, null wenn Token ungültig/abgelaufen
*/
public LinkedAccount redeemToken(String token, String externalId, String externalName, String type) {
token = token.trim().toUpperCase();
PendingToken pending = pendingTokens.get(token);
if (pending == null || pending.isExpired()) {
pendingTokens.remove(token);
return null;
}
// Typ muss übereinstimmen
if (!pending.type.equals(type)) return null;
pendingTokens.remove(token);
// Bestehenden Account holen oder neu anlegen
LinkedAccount account = links.computeIfAbsent(pending.uuid, k -> {
LinkedAccount a = new LinkedAccount();
a.minecraftUUID = pending.uuid;
a.minecraftName = pending.playerName;
return a;
});
account.minecraftName = pending.playerName; // aktuell halten
if ("discord".equals(pending.type)) {
account.discordUserId = externalId;
account.discordUsername = externalName;
} else if ("telegram".equals(pending.type)) {
account.telegramUserId = externalId;
account.telegramUsername = externalName;
}
save();
return account;
}
// ===== Lookup =====
public LinkedAccount getByUUID(UUID uuid) {
return links.get(uuid);
}
public LinkedAccount getByDiscordId(String discordUserId) {
for (LinkedAccount a : links.values()) {
if (discordUserId.equals(a.discordUserId)) return a;
}
return null;
}
public LinkedAccount getByTelegramId(String telegramUserId) {
for (LinkedAccount a : links.values()) {
if (telegramUserId.equals(a.telegramUserId)) return a;
}
return null;
}
/** Gibt den Minecraft-Namen für eine Discord-User-ID zurück, oder null. */
public String getMinecraftNameByDiscordId(String discordUserId) {
LinkedAccount a = getByDiscordId(discordUserId);
return a != null ? a.minecraftName : null;
}
/** Gibt den Minecraft-Namen für eine Telegram-User-ID zurück, oder null. */
public String getMinecraftNameByTelegramId(String telegramUserId) {
LinkedAccount a = getByTelegramId(telegramUserId);
return a != null ? a.minecraftName : null;
}
/** Prüft ob ein Token gerade aussteht (für Tab-Complete etc.). */
public boolean hasPendingToken(UUID uuid, String type) {
for (PendingToken t : pendingTokens.values()) {
if (t.uuid.equals(uuid) && t.type.equals(type) && !t.isExpired()) return true;
}
return false;
}
// ===== Verknüpfung aufheben =====
public boolean unlinkDiscord(UUID uuid) {
LinkedAccount a = links.get(uuid);
if (a == null || a.discordUserId.isEmpty()) return false;
a.discordUserId = "";
a.discordUsername = "";
cleanupEmpty(uuid);
save();
return true;
}
public boolean unlinkTelegram(UUID uuid) {
LinkedAccount a = links.get(uuid);
if (a == null || a.telegramUserId.isEmpty()) return false;
a.telegramUserId = "";
a.telegramUsername = "";
cleanupEmpty(uuid);
save();
return true;
}
private void cleanupEmpty(UUID uuid) {
LinkedAccount a = links.get(uuid);
if (a != null && a.discordUserId.isEmpty() && a.telegramUserId.isEmpty()) {
links.remove(uuid);
}
}
// ===== Convenience-Methoden für Bridges =====
/**
* Löst einen Telegram-Token ein.
* Wrapper für redeemToken mit type="telegram".
*/
public LinkedAccount redeemTelegram(String token, String telegramUserId, String telegramUsername) {
return redeemToken(token, telegramUserId, telegramUsername, "telegram");
}
/**
* Löst einen Discord-Token ein.
* Wrapper für redeemToken mit type="discord".
*/
public LinkedAccount redeemDiscord(String token, String discordUserId, String discordUsername) {
return redeemToken(token, discordUserId, discordUsername, "discord");
}
/**
* Gibt den Anzeigenamen für einen Telegram-Nutzer zurück.
* Wenn verknüpft: "MinecraftName (@telegram)", sonst: "@telegram"
*/
public String resolveTelegramName(String telegramUserId, String fallbackName) {
String mc = getMinecraftNameByTelegramId(telegramUserId);
return mc != null ? mc : fallbackName;
}
/**
* Gibt den Anzeigenamen für einen Discord-Nutzer zurück.
* Wenn verknüpft: Minecraft-Name, sonst: Discord-Username
*/
public String resolveDiscordName(String discordUserId, String fallbackName) {
String mc = getMinecraftNameByDiscordId(discordUserId);
return mc != null ? mc : fallbackName;
}
/**
* Gibt das korrekte Format-String zurück abhängig ob Account verknüpft.
* linked=true → linkedFormat (mit {player}), false → unlinkedFormat (mit {user})
*/
public boolean isLinkedTelegram(String telegramUserId) {
return getByTelegramId(telegramUserId) != null;
}
public boolean isLinkedDiscord(String discordUserId) {
return getByDiscordId(discordUserId) != null;
}
// ===== Persistenz =====
public void save() {
try (BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(file), "UTF-8"))) {
for (LinkedAccount a : links.values()) {
bw.write(a.minecraftUUID
+ "|" + esc(a.minecraftName)
+ "|" + esc(a.discordUserId)
+ "|" + esc(a.discordUsername)
+ "|" + esc(a.telegramUserId)
+ "|" + esc(a.telegramUsername));
bw.newLine();
}
} catch (IOException e) {
logger.warning("[ChatModule] Fehler beim Speichern der Account-Links: " + e.getMessage());
}
}
public void load() {
links.clear();
if (!file.exists()) return;
try (BufferedReader br = new BufferedReader(
new InputStreamReader(new FileInputStream(file), "UTF-8"))) {
String line;
while ((line = br.readLine()) != null) {
line = line.trim();
if (line.isEmpty()) continue;
String[] p = line.split("\\|", -1);
if (p.length < 6) continue;
try {
LinkedAccount a = new LinkedAccount();
a.minecraftUUID = UUID.fromString(p[0]);
a.minecraftName = unesc(p[1]);
a.discordUserId = unesc(p[2]);
a.discordUsername = unesc(p[3]);
a.telegramUserId = unesc(p[4]);
a.telegramUsername = unesc(p[5]);
if (!a.discordUserId.isEmpty() || !a.telegramUserId.isEmpty()) {
links.put(a.minecraftUUID, a);
}
} catch (Exception ignored) {}
}
} catch (IOException e) {
logger.warning("[ChatModule] Fehler beim Laden der Account-Links: " + e.getMessage());
}
}
private static String esc(String s) {
if (s == null) return "";
return s.replace("\\", "\\\\").replace("|", "\\p");
}
private static String unesc(String s) {
if (s == null) return "";
return s.replace("\\p", "|").replace("\\\\", "\\");
}
}