// ==UserScript== // @name test // @namespace http://tampermonkey.net/ // @version 2025-11-26 // @description try to take over the world! // @author You // @match https://www.naver.com/ // @icon https://www.google.com/s2/favicons?sz=64&domain=google.com // @grant none // ==/UserScript== (function() { 'use strict'; // Your code here... console.log(">>> 봇 가동 시작: 화면을 감시합니다..."); // 클릭 추적 객체 (개선 버전용) const clickTracker = {}; // ============================================================ // [설정 구역] 여기만 상황에 맞춰 수정하세요! (F12 개발자 도구 활용) // ============================================================ const TARGET_TIME = ""; // (옵션) 예: "2023-10-25 10:00:00", 비워두면 즉시 실행 const USE_ENHANCED_CLICK = false; // true: 개선 버전 (클릭 제한 + 마우스 이벤트), false: 기본 버전 const MAX_CLICK_ATTEMPTS = 3; // 개선 버전 사용 시 같은 버튼을 최대 못 번 클릭할지 설정 const COOLDOWN_MS = 2000; // 동일 버튼 재시도 간 최소 대기시간 const STOP_ON_LIMIT = true; // true면 제한 도달 시 루프 종료 const STOP_ON_LIMIT_ANY = true; // true면 어떤 키든 제한 도달 즉시 종료 const SELECTORS = { // 1. 예약/예매하기 버튼 (가장 중요!) reserveBtn: ".btn_reservation, #btn_booking, .btn_red", // 2. 빈자리 (좌석) emptySeat: ".site_list .available, .seat_box .on", // 3. 팝업 닫기 버튼 (여러 개일 수 있음) popupClose: ".popup_close, .btn_layer_close, .close_btn, button[class*='close']", // 4. 다음 단계 / 결제하기 버튼 nextBtn: ".btn_next, #btn_pay", // 5. 약관 동의 체크박스 agreeCheck: "#check_all, .agree_all", // 6. 자동 클릭할 버튼들 (순서대로 시도) autoClickButtons: [ { selector: "a[role='tab']", text: "스포츠" }, // 1단계: 네이버 스포츠 탭 { selector: "", text: "" }, // 2단계: 예매하기 등 { selector: "", text: "" } // 3단계: 구매 등 ] // selector 우선 시도 → 실패 시 text로 백업 검색 // 둘 다 비어있으면 해당 단계 무시 }; // ============================================================ let loopStopped = false; function stopLoop(reason) { if (loopStopped) return; loopStopped = true; clearInterval(loop); console.log(`[봇 종료] ${reason}`); } // [엔진] 0.2초마다 화면을 스캔하는 루프 const loop = setInterval(() => { // 0. 시간 체크 (설정된 경우에만) if (TARGET_TIME !== "") { const now = new Date().getTime(); const target = new Date(TARGET_TIME).getTime(); if (now < target) { return; } } // [수정됨] 1. 화면에 있는 '모든' 팝업 닫기 버튼 찾아서 누르기 const popups = document.querySelectorAll(SELECTORS.popupClose); if (popups.length > 0) { popups.forEach((popup) => { // 화면에 눈으로 보이는(offsetParent가 있는) 팝업만 클릭 if (popup.offsetParent !== null) { popup.click(); console.log("방해되는 팝업 제거함"); } }); } // 1-1. 자동 클릭 (selector 우선 → text 백업, 순서대로 시도) for (let config of SELECTORS.autoClickButtons) { // 둘 다 비어있으면 건너뜀 if ((!config.selector || config.selector.trim() === "") && (!config.text || config.text.trim() === "")) { continue; } let clicked = false; // (1) selector가 있으면 우선 시도 if (config.selector && config.selector.trim() !== "") { clicked = clickByText(config.text || "", config.selector); } // (2) selector로 못 찾았고 text가 있으면 백업 시도 if (!clicked && config.text && config.text.trim() !== "") { clicked = clickByText(config.text); } // 하나라도 클릭했으면 종료 (다음 버튼은 시도 안 함) if (clicked) break; } // 2. 빈자리가 보이면 최우선으로 잡기! (좌석 선택 -> 예약 버튼 순서가 안전함) const seats = document.querySelectorAll(SELECTORS.emptySeat); let seatClicked = false; if (seats && seats.length > 0) { console.log(`빈자리 ${seats.length}개 발견! 첫 번째 클릭`); if (USE_ENHANCED_CLICK) { seatClicked = enhancedClick(seats[0], 'emptySeat'); } else { seats[0].click(); seatClicked = true; } } // 셀렉터로 못 찾았거나 실패했으면 텍스트로 백업 탐색 if (!seatClicked) { if (!clickByText('좌석')) { clickByText('선택'); } } // 3. '예약하기' 버튼이 보이면 클릭 const btn = document.querySelector(SELECTORS.reserveBtn); let reserveClicked = false; if (btn && !btn.disabled) { if (USE_ENHANCED_CLICK) { reserveClicked = enhancedClick(btn, 'reserveBtn'); } else { btn.click(); console.log("메인 예약 버튼 클릭!"); reserveClicked = true; } } // 셀렉터로 못 찾았거나 5회 실패했으면 '글자'로 비상 탐색 if (!reserveClicked) { if (!clickByText('예약하기')) { clickByText('예매하기'); } } // 4. 약관 동의 & 다음 버튼 const agree = document.querySelector(SELECTORS.agreeCheck); let agreeClicked = false; if (agree && !agree.checked) { if (USE_ENHANCED_CLICK) { agreeClicked = enhancedClick(agree, 'agreeCheck'); } else { agree.click(); console.log("약관 동의 체크"); agreeClicked = true; } } // 셀렉터로 못 찾았거나 실패했으면 텍스트로 백업 탐색 if (!agreeClicked && agree && !agree.checked) { if (!clickByText('동의')) { clickByText('전체 동의'); } } const next = document.querySelector(SELECTORS.nextBtn); let nextClicked = false; if (next) { if (USE_ENHANCED_CLICK) { nextClicked = enhancedClick(next, 'nextBtn'); } else { next.click(); console.log("다음 단계로 이동"); nextClicked = true; } } // 셀렉터로 못 찾았거나 실패했으면 텍스트로 백업 탐색 if (!nextClicked) { if (!clickByText('다음')) { if (!clickByText('결제')) { clickByText('결제하기'); } } } }, 200); // 0.2초(200ms) 간격으로 무한 반복 // [보조 함수] 개선된 클릭 (마우스 이벤트 + 클릭 횟수 제한) function enhancedClick(elem, key) { // 클릭 횟수 확인 if (!clickTracker[key]) { clickTracker[key] = { count: 0, lastUrl: window.location.href }; } const tracker = clickTracker[key]; // 같은 URL에서 MAX_CLICK_ATTEMPTS회 이상 클릭했으면 포기 if (tracker.count >= MAX_CLICK_ATTEMPTS && tracker.lastUrl === window.location.href) { console.log(`[개선 클릭] '${key}' ${MAX_CLICK_ATTEMPTS}회 시도 후 포기 (페이지 응답 없음)`); if (STOP_ON_LIMIT && STOP_ON_LIMIT_ANY) { stopLoop(`'${key}' ${MAX_CLICK_ATTEMPTS}회 제한 도달`); } return false; } // URL이 바뀌었으면 카운터 리셋 if (tracker.lastUrl !== window.location.href) { tracker.count = 0; tracker.lastUrl = window.location.href; } // 가시 영역으로 스크롤 및 포커스 try { elem.scrollIntoView({ block: 'center', inline: 'center', behavior: 'instant' }); } catch (_) {} try { elem.focus({ preventScroll: true }); } catch (_) {} // 실제 사용자 유사 이벤트 시퀀스 const events = ['pointerdown','mousedown','pointerup','mouseup','click']; for (const type of events) { const ev = new MouseEvent(type, { bubbles: true, cancelable: true, view: window }); elem.dispatchEvent(ev); } // 안전망으로 기본 클릭도 호출 try { elem.click(); } catch (_) {} tracker.count++; console.log(`[개선 클릭] '${key}' 클릭 (${tracker.count}/${MAX_CLICK_ATTEMPTS}회)`); return true; } // [보조 함수] 텍스트/셀렉터로 버튼 찾기 (강화 버전) function clickByText(text, selector = 'a, button, [role="button"], [role="tab"], input[type="button"], input[type="submit"]') { // 먼저 클릭 제한 확인 (요소 검색 전) const key = `text:${text}`; if (!clickTracker[key]) { clickTracker[key] = { count: 0, lastUrl: window.location.href, lastClickTime: 0 }; } const tracker = clickTracker[key]; const now = Date.now(); // URL이 바뀌었으면 카운터 리셋 if (tracker.lastUrl !== window.location.href) { tracker.count = 0; tracker.lastUrl = window.location.href; tracker.lastClickTime = 0; } // 마지막 클릭 후 2초 이내면 재시도 안 함 (SPA 페이지 전환 대기) if (now - tracker.lastClickTime < COOLDOWN_MS) { return false; } // 같은 URL에서 MAX_CLICK_ATTEMPTS회 이상 클릭했으면 아예 요소 검색도 안 함 if (tracker.count >= MAX_CLICK_ATTEMPTS && tracker.lastUrl === window.location.href) { // 최초 1회만 로그 출력 (무한 반복 방지) if (tracker.count === MAX_CLICK_ATTEMPTS) { console.log(`텍스트('${text}') ${MAX_CLICK_ATTEMPTS}회 시도 후 포기 (페이지 응답 없음)`); tracker.count++; // 카운터를 증가시켜 다시 로그가 안 뜨게 함 if (STOP_ON_LIMIT && STOP_ON_LIMIT_ANY) { stopLoop(`'text:${text}' ${MAX_CLICK_ATTEMPTS}회 제한 도달`); } } return false; } const elements = document.querySelectorAll(selector); for (let elem of elements) { if (elem.textContent.includes(text) && elem.offsetParent !== null) { // aria-selected 속성이 있으면 "false"인 것만 클릭 (이미 선택된 탭은 건너뜀) const ariaSelected = elem.getAttribute('aria-selected'); if (ariaSelected !== null && ariaSelected !== 'false') { console.log(`텍스트('${text}') 이미 선택됨 (aria-selected="${ariaSelected}") - 건너뜀`); continue; // 다른 후보가 있을 수 있으므로 계속 탐색 } // 개선 버전 사용 여부에 따라 클릭 방식 선택 if (USE_ENHANCED_CLICK) { // 가시 영역으로 스크롤 및 포커스 try { elem.scrollIntoView({ block: 'center', inline: 'center', behavior: 'instant' }); } catch (_) {} try { elem.focus({ preventScroll: true }); } catch (_) {} // 사용자 유사 이벤트 시퀀스 const events = ['pointerdown','mousedown','pointerup','mouseup','click']; for (const type of events) { const ev = new MouseEvent(type, { bubbles: true, cancelable: true, view: window }); elem.dispatchEvent(ev); } try { elem.click(); } catch (_) {} tracker.count++; tracker.lastClickTime = now; console.log(`[개선 클릭] 텍스트('${text}') 클릭 (${tracker.count}/${MAX_CLICK_ATTEMPTS}회)`); return true; } else { try { elem.scrollIntoView({ block: 'center', inline: 'center', behavior: 'instant' }); } catch (_) {} try { elem.focus({ preventScroll: true }); } catch (_) {} elem.click(); tracker.count++; tracker.lastClickTime = now; console.log(`텍스트('${text}')로 버튼 클릭 성공 (${tracker.count}/${MAX_CLICK_ATTEMPTS}회)`); return true; } } } return false; // 요소 자체를 못 찾음 } })();