// ==UserScript==
// @name         Advanced Web Crawler
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  고급 웹 크롤러 스크립트 with Start/Stop 버튼
// @author       Your Name
// @match        https://www.google.com/search*
// @match        https://www.bing.com/search*
// @match        https://search.yahoo.com/search*
// @match        https://duckduckgo.com/*
// @match        https://www.baidu.com/s*
// @match        https://www.yandex.com/search/*
// @match        https://search.naver.com/search*
// @match        https://search.daum.net/search*
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @connect      *
// @require      https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js
// ==/UserScript==

(function() {
    'use strict';

    // 기본 설정
    const settings = {
        delay: parseInt(GM_getValue('delay', 3000), 10), // 기본 딜레이 3초
        blacklist: GM_getValue('blacklist', []),
        whitelist: GM_getValue('whitelist', [])
    };

    let visitedUrls = GM_getValue('visitedUrls', []);
    let logs = [];
    let data = [];
    let isCrawling = false;
    let currentPage = 1;
    let totalPages = 1;
    let progress = 0;

    // UI 생성
    function createUI() {
        const style = `
            /* 스타일 추가 */
            #crawler-container { position: fixed; top: 10px; right: 10px; width: 500px; height: 700px; background: #fff; border: 1px solid #ccc; z-index: 10000; font-family: Arial, sans-serif; box-shadow: 0 4px 8px rgba(0,0,0,0.2); }
            #crawler-tabs { display: flex; background: #f1f1f1; }
            .crawler-tab { flex: 1; padding: 10px; text-align: center; cursor: pointer; }
            .crawler-tab.active { background: #ddd; }
            #crawler-content { padding: 10px; overflow: auto; height: 600px; }
            /* 추가적인 스타일 */
            table { width: 100%; border-collapse: collapse; }
            th, td { padding: 8px; text-align: left; border-bottom: 1px solid #ddd; }
            th { background-color: #f2f2f2; }
            tr:hover { background-color: #f5f5f5; }
            .progress-bar { width: 100%; background-color: #f3f3f3; border: 1px solid #ccc; border-radius: 5px; overflow: hidden; margin-bottom: 10px; }
            .progress-bar-fill { height: 20px; background-color: #4caf50; width: 0%; transition: width 0.2s; }
            .modal { display: none; position: fixed; z-index: 10001; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0,0,0,0.4); }
            .modal-content { background-color: #fff; margin: 5% auto; padding: 20px; border: 1px solid #888; width: 80%; max-height: 80%; overflow-y: auto; }
            .close { color: #aaa; float: right; font-size: 28px; font-weight: bold; cursor: pointer; }
            .control-buttons { margin: 10px 0; }
            .control-buttons button { padding: 10px 20px; margin-right: 10px; cursor: pointer; }
        `;
        $('head').append(`<style>${style}</style>`);

        const container = $(`
            <div id="crawler-container">
                <div id="crawler-tabs">
                    <div class="crawler-tab active" data-tab="logs" title="로그 보기">📜 로그</div>
                    <div class="crawler-tab" data-tab="data" title="수집된 데이터 보기">📊 데이터</div>
                    <div class="crawler-tab" data-tab="settings" title="설정 변경">⚙️ 설정</div>
                </div>
                <div id="crawler-content">
                    <div id="tab-logs">
                        <div class="progress-bar">
                            <div class="progress-bar-fill"></div>
                        </div>
                        <div class="control-buttons">
                            <button id="start-crawling" title="크롤링을 시작합니다.">▶ 시작</button>
                            <button id="stop-crawling" title="크롤링을 중지합니다." disabled>■ 중지</button>
                        </div>
                        <div id="log-messages">로그 내용이 여기에 표시됩니다.</div>
                    </div>
                    <div id="tab-data" style="display:none;">
                        <input type="text" id="search-filter" placeholder="검색 필터..." title="데이터 필터링을 위한 키워드를 입력하세요." style="width: 100%; padding: 5px; margin-bottom: 10px;">
                        <table>
                            <thead>
                                <tr>
                                    <th>사이트 이름</th>
                                    <th>주소</th>
                                    <th>설명</th>
                                    <th>HTML</th>
                                    <th>분류</th>
                                </tr>
                            </thead>
                            <tbody id="data-table-body">
                                <!-- 데이터 행이 여기에 추가됩니다. -->
                            </tbody>
                        </table>
                    </div>
                    <div id="tab-settings" style="display:none;">
                        <h3>설정</h3>
                        <div>
                            <label for="delay-input">크롤링 딜레이 (ms): </label>
                            <input type="number" id="delay-input" value="${settings.delay}" title="크롤링 요청 사이의 딜레이를 밀리초 단위로 설정하세요.">
                        </div>
                        <div>
                            <h4>블랙리스트 도메인</h4>
                            <textarea id="blacklist-input" rows="5" style="width: 100%;" title="크롤링에서 제외할 도메인을 입력하세요. 각 도메인은 줄바꿈으로 구분됩니다.">${settings.blacklist.join('\n')}</textarea>
                        </div>
                        <div>
                            <h4>화이트리스트 도메인</h4>
                            <textarea id="whitelist-input" rows="5" style="width: 100%;" title="크롤링을 허용할 도메인을 입력하세요. 각 도메인은 줄바꿈으로 구분됩니다.">${settings.whitelist.join('\n')}</textarea>
                        </div>
                        <button id="save-settings" title="설정을 저장합니다." style="margin-top: 10px; padding: 5px 10px;">저장</button>
                    </div>
                </div>
            </div>

            <!-- 모달 창 -->
            <div id="html-modal" class="modal">
                <div class="modal-content">
                    <span class="close">&times;</span>
                    <pre id="modal-html-content"></pre>
                </div>
            </div>
        `);
        $('body').append(container);

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

        // 단축키 이벤트
        $(document).on('keydown', function(e) {
            if (e.ctrlKey) {
                switch (e.key) {
                    case '1':
                        $('.crawler-tab[data-tab="logs"]').click();
                        break;
                    case '2':
                        $('.crawler-tab[data-tab="data"]').click();
                        break;
                    case '3':
                        $('.crawler-tab[data-tab="settings"]').click();
                        break;
                    case 's':
                        saveSettings();
                        break;
                    case 'p':
                        if (!isCrawling) startCrawling();
                        break;
                    case 'q':
                        if (isCrawling) stopCrawling();
                        break;
                    case 'd':
                        downloadCSV();
                        break;
                    case 'j':
                        downloadJSON();
                        break;
                }
            }
        });

        // 설정 저장 버튼 이벤트
        $('#save-settings').on('click', saveSettings);

        // 검색 필터 기능
        $('#search-filter').on('input', function() {
            filterTable($(this).val());
        });

        // 모달 창 기능
        const modal = document.getElementById("html-modal");
        const span = document.getElementsByClassName("close")[0];
        span.onclick = function() { modal.style.display = "none"; }
        window.onclick = function(event) {
            if (event.target == modal) {
                modal.style.display = "none";
            }
        }

        // 시작 및 중지 버튼 이벤트
        $('#start-crawling').on('click', startCrawling);
        $('#stop-crawling').on('click', stopCrawling);
    }

    // 로깅 함수
    function log(message, type = 'info') {
        const timestamp = new Date().toLocaleString();
        logs.push({ timestamp, message, type });
        updateLogsUI();
        console[type](message);
    }

    function updateLogsUI() {
        const logHtml = logs.map(log => `<div>[${log.timestamp}] [${log.type}] ${log.message}</div>`).join('');
        $('#log-messages').html(logHtml);
        $('.progress-bar-fill').css('width', `${progress.toFixed(2)}%`);
    }

    // 데이터 업데이트 함수
    function updateDataUI() {
        const filter = $('#search-filter').val().toLowerCase();
        let filteredData = data;
        if (filter) {
            filteredData = data.filter(item =>
                item.name.toLowerCase().includes(filter) ||
                item.url.toLowerCase().includes(filter) ||
                item.description.toLowerCase().includes(filter) ||
                item.category.toLowerCase().includes(filter)
            );
        }

        let tableHtml = filteredData.map(item => `
            <tr>
                <td>${item.name}</td>
                <td><a href="${item.url}" target="_blank">${item.url}</a></td>
                <td>${item.description}</td>
                <td><button class="view-html" data-html="${encodeURIComponent(item.html)}" title="HTML 내용을 봅니다.">보기</button></td>
                <td>${item.category}</td>
            </tr>
        `).join('');
        $('#data-table-body').html(tableHtml);

        // HTML 보기 모달
        $('.view-html').on('click', function() {
            const htmlContent = decodeURIComponent($(this).data('html'));
            $('#modal-html-content').text(htmlContent);
            $('#html-modal').show();
        });
    }

    // 설정 저장 함수
    function saveSettings() {
        const delay = parseInt($('#delay-input').val(), 10);
        settings.delay = isNaN(delay) ? 3000 : delay;

        const blacklist = $('#blacklist-input').val().split('\n').map(d => d.trim()).filter(d => d);
        settings.blacklist = blacklist;
        GM_setValue('blacklist', blacklist);

        const whitelist = $('#whitelist-input').val().split('\n').map(d => d.trim()).filter(d => d);
        settings.whitelist = whitelist;
        GM_setValue('whitelist', whitelist);

        GM_setValue('delay', settings.delay);

        log('설정이 저장되었습니다.');
    }

    // 크롤링 시작 함수
    async function startCrawling() {
        if (isCrawling) {
            log('이미 크롤링이 진행 중입니다.', 'warning');
            return;
        }
        isCrawling = true;
        log('크롤링을 시작합니다.');
        $('#start-crawling').prop('disabled', true);
        $('#stop-crawling').prop('disabled', false);
        currentPage = 1;
        totalPages = determineTotalPages();
        await crawlPage();
    }

    // 크롤링 중지 함수
    function stopCrawling() {
        if (!isCrawling) {
            log('크롤링이 진행 중이지 않습니다.', 'warning');
            return;
        }
        isCrawling = false;
        log('크롤링이 중지되었습니다.');
        $('#start-crawling').prop('disabled', false);
        $('#stop-crawling').prop('disabled', true);
    }

    // 크롤링 함수
    async function crawlPage() {
        while (isCrawling && currentPage <= totalPages) {
            log(`페이지 ${currentPage} 크롤링 시작.`);
            const searchResults = extractSearchResults();
            if (!searchResults) {
                log('검색 결과를 찾을 수 없습니다.', 'error');
                break;
            }

            for (let result of searchResults) {
                if (!isCrawling) break;

                const name = result.name;
                const link = result.url;
                const description = result.description;

                // 중복 검사
                if (visitedUrls.includes(link)) {
                    log(`중복된 URL 발견: ${link}`, 'warning');
                    continue;
                }

                // 블랙리스트/화이트리스트 검사
                if (settings.whitelist.length > 0 && !settings.whitelist.some(domain => link.includes(domain))) {
                    log(`화이트리스트에 없는 도메인: ${link}`, 'warning');
                    continue;
                }
                if (settings.blacklist.some(domain => link.includes(domain))) {
                    log(`블랙리스트에 있는 도메인: ${link}`, 'warning');
                    continue;
                }

                // 데이터 수집
                try {
                    const response = await GM_xmlhttpRequestPromise(link);
                    const pageHtml = response.responseText;
                    const parser = new DOMParser();
                    const pageDoc = parser.parseFromString(pageHtml, 'text/html');
                    const textContent = pageDoc.body.innerText;

                    // 콘텐츠 품질 평가
                    const qualityScore = evaluateContentQuality(textContent);

                    // 키워드 분류
                    const category = classifyKeywords(textContent);

                    data.push({
                        name,
                        url: link,
                        description,
                        html: pageHtml,
                        category,
                        quality: qualityScore
                    });
                    updateDataUI();
                    log(`데이터 수집 완료: ${link}`);
                } catch (error) {
                    log(`데이터 수집 실패: ${link}`, 'error');
                }

                visitedUrls.push(link);
                GM_setValue('visitedUrls', visitedUrls);
            }

            // 진행률 업데이트
            progress = (currentPage / totalPages) * 100;
            updateLogsUI();

            // 다음 페이지로 이동
            if (currentPage < totalPages && isCrawling) {
                currentPage++;
                log(`다음 페이지로 이동: ${currentPage}`, 'info');
                await delay(settings.delay);
                navigateToNextPage();
            } else {
                break;
            }
        }

        if (isCrawling) {
            log('크롤링이 완료되었습니다.');
        }
        isCrawling = false;
        $('#start-crawling').prop('disabled', false);
        $('#stop-crawling').prop('disabled', true);
    }

    // 검색 결과 추출 함수 (각 검색 엔진에 맞게 수정 필요)
    function extractSearchResults() {
        let results = [];
        // 구글 검색 결과 추출
        if (window.location.hostname.includes('google.com')) {
            const items = document.querySelectorAll('div.g');
            items.forEach(item => {
                const nameElement = item.querySelector('h3');
                const linkElement = item.querySelector('a');
                const descElement = item.querySelector('.IsZvec');

                const name = nameElement ? nameElement.innerText : 'No Title';
                const link = linkElement ? linkElement.href : '';
                const description = descElement ? descElement.innerText : 'No Description';

                if (link) results.push({ name, url: link, description });
            });
            totalPages = parseInt(document.querySelector('div#foot a:last-child')?.innerText) || 1;
        }
        // 빙 검색 결과 추출
        else if (window.location.hostname.includes('bing.com')) {
            const items = document.querySelectorAll('.b_algo');
            items.forEach(item => {
                const nameElement = item.querySelector('h2');
                const linkElement = item.querySelector('a');
                const descElement = item.querySelector('.b_caption p');

                const name = nameElement ? nameElement.innerText : 'No Title';
                const link = linkElement ? linkElement.href : '';
                const description = descElement ? descElement.innerText : 'No Description';

                if (link) results.push({ name, url: link, description });
            });
            totalPages = parseInt(document.querySelector('a.sb_pagN')?.previousSibling?.innerText) || 1;
        }
        // 야후 검색 결과 추출
        else if (window.location.hostname.includes('search.yahoo.com')) {
            const items = document.querySelectorAll('.dd.algo');
            items.forEach(item => {
                const nameElement = item.querySelector('h3.title');
                const linkElement = item.querySelector('a');
                const descElement = item.querySelector('.compText a');

                const name = nameElement ? nameElement.innerText : 'No Title';
                const link = linkElement ? linkElement.href : '';
                const description = descElement ? descElement.innerText : 'No Description';

                if (link) results.push({ name, url: link, description });
            });
            totalPages = parseInt(document.querySelector('a.next')?.innerText) || 1;
        }
        // 네이버 검색 결과 추출
        else if (window.location.hostname.includes('naver.com')) {
            const items = document.querySelectorAll('.search_result .sh_cdp');
            items.forEach(item => {
                const nameElement = item.querySelector('.news_tit');
                const linkElement = nameElement ? nameElement.href : '';
                const descElement = item.querySelector('.news_dsc');

                const name = nameElement ? nameElement.innerText : 'No Title';
                const link = linkElement ? linkElement : '';
                const description = descElement ? descElement.innerText : 'No Description';

                if (link) results.push({ name, url: link, description });
            });
            totalPages = parseInt(document.querySelector('.sc_page_inner a.on + a')?.innerText) || 1;
        }
        // 다른 검색 엔진에 대한 추가 로직 필요

        return results.length > 0 ? results : null;
    }

    // 다음 페이지로 이동하는 함수 (각 검색 엔진에 맞게 수정 필요)
    function navigateToNextPage() {
        let nextPageUrl = null;
        // 구글 다음 페이지 링크
        if (window.location.hostname.includes('google.com')) {
            const nextLink = document.querySelector('a#pnnext');
            if (nextLink) nextPageUrl = nextLink.href;
        }
        // 빙 다음 페이지 링크
        else if (window.location.hostname.includes('bing.com')) {
            const nextLink = document.querySelector('a.sb_pagN');
            if (nextLink) nextPageUrl = nextLink.href;
        }
        // 야후 다음 페이지 링크
        else if (window.location.hostname.includes('search.yahoo.com')) {
            const nextLink = document.querySelector('a.next');
            if (nextLink) nextPageUrl = nextLink.href;
        }
        // 네이버 다음 페이지 링크
        else if (window.location.hostname.includes('naver.com')) {
            const nextLink = document.querySelector('.sc_page_inner a.btn_next');
            if (nextLink) nextPageUrl = nextLink.href;
        }
        // 다른 검색 엔진에 대한 추가 로직 필요

        if (nextPageUrl) {
            window.location.href = nextPageUrl;
        } else {
            log('다음 페이지 링크를 찾을 수 없습니다.', 'error');
            stopCrawling();
        }
    }

    // GM_xmlhttpRequest를 Promise로 래핑
    function GM_xmlhttpRequestPromise(url) {
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: "GET",
                url: url,
                onload: resolve,
                onerror: reject
            });
        });
    }

    // 콘텐츠 품질 평가 함수
    function evaluateContentQuality(text) {
        const wordCount = text.split(/\s+/).length;
        return wordCount;
    }

    // 키워드 분류 함수
    function classifyKeywords(text) {
        const keywords = {
            '교육': ['학교', '교육', '학생', '교수', '강의'],
            '기술': ['기술', '소프트웨어', '하드웨어', '프로그래밍', '개발'],
            '뉴스': ['뉴스', '속보', '기사', '보도'],
            '엔터테인먼트': ['영화', '음악', '연예', '게임', '드라마'],
            '스포츠': ['축구', '야구', '농구', '경기', '토너먼트']
            // 추가 키워드 및 카테고리
        };
        for (const [category, kws] of Object.entries(keywords)) {
            if (kws.some(kw => text.includes(kw))) {
                return category;
            }
        }
        return '기타';
    }

    // CSV 다운로드 함수
    function downloadCSV() {
        const csvContent = "data:text/csv;charset=utf-8,"
            + ["사이트 이름,주소,설명,카테고리,품질점수"]
            .concat(data.map(d => `"${d.name.replace(/"/g, '""')}","${d.url.replace(/"/g, '""')}","${d.description.replace(/"/g, '""')}","${d.category}","${d.quality}"`))
            .join("\n");
        const encodedUri = encodeURI(csvContent);
        const link = document.createElement("a");
        link.setAttribute("href", encodedUri);
        link.setAttribute("download", "data.csv");
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
    }

    // JSON 다운로드 함수
    function downloadJSON() {
        const jsonContent = JSON.stringify(data, null, 2);
        const blob = new Blob([jsonContent], { type: "application/json" });
        const url = URL.createObjectURL(blob);
        const link = document.createElement("a");
        link.setAttribute("href", url);
        link.setAttribute("download", "data.json");
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
    }

    // 테이블 필터링 함수
    function filterTable(keyword) {
        $('#data-table-body tr').each(function() {
            const rowText = $(this).text().toLowerCase();
            if (rowText.includes(keyword.toLowerCase())) {
                $(this).show();
            } else {
                $(this).hide();
            }
        });
    }

    // 초기화 함수
    function init() {
        createUI();
        log('크롤러가 초기화되었습니다.');
    }

    // 페이지 로드 시 초기화
    window.addEventListener('load', init);

    // 크롤링을 중지했을 때 다음 페이지로 이동하지 않도록 설정
    function determineTotalPages() {
        // 각 검색 엔진에서 총 페이지 수를 결정하는 로직을 추가할 수 있습니다.
        // 현재는 기본값 1로 설정
        return 5; // 예시로 총 5페이지로 설정
    }

    // 딜레이 함수
    function delay(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }

    // 추가: 크롤링이 시작되거나 중지될 때 버튼 상태 변경
    function setButtonStates() {
        if (isCrawling) {
            $('#start-crawling').prop('disabled', true);
            $('#stop-crawling').prop('disabled', false);
        } else {
            $('#start-crawling').prop('disabled', false);
            $('#stop-crawling').prop('disabled', true);
        }
    }

    // 크롤링 로직을 업데이트하여 버튼 상태를 반영
    async function crawlPage() {
        while (isCrawling && currentPage <= totalPages) {
            log(`페이지 ${currentPage} 크롤링 시작.`);
            const searchResults = extractSearchResults();
            if (!searchResults) {
                log('검색 결과를 찾을 수 없습니다.', 'error');
                break;
            }

            for (let result of searchResults) {
                if (!isCrawling) break;

                const name = result.name;
                const link = result.url;
                const description = result.description;

                // 중복 검사
                if (visitedUrls.includes(link)) {
                    log(`중복된 URL 발견: ${link}`, 'warning');
                    continue;
                }

                // 블랙리스트/화이트리스트 검사
                if (settings.whitelist.length > 0 && !settings.whitelist.some(domain => link.includes(domain))) {
                    log(`화이트리스트에 없는 도메인: ${link}`, 'warning');
                    continue;
                }
                if (settings.blacklist.some(domain => link.includes(domain))) {
                    log(`블랙리스트에 있는 도메인: ${link}`, 'warning');
                    continue;
                }

                // 데이터 수집
                try {
                    const response = await GM_xmlhttpRequestPromise(link);
                    const pageHtml = response.responseText;
                    const parser = new DOMParser();
                    const pageDoc = parser.parseFromString(pageHtml, 'text/html');
                    const textContent = pageDoc.body.innerText;

                    // 콘텐츠 품질 평가
                    const qualityScore = evaluateContentQuality(textContent);

                    // 키워드 분류
                    const category = classifyKeywords(textContent);

                    data.push({
                        name,
                        url: link,
                        description,
                        html: pageHtml,
                        category,
                        quality: qualityScore
                    });
                    updateDataUI();
                    log(`데이터 수집 완료: ${link}`);
                } catch (error) {
                    log(`데이터 수집 실패: ${link}`, 'error');
                }

                visitedUrls.push(link);
                GM_setValue('visitedUrls', visitedUrls);
            }

            // 진행률 업데이트
            progress = (currentPage / totalPages) * 100;
            updateLogsUI();

            // 다음 페이지로 이동
            if (currentPage < totalPages && isCrawling) {
                currentPage++;
                log(`다음 페이지로 이동: ${currentPage}`, 'info');
                await delay(settings.delay);
                navigateToNextPage();
            } else {
                break;
            }
        }

        if (isCrawling) {
            log('크롤링이 완료되었습니다.');
        }
        isCrawling = false;
        setButtonStates();
    }
})();                                                       이거 다음 페이지로 안 넘어가고 크롤링이 1페이지만 되고 다음 페이지로 안 넘어감