// ==UserScript== // @name QQ音乐歌单一键下载到本地 // @namespace npm/vite-plugin-monkey // @version 0.0.0 // @author lsq_li // @icon https://vitejs.dev/logo.svg // @match https://y.qq.com/n/ryqq/player // @description QQ音乐歌单一键式本地下载、音乐下载。(注意:使用前请登录qq音乐账号,再下载音乐,若遇到下载失败,请刷新后重试,或者检查网络流畅)使用流程: 进入qq音乐官网https://y.qq.com/n/ryqq。再选择需要下载的歌单播放歌单全部音乐。进入播放界面提示是否下载。点击确认获取本地音乐(歌曲多可能需要一段时间)。等待一段时间后,即可下载歌单内全部音乐。(适用于u盘音响设备,或者车内音响设备音乐下载本地)*****请勿运用到商业用途。若用于商业用途与本人无关。 // @license GPL License // @require https://cdn.jsdelivr.net/npm/react@18.3.1/umd/react.production.min.js // @require https://cdn.jsdelivr.net/npm/react-dom@18.3.1/umd/react-dom.production.min.js // @downloadURL none // ==/UserScript== (function (require$$0, require$$0$1) { 'use strict'; var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); var jsxRuntime = { exports: {} }; var reactJsxRuntime_production_min = {}; /** * @license React * react-jsx-runtime.production.min.js * * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ var f = require$$0, k = Symbol.for("react.element"), l = Symbol.for("react.fragment"), m$1 = Object.prototype.hasOwnProperty, n = f.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner, p = { key: true, ref: true, __self: true, __source: true }; function q(c, a, g) { var b, d = {}, e = null, h = null; void 0 !== g && (e = "" + g); void 0 !== a.key && (e = "" + a.key); void 0 !== a.ref && (h = a.ref); for (b in a) m$1.call(a, b) && !p.hasOwnProperty(b) && (d[b] = a[b]); if (c && c.defaultProps) for (b in a = c.defaultProps, a) void 0 === d[b] && (d[b] = a[b]); return { $$typeof: k, type: c, key: e, ref: h, props: d, _owner: n.current }; } reactJsxRuntime_production_min.Fragment = l; reactJsxRuntime_production_min.jsx = q; reactJsxRuntime_production_min.jsxs = q; { jsxRuntime.exports = reactJsxRuntime_production_min; } var jsxRuntimeExports = jsxRuntime.exports; var client = {}; var m = require$$0$1; { client.createRoot = m.createRoot; client.hydrateRoot = m.hydrateRoot; } const syncRecursive = (data, index, callback, asnyCallback) => { if (index >= data.length) { callback(data); return; } console.log("Processing data:", data[index]); if (asnyCallback) asnyCallback(index); const audio = document.querySelector("audio"); data[index] = { ...data[index], downUrl: audio.src }; const btn = document.querySelector(".btn_big_next"); btn.click(); setTimeout(() => { syncRecursive(data, index + 1, callback, asnyCallback); }, 2e3); }; const splitAndMoveElement = (arr, targetId) => { const targetIndex = arr.findIndex((item) => item.id === targetId); if (targetIndex === -1) { return arr; } else if (targetIndex === 0) { return arr; } const afterTargetElements = arr.slice(targetIndex); const beforeTargetElements = arr.slice(0, targetIndex - 1); return [...afterTargetElements, ...beforeTargetElements]; }; class FileDownloader { constructor(maxConcurrentDownloads = 1) { __publicField(this, "maxConcurrentDownloads"); __publicField(this, "downloadQueue"); __publicField(this, "numActiveDownloads"); this.maxConcurrentDownloads = maxConcurrentDownloads; this.downloadQueue = []; this.numActiveDownloads = 0; } addToDownloadQueue(fileUrl, fileName) { this.downloadQueue.push({ fileUrl, fileName }); this.processDownloadQueue(); } processDownloadQueue() { while (this.numActiveDownloads < this.maxConcurrentDownloads && this.downloadQueue.length > 0) { const { fileUrl, fileName } = this.downloadQueue.shift(); this.startDownload(fileUrl, fileName); } } startDownload(fileUrl, fileName) { this.numActiveDownloads++; fetch(fileUrl).then((response) => { return response.blob(); }).then((blob) => { this.downloadFile(blob, fileName); }).catch((error) => { console.error("Error downloading file:", error); }).finally(() => { this.numActiveDownloads--; this.processDownloadQueue(); }); } downloadFile(blob, fileName) { const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; link.setAttribute("download", fileName + ".mp3"); document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); } async downloadFileConvert(response, fileName) { const file = new File([await response.blob()], fileName, { type: response.headers.get("content-type") }); const formData = new FormData(); formData.append("file", file); formData.append("targetformat", "mp3"); formData.append("audiobitratetype", "0"); formData.append("customaudiobitrate", ""); formData.append("audiosamplingtype", "0"); formData.append("customaudiosampling", ""); formData.append("code", "82000"); formData.append("filelocation", "local"); formData.append("legal", "Our PHP programs can only be used in aconvert.com. We DO NOT allow using our PHP programs in any third-party websites, software or apps. We will report abuse to your cloud provider, Google Play and App store if illegal usage found!"); const uploadResponse = await fetch("/postFilesToMp3/convert/convert9.php", { method: "POST", body: formData }); if (uploadResponse.state === "SUCCESS") { console.log("SUCCESS"); } else { console.error("Error uploading file:", await uploadResponse.text()); } } } function App(props) { const [loadding, setLoading] = require$$0.useState(false); const [process, setProcess] = require$$0.useState(0); const [totalNum, setTotalNum] = require$$0.useState(0); require$$0.useEffect(() => { if (!window.isExceOne) { window.isExceOne = true; const msg = confirm("是否下载播放列表音乐.tip: 下载网页播放器内全部音乐,若需要挑选请点击取消,修改播放器内音乐,刷新页面点击确认等待全部下载!!!"); if (msg === true) { setLoading(true); const body = document.querySelector("#app"); body.style.visibility = "hidden"; const addXMLRequestCallback = (callback) => { let oldSend = null; let i = null; if (XMLHttpRequest.callbacks) { XMLHttpRequest.callbacks.push(callback); } else { XMLHttpRequest.callbacks = [callback]; oldSend = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.send = function() { for (i = 0; i < XMLHttpRequest.callbacks.length; i++) { XMLHttpRequest.callbacks[i](this); } oldSend.apply(this, arguments); }; } }; addXMLRequestCallback((xhr) => { xhr.addEventListener("loadend", function() { var _a, _b, _c; if (xhr.readyState == 4 && xhr.status == 200) { if (xhr.responseURL.indexOf("cgi-bin/musics.fcg") !== -1) { let list = JSON.parse(xhr.response); let preUrl = void 0; let songList = void 0; let songId = void 0; let songName = void 0; (_a = Object.keys(list)) == null ? void 0 : _a.forEach((item) => { var _a2, _b2, _c2, _d, _e, _f, _g, _h; if (preUrl === void 0) { preUrl = (_d = (_c2 = (_b2 = (_a2 = list[item]) == null ? void 0 : _a2.data) == null ? void 0 : _b2.midurlinfo) == null ? void 0 : _c2[0]) == null ? void 0 : _d.purl; } if (songList === void 0) { songList = (_f = (_e = list[item]) == null ? void 0 : _e.data) == null ? void 0 : _f.tracks; } if (songId === void 0) { songId = (_h = (_g = list[item]) == null ? void 0 : _g.data) == null ? void 0 : _h.songID; } }); if (songId !== void 0) { songName = (_c = (_b = songList == null ? void 0 : songList.filter( (item) => (item == null ? void 0 : item.id) === songId )) == null ? void 0 : _b[0]) == null ? void 0 : _c.name; songList == null ? void 0 : songList.findIndex( (item) => (item == null ? void 0 : item.id) === songId ); songList = splitAndMoveElement(songList, songId); setTotalNum(songList == null ? void 0 : songList.length); } if (preUrl !== void 0 && songName !== void 0) { const btn = document.querySelector(".btn_big_next"); btn.click(); songList[0] = { ...songList[0], downUrl: "https://ws6.stream.qqmusic.qq.com/" + preUrl }; syncRecursive( songList, 1, (data) => { console.log(data, "allData"); const fileDownloader2 = new FileDownloader(songList == null ? void 0 : songList.length); songList == null ? void 0 : songList.forEach( (item, i) => { setTimeout(() => { fileDownloader2.addToDownloadQueue( item == null ? void 0 : item.downUrl, item == null ? void 0 : item.name ); }, 800 * i); } ); const body2 = document.querySelector("#app"); body2.style.visibility = "visible"; setLoading(false); }, (index) => { setProcess(index); } ); console.log(preUrl, "aaaa"); } } } }); }); } } }, []); return loadding ? /* @__PURE__ */ jsxRuntimeExports.jsxs( "div", { style: { width: "100%", height: "100vh", zIndex: 999999, background: "#cecece", fontSize: 24, color: "white", position: "absolute", top: 0, left: 0 }, children: [ "下载中... ", "(" + process + "/" + totalNum + ")" ] } ) : /* @__PURE__ */ jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment, {}); } const fileDownloader = new FileDownloader(10); client.createRoot( (() => { const app = document.createElement("div"); document.body.append(app); return app; })() ).render( /* @__PURE__ */ jsxRuntimeExports.jsx(require$$0.StrictMode, { children: /* @__PURE__ */ jsxRuntimeExports.jsx( App, { DownloadList: (url, filename) => fileDownloader.addToDownloadQueue(url, filename) } ) }) ); })(React, ReactDOM);