// ==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/[email protected]/umd/react.production.min.js
// @require https://cdn.jsdelivr.net/npm/[email protected]/umd/react-dom.production.min.js
// ==/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);