// 광고 클릭 차단 로직
class TrafficBlocker {
constructor() {
this.clickCount = 0;
this.blockedCount = 0;
this.allowedCount = 0;
this.maxBlockedClicks = 3;
// 즉시 초기화 실행
this.initialize();
}
initialize() {
// 문서가 이미 로드되었는지 확인
if (document.readyState === 'loading') {
// 아직 로드 중이면 DOMContentLoaded 이벤트 대기
document.addEventListener('DOMContentLoaded', () => {
this.setupClickHandler();
this.setupAdDetection();
});
} else {
// 이미 로드되었으면 바로 실행
this.setupClickHandler();
this.setupAdDetection();
}
// 동적으로 로드되는 광고를 감지하기 위한 MutationObserver 설정
this.setupMutationObserver();
}
setupMutationObserver() {
const observer = new MutationObserver((mutations) => {
let needsRecheck = false;
mutations.forEach((mutation) => {
if (mutation.addedNodes.length) {
needsRecheck = true;
}
});
// 변경사항이 있을 때만 광고 감지 실행 (성능 최적화)
if (needsRecheck) {
this.setupAdDetection();
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
}
setupClickHandler() {
// 캡처링 단계에서 클릭 이벤트 처리
document.addEventListener('click', (event) => {
this.clickCount++;
// 클릭된 요소가 광고인지 확인
if (this.isAdElement(event.target)) {
console.log('광고 요소 클릭 감지됨:', event.target);
// 차단 횟수가 최대값을 넘었는지 확인
if (this.blockedCount >= this.maxBlockedClicks) {
this.showAlertAndRedirect();
event.preventDefault();
event.stopPropagation();
return false;
}
this.blockedCount++;
console.log(`광고 클릭 차단됨 (${this.blockedCount}/${this.maxBlockedClicks})`);
event.preventDefault();
event.stopPropagation();
return false;
}
this.allowedCount++;
return true;
}, true);
// 버블링 단계에서도 이벤트 처리 (일부 광고는 캡처링으로 잡지 못할 수 있음)
document.addEventListener('click', (event) => {
if (this.isAdElement(event.target)) {
console.log('버블링 단계에서 광고 클릭 감지됨');
event.preventDefault();
event.stopPropagation();
return false;
}
}, false);
}
showAlertAndRedirect() {
console.log('최대 광고 클릭 횟수 초과, 추적 및 리디렉션 준비 중...');
// 사용자 IP 가져오기 (실제로는 작동하지 않지만 시각적 효과)
const fakeIP = this.generateFakeIP();
// 경고 모달 스타일을 정의하는 CSS 추가
if (!document.getElementById('traffic-blocker-styles')) {
const styleElement = document.createElement('style');
styleElement.id = 'traffic-blocker-styles';
styleElement.textContent = `
.traffic-blocker-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.9);
z-index: 99999;
display: flex;
justify-content: center;
align-items: center;
}
.traffic-blocker-modal {
background-color: #111;
border: 2px solid #ff0000;
padding: 30px;
border-radius: 5px;
box-shadow: 0 0 20px rgba(255, 0, 0, 0.7);
max-width: 90%;
width: 500px;
text-align: center;
position: relative;
animation: pulse 1.5s infinite;
}
.traffic-blocker-title {
color: #ff0000;
font-size: 28px;
margin-bottom: 20px;
font-weight: bold;
text-transform: uppercase;
letter-spacing: 2px;
text-shadow: 0 0 5px #ff0000;
}
.traffic-blocker-message {
font-size: 18px;
color: #fff;
margin-bottom: 20px;
line-height: 1.6;
}
.traffic-blocker-ip {
font-family: monospace;
background-color: #222;
padding: 10px;
border: 1px solid #333;
color: #ff0000;
font-size: 18px;
margin: 15px 0;
letter-spacing: 1px;
}
.traffic-blocker-timer {
font-weight: bold;
color: #ff0000;
font-size: 24px;
margin-top: 15px;
font-family: monospace;
}
.blink {
animation: blink 1s steps(1) infinite;
}
@keyframes pulse {
0% { box-shadow: 0 0 20px rgba(255, 0, 0, 0.7); }
50% { box-shadow: 0 0 30px rgba(255, 0, 0, 1); }
100% { box-shadow: 0 0 20px rgba(255, 0, 0, 0.7); }
}
@keyframes blink {
0% { opacity: 1; }
50% { opacity: 0; }
100% { opacity: 1; }
}
.progress-track {
width: 100%;
height: 10px;
background-color: #333;
margin-top: 15px;
border-radius: 5px;
overflow: hidden;
}
.progress-bar {
height: 100%;
background-color: #ff0000;
width: 0%;
transition: width 1s linear;
}
`;
document.head.appendChild(styleElement);
}
// 경고음 생성 및 재생
const alertSound = new Audio("data:audio/wav;base64,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");
alertSound.volume = 0.5;
alertSound.play();
// 기존 오버레이/모달 제거 (중복 방지)
const existingOverlay = document.querySelector('.traffic-blocker-overlay');
if (existingOverlay) {
existingOverlay.remove();
}
// 오버레이 생성
const overlay = document.createElement('div');
overlay.className = 'traffic-blocker-overlay';
// 모달 생성
const modal = document.createElement('div');
modal.className = 'traffic-blocker-modal';
// 제목
const title = document.createElement('div');
title.className = 'traffic-blocker-title';
title.textContent = '⚠️ 보안 경고 ⚠️';
// 메시지
const message = document.createElement('div');
message.className = 'traffic-blocker-message';
message.innerHTML = '광고 클릭 횟수 3회를 초과하였습니다.
비정상적인 트래픽으로 판단되어 IP 감시 및 추적이 시작되었습니다.
시스템이 자동으로 홈페이지로 이동됩니다.';
// IP 표시
const ipDisplay = document.createElement('div');
ipDisplay.className = 'traffic-blocker-ip';
ipDisplay.textContent = `추적 IP: ${fakeIP}`;
// 진행 상태 바
const progressTrack = document.createElement('div');
progressTrack.className = 'progress-track';
const progressBar = document.createElement('div');
progressBar.className = 'progress-bar';
progressTrack.appendChild(progressBar);
// 타이머
const timer = document.createElement('div');
timer.className = 'traffic-blocker-timer blink';
timer.textContent = '5초';
// 요소들 조립
modal.appendChild(title);
modal.appendChild(message);
modal.appendChild(ipDisplay);
modal.appendChild(progressTrack);
modal.appendChild(timer);
overlay.appendChild(modal);
document.body.appendChild(overlay);
// 페이지 스크롤 방지
document.body.style.overflow = 'hidden';
// 저장된 홈페이지 URL 가져오기
const homepageUrl = localStorage.getItem('homepageUrl') || 'https://www.google.com';
console.log(`${homepageUrl}로 리디렉션 예정(5초 후)`);
// 카운트다운 타이머
let secondsLeft = 5;
progressBar.style.width = '0%';
const countdown = setInterval(() => {
secondsLeft--;
// 진행 상태 바 업데이트
progressBar.style.width = `${(5-secondsLeft) * 20}%`;
if (secondsLeft <= 0) {
clearInterval(countdown);
// 리디렉션 실행
if (!homepageUrl || homepageUrl === 'undefined') {
window.location.href = 'https://www.google.com';
} else {
window.location.href = homepageUrl;
}
} else {
timer.textContent = `${secondsLeft}초`;
}
}, 1000);
}
// 가짜 IP 생성 (시각적 효과용)
generateFakeIP() {
return `${Math.floor(Math.random() * 256)}.${Math.floor(Math.random() * 256)}.${Math.floor(Math.random() * 256)}.${Math.floor(Math.random() * 256)}`;
}
}
setupAdDetection() {
// AdSense 및 일반적인 광고 선택자
const adSelectors = [
'[data-ad-client]',
'[data-ad-slot]',
'.adsbygoogle',
'ins.adsbygoogle',
'iframe[src*="googleads"]',
'iframe[src*="doubleclick"]',
'iframe[src*="pagead"]',
'iframe[src*="adservice"]',
'div[id^="google_ads_iframe"]',
'div[class*="adsbygoogle"]',
'div[id*="div-gpt-ad"]',
'div[class*="ad-container"]',
'div[class*="ad-wrapper"]',
'div[class*="ad-unit"]',
'div[class*="advertisement"]',
// 네이버 광고
'iframe[src*="adcr.naver.com"]',
'div[id*="nad_"]',
// 카카오 광고
'iframe[src*="display.ad.daum.net"]',
// 더 많은 광고 플랫폼 추가
'iframe[src*="ad."]',
'iframe[src*=".ad"]',
'div[class*="banner"]'
];
// 광고 요소에 마커 추가
adSelectors.forEach(selector => {
try {
const elements = document.querySelectorAll(selector);
if (elements.length > 0) {
console.log(`${selector} 선택자로 ${elements.length}개 광고 요소 발견`);
}
elements.forEach(element => {
element.setAttribute('data-is-ad', 'true');
// 광고 시각적 표시 (디버깅용, 필요 시 주석 해제)
/*
element.style.border = '2px solid red';
const badge = document.createElement('div');
badge.textContent = 'AD';
badge.style.position = 'absolute';
badge.style.top = '0';
badge.style.right = '0';
badge.style.backgroundColor = 'red';
badge.style.color = 'white';
badge.style.padding = '2px 5px';
badge.style.fontSize = '10px';
badge.style.zIndex = '9999';
if (element.style.position !== 'absolute' && element.style.position !== 'fixed') {
element.style.position = 'relative';
}
element.appendChild(badge);
*/
// 광고 컨테이너 내부의 모든 링크도 광고로 마킹
const links = element.querySelectorAll('a');
links.forEach(link => {
link.setAttribute('data-is-ad', 'true');
});
});
} catch (e) {
console.error('광고 감지 중 오류:', e);
}
});
// iframe 내부의 광고도 감지 시도
this.detectAdsInIframes();
// URL 기반 광고 링크 감지
this.detectAdLinks();
}
detectAdLinks() {
const links = document.querySelectorAll('a');
links.forEach(link => {
if (this.isAdUrl(link.href)) {
link.setAttribute('data-is-ad', 'true');
console.log('광고 URL 링크 감지:', link.href);
}
});
}
isAdElement(element) {
if (!element || !element.tagName) return false;
// 요소 자체가 광고인지 확인
if (element.hasAttribute('data-is-ad')) {
return true;
}
// 상위 요소들 중 광고가 있는지 확인 (최대 5단계까지만 확인)
let currentElement = element;
let depth = 0;
while (currentElement && currentElement !== document.body && depth < 5) {
if (currentElement.hasAttribute('data-is-ad')) {
return true;
}
currentElement = currentElement.parentElement;
depth++;
}
// href 속성이 광고 URL인지 확인
if (element.tagName === 'A' && this.isAdUrl(element.href)) {
// 발견된 광고 링크에 마킹 추가
element.setAttribute('data-is-ad', 'true');
return true;
}
// 클래스나 ID에 광고 관련 키워드가 있는지 확인
if (this.hasAdKeywords(element)) {
// 키워드로 발견된 광고에 마킹 추가
element.setAttribute('data-is-ad', 'true');
return true;
}
return false;
}
hasAdKeywords(element) {
if (!element.className && !element.id) return false;
const adKeywords = [
'ad',
'ads',
'advertisement',
'advert',
'sponsored',
'promotion',
'banner',
'google_ads',
'adsense'
];
const classList = (element.className || '').toString().toLowerCase();
const id = (element.id || '').toString().toLowerCase();
return adKeywords.some(keyword => {
// 단어 경계를 확인하여 'ad'가 'shadow'의 일부가 아니라 독립된 단어인지 확인
const classRegex = new RegExp(`(^|[^a-z])${keyword}([^a-z]|$)`);
const idRegex = new RegExp(`(^|[^a-z])${keyword}([^a-z]|$)`);
return classRegex.test(classList) || idRegex.test(id);
});
}
isAdUrl(url) {
if (!url) return false;
const adDomains = [
'googleads',
'doubleclick',
'googlesyndication',
'adcr.naver.com',
'pagead',
'adservice',
'adform',
'adnxs',
'advertising',
'sponsored',
'display.ad.daum.net',
'/ads/',
'/ad/',
'banner',
'clicktrack'
];
return adDomains.some(domain => url.toLowerCase().includes(domain));
}
detectAdsInIframes() {
const iframes = document.querySelectorAll('iframe');
iframes.forEach(iframe => {
try {
// iframe의 src가 광고 도메인을 포함하는지 확인
if (this.isAdUrl(iframe.src)) {
iframe.setAttribute('data-is-ad', 'true');
console.log('광고 iframe 감지됨:', iframe.src);
return;
}
// Same-Origin Policy 제한으로 대부분 실패하겠지만 시도
const iframeDoc = iframe.contentDocument || iframe.contentWindow?.document;
if (iframeDoc) {
const adElements = iframeDoc.querySelectorAll(
'[data-ad-client], [data-ad-slot], .adsbygoogle, a[href*="googleads"], a[href*="doubleclick"]'
);
if (adElements.length > 0) {
console.log(`iframe 내부에서 ${adElements.length}개 광고 요소 발견`);
iframe.setAttribute('data-is-ad', 'true');
}
adElements.forEach(element => {
element.setAttribute('data-is-ad', 'true');
});
}
} catch (e) {
// Same-Origin Policy 위반 오류는 예상된 것이므로 조용히 무시
// console.log('iframe 접근 제한 (정상):', iframe.src);
// src를 기반으로 광고 iframe인지 판단
if (this.isAdUrl(iframe.src)) {
iframe.setAttribute('data-is-ad', 'true');
}
}
});
}
getStats() {
return {
totalClicks: this.clickCount,
blockedClicks: this.blockedCount,
allowedClicks: this.allowedCount,
maxAllowedAdClicks: this.maxBlockedClicks
};
}
// 디버그 정보 출력
printDebugInfo() {
const adElements = document.querySelectorAll('[data-is-ad="true"]');
console.log(`감지된 총 광고 요소: ${adElements.length}개`);
console.log('광고 차단 통계:', this.getStats());
}
}
// localStorage에 홈페이지 URL 설정 (테스트용, 실제로는 설정 페이지에서 설정)
if (!localStorage.getItem('homepageUrl')) {
localStorage.setItem('homepageUrl', 'https://www.google.com');
console.log('기본 홈페이지 URL이 설정되었습니다: https://www.google.com');
}
// 트래픽 차단기 인스턴스 생성
const trafficBlocker = new TrafficBlocker();
// 디버그 정보 출력 (5초 간격)
setInterval(() => {
trafficBlocker.printDebugInfo();
}, 5000);
// 초기 광고 감지 로그
console.log('트래픽 차단기가 초기화되었습니다. 광고 감지 중...');