From 1678117d16a954ec7b92c863ed9a007b7ddc52e0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Yusuf=20=C4=B0pek?=
Date: Sun, 12 Oct 2025 21:37:53 +0300
Subject: [PATCH] feat: add support for super stickers in chat and overlay
components, including image display, accessibility labels, and backend
parsing enhancements
---
.gitignore | 25 ++++++++++++++++++++++
backend/src/index.ts | 11 +++++++---
backend/src/ingestion/youtubei.ts | 35 +++++++++++++++++++++++++++----
client/app/dashboard/page.tsx | 13 ++++++++++++
client/app/globals.css | 34 ++++++++++++++++++++++++++++++
client/app/overlay/page.tsx | 13 ++++++++++++
memory-bank/activeContext.md | 12 ++++++-----
memory-bank/progress.md | 7 ++++---
memory-bank/systemPatterns.md | 1 +
shared/chat.ts | 2 ++
10 files changed, 138 insertions(+), 15 deletions(-)
diff --git a/.gitignore b/.gitignore
index cd0f817..5230746 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,3 +7,28 @@ pnpm-lock.yaml
out
dist
coverage
+
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+dev-debug.log
+# Dependency directories
+node_modules/
+# Environment variables
+# Editor directories and files
+.idea
+.vscode
+.cursor
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+# OS specific
+
+# Task files and project management
+.taskmaster/
+prd.txt
diff --git a/backend/src/index.ts b/backend/src/index.ts
index 8caa3c3..cc35336 100644
--- a/backend/src/index.ts
+++ b/backend/src/index.ts
@@ -224,13 +224,18 @@ export async function startBackend() {
return { error: 'url parameter is required' };
}
- // Only allow YouTube CDN domains
- const allowedDomains = ['yt3.ggpht.com', 'yt4.ggpht.com', 'i.ytimg.com'];
+ // Only allow YouTube CDN and Google User Content domains
+ const allowedDomains = [
+ 'yt3.ggpht.com',
+ 'yt4.ggpht.com',
+ 'i.ytimg.com',
+ 'lh3.googleusercontent.com' // For super stickers
+ ];
try {
const urlObj = new URL(url);
if (!allowedDomains.includes(urlObj.hostname)) {
reply.status(403);
- return { error: 'Only YouTube CDN URLs are allowed' };
+ return { error: 'Only YouTube CDN and Google User Content URLs are allowed' };
}
} catch (error) {
reply.status(400);
diff --git a/backend/src/ingestion/youtubei.ts b/backend/src/ingestion/youtubei.ts
index 28885d2..343e562 100644
--- a/backend/src/ingestion/youtubei.ts
+++ b/backend/src/ingestion/youtubei.ts
@@ -65,9 +65,9 @@ export async function bootstrapInnertube(videoId: string): Promise {
// Log the complete raw action data from YouTube (commented out for production)
- // console.log('=== YOUTUBE RAW MESSAGE DATA ===');
- // console.log('Action type:', action?.type);
- // console.log('Complete action object:', JSON.stringify(action, null, 2));
+ console.log('=== YOUTUBE RAW MESSAGE DATA ===');
+ console.log('Action type:', action?.type);
+ console.log('Complete action object:', JSON.stringify(action, null, 2));
const normalized = normalizeAction(action);
if (normalized) {
@@ -307,10 +307,37 @@ function extractSuperChatInfo(item: any): SuperChatInfo | undefined {
}
}
+ // Extract super sticker image URL if present
+ let stickerUrl: string | undefined;
+ let stickerAlt: string | undefined;
+
+ if (Array.isArray(item.sticker) && item.sticker.length > 0) {
+ // Prefer larger image (first in array is usually largest)
+ const stickerThumb = item.sticker[0];
+ if (stickerThumb?.url) {
+ // URLs from YouTube might be protocol-relative (//domain.com)
+ // Convert to absolute HTTPS URL
+ let url = String(stickerThumb.url);
+ if (url.startsWith('//')) {
+ url = 'https:' + url;
+ } else if (!url.startsWith('http')) {
+ url = 'https://' + url;
+ }
+ stickerUrl = url;
+ }
+
+ // Extract accessibility label for alt text
+ if (item.sticker_accessibility_label) {
+ stickerAlt = String(item.sticker_accessibility_label);
+ }
+ }
+
return {
amount,
currency,
- color
+ color,
+ stickerUrl,
+ stickerAlt
};
}
diff --git a/client/app/dashboard/page.tsx b/client/app/dashboard/page.tsx
index 5adc510..8f5a3c8 100644
--- a/client/app/dashboard/page.tsx
+++ b/client/app/dashboard/page.tsx
@@ -651,6 +651,19 @@ function ChatItem({ message, isSelected, onSelect, onLinkClick, isPreviouslySele
)}
+ {message.superChat?.stickerUrl && (
+
+
})
{
+ // Hide image on error
+ e.currentTarget.style.display = 'none';
+ }}
+ />
+
+ )}
);
}
diff --git a/client/app/globals.css b/client/app/globals.css
index 6119ac0..f018c9c 100644
--- a/client/app/globals.css
+++ b/client/app/globals.css
@@ -1043,6 +1043,40 @@ main {
display: inline-block;
}
+/* Super sticker images */
+.chatItem__sticker {
+ display: flex;
+ justify-content: center;
+ margin-top: 0.75rem;
+ padding-top: 0.75rem;
+ border-top: 1px solid rgba(255, 255, 255, 0.05);
+}
+
+.chatItem__stickerImage {
+ max-width: 144px;
+ max-height: 144px;
+ width: auto;
+ height: auto;
+ border-radius: 8px;
+ object-fit: contain;
+}
+
+.overlay__sticker {
+ display: flex;
+ justify-content: center;
+ margin-top: 0.75rem;
+ padding-top: 0.75rem;
+}
+
+.overlay__stickerImage {
+ max-width: 144px;
+ max-height: 144px;
+ width: auto;
+ height: auto;
+ border-radius: 8px;
+ object-fit: contain;
+}
+
/* Message links */
.message-link {
color: #60a5fa;
diff --git a/client/app/overlay/page.tsx b/client/app/overlay/page.tsx
index e5684e7..51aa4a5 100644
--- a/client/app/overlay/page.tsx
+++ b/client/app/overlay/page.tsx
@@ -117,6 +117,19 @@ export default function OverlayPage() {
) : displayMessage.text && displayMessage.text !== 'N/A' && (
{displayMessage.text}
)}
+ {displayMessage.superChat.stickerUrl && (
+
+
})
{
+ // Hide image on error
+ e.currentTarget.style.display = 'none';
+ }}
+ />
+
+ )}
>
) : (displayMessage.membershipGift || displayMessage.membershipGiftPurchase) ? (
<>
diff --git a/memory-bank/activeContext.md b/memory-bank/activeContext.md
index 7c7b0db..98916bf 100644
--- a/memory-bank/activeContext.md
+++ b/memory-bank/activeContext.md
@@ -29,13 +29,15 @@
- **Image Proxy**: Added `/proxy/image` endpoint in backend with in-memory caching (24hr TTL, max 1000 images) to prevent YouTube CDN 429 rate limit errors. All avatars, badges, and emojis now route through proxy with stale-on-error fallback.
- **Message Switching Animation**: Added smooth fade-out/fade-in transitions when switching between selected messages in overlay (300ms duration).
- **Smart Message Preservation**: Implemented intelligent message trimming - regular chat messages limited to 200, but superchats and memberships preserved for entire session. Trimming happens on every message to prevent loss of special messages.
+- **Super Sticker Support**: Added full support for super sticker image display. Backend parser extracts sticker URL and accessibility label from `item.sticker` array in YouTube data. Added `stickerUrl` and `stickerAlt` fields to `SuperChatInfo` type. Image proxy whitelist updated to include `lh3.googleusercontent.com` domain. Dashboard and overlay both render super stickers with 144x144px max dimensions, centered layout, and graceful error handling that hides broken images. Protocol-relative URLs (`//domain.com`) are automatically converted to HTTPS.
+- **Task Master AI Integration**: Initialized Task Master AI with OpenRouter's x-ai/grok-code-fast-1 model for task management. Successfully completed Task 2 (Super Sticker Display) with 5 subtasks.
## Immediate Next Steps
-1. Test with live YouTube stream to verify badge parsing and superchat detection
-2. Add search/filter functionality for chat messages
-3. Implement error recovery and reconnection logic for stream interruptions
-4. Add keyboard shortcuts for quick message selection
-5. Consider persistent cache for images (SQLite or file-based) for better reliability
+1. Implement chat leaderboard badge support (Task 3)
+2. Add live poll display functionality (Task 4)
+3. Implement user authentication via YouTube OAuth 2.0 (Task 5)
+4. Consider persistent cache for images (SQLite or file-based) for better reliability
+5. Add error recovery and reconnection logic for stream interruptions
## Open Questions
- Whether to add message search/filtering UI controls
diff --git a/memory-bank/progress.md b/memory-bank/progress.md
index dc88d14..7cfd191 100644
--- a/memory-bank/progress.md
+++ b/memory-bank/progress.md
@@ -21,7 +21,7 @@
- [x] Conditional auto-scrolling for chat panels.
- [x] Timezone-aware timestamp formatting for all messages.
- [x] Visual selection state management (active, normal, previously-selected).
-- [ ] Fix super sticker image not showing.
+- [x] Fix super sticker image not showing.
- [ ] Add chat leaderboard badge.
- [ ] Show open polls.
- [ ] Handle error states (rate limits, disconnects) gracefully in UI.
@@ -55,7 +55,8 @@
- **✅ Timestamp Resolution Fixed**: Critical bug resolved - timestamps now display correct current time instead of 1970 epoch
- **✅ Timezone Support**: Dashboard timestamps display in user's local timezone with browser detection and GMT+3 fallback
- **✅ Visual Selection States**: Three-tier feedback system (active/normal/previously-selected) for better UX
-- **✅ Image Proxy Implemented**: Backend now proxies all YouTube CDN images (avatars, badges, emojis) with 24hr in-memory cache to prevent 429 rate limit errors
+- **✅ Image Proxy Implemented**: Backend now proxies all YouTube CDN images (avatars, badges, emojis, super stickers) with 24hr in-memory cache to prevent 429 rate limit errors. Added lh3.googleusercontent.com domain for super stickers.
- **✅ Overlay Animations**: Smooth fade-out/in transitions when switching between messages
- **✅ Smart Storage**: Regular messages limited to 200, superchats and memberships preserved for entire session
-- **Next**: Add search/filter, error recovery, and polish UX details
+- **✅ Super Sticker Support**: Super sticker images now display correctly in both dashboard and overlay with proper error handling and accessibility labels
+- **Next**: Add chat leaderboard badge, show open polls, implement error recovery
diff --git a/memory-bank/systemPatterns.md b/memory-bank/systemPatterns.md
index 978ba30..7585899 100644
--- a/memory-bank/systemPatterns.md
+++ b/memory-bank/systemPatterns.md
@@ -21,3 +21,4 @@
- **Image Proxy Pattern**: Backend `/proxy/image` endpoint caches YouTube CDN images (avatars, badges, emojis) with MD5-hashed keys, 24hr TTL, and 1000-image LRU eviction. Returns stale cache on 429 errors or network failures. Frontend `proxyImageUrl()` helper transparently rewrites YouTube CDN URLs to use proxy.
- **Smart Message Storage**: Intelligent trimming system preserves all superchats and memberships for entire session while limiting regular messages to 200 most recent. Trimming occurs on every message addition to prevent special message loss. Uses order-preserving algorithm that identifies special messages by index and removes only oldest regular messages.
- **Overlay Animation System**: Two-state approach with `message` (backend data) and `displayMessage` (UI state) enables smooth fade transitions when switching messages. 300ms fade-out followed by content swap and automatic fade-in.
+- **Super Sticker Parsing**: Backend `extractSuperChatInfo()` function detects super stickers via `item.sticker` array. Extracts largest image (144x144px) from sticker thumbnails array, handles protocol-relative URLs by converting to HTTPS, and captures accessibility labels for screen readers. Frontend conditionally renders stickers with error handling that hides broken images via `onError` handler.
diff --git a/shared/chat.ts b/shared/chat.ts
index 519033a..7f92cdc 100644
--- a/shared/chat.ts
+++ b/shared/chat.ts
@@ -9,6 +9,8 @@ export type SuperChatInfo = {
amount: string;
currency: string;
color: string;
+ stickerUrl?: string; // For super stickers
+ stickerAlt?: string; // Accessibility label for super stickers
};
export type MessageRun = {