doesitarm/helpers/api/youtube/build.js
ThatGuySam d45b587434 Finish axios migration via shared native HTTP helper
Replace all in-scope axios callsites with a new helpers/http.js wrapper over native fetch, including JSON/text GET, JSON POST, HEAD checks, and transient 5xx retry behavior; update all browser, build, script, and proxy API clients to use it; add focused unit tests; and remove axios from package dependencies.

Constraint: Preserve API/build and deployment behavior while lowering transport surface area.

Rejected: inline fetch replacements at each callsite | rejected to avoid inconsistent error/retry semantics.

Confidence: high

Scope-risk: moderate

Directive: Keep helper in place as the transport boundary and update tests when changing request semantics.

Tested: pnpm run -s typecheck, pnpm -s run test-prebuild, pnpm -s run test, pnpm -s run test:browser, pnpm -s run netlify-build, smoke GETs on /apple-silicon-app-test and /apple-silicon-app-test/?version=2

Not-tested: branch/netlify deployment health in CI pipeline after merge
2026-04-06 12:09:16 -05:00

176 lines
5.4 KiB
JavaScript

import fs from 'fs-extra'
import { google } from 'googleapis'
import { playlists, benchmarksPlaylistId } from './playlists.js'
import { getJson } from '~/helpers/http.js'
export const youtubeVideoPath = './static/api/youtube-videos.json'
async function getPlaylistsItems ( { playlistId } = {} ) {
const perPage = 50
// Setup Youtube API V3 Service instance
const service = google.youtube('v3')
// Fetch data from the Youtube API
const { errors = null, data = null } = await service.playlistItems.list({
key: process.env.GOOGLE_API_KEY,
part: 'snippet,contentDetails',
playlistId,
maxResults: perPage
}).catch(({ errors }) => {
console.log('Error fetching playlist', errors)
return {
errors
}
})
// Send an error response if something went wrong
if (errors !== null) {
throw new Error(errors)
return
}
const items = data.items
// If there are more results then push them to our playlist
if (data.nextPageToken !== null) {
// Store the token for page #2 into our variable
let pageToken = data.nextPageToken
while (pageToken !== null) {
// Fetch data from the Youtube API
const youtubePageResponse = await service.playlistItems.list({
key: process.env.GOOGLE_API_KEY,
part: 'snippet,contentDetails',
playlistId,
maxResults: perPage,
pageToken: pageToken
})
// Add the videos from this page on to our total items list
youtubePageResponse.data.items.forEach(item => items.push(item))
// Now that we're done set up the next page token or empty out the pageToken variable so our loop will stop
pageToken = ('nextPageToken' in youtubePageResponse.data) ? youtubePageResponse.data.nextPageToken : null
}
}
console.log(`Fetched ${items.length} videos from https://www.youtube.com/playlist?list=${ playlistId }`)
return items
}
async function getYouTubeVideos ( options = {} ) {
const {
// requestsDelay = 3600,
} = options
// Fetch all videos from playlists
const playlistSets = []
for ( const playlistToFetch of playlists ) {
// console.log('playlistJsonUrl', playlistJsonUrl)
const playlistItems = await getPlaylistsItems({
playlistId: playlistToFetch.id
})
// console.log('playlistItems', playlistItems.length)
playlistSets.push( playlistItems )
}
// Pull benchmarksPlaylist out of playlist sets
// benchmarksPlaylistId
const benchmarksVideoIds = playlistSets.find( playlist => {
// Skip empty playlists
if (playlist.length === 0) return false
// Get this playlist's ID from first video
// and check against benchmarksPlaylistId
return playlist[0].snippet.playlistId === benchmarksPlaylistId
}).map( video => video.contentDetails.videoId)
// Creat an object to store playlist items
const playlistItems = {}
// Loop through the sets and store all the videos into a single array
for (const playlistSet of playlistSets) {
for (const playlistItem of playlistSet) {
// If we've already stored this video
// then skip
if (playlistItems.hasOwnProperty(playlistItem.contentDetails.videoId)) continue
const tags = []
// If this video is in the benchmarks playlist
// then add the benchmark tag
if (benchmarksVideoIds.includes(playlistItem.contentDetails.videoId)) {
tags.push('benchmark')
}
// Store newly found video
playlistItems[playlistItem.contentDetails.videoId] = {
title: playlistItem.snippet.title,
description: playlistItem.snippet.description,
timestamps: [],
rawData: playlistItem,
tags
}
}
}
// Loop through playlist items and store timestamp data
for (const videoId in playlistItems) {
// console.log('playlistItem', playlistItem)
// If the description is empty
// then skip
if (playlistItems[videoId].description.trim().length === 0) continue
// Break up the description by line breaks
const descriptionLines = playlistItems[videoId].description.split(/\r?\n/)
// console.log('descriptionLines', descriptionLines)
for (const line of descriptionLines) {
// https://stackoverflow.com/a/11067610/1397641
const matches = line.match(/(?:([0-5]?[0-9]):)?([0-5]?[0-9]):([0-5][0-9])/)
// If there are no timestamps on this line
// then skip
if (matches === null) continue
playlistItems[videoId].timestamps.push({
time: matches[0],
fullText: line
})
}
}
return playlistItems
}
export async function saveYouTubeVideos () {
// We'll need to lean up this function to work with 10k API Requests
// before we can use it again
// const youtubeVideos = await getYouTubeVideos()
// Locked previously sucessful YouTube API data for now
const youtubeVideos = await getJson( process.env.VIDEO_SOURCE )
// Save to JSON
await fs.outputJson( youtubeVideoPath, youtubeVideos )
}