// ==UserScript==
// @name 이걸로 내 소원 사이트 찾으셈
// @namespace http://tampermonkey.net/
// @version 3.8
// @description 다양한 검색 엔진의 결과를 크롤링하여 데이터를 수집하고 표시합니다. 다크 모드 지원, 에러 핸들링 강화, 크롤링 대상 필터링 옵션, 다양한 검색 엔진 지원 등 고도화된 기능을 포함합니다.
// @author YourName
// @match ://www.google.com/
// @match ://www.bing.com/
// @match ://duckduckgo.com/
// @match ://search.yahoo.com/
// @match ://www.baidu.com/
// @match ://www.ecosia.org/
// @match ://www.yandex.com/
// @match ://ask.com/
// @match ://www.qwant.com/
// @match ://뉴토끼.site/
// @match ://lettercounter.net/
// @match ://tv.54.wiKi/
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_addValueChangeListener
// @grant GM_xmlhttpRequest
// @connect *
// @run-at document-end
// ==/UserScript==

(function() {
'use strict';


// 지원하는 검색 엔진 설정
const searchEngines = {
    'www.google.com': {
        name: 'Google',
        resultSelector: 'div.g',
        titleSelector: 'h3',
        linkSelector: 'a',
        descriptionSelector: 'span.aCOpRe, .VwiC3b',
        nextButtonSelector: '#pnnext',
        queryParam: 'q',
        additionalParams: 'num=100&newwindow=1&tbm=tkp&tbs=' // tbm=tkp for Knowledge panel 등을 포함
    },
    'www.bing.com': {
        name: 'Bing',
        resultSelector: 'li.b_algo',
        titleSelector: 'h2',
        linkSelector: 'a',
        descriptionSelector: 'p',
        nextButtonSelector: 'a.sb_pagN',
        queryParam: 'q',
        additionalParams: 'count=100&setlang=ko-kr&tbs='
    },
    'duckduckgo.com': {
        name: 'DuckDuckGo',
        resultSelector: 'div.result',
        titleSelector: 'h2 > a.result__a',
        linkSelector: 'a.result__a',
        descriptionSelector: '.result__snippet',
        nextButtonSelector: 'a.result--more__btn',
        queryParam: 'q',
        additionalParams: 'kl=ko-ko&tbs='
    },
    // 다른 검색 엔진도 동일하게 설정
    'search.yahoo.com': {
        name: 'Yahoo',
        resultSelector: 'div.dd.algo',
        titleSelector: 'h3.title',
        linkSelector: 'a',
        descriptionSelector: 'div.compText a.abstr',
        nextButtonSelector: 'a.next',
        queryParam: 'p',
        additionalParams: 'n=100&newwindow=1&tbs='
    },
    'www.baidu.com': {
        name: 'Baidu',
        resultSelector: 'div.result',
        titleSelector: 'h3 a',
        linkSelector: 'h3 a',
        descriptionSelector: 'div.c-abstract',
        nextButtonSelector: 'a.n',
        queryParam: 'wd',
        additionalParams: 'rn=100&tbs='
    },
    'www.ecosia.org': {
        name: 'Ecosia',
        resultSelector: 'div.result',
        titleSelector: 'h2 a',
        linkSelector: 'h2 a',
        descriptionSelector: 'div.abstract',
        nextButtonSelector: 'a.next',
        queryParam: 'q',
        additionalParams: 'num=100&newwindow=1&tbs='
    },
    'www.yandex.com': {
        name: 'Yandex',
        resultSelector: 'li.serp-item',
        titleSelector: 'h2 a',
        linkSelector: 'h2 a',
        descriptionSelector: 'div.text-container',
        nextButtonSelector: 'a.Link_theme_outer',
        queryParam: 'text',
        additionalParams: 'num=100&tbs='
    },
    'ask.com': {
        name: 'Ask',
        resultSelector: 'div.js-results-list div.Result',
        titleSelector: 'h2 a',
        linkSelector: 'h2 a',
        descriptionSelector: 'p.partial',
        nextButtonSelector: 'a.Next',
        queryParam: 'q',
        additionalParams: 'count=100&newwindow=1&tbs='
    },
    'www.qwant.com': {
        name: 'Qwant',
        resultSelector: 'div.results div.result',
        titleSelector: 'h3 a',
        linkSelector: 'h3 a',
        descriptionSelector: 'div.result__desc',
        nextButtonSelector: 'a.pagination__btn.pagination__btn--next',
        queryParam: 'q',
        additionalParams: 'count=100&newwindow=1&tbs='
    },
    '뉴토끼.site': { // 추가된 검색 엔진 설정
        name: '뉴토끼',
        resultSelector: '.search-result',
        titleSelector: '.result-title > a',
        linkSelector: '.result-title > a',
        descriptionSelector: '.result-description',
        nextButtonSelector: '.pagination-next',
        queryParam: 'q',
        additionalParams: 'num=100&newwindow=1&tbs='
    },
    'lettercounter.net': { // 추가된 검색 엔진 설정
        name: 'LetterCounter',
        resultSelector: '.search-results .result-item',
        titleSelector: '.result-title > a',
        linkSelector: '.result-title > a',
        descriptionSelector: '.result-snippet',
        nextButtonSelector: '.pagination-next',
        queryParam: 'q',
        additionalParams: 'num=100&newwindow=1&tbs='
    },
    'tv.54.wiKi': { // 추가된 TV 사이트 설정
        name: 'TV54',
        resultSelector: '.tv-search-result',
        titleSelector: '.tv-result-title > a',
        linkSelector: '.tv-result-title > a',
        descriptionSelector: '.tv-result-description',
        nextButtonSelector: '.tv-pagination-next',
        queryParam: 'q',
        additionalParams: 'num=100&newwindow=1&tbs='
    },
    'startpage.com': {
        name: 'Startpage',
        resultSelector: 'div.search_result',
        titleSelector: 'h3 a',
        linkSelector: 'h3 a',
        descriptionSelector: 'div.caption',
        nextButtonSelector: 'a.next',
        queryParam: 'query',
        additionalParams: 'num=100&newwindow=1&tbs='
    },
    'www.wolframalpha.com': {
        name: 'WolframAlpha',
        resultSelector: 'div.search-result',
        titleSelector: 'h2 a',
        linkSelector: 'h2 a',
        descriptionSelector: 'div.snippet',
        nextButtonSelector: 'a.next',
        queryParam: 'i',
        additionalParams: 'num=100&newwindow=1&tbs='
    }
};

// 현재 검색 엔진 감지
const currentHost = window.location.host;
const engine = searchEngines[currentHost];
if (!engine) {
    console.log('지원되지 않는 검색 엔진입니다.');
    return;
}

// 초기화 크롤링 상태 (기존 값이 없을 경우에만 설정)
if (GM_getValue('isCrawling') === undefined) {
    GM_setValue('isCrawling', false);
}
if (GM_getValue('crawlData') === undefined) {
    GM_setValue('crawlData', JSON.stringify([]));
}
if (GM_getValue('currentPage') === undefined) {
    GM_setValue('currentPage', 1);
}
// 크롤링 속도 고정
const fixedCrawlDelay = 100; // 100ms
if (GM_getValue('logData') === undefined) {
    GM_setValue('logData', JSON.stringify([]));
}
if (GM_getValue('tvPage') === undefined) {
    GM_setValue('tvPage', 54); // 초기 TV 페이지 번호 설정
}
if (GM_getValue('darkMode') === undefined) {
    GM_setValue('darkMode', false); // 다크 모드 초기값 설정
}
if (GM_getValue('currentFilter') === undefined) {
    GM_setValue('currentFilter', ''); // 필터 키워드 초기값 설정
}
if (GM_getValue('blacklist') === undefined) {
    GM_setValue('blacklist', JSON.stringify([])); // 블랙리스트 초기값 설정
}
if (GM_getValue('whitelist') === undefined) {
    GM_setValue('whitelist', JSON.stringify([])); // 화이트리스트 초기값 설정
}
if (GM_getValue('crawlStartTime') === undefined) {
    GM_setValue('crawlStartTime', null); // 크롤링 시작 시간 초기화
}

// 크롤링 진행 시간 저장
let crawlStartTime = GM_getValue('crawlStartTime');

// UI 생성 (한 번만 생성)
if (!document.getElementById('crawlUI')) {
    createUI();
}

// UI 업데이트 함수
function updateUI() {
    const isCrawling = GM_getValue('isCrawling');
    document.getElementById('startButton').disabled = isCrawling;
    document.getElementById('stopButton').disabled = !isCrawling;
    document.getElementById('progressBar').value = getProgressPercentage();
    document.getElementById('progressText').innerText = `${getProgressPercentage()}% 완료`;
    document.getElementById('currentTVPageIndicator').innerText = `📺 현재 TV 페이지: ${GM_getValue('tvPage')}`;
    document.getElementById('darkModeToggle').checked = GM_getValue('darkMode');
    document.getElementById('estimatedTime').innerText = getEstimatedFinishTime();
}

// UI 생성 함수
function createUI() {
    const container = document.createElement('div');
    container.id = 'crawlUI';
    // 컨테이너 스타일링 (크기를 작게 조정)
    container.style.position = 'fixed';
    container.style.top = '10px';
    container.style.right = '10px';
    container.style.padding = '15px';
    container.style.backgroundColor = '#FFFFFF';
    container.style.border = '1px solid #DDD';
    container.style.borderRadius = '6px';
    container.style.boxShadow = '0 2px 8px rgba(0,0,0,0.2)';
    container.style.zIndex = '10000';
    container.style.width = '400px';
    container.style.fontFamily = '"Segoe UI", Tahoma, Geneva, Verdana, sans-serif';
    container.style.fontSize = '12px';
    container.style.color = '#333';
    container.style.maxHeight = '80vh';
    container.style.overflowY = 'auto';

    // 스타일 시트 추가 (다크 모드 지원 및 Firefox 스타일)
    const style = document.createElement('style');
    style.textContent = `
        #crawlUI {
            background-color: #f2f2f2;
            color: #333;
            transition: all 0.3s ease;
        }
        #crawlUI.dark-mode {
            background-color: #2E2E2E !important;
            color: #FFFFFF !important;
            border: 1px solid #555 !important;
        }
        #crawlUI h2 {
            margin: 0 0 10px 0;
            font-size: 18px;
            color: #3388ff;
            display: flex;
            align-items: center;
            justify-content: center;
        }
        #crawlUI h2 svg {
            margin-right: 6px;
        }
        #crawlUI button {
            cursor: pointer;
            transition: background-color 0.3s, transform 0.2s;
            border: none;
            border-radius: 4px;
            font-size: 12px;
            padding: 8px;
            color: #fff;
            margin: 2px 0;
        }
        #crawlUI button:disabled {
            cursor: not-allowed;
            opacity: 0.6;
        }
        #crawlUI button:hover:not(:disabled) {
            transform: scale(1.02);
        }
        #crawlUI button:active:not(:disabled) {
            transform: scale(0.98);
        }
        #crawlUI table th {
            background-color: #3388ff;
            color: #fff;
            position: sticky;
            top: 0;
            z-index: 2;
            font-weight: bold;
            padding: 6px;
            border: 1px solid #e1e1e1;
        }
        #crawlUI table th, #crawlUI table td {
            padding: 6px;
            border: 1px solid #e1e1e1;
            text-align: left;
            font-size: 12px;
        }
        #crawlUI textarea {
            background-color: #f9f9f9;
            color: #333;
            border: 1px solid #CCC;
            border-radius: 4px;
            resize: none;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            font-size: 12px;
            padding: 6px;
            width: 100%;
            height: 80px;
        }
        .iframe-container {
            position: relative;
            padding-bottom: 56.25%;
            /* 16:9 */
            padding-top: 25px;
            height: 0;
        }
        .iframe-container iframe {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            border: 0;
        }
        /* 다크 모드 스타일 */
        .dark-mode textarea {
            background-color: #3E3E3E !important;
            color: #FFFFFF !important;
            border: 1px solid #555 !important;
        }
        .dark-mode table th, .dark-mode table td {
            border: 1px solid #555 !important;
        }
        /* 스크롤바 스타일 */
        #crawlUI::-webkit-scrollbar {
            width: 6px;
        }
        #crawlUI::-webkit-scrollbar-track {
            background: #f1f1f1;
        }
        #crawlUI::-webkit-scrollbar-thumb {
            background: #ccc;
            border-radius: 3px;
        }
        #crawlUI::-webkit-scrollbar-thumb:hover {
            background: #aaa;
        }
        /* 필터 input 스타일 */
        #filterInput {
            width: 100%;
            padding: 6px;
            margin-bottom: 8px;
            border: 1px solid #CCC;
            border-radius: 4px;
            box-sizing: border-box;
            font-size: 12px;
        }
        /* 설정 탭 스타일 */
        #settingsTab {
            width: 100%;
            padding: 8px;
            margin-bottom: 10px;
            background-color: #3388ff;
            color: #fff;
            border: 1px solid #ddd;
            border-radius: 4px;
            font-weight: bold;
            cursor: pointer;
            text-align: center;
            font-size: 12px;
        }
        #settingsContent {
            display: none;
            margin-bottom: 10px;
        }
        #settingsContent.visible {
            display: block;
        }
        .settings-section {
            margin-bottom: 8px;
        }
        .settings-section label {
            display: block;
            margin-bottom: 4px;
            font-weight: bold;
            font-size: 12px;
        }
        .settings-section input {
            width: 100%;
            padding: 5px;
            border: 1px solid #CCC;
            border-radius: 4px;
            box-sizing: border-box;
            font-size: 12px;
            margin-bottom: 4px;
        }
        /* 테이블 스크롤 */
        #crawlResultsContainer {
            max-height: 150px;
            overflow-y: auto;
            border: 1px solid #e1e1e1;
            border-radius: 4px;
            margin-bottom: 10px;
            background-color: #FFF;
        }
        /* 진행률 바 상세 정보 */
        #progressDetails {
            display: flex;
            justify-content: space-between;
            margin-top: 4px;
            font-size: 10px;
            color: #555;
        }
        /* 애니메이션 사이트 뷰어 섹션 스타일 */
        #animeViewerContainer, #tvViewerContainer, #htmlViewerContainer {
            margin-bottom: 10px;
            border: 1px solid #e1e1e1;
            border-radius: 4px;
            padding: 8px;
            background-color: #FFF;
        }
        /* 버튼 추가 */
        .control-button {
            width: 100%;
            padding: 6px;
            margin-bottom: 6px;
            border: none;
            border-radius: 4px;
            font-size: 12px;
            cursor: pointer;
            transition: background-color 0.3s;
        }
        .control-button.start {
            background-color: #3388ff;
            color: #FFF;
        }
        .control-button.start:hover:not(:disabled) {
            background-color: #2868c7;
        }
        .control-button.stop {
            background-color: #d9534f;
            color: #FFF;
        }
        .control-button.stop:hover:not(:disabled) {
            background-color: #c9302c;
        }
        .control-button.reset {
            background-color: #6c757d;
            color: #FFF;
        }
        .control-button.reset:hover {
            background-color: #5a6268;
        }
        .control-button.refresh-tv {
            background-color: #28a745;
            color: #FFF;
        }
        .control-button.refresh-tv:hover {
            background-color: #218838;
        }
        /* Estimated Time Style */
        #estimatedTime {
            margin-top: 4px;
            font-size: 10px;
            color: #555;
        }
    `;
    container.appendChild(style);

    // 제목
    const title = document.createElement('h2');
    title.innerHTML = `
        <svg xmlns="http://www.w3.org/2000/svg" height="18" viewBox="0 0 24 24" width="18" fill="#3388ff">
            <path d="M0 0h24v24H0V0z" fill="none"/>
            <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48
            10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.95-7-7.93
            0-3.87 3.13-7 7-7s7 3.13 7 7c0 3.98-3.05 7.44-7
            7.93v-2.93h0zm0-12c-2.76 0-5 2.24-5 5s2.24
            5 5 5 5-2.24 5-5-2.24-5-5-5z"/>
        </svg>
        🔍 크롤러
    `;
    container.appendChild(title);

    // 설정 탭
    const settingsTab = document.createElement('div');
    settingsTab.id = 'settingsTab';
    settingsTab.innerText = '⚙️ 설정';
    settingsTab.onclick = () => {
        settingsContent.classList.toggle('visible');
    };
    container.appendChild(settingsTab);

    // 설정 내용
    const settingsContent = document.createElement('div');
    settingsContent.id = 'settingsContent';

    // 화이트리스트
    const whitelistSection = document.createElement('div');
    whitelistSection.classList.add('settings-section');
    const whitelistLabel = document.createElement('label');
    whitelistLabel.setAttribute('for', 'whitelistInput');
    whitelistLabel.innerText = '✅ 화이트리스트 도메인 (콤마로 구분)';
    whitelistSection.appendChild(whitelistLabel);
    const whitelistInput = document.createElement('input');
    whitelistInput.type = 'text';
    whitelistInput.id = 'whitelistInput';
    whitelistInput.value = GM_getValue('whitelist') ? JSON.parse(GM_getValue('whitelist')).join(', ') : '';
    whitelistInput.placeholder = '예: example.com, another.com';
    whitelistInput.onchange = () => {
        const domains = whitelistInput.value.split(',').map(d => d.trim()).filter(d => d.length > 0);
        GM_setValue('whitelist', JSON.stringify(domains));
        log(`✅ 화이트리스트 업데이트: ${domains.join(', ')}`);
    };
    whitelistSection.appendChild(whitelistInput);
    settingsContent.appendChild(whitelistSection);

    // 블랙리스트
    const blacklistSection = document.createElement('div');
    blacklistSection.classList.add('settings-section');
    const blacklistLabel = document.createElement('label');
    blacklistLabel.setAttribute('for', 'blacklistInput');
    blacklistLabel.innerText = '🚫 블랙리스트 도메인 (콤마로 구분)';
    blacklistSection.appendChild(blacklistLabel);
    const blacklistInput = document.createElement('input');
    blacklistInput.type = 'text';
    blacklistInput.id = 'blacklistInput';
    blacklistInput.value = GM_getValue('blacklist') ? JSON.parse(GM_getValue('blacklist')).join(', ') : '';
    blacklistInput.placeholder = '예: spam.com, ads.com';
    blacklistInput.onchange = () => {
        const domains = blacklistInput.value.split(',').map(d => d.trim()).filter(d => d.length > 0);
        GM_setValue('blacklist', JSON.stringify(domains));
        log(`🚫 블랙리스트 업데이트: ${domains.join(', ')}`);
    };
    blacklistSection.appendChild(blacklistInput);
    settingsContent.appendChild(blacklistSection);

    container.appendChild(settingsContent);

    // 다크 모드 토글
    const darkModeContainer = document.createElement('div');
    darkModeContainer.style.marginBottom = '10px';
    darkModeContainer.style.display = 'flex';
    darkModeContainer.style.alignItems = 'center';
    darkModeContainer.style.justifyContent = 'space-between';

    const darkModeLabel = document.createElement('label');
    darkModeLabel.innerText = '🌙 다크 모드';
    darkModeLabel.setAttribute('for', 'darkModeToggle');
    darkModeLabel.style.fontWeight = 'bold';
    darkModeContainer.appendChild(darkModeLabel);

    const darkModeToggle = document.createElement('input');
    darkModeToggle.type = 'checkbox';
    darkModeToggle.id = 'darkModeToggle';
    darkModeToggle.checked = GM_getValue('darkMode');
    darkModeToggle.style.transform = 'scale(1.2)';
    darkModeToggle.onchange = () => {
        GM_setValue('darkMode', darkModeToggle.checked);
    };
    darkModeContainer.appendChild(darkModeToggle);

    container.appendChild(darkModeContainer);

    // 크롤링 대상 필터링 옵션
    const filterContainer = document.createElement('div');
    filterContainer.style.marginBottom = '10px';

    const filterLabel = document.createElement('label');
    filterLabel.innerText = '🔍 테이블 검색 필터';
    filterLabel.setAttribute('for', 'filterInput');
    filterLabel.style.display = 'block';
    filterLabel.style.marginBottom = '4px';
    filterLabel.style.fontWeight = 'bold';
    filterContainer.appendChild(filterLabel);

    const filterInput = document.createElement('input');
    filterInput.type = 'text';
    filterInput.id = 'filterInput';
    filterInput.value = GM_getValue('currentFilter') || ''; // 필터 저장 및 로드
    filterInput.placeholder = '키워드를 입력하세요...';
    filterInput.style.width = '100%';
    filterInput.style.padding = '6px';
    filterInput.style.border = '1px solid #CCC';
    filterInput.style.borderRadius = '4px';
    filterInput.style.boxSizing = 'border-box';
    filterInput.style.fontSize = '12px';
    filterInput.oninput = () => {
        const keyword = filterInput.value;
        GM_setValue('currentFilter', keyword); // 필터 키워드 저장
        filterTable(keyword);
    };
    filterContainer.appendChild(filterInput);
    container.appendChild(filterContainer);

    // 버튼 생성
    const startButton = document.createElement('button');
    startButton.id = 'startButton';
    startButton.innerHTML = '▶️ 시작';
    startButton.classList.add('control-button', 'start');
    startButton.onclick = startCrawling;
    container.appendChild(startButton);

    const stopButton = document.createElement('button');
    stopButton.id = 'stopButton';
    stopButton.innerHTML = '⏹️ 중지';
    stopButton.classList.add('control-button', 'stop');
    stopButton.onclick = stopCrawling;
    stopButton.disabled = !GM_getValue('isCrawling');
    container.appendChild(stopButton);

    // 초기화 버튼
    const resetButton = document.createElement('button');
    resetButton.innerHTML = '🔄 초기화';
    resetButton.classList.add('control-button', 'reset');
    resetButton.onclick = resetCrawling;
    container.appendChild(resetButton);

    // 진행률 표시
    const progressContainer = document.createElement('div');
    progressContainer.style.marginBottom = '10px';

    const progressLabel = document.createElement('label');
    progressLabel.innerText = '📊 진행률';
    progressLabel.style.display = 'block';
    progressLabel.style.marginBottom = '4px';
    progressLabel.style.fontWeight = 'bold';
    progressContainer.appendChild(progressLabel);

    const progressBar = document.createElement('progress');
    progressBar.id = 'progressBar';
    progressBar.value = getProgressPercentage();
    progressBar.max = 100;
    progressBar.style.width = '100%';
    progressBar.style.height = '12px';
    progressBar.style.borderRadius = '6px';
    progressBar.style.marginBottom = '4px';
    progressBar.style.transition = 'width 0.3s, background-color 0.3s';
    progressBar.style.webkitAppearance = 'none';
    progressBar.style.appearance = 'none';
    progressBar.style.backgroundColor = '#e0e0e0';
    progressBar.style.border = '1px solid #ccc';
    progressContainer.appendChild(progressBar);

    const progressText = document.createElement('span');
    progressText.id = 'progressText';
    progressText.innerText = `${getProgressPercentage()}% 완료`;
    progressText.style.fontWeight = 'bold';
    progressContainer.appendChild(progressText);

    // 상세 진행률 정보
    const progressDetails = document.createElement('div');
    progressDetails.id = 'progressDetails';
    progressDetails.innerHTML = `
        <span>${getEstimatedFinishTime()}</span>
    `;
    progressDetails.style.display = 'flex';
    progressDetails.style.justifyContent = 'space-between';
    progressDetails.style.marginTop = '4px';
    progressDetails.style.fontSize = '10px';
    progressDetails.style.color = '#555';
    progressContainer.appendChild(progressDetails);

    container.appendChild(progressContainer);

    // 현재 TV 페이지 표시
    const tvPageIndicator = document.createElement('div');
    tvPageIndicator.id = 'currentTVPageIndicator';
    tvPageIndicator.innerText = `📺 현재 TV 페이지: ${GM_getValue('tvPage')}`;
    tvPageIndicator.style.marginBottom = '10px';
    tvPageIndicator.style.fontWeight = 'bold';
    container.appendChild(tvPageIndicator);

    // 로그 창
    const logContainer = document.createElement('div');
    logContainer.style.marginBottom = '10px';

    const logTitle = document.createElement('h3');
    logTitle.innerText = '📝 로그';
    logTitle.style.marginBottom = '6px';
    logTitle.style.fontSize = '14px';
    logTitle.style.color = '#333';
    logContainer.appendChild(logTitle);

    const logArea = document.createElement('textarea');
    logArea.id = 'crawlLog';
    logArea.style.width = '100%';
    logArea.style.height = '80px';
    logArea.style.padding = '6px';
    logArea.style.border = '1px solid #CCC';
    logArea.style.borderRadius = '4px';
    logArea.style.resize = 'none';
    logArea.style.fontFamily = '"Segoe UI", Tahoma, Geneva, Verdana, sans-serif';
    logArea.style.fontSize = '12px';
    logArea.style.backgroundColor = '#f9f9f9';
    logArea.style.color = '#333';
    logArea.disabled = true;
    logContainer.appendChild(logArea);

    container.appendChild(logContainer);

    // 결과 테이블 생성
    const tableContainer = document.createElement('div');
    tableContainer.id = 'crawlResultsContainer';
    tableContainer.style.marginBottom = '10px';
    tableContainer.style.maxHeight = '150px';
    tableContainer.style.overflowY = 'auto';
    tableContainer.style.border = '1px solid #e1e1e1';
    tableContainer.style.borderRadius = '4px';
    tableContainer.style.backgroundColor = '#FFF';

    const table = document.createElement('table');
    table.id = 'crawlResults';
    table.style.width = '100%';
    table.style.borderCollapse = 'collapse';

    const thead = document.createElement('thead');
    const headerRow = document.createElement('tr');
    ['📰 사이트 이름', '🔗 주소', '📝 설명', '🗨️ 품질', '🗂️ 분류', '📄 보기'].forEach(text => {
        const th = document.createElement('th');
        th.innerText = text;
        thead.appendChild(th);
    });
    table.appendChild(thead);

    const tbody = document.createElement('tbody');
    tbody.id = 'crawlTableBody';
    table.appendChild(tbody);

    tableContainer.appendChild(table);
    container.appendChild(tableContainer);

    // HTML 뷰어 섹션 추가
    const htmlViewerContainer = document.createElement('div');
    htmlViewerContainer.id = 'htmlViewerContainer';
    htmlViewerContainer.style.display = 'none';
    htmlViewerContainer.style.marginBottom = '10px';
    htmlViewerContainer.style.border = '1px solid #e1e1e1';
    htmlViewerContainer.style.borderRadius = '4px';
    htmlViewerContainer.style.padding = '8px';
    htmlViewerContainer.style.backgroundColor = '#FFF';

    const htmlViewerTitle = document.createElement('h3');
    htmlViewerTitle.innerText = '📄 HTML 뷰어';
    htmlViewerTitle.style.marginBottom = '6px';
    htmlViewerTitle.style.fontSize = '14px';
    htmlViewerTitle.style.color = '#333';
    htmlViewerContainer.appendChild(htmlViewerTitle);

    const closeViewerButton = document.createElement('button');
    closeViewerButton.innerHTML = '❌ 닫기';
    closeViewerButton.classList.add('control-button', 'stop');
    closeViewerButton.style.marginBottom = '6px';
    closeViewerButton.onclick = () => {
        htmlViewerContainer.style.display = 'none';
        htmlViewerContent.src = '';
    };
    htmlViewerContainer.appendChild(closeViewerButton);

    const htmlViewerContent = document.createElement('iframe');
    htmlViewerContent.id = 'htmlViewerContent';
    htmlViewerContent.style.width = '100%';
    htmlViewerContent.style.height = '200px';
    htmlViewerContent.style.border = '1px solid #CCC';
    htmlViewerContent.style.borderRadius = '4px';
    htmlViewerContent.style.marginTop = '6px';
    htmlViewerContainer.appendChild(htmlViewerContent);

    container.appendChild(htmlViewerContainer);

    // 애니메이션 사이트 뷰어 추가
    const animeViewerContainer = document.createElement('div');
    animeViewerContainer.id = 'animeViewerContainer';
    animeViewerContainer.style.marginBottom = '10px';
    animeViewerContainer.style.border = '1px solid #e1e1e1';
    animeViewerContainer.style.borderRadius = '4px';
    animeViewerContainer.style.padding = '8px';
    animeViewerContainer.style.backgroundColor = '#FFF';

    const animeViewerTitle = document.createElement('h3');
    animeViewerTitle.innerText = '🎬 애니메이션 보기';
    animeViewerTitle.style.marginBottom = '6px';
    animeViewerTitle.style.fontSize = '14px';
    animeViewerTitle.style.color = '#333';
    animeViewerContainer.appendChild(animeViewerTitle);

    const animeViewerFrame = document.createElement('iframe');
    animeViewerFrame.src = 'https://linkkf.net/';
    animeViewerFrame.style.width = '100%';
    animeViewerFrame.style.height = '200px';
    animeViewerFrame.style.border = '1px solid #CCC';
    animeViewerFrame.style.borderRadius = '4px';
    animeViewerFrame.style.marginTop = '6px';
    animeViewerContainer.appendChild(animeViewerFrame);

    container.appendChild(animeViewerContainer);

    // TV 사이트 뷰어 추가
    const tvViewerContainer = document.createElement('div');
    tvViewerContainer.id = 'tvViewerContainer';
    tvViewerContainer.style.marginBottom = '10px';
    tvViewerContainer.style.border = '1px solid #e1e1e1';
    tvViewerContainer.style.borderRadius = '4px';
    tvViewerContainer.style.padding = '8px';
    tvViewerContainer.style.backgroundColor = '#FFF';

    const tvViewerTitle = document.createElement('h3');
    tvViewerTitle.innerText = '📺 TV 사이트 보기';
    tvViewerTitle.style.marginBottom = '6px';
    tvViewerTitle.style.fontSize = '14px';
    tvViewerTitle.style.color = '#333';
    tvViewerContainer.appendChild(tvViewerTitle);

    const tvViewerFrame = document.createElement('iframe');
    tvViewerFrame.id = 'tvViewerFrame';
    tvViewerFrame.src = `https://tv.${GM_getValue('tvPage')}.wiKi/`;
    tvViewerFrame.style.width = '100%';
    tvViewerFrame.style.height = '200px';
    tvViewerFrame.style.border = '1px solid #CCC';
    tvViewerFrame.style.borderRadius = '4px';
    tvViewerFrame.style.marginTop = '6px';
    tvViewerFrame.onload = () => {
        // 페이지가 성공적으로 로드되면 로그에 기록
        log(`✅ TV 사이트 로드 완료: https://tv.${GM_getValue('tvPage')}.wiKi/`);
    };
    tvViewerFrame.onerror = () => {
        // 페이지 로드 실패 시 로그에 기록
        log(`❌ TV 사이트 로드 실패: https://tv.${GM_getValue('tvPage')}.wiKi/`);
        alert(`TV 사이트 로드 실패: https://tv.${GM_getValue('tvPage')}.wiKi/`);
    };
    tvViewerContainer.appendChild(tvViewerFrame);

    // 새로 고침 버튼 추가
    const refreshTVButton = document.createElement('button');
    refreshTVButton.innerHTML = '🔄 TV 사이트 새로 고침';
    refreshTVButton.classList.add('control-button', 'refresh-tv');
    refreshTVButton.style.marginTop = '6px';
    refreshTVButton.onclick = refreshTVSite;
    tvViewerContainer.appendChild(refreshTVButton);

    container.appendChild(tvViewerContainer);

    // Estimated Time Display
    const estimatedTime = document.createElement('div');
    estimatedTime.id = 'estimatedTime';
    estimatedTime.innerText = '⏳ 예상 완료 시간: 계산 중...';
    estimatedTime.style.marginTop = '4px';
    estimatedTime.style.fontSize = '10px';
    estimatedTime.style.color = '#555';
    container.appendChild(estimatedTime);

    document.body.appendChild(container);

    // 다크 모드 초기 설정
    toggleDarkMode(GM_getValue('darkMode'));

    // 초기 테이블 데이터 로드
    loadCrawlData();
    loadLogData();
    loadFilterKeywords();

    // 크롤링 계속 진행하기 위한 자동 실행
    if (GM_getValue('isCrawling')) {
        setTimeout(() => {
            crawlPage();
        }, fixedCrawlDelay);
    }
}

// 다크 모드 토글 함수
function toggleDarkMode(isDark) {
    const container = document.getElementById('crawlUI');
    if (isDark) {
        container.classList.add('dark-mode');
    } else {
        container.classList.remove('dark-mode');
    }
}

// 크롤링 시작 함수
function startCrawling() {
    if (GM_getValue('isCrawling')) return;
    GM_setValue('isCrawling', true);
    GM_setValue('crawlStartTime', new Date().getTime()); // 시작 시간 저장
    log('🔍 크롤링 시작');
    if (GM_getValue('currentPage') === 1) {
        GM_setValue('crawlData', JSON.stringify([]));
        clearTable();
        clearLog();
    }
    crawlPage();
}

// 크롤링 중지 함수
function stopCrawling() {
    if (!GM_getValue('isCrawling')) return;
    GM_setValue('isCrawling', false);
    log('⏹️ 크롤링 중지');
}

// 크롤링 초기화 함수
function resetCrawling() {
    stopCrawling();
    GM_setValue('crawlData', JSON.stringify([]));
    GM_setValue('currentPage', 1);
    GM_setValue('tvPage', 54); // TV 페이지 번호 초기화
    GM_setValue('crawlStartTime', null); // 크롤링 시작 시간 초기화
    clearTable();
    clearLog();
    log('🔄 크롤링 초기화 완료');
    updateProgressDetails();
    document.getElementById('filterInput').value = '';
    GM_setValue('currentFilter', '');
    filterTable(''); // 필터 초기화
}

// 테이블 초기화
function clearTable() {
    const tbody = document.getElementById('crawlTableBody');
    if (tbody) {
        tbody.innerHTML = '';
    }
}

// 로그 초기화
function clearLog() {
    GM_setValue('logData', JSON.stringify([]));
    loadLogData();
}

// 크롤링 데이터 로드 및 테이블 업데이트
function loadCrawlData() {
    const crawlData = JSON.parse(GM_getValue('crawlData'));
    crawlData.forEach(item => {
        addRowToTable(item.title, item.url, item.description, item.quality, item.classification);
    });
}

// 로그 데이터 로드 및 로그 표시
function loadLogData() {
    const logData = JSON.parse(GM_getValue('logData'));
    const logArea = document.getElementById('crawlLog');
    logArea.value = logData.join('\n');
    logArea.scrollTop = logArea.scrollHeight;
}

// 필터 키워드 로드
function loadFilterKeywords() {
    const filterKeywords = GM_getValue('currentFilter') || '';
    filterTable(filterKeywords);
}

// 로그 추가 함수
function log(message) {
    const timestamp = new Date().toLocaleTimeString();
    const logEntry = `[${timestamp}] ${message}`;
    let logData = JSON.parse(GM_getValue('logData'));
    logData.push(logEntry);
    GM_setValue('logData', JSON.stringify(logData));

    const logArea = document.getElementById('crawlLog');
    logArea.value += logEntry + '\n';
    logArea.scrollTop = logArea.scrollHeight;
}

// 진행률 계산 함수
function getProgressPercentage() {
    // 최대 페이지 수를 동적으로 설정하거나 기본값 사용
    const maxPages = 100;
    const currentPage = GM_getValue('currentPage');
    return Math.min(Math.floor((currentPage / maxPages) * 100), 100);
}

// 상세 진행률 정보 업데이트 함수
function updateProgressDetails() {
    const progressDetails = document.getElementById('progressDetails');
    if (progressDetails) {
        progressDetails.innerHTML = `
            <span>${getEstimatedFinishTime()}</span>
        `;
    }
}

// 예상 완료 시간 계산 함수
function getEstimatedFinishTime() {
    const crawlStart = GM_getValue('crawlStartTime');
    if (!crawlStart) return '⏳ 예상 완료 시간: 아직 시작되지 않음';

    const totalPages = 100;
    const currentPage = GM_getValue('currentPage');
    const pagesLeft = totalPages - currentPage;
    const elapsedTime = new Date().getTime() - crawlStart;
    const rate = elapsedTime / currentPage; // ms per page
    const estimatedRemaining = rate * pagesLeft;
    const estimatedFinish = new Date(new Date().getTime() + estimatedRemaining);
    return `⏳ 예상 완료 시간: ${estimatedFinish.toLocaleTimeString()}`;
}

// 크롤링 페이지 처리
function crawlPage() {
    if (!GM_getValue('isCrawling')) return;

    log(`📄 페이지 ${GM_getValue('currentPage')} 크롤링 중...`);

    let crawlData = JSON.parse(GM_getValue('crawlData'));
    const results = document.querySelectorAll(engine.resultSelector);

    const blacklist = GM_getValue('blacklist') ? JSON.parse(GM_getValue('blacklist')) : [];
    const whitelist = GM_getValue('whitelist') ? JSON.parse(GM_getValue('whitelist')) : [];

    // 화이트리스트가 설정된 경우
    if (whitelist.length > 0 && !whitelist.includes(currentHost)) {
        log('✅ 화이트리스트에 포함되지 않은 도메인, 크롤링 중지.');
        GM_setValue('isCrawling', false);
        GM_setValue('crawlStartTime', null);
        log('🏁 크롤링 완료');
        alert('크롤링이 완료되었습니다!');
        return;
    }

    results.forEach(result => {
        // 블랙리스트 도메인 필터링
        const linkElement = result.querySelector(engine.linkSelector);
        if (linkElement) {
            try {
                const url = new URL(linkElement.href);
                const domain = url.hostname.replace('www.', '');
                if (blacklist.includes(domain)) {
                    log(`🚫 블랙리스트 도메인 필터링: ${domain}`);
                    return;
                }
            } catch (e) {
                log(`⚠️ 유효하지 않은 URL: ${linkElement.href}`);
                return;
            }
        }

        // 광고 제외 로직 (예: 특정 클래스나 도메인 필터링)
        if (isAd(result)) return;

        const titleElement = result.querySelector(engine.titleSelector);
        const linkEl = result.querySelector(engine.linkSelector);
        const descriptionElement = result.querySelector(engine.descriptionSelector);

        if (titleElement && linkEl) {
            const title = titleElement.innerText.trim();
            const url = linkEl.href;
            const description = descriptionElement ? descriptionElement.innerText.trim() : '';

            // 필터 키워드 체크
            const currentFilter = GM_getValue('currentFilter') || '';
            if (currentFilter) {
                const keywords = currentFilter.split(',').map(k => k.trim()).filter(k => k.length > 0);
                const match = keywords.every(kw => {
                    // OR 조건 처리
                    const orKeywords = kw.split('|').map(k => k.trim()).filter(k => k.length > 0);
                    return orKeywords.some(subKw => {
                        return title.toLowerCase().includes(subKw.toLowerCase()) || description.toLowerCase().includes(subKw.toLowerCase());
                    });
                });
                if (!match) {
                    log(`🚫 필터링됨: ${title} - ${url}`);
                    return;
                }
            }

            // 중복 검사
            if (!crawlData.some(item => item.url === url)) {
                const quality = evaluateContentQuality(description);
                const classification = classifyKeywords(description);
                crawlData.push({ title, url, description, quality, classification });
                addRowToTable(title, url, description, quality, classification);
                log(`✅ 데이터 수집: ${title} - ${url}`);
            }
        }
    });

    GM_setValue('crawlData', JSON.stringify(crawlData));
    updateUI();
    updateProgressDetails();

    // 다음 페이지로 이동
    const nextButton = document.querySelector(engine.nextButtonSelector);
    if (nextButton && GM_getValue('currentPage') < 100 && GM_getValue('isCrawling')) { // 최대 페이지 수 설정
        let nextPageUrl = nextButton.href;

        // 현재 URL에서 기존 파라미터를 가져와 추가 파라미터를 덧붙임
        const urlObj = new URL(nextPageUrl);
        const params = new URLSearchParams(urlObj.search);
        // 기존 추가 파라미터가 없으면 추가
        if (!urlObj.search.includes(engine.additionalParams.split('&tbs=')[0])) {
            params.append(engine.additionalParams.split('&tbs=')[0].split('=')[0], engine.additionalParams.split('&tbs=')[0].split('=')[1]);
        }

        // 검색 쿼리 가져오기
        let query = params.get(engine.queryParam) || '';

        // OR 및 | 연산자 처리: 반드시 공백을 포함하고 OR은 대문자
        query = query.replace(/\s*\|\s*/g, ' OR ').replace(/or/g, 'OR');

        // site: 연산자 처리: ?가 포함되지 않음
        query = query.replace(/site:([^\s?]+)/g, 'site:$1');

        // 큰따옴표로 묶인 정확한 검색어 처리
        query = query.replace(/"(.*?)"/g, (match, p1) => `"${p1}"`);

        // - 연산자 처리
        query = query.replace(/-([^\s]+)/g, '-$1');

        // intitle:, inurl:, define: 소문자 처리
        query = query.replace(/intitle:/gi, 'intitle:').replace(/inurl:/gi, 'inurl:').replace(/define:/gi, 'define:');

        // 검색 파라미터 업데이트
        params.set(engine.queryParam, query);
        urlObj.search = params.toString();
        nextPageUrl = urlObj.toString();

        GM_setValue('currentPage', GM_getValue('currentPage') + 1);
        updateUI();
        updateProgressDetails();
        log(`➡️ 다음 페이지로 이동: 페이지 ${GM_getValue('currentPage')}`);
        setTimeout(() => {
            window.location.href = nextPageUrl;
        }, fixedCrawlDelay); // 고정 크롤링 속도 사용 (100ms)
    } else {
        GM_setValue('isCrawling', false);
        GM_setValue('crawlStartTime', null);
        log('🏁 크롤링 완료');
        alert('크롤링이 완료되었습니다!');
    }
}

// 테이블에 데이터 추가
function addRowToTable(title, url, description, quality, classification) {
    const tbody = document.getElementById('crawlTableBody');
    if (!tbody) return;

    const row = tbody.insertRow();

    const titleCell = row.insertCell();
    titleCell.innerText = title;
    titleCell.style.fontWeight = 'bold';

    const urlCell = row.insertCell();
    const link = document.createElement('a');
    link.href = url;
    link.innerText = url;
    link.target = '_blank';
    link.style.color = '#3388ff';
    urlCell.appendChild(link);

    const descCell = row.insertCell();
    descCell.innerText = description;
    descCell.style.color = '#555';

    const qualityCell = row.insertCell();
    qualityCell.innerText = quality;
    qualityCell.style.color = quality > 50 ? '#28a745' : '#dc3545'; // 품질 기준 조정
    qualityCell.style.fontWeight = 'bold';

    const classificationCell = row.insertCell();
    classificationCell.innerText = classification;
    classificationCell.style.fontWeight = 'bold';

    const viewHtmlCell = row.insertCell();
    const viewHtmlButton = document.createElement('button');
    viewHtmlButton.innerHTML = '📄 보기';
    viewHtmlButton.style.padding = '4px 8px';
    viewHtmlButton.style.backgroundColor = '#28a745';
    viewHtmlButton.style.color = '#FFF';
    viewHtmlButton.style.border = 'none';
    viewHtmlButton.style.borderRadius = '4px';
    viewHtmlButton.style.cursor = 'pointer';
    viewHtmlButton.style.fontSize = '10px';
    viewHtmlButton.style.transition = 'background-color 0.3s';
    viewHtmlButton.onmouseenter = () => {
        viewHtmlButton.style.backgroundColor = '#218838';
    };
    viewHtmlButton.onmouseleave = () => {
        viewHtmlButton.style.backgroundColor = '#28a745';
    };
    viewHtmlButton.onclick = () => viewHTML(url);
    viewHtmlCell.appendChild(viewHtmlButton);
}

// HTML 뷰어 열기
function viewHTML(url) {
    log(`🔗 HTML 요청: ${url}`);
    GM_xmlhttpRequest({
        method: 'GET',
        url: url,
        onload: function(response) {
            if (response.status >= 200 && response.status < 300) {
                displayHTML(response.responseText, url);
                log(`✅ HTML 로드 완료: ${url}`);
            } else {
                log(`❌ HTML 로드 실패 (${response.status}): ${url}`);
                alert(`HTML 로드 실패: ${response.status} ${response.statusText}`);
            }
        },
        onerror: function(err) {
            log(`⚠️ HTML 요청 에러: ${url}`);
            alert(`HTML 요청 에러: ${err}`);
        }
    });
}

// HTML을 뷰어에 표시
function displayHTML(htmlContent, url) {
    const htmlViewerContainer = document.getElementById('htmlViewerContainer');
    const htmlViewerContent = document.getElementById('htmlViewerContent');

    // Blob을 사용하여 안전하게 iframe에 로드
    const blob = new Blob([htmlContent], { type: 'text/html' });
    const blobURL = URL.createObjectURL(blob);
    htmlViewerContent.src = blobURL;

    htmlViewerContainer.style.display = 'block';
}

// TV 사이트 새로 고침 함수
function refreshTVSite() {
    let currentTVPage = GM_getValue('tvPage');
    currentTVPage += 1;
    GM_setValue('tvPage', currentTVPage);
    const tvFrame = document.getElementById('tvViewerFrame');
    tvFrame.src = `https://tv.${currentTVPage}.wiKi/`;
    log(`📺 TV 사이트 새로 고침: https://tv.${currentTVPage}.wiKi/`);
}

// 필터 테이블 함수
function filterTable(keyword) {
    const tbody = document.getElementById('crawlTableBody');
    const rows = tbody.getElementsByTagName('tr');
    for (let i = 0; i < rows.length; i++) {
        const cells = rows[i].getElementsByTagName('td');
        let match = true;
        if (keyword) {
            const keywords = keyword.split(',').map(k => k.trim()).filter(k => k.length > 0);
            match = keywords.every(kw => {
                return Array.from(kw.split('|')).some(subKw => {
                    return Array.from(cells).some(cell => cell.innerText.toLowerCase().includes(subKw.toLowerCase()));
                });
            });
        }
        rows[i].style.display = match ? '' : 'none';
    }
}

// 콘텐츠 품질 평가 함수
function evaluateContentQuality(text) {
    const wordCount = text.split(/\s+/).length;
    let score = wordCount * 10;
    score = score > 100 ? 100 : score;
    return score;
}

// 키워드 분류 함수
function classifyKeywords(description) {
    const categories = {
        '기술': ['기술', '테크', 'software'],
        '건강': ['건강', '헬스', '의학'],
        '스포츠': ['스포츠', '축구', '야구'],
        '엔터테인먼트': ['영화', '음악', '드라마'],
        '비즈니스': ['비즈니스', '경제', '주식']
    };
    for (let category in categories) {
        for (let keyword of categories[category]) {
            if (description.toLowerCase().includes(keyword.toLowerCase())) {
                return category;
            }
        }
    }
    return '기타';
}

// 광고 제외 필터링 함수 (필요 시 확장 가능)
function isAd(resultElement) {
    // 광고로 간주되는 특정 클래스나 속성 체크
    const adClasses = ['ad', 'ads', 'commercial', 'sponsored', 'promo'];
    return adClasses.some(cls => resultElement.classList.contains(cls));
}

// 크롤링 상태 변경 리스너
GM_addValueChangeListener('isCrawling', (name, oldValue, newValue, remote) => {
    updateUI();
    log(`🔄 크롤링 상태 변경: ${newValue ? '시작됨' : '중지됨'}`);
});

// 다크 모드 변경 리스너
GM_addValueChangeListener('darkMode', (name, oldValue, newValue, remote) => {
    toggleDarkMode(newValue);
    log(`🌙 다크 모드 변경: ${newValue ? '활성화됨' : '비활성화됨'}`);
});

// 필터 키워드 변경 리스너
GM_addValueChangeListener('currentFilter', (name, oldValue, newValue, remote) => {
    loadFilterKeywords();
});

// 화이트리스트/블랙리스트 변경 리스너
GM_addValueChangeListener('blacklist', (name, oldValue, newValue, remote) => {
    log(`🚫 블랙리스트 업데이트: ${JSON.parse(newValue).join(', ')}`);
});

GM_addValueChangeListener('whitelist', (name, oldValue, newValue, remote) => {
    log(`✅ 화이트리스트 업데이트: ${JSON.parse(newValue).join(', ')}`);
});

updateUI();
updateProgressDetails();
})(); ui 개선해줘!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 애니 보여주는 사이트에서 애니보고 있는데 다음 페이지 넘어가면 갑자기 영상이 초기화가 됨. 해결 해줘.  + 그리고 검색어 같은걸 예약 해두는 기능 추가해서 내가 검색 창에 하고 크롤링 다 끝난 후에 다시 내가 예약해둔 검색어로 검색해주셈 ㄱㄱ  코드만 쓰셈. 코드만 써.