Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
74 lines
3.0 KiB
JavaScript
74 lines
3.0 KiB
JavaScript
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 };
|
|
}
|