// ==UserScript==
// @name         고도화된 다중 검색 엔진 크롤러
// @namespace    http://tampermonkey.net/
// @version      2.0
// @description  다양한 검색 엔진의 결과를 크롤링하여 데이터를 수집하고 표시합니다. 페이지 이동 시 데이터 초기화 문제 수정, UI 개선, 로그 기능 추가 등 다수의 고도화된 기능을 포함합니다.
// @author       YourName
// @match        *://www.google.com/*
// @match        *://www.bing.com/*
// @match        *://duckduckgo.com/*
// @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'
        },
        'www.bing.com': {
            name: 'Bing',
            resultSelector: 'li.b_algo',
            titleSelector: 'h2',
            linkSelector: 'a',
            descriptionSelector: 'p',
            nextButtonSelector: 'a.sb_pagN'
        },
        'duckduckgo.com': {
            name: 'DuckDuckGo',
            resultSelector: 'div.result',
            titleSelector: 'h2 > a.result__a',
            linkSelector: 'a.result__a',
            descriptionSelector: '.result__snippet',
            nextButtonSelector: 'a.result--more__btn'
        }
        // 필요한 경우 추가 검색 엔진 설정 가능
    };

    // 현재 검색 엔진 감지
    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);
    }
    if (GM_getValue('crawlDelay') === undefined) {
        GM_setValue('crawlDelay', 1000);
    }
    if (GM_getValue('logData') === undefined) {
        GM_setValue('logData', JSON.stringify([]));
    }

    // 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('currentPageIndicator').innerText = `현재 페이지: ${GM_getValue('currentPage')}`;
    }

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

    // UI 생성 함수
    function createUI() {
        const container = document.createElement('div');
        container.id = 'crawlUI';
        container.style.position = 'fixed';
        container.style.top = '20px';
        container.style.right = '20px';
        container.style.padding = '20px';
        container.style.backgroundColor = 'rgba(255, 255, 255, 0.95)';
        container.style.border = '2px solid #4CAF50';
        container.style.borderRadius = '10px';
        container.style.zIndex = 10000;
        container.style.boxShadow = '0 0 15px rgba(0,0,0,0.3)';
        container.style.fontFamily = 'Arial, sans-serif';
        container.style.fontSize = '14px';
        container.style.width = '350px';
        container.style.maxHeight = '90vh';
        container.style.overflowY = 'auto';

        // 제목
        const title = document.createElement('h2');
        title.innerText = '검색 엔진 크롤러';
        title.style.margin = '0 0 15px 0';
        title.style.fontSize = '18px';
        container.appendChild(title);

        // 크롤링 속도 설정
        const speedContainer = document.createElement('div');
        speedContainer.style.marginBottom = '15px';

        const speedLabel = document.createElement('label');
        speedLabel.innerText = '크롤링 속도 (ms): ';
        speedLabel.setAttribute('for', 'crawlDelayInput');
        speedLabel.style.display = 'block';
        speedLabel.style.marginBottom = '5px';
        speedContainer.appendChild(speedLabel);

        const speedInput = document.createElement('input');
        speedInput.type = 'number';
        speedInput.id = 'crawlDelayInput';
        speedInput.value = GM_getValue('crawlDelay');
        speedInput.min = 100;
        speedInput.style.width = '100%';
        speedInput.style.padding = '5px';
        speedInput.style.border = '1px solid #ccc';
        speedInput.style.borderRadius = '4px';
        speedInput.onchange = () => {
            const val = parseInt(speedInput.value);
            if (isNaN(val) || val < 100) {
                alert('유효한 숫자를 입력하세요 (최소 100ms).');
                speedInput.value = GM_getValue('crawlDelay');
            } else {
                GM_setValue('crawlDelay', val);
                log(`크롤링 속도 변경: ${val}ms`);
            }
        };
        speedContainer.appendChild(speedInput);
        container.appendChild(speedContainer);

        // 버튼 생성
        const buttonContainer = document.createElement('div');
        buttonContainer.style.display = 'flex';
        buttonContainer.style.justifyContent = 'space-between';
        buttonContainer.style.marginBottom = '15px';

        const startButton = document.createElement('button');
        startButton.id = 'startButton';
        startButton.innerText = '시작';
        startButton.style.flex = '1';
        startButton.style.marginRight = '5px';
        startButton.style.padding = '10px';
        startButton.style.backgroundColor = '#4CAF50';
        startButton.style.color = 'white';
        startButton.style.border = 'none';
        startButton.style.borderRadius = '4px';
        startButton.style.cursor = 'pointer';
        startButton.onclick = startCrawling;
        buttonContainer.appendChild(startButton);

        const stopButton = document.createElement('button');
        stopButton.id = 'stopButton';
        stopButton.innerText = '중지';
        stopButton.style.flex = '1';
        stopButton.style.marginLeft = '5px';
        stopButton.style.padding = '10px';
        stopButton.style.backgroundColor = '#f44336';
        stopButton.style.color = 'white';
        stopButton.style.border = 'none';
        stopButton.style.borderRadius = '4px';
        stopButton.style.cursor = 'pointer';
        stopButton.onclick = stopCrawling;
        stopButton.disabled = !GM_getValue('isCrawling');
        buttonContainer.appendChild(stopButton);

        container.appendChild(buttonContainer);

        // 초기화 버튼
        const resetButton = document.createElement('button');
        resetButton.innerText = '초기화';
        resetButton.style.width = '100%';
        resetButton.style.padding = '10px';
        resetButton.style.backgroundColor = '#555555';
        resetButton.style.color = 'white';
        resetButton.style.border = 'none';
        resetButton.style.borderRadius = '4px';
        resetButton.style.cursor = 'pointer';
        resetButton.onclick = resetCrawling;
        container.appendChild(resetButton);

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

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

        const progressBar = document.createElement('progress');
        progressBar.id = 'progressBar';
        progressBar.value = getProgressPercentage();
        progressBar.max = 100;
        progressBar.style.width = '80%';
        progressBar.style.marginRight = '10px';
        progressContainer.appendChild(progressBar);

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

        container.appendChild(progressContainer);

        // 현재 페이지 표시
        const pageIndicator = document.createElement('div');
        pageIndicator.id = 'currentPageIndicator';
        pageIndicator.innerText = `현재 페이지: ${GM_getValue('currentPage')}`;
        pageIndicator.style.marginBottom = '15px';
        container.appendChild(pageIndicator);

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

        const logTitle = document.createElement('h3');
        logTitle.innerText = '로그';
        logTitle.style.marginBottom = '5px';
        logTitle.style.fontSize = '16px';
        logContainer.appendChild(logTitle);

        const logArea = document.createElement('textarea');
        logArea.id = 'crawlLog';
        logArea.style.width = '100%';
        logArea.style.height = '150px';
        logArea.style.padding = '5px';
        logArea.style.border = '1px solid #ccc';
        logArea.style.borderRadius = '4px';
        logArea.style.resize = 'none';
        logArea.disabled = true;
        logContainer.appendChild(logArea);

        container.appendChild(logContainer);

        // 결과 테이블 생성
        const tableContainer = document.createElement('div');
        tableContainer.style.marginTop = '20px';
        tableContainer.style.maxHeight = '300px';
        tableContainer.style.overflowY = 'auto';
        tableContainer.style.border = '1px solid #ddd';
        tableContainer.style.borderRadius = '4px';
        tableContainer.style.padding = '10px';
        tableContainer.style.backgroundColor = '#f9f9f9';

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

        const header = table.insertRow();
        ['사이트 이름', '주소', '설명'].forEach(text => {
            const th = document.createElement('th');
            th.innerText = text;
            th.style.border = '1px solid #ddd';
            th.style.padding = '8px';
            th.style.backgroundColor = '#f2f2f2';
            th.style.textAlign = 'left';
            table.appendChild(th);
        });

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

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

        document.body.appendChild(container);

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

    // 크롤링 시작 함수
    function startCrawling() {
        if (GM_getValue('isCrawling')) return;
        GM_setValue('isCrawling', true);
        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);
        clearTable();
        clearLog();
        log('크롤링 초기화 완료');
    }

    // 테이블 초기화
    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);
        });
    }

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

    // 로그 추가 함수
    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() {
        // 임시: 최대 페이지 수를 100으로 설정
        const maxPages = 100;
        const currentPage = GM_getValue('currentPage');
        return Math.min(Math.floor((currentPage / maxPages) * 100), 100);
    }

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

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

        let crawlData = JSON.parse(GM_getValue('crawlData'));
        const results = document.querySelectorAll(engine.resultSelector);
        results.forEach(result => {
            // 광고 제외 로직 (예: 특정 클래스나 도메인 필터링)
            if (result.querySelector('.ad') || result.querySelector('.ads')) return;

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

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

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

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

        // 다음 페이지로 이동
        const nextButton = document.querySelector(engine.nextButtonSelector);
        if (nextButton && GM_getValue('currentPage') < 100 && GM_getValue('isCrawling')) { // 최대 페이지 수 설정
            GM_setValue('currentPage', GM_getValue('currentPage') + 1);
            updateUI();
            setTimeout(() => {
                nextButton.click();
            }, GM_getValue('crawlDelay'));
            log(`다음 페이지로 이동: ${GM_getValue('currentPage')}`);
        } else {
            GM_setValue('isCrawling', false);
            log('크롤링 완료');
            alert('크롤링이 완료되었습니다!');
        }
    }

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

        const row = tbody.insertRow();

        const titleCell = row.insertCell();
        titleCell.innerText = title;
        titleCell.style.border = '1px solid #ddd';
        titleCell.style.padding = '8px';

        const urlCell = row.insertCell();
        const link = document.createElement('a');
        link.href = url;
        link.innerText = url;
        link.target = '_blank';
        urlCell.appendChild(link);
        urlCell.style.border = '1px solid #ddd';
        urlCell.style.padding = '8px';
        urlCell.style.wordBreak = 'break-all';

        const descCell = row.insertCell();
        descCell.innerText = description;
        descCell.style.border = '1px solid #ddd';
        descCell.style.padding = '8px';
    }

    // 페이지 이동 시 크롤링 계속
    window.addEventListener('load', () => {
        if (GM_getValue('isCrawling')) {
            setTimeout(() => {
                crawlPage();
            }, GM_getValue('crawlDelay'));
        }
    });

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

    updateUI();

})();                   HLML 를 따오는 거 추가 후  https://codebeautify.org/htmlviewer 이거 바탕으로 바로 보여주게 해주셈 / https://linkkf.net/ 이거 애니 보여주는 사이트거든? 이거 UI에 넣고 바로 이 사이트 띄우기 가능함?? 코드만 쓰셈