import { computeScore, shouldSave } from './score.js'; export const RECHECK_MS = 24 * 60 * 60 * 1000; const watchUrl = (videoId) => `https://www.youtube.com/watch?v=${videoId}`; function isFresh(checkedAt, nowMs) { return checkedAt !== undefined && nowMs - Date.parse(checkedAt) < RECHECK_MS; } export async function processBatch(items, { settings, db, api, now = new Date() }) { const nowMs = now.getTime(); const nowIso = now.toISOString(); const byId = new Map(items.map((item) => [item.videoId, item])); const seen = await db.getSeen([...byId.keys()]); const due = [...byId.values()].filter( (item) => item.vidiqScore !== null || !isFresh(seen.get(item.videoId)?.checkedAt, nowMs), ); if (due.length === 0) return { checked: 0, saved: 0 }; const { videos, missingIds } = await api.fetchVideos(due.map((item) => item.videoId)); const channelIds = [...new Set(videos.map((video) => video.channelId))]; const cachedChannels = await db.getChannels(channelIds); const staleIds = channelIds.filter((id) => !isFresh(cachedChannels.get(id)?.fetchedAt, nowMs)); const fetchedChannels = staleIds.length > 0 ? await api.fetchChannels(staleIds) : []; const channelRecords = fetchedChannels.map((channel) => ({ ...channel, fetchedAt: nowIso })); const channels = new Map(cachedChannels); for (const record of channelRecords) channels.set(record.channelId, record); const existing = await db.getOutliers(videos.map((video) => video.videoId)); const outliers = []; for (const video of videos) { const item = byId.get(video.videoId); const previous = existing.get(video.videoId); const channel = channels.get(video.channelId); const subscribers = channel?.subscribers ?? null; const score = computeScore(video.views, subscribers); const vidiqScore = item.vidiqScore ?? previous?.vidiqScore ?? null; if (!previous && !shouldSave({ views: video.views, score, vidiqScore }, settings)) continue; outliers.push({ videoId: video.videoId, title: video.title, url: watchUrl(video.videoId), thumbnailUrl: video.thumbnailUrl, channelId: video.channelId, channelTitle: video.channelTitle, channelUrl: channel?.url ?? `https://www.youtube.com/channel/${video.channelId}`, subscribers, views: video.views, publishedAt: video.publishedAt, durationSec: video.durationSec, isShort: item.viaShortsUrl || previous?.isShort || false, score, vidiqScore, foundOn: previous?.foundOn ?? item.page, firstSeenAt: previous?.firstSeenAt ?? nowIso, updatedAt: nowIso, }); } // Results first, `seen` last: if anything above throws, the ids are retried later. await db.putChannels(channelRecords); await db.putOutliers(outliers); await db.putSeen([ ...videos.map((video) => ({ videoId: video.videoId, checkedAt: nowIso, status: 'ok' })), ...missingIds.map((videoId) => ({ videoId, checkedAt: nowIso, status: 'unavailable' })), ]); return { checked: due.length, saved: outliers.length }; }