Shows the correct answer on NoRedInk quizzes at the press of a button
Size
10.1 KB
Version
1.0.1
Created
Oct 22, 2025
Updated
1 day ago
1// ==UserScript==
2// @name NoRedInk Answer Revealer
3// @description Shows the correct answer on NoRedInk quizzes at the press of a button
4// @version 1.0.1
5// @match https://*.noredink.com/*
6// @icon https://www.noredink.com/assets/favicon/favicon-8ac1ca529bfa7f8c60a434623b0fb3341b014ab5e4d6fe83693cfb8f74df5fe1.ico
7// ==/UserScript==
8(function() {
9 'use strict';
10
11 console.log('NoRedInk Answer Revealer 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 // Create and inject the "Show Answer" button
27 function createShowAnswerButton() {
28 // Check if button already exists
29 if (document.getElementById('show-answer-btn')) {
30 return;
31 }
32
33 const button = document.createElement('button');
34 button.id = 'show-answer-btn';
35 button.textContent = '🔍 Show Answer';
36 button.style.cssText = `
37 position: fixed;
38 top: 20px;
39 right: 20px;
40 z-index: 10000;
41 padding: 12px 24px;
42 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
43 color: white;
44 border: none;
45 border-radius: 8px;
46 font-size: 16px;
47 font-weight: bold;
48 cursor: pointer;
49 box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
50 transition: all 0.3s ease;
51 `;
52
53 button.addEventListener('mouseenter', () => {
54 button.style.transform = 'translateY(-2px)';
55 button.style.boxShadow = '0 6px 20px rgba(0, 0, 0, 0.3)';
56 });
57
58 button.addEventListener('mouseleave', () => {
59 button.style.transform = 'translateY(0)';
60 button.style.boxShadow = '0 4px 15px rgba(0, 0, 0, 0.2)';
61 });
62
63 button.addEventListener('click', showAnswer);
64 document.body.appendChild(button);
65 console.log('Show Answer button created');
66 }
67
68 // Show loading state
69 function showLoading() {
70 const button = document.getElementById('show-answer-btn');
71 if (button) {
72 button.textContent = '⏳ Analyzing...';
73 button.disabled = true;
74 button.style.opacity = '0.7';
75 button.style.cursor = 'not-allowed';
76 }
77 }
78
79 // Hide loading state
80 function hideLoading() {
81 const button = document.getElementById('show-answer-btn');
82 if (button) {
83 button.textContent = '🔍 Show Answer';
84 button.disabled = false;
85 button.style.opacity = '1';
86 button.style.cursor = 'pointer';
87 }
88 }
89
90 // Extract question data from the page
91 function extractQuestionData() {
92 const questionData = {
93 instructions: '',
94 claim: '',
95 evidence: '',
96 options: []
97 };
98
99 // Get instructions
100 const instructionsEl = document.querySelector('#instructions-with-optional-highlights');
101 if (instructionsEl) {
102 questionData.instructions = instructionsEl.textContent.trim();
103 }
104
105 // Get claim
106 const claimEl = document.querySelector('[data-testid="claim-text"]');
107 if (claimEl) {
108 questionData.claim = claimEl.textContent.trim();
109 }
110
111 // Get evidence
112 const evidenceEl = document.querySelector('[data-testid="evidence-text"]');
113 if (evidenceEl) {
114 questionData.evidence = evidenceEl.textContent.trim();
115 }
116
117 // Get all answer options
118 const optionButtons = document.querySelectorAll('.page-question-outline-multiple-choice-Option button[data-nri-description="Nri-Ui-Button-V10-customButton"]');
119 optionButtons.forEach((button, index) => {
120 const optionText = button.textContent.trim();
121 if (optionText) {
122 questionData.options.push({
123 index: index,
124 text: optionText,
125 element: button
126 });
127 }
128 });
129
130 console.log('Extracted question data:', questionData);
131 return questionData;
132 }
133
134 // Use AI to determine the correct answer
135 async function showAnswer() {
136 console.log('Show Answer button clicked');
137 showLoading();
138
139 try {
140 const questionData = extractQuestionData();
141
142 if (questionData.options.length === 0) {
143 alert('No answer options found on this page.');
144 hideLoading();
145 return;
146 }
147
148 // Build prompt for AI
149 const prompt = `You are analyzing a NoRedInk quiz question. Based on the following information, determine which answer is correct.
150
151Instructions: ${questionData.instructions}
152Claim: ${questionData.claim}
153Evidence: ${questionData.evidence}
154
155Answer Options:
156${questionData.options.map((opt, i) => `${i + 1}. ${opt.text}`).join('\n')}
157
158Analyze the evidence in relation to the claim and determine which option is correct. Consider:
159- Does the evidence strongly support the claim?
160- Is the evidence factual and credible?
161- Does the evidence come from a reliable source?
162
163Return the correct answer.`;
164
165 console.log('Sending prompt to AI:', prompt);
166
167 const response = await RM.aiCall(prompt, {
168 type: "json_schema",
169 json_schema: {
170 name: "answer_analysis",
171 schema: {
172 type: "object",
173 properties: {
174 correctAnswerIndex: {
175 type: "number",
176 description: "The index (0-based) of the correct answer"
177 },
178 correctAnswerText: {
179 type: "string",
180 description: "The text of the correct answer"
181 },
182 explanation: {
183 type: "string",
184 description: "Brief explanation of why this is the correct answer"
185 }
186 },
187 required: ["correctAnswerIndex", "correctAnswerText", "explanation"]
188 }
189 }
190 });
191
192 console.log('AI response:', response);
193
194 // Highlight the correct answer
195 highlightCorrectAnswer(questionData.options, response.correctAnswerIndex, response.explanation);
196
197 } catch (error) {
198 console.error('Error showing answer:', error);
199 alert('Failed to determine the answer. Please try again.');
200 } finally {
201 hideLoading();
202 }
203 }
204
205 // Highlight the correct answer on the page
206 function highlightCorrectAnswer(options, correctIndex, explanation) {
207 // Remove any existing highlights
208 document.querySelectorAll('.answer-highlight').forEach(el => el.remove());
209
210 if (correctIndex >= 0 && correctIndex < options.length) {
211 const correctOption = options[correctIndex];
212 const button = correctOption.element;
213 const container = button.closest('.page-question-outline-multiple-choice-Option');
214
215 if (container) {
216 // Add highlight styling
217 container.style.position = 'relative';
218 container.style.border = '3px solid #10b981';
219 container.style.borderRadius = '8px';
220 container.style.backgroundColor = '#d1fae5';
221 container.style.padding = '8px';
222 container.style.transition = 'all 0.3s ease';
223
224 // Add checkmark indicator
225 const indicator = document.createElement('div');
226 indicator.className = 'answer-highlight';
227 indicator.textContent = '✓ Correct Answer';
228 indicator.style.cssText = `
229 position: absolute;
230 top: -12px;
231 right: 10px;
232 background: #10b981;
233 color: white;
234 padding: 4px 12px;
235 border-radius: 12px;
236 font-size: 12px;
237 font-weight: bold;
238 box-shadow: 0 2px 8px rgba(16, 185, 129, 0.3);
239 `;
240 container.appendChild(indicator);
241
242 // Show explanation
243 const explanationBox = document.createElement('div');
244 explanationBox.className = 'answer-highlight';
245 explanationBox.textContent = explanation;
246 explanationBox.style.cssText = `
247 margin-top: 12px;
248 padding: 12px;
249 background: #f0fdf4;
250 border-left: 4px solid #10b981;
251 border-radius: 4px;
252 font-size: 14px;
253 color: #065f46;
254 line-height: 1.5;
255 `;
256 container.appendChild(explanationBox);
257
258 // Scroll to the correct answer
259 container.scrollIntoView({ behavior: 'smooth', block: 'center' });
260
261 console.log('Highlighted correct answer:', correctOption.text);
262 }
263 }
264 }
265
266 // Initialize the extension
267 function init() {
268 console.log('Initializing NoRedInk Answer Revealer');
269
270 // Wait for the page to load
271 if (document.readyState === 'loading') {
272 document.addEventListener('DOMContentLoaded', createShowAnswerButton);
273 } else {
274 createShowAnswerButton();
275 }
276
277 // Watch for navigation changes (NoRedInk is a single-page app)
278 const observer = new MutationObserver(debounce(() => {
279 createShowAnswerButton();
280 }, 500));
281
282 observer.observe(document.body, {
283 childList: true,
284 subtree: true
285 });
286 }
287
288 // Start the extension
289 init();
290})();