Created
May 20, 2025 13:53
-
-
Save swamyg/51073d1b70ef0011281aa892d364e795 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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