Hack Wayground

Hack Wayground/quizizz

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да инсталирате разширение, като например Tampermonkey .

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

(Вече имам инсталиран мениджър на стиловете, искам да го инсталирам!)

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