您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Automatically answers ReadTheory quiz questions using AI
// ==UserScript== // @name ReadTheory // @namespace http://tampermonkey.net/ // @version 1.0 // @description Automatically answers ReadTheory quiz questions using AI // @author theroyalwhale // @match https://readtheoryapp.com/app/student/quiz // @grant GM_xmlhttpRequest // @connect openrouter.ai // @icon https://dyntech.cc/favicon?q=https://readtheory.org // @license MIT // ==/UserScript== (function() { 'use strict'; const AI_API_URL = 'https://openrouter.ai/api/v1/chat/completions'; const AI_API_KEY = 'Grab your own key from OpenRouter'; const AI_MODEL = 'moonshotai/kimi-k2-0905'; let isProcessing = false; let currentUrl = window.location.href; let lastProcessedQuestion = null; let noSubmitButtonCount = 0; // Check if question has already been processed function isQuestionAlreadyProcessed(question) { return lastProcessedQuestion === question; } // Check if an answer is already selected function isAnswerAlreadySelected() { const selectedAnswer = document.querySelector('.student-quiz-page__answer.answer-card-wrapper.selected') || document.querySelector('.student-quiz-page__answer.answer-card-wrapper[aria-checked="true"]') || document.querySelector('.answer-card-wrapper.selected'); return selectedAnswer !== null; } // Check if submit/next button exists function submitButtonExists() { const nextButton = document.querySelector('.primary-button.student-quiz-page__question-next.next-btn.quiz-tab-item'); const submitButton = document.querySelector('.primary-button.student-quiz-page__question-submit.quiz-tab-item'); return nextButton || submitButton; } function isOnQuizPage() { return window.location.href.includes('/app/student/quiz') && window.location.href === currentUrl; } // Extract passage text from description wrapper function extractPassageText() { const descriptionWrapper = document.querySelector('.description-wrapper'); if (!descriptionWrapper) return null; const paragraphs = descriptionWrapper.querySelectorAll('p'); let passageText = ''; paragraphs.forEach(p => { passageText += p.textContent.trim() + '\n'; }); return passageText.trim(); } // Extract question text function extractQuestion() { const questionElement = document.querySelector('.student-quiz-page__question[role="title"]'); return questionElement ? questionElement.textContent.trim() : null; } // Extract answer choices function extractAnswerChoices() { const answersContainer = document.querySelector('.student-quiz-page__answers.quiz-tab-item[role="main"]'); if (!answersContainer) return null; const choices = []; const choiceElements = answersContainer.querySelectorAll('.student-quiz-page__answer.answer-card-wrapper[role="radio"]'); choiceElements.forEach((element, index) => { const alphaElement = element.querySelector('.answer-card__alpha'); const bodyElement = element.querySelector('.answer-card__body'); if (alphaElement && bodyElement) { const letter = alphaElement.textContent.trim(); const text = bodyElement.textContent.trim(); choices.push({ letter: letter, text: text, element: element, index: index }); } }); return choices; } // Call AI API to get the answer async function getAIAnswer(passageText, question, choices) { const choicesText = choices.map(choice => `${choice.letter}. ${choice.text}`).join('\n'); const prompt = `Please read the following passage and answer the multiple choice question. You MUST respond with ONLY the letter of the correct answer (A, B, C, D, or E). Do not include any explanation or additional text - just the single letter. PASSAGE: ${passageText} QUESTION: ${question} CHOICES: ${choicesText} Answer (single letter only):`; try { // Generic API call structure - modify based on your specific AI service const response = await fetch(AI_API_URL, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${AI_API_KEY}` }, body: JSON.stringify({ model: AI_MODEL, messages: [ { role: 'user', content: prompt } ], max_tokens: 10, temperature: 0.1 }) }); if (!response.ok) { throw new Error(`API request failed: ${response.status}`); } const data = await response.json(); // Extract answer based on common API response formats let answer; if (data.choices && data.choices[0] && data.choices[0].message) { // OpenAI format answer = data.choices[0].message.content.trim().toUpperCase(); } else if (data.content && data.content[0] && data.content[0].text) { // Anthropic format answer = data.content[0].text.trim().toUpperCase(); } else if (typeof data === 'string') { // Simple string response answer = data.trim().toUpperCase(); } else { throw new Error('Unexpected API response format'); } // Extract just the letter (remove any extra characters) const letterMatch = answer.match(/[A-Z]/); return letterMatch ? letterMatch[0] : null; } catch (error) { console.error('AI API Error:', error); return null; } } // Click the answer choice function selectAnswer(choices, answerLetter) { const selectedChoice = choices.find(choice => choice.letter === answerLetter); if (selectedChoice) { console.log(`Selecting answer: ${answerLetter} - ${selectedChoice.text}`); selectedChoice.element.click(); return true; } return false; } // Click the submit button function submitAnswer() { // Try to find Next button first (after answer is selected) let submitButton = document.querySelector('.primary-button.student-quiz-page__question-next.next-btn.quiz-tab-item'); // If Next button not found, try the Submit button if (!submitButton) { submitButton = document.querySelector('.primary-button.student-quiz-page__question-submit.quiz-tab-item'); } if (submitButton && !submitButton.classList.contains('disabled')) { console.log('Clicking button:', submitButton.textContent.trim()); submitButton.click(); return true; } return false; } // Main processing function async function processQuestion() { if (isProcessing || !isOnQuizPage()) return; // Check if submit button exists, if not for multiple checks, refresh if (!submitButtonExists()) { noSubmitButtonCount++; if (noSubmitButtonCount >= 3) { console.log('Submit button missing for 3+ checks, refreshing page...'); location.reload(); return; } return; } else { noSubmitButtonCount = 0; // Reset counter if button exists } // Extract question to check if it's already processed const question = extractQuestion(); if (!question) { console.log('No question found, waiting...'); return; } // Skip if already processed this question if (isQuestionAlreadyProcessed(question)) { console.log('Question already processed, skipping AI call...'); // Check if we need to submit an already selected answer if (isAnswerAlreadySelected()) { setTimeout(() => { if (submitAnswer()) { console.log('Previously selected answer submitted'); setTimeout(() => { isProcessing = false; currentUrl = window.location.href; lastProcessedQuestion = null; // Reset for new question }, 3000); } }, 5000); } return; } // Skip if answer is already selected (but question is new) if (isAnswerAlreadySelected()) { console.log('Answer already selected for this question, just submitting...'); lastProcessedQuestion = question; setTimeout(() => { if (submitAnswer()) { console.log('Already selected answer submitted'); setTimeout(() => { isProcessing = false; currentUrl = window.location.href; lastProcessedQuestion = null; }, 3000); } }, 5000); return; } isProcessing = true; console.log('Processing new question...'); try { // Extract all required elements const passageText = extractPassageText(); const choices = extractAnswerChoices(); if (!passageText || !question || !choices || choices.length === 0) { console.log('Missing required elements, waiting...'); isProcessing = false; return; } console.log('Passage extracted:', passageText.substring(0, 100) + '...'); console.log('Question:', question); console.log('Choices:', choices.map(c => `${c.letter}: ${c.text}`)); // Mark this question as processed lastProcessedQuestion = question; // Get AI answer const aiAnswer = await getAIAnswer(passageText, question, choices); if (!aiAnswer) { console.error('Failed to get AI answer'); isProcessing = false; lastProcessedQuestion = null; // Reset if failed return; } console.log('AI Answer:', aiAnswer); // Select the answer if (selectAnswer(choices, aiAnswer)) { // Wait 5 seconds before submitting setTimeout(() => { if (submitAnswer()) { console.log('Answer submitted successfully'); // Wait for page to refresh/change setTimeout(() => { isProcessing = false; currentUrl = window.location.href; lastProcessedQuestion = null; // Reset for new question }, 3000); } else { console.log('Submit button not ready, will retry...'); isProcessing = false; } }, 5000); } else { console.error('Failed to select answer:', aiAnswer); isProcessing = false; lastProcessedQuestion = null; // Reset if failed } } catch (error) { console.error('Error processing question:', error); isProcessing = false; lastProcessedQuestion = null; // Reset if error } } // Start the automation function startAutomation() { console.log('ReadTheory Auto Answer script started'); console.log('Make sure to configure your AI API credentials in the script!'); // Initial attempt setTimeout(processQuestion, 2000); // Set up interval to check for new questions setInterval(() => { if (isOnQuizPage() && !isProcessing) { processQuestion(); } else if (!isOnQuizPage()) { console.log('Quiz completed or navigated away from quiz page'); } }, 3000); } // Wait for page to load completely if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', startAutomation); } else { startAutomation(); } })();