|
2025-11-25 19:54
조회: 27,464
추천: 42
첨부파일
1.zip [4Kb] 7주년 카드 메모리 자동화 개선판우선 해당 글은 다른 분이 올려주신 코드에서 개선한 버전으로 아래 기능이 추가되었습니다. 화요일에 몰빵으로 하려니 20~21판을 해야하는데, 기존 스크립트는 게임 플레이 버튼을 계속 눌러줘야 해서 이 부분에 대한 개선을 진행했습니다. 해당 스크립트는 위 게시글을 기반으로 개선한 스크립트입니다. 개선 내용 1. 게임이 끝난 이후 자동으로 게임 재시작 (토큰 모두 사용할 때 까지) 2. 게임 중 ESC를 누르면 해당 판은 마무리 한 상태에서 다음 게임 진행하지 않음 3. 게임중 뜨는 팝업창 자동으로 닫음 해당 스크립트는 크롬 개발자 모드(F12)에서 "Cources -> Snippets"으로 진행을 권장드립니다. 자세한 사용 방법은 원글 참조해주세요. Snippets 을 못 찾는 분들을 위해 사진 첨부합니다. " >> "를 눌러서 Snippets 으로 변경해야 그 아래에 New Snippets 가 보입니다. ![]() 스크립트 사용에 대한 책임은 지지 않습니다. [아래 코드부터 복사하세요] [메모장도 추가적으로 첨부합니다] (function () { if (window.__lostArkCardHelperInstalled) return; window.__lostArkCardHelperInstalled = true; // ============================== // 1. 카드 정보 수집 코어 (XHR 훅) // ============================== (function installCardMemoCore() { if (window.__cardMemoInstalled) return; window.__cardMemoInstalled = true; window.__cardMemo = window.__cardMemo || {}; window.__cardDone = window.__cardDone || []; const API_PATH = "/Promotion/Card/GetCard251105"; (function hookXHR() { const OriginalXHR = window.XMLHttpRequest; function WrappedXHR() { const xhr = new OriginalXHR(); let method = null; let url = null; let body = null; const origOpen = xhr.open; xhr.open = function (m, u) { method = m; url = u; return origOpen.apply(xhr, arguments); }; const origSend = xhr.send; xhr.send = function (data) { body = data; xhr.addEventListener("load", function () { try { if (!url || !method) return; const fullUrl = new URL(url, location.href); if (!fullUrl.pathname.endsWith(API_PATH)) return; if (method.toUpperCase() !== "POST") return; // 요청 body에서 index 추출 let indexStr = null; try { if (typeof body === "string") { const m = body.match(/(?:^|&)index=([^&]+)/); if (m) indexStr = decodeURIComponent(m[1]); } else if (body instanceof FormData || body instanceof URLSearchParams) { indexStr = body.get("index"); } } catch (e) {} // 응답 파싱 try { const text = xhr.responseText; if (!text) return; const json = JSON.parse(text); if (!json) return; // 카드 이미지 메모 if (json.img != null) { const idx = indexStr != null ? Number(indexStr) : null; if (idx != null && !Number.isNaN(idx) && idx >= 0 && idx < 18) { const imgUrl = new URL( json.img, "https://cdn-lostark.game.onstove.com" ).href; window.__cardMemo[idx] = imgUrl; } } // 매칭된 카드 인덱스 저장 if (json.isMatch && Array.isArray(json.index)) { window.__cardDone = [ ...(window.__cardDone || []), ...json.index, ]; } // 게임 완전 종료 시 상태 초기화 if (json.complete) { window.__cardDone = []; window.__cardMemo = {}; } } catch (e) {} } catch (e) {} }); return origSend.apply(xhr, arguments); }; return xhr; } WrappedXHR.prototype = OriginalXHR.prototype; window.XMLHttpRequest = WrappedXHR; // 페이지 떠날 때 원복 if (!window.__xhrRestoreInstalled) { window.__xhrRestoreInstalled = true; window.addEventListener("beforeunload", function () { try { window.XMLHttpRequest = OriginalXHR; } catch (e) {} }); } })(); })(); // ============================== // 2. 자동 플레이어 + 팝업 자동 처리 // ============================== (function installAutoPlayer() { if (window.__lostArkAutoPlayerInstalled) return; window.__lostArkAutoPlayerInstalled = true; if (!window.__cardMemo) window.__cardMemo = {}; if (!window.__cardDone) window.__cardDone = []; window.__cardBusy = false; window.__cardAutoPlayRunning = false; window.__cardHasPlayedOnce = window.__cardHasPlayedOnce || false; // ESC: "이번 판 끝까지 하고 이후부터는 자동 동작 중단" 플래그 window.__laStopAfterThisGame = window.__laStopAfterThisGame || false; // 토큰 부족으로 인한 하드 스톱 플래그 window.__laTokenLackStop = window.__laTokenLackStop || false; // ESC 키 이벤트: 종료 예약 + 콘솔 메시지 if (!window.__laEscKeyListenerInstalled) { window.__laEscKeyListenerInstalled = true; window.addEventListener("keydown", function (e) { try { if (e.key === "Escape" || e.key === "Esc") { if (!window.__laStopAfterThisGame) { window.__laStopAfterThisGame = true; console.log("ESC가 입력되어 카드를 모두 맞춘 후 스크립트 종료 예정"); } else { console.log("이미 스크립트 종료 예정입니다."); } } } catch (err) {} }); } // -------------------------------- // 유틸 // -------------------------------- function sleep(ms) { return new Promise((r) => setTimeout(r, ms)); } // "게임 플레이" 버튼 0.5초 후 클릭 // ESC 이후에는 실제 클릭 시점에 막는다. function scheduleClickPlayButton() { setTimeout(function () { try { if (window.__laStopAfterThisGame || window.__laTokenLackStop) return; const btn = document.querySelector("#playBtn"); if (btn && !btn.disabled) { btn.click(); } } catch (e) {} }, 500); } // -------------------------------- // CLEAR 팝업 자동 "확인" + 이후 게임 플레이 버튼 클릭 // (CLEAR는 항상 닫고, ESC/토큰부족 이후에는 게임 플레이 버튼만 생략) // 팝업이 감지된 뒤 0.5초 후에 확인 버튼 클릭 // -------------------------------- function clickClearPopupIfExists() { try { const modals = document.querySelectorAll(".lui-modal__window"); if (!modals || modals.length === 0) return false; for (const modal of modals) { const titleEl = modal.querySelector(".lui-modal__title"); if (!titleEl) continue; const titleText = (titleEl.textContent || "").trim(); if (titleText !== "CLEAR") continue; // CLEAR 팝업만 대상 // 이미 처리한 CLEAR 팝업이면 스킵 if (modal.dataset.laClearHandled === "1") continue; modal.dataset.laClearHandled = "1"; const confirmBtn = modal.querySelector(".lui-modal__confirm"); if (confirmBtn && !confirmBtn.disabled) { // 0.5초 후에 CLEAR 확인 클릭 setTimeout(function () { try { if (!document.body.contains(modal)) return; const btn = modal.querySelector(".lui-modal__confirm"); if (btn && !btn.disabled) { btn.click(); // 종료예약/토큰부족이 아니면 다음 게임 자동 시작 if (!window.__laStopAfterThisGame && !window.__laTokenLackStop) { scheduleClickPlayButton(); } } } catch (e) {} }, 500); return true; } } } catch (e) {} return false; } // -------------------------------- // 토큰 사용 안내 팝업 자동 "확인" (0.5초 후) // ESC/토큰부족 이후에는 더 이상 확인 누르지 않음 // -------------------------------- function watchTokenPopupAndConfirm() { try { if (window.__laStopAfterThisGame || window.__laTokenLackStop) return; const modals = document.querySelectorAll(".lui-modal__window"); if (!modals || modals.length === 0) return; for (const modal of modals) { // 이미 처리 예약된 모달이면 스킵 if (modal.dataset.laTokenConfirmScheduled === "1") continue; const textEl = modal.querySelector(".popup_text"); if (!textEl) continue; const text = (textEl.textContent || "").replace(/s+/g, " ").trim(); // 토큰 사용 안내 팝업 식별 (핵심 문구 포함 여부 검사) if (text.includes("토큰을 사용하여 카드 메모리 게임을")) { modal.dataset.laTokenConfirmScheduled = "1"; setTimeout(function () { try { if (window.__laStopAfterThisGame || window.__laTokenLackStop) return; // 모달이 아직 존재하는지 다시 확인 if (!document.body.contains(modal)) return; const confirmBtn = modal.querySelector(".lui-modal__confirm"); if (confirmBtn && !confirmBtn.disabled) { confirmBtn.click(); } } catch (e) {} }, 500); } } } catch (e) {} } // -------------------------------- // 아이템 획득 팝업 자동 "확인" (0.5초 후) // -------------------------------- function watchRewardPopupAndConfirm() { try { if (window.__laTokenLackStop) return; // 하드 스톱 후에는 아무 것도 안 함 const modals = document.querySelectorAll(".lui-modal__window"); if (!modals || modals.length === 0) return; for (const modal of modals) { const titleEl = modal.querySelector(".lui-modal__title"); if (!titleEl) continue; const titleText = (titleEl.textContent || "").trim(); if (titleText !== "아이템 획득") continue; // 아이템 획득 팝업만 대상 // 이미 처리 예약된 모달이면 스킵 if (modal.dataset.laRewardConfirmScheduled === "1") continue; modal.dataset.laRewardConfirmScheduled = "1"; setTimeout(function () { try { if (window.__laTokenLackStop) return; if (!document.body.contains(modal)) return; const confirmBtn = modal.querySelector(".lui-modal__confirm"); if (confirmBtn && !confirmBtn.disabled) { confirmBtn.click(); } } catch (e) {} }, 500); } } catch (e) {} } // -------------------------------- // 토큰 부족 팝업 감지 → 즉시 전체 스크립트 중단 // -------------------------------- function watchTokenLackAndStop() { try { if (window.__laTokenLackStop) return; const modals = document.querySelectorAll(".lui-modal__window"); if (!modals || modals.length === 0) return; for (const modal of modals) { const textEl = modal.querySelector(".popup_text"); if (!textEl) continue; const text = (textEl.textContent || "").replace(/s+/g, " ").trim(); if (text.includes("토큰이 부족합니다.")) { // 하드 스톱 플래그 설정 window.__laTokenLackStop = true; window.__laStopAfterThisGame = true; window.__cardAutoPlayRunning = false; console.log("토큰이 부족하여 스크립트가 중단됩니다."); return; } } } catch (e) {} } // -------------------------------- // 팝업 워처: CLEAR + 토큰 팝업 + 아이템 획득 팝업 + 토큰 부족 팝업 검사 // -------------------------------- if (!window.__clearPopupWatcherInstalled) { window.__clearPopupWatcherInstalled = true; setInterval(function () { // 1) 먼저 토큰 부족 팝업 감지 watchTokenLackAndStop(); // 토큰 부족이 감지되면 이후 모든 자동 동작 중단 if (window.__laTokenLackStop) return; // 2) 나머지 팝업 처리 clickClearPopupIfExists(); // CLEAR는 항상 닫음 (0.5초 딜레이 포함) watchTokenPopupAndConfirm(); // 토큰 팝업은 종료예약/토큰부족 후에는 더 이상 확인 안 눌림 watchRewardPopupAndConfirm(); // 아이템 획득 팝업도 0.5초 후 자동 확인 }, 500); } // -------------------------------- // 게임 보드 / 카드 관련 유틸 // -------------------------------- function getCardElement(index) { return document.querySelector( `section#grid button.card[data-idx="${index}"]` ); } function isBoardReady() { return !!document.querySelector('section#grid button.card[data-idx="0"]'); } function isStateEmpty() { const memo = window.__cardMemo || {}; const done = window.__cardDone || []; return Object.keys(memo).length === 0 && done.length === 0; } function isClickable(el) { if (!el) return false; if (el.disabled) return false; const rect = el.getBoundingClientRect(); if (rect.width === 0 || rect.height === 0) return false; return true; } async function waitForBoardReadyFull(timeout = 5000) { const start = Date.now(); while (Date.now() - start < timeout) { const all = document.querySelectorAll( "section#grid button.card[data-idx]" ); if (all && all.length >= 18) return true; await sleep(100); } return false; } async function ensureBoardReady() { if (!(await waitForBoardReadyFull(5000))) return false; return true; } async function clickWhenReady(index, timeout = 4000) { const start = Date.now(); while (Date.now() - start < timeout) { const el = getCardElement(index); if (isClickable(el)) { el.click(); return true; } await sleep(50); } return false; } // -------------------------------- // 카드 선택 전략 관련 함수들 // -------------------------------- function findMatchedPairFromMemory() { const memo = window.__cardMemo || {}; const done = new Set(window.__cardDone || []); const map = new Map(); for (let i = 0; i < 18; i++) { const img = memo[i]; if (!img) continue; if (done.has(i)) continue; if (!map.has(img)) map.set(img, []); map.get(img).push(i); } for (const [img, idxs] of map.entries()) { if (idxs.length >= 2) return idxs.slice(0, 2); } return null; } function findOneUnknownCard(exceptIdx = null) { const memo = window.__cardMemo || {}; const done = new Set(window.__cardDone || []); for (let i = 0; i < 18; i++) { if (i === exceptIdx) continue; if (done.has(i)) continue; if (!memo[i]) return i; } return null; } function findAnyUnmatchedCard(exceptIdx = null) { const done = new Set(window.__cardDone || []); for (let i = 0; i < 18; i++) { if (i === exceptIdx) continue; if (done.has(i)) continue; return i; } return null; } function findSameImageCard(imgUrl, exceptIdx) { const memo = window.__cardMemo || {}; const done = new Set(window.__cardDone || []); for (let i = 0; i < 18; i++) { if (i === exceptIdx) continue; if (done.has(i)) continue; if (memo[i] === imgUrl) return i; } return null; } function isGameComplete() { const done = new Set(window.__cardDone || []); return done.size >= 18; } // -------------------------------- // 한 스텝 실행 // -------------------------------- async function playOneStep() { if (window.__cardBusy) return true; if (window.__laTokenLackStop) return false; if (!(await ensureBoardReady())) return false; if (isGameComplete()) return false; window.__cardBusy = true; try { // 1) 기억된 짝이 있으면 그 짝부터 처리 let pair = findMatchedPairFromMemory(); if (pair) { const ok1 = await clickWhenReady(pair[0]); if (!ok1) return false; await sleep(600); const ok2 = await clickWhenReady(pair[1]); if (!ok2) return false; await sleep(1200); return true; } // 2) 새로운 카드 한 장 뒤집기 let firstIndex = findOneUnknownCard(); if (firstIndex == null) { firstIndex = findAnyUnmatchedCard(); if (firstIndex == null) return false; } const okFirst = await clickWhenReady(firstIndex); if (!okFirst) return false; await sleep(600); const imgFirst = (window.__cardMemo || {})[firstIndex]; // 3) 두 번째 카드 선택 let secondIndex = null; if (imgFirst) { secondIndex = findSameImageCard(imgFirst, firstIndex); } if (secondIndex == null) { secondIndex = findOneUnknownCard(firstIndex); if (secondIndex == null) { secondIndex = findAnyUnmatchedCard(firstIndex); } } if (secondIndex != null) { const okSecond = await clickWhenReady(secondIndex); if (!okSecond) return false; await sleep(1200); } return true; } finally { window.__cardBusy = false; } } // -------------------------------- // 전체 자동 플레이 루프 // -------------------------------- async function autoPlayAll() { if (window.__cardAutoPlayRunning) return; if (window.__laTokenLackStop) return; window.__cardAutoPlayRunning = true; window.__cardHasPlayedOnce = true; while (window.__cardAutoPlayRunning) { if (window.__laTokenLackStop) break; const moved = await playOneStep(); if (!moved || isGameComplete()) break; } window.__cardAutoPlayRunning = false; } function stopAutoPlay() { window.__cardAutoPlayRunning = false; } window.autoPlayAll = autoPlayAll; window.stopAutoPlay = stopAutoPlay; // -------------------------------- // 새 게임 자동 감시 // 종료예약/토큰부족 후에는 새 판 자동 시작 안 함 // -------------------------------- if (!window.__watchInstalled) { window.__watchInstalled = true; (async function watchForNewGame() { let pendingToken = null; while (true) { if ( window.__cardHasPlayedOnce && !window.__cardAutoPlayRunning && !window.__laStopAfterThisGame && !window.__laTokenLackStop && isBoardReady() && isStateEmpty() && !pendingToken ) { const token = {}; pendingToken = token; await sleep(300); if ( pendingToken === token && window.__cardHasPlayedOnce && !window.__cardAutoPlayRunning && !window.__laStopAfterThisGame && !window.__laTokenLackStop && isBoardReady() && isStateEmpty() ) { autoPlayAll(); } if (pendingToken === token) pendingToken = null; } await sleep(1000); } })(); } // 로딩 즉시 1회 자동 플레이 시작 autoPlayAll(); })(); })();
- Well begun is half done -
|





1.zip [4Kb]
