diff --git a/backend/src/ingestion/youtubei.ts b/backend/src/ingestion/youtubei.ts index 04392fc..3b17832 100644 --- a/backend/src/ingestion/youtubei.ts +++ b/backend/src/ingestion/youtubei.ts @@ -152,30 +152,32 @@ function extractBadges(item: any): Badge[] { } function extractSuperChatInfo(item: any): SuperChatInfo | undefined { - if (item.type !== 'LiveChatPaidMessage') return undefined; + // Support multiple superchat-like types from Innertube + const superTypes = new Set([ + 'LiveChatPaidMessage', + 'LiveChatPaidSticker', + 'liveChatPaidMessageRenderer', + 'liveChatPaidStickerRenderer' + ]); + const typeName = String(item.type || item.item_type || item.renderer || '').trim(); + const isSuper = superTypes.has(typeName) || !!(item.purchase_amount_text || item.purchaseAmountText); + if (!isSuper) return undefined; // Try multiple possible field names for the amount let amount = ''; - if (item.purchase_amount_text) { - amount = typeof item.purchase_amount_text === 'string' - ? item.purchase_amount_text - : item.purchase_amount_text.simpleText || item.purchase_amount_text.toString(); - } else if (item.purchaseAmountText) { - amount = typeof item.purchaseAmountText === 'string' - ? item.purchaseAmountText - : item.purchaseAmountText.simpleText || item.purchaseAmountText.toString(); + const amt1 = item.purchase_amount_text; + const amt2 = item.purchaseAmountText; + if (amt1) { + amount = typeof amt1 === 'string' ? amt1 : (amt1.simpleText || amt1.toString()); + } else if (amt2) { + amount = typeof amt2 === 'string' ? amt2 : (amt2.simpleText || amt2.toString()); } else if (item.amount) { - amount = item.amount.toString(); + amount = String(item.amount); } - + // Try multiple possible field names for color - const color = item.body_background_color?.toString() - || item.bodyBackgroundColor?.toString() - || item.headerBackgroundColor?.toString() - || '#1e3a8a'; - - console.log('[SuperChat] Extracted:', { amount, color, rawItem: item }); - + const color = (item.body_background_color ?? item.bodyBackgroundColor ?? item.headerBackgroundColor ?? '#1e3a8a').toString(); + return { amount: amount || 'Super Chat', currency: item.currency ?? 'USD', @@ -191,11 +193,15 @@ function normalizeAction(action: any): ChatMessage | null { const item = action.item; if (!item) return null; - if ( - item.type === 'LiveChatTextMessage' || - item.type === 'LiveChatPaidMessage' || - item.type === 'LiveChatMembershipItem' - ) { + // Normalize various live chat events + const itemType = String(item.type || '').trim(); + const isText = itemType === 'LiveChatTextMessage'; + const isPaid = !!extractSuperChatInfo(item); + const isMembership = itemType === 'LiveChatMembershipItem'; + const isGiftPurchase = itemType === 'LiveChatGiftMembershipsPurchase' || itemType === 'LiveChatSponsorshipsGiftRedemptionAnnouncement'; + const isGiftReceived = itemType === 'LiveChatGiftMembershipReceived'; + + if (isText || isPaid || isMembership || isGiftPurchase || isGiftReceived) { const badges = extractBadges(item); const isModerator = badges.some(b => b.type === 'moderator'); const isMember = badges.some(b => b.type === 'member'); @@ -203,7 +209,7 @@ function normalizeAction(action: any): ChatMessage | null { // Extract membership level for new members let membershipLevel: string | undefined; - if (item.type === 'LiveChatMembershipItem') { + if (isMembership || isGiftPurchase || isGiftReceived) { membershipLevel = item.header_subtext?.toString() || item.header_primary_text?.toString() || 'New member'; @@ -220,7 +226,7 @@ function normalizeAction(action: any): ChatMessage | null { isMember, isVerified, superChat: extractSuperChatInfo(item), - membershipGift: item.type === 'LiveChatMembershipItem', + membershipGift: isMembership || isGiftPurchase || isGiftReceived, membershipLevel }; } diff --git a/client/app/overlay/page.tsx b/client/app/overlay/page.tsx index 3da09cf..945d6f3 100644 --- a/client/app/overlay/page.tsx +++ b/client/app/overlay/page.tsx @@ -91,7 +91,7 @@ export default function OverlayPage() { )} {message.membershipGift && (
- 🎁 New Member! + 🎁 {message.membershipLevel || 'New Member'}
)}

{message.text}

diff --git a/memory-bank/activeContext.md b/memory-bank/activeContext.md index 8879120..195b7a3 100644 --- a/memory-bank/activeContext.md +++ b/memory-bank/activeContext.md @@ -14,6 +14,10 @@ - Centered overlay message card content so live chat, superchat, and membership boxes align for OBS - Reworked dashboard grid so live chat runs full-height on the left and super chat/new member panels stack in a centered right column with tuned spacing and clear separation between the stacked cards - Moved the connection control into the header (inline, centered between logo/title and status) with a compact input width +- Fixed panel heights to fit within the viewport (no page scroll); grid rows split 1fr/1fr with internal lists scrolling so bottom edges are always visible +- Improved parsing so superchat amounts/colors show reliably; overlay and dashboard both display the amount +- Overlay membership banner now shows the membership level text +- Membership gifts are recognized and included in the New Members list ## Immediate Next Steps 1. Test with live YouTube stream to verify badge parsing and superchat detection