Show an avatar, description or away message being cleared

Client and channel updates were only applied when the field had a
value, since ts3j reads a missing field back as an empty string. A
removed avatar, an emptied description or a cleared away message
arrives as the field without a value, so they stayed as they were. Such
fields are now applied whenever the update carries them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-25 10:40:00 +00:00
parent 8c735a05b8
commit 7cb4f47a81
2 changed files with 27 additions and 7 deletions

View File

@@ -314,12 +314,12 @@ final class ConnectionEventHandler implements TS3Listener {
// Both fields travel together, so an absent message here means "no message"
// — which `has` cannot tell from "not reported".
c.awayMessage = c.away ? TeamspeakConnection.orEmpty(e.get("client_away_message")) : "";
} else if (has(e, "client_away_message")) {
c.awayMessage = e.get("client_away_message");
} else if (reported(e, "client_away_message")) {
c.awayMessage = TeamspeakConnection.orEmpty(e.get("client_away_message"));
}
if (has(e, "client_talk_power")) c.talkPower = e.getInt("client_talk_power");
if (has(e, "client_description")) {
c.description = e.get("client_description");
if (reported(e, "client_description")) {
c.description = TeamspeakConnection.orEmpty(e.get("client_description"));
conn.ui.onInfoUpdated();
}
// The answer to clientgetvariables.
@@ -330,7 +330,7 @@ final class ConnectionEventHandler implements TS3Listener {
}
if (has(e, "client_is_channel_commander"))
c.channelCommander = e.getBoolean("client_is_channel_commander");
if (has(e, "client_flag_avatar")) c.avatarFlag = TeamspeakConnection.orEmpty(e.get("client_flag_avatar"));
if (reported(e, "client_flag_avatar")) c.avatarFlag = TeamspeakConnection.orEmpty(e.get("client_flag_avatar"));
announceClientUpdate(e, c, renamed, oldName);
conn.ui.onModelChanged();
}
@@ -398,7 +398,7 @@ final class ConnectionEventHandler implements TS3Listener {
ChannelNode ch = conn.getModel().getChannel(safeInt(e, "cid"));
if (ch == null) return;
// The answer to channelgetdescription; an empty description comes as a bare key.
if (e.getMap().containsKey("channel_description")) {
if (reported(e, "channel_description")) {
ch.description = TeamspeakConnection.orEmpty(e.get("channel_description"));
ch.descriptionLoaded = true;
conn.ui.onInfoUpdated();
@@ -425,7 +425,7 @@ final class ConnectionEventHandler implements TS3Listener {
private static void applyChannelProperties(ChannelNode ch, BaseEvent e) {
if (has(e, "channel_name")) ch.name = e.get("channel_name");
// An emptied topic arrives as a bare key, which has() would skip.
if (e.getMap().containsKey("channel_topic")) ch.topic = TeamspeakConnection.orEmpty(e.get("channel_topic"));
if (reported(e, "channel_topic")) ch.topic = TeamspeakConnection.orEmpty(e.get("channel_topic"));
if (has(e, "channel_icon_id")) ch.iconId = TeamspeakConnection.safeLong(e, "channel_icon_id");
if (has(e, "channel_flag_password")) ch.hasPassword = e.getBoolean("channel_flag_password");
if (has(e, "channel_flag_permanent")) ch.permanent = e.getBoolean("channel_flag_permanent");
@@ -681,6 +681,11 @@ final class ConnectionEventHandler implements TS3Listener {
return value != null && !value.isEmpty();
}
/** Whether the event carries a field at all, even empty: for text that can be cleared. */
private static boolean reported(BaseEvent e, String key) {
return e.getMap().containsKey(key);
}
/** Parses a comma-separated id list (e.g. server groups "6,12,15"). */
private static int[] parseIntList(String csv) {
if (csv == null || csv.isEmpty()) return new int[0];

View File

@@ -1,12 +1,16 @@
package com.ts3client.net;
import com.github.manevolent.ts3j.command.MultiCommand;
import com.github.manevolent.ts3j.event.ClientUpdatedEvent;
import com.github.manevolent.ts3j.protocol.ProtocolRole;
import org.junit.jupiter.api.Test;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* Pins the ts3j behaviour {@code ConnectionEventHandler.has(...)} exists for: a field
@@ -22,4 +26,15 @@ class EventFieldsTest {
assertEquals("", e.get("client_is_recording"));
assertEquals("", e.get("client_nickname"));
}
/** What {@code reported(...)} relies on: a cleared field still arrives, just without a value. */
@Test
void clearedFieldsAreStillInTheMap() {
Map<String, String> fields = MultiCommand.parse(ProtocolRole.SERVER,
"notifyclientupdated clid=7 client_flag_avatar client_description=").simplifyOne().toMap();
ClientUpdatedEvent e = new ClientUpdatedEvent(fields);
assertTrue(e.getMap().containsKey("client_flag_avatar"));
assertTrue(e.getMap().containsKey("client_description"));
assertFalse(e.getMap().containsKey("client_away_message"));
}
}