Size
12.0 KB
Version
1.0.1
Created
Mar 18, 2026
Updated
3 days ago
1// ==UserScript==
2// @name i-Ready Auto Answer
3// @description Automatically answers i-Ready questions using AI
4// @version 1.0.1
5// @match https://*.login.i-ready.com/*
6// @icon https://login.i-ready.com/favicon.ico
7// ==/UserScript==
8(function() {
9 'use strict';
10
11 console.log('i-Ready Auto Answer extension loaded');
12
13 // Debounce function to prevent excessive calls
14 function debounce(func, wait) {
15 let timeout;
16 return function executedFunction(...args) {
17 const later = () => {
18 clearTimeout(timeout);
19 func(...args);
20 };
21 clearTimeout(timeout);
22 timeout = setTimeout(later, wait);
23 };
24 }
25
26 // Get the iframe element
27 function getIframe() {
28 return document.querySelector('#html5Iframe');
29 }
30
31 // Get the iframe document
32 function getIframeDoc() {
33 const iframe = getIframe();
34 if (iframe && iframe.contentDocument) {
35 return iframe.contentDocument;
36 }
37 return null;
38 }
39
40 // Extract question text from the page
41 function extractQuestion() {
42 const iframeDoc = getIframeDoc();
43 if (!iframeDoc) return null;
44
45 // Look for question text in various possible locations
46 const questionSelectors = [
47 '[data-testid*="question"]',
48 '[class*="question"]',
49 '[class*="prompt"]',
50 '[role="heading"]',
51 '.instruction-text',
52 '.question-text'
53 ];
54
55 for (const selector of questionSelectors) {
56 const element = iframeDoc.querySelector(selector);
57 if (element && element.textContent.trim()) {
58 console.log('Found question element:', element);
59 return element.textContent.trim();
60 }
61 }
62
63 // Fallback: get all visible text
64 const body = iframeDoc.body;
65 if (body) {
66 const allText = body.textContent.trim();
67 console.log('Extracted text from body:', allText.substring(0, 200));
68 return allText;
69 }
70
71 return null;
72 }
73
74 // Find answer options
75 function findAnswerOptions() {
76 const iframeDoc = getIframeDoc();
77 if (!iframeDoc) return [];
78
79 const options = [];
80
81 // Look for clickable answer elements
82 const answerSelectors = [
83 'button[data-testid*="answer"]',
84 'button[class*="answer"]',
85 'div[role="button"][class*="answer"]',
86 'div[class*="choice"]',
87 'button[class*="choice"]',
88 '[data-testid*="choice"]'
89 ];
90
91 for (const selector of answerSelectors) {
92 const elements = iframeDoc.querySelectorAll(selector);
93 if (elements.length > 0) {
94 console.log(`Found ${elements.length} answer options with selector: ${selector}`);
95 elements.forEach((el, index) => {
96 options.push({
97 element: el,
98 text: el.textContent.trim(),
99 index: index
100 });
101 });
102 break;
103 }
104 }
105
106 return options;
107 }
108
109 // Find input fields for text answers
110 function findInputFields() {
111 const iframeDoc = getIframeDoc();
112 if (!iframeDoc) return [];
113
114 const inputs = iframeDoc.querySelectorAll('input[type="text"], input[type="number"], textarea');
115 console.log(`Found ${inputs.length} input fields`);
116 return Array.from(inputs);
117 }
118
119 // Use AI to answer the question
120 async function getAIAnswer(question, options) {
121 try {
122 console.log('Asking AI to answer question:', question);
123
124 if (options && options.length > 0) {
125 // Multiple choice question
126 const optionsText = options.map((opt, idx) => `${idx + 1}. ${opt.text}`).join('\n');
127
128 const response = await RM.aiCall(
129 `You are helping a student with an i-Ready question. Answer this question by selecting the correct option number (1, 2, 3, or 4).\n\nQuestion: ${question}\n\nOptions:\n${optionsText}\n\nRespond with ONLY the number of the correct answer.`,
130 {
131 type: "json_schema",
132 json_schema: {
133 name: "answer_selection",
134 schema: {
135 type: "object",
136 properties: {
137 answerNumber: {
138 type: "number",
139 description: "The number of the correct answer option (1-4)"
140 },
141 reasoning: {
142 type: "string",
143 description: "Brief explanation of why this is correct"
144 }
145 },
146 required: ["answerNumber"]
147 }
148 }
149 }
150 );
151
152 console.log('AI response:', response);
153 return {
154 type: 'multiple_choice',
155 answerIndex: response.answerNumber - 1,
156 reasoning: response.reasoning
157 };
158 } else {
159 // Text input question
160 const answer = await RM.aiCall(
161 `You are helping a student with an i-Ready question. Provide a concise, correct answer to this question.\n\nQuestion: ${question}\n\nProvide ONLY the answer, no explanation.`
162 );
163
164 console.log('AI answer:', answer);
165 return {
166 type: 'text_input',
167 answer: answer.trim()
168 };
169 }
170 } catch (error) {
171 console.error('Error getting AI answer:', error);
172 return null;
173 }
174 }
175
176 // Click the answer button
177 function clickAnswer(option) {
178 try {
179 console.log('Clicking answer:', option.text);
180 option.element.click();
181 return true;
182 } catch (error) {
183 console.error('Error clicking answer:', error);
184 return false;
185 }
186 }
187
188 // Fill in text input
189 function fillTextInput(input, answer) {
190 try {
191 console.log('Filling input with:', answer);
192 input.value = answer;
193 input.dispatchEvent(new Event('input', { bubbles: true }));
194 input.dispatchEvent(new Event('change', { bubbles: true }));
195 return true;
196 } catch (error) {
197 console.error('Error filling input:', error);
198 return false;
199 }
200 }
201
202 // Find and click submit/next button
203 function clickSubmitButton() {
204 const iframeDoc = getIframeDoc();
205 if (!iframeDoc) return false;
206
207 const submitSelectors = [
208 'button[data-testid*="submit"]',
209 'button[data-testid*="next"]',
210 'button[class*="submit"]',
211 'button[class*="next"]',
212 'button[class*="continue"]',
213 'button:contains("Submit")',
214 'button:contains("Next")',
215 'button:contains("Continue")'
216 ];
217
218 for (const selector of submitSelectors) {
219 const button = iframeDoc.querySelector(selector);
220 if (button && !button.disabled) {
221 console.log('Clicking submit button');
222 setTimeout(() => button.click(), 1000);
223 return true;
224 }
225 }
226
227 return false;
228 }
229
230 // Main auto-answer function
231 async function autoAnswer() {
232 console.log('Starting auto-answer process...');
233
234 // Wait a bit for the page to load
235 await new Promise(resolve => setTimeout(resolve, 2000));
236
237 const question = extractQuestion();
238 if (!question) {
239 console.log('No question found');
240 return;
241 }
242
243 console.log('Question found:', question);
244
245 // Check for multiple choice options
246 const options = findAnswerOptions();
247
248 // Check for text inputs
249 const inputs = findInputFields();
250
251 if (options.length === 0 && inputs.length === 0) {
252 console.log('No answer options or input fields found');
253 return;
254 }
255
256 // Get AI answer
257 const aiResponse = await getAIAnswer(question, options.length > 0 ? options : null);
258
259 if (!aiResponse) {
260 console.log('Failed to get AI answer');
261 return;
262 }
263
264 // Apply the answer
265 if (aiResponse.type === 'multiple_choice' && options.length > 0) {
266 const selectedOption = options[aiResponse.answerIndex];
267 if (selectedOption) {
268 console.log('AI selected option:', selectedOption.text);
269 if (aiResponse.reasoning) {
270 console.log('Reasoning:', aiResponse.reasoning);
271 }
272 clickAnswer(selectedOption);
273
274 // Try to click submit button after a delay
275 setTimeout(() => clickSubmitButton(), 1500);
276 }
277 } else if (aiResponse.type === 'text_input' && inputs.length > 0) {
278 inputs.forEach(input => fillTextInput(input, aiResponse.answer));
279
280 // Try to click submit button after a delay
281 setTimeout(() => clickSubmitButton(), 1500);
282 }
283 }
284
285 // Create a control button
286 function createControlButton() {
287 const button = document.createElement('button');
288 button.textContent = '🤖 Auto Answer';
289 button.style.cssText = `
290 position: fixed;
291 top: 20px;
292 right: 20px;
293 z-index: 999999;
294 padding: 12px 20px;
295 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
296 color: white;
297 border: none;
298 border-radius: 8px;
299 font-size: 16px;
300 font-weight: bold;
301 cursor: pointer;
302 box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
303 transition: all 0.3s ease;
304 `;
305
306 button.addEventListener('mouseenter', () => {
307 button.style.transform = 'scale(1.05)';
308 button.style.boxShadow = '0 6px 20px rgba(0, 0, 0, 0.3)';
309 });
310
311 button.addEventListener('mouseleave', () => {
312 button.style.transform = 'scale(1)';
313 button.style.boxShadow = '0 4px 15px rgba(0, 0, 0, 0.2)';
314 });
315
316 button.addEventListener('click', async () => {
317 button.textContent = '⏳ Processing...';
318 button.disabled = true;
319
320 await autoAnswer();
321
322 setTimeout(() => {
323 button.textContent = '🤖 Auto Answer';
324 button.disabled = false;
325 }, 3000);
326 });
327
328 document.body.appendChild(button);
329 console.log('Control button created');
330 }
331
332 // Initialize the extension
333 function init() {
334 console.log('Initializing i-Ready Auto Answer extension');
335
336 // Wait for the page to load
337 if (document.readyState === 'loading') {
338 document.addEventListener('DOMContentLoaded', () => {
339 setTimeout(createControlButton, 1000);
340 });
341 } else {
342 setTimeout(createControlButton, 1000);
343 }
344
345 // Monitor for iframe changes
346 const observer = new MutationObserver(debounce(() => {
347 const iframe = getIframe();
348 if (iframe && iframe.contentDocument) {
349 console.log('Iframe content changed');
350 }
351 }, 1000));
352
353 observer.observe(document.body, {
354 childList: true,
355 subtree: true
356 });
357 }
358
359 // Start the extension
360 init();
361})();