🏠 Home 

NexusPHP PT Helper Plus

基于NexusPHP的PT网站的辅助脚本


Install this script?
Author's suggested script

You may also like AC-baidu-google_sogou_bing_RedirectRemove_favicon_adaway_TwoLine.

Install this script
  1. // ==UserScript==
  2. // @name NexusPHP PT Helper Plus
  3. // @name:zh-CN NexusPHP PT 助手增强版
  4. // @namespace https://greasyfork.org/zh-CN/users/7326
  5. // @version 0.0.9
  6. // @description 基于NexusPHP的PT网站的辅助脚本
  7. // @description:zh-CN 适用于基于 NexusPHP 的 PT 站的辅助脚本
  8. // @author 〃萝卜
  9. // @match *://*.hdhome.org/*
  10. // @match *://*.pthome.net/*
  11. // @match *://*.byr.cn/*
  12. // @match *://*.tjupt.org/*
  13. // @match *://*.hdsky.me/*
  14. // @match *://*.btschool.club/*
  15. // @match *://*.et8.org/*
  16. // @match *://*.msg.vg/*
  17. // @match *://*.beitai.pt/*
  18. // @match *://*.52pt.site/*
  19. // @match *://*.cnscg.club/*
  20. // @match *://*.hdstreet.club/*
  21. // @match *://*.moecat.best/*
  22. // @note V0.0.9 新增pthome的支持,优化passkey获取流程,修复提取url的地址参数
  23. // @grant unsafeWindow
  24. // @grant GM_addStyle
  25. // @grant GM_setClipboard
  26. // @run-at document-end
  27. // ==/UserScript==
  28. 'use strict';
  29. let domParser = null, passkey = localStorage.getItem('passkey');
  30. let isHalfPage = false;
  31. /**
  32. * @class
  33. * @memberof LuCI
  34. * @hideconstructor
  35. * @classdesc
  36. *
  37. * Slightly modified version of `LuCI.dom` (https://github.com/openwrt/luci/blob/5d55a0a4a9c338f64818ac73b7d5f28079aa95b7/modules/luci-base/htdocs/luci-static/resources/luci.js#L2080),
  38. * which is licensed under Apache License 2.0 (https://github.com/openwrt/luci/blob/master/LICENSE).
  39. *
  40. * The `dom` class provides convenience method for creating and
  41. * manipulating DOM elements.
  42. */
  43. const dom = {
  44. /**
  45. * Tests whether the given argument is a valid DOM `Node`.
  46. *
  47. * @instance
  48. * @memberof LuCI.dom
  49. * @param {*} e
  50. * The value to test.
  51. *
  52. * @returns {boolean}
  53. * Returns `true` if the value is a DOM `Node`, else `false`.
  54. */
  55. elem: function(e) {
  56. return (e != null && typeof(e) == 'object' && 'nodeType' in e);
  57. },
  58. /**
  59. * Parses a given string as HTML and returns the first child node.
  60. *
  61. * @instance
  62. * @memberof LuCI.dom
  63. * @param {string} s
  64. * A string containing an HTML fragment to parse. Note that only
  65. * the first result of the resulting structure is returned, so an
  66. * input value of `<div>foo</div> <div>bar</div>` will only return
  67. * the first `div` element node.
  68. *
  69. * @returns {Node}
  70. * Returns the first DOM `Node` extracted from the HTML fragment or
  71. * `null` on parsing failures or if no element could be found.
  72. */
  73. parse: function(s) {
  74. var elem;
  75. try {
  76. domParser = domParser || new DOMParser();
  77. let d = domParser.parseFromString(s, 'text/html');
  78. elem = d.body.firstChild || d.head.firstChild;
  79. }
  80. catch(e) {}
  81. if (!elem) {
  82. try {
  83. dummyElem = dummyElem || document.createElement('div');
  84. dummyElem.innerHTML = s;
  85. elem = dummyElem.firstChild;
  86. }
  87. catch (e) {}
  88. }
  89. return elem || null;
  90. },
  91. /**
  92. * Tests whether a given `Node` matches the given query selector.
  93. *
  94. * This function is a convenience wrapper around the standard
  95. * `Node.matches("selector")` function with the added benefit that
  96. * the `node` argument may be a non-`Node` value, in which case
  97. * this function simply returns `false`.
  98. *
  99. * @instance
  100. * @memberof LuCI.dom
  101. * @param {*} node
  102. * The `Node` argument to test the selector against.
  103. *
  104. * @param {string} [selector]
  105. * The query selector expression to test against the given node.
  106. *
  107. * @returns {boolean}
  108. * Returns `true` if the given node matches the specified selector
  109. * or `false` when the node argument is no valid DOM `Node` or the
  110. * selector didn't match.
  111. */
  112. matches: function(node, selector) {
  113. var m = this.elem(node) ? node.matches || node.msMatchesSelector : null;
  114. return m ? m.call(node, selector) : false;
  115. },
  116. /**
  117. * Returns the closest parent node that matches the given query
  118. * selector expression.
  119. *
  120. * This function is a convenience wrapper around the standard
  121. * `Node.closest("selector")` function with the added benefit that
  122. * the `node` argument may be a non-`Node` value, in which case
  123. * this function simply returns `null`.
  124. *
  125. * @instance
  126. * @memberof LuCI.dom
  127. * @param {*} node
  128. * The `Node` argument to find the closest parent for.
  129. *
  130. * @param {string} [selector]
  131. * The query selector expression to test against each parent.
  132. *
  133. * @returns {Node|null}
  134. * Returns the closest parent node matching the selector or
  135. * `null` when the node argument is no valid DOM `Node` or the
  136. * selector didn't match any parent.
  137. */
  138. parent: function(node, selector) {
  139. if (this.elem(node) && node.closest)
  140. return node.closest(selector);
  141. while (this.elem(node))
  142. if (this.matches(node, selector))
  143. return node;
  144. else
  145. node = node.parentNode;
  146. return null;
  147. },
  148. /**
  149. * Appends the given children data to the given node.
  150. *
  151. * @instance
  152. * @memberof LuCI.dom
  153. * @param {*} node
  154. * The `Node` argument to append the children to.
  155. *
  156. * @param {*} [children]
  157. * The childrens to append to the given node.
  158. *
  159. * When `children` is an array, then each item of the array
  160. * will be either appended as child element or text node,
  161. * depending on whether the item is a DOM `Node` instance or
  162. * some other non-`null` value. Non-`Node`, non-`null` values
  163. * will be converted to strings first before being passed as
  164. * argument to `createTextNode()`.
  165. *
  166. * When `children` is a function, it will be invoked with
  167. * the passed `node` argument as sole parameter and the `append`
  168. * function will be invoked again, with the given `node` argument
  169. * as first and the return value of the `children` function as
  170. * second parameter.
  171. *
  172. * When `children` is is a DOM `Node` instance, it will be
  173. * appended to the given `node`.
  174. *
  175. * When `children` is any other non-`null` value, it will be
  176. * converted to a string and appened to the `innerHTML` property
  177. * of the given `node`.
  178. *
  179. * @returns {Node|null}
  180. * Returns the last children `Node` appended to the node or `null`
  181. * if either the `node` argument was no valid DOM `node` or if the
  182. * `children` was `null` or didn't result in further DOM nodes.
  183. */
  184. append: function(node, children) {
  185. if (!this.elem(node))
  186. return null;
  187. if (Array.isArray(children)) {
  188. for (var i = 0; i < children.length; i++)
  189. if (this.elem(children[i]))
  190. node.appendChild(children[i]);
  191. else if (children !== null && children !== undefined)
  192. node.appendChild(document.createTextNode('' + children[i]));
  193. return node.lastChild;
  194. }
  195. else if (typeof(children) === 'function') {
  196. return this.append(node, children(node));
  197. }
  198. else if (this.elem(children)) {
  199. return node.appendChild(children);
  200. }
  201. else if (children !== null && children !== undefined) {
  202. node.innerHTML = '' + children;
  203. return node.lastChild;
  204. }
  205. return null;
  206. },
  207. /**
  208. * Replaces the content of the given node with the given children.
  209. *
  210. * This function first removes any children of the given DOM
  211. * `Node` and then adds the given given children following the
  212. * rules outlined below.
  213. *
  214. * @instance
  215. * @memberof LuCI.dom
  216. * @param {*} node
  217. * The `Node` argument to replace the children of.
  218. *
  219. * @param {*} [children]
  220. * The childrens to replace into the given node.
  221. *
  222. * When `children` is an array, then each item of the array
  223. * will be either appended as child element or text node,
  224. * depending on whether the item is a DOM `Node` instance or
  225. * some other non-`null` value. Non-`Node`, non-`null` values
  226. * will be converted to strings first before being passed as
  227. * argument to `createTextNode()`.
  228. *
  229. * When `children` is a function, it will be invoked with
  230. * the passed `node` argument as sole parameter and the `append`
  231. * function will be invoked again, with the given `node` argument
  232. * as first and the return value of the `children` function as
  233. * second parameter.
  234. *
  235. * When `children` is is a DOM `Node` instance, it will be
  236. * appended to the given `node`.
  237. *
  238. * When `children` is any other non-`null` value, it will be
  239. * converted to a string and appened to the `innerHTML` property
  240. * of the given `node`.
  241. *
  242. * @returns {Node|null}
  243. * Returns the last children `Node` appended to the node or `null`
  244. * if either the `node` argument was no valid DOM `node` or if the
  245. * `children` was `null` or didn't result in further DOM nodes.
  246. */
  247. content: function(node, children) {
  248. if (!this.elem(node))
  249. return null;
  250. while (node.firstChild)
  251. node.removeChild(node.firstChild);
  252. return this.append(node, children);
  253. },
  254. /**
  255. * Sets attributes or registers event listeners on element nodes.
  256. *
  257. * @instance
  258. * @memberof LuCI.dom
  259. * @param {*} node
  260. * The `Node` argument to set the attributes or add the event
  261. * listeners for. When the given `node` value is not a valid
  262. * DOM `Node`, the function returns and does nothing.
  263. *
  264. * @param {string|Object<string, *>} key
  265. * Specifies either the attribute or event handler name to use,
  266. * or an object containing multiple key, value pairs which are
  267. * each added to the node as either attribute or event handler,
  268. * depending on the respective value.
  269. *
  270. * @param {*} [val]
  271. * Specifies the attribute value or event handler function to add.
  272. * If the `key` parameter is an `Object`, this parameter will be
  273. * ignored.
  274. *
  275. * When `val` is of type function, it will be registered as event
  276. * handler on the given `node` with the `key` parameter being the
  277. * event name.
  278. *
  279. * When `val` is of type object, it will be serialized as JSON and
  280. * added as attribute to the given `node`, using the given `key`
  281. * as attribute name.
  282. *
  283. * When `val` is of any other type, it will be added as attribute
  284. * to the given `node` as-is, with the underlying `setAttribute()`
  285. * call implicitely turning it into a string.
  286. */
  287. attr: function(node, key, val) {
  288. if (!this.elem(node))
  289. return null;
  290. var attr = null;
  291. if (typeof(key) === 'object' && key !== null)
  292. attr = key;
  293. else if (typeof(key) === 'string')
  294. attr = {}, attr[key] = val;
  295. for (key in attr) {
  296. if (!attr.hasOwnProperty(key) || attr[key] == null)
  297. continue;
  298. switch (typeof(attr[key])) {
  299. case 'function':
  300. node.addEventListener(key, attr[key]);
  301. break;
  302. case 'object':
  303. node.setAttribute(key, JSON.stringify(attr[key]));
  304. break;
  305. default:
  306. node.setAttribute(key, attr[key]);
  307. }
  308. }
  309. },
  310. /**
  311. * Creates a new DOM `Node` from the given `html`, `attr` and
  312. * `data` parameters.
  313. *
  314. * This function has multiple signatures, it can be either invoked
  315. * in the form `create(html[, attr[, data]])` or in the form
  316. * `create(html[, data])`. The used variant is determined from the
  317. * type of the second argument.
  318. *
  319. * @instance
  320. * @memberof LuCI.dom
  321. * @param {*} html
  322. * Describes the node to create.
  323. *
  324. * When the value of `html` is of type array, a `DocumentFragment`
  325. * node is created and each item of the array is first converted
  326. * to a DOM `Node` by passing it through `create()` and then added
  327. * as child to the fragment.
  328. *
  329. * When the value of `html` is a DOM `Node` instance, no new
  330. * element will be created but the node will be used as-is.
  331. *
  332. * When the value of `html` is a string starting with `<`, it will
  333. * be passed to `dom.parse()` and the resulting value is used.
  334. *
  335. * When the value of `html` is any other string, it will be passed
  336. * to `document.createElement()` for creating a new DOM `Node` of
  337. * the given name.
  338. *
  339. * @param {Object<string, *>} [attr]
  340. * Specifies an Object of key, value pairs to set as attributes
  341. * or event handlers on the created node. Refer to
  342. * {@link LuCI.dom#attr dom.attr()} for details.
  343. *
  344. * @param {*} [data]
  345. * Specifies children to append to the newly created element.
  346. * Refer to {@link LuCI.dom#append dom.append()} for details.
  347. *
  348. * @throws {InvalidCharacterError}
  349. * Throws an `InvalidCharacterError` when the given `html`
  350. * argument contained malformed markup (such as not escaped
  351. * `&` characters in XHTML mode) or when the given node name
  352. * in `html` contains characters which are not legal in DOM
  353. * element names, such as spaces.
  354. *
  355. * @returns {Node}
  356. * Returns the newly created `Node`.
  357. */
  358. create: function() {
  359. var html = arguments[0],
  360. attr = arguments[1],
  361. data = arguments[2],
  362. elem;
  363. if (!(attr instanceof Object) || Array.isArray(attr))
  364. data = attr, attr = null;
  365. if (Array.isArray(html)) {
  366. elem = document.createDocumentFragment();
  367. for (var i = 0; i < html.length; i++)
  368. elem.appendChild(this.create(html[i]));
  369. }
  370. else if (this.elem(html)) {
  371. elem = html;
  372. }
  373. else if (html.charCodeAt(0) === 60) {
  374. elem = this.parse(html);
  375. }
  376. else {
  377. elem = document.createElement(html);
  378. }
  379. if (!elem)
  380. return null;
  381. this.attr(elem, attr);
  382. this.append(elem, data);
  383. return elem;
  384. },
  385. /**
  386. * The ignore callback function is invoked by `isEmpty()` for each
  387. * child node to decide whether to ignore a child node or not.
  388. *
  389. * When this function returns `false`, the node passed to it is
  390. * ignored, else not.
  391. *
  392. * @callback LuCI.dom~ignoreCallbackFn
  393. * @param {Node} node
  394. * The child node to test.
  395. *
  396. * @returns {boolean}
  397. * Boolean indicating whether to ignore the node or not.
  398. */
  399. /**
  400. * Tests whether a given DOM `Node` instance is empty or appears
  401. * empty.
  402. *
  403. * Any element child nodes which have the CSS class `hidden` set
  404. * or for which the optionally passed `ignoreFn` callback function
  405. * returns `false` are ignored.
  406. *
  407. * @instance
  408. * @memberof LuCI.dom
  409. * @param {Node} node
  410. * The DOM `Node` instance to test.
  411. *
  412. * @param {LuCI.dom~ignoreCallbackFn} [ignoreFn]
  413. * Specifies an optional function which is invoked for each child
  414. * node to decide whether the child node should be ignored or not.
  415. *
  416. * @returns {boolean}
  417. * Returns `true` if the node does not have any children or if
  418. * any children node either has a `hidden` CSS class or a `false`
  419. * result when testing it using the given `ignoreFn`.
  420. */
  421. isEmpty: function(node, ignoreFn) {
  422. for (var child = node.firstElementChild; child != null; child = child.nextElementSibling)
  423. if (!child.classList.contains('hidden') && (!ignoreFn || !ignoreFn(child)))
  424. return false;
  425. return true;
  426. }
  427. };
  428. function E() { return dom.create.apply(dom, arguments); }
  429. function override(object, method, newMethod) {
  430. const original = object[method];
  431. object[method] = function(...args) {
  432. return newMethod.apply(this, [original.bind(this)].concat(args));
  433. };
  434. Object.assign(object[method], original);
  435. }
  436. function getTorrentURL(url) {
  437. const u = new URL(url);
  438. const id = u.searchParams.get('id');
  439. u.pathname = '/download.php';
  440. u.hash = '';
  441. u.search = '';
  442. u.searchParams.set('id', id);
  443. u.searchParams.set('passkey', passkey);
  444. return u.href;
  445. }
  446. function savePasskeyFromUrl(url) {
  447. passkey = new URL(url).searchParams.get('passkey');
  448. if (passkey)
  449. localStorage.setItem('passkey', passkey);
  450. else
  451. localStorage.removeItem('passkey');
  452. }
  453. function shouldSkipThis(trNode){
  454. if(trNode.className.indexOf("half") >= 0){
  455. // 如果是两行节点 ==> 特殊之处是:他的子节点中有一个是和他同className的
  456. if(trNode.querySelector("." + trNode.className) == null){
  457. return true;
  458. }
  459. isHalfPage = true;
  460. }
  461. return false;
  462. }
  463. function addListSelect(trlist) {
  464. trlist[0].prepend(E('td', {
  465. class: 'colhead',
  466. align: 'center'
  467. }, '链接'));
  468. trlist[0].prepend(E('td', {
  469. class: 'colhead',
  470. align: 'center',
  471. style: 'padding: 0px'
  472. }, E('button', {
  473. class: 'btn',
  474. style: 'font-size: 9pt;',
  475. click: function() {
  476. passkey = localStorage.getItem('passkey');
  477. if (!passkey) {
  478. alert('No passkey!');
  479. return;
  480. }
  481. let text = '';
  482. for (let i of this.parentElement.parentElement.parentElement.getElementsByClassName('my_selected')) {
  483. text += getTorrentURL(i.getElementsByTagName('a')[1].href) + '\n';
  484. }
  485. GM_setClipboard(text);
  486. this.innerHTML = "<span style='color:red'>已复制</span>";
  487. }
  488. }, '复制')));
  489. let mousedown = false;
  490. for (var i = 1; i < trlist.length; ++i) {
  491. if(shouldSkipThis(trlist[i])) continue; // 对于某些跨行的需要跳过某些行
  492. const seltd = E('td', {
  493. class: 'rowfollow nowrap',
  494. style: 'padding: 0px;',
  495. align: 'center',
  496. rowSpan: isHalfPage ? '2':'1',
  497. mousedown: function(e) {
  498. e.preventDefault();
  499. mousedown = true;
  500. this.firstChild.click();
  501. },
  502. mouseenter: function() {
  503. if (mousedown)
  504. this.firstChild.click();
  505. }
  506. }, E('input', {
  507. type: 'checkbox',
  508. style: 'zoom: 1.5;',
  509. click: function() {
  510. this.parentElement.parentElement.classList.toggle('my_selected');
  511. },
  512. mousedown: function(e) { e.stopPropagation(); }
  513. }));
  514. const copytd = seltd.cloneNode();
  515. copytd.append(E('button', {
  516. class: 'btn',
  517. click: function() {
  518. passkey = localStorage.getItem('passkey');
  519. if (!passkey) {
  520. alert('No passkey!');
  521. return;
  522. }
  523. GM_setClipboard(getTorrentURL(this.parentElement.nextElementSibling.nextElementSibling.getElementsByTagName('a')[0].href));
  524. this.innerHTML = "<span style='color:red'>已复制</span>";
  525. }
  526. }, '复制'));
  527. trlist[i].prepend(copytd);
  528. trlist[i].prepend(seltd);
  529. }
  530. document.addEventListener('mouseup', function(e) {
  531. if (mousedown) {
  532. e.preventDefault();
  533. mousedown = false;
  534. }
  535. });
  536. }
  537. function modifyAnchor(a, url) {
  538. a.href = url;
  539. a.addEventListener('click', function(ev) {
  540. ev.preventDefault();
  541. ev.stopPropagation();
  542. GM_setClipboard(this.href);
  543. if (!this.getAttribute('data-copied')) {
  544. this.setAttribute('data-copied', '1');
  545. this.parentElement.previousElementSibling.innerHTML += '(已复制)';
  546. }
  547. });
  548. }
  549. (function() {
  550. GM_addStyle(`<style>
  551. .my_selected { background-color: rgba(0, 0, 0, 0.4); }
  552. td.rowfollow button { font-size: 9pt; }
  553. </style>`);
  554. if(localStorage.getItem('passkey') == null){
  555. var insIframe = document.createElement("iframe");
  556. insIframe.src="/usercp.php";
  557. document.body.appendChild(insIframe);
  558. }
  559. switch (location.pathname) {
  560. case '/torrents.php': {
  561. const trlist = document.querySelectorAll('.torrents > tbody > tr');
  562. addListSelect(trlist);
  563. }
  564. break;
  565. case '/details.php': {
  566. let dlAnchor = document.getElementById('direct_link'); // tjupt.org
  567. if (!dlAnchor) {
  568. var trlist = document.querySelectorAll('#outer > h1 + table > tbody > tr');
  569. const names = ['种子链接'];
  570. for (let i of trlist) {
  571. const name = i.firstElementChild.innerText;
  572. if (names.includes(name)) {
  573. dlAnchor = i.querySelector("a");
  574. break;
  575. }
  576. }
  577. }
  578. if (dlAnchor) {
  579. const url = dlAnchor.getAttribute('href') || dlAnchor.getAttribute('data-clipboard-text'); // hdhome.org || tjupt.org
  580. modifyAnchor(dlAnchor, url);
  581. savePasskeyFromUrl(url);
  582. } else {
  583. let text = '没有 passkey, 点此打开控制面板获取 passkey';
  584. let url = null;
  585. if (passkey) {
  586. url = getTorrentURL(location);
  587. const u = new URL(url);
  588. u.searchParams.set('passkey', '***');
  589. text = u.href;
  590. }
  591. const a = E('a', { href: '/usercp.php' }, text);
  592. if (url)
  593. modifyAnchor(a, url);
  594. trlist[0].insertAdjacentElement('afterend', E('tr', [
  595. E('td', {
  596. class: 'rowhead nowrap',
  597. valign: 'top',
  598. align: 'right'
  599. }, '种子链接'),
  600. E('td', {
  601. class: 'rowfollow',
  602. valign: 'top',
  603. align: 'left'
  604. }, a)
  605. ]));
  606. }
  607. }
  608. break;
  609. case '/usercp.php': {
  610. const url = new URL(location);
  611. if(!url.searchParams.get('action')) {
  612. const names = ['passkey', '密钥'];
  613. for (let i of document.querySelectorAll('#outer > .main + table tr')) {
  614. const name = i.firstElementChild.innerText;
  615. if (names.includes(name)) { // 修复因为多个元素导致的错误
  616. passkey = i.lastElementChild.innerText;
  617. i.lastElementChild.innerHTML += ' (已获取)';
  618. break;
  619. }
  620. }
  621. if (passkey)
  622. localStorage.setItem('passkey', passkey);
  623. else
  624. localStorage.removeItem('passkey');
  625. }
  626. }
  627. break;
  628. case '/userdetails.php': {
  629. override(unsafeWindow, 'getusertorrentlistajax', function(original, userid, type, blockid) {
  630. if (original(userid, type, blockid)) {
  631. const blockdiv = document.getElementById(blockid);
  632. addListSelect(blockdiv.getElementsByTagName('tr'));
  633. return true;
  634. }
  635. return false;
  636. });
  637. }
  638. break;
  639. }
  640. })();