// ==UserScript==
// @name Advanced Web Crawler
// @namespace http://tampermonkey.net/
// @version 1.0
// @description 자동 웹 크롤러 with UI, logging, and various features
// @author Your Name
// @match :///*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_download
// @connect *
// @require https://code.jquery.com/jquery-3.6.0.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js
// ==/UserScript==

(function() {
'use strict';


// ====== 기본 설정 ======
const settings = {
    delay: 3000, // 딜레이 (밀리초)
    blacklist: [], // 블랙리스트 도메인
    whitelist: [], // 화이트리스트 도메인
};

// ====== UI 생성 ======
const createUI = () => {
    // 스타일 추가
    const style = `
        /* UI 스타일링 */
        #crawler-ui { position: fixed; top: 10px; right: 10px; width: 400px; height: 600px; background: white; border: 1px solid #ccc; z-index: 10000; font-family: Arial, sans-serif; }
        #crawler-tabs { display: flex; border-bottom: 1px solid #ccc; }
        .crawler-tab { flex: 1; padding: 10px; text-align: center; cursor: pointer; }
        .crawler-tab.active { background: #f0f0f0; }
        #crawler-content { padding: 10px; height: calc(100% - 40px); overflow: auto; }
        .progress-bar { width: 100%; background: #f3f3f3; border: 1px solid #ccc; height: 20px; position: relative; margin-bottom: 10px; }
        .progress { background: #4caf50; height: 100%; width: 0%; }
        table { width: 100%; border-collapse: collapse; }
        th, td { border: 1px solid #ddd; padding: 8px; }
        th { background-color: #f2f2f2; }
        tr:hover { background-color: #f1f1f1; }
        /* 모달 스타일 */
        .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: 15% auto; padding: 20px; border: 1px solid #888; width: 80%; height: 80%; overflow: auto; }
        .close { color: #aaa; float: right; font-size: 28px; font-weight: bold; cursor: pointer; }
    `;
    $('head').append(`<style>${style}</style>`);

    // UI 구조
    const ui = `
        <div id="crawler-ui">
            <div id="crawler-tabs">
                <div class="crawler-tab active" data-tab="logs">📜 로그</div>
                <div class="crawler-tab" data-tab="data">📊 데이터</div>
                <div class="crawler-tab" data-tab="settings">⚙️ 설정</div>
            </div>
            <div id="crawler-content">
                <div id="logs" class="tab-content">
                    <textarea id="log-area" style="width: 100%; height: 90%;"></textarea>
                </div>
                <div id="data" class="tab-content" style="display:none;">
                    <div class="progress-bar"><div class="progress" id="progress"></div></div>
                    <table id="data-table">
                        <thead>
                            <tr>
                                <th>사이트 이름</th>
                                <th>주소</th>
                                <th>설명</th>
                                <th>분류</th>
                                <th>질 점수</th>
                                <th>HTML</th>
                            </tr>
                        </thead>
                        <tbody></tbody>
                    </table>
                    <button id="download-csv">CSV 다운로드</button>
                    <button id="download-json">JSON 다운로드</button>
                </div>
                <div id="settings" class="tab-content" style="display:none;">
                    <label>딜레이 (밀리초):</label>
                    <input type="number" id="delay-input" value="${settings.delay}" title="크롤링 딜레이 설정"><br>
                    <label>블랙리스트 도메인 (콤마로 구분):</label>
                    <input type="text" id="blacklist-input" value="${settings.blacklist.join(',')}" title="블랙리스트 도메인"><br>
                    <label>화이트리스트 도메인 (콤마로 구분):</label>
                    <input type="text" id="whitelist-input" value="${settings.whitelist.join(',')}" title="화이트리스트 도메인"><br>
                    <button id="save-settings">설정 저장 (Ctrl+S)</button>
                </div>
            </div>
        </div>
        <!-- 모달 -->
        <div id="html-modal" class="modal">
            <div class="modal-content">
                <span class="close">&times;</span>
                <pre id="modal-content"></pre>
            </div>
        </div>
    `;
    $('body').append(ui);

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

    // 모달 이벤트
    $('.close').on('click', function() {
        $('#html-modal').hide();
    });
    $(window).on('click', function(event) {
        if (event.target.id === 'html-modal') {
            $('#html-modal').hide();
        }
    });

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

    // 다운로드 버튼 이벤트
    $('#download-csv').on('click', downloadCSV);
    $('#download-json').on('click', downloadJSON);
};

// ====== 설정 저장 ======
const saveSettings = () => {
    settings.delay = parseInt($('#delay-input').val()) || 3000;
    settings.blacklist = $('#blacklist-input').val().split(',').map(s => s.trim()).filter(s => s);
    settings.whitelist = $('#whitelist-input').val().split(',').map(s => s.trim()).filter(s => s);
    GM_setValue('settings', settings);
    log('설정이 저장되었습니다.');
};

// ====== 설정 로드 ======
const loadSettings = () => {
    const saved = GM_getValue('settings');
    if (saved) {
        settings.delay = saved.delay;
        settings.blacklist = saved.blacklist;
        settings.whitelist = saved.whitelist;
        $('#delay-input').val(settings.delay);
        $('#blacklist-input').val(settings.blacklist.join(','));
        $('#whitelist-input').val(settings.whitelist.join(','));
    }
};

// ====== 로그 기능 ======
const log = (message, type = 'info') => {
    const timestamp = new Date().toLocaleTimeString();
    $('#log-area').val(`${timestamp} [${type.toUpperCase()}] ${message}\n` + $('#log-area').val());
};

// ====== 데이터 추가 ======
const addData = (data) => {
    const row = `
        <tr>
            <td>${data.name}</td>
            <td><a href="${data.url}" target="_blank">${data.url}</a></td>
            <td>${data.description}</td>
            <td>${data.category}</td>
            <td>${data.score}</td>
            <td><button class="view-html" data-html="${escapeHtml(data.html)}">보기</button></td>
        </tr>
    `;
    $('#data-table tbody').append(row);
};

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

// ====== Escape/Unescape HTML ======
const escapeHtml = (unsafe) => {
    return unsafe
         .replace(/&/g, "&amp;")
         .replace(/</g, "&lt;")
         .replace(/>/g, "&gt;")
         .replace(/"/g, "&quot;")
         .replace(/'/g, "&#039;");
};

const unescapeHtml = (safe) => {
    return safe
         .replace(/&lt;/g, "<")
         .replace(/&gt;/g, ">")
         .replace(/&amp;/g, "&")
         .replace(/&quot;/g, "\"")
         .replace(/&#039;/g, "'");
};

// ====== 다운로드 기능 ======
const downloadCSV = () => {
    const ws = XLSX.utils.table_to_sheet(document.getElementById('data-table'));
    const wb = XLSX.utils.book_new();
    XLSX.utils.book_append_sheet(wb, ws, "데이터");
    XLSX.writeFile(wb, "data.csv");
    log('CSV 파일이 다운로드되었습니다.');
};

const downloadJSON = () => {
    const data = [];
    $('#data-table tbody tr').each(function() {
        const cols = $(this).find('td');
        data.push({
            name: $(cols[0]).text(),
            url: $(cols[1]).find('a').attr('href'),
            description: $(cols[2]).text(),
            category: $(cols[3]).text(),
            score: $(cols[4]).text(),
            html: $(cols[5]).find('button').data('html')
        });
    });
    const blob = new Blob([JSON.stringify(data, null, 2)], {type : 'application/json'});
    GM_download({
        url: URL.createObjectURL(blob),
        name: 'data.json',
        saveAs: true
    });
    log('JSON 파일이 다운로드되었습니다.');
};

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

// ====== 크롤링 기능 ======
let crawling = false;
const crawledUrls = new Set();

const startCrawling = () => {
    if (crawling) {
        log('이미 크롤링이 진행 중입니다.', 'warning');
        return;
    }
    crawling = true;
    log('크롤링을 시작합니다.');
    // 예시: Google 검색 페이지에서 크롤링 시작
    // 실제 사용할 검색 엔진에 맞게 수정 필요
    performSearch('예시 검색어');
};

const stopCrawling = () => {
    if (!crawling) {
        log('현재 진행 중인 크롤링이 없습니다.', 'warning');
        return;
    }
    crawling = false;
    log('크롤링이 중지되었습니다.');
};

const performSearch = (query) => {
    const searchUrl = `https://www.google.com/search?q=${encodeURIComponent(query)}`;
    window.location.href = searchUrl;
};

// ====== 페이지 분석 ======
const analyzePage = () => {
    if (!crawling) return;
    log('페이지 분석을 시작합니다.');
    // 예시: Google 검색 결과에서 링크 추출
    // 실제 사용할 검색 엔진에 맞게 수정 필요
    const results = [];
    $('a').each(function() {
        const url = $(this).attr('href');
        if (url && url.startsWith('http') && !isBlacklisted(url) && isWhitelisted(url)) {
            results.push(url);
        }
    });
    log(`찾은 URL 개수: ${results.length}`);
    processResults(results);
};

const isBlacklisted = (url) => {
    return settings.blacklist.some(domain => url.includes(domain));
};

const isWhitelisted = (url) => {
    if (settings.whitelist.length === 0) return true;
    return settings.whitelist.some(domain => url.includes(domain));
};

// ====== 결과 처리 ======
const processResults = async (urls) => {
    for (let i = 0; i < urls.length; i++) {
        if (!crawling) break;
        const url = urls[i];
        if (crawledUrls.has(url)) {
            log(`이미 크롤링한 URL: ${url}`, 'warning');
            continue;
        }
        crawledUrls.add(url);
        try {
            log(`크롤링 중: ${url}`);
            const response = await fetch(url);
            const html = await response.text();
            const parser = new DOMParser();
            const doc = parser.parseFromString(html, 'text/html');
            const name = doc.querySelector('title') ? doc.querySelector('title').innerText : 'No Title';
            const description = doc.querySelector('meta[name="description"]') ? doc.querySelector('meta[name="description"]').getAttribute('content') : 'No Description';
            const category = classifyKeywords(description);
            const score = evaluateContentQuality(description);
            addData({ name, url, description, category, score, html });
            updateProgress((i + 1) / urls.length * 100);
            await delay(settings.delay);
        } catch (error) {
            log(`에러 발생: ${url} - ${error.message}`, 'error');
            // 재시도 로직 추가 가능
        }
    }
    log('크롤링이 완료되었습니다.');
    crawling = false;
};

// ====== 진행률 업데이트 ======
const updateProgress = (percentage) => {
    $('#progress').css('width', `${percentage}%`);
};

// ====== 딜레이 ======
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));

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

// ====== 키워드 분류 ======
const classifyKeywords = (text) => {
    const categories = {
        '교육': ['학생', '학교', '교육', '학습'],
        '기술': ['기술', '소프트웨어', '프로그래밍', '개발'],
        '비즈니스': ['비즈니스', '회사', '시장', '경제'],
        // 추가 카테고리 및 키워드 추가 가능
    };
    for (const [category, keywords] of Object.entries(categories)) {
        for (const keyword of keywords) {
            if (text.includes(keyword)) {
                return category;
            }
        }
    }
    return '기타';
};

// ====== 초기화 ======
const init = () => {
    createUI();
    loadSettings();
    setupKeyboardShortcuts();
    analyzePage();
};

// DOM 로드 후 초기화
$(document).ready(function() {
    init();
});
})(); 시작이랑 중지 버튼 초기화 버튼 추가.

설정 쪽 다양하면서도 세심하게 만드셈 그리고 다양한 기능들을 엄청 많이 추가하셈 굉장히 실용적이면서도 참신해야 함

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

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

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

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

또 UI창 크기를 조절할 수 있는 기능을 추가하셈

각 검색 결과의 전체 HTML 내용과 주요 텍스트 내용을 비동기로 수집합니다.
수집된 내용을 데이터 테이블의 🗨️ 텍스트 내용과 📄 HTML 내용 열에 표시합니다.
📄 HTML 내용 셀을 클릭하면 모달 창으로 전체 HTML 내용을 확인할 수 있습니다. 이것도 추 가

HTML 내용 수집 및 텍스트 추출 , 크롤링 사이트를 확실하게 조사하는 방법들 대폭 추가해줘