1414 lines
36 KiB
JavaScript
1414 lines
36 KiB
JavaScript
"use strict";
|
|
|
|
var testHttpClient = null;
|
|
var testConfig = null;
|
|
var defaultClient = null;
|
|
var defaultClientSignature = null;
|
|
|
|
var SEARCH_PAGE_SIZE = 20;
|
|
var LIST_PAGE_SIZE = 60;
|
|
var PLUGIN_SRC_PLACEHOLDER = ["__MUSIC", "_SERVER", "_PLUGIN", "_SRC_URL__"].join("");
|
|
var DEFAULT_PLUGIN_SRC_URL = "__MUSIC_SERVER_PLUGIN_SRC_URL__";
|
|
|
|
function hasOwn(obj, key) {
|
|
return Object.prototype.hasOwnProperty.call(obj, key);
|
|
}
|
|
|
|
function toTrimmedString(value) {
|
|
if (value === null || value === undefined) {
|
|
return "";
|
|
}
|
|
return String(value).trim();
|
|
}
|
|
|
|
function normalizeBaseUrl(url) {
|
|
var value = toTrimmedString(url);
|
|
if (!value) {
|
|
return "";
|
|
}
|
|
return value.replace(/\/+$/, "");
|
|
}
|
|
|
|
function isPluginSrcPlaceholder(value) {
|
|
return toTrimmedString(value) === PLUGIN_SRC_PLACEHOLDER;
|
|
}
|
|
|
|
function normalizeAccessToken(value) {
|
|
var token = toTrimmedString(value);
|
|
if (!token) {
|
|
return "";
|
|
}
|
|
return token.replace(/^Bearer\s+/i, "").trim();
|
|
}
|
|
|
|
function createFallbackClientId(accessToken) {
|
|
var token = normalizeAccessToken(accessToken);
|
|
var hash = 0;
|
|
var i = 0;
|
|
var unsigned = 0;
|
|
|
|
if (!token) {
|
|
return "";
|
|
}
|
|
|
|
for (i = 0; i < token.length; i += 1) {
|
|
hash = ((hash << 5) - hash + token.charCodeAt(i)) | 0;
|
|
}
|
|
unsigned = hash >>> 0;
|
|
return "mf-fallback-" + unsigned.toString(16);
|
|
}
|
|
|
|
function getRuntimeEnv() {
|
|
if (typeof env !== "undefined" && env) {
|
|
return env;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function readConfigValue(key) {
|
|
var runtimeEnv = null;
|
|
var variablesFromGetter = null;
|
|
var variables = null;
|
|
var i = 0;
|
|
var item = null;
|
|
|
|
try {
|
|
if (testConfig && hasOwn(testConfig, key)) {
|
|
return testConfig[key];
|
|
}
|
|
|
|
runtimeEnv = getRuntimeEnv();
|
|
if (!runtimeEnv || typeof runtimeEnv !== "object") {
|
|
return undefined;
|
|
}
|
|
|
|
if (typeof runtimeEnv.getUserVariables === "function") {
|
|
try {
|
|
variablesFromGetter = runtimeEnv.getUserVariables();
|
|
if (
|
|
variablesFromGetter &&
|
|
typeof variablesFromGetter === "object" &&
|
|
hasOwn(variablesFromGetter, key)
|
|
) {
|
|
return variablesFromGetter[key];
|
|
}
|
|
if (Array.isArray(variablesFromGetter)) {
|
|
for (i = 0; i < variablesFromGetter.length; i += 1) {
|
|
item = variablesFromGetter[i];
|
|
if (
|
|
item &&
|
|
typeof item === "object" &&
|
|
toTrimmedString(item.key) === key &&
|
|
hasOwn(item, "value")
|
|
) {
|
|
return item.value;
|
|
}
|
|
}
|
|
}
|
|
} catch (_error) {
|
|
variablesFromGetter = null;
|
|
}
|
|
}
|
|
|
|
try {
|
|
variables = runtimeEnv.userVariables;
|
|
} catch (_error2) {
|
|
variables = null;
|
|
}
|
|
if (variables && typeof variables === "object" && hasOwn(variables, key)) {
|
|
return variables[key];
|
|
}
|
|
if (Array.isArray(variables)) {
|
|
for (i = 0; i < variables.length; i += 1) {
|
|
item = variables[i];
|
|
if (
|
|
item &&
|
|
typeof item === "object" &&
|
|
toTrimmedString(item.key) === key &&
|
|
hasOwn(item, "value")
|
|
) {
|
|
return item.value;
|
|
}
|
|
}
|
|
}
|
|
} catch (_error3) {
|
|
return undefined;
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
function readFirstConfigValue(keys) {
|
|
var i = 0;
|
|
var value = undefined;
|
|
|
|
for (i = 0; i < keys.length; i += 1) {
|
|
value = readConfigValue(keys[i]);
|
|
if (value === undefined || value === null) {
|
|
continue;
|
|
}
|
|
if (toTrimmedString(value)) {
|
|
return value;
|
|
}
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
function readPluginSrcUrl() {
|
|
var explicitSrcUrl = readFirstConfigValue(["srcUrl"]);
|
|
if (explicitSrcUrl) {
|
|
return explicitSrcUrl;
|
|
}
|
|
if (
|
|
typeof module !== "undefined" &&
|
|
module &&
|
|
module.exports &&
|
|
typeof module.exports.srcUrl === "string"
|
|
) {
|
|
return module.exports.srcUrl;
|
|
}
|
|
return DEFAULT_PLUGIN_SRC_URL;
|
|
}
|
|
|
|
function normalizeConfiguredBaseUrl(value) {
|
|
var raw = toTrimmedString(value);
|
|
var withScheme = "";
|
|
var parsed = null;
|
|
var pathname = "";
|
|
var fallbackRaw = "";
|
|
|
|
if (!raw || isPluginSrcPlaceholder(raw)) {
|
|
return "";
|
|
}
|
|
|
|
if (/^[a-z][a-z\d+\-.]*:\/\//i.test(raw)) {
|
|
withScheme = raw;
|
|
} else {
|
|
withScheme = "http://" + raw;
|
|
}
|
|
|
|
try {
|
|
parsed = new URL(withScheme);
|
|
parsed.hash = "";
|
|
pathname = parsed.pathname || "";
|
|
if (/\/plugins\/[^/]+\.js$/i.test(pathname)) {
|
|
parsed.pathname = pathname.replace(/\/plugins\/[^/]+\.js$/i, "") || "/";
|
|
parsed.search = "";
|
|
}
|
|
return normalizeBaseUrl(parsed.toString());
|
|
} catch (_error) {
|
|
fallbackRaw = withScheme.replace(/\/plugins\/[^/?#]+\.js(?:[?#].*)?$/i, "");
|
|
return normalizeBaseUrl(fallbackRaw);
|
|
}
|
|
}
|
|
|
|
function resolveBaseUrl() {
|
|
var fromSrcUrl = normalizeConfiguredBaseUrl(readPluginSrcUrl());
|
|
var configured = "";
|
|
|
|
if (fromSrcUrl) {
|
|
return fromSrcUrl;
|
|
}
|
|
|
|
configured = normalizeConfiguredBaseUrl(
|
|
readFirstConfigValue(["baseUrl", "baseURL", "serverUrl", "serverURL"]),
|
|
);
|
|
if (configured) {
|
|
return configured;
|
|
}
|
|
return "";
|
|
}
|
|
|
|
function readRuntimeValue(key) {
|
|
var runtimeEnv = null;
|
|
var runtimeValues = null;
|
|
|
|
try {
|
|
if (testConfig && hasOwn(testConfig, key)) {
|
|
return testConfig[key];
|
|
}
|
|
|
|
runtimeEnv = getRuntimeEnv();
|
|
if (!runtimeEnv) {
|
|
return undefined;
|
|
}
|
|
|
|
if (typeof runtimeEnv.getRuntimeValue === "function") {
|
|
try {
|
|
return runtimeEnv.getRuntimeValue(key);
|
|
} catch (_error) {
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
try {
|
|
runtimeValues = runtimeEnv.runtimeValues;
|
|
} catch (_error2) {
|
|
runtimeValues = null;
|
|
}
|
|
if (runtimeValues && typeof runtimeValues === "object") {
|
|
return runtimeValues[key];
|
|
}
|
|
} catch (_error3) {
|
|
return undefined;
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
function buildConfigSnapshot() {
|
|
var accessToken = normalizeAccessToken(
|
|
readFirstConfigValue([
|
|
"accessToken",
|
|
"token",
|
|
"authToken",
|
|
"authorization",
|
|
"Authorization",
|
|
]),
|
|
);
|
|
|
|
return {
|
|
baseUrl: resolveBaseUrl(),
|
|
accessToken: accessToken,
|
|
runtimeClientId: String(
|
|
readConfigValue("runtimeClientId") ||
|
|
readRuntimeValue("runtimeClientId") ||
|
|
createFallbackClientId(accessToken) ||
|
|
"",
|
|
),
|
|
runtimeClientLabel: String(
|
|
readConfigValue("runtimeClientLabel") ||
|
|
readRuntimeValue("runtimeClientLabel") ||
|
|
"",
|
|
),
|
|
};
|
|
}
|
|
|
|
function getConfigSignature(snapshot) {
|
|
return [
|
|
snapshot.baseUrl,
|
|
snapshot.accessToken,
|
|
snapshot.runtimeClientId,
|
|
snapshot.runtimeClientLabel,
|
|
].join("|");
|
|
}
|
|
|
|
function buildClientHeaders(snapshot) {
|
|
var currentSnapshot = snapshot || buildConfigSnapshot();
|
|
var headers = {};
|
|
|
|
if (currentSnapshot.accessToken) {
|
|
headers.Authorization = "Bearer " + currentSnapshot.accessToken;
|
|
}
|
|
if (currentSnapshot.runtimeClientId) {
|
|
headers["X-Music-Client-Id"] = currentSnapshot.runtimeClientId;
|
|
}
|
|
if (currentSnapshot.runtimeClientLabel) {
|
|
headers["X-Music-Client-Label"] = currentSnapshot.runtimeClientLabel;
|
|
}
|
|
return headers;
|
|
}
|
|
|
|
function appendQueryParams(url, params) {
|
|
var pairs = [];
|
|
var key = "";
|
|
var value = null;
|
|
var query = "";
|
|
|
|
if (!params || typeof params !== "object") {
|
|
return url;
|
|
}
|
|
|
|
for (key in params) {
|
|
if (!hasOwn(params, key)) {
|
|
continue;
|
|
}
|
|
value = params[key];
|
|
if (value === undefined || value === null) {
|
|
continue;
|
|
}
|
|
pairs.push(
|
|
encodeURIComponent(key) + "=" + encodeURIComponent(String(value)),
|
|
);
|
|
}
|
|
|
|
if (pairs.length === 0) {
|
|
return url;
|
|
}
|
|
|
|
query = pairs.join("&");
|
|
if (!query) {
|
|
return url;
|
|
}
|
|
|
|
if (url.indexOf("?") !== -1) {
|
|
return url + "&" + query;
|
|
}
|
|
return url + "?" + query;
|
|
}
|
|
|
|
function copyObject(source) {
|
|
var target = {};
|
|
var key = "";
|
|
|
|
if (!source || typeof source !== "object") {
|
|
return target;
|
|
}
|
|
|
|
for (key in source) {
|
|
if (hasOwn(source, key)) {
|
|
target[key] = source[key];
|
|
}
|
|
}
|
|
return target;
|
|
}
|
|
|
|
function createFetchClient(snapshot) {
|
|
var baseHeaders = buildClientHeaders(snapshot);
|
|
|
|
if (typeof fetch !== "function") {
|
|
throw new Error("http_client_unavailable");
|
|
}
|
|
|
|
async function request(method, path, options) {
|
|
var opts = options || {};
|
|
var url = joinWithBaseUrl(path);
|
|
var headers = copyObject(baseHeaders);
|
|
var body = undefined;
|
|
var response = null;
|
|
var text = "";
|
|
var payload = {};
|
|
|
|
url = appendQueryParams(url, opts.params);
|
|
if (opts.data !== undefined) {
|
|
headers["Content-Type"] = "application/json";
|
|
body = JSON.stringify(opts.data);
|
|
}
|
|
|
|
response = await fetch(url, {
|
|
method: method,
|
|
headers: headers,
|
|
body: body,
|
|
});
|
|
text = await response.text();
|
|
if (text) {
|
|
try {
|
|
payload = JSON.parse(text);
|
|
} catch (_error) {
|
|
payload = text;
|
|
}
|
|
}
|
|
|
|
if (!response.ok) {
|
|
throw new Error("http_" + response.status);
|
|
}
|
|
|
|
return { data: payload };
|
|
}
|
|
|
|
return {
|
|
get: function get(path, config) {
|
|
return request("GET", path, { params: config && config.params });
|
|
},
|
|
post: function post(path, data) {
|
|
return request("POST", path, { data: data });
|
|
},
|
|
};
|
|
}
|
|
|
|
function createDefaultClient(snapshot) {
|
|
var currentSnapshot = snapshot || buildConfigSnapshot();
|
|
var headers = buildClientHeaders(currentSnapshot);
|
|
var axios = null;
|
|
|
|
try {
|
|
axios = require("axios");
|
|
if (axios && typeof axios.create === "function") {
|
|
return axios.create({
|
|
baseURL: currentSnapshot.baseUrl || undefined,
|
|
headers: headers,
|
|
});
|
|
}
|
|
} catch (_error) {
|
|
axios = null;
|
|
}
|
|
|
|
return createFetchClient(currentSnapshot);
|
|
}
|
|
|
|
function getClient() {
|
|
var snapshot = null;
|
|
var signature = "";
|
|
|
|
if (testHttpClient) {
|
|
return testHttpClient;
|
|
}
|
|
|
|
snapshot = buildConfigSnapshot();
|
|
signature = getConfigSignature(snapshot);
|
|
|
|
if (!defaultClient || defaultClientSignature !== signature) {
|
|
defaultClient = createDefaultClient(snapshot);
|
|
defaultClientSignature = signature;
|
|
}
|
|
|
|
return defaultClient;
|
|
}
|
|
|
|
function __setHttpClientForTests(client) {
|
|
testHttpClient = client || null;
|
|
}
|
|
|
|
function __setConfigForTests(config) {
|
|
testConfig = config || null;
|
|
defaultClient = null;
|
|
defaultClientSignature = null;
|
|
}
|
|
|
|
function __clearTestState() {
|
|
testHttpClient = null;
|
|
testConfig = null;
|
|
defaultClient = null;
|
|
defaultClientSignature = null;
|
|
}
|
|
|
|
function toPositivePage(page) {
|
|
var value = Number(page);
|
|
if (!isFinite(value) || value <= 0) {
|
|
return 1;
|
|
}
|
|
return Math.floor(value);
|
|
}
|
|
|
|
function toNonNegativeInt(value) {
|
|
var parsed = Number(value);
|
|
if (!isFinite(parsed) || parsed < 0) {
|
|
return null;
|
|
}
|
|
return Math.floor(parsed);
|
|
}
|
|
|
|
function normalizeDuration(value) {
|
|
var parsed = Number(value);
|
|
if (!isFinite(parsed) || parsed <= 0) {
|
|
return 0;
|
|
}
|
|
if (parsed > 1000) {
|
|
return Math.round(parsed / 1000);
|
|
}
|
|
return Math.round(parsed);
|
|
}
|
|
|
|
function pickArrayField(obj, fieldNames) {
|
|
var i = 0;
|
|
var fieldName = "";
|
|
|
|
if (!obj || typeof obj !== "object") {
|
|
return null;
|
|
}
|
|
|
|
for (i = 0; i < fieldNames.length; i += 1) {
|
|
fieldName = fieldNames[i];
|
|
if (Array.isArray(obj[fieldName])) {
|
|
return obj[fieldName];
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function extractList(payload) {
|
|
var list = null;
|
|
|
|
if (Array.isArray(payload)) {
|
|
return payload;
|
|
}
|
|
if (!payload || typeof payload !== "object") {
|
|
return [];
|
|
}
|
|
|
|
list = pickArrayField(payload, [
|
|
"data",
|
|
"musicList",
|
|
"items",
|
|
"list",
|
|
"rows",
|
|
"songs",
|
|
"tracks",
|
|
"toplists",
|
|
]);
|
|
if (list) {
|
|
return list;
|
|
}
|
|
|
|
if (payload.data && typeof payload.data === "object") {
|
|
return extractList(payload.data);
|
|
}
|
|
|
|
return [];
|
|
}
|
|
|
|
function joinArtists(value) {
|
|
var parts = [];
|
|
var i = 0;
|
|
var item = null;
|
|
var part = "";
|
|
|
|
if (Array.isArray(value)) {
|
|
for (i = 0; i < value.length; i += 1) {
|
|
item = value[i];
|
|
if (typeof item === "string") {
|
|
part = toTrimmedString(item);
|
|
} else if (item && typeof item === "object") {
|
|
part = toTrimmedString(item.name || item.artist || item.title);
|
|
} else {
|
|
part = "";
|
|
}
|
|
if (part) {
|
|
parts.push(part);
|
|
}
|
|
}
|
|
return parts.join(", ");
|
|
}
|
|
|
|
if (value && typeof value === "object") {
|
|
return toTrimmedString(value.name || value.artist || value.title);
|
|
}
|
|
|
|
return toTrimmedString(value);
|
|
}
|
|
|
|
function parsePublicId(publicId) {
|
|
var raw = toTrimmedString(publicId);
|
|
var segments = [];
|
|
|
|
if (!raw) {
|
|
return "";
|
|
}
|
|
|
|
segments = raw.split(":");
|
|
if (!segments.length) {
|
|
return raw;
|
|
}
|
|
return segments[segments.length - 1];
|
|
}
|
|
|
|
function parsePublicItemRef(publicId) {
|
|
var raw = toTrimmedString(publicId);
|
|
var segments = [];
|
|
|
|
if (!raw) {
|
|
return { kind: "", id: "" };
|
|
}
|
|
|
|
segments = raw.split(":");
|
|
if (segments.length >= 3 && segments[0] === "catalogsync") {
|
|
return {
|
|
kind: toTrimmedString(segments[1]),
|
|
id: toTrimmedString(segments.slice(2).join(":")),
|
|
};
|
|
}
|
|
|
|
return {
|
|
kind: "",
|
|
id: parsePublicId(raw),
|
|
};
|
|
}
|
|
|
|
function mapMusicItem(item) {
|
|
var albumRaw = {};
|
|
var id = "";
|
|
var title = "";
|
|
var album = "";
|
|
var artwork = "";
|
|
var artist = "";
|
|
var duration = 0;
|
|
|
|
if (!item || typeof item !== "object") {
|
|
return null;
|
|
}
|
|
|
|
if (item.album && typeof item.album === "object") {
|
|
albumRaw = item.album;
|
|
}
|
|
|
|
id = toTrimmedString(item.id || item.songId || item.song_id || item.musicId);
|
|
title = toTrimmedString(item.title || item.name || item.songName);
|
|
if (!id && !title) {
|
|
return null;
|
|
}
|
|
|
|
if (typeof item.album === "string") {
|
|
album = toTrimmedString(item.album);
|
|
} else {
|
|
album = toTrimmedString(albumRaw.name || albumRaw.title);
|
|
}
|
|
artwork = toTrimmedString(
|
|
item.artwork ||
|
|
item.coverImg ||
|
|
item.cover ||
|
|
item.picUrl ||
|
|
albumRaw.artwork ||
|
|
albumRaw.coverImg ||
|
|
albumRaw.cover ||
|
|
albumRaw.picUrl,
|
|
);
|
|
artist = joinArtists(item.artist || item.artists || item.ar || item.singers);
|
|
duration = normalizeDuration(
|
|
item.duration ||
|
|
item.durationSec ||
|
|
item.duration_sec ||
|
|
item.length ||
|
|
item.dt,
|
|
);
|
|
|
|
return {
|
|
id: id || title,
|
|
title: title,
|
|
artist: artist,
|
|
album: album,
|
|
artwork: artwork,
|
|
duration: duration,
|
|
};
|
|
}
|
|
|
|
function mapSheetItem(item) {
|
|
var id = "";
|
|
var result = {};
|
|
var title = "";
|
|
var artist = "";
|
|
var description = "";
|
|
var coverImg = "";
|
|
var worksNumValue = null;
|
|
var playableWorksNumRaw = null;
|
|
var playableWorksNumValue = null;
|
|
var playCountValue = null;
|
|
|
|
if (!item || typeof item !== "object") {
|
|
return null;
|
|
}
|
|
|
|
id = toTrimmedString(item.id || item.sheetId || item.playlistId);
|
|
if (!id) {
|
|
return null;
|
|
}
|
|
|
|
result.id = id;
|
|
|
|
title = toTrimmedString(item.title || item.name);
|
|
artist = toTrimmedString(
|
|
item.artist ||
|
|
(item.creator && item.creator.nickname) ||
|
|
(item.owner && item.owner.nickname) ||
|
|
item.updateFrequency,
|
|
);
|
|
description = toTrimmedString(item.description || item.desc);
|
|
coverImg = toTrimmedString(
|
|
item.coverImg ||
|
|
item.cover ||
|
|
item.artwork ||
|
|
item.picUrl ||
|
|
item.coverImgUrl,
|
|
);
|
|
worksNumValue = toNonNegativeInt(item.worksNum || item.trackCount || item.musicCount);
|
|
if (item.playableWorksNum !== null && item.playableWorksNum !== undefined) {
|
|
playableWorksNumRaw = item.playableWorksNum;
|
|
} else {
|
|
playableWorksNumRaw = item.playableSongCount;
|
|
}
|
|
playableWorksNumValue = toNonNegativeInt(playableWorksNumRaw);
|
|
playCountValue = toNonNegativeInt(item.play_count);
|
|
|
|
if (title) {
|
|
result.title = title;
|
|
}
|
|
if (artist) {
|
|
result.artist = artist;
|
|
}
|
|
if (description) {
|
|
result.description = description;
|
|
}
|
|
if (coverImg) {
|
|
result.coverImg = coverImg;
|
|
}
|
|
if (worksNumValue !== null) {
|
|
result.worksNum = worksNumValue;
|
|
}
|
|
if (playableWorksNumValue !== null) {
|
|
result.playableWorksNum = playableWorksNumValue;
|
|
}
|
|
if (playCountValue !== null) {
|
|
result.play_count = playCountValue;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function mapArtistItem(item) {
|
|
var id = "";
|
|
var name = "";
|
|
var worksNum = null;
|
|
|
|
if (!item || typeof item !== "object") {
|
|
return null;
|
|
}
|
|
|
|
id = toTrimmedString(item.id || item.artistId || item.artist_id);
|
|
name = toTrimmedString(item.name || item.title);
|
|
if (!id && !name) {
|
|
return null;
|
|
}
|
|
|
|
worksNum = toNonNegativeInt(item.worksNum || item.musicCount || item.playableSongCount);
|
|
|
|
return {
|
|
id: id || name,
|
|
name: name || id,
|
|
avatar: toTrimmedString(item.avatar || item.avatarUrl || item.coverImg || item.artwork),
|
|
description: toTrimmedString(item.description || item.desc),
|
|
worksNum: worksNum !== null ? worksNum : 0,
|
|
platform: toTrimmedString(item.platform || "catalogsync"),
|
|
supportedArtistTabs: Array.isArray(item.supportedArtistTabs)
|
|
? item.supportedArtistTabs
|
|
: ["music"],
|
|
};
|
|
}
|
|
|
|
function mapTagItem(item) {
|
|
var id = "";
|
|
var title = "";
|
|
|
|
if (!item || typeof item !== "object") {
|
|
return null;
|
|
}
|
|
|
|
id = toTrimmedString(item.id || item.value || item.key || item.name);
|
|
title = toTrimmedString(item.title || item.name || item.label || id);
|
|
if (!id && !title) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
id: id || title,
|
|
title: title || id,
|
|
};
|
|
}
|
|
|
|
function mapTopListGroup(group, fallbackTitle) {
|
|
var rawItems = [];
|
|
var items = [];
|
|
var i = 0;
|
|
var mapped = null;
|
|
var title = "";
|
|
|
|
if (!group || typeof group !== "object") {
|
|
return null;
|
|
}
|
|
|
|
rawItems = pickArrayField(group, ["data", "items", "list", "toplists"]) || [];
|
|
for (i = 0; i < rawItems.length; i += 1) {
|
|
mapped = mapSheetItem(rawItems[i]);
|
|
if (mapped) {
|
|
items.push(mapped);
|
|
}
|
|
}
|
|
if (!items.length) {
|
|
return null;
|
|
}
|
|
|
|
title = toTrimmedString(group.title || group.name || fallbackTitle);
|
|
return {
|
|
title: title,
|
|
data: items,
|
|
};
|
|
}
|
|
|
|
function normalizeTopListGroups(payload) {
|
|
var source = payload && typeof payload === "object" ? payload : {};
|
|
var baseList = null;
|
|
var first = null;
|
|
var firstIsGroup = false;
|
|
var groups = [];
|
|
var i = 0;
|
|
var group = null;
|
|
var mapped = null;
|
|
var data = [];
|
|
var row = null;
|
|
|
|
if (Array.isArray(payload)) {
|
|
baseList = payload;
|
|
} else {
|
|
baseList = pickArrayField(source, ["data", "groups", "list"]);
|
|
}
|
|
|
|
if (!Array.isArray(baseList) || !baseList.length) {
|
|
return [];
|
|
}
|
|
|
|
first = baseList[0];
|
|
firstIsGroup = !!(
|
|
first &&
|
|
typeof first === "object" &&
|
|
pickArrayField(first, ["data", "items", "list", "toplists"])
|
|
);
|
|
|
|
if (firstIsGroup) {
|
|
for (i = 0; i < baseList.length; i += 1) {
|
|
mapped = mapTopListGroup(baseList[i], "");
|
|
if (mapped) {
|
|
groups.push(mapped);
|
|
}
|
|
}
|
|
return groups;
|
|
}
|
|
|
|
for (i = 0; i < baseList.length; i += 1) {
|
|
row = mapSheetItem(baseList[i]);
|
|
if (row) {
|
|
data.push(row);
|
|
}
|
|
}
|
|
group = {
|
|
title: toTrimmedString(source.title || source.name),
|
|
data: data,
|
|
};
|
|
return [group];
|
|
}
|
|
|
|
function resolveIsEnd(payload, page, pageSize, listLength) {
|
|
var total = null;
|
|
|
|
if (payload && typeof payload === "object") {
|
|
if (typeof payload.isEnd === "boolean") {
|
|
return payload.isEnd;
|
|
}
|
|
if (typeof payload.is_end === "boolean") {
|
|
return payload.is_end;
|
|
}
|
|
if (typeof payload.more === "boolean") {
|
|
return !payload.more;
|
|
}
|
|
|
|
total = Number(
|
|
payload.total ||
|
|
payload.count ||
|
|
payload.totalCount ||
|
|
payload.total_count,
|
|
);
|
|
if (isFinite(total) && total >= 0) {
|
|
return page * pageSize >= total;
|
|
}
|
|
}
|
|
|
|
return listLength < pageSize;
|
|
}
|
|
|
|
async function requestGet(path, params) {
|
|
var client = getClient();
|
|
var response = await client.get(path, params ? { params: params } : undefined);
|
|
|
|
if (
|
|
response &&
|
|
typeof response === "object" &&
|
|
hasOwn(response, "data")
|
|
) {
|
|
return response.data || {};
|
|
}
|
|
return response || {};
|
|
}
|
|
|
|
async function requestPost(path, data) {
|
|
var client = getClient();
|
|
var response = await client.post(path, data);
|
|
|
|
if (
|
|
response &&
|
|
typeof response === "object" &&
|
|
hasOwn(response, "data")
|
|
) {
|
|
return response.data || {};
|
|
}
|
|
return response || {};
|
|
}
|
|
|
|
async function getPluginStatus() {
|
|
try {
|
|
var payload = await requestGet("/auth/v1/token-status");
|
|
if (payload && typeof payload === "object") {
|
|
return payload;
|
|
}
|
|
return null;
|
|
} catch (_error) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function isAbsoluteUrl(url) {
|
|
return /^[a-z][a-z\d+\-.]*:\/\//i.test(toTrimmedString(url));
|
|
}
|
|
|
|
function getUrlProtocol(url) {
|
|
var matched = toTrimmedString(url).match(/^([a-z][a-z\d+\-.]*:)/i);
|
|
return matched ? matched[1] : "";
|
|
}
|
|
|
|
function joinWithBaseUrl(path) {
|
|
var rawPath = toTrimmedString(path);
|
|
var baseUrl = "";
|
|
var protocol = "";
|
|
|
|
if (!rawPath) {
|
|
return "";
|
|
}
|
|
if (isAbsoluteUrl(rawPath)) {
|
|
return rawPath;
|
|
}
|
|
|
|
baseUrl = resolveBaseUrl();
|
|
if (rawPath.indexOf("//") === 0) {
|
|
protocol = getUrlProtocol(baseUrl);
|
|
if (!protocol) {
|
|
return rawPath;
|
|
}
|
|
return protocol + rawPath;
|
|
}
|
|
|
|
if (!baseUrl) {
|
|
return rawPath;
|
|
}
|
|
if (rawPath.charAt(0) === "/") {
|
|
return baseUrl + rawPath;
|
|
}
|
|
return baseUrl + "/" + rawPath;
|
|
}
|
|
|
|
function normalizeTagValue(tag) {
|
|
if (tag && typeof tag === "object") {
|
|
if (hasOwn(tag, "id")) {
|
|
return toTrimmedString(tag.id);
|
|
}
|
|
return toTrimmedString(tag.value || tag.title || tag.name);
|
|
}
|
|
return toTrimmedString(tag);
|
|
}
|
|
|
|
async function getMediaSource(musicItem, quality) {
|
|
var payload = null;
|
|
var stream = null;
|
|
var streamUrl = "";
|
|
|
|
try {
|
|
payload = await requestPost("/mf/v1/media/resolve", {
|
|
song_id: musicItem && musicItem.id,
|
|
quality: quality,
|
|
});
|
|
if (payload && payload.stream && typeof payload.stream === "object") {
|
|
stream = payload.stream;
|
|
}
|
|
streamUrl = joinWithBaseUrl(stream && stream.url);
|
|
if (!streamUrl) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
url: streamUrl,
|
|
headers:
|
|
stream && stream.headers && typeof stream.headers === "object"
|
|
? stream.headers
|
|
: {},
|
|
quality: toTrimmedString(
|
|
payload &&
|
|
payload.selected_source &&
|
|
payload.selected_source.quality,
|
|
) || quality,
|
|
};
|
|
} catch (_error) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async function search(query, page, type) {
|
|
var normalizedPage = 1;
|
|
var endpoint = "";
|
|
var payload = null;
|
|
var rawList = [];
|
|
var data = [];
|
|
var i = 0;
|
|
var mapped = null;
|
|
var mapper = null;
|
|
|
|
if (type === "music") {
|
|
endpoint = "/mf/v1/search/songs";
|
|
mapper = mapMusicItem;
|
|
} else if (type === "artist") {
|
|
endpoint = "/mf/v1/search/artists";
|
|
mapper = mapArtistItem;
|
|
} else if (type === "sheet") {
|
|
endpoint = "/mf/v1/search/sheets";
|
|
mapper = mapSheetItem;
|
|
} else {
|
|
return {
|
|
isEnd: true,
|
|
data: [],
|
|
};
|
|
}
|
|
|
|
try {
|
|
normalizedPage = toPositivePage(page);
|
|
payload = await requestGet(endpoint, {
|
|
q: toTrimmedString(query),
|
|
page: normalizedPage,
|
|
page_size: SEARCH_PAGE_SIZE,
|
|
});
|
|
rawList = extractList(payload);
|
|
for (i = 0; i < rawList.length; i += 1) {
|
|
mapped = mapper(rawList[i]);
|
|
if (mapped) {
|
|
data.push(mapped);
|
|
}
|
|
}
|
|
|
|
return {
|
|
isEnd: resolveIsEnd(payload, normalizedPage, SEARCH_PAGE_SIZE, rawList.length),
|
|
data: data,
|
|
};
|
|
} catch (_error) {
|
|
return {
|
|
isEnd: true,
|
|
data: [],
|
|
};
|
|
}
|
|
}
|
|
|
|
async function getRecommendSheetTags() {
|
|
var payload = null;
|
|
var source = {};
|
|
var pinnedRaw = [];
|
|
var groupsRaw = [];
|
|
var pinned = [];
|
|
var groups = [];
|
|
var i = 0;
|
|
var j = 0;
|
|
var mapped = null;
|
|
var group = null;
|
|
var groupData = [];
|
|
|
|
try {
|
|
payload = await requestGet("/mf/v1/recommend/tags");
|
|
source = payload && typeof payload === "object" ? payload : {};
|
|
|
|
if (Array.isArray(source.pinned)) {
|
|
pinnedRaw = source.pinned;
|
|
} else if (Array.isArray(source.hot)) {
|
|
pinnedRaw = source.hot;
|
|
}
|
|
for (i = 0; i < pinnedRaw.length; i += 1) {
|
|
mapped = mapTagItem(pinnedRaw[i]);
|
|
if (mapped) {
|
|
pinned.push(mapped);
|
|
}
|
|
}
|
|
|
|
if (Array.isArray(source.data)) {
|
|
groupsRaw = source.data;
|
|
} else if (Array.isArray(source.groups)) {
|
|
groupsRaw = source.groups;
|
|
}
|
|
for (i = 0; i < groupsRaw.length; i += 1) {
|
|
group = groupsRaw[i];
|
|
groupData = [];
|
|
if (group && Array.isArray(group.data)) {
|
|
for (j = 0; j < group.data.length; j += 1) {
|
|
mapped = mapTagItem(group.data[j]);
|
|
if (mapped) {
|
|
groupData.push(mapped);
|
|
}
|
|
}
|
|
}
|
|
groups.push({
|
|
title: toTrimmedString(group && (group.title || group.name)),
|
|
data: groupData,
|
|
});
|
|
}
|
|
|
|
return {
|
|
pinned: pinned,
|
|
data: groups,
|
|
};
|
|
} catch (_error) {
|
|
return {
|
|
pinned: [],
|
|
data: [],
|
|
};
|
|
}
|
|
}
|
|
|
|
async function getRecommendSheetsByTag(tag, page) {
|
|
var normalizedPage = toPositivePage(page);
|
|
var payload = null;
|
|
var rawList = [];
|
|
var data = [];
|
|
var i = 0;
|
|
var mapped = null;
|
|
|
|
try {
|
|
payload = await requestGet("/mf/v1/recommend/sheets", {
|
|
tag: normalizeTagValue(tag) || "all",
|
|
page: normalizedPage,
|
|
page_size: LIST_PAGE_SIZE,
|
|
});
|
|
rawList = extractList(payload);
|
|
for (i = 0; i < rawList.length; i += 1) {
|
|
mapped = mapSheetItem(rawList[i]);
|
|
if (mapped) {
|
|
data.push(mapped);
|
|
}
|
|
}
|
|
|
|
return {
|
|
isEnd: resolveIsEnd(payload, normalizedPage, LIST_PAGE_SIZE, rawList.length),
|
|
data: data,
|
|
};
|
|
} catch (_error) {
|
|
return {
|
|
isEnd: true,
|
|
data: [],
|
|
};
|
|
}
|
|
}
|
|
|
|
async function getMusicSheetInfo(sheetItem, page) {
|
|
var normalizedPage = toPositivePage(page);
|
|
var sourceItem = sheetItem && typeof sheetItem === "object" ? sheetItem : {};
|
|
var itemRef = parsePublicItemRef(sourceItem.id);
|
|
var mappedSheetItem = mapSheetItem(sourceItem);
|
|
var endpointBase = "";
|
|
var detail = null;
|
|
var tracksPayload = null;
|
|
var rawList = [];
|
|
var musicList = [];
|
|
var i = 0;
|
|
var mapped = null;
|
|
var result = null;
|
|
|
|
if (!itemRef.id) {
|
|
return {
|
|
isEnd: true,
|
|
sheetItem: normalizedPage === 1 ? mappedSheetItem || sourceItem : sourceItem,
|
|
musicList: [],
|
|
};
|
|
}
|
|
|
|
endpointBase =
|
|
itemRef.kind === "toplist"
|
|
? "/mf/v1/toplists/" + itemRef.id
|
|
: "/mf/v1/playlists/" + itemRef.id;
|
|
|
|
try {
|
|
if (normalizedPage === 1) {
|
|
try {
|
|
detail = await requestGet(endpointBase);
|
|
mappedSheetItem = mapSheetItem(detail) || mappedSheetItem;
|
|
} catch (_error) {
|
|
detail = null;
|
|
}
|
|
}
|
|
|
|
tracksPayload = await requestGet(endpointBase + "/tracks", {
|
|
page: normalizedPage,
|
|
page_size: LIST_PAGE_SIZE,
|
|
});
|
|
rawList = extractList(tracksPayload);
|
|
for (i = 0; i < rawList.length; i += 1) {
|
|
mapped = mapMusicItem(rawList[i]);
|
|
if (mapped) {
|
|
musicList.push(mapped);
|
|
}
|
|
}
|
|
|
|
result = {
|
|
isEnd: resolveIsEnd(
|
|
tracksPayload,
|
|
normalizedPage,
|
|
LIST_PAGE_SIZE,
|
|
rawList.length,
|
|
),
|
|
musicList: musicList,
|
|
};
|
|
if (normalizedPage === 1) {
|
|
result.sheetItem =
|
|
mappedSheetItem ||
|
|
mapSheetItem(sourceItem) || {
|
|
id: toTrimmedString(sourceItem.id || itemRef.id),
|
|
};
|
|
}
|
|
return result;
|
|
} catch (_error2) {
|
|
result = {
|
|
isEnd: true,
|
|
musicList: [],
|
|
};
|
|
if (normalizedPage === 1) {
|
|
result.sheetItem =
|
|
mappedSheetItem ||
|
|
mapSheetItem(sourceItem) || {
|
|
id: toTrimmedString(sourceItem.id || itemRef.id),
|
|
};
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
|
|
async function getArtistWorks(artistItem, page, type) {
|
|
var normalizedPage = toPositivePage(page);
|
|
var itemRef = parsePublicItemRef(artistItem && artistItem.id);
|
|
var payload = null;
|
|
var rawList = [];
|
|
var data = [];
|
|
var i = 0;
|
|
var mapped = null;
|
|
|
|
if (type !== "music" || itemRef.kind !== "artist" || !itemRef.id) {
|
|
return { isEnd: true, data: [] };
|
|
}
|
|
|
|
try {
|
|
payload = await requestGet("/mf/v1/artists/" + itemRef.id + "/tracks", {
|
|
page: normalizedPage,
|
|
page_size: LIST_PAGE_SIZE,
|
|
});
|
|
rawList = extractList(payload);
|
|
for (i = 0; i < rawList.length; i += 1) {
|
|
mapped = mapMusicItem(rawList[i]);
|
|
if (mapped) {
|
|
data.push(mapped);
|
|
}
|
|
}
|
|
return {
|
|
isEnd: resolveIsEnd(payload, normalizedPage, LIST_PAGE_SIZE, rawList.length),
|
|
data: data,
|
|
};
|
|
} catch (_error) {
|
|
return { isEnd: true, data: [] };
|
|
}
|
|
}
|
|
|
|
async function getTopLists() {
|
|
try {
|
|
var payload = await requestGet("/mf/v1/toplists");
|
|
return normalizeTopListGroups(payload);
|
|
} catch (_error) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
async function getTopListDetail(topListItem, page) {
|
|
var normalizedPage = toPositivePage(page);
|
|
var sourceItem = topListItem && typeof topListItem === "object" ? topListItem : {};
|
|
var toplistId = parsePublicId(sourceItem.id);
|
|
var mappedTopListItem = mapSheetItem(sourceItem);
|
|
var detail = null;
|
|
var tracksPayload = null;
|
|
var rawList = [];
|
|
var musicList = [];
|
|
var i = 0;
|
|
var mapped = null;
|
|
var result = null;
|
|
|
|
if (!toplistId) {
|
|
return {
|
|
isEnd: true,
|
|
topListItem: normalizedPage === 1 ? mappedTopListItem || sourceItem : sourceItem,
|
|
musicList: [],
|
|
};
|
|
}
|
|
|
|
try {
|
|
if (normalizedPage === 1) {
|
|
try {
|
|
detail = await requestGet("/mf/v1/toplists/" + toplistId);
|
|
mappedTopListItem = mapSheetItem(detail) || mappedTopListItem;
|
|
} catch (_error) {
|
|
detail = null;
|
|
}
|
|
}
|
|
|
|
tracksPayload = await requestGet("/mf/v1/toplists/" + toplistId + "/tracks", {
|
|
page: normalizedPage,
|
|
page_size: LIST_PAGE_SIZE,
|
|
});
|
|
rawList = extractList(tracksPayload);
|
|
for (i = 0; i < rawList.length; i += 1) {
|
|
mapped = mapMusicItem(rawList[i]);
|
|
if (mapped) {
|
|
musicList.push(mapped);
|
|
}
|
|
}
|
|
|
|
result = {
|
|
isEnd: resolveIsEnd(
|
|
tracksPayload,
|
|
normalizedPage,
|
|
LIST_PAGE_SIZE,
|
|
rawList.length,
|
|
),
|
|
topListItem:
|
|
normalizedPage === 1
|
|
? mappedTopListItem ||
|
|
mapSheetItem(sourceItem) || {
|
|
id: toTrimmedString(sourceItem.id || toplistId),
|
|
}
|
|
: sourceItem || {
|
|
id: toTrimmedString(sourceItem.id || toplistId),
|
|
},
|
|
musicList: musicList,
|
|
};
|
|
return result;
|
|
} catch (_error2) {
|
|
result = {
|
|
isEnd: true,
|
|
musicList: [],
|
|
};
|
|
if (normalizedPage === 1) {
|
|
result.topListItem =
|
|
mappedTopListItem ||
|
|
mapSheetItem(sourceItem) || {
|
|
id: toTrimmedString(sourceItem.id || toplistId),
|
|
};
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
platform: "Music_Server",
|
|
version: "17010.0.8",
|
|
author: "Codex",
|
|
srcUrl: "__MUSIC_SERVER_PLUGIN_SRC_URL__",
|
|
cacheControl: "no-cache",
|
|
primaryKey: ["id"],
|
|
description: "Music_Server private plugin for playlists, toplists, search, playback, and token status.",
|
|
supportedSearchType: ["music", "artist", "sheet"],
|
|
userVariables: [
|
|
{ key: "baseUrl", name: "Base URL" },
|
|
{ key: "accessToken", name: "Access Token" },
|
|
],
|
|
normalizeBaseUrl: normalizeBaseUrl,
|
|
readConfigValue: readConfigValue,
|
|
readRuntimeValue: readRuntimeValue,
|
|
createDefaultClient: createDefaultClient,
|
|
getClient: getClient,
|
|
search: search,
|
|
getArtistWorks: getArtistWorks,
|
|
getMediaSource: getMediaSource,
|
|
getRecommendSheetTags: getRecommendSheetTags,
|
|
getRecommendSheetsByTag: getRecommendSheetsByTag,
|
|
getMusicSheetInfo: getMusicSheetInfo,
|
|
getTopLists: getTopLists,
|
|
getTopListDetail: getTopListDetail,
|
|
getPluginStatus: getPluginStatus,
|
|
__setHttpClientForTests: __setHttpClientForTests,
|
|
__setConfigForTests: __setConfigForTests,
|
|
__clearTestState: __clearTestState,
|
|
};
|