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) {
|
||||
|
||||
@@ -29,6 +29,7 @@ import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@@ -138,13 +139,30 @@ public final class ChatPanel extends JPanel {
|
||||
|
||||
/** Opens (or focuses) the private chat with {@code clientId}. */
|
||||
public void openPrivateChat(int clientId, String nickname) {
|
||||
openPrivateChat(clientId, "", nickname);
|
||||
}
|
||||
|
||||
/** Opens (or focuses) the private chat with {@code clientId}. */
|
||||
public void openPrivateChat(int clientId, String uniqueId, String nickname) {
|
||||
edt(() -> {
|
||||
Tab tab = privateTab(clientId, nickname);
|
||||
Tab tab = privateTab(clientId, uniqueId, nickname);
|
||||
tabs.setSelectedComponent(tab.scroll);
|
||||
input.requestFocusInWindow();
|
||||
});
|
||||
}
|
||||
|
||||
public void setServerHistory(List<String> htmlEntries) {
|
||||
edt(() -> serverTab.setHistory(htmlEntries));
|
||||
}
|
||||
|
||||
public void setChannelHistory(List<String> htmlEntries) {
|
||||
edt(() -> channelTab.setHistory(htmlEntries));
|
||||
}
|
||||
|
||||
public void setPrivateHistory(int clientId, String nickname, List<String> htmlEntries) {
|
||||
edt(() -> privateTab(clientId, "", nickname).setHistory(htmlEntries));
|
||||
}
|
||||
|
||||
/** Drops every private chat tab, e.g. after disconnecting. */
|
||||
public void closePrivateChats() {
|
||||
edt(() -> {
|
||||
@@ -214,17 +232,35 @@ public final class ChatPanel extends JPanel {
|
||||
+ BBCode.linksToHtml(text) + "</span>"));
|
||||
}
|
||||
|
||||
public void appendServerLogHtml(String html) {
|
||||
edt(() -> serverTab.appendRawHtml(html));
|
||||
}
|
||||
|
||||
public void appendServerMessage(int fromId, String from, String text) {
|
||||
edt(() -> serverTab.appendMessage(fromId, from, text));
|
||||
appendServerMessage(fromId, "", from, text);
|
||||
}
|
||||
|
||||
public void appendServerMessage(int fromId, String uniqueId, String from, String text) {
|
||||
edt(() -> serverTab.appendMessage(fromId, uniqueId, from, text));
|
||||
}
|
||||
|
||||
public void appendChannelMessage(int fromId, String from, String text) {
|
||||
edt(() -> channelTab.appendMessage(fromId, from, text));
|
||||
appendChannelMessage(fromId, "", from, text);
|
||||
}
|
||||
|
||||
public void appendChannelMessage(int fromId, String uniqueId, String from, String text) {
|
||||
edt(() -> channelTab.appendMessage(fromId, uniqueId, from, text));
|
||||
}
|
||||
|
||||
/** Appends to the private chat with {@code peerId}, creating the tab if needed. */
|
||||
public void appendPrivateMessage(int peerId, String peerName, int fromId, String from, String text) {
|
||||
edt(() -> privateTab(peerId, peerName).appendMessage(fromId, from, text));
|
||||
appendPrivateMessage(peerId, "", peerName, fromId, "", from, text);
|
||||
}
|
||||
|
||||
/** Appends to the private chat with {@code peerId}, creating the tab if needed. */
|
||||
public void appendPrivateMessage(int peerId, String peerUniqueId, String peerName,
|
||||
int fromId, String fromUniqueId, String from, String text) {
|
||||
edt(() -> privateTab(peerId, peerUniqueId, peerName).appendMessage(fromId, fromUniqueId, from, text));
|
||||
}
|
||||
|
||||
private String stamp() {
|
||||
@@ -232,11 +268,17 @@ public final class ChatPanel extends JPanel {
|
||||
}
|
||||
|
||||
private Tab privateTab(int clientId, String nickname) {
|
||||
return privateTab(clientId, "", nickname);
|
||||
}
|
||||
|
||||
private Tab privateTab(int clientId, String uniqueId, String nickname) {
|
||||
Tab tab = privateTabs.get(clientId);
|
||||
if (tab == null) {
|
||||
tab = new Tab(Target.PRIVATE, clientId, nickname, Icons.privateChat());
|
||||
tab = new Tab(Target.PRIVATE, clientId, uniqueId, nickname, Icons.privateChat());
|
||||
privateTabs.put(clientId, tab);
|
||||
addTab(tab, true);
|
||||
} else {
|
||||
tab.setClientUniqueId(uniqueId);
|
||||
}
|
||||
return tab;
|
||||
}
|
||||
@@ -301,6 +343,7 @@ public final class ChatPanel extends JPanel {
|
||||
|
||||
final Target target;
|
||||
final int clientId;
|
||||
private String clientUniqueId;
|
||||
private String title;
|
||||
final JEditorPane log = HtmlStyles.pane("font-family:sans-serif; font-size:11px; margin:4px 6px;");
|
||||
final JScrollPane scroll;
|
||||
@@ -311,17 +354,23 @@ public final class ChatPanel extends JPanel {
|
||||
private JLabel titleLabel;
|
||||
|
||||
Tab(Target target, int clientId, String title, Icon icon) {
|
||||
this(target, clientId, title, icon, null, null);
|
||||
this(target, clientId, "", title, icon, null, null);
|
||||
}
|
||||
|
||||
Tab(Target target, int clientId, String clientUniqueId, String title, Icon icon) {
|
||||
this(target, clientId, clientUniqueId, title, icon, null, null);
|
||||
}
|
||||
|
||||
/** A static-content tab: no send target, closing it runs {@code onClose}. */
|
||||
Tab(String noteKey, Icon icon, String title, Runnable onClose) {
|
||||
this(null, 0, title, icon, noteKey, onClose);
|
||||
this(null, 0, "", title, icon, noteKey, onClose);
|
||||
}
|
||||
|
||||
private Tab(Target target, int clientId, String title, Icon icon, String noteKey, Runnable onClose) {
|
||||
private Tab(Target target, int clientId, String clientUniqueId, String title,
|
||||
Icon icon, String noteKey, Runnable onClose) {
|
||||
this.target = target;
|
||||
this.clientId = clientId;
|
||||
this.clientUniqueId = clientUniqueId == null ? "" : clientUniqueId;
|
||||
this.title = title;
|
||||
this.icon = icon;
|
||||
this.noteKey = noteKey;
|
||||
@@ -338,6 +387,10 @@ public final class ChatPanel extends JPanel {
|
||||
scroll.setBorder(BorderFactory.createEmptyBorder());
|
||||
}
|
||||
|
||||
void setClientUniqueId(String uniqueId) {
|
||||
if (uniqueId != null && !uniqueId.isBlank()) clientUniqueId = uniqueId;
|
||||
}
|
||||
|
||||
private void onLink(String href) {
|
||||
if (href == null) return;
|
||||
TsLink.Ref ref = TsLink.parse(href);
|
||||
@@ -405,16 +458,27 @@ public final class ChatPanel extends JPanel {
|
||||
titleLabel.setForeground(unread ? Theme.accent() : null);
|
||||
}
|
||||
|
||||
void appendMessage(int fromId, String from, String text) {
|
||||
void appendMessage(int fromId, String fromUniqueId, String from, String text) {
|
||||
String name = BBCode.escape(from);
|
||||
String sender = fromId > 0
|
||||
? "<a class=\"" + BBCode.IDENTITY_LINK_CLASS + "\" href=\""
|
||||
+ BBCode.escape(TsLink.clientHref(fromId, "", from)) + "\">" + name + "</a>"
|
||||
+ BBCode.escape(TsLink.clientHref(fromId,
|
||||
fromUniqueId == null || fromUniqueId.isBlank() ? clientUniqueId : fromUniqueId,
|
||||
from)) + "\">" + name + "</a>"
|
||||
: "<span class=\"name\">" + name + "</span>";
|
||||
appendLine("<span class=\"muted\">" + stamp() + "</span>"
|
||||
+ sender + ": " + BBCode.toHtml(text));
|
||||
}
|
||||
|
||||
void setHistory(List<String> htmlEntries) {
|
||||
StringBuilder html = new StringBuilder();
|
||||
if (htmlEntries != null) {
|
||||
for (String entry : htmlEntries) html.append(normalizeTs3LogEntry(entry));
|
||||
}
|
||||
log.setText("<html><body><div id=\"chatlog\">" + html + "</div></body></html>");
|
||||
log.setCaretPosition(log.getDocument().getLength());
|
||||
}
|
||||
|
||||
/** Replaces the whole log with fixed content; used by static-content tabs. */
|
||||
void setStaticContent(String html) {
|
||||
log.setText("<html><body><div id=\"chatlog\">" + html + "</div></body></html>");
|
||||
@@ -422,13 +486,26 @@ public final class ChatPanel extends JPanel {
|
||||
}
|
||||
|
||||
void appendLine(String html) {
|
||||
appendRawHtml("<div>" + html + "</div>");
|
||||
}
|
||||
|
||||
void appendRawHtml(String html) {
|
||||
try {
|
||||
HTMLDocument doc = (HTMLDocument) log.getDocument();
|
||||
doc.insertBeforeEnd(doc.getElement("chatlog"), "<div>" + html + "</div>");
|
||||
doc.insertBeforeEnd(doc.getElement("chatlog"), normalizeTs3LogEntry(html));
|
||||
log.setCaretPosition(doc.getLength());
|
||||
} catch (BadLocationException | IOException ignored) {
|
||||
}
|
||||
if (tabs.getSelectedComponent() != scroll) setUnread(true);
|
||||
}
|
||||
|
||||
private String normalizeTs3LogEntry(String html) {
|
||||
if (html == null || html.isBlank()) return "";
|
||||
String normalized = html
|
||||
.replaceAll("(?i)<img\\b[^>]*>", "")
|
||||
.replaceFirst("(?i)^\\s*<p\\b", "<div")
|
||||
.replaceFirst("(?i)</p>\\s*$", "</div>");
|
||||
return normalized.startsWith("<div") ? normalized : "<div>" + normalized + "</div>";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.ts3client.net.TeamspeakConnection;
|
||||
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.SwingUtilities;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Marshals {@link ConnectionListener} callbacks onto the EDT and fans them out to
|
||||
@@ -129,18 +130,34 @@ final class ServerTabConnectionEvents implements ConnectionListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChat(ChatScope scope, int fromClientId, String fromName, String message) {
|
||||
switch (scope) {
|
||||
case PRIVATE:
|
||||
chatPanel.appendPrivateMessage(fromClientId, fromName, fromClientId, fromName, message);
|
||||
break;
|
||||
case SERVER:
|
||||
chatPanel.appendServerMessage(fromClientId, fromName, message);
|
||||
break;
|
||||
default:
|
||||
chatPanel.appendChannelMessage(fromClientId, fromName, message);
|
||||
break;
|
||||
}
|
||||
public void onChat(ChatScope scope, int fromClientId, String fromUniqueId, String fromName, String message) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
switch (scope) {
|
||||
case PRIVATE:
|
||||
chatPanel.appendPrivateMessage(fromClientId, fromUniqueId, fromName,
|
||||
fromClientId, fromUniqueId, fromName, message);
|
||||
break;
|
||||
case SERVER:
|
||||
chatPanel.appendServerMessage(fromClientId, fromUniqueId, fromName, message);
|
||||
break;
|
||||
default:
|
||||
chatPanel.appendChannelMessage(fromClientId, fromUniqueId, fromName, message);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChatHistory(ChatScope scope, List<String> htmlEntries) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
if (scope == ChatScope.SERVER) chatPanel.setServerHistory(htmlEntries);
|
||||
else if (scope == ChatScope.CHANNEL) chatPanel.setChannelHistory(htmlEntries);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPrivateChatHistory(int clientId, String nickname, List<String> htmlEntries) {
|
||||
SwingUtilities.invokeLater(() -> chatPanel.setPrivateHistory(clientId, nickname, htmlEntries));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -165,6 +182,11 @@ final class ServerTabConnectionEvents implements ConnectionListener {
|
||||
SwingUtilities.invokeLater(() -> chatPanel.appendServerLog(message));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServerLogHtml(String html) {
|
||||
SwingUtilities.invokeLater(() -> chatPanel.appendServerLogHtml(html));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPoke(String fromName, String message) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
|
||||
@@ -123,7 +123,8 @@ final class ServerTabTreeActions implements ServerTreePanel.Actions {
|
||||
|
||||
@Override
|
||||
public void openPrivateChat(ClientEntry client) {
|
||||
chatPanel.openPrivateChat(client.id, client.nickname);
|
||||
chatPanel.openPrivateChat(client.id, client.uniqueId, client.nickname);
|
||||
conn.loadPrivateChatHistory(client.id, client.uniqueId);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user