From 693ee684ab03a472a9f338d97f9821d2080750ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yusuf=20=C4=B0pek?= Date: Sat, 11 Oct 2025 20:13:41 +0300 Subject: [PATCH] feat: integrate timezone support for message timestamps in chat and overlay components --- backend/src/ingestion/youtubei.ts | 27 ++++++++- client/app/dashboard/page.tsx | 10 +++- client/app/globals.css | 8 +++ client/app/layout.tsx | 7 ++- client/app/overlay/page.tsx | 4 ++ client/lib/TimezoneContext.tsx | 43 ++++++++++++++ client/lib/timezone.ts | 98 +++++++++++++++++++++++++++++++ 7 files changed, 192 insertions(+), 5 deletions(-) create mode 100644 client/lib/TimezoneContext.tsx create mode 100644 client/lib/timezone.ts diff --git a/backend/src/ingestion/youtubei.ts b/backend/src/ingestion/youtubei.ts index b45a368..28885d2 100644 --- a/backend/src/ingestion/youtubei.ts +++ b/backend/src/ingestion/youtubei.ts @@ -64,6 +64,11 @@ export async function bootstrapInnertube(videoId: string): Promise { + // 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)); + const normalized = normalizeAction(action); if (normalized) { emitter.emit('message', normalized); @@ -156,8 +161,23 @@ function resolveTimestamp(timestamp: number | string | undefined): string { } const numeric = typeof timestamp === 'string' ? Number(timestamp) : timestamp; + if (Number.isFinite(numeric)) { - const millis = numeric > 1e12 ? numeric / 1000 : numeric; + // YouTube timestamps are in microseconds (16 digits) or milliseconds (13 digits) + // If it's microseconds (>= 1e15), divide by 1000 to get milliseconds + // If it's milliseconds (>= 1e12), use as is + let millis: number; + if (numeric >= 1e15) { + // Microseconds - convert to milliseconds + millis = numeric / 1000; + } else if (numeric >= 1e12) { + // Already milliseconds + millis = numeric; + } else { + // Fallback for smaller numbers + millis = numeric; + } + return new Date(millis).toISOString(); } @@ -389,6 +409,9 @@ function normalizeAction(action: any): ChatMessage | null { ? (item.header_subtext?.text || item.header_primary_text?.text || '') : ''; + // Use timestamp_usec (microseconds) as it's more accurate than timestamp (milliseconds) + const timestampToUse = item.timestamp_usec ?? item.timestamp; + return { id: String(item.id ?? item.timestamp_usec ?? Date.now()), author: authorName, @@ -396,7 +419,7 @@ function normalizeAction(action: any): ChatMessage | null { authorChannelId: authorChannelId ? String(authorChannelId) : undefined, text: resolvedText || membershipFallbackText, runs: (() => { const r = resolveMessageRuns(item); return r.length ? r : undefined; })(), - publishedAt: resolveTimestamp(item.timestamp ?? item.timestamp_usec), + publishedAt: resolveTimestamp(timestampToUse), badges: badges.length > 0 ? badges : undefined, isModerator, isMember, diff --git a/client/app/dashboard/page.tsx b/client/app/dashboard/page.tsx index e3ee168..3c4b8b1 100644 --- a/client/app/dashboard/page.tsx +++ b/client/app/dashboard/page.tsx @@ -2,6 +2,8 @@ import { useCallback, useEffect, useMemo, useState, useRef } from 'react'; import type { ChatMessage } from '@shared/chat'; +import { useTimezone } from '../../lib/TimezoneContext'; +import { formatTimestamp } from '../../lib/timezone'; // URL regex for detecting links (http/https) const URL_REGEX = /(https?:\/\/[^\s]+)/gi; @@ -582,6 +584,8 @@ function ChatListPanel({ messages, renderItem, emptyState }: ChatListPanelProps) } function ChatItem({ message, isSelected, onSelect, onLinkClick }: ChatItemProps) { + const { timezone } = useTimezone(); + return (