From c9aac6e4f195dce48f16b0b7a9b06f788ba383b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Kr=C3=BChlmann?= Date: Tue, 13 Jan 2026 22:00:24 +0100 Subject: [PATCH] Include twitch message metadata --- .../chat-overlay/source.js | 71 +++++++++++++++++-- src/main/resources/static/js/broadcast.js | 22 +++--- .../static/js/broadcast/twitchChat.js | 9 ++- 3 files changed, 87 insertions(+), 15 deletions(-) diff --git a/doc/marketplace-scripts/chat-overlay/source.js b/doc/marketplace-scripts/chat-overlay/source.js index d6faa81..99a5e8c 100644 --- a/doc/marketplace-scripts/chat-overlay/source.js +++ b/doc/marketplace-scripts/chat-overlay/source.js @@ -29,9 +29,62 @@ function formatLines(messages, ctx, width) { const maxWidth = Math.max(width - PADDING * 2, 0); const lines = []; messages.forEach((message) => { - const prefix = message.displayName ? `${message.displayName}: ` : ""; - const raw = `${prefix}${message.message || ""}`; - wrapLine(ctx, raw, maxWidth).forEach((line) => lines.push(line)); + const prefixText = message.displayName ? `${message.displayName}: ` : ""; + const bodyText = message.message || ""; + const nameColor = message.tags?.color || "#ffffff"; + if (!prefixText) { + wrapLine(ctx, bodyText, maxWidth).forEach((line) => + lines.push({ + prefixText: "", + prefixWidth: 0, + nameColor, + text: line, + }), + ); + return; + } + + const prefixWidth = ctx.measureText(prefixText).width; + const words = bodyText.split(" "); + let current = ""; + let isFirstLine = true; + let availableWidth = Math.max(maxWidth - prefixWidth, 0); + + const flushLine = () => { + lines.push({ + prefixText: isFirstLine ? prefixText : "", + prefixWidth: isFirstLine ? prefixWidth : 0, + nameColor, + text: current, + }); + current = ""; + isFirstLine = false; + availableWidth = maxWidth; + }; + + if (!words.length || !bodyText.trim()) { + lines.push({ + prefixText, + prefixWidth, + nameColor, + text: "", + }); + return; + } + + words.forEach((word) => { + const test = current ? `${current} ${word}` : word; + if (ctx.measureText(test).width > availableWidth && current) { + flushLine(); + current = word; + } else { + current = test; + } + }); + + if (current) { + flushLine(); + } }); return lines.slice(-MAX_LINES); } @@ -53,15 +106,21 @@ function tick(context) { const lines = formatLines(messages, ctx, width); const boxHeight = lines.length * LINE_HEIGHT + PADDING * 2; const boxWidth = Math.max( - ...lines.map((line) => ctx.measureText(line).width), + ...lines.map((line) => line.prefixWidth + ctx.measureText(line.text).width), 120, ); ctx.fillStyle = "rgba(0, 0, 0, 0.55)"; ctx.fillRect(PADDING, height - boxHeight - PADDING, boxWidth + PADDING * 2, boxHeight); - ctx.fillStyle = "#ffffff"; lines.forEach((line, index) => { - ctx.fillText(line, PADDING * 2, height - boxHeight - PADDING + PADDING + index * LINE_HEIGHT); + const x = PADDING * 2; + const y = height - boxHeight - PADDING + PADDING + index * LINE_HEIGHT; + if (line.prefixText) { + ctx.fillStyle = line.nameColor || "#ffffff"; + ctx.fillText(line.prefixText, x, y); + } + ctx.fillStyle = "#ffffff"; + ctx.fillText(line.text, x + line.prefixWidth, y); }); } diff --git a/src/main/resources/static/js/broadcast.js b/src/main/resources/static/js/broadcast.js index a1a5a50..517629f 100644 --- a/src/main/resources/static/js/broadcast.js +++ b/src/main/resources/static/js/broadcast.js @@ -7,14 +7,20 @@ const scriptLayer = document.getElementById("broadcast-script-layer"); setUpElectronWindowFrame(); const renderer = new BroadcastRenderer({ canvas, scriptLayer, broadcaster, showToast }); -const disconnectChat = connectTwitchChat(broadcaster, ({ channel, displayName, message }) => { - console.log(`[twitch:${broadcaster}] ${displayName}: ${message}`); - renderer.receiveChatMessage({ - channel, - displayName, - message, - }); -}); +const disconnectChat = connectTwitchChat( + broadcaster, + ({ channel, displayName, message, tags, prefix, raw }) => { + console.log(`[twitch:${broadcaster}] ${displayName}: ${message}`); + renderer.receiveChatMessage({ + channel, + displayName, + message, + tags, + prefix, + raw, + }); + }, +); setUpElectronWindowResizeListener(canvas); renderer.start(); diff --git a/src/main/resources/static/js/broadcast/twitchChat.js b/src/main/resources/static/js/broadcast/twitchChat.js index a5e9f6e..1580a7f 100644 --- a/src/main/resources/static/js/broadcast/twitchChat.js +++ b/src/main/resources/static/js/broadcast/twitchChat.js @@ -47,6 +47,7 @@ const parseIrcMessage = (rawLine) => { params, prefix, tags, + raw: rawLine, }; }; @@ -64,6 +65,9 @@ const extractChatMessage = (ircMessage) => { channel, displayName, message, + tags: ircMessage.tags, + prefix: ircMessage.prefix, + raw: ircMessage.raw, }; }; @@ -80,7 +84,7 @@ export const connectTwitchChat = (channelName, onMessage = console.log) => { socket.addEventListener("open", () => { socket.send(`PASS ${ANON_PASSWORD}`); socket.send(`NICK ${nick}`); - socket.send("CAP REQ :twitch.tv/tags twitch.tv/commands"); + socket.send("CAP REQ :twitch.tv/tags twitch.tv/commands twitch.tv/membership"); socket.send(`JOIN #${normalizedChannel}`); }); @@ -102,6 +106,9 @@ export const connectTwitchChat = (channelName, onMessage = console.log) => { channel: chatMessage.channel, displayName: chatMessage.displayName, message: chatMessage.message, + tags: chatMessage.tags, + prefix: chatMessage.prefix, + raw: chatMessage.raw, }); } });