Greasy Fork is available in English.
在标题下方新起一行展示 {tmdbid=id},不影响原标题样式
// ==UserScript==
// @name gying TMDB 助手
// @namespace http://tampermonkey.net/
// @version 2.2
// @description 在标题下方新起一行展示 {tmdbid=id},不影响原标题样式
// @author Gemini
// @match *://www.gying.net/*
// @match *://www.gying.org/*
// @match *://*.xn--wcv59z.com/*
// @match *://*.xn--kivn76b41nnhi.com/*
// @match *://*.xn--rhqp87dfoiv9a830g.com/*
// @match *://*.xn--74qy8dk4drvg29x.com/*
// @match *://*.xn--10vr61a3xc5x3b.com/*
// @match *://*.xn--74qz10cqsltibh40akss.com/*
// @match *://*.xn--dpqv20e8ug6r8a.com/*
// @match *://*.xn--vcsx1ip8b8w4i.com/*
// @connect tmdb.org
// @connect api.tmdb.org
// @grant GM_xmlhttpRequest
// @grant GM_setClipboard
// @grant GM_setValue
// @grant GM_getValue
// @license MIT
// @run-at document-end
// ==/UserScript==
(function() {
'use strict';
const KEY_NAME = "TMDB_API_KEY_STORAGE";
const getApiKey = () => {
let key = GM_getValue(KEY_NAME);
if (!key) {
key = prompt("首次使用,请输入您的 TMDB API Key:");
if (key && key.length >= 30) { GM_setValue(KEY_NAME, key); } else { return null; }
}
return key;
};
const start = () => {
const apiKey = getApiKey();
if (!apiKey) return;
const h1 = document.querySelector('.article-header h1') || document.querySelector('h1');
// 清理标题:去除括号、第一个空格后的内容
const rawTitle = h1 ? h1.innerText.replace(/(\(|\[|\(|\【).*?(\)|\]|\)|\】)/g, '').trim().split(' ')[0] : "";
const pageText = document.body.innerText;
const yearMatch = pageText.match(/(19|20)\d{2}/);
const pageYear = yearMatch ? yearMatch[0] : null;
let imdbId = "";
const links = document.querySelectorAll('a[href*="imdb.com"]');
for (let a of links) {
const m = a.href.match(/tt\d+/);
if (m) { imdbId = m[0]; break; }
}
if (imdbId) {
fetchByImdb(imdbId, apiKey, rawTitle, pageYear);
} else if (rawTitle) {
fetchByTitle(rawTitle, pageYear, apiKey);
}
};
const fetchByTitle = (title, year, apiKey, secondTry = false) => {
const isMvPage = window.location.pathname.includes('/mv/');
const type = isMvPage ? 'movie' : 'tv';
const yearParam = isMvPage ? 'year' : 'first_air_date_year';
let searchUrl = `https://api.tmdb.org/3/search/${type}?api_key=${apiKey}&query=${encodeURIComponent(title)}&language=zh-CN&${year && !secondTry ? yearParam + '=' + year : ''}`;
GM_xmlhttpRequest({
method: "GET",
url: searchUrl,
onload: function(res) {
if (res.status === 200) {
const data = JSON.parse(res.responseText);
const results = data.results || [];
// 过滤出标题完全一致的结果
const exactMatches = results.filter(r => (r.title === title || r.name === title));
if (exactMatches.length === 1) {
// 唯一匹配:直接渲染
const bestItem = exactMatches[0];
renderTag(bestItem.title || bestItem.name, bestItem.release_date || bestItem.first_air_date, bestItem.id);
} else if (exactMatches.length > 1) {
// 多个完全匹配:展示橙色警告,人工处理
renderManualLink(title, `⚠️ 存在 ${exactMatches.length} 条同名结果,请点击手动核对`, "#faad14");
} else if (!secondTry && year) {
// 零个匹配且有年份:尝试去掉年份搜一次
fetchByTitle(title, null, apiKey, true);
} else {
// 零个匹配:展示红色警告
renderManualLink(title, `🔍 未能精准匹配,请点击手动搜索: "${title}"`, "#ff4d4f");
}
}
}
});
};
const renderManualLink = (title, label, bgColor) => {
if (document.getElementById('tmdb-copy-btn')) return;
const h1 = document.querySelector('.article-header h1') || document.querySelector('h1');
const container = document.createElement('div');
container.style.marginTop = '10px';
const link = document.createElement('a');
link.href = `https://www.themoviedb.org/search?query=${encodeURIComponent(title)}`;
link.target = "_blank";
link.innerText = label;
Object.assign(link.style, {
display: 'inline-block',
padding: '4px 12px',
backgroundColor: bgColor,
color: 'white',
borderRadius: '4px',
fontSize: '13px',
textDecoration: 'none',
fontWeight: 'bold',
boxShadow: '0 2px 5px rgba(0,0,0,0.1)'
});
container.appendChild(link);
h1.after(container);
};
// (fetchByImdb, fetchTvDetail, processFindData, renderTag 函数保持一致...)
const fetchByImdb = (imdbId, apiKey, rawTitle, pageYear) => {
const findUrl = `https://api.tmdb.org/3/find/${imdbId}?api_key=${apiKey}&external_source=imdb_id&language=zh-CN`;
GM_xmlhttpRequest({
method: "GET",
url: findUrl,
onload: function(res) {
const data = JSON.parse(res.responseText);
if (res.status === 200 && (data.movie_results?.length || data.tv_results?.length || data.tv_episode_results?.length)) {
processFindData(data, apiKey);
} else if (rawTitle) {
fetchByTitle(rawTitle, pageYear, apiKey);
}
}
});
};
const processFindData = (data, apiKey) => {
if (data.movie_results?.length > 0) {
const res = data.movie_results[0];
renderTag(res.title, res.release_date, res.id);
} else if (data.tv_results?.length > 0) {
const res = data.tv_results[0];
renderTag(res.name, res.first_air_date, res.id);
} else if (data.tv_episode_results?.length > 0) {
fetchTvDetail(data.tv_episode_results[0].show_id, apiKey);
}
};
const fetchTvDetail = (showId, apiKey) => {
const detailUrl = `https://api.tmdb.org/3/tv/${showId}?api_key=${apiKey}&language=zh-CN`;
GM_xmlhttpRequest({
method: "GET",
url: detailUrl,
onload: function(res) {
if (res.status === 200) {
const data = JSON.parse(res.responseText);
renderTag(data.name, data.first_air_date, data.id);
}
}
});
};
const renderTag = (name, date, id) => {
if (document.getElementById('tmdb-copy-btn')) return;
const year = date ? date.split('-')[0] : '未知';
const text = `${name} (${year}) {tmdbid=${id}}`;
const h1 = document.querySelector('.article-header h1') || document.querySelector('h1');
const container = document.createElement('div');
container.style.marginTop = '10px';
const btn = document.createElement('span');
btn.id = 'tmdb-copy-btn';
btn.innerText = `[ ${text} ]`;
Object.assign(btn.style, {
display: 'inline-block', padding: '4px 12px', background: 'rgba(1, 180, 228, 0.1)',
color: '#01b4e4', border: '1px solid #01b4e4', borderRadius: '4px',
fontSize: '14px', cursor: 'pointer', transition: 'all 0.2s'
});
btn.onclick = (e) => {
if (e.ctrlKey) {
const newKey = prompt("请输入新的 TMDB API Key:");
if (newKey) GM_setValue(KEY_NAME, newKey);
return;
}
GM_setClipboard(text);
const old = btn.innerText;
btn.innerText = "✅ 复制成功";
btn.style.backgroundColor = '#21d07a';
btn.style.color = 'white';
setTimeout(() => {
btn.innerText = old;
btn.style.backgroundColor = 'rgba(1, 180, 228, 0.1)';
btn.style.color = '#01b4e4';
}, 1000);
};
container.appendChild(btn);
h1.after(container);
};
setTimeout(start, 1000);
})();