Skip to main content
Glama
RahulPatkiWork

YouTube Transcript MCP Server

analytics.ts8.05 kB
// Assuming TRANSCRIPT_CACHE is bound in the environment (env.TRANSCRIPT_CACHE) const ANALYTICS_TTL_SECONDS = 60 * 60 * 24; // 24 hours for general stats const POPULAR_VIDEOS_TTL_SECONDS = 60 * 60 * 24 * 7; // 7 days for popular videos list // --- Key Generation Functions --- function getDailyRequestsKey(date: string): string { // date in YYYY-MM-DD format return `analytics:requests:${date}`; } function getDailyErrorsKey(date: string, errorType?: string): string { // date in YYYY-MM-DD return `analytics:errors:${date}${errorType ? ':' + errorType : ':general'}`; } function getVideoRequestsKey(videoId: string): string { return `analytics:videos:${videoId}`; // Already used in cache.ts, but good to have a helper here too } function getPopularVideosWeeklyKey(): string { // Simple weekly key based on ISO week number const now = new Date(); const year = now.getUTCFullYear(); const firstDayOfYear = new Date(Date.UTC(year, 0, 1)); const days = Math.floor((now.getTime() - firstDayOfYear.getTime()) / (24 * 60 * 60 * 1000)); const weekNumber = Math.ceil((days + firstDayOfYear.getUTCDay() + 1) / 7); return `analytics:popular:weekly:${year}-W${String(weekNumber).padStart(2, '0')}`; } // --- Core Analytics Functions --- /** * Logs a request, incrementing relevant counters for daily requests, video-specific requests, * and error types if applicable. * @param env The Worker environment containing the KV namespace. * @param videoId The YouTube video ID involved in the request. * @param success Whether the request was successful (resulted in a transcript). * @param errorType Optional string describing the type of error if success is false. */ export async function logRequest( env: any, videoId: string, success: boolean, errorType?: string ): Promise<void> { if (!env.TRANSCRIPT_CACHE) { console.warn('TRANSCRIPT_CACHE not bound, skipping analytics logging.'); return; } const today = new Date(); const dateKey = `${today.getUTCFullYear()}-${String(today.getUTCMonth() + 1).padStart(2, '0')}-${String(today.getUTCDate()).padStart(2, '0')}`; if (!success) { const dailyErrKey = getDailyErrorsKey(dateKey, errorType || 'unknown'); try { const currentErrors = await env.TRANSCRIPT_CACHE.get(dailyErrKey); const newErrorCount = currentErrors ? parseInt(currentErrors, 10) + 1 : 1; if(!isNaN(newErrorCount)) { await env.TRANSCRIPT_CACHE.put(dailyErrKey, newErrorCount.toString(), { expirationTtl: ANALYTICS_TTL_SECONDS * 2 }); } else { await env.TRANSCRIPT_CACHE.put(dailyErrKey, "1", { expirationTtl: ANALYTICS_TTL_SECONDS * 2 }); } } catch (e: any) { console.error(`Error updating daily error count (${dailyErrKey}): ${e.message}`); } } } /** * Retrieves daily statistics for requests and errors. * @param env The Worker environment containing the KV namespace. * @param date The date string in 'YYYY-MM-DD' format. * @returns An object with total requests and error counts (by type). */ export async function getDailyStats(env: any, date: string): Promise<{ requests: number; errors: Record<string, number>; totalErrors: number }> { if (!env.TRANSCRIPT_CACHE) { console.warn('TRANSCRIPT_CACHE not bound, cannot get daily stats.'); return { requests: 0, errors: {}, totalErrors: 0 }; } let requestCount = 0; const dailyReqKey = getDailyRequestsKey(date); try { const reqVal = await env.TRANSCRIPT_CACHE.get(dailyReqKey); requestCount = reqVal ? parseInt(reqVal, 10) : 0; if (isNaN(requestCount)) requestCount = 0; } catch (e: any) { console.error(`Error fetching daily request count (${dailyReqKey}): ${e.message}`); } const errors: Record<string, number> = {}; let totalErrors = 0; try { const listOptions: KVNamespaceListOptions = { prefix: `analytics:errors:${date}:` }; const errorKeysResult = await env.TRANSCRIPT_CACHE.list(listOptions); for (const key of errorKeysResult.keys) { const errorType = key.name.substring(`analytics:errors:${date}:`.length); const errVal = await env.TRANSCRIPT_CACHE.get(key.name); const count = errVal ? parseInt(errVal, 10) : 0; if (!isNaN(count)) { errors[errorType] = count; totalErrors += count; } else { errors[errorType] = 0; } } // Note: simplified for MVP; does not handle pagination for error types if many unique error types exist. } catch (e: any) { console.error(`Error fetching daily error stats for date ${date}: ${e.message}`); } return { requests: requestCount, errors, totalErrors }; } /** * Retrieves a list of popular videos based on request counts. * This function reads a pre-compiled list from KV. * @param env The Worker environment containing the KV namespace. * @param limit The maximum number of popular videos to return. * @returns An array of popular videos with their counts. */ export async function getPopularVideos( env: any, limit: number ): Promise<Array<{ videoId: string; count: number }>> { if (!env.TRANSCRIPT_CACHE) { console.warn('TRANSCRIPT_CACHE not bound, cannot get popular videos.'); return []; } const popularKey = getPopularVideosWeeklyKey(); try { const popularData = await env.TRANSCRIPT_CACHE.get(popularKey); if (popularData) { const videos = JSON.parse(popularData) as Array<{ videoId: string; count: number }>; return videos.slice(0, limit); } } catch (e: any) { console.error(`Error fetching or parsing popular videos (${popularKey}): ${e.message}`); } return []; } /** * Updates the list of weekly popular videos by scanning video request counts. * WARNING: This function can be resource-intensive with many unique video IDs in KV. * It's intended to be run periodically (e.g., via a cron trigger), not on every user request. * @param env The Worker environment containing the KV namespace. * @param topN The number of top videos to store in the popular list. */ export async function updatePopularVideosList(env: any, topN: number = 20): Promise<void> { if (!env.TRANSCRIPT_CACHE) { console.warn('TRANSCRIPT_CACHE not bound, cannot update popular videos.'); return; } console.log('Attempting to update popular videos list...'); const videoCounts: Array<{ videoId: string; count: number }> = []; let currentCursor: string | undefined = undefined; try { do { const listResult: KVNamespaceListResult<unknown> = await env.TRANSCRIPT_CACHE.list({ prefix: 'analytics:videos:', cursor: currentCursor, limit: 1000, // Max 1000, adjust as needed }); for (const key of listResult.keys) { const videoId = key.name.substring('analytics:videos:'.length); const countStr = await env.TRANSCRIPT_CACHE.get(key.name); const count = countStr ? parseInt(countStr, 10) : 0; if (!isNaN(count) && count > 0) { videoCounts.push({ videoId, count }); } } if (listResult.list_complete) { currentCursor = undefined; // No more keys } else { currentCursor = listResult.cursor; } } while (currentCursor); videoCounts.sort((a, b) => b.count - a.count); const topVideos = videoCounts.slice(0, topN); if (topVideos.length > 0) { const popularKey = getPopularVideosWeeklyKey(); await env.TRANSCRIPT_CACHE.put(popularKey, JSON.stringify(topVideos), { expirationTtl: POPULAR_VIDEOS_TTL_SECONDS }); console.log(`Updated popular videos list (${popularKey}) with ${topVideos.length} videos.`); } else { console.log('No video data found to update popular videos list.'); } } catch (error: any) { console.error('Error updating popular videos list:', error.message); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/RahulPatkiWork/youtube-transcript-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server