Kick and ban clients from the context menu

The client menu gains "Kick Client from Channel", "Kick Client from
Server" and "Ban Client", each asking for a reason first. Kicks use a
shared prompt that enforces the protocol's 40-character limit; the ban
dialog adds a duration, whose unit dropdown ends in "Permanent" — the
zero-length ban the server understands.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-14 14:29:21 +00:00
parent d37c6c9ba9
commit 0f8de796ea
6 changed files with 320 additions and 0 deletions

View File

@@ -42,6 +42,10 @@ public final class TeamspeakConnection implements TS3Listener {
/** Upper bound for a downloaded group icon; anything larger is not an icon. */
private static final int MAX_ICON_BYTES = 1024 * 1024;
/** The protocol's reason ids for the two flavours of <b>clientkick</b>. */
private static final int REASON_KICK_CHANNEL = 4;
private static final int REASON_KICK_SERVER = 5;
/** Shortest gap between two "you are talking while muted" reminders. */
private static final long MUTED_TALK_COOLDOWN_NANOS = 5_000_000_000L;
@@ -520,6 +524,48 @@ public final class TeamspeakConnection implements TS3Listener {
}, "ts3j-move-client").start();
}
/** Kicks a client out of its channel, back into the server's default one. */
public void kickFromChannel(int clientId, String reason) {
kick(clientId, REASON_KICK_CHANNEL, reason, "Could not kick client from the channel: ");
}
/** Kicks a client off the server entirely. */
public void kickFromServer(int clientId, String reason) {
kick(clientId, REASON_KICK_SERVER, reason, "Could not kick client from the server: ");
}
private void kick(int clientId, int reasonId, String reason, String errorPrefix) {
new Thread(() -> {
try {
SingleCommand cmd = new SingleCommand("clientkick", ProtocolRole.CLIENT);
cmd.add(new CommandSingleParameter("clid", Integer.toString(clientId)));
cmd.add(new CommandSingleParameter("reasonid", Integer.toString(reasonId)));
if (reason != null && !reason.isEmpty()) {
cmd.add(new CommandSingleParameter("reasonmsg", reason));
}
client.executeCommand(cmd).complete();
} catch (Exception e) {
error(errorPrefix + rootMessage(e));
}
}, "ts3j-kick").start();
}
/**
* Bans a client from the server.
*
* @param seconds how long the ban lasts, or 0 for a permanent one
*/
public void banClient(int clientId, long seconds, String reason) {
new Thread(() -> {
try {
client.banClient(clientId, seconds <= 0 ? null : (int) Math.min(seconds, Integer.MAX_VALUE),
reason == null || reason.isEmpty() ? null : reason);
} catch (Exception e) {
error("Could not ban client: " + rootMessage(e));
}
}, "ts3j-ban").start();
}
/**
* Re-parents and repositions a channel.
*