├── README.md
├── assets
└── demo
│ └── gif
│ └── demo1.gif
└── 电影信息查询脚本.js
/README.md:
--------------------------------------------------------------------------------
1 | # 电影信息查询脚本
2 |
3 | ## :book: 介绍 Introduction
4 |
5 | 本脚本用于在豆瓣电影页面(如:[肖申克的救赎 (豆瓣)](https://movie.douban.com/subject/1292052/))获取电影信息,多用于各大 PT 站点发布种子填写介绍。与主流格式(PT-Gen)基本一致。
6 |
7 | ## :balloon: 特性 Features
8 |
9 | - 纯前端处理,无托管服务器,本获取信息服务无访问次数限制;
10 |
11 | - 使用 [IMDb 官方插件接口](https://www.imdb.com/plugins)(支持 JSONP 跨域获取,虽然用户脚本可以不考虑跨域问题)获取评分,相比于 OMDb,无需 API Key,分数获取迅速,更新及时(1次/24h);
12 |
13 | `https://p.media-imdb.com/static-content/documents/v1/title/tt${ID}/ratings%3Fjsonp=imdb.rating.run:imdb.api.title.ratings/data.json`
14 |
15 | - 考虑到豆瓣的 API 极其不稳定并有下线的可能性,本脚本尽量避免了对豆瓣 API 的调用,以避免对其过多的依赖;
16 |
17 | 最新情况:[豆瓣 API 疑似彻底下线](https://v2ex.com/t/699393),所有已知的公开 API Key 均报错:`code: 109, msg: "invalid_credencial2"`
18 |
19 | - 部分豆瓣条目需用户登录后才能访问,登录豆瓣账户后使用本脚本即可获取相应条目信息;
20 |
21 | - 丰富了演职员列表信息,包括饰演、配音数据,皆从豆瓣爬取;
22 |
23 | - 自动获取时光网对应电影条目的**幕后揭秘**信息。
24 |
25 | ## :dart: 待办 TODOs
26 |
27 | - [ ] 主要进行一些维护工作,新功能视情况增加。
28 |
29 | ## :roller_coaster: 演示 Demo
30 |
31 | 
32 |
33 | ## :hammer_and_wrench: 安装 Installation
34 |
35 | 请到 [Greasy Fork](https://greasyfork.org/zh-CN/scripts/38878-%E7%94%B5%E5%BD%B1%E4%BF%A1%E6%81%AF%E6%9F%A5%E8%AF%A2%E8%84%9A%E6%9C%AC) 页面,配合 [Tampermonkey](https://www.tampermonkey.net/) 浏览器扩展安装使用。
36 |
37 | ## :building_construction: 贡献 Contribution
38 |
39 | 本脚本暂时不开放多人编辑权限,如有问题和想法请先到 [Greasy Fork 反馈页面](https://greasyfork.org/zh-CN/scripts/38878-%E7%94%B5%E5%BD%B1%E4%BF%A1%E6%81%AF%E6%9F%A5%E8%AF%A2%E8%84%9A%E6%9C%AC/feedback)或 [Github Issues 页面](https://github.com/Sec-ant/MovieInfoGen/issues)提交。
40 |
41 | ## :copyright: 许可 License
42 |
43 | [GNU General Public License v3.0 or later](https://spdx.org/licenses/GPL-3.0-or-later.html)
44 |
45 | ## :mailbox: 邮箱 Email
46 |
47 | zzwu@zju.edu.cn
48 |
49 | ## :heart: 捐赠 Donation
50 |
51 |
支付宝 Alipay | 微信 Wechat |
52 |  |  |
53 |
--------------------------------------------------------------------------------
/assets/demo/gif/demo1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/N3xusHD/MovieInfoGen/af4f6a3df54f185aae7a5e05829236987be76df7/assets/demo/gif/demo1.gif
--------------------------------------------------------------------------------
/电影信息查询脚本.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name 电影信息查询脚本
3 | // @description Fetch Douban Description, IMDb information for PT upload
4 | // @version 3.7.10
5 | // @author Secant(TYT@NexusHD)
6 | // @include http*://movie.douban.com/subject/*
7 | // @require https://cdn.staticfile.org/jquery/3.4.1/jquery.min.js
8 | // @icon https://movie.douban.com/favicon.ico
9 | // @contributionURL https://i.loli.net/2020/02/28/JPGgHc3UMwXedhv.jpg
10 | // @contributionAmount 10
11 | // @namespace https://greasyfork.org/users/152136
12 | // @grant GM_xmlhttpRequest
13 | // @connect front-gateway.mtime.cn
14 | // @connect api.douban.com
15 | // @connect proxy.secant.workers.dev
16 | // @connect p.media-imdb.com
17 | // ==/UserScript==
18 |
19 | /*jshint esversion: 8 */
20 |
21 | (function ($) {
22 | const a = [
23 | "MGIyYmRlZGE0M2I1Njg4OTI=",
24 | "MGRhZDU1MWVjMGY4NGVkMDI=",
25 | "OTA3ZmY1YzQyZThlYzcw",
26 | "MDI2NDZkM2ZiNjlhNTJmZjA=",
27 | "MTgzOWM4ZWNiMjAzOTli",
28 | "NzJkNDdiZjIzY2VmOGZk",
29 | "OWVjYmI1MzQ0MjUyYTRh",
30 | "MGRmOTkzYzY2YzBjNjM2ZTI=",
31 | ];
32 | (function (b, e) {
33 | const f = function (g) {
34 | while (--g) {
35 | b.push(b.shift());
36 | }
37 | };
38 | f(++e);
39 | })(a, 0x1e4);
40 | const b = function (c, d) {
41 | c = c - 0x0;
42 | let e = a[c];
43 | if (b.pbhcos === undefined) {
44 | (function () {
45 | const g =
46 | typeof window !== "undefined"
47 | ? window
48 | : typeof process === "object" &&
49 | typeof require === "function" &&
50 | typeof global === "object"
51 | ? global
52 | : this;
53 | const h =
54 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
55 | g.atob ||
56 | (g.atob = function (i) {
57 | const j = String(i).replace(/=+$/, "");
58 | let k = "";
59 | for (
60 | let l = 0x0, m, n, o = 0x0;
61 | (n = j.charAt(o++));
62 | ~n && ((m = l % 0x4 ? m * 0x40 + n : n), l++ % 0x4)
63 | ? (k += String.fromCharCode(
64 | 0xff & (m >> ((-0x2 * l) & 0x6))
65 | ))
66 | : 0x0
67 | ) {
68 | n = h.indexOf(n);
69 | }
70 | return k;
71 | });
72 | })();
73 | b.ICpnUS = function (g) {
74 | const h = atob(g);
75 | let j = [];
76 | for (let k = 0x0, l = h.length; k < l; k++) {
77 | j +=
78 | "%" + ("00" + h.charCodeAt(k).toString(0x10)).slice(-0x2);
79 | }
80 | return decodeURIComponent(j);
81 | };
82 | b.Snsmje = {};
83 | b.pbhcos = !![];
84 | }
85 | const f = b.Snsmje[c];
86 | if (f === undefined) {
87 | e = b.ICpnUS(e);
88 | b.Snsmje[c] = e;
89 | } else {
90 | e = f;
91 | }
92 | return e;
93 | };
94 | const DoubanAPIKeys = [
95 | b("0x7") + b("0x1"),
96 | b("0x5") + b("0x6"),
97 | b("0x4") + b("0x0"),
98 | b("0x3") + b("0x2"),
99 | ];
100 | const TIMEOUT = 6000;
101 | const $toggle = $('描述文本:');
102 | const $infoGen = $('获取');
103 | const $message = $('');
104 | const $copyPaste = $('').css({
105 | position: "absolute",
106 | top: 0,
107 | left: -9999,
108 | });
109 | function decodeEntities(encodedString) {
110 | var textArea = document.createElement("textarea");
111 | textArea.innerHTML = encodedString;
112 | return textArea.value;
113 | }
114 | function addComma(x) {
115 | var parts = x.toString().split(".");
116 | parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
117 | return parts.join(".");
118 | }
119 | function jsCopy(e) {
120 | e.select();
121 | try {
122 | return document.execCommand("copy");
123 | } catch (err) {
124 | return false;
125 | }
126 | }
127 | function $$(htmlString) {
128 | return $(htmlString, document.implementation.createHTMLDocument("virtual"));
129 | }
130 | $.fn.extend({
131 | nextNodeUtil: function (selector) {
132 | const siblings = [];
133 | try {
134 | let current = this[0];
135 | do {
136 | siblings.push(current);
137 | current = current.nextSibling;
138 | } while (
139 | current.nodeType === Node.TEXT_NODE ||
140 | !current.matches(selector)
141 | );
142 | return $(siblings);
143 | } catch (e) {
144 | return $(siblings);
145 | }
146 | },
147 | });
148 | function getPoster() {
149 | try {
150 | return $("#mainpic img")[0].src.replace(
151 | /^.+(p\d+).+$/,
152 | (_, p1) =>
153 | `https://img9.doubanio.com/view/photo/l_ratio_poster/public/${p1}.jpg`
154 | );
155 | } catch (e) {
156 | return null;
157 | }
158 | }
159 | function getTitles() {
160 | let isChinese = false;
161 | const chineseTitle = document.title.replace(/\(豆瓣\)$/, "").trim();
162 | const originalTitle =
163 | $("#content h1>span[property]").text().replace(chineseTitle, "").trim() ||
164 | ((isChinese = true), chineseTitle);
165 | try {
166 | let akaTitles = $('#info span.pl:contains("又名")')[0]
167 | .nextSibling.textContent.trim()
168 | .split(" / ");
169 | const transTitle = isChinese
170 | ? akaTitles.find((e) => {
171 | return e.match(/[a-z]/i);
172 | }) || chineseTitle
173 | : chineseTitle;
174 | const priority = (e) => {
175 | if (e === transTitle) {
176 | return 0;
177 | }
178 | if (e.match(/\(港.?台\)/)) {
179 | return 1;
180 | }
181 | if (e.match(/\([港台]\)/)) {
182 | return 2;
183 | }
184 | return 3;
185 | };
186 | akaTitles = akaTitles
187 | .sort((a, b) => priority(a) - priority(b))
188 | .filter((e) => e !== transTitle);
189 | return [
190 | {
191 | chineseTitle: chineseTitle,
192 | originalTitle: originalTitle,
193 | translatedTitle: transTitle,
194 | alsoKnownAsTitles: akaTitles,
195 | },
196 | isChinese,
197 | ];
198 | } catch (e) {
199 | return [
200 | {
201 | chineseTitle: chineseTitle,
202 | originalTitle: originalTitle,
203 | translatedTitle: chineseTitle,
204 | alsoKnownAsTitles: [],
205 | },
206 | isChinese,
207 | ];
208 | }
209 | }
210 | function getYear() {
211 | return parseInt($("#content>h1>span.year").text().slice(1, -1));
212 | }
213 | function getRegions() {
214 | try {
215 | return $('#info span.pl:contains("制片国家/地区")')[0]
216 | .nextSibling.textContent.trim()
217 | .split(" / ");
218 | } catch (e) {
219 | return [];
220 | }
221 | }
222 | function getGenres() {
223 | try {
224 | return $('#info span[property="v:genre"]')
225 | .toArray()
226 | .map((e) => e.innerText.trim());
227 | } catch (e) {
228 | return [];
229 | }
230 | }
231 | function getLanguages() {
232 | try {
233 | return $('#info span.pl:contains("语言")')[0]
234 | .nextSibling.textContent.trim()
235 | .split(" / ");
236 | } catch (e) {
237 | return [];
238 | }
239 | }
240 | function getReleaseDates() {
241 | try {
242 | return $('#info span[property="v:initialReleaseDate"]')
243 | .toArray()
244 | .map((e) => e.innerText.trim())
245 | .sort((a, b) => new Date(a) - new Date(b));
246 | } catch (e) {
247 | return [];
248 | }
249 | }
250 | function getDurations() {
251 | try {
252 | return $('span[property="v:runtime"]')
253 | .nextNodeUtil("br")
254 | .toArray()
255 | .map((e) => e.textContent)
256 | .join("")
257 | .trim()
258 | .split(" / ");
259 | } catch (e) {
260 | return [];
261 | }
262 | }
263 | function getEpisodeDuration() {
264 | try {
265 | return $(
266 | '#info span.pl:contains("单集片长")'
267 | )[0].nextSibling.textContent.trim();
268 | } catch (e) {
269 | return null;
270 | }
271 | }
272 | function getEpisodeCount() {
273 | try {
274 | return parseInt(
275 | $('#info span.pl:contains("集数")')[0].nextSibling.textContent.trim()
276 | );
277 | } catch (e) {
278 | return null;
279 | }
280 | }
281 | function getTags() {
282 | return $("div.tags-body>a")
283 | .toArray()
284 | .map((e) => e.textContent);
285 | }
286 | function getDoubanID() {
287 | return window.location.href.match(/subject\/(\d+)/)[1];
288 | }
289 | function getDoubanScore() {
290 | const $interest = $("#interest_sectl");
291 | const ratingAverage = parseFloat(
292 | $interest.find('[property="v:average"]').text()
293 | );
294 | const ratingVotes = parseInt($interest.find('[property="v:votes"]').text());
295 | const ratingHist = Object.fromEntries(
296 | $interest
297 | .find(".ratings-on-weight .rating_per")
298 | .toArray()
299 | .map((e, i) => [5 - i, parseFloat(e.textContent.slice(0, -1)) / 100])
300 | );
301 | return {
302 | rating: ratingAverage,
303 | ratingCount: ratingVotes,
304 | ratingHistograms: {
305 | "Douban Users": {
306 | aggregateRating: ratingAverage,
307 | demographic: "Douban Users",
308 | histogram: ratingHist,
309 | totalRatings: ratingVotes,
310 | },
311 | },
312 | };
313 | }
314 | function getDescription() {
315 | try {
316 | return Array.from(
317 | $('[id^="link-report"]>[property="v:summary"],[id^="link-report"]>span.all.hidden')[0]
318 | .childNodes
319 | )
320 | .filter((e) => e.nodeType === 3)
321 | .map((e) => e.textContent.trim())
322 | .join("\n");
323 | } catch (e) {
324 | return null;
325 | }
326 | }
327 | async function DoubanAPI(
328 | ID,
329 | apikeys = DoubanAPIKeys.slice(),
330 | timeout = TIMEOUT
331 | ) {
332 | if (ID) {
333 | const index = Math.floor(Math.random() * apikeys.length);
334 | const [apikey] = apikeys.splice(index, 1);
335 | return new Promise((resolve) => {
336 | GM_xmlhttpRequest({
337 | method: "GET",
338 | url: `https://api.douban.com/v2/movie/${ID}?apikey=${apikey}`,
339 | headers: {
340 | referrer: "http://api.douban.com/",
341 | },
342 | responseType: "json",
343 | timout: timeout,
344 | onload: (x) => {
345 | const e = x.response;
346 | if (e.code === 104 && apikeys.length > 0) {
347 | console.warn(e);
348 | resolve(DoubanAPI(ID, apikeys));
349 | } else if (e.code) {
350 | console.warn(e);
351 | resolve(null);
352 | } else {
353 | resolve(e);
354 | }
355 | },
356 | ontimeout: (e) => {
357 | console.warn(e);
358 | resolve(null);
359 | },
360 | onerror: (e) => {
361 | console.warn(e);
362 | resolve(null);
363 | },
364 | });
365 | });
366 | } else {
367 | return null;
368 | }
369 | }
370 | async function getURL_GM(url, headers, data) {
371 | return new Promise(resolve => GM.xmlHttpRequest({
372 | method: data ? 'POST' : 'GET',
373 | url: url,
374 | headers: headers,
375 | data: data,
376 | onload: function (response) {
377 | if (response.status >= 200 && response.status < 400) {
378 | resolve(response.responseText);
379 | } else {
380 | console.error(`Error getting ${url}:`, response.status, response.statusText, response.responseText);
381 | resolve();
382 | }
383 | },
384 | onerror: function (response) {
385 | console.error(`Error during GM.xmlHttpRequest to ${url}:`, response.statusText);
386 | resolve();
387 | }
388 | }));
389 | }
390 | async function getJSONP_GM(url, headers, post_data) {
391 | const data = await getURL_GM(url, headers, post_data);
392 | if (data) {
393 | const end = data.lastIndexOf(')');
394 | const [, json] = data.substring(0, end).split('(', 2);
395 | return JSON.parse(json);
396 | }
397 | }
398 | async function getIMDbID(timeout = TIMEOUT) {
399 | const imdb_text = [...document.querySelectorAll('#info > span.pl')].find(s => s.innerText.trim() == 'IMDb:');
400 | if (!imdb_text) {
401 | console.log('IMDb id not available');
402 | return;
403 | }
404 | const text_node = imdb_text.nextSibling.nextSibling;
405 | const id = text_node.textContent.trim();
406 | return id;
407 | }
408 | async function getIMDbScore(ID, timeout = TIMEOUT) {
409 | if (ID) {
410 | const imdb_url = `https://p.media-imdb.com/static-content/documents/v1/title/${ID}/ratings%3Fjsonp=imdb.rating.run:imdb.api.title.ratings/data.json`;
411 | let [imdb_data] = await Promise.all([getJSONP_GM(imdb_url)]);
412 | return imdb_data.resource;
413 | } else {
414 | return null;
415 | }
416 | }
417 | async function getAwards(ID, timeout = TIMEOUT) {
418 | if (ID) {
419 | const resp = await Promise.race([
420 | fetch(`https://movie.douban.com/subject/${ID}/awards`),
421 | new Promise((resolve) =>
422 | setTimeout(() => {
423 | resolve({
424 | ok: false,
425 | message: `fetch ${ID} douban awards time out`,
426 | });
427 | }, timeout)
428 | ),
429 | ]);
430 | if (resp.ok) {
431 | const htmlString = await resp.text();
432 | return $$(htmlString)
433 | .find("div.awards")
434 | .toArray()
435 | .map((e) => {
436 | const $title = $(e).find(".hd>h2");
437 | const $awards = $(e).find(".award");
438 | return {
439 | name: $title.find("a").text().trim(),
440 | year: parseInt($title.find(".year").text().match(/\d+/)[0]),
441 | awards: $awards.toArray().map((e) => ({
442 | name: $(e).find("li:first-of-type").text().trim(),
443 | people: $(e)
444 | .find("li:nth-of-type(2)")
445 | .text()
446 | .split("/")
447 | .map((e) => e.trim()),
448 | })),
449 | };
450 | });
451 | } else {
452 | return [];
453 | }
454 | } else {
455 | return [];
456 | }
457 | }
458 | async function getCelebrities(ID, timeout = TIMEOUT) {
459 | if (ID) {
460 | const fetchCeleb = await Promise.race([
461 | fetch(`https://movie.douban.com/subject/${ID}/celebrities`),
462 | new Promise((resolve) =>
463 | setTimeout(() => {
464 | resolve({
465 | ok: false,
466 | message: `fetch ${ID} douban celebrities time out`,
467 | });
468 | }, timeout)
469 | ),
470 | ]);
471 | const fetchAPI = DoubanAPI(ID);
472 | const respCeleb = await fetchCeleb;
473 | let jsonCeleb, jsonAPI;
474 | if (respCeleb.ok) {
475 | const htmlString = await respCeleb.text();
476 | const entries = $$(htmlString)
477 | .find("#celebrities>div.list-wrapper")
478 | .toArray()
479 | .map((e) => {
480 | const [positionChinese, positionForeign] = $(e)
481 | .find("h2")
482 | .text()
483 | .match(/([^ ]*)(?:$| )(.*)/)
484 | .slice(1, 3);
485 | const people = $(e)
486 | .find("li.celebrity")
487 | .toArray()
488 | .map((e) => {
489 | let [nameChinese, nameForeign] = $(e)
490 | .find(".info>.name")
491 | .text()
492 | .match(/([^ ]*)(?:$| )(.*)/)
493 | .slice(1, 3);
494 | if (!nameChinese.match(/[\u4E00-\u9FCC]/)) {
495 | nameForeign = nameChinese + " " + nameForeign;
496 | nameChinese = null;
497 | }
498 | const [roleChinese, roleForeign, character] = $(e)
499 | .find(".info>.role")
500 | .text()
501 | .match(/([^ ]*)(?:$| )([^(]*)(?:$| )(.*)/)
502 | .slice(1, 4);
503 | return {
504 | name: {
505 | chs: nameChinese,
506 | for: nameForeign,
507 | },
508 | role: {
509 | chs: roleChinese,
510 | for: roleForeign,
511 | },
512 | character: character.replace(/[()]/g, ""),
513 | };
514 | });
515 | return [
516 | positionForeign.toLowerCase(),
517 | {
518 | position: positionChinese,
519 | people: people,
520 | },
521 | ];
522 | });
523 | if (entries.length) {
524 | jsonCeleb = Object.fromEntries(entries);
525 | } else {
526 | jsonCeleb = null;
527 | }
528 | } else {
529 | jsonCeleb = null;
530 | }
531 | const respAPI = await fetchAPI;
532 | if (respAPI) {
533 | const splitName = (e, chsRole, forRole) => {
534 | let [nameChinese, nameForeign] = e
535 | .match(/([^ ]*)(?:$| )(.*)/)
536 | .slice(1, 3);
537 | if (!nameChinese.match(/[\u4E00-\u9FCC]/)) {
538 | nameForeign = nameChinese + " " + nameForeign;
539 | nameChinese = null;
540 | }
541 | return {
542 | name: {
543 | chs: nameChinese,
544 | for: nameForeign,
545 | },
546 | role: {
547 | chs: chsRole,
548 | for: forRole,
549 | },
550 | character: "",
551 | };
552 | };
553 | jsonAPI = {
554 | director: {
555 | position: "导演",
556 | people: (respAPI.attrs.director || []).map((e) =>
557 | splitName(e, "导演", "Director")
558 | ),
559 | },
560 | cast: {
561 | position: "演员",
562 | people: (respAPI.attrs.cast || []).map((e) =>
563 | splitName(e, "演员", "Actor/Actress")
564 | ),
565 | },
566 | writer: {
567 | position: "编剧",
568 | people: (respAPI.attrs.writer || []).map((e) =>
569 | splitName(e, "编剧", "Writer")
570 | ),
571 | },
572 | };
573 | } else {
574 | jsonAPI = null;
575 | }
576 | if (jsonCeleb === null) {
577 | return jsonAPI;
578 | } else if (jsonAPI === null) {
579 | return jsonCeleb;
580 | } else {
581 | ["director", "cast", "writer"].forEach((prop) => {
582 | if (jsonCeleb[prop]) {
583 | jsonAPI[prop].people.forEach((e) => {
584 | const flag = jsonCeleb[prop].people.filter((f) => {
585 | return f.name.for === e.name.for;
586 | });
587 | if (flag.length === 0) {
588 | jsonCeleb[prop].people.push(e);
589 | }
590 | });
591 | } else {
592 | jsonCeleb[prop] = jsonAPI[prop];
593 | }
594 | });
595 | return jsonCeleb;
596 | }
597 | } else {
598 | return null;
599 | }
600 | }
601 | // Mtime is HTTP only
602 | async function MtimeSearch(
603 | chineseTitle,
604 | year,
605 | timeout = TIMEOUT + 2000 //mtime search is slow?
606 | ) {
607 | return new Promise((resolve) => {
608 | GM_xmlhttpRequest({
609 | method: "GET",
610 | url: `https://front-gateway.mtime.cn/mtime-search/search/unionSearch?keyword=${encodeURIComponent(
611 | chineseTitle
612 | )}`,
613 | headers: {
614 | "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
615 | },
616 | responseType: "json",
617 | timeout: timeout,
618 | onload: (x) => {
619 | const e = x.response;
620 | try {
621 | resolve(
622 | e.data.movies.find((movie) => {
623 | const titles = movie.titleOthersCn;
624 | titles.unshift(movie.name, movie.nameEn);
625 | if (year !== movie.year && year !== movie.rYear) {
626 | return false;
627 | }
628 | if (titles.some((title) => chineseTitle.includes(title))) {
629 | return true;
630 | }
631 | return false;
632 | }) || null
633 | );
634 | } catch (e) {
635 | console.warn(e);
636 | resolve(null);
637 | }
638 | },
639 | ontimeout: (e) => {
640 | console.warn(e);
641 | resolve(null);
642 | },
643 | onerror: (e) => {
644 | console.warn(e);
645 | resolve(null);
646 | },
647 | });
648 | });
649 | }
650 | // ERROR 521 is handled. (generate cookies on the spot)
651 | async function getBehindTheScene(ID, timeout = TIMEOUT) {
652 | if (ID) {
653 | return new Promise((resolve) => {
654 | GM_xmlhttpRequest({
655 | method: "GET",
656 | url: `https://front-gateway.mtime.cn/library/movie/extendDetail.api?movieId=${ID}`,
657 | headers: {
658 | "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
659 | },
660 | responseType: "json",
661 | timeout: timeout,
662 | onload: (x) => {
663 | try {
664 | resolve(x.response.data.dataBankEntry);
665 | } catch (e) {
666 | console.warn(e);
667 | resolve(null);
668 | }
669 | },
670 | ontimeout: (e) => {
671 | console.warn(e);
672 | resolve(null);
673 | },
674 | onerror: (e) => {
675 | console.warn(e);
676 | resolve(null);
677 | },
678 | });
679 | });
680 | } else {
681 | return null;
682 | }
683 | }
684 | async function getInfo() {
685 | const [titles, isChinese] = getTitles(),
686 | year = getYear(),
687 | regions = getRegions(),
688 | genres = getGenres(),
689 | languages = getLanguages(),
690 | releaseDates = getReleaseDates(),
691 | durations = getDurations(),
692 | episodeDuration = getEpisodeDuration(),
693 | episodeCount = getEpisodeCount(),
694 | tags = getTags(),
695 | DoubanID = getDoubanID(),
696 | DoubanScore = getDoubanScore(),
697 | poster = getPoster(),
698 | description = getDescription();
699 |
700 | let IMDbID, IMDbScore, awards, celebrities, behindTheScene;
701 |
702 | const concurrentFetches = [];
703 |
704 | concurrentFetches.push(
705 | // IMDb Fetch
706 | getIMDbID()
707 | .then((e) => {
708 | IMDbID = e;
709 | return getIMDbScore(IMDbID);
710 | })
711 | .then((e) => {
712 | IMDbScore = e;
713 | }),
714 |
715 | // Awards Fetch
716 | getAwards(DoubanID).then((e) => {
717 | awards = e;
718 | }),
719 |
720 | // Celebrities Fetch
721 | getCelebrities(DoubanID).then((e) => {
722 | celebrities = e;
723 | }),
724 |
725 | // MTime
726 | // 更换时光网接口
727 | MtimeSearch(titles.chineseTitle, year)
728 | .then((e) => (e ? getBehindTheScene(e.movieId) : null))
729 | .then((e) => {
730 | behindTheScene = e;
731 | })
732 | );
733 |
734 | await Promise.all(concurrentFetches);
735 | // Amend Titles
736 | if (IMDbScore && IMDbScore.title) {
737 | if (isChinese) {
738 | if (!titles.translatedTitle.includes(IMDbScore.title)) {
739 | titles.alsoKnownAsTitles.push(titles.translatedTitle);
740 | const index = titles.alsoKnownAsTitles.indexOf(IMDbScore.title);
741 | if (index >= 0) {
742 | titles.alsoKnownAsTitles.splice(index, 1);
743 | }
744 | titles.translatedTitle = IMDbScore.title;
745 | }
746 | } else {
747 | if (
748 | !titles.originalTitle.includes(IMDbScore.title) &&
749 | titles.alsoKnownAsTitles.indexOf(IMDbScore.title) === -1
750 | ) {
751 | titles.alsoKnownAsTitles.push(IMDbScore.title);
752 | }
753 | }
754 | }
755 | return {
756 | poster: poster,
757 | titles: titles,
758 | year: year,
759 | regions: regions,
760 | genres: genres,
761 | languages: languages,
762 | releaseDates: releaseDates,
763 | durations: durations,
764 | episodeDuration: episodeDuration,
765 | episodeCount: episodeCount,
766 | tags: tags,
767 | DoubanID: DoubanID,
768 | DoubanScore: DoubanScore,
769 | IMDbID: IMDbID,
770 | IMDbScore: IMDbScore,
771 | awards: awards,
772 | celebrities: celebrities,
773 | description: description,
774 | behindTheScene: behindTheScene,
775 | };
776 | }
777 |
778 | function formatInfo(info) {
779 | let temp;
780 | const infoText = (
781 | (info.poster ? `[img]${info.poster}[/img]\n\n` : "") +
782 | "◎译 名 " +
783 | [info.titles.translatedTitle]
784 | .concat(info.titles.alsoKnownAsTitles)
785 | .join(" / ") +
786 | "\n" +
787 | "◎片 名 " +
788 | info.titles.originalTitle +
789 | "\n" +
790 | "◎年 代 " +
791 | info.year +
792 | "\n" +
793 | (info.regions.length
794 | ? "◎产 地 " + info.regions.join(" / ") + "\n"
795 | : "") +
796 | (info.genres.length
797 | ? "◎类 别 " + info.genres.join(" / ") + "\n"
798 | : "") +
799 | (info.languages.length
800 | ? "◎语 言 " + info.languages.join(" / ") + "\n"
801 | : "") +
802 | (info.releaseDates.length
803 | ? "◎上映日期 " + info.releaseDates.join(" / ") + "\n"
804 | : "") +
805 | (info.IMDbScore && info.IMDbScore.rating
806 | ? `◎IMDb评星 ${((temp = Math.round(info.IMDbScore.rating * 2)),
807 | "★".repeat(Math.floor(temp / 2)) +
808 | (temp % 2 === 1 ? "✦" : "") +
809 | "☆".repeat(10 - Math.ceil(temp / 2)))
810 | }\n◎IMDb评分 ${Number(info.IMDbScore.rating).toFixed(
811 | 1
812 | )}/10 from ${addComma(info.IMDbScore.ratingCount)} users\n`
813 | : "") +
814 | (info.IMDbID
815 | ? `◎IMDb链接 https://www.imdb.com/title/${info.IMDbID}/\n`
816 | : "") +
817 | (info.DoubanScore && info.DoubanScore.rating
818 | ? `◎豆瓣评星 ${((temp = Math.round(info.DoubanScore.rating)),
819 | "★".repeat(Math.floor(temp / 2)) +
820 | (temp % 2 === 1 ? "✦" : "") +
821 | "☆".repeat(5 - Math.ceil(temp / 2)))
822 | }\n◎豆瓣评分 ${Number(info.DoubanScore.rating).toFixed(
823 | 1
824 | )}/10 from ${addComma(info.DoubanScore.ratingCount)} users\n`
825 | : "") +
826 | (info.DoubanID
827 | ? `◎豆瓣链接 https://movie.douban.com/subject/${info.DoubanID}/\n`
828 | : "") +
829 | (info.durations && info.durations.length
830 | ? "◎片 长 " + info.durations.join(" / ") + "\n"
831 | : "") +
832 | (info.episodeDuration
833 | ? "◎单集片长 " + info.episodeDuration + "\n"
834 | : "") +
835 | (info.episodeCount ? "◎集 数 " + info.episodeCount + "\n" : "") +
836 | (info.celebrities
837 | ? Object.entries(info.celebrities)
838 | .map((e) => {
839 | const position = e[1].position;
840 | let title = "◎";
841 | switch (position.length) {
842 | case 1:
843 | title += " " + position + " ";
844 | break;
845 | case 2:
846 | title += position.split("").join(" ") + " ";
847 | break;
848 | case 3:
849 | title += position.split("").join(" ") + " ";
850 | break;
851 | case 4:
852 | title += position + " ";
853 | break;
854 | default:
855 | title += position + "\n ";
856 | }
857 | const people = e[1].people
858 | .map((f, i) => {
859 | const name = f.name.chs
860 | ? f.name.for
861 | ? f.name.chs + " / " + f.name.for
862 | : f.name.chs
863 | : f.name.for;
864 | return (
865 | (i > 0 ? " " : "") +
866 | name +
867 | (f.character ? ` | ${f.character}` : "")
868 | );
869 | })
870 | .join("\n");
871 | return title + people;
872 | })
873 | .join("\n") + "\n\n"
874 | : "") +
875 | (info.tags.length ? "◎标 签 " + info.tags.join(" | ") + "\n\n" : "") +
876 | (info.description
877 | ? "◎简 介 \n" + info.description.replace(/^|\n/g, "\n ") + "\n\n"
878 | : "") +
879 | (info.awards.length
880 | ? "◎获奖情况 \n\n" +
881 | info.awards
882 | .map((e) => {
883 | const awardName = " " + e.name + " (" + e.year + ")\n";
884 | const awardItems = e.awards
885 | .map((e) => " " + e.name + (e.people ? " " + e.people : ""))
886 | .join("\n");
887 | return awardName + awardItems;
888 | })
889 | .join("\n\n") +
890 | "\n\n"
891 | : "") +
892 | (info.behindTheScene
893 | ? (info.behindTheScene.classicLineList &&
894 | info.behindTheScene.classicLineList.length > 0
895 | ? "◎台词金句\n\n " +
896 | info.behindTheScene.classicLineList
897 | .map((e) => e.replace(/\r?\n/g, "\n "))
898 | .join("\n ") +
899 | "\n\n"
900 | : "") +
901 | (info.behindTheScene.behindTextList &&
902 | info.behindTheScene.behindTextList.length > 0
903 | ? "◎幕后揭秘\n\n " +
904 | info.behindTheScene.behindTextList
905 | .map((e) =>
906 | decodeEntities(e)
907 | .replace(/<.+?>/g, "")
908 | .replace(/ /g, "\n\n ")
909 | .trim()
910 | )
911 | .join("\n\n ") +
912 | "\n\n"
913 | : "")
914 | : "")
915 | ).trim();
916 | return infoText;
917 | }
918 |
919 | $("body").append($copyPaste);
920 | $("#info").append($toggle).append(" ").append($infoGen).append($message);
921 | $infoGen.state = 0;
922 | const infoGenClickEvent = async (e) => {
923 | switch ($infoGen.state) {
924 | case 0: //获取
925 | $infoGen.off("click");
926 | $infoGen.text("获取中...");
927 | $copyPaste.val(formatInfo(await getInfo()));
928 | $infoGen.state = 1;
929 | $infoGen.text("复制");
930 | $infoGen.on("click", infoGenClickEvent);
931 | break;
932 | case 1: //复制
933 | {
934 | const copyResult = jsCopy($copyPaste[0]);
935 | if (copyResult) {
936 | $message
937 | .css({ color: "green" })
938 | .text("复制成功")
939 | .fadeIn(200, () => $message.fadeOut(200));
940 | } else {
941 | $message
942 | .css({ color: "red" })
943 | .text("复制失败")
944 | .fadeIn(200, () => $message.fadeOut(200));
945 | }
946 | }
947 | break;
948 | }
949 | };
950 | $infoGen.on("click", infoGenClickEvent);
951 | })(window.$.noConflict(true));
952 |
--------------------------------------------------------------------------------