Greasy Fork

来自缓存

Greasy Fork is available in English.

gying TMDB 助手

在标题下方新起一行展示 {tmdbid=id},不影响原标题样式

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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);
})();