* Batch-fetches full video resources for the given IDs via `videos.list`. * * `videos.list` accepts up to 50 comma-separated IDs and costs a flat 1 quota unit per * call regardless of ID count, so a single call covers a full `playlistItems.list` page. * Videos that are private, deleted, or regio
( apiKey: string, videoIds: string[], retryOptions?: Parameters<typeof fetchWithRetry>[2] )
| 224 | * Videos that are private, deleted, or region-blocked are simply absent from the response. |
| 225 | */ |
| 226 | async function fetchVideosByIds( |
| 227 | apiKey: string, |
| 228 | videoIds: string[], |
| 229 | retryOptions?: Parameters<typeof fetchWithRetry>[2] |
| 230 | ): Promise<Map<string, VideoItem>> { |
| 231 | const result = new Map<string, VideoItem>() |
| 232 | if (videoIds.length === 0) return result |
| 233 | |
| 234 | const url = `${YOUTUBE_API_BASE}/videos?part=snippet,contentDetails,status&id=${encodeURIComponent( |
| 235 | videoIds.join(',') |
| 236 | )}&key=${encodeURIComponent(apiKey)}` |
| 237 | |
| 238 | const response = await fetchWithRetry( |
| 239 | url, |
| 240 | { method: 'GET', headers: { Accept: 'application/json' } }, |
| 241 | retryOptions |
| 242 | ) |
| 243 | |
| 244 | if (!response.ok) { |
| 245 | const errorText = await response.text().catch(() => '') |
| 246 | logger.error('Failed to batch-fetch YouTube videos', { |
| 247 | count: videoIds.length, |
| 248 | status: response.status, |
| 249 | error: errorText.slice(0, 500), |
| 250 | }) |
| 251 | throw new Error(`Failed to batch-fetch YouTube videos: ${response.status}`) |
| 252 | } |
| 253 | |
| 254 | const data = await response.json() |
| 255 | const items = (data.items ?? []) as VideoItem[] |
| 256 | for (const item of items) { |
| 257 | if (item.id) result.set(item.id, item) |
| 258 | } |
| 259 | return result |
| 260 | } |
| 261 | |
| 262 | /** |
| 263 | * Builds the full document for a video, combining title and description as plain-text |
no test coverage detected