feat: enhance YouTube chat ingestion with support for gift memberships

Expand YouTube chat ingestion to handle membership gift events including purchases and redemptions. Update SuperChat extraction to support multiple paid message types and improve field name handling. Display membership level in overlay for gift events.
This commit is contained in:
Yusuf İpek
2025-10-05 04:42:47 +03:00
parent 0dd6872af7
commit 5d96e2e3ff
3 changed files with 36 additions and 26 deletions
+29 -23
View File
@@ -152,29 +152,31 @@ function extractBadges(item: any): Badge[] {
} }
function extractSuperChatInfo(item: any): SuperChatInfo | undefined { 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 // Try multiple possible field names for the amount
let amount = ''; let amount = '';
if (item.purchase_amount_text) { const amt1 = item.purchase_amount_text;
amount = typeof item.purchase_amount_text === 'string' const amt2 = item.purchaseAmountText;
? item.purchase_amount_text if (amt1) {
: item.purchase_amount_text.simpleText || item.purchase_amount_text.toString(); amount = typeof amt1 === 'string' ? amt1 : (amt1.simpleText || amt1.toString());
} else if (item.purchaseAmountText) { } else if (amt2) {
amount = typeof item.purchaseAmountText === 'string' amount = typeof amt2 === 'string' ? amt2 : (amt2.simpleText || amt2.toString());
? item.purchaseAmountText
: item.purchaseAmountText.simpleText || item.purchaseAmountText.toString();
} else if (item.amount) { } else if (item.amount) {
amount = item.amount.toString(); amount = String(item.amount);
} }
// Try multiple possible field names for color // Try multiple possible field names for color
const color = item.body_background_color?.toString() const color = (item.body_background_color ?? item.bodyBackgroundColor ?? item.headerBackgroundColor ?? '#1e3a8a').toString();
|| item.bodyBackgroundColor?.toString()
|| item.headerBackgroundColor?.toString()
|| '#1e3a8a';
console.log('[SuperChat] Extracted:', { amount, color, rawItem: item });
return { return {
amount: amount || 'Super Chat', amount: amount || 'Super Chat',
@@ -191,11 +193,15 @@ function normalizeAction(action: any): ChatMessage | null {
const item = action.item; const item = action.item;
if (!item) return null; if (!item) return null;
if ( // Normalize various live chat events
item.type === 'LiveChatTextMessage' || const itemType = String(item.type || '').trim();
item.type === 'LiveChatPaidMessage' || const isText = itemType === 'LiveChatTextMessage';
item.type === 'LiveChatMembershipItem' 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 badges = extractBadges(item);
const isModerator = badges.some(b => b.type === 'moderator'); const isModerator = badges.some(b => b.type === 'moderator');
const isMember = badges.some(b => b.type === 'member'); const isMember = badges.some(b => b.type === 'member');
@@ -203,7 +209,7 @@ function normalizeAction(action: any): ChatMessage | null {
// Extract membership level for new members // Extract membership level for new members
let membershipLevel: string | undefined; let membershipLevel: string | undefined;
if (item.type === 'LiveChatMembershipItem') { if (isMembership || isGiftPurchase || isGiftReceived) {
membershipLevel = item.header_subtext?.toString() || membershipLevel = item.header_subtext?.toString() ||
item.header_primary_text?.toString() || item.header_primary_text?.toString() ||
'New member'; 'New member';
@@ -220,7 +226,7 @@ function normalizeAction(action: any): ChatMessage | null {
isMember, isMember,
isVerified, isVerified,
superChat: extractSuperChatInfo(item), superChat: extractSuperChatInfo(item),
membershipGift: item.type === 'LiveChatMembershipItem', membershipGift: isMembership || isGiftPurchase || isGiftReceived,
membershipLevel membershipLevel
}; };
} }
+1 -1
View File
@@ -91,7 +91,7 @@ export default function OverlayPage() {
)} )}
{message.membershipGift && ( {message.membershipGift && (
<div className="overlay__membership"> <div className="overlay__membership">
🎁 New Member! 🎁 {message.membershipLevel || 'New Member'}
</div> </div>
)} )}
<p className="overlay__text">{message.text}</p> <p className="overlay__text">{message.text}</p>
+4
View File
@@ -14,6 +14,10 @@
- Centered overlay message card content so live chat, superchat, and membership boxes align for OBS - 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 - 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 - 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 ## Immediate Next Steps
1. Test with live YouTube stream to verify badge parsing and superchat detection 1. Test with live YouTube stream to verify badge parsing and superchat detection