/** * Music API Library for Deno * Contains all the core functionality for YouTube Music, YouTube Search, JioSaavn, and Last.fm */ // ============ YOUTUBE MUSIC API ============ export class YTMusic { private baseURL: string; private apiKey = "AIzaSyC9XL3ZjWjXClIX1FmUxJq--EohcD4_oSs"; private context: any; constructor() { this.baseURL = "https://music.youtube.com/youtubei/v1"; this.context = { client: { hl: "en", gl: "US", clientName: "WEB_REMIX", clientVersion: "1.20251015.03.00", platform: "DESKTOP", }, }; } async search(query: string, filter?: string, continuationToken?: string, ignoreSpelling = false) { const params: any = continuationToken ? { continuation: continuationToken } : { query, params: this.getFilterParams(filter) }; const data = await this.makeRequest("search", params); return this.parseSearchResults(data); } async getSearchSuggestions(query: string): Promise { const data = await this.makeRequest("music/get_search_suggestions", { input: query }); return this.parseSuggestions(data); } async getSong(videoId: string) { const data = await this.makeRequest("player", { videoId }); const details = data?.videoDetails || {}; return { videoId: details.videoId, title: details.title, author: details.author, lengthSeconds: details.lengthSeconds, thumbnail: details.thumbnail?.thumbnails?.[0]?.url, }; } async getAlbum(browseId: string) { const data = await this.makeRequest("browse", { browseId }); const header = data?.header?.musicDetailHeaderRenderer || data?.header?.musicImmersiveHeaderRenderer || {}; const contents = data?.contents?.singleColumnBrowseResultsRenderer?.tabs?.[0]?.tabRenderer?.content?.sectionListRenderer?.contents || []; return { title: header.title?.runs?.[0]?.text, artist: header.subtitle?.runs?.[0]?.text, thumbnail: header.thumbnail?.musicThumbnailRenderer?.thumbnail?.thumbnails?.[0]?.url, tracks: this.parseTracksFromContents(contents), }; } async getArtist(browseId: string) { const data = await this.makeRequest("browse", { browseId }); const header = data?.header?.musicImmersiveHeaderRenderer || data?.header?.musicVisualHeaderRenderer || {}; return { name: header.title?.runs?.[0]?.text, description: header.description?.runs?.[0]?.text, thumbnail: header.thumbnail?.musicThumbnailRenderer?.thumbnail?.thumbnails?.[0]?.url, }; } async getArtistSummary(artistId: string, country = "US") { const url = "https://music.youtube.com/youtubei/v1/browse?prettyPrint=false"; const body = { browseId: artistId, context: { client: { ...this.context.client, gl: country } }, }; const response = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); const data = await response.json(); const header = data?.header?.musicImmersiveHeaderRenderer || data?.header?.musicVisualHeaderRenderer; const contents = data?.contents?.singleColumnBrowseResultsRenderer?.tabs?.[0]?.tabRenderer?.content?.sectionListRenderer?.contents || []; // Find top songs playlist ID let playlistId = null; for (const item of contents) { if (item.musicShelfRenderer?.title?.runs?.[0]?.text === "Top songs") { playlistId = item.musicShelfRenderer.contents?.[0]?.musicResponsiveListItemRenderer?.flexColumns?.[0] ?.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.[0]?.navigationEndpoint?.watchEndpoint?.playlistId; break; } } // Find recommended artists let recommendedArtists = null; for (const item of contents) { const headerTitle = item.musicCarouselShelfRenderer?.header?.musicCarouselShelfBasicHeaderRenderer?.title?.runs?.[0]?.text; if (headerTitle === "Fans might also like") { recommendedArtists = (item.musicCarouselShelfRenderer.contents || []).map((it: any) => ({ name: it.musicTwoRowItemRenderer?.title?.runs?.[0]?.text, browseId: it.musicTwoRowItemRenderer?.navigationEndpoint?.browseEndpoint?.browseId, thumbnail: it.musicTwoRowItemRenderer?.thumbnailRenderer?.musicThumbnailRenderer?.thumbnail?.thumbnails?.[0]?.url, })); break; } } return { artistName: header?.title?.runs?.[0]?.text, artistAvatar: header?.thumbnail?.musicThumbnailRenderer?.thumbnail?.thumbnails?.[0]?.url, playlistId, recommendedArtists, }; } async getPlaylist(playlistId: string) { const data = await this.makeRequest("browse", { browseId: `VL${playlistId.replace(/^VL/, "")}` }); const header = data?.header?.musicDetailHeaderRenderer || {}; const contents = data?.contents?.singleColumnBrowseResultsRenderer?.tabs?.[0]?.tabRenderer?.content?.sectionListRenderer?.contents || []; return { title: header.title?.runs?.[0]?.text, author: header.subtitle?.runs?.[0]?.text, thumbnail: header.thumbnail?.musicThumbnailRenderer?.thumbnail?.thumbnails?.[0]?.url, tracks: this.parseTracksFromContents(contents), }; } async getCharts(country?: string) { const data = await this.makeRequest("browse", { browseId: "FEmusic_charts", formData: { selectedValues: [country || "US"] }, }); return this.parseChartsData(data); } async getMoodCategories() { const data = await this.makeRequest("browse", { browseId: "FEmusic_moods_and_genres" }); return this.parseMoodsData(data); } async getMoodPlaylists(categoryId: string) { const data = await this.makeRequest("browse", { browseId: categoryId }); const contents = data?.contents?.singleColumnBrowseResultsRenderer?.tabs?.[0]?.tabRenderer?.content?.sectionListRenderer?.contents || []; const playlists: any[] = []; for (const section of contents) { const items = section.musicShelfRenderer?.contents || []; for (const item of items) { const parsed = this.parseMusicItem(item.musicResponsiveListItemRenderer); if (parsed) playlists.push(parsed); } } return playlists; } async getWatchPlaylist(videoId?: string, playlistId?: string, radio = false, shuffle = false, limit = 25) { const data = await this.makeRequest("next", { videoId, playlistId, radio, shuffle }); const contents = data?.contents?.singleColumnMusicWatchNextResultsRenderer?.tabbedRenderer?.watchNextTabbedResultsRenderer ?.tabs?.[0]?.tabRenderer?.content?.musicQueueRenderer?.content?.playlistPanelRenderer?.contents || []; const tracks = contents.map((item: any) => { const video = item.playlistPanelVideoRenderer; if (!video) return null; return { videoId: video.videoId, title: video.title?.runs?.[0]?.text, author: video.shortBylineText?.runs?.[0]?.text, thumbnail: video.thumbnail?.thumbnails?.[0]?.url, }; }).filter(Boolean); return { tracks: tracks.slice(0, limit) }; } async getRelated(videoId: string) { // Use YouTube's next endpoint for related videos const url = `https://www.youtube.com/youtubei/v1/next?key=${this.apiKey}`; const body = { videoId, context: { client: { clientName: "WEB", clientVersion: "2.20251013.01.00" } }, }; const response = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); const data = await response.json(); const secondaryResults = data?.contents?.twoColumnWatchNextResults?.secondaryResults?.secondaryResults?.results || []; return secondaryResults .filter((item: any) => item.compactVideoRenderer) .map((item: any) => { const video = item.compactVideoRenderer; const durationText = video.lengthText?.simpleText || ""; let durationSeconds = 0; if (durationText) { const parts = durationText.split(":").map((p: string) => parseInt(p) || 0); if (parts.length === 2) durationSeconds = parts[0] * 60 + parts[1]; else if (parts.length === 3) durationSeconds = parts[0] * 3600 + parts[1] * 60 + parts[2]; } return { videoId: video.videoId, title: video.title?.simpleText || video.title?.runs?.[0]?.text, artist: video.shortBylineText?.runs?.[0]?.text, thumbnail: video.thumbnail?.thumbnails?.[0]?.url, duration: durationText, duration_seconds: durationSeconds, isShort: durationSeconds > 0 && durationSeconds <= 60, }; }) .filter((v: any) => v.videoId && !v.isShort) .slice(0, 20); } private async makeRequest(endpoint: string, params: any) { const url = `${this.baseURL}/${endpoint}?key=${this.apiKey}`; const body = { context: this.context, ...params }; const response = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); return response.json(); } private getFilterParams(filter?: string): string { const filterMap: Record = { songs: "Eg-KAQwIARAAGAAgACgAMABqChAEEAUQAxAKEAk%3D", videos: "Eg-KAQwIABABGAAgACgAMABqChAEEAUQAxAKEAk%3D", albums: "Eg-KAQwIABAAGAEgACgAMABqChAEEAUQAxAKEAk%3D", artists: "EgWKAQIgAWoKEAMQBBAJEAoQBQ%3D%3D", playlists: "Eg-KAQwIABAAGAAgACgBMABqChAEEAUQAxAKEAk%3D", }; return filterMap[filter || ""] || filterMap.songs; } private parseSearchResults(data: any) { const results: any[] = []; let continuationToken: string | null = null; // Handle continuation const actions = data?.onResponseReceivedCommands || []; for (const action of actions) { const items = action?.appendContinuationItemsAction?.continuationItems || []; for (const entry of items) { if (entry.musicShelfRenderer || entry.musicShelfContinuation) { const shelf = entry.musicShelfRenderer || entry.musicShelfContinuation; for (const item of shelf.contents || []) { const parsed = this.parseMusicItem(item.musicResponsiveListItemRenderer); if (parsed) results.push(parsed); } continuationToken = shelf.continuations?.[0]?.nextContinuationData?.continuation || continuationToken; } if (entry.continuationItemRenderer) { continuationToken = entry.continuationItemRenderer?.continuationEndpoint?.continuationCommand?.token || continuationToken; } } } // Handle initial results if (results.length === 0) { const sections = data?.contents?.tabbedSearchResultsRenderer?.tabs?.[0]?.tabRenderer?.content?.sectionListRenderer?.contents || []; for (const section of sections) { if (section.musicShelfRenderer) { for (const item of section.musicShelfRenderer.contents || []) { const parsed = this.parseMusicItem(item.musicResponsiveListItemRenderer); if (parsed) results.push(parsed); } continuationToken = section.musicShelfRenderer.continuations?.[0]?.nextContinuationData?.continuation || continuationToken; } } } return { results, continuationToken }; } private parseSuggestions(data: any): string[] { const suggestions: string[] = []; const contents = data?.contents?.[0]?.searchSuggestionsSectionRenderer?.contents || data?.contents || []; for (const content of contents) { const runs = content?.searchSuggestionRenderer?.suggestion?.runs || []; const text = runs.map((r: any) => r.text).join(""); if (text) suggestions.push(text); } return suggestions; } private parseMusicItem(item: any) { if (!item) return null; const title = item.flexColumns?.[0]?.musicResponsiveListItemFlexColumnRenderer?.text?.runs?.[0]?.text; const thumbnail = item.thumbnail?.musicThumbnailRenderer?.thumbnail?.thumbnails?.[0]?.url; const videoId = item.overlay?.musicItemThumbnailOverlayRenderer?.content?.musicPlayButtonRenderer ?.playNavigationEndpoint?.watchEndpoint?.videoId; const browseId = item.navigationEndpoint?.browseEndpoint?.browseId; const subtitle = item.flexColumns?.[1]?.musicResponsiveListItemFlexColumnRenderer?.text?.runs || []; const artists = subtitle .filter((r: any) => r.navigationEndpoint?.browseEndpoint?.browseEndpointContextSupportedConfigs ?.browseEndpointContextMusicConfig?.pageType === "MUSIC_PAGE_TYPE_ARTIST") .map((r: any) => ({ name: r.text, id: r.navigationEndpoint?.browseEndpoint?.browseId })); const duration = item.fixedColumns?.[0]?.musicResponsiveListItemFixedColumnRenderer?.text?.runs?.[0]?.text; return { title, thumbnails: [{ url: thumbnail }], videoId, browseId, artists, duration, resultType: videoId ? "song" : browseId?.startsWith("UC") ? "artist" : "album", }; } private parseTracksFromContents(contents: any[]): any[] { const tracks: any[] = []; for (const section of contents) { const items = section.musicShelfRenderer?.contents || []; for (const item of items) { const parsed = this.parseMusicItem(item.musicResponsiveListItemRenderer); if (parsed) tracks.push(parsed); } } return tracks; } private parseChartsData(data: any) { const results: any[] = []; // Try different response structures const contents = data?.contents?.singleColumnBrowseResultsRenderer?.tabs?.[0]?.tabRenderer?.content?.sectionListRenderer?.contents || []; for (const section of contents) { // Handle musicCarouselShelfRenderer (common for charts) if (section.musicCarouselShelfRenderer) { const title = section.musicCarouselShelfRenderer?.header?.musicCarouselShelfBasicHeaderRenderer?.title?.runs?.[0]?.text; const items = (section.musicCarouselShelfRenderer?.contents || []).map((item: any) => { const renderer = item.musicTwoRowItemRenderer || item.musicResponsiveListItemRenderer; return this.parseTwoRowItem(renderer); }).filter(Boolean); if (title && items.length) results.push({ title, items }); } // Handle musicShelfRenderer if (section.musicShelfRenderer) { const title = section.musicShelfRenderer?.title?.runs?.[0]?.text; const items = (section.musicShelfRenderer?.contents || []).map((item: any) => this.parseMusicItem(item.musicResponsiveListItemRenderer) ).filter(Boolean); if (title && items.length) results.push({ title, items }); } } return results; } private parseMoodsData(data: any) { const results: any[] = []; const contents = data?.contents?.singleColumnBrowseResultsRenderer?.tabs?.[0]?.tabRenderer?.content?.sectionListRenderer?.contents || []; for (const section of contents) { if (section.gridRenderer) { const items = (section.gridRenderer?.items || []).map((item: any) => { const nav = item.musicNavigationButtonRenderer; if (!nav) return null; return { title: nav.buttonText?.runs?.[0]?.text, browseId: nav.clickCommand?.browseEndpoint?.browseId, color: nav.solid?.leftStripeColor, }; }).filter(Boolean); if (items.length) results.push({ title: "Moods & Genres", items }); } if (section.musicCarouselShelfRenderer) { const title = section.musicCarouselShelfRenderer?.header?.musicCarouselShelfBasicHeaderRenderer?.title?.runs?.[0]?.text; const items = (section.musicCarouselShelfRenderer?.contents || []).map((item: any) => { const nav = item.musicNavigationButtonRenderer; if (!nav) return null; return { title: nav.buttonText?.runs?.[0]?.text, browseId: nav.clickCommand?.browseEndpoint?.browseId, color: nav.solid?.leftStripeColor, }; }).filter(Boolean); if (title && items.length) results.push({ title, items }); } } return results; } private parseTwoRowItem(item: any) { if (!item) return null; return { title: item.title?.runs?.[0]?.text, subtitle: item.subtitle?.runs?.map((r: any) => r.text).join(""), thumbnails: item.thumbnailRenderer?.musicThumbnailRenderer?.thumbnail?.thumbnails, videoId: item.navigationEndpoint?.watchEndpoint?.videoId, browseId: item.navigationEndpoint?.browseEndpoint?.browseId, playlistId: item.navigationEndpoint?.watchEndpoint?.playlistId, }; } } // ============ YOUTUBE SEARCH ============ export class YouTubeSearch { private searchURL = "https://www.youtube.com/results"; private continuationURL = "https://www.youtube.com/youtubei/v1/search"; private suggestionsURL = "https://suggestqueries-clients6.youtube.com/complete/search"; private apiKey: string | null = null; private clientVersion: string | null = null; async searchVideos(query: string | null, continuationToken?: string) { if (continuationToken) { return this.fetchContinuation(continuationToken, "video"); } if (!query) throw new Error("Query is required for initial search"); const response = await fetch(`${this.searchURL}?search_query=${encodeURIComponent(query)}&sp=EgIQAQ%253D%253D`); const html = await response.text(); this.extractAPIConfig(html); return this.parseVideoResults(html); } async searchChannels(query: string | null, continuationToken?: string) { if (continuationToken) { return this.fetchContinuation(continuationToken, "channel"); } if (!query) throw new Error("Query is required for initial search"); const response = await fetch(`${this.searchURL}?search_query=${encodeURIComponent(query)}&sp=EgIQAg%253D%253D`); const html = await response.text(); this.extractAPIConfig(html); return this.parseChannelResults(html); } async searchPlaylists(query: string | null, continuationToken?: string) { if (continuationToken) { return this.fetchContinuation(continuationToken, "playlist"); } if (!query) throw new Error("Query is required for initial search"); const response = await fetch(`${this.searchURL}?search_query=${encodeURIComponent(query)}&sp=EgIQAw%253D%253D`); const html = await response.text(); this.extractAPIConfig(html); return this.parsePlaylistResults(html); } async getSuggestions(query: string): Promise { try { const url = `${this.suggestionsURL}?ds=yt&client=youtube&q=${encodeURIComponent(query)}`; const response = await fetch(url); const text = await response.text(); // Parse JSONP response const start = text.indexOf("("); const end = text.lastIndexOf(")"); if (start === -1 || end === -1) return this.getStaticSuggestions(query); const json = JSON.parse(text.slice(start + 1, end)); return (json[1] || []).map((item: any) => Array.isArray(item) ? item[0] : item).slice(0, 10); } catch { return this.getStaticSuggestions(query); } } private extractAPIConfig(html: string) { const apiKeyMatch = html.match(/"INNERTUBE_API_KEY":"([^"]+)"/); const clientVersionMatch = html.match(/"clientVersion":"([^"]+)"/); if (apiKeyMatch) this.apiKey = apiKeyMatch[1]; if (clientVersionMatch) this.clientVersion = clientVersionMatch[1]; } private async fetchContinuation(token: string, type: string) { if (!this.apiKey) throw new Error("API key not initialized"); const response = await fetch(`${this.continuationURL}?key=${this.apiKey}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ continuation: token, context: { client: { clientName: "WEB", clientVersion: this.clientVersion || "2.20231219.01.00" } }, }), }); const data = await response.json(); return this.parseContinuationResults(data, type); } private parseVideoResults(html: string) { const results: any[] = []; let continuationToken: string | null = null; const jsonMatch = html.match(/var ytInitialData = ({.+?});/); if (!jsonMatch) return { results, continuationToken }; const data = JSON.parse(jsonMatch[1]); const sections = data?.contents?.twoColumnSearchResultsRenderer?.primaryContents?.sectionListRenderer?.contents || []; const items = sections[0]?.itemSectionRenderer?.contents || []; for (const item of items) { if (item.videoRenderer) { results.push(this.parseVideoRenderer(item.videoRenderer)); } } continuationToken = this.extractContinuationToken(data); return { results, continuationToken }; } private parseChannelResults(html: string) { const results: any[] = []; let continuationToken: string | null = null; const jsonMatch = html.match(/var ytInitialData = ({.+?});/); if (!jsonMatch) return { results, continuationToken }; const data = JSON.parse(jsonMatch[1]); const sections = data?.contents?.twoColumnSearchResultsRenderer?.primaryContents?.sectionListRenderer?.contents || []; for (const section of sections) { for (const item of section?.itemSectionRenderer?.contents || []) { if (item.channelRenderer) { results.push(this.parseChannelRenderer(item.channelRenderer)); } } } continuationToken = this.extractContinuationToken(data); return { results, continuationToken }; } private parsePlaylistResults(html: string) { const results: any[] = []; let continuationToken: string | null = null; const jsonMatch = html.match(/var ytInitialData = ({.+?});/); if (!jsonMatch) return { results, continuationToken }; const data = JSON.parse(jsonMatch[1]); const sections = data?.contents?.twoColumnSearchResultsRenderer?.primaryContents?.sectionListRenderer?.contents || []; for (const section of sections) { for (const item of section?.itemSectionRenderer?.contents || []) { if (item.playlistRenderer) { results.push(this.parsePlaylistRenderer(item.playlistRenderer)); } } } continuationToken = this.extractContinuationToken(data); return { results, continuationToken }; } private parseContinuationResults(data: any, type: string) { const results: any[] = []; let continuationToken: string | null = null; const actions = data?.onResponseReceivedCommands || []; for (const action of actions) { const items = action?.appendContinuationItemsAction?.continuationItems || []; for (const item of items) { if (item.continuationItemRenderer) { continuationToken = item.continuationItemRenderer?.continuationEndpoint?.continuationCommand?.token; continue; } if (type === "video" && item.videoRenderer) { results.push(this.parseVideoRenderer(item.videoRenderer)); } else if (type === "channel" && item.channelRenderer) { results.push(this.parseChannelRenderer(item.channelRenderer)); } else if (type === "playlist" && item.playlistRenderer) { results.push(this.parsePlaylistRenderer(item.playlistRenderer)); } if (item.itemSectionRenderer?.contents) { for (const inner of item.itemSectionRenderer.contents) { if (type === "video" && inner.videoRenderer) results.push(this.parseVideoRenderer(inner.videoRenderer)); else if (type === "channel" && inner.channelRenderer) results.push(this.parseChannelRenderer(inner.channelRenderer)); else if (type === "playlist" && inner.playlistRenderer) results.push(this.parsePlaylistRenderer(inner.playlistRenderer)); } } } } return { results, continuationToken }; } private extractContinuationToken(data: any): string | null { const sections = data?.contents?.twoColumnSearchResultsRenderer?.primaryContents?.sectionListRenderer?.contents || []; for (const section of sections) { if (section.continuationItemRenderer) { return section.continuationItemRenderer?.continuationEndpoint?.continuationCommand?.token || null; } } return null; } private parseVideoRenderer(v: any) { return { type: "video", id: v.videoId, title: v.title?.runs?.[0]?.text, duration: v.lengthText?.simpleText, channel: { id: v.ownerText?.runs?.[0]?.navigationEndpoint?.browseEndpoint?.browseId, name: v.ownerText?.runs?.[0]?.text, }, thumbnails: v.thumbnail?.thumbnails, publishedTime: v.publishedTimeText?.simpleText, viewCount: { text: v.viewCountText?.simpleText }, link: `https://www.youtube.com/watch?v=${v.videoId}`, }; } private parseChannelRenderer(c: any) { return { type: "channel", channelId: c.channelId, title: c.title?.simpleText, thumbnail: c.thumbnail?.thumbnails?.[0]?.url, subscriberCount: c.subscriberCountText?.simpleText, videoCount: c.videoCountText?.simpleText, url: `https://www.youtube.com/channel/${c.channelId}`, }; } private parsePlaylistRenderer(p: any) { return { type: "playlist", playlistId: p.playlistId, title: p.title?.simpleText, thumbnail: p.thumbnails?.[0]?.thumbnails?.[0]?.url, videoCount: p.videoCount, author: p.shortBylineText?.runs?.[0]?.text, url: `https://www.youtube.com/playlist?list=${p.playlistId}`, }; } private getStaticSuggestions(query: string): string[] { return [query, `${query} video`, `${query} 2024`, `${query} tutorial`, `${query} song`]; } } // ============ LAST.FM ============ export const LastFM = { API_KEY: "0867bcb6f36c879398969db682a7b69b", async getSimilarTracks(title: string, artist: string, limit = "5") { const url = `https://ws.audioscrobbler.com/2.0/?method=track.getsimilar&artist=${encodeURIComponent(artist)}&track=${encodeURIComponent(title)}&api_key=${this.API_KEY}&limit=${limit}&format=json`; try { const response = await fetch(url); const data = await response.json(); if (data?.error) return { error: data.message || "Last.fm error" }; return (data?.similartracks?.track || []) .map((t: any) => ({ title: t.name, artist: t?.artist?.name })) .filter((t: any) => t.title && t.artist); } catch { return { error: "Failed to fetch similar tracks" }; } }, }; // ============ STREAMING SOURCES ============ let instancesCache: any = null; let instancesCacheTime = 0; const CACHE_DURATION = 5 * 60 * 1000; export async function getDynamicInstances() { const now = Date.now(); if (instancesCache && (now - instancesCacheTime) < CACHE_DURATION) { return instancesCache; } try { const response = await fetch("https://raw.githubusercontent.com/n-ce/Uma/main/dynamic_instances.json"); instancesCache = await response.json(); instancesCacheTime = now; return instancesCache; } catch { return { piped: ["https://api.piped.private.coffee"], invidious: ["https://invidious.nikkosphere.com", "https://yt.omada.cafe"], }; } } export async function fetchFromPiped(videoId: string) { const instances = await getDynamicInstances(); const pipedInstances = instances.piped || []; for (const instance of pipedInstances) { try { const response = await fetch(`${instance}/streams/${videoId}`); const data = await response.json(); if (data?.audioStreams?.length) { return { success: true, instance, streamingUrls: data.audioStreams.map((s: any) => ({ url: s.url, quality: s.quality, mimeType: s.mimeType, bitrate: s.bitrate, })), metadata: { id: videoId, title: data.title, uploader: data.uploader, thumbnail: data.thumbnailUrl, duration: data.duration, views: data.views, }, }; } } catch { continue; } } return { success: false, error: "No working Piped instances found" }; } export async function fetchFromInvidious(videoId: string) { const instances = await getDynamicInstances(); const invidiousInstances = instances.invidious || []; for (const instance of invidiousInstances) { try { const response = await fetch(`${instance}/api/v1/videos/${videoId}`); const data = await response.json(); if (data) { const audioFormats = (data.adaptiveFormats || []).filter((f: any) => f.type?.includes("audio") || f.mimeType?.includes("audio") ); return { success: true, instance, streamingUrls: audioFormats.map((f: any) => ({ url: f.url, bitrate: f.bitrate, type: f.type, audioQuality: f.audioQuality, })), metadata: { id: videoId, title: data.title, author: data.author, thumbnail: data.videoThumbnails?.[0]?.url, lengthSeconds: data.lengthSeconds, viewCount: data.viewCount, }, }; } } catch { continue; } } return { success: false, error: "No working Invidious instances found" }; } // ============ LYRICS (LRCLib - Free, No API Key) ============ export async function getLyrics(title: string, artist: string, duration?: number) { try { // Try exact match first let url = `https://lrclib.net/api/get?track_name=${encodeURIComponent(title)}&artist_name=${encodeURIComponent(artist)}`; if (duration) url += `&duration=${duration}`; let response = await fetch(url); let data = await response.json(); // If no exact match, try search if (!data || data.statusCode === 404) { const searchUrl = `https://lrclib.net/api/search?q=${encodeURIComponent(`${title} ${artist}`)}`; response = await fetch(searchUrl); const results = await response.json(); if (Array.isArray(results) && results.length > 0) { data = results[0]; } } if (!data || data.statusCode) { return { success: false, error: "Lyrics not found" }; } return { success: true, trackName: data.trackName, artistName: data.artistName, albumName: data.albumName, duration: data.duration, plainLyrics: data.plainLyrics, syncedLyrics: data.syncedLyrics, // LRC format with timestamps }; } catch (err) { return { success: false, error: String(err) }; } } // ============ TRENDING MUSIC (YouTube Music Search by Country) ============ export async function getTrendingMusic(country = "United States", ytmusic?: YTMusic) { try { if (ytmusic) { const searchQueries = [ `${country}n music 2026`, `${country}n songs`, `${country}n hits`, `popular ${country}n music`, `new ${country}n songs 2026`, ]; const allTracks: any[] = []; const seenIds = new Set(); for (const query of searchQueries) { if (allTracks.length >= 30) break; const results = await ytmusic.search(query, "songs"); if (results.results) { for (const t of results.results) { if (t.videoId && !seenIds.has(t.videoId)) { seenIds.add(t.videoId); allTracks.push({ name: t.title, artist: t.artists?.map((a: any) => a.name).join(", "), videoId: t.videoId, thumbnail: t.thumbnails?.[0]?.url, duration: t.duration, }); } if (allTracks.length >= 30) break; } } } if (allTracks.length > 0) { return { success: true, country: country, tracks: allTracks, }; } } return { success: false, error: "Could not fetch trending" }; } catch (err) { return { success: false, error: String(err) }; } } // ============ RADIO (Infinite Mix based on song) ============ export async function getRadio(videoId: string, ytmusic: YTMusic) { try { // Use YouTube Music's radio feature const data = await ytmusic.getWatchPlaylist(videoId, undefined, true, false, 50); if (data.tracks && data.tracks.length > 0) { return { success: true, seedVideoId: videoId, tracks: data.tracks, }; } return { success: false, error: "Could not generate radio" }; } catch (err) { return { success: false, error: String(err) }; } } // ============ TOP ARTISTS BY COUNTRY (YouTube Music Search) ============ export async function getTopArtists(country?: string, limit = 20, ytmusic?: YTMusic) { try { if (country && ytmusic) { // More specific search queries for artists FROM that country const searchQueries = [ `${country}n artist`, // Tunisian artist `${country}n singer`, `${country}n rapper`, `${country}n musician`, `artist from ${country}`, `singer from ${country}`, ]; const allArtists: any[] = []; const seenIds = new Set(); for (const query of searchQueries) { if (allArtists.length >= limit) break; const results = await ytmusic.search(query, "artists"); if (results.results) { for (const a of results.results) { if (a.browseId && !seenIds.has(a.browseId)) { seenIds.add(a.browseId); allArtists.push({ name: a.title, browseId: a.browseId, thumbnail: a.thumbnails?.[0]?.url, }); } if (allArtists.length >= limit) break; } } } if (allArtists.length > 0) { return { success: true, country: country, artists: allArtists.slice(0, limit), }; } } // Fallback to Last.fm global charts const url = `https://ws.audioscrobbler.com/2.0/?method=chart.gettopartists&api_key=${LastFM.API_KEY}&limit=${limit}&format=json`; const response = await fetch(url); const data = await response.json(); const artists = data?.artists?.artist || []; return { success: true, country: "Global", artists: artists.map((a: any) => ({ name: a.name, playcount: a.playcount, listeners: a.listeners, url: a.url, image: a.image?.find((i: any) => i.size === "large")?.["#text"], })), }; } catch (err) { return { success: false, error: String(err) }; } } // ============ TOP TRACKS BY COUNTRY (YouTube Music Search) ============ export async function getTopTracks(country?: string, limit = 20, ytmusic?: YTMusic) { try { if (country && ytmusic) { // More specific search queries for music FROM that country const searchQueries = [ `${country}n music`, // Tunisian music `${country}n songs`, `${country}n rap`, `${country}n hits 2026`, `music from ${country}`, `songs from ${country}`, ]; const allTracks: any[] = []; const seenIds = new Set(); for (const query of searchQueries) { if (allTracks.length >= limit) break; const results = await ytmusic.search(query, "songs"); if (results.results) { for (const t of results.results) { if (t.videoId && !seenIds.has(t.videoId)) { seenIds.add(t.videoId); allTracks.push({ name: t.title, artist: t.artists?.map((a: any) => a.name).join(", "), videoId: t.videoId, thumbnail: t.thumbnails?.[0]?.url, duration: t.duration, }); } if (allTracks.length >= limit) break; } } } if (allTracks.length > 0) { return { success: true, country: country, tracks: allTracks.slice(0, limit), }; } } // Fallback to Last.fm global charts const url = `https://ws.audioscrobbler.com/2.0/?method=chart.gettoptracks&api_key=${LastFM.API_KEY}&limit=${limit}&format=json`; const response = await fetch(url); const data = await response.json(); const tracks = data?.tracks?.track || []; return { success: true, country: "Global", tracks: tracks.map((t: any) => ({ name: t.name, artist: t.artist?.name, playcount: t.playcount, listeners: t.listeners, url: t.url, })), }; } catch (err) { return { success: false, error: String(err) }; } } // ============ ARTIST INFO (Last.fm) ============ export async function getArtistInfo(artist: string) { try { const url = `https://ws.audioscrobbler.com/2.0/?method=artist.getinfo&artist=${encodeURIComponent(artist)}&api_key=${LastFM.API_KEY}&format=json`; const response = await fetch(url); const data = await response.json(); if (data?.error) { return { success: false, error: data.message }; } const a = data?.artist; return { success: true, name: a?.name, bio: a?.bio?.summary?.replace(/<[^>]*>/g, ""), // Strip HTML tags: a?.tags?.tag?.map((t: any) => t.name) || [], similar: a?.similar?.artist?.map((s: any) => s.name) || [], stats: { listeners: a?.stats?.listeners, playcount: a?.stats?.playcount, }, image: a?.image?.find((i: any) => i.size === "large")?.["#text"], }; } catch (err) { return { success: false, error: String(err) }; } } // ============ TRACK INFO (Last.fm) ============ export async function getTrackInfo(title: string, artist: string) { try { const url = `https://ws.audioscrobbler.com/2.0/?method=track.getInfo&artist=${encodeURIComponent(artist)}&track=${encodeURIComponent(title)}&api_key=${LastFM.API_KEY}&format=json`; const response = await fetch(url); const data = await response.json(); if (data?.error) { return { success: false, error: data.message }; } const t = data?.track; return { success: true, name: t?.name, artist: t?.artist?.name, album: t?.album?.title, duration: t?.duration, listeners: t?.listeners, playcount: t?.playcount, tags: t?.toptags?.tag?.map((tag: any) => tag.name) || [], wiki: t?.wiki?.summary?.replace(/<[^>]*>/g, ""), }; } catch (err) { return { success: false, error: String(err) }; } }