UNPKG

erelasfy

Version:

A plugin for erela.js to play music from spotify

268 lines (267 loc) 13 kB
"use strict"; 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;