【使用前先看介绍/有问题可反馈】UOOC 优课联盟助手:视频助手(倍速/静音/播放/连播)+ AI智能答题。点击⚙️配置API,勾选"LLM答题"后,点击"开始答题"即可。控制台可自动收起,鼠标移开后自动隐藏。键盘←→控制快进/快退,↑↓控制音量,空格控制播放/暂停。
// ==UserScript==
// @name UOOC assistant
// @namespace http://tampermonkey.net/
// @version 1.0.8
// @description 【使用前先看介绍/有问题可反馈】UOOC 优课联盟助手:视频助手(倍速/静音/播放/连播)+ AI智能答题。点击⚙️配置API,勾选"LLM答题"后,点击"开始答题"即可。控制台可自动收起,鼠标移开后自动隐藏。键盘←→控制快进/快退,↑↓控制音量,空格控制播放/暂停。
// @author cc & wybbb1
// @include https://www.uooc.net.cn/home/learn/index*
// @include https://www.uooc.net.cn/home/course/exam/*
// @include https://www.uooc.net.cn/home/exam/*
// @include https://www.uooc.net.cn/exam/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_notification
// @run-at document-idle
// @license MIT
// ==/UserScript==
(function() {
'use strict';
console.log('[UOOC助手] 脚本开始加载...');
// 全局LLM启用状态
window.llmEnabled = false;
// ==================== LLM配置管理模块 ====================
const LLMConfig = {
get: function() {
try {
return GM_getValue('llm_config', null);
} catch (e) {
console.log('[UOOC助手] GM_getValue不可用,使用localStorage');
return JSON.parse(localStorage.getItem('llm_config') || 'null');
}
},
save: function(config) {
try {
GM_setValue('llm_config', config);
} catch (e) {
console.log('[UOOC助手] GM_setValue不可用,使用localStorage');
localStorage.setItem('llm_config', JSON.stringify(config));
}
},
showConfigUI: function() {
const config = this.get() || { baseUrl: '', apiKey: '', model: '' };
const dialog = document.createElement('div');
dialog.id = 'llm-config-dialog';
dialog.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 20px;
border-radius: 10px;
box-shadow: 0 0 20px rgba(0,0,0,0.5);
z-index: 999999;
min-width: 400px;
font-family: Arial, sans-serif;
`;
dialog.innerHTML = `
<h3 style="margin: 0 0 15px 0; color: #333;">AI答题配置</h3>
<div style="margin-bottom: 10px;">
<label style="display: block; margin-bottom: 5px; color: #666;">API Base URL:</label>
<input type="text" id="llm-baseurl" value="${config.baseUrl || ''}"
placeholder="例如: https://api.openai.com/v1"
style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box;">
</div>
<div style="margin-bottom: 10px;">
<label style="display: block; margin-bottom: 5px; color: #666;">API Key:</label>
<input type="password" id="llm-apikey" value="${config.apiKey || ''}"
placeholder="输入你的API Key"
style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box;">
</div>
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px; color: #666;">模型名称 (可选):</label>
<input type="text" id="llm-model" value="${config.model || ''}"
placeholder="例如: gpt-3.5-turbo (不填则使用默认)"
style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box;">
</div>
<div style="text-align: right;">
<button id="llm-config-cancel" style="padding: 8px 20px; margin-right: 10px; border: 1px solid #ddd; background: white; border-radius: 4px; cursor: pointer;">取消</button>
<button id="llm-config-save" style="padding: 8px 20px; background: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer;">保存</button>
</div>
<div style="margin-top: 10px; padding-top: 10px; border-top: 1px solid #eee; font-size: 11px; color: #999;">
提示: 配置会保存在本地,只需配置一次。支持OpenAI兼容格式的API。
</div>
`;
document.body.appendChild(dialog);
document.getElementById('llm-config-cancel').onclick = function() {
document.body.removeChild(dialog);
};
document.getElementById('llm-config-save').onclick = function() {
const baseUrl = document.getElementById('llm-baseurl').value.trim();
const apiKey = document.getElementById('llm-apikey').value.trim();
const model = document.getElementById('llm-model').value.trim();
if (!baseUrl || !apiKey) {
alert('请填写完整的API配置!');
return;
}
LLMConfig.save({ baseUrl, apiKey, model });
document.body.removeChild(dialog);
alert('AI答题配置已保存!');
};
}
};
// ==================== 测评页面功能模块 ====================
// 检测是否在测评页面
function isQuizPage() {
// 检查主页面
if (document.querySelector('.queContainer') || document.querySelector('#examMain')) {
return true;
}
// 检查iframe
const iframe = document.querySelector('iframe');
if (iframe) {
try {
const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
return iframeDoc.querySelector('.queContainer') || iframeDoc.querySelector('#examMain');
} catch (e) {
return false;
}
}
return false;
}
// 获取测评页面的document(可能是iframe)
function getQuizDocument() {
// 先检查主页面
const mainDocContainers = document.querySelectorAll('.queContainer');
if (mainDocContainers.length > 0) {
console.log('[UOOC助手-AI] 在主文档中找到题目');
return document;
}
// 检查iframe
const iframe = document.querySelector('iframe');
if (iframe) {
try {
const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
const iframeContainers = iframeDoc.querySelectorAll('.queContainer');
if (iframeContainers.length > 0) {
console.log('[UOOC助手-AI] 在iframe中找到题目');
return iframeDoc;
}
} catch (e) {
console.log('[UOOC助手-AI] 无法访问iframe:', e.message);
}
}
return null;
}
// 提取题目
function extractQuestions() {
console.log('[UOOC助手-AI] 开始提取题目...');
// 获取测评页面的document
const quizDoc = getQuizDocument();
if (!quizDoc) {
console.log('[UOOC助手-AI] 未找到测评页面document');
return [];
}
// 调试:检查页面元素
const containers = quizDoc.querySelectorAll('.queContainer');
console.log('[UOOC助手-AI] 找到 .queContainer 元素数量:', containers.length);
// 如果没找到
if (containers.length === 0) {
console.log('[UOOC助手-AI] 未找到题目容器');
console.log('[UOOC助手-AI] 当前URL:', window.location.href);
return [];
}
const questions = [];
containers.forEach((container, index) => {
console.log(`[UOOC助手-AI] 处理第 ${index + 1} 个容器`);
const typeElem = container.querySelector('input[type="radio"], input[type="checkbox"]');
if (!typeElem) {
console.log(`[UOOC助手-AI] 第 ${index + 1} 个容器:未找到 input 元素`);
return;
}
const isRadio = typeElem.type === 'radio';
const questionText = container.querySelector('.ti-q-c')?.innerText.trim() || '';
const options = [];
container.querySelectorAll('.ti-a').forEach((optElem) => {
const optionLabel = optElem.querySelector('.ti-a-i')?.innerText.trim() || '';
const optionText = optElem.querySelector('.ti-a-c')?.innerText.trim() || '';
const inputElem = optElem.querySelector('input');
options.push({
label: optionLabel,
text: optionText,
value: inputElem?.value || '',
input: inputElem
});
});
let questionType = '判断题';
if (options.length > 2) {
questionType = isRadio ? '单选题' : '多选题';
} else if (options.length === 2) {
questionType = '判断题';
}
console.log(`[UOOC助手-AI] 第 ${index + 1} 题:${questionType},选项数量: ${options.length}`);
questions.push({
index: index + 1,
type: questionType,
question: questionText,
options: options,
isRadio: isRadio
});
});
console.log('[UOOC助手-AI] 提取到', questions.length, '道题目');
return questions;
}
// 构建LLM提示词
function buildPrompt(questions) {
let prompt = '请回答以下选择题,每题直接给出答案选项字母(如A、B、C、D或A、B等),不需要解释。\n\n';
questions.forEach(q => {
prompt += `第${q.index}题 [${q.type}]\n`;
prompt += `题目:${q.question}\n`;
q.options.forEach(opt => {
prompt += `${opt.label}. ${opt.text}\n`;
});
prompt += '\n';
});
prompt += '\n请按以下格式返回答案(每行一个题号和答案):\n';
prompt += '1. A\n2. B\n3. A,B\n...\n';
prompt += '注意:多选题答案用逗号分隔,判断题A表示正确,B表示错误。';
return prompt;
}
// 解析LLM返回的答案
function parseAnswers(llmResponse, questions) {
const answers = [];
const lines = llmResponse.split('\n');
lines.forEach(line => {
const match = line.trim().match(/^(\d+)\.\s*([A-Z,]+)$/);
if (match) {
const qIndex = parseInt(match[1]) - 1;
const answer = match[2].toUpperCase().split(',').map(a => a.trim());
answers[qIndex] = answer;
}
});
// 如果解析数量不匹配,尝试其他格式
if (answers.filter(a => a).length < questions.length) {
console.log('[UOOC助手-AI] 标准格式解析失败,尝试智能匹配...');
// 尝试从文本中提取所有答案
const allAnswers = llmResponse.match(/\d+\.[\s]*[A-Z]+/g);
if (allAnswers) {
allAnswers.forEach((ans, idx) => {
const match = ans.match(/\d+\.[\s]*([A-Z,]+)/);
if (match) {
const answer = match[1].toUpperCase().split(',').map(a => a.trim());
answers[idx] = answer;
}
});
}
}
return answers;
}
// 调用LLM API
async function callLLM(questions) {
const config = LLMConfig.get();
if (!config || !config.baseUrl || !config.apiKey) {
alert('请先配置AI API!点击页面右上角的"AI设置"按钮进行配置。');
return null;
}
console.log('[UOOC助手-AI] 开始调用LLM API...');
const prompt = buildPrompt(questions);
try {
const response = await fetch(`${config.baseUrl}/chat/completions`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${config.apiKey}`
},
body: JSON.stringify({
model: config.model || 'gpt-3.5-turbo',
messages: [
{ role: 'system', content: '你是一个专业的答题助手,请严格按照要求的格式返回答案。' },
{ role: 'user', content: prompt }
],
temperature: 0.3
})
});
if (!response.ok) {
throw new Error(`API请求失败: ${response.status} ${response.statusText}`);
}
const data = await response.json();
const answerText = data.choices[0].message.content;
console.log('[UOOC助手-AI] LLM返回:', answerText);
return parseAnswers(answerText, questions);
} catch (error) {
console.error('[UOOC助手-AI] LLM调用失败:', error);
alert(`AI答题失败: ${error.message}\n请检查API配置是否正确。`);
return null;
}
}
// 填入答案
function fillAnswers(questions, answers) {
console.log('[UOOC助手-AI] 开始填入答案...');
let filledCount = 0;
questions.forEach((q, idx) => {
const answer = answers[idx];
if (!answer) {
console.log(`[UOOC助手-AI] 第${q.index}题:未找到答案`);
return;
}
// 点击对应的选项
answer.forEach(ans => {
const option = q.options.find(opt => opt.value === ans);
if (option && option.input) {
option.input.click();
console.log(`[UOOC助手-AI] 第${q.index}题:选择 ${ans}`);
filledCount++;
}
});
});
console.log('[UOOC助手-AI] 共填入', filledCount, '个答案');
return filledCount;
}
// 主答题流程
async function autoAnswerQuiz() {
console.log('[UOOC助手-AI] ===== 开始自动答题流程 =====');
// 检查配置
const config = LLMConfig.get();
if (!config || !config.baseUrl || !config.apiKey) {
console.log('[UOOC助手-AI] 未配置API,显示配置界面');
LLMConfig.showConfigUI();
return;
}
// 提取题目
const questions = extractQuestions();
if (questions.length === 0) {
console.log('[UOOC助手-AI] 未找到题目');
return;
}
console.log(`[UOOC助手-AI] 检测到 ${questions.length} 道题目,开始AI答题...`);
// 调用LLM
const answers = await callLLM(questions);
if (!answers) return;
// 填入答案
const filledCount = fillAnswers(questions, answers);
// 提示用户检查并提交
setTimeout(function() {
alert(`✅ AI答题完成!\n\n已自动填入 ${filledCount} 个答案。\n\n请仔细检查答案后,手动点击页面左下方的"提交试卷"按钮。`);
}, 500);
}
// 在测评页面显示提示
function showQuizPageHint() {
if (document.getElementById('llm-quiz-hint')) {
return;
}
// 只有在LLM启用时才显示提示
if (!window.llmEnabled) {
console.log('[UOOC助手-AI] LLM未启用,不显示答题提示');
return;
}
console.log('[UOOC助手-AI] 检测到测评页面,LLM已启用');
// 标记已显示
const marker = document.createElement('div');
marker.id = 'llm-quiz-hint';
marker.style.display = 'none';
document.body.appendChild(marker);
// 在控制台提示
console.log('[UOOC助手-AI] 在视频页面点击"🤖 开始答题"按钮即可开始AI答题');
}
// ==================== 原有视频助手功能 ====================
function ckeckTestIgnorable() {
var cid = location.href.match(/index#\/(\d+)\//)[1];
console.log('[UOOC助手] 课程ID:', cid);
$.ajax({
url: 'https://www.uooc.net.cn/home/learn/getCourseLearn',
type: 'GET',
data: { cid: cid },
success: function(res) {
window.canIgnoreTest = Boolean(res.data.course_learn_mode === '20');
console.log('[UOOC助手] 可忽略测验:', window.canIgnoreTest);
},
error: function(err) {
console.log('[UOOC助手] 获取课程学习模式失败,使用默认设置');
window.canIgnoreTest = false;
}
});
}
function bindChapterChange() {
function bindSubChapterChange() {
var sourceView = document.querySelector('[source-view]');
if (!sourceView) return;
var sObserver = new MutationObserver(function(mutations) {
if (document.querySelector('[source-view] [uooc-video] video')) {
console.log('[UOOC助手] 检测到视频变化');
setTimeout(start, 250);
}
});
sObserver.observe(sourceView, { childList: true });
}
var mainLeft = document.querySelector('.learn-main-left');
if (!mainLeft) {
console.log('[UOOC助手] 未找到.learn-main-left元素');
return;
}
var mObserver = new MutationObserver(function(mutations) {
bindSubChapterChange();
});
mObserver.observe(mainLeft, { childList: true });
bindSubChapterChange();
}
function autoQuiz() {
function autoQuizAnswer() {
try {
let quizLayer = document.getElementById('quizLayer');
let sourceDiv = document.querySelector('div[uooc-video]');
if (!quizLayer || !sourceDiv) return;
let source = JSON.parse(sourceDiv.getAttribute('source'));
let quizQuestion = document.querySelector('.smallTest-view .ti-q-c');
if (!quizQuestion) return;
let quizQuestionText = quizQuestion.innerHTML;
let quizData = source.quiz.find(q => q.question === quizQuestionText);
if (!quizData) return;
let quizAnswer = quizData.answer;
let quizOptions = quizLayer.querySelector('div.ti-alist');
if (!quizOptions) return;
for (let ans of eval(quizAnswer)) {
if (quizOptions.children[ans.charCodeAt() - 'A'.charCodeAt()]) {
quizOptions.children[ans.charCodeAt() - 'A'.charCodeAt()].click();
}
}
let submitBtn = quizLayer.querySelector('button');
if (submitBtn) submitBtn.click();
console.log('[UOOC助手] 自动答题完成');
} catch (err) {
console.error('[UOOC助手] 自动答题出错:', err);
}
}
var learnView = document.querySelector('.lean_view');
if (!learnView) return;
var observer = new MutationObserver(function(mutations) {
for (let mutation of mutations) {
let node = mutation.addedNodes[0];
if (node && node.id && node.id.includes('layui-layer')) {
console.log('[UOOC助手] 检测到测验弹窗');
autoQuizAnswer();
break;
}
}
});
observer.observe(learnView, { childList: true });
}
function bindVideoEvents() {
var video = document.querySelector('#player_html5_api');
if (!video) {
console.log('[UOOC助手] 未找到视频元素');
return;
}
console.log('[UOOC助手] 绑定视频事件');
video.onpause = function() { if (document.getElementById('play') && document.getElementById('play').checked) this.play(); };
video.onended = findNextVideo;
}
function start() {
console.log('[UOOC助手] start() 函数执行');
bindKeyboardEvents();
bindVideoEvents();
autoQuiz();
var video = document.getElementById('player_html5_api');
var volume = document.getElementById('volume');
var play = document.getElementById('play');
var rate = document.getElementById('rate');
if (!video) {
console.log('[UOOC助手] start() 中未找到视频元素');
return;
}
if (volume && volume.checked) video.muted = true;
if (play && play.checked && video.paused) {
console.log('[UOOC助手] 自动播放视频');
video.play();
}
if (rate && rate.checked) {
console.log('[UOOC助手] 设置2倍速');
video.playbackRate = 2.0;
}
}
function placeComponents() {
console.log('[UOOC助手] 开始放置UI组件');
function copyToClipboard(content) {
var t = document.createElement('textarea');
t.value = content;
document.body.appendChild(t);
t.select();
document.execCommand('copy');
document.body.removeChild(t);
}
function getCheckbox(name, text) {
var p = document.createElement('p');
p.style = 'color: #ccc; padding-left: 10px;';
var checkbox = document.createElement('input');
checkbox.id = checkbox.name = checkbox.value = name;
checkbox.type = 'checkbox';
checkbox.checked = true;
checkbox.style = 'margin-left: 15px; width: 12px; height: 12px;';
p.append(checkbox);
var label = document.createElement('label');
label.htmlFor = name;
label.innerText = text;
label.style = 'margin-left: 13px; font-size: 12px;';
p.append(label);
return p;
}
function getContainer(_id) {
var container = document.createElement('div');
container.id = _id;
container.style = 'display: flex; flex-direction: row; align-items: center;';
return container;
}
function getCopyButton() {
var copyButton = document.createElement('p');
copyButton.style = 'color: #ccc; padding-left: 10px;';
var btn = document.createElement('button');
btn.innerText = '复制题目答案';
btn.style = 'margin-left: 13px; padding: 0 5px 0; font-size: 12px; cursor: pointer;';
btn.onclick = function() {
try {
var testPaperTop = frames[0] ? frames[0].document.querySelector('.testPaper-Top') : document.querySelector('.testPaper-Top');
if (!testPaperTop) {
alert('该页面不是测验页面,无法复制内容');
} else {
if (testPaperTop.querySelector('.fl_right')) {
var queItems = frames[0] ? Array.from(frames[0].document.querySelectorAll('.queItems')) : Array.from(document.querySelectorAll('.queItems'));
var content = queItems.map(queType => {
var res = '';
if (queType.querySelector('.queItems-type').innerText.indexOf('选') >= 0) {
var questions = queType.querySelectorAll('.queContainer');
res += Array.from(questions).map((question) => {
var que = question.querySelector('.queBox').innerText.replace(/\n{2,}/g, '\n').replace(/(\w\.)\n/g, '$1 ');
var ans = question.querySelector('.answerBox div:first-child').innerText.replace(/\n/g, '');
var scoresDiv = question.querySelector('.scores');
var right = false;
if (scoresDiv) {
var match = scoresDiv.innerText.match(/\d+\.?\d+/g);
if (match) {
var right = match.map(score => eval(score));
right = right[0] === right[1];
}
}
return `${que}\n${ans}\n是否正确:${right}\n`;
}).join('\n');
}
return res;
}).join('\n');
copyToClipboard(content);
alert('题目及答案已复制到剪切板');
} else {
alert('该测验可能还没提交,无法复制');
}
}
} catch (err) {
alert('复制出错:' + err.message);
}
};
// copyButton.appendChild(btn);
return copyButton;
}
function setCheckboxes(container) {
var rateCheckbox = getCheckbox('rate', '倍速');
var volumeCheckbox = getCheckbox('volume', '静音');
var playCheckbox = getCheckbox('play', '播放');
var continueCheckbox = getCheckbox('continue', '连播');
var copyButton = getCopyButton();
var video = document.getElementById('player_html5_api');
// 创建LLM答题复选框和设置按钮
var llmContainer = document.createElement('p');
llmContainer.style = 'color: #ccc; padding-left: 10px; display: flex; align-items: center;';
// 创建设置按钮(齿轮图标)
var settingBtn = document.createElement('span');
settingBtn.innerHTML = '⚙️';
settingBtn.style = 'margin-left: 15px; font-size: 16px; cursor: pointer; margin-right: 5px;';
settingBtn.title = '配置AI答题参数';
settingBtn.onclick = function() {
LLMConfig.showConfigUI();
};
// 创建LLM复选框
var llmCheckbox = document.createElement('input');
llmCheckbox.id = llmCheckbox.name = llmCheckbox.value = 'llm';
llmCheckbox.type = 'checkbox';
llmCheckbox.checked = window.llmEnabled;
llmCheckbox.style = 'width: 12px; height: 12px;';
llmCheckbox.onchange = function(event) {
window.llmEnabled = event.target.checked;
console.log('[UOOC助手] LLM答题', window.llmEnabled ? '已启用' : '已禁用');
if (window.llmEnabled) {
// 检查配置
const config = LLMConfig.get();
if (!config || !config.baseUrl || !config.apiKey) {
alert('LLM答题已启用,但尚未配置API参数!\n\n请点击⚙️图标进行配置。');
setTimeout(function() {
LLMConfig.showConfigUI();
}, 300);
}
}
// 更新答题按钮状态
updateAnswerButtonState();
};
var llmLabel = document.createElement('label');
llmLabel.htmlFor = 'llm';
llmLabel.innerText = 'LLM答题';
llmLabel.style = 'margin-left: 13px; font-size: 12px;';
// 创建"开始答题"按钮
var answerBtn = document.createElement('button');
answerBtn.id = 'llm-answer-btn';
answerBtn.innerHTML = '🤖 开始答题';
answerBtn.className = 'btn btn-primary';
answerBtn.style.cssText = `
margin-left: 10px;
padding: 4px 12px;
font-size: 12px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s;
opacity: 0.5;
pointer-events: none;
`;
answerBtn.onmouseover = function() {
if (window.llmEnabled) {
this.style.transform = 'scale(1.05)';
this.style.boxShadow = '0 2px 8px rgba(102, 126, 234, 0.4)';
}
};
answerBtn.onmouseout = function() {
this.style.transform = 'scale(1)';
this.style.boxShadow = 'none';
};
answerBtn.onclick = async function() {
if (!window.llmEnabled) {
alert('请先勾选"LLM答题"复选框!');
return;
}
// 禁用按钮,显示loading
answerBtn.disabled = true;
answerBtn.innerHTML = '⏳ 正在答题...';
answerBtn.style.opacity = '0.7';
try {
await autoAnswerQuiz();
// 答题完成
answerBtn.innerHTML = '✅ 答题完成';
answerBtn.style.background = '#28a745';
setTimeout(function() {
answerBtn.disabled = false;
answerBtn.innerHTML = '🤖 开始答题';
answerBtn.style.background = 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)';
answerBtn.style.opacity = '1';
}, 3000);
} catch (error) {
// 答题失败
answerBtn.innerHTML = '❌ 答题失败';
answerBtn.style.background = '#dc3545';
setTimeout(function() {
answerBtn.disabled = false;
answerBtn.innerHTML = '🤖 开始答题';
answerBtn.style.background = 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)';
answerBtn.style.opacity = '1';
}, 2000);
}
};
// 更新按钮状态的函数
function updateAnswerButtonState() {
if (window.llmEnabled) {
answerBtn.style.opacity = '1';
answerBtn.style.pointerEvents = 'auto';
} else {
answerBtn.style.opacity = '0.5';
answerBtn.style.pointerEvents = 'none';
}
}
llmContainer.appendChild(settingBtn);
llmContainer.appendChild(llmCheckbox);
llmContainer.appendChild(llmLabel);
llmContainer.appendChild(answerBtn);
if (rateCheckbox.firstElementChild) {
rateCheckbox.firstElementChild.onchange = function(event) {
if (video) {
if (event.target.checked) video.playbackRate = 2;
else video.playbackRate = 1;
}
};
}
if (volumeCheckbox.firstElementChild) {
volumeCheckbox.firstElementChild.onchange = function(event) {
if (video) {
video.muted = event.target.checked;
}
};
}
if (playCheckbox.firstElementChild) {
playCheckbox.firstElementChild.onchange = function(event) {
if (video) {
if (event.target.checked) video.play();
else video.pause();
}
};
}
container.appendChild(llmContainer);
container.appendChild(rateCheckbox);
container.appendChild(volumeCheckbox);
container.appendChild(playCheckbox);
container.appendChild(continueCheckbox);
container.appendChild(copyButton);
}
/*function setPrompt(container) {
var div = document.createElement('div');
div.innerHTML = `提示:<u>该版本为内测版,使用时请先关闭正式版</u>,<u><a href="https://greasyfork.org/zh-CN/scripts/425837-uooc-assistant-beta/feedback" target="_blank" style="color: yellow;">若出现 BUG 点此反馈</a></u>,键盘的 ← 和 → 可以控制快进/快退,↑ 和 ↓ 可以控制音量增大/减小,空格键可以控制播放/暂停`;
div.style = 'color: #cccccc; height: min-height; margin: 0 20px 0; padding: 0 5px; border-radius: 5px; font-size: 12px;';
container.appendChild(div);
}*/
function setAppreciationCode(container) {
var a = document.createElement('a');
a.href = 'https://s1.ax1x.com/2020/11/08/BTeRqe.png';
a.target = '_blank';
a.innerHTML = '<u>本脚本使用完全免费,您的打赏是作者维护下去的最大动力!点此打赏作者😊</u>';
a.style = 'color: #cccccc; font-weight: bold; height: min-height; margin: 0 20px 0; padding: 0 5px; border-radius: 5px; font-size: 11px;';
container.appendChild(a);
}
var head = document.querySelector('.learn-head');
if (!head) {
console.log('[UOOC助手] 未找到.learn-head元素,等待页面加载...');
return false;
}
console.log('[UOOC助手] 找到.learn-head元素');
// 避免重复添加
if (document.getElementById('checkbox-container')) {
console.log('[UOOC助手] UI组件已存在,跳过添加');
return true;
}
// 创建包裹容器(用于包裹head的原有内容)
const headContent = document.createElement('div');
headContent.id = 'head-content-wrapper';
// 将head的所有子元素移动到contentWrapper中
while (head.firstChild) {
headContent.appendChild(head.firstChild);
}
// 为head添加过渡动画样式
head.style.transition = 'max-height 0.4s ease, opacity 0.4s ease, margin-top 0.4s ease';
head.style.overflow = 'visible';
head.style.position = 'relative';
// 创建切换按钮(放在head内,但在contentWrapper外)
const toggleBtn = document.createElement('div');
toggleBtn.id = 'control-panel-toggle';
toggleBtn.innerHTML = '▲';
toggleBtn.style.cssText = `
position: absolute;
bottom: -25px;
left: 50%;
transform: translateX(-50%);
width: 80px;
height: 25px;
background: rgba(51, 51, 51, 0.95);
color: #ccc;
text-align: center;
line-height: 25px;
font-size: 12px;
cursor: pointer;
border-radius: 0 0 10px 10px;
transition: all 0.3s ease;
z-index: 1000;
user-select: none;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
`;
toggleBtn.onmouseover = function() {
this.style.background = 'rgba(102, 126, 234, 0.95)';
this.style.color = 'white';
};
toggleBtn.onmouseout = function() {
this.style.background = 'rgba(51, 51, 51, 0.95)';
this.style.color = '#ccc';
};
// 创建控制台容器
var checkboxContainer = getContainer('checkbox-container');
checkboxContainer.style.cssText = `
display: flex;
flex-direction: row;
align-items: center;
`;
setCheckboxes(checkboxContainer);
setAppreciationCode(checkboxContainer);
// 组装元素:contentWrapper在head内,toggleBtn在head内但在contentWrapper外
head.appendChild(headContent);
head.appendChild(checkboxContainer);
head.appendChild(toggleBtn);
// 记录原始高度
const originalHeight = head.offsetHeight + 30;
head.style.height = originalHeight + 'px';
// 控制显示/隐藏的函数
let isPanelVisible = true;
function hidePanel() {
if (!isPanelVisible) return;
isPanelVisible = false;
// 只隐藏contentWrapper和checkboxContainer,不隐藏toggleBtn
headContent.style.maxHeight = '0px';
headContent.style.opacity = '0';
headContent.style.overflow = 'hidden';
headContent.style.transition = 'max-height 0.4s ease, opacity 0.4s ease';
checkboxContainer.style.maxHeight = '0px';
checkboxContainer.style.opacity = '0';
checkboxContainer.style.overflow = 'hidden';
checkboxContainer.style.transition = 'max-height 0.4s ease, opacity 0.4s ease';
// 同时调整整个head的高度
head.style.maxHeight = '25px'; // 只保留toggleBtn的高度
head.style.opacity = '1';
toggleBtn.innerHTML = '▼';
}
function showPanel() {
if (isPanelVisible) return;
isPanelVisible = true;
// 恢复contentWrapper和checkboxContainer
headContent.style.maxHeight = originalHeight + 'px';
headContent.style.opacity = '1';
checkboxContainer.style.maxHeight = originalHeight + 'px';
checkboxContainer.style.opacity = '1';
// 恢复整个head的高度
head.style.maxHeight = originalHeight + 'px';
head.style.opacity = '1';
toggleBtn.innerHTML = '▲';
}
toggleBtn.onclick = function() {
if (isPanelVisible) {
hidePanel();
} else {
showPanel();
}
};
console.log('[UOOC助手] UI组件添加完成');
return true;
}
function bindKeyboardEvents() {
console.log('[UOOC助手] 绑定键盘事件');
document.onkeydown = function(event) {
var complete = false;
var basicActiveDiv = document.querySelector('div.basic.active');
var video = document.getElementById('player_html5_api');
if (!video) return;
if (basicActiveDiv && basicActiveDiv.classList.contains('complete')) complete = true;
switch (event.key) {
case 'ArrowLeft': {
event.preventDefault();
video.currentTime -= 10;
break;
}
case 'ArrowRight': {
event.preventDefault();
if (complete) video.currentTime += 10;
break;
}
case 'ArrowUp': {
event.preventDefault();
if (video.volume + 0.1 <= 1.0) video.volume += 0.1;
else video.volume = 1.0;
break;
}
case 'ArrowDown': {
event.preventDefault();
if (video.volume - 0.1 >= 0.0) video.volume -= 0.1;
else video.volume = 0.0;
break;
}
case ' ': {
event.preventDefault();
let continueCheckbox = document.getElementById('play');
if (continueCheckbox) continueCheckbox.click();
break;
}
}
};
}
function findNextVideo() {
var video = document.getElementById('player_html5_api');
if (video) {
if (!document.getElementById('continue') || !document.getElementById('continue').checked) {
video.currentTime = 0;
} else {
let current_video = document.querySelector('.basic.active');
if (!current_video) return;
let next_part = current_video.parentNode;
let next_video = current_video;
let isVideo = (node) => { return Boolean(node.querySelector('span.icon-video')); };
let canBack = () => { return Boolean(next_part.parentNode.parentNode.tagName === 'LI'); };
let toNextVideo = () => {
next_video = next_video.nextElementSibling;
while (next_video && !isVideo(next_video)) next_video = next_video.nextElementSibling;
};
let isExistsVideo = () => {
let _video = next_part.firstElementChild;
while (_video && !isVideo(_video)) _video = _video.nextElementSibling;
return Boolean(_video && isVideo(_video));
};
let isExistsNextVideo = () => {
let _video = current_video.nextElementSibling;
while (_video && !isVideo(_video)) _video = _video.nextElementSibling;
return Boolean(_video && isVideo(_video));
};
let isExistsNextListAfterFile = () => {
let part = next_part.nextElementSibling;
return Boolean(part && part.childElementCount > 0);
};
let toNextListAfterFile = () => { next_part = next_part.nextElementSibling; };
let toOuterList = () => { next_part = next_part.parentNode.parentNode; };
let toOuterItem = () => { next_part = next_part.parentNode; };
let isExistsNextListAfterList = () => { return Boolean(next_part.nextElementSibling); };
let toNextListAfterList = () => { next_part = next_part.nextElementSibling; };
let expandList = () => { next_part.firstElementChild.click(); };
let toExpandListFirstElement = () => {
next_part = next_part.firstElementChild.nextElementSibling;
if (next_part && next_part.classList.contains('unfoldInfo')) next_part = next_part.nextElementSibling;
};
let isList = () => { return Boolean(next_part.tagName === 'UL'); };
let toInnerList = () => { next_part = next_part.firstElementChild; };
let toFirstVideo = () => {
next_video = next_part.firstElementChild;
while (next_video && !isVideo(next_video)) next_video = next_video.nextElementSibling;
};
let mode = {
FIRST_VIDEO: 'FIRST_VIDEO',
NEXT_VIDEO: 'NEXT_VIDEO',
LAST_LIST: 'LAST_LIST',
NEXT_LIST: 'NEXT_LIST',
INNER_LIST: 'INNER_LIST',
OUTER_LIST: 'OUTER_LIST',
OUTER_ITEM: 'OUTER_ITEM',
};
let search = (_mode) => {
switch (_mode) {
case mode.FIRST_VIDEO:
if (isExistsVideo()) {
toFirstVideo();
if (next_video) next_video.click();
start();
} else if (isExistsNextListAfterFile()) {
search(mode.LAST_LIST);
} else if (window.canIgnoreTest) {
next_part = next_part.lastElementChild;
search(mode.OUTER_LIST);
}
break;
case mode.NEXT_VIDEO:
if (isExistsNextVideo()) {
toNextVideo();
if (next_video) next_video.click();
start();
} else if (isExistsNextListAfterFile()) {
search(mode.LAST_LIST);
} else {
search(mode.OUTER_ITEM);
}
break;
case mode.LAST_LIST:
toNextListAfterFile();
toInnerList();
search(mode.INNER_LIST);
break;
case mode.NEXT_LIST:
toNextListAfterList();
search(mode.INNER_LIST);
break;
case mode.INNER_LIST:
if (next_part.firstElementChild) {
expandList();
function waitForExpand() {
if (next_part.firstElementChild.nextElementSibling) {
if (next_part.firstElementChild.nextElementSibling.childElementCount === 0) {
search(mode.OUTER_LIST);
} else {
toExpandListFirstElement();
if (isList()) {
toInnerList();
search(mode.INNER_LIST);
} else {
search(mode.FIRST_VIDEO);
}
}
} else {
setTimeout(waitForExpand, 250);
}
}
waitForExpand();
}
break;
case mode.OUTER_LIST:
toOuterList();
if (isExistsNextListAfterList()) {
search(mode.NEXT_LIST);
} else if (canBack()) {
search(mode.OUTER_LIST);
} else {
// perhaps there is no next list
}
break;
case mode.OUTER_ITEM:
toOuterItem();
if (isExistsNextListAfterList()) {
toNextListAfterList();
search(mode.INNER_LIST);
} else if (canBack()){
search(mode.OUTER_LIST);
} else {
// perhaps there is no list
}
break;
default:
break;
}
};
try {
search(mode.NEXT_VIDEO);
} catch (err) {
console.error('[UOOC助手] 查找下一个视频出错:', err);
}
}
}
}
function init() {
console.log('[UOOC助手] 开始初始化...');
// 检测页面类型
const currentUrl = window.location.href;
// 测评页面
if (currentUrl.includes('/exam') || isQuizPage()) {
console.log('[UOOC助手-AI] 检测到测评页面');
setTimeout(function() {
showQuizPageHint();
}, 1000);
return;
}
// 视频学习页面
// 检查必要的全局对象
if (typeof $ === 'undefined') {
console.log('[UOOC助手] jQuery未加载,等待...');
setTimeout(init, 500);
return;
}
ckeckTestIgnorable();
function waitHead() {
if (document.querySelector('.learn-head')) {
console.log('[UOOC助手] .learn-head已存在,开始放置组件');
var uiSuccess = placeComponents();
bindChapterChange();
function ready() {
console.log('[UOOC助手] UOOC assistant beta has initialized.');
// 检查LLM启用状态和配置
if (window.llmEnabled) {
const config = LLMConfig.get();
if (!config || !config.baseUrl || !config.apiKey) {
console.log('[UOOC助手] LLM已启用但未配置API');
setTimeout(function() {
alert('LLM答题功能已启用,但尚未配置API参数!\n\n请点击⚙️图标进行配置。');
LLMConfig.showConfigUI();
}, 1000);
} else {
console.log('[UOOC助手] LLM配置正常,已准备就绪');
}
}
start();
}
if (document.getElementById('player_html5_api')) {
ready();
} else {
var iconVideo = document.querySelector('.icon-video');
if (iconVideo) {
console.log('[UOOC助手] 点击视频图标');
iconVideo.click();
setTimeout(ready, 250);
} else {
console.log('[UOOC助手] 未找到视频图标,等待用户手动选择');
}
}
} else {
console.log('[UOOC助手] 等待.learn-head元素...');
setTimeout(waitHead, 250);
}
}
waitHead();
}
// 不使用window.onload,直接执行
console.log('[UOOC助手] 脚本已加载,等待页面就绪');
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();