commit c3817daac19f8fb1bbd30336e22fd316412c19ee Author: tae2564 Date: Thu Nov 27 18:40:47 2025 +0900 최초 프로젝트 업로드 diff --git a/ChromeAutoScript.js b/ChromeAutoScript.js new file mode 100644 index 0000000..5508cde --- /dev/null +++ b/ChromeAutoScript.js @@ -0,0 +1,324 @@ +// ==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; // 요소 자체를 못 찾음 + } + +})(); \ No newline at end of file