@ziplayer/plugin
Version:
A modular Discord voice player with plugin system
305 lines • 10.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SpotifyPlugin = void 0;
const ziplayer_1 = require("ziplayer");
/**
* A minimal Spotify plugin for metadata extraction and display purposes.
*
* This plugin provides support for:
* - Spotify track URLs/URIs (spotify:track:...)
* - Spotify playlist URLs/URIs (spotify:playlist:...)
* - Spotify album URLs/URIs (spotify:album:...)
* - Metadata extraction using Spotify's public oEmbed endpoint
*
* **Important Notes:**
* - This plugin does NOT provide audio streams (player is expected to redirect/fallback upstream)
* - This plugin does NOT expand playlists/albums (no SDK; oEmbed doesn't enumerate items)
* - This plugin only provides display metadata for Spotify content
*
* @example
*
* const spotifyPlugin = new SpotifyPlugin();
*
* // Add to PlayerManager
* const manager = new PlayerManager({
* plugins: [spotifyPlugin]
* });
*
* // Get metadata for a Spotify track
* const result = await spotifyPlugin.search("spotify:track:4iV5W9uYEdYUVa79Axb7Rh", "user123");
* console.log(result.tracks[0].metadata); // Contains Spotify metadata
*
*
* @since 1.1.0
*/
class SpotifyPlugin extends ziplayer_1.BasePlugin {
constructor() {
super(...arguments);
this.name = "spotify";
this.version = "1.1.0";
}
/**
* Determines if this plugin can handle the given query.
*
* @param query - The search query or URL to check
* @returns `true` if the query is a Spotify URL/URI, `false` otherwise
*
* @example
*
* plugin.canHandle("spotify:track:4iV5W9uYEdYUVa79Axb7Rh"); // true
* plugin.canHandle("https://open.spotify.com/track/4iV5W9uYEdYUVa79Axb7Rh"); // true
* plugin.canHandle("youtube.com/watch?v=123"); // false
*
*/
canHandle(query) {
const q = query.toLowerCase().trim();
if (q.startsWith("spotify:"))
return true;
try {
const u = new URL(q);
return u.hostname === "open.spotify.com";
}
catch {
return false;
}
}
/**
* Validates if a URL/URI is a valid Spotify URL/URI.
*
* @param url - The URL/URI to validate
* @returns `true` if the URL/URI is a valid Spotify URL/URI, `false` otherwise
*
* @example
*
* plugin.validate("spotify:track:4iV5W9uYEdYUVa79Axb7Rh"); // true
* plugin.validate("https://open.spotify.com/track/4iV5W9uYEdYUVa79Axb7Rh"); // true
* plugin.validate("https://youtube.com/watch?v=123"); // false
*
*/
validate(url) {
if (url.startsWith("spotify:"))
return true;
try {
const u = new URL(url);
return u.hostname === "open.spotify.com";
}
catch {
return false;
}
}
/**
* Extracts metadata from Spotify URLs/URIs using the oEmbed API.
*
* This method handles Spotify track, playlist, and album URLs/URIs by fetching
* display metadata from Spotify's public oEmbed endpoint. It does not provide
* audio streams or expand playlists/albums.
*
* @param query - The Spotify URL/URI to extract metadata from
* @param requestedBy - The user ID who requested the extraction
* @returns A SearchResult containing a single track with metadata (no audio stream)
*
* @example
*
* // Extract track metadata
* const result = await plugin.search("spotify:track:4iV5W9uYEdYUVa79Axb7Rh", "user123");
* console.log(result.tracks[0].metadata); // Contains Spotify metadata
*
* // Extract playlist metadata
* const playlistResult = await plugin.search("https://open.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M", "user123");
* console.log(playlistResult.tracks[0].metadata.kind); // "playlist"
*
*/
async search(query, requestedBy) {
if (!this.validate(query)) {
return { tracks: [] };
}
const kind = this.identifyKind(query);
if (kind === "track") {
const t = await this.buildTrackFromUrlOrUri(query, requestedBy);
return { tracks: t ? [t] : [] };
}
if (kind === "playlist") {
const t = await this.buildHeaderItem(query, requestedBy, "playlist");
return { tracks: t ? [t] : [] };
}
if (kind === "album") {
const t = await this.buildHeaderItem(query, requestedBy, "album");
return { tracks: t ? [t] : [] };
}
return { tracks: [] };
}
/**
* Extracts tracks from a Spotify playlist URL.
*
* **Note:** This method is not implemented as this plugin does not support
* playlist expansion. It always returns an empty array.
*
* @param _input - The Spotify playlist URL (unused)
* @param _requestedBy - The user ID who requested the extraction (unused)
* @returns An empty array (playlist expansion not supported)
*
* @example
*
* const tracks = await plugin.extractPlaylist("spotify:playlist:123", "user123");
* console.log(tracks); // [] - empty array
*
*/
async extractPlaylist(_input, _requestedBy) {
return [];
}
/**
* Extracts tracks from a Spotify album URL.
*
* **Note:** This method is not implemented as this plugin does not support
* album expansion. It always returns an empty array.
*
* @param _input - The Spotify album URL (unused)
* @param _requestedBy - The user ID who requested the extraction (unused)
* @returns An empty array (album expansion not supported)
*
* @example
*
* const tracks = await plugin.extractAlbum("spotify:album:123", "user123");
* console.log(tracks); // [] - empty array
*
*/
async extractAlbum(_input, _requestedBy) {
return [];
}
/**
* Attempts to get an audio stream for a Spotify track.
*
* **Note:** This method always throws an error as this plugin does not support
* audio streaming. The player is expected to redirect to other plugins or
* use fallback mechanisms for actual audio playback.
*
* @param _track - The Track object (unused)
* @throws {Error} Always throws "Spotify streaming is not supported by this plugin"
*
* @example
*
* try {
* const stream = await plugin.getStream(track);
* } catch (error) {
* console.log(error.message); // "Spotify streaming is not supported by this plugin"
* }
*
*/
async getStream(_track) {
throw new Error("Spotify streaming is not supported by this plugin");
}
identifyKind(input) {
if (input.startsWith("spotify:")) {
if (input.includes(":track:"))
return "track";
if (input.includes(":playlist:"))
return "playlist";
if (input.includes(":album:"))
return "album";
return "unknown";
}
try {
const u = new URL(input);
const parts = u.pathname.split("/").filter(Boolean);
const kind = parts[0];
if (kind === "track")
return "track";
if (kind === "playlist")
return "playlist";
if (kind === "album")
return "album";
return "unknown";
}
catch {
return "unknown";
}
}
extractId(input) {
if (!input)
return null;
if (input.startsWith("spotify:")) {
const parts = input.split(":");
return parts[2] || null;
}
try {
const u = new URL(input);
const parts = u.pathname.split("/").filter(Boolean);
return parts[1] || null; // /track/<id>
}
catch {
return null;
}
}
async buildTrackFromUrlOrUri(input, requestedBy) {
const id = this.extractId(input);
if (!id)
return null;
const url = this.toShareUrl(input, "track", id);
const meta = await this.fetchOEmbed(url).catch(() => undefined);
const title = meta?.title || `Spotify Track ${id}`;
const thumbnail = meta?.thumbnail_url;
const track = {
id,
title,
url,
duration: 0,
thumbnail,
requestedBy,
source: this.name,
metadata: {
author: meta?.author_name,
provider: meta?.provider_name,
spotify_id: id,
},
};
return track;
}
async buildHeaderItem(input, requestedBy, kind) {
const id = this.extractId(input);
if (!id)
return null;
const url = this.toShareUrl(input, kind, id);
const meta = await this.fetchOEmbed(url).catch(() => undefined);
const title = meta?.title || `Spotify ${kind} ${id}`;
const thumbnail = meta?.thumbnail_url;
return {
id,
title,
url,
duration: 0,
thumbnail,
requestedBy,
source: this.name,
metadata: {
author: meta?.author_name,
provider: meta?.provider_name,
spotify_id: id,
kind,
},
};
}
toShareUrl(input, expectedKind, id) {
if (input.startsWith("spotify:")) {
return `https://open.spotify.com/${expectedKind}/${id}`;
}
try {
const u = new URL(input);
const parts = u.pathname.split("/").filter(Boolean);
const kind = parts[0] || expectedKind;
const realId = parts[1] || id;
return `https://open.spotify.com/${kind}/${realId}`;
}
catch {
return `https://open.spotify.com/${expectedKind}/${id}`;
}
}
async fetchOEmbed(pageUrl) {
const endpoint = `https://open.spotify.com/oembed?url=${encodeURIComponent(pageUrl)}`;
const res = await fetch(endpoint);
if (!res.ok)
throw new Error(`oEmbed HTTP ${res.status}`);
return res.json();
}
}
exports.SpotifyPlugin = SpotifyPlugin;
//# sourceMappingURL=SpotifyPlugin.js.map