ym-api
Version:
A Node.js wrapper for the Yandex.Music API (Unofficial) http://music.yandex.ru
384 lines (383 loc) • 13 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const PreparedRequest_1 = require("./PreparedRequest");
const config_1 = __importDefault(require("./config"));
const HttpClient_1 = __importDefault(require("./HttpClient"));
const xml2js_1 = require("xml2js");
const crypto_1 = __importDefault(require("crypto"));
class YMApi {
constructor(httpClient = new HttpClient_1.default(), config = config_1.default) {
this.httpClient = httpClient;
this.config = config;
this.user = {
password: "",
token: "",
uid: 0,
username: "",
};
}
getAuthHeader() {
return {
Authorization: `OAuth ${this.user.token}`,
};
}
/**
* Authentication
*/
async init(config) {
// Skip auth if access_token and uid are present
if (config.access_token && config.uid) {
this.user.token = config.access_token;
this.user.uid = config.uid;
return {
access_token: config.access_token,
uid: config.uid,
};
}
if (!config.username || !config.password) {
throw new Error("username && password || access_token && uid must be set");
}
this.user.username = config.username;
this.user.password = config.password;
const data = (await this.httpClient.post(PreparedRequest_1.authRequest().setPath("/1/token").setBodyData({
grant_type: "password",
username: this.user.username,
password: this.user.password,
client_id: this.config.oauth.CLIENT_ID,
client_secret: this.config.oauth.CLIENT_SECRET,
})));
this.user.token = data.access_token;
this.user.uid = data.uid;
return data;
}
/**
* GET: /account/status
* Get account status for curren user
*/
getAccountStatus() {
const request = PreparedRequest_1.apiRequest()
.setPath("/account/status")
.addHeaders(this.getAuthHeader());
return this.httpClient.get(request);
}
/**
* GET: /feed
* Get the user's feed
*/
getFeed() {
const request = PreparedRequest_1.apiRequest()
.setPath("/feed")
.addHeaders(this.getAuthHeader());
return this.httpClient.get(request);
}
/**
* GET: /genres
* Get a list of music genres
*/
getGenres() {
const request = PreparedRequest_1.apiRequest()
.setPath("/genres")
.addHeaders(this.getAuthHeader());
return this.httpClient.get(request);
}
/**
* GET: /search
* Search artists, tracks, albums.
*/
search(query, options = {}) {
const type = !options.type ? "all" : options.type;
const page = String(!options.page ? 0 : options.page);
const nococrrect = String(options.nococrrect == null ? false : options.nococrrect);
const request = PreparedRequest_1.apiRequest()
.setPath("/search")
.addHeaders(this.getAuthHeader())
.setQuery({
type,
text: query,
page,
nococrrect,
});
if (options.pageSize !== void 0) {
request.addQuery({ pageSize: String(options.pageSize) });
}
return this.httpClient.get(request);
}
searchArtists(query, options = {}) {
return this.search(query, {
...options,
type: "artist",
});
}
searchTracks(query, options = {}) {
return this.search(query, {
...options,
type: "track",
});
}
searchAlbums(query, options = {}) {
return this.search(query, {
...options,
type: "album",
});
}
searchAll(query, options = {}) {
return this.search(query, {
...options,
type: "all",
});
}
/**
* GET: /users/[user_id]/playlists/list
* Get a user's playlists.
*/
getUserPlaylists(user = null) {
const uid = [null, 0, ""].includes(user) ? this.user.uid : user;
const request = PreparedRequest_1.apiRequest()
.setPath(`/users/${uid}/playlists/list`)
.addHeaders(this.getAuthHeader());
return this.httpClient.get(request);
}
/**
* GET: /users/[user_id]/playlists/[playlist_kind]
* Get a playlist without tracks
*/
getPlaylist(playlistId, user = null) {
const uid = [null, 0, ""].includes(user) ? this.user.uid : user;
const request = PreparedRequest_1.apiRequest()
.setPath(`/users/${uid}/playlists/${playlistId}`)
.addHeaders(this.getAuthHeader());
return this.httpClient.get(request);
}
/**
* GET: /users/[user_id]/playlists
* Get an array of playlists with tracks
*/
getPlaylists(playlists, user = null, options = {}) {
const uid = [null, 0, ""].includes(user) ? this.user.uid : user;
const kinds = playlists.join();
const mixed = String(options.mixed == null ? false : options.mixed);
const richTracks = String(options["rich-tracks"] == null ? false : options["rich-tracks"]);
const request = PreparedRequest_1.apiRequest()
.setPath(`/users/${uid}/playlists`)
.addHeaders(this.getAuthHeader())
.setQuery({
kinds,
mixed,
"rich-tracks": richTracks,
});
return this.httpClient.get(request);
}
/**
* POST: /users/[user_id]/playlists/create
* Create a new playlist
*/
createPlaylist(name, options = {}) {
const visibility = !options.visibility ? "private" : options.visibility;
const request = PreparedRequest_1.apiRequest()
.setPath(`/users/${this.user.uid}/playlists/create`)
.addHeaders(this.getAuthHeader())
.setBodyData({
title: name,
visibility,
});
return this.httpClient.post(request);
}
/**
* POST: /users/[user_id]/playlists/[playlist_kind]/delete
* Remove a playlist
*/
removePlaylist(playlistId) {
const request = PreparedRequest_1.apiRequest()
.setPath(`/users/${this.user.uid}/playlists/${playlistId}/delete`)
.addHeaders(this.getAuthHeader());
return this.httpClient.post(request);
}
/**
* POST: /users/[user_id]/playlists/[playlist_kind]/name
* Change playlist name
*/
renamePlaylist(playlistId, name) {
const request = PreparedRequest_1.apiRequest()
.setPath(`/users/${this.user.uid}/playlists/${playlistId}/name`)
.addHeaders(this.getAuthHeader())
.setBodyData({
value: name,
});
return this.httpClient.post(request);
}
/**
* POST: /users/[user_id]/playlists/[playlist_kind]/change-relative
* Add tracks to the playlist
*/
addTracksToPlaylist(playlistId, tracks, revision, options = {}) {
const at = !options.at ? 0 : options.at;
const request = PreparedRequest_1.apiRequest()
.setPath(`/users/${this.user.uid}/playlists/${playlistId}/change-relative`)
.addHeaders(this.getAuthHeader())
.setBodyData({
diff: JSON.stringify([
{
op: "insert",
at,
tracks: tracks,
},
]),
revision: String(revision),
});
return this.httpClient.post(request);
}
/**
* POST: /users/[user_id]/playlists/[playlist_kind]/change-relative
* Remove tracks from the playlist
*/
removeTracksFromPlaylist(playlistId, tracks, revision, options = {}) {
const from = !options.from ? 0 : options.from;
const to = !options.to ? tracks.length : options.to;
const request = PreparedRequest_1.apiRequest()
.setPath(`/users/${this.user.uid}/playlists/${playlistId}/change-relative`)
.addHeaders(this.getAuthHeader())
.setBodyData({
diff: JSON.stringify([
{
op: "delete",
from,
to,
tracks,
},
]),
revision: String(revision),
});
return this.httpClient.post(request);
}
/**
* GET: /tracks/[track_id]
* Get an array of playlists with tracks
*/
getTrack(trackId) {
const request = PreparedRequest_1.apiRequest()
.setPath(`/tracks/${trackId}`)
.addHeaders(this.getAuthHeader());
return this.httpClient.get(request);
}
/**
* GET: /tracks/[track_id]
* Get single track
*/
async getSingleTrack(trackId) {
const tracks = await this.getTrack(trackId);
if (tracks.length !== 1) {
throw new Error(`More than one result received`);
}
return tracks.pop();
}
/**
* GET: /tracks/[track_id]/supplement
* Get an array of playlists with tracks
*/
getTrackSupplement(trackId) {
const request = PreparedRequest_1.apiRequest()
.setPath(`/tracks/${trackId}/supplement`)
.addHeaders(this.getAuthHeader());
return this.httpClient.get(request);
}
/**
* GET: /tracks/[track_id]/download-info
* Get track download information
*/
getTrackDownloadInfo(trackId) {
const request = PreparedRequest_1.apiRequest()
.setPath(`/tracks/${trackId}/download-info`)
.addHeaders(this.getAuthHeader());
return this.httpClient.get(request);
}
/**
* Get track direct link
*/
async getTrackDirectLink(trackDownloadUrl) {
const request = PreparedRequest_1.directLinkRequest(trackDownloadUrl);
const xml = await this.httpClient.get(request);
const parsedXml = await xml2js_1.parseStringPromise(xml);
const host = parsedXml["download-info"].host[0];
const path = parsedXml["download-info"].path[0];
const ts = parsedXml["download-info"].ts[0];
const s = parsedXml["download-info"].s[0];
const sign = crypto_1.default
.createHash("md5")
.update("XGRlBW9FXlekgbPrRHuSiA" + path.slice(1) + s)
.digest("hex");
return `https://${host}/get-mp3/${sign}/${ts}${path}`;
}
/**
* GET: /albums/[album_id]
* Get an album
*/
getAlbum(albumId, withTracks = false) {
const request = PreparedRequest_1.apiRequest()
.setPath(`/albums/${albumId}${withTracks ? "/with-tracks" : ""}`)
.addHeaders(this.getAuthHeader());
return this.httpClient.get(request);
}
getAlbumWithTracks(albumId) {
return this.getAlbum(albumId, true);
}
/**
* GET: /albums
* Get an albums
*/
getAlbums(albumIds) {
const request = PreparedRequest_1.apiRequest()
.setPath(`/albums`)
.setBodyData({ albumIds: albumIds.join() })
.addHeaders(this.getAuthHeader());
return this.httpClient.post(request);
}
/**
* GET: /artists/[artist_id]
* Get an artist
*/
getArtist(artistId) {
const request = PreparedRequest_1.apiRequest()
.setPath(`/artists/${artistId}`)
.addHeaders(this.getAuthHeader());
return this.httpClient.get(request);
}
/**
* GET: /artists
* Get an artists
*/
getArtists(artistIds) {
const request = PreparedRequest_1.apiRequest()
.setPath(`/artists`)
.setBodyData({ artistIds: artistIds.join() })
.addHeaders(this.getAuthHeader());
return this.httpClient.post(request);
}
/**
* GET: /artists/[artist_id]/tracks
* Get tracks by artist id
*/
getArtistTracks(artistId, options = {}) {
const page = String(!options.page ? 0 : options.page);
const request = PreparedRequest_1.apiRequest()
.setPath(`/artists/${artistId}/tracks`)
.addHeaders(this.getAuthHeader())
.setQuery({
page,
});
if (options.pageSize !== void 0) {
request.addQuery({ pageSize: String(options.pageSize) });
}
return this.httpClient.get(request);
}
getLikedTracks(user = null) {
const uid = [null, 0, ""].includes(user) ? this.user.uid : user;
const request = PreparedRequest_1.apiRequest()
.setPath(`/users/${uid}/likes/tracks`)
.addHeaders(this.getAuthHeader());
return this.httpClient.get(request);
}
}
exports.default = YMApi;