최초 프로젝트 업로드
This commit is contained in:
324
ChromeAutoScript.js
Normal file
324
ChromeAutoScript.js
Normal file
@@ -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; // 요소 자체를 못 찾음
|
||||||
|
}
|
||||||
|
|
||||||
|
})();
|
||||||
Reference in New Issue
Block a user