蜜柑计划批量复制磁链
// ==UserScript==
// @name 蜜柑计划批量复制磁链
// @description 蜜柑计划批量复制磁链, 并保存为 md 文件.
// @version 1.0.0
// @author Yiero
// @match https://mikanani.me/Home/Bangumi/*
// @license GPL-3
// @namespace https://github.com/AliubYiero/TamperMonkeyScripts
// @grant GM_download
// ==/UserScript==
/*
* @module : @yiero/gmlib
* @author : Yiero
* @version : 0.1.12
* @description : GM Lib for Tampermonkey
* @keywords : tampermonkey, lib, scriptcat, utils
* @license : MIT
* @repository : git+https://github.com/AliubYiero/GmLib.git
*/
const returnElement = (selector, options, resolve, reject) => {
setTimeout(() => {
const element = options.parent.querySelector(selector);
if (!element) {
reject(new Error("Void Element"));
return;
}
resolve(element);
}, options.delayPerSecond * 1e3);
};
const getElementByTimer = (selector, options, resolve, reject) => {
const intervalDelay = 100;
let intervalCounter = 0;
const maxIntervalCounter = Math.ceil(options.timeoutPerSecond * 1e3 / intervalDelay);
const timer = window.setInterval(() => {
if (++intervalCounter > maxIntervalCounter) {
clearInterval(timer);
returnElement(selector, options, resolve, reject);
return;
}
const element = options.parent.querySelector(selector);
if (element) {
clearInterval(timer);
returnElement(selector, options, resolve, reject);
}
}, intervalDelay);
};
const getElementByMutationObserver = (selector, options, resolve, reject) => {
const timer = options.timeoutPerSecond && window.setTimeout(() => {
observer.disconnect();
returnElement(selector, options, resolve, reject);
}, options.timeoutPerSecond * 1e3);
const observeElementCallback = (mutations) => {
mutations.forEach((mutation) => {
mutation.addedNodes.forEach((addNode) => {
if (addNode.nodeType !== Node.ELEMENT_NODE) {
return;
}
const addedElement = addNode;
const element = addedElement.matches(selector) ? addedElement : addedElement.querySelector(selector);
if (element) {
timer && clearTimeout(timer);
returnElement(selector, options, resolve, reject);
}
});
});
};
const observer = new MutationObserver(observeElementCallback);
observer.observe(options.parent, {
subtree: true,
childList: true
});
return true;
};
function elementWaiter(selector, options) {
const elementWaiterOptions = {
parent: document,
timeoutPerSecond: 20,
delayPerSecond: 0.5,
...options
};
return new Promise((resolve, reject) => {
const targetElement = elementWaiterOptions.parent.querySelector(selector);
if (targetElement) {
returnElement(selector, elementWaiterOptions, resolve, reject);
return;
}
if (MutationObserver) {
getElementByMutationObserver(selector, elementWaiterOptions, resolve, reject);
return;
}
getElementByTimer(selector, elementWaiterOptions, resolve, reject);
});
}
const registerButton = (clickCallback) => {
const subGroupNodeList = document.querySelectorAll(".subgroup-text");
const appendButton = (subGroupNode) => {
const downloadLink = document.createElement("a");
downloadLink.classList.add("download-link", "subgroup-subscribe");
downloadLink.textContent = "\u4E0B\u8F7D\u6240\u6709\u94FE\u63A5";
downloadLink.addEventListener("click", (e) => {
clickCallback(subGroupNode, e);
});
subGroupNode.appendChild(downloadLink);
};
subGroupNodeList.forEach((subGroupNode) => {
appendButton(subGroupNode);
});
};
const downloadTextFile = (fileName, fileContent, mimeType = "plain/text") => {
const textBlob = new Blob([fileContent], { type: mimeType });
const textUrl = URL.createObjectURL(textBlob);
GM_download({
url: textUrl,
name: fileName,
onload() {
URL.revokeObjectURL(textUrl);
}
});
};
const parseAnimationData = (magnetContainer) => {
const animationItemList = Array.from(magnetContainer.querySelectorAll("tbody > tr"));
const animationInfoList = animationItemList.map((trNode) => {
var _a;
const [fileNameNode, fileSizeNode, updateTimeNode] = trNode.querySelectorAll("td");
if (!fileNameNode || !fileSizeNode || !updateTimeNode) return void 0;
const fileName = ((_a = fileNameNode.querySelector("a.magnet-link-wrap")) == null ? void 0 : _a.textContent) || "";
const upperFileName = fileName.toUpperCase();
const language = upperFileName.includes("GB") ? "GB" : upperFileName.includes("BIG5") ? "BIG5" : "UNKNOWN";
const quality = upperFileName.includes("720P") ? "720P" : upperFileName.includes("1080P") ? "1080P" : "UNKNOWN";
const magnetLinkNode = fileNameNode.querySelector("a.magnet-link");
if (!magnetLinkNode) return void 0;
const magnetLink = magnetLinkNode.dataset.clipboardText || "";
return {
fileName,
magnetLink,
language,
quality,
fileSize: fileSizeNode.textContent || "",
updateTime: updateTimeNode.textContent || ""
};
});
const sortedAnimationInfoList = animationInfoList.toSorted((a, b) => {
const parseUpdateTimeWeight = (updateTime) => {
const UPDATE_TIME_WEIGHT = 1e12;
return Date.parse(updateTime) / UPDATE_TIME_WEIGHT;
};
const parseLanguageWeight = (language) => {
const LANGUAGE_WEIGHT = 1e3;
const languageWeightMapper = {
"GB": 1,
"BIG5": 2,
"UNKNOWN": 3
};
return languageWeightMapper[language] * LANGUAGE_WEIGHT;
};
const parseQualityWeight = (quality) => {
const QUALITY_WEIGHT = 10;
const qualityWeightMapper = {
"1080P": 1,
"720P": 2,
"UNKNOWN": 3
};
return qualityWeightMapper[quality] * QUALITY_WEIGHT;
};
const aWeight = parseLanguageWeight(a.language) + parseQualityWeight(a.quality) + parseUpdateTimeWeight(a.updateTime);
const bWeight = parseLanguageWeight(b.language) + parseQualityWeight(b.quality) + parseQualityWeight(b.quality);
return aWeight - bWeight;
});
return sortedAnimationInfoList;
};
const stringifyAnimationData = (animationInfoList) => {
const groupByAnimationInfo = Object.groupBy(animationInfoList, (animationInfo) => {
return `${animationInfo.language}-${animationInfo.quality}`;
});
return Object.entries(groupByAnimationInfo).map(([title, infoList]) => {
return `
## ${title}
| \u756A\u5267\u540D | \u5927\u5C0F | \u66F4\u65B0\u65F6\u95F4 | \u4E0B\u8F7D\u94FE\u63A5 |
| ----- | ---- | ------ | ------- |
${infoList.map((info) => `| \`${info.fileName}\` | ${info.fileSize} | ${info.updateTime} | [#](${info.magnetLink}) |`).join("\n")}
\`\`\`plain
${infoList.map((info) => info.magnetLink).join("\n")}
\`\`\`
`;
}).join("\n\n---\n\n");
};
(async () => {
var _a;
await elementWaiter("footer.footer");
const animationName = (_a = document.querySelector(".bangumi-title")) == null ? void 0 : _a.textContent;
if (!animationName) return;
document.querySelectorAll('a.episode-expand.js-expand-episode:not([style="display: none;"])').forEach((item) => item.click());
registerButton((subGroupNode) => {
var _a2;
const subGroupNameNode = subGroupNode.firstElementChild;
if (!subGroupNameNode) return;
const subGroupName = subGroupNameNode.classList.contains("dropdown") ? (((_a2 = subGroupNameNode.querySelector(".dropdown-toggle")) == null ? void 0 : _a2.textContent) || "").trim() : (subGroupNameNode.textContent || "").trim();
if (!subGroupName) return;
const magnetContainer = subGroupNode.nextElementSibling;
if (!(magnetContainer && magnetContainer.tagName === "TABLE")) return;
const animationInfoList = parseAnimationData(magnetContainer);
const animationDataHeader = `# ${animationName}
> - \u5B57\u5E55\u7EC4: ${subGroupName}
> - \u756A\u5267\u94FE\u63A5: ${document.URL}`;
const animationDataContent = stringifyAnimationData(animationInfoList);
downloadTextFile(`${animationName.trim()}-${subGroupName.trim()}.md`, `${animationDataHeader}
${animationDataContent}`, "text/markdown");
});
})();