diff --git a/ChatTwo/Http/MessageProtocol/DataStructure.cs b/ChatTwo/Http/MessageProtocol/DataStructure.cs index 4c03ac2..9b24fda 100644 --- a/ChatTwo/Http/MessageProtocol/DataStructure.cs +++ b/ChatTwo/Http/MessageProtocol/DataStructure.cs @@ -3,33 +3,69 @@ namespace ChatTwo.Http.MessageProtocol; #region Outgoing SSE +/// +/// Contains the current channel name +/// public struct SwitchChannel(MessageTemplate[] channelName) { [JsonProperty("channelName")] public MessageTemplate[] ChannelName = channelName; } +/// +/// Contains one or multiple channels that are valid for the user to pick from +/// public struct ChannelList(Dictionary channels) { [JsonProperty("channels")] public Dictionary Channels = channels; } +/// +/// Contains one or multiple messages +/// public struct Messages(MessageResponse[] set) { [JsonProperty("messages")] public MessageResponse[] Set = set; } +/// +/// Contains a single message with all its templates and a timestamp +/// public struct MessageResponse() { [JsonProperty("timestamp")] public string Timestamp = ""; [JsonProperty("templates")] public MessageTemplate[] Templates; } +/// +/// Template that is used for the channel name or any message posted to the chatlog +/// 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 + /// + /// empty = Ignore + /// [JsonProperty("payload")] public required string Payload; + /// + /// Used for text and emote. + /// [JsonProperty("content")] public string Content = ""; + + /// + /// Used for icon. + /// [JsonProperty("id")] public uint Id; + + /// + /// Used for text and url + /// [JsonProperty("color")] public uint Color; public static MessageTemplate Empty => new() {Payload = "empty"}; @@ -49,11 +85,19 @@ public struct ErrorResponse(string reason) #endregion #region Incoming POST +/// +/// Message must fulfill the posting requirement +/// Greater than or equal 2 characters +/// Less than or equal 500 characters +/// public struct IncomingMessage() { [JsonProperty("message")] public string Message = string.Empty; } +/// +/// Channel must be a valid uint number +/// public struct IncomingChannel() { [JsonProperty("channel")] public uint Channel = uint.MaxValue; diff --git a/ChatTwo/Http/static/start.js b/ChatTwo/Http/static/start.js index a9fe997..43dbfa3 100644 --- a/ChatTwo/Http/static/start.js +++ b/ChatTwo/Http/static/start.js @@ -74,10 +74,7 @@ updateChannelHint(templates) { this.elements.channelHint.innerHTML = ''; - - for(const template of templates) { - this.elements.channelHint.appendChild(this.processTemplate(template)); - } + this.elements.channelHint.appendChild(this.processTemplate(templates)); } updateChannels(channels) { @@ -131,14 +128,11 @@ const liMessage = document.createElement('li'); const spanTimestamp = document.createElement('span'); spanTimestamp.classList.add('timestamp'); - const spanMessage = document.createElement('span'); - spanMessage.classList.add('message'); - spanTimestamp.innerText = messageData.timestamp; - for(const template of messageData.templates) { - spanMessage.appendChild(this.processTemplate(template)); - } + const spanMessage = document.createElement('span'); + spanMessage.classList.add('message'); + spanMessage.appendChild(this.processTemplate(messageData.templates)) liMessage.appendChild(spanTimestamp); liMessage.appendChild(spanMessage); @@ -149,27 +143,32 @@ } } - 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; + processTemplate(templates) { + const frag = document.createDocumentFragment(); + + for( const template of templates ) { + const spanElement = frag.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': + continue; + } + + frag.appendChild(this.processTemplate(template)); } - return spanElement; + return frag; } processTextTemplate(template, spanContent) { @@ -178,27 +177,26 @@ } processUrlTemplate(template, spanContent) { - // TODO Sanitize href? - let urlElement = document.createElement('a'); + const urlElement = document.createElement('a'); urlElement.innerText = template.content; - urlElement.href = template.content; + urlElement.href = encodeURI(template.content); urlElement.target = '_blank' this.processColor(template, spanContent); } + // converts a RGBA uint number to components 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; + const r = (template.color & 0xFF000000) >>> 24; + const g = (template.color & 0xFF0000) >>> 16; + const b = (template.color & 0xFF00) >>> 8; + const 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'); + const imgElement = document.createElement('img'); imgElement.src = `/emote/${template.content}`; spanContent.classList.add('emote-icon');