// ==UserScript==
// @name mTorrent (M-Team) PT Helper
// @name:zh-CN mTorrent (M-Team) PT 助手
// @namespace https://greasyfork.org/zh-CN/users/163820-ysc3839
// @description Helper script for mTorrent based PT sites (M-Team)
// @description:zh-CN 用于基于 mTorrent PT 站 (M-Team) 的辅助脚本
// @version 1.2.0
// @author ysc3839
// @license MIT
// @match https://kp.m-team.cc/*
// @match https://test2.m-team.cc/*
// @run-at document-idle
// @grant none
// ==/UserScript==
// This file is built from https://github.com/ysc3839/mTorrent-PT-Helper DO NOT EDIT.
// Icon svgs are from https://github.com/ant-design/ant-design-icons Licensed unser MIT LICENSE.
// jsx.ts
function E(tag, attr, ...nodes) {
const ns = attr?.xmlns, e = tag ? ns ? document.createElementNS(ns, tag) : document.createElement(tag) : new DocumentFragment();
if (attr) {
delete attr.xmlns;
for (const k in attr) {
const v = attr[k];
if (typeof v === "function") {
e.addEventListener(k, v);
} else {
e.setAttribute(k, v);
}
}
}
e.append(...nodes);
return e;
}
// icons.tsx
var xmlns = "http://www.w3.org/2000/svg";
var Copy = () => E("span", { role: "img", "aria-label": "copy", class: "anticon anticon-copy" }, E("svg", { xmlns, viewBox: "64 64 896 896", focusable: "false", "data-icon": "copy", width: "1em", height: "1em", fill: "currentColor", "aria-hidden": "true" }, E("path", { xmlns, d: "M832 64H296c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h496v688c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V96c0-17.7-14.3-32-32-32zM704 192H192c-17.7 0-32 14.3-32 32v530.7c0 8.5 3.4 16.6 9.4 22.6l173.3 173.3c2.2 2.2 4.7 4 7.4 5.5v1.9h4.2c3.5 1.3 7.2 2 11 2H704c17.7 0 32-14.3 32-32V224c0-17.7-14.3-32-32-32zM350 856.2L263.9 770H350v86.2zM664 888H414V746c0-22.1-17.9-40-40-40H232V264h432v624z" })));
var Loading = () => E("span", { role: "img", "aria-label": "loading", class: "anticon anticon-loading anticon-spin" }, E("svg", { xmlns, viewBox: "0 0 1024 1024", focusable: "false", "data-icon": "loading", width: "1em", height: "1em", fill: "currentColor", "aria-hidden": "true" }, E("path", { xmlns, d: "M988 548c-19.9 0-36-16.1-36-36 0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 00-94.3-139.9 437.71 437.71 0 00-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.3C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3.1 19.9-16 36-35.9 36z" })));
var Check = () => E("span", { role: "img", "aria-label": "check", class: "anticon anticon-check" }, E("svg", { xmlns, viewBox: "64 64 896 896", focusable: "false", "data-icon": "check", width: "1em", height: "1em", fill: "currentColor", "aria-hidden": "true" }, E("path", { xmlns, d: "M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 00-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z" })));
var ArrowUp = () => E("span", { role: "img", "aria-label": "arrow-up", class: "anticon anticon-arrow-up" }, E("svg", { xmlns, viewBox: "64 64 896 896", focusable: "false", "data-icon": "arrow-up", width: "1em", height: "1em", fill: "currentColor", "aria-hidden": "true" }, E("path", { xmlns, d: "M868 545.5L536.1 163a31.96 31.96 0 00-48.3 0L156 545.5a7.97 7.97 0 006 13.2h81c4.6 0 9-2 12.1-5.5L474 300.9V864c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V300.9l218.9 252.3c3 3.5 7.4 5.5 12.1 5.5h81c6.8 0 10.5-8 6-13.2z" })));
var ArrowDown = () => E("span", { role: "img", "aria-label": "arrow-down", class: "anticon anticon-arrow-down" }, E("svg", { xmlns, viewBox: "64 64 896 896", focusable: "false", "data-icon": "arrow-down", width: "1em", height: "1em", fill: "currentColor", "aria-hidden": "true" }, E("path", { xmlns, d: "M862 465.3h-81c-4.6 0-9 2-12.1 5.5L550 723.1V160c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v563.1L255.1 470.8c-3-3.5-7.4-5.5-12.1-5.5h-81c-6.8 0-10.5 8.1-6 13.2L487.9 861a31.96 31.96 0 0048.3 0L868 478.5c4.5-5.2.8-13.2-6-13.2z" })));
// content.ts
var specialClass;
function waitForContent() {
const e = document.getElementsByClassName("mx-auto")[0];
if (e)
return Promise.resolve(e);
return new Promise((resolve) => {
const root = document.getElementById("root");
new MutationObserver(function(records) {
for (const r of records) {
for (const n of r.addedNodes) {
if (n.nodeType === Node.ELEMENT_NODE && n.id === "app-content") {
this.disconnect();
for (const c of n.classList) {
if (c.startsWith("css-")) {
specialClass = c;
break;
}
}
const e2 = n.getElementsByClassName("mx-auto")[0];
resolve(e2);
return;
}
}
}
}).observe(root, { childList: true });
});
}
// api.ts
var apiUrls = (() => {
let urls = [];
try {
urls = _APIHOSTS.map((u) => new URL(u));
} catch (e) {
console.warn("get _APIHOSTS error:", e);
}
urls.push(new URL(location.origin + "/api"));
return urls;
})();
function isApiUrlWithPath(u, path) {
for (const a of apiUrls) {
if (u.origin === a.origin && u.pathname === a.pathname + path)
return true;
}
return false;
}
function getApiUrl() {
return localStorage.getItem("apiHost") || apiUrls[Math.random() * apiUrls.length | 0].href;
}
function getAuth() {
return localStorage.getItem("auth");
}
function getApiFetchOptions() {
return { method: "POST", headers: { authorization: getAuth() } };
}
async function genDlToken(id) {
const f = new FormData();
f.set("id", id);
const opts = getApiFetchOptions();
opts.body = f;
const res = await fetch(getApiUrl() + "/torrent/genDlToken", opts);
const data = await res.json();
if (data.code !== "0") {
console.error("genDlToken API error:", data);
return null;
}
return data.data;
}
async function getTorrentPeers(id) {
const f = new FormData();
f.set("id", id);
const opts = getApiFetchOptions();
opts.body = f;
const res = await fetch(getApiUrl() + "/torrent/peers", opts);
const data = await res.json();
if (data.code !== "0") {
console.error("getTorrentPeers API error:", data);
return null;
}
return data.data;
}
// keystate.ts
var modifierState = false;
(() => {
const onkey = (e) => {
if (!e.repeat) {
modifierState = e.altKey || e.ctrlKey || e.metaKey || e.shiftKey;
}
};
document.addEventListener("keydown", onkey);
document.addEventListener("keyup", onkey);
})();
// torrents-table.tsx
var TorrentsTableManager = class {
type;
table;
observer;
rowsCount;
peersColIndex;
linkColor;
constructor() {
this.observer = new MutationObserver(this.onTbodyChange.bind(this));
}
set(table, type) {
this.observer.disconnect();
this.type = type;
this.table = table;
if (table) {
const theadr = table.querySelector("thead > tr"), tbody = table.getElementsByTagName("tbody")[0];
if (theadr && tbody) {
const c = theadr.children;
this.rowsCount = c.length;
if (this.type === 2 /* Rankings */) {
for (let i = c.length - 1; i >= 0; --i) {
const e = c[i].firstElementChild;
if (e && e.getAttribute("color") === "green") {
this.peersColIndex = i;
break;
}
}
}
this.modifyPlaceholderColspan(tbody);
this.addListSelectHead(theadr, tbody);
this.observer.observe(tbody, { childList: true });
}
}
}
modifyPlaceholderColspan(tbody) {
const td = tbody.querySelector("tr.ant-table-placeholder > td[colspan]");
if (td)
++td.colSpan;
}
addListSelectHead(theadr, tbody) {
let span, loading = false;
theadr.prepend(E("th", { class: "border border-solid border-black p-2", style: "width: 42px;" }, E(
"button",
{
type: "button",
class: `ant-btn ant-btn-default ant-btn-sm ant-btn-icon-only ${specialClass}`,
click: async function() {
if (!loading) {
const selected = tbody.getElementsByClassName("ant-table-row-selected");
if (selected.length !== 0) {
const setLoading = (l) => {
loading = l;
this.classList.toggle("ant-btn-loading", l);
span.classList.toggle("ant-btn-loading-icon", l);
span.replaceChildren(l ? Loading() : Check());
};
setLoading(true);
const reqs = [];
for (const tr of selected) {
reqs.push(genDlToken(tr.dataset.id));
}
const urls = await Promise.all(reqs);
if (!urls.some((i) => !i)) {
await navigator.clipboard.writeText(urls.join("\n"));
setLoading(false);
}
}
}
}
},
span = E("span", { class: "ant-btn-icon" }, Copy())
)));
}
addListSelect(tr) {
const origClass = tr.className;
tr.style.transition = "background-color .2s";
const a = tr.querySelector('a[href^="/detail/"]');
if (a) {
tr.dataset.id = a.getAttribute("href").slice(8);
}
let label, span;
tr.prepend(E("td", { class: "border border-solid border-black p-2 ", align: "center" }, label = E("label", { class: `ant-checkbox-wrapper ${specialClass}`, style: "transform: scale(1.5);" }, span = E("span", { class: `ant-checkbox ant-wave-target ${specialClass}` }, E("input", { class: "ant-checkbox-input", type: "checkbox", change: function() {
const c = this.checked;
span.classList.toggle("ant-checkbox-checked", c);
label.classList.toggle("ant-checkbox-wrapper-checked", c);
if (c) {
const l = tr.classList;
l.remove("bg-sticky_top", "bg-sticky_normal");
l.add("bg-black/10", "ant-table-row-selected");
} else {
tr.className = origClass;
}
} }), E("span", { class: "ant-checkbox-inner" })))));
}
getPeersSpan(n) {
if (this.type === 1 /* Torrents */) {
if (!this.peersColIndex) {
const c = n.children;
for (let i = c.length - 1; i >= 0; --i) {
const e = c[i];
if (e.getElementsByClassName("anticon-arrow-up").length !== 0 && e.getElementsByClassName("anticon-arrow-down").length !== 0) {
this.peersColIndex = i;
break;
}
}
}
const peersTd = n.children[this.peersColIndex];
let seeders, leechers;
if (peersTd) {
const spans = peersTd.getElementsByTagName("span");
for (const s of spans) {
if (s.childElementCount === 0) {
if (!seeders) {
seeders = s;
} else if (!leechers) {
leechers = s;
break;
}
}
}
}
return [seeders, leechers];
} else if (this.type === 2 /* Rankings */) {
const seeders = n.children[this.peersColIndex], leechers = n.children[this.peersColIndex + 1];
return [seeders.firstElementChild, leechers.firstElementChild];
}
}
addPeersLink(p, id, seeders, leechers) {
const c = Array.from(p.children);
for (const i of c) {
i.remove();
}
const a = E("a", { href: `/detail/${id}#peers`, target: "_blank", style: "display: flex; flex-direction: column; align-items: center;" }, ...c);
if (seeders && leechers) {
let loading = false, loadingIcon;
a.addEventListener("click", async function(e) {
if (!modifierState) {
e.preventDefault();
if (!loading) {
loading = true;
if (!loadingIcon) {
loadingIcon = Loading();
this.prepend(loadingIcon);
}
const peers = await getTorrentPeers(id);
if (peers) {
let s = 0, l = 0;
for (const p2 of peers) {
if (p2.left === "0") {
++s;
} else {
++l;
}
}
seeders.textContent = s;
leechers.textContent = l;
loadingIcon.remove();
loadingIcon = null;
}
loading = false;
}
}
});
}
if (this.linkColor) {
a.style.color = this.linkColor;
p.append(a);
} else {
p.append(a);
a.style.color = this.linkColor = a.computedStyleMap().get("color");
}
}
setPeersLinkAndColor(n) {
const [seeders, leechers] = this.getPeersSpan(n);
if (seeders && leechers) {
const seedersCount = parseInt(seeders.innerText, 10), leechersCount = parseInt(leechers.innerText, 10);
if (leechersCount && !Number.isNaN(seedersCount)) {
const ratio = seedersCount / leechersCount;
let color;
if (ratio < 0.025)
color = "#ff0000";
else if (ratio < 0.05)
color = "#ee0000";
else if (ratio < 0.075)
color = "#dd0000";
else if (ratio < 0.1)
color = "#cc0000";
else if (ratio < 0.125)
color = "#bb0000";
else if (ratio < 0.15)
color = "#aa0000";
else if (ratio < 0.175)
color = "#990000";
else if (ratio < 0.2)
color = "#880000";
else if (ratio < 0.225)
color = "#770000";
else if (ratio < 0.25)
color = "#660000";
else if (ratio < 0.275)
color = "#550000";
else if (ratio < 0.3)
color = "#440000";
else if (ratio < 0.325)
color = "#330000";
else if (ratio < 0.35)
color = "#220000";
else if (ratio < 0.375)
color = "#110000";
if (color)
seeders.style.color = color;
}
}
if (this.type === 1 /* Torrents */) {
const peersTd = n.children[this.peersColIndex];
if (peersTd) {
this.addPeersLink(peersTd, n.dataset.id, seeders, leechers);
}
} else if (this.type === 2 /* Rankings */) {
if (seeders && leechers) {
const { id } = n.dataset;
this.addPeersLink(seeders, id, seeders, leechers);
this.addPeersLink(leechers, id, seeders, leechers);
}
}
}
replaceDownloadIcon(n) {
const i = n.getElementsByClassName("anticon-download")[0];
if (i)
i.replaceWith(Copy());
}
onTbodyChange(records) {
for (const r of records) {
for (const n of r.addedNodes) {
if (n.tagName === "TR" && n.childElementCount === this.rowsCount && n.className !== "ant-table-placeholder") {
this.addListSelect(n);
this.setPeersLinkAndColor(n);
this.replaceDownloadIcon(n);
}
}
}
}
};
var torrentsTable = new TorrentsTableManager();
function findAndSetTable(n, type) {
if (n) {
const e = n.querySelector("table");
if (e) {
torrentsTable.set(e, type);
return true;
}
} else {
torrentsTable.set(void 0, void 0);
}
}
function handleTorrentsTable(records, type) {
for (const r of records) {
for (const n of r.addedNodes) {
if (n.nodeType === Node.ELEMENT_NODE) {
if (findAndSetTable(n, type))
return;
}
}
}
}
// xhr-hook.ts
var xhr = XMLHttpRequest;
var xhrPrototypeProxy = new Proxy(xhr.prototype, {
get(target, prop, receiver) {
const v = Reflect.get(...arguments);
if (prop === "responseText") {
const u = new URL(receiver.responseURL);
if (isApiUrlWithPath("/torrent/peers")) {
XMLHttpRequest = xhr;
const data = JSON.parse(v);
if (data.code === "0") {
data.data.sort((a, b) => {
return parseInt(a.left, 10) - parseInt(b.left, 10);
});
return JSON.stringify(data);
}
}
}
return v;
}
});
var xhrProxy = new Proxy(xhr, {
construct() {
const o = Reflect.construct(...arguments);
Object.setPrototypeOf(o, xhrPrototypeProxy);
return o;
}
});
function hookXHR(enable) {
XMLHttpRequest = enable ? xhrProxy : xhr;
}
// detail-page.ts
var peersTr;
function handlePeersTr(trs, storeTr) {
for (let i = trs.length - 1; i >= 0; --i) {
const tr = trs[i], labelText = tr.getElementsByClassName("ant-descriptions-item-label")[0]?.textContent;
if (labelText === "同伴" || labelText === "Peers") {
if (storeTr)
peersTr = tr;
return tr.getElementsByTagName("button")[0];
}
}
}
function clickButton(button) {
hookXHR(true);
button.click();
}
function hookButtonClick(button) {
button.onclick = function() {
this.onclick = null;
hookXHR(true);
};
}
function scrollToPeers() {
if (peersTr) {
peersTr.scrollIntoView({ behavior: "smooth" });
peersTr = null;
}
}
function handleDetailPage(e) {
e = e.firstElementChild;
if (e?.classList.contains("detail-view")) {
const showPeers = location.hash === "#peers";
const tbody = e.querySelector(".ant-descriptions-view > table > tbody");
if (tbody) {
const button = handlePeersTr(tbody.children, showPeers);
if (button) {
if (showPeers) {
clickButton(button);
setTimeout(scrollToPeers, 1e3);
} else {
hookButtonClick(button);
}
}
let first = true;
const o = new MutationObserver(function(records) {
if (first) {
first = false;
setTimeout(() => {
this.disconnect();
scrollToPeers();
}, 1e3);
}
const button2 = handlePeersTr(tbody.children, showPeers);
if (button2 && !showPeers) {
hookButtonClick(button2);
}
});
setTimeout(o.disconnect.bind(o), 1e4);
o.observe(tbody, { childList: true });
}
}
}
// intercept-dl.ts
navigation.addEventListener("navigate", (e) => {
console.log("navigate", e);
if (!modifierState && e.cancelable && !e.hashChange && e.navigationType === "push") {
const url = e.destination.url, u = new URL(url);
if (isApiUrlWithPath(u, "/rss/dl")) {
e.preventDefault();
navigator.clipboard.writeText(url);
}
}
});
// upload-page.tsx
var dragging = false;
var dragOverlay;
function onDragEnter() {
if (!dragging) {
dragging = true;
if (!dragOverlay) {
dragOverlay = E("div", { style: `position: fixed; top: 0; left: 0; z-index: 100; width: 100%; height: 100%; color: white; background: #00000080; user-select: none; justify-content: center; align-items: center; display: none;`, dragleave: function() {
dragging = false;
this.style.display = "none";
}, dragover: function(e) {
e.preventDefault();
}, drop: function(e) {
e.preventDefault();
dragging = false;
this.style.display = "none";
const items = e.dataTransfer?.items;
if (items) {
let f;
for (const i of items) {
const entry = i.webkitGetAsEntry();
if (entry?.isFile) {
f = i.getAsFile();
break;
}
}
if (f) {
const torrentInput = document.getElementById("torrent-input");
if (torrentInput) {
const dt = new DataTransfer();
dt.items.add(f);
torrentInput.files = dt.files;
torrentInput.dispatchEvent(new Event("change", { bubbles: true }));
}
}
}
} }, E("div", { style: "pointer-events: none; background: #000a; border-radius: 10px; padding: 10px;" }, E("h1", { style: "margin: 0;" }, "放下以选择该文件")));
document.body.append(dragOverlay);
}
dragOverlay.style.display = "flex";
}
}
function enterUploadPage() {
addEventListener("dragenter", onDragEnter);
}
function exitUploadPage() {
removeEventListener("dragenter", onDragEnter);
}
// main.user.tsx
function addStyle(style) {
const s = document.createElement("style");
s.textContent = style;
document.head.appendChild(s);
}
addStyle(`@media screen and (max-width: 1380px) {
#app-content > div { width: 100% !important; }
.mx-auto { margin: 0 !important; }
}
.braft-output-content {
max-height: none !important;
overflow-y: auto !important;
}
.ant-image {
background: unset !important;
}
.ant-image .ant-image-img-placeholder {
background: unset !important;
}`);
function replaceUpDownIcon() {
const up = document.querySelector("img.arrowup"), down = document.querySelector("img.arrowdown");
if (up && down) {
const upIcon = ArrowUp(), downIcon = ArrowDown();
upIcon.classList.add("text-mt-dark-green");
downIcon.classList.add("text-mt-dark-red");
up.replaceWith(upIcon);
down.replaceWith(downIcon);
}
}
function getPageType() {
return location.pathname.split("/")[1];
}
waitForContent().then((e) => {
replaceUpDownIcon();
const page = getPageType();
if (page === "upload") {
enterUploadPage();
} else if (page === "browse") {
findAndSetTable(e, 1 /* Torrents */);
}
new MutationObserver(function(records) {
const page2 = getPageType();
if (page2 === "upload") {
enterUploadPage();
} else {
exitUploadPage();
if (page2 === "browse") {
handleTorrentsTable(records, 1 /* Torrents */);
} else {
findAndSetTable();
if (page2 === "detail") {
handleDetailPage(e);
}
}
}
}).observe(e, { childList: true });
});