mirror of
https://github.com/yusufipk/YTChatHub.git
synced 2026-09-11 19:06:14 +00:00
feat: add leaderboard rank extraction and display in chat and overlay components, including CSS styling for leaderboard badge
This commit is contained in:
@@ -65,9 +65,9 @@ export async function bootstrapInnertube(videoId: string): Promise<IngestionCont
|
|||||||
|
|
||||||
liveChat.on('chat-update', (action: any) => {
|
liveChat.on('chat-update', (action: any) => {
|
||||||
// Log the complete raw action data from YouTube (commented out for production)
|
// Log the complete raw action data from YouTube (commented out for production)
|
||||||
console.log('=== YOUTUBE RAW MESSAGE DATA ===');
|
//console.log('=== YOUTUBE RAW MESSAGE DATA ===');
|
||||||
console.log('Action type:', action?.type);
|
//console.log('Action type:', action?.type);
|
||||||
console.log('Complete action object:', JSON.stringify(action, null, 2));
|
//console.log('Complete action object:', JSON.stringify(action, null, 2));
|
||||||
|
|
||||||
const normalized = normalizeAction(action);
|
const normalized = normalizeAction(action);
|
||||||
if (normalized) {
|
if (normalized) {
|
||||||
@@ -233,6 +233,24 @@ function extractBadgesFromHeader(header: any): Badge[] {
|
|||||||
return badges;
|
return badges;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function extractLeaderboardRank(item: any): number | undefined {
|
||||||
|
// Check for before_content_buttons array (where leaderboard badge appears)
|
||||||
|
if (!Array.isArray(item.before_content_buttons)) return undefined;
|
||||||
|
|
||||||
|
for (const button of item.before_content_buttons) {
|
||||||
|
// Look for CROWN icon (leaderboard indicator)
|
||||||
|
if (button.icon_name === 'CROWN' && button.title) {
|
||||||
|
// Title is like "#3", "#1", etc.
|
||||||
|
const match = String(button.title).match(/#(\d+)/);
|
||||||
|
if (match && match[1]) {
|
||||||
|
return parseInt(match[1], 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
function extractSuperChatInfo(item: any): SuperChatInfo | undefined {
|
function extractSuperChatInfo(item: any): SuperChatInfo | undefined {
|
||||||
const superTypes = new Set([
|
const superTypes = new Set([
|
||||||
'LiveChatPaidMessage',
|
'LiveChatPaidMessage',
|
||||||
@@ -439,6 +457,9 @@ function normalizeAction(action: any): ChatMessage | null {
|
|||||||
// Use timestamp_usec (microseconds) as it's more accurate than timestamp (milliseconds)
|
// Use timestamp_usec (microseconds) as it's more accurate than timestamp (milliseconds)
|
||||||
const timestampToUse = item.timestamp_usec ?? item.timestamp;
|
const timestampToUse = item.timestamp_usec ?? item.timestamp;
|
||||||
|
|
||||||
|
// Extract leaderboard rank if present
|
||||||
|
const leaderboardRank = extractLeaderboardRank(item);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: String(item.id ?? item.timestamp_usec ?? Date.now()),
|
id: String(item.id ?? item.timestamp_usec ?? Date.now()),
|
||||||
author: authorName,
|
author: authorName,
|
||||||
@@ -455,7 +476,8 @@ function normalizeAction(action: any): ChatMessage | null {
|
|||||||
membershipGift: isMembership,
|
membershipGift: isMembership,
|
||||||
membershipGiftPurchase: isGiftPurchase,
|
membershipGiftPurchase: isGiftPurchase,
|
||||||
membershipLevel,
|
membershipLevel,
|
||||||
giftCount
|
giftCount,
|
||||||
|
leaderboardRank
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -616,6 +616,11 @@ function ChatItem({ message, isSelected, onSelect, onLinkClick, isPreviouslySele
|
|||||||
<span className="chatItem__membership">{message.membershipLevel}:</span>
|
<span className="chatItem__membership">{message.membershipLevel}:</span>
|
||||||
)}
|
)}
|
||||||
<span className="chatItem__author">{message.author}</span>
|
<span className="chatItem__author">{message.author}</span>
|
||||||
|
{message.leaderboardRank && (
|
||||||
|
<span className="badge badge--leaderboard" title={`#${message.leaderboardRank} on leaderboard`}>
|
||||||
|
👑 #{message.leaderboardRank}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
{message.badges && message.badges.map((badge, i) => (
|
{message.badges && message.badges.map((badge, i) => (
|
||||||
badge.imageUrl ? (
|
badge.imageUrl ? (
|
||||||
<img key={i} src={proxyImageUrl(badge.imageUrl)} alt={badge.label} className="badge badge--image" title={badge.label} />
|
<img key={i} src={proxyImageUrl(badge.imageUrl)} alt={badge.label} className="badge badge--image" title={badge.label} />
|
||||||
|
|||||||
@@ -784,6 +784,12 @@ main {
|
|||||||
color: #93c5fd;
|
color: #93c5fd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.badge--leaderboard {
|
||||||
|
background: rgba(251, 191, 36, 0.2);
|
||||||
|
color: #fcd34d;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
.memberItem {
|
.memberItem {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -170,6 +170,11 @@ export default function OverlayPage() {
|
|||||||
<div>
|
<div>
|
||||||
<div className="overlay__authorLine">
|
<div className="overlay__authorLine">
|
||||||
<span className="overlay__author">{displayMessage.author}</span>
|
<span className="overlay__author">{displayMessage.author}</span>
|
||||||
|
{displayMessage.leaderboardRank && (
|
||||||
|
<span className="overlay__badge overlay__badge--leaderboard" title={`#${displayMessage.leaderboardRank} on leaderboard`}>
|
||||||
|
👑 #{displayMessage.leaderboardRank}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
{displayMessage.badges && displayMessage.badges.map((badge, i) => (
|
{displayMessage.badges && displayMessage.badges.map((badge, i) => (
|
||||||
badge.imageUrl ? (
|
badge.imageUrl ? (
|
||||||
<img key={i} src={proxyImageUrl(badge.imageUrl)} alt={badge.label} className="overlay__badge overlay__badge--image" title={badge.label} />
|
<img key={i} src={proxyImageUrl(badge.imageUrl)} alt={badge.label} className="overlay__badge overlay__badge--image" title={badge.label} />
|
||||||
|
|||||||
@@ -36,4 +36,5 @@ export type ChatMessage = {
|
|||||||
membershipGiftPurchase?: boolean;
|
membershipGiftPurchase?: boolean;
|
||||||
membershipLevel?: string; // e.g., "New member", "Member (6 months)", etc.
|
membershipLevel?: string; // e.g., "New member", "Member (6 months)", etc.
|
||||||
giftCount?: number; // Number of memberships gifted
|
giftCount?: number; // Number of memberships gifted
|
||||||
|
leaderboardRank?: number; // YouTube leaderboard rank (1, 2, 3, etc.)
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user