ReadTheory

Automatically answers ReadTheory quiz questions using AI

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği yüklemek için Tampermonkey gibi bir uzantı yüklemeniz gerekir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği indirebilmeniz için ayrıca Tampermonkey gibi bir eklenti kurmanız gerekmektedir.

Bu komut dosyasını yüklemek için bir kullanıcı komut dosyası yöneticisi uzantısı yüklemeniz gerekecek.

(Zaten bir kullanıcı komut dosyası yöneticim var, kurmama izin verin!)

Bu stili yüklemek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için Stylus gibi bir uzantı kurmanız gerekir.

Bu stili yükleyebilmek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı kurmanız gerekir.

Bu stili yükleyebilmek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

(Zateb bir user-style yöneticim var, yükleyeyim!)

// ==UserScript==
// @name         ReadTheory
// @namespace    http://tampermonkey.net/
// @version      3.0
// @description  Automatically answers ReadTheory quiz questions using AI
// @author       cfpy67
// @match        https://readtheoryapp.com/app/student/quiz
// @grant        none
// @icon         https://dyntech.cc/favicon?q=https://readtheory.org
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    const API_URL = 'https://openrouter.ai/api/v1/chat/completions';
    const API_KEY = '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';
    const MODEL = 'openrouter/free';

    const DELAYS = {
        SELECT_TO_SUBMIT: 800,
        POLL_INTERVAL: 1500,
        INIT: 2000,
        NO_BUTTON_RELOAD: 3,
    };

    let processing = false;
    let lastQuestion = null;
    let noButtonStreak = 0;

    function getPassage() {
        const wrapper = document.querySelector('.description-wrapper');
        if (!wrapper) return null;
        return [...wrapper.querySelectorAll('p')]
            .map(p => p.textContent.trim())
            .filter(Boolean)
            .join('\n');
    }

    function getQuestion() {
        const el = document.querySelector('.student-quiz-page__question');
        return el ? el.textContent.trim() : null;
    }

    function getChoices() {
        const answers = document.querySelector('.student-quiz-page__answers[role="radiogroup"]');
        if (!answers) return null;
        return [...answers.querySelectorAll('.student-quiz-page__answer.answer-card-wrapper[role="radio"]')]
            .map((el) => {
                const letter = el.querySelector('.answer-card__alpha')?.textContent.trim();
                const text = el.querySelector('.answer-card__body')?.textContent.trim();
                if (!letter || !text) return null;
                return { letter, text, el };
            })
            .filter(Boolean);
    }

    function isAnswerSelected() {
        return !!document.querySelector(
            '.student-quiz-page__answer.answer-card-wrapper.selected, ' +
            '.student-quiz-page__answer.answer-card-wrapper[aria-checked="true"]'
        );
    }

    function getActionButton() {
        return (
            document.querySelector('.primary-button.student-quiz-page__question-next.next-btn.quiz-tab-item') ||
            document.querySelector('.primary-button.student-quiz-page__question-submit.quiz-tab-item')
        );
    }

    function clickSubmit() {
        const btn = getActionButton();
        if (btn && !btn.classList.contains('disabled')) {
            btn.click();
            return true;
        }
        return false;
    }

    function sleep(ms) {
        return new Promise(r => setTimeout(r, ms));
    }

    function waitForNewQuestion(oldQuestion, timeout = 8000) {
        return new Promise((resolve) => {
            const deadline = Date.now() + timeout;
            const iv = setInterval(() => {
                const q = getQuestion();
                if ((q && q !== oldQuestion) || Date.now() > deadline) {
                    clearInterval(iv);
                    resolve();
                }
            }, 200);
        });
    }

    async function askAI(passage, question, choices) {
        const choiceLines = choices.map(c => `${c.letter}. ${c.text}`).join('\n');
        const prompt = `Read the passage and answer the multiple choice question.
Respond with ONLY a single letter (A, B, C, D, or E). No explanation.

PASSAGE:
${passage}

QUESTION:
${question}

CHOICES:
${choiceLines}

Answer:`;

        const res = await fetch(API_URL, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'Authorization': `Bearer ${API_KEY}`,
            },
            body: JSON.stringify({
                model: MODEL,
                max_tokens: 200,
                temperature: 0.1,
                reasoning: { effort: 'none' },
                messages: [{ role: 'user', content: prompt }],
            }),
        });

        if (!res.ok) throw new Error(`API ${res.status}`);

        const data = await res.json();
        const raw = data?.choices?.[0]?.message?.content?.trim().toUpperCase() ?? '';
        const match = raw.match(/[A-E]/);
        return match ? match[0] : null;
    }

    function selectAnswer(choices, letter) {
        const target = choices.find(c => c.letter === letter);
        if (!target) return false;
        target.el.click();
        return true;
    }

    async function tick() {
        if (processing) return;

        const btn = getActionButton();
        if (!btn) {
            noButtonStreak++;
            if (noButtonStreak >= DELAYS.NO_BUTTON_RELOAD) {
                console.log('[RT] No button for 3 ticks, reloading');
                location.reload();
            }
            return;
        }
        noButtonStreak = 0;

        const question = getQuestion();
        if (!question) return;

        if (question === lastQuestion) return;

        if (isAnswerSelected()) {
            lastQuestion = question;
            processing = true;
            await sleep(DELAYS.SELECT_TO_SUBMIT);
            if (clickSubmit()) await waitForNewQuestion(question);
            processing = false;
            lastQuestion = null;
            return;
        }

        const passage = getPassage();
        const choices = getChoices();
        if (!passage || !choices?.length) return;

        processing = true;
        lastQuestion = question;

        console.log('[RT] Question:', question);

        try {
            const answer = await askAI(passage, question, choices);
            if (!answer) throw new Error('No answer from AI');

            console.log('[RT] AI picked:', answer);

            if (!selectAnswer(choices, answer)) throw new Error(`Couldn't click choice ${answer}`);

            await sleep(DELAYS.SELECT_TO_SUBMIT);

            if (clickSubmit()) {
                console.log('[RT] Submitted');
                await waitForNewQuestion(question);
            } else {
                console.warn('[RT] Submit button not ready');
            }
        } catch (err) {
            console.error('[RT] Error:', err);
        }

        processing = false;
        lastQuestion = null;
    }

    function init() {
        console.log('[RT] Started');
        setTimeout(tick, DELAYS.INIT);
        setInterval(() => {
            if (!processing) tick();
        }, DELAYS.POLL_INTERVAL);
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();