您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Parse Douban Info
当前为
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greasyfork.icu/scripts/438042/1006083/Douban%20Info%20Class.js
// ==UserScript== // @name Douban Info Class // @description parse douban info // @version 0.0.28 // @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 timeout = 6000; constructor(id) { Object.defineProperties(this, { id: this.promisedGetterLazify(async () => { return id; }, "id"), subjectPathname: this.promisedGetterLazify( async () => { const subjectPathname = "/subject/" + (await this.id) + "/"; return subjectPathname; }, "subjectPathname", false ), awardPathname: this.promisedGetterLazify( async () => { const awardPathname = "/subject/" + (await this.id) + "/awards/"; return awardPathname; }, "awardPathname", false ), celebrityPathname: this.promisedGetterLazify( async () => { const celebrityPathname = "/subject/" + (await this.id) + "/celebrities/"; return celebrityPathname; }, "celebrityPathname", false ), subjectDoc: this.promisedGetterLazify( async () => { const currentURL = new URL(window.location.href); let doc; if ( currentURL.origin === DoubanInfo.origin && currentURL.pathname === (await this.subjectPathname) ) { doc = document; } else { doc = this.getDoc( new URL(await this.subjectPathname, DoubanInfo.origin).toString(), { headers: { referrer: DoubanInfo.origin, }, } ); } return doc; }, "subjectDoc", false ), awardDoc: this.promisedGetterLazify( async () => { const currentURL = new URL(window.location.href); let doc; if ( currentURL.origin === DoubanInfo.origin && currentURL.pathname === (await this.awardPathname) ) { doc = document; } else { doc = this.getDoc( new URL(await this.awardPathname, DoubanInfo.origin).toString(), { headers: { referrer: DoubanInfo.origin, }, } ); } return doc; }, "awardDoc", false ), celebrityDoc: this.promisedGetterLazify( async () => { const currentURL = new URL(window.location.href); let doc; if ( currentURL.origin === DoubanInfo.origin && currentURL.pathname === (await this.celebrityPathname) ) { doc = document; } else { doc = this.getDoc( new URL( await this.celebrityPathname, DoubanInfo.origin ).toString(), { headers: { referrer: DoubanInfo.origin, }, } ); } return doc; }, "celebrityDoc", false ), linkingData: this.promisedGetterLazify( async () => { const doc = await this.subjectDoc; const ld = dJSON.parse( heDecode( doc?.querySelector("head>script[type='application/ld+json']") ?.textContent ) ) || null; return ld; }, "linkingData", false ), type: this.promisedGetterLazify(async () => { const ld = await this.linkingData; const type = ld?.["@type"]?.toLowerCase() || null; return type; }, "type"), poster: this.promisedGetterLazify(async () => { const doc = await this.subjectDoc; const ld = await this.linkingData; const posterFromDoc = 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 = (posterFromDoc || posterFromMeta || posterFromLD) ?.replace("s_ratio_poster", "l_ratio_poster") .replace(/img\d+\.doubanio\.com/, "img9.doubanio.com") .replace(/\.webp$/i, ".jpg") || null; return poster; }, "poster"), title: this.promisedGetterLazify( async () => { const doc = await this.subjectDoc; const ld = await this.linkingData; const titleFromDoc = 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 = titleFromDoc || titleFromMeta || titleFromLD; return title; }, "title", false ), year: this.promisedGetterLazify(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; }, "year"), chineseTitle: this.promisedGetterLazify(async () => { const doc = await this.subjectDoc; const chineseTitle = doc?.title?.slice(0, -5); return chineseTitle; }, "chineseTitle"), originalTitle: this.promisedGetterLazify(async () => { let originalTitle; if (await this.isChinese) { originalTitle = await this.chineseTitle; } else { originalTitle = (await this.title) ?.replace(await this.chineseTitle, "") .trim(); } return originalTitle; }, "originalTitle"), aka: this.promisedGetterLazify(async () => { const doc = await this.subjectDoc; const priority = (t) => /\(港.?台\)/.test(t) ? 1 : /\((?:[港台]|香港|台湾)\)/.test(t) ? 2 : 3; let 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)) || []; if (aka.length === 0) { aka = null; } return aka; }, "aka"), isChinese: this.promisedGetterLazify( async () => { let isChinese = false; if ((await this.title) === (await this.chineseTitle)) { isChinese = true; } return isChinese; }, "isChinese", false ), region: this.promisedGetterLazify(async () => { const doc = await this.subjectDoc; let region = [...(doc?.querySelectorAll("body #info span.pl") || [])] .find((n) => n.textContent.includes("制片国家/地区")) ?.nextSibling?.textContent.split("/") .map((r) => r.trim()) || []; if (region.length === 0) { region = null; } return region; }, "region"), language: this.promisedGetterLazify(async () => { const doc = await this.subjectDoc; let language = [...(doc?.querySelectorAll("body #info span.pl") || [])] .find((n) => n.textContent.includes("语言")) ?.nextSibling?.textContent.split("/") .map((l) => l.trim()) || []; if (language.length === 0) { language = null; } return language; }, "language"), genre: this.promisedGetterLazify(async () => { const doc = await this.subjectDoc; const ld = await this.linkingData; let genreFromDoc = [ ...(doc?.querySelectorAll('body #info span[property="v:genre"]') || []), ].map((g) => g.textContent.trim()); if (genreFromDoc.length === 0) { genreFromDoc = null; } let genreFromLD = ld?.genre || []; if (genreFromLD.length === 0) { genreFromLD = null; } const genre = genreFromDoc || genreFromLD; return genre; }, "genre"), duration: this.promisedGetterLazify(async () => { const doc = await this.subjectDoc; const ld = await this.linkingData; const type = await this.type; let movieDurationFromDoc = null, episodeDurationFromDoc = 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 !== "") { movieDurationFromDoc = durationString .split("/") .map((str) => { str = str.trim(); const duration = parseInt(str || 0, 10) * 60 || null; const whereabouts = str.match(/(?<=\().+?(?=\)$)/)?.[0] || null; return { duration, whereabouts, }; }) .filter((d) => d.duration); if (movieDurationFromDoc.length === 0) { movieDurationFromDoc = null; } } } else if (type === "tvseries") { const episodeDurationSecondsFromDoc = parseInt( [...(doc?.querySelectorAll("body #info span.pl") || [])] .find((n) => n.textContent.includes("单集片长")) ?.nextSibling?.textContent.trim() || 0, 10 ) * 60 || null; if (episodeDurationSecondsFromDoc) { episodeDurationFromDoc = [ { duration: episodeDurationSecondsFromDoc, whereabouts: null, }, ]; } } let durationFromMeta = null; const durationSecondsFromMeta = parseInt( doc?.querySelector("head>meta[property='video:duration']") ?.content || 0, 10 ) || null; if (durationSecondsFromMeta) { durationFromMeta = [ { duration: durationSecondsFromMeta, whereabouts: null, }, ]; } let durationFromLD = null; const durationSecondsFromLD = parseInt( ld?.duration?.replace( /^PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?$/, (_, p1, p2, p3) => { return ( parseInt(p1 || 0, 10) * 3600 + parseInt(p2 || 0, 10) * 60 + parseInt(p3 || 0, 10) ).toString(); } ) || 0, 10 ) || null; if (durationSecondsFromLD) { durationFromLD = [ { duration: durationSecondsFromLD, whereabouts: null, }, ]; } const duration = movieDurationFromDoc || episodeDurationFromDoc || durationFromMeta || durationFromLD; return duration; }, "duration"), datePublished: this.promisedGetterLazify(async () => { const doc = await this.subjectDoc; const ld = await this.linkingData; let datePublishedFromDoc = [ ...(doc?.querySelectorAll( 'body #info span[property="v:initialReleaseDate"]' ) || []), ] .map((e) => ({ date: new Date(e.textContent.trim()), whereabouts: e.textContent.match(/(?<=\().+?(?=\)$)/)?.[0] || null, })) .sort((d1, d2) => { d1.date - d2.date; }); if (datePublishedFromDoc.length === 0) { datePublishedFromDoc = null; } const datePublishedStringFromLD = ld?.datePublished || null; let datePublishedFromLD = null; if (datePublishedStringFromLD) { datePublishedFromLD = [ { date: new Date(datePublishedStringFromLD), whereabouts: null }, ]; } const datePublished = datePublishedFromDoc || datePublishedFromLD; return datePublished; }, "datePublished"), episodeCount: this.promisedGetterLazify(async () => { if ((await this.type) === "tvseries") { const doc = await this.subjectDoc; const episodeCount = parseInt( [...(doc?.querySelectorAll("body #info span.pl") || [])] .find((n) => n.textContent.includes("集数")) ?.nextSibling?.textContent.trim() || 0, 10 ) || null; return episodeCount; } else { return null; } }, "episodeCount"), tag: this.promisedGetterLazify(async () => { const doc = await this.subjectDoc; let tag = [ ...(doc?.querySelectorAll("body div.tags-body>a") || []), ].map((t) => t.textContent); if (tag.length === 0) { tag = null; } return tag; }, "tag"), rating: this.promisedGetterLazify(async () => { const doc = await this.subjectDoc; let ratingFromDoc = null; let ratingCountFromDoc = parseInt( doc?.querySelector('body #interest_sectl [property="v:votes"]') ?.textContent || 0, 10 ) || null; let ratingValueFromDoc = parseFloat( doc?.querySelector('body #interest_sectl [property="v:average"]') ?.textContent || 0 ) || null; if (ratingCountFromDoc && ratingValueFromDoc) { ratingFromDoc = { ratingCount: ratingCountFromDoc, ratingValue: ratingValueFromDoc, bestRating: 10, }; } const ld = await this.linkingData; let ratingFromLD = null; let ratingCountFromLD = parseInt(ld?.aggregateRating?.ratingCount || 0, 10) || null; let ratingValueFromLD = parseFloat(ld?.aggregateRating?.ratingValue || 0) || null; if (ratingCountFromLD && ratingValueFromLD) { ratingFromLD = { ratingCount: ratingCountFromLD, ratingValue: ratingValueFromLD, bestRating: 10, }; } const rating = ratingFromDoc || ratingFromLD; return rating; }, "rating"), description: this.promisedGetterLazify(async () => { const doc = await this.subjectDoc; const ld = await this.linkingData; const descriptionFromDoc = [ ...(doc?.querySelector( 'body #link-report>[property="v:summary"],body #link-report>span.all.hidden' )?.childNodes || []), ] .filter((e) => e.nodeType === 3) .map((e) => e.textContent.trim()) .join("\n") || null; const descriptionFromMeta = doc?.querySelector("head>meta[property='og:description']")?.content || null; const descriptionFromLD = ld?.description || null; const description = descriptionFromDoc || descriptionFromMeta || descriptionFromLD; return description; }, "description"), imdbId: this.promisedGetterLazify(async () => { const doc = await this.subjectDoc; let imdbId = null; if ( doc?.querySelector("body #season option:checked")?.textContent !== "1" || false ) { const doubanId = doc.querySelector("body #season option:first-of-type")?.value || null; if (doubanId) { const firstSeasonDoubanInfo = new DoubanInfo(doubanId); imdbId = await firstSeasonDoubanInfo.imdbId; } } else { imdbId = [...(doc?.querySelectorAll("body #info span.pl") || [])] .find((n) => n.textContent.includes("IMDb:")) ?.nextSibling?.textContent.match(/tt(\d+)/)?.[1] || null; } return imdbId; }, "imdbId"), awardData: this.promisedGetterLazify(async () => { const doc = await this.awardDoc; let awardData = [...(doc?.querySelectorAll("div.awards") || [])] .map((awardNode) => { const event = awardNode?.querySelector(".hd>h2 a")?.textContent.trim() || null; const year = parseInt( awardNode ?.querySelector(".hd>h2 .year") ?.textContent.match(/\d+/)?.[0] || 0, 10 ) || null; let awards = [...(awardNode?.querySelectorAll(".award") || [])] .map((a) => { const award = a.querySelector("li:first-of-type")?.textContent.trim() || null; let recipient = a .querySelector("li:nth-of-type(2)") ?.textContent.split("/") .map((p) => p.trim()); if (recipient.length === 0) { recipient = null; } if (award) { return { award, recipient, }; } else { return null; } }) .filter((a) => !!a); if (awards.length === 0) { awards = null; } if (event) { return { event, year, awards, }; } else { return null; } }) .filter((a) => !!a); if (awardData.length === 0) { awardData = null; } return awardData; }, "awardData"), celebrityData: this.promisedGetterLazify(async () => {}, "celebrityData"), }); } get info() { return (async () => { let info = {}; for (let key in this) { info[key] = await this[key]; } return info; })(); } promisedGetterLazify(fun, propertyName, isEnumarable = true) { return { configurable: true, enumerable: isEnumarable, get: function () { Object.defineProperty(this, propertyName, { writable: false, enumerable: isEnumarable, value: fun(), }); return this[propertyName]; }, }; } getDoc(url, { headers = {} } = {}) { doc = new Promise(async (resolve) => { GM_xmlhttpRequest({ method: "GET", url: url, headers: headers, 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); }, }); }); } }