// ==UserScript==
// @name 网易云音乐:云盘快传(含周杰伦)|歌曲下载&转存云盘|云盘匹配纠正|高音质试听
// @namespace https://github.com/Cinvin/myuserscripts
// @version 4.2.3
// @author cinvin
// @description 无需文件云盘快传歌曲(含周杰伦)、歌曲下载&转存云盘(可批量)、云盘匹配纠正、高音质试听、完整歌单列表、评论区显示IP属地、使用指定的IP地址发送评论、歌单歌曲排序(时间、红心数、评论数)、专辑页加载Disc信息、限免VIP歌曲下载上传、云盘音质提升、本地文件上传云盘、云盘导入导出。
// @license MIT
// @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAALiSURBVHgBpVZLThtBEH3d40WkIGWWSSDSGMw6cAP7BBlOgDkB5gSYE2BOgDlBnBNgToCzDmQmii0iZYEXsRRBPJ1X6R4+Mz3IKLVxT1f1q9/rais8IQmiCLjZNlBNA8O1iqzGpAoqNcAnjfmgjh9pFYbyA7+ODIJjAjSxmPTp6MDnSJfBV3YzBOfPABdp88zpBd7ERcWjDC7xdp9bXfyXZHtruOqVHNjITW8RCGZ3xOSHClnITwaF6KFeQ7XqGA/tGrbmBO9W4E0tIFL33W9g0gmQpWuY9A30XvEAsT4mCMM7B3MEXf6EZUN8ZvM2BVAY47bPyK6QuvNLLCcGWdcTFPVLnX8OJHrWadsHXsOsKeuvWD6lza7ThHWkzEqJw4gRvodXzK5kodn92KNNa5hz/0Uo7BBGicOMtc0b2MA4ZnZ17h34HUgWL9uakX3wKIfCaVe6yDqcNWv4NUrINIkswfKGGK5j3K1yItkp1vEahfpr3GwCwZTRJ25rRxoqNbdlUS2gNspwe02Qdh2TEymj5+6MNDzNreMnDwfNe4ezwQXexS4bksLE0geOC9qhJxkR/ASeMmlUS1ilCIBXdmWm1m5pg/0Y+mzFQVrclIhY11H+zWbFDXwfkDlHjHrAHA7svMKGzWheFcxUmpwWdwWwxhqLgds6FEAyp7OK8Rbwm/3R+3BZBjCjP6hFRRxO4G96DnVWVMi9kBpzmbND6JpII8meYwbAZqu20/WFcQqmXcZRA2Vv5e01SmKH1hesdDXMPjzCQDiPZlvuviRFvdwTbdmAYfm4PpTxKzwXQ2EJ7UbusWE/sq1VTFr5ZfT4d5khH3bBOfzMqXxMeC/a/Dn0nEt5pnXnwBl3nLFXJEsZF7IWmnIdVwQEya6Bq4E7dy9P1XtRoeO9dUzKD04uLpM7Cj5DOGGznTzyXEo3mTOnJ29AxdWvkr59Nx6Di6inTrnmxzJx3a11WT382zIjW6bTKoy/H+Iy6oHlZ+kAAAAASUVORK5CYII=
// @match https://music.163.com/*
// @require https://fastly.jsdelivr.net/npm/sweetalert2@11.12.2/dist/sweetalert2.all.min.js
// @require https://fastly.jsdelivr.net/npm/ajax-hook@3.0.3/dist/ajaxhook.min.js
// @require https://fastly.jsdelivr.net/npm/jsmediatags@3.9.7/dist/jsmediatags.min.js
// @require https://fastly.jsdelivr.net/npm/node-forge@1.3.1/dist/forge.min.js
// @grant GM_addStyle
// @grant GM_download
// @grant GM_getValue
// @grant GM_registerMenuCommand
// @grant GM_setValue
// @grant GM_xmlhttpRequest
// @grant unsafeWindow
// @run-at document-start
// ==/UserScript==
(function () {
'use strict';
var _GM_getValue = /* @__PURE__ */ (() => typeof GM_getValue != "undefined" ? GM_getValue : void 0)();
var _unsafeWindow = /* @__PURE__ */ (() => typeof unsafeWindow != "undefined" ? unsafeWindow : void 0)();
const levelOptions = { jymaster: "超清母带", dolby: "杜比全景声", sky: "沉浸环绕声", jyeffect: "高清环绕声", hires: "Hi-Res", lossless: "无损", exhigh: "极高", higher: "较高", standard: "标准" };
const levelWeight = { jymaster: 9, dolby: 8, sky: 7, jyeffect: 6, hires: 5, lossless: 4, exhigh: 3, higher: 2, standard: 1, none: 0 };
const defaultOfDEFAULT_LEVEL = "jymaster";
const uploadChunkSize = 8 * 1024 * 1024;
const songMark = { explicit: 1048576 };
const iv = "0102030405060708";
const presetKey = "0CoJUm6Qyw8W8jud";
const base62 = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
const publicKey = `-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDgtQn2JZ34ZC28NWYpAUd98iZ37BUrX/aKzmFbt7clFSs6sXqHauqKWqdtLkF2KexO40H1YTX8z2lSgBBOAxLsvaklV8k4cBFK9snQXE9/DDaFt6Rr7iVZMldczhC0JNgTz+SHXT6CBHuX3e9SdB1Ua44oncaTWz7OBGLbCiK45wIDAQAB
-----END PUBLIC KEY-----`;
const aesEncrypt = (text, key, iv2) => {
let cipher = forge.cipher.createCipher("AES-CBC", key);
cipher.start({ iv: iv2 });
cipher.update(forge.util.createBuffer(forge.util.encodeUtf8(text)));
cipher.finish();
let encrypted = cipher.output;
return forge.util.encode64(encrypted.getBytes());
};
const rsaEncrypt = (str, key) => {
const forgePublicKey = forge.pki.publicKeyFromPem(key);
const encrypted = forgePublicKey.encrypt(str, "NONE");
return forge.util.bytesToHex(encrypted);
};
const weapi = (object) => {
const text = JSON.stringify(object);
let secretKey = "";
for (let i = 0; i < 16; i++) {
secretKey += base62.charAt(Math.round(Math.random() * 61));
}
return {
params: aesEncrypt(
aesEncrypt(text, presetKey, iv),
secretKey,
iv
),
encSecKey: rsaEncrypt(secretKey.split("").reverse().join(""), publicKey)
};
};
const CookieMap = {
web: true,
android: "os=android;appver=9.1.78;channel=netease;osver=14;buildver=241009150147;",
pc: "os=pc;appver=3.0.18.203152;channel=netease;osver=Microsoft-Windows-10-Professional-build-19045-64bit;"
};
const UserAgentMap = {
web: void 0,
android: "NeteaseMusic/9.1.78.241009150147(9001078);Dalvik/2.1.0 (Linux; U; Android 14; V2318A Build/TP1A.220624.014)",
pc: "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Safari/537.36 Chrome/91.0.4472.164 NeteaseMusicDesktop/3.0.18.203152"
};
const weapiRequest = (url2, config) => {
let data = config.data || {};
let clientType = config.clientType || "pc";
let csrfToken = document.cookie.match(/_csrf=([^(;|$)]+)/);
data.csrf_token = csrfToken ? csrfToken[1] : "";
const encRes = weapi(data);
let headers = {
"content-type": "application/x-www-form-urlencoded",
"user-agent": UserAgentMap[clientType]
};
if (config.ip) {
headers["X-Real-IP"] = config.ip;
headers["X-Forwarded-For"] = config.ip;
}
const details = {
url: url2.replace("api", "weapi") + `?csrf_token=${data.csrf_token}`,
method: "POST",
responseType: "json",
headers,
cookie: CookieMap[clientType],
data: `params=${encodeURIComponent(encRes.params)}&encSecKey=${encodeURIComponent(encRes.encSecKey)}`,
onload: (res) => {
config.onload(res.response);
},
onerror: config.onerror
};
GM_xmlhttpRequest(details);
};
const fileSizeDesc = (fileSize) => {
if (fileSize < 1024) {
return fileSize + "B";
} else if (fileSize >= 1024 && fileSize < Math.pow(1024, 2)) {
return (fileSize / 1024).toFixed(1).toString() + "K";
} else if (fileSize >= Math.pow(1024, 2) && fileSize < Math.pow(1024, 3)) {
return (fileSize / Math.pow(1024, 2)).toFixed(1).toString() + "M";
} else if (fileSize > Math.pow(1024, 3) && fileSize < Math.pow(1024, 4)) {
return (fileSize / Math.pow(1024, 3)).toFixed(2).toString() + "G";
} else if (fileSize > Math.pow(1024, 4)) {
return (fileSize / Math.pow(1024, 4)).toFixed(2).toString() + "T";
}
};
const duringTimeDesc = (dt) => {
let secondTotal = Math.floor(dt / 1e3);
let min = Math.floor(secondTotal / 60);
let sec = secondTotal % 60;
return min.toString().padStart(2, "0") + ":" + sec.toString().padStart(2, "0");
};
const levelDesc = (level) => {
return levelOptions[level] || level;
};
const getArtistTextInSongDetail = (song) => {
let artist = "";
if (song.ar && song.ar[0].name && song.ar[0].name.length > 0) {
artist = song.ar.map((ar) => ar.name).join();
} else if (song.pc && song.pc.ar && song.pc.ar.length > 0) {
artist = song.pc.ar;
}
return artist;
};
const getAlbumTextInSongDetail = (song) => {
let album = "";
if (song.al && song.al.name && song.al.name.length > 0) {
album = song.al.name;
} else if (song.pc && song.pc.alb && song.pc.alb.length > 0) {
album = song.pc.alb;
}
return album;
};
const nameFileWithoutExt = (title, artist, out) => {
if (out == "title" || !artist || artist.length == 0) {
return title;
}
if (out == "artist-title") {
return `${artist} - ${title}`;
}
if (out == "title-artist") {
return `${title} - ${artist}`;
}
};
const escapeHTML = (string) => string.replace(
/[&<>'"]/g,
(word) => ({
"&": "&",
"<": "<",
">": ">",
"'": "'",
'"': """
})[word] || word
);
const hookTopWindow = () => {
ah.proxy({
onResponse: (response, handler) => {
if (response.config.url.includes("/weapi/song/enhance/player/url/v1")) {
let content = JSON.parse(response.response);
let songId = content.data[0].id;
let targetLevel = _GM_getValue("DEFAULT_LEVEL", defaultOfDEFAULT_LEVEL);
if (content.data[0].type.toLowerCase() !== "mp3" && content.data[0].type.toLowerCase() !== "m4a") {
content.data[0].type = "mp3";
}
if (content.data[0].url) {
if (content.data[0].level == "standard") {
if (targetLevel != "standard") {
let apiData = {
"/api/song/enhance/player/url/v1": JSON.stringify({
ids: JSON.stringify([songId]),
level: targetLevel,
encodeType: "mp3"
})
};
if (content.data[0].fee == 0) {
apiData["/api/song/enhance/download/url/v1"] = JSON.stringify({
id: songId,
level: levelWeight[targetLevel] > levelWeight.hires ? "hires" : targetLevel,
encodeType: "mp3"
});
}
weapiRequest("/api/batch", {
data: apiData,
onload: (res) => {
let songUrl = res["/api/song/enhance/player/url/v1"].data[0].url;
let songLevel = res["/api/song/enhance/player/url/v1"].data[0].level;
if (res["/api/song/enhance/download/url/v1"]) {
let songDLLevel = res["/api/song/enhance/download/url/v1"].data.level;
if (res["/api/song/enhance/download/url/v1"].data.url && (levelWeight[songDLLevel] || -1) > (levelWeight[songLevel] || 99)) {
songUrl = res["/api/song/enhance/download/url/v1"].data.url;
songLevel = songDLLevel;
}
}
if (songLevel != "standard") {
content.data[0].url = songUrl;
_unsafeWindow.player.tipPlay(levelDesc(songLevel) + "音质");
}
response.response = JSON.stringify(content);
handler.next(response);
},
onerror: (res) => {
console.error("/api/batch", apiData, res);
response.response = JSON.stringify(content);
handler.next(response);
}
});
} else {
response.response = JSON.stringify(content);
handler.next(response);
}
} else {
_unsafeWindow.player.tipPlay(levelDesc(content.data[0].level) + "音质(云盘文件)");
response.response = JSON.stringify(content);
handler.next(response);
}
} else {
response.response = JSON.stringify(content);
handler.next(response);
}
} else {
handler.next(response);
}
}
}, _unsafeWindow);
};
const sleep = (millisec) => {
return new Promise((resolve) => setTimeout(resolve, millisec));
};
const showConfirmBox = (msg) => {
Swal.fire({
title: "提示",
text: msg,
confirmButtonText: "确定"
});
};
const showTips = (tip, type) => {
unsafeWindow.g_showTipCard({
tip,
type
});
};
const saveContentAsFile = (content, fileName) => {
let data = new Blob([content], {
type: "type/plain"
});
let fileurl = URL.createObjectURL(data);
GM_download({
url: fileurl,
name: fileName,
onload: function() {
URL.revokeObjectURL(data);
},
onerror: function(e) {
console.error(e);
showTips(`下载失败,请尝试将 .${fileName.split(".").pop()} 格式加入 文件扩展名白名单`, 2);
}
});
};
const createBigButton = (desc, parent, appendWay) => {
let btn = document.createElement("a");
btn.className = "u-btn2 u-btn2-1";
let btnDesc = document.createElement("i");
btnDesc.innerHTML = desc;
btn.appendChild(btnDesc);
btn.style.margin = "5px";
if (appendWay === 1) {
parent.appendChild(btn);
} else {
parent.insertBefore(btn, parent.lastChild);
}
return btn;
};
class Uploader {
constructor(config, showAll = false) {
this.songs = [];
this.config = config;
this.filter = {
text: "",
noCopyright: true,
vip: true,
pay: true,
lossless: false,
all: showAll,
songIndexs: []
};
this.page = {
current: 1,
max: 1,
limitCount: 50
};
this.batchUpload = {
threadMax: 2,
threadCount: 2,
working: false,
finnishThread: 0,
songIndexs: []
};
}
start() {
this.showPopup();
}
showPopup() {
Swal.fire({
showCloseButton: true,
showConfirmButton: false,
width: 800,
html: `<style>
table {
width: 100%;
border-spacing: 0px;
border-collapse: collapse;
}
table th, table td {
text-align: left;
text-overflow: ellipsis;
}
table tbody {
display: block;
width: 100%;
max-height: 400px;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
table thead tr, table tbody tr, table tfoot tr {
box-sizing: border-box;
table-layout: fixed;
display: table;
width: 100%;
}
table tbody tr td{
border-bottom: none;
}
tr th:nth-child(1),tr td:nth-child(1){
width: 8%;
}
tr th:nth-child(2){
width: 35%;
}
tr td:nth-child(2){
width: 10%;
}
tr td:nth-child(3){
width: 25%;
}
tr th:nth-child(3),tr td:nth-child(4){
width: 20%;
}
tr th:nth-child(4),tr td:nth-child(5){
width: 8%;
}
tr th:nth-child(5),tr td:nth-child(6){
width: 16%;
}
tr th:nth-child(6),tr td:nth-child(7){
width: 8%;
}
</style>
<input id="text-filter" class="swal2-input" type="text" placeholder="歌曲过滤">
<div id="my-cbs">
<input class="form-check-input" type="checkbox" value="" id="cb-copyright" checked><label class="form-check-label" for="cb-copyright">无版权</label>
<input class="form-check-input" type="checkbox" value="" id="cb-vip" checked><label class="form-check-label" for="cb-vip">VIP</label>
<input class="form-check-input" type="checkbox" value="" id="cb-pay" checked><label class="form-check-label" for="cb-pay">数字专辑</label>
<input class="form-check-input" type="checkbox" value="" id="cb-lossless"><label class="form-check-label" for="cb-lossless">无损资源</label>
<input class="form-check-input" type="checkbox" value="" id="cb-all" ${this.filter.all ? "checked" : ""}><label class="form-check-label" for="cb-all">全部歌曲</label>
</div>
<button type="button" class="swal2-confirm swal2-styled" aria-label="" style="display: inline-block;" id="btn-upload-batch">全部上传</button>
<table border="1" frame="hsides" rules="rows"><thead><tr><th>操作</th><th>歌曲标题</th><th>歌手</th><th>时长</th><th>文件信息</th><th>备注</th> </tr></thead><tbody></tbody></table>
`,
footer: "<div></div>",
didOpen: () => {
let container = Swal.getHtmlContainer();
let footer = Swal.getFooter();
let tbody = container.querySelector("tbody");
this.popupObj = {
container,
tbody,
footer
};
let filterInput = container.querySelector("#text-filter");
filterInput.addEventListener("change", () => {
let filtertext = filterInput.value.trim();
if (this.filter.text != filtertext) {
this.filter.text = filtertext;
this.applyFilter();
}
});
let copyrightInput = container.querySelector("#cb-copyright");
copyrightInput.addEventListener("change", () => {
this.filter.noCopyright = copyrightInput.checked;
this.applyFilter();
});
let vipInput = container.querySelector("#cb-vip");
vipInput.addEventListener("change", () => {
this.filter.vip = vipInput.checked;
this.applyFilter();
});
let payInput = container.querySelector("#cb-pay");
payInput.addEventListener("change", () => {
this.filter.pay = payInput.checked;
this.applyFilter();
});
let losslessInput = container.querySelector("#cb-lossless");
losslessInput.addEventListener("change", () => {
this.filter.lossless = losslessInput.checked;
this.applyFilter();
});
let allInput = container.querySelector("#cb-all");
allInput.addEventListener("change", () => {
this.filter.all = allInput.checked;
this.applyFilter();
});
let uploader = this;
this.btnUploadBatch = container.querySelector("#btn-upload-batch");
this.btnUploadBatch.addEventListener("click", () => {
if (this.batchUpload.working) {
return;
}
this.batchUpload.songIndexs = [];
this.filter.songIndexs.forEach((idx) => {
if (!uploader.songs[idx].uploaded) {
uploader.batchUpload.songIndexs.push(idx);
}
});
if (this.batchUpload.songIndexs.length == 0) {
showTips("没有需要上传的歌曲", 1);
return;
}
this.batchUpload.working = true;
this.batchUpload.finnishThread = 0;
this.batchUpload.threadCount = Math.min(this.batchUpload.songIndexs.length, this.batchUpload.threadMax);
for (let i = 0; i < this.batchUpload.threadCount; i++) {
this.uploadSong(this.batchUpload.songIndexs[i]);
}
});
this.fetchSongInfo();
}
});
}
fetchSongInfo() {
let ids = this.config.data.map((item) => {
return {
"id": item.id
};
});
this.popupObj.tbody.innerHTML = "正在获取歌曲信息...";
this.fetchSongInfoSub(ids, 0);
}
fetchSongInfoSub(ids, startIndex) {
if (startIndex >= ids.length) {
if (this.songs.length == 0) {
this.popupObj.tbody.innerHTML = "没有可以上传的歌曲";
return;
}
this.songs.sort((a, b) => {
if (a.albumid != b.albumid) {
return b.albumid - a.albumid;
}
return a.id - b.id;
});
this.createTableRow();
this.applyFilter();
return;
}
this.popupObj.tbody.innerHTML = `正在获取第${startIndex + 1}到${Math.min(ids.length, startIndex + 1e3)}首歌曲信息...`;
let uploader = this;
weapiRequest("/api/v3/song/detail", {
data: {
c: JSON.stringify(ids.slice(startIndex, startIndex + 1e3))
},
onload: function(content) {
let songslen = content.songs.length;
let privilegelen = content.privileges.length;
for (let i = 0; i < privilegelen; i++) {
if (!content.privileges[i].cs) {
let config = uploader.config.data.find((item2) => {
return item2.id == content.privileges[i].id;
});
let item = {
id: content.privileges[i].id,
name: "未知",
album: "未知",
albumid: 0,
artists: "未知",
tns: "",
//翻译
dt: duringTimeDesc(0),
filename: "未知." + config.ext,
ext: config.ext,
md5: config.md5,
size: config.size,
bitrate: config.bitrate,
picUrl: "http://p4.music.126.net/UeTuwE7pvjBpypWLudqukA==/3132508627578625.jpg",
isNoCopyright: content.privileges[i].st < 0,
isVIP: false,
isPay: false,
uploaded: false,
needMatch: config.name == void 0
};
for (let j = 0; j < songslen; j++) {
if (content.songs[j].id == content.privileges[i].id) {
item.name = content.songs[j].name;
item.album = getAlbumTextInSongDetail(content.songs[j]);
item.albumid = content.songs[j].al.id || 0;
item.artists = getArtistTextInSongDetail(content.songs[j]);
item.tns = content.songs[j].tns ? content.songs[j].tns.join() : "";
item.dt = duringTimeDesc(content.songs[j].dt || 0);
item.filename = nameFileWithoutExt(item.name, item.artists, "artist-title") + "." + config.ext;
item.picUrl = content.songs[j].al && content.songs[j].al.picUrl ? content.songs[j].al.picUrl : "http://p4.music.126.net/UeTuwE7pvjBpypWLudqukA==/3132508627578625.jpg";
item.isVIP = content.songs[j].fee == 1;
item.isPay = content.songs[j].fee == 4;
break;
}
}
if (config.name) {
item.name = config.name;
item.album = config.al;
item.artists = config.ar;
item.filename = nameFileWithoutExt(item.name, item.artists, "artist-title") + "." + config.ext;
}
uploader.songs.push(item);
}
}
uploader.fetchSongInfoSub(ids, startIndex + 1e3);
}
});
}
createTableRow() {
for (let i = 0; i < this.songs.length; i++) {
let song = this.songs[i];
let tablerow = document.createElement("tr");
tablerow.innerHTML = `<td><button type="button" class="swal2-styled">上传</button></td><td><a href="https://music.163.com/album?id=${song.albumid}" target="_blank"><img src="${song.picUrl}?param=50y50&quality=100" title="${song.album}"></a></td><td><a href="https://music.163.com/song?id=${song.id}" target="_blank">${song.name}</a></td><td>${song.artists}</td><td>${song.dt}</td><td>${fileSizeDesc(song.size)} ${song.ext.toUpperCase()}</td><td class="song-remark"></td>`;
let songTitle = tablerow.querySelector(".song-remark");
if (song.isNoCopyright) {
songTitle.innerHTML = "无版权";
} else if (song.isVIP) {
songTitle.innerHTML = "VIP";
} else if (song.isPay) {
songTitle.innerHTML = "数字专辑";
}
let btn = tablerow.querySelector("button");
btn.addEventListener("click", () => {
if (this.batchUpload.working) {
return;
}
this.uploadSong(i);
});
song.tablerow = tablerow;
}
}
applyFilter() {
this.filter.songIndexs = [];
let filterText = this.filter.text;
let isNoCopyright = this.filter.noCopyright;
let isVIP = this.filter.vip;
let isPay = this.filter.pay;
let isLossless = this.filter.lossless;
let isALL = this.filter.all;
for (let i = 0; i < this.songs.length; i++) {
let song = this.songs[i];
if (filterText.length > 0 && !song.name.includes(filterText) && !song.album.includes(filterText) && !song.artists.includes(filterText) && !song.tns.includes(filterText)) {
continue;
}
if (isALL) {
this.filter.songIndexs.push(i);
} else if (isNoCopyright && song.isNoCopyright) {
this.filter.songIndexs.push(i);
} else if (isVIP && song.isVIP) {
this.filter.songIndexs.push(i);
} else if (isPay && song.isPay) {
this.filter.songIndexs.push(i);
} else if (isLossless && song.ext == "flac") {
this.filter.songIndexs.push(i);
}
}
this.page.current = 1;
this.page.max = Math.ceil(this.filter.songIndexs.length / this.page.limitCount);
this.renderData();
this.renderFilterInfo();
}
renderData() {
if (this.filter.songIndexs.length == 0) {
this.popupObj.tbody.innerHTML = "空空如也";
this.popupObj.footer.innerHTML = "";
return;
}
this.popupObj.tbody.innerHTML = "";
let songBegin = (this.page.current - 1) * this.page.limitCount;
let songEnd = Math.min(this.filter.songIndexs.length, songBegin + this.page.limitCount);
for (let i = songBegin; i < songEnd; i++) {
this.popupObj.tbody.appendChild(this.songs[this.filter.songIndexs[i]].tablerow);
}
let pageIndexs = [1];
let floor = Math.max(2, this.page.current - 2);
let ceil = Math.min(this.page.max - 1, this.page.current + 2);
for (let i = floor; i <= ceil; i++) {
pageIndexs.push(i);
}
if (this.page.max > 1) {
pageIndexs.push(this.page.max);
}
let uploader = this;
this.popupObj.footer.innerHTML = "";
pageIndexs.forEach((pageIndex) => {
let pageBtn = document.createElement("button");
pageBtn.setAttribute("type", "button");
pageBtn.className = "swal2-styled";
pageBtn.innerHTML = pageIndex;
if (pageIndex != uploader.page.current) {
pageBtn.addEventListener("click", () => {
uploader.page.current = pageIndex;
uploader.renderData();
});
} else {
pageBtn.style.background = "white";
}
uploader.popupObj.footer.appendChild(pageBtn);
});
}
renderFilterInfo() {
let sizeTotal = 0;
let countCanUpload = 0;
this.filter.songIndexs.forEach((idx) => {
let song = this.songs[idx];
if (!song.uploaded) {
countCanUpload += 1;
sizeTotal += song.size;
}
});
this.btnUploadBatch.innerHTML = "全部上传";
if (countCanUpload > 0) {
this.btnUploadBatch.innerHTML += ` (${countCanUpload}首 ${fileSizeDesc(sizeTotal)})`;
}
}
uploadSong(songIndex) {
let song = this.songs[songIndex];
let uploader = this;
try {
let songCheckData = [{
md5: song.md5,
songId: song.id,
bitrate: song.bitrate,
fileSize: song.size
}];
weapiRequest("/api/cloud/upload/check/v2", {
data: {
uploadType: 0,
songs: JSON.stringify(songCheckData)
},
onload: (res1) => {
if (res1.code != 200) {
console.error(song.name, "1.检查资源", res1);
uploader.onUploadFail(songIndex);
return;
}
if (res1.data.length < 1) {
if (song.id > 0) {
uploader.songs[songIndex].id = 0;
uploader.uploadSong(songIndex);
} else {
console.error(song.name, "1.检查资源", res1);
uploader.onUploadFail(songIndex);
}
return;
}
console.log(song.name, "1.检查资源", res1);
song.cloudId = res1.data[0].songId;
showTips(`(2/6)${song.name} 检查资源`, 1);
if (res1.data[0].upload == 1) {
uploader.uploadSongWay1Part1(songIndex);
} else {
uploader.uploadSongWay2Part1(songIndex);
}
},
onerror: function(res) {
console.error(song.name, "1.检查资源", res);
uploader.onUploadFail(songIndex);
}
});
} catch (e) {
console.error(e);
uploader.onUploadFail(songIndex);
}
}
uploadSongWay1Part1(songIndex) {
let song = this.songs[songIndex];
let uploader = this;
let importSongData = [{
songId: song.cloudId,
bitrate: song.bitrate,
song: song.filename,
artist: song.artists,
album: song.album,
fileName: song.filename
}];
try {
weapiRequest("/api/cloud/user/song/import", {
data: {
uploadType: 0,
songs: JSON.stringify(importSongData)
},
onload: (res) => {
if (res.code != 200 || res.data.successSongs.length < 1) {
console.error(song.name, "2.导入文件", res);
uploader.onUploadFail(songIndex);
return;
}
console.log(song.name, "2.导入文件", res);
song.cloudSongId = res.data.successSongs[0].song.songId;
uploader.uploadSongMatch(songIndex);
},
onerror: (responses2) => {
console.error(song.name, "2.导入歌曲", responses2);
uploader.onUploadFail(songIndex);
}
});
} catch (e) {
console.error(e);
uploader.onUploadFail(songIndex);
}
}
uploadSongWay2Part1(songIndex) {
let song = this.songs[songIndex];
let uploader = this;
try {
weapiRequest("/api/nos/token/alloc", {
data: {
filename: song.filename,
length: song.size,
ext: song.ext,
type: "audio",
bucket: "jd-musicrep-privatecloud-audio-public",
local: false,
nos_product: 3,
md5: song.md5
},
onload: (tokenRes) => {
song.token = tokenRes.result.token;
song.objectKey = tokenRes.result.objectKey;
song.resourceId = tokenRes.result.resourceId;
song.expireTime = Date.now() + 6e4;
console.log(song.name, "2.2.开始上传", tokenRes);
uploader.uploadSongWay2Part2(songIndex);
},
onerror: (responses2) => {
console.error(song.name, "2.获取令牌", responses2);
uploader.onUploadFail(songIndex);
}
});
} catch (e) {
console.error(e);
uploader.onUploadFail(songIndex);
}
}
uploadSongWay2Part2(songIndex) {
let song = this.songs[songIndex];
let uploader = this;
weapiRequest("/api/upload/cloud/info/v2", {
data: {
md5: song.md5,
songid: song.cloudId,
filename: song.filename,
song: song.name,
album: song.album,
artist: song.artists,
bitrate: String(song.bitrate || 128),
resourceId: song.resourceId
},
onload: (res3) => {
if (res3.code != 200) {
if (song.expireTime < Date.now() || res3.msg && res3.msg.includes("rep create failed")) {
console.error(song.name, "3.提交文件", res3);
uploader.onUploadFail(songIndex);
} else {
console.log(song.name, "3.正在转码", res3);
sleep(1e3).then(() => {
uploader.uploadSongWay2Part2(songIndex);
});
}
return;
}
console.log(song.name, "3.提交文件", res3);
weapiRequest("/api/cloud/pub/v2", {
data: {
songid: res3.songId
},
onload: (res4) => {
if (res4.code != 200 && res4.code != 201) {
console.error(song.name, "4.发布资源", res4);
uploader.onUploadFail(songIndex);
return;
}
console.log(song.name, "4.发布资源", res4);
song.cloudSongId = res4.privateCloud.songId;
uploader.uploadSongMatch(songIndex);
},
onerror: function(res) {
console.error(song.name, "4.发布资源", res);
uploader.onUploadFail(songIndex);
}
});
},
onerror: function(res) {
console.error(song.name, "3.提交文件", res);
uploader.onUploadFail(songIndex);
}
});
}
uploadSongMatch(songIndex) {
let song = this.songs[songIndex];
let uploader = this;
if (song.cloudSongId != song.id && song.id > 0) {
weapiRequest("/api/cloud/user/song/match", {
data: {
songId: song.cloudSongId,
adjustSongId: song.id
},
onload: (res5) => {
if (res5.code != 200) {
console.error(song.name, "5.匹配歌曲", res5);
uploader.onUploadFail(songIndex);
return;
}
console.log(song.name, "5.匹配歌曲", res5);
console.log(song.name, "完成");
uploader.onUploadSucess(songIndex);
},
onerror: function(res) {
console.error(song.name, "5.匹配歌曲", res);
uploader.onUploadFail(songIndex);
}
});
} else {
console.log(song.name, "完成");
uploader.onUploadSucess(songIndex);
}
}
onUploadFail(songIndex) {
let song = this.songs[songIndex];
showTips(`${song.name} - ${song.artists} - ${song.album} 上传失败`, 2);
this.onUploadFinnsh(songIndex);
}
onUploadSucess(songIndex) {
let song = this.songs[songIndex];
showTips(`${song.name} - ${song.artists} - ${song.album} 上传成功`, 1);
song.uploaded = true;
let btnUpload = song.tablerow.querySelector("button");
btnUpload.innerHTML = "已上传";
btnUpload.disabled = "disabled";
this.onUploadFinnsh(songIndex);
}
onUploadFinnsh(songIndex) {
if (this.batchUpload.working) {
let batchSongIdx = this.batchUpload.songIndexs.indexOf(songIndex);
if (batchSongIdx + this.batchUpload.threadCount < this.batchUpload.songIndexs.length) {
this.uploadSong(this.batchUpload.songIndexs[batchSongIdx + this.batchUpload.threadCount]);
} else {
this.batchUpload.finnishThread += 1;
if (this.batchUpload.finnishThread == this.batchUpload.threadCount) {
this.batchUpload.working = false;
this.renderFilterInfo();
showTips("上传完成", 1);
}
}
} else {
this.renderFilterInfo();
}
}
}
const baseCDNURL = "https://fastly.jsdelivr.net/gh/Cinvin/cdn@latest/artist/";
const optionMap = {
0: "热门",
1: "华语男歌手",
2: "华语女歌手",
3: "华语组合",
4: "欧美男歌手",
5: "欧美女歌手",
6: "欧美组合",
7: "日本男歌手",
8: "日本女歌手",
9: "日本组合",
10: "韩国男歌手",
11: "韩国女歌手",
12: "韩国组合"
};
const cloudUpload = (uiArea) => {
let btnUpload = createBigButton("快速上传加载中", uiArea, 2);
let btnUploadDesc = btnUpload.firstChild;
let toplist = [];
let selectOptions = {
"热门": {},
"华语男歌手": {},
"华语女歌手": {},
"华语组合": {},
"欧美男歌手": {},
"欧美女歌手": {},
"欧美组合": {},
"日本男歌手": {},
"日本女歌手": {},
"日本组合": {},
"韩国男歌手": {},
"韩国女歌手": {},
"韩国组合": {}
};
let artistmap = {};
fetch(`${baseCDNURL}top.json`).then((r) => r.json()).then((r) => {
toplist = r;
toplist.forEach((artist) => {
selectOptions[optionMap[artist.categroy]][artist.id] = `${artist.name}(${artist.count}首/${artist.sizeDesc})`;
artistmap[artist.id] = artist;
});
btnUpload.addEventListener("click", ShowCloudUploadPopUp);
btnUploadDesc.innerHTML = "云盘快速上传";
});
function ShowCloudUploadPopUp() {
Swal.fire({
title: "快速上传",
input: "select",
inputOptions: selectOptions,
inputPlaceholder: "选择歌手",
confirmButtonText: "下一步",
showCloseButton: true,
footer: '<a href="https://github.com/Cinvin/myuserscripts" target="_blank"><img src="https://img.shields.io/github/stars/cinvin/myuserscripts?style=social" alt="Github"></a>',
inputValidator: (value) => {
if (!value) {
return "请选择歌手";
}
}
}).then((result) => {
if (result.isConfirmed) {
fetchCDNConfig(result.value);
}
});
}
function fetchCDNConfig(artistId) {
showTips(`正在获取资源配置...`, 1);
fetch(`${baseCDNURL}${artistId}.json`).then((r) => r.json()).then((r) => {
let uploader = new Uploader(r);
uploader.start();
}).catch(`获取资源配置失败`);
}
};
const cloudMatch = (uiArea) => {
let btnMatch = createBigButton("云盘匹配纠正", uiArea, 2);
btnMatch.addEventListener("click", () => {
let matcher = new Matcher();
matcher.start();
});
class Matcher {
start() {
this.cloudCountLimit = 50;
this.currentPage = 1;
this.filter = {
text: "",
notMatch: false,
songs: [],
filterInput: null,
notMatchCb: null
};
this.controls = {
tbody: null,
pageArea: null,
cloudDesc: null
};
this.openCloudList();
}
openCloudList() {
Swal.fire({
showCloseButton: true,
showConfirmButton: false,
width: 800,
html: `<style>
table {
width: 100%;
border-spacing: 0px;
border-collapse: collapse;
}
table th, table td {
text-align: left;
text-overflow: ellipsis;
}
table tbody {
display: block;
width: 100%;
max-height: 400px;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
table thead tr, table tbody tr, table tfoot tr {
box-sizing: border-box;
table-layout: fixed;
display: table;
width: 100%;
}
table tbody tr td{
border-bottom: none;
}
tr th:nth-child(1),tr td:nth-child(1){
width: 8%;
}
tr th:nth-child(2){
width: 32%;
}
tr td:nth-child(2){
width: 8%;
}
tr td:nth-child(3){
width: 25%;
}
tr th:nth-child(3),tr td:nth-child(4){
width: 18%;
}
tr th:nth-child(4),tr td:nth-child(5){
width: 8%;
}
tr th:nth-child(5),tr td:nth-child(6){
width: 18%;
}
tr th:nth-child(6),tr td:nth-child(7){
width: 15%;
}
</style>
<input class="swal2-input" type="text" value="${this.filter.text}" id="text-filter" placeholder="歌曲过滤">
<input class="form-check-input" type="checkbox" value="" id="cb-notmatch" ${this.filter.notMatch ? "checked" : ""}><label class="form-check-label" for="cb-notmatch">未匹配歌曲</label>
`,
footer: `<div id="page-area"></div><br><div id="cloud-desc">${this.controls.cloudDesc ? this.controls.cloudDesc.innerHTML : ""}</div>`,
didOpen: () => {
let cloudListContainer = Swal.getHtmlContainer();
let cloudListFooter = Swal.getFooter();
cloudListFooter.style.display = "block";
cloudListFooter.style.textAlign = "center";
let songtb = document.createElement("table");
songtb.border = 1;
songtb.frame = "hsides";
songtb.rules = "rows";
songtb.innerHTML = `<thead><tr><th>操作</th><th>歌曲标题</th><th>歌手</th><th>时长</th><th>文件信息</th><th>上传日期</th> </tr></thead><tbody></tbody>`;
let tbody = songtb.querySelector("tbody");
this.controls.tbody = tbody;
this.controls.pageArea = cloudListFooter.querySelector("#page-area");
this.controls.cloudDesc = cloudListFooter.querySelector("#cloud-desc");
let filterInput = cloudListContainer.querySelector("#text-filter");
let notMatchCb = cloudListContainer.querySelector("#cb-notmatch");
this.filter.filterInput = filterInput;
this.filter.notMatchCb = notMatchCb;
let matcher = this;
filterInput.addEventListener("change", () => {
if (matcher.filter.text == filterInput.value.trim()) {
return;
}
matcher.filter.text = filterInput.value.trim();
this.onCloudInfoFilterChange();
});
notMatchCb.addEventListener("change", () => {
matcher.filter.notMatch = notMatchCb.checked;
this.onCloudInfoFilterChange();
});
cloudListContainer.appendChild(songtb);
if (this.filter.text == "" && !this.filter.notMatch) {
this.fetchCloudInfoForMatchTable((this.currentPage - 1) * this.cloudCountLimit);
} else {
this.sepreateFilterCloudListPage(this.currentPage);
}
}
});
}
fetchCloudInfoForMatchTable(offset) {
this.controls.tbody.innerHTML = "正在获取...";
let matcher = this;
weapiRequest("/api/v1/cloud/get", {
data: {
limit: this.cloudCountLimit,
offset
},
onload: (res) => {
matcher.currentPage = offset / this.cloudCountLimit + 1;
let maxPage = Math.ceil(res.count / this.cloudCountLimit);
this.controls.cloudDesc.innerHTML = `云盘容量 ${fileSizeDesc(res.size)}/${fileSizeDesc(res.maxSize)} 共${res.count}首歌曲`;
let pageIndexs = [1];
let floor = Math.max(2, matcher.currentPage - 2);
let ceil = Math.min(maxPage - 1, matcher.currentPage + 2);
for (let i = floor; i <= ceil; i++) {
pageIndexs.push(i);
}
if (maxPage > 1) {
pageIndexs.push(maxPage);
}
matcher.controls.pageArea.innerHTML = "";
pageIndexs.forEach((pageIndex) => {
let pageBtn = document.createElement("button");
pageBtn.setAttribute("type", "button");
pageBtn.className = "swal2-styled";
pageBtn.innerHTML = pageIndex;
if (pageIndex != matcher.currentPage) {
pageBtn.addEventListener("click", () => {
matcher.fetchCloudInfoForMatchTable(matcher.cloudCountLimit * (pageIndex - 1));
});
} else {
pageBtn.style.background = "white";
}
matcher.controls.pageArea.appendChild(pageBtn);
});
this.fillCloudListTable(res.data);
}
});
}
fillCloudListTable(songs) {
let matcher = this;
matcher.controls.tbody.innerHTML = "";
if (songs.length == 0) {
matcher.controls.tbody.innerHTML = "空空如也";
}
songs.forEach(function(song) {
let album = song.album;
let picUrl = "http://p4.music.126.net/UeTuwE7pvjBpypWLudqukA==/3132508627578625.jpg";
if (song.simpleSong.al && song.simpleSong.al.picUrl) {
picUrl = song.simpleSong.al.picUrl;
}
if (song.simpleSong.al && song.simpleSong.al.name && song.simpleSong.al.name.length > 0) {
album = song.simpleSong.al.name;
}
let artist = song.artist;
if (song.simpleSong.ar) {
let artist2 = "";
let arcount = 0;
song.simpleSong.ar.forEach((ar) => {
if (ar.name) {
if (ar.id > 0) artist2 += `<a target="_blank" href="https://music.163.com/artist?id=${ar.id}">${ar.name}<a>,`;
else artist2 += ar.name + ",";
arcount += 1;
}
});
if (arcount > 0) {
artist = artist2.substring(0, artist2.length - 1);
}
}
let dateObj = new Date(song.addTime);
let addTime = `${dateObj.getFullYear()}-${dateObj.getMonth() + 1}-${dateObj.getDate()}`;
let tablerow = document.createElement("tr");
tablerow.innerHTML = `<td><button type="button" class="swal2-styled">匹配</button></td><td><a class="album-link"><img src="${picUrl}?param=50y50&quality=100" title="${album}"></a></td><td><a class="song-link" target="_blank" href="https://music.163.com/song?id=${song.simpleSong.id}">${song.simpleSong.name}</a></td><td>${artist}</td><td>${duringTimeDesc(song.simpleSong.dt)}</td><td>${fileSizeDesc(song.fileSize)} ${levelDesc(song.simpleSong.privilege.plLevel)}</td><td>${addTime}</td>`;
if (song.simpleSong.al && song.simpleSong.al.id > 0) {
let albumLink = tablerow.querySelector(".album-link");
albumLink.href = "https://music.163.com/album?id=" + song.simpleSong.al.id;
albumLink.target = "_blank";
}
let btn = tablerow.querySelector("button");
btn.addEventListener("click", () => {
matcher.openMatchPopup(song);
});
matcher.controls.tbody.appendChild(tablerow);
});
}
onCloudInfoFilterChange() {
this.filter.songs = [];
if (this.filter.text == "" && !this.filter.notMatch) {
this.fetchCloudInfoForMatchTable(0);
return;
}
this.filter.filterInput.setAttribute("disabled", 1);
this.filter.notMatchCb.setAttribute("disabled", 1);
this.cloudInfoFilterFetchData(0);
}
cloudInfoFilterFetchData(offset) {
let matcher = this;
if (offset == 0) {
this.filter.songs = [];
}
weapiRequest("/api/v1/cloud/get", {
data: {
limit: 1e3,
offset
},
onload: (res) => {
matcher.controls.tbody.innerHTML = `正在搜索第${offset + 1}到${Math.min(offset + 1e3, res.count)}云盘歌曲`;
res.data.forEach((song) => {
if (matcher.filter.text.length > 0) {
let matchFlag = false;
if (song.album.includes(matcher.filter.text) || song.artist.includes(matcher.filter.text) || song.simpleSong.name.includes(matcher.filter.text) || song.simpleSong.al && song.simpleSong.al.id > 0 && song.simpleSong.al.name && song.simpleSong.al.name.includes(matcher.filter.text)) {
matchFlag = true;
}
if (!matchFlag && song.simpleSong.ar) {
song.simpleSong.ar.forEach((ar) => {
if (ar.name && ar.name.includes(matcher.filter.text)) {
matchFlag = true;
}
});
if (!matchFlag) {
return;
}
}
}
if (matcher.filter.notMatch && song.simpleSong.cd) {
return;
}
matcher.filter.songs.push(song);
});
if (res.hasMore) {
res = {};
matcher.cloudInfoFilterFetchData(offset + 1e3);
} else {
matcher.sepreateFilterCloudListPage(1);
matcher.filter.filterInput.removeAttribute("disabled");
matcher.filter.notMatchCb.removeAttribute("disabled");
}
}
});
}
sepreateFilterCloudListPage(currentPage) {
this.currentPage = currentPage;
let matcher = this;
let count = this.filter.songs.length;
let maxPage = Math.ceil(count / this.cloudCountLimit);
this.controls.pageArea.innerHTML = "";
let pageIndexs = [1];
let floor = Math.max(2, currentPage - 2);
let ceil = Math.min(maxPage - 1, currentPage + 2);
for (let i = floor; i <= ceil; i++) {
pageIndexs.push(i);
}
if (maxPage > 1) {
pageIndexs.push(maxPage);
}
matcher.controls.pageArea.innerHTML = "";
pageIndexs.forEach((pageIndex) => {
let pageBtn = document.createElement("button");
pageBtn.setAttribute("type", "button");
pageBtn.className = "swal2-styled";
pageBtn.innerHTML = pageIndex;
if (pageIndex != currentPage) {
pageBtn.addEventListener("click", () => {
matcher.sepreateFilterCloudListPage(pageIndex);
});
} else {
pageBtn.style.background = "white";
}
matcher.controls.pageArea.appendChild(pageBtn);
});
let songindex = (currentPage - 1) * this.cloudCountLimit;
matcher.fillCloudListTable(matcher.filter.songs.slice(songindex, songindex + this.cloudCountLimit));
}
openMatchPopup(song) {
let matcher = this;
Swal.fire({
showCloseButton: true,
title: `歌曲 ${song.simpleSong.name} 匹配纠正`,
input: "number",
inputLabel: "目标歌曲ID",
footer: "ID为0时解除匹配 歌曲页面网址里的数字就是ID",
inputValidator: (value) => {
if (!value) {
return "内容为空";
}
},
didOpen: () => {
let titleDOM = Swal.getTitle();
weapiRequest("/api/song/enhance/player/url/v1", {
data: {
immerseType: "ste",
ids: JSON.stringify([song.simpleSong.id]),
level: "standard",
encodeType: "mp3"
},
onload: (content) => {
titleDOM.innerHTML += " 文件时长" + duringTimeDesc(content.data[0].time);
}
});
}
}).then((result) => {
if (result.isConfirmed) {
let fromId = song.simpleSong.id;
let toId = result.value;
weapiRequest("/api/cloud/user/song/match", {
data: {
songId: fromId,
adjustSongId: toId
},
onload: (res) => {
if (res.code != 200) {
showTips(res.message || res.msg || "匹配失败", 2);
} else {
let msg = "解除匹配成功";
if (toId > 0) {
msg = "匹配成功";
if (res.matchData) {
msg = `${res.matchData.songName} 成功匹配到 ${res.matchData.simpleSong.name} `;
}
}
showTips(msg, 1);
if (matcher.filter.songs.length > 0 && res.matchData) {
for (let i = 0; i < matcher.filter.songs.length; i++) {
if (matcher.filter.songs[i].simpleSong.id == fromId) {
res.matchData.simpleSong.privilege = matcher.filter.songs[i].simpleSong.privilege;
matcher.filter.songs[i] = res.matchData;
break;
}
}
}
}
matcher.openCloudList();
}
});
} else {
matcher.openCloudList();
}
});
}
}
};
class ncmDownUpload {
constructor(songs, showfinishBox = true, onSongDUSuccess = null, onSongDUFail = null, out = "artist-title") {
this.songs = songs;
this.currentIndex = 0;
this.failSongs = [];
this.out = out;
this.showfinishBox = showfinishBox;
this.onSongDUSuccess = onSongDUSuccess;
this.onSongDUFail = onSongDUFail;
}
startUpload() {
this.currentIndex = 0;
this.failSongs = [];
if (this.songs.length > 0) {
this.uploadSong(this.songs[0]);
}
}
uploadSong(song) {
try {
weapiRequest(song.api.url, {
data: song.api.data,
onload: (content) => {
showTips(`(1/3)${song.title} 获取文件信息完成`, 1);
let resData = content.data[0] || content.data;
if (resData.url != null) {
song.fileFullName = nameFileWithoutExt(song.title, song.artist, this.out) + "." + resData.type.toLowerCase();
song.dlUrl = resData.url;
song.md5 = resData.md5;
song.size = resData.size;
song.ext = resData.type.toLowerCase();
song.bitrate = Math.floor(resData.br / 1e3);
let songCheckData = [{
md5: song.md5,
songId: song.id,
bitrate: song.bitrate,
fileSize: song.size
}];
weapiRequest("/api/cloud/upload/check/v2", {
data: {
uploadType: 0,
songs: JSON.stringify(songCheckData)
},
onload: (res1) => {
console.log(song.title, "1.检查资源", res1);
if (res1.code != 200 || res1.data.length < 1) {
this.uploadSongFail(song);
return;
}
showTips(`(2/3)${song.title} 检查资源`, 1);
song.cloudId = res1.data[0].songId;
if (res1.data[0].upload == 1) {
this.uploadSongWay1Part1(song);
} else if (res1.data[0].upload == 2) {
this.uploadSongWay2Part1(song);
} else {
this.uploadSongWay3Part1(song);
}
},
onerror: (res) => {
console.error(song.title, "1.检查资源", res);
this.uploadSongFail(song);
}
});
} else {
this.uploadSongFail(song);
}
},
onerror: (res) => {
console.error(song.title, "0.获取URL", res);
this.uploadSongFail(song);
}
});
} catch (e) {
console.error(e);
this.uploadSongFail(song);
}
}
uploadSongWay1Part1(song) {
let importSongData = [{
songId: song.cloudId,
bitrate: song.bitrate,
song: song.fileFullName,
artist: song.artist,
album: song.album,
fileName: song.fileFullName
}];
try {
weapiRequest("/api/cloud/user/song/import", {
data: {
uploadType: 0,
songs: JSON.stringify(importSongData)
},
onload: (res) => {
console.log(song.title, "2.导入文件", res);
if (res.code != 200 || res.data.successSongs.length < 1) {
console.error(song.title, "2.导入文件", res);
this.uploadSongFail(song);
return;
}
showTips(`(3/3)${song.title} 2.导入文件完成`, 1);
song.cloudSongId = res.data.successSongs[0].song.songId;
this.uploadSongMatch(song);
},
onerror: (responses2) => {
console.error(song.title, "2.导入歌曲", responses2);
this.uploadSongFail(song);
}
});
} catch (e) {
console.error(e);
this.uploadSongFail(song);
}
}
uploadSongWay2Part1(song) {
try {
weapiRequest("/api/nos/token/alloc", {
data: {
filename: song.fileFullName,
length: song.size,
ext: song.ext,
type: "audio",
bucket: "jd-musicrep-privatecloud-audio-public",
local: false,
nos_product: 3,
md5: song.md5
},
onload: (res2) => {
if (res2.code != 200) {
console.error(song.title, "2.获取令牌", res2);
this.uploadSongFail(song);
return;
}
song.resourceId = res2.result.resourceId;
showTips(`(3/6)${song.title} 获取令牌完成`, 1);
console.log(song.title, "2.获取令牌", res2);
showTips(`(3/6)${song.title} 开始上传文件`, 1);
this.uploadSongPart2(song);
},
onerror: (res) => {
console.error(song.title, "2.获取令牌", res);
this.uploadSongFail(song);
}
});
} catch (e) {
console.error(e);
this.uploadSongFail(song);
}
}
uploadSongPart2(song) {
showTips(`(3.1/6)${song.title} 开始下载文件`, 1);
try {
GM_xmlhttpRequest({
method: "GET",
url: song.dlUrl,
headers: {
"Content-Type": "audio/mpeg"
},
responseType: "blob",
onload: (response) => {
showTips(`(3.2/6)${song.title} 文件下载完成`, 1);
let buffer = response.response;
weapiRequest("/api/nos/token/alloc", {
data: {
filename: song.fileFullName,
length: song.size,
ext: song.ext,
type: "audio",
bucket: "jd-musicrep-privatecloud-audio-public",
local: false,
nos_product: 3,
md5: song.md5
},
onload: (tokenRes) => {
song.token = tokenRes.result.token;
song.objectKey = tokenRes.result.objectKey;
console.log(song.title, "2.2.开始上传", tokenRes);
showTips(`(3.3/6)${song.title} 开始上传文件`, 1);
this.uploadFile(buffer, song, 0);
},
onerror: (responses2) => {
console.error(song.title, "2.1.获取令牌", responses2);
this.uploadSongFail(song);
}
});
}
});
} catch (e) {
console.error(e);
this.uploadSongFail(song);
}
}
uploadFile(data, song, offset, context = null) {
let complete = offset + uploadChunkSize > song.size;
let url2 = `http://45.127.129.8/jd-musicrep-privatecloud-audio-public/${encodeURIComponent(song.objectKey)}?offset=${offset}&complete=${String(complete)}&version=1.0`;
if (context) url2 += `&context=${context}`;
GM_xmlhttpRequest({
method: "POST",
url: url2,
headers: {
"x-nos-token": song.token,
"Content-MD5": song.md5,
"Content-Type": "audio/mpeg"
},
data: data.slice(offset, offset + uploadChunkSize),
onload: (response3) => {
let res = JSON.parse(response3.response);
if (complete) {
console.log(song.title, "2.5.上传文件完成", res);
showTips(`(4/6)${song.title} 上传文件完成`, 1);
this.uploadSongPart3(song);
} else {
showTips(`(4/6)${song.title} 正在上传${fileSizeDesc(res.offset)}/${fileSizeDesc(song.size)}`, 1);
this.uploadFile(data, song, res.offset, res.context);
}
},
onerror: (response3) => {
console.error(song.title, "文件上传时失败", response3);
this.uploadSongFail(song);
}
});
}
uploadSongWay3Part1(song) {
try {
weapiRequest("/api/nos/token/alloc", {
data: {
filename: song.fileFullName,
length: song.size,
ext: song.ext,
type: "audio",
bucket: "jd-musicrep-privatecloud-audio-public",
local: false,
nos_product: 3,
md5: song.md5
},
onload: (res2) => {
if (res2.code != 200) {
console.error(song.title, "2.获取令牌", res2);
this.uploadSongFail(song);
return;
}
song.resourceId = res2.result.resourceId;
showTips(`(3/6)${song.title} 获取令牌完成`, 1);
console.log(song.title, "2.获取令牌", res2);
this.uploadSongPart3(song);
},
onerror: (res) => {
console.error(song.title, "2.获取令牌", res);
this.uploadSongFail(song);
}
});
} catch (e) {
console.error(e);
this.uploadSongFail(song);
}
}
uploadSongPart3(song) {
try {
console.log(song);
weapiRequest("/api/upload/cloud/info/v2", {
data: {
md5: song.md5,
songid: song.cloudId,
filename: song.fileFullName,
song: song.title,
album: song.album,
artist: song.artist,
bitrate: String(song.bitrate),
resourceId: song.resourceId
},
onload: (res3) => {
if (res3.code != 200) {
if (song.expireTime < Date.now() || res3.msg && res3.msg.includes("rep create failed")) {
console.error(song.title, "3.提交文件", res3);
this.uploadSongFail(song);
} else {
console.log(song.title, "3.正在转码", res3);
showTips(`(5/6)${song.title} 正在转码...`, 1);
sleep(1e3).then(() => {
this.uploadSongPart3(song);
});
}
return;
}
console.log(song.title, "3.提交文件", res3);
showTips(`(5/6)${song.title} 提交文件完成`, 1);
weapiRequest("/api/cloud/pub/v2", {
data: {
songid: res3.songId
},
onload: (res4) => {
if (res4.code != 200 && res4.code != 201) {
console.error(song.title, "4.发布资源", res4);
this.uploadSongFail(song);
return;
}
console.log(song.title, "4.发布资源", res4);
showTips(`(5/6)${song.title} 提交文件完成`, 1);
song.cloudSongId = res4.privateCloud.songId;
this.uploadSongMatch(song);
},
onerror: (res) => {
console.error(song.title, "4.发布资源", res);
this.uploadSongFail(song);
}
});
},
onerror: (res) => {
console.error(song.title, "3.提交文件", res);
this.uploadSongFail(song);
}
});
} catch (e) {
console.error(e);
this.uploadSongFail(song);
}
}
uploadSongMatch(song) {
if (song.cloudSongId != song.id) {
weapiRequest("/api/cloud/user/song/match", {
data: {
songId: song.cloudSongId,
adjustSongId: song.id
},
onload: (res5) => {
if (res5.code != 200) {
console.error(song.title, "5.匹配歌曲", res5);
this.uploadSongFail(song);
return;
}
console.log(song.title, "5.匹配歌曲", res5);
console.log(song.title, "完成");
showTips(`(6/6)${song.title} 上传完成`, 1);
this.uploadSongSuccess(song);
},
onerror: (res) => {
console.error(song.title, "5.匹配歌曲", res);
this.uploadSongFail(song);
}
});
} else {
console.log(song.title, "完成");
showTips(`${song.title} 上传完成`, 1);
this.uploadSongSuccess(song);
}
}
uploadSongFail(song) {
showTips(`${song.title} 上传失败`, 2);
this.failSongs.push(song);
if (this.onSongDUFail) this.onSongDUFail(song);
this.uploadNextSong();
}
uploadSongSuccess(song) {
if (this.onSongDUSuccess) this.onSongDUSuccess(song);
this.uploadNextSong();
}
uploadNextSong() {
this.currentIndex += 1;
if (this.currentIndex < this.songs.length) {
this.uploadSong(this.songs[this.currentIndex]);
} else {
let msg = this.failSongs == 0 ? `${this.songs[0].title}上传完成` : `${this.songs[0].title}上传失败`;
if (this.songs.length > 1) msg = this.failSongs == 0 ? "全部上传完成" : `上传完毕,存在${this.failSongs.length}首上传失败的歌曲.它们为:${this.failSongs.map((song) => song.title).join()}`;
if (this.showfinishBox) {
showConfirmBox(msg);
}
}
}
}
const cloudUpgrade = (uiArea) => {
let btnUpgrade = createBigButton("云盘音质提升", uiArea, 2);
btnUpgrade.addEventListener("click", ShowCloudUpgradePopUp);
function ShowCloudUpgradePopUp() {
Swal.fire({
title: "云盘音质提升",
input: "select",
inputOptions: { lossless: "无损", hires: "Hi-Res" },
inputPlaceholder: "选择目标音质",
confirmButtonText: "下一步",
showCloseButton: true,
footer: "<div>寻找网易云音源比云盘音质好的歌曲,然后进行删除并重新上传</div><div>⚠️可能出现删除了歌曲但上传失败的情况</div><div>部分歌曲无法判断是否能提升</div>",
inputValidator: (value) => {
if (!value) {
return "请选择目标音质";
}
}
}).then((result) => {
if (result.isConfirmed) {
checkVIPBeforeUpgrade(result.value);
}
});
}
function checkVIPBeforeUpgrade(level) {
weapiRequest(`/api/v1/user/detail/${_unsafeWindow.GUser.userId}`, {
onload: (res) => {
if (res.profile.vipType <= 10) {
showConfirmBox("当前不是会员,无法获取无损以上音源,领取个会员礼品卡再来吧。");
} else {
let upgrade = new Upgrader(level);
upgrade.start();
}
}
});
}
class Upgrader {
constructor(level) {
this.targetLevel = level;
this.targetWeight = levelWeight[level];
this.songs = [];
this.page = {
current: 1,
max: 1,
limitCount: 50
};
this.filter = {
text: "",
songIndexs: []
};
this.batchUpgrade = {
threadMax: 1,
threadCount: 1,
working: false,
finnishThread: 0,
songIndexs: []
};
}
start() {
this.showPopup();
}
showPopup() {
Swal.fire({
showCloseButton: true,
showConfirmButton: false,
width: 800,
html: `<style>
table {
width: 100%;
border-spacing: 0px;
border-collapse: collapse;
}
table th, table td {
text-align: left;
text-overflow: ellipsis;
}
table tbody {
display: block;
width: 100%;
max-height: 400px;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
table thead tr, table tbody tr, table tfoot tr {
box-sizing: border-box;
table-layout: fixed;
display: table;
width: 100%;
}
table tbody tr td{
border-bottom: none;
}
tr th:nth-child(1),tr td:nth-child(1){
width: 8%;
}
tr th:nth-child(2){
width: 35%;
}
tr td:nth-child(2){
width: 10%;
}
tr td:nth-child(3){
width: 25%;
}
tr th:nth-child(3),tr td:nth-child(4){
width: 20%;
}
tr th:nth-child(4),tr td:nth-child(5){
width: 16%;
}
tr th:nth-child(5),tr td:nth-child(6){
width: 16%;
}
</style>
<input id="text-filter" class="swal2-input" type="text" placeholder="歌曲过滤">
<button type="button" class="swal2-confirm swal2-styled" aria-label="" style="display: inline-block;" id="btn-upgrade-batch">全部提升音质</button>
<table border="1" frame="hsides" rules="rows"><thead><tr><th>操作</th><th>歌曲标题</th><th>歌手</th><th>云盘音源</th><th>目标音源</th> </tr></thead><tbody></tbody></table>
`,
footer: "<div></div>",
didOpen: () => {
let container = Swal.getHtmlContainer();
let tbody = container.querySelector("tbody");
let footer = Swal.getFooter();
this.popupObj = {
container,
tbody,
footer
};
let filterInput = container.querySelector("#text-filter");
filterInput.addEventListener("change", () => {
let filtertext = filterInput.value.trim();
if (this.filter.text != filtertext) {
this.filter.text = filtertext;
this.applyFilter();
}
});
let upgrader = this;
this.btnUpgradeBatch = container.querySelector("#btn-upgrade-batch");
this.btnUpgradeBatch.addEventListener("click", () => {
if (this.batchUpgrade.working) {
return;
}
this.batchUpgrade.songIndexs = [];
this.filter.songIndexs.forEach((idx) => {
if (!upgrader.songs[idx].upgraded) {
upgrader.batchUpgrade.songIndexs.push(idx);
}
});
if (this.batchUpgrade.songIndexs.length == 0) {
showTips("没有需要提升的歌曲", 1);
return;
}
this.batchUpgrade.working = true;
this.batchUpgrade.finnishThread = 0;
this.batchUpgrade.threadCount = Math.min(this.batchUpgrade.songIndexs.length, this.batchUpgrade.threadMax);
for (let i = 0; i < this.batchUpgrade.threadCount; i++) {
this.upgradeSong(this.batchUpgrade.songIndexs[i]);
}
});
this.fetchSongInfo();
}
});
}
fetchSongInfo() {
this.popupObj.tbody.innerHTML = "正在查找云盘歌曲...";
this.fetchCloudSongInfoSub(0, []);
}
fetchCloudSongInfoSub(offset, songIds) {
let upgrader = this;
weapiRequest("/api/v1/cloud/get", {
data: {
limit: 1e3,
offset
},
onload: (res) => {
upgrader.popupObj.tbody.innerHTML = `正在搜索第${offset + 1}到${Math.min(offset + 1e3, res.count)}云盘歌曲`;
res.data.forEach((song) => {
if (song.simpleSong.privilege.toast) return;
if (song.simpleSong.privilege.fee == 0 && song.simpleSong.privilege.flLevel == "none") return;
if (song.simpleSong.privilege.fee == 4) return;
if (song.simpleSong.privilege.playMaxBrLevel == "none") return;
let cloudWeight = levelWeight[song.simpleSong.privilege.plLevel] || 0;
if (cloudWeight >= this.targetWeight) return;
songIds.push({ "id": song.simpleSong.id });
upgrader.popupObj.tbody.innerHTML = `正在搜索第${offset + 1}到${Math.min(offset + 1e3, res.count)}云盘歌曲 找到${songIds.length}首可能有提升的歌曲`;
});
if (res.hasMore) {
res = {};
upgrader.fetchCloudSongInfoSub(offset + 1e3, songIds);
} else {
upgrader.filterTargetLevelSongSub(0, songIds);
}
}
});
}
filterTargetLevelSongSub(offset, songIds) {
let upgrader = this;
upgrader.popupObj.tbody.innerHTML = `正在确认${songIds.length}首潜在歌曲是否有目标音质`;
if (offset >= songIds.length) {
upgrader.createTableRow();
upgrader.applyFilter();
return;
}
weapiRequest("/api/v3/song/detail", {
data: {
c: JSON.stringify(songIds.slice(offset, offset + 1e3))
},
onload: function(content) {
let songlen = content.songs.length;
let privilegelen = content.privileges.length;
for (let i = 0; i < songlen; i++) {
for (let j = 0; j < privilegelen; j++) {
if (content.songs[i].id == content.privileges[j].id) {
let songItem = {
id: content.songs[i].id,
name: content.songs[i].name,
album: getAlbumTextInSongDetail(content.songs[i]),
albumid: content.songs[i].al.id || 0,
artists: getArtistTextInSongDetail(content.songs[i]),
tns: content.songs[i].tns ? content.songs[i].tns.join() : "",
//翻译
dt: duringTimeDesc(content.songs[i].dt || 0),
picUrl: content.songs[i].al && content.songs[i].al.picUrl ? content.songs[i].al.picUrl : "http://p4.music.126.net/UeTuwE7pvjBpypWLudqukA==/3132508627578625.jpg",
upgraded: false
};
content.songs[i].pc.br;
if (upgrader.targetLevel == "lossless" && content.songs[i].sq) {
songItem.fileinfo = { originalLevel: content.privileges[j].plLevel, originalBr: content.songs[i].pc.br, tagetBr: Math.round(content.songs[i].sq.br / 1e3) };
if (songItem.fileinfo.originalBr + 10 <= songItem.fileinfo.tagetBr) {
upgrader.songs.push(songItem);
}
} else if (upgrader.targetLevel == "hires" && content.songs[i].hr) {
songItem.fileinfo = { originalLevel: content.privileges[j].plLevel, originalBr: content.songs[i].pc.br, tagetBr: Math.round(content.songs[i].hr.br / 1e3) };
if (songItem.fileinfo.originalBr + 10 <= songItem.fileinfo.tagetBr) {
upgrader.songs.push(songItem);
}
}
break;
}
}
}
upgrader.filterTargetLevelSongSub(offset + 1e3, songIds);
}
});
}
createTableRow() {
let tagetLevelDesc = levelDesc(this.targetLevel);
for (let i = 0; i < this.songs.length; i++) {
let song = this.songs[i];
let tablerow = document.createElement("tr");
tablerow.innerHTML = `<td><button type="button" class="swal2-styled">提升</button></td><td><a href="https://music.163.com/album?id=${song.albumid}" target="_blank"><img src="${song.picUrl}?param=50y50&quality=100" title="${song.album}"></a></td><td><a href="https://music.163.com/song?id=${song.id}" target="_blank">${song.name}</a></td><td>${song.artists}</td><td>${levelDesc(song.fileinfo.originalLevel)} ${song.fileinfo.originalBr}k</td><td>${tagetLevelDesc} ${song.fileinfo.tagetBr}k</td>`;
let btn = tablerow.querySelector("button");
btn.addEventListener("click", () => {
if (this.batchUpgrade.working) {
return;
}
this.upgradeSong(i);
});
song.tablerow = tablerow;
}
}
applyFilter() {
this.filter.songIndexs = [];
let filterText = this.filter.text;
for (let i = 0; i < this.songs.length; i++) {
let song = this.songs[i];
if (filterText.length > 0 && !song.name.includes(filterText) && !song.album.includes(filterText) && !song.artists.includes(filterText) && !song.tns.includes(filterText)) {
continue;
}
this.filter.songIndexs.push(i);
}
this.page.current = 1;
this.page.max = Math.ceil(this.filter.songIndexs.length / this.page.limitCount);
this.renderData();
this.renderFilterInfo();
}
renderData() {
if (this.filter.songIndexs.length == 0) {
this.popupObj.tbody.innerHTML = "内容为空";
this.popupObj.footer.innerHTML = "";
return;
}
this.popupObj.tbody.innerHTML = "";
let songBegin = (this.page.current - 1) * this.page.limitCount;
let songEnd = Math.min(this.filter.songIndexs.length, songBegin + this.page.limitCount);
for (let i = songBegin; i < songEnd; i++) {
this.popupObj.tbody.appendChild(this.songs[this.filter.songIndexs[i]].tablerow);
}
let pageIndexs = [1];
let floor = Math.max(2, this.page.current - 2);
let ceil = Math.min(this.page.max - 1, this.page.current + 2);
for (let i = floor; i <= ceil; i++) {
pageIndexs.push(i);
}
if (this.page.max > 1) {
pageIndexs.push(this.page.max);
}
let upgrader = this;
this.popupObj.footer.innerHTML = "";
pageIndexs.forEach((pageIndex) => {
let pageBtn = document.createElement("button");
pageBtn.setAttribute("type", "button");
pageBtn.className = "swal2-styled";
pageBtn.innerHTML = pageIndex;
if (pageIndex != upgrader.page.current) {
pageBtn.addEventListener("click", () => {
upgrader.page.current = pageIndex;
upgrader.renderData();
});
} else {
pageBtn.style.background = "white";
}
upgrader.popupObj.footer.appendChild(pageBtn);
});
}
renderFilterInfo() {
let sizeTotal = 0;
let countCanUpgrade = 0;
this.filter.songIndexs.forEach((idx) => {
let song = this.songs[idx];
if (!song.upgraded) {
countCanUpgrade += 1;
sizeTotal += song.size;
}
});
this.btnUpgradeBatch.innerHTML = "全部提升";
if (countCanUpgrade > 0) {
this.btnUpgradeBatch.innerHTML += ` (${countCanUpgrade}首)`;
}
}
upgradeSong(songIndex) {
let song = this.songs[songIndex];
let upgrade = this;
try {
weapiRequest("/api/cloud/del", {
data: {
songIds: [song.id]
},
onload: (content) => {
console.log(content);
if (content.code == 200) {
showTips(`${song.name}删除成功`, 1);
}
let songItem = { api: { url: "/api/song/enhance/player/url/v1", data: { ids: JSON.stringify([song.id]), level: upgrade.targetLevel, encodeType: "mp3" } }, id: song.id, title: song.name, artist: song.artists, album: song.album, songIndex, Upgrader: this };
let ULobj = new ncmDownUpload([songItem], false, this.onUpgradeSucess, this.onUpgradeFail);
ULobj.startUpload();
}
});
} catch (e) {
console.error(e);
upgrade.onUpgradeFail(songIndex);
}
}
onUpgradeFail(ULsong) {
let song = ULsong.Upgrader.songs[ULsong.songIndex];
showTips(`${song.name} 音质提升失败`, 2);
ULsong.Upgrader.onUpgradeFinnsh(ULsong.songIndex);
}
onUpgradeSucess(ULsong) {
let song = ULsong.Upgrader.songs[ULsong.songIndex];
showTips(`${song.name} 音质提升成功`, 1);
song.upgraded = true;
let btnUpgrade2 = song.tablerow.querySelector("button");
btnUpgrade2.innerHTML = "已提升";
btnUpgrade2.disabled = "disabled";
ULsong.Upgrader.onUpgradeFinnsh(ULsong.songIndex);
}
onUpgradeFinnsh(songIndex) {
if (this.batchUpgrade.working) {
let batchSongIdx = this.batchUpgrade.songIndexs.indexOf(songIndex);
if (batchSongIdx + this.batchUpgrade.threadCount < this.batchUpgrade.songIndexs.length) {
this.upgradeSong(this.batchUpgrade.songIndexs[batchSongIdx + this.batchUpgrade.threadCount]);
} else {
this.batchUpgrade.finnishThread += 1;
if (this.batchUpgrade.finnishThread == this.batchUpgrade.threadCount) {
this.batchUpgrade.working = false;
this.renderFilterInfo();
showTips("歌曲提升完成", 1);
}
}
} else {
this.renderFilterInfo();
}
}
}
};
const cloudLocalUpload = (uiArea) => {
let btnLocalUpload = createBigButton("云盘本地上传", uiArea, 2);
btnLocalUpload.addEventListener("click", ShowLocalUploadPopUp);
function ShowLocalUploadPopUp() {
Swal.fire({
title: "云盘本地上传",
html: `<div id="my-file">
<input id='song-file' type="file" accept="audio/*" multiple="multiple" class="swal2-file" placeholder="" style="display: flex;">
</div>
<div id="my-rd">
<div class="swal2-radio"">
<label><input type="radio" name="file-info" value="autofill" checked><span class="swal2-label">直接上传</span></label>
<label><input type="radio" name="file-info" value="needInput" id="need-fill-info-radio"><span class="swal2-label">先填写文件的歌手、专辑信息</span></label>
</div>
</div>`,
confirmButtonText: "上传",
showCloseButton: true,
preConfirm: (level) => {
let files = document.getElementById("song-file").files;
if (files.length == 0) return Swal.showValidationMessage("请选择文件");
return {
files,
needFillInfo: document.getElementById("need-fill-info-radio").checked
};
}
}).then((result) => {
if (result.isConfirmed) {
new LocalUpload().start(result.value);
}
});
}
class LocalUpload {
start(config) {
this.files = config.files;
this.needFillInfo = config.needFillInfo;
this.task = [];
this.currentIndex = 0;
this.failIndexs = [];
for (let i = 0; i < config.files.length; i++) {
let file = config.files[i];
let fileName = file.name;
let song = {
id: -2,
songFile: file,
fileFullName: fileName,
title: fileName.slice(0, fileName.lastIndexOf(".")),
artist: "未知",
album: "未知",
size: file.size,
ext: fileName.slice(fileName.lastIndexOf(".") + 1),
bitrate: 128
};
this.task.push(song);
}
showTips(`开始获取文件中的标签信息`, 1);
this.readFileTags(0);
}
readFileTags(songIndex) {
if (songIndex >= this.task.length) {
if (this.needFillInfo) {
this.showFillSongInforBox();
} else {
this.localUploadPart1(0);
}
return;
}
let fileData = this.task[songIndex].songFile;
new jsmediatags.Reader(fileData).read({
onSuccess: (res) => {
if (res.tags.title) this.task[songIndex].title = res.tags.title;
if (res.tags.artist) this.task[songIndex].artist = res.tags.artist;
if (res.tags.album) this.task[songIndex].album = res.tags.album;
this.readFileTags(songIndex + 1);
},
onError: (error) => {
this.readFileTags(songIndex + 1);
}
});
}
showFillSongInforBox() {
Swal.fire({
html: `<style>
table {
width: 100%;
border-spacing: 0px;
border-collapse: collapse;
}
table th, table td {
text-align: left;
text-overflow: ellipsis;
}
table tbody {
display: block;
width: 100%;
max-height: 400px;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
table thead tr, table tbody tr, table tfoot tr {
box-sizing: border-box;
table-layout: fixed;
display: table;
width: 100%;
}
table tbody tr td{
border-bottom: none;
}
tr th:nth-child(1),tr td:nth-child(1){
width: 16%;
}
tr th:nth-child(2),tr td:nth-child(2){
width: 30%;
}
tr th:nth-child(3),tr td:nth-child(3){
width: 27%;
}
tr th:nth-child(4),tr td:nth-child(4){
width: 27%;
}
</style>
<table border="1" frame="hsides" rules="rows"><thead><tr><th>操作</th><th>歌曲标题</th><th>歌手</th><th>专辑</th></tr></thead><tbody></tbody></table>
`,
confirmButtonText: "上传",
allowOutsideClick: false,
allowEscapeKey: false,
showCloseButton: false,
didOpen: () => {
let container = Swal.getHtmlContainer();
let tbody = container.querySelector("tbody");
for (let i = 0; i < this.task.length; i++) {
let tablerow = document.createElement("tr");
tablerow.innerHTML = `<td><button type="button" class="swal2-styled my-edit">编辑</button></td><td>${this.task[i].title}</td><td>${this.task[i].artist}</td><td>${this.task[i].album}</td>`;
let btnEdit = tablerow.querySelector(".my-edit");
btnEdit.addEventListener("click", () => {
this.showEditInforBox(i);
});
tbody.appendChild(tablerow);
}
}
}).then((result) => {
if (result.isConfirmed) {
this.localUploadPart1(0);
}
});
}
showEditInforBox(songIndex) {
Swal.fire({
title: this.task[songIndex].fileFullName,
html: `<div><label for="text-title">歌名</label><input class="swal2-input" id="text-title" type="text" value="${this.task[songIndex].title}"></div>
<div><label for="text-artist">歌手</label><input class="swal2-input" id="text-artist" type="text" value="${this.task[songIndex].artist}"></div>
<div><label for="text-album">专辑</label><input class="swal2-input" id="text-album" type="text" value="${this.task[songIndex].album}"></div>`,
allowOutsideClick: false,
allowEscapeKey: false,
showCloseButton: false,
confirmButtonText: "确定",
preConfirm: () => {
let songTitle = document.getElementById("text-title").value.trim();
if (songTitle.length == 0) return Swal.showValidationMessage("歌名不能为空");
return {
title: songTitle,
artist: document.getElementById("text-artist").value.trim(),
album: document.getElementById("text-album").value.trim()
};
}
}).then((result) => {
if (result.isConfirmed) {
this.task[songIndex].title = result.value.title;
this.task[songIndex].artist = result.value.artist;
this.task[songIndex].album = result.value.album;
this.showFillSongInforBox();
}
});
}
localUploadPart1(songindex) {
let self = this;
let song = self.task[songindex];
let reader = new FileReader();
let chunkSize = 1024 * 1024;
let loaded = 0;
let md5sum = _unsafeWindow.CryptoJS.algo.MD5.create();
showTips(`(1/5)${song.title} 正在获取文件MD5值`, 1);
reader.onload = function(e) {
md5sum.update(_unsafeWindow.CryptoJS.enc.Latin1.parse(reader.result));
loaded += e.loaded;
if (loaded < song.size) {
readBlob(loaded);
} else {
showTips(`(1/5)${song.title} 已计算文件MD5值`, 1);
song.md5 = md5sum.finalize().toString();
try {
weapiRequest("/api/cloud/upload/check", {
data: {
songId: 0,
md5: song.md5,
length: song.size,
ext: song.ext,
version: 1,
bitrate: song.bitrate
},
onload: (res1) => {
console.log(song.title, "1.检查资源", res1);
if (res1.code != 200) {
console.error(song.title, "1.检查资源", res1);
self.uploadFail();
return;
}
song.cloudId = res1.songId;
song.needUpload = res1.needUpload;
weapiRequest("/api/nos/token/alloc", {
data: {
filename: song.title,
length: song.size,
ext: song.ext,
type: "audio",
bucket: "jd-musicrep-privatecloud-audio-public",
local: false,
nos_product: 3,
md5: song.md5
},
onload: (res2) => {
if (res2.code != 200) {
console.error(song.title, "2.获取令牌", res2);
self.uploadFail();
return;
}
song.resourceId = res2.result.resourceId;
song.token = res2.result.token;
song.objectKey = res2.result.objectKey;
showTips(`(3/5)${song.title} 开始上传文件`, 1);
console.log(song.title, "2.获取令牌", res2);
if (res1.needUpload) {
self.localUploadFile(songindex, 0);
} else {
song.expireTime = Date.now() + 6e4;
self.localUploadPart2(songindex);
}
},
onerror: (res) => {
console.error(song.title, "2.获取令牌", res);
self.uploadFail();
}
});
},
onerror: (res) => {
console.error(song.title, "1.检查资源", res);
self.uploadFail();
}
});
} catch (e2) {
console.error(e2);
self.uploadFail();
}
}
};
readBlob(0);
function readBlob(offset) {
let blob = song.songFile.slice(offset, offset + chunkSize);
reader.readAsBinaryString(blob);
}
}
localUploadFile(songindex, offset, context = null) {
let self = this;
let song = self.task[songindex];
try {
let complete = offset + uploadChunkSize > song.size;
let url2 = `http://45.127.129.8/jd-musicrep-privatecloud-audio-public/${encodeURIComponent(song.objectKey)}?offset=${offset}&complete=${String(complete)}&version=1.0`;
if (context) url2 += `&context=${context}`;
GM_xmlhttpRequest({
method: "POST",
url: url2,
headers: {
"x-nos-token": song.token,
"Content-MD5": song.md5,
"Content-Type": "audio/mpeg"
},
data: song.songFile.slice(offset, offset + uploadChunkSize),
onload: (response3) => {
let res = JSON.parse(response3.response);
if (complete) {
console.log(song.title, "2.5.上传文件完成", res);
showTips(`(3.5/5)${song.title} 上传文件完成`, 1);
song.expireTime = Date.now() + 6e4;
self.localUploadPart2(songindex);
} else {
showTips(`(3.4/5)${song.title} 正在上传${fileSizeDesc(res.offset)}/${fileSizeDesc(song.size)}`, 1);
self.localUploadFile(songindex, res.offset, res.context);
}
},
onerror: (response3) => {
console.error(song.title, "文件上传时失败", response3);
self.uploadFail();
}
});
} catch (e) {
console.error(e);
self.uploadFail();
}
}
localUploadPart2(songindex) {
let self = this;
let song = self.task[songindex];
try {
weapiRequest("/api/upload/cloud/info/v2", {
data: {
md5: song.md5,
songid: song.cloudId,
filename: song.fileFullName,
song: song.title,
album: song.album,
artist: song.artist,
bitrate: String(song.bitrate),
resourceId: song.resourceId
},
onload: (res3) => {
if (res3.code != 200) {
if (song.expireTime < Date.now() || res3.msg && res3.msg.includes("rep create failed")) {
console.error(song.title, "3.提交文件", res3);
self.uploadFail();
} else {
console.log(song.title, "3.正在转码", res3);
showTips(`(4/5)${song.title} 正在转码...`, 1);
sleep(1e3).then(() => {
self.localUploadPart2(songindex);
});
}
return;
}
console.log(song.title, "3.提交文件", res3);
showTips(`(4/5)${song.title} 提交文件完成`, 1);
weapiRequest("/api/cloud/pub/v2", {
data: {
songid: res3.songId
},
onload: (res4) => {
if (res4.code != 200 && res4.code != 201) {
console.error(song.title, "4.发布资源", res4);
self.uploadFail();
return;
}
showTips(`(5/5)${song.title} 上传完成`, 1);
self.uploadSuccess();
},
onerror: (res) => {
console.error(song.title, "4.发布资源", res);
self.uploadFail();
}
});
},
onerror: (res) => {
console.error(song.title, "3.提交文件", res);
self.uploadFail();
}
});
} catch (e) {
console.error(e);
self.uploadFail();
}
}
uploadFail() {
this.failIndexs.push(this.currentIndex);
showTips(`${this.task[this.currentIndex].title}上传失败`, 2);
this.uploadNext();
}
uploadSuccess() {
this.uploadNext();
}
uploadNext() {
this.currentIndex += 1;
if (this.currentIndex >= this.task.length) {
this.uploadFinnsh();
} else {
this.localUploadPart1(this.currentIndex);
}
}
uploadFinnsh() {
let msg = "上传完成";
if (this.failIndexs.length > 0) {
msg += ",以下文件上传失败:";
msg += this.failIndexs.map((idx) => this.task[idx].fileFullName).join();
}
showConfirmBox(msg);
}
}
};
const freeVIPSong = (uiArea) => {
let btnVIPfreeA = createBigButton("限免VIP歌曲A", uiArea, 2);
btnVIPfreeA.addEventListener("click", VIPfreeA);
function VIPfreeA() {
weapiRequest("/api/homepage/block/page", {
data: {
cursor: JSON.stringify({ offset: 0, blockCodeOrderList: ["HOMPAGE_BLOCK_VIP_RCMD"] }),
refresh: true,
extInfo: JSON.stringify({ refreshType: 1, abInfo: { "hp-new-homepageV3.1": "t3" }, netstate: 1 })
},
clientType: "android",
onload: (res) => {
let songList = res.data.blocks[0].resourceIdList.map((item) => {
return {
"id": Number(item)
};
});
openVIPDownLoadPopup(songList, "APP发现页「免费听VIP歌曲」的内容", 23);
}
});
}
let btnVIPfreeB = createBigButton("限免VIP歌曲B", uiArea, 2);
btnVIPfreeB.addEventListener("click", VIPfreeB);
function VIPfreeB() {
weapiRequest("/api/v6/playlist/detail", {
data: {
id: 8402996200,
n: 1e5,
s: 8
},
onload: (res) => {
let songList = res.playlist.trackIds.map((item) => {
return {
"id": Number(item.id)
};
});
openVIPDownLoadPopup(songList, '歌单<a href="https://music.163.com/#/playlist?id=8402996200" target="_blank">「会员雷达」</a>的内容', 22);
}
});
}
function openVIPDownLoadPopup(songIds, footer, trialMode) {
Swal.fire({
title: "限免VIP歌曲",
showCloseButton: true,
showConfirmButton: false,
width: 800,
html: `<style>
table {
width: 100%;
border-spacing: 0px;
border-collapse: collapse;
}
table th, table td {
text-align: left;
text-overflow: ellipsis;
}
table tbody {
display: block;
width: 100%;
max-height: 400px;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
table thead tr, table tbody tr, table tfoot tr {
box-sizing: border-box;
table-layout: fixed;
display: table;
width: 100%;
}
table tbody tr td{
border-bottom: none;
}
tr th:nth-child(1),tr td:nth-child(1){
width: 16%;
}
tr th:nth-child(2){
width: 48%;
}
tr td:nth-child(2){
width: 10%;
}
tr td:nth-child(3){
width: 30%;
}
tr th:nth-child(3),tr td:nth-child(4){
width: 28%;
}
tr th:nth-child(4),tr td:nth-child(5){
width: 8%;
}
tr th:nth-child(5),tr td:nth-child(6){
width: 8%;
}
</style>
<table border="1" frame="hsides" rules="rows"><thead><tr><th>操作</th><th>歌曲标题</th><th>歌手</th><th>时长</th><th>大小</th> </tr></thead><tbody></tbody></table>
`,
footer: footer + ',只有标准(128k)音质<a href="https://github.com/Cinvin/myuserscripts" target="_blank"><img src="https://img.shields.io/github/stars/cinvin/myuserscripts?style=social" alt="Github"></a>',
didOpen: () => {
let container = Swal.getHtmlContainer();
Swal.getFooter();
let tbody = container.querySelector("tbody");
weapiRequest("/api/v3/song/detail", {
data: {
c: JSON.stringify(songIds)
},
onload: function(content) {
let songlen = content.songs.length;
let privilegelen = content.privileges.length;
for (let i = 0; i < songlen; i++) {
for (let j = 0; j < privilegelen; j++) {
if (content.songs[i].id == content.privileges[j].id) {
if (content.privileges[j].cs) {
break;
}
let songArtist = content.songs[i].ar ? content.songs[i].ar.map((ar) => `<a target="_blank" href="https://music.163.com/artist?id=${ar.id}">${ar.name}<a>`).join() : "";
let songTitle = content.songs[i].name;
let filename = nameFileWithoutExt(songTitle, songArtist, "artist-title");
let tablerow = document.createElement("tr");
tablerow.innerHTML = `<td><button type="button" class="swal2-styled mydl">下载</button><button type="button" class="swal2-styled myul">上传</button></td><td><a href="https://music.163.com/album?id=${content.songs[i].al.id}" target="_blank"><img src="${content.songs[i].al.picUrl}?param=50y50&quality=100" title="${getAlbumTextInSongDetail(content.songs[i])}"></a></td><td><a href="https://music.163.com/song?id=${content.songs[i].id}" target="_blank">${content.songs[i].name}</a></td><td>${songArtist}</td><td>${duringTimeDesc(content.songs[i].dt || 0)}</td><td>${fileSizeDesc(content.songs[i].l.size)}</td>`;
let btnDL = tablerow.querySelector(".mydl");
btnDL.addEventListener("click", () => {
TrialDownLoad(content.songs[i].id, trialMode, filename);
});
let btnUL = tablerow.querySelector(".myul");
btnUL.addEventListener("click", () => {
let songItem = { api: { url: "/api/song/enhance/player/url/v1", data: { ids: JSON.stringify([content.songs[i].id]), trialMode, level: "exhigh", encodeType: "mp3" } }, id: content.songs[i].id, title: content.songs[i].name, artist: getArtistTextInSongDetail(content.songs[i]), album: getAlbumTextInSongDetail(content.songs[i]) };
let ULobj = new ncmDownUpload([songItem], false);
ULobj.startUpload();
});
tbody.appendChild(tablerow);
break;
}
}
}
}
});
}
});
}
function TrialDownLoad(songId, trialMode, filename) {
weapiRequest("/api/song/enhance/player/url/v1", {
data: {
immerseType: "ste",
trialMode,
ids: JSON.stringify([songId]),
level: "exhigh",
encodeType: "mp3"
},
onload: (content) => {
if (content.data[0].url != null) {
GM_download({
url: content.data[0].url,
name: filename + "." + content.data[0].type.toLowerCase(),
onload: function() {
},
onerror: function(e) {
console.error(e);
showTips("下载失败", 2);
}
});
} else {
showTips("下载失败", 2);
}
}
});
}
};
const cloudExport = (uiArea) => {
let btnExport = createBigButton("云盘导出", uiArea, 2);
btnExport.addEventListener("click", openExportPopup);
function openExportPopup() {
Swal.fire({
title: "云盘导出",
showCloseButton: true,
html: `<div><label>歌手<input class="swal2-input" id="text-artist" placeholder="选填" type="text"></label></div>
<div><label>专辑<input class="swal2-input" id="text-album" placeholder="选填" type="text"></label></div>
<div><label>歌名<input class="swal2-input" id="text-song" placeholder="选填" type="text"></label></div>
<div><label>歌单ID<input class="swal2-input" id="text-playlistid" placeholder="选填" type="number"></label></div>`,
footer: "过滤条件取交集",
confirmButtonText: "导出",
preConfirm: () => {
return [
document.getElementById("text-artist").value.trim(),
document.getElementById("text-album").value.trim(),
document.getElementById("text-song").value.trim(),
document.getElementById("text-playlistid").value
];
}
}).then((result) => {
if (result.isConfirmed) {
exportCloud(result.value);
}
});
}
function exportCloud(filter) {
showTips("开始导出", 1);
if (filter[3]) {
exportCloudByPlaylist(filter);
} else {
exportCloudSub(filter, {
data: []
}, 0);
}
}
function exportCloudSub(filter, config, offset) {
weapiRequest("/api/v1/cloud/get", {
data: {
limit: 1e3,
offset
},
onload: (res) => {
showTips(`正在获取第${offset + 1}到${Math.min(offset + 1e3, res.count)}首云盘歌曲信息`, 1);
let matchSongs = [];
res.data.forEach((song) => {
if (song.simpleSong.al && song.simpleSong.al.id > 0) {
if (filter[0].length > 0) {
let flag = false;
for (let i = 0; i < song.simpleSong.ar.length; i++) {
if (song.simpleSong.ar[i].name == filter[0]) {
flag = true;
break;
}
}
if (!flag) {
return;
}
}
if (filter[1].length > 0 && filter[1] != getAlbumTextInSongDetail(song.simpleSong)) {
return;
}
if (filter[2].length > 0 && filter[2] != song.simpleSong.name) {
return;
}
let songItem = {
"id": song.songId,
"size": song.fileSize,
"ext": song.fileName.split(".").pop().toLowerCase(),
"bitrate": song.bitrate,
"md5": null
};
matchSongs.push(songItem);
} else {
if (filter[0].length > 0 && song.artist != filter[0]) {
return;
}
if (filter[1].length > 0 && song.album != filter[1]) {
return;
}
if (filter[2].length > 0 && song.songName != filter[2]) {
return;
}
let songItem = {
"id": song.songId,
"size": song.fileSize,
"ext": song.fileName.split(".").pop().toLowerCase(),
"bitrate": song.bitrate,
"md5": null,
"name": song.songName,
"al": song.album,
"ar": song.artist
};
matchSongs.push(songItem);
}
});
let ids = matchSongs.map((song) => song.id);
if (ids.length > 0) {
weapiRequest("/api/song/enhance/player/url/v1", {
data: {
ids: JSON.stringify(ids),
level: "hires",
encodeType: "mp3"
},
onload: (res2) => {
if (res2.code != 200) {
exportCloudSub(filter, config, offset);
return;
}
matchSongs.forEach((song) => {
let songId = song.id;
for (let i = 0; i < res2.data.length; i++) {
if (res2.data[i].id == songId) {
song.md5 = res2.data[i].md5;
config.data.push(song);
break;
}
}
});
if (res.hasMore) {
exportCloudSub(filter, config, offset + 1e3);
} else {
configToFile(config);
}
}
});
} else {
if (res.hasMore) {
exportCloudSub(filter, config, offset + 1e3);
} else {
configToFile(config);
}
}
}
});
}
function exportCloudByPlaylist(filter) {
weapiRequest("/api/v6/playlist/detail", {
data: {
id: filter[3],
n: 1e5,
s: 8
},
onload: (res) => {
let trackIds = res.playlist.trackIds.map((item) => {
return item.id;
});
exportCloudByPlaylistSub(filter, trackIds, {
data: []
}, 0);
}
});
}
function exportCloudByPlaylistSub(filter, trackIds, config, offset) {
let limit = 100;
if (trackIds.length <= offset) {
configToFile(config);
return;
}
showTips(`正在获取第${offset + 1}到${Math.min(offset + limit, trackIds.length)}首云盘歌曲信息`, 1);
weapiRequest("/api/v1/cloud/get/byids", {
data: {
songIds: JSON.stringify(trackIds.slice(offset, offset + limit))
},
onload: function(res) {
let matchSongs = [];
res.data.forEach((song) => {
if (song.simpleSong.al && song.simpleSong.al.id > 0) {
if (filter[0].length > 0) {
let flag = false;
for (let i = 0; i < song.simpleSong.ar.length; i++) {
if (song.simpleSong.ar[i].name == filter[0]) {
flag = true;
break;
}
}
if (!flag) {
return;
}
}
if (filter[1].length > 0 && filter[1] != getAlbumTextInSongDetail(song.simpleSong)) {
return;
}
if (filter[2].length > 0 && filter[2] != song.simpleSong.name) {
return;
}
let songItem = {
"id": song.songId,
"size": song.fileSize,
"ext": song.fileName.split(".").pop().toLowerCase(),
"bitrate": song.bitrate,
"md5": null
};
matchSongs.push(songItem);
} else {
if (filter[0].length > 0 && song.artist != filter[0]) {
return;
}
if (filter[1].length > 0 && song.album != filter[1]) {
return;
}
if (filter[2].length > 0 && song.songName != filter[2]) {
return;
}
let songItem = {
"id": song.songId,
"size": song.fileSize,
"ext": song.fileName.split(".").pop().toLowerCase(),
"bitrate": song.bitrate,
"md5": null,
"name": song.songName,
"al": song.album,
"ar": song.artist
};
matchSongs.push(songItem);
}
});
let ids = matchSongs.map((song) => song.id);
if (ids.length > 0) {
weapiRequest("/api/song/enhance/player/url/v1", {
data: {
ids: JSON.stringify(ids),
level: "hires",
encodeType: "mp3"
},
onload: (res2) => {
if (res2.code != 200) {
exportCloudByPlaylistSub(filter, trackIds, config, offset);
return;
}
matchSongs.forEach((song) => {
let songId = song.id;
for (let i = 0; i < res2.data.length; i++) {
if (res2.data[i].id == songId) {
song.md5 = res2.data[i].md5;
config.data.push(song);
break;
}
}
});
exportCloudByPlaylistSub(filter, trackIds, config, offset + limit);
}
});
} else {
exportCloudByPlaylistSub(filter, trackIds, config, offset + limit);
}
}
});
}
function configToFile(config) {
let content = JSON.stringify(config);
let temp = document.createElement("a");
let data = new Blob([content], {
type: "type/plain"
});
let fileurl = URL.createObjectURL(data);
temp.href = fileurl;
temp.download = "网易云云盘信息.json";
temp.click();
URL.revokeObjectURL(data);
showTips(`导出云盘信息完成,共${config.data.length}首歌曲`, 1);
}
};
const cloudImport = (uiArea) => {
let btnImport = createBigButton("云盘导入", uiArea, 2);
btnImport.addEventListener("click", openImportPopup);
function openImportPopup() {
Swal.fire({
title: "云盘导入",
input: "file",
inputAttributes: {
"accept": "application/json",
"aria-label": "选择文件"
},
confirmButtonText: "导入"
}).then((result) => {
if (result.isConfirmed) {
importCloud(result.value);
}
});
}
function importCloud(file) {
let reader = new FileReader();
reader.readAsText(file);
reader.onload = (e) => {
let uploader = new Uploader(JSON.parse(e.target.result), true);
uploader.start();
};
}
};
const myHomeMain = (userId) => {
const isUserHome = userId === unsafeWindow.GUser.userId;
let editArea = document.querySelector("#head-box > dd > div.name.f-cb > div > div.edit");
if (isUserHome && editArea) {
cloudUpload(editArea);
cloudMatch(editArea);
cloudUpgrade(editArea);
cloudLocalUpload(editArea);
freeVIPSong(editArea);
cloudExport(editArea);
cloudImport(editArea);
}
};
const extractLrcRegex = /^(?<lyricTimestamps>(?:\[.+?\])+)(?!\[)(?<content>.+)$/gm;
const extractTimestampRegex = /\[(?<min>\d+):(?<sec>\d+)(?:\.|:)*(?<ms>\d+)*\]/g;
const combineLyric = (lyricOri, lyricAdd) => {
let resLyric = {
lyric: "",
parsedLyric: lyricOri.parsedLyric.slice(0)
};
for (const parsedAddLyric of lyricAdd.parsedLyric) {
resLyric.parsedLyric.splice(parsedLyricsBinarySearch(parsedAddLyric, resLyric.parsedLyric), 0, parsedAddLyric);
}
resLyric.lyric = resLyric.parsedLyric.map((lyric) => lyric.line).join("\n");
return resLyric;
};
const parseLyric = (lrc) => {
const parsedLyrics = [];
for (const line of lrc.trim().matchAll(extractLrcRegex)) {
const { lyricTimestamps, content } = line.groups;
for (const timestamp of lyricTimestamps.matchAll(extractTimestampRegex)) {
const { min, sec, ms } = timestamp.groups;
const rawTime = timestamp[0];
const time = Number(min) * 60 + Number(sec) + Number((ms ?? "000").padEnd(3, "0")) * 1e-3;
const parsedLyric = { rawTime, time, content: trimLyricContent(content), line: line[0] };
parsedLyrics.splice(parsedLyricsBinarySearch(parsedLyric, parsedLyrics), 0, parsedLyric);
}
}
return parsedLyrics;
};
const parsedLyricsBinarySearch = (lyric, lyrics) => {
let time = lyric.time;
let low = 0;
let high = lyrics.length - 1;
while (low <= high) {
const mid = Math.floor((low + high) / 2);
const midTime = lyrics[mid].time;
if (midTime === time) {
return mid;
} else if (midTime < time) {
low = mid + 1;
} else {
high = mid - 1;
}
}
return low;
};
const trimLyricContent = (content) => {
let t = content.trim();
return t.length < 1 ? content : t;
};
const handleLyric = (lyricRes) => {
var _a, _b, _c;
if (lyricRes.pureMusic || lyricRes.needDesc) return {
orilrc: {
lyric: "",
parsedLyric: []
}
};
const lrc = ((_a = lyricRes == null ? void 0 : lyricRes.lrc) == null ? void 0 : _a.lyric) || "";
const rlrc = ((_b = lyricRes == null ? void 0 : lyricRes.romalrc) == null ? void 0 : _b.lyric) || "";
const tlrc = ((_c = lyricRes == null ? void 0 : lyricRes.tlyric) == null ? void 0 : _c.lyric) || "";
let LyricObj = {
orilrc: {
lyric: lrc,
parsedLyric: parseLyric(lrc)
},
romalrc: {
lyric: rlrc,
parsedLyric: parseLyric(rlrc)
},
tlyriclrc: {
lyric: tlrc,
parsedLyric: parseLyric(tlrc)
}
};
if (LyricObj.orilrc.parsedLyric.length > 0 && LyricObj.tlyriclrc.parsedLyric.length > 0) {
LyricObj.oritlrc = combineLyric(LyricObj.tlyriclrc, LyricObj.orilrc);
}
if (LyricObj.orilrc.parsedLyric.length > 0 && LyricObj.romalrc.parsedLyric.length > 0) {
LyricObj.oriromalrc = combineLyric(LyricObj.orilrc, LyricObj.romalrc);
}
return LyricObj;
};
const batchDownloadSongs = (songList, config) => {
if (songList.length == 0) {
showConfirmBox("没有可下载的歌曲");
return;
}
Swal.fire({
title: "批量下载",
allowOutsideClick: false,
allowEscapeKey: false,
showCloseButton: false,
showConfirmButton: false,
width: 800,
html: `<style>
table {
width: 100%;
border-spacing: 0px;
border-collapse: collapse;
}
table th, table td {
text-align: left;
text-overflow: ellipsis;
}
table tbody {
display: block;
width: 100%;
max-height: 400px;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
table thead tr, table tbody tr, table tfoot tr {
box-sizing: border-box;
table-layout: fixed;
display: table;
width: 100%;
}
table tbody tr td{
border-bottom: none;
}
tr th:nth-child(1),tr td:nth-child(1){
width: 26%;
}
tr th:nth-child(2),tr td:nth-child(2){
width: 22%;
}
tr th:nth-child(3),tr td:nth-child(3){
width: 22%;
}
tr th:nth-child(4),tr td:nth-child(4){
width: 10%;
}
tr th:nth-child(5),tr td:nth-child(5){
width: 10%;
}
tr th:nth-child(6),tr td:nth-child(6){
width: 10%;
}
</style>
<table border="1" frame="hsides" rules="rows"><thead><tr><th>歌曲标题</th><th>歌手</th><th>专辑</th><th>音质</th><th>大小</th><th>进度</th> </tr></thead><tbody></tbody></table>
`,
footer: "<div></div>",
didOpen: () => {
let container = Swal.getHtmlContainer();
let tbodyDOM = container.querySelector("tbody");
let threadList = [];
for (let i = 0; i < config.threadCount; i++) {
let trDOM = document.createElement("tr");
tbodyDOM.appendChild(trDOM);
threadList.push({ tableRowDOM: trDOM, working: true });
}
config.finnshCount = 0;
config.errorSongs = [];
config.skipSongs = [];
config.taskCount = songList.length;
config.threadList = threadList;
for (let i = 0; i < config.threadCount; i++) {
downloadSongSub(i, songList, config);
}
}
});
};
const downloadSongSub = (threadIndex, songList, config) => {
let song = songList.shift();
let tableRowDOM = config.threadList[threadIndex].tableRowDOM;
if (song == void 0) {
config.threadList[threadIndex].working = false;
let allFinnsh = true;
for (let i = 0; i < config.threadCount; i++) {
if (config.threadList[i].working) {
allFinnsh = false;
break;
}
}
if (allFinnsh) {
let finnshText = "下载完成";
if (config.skipSongs.length > 0) {
finnshText += `
有${config.skipSongs.length}首歌曲不是目标音质,未进行下载。`;
}
if (config.errorSongs.length > 0) {
finnshText += `
以下${config.errorSongs.length}首歌曲下载失败: ${config.errorSongs.map((song2) => `<a href="https://music.163.com/#/song?id=${song2.id}">${song2.title}</a>`).join()}`;
}
Swal.update({
allowOutsideClick: true,
allowEscapeKey: true,
showCloseButton: true,
showConfirmButton: true,
html: finnshText
});
}
return;
}
tableRowDOM.innerHTML = `<td>${song.title}</td><td>${song.artist}</td><td>${song.album}</td><td class='my-level'></td><td class='my-size'></td><td class='my-pr'></td>`;
let levelText = tableRowDOM.querySelector(".my-level");
let sizeText = tableRowDOM.querySelector(".my-size");
let prText = tableRowDOM.querySelector(".my-pr");
try {
weapiRequest(song.api.url, {
data: song.api.data,
onload: (content) => {
let resData = content.data[0] || content.data;
if (resData.url != null) {
if (config.targetLevelOnly && config.level != resData.level) {
prText.innerHTML = `跳过下载`;
config.skipSongs.push(song);
downloadSongSub(threadIndex, songList, config);
return;
}
let fileName = nameFileWithoutExt(song.title, song.artist, config.out).replace("/", "/");
let fileFullName = fileName + "." + resData.type.toLowerCase();
let folder = "";
if (config.folder != "none" && song.artist.length > 0) {
folder = song.artist.replace("/", "/") + "/";
}
if (config.folder == "artist-album" && song.album.length > 0) {
folder += song.album.replace("/", "/") + "/";
}
fileFullName = folder + fileFullName;
let dlUrl = resData.url;
levelText.innerHTML = levelDesc(resData.level);
sizeText.innerHTML = fileSizeDesc(resData.size);
GM_download({
url: dlUrl,
name: fileFullName,
onprogress: function(e) {
prText.innerHTML = `${fileSizeDesc(e.loaded)}`;
},
onload: function() {
config.finnshCount += 1;
Swal.getFooter().innerHTML = `已完成: ${config.finnshCount} 总共: ${config.taskCount}`;
prText.innerHTML = `完成`;
if (config.downloadLyric) {
downloadSongLyric(song.id, folder + fileName);
}
downloadSongSub(threadIndex, songList, config);
},
onerror: function(e) {
if (song.retry) {
prText.innerHTML = `下载出错`;
config.errorSongs.push(song);
} else {
prText.innerHTML = `下载出错 稍后重试`;
song.retry = true;
songList.push(song);
}
console.error(e, dlUrl, fileFullName);
downloadSongSub(threadIndex, songList, config);
}
});
} else {
showTips(`${song.title} 无法下载`, 2);
prText.innerHTML = `无法下载`;
config.errorSongs.push(song);
downloadSongSub(threadIndex, songList, config);
}
},
onerror: (res) => {
console.error(res);
if (song.retry) {
prText.innerHTML = `下载出错`;
config.errorSongs.push(song);
} else {
prText.innerHTML = `下载出错 稍后重试`;
song.retry = true;
songList.push(song);
}
downloadSongSub(threadIndex, songList, config);
}
});
} catch (e) {
console.error(e);
if (song.retry) {
prText.innerHTML = `下载出错`;
config.errorSongs.push(song);
} else {
prText.innerHTML = `下载出错 稍后重试`;
song.retry = true;
songList.push(song);
}
downloadSongSub(threadIndex, songList, config);
}
};
const downloadSongLyric = (songId, fileName) => {
weapiRequest("/api/song/lyric/v1", {
data: { id: songId, cp: false, tv: 0, lv: 0, rv: 0, kv: 0, yv: 0, ytv: 0, yrv: 0 },
onload: (content) => {
if (content.pureMusic) return;
const LyricObj = handleLyric(content);
if (LyricObj.orilrc.parsedLyric.length == 0) return;
const LyricItem = LyricObj.oritlrc || LyricObj.orilrc;
saveContentAsFile(LyricItem.lyric, fileName + ".lrc");
}
});
};
class SongDetail {
constructor() {
this.domReady = false;
this.dataFetched = false;
this.flag = true;
}
fetchSongData(songId) {
this.songId = songId;
weapiRequest("/api/batch", {
data: {
"/api/v3/song/detail": JSON.stringify({ c: JSON.stringify([{ "id": this.songId }]) }),
"/api/song/music/detail/get": JSON.stringify({ "songId": this.songId, "immerseType": "ste" }),
"/api/song/red/count": JSON.stringify({ "songId": this.songId }),
"/api/song/lyric/v1": JSON.stringify({ id: this.songId, cp: false, tv: 0, lv: 0, rv: 0, kv: 0, yv: 0, ytv: 0, yrv: 0 }),
"/api/song/play/about/block/page": JSON.stringify({ "songId": this.songId })
},
onload: (res) => {
console.log(res);
this.SongRes = res;
this.dataFetched = true;
this.checkStartCreateDom();
}
});
}
onDomReady() {
this.maindDiv = document.querySelector(".cvrwrap");
this.domReady = true;
this.checkStartCreateDom();
}
checkStartCreateDom() {
if (this.domReady && this.dataFetched && this.flag) {
this.flag = false;
this.createDoms();
}
}
createDoms() {
var _a, _b, _c, _d, _e, _f, _g;
this.songDetailObj = this.SongRes["/api/v3/song/detail"].songs[0];
this.title = this.songDetailObj.name;
this.album = getAlbumTextInSongDetail(this.songDetailObj);
this.artist = getArtistTextInSongDetail(this.songDetailObj);
this.filename = nameFileWithoutExt(this.title, this.artist, "artist-title");
this.songDetailObj = this.songDetailObj;
if (this.SongRes["/api/v3/song/detail"].privileges[0].plLevel != "none") {
this.createTitle("下载歌曲");
this.downLoadTableBody = this.createTable().querySelector("tbody");
let plLevel = this.SongRes["/api/v3/song/detail"].privileges[0].plLevel;
let dlLevel = this.SongRes["/api/v3/song/detail"].privileges[0].dlLevel;
let songPlWeight = levelWeight[plLevel] || 0;
let songDlWeight = levelWeight[dlLevel] || 0;
let songDetail = this.SongRes["/api/song/music/detail/get"].data;
if (this.SongRes["/api/v3/song/detail"].privileges[0].cs) {
this.createDLRow(`云盘文件 ${this.SongRes["/api/v3/song/detail"].songs[0].pc.br}k`, plLevel, "pl");
} else {
this.createTitle("转存云盘");
this.upLoadTableBody = this.createTable().querySelector("tbody");
if (songDlWeight > songPlWeight && this.SongRes["/api/v3/song/detail"].privileges[0].fee == 0) {
const channel2 = "dl";
if (songDetail.hr && songDlWeight >= 5 && songPlWeight < 5) {
const desc = `${Math.round(songDetail.hr.br / 1e3)}k ${fileSizeDesc(songDetail.hr.size)} ${songDetail.hr.sr / 1e3}kHz`;
const level = "hires";
this.createDLRow(desc, level, channel2);
this.createULRow(desc, level, channel2);
}
if (songDetail.sq && songDlWeight >= 4 && songPlWeight < 4) {
const desc = `${Math.round(songDetail.sq.br / 1e3)}k ${fileSizeDesc(songDetail.sq.size)} ${songDetail.sq.sr / 1e3}kHz`;
const level = "lossless";
this.createDLRow(desc, level, channel2);
this.createULRow(desc, level, channel2);
}
if (songDetail.h && songDlWeight >= 3 && songPlWeight < 3) {
const desc = `${Math.round(songDetail.h.br / 1e3)}k ${fileSizeDesc(songDetail.h.size)}`;
const level = "exhigh";
this.createDLRow(desc, level, channel2);
this.createULRow(desc, level, channel2);
}
if (songDetail.m && songDlWeight >= 2 && songPlWeight < 2) {
const desc = `${Math.round(songDetail.m.br / 1e3)}k ${fileSizeDesc(songDetail.m.size)}`;
const level = "higher";
this.createDLRow(desc, level, channel2);
this.createULRow(desc, level, channel2);
}
}
const channel = "pl";
if (songDetail.jm && songPlWeight >= 7) {
const desc = `${Math.round(songDetail.jm.br / 1e3)}k ${fileSizeDesc(songDetail.jm.size)} ${songDetail.jm.sr / 1e3}kHz`;
const level = "jymaster";
this.createDLRow(desc, level, channel);
this.createULRow(desc, level, channel);
}
if (songDetail.db && songPlWeight >= 7) {
const desc = `${Math.round(songDetail.db.br / 1e3)}k ${fileSizeDesc(songDetail.db.size)} ${songDetail.db.sr / 1e3}kHz`;
const level = "dolby";
this.createDLRow(desc, level, channel);
this.createULRow(desc, level, channel);
}
if (songDetail.sk && songPlWeight >= 7) {
const desc = `${Math.round(songDetail.sk.br / 1e3)}k ${fileSizeDesc(songDetail.sk.size)} ${songDetail.sk.sr / 1e3}kHz`;
const level = "sky";
this.createDLRow(desc, level, channel);
this.createULRow(desc, level, channel);
}
if (songDetail.je && songPlWeight >= 4) {
const desc = `${Math.round(songDetail.je.br / 1e3)}k ${fileSizeDesc(songDetail.je.size)} ${songDetail.je.sr / 1e3}kHz`;
const level = "jyeffect";
this.createDLRow(desc, level, channel);
this.createULRow(desc, level, channel);
}
if (songDetail.hr && songPlWeight >= 4) {
const desc = `${Math.round(songDetail.hr.br / 1e3)}k ${fileSizeDesc(songDetail.hr.size)} ${songDetail.hr.sr / 1e3}kHz `;
const level = "hires";
this.createDLRow(desc, level, channel);
this.createULRow(desc, level, channel);
}
if (songDetail.sq && songPlWeight >= 4) {
const desc = `${Math.round(songDetail.sq.br / 1e3)}k ${fileSizeDesc(songDetail.sq.size)} ${songDetail.sq.sr / 1e3}kHz`;
const level = "lossless";
this.createDLRow(desc, level, channel);
this.createULRow(desc, level, channel);
}
if (songDetail.h && songPlWeight >= 3) {
const desc = `${Math.round(songDetail.h.br / 1e3)}k ${fileSizeDesc(songDetail.h.size)}`;
const level = "exhigh";
this.createDLRow(desc, level, channel);
this.createULRow(desc, level, channel);
}
if (songDetail.m && songPlWeight >= 2) {
const desc = `${Math.round(songDetail.m.br / 1e3)}k ${fileSizeDesc(songDetail.m.size)}`;
const level = "higher";
this.createDLRow(desc, level, channel);
this.createULRow(desc, level, channel);
}
if (songDetail.l && songPlWeight >= 1) {
const desc = `${Math.round(songDetail.l.br / 1e3)}k ${fileSizeDesc(songDetail.l.size)}`;
const level = "standard";
this.createDLRow(desc, level, channel);
this.createULRow(desc, level, channel);
}
this.createHideButtonRow(this.downLoadTableBody);
this.createHideButtonRow(this.upLoadTableBody);
}
}
this.createTitle("歌曲其他信息");
this.infoTableBody = this.createTable().querySelector("tbody");
if (!this.SongRes["/api/song/lyric/v1"].pureMusic) {
this.lyricObj = handleLyric(this.SongRes["/api/song/lyric/v1"]);
if (this.lyricObj.orilrc.lyric.length > 0) {
this.lyricBlock = this.createTableRow(this.infoTableBody, "下载歌词");
if (this.lyricObj.oritlrc) {
let btn2 = this.createButton("原歌词+翻译");
btn2.addEventListener("click", () => {
this.downloadLyric("oritlrc");
});
this.lyricBlock.appendChild(btn2);
}
if (this.lyricObj.oriromalrc) {
let btn2 = this.createButton("罗马音+原歌词");
btn2.addEventListener("click", () => {
this.downloadLyric("oriromalrc");
});
this.lyricBlock.appendChild(btn2);
}
let btn = this.createButton("原歌词");
btn.addEventListener("click", () => {
this.downloadLyric("orilrc");
});
this.lyricBlock.appendChild(btn);
}
}
if (this.songDetailObj.al.picUrl) {
let btn = this.createButton("专辑封面原图");
btn.href = this.songDetailObj.al.picUrl;
btn.target = "_blank";
this.createButtonDescTableRow(this.infoTableBody, btn, null);
}
if (this.SongRes["/api/song/red/count"].data.count > 0) {
let redBlock = this.createTableRow(this.infoTableBody, "红心数量");
redBlock.innerHTML = `<span>${this.SongRes["/api/song/red/count"].data.count}</span>`;
}
if (this.songDetailObj.originCoverType > 0) {
let originCoverTypeBlock = this.createTableRow(this.infoTableBody, "原唱翻唱类型");
originCoverTypeBlock.innerHTML = `<span>${this.songDetailObj.originCoverType == 1 ? "原唱" : "翻唱"}</span>`;
}
if ((this.songDetailObj.mark & songMark.explicit) == songMark.explicit) {
let explicitBlock = this.createTableRow(this.infoTableBody, "🅴");
explicitBlock.innerHTML = `内容含有不健康因素`;
}
for (let block of this.SongRes["/api/song/play/about/block/page"].data.blocks) {
if (block.code == "SONG_PLAY_ABOUT_MUSIC_MEMORY" && block.creatives.length > 0) {
for (let creative of block.creatives) {
for (let resource of creative.resources) {
if (resource.resourceType == "FIRST_LISTEN") {
let firstTimeBlock = this.createTableRow(this.infoTableBody, "第一次听");
firstTimeBlock.innerHTML = resource.resourceExt.musicFirstListenDto.date;
} else if (resource.resourceType == "TOTAL_PLAY") {
let recordBlock = this.createTableRow(this.infoTableBody, "累计播放");
let recordText = ` ${resource.resourceExt.musicTotalPlayDto.playCount}次`;
if (resource.resourceExt.musicTotalPlayDto.duration > 0) {
recordText += ` ${resource.resourceExt.musicTotalPlayDto.duration}分钟`;
}
if (resource.resourceExt.musicTotalPlayDto.text.length > 0) {
recordText += " " + resource.resourceExt.musicTotalPlayDto.text;
}
recordBlock.innerHTML = recordText;
}
}
}
}
if (block.code == "SONG_PLAY_ABOUT_SONG_BASIC" && block.creatives.length > 0) {
for (let creative of block.creatives) {
if (creative.creativeType == "sheet" && creative.resources.length == 0) continue;
if (!((_a = creative == null ? void 0 : creative.uiElement) == null ? void 0 : _a.mainTitle)) continue;
let wikiItemBlock = this.createTableRow(this.infoTableBody, creative.uiElement.mainTitle.title);
if (creative.uiElement.descriptions) {
let descriptionDiv = document.createElement("div");
for (let description of creative.uiElement.descriptions) {
let descriptionP = this.createText(description.description);
descriptionDiv.appendChild(descriptionP);
}
wikiItemBlock.appendChild(descriptionDiv);
}
if (creative.uiElement.textLinks) {
for (let textLink of creative.uiElement.textLinks) {
let textLinkP = this.createText(textLink.text);
wikiItemBlock.appendChild(textLinkP);
}
}
if (creative.resources) {
for (let resource of creative.resources) {
let resourceDiv = document.createElement("div");
resourceDiv.className = "des s-fc3";
if (resource.uiElement.mainTitle) {
let IsLink = ((_c = (_b = resource.action) == null ? void 0 : _b.clickAction) == null ? void 0 : _c.action) == 1 && ((_e = (_d = resource.action) == null ? void 0 : _d.clickAction) == null ? void 0 : _e.targetUrl.startsWith("https://"));
let mainTitleItem = IsLink ? this.createButton(resource.uiElement.mainTitle.title) : this.createText(resource.uiElement.mainTitle.title);
if (IsLink) {
mainTitleItem.target = "_blank";
mainTitleItem.href = (_g = (_f = resource.action) == null ? void 0 : _f.clickAction) == null ? void 0 : _g.targetUrl;
}
wikiItemBlock.appendChild(mainTitleItem);
}
if (resource.uiElement.subTitles) {
let subTitleP = this.createText(resource.uiElement.subTitles.map((t) => t.title).join(" "));
subTitleP.innerHTML = resource.uiElement.subTitles.map((t) => t.title).join(" ");
wikiItemBlock.appendChild(subTitleP);
}
if (resource.uiElement.descriptions) {
for (let description of resource.uiElement.descriptions) {
let descriptionP = this.createText(description.description);
wikiItemBlock.appendChild(descriptionP);
}
}
if (resource.uiElement.images) {
for (let image of resource.uiElement.images) {
let imageA = this.createButton(image.title);
imageA.target = "_blank";
imageA.href = image.imageUrl || image.imageWithoutTextUrl;
wikiItemBlock.appendChild(imageA);
}
}
if (resource.uiElement.textLinks) {
for (let textLink of resource.uiElement.textLinks) {
if (textLink.text) {
let textLinkP = this.createText(textLink.text);
wikiItemBlock.appendChild(textLinkP);
}
}
}
}
}
}
}
}
}
createTitle(title) {
let h3 = document.createElement("h3");
h3.innerHTML = `<span class="f-fl" style="margin-top: 10px;margin-bottom: 10px;">${title}</span>`;
this.maindDiv.appendChild(h3);
}
createTable() {
let table = document.createElement("table");
table.className = "m-table";
let tbody = document.createElement("tbody");
table.appendChild(tbody);
this.maindDiv.appendChild(table);
return table;
}
createTableRow(tbody, title, needHide = false) {
let row = document.createElement("tr");
if (tbody.children.length % 2 == 0) row.className = "even";
if (needHide && tbody.children.length > 0) row.style.display = "none";
row.innerHTML = `<td><div><span>${title || ""}</span></div></td><td><div></div></td>`;
tbody.appendChild(row);
return row.querySelector("tr > td:nth-child(2) > div");
}
createButtonDescTableRow(tbody, btn, desc, needHide = false) {
let row = document.createElement("tr");
if (tbody.children.length % 2 == 0) row.className = "even";
if (needHide && tbody.children.length > 0) row.style.display = "none";
row.innerHTML = `<td ${desc ? 'style="width: 23%;"' : ""}><div></div></td><td><div><span>${desc || ""}</span></div></td>`;
let firstArea = row.querySelector("tr > td:nth-child(1) > div");
firstArea.appendChild(btn);
tbody.appendChild(row);
return row;
}
createHideButtonRow(tbody) {
if (tbody.children.length < 2) return;
let row = document.createElement("tr");
row.innerHTML = `<td><div><a class="s-fc7">展开<i class="u-icn u-icn-69"></i></a></div></td>`;
let btn = row.querySelector("a");
btn.addEventListener("click", () => {
for (let i = 1; i < tbody.children.length - 1; i++) {
if (tbody.children[i].style.display == "none") {
tbody.children[i].style.display = "";
} else {
tbody.children[i].style.display = "none";
}
}
if (btn.innerHTML.startsWith("展开")) {
btn.innerHTML = '收起<i class="u-icn u-icn-70"></i>';
} else {
btn.innerHTML = '展开<i class="u-icn u-icn-69"></i>';
}
});
tbody.appendChild(row);
}
createButton(desc) {
let btn = document.createElement("a");
btn.text = desc;
btn.className = "s-fc7";
btn.style.marginRight = "10px";
return btn;
}
createText(desc) {
let btn = document.createElement("span");
btn.innerHTML = desc;
btn.style.marginRight = "10px";
return btn;
}
createDLRow(desc, level, channel) {
let btn = this.createButton(levelDesc(level));
btn.addEventListener("click", () => {
this.dwonloadSong(channel, level, btn);
});
this.createButtonDescTableRow(this.downLoadTableBody, btn, desc, true);
}
createULRow(desc, level, channel) {
if (!unsafeWindow.GUser.userId) return;
let apiUrl = "/api/song/enhance/player/url/v1";
if (channel == "dl") apiUrl = "/api/song/enhance/download/url/v1";
let data = { ids: JSON.stringify([this.songId]), level, encodeType: "mp3" };
if (channel == "dl") data = { id: this.songId, level, encodeType: "mp3" };
let api = { url: apiUrl, data };
let songItem = { api, id: this.songId, title: this.title, artist: this.artist, album: this.album };
let btn = this.createButton(levelDesc(level));
btn.addEventListener("click", () => {
let ULobj = new ncmDownUpload([songItem]);
ULobj.startUpload();
});
this.createButtonDescTableRow(this.upLoadTableBody, btn, desc, true);
}
dwonloadSong(channel, level, dlbtn) {
let url2 = "/api/song/enhance/player/url/v1";
if (channel == "dl") url2 = "/api/song/enhance/download/url/v1";
let data = { ids: JSON.stringify([this.songId]), level, encodeType: "mp3" };
if (channel == "dl") data = { id: this.songId, level, encodeType: "mp3" };
let songItem = {
id: this.songId,
title: this.songDetailObj.name,
artist: this.artist,
album: this.album,
song: this.songDetailObj,
privilege: this.songDetailObj,
api: { url: url2, data }
};
const config = {
out: "artist-title",
threadCount: 1,
folder: "none"
};
batchDownloadSongs([songItem], config);
}
downloadLyric(lrcKey) {
saveContentAsFile(this.lyricObj[lrcKey].lyric, this.filename + ".lrc");
}
}
let songDetailObj = new SongDetail();
const filterSongs = (songList, config) => {
let songFilteredList = [];
for (let song of songList) {
if (song.privilege.st < 0 || song.privilege.plLevel == "none") continue;
if (song.privilege.cs && config.skipCloud) continue;
if (song.privilege.fee == 0 && !config.free) continue;
if (song.privilege.fee == 1 && !config.VIP) continue;
if (song.privilege.fee == 4 && !config.pay) continue;
if (song.privilege.fee == 8 && !config.lowFree) continue;
songFilteredList.push(song);
}
return songFilteredList;
};
const PlayAPIDataLimit = 1e3;
const CheckAPIDataLimit = 100;
const importAPIDataLimit = 10;
class ncmDownUploadBatch {
constructor(songs, config) {
this.hasError = false;
this.songs = songs;
this.songIdIndexsMap = {};
this.playerApiSongIds = [];
this.downloadApiSongIds = [];
for (let i = 0; i < songs.length; i++) {
const songId = songs[i].id;
this.songIdIndexsMap[songId] = i;
if (songs[i].api.url === "/api/song/enhance/player/url/v1") {
this.playerApiSongIds.push(songId);
} else {
this.downloadApiSongIds.push(songId);
}
}
this.successSongsId = [];
this.skipSongs = [];
this.failSongs = [];
this.config = config;
this.log = "";
}
startUpload() {
Swal.fire({
input: "textarea",
inputLabel: "批量转存云盘",
confirmButtonText: "关闭",
allowOutsideClick: false,
allowEscapeKey: false,
showCloseButton: false,
showConfirmButton: true,
inputAttributes: {
"readonly": true
},
footer: "浏览器F12控制台中可查看所有的接口返回内容,出错时可进行检查。",
didOpen: () => {
this.textarea = Swal.getInput();
this.textarea.style = "height: 300px;";
this.comfirmBtn = Swal.getConfirmButton();
this.comfirmBtn.style = "display: none;";
this.fetchFileDetail();
}
});
}
fetchFileDetail() {
this.addLog(`将上传 ${this.songs.length} 首歌`);
this.addLog("第一步:获取歌曲文件信息");
if (this.playerApiSongIds.length > 0) {
this.addLog("通过试听接口获取歌曲文件信息");
this.fetchFileDetailByPlayerApi(0);
} else {
this.fetchFileDetailByDownloadApi();
}
}
fetchFileDetailByPlayerApi(offset, retry = false) {
if (offset >= this.playerApiSongIds.length) {
this.addLog("通过试听接口获取歌曲文件信息完成");
this.fetchFileDetailByDownloadApi();
return;
}
this.addLog(`正在获取第 ${offset + 1} 到 第 ${Math.min(offset + PlayAPIDataLimit, this.playerApiSongIds.length)} 首歌曲`);
const ids = this.playerApiSongIds.slice(offset, offset + PlayAPIDataLimit);
weapiRequest("/api/song/enhance/player/url/v1", {
data: {
ids: JSON.stringify(ids),
level: this.config.level,
encodeType: "mp3"
},
onload: (content) => {
if (content.code != 200) {
console.error("试听接口", content);
if (!retry) {
this.addLog("接口调用失败,1秒后重试");
sleep(1e3).then(() => {
this.fetchFileDetailByPlayerApi(offset, retry = true);
});
} else {
this.addLog("接口调用失败,将跳过出错歌曲");
this.hasError = true;
sleep(1e3).then(() => {
this.fetchFileDetailByPlayerApi(offset + PlayAPIDataLimit);
});
}
return;
}
console.log("试听接口", content);
content.data.forEach((songFileData) => {
let songIndex = this.songIdIndexsMap[songFileData.id];
if (this.config.targetLevelOnly && this.config.level != songFileData.level) {
if (this.songs[songIndex].api.url === "/api/song/enhance/player/url/v1") {
this.skipSongs.push(this.songs[songIndex].title);
}
} else if (songFileData.md5) {
this.songs[songIndex].fileFullName = nameFileWithoutExt(this.songs[songIndex].title, this.songs[songIndex].artist, this.config.out) + "." + songFileData.type.toLowerCase();
this.songs[songIndex].md5 = songFileData.md5;
this.songs[songIndex].size = songFileData.size;
this.songs[songIndex].level = songFileData.level;
this.songs[songIndex].ext = songFileData.type.toLowerCase();
this.songs[songIndex].bitrate = Math.floor(songFileData.br / 1e3);
} else {
console.error("试听接口", this.songs[songIndex].title, songFileData);
this.failSongs.push(this.songs[songIndex].title + ":通过试听接口获取文件信息失败");
}
});
this.fetchFileDetailByPlayerApi(offset + PlayAPIDataLimit);
},
onerror: (content) => {
console.error("试听接口", content);
if (!retry) {
this.addLog("试听接口调用时报错,1秒后重试");
sleep(1e3).then(() => {
this.fetchFileDetailByPlayerApi(offset, retry = true);
});
} else {
this.addLog("试听接口调用时报错,将跳过出错歌曲");
this.hasError = true;
sleep(1e3).then(() => {
this.fetchFileDetailByPlayerApi(offset + PlayAPIDataLimit);
});
}
}
});
}
fetchFileDetailByDownloadApi() {
if (this.downloadApiSongIds.length > 0) {
this.addLog("通过下载接口获取更好音质(非vip用户少数歌曲可获取到无损、HiRes音质)");
this.fetchFileDetailByDownloadApiSub(0);
} else {
this.fetchCloudId();
}
}
fetchFileDetailByDownloadApiSub(offset, retry = false) {
if (offset >= this.downloadApiSongIds.length) {
this.addLog("通过下载接口获取歌曲文件信息完成");
this.fetchCloudId();
return;
}
let songId = this.downloadApiSongIds[offset];
let songIndex = this.songIdIndexsMap[songId];
weapiRequest(
"/api/song/enhance/download/url/v1",
{
data: this.songs[songIndex].api.data,
onload: (content) => {
if (content.code != 200) {
console.error("下载接口", content);
if (!retry) {
this.addLog("接口调用失败,1秒后重试");
sleep(1e3).then(() => {
this.fetchFileDetailByDownloadApiSub(offset, retry = true);
});
} else {
this.addLog(`歌曲 ${this.songs[songIndex].title} 下载接口调用失败,跳过`);
this.failSongs.push(this.songs[songIndex].title + ":通过下载接口获取文件信息失败");
this.hasError = true;
sleep(1e3).then(() => {
this.fetchFileDetailByDownloadApiSub(offset + 1);
});
}
return;
}
console.log("下载接口", content);
if (this.config.targetLevelOnly && this.config.level != content.data.level) {
this.skipSongs.push(this.songs[songIndex].title);
} else if (content.data.md5) {
this.songs[songIndex].fileFullName = nameFileWithoutExt(this.songs[songIndex].title, this.songs[songIndex].artist, this.config.out) + "." + content.data.type.toLowerCase();
this.songs[songIndex].md5 = content.data.md5;
this.songs[songIndex].size = content.data.size;
this.songs[songIndex].level = content.data.level;
this.songs[songIndex].ext = content.data.type.toLowerCase();
this.songs[songIndex].bitrate = Math.floor(content.data.br / 1e3);
this.addLog(`${this.songs[songIndex].title} 通过下载接口获取到 ${levelDesc(content.data.level)} 音质文件信息`);
} else {
this.failSongs.push(this.songs[songIndex].title + ":通过下载接口获取文件信息失败");
}
this.fetchFileDetailByDownloadApiSub(offset + 1);
},
onerror: (content) => {
console.error("下载接口", content);
if (!retry) {
this.addLog("下载接口调用时报错,1秒后重试");
sleep(1e3).then(() => {
this.fetchFileDetailByDownloadApiSub(offset, retry = true);
});
} else {
this.addLog(`歌曲 ${this.songs[songIndex].title} 下载接口调用失败,跳过`);
this.failSongs.push(this.songs[songIndex].title + ":通过下载接口获取文件信息失败");
this.hasError = true;
sleep(1e3).then(() => {
this.fetchFileDetailByDownloadApiSub(offset + 1);
});
}
}
}
);
}
fetchCloudId() {
this.addLog("第二步:获取文件的云盘ID");
this.fetchCloudIdSub(0);
}
fetchCloudIdSub(offset, retry = false) {
if (offset >= this.songs.length) {
this.addLog("获取文件的云盘ID完成");
this.importSongs();
return;
}
let songMD5Map = {};
let songCheckDatas = [];
let index = offset;
while (index < this.songs.length && songCheckDatas.length < CheckAPIDataLimit) {
let song = this.songs[index];
if (song.md5) {
songCheckDatas.push({
md5: song.md5,
songId: song.id,
bitrate: song.bitrate,
fileSize: song.size
});
songMD5Map[song.md5] = song.id;
}
index += 1;
}
this.addLog(`正在获取第 ${offset + 1} 到 第 ${index} 首歌曲`);
if (songCheckDatas.length == 0) {
this.fetchCloudIdSub(index);
return;
}
weapiRequest("/api/cloud/upload/check/v2", {
data: {
uploadType: 0,
songs: JSON.stringify(songCheckDatas)
},
onload: (content) => {
if (content.code != 200 || content.data.length == 0) {
console.error("获取文件云盘ID接口", content);
if (!retry) {
this.addLog("接口调用失败,1秒后重试");
sleep(1e3).then(() => {
this.fetchCloudIdSub(offset, retry = true);
});
} else {
this.addLog("接口调用失败,将跳过出错歌曲");
this.hasError = true;
sleep(1e3).then(() => {
this.fetchCloudIdSub(index);
});
}
return;
}
console.log("获取文件云盘ID接口", content);
let hasFail = false;
content.data.forEach((fileData) => {
const songId = songMD5Map[fileData.md5];
const songIndex = this.songIdIndexsMap[songId];
if (fileData.upload == 1) {
this.songs[songIndex].cloudId = fileData.songId;
} else {
this.failSongs.push(this.songs[songIndex].title);
hasFail = true;
}
});
if (hasFail) {
console.error("获取文件云盘ID api", content);
}
this.fetchCloudIdSub(index);
},
onerror: (content) => {
console.error("获取文件云盘ID接口", content);
if (!retry) {
this.addLog("调用接口时报错,1秒后重试");
sleep(1e3).then(() => {
this.fetchCloudIdSub(offset, retry = true);
});
} else {
this.addLog("调用接口时报错,将跳过出错歌曲");
this.hasError = true;
sleep(1e3).then(() => {
this.fetchCloudIdSub(index);
});
}
}
});
}
importSongs() {
this.addLog("第三步:文件导入云盘");
this.importSongsSub(0);
}
importSongsSub(offset, retry = false) {
if (offset >= this.songs.length) {
this.final();
return;
}
let songCloudIdMap = {};
let importSongDatas = [];
let index = offset;
while (index < this.songs.length && importSongDatas.length < importAPIDataLimit) {
let song = this.songs[index];
if (song.cloudId) {
importSongDatas.push({
songId: song.cloudId,
bitrate: song.bitrate,
song: song.fileFullName,
artist: song.artist,
album: song.album,
fileName: song.fileFullName
});
songCloudIdMap[song.cloudId] = song.id;
}
index += 1;
}
if (importSongDatas.length == 0) {
this.importSongsSub(index);
return;
}
weapiRequest("/api/cloud/user/song/import", {
data: {
uploadType: 0,
songs: JSON.stringify(importSongDatas)
},
onload: (content) => {
if (content.code != 200) {
console.error("歌曲导入云盘接口", content);
if (!retry) {
this.addLog("接口调用失败,1秒后重试");
sleep(1e3).then(() => {
this.importSongsSub(offset, retry = true);
});
} else {
this.addLog("接口调用失败,将跳过出错歌曲");
this.hasError = true;
sleep(1e3).then(() => {
this.importSongsSub(index);
});
}
}
console.log("歌曲导入云盘接口", content);
if (content.data.successSongs.length > 0) {
let successSongs = [];
content.data.successSongs.forEach((successSong) => {
let songId = songCloudIdMap[successSong.songId];
this.successSongsId.push(songId);
successSongs.push(this.songs[this.songIdIndexsMap[songId]].title);
});
this.addLog(`以下歌曲上传成功:${successSongs.join()}`);
}
if (content.data.failed.length > 0) {
console.error("导入歌曲接口,存在上传失败歌曲。", content.data.failed);
content.data.failed.forEach((failSong) => {
let songId = songCloudIdMap[failSong.songId];
let songTItle = this.songs[this.songIdIndexsMap[songId]].title;
if (failSong.msg) {
songTItle += ":" + failSong.msg;
}
this.failSongs.push(songTItle);
});
}
this.importSongsSub(index);
},
onerror: (content) => {
console.error("歌曲导入云盘", content);
if (!retry) {
this.addLog("调用接口时报错,1秒后重试");
sleep(1e3).then(() => {
this.importSongsSub(offset, retry = true);
});
} else {
this.addLog("调用接口时报错,将跳过出错歌曲");
this.hasError = true;
sleep(1e3).then(() => {
this.importSongsSub(index);
});
}
}
});
}
final() {
this.addLog("上传结束");
if (this.hasError) {
this.addLog("调用接口时存在报错,跳过了部分歌曲。请尝试重新上传");
}
if (this.skipSongs.length > 0) {
this.addLog(`有${this.skipSongs.length}首歌不是目标音质不进行上传`);
}
if (this.failSongs.length > 0) {
this.addLog(`以下${this.failSongs.length}首歌上传失败:${this.failSongs.join()}`);
}
this.updateSongCloudStatus();
this.comfirmBtn.style = "display: inline-block;";
}
addLog(log) {
this.log += log + "\n";
this.textarea.value = this.log;
this.textarea.scrollTop = this.textarea.scrollHeight;
}
//更新缓存歌曲的云盘状态
updateSongCloudStatus() {
if (this.successSongsId.length > 0) {
if (this.config.listType == "playlist") {
playlistDetailObj.updateSongsCloudStatus(this.successSongsId);
} else if (this.config.listType == "album") {
albumDetailObj.updateSongsCloudStatus(this.successSongsId);
}
}
}
}
const batchUploadSongs = (songList, config) => {
if (songList.length == 0) {
showConfirmBox("没有可上传的歌曲");
return;
}
showTips(`开始下载上传${songList.length}首歌曲`, 1);
let ULobj = new ncmDownUploadBatch(songList, config);
ULobj.startUpload();
};
const createSongsUrlApi = (songList, config) => {
for (let songItem of songList) {
let api = { url: "/api/song/enhance/player/url/v1", data: { ids: JSON.stringify([songItem.id]), level: config.level, encodeType: "mp3" } };
if (songItem.privilege.fee == 0 && (levelWeight[songItem.privilege.plLevel] || 99) < (levelWeight[songItem.privilege.dlLevel] || -1)) api = { url: "/api/song/enhance/download/url/v1", data: { id: songItem.id, level: config.level, encodeType: "mp3" } };
songItem.api = api;
}
if (config.action == "batchUpload") {
batchUploadSongs(songList, config);
} else if (config.action == "batchDownload") {
batchDownloadSongs(songList, config);
}
};
const downloadSongBatch$1 = (playlistId, uiArea) => {
let btnBatchDownload = createBigButton("批量下载", uiArea, 1);
btnBatchDownload.addEventListener("click", () => {
ShowBatchDLPopUp({ listType: "playlist", listId: playlistId });
});
};
const ShowBatchDLULPopUp = (config) => {
Swal.fire({
width: 600,
title: "批量转存云盘",
html: `<div id="my-cbs">
<label><input class="form-check-input" type="checkbox" value="" id="cb-fee1" checked>VIP歌曲</label>
<label><input class="form-check-input" type="checkbox" value="" id="cb-fee4" checked>付费专辑歌曲</label>
<label><input class="form-check-input" type="checkbox" value="" id="cb-fee8">低音质免费歌曲</label>
<labe><input class="form-check-input" type="checkbox" value="" id="cb-fee0">免费歌曲</label>
</div>
<div id="my-cbs2">
<label><input class="form-check-input" type="checkbox" value="" id="cb-targetLevelOnly">仅获取到目标音质时上传</label>
</div>
<div id="my-level">
<label>优先转存音质<select id="level-select" class="swal2-select"><option value="jymaster" selected="">超清母带</option><option value="dolby">杜比全景声</option><option value="sky">沉浸环绕声</option><option value="jyeffect">高清环绕声</option><option value="hires">Hi-Res</option><option value="lossless">无损</option><option value="exhigh">极高</option></select></label>
</div>
<div id="my-out">
<label>文件命名格式<select id="out-select" class="swal2-select"><option value="artist-title" selected="">歌手 - 歌曲名</option><option value="title">歌曲名</option><option value="title-artist">歌曲名-歌手</option></select></label>
</div>
`,
confirmButtonText: "开始转存",
showCloseButton: true,
footer: '<span></span><a href="https://github.com/Cinvin/myuserscripts"><img src="https://img.shields.io/github/stars/cinvin/myuserscripts?style=social" alt="Github"></a>',
focusConfirm: false,
preConfirm: () => {
return {
free: document.getElementById("cb-fee0").checked,
VIP: document.getElementById("cb-fee1").checked,
pay: document.getElementById("cb-fee4").checked,
lowFree: document.getElementById("cb-fee8").checked,
targetLevelOnly: document.getElementById("cb-targetLevelOnly").checked,
skipCloud: true,
level: document.getElementById("level-select").value,
out: document.getElementById("out-select").value,
listType: config.listType,
listId: config.listId,
action: "batchUpload"
};
}
}).then((res) => {
if (res.isConfirmed) {
if (res.value.listType == "playlist") {
let filtedSongList = filterSongs(playlistDetailObj.playlistSongList, res.value);
createSongsUrlApi(filtedSongList, res.value);
} else if (res.value.listType == "album") {
let filtedSongList = filterSongs(albumDetailObj.albumSongList, res.value);
createSongsUrlApi(filtedSongList, res.value);
}
}
});
};
const uploadSongBatch$1 = (playlistId, uiArea) => {
let btnBatchUpload = createBigButton("批量转存云盘", uiArea, 1);
btnBatchUpload.addEventListener("click", () => {
ShowBatchDLULPopUp({ listType: "playlist", listId: playlistId });
});
};
const sortSongs = (playlistId, uiArea) => {
let btnPlaylistSort = createBigButton("歌单排序", uiArea, 1);
btnPlaylistSort.addEventListener("click", () => {
ShowPLSortPopUp(playlistId);
});
};
const ShowPLSortPopUp = (playlistId) => {
Swal.fire({
title: "歌单内歌曲排序",
input: "select",
inputOptions: ["发行时间降序", "发行时间升序", "红心数量降序", "红心数量升序", "评论数量降序", "评论数量升序"],
inputPlaceholder: "选择排序方式",
confirmButtonText: "开始排序",
showCloseButton: true,
focusConfirm: false,
inputValidator: (way) => {
if (!way) {
return "请选择排序方式";
}
}
}).then((res) => {
if (!res.isConfirmed) return;
if (res.value == 0) {
PlaylistTimeSort(playlistId, true);
} else if (res.value == 1) {
PlaylistTimeSort(playlistId, false);
} else if (res.value == 2) {
PlaylistCountSort(playlistId, true, "Red");
} else if (res.value == 3) {
PlaylistCountSort(playlistId, false, "Red");
} else if (res.value == 4) {
PlaylistCountSort(playlistId, true, "Comment");
} else if (res.value == 5) {
PlaylistCountSort(playlistId, false, "Comment");
}
});
};
const PlaylistTimeSort = (playlistId, descending) => {
showTips(`正在获取歌单内歌曲信息`, 1);
weapiRequest("/api/v6/playlist/detail", {
data: {
id: playlistId,
n: 1e5,
s: 8
},
onload: (content) => {
let songList = [];
let tracklen = content.playlist.tracks.length;
for (let i = 0; i < tracklen; i++) {
let songItem = { id: content.playlist.tracks[i].id, publishTime: content.playlist.tracks[i].publishTime, albumId: content.playlist.tracks[i].al.id, cd: content.playlist.tracks[i].cd ? Number(content.playlist.tracks[i].cd.split(" ")[0]) : 0, no: content.playlist.tracks[i].no };
songList.push(songItem);
}
if (content.playlist.trackCount > content.playlist.tracks.length) {
showTips(`大歌单,开始分批获取${content.playlist.trackCount}首歌信息`, 1);
let trackIds = content.playlist.trackIds.map((item) => {
return {
"id": item.id
};
});
PlaylistTimeSortFetchAll(playlistId, descending, trackIds, 0, songList);
} else {
PlaylistTimeSortFetchAllPublishTime(playlistId, descending, 0, songList, {});
}
}
});
};
const PlaylistTimeSortFetchAll = (playlistId, descending, trackIds, startIndex, songList) => {
if (startIndex >= trackIds.length) {
PlaylistTimeSortFetchAllPublishTime(playlistId, descending, 0, songList, {});
return;
}
weapiRequest("/api/v3/song/detail", {
data: {
c: JSON.stringify(trackIds.slice(startIndex, startIndex + 1e3))
},
onload: function(content) {
let songlen = content.songs.length;
for (let i = 0; i < songlen; i++) {
let songItem = { id: content.songs[i].id, publishTime: content.songs[i].publishTime, albumId: content.songs[i].al.id, cd: content.songs[i].cd ? Number(content.songs[i].cd.split(" ")[0]) : 0, no: content.songs[i].no };
songList.push(songItem);
}
PlaylistTimeSortFetchAll(playlistId, descending, trackIds, startIndex + content.songs.length, songList);
}
});
};
const PlaylistTimeSortFetchAllPublishTime = (playlistId, descending, index, songList, aldict) => {
if (index >= songList.length) {
PlaylistTimeSortSongs(playlistId, descending, songList);
return;
}
if (index == 0) showTips("开始获取歌曲专辑发行时间");
if (index % 10 == 9) showTips(`正在获取歌曲专辑发行时间(${index + 1}/${songList.length})`);
let albumId = songList[index].albumId;
if (albumId <= 0) {
PlaylistTimeSortFetchAllPublishTime(playlistId, descending, index + 1, songList, aldict);
return;
}
if (aldict[albumId]) {
songList[index].publishTime = aldict[albumId];
PlaylistTimeSortFetchAllPublishTime(playlistId, descending, index + 1, songList, aldict);
return;
}
weapiRequest(`/api/v1/album/${albumId}`, {
onload: function(content) {
let publishTime = content.album.publishTime;
aldict[albumId] = publishTime;
songList[index].publishTime = publishTime;
PlaylistTimeSortFetchAllPublishTime(playlistId, descending, index + 1, songList, aldict);
}
});
};
const PlaylistTimeSortSongs = (playlistId, descending, songList) => {
songList.sort((a, b) => {
if (a.publishTime != b.publishTime) {
if (descending) {
return b.publishTime - a.publishTime;
} else {
return a.publishTime - b.publishTime;
}
} else if (a.albumId != b.albumId) {
if (descending) {
return b.albumId - a.albumId;
} else {
return a.albumId - b.albumId;
}
} else if (a.cd != b.cd) {
return a.cd - b.cd;
} else if (a.no != b.no) {
return a.no - b.no;
}
return a.id - b.id;
});
let trackIds = songList.map((song) => song.id);
weapiRequest("/api/playlist/manipulate/tracks", {
data: {
pid: playlistId,
trackIds: JSON.stringify(trackIds),
op: "update"
},
onload: function(content) {
if (content.code == 200) {
showConfirmBox("排序完成");
} else {
showConfirmBox("排序失败," + content);
}
}
});
};
const PlaylistCountSort = (playlistId, descending, way) => {
showTips(`正在获取歌单内歌曲信息`, 1);
weapiRequest("/api/v6/playlist/detail", {
data: {
id: playlistId,
n: 1e5,
s: 8
},
onload: (content) => {
let songList = content.playlist.trackIds.map((item) => {
return {
"id": item.id,
"count": 0
};
});
let trackIds = content.playlist.trackIds.map((item) => {
return item.id;
});
if (way == "Red") {
PlaylistCountSortFetchRedCount(playlistId, songList, 0, descending);
} else if (way == "Comment") {
PlaylistCountSortFetchCommentCount(playlistId, songList, trackIds, 0, descending);
}
}
});
};
const PlaylistCountSortFetchRedCount = (playlistId, songList, index, descending) => {
if (index >= songList.length) {
PlaylistCountSortSongs(playlistId, descending, songList);
return;
}
if (index == 0) showTips("开始获取歌曲红心数量");
if (index % 10 == 9) showTips(`正在获取歌曲红心数量(${index + 1}/${songList.length})`);
weapiRequest("/api/song/red/count", {
data: {
songId: songList[index].id
},
onload: function(content) {
songList[index].count = content.data.count;
PlaylistCountSortFetchRedCount(playlistId, songList, index + 1, descending);
}
});
};
const PlaylistCountSortFetchCommentCount = (playlistId, songList, trackIds, index, descending) => {
if (index >= songList.length) {
PlaylistCountSortSongs(playlistId, descending, songList);
return;
}
if (index == 0) showTips("开始获取歌曲评论数量");
else showTips(`正在获取歌曲评论数量(${index + 1}/${songList.length})`);
weapiRequest("/api/resource/commentInfo/list", {
data: {
resourceType: "4",
resourceIds: JSON.stringify(trackIds.slice(index, index + 1e3))
},
onload: function(content) {
content.data.forEach((item) => {
let songId = item.resourceId;
for (let i = 0; i < songList.length; i++) {
if (songList[i].id == songId) {
songList[i].count = item.commentCount;
break;
}
}
});
PlaylistCountSortFetchCommentCount(playlistId, songList, trackIds, index + 1e3, descending);
}
});
};
const PlaylistCountSortSongs = (playlistId, descending, songList) => {
songList.sort((a, b) => {
if (a.count != b.count) {
if (descending) {
return b.count - a.count;
} else {
return a.count - b.count;
}
}
return a.id - b.id;
});
let trackIds = songList.map((song) => song.id);
weapiRequest("/api/playlist/manipulate/tracks", {
data: {
pid: playlistId,
trackIds: JSON.stringify(trackIds),
op: "update"
},
onload: function(content) {
if (content.code == 200) {
showConfirmBox("排序完成");
} else {
showConfirmBox("排序失败");
}
}
});
};
class PlaylistDetail {
constructor() {
this.domReady = false;
this.dataFetched = false;
this.flag = true;
const params2 = new URLSearchParams(unsafeWindow.location.search);
this.playlistId = Number(params2.get("id"));
this._hash = params2.get("_hash");
this.playlist = null;
this.playlistSongList = [];
this.playableSongList = [];
this.rowHTMLList = [];
}
fetchPlaylistFullData(playlistId) {
weapiRequest("/api/v6/playlist/detail", {
data: {
id: playlistId,
n: 1e5,
s: 8
},
onload: (content) => {
this.playlist = content.playlist;
if (content.playlist.trackCount > content.playlist.tracks.length) {
let trackIds = content.playlist.trackIds.map((item) => {
return {
"id": item.id
};
});
this.getPlaylistAllSongsSub(trackIds, 0);
} else {
this.addSongInToSongList(content);
this.onFetchDatafinnsh();
}
}
});
}
getPlaylistAllSongsSub(trackIds, startIndex) {
if (startIndex >= trackIds.length) {
this.onFetchDatafinnsh();
return;
}
weapiRequest("/api/v3/song/detail", {
data: {
c: JSON.stringify(trackIds.slice(startIndex, startIndex + 1e3))
},
onload: (content) => {
this.addSongInToSongList(content);
this.getPlaylistAllSongsSub(trackIds, startIndex + content.songs.length);
}
});
}
addSongInToSongList(content) {
const songs = content.songs || content.playlist.tracks;
const privileges = content.privileges;
const songlen = songs.length;
const privilegelen = privileges.length;
for (let i = 0; i < songlen; i++) {
for (let j = 0; j < privilegelen; j++) {
if (songs[i].id == privileges[j].id) {
let songItem = {
id: songs[i].id,
title: songs[i].name,
artist: getArtistTextInSongDetail(songs[i]),
album: getAlbumTextInSongDetail(songs[i]),
song: songs[i],
privilege: privileges[j]
};
this.playlistSongList.push(songItem);
break;
}
}
}
}
onFetchDatafinnsh() {
this.playlistSongList.forEach((songItem) => {
this.createFormatAddToData(songItem);
});
this.dataFetched = true;
this.checkStartInitBtn();
}
createFormatAddToData(songItem) {
if (songItem.privilege.plLevel != "none") {
let addToFormat = {
album: songItem.song.al,
alias: songItem.song.alia || songItem.song.ala || [],
artists: songItem.song.ar || [],
commentThreadId: "R_SO_4_" + songItem.song.id,
copyrightId: songItem.song.cp || 0,
duration: songItem.song.dt || 0,
id: songItem.song.id,
mvid: songItem.song.mv || 0,
name: songItem.song.name || "",
cd: songItem.song.cd,
position: songItem.song.no || 0,
ringtone: songItem.song.rt,
rtUrl: songItem.song.rtUrl,
status: songItem.song.st || 0,
pstatus: songItem.song.pst || 0,
fee: songItem.song.fee || 0,
version: songItem.song.v || 0,
eq: songItem.song.eq,
songType: songItem.song.t || 0,
mst: songItem.song.mst,
score: songItem.song.pop || 0,
ftype: songItem.song.ftype,
rtUrls: songItem.song.rtUrls,
transNames: songItem.song.tns,
privilege: songItem.song.privilege,
lyrics: songItem.song.lyrics,
alg: songItem.song.alg,
source: {
fdata: String(this.playlistId),
fid: 13,
link: `playlist?id=${this.playlistId}&_hash=songlist-${songItem.song.id}`,
title: "歌单"
}
};
this.playableSongList.push(addToFormat);
}
}
onDomReady() {
this.operationArea = document.querySelector("#content-operation");
this.songListTextDom = document.querySelector("div.u-title.u-title-1.f-cb > h3 > span");
this.playCount = document.querySelector("#play-count");
this.songListTextDom.innerHTML = "获取歌单数据中...";
this.domReady = true;
this.checkStartInitBtn();
}
checkStartInitBtn() {
if (this.domReady && this.dataFetched && this.flag) {
this.flag = false;
this.renderPlayAllBtn();
this.appendBtns();
this.fillTableSong();
let playlistTrackCount = document.querySelector("#playlist-track-count");
if (playlistTrackCount) playlistTrackCount.innerHTML = this.playlistSongList.length;
this.songListTextDom.innerHTML = "歌曲列表";
}
}
renderPlayAllBtn() {
this.operationArea.innerHTML = `
<a style="display:none" class="u-btn2 u-btn2-2 u-btni-addply f-fl" hidefocus="true" title="播放"><i><em class="ply"></em>播放全部(${this.playableSongList.length})</i></a>
<a style="display:none" class="u-btni u-btni-add" hidefocus="true" title="添加到播放列表"></a>
` + this.operationArea.innerHTML;
this.operationArea.children[0].addEventListener("click", () => {
unsafeWindow.top.player.addTo(this.playableSongList, true, true);
weapiRequest("/api/playlist/update/playcount", {
data: {
id: this.playlistId
},
onload: (content) => {
if (content.code == 200) this.playCount.innerHTML = Number(this.playCount.innerHTML) + 1;
}
});
});
this.operationArea.children[1].addEventListener("click", () => {
unsafeWindow.top.player.addTo(this.playableSongList, false, false);
});
this.operationArea.children[0].style.display = "";
this.operationArea.children[1].style.display = "";
this.operationArea.children[2].style.display = "none";
this.operationArea.children[3].style.display = "none";
}
appendBtns() {
var _a;
downloadSongBatch$1(this.playlistId, this.operationArea);
uploadSongBatch$1(this.playlistId, this.operationArea);
const creatorhomeURL = (_a = document.head.querySelector("[property~='music:creator'][content]")) == null ? void 0 : _a.content;
const creatorId = new URLSearchParams(new URL(creatorhomeURL).search).get("id");
if (creatorId == unsafeWindow.GUser.userId) {
sortSongs(this.playlistId, this.operationArea);
}
}
fillTableSong() {
const timestamp = document.querySelector(".m-table > tbody > tr").id.slice(-13);
this.playlistSongList.forEach((songItem, index) => {
this.createRowHTML(songItem, index, timestamp);
});
const table = document.querySelector(".m-table");
if (table) {
const tableStyles = `
.m-table .ncmextend-playlist-playbtn {
display: none;
}
.m-table tr:hover .ncmextend-playlist-playbtn {
display: block;
}
.m-table .ncmextend-playlist-playbtn:has(.ply-z-slt) {
display: block;
}
.m-table .ncmextend-playlist-songindex:has(+ div > .ply-z-slt) {
display: none;
}
.m-table .ncmextend-playlist-songindex {
color: #999;
float: left;
margin-left: -8px;
width: 40px;
text-align: center;
}
.m-table tr:hover .ncmextend-playlist-songindex {
display: none;
}
.m-table .ncmextend-playlist-viponly {
color: #999;
float: left;
margin-left: -8px;
width: 40px;
text-align: center;
}
.m-table .ncmextend-playlist-songtitle {
height: 20px;
margin-right: 20px;
margin-top: 5px;
font-size: 16px;
}
.m-table .ncmextend-playlist-songartist {
height: 20px;
margin-right: 20px;
margin-top: 5px;
}
.m-table .ncmextend-playlist-songalbum {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
text-overflow: ellipsis;
}
`;
GM_addStyle(tableStyles);
table.className = "m-table m-table-rank";
table.innerHTML = `
<thead><tr>
<th style="width:40px;"><div class="wp"> </div></th>
<th><div class="wp">歌名/歌手</div></th>
<th class="w4"><div class="wp af3"></div></th>
<th style="width:90px;"><div class="wp af1"></div></th>
</tr></thead>
<tbody>${this.rowHTMLList.join("")}</tbody>
`;
const playing = unsafeWindow.top.player.getPlaying();
if (playing.track) {
const plybtn = document.querySelector(`[id="${playing.track.id}${timestamp}"] > td:nth-child(1) > div > div.ncmextend-playlist-playbtn > span`);
if (plybtn) {
plybtn.className = plybtn.className.trimEnd() + " ply-z-slt";
}
}
if (/^songlist-(\d+)$/.test(this._hash)) {
const tr = document.querySelector(`[id="${this._hash.slice(9)}${timestamp}"]`);
if (tr) tr.scrollIntoView();
}
this.deleteMoreInfoUI();
}
}
createRowHTML(songItem, index, timestamp) {
this.bodyId = document.body.className.replace(/\D/g, "");
const status = songItem.privilege.st < 0;
const deletable = this.playlist.creator.userId === unsafeWindow.GUser.userId;
const needVIP = songItem.privilege.plLevel == "none" && !status;
const durationText = duringTimeDesc(songItem.song.dt);
const artistText = escapeHTML(songItem.artist);
const annotation = escapeHTML(songItem.song.tns ? songItem.song.tns[0] : songItem.song.alias ? songItem.song.alias[0] : "");
const albumName = escapeHTML(songItem.album);
const songName = escapeHTML(songItem.title);
let playBtnHTML = `<span data-res-id="${songItem.id}" data-res-type="18" data-res-action="play" data-res-from="13" data-res-data="${this.playlist.id}" class="ply "></span>`;
if (needVIP) playBtnHTML = `<span class='ncmextend-playlist-viponly'>需要VIP</span>`;
let artistContent = "";
songItem.song.ar.forEach((ar) => {
if (ar.name) {
if (ar.id > 0) artistContent += `<a href="#/artist?id=${ar.id}" hidefocus="true">${escapeHTML(ar.name)}</a>/`;
else artistContent += escapeHTML(ar.name) + "/";
}
});
if (artistContent.length > 0) artistContent = artistContent.slice(0, -1);
else artistContent = artistText;
let albumContent = albumName;
if (songItem.song.al.id > 0) albumContent = `<a href="#/album?id=${songItem.song.al.id}" title="${albumName}">${albumName}</a>`;
const rowHTML = `
<tr id="${songItem.id}${timestamp}" class="${index % 2 ? "" : "even"} ${status ? "js-dis" : ""}">
<td>
<div class="hd ">
<div class="ncmextend-playlist-songindex">
<span>${index + 1}</span>
</div>
<div class="ncmextend-playlist-playbtn">
${playBtnHTML}
</div>
</div>
</td>
<td class="rank">
<div class="f-cb">
<div class="tt">
<a href="#/song?id=${songItem.id}" title="${songName}">
<img class="rpic" src="${songItem.song.al.picUrl}?param=50y50&quality=100">
</a>
<div class="ncmextend-playlist-songtitle">
<span class="txt" style="max-width: 78%;">
<a href="#/song?id=${songItem.id}"><b title="${songName}${annotation ? ` - (${annotation})` : ""}"><div class="soil"></div>${songName}</b></a>
${annotation ? `<span title="${annotation}" class="s-fc8">${annotation ? ` - (${annotation})` : ""}</span>` : ""}
${songItem.song.mv ? `<a href="#/mv?id=${songItem.song.mv}" title="播放mv" class="mv">MV</a>` : ""}
</span>
</div>
<div title="${artistText}" class="ncmextend-playlist-songartist">
<span title="${artistText}" class="txt" style="max-width: 78%;">
${artistContent}
</span>
</div>
</div>
</div>
</td>
<td>
<div class="ncmextend-playlist-songalbum">
${albumContent}
</div>
</td>
<td class=" s-fc3">
<span class="u-dur candel">${durationText}</span>
<div class="opt hshow">
<a class="u-icn u-icn-81 icn-add" href="javascript:;" title="添加到播放列表" hidefocus="true" data-res-type="18" data-res-id="${songItem.id}" data-res-action="addto" data-res-from="13" data-res-data="${this.playlist.id}"></a>
<span data-res-id="${songItem.id}" data-res-type="18" data-res-action="fav" class="icn icn-fav" title="收藏"></span>
<span data-res-id="${songItem.id}" data-res-type="18" data-res-action="share" data-res-name="${albumName}" data-res-author="${artistText}" data-res-pic="${songItem.song.al.picUrl}" class="icn icn-share" title="分享">分享</span>
${deletable ? `<span data-res-id="${songItem.id}" data-res-type="18" data-res-from="13" data-res-data="${this.playlist.id}" data-res-action="delete" class="icn icn-del" title="删除">删除</span>` : ""}
</div>
</td>
</tr>
`;
this.rowHTMLList.push(rowHTML);
}
deleteMoreInfoUI() {
const seeMore = document.querySelector(".m-playlist-see-more");
if (seeMore) seeMore.parentNode.removeChild(seeMore);
}
updateSongsCloudStatus(songIds) {
songIds.forEach((songId) => {
for (let i = 0; i < this.playlistSongList.length; i++) {
if (this.playlistSongList[i].id == songId) {
this.playlistSongList[i].privilege.cs = true;
break;
}
}
});
}
}
let playlistDetailObj = new PlaylistDetail();
const ShowBatchDLPopUp = (config) => {
Swal.fire({
width: 650,
title: "批量下载",
html: `<div id="my-cbs">
<label><input class="form-check-input" type="checkbox" value="" id="cb-fee1" checked>VIP歌曲</label>
<label><input class="form-check-input" type="checkbox" value="" id="cb-fee4" checked>付费专辑歌曲</label>
<label><input class="form-check-input" type="checkbox" value="" id="cb-fee8" checked>低音质免费歌曲</label>
<label><input class="form-check-input" type="checkbox" value="" id="cb-fee0" checked>免费和云盘未匹配歌曲</label>
</div>
<div id="my-cbs2">
<label><input class="form-check-input" type="checkbox" value="" id="cb-skipcloud">跳过云盘歌曲</label>
<label><input class="form-check-input" type="checkbox" value="" id="cb-dlLyric">下载歌词文件(.lrc)</label>
<label><input class="form-check-input" type="checkbox" value="" id="cb-targetLevelOnly">仅获取到目标音质时下载</label>
</div>
<div id="my-level">
<label>优先下载音质<select id="level-select" class="swal2-select"><option value="jymaster" selected="">超清母带</option><option value="dolby">杜比全景声</option><option value="sky">沉浸环绕声</option><option value="jyeffect">高清环绕声</option><option value="hires">Hi-Res</option><option value="lossless">无损</option><option value="exhigh">极高</option></select></label>
</div>
<div id="my-out">
<label>文件命名格式<select id="out-select" class="swal2-select"><option value="artist-title" selected="">歌手 - 歌曲名</option><option value="title">歌曲名</option><option value="title-artist">歌曲名 - 歌手</option></select></label>
</div>
<div id="my-folder">
<label>文件夹格式<select id="folder-select" class="swal2-select"><option value="none" selected="">不建立文件夹</option><option value="artist">建立歌手文件夹</option><option value="artist-album">建立歌手 \\ 专辑文件夹</option></select></label>
</div>
<div id="my-thread-count">
<label>同时下载的歌曲数<select id="thread-count-select" class="swal2-select"><option value=4 selected="">4</option><option value=3>3</option><option value="2">2</option><option value=1>1</option></select></label>
</div>
`,
confirmButtonText: "开始下载",
showCloseButton: true,
footer: '<span>请将 <b>TamperMonkey</b> 插件设置中的 <b>下载模式</b> 设置为 <b>浏览器 API</b> 并将 <b>/.(mp3|flac|lrc)$/</b> 添加进 <b>文件扩展名白名单</b> 以保证能正常下载。</span><a href="https://github.com/Cinvin/myuserscripts"><img src="https://img.shields.io/github/stars/cinvin/myuserscripts?style=social" alt="Github"></a>',
focusConfirm: false,
preConfirm: () => {
let container = Swal.getHtmlContainer();
return {
free: container.querySelector("#cb-fee0").checked,
VIP: container.querySelector("#cb-fee1").checked,
pay: container.querySelector("#cb-fee4").checked,
lowFree: container.querySelector("#cb-fee8").checked,
skipCloud: container.querySelector("#cb-skipcloud").checked,
downloadLyric: container.querySelector("#cb-dlLyric").checked,
targetLevelOnly: container.querySelector("#cb-targetLevelOnly").checked,
level: container.querySelector("#level-select").value,
out: container.querySelector("#out-select").value,
folder: container.querySelector("#folder-select").value,
threadCount: Number(container.querySelector("#thread-count-select").value),
listType: config.listType,
listId: config.listId,
action: "batchDownload"
};
}
}).then((res) => {
if (res.isConfirmed) {
if (res.value.listType == "playlist") {
let filtedSongList = filterSongs(playlistDetailObj.playlistSongList, res.value);
createSongsUrlApi(filtedSongList, res.value);
} else if (res.value.listType == "album") {
let filtedSongList = filterSongs(albumDetailObj.albumSongList, res.value);
createSongsUrlApi(filtedSongList, res.value);
}
}
});
};
const downloadSongBatch = (albumId, uiArea) => {
let btnBatchDownload = createBigButton("批量下载", uiArea, 1);
btnBatchDownload.addEventListener("click", () => {
ShowBatchDLPopUp({ listType: "album", listId: albumId });
});
};
const uploadSongBatch = (albumId, uiArea) => {
let btnBatchUpload = createBigButton("批量转存云盘", uiArea, 1);
btnBatchUpload.addEventListener("click", () => {
ShowBatchDLULPopUp({ listType: "album", listId: albumId });
});
};
class AlbumDetail {
constructor() {
this.domReady = false;
this.dataFetched = false;
this.flag = true;
this.albumSongList = [];
this.albumRes = null;
this.albumDiscList = [];
const params2 = new URLSearchParams(unsafeWindow.location.search);
this.playlistId = Number(params2.get("id"));
this._hash = params2.get("_hash");
}
fetchAlbumData(albumId) {
this.albumId = albumId;
weapiRequest(`/api/v1/album/${albumId}`, {
onload: (content) => {
this.albumRes = content;
for (let i = 0; i < content.songs.length; i++) {
let songItem = {
id: content.songs[i].id,
title: content.songs[i].name,
artist: getArtistTextInSongDetail(content.songs[i]),
album: getAlbumTextInSongDetail(content.songs[i]),
song: content.songs[i],
privilege: content.songs[i].privilege
};
this.albumSongList.push(songItem);
const discInfos = content.songs[i].cd ? content.songs[i].cd.split(" ") : [];
if (discInfos.length > 0) {
const discIndex = parseInt(discInfos[0]);
while (this.albumDiscList.length < discIndex) {
this.albumDiscList.push(null);
}
if (this.albumDiscList[discIndex - 1] === null) {
let discTitle = `Disc ${discIndex}`;
if (discInfos.length > 1) discTitle += " " + discInfos.slice(1).join(" ");
this.albumDiscList[discIndex - 1] = { title: discTitle, songs: [] };
}
this.albumDiscList[discIndex - 1].songs.push(songItem);
}
}
this.dataFetched = true;
this.checkStartCreateDom();
}
});
}
onDomReady() {
this.domReady = true;
this.descriptionArea = document.querySelector(".topblk");
this.operationArea = document.querySelector("#content-operation");
this.checkStartCreateDom();
}
checkStartCreateDom() {
if (this.domReady && this.dataFetched && this.flag) {
this.flag = false;
this.AppendInfos();
this.AppendBtns();
if (this.albumDiscList.length > 1) this.createDiscTable();
}
}
AppendInfos() {
this.descriptionArea.innerHTML += `<p class="intr"><b>专辑类型:</b>${this.albumRes.album.type} ${this.albumRes.album.subType}</p>`;
if ((this.albumRes.album.mark & songMark.explicit) == songMark.explicit) {
this.descriptionArea.innerHTML += `<p class="intr"><b>🅴:</b>内容含有不健康因素</p>`;
}
if (this.albumRes.album.blurPicUrl) {
this.descriptionArea.innerHTML += `<p class="intr"><a class="s-fc7" href="${this.albumRes.album.blurPicUrl}" target="_blank">专辑封面原图</a></p>`;
}
}
AppendBtns() {
downloadSongBatch(this.albumId, this.operationArea);
uploadSongBatch(this.albumId, this.operationArea);
}
createDiscTable() {
const tableRows = document.querySelectorAll(".m-table-album tr");
const tableParent = document.querySelector("div:has(> .m-table-album)");
let isTableCreated = false;
this.albumDiscList.forEach((disc, index) => {
if (disc === null) return;
isTableCreated = true;
tableParent.innerHTML += `
<div class="u-title u-title-1 f-cb" style="margin-top: 10px"><h3><span class="f-ff2">${disc.title}</span></h3><span class="sub s-fc3">${disc.songs.length}首歌</span></div>
<table class="m-table m-table-album">
<thead><tr><th class="first w1"><div class="wp"> </div></th><th><div class="wp">歌曲标题</div></th><th class="w2-1"><div class="wp">时长</div></th><th class="w4"><div class="wp">歌手</div></th></tr></thead>
<tbody id="ncmextend-disc-${index}"></tbody>
</table>
`;
let tbody = tableParent.querySelector(`#ncmextend-disc-${index}`);
disc.songs.forEach((songItem, songIndex) => {
tableRows.forEach((tableRow) => {
if (Number(tableRow.id.slice(0, -13)) === songItem.id) {
tableRow.querySelector(".num").innerHTML = songItem.song.no;
tableRow.className = songIndex % 2 == 0 ? "even " : "";
if (songItem.privilege.st < 0) tableRow.className += "js-dis";
tbody.appendChild(tableRow);
}
});
});
});
if (isTableCreated) {
const originTitle = document.querySelector(".n-songtb .u-title");
originTitle.parentNode.removeChild(originTitle);
tableParent.removeChild(tableParent.firstChild);
}
if (/^songlist-(\d+)$/.test(this._hash) && tableRows.length > 0) {
const timestamp = document.querySelector(".m-table > tbody > tr").id.slice(-13);
const tr = document.querySelector(`[id="${this._hash.slice(9)}${timestamp}"]`);
if (tr) tr.scrollIntoView();
}
}
updateSongsCloudStatus(songIds) {
songIds.forEach((songId) => {
for (let i = 0; i < this.albumSongList.length; i++) {
if (this.albumSongList[i].id == songId) {
this.albumSongList[i].privilege.cs = true;
break;
}
}
});
}
}
let albumDetailObj = new AlbumDetail();
const hookWindowForCommentBox = (window) => {
ah.proxy({
onResponse: (response, handler) => {
if (response.config.url.includes("/weapi/comment/resource/comments/get")) {
let content = JSON.parse(response.response);
storageCommentInfo(content);
handler.next(response);
} else {
handler.next(response);
}
}
}, window);
};
const storageCommentInfo = (CommentRes) => {
var _a, _b, _c;
if (!unsafeWindow.top.GUserScriptObjects.storageCommentInfos) unsafeWindow.top.GUserScriptObjects.storageCommentInfos = {};
const comments = CommentRes.data.comments.concat(CommentRes.data.hotComments);
for (let comment of comments) {
if (!(comment == null ? void 0 : comment.commentId)) continue;
let appendText = "";
if ((_a = comment == null ? void 0 : comment.ipLocation) == null ? void 0 : _a.location) appendText += comment.ipLocation.location + " ";
if ((_c = (_b = comment == null ? void 0 : comment.extInfo) == null ? void 0 : _b.endpoint) == null ? void 0 : _c.OS_TYPE) appendText += comment.extInfo.endpoint.OS_TYPE;
unsafeWindow.top.GUserScriptObjects.storageCommentInfos[String(comment.commentId)] = appendText.trim();
}
};
const observerCommentBox = (commentBox) => {
let observer = new MutationObserver((mutations, observer2) => {
mutations.forEach((mutation) => {
if (mutation.type == "childList" && mutation.addedNodes.length > 0) {
for (let node of mutation.addedNodes) {
if (node.className == "itm") {
commentItemAddInfo(node);
}
}
}
});
});
observer.observe(commentBox, {
childList: true,
subtree: true
});
};
const commentItemAddInfo = (commentItem) => {
if (commentItem.querySelector(".ipInfo")) return;
const commentId = commentItem.getAttribute("data-id");
let timeArea = commentItem.querySelector("div.time");
if (unsafeWindow.top.GUserScriptObjects.storageCommentInfos[commentId]) {
timeArea.innerHTML += ` <span class="ipInfo">${unsafeWindow.top.GUserScriptObjects.storageCommentInfos[commentId]}</span>`;
}
};
const InfoFirstPage = (commentBox) => {
const commentItems = commentBox.querySelectorAll("div.itm");
for (const commentItem of commentItems) {
commentItemAddInfo(commentItem);
}
};
const addCommentWithCumstomIP = (commentBox) => {
const commentTextarea = commentBox.querySelector("textarea");
const threadId = commentBox.getAttribute("data-tid");
const btnsArea = commentBox.querySelector(".btns");
let ipBtn = document.createElement("a");
ipBtn.className = "s-fc7";
ipBtn.innerHTML = "使用指定IP地址评论";
ipBtn.addEventListener("click", () => {
const content = commentTextarea.value.trim();
if (content.length == 0) {
showConfirmBox("评论内容不能为空");
return;
}
GM_getValue("lastIPValue", "");
Swal.fire({
input: "text",
inputLabel: "IP地址",
inputValue: GM_getValue("lastIPValue", ""),
inputValidator: (value) => {
if (!/((25[0-5]|2[0-4]\d|((1\d{2})|([1-9]?\d)))\.){3}(25[0-5]|2[0-4]\d|((1\d{2})|([1-9]?\d)))/.test(value)) {
return "IP格式不正确";
}
},
confirmButtonText: "发送评论",
showCloseButton: true,
footer: `
<div>可参考:<a href="https://zh-hans.ipshu.com/country-list" target="_blank">IP 国家/地区列表</a></div>
<div>需不显示属地请填 <b>127.0.0.1</b></div>
`
}).then((result) => {
if (result.isConfirmed) {
GM_setValue("lastIPValue", result.value);
weapiRequest("/api/resource/comments/add", {
data: {
threadId,
content
},
ip: result.value,
clientType: "web",
onload: (res) => {
console.log(res);
if (res.code == 200) {
showConfirmBox("评论成功,请刷新网页查看");
} else {
showConfirmBox("评论失败," + JSON.stringify(res));
}
}
});
}
});
});
btnsArea.appendChild(ipBtn);
};
const registerMenuCommand = () => {
GM_registerMenuCommand(`优先试听音质`, setLevel);
function setLevel() {
Swal.fire({
title: "优先试听音质",
input: "select",
inputOptions: levelOptions,
inputValue: GM_getValue("DEFAULT_LEVEL", defaultOfDEFAULT_LEVEL),
confirmButtonText: "确定",
showCloseButton: true,
footer: '<a href="https://github.com/Cinvin/myuserscripts" target="_blank"><img src="https://img.shields.io/github/stars/cinvin/myuserscripts?style=social" alt="Github"></a>'
}).then((result) => {
if (result.isConfirmed) {
GM_setValue("DEFAULT_LEVEL", result.value);
}
});
}
};
const url = _unsafeWindow.location.href;
const params = new URLSearchParams(_unsafeWindow.location.search);
const paramId = Number(params.get("id"));
const onStart = () => {
console.log("[ncmExtend] onStart()");
if (_unsafeWindow.self === _unsafeWindow.top) {
_unsafeWindow.GUserScriptObjects = {};
hookTopWindow();
const iframes = document.getElementsByTagName("iframe");
for (let iframe of iframes) {
hookWindowForCommentBox(iframe.contentWindow);
}
} else if (_unsafeWindow.name === "contentFrame") {
hookWindowForCommentBox(_unsafeWindow);
if (paramId > 0) {
if (url.includes("/song?")) {
songDetailObj.fetchSongData(paramId);
} else if (url.includes("/playlist?")) {
playlistDetailObj.fetchPlaylistFullData(paramId);
} else if (url.includes("/album?")) {
albumDetailObj.fetchAlbumData(paramId);
}
}
}
};
const onDomReady = () => {
console.log("[ncmExtend] onDomReady()");
if (paramId > 0) {
if (url.includes("/user/home?")) {
myHomeMain(paramId);
} else if (url.includes("/song?")) {
songDetailObj.onDomReady();
} else if (url.includes("/playlist?")) {
playlistDetailObj.onDomReady();
} else if (url.includes("/album?")) {
albumDetailObj.onDomReady();
}
}
const commentBox = document.querySelector("#comment-box");
if (commentBox) {
observerCommentBox(commentBox);
InfoFirstPage(commentBox);
addCommentWithCumstomIP(commentBox);
}
if (_unsafeWindow.name === "contentFrame") {
registerMenuCommand();
}
};
const DOM_READY = "DOMContentLoaded";
onStart();
_unsafeWindow.addEventListener(DOM_READY, () => {
onDomReady();
});
})();