mirror of
https://github.com/yusufipk/YTChatHub.git
synced 2026-09-11 10:56:17 +00:00
feat: add support for super stickers in chat and overlay components, including image display, accessibility labels, and backend parsing enhancements
This commit is contained in:
+25
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -65,9 +65,9 @@ export async function bootstrapInnertube(videoId: string): Promise<IngestionCont
|
||||
|
||||
liveChat.on('chat-update', (action: any) => {
|
||||
// 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
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -651,6 +651,19 @@ function ChatItem({ message, isSelected, onSelect, onLinkClick, isPreviouslySele
|
||||
<MessageText text={message.text} onLinkClick={onLinkClick} />
|
||||
</p>
|
||||
)}
|
||||
{message.superChat?.stickerUrl && (
|
||||
<div className="chatItem__sticker">
|
||||
<img
|
||||
src={proxyImageUrl(message.superChat.stickerUrl)}
|
||||
alt={message.superChat.stickerAlt || 'Super Sticker'}
|
||||
className="chatItem__stickerImage"
|
||||
onError={(e) => {
|
||||
// Hide image on error
|
||||
e.currentTarget.style.display = 'none';
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -117,6 +117,19 @@ export default function OverlayPage() {
|
||||
) : displayMessage.text && displayMessage.text !== 'N/A' && (
|
||||
<p className="overlay__superchat-text">{displayMessage.text}</p>
|
||||
)}
|
||||
{displayMessage.superChat.stickerUrl && (
|
||||
<div className="overlay__sticker">
|
||||
<img
|
||||
src={proxyImageUrl(displayMessage.superChat.stickerUrl)}
|
||||
alt={displayMessage.superChat.stickerAlt || 'Super Sticker'}
|
||||
className="overlay__stickerImage"
|
||||
onError={(e) => {
|
||||
// Hide image on error
|
||||
e.currentTarget.style.display = 'none';
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : (displayMessage.membershipGift || displayMessage.membershipGiftPurchase) ? (
|
||||
<>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
Reference in New Issue
Block a user