mirror of
https://github.com/yusufipk/YTChatHub.git
synced 2026-09-11 10:56:17 +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) => {
|
||||
// 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) {
|
||||
@@ -233,6 +233,24 @@ function extractBadgesFromHeader(header: any): Badge[] {
|
||||
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 {
|
||||
const superTypes = new Set([
|
||||
'LiveChatPaidMessage',
|
||||
@@ -439,6 +457,9 @@ function normalizeAction(action: any): ChatMessage | null {
|
||||
// Use timestamp_usec (microseconds) as it's more accurate than timestamp (milliseconds)
|
||||
const timestampToUse = item.timestamp_usec ?? item.timestamp;
|
||||
|
||||
// Extract leaderboard rank if present
|
||||
const leaderboardRank = extractLeaderboardRank(item);
|
||||
|
||||
return {
|
||||
id: String(item.id ?? item.timestamp_usec ?? Date.now()),
|
||||
author: authorName,
|
||||
@@ -455,7 +476,8 @@ function normalizeAction(action: any): ChatMessage | null {
|
||||
membershipGift: isMembership,
|
||||
membershipGiftPurchase: isGiftPurchase,
|
||||
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__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) => (
|
||||
badge.imageUrl ? (
|
||||
<img key={i} src={proxyImageUrl(badge.imageUrl)} alt={badge.label} className="badge badge--image" title={badge.label} />
|
||||
|
||||
@@ -784,6 +784,12 @@ main {
|
||||
color: #93c5fd;
|
||||
}
|
||||
|
||||
.badge--leaderboard {
|
||||
background: rgba(251, 191, 36, 0.2);
|
||||
color: #fcd34d;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.memberItem {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -170,6 +170,11 @@ export default function OverlayPage() {
|
||||
<div>
|
||||
<div className="overlay__authorLine">
|
||||
<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) => (
|
||||
badge.imageUrl ? (
|
||||
<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;
|
||||
membershipLevel?: string; // e.g., "New member", "Member (6 months)", etc.
|
||||
giftCount?: number; // Number of memberships gifted
|
||||
leaderboardRank?: number; // YouTube leaderboard rank (1, 2, 3, etc.)
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user