Hack Wayground

Hack Wayground V2

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

You will need to install an extension such as Tampermonkey to install this script.

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==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(/&nbsp;/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));
})();