Hack Wayground V2
// ==UserScript==
// @name Hack Wayground
// @author Trần Bảo Ngọc
// @description Hack Wayground V2
// @namespace http://tampermonkey.net/
// @match https://wayground.com/*
// @grant GM_xmlhttpRequest
// @grant unsafeWindow
// @grant GM_addStyle
// @run-at document-end
// @icon https://blackarch.org/images/logo/ba-logo.png
// @version 4.0
// ==/UserScript==
(function() {
'use strict';
GM_addStyle(`
#solver-panel {
position: fixed; bottom: 20px; left: 20px; z-index: 999999;
padding: 12px; background-color: rgba(26, 27, 30, 0.85);
backdrop-filter: blur(10px); border-radius: 16px;
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.4);
min-width: 260px; max-width: 320px;
border: 1px solid rgba(255, 255, 255, 0.1);
}
#solver-status {
color: white; font-size: 15px; font-weight: 600; margin-bottom: 10px;
text-align: left; word-wrap: break-word; white-space: normal;
}
#key-container, #pin-container { display: flex; gap: 8px; margin-bottom: 8px; }
#key-input, #pin-input {
flex-grow: 1; border: 1px solid rgba(255, 255, 255, 0.2);
background-color: rgba(0, 0, 0, 0.3); color: white; border-radius: 8px;
padding: 8px 12px; font-size: 14px; outline: none; text-align: center;
}
#key-input:focus, #pin-input:focus { border-color: #a78bfa; }
#save-key-btn, #load-btn, #change-key-btn {
background: linear-gradient(135deg, #a78bfa 0%, #8b5cf6 100%);
border: none; border-radius: 8px; color: white; font-weight: 600;
padding: 0 20px; cursor: pointer; transition: transform 0.2s ease;
}
#change-key-btn {
background: linear-gradient(135deg, #fbbf24 0%, #f59e0b 100%);
padding: 6px 12px; font-size: 12px; width: 100%;
}
#save-key-btn:hover, #load-btn:hover, #change-key-btn:hover { transform: scale(1.05); }
#save-key-btn:disabled, #load-btn:disabled { cursor: not-allowed; background: #555; }
`);
const STORAGE_KEY = 'wayground_api_key';
const cachedAnswers = new Map();
let lastProcessedQuestionId = '';
let currentApiKey = sessionStorage.getItem(STORAGE_KEY) || localStorage.getItem(STORAGE_KEY) || '';
const normalizeText = (text) => {
if (!text) return '';
return text.replace(/<[^>]*>/g, '').replace(/ /g, ' ')
.replace(/[^\w\d\s\+\-\*\/\=\(\)\.\,\^%]/g, '').replace(/\s+/g, '').toLowerCase();
};
const cleanTextForDisplay = (text) => text?.replace(/<p>|<\/p>/g, '').trim().replace(/\s+/g, ' ') || '';
function findGamePin() {
const pinRegex = /\b(\d{4})\s(\d{4})\b/;
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
let node;
while (node = walker.nextNode()) {
const match = node.nodeValue.trim().match(pinRegex);
if (match && node.parentElement?.offsetParent !== null) return match[0].replace(/\s/g, '');
}
return null;
}
async function authenticateWithKey(key, statusDisplay) {
statusDisplay.textContent = `🔑 Đang xác thực...`;
return new Promise((resolve) => {
GM_xmlhttpRequest({
method: "GET", url: `https://api.cheatnetwork.eu/auth/redirect?backup=${key}`,
headers: { "Host": "api.cheatnetwork.eu" },
anonymous: false,
onload: (res) => {
if (res.status >= 200 && res.status < 300) {
sessionStorage.setItem(STORAGE_KEY, key);
localStorage.setItem(STORAGE_KEY, key);
currentApiKey = key;
resolve(true);
} else resolve(false);
},
onerror: () => resolve(false)
});
});
}
async function fetchAndCacheAnswers(pin, statusDisplay, isRetry = false) {
if (!isRetry) statusDisplay.textContent = `🌀 Đang tải đáp án...`;
const useAnonymous = !isRetry;
return new Promise((resolve) => {
GM_xmlhttpRequest({
method: "GET",
url: `https://api.cheatnetwork.eu/quizizz/${pin}/answers`,
headers: { "Host": "api.cheatnetwork.eu" },
anonymous: useAnonymous,
onload: async (res) => {
try {
let data;
try { data = JSON.parse(res.responseText); } catch(e) { throw new Error("API Error"); }
if (data && data.success === false && data.message && data.message.includes("usage limit")) {
statusDisplay.textContent = "⚠️ Bạn đã hết token vui lòng đổi key";
statusDisplay.style.color = '#ff5555';
resolve(false);
return;
}
if (data && data.success === false && data.action === 'login') {
if (!isRetry) {
console.log("🔒 Server yêu cầu đăng nhập...");
const authSuccess = await authenticateWithKey(currentApiKey, statusDisplay);
if (authSuccess) {
const retryResult = await fetchAndCacheAnswers(pin, statusDisplay, true);
resolve(retryResult);
return;
} else {
statusDisplay.textContent = "❌ Key lỗi / Hết hạn";
resolve(false);
return;
}
} else {
statusDisplay.textContent = "❌ Truy cập bị từ chối";
resolve(false);
return;
}
}
if (!data?.answers) throw new Error("No Data");
data.answers.forEach(item => {
if (!item.id) return;
if (item.type === 'MSQ' && Array.isArray(item.answer)) {
const multi = item.answer.map(idx => item.options?.[idx]?.text).filter(Boolean);
if (multi.length) cachedAnswers.set(item.id, multi);
} else if (Array.isArray(item.answer) && item.answer.length > 0) {
const opt = item.options?.[item.answer[0]];
if (opt?.text) cachedAnswers.set(item.id, opt.text);
}
});
resolve(cachedAnswers.size > 0);
} catch (e) {
if(!isRetry) statusDisplay.textContent = "❌ Lỗi dữ liệu";
resolve(false);
}
},
onerror: () => { statusDisplay.textContent = "❌ Lỗi mạng"; resolve(false); }
});
});
}
function getCurrentQuestionData() {
const questionContainer = document.querySelector('[data-quesid]');
if (!questionContainer) return null;
const questionId = questionContainer.dataset.quesid;
const options = Array.from(document.querySelectorAll('.option.is-selectable')).map(el => ({
rawText: el.querySelector('.option-text-inner, .text-container')?.innerText || "",
element: el,
}));
if (options.length > 0) return { questionId, type: 'CHOICE', options };
const inputEl = document.querySelector('input.question-input, textarea.question-input, input[type="text"], textarea');
if (inputEl) return { questionId, type: 'BLANK', inputEl };
return null;
}
function solveQuestion(answer, questionData) {
const safeSubmit = (attempts = 0) => {
if (attempts > 20) return;
const buttons = Array.from(document.querySelectorAll('button'));
const submitBtn = buttons.find(btn => {
const text = btn.innerText.trim().toLowerCase();
return ['submit', 'nộp', 'nộp bài', 'trả lời'].includes(text) ||
btn.classList.contains('submit-btn') || btn.querySelector('i.fa-arrow-right');
});
if (submitBtn && !submitBtn.disabled && submitBtn.offsetParent !== null) {
submitBtn.click();
} else {
setTimeout(() => safeSubmit(attempts + 1), 500);
}
};
if (questionData.type === 'CHOICE') {
const answersArray = Array.isArray(answer) ? answer : [answer];
const normalizedTargets = answersArray.map(a => normalizeText(a));
let scriptDidClick = false;
questionData.options.forEach(opt => {
const optTextNorm = normalizeText(opt.rawText);
if (normalizedTargets.includes(optTextNorm)) {
opt.element.style.border = '4px solid #00FF00';
opt.element.click();
scriptDidClick = true;
}
});
if (scriptDidClick) setTimeout(() => safeSubmit(), 500);
}
else if (questionData.type === 'BLANK' && questionData.inputEl) {
const textAnswer = Array.isArray(answer) ? answer[0] : answer;
questionData.inputEl.value = textAnswer;
questionData.inputEl.dispatchEvent(new Event('input', { bubbles: true }));
questionData.inputEl.dispatchEvent(new Event('change', { bubbles: true }));
setTimeout(() => safeSubmit(), 800);
}
}
async function mainSolver(statusDisplay) {
if (cachedAnswers.size === 0) return;
const questionData = getCurrentQuestionData();
if (!questionData?.questionId) return;
const answer = cachedAnswers.get(questionData.questionId);
if (answer) {
const displayAnswerText = Array.isArray(answer) ? answer.map(cleanTextForDisplay).join('<br>') : cleanTextForDisplay(answer);
statusDisplay.innerHTML = `💡 Đáp án:<div style="margin-top: 5px; color: #50fa7b;">${displayAnswerText}</div>`;
setTimeout(() => solveQuestion(answer, questionData), 400);
} else {
statusDisplay.textContent = "❓ Chưa có đáp án";
statusDisplay.style.color = '#ff5555';
}
}
function setupUI() {
if (document.getElementById('solver-panel')) return;
document.body.insertAdjacentHTML('beforeend', `
<div id="solver-panel">
<div id="solver-status">🔑 Nhập API Key</div>
<div id="key-container">
<input type="text" id="key-input" placeholder="Key..." value="${currentApiKey}">
<button id="save-key-btn">Lưu</button>
</div>
</div>
`);
const panel = document.getElementById('solver-panel');
const status = document.getElementById('solver-status');
const keyInput = document.getElementById('key-input');
const saveBtn = document.getElementById('save-key-btn');
const showPinUI = () => {
document.getElementById('key-container').style.display = 'none';
const pinDiv = document.createElement('div');
pinDiv.id = 'pin-container';
pinDiv.innerHTML = `<input type="text" id="pin-input" placeholder="PIN..."><button id="load-btn">Tải</button>`;
const changeKey = document.createElement('button');
changeKey.id = 'change-key-btn';
changeKey.textContent = 'Đổi Key Khác';
panel.appendChild(pinDiv); panel.appendChild(changeKey);
const loadBtn = document.getElementById('load-btn');
const pinIn = document.getElementById('pin-input');
const runLoad = async () => {
const pin = pinIn.value.trim();
if(!pin) return;
loadBtn.disabled = true;
if(await fetchAndCacheAnswers(pin, status)) {
pinDiv.style.display = 'none'; changeKey.style.display = 'none';
status.textContent = "🚀 Sẵn sàng!"; status.style.color = 'white';
const observer = new MutationObserver(() => {
const qContainer = document.querySelector('[data-quesid]');
const qId = qContainer?.dataset.quesid;
if (qId && qId !== lastProcessedQuestionId) {
lastProcessedQuestionId = qId;
setTimeout(() => mainSolver(status), 500);
}
});
observer.observe(document.body, { childList: true, subtree: true });
setTimeout(() => mainSolver(status), 500);
} else { loadBtn.disabled = false; }
};
loadBtn.onclick = runLoad;
pinIn.onkeydown = (e) => e.key === 'Enter' && runLoad();
changeKey.onclick = () => { sessionStorage.removeItem(STORAGE_KEY); localStorage.removeItem(STORAGE_KEY); location.reload(); };
const finder = setInterval(() => {
const p = findGamePin();
if(p) { clearInterval(finder); pinIn.value = p; status.textContent = "✅ Thấy PIN!"; status.style.color = '#50fa7b'; setTimeout(runLoad, 500); }
}, 1000);
setTimeout(() => clearInterval(finder), 20000);
};
const onSaveKey = async () => {
const k = keyInput.value.trim();
if(!k) return;
saveBtn.disabled = true;
if(await authenticateWithKey(k, status)) { status.textContent = "✅ Key OK"; status.style.color = '#50fa7b'; setTimeout(showPinUI, 800); }
else { status.textContent = "❌ Key sai"; status.style.color = '#ff5555'; saveBtn.disabled = false; }
};
saveBtn.onclick = onSaveKey;
keyInput.onkeydown = (e) => e.key === 'Enter' && onSaveKey();
if (currentApiKey) showPinUI();
}
window.addEventListener('load', () => setTimeout(setupUI, 1000));
})();