您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Parse Douban Info
当前为
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greasyfork.icu/scripts/438042/1005555/Douban%20Info%20Class.js
// ==UserScript== // @name Douban Info Class // @description parse douban info // @version 0.0.20 // @author Secant(TYT@NexusHD) // @icon https://movie.douban.com/favicon.ico // @contributionURL https://i.loli.net/2020/02/28/JPGgHc3UMwXedhv.jpg // @contributionAmount 10 // @namespace https://greasyfork.org/users/152136 // @grant GM_xmlhttpRequest // @connect movie.douban.com class DoubanInfo { static origin = "https://movie.douban.com"; static subjectPathName = "/subject/"; static timeout = 6000; constructor(id) { Object.defineProperties(this, { id: { configurable: true, enumerable: true, get: function () { Object.defineProperty(this, "id", { writable: false, enumerable: true, value: (async () => { return id; })(), }); return this.id; }, }, pathname: { configurable: true, get: function () { Object.defineProperty(this, "pathname", { writable: false, value: (async () => { const pathname = DoubanInfo.subjectPathName + (await this.id) + "/"; return pathname; })(), }); return this.pathname; }, }, subjectDoc: { configurable: true, get: function () { Object.defineProperty(this, "subjectDoc", { writable: false, value: (async () => { const currentURL = new URL(window.location.href); let doc; if ( currentURL.origin === DoubanInfo.origin && currentURL.pathname === (await this.pathname) ) { doc = document; } else { doc = new Promise(async (resolve) => { GM_xmlhttpRequest({ method: "GET", url: new URL( await this.pathname, DoubanInfo.origin ).toString(), headers: { referrer: DoubanInfo.origin, }, timout: DoubanInfo.timeout, onload: (resp) => { try { resolve( new DOMParser().parseFromString( resp.responseText, "text/html" ) ); } catch (err) { console.warn(err); resolve(null); } }, ontimeout: (e) => { console.warn(e); resolve(null); }, onerror: (e) => { console.warn(e); resolve(null); }, }); }); } return doc; })(), }); return this.subjectDoc; }, }, linkingData: { configurable: true, get: function () { Object.defineProperty(this, "linkingData", { writable: false, value: (async () => { const doc = await this.subjectDoc; const ld = dJSON.parse( heDecode( doc?.querySelector( "head>script[type='application/ld+json']" )?.textContent ) ) || null; return ld; })(), }); return this.linkingData; }, }, type: { configurable: true, enumerable: true, get: function () { Object.defineProperty(this, "type", { writable: false, enumerable: true, value: (async () => { const ld = await this.linkingData; const type = ld?.["@type"]?.toLowerCase() || null; return type; })(), }); return this.type; }, }, poster: { configurable: true, enumerable: true, get: function () { Object.defineProperty(this, "poster", { writable: false, enumerable: true, value: (async () => { const doc = await this.subjectDoc; const ld = await this.linkingData; const posterFromPage = doc?.querySelector("body #mainpic img")?.src || null; const posterFromMeta = doc?.querySelector("head>meta[property='og:image']")?.content || null; const posterFromLD = ld?.image || null; const poster = (posterFromPage || posterFromMeta || posterFromLD) ?.replace("s_ratio_poster", "l_ratio_poster") .replace(/img\d+\.doubanio\.com/, "img9.doubanio.com") .replace(/\.webp$/i, ".jpg") || null; return poster; })(), }); return this.poster; }, }, title: { configurable: true, get: function () { Object.defineProperty(this, "title", { writable: false, value: (async () => { const doc = await this.subjectDoc; const ld = await this.linkingData; const titleFromPage = doc?.querySelector("body #content h1>span[property]") ?.textContent || null; const titleFromMeta = doc?.querySelector("head>meta[property='og:title']")?.content || null; const titleFromLD = ld?.name || null; const title = titleFromPage || titleFromMeta || titleFromLD; return title; })(), }); return this.title; }, }, year: { configurable: true, enumerable: true, get: function () { Object.defineProperty(this, "year", { writable: false, enumerable: true, value: (async () => { const doc = await this.subjectDoc; const year = parseInt( doc ?.querySelector("body #content>h1>span.year") ?.textContent.slice(1, -1) || 0, 10 ) || null; return year; })(), }); return this.year; }, }, chineseTitle: { configurable: true, enumerable: true, get: function () { Object.defineProperty(this, "chineseTitle", { writable: false, enumerable: true, value: (async () => { const doc = await this.subjectDoc; const chineseTitle = doc?.title?.slice(0, -5); return chineseTitle; })(), }); return this.chineseTitle; }, }, originalTitle: { configurable: true, enumerable: true, get: function () { Object.defineProperty(this, "originalTitle", { writable: false, enumerable: true, value: (async () => { let originalTitle; if (await this.isChinese) { originalTitle = await this.chineseTitle; } else { originalTitle = (await this.title) ?.replace(await this.chineseTitle, "") .trim(); } return originalTitle; })(), }); return this.originalTitle; }, }, aka: { configurable: true, enumerable: true, get: function () { Object.defineProperty(this, "aka", { writable: false, enumerable: true, value: (async () => { const doc = await this.subjectDoc; const priority = (t) => /\(港.?台\)/.test(t) ? 1 : /\([港台]\)/.test(t) ? 2 : 3; const aka = [...(doc?.querySelectorAll("body #info span.pl") || [])] .find((n) => n.textContent.includes("又名")) ?.nextSibling?.textContent.split("/") .map((t) => t.trim()) .sort((t1, t2) => priority(t1) - priority(t2)) || []; return aka; })(), }); return this.aka; }, }, isChinese: { configurable: true, get: function () { Object.defineProperty(this, "isChinese", { writable: false, value: (async () => { let isChinese = false; if ((await this.title) === (await this.chineseTitle)) { isChinese = true; } return isChinese; })(), }); return this.isChinese; }, }, region: { configurable: true, enumerable: true, get: function () { Object.defineProperty(this, "region", { writable: false, enumerable: true, value: (async () => { const doc = await this.subjectDoc; const region = [...(doc?.querySelectorAll("body #info span.pl") || [])] .find((n) => n.textContent.includes("制片国家/地区")) ?.nextSibling?.textContent.split("/") .map((r) => r.trim()) || []; return region; })(), }); return this.region; }, }, language: { configurable: true, enumerable: true, get: function () { Object.defineProperty(this, "language", { writable: false, enumerable: true, value: (async () => { const doc = await this.subjectDoc; const language = [...(doc?.querySelectorAll("body #info span.pl") || [])] .find((n) => n.textContent.includes("语言")) ?.nextSibling?.textContent.split("/") .map((l) => l.trim()) || []; return language; })(), }); return this.language; }, }, duration: { configurable: true, enumerable: true, get: function () { Object.defineProperty(this, "duration", { writable: false, enumerable: true, value: (async () => { const doc = await this.subjectDoc; const ld = await this.linkingData; const type = await this.type; let movieDurationFromPage = null, episodeDurationFromPage = null; if (type === "movie") { let durationString = ""; let node = doc?.querySelector('body span[property="v:runtime"]') || null; while (node && node.nodeName !== "BR") { durationString += node.textContent; node = node.nextSibling; } if (durationString === "") { movieDurationFromPage = null; } else { movieDurationFromPage = durationString .split("/") .map((str) => { str = str.trim(); const duration = parseInt(str || 0, 10) * 60 || null; const type = str.match(/(?<=\().+?(?=\)$)/)?.[0] || null; return { duration, type, }; }) .filter((d) => d.duration); } } else if (type === "tvseries") { episodeDurationFromPage = parseInt( [...(doc?.querySelectorAll("body #info span.pl") || [])] .find((n) => n.textContent.includes("单集片长")) ?.nextSibling?.textContent.trim() || 0, 10 ) * 60 || null; } const durationFromMeta = parseInt( doc?.querySelector("head>meta[property='video:duration']") ?.content || 0, 10 ) || null; const durationFromLD = ld?.duration?.replace( /^PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?$/, (_, p1, p2, p3) => { return ( parseInt(p1, 10) * 3600 + parseInt(p2, 10) * 60 + parseInt(p3, 10) ).toString(); } ) || null; const duration = movieDurationFromPage ? movieDurationFromPage : episodeDurationFromPage ? [ { duration: episodeDurationFromPage, type: null, }, ] : durationFromMeta ? [{ duration: durationFromMeta, type: null }] : durationFromLD ? [{ duration: durationFromLD, type: null }] : []; return duration; })(), }); return this.duration; }, }, genre: { configurable: true, enumerable: true, get: function () { Object.defineProperty(this, "genre", { writable: false, enumerable: true, value: (async () => { const doc = await this.subjectDoc; const ld = await this.linkingData; const genreFromPage = [ ...(doc?.querySelectorAll( 'body #info span[property="v:genre"]' ) || []), ].map((g) => g.textContent.trim()); const genreFromLD = ld?.genre || []; const genre = genreFromPage || genreFromLD; return genre; })(), }); return this.genre; }, }, }); } get info() { return (async () => { let info = {}; for (let key in this) { info[key] = await this[key]; } return info; })(); } }