// ==UserScript==
// @name         Comprehensive Web Crawler
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  자동 웹 크롤링 및 데이터 수집 스크립트
// @author       Your Name
// @match        *://*/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_download
// @require      https://cdnjs.cloudflare.com/ajax/libs/papaparse/5.3.2/papaparse.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js
// ==/UserScript==

(function() {
    'use strict';

    // 설정
    const SETTINGS = {
        crawlDelay: 3000, // 3초 대기
        maxPages: 100, // 최대 페이지 수
        blacklist: [], // 블랙리스트 도메인
        whitelist: [] // 화이트리스트 도메인
    };

    // 상태 변수
    let isCrawling = false;
    let currentPage = 1;
    let totalPages = 1;
    let progress = 0;
    let collectedData = [];
    let visitedURLs = new Set();
    let logs = [];

    // UI 생성
    const createUI = () => {
        // 스타일 추가
        const style = `
            #crawlerUI {
                position: fixed;
                top: 10px;
                right: 10px;
                width: 400px;
                height: 600px;
                background: white;
                border: 1px solid #ccc;
                z-index: 9999;
                font-family: Arial, sans-serif;
            }
            #crawlerTabs {
                display: flex;
                background: #f1f1f1;
                cursor: pointer;
            }
            .crawlerTab {
                flex: 1;
                padding: 10px;
                text-align: center;
                border-right: 1px solid #ccc;
            }
            .crawlerTab:last-child {
                border-right: none;
            }
            .crawlerTab.active {
                background: #fff;
                font-weight: bold;
            }
            #crawlerContent > div {
                display: none;
                padding: 10px;
                height: 540px;
                overflow-y: auto;
            }
            #crawlerContent > div.active {
                display: block;
            }
            table {
                width: 100%;
                border-collapse: collapse;
            }
            table, th, td {
                border: 1px solid #ccc;
            }
            th, td {
                padding: 8px;
                text-align: left;
            }
            #progressBarContainer {
                width: 100%;
                background-color: #f3f3f3;
                border: 1px solid #ccc;
                margin-top: 10px;
            }
            #progressBar {
                width: 0%;
                height: 20px;
                background-color: #4caf50;
            }
            .tooltip {
                position: relative;
                display: inline-block;
            }
            .tooltip .tooltiptext {
                visibility: hidden;
                width: 120px;
                background-color: black;
                color: #fff;
                text-align: center;
                border-radius: 6px;
                padding: 5px 0;
                position: absolute;
                z-index: 1;
                bottom: 125%;
                left: 50%;
                margin-left: -60px;
                opacity: 0;
                transition: opacity 0.3s;
            }
            .tooltip:hover .tooltiptext {
                visibility: visible;
                opacity: 1;
            }
        `;
        const styleElem = document.createElement('style');
        styleElem.innerHTML = style;
        document.head.appendChild(styleElem);

        // UI 구조
        const ui = `
            <div id="crawlerUI">
                <div id="crawlerTabs">
                    <div class="crawlerTab active" data-tab="logs">📜 로그</div>
                    <div class="crawlerTab" data-tab="data">📊 데이터</div>
                    <div class="crawlerTab" data-tab="settings">⚙️ 설정</div>
                </div>
                <div id="crawlerContent">
                    <div id="logs" class="active">
                        <ul id="logList"></ul>
                    </div>
                    <div id="data">
                        <input type="text" id="searchInput" placeholder="검색..." style="width: 100%; padding: 5px; margin-bottom: 10px;">
                        <table id="dataTable">
                            <thead>
                                <tr>
                                    <th>사이트 이름</th>
                                    <th>주소</th>
                                    <th>설명</th>
                                    <th>분류</th>
                                    <th>품질 점수</th>
                                </tr>
                            </thead>
                            <tbody></tbody>
                        </table>
                    </div>
                    <div id="settings">
                        <label>크롤링 속도 (ms): <input type="number" id="crawlDelayInput" value="${SETTINGS.crawlDelay}" min="1000"></label><br><br>
                        <label>블랙리스트 도메인 (쉼표로 구분): <input type="text" id="blacklistInput" value="${SETTINGS.blacklist.join(', ')}" style="width: 100%;"></label><br><br>
                        <label>화이트리스트 도메인 (쉼표로 구분): <input type="text" id="whitelistInput" value="${SETTINGS.whitelist.join(', ')}" style="width: 100%;"></label><br><br>
                        <button id="saveSettings">설정 저장 (Ctrl+S)</button>
                    </div>
                </div>
                <div id="progressBarContainer">
                    <div id="progressBar"></div>
                </div>
            </div>
        `;
        document.body.insertAdjacentHTML('beforeend', ui);

        // 탭 전환 이벤트
        document.querySelectorAll('.crawlerTab').forEach(tab => {
            tab.addEventListener('click', () => {
                document.querySelectorAll('.crawlerTab').forEach(t => t.classList.remove('active'));
                tab.classList.add('active');
                const tabName = tab.getAttribute('data-tab');
                document.querySelectorAll('#crawlerContent > div').forEach(content => {
                    if(content.id === tabName){
                        content.classList.add('active');
                    } else {
                        content.classList.remove('active');
                    }
                });
            });
        });

        // 설정 저장 버튼
        document.getElementById('saveSettings').addEventListener('click', saveSettings);

        // 검색 필터
        document.getElementById('searchInput').addEventListener('input', filterTable);
    };

    // 설정 저장 함수
    const saveSettings = () => {
        SETTINGS.crawlDelay = parseInt(document.getElementById('crawlDelayInput').value) || 3000;
        SETTINGS.blacklist = document.getElementById('blacklistInput').value.split(',').map(s => s.trim()).filter(s => s);
        SETTINGS.whitelist = document.getElementById('whitelistInput').value.split(',').map(s => s.trim()).filter(s => s);
        log(`설정이 저장되었습니다.`);
    };

    // 로그 추가 함수
    const log = (message, type = 'info') => {
        logs.push({message, type});
        const logList = document.getElementById('logList');
        const li = document.createElement('li');
        li.textContent = message;
        if(type === 'error') li.style.color = 'red';
        logList.appendChild(li);
    };

    // 데이터 추가 함수
    const addData = (data) => {
        collectedData.push(data);
        updateDataTable();
    };

    // 데이터 테이블 업데이트
    const updateDataTable = () => {
        const tbody = document.querySelector('#dataTable tbody');
        tbody.innerHTML = '';
        collectedData.forEach((data, index) => {
            const tr = document.createElement('tr');

            const nameTd = document.createElement('td');
            nameTd.textContent = data.name;
            tr.appendChild(nameTd);

            const urlTd = document.createElement('td');
            const link = document.createElement('a');
            link.href = data.url;
            link.textContent = data.url;
            link.target = '_blank';
            urlTd.appendChild(link);
            tr.appendChild(urlTd);

            const descTd = document.createElement('td');
            descTd.textContent = data.description;
            tr.appendChild(descTd);

            const categoryTd = document.createElement('td');
            categoryTd.textContent = data.category;
            tr.appendChild(categoryTd);

            const qualityTd = document.createElement('td');
            qualityTd.textContent = data.qualityScore;
            tr.appendChild(qualityTd);

            tbody.appendChild(tr);
        });
    };

    // 테이블 필터링 함수
    const filterTable = () => {
        const keyword = document.getElementById('searchInput').value.toLowerCase();
        const rows = document.querySelectorAll('#dataTable tbody tr');
        rows.forEach(row => {
            const cells = row.querySelectorAll('td');
            const match = Array.from(cells).some(td => td.textContent.toLowerCase().includes(keyword));
            row.style.display = match ? '' : 'none';
        });
    };

    // 진행률 업데이트
    const updateProgress = () => {
        progress = (currentPage / totalPages) * 100;
        document.getElementById('progressBar').style.width = `${progress}%`;
    };

    // 크롤링 시작 함수
    const startCrawling = async (searchKeyword) => {
        if(isCrawling) {
            log('이미 크롤링이 진행 중입니다.', 'error');
            return;
        }
        isCrawling = true;
        currentPage = 1;
        progress = 0;
        collectedData = [];
        visitedURLs.clear();
        log(`크롤링 시작: "${searchKeyword}"`);

        while(currentPage <= totalPages && isCrawling) {
            log(`페이지 ${currentPage} 수집 중...`);
            try {
                // 검색 페이지 로드
                const searchUrl = `https://www.example.com/search?q=${encodeURIComponent(searchKeyword)}&page=${currentPage}`;
                await loadPage(searchUrl);

                // 데이터 수집
                const results = collectDataFromPage();
                totalPages = getTotalPages(); // 페이지 수 업데이트

                for(const result of results) {
                    if(visitedURLs.has(result.url)) {
                        log(`중복된 URL 발견: ${result.url}`, 'error');
                        continue;
                    }
                    if(SETTINGS.whitelist.length > 0 && !SETTINGS.whitelist.some(domain => result.url.includes(domain))) {
                        log(`화이트리스트에 없는 도메인: ${result.url}`, 'error');
                        continue;
                    }
                    if(SETTINGS.blacklist.some(domain => result.url.includes(domain))) {
                        log(`블랙리스트 도메인: ${result.url}`, 'error');
                        continue;
                    }

                    visitedURLs.add(result.url);
                    const pageContent = await fetchPageContent(result.url);
                    const description = extractDescription(pageContent);
                    const qualityScore = evaluateContentQuality(description);
                    const category = classifyKeywords(description);

                    addData({
                        name: result.name,
                        url: result.url,
                        description: description,
                        category: category,
                        qualityScore: qualityScore
                    });

                    await delay(SETTINGS.crawlDelay);
                }

                updateProgress();
                currentPage++;
            } catch(error) {
                log(`에러 발생: ${error.message}`, 'error');
            }
        }

        isCrawling = false;
        log('크롤링이 완료되었습니다.');
    };

    // 크롤링 중지 함수
    const stopCrawling = () => {
        if(isCrawling) {
            isCrawling = false;
            log('크롤링이 중지되었습니다.');
        }
    };

    // 초기화 함수
    const initialize = () => {
        createUI();
        addKeyboardShortcuts();
        log('크롤러 초기화 완료.');
    };

    // 키보드 단축키 설정
    const addKeyboardShortcuts = () => {
        document.addEventListener('keydown', (e) => {
            if(e.ctrlKey) {
                switch(e.key.toLowerCase()) {
                    case 's':
                        e.preventDefault();
                        saveSettings();
                        break;
                    case 'p':
                        e.preventDefault();
                        const keyword = prompt('검색어를 입력하세요:');
                        if(keyword) startCrawling(keyword);
                        break;
                    case 'q':
                        e.preventDefault();
                        stopCrawling();
                        break;
                    case 'd':
                        e.preventDefault();
                        downloadCSV();
                        break;
                    case 'j':
                        e.preventDefault();
                        downloadJSON();
                        break;
                    case '1':
                        e.preventDefault();
                        activateTab('logs');
                        break;
                    case '2':
                        e.preventDefault();
                        activateTab('data');
                        break;
                    case '3':
                        e.preventDefault();
                        activateTab('settings');
                        break;
                }
            }
        });
    };

    // 탭 활성화 함수
    const activateTab = (tabName) => {
        document.querySelectorAll('.crawlerTab').forEach(tab => {
            if(tab.getAttribute('data-tab') === tabName){
                tab.click();
            }
        });
    };

    // CSV 다운로드
    const downloadCSV = () => {
        const csv = Papa.unparse(collectedData);
        GM_download({
            url: 'data:text/csv;charset=utf-8,' + encodeURIComponent(csv),
            name: 'data.csv',
            saveAs: true
        });
        log('CSV 파일이 다운로드되었습니다.');
    };

    // JSON 다운로드
    const downloadJSON = () => {
        const json = JSON.stringify(collectedData, null, 2);
        GM_download({
            url: 'data:application/json;charset=utf-8,' + encodeURIComponent(json),
            name: 'data.json',
            saveAs: true
        });
        log('JSON 파일이 다운로드되었습니다.');
    };

    // 페이지 로드 함수
    const loadPage = (url) => {
        return new Promise((resolve, reject) => {
            fetch(url)
                .then(response => response.text())
                .then(html => {
                    // DOM 파싱
                    const parser = new DOMParser();
                    const doc = parser.parseFromString(html, 'text/html');
                    document.documentElement.innerHTML = doc.documentElement.innerHTML;
                    resolve();
                })
                .catch(err => reject(err));
        });
    };

    // 데이터 수집 함수 (사이트 이름과 URL)
    const collectDataFromPage = () => {
        // 여기서는 예시로 검색 결과의 링크를 수집합니다.
        // 실제 사이트 구조에 맞게 수정해야 합니다.
        const results = [];
        document.querySelectorAll('a.result-link').forEach(link => {
            results.push({
                name: link.textContent.trim(),
                url: link.href
            });
        });
        return results;
    };

    // 전체 페이지 수 가져오기
    const getTotalPages = () => {
        // 여기서는 예시로 총 페이지 수를 가져옵니다.
        // 실제 사이트 구조에 맞게 수정해야 합니다.
        const total = parseInt(document.querySelector('.total-pages').textContent.trim()) || 1;
        return Math.min(total, SETTINGS.maxPages);
    };

    // 페이지 내용 가져오기
    const fetchPageContent = async (url) => {
        const response = await fetch(url);
        return await response.text();
    };

    // 설명 추출 함수
    const extractDescription = (html) => {
        const parser = new DOMParser();
        const doc = parser.parseFromString(html, 'text/html');
        // 실제 사이트 구조에 맞게 수정
        const desc = doc.querySelector('meta[name="description"]')?.getAttribute('content') || '설명 없음';
        return desc;
    };

    // 콘텐츠 품질 평가 함수
    const evaluateContentQuality = (text) => {
        const wordCount = text.split(/\s+/).length;
        return wordCount >= 100 ? '높음' : '낮음';
    };

    // 키워드 분류 함수
    const classifyKeywords = (text) => {
        const keywords = ['교육', '테크', '게임', '뉴스']; // 예시 키워드
        for(const keyword of keywords) {
            if(text.includes(keyword)) {
                return keyword;
            }
        }
        return '기타';
    };

    // 지연 함수
    const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));

    // 에러 핸들링 강화 (자동 재시도 등)
    window.addEventListener('error', (e) => {
        log(`전역 에러: ${e.message}`, 'error');
    });

    // 초기화 호출
    initialize();

})();                창 조절 기능 추가. 내가 크롤링 하고 있는 사이트들의 UI를 한 눈에 알아볼 수 있는 기능 추가