Skip to content

Instantly share code, notes, and snippets.

@bambooGHT
Last active July 3, 2024 15:13
Show Gist options
  • Save bambooGHT/922f5c42c0343ef6bff0962b6a36ba04 to your computer and use it in GitHub Desktop.
Save bambooGHT/922f5c42c0343ef6bff0962b6a36ba04 to your computer and use it in GitHub Desktop.
筛选文件下载脚本
// ==UserScript==
// @name filterDownFile
// @namespace https://github.com/bambooGHT
// @version 1.3.2
// @description dom改了,改一下代码
// @author bambooGHT
// @match https://www.asmr.one/*
// @match https://asmr.one/*
// @match https://asmr-100.com/*
// @match https://www.asmr-100.com/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=asmr.one
// @grant GM_xmlhttpRequest
// @grant none
// @updateURL https://gist.github.com/bambooGHT/922f5c42c0343ef6bff0962b6a36ba04/raw/asmrDownload.user.js
// @downloadURL https://gist.github.com/bambooGHT/922f5c42c0343ef6bff0962b6a36ba04/raw/asmrDownload.user.js
// ==/UserScript==
(() => {
const _historyWrap = (type) => {
const orig = history[type];
const e = new Event(type);
return function () {
const rv = orig.apply(this, arguments);
e.arguments = arguments;
window.dispatchEvent(e);
return rv;
};
};
history.pushState = _historyWrap('pushState');
let script = document.createElement('script');
script.setAttribute('type', 'text/javascript');
script.src = "https://jimmywarting.github.io/StreamSaver.js/examples/zip-stream.js";
document.documentElement.appendChild(script);
let script2 = document.createElement('script');
script2.setAttribute('type', 'text/javascript');
script2.src = "https://cdn.jsdelivr.net/npm/streamsaver@2.0.3/StreamSaver.min.js";
document.documentElement.appendChild(script2);
let script3 = document.createElement('script');
script3.setAttribute('type', 'text/javascript');
script3.src = "https://cdn.jsdelivr.net/gh/eligrey/Blob.js/Blob.js";
document.documentElement.appendChild(script3);
let script5 = document.createElement('script');
script5.setAttribute('type', 'text/javascript');
script5.src = "https://cdn.jsdelivr.net/npm/web-streams-polyfill@3.2.1/dist/ponyfill.min.js";
document.documentElement.appendChild(script5);
const darkCss = `
body:not(.body--dark) {
.newDOM-dark {
border-top: 1px solid #fff !important;
}
}
body.body--dark {
.newDOM-dark {
background: var(--q-color-dark) !important;
color: #fff !important;
}
.newDOM-dark {
border-top: 1px solid hsla(0, 0%, 100%, 0.28) !important;
}
}
`;
const style = document.createElement("style");
style.innerHTML = darkCss;
document.head.appendChild(style);
})();
const newButtom = (innerHTML) => {
const name = document.querySelector(".text-weight-regular").innerText.replace(/([\\\/:*?"<>|]|(【.*?】))/g, ' ').trim();
const buttom = document.createElement('button');
buttom.innerHTML = innerHTML;
buttom.setAttribute("class", "q-btn q-btn-item non-selectable no-outline q-mt-sm shadow-4 q-mx-xs q-px-sm q-btn--standard q-btn--rectangle bg-cyan text-white q-btn--actionable q-focusable q-hoverable q-btn--wrap q-btn--dense");
buttom.style.lineHeight = '32px';
return { buttom, name };
};
//获取当前rj号数据
const getData = async () => {
const RJindex = window.location.pathname.slice(8);
const result = await fetch("https://api.asmr.one/api/tracks/" + RJindex);
const data = await result.json();
return data;
};
//格式化数据
const dataFormat = (data, name, path = "") => {
return data.flatMap((value) => {
if (value.isDown) {
return fileStitch(`${name}/${path}${value.children[0] ? `${value.title}` : ""}`, value.mediaDownloadUrl ? value : value.children);
}
else {
const children = value.children;
if (children[0] && isDown(children).some(item => item.isDown === true)) {
return dataFormat(children, name, `${path}${value.title}/`);
}
return [];
}
});
};
const fileStitch = (name, data) => {
return (Array.isArray(data) ? (name += "/", data) : [data]).flatMap(({ children, ...value }) => {
const filename = `${name}${value.title}`;
return children[0] ? fileStitch(filename, children) : {
fileurl: value.mediaDownloadUrl,
filename,
};
});
};
//打包成压缩包下载
const downloadZip = (name, files) => {
const zipFileOutputStream = streamSaver.createWriteStream(name);
const progress = updateDownText(files.length);
const Files = files.values();
const readableZipStream = new ZIP({
async pull(ctrl) {
const data = Files.next();
if (data.done) {
ctrl.close();
progress.end();
return;
}
progress.next();
const { fileurl, filename } = data.value;
const process = () => {
return new Promise(async (res) => {
const downFns = await segmentDown(fileurl, filename);
const stream = new ReadableStream({
async start(c) {
if (!downFns[0]) {
c.close();
res();
return;
}
const data = await Promise.all(downFns.splice(0, 6).map(p => p()));
for (const item of data) {
const data = await item.arrayBuffer();
c.enqueue(new Uint8Array(data));
}
this.start(c);
}
});
ctrl.enqueue({ name: filename, stream: () => stream });
});
};
await process();
}
});
if (window.WritableStream && readableZipStream.pipeTo) {
readableZipStream.pipeTo(zipFileOutputStream);
}
};
const newDownLoadDOMZIP = () => {
const { buttom, name } = newButtom("下载筛选文件(压缩包)");
buttom.onclick = (e) => {
const data = dataFormat(Object.values(fileData), name);
if (!data.length) return;
e.stopPropagation();
downloadZip(name + '.zip', data.flat(Infinity));
};
return buttom;
};
const isDown = (arr) => {
return arr.flatMap(({ children, ...value }) => {
return [value, ...isDown(children)];
});
};
const dataFormat1 = async (dir, arr) => {
return (await Promise.all(arr.map(async (value) => {
if (value.isDown) return await fileStitch1(dir, value.title, value.mediaDownloadUrl ? value : value.children);
else {
const children = value.children;
if (children[0] && isDown(children).some(item => item.isDown === true)) {
const newDir = await getSaveDir(dir, value.title);
return await dataFormat1(newDir, children);
}
return [];
}
}))).flat(Infinity);
};
const fileStitch1 = async (dir, name, arr) => {
if (Array.isArray(arr)) {
dir = await getSaveDir(dir, name);
} else {
arr = [arr];
}
return await Promise.all(arr.map(async ({ children, ...value }) => {
return children[0] ? await fileStitch1(dir, value.title, children)
: async () => {
await saveFile(dir, value.title, value.mediaDownloadUrl);
};
}));
};
//获取保存文件的目录api
const getSaveDir = async (dir, name) => {
const saveDir = await dir.getDirectoryHandle(name, { create: true });
return saveDir;
};
//保存文件
const saveFile = async (saveDir, name, url) => {
try {
await saveDir.getFileHandle(name);
return true;
} catch (error) {
const save = await saveDir.getFileHandle(name, { create: true });
/** @type {WritableStream} */
const writable = await save.createWritable();
const downFns = await segmentDown(url, name);
const fn = async () => {
const data = await Promise.all(downFns.splice(0, 6).map(p => p()));
for (const item of data) {
await item.body.pipeTo(writable, { preventClose: true });
}
if (downFns[0]) await fn();
};
await fn();
writable.close();
}
};
const newDownLoadDOM = () => {
const { buttom, name } = newButtom("下载筛选文件");
buttom.onclick = async (e) => {
const getDir = await showDirectoryPicker({ mode: "readwrite" });
const saveDir = await getSaveDir(getDir, name);
const data = await dataFormat1(saveDir, Object.values(fileData));
if (!data.length) return;
const progress = updateDownText(data.length);
for (const item of data) {
progress.next();
await item();
}
progress.end();
};
return buttom;
};
const updateDownText = (length) => {
let i = 0;
let size = 0;
const { buttom: progressDOM } = newButtom("");
const filterDown = [...document.querySelectorAll('.q-pa-sm')].at(-1);
filterDown.appendChild(progressDOM);
updateText = (Size) => {
size += Size;
progressDOM.innerText = `正在下载 ${i} / ${length} size: ${clacSize(size)}`;
};
const next = () => {
++i;
updateText(0);
};
const end = () => {
progressDOM.innerText = `筛选的文件已下载完成 ${length} size: ${clacSize(size)}`;
updateText = () => { };
setTimeout(() => {
filterDown.removeChild(progressDOM);
}, 5000);
};
return {
next,
end
};
};
// 最大重试次数
const MAX_RETRIES = 6;
const SEGMENT_MAX_SIZE = 10 * 1024 * 1024;
let updateText = () => { };
const getTotalFileSize = async (url) => {
const response = await fetch(url, { method: 'HEAD' });
return parseInt(response.headers.get('Content-Length'));
};
const getSpecifyBytes = (start, end) => {
return {
Range: `bytes=${start}-${end}`,
};
};
const segmentDown = async (url, name) => {
const totalSize = await getTotalFileSize(url);
const segments = [];
let startByte = 0;
while (startByte < totalSize) {
const endByte1 = startByte + SEGMENT_MAX_SIZE;
const endByte = Math.min(endByte1, totalSize);
const headers = getSpecifyBytes(startByte, endByte);
/** @returns {Promise<Response>} */
const downFn = async (retryCount = 0) => {
try {
const data = await fetch(url, { headers });
const value = endByte1 >= totalSize ? totalSize - (endByte1 - SEGMENT_MAX_SIZE) : SEGMENT_MAX_SIZE;
updateText(value);
return data;
} catch {
if (retryCount > MAX_RETRIES) {
throw Error("下载失败");
}
console.log(`下载失败 正在重试. 文件名:${name}`);
return downFn(retryCount + 1);
}
};
segments.push(downFn);
startByte = endByte + 1;
}
return segments;
};
const isdata = () => {
setTimeout(async () => {
if (fileData.length) {
initDOM();
}
}, 200);
};
const fn = async () => {
fileData = await getData();
isdata();
const filterDown = [...document.querySelectorAll('.q-pa-sm')].at(-1);
filterDown.appendChild(newDownLoadDOMZIP());
filterDown.appendChild(newDownLoadDOM());
};
window.addEventListener('pushState', async (e) => {
const path = window.location.href;
if (path.includes('work/RJ') && !path.includes('?')) {
await fn();
}
});
window.onload = async () => {
await fn();
};
const newDOM = (name, info, right) => {
return `<div role="listitem" tabindex="0" style="padding-left:${right}px" class="newDOM-dark non-selectable q-card q-item q-item-type row no-wrap q-item--clickable q-link cursor-pointer q-focusable q-hoverable">
<div tabindex="-1" class="q-focus-helper"></div>
<div class="q-item__section column q-item__section--avatar q-item__section--side justify-center">
<input type="checkbox">
</div>
<div class="q-item__section column q-item__section--main justify-center">
<div class="q-item__label" style="overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;">
${name}
</div>
${info ?
`<div class="q-item__label q-item__label--caption text-caption ellipsis">
${info}
</div>`
: ""}
</div>
</div>`;
};
let fileData = {};
const initDOM = () => {
fileData = 遞歸文件(fileData);
};
const formatDuration = (duration) => {
const hours = Math.floor(duration / 3600);
const minutes = Math.floor((duration % 3600) / 60);
const seconds = Math.floor(duration % 60);
let formattedDuration = "";
if (hours > 0) {
formattedDuration += hours.toString().padStart(2, "0") + ":";
}
formattedDuration += minutes.toString().padStart(2, "0") + ":" +
seconds.toString().padStart(2, "0");
return formattedDuration;
};
const 遞歸文件 = (data) => {
const DOM = document.getElementById("work-tree");
const domArr = {};
const 遞歸 = (DATA, oldValue = undefined, left = 5) => {
for (const item of DATA) {
const subName = item.children?.length ? item.children.length + "項目" : (item.duration ? formatDuration(item.duration) : "");
let newElement = document.createElement("div");
newElement.innerHTML = newDOM(item.title, subName, left);
newElement = newElement.firstChild;
const value = { dom: newElement, title: item.title, parent: oldValue, isDown: false, children: [] };
if (item.mediaDownloadUrl) value.mediaDownloadUrl = item.mediaDownloadUrl;
const inputEvent = createDownEvent(value, oldValue);
const input = newElement.querySelector("input");
input.onchange = inputEvent;
input.onclick = (e) => {
e.stopPropagation();
};
newElement.onclick = () => input.click();
if (oldValue) oldValue.children.push(value);
else domArr[item.title] = value;
if (item.children) {
DOM.appendChild(newElement);
遞歸(item.children, value, left + 25);
} else {
DOM.appendChild(newElement);
}
}
};
遞歸(data);
return domArr;
};
const createDownEvent = (value, parent) => {
return (e) => {
const open = (Children, boo) => {
for (const item of Children) {
const inputElement = item.dom.querySelector("input");
inputElement.checked = boo;
item.isDown = boo;
if (item.children) open(item.children, inputElement.checked);
}
};
if (value.children) open(value.children, e.target.checked);
value.isDown = e.target.checked;
isopen(parent);
};
};
const isopen = (parent) => {
let booArr = [];
const fn = (children) => {
booArr = [];
for (const item of children) {
booArr.push(item.isDown);
}
};
if (parent?.children) {
fn(parent.children);
do {
const is = booArr.every(p => p === true);
const parentInput = parent.dom.querySelector("input");
parentInput.checked = is;
parent.isDown = is;
parent = parent.parent;
if (parent?.children) fn(parent.children);
} while (parent);
}
};
const clacSize = (size) => {
const aMultiples = ['B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'];
const bye = 1024;
if (size < bye) return size + aMultiples[0];
let i = 0;
for (var l = 0; l < 8; l++) {
if (size / Math.pow(bye, l) < 1) break;
i = l;
}
return `${(size / Math.pow(bye, i)).toFixed(2)}${aMultiples[i]}`;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment