// ==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를 한 눈에 알아볼 수 있는 기능 추가  크롤링하는 사이트를 더더 확실하게 할 수 있는 방법들을 추가하셈 또 데이터쪽  굉장히 세심하면서도 다양한 기능들 많이 엄청 추가하셈. 굉장히 실용적이면서도 참신한 걸로  하지만 본질적인 거는 남겨놓고 ㅇㅇ 그리고 검색 엔진 다양하게 하셈 



그리고 맨위에 좋합 로그 놓고 버그나 밑에 그런 것들 로그들 다 따로따로 놓고  걍 모든 로그 나누고 맨 위에 좋합 로그 넣어두셈

그리고 종합로그나 버그 그런 것들 로그에서  버그나 그런 것들은 로그 메세지를 빨간 색 

또 크롤링 한 사이트 저장하고 또 그 저장한 사이트 들중에서 뺴고 넣고 이런 기능 추가