// ==UserScript== // @name 网易云音乐:云盘快传(含周杰伦)|歌曲下载&转存云盘|云盘匹配纠正|高音质试听 // @namespace https://github.com/Cinvin/myuserscripts // @version 4.2.8 // @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_cookie // @grant GM_download // @grant GM_getValue // @grant GM_registerMenuCommand // @grant GM_setValue // @grant GM_xmlhttpRequest // @grant unsafeWindow // @run-at document-start // @downloadURL https://update.greasyfork.icu/scripts/459633/%E7%BD%91%E6%98%93%E4%BA%91%E9%9F%B3%E4%B9%90%3A%E4%BA%91%E7%9B%98%E5%BF%AB%E4%BC%A0%28%E5%90%AB%E5%91%A8%E6%9D%B0%E4%BC%A6%29%7C%E6%AD%8C%E6%9B%B2%E4%B8%8B%E8%BD%BD%E8%BD%AC%E5%AD%98%E4%BA%91%E7%9B%98%7C%E4%BA%91%E7%9B%98%E5%8C%B9%E9%85%8D%E7%BA%A0%E6%AD%A3%7C%E9%AB%98%E9%9F%B3%E8%B4%A8%E8%AF%95%E5%90%AC.user.js // @updateURL https://update.greasyfork.icu/scripts/459633/%E7%BD%91%E6%98%93%E4%BA%91%E9%9F%B3%E4%B9%90%3A%E4%BA%91%E7%9B%98%E5%BF%AB%E4%BC%A0%28%E5%90%AB%E5%91%A8%E6%9D%B0%E4%BC%A6%29%7C%E6%AD%8C%E6%9B%B2%E4%B8%8B%E8%BD%BD%E8%BD%AC%E5%AD%98%E4%BA%91%E7%9B%98%7C%E4%BA%91%E7%9B%98%E5%8C%B9%E9%85%8D%E7%BA%A0%E6%AD%A3%7C%E9%AB%98%E9%9F%B3%E8%B4%A8%E8%AF%95%E5%90%AC.meta.js // ==/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) }; }; var 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;" }; var 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 requestQueue = []; const REQUEST_INTERVAL = 50; setInterval(() => { if (requestQueue.length > 0) { const requestFn = requestQueue.shift(); requestFn(); } }, REQUEST_INTERVAL); setDeviceId(); 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 }; enqueueAPIRequest(details); }; function enqueueAPIRequest(data) { return new Promise((resolve, reject) => { requestQueue.push(() => { callAPI(data); }); }); } function callAPI(data) { GM_xmlhttpRequest(data); } function setDeviceId() { const requestHeader = JSON.parse(GM_getValue("requestHeader", "{}")); if ("appendCookie" in requestHeader) { CookieMap["pc"] = requestHeader.appendCookie; UserAgentMap["pc"] = requestHeader.userAgent; } else { GM_cookie.list({ name: "sDeviceId" }, function(cookies, error) { if (!error) { if (cookies.length > 0) { CookieMap["android"] += `deviceId=${cookies[0].value};`; CookieMap["pc"] += `deviceId=${cookies[0].value};`; } } else { console.error(error); } }); } } 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; }; const headerSetting = (uiArea) => { let btnExport = createBigButton("设置请求头", uiArea, 2); btnExport.addEventListener("click", openExportPopup); function openExportPopup() { Swal.fire({ title: "设置客户端请求头(Header)", showCloseButton: true, html: `
`, footer: '
以上内容需要自行使用Reqable等抓包工具,获取网易云音乐客户端的请求头。
设置的目的是尽量模拟客户端调用,避免被风控系统检测到。
', confirmButtonText: "设置", preConfirm: () => { return { cookie: document.getElementById("text-cookie").value.trim(), userAgent: document.getElementById("text-userAgent").value.trim() }; }, didOpen: () => { let container = Swal.getHtmlContainer(); let cookieInput = container.querySelector("#text-cookie"); let userAgentInput = container.querySelector("#text-userAgent"); const requestHeader = JSON.parse(GM_getValue("requestHeader", "{}")); if (requestHeader.originalCookie) { cookieInput.value = requestHeader.originalCookie; } if (requestHeader.userAgent) { userAgentInput.value = requestHeader.userAgent; } } }).then((result) => { if (result.isConfirmed) { setHeader(result.value); } }); } function setHeader(config) { const cookieObject = tryParseJSON(config.cookie) || parseCookie(config.cookie); if (config.userAgent.length == 0) { showConfirmBox("请填写UserAgent"); return; } if (Object.keys(cookieObject).length == 0) { showConfirmBox("cookie格式不正确,支持标准的cookie格式和JSON格式"); return; } if (!(cookieObject.MUSIC_U && cookieObject.deviceId && cookieObject.clientSign)) { showConfirmBox("cookie内容不完整,cookie中一定会有MUSIC_U、deviceId等字段"); return; } const excludeCookie = ["MUSIC_U"]; let appendCookieText = ""; GM_cookie.list({}, function(cookies, error) { if (!error) { const webCoookieObject = {}; cookies.forEach((item) => { webCoookieObject[item.name] = item; }); for (const key in cookieObject) { if (key in webCoookieObject) { GM_cookie.set({ name: key, value: cookieObject[key], domain: webCoookieObject[key].domain, path: webCoookieObject[key].path, secure: webCoookieObject[key].secure, httpOnly: webCoookieObject[key].httpOnly, expirationDate: webCoookieObject[key].expirationDate }); } else if (!excludeCookie.includes(key)) { appendCookieText += `${key}=${cookieObject[key]};`; } } GM_cookie.set({ name: "MUSIC_U", value: cookieObject.MUSIC_U, domain: ".music.163.com", path: "/", httpOnly: true, expirationDate: Math.floor(Date.now() / 1e3) + 60 * 60 * 24 * 400 }); GM_setValue("requestHeader", JSON.stringify({ originalCookie: config.cookie, appendCookie: appendCookieText, userAgent: config.userAgent })); showConfirmBox("设置完成,刷新网页生效。"); } else { console.error(error); } }); } function parseCookie(cookieString) { return cookieString.split(";").map((part) => part.trim()).filter((part) => part).reduce((cookies, part) => { const [key, value] = part.split("=", 2); cookies[key.trim()] = value ? value.trim() : ""; return cookies; }, {}); } function tryParseJSON(jsonString) { try { var o = JSON.parse(jsonString); if (o && typeof o === "object") { return o; } } catch (e) { } return false; } }; 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 extractLrcRegex = /^(?(?:\[.+?\])+)(?!\[)(?.+)$/gm; const extractTimestampRegex = /\[(?\d+):(?\d+)(?:\.|:)*(?\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: `
歌曲标题歌手专辑音质大小进度
`, footer: "
", 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) => `${song2.title}`).join()}`; } Swal.update({ allowOutsideClick: true, allowEscapeKey: true, showCloseButton: true, showConfirmButton: true, html: finnshText }); } return; } tableRowDOM.innerHTML = `${song.title}${song.artist}${song.album}`; 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"); } }); }; 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: `
免费歌曲
`, confirmButtonText: "开始转存", showCloseButton: true, footer: 'Github', 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 = ` ` + 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 = `
 
歌名/歌手
${this.rowHTMLList.join("")} `; 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 = ``; if (needVIP) playBtnHTML = `需要VIP`; let artistContent = ""; songItem.song.ar.forEach((ar) => { if (ar.name) { if (ar.id > 0) artistContent += `${escapeHTML(ar.name)}/`; 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 = `${albumName}`; const rowHTML = `
${index + 1}
${playBtnHTML}
${songName}
${annotation ? `${annotation ? ` - (${annotation})` : ""}` : ""} ${songItem.song.mv ? `MV` : ""}
${artistContent}
${albumContent}
${durationText}
分享 ${deletable ? `删除` : ""}
`; 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: `
`, confirmButtonText: "开始下载", showCloseButton: true, footer: '请将 TamperMonkey 插件设置中的 下载模式 设置为 浏览器 API 并将 /.(mp3|flac|lrc)$/ 添加进 文件扩展名白名单 以保证能正常下载。Github', 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 += `

专辑类型:${this.albumRes.album.type} ${this.albumRes.album.subType}

`; if ((this.albumRes.album.mark & songMark.explicit) == songMark.explicit) { this.descriptionArea.innerHTML += `

🅴:内容含有不健康因素

`; } if (this.albumRes.album.blurPicUrl) { this.descriptionArea.innerHTML += `

专辑封面原图

`; } } 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 += `

${disc.title}

${disc.songs.length}首歌
 
歌曲标题
时长
歌手
`; 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 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); } } } } 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 = { working: false, stopFlag: false, songIndexs: [], checkOffset: 0, importOffset: 0, matchOffset: 0 }; } start() { this.showPopup(); } showPopup() { Swal.fire({ showCloseButton: true, allowOutsideClick: false, allowEscapeKey: false, showConfirmButton: false, width: 800, html: `
操作歌曲标题歌手时长文件信息备注
`, footer: "
", 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) { this.batchUpload.stopFlag = true; return; } this.batchUpload.songIndexs = []; this.filter.songIndexs.forEach((idx) => { const song = uploader.songs[idx]; if (!song.uploaded && song.uploadType != 0) { uploader.batchUpload.songIndexs.push(idx); } }); if (this.batchUpload.songIndexs.length == 0) { showTips("没有需要上传的歌曲", 1); return; } this.batchUpload.working = true; this.batchUpload.stopFlag = false; this.batchUpload.checkOffset = 0; this.batchUpload.importOffset = 0; this.batchUpload.matchOffset = 0; this.btnUploadBatch.innerHTML = "停止"; this.uploadSongBatch(); }); this.fetchSongInfo(); }, willClose: () => { this.batchUpload.stopFlag = true; } }); } 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) { if (content.code != 200 || !content.songs) { setTimeout(uploader.fetchSongInfoSub(ids, startIndex), 1e3); return; } 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 = `${song.name}${song.artists}${song.dt}${fileSizeDesc(song.size)} ${song.ext.toUpperCase()}`; 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 uploadbtn = tablerow.querySelector(".uploadbtn"); uploadbtn.addEventListener("click", () => { if (this.batchUpload.working) { return; } this.uploadSong(i); }); let reuploadbtn = tablerow.querySelector(".reuploadbtn"); reuploadbtn.addEventListener("click", () => { if (this.batchUpload.working) { return; } this.uploadSongWay2Part1(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 && song.uploadType != 0) { countCanUpload += 1; sizeTotal += song.size; } }); this.btnUploadBatch.innerHTML = "全部上传"; if (countCanUpload > 0) { this.btnUploadBatch.innerHTML += ` (${countCanUpload}首 ${fileSizeDesc(sizeTotal)})`; } } setReUploadButtonViewable(songIndex) { let uploadbtn = this.songs[songIndex].tablerow.querySelector(".uploadbtn"); uploadbtn.style.display = "none"; let reuploadbtn = this.songs[songIndex].tablerow.querySelector(".reuploadbtn"); reuploadbtn.style.display = ""; } uploadSong(songIndex) { let song = this.songs[songIndex]; if (song.cloudId) { if (song.uploadType == 1) { this.uploadSongImport(songIndex); } else if (song.uploadType == 0) { this.uploadSongWay2Part1(songIndex); } return; } 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.uploadType = res1.data[0].upload; if (song.uploadType == 1) { console.log(song.name, "1.检查资源", res1); showTips(`(1/3)${song.name} 检查资源`, 1); song.cloudId = res1.data[0].songId; uploader.uploadSongImport(songIndex); } else if (song.uploadType == 0) { uploader.setReUploadButtonViewable(songIndex); song.cloudId = res1.data[0].songId; uploader.onUploadFinnsh(); } else { console.error(song.name, "1.检查资源", res1); showTips(`(1/3)${song.name} 文件无法上传`, 2); song.uploaded = true; let btnUpload = song.tablerow.querySelector(".uploadbtn"); btnUpload.innerHTML = "无法上传"; btnUpload.disabled = "disabled"; uploader.onUploadFinnsh(); } }, onerror: function(res) { console.error(song.name, "1.检查资源", res); uploader.onUploadFail(songIndex); } }); } catch (e) { console.error(e); uploader.onUploadFail(songIndex); } } uploadSongImport(songIndex) { let song = this.songs[songIndex]; if (song.cloudSongId) { this.uploadSongMatch(songIndex); return; } 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); } } 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); } } 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); } }); } onUploadFail(songIndex) { let song = this.songs[songIndex]; showTips(`${song.name} - ${song.artists} - ${song.album} 上传失败`, 2); this.onUploadFinnsh(); } onUploadSucess(songIndex) { let song = this.songs[songIndex]; showTips(`${song.name} - ${song.artists} - ${song.album} 上传成功`, 1); this.setSongUploaded(song); this.onUploadFinnsh(); } onUploadFinnsh() { this.renderFilterInfo(); } setSongUploaded(song) { song.uploaded = true; let btnSelect = ".uploadbtn"; if (song.uploadType == 0) { btnSelect = ".reuploadbtn"; } let btnUpload = song.tablerow.querySelector(btnSelect); btnUpload.innerHTML = "已上传"; btnUpload.disabled = "disabled"; } uploadSongBatch(retry = false) { if (this.batchUpload.checkOffset >= this.batchUpload.songIndexs.length) { this.onBatchUploadFinnsh(); showTips("批量上传完成", 1); return; } if (this.batchUpload.stopFlag) { this.onBatchUploadFinnsh(); return; } let songMD5IndexMap = {}; let songCheckDatas = []; let indexOfSongIndexs = this.batchUpload.checkOffset; let endIndex = Math.min(this.batchUpload.songIndexs.length, this.batchUpload.checkOffset + CheckAPIDataLimit); while (indexOfSongIndexs < endIndex) { let songIndex = this.batchUpload.songIndexs[indexOfSongIndexs]; let song = this.songs[songIndex]; songCheckDatas.push({ md5: song.md5, songId: song.id, bitrate: song.bitrate, fileSize: song.size }); songMD5IndexMap[song.md5] = songIndex; indexOfSongIndexs += 1; } 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) { showTips("接口调用失败,1秒后重试", 2); this.addLog("接口调用失败,1秒后重试"); sleep(1e3).then(() => { this.uploadSongBatch(retry = true); }); } else { this.batchUpload.checkOffset = endIndex; this.uploadSongBatch(); } return; } showTips(`获取第 ${this.batchUpload.checkOffset + 1} 到 第 ${indexOfSongIndexs} 首歌曲云盘ID`, 1); console.log("获取文件云盘ID接口", content); content.data.forEach((fileData) => { const songIndex = songMD5IndexMap[fileData.md5]; this.songs[songIndex].uploadType = fileData.upload; this.songs[songIndex].cloudId = fileData.songId; if (fileData.upload == 0) { this.setReUploadButtonViewable(songIndex); } else if (fileData.upload > 1) { this.songs[songIndex].uploaded = true; let btnUpload = this.songs[songIndex].tablerow.querySelector(".uploadbtn"); btnUpload.innerHTML = "无法上传"; btnUpload.disabled = "disabled"; } }); this.batchUpload.checkOffset = endIndex; this.uploadSongImportBatch(); }, onerror: (content) => { console.error("获取文件云盘ID接口", content); if (!retry) { showTips("接口调用失败,1秒后重试", 2); sleep(1e3).then(() => { this.uploadSongBatch(retry = true); }); } else { this.batchUpload.checkOffset = endIndex; this.uploadSongBatch(); } } }); } uploadSongImportBatch(retry = false) { if (this.batchUpload.importOffset >= this.batchUpload.checkOffset) { this.uploadSongBatch(); return; } if (this.batchUpload.stopFlag) { this.onBatchUploadFinnsh(); return; } let songCloudIdIndexMap = {}; let importSongDatas = []; let indexOfSongIndexs = this.batchUpload.importOffset; let maxIndex = Math.min(this.batchUpload.checkOffset, this.batchUpload.importOffset + importAPIDataLimit); while (indexOfSongIndexs < maxIndex) { let songIndex = this.batchUpload.songIndexs[indexOfSongIndexs]; let song = this.songs[songIndex]; if ("cloudId" in song) { importSongDatas.push({ songId: song.cloudId, bitrate: song.bitrate, song: song.filename, artist: song.artists, album: song.album, fileName: song.filename }); songCloudIdIndexMap[song.cloudId] = songIndex; } indexOfSongIndexs += 1; } weapiRequest("/api/cloud/user/song/import", { data: { uploadType: 0, songs: JSON.stringify(importSongDatas) }, onload: (content) => { if (content.code != 200) { console.error("歌曲导入云盘接口", content); if (!retry) { showTips("接口调用失败,1秒后重试", 1); sleep(1e3).then(() => { this.uploadSongImportBatch(retry = true); }); } else { this.batchUpload.importOffset = indexOfSongIndexs; this.uploadSongImportBatch(); } } console.log("歌曲导入云盘接口", content); if (content.data.successSongs.length > 0) { let successSongNames = []; content.data.successSongs.forEach((successSong) => { let song = this.songs[songCloudIdIndexMap[successSong.songId]]; song.cloudSongId = successSong.song.songId; if (song.cloudSongId == song.id) { this.setSongUploaded(song); successSongNames.push(song.name); } }); showTips(`成功上传${successSongNames.length}首:${successSongNames.join("、")}`, 1); } this.batchUpload.importOffset = indexOfSongIndexs; this.uploadSongMatchBatch(); }, onerror: (content) => { console.error("歌曲导入云盘", content); if (!retry) { showTips("接口调用失败,1秒后重试", 1); sleep(1e3).then(() => { this.uploadSongImportBatch(retry = true); }); } else { this.batchUpload.importOffset = indexOfSongIndexs; this.uploadSongImportBatch(); } } }); } uploadSongMatchBatch(retry = false) { if (this.batchUpload.matchOffset >= this.batchUpload.importOffset) { this.uploadSongImportBatch(); return; } let songIndex = this.batchUpload.songIndexs[this.batchUpload.matchOffset]; let song = this.songs[songIndex]; if (!("cloudSongId" in song) || song.cloudSongId == song.id || song.id <= 0) { this.batchUpload.matchOffset += 1; this.uploadSongMatchBatch(); return; } weapiRequest("/api/cloud/user/song/match", { data: { songId: song.cloudSongId, adjustSongId: song.id }, onload: (res5) => { if (res5.code != 200) { console.error(song.name, "匹配歌曲", res5); if (!retry) { showTips("接口调用失败,1秒后重试", 1); sleep(1e3).then(() => { this.uploadSongMatchBatch(retry = true); }); } else { this.batchUpload.matchOffset += 1; this.uploadSongMatchBatch(); } return; } console.log(song.name, "匹配歌曲", res5); this.setSongUploaded(song); showTips(`成功上传1首歌曲`, 1); this.batchUpload.matchOffset += 1; this.uploadSongMatchBatch(); }, onerror: function(res) { console.error(song.name, "匹配歌曲", res); if (!retry) { showTips("接口调用失败,1秒后重试", 1); sleep(1e3).then(() => { this.uploadSongMatchBatch(retry = true); }); } else { this.batchUpload.matchOffset += 1; this.uploadSongMatchBatch(); } } }); } onBatchUploadFinnsh() { this.batchUpload.working = false; 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: '
建议先设置好请求头,以避免上传失败
Github
', 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: ` `, footer: `

${this.controls.cloudDesc ? this.controls.cloudDesc.innerHTML : ""}
`, 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 = `操作歌曲标题歌手时长文件信息上传日期 `; 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 += `${ar.name},`; 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 = `${song.simpleSong.name}${artist}${duringTimeDesc(song.simpleSong.dt)}${fileSizeDesc(song.fileSize)} ${levelDesc(song.simpleSong.privilege.plLevel)}${addTime}`; 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} 匹配纠正`, width: 800, confirmButtonText: "匹配", html: `
操作歌曲标题歌手时长
`, footer: "ID为0时解除匹配 歌曲页面网址里的数字就是ID", preConfirm: () => { const targetId = document.getElementById("target-id").value; return targetId; }, didOpen: () => { const container = Swal.getHtmlContainer(); this.targetIdDom = container.querySelector("#target-id"); this.searchDom = container.querySelector("#search-text"); this.searchBtn = container.querySelector("#btn-search"); this.titleDOM = Swal.getTitle(); this.tbody = container.querySelector("tbody"); this.fileDuringTime = 0; let songTitle = song.songName; let songAlbum = song.album; let songArtist = song.artist; const pointIndex = songTitle.lastIndexOf("."); if (pointIndex > 0) { songTitle = songTitle.substring(0, pointIndex); } const hyphenIndex = songTitle.lastIndexOf("-"); if (hyphenIndex > 0) { songArtist = songTitle.substring(0, hyphenIndex).trim(); songTitle = songTitle.substring(hyphenIndex + 1).trim(); } if (songArtist === "未知" || songArtist === "未知歌手") songArtist = ""; if (songAlbum === "未知" || songAlbum === "未知专辑") songAlbum = ""; const keyword = `${songTitle} ${songArtist} ${songAlbum}`.trim(); this.searchDom.value = keyword; weapiRequest("/api/batch", { data: { "/api/song/enhance/player/url/v1": JSON.stringify({ immerseType: "ste", ids: JSON.stringify([song.simpleSong.id]), level: "standard", encodeType: "mp3" }), "/api/cloudsearch/get/web": JSON.stringify({ s: keyword, type: 1, limit: 30, offset: 0, total: true }) }, onload: (content) => { if (content.code != 200) { return; } const playerContent = content["/api/song/enhance/player/url/v1"]; const searchContent = content["/api/cloudsearch/get/web"]; this.fileDuringTime = playerContent.data[0].time; this.titleDOM.innerHTML += " 文件时长" + duringTimeDesc(this.fileDuringTime); this.fiilSearchTable(searchContent); } }); this.searchBtn.addEventListener("click", () => { const searchWord = this.searchDom.value.trim(); weapiRequest("/api/cloudsearch/get/web", { data: { s: searchWord, type: 1, limit: 30, offset: 0, total: true }, onload: (searchContent) => { if (searchContent.code != 200) { return; } this.fiilSearchTable(searchContent); } }); }); } }).then((result) => { if (result.isConfirmed && result.value.length > 0) { 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(); } }); } fiilSearchTable(searchContent) { this.tbody.innerHTML = ""; if (searchContent.result.songs.length > 0) { searchContent.result.songs.forEach((matchSong) => { let tablerow = document.createElement("tr"); let songName = matchSong.name; if ("pc" in matchSong) { songName += " ☁️"; } const artists = matchSong.ar.map((ar) => `${ar.name}`).join(); const needHighLight = Math.abs(matchSong.dt - this.fileDuringTime) < 1e3; const dtstyle = needHighLight ? "color:SpringGreen;" : ""; tablerow.innerHTML = `${songName}${artists}${duringTimeDesc(matchSong.dt)}`; let selectbtn = tablerow.querySelector(".selectbtn"); selectbtn.addEventListener("click", () => { this.targetIdDom.value = matchSong.id; }); this.tbody.appendChild(tablerow); }); } } } }; 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: "
寻找网易云音源比云盘音质好的歌曲,然后进行删除并重新上传
⚠️可能会出现删除了歌曲但上传失败的情况
因此请自行做好备份
建议先设置好请求头,以避免上传失败
", 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: `
操作歌曲标题歌手云盘音源目标音源
`, footer: "
", 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 = `${song.name}${song.artists}${levelDesc(song.fileinfo.originalLevel)} ${song.fileinfo.originalBr}k${tagetLevelDesc} ${song.fileinfo.tagetBr}k`; 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: `
`, 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: `
操作歌曲标题歌手专辑
`, 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 = `${this.task[i].title}${this.task[i].artist}${this.task[i].album}`; 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: `
`, 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 btnVIPfreeB = createBigButton("限免VIP歌曲", 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, '歌单「会员雷达」的内容', 22); } }); } function openVIPDownLoadPopup(songIds, footer, trialMode) { Swal.fire({ title: "限免VIP歌曲", showCloseButton: true, showConfirmButton: false, width: 800, html: `
操作歌曲标题歌手时长大小
`, footer: footer + ',只有标准(128k)音质Github', 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 = getArtistTextInSongDetail(content.songs[i]); let songTitle = content.songs[i].name; let filename = nameFileWithoutExt(songTitle, songArtist, "artist-title"); songArtist = content.songs[i].ar ? content.songs[i].ar.map((ar) => `${ar.name}`).join() : ""; let tablerow = document.createElement("tr"); tablerow.innerHTML = `${content.songs[i].name}${songArtist}${duringTimeDesc(content.songs[i].dt || 0)}${fileSizeDesc(content.songs[i].l.size)}`; 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: `
`, 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) { showTips(`正在获取第${offset + 1}到${offset + 1e3}首云盘歌曲信息`, 1); weapiRequest("/api/v1/cloud/get", { data: { limit: 1e3, offset }, onload: (res) => { if (res.code != 200 || !res.data) { setTimeout(exportCloudSub(filter, config, offset), 1e3); return; } 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 || !res2.data) { setTimeout(exportCloudSub(filter, config, offset), 1e3); 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: "导入", footer: '
建议先设置好请求头,以避免上传失败
Github
' }).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) { headerSetting(editArea); cloudUpload(editArea); cloudMatch(editArea); cloudUpgrade(editArea); cloudLocalUpload(editArea); freeVIPSong(editArea); cloudExport(editArea); cloudImport(editArea); } }; 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 = `${this.SongRes["/api/song/red/count"].data.count}`; } if (this.songDetailObj.originCoverType > 0) { let originCoverTypeBlock = this.createTableRow(this.infoTableBody, "原唱翻唱类型"); originCoverTypeBlock.innerHTML = `${this.songDetailObj.originCoverType == 1 ? "原唱" : "翻唱"}`; } 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 = `${title}`; 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 = `
${title || ""}
`; 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 = `
${desc || ""}
`; 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 = ``; 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 = '收起'; } else { btn.innerHTML = '展开'; } }); 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 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 += ` ${unsafeWindow.top.GUserScriptObjects.storageCommentInfos[commentId]}`; } }; 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: `
需不显示属地请填 127.0.0.1
` }).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: 'Github' }).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(); }); })();