// ==UserScript== // @name 学习通AI自动打分脚本 // @namespace http://tampermonkey.net/ // @version 2.0 // @description 使用DeepSeek AI自动批改图片答案 // @author jjw // @match https://mooc2-ans.chaoxing.com/mooc2-ans/work/library/review-work* // @grant GM_xmlhttpRequest // @grant GM_getValue // @grant GM_setValue // ==/UserScript== (function() { 'use strict'; const CONFIG = { apiKey: 'sk-08630653fc1c40399e86a193cbc8f5b6', apiUrl: 'https://api.deepseek.com/v1/chat/completions', model: 'deepseek-chat', autoSubmit: true, reReview: false, processingDelay: 2000, }; console.log("🚀 AI批改脚本已启动!" + window.location.href); function fetchImageAsBase64(url) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url: url, responseType: 'blob', timeout: 15000, onload: function(response) { var reader = new FileReader(); reader.onloadend = function() { resolve(reader.result); }; reader.onerror = function(e) { reject(new Error('FileReader failed')); }; reader.readAsDataURL(response.response); }, onerror: function(e) { reject(new Error('图片加载失败: ' + url)); }, ontimeout: function() { reject(new Error('图片加载超时: ' + url)); } }); }); } function getAnswerImages(questionItem) { var images = questionItem.querySelectorAll('img.ans-ued-img'); var srcs = []; for (var i = 0; i < images.length; i++) { if (images[i].src && images[i].src.trim()) { srcs.push(images[i].src.trim()); } } if (srcs.length === 0) { var iframe = questionItem.querySelector('iframe.bbsContainer'); if (iframe) { var answerStr = iframe.getAttribute('answer') || ''; var re = /]+src="([^"]+)"/gi; var m; while ((m = re.exec(answerStr)) !== null) { srcs.push(m[1]); } } } return srcs; } function getQuestionText(questionItem) { var h3 = questionItem.querySelector('h3.mark_name'); if (!h3) return '无题目描述'; var hiddenTitle = h3.querySelector('.hiddenTitle'); if (hiddenTitle) { return hiddenTitle.textContent.replace(/\s+/g, ' ').trim() || '无题目描述'; } var clone = h3.cloneNode(true); var shallow = clone.querySelector('.colorShallow'); if (shallow) shallow.remove(); return clone.textContent.replace(/^\d+\.\s*/, '').replace(/\s+/g, ' ').trim() || '无题目描述'; } function getMaxScore(questionItem) { var input = questionItem.querySelector('input.questionScore'); if (!input) return 10; var placeholder = input.getAttribute('placeholder') || ''; var match = placeholder.match(/(\d+)/); if (match) { var max = parseFloat(match[match.length - 1]); return max || 10; } return 10; } function getScoreInput(questionItem) { return questionItem.querySelector('input.questionScore'); } function callDeepSeek(questionText, imageBase64List, maxScore) { var content = [ { type: 'text', text: '你是一位严格的作业批改老师。请根据以下题目要求和学生提交的答案截图(可能有多张),评判答案的正确性和完整程度。\n\n【题目要求】\n' + questionText + '\n\n【评分规则】\n- 满分: ' + maxScore + '分\n- 完全正确且完整: 给' + maxScore + '分\n- 基本正确但有小瑕疵: 给满分的70%-90%\n- 部分正确: 给满分的40%-70%\n- 基本不对: 给满分的10%-40%\n- 完全错误或未作答: 给0分\n\n请只回复一个数字作为分数,不要包含任何其他文字。' } ]; for (var i = 0; i < imageBase64List.length; i++) { content.push({ type: 'image_url', image_url: { url: imageBase64List[i] } }); } return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'POST', url: CONFIG.apiUrl, headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + CONFIG.apiKey }, data: JSON.stringify({ model: CONFIG.model, messages: [ { role: 'user', content: content } ], max_tokens: 100, temperature: 0.1 }), timeout: 60000, onload: function(response) { try { var data = JSON.parse(response.responseText); if (data.error) { reject(new Error('API错误: ' + (data.error.message || JSON.stringify(data.error)))); return; } if (data.choices && data.choices[0]) { var text = data.choices[0].message.content.trim(); var scoreMatch = text.match(/(\d+(\.\d+)?)/); if (scoreMatch) { resolve(parseFloat(scoreMatch[1])); } else { console.warn('无法从回复中提取分数: "' + text + '"'); resolve(null); } } else { reject(new Error('API返回格式异常: ' + JSON.stringify(data))); } } catch(e) { reject(e); } }, onerror: function(e) { reject(new Error('API请求失败')); }, ontimeout: function() { reject(new Error('API请求超时')); } }); }); } function fillScore(input, score) { input.value = score; input.dispatchEvent(new Event('input', { bubbles: true })); if (input.onkeyup) input.onkeyup(); if (input.onchange) input.onchange(); if (input.onblur) input.onblur(); input.dispatchEvent(new Event('blur', { bubbles: true })); } function sleep(ms) { return new Promise(function(resolve) { setTimeout(resolve, ms); }); } async function processAllQuestions() { var items = document.querySelectorAll('.mark_item1'); if (items.length === 0) { console.log('⏳ 未检测到题目,3秒后重试...'); await sleep(3000); items = document.querySelectorAll('.mark_item1'); if (items.length === 0) { console.log('❌ 仍未找到题目,请刷新页面重试'); return; } } console.log('📋 共检测到 ' + items.length + ' 道题目'); var scoredCount = 0; var skipCount = 0; var failCount = 0; var noImageCount = 0; for (var i = 0; i < items.length; i++) { var item = items[i]; var input = getScoreInput(item); if (!input) continue; if (!CONFIG.reReview && input.value !== '') { skipCount++; continue; } var maxScore = getMaxScore(item); var questionText = getQuestionText(item); var imageUrls = getAnswerImages(item); console.log('📝 [' + (i + 1) + '/' + items.length + '] 正在评分: "' + questionText.substring(0, 40) + '..." (满分' + maxScore + '分)'); if (imageUrls.length === 0) { console.warn(' ⚠️ 未找到答案图片,跳过此题'); noImageCount++; continue; } try { console.log(' 🔄 加载 ' + imageUrls.length + ' 张图片...'); var base64Images = []; for (var j = 0; j < imageUrls.length; j++) { try { var b64 = await fetchImageAsBase64(imageUrls[j]); base64Images.push(b64); } catch(imgErr) { console.warn(' 图片 ' + (j + 1) + ' 加载失败: ' + imgErr.message); } } if (base64Images.length === 0) { console.warn(' ⚠️ 图片全部加载失败,跳过此题'); noImageCount++; continue; } console.log(' 🤖 正在AI评分...'); var score = await callDeepSeek(questionText, base64Images, maxScore); if (score !== null && score !== undefined) { var finalScore = Math.max(0, Math.min(Math.round(score), maxScore)); console.log(' ✅ AI评分: ' + finalScore + '/' + maxScore + ' 分'); fillScore(input, finalScore); scoredCount++; } else { console.warn(' ❌ AI未能返回有效分数'); failCount++; } } catch (err) { console.error(' ❌ 处理出错: ' + (err.message || err)); failCount++; } await sleep(CONFIG.processingDelay); } console.log('--- 批改完成 ---'); console.log(' 已评分: ' + scoredCount + ' | 跳过: ' + skipCount + ' | 失败: ' + failCount + ' | 无图片: ' + noImageCount); if (CONFIG.autoSubmit) { await sleep(1000); var submitBtn = document.querySelector('a[onclick="markAction(0)"]'); if (submitBtn) { console.log('>>> 自动点击提交...'); submitBtn.click(); } else { console.log('❌ 没找到提交按钮,请手动提交'); } } } setTimeout(async function() { await processAllQuestions(); }, 2000); })();