const qs = require('querystring');
const Track = require('../structures/Track.js');
const Audio = require('../managers/Audio.js');
const API = 'https://api.spotify.com/v1/me/tracks';
const HTTPError = require('../HTTPError.js');
const ApiError = require('../ApiError.js');
class TrackManager {
/**
* Manages spotify tracks.
* @param {Spotify} spotify - The spotify client.
*/
constructor(spotify) {
/**
* The spotify client.
* @type {Spotify}
*/
this.spotify = spotify;
/**
* The audio features.
* @type {Audio}
*/
this.audio = new Audio(spotify);
}
/**
* Get Spotify catalog information for a single track identified by its unique Spotify ID.
* @param {string} id - The Spotify ID for the track.
* @returns {Promise<Track|HTTPError|ApiError>}
*/
get(id) {
const path = 'https://api.spotify.com/v1/tracks/' + id;
return new Promise((resolve, reject) => {
this.spotify.util
.fetch({
path,
})
.then((response) => {
this.spotify.util.toJson(response).then((body) => {
if (body) {
if (response.status == 200) {
const track = new Track(this.spotify, body);
return resolve(track);
}
reject(new ApiError(body.error));
}
reject(new HTTPError(response));
});
});
});
}
/**
* Get a list of the songs saved in the current Spotify user's 'Your Music' library.
* @param {LimitOptions} options
* @returns {Promise<Track[]|HTTPError|ApiError>}
*/
saved({ limit = 20, offset = 0 } = {}) {
const options = qs.stringify({
limit,
offset,
});
const path = API + '?' + options;
return new Promise((resolve, reject) => {
this.spotify.util
.fetch({
path,
})
.then((response) => {
this.spotify.util.toJson(response).then((body) => {
if (body) {
if (response.status == 200) {
const tracks = body.items.map(
(t) => new Track(this.spotify, t)
);
return resolve(tracks);
}
reject(new ApiError(body.error));
}
reject(new HTTPError(response));
});
});
});
}
/**
* Save one or more tracks to the current user's 'Your Music' library.
* @param {string|string[]} ids - A list of the Spotify IDs.
* @returns {Promise<Status|HTTPError|ApiError>}
*/
save(ids) {
const options = qs.stringify({
ids: Array.isArray(ids) ? ids.join(',') : ids,
});
const path = API + '?' + options;
return new Promise((resolve, reject) => {
this.spotify.util
.fetch({
path,
method: 'put',
})
.then((response) => {
this.spotify.util.toJson(response).then((body) => {
if (response.status == 200) {
resolve({ status: response.status });
} else if (body) {
reject(new ApiError(body.error));
}
reject(new HTTPError(response));
});
});
});
}
/**
* Remove one or more tracks from the current user's 'Your Music' library.
* @param {string|string[]} ids - A list of the Spotify IDs.
* @returns {Promise<Status|HTTPError|ApiError>}
*/
remove(ids) {
const options = qs.stringify({
ids: Array.isArray(ids) ? ids.join(',') : ids,
});
const path = API + '?' + options;
return new Promise((resolve, reject) => {
this.spotify.util
.fetch({
path,
method: 'delete',
})
.then((response) => {
this.spotify.util.toJson(response).then((body) => {
if (response.status == 200) {
resolve({ status: response.status });
} else if (body) {
reject(new ApiError(body.error));
}
reject(new HTTPError(response));
});
});
});
}
/**
* Check if one or more tracks is already saved in the current Spotify user's 'Your Music' library.
* @param {string|string[]} ids
* @returns {Promise<boolean[]|HTTPError|ApiError>}
*/
starred(ids) {
const options = qs.stringify({
ids: Array.isArray(ids) ? ids.join(',') : ids,
});
const path = API + '/contains?' + options;
return new Promise((resolve, reject) => {
this.spotify.util
.fetch({
path,
})
.then((response) => {
this.spotify.util.toJson(response).then((body) => {
if (body) {
if (response.status == 200) {
return resolve(body);
}
reject(new ApiError(response));
}
reject(new HTTPError(response));
});
});
});
}
/**
* Get Spotify catalog information about tracks.
* @param {string} query - Your search query.
* @param {SearchOptions} options
* @returns {Promise<Track[]|HTTPError|ApiError>}
*/
search(query, { external = false, limit = 20, offset = 0 } = {}) {
const opts = {
q: query,
type: 'track',
limit,
offset,
};
if (external) {
opts['include_external'] = 'audio';
}
const options = qs.stringify(opts);
const path = 'https://api.spotify.com/v1/search?' + options;
return new Promise((resolve, reject) => {
this.spotify.util
.fetch({
path,
})
.then((response) => {
this.spotify.util.toJson(response).then((body) => {
if (body) {
if (response.status == 200) {
const tracks = body.tracks.items.map(
(p) => new Track(this.spotify, p)
);
return resolve(tracks);
}
reject(new ApiError(body.error));
}
reject(new HTTPError(response));
});
});
});
}
/**
* Recommendations are generated based on the available information for a given seed entity and matched against similar artists and tracks.
* @param {RecommendedOptions} options
* @returns {Promise<Tracks[]|HTTPError|ApiError>}
*/
recommendations({
seeds = {},
max = {},
min = {},
target = {},
limit = 20,
} = {}) {
const attributes = [
'acousticness',
'danceability',
'duration_ms',
'energy',
'instrumentalness',
'key',
'liveness',
'loudness',
'mode',
'popularity',
'speachiness',
'temp',
'time_signature',
'valence',
];
const { artists = [], genres = [], tracks = [] } = seeds;
const opts = {
seed_artists: artists,
seed_genres: genres,
seed_tracks: tracks,
limit,
};
const structures = [max, min, target];
structures.forEach((struct, index) => {
attributes.forEach((attribute) => {
const types = ['max', 'min', 'target'];
const key = types[index] + '_' + attribute;
if (struct[attribute]) {
opts[key] = struct[attribute];
}
});
});
const options = qs.stringify(opts);
const path = 'https://api.spotify.com/v1/recommendations?' + options;
return new Promise((resolve, reject) => {
this.spotify.util
.fetch({
path,
})
.then((response) => {
this.spotify.util.toJson(response).then((body) => {
if (body) {
if (response.status == 200) {
const tracks = body.tracks.map(
(t) => new Track(this.spotify, t)
);
return resolve(tracks);
}
reject(new ApiError(body.error));
}
reject(new HTTPError(response));
});
});
});
}
}
module.exports = TrackManager;
/**
* @typedef {Object} RecommendedOptions
* @property {SeedOptions} seeds - Recommendations based on the seed options.
* @property {Attributes} [max] - The maximum values of for the attributes.
* @property {Attributes} [min] - The minimum values of for the attributes.
* @property {Attributes} [target] - The target values of for the attributes.
* @property {number} [limit=20] - The maximum number of items to return. Minimum: 1. Maximum: 50.
*/
/**
* @typedef {Object} SeedOptions
* @property {string[]} artists - A list of Spotify IDs for seed artists.
* @property {string[]} genres - A list of any genres in the set of available genre seeds.
* @property {string[]} tracks - A list of Spotify IDs for a seed track.
*/
/**
* https://developer.spotify.com/documentation/web-api/reference/#/operations/get-recommendations
* @typedef {Object} Attributes
* @property {number} acousticness - \>= 0 <= 1
* @property {number} danceability - \>= 0 <= 1
* @property {number} duration_ms
* @property {number} energy - \>= 0 <= 1
* @property {number} instrumentalness - \>= 0 <= 1
* @property {number} key - \>= 0 <= 11
* @property {number} liveness - \>= 0 <= 1
* @property {number} loudness
* @property {number} mode - \>= 0 <= 1
* @property {number} popularity - \>= 0 <= 100
* @property {number} speachiness - \>= 0 <= 1
* @property {number} temp
* @property {number} time_signature
* @property {number} valence
*/