// ==UserScript==
// @name         이걸로 내 소원 사이트 찾으셈
// @namespace    http://tampermonkey.net/
// @version      3.17
// @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&tbs='
        },
        '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': {
            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) return;

    // 초기 설정
    const defaultSettings = {
        isCrawling: false,
        crawlData: {},
        logData: [],
        tvPage: 54,
        darkMode: false,
        currentFilter: '',
        blacklist: [],
        whitelist: [],
        crawlStartTime: null,
        manualSearchQueue: [],
        scheduledSearchQueue: []
    };

    for (let key in defaultSettings) {
        if (GM_getValue(key) === undefined) {
            GM_setValue(key, key === 'crawlStartTime' ? null : JSON.stringify(defaultSettings[key]));
        }
    }

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

    // 검색 감지
    let lastQuery = GM_getValue('lastQuery') || '';
    observeSearch();

    function observeSearch() {
        const observer = new MutationObserver(() => {
            const params = new URLSearchParams(window.location.search);
            const query = params.get(engine.queryParam);
            if (query && query !== lastQuery) {
                lastQuery = query;
                GM_setValue('lastQuery', lastQuery);
                let manualQueue = JSON.parse(GM_getValue('manualSearchQueue'));
                manualQueue.push(query);
                GM_setValue('manualSearchQueue', JSON.stringify(manualQueue));
                log(`🔍 수동 검색어 감지 및 예약 추가: "${query}"`);
                if (!JSON.parse(GM_getValue('isCrawling'))) {
                    startCrawling();
                }
            }
        });

        observer.observe(document.body, { childList: true, subtree: true });
    }

    // UI 업데이트
    function updateUI() {
        const isCrawling = JSON.parse(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 페이지: ${JSON.parse(GM_getValue('tvPage'))}`;
        document.getElementById('darkModeToggle').checked = JSON.parse(GM_getValue('darkMode'));
        document.getElementById('estimatedTime').innerText = getEstimatedFinishTime();
        updateScheduledSearches();
        updateManualSearches();
    }

    // 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 = '20px';
        container.style.backgroundColor = '#f2f2f2';
        container.style.border = '1px solid #ccc';
        container.style.borderRadius = '10px';
        container.style.boxShadow = '0 4px 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 = '14px';
        container.style.color = '#333';
        container.style.maxHeight = '90vh';
        container.style.overflowY = 'auto';

        // 스타일
        const style = document.createElement('style');
        style.textContent = `
            #crawlUI.dark-mode {
                background-color: #2E2E2E !important;
                color: #FFFFFF !important;
                border: 1px solid #555 !important;
            }
            #crawlUI h2 {
                margin: 0 0 15px 0;
                font-size: 24px;
                color: #3388ff;
                text-align: center;
                display: flex;
                align-items: center;
                justify-content: center;
            }
            #crawlUI h2 svg {
                margin-right: 8px;
            }
            #crawlUI button {
                cursor: pointer;
                transition: background-color 0.3s;
                border: none;
                border-radius: 5px;
                padding: 10px;
                color: #fff;
                margin: 5px 0;
                width: 100%;
                font-size: 14px;
            }
            #crawlUI button:disabled {
                cursor: not-allowed;
                opacity: 0.6;
            }
            #crawlUI button.start {
                background-color: #28a745;
            }
            #crawlUI button.start:hover:not(:disabled) {
                background-color: #218838;
            }
            #crawlUI button.stop {
                background-color: #dc3545;
            }
            #crawlUI button.stop:hover:not(:disabled) {
                background-color: #c82333;
            }
            #crawlUI button.reset {
                background-color: #6c757d;
            }
            #crawlUI button.reset:hover {
                background-color: #5a6268;
            }
            #crawlUI button.refresh-tv {
                background-color: #17a2b8;
            }
            #crawlUI button.refresh-tv:hover {
                background-color: #138496;
            }
            #crawlUI label {
                display: block;
                margin-bottom: 5px;
                font-weight: bold;
            }
            #crawlUI input[type="text"] {
                width: 100%;
                padding: 8px;
                margin-bottom: 10px;
                border: 1px solid #ccc;
                border-radius: 5px;
                box-sizing: border-box;
                font-size: 14px;
            }
            #crawlUI textarea {
                width: 100%;
                height: 100px;
                padding: 8px;
                border: 1px solid #ccc;
                border-radius: 5px;
                box-sizing: border-box;
                font-size: 14px;
                resize: none;
                background-color: #fff;
                color: #333;
            }
            #crawlUI.dark-mode textarea {
                background-color: #3E3E3E !important;
                color: #FFFFFF !important;
                border: 1px solid #555 !important;
            }
            #crawlUI table {
                width: 100%;
                border-collapse: collapse;
                margin-bottom: 10px;
                font-size: 12px;
            }
            #crawlUI th, #crawlUI td {
                border: 1px solid #ddd;
                padding: 8px;
                text-align: left;
            }
            #crawlUI th {
                background-color: #3388ff;
                color: #fff;
            }
            #crawlUI .search-section {
                margin-bottom: 20px;
            }
            #crawlUI .search-section h3 {
                margin-bottom: 10px;
                font-size: 18px;
                color: #555;
            }
            #crawlUI .search-section div {
                max-height: 100px;
                overflow-y: auto;
                margin-bottom: 10px;
            }
            #crawlUI .search-item {
                display: flex;
                justify-content: space-between;
                align-items: center;
                padding: 5px 0;
                border-bottom: 1px solid #eee;
            }
            #crawlUI .search-item:last-child {
                border-bottom: none;
            }
            #crawlUI .remove-button {
                background-color: #dc3545;
                border: none;
                border-radius: 3px;
                color: #fff;
                padding: 3px 6px;
                cursor: pointer;
                font-size: 12px;
            }
            #crawlUI .remove-button:hover {
                background-color: #c82333;
            }
            #crawlUI .log-section textarea {
                height: 150px;
            }
        `;
        container.appendChild(style);

        // 제목
        const title = document.createElement('h2');
        title.innerHTML = `
            <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24" 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 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8
                8-8 8 3.59 8 8-3.59 8-8 8zm-1-13h2v6h-2zm0 8h2v2h-2z"/>
            </svg>
            🔍 크롤러
        `;
        container.appendChild(title);

        // 설정 토글 버튼
        const settingsTab = document.createElement('button');
        settingsTab.innerText = '⚙️ 설정 토글';
        settingsTab.classList.add('reset');
        settingsTab.style.marginTop = '10px';
        settingsTab.onclick = () => {
            settingsContent.classList.toggle('visible');
        };
        container.appendChild(settingsTab);

        // 설정 섹션
        const settingsContent = document.createElement('div');
        settingsContent.classList.add('toggle-section', 'section');

        // 화이트리스트
        const whitelistLabel = document.createElement('label');
        whitelistLabel.innerText = '✅ 화이트리스트 도메인 (콤마로 구분)';
        settingsContent.appendChild(whitelistLabel);
        const whitelistInput = document.createElement('input');
        whitelistInput.type = 'text';
        whitelistInput.id = 'whitelistInput';
        whitelistInput.value = 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);
            GM_setValue('whitelist', JSON.stringify(domains));
            log(`✅ 화이트리스트 업데이트: ${domains.join(', ')}`);
        };
        settingsContent.appendChild(whitelistInput);

        // 블랙리스트
        const blacklistLabel = document.createElement('label');
        blacklistLabel.innerText = '🚫 블랙리스트 도메인 (콤마로 구분)';
        settingsContent.appendChild(blacklistLabel);
        const blacklistInput = document.createElement('input');
        blacklistInput.type = 'text';
        blacklistInput.id = 'blacklistInput';
        blacklistInput.value = 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);
            GM_setValue('blacklist', JSON.stringify(domains));
            log(`🚫 블랙리스트 업데이트: ${domains.join(', ')}`);
        };
        settingsContent.appendChild(blacklistInput);

        // 다크 모드
        const darkModeLabel = document.createElement('label');
        darkModeLabel.innerText = '🌙 다크 모드';
        darkModeLabel.setAttribute('for', 'darkModeToggle');
        settingsContent.appendChild(darkModeLabel);
        const darkModeToggle = document.createElement('input');
        darkModeToggle.type = 'checkbox';
        darkModeToggle.id = 'darkModeToggle';
        darkModeToggle.checked = JSON.parse(GM_getValue('darkMode'));
        darkModeToggle.onchange = () => {
            GM_setValue('darkMode', JSON.stringify(darkModeToggle.checked));
            toggleDarkMode(darkModeToggle.checked);
            log(`🌙 다크 모드 ${darkModeToggle.checked ? '활성화됨' : '비활성화됨'}`);
        };
        settingsContent.appendChild(darkModeToggle);

        // 크롤링 필터
        const filterLabel = document.createElement('label');
        filterLabel.innerText = '🔍 테이블 검색 필터';
        settingsContent.appendChild(filterLabel);
        const filterInput = document.createElement('input');
        filterInput.type = 'text';
        filterInput.id = 'filterInput';
        filterInput.value = JSON.parse(GM_getValue('currentFilter'));
        filterInput.placeholder = '키워드를 입력하세요...';
        filterInput.oninput = () => {
            const keyword = filterInput.value.trim();
            GM_setValue('currentFilter', JSON.stringify(keyword));
            filterTable(keyword);
        };
        settingsContent.appendChild(filterInput);

        container.appendChild(settingsContent);

        // 예약 검색어 섹션
        const scheduledContainer = document.createElement('div');
        scheduledContainer.classList.add('section');

        const scheduledTitle = document.createElement('label');
        scheduledTitle.innerText = '📅 예약 검색어';
        scheduledContainer.appendChild(scheduledTitle);

        const scheduledInput = document.createElement('input');
        scheduledInput.type = 'text';
        scheduledInput.id = 'addScheduledSearchInput';
        scheduledInput.placeholder = '검색어를 입력하세요...';
        scheduledContainer.appendChild(scheduledInput);

        const addButton = document.createElement('button');
        addButton.innerText = '➕ 추가';
        addButton.classList.add('start');
        addButton.onclick = addScheduledSearch;
        scheduledContainer.appendChild(addButton);

        const scheduledList = document.createElement('div');
        scheduledList.id = 'scheduledSearchesList';
        scheduledList.style.maxHeight = '100px';
        scheduledList.style.overflowY = 'auto';
        scheduledContainer.appendChild(scheduledList);

        container.appendChild(scheduledContainer);

        // 수동 검색어 섹션
        const manualContainer = document.createElement('div');
        manualContainer.classList.add('section');

        const manualTitle = document.createElement('label');
        manualTitle.innerText = '📝 수동 검색어';
        manualContainer.appendChild(manualTitle);

        const manualList = document.createElement('div');
        manualList.id = 'manualSearchesList';
        manualList.style.maxHeight = '100px';
        manualList.style.overflowY = 'auto';
        manualContainer.appendChild(manualList);

        container.appendChild(manualContainer);

        // 크롤링 컨트롤 버튼
        const controlsContainer = document.createElement('div');
        controlsContainer.classList.add('section');

        const startButtonUI = document.createElement('button');
        startButtonUI.innerText = '▶️ 크롤링 시작';
        startButtonUI.classList.add('start');
        startButtonUI.id = 'startButton';
        startButtonUI.onclick = startCrawling;
        controlsContainer.appendChild(startButtonUI);

        const stopButtonUI = document.createElement('button');
        stopButtonUI.innerText = '⏹️ 크롤링 중지';
        stopButtonUI.classList.add('stop');
        stopButtonUI.id = 'stopButton';
        stopButtonUI.onclick = stopCrawling;
        controlsContainer.appendChild(stopButtonUI);

        const resetButtonUI = document.createElement('button');
        resetButtonUI.innerText = '🔄 초기화';
        resetButtonUI.classList.add('reset');
        resetButtonUI.onclick = resetCrawling;
        controlsContainer.appendChild(resetButtonUI);

        container.appendChild(controlsContainer);

        // 진행률 표시
        const progressContainer = document.createElement('div');
        progressContainer.classList.add('section');

        const progressLabel = document.createElement('label');
        progressLabel.innerText = '📊 진행률';
        progressContainer.appendChild(progressLabel);

        const progressBar = document.createElement('progress');
        progressBar.id = 'progressBar';
        progressBar.value = getProgressPercentage();
        progressBar.max = 100;
        progressContainer.appendChild(progressBar);

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

        container.appendChild(progressContainer);

        // 예상 완료 시간
        const estimatedTime = document.createElement('div');
        estimatedTime.id = 'estimatedTime';
        estimatedTime.innerText = '⏳ 예상 완료 시간: 계산 중...';
        estimatedTime.classList.add('section');
        container.appendChild(estimatedTime);

        // 현재 TV 페이지 표시
        const tvPageIndicator = document.createElement('div');
        tvPageIndicator.id = 'currentTVPageIndicator';
        tvPageIndicator.innerText = `📺 현재 TV 페이지: ${JSON.parse(GM_getValue('tvPage'))}`;
        tvPageIndicator.classList.add('section');
        container.appendChild(tvPageIndicator);

        // 로그 창
        const logLabel = document.createElement('label');
        logLabel.innerText = '📝 로그';
        logLabel.classList.add('section');
        container.appendChild(logLabel);
        const logArea = document.createElement('textarea');
        logArea.id = 'crawlLog';
        logArea.disabled = true;
        logArea.style.height = '150px';
        logArea.classList.add('log-section');
        container.appendChild(logArea);

        // 결과 테이블
        const table = document.createElement('table');
        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);
        container.appendChild(table);

        // HTML 뷰어
        const htmlViewerContainer = document.createElement('div');
        htmlViewerContainer.id = 'htmlViewerContainer';
        htmlViewerContainer.style.display = 'none';
        htmlViewerContainer.classList.add('section');
        const htmlViewerTitle = document.createElement('h3');
        htmlViewerTitle.innerText = '📄 HTML 뷰어';
        htmlViewerContainer.appendChild(htmlViewerTitle);
        const closeViewerButton = document.createElement('button');
        closeViewerButton.innerText = '❌ 닫기';
        closeViewerButton.classList.add('stop');
        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 = '300px';
        htmlViewerContent.style.border = '1px solid #CCC';
        htmlViewerContainer.appendChild(htmlViewerContent);
        container.appendChild(htmlViewerContainer);

        // 애니메이션 사이트 뷰어
        const animeViewerContainer = document.createElement('div');
        animeViewerContainer.id = 'animeViewerContainer';
        animeViewerContainer.classList.add('section');
        const animeViewerTitle = document.createElement('h3');
        animeViewerTitle.innerText = '🎬 애니메이션 보기';
        animeViewerContainer.appendChild(animeViewerTitle);
        const animeViewerFrame = document.createElement('iframe');
        animeViewerFrame.src = 'https://linkkf.net/';
        animeViewerFrame.style.width = '100%';
        animeViewerFrame.style.height = '300px';
        animeViewerFrame.style.border = '1px solid #CCC';
        animeViewerContainer.appendChild(animeViewerFrame);
        container.appendChild(animeViewerContainer);

        // TV 사이트 뷰어
        const tvViewerContainer = document.createElement('div');
        tvViewerContainer.id = 'tvViewerContainer';
        tvViewerContainer.classList.add('section');
        const tvViewerTitle = document.createElement('h3');
        tvViewerTitle.innerText = '📺 TV 사이트 보기';
        tvViewerContainer.appendChild(tvViewerTitle);
        const tvViewerFrame = document.createElement('iframe');
        tvViewerFrame.id = 'tvViewerFrame';
        tvViewerFrame.src = `https://tv.${JSON.parse(GM_getValue('tvPage'))}.wiKi/`;
        tvViewerFrame.style.width = '100%';
        tvViewerFrame.style.height = '300px';
        tvViewerFrame.style.border = '1px solid #CCC';
        tvViewerContainer.appendChild(tvViewerFrame);
        const refreshTVButton = document.createElement('button');
        refreshTVButton.innerText = '🔄 TV 사이트 새로 고침';
        refreshTVButton.classList.add('refresh-tv');
        refreshTVButton.onclick = refreshTVSite;
        tvViewerContainer.appendChild(refreshTVButton);
        container.appendChild(tvViewerContainer);

        document.body.appendChild(container);

        toggleDarkMode(JSON.parse(GM_getValue('darkMode')));
        loadCrawlData();
        loadLogData();
        loadFilterKeywords();
        updateScheduledSearches();
        updateManualSearches();
    }

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

    // 크롤링 시작
    function startCrawling() {
        if (JSON.parse(GM_getValue('isCrawling'))) return;
        const manualQueue = JSON.parse(GM_getValue('manualSearchQueue'));
        const scheduledQueue = JSON.parse(GM_getValue('scheduledSearchQueue'));
        if (manualQueue.length === 0 && scheduledQueue.length === 0) {
            alert('예약된 검색어가 없습니다!');
            return;
        }
        GM_setValue('isCrawling', JSON.stringify(true));
        GM_setValue('crawlStartTime', JSON.stringify(Date.now()));
        log('🔍 크롤링 시작');
        processNextSearch();
    }

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

    // 크롤링 초기화
    function resetCrawling() {
        stopCrawling();
        GM_setValue('crawlData', JSON.stringify({}));
        GM_setValue('logData', JSON.stringify([]));
        GM_setValue('currentPage', JSON.stringify(1));
        GM_setValue('tvPage', JSON.stringify(54));
        GM_setValue('crawlStartTime', JSON.stringify(null));
        GM_setValue('manualSearchQueue', JSON.stringify([]));
        GM_setValue('scheduledSearchQueue', JSON.stringify([]));
        clearTable();
        clearLog();
        log('🔄 크롤링 초기화 완료');
        updateProgressDetails();
        document.getElementById('filterInput').value = '';
        GM_setValue('currentFilter', JSON.stringify(''));
        filterTable('');
        updateScheduledSearches();
        updateManualSearches();
    }

    // 테이블 초기화
    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'));
        for (let query in crawlData) {
            crawlData[query].forEach(item => addRowToTable(item, query));
        }
    }

    // 로그 데이터 로드
    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 filter = JSON.parse(GM_getValue('currentFilter'));
        filterTable(filter);
    }

    // 로그 추가
    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 totalQueries = getTotalQueries();
        const processedQueries = getProcessedQueries();
        if (totalQueries === 0) return 0;
        return Math.min(Math.floor((processedQueries / totalQueries) * 100), 100);
    }

    // 전체 큐 길이 계산
    function getTotalQueries() {
        const manualQueue = JSON.parse(GM_getValue('manualSearchQueue'));
        const scheduledQueue = JSON.parse(GM_getValue('scheduledSearchQueue'));
        return manualQueue.length + scheduledQueue.length;
    }

    // 처리된 큐 길이 계산
    function getProcessedQueries() {
        const crawlData = JSON.parse(GM_getValue('crawlData'));
        let count = 0;
        for (let query in crawlData) {
            count += crawlData[query].length;
        }
        return count;
    }

    // 상세 진행률 업데이트
    function updateProgressDetails() {
        const progressDetails = document.getElementById('estimatedTime');
        const crawlStart = parseInt(GM_getValue('crawlStartTime'));
        if (!crawlStart) {
            progressDetails.innerText = '⏳ 예상 완료 시간: 아직 시작되지 않음';
            return;
        }
        const totalQueries = getTotalQueries();
        const processedQueries = getProcessedQueries();
        const elapsedTime = Date.now() - crawlStart;
        const rate = processedQueries > 0 ? elapsedTime / processedQueries : 0;
        const queriesLeft = totalQueries - processedQueries;
        const estimatedRemaining = rate * queriesLeft;
        const estimatedFinish = new Date(Date.now() + estimatedRemaining);
        progressDetails.innerText = `⏳ 예상 완료 시간: ${estimatedFinish.toLocaleTimeString()}`;
    }

    // 예약 검색어 추가
    function addScheduledSearch() {
        const input = document.getElementById('addScheduledSearchInput');
        const query = input.value.trim();
        if (!query) {
            alert('검색어를 입력하세요!');
            return;
        }
        let queue = JSON.parse(GM_getValue('scheduledSearchQueue'));
        queue.push(query);
        GM_setValue('scheduledSearchQueue', JSON.stringify(queue));
        log(`📅 예약된 검색어 추가: "${query}"`);
        input.value = '';
        updateScheduledSearches();
    }

    // 예약 검색어 목록 업데이트
    function updateScheduledSearches() {
        const list = document.getElementById('scheduledSearchesList');
        list.innerHTML = '';
        const queue = JSON.parse(GM_getValue('scheduledSearchQueue'));
        queue.forEach((search, index) => {
            const item = document.createElement('div');
            item.classList.add('search-item');

            const span = document.createElement('span');
            span.innerText = search;
            item.appendChild(span);

            const removeBtn = document.createElement('button');
            removeBtn.innerText = '❌';
            removeBtn.classList.add('remove-button');
            removeBtn.onclick = () => removeScheduledSearch(index);
            item.appendChild(removeBtn);

            list.appendChild(item);
        });
    }

    // 예약 검색어 제거
    function removeScheduledSearch(index) {
        let queue = JSON.parse(GM_getValue('scheduledSearchQueue'));
        const removed = queue.splice(index, 1);
        GM_setValue('scheduledSearchQueue', JSON.stringify(queue));
        log(`📅 예약된 검색어 제거: "${removed[0]}"`);
        updateScheduledSearches();
    }

    // 수동 검색어 목록 업데이트
    function updateManualSearches() {
        const list = document.getElementById('manualSearchesList');
        list.innerHTML = '';
        const queue = JSON.parse(GM_getValue('manualSearchQueue'));
        queue.forEach((search, index) => {
            const item = document.createElement('div');
            item.classList.add('search-item');

            const span = document.createElement('span');
            span.innerText = search;
            item.appendChild(span);

            const removeBtn = document.createElement('button');
            removeBtn.innerText = '❌';
            removeBtn.classList.add('remove-button');
            removeBtn.onclick = () => removeManualSearch(index);
            item.appendChild(removeBtn);

            list.appendChild(item);
        });
    }

    // 수동 검색어 제거
    function removeManualSearch(index) {
        let queue = JSON.parse(GM_getValue('manualSearchQueue'));
        const removed = queue.splice(index, 1);
        GM_setValue('manualSearchQueue', JSON.stringify(queue));
        log(`📝 수동 검색어 제거: "${removed[0]}"`);
        updateManualSearches();
    }

    // 크롤링 시작 시 다음 검색어 처리
    function processNextSearch() {
        if (!JSON.parse(GM_getValue('isCrawling'))) return;
        let manualQueue = JSON.parse(GM_getValue('manualSearchQueue'));
        let scheduledQueue = JSON.parse(GM_getValue('scheduledSearchQueue'));

        let nextQuery = null;
        let isManual = false;

        if (manualQueue.length > 0) {
            nextQuery = manualQueue.shift();
            isManual = true;
            GM_setValue('manualSearchQueue', JSON.stringify(manualQueue));
            log(`📝 수동 검색어 시작: "${nextQuery}"`);
        } else if (scheduledQueue.length > 0) {
            nextQuery = scheduledQueue.shift();
            GM_setValue('scheduledSearchQueue', JSON.stringify(scheduledQueue));
            log(`📅 예약된 검색어 시작: "${nextQuery}"`);
        }

        if (nextQuery) {
            performSearch(nextQuery, isManual);
        } else {
            GM_setValue('isCrawling', JSON.stringify(false));
            GM_setValue('crawlStartTime', JSON.stringify(null));
            log('🏁 모든 크롤링 완료');
            alert('모든 예약된 크롤링이 완료되었습니다!');
        }
    }

    // 검색 수행 및 크롤링
    function performSearch(query, isManual = false) {
        const params = new URLSearchParams();
        params.set(engine.queryParam, query);
        if (engine.additionalParams) {
            const additional = engine.additionalParams.split('&').filter(param => !param.includes('tbs=')).join('&');
            additional.split('&').forEach(param => {
                const [key, value] = param.split('=');
                if (key && value) params.set(key, value);
            });
        }
        const searchUrl = `https://${currentHost}/search?${params.toString()}`;
        log(`🔍 검색 시작: "${query}"`);
        GM_setValue('currentPage', JSON.stringify(1));
        crawlPages(searchUrl, query, isManual);
    }

    // 모든 페이지 크롤링
    function crawlPages(url, query, isManual) {
        GM_xmlhttpRequest({
            method: 'GET',
            url: url,
            headers: {
                'User-Agent': navigator.userAgent,
                'Accept': 'text/html'
            },
            onload: function(response) {
                if (response.status >= 200 && response.status < 300) {
                    const parser = new DOMParser();
                    const doc = parser.parseFromString(response.responseText, 'text/html');
                    const results = doc.querySelectorAll(engine.resultSelector);
                    processResults(results, query);
                    const nextButton = doc.querySelector(engine.nextButtonSelector);
                    const currentPage = parseInt(GM_getValue('currentPage'));
                    if (nextButton && currentPage < 100 && JSON.parse(GM_getValue('isCrawling'))) {
                        GM_setValue('currentPage', JSON.stringify(currentPage + 1));
                        log(`➡️ 다음 페이지로 이동: 페이지 ${currentPage + 1}`);
                        const nextPageUrl = nextButton.href;
                        setTimeout(() => {
                            crawlPages(nextPageUrl, query, isManual);
                        }, 1000); // 1초 지연
                    } else {
                        log('🏁 현재 검색어의 모든 페이지 크롤링 완료');
                        processNextSearch();
                    }
                } else {
                    log(`❌ 페이지 로드 실패 (${response.status}): ${url}`);
                    processNextSearch();
                }
            },
            onerror: function() {
                log(`⚠️ 페이지 요청 에러: ${url}`);
                processNextSearch();
            }
        });
    }

    // 결과 처리
    function processResults(results, query) {
        let crawlData = JSON.parse(GM_getValue('crawlData'));
        if (!crawlData[query]) {
            crawlData[query] = [];
        }
        const blacklist = JSON.parse(GM_getValue('blacklist'));
        const whitelist = JSON.parse(GM_getValue('whitelist'));
        const currentFilter = JSON.parse(GM_getValue('currentFilter'));

        results.forEach(result => {
            const linkEl = result.querySelector(engine.linkSelector);
            const titleEl = result.querySelector(engine.titleSelector);
            const descEl = result.querySelector(engine.descriptionSelector);

            if (linkEl && titleEl) {
                try {
                    const url = new URL(linkEl.href);
                    const domain = url.hostname.replace('www.', '');
                    if (whitelist.length > 0 && !whitelist.includes(domain)) {
                        log(`✅ 화이트리스트 미포함 도메인 필터링: ${domain}`);
                        return;
                    }
                    if (blacklist.includes(domain)) {
                        log(`🚫 블랙리스트 도메인 필터링: ${domain}`);
                        return;
                    }
                } catch {
                    log(`⚠️ 유효하지 않은 URL: ${linkEl.href}`);
                    return;
                }

                if (isAd(result)) {
                    return;
                }

                const title = titleEl.innerText.trim();
                const url = linkEl.href;
                const description = descEl ? descEl.innerText.trim() : '';

                // 필터링
                if (currentFilter) {
                    const keywords = currentFilter.split(',').map(k => k.trim()).filter(k => k);
                    const match = keywords.every(kw => {
                        const orKeywords = kw.split('|').map(k => k.trim()).filter(k => k);
                        return orKeywords.some(subKw => {
                            return title.toLowerCase().includes(subKw.toLowerCase()) || description.toLowerCase().includes(subKw.toLowerCase());
                        });
                    });
                    if (!match) {
                        log(`🚫 필터링됨: ${title} - ${url}`);
                        return;
                    }
                }

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

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

    // 테이블에 행 추가
    function addRowToTable(item, query) {
        const tbody = document.getElementById('crawlTableBody');
        if (!tbody) return;

        // 그룹 헤더 추가
        let groupHeader = document.getElementById(`group-${sanitize(query)}`);
        if (!groupHeader) {
            groupHeader = document.createElement('tr');
            groupHeader.id = `group-${sanitize(query)}`;
            const th = document.createElement('th');
            th.colSpan = 6;
            th.style.backgroundColor = '#ffa500';
            th.style.color = '#fff';
            th.style.textAlign = 'center';
            th.innerText = `📝 검색어: ${query}`;
            groupHeader.appendChild(th);
            tbody.appendChild(groupHeader);
        }

        const row = document.createElement('tr');

        const titleCell = document.createElement('td');
        titleCell.innerText = item.title;
        titleCell.style.fontWeight = 'bold';
        row.appendChild(titleCell);

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

        const descCell = document.createElement('td');
        descCell.innerText = item.description;
        descCell.style.color = '#555';
        row.appendChild(descCell);

        const qualityCell = document.createElement('td');
        qualityCell.innerText = item.quality;
        qualityCell.style.color = item.quality > 50 ? '#28a745' : '#dc3545';
        qualityCell.style.fontWeight = 'bold';
        row.appendChild(qualityCell);

        const classificationCell = document.createElement('td');
        classificationCell.innerText = item.classification;
        classificationCell.style.fontWeight = 'bold';
        row.appendChild(classificationCell);

        const viewButtonCell = document.createElement('td');
        const viewBtn = document.createElement('button');
        viewBtn.innerText = '📄 보기';
        viewBtn.style.backgroundColor = '#17a2b8';
        viewBtn.style.color = '#fff';
        viewBtn.style.border = 'none';
        viewBtn.style.borderRadius = '4px';
        viewBtn.style.cursor = 'pointer';
        viewBtn.onclick = () => viewHTML(item.url);
        viewButtonCell.appendChild(viewBtn);
        row.appendChild(viewButtonCell);

        tbody.appendChild(row);
    }

    // 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);
                    log(`✅ HTML 로드 완료: ${url}`);
                } else {
                    log(`❌ HTML 로드 실패 (${response.status}): ${url}`);
                    alert(`HTML 로드 실패: ${response.status} ${response.statusText}`);
                }
            },
            onerror: function() {
                log(`⚠️ HTML 요청 에러: ${url}`);
                alert(`HTML 요청 에러: ${url}`);
            }
        });
    }

    // HTML 디스플레이
    function displayHTML(htmlContent) {
        const viewer = document.getElementById('htmlViewerContainer');
        const iframe = document.getElementById('htmlViewerContent');
        const blob = new Blob([htmlContent], { type: 'text/html' });
        const url = URL.createObjectURL(blob);
        iframe.src = url;
        viewer.style.display = 'block';
    }

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

    // 테이블 필터링
    function filterTable(keyword) {
        const tbody = document.getElementById('crawlTableBody');
        const rows = tbody.getElementsByTagName('tr');
        for (let row of rows) {
            if (row.id.startsWith('group-')) continue; // 그룹 헤더는 표시
            const cells = row.getElementsByTagName('td');
            let match = true;
            if (keyword) {
                const keywords = keyword.split(',').map(k => k.trim()).filter(k => k);
                match = keywords.every(kw => {
                    const orKeywords = kw.split('|').map(k => k.trim()).filter(k => k);
                    return orKeywords.some(subKw => {
                        return Array.from(cells).some(cell => cell.innerText.toLowerCase().includes(subKw.toLowerCase()));
                    });
                });
            }
            row.style.display = match ? '' : 'none';
        }
    }

    // 콘텐츠 품질 평가
    function evaluateContentQuality(text) {
        const wordCount = text.split(/\s+/).length;
        let score = wordCount * 5;
        return score > 100 ? 100 : 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(element) {
        const adClasses = ['ad', 'ads', 'commercial', 'sponsored', 'promo'];
        return adClasses.some(cls => element.classList.contains(cls));
    }

    // 크롤링 상태 리스너
    GM_addValueChangeListener('isCrawling', () => {
        updateUI();
    });

    // 다크 모드 리스너
    GM_addValueChangeListener('darkMode', () => {
        const isDark = JSON.parse(GM_getValue('darkMode'));
        toggleDarkMode(isDark);
    });

    // 필터 키워드 리스너
    GM_addValueChangeListener('currentFilter', () => {
        loadFilterKeywords();
    });

    // 화이트리스트 리스너
    GM_addValueChangeListener('whitelist', () => {
        log(`✅ 화이트리스트 업데이트: ${JSON.parse(GM_getValue('whitelist')).join(', ')}`);
    });

    // 블랙리스트 리스너
    GM_addValueChangeListener('blacklist', () => {
        log(`🚫 블랙리스트 업데이트: ${JSON.parse(GM_getValue('blacklist')).join(', ')}`);
    });

    // 예약 검색 큐 리스너
    GM_addValueChangeListener('scheduledSearchQueue', () => {
        updateScheduledSearches();
    });

    // 수동 검색 큐 리스너
    GM_addValueChangeListener('manualSearchQueue', () => {
        updateManualSearches();
    });

    updateUI();
    updateProgressDetails();

    // 크롤링 상태 변경 시 자동 다음 검색어 처리
    GM_addValueChangeListener('isCrawling', (name, oldValue, newValue) => {
        if (JSON.parse(newValue)) {
            processNextSearch();
        }
    });

    // 안전하게 쿼리 이름을 ID로 변환
    function sanitize(str) {
        return str.replace(/[^a-z0-9]/gi, '_').toLowerCase();
    }

})();           예약 검색어 기능 삭제해