Hack Wayground

Hack Wayground/quizizz

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

// ==UserScript==
// @name         Hack Wayground
// @author       Trần Bảo Ngọc
// @description  Hack Wayground/quizizz
// @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';
    const BL = ['playerExited','playerResumed','infractionType','extensionDetected','windowResizeDetected','rightClickDetected','pasteDetected'],
          blocked = d => typeof d === 'string' && BL.some(k => d.includes(k)),
          _fetch = window.fetch, _xhrSend = XMLHttpRequest.prototype.send, _parse = JSON.parse;

    window.fetch = async function(...a) {
        return a[1]?.body && blocked(a[1].body) ? new Response('{"success":true}', {status:200}) : _fetch.apply(this, a);
    };
    XMLHttpRequest.prototype.send = function(b) {
        if (blocked(b)) {
            Object.defineProperties(this, {readyState:{value:4,configurable:1},status:{value:200,configurable:1}});
            return this.onreadystatechange?.();
        }
        _xhrSend.apply(this, arguments);
    };
    JSON.parse = function(...a) {
        const r = _parse.apply(this, a);
        if (r?.type === 'RN_APP_STATE_CHANGE' && r.value === 'background') r.value = 'foreground';
        return r;
    };

    const stop = e => e.stopImmediatePropagation();
    'visibilitychange blur mouseleave pagehide resize contextmenu copy paste fullscreenchange webkitfullscreenchange'
        .split(' ').forEach(e => (window.addEventListener(e, stop, !0), document.addEventListener(e, stop, !0)));

    const def = (o, p, v) => { try { Object.getOwnPropertyDescriptor(o, p)?.configurable !== !1 && Object.defineProperty(o, p, {get:()=>v, configurable:!0}) } catch{} },
          el = () => document.documentElement;
    for (const o of [Document.prototype, document]) {
        def(o, 'visibilityState', 'visible'); def(o, 'hidden', !1);
        def(o, 'fullscreenElement', el); def(o, 'webkitFullscreenElement', el);
    }
    window.onblur = document.onblur = null;
    document.hasFocus = () => !0;

    window.addEventListener('keydown', e => {
        if (e.key === 'F2') (document.fullscreenElement ? document.exitFullscreen() : document.documentElement.requestFullscreen()).catch(() => {});
    }, !0);
    GM_addStyle(`
        #solver-panel{position:fixed;bottom:20px;left:20px;z-index:999999;padding:12px;background:rgba(26,27,30,.85);backdrop-filter:blur(10px);border-radius:16px;box-shadow:0 8px 30px rgba(0,0,0,.4);min-width:260px;max-width:320px;border:1px solid rgba(255,255,255,.1)}
        #solver-status{color:#fff;font-size:15px;font-weight:600;margin-bottom:10px;transition:.3s;text-align:left;word-wrap:break-word;white-space:normal}
        #pin-container{display:flex;gap:8px}
        #pin-input{flex:1;border:1px solid rgba(255,255,255,.2);background:rgba(0,0,0,.3);color:#fff;border-radius:8px;padding:8px 12px;font-size:14px;outline:0;text-align:center;transition:.2s}
        #pin-input:focus{border-color:#a78bfa}
        #load-btn{background:linear-gradient(135deg,#a78bfa,#8b5cf6);border:0;border-radius:8px;color:#fff;font-weight:600;padding:0 20px;cursor:pointer;transition:.2s}
        #load-btn:hover{transform:scale(1.05)}
        #load-btn:disabled{cursor:not-allowed;background:#555}
        *{user-select:text!important;-webkit-user-select:text!important}
    `);

    const cache = new Map();
    let lastQid = '';
    const clean = t => t?.replace(/<\/?p>/g, '').trim().replace(/\s+/g, ' ') || '';

    const $ = s => document.querySelector(s);
    const $$ = s => [...document.querySelectorAll(s)];

    function findGamePin() {
        const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
        let node;
        while (node = walker.nextNode()) {
            const m = node.nodeValue.trim().match(/\b(\d{4})\s(\d{4})\b/);
            if (m && node.parentElement?.offsetParent !== null) return m[0].replace(/\s/g, '');
        }
        return null;
    }

    async function fetchAnswers(pin, status) {
        status.textContent = '🌀 Đang tải đáp án...';
        status.style.color = '#fff';
        try {
            const res = await _fetch(`https://api.quizit.online/quizizz?pin=${pin}`);
            if (!res.ok) throw new Error(`API: ${res.status}`);
            const data = await res.json();
            const list = data.answers || data.data?.answers;
            if (!list?.length) throw new Error('Không có đáp án từ API');

            for (const item of list) {
                const id = item.id || item._id;
                if (!id) continue;
                if (item.type === 'OPEN') { cache.set(id, '📝 Tự luận'); continue; }
                if (item.type === 'MSQ' && Array.isArray(item.answers)) {
                    const ans = item.answers.map(a => clean(a.text)).filter(Boolean);
                    if (ans.length) cache.set(id, ans);
                } else {
                    const ans = clean(item.answers?.[0]?.text);
                    if (ans) cache.set(id, ans);
                }
            }
            if (!cache.size) throw new Error('Đáp án rỗng');
            return true;
        } catch (e) {
            status.textContent = `❌ ${e.message}`;
            status.style.color = '#ff5555';
            return false;
        }
    }

    function getQuestion() {
        const container = $('[data-quesid]');
        if (!container) return null;
        const qid = container.dataset.quesid;
        const options = $$('.option.is-selectable').map(el => ({
            text: clean(el.querySelector('.option-text-inner, .text-container')?.innerText),
            element: el,
        }));
        if (options.length) return { qid, type: 'CHOICE', options };
        if ($('input.question-input, textarea.question-input, input[type="text"], textarea'))
            return { qid, type: 'BLANK' };
        return null;
    }

    function autoSubmit() {
        setTimeout(() => {
            const btn = $$('button').find(b => b.innerText.trim().toLowerCase() === 'submit')
                     || $('.submit-button-wrapper button, button.submit-btn');
            if (btn && !btn.disabled) btn.click();
        }, 350);
    }

    function solve(answer, q) {
        if (q.type === 'CHOICE') {
            const targets = Array.isArray(answer) ? answer : [answer];
            targets.forEach(t => {
                const opt = q.options.find(o => o.text === t);
                if (opt) { opt.element.style.border = '4px solid #00FF00'; opt.element.click(); }
            });
            if (Array.isArray(answer)) autoSubmit();
        } else if (q.type === 'BLANK') {
            const input = $('input.question-input, textarea.question-input, input[type="text"], textarea');
            if (input) {
                input.value = answer;
                input.dispatchEvent(new Event('input', { bubbles: true }));
                autoSubmit();
            }
        }
    }

    function mainSolver(status) {
        if (!cache.size) return;
        const q = getQuestion();
        if (!q?.qid) return;
        const ans = cache.get(q.qid);
        if (ans) {
            const display = Array.isArray(ans) ? ans.join('<br>') : ans;
            status.innerHTML = `💡 Đáp án:<div style="margin-top:5px;color:#50fa7b;font-weight:400">${display}</div>`;
            if (typeof ans === 'string' && ans.startsWith('📝')) return;
            solve(ans, q);
        } else {
            status.textContent = '❓ Không tìm thấy đáp án';
            status.style.color = '#ff5555';
        }
    }

    function startObserver(status) {
        new MutationObserver(() => {
            const qid = $('[data-quesid]')?.dataset.quesid;
            if (qid && qid !== lastQid) { lastQid = qid; setTimeout(() => mainSolver(status), 500); }
        }).observe(document.body, { childList: !0, subtree: !0 });
    }

    function init() {
        if ($('#solver-panel')) return;
        document.body.insertAdjacentHTML('beforeend', `
            <div id="solver-panel">
                <div id="solver-status">🔎 Đang tìm Room Code...</div>
                <div id="pin-container">
                    <input type="text" id="pin-input" placeholder="Chờ chút...">
                    <button id="load-btn">Tải</button>
                </div>
            </div>`);

        const btn = $('#load-btn'), input = $('#pin-input'), status = $('#solver-status'), pinBox = $('#pin-container');

        const handleLoad = async () => {
            const pin = input.value.trim().replace(/\s/g, '');
            if (!pin) return;
            btn.disabled = input.disabled = true;
            if (await fetchAnswers(pin, status)) {
                pinBox.style.display = 'none';
                status.textContent = '🚀 Sẵn sàng! Đang chờ câu hỏi...';
                status.style.color = '#fff';
                startObserver(status);
            } else btn.disabled = input.disabled = false;
        };

        btn.addEventListener('click', handleLoad);
        input.addEventListener('keydown', e => e.key === 'Enter' && handleLoad());

        const finder = setInterval(() => {
            const pin = findGamePin();
            if (pin) {
                clearInterval(finder);
                input.value = pin;
                status.textContent = '✅ Đã tìm thấy Room Code';
                status.style.color = '#50fa7b';
                setTimeout(handleLoad, 500);
            }
        }, 1000);
        setTimeout(() => clearInterval(finder), 20000);
    }

    window.addEventListener('load', () => setTimeout(init, 1000));
})();