erelasfy
Version:
A plugin for erela.js to play music from spotify
268 lines (267 loc) • 13 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getSpotifyToken = exports.Erelasfy = void 0;
const constants_1 = require("./constants");
const erelajs_1 = require("@drgatoxd/erelajs");
const validate_1 = require("./util/validate");
const axios_1 = __importDefault(require("axios"));
const buildSearch_1 = require("./util/buildSearch");
class Erelasfy extends erelajs_1.Plugin {
constructor(options) {
super();
this.BASE_URL = 'https://api.spotify.com/v1';
this.regexp = /(?:https:\/\/open\.spotify\.com\/|spotify:)(?:.+)?(track|playlist|album|artist)[\/:]([A-Za-z0-9]+)/;
this.token = '';
(0, validate_1.validateOptions)(options);
this.options = Object.assign(Object.assign({}, constants_1.DefaultOptions), options);
this.authorization = `Basic ${Buffer.from(`${options.clientId}:${options.clientSecret}`).toString('base64')}`;
void this.renew();
}
load(manager) {
this.manager = manager;
this._search = manager.search.bind(manager);
manager.search = this.search.bind(this);
}
getMethod(url) {
var _a;
const [, type] = (_a = url.match(this.regexp)) !== null && _a !== void 0 ? _a : [];
return type == 'track'
? this.getTrack
: type == 'album'
? this.getAlbumTracks
: type == 'playlist'
? this.getPlaylistTracks
: type == 'artist'
? this.getArtistTopTracks
: undefined;
}
search(query, requester) {
var _a, _b, _c, _d;
return __awaiter(this, void 0, void 0, function* () {
const finalQuery = typeof query == 'string' ? query : query.query;
const [, type, id] = (_a = finalQuery.match(this.regexp)) !== null && _a !== void 0 ? _a : [];
const func = (_b = this.getMethod(finalQuery)) === null || _b === void 0 ? void 0 : _b.bind(this);
if (id != undefined && func && type) {
try {
const data = yield func(id);
const loadType = type === 'track' ? 'TRACK_LOADED' : 'PLAYLIST_LOADED';
const name = ['playlist', 'album', 'artist'].includes(type) ? data.name || null : null;
const tracks = (yield Promise.all(data.tracks.map((query) => __awaiter(this, void 0, void 0, function* () {
const track = query.resolve();
return track;
}))))
.filter(track => !!track)
.map(t => erelajs_1.TrackUtils.build(t, requester));
return (0, buildSearch_1.buildSearch)(loadType, tracks, null, name);
}
catch (err) {
console.log(err);
const e = err;
return (0, buildSearch_1.buildSearch)((_c = e.loadType) !== null && _c !== void 0 ? _c : 'LOAD_FAILED', null, (_d = e.message) !== null && _d !== void 0 ? _d : null, null);
}
}
else {
return this._search(query, requester);
}
});
}
getTrack(id) {
return __awaiter(this, void 0, void 0, function* () {
const data = yield this.makeRequest(`/tracks/${id}`);
return { tracks: [this.buildUnresolved(data)] };
});
}
getAlbumTracks(id) {
return __awaiter(this, void 0, void 0, function* () {
const album = yield this.makeRequest(`/albums/${id}`);
const tracks = yield Promise.all(album.tracks.items.filter(Erelasfy.filterNullOrUndefined).map(item => this.getTrack(item.id)));
let next = album.tracks.next;
let page = 1;
while (next != null &&
(!this.options.albumLimit ? true : page < (this.options.albumLimit || 50))) {
const nextPage = yield this.makeRequest(next);
const nextTracks = yield Promise.all(nextPage.items.filter(Erelasfy.filterNullOrUndefined).map(item => this.getTrack(item.id)));
tracks.push(...nextTracks);
next = nextPage.next;
page++;
}
return { tracks: tracks.map(x => x.tracks[0]), name: album.name };
});
}
getPlaylistTracks(id) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
const playlist = yield this.makeRequest(`/playlists/${id}`);
const tracks = playlist.tracks.items
.filter(Erelasfy.filterNullOrUndefined)
.map(x => this.buildUnresolved(x.track));
let next = playlist.tracks.next;
let page = 1;
while (next != null &&
(!this.options.albumLimit ? true : page < (this.options.albumLimit || 50))) {
const nextPage = yield this.makeRequest(next);
const nextTracks = nextPage.items
.filter(Erelasfy.filterNullOrUndefined)
.map(x => this.buildUnresolved(x.track));
tracks.push(...nextTracks);
next = nextPage.next;
page++;
}
return {
tracks,
name: playlist.name,
thumbnail: (_a = playlist.images.sort((a, b) => b.width - a.width)[0]) === null || _a === void 0 ? void 0 : _a.url
};
});
}
getArtistTopTracks(id) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
const artist = yield this.makeRequest(`/artists/${id}`);
const playlist = yield this.makeRequest(`/artists/${id}/top-tracks?market=ES`);
const tracks = playlist.tracks
.filter(Erelasfy.filterNullOrUndefined)
.map(x => this.buildUnresolved(x));
return {
tracks,
name: `${artist.name}: Popular`,
thumbnail: (_a = artist.images.sort((a, b) => b.width - a.width)[0]) === null || _a === void 0 ? void 0 : _a.url
};
});
}
makeRequest(endpoint) {
return __awaiter(this, void 0, void 0, function* () {
const res = yield (0, axios_1.default)({
method: 'GET',
url: endpoint.startsWith('https://')
? endpoint
: `${this.BASE_URL}${endpoint.startsWith('/') ? endpoint : `/${endpoint}`}`,
headers: {
Authorization: this.token
}
});
return res.data;
});
}
renewToken() {
return __awaiter(this, void 0, void 0, function* () {
const { data: { access_token, expires_in } } = yield (0, axios_1.default)({
method: 'POST',
url: 'https://accounts.spotify.com/api/token',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: this.authorization
},
params: {
grant_type: 'client_credentials'
}
}).catch(() => ({ data: { access_token: null, expires_in: null } }));
if (!access_token || !expires_in) {
throw new Error('Invalid Spotify client.');
}
this.token = `Bearer ${access_token}`;
return expires_in * 1000;
});
}
renew() {
return __awaiter(this, void 0, void 0, function* () {
const expiresIn = yield this.renewToken();
setTimeout(() => this.renew(), expiresIn);
});
}
buildUnresolved(track) {
var _a, _b, _c, _d;
const _this = this;
return {
info: {
identifier: track.id,
title: track.name,
author: ((_a = track.artists[0]) === null || _a === void 0 ? void 0 : _a.name) || 'Unknown',
uri: track.external_urls.spotify,
length: track.duration_ms,
thumbnail: ((_c = (_b = track.album) === null || _b === void 0 ? void 0 : _b.images.sort((x, y) => y.width - x.width)[0]) === null || _c === void 0 ? void 0 : _c.url) ||
'https://m.media-amazon.com/images/I/61T60YWIp3L._SS500_.jpg',
isrc: (_d = track.external_ids) === null || _d === void 0 ? void 0 : _d.isrc
},
resolve() {
return _this.resolve(this);
}
};
}
resolve(track) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
if (!this.manager)
throw new Error('No manager found.');
const params = new URLSearchParams({
identifier: `ytsearch:${track.info.isrc
? `\\"${track.info.isrc}"\\`
: `${track.info.title} - ${track.info.author} audio`}`
});
const options = (_a = this.manager.nodes.find(x => x.connected)) === null || _a === void 0 ? void 0 : _a.options;
if (!options)
throw new Error('No available nodes.');
const { data } = yield axios_1.default.get(`http${options.secure ? 's' : ''}://${options.host}:${options.port}/loadtracks?${params.toString()}`, {
headers: {
Authorization: options.password || 'youshallnotpass'
}
});
let lavatrack = data.tracks[0];
if (!data.tracks.length && !this.options.isrcOnly) {
const ns = new URLSearchParams({
identifier: `ytsearch:${track.info.title} ${track.info.author} audio`
});
const { data } = yield axios_1.default.get(`http${options.secure ? 's' : ''}://${options.host}:${options.port}/loadtracks?${ns.toString()}`, {
headers: {
Authorization: options.password || 'youshallnotpass'
}
});
lavatrack = data.tracks[0];
}
if (lavatrack) {
lavatrack.info = Object.assign(Object.assign({}, lavatrack.info), { title: track.info.title || lavatrack.info.title, author: track.info.author || lavatrack.info.author, uri: track.info.uri || lavatrack.info.uri, thumbnail: track.info.thumbnail ||
lavatrack.info.thumbnail ||
'https://upload.wikimedia.org/wikipedia/commons/3/3c/No-album-art.png', length: lavatrack.info.length || track.info.length });
}
else
return undefined;
return Object.freeze(lavatrack);
});
}
static filterNullOrUndefined(value) {
return typeof value !== 'undefined' ? value !== null : typeof value !== 'undefined';
}
}
exports.Erelasfy = Erelasfy;
function getSpotifyToken(clientId, clientSecret) {
return __awaiter(this, void 0, void 0, function* () {
const { data: { access_token, expires_in } } = yield (0, axios_1.default)({
method: 'POST',
url: 'https://accounts.spotify.com/api/token',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString('base64')}`
},
params: {
grant_type: 'client_credentials'
}
}).catch(() => ({ data: { access_token: null, expires_in: null } }));
if (!access_token || !expires_in) {
throw new Error('Invalid Spotify client.');
}
return { token: access_token, expires: expires_in };
});
}
exports.getSpotifyToken = getSpotifyToken;