fix: YouTube channel ID (UC...) handling + handle-to-channelId resolution
discover-channels: extractHandle('youtube') now detects UC* channel IDs
and returns them without @ prefix (previously @UC... caused verify fail)
verifyHandles: verifyYouTube uses cleanHandle for UC* check, requests
part=id,snippet for richer data
collect-channel-data: if channelId missing but handle present, resolves
via forHandle/forUsername lookup or direct UC* detection before skipping
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
claude/bold-hawking
parent
163751410f
commit
df8f84c3b9
|
|
@ -66,11 +66,11 @@ async function verifyYouTube(handle: string, apiKey: string): Promise<VerifiedCh
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try as channel ID directly (starts with UC)
|
// Try as channel ID directly (starts with UC)
|
||||||
if (handle.startsWith('UC')) {
|
if (cleanHandle.startsWith('UC')) {
|
||||||
const res = await fetch(`${YT_BASE}/channels?part=id&id=${handle}&key=${apiKey}`);
|
const res = await fetch(`${YT_BASE}/channels?part=id,snippet&id=${cleanHandle}&key=${apiKey}`);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (data.items?.[0]) {
|
if (data.items?.[0]) {
|
||||||
return { handle, verified: true, channelId: handle, url: `https://youtube.com/channel/${handle}` };
|
return { handle: cleanHandle, verified: true, channelId: cleanHandle, url: `https://youtube.com/channel/${cleanHandle}` };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,22 @@ Deno.serve(async (req) => {
|
||||||
if (YOUTUBE_API_KEY && ytVerified?.verified) {
|
if (YOUTUBE_API_KEY && ytVerified?.verified) {
|
||||||
tasks.push((async () => {
|
tasks.push((async () => {
|
||||||
const YT = "https://www.googleapis.com/youtube/v3";
|
const YT = "https://www.googleapis.com/youtube/v3";
|
||||||
const channelId = (ytVerified?.channelId as string) || "";
|
let channelId = (ytVerified?.channelId as string) || "";
|
||||||
|
|
||||||
|
// If no channelId, try to resolve from handle
|
||||||
|
if (!channelId && ytVerified?.handle) {
|
||||||
|
const h = (ytVerified.handle as string).replace(/^@/, '');
|
||||||
|
if (h.startsWith('UC')) {
|
||||||
|
channelId = h;
|
||||||
|
} else {
|
||||||
|
for (const param of ['forHandle', 'forUsername']) {
|
||||||
|
const lookupRes = await fetch(`${YT}/channels?part=id&${param}=${h}&key=${YOUTUBE_API_KEY}`);
|
||||||
|
const lookupData = await lookupRes.json();
|
||||||
|
channelId = lookupData.items?.[0]?.id || '';
|
||||||
|
if (channelId) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!channelId) return;
|
if (!channelId) return;
|
||||||
|
|
||||||
const chRes = await fetch(`${YT}/channels?part=snippet,statistics,brandingSettings&id=${channelId}&key=${YOUTUBE_API_KEY}`);
|
const chRes = await fetch(`${YT}/channels?part=snippet,statistics,brandingSettings&id=${channelId}&key=${YOUTUBE_API_KEY}`);
|
||||||
|
|
|
||||||
|
|
@ -221,6 +221,8 @@ Deno.serve(async (req) => {
|
||||||
h = h.replace(/^@/, '');
|
h = h.replace(/^@/, '');
|
||||||
// Reject if it looks like a non-YouTube URL
|
// Reject if it looks like a non-YouTube URL
|
||||||
if (h.includes('http') || h.includes('/') || h.includes('.com')) return null;
|
if (h.includes('http') || h.includes('/') || h.includes('.com')) return null;
|
||||||
|
// Channel IDs start with UC — don't add @ prefix
|
||||||
|
if (/^UC[a-zA-Z0-9_-]{20,}$/.test(h)) return h;
|
||||||
if (/^[a-zA-Z0-9._-]+$/.test(h) && h.length >= 2) return `@${h}`;
|
if (/^[a-zA-Z0-9._-]+$/.test(h) && h.length >= 2) return `@${h}`;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue