From cba741a94f6e525301aac6c4fc5c40e38cc88d0a Mon Sep 17 00:00:00 2001 From: doomtube Date: Fri, 9 Jan 2026 21:58:57 -0500 Subject: [PATCH] fixes lol --- backend/src/services/AuthService.cpp | 3 ++ frontend/src/lib/chat/ttsStore.js | 3 ++ .../lib/components/chat/ChatMessage.svelte | 30 ++++++++++- .../src/lib/components/chat/ChatPanel.svelte | 52 ++++++++++++++++--- 4 files changed, 80 insertions(+), 8 deletions(-) diff --git a/backend/src/services/AuthService.cpp b/backend/src/services/AuthService.cpp index 88b74e5..0ee3a3f 100644 --- a/backend/src/services/AuthService.cpp +++ b/backend/src/services/AuthService.cpp @@ -739,6 +739,7 @@ std::string AuthService::generateToken(const UserInfo& user) { .set_payload_claim("is_moderator", jwt::claim(std::to_string(user.isModerator))) .set_payload_claim("is_streamer", jwt::claim(std::to_string(user.isStreamer))) .set_payload_claim("is_restreamer", jwt::claim(std::to_string(user.isRestreamer))) + .set_payload_claim("is_texter", jwt::claim(std::to_string(user.isTexter))) .set_payload_claim("is_disabled", jwt::claim(std::to_string(user.isDisabled))) // SECURITY FIX #26 .set_payload_claim("token_version", jwt::claim(std::to_string(user.tokenVersion))) // SECURITY FIX #10 .set_payload_claim("color_code", jwt::claim( @@ -775,6 +776,8 @@ bool AuthService::validateToken(const std::string& token, UserInfo& userInfo) { decoded.get_payload_claim("is_streamer").as_string() == "1" : false; userInfo.isRestreamer = decoded.has_payload_claim("is_restreamer") ? decoded.get_payload_claim("is_restreamer").as_string() == "1" : false; + userInfo.isTexter = decoded.has_payload_claim("is_texter") ? + decoded.get_payload_claim("is_texter").as_string() == "1" : false; // SECURITY FIX #26: Extract disabled status userInfo.isDisabled = decoded.has_payload_claim("is_disabled") ? diff --git a/frontend/src/lib/chat/ttsStore.js b/frontend/src/lib/chat/ttsStore.js index f645f6a..be6ae28 100644 --- a/frontend/src/lib/chat/ttsStore.js +++ b/frontend/src/lib/chat/ttsStore.js @@ -93,6 +93,9 @@ function cleanTextForTTS(text) { clean = clean.replace(/~~(.+?)~~/g, '$1'); // strikethrough clean = clean.replace(/`(.+?)`/g, '$1'); // code + // Remove markdown heading markers (# ## ### etc.) + clean = clean.replace(/^#{1,6}\s*/gm, ''); + // Remove URLs completely (don't read them aloud) clean = clean.replace(/https?:\/\/\S+/g, ''); diff --git a/frontend/src/lib/components/chat/ChatMessage.svelte b/frontend/src/lib/components/chat/ChatMessage.svelte index 5a696fe..9103a1c 100644 --- a/frontend/src/lib/components/chat/ChatMessage.svelte +++ b/frontend/src/lib/components/chat/ChatMessage.svelte @@ -176,6 +176,15 @@ } return match; }); + + // Replace

:sticker: with marquee-wrapped h1 sticker + msg = msg.replace(/^

:(\w+):$/gim, (match, stickerName) => { + const stickerKey = stickerName.toLowerCase(); + if (stickersMap[stickerKey]) { + return `

${stickerName}

`; + } + return match; + }); } // Replace inline :sticker: with img tags (all modes with stickers enabled) @@ -203,7 +212,7 @@ // Step 5: Sanitize with DOMPurify - allow img tags and safe CSS html = DOMPurify.sanitize(html, { - ALLOWED_TAGS: ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'img', 'br', 'strong', 'em', 'code', 'pre', 'del', 'span'], + ALLOWED_TAGS: ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'img', 'br', 'strong', 'em', 'code', 'pre', 'del', 'span', 'marquee'], ALLOWED_ATTR: ['src', 'alt', 'title', 'class', 'style', 'data-sticker', 'onerror'], FORBID_TAGS: ['a', 'button', 'script'], ALLOW_DATA_ATTR: false @@ -901,6 +910,25 @@ vertical-align: bottom; } + /* Marquee stickers */ + .message-content :global(marquee) { + display: block; + overflow: hidden; + } + + .message-content :global(marquee h1) { + display: inline-block; + margin: 0; + } + + .message-content :global(marquee h1 img) { + max-width: 475px; + max-height: none; + height: auto; + width: auto; + vertical-align: bottom; + } + .mod-menu { position: absolute; right: 0; diff --git a/frontend/src/lib/components/chat/ChatPanel.svelte b/frontend/src/lib/components/chat/ChatPanel.svelte index cb36806..10fa7e2 100644 --- a/frontend/src/lib/components/chat/ChatPanel.svelte +++ b/frontend/src/lib/components/chat/ChatPanel.svelte @@ -251,20 +251,58 @@ } }); - // Watch for content size changes (e.g., when images load) + // Watch for images loading inside messages (e.g., stickers) // This ensures autoscroll works even when images finish loading after message is added - let resizeObserver; + let mutationObserver; if (messagesContainer) { - resizeObserver = new ResizeObserver(() => { - scrollToBottom(); + // Track images we've already added listeners to + const trackedImages = new WeakSet(); + + const attachImageLoadListeners = (container) => { + const images = container.querySelectorAll('img'); + images.forEach(img => { + if (!trackedImages.has(img)) { + trackedImages.add(img); + if (!img.complete) { + img.addEventListener('load', scrollToBottom, { once: true }); + } + } + }); + }; + + // Attach to existing images + attachImageLoadListeners(messagesContainer); + + // Watch for new images being added + mutationObserver = new MutationObserver((mutations) => { + for (const mutation of mutations) { + if (mutation.type === 'childList') { + mutation.addedNodes.forEach(node => { + if (node.nodeType === Node.ELEMENT_NODE) { + attachImageLoadListeners(node); + // Also check if the node itself is an image + if (node.tagName === 'IMG' && !trackedImages.has(node)) { + trackedImages.add(node); + if (!node.complete) { + node.addEventListener('load', scrollToBottom, { once: true }); + } + } + } + }); + } + } + }); + + mutationObserver.observe(messagesContainer, { + childList: true, + subtree: true }); - resizeObserver.observe(messagesContainer); } return () => { unsubscribe(); - if (resizeObserver) { - resizeObserver.disconnect(); + if (mutationObserver) { + mutationObserver.disconnect(); } }; });