pt==
// @name 종합 웹 크롤러 7.8.2 개선판
// @namespace http://tampermonkey.net/
// @version 7.8.2
// @description 고급 기능을 갖춘 웹 크롤러로 검색 결과를 실시간으로 수집하고 표시 (동시 크롤링, 번역 기능, UI 개선 등)
// @author YourName
// @match ://www.google.com/search
// @match ://www.bing.com/search
// @match ://search.daum.net/search
// @match ://search.naver.com/search
// @match ://search.yahoo.com/search
// @match ://duckduckgo.com/
// @match ://www.ecosia.org/search
// @match ://.onion/*
// @grant GM_xmlhttpRequest
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_notification
// @connect *
// @require https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js
// @require https://cdn.jsdelivr.net/npm/chart.js
// @require https://cdnjs.cloudflare.com/ajax/libs/Sortable/1.14.0/Sortable.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/i18next/21.6.15/i18next.min.js
// ==/UserScript==

(function() {
'use strict';

// CSS 스타일 추가 (크롤러 창 크기 축소 및 UI 개선)
GM_addStyle(`
/* 기본 컨테이너 스타일 /
#crawler-container {
position: fixed;
top: 20px;
right: 20px;
background-color: #1e1e1e;
border: 1px solid #4CAF50;
padding: 15px;
z-index: 10000;
width: 600px; / 크기를 작게 조정 */
max-height: 85vh;
overflow: auto;
box-shadow: 0 4px 20px rgba(0,0,0,0.3);
border-radius: 8px;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
font-size: 14px;
display: flex;
flex-direction: column;
transition: all 0.3s ease;
color: #ffffff;
}

/* 헤더 스타일 */
#crawler-container header {
display: flex;
justify-content: space-between;
align-items: center;
background-color: #333333;
color: #fff;
padding: 10px 15px;
border-radius: 6px 6px 0 0;
cursor: move;
}

#crawler-container header .title {
font-size: 20px;
font-weight: bold;
}

#crawler-container header .close-btn {
font-size: 24px;
cursor: pointer;
transition: color 0.3s;
}

#crawler-container header .close-btn:hover {
color: #ff4d4d;
}

/* 탭 스타일 */
#crawler-container .tabs {
display: flex;
border-bottom: 1px solid #4CAF50;
margin-top: 10px;
}

#crawler-container .tab {
flex: 1;
padding: 10px;
text-align: center;
cursor: pointer;
background-color: #2c2c2c;
transition: background-color 0.3s, color 0.3s;
border-radius: 4px 4px 0 0;
margin-right: 2px;
font-weight: bold;
}

#crawler-container .tab:last-child {
margin-right: 0;
}

#crawler-container .tab.active {
background-color: #4CAF50;
color: #fff;
}

/* 탭 콘텐츠 스타일 */
#crawler-container .tab-content {
display: none;
padding: 15px;
flex-grow: 1;
background-color: #2c2c2c;
border-radius: 0 0 8px 8px;
}

#crawler-container .tab-content.active {
display: block;
}

/* 버튼 스타일 */
#crawler-container button {
padding: 8px 12px;
margin: 6px 3px;
border: none;
border-radius: 4px;
background-color: #4CAF50;
color: #fff;
cursor: pointer;
transition: background-color 0.3s;
font-size: 14px;
}

#crawler-container button:hover {
background-color: #45a049;
}

/* Toggle 스위치 스타일 */
.switch {
position: relative;
display: inline-block;
width: 50px;
height: 24px;
margin-left: 10px;
vertical-align: middle;
}

.switch input {
opacity: 0;
width: 0;
height: 0;
}

.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
transition: .4s;
border-radius: 24px;
}

.slider:before {
position: absolute;
content: "";
height: 20px;
width: 20px;
left: 2px;
bottom: 2px;
background-color: white;
transition: .4s;
border-radius: 50%;
}

input:checked + .slider {
background-color: #4CAF50;
}

input:focus + .slider {
box-shadow: 0 0 1px #4CAF50;
}

input:checked + .slider:before {
transform: translateX(26px);
}

/* 기타 섹션 스타일 */
#crawler-container .options,
#crawler-container .controls,
#crawler-container .darkweb-search,
#crawler-container .translator-section,
#crawler-container .search-results-filter {
margin-bottom: 15px;
}

/* 입력 필드 스타일 */
#crawler-container input[type="text"],
#crawler-container textarea,
#crawler-container input[type="number"],
#crawler-container select {
width: 100%;
padding: 8px;
margin-top: 5px;
border: 1px solid #555;
border-radius: 4px;
font-size: 14px;
background-color: #3c3c3c;
color: #fff;
}

/* 테이블 스타일 */
#crawler-container table {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
font-size: 14px;
background-color: #3c3c3c;
}

#crawler-container th, #crawler-container td {
border: 1px solid #555;
padding: 8px;
text-align: left;
vertical-align: middle;
}

#crawler-container th {
background-color: #4CAF50;
color: #fff;
position: sticky;
top: 0;
z-index: 2;
}

/* 로그 섹션 스타일 */
#crawler-container .log {
max-height: 150px;
overflow-y: auto;
background-color: #1e1e1e;
padding: 10px;
border: 1px solid #555;
border-radius: 4px;
font-size: 14px;
white-space: pre-wrap;
}

/* 진행률 표시줄과 상태 텍스트 */
#crawler-container .progress-section {
display: flex;
align-items: center;
margin-top: 10px;
}

#crawler-container progress {
flex: 1;
height: 20px;
margin-right: 10px;
}

#crawler-container .status {
font-size: 14px;
font-weight: bold;
margin-top: 10px;
}

/* 차트 컨테이너 /
#crawler-container .chart-container {
position: relative;
height: 200px; / 크기 조정 */
margin-top: 20px;
}

/* HTML 디스플레이 섹션 /
#crawler-container .html-display {
width: 100%;
height: 400px; / 크기 조정 */
border: 1px solid #4CAF50;
margin-top: 15px;
border-radius: 8px;
}

/* 미리보기 팝업 스타일 */
.preview-popup {
position: absolute;
top: 60px;
left: 0;
width: 400px;
height: 300px;
background-color: #ffffff;
border: 1px solid #4CAF50;
box-shadow: 0 4px 10px rgba(0,0,0,0.2);
z-index: 10001;
display: none;
border-radius: 8px;
}

.preview-popup iframe {
width: 100%;
height: 100%;
border: none;
border-radius: 8px;
}

/* 번역기 섹션 스타일 */
#crawler-container .translator-section {
display: flex;
flex-direction: column;
}

#crawler-container .translator-section select,
#crawler-container .translator-section button {
margin-top: 5px;
}

/* 다크웹 팝업 스타일 */
.darkweb-popup {
position: absolute;
top: 60px;
left: 0;
width: 400px;
max-height: 350px;
background-color: #ffffff;
border: 1px solid #4CAF50;
box-shadow: 0 4px 10px rgba(0,0,0,0.2);
z-index: 10001;
overflow-y: auto;
display: none;
border-radius: 8px;
}

/* 검색 결과 필터링 섹션 */
#crawler-container .search-results-filter input[type="text"] {
margin-top: 0;
}

/* 반응형 디자인 */
@media (max-width: 700px) {
#crawler-container {
width: 95%;
right: 2.5%;
}
.preview-popup, .darkweb-popup {
width: 90%;
left: 5%;
}
}
`);

// UI 생성
const container = document.createElement('div');
container.id = 'crawler-container';
container.innerHTML = `

🔥 웹 크롤러 7.8 ×
설정
결과
로그
다크웹
HTML
번역기
▶️ 시작 ⏸️ 일시정지 ⏹️ 중지 🔄 초기화 💾 내보내기 🔍 검색 엔진
 HTML 가져오기  이미지 가져오기  광고 제외 필터링 기능 활성화 
중복 허용 횟수: 
2
재시도 횟수: 
3
 재시도 간격 (ms): 
500
 엄격 사이트 필터링 

 페이지 0
현재 페이지: 0
방문된 사이트: 0
경과 시간: 00:00:00
예상 남은 시간: 00:00:00
결과 검색...
사이트 이름	주소	설명	이미지	HTML 링크	페이지 로딩 시간 (ms)
로그:
다크웹 검색 엔진 ➕ 추가
번역 언어 선택: 
영어
 번역
`; document.body.appendChild(container);
// 드래그 가능하게 설정
(function makeDraggable(elmnt) {
const header = elmnt.querySelector('header');
const closeBtn = elmnt.querySelector('.close-btn');
let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
header.onmousedown = dragMouseDown;


closeBtn.onclick = () => {
    elmnt.style.display = 'none';
}

function dragMouseDown(e) {
    e = e || window.event;
    e.preventDefault();
    pos3 = e.clientX;
    pos4 = e.clientY;
    document.onmouseup = closeDragElement;
    document.onmousemove = elementDrag;
}

function elementDrag(e) {
    e = e || window.event;
    e.preventDefault();
    pos1 = pos3 - e.clientX;
    pos2 = pos4 - e.clientY;
    pos3 = e.clientX;
    pos4 = e.clientY;
    elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
    elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
}

function closeDragElement() {
    document.onmouseup = null;
    document.onmousemove = null;
}
})(container);

// 탭 기능 구현
(function setupTabs() {
const tabs = container.querySelectorAll('.tab');
const tabContents = container.querySelectorAll('.tab-content');


tabs.forEach(tab => {
    tab.addEventListener('click', () => {
        tabs.forEach(t => t.classList.remove('active'));
        tab.classList.add('active');

        const target = tab.getAttribute('data-tab');
        tabContents.forEach(content => {
            if(content.id === target){
                content.classList.add('active');
            } else {
                content.classList.remove('active');
            }
        });
    });
});
})();

// 변수 초기화
let isRunning = false;
let isPaused = false;
let currentPage = 1;
let results = [];
let searchQuery = '';
let searchEngine = '';
let visitedLinks = new Map(); // 도메인별 방문 횟수 추적
let logMessages = [];
let duplicateThreshold = 2; // 중복 허용 횟수
let retryAttempts = 3; // 재시도 횟수
let retryInterval = 500; // 재시도 간격 (ms)
let startTime = null;
let elapsedInterval = null;
let crawlChart = null;
let crawlData = {
labels: [], // 도메인 이름
datasets: [{
label: '방문 횟수',
data: [],
backgroundColor: 'rgba(54, 162, 235, 0.6)',
borderColor: 'rgba(54, 162, 235, 1)',
borderWidth: 1
}]
};
let filteringEnabled = true; // 필터링 활성화 여부
let concurrentRequests = 5; // 동시 요청 수
let currentRequests = 0; // 현재 요청 수
let requestQueue = []; // 요청 대기열
let translateLanguage = 'en'; // 기본 번역 언어

// 다크웹 검색 엔진 목록 (기본 엔진 추가)
const defaultDarkwebEngines = [
{ name: 'Torch', url: 'http://xmh57jrzrnw6insl.onion/' },
{ name: 'Ahmia', url: 'http://msydqstlz2kzerdg.onion/' },
{ name: 'Haystak', url: 'http://haystakvxad7wbk5.onion/' },
{ name: 'Not Evil', url: 'http://hss3uro2hsxfogfq.onion/' },
{ name: 'Phobos', url: 'http://phobos6uj4abm4pf.onion/' }
];

// 버튼 및 요소 가져오기
const startBtn = document.getElementById('startBtn');
const pauseBtn = document.getElementById('pauseBtn');
const stopBtn = document.getElementById('stopBtn');
const resetBtn = document.getElementById('resetBtn');
const exportBtn = document.getElementById('exportBtn');
const searchEnginesBtn = document.getElementById('searchEnginesBtn');
const progressBar = document.getElementById('progressBar');
const progressText = document.getElementById('progressText');
const resultsBody = document.getElementById('resultsBody');
const logContainer = document.getElementById('log');
const currentPageElem = document.getElementById('currentPage');
const toggleHTML = document.getElementById('toggleHTML');
const toggleImages = document.getElementById('toggleImages');
const toggleAds = document.getElementById('toggleAds');
const duplicateThresholdInput = document.getElementById('duplicateThreshold');
const retryAttemptsInput = document.getElementById('retryAttempts');
const retryIntervalInput = document.getElementById('retryInterval');
const strictSiteFilter = document.getElementById('strictSiteFilter');
const visitedCountElem = document.getElementById('visitedCount');
const elapsedTimeElem = document.getElementById('elapsedTime');
const remainingTimeElem = document.getElementById('remainingTime');
const crawlChartCtx = document.getElementById('crawlChart').getContext('2d');
const filteringToggle = document.getElementById('filteringToggle');
const darkwebEnginesList = document.getElementById('darkwebEnginesList');
const addDarkwebEngineBtn = container.querySelector('.add-darkweb-engine-btn');
const translateSelect = document.getElementById('translateSelect');
const translateBtn = document.getElementById('translateBtn');
const translatedContent = document.getElementById('translatedContent');
const resultSearchInput = document.getElementById('resultSearchInput'); // 추가된 검색 입력 필드

// 이벤트 리스너
startBtn.addEventListener('click', startCrawling);
pauseBtn.addEventListener('click', togglePause);
stopBtn.addEventListener('click', stopCrawling);
resetBtn.addEventListener('click', resetCrawling);
exportBtn.addEventListener('click', exportResults);
searchEnginesBtn.addEventListener('click', openSearchEngines);
duplicateThresholdInput.addEventListener('change', updateDuplicateThreshold);
retryAttemptsInput.addEventListener('change', updateRetryAttempts);
retryIntervalInput.addEventListener('change', updateRetryInterval);
strictSiteFilter.addEventListener('change', updateStrictSiteFilter);
filteringToggle.addEventListener('change', toggleFiltering);
addDarkwebEngineBtn.addEventListener('click', addDarkwebEngine);
translateSelect.addEventListener('change', () => {
translateLanguage = translateSelect.value;
});
translateBtn.addEventListener('click', translateContent);
resultSearchInput.addEventListener('input', filterResults); // 추가된 이벤트 리스너

// 페이지 새로 고침 시 크롤링 초기화
window.addEventListener('beforeunload', () => {
resetCrawling();
});

// 초기화 로드
loadLogs();
loadCrawlData();
loadDarkwebEngines();

// 차트 초기화
function initializeChart() {
crawlChart = new Chart(crawlChartCtx, {
type: 'bar',
data: crawlData,
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: { beginAtZero: true }
},
plugins: {
legend: {
display: false
},
tooltip: {
enabled: true
}
}
}
});
}
initializeChart();

// 로깅 함수
function log(message) {
const time = new Date().toLocaleTimeString();
const entry = [${time}] ${message};
logMessages.push(entry);
logContainer.innerHTML += ${entry}<br>;
logContainer.scrollTop = logContainer.scrollHeight;
// 저장
GM_setValue('crawlerLogs', JSON.stringify(logMessages));
}

function loadLogs() {
const storedLogs = GM_getValue('crawlerLogs', []);
if (storedLogs.length > 0) {
logMessages = JSON.parse(storedLogs);
logContainer.innerHTML = <strong>로그:</strong><br> + logMessages.join('
');
logContainer.scrollTop = logContainer.scrollHeight;
}
}

function updateDuplicateThreshold() {
const value = parseInt(duplicateThresholdInput.value);
duplicateThreshold = isNaN(value) || value < 1 ? 2 : value;
log(🔄 중복 허용 횟수 설정: ${duplicateThreshold});
}

function updateRetryAttempts() {
const value = parseInt(retryAttemptsInput.value);
retryAttempts = isNaN(value) || value < 1 ? 3 : value;
log(🔄 재시도 횟수 설정: ${retryAttempts});
}

function updateRetryInterval() {
const value = parseInt(retryIntervalInput.value);
retryInterval = isNaN(value) || value < 100 ? 500 : value;
log(🔄 재시도 간격 설정: ${retryInterval}ms);
}

function updateStrictSiteFilter() {
const isStrict = strictSiteFilter.checked;
log(🔄 엄격 사이트 필터링 설정: ${isStrict ? 'ON' : 'OFF'});
}

function toggleFiltering() {
filteringEnabled = filteringToggle.checked;
if (filteringEnabled) {
log('🔄 필터링 기능 활성화됨.');
} else {
log('🔄 필터링 기능 비활성화됨.');
}
}

function getSearchQueryAndEngine() {
const url = window.location.href;
if (url.includes('google.com/search')) {
const params = new URLSearchParams(window.location.search);
searchQuery = params.get('q') || '';
searchEngine = 'google';
} else if (url.includes('bing.com/search')) {
const params = new URLSearchParams(window.location.search);
searchQuery = params.get('q') || '';
searchEngine = 'bing';
} else if (url.includes('daum.net/search')) {
const params = new URLSearchParams(window.location.search);
searchQuery = params.get('q') || '';
searchEngine = 'daum';
} else if (url.includes('search.naver.com/search')) {
const params = new URLSearchParams(window.location.search);
searchQuery = params.get('query') || '';
searchEngine = 'naver';
} else if (url.includes('search.yahoo.com/search')) {
const params = new URLSearchParams(window.location.search);
searchQuery = params.get('p') || '';
searchEngine = 'yahoo';
} else if (url.includes('duckduckgo.com/')) {
const params = new URLSearchParams(window.location.search);
searchQuery = params.get('q') || '';
searchEngine = 'duckduckgo';
} else if (url.includes('ecosia.org/search')) {
const params = new URLSearchParams(window.location.search);
searchQuery = params.get('q') || '';
searchEngine = 'ecosia';
} else if (url.includes('.onion')) {
// 다크웹 검색 엔진 중 하나로 설정
searchEngine = 'darkweb';
const params = new URLSearchParams(window.location.search);
searchQuery = params.get('q') || '';
if (!searchQuery) {
searchQuery = prompt('다크웹 검색어를 입력하세요:');
}
} else {
searchQuery = '';
searchEngine = '';
}
log(🔍 검색 엔진: ${searchEngine}, 검색어: "${searchQuery}");
}

async function startCrawling() {
if (isRunning) {
alert('이미 크롤링이 진행 중입니다.');
return;
}
getSearchQueryAndEngine();
if (!searchQuery) {
alert('현재 페이지에서 검색어를 찾을 수 없습니다.');
log('⚠️ 검색어 미입력 또는 인식 실패.');
return;
}
isRunning = true;
isPaused = false;
currentPage = 1;
results = [];
visitedLinks.clear();
resultsBody.innerHTML = '';
visitedCountElem.innerText = '0';
elapsedTimeElem.innerText = '00:00:00';
remainingTimeElem.innerText = '00:00:00';
logMessages = [];
logContainer.innerHTML = <strong>로그:</strong><br>;
GM_setValue('crawlerLogs', JSON.stringify(logMessages));
log('✅ 크롤링을 시작합니다.');
updateDuplicateThreshold();
updateRetryAttempts();
updateRetryInterval();
await enqueueCrawlPages(concurrentRequests);
startTime = Date.now();
elapsedInterval = setInterval(updateElapsedTime, 1000);
}

function togglePause() {
if (!isRunning) {
alert('일시정지/재개할 크롤링이 없습니다.');
return;
}
isPaused = !isPaused;
if (isPaused) {
log('⏸️ 크롤링을 일시정지하였습니다.');
pauseBtn.innerText = '▶️ 재개';
} else {
log('▶️ 크롤링을 재개하였습니다.');
pauseBtn.innerText = '⏸️ 일시정지';
processQueue();
}
}

function stopCrawling() {
if (!isRunning) {
alert('중지할 크롤링이 없습니다.');
return;
}
isRunning = false;
isPaused = false;
pauseBtn.innerText = '⏸️ 일시정지';
clearInterval(elapsedInterval);
updateProgress();
log('🚫 크롤링을 중지하였습니다.');
GM_notification({
text: '크롤링이 중지되었습니다.',
title: '웹 크롤러 7.8',
timeout: 3000
});
}

async function enqueueCrawlPages(limit) {
for (let i = 0; i < limit; i++) {
queueCrawlPage();
}
}

function queueCrawlPage() {
if (!isRunning || isPaused) return;
if (currentRequests >= concurrentRequests) return;


currentRequests++;
crawlPage().then(() => {
    currentRequests--;
    queueCrawlPage();
});
}

async function crawlPage() {
if (!isRunning || isPaused) {
return;
}


// 검색 엔진에 따른 URL 구성
let url = '';
if (searchEngine === 'google') {
    url = `https://www.google.com/search?q=${encodeURIComponent(searchQuery)}&start=${(currentPage-1)*10}`;
} else if (searchEngine === 'bing') {
    url = `https://www.bing.com/search?q=${encodeURIComponent(searchQuery)}&first=${(currentPage-1)*10 +1}`;
} else if (searchEngine === 'daum') {
    url = `https://search.daum.net/search?q=${encodeURIComponent(searchQuery)}&page=${currentPage}`;
} else if (searchEngine === 'naver') {
    url = `https://search.naver.com/search.naver?query=${encodeURIComponent(searchQuery)}&start=${(currentPage-1)*10 +1}`;
} else if (searchEngine === 'yahoo') {
    url = `https://search.yahoo.com/search?p=${encodeURIComponent(searchQuery)}&b=${(currentPage-1)*10 +1}`;
} else if (searchEngine === 'duckduckgo') {
    url = `https://duckduckgo.com/html/?q=${encodeURIComponent(searchQuery)}&s=${(currentPage-1)*10}`;
} else if (searchEngine === 'ecosia') {
    url = `https://www.ecosia.org/search?q=${encodeURIComponent(searchQuery)}&page=${currentPage}`;
} else if (searchEngine === 'darkweb') {
    // 다크웹 검색 엔진 중 하나 선택
    const darkwebEngines = getDarkwebEngines();
    if (darkwebEngines.length === 0) {
        log('⚠️ 다크웹 검색 엔진이 설정되지 않았습니다.');
        isRunning = false;
        return;
    }
    const randomEngine = darkwebEngines[Math.floor(Math.random() * darkwebEngines.length)];
    url = `${randomEngine.url}/search?q=${encodeURIComponent(searchQuery)}&page=${currentPage}`;
} else {
    alert('지원하지 않는 검색 엔진입니다.');
    log('❌ 지원하지 않는 검색 엔진.');
    isRunning = false;
    return;
}

log(`📄 페이지 ${currentPage} 크롤링 중: ${url}`);

return new Promise((resolve) => {
    GM_xmlhttpRequest({
        method: "GET",
        url: url,
        headers: {
            "User-Agent": navigator.userAgent,
            "Accept": "text/html"
        },
        onload: async function(response) {
            if (response.status !== 200) {
                log(`❌ 페이지 ${currentPage} 로드 실패: 상태 ${response.status}`);
                await handleCrawlError(currentPage, retryAttempts);
                resolve();
                return;
            }

            const parser = new DOMParser();
            const doc = parser.parseFromString(response.responseText, 'text/html');
            let items = [];
            if (searchEngine === 'google') {
                items = doc.querySelectorAll('div.g');
            } else if (searchEngine === 'bing') {
                items = doc.querySelectorAll('li.b_algo');
            } else if (searchEngine === 'daum') {
                items = doc.querySelectorAll('div.wrap_cont');
            } else if (searchEngine === 'naver') {
                items = doc.querySelectorAll('div.basic1');
            } else if (searchEngine === 'yahoo') {
                items = doc.querySelectorAll('div.compTitle');
            } else if (searchEngine === 'duckduckgo') {
                items = doc.querySelectorAll('div.result');
            } else if (searchEngine === 'ecosia') {
                items = doc.querySelectorAll('div.result');
            } else if (searchEngine === 'darkweb') {
                // 다크웹 검색 결과 추출 (예시: Torch 검색 엔진 기준)
                items = doc.querySelectorAll('.result'); // 실제 검색 엔진에 맞게 조정 필요
            }

            // 만약 검색 결과가 없다면 크롤링 종료
            if (items.length === 0) {
                log('🔍 더 이상 크롤링할 페이지가 없습니다. 크롤링을 종료합니다.');
                isRunning = false;
                isPaused = false;
                pauseBtn.innerText = '⏸️ 일시정지';
                clearInterval(elapsedInterval);
                updateProgress();
                GM_notification({
                    text: '크롤링이 완료되었습니다.',
                    title: '웹 크롤러 7.8',
                    timeout: 3000
                });
                resolve();
                return;
            }

            log(`📊 페이지 ${currentPage}에서 ${items.length}개 항목 발견.`);

            // 병렬 처리: Promise.all을 이용하여 비동기적으로 처리
            const itemPromises = Array.from(items).map(async (item) => {
                let title = 'N/A';
                let link = 'N/A';
                let description = 'N/A';
                let image = 'N/A';
                let htmlLink = '비활성화';
                let pageLoadTime = 'N/A';

                // 각 검색 엔진에 따른 데이터 추출
                if (searchEngine === 'google') {
                    const titleElem = item.querySelector('h3');
                    const linkElem = item.querySelector('a');
                    const descElem = item.querySelector('.IsZvec') || item.querySelector('.VwiC3b');
                    if (titleElem) title = titleElem.innerText;
                    if (linkElem) link = linkElem.href;
                    if (descElem) description = descElem.innerText;

                    // 이미지 가져오기 (가능한 경우)
                    const imgElem = item.querySelector('img');
                    if (imgElem && imgElem.src) {
                        image = `<img src="${imgElem.src}" alt="이미지" style="max-width: 80px; max-height: 80px;">`;
                    }

                    // HTML 보기 링크
                    if (toggleHTML.checked && link !== 'N/A') {
                        htmlLink = `<button class="html-btn" data-url="${encodeURIComponent(link)}">보기</button>`;
                    }
                } else if (searchEngine === 'bing') {
                    const titleElem = item.querySelector('h2 a');
                    const descElem = item.querySelector('p');
                    if (titleElem) {
                        title = titleElem.innerText;
                        link = titleElem.href;
                    }
                    if (descElem) description = descElem.innerText;

                    // 이미지 가져오기 (가능한 경우)
                    const imgElem = item.querySelector('img');
                    if (imgElem && imgElem.src) {
                        image = `<img src="${imgElem.src}" alt="이미지" style="max-width: 80px; max-height: 80px;">`;
                    }

                    // HTML 보기 링크
                    if (toggleHTML.checked && link !== 'N/A') {
                        htmlLink = `<button class="html-btn" data-url="${encodeURIComponent(link)}">보기</button>`;
                    }
                } else if (searchEngine === 'daum') {
                    const titleElem = item.querySelector('a.link_fax');
                    const descElem = item.querySelector('div.cont_inner');
                    if (titleElem) {
                        title = titleElem.innerText.trim();
                        link = titleElem.href;
                    }
                    if (descElem) description = descElem.innerText.trim();

                    // 이미지 가져오기 (가능한 경우)
                    const imgElem = item.querySelector('img');
                    if (imgElem && imgElem.src) {
                        image = `<img src="${imgElem.src}" alt="이미지" style="max-width: 80px; max-height: 80px;">`;
                    }

                    // HTML 보기 링크
                    if (toggleHTML.checked && link !== 'N/A') {
                        htmlLink = `<button class="html-btn" data-url="${encodeURIComponent(link)}">보기</button>`;
                    }
                } else if (searchEngine === 'naver') {
                    const titleElem = item.querySelector('a.tit');
                    const descElem = item.querySelector('div.desc');
                    if (titleElem) {
                        title = titleElem.innerText;
                        link = titleElem.href;
                    }
                    if (descElem) description = descElem.innerText;

                    // 이미지 가져오기 (가능한 경우)
                    const imgElem = item.querySelector('img.thumb');
                    if (imgElem && imgElem.src) {
                        image = `<img src="${imgElem.src}" alt="이미지" style="max-width: 80px; max-height: 80px;">`;
                    }

                    // HTML 보기 링크
                    if (toggleHTML.checked && link !== 'N/A') {
                        htmlLink = `<button class="html-btn" data-url="${encodeURIComponent(link)}">보기</button>`;
                    }
                } else if (searchEngine === 'yahoo') {
                    const titleElem = item.querySelector('h3');
                    const linkElem = item.querySelector('a');
                    const descElem = item.querySelector('div.compText');
                    if (titleElem) title = titleElem.innerText;
                    if (linkElem) link = linkElem.href;
                    if (descElem) description = descElem.innerText;

                    // 이미지 가져오기 (가능한 경우)
                    const imgElem = item.querySelector('img');
                    if (imgElem && imgElem.src) {
                        image = `<img src="${imgElem.src}" alt="이미지" style="max-width: 80px; max-height: 80px;">`;
                    }

                    // HTML 보기 링크
                    if (toggleHTML.checked && link !== 'N/A') {
                        htmlLink = `<button class="html-btn" data-url="${encodeURIComponent(link)}">보기</button>`;
                    }
                } else if (searchEngine === 'duckduckgo') {
                    const titleElem = item.querySelector('a.result__a');
                    const descElem = item.querySelector('a.result__snippet');
                    if (titleElem) {
                        title = titleElem.innerText;
                        link = titleElem.href;
                    }
                    if (descElem) description = descElem.innerText;

                    // 이미지 가져오기 (가능한 경우)
                    const imgElem = item.querySelector('img.result__thumbnail');
                    if (imgElem && imgElem.src) {
                        image = `<img src="${imgElem.src}" alt="이미지" style="max-width: 80px; max-height: 80px;">`;
                    }

                    // HTML 보기 링크
                    if (toggleHTML.checked && link !== 'N/A') {
                        htmlLink = `<button class="html-btn" data-url="${encodeURIComponent(link)}">보기</button>`;
                    }
                } else if (searchEngine === 'ecosia') {
                    const titleElem = item.querySelector('a.result-title');
                    const descElem = item.querySelector('div.result-description');
                    if (titleElem) {
                        title = titleElem.innerText;
                        link = titleElem.href;
                    }
                    if (descElem) description = descElem.innerText;

                    // 이미지 가져오기 (가능한 경우)
                    const imgElem = item.querySelector('img.result-image');
                    if (imgElem && imgElem.src) {
                        image = `<img src="${imgElem.src}" alt="이미지" style="max-width: 80px; max-height: 80px;">`;
                    }

                    // HTML 보기 링크
                    if (toggleHTML.checked && link !== 'N/A') {
                        htmlLink = `<button class="html-btn" data-url="${encodeURIComponent(link)}">보기</button>`;
                    }
                } else if (searchEngine === 'darkweb') {
                    // 다크웹 검색 결과 추출 (예시: Torch 검색 엔진 기준)
                    const titleElem = item.querySelector('a');
                    const descElem = item.querySelector('p');
                    if (titleElem) {
                        title = titleElem.innerText;
                        link = titleElem.href;
                    }
                    if (descElem) description = descElem.innerText;

                    // 이미지 가져오기 (가능한 경우)
                    const imgElem = item.querySelector('img');
                    if (imgElem && imgElem.src) {
                        image = `<img src="${imgElem.src}" alt="이미지" style="max-width: 80px; max-height: 80px;">`;
                    }

                    // HTML 보기 링크
                    if (toggleHTML.checked && link !== 'N/A') {
                        htmlLink = `<button class="html-btn" data-url="${encodeURIComponent(link)}">보기</button>`;
                    }
                }

                // 광고 제외 기능 적용
                if (toggleAds.checked) {
                    const combinedText = `${title} ${description}`.toLowerCase();
                    const isAd = combinedText.includes('advertisement') || combinedText.includes('광고');
                    if (isAd) {
                        log(`🚫 광고 항목 제외: "${title}"`);
                        return null;
                    }
                }

                // 도메인 추출
                const domain = extractDomain(link);
                if (!domain) return null;

                // 중복 검사 (도메인별 방문 횟수)
                let visitCount = visitedLinks.get(domain) || 0;
                if (visitCount >= duplicateThreshold) {
                    log(`🔄 중복 방문 제한: "${domain}"`);
                    return null;
                }

                // 방문 횟수 업데이트
                visitedLinks.set(domain, visitCount + 1);
                visitedCountElem.innerText = visitedLinks.size.toString();

                // 페이지 내용 가져오기 및 필터링
                if (filteringEnabled) {
                    try {
                        const response = await fetch(link, { mode: 'cors' });
                        const text = await response.text();
                        if (text.toLowerCase().includes('기부')) { // "기부" 단어 필터링
                            log(`🚫 필터링: "기부" 단어가 포함된 사이트 제외: "${title}"`);
                            return null;
                        }
                    } catch (err) {
                        log(`⚠️ 사이트 내용 검토 실패: "${title}". 계속 진행합니다.`);
                    }
                }

                // 페이지 로딩 시간 측정
                const loadStart = performance.now();
                try {
                    // mode: 'cors'로 변경하여 실제 응답을 받을 수 있도록 함
                    const fetchResponse = await fetch(link, { mode: 'cors' });
                    if (fetchResponse.ok) {
                        const loadEnd = performance.now();
                        pageLoadTime = Math.round(loadEnd - loadStart);
                    } else {
                        pageLoadTime = '응답 불량';
                    }
                } catch (err) {
                    pageLoadTime = '계산 불가';
                }

                // HTML 옵션
                if (!toggleHTML.checked) {
                    htmlLink = '비활성화';
                }

                // 이미지 옵션
                if (!toggleImages.checked) {
                    image = '비활성화';
                }

                const result = { title, link, description, image, htmlLink, pageLoadTime };
                results.push(result);

                // 결과 테이블에 추가
                const row = document.createElement('tr');
                row.innerHTML = `
                    <td>${escapeHTML(title)}</td>
                    <td><a href="${escapeHTML(link)}" target="_blank">${escapeHTML(link)}</a></td>
                    <td>${escapeHTML(description)}</td>
                    <td>${image}</td>
                    <td>${htmlLink}</td>
                    <td>${pageLoadTime}</td>
                `;
                resultsBody.appendChild(row);

                return result;
            });

            // Promise.all을 통해 모든 항목 처리 완료 대기
            await Promise.all(itemPromises);

            // 필터링된 결과만 유지
            resultsBody.querySelectorAll('tr').forEach(row => {
                if (!row.innerHTML.includes('비활성화') && !row.innerHTML.includes('보기')) {
                    // 추가 로직이 필요한 경우 여기에 작성
                }
            });

            // 데이터 시각화 업데이트
            updateChart();

            // 진행률 업데이트
            updateProgress();

            // 다음 페이지로 이동
            currentPage++;
            currentPageElem.innerText = currentPage;

            // 대기 시간 추가하여 서버에 부담 주지 않기
            setTimeout(() => {
                queueCrawlPage();
            }, isPaused ? 0 : 1000); // 대기 시간을 1초로 줄임

            resolve();
        },
        onerror: function() {
            log(`❌ 페이지 ${currentPage} 요청 중 오류 발생.`);
            handleCrawlError(currentPage, retryAttempts).then(() => {
                resolve();
            });
        }
    });
});
}

async function handleCrawlError(page, retriesLeft) {
if (retriesLeft > 0 && isRunning) {
log(🔄 페이지 ${page} 재시도 (${retriesLeft}회 남음).);
return new Promise((resolve) => {
setTimeout(() => {
const url = constructURL(page);
GM_xmlhttpRequest({
method: "GET",
url: url,
headers: {
"User-Agent": navigator.userAgent,
"Accept": "text/html"
},
onload: async function(response) {
if (response.status === 200) {
log(✅ 페이지 ${page} 재시도 성공.);
await crawlPage();
} else {
await handleCrawlError(page, retriesLeft - 1);
}
resolve();
},
onerror: async function() {
await handleCrawlError(page, retriesLeft - 1);
resolve();
}
});
}, retryInterval);
});
} else {
log(❌ 페이지 ${page} 로드 실패: 최대 재시도 횟수 초과.);
// 크롤링을 중지하지 않고 다음 페이지로 넘어감
currentPage++;
currentPageElem.innerText = currentPage;
setTimeout(() => {
queueCrawlPage();
}, 1000);
return Promise.resolve();
}
}

function constructURL(page) {
if (searchEngine === 'google') {
return https://www.google.com/search?q=${encodeURIComponent(searchQuery)}&start=${(page-1)*10};
} else if (searchEngine === 'bing') {
return https://www.bing.com/search?q=${encodeURIComponent(searchQuery)}&first=${(page-1)*10 +1};
} else if (searchEngine === 'daum') {
return https://search.daum.net/search?q=${encodeURIComponent(searchQuery)}&page=${page};
} else if (searchEngine === 'naver') {
return https://search.naver.com/search.naver?query=${encodeURIComponent(searchQuery)}&start=${(page-1)*10 +1};
} else if (searchEngine === 'yahoo') {
return https://search.yahoo.com/search?p=${encodeURIComponent(searchQuery)}&b=${(page-1)*10 +1};
} else if (searchEngine === 'duckduckgo') {
return https://duckduckgo.com/html/?q=${encodeURIComponent(searchQuery)}&s=${(page-1)*10};
} else if (searchEngine === 'ecosia') {
return https://www.ecosia.org/search?q=${encodeURIComponent(searchQuery)}&page=${page};
} else if (searchEngine === 'darkweb') {
// 다크웹 검색 엔진 중 하나 선택
const darkwebEngines = getDarkwebEngines();
if (darkwebEngines.length === 0) {
return '';
}
const randomEngine = darkwebEngines[Math.floor(Math.random() * darkwebEngines.length)];
return ${randomEngine.url}/search?q=${encodeURIComponent(searchQuery)}&page=${page};
} else {
return '';
}
}

function updateProgress() {
// 무제한이므로 진행률을 페이지 수에 기반하여 표시
const progressPercent = (currentPage % 100) * 1; // 100페이지마다 100%로 리셋
progressBar.value = progressPercent > 100 ? 100 : progressPercent;
progressText.innerText = 페이지 ${currentPage};
if (progressPercent >= 100) {
progressBar.value = 0;
}
}

function resetCrawling() {
isRunning = false;
isPaused = false;
currentPage = 1;
results = [];
visitedLinks.clear();
resultsBody.innerHTML = '';
progressBar.value = 0;
progressText.innerText = '페이지 0';
currentPageElem.innerText = '0';
visitedCountElem.innerText = '0';
elapsedTimeElem.innerText = '00:00:00';
remainingTimeElem.innerText = '00:00:00';
logMessages = [];
logContainer.innerHTML = <strong>로그:</strong><br>;
GM_setValue('crawlerLogs', JSON.stringify(logMessages));
// 차트 초기화
crawlData.labels = [];
crawlData.datasets[0].data = [];
if (crawlChart) {
crawlChart.update();
}
clearInterval(elapsedInterval);
log('🔄 크롤링을 초기화하였습니다.');
}

function exportResults() {
if (results.length === 0) {
alert('내보낼 결과가 없습니다.');
return;
}
const format = prompt('내보낼 형식을 선택하세요 (xlsx, csv, json):', 'xlsx');
if (!format) return;
if (format.toLowerCase() === 'xlsx') {
const ws = XLSX.utils.json_to_sheet(results);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, "결과");
XLSX.writeFile(wb, "크롤링_결과.xlsx");
log('💾 결과를 "크롤링_결과.xlsx" 파일로 내보냈습니다.');
GM_notification({
text: '결과를 "크롤링_결과.xlsx" 파일로 내보냈습니다.',
title: '웹 크롤러 7.8',
timeout: 3000
});
} else if (format.toLowerCase() === 'csv') {
const csv = results.map(row => [
"${row.title.replace(/"/g, '""')}",
"${row.link.replace(/"/g, '""')}",
"${row.description.replace(/"/g, '""')}",
"${row.image.replace(/"/g, '""')}",
"${row.htmlLink.replace(/"/g, '""')}",
"${row.pageLoadTime}"
].join(',')).join('\n');
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = '크롤링_결과.csv';
a.click();
log('💾 결과를 "크롤링_결과.csv" 파일로 내보냈습니다.');
GM_notification({
text: '결과를 "크롤링_결과.csv" 파일로 내보냈습니다.',
title: '웹 크롤러 7.8',
timeout: 3000
});
} else if (format.toLowerCase() === 'json') {
const json = JSON.stringify(results, null, 2);
const blob = new Blob([json], { type: 'application/json;charset=utf-8;' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = '크롤링_결과.json';
a.click();
log('💾 결과를 "크롤링_결과.json" 파일로 내보냈습니다.');
GM_notification({
text: '결과를 "크롤링_결과.json" 파일로 내보냈습니다.',
title: '웹 크롤러 7.8',
timeout: 3000
});
} else {
alert('지원하지 않는 형식입니다.');
}
}

function escapeHTML(str) {
if (!str) return '';
return str.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}

function extractDomain(url) {
try {
const urlObj = new URL(url);
return urlObj.hostname.replace('www.', '');
} catch (e) {
return null;
}
}

function updateElapsedTime() {
if (!startTime) return;
const now = Date.now();
const elapsed = Math.floor((now - startTime) / 1000);
elapsedTimeElem.innerText = formatTime(elapsed);
const estimatedTotal = currentPage * 1.0; // 예측: 각 페이지 1초
const remaining = Math.max(estimatedTotal - elapsed, 0);
remainingTimeElem.innerText = formatTime(remaining);
}

function formatTime(seconds) {
const hrs = String(Math.floor(seconds / 3600)).padStart(2, '0');
const mins = String(Math.floor((seconds % 3600) / 60)).padStart(2, '0');
const secs = String(seconds % 60).padStart(2, '0');
return ${hrs}:${mins}:${secs};
}

function updateChart() {
crawlData.labels = Array.from(visitedLinks.keys());
crawlData.datasets[0].data = Array.from(visitedLinks.values());


if (crawlChart) {
    crawlChart.update();
}
// 저장
GM_setValue('crawlerData', JSON.stringify(crawlData));
}

function loadCrawlData() {
const storedData = GM_getValue('crawlerData', null);
if (storedData) {
const parsedData = JSON.parse(storedData);
crawlData = parsedData;
if (crawlChart) {
crawlChart.data = crawlData;
crawlChart.update();
}
}
}

function saveCrawlData() {
GM_setValue('crawlerData', JSON.stringify(crawlData));
}

// 주기적으로 크롤링 데이터를 저장
setInterval(saveCrawlData, 5000);

// 검색 엔진 바로 가기 버튼 기능
function openSearchEngines() {
const engineUrls = {
'Google': 'https://www.google.com/',
'Bing': 'https://www.bing.com/',
'Daum': 'https://www.daum.net/',
'Naver': 'https://www.naver.com/',
'Yahoo': 'https://www.yahoo.com/',
'DuckDuckGo': 'https://duckduckgo.com/',
'Ecosia': 'https://www.ecosia.org/',
'Torch': 'http://xmh57jrzrnw6insl.onion/',
'Ahmia': 'http://msydqstlz2kzerdg.onion/',
'Haystak': 'http://haystakvxad7wbk5.onion/',
'Not Evil': 'http://hss3uro2hsxfogfq.onion/',
'Phobos': 'http://phobos6uj4abm4pf.onion/'
};
let html = '📡 검색 엔진:
';
for (const [name, url] of Object.entries(engineUrls)) {
html += <button class="search-engine-btn" data-url="${url}">${name}</button><br>;
}
// 팝업 생성
const popup = document.createElement('div');
popup.className = 'preview-popup';
popup.innerHTML = html;
container.appendChild(popup);
popup.style.display = 'block';


// 버튼 이벤트 리스너
popup.querySelectorAll('.search-engine-btn').forEach(btn => {
    btn.addEventListener('click', () => {
        window.open(btn.getAttribute('data-url'), '_blank');
        popup.style.display = 'none';
    });
});

// 외부 클릭 시 팝업 닫기
document.addEventListener('click', function(event) {
    if (!popup.contains(event.target) && event.target !== searchEnginesBtn) {
        popup.style.display = 'none';
    }
}, { once: true });
}

// HTML 탭에 콘텐츠 표시 기능
container.addEventListener('click', function(event) {
if (event.target.classList.contains('html-btn')) {
const url = decodeURIComponent(event.target.getAttribute('data-url'));
showHTMLInUI(url);
}
});

container.addEventListener('mouseover', function(event) {
if (event.target.classList.contains('html-btn')) {
const url = decodeURIComponent(event.target.getAttribute('data-url'));
showPreviewPopup(event.target, url);
}
});

container.addEventListener('mouseout', function(event) {
if (event.target.classList.contains('html-btn')) {
const popup = container.querySelector('.preview-popup');
if (popup) {
popup.style.display = 'none';
}
}
});

function showHTMLInUI(url) {
// UI 내에 iframe을 사용하여 사이트 내용 표시
let iframe = container.querySelector('.html-display');
if (!iframe) {
iframe = document.createElement('iframe');
iframe.className = 'html-display';
container.querySelector('#html-display').appendChild(iframe);
}
iframe.src = url;
// 탭을 'HTML'로 전환
switchTab('html-display');
log(🔍 HTML 보기: ${url});
}

function showPreviewPopup(target, url) {
// 팝업 생성
let popup = container.querySelector('.preview-popup');
if (!popup) {
popup = document.createElement('div');
popup.className = 'preview-popup';
container.appendChild(popup);
}
popup.style.display = 'block';
popup.style.left = ${target.offsetLeft}px;
popup.style.top = ${target.offsetTop + target.offsetHeight + 5}px;
popup.innerHTML = <iframe src="${url}" style="width:100%; height:100%; border:none;"></iframe>;
}

function switchTab(tabId) {
const tabs = container.querySelectorAll('.tab');
const tabContents = container.querySelectorAll('.tab-content');


tabs.forEach(t => {
    if(t.getAttribute('data-tab') === tabId){
        t.classList.add('active');
    } else {
        t.classList.remove('active');
    }
});

tabContents.forEach(content => {
    if(content.id === tabId){
        content.classList.add('active');
    } else {
        content.classList.remove('active');
    }
});
}

// 다크웹 검색 엔진 관리
function loadDarkwebEngines() {
const storedEngines = GM_getValue('darkwebEngines', defaultDarkwebEngines);
darkwebEnginesList.innerHTML = '';
storedEngines.forEach(engine => {
const li = document.createElement('li');
li.style.display = 'flex';
li.style.justifyContent = 'space-between';
li.style.alignItems = 'center';
li.style.marginBottom = '8px';
li.innerHTML = <span>${escapeHTML(engine.name)}</span> <button class="remove-darkweb-engine-btn" data-url="${engine.url}" style="background-color: #ff4d4d;">🗑️</button>;
darkwebEnginesList.appendChild(li);
});


// 삭제 버튼 이벤트 리스너
darkwebEnginesList.querySelectorAll('.remove-darkweb-engine-btn').forEach(btn => {
    btn.addEventListener('click', () => {
        const url = btn.getAttribute('data-url');
        removeDarkwebEngine(url);
    });
});
}

function addDarkwebEngine() {
const name = prompt('다크웹 검색 엔진 이름을 입력하세요:');
if (!name) {
alert('이름을 입력하지 않았습니다.');
return;
}
const url = prompt('다크웹 검색 엔진 URL을 입력하세요 (예: http://exampleonion.onion/):');
if (!url) {
alert('URL을 입력하지 않았습니다.');
return;
}
const newEngine = { name, url };
const storedEngines = GM_getValue('darkwebEngines', defaultDarkwebEngines);
storedEngines.push(newEngine);
GM_setValue('darkwebEngines', storedEngines);
loadDarkwebEngines();
log(🔄 다크웹 검색 엔진 추가: ${name} (${url}));
}

function removeDarkwebEngine(url) {
let storedEngines = GM_getValue('darkwebEngines', defaultDarkwebEngines);
storedEngines = storedEngines.filter(engine => engine.url !== url);
GM_setValue('darkwebEngines', storedEngines);
loadDarkwebEngines();
log(🔄 다크웹 검색 엔진 제거: ${url});
}

function getDarkwebEngines() {
return GM_getValue('darkwebEngines', defaultDarkwebEngines);
}

// 번역기 기능 추가
async function translateContent() {
if (results.length === 0) {
alert('번역할 결과가 없습니다.');
return;
}
translatedContent.innerHTML = '번역 중...';


try {
    // i18next를 이용한 번역
    const texts = results.map(r => r.description).join('. ');
    const translatedText = await translateText(texts, translateLanguage);
    translatedContent.innerText = translatedText;
    log('🔄 번역이 완료되었습니다.');
} catch (error) {
    translatedContent.innerText = '번역 중 오류가 발생했습니다.';
    log(`❌ 번역 오류: ${error}`);
}
}

async function translateText(text, targetLang) {
// Google Translate API 사용 (API 키 필요)
const apiKey = 'YOUR_GOOGLE_TRANSLATE_API_KEY'; // [필수] 실제 API 키로 교체하세요.
const url = https://translation.googleapis.com/language/translate/v2?key=${apiKey};
const data = {
q: text,
target: targetLang
};
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
const result = await response.json();
if (result.error) {
throw new Error(result.error.message);
}
return result.data.translations.map(t => t.translatedText).join('. ');
}

// 크롤링 관련 함수들은 기존과 동일하게 유지됩니다.

// 결과 탭의 검색 기능 구현
function filterResults() {
const filter = resultSearchInput.value.toLowerCase();
const rows = resultsBody.getElementsByTagName('tr');
Array.from(rows).forEach(row => {
const columns = row.getElementsByTagName('td');
let match = false;
Array.from(columns).forEach(col => {
if (col.textContent.toLowerCase().includes(filter)) {
match = true;
}
});
if (match) {
row.style.display = '';
} else {
row.style.display = 'none';
}
});
}

// 크롤링 관련 함수들은 기존과 동일하게 유지됩니다.
})();중복 방문 제한을 없애고 내가 말한 거르는 기능들만 추가해! + 그리고 사이트 거르는 기능 탭으로 따로 분리하셈 + 제한 자체를 여기에서 없애셈 + 전에 한거는 반영하지 말고 코드만 전에 걸 쓰셈 + ui대폭 개선하셈 + 크롤러 성능 대폭 개선하셈 + 기능들 대폭 개선하셈