import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
};
// YouTube category IDs mapping
const categoryIds: Record = {
'gaming': '20',
'music': '10',
'education': '27',
'entertainment': '24',
'technology': '28',
'sports': '17',
'lifestyle': '22',
'news': '25',
'travel': '19',
'food': '26',
'pets': '15',
'autos': '2',
};
// Country codes
const validCountries = [
'US', 'GB', 'IN', 'CA', 'BR', 'DE', 'JP', 'AU', 'FR', 'ES', 'TR', 'PH', 'MX', 'KR', 'RU', 'IT', 'NL', 'SE', 'PL', 'ID'
];
interface ChannelData {
id: string;
title: string;
description: string;
customUrl: string;
thumbnails: any;
country?: string;
publishedAt: string;
subscriberCount: number;
viewCount: number;
videoCount: number;
subscriberGrowthIndicator?: string;
category?: string;
}
serve(async (req) => {
if (req.method === 'OPTIONS') {
return new Response(null, { headers: corsHeaders });
}
try {
const YOUTUBE_API_KEY = Deno.env.get('YOUTUBE_API_KEY');
if (!YOUTUBE_API_KEY) {
throw new Error('YouTube API key not configured');
}
const { category, country, limit = 50, rankBy = 'subscribers', searchQuery } = await req.json();
console.log(`Fetching top channels - Category: ${category || 'all'}, Country: ${country || 'all'}, Limit: ${limit}, Search: ${searchQuery || 'none'}`);
let channelIds: string[] = [];
// If search query is provided, search for channels directly
if (searchQuery && searchQuery.trim()) {
const searchUrl = `https://www.googleapis.com/youtube/v3/search?part=snippet&type=channel&q=${encodeURIComponent(searchQuery)}&maxResults=${Math.min(limit, 50)}&key=${YOUTUBE_API_KEY}`;
console.log('Searching channels with query:', searchQuery);
const searchResponse = await fetch(searchUrl);
if (!searchResponse.ok) {
const errorText = await searchResponse.text();
console.error('YouTube search API error:', errorText);
throw new Error('Failed to search YouTube channels');
}
const searchData = await searchResponse.json();
channelIds = searchData.items?.map((item: any) => item.id.channelId || item.snippet.channelId).filter(Boolean) || [];
} else {
// For browsing top channels, use mostPopular videos endpoint then extract channels
let videosUrl = `https://www.googleapis.com/youtube/v3/videos?part=snippet,statistics&chart=mostPopular&maxResults=50&key=${YOUTUBE_API_KEY}`;
if (category && categoryIds[category]) {
videosUrl += `&videoCategoryId=${categoryIds[category]}`;
}
if (country && validCountries.includes(country)) {
videosUrl += `®ionCode=${country}`;
}
console.log('Fetching popular videos to extract channels');
const videosResponse = await fetch(videosUrl);
if (!videosResponse.ok) {
const errorText = await videosResponse.text();
console.error('YouTube videos API error:', errorText);
throw new Error('Failed to fetch popular videos');
}
const videosData = await videosResponse.json();
// Extract unique channel IDs from popular videos
const uniqueChannelIds = new Set ();
videosData.items?.forEach((video: any) => {
if (video.snippet.channelId) {
uniqueChannelIds.add(video.snippet.channelId);
}
});
channelIds = Array.from(uniqueChannelIds);
console.log(`Extracted ${channelIds.length} unique channels from popular videos`);
}
if (channelIds.length === 0) {
console.log('No channels found');
return new Response(JSON.stringify({ channels: [], total: 0 }), {
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
});
}
// Fetch detailed channel statistics (API allows max 50 per request)
const statsUrl = `https://www.googleapis.com/youtube/v3/channels?part=snippet,statistics,brandingSettings&id=${channelIds.slice(0, 50).join(',')}&key=${YOUTUBE_API_KEY}`;
console.log('Fetching channel statistics for', channelIds.length, 'channels');
const statsResponse = await fetch(statsUrl);
if (!statsResponse.ok) {
const errorText = await statsResponse.text();
console.error('YouTube channels API error:', errorText);
throw new Error('Failed to fetch channel statistics');
}
const statsData = await statsResponse.json();
// Process and format channel data
const channels: ChannelData[] = statsData.items?.map((channel: any) => {
const subscriberCount = parseInt(channel.statistics.subscriberCount) || 0;
const viewCount = parseInt(channel.statistics.viewCount) || 0;
const videoCount = parseInt(channel.statistics.videoCount) || 0;
// Calculate growth indicator based on views per video
const viewsPerVideo = videoCount > 0 ? viewCount / videoCount : 0;
let growthIndicator = 'stable';
if (viewsPerVideo > 1000000) growthIndicator = 'explosive';
else if (viewsPerVideo > 100000) growthIndicator = 'high';
else if (viewsPerVideo > 10000) growthIndicator = 'moderate';
else if (viewsPerVideo < 1000) growthIndicator = 'low';
return {
id: channel.id,
title: channel.snippet.title,
description: channel.snippet.description?.substring(0, 200) || '',
customUrl: channel.snippet.customUrl || '',
thumbnails: channel.snippet.thumbnails,
country: channel.snippet.country || channel.brandingSettings?.channel?.country || 'Unknown',
publishedAt: channel.snippet.publishedAt,
subscriberCount,
viewCount,
videoCount,
subscriberGrowthIndicator: growthIndicator,
category: category || 'general'
};
}) || [];
// Sort channels by selected metric
channels.sort((a, b) => {
switch (rankBy) {
case 'views':
return b.viewCount - a.viewCount;
case 'videos':
return b.videoCount - a.videoCount;
default:
return b.subscriberCount - a.subscriberCount;
}
});
// Add rank positions
const rankedChannels = channels.map((channel, index) => ({
...channel,
rank: index + 1
}));
console.log(`Returning ${rankedChannels.length} channels`);
return new Response(JSON.stringify({
channels: rankedChannels,
total: rankedChannels.length,
filters: { category, country, rankBy, searchQuery }
}), {
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
});
} catch (error) {
console.error('Top channels error:', error);
return new Response(JSON.stringify({
error: error instanceof Error ? error.message : 'Failed to fetch top channels'
}), {
status: 500,
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
});
}
});




