JavScript

一站式体验,JavBus 兼容

Ajankohdalta 24.4.2022. Katso uusin versio.

  1. // ==UserScript==
  2. // @name JavScript
  3. // @namespace JavScript@blc
  4. // @version 3.1.0
  5. // @author blc
  6. // @description 一站式体验,JavBus 兼容
  7. // @icon https://s1.ax1x.com/2022/04/01/q5lzYn.png
  8. // @include *
  9. // @require https://unpkg.com/masonry-layout@4/dist/masonry.pkgd.min.js
  10. // @require https://unpkg.com/infinite-scroll@4/dist/infinite-scroll.pkgd.min.js
  11. // @resource play https://s4.ax1x.com/2022/01/12/7nYuKe.png
  12. // @resource success https://s1.ax1x.com/2022/04/01/q5l2LD.png
  13. // @resource info https://s1.ax1x.com/2022/04/01/q5lyz6.png
  14. // @resource warn https://s1.ax1x.com/2022/04/01/q5lgsO.png
  15. // @resource error https://s1.ax1x.com/2022/04/01/q5lcQK.png
  16. // @connect *
  17. // @run-at document-start
  18. // @grant GM_registerMenuCommand
  19. // @grant GM_getResourceURL
  20. // @grant GM_xmlhttpRequest
  21. // @grant GM_setClipboard
  22. // @grant GM_notification
  23. // @grant GM_addElement
  24. // @grant GM_deleteValue
  25. // @grant GM_listValues
  26. // @grant GM_openInTab
  27. // @grant GM_addStyle
  28. // @grant GM_setValue
  29. // @grant GM_getValue
  30. // @grant GM_info
  31. // @license GPL-3.0-only
  32. // @compatible chrome
  33. // @compatible edge
  34. // ==/UserScript==
  35.  
  36. /**
  37. * TODO:
  38. * ⏳ 脚本 - JavDB 兼容
  39. * ⏳ 详情 - 发送磁链至 aria2 下载?
  40. * ⏳ 列表 - 自定义数据聚合页
  41. * ⏳ 脚本 - icon, style, bootstrap 精简 & 调整统一
  42. */
  43.  
  44. (function () {
  45. // match domain
  46. const MatchDomains = [
  47. { domain: "JavBus", regex: /(jav|bus|dmm|see|cdn|fan){2}\./g },
  48. { domain: "JavDB", regex: /javdb\d*\.com/g },
  49. { domain: "Drive115", regex: /captchaapi\.115\.com/g },
  50. ];
  51. const Matched = MatchDomains.find(({ regex }) => regex.test(location.host));
  52. if (!Matched?.domain) return;
  53.  
  54. // document
  55. const DOC = document;
  56. DOC.create = (tag, attr = {}, child) => {
  57. const element = DOC.createElement(tag);
  58. Object.keys(attr).forEach(name => element.setAttribute(name, attr[name]));
  59. typeof child === "string" && element.appendChild(DOC.createTextNode(child));
  60. typeof child === "object" && element.appendChild(child);
  61. return element;
  62. };
  63.  
  64. // request
  65. const request = (url, data = {}, method = "GET", params = {}) => {
  66. if (!url) return;
  67.  
  68. method = method ? method.toUpperCase().trim() : "GET";
  69. if (!["GET", "POST"].includes(method)) return;
  70.  
  71. if (Object.prototype.toString.call(data) === "[object Object]") {
  72. data = Object.keys(data).reduce((pre, cur) => {
  73. return `${pre ? `${pre}&` : pre}${cur}=${encodeURIComponent(data[cur])}`;
  74. }, "");
  75. }
  76.  
  77. if (method === "GET") {
  78. params.responseType = params.responseType ?? "document";
  79. if (data) {
  80. if (url.includes("?")) {
  81. url = `${url}${url.charAt(url.length - 1) === "&" ? "" : "&"}${data}`;
  82. } else {
  83. url = `${url}?${data}`;
  84. }
  85. }
  86. }
  87. if (method === "POST") {
  88. params.responseType = params.responseType ?? "json";
  89. const headers = params.headers ?? {};
  90. params.headers = { "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8", ...headers };
  91. }
  92.  
  93. return new Promise(resolve => {
  94. GM_xmlhttpRequest({
  95. url,
  96. data,
  97. method,
  98. timeout: 20000,
  99. onload: ({ status, response }) => {
  100. if (response?.errcode === 911) verify();
  101. if (status === 404) response = false;
  102. if (response && ["", "text"].includes(params.responseType)) {
  103. const htmlRegex = /<\/?[a-z][\s\S]*>/i;
  104. const jsonRegex = /^{.*}$/;
  105. if (htmlRegex.test(response)) {
  106. response = new DOMParser().parseFromString(response, "text/html");
  107. } else if (jsonRegex.test(response)) {
  108. response = JSON.parse(response);
  109. }
  110. }
  111. resolve(response);
  112. },
  113. ...params,
  114. });
  115. });
  116. };
  117.  
  118. // utils
  119. const getDate = timestamp => {
  120. const date = timestamp ? new Date(timestamp) : new Date();
  121. const Y = date.getFullYear();
  122. const M = `${date.getMonth() + 1}`.padStart(2, "0");
  123. const D = `${date.getDate()}`.padStart(2, "0");
  124. return `${Y}-${M}-${D}`;
  125. };
  126. const addCopyTarget = (selectors, attr = {}) => {
  127. const node = DOC.querySelector(selectors);
  128. const _attr = { "data-copy": node.textContent.trim(), class: "x-ml", href: "javascript:void(0);" };
  129. const target = DOC.create("a", { ..._attr, ...attr }, "复制");
  130. target.addEventListener("click", handleCopyTxt);
  131. node.appendChild(target);
  132. };
  133. const handleCopyTxt = (e, text) => {
  134. if (!e?.target?.dataset?.copy?.trim()) return;
  135.  
  136. e.preventDefault();
  137. e.stopPropagation();
  138.  
  139. const { target } = e;
  140. GM_setClipboard(target.dataset.copy.trim());
  141. const originText = target.textContent ?? "";
  142. target.textContent = text ?? "成功";
  143.  
  144. const timer = setTimeout(() => {
  145. target.textContent = originText;
  146. clearTimeout(timer);
  147. }, 400);
  148.  
  149. return 1;
  150. };
  151. const transToBytes = sizeStr => {
  152. const sizer = [
  153. { unit: /byte/gi, transform: size => size },
  154. { unit: /kb/gi, transform: size => size * Math.pow(1000, 1) },
  155. { unit: /mb/gi, transform: size => size * Math.pow(1000, 2) },
  156. { unit: /gb/gi, transform: size => size * Math.pow(1000, 3) },
  157. { unit: /kib/gi, transform: size => size * Math.pow(1024, 1) },
  158. { unit: /mib/gi, transform: size => size * Math.pow(1024, 2) },
  159. { unit: /gib/gi, transform: size => size * Math.pow(1024, 3) },
  160. ];
  161.  
  162. const size = sizeStr.replace(/[a-zA-Z\s]/g, "");
  163. if (size <= 0) return 0;
  164.  
  165. return (
  166. sizer
  167. .find(({ unit }) => unit.test(sizeStr))
  168. ?.transform(size)
  169. ?.toFixed(2) ?? 0
  170. );
  171. };
  172. const unique = (arr, key) => {
  173. if (!key) return Array.from(new Set(arr));
  174.  
  175. arr = arr.map(item => {
  176. item[key] = item[key]?.toLowerCase();
  177. return item;
  178. });
  179. return Array.from(new Set(arr.map(e => e[key]))).map(e => arr.find(x => x[key] === e));
  180. };
  181. const notify = msg => {
  182. GM_notification({
  183. highlight: true,
  184. silent: true,
  185. timeout: 2000,
  186. ...msg,
  187. text: msg?.text ?? GM_info.script.name,
  188. image: GM_getResourceURL(msg?.image ?? "info"),
  189. onclick: msg?.clickUrl ? () => GM_openInTab(msg.clickUrl, { setParent: true, active: true }) : () => {},
  190. });
  191. };
  192. const verify = () => {
  193. const h = 667;
  194. const w = 375;
  195. const t = (window.screen.availHeight - h) / 2;
  196. const l = (window.screen.availWidth - w) / 2;
  197.  
  198. window.open(
  199. `https://captchaapi.115.com/?ac=security_code&type=web&cb=Close911_${new Date().getTime()}`,
  200. "验证账号",
  201. `height=${h},width=${w},top=${t},left=${l},toolbar=no,menubar=no,scrollbars=no,resizable=no,location=no,status=no`
  202. );
  203. };
  204. const delay = n => new Promise(r => setTimeout(r, n * 1000));
  205. const codeParse = code => {
  206. const codes = code
  207. .split(/-|_/)
  208. .filter(Boolean)
  209. .map(item => (item.startsWith("0") ? item.slice(1) : item));
  210.  
  211. return {
  212. prefix: codes[0],
  213. regex: new RegExp(codes.join(".{0,4}"), "i"),
  214. };
  215. };
  216.  
  217. // store
  218. class Store {
  219. static init() {
  220. const date = getDate();
  221. const cdKey = "CD";
  222. if (GM_getValue(cdKey, "") === date) return;
  223.  
  224. GM_setValue(cdKey, date);
  225. GM_setValue("DETAILS", {});
  226. GM_setValue("RESOURCE", []);
  227. }
  228. static getDetail(key) {
  229. const details = GM_getValue("DETAILS", {});
  230. return details[key] ?? {};
  231. }
  232. static upDetail(key, val) {
  233. const details = GM_getValue("DETAILS", {});
  234. details[key] = { ...this.getDetail(key), ...val };
  235. GM_setValue("DETAILS", details);
  236. }
  237. }
  238.  
  239. // apis
  240. class Apis {
  241. // movie
  242. static async movieImg(code) {
  243. code = code.toUpperCase();
  244.  
  245. const [blogJav, javStore] = await Promise.all([
  246. request(`https://www.google.com/search?q=${code} site:blogjav.net`),
  247. request(`https://javstore.net/search/${code}.html`),
  248. ]);
  249.  
  250. const [bjRes, jsRes] = await Promise.all([
  251. request(
  252. `http://webcache.googleusercontent.com/search?q=cache:${
  253. blogJav?.querySelector("#rso .g .yuRUbf a")?.href
  254. }`
  255. ),
  256. request(javStore?.querySelector("#content_news li a")?.href),
  257. ]);
  258.  
  259. let bjImg = "";
  260. if (bjRes) {
  261. bjImg = bjRes
  262. ?.querySelector("#page .entry-content a img")
  263. ?.getAttribute("data-lazy-src")
  264. .replace("//t", "//img")
  265. .replace("thumbs", "images");
  266. }
  267.  
  268. return bjImg || jsRes?.querySelector(".news a img[alt*='.th']").src.replace(".th", "") || "";
  269. }
  270. static async movieVideo(code, studio) {
  271. code = code.toLowerCase();
  272.  
  273. if (studio) {
  274. const matchList = [
  275. {
  276. name: "東京熱",
  277. match: "https://my.cdn.tokyo-hot.com/media/samples/%s.mp4",
  278. },
  279. {
  280. name: "カリビアンコム",
  281. match: "https://smovie.caribbeancom.com/sample/movies/%s/1080p.mp4",
  282. },
  283. {
  284. name: "一本道",
  285. match: "http://smovie.1pondo.tv/sample/movies/%s/1080p.mp4",
  286. },
  287. {
  288. name: "HEYZO",
  289. trans: code => code.replace(/HEYZO\-/gi, ""),
  290. match: "https://www.heyzo.com/contents/3000/%s/heyzo_hd_%s_sample.mp4",
  291. },
  292. ];
  293. const matched = matchList.find(({ name }) => name === studio);
  294. if (matched) return matched.match.replace(/%s/g, matched.trans ? matched.trans(code) : code);
  295. }
  296.  
  297. let [r18, xrmoo] = await Promise.all([
  298. request(`https://www.r18.com/common/search/order=match/searchword=${code}/`),
  299. request(`http://dmm.xrmoo.com/sindex.php?searchstr=${code}`),
  300. ]);
  301.  
  302. r18 = r18?.querySelector("a.js-view-sample");
  303.  
  304. return (
  305. r18?.getAttribute("data-video-high") ||
  306. r18?.getAttribute("data-video-med") ||
  307. r18?.getAttribute("data-video-low") ||
  308. xrmoo
  309. ?.querySelector(".card .card-footer a.viewVideo")
  310. ?.getAttribute("data-link")
  311. .replace("_sm_w", "_dmb_w") ||
  312. ""
  313. );
  314. }
  315. static async moviePlayer(code) {
  316. code = code.toUpperCase();
  317. const { regex } = codeParse(code);
  318.  
  319. const matchList = [
  320. {
  321. site: "Netflav",
  322. host: "https://netflav.com/",
  323. search: "search?type=title&keyword=%s",
  324. selectors: ".grid_root .grid_cell",
  325. filter: {
  326. name: e => e?.querySelector(".grid_title").textContent,
  327. },
  328. },
  329. {
  330. site: "Avgle",
  331. host: "https://avgle.com/",
  332. search: "search/videos?search_query=%s&search_type=videos",
  333. selectors: ".row .well.well-sm",
  334. filter: {
  335. name: e => e?.querySelector(".video-title")?.textContent,
  336. },
  337. },
  338. ];
  339.  
  340. const matched = await Promise.all(
  341. matchList.map(({ host, search }) => request(`${host}${search.replace(/%s/g, code)}`))
  342. );
  343.  
  344. const players = [];
  345. for (let index = 0; index < matchList.length; index++) {
  346. let node = matched[index];
  347. if (!node) continue;
  348.  
  349. const { selectors, site, filter, host } = matchList[index];
  350. node = node?.querySelectorAll(selectors);
  351. if (!node?.length) continue;
  352.  
  353. for (const item of node) {
  354. const player = { from: site };
  355. Object.keys(filter).forEach(key => {
  356. player[key] = filter[key](item) ?? "";
  357. });
  358.  
  359. const { name } = player;
  360. let link = item?.querySelector("a")?.getAttribute("href");
  361. if (!name || !regex.test(name) || !link) continue;
  362.  
  363. player.link = !link.includes("//") ? `${host}${link.replace(/^\//, "")}` : link;
  364. if (!("zh" in player)) player.zh = /中文/g.test(name);
  365.  
  366. if (player.zh) {
  367. players.unshift(player);
  368. break;
  369. }
  370. players.push(player);
  371. }
  372.  
  373. if (players.find(item => item.zh)) break;
  374. }
  375.  
  376. return players.length ? players[0].link : "";
  377. }
  378. static async movieTitle(sentence) {
  379. const st = encodeURIComponent(sentence.trim());
  380. const data = {
  381. async: `translate,sl:auto,tl:zh-CN,st:${st},id:1650701080679,qc:true,ac:false,_id:tw-async-translate,_pms:s,_fmt:pc`,
  382. };
  383.  
  384. const res = await request(
  385. "https://www.google.com/async/translate?vet=12ahUKEwixq63V3Kn3AhUCJUQIHdMJDpkQqDh6BAgCECw..i&ei=CbNjYvGCPYLKkPIP05O4yAk&yv=3",
  386. data,
  387. "POST",
  388. { responseType: "" }
  389. );
  390.  
  391. return res?.querySelector("#tw-answ-target-text")?.textContent ?? "";
  392. }
  393. static async movieStar(code) {
  394. code = code.toUpperCase();
  395. const site = "https://javdb.com";
  396.  
  397. let res = await request(`${site}/search?q=${code}&sb=0`);
  398. const href = res?.querySelector("#videos .grid-item a").getAttribute("href");
  399. if (!href) return;
  400.  
  401. res = await request(`${site}${href}`);
  402. res = res?.querySelectorAll(".movie-panel-info > .panel-block");
  403. if (!res?.length) return;
  404.  
  405. res = res[res.length - 2]?.querySelector(".value").textContent.trim();
  406. return res
  407. .split(/\n/)
  408. .filter(item => item.indexOf("♀") !== -1)
  409. .map(item => item.replace("♀", "").trim());
  410. }
  411. static async movieMagnet(code) {
  412. code = code.toUpperCase();
  413.  
  414. const matchList = [
  415. {
  416. site: "Sukebei",
  417. host: "https://sukebei.nyaa.si/",
  418. search: "?f=0&c=0_0&q=%s",
  419. selectors: ".table-responsive table tbody tr",
  420. filter: {
  421. name: e => e?.querySelector("td:nth-child(2) a").textContent,
  422. link: e => e?.querySelector("td:nth-child(3) a:last-child").href,
  423. size: e => e?.querySelector("td:nth-child(4)").textContent,
  424. date: e => e?.querySelector("td:nth-child(5)").textContent.split(" ")[0],
  425. href: e => e?.querySelector("td:nth-child(2) a").getAttribute("href"),
  426. },
  427. },
  428. {
  429. site: "BTSOW",
  430. host: "https://btsow.rest/",
  431. search: "search/%s",
  432. selectors: ".data-list .row:not(.hidden-xs)",
  433. filter: {
  434. name: e => e?.querySelector(".file").textContent,
  435. link: e => `magnet:?xt=urn:btih:${e?.querySelector("a").href.split("/").pop()}`,
  436. size: e => e?.querySelector(".size").textContent,
  437. date: e => e?.querySelector(".date").textContent,
  438. href: e => e?.querySelector("a").getAttribute("href"),
  439. },
  440. },
  441. ];
  442.  
  443. const matched = await Promise.all(
  444. matchList.map(({ host, search }) => request(`${host}${search.replace(/%s/g, code)}`))
  445. );
  446.  
  447. const magnets = [];
  448. for (let index = 0; index < matchList.length; index++) {
  449. let node = matched[index];
  450. if (!node) continue;
  451.  
  452. const { selectors, site, filter, host } = matchList[index];
  453. node = node?.querySelectorAll(selectors);
  454. if (!node?.length) continue;
  455.  
  456. for (const item of node) {
  457. const magnet = { from: site };
  458. Object.keys(filter).forEach(key => {
  459. magnet[key] = filter[key](item)?.trim();
  460. });
  461.  
  462. magnet.bytes = transToBytes(magnet.size);
  463. magnet.zh = /中文/g.test(magnet.name);
  464. magnet.link = magnet.link.split("&")[0];
  465. const { href } = magnet;
  466. if (href && !href.includes("//")) magnet.href = `${host}${href.replace(/^\//, "")}`;
  467.  
  468. magnets.push(magnet);
  469. }
  470. }
  471.  
  472. return magnets;
  473. }
  474. // drive
  475. static async searchVideo(params = { search_value: "" } | "") {
  476. if (typeof params === "string") params = { search_value: params };
  477. if (!params.search_value.trim()) return [];
  478.  
  479. const res = await request(
  480. "https://webapi.115.com/files/search",
  481. {
  482. offset: 0,
  483. limit: 10000,
  484. date: "",
  485. aid: 1,
  486. cid: 0,
  487. pick_code: "",
  488. type: 4,
  489. source: "",
  490. format: "json",
  491. o: "user_ptime",
  492. asc: 0,
  493. star: "",
  494. suffix: "",
  495. ...params,
  496. },
  497. "GET",
  498. { responseType: "json" }
  499. );
  500.  
  501. return (res.data ?? []).map(({ cid, fid, n, pc, t }) => {
  502. return { cid, fid, n, pc, t };
  503. });
  504. }
  505. static async getFile(params = {}) {
  506. const res = await request(
  507. "https://webapi.115.com/files",
  508. {
  509. aid: 1,
  510. cid: 0,
  511. o: "user_ptime",
  512. asc: 0,
  513. offset: 0,
  514. show_dir: 1,
  515. limit: 115,
  516. code: "",
  517. scid: "",
  518. snap: 0,
  519. natsort: 1,
  520. record_open_time: 1,
  521. source: "",
  522. format: "json",
  523. ...params,
  524. },
  525. "GET",
  526. { responseType: "json" }
  527. );
  528.  
  529. return res?.data ?? [];
  530. }
  531. static async getSign() {
  532. const { state, sign, time } = await request(
  533. "http://115.com/",
  534. { ct: "offline", ac: "space", _: new Date().getTime() },
  535. "GET",
  536. { responseType: "json" }
  537. );
  538.  
  539. if (state) return { sign, time };
  540.  
  541. notify({
  542. title: "请求失败,115未登录",
  543. text: "点击跳转登录",
  544. image: "error",
  545. clickUrl: "http://115.com/?mode=login",
  546. timeout: 3000,
  547. });
  548. }
  549. static async addTaskUrl({ url, wp_path_id = "", sign, time }) {
  550. const _sign = sign && time ? { sign, time } : await this.getSign();
  551. if (!_sign) return;
  552.  
  553. return await request(
  554. "https://115.com/web/lixian/?ct=lixian&ac=add_task_url",
  555. { url, wp_path_id, ..._sign },
  556. "POST"
  557. );
  558. }
  559. static getVideo(cid = "") {
  560. return this.getFile({ cid, custom_order: 0, star: "", suffix: "", type: 4 });
  561. }
  562. static driveRename(res) {
  563. const data = {};
  564. for (const { fid, file_name } of res) data[`files_new_name[${fid}]`] = file_name;
  565.  
  566. return request("https://webapi.115.com/files/batch_rename", data, "POST");
  567. }
  568. }
  569.  
  570. // common
  571. class Common {
  572. menus = {
  573. tabs: [
  574. { title: "全站", key: "global", prefix: "G" },
  575. { title: "列表页", key: "list", prefix: "L" },
  576. { title: "详情页", key: "movie", prefix: "M" },
  577. { title: "115 相关", key: "drive", prefix: "D" },
  578. ],
  579. commands: [
  580. "G_DARK",
  581. "G_SEARCH",
  582. "G_CLICK",
  583. "L_MIT",
  584. "L_MTH",
  585. "L_MTL",
  586. "L_SCROLL",
  587. "M_IMG",
  588. "M_VIDEO",
  589. "M_PLAYER",
  590. "M_TITLE",
  591. "M_STAR",
  592. "M_SUB",
  593. "M_SORT",
  594. "M_MAGNET",
  595. "D_MATCH",
  596. "D_CID",
  597. "D_VERIFY",
  598. "D_RENAME",
  599. ],
  600. details: [
  601. {
  602. name: "暗黑模式",
  603. key: "G_DARK",
  604. type: "switch",
  605. info: "常用页面暗黑模式",
  606. defaultVal: true,
  607. hotkey: "d",
  608. },
  609. {
  610. name: "快捷搜索",
  611. key: "G_SEARCH",
  612. type: "switch",
  613. info: "<kbd>/</kbd> 获取焦点,<kbd>ctrl</kbd> + <kbd>/</kbd> 快速搜索粘贴板第一项",
  614. defaultVal: true,
  615. hotkey: "k",
  616. },
  617. {
  618. name: "点击事件",
  619. key: "G_CLICK",
  620. type: "switch",
  621. info: "影片/演员卡片以新窗口打开,左击前台,右击后台",
  622. defaultVal: true,
  623. hotkey: "c",
  624. },
  625. {
  626. name: "预览图替换",
  627. key: "L_MIT",
  628. type: "switch",
  629. info: "替换为封面大图",
  630. defaultVal: true,
  631. },
  632. {
  633. name: "标题等高",
  634. key: "L_MTH",
  635. type: "switch",
  636. info: "影片标题强制等高",
  637. defaultVal: false,
  638. },
  639. {
  640. name: "标题最大行",
  641. key: "L_MTL",
  642. type: "number",
  643. info: "影片标题最大显示行数,超出省略。0 不限制 (等高模式下最小有效值 1)",
  644. placeholder: "仅支持整数 ≥ 0",
  645. defaultVal: 1,
  646. },
  647. {
  648. name: "滚动加载",
  649. key: "L_SCROLL",
  650. type: "switch",
  651. info: "滚动自动加载下一页",
  652. defaultVal: true,
  653. },
  654. {
  655. name: "预览大图",
  656. key: "M_IMG",
  657. type: "switch",
  658. info: `获取自 <a href="https://blogjav.net/" class="link-primary">BlogJav</a>, <a href="https://javstore.net/" class="link-primary">JavStore</a>`,
  659. defaultVal: true,
  660. },
  661. {
  662. name: "预览视频",
  663. key: "M_VIDEO",
  664. type: "switch",
  665. info: `获取自 <a href="https://www.r18.com/" class="link-primary">R18</a>, <a href="http://dmm.xrmoo.com/" class="link-primary">闲人吧</a>`,
  666. defaultVal: true,
  667. },
  668. {
  669. name: "在线播放",
  670. key: "M_PLAYER",
  671. type: "switch",
  672. info: `获取自 <a href="https://netflav.com/" class="link-primary">Netflav</a>, <a href="https://avgle.com/" class="link-primary">Avgle</a>`,
  673. defaultVal: true,
  674. },
  675. {
  676. name: "标题机翻",
  677. key: "M_TITLE",
  678. type: "switch",
  679. info: `翻自 <a href="https://google.com/" class="link-primary">Google</a>`,
  680. defaultVal: true,
  681. },
  682. {
  683. name: "演员匹配",
  684. key: "M_STAR",
  685. type: "switch",
  686. info: `如无,获取自 <a href="https://javdb.com/" class="link-primary">JavDB</a>`,
  687. defaultVal: true,
  688. },
  689. {
  690. name: "字幕筛选",
  691. key: "M_SUB",
  692. type: "switch",
  693. info: "额外针对名称为 <code>大写字母</code> + <code>-C</code> 资源判断",
  694. defaultVal: false,
  695. },
  696. {
  697. name: "磁力排序",
  698. key: "M_SORT",
  699. type: "switch",
  700. info: "综合排序 <code>字幕</code> > <code>大小</code> > <code>日期</code>",
  701. defaultVal: true,
  702. },
  703. {
  704. name: "磁力搜索",
  705. key: "M_MAGNET",
  706. type: "switch",
  707. info: `自动去重,获取自 <a href="https://sukebei.nyaa.si/" class="link-primary">Sukebei</a>, <a href="https://btsow.rest/" class="link-primary">BTSOW</a>`,
  708. defaultVal: true,
  709. },
  710. {
  711. name: "网盘资源",
  712. key: "D_MATCH",
  713. type: "switch",
  714. info: "资源匹配 & 离线开关 (<strong>确保已登录网盘</strong>)",
  715. defaultVal: true,
  716. },
  717. {
  718. name: "下载目录",
  719. key: "D_CID",
  720. type: "input",
  721. info: "离线下载自定目录 <strong>cid</strong> 或 <strong>动态参数</strong>,建议 <strong>cid</strong> 效率更高<br><strong>动态参数</strong> 支持网盘根目录下文件夹名称<br>默认动态参数 <code>${云下载}</code>",
  722. placeholder: "cid 或 动态参数",
  723. defaultVal: "${云下载}",
  724. },
  725. {
  726. name: "文件验证",
  727. key: "D_VERIFY",
  728. type: "number",
  729. info: "『<strong>一键离线</strong>』可用,查询以验证离线下载结果,每次间隔一秒<br>设置次数上限,次数越多验证越精准<br>建议 3 ~ 5,默认 3",
  730. placeholder: "仅支持整数 ≥ 0",
  731. defaultVal: 3,
  732. },
  733. {
  734. name: "文件重命名",
  735. key: "D_RENAME",
  736. type: "input",
  737. info: '需要『<strong>文件验证</strong>』&『<strong>一键离线</strong>』可用,支持动态参数如下<br><code>${字幕}</code> "【中文字幕】",非字幕资源则为空<br><code>${番号}</code> 页面番号,字母自动转大写;番号为必须值,如新命名未包含将自动追加前缀<br><code>${标题}</code> 页面标题,标题可能已包含番号,自行判断',
  738. placeholder: "勿填写后缀,可能导致资源不可用",
  739. defaultVal: "${字幕}${番号} - ${标题}",
  740. },
  741. ],
  742. };
  743. route = null;
  744. pcUrl = "https://v.anxia.com/?pickcode=";
  745.  
  746. init() {
  747. Store.init();
  748. this.route = Object.keys(this.routes).find(key => this.routes[key].test(location.pathname));
  749. this.createMenu();
  750. return { ...this, ...this[this.route] };
  751. }
  752. createMenu() {
  753. GM_addStyle(`
  754. .x-scrollbar-hide ::-webkit-scrollbar {
  755. display: none;
  756. }
  757. .x-mask {
  758. display: none;
  759. position: fixed;
  760. z-index: 9999;
  761. width: 100vw;
  762. height: 100vh;
  763. top: 0;
  764. left: 0;
  765. border: none;
  766. background: transparent;
  767. backdrop-filter: blur(50px);
  768. padding: 0;
  769. margin: 0;
  770. box-sizing: border-box;
  771. }
  772. iframe.x-mask {
  773. backdrop-filter: none;
  774. }
  775. .x-show {
  776. display: block !important;
  777. }
  778. `);
  779.  
  780. let { tabs, commands, details } = this.menus;
  781.  
  782. const exclude = this.excludeMenu;
  783. if (exclude?.length) {
  784. const regex = new RegExp(`^(?!${exclude.join("|")})`);
  785. commands = commands.filter(command => regex.test(command));
  786. tabs = tabs.filter(tab => commands.find(command => command.startsWith(tab.prefix)));
  787. }
  788. if (!commands.length) return;
  789.  
  790. const domain = Matched.domain;
  791. const active = tabs.find(({ key }) => key === this.route) ?? tabs[0];
  792.  
  793. let tabStr = "";
  794. let panelStr = "";
  795. for (let index = 0; index < tabs.length; index++) {
  796. const { title, key, prefix } = tabs[index];
  797.  
  798. const curCommands = commands.filter(command => command.startsWith(prefix));
  799. const curLen = curCommands.length;
  800. if (!curLen) continue;
  801.  
  802. const isActive = key === active.key;
  803. tabStr += `
  804. <a
  805. class="nav-link${isActive ? " active" : ""}"
  806. id="${key}-tab"
  807. aria-controls="${key}"
  808. aria-selected="${isActive}"
  809. data-bs-toggle="pill"
  810. href="#${key}"
  811. role="tab"
  812. >
  813. ${title}设置
  814. </a>`;
  815. panelStr += `
  816. <div
  817. class="tab-pane fade${isActive ? " show active" : ""}"
  818. id="${key}"
  819. aria-labelledby="${key}-tab"
  820. role="tabpanel"
  821. >
  822. `;
  823.  
  824. for (let curIdx = 0; curIdx < curLen; curIdx++) {
  825. const {
  826. key: curKey,
  827. defaultVal,
  828. name,
  829. type,
  830. hotkey = "",
  831. placeholder = "",
  832. info,
  833. } = details.find(item => item.key === curCommands[curIdx]);
  834.  
  835. const uniKey = `${domain}_${curKey}`;
  836. const val = GM_getValue(uniKey, defaultVal);
  837. this[curKey] = val;
  838.  
  839. panelStr += `<div${curIdx + 1 === curLen ? "" : ` class="mb-3"`}>`;
  840.  
  841. if (type === "switch") {
  842. if (curKey.startsWith("G")) {
  843. GM_registerMenuCommand(
  844. `${val ? "关闭" : "开启"}${name}`,
  845. () => {
  846. GM_setValue(uniKey, !val);
  847. location.reload();
  848. },
  849. hotkey
  850. );
  851. }
  852. panelStr += `
  853. <div class="form-check form-switch">
  854. <input
  855. type="checkbox"
  856. class="form-check-input"
  857. id="${curKey}"
  858. aria-describedby="${curKey}_Help"
  859. ${val ? "checked" : ""}
  860. name="${curKey}"
  861. >
  862. <label class="form-check-label" for="${curKey}">${name}</label>
  863. </div>
  864. `;
  865. } else {
  866. panelStr += `
  867. <label class="form-label" for="${curKey}">${name}</label>
  868. <input
  869. type="${type}"
  870. class="form-control"
  871. id="${curKey}"
  872. aria-describedby="${curKey}_Help"
  873. value="${val ?? ""}"
  874. placeholder="${placeholder}"
  875. name="${curKey}"
  876. >
  877. `;
  878. }
  879.  
  880. if (info) panelStr += `<div id="${curKey}_Help" class="form-text">${info}</div>`;
  881. panelStr += `</div>`;
  882. }
  883.  
  884. panelStr += `</div>`;
  885. }
  886.  
  887. if (!tabStr || !panelStr) return;
  888. DOC.addEventListener("DOMContentLoaded", () => {
  889. DOC.body.insertAdjacentHTML(
  890. "beforeend",
  891. `<iframe
  892. class="x-mask"
  893. id="control-panel"
  894. name="control-panel"
  895. src="about:blank"
  896. title="控制面板"
  897. ></iframe>`
  898. );
  899. const iframe = DOC.querySelector("iframe#control-panel");
  900. const _DOC = iframe.contentWindow.document;
  901.  
  902. _DOC.querySelector("head").insertAdjacentHTML(
  903. "beforeend",
  904. `<link
  905. href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"
  906. rel="stylesheet"
  907. >
  908. <style>${this.style}</style>
  909. <base target="_blank">`
  910. );
  911. const body = _DOC.querySelector("body");
  912. body.classList.add("bg-transparent");
  913. GM_addElement(body, "script", {
  914. src: "https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js",
  915. });
  916. body.insertAdjacentHTML(
  917. "afterbegin",
  918. `
  919. <button
  920. type="button"
  921. class="d-none"
  922. id="openModal"
  923. class="btn btn-primary"
  924. data-bs-toggle="modal"
  925. data-bs-target="#controlPanel"
  926. >
  927. open
  928. </button>
  929. <div
  930. class="modal fade"
  931. id="controlPanel"
  932. tabindex="-1"
  933. aria-labelledby="controlPanelLabel"
  934. aria-hidden="true"
  935. >
  936. <div class="modal-dialog modal-lg modal-fullscreen-lg-down modal-dialog-scrollable">
  937. <div class="modal-content">
  938. <div class="modal-header">
  939. <h5 class="modal-title" id="controlPanelLabel">
  940. 控制面板
  941. -
  942. <a
  943. href="https://sleazyfork.org/zh-CN/scripts/435360-javscript"
  944. class="link-secondary text-decoration-none"
  945. target="_blank"
  946. >
  947. ${GM_info.script.name} v${GM_info.script.version}
  948. </a>
  949. </h5>
  950. <button
  951. type="button"
  952. class="btn-close"
  953. data-bs-dismiss="modal"
  954. aria-label="Close"
  955. >
  956. </button>
  957. </div>
  958. <div class="modal-body">
  959. <form class="mb-0">
  960. <div class="d-flex align-items-start">
  961. <div
  962. class="nav flex-column nav-pills me-3 sticky-top"
  963. id="v-pills-tab"
  964. role="tablist"
  965. aria-orientation="vertical"
  966. >
  967. ${tabStr}
  968. </div>
  969. <div class="tab-content flex-fill" id="v-pills-tabContent">
  970. ${panelStr}
  971. </div>
  972. </div>
  973. </form>
  974. </div>
  975. <div class="modal-footer">
  976. <button
  977. type="button"
  978. class="btn btn-danger"
  979. data-bs-dismiss="modal"
  980. data-action="restart"
  981. >
  982. 重置脚本
  983. </button>
  984. <button
  985. type="button"
  986. class="btn btn-secondary"
  987. data-bs-dismiss="modal"
  988. data-action="reset"
  989. >
  990. 恢复所有默认
  991. </button>
  992. <button
  993. type="button"
  994. class="btn btn-primary"
  995. data-bs-dismiss="modal"
  996. data-action="save"
  997. >
  998. 保存设置
  999. </button>
  1000. </div>
  1001. </div>
  1002. </div>
  1003. </div>
  1004. `
  1005. );
  1006. body.querySelector(".modal-footer").addEventListener("click", e => {
  1007. const { action } = e.target.dataset;
  1008. if (!action) return;
  1009.  
  1010. e.preventDefault();
  1011. e.stopPropagation();
  1012.  
  1013. if (action === "save") {
  1014. const data = Object.fromEntries(new FormData(body.querySelector("form")).entries());
  1015. commands.forEach(key => GM_setValue(`${domain}_${key}`, data[key] ?? ""));
  1016. }
  1017. if (action === "reset") {
  1018. GM_listValues().forEach(name => name.startsWith(domain) && GM_deleteValue(name));
  1019. }
  1020. if (action === "restart") {
  1021. GM_listValues().forEach(name => GM_deleteValue(name));
  1022. }
  1023.  
  1024. location.reload();
  1025. });
  1026.  
  1027. const toggleIframe = () => {
  1028. DOC.body.parentNode.classList.toggle("x-scrollbar-hide");
  1029. iframe.classList.toggle("x-show");
  1030. };
  1031. const openModal = () => {
  1032. if (iframe.classList.contains("x-show")) return;
  1033. toggleIframe();
  1034. _DOC.querySelector("#openModal").click();
  1035. };
  1036. GM_registerMenuCommand("控制面板", openModal, "s");
  1037. _DOC.querySelector("#controlPanel").addEventListener("hidden.bs.modal", toggleIframe);
  1038. });
  1039. }
  1040.  
  1041. // styles
  1042. variables = `
  1043. :root {
  1044. --x-bgc: #121212;
  1045. --x-sub-bgc: #202020;
  1046. --x-ftc: #fffffff2;
  1047. --x-sub-ftc: #aaaaaa;
  1048. --x-grey: #313131;
  1049. --x-blue: #0a84ff;
  1050. --x-orange: #ff9f0a;
  1051. --x-green: #30d158;
  1052. --x-red: #ff453a;
  1053.  
  1054. --x-line-h: 22px;
  1055.  
  1056. --x-thumb-w: 190px;
  1057. --x-cover-w: 295px;
  1058.  
  1059. --x-thumb-ratio: 334 / 473;
  1060. --x-cover-ratio: 135 / 91;
  1061. --x-avatar-ratio: 1;
  1062. --x-sprite-ratio: 4 / 3;
  1063.  
  1064. --x-shadow: 0 1px 3px rgb(0 0 0 / 30%);
  1065. }
  1066. `;
  1067. style = `
  1068. ::-webkit-scrollbar {
  1069. width: 8px !important;
  1070. height: 8px !important;
  1071. }
  1072. ::-webkit-scrollbar-thumb {
  1073. border-radius: 4px !important;
  1074. background: #c1c1c1;
  1075. }
  1076. * {
  1077. outline: none !important;
  1078. text-shadow: none !important;
  1079. text-decoration: none !important;
  1080. }
  1081. body {
  1082. overflow-y: overlay;
  1083. }
  1084. footer {
  1085. display: none !important;
  1086. }
  1087. `;
  1088. dmStyle = `
  1089. ::-webkit-scrollbar-thumb, button {
  1090. background: var(--x-grey) !important;
  1091. }
  1092. * {
  1093. box-shadow: none !important;
  1094. }
  1095. *:not(span[class]) {
  1096. border-color: var(--x-grey) !important;
  1097. }
  1098. html, body, input, textarea {
  1099. background: var(--x-bgc) !important;
  1100. }
  1101. body, *::placeholder {
  1102. color: var(--x-sub-ftc) !important;
  1103. }
  1104. nav {
  1105. background: var(--x-sub-bgc) !important;
  1106. }
  1107. a, button, h1, h2, h3, h4, h5, h6, input, p, textarea {
  1108. color: var(--x-ftc) !important;
  1109. }
  1110. img {
  1111. filter: brightness(.9) contrast(.9) !important;
  1112. }
  1113. `;
  1114. customStyle = `
  1115. #x-status {
  1116. margin-bottom: 20px;
  1117. color: var(--x-sub-ftc);
  1118. text-align: center;
  1119. font-size: 14px !important;
  1120. }
  1121. .x-in {
  1122. transition: opacity .25s linear;
  1123. opacity: 1 !important;
  1124. }
  1125. .x-out {
  1126. transition: opacity .25s linear;
  1127. opacity: 0 !important;
  1128. }
  1129. .x-cover {
  1130. width: var(--x-cover-w) !important;
  1131. }
  1132. .x-cover > *:first-child {
  1133. aspect-ratio: var(--x-cover-ratio) !important;
  1134. }
  1135. .x-ellipsis {
  1136. overflow: hidden;
  1137. text-overflow: ellipsis;
  1138. display: -webkit-box !important;
  1139. -webkit-line-clamp: 1;
  1140. -webkit-box-orient: vertical;
  1141. white-space: unset !important;
  1142. }
  1143. .x-line {
  1144. overflow: hidden;
  1145. text-overflow: ellipsis;
  1146. white-space: nowrap;
  1147. }
  1148. .x-title {
  1149. line-height: var(--x-line-h) !important;
  1150. }
  1151. .x-matched {
  1152. font-weight: bold;
  1153. color: var(--x-blue) !important;
  1154. }
  1155. .x-player {
  1156. position: relative;
  1157. overflow: hidden;
  1158. display: block;
  1159. cursor: pointer;
  1160. }
  1161. .x-player::after {
  1162. content: "";
  1163. position: absolute;
  1164. width: 100%;
  1165. height: 100%;
  1166. top: 0;
  1167. left: 0;
  1168. transition: all .25s ease-out;
  1169. background-color: rgba(0, 0, 0, .2);
  1170. background-position: center;
  1171. background-repeat: no-repeat;
  1172. opacity: .85;
  1173. background-image: url(${GM_getResourceURL("play")});
  1174. background-size: 40px;
  1175. }
  1176. .x-player:hover::after {
  1177. background-color: rgba(0, 0, 0, 0);
  1178. }
  1179. .x-player img {
  1180. filter: none !important;
  1181. }
  1182. `;
  1183.  
  1184. // G_DARK
  1185. globalDark = (css = "", dark = "") => {
  1186. if (this.G_DARK) css += dark;
  1187. if (css) GM_addStyle(css.includes("var(--x-") ? `${this.variables}${css}` : css);
  1188. };
  1189. // G_SEARCH
  1190. globalSearch = (selectors, pathname) => {
  1191. if (!this.G_SEARCH) return;
  1192.  
  1193. DOC.addEventListener("keydown", async e => {
  1194. if (e.ctrlKey && e.keyCode === 191) {
  1195. const text = (await navigator.clipboard.readText()).trim();
  1196. if (!text) return;
  1197.  
  1198. GM_openInTab(`${location.origin}${pathname.replace("%s", text)}`, {
  1199. setParent: true,
  1200. active: true,
  1201. });
  1202. }
  1203. });
  1204.  
  1205. DOC.addEventListener("keyup", e => {
  1206. if (e.keyCode === 191 && !["INPUT", "TEXTAREA"].includes(DOC.activeElement.nodeName)) {
  1207. DOC.querySelector(selectors).focus();
  1208. }
  1209. });
  1210. };
  1211. // G_CLICK
  1212. globalClick = (selectors, node = DOC) => {
  1213. node.addEventListener("click", e => {
  1214. const { target } = e;
  1215.  
  1216. let url = "";
  1217. if (target.classList.contains("x-player")) {
  1218. url = `${this.pcUrl}${target.dataset.code}`;
  1219. } else if (this.G_CLICK) {
  1220. const target = getTarget(e);
  1221. if (target) url = target.href;
  1222. }
  1223. if (!url) return;
  1224.  
  1225. e.preventDefault();
  1226. e.stopPropagation();
  1227. GM_openInTab(url, { setParent: true, active: true });
  1228. });
  1229.  
  1230. const getTarget = e => {
  1231. const item = e.target.closest(selectors);
  1232. return !item?.href || !node.contains(item) ? false : item;
  1233. };
  1234.  
  1235. if (!this.G_CLICK) return;
  1236.  
  1237. let _event;
  1238.  
  1239. node.addEventListener("mousedown", e => {
  1240. if (e.button !== 2) return;
  1241.  
  1242. const target = getTarget(e);
  1243. if (!target) return;
  1244.  
  1245. e.preventDefault();
  1246.  
  1247. target.oncontextmenu = e => e.preventDefault();
  1248. _event = e;
  1249. });
  1250.  
  1251. node.addEventListener("mouseup", e => {
  1252. if (e.button !== 2) return;
  1253.  
  1254. const target = getTarget(e);
  1255. if (!target || !_event) return;
  1256.  
  1257. e.preventDefault();
  1258.  
  1259. const { clientX, clientY } = e;
  1260. const { clientX: _clientX, clientY: _clientY } = _event;
  1261. if (Math.abs(clientX - _clientX) + Math.abs(clientY - _clientY) > 5) return;
  1262.  
  1263. GM_openInTab(target.href, { setParent: true, active: false });
  1264. });
  1265. };
  1266.  
  1267. // L_MIT
  1268. listMovieImgType = (node, condition) => {
  1269. const img = node.querySelector("img");
  1270. if (!this.L_MIT || !img) return;
  1271.  
  1272. node.classList.add("x-cover");
  1273. img.loading = "lazy";
  1274. const { src = "" } = img;
  1275. img.src = condition.find(({ regex }) => regex.test(src))?.replace(src);
  1276. };
  1277. // L_MTL, L_MTH
  1278. listMovieTitle = () => {
  1279. let num = parseInt(this.L_MTL ?? 0, 10);
  1280. if (this.L_MTH && num < 1) num = 1;
  1281.  
  1282. return `
  1283. .x-title {
  1284. -webkit-line-clamp: ${num <= 0 ? "unset" : num};
  1285. ${this.L_MTH ? `height: calc(var(--x-line-h) * ${num}) !important;` : ""}
  1286. }
  1287. `;
  1288. };
  1289. // L_SCROLL
  1290. listScroll = (container, itemSelector, path) => {
  1291. const items = container.querySelectorAll(itemSelector);
  1292. const msnry = new Masonry(container, {
  1293. itemSelector,
  1294. columnWidth: items[items.length - 2] ?? items[items.length - 1],
  1295. fitWidth: true,
  1296. visibleStyle: { opacity: 1 },
  1297. hiddenStyle: { opacity: 0 },
  1298. });
  1299. container.classList.add("x-in");
  1300.  
  1301. if (!this.L_SCROLL) return;
  1302.  
  1303. let nextURL;
  1304. const updateNextURL = (node = DOC) => {
  1305. nextURL = node.querySelector(path)?.href;
  1306. };
  1307. updateNextURL();
  1308.  
  1309. const infScroll = new InfiniteScroll(container, {
  1310. path: () => nextURL,
  1311. checkLastPage: path,
  1312. outlayer: msnry,
  1313. history: false,
  1314. });
  1315.  
  1316. infScroll?.on("request", async (_, fetchPromise) => {
  1317. const { body } = await fetchPromise.then();
  1318. if (body) updateNextURL(body);
  1319. });
  1320.  
  1321. const status = DOC.create("div", { id: "x-status" });
  1322. container.insertAdjacentElement("afterend", status);
  1323. let textContent = "加载中...";
  1324. const noMore = "没有更多了";
  1325. try {
  1326. const path = infScroll.getPath() ?? "";
  1327. if (!path) textContent = noMore;
  1328. } catch (err) {
  1329. textContent = noMore;
  1330. }
  1331. status.textContent = textContent;
  1332.  
  1333. infScroll?.once("last", () => {
  1334. status.textContent = noMore;
  1335. });
  1336.  
  1337. return infScroll;
  1338. };
  1339.  
  1340. // M_IMG
  1341. movieImg = async ({ code }, start) => {
  1342. if (!this.M_IMG) return;
  1343.  
  1344. start && start();
  1345. let img = Store.getDetail(code)?.img;
  1346. if (!img) {
  1347. img = await Apis.movieImg(code);
  1348. if (img) Store.upDetail(code, { img });
  1349. }
  1350. return img;
  1351. };
  1352. // M_VIDEO
  1353. movieVideo = async ({ code, studio }, start) => {
  1354. if (!this.M_VIDEO) return;
  1355.  
  1356. start && start();
  1357. let video = Store.getDetail(code)?.video;
  1358. if (!video) {
  1359. video = await Apis.movieVideo(code, studio);
  1360. if (video) Store.upDetail(code, { video });
  1361. }
  1362. return video;
  1363. };
  1364. // M_PLAYER
  1365. moviePlayer = async ({ code }, start) => {
  1366. if (!this.M_PLAYER) return;
  1367.  
  1368. start && start();
  1369. let player = Store.getDetail(code)?.player;
  1370. if (!player) {
  1371. player = await Apis.moviePlayer(code);
  1372. if (player) Store.upDetail(code, { player });
  1373. }
  1374. return player;
  1375. };
  1376. // M_TITLE
  1377. movieTitle = async ({ code, title }, start) => {
  1378. if (!this.M_TITLE) return;
  1379.  
  1380. start && start();
  1381. let transTitle = Store.getDetail(code)?.transTitle;
  1382. if (!transTitle) {
  1383. transTitle = await Apis.movieTitle(title);
  1384. if (transTitle) Store.upDetail(code, { transTitle });
  1385. }
  1386. return transTitle;
  1387. };
  1388. // M_STAR
  1389. movieStar = async ({ code, star: hasStar }, start) => {
  1390. if (!this.M_STAR || hasStar) return;
  1391.  
  1392. start && start();
  1393. let star = Store.getDetail(code)?.star;
  1394. if (!star?.length) {
  1395. star = await Apis.movieStar(code);
  1396. if (star?.length) Store.upDetail(code, { star });
  1397. }
  1398. return star;
  1399. };
  1400. // M_SUB
  1401. movieSub = (magnets, start) => {
  1402. if (!this.M_SUB) return magnets;
  1403.  
  1404. start && start();
  1405. const regex = /[A-Z]+-\d+-C/;
  1406. return magnets.map(item => {
  1407. item.zh = item.zh && !regex.test(item.name);
  1408. return item;
  1409. });
  1410. };
  1411. // M_SORT
  1412. movieSort = (magnets, start) => {
  1413. if (!this.M_SORT) return magnets;
  1414.  
  1415. start && start();
  1416. return magnets.length <= 1
  1417. ? magnets
  1418. : magnets.sort((pre, next) => {
  1419. if (pre.zh === next.zh) {
  1420. if (pre.bytes === next.bytes) return next.date - pre.date;
  1421. return next.bytes - pre.bytes;
  1422. } else {
  1423. return pre.zh > next.zh ? -1 : 1;
  1424. }
  1425. });
  1426. };
  1427. // M_MAGNET
  1428. movieMagnet = async ({ code }, start) => {
  1429. if (!this.M_MAGNET) return;
  1430.  
  1431. start && start();
  1432. let magnet = Store.getDetail(code)?.magnet;
  1433. if (!magnet?.length) {
  1434. magnet = unique(await Apis.movieMagnet(code), "link");
  1435. if (magnet?.length) Store.upDetail(code, { magnet });
  1436. }
  1437. return magnet;
  1438. };
  1439.  
  1440. // D_MATCH
  1441. driveMatch = async ({ code, res }, start) => {
  1442. if (!this.D_MATCH) return;
  1443.  
  1444. start && start();
  1445. code = code.toUpperCase();
  1446. const { prefix, regex } = codeParse(code);
  1447.  
  1448. if (res === "list") {
  1449. res = Store.getDetail(code)?.res;
  1450. if (!res?.length) {
  1451. const RESOURCE = GM_getValue("RESOURCE", []);
  1452. let item = RESOURCE.find(item => item.prefix === prefix);
  1453. if (!item) {
  1454. item = { prefix, res: await Apis.searchVideo(prefix) };
  1455. RESOURCE.push(item);
  1456. GM_setValue("RESOURCE", RESOURCE);
  1457. }
  1458. res = await this.driveMatch({ ...item, code });
  1459. }
  1460. } else {
  1461. let _res = res ?? (await Apis.searchVideo(prefix));
  1462. if (_res?.length) {
  1463. _res = _res.filter(({ n }) => regex.test(n));
  1464. }
  1465. if (!res) Store.upDetail(code, { res: _res });
  1466. res = _res;
  1467. }
  1468.  
  1469. return res;
  1470. };
  1471. // D_CID
  1472. driveCid = async () => {
  1473. let cid = this.D_CID;
  1474. if (/^\$\{.+\}$/.test(cid)) {
  1475. cid = cid.replace(/\$|\{|\}/g, "").trim();
  1476. const res = await Apis.getFile();
  1477. cid = res.find(({ n }) => n === cid)?.cid ?? "";
  1478. }
  1479. return cid;
  1480. };
  1481. // D_VERIFY
  1482. driveVerify = async ({ code, cid = "" }) => {
  1483. let verify = this.D_VERIFY <= 0;
  1484.  
  1485. for (let idx = 0; idx < this.D_VERIFY; idx++) {
  1486. await delay(1);
  1487.  
  1488. let res = await Apis.getVideo(cid);
  1489. res = await this.driveMatch({ code, res });
  1490.  
  1491. res = res.filter(({ t }) => t.startsWith(getDate()));
  1492. if (!res.length) continue;
  1493.  
  1494. const fids = (Store.getDetail(code)?.res ?? []).map(({ fid }) => fid);
  1495. res = res.filter(({ fid }) => !fids.includes(fid));
  1496. if (!res.length) continue;
  1497.  
  1498. verify = res;
  1499. break;
  1500. }
  1501.  
  1502. return verify;
  1503. };
  1504. // D_RENAME
  1505. driveRename = ({ cid, res, zh, code, title }) => {
  1506. let file_name = this.D_RENAME?.trim();
  1507. if (!file_name) return;
  1508.  
  1509. code = code.toUpperCase();
  1510. file_name = file_name
  1511. .replace(/\$\{字幕\}/g, zh ? "【中文字幕】" : "")
  1512. .replace(/\$\{番号\}/g, code)
  1513. .replace(/\$\{标题\}/g, title);
  1514.  
  1515. if (!codeParse(code).regex.test(file_name)) file_name = `${code} - ${file_name}`;
  1516.  
  1517. res = res
  1518. .filter(item => item.ico)
  1519. .map(item => {
  1520. item.file_name = `${file_name}.${item.ico}`;
  1521. return item;
  1522. });
  1523.  
  1524. unique(res.map(item => item.cid).filter(item => item !== cid)).forEach(fid => res.push({ fid, file_name }));
  1525.  
  1526. return Apis.driveRename(res);
  1527. };
  1528.  
  1529. driveOffline = async (e, { magnets, code, title }) => {
  1530. const { target } = e;
  1531. const { magnet: type } = target.dataset;
  1532. if (!type) return;
  1533.  
  1534. e.preventDefault();
  1535. e.stopPropagation();
  1536.  
  1537. const { classList } = target;
  1538. if (classList.contains("active")) return;
  1539.  
  1540. classList.add("active");
  1541. const originText = target.textContent;
  1542. target.textContent = "请求中...";
  1543.  
  1544. const wp_path_id = await this.driveCid();
  1545.  
  1546. if (type === "all") {
  1547. const warnMsg = { title: "一键离线任务失败", image: "warn" };
  1548. const successMsg = { title: "一键离线任务成功", image: "success", timeout: 3000 };
  1549.  
  1550. const magnetLen = magnets.length;
  1551.  
  1552. for (let index = 0; index < magnetLen; index++) {
  1553. const sign = await Apis.getSign();
  1554. if (!sign) break;
  1555.  
  1556. const isLast = index + 1 === magnetLen;
  1557. const { link: url, zh } = magnets[index];
  1558.  
  1559. let res = await Apis.addTaskUrl({ url, wp_path_id, ...sign });
  1560. if (!res?.state) {
  1561. if (res.errcode === 911) {
  1562. notify({ title: "一键离线任务中断", text: res.error_msg, image: "warn", highlight: false });
  1563. break;
  1564. }
  1565. if (!isLast) continue;
  1566. notify(warnMsg);
  1567. break;
  1568. }
  1569.  
  1570. const cid = wp_path_id;
  1571.  
  1572. res = await this.driveVerify({ code, cid });
  1573. if (!res) {
  1574. if (!isLast) continue;
  1575. notify(warnMsg);
  1576. break;
  1577. }
  1578.  
  1579. if (res?.length) {
  1580. successMsg.text = "点击跳转目录";
  1581. successMsg.clickUrl = `https://115.com/?cid=${res[0].cid}&offset=0&mode=wangpan`;
  1582. await this.driveRename({ cid, res, zh, code, title });
  1583. }
  1584.  
  1585. notify(successMsg);
  1586. break;
  1587. }
  1588. } else if (type) {
  1589. const url = type;
  1590. const res = await Apis.addTaskUrl({ url, wp_path_id });
  1591. if (res) {
  1592. notify({
  1593. title: `离线任务添加${res.state ? "成功" : "失败"}`,
  1594. text: res.error_msg ?? "",
  1595. image: res.state ? "info" : "warn",
  1596. highlight: false,
  1597. });
  1598. }
  1599. }
  1600.  
  1601. classList.remove("active");
  1602. target.textContent = originText;
  1603. };
  1604. }
  1605.  
  1606. // javbus
  1607. class JavBus extends Common {
  1608. constructor() {
  1609. super();
  1610. return super.init();
  1611. }
  1612.  
  1613. excludeMenu = [];
  1614. routes = {
  1615. list: /^\/((uncensored|uncensored\/)?(page\/\d+)?$)|((uncensored\/)?((search|searchstar|actresses|genre|star|studio|label|series|director|member)+\/)|actresses(\/\d+)?)+/i,
  1616. genre: /^\/(uncensored\/)?genre$/i,
  1617. forum: /^\/forum\//i,
  1618. movie: /^\/[\w]+(-|_)?[\d]*.*$/i,
  1619. };
  1620.  
  1621. // styles
  1622. _style = `
  1623. .ad-box {
  1624. display: none !important;
  1625. }
  1626. `;
  1627. _dmStyle = `
  1628. .nav > li > a:hover,
  1629. .nav > li > a:focus,
  1630. .dropdown-menu > li > a:hover,
  1631. .dropdown-menu > li > a:focus {
  1632. background: var(--x-grey) !important;
  1633. }
  1634. .nav > li.active > a,
  1635. .nav > .open > a,
  1636. .nav > .open > a:hover,
  1637. .nav > .open > a:focus,
  1638. .dropdown-menu {
  1639. background: var(--x-bgc) !important;
  1640. }
  1641. .modal-content, .alert {
  1642. background: var(--x-sub-bgc) !important;
  1643. }
  1644. .btn-primary {
  1645. background: var(--x-blue) !important;
  1646. border-color: var(--x-blue) !important;
  1647. }
  1648. .btn-success {
  1649. background: var(--x-green) !important;
  1650. border-color: var(--x-green) !important;
  1651. }
  1652. .btn-warning {
  1653. background: var(--x-orange) !important;
  1654. border-color: var(--x-orange) !important;
  1655. }
  1656. .btn-danger {
  1657. background: var(--x-red) !important;
  1658. border-color: var(--x-red) !important;
  1659. }
  1660. .btn.disabled, .btn[disabled], fieldset[disabled] .btn {
  1661. opacity: .8 !important;
  1662. }
  1663. `;
  1664. boxStyle = `
  1665. .movie-box,
  1666. .avatar-box,
  1667. .sample-box {
  1668. width: var(--x-thumb-w) !important;
  1669. border: none !important;
  1670. margin: 10px !important;
  1671. }
  1672. .movie-box .photo-frame,
  1673. .avatar-box .photo-frame,
  1674. .sample-box .photo-frame {
  1675. height: auto !important;
  1676. margin: 10px !important;
  1677. border: none !important;
  1678. }
  1679. .movie-box .photo-frame {
  1680. aspect-ratio: var(--x-thumb-ratio);
  1681. }
  1682. .avatar-box .photo-frame {
  1683. aspect-ratio: var(--x-avatar-ratio);
  1684. }
  1685. .sample-box .photo-frame {
  1686. aspect-ratio: var(--x-sprite-ratio);
  1687. }
  1688. .movie-box img,
  1689. .avatar-box img,
  1690. .sample-box img {
  1691. min-width: unset !important;
  1692. min-height: unset !important;
  1693. max-width: none !important;
  1694. max-height: none !important;
  1695. margin: 0 !important;
  1696. width: 100% !important;
  1697. height: 100% !important;
  1698. object-fit: cover !important;
  1699. }
  1700. .movie-box > *,
  1701. .avatar-box > *,
  1702. .sample-box > * {
  1703. background: unset !important;
  1704. }
  1705. .movie-box > *:nth-child(2),
  1706. .avatar-box > *:nth-child(2),
  1707. .sample-box > *:nth-child(2) {
  1708. padding: 0 10px 10px !important;
  1709. border: none !important;
  1710. line-height: var(--x-line-h) !important;
  1711. height: auto !important;
  1712. }
  1713. `;
  1714. dmBoxStyle = `
  1715. .movie-box,
  1716. .avatar-box,
  1717. .sample-box {
  1718. background: var(--x-sub-bgc) !important;
  1719. }
  1720. .movie-box > *:nth-child(2),
  1721. .avatar-box > *:nth-child(2),
  1722. .sample-box > *:nth-child(2) {
  1723. color: unset;
  1724. }
  1725. .movie-box date {
  1726. color: var(--x-sub-ftc) !important;
  1727. }
  1728. `;
  1729.  
  1730. // methods
  1731. _globalSearch = () => {
  1732. this.globalSearch("#search-input", "/search/%s");
  1733. };
  1734. _globalClick = () => {
  1735. this.globalClick([".movie-box", ".avatar-box"]);
  1736. };
  1737. modifyMovieBox = (node = DOC) => {
  1738. const items = node.querySelectorAll(".movie-box");
  1739. for (const item of items) {
  1740. const info = item.querySelector(".photo-info span");
  1741. info.innerHTML = info.innerHTML.replace(/<\/?span.*>|<br>/g, "");
  1742. const title = info.firstChild;
  1743. if (!title) continue;
  1744. const titleText = title.textContent.trim();
  1745. const _title = DOC.create("div", { title: titleText, class: "x-ellipsis x-title" }, titleText);
  1746. info.replaceChild(_title, title);
  1747. }
  1748. };
  1749.  
  1750. // modules
  1751. list = {
  1752. docStart() {
  1753. const style = `
  1754. #waterfall {
  1755. display: none;
  1756. opacity: 0;
  1757. }
  1758. #waterfall .item {
  1759. float: unset !important;
  1760. }
  1761. .search-header {
  1762. padding: 0 !important;
  1763. background: none !important;
  1764. box-shadow: none !important;
  1765. }
  1766. .search-header .nav-tabs {
  1767. display: none !important;
  1768. }
  1769. .alert-common {
  1770. margin: 20px 20px 0 !important;
  1771. }
  1772. .alert-page {
  1773. margin: 20px !important;
  1774. }
  1775. .text-center.hidden-xs {
  1776. display: none;
  1777. line-height: 0;
  1778. }
  1779. ul.pagination {
  1780. margin-bottom: 40px;
  1781. }
  1782. .movie-box .x-title + div {
  1783. height: var(--x-line-h) !important;
  1784. margin: 4px 0;
  1785. }
  1786. .avatar-box .pb10 {
  1787. padding: 0 !important;
  1788. }
  1789. .avatar-box .pb10:not(:last-child) {
  1790. margin-bottom: 4px !important;
  1791. }
  1792. .avatar-box p {
  1793. margin: 0 0 6px !important;
  1794. }
  1795. .mleft {
  1796. display: flex !important;
  1797. align-items: center;
  1798. }
  1799. .mleft .btn-xs {
  1800. margin: 0 6px 0 0 !important;
  1801. }
  1802. `;
  1803. const dmStyle = `
  1804. .pagination > li > a {
  1805. background-color: var(--x-sub-bgc) !important;
  1806. color: var(--x-ftc) !important;
  1807. }
  1808. .pagination > li:not(.active) > a:hover {
  1809. background-color: var(--x-grey) !important;
  1810. }
  1811. .nav-pills > li.active > a {
  1812. background-color: var(--x-blue) !important;
  1813. }
  1814. `;
  1815. this.globalDark(
  1816. `${this.style}${this._style}${this.boxStyle}${this.customStyle}${style}${this.listMovieTitle()}`,
  1817. `${this.dmStyle}${this._dmStyle}${this.dmBoxStyle}${dmStyle}`
  1818. );
  1819. },
  1820. contentLoaded() {
  1821. const nav = DOC.querySelector(".search-header .nav");
  1822. if (nav) nav.classList.replace("nav-tabs", "nav-pills");
  1823.  
  1824. this._globalSearch();
  1825. this._globalClick();
  1826.  
  1827. this.modifyLayout();
  1828. },
  1829. modifyLayout() {
  1830. const waterfall = DOC.querySelector("#waterfall");
  1831. if (!waterfall) return;
  1832.  
  1833. const isStarDetail = /^\/(uncensored\/)?star\/\w+/i.test(location.pathname);
  1834.  
  1835. const _waterfall = waterfall.cloneNode(true);
  1836. _waterfall.removeAttribute("style");
  1837. _waterfall.setAttribute("class", "x-show");
  1838. const items = this.modifyItem(_waterfall);
  1839.  
  1840. const itemsLen = items?.length;
  1841. if (itemsLen) {
  1842. _waterfall.innerHTML = "";
  1843. for (let index = 0; index < itemsLen; index++) {
  1844. if (isStarDetail && !index) continue;
  1845. _waterfall.appendChild(items[index]);
  1846. }
  1847. }
  1848. waterfall.parentElement.replaceChild(_waterfall, waterfall);
  1849.  
  1850. const infScroll = this.listScroll(_waterfall, ".item", "#next");
  1851. if (!infScroll) return DOC.querySelector(".text-center.hidden-xs")?.classList.add("x-show");
  1852.  
  1853. infScroll?.on("request", async (_, fetchPromise) => {
  1854. const { body } = await fetchPromise.then();
  1855. if (!body) return;
  1856.  
  1857. let items = this.modifyItem(body);
  1858. if (isStarDetail) [_, ...items] = items;
  1859. infScroll.appendItems(items);
  1860. infScroll.options.outlayer.appended(items);
  1861. });
  1862. },
  1863. modifyItem(container) {
  1864. const items = container.querySelectorAll(".item");
  1865. for (const item of items) {
  1866. item.removeAttribute("style");
  1867. item.setAttribute("class", "item");
  1868. this._listMovieImgType(item);
  1869. this.modifyAvatarBox(item);
  1870. this.modifyMovieBox(item);
  1871. }
  1872. this._driveMatch(container);
  1873. return items;
  1874. },
  1875. _listMovieImgType(node) {
  1876. const item = node.querySelector(".movie-box");
  1877. if (!item) return;
  1878.  
  1879. const condition = [
  1880. {
  1881. regex: /\/thumb(s)?\//gi,
  1882. replace: val => val.replace(/\/thumb(s)?\//gi, "/cover/").replace(/\.jpg/gi, "_b.jpg"),
  1883. },
  1884. {
  1885. regex: /pics\.dmm\.co\.jp/gi,
  1886. replace: val => val.replace("ps.jpg", "pl.jpg"),
  1887. },
  1888. ];
  1889.  
  1890. this.listMovieImgType(item, condition);
  1891. },
  1892. modifyAvatarBox(node = DOC) {
  1893. const items = node.querySelectorAll(".avatar-box");
  1894. for (const item of items) {
  1895. const span = item.querySelector("span");
  1896. if (span.classList.contains("mleft")) {
  1897. const title = span.firstChild;
  1898. const titleText = title.textContent.trim();
  1899. const _title = DOC.create("div", { title: titleText, class: "x-line" }, titleText);
  1900. title.parentElement.replaceChild(_title, title);
  1901. span.insertAdjacentElement("afterbegin", span.querySelector("button"));
  1902. continue;
  1903. }
  1904. span.classList.add("x-line");
  1905. }
  1906. },
  1907. async _driveMatch(node = DOC) {
  1908. const items = node.querySelectorAll(".movie-box");
  1909. for (const item of items) {
  1910. const code = item.querySelector("date")?.textContent?.trim();
  1911. if (!code) continue;
  1912.  
  1913. const res = await this.driveMatch({ code, res: "list" });
  1914. if (!res?.length) continue;
  1915.  
  1916. const frame = item.querySelector(".photo-frame");
  1917. frame.classList.add("x-player");
  1918. frame.setAttribute("title", "点击播放");
  1919. frame.setAttribute("data-code", res[0].pc);
  1920. item.querySelector(".x-title").classList.add("x-matched");
  1921. }
  1922. },
  1923. };
  1924. genre = {
  1925. docStart() {
  1926. const style = `
  1927. .alert-common {
  1928. margin: 20px 0 0 !important;
  1929. }
  1930. .container-fluid {
  1931. padding: 0 20px !important;
  1932. }
  1933. h4 {
  1934. margin: 20px 0 10px 0 !important;
  1935. }
  1936. .genre-box {
  1937. padding: 20px !important;
  1938. margin: 10px 0 20px 0 !important;
  1939. }
  1940. .genre-box a {
  1941. cursor: pointer !important;
  1942. user-select: none !important;
  1943. text-align: left !important;
  1944. }
  1945. .genre-box input {
  1946. margin: 0 !important;
  1947. vertical-align: middle !important;
  1948. }
  1949. .x-last-box {
  1950. margin-bottom: 70px !important;
  1951. }
  1952. button.btn.btn-danger.btn-block.btn-genre {
  1953. position: fixed !important;
  1954. bottom: 0 !important;
  1955. left: 0 !important;
  1956. margin: 0 !important;
  1957. border: none !important;
  1958. border-radius: 0 !important;
  1959. }
  1960. `;
  1961. const dmStyle = `
  1962. .genre-box {
  1963. background: var(--x-sub-bgc) !important;
  1964. }
  1965. `;
  1966. this.globalDark(`${this.style}${this._style}${style}`, `${this.dmStyle}${this._dmStyle}${dmStyle}`);
  1967. },
  1968. contentLoaded() {
  1969. this._globalSearch();
  1970. if (!DOC.querySelector("button.btn.btn-danger.btn-block.btn-genre")) return;
  1971.  
  1972. const box = DOC.querySelectorAll(".genre-box");
  1973. box[box.length - 1].classList.add("x-last-box");
  1974. DOC.querySelector(".container-fluid.pt10").addEventListener("click", ({ target }) => {
  1975. if (target.nodeName !== "A" || !target.classList.contains("text-center")) return;
  1976. const checkbox = target.querySelector("input");
  1977. checkbox.checked = !checkbox.checked;
  1978. });
  1979. },
  1980. };
  1981. forum = {
  1982. docStart() {
  1983. const style = `
  1984. .bcpic,
  1985. .banner728,
  1986. .banner300,
  1987. .jav-footer {
  1988. display: none !important;
  1989. }
  1990. #toptb {
  1991. position: fixed !important;
  1992. top: 0 !important;
  1993. left: 0 !important;
  1994. right: 0 !important;
  1995. z-index: 999 !important;
  1996. border-color: #e7e7e7;
  1997. box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075);
  1998. }
  1999. #search-input {
  2000. border-right: none !important;
  2001. }
  2002. .jav-button {
  2003. margin-top: -3px !important;
  2004. margin-left: -4px !important;
  2005. }
  2006. #wp {
  2007. margin-top: 55px !important;
  2008. }
  2009. .biaoqicn_show a {
  2010. width: 20px !important;
  2011. height: 20px !important;
  2012. line-height: 20px !important;
  2013. opacity: .7;
  2014. }
  2015. #online .bm_h {
  2016. border-bottom: none !important;
  2017. }
  2018. #online .bm_h .o img {
  2019. margin-top: 48%;
  2020. }
  2021. #moquu_top {
  2022. right: 20px;
  2023. bottom: 20px;
  2024. }
  2025. `;
  2026. this.globalDark(`${this.style}${style}`);
  2027. },
  2028. contentLoaded() {
  2029. this._globalSearch();
  2030. },
  2031. };
  2032. movie = {
  2033. params: {},
  2034. magnets: null,
  2035.  
  2036. docStart() {
  2037. const style = `
  2038. #mag-submit-show,
  2039. #mag-submit,
  2040. #magnet-table,
  2041. h4[style="position:relative"],
  2042. h4[style="position:relative"] + .row,
  2043. .info span.glyphicon-info-sign {
  2044. display: none !important;
  2045. }
  2046. html {
  2047. padding-right: 0 !important;
  2048. }
  2049. @media (min-width: 1270px) {
  2050. .container { width: 1270px; }
  2051. }
  2052. .container {
  2053. margin-bottom: 40px;
  2054. }
  2055. .row.movie {
  2056. padding: 0 !important;
  2057. }
  2058. #magneturlpost + .movie {
  2059. padding: 10px !important;
  2060. margin-top: 20px !important;
  2061. }
  2062. .screencap, .info {
  2063. padding: 10px !important;
  2064. border: none !important;
  2065. }
  2066. .bigImage {
  2067. position: relative;
  2068. overflow: hidden;
  2069. display: block;
  2070. aspect-ratio: var(--x-cover-ratio);
  2071. opacity: 0;
  2072. }
  2073. .info p {
  2074. line-height: var(--x-line-h) !important;
  2075. }
  2076. .star-box img {
  2077. width: 100% !important;
  2078. height: auto !important;
  2079. margin: 0 !important;
  2080. }
  2081. .star-box .star-name {
  2082. padding: 6px 0 !important;
  2083. background: unset !important;
  2084. border: none !important;
  2085. }
  2086. #avatar-waterfall,
  2087. #sample-waterfall,
  2088. #related-waterfall {
  2089. margin: -10px !important;
  2090. word-spacing: -20px;
  2091. }
  2092. .avatar-box,
  2093. .sample-box,
  2094. .movie-box {
  2095. word-spacing: 0 !important;
  2096. vertical-align: top !important;
  2097. }
  2098. .movie-box > *:nth-child(2) {
  2099. text-align: left !important;
  2100. min-height: 32px !important;
  2101. }
  2102. .x-ml {
  2103. margin-left: 10px;
  2104. }
  2105. .x-mr {
  2106. margin-right: 10px;
  2107. }
  2108. .x-grass-img {
  2109. object-fit: cover;
  2110. }
  2111. .x-grass-mask,
  2112. .x-contain {
  2113. position: absolute;
  2114. width: 100% !important;
  2115. height: 100% !important;
  2116. top: 0;
  2117. left: 0;
  2118. }
  2119. .x-grass-mask {
  2120. backdrop-filter: blur(50px);
  2121. }
  2122. .x-contain {
  2123. opacity: 0;
  2124. object-fit: contain;
  2125. z-index: -1;
  2126. }
  2127. .x-contain.x-in {
  2128. z-index: 9 !important;
  2129. }
  2130. .mfp-img {
  2131. max-height: unset !important;
  2132. }
  2133. .btn-group button {
  2134. border-width: .5px !important;
  2135. }
  2136. .x-res * {
  2137. color: #CC0000 !important;
  2138. }
  2139. .x-table {
  2140. margin: 0 !important;
  2141. }
  2142. .x-caption {
  2143. display: flex;
  2144. align-items: center;
  2145. }
  2146. .x-caption .label {
  2147. position: unset !important;
  2148. }
  2149. .x-table tr {
  2150. table-layout: fixed;
  2151. display: table;
  2152. width: 100%;
  2153. }
  2154. .x-table tr > * {
  2155. vertical-align: middle !important;
  2156. border-left: none !important;
  2157. }
  2158. .x-table tr > *:first-child {
  2159. width: 50px;
  2160. }
  2161. .x-table tr > *:nth-child(2) {
  2162. width: 33.3%;
  2163. }
  2164. .x-table tr > *:last-child,
  2165. .x-table tfoot tr > th:not(:nth-child(3)) {
  2166. border-right: none !important;
  2167. }
  2168. .x-table tbody {
  2169. display: block;
  2170. max-height: calc(37px * 10 - 1px);
  2171. overflow: overlay;
  2172. table-layout: fixed;
  2173. }
  2174. .x-table tbody tr > * {
  2175. border-top: none !important;
  2176. }
  2177. .x-table tbody tr:last-child > *,
  2178. .x-table tfoot tr > * {
  2179. border-bottom: none !important;
  2180. }
  2181. `;
  2182. const dmStyle = `
  2183. .movie,
  2184. .btn-group button[disabled],
  2185. .star-box-up li,
  2186. .table-striped > tbody > tr:nth-of-type(odd) {
  2187. background: var(--x-sub-bgc) !important;
  2188. }
  2189. .btn-group button.active,
  2190. .x-offline.active {
  2191. background: var(--x-bgc) !important;
  2192. }
  2193. tbody tr:hover,
  2194. .table-striped > tbody > tr:nth-of-type(odd):hover {
  2195. background: var(--x-grey) !important;
  2196. }
  2197. `;
  2198. this.globalDark(
  2199. `${this.style}${this._style}${this.boxStyle}${this.customStyle}${style}`,
  2200. `${this.dmStyle}${this._dmStyle}${this.dmBoxStyle}${dmStyle}`
  2201. );
  2202. },
  2203. contentLoaded() {
  2204. this._globalSearch();
  2205. this._globalClick();
  2206.  
  2207. this.params = this.getParams();
  2208.  
  2209. addCopyTarget("h3", { title: "复制标题" });
  2210.  
  2211. this.initSwitch();
  2212. this.updateSwitch({ key: "img", title: "预览大图" });
  2213. this.updateSwitch({ key: "video", title: "预览视频" });
  2214. this.updateSwitch({ key: "player", title: "在线播放", type: "link" });
  2215.  
  2216. this._movieTitle();
  2217. addCopyTarget("span[style='color:#CC0000;']", { title: "复制番号" });
  2218. this._movieStar();
  2219.  
  2220. this._driveMatch();
  2221. DOC.querySelector(".x-offline")?.addEventListener("click", e => this._driveOffline(e));
  2222.  
  2223. const tableObs = new MutationObserver((_, obs) => {
  2224. obs.disconnect();
  2225. this.refactorTable();
  2226. });
  2227. tableObs.observe(DOC.querySelector("#movie-loading"), { attributes: true, attributeFilter: ["style"] });
  2228.  
  2229. this.modifyMovieBox();
  2230. },
  2231. getParams() {
  2232. const info = DOC.querySelector(".info");
  2233. const { textContent } = info;
  2234. return {
  2235. title: DOC.querySelector("h3").textContent,
  2236. code: info.querySelector("span[style='color:#CC0000;']").textContent,
  2237. date: info.querySelector("p:nth-child(2)").childNodes[1].textContent.trim(),
  2238. studio: textContent.match(/(?<=製作商: ).+/g)?.pop(0),
  2239. star: !/暫無出演者資訊/g.test(textContent),
  2240. };
  2241. },
  2242. initSwitch() {
  2243. const bigImage = DOC.querySelector(".bigImage");
  2244.  
  2245. const img = bigImage.querySelector("img");
  2246. img.classList.add("x-grass-img");
  2247.  
  2248. bigImage.insertAdjacentHTML(
  2249. "beforeend",
  2250. `<div class="x-grass-mask"></div><img src="${img.src}" id="x-switch-cover" class="x-contain x-in">`
  2251. );
  2252. bigImage.classList.add("x-in");
  2253.  
  2254. const info = DOC.querySelector(".info");
  2255. info.insertAdjacentHTML(
  2256. "afterbegin",
  2257. `<div class="btn-group btn-group-justified mb10" hidden id="x-switch" role="group">
  2258. <div class="btn-group btn-group-sm" role="group" title="点击放大">
  2259. <button type="button" class="btn btn-default active" for="x-switch-cover">封面</button>
  2260. </div>
  2261. </div>`
  2262. );
  2263.  
  2264. const switcher = info.querySelector("#x-switch");
  2265. switcher.addEventListener("click", ({ target }) => {
  2266. const id = target.getAttribute("for");
  2267. const { classList } = target;
  2268.  
  2269. if (!id) return;
  2270. if (classList.contains("active")) {
  2271. const active = bigImage.querySelector(".x-contain.x-in");
  2272. const { nodeName } = active;
  2273. if (nodeName === "IMG") bigImage.click();
  2274. if (nodeName === "VIDEO") active.muted = !active.muted;
  2275. return;
  2276. }
  2277.  
  2278. const preActive = switcher.querySelector("button.active");
  2279. preActive.classList.toggle("active");
  2280. preActive.setAttribute("title", "点击切换");
  2281. classList.toggle("active");
  2282. target.removeAttribute("title");
  2283.  
  2284. bigImage.querySelector(".x-contain.x-in").classList.toggle("x-in");
  2285. const targetNode = bigImage.querySelector(`#${id}`);
  2286. targetNode.classList.toggle("x-in");
  2287.  
  2288. bigImage.querySelectorAll("video.x-contain:not(.x-in)").forEach(v => v?.pause());
  2289.  
  2290. const { nodeName, src } = targetNode;
  2291. if (nodeName === "VIDEO") {
  2292. targetNode.focus();
  2293. return targetNode.play();
  2294. }
  2295. bigImage.querySelector(".x-grass-img").src = src;
  2296. bigImage.href = src;
  2297. });
  2298. },
  2299. async updateSwitch({ key, title, type }) {
  2300. const id = `x-switch-${key}`;
  2301. if (!type) type = key;
  2302. const switcher = DOC.querySelector("#x-switch");
  2303.  
  2304. const start = () => {
  2305. if (!switcher.classList.contains("x-show")) switcher.classList.add("x-show");
  2306. switcher.insertAdjacentHTML(
  2307. "beforeend",
  2308. `<div class="btn-group btn-group-sm" role="group" title="查询中...">
  2309. <button type="button" class="btn btn-default" for="${id}" disabled>${title}</button>
  2310. </div>`
  2311. );
  2312. };
  2313.  
  2314. const src = await this[`movie${key[0].toUpperCase()}${key.slice(1)}`](this.params, start);
  2315. const node = switcher.querySelector(`button[for="${id}"]`);
  2316. if (!node) return;
  2317.  
  2318. const nodeParent = node.parentNode;
  2319. if (!src) return nodeParent.setAttribute("title", "暂无资源");
  2320.  
  2321. node.removeAttribute("disabled");
  2322.  
  2323. if (type === "link") {
  2324. nodeParent.removeAttribute("title");
  2325. node.setAttribute("title", "跳转链接");
  2326.  
  2327. node.addEventListener("click", e => {
  2328. e.preventDefault();
  2329. e.stopPropagation();
  2330. GM_openInTab(src, { setParent: true, active: true });
  2331. });
  2332. } else {
  2333. node.setAttribute("title", "点击切换");
  2334. nodeParent.setAttribute("title", "点击放大");
  2335.  
  2336. const item = DOC.create(type, { src, id, class: "x-contain" });
  2337.  
  2338. if (type === "video") {
  2339. nodeParent.setAttribute("title", "切换静音");
  2340.  
  2341. item.controls = true;
  2342. item.currentTime = 3;
  2343. item.muted = true;
  2344. item.preload = "metadata";
  2345. item.addEventListener("click", e => {
  2346. e.preventDefault();
  2347. e.stopPropagation();
  2348. const { target: video } = e;
  2349. video.paused ? video.play() : video.pause();
  2350. });
  2351. }
  2352.  
  2353. DOC.querySelector(".bigImage").insertAdjacentElement("beforeend", item);
  2354. }
  2355. },
  2356. async _movieTitle() {
  2357. const start = () => {
  2358. DOC.querySelector("#x-switch").insertAdjacentHTML(
  2359. "afterend",
  2360. `<p><span class="header">机翻标题: </span><span class="x-transTitle">查询中...</span></p>`
  2361. );
  2362. };
  2363.  
  2364. const transTitle = await this.movieTitle(this.params, start);
  2365. const transTitleNode = DOC.querySelector(".x-transTitle");
  2366. if (transTitleNode) transTitleNode.textContent = transTitle ?? "查询失败";
  2367. },
  2368. async _movieStar() {
  2369. const start = () => {
  2370. const starShow = DOC.querySelector("p.star-show");
  2371. starShow.nextElementSibling.nextSibling.remove();
  2372. starShow.insertAdjacentHTML("afterend", `<p class="x-star">查询中...</p>`);
  2373. };
  2374.  
  2375. const star = await this.movieStar(this.params, start);
  2376. const starNode = DOC.querySelector(".x-star");
  2377. if (!starNode) return;
  2378.  
  2379. starNode.innerHTML = !star?.length
  2380. ? "暂无演员数据"
  2381. : star.reduce(
  2382. (acc, cur) =>
  2383. `${acc}<span class="genre"><label><a href="/search/${cur}">${cur}</a></label></span>`,
  2384. ""
  2385. );
  2386. },
  2387. async _driveMatch() {
  2388. const start = () => {
  2389. if (DOC.querySelector(".x-res")) return;
  2390.  
  2391. GM_addStyle(`tbody a[data-magnet] { display: inline !important; }`);
  2392. DOC.querySelector(".info").insertAdjacentHTML(
  2393. "beforeend",
  2394. `<p class="header">网盘资源:</p><p class="x-res">查询中...</p><button type="button" class="btn btn-default btn-sm btn-block x-offline" data-magnet="all">一键离线</button>`
  2395. );
  2396. };
  2397.  
  2398. const res = await this.driveMatch(this.params, start);
  2399. const resNode = DOC.querySelector(".x-res");
  2400. if (!resNode) return;
  2401.  
  2402. resNode.innerHTML = !res?.length
  2403. ? "暂无网盘资源"
  2404. : res.reduce(
  2405. (acc, { pc, t, n }) =>
  2406. `${acc}<div class="x-line"><a href="${this.pcUrl}${pc}" target="_blank" title="${t} / ${n}">${n}</a></div>`,
  2407. ""
  2408. );
  2409. },
  2410. refactorTable() {
  2411. const table = DOC.querySelector("#magnet-table");
  2412. table.parentElement.innerHTML = `
  2413. <table class="table table-striped table-hover table-bordered x-table">
  2414. <caption><div class="x-caption">重构的表格</div></caption>
  2415. <thead>
  2416. <tr>
  2417. <th scope="col">#</th>
  2418. <th scope="col">磁力名称</th>
  2419. <th scope="col">档案大小</th>
  2420. <th scope="col" class="text-center">分享日期</th>
  2421. <th scope="col" class="text-center">来源</th>
  2422. <th scope="col" class="text-center">字幕</th>
  2423. <th scope="col">操作</th>
  2424. </tr>
  2425. </thead>
  2426. <tbody>
  2427. <tr><th scope="row" colspan="7" class="text-center text-muted">暂无数据</th></tr>
  2428. </tbody>
  2429. <tfoot>
  2430. <tr>
  2431. <th scope="row"></th>
  2432. <th></th>
  2433. <th colspan="4" class="text-right">总数</th>
  2434. <td>0</td>
  2435. </tr>
  2436. </tfoot>
  2437. </table>
  2438. `;
  2439.  
  2440. DOC.querySelector(".x-table tbody").addEventListener("click", e => {
  2441. !handleCopyTxt(e, "复制成功") && this._driveOffline(e);
  2442. });
  2443.  
  2444. const magnets = [];
  2445. for (const tr of table.querySelectorAll("tr")) {
  2446. const [link, size, date] = tr.querySelectorAll("td");
  2447. const _link = link?.querySelector("a");
  2448. const _size = size?.textContent.trim();
  2449. if (!_link || !_size || !date) continue;
  2450.  
  2451. magnets.push({
  2452. name: _link.textContent.trim(),
  2453. link: _link.href.split("&")[0],
  2454. zh: !!link.querySelector("a.btn.btn-mini-new.btn-warning.disabled"),
  2455. size: _size,
  2456. bytes: transToBytes(_size),
  2457. date: date.textContent.trim(),
  2458. });
  2459. }
  2460.  
  2461. this.refactorTd(magnets);
  2462. this._movieMagnet();
  2463. },
  2464. async _movieMagnet() {
  2465. const start = () => {
  2466. DOC.querySelector(".x-caption").insertAdjacentHTML(
  2467. "beforeend",
  2468. `<span class="label label-success"><span class="glyphicon glyphicon-ok-sign" aria-hidden="true"></span> 磁力搜索</span><span class="label label-success"><span class="glyphicon glyphicon-ok-sign" aria-hidden="true"></span> 自动去重</span>`
  2469. );
  2470. };
  2471.  
  2472. const magnets = await this.movieMagnet(this.params, start);
  2473. if (magnets?.length) this.refactorTd(magnets);
  2474. },
  2475. refactorTd(magnets) {
  2476. const table = DOC.querySelector(".x-table");
  2477. const caption = table.querySelector(".x-caption");
  2478.  
  2479. let subStart = () => {
  2480. caption.insertAdjacentHTML(
  2481. "beforeend",
  2482. `<span class="label label-success"><span class="glyphicon glyphicon-ok-sign" aria-hidden="true"></span> 字幕筛选</span>`
  2483. );
  2484. };
  2485. let sortStart = () => {
  2486. caption.insertAdjacentHTML(
  2487. "beforeend",
  2488. `<span class="label label-success"><span class="glyphicon glyphicon-ok-sign" aria-hidden="true"></span> 磁力排序</span>`
  2489. );
  2490. };
  2491.  
  2492. if (this.magnets) {
  2493. subStart = null;
  2494. sortStart = null;
  2495. magnets = unique([...this.magnets, ...magnets], "link");
  2496. }
  2497.  
  2498. table.querySelector("tfoot td").textContent = magnets.length;
  2499.  
  2500. magnets = this.movieSub(magnets, subStart);
  2501. magnets = this.movieSort(magnets, sortStart);
  2502. this.magnets = magnets;
  2503.  
  2504. magnets = this.createTd(magnets);
  2505. if (!magnets) return;
  2506. table.querySelector("tbody").innerHTML = magnets;
  2507.  
  2508. if (!subStart || !sortStart) return;
  2509. const node = table.querySelector("thead th:last-child");
  2510. node.innerHTML = `<a href="javascript:void(0);" title="复制所有磁力链接">全部复制</a>`;
  2511. node.querySelector("a").addEventListener("click", e => {
  2512. e.preventDefault();
  2513. e.stopPropagation();
  2514.  
  2515. GM_setClipboard(this.magnets.map(({ link }) => link).join("\n"));
  2516. const { target } = e;
  2517. target.textContent = "复制成功";
  2518.  
  2519. const timer = setTimeout(() => {
  2520. target.textContent = "全部复制";
  2521. clearTimeout(timer);
  2522. }, 300);
  2523. });
  2524. },
  2525. createTd(magnets) {
  2526. if (!magnets.length) return;
  2527. return magnets.reduce(
  2528. (acc, { name, link, size, date, from, href, zh }, index) => `
  2529. ${acc}
  2530. <tr>
  2531. <th scope="row">${index + 1}</th>
  2532. <th class="x-line" title="${name}">
  2533. <a href="${link}">${name}</a>
  2534. </th>
  2535. <td>${size}</td>
  2536. <td class="text-center">${date}</td>
  2537. <td class="text-center">
  2538. <a${href ? ` href="${href}" target="_blank" title="查看详情"` : ""}>
  2539. <code>${from ?? Matched.domain}</code>
  2540. </a>
  2541. </td>
  2542. <td class="text-center">
  2543. <span
  2544. class="glyphicon ${
  2545. zh ? "glyphicon-ok-circle text-success" : "glyphicon-remove-circle text-danger"
  2546. }"
  2547. >
  2548. </span>
  2549. </td>
  2550. <td>
  2551. <a
  2552. href="javascript:void(0);"
  2553. data-copy="${link}"
  2554. class="x-mr"
  2555. title="复制磁力链接"
  2556. >
  2557. 链接复制
  2558. </a>
  2559. <a
  2560. hidden
  2561. href="javascript:void(0);"
  2562. data-magnet="${link}"
  2563. class="text-success"
  2564. title="仅添加离线任务"
  2565. >
  2566. 添加离线
  2567. </a>
  2568. </td>
  2569. </tr>
  2570. `,
  2571. ""
  2572. );
  2573. },
  2574. async _driveOffline(e) {
  2575. await this.driveOffline(e, { ...this.params, magnets: this.magnets });
  2576. await delay(1);
  2577. this._driveMatch();
  2578. },
  2579. };
  2580. }
  2581.  
  2582. // javdb
  2583. class JavDB extends Common {
  2584. constructor() {
  2585. super();
  2586. return super.init();
  2587. }
  2588.  
  2589. excludeMenu = ["G_DARK", "M", "D_CID", "D_VERIFY", "D_RENAME"];
  2590. routes = {
  2591. list: /^\/$|^\/(guess|censored|uncensored|western|fc2|anime|search|video_codes|tags|rankings|actors|series|makers|directors|publishers)/i,
  2592. movie: /^\/v\//i,
  2593. // user: /^\/users\//i,
  2594. };
  2595.  
  2596. // styles
  2597. _style = `
  2598. html {
  2599. overflow: overlay;
  2600. }
  2601. body {
  2602. min-height: 100%;
  2603. }
  2604. .app-desktop-banner,
  2605. #footer {
  2606. display: none !important;
  2607. }
  2608. .float-buttons {
  2609. right: 8px;
  2610. }
  2611. `;
  2612. _customStyle = `
  2613. section.section {
  2614. padding: 20px 20px 0;
  2615. }
  2616. #search-bar-container {
  2617. overflow-x: hidden;
  2618. margin: 0 0 20px !important;
  2619. padding: 0 !important;
  2620. }
  2621. #search-bar-container .column {
  2622. margin: 0 !important;
  2623. padding: 0 !important;
  2624. }
  2625. #search-type,
  2626. #video-search,
  2627. #video-search:hover {
  2628. border: none;
  2629. box-shadow: none;
  2630. }
  2631. .notification:not(:last-child),
  2632. .title:not(:last-child) {
  2633. margin-bottom: 20px !important;
  2634. }
  2635. `;
  2636.  
  2637. // methods
  2638. _globalSearch = () => {
  2639. this.globalSearch("#video-search", "/search?q=%s");
  2640. };
  2641. changeScrollBarColor = () => {
  2642. if (DOC.documentElement.dataset.theme !== "dark") return;
  2643. GM_addStyle(`
  2644. ::-webkit-scrollbar {
  2645. background: #0a0a0a !important;
  2646. }
  2647. ::-webkit-scrollbar-thumb {
  2648. background: var(--x-grey) !important;
  2649. }
  2650. img {
  2651. filter: brightness(.9) contrast(.9) !important;
  2652. }
  2653. `);
  2654. };
  2655.  
  2656. // modules
  2657. list = {
  2658. docStart() {
  2659. let style = `
  2660. .main-title {
  2661. padding: 0 !important;
  2662. }
  2663. .tabs.is-boxed {
  2664. margin: 0 !important;
  2665. padding: 0 !important;
  2666. }
  2667. .index-toolbar,
  2668. .actor-filter-toolbar {
  2669. padding: 20px 0 0 !important;
  2670. }
  2671. #tags,
  2672. .index-toolbar .button-group {
  2673. margin: 0 !important;
  2674. }
  2675. .actor-filter {
  2676. margin: 20px 0 0 !important;
  2677. }
  2678.  
  2679. .video-container:not(.awards) .columns,
  2680. .section-container:not(.awards) {
  2681. margin: 10px auto;
  2682. display: none;
  2683. opacity: 0;
  2684. }
  2685. nav.pagination {
  2686. display: none;
  2687. margin: 0 !important;
  2688. border: none !important;
  2689. padding: 20px 0 40px 0;
  2690. }
  2691. :root[data-theme=dark] nav.pagination {
  2692. border: none !important;
  2693. }
  2694. `;
  2695. if (/^\/(video_codes|series|makers|directors)/i.test(location.pathname)) {
  2696. style = `${style}
  2697. .columns.is-mobile.section-columns {
  2698. padding: 0 !important;
  2699. margin: 0 0 10px !important;
  2700. }
  2701. .columns.is-mobile.section-columns .column.section-title {
  2702. margin: 0 !important;
  2703. padding: 0 !important;
  2704. }
  2705. .columns.is-mobile.section-columns + .columns {
  2706. margin: 0 0 20px !important;
  2707. padding: 0 !important;
  2708. }
  2709. .columns.is-mobile.section-columns + .columns .column.section-addition {
  2710. padding: 0 !important;
  2711. margin: 0 !important;
  2712. }
  2713. `;
  2714. }
  2715. if (/^\/rankings/i.test(location.pathname)) {
  2716. style = `${style}
  2717. section.section .section {
  2718. padding: 0 !important;
  2719. }
  2720. .title.is-4.divider-title {
  2721. padding: 20px 0 !important;
  2722. margin: 0 !important;
  2723. }
  2724. .awards.section-container {
  2725. padding: 10px 0 !important;
  2726. margin: 0 -10px !important;
  2727. }
  2728. .awards,
  2729. .awards .videos {
  2730. padding: 0 !important;
  2731. }
  2732. `;
  2733. }
  2734.  
  2735. const movieBoxStyle = `
  2736. .video-container .columns .column {
  2737. padding: 10px !important;
  2738. margin: 0;
  2739. min-width: unset;
  2740. max-width: none;
  2741. }
  2742. .video-container .columns .column .box {
  2743. padding: 10px;
  2744. width: var(--x-thumb-w);
  2745. box-shadow: var(--x-shadow) !important;
  2746. }
  2747. .video-container .columns .column .box:hover {
  2748. box-shadow: var(--x-shadow) !important;
  2749. }
  2750. :root[data-theme=dark] .video-container .columns .column .box:hover {
  2751. border: 1px solid #363636;
  2752. }
  2753. .video-container .columns .column .box .item-image {
  2754. aspect-ratio: var(--x-thumb-ratio);
  2755. }
  2756. .video-container .columns .column .box .item-image:hover img {
  2757. transform: none;
  2758. }
  2759. .video-container .columns .column .box .item-image img {
  2760. min-height: unset !important;
  2761. max-width: none !important;
  2762. width: 100% !important;
  2763. height: 100% !important;
  2764. object-fit: cover !important;
  2765. }
  2766. .video-container .columns .column .box .uid,
  2767. .video-container .columns .column .box .video-title2 {
  2768. padding-top: 10px;
  2769. font-size: 1em;
  2770. line-height: 1.5;
  2771. }
  2772. .video-container .columns .column .box .video-title,
  2773. .video-container .columns .column .box .uid2 {
  2774. color: #4a4a4a;
  2775. font-size: .8rem;
  2776. }
  2777. .video-container .columns .column .box .tags .tag {
  2778. padding: .1rem .4rem;
  2779. }
  2780. `;
  2781. const avatarBoxStyle = `
  2782. .actor-box {
  2783. padding: 10px !important;
  2784. margin: 10px !important;
  2785. width: var(--x-thumb-w) !important;
  2786. box-shadow: var(--x-shadow) !important;
  2787. }
  2788. .actor-box a figure.image {
  2789. aspect-ratio: var(--x-avatar-ratio);
  2790. }
  2791. .actor-box a figure span.avatar {
  2792. width: 100% !important;
  2793. height: 100% !important;
  2794. }
  2795. .actor-box a strong {
  2796. padding-top: 10px;
  2797. }
  2798. `;
  2799. const cardBoxStyle = `
  2800. #series.section-container .x-item,
  2801. #codes.section-container .x-item,
  2802. #makers.section-container .x-item {
  2803. padding: 10px;
  2804. width: fit-content;
  2805. }
  2806. #series.section-container .x-item .box,
  2807. #codes.section-container .x-item .box,
  2808. #makers.section-container .x-item .box {
  2809. padding: 10px;
  2810. box-shadow: var(--x-shadow) !important;
  2811. width: var(--x-cover-w) !important;
  2812. }
  2813. #series.section-container .x-item .box strong,
  2814. #codes.section-container .x-item .box strong,
  2815. #makers.section-container .x-item .box strong {
  2816. display: block;
  2817. }
  2818. `;
  2819. this.globalDark(
  2820. `${this.style}${this._style}${this.customStyle}${style}${
  2821. this._customStyle
  2822. }${movieBoxStyle}${avatarBoxStyle}${cardBoxStyle}${this.listMovieTitle()}`
  2823. );
  2824. },
  2825. contentLoaded() {
  2826. this._globalSearch();
  2827. this.globalClick([".video-container a", ".section-container a"]);
  2828. this.modifyLayout();
  2829. },
  2830. load() {
  2831. this.changeScrollBarColor();
  2832. },
  2833. getContainer(node = DOC) {
  2834. const selectors = [".video-container:not(.awards) .columns", ".section-container:not(.awards)"];
  2835. const container = node.querySelectorAll(selectors)[0];
  2836. const id = container.id || container.parentElement.id;
  2837. if (container && id) return { container, id };
  2838. },
  2839. modifyLayout() {
  2840. const container = this.getContainer();
  2841. if (!container) return;
  2842. const { container: waterfall, id } = container;
  2843.  
  2844. const _waterfall = waterfall.cloneNode(true);
  2845. const items = this.modifyItem(_waterfall, id);
  2846. if (items.length) {
  2847. _waterfall.innerHTML = "";
  2848. items.forEach(item => _waterfall.appendChild(item));
  2849. }
  2850. waterfall.parentElement.replaceChild(_waterfall, waterfall);
  2851. if (!items.length) return _waterfall.classList.add("x-in");
  2852.  
  2853. const infScroll = this.listScroll(_waterfall, ".x-item", ".pagination-next");
  2854. if (!infScroll) {
  2855. DOC.querySelector("nav.pagination").style.cssText += "display:flex;";
  2856. return;
  2857. }
  2858.  
  2859. infScroll?.on("request", async (_, fetchPromise) => {
  2860. const { body } = await fetchPromise.then();
  2861. if (!body) return;
  2862. const container = this.getContainer(body);
  2863. if (!container) return;
  2864.  
  2865. const { container: waterfall, id } = container;
  2866. const items = this.modifyItem(waterfall, id);
  2867. infScroll.appendItems(items);
  2868. infScroll.options.outlayer.appended(items);
  2869. });
  2870. },
  2871. modifyItem(container, type) {
  2872. if (type === "videos") {
  2873. container.setAttribute("class", "columns x-show");
  2874. return this.modifyMovieBox(container);
  2875. }
  2876. container.classList.add("x-show");
  2877. if (type === "actors") return this.modifyAvatarBox(container);
  2878. if (["series", "makers", "codes"].includes(type)) return this.modifyCardBox(container);
  2879. },
  2880. modifyMovieBox(container) {
  2881. const items = container.querySelectorAll(".column");
  2882. for (const item of items) {
  2883. item.setAttribute("class", "column x-item");
  2884. // modify title
  2885. const title = item.querySelectorAll([".video-title", ".uid2"])[0];
  2886. if (title) {
  2887. title.classList.add("x-ellipsis");
  2888. title.classList.add("x-title");
  2889. }
  2890. // modify cover
  2891. const img = item.querySelector("img");
  2892. if (!img) continue;
  2893. img.removeAttribute("class");
  2894. const { src } = img.dataset;
  2895. if (src !== img.src) img.src = src;
  2896. // cover type
  2897. this._listMovieImgType(item);
  2898. }
  2899. this._driveMatch(container);
  2900. return items;
  2901. },
  2902. modifyAvatarBox(container) {
  2903. const items = container.querySelectorAll(".box.actor-box");
  2904. for (const item of items) {
  2905. item.classList.add("x-item");
  2906. }
  2907. return items;
  2908. },
  2909. modifyCardBox(container) {
  2910. const items = container.querySelectorAll(".column.is-3");
  2911. for (const item of items) {
  2912. item.classList.add("x-item");
  2913. item.querySelector("strong").classList.add("x-ellipsis");
  2914. }
  2915. return items;
  2916. },
  2917.  
  2918. _listMovieImgType(node) {
  2919. const item = node.querySelector(".box");
  2920. if (!item) return;
  2921.  
  2922. const condition = [{ regex: /\/thumbs\//gi, replace: val => val.replace(/\/thumbs\//gi, "/covers/") }];
  2923. this.listMovieImgType(item, condition);
  2924. },
  2925. async _driveMatch(node) {
  2926. const items = node.querySelectorAll(".column");
  2927. for (const item of items) {
  2928. const code = item.querySelector(".uid")?.textContent?.trim();
  2929. if (!code) continue;
  2930. const res = await this.driveMatch({ code, res: "list" });
  2931. if (!res?.length) continue;
  2932.  
  2933. const frame = item.querySelector(".item-image");
  2934. frame.classList.add("x-player");
  2935. frame.setAttribute("title", "点击播放");
  2936. frame.setAttribute("data-code", res[0].pc);
  2937. item.querySelector(".x-title").classList.add("x-matched");
  2938. }
  2939. },
  2940. };
  2941. movie = {
  2942. docStart() {
  2943. const style = `
  2944. .video-meta-panel {
  2945. padding: 0;
  2946. margin-bottom: 20px;
  2947. }
  2948. .video-meta-panel > .columns {
  2949. margin: 0;
  2950. padding: 0;
  2951. }
  2952. .video-meta-panel > .columns > .column {
  2953. padding: 10px;
  2954. margin: 0;
  2955. }
  2956. `;
  2957. this.globalDark(`${this.style}${this._style}${this._customStyle}${style}`);
  2958. },
  2959. contentLoaded() {
  2960. this._globalSearch();
  2961. this.globalClick([".message.video-panel .message-body .tile-images.tile-small a"]);
  2962. },
  2963. load() {
  2964. this.changeScrollBarColor();
  2965. },
  2966. };
  2967. // users = {};
  2968. }
  2969.  
  2970. // 115
  2971. class Drive115 {
  2972. contentLoaded() {
  2973. window.focus();
  2974. DOC.querySelector(".bottom button").addEventListener("click", () => {
  2975. const interval = setInterval(() => {
  2976. if (DOC.querySelector(".vcode-hint").getAttribute("style").indexOf("none") !== -1) {
  2977. clearTimer();
  2978. window.open("", "_self");
  2979. window.close();
  2980. }
  2981. }, 300);
  2982.  
  2983. const timeout = setTimeout(() => clearTimer(), 600);
  2984.  
  2985. const clearTimer = () => {
  2986. clearInterval(interval);
  2987. clearTimeout(timeout);
  2988. };
  2989. });
  2990. }
  2991. }
  2992.  
  2993. const Process = eval(`new ${Matched.domain}()`);
  2994. Process.docStart && Process.docStart();
  2995. Process.contentLoaded && DOC.addEventListener("DOMContentLoaded", () => Process.contentLoaded());
  2996. Process.load && window.addEventListener("load", () => Process.load());
  2997. })();