Move message processing to javascript, future proofing it for other frameworks
This commit is contained in:
@@ -3,9 +3,9 @@
|
|||||||
namespace ChatTwo.Http.MessageProtocol;
|
namespace ChatTwo.Http.MessageProtocol;
|
||||||
|
|
||||||
#region Outgoing SSE
|
#region Outgoing SSE
|
||||||
public struct SwitchChannel(string name)
|
public struct SwitchChannel(MessageTemplate[] channelName)
|
||||||
{
|
{
|
||||||
[JsonProperty("channel")] public string Name = name;
|
[JsonProperty("channelName")] public MessageTemplate[] ChannelName = channelName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct ChannelList(Dictionary<string, uint> channels)
|
public struct ChannelList(Dictionary<string, uint> channels)
|
||||||
@@ -21,7 +21,18 @@ public struct Messages(MessageResponse[] set)
|
|||||||
public struct MessageResponse()
|
public struct MessageResponse()
|
||||||
{
|
{
|
||||||
[JsonProperty("timestamp")] public string Timestamp = "";
|
[JsonProperty("timestamp")] public string Timestamp = "";
|
||||||
[JsonProperty("messageHTML")] public string Message = "";
|
[JsonProperty("templates")] public MessageTemplate[] Templates;
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct MessageTemplate()
|
||||||
|
{
|
||||||
|
[JsonProperty("payload")] public required string Payload;
|
||||||
|
|
||||||
|
[JsonProperty("content")] public string Content = "";
|
||||||
|
[JsonProperty("id")] public uint Id;
|
||||||
|
[JsonProperty("color")] public uint Color;
|
||||||
|
|
||||||
|
public static MessageTemplate Empty => new() {Payload = "empty"};
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|||||||
+16
-30
@@ -1,5 +1,4 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Net;
|
|
||||||
using ChatTwo.Code;
|
using ChatTwo.Code;
|
||||||
using ChatTwo.Http.MessageProtocol;
|
using ChatTwo.Http.MessageProtocol;
|
||||||
using ChatTwo.Util;
|
using ChatTwo.Util;
|
||||||
@@ -16,9 +15,9 @@ public class Processing
|
|||||||
Plugin = plugin;
|
Plugin = plugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal string ReadChannelName(Chunk[] channelName)
|
internal MessageTemplate[] ReadChannelName(Chunk[] channelName)
|
||||||
{
|
{
|
||||||
return string.Join("", channelName.Select(chunk => ProcessChunk(chunk, noColor: true)));
|
return channelName.Select(ProcessChunk).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async Task<MessageResponse[]> ReadMessageList()
|
internal async Task<MessageResponse[]> ReadMessageList()
|
||||||
@@ -34,12 +33,9 @@ public class Processing
|
|||||||
Timestamp = message.Date.ToLocalTime().ToString("t", !Plugin.Config.Use24HourClock ? null : CultureInfo.CreateSpecificCulture("es-ES"))
|
Timestamp = message.Date.ToLocalTime().ToString("t", !Plugin.Config.Use24HourClock ? null : CultureInfo.CreateSpecificCulture("es-ES"))
|
||||||
};
|
};
|
||||||
|
|
||||||
var content = "";
|
var sender = message.Sender.Select(ProcessChunk);
|
||||||
if (message.Sender.Count > 0)
|
var content = message.Content.Select(ProcessChunk);
|
||||||
content = message.Sender.Aggregate(content, (current, chunk) => current + ProcessChunk(chunk));
|
response.Templates = sender.Concat(content).ToArray();
|
||||||
|
|
||||||
content = message.Content.Aggregate(content, (current, chunk) => current + ProcessChunk(chunk));
|
|
||||||
response.Message = content;
|
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
@@ -56,13 +52,12 @@ public class Processing
|
|||||||
sse.OutboundQueue.Enqueue(new ChannelListEvent(new ChannelList(channels.ToDictionary(pair => pair.Key, pair => (uint)pair.Value))));
|
sse.OutboundQueue.Enqueue(new ChannelListEvent(new ChannelList(channels.ToDictionary(pair => pair.Key, pair => (uint)pair.Value))));
|
||||||
}
|
}
|
||||||
|
|
||||||
private string ProcessChunk(Chunk chunk, bool noColor = false)
|
private MessageTemplate ProcessChunk(Chunk chunk)
|
||||||
{
|
{
|
||||||
if (chunk is IconChunk { } icon)
|
if (chunk is IconChunk { } icon)
|
||||||
{
|
{
|
||||||
return IconUtil.GfdFileView.TryGetEntry((uint) icon.Icon, out _)
|
var iconId = (uint)icon.Icon;
|
||||||
? $"<span class=\"gfd-icon gfd-icon-hq-{(uint)icon.Icon}\"></span>"
|
return IconUtil.GfdFileView.TryGetEntry(iconId, out _) ? new MessageTemplate {Payload = "icon", Id = iconId}: MessageTemplate.Empty;
|
||||||
: "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chunk is TextChunk { } text)
|
if (chunk is TextChunk { } text)
|
||||||
@@ -71,20 +66,18 @@ public class Processing
|
|||||||
{
|
{
|
||||||
var image = EmoteCache.GetEmote(emotePayload.Code);
|
var image = EmoteCache.GetEmote(emotePayload.Code);
|
||||||
|
|
||||||
// The emote name should be safe, it is checked against a list from BTTV.
|
|
||||||
// Still sanitizing it for the extra safety.
|
|
||||||
if (image is { Failed: false })
|
if (image is { Failed: false })
|
||||||
return $"<span class=\"emote-icon\"><img src=\"/emote/{WebUtility.HtmlEncode(emotePayload.Code)}\"></span>";
|
return new MessageTemplate { Payload = "emote", Color = 0, Content = emotePayload.Code };
|
||||||
}
|
}
|
||||||
|
|
||||||
var colour = text.Foreground;
|
var color = text.Foreground;
|
||||||
if (colour == null && text.FallbackColour != null)
|
if (color == null && text.FallbackColour != null)
|
||||||
{
|
{
|
||||||
var type = text.FallbackColour.Value;
|
var type = text.FallbackColour.Value;
|
||||||
colour = Plugin.Config.ChatColours.TryGetValue(type, out var col) ? col : type.DefaultColor();
|
color = Plugin.Config.ChatColours.TryGetValue(type, out var col) ? col : type.DefaultColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
var color = ColourUtil.RgbaToComponents(colour ?? 0);
|
color ??= 0;
|
||||||
|
|
||||||
var userContent = text.Content ?? "";
|
var userContent = text.Content ?? "";
|
||||||
if (Plugin.ChatLogWindow.ScreenshotMode)
|
if (Plugin.ChatLogWindow.ScreenshotMode)
|
||||||
@@ -95,17 +88,10 @@ public class Processing
|
|||||||
userContent = Plugin.ChatLogWindow.HidePlayerInString(userContent, player.Name.TextValue, player.HomeWorld.Id);
|
userContent = Plugin.ChatLogWindow.HidePlayerInString(userContent, player.Name.TextValue, player.HomeWorld.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTML encode any user content to prevent xss
|
var isNotUrl = text.Link is not UriPayload;
|
||||||
userContent = WebUtility.HtmlEncode(userContent);
|
return new MessageTemplate { Payload = isNotUrl ? "text" : "url", Color = color.Value, Content = userContent };
|
||||||
|
|
||||||
if (text.Link is UriPayload uri)
|
|
||||||
userContent = $"<a href=\"{uri.Uri}\" target=\"_blank\">{userContent}</a>";
|
|
||||||
|
|
||||||
return noColor
|
|
||||||
? userContent
|
|
||||||
: $"<span style=\"color:rgba({color.r}, {color.g}, {color.b}, {color.a})\">{userContent}</span>";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return string.Empty;
|
return MessageTemplate.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,8 +72,12 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
updateChannelHint(labelHTML) {
|
updateChannelHint(templates) {
|
||||||
this.elements.channelHint.innerHTML = labelHTML;
|
this.elements.channelHint.innerHTML = '';
|
||||||
|
|
||||||
|
for(const template of templates) {
|
||||||
|
this.elements.channelHint.appendChild(this.processTemplate(template));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateChannels(channels) {
|
updateChannels(channels) {
|
||||||
@@ -131,7 +135,10 @@
|
|||||||
spanMessage.classList.add('message');
|
spanMessage.classList.add('message');
|
||||||
|
|
||||||
spanTimestamp.innerText = messageData.timestamp;
|
spanTimestamp.innerText = messageData.timestamp;
|
||||||
spanMessage.innerHTML = messageData.messageHTML;
|
|
||||||
|
for(const template of messageData.templates) {
|
||||||
|
spanMessage.appendChild(this.processTemplate(template));
|
||||||
|
}
|
||||||
|
|
||||||
liMessage.appendChild(spanTimestamp);
|
liMessage.appendChild(spanTimestamp);
|
||||||
liMessage.appendChild(spanMessage);
|
liMessage.appendChild(spanMessage);
|
||||||
@@ -142,6 +149,67 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
processTemplate(template) {
|
||||||
|
const spanElement = document.createElement('span');
|
||||||
|
switch (template.payload) {
|
||||||
|
case 'text':
|
||||||
|
this.processTextTemplate(template, spanElement);
|
||||||
|
break;
|
||||||
|
case 'url':
|
||||||
|
this.processUrlTemplate(template, spanElement);
|
||||||
|
break;
|
||||||
|
case 'emote':
|
||||||
|
this.processEmote(template, spanElement);
|
||||||
|
break;
|
||||||
|
case 'icon':
|
||||||
|
this.processIcon(template, spanElement);
|
||||||
|
break;
|
||||||
|
case 'empty':
|
||||||
|
// Do nothing
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return spanElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
processTextTemplate(template, spanContent) {
|
||||||
|
spanContent.innerText = template.content;
|
||||||
|
this.processColor(template, spanContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
processUrlTemplate(template, spanContent) {
|
||||||
|
// TODO Sanitize href?
|
||||||
|
let urlElement = document.createElement('a');
|
||||||
|
urlElement.innerText = template.content;
|
||||||
|
urlElement.href = template.content;
|
||||||
|
urlElement.target = '_blank'
|
||||||
|
|
||||||
|
this.processColor(template, spanContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
processColor(template, spanContent) {
|
||||||
|
let r = (template.color & 0xFF000000) >>> 24;
|
||||||
|
let g = (template.color & 0xFF0000) >>> 16;
|
||||||
|
let b = (template.color & 0xFF00) >>> 8;
|
||||||
|
let a = (template.color & 0xFF) / 255.0;
|
||||||
|
|
||||||
|
spanContent.style.color = `rgba(${r}, ${g}, ${b}, ${a})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
processEmote(template, spanContent) {
|
||||||
|
// TODO Sanitize url?
|
||||||
|
let imgElement = document.createElement('img');
|
||||||
|
imgElement.src = `/emote/${template.content}`;
|
||||||
|
|
||||||
|
spanContent.classList.add('emote-icon');
|
||||||
|
spanContent.appendChild(imgElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
processIcon(template, spanContent) {
|
||||||
|
spanContent.classList.add('gfd-icon');
|
||||||
|
spanContent.classList.add(`gfd-icon-hq-${template.id}`);
|
||||||
|
}
|
||||||
|
|
||||||
clearAllMessages() {
|
clearAllMessages() {
|
||||||
this.elements.messagesList.innerHTML = '';
|
this.elements.messagesList.innerHTML = '';
|
||||||
}
|
}
|
||||||
@@ -156,7 +224,7 @@
|
|||||||
|
|
||||||
this.sse.addEventListener('switch-channel', (event) => {
|
this.sse.addEventListener('switch-channel', (event) => {
|
||||||
try {
|
try {
|
||||||
this.updateChannelHint(JSON.parse(event.data).channel);
|
this.updateChannelHint(JSON.parse(event.data).channelName);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user