UNPKG

spotify-find-preview

Version:

Get Spotify song preview URLs along with song details. Now supports searching by song name and artist for improved accuracy. Modern TypeScript module with full type support.

140 lines 5.16 kB
import axios from 'axios'; import * as cheerio from 'cheerio'; import SpotifyWebApi from 'spotify-web-api-node'; import { config as dotenvConfig } from 'dotenv'; // Load environment variables dotenvConfig(); /** * Creates a Spotify API client with credentials from environment variables * @throws {Error} If SPOTIFY_CLIENT_ID or SPOTIFY_CLIENT_SECRET are not set * @returns {SpotifyWebApi} Configured Spotify API client */ function createSpotifyApi() { const clientId = process.env.SPOTIFY_CLIENT_ID; const clientSecret = process.env.SPOTIFY_CLIENT_SECRET; if (!clientId || !clientSecret) { throw new Error('SPOTIFY_CLIENT_ID and SPOTIFY_CLIENT_SECRET environment variables are required'); } return new SpotifyWebApi({ clientId, clientSecret }); } /** * Fetches and extracts Spotify CDN links from a Spotify URL * @param url - The Spotify track URL to scrape * @returns Promise resolving to an array of preview URLs * @throws {Error} If fetching or parsing fails */ async function getSpotifyLinks(url) { try { const response = await axios.get(url); const html = response.data; const $ = cheerio.load(html); const scdnLinks = new Set(); $('*').each((_, element) => { if ('attribs' in element && element.attribs) { const attrs = element.attribs; Object.values(attrs).forEach(value => { if (value && typeof value === 'string' && value.includes('p.scdn.co')) { scdnLinks.add(value); } }); } }); return Array.from(scdnLinks); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; throw new Error(`Failed to fetch preview URLs: ${errorMessage}`); } } /** * Search for songs and get their preview URLs * @param songName - The name of the song to search for * @param artistOrLimit - Artist name (string) or limit (number) for backward compatibility * @param limit - Maximum number of results to return (only used when artistOrLimit is a string) * @returns Promise resolving to search results with track information * * @example * // Basic search * await searchAndGetLinks('Shape of You'); * * @example * // Search with limit * await searchAndGetLinks('Shape of You', 3); * * @example * // Search with artist for better accuracy * await searchAndGetLinks('Shape of You', 'Ed Sheeran', 2); */ export async function searchAndGetLinks(songName, artistOrLimit, limit = 5) { try { if (!songName) { throw new Error('Song name is required'); } // Handle backward compatibility and parameter parsing let artist = null; let actualLimit = 5; if (typeof artistOrLimit === 'string') { // New usage: searchAndGetLinks(songName, artist, limit) artist = artistOrLimit; actualLimit = limit; } else if (typeof artistOrLimit === 'number') { // Old usage: searchAndGetLinks(songName, limit) actualLimit = artistOrLimit; } else if (artistOrLimit === undefined) { // Default usage: searchAndGetLinks(songName) actualLimit = 5; } const spotifyApi = createSpotifyApi(); const data = await spotifyApi.clientCredentialsGrant(); spotifyApi.setAccessToken(data.body['access_token']); // Construct search query with artist if provided let searchQuery = songName; if (artist) { searchQuery = `track:"${songName}" artist:"${artist}"`; } const searchResults = await spotifyApi.searchTracks(searchQuery); if (!searchResults.body.tracks || searchResults.body.tracks.items.length === 0) { return { success: false, error: 'No songs found', results: [] }; } const tracks = searchResults.body.tracks.items.slice(0, actualLimit); const results = await Promise.all(tracks.map(async (track) => { const spotifyUrl = track.external_urls.spotify; const previewUrls = await getSpotifyLinks(spotifyUrl); return { name: `${track.name} - ${track.artists.map((artist) => artist.name).join(', ')}`, spotifyUrl, previewUrls, trackId: track.id, albumName: track.album.name, releaseDate: track.album.release_date, popularity: track.popularity, durationMs: track.duration_ms }; })); return { success: true, searchQuery, results }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; return { success: false, error: errorMessage, results: [] }; } } // Default export for backward compatibility export default searchAndGetLinks; //# sourceMappingURL=index.js.map