// ==UserScript==
// @name         Advanced Multi-Engine Web Crawler
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  여러 검색 엔진을 지원하는 고급 웹 크롤링 및 데이터 수집 도구
// @author       Your Name
// @match        *://www.google.com/search*
// @match        *://www.bing.com/search*
// @match        *://duckduckgo.com/*
// @match        *://search.yahoo.com/*
// @grant        GM_download
// @grant        GM_setValue
// @grant        GM_getValue
// @require      https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js
// @require      https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js
// ==/UserScript==

(function() {
    'use strict';

    // 기본 설정
    const defaultSettings = {
        delay: 2000, // 요청 간 딜레이 (밀리초)
        maxPages: 5, // 최대 페이지 수
        blacklist: ['ads', 'sponsor', 'advertisement'], // 블랙리스트 도메인
        whitelist: [], // 화이트리스트 도메인
    };

    // 설정 불러오기 또는 기본 설정 저장
    const settings = GM_getValue('crawlerSettings', defaultSettings);

    // 상태 변수 불러오기
    let currentPage = GM_getValue('currentPage', 1);
    let results = GM_getValue('results', []);
    let visited = new Set(GM_getValue('visited', []));

    // 검색 엔진 식별
    const searchEngine = identifySearchEngine();

    // UI 생성
    function createUI() {
        if ($('#crawler-ui').length) return; // UI가 이미 존재하면 생성하지 않음

        const ui = $(`
            <div id="crawler-ui" style="position: fixed; top: 10px; right: 10px; width: 350px; background: white; border: 2px solid #444; border-radius: 8px; z-index: 10000; font-family: Arial, sans-serif; box-shadow: 0 0 10px rgba(0,0,0,0.5);">
                <div style="display: flex; border-bottom: 2px solid #444; background: #f0f0f0; border-top-left-radius: 8px; border-top-right-radius: 8px;">
                    <div class="tab active" data-tab="log" style="flex: 1; padding: 10px; cursor: pointer; text-align: center;">📜 로그</div>
                    <div class="tab" data-tab="data" style="flex: 1; padding: 10px; cursor: pointer; text-align: center;">📊 데이터</div>
                    <div class="tab" data-tab="settings" style="flex: 1; padding: 10px; cursor: pointer; text-align: center;">⚙️ 설정</div>
                </div>
                <div class="tab-content" id="log" style="padding: 10px; display: block; max-height: 200px; overflow-y: auto; font-size: 12px;">
                    <h3>로그</h3>
                    <div id="log-content" style="max-height: 160px; overflow-y: auto;"></div>
                </div>
                <div class="tab-content" id="data" style="padding: 10px; display: none;">
                    <h3>데이터</h3>
                    <input type="text" id="search-filter" placeholder="실시간 검색..." style="width: 100%; padding: 5px; margin-bottom: 10px; box-sizing: border-box;">
                    <table id="data-table" border="1" width="100%" style="border-collapse: collapse; font-size: 12px;">
                        <thead>
                            <tr>
                                <th>사이트 이름</th>
                                <th>주소</th>
                                <th>설명</th>
                                <th>분류</th>
                                <th>품질 점수</th>
                                <th>HTML 보기</th>
                            </tr>
                        </thead>
                        <tbody></tbody>
                    </table>
                </div>
                <div class="tab-content" id="settings" style="padding: 10px; display: none;">
                    <h3>설정</h3>
                    <label>딜레이 (ms): <input type="number" id="delay" value="${settings.delay}" style="width: 100%; margin-bottom: 10px;"></label>
                    <label>최대 페이지: <input type="number" id="maxPages" value="${settings.maxPages}" style="width: 100%; margin-bottom: 10px;"></label>
                    <label>블랙리스트 (콤마로 구분): <input type="text" id="blacklist" value="${settings.blacklist.join(', ')}" style="width: 100%; margin-bottom: 10px;"></label>
                    <label>화이트리스트 (콤마로 구분): <input type="text" id="whitelist" value="${settings.whitelist.join(', ')}" style="width: 100%; margin-bottom: 10px;"></label>
                    <button id="save-settings" style="width: 100%; padding: 5px;">저장</button>
                </div>
                <div style="padding: 10px; border-top: 2px solid #444; background: #f9f9f9; border-bottom-left-radius: 8px; border-bottom-right-radius: 8px;">
                    <button id="start-crawl" style="width: 48%; padding: 5px;">크롤링 시작</button>
                    <button id="stop-crawl" style="width: 48%; padding: 5px; margin-left: 4%;">크롤링 중지</button><br><br>
                    <button id="download-csv" style="width: 48%; padding: 5px;">CSV 다운로드</button>
                    <button id="download-json" style="width: 48%; padding: 5px; margin-left: 4%;">JSON 다운로드</button><br><br>
                    <div>진행률: <span id="progress">0%</span></div>
                </div>
            </div>

            <!-- Modal 창 -->
            <div id="html-modal" style="display:none; position: fixed; top: 0; left: 0; width:100%; height:100%; background: rgba(0,0,0,0.5); z-index:10001;">
                <div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 80%; height: 80%; background: white; padding: 20px; overflow: auto; border-radius: 8px;">
                    <h3>HTML 내용</h3>
                    <pre id="modal-content" style="white-space: pre-wrap; word-wrap: break-word; max-height: 70%; overflow-y: auto;"></pre>
                    <button id="close-modal" style="padding: 5px 10px;">닫기</button>
                </div>
            </div>
        `);

        $('body').append(ui);

        // 탭 전환 이벤트
        $('.tab').on('click', function() {
            const tab = $(this).data('tab');
            $('.tab').removeClass('active');
            $(this).addClass('active');
            $('.tab-content').hide();
            $(`#${tab}`).show();
        });

        // 설정 저장 이벤트
        $('#save-settings').on('click', function() {
            settings.delay = parseInt($('#delay').val());
            settings.maxPages = parseInt($('#maxPages').val());
            settings.blacklist = $('#blacklist').val().split(',').map(s => s.trim());
            settings.whitelist = $('#whitelist').val().split(',').map(s => s.trim());
            GM_setValue('crawlerSettings', settings);
            log('설정이 저장되었습니다.');
        });

        // 크롤링 시작 버튼
        $('#start-crawl').on('click', startCrawling);

        // 크롤링 중지 버튼
        $('#stop-crawl').on('click', stopCrawling);

        // CSV 다운로드 버튼
        $('#download-csv').on('click', downloadCSV);

        // JSON 다운로드 버튼
        $('#download-json').on('click', downloadJSON);

        // HTML 모달 닫기
        $('#close-modal').on('click', function() {
            $('#html-modal').hide();
        });

        // HTML 내용 보기 이벤트
        $('body').on('click', '.view-html', function() {
            const htmlContent = $(this).data('html');
            $('#modal-content').text(htmlContent);
            $('#html-modal').show();
        });

        // 데이터 검색 필터링
        $('#search-filter').on('input', function() {
            const filter = $(this).val().toLowerCase();
            $('#data-table tbody tr').each(function() {
                const rowText = $(this).text().toLowerCase();
                $(this).toggle(rowText.includes(filter));
            });
        });
    }

    // 로그 추가 함수
    function log(message) {
        const timestamp = new Date().toLocaleTimeString();
        $('#log-content').append(`<div>[${timestamp}] ${message}</div>`);
        $('#log-content').scrollTop($('#log-content')[0].scrollHeight);
        console.log(message);
    }

    // 크롤링 제어 변수
    let crawling = false;

    // 크롤링 시작
    async function startCrawling() {
        if (crawling) {
            log('이미 크롤링이 진행 중입니다.');
            return;
        }
        crawling = true;
        log('크롤링을 시작합니다.');
        await crawlPage();
    }

    // 크롤링 중지
    function stopCrawling() {
        if (!crawling) {
            log('크롤링이 진행 중이지 않습니다.');
            return;
        }
        crawling = false;
        log('크롤링이 중지되었습니다.');
    }

    // 페이지 크롤링 함수
    async function crawlPage() {
        while (crawling && currentPage <= settings.maxPages) {
            log(`페이지 ${currentPage} 크롤링 중...`);
            const items = extractSearchResults();

            for (let item of items) {
                if (!crawling) break;

                const { name, url, description, html } = item;

                // 중복 검사
                if (visited.has(url)) {
                    log(`이미 방문한 사이트: ${url}`);
                    continue;
                }

                // 화이트리스트/블랙리스트 검사
                if (settings.whitelist.length > 0) {
                    const isWhitelisted = settings.whitelist.some(domain => url.includes(domain));
                    if (!isWhitelisted) {
                        log(`화이트리스트에 없는 사이트: ${url}`);
                        continue;
                    }
                }
                if (settings.blacklist.some(domain => url.includes(domain))) {
                    log(`블랙리스트에 있는 사이트: ${url}`);
                    continue;
                }

                visited.add(url);
                GM_setValue('visited', Array.from(visited));

                // 콘텐츠 품질 평가 (단어 수 기준)
                const wordCount = description.split(' ').length;
                const qualityScore = wordCount > 100 ? '높음' : '낮음';

                // 키워드 분류
                const categories = classifyKeywords(description);

                // 결과 저장
                results.push({
                    name,
                    url,
                    description,
                    category: categories.join(', '),
                    quality: qualityScore,
                    html
                });

                GM_setValue('results', results);
                updateDataTable();
                log(`수집 완료: ${name} (${url})`);

                // 진행률 업데이트
                const progress = Math.min((results.length / (settings.maxPages * items.length)) * 100, 100);
                $('#progress').text(`${progress.toFixed(2)}%`);

                await delay(settings.delay);
            }

            // 다음 페이지로 이동
            if (currentPage < settings.maxPages) {
                const nextButton = getNextButton();
                if (nextButton) {
                    nextButton.click();
                    currentPage++;
                    GM_setValue('currentPage', currentPage);
                    await delay(settings.delay * 2);
                } else {
                    log('더 이상 페이지가 없습니다.');
                    break;
                }
            } else {
                log('최대 페이지 수에 도달했습니다.');
                break;
            }
        }
        crawling = false;
        log('크롤링이 완료되었습니다.');
    }

    // 검색 결과 추출 함수
    function extractSearchResults() {
        let items = [];
        switch(searchEngine) {
            case 'Google':
                $('.g').each(function() {
                    const name = $(this).find('h3').text() || 'No Title';
                    const url = $(this).find('a').attr('href') || 'No URL';
                    const description = $(this).find('.VwiC3b').text() || 'No Description';
                    const html = $('<div>').append($(this).clone()).html();
                    items.push({ name, url, description, html });
                });
                break;
            case 'Bing':
                $('.b_algo').each(function() {
                    const name = $(this).find('h2').text() || 'No Title';
                    const url = $(this).find('a').attr('href') || 'No URL';
                    const description = $(this).find('.b_caption p').text() || 'No Description';
                    const html = $('<div>').append($(this).clone()).html();
                    items.push({ name, url, description, html });
                });
                break;
            case 'DuckDuckGo':
                $('.result').each(function() {
                    const name = $(this).find('.result__title').text() || 'No Title';
                    const url = $(this).find('a.result__a').attr('href') || 'No URL';
                    const description = $(this).find('.result__snippet').text() || 'No Description';
                    const html = $('<div>').append($(this).clone()).html();
                    items.push({ name, url, description, html });
                });
                break;
            case 'Yahoo':
                $('.dd.algo').each(function() {
                    const name = $(this).find('h3').text() || 'No Title';
                    const url = $(this).find('a').attr('href') || 'No URL';
                    const description = $(this).find('.compText p').text() || 'No Description';
                    const html = $('<div>').append($(this).clone()).html();
                    items.push({ name, url, description, html });
                });
                break;
            default:
                log('지원하지 않는 검색 엔진입니다.');
        }
        return items;
    }

    // 다음 페이지 버튼 가져오기
    function getNextButton() {
        switch(searchEngine) {
            case 'Google':
                return document.querySelector('#pnnext');
            case 'Bing':
                return document.querySelector('a.sb_pagN');
            case 'DuckDuckGo':
                return document.querySelector('a.result--more__btn');
            case 'Yahoo':
                return document.querySelector('a.next');
            default:
                return null;
        }
    }

    // 데이터 테이블 업데이트
    function updateDataTable() {
        const tbody = $('#data-table tbody');
        tbody.empty();
        for (let item of results) {
            tbody.append(`
                <tr>
                    <td>${item.name}</td>
                    <td><a href="${item.url}" target="_blank">${item.url}</a></td>
                    <td>${item.description}</td>
                    <td>${item.category}</td>
                    <td>${item.quality}</td>
                    <td><button class="view-html" data-html="${escapeHtml(item.html)}">보기</button></td>
                </tr>
            `);
        }
    }

    // CSV 다운로드
    function downloadCSV() {
        if (results.length === 0) {
            log('다운로드할 데이터가 없습니다.');
            return;
        }
        const ws = XLSX.utils.json_to_sheet(results);
        const wb = XLSX.utils.book_new();
        XLSX.utils.book_append_sheet(wb, ws, 'Results');
        XLSX.writeFile(wb, 'results.csv');
        log('CSV 파일이 다운로드되었습니다.');
    }

    // JSON 다운로드
    function downloadJSON() {
        if (results.length === 0) {
            log('다운로드할 데이터가 없습니다.');
            return;
        }
        const blob = new Blob([JSON.stringify(results, null, 2)], {type: 'application/json'});
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = 'results.json';
        a.click();
        URL.revokeObjectURL(url);
        log('JSON 파일이 다운로드되었습니다.');
    }

    // 지연 함수
    function delay(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }

    // 키워드 분류 함수 (예시)
    function classifyKeywords(text) {
        const categories = [];
        const keywords = {
            '교육': ['학교', '교육', '강의', '학습'],
            '테크': ['기술', '소프트웨어', '프로그래밍', '개발'],
            '비즈니스': ['비즈니스', '마케팅', '스타트업', '기업'],
            '엔터테인먼트': ['영화', '음악', '게임', '연예'],
            '건강': ['건강', '병원', '의료', '운동']
        };
        for (let [category, keys] of Object.entries(keywords)) {
            if (keys.some(key => text.includes(key))) {
                categories.push(category);
            }
        }
        return categories.length > 0 ? categories : ['기타'];
    }

    // 검색 엔진 식별 함수
    function identifySearchEngine() {
        const url = window.location.hostname;
        if (url.includes('google.com')) return 'Google';
        if (url.includes('bing.com')) return 'Bing';
        if (url.includes('duckduckgo.com')) return 'DuckDuckGo';
        if (url.includes('yahoo.com')) return 'Yahoo';
        return 'Unknown';
    }

    // HTML 이스케이프 함수
    function escapeHtml(text) {
        return text.replace(/&/g, "&amp;")
                   .replace(/</g, "&lt;")
                   .replace(/>/g, "&gt;")
                   .replace(/"/g, "&quot;")
                   .replace(/'/g, "&#039;");
    }

    // 초기 UI 생성 및 데이터 로드
    $(document).ready(function() {
        createUI();
        updateDataTable();
        $('#progress').text(`${((results.length / (settings.maxPages * 10)) * 100).toFixed(2)}%`); // 예시: 10개 결과 per page
    });

    // 단축키 지원
    $(document).on('keydown', function(e) {
        if (e.ctrlKey) {
            switch (e.key) {
                case '1':
                    $('.tab[data-tab="log"]').click();
                    break;
                case '2':
                    $('.tab[data-tab="data"]').click();
                    break;
                case '3':
                    $('.tab[data-tab="settings"]').click();
                    break;
                case 's':
                    $('#save-settings').click();
                    break;
                case 'p':
                    $('#start-crawl').click();
                    break;
                case 'q':
                    $('#stop-crawl').click();
                    break;
                case 'd':
                    if (e.shiftKey) {
                        downloadJSON();
                    } else {
                        downloadCSV();
                    }
                    break;
            }
        }
    });

})();

데이터 칸을 더 효율성 좋게 그리고 가독성 좋게 그리고 디자인 좋게 다시 해줘. 또한 명령문을 더 정확하게 이행해. 알겟찌?