moviepilotNameTest

moviepilots名称测试

  1. // ==UserScript==
  2. // @name moviepilotNameTest
  3. // @namespace http://tampermonkey.net/
  4. // @version 2.3.6
  5. // @description moviepilots名称测试
  6. // @author yubanmeiqin9048
  7. // @match https://*/detail/*
  8. // @match https://*/details.php?id=*
  9. // @match https://*/details_movie.php?id=*
  10. // @match https://*/details_tv.php?id=*
  11. // @match https://*/details_animate.php?id=*
  12. // @match https://bangumi.moe/*
  13. // @match https://*.acgnx.se/*
  14. // @match https://*.dmhy.org/*
  15. // @match https://nyaa.si/*
  16. // @match https://mikanani.me/*
  17. // @match https://*.skyey2.com/*
  18. // @grant GM_log
  19. // @grant GM_xmlhttpRequest
  20. // @connect *
  21. // @license MIT
  22. // ==/UserScript==
  23. const windowPopup = true;
  24. const moviepilotUrl = 'http://localhost:3000';
  25. const moviepilotUser = 'admin';
  26. const moviepilotPassword = 'password';
  27. const apiKey = "apikey"
  28. let ptype = '';
  29. let btype = '';
  30. let site_domain = window.location.hostname;
  31. let torrent_info = { "site": 0, "site_name": "", "site_cookie": "", "site_ua": "", "site_proxy": null, "site_order": null, "title": "", "description": "", "imdbid": null, "enclosure": "", "page_url": "", "size": 0, "seeders": 0, "peers": 0, "grabs": 0, "pubdate": "", "date_elapsed": null, "uploadvolumefactor": 1, "downloadvolumefactor": 0, "hit_and_run": false, "labels": [], "pri_order": 0, "volume_factor": "普通" }
  32.  
  33. function waitForElements(selectors, timeout = 30000) {
  34. return new Promise((resolve, reject) => {
  35. const interval = 100; // check every 100ms
  36. const maxTries = timeout / interval;
  37. let tries = 0;
  38.  
  39. const checkExist = setInterval(() => {
  40. let allFound = true;
  41. const elements = selectors.map(selector => {
  42. const foundElements = document.getElementsByClassName(selector);
  43. if (foundElements.length === 0) {
  44. allFound = false;
  45. }
  46. return foundElements;
  47. });
  48.  
  49. if (allFound) {
  50. clearInterval(checkExist);
  51. resolve(elements);
  52. } else if (tries >= maxTries) {
  53. clearInterval(checkExist);
  54. reject(new Error(`Elements ${selectors.join(', ')} not found within ${timeout}ms`));
  55. }
  56. tries++;
  57. }, interval);
  58. });
  59. }
  60.  
  61. function renderTag(ptype, string, background_color) {
  62. if (ptype == 'hhanclub') {
  63. return `<span class="flex justify-center items-center rounded-md text-[12px] h-[18px] mr-2 px-[5px] font-bold" style="background-color:${background_color};color:#ffffff;">${string}</span>`;
  64. } else {
  65. return `<span style=\"background-color:${background_color};color:#ffffff;border-radius:0;font-size:12px;margin:0 4px 0 0;padding:1px 2px\">${string}</span>`;
  66. }
  67. }
  68.  
  69. function renderMoviepilotTag(ptype, tag) {
  70. if (ptype == "common") {
  71. return `<td class="rowhead nowrap" valign="top" align="right">MoviePilot</td><td class="rowfollow" valign="top" align="left">${tag}</td>`;
  72. } else if (ptype == 'm-team') {
  73. return `<th class="ant-descriptions-item-label" style="width: 135px; text-align: right;" colspan="1"><span>MoviePilot</span></th><td class="ant-descriptions-item-content" colspan="1"><span>${tag}</span></td>`;
  74. } else {
  75. return tag
  76. }
  77. }
  78.  
  79. function getSize(sizeStr) {
  80. let match = sizeStr.match(/(\d+\.\d+) (GB|MB|KB)/);
  81. if (!match) return 0;
  82. let size = parseFloat(match[1]);
  83. let unit = match[2].toLowerCase();
  84. switch (unit) {
  85. case 'mb':
  86. return size * 1024 ** 2;
  87. case 'gb':
  88. return size * 1024 ** 3;
  89. case 'tb':
  90. return size * 1024 ** 4;
  91. default:
  92. return 0;
  93. }
  94. }
  95.  
  96. function getFormattedDate() {
  97. const now = new Date();
  98. const year = now.getFullYear();
  99. const month = String(now.getMonth() + 1).padStart(2, '0');
  100. const day = String(now.getDate()).padStart(2, '0');
  101. const hours = String(now.getHours()).padStart(2, '0');
  102. const minutes = String(now.getMinutes()).padStart(2, '0');
  103. const seconds = String(now.getSeconds()).padStart(2, '0');
  104. return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
  105. }
  106.  
  107. function login() {
  108. return new Promise(function (resolve, reject) {
  109. GM_xmlhttpRequest({
  110. method: 'POST',
  111. responseType: 'json',
  112. url: moviepilotUrl + '/api/v1/login/access-token',
  113. data: `username=${moviepilotUser}&password=${moviepilotPassword}`,
  114. headers: {
  115. "accept": "application/json",
  116. "content-type": "application/x-www-form-urlencoded"
  117. },
  118. onload: (res) => {
  119. if (res.status === 200) {
  120. resolve(res.response.access_token);
  121. } else if (res.status === 404) {
  122. reject(new Error('登录失败'));
  123. } else {
  124. reject(new Error('Unexpected status code: ' + res.status));
  125. }
  126. },
  127. onerror: (err) => {
  128. GM_log(err)
  129. reject(new Error('未知错误'));
  130. }
  131. });
  132. });
  133. }
  134.  
  135. function recognize(token, title, subtitle) {
  136. return new Promise(function (resolve, reject) {
  137. GM_xmlhttpRequest({
  138. url: moviepilotUrl + `/api/v1/media/recognize?title=${title}&subtitle=${subtitle}`,
  139. method: "GET",
  140. headers: {
  141. "user-agent": navigator.userAgent,
  142. "content-type": "application/json",
  143. "Authorization": `bearer ${token}`
  144. },
  145. responseType: "json",
  146. onload: (res) => {
  147. if (res.status === 200) {
  148. resolve(res.response);
  149. } else if (res.status === 404) {
  150. reject(new Error('识别失败'));
  151. } else {
  152. reject(new Error('Unexpected status code: ' + res.status));
  153. }
  154. },
  155. onerror: (err) => {
  156. GM_log(err)
  157. reject(new Error('未知错误'));
  158. }
  159. });
  160. });
  161. }
  162.  
  163. function getSite(token) {
  164. return new Promise(function (resolve, reject) {
  165. GM_xmlhttpRequest({
  166. url: moviepilotUrl + `/api/v1/site/domain/${site_domain}`,
  167. method: "GET",
  168. headers: {
  169. "user-agent": navigator.userAgent,
  170. "content-type": "application/json",
  171. "Authorization": `bearer ${token}`
  172. },
  173. responseType: "json",
  174. onload: (res) => {
  175. if (res.status === 200) {
  176. resolve(res.response);
  177. } else if (res.status === 404) {
  178. reject(new Error('站点不存在'));
  179. } else {
  180. reject(new Error('Unexpected status code: ' + res.status));
  181. }
  182. },
  183. onerror: (err) => {
  184. GM_log(err)
  185. reject(new Error('未知错误'));
  186. }
  187. });
  188. });
  189. }
  190.  
  191. function downloadTorrentPublic(downloadButton, token, download_link) {
  192. downloadButton.textContent = "下载中";
  193. downloadButton.disabled = true;
  194. return new Promise(function (resolve, reject) {
  195. GM_xmlhttpRequest({
  196. method: 'GET',
  197. responseType: 'json',
  198. url: moviepilotUrl + `/api/v1/plugin/DownloaderApi/download_torrent_notest?apikey=${apiKey}&torrent_url=${download_link}`,
  199. headers: {
  200. "accept": "application/json",
  201. "content-type": "application/json",
  202. "Authorization": `bearer ${token}`
  203. },
  204. onload: (res) => {
  205. let status = JSON.parse(res.responseText)
  206. GM_log(status)
  207. if (status.success) {
  208. resolve();
  209. } else {
  210. reject(new Error(status.message));
  211. }
  212. }
  213. })
  214. })
  215. }
  216.  
  217. function downloadTorrent(downloadButton, token, media_info, torrent_name, torrent_description, download_link, torrent_size) {
  218. downloadButton.textContent = "下载中";
  219. downloadButton.disabled = true;
  220. return new Promise(function (resolve, reject) {
  221. getSite(token).then(data => {
  222. torrent_info.title = torrent_name
  223. torrent_info.description = torrent_description
  224. torrent_info.page_url = window.location.href
  225. torrent_info.enclosure = download_link
  226. torrent_info.size = torrent_size
  227. torrent_info.site = data.id
  228. torrent_info.site_name = data.name
  229. torrent_info.site_cookie = data.cookie
  230. torrent_info.proxy = data.proxy
  231. // torrent_info.pri_order=data.pri
  232. torrent_info.pubdate = getFormattedDate()
  233. torrent_info.site_ua = navigator.userAgent
  234. let download_info = {
  235. media_in: media_info,
  236. torrent_in: torrent_info
  237. }
  238. GM_xmlhttpRequest({
  239. method: 'POST',
  240. responseType: 'json',
  241. url: moviepilotUrl + `/api/v1/download/`,
  242. data: JSON.stringify(download_info),
  243. headers: {
  244. "accept": "application/json",
  245. "content-type": "application/json",
  246. "Authorization": `bearer ${token}`
  247. },
  248. onload: (res) => {
  249. GM_log(res.response.data)
  250. if (res.status === 200) {
  251. resolve();
  252. } else {
  253. reject(new Error('Unexpected status code: ' + res.status));
  254. }
  255. }
  256. })
  257. }).catch(err => {
  258. reject(err);
  259. });
  260. })
  261. }
  262.  
  263. function creatRecognizeRow(row, ptype, torrent_name, torrent_description, download_link, torrent_size) {
  264. row.innerHTML = renderMoviepilotTag(ptype, "识别中");
  265. login().then(token => {
  266. recognize(token, torrent_name, torrent_description).then(data => {
  267. GM_log(data.status)
  268. if (data.media_info) {
  269. let html = '';
  270. html += data.media_info.ptype ? renderTag(ptype, data.media_info.ptype, '#2775b6') : '';
  271. html += data.media_info.category ? renderTag(ptype, data.media_info.category, '#2775b6') : '';
  272. html += data.media_info.title ? renderTag(ptype, data.media_info.title, '#c54640') : '';
  273. html += data.meta_info.season_episode ? renderTag(ptype, data.meta_info.season_episode, '#e6702e') : '';
  274. html += data.meta_info.year ? renderTag(ptype, data.meta_info.year, '#e6702e') : '';
  275. html += data.media_info.tmdb_id ? '<a href="' + data.media_info.detail_link + '" target="_blank">' + renderTag(ptype, data.media_info.tmdb_id, '#5bb053') + '</a>' : '';
  276. html += data.meta_info.resource_type ? renderTag(ptype, data.meta_info.resource_type, '#677489') : '';
  277. html += data.meta_info.resource_pix ? renderTag(ptype, data.meta_info.resource_pix, '#677489') : '';
  278. html += data.meta_info.video_encode ? renderTag(ptype, data.meta_info.video_encode, '#677489') : '';
  279. html += data.meta_info.audio_encode ? renderTag(ptype, data.meta_info.audio_encode, '#677489') : '';
  280. html += data.meta_info.resource_team ? renderTag(ptype, data.meta_info.resource_team, '#701eeb') : '';
  281. html += (ptype === 'common') ?
  282. '<button id="download-button">下载种子</button>' :
  283. (ptype === 'hhanclub') ?
  284. '<button id="download-button" class="flex justify-center items-center rounded-md text-[12px] h-[18px] mr-2 px-[5px] font-bold" style="background-color:#cdae9c;color:#ffffff;">下载种子</button>' :
  285. ''
  286. row.innerHTML = renderMoviepilotTag(ptype, html);
  287. let downloadButton = document.getElementById("download-button");
  288. if (downloadButton) {
  289. downloadButton.addEventListener("click", function () {
  290. downloadTorrent(downloadButton, token, data.media_info, torrent_name, torrent_description, download_link, torrent_size).then(data => { downloadButton.textContent = "下载成功" }).catch(err => {
  291. downloadButton.disabled = false;
  292. downloadButton.textContent = err;
  293. });
  294. })
  295. }
  296. } else {
  297. row.innerHTML = renderMoviepilotTag(ptype, `识别失败`);
  298. }
  299. }).catch(error => {
  300. GM_log(error)
  301. row.innerHTML = renderMoviepilotTag(ptype, `识别失败`);
  302. });
  303. }).catch(error => {
  304. row.innerHTML = renderMoviepilotTag(ptype, `${error}`);
  305. });
  306. }
  307.  
  308. function creatPushButton(btype, element, download_link) {
  309. login().then(token => {
  310. let html = ""
  311. if (btype == "dmhy") {
  312. html = '<p><strong>MoviePilot:</strong>&nbsp;<a id="download-button">推送到MP</a></p>';
  313. } else if (btype == "bangumi") {
  314. html = '<button id="download-button" class="md-primary md-button md-default-theme" ng-transclude="" style="float: right; touch-action: pan-y; user-select: none;" tabindex="0"><i class="fa fa-file ng-scope"></i> 推送到MP</button>';
  315. } else {
  316. html = '<a class="btn episode-btn" id="download-button">推送到MP</a>'
  317. }
  318. element.insertAdjacentHTML('afterbegin', html);
  319. let downloadButton = document.getElementById("download-button");
  320. downloadButton.addEventListener("click", function () {
  321. downloadTorrentPublic(downloadButton, token, download_link).then(data => { downloadButton.textContent = "下载成功" }).catch(err => {
  322. downloadButton.disabled = false;
  323. downloadButton.textContent = err;
  324. });
  325. })
  326. })
  327. }
  328.  
  329. function creatRecognizeTip(tip, text) {
  330. tip.showText(`识别中`);
  331. login().then(token => {
  332. GM_log(text);
  333. recognize(token, encodeURIComponent(text), '').then(data => {
  334. GM_log(data.status)
  335. let html = '';
  336. html += data.media_info.ptype ? `类型:${data.media_info.ptype}<br>` : '';
  337. html += data.media_info.category ? `分类:${data.media_info.category}<br>` : '';
  338. html += data.media_info.title ? `标题:${data.media_info.title}<br>` : '';
  339. html += data.meta_info.season_episode ? `季集:${data.meta_info.season_episode}<br>` : '';
  340. html += data.meta_info.year ? `年份:${data.media_info.year}<br>` : '';
  341. html += data.meta_info.resource_team ? `制作:${data.meta_info.resource_team}<br>` : '';
  342. html += data.media_info.tmdb_id ? 'tmdb:<a href="' + data.media_info.detail_link + '" target="_blank">' + data.media_info.tmdb_id + '</a>' : 'tmdb:未识别';
  343. tip.showText(html);
  344. }).catch(error => {
  345. GM_log(error)
  346. tip.showText(`识别失败`);
  347. });
  348. }).catch(error => {
  349. tip.showText(`${error}`);
  350. });
  351. }
  352.  
  353.  
  354. (function () {
  355. 'use strict';
  356. // 结果面板
  357. class RecognizeTip {
  358. constructor() {
  359. const div = document.createElement('div');
  360. div.hidden = true;
  361. div.setAttribute('style',
  362. `position:absolute!important;
  363. font-size:13px!important;
  364. overflow:auto!important;
  365. background:#fff!important;
  366. font-family:sans-serif,Arial!important;
  367. font-weight:normal!important;
  368. text-align:left!important;
  369. color:#000!important;
  370. padding:0.5em 1em!important;
  371. line-height:1.5em!important;
  372. border-radius:5px!important;
  373. border:1px solid #ccc!important;
  374. box-shadow:4px 4px 8px #888!important;
  375. max-width:350px!important;
  376. max-height:216px!important;
  377. z-index:2147483647!important;`
  378. );
  379. document.documentElement.appendChild(div);
  380. //点击了内容面板,不再创建图标
  381. div.addEventListener('mouseup', e => e.stopPropagation());
  382. this._tip = div;
  383. }
  384. showText(text) { //显示测试结果
  385. this._tip.innerHTML = text;
  386. this._tip.hidden = !1;
  387. }
  388. hide() {
  389. this._tip.innerHTML = '';
  390. this._tip.hidden = true;
  391. }
  392. pop(ev) {
  393. this._tip.style.top = ev.pageY + 'px';
  394. //面板最大宽度为350px
  395. this._tip.style.left = (ev.pageX + 350 <= document.body.clientWidth ?
  396. ev.pageX : document.body.clientWidth - 350) + 'px';
  397. }
  398. }
  399. const tip = new RecognizeTip();
  400.  
  401. class Icon {
  402. constructor() {
  403. const icon = document.createElement('span');
  404. icon.hidden = true;
  405. icon.innerHTML = `<svg style="margin:4px !important;" width="16" height="16" viewBox="0 0 24 24">
  406. <path d="M12 2L22 12L12 22L2 12Z" style="fill:none;stroke:#3e84f4;stroke-width:2;"></path></svg>`;
  407. icon.setAttribute('style',
  408. `width:24px!important;
  409. height:24px!important;
  410. background:#fff!important;
  411. border-radius:50%!important;
  412. box-shadow:4px 4px 8px #888!important;
  413. position:absolute!important;
  414. z-index:2147483647!important;`
  415. );
  416. document.documentElement.appendChild(icon);
  417. //拦截二个鼠标事件,以防止选中的文本消失
  418. icon.addEventListener('mousedown', e => e.preventDefault(), true);
  419. icon.addEventListener('mouseup', ev => ev.preventDefault(), true);
  420. icon.addEventListener('click', ev => {
  421. if (ev.ctrlKey) navigator.clipboard.readText()
  422. .then(text => {
  423. this.queryText(text.trim(), ev);
  424. })
  425. .catch(err => {
  426. console.error('Failed to read contents: ', err);
  427. });
  428. else {
  429. const text = window.getSelection().toString().trim().replace(/\s{2,}/g, ' ');
  430. this.queryText(text, ev);
  431. }
  432. });
  433. this._icon = icon;
  434. }
  435. pop(ev) {
  436. const icon = this._icon;
  437. icon.style.top = ev.pageY + 9 + 'px';
  438. icon.style.left = ev.pageX + -18 + 'px';
  439. icon.hidden = !1;
  440. setTimeout(this.hide.bind(this), 2e3);
  441. }
  442. hide() {
  443. this._icon.hidden = true;
  444. }
  445. queryText(text, ev) {
  446. if (text) {
  447. this._icon.hidden = true;
  448. tip.pop(ev);
  449. creatRecognizeTip(tip, text);
  450. }
  451. }
  452. }
  453. const icon = new Icon();
  454.  
  455. document.addEventListener('mouseup', function (e) {
  456. var text = window.getSelection().toString().trim();
  457. GM_log(text);
  458. if (!text) {
  459. icon.hide();
  460. tip.hide();
  461. }
  462. else if (windowPopup) {icon.pop(e)};
  463. });
  464.  
  465. if (site_domain.includes('m-team')) {
  466. waitForElements(['ant-descriptions-row']).then((elementsArray) => {
  467. ptype = 'm-team'
  468. console.log(elementsArray);
  469. let rows = elementsArray[0]
  470. let torrent_name = (rows[0].firstElementChild.nextElementSibling.outerText.split('\n')[0]);
  471. let torrent_description = rows[1].textContent;
  472. let download_link = ''
  473. let torrent_size = '';
  474. let table = rows[0].parentNode;
  475. let row = table.insertRow(2);
  476. row.className = 'ant-descriptions-row';
  477. if (torrent_name) {
  478. creatRecognizeRow(row, ptype, torrent_name, torrent_description, download_link, torrent_size)
  479. }
  480. }).catch((error) => {
  481. console.error(error);
  482. });
  483. } else if (site_domain.includes('hhanclub')) {
  484. waitForElements(['font-bold leading-6']).then((elementsArray) => {
  485. ptype = 'hhanclub'
  486. let divs = elementsArray[0];
  487. console.log(elementsArray)
  488. let torrent_index_div = document.querySelector('a.index');
  489. let torrent_name = divs[3].innerText;
  490. let torrent_description = divs[5].innerText;
  491. let download_link = torrent_index_div.href;
  492. let torrent_size = getSize(divs[7].nextElementSibling.innerText);
  493. if (torrent_name) {
  494. divs[3].insertAdjacentHTML('afterend', '<div class="font-bold leading-6">moviepilot</div><div class="font-light leading-6 flex flex-wrap"><div id="moviepilot" class="font-light leading-6 flex"></div></div>');
  495. let row = document.getElementById("moviepilot");
  496. creatRecognizeRow(row, ptype, torrent_name, torrent_description, download_link, torrent_size)
  497. }
  498. }).catch((error) => {
  499. console.error(error);
  500. });
  501. } else if (site_domain.includes('bangumi')) {
  502. waitForElements(['md-actions','torrent-info']).then((elementsArray) => {
  503. btype = "bangumi";
  504. let buttonElement = elementsArray[0];
  505. let titleElement = elementsArray[1];
  506. console.log(elementsArray);
  507. let download_base_link = buttonElement[0].firstElementChild.formAction.replace(site_domain, site_domain + '/download')
  508. let title = titleElement[0].outerText;
  509. let download_link = download_base_link + "/" + title.replaceAll("/","_") + '.torrent'
  510. console.log(download_link);
  511. creatPushButton(btype, buttonElement[0], download_link)
  512. }).catch((error) => {
  513. console.error(error);
  514. });
  515. } else if (site_domain.includes('mikanani')) {
  516. waitForElements(['leftbar-nav']).then((elementsArray) => {
  517. btype = "mikan";
  518. let buttonElement = elementsArray[0];
  519. console.log(elementsArray);
  520. let download_link = buttonElement[0].firstElementChild.href
  521. console.log(download_link);
  522. creatPushButton(btype, buttonElement[0], download_link)
  523. }).catch((error) => {
  524. console.error(error);
  525. });
  526. } else if (site_domain.includes('dmhy')) {
  527. waitForElements(['dis ui-tabs-panel ui-widget-content ui-corner-bottom']).then((elementsArray) => {
  528. btype = "dmhy";
  529. let buttonElement = elementsArray[0];
  530. console.log(elementsArray);
  531. let download_link = buttonElement[0].firstElementChild.lastElementChild.href
  532. console.log(download_link);
  533. creatPushButton(btype,buttonElement[0], download_link)
  534. }).catch((error) => {
  535. console.error(error);
  536. });
  537. } else {
  538. waitForElements(['rowhead']).then((elementsArray) => {
  539. ptype = 'common'
  540. console.log(elementsArray);
  541. let rows = elementsArray[0]
  542. let torrent_name = (rows[0].nextElementSibling.firstElementChild.text).replace(/^\[(.*?)\]\./, '');
  543. let torrent_description = rows[1].nextElementSibling.innerText;
  544. let download_link = rows[0].nextElementSibling.firstElementChild.href;
  545. let torrent_size = getSize(rows[2].nextElementSibling.innerText);
  546. GM_log(download_link);
  547. let table = rows[1].parentNode.parentNode.parentNode;
  548. let row = table.insertRow(2);
  549. if (torrent_name) {
  550. creatRecognizeRow(row, ptype, torrent_name, torrent_description, download_link, torrent_size)
  551. }
  552. }).catch((error) => {
  553. console.error(error);
  554. });
  555. }
  556. })();