Skip to content

Instantly share code, notes, and snippets.

@swamyg
Created May 20, 2025 13:53
Show Gist options
  • Save swamyg/51073d1b70ef0011281aa892d364e795 to your computer and use it in GitHub Desktop.
Save swamyg/51073d1b70ef0011281aa892d364e795 to your computer and use it in GitHub Desktop.
import { users, type User, type InsertUser, type Favorite, type YouTubeVideo } from "@shared/schema";
import axios from "axios";
// YouTube API related functions
async function fetchYouTubeVideos(query: string): Promise<YouTubeVideo[]> {
try {
const apiKey = process.env.YOUTUBE_API_KEY;
if (!apiKey) {
throw new Error("YouTube API key is not configured");
}
console.log(`Searching YouTube for: "${query}" with API key: ${apiKey}`);
// Construct the URL for logging purposes
const youtubeApiUrl = 'https://www.googleapis.com/youtube/v3/search';
const params = {
part: 'snippet',
maxResults: 20,
q: query,
type: 'video',
key: apiKey,
safeSearch: 'moderate',
videoEmbeddable: true,
order: 'relevance',
regionCode: 'US'
};
console.log("YouTube API URL:", youtubeApiUrl);
console.log("YouTube API params:", JSON.stringify(params, null, 2));
const response = await axios.get(youtubeApiUrl, {
params
});
if (!response.data.items || response.data.items.length === 0) {
console.log("No search results found");
return [];
}
console.log(`Found ${response.data.items.length} videos for query "${query}"`);
// Extract video IDs for fetching details
const videoIds = response.data.items.map((item: any) => item.id.videoId).join(',');
// Get additional details for all videos in a single request
console.log(`Fetching additional details for ${response.data.items.length} videos`);
const detailsResponse = await axios.get('https://www.googleapis.com/youtube/v3/videos', {
params: {
part: 'snippet,contentDetails,statistics',
id: videoIds,
key: apiKey,
}
});
// Create a map of video details by ID for easy lookup
const videoDetailsMap = new Map();
if (detailsResponse.data.items && detailsResponse.data.items.length > 0) {
detailsResponse.data.items.forEach((item: any) => {
videoDetailsMap.set(item.id, {
viewCount: item.statistics?.viewCount || "0",
duration: item.contentDetails?.duration || "PT0S"
});
});
}
// Map search results with details
return response.data.items.map((item: any) => {
const videoId = item.id.videoId;
const details = videoDetailsMap.get(videoId) || { viewCount: "0", duration: "PT0S" };
return {
videoId: videoId,
title: item.snippet.title,
description: item.snippet.description,
thumbnailUrl: item.snippet.thumbnails.medium.url,
channelTitle: item.snippet.channelTitle,
publishedAt: item.snippet.publishedAt,
viewCount: details.viewCount,
duration: details.duration
};
});
} catch (error: any) {
console.error("Error fetching YouTube videos:", error);
if (error.response) {
console.error(`API Error: ${error.response.status} - ${JSON.stringify(error.response.data)}`);
}
throw new Error(`Failed to fetch YouTube videos: ${error.message}`);
}
}
// Fetch YouTube search suggestions
async function fetchYouTubeSearchSuggestions(query: string): Promise<string[]> {
try {
if (!query || query.length < 1) {
return [];
}
// Use a different YouTube suggestion API that's more reliable
const url = `https://clients1.google.com/complete/search?client=youtube&hl=en&gs_ri=youtube&ds=yt&q=${encodeURIComponent(query)}`;
console.log(`Fetching suggestions from: ${url}`);
const response = await axios.get(url, {
headers: {
'Accept-Language': 'en-US,en;q=0.9',
}
});
console.log('Response received from suggestions API');
// The API returns a string that looks like a function call with JSON data
// We need to extract the JSON part
if (response.data && typeof response.data === 'string') {
// Response is usually in the format: window.google.ac.h(JSON_DATA)
const match = response.data.match(/window\.google\.ac\.h\((.*)\)/);
if (match && match[1]) {
try {
const jsonData = JSON.parse(match[1]);
// The suggestions are in the first element of the array, and each suggestion is an array where the first element is the text
if (Array.isArray(jsonData) && jsonData.length > 0 && Array.isArray(jsonData[1])) {
const suggestions = jsonData[1].map((item: any) => item[0]);
console.log(`Found ${suggestions.length} search suggestions for "${query}":`, suggestions);
return suggestions;
}
} catch (parseError) {
console.error('Error parsing suggestion data:', parseError);
}
}
} else if (response.data && Array.isArray(response.data) && response.data.length > 1) {
// Try the original format as fallback
const suggestions = response.data[1];
if (Array.isArray(suggestions)) {
console.log(`Found ${suggestions.length} search suggestions for "${query}":`, suggestions);
return suggestions;
}
}
console.log('No suggestions found or unexpected format:', response.data);
return [];
} catch (error) {
console.error('Error fetching YouTube search suggestions:', error);
if (axios.isAxiosError(error) && error.response) {
console.error('Response error data:', error.response.data);
console.error('Response status:', error.response.status);
}
return [];
}
}
async function fetchYouTubeVideoDetails(videoId: string): Promise<YouTubeVideo | null> {
try {
const apiKey = process.env.YOUTUBE_API_KEY;
if (!apiKey) {
throw new Error("YouTube API key is not configured");
}
console.log(`Fetching video details for ID: ${videoId}`);
const response = await axios.get('https://www.googleapis.com/youtube/v3/videos', {
params: {
part: 'snippet,contentDetails,statistics',
id: videoId,
key: apiKey,
}
});
if (!response.data.items || response.data.items.length === 0) {
console.log(`No video found with ID: ${videoId}`);
return null;
}
const item = response.data.items[0];
return {
videoId: item.id,
title: item.snippet.title,
description: item.snippet.description,
thumbnailUrl: item.snippet.thumbnails.medium.url,
channelTitle: item.snippet.channelTitle,
publishedAt: item.snippet.publishedAt,
viewCount: item.statistics.viewCount || "0",
duration: item.contentDetails.duration || "PT0S"
};
} catch (error: any) {
console.error("Error fetching YouTube video details:", error);
if (error.response) {
console.error(`API Error: ${error.response.status} - ${JSON.stringify(error.response.data)}`);
}
throw new Error(`Failed to fetch YouTube video details: ${error.message}`);
}
}
export interface IStorage {
getUser(id: number): Promise<User | undefined>;
getUserByUsername(username: string): Promise<User | undefined>;
createUser(user: InsertUser): Promise<User>;
searchYouTubeVideos(query: string): Promise<YouTubeVideo[]>;
getYouTubeSearchSuggestions(query: string): Promise<string[]>;
getYouTubeVideoDetails(videoId: string): Promise<YouTubeVideo | null>;
getAllFavorites(): Promise<Favorite[]>;
getFavorite(id: number): Promise<Favorite | undefined>;
addFavorite(favorite: Omit<Favorite, "id" | "userId" | "createdAt">): Promise<Favorite>;
removeFavorite(id: number): Promise<void>;
}
export class MemStorage implements IStorage {
private users: Map<number, User>;
private favorites: Map<number, Favorite>;
currentUserId: number;
currentFavoriteId: number;
constructor() {
this.users = new Map();
this.favorites = new Map();
this.currentUserId = 1;
this.currentFavoriteId = 1;
}
async getUser(id: number): Promise<User | undefined> {
return this.users.get(id);
}
async getUserByUsername(username: string): Promise<User | undefined> {
return Array.from(this.users.values()).find(
(user) => user.username === username,
);
}
async createUser(insertUser: InsertUser): Promise<User> {
const id = this.currentUserId++;
const user: User = { ...insertUser, id };
this.users.set(id, user);
return user;
}
async searchYouTubeVideos(query: string): Promise<YouTubeVideo[]> {
return fetchYouTubeVideos(query);
}
async getYouTubeSearchSuggestions(query: string): Promise<string[]> {
return fetchYouTubeSearchSuggestions(query);
}
async getYouTubeVideoDetails(videoId: string): Promise<YouTubeVideo | null> {
return fetchYouTubeVideoDetails(videoId);
}
async getAllFavorites(): Promise<Favorite[]> {
return Array.from(this.favorites.values());
}
async getFavorite(id: number): Promise<Favorite | undefined> {
return this.favorites.get(id);
}
async addFavorite(favorite: Omit<Favorite, "id" | "userId" | "createdAt">): Promise<Favorite> {
const id = this.currentFavoriteId++;
const timestamp = new Date();
// Use 1 as default userId since we're not implementing auth in this demo
const userId = 1;
const newFavorite: Favorite = {
id,
userId,
...favorite,
createdAt: timestamp
};
this.favorites.set(id, newFavorite);
return newFavorite;
}
async removeFavorite(id: number): Promise<void> {
this.favorites.delete(id);
}
}
export const storage = new MemStorage();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment