Greasy Fork is available in English.
全自动刷课 | 智能避让 | 自动进入讨论 | 自动回复 | 自动净化AI答案 | 支持暂停/继续 | 自动关闭标签 | 修复UI显示
// ==UserScript==
// @name 优学院答题助手 V14.9
// @namespace https://thewinds.me/
// @version 14.9.8
// @description 全自动刷课 | 智能避让 | 自动进入讨论 | 自动回复 | 自动净化AI答案 | 支持暂停/继续 | 自动关闭标签 | 修复UI显示
// @author Winds (Modified)
// @license CC-BY-NC-4.0
// @match *://*.ulearning.cn/*
// @connect homeworkapi.ulearning.cn
// @connect workers.dev
// @connect *
// @grant GM_xmlhttpRequest
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @grant unsafeWindow
// ==/UserScript==
(function() {
'use strict';
// ================= 配置区域 =================
const USER_API_URL = GM_getValue('UL_AI_URL', '');
const CONFIG = {
get aiBaseUrl() { return USER_API_URL; },
interval: 2000,
selectors: {
listContainer: ".table-homework",
writeBtn: ".item-operation .button-red-solid",
nextPageBtn: ".pagination-wrap .next",
questionContainer: ".question-choice, .question-gap-filling, .question-short-answer",
titleContext: ".title",
question: ".question-title",
textInput: ".ul-textarea__inner",
choiceItem: ".choice-item",
choiceIndex: ".index",
choiceClickArea: ".ul-radio, .ul-checkbox",
judgeTrueIcon: ".icon-zhengque",
judgeFalseIcon: ".icon-cuowu1",
checkedSelector: ".is-checked, .is-active, input:checked",
startQuizBtn: ".ul-button--primary",
bottomBarNum: ".number .answered",
submitBtn: ".ul-button--primary",
modalConfirmBtn: ".ul-message-box .ul-button--primary",
headerBackBtn: ".header-back, .goback, .icon-fanhui",
resultPageMarker: ".homework-result-report, .score-panel",
itemName: ".item-name",
checkboxClass: ".ul-checkbox",
// 👇👇👇 讨论区相关选择器 👇👇👇
discussionPageMarker: ".course-discussion-page", // 讨论列表页标志
discussionRow: ".discussion-list .table-body", // 讨论列表行
myPostCount: ".item-post-my span", // 我的帖子计数
viewDiscussionBtn: ".item-operation .button-red-hollow", // 查看按钮
// 👇👇👇 讨论详情页自动回复相关 👇👇👇
discussionDetailTitle: ".contentTopText", // 题目容器
discussionSubmitBtn: ".el-button.submit.el-button--primary" // 提交按钮
}
};
let API_ANSWERS = {};
let HAS_FETCHED_API = false;
let hasAutoReplied = false; // 防止单页重复回复
// ================= 状态管理 =================
const isListPage = () => document.querySelector(CONFIG.selectors.listContainer) !== null;
const isResultPage = () => location.href.includes("stuReport") || document.querySelector(CONFIG.selectors.resultPageMarker) !== null;
const isDiscussionListPage = () => document.querySelector(CONFIG.selectors.discussionPageMarker) !== null;
// 判断是否为讨论详情页
const isDiscussionDetailPage = () => {
return document.querySelector(CONFIG.selectors.discussionDetailTitle) !== null &&
document.getElementById('cke_editorContainer') !== null;
};
const isPotentialQuizPage = () => !isListPage() && !isResultPage() && !isDiscussionListPage() && !isDiscussionDetailPage() && (
document.querySelectorAll(CONFIG.selectors.questionContainer).length > 0 ||
document.querySelector(CONFIG.selectors.startQuizBtn) !== null
);
let isRunning = false;
let isPaused = false;
let questionsList = [];
let currentIndex = 0;
// ================= 首次运行检查 =================
function checkFirstRun() {
if (!GM_getValue('UL_HAS_INIT', false)) {
const tips = "🎉 欢迎使用优学院助手!\n\n请输入AI API地址(留空则只做客观题):\n注意:配置API后,进入讨论题将自动生成回复!";
const input = prompt(tips, "");
if (input !== null) {
GM_setValue('UL_AI_URL', input.trim());
GM_setValue('UL_HAS_INIT', true);
location.reload();
}
}
}
// ================= API 获取 (客观题) =================
function getIdsFromUrl() {
const url = location.href;
const ocIdMatch = url.match(/ocId=(\d+)/);
const homeworkIdMatch = url.match(/homeworkId=(\d+)/);
if (ocIdMatch && homeworkIdMatch) return { ocId: ocIdMatch[1], homeworkId: homeworkIdMatch[1] };
return null;
}
function getToken() {
const match = document.cookie.match(/token=([^;]+)/);
return match ? match[1] : localStorage.getItem("token");
}
function fetchStandardAnswers(callback) {
const ids = getIdsFromUrl();
if (!ids) { if(callback) callback(false); return; }
const token = getToken();
if (!token) { if(callback) callback(false); return; }
const apiUrl = `https://homeworkapi.ulearning.cn/quiz/homework/stu/questions?homeworkId=${ids.homeworkId}&ocId=${ids.ocId}&showAnswer=true`;
const statusEl = document.querySelector('#status-text');
if(statusEl) statusEl.innerHTML = "📡 正在抓取标准答案...";
GM_xmlhttpRequest({
method: "GET",
url: apiUrl,
headers: { "Authorization": token, "User-Agent": navigator.userAgent },
onload: function(response) {
try {
const json = JSON.parse(response.responseText);
if (json.result) {
API_ANSWERS = {};
json.result.forEach((q, index) => {
if(q.id) API_ANSWERS[q.id] = q.correctAnswer;
API_ANSWERS[`INDEX_${index}`] = q.correctAnswer;
});
HAS_FETCHED_API = true;
if(statusEl) statusEl.innerHTML = `✅ 成功获取 ${json.result.length} 题答案`;
if(callback) callback(true);
} else {
if(callback) callback(false);
}
} catch (e) { if(callback) callback(false); }
},
onerror: () => { if(callback) callback(false); }
});
}
// ================= UI 创建 (已修复宽度) =================
GM_addStyle(`
#ai-panel {
position: fixed; top: 20px; right: 20px;
width: 360px; /* 🟢 宽度增加到360px */
height: 420px;
background: #fff; box-shadow: 0 4px 20px rgba(0,0,0,0.15);
border-radius: 12px; z-index: 99999; font-family: sans-serif;
border: 1px solid #ebeef5; display: flex; flex-direction: column;
transition: all 0.3s; overflow: hidden;
}
#ai-header {
padding: 12px 15px; background: #2c3e50; color: white;
height: 44px; box-sizing: border-box; font-weight: 600;
display: flex; justify-content: space-between; align-items: center; cursor: move;
}
#ai-content { padding: 15px; overflow-y: auto; flex-grow: 1; display: flex; flex-direction: column; }
.ai-btn {
background: rgba(255,255,255,0.2);
border: 1px solid rgba(255,255,255,0.6);
color: white;
padding: 3px 8px; /* 🟢 减小内边距 */
border-radius: 4px;
cursor: pointer;
font-size: 12px;
margin-left: 3px; /* 🟢 减小间距 */
}
.ai-btn:hover { background: rgba(255,255,255,0.3); }
.reasoning { color: #666; font-style: italic; background: #f8f9fa; padding: 8px; margin-bottom: 10px; font-size: 12px; border-left: 3px solid #ddd; }
.answer { color: #333; font-weight: 600; white-space: pre-wrap; }
.current-q { border: 2px solid #2c3e50 !important; box-shadow: 0 0 10px rgba(44,62,80,0.2); }
#ai-panel.minimized {
width: 50px !important; height: 50px !important;
border-radius: 50%; background-color: #2c3e50;
}
#ai-panel.minimized #ai-content, #ai-panel.minimized #ai-header { opacity: 0; pointer-events: none; }
#ai-panel.minimized::after {
content: "⚙️"; font-size: 24px; position: absolute;
top: 50%; left: 50%; transform: translate(-50%, -50%);
color: white; pointer-events: none;
}
`);
function createUI() {
if (document.getElementById('ai-panel')) return;
const panel = document.createElement('div');
panel.id = 'ai-panel';
panel.innerHTML = `
<div id="ai-header">
<span>🤖 优学院助手</span>
<div style="display:flex; align-items:center;"> <button class="ai-btn" id="btn-settings" title="设置">⚙️</button>
<button class="ai-btn" id="btn-pause" style="display:none;" title="暂停/继续">⏸</button>
<button class="ai-btn" id="btn-stop" style="display:none; background:#e74c3c;" title="停止">⏹</button>
<button class="ai-btn" id="btn-action">▶ 启动</button>
<button class="ai-btn" id="btn-minimize" title="最小化">❌</button>
</div>
</div>
<div id="ai-content">
<div id="status-text" style="color:#666; text-align:center; margin-top:5px;">等待操作...</div>
<div id="ai-log" style="margin-top:15px; flex-grow:1;"></div>
</div>`;
document.body.appendChild(panel);
const actionBtn = panel.querySelector('#btn-action');
const stopBtn = panel.querySelector('#btn-stop');
const pauseBtn = panel.querySelector('#btn-pause');
const minimizeBtn = panel.querySelector('#btn-minimize');
minimizeBtn.onclick = (e) => { e.stopPropagation(); panel.classList.add('minimized'); };
panel.onclick = () => { if (panel.classList.contains('minimized')) panel.classList.remove('minimized'); };
const header = panel.querySelector('#ai-header');
let isDragging = false, startX, startY, initLeft, initTop;
header.onmousedown = (e) => { isDragging = true; startX = e.clientX; startY = e.clientY; const rect = panel.getBoundingClientRect(); initLeft = rect.left; initTop = rect.top; };
document.onmousemove = (e) => { if(isDragging) { panel.style.left = (initLeft + e.clientX - startX) + 'px'; panel.style.top = (initTop + e.clientY - startY) + 'px'; } };
document.onmouseup = () => isDragging = false;
panel.querySelector('#btn-settings').onclick = () => {
const current = GM_getValue('UL_AI_URL', '');
const newUrl = prompt("配置 AI API 地址:", current);
if (newUrl !== null) { GM_setValue('UL_AI_URL', newUrl.trim()); location.reload(); }
};
stopBtn.onclick = () => { isRunning = false; GM_setValue('UL_QUEUE_MODE', false); location.reload(); };
pauseBtn.onclick = togglePause;
// 页面类型判断与初始化
if (isDiscussionDetailPage()) {
// 讨论详情页
document.querySelector('#status-text').innerText = "📝 讨论详情页";
actionBtn.innerText = "▶ 自动回复";
actionBtn.onclick = () => { processDiscussionReply(); showPauseBtn(); };
// 队列模式自动开始
if (GM_getValue('UL_QUEUE_MODE', false)) {
showPauseBtn();
setTimeout(processDiscussionReply, 2000);
}
} else if (isDiscussionListPage()) {
// 讨论列表页
document.querySelector('#status-text').innerText = "💬 讨论列表";
actionBtn.innerText = "▶ 扫描未做";
actionBtn.onclick = () => { processDiscussionListPage(); showPauseBtn(); };
// 自动模式
if(GM_getValue('UL_QUEUE_MODE', false)) showPauseBtn();
setTimeout(processDiscussionListPage, 1500);
} else if (isListPage()) {
GM_setValue('UL_LIST_URL', window.location.href);
actionBtn.innerText = "▶ 队列";
actionBtn.onclick = () => { startListQueue(); showPauseBtn(); };
if (GM_getValue('UL_QUEUE_MODE', false)) {
actionBtn.style.display = 'none'; stopBtn.style.display = 'inline-block';
showPauseBtn();
setTimeout(processListPage, 2000);
}
} else if (isResultPage()) {
document.querySelector('#status-text').innerText = "✅ 作业已完成";
actionBtn.innerText = "↩️ 返回";
actionBtn.onclick = goBackToList;
if (GM_getValue('UL_QUEUE_MODE', false)) setTimeout(goBackToList, 3000);
} else if (isPotentialQuizPage()) {
actionBtn.innerText = "▶ 答题";
actionBtn.onclick = () => { startQuiz(false); showPauseBtn(); };
setTimeout(() => { startQuiz(true); showPauseBtn(); }, 1500);
}
}
// ================= 暂停逻辑 =================
function showPauseBtn() {
document.querySelector('#btn-pause').style.display = 'inline-block';
}
function togglePause() {
isPaused = !isPaused;
const btn = document.querySelector('#btn-pause');
const status = document.querySelector('#status-text');
if (isPaused) {
btn.innerText = "▶";
btn.style.background = "#27ae60";
status.innerHTML = `<span style="color:#f39c12">⏸ 已暂停</span>`;
} else {
btn.innerText = "⏸";
btn.style.background = "";
status.innerHTML = "⚡ 继续运行...";
// 根据当前页面类型恢复执行
if (isDiscussionDetailPage()) processDiscussionReply();
else if (isDiscussionListPage()) processDiscussionListPage();
else if (isListPage()) processListPage();
else if (isPotentialQuizPage()) processNextQuizItem();
}
}
// ================= 核心功能1:讨论详情页自动回复 =================
function processDiscussionReply() {
if (!isDiscussionDetailPage() || hasAutoReplied) return;
if (isPaused) return; // 暂停检查
const logDiv = document.querySelector('#ai-log');
const statusText = document.querySelector('#status-text');
if (!CONFIG.aiBaseUrl) {
if(logDiv) logDiv.innerHTML += `<div class="reasoning" style="color:red">⚠️ 未配置AI,无法自动回复。请在设置中配置API。</div>`;
return;
}
hasAutoReplied = true; // 锁定防止重复
statusText.innerText = "🧠 AI 生成观点中...";
if(logDiv) logDiv.innerHTML = '<div class="reasoning">正在提取题目...</div>';
// 提取题目
const titleEl = document.querySelector(CONFIG.selectors.discussionDetailTitle);
let promptText = "请就以下话题发表一段深刻的、正能量的、符合大学生身份的观点(150字左右):\n";
if (titleEl) {
promptText += titleEl.innerText.trim();
} else {
promptText += "未知话题";
}
const payload = { prompt: promptText };
GM_xmlhttpRequest({
method: "POST",
url: CONFIG.aiBaseUrl,
headers: { "Content-Type": "application/json" },
data: JSON.stringify(payload),
responseType: 'text',
onload: function(response) {
// 处理流式或非流式响应
let answer = "";
if (response.responseText.includes("data: ")) {
const chunks = response.responseText.split('data: ');
chunks.forEach(c => {
if(!c.trim() || c.includes('[DONE]')) return;
try { answer += JSON.parse(c).choices[0].delta.content || ""; } catch(e){}
});
} else {
try {
const json = JSON.parse(response.responseText);
answer = json.choices[0].message.content || json.response;
} catch(e) { answer = response.responseText; }
}
if (!answer) {
if(logDiv) logDiv.innerHTML += `<div class="reasoning" style="color:red">AI 响应为空</div>`;
return;
}
// 👇👇👇 净化 AI 答案:删除 "XXX字" 相关的描述 👇👇👇
answer = answer.replace(/[((\[]?\s*\d+\s*字(左右)?\s*[))\]]?[::]?/g, "").trim();
if(logDiv) logDiv.innerHTML += `<div class="answer">${answer}</div>`;
statusText.innerText = "✍️ 正在写入编辑器...";
// 写入 CKEditor (需要 unsafeWindow)
try {
if (unsafeWindow.CKEDITOR && unsafeWindow.CKEDITOR.instances && unsafeWindow.CKEDITOR.instances.editorContainer) {
unsafeWindow.CKEDITOR.instances.editorContainer.setData(answer);
if(logDiv) logDiv.innerHTML += `<div class="reasoning">写入成功,2秒后提交...</div>`;
setTimeout(() => {
if (isPaused) return; // 提交前检查暂停
const submitBtn = document.querySelector(CONFIG.selectors.discussionSubmitBtn);
if (submitBtn) {
submitBtn.click();
statusText.innerText = "✅ 已提交";
if(logDiv) logDiv.innerHTML += `<div class="reasoning" style="color:green">已点击提交。2秒后自动关闭...</div>`;
setTimeout(() => {
if (isPaused) return; // 关闭前检查暂停
// 【核心逻辑】完成任务:设置标志位 + 关闭窗口
GM_setValue('UL_DISCUSSION_STATE', 'finished');
window.close();
// 兜底:如果无法关闭(比如同一个标签页打开的),尝试返回
setTimeout(() => {
statusText.innerText = "⚠️ 窗口未关闭,尝试返回...";
goBackToList();
}, 500);
}, 2000);
} else {
if(logDiv) logDiv.innerHTML += `<div class="reasoning" style="color:red">未找到提交按钮</div>`;
}
}, 2000);
} else {
if(logDiv) logDiv.innerHTML += `<div class="reasoning" style="color:red">CKEditor 对象未找到,无法自动写入。</div>`;
}
} catch (e) {
console.error(e);
if(logDiv) logDiv.innerHTML += `<div class="reasoning" style="color:red">写入出错: ${e.message}</div>`;
}
},
onerror: () => {
if(logDiv) logDiv.innerHTML += `<div class="reasoning" style="color:red">AI 请求失败</div>`;
}
});
}
// ================= 核心功能2:讨论列表自动化 =================
function processDiscussionListPage() {
if (!isDiscussionListPage()) return;
if (isPaused) return; // 暂停检查
// 【关键】保存当前列表页地址
GM_setValue('UL_LIST_URL', window.location.href);
const logDiv = document.querySelector('#ai-log');
const statusText = document.querySelector('#status-text');
statusText.innerText = "🔍 扫描未做讨论...";
if(logDiv) logDiv.innerHTML = '<div class="reasoning">正在分析列表...</div>';
const rows = Array.from(document.querySelectorAll(CONFIG.selectors.discussionRow));
let targetBtn = null;
let targetTitle = "";
for (let row of rows) {
const myCountEl = row.querySelector(CONFIG.selectors.myPostCount);
if (myCountEl) {
const count = parseInt(myCountEl.innerText.trim());
// 只有当帖子数为 0 时才进入
if (!isNaN(count) && count === 0) {
const btn = row.querySelector(CONFIG.selectors.viewDiscussionBtn);
const titleEl = row.querySelector('.discussion-name .text span');
if (btn && btn.innerText.includes("查看")) {
targetBtn = btn;
targetTitle = titleEl ? titleEl.innerText : "未知主题";
break;
}
}
}
}
if (targetBtn) {
GM_setValue('UL_QUEUE_MODE', true); // 确保进入详情页后知道是自动模式
GM_setValue('UL_DISCUSSION_STATE', 'processing'); // 标记为正在处理
statusText.innerText = "⚡ 进入讨论 (完成后自动刷新)...";
if(logDiv) logDiv.innerHTML += `<div class="answer" style="color:#e67e22">发现未做讨论:${targetTitle}</div>`;
// 【核心逻辑】开启轮询,等待子标签页完成任务
const checkTimer = setInterval(() => {
if (isPaused) return; // 轮询中检查暂停,但不清除定时器,等待恢复
const state = GM_getValue('UL_DISCUSSION_STATE');
if (state === 'finished') {
clearInterval(checkTimer);
GM_setValue('UL_DISCUSSION_STATE', 'idle'); // 重置状态
statusText.innerText = "✅ 检测到任务完成,正在刷新...";
if(logDiv) logDiv.innerHTML += `<div class="reasoning" style="color:green">任务完成,刷新列表...</div>`;
setTimeout(() => location.reload(), 1000); // 刷新页面
}
}, 1000);
setTimeout(() => {
if(!isPaused) targetBtn.click();
}, 2000);
} else {
statusText.innerText = "✅ 本页已完成";
const nextBtn = document.querySelector(CONFIG.selectors.nextPageBtn);
if (nextBtn && !nextBtn.classList.contains('disabled') && nextBtn.style.display !== 'none') {
if(logDiv) logDiv.innerHTML += `<div class="reasoning">自动翻页中...</div>`;
setTimeout(() => {
if(!isPaused) { nextBtn.click(); setTimeout(processDiscussionListPage, 3000); }
}, 2000);
} else {
if(logDiv) logDiv.innerHTML += `<div class="reasoning" style="color:green; font-weight:bold">🎉 所有讨论均已完成!</div>`;
GM_setValue('UL_QUEUE_MODE', false);
}
}
}
// ================= 列表页逻辑 =================
function startListQueue() {
GM_setValue('UL_LIST_URL', window.location.href);
GM_setValue('UL_QUEUE_MODE', true);
location.reload();
}
function processListPage() {
if (!isListPage() || !GM_getValue('UL_QUEUE_MODE', false)) return;
if (isPaused) return; // 暂停检查
const btns = Array.from(document.querySelectorAll(CONFIG.selectors.writeBtn));
const todoBtns = btns.filter(btn => {
const txt = btn.innerText;
if (!txt.includes("写作业") && !txt.includes("继续")) return false;
if (!CONFIG.aiBaseUrl) {
const row = btn.closest('.tr') || btn.closest('li');
const titleEl = row ? row.querySelector(CONFIG.selectors.itemName) : null;
if (titleEl && titleEl.innerText.includes("主观题")) return false;
}
return true;
});
if (todoBtns.length > 0) {
document.querySelector('#status-text').innerText = `发现 ${todoBtns.length} 个作业`;
setTimeout(() => { if (!isPaused) todoBtns[0].click(); }, 3000);
} else {
const nextBtn = document.querySelector(CONFIG.selectors.nextPageBtn);
if (nextBtn && !nextBtn.classList.contains('disabled')) {
setTimeout(() => { if(!isPaused) nextBtn.click(); setTimeout(processListPage, 3000); }, 2000);
} else {
GM_setValue('UL_QUEUE_MODE', false);
document.querySelector('#status-text').innerText = "全部完成";
}
}
}
// ================= 答题页提交逻辑 =================
function checkAndSubmit() {
const progressEl = document.querySelector(CONFIG.selectors.bottomBarNum);
let isFinished = false;
if (progressEl) {
const match = progressEl.innerText.match(/(\d+)\s*\/\s*(\d+)/);
if (match && parseInt(match[1]) >= parseInt(match[2])) isFinished = true;
}
if (isFinished) {
const submitBtn = Array.from(document.querySelectorAll(CONFIG.selectors.submitBtn)).find(b => b.innerText.includes("提交"));
if (submitBtn) {
submitBtn.click();
setTimeout(() => {
if (isPaused) return; // 暂停检查
const confirmBtn = Array.from(document.querySelectorAll(CONFIG.selectors.modalConfirmBtn)).find(b => b.innerText.includes("确定"));
if (confirmBtn) {
confirmBtn.click();
document.querySelector('#ai-log').innerHTML += `<div class="reasoning">提交成功,准备返回...</div>`;
setTimeout(goBackToList, 3000);
}
}, 1000);
return true;
}
}
return false;
}
function goBackToList() {
const savedUrl = GM_getValue('UL_LIST_URL');
if (savedUrl && savedUrl.includes('ulearning')) {
window.location.href = savedUrl;
return;
}
const backBtn = document.querySelector(CONFIG.selectors.headerBackBtn);
if (backBtn) { backBtn.click(); return; }
window.history.back();
}
function startQuiz(isAuto) {
const startBtns = Array.from(document.querySelectorAll(CONFIG.selectors.startQuizBtn));
const realStartBtn = startBtns.find(b => b.innerText.includes("开始答题"));
if (realStartBtn) { realStartBtn.click(); setTimeout(() => startQuiz(isAuto), 2000); return; }
questionsList = Array.from(document.querySelectorAll(CONFIG.selectors.questionContainer));
if (questionsList.length === 0) return;
isRunning = true;
document.querySelector('#btn-action').style.display = 'none';
document.querySelector('#btn-stop').style.display = 'inline-block';
fetchStandardAnswers(() => processNextQuizItem());
}
function processNextQuizItem() {
if (!isRunning || isPaused) return; // 暂停检查
if (currentIndex >= questionsList.length) {
if(!checkAndSubmit()) setTimeout(goBackToList, 2000);
return;
}
const container = questionsList[currentIndex];
container.scrollIntoView({ behavior: "smooth", block: "center" });
if (isQuestionAnswered(container)) { currentIndex++; setTimeout(processNextQuizItem, 500); return; }
const apiResult = getApiAnswerForQuestion(currentIndex);
if (apiResult) {
if (apiResult[0] === 'true' || apiResult[0] === 'false') autoSelectJudge(apiResult[0], container);
else autoSelectChoices(apiResult.join(" "), container);
currentIndex++; setTimeout(processNextQuizItem, 1000);
} else if (CONFIG.aiBaseUrl) {
callAI(container, (ans) => {
autoFillText(cleanMarkdown(ans), container);
currentIndex++; setTimeout(processNextQuizItem, 3000);
});
} else {
currentIndex++; setTimeout(processNextQuizItem, 1000);
}
}
function getApiAnswerForQuestion(index) {
if (!HAS_FETCHED_API) return null;
return API_ANSWERS[`INDEX_${index}`] || null;
}
function isQuestionAnswered(container) {
const input = container.querySelector(CONFIG.selectors.textInput);
if (input && input.value.trim()) return true;
return container.querySelectorAll(CONFIG.selectors.checkedSelector).length > 0;
}
function autoSelectJudge(ans, container) {
const sel = ans === 'true' || ans.includes('正确') ? CONFIG.selectors.judgeTrueIcon : CONFIG.selectors.judgeFalseIcon;
container.querySelector(sel)?.closest('.ul-radio')?.click();
}
async function autoSelectChoices(ansStr, container) {
const choices = container.querySelectorAll(CONFIG.selectors.choiceItem);
for (let c of choices) {
const idx = c.querySelector(CONFIG.selectors.choiceIndex)?.innerText.trim().replace('.','');
if (idx && ansStr.toUpperCase().includes(idx)) c.querySelector(CONFIG.selectors.choiceClickArea)?.click();
}
}
function callAI(container, callback) {
const qElem = container.querySelector(CONFIG.selectors.question);
if (!qElem) return;
const prompt = `题目:${qElem.innerText.trim()}\n请直接给出答案,不要解释。`;
GM_xmlhttpRequest({
method: "POST",
url: CONFIG.aiBaseUrl,
headers: { "Content-Type": "application/json" },
data: JSON.stringify({ prompt: prompt }),
onload: function(response) {
let fullText = "";
if (response.responseText.includes("data: ")) {
const chunks = response.responseText.split('data: ');
chunks.forEach(c => {
if(!c.trim() || c.includes('[DONE]')) return;
try { fullText += JSON.parse(c).choices[0].delta.content || ""; } catch(e){}
});
} else {
try { fullText = JSON.parse(response.responseText).choices[0].message.content; } catch(e){ fullText = response.responseText; }
}
callback(fullText);
}
});
}
function cleanMarkdown(text) { return text.replace(/\*\*/g, "").replace(/#/g, "").trim(); }
function autoFillText(text, container) {
const input = container.querySelector(CONFIG.selectors.textInput);
if (input) { input.value = text; input.dispatchEvent(new Event('input')); input.dispatchEvent(new Event('blur')); }
}
window.addEventListener('load', () => setTimeout(() => { createUI(); checkFirstRun(); }, 2000));
})();