feat: add YouTube API helper functions
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
1b3c4c929f
commit
94f68e9cae
@@ -0,0 +1,70 @@
|
||||
export class ApiError extends Error {
|
||||
constructor(kind, message, status = null) {
|
||||
super(message);
|
||||
this.name = 'ApiError';
|
||||
this.kind = kind;
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
|
||||
export function chunk(items, size) {
|
||||
const batches = [];
|
||||
for (let i = 0; i < items.length; i += size) batches.push(items.slice(i, i + size));
|
||||
return batches;
|
||||
}
|
||||
|
||||
export function parseIsoDuration(iso) {
|
||||
const match = /^P(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?)?$/.exec(iso ?? '');
|
||||
if (!match) return null;
|
||||
const [, days = 0, hours = 0, minutes = 0, seconds = 0] = match;
|
||||
return Number(days) * 86400 + Number(hours) * 3600 + Number(minutes) * 60 + Number(seconds);
|
||||
}
|
||||
|
||||
const THUMBNAIL_ORDER = ['maxres', 'standard', 'high', 'medium', 'default'];
|
||||
|
||||
export function pickThumbnail(thumbnails) {
|
||||
for (const size of THUMBNAIL_ORDER) {
|
||||
const url = thumbnails?.[size]?.url;
|
||||
if (url) return url;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const QUOTA_REASONS = new Set(['quotaExceeded', 'dailyLimitExceeded']);
|
||||
const KEY_REASONS = new Set([
|
||||
'keyInvalid',
|
||||
'API_KEY_INVALID',
|
||||
'accessNotConfigured',
|
||||
'ipRefererBlocked',
|
||||
'API_KEY_HTTP_REFERRER_BLOCKED',
|
||||
'API_KEY_SERVICE_BLOCKED',
|
||||
'SERVICE_DISABLED',
|
||||
]);
|
||||
|
||||
export function errorReasons(body) {
|
||||
const error = body?.error;
|
||||
return [...(error?.errors ?? []), ...(error?.details ?? [])].map((entry) => entry.reason).filter(Boolean);
|
||||
}
|
||||
|
||||
export function classifyError(status, body) {
|
||||
const reasons = errorReasons(body);
|
||||
if (reasons.some((reason) => QUOTA_REASONS.has(reason))) return 'quota';
|
||||
if (reasons.some((reason) => KEY_REASONS.has(reason))) return 'invalid-key';
|
||||
if (status >= 500 || status === 429) return 'server';
|
||||
return 'other';
|
||||
}
|
||||
|
||||
// The Data API quota resets at midnight Pacific Time.
|
||||
export function nextQuotaReset(now = new Date()) {
|
||||
const parts = new Intl.DateTimeFormat('en-US', {
|
||||
timeZone: 'America/Los_Angeles',
|
||||
hourCycle: 'h23',
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
second: 'numeric',
|
||||
}).formatToParts(now);
|
||||
const part = (type) => Number(parts.find((p) => p.type === type).value);
|
||||
const sinceMidnightMs =
|
||||
((part('hour') * 60 + part('minute')) * 60 + part('second')) * 1000 + now.getMilliseconds();
|
||||
return now.getTime() - sinceMidnightMs + 24 * 60 * 60 * 1000;
|
||||
}
|
||||
Reference in New Issue
Block a user