From 4b94c6e30e2ed634c33dd48c07c17bf8f8f744cc Mon Sep 17 00:00:00 2001 From: Infi Date: Mon, 17 Nov 2025 17:48:53 +0100 Subject: [PATCH] - Identify web payloads better - Switch to IconId field name - Add unique id to every message - Automate nodejs build step via csproj - Add unread color to tab opener - Add unread number to tab name - Update ImageSharp dep --- ChatTwo.Tests/ChatTwo.Tests.csproj | 4 --- ChatTwo/ChatTwo.csproj | 27 +++++++-------- .../Frontend/src/components/TabPane.svelte | 7 ++-- .../src/components/TabPaneOpener.svelte | 4 +-- ChatTwo/Http/Frontend/src/lib/chat.svelte.ts | 33 +++++++++++-------- ChatTwo/Http/Frontend/src/lib/payload.ts | 25 ++++++++++++++ ChatTwo/Http/HostContext.cs | 2 +- ChatTwo/Http/MessageProtocol/DataStructure.cs | 18 ++++------ ChatTwo/Http/MessageProtocol/PayloadType.cs | 31 +++++++++++++++++ ChatTwo/Http/Processing.cs | 6 ++-- ChatTwo/MessageManager.cs | 17 +++++++--- ChatTwo/packages.lock.json | 6 ++-- 12 files changed, 121 insertions(+), 59 deletions(-) create mode 100644 ChatTwo/Http/Frontend/src/lib/payload.ts create mode 100644 ChatTwo/Http/MessageProtocol/PayloadType.cs diff --git a/ChatTwo.Tests/ChatTwo.Tests.csproj b/ChatTwo.Tests/ChatTwo.Tests.csproj index 984b19b..3af4180 100644 --- a/ChatTwo.Tests/ChatTwo.Tests.csproj +++ b/ChatTwo.Tests/ChatTwo.Tests.csproj @@ -38,10 +38,6 @@ $(DalamudLibPath)\FFXIVClientStructs.dll false - - $(DalamudLibPath)\ImGui.NET.dll - false - $(DalamudLibPath)\Lumina.dll false diff --git a/ChatTwo/ChatTwo.csproj b/ChatTwo/ChatTwo.csproj index 85dde5c..00ad333 100755 --- a/ChatTwo/ChatTwo.csproj +++ b/ChatTwo/ChatTwo.csproj @@ -1,6 +1,6 @@ - 1.31.2 + 1.32.0 net9.0-windows enable enable @@ -17,7 +17,7 @@ - + @@ -40,15 +40,16 @@ - - - Always - - - Always - - - Always - - + + + + + + + + + + + + diff --git a/ChatTwo/Http/Frontend/src/components/TabPane.svelte b/ChatTwo/Http/Frontend/src/components/TabPane.svelte index 5e2cc42..2298655 100644 --- a/ChatTwo/Http/Frontend/src/components/TabPane.svelte +++ b/ChatTwo/Http/Frontend/src/components/TabPane.svelte @@ -25,8 +25,9 @@ } function ontransitionend() { - if (scrolledToBottom) + if (scrolledToBottom) { scrollMessagesToBottom(); + } } @@ -50,9 +51,9 @@
    {#each knownTabs as tab} -
  1. +
  2. {/each} diff --git a/ChatTwo/Http/Frontend/src/components/TabPaneOpener.svelte b/ChatTwo/Http/Frontend/src/components/TabPaneOpener.svelte index 5971efe..a03e8df 100644 --- a/ChatTwo/Http/Frontend/src/components/TabPaneOpener.svelte +++ b/ChatTwo/Http/Frontend/src/components/TabPaneOpener.svelte @@ -1,5 +1,5 @@ - diff --git a/ChatTwo/Http/Frontend/src/lib/chat.svelte.ts b/ChatTwo/Http/Frontend/src/lib/chat.svelte.ts index 76f41b9..d27ffe1 100644 --- a/ChatTwo/Http/Frontend/src/lib/chat.svelte.ts +++ b/ChatTwo/Http/Frontend/src/lib/chat.svelte.ts @@ -1,4 +1,5 @@ import { channelOptions, isChannelLocked, selectedTab, knownTabs, chatInput, messagesList, scrollMessagesToBottom } from "$lib/shared.svelte"; +import { WebPayloadType } from "$lib/payload"; import { source, type Source } from "sveltekit-sse"; interface ChatElements { @@ -17,15 +18,16 @@ interface Messages { // ref `DataStructure.MessageResponse` interface MessageResponse { + id: string; timestamp: string; templates: Template[]; } // ref `DataStructure.MessageTemplate` interface Template { - id: number; - payload: string; + payloadType: WebPayloadType; content: string; + iconId: number; color: number; } @@ -73,9 +75,6 @@ export class ChatTwoWeb { setupDOMElements() { this.elements = { - // channelHint: document.getElementById('channel-hint'), - // channelSelect: document.getElementById('channel-select'), - messagesContainer: document.querySelector('#messages > .scroll-container')!, messagesList: document.getElementById('messages-list'), @@ -209,20 +208,20 @@ export class ChatTwoWeb { for( const template of templates ) { const spanElement = document.createElement('span'); - switch (template.payload) { - case 'text': + switch (template.payloadType) { + case WebPayloadType.RawText: this.processTextTemplate(template, spanElement); break; - case 'url': + case WebPayloadType.CustomUri: this.processUrlTemplate(template, spanElement); break; - case 'emote': + case WebPayloadType.CustomEmote: this.processEmote(template, spanElement); break; - case 'icon': + case WebPayloadType.Icon: this.processIcon(template, spanElement); break; - case 'empty': + default: continue; } @@ -272,7 +271,7 @@ export class ChatTwoWeb { processIcon(template: Template, spanElement: HTMLSpanElement) { spanElement.classList.add('gfd-icon'); - spanElement.classList.add(`gfd-icon-hq-${template.id}`); + spanElement.classList.add(`gfd-icon-hq-${template.iconId}`); } clearAllMessages() { @@ -378,10 +377,18 @@ export class ChatTwoWeb { // the unread state of a specific tab has changed this.connection.select('tab-unread-state').subscribe((data: string) => { - console.log(`tab-unread-state: ${data}`) + console.log(`tab-unread-state`, data) if (data) { try { const chatTabUnreadState: ChatTabUnreadState = JSON.parse(data); + let tab = knownTabs.find((tab) => tab.index === chatTabUnreadState.index); + if (tab) { + tab.unreadCount = chatTabUnreadState.unreadCount; + } + else { + console.error("Unable to find tab!") + console.error(chatTabUnreadState) + } } catch (error) { console.error(error); } diff --git a/ChatTwo/Http/Frontend/src/lib/payload.ts b/ChatTwo/Http/Frontend/src/lib/payload.ts new file mode 100644 index 0000000..ec3ad97 --- /dev/null +++ b/ChatTwo/Http/Frontend/src/lib/payload.ts @@ -0,0 +1,25 @@ +export enum WebPayloadType { + // Dalamud + Unknown, + Player, + Item, + Status, + RawText, + UIForeground, + UIGlow, + MapLink, + AutoTranslateText, + EmphasisItalic, + Icon, + Quest, + DalamudLink, + NewLine, + SeHyphen, + PartyFinder, + + // Custom + CustomPartyFinder = 0x50, + CustomAchievement = 0x51, + CustomUri = 0x52, + CustomEmote = 0x53, +} \ No newline at end of file diff --git a/ChatTwo/Http/HostContext.cs b/ChatTwo/Http/HostContext.cs index ae8d023..944f8ef 100644 --- a/ChatTwo/Http/HostContext.cs +++ b/ChatTwo/Http/HostContext.cs @@ -18,7 +18,7 @@ public class HostContext public readonly List EventConnections = []; public readonly CancellationTokenSource TokenSource = new(); - public readonly string StaticDir = Path.Combine(Plugin.Interface.AssemblyLocation.DirectoryName!, "Http/Frontend/build"); + public readonly string StaticDir = Path.Combine(Plugin.Interface.AssemblyLocation.DirectoryName!, "Frontend/"); public HostContext(ServerCore core) { diff --git a/ChatTwo/Http/MessageProtocol/DataStructure.cs b/ChatTwo/Http/MessageProtocol/DataStructure.cs index d4a8eb5..432567c 100644 --- a/ChatTwo/Http/MessageProtocol/DataStructure.cs +++ b/ChatTwo/Http/MessageProtocol/DataStructure.cs @@ -61,6 +61,7 @@ public struct Messages(MessageResponse[] set) /// public struct MessageResponse() { + [JsonProperty("id")] public Guid Id = Guid.NewGuid(); [JsonProperty("timestamp")] public string Timestamp = ""; [JsonProperty("templates")] public MessageTemplate[] Templates; } @@ -71,17 +72,10 @@ public struct MessageResponse() public struct MessageTemplate() { /// - /// Template type - /// - /// icon = a game icon - /// emote = BetterTTV emote - /// url = Simple url that should be clickable - /// text = Simple text content of the message - /// - /// Note: - /// Empty is used for invalid payloads + /// The type of payload. + /// Dalamuds enum is just a baseline, there exists more that are expressed through raw values. /// - [JsonProperty("payload")] public required string Payload; + [JsonProperty("payloadType")] public WebPayloadType PayloadType = WebPayloadType.Unknown; /// /// Used for text and emote. @@ -91,7 +85,7 @@ public struct MessageTemplate() /// /// Used for an icon. /// - [JsonProperty("id")] public uint Id; + [JsonProperty("iconId")] public uint IconId; /// /// Used for text and url @@ -101,7 +95,7 @@ public struct MessageTemplate() /// [JsonProperty("color")] public uint Color; - public static MessageTemplate Empty => new() {Payload = "empty"}; + public static MessageTemplate Empty => new(); } #endregion diff --git a/ChatTwo/Http/MessageProtocol/PayloadType.cs b/ChatTwo/Http/MessageProtocol/PayloadType.cs new file mode 100644 index 0000000..08c603e --- /dev/null +++ b/ChatTwo/Http/MessageProtocol/PayloadType.cs @@ -0,0 +1,31 @@ +namespace ChatTwo.Http.MessageProtocol; + +/// +/// Baseline: +/// +public enum WebPayloadType +{ + // Dalamud + Unknown, + Player, + Item, + Status, + RawText, + UIForeground, + UIGlow, + MapLink, + AutoTranslateText, + EmphasisItalic, + Icon, + Quest, + DalamudLink, + NewLine, + SeHyphen, + PartyFinder, + + // Custom + CustomPartyFinder = 0x50, + CustomAchievement = 0x51, + CustomUri = 0x52, + CustomEmote = 0x53, +} \ No newline at end of file diff --git a/ChatTwo/Http/Processing.cs b/ChatTwo/Http/Processing.cs index 494b1ec..376162f 100644 --- a/ChatTwo/Http/Processing.cs +++ b/ChatTwo/Http/Processing.cs @@ -46,7 +46,7 @@ public class Processing if (chunk is IconChunk { } icon) { var iconId = (uint)icon.Icon; - return IconUtil.GfdFileView.TryGetEntry(iconId, out _) ? new MessageTemplate {Payload = "icon", Id = iconId}: MessageTemplate.Empty; + return IconUtil.GfdFileView.TryGetEntry(iconId, out _) ? new MessageTemplate {PayloadType = WebPayloadType.Icon, IconId = iconId}: MessageTemplate.Empty; } if (chunk is TextChunk { } text) @@ -56,7 +56,7 @@ public class Processing var image = EmoteCache.GetEmote(emotePayload.Code); if (image is { Failed: false }) - return new MessageTemplate { Payload = "emote", Color = 0, Content = emotePayload.Code }; + return new MessageTemplate { PayloadType = WebPayloadType.CustomEmote, Color = 0, Content = emotePayload.Code }; } var color = text.Foreground; @@ -78,7 +78,7 @@ public class Processing } var isNotUrl = text.Link is not UriPayload; - return new MessageTemplate { Payload = isNotUrl ? "text" : "url", Color = color.Value, Content = userContent }; + return new MessageTemplate { PayloadType = isNotUrl ? WebPayloadType.RawText : WebPayloadType.CustomUri, Color = color.Value, Content = userContent }; } return MessageTemplate.Empty; diff --git a/ChatTwo/MessageManager.cs b/ChatTwo/MessageManager.cs index 9e7b850..91deb51 100644 --- a/ChatTwo/MessageManager.cs +++ b/ChatTwo/MessageManager.cs @@ -218,12 +218,19 @@ internal class MessageManager : IAsyncDisposable // called for each message. private unsafe void ContentIdResolver(RaptureLogModule* agent, ulong contentId, ulong accountId, int messageIndex, ushort worldId, ushort chatType) { - ContentIdResolverHook?.Original(agent, contentId, accountId, messageIndex, worldId, chatType); - if (PendingSync.Count == 0) - return; + try + { + ContentIdResolverHook?.Original(agent, contentId, accountId, messageIndex, worldId, chatType); + if (PendingSync.Count == 0) + return; - PendingSync.Last().ContentId = contentId; - PendingSync.Last().AccountId = accountId; + PendingSync.Last().ContentId = contentId; + PendingSync.Last().AccountId = accountId; + } + catch (Exception ex) + { + Plugin.Log.Error(ex, "Error in ContentIdResolver"); + } } private void ProcessMessage(PendingMessage pendingMessage) diff --git a/ChatTwo/packages.lock.json b/ChatTwo/packages.lock.json index acbadf0..aad3b57 100644 --- a/ChatTwo/packages.lock.json +++ b/ChatTwo/packages.lock.json @@ -44,9 +44,9 @@ }, "SixLabors.ImageSharp": { "type": "Direct", - "requested": "[3.1.11, )", - "resolved": "3.1.11", - "contentHash": "JfPLyigLthuE50yi6tMt7Amrenr/fA31t2CvJyhy/kQmfulIBAqo5T/YFUSRHtuYPXRSaUHygFeh6Qd933EoSw==" + "requested": "[3.1.12, )", + "resolved": "3.1.12", + "contentHash": "iAg6zifihXEFS/t7fiHhZBGAdCp3FavsF4i2ZIDp0JfeYeDVzvmlbY1CNhhIKimaIzrzSi5M/NBFcWvZT2rB/A==" }, "Watson.Lite": { "type": "Direct",