Greasy Fork

TMDB 한국 지원 강화

TMDB 영화/TV 시리즈 페이지에 한국어, 영어, 원어 제목 추가, 개별 클립보드 복사 기능, 한국 시청등급 및 제작국 표시

目前为 2024-12-04 提交的版本。查看 最新版本

// ==UserScript==
// @name         TMDB 한국 지원 강화
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  TMDB 영화/TV 시리즈 페이지에 한국어, 영어, 원어 제목 추가, 개별 클립보드 복사 기능, 한국 시청등급 및 제작국 표시
// @match        https://www.themoviedb.org/*
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// ==/UserScript==

// 주의사항: 아래 YOUR_API_KEY 부분을 실제 TMDB API 키로 교체하는 것을 잊지 마세요.
const apiKey = "YOUR_API_KEY";

(function() {
    'use strict';

    GM_addStyle(`
        .additional-titles { font-size: 1.1em; line-height: 1.4; margin-bottom: 10px; }
        .additional-title { cursor: pointer; transition: color 0.3s; }
        .additional-title:hover { color: blue !important; }
        #additional-info { 
            margin-top: 10px; 
            clear: both; 
            display: flex;
            align-items: center;
            width: 100%;
        }
        #production-countries { 
            font-size: inherit; 
            margin-right: 20px;
        }
        #external-links { 
            font-size: inherit; 
        }
        #external-links a { 
            margin-right: 10px; 
            text-decoration: none; 
            color: inherit;
            transition: color 0.3s;
        }
        #external-links a:hover { 
            color: blue;
        }
    `);

    const copyToClipboard = text => {
        navigator.clipboard.writeText(text).then(() => {
            showTemporaryMessage(`${text} 클립보드에 복사됨`);
        });
    };

    const showTemporaryMessage = message => {
        const messageElement = document.createElement('div');
        Object.assign(messageElement.style, {
            position: 'fixed', top: '10px', left: '50%', transform: 'translateX(-50%)',
            backgroundColor: 'rgba(0, 0, 0, 0.7)', color: 'white', padding: '10px',
            borderRadius: '5px', zIndex: '9999'
        });
        messageElement.textContent = message;
        document.body.appendChild(messageElement);
        setTimeout(() => document.body.removeChild(messageElement), 1000);
    };

    const getIdAndType = () => {
        const [, type, id] = window.location.pathname.split('/');
        return { id, type };
    };

    const fetchData = async () => {
        const { id, type } = getIdAndType();
        if (!id || !type) return;

        const url = `https://api.themoviedb.org/3/${type}/${id}?api_key=${apiKey}&language=ko-KR&append_to_response=external_ids,release_dates,content_ratings`;

        try {
            const response = await new Promise((resolve, reject) => {
                GM_xmlhttpRequest({
                    method: "GET",
                    url: url,
                    onload: response => response.status === 200 ? resolve(JSON.parse(response.responseText)) : reject(`API 요청 실패: ${response.status}`),
                    onerror: reject
                });
            });

            const koTitle = response.title || response.name || '한국어 제목 미기재';
            const enTitle = response.original_title || response.original_name || '영어 제목 미기재';
            const originalTitle = response.original_title || response.original_name || '원어 제목 미기재';
            const koreanRating = getKoreanCertification(response, type);
            const productionCountries = getProductionCountries(response);
            const imdbId = response.imdb_id || response.external_ids?.imdb_id;
            const wikidataId = response.external_ids?.wikidata_id;
            const tvdbId = response.external_ids?.tvdb_id;

            displayTitles(koTitle, enTitle, originalTitle);
            displayKoreanRating(koreanRating);
            displayAdditionalInfo(productionCountries, koTitle, imdbId, wikidataId, tvdbId);
        } catch (error) {
            console.error('TMDB API 요청 오류:', error);
        }
    };

    const displayTitles = (koTitle, enTitle, originalTitle) => {
        const titleElement = document.querySelector('.title h2') || document.querySelector('.header .title h2');
        if (!titleElement) return;

        const titleContainer = document.createElement('div');
        titleContainer.className = 'additional-titles';
        
        titleContainer.innerHTML = `
            <span class="label">한제: </span><span class="additional-title ko-title">${koTitle}</span>   /   
            <span class="label">영제: </span><span class="additional-title en-title">${enTitle}</span>   /   
            <span class="label">원제: </span><span class="additional-title original-title">${originalTitle}</span>
        `;

        // 제목 컨테이너를 h2 요소 위에 삽입
        titleElement.parentNode.insertBefore(titleContainer, titleElement);

        ['ko-title', 'en-title', 'original-title'].forEach(className => {
            titleContainer.querySelector(`.${className}`).addEventListener('click', function() {
                copyToClipboard(this.textContent);
            });
        });
    };

    const getKoreanCertification = (data, type) => {
        const ratings = type === 'movie' ? data.release_dates?.results : data.content_ratings?.results;
        const koreanRating = ratings?.find(r => r.iso_3166_1 === 'KR')?.release_dates?.[0]?.certification || 
                             ratings?.find(r => r.iso_3166_1 === 'KR')?.rating;
        return koreanRating || '등급미정';
    };

    const getProductionCountries = (data) => {
        return data.production_countries?.map(country => country.name).join(', ') || '정보 없음';
    };

    const displayKoreanRating = rating => {
        const factsElement = document.querySelector('.facts');
        if (!factsElement) return;

        let koreanRatingElement = document.getElementById('korean-rating');
        if (!koreanRatingElement) {
            koreanRatingElement = Object.assign(document.createElement('span'), {
                id: 'korean-rating',
                style: 'font-size: 1em; margin-right: 10px; font-weight: bold;'
            });
            factsElement.insertBefore(koreanRatingElement, factsElement.firstChild);
        }
        
        koreanRatingElement.textContent = rating;
    };

    const displayAdditionalInfo = (countries, koTitle, imdbId, wikidataId, tvdbId) => {
        const factsElement = document.querySelector('.facts');
        
        let additionalInfoContainer = document.getElementById('additional-info');
        
        if (!additionalInfoContainer) {
            additionalInfoContainer = document.createElement('div');
            additionalInfoContainer.id = "additional-info";
            
            // facts 요소의 부모에 추가 정보 컨테이너를 삽입
            factsElement.parentNode.insertBefore(additionalInfoContainer, factsElement.nextSibling);
            
            additionalInfoContainer.innerHTML = `
                <div id="production-countries">• 제작국: ${countries}</div>
                <div id="external-links">
                    <a href="https://m.kinolights.com/search/contents?keyword=${encodeURIComponent(koTitle)}" target="_blank">키노</a>
                    ${imdbId ? `<a href="https://www.imdb.com/title/${imdbId}" target="_blank">IMDB</a>` : ''}
                    ${wikidataId ? `<a href="https://www.wikidata.org/wiki/${wikidataId}" target="_blank">Wikidata</a>` : ''}
                    ${tvdbId ? `<a href="https://www.thetvdb.com/dereferrer/series/${tvdbId}" target="_blank">TVDB</a>` : ''}
                </div>
            `;
            
           // 추가 정보 컨테이너를 아래쪽 행으로 이동
           additionalInfoContainer.style.clear = "both";
           additionalInfoContainer.style.display = "block";
           additionalInfoContainer.style.width= "100%";
           
           // 제작국 항목과 외부 링크 항목을 같은 줄에 표시하기 위해 flexbox 사용
           additionalInfoContainer.style.display= "flex";
           additionalInfoContainer.style.alignItems= "center";
           additionalInfoContainer.style.justifyContent= "flex-start";
           
           // 제작국 항목과 외부 링크 항목의 마진 설정
           additionalInfoContainer.querySelector('#production-countries').style.marginRight= "20px";
           
           // 외부 링크 항목의 스타일 설정
           additionalInfoContainer.querySelector('#external-links').style.fontSize= "inherit";
           
           // 링크 텍스트의 색상 설정
           additionalInfoContainer.querySelectorAll('#external-links a').forEach(link => {
               link.style.color= "#007BFF"; // 기본 색상 설정
               link.style.marginRight= "10px"; // 링크 간격 설정
               link.style.textDecoration= "none"; // 밑줄 제거
               link.addEventListener('mouseover', () => link.style.color= "blue"); // 마우스 오버 시 색상 변경
               link.addEventListener('mouseout', () => link.style.color= "#007BFF"); // 마우스 아웃 시 색상 복원
           });
       }
   };

   const init = () => {
       fetchData();
   };

   window.addEventListener('load', init);

   new MutationObserver(() => {
       const url = location.href;
       if (url !== lastUrl) {
           lastUrl = url;
           init();
       }
   }).observe(document, {subtree: true, childList: true});

   let lastUrl = location.href;
})();