import { PrismaClient } from '@prisma/client'; import { createDecipheriv, scryptSync } from 'crypto'; import { google } from 'googleapis'; const MISSING_IDS = [ '4dcg46RQqnM', '9xsmLBY20wA', 'DmmQ7HPxBRg', 'G58lGGaMOOY', 'I5u-8xk6BHM', 'OdHheItBlyQ', 'VtfmpQXPheA', 'hIgaxCUX9ts', 'nHWtSlE-kgE', 'ye3b5J7zX0M', 'zW7yrpUs34Q', ]; const CHANNEL_ID = 'cmpfdm4el008nn3uoemascs0z'; const PRIVACY_MAP = { public: 'PUBLIC', private: 'PRIVATE', unlisted: 'UNLISTED' }; function decrypt(text) { const key = scryptSync('7017a681f5b32c3799d1bbf58ce58064', 'studioflow-salt', 32); const [ivHex, encHex] = text.split(':'); const decipher = createDecipheriv('aes-256-cbc', key, Buffer.from(ivHex, 'hex')); return Buffer.concat([decipher.update(Buffer.from(encHex, 'hex')), decipher.final()]).toString('utf8'); } const prisma = new PrismaClient(); const channel = await prisma.channel.findUniqueOrThrow({ where: { id: CHANNEL_ID } }); // Persist supplemental IDs on the channel const existing = new Set(channel.supplementalVideoIds ?? []); for (const id of MISSING_IDS) existing.add(id); await prisma.channel.update({ where: { id: CHANNEL_ID }, data: { supplementalVideoIds: [...existing] }, }); console.log(`Saved ${existing.size} supplemental video IDs on channel`); // Set up YouTube client and refresh access token const oauth2 = new google.auth.OAuth2( '496320251583-rpbr13i0rh1112cdvv9ui5s373735qqj.apps.googleusercontent.com', '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', ); oauth2.setCredentials({ access_token: decrypt(channel.youtubeAccessToken), refresh_token: channel.youtubeRefreshToken ? decrypt(channel.youtubeRefreshToken) : undefined, }); // Force token refresh so we always have a valid access token const { credentials } = await oauth2.refreshAccessToken(); oauth2.setCredentials(credentials); console.log('Access token refreshed'); const yt = google.youtube({ version: 'v3', auth: oauth2 }); // Fetch metadata for all missing IDs const res = await yt.videos.list({ part: ['snippet', 'status'], id: MISSING_IDS, maxResults: 50, }); const items = res.data.items ?? []; console.log(`YouTube returned ${items.length} of ${MISSING_IDS.length} requested IDs`); const notFound = MISSING_IDS.filter((id) => !items.find((i) => i.id === id)); if (notFound.length > 0) console.warn(`Not returned by YouTube API: ${notFound.join(', ')}`); let created = 0; let updated = 0; for (const item of items) { const ytId = item.id; if (!ytId) continue; const snippet = item.snippet; const status = item.status; const ytFields = { title: snippet?.title ?? '(Untitled)', youtubeDescription: snippet?.description ?? undefined, tags: snippet?.tags ?? [], categoryId: snippet?.categoryId ?? undefined, publishedAt: snippet?.publishedAt ? new Date(snippet.publishedAt) : undefined, privacyStatus: PRIVACY_MAP[status?.privacyStatus ?? ''] ?? 'PRIVATE', defaultLanguage: snippet?.defaultLanguage ?? undefined, defaultAudioLanguage: snippet?.defaultAudioLanguage ?? undefined, selfDeclaredMadeForKids: status?.selfDeclaredMadeForKids ?? undefined, embeddable: status?.embeddable ?? undefined, license: status?.license ?? undefined, }; const existing = await prisma.video.findUnique({ where: { youtubeVideoId: ytId } }); if (!existing) { await prisma.video.create({ data: { youtubeVideoId: ytId, channelId: CHANNEL_ID, ...ytFields } }); console.log(` CREATED: ${ytId} — ${ytFields.title}`); created++; } else { await prisma.video.update({ where: { youtubeVideoId: ytId }, data: { channelId: CHANNEL_ID, ...ytFields }, }); console.log(` UPDATED: ${ytId} — ${ytFields.title}`); updated++; } } console.log(`\nDone: ${created} created, ${updated} updated, ${notFound.length} not found on YouTube`); await prisma.$disconnect();