PT 助手

私密种子站点的助手

当前为 2023-02-23 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name PT Helper
  3. // @name:zh-CN PT 助手
  4. // @version 0.1.0
  5. // @namespace https://github.com/amorphobia/pt-helper
  6. // @description A helper for private trackers
  7. // @description:zh-CN 私密种子站点的助手
  8. // @author amorphobia
  9. // @homepage https://github.com/amorphobia/pt-helper
  10. // @icon data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+IDxzdmcgaWQ9IkxheWVyXzEiIGRhdGEtbmFtZT0iTGF5ZXIgMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgNTEyIDUxMiI+PGRlZnM+PHN0eWxlPi5jbHMtMXtmaWxsOiNmZmQzOGQ7fS5jbHMtMntmaWxsOiM2ZDQxMzU7fTwvc3R5bGU+PC9kZWZzPjx0aXRsZT5zaGFyZS1maWxsZWQ8L3RpdGxlPjxjaXJjbGUgY2xhc3M9ImNscy0xIiBjeD0iNDA1LjMzIiBjeT0iMTA2LjY3IiByPSI4NS4zMyIvPjxjaXJjbGUgY2xhc3M9ImNscy0xIiBjeD0iNDA1LjMzIiBjeT0iNDA1LjMzIiByPSI4NS4zMyIvPjxjaXJjbGUgY2xhc3M9ImNscy0xIiBjeD0iMTA2LjY3IiBjeT0iMjU2IiByPSI4NS4zMyIvPjxwYXRoIGNsYXNzPSJjbHMtMiIgZD0iTTQwNS4zMywyOTguNjdhMTA2LjU0LDEwNi41NCwwLDAsMC04My45LDQwLjg2TDIwOS42OSwyODMuNjZhMTA2Ljc4LDEwNi43OCwwLDAsMCwwLTU1LjMxbDExMS43NS01NS44N2ExMDYuMjcsMTA2LjI3LDAsMSwwLTE5LjEzLTM4LjE1TDE5MC41NiwxOTAuMmExMDYuNjcsMTA2LjY3LDAsMSwwLDAsMTMxLjZsMTExLjc1LDU1Ljg3YTEwNi42NywxMDYuNjcsMCwxLDAsMTAzLTc5Wm0wLTI1NmE2NCw2NCwwLDEsMS02NCw2NEE2NC4wNyw2NC4wNywwLDAsMSw0MDUuMzMsNDIuNjdaTTEwNi42NywzMjBhNjQsNjQsMCwxLDEsNjQtNjRBNjQuMDcsNjQuMDcsMCwwLDEsMTA2LjY3LDMyMFpNNDA1LjMzLDQ2OS4zM2E2NCw2NCwwLDEsMSw2NC02NEE2NC4wNyw2NC4wNywwLDAsMSw0MDUuMzMsNDY5LjMzWiIvPjwvc3ZnPg==
  11. // @supportURL https://github.com/amorphobia/pt-helper/issues
  12. // @license AGPL-3.0-or-later
  13. // @match *://hhanclub.top/*
  14. // @match *://nanyangpt.com/*
  15. // @match *://pt.sjtu.edu.cn/*
  16. // @match *://tjupt.org/*
  17. // @grant GM_addStyle
  18. // @grant GM_registerMenuCommand
  19. // @grant GM_unregisterMenuCommand
  20. // @grant GM_openInTab
  21. // @grant GM_getValue
  22. // @grant GM_setValue
  23. // @grant GM_notification
  24. // @grant GM_xmlhttpRequest
  25. // @noframes
  26. // ==/UserScript==
  27.  
  28. /******/ (() => { // webpackBootstrap
  29. /******/ "use strict";
  30. /******/ var __webpack_modules__ = ([
  31. /* 0 */,
  32. /* 1 */
  33. /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
  34.  
  35.  
  36. Object.defineProperty(exports, "__esModule", ({ value: true }));
  37. exports.Hhanclub = void 0;
  38. const index_1 = __webpack_require__(2);
  39. class Hhanclub extends index_1.NexusPHP {
  40. constructor() {
  41. super("hhanclub.top");
  42. this.menu_items = [
  43. {
  44. "id": "bannerFold",
  45. "type": "switch",
  46. "display": "自动折叠横幅(隐藏时折叠设置无效)",
  47. "name": "自动折叠横幅",
  48. "value": true
  49. },
  50. {
  51. "id": "bannerHide",
  52. "type": "switch",
  53. "display": "隐藏横幅(隐藏时折叠设置无效)",
  54. "name": "隐藏横幅",
  55. "value": false
  56. }
  57. ].concat(this.menu_items);
  58. }
  59. onLoad() {
  60. super.onLoad();
  61. if (this.getHostValue("bannerHide")) {
  62. this.css += `
  63. td.clear.nowrap img {
  64. display: none;
  65. }`;
  66. }
  67. else if (this.getHostValue("bannerFold")) {
  68. const banner = document.querySelector("td.clear.nowrap");
  69. const original_height = banner === null || banner === void 0 ? void 0 : banner.clientHeight;
  70. this.css += `
  71. td.clear.nowrap img {
  72. height: 10px;
  73. overflow: hidden;
  74. transition: height 0.5s;
  75. }
  76.  
  77. td.clear.nowrap img:hover {
  78. height: ${original_height}px;
  79. }`;
  80. }
  81. }
  82. }
  83. exports.Hhanclub = Hhanclub;
  84.  
  85.  
  86. /***/ }),
  87. /* 2 */
  88. /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
  89.  
  90.  
  91. Object.defineProperty(exports, "__esModule", ({ value: true }));
  92. exports.NexusPHP = void 0;
  93. const common_1 = __webpack_require__(3);
  94. class NexusPHP extends common_1.Common {
  95. constructor(host) {
  96. super(host);
  97. this.passkey = "";
  98. this.menu_items = [
  99. {
  100. "id": "thanks",
  101. "type": "switch",
  102. "display": "自动说谢谢",
  103. "name": "自动说谢谢",
  104. "value": true
  105. }
  106. ].concat(this.menu_items);
  107. }
  108. onLoad() {
  109. super.onLoad();
  110. this.getPasskey();
  111. if (this.getHostValue("thanks") && location.href.indexOf("/details.php") >= 0) {
  112. this.sayThanks();
  113. }
  114. }
  115. getPasskey() {
  116. const value = this.getHostValue("passkey");
  117. let passkey = "";
  118. if (value) {
  119. passkey = String(value);
  120. }
  121. if (passkey != "") {
  122. this.passkey = passkey;
  123. return;
  124. }
  125. const cp_url = "https://" + this.host + "/usercp.php";
  126. GM_xmlhttpRequest({
  127. method: "GET",
  128. url: cp_url,
  129. onload: (response) => {
  130. if (response.status != 200) {
  131. console.log("Failed to get passkey.");
  132. return;
  133. }
  134. const container = document.implementation.createHTMLDocument().documentElement;
  135. container.innerHTML = response.responseText;
  136. const tds = container.querySelectorAll("td.rowfollow");
  137. for (const td of tds) {
  138. const tc = td;
  139. const re = /[\w\d]{32}/;
  140. const result = re.exec(tc.innerText);
  141. if (result) {
  142. this.setHostValue("passkey", result[0]);
  143. this.passkey = result[0];
  144. return;
  145. }
  146. }
  147. },
  148. onabort: () => {
  149. console.log("Abort to get passkey");
  150. },
  151. onerror: () => {
  152. console.log("Error to get passkey");
  153. }
  154. });
  155. }
  156. sayThanks() {
  157. this.wait(2000).then(() => {
  158. const input = document.querySelector("#saythanks");
  159. if (input && !input.disabled) {
  160. input.click();
  161. }
  162. }).catch(() => { console.log("Failed to say thanks."); });
  163. }
  164. }
  165. exports.NexusPHP = NexusPHP;
  166.  
  167.  
  168. /***/ }),
  169. /* 3 */
  170. /***/ ((__unused_webpack_module, exports) => {
  171.  
  172.  
  173. Object.defineProperty(exports, "__esModule", ({ value: true }));
  174. exports.Common = void 0;
  175. const helper_home = "https://github.com/amorphobia/pt-helper";
  176. const num_emoji = ["0️⃣", "1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣", "8️⃣", "9️⃣", "🔟"];
  177. class Common {
  178. constructor(host) {
  179. this.host = "";
  180. this.menu_items = [{
  181. "id": "feedback",
  182. "type": "link",
  183. "display": "💬反馈与建议",
  184. "value": helper_home + "/issues"
  185. }];
  186. this.host = host;
  187. this.registered_items = [];
  188. this.css = "";
  189. }
  190. init() {
  191. for (const item of this.menu_items) {
  192. item.id = this.host + "_" + item.id;
  193. if ((item.type == "switch" || item.type == "selection") && GM_getValue(item.id) == null) {
  194. GM_setValue(item.id, item.value);
  195. }
  196. }
  197. this.registerMenu();
  198. }
  199. onLoad() { }
  200. addStyle() {
  201. if (typeof GM_addStyle !== "undefined") {
  202. GM_addStyle(this.css);
  203. }
  204. else {
  205. const style = document.createElement("style");
  206. style.appendChild(document.createTextNode(this.css));
  207. (document.querySelector("head") || document.documentElement).appendChild(style);
  208. }
  209. }
  210. registerMenu() {
  211. for (const item of this.registered_items) {
  212. GM_unregisterMenuCommand(item);
  213. }
  214. this.registered_items = [];
  215. for (const item of this.menu_items) {
  216. const value = GM_getValue(item.id);
  217. if (value && value != null) {
  218. item.value = value;
  219. }
  220. let reg_item;
  221. switch (item.type) {
  222. case "switch":
  223. reg_item = GM_registerMenuCommand(`${item.value ? "✅" : "❌"}${item.display}`, () => {
  224. this.toggleSwitch(item);
  225. });
  226. break;
  227. case "selection":
  228. reg_item = GM_registerMenuCommand(`${num_emoji[item.value]}${item.display[item.value]}`, () => {
  229. this.nextSelection(item);
  230. });
  231. break;
  232. case "link":
  233. reg_item = GM_registerMenuCommand(`${item.display}`, () => {
  234. GM_openInTab(item.value, { active: true, insert: true, setParent: true });
  235. });
  236. break;
  237. case "text":
  238. reg_item = GM_registerMenuCommand(`${item.display}`, () => { });
  239. break;
  240. default:
  241. console.log(`Unrecognized menu item: ${item.id}`);
  242. break;
  243. }
  244. if (reg_item !== undefined) {
  245. this.registered_items.push(reg_item);
  246. }
  247. }
  248. }
  249. toggleSwitch(item) {
  250. const status = item.value ? "关闭" : "开启";
  251. GM_setValue(item.id, !item.value);
  252. GM_notification({
  253. text: `已${status}「${item.name}」\n(点击刷新网页后生效)`,
  254. timeout: 3500,
  255. onclick: () => { location.reload(); }
  256. });
  257. this.registerMenu();
  258. }
  259. nextSelection(item) {
  260. const new_value = (item.value + 1) % item.display.length;
  261. GM_setValue(item.id, new_value);
  262. GM_notification({
  263. text: `切换为「${item.display[new_value]}」\n(点击刷新网页后生效)`,
  264. timeout: 3500,
  265. onclick: () => { location.reload(); }
  266. });
  267. this.registerMenu();
  268. }
  269. wait(ms) {
  270. return new Promise((resolve, reject) => {
  271. setTimeout(resolve, ms);
  272. });
  273. }
  274. getHostValue(id) {
  275. return GM_getValue(this.host + "_" + id);
  276. }
  277. setHostValue(id, value) {
  278. GM_setValue(this.host + "_" + id, value);
  279. }
  280. }
  281. exports.Common = Common;
  282.  
  283.  
  284. /***/ }),
  285. /* 4 */
  286. /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
  287.  
  288.  
  289. Object.defineProperty(exports, "__esModule", ({ value: true }));
  290. exports.NanyangPT = void 0;
  291. const NexusPHP_1 = __webpack_require__(2);
  292. class NanyangPT extends NexusPHP_1.NexusPHP {
  293. constructor() {
  294. super("nanyangpt.com");
  295. this.menu_items = [
  296. {
  297. "id": "bannerHide",
  298. "type": "switch",
  299. "display": "隐藏横幅",
  300. "name": "隐藏横幅",
  301. "value": false
  302. }
  303. ].concat(this.menu_items);
  304. }
  305. onLoad() {
  306. super.onLoad();
  307. if (this.getHostValue("bannerHide")) {
  308. const info = document.querySelector("#info_block");
  309. const info_height = (info === null || info === void 0 ? void 0 : info.clientHeight) ? info.clientHeight + 5 : 30;
  310. this.css += `
  311. table.head {
  312. display: none;
  313. }
  314.  
  315. table.mainouter {
  316. margin-top: ${info_height}px;
  317. }`;
  318. }
  319. }
  320. }
  321. exports.NanyangPT = NanyangPT;
  322.  
  323.  
  324. /***/ }),
  325. /* 5 */
  326. /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
  327.  
  328.  
  329. Object.defineProperty(exports, "__esModule", ({ value: true }));
  330. exports.SJTU = void 0;
  331. const index_1 = __webpack_require__(2);
  332. class SJTU extends index_1.NexusPHP {
  333. constructor() {
  334. super("pt.sjtu.edu.cn");
  335. }
  336. }
  337. exports.SJTU = SJTU;
  338.  
  339.  
  340. /***/ }),
  341. /* 6 */
  342. /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
  343.  
  344.  
  345. Object.defineProperty(exports, "__esModule", ({ value: true }));
  346. exports.TJUPT = void 0;
  347. const index_1 = __webpack_require__(2);
  348. class TJUPT extends index_1.NexusPHP {
  349. constructor() {
  350. super("tjupt.org");
  351. this.menu_items = [
  352. {
  353. "id": "bannerFold",
  354. "type": "switch",
  355. "display": "自动折叠横幅(隐藏时折叠设置无效)",
  356. "name": "自动折叠横幅",
  357. "value": true
  358. },
  359. {
  360. "id": "bannerHide",
  361. "type": "switch",
  362. "display": "隐藏横幅(隐藏时折叠设置无效)",
  363. "name": "隐藏横幅",
  364. "value": false
  365. },
  366. {
  367. "id": "stickyHide",
  368. "type": "selection",
  369. "display": [
  370. "显示所有置顶",
  371. "隐藏一重置顶",
  372. "隐藏一、二重置顶",
  373. "隐藏所有置顶"
  374. ],
  375. "value": 0
  376. },
  377. {
  378. "id": "directLink",
  379. "type": "switch",
  380. "display": "种子直链按钮(左键点击按钮复制直链)",
  381. "name": "种子直链",
  382. "value": true
  383. },
  384. {
  385. "id": "colorBlind",
  386. "type": "switch",
  387. "display": "色盲模式",
  388. "name": "色盲模式",
  389. "value": false
  390. }
  391. ].concat(this.menu_items);
  392. }
  393. onLoad() {
  394. var _a;
  395. super.onLoad();
  396. if (this.getHostValue("bannerHide")) {
  397. this.css += `
  398. .logo_img img {
  399. display: none;
  400. }`;
  401. }
  402. else if (this.getHostValue("bannerFold")) {
  403. const logo_img = document.querySelector(".logo_img");
  404. const original_height = logo_img === null || logo_img === void 0 ? void 0 : logo_img.clientHeight;
  405. this.css += `
  406. .logo_img {
  407. height: 10px;
  408. overflow: hidden;
  409. transition: height 0.5s;
  410. }
  411.  
  412. .logo_img:hover {
  413. height: ${original_height}px;
  414. }`;
  415. }
  416. switch (this.getHostValue("stickyHide")) {
  417. case 3:
  418. this.css += `
  419. .triple_sticky_bg {
  420. display: none;
  421. }`;
  422. case 2:
  423. this.css += `
  424. .double_sticky_bg {
  425. display: none;
  426. }`;
  427. case 1:
  428. this.css += `
  429. .sticky_bg {
  430. display: none;
  431. }`;
  432. default:
  433. break;
  434. }
  435. if (this.getHostValue("directLink") && this.passkey != "") {
  436. const id_re = /id=[\d]+/;
  437. const tds = document.querySelectorAll("table.torrentname > tbody > tr:nth-of-type(1) > td:nth-of-type(3)");
  438. for (const td of tds) {
  439. const dl = td.querySelector("a");
  440. const result = id_re.exec((_a = dl === null || dl === void 0 ? void 0 : dl.href) !== null && _a !== void 0 ? _a : "");
  441. if (!result) {
  442. continue;
  443. }
  444. const direct_link = `https://www.${this.host}/download.php?${result[0]}&passkey=${this.passkey}`;
  445. const img = document.createElement("img");
  446. img.setAttribute("src", "pic/trans.gif");
  447. img.setAttribute("class", "torrent_direct_link");
  448. img.setAttribute("alt", "DL");
  449. const a = document.createElement("a");
  450. a.setAttribute("title", "左键单击复制,链接中包含个人秘钥Passkey,切勿泄露!");
  451. a.setAttribute("onclick", "return false");
  452. a.setAttribute("id", "direct_link");
  453. a.setAttribute("href", direct_link);
  454. a.setAttribute("data-clipboard-text", direct_link);
  455. a.appendChild(img);
  456. td.prepend(a);
  457. }
  458. this.css += `
  459. img.torrent_direct_link {
  460. width: 16px;
  461. height: 16px;
  462. background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAH5QTFRFR3BMyKN4cz0Td3d3sLCw6enp////qHg4oaGhcz0TlpaWkV8opnU2lmQrjVklqHg4m2kvo3I03ruJiFMhn24y4r+N2raG5cSP9+jQg04e1rKD68uUnYpv6ceS0q5/fkkaoaGhzqp8h4eHr6+v+Pj4ekQXdkAV+/v78fHxy6Z6f0p3WgAAAAp0Uk5TAP///////5aWlrne7esAAACHSURBVBjTbc5HEsIwEERRA5qxLeecc77/BTEN0oq/m1ddKhnG36xxtPRhBq2UxyFlG5gAt5zXk+hc59IFRM3yQksTAdKOf3UpICxYCEFEXIQAL2NCnHkAJ/4s7jh2AH6uFrkPSOrvgrhOAFWvFn0FGCYWhDemAbBd6h/XBtgfuh1gP3X2fb4BlrkIUt3i2kgAAAAASUVORK5CYII=');
  463. padding-bottom: 1px;
  464. }`;
  465. location.assign("javascript:registerClipboardJS('#direct_link');void(0)");
  466. }
  467. if (this.getHostValue("colorBlind")) {
  468. if (location.href.indexOf("/classes.php") >= 0) {
  469. const spans = document.querySelectorAll("table.main > tbody > tr > td:nth-of-type(2) > ul > li > span[style=\"color: green\"]");
  470. for (const span of spans) {
  471. span.setAttribute("style", "color: blue");
  472. }
  473. }
  474. }
  475. }
  476. getPasskey() {
  477. const value = this.getHostValue("passkey");
  478. let passkey = "";
  479. if (value) {
  480. passkey = String(value);
  481. }
  482. if (passkey != "") {
  483. this.passkey = passkey;
  484. return;
  485. }
  486. const link = document.querySelector("[title=\"Latest Torrents\"]");
  487. const re = /passkey=([\d\w]+)/;
  488. const result = re.exec(link.href);
  489. this.passkey = result && result.length > 1 ? result[1] : "";
  490. if (this.passkey != "") {
  491. this.setHostValue("passkey", this.passkey);
  492. }
  493. }
  494. }
  495. exports.TJUPT = TJUPT;
  496.  
  497.  
  498. /***/ })
  499. /******/ ]);
  500. /************************************************************************/
  501. /******/ // The module cache
  502. /******/ var __webpack_module_cache__ = {};
  503. /******/
  504. /******/ // The require function
  505. /******/ function __webpack_require__(moduleId) {
  506. /******/ // Check if module is in cache
  507. /******/ var cachedModule = __webpack_module_cache__[moduleId];
  508. /******/ if (cachedModule !== undefined) {
  509. /******/ return cachedModule.exports;
  510. /******/ }
  511. /******/ // Create a new module (and put it into the cache)
  512. /******/ var module = __webpack_module_cache__[moduleId] = {
  513. /******/ // no module.id needed
  514. /******/ // no module.loaded needed
  515. /******/ exports: {}
  516. /******/ };
  517. /******/
  518. /******/ // Execute the module function
  519. /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
  520. /******/
  521. /******/ // Return the exports of the module
  522. /******/ return module.exports;
  523. /******/ }
  524. /******/
  525. /************************************************************************/
  526. var __webpack_exports__ = {};
  527. // This entry need to be wrapped in an IIFE because it need to be isolated against other modules in the chunk.
  528. (() => {
  529. var exports = __webpack_exports__;
  530.  
  531. Object.defineProperty(exports, "__esModule", ({ value: true }));
  532. const hhanclub_top_1 = __webpack_require__(1);
  533. const nanyangpt_com_1 = __webpack_require__(4);
  534. const pt_sjtu_edu_cn_1 = __webpack_require__(5);
  535. const index_1 = __webpack_require__(6);
  536. const host = window.location.host;
  537. const sites = new Map([
  538. ["hhanclub.top", new hhanclub_top_1.Hhanclub()],
  539. ["nanyangpt.com", new nanyangpt_com_1.NanyangPT()],
  540. ["pt.sjtu.edu.cn", new pt_sjtu_edu_cn_1.SJTU()],
  541. ["tjupt.org", new index_1.TJUPT()]
  542. ]);
  543. const site = sites.get(host);
  544. if (site) {
  545. site.init();
  546. site.onLoad();
  547. site.addStyle();
  548. }
  549.  
  550. })();
  551.  
  552. /******/ })()
  553. ;