Douban Info Class

Parse Douban Info

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.gf.qytechs.cn/scripts/438042/1006692/Douban%20Info%20Class.js

  1. // ==UserScript==
  2. // @name Douban Info Class
  3. // @description parse douban info
  4. // @version 0.0.51
  5. // @author Secant(TYT@NexusHD)
  6.  
  7. // config cache encryption or compression
  8. gs.config.ttl = 43200;
  9. gs.config.encrypt = true;
  10. gs.config.encrypter = (data) => LZString.compress(data);
  11. gs.config.decrypter = (encryptedString) => LZString.decompress(encryptedString);
  12.  
  13. class MInfo {
  14. get info() {
  15. return (async () => {
  16. let info = {};
  17. for (let key in this) {
  18. info[key] = await this[key];
  19. }
  20. return info;
  21. })();
  22. }
  23.  
  24. promisedGetterLazify(fun, propertyName, isEnumarable = true) {
  25. return {
  26. configurable: true,
  27. enumerable: isEnumarable,
  28. get: function () {
  29. Object.defineProperty(this, propertyName, {
  30. writable: false,
  31. enumerable: isEnumarable,
  32. value: fun(),
  33. });
  34. return this[propertyName];
  35. },
  36. };
  37. }
  38.  
  39. async getResponseText(url, options = { headers: {} }, cacheConfig = {}) {
  40. let responseText = null;
  41. responseText = gs.get(url, cacheConfig);
  42. if (responseText) {
  43. return (async () => responseText)();
  44. } else {
  45. return new Promise((resolve) => {
  46. GM_xmlhttpRequest({
  47. method: "GET",
  48. url: url,
  49. headers: options.headers,
  50. timeout: options.timeout,
  51. onload: (resp) => {
  52. const { status, statusText, responseText } = resp;
  53. if (status === 200) {
  54. resolve(responseText);
  55. gs.set(url, responseText, cacheConfig);
  56. } else {
  57. console.warn(statusText);
  58. resolve(null);
  59. }
  60. },
  61. ontimeout: (e) => {
  62. console.warn(e);
  63. resolve(null);
  64. },
  65. onerror: (e) => {
  66. console.warn(e);
  67. resolve(null);
  68. },
  69. });
  70. });
  71. }
  72. }
  73.  
  74. flushCache(force = false) {
  75. gs.flush(force);
  76. }
  77. }
  78.  
  79. class DoubanInfo extends MInfo {
  80. static origin = "https://movie.douban.com";
  81. static timeout = 6000;
  82. static cacheConfig = {
  83. ttl: 43200,
  84. encrypt: true,
  85. encrypter: (data) => LZString.compress(data),
  86. decrypter: (encryptedString) => LZString.decompress(encryptedString),
  87. };
  88.  
  89. constructor(id) {
  90. super();
  91. // define promised lazy getters
  92. Object.defineProperties(this, {
  93. id: this.promisedGetterLazify(async () => {
  94. return id;
  95. }, "id"),
  96. subjectPathname: this.promisedGetterLazify(
  97. async () => {
  98. const subjectPathname = `/subject/${await this.id}/`;
  99. return subjectPathname;
  100. },
  101. "subjectPathname",
  102. false
  103. ),
  104. awardPathname: this.promisedGetterLazify(
  105. async () => {
  106. const awardPathname = `/subject/${await this.id}/awards/`;
  107. return awardPathname;
  108. },
  109. "awardPathname",
  110. false
  111. ),
  112. celebrityPathname: this.promisedGetterLazify(
  113. async () => {
  114. const celebrityPathname = `/subject/${await this.id}/celebrities`;
  115. return celebrityPathname;
  116. },
  117. "celebrityPathname",
  118. false
  119. ),
  120. subjectDoc: this.promisedGetterLazify(
  121. async () => {
  122. const currentURL = new URL(window.location.href);
  123. let doc = null;
  124. if (
  125. currentURL.origin === DoubanInfo.origin &&
  126. currentURL.pathname === (await this.subjectPathname)
  127. ) {
  128. doc = document;
  129. } else {
  130. const url = new URL(
  131. await this.subjectPathname,
  132. DoubanInfo.origin
  133. ).toString();
  134. const options = {
  135. headers: {
  136. referrer: DoubanInfo.origin,
  137. },
  138. timeout: DoubanInfo.timeout,
  139. };
  140. const cacheConfig = DoubanInfo.cacheConfig;
  141. const responseText = await this.getResponseText(
  142. url,
  143. options,
  144. cacheConfig
  145. );
  146. if (responseText) {
  147. try {
  148. doc = new DOMParser().parseFromString(
  149. responseText,
  150. "text/html"
  151. );
  152. } catch (e) {
  153. console.warn(e);
  154. }
  155. } else {
  156. console.warn("no response text");
  157. }
  158. }
  159. return doc;
  160. },
  161. "subjectDoc",
  162. false
  163. ),
  164. awardDoc: this.promisedGetterLazify(
  165. async () => {
  166. const currentURL = new URL(window.location.href);
  167. let doc = null;
  168. if (
  169. currentURL.origin === DoubanInfo.origin &&
  170. currentURL.pathname === (await this.awardPathname)
  171. ) {
  172. doc = document;
  173. } else {
  174. const url = new URL(
  175. await this.awardPathname,
  176. DoubanInfo.origin
  177. ).toString();
  178. const options = {
  179. headers: {
  180. referrer: DoubanInfo.origin,
  181. },
  182. timeout: DoubanInfo.timeout,
  183. };
  184. const cacheConfig = DoubanInfo.cacheConfig;
  185. const responseText = await this.getResponseText(
  186. url,
  187. options,
  188. cacheConfig
  189. );
  190. if (responseText) {
  191. try {
  192. doc = new DOMParser().parseFromString(
  193. responseText,
  194. "text/html"
  195. );
  196. } catch (e) {
  197. console.warn(e);
  198. }
  199. } else {
  200. console.warn("no response text");
  201. }
  202. }
  203. return doc;
  204. },
  205. "awardDoc",
  206. false
  207. ),
  208. celebrityDoc: this.promisedGetterLazify(
  209. async () => {
  210. const currentURL = new URL(window.location.href);
  211. let doc = null;
  212. if (
  213. currentURL.origin === DoubanInfo.origin &&
  214. currentURL.pathname === (await this.celebrityPathname)
  215. ) {
  216. doc = document;
  217. } else {
  218. const url = new URL(
  219. await this.celebrityPathname,
  220. DoubanInfo.origin
  221. ).toString();
  222. const options = {
  223. headers: {
  224. referrer: DoubanInfo.origin,
  225. },
  226. timeout: DoubanInfo.timeout,
  227. };
  228. const cacheConfig = DoubanInfo.cacheConfig;
  229. const responseText = await this.getResponseText(
  230. url,
  231. options,
  232. cacheConfig
  233. );
  234. if (responseText) {
  235. try {
  236. doc = new DOMParser().parseFromString(
  237. responseText,
  238. "text/html"
  239. );
  240. } catch (e) {
  241. console.warn(e);
  242. }
  243. } else {
  244. console.warn("no response text");
  245. }
  246. }
  247. return doc;
  248. },
  249. "celebrityDoc",
  250. false
  251. ),
  252. linkingData: this.promisedGetterLazify(
  253. async () => {
  254. const doc = await this.subjectDoc;
  255. const ld =
  256. dirtyJson.parse(
  257. htmlEntities.decode(
  258. doc?.querySelector("head>script[type='application/ld+json']")
  259. ?.textContent
  260. )
  261. ) || null;
  262. return ld;
  263. },
  264. "linkingData",
  265. false
  266. ),
  267. type: this.promisedGetterLazify(async () => {
  268. const ld = await this.linkingData;
  269. const type = ld?.["@type"]?.toLowerCase() || null;
  270. return type;
  271. }, "type"),
  272. poster: this.promisedGetterLazify(async () => {
  273. const doc = await this.subjectDoc;
  274. const ld = await this.linkingData;
  275. const posterFromDoc =
  276. doc?.querySelector("body #mainpic img")?.src || null;
  277. const posterFromMeta =
  278. doc?.querySelector("head>meta[property='og:image']")?.content || null;
  279. const posterFromLD = ld?.image || null;
  280. const poster =
  281. (posterFromDoc || posterFromMeta || posterFromLD)
  282. ?.replace("s_ratio_poster", "l_ratio_poster")
  283. .replace(/img\d+\.doubanio\.com/, "img9.doubanio.com")
  284. .replace(/\.webp$/i, ".jpg") || null;
  285. return poster;
  286. }, "poster"),
  287. title: this.promisedGetterLazify(
  288. async () => {
  289. const doc = await this.subjectDoc;
  290. const ld = await this.linkingData;
  291. const titleFromDoc =
  292. doc?.querySelector("body #content h1>span[property]")
  293. ?.textContent || null;
  294. const titleFromMeta =
  295. doc?.querySelector("head>meta[property='og:title']")?.content ||
  296. null;
  297. const titleFromLD = ld?.name || null;
  298. const title = titleFromDoc || titleFromMeta || titleFromLD;
  299. return title;
  300. },
  301. "title",
  302. false
  303. ),
  304. year: this.promisedGetterLazify(async () => {
  305. const doc = await this.subjectDoc;
  306. const year =
  307. parseInt(
  308. doc
  309. ?.querySelector("body #content>h1>span.year")
  310. ?.textContent.slice(1, -1) || 0,
  311. 10
  312. ) || null;
  313. return year;
  314. }, "year"),
  315. chineseTitle: this.promisedGetterLazify(async () => {
  316. const doc = await this.subjectDoc;
  317. const chineseTitle = doc?.title?.slice(0, -5);
  318. return chineseTitle;
  319. }, "chineseTitle"),
  320. originalTitle: this.promisedGetterLazify(async () => {
  321. let originalTitle;
  322. if (await this.isChinese) {
  323. originalTitle = await this.chineseTitle;
  324. } else {
  325. originalTitle = (await this.title)
  326. ?.replace(await this.chineseTitle, "")
  327. .trim();
  328. }
  329. return originalTitle;
  330. }, "originalTitle"),
  331. aka: this.promisedGetterLazify(async () => {
  332. const doc = await this.subjectDoc;
  333. const priority = (t) =>
  334. /\(港.?台\)/.test(t) ? 1 : /\((?:[港台]|香港|台湾)\)/.test(t) ? 2 : 3;
  335. let aka =
  336. [...(doc?.querySelectorAll("body #info span.pl") || [])]
  337. .find((n) => n.textContent.includes("又名"))
  338. ?.nextSibling?.textContent.split("/")
  339. .map((t) => t.trim())
  340. .sort((t1, t2) => priority(t1) - priority(t2)) || [];
  341. if (aka.length === 0) {
  342. aka = null;
  343. }
  344. return aka;
  345. }, "aka"),
  346. isChinese: this.promisedGetterLazify(
  347. async () => {
  348. let isChinese = false;
  349. if ((await this.title) === (await this.chineseTitle)) {
  350. isChinese = true;
  351. }
  352. return isChinese;
  353. },
  354. "isChinese",
  355. false
  356. ),
  357. region: this.promisedGetterLazify(async () => {
  358. const doc = await this.subjectDoc;
  359. let region =
  360. [...(doc?.querySelectorAll("body #info span.pl") || [])]
  361. .find((n) => n.textContent.includes("制片国家/地区"))
  362. ?.nextSibling?.textContent.split("/")
  363. .map((r) => r.trim()) || [];
  364. if (region.length === 0) {
  365. region = null;
  366. }
  367. return region;
  368. }, "region"),
  369. language: this.promisedGetterLazify(async () => {
  370. const doc = await this.subjectDoc;
  371. let language =
  372. [...(doc?.querySelectorAll("body #info span.pl") || [])]
  373. .find((n) => n.textContent.includes("语言"))
  374. ?.nextSibling?.textContent.split("/")
  375. .map((l) => l.trim()) || [];
  376. if (language.length === 0) {
  377. language = null;
  378. }
  379. return language;
  380. }, "language"),
  381. genre: this.promisedGetterLazify(async () => {
  382. const doc = await this.subjectDoc;
  383. const ld = await this.linkingData;
  384. let genreFromDoc = [
  385. ...(doc?.querySelectorAll('body #info span[property="v:genre"]') ||
  386. []),
  387. ].map((g) => g.textContent.trim());
  388. if (genreFromDoc.length === 0) {
  389. genreFromDoc = null;
  390. }
  391. let genreFromLD = ld?.genre || [];
  392. if (genreFromLD.length === 0) {
  393. genreFromLD = null;
  394. }
  395. const genre = genreFromDoc || genreFromLD;
  396. return genre;
  397. }, "genre"),
  398. duration: this.promisedGetterLazify(async () => {
  399. const doc = await this.subjectDoc;
  400. const ld = await this.linkingData;
  401. const type = await this.type;
  402. let movieDurationFromDoc = null,
  403. episodeDurationFromDoc = null;
  404. if (type === "movie") {
  405. let durationString = "";
  406. let node =
  407. doc?.querySelector('body span[property="v:runtime"]') || null;
  408. while (node && node.nodeName !== "BR") {
  409. durationString += node.textContent;
  410. node = node.nextSibling;
  411. }
  412. if (durationString !== "") {
  413. movieDurationFromDoc = durationString
  414. .split("/")
  415. .map((str) => {
  416. str = str.trim();
  417. const strOI = splitOI(str);
  418. const duration = parseInt(strOI.o || 0, 10) * 60 || null;
  419. const whereabouts = strOI.i || null;
  420. return {
  421. duration,
  422. whereabouts,
  423. };
  424. })
  425. .filter((d) => d.duration);
  426. if (movieDurationFromDoc.length === 0) {
  427. movieDurationFromDoc = null;
  428. }
  429. }
  430. } else if (type === "tvseries") {
  431. const episodeDurationSecondsFromDoc =
  432. parseInt(
  433. [...(doc?.querySelectorAll("body #info span.pl") || [])]
  434. .find((n) => n.textContent.includes("单集片长"))
  435. ?.nextSibling?.textContent.trim() || 0,
  436. 10
  437. ) * 60 || null;
  438. if (episodeDurationSecondsFromDoc) {
  439. episodeDurationFromDoc = [
  440. {
  441. duration: episodeDurationSecondsFromDoc,
  442. whereabouts: null,
  443. },
  444. ];
  445. }
  446. }
  447. let durationFromMeta = null;
  448. const durationSecondsFromMeta =
  449. parseInt(
  450. doc?.querySelector("head>meta[property='video:duration']")
  451. ?.content || 0,
  452. 10
  453. ) || null;
  454. if (durationSecondsFromMeta) {
  455. durationFromMeta = [
  456. {
  457. duration: durationSecondsFromMeta,
  458. whereabouts: null,
  459. },
  460. ];
  461. }
  462. let durationFromLD = null;
  463. const durationSecondsFromLD =
  464. parseInt(
  465. ld?.duration?.replace(
  466. /^PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?$/,
  467. (_, p1, p2, p3) => {
  468. return (
  469. parseInt(p1 || 0, 10) * 3600 +
  470. parseInt(p2 || 0, 10) * 60 +
  471. parseInt(p3 || 0, 10)
  472. ).toString();
  473. }
  474. ) || 0,
  475. 10
  476. ) || null;
  477. if (durationSecondsFromLD) {
  478. durationFromLD = [
  479. {
  480. duration: durationSecondsFromLD,
  481. whereabouts: null,
  482. },
  483. ];
  484. }
  485. const duration =
  486. movieDurationFromDoc ||
  487. episodeDurationFromDoc ||
  488. durationFromMeta ||
  489. durationFromLD;
  490. return duration;
  491. }, "duration"),
  492. datePublished: this.promisedGetterLazify(async () => {
  493. const doc = await this.subjectDoc;
  494. const ld = await this.linkingData;
  495. let datePublishedFromDoc = [
  496. ...(doc?.querySelectorAll(
  497. 'body #info span[property="v:initialReleaseDate"]'
  498. ) || []),
  499. ]
  500. .map((e) => {
  501. const str = e.textContent.trim();
  502. const strOI = splitOI(str);
  503. if (!strOI.o) {
  504. return null;
  505. } else {
  506. return {
  507. date: new Date(strOI.o),
  508. whereabouts: strOI.i || null,
  509. };
  510. }
  511. })
  512. .filter((e) => !!e)
  513. .sort((d1, d2) => {
  514. d1.date - d2.date;
  515. });
  516. if (datePublishedFromDoc.length === 0) {
  517. datePublishedFromDoc = null;
  518. }
  519. const datePublishedStringFromLD = ld?.datePublished || null;
  520. let datePublishedFromLD = null;
  521. if (datePublishedStringFromLD) {
  522. datePublishedFromLD = [
  523. { date: new Date(datePublishedStringFromLD), whereabouts: null },
  524. ];
  525. }
  526. const datePublished = datePublishedFromDoc || datePublishedFromLD;
  527. return datePublished;
  528. }, "datePublished"),
  529. episodeCount: this.promisedGetterLazify(async () => {
  530. if ((await this.type) === "tvseries") {
  531. const doc = await this.subjectDoc;
  532. const episodeCount =
  533. parseInt(
  534. [...(doc?.querySelectorAll("body #info span.pl") || [])]
  535. .find((n) => n.textContent.includes("集数"))
  536. ?.nextSibling?.textContent.trim() || 0,
  537. 10
  538. ) || null;
  539. return episodeCount;
  540. } else {
  541. return null;
  542. }
  543. }, "episodeCount"),
  544. tag: this.promisedGetterLazify(async () => {
  545. const doc = await this.subjectDoc;
  546. let tag = [
  547. ...(doc?.querySelectorAll("body div.tags-body>a") || []),
  548. ].map((t) => t.textContent);
  549. if (tag.length === 0) {
  550. tag = null;
  551. }
  552. return tag;
  553. }, "tag"),
  554. rating: this.promisedGetterLazify(async () => {
  555. const doc = await this.subjectDoc;
  556. let ratingFromDoc = null;
  557. let countFromDoc =
  558. parseInt(
  559. doc?.querySelector('body #interest_sectl [property="v:votes"]')
  560. ?.textContent || 0,
  561. 10
  562. ) || null;
  563. let valueFromDoc =
  564. parseFloat(
  565. doc?.querySelector('body #interest_sectl [property="v:average"]')
  566. ?.textContent || 0
  567. ) || null;
  568. if (countFromDoc && valueFromDoc) {
  569. ratingFromDoc = {
  570. count: countFromDoc,
  571. value: valueFromDoc,
  572. max: 10,
  573. };
  574. }
  575. const ld = await this.linkingData;
  576. let ratingFromLD = null;
  577. let countFromLD =
  578. parseInt(ld?.aggregateRating?.ratingCount || 0, 10) || null;
  579. let valueFromLD =
  580. parseFloat(ld?.aggregateRating?.ratingValue || 0) || null;
  581. if (countFromLD && valueFromLD) {
  582. ratingFromLD = {
  583. count: countFromLD,
  584. value: valueFromLD,
  585. max: 10,
  586. };
  587. }
  588. const rating = ratingFromDoc || ratingFromLD;
  589. return rating;
  590. }, "rating"),
  591. description: this.promisedGetterLazify(async () => {
  592. const doc = await this.subjectDoc;
  593. const ld = await this.linkingData;
  594. const descriptionFromDoc =
  595. [
  596. ...(doc?.querySelector(
  597. 'body #link-report>[property="v:summary"],body #link-report>span.all.hidden'
  598. )?.childNodes || []),
  599. ]
  600. .filter((e) => e.nodeType === 3)
  601. .map((e) => e.textContent.trim())
  602. .join("\n") || null;
  603. const descriptionFromMeta =
  604. doc?.querySelector("head>meta[property='og:description']")?.content ||
  605. null;
  606. const descriptionFromLD = ld?.description || null;
  607. const description =
  608. descriptionFromDoc || descriptionFromMeta || descriptionFromLD;
  609. return description;
  610. }, "description"),
  611. imdbId: this.promisedGetterLazify(async () => {
  612. const doc = await this.subjectDoc;
  613. let imdbId = null;
  614. if (
  615. doc?.querySelector("body #season option:checked")?.textContent !==
  616. "1" ||
  617. false
  618. ) {
  619. const doubanId =
  620. doc.querySelector("body #season option:first-of-type")?.value ||
  621. null;
  622. if (doubanId) {
  623. const firstSeasonDoubanInfo = new DoubanInfo(doubanId);
  624. imdbId = await firstSeasonDoubanInfo.imdbId;
  625. }
  626. } else {
  627. imdbId =
  628. [...(doc?.querySelectorAll("body #info span.pl") || [])]
  629. .find((n) => n.textContent.includes("IMDb:"))
  630. ?.nextSibling?.textContent.match(/tt(\d+)/)?.[1] || null;
  631. }
  632. return imdbId;
  633. }, "imdbId"),
  634. awardData: this.promisedGetterLazify(async () => {
  635. const doc = await this.awardDoc;
  636. let awardData = [...(doc?.querySelectorAll("body div.awards") || [])]
  637. .map((awardNode) => {
  638. const event =
  639. awardNode?.querySelector(".hd>h2 a")?.textContent.trim() || null;
  640. const year =
  641. parseInt(
  642. awardNode
  643. ?.querySelector(".hd>h2 .year")
  644. ?.textContent.match(/\d+/)?.[0] || 0,
  645. 10
  646. ) || null;
  647. let award = [...(awardNode?.querySelectorAll(".award") || [])]
  648. .map((a) => {
  649. const name =
  650. a.querySelector("li:first-of-type")?.textContent.trim() ||
  651. null;
  652. let recipient = a
  653. .querySelector("li:nth-of-type(2)")
  654. ?.textContent.split("/")
  655. .map((p) => p.trim() || null)
  656. .filter((p) => !!p);
  657. if (recipient.length === 0) {
  658. recipient = null;
  659. }
  660. if (name) {
  661. return {
  662. name,
  663. recipient,
  664. };
  665. } else {
  666. return null;
  667. }
  668. })
  669. .filter((a) => !!a);
  670. if (award.length === 0) {
  671. award = null;
  672. }
  673. if (event) {
  674. return {
  675. event,
  676. year,
  677. award,
  678. };
  679. } else {
  680. return null;
  681. }
  682. })
  683. .filter((a) => !!a);
  684. if (awardData.length === 0) {
  685. awardData = null;
  686. }
  687. return awardData;
  688. }, "awardData"),
  689. celebrityData: this.promisedGetterLazify(async () => {
  690. const doc = await this.celebrityDoc;
  691. let celebrityData = [
  692. ...(doc?.querySelectorAll("body #celebrities>div.list-wrapper") ||
  693. []),
  694. ]
  695. .map((o) => {
  696. const occupation =
  697. o.querySelector("h2")?.textContent.trim() || null;
  698. let occupationCh = null;
  699. let occupationEn = null;
  700. if (occupation) {
  701. const occupationSplitted = splitChEn(occupation);
  702. occupationCh = occupationSplitted.ch;
  703. occupationEn = occupationSplitted.en;
  704. }
  705. const celebrities = [...(o.querySelectorAll("li.celebrity") || [])]
  706. .map((c) => {
  707. const name =
  708. c.querySelector(".info>.name")?.textContent.trim() || null;
  709. let nameCh = null;
  710. let nameEn = null;
  711. if (name) {
  712. const nameSplitted = splitChEn(name);
  713. nameCh = nameSplitted.ch;
  714. nameEn = nameSplitted.en;
  715. }
  716. const creditAndAttribute =
  717. c.querySelector(".info>.role")?.textContent.trim() || null;
  718. let credit = null;
  719. let attribute = null;
  720. let creditCh = null;
  721. let creditEn = null;
  722. if (creditAndAttribute) {
  723. const creditAndAttributeSplitted =
  724. splitOI(creditAndAttribute);
  725. credit = creditAndAttributeSplitted.o;
  726. attribute = creditAndAttributeSplitted.i;
  727. if (credit) {
  728. const creditSplitted = splitChEn(credit);
  729. creditCh = creditSplitted.ch;
  730. creditEn = creditSplitted.en;
  731. }
  732. }
  733. if (!credit && occupation) {
  734. credit = occupation;
  735. creditCh = occupationCh;
  736. creditEn = occupationEn;
  737. }
  738. if (!occupation && !name && !credit && !attribute) {
  739. return null;
  740. } else {
  741. return {
  742. occupation: {
  743. value: occupation,
  744. ch: occupationCh,
  745. en: occupationEn,
  746. },
  747. name: {
  748. value: name,
  749. ch: nameCh,
  750. en: nameEn,
  751. },
  752. credit: {
  753. value: credit,
  754. ch: creditCh,
  755. en: creditEn,
  756. },
  757. attribute: {
  758. value: attribute,
  759. },
  760. };
  761. }
  762. })
  763. .filter((c) => !!c);
  764. return celebrities;
  765. })
  766. .flat();
  767. if (celebrityData.length === 0) {
  768. celebrityData = null;
  769. }
  770. return celebrityData;
  771. }, "celebrityData"),
  772. });
  773. }
  774. }
  775.  
  776. class IMDbInfo extends MInfo {
  777. static originalOrigin = "https://www.imdb.com";
  778. static originalPluginOrigin = "http://p.media-imdb.com";
  779. static proxyOrigin = "https://proxy.secant.workers.dev";
  780. static isProxified = true;
  781. static origin = IMDbInfo.isProxified
  782. ? IMDbInfo.proxyOrigin
  783. : IMDbInfo.originalOrigin;
  784. static pluginOrigin = IMDbInfo.isProxified
  785. ? IMDbInfo.proxyOrigin
  786. : IMDbInfo.originalPluginOrigin;
  787. static timeout = 6000;
  788. static cacheConfig = {
  789. ttl: 43200,
  790. encrypt: true,
  791. encrypter: (data) => LZString.compress(data),
  792. decrypter: (encryptedString) => LZString.decompress(encryptedString),
  793. };
  794.  
  795. constructor(id) {
  796. super();
  797. // define promised lazy getters
  798. Object.defineProperties(this, {
  799. id: this.promisedGetterLazify(async () => {
  800. return id;
  801. }, "id"),
  802. titlePathname: this.promisedGetterLazify(
  803. async () => {
  804. let titlePathname;
  805. if (IMDbInfo.isProxified) {
  806. titlePathname = `/worker/proxy/www.imdb.com/title/tt${await this
  807. .id}/`;
  808. } else {
  809. titlePathname = `/title/tt${await this.id}/`;
  810. }
  811. return titlePathname;
  812. },
  813. "titlePathname",
  814. false
  815. ),
  816. releaseInfoPathname: this.promisedGetterLazify(
  817. async () => {
  818. let releaseInfoPathname;
  819. if (IMDbInfo.isProxified) {
  820. releaseInfoPathname = `/worker/proxy/www.imdb.com/title/tt${await this
  821. .id}/releaseinfo`;
  822. } else {
  823. releaseInfoPathname = `/title/tt${await this.id}/releaseinfo`;
  824. }
  825. return releaseInfoPathname;
  826. },
  827. "releaseInfoPathname",
  828. false
  829. ),
  830. pluginPathname: this.promisedGetterLazify(
  831. async () => {
  832. let pluginPathname;
  833. if (IMDbInfo.isProxified) {
  834. pluginPathname = `/worker/proxy/p.media-imdb.com/static-content/documents/v1/title/tt${await this
  835. .id}/ratings%253Fjsonp=imdb.rating.run:imdb.api.title.ratings/data.json`;
  836. } else {
  837. pluginPathname = `/static-content/documents/v1/title/tt${await this
  838. .id}/ratings%3Fjsonp=imdb.rating.run:imdb.api.title.ratings/data.json`;
  839. }
  840. return pluginPathname;
  841. },
  842. "pluginPathname",
  843. false
  844. ),
  845. titleDoc: this.promisedGetterLazify(
  846. async () => {
  847. const currentURL = new URL(window.location.href);
  848. let doc = null;
  849. if (
  850. currentURL.origin === IMDbInfo.origin &&
  851. currentURL.pathname === (await this.titlePathname)
  852. ) {
  853. doc = document;
  854. } else {
  855. const url = new URL(
  856. await this.titlePathname,
  857. IMDbInfo.origin
  858. ).toString();
  859. const options = {
  860. headers: {
  861. referrer: IMDbInfo.origin,
  862. },
  863. timeout: IMDbInfo.timeout,
  864. };
  865. const cacheConfig = IMDbInfo.cacheConfig;
  866. const responseText = await this.getResponseText(
  867. url,
  868. options,
  869. cacheConfig
  870. );
  871. if (responseText) {
  872. try {
  873. doc = new DOMParser().parseFromString(
  874. responseText,
  875. "text/html"
  876. );
  877. } catch (e) {
  878. console.warn(e);
  879. }
  880. } else {
  881. console.warn("no response text");
  882. }
  883. }
  884. return doc;
  885. },
  886. "titleDoc",
  887. false
  888. ),
  889. releaseInfoDoc: this.promisedGetterLazify(
  890. async () => {
  891. const currentURL = new URL(window.location.href);
  892. let doc = null;
  893. if (
  894. currentURL.origin === IMDbInfo.origin &&
  895. currentURL.pathname === (await this.releaseInfoPathname)
  896. ) {
  897. doc = document;
  898. } else {
  899. const url = new URL(
  900. await this.releaseInfoPathname,
  901. IMDbInfo.origin
  902. ).toString();
  903. const options = {
  904. headers: {
  905. referrer: IMDbInfo.origin,
  906. },
  907. timeout: IMDbInfo.timeout,
  908. };
  909. const cacheConfig = IMDbInfo.cacheConfig;
  910. const responseText = await this.getResponseText(
  911. url,
  912. options,
  913. cacheConfig
  914. );
  915. if (responseText) {
  916. try {
  917. doc = new DOMParser().parseFromString(
  918. responseText,
  919. "text/html"
  920. );
  921. } catch (e) {
  922. console.warn(e);
  923. }
  924. } else {
  925. console.warn("no response text");
  926. }
  927. }
  928. return doc;
  929. },
  930. "titleDoc",
  931. false
  932. ),
  933. pluginResponseText: this.promisedGetterLazify(
  934. async () => {
  935. let responseText = null;
  936. const url = new URL(
  937. await this.pluginPathname,
  938. IMDbInfo.pluginOrigin
  939. ).toString();
  940. const options = {
  941. headers: {
  942. referrer: IMDbInfo.pluginOrigin,
  943. },
  944. timeout: IMDbInfo.timeout,
  945. };
  946. const cacheConfig = IMDbInfo.cacheConfig;
  947. const pluginResponseText = await this.getResponseText(
  948. url,
  949. options,
  950. cacheConfig
  951. );
  952. if (!pluginResponseText) {
  953. console.warn("no response text");
  954. }
  955. return pluginResponseText;
  956. },
  957. "pluginResponseText",
  958. false
  959. ),
  960. linkingData: this.promisedGetterLazify(
  961. async () => {
  962. const doc = await this.titleDoc;
  963. const ld =
  964. dirtyJson.parse(
  965. htmlEntities.decode(
  966. doc?.querySelector("head>script[type='application/ld+json']")
  967. ?.textContent
  968. )
  969. ) || null;
  970. return ld;
  971. },
  972. "linkingData",
  973. false
  974. ),
  975. });
  976. }
  977. }
  978.  
  979. class MtimeInfo extends MInfo {
  980. constructor(id) {
  981. super();
  982. // define promised lazy getters
  983. Object.defineProperties(this, {
  984. id: this.promisedGetterLazify(async () => {
  985. return id;
  986. }, "id"),
  987. });
  988. }
  989. }
  990.  
  991. function splitOI(word) {
  992. word = word.trim();
  993. const splitOIRegExp = /^(?<o>.*?)(?:\((?<i>[^\(]*?)\))?$/;
  994. let { o = null, i = null } = word.match(splitOIRegExp)?.groups || {};
  995. return {
  996. o: o ? o.trim() : o,
  997. i: i ? i.trim() : i,
  998. };
  999. }
  1000.  
  1001. function splitChEn(word) {
  1002. word = word.trim();
  1003. const splitChEnRegExp =
  1004. /^(?<ch>.*\p{Script=Han}[^\s\p{Script=Han}]*)(?: +(?<en1>[^\p{Script=Han}]*))?$|^(?<en2>[^\p{Script=Han}]*)$/u;
  1005. const splitEnEnRegExp = /^(?<en>[^\p{Script=Han}]*?) +\k<en>$/u;
  1006. let {
  1007. ch = null,
  1008. en1 = null,
  1009. en2 = null,
  1010. } = word.match(splitChEnRegExp)?.groups || {};
  1011. let en = (en1 || en2)?.trim() || null;
  1012. if (ch === null) {
  1013. en = en.match(splitEnEnRegExp)?.groups.en || en;
  1014. }
  1015. return {
  1016. ch: ch ? ch.trim() : ch,
  1017. en: en ? en.trim() : en,
  1018. };
  1019. }

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址