Greasy Fork is available in English.

Hack Wayground

Hack Wayground/quizizz

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

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

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

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