TeamSpeak: Backoff beim Verbinden der Raum-Clients, kürzere Nicknames, alle Channels abonnieren, Anwesenheit loggen

This commit is contained in:
MineTec committed 2026-10-18 13:20:06 +02:00
1 parent 3c3a9dd836
commit ba22c9c79f
6 files changed
+38 -8

No files matched your search

@@ -38,6 +38,7 @@ public final class Bridge {
/** Zeitpunkte, bis zu denen Raum bzw. Bot noch gebraucht werden (für das Nachlaufen). */
private static final class Presence {
long roomWanted, teamspeakWanted, discordWanted;
String lastCounts = "";
boolean teamspeakConnected, discordConnected;
}
@@ -84,6 +85,11 @@ public final class Bridge {
int mc = plugin.minecraftMembers(room.key());
int ts = teamspeak != null ? teamspeak.humansIn(room) : 0;
int dc = bot != null ? bot.humans() : 0;
String counts = "Minecraft " + mc + ", TeamSpeak " + ts + ", Discord " + dc;
if (!counts.equals(p.lastCounts)) {
LOG.info("{}: {}", room.name(), counts);
p.lastCounts = counts;
}
int sides = (mc > 0 ? 1 : 0) + (ts > 0 ? 1 : 0) + (dc > 0 ? 1 : 0);
if (sides >= 2) p.roomWanted = now;
@@ -73,9 +73,9 @@ public record BridgeConfig(
integer(root, "lingerSeconds", 15) * 1000L,
new Teamspeak(string(ts, "host", ""), integer(ts, "port", 9987), string(ts, "password", ""),
string(ts, "parentChannel", "0"), string(ts, "nickname", "Crosstalk"),
string(ts, "roomNickname", "%room% (Minecraft/Discord)"), integer(ts, "securityLevel", 15), version),
string(ts, "roomNickname", "%room% (MC/DC)"), integer(ts, "securityLevel", 15), version),
new Discord(string(dc, "guild", ""), string(dc, "category", ""),
string(dc, "nickname", "%room% (Minecraft/TS)")),
string(dc, "nickname", "%room% (MC/TS)")),
List.copyOf(rooms));
}
@@ -83,6 +83,7 @@ public final class DiscordRoomBot implements AudioSendHandler, AudioReceiveHandl
LOG.info("{}: Discord-Bot {} bereit, Channel #{}", room.name(), jda.getSelfUser().getName(), channel.getName());
String nickname = config.nickname().replace("%room%", room.name());
if (nickname.length() > 32) nickname = nickname.substring(0, 32);
if (!nickname.equals(guild.getSelfMember().getNickname())) {
guild.getSelfMember().modifyNickname(nickname).queue(null,
e -> LOG.debug("{}: Nickname nicht gesetzt: {}", room.name(), e.getMessage()));
@@ -21,6 +21,7 @@ import java.util.function.Consumer;
/** Ein TS-Client im Raum-Channel: spielt die Mischung aus Minecraft und Discord ab und hört zu. */
final class TeamspeakRoomClient implements Microphone, TS3Listener {
private static final Logger LOG = LoggerFactory.getLogger(TeamspeakRoomClient.class);
private static final int MAX_NICKNAME = 30;
private final BridgeConfig.Teamspeak config;
private final Room room;
@@ -46,7 +47,8 @@ final class TeamspeakRoomClient implements Microphone, TS3Listener {
static LocalTeamspeakClientSocket newSocket(BridgeConfig.Teamspeak config, LocalIdentity identity, String nickname) {
LocalTeamspeakClientSocket socket = new LocalTeamspeakClientSocket();
socket.setIdentity(identity);
socket.setNickname(nickname);
// Längere Nicknames lehnt der Server mit "invalid parameter size" ab
socket.setNickname(nickname.length() > MAX_NICKNAME ? nickname.substring(0, MAX_NICKNAME) : nickname);
BridgeConfig.ClientVersion version = config.clientVersion();
if (version != null) socket.setClientVersion(version.platform(), version.version(), version.sign());
socket.setExceptionHandler(e -> LOG.debug("ts3j: {}", e.toString()));
@@ -44,6 +44,8 @@ public final class TeamspeakSide implements TS3Listener {
private static final long RECONNECT_SECONDS = 30;
/** client_type 0 = normaler Client, 1 = ServerQuery. */
private static final int CLIENT_TYPE_VOICE = 0;
private static final long RETRY_MIN_MILLIS = 30_000;
private static final long RETRY_MAX_MILLIS = 300_000;
private record TsClient(int channel, int type) {}
private record TsChannel(int parent, String name) {}
@@ -58,6 +60,9 @@ public final class TeamspeakSide implements TS3Listener {
private final Map<Integer, TsChannel> channels = new ConcurrentHashMap<>();
private final Map<String, Integer> roomChannels = new ConcurrentHashMap<>();
private final Map<String, TeamspeakRoomClient> roomClients = new ConcurrentHashMap<>();
private final Set<String> pendingConnect = ConcurrentHashMap.newKeySet();
private final Map<String, Long> backoff = new ConcurrentHashMap<>();
private final Map<String, Long> nextAttempt = new ConcurrentHashMap<>();
private volatile LocalTeamspeakClientSocket observer;
private volatile boolean ready;
@@ -106,9 +111,18 @@ public final class TeamspeakSide implements TS3Listener {
return count;
}
/** Raum-Client verbinden oder trennen. Läuft asynchron, mehrfaches Aufrufen schadet nicht. */
/**
* Raum-Client verbinden oder trennen. Läuft asynchron, mehrfaches Aufrufen schadet nicht.
* Nach einem Fehlschlag wird mit wachsendem Abstand neu versucht, sonst greift der Flood-Schutz
* des Servers und bannt die IP.
*/
public void setRoomConnected(Room room, boolean connected) {
if (connected) {
if (roomClients.containsKey(room.key()) || System.currentTimeMillis() < nextAttempt.getOrDefault(room.key(), 0L)) return;
if (!pendingConnect.add(room.key())) return;
}
executor.execute(() -> {
pendingConnect.remove(room.key());
TeamspeakRoomClient client = roomClients.get(room.key());
if (connected && client == null && ready) {
Integer channel = roomChannels.get(room.key());
@@ -118,9 +132,14 @@ public final class TeamspeakSide implements TS3Listener {
loadIdentity("ts-" + room.key() + ".ini"), this::onRoomClientLost);
roomClients.put(room.key(), client);
client.connect();
backoff.remove(room.key());
LOG.info("{}: TeamSpeak-Client im Channel {}", room.name(), channel);
} catch (Exception e) {
LOG.warn("{}: TeamSpeak-Client konnte nicht verbinden: {}", room.name(), e.toString());
long wait = Math.min(backoff.getOrDefault(room.key(), RETRY_MIN_MILLIS / 2) * 2, RETRY_MAX_MILLIS);
backoff.put(room.key(), wait);
nextAttempt.put(room.key(), System.currentTimeMillis() + wait);
LOG.warn("{}: TeamSpeak-Client konnte nicht verbinden ({}), neuer Versuch in {} s",
room.name(), e.toString(), wait / 1000);
roomClients.remove(room.key());
if (client != null) client.disconnect();
}
@@ -151,6 +170,8 @@ public final class TeamspeakSide implements TS3Listener {
socket.addListener(this);
observer = socket;
socket.connect(new InetSocketAddress(config.host(), config.port()), emptyToNull(config.password()), 15000L);
// Clients in nicht abonnierten Channels meldet der Server nicht
socket.subscribeAll();
awaitInitialState();
LOG.info("TeamSpeak verbunden mit {}:{} ({} Channels, {} Clients sichtbar)",
config.host(), config.port(), channels.size(), clients.size());