Update connection and chat handling
This commit is contained in:
@@ -586,8 +586,9 @@ final class ConnectionEventHandler implements TS3Listener {
|
||||
notification = SoundEvent.CHAT_RECEIVED_MESSAGE_SERVER;
|
||||
break;
|
||||
}
|
||||
conn.persistIncomingChat(scope, e.getInvokerId(), e.getInvokerUniqueId(), e.getInvokerName(), e.getMessage());
|
||||
conn.sound(notification, conn.clientVars(e.getInvokerId(), e.getInvokerName()));
|
||||
conn.ui.onChat(scope, e.getInvokerId(), e.getInvokerName(), e.getMessage());
|
||||
conn.ui.onChat(scope, e.getInvokerId(), e.getInvokerUniqueId(), e.getInvokerName(), e.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.ts3client.net;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* UI-facing callbacks fired by {@link TeamspeakConnection}. Implementations are
|
||||
* responsible for marshalling to the Swing EDT.
|
||||
@@ -24,7 +26,15 @@ public interface ConnectionListener {
|
||||
/** A group icon finished downloading; repaint anything showing icons. */
|
||||
void onIconsUpdated();
|
||||
|
||||
void onChat(ChatScope scope, int fromClientId, String fromName, String message);
|
||||
void onChat(ChatScope scope, int fromClientId, String fromUniqueId, String fromName, String message);
|
||||
|
||||
/** Previously persisted TS3 HTML log entries for one chat scope. */
|
||||
default void onChatHistory(ChatScope scope, List<String> htmlEntries) {
|
||||
}
|
||||
|
||||
/** Previously persisted TS3 HTML log entries for a private chat. */
|
||||
default void onPrivateChatHistory(int clientId, String nickname, List<String> htmlEntries) {
|
||||
}
|
||||
|
||||
/** A client (possibly the local one) started or stopped talking. */
|
||||
void onTalkStateChanged(int clientId, boolean talking);
|
||||
@@ -40,4 +50,9 @@ public interface ConnectionListener {
|
||||
* a group being (un)assigned, and so on.
|
||||
*/
|
||||
void onServerLog(String message);
|
||||
|
||||
/** A server-log line already rendered in TS3's HTML log format. */
|
||||
default void onServerLogHtml(String html) {
|
||||
onServerLog(html == null ? "" : html);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.github.manevolent.ts3j.api.Channel;
|
||||
import com.github.manevolent.ts3j.api.Client;
|
||||
import com.github.manevolent.ts3j.command.MultiCommand;
|
||||
import com.github.manevolent.ts3j.command.SingleCommand;
|
||||
import com.github.manevolent.ts3j.command.parameter.CommandParameter;
|
||||
import com.github.manevolent.ts3j.command.parameter.CommandSingleParameter;
|
||||
import com.github.manevolent.ts3j.event.*;
|
||||
import com.github.manevolent.ts3j.identity.LocalIdentity;
|
||||
@@ -13,6 +14,8 @@ import com.github.manevolent.ts3j.util.Ts3Crypt;
|
||||
import com.ts3client.audio.AudioBackend;
|
||||
import com.ts3client.audio.VoiceInput;
|
||||
import com.ts3client.audio.VoiceOutput;
|
||||
import com.ts3client.chatlog.ChatLogScope;
|
||||
import com.ts3client.chatlog.ChatLogStore;
|
||||
import com.ts3client.config.Settings;
|
||||
import com.ts3client.net.filetransfer.FileTransfer;
|
||||
import com.ts3client.net.filetransfer.FileTransferManager;
|
||||
@@ -22,8 +25,10 @@ import com.ts3client.sound.SoundNotifier;
|
||||
import com.ts3client.text.TsLink;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -72,6 +77,7 @@ public final class TeamspeakConnection implements TS3Listener {
|
||||
private VoiceOutput playback;
|
||||
private LocalIdentity identity;
|
||||
private FileTransferManager fileTransfers;
|
||||
private ChatLogStore chatLogs;
|
||||
/**
|
||||
* Runs ts3j's event callbacks. Supplying it ourselves (rather than leaving ts3j to make
|
||||
* its own) is what lets {@link #awaitEventsProcessed()} tell when everything the server
|
||||
@@ -287,6 +293,7 @@ public final class TeamspeakConnection implements TS3Listener {
|
||||
leaving = false;
|
||||
disconnectAnnounced = false;
|
||||
deafened = false;
|
||||
loadChatLogs();
|
||||
ui.onConnected();
|
||||
requestPermissionNames();
|
||||
|
||||
@@ -517,6 +524,7 @@ public final class TeamspeakConnection implements TS3Listener {
|
||||
playback = null;
|
||||
client = null;
|
||||
fileTransfers = null;
|
||||
chatLogs = null;
|
||||
selfClientId = -1;
|
||||
stats.clear();
|
||||
|
||||
@@ -749,6 +757,7 @@ public final class TeamspeakConnection implements TS3Listener {
|
||||
ClientEntry self = model.getClient(selfClientId);
|
||||
int cid = self != null ? self.channelId : 0;
|
||||
client.sendChannelMessage(cid, text);
|
||||
persistOutgoingChat(ConnectionListener.ChatScope.CHANNEL, 0, "", text);
|
||||
sound(SoundEvent.CHAT_SENT_MESSAGE_CHANNEL, channelVars(cid, null));
|
||||
} catch (Exception e) {
|
||||
error("Message failed: " + rootMessage(e));
|
||||
@@ -760,6 +769,7 @@ public final class TeamspeakConnection implements TS3Listener {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
client.sendServerMessage(text);
|
||||
persistOutgoingChat(ConnectionListener.ChatScope.SERVER, 0, "", text);
|
||||
sound(SoundEvent.CHAT_SENT_MESSAGE_SERVER, serverVars());
|
||||
} catch (Exception e) {
|
||||
error("Message failed: " + rootMessage(e));
|
||||
@@ -771,6 +781,16 @@ public final class TeamspeakConnection implements TS3Listener {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
client.sendPrivateMessage(clientId, text);
|
||||
ClientEntry peer = model.getClient(clientId);
|
||||
String peerUid = peer != null ? peer.uniqueId : "";
|
||||
if (peerUid == null || peerUid.isBlank()) {
|
||||
try {
|
||||
Client info = client.getClientInfo(clientId);
|
||||
if (info != null) peerUid = info.getUniqueIdentifier();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
persistOutgoingChat(ConnectionListener.ChatScope.PRIVATE, clientId, peerUid, text);
|
||||
sound(SoundEvent.CHAT_SENT_MESSAGE_CLIENT, clientVars(clientId, null));
|
||||
} catch (Exception e) {
|
||||
error("Message failed: " + rootMessage(e));
|
||||
@@ -1199,9 +1219,162 @@ public final class TeamspeakConnection implements TS3Listener {
|
||||
sounds.fire(event, deafened, variables);
|
||||
}
|
||||
|
||||
private void loadChatLogs() {
|
||||
try {
|
||||
String uniqueId = serverUniqueId();
|
||||
if (uniqueId == null) {
|
||||
chatLogs = null;
|
||||
ui.onStatus("Chat logs unavailable: server unique id was not reported");
|
||||
return;
|
||||
}
|
||||
ChatLogStore logs = new ChatLogStore(uniqueId);
|
||||
chatLogs = logs;
|
||||
logs.begin();
|
||||
List<String> serverHistory = logs.serverHtmlHistory();
|
||||
List<String> channelHistory = logs.channelHtmlHistory();
|
||||
ui.onChatHistory(ConnectionListener.ChatScope.SERVER, serverHistory);
|
||||
ui.onChatHistory(ConnectionListener.ChatScope.CHANNEL, channelHistory);
|
||||
ui.onStatus("Loaded chat logs from " + logs.serverDir()
|
||||
+ " (" + serverHistory.size() + " server, " + channelHistory.size() + " channel)");
|
||||
} catch (Exception e) {
|
||||
chatLogs = null;
|
||||
ui.onStatus("Chat logs unavailable: " + rootMessage(e));
|
||||
}
|
||||
}
|
||||
|
||||
private String serverUniqueId() {
|
||||
String uniqueId = validServerUniqueId(firstPresent(
|
||||
client.getServerProperties().get("virtualserver_unique_identifier"),
|
||||
client.getServerProperties().get("virtualserver_unique_id")));
|
||||
if (uniqueId != null) return uniqueId;
|
||||
|
||||
uniqueId = serverUniqueIdFrom(new SingleCommand("serverinfo", ProtocolRole.CLIENT));
|
||||
if (uniqueId != null) return uniqueId;
|
||||
|
||||
try {
|
||||
SingleCommand command = new SingleCommand("servergetvariables", ProtocolRole.CLIENT);
|
||||
String serverId = client.getServerProperties().get("virtualserver_id");
|
||||
if (serverId != null && !serverId.isBlank()) {
|
||||
command.add(new CommandSingleParameter("virtualserver_id", serverId));
|
||||
}
|
||||
return serverUniqueIdFrom(command);
|
||||
} catch (Exception ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String serverUniqueIdFrom(SingleCommand command) {
|
||||
try {
|
||||
for (SingleCommand response : client.executeCommand(command).get()) {
|
||||
String uniqueId = validServerUniqueId(firstPresent(
|
||||
parameterValue(response, "virtualserver_unique_identifier"),
|
||||
parameterValue(response, "virtualserver_unique_id")));
|
||||
if (uniqueId != null) return uniqueId;
|
||||
}
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static String validServerUniqueId(String value) {
|
||||
if (value == null || value.isBlank()) return null;
|
||||
try {
|
||||
byte[] decoded = Base64.getDecoder().decode(value);
|
||||
if (decoded.length != 20) return null;
|
||||
|
||||
// TS3 server UIDs are SHA-1-sized binary identifiers represented as base64.
|
||||
// Plain host strings are rejected before they can become log folder names.
|
||||
boolean printable = true;
|
||||
for (byte b : decoded) {
|
||||
int c = b & 0xff;
|
||||
if (c < 0x20 || c > 0x7e) {
|
||||
printable = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return printable ? null : value;
|
||||
} catch (IllegalArgumentException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static String firstPresent(String... values) {
|
||||
for (String value : values) {
|
||||
if (value != null && !value.isBlank()) return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String parameterValue(SingleCommand command, String key) {
|
||||
CommandParameter parameter = command.get(key);
|
||||
return parameter == null ? null : parameter.getValue();
|
||||
}
|
||||
|
||||
public void loadPrivateChatHistory(int clientId, String uniqueId) {
|
||||
ChatLogStore logs = chatLogs;
|
||||
if (logs == null || uniqueId == null || uniqueId.isBlank()) return;
|
||||
try {
|
||||
ClientEntry c = model.getClient(clientId);
|
||||
ui.onPrivateChatHistory(clientId, c != null ? c.nickname : "Client " + clientId,
|
||||
logs.privateHtmlHistory(uniqueId));
|
||||
} catch (IOException e) {
|
||||
ui.onStatus("Private chat log unavailable: " + rootMessage(e));
|
||||
}
|
||||
}
|
||||
|
||||
void persistIncomingChat(ConnectionListener.ChatScope scope, int fromClientId, String fromUniqueId,
|
||||
String fromName, String message) {
|
||||
ChatLogStore logs = chatLogs;
|
||||
if (logs == null) return;
|
||||
try {
|
||||
logs.appendMessage(toLogScope(scope), false, fromClientId, fromUniqueId, fromName, message);
|
||||
} catch (IOException e) {
|
||||
ui.onStatus("Could not write chat log: " + rootMessage(e));
|
||||
}
|
||||
}
|
||||
|
||||
private void persistOutgoingChat(ConnectionListener.ChatScope scope, int peerClientId, String peerUniqueId,
|
||||
String message) {
|
||||
ChatLogStore logs = chatLogs;
|
||||
if (logs == null) return;
|
||||
try {
|
||||
ClientEntry self = model.getClient(selfClientId);
|
||||
String uid = identity != null ? identity.getUid().toBase64() : "";
|
||||
String name = self != null ? self.nickname : settings.nickname;
|
||||
if (scope == ConnectionListener.ChatScope.PRIVATE) {
|
||||
if (peerUniqueId == null || peerUniqueId.isBlank()) return;
|
||||
logs.appendPrivateMessage(peerUniqueId, true, selfClientId, uid, name, message);
|
||||
} else {
|
||||
logs.appendMessage(toLogScope(scope), true, selfClientId, uid, name, message);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
ui.onStatus("Could not write chat log: " + rootMessage(e));
|
||||
}
|
||||
}
|
||||
|
||||
private static ChatLogScope toLogScope(ConnectionListener.ChatScope scope) {
|
||||
return switch (scope) {
|
||||
case SERVER -> ChatLogScope.SERVER;
|
||||
case CHANNEL -> ChatLogScope.CHANNEL;
|
||||
case PRIVATE -> ChatLogScope.PRIVATE;
|
||||
};
|
||||
}
|
||||
|
||||
/** Records a line in the server tab's log, the way native TS3 reports server activity. */
|
||||
void log(String message) {
|
||||
if (connected) ui.onServerLog(message);
|
||||
if (!connected) return;
|
||||
ChatLogStore logs = chatLogs;
|
||||
if (logs == null) {
|
||||
ui.onServerLog(message);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
ui.onServerLogHtml(logs.appendServerEvent(message));
|
||||
} catch (IOException e) {
|
||||
ui.onServerLog(message);
|
||||
ui.onStatus("Could not write server log: " + rootMessage(e));
|
||||
}
|
||||
}
|
||||
|
||||
String channelName(int channelId) {
|
||||
|
||||
Reference in New Issue
Block a user