Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save zhaocai/3d0db6a39abde677fb6eaf829ef27b06 to your computer and use it in GitHub Desktop.

Select an option

Save zhaocai/3d0db6a39abde677fb6eaf829ef27b06 to your computer and use it in GitHub Desktop.
在影巢影视详情页缺少豆瓣信息时,通过 IMDb ID 或片名自动反查并无缝补全豆瓣链接与评分
// ==UserScript==
// @name 影巢影视详情页自动补全豆瓣信息
// @namespace zhaowu
// @version 1.2.3
// @description 在影巢影视详情页缺少豆瓣或IMDb信息时自动补全链接与评分,并在 IMDb 显示 0.0 异常时自动反查修正真实评分
// @author zhaowu
// @icon data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><path fill="%2300B51D" d="M1003.52 872.448c0 72.39-58.68 131.072-131.072 131.072H151.552c-72.39 0-131.072-58.68-131.072-131.072V151.552c0-72.39 58.68-131.072 131.072-131.072h720.896c72.39 0 131.072 58.68 131.072 131.072v720.896z"/><path fill="%23FFF" d="M822.6 184.32H201.4a11.5 11.5 0 0 0-11.5 11.5v46a11.5 11.5 0 0 0 11.5 11.5h621.2a11.5 11.5 0 0 0 11.5-11.5v-46a11.5 11.5 0 0 0-11.5-11.5zm0 586.33H663.84l47.46-150.1h58.89a11.5 11.5 0 0 0 11.5-11.5V330.41a11.5 11.5 0 0 0-11.5-11.5H253.82a11.5 11.5 0 0 0-11.5 11.5v278.64a11.5 11.5 0 0 0 11.5 11.5h364.18l-47.45 150.1H439.32l-34.8-110.05a11.5 11.5 0 0 0-15.14-11.5H319.06c-6.36 0-9.88 5.14-7.86 11.5l34.8 110.05H201.4a11.5 11.5 0 0 0-11.5 11.5v46a11.5 11.5 0 0 0 11.5 11.5h621.2a11.5 11.5 0 0 0 11.5-11.5v-46a11.5 11.5 0 0 0-11.5-11.5zM351.6 536.83V402.62a11.5 11.5 0 0 1 11.5-11.5h297.81a11.5 11.5 0 0 1 11.5 11.5v134.21a11.5 11.5 0 0 1-11.5 11.5H363.09a11.5 11.5 0 0 1-11.5-11.5z"/></svg>
// @downloadURL https://gist.githubusercontent.com/zhaocai/3d0db6a39abde677fb6eaf829ef27b06/raw/%E5%BD%B1%E5%B7%A2%E5%BD%B1%E8%A7%86%E8%AF%A6%E6%83%85%E9%A1%B5%E8%87%AA%E5%8A%A8%E8%A1%A5%E5%85%A8%E8%B1%86%E7%93%A3%E4%BF%A1%E6%81%AF.user.js
// @updateURL https://gist.githubusercontent.com/zhaocai/3d0db6a39abde677fb6eaf829ef27b06/raw/%E5%BD%B1%E5%B7%A2%E5%BD%B1%E8%A7%86%E8%AF%A6%E6%83%85%E9%A1%B5%E8%87%AA%E5%8A%A8%E8%A1%A5%E5%85%A8%E8%B1%86%E7%93%A3%E4%BF%A1%E6%81%AF.user.js
// @match https://re0.me/*
// @match https://hdhive.com/*
// @grant GM_xmlhttpRequest
// @grant GM_getValue
// @grant GM_setValue
// @connect douban.com
// @connect imdb.com
// @connect www.imdb.com
// @connect media-imdb.com
// @connect v3.sg.media-imdb.com
// @run-at document-idle
// ==/UserScript==
(function () {
'use strict';
const CACHE_KEY_PREFIX = 'hdhive_douban_';
const IMDB_CACHE_PREFIX = 'hdhive_imdb_fix_';
const IMDB_SEARCH_CACHE_PREFIX = 'hdhive_imdb_search_';
const DEFAULT_TTL = 7 * 24 * 60 * 60 * 1000;
const NEGATIVE_TTL = 2 * 24 * 60 * 60 * 1000; // 负缓存 2 天
const fetchingIds = new Set();
const fetchingImdbIds = new Set();
const fetchingImdbSearches = new Set();
function log(...args) {
console.log('[影巢评分增强]', ...args);
}
// 智能分级缓存计算:老片(上映年份 < 当年)缓存 90 天,新片 7 天
function calculateTTL(year) {
const currentYear = new Date().getFullYear();
const y = parseInt(year, 10);
if (!isNaN(y) && y < currentYear) {
return 90 * 24 * 60 * 60 * 1000; // 老片缓存 90 天
}
return DEFAULT_TTL; // 新片或当年电影缓存 7 天
}
// 跨脚本共享缓存工具(GM_getValue + localStorage 双写双读)
function getCache(key) {
let raw = null;
try { raw = GM_getValue(key, null); } catch (e) {}
if (!raw) {
try { raw = localStorage.getItem(key); } catch (e) {}
}
if (!raw) return null;
try {
const cached = JSON.parse(raw);
const ttl = cached.ttl || DEFAULT_TTL;
if (Date.now() - cached.timestamp < ttl) {
return cached.data;
}
} catch (e) {}
return null;
}
function setCache(key, data, ttl) {
const val = JSON.stringify({ timestamp: Date.now(), data, ttl: ttl || DEFAULT_TTL });
try { GM_setValue(key, val); } catch (e) {}
try { localStorage.setItem(key, val); } catch (e) {}
}
// 格式化投票人数,如 53186 -> 53.2k
function formatCount(rawCount) {
const clean = String(rawCount).replace(/,/g, '').trim();
const num = parseInt(clean, 10);
if (isNaN(num)) return rawCount;
if (num >= 1000) {
return (num / 1000).toFixed(1) + 'k';
}
return num.toString();
}
// 提取原始标题 / 外文原名
function extractOriginalTitle() {
const container = document.querySelector('[class*="MediaInfo"], [class*="detail"], [class*="intro"], header') || document.body;
const allElements = container.querySelectorAll('*');
for (const el of allElements) {
if (el.children.length <= 1) {
const text = el.textContent.trim();
const m = text.match(/^(?:原始标题|原名|Original\s*Title)[::]\s*(.+)$/i);
if (m && m[1]) return m[1].trim();
}
}
for (const el of allElements) {
if (el.textContent.includes('原始标题') || el.textContent.includes('原名')) {
const m = el.textContent.match(/(?:原始标题|原名)[::]\s*([^\n\r\t]+)/);
if (m && m[1]) return m[1].trim();
}
}
return '';
}
// 提取当前详情页关键容器与数据
function getPageInfo() {
// 查找评分栏容器
const container = document.querySelector(
'.MediaInfo_ratings__Ym3j4, [class*="MediaInfo_ratings"], [class*="ratings__"]'
);
if (!container) return null;
// 是否已有豆瓣链接或已补全
const hasDouban = !!container.querySelector(
'a[href*="douban.com"], [data-autofill-douban="true"]'
);
// 提取 IMDb 链接元素与 ID
const imdbLinkEl = container.querySelector('a[href*="imdb.com"]');
const autofillImdbEl = container.querySelector('[data-autofill-imdb="true"]');
const hasImdb = !!(imdbLinkEl || autofillImdbEl);
const imdbLink = imdbLinkEl?.href || autofillImdbEl?.href;
const imdbMatch = imdbLink?.match(/tt\d+/);
const imdbId = imdbMatch ? imdbMatch[0] : null;
// 提取标题与年份
const titleEl = document.querySelector('h1');
const rawTitle = titleEl ? titleEl.textContent.trim() : document.title;
const cleanTitle = rawTitle.replace(/\s*\(?\d{4}\)?.*$/, '').trim();
const yearMatch = rawTitle.match(/\b(19\d\d|20\d\d)\b/) ||
document.querySelector('[class*="meta"], [class*="year"]')?.textContent.match(/\b(19\d\d|20\d\d)\b/);
const year = yearMatch ? yearMatch[1] : '';
// 提取原始标题与影视类型 (是否为电视剧)
const originalTitle = extractOriginalTitle();
const isTv = location.pathname.includes('/tv/') || location.href.includes('/tv/');
return { container, imdbId, imdbLinkEl, cleanTitle, originalTitle, year, isTv, hasDouban, hasImdb };
}
// 从豆瓣搜索结果解析评分条目
function parseDoubanSearch(html) {
if (!html) return null;
const doc = new DOMParser().parseFromString(html, 'text/html');
// 查找影视类别下的第一个结果条目
const linkEl = doc.querySelector(
'.result .title a[href*="movie.douban.com%2Fsubject"], .result .title a[href*="subject"]'
);
if (!linkEl) return null;
let subjectUrl = linkEl.getAttribute('href') || '';
// 解码 link2 重定向地址
if (subjectUrl.includes('url=')) {
try {
const rawUrl = new URL(subjectUrl, 'https://www.douban.com').searchParams.get('url');
if (rawUrl) subjectUrl = rawUrl;
} catch (e) {
const m = subjectUrl.match(/url=([^&]+)/);
if (m) subjectUrl = decodeURIComponent(m[1]);
}
}
// 提取 subject id 并规范化 URL
const sidMatch = subjectUrl.match(/subject\/(\d+)/);
if (!sidMatch) return null;
const finalUrl = `https://movie.douban.com/subject/${sidMatch[1]}/`;
// 提取评分
const resultItem = linkEl.closest('.result');
const ratingEl = resultItem?.querySelector('.rating_nums');
let score = ratingEl ? ratingEl.textContent.trim() : '';
// 如果没有数字评分,从条目文本中提取豆瓣原状态(如:评价人数不足、尚未上映、暂无评分)
if (!score && resultItem) {
const rawText = resultItem.textContent;
const statusMatch = rawText.match(/\((评价人数不足|尚未上映|暂无评分|暂无)\)/);
if (statusMatch) {
score = statusMatch[1] === '暂无' ? '暂无评分' : statusMatch[1];
} else {
score = '暂无评分';
}
}
if (!score) score = '暂无评分';
// 提取评价人数
let countText = '';
if (resultItem) {
const countMatch = resultItem.textContent.match(/\(([\d,]+)\s*人评价\)/);
if (countMatch) {
countText = formatCount(countMatch[1]);
}
}
return {
url: finalUrl,
score,
count: countText
};
}
// 豆瓣官方搜索请求
function queryDouban(keyword) {
return new Promise((resolve, reject) => {
const searchUrl = `https://www.douban.com/search?cat=1002&q=${encodeURIComponent(keyword)}`;
GM_xmlhttpRequest({
method: 'GET',
url: searchUrl,
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
},
onload: (res) => {
if (res.status >= 200 && res.status < 300) {
const data = parseDoubanSearch(res.responseText);
resolve(data);
} else {
reject(new Error(`HTTP ${res.status}`));
}
},
onerror: reject,
ontimeout: () => reject(new Error('请求超时'))
});
});
}
// 创建原生同款视觉的豆瓣评分元素
function createDoubanElement(data, queryKey) {
const a = document.createElement('a');
a.target = '_blank';
a.rel = 'noopener noreferrer';
a.style.textDecoration = 'none';
let targetUrl = (data && data.url && typeof data.url === 'string' && data.url.startsWith('http')) ? data.url : '';
if (!targetUrl && queryKey) {
targetUrl = `https://www.douban.com/search?cat=1002&q=${encodeURIComponent(queryKey)}`;
}
a.href = targetUrl || 'https://movie.douban.com/';
a.setAttribute('data-autofill-douban', 'true');
a.title = '豆瓣评分 (脚本自动匹配)';
const itemDiv = document.createElement('div');
itemDiv.setAttribute('data-rating-item', '');
itemDiv.className = 'hdhive-un7kiq';
itemDiv.style.cssText = 'display:inline-flex;align-items:center;gap:6px;';
let displayScore = data.score;
if (!displayScore || displayScore === '暂无') {
displayScore = '暂无评分';
}
const isNumeric = /^\d+(\.\d+)?$/.test(displayScore);
const scoreText = isNumeric ? `${displayScore}/10` : displayScore;
const scoreStyle = isNumeric
? 'font-weight:700;color:#00b81e;font-family:-apple-system,BlinkMacSystemFont,\'Segoe UI\',Roboto,sans-serif;'
: 'font-weight:normal;color:#00b81e;font-size:13px;letter-spacing:0.2px;font-family:-apple-system,BlinkMacSystemFont,\'Segoe UI\',Roboto,sans-serif;';
if (isNumeric) {
a.title = `豆瓣评分: ${displayScore}/10${data.count ? ' (' + data.count + '人评价)' : ''} · 点击前往豆瓣详情页`;
} else {
a.title = `豆瓣状态: ${displayScore} · 点击前往豆瓣搜索`;
}
// 豆瓣官方矢量图标 SVG (拟物弧光径向微光)
itemDiv.innerHTML = `
<svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="20" height="20" data-rating-logo="douban" style="flex-shrink:0;">
<defs>
<radialGradient id="hdhive-db-glow" cx="50%" cy="20%" r="75%">
<stop offset="0%" stop-color="#009919"></stop>
<stop offset="65%" stop-color="#007013"></stop>
<stop offset="100%" stop-color="#004a0c"></stop>
</radialGradient>
</defs>
<path d="M1003.52 872.448c0 72.38997333-58.68202667 131.072-131.072 131.072H151.552c-72.38997333 0-131.072-58.68202667-131.072-131.072V151.552c0-72.38997333 58.68202667-131.072 131.072-131.072h720.896c72.38997333 0 131.072 58.68202667 131.072 131.072v720.896z" fill="url(#hdhive-db-glow)"></path>
<path d="M822.59694933 184.32H201.40305067a11.501568 11.501568 0 0 0-11.501568 11.501568v46.01173333c0 6.35153067 5.15003733 11.501568 11.501568 11.501568h621.19389866a11.501568 11.501568 0 0 0 11.501568-11.501568v-46.01173333A11.501568 11.501568 0 0 0 822.59694933 184.32zM822.59694933 770.654208h-158.75549866l47.45352533-150.09928533h58.88955733a11.501568 11.501568 0 0 0 11.501568-11.501568V330.41066667a11.49610667 11.49610667 0 0 0-11.501568-11.501568h-516.36906666a11.501568 11.501568 0 0 0-11.501568 11.501568v278.642688c0 6.35153067 5.144576 11.501568 11.501568 11.501568h364.183552l-47.45898667 150.09928533h-131.23584l-34.799616-110.051328c-2.00430933-6.356992-8.781824-11.501568-15.13335467-11.501568H319.05655467c-6.35153067 0-9.87409067 5.144576-7.85885867 11.501568l34.79415467 110.051328H201.40305067a11.501568 11.501568 0 0 0-11.501568 11.501568v46.01719467c0 6.35153067 5.15003733 11.501568 11.501568 11.501568h621.19389866a11.501568 11.501568 0 0 0 11.501568-11.501568v-46.01719467a11.501568 11.501568 0 0 0-11.501568-11.501568z m-471.007232-233.82152533V402.620416c0-6.35153067 5.144576-11.501568 11.501568-11.501568h297.811968c6.35153067 0 11.501568 5.15549867 11.501568 11.501568v134.21226667a11.501568 11.501568 0 0 1-11.501568 11.501568H363.09128533a11.501568 11.501568 0 0 1-11.501568-11.501568z" fill="#FFFFFF"></path>
</svg>
<span style="${scoreStyle}">${scoreText}</span>
${data.count ? `<span data-rating-count="" style="color:rgba(255,255,255,0.45);font-size:12px;margin-left:2px;">(${data.count})</span>` : ''}
`;
a.appendChild(itemDiv);
return a;
}
// IMDb 官方结构化数据抓取与解析
function queryIMDb(imdbId) {
return new Promise((resolve, reject) => {
const url = `https://www.imdb.com/title/${imdbId}/`;
GM_xmlhttpRequest({
method: 'GET',
url: url,
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Accept-Language': 'en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7'
},
onload: (res) => {
if (res.status >= 200 && res.status < 300) {
try {
const html = res.responseText;
// 1. 优先从 ld+json 解析
const ldMatch = html.match(/<script type="application\/ld\+json">([\s\S]*?)<\/script>/i);
if (ldMatch && ldMatch[1]) {
try {
const json = JSON.parse(ldMatch[1]);
const agg = json.aggregateRating;
if (agg && agg.ratingValue) {
resolve({
score: parseFloat(agg.ratingValue).toFixed(1),
count: agg.ratingCount ? formatCount(agg.ratingCount) : ''
});
return;
}
} catch (e) {
// json 解析失败,尝试正则兜底
}
}
// 2. 正则兜底
const scoreMatch = html.match(/"ratingValue":\s*"?([\d\.]+)"?/);
const countMatch = html.match(/"ratingCount":\s*(\d+)/);
if (scoreMatch && parseFloat(scoreMatch[1]) > 0) {
resolve({
score: parseFloat(scoreMatch[1]).toFixed(1),
count: countMatch ? formatCount(countMatch[1]) : ''
});
return;
}
resolve(null);
} catch (err) {
reject(err);
}
} else {
reject(new Error(`IMDb HTTP ${res.status}`));
}
},
onerror: reject,
ontimeout: () => reject(new Error('IMDb 请求超时'))
});
});
}
// IMDb 官方搜索建议 API
function searchImdbSuggestion(query) {
return new Promise((resolve) => {
const url = `https://v3.sg.media-imdb.com/suggestion/x/${encodeURIComponent(query)}.json`;
GM_xmlhttpRequest({
method: 'GET',
url: url,
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
},
onload: (res) => {
if (res.status >= 200 && res.status < 300) {
try {
const json = JSON.parse(res.responseText);
resolve(json.d || []);
} catch (e) {
resolve([]);
}
} else {
resolve([]);
}
},
onerror: () => resolve([]),
ontimeout: () => resolve([])
});
});
}
// 从候选条目中择优匹配最贴近的影视
function findBestImdbMatch(items, year, isTv) {
if (!items || items.length === 0) return null;
const y = parseInt(year, 10);
let candidates = items.filter(it => it.id && it.id.startsWith('tt') && (!it.qid || it.qid !== 'person'));
if (candidates.length === 0) return null;
// 若指定了类型,优先考虑类型匹配的候选者
if (typeof isTv === 'boolean') {
const typeMatched = candidates.filter(it => {
const isSeries = it.qid === 'tvSeries' || it.qid === 'tvMiniSeries' || (it.q && it.q.toLowerCase().includes('tv'));
return isTv ? isSeries : !isSeries;
});
if (typeMatched.length > 0) {
candidates = typeMatched;
}
}
if (!isNaN(y)) {
// 精确年份匹配
const exact = candidates.find(it => it.y === y);
if (exact) return exact;
// 差异 <= 1 年
const close1 = candidates.find(it => Math.abs(it.y - y) <= 1);
if (close1) return close1;
// 差异 <= 2 年
const close2 = candidates.find(it => Math.abs(it.y - y) <= 2);
if (close2) return close2;
}
return candidates[0];
}
// 搜索 IMDb ID (优先原始标题,回退中文标题)
async function searchImdbId(cleanTitle, originalTitle, year, isTv) {
let match = null;
if (originalTitle && originalTitle !== cleanTitle) {
const items = await searchImdbSuggestion(originalTitle);
match = findBestImdbMatch(items, year, isTv);
}
if (!match && cleanTitle) {
const items = await searchImdbSuggestion(cleanTitle);
match = findBestImdbMatch(items, year, isTv);
}
return match ? match.id : null;
}
// 创建原生同款视觉的 IMDb 评分元素
function createImdbElement(imdbId, data) {
const a = document.createElement('a');
a.target = '_blank';
a.rel = 'noopener noreferrer';
a.style.textDecoration = 'none';
a.href = `https://www.imdb.com/title/${imdbId}/`;
a.setAttribute('data-autofill-imdb', 'true');
const itemDiv = document.createElement('div');
itemDiv.setAttribute('data-rating-item', '');
itemDiv.className = 'hdhive-un7kiq';
itemDiv.style.cssText = 'display:inline-flex;align-items:center;gap:6px;';
const displayScore = data.score || '暂无评分';
const isNumeric = /^\d+(\.\d+)?$/.test(displayScore) && parseFloat(displayScore) > 0;
const scoreText = isNumeric ? `${displayScore}/10` : displayScore;
const scoreStyle = isNumeric
? 'font-weight:700;color:#e6a800;font-family:-apple-system,BlinkMacSystemFont,\'Segoe UI\',Roboto,sans-serif;'
: 'font-weight:normal;color:#e6a800;font-size:13px;letter-spacing:0.2px;font-family:-apple-system,BlinkMacSystemFont,\'Segoe UI\',Roboto,sans-serif;';
if (isNumeric) {
a.title = `IMDb 评分: ${displayScore}/10${data.count ? ' (' + data.count + '人评价)' : ''} · 点击前往 IMDb 详情页`;
} else {
a.title = `IMDb 状态: ${displayScore} · 点击前往 IMDb 详情页`;
}
// 原生同款 IMDb 官方矢量图标 (拟物弧光径向微光)
const imdbSvg = `<svg id="home_img" class="ipc-logo" xmlns="http://www.w3.org/2000/svg" width="40" height="20" viewBox="0 0 64 32" data-rating-logo="imdb" style="flex-shrink:0"><defs><radialGradient id="hdhive-imdb-glow" cx="50%" cy="20%" r="75%"><stop offset="0%" stop-color="#e6a800"></stop><stop offset="65%" stop-color="#ba8300"></stop><stop offset="100%" stop-color="#7a5400"></stop></radialGradient></defs><g fill="url(#hdhive-imdb-glow)"><rect x="0" y="0" width="100%" height="100%" rx="4"></rect></g><g transform="translate(8.000000, 7.000000)" fill="#000000" fill-rule="nonzero"><polygon points="0 18 5 18 5 0 0 0"></polygon><path d="M15.6725178,0 L14.5534833,8.40846934 L13.8582008,3.83502426 C13.65661,2.37009263 13.4632474,1.09175121 13.278113,0 L7,0 L7,18 L11.2416347,18 L11.2580911,6.11380679 L13.0436094,18 L16.0633571,18 L17.7583653,5.8517865 L17.7707076,18 L22,18 L22,0 L15.6725178,0 Z"></path><path d="M24,18 L24,0 L31.8045586,0 C33.5693522,0 35,1.41994415 35,3.17660424 L35,14.8233958 C35,16.5777858 33.5716617,18 31.8045586,18 L24,18 Z M29.8322479,3.2395236 C29.6339219,3.13233348 29.2545158,3.08072342 28.7026524,3.08072342 L28.7026524,14.8914865 C29.4312846,14.8914865 29.8796736,14.7604764 30.0478195,14.4865461 C30.2159654,14.2165858 30.3021941,13.486105 30.3021941,12.2871637 L30.3021941,5.3078959 C30.3021941,4.49404499 30.272014,3.97397442 30.2159654,3.74371416 C30.1599168,3.5134539 30.0348852,3.34671372 29.8322479,3.2395236 Z"></path><path d="M44.4299079,4.50685823 L44.749518,4.50685823 C46.5447098,4.50685823 48,5.91267586 48,7.64486762 L48,14.8619906 C48,16.5950653 46.5451816,18 44.749518,18 L44.4299079,18 C43.3314617,18 42.3602746,17.4736618 41.7718697,16.6682739 L41.4838962,17.7687785 L37,17.7687785 L37,0 L41.7843263,0 L41.7843263,5.78053556 C42.4024982,5.01015739 43.3551514,4.50685823 44.4299079,4.50685823 Z M43.4055679,13.2842155 L43.4055679,9.01907814 C43.4055679,8.31433946 43.3603268,7.85185468 43.2660746,7.63896485 C43.1718224,7.42607505 42.7955881,7.2893916 42.5316822,7.2893916 C42.267776,7.2893916 41.8607934,7.40047379 41.7816216,7.58767002 L41.7816216,9.01907814 L41.7816216,13.4207851 L41.7816216,14.8074788 C41.8721037,15.0130276 42.2602358,15.1274059 42.5316822,15.1274059 C42.8031285,15.1274059 43.1982131,15.0166981 43.281155,14.8074788 C43.3640968,14.5982595 43.4055679,14.0880581 43.4055679,13.2842155 Z"></path></g></svg>`;
itemDiv.innerHTML = `
${imdbSvg}
<span style="${scoreStyle}">${scoreText}</span>
${data.count ? `<span data-rating-count="" style="color:rgba(255,255,255,0.45);font-size:12px;margin-left:2px;">(${data.count})</span>` : ''}
`;
a.appendChild(itemDiv);
return a;
}
// 规范化插入 IMDb 评分元素(保持 TMDB -> IMDb -> 豆瓣 的原生排列顺序)
function insertImdbElement(container, imdbEl) {
if (!container || !imdbEl) return;
if (container.querySelector('a[href*="imdb.com"], [data-autofill-imdb="true"]')) return;
// 1. 若已有豆瓣元素,插入到豆瓣之前
const doubanEl = container.querySelector('a[href*="douban.com"], [data-autofill-douban="true"]');
if (doubanEl) {
doubanEl.insertAdjacentElement('beforebegin', imdbEl);
return;
}
// 2. 若有 TMDB 元素,插入到 TMDB 之后
const tmdbEl = container.querySelector('a[href*="themoviedb.org"], [data-rating-logo="tmdb"]')?.closest('a');
if (tmdbEl && tmdbEl.nextElementSibling) {
tmdbEl.insertAdjacentElement('afterend', imdbEl);
return;
}
// 3. 否则默认追加到末尾
container.appendChild(imdbEl);
}
// 渲染纠错后的 IMDb 评分与人数
function applyImdbFix(imdbLinkEl, data) {
if (!data || !data.score || parseFloat(data.score) === 0) return;
imdbLinkEl.setAttribute('data-imdb-fixed', 'true');
imdbLinkEl.title = `IMDb 官方真实评分: ${data.score}/10${data.count ? ' (' + data.count + '人评价)' : ''} (已自动纠错)`;
// 寻找分数 span(包含 /10 或纯数字)
const spans = Array.from(imdbLinkEl.querySelectorAll('span'));
const scoreSpan = spans.find(s => /\/10/.test(s.textContent) || /^\d+(\.\d+)?$/.test(s.textContent.trim()));
if (scoreSpan) {
scoreSpan.innerHTML = `${data.score}<!-- -->/10`;
}
// 寻找人数 span(带 data-rating-count 或者包含括号)
let countSpan = imdbLinkEl.querySelector('[data-rating-count]') || spans.find(s => /\(.*\)/.test(s.textContent));
if (data.count) {
if (countSpan) {
countSpan.innerHTML = `(<!-- -->${data.count}<!-- -->)`;
countSpan.style.display = '';
} else if (scoreSpan) {
const newCountSpan = document.createElement('span');
newCountSpan.setAttribute('data-rating-count', '');
newCountSpan.style.cssText = 'color:rgba(255,255,255,0.45);font-size:12px;margin-left:2px;';
newCountSpan.innerHTML = `(<!-- -->${data.count}<!-- -->)`;
scoreSpan.insertAdjacentElement('afterend', newCountSpan);
}
}
}
// 自动检测并修正原生 IMDb 0.0 异常
async function fixImdbScore(imdbLinkEl, imdbId, year) {
if (!imdbLinkEl || !imdbId) return;
if (imdbLinkEl.getAttribute('data-imdb-fixed') === 'true') return;
// 检查是否显示 0.0
const text = imdbLinkEl.textContent || '';
const hasZeroScore = /\b0(\.0)?\s*\/\s*10\b/.test(text) || /\b0\.0\b/.test(text);
if (!hasZeroScore) return;
// 检查本地缓存
const cachedData = getCache(IMDB_CACHE_PREFIX + imdbId);
if (cachedData) {
log('命中 IMDb 本地缓存,自动纠正评分:', imdbId, cachedData);
applyImdbFix(imdbLinkEl, cachedData);
return;
}
if (fetchingImdbIds.has(imdbId)) return;
fetchingImdbIds.add(imdbId);
log('检测到 IMDb 0.0 异常,开始反查官方真实评分:', imdbId);
try {
const data = await queryIMDb(imdbId);
if (data && parseFloat(data.score) > 0) {
log('IMDb 真实评分反查成功:', imdbId, data);
setCache(IMDB_CACHE_PREFIX + imdbId, data, calculateTTL(year));
applyImdbFix(imdbLinkEl, data);
} else {
log('IMDb 未查到有效评分或评分确为0:', imdbId);
setCache(IMDB_CACHE_PREFIX + imdbId, null, NEGATIVE_TTL);
}
} catch (err) {
log('反查 IMDb 评分出错:', err.message);
} finally {
fetchingImdbIds.delete(imdbId);
}
}
// 处理 IMDb 缺失时的自动反查与补全
async function processImdbFill(container, cleanTitle, originalTitle, year, isTv) {
const searchKey = `${cleanTitle}_${originalTitle || ''}_${year}_${isTv ? 'tv' : 'movie'}`;
if (fetchingImdbSearches.has(searchKey)) return;
// 检查搜索缓存
const cachedSearch = getCache(IMDB_SEARCH_CACHE_PREFIX + searchKey);
if (cachedSearch !== null) {
if (cachedSearch && cachedSearch.imdbId) {
if (!container.querySelector('a[href*="imdb.com"], [data-autofill-imdb="true"]')) {
log('命中 IMDb 补全缓存,直接渲染:', cachedSearch);
insertImdbElement(container, createImdbElement(cachedSearch.imdbId, cachedSearch));
}
}
return;
}
fetchingImdbSearches.add(searchKey);
log('页面缺少 IMDb,开始反查 IMDb ID:', { cleanTitle, originalTitle, year, isTv });
try {
const imdbId = await searchImdbId(cleanTitle, originalTitle, year, isTv);
if (imdbId) {
log('IMDb 反查匹配成功,获取到 ID:', imdbId);
// 查询详情评分 (先查单项缓存)
let ratingData = getCache(IMDB_CACHE_PREFIX + imdbId);
if (!ratingData) {
try {
ratingData = await queryIMDb(imdbId);
} catch (e) {
log('抓取 IMDb 详情评分失败:', e.message);
}
}
if (!ratingData || !ratingData.score) {
ratingData = { score: '暂无评分', count: '' };
}
// 写入缓存
const fullData = { imdbId, ...ratingData };
setCache(IMDB_CACHE_PREFIX + imdbId, ratingData, calculateTTL(year));
setCache(IMDB_SEARCH_CACHE_PREFIX + searchKey, fullData, calculateTTL(year));
insertImdbElement(container, createImdbElement(imdbId, ratingData));
} else {
log('未在 IMDb 检索到匹配条目:', cleanTitle);
setCache(IMDB_SEARCH_CACHE_PREFIX + searchKey, null, NEGATIVE_TTL);
}
} catch (err) {
log('IMDb 自动反查补全出错:', err.message);
} finally {
fetchingImdbSearches.delete(searchKey);
}
}
// 处理豆瓣自动补全
async function processDoubanFill(container, imdbId, cleanTitle, year) {
const queryKey = imdbId || cleanTitle;
if (!queryKey) return;
if (fetchingIds.has(queryKey)) return;
// 检查缓存
const cachedData = getCache(CACHE_KEY_PREFIX + queryKey);
if (cachedData !== null) {
if (cachedData) {
if (!container.querySelector('[data-autofill-douban="true"]')) {
log('命中豆瓣本地缓存,直接渲染评分:', queryKey, cachedData);
container.appendChild(createDoubanElement(cachedData, queryKey));
}
// 若旧缓存缺少完整 url 或缺少 count,后台静默重新拉取完整数据并更新 DOM 与缓存
if (!cachedData.url || !cachedData.url.startsWith('http') || !cachedData.count) {
log('检测到旧缓存缺少豆瓣详情页 URL 或人数,后台异步拉取补齐:', queryKey);
queryDouban(queryKey).then(freshData => {
if (freshData && freshData.score) {
setCache(CACHE_KEY_PREFIX + queryKey, freshData, calculateTTL(year));
const curA = container.querySelector('[data-autofill-douban="true"]');
if (curA) {
if (freshData.url) curA.href = freshData.url;
if (freshData.count) {
let countSpan = curA.querySelector('[data-rating-count]');
if (!countSpan) {
countSpan = document.createElement('span');
countSpan.setAttribute('data-rating-count', '');
countSpan.style.cssText = 'color:rgba(255,255,255,0.45);font-size:12px;margin-left:2px;';
curA.querySelector('.hdhive-un7kiq')?.appendChild(countSpan);
}
countSpan.textContent = `(${freshData.count})`;
}
const isNumeric = /^\d+(\.\d+)?$/.test(freshData.score);
curA.title = isNumeric
? `豆瓣评分: ${freshData.score}/10${freshData.count ? ' (' + freshData.count + '人评价)' : ''} · 点击前往豆瓣详情页`
: `豆瓣状态: ${freshData.score} · 点击前往豆瓣搜索`;
}
}
}).catch(() => {});
}
}
return;
}
fetchingIds.add(queryKey);
log('开始查询豆瓣评分,关键词:', queryKey);
try {
const data = await queryDouban(queryKey);
if (data) {
log('豆瓣查询成功,补全评分:', data);
setCache(CACHE_KEY_PREFIX + queryKey, data, calculateTTL(year));
if (!container.querySelector('[data-autofill-douban="true"]')) {
container.appendChild(createDoubanElement(data, queryKey));
}
} else {
log('未在豆瓣检索到对应条目:', queryKey);
// 负缓存 2 天,避免每次刷新重试
setCache(CACHE_KEY_PREFIX + queryKey, null, NEGATIVE_TTL);
}
} catch (err) {
log('查询豆瓣出错:', err.message);
} finally {
fetchingIds.delete(queryKey);
}
}
// 主补全与纠错入口
async function processPage() {
const info = getPageInfo();
if (!info) return;
const { container, imdbId, imdbLinkEl, cleanTitle, originalTitle, year, isTv, hasDouban, hasImdb } = info;
// 1. 若页面自带的 IMDb 为 0.0 异常,独立触发反查纠正
if (imdbLinkEl && imdbId) {
fixImdbScore(imdbLinkEl, imdbId, year);
} else if (!hasImdb) {
// 2. 若页面没有 IMDb,触发 IMDb 补全
processImdbFill(container, cleanTitle, originalTitle, year, isTv);
}
// 3. 若缺少豆瓣评分,执行豆瓣补全
if (!hasDouban) {
processDoubanFill(container, imdbId, cleanTitle, year);
}
}
// SPA 路由与 DOM 变化监控
let lastUrl = location.href;
const observer = new MutationObserver(() => {
if (location.href !== lastUrl) {
lastUrl = location.href;
setTimeout(processPage, 500);
} else {
processPage();
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
// 页面初次加载执行
setTimeout(processPage, 600);
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment