98 lines
4.2 KiB
JavaScript
98 lines
4.2 KiB
JavaScript
/* Client-side part of StreamGoose */
|
|
|
|
/* escape HTML entities (for non-server-provided stuff) */
|
|
function esc(s) {
|
|
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
.replace(/"/g, '"').replace(/'/g, ''')
|
|
}
|
|
|
|
/* handle Twitch, YouTube and Kick emotes */
|
|
function handleEmotes(text) {
|
|
var twurl = 'https://static-cdn.jtvnw.net/emoticons/v2/$id/default/dark/1.0'
|
|
var kickurl = 'https://files.kick.com/emotes/$id/fullsize'
|
|
/* Handle Twitch emotes coming from backends */
|
|
text = text.replace(/#TWEMOTE-[^\s]+#/g,
|
|
a => '<img src="' + twurl.replace('$id', a.split('-')[1].slice(0, -1)) + '">')
|
|
/* Handle Kick emotes coming from backends */
|
|
text = text.replace(/#KKEMOTE-[^\s]+#/g,
|
|
a => '<img src="' + kickurl.replace('$id', a.split('-')[1].slice(0, -1)) + '">')
|
|
/* Handle YouTube emotes */
|
|
text = text.replace(/:[^\s]+:/g, a => {
|
|
if (YT_EMOTES.indexOf(a) > -1) /* found in the database */
|
|
return '<img src="/img/yt-emotes/' + a + '.png">'
|
|
else return a
|
|
})
|
|
return text
|
|
}
|
|
|
|
/* detect if there are any overridden styles */
|
|
function overrideStyles(text) {
|
|
var styles = '', re
|
|
if (messageHighlightRules) {
|
|
for (re in messageHighlightRules) {
|
|
if ((new RegExp(re, 'iu')).test(text))
|
|
styles += 'color:' + messageHighlightRules[re] + ';'
|
|
}
|
|
}
|
|
if (styles.length > 0) styles = ' style="' + esc(styles) + '"'
|
|
return styles
|
|
}
|
|
|
|
/* individual message appender */
|
|
function gooseAppendMessage(id, native_id, author_id, type, timestamp, username, text, icon) {
|
|
var html = ""
|
|
if (parseInt(id) > 0 && type.length > 0) {
|
|
if (type == "delmsg") { /* message deletion event */
|
|
var el = document.querySelector('[data-native-id="' + esc(native_id) + '"]')
|
|
if (el) el.parentNode.removeChild(el)
|
|
} else if (type == "deluser") { /* bulk deletion event by username */
|
|
var els = document.querySelectorAll('[data-author-id="' + esc(author_id) + '"]')
|
|
if (els && els.length > 0) {
|
|
var i, l = els.length
|
|
for (i = 0; i < l; i++) els[i].parentNode.removeChild(els[i])
|
|
}
|
|
} else { /* message appending event */
|
|
var classes = type.split('^'), cl = classes.length, j,
|
|
colorpref = null, badges = [], platform, bname
|
|
for (j = 0; j < cl; j++) { /* detect color preference */
|
|
if (classes[j].startsWith('color'))
|
|
colorpref = classes[j].slice(5) /* remove color word */
|
|
if (classes[j].startsWith('badge')) { /* remove the class but cache it */
|
|
badges.push(classes[j].slice(5)) /* remove badge word */
|
|
classes[j] = "" /* remove the class from the list */
|
|
}
|
|
}
|
|
html += '<div data-msg-id="' + id + '" data-native-id="' + esc(native_id) + '" data-author-id="'
|
|
+ esc(author_id) + '" class="' + classes.join(' ') + '">'
|
|
/* handle badges and icons */
|
|
html += '<span class=icon>'
|
|
for (j = 0, cl = badges.length; j < cl; j++) { /* iterate over badges */
|
|
platform = badges[j].slice(0, 2) /* first 2 characters denote the platform */
|
|
bname = badges[j].slice(2) /* the rest is the badge name itself */
|
|
html += '<img class=badge src="/badge/{platform}/{badgename}">'
|
|
.replace('{platform}', platform).replace('{badgename}', bname)
|
|
}
|
|
if (icon.length > 0) /* user icon, if present */
|
|
html += '<img src="' + icon + '">'
|
|
html += '</span>'
|
|
html += '<span class=timestamp>' + timestamp + '</span>'
|
|
html += '<span class=username'
|
|
if (colorpref) html += ' style="color:' + colorpref + '"'
|
|
html += '>' + username + '</span>'
|
|
html += '<span class=msg' + overrideStyles(text) + '>' + handleEmotes(text) + '</span></div>'
|
|
document.getElementById("msglist").innerHTML += html
|
|
}
|
|
}
|
|
/* autoscroll to the end */
|
|
window.setTimeout(function () { window.scrollTo(0, document.body.scrollHeight) }, 150)
|
|
}
|
|
|
|
/* entry point, set up an event source */
|
|
window.addEventListener('DOMContentLoaded', function () {
|
|
var msgSource = new EventSource('/sse')
|
|
msgSource.onmessage = e => {
|
|
var msg = JSON.parse(e.data)
|
|
gooseAppendMessage(msg.id, msg.native_id, msg.author_id, msg.type, msg.timestamp, msg.username, msg.text, msg.icon)
|
|
}
|
|
}, false)
|