(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();
})();
})();