"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { SpotifyPlugin: () => SpotifyPlugin, default: () => src_default }); module.exports = __toCommonJS(src_exports); // src/API.ts var import_spotify_url_info = __toESM(require("spotify-url-info")); var import_spotify_web_api_node = __toESM(require("spotify-web-api-node")); var import_undici = require("undici"); var import_distube = require("distube"); var import_spotify_uri = require("spotify-uri"); var SUPPORTED_TYPES = ["album", "playlist", "track", "artist"]; var WEB_API = new import_spotify_web_api_node.default(); var INFO = (0, import_spotify_url_info.default)(import_undici.fetch); var firstWarning1 = true; var firstWarning2 = true; var apiError = /* @__PURE__ */ __name((e) => new import_distube.DisTubeError( "SPOTIFY_API_ERROR", `The URL is private or unavailable.${e?.body?.error?.message ? ` Details: ${e.body.error.message}` : ""}${e?.statusCode ? ` Status code: ${e.statusCode}.` : ""}` ), "apiError"); var APITrack = class { static { __name(this, "APITrack"); } type; id; name; artists; thumbnail; duration; constructor(info) { this.type = "track"; this.id = info.id; this.name = info.name; this.artists = info.artists; this.thumbnail = info.album?.images?.[0]?.url; this.duration = info.duration_ms; } }; var mergeAlbumTrack = /* @__PURE__ */ __name((album, track) => { track.album = album; return track; }, "mergeAlbumTrack"); var API = class { static { __name(this, "API"); } _hasCredentials = false; _expirationTime = 0; _tokenAvailable = false; topTracksCountry = "US"; constructor(clientId, clientSecret, topTracksCountry) { if (clientId && clientSecret) { this._hasCredentials = true; WEB_API.setClientId(clientId); WEB_API.setClientSecret(clientSecret); } if (topTracksCountry) { if (!/^[A-Z]{2}$/.test(topTracksCountry)) throw new Error("Invalid region code"); this.topTracksCountry = topTracksCountry; } } isSupportedTypes(type) { return SUPPORTED_TYPES.includes(type); } async refreshToken() { if (Date.now() < this._expirationTime) return; if (this._hasCredentials) { try { const { body } = await WEB_API.clientCredentialsGrant(); WEB_API.setAccessToken(body.access_token); this._expirationTime = Date.now() + body.expires_in * 1e3 - 5e3; } catch (e) { if (firstWarning1) { firstWarning1 = false; this._hasCredentials = false; console.warn(e); console.warn("[SPOTIFY_PLUGIN_API] Cannot get token from your credentials. Try scraping token instead."); } } } if (!this._hasCredentials) { const response = await (0, import_undici.fetch)("https://open.spotify.com/"); const body = await response.text(); const token = body.match(/"accessToken":"(.+?)"/)?.[1]; if (!token) { this._tokenAvailable = false; if (firstWarning2) { firstWarning2 = false; console.warn( "[SPOTIFY_PLUGIN_API] Cannot get token from scraping. Cannot fetch more than 100 tracks from a playlist or album." ); } return; } WEB_API.setAccessToken(token); const expiration = body.match(/"accessTokenExpirationTimestampMs":(\d+)/)?.[1]; if (expiration) this._expirationTime = Number(expiration) - 5e3; } this._tokenAvailable = true; } parseUrl(url) { return (0, import_spotify_uri.parse)(url); } async getData(url) { const { type, id } = this.parseUrl(url); if (!id) throw new import_distube.DisTubeError("SPOTIFY_API_INVALID_URL", "Invalid URL"); if (!this.isSupportedTypes(type)) throw new import_distube.DisTubeError("SPOTIFY_API_UNSUPPORTED_TYPE", "Unsupported URL type"); await this.refreshToken(); if (type === "track") { if (!this._tokenAvailable) { return INFO.getData(url); } try { const { body } = await WEB_API.getTrack(id); return new APITrack(body); } catch (e) { throw apiError(e); } } if (!this._tokenAvailable) { const data = await INFO.getData(url); const thumbnail = data.coverArt?.sources?.[0]?.url; return { type, name: data.title, thumbnail, url, tracks: data.trackList.map((i) => ({ type: "track", id: this.parseUrl(i.uri).id, name: i.title, artists: [{ name: i.subtitle }], duration: i.duration, thumbnail })) }; } try { const { body } = await WEB_API[type === "album" ? "getAlbum" : type === "playlist" ? "getPlaylist" : "getArtist"](id); return { type, name: body.name, thumbnail: body.images?.[0]?.url, url: body.external_urls?.spotify, tracks: (await this.#getTracks(body)).filter((t) => t?.type === "track").map((t) => new APITrack(t)) }; } catch (e) { throw apiError(e); } } async #getTracks(data) { switch (data.type) { case "artist": { return (await WEB_API.getArtistTopTracks(data.id, this.topTracksCountry)).body.tracks; } case "album": { const tracks = await this.#getPaginatedItems(data); return tracks.map((t) => mergeAlbumTrack(data, t)); } case "playlist": { return (await this.#getPaginatedItems(data)).map((i) => i.track).filter(import_distube.isTruthy); } } } async #getPaginatedItems(data) { const items = data.tracks.items; const isPlaylist = data.type === "playlist"; const limit = isPlaylist ? 100 : 50; const method = isPlaylist ? "getPlaylistTracks" : "getAlbumTracks"; while (data.tracks.next) { await this.refreshToken(); data.tracks = (await WEB_API[method](data.id, { offset: data.tracks.offset + data.tracks.limit, limit })).body; items.push(...data.tracks.items); } return items; } }; // src/index.ts var import_distube2 = require("distube"); var SpotifyPlugin = class extends import_distube2.InfoExtractorPlugin { static { __name(this, "SpotifyPlugin"); } api; constructor(options = {}) { super(); if (typeof options !== "object" || Array.isArray(options)) { throw new import_distube2.DisTubeError("INVALID_TYPE", ["object", "undefined"], options, "SpotifyPluginOptions"); } (0, import_distube2.checkInvalidKey)(options, ["api"], "SpotifyPluginOptions"); if (options.api !== void 0 && (typeof options.api !== "object" || Array.isArray(options.api))) { throw new import_distube2.DisTubeError("INVALID_TYPE", ["object", "undefined"], options.api, "api"); } else if (options.api) { if (options.api.clientId && typeof options.api.clientId !== "string") { throw new import_distube2.DisTubeError("INVALID_TYPE", "string", options.api.clientId, "SpotifyPluginOptions.api.clientId"); } if (options.api.clientSecret && typeof options.api.clientSecret !== "string") { throw new import_distube2.DisTubeError( "INVALID_TYPE", "string", options.api.clientSecret, "SpotifyPluginOptions.api.clientSecret" ); } if (options.api.topTracksCountry && typeof options.api.topTracksCountry !== "string") { throw new import_distube2.DisTubeError( "INVALID_TYPE", "string", options.api.topTracksCountry, "SpotifyPluginOptions.api.topTracksCountry" ); } } this.api = new API(options.api?.clientId, options.api?.clientSecret, options.api?.topTracksCountry); } validate(url) { if (typeof url !== "string" || !url.includes("spotify")) return false; try { const parsedURL = this.api.parseUrl(url); if (!parsedURL.type || !this.api.isSupportedTypes(parsedURL.type)) return false; return true; } catch (error) { return false; } } async resolve(url, options) { const data = await this.api.getData(url); if (data.type === "track") { return new import_distube2.Song( { plugin: this, source: "spotify", playFromSource: false, name: data.name, id: data.id, url: `https://open.spotify.com/track/${data.id}`, thumbnail: data.thumbnail, uploader: { name: data.artists.map((a) => a.name).join(", ") }, duration: data.duration / 1e3 }, options ); } return new import_distube2.Playlist( { source: "spotify", name: data.name, url: data.url, thumbnail: data.thumbnail, songs: data.tracks.map( (t) => new import_distube2.Song( { plugin: this, source: "spotify", id: t.id, playFromSource: false, name: t.name, thumbnail: t.thumbnail, uploader: { name: t.artists.map((a) => a.name).join(", ") }, url: `https://open.spotify.com/track/${t.id}`, duration: t.duration / 1e3 }, options ) ) }, options ); } createSearchQuery(song) { return `${song.name} ${song.uploader.name}`; } getRelatedSongs() { return []; } }; var src_default = SpotifyPlugin; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { SpotifyPlugin }); //# sourceMappingURL=index.js.map