var __defProp = Object.defineProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });

// src/API.ts
import SpotifyInfo from "spotify-url-info";
import SpotifyWebApi from "spotify-web-api-node";
import { fetch } from "undici";
import { DisTubeError, isTruthy } from "distube";
import { parse as parseSpotifyUri } from "spotify-uri";
var SUPPORTED_TYPES = ["album", "playlist", "track", "artist"];
var WEB_API = new SpotifyWebApi();
var INFO = SpotifyInfo(fetch);
var firstWarning1 = true;
var firstWarning2 = true;
var apiError = /* @__PURE__ */ __name((e) => new 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 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 parseSpotifyUri(url);
  }
  async getData(url) {
    const { type, id } = this.parseUrl(url);
    if (!id) throw new DisTubeError("SPOTIFY_API_INVALID_URL", "Invalid URL");
    if (!this.isSupportedTypes(type)) throw new 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(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
import { DisTubeError as DisTubeError2, InfoExtractorPlugin, Playlist, Song, checkInvalidKey } from "distube";
var SpotifyPlugin = class extends InfoExtractorPlugin {
  static {
    __name(this, "SpotifyPlugin");
  }
  api;
  constructor(options = {}) {
    super();
    if (typeof options !== "object" || Array.isArray(options)) {
      throw new DisTubeError2("INVALID_TYPE", ["object", "undefined"], options, "SpotifyPluginOptions");
    }
    checkInvalidKey(options, ["api"], "SpotifyPluginOptions");
    if (options.api !== void 0 && (typeof options.api !== "object" || Array.isArray(options.api))) {
      throw new DisTubeError2("INVALID_TYPE", ["object", "undefined"], options.api, "api");
    } else if (options.api) {
      if (options.api.clientId && typeof options.api.clientId !== "string") {
        throw new DisTubeError2("INVALID_TYPE", "string", options.api.clientId, "SpotifyPluginOptions.api.clientId");
      }
      if (options.api.clientSecret && typeof options.api.clientSecret !== "string") {
        throw new DisTubeError2(
          "INVALID_TYPE",
          "string",
          options.api.clientSecret,
          "SpotifyPluginOptions.api.clientSecret"
        );
      }
      if (options.api.topTracksCountry && typeof options.api.topTracksCountry !== "string") {
        throw new DisTubeError2(
          "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 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 Playlist(
      {
        source: "spotify",
        name: data.name,
        url: data.url,
        thumbnail: data.thumbnail,
        songs: data.tracks.map(
          (t) => new 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;
export {
  SpotifyPlugin,
  src_default as default
};
//# sourceMappingURL=index.mjs.map