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
+31 -25
View File
@@ -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
};
}
+1 -1
View File
@@ -91,7 +91,7 @@ export default function OverlayPage() {
)}
{message.membershipGift && (
<div className="overlay__membership">
🎁 New Member!
🎁 {message.membershipLevel || 'New Member'}
</div>
)}
<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
- 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