近几天,只干了两件事
如果你是来更新华为考试助手的,请滑动到底部,查看代码复制切换即可!
联通500G流量
联通云盘有个专属的500G流量,需要花费10元开通一个网盘的会员,然后去领取。这领取的500G流量是显示0元的自动续订,查看业务到期时间也符合长期。十元的会员开通之后,马上退订了,一切等着9月份的到来,看看500G流量会不会持续。
这个500G流量虽然是联通云盘的专属流量,但是可以通过节点免流。而且牛逼的大佬找到了很多host。通过伪装这些host就能实现走这个500G流量。
我还探索着用自己的小水管搭建了节点,也是成功的免流~跳点也不是很高。
华为考试助手
以前嫌弃华为的这个考试活动麻烦,但是这次的活动我参加了。
我第一次参见考试,发现需要摄像头,但是我的笔记本电脑没有自带摄像头,所以我通过安卓手机下载了一个叫做DroidCam的应用,然后用手机连接到电脑上,就可以使用摄像头了。
考试过程中我发现,检测的只是有没有人脸,并没有检测动态或者静态。
于是有了第一个脚本,功能实现的只是,上传一段视频,替代浏览器的摄像头,和obs的功能类似。
用这个脚本考试,通过豆包什么的开视频。一个题一个题的让豆包回答,但是效率太慢。后面我去参考别人的考试过程,发现了防止切屏和提取试题的两个脚本。
于是我在我的脚本里面集成了这两个功能,就可以实现考试摄像头检测、取消切屏限制、提取试题的功能。我直接内置AI接口,通过api接口直接实现获取试题的答案,通过悬浮窗的形式返给我。
本来想实现自动答题的功能,也做了,但是只实现了对于判断题和单选题的自动答题,多选题也实现过,但是当时的自动答题并非自动执行,而是需要手动点击 自动答题 按钮,点一次做一道题,只需要一直点击就能做完。但是想着完全自动,导致出现错误,且手动点击的版本也丢失了,加上我已经做完所有的考试,积分也够了,并且每次测试都要消耗token,导致我欠费了2元。50wtoken很快用完,然后还欠费了。后面发现是上传的试题消耗token太多,现在精简了上传的试题,只上传必要的信息,减少了token的消耗。
上面的版本的脚本 AI的配置是固定在脚本里面,因为欠费的原因,我更换了其他家的AI,测试发现很多的AI都适用,于是将其单独拿出来放到了脚本的设置页面。用户使用AI需要先自己配置AI,然后才能使用获取答案。如果不配置,可以使用提取试题的功能,将试题直接发给像是Chatgpt、豆包、deepseek等的ai,让它们给答案。免去了配置AI的步骤。而且通过api对接获取答案的快慢和模型有关系。我不直到现在的版本获取速度如何,反正我后面测试的速度慢了很多可能与ai模型有关,因为之前用的ai欠费了,没法做对比,之前的ai返回答案10几秒吧。
脚本代码如下,使用方法就不介绍了,如果可能会单独出一篇使用教程(大概率不会了)
脚本转载麻烦声明一下本文地址,麻烦保留来源
更新记录
1.0 初始版本 下载地址
1.01.1 增加耗时统计(看下方代码)
当前版本1.1,持续更新!欢迎反馈!
// ==UserScript==
// @name 华为考试助手
// @namespace http://tampermonkey.net/
// @version 1.1
// @description 帮助用户快速通过华为考试.
// @author YoungYet 博客 www.blog.yt
// @match *://*.huaweicloud.com/*
// @icon https://www.huaweicloud.com/favicon.ico
// @run-at document-start
// @grant GM_addStyle
// @grant GM_setClipboard
// @grant unsafeWindow
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @grant GM_listValues
// ==/UserScript==
(function () {
"use strict";
const window = unsafeWindow;
const blackListedEvents = new Set([
"visibilitychange",
"blur",
"focus",
"pagehide",
"freeze",
"resume",
"mouseleave",
"mouseout",
]);
const spoofedDocumentProperties = {
hidden: { value: false, configurable: true },
mozHidden: { value: false, configurable: true },
msHidden: { value: false, configurable: true },
webkitHidden: { value: false, configurable: true },
visibilityState: { value: "visible", configurable: true },
hasFocus: { value: () => true, configurable: true },
};
const eventHandlersToNullifyDocument = [
"onvisibilitychange",
"onblur",
"onfocus",
"onmouseleave",
"onmouseout",
"onpagehide",
"onfreeze",
"onresume",
];
const eventHandlersToNullifyWindow = [
"onblur",
"onfocus",
"onpagehide",
"onpageshow",
"onfreeze",
"onresume",
"onmouseleave",
"onmouseout",
];
const isDebug = false;
const scriptPrefix = "[防切屏检测]";
// 远程更新功能
const UPDATE_CHECK_URL = 'https://raw.githubusercontent.com/youngyet555/date/main/scriptcool/hwzsupdate.json';
const UPDATE_CHECK_INTERVAL = 12 * 60 * 60 * 1000; // 12小时
let lastUpdateCheck = GM_getValue('lastUpdateCheck', 0);
let currentVersion = '1.1'; // 当前版本号
const log = console.log.bind(
console,
`%c${scriptPrefix}`,
"color: #4CAF50; font-weight: bold;"
);
const warn = console.warn.bind(
console,
`%c${scriptPrefix}`,
"color: #FFC107; font-weight: bold;"
);
const error = console.error.bind(
console,
`%c${scriptPrefix}`,
"color: #F44336; font-weight: bold;"
);
const debug = isDebug ? log : () => {};
// 检查更新
async function checkForUpdates(showNotification = true) {
try {
const response = await fetch(UPDATE_CHECK_URL);
if (!response.ok) {
throw new Error('更新检查失败');
}
const updateInfo = await response.json();
const remoteVersion = updateInfo.version;
// 更新最后检查时间
GM_setValue('lastUpdateCheck', Date.now());
if (compareVersions(remoteVersion, currentVersion) > 0) {
// 有新版本
if (showNotification) {
showUpdateNotification(updateInfo);
}
return true;
} else {
// 没有新版本
if (showNotification) {
alert('当前已是最新版本!');
}
return false;
}
} catch (error) {
console.error('检查更新失败:', error);
if (showNotification) {
alert('检查更新失败,请稍后再试!');
}
return false;
}
}
// 版本比较函数
function compareVersions(version1, version2) {
const v1Parts = version1.split('.').map(Number);
const v2Parts = version2.split('.').map(Number);
for (let i = 0; i < Math.max(v1Parts.length, v2Parts.length); i++) {
const v1Part = v1Parts[i] || 0;
const v2Part = v2Parts[i] || 0;
if (v1Part > v2Part) return 1;
if (v1Part < v2Part) return -1;
}
return 0;
}
// 显示更新通知
function showUpdateNotification(updateInfo) {
const notification = document.createElement('div');
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
width: 350px;
background: white;
border-radius: 10px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
z-index: 10000000;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
overflow: hidden;
`;
const header = document.createElement('div');
header.style.cssText = `
background: linear-gradient(135deg, #4a90e2, #50c878);
color: white;
padding: 15px;
font-weight: bold;
font-size: 16px;
`;
header.textContent = '发现新版本!';
const content = document.createElement('div');
content.style.cssText = `
padding: 15px;
`;
const versionInfo = document.createElement('p');
versionInfo.style.cssText = `
margin: 0 0 10px 0;
font-weight: bold;
`;
versionInfo.textContent = `新版本: ${updateInfo.version}`;
const changelog = document.createElement('p');
changelog.style.cssText = `
margin: 0 0 15px 0;
font-size: 14px;
color: #555;
`;
changelog.textContent = `更新内容: ${updateInfo.changelog}`;
const buttons = document.createElement('div');
buttons.style.cssText = `
display: flex;
justify-content: space-between;
`;
const downloadButton = document.createElement('button');
downloadButton.textContent = '立即更新';
downloadButton.style.cssText = `
padding: 8px 15px;
background: #28a745;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
font-weight: 600;
`;
downloadButton.onclick = () => {
window.open(updateInfo.downloadUrl, '_blank');
document.body.removeChild(notification);
};
const laterButton = document.createElement('button');
laterButton.textContent = '稍后提醒';
laterButton.style.cssText = `
padding: 8px 15px;
background: #6c757d;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
font-weight: 600;
`;
laterButton.onclick = () => {
document.body.removeChild(notification);
};
buttons.appendChild(downloadButton);
buttons.appendChild(laterButton);
content.appendChild(versionInfo);
content.appendChild(changelog);
content.appendChild(buttons);
notification.appendChild(header);
notification.appendChild(content);
document.body.appendChild(notification);
}
// 自动检查更新
function autoCheckForUpdates() {
const now = Date.now();
if (now - lastUpdateCheck >= UPDATE_CHECK_INTERVAL) {
checkForUpdates(false); // 不显示通知
}
}
// 初始化自动检查更新
setInterval(autoCheckForUpdates, 60 * 60 * 1000); // 每小时检查一次是否到了12小时的间隔
function patchToString(modifiedFunction, originalFunction) {
if (
typeof modifiedFunction !== "function" ||
typeof originalFunction !== "function"
)
return;
try {
modifiedFunction.toString = () =>
Function.prototype.toString.call(originalFunction);
} catch (e) {
error("patchToString failed:", e, "for function:", originalFunction.name);
}
}
function patchAddEventListener(targetObject, objectName) {
if (!targetObject || typeof targetObject.addEventListener !== "function")
return;
const originalAddEventListener = targetObject.addEventListener;
targetObject.addEventListener = function (
type,
listener,
optionsOrCapture
) {
if (blackListedEvents.has(type.toLowerCase())) {
log(`BLOCKED ${objectName}.addEventListener: ${type}`);
return undefined;
}
return originalAddEventListener.call(
this,
type,
listener,
optionsOrCapture
);
};
patchToString(targetObject.addEventListener, originalAddEventListener);
}
function patchRemoveEventListener(targetObject, objectName) {
if (!targetObject || typeof targetObject.removeEventListener !== "function")
return;
const originalRemoveEventListener = targetObject.removeEventListener;
targetObject.removeEventListener = function (
type,
listener,
optionsOrCapture
) {
return originalRemoveEventListener.call(
this,
type,
listener,
optionsOrCapture
);
};
patchToString(
targetObject.removeEventListener,
originalRemoveEventListener
);
}
function spoofProperties(targetObject, propertiesToSpoof, objectName) {
if (!targetObject) return;
for (const prop in propertiesToSpoof) {
if (Object.prototype.hasOwnProperty.call(propertiesToSpoof, prop)) {
try {
Object.defineProperty(targetObject, prop, propertiesToSpoof[prop]);
} catch (e) {
error(`Failed to spoof ${objectName}.${prop}:`, e);
}
}
}
}
function nullifyEventHandlers(targetObject, eventHandlerNames, objectName) {
if (!targetObject) return;
eventHandlerNames.forEach((handlerName) => {
try {
Object.defineProperty(targetObject, handlerName, {
get: () => undefined,
set: () => {
log(`Attempt to set ${objectName}.${handlerName} blocked.`);
},
configurable: true,
});
} catch (e) {
error(`Failed to nullify ${objectName}.${handlerName}:`, e);
}
});
}
log("Anti screen-switch script starting...");
patchAddEventListener(window, "window");
patchRemoveEventListener(window, "window");
patchAddEventListener(document, "document");
patchRemoveEventListener(document, "document");
spoofProperties(document, spoofedDocumentProperties, "document");
nullifyEventHandlers(document, eventHandlersToNullifyDocument, "document");
nullifyEventHandlers(window, eventHandlersToNullifyWindow, "window");
const observer = new MutationObserver((mutations, obs) => {
if (document.body) {
patchAddEventListener(document.body, "document.body");
patchRemoveEventListener(document.body, "document.body");
nullifyEventHandlers(
document.body,
["onmouseleave", "onmouseout", "onblur", "onfocus"],
"document.body"
);
obs.disconnect();
}
});
if (document.body) {
patchAddEventListener(document.body, "document.body");
patchRemoveEventListener(document.body, "document.body");
nullifyEventHandlers(
document.body,
["onmouseleave", "onmouseout", "onblur", "onfocus"],
"document.body"
);
} else {
observer.observe(document.documentElement || document, {
childList: true,
subtree: true,
});
}
log("Anti screen-switch script loaded. Monitoring active.");
const config = {
panelPosition: { top: "20px", left: "20px" },
panelCollapsed: true,
};
let state = {
virtualContent: null,
isVideo: false,
loopVideo: true,
virtualStream: null,
fileName: null,
panelCollapsed: config.panelCollapsed,
};
function initIndexedDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open("CameraReplacerDB", 1);
request.onerror = () => reject(request.error);
request.onsuccess = () => resolve(request.result);
request.onupgradeneeded = (event) => {
const db = event.target.result;
if (!db.objectStoreNames.contains("videoFiles")) {
db.createObjectStore("videoFiles", { keyPath: "fileName" });
}
};
});
}
async function loadVideoFromIndexedDB(fileName) {
try {
const db = await initIndexedDB();
return new Promise((resolve, reject) => {
const transaction = db.transaction("videoFiles", "readonly");
const store = transaction.objectStore("videoFiles");
const request = store.get(fileName);
request.onsuccess = () => {
if (request.result) {
resolve(request.result.data);
} else {
resolve(null);
}
};
request.onerror = () => reject(request.error);
});
} catch (error) {
console.error("加载视频文件失败:", error);
return null;
}
}
async function saveVideoToIndexedDB(fileName, data) {
try {
const db = await initIndexedDB();
return new Promise((resolve, reject) => {
const transaction = db.transaction("videoFiles", "readwrite");
const store = transaction.objectStore("videoFiles");
const request = store.put({ fileName, data });
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
} catch (error) {
console.error("保存视频文件失败:", error);
}
}
async function loadSettings() {
try {
const saved = GM_getValue("cameraReplacerSettings");
if (saved) {
const settings = JSON.parse(saved);
state.panelCollapsed =
settings.panelCollapsed !== undefined
? settings.panelCollapsed
: config.panelCollapsed;
if (settings.fileName) {
state.fileName = settings.fileName;
state.isVideo = settings.isVideo || false;
const fileContent = GM_getValue(`file_${settings.fileName}`);
if (fileContent) {
state.virtualContent = fileContent;
setTimeout(() => {
updateFileInfo(state.fileName);
}, 50);
} else {
const fileData = await loadVideoFromIndexedDB(settings.fileName);
if (fileData) {
state.virtualContent = fileData;
try {
GM_setValue(`file_${settings.fileName}`, fileData);
} catch (e) {
console.warn("文件太大,无法保存到Tampermonkey存储:", e);
}
setTimeout(() => {
updateFileInfo(state.fileName);
}, 50);
} else {
setTimeout(() => {
updateFileInfo(state.fileName + " (刷新页面后需要重新上传)");
}, 50);
}
}
}
}
} catch (error) {
console.error("加载设置失败:", error);
const saved = localStorage.getItem("cameraReplacerSettings");
if (saved) {
const settings = JSON.parse(saved);
state.panelCollapsed =
settings.panelCollapsed !== undefined
? settings.panelCollapsed
: config.panelCollapsed;
if (settings.fileName) {
state.fileName = settings.fileName;
state.isVideo = settings.isVideo || false;
const fileData = await loadVideoFromIndexedDB(settings.fileName);
if (fileData) {
state.virtualContent = fileData;
try {
GM_setValue(`file_${settings.fileName}`, fileData);
} catch (e) {
console.warn("文件太大,无法保存到Tampermonkey存储:", e);
}
setTimeout(() => {
updateFileInfo(state.fileName);
}, 50);
} else {
setTimeout(() => {
updateFileInfo(state.fileName + " (刷新页面后需要重新上传)");
}, 50);
}
}
}
}
}
function saveSettings() {
const settings = {
panelCollapsed: state.panelCollapsed,
virtualContent: null,
isVideo: state.isVideo,
fileName: state.fileName,
};
try {
GM_setValue("cameraReplacerSettings", JSON.stringify(settings));
} catch (error) {
console.error("保存设置到Tampermonkey存储失败:", error);
localStorage.setItem("cameraReplacerSettings", JSON.stringify(settings));
}
}
function getAIConfig() {
try {
const configStr = GM_getValue("aiConfig");
if (configStr) {
return JSON.parse(configStr);
}
const localConfig = localStorage.getItem("aiConfig");
if (localConfig) {
return JSON.parse(localConfig);
}
} catch (error) {
console.error("获取AI配置失败:", error);
}
return {
apiKey: "",
apiUrl: "",
model: "",
};
}
function saveAIConfig(config) {
try {
const configStr = JSON.stringify(config);
GM_setValue("aiConfig", configStr);
localStorage.setItem("aiConfig", configStr);
return true;
} catch (error) {
console.error("保存AI配置失败:", error);
alert("保存AI配置失败,请重试!");
return false;
}
}
function clearAIConfig() {
try {
GM_deleteValue("aiConfig");
localStorage.removeItem("aiConfig");
alert("AI配置已清除!");
return true;
} catch (error) {
console.error("清除AI配置失败:", error);
alert("清除AI配置失败,请重试!");
return false;
}
}
function showAIConfigDialog() {
const currentConfig = getAIConfig();
const dialog = document.createElement("div");
dialog.id = "ai-config-dialog";
dialog.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.7);
z-index: 1000000;
display: flex;
align-items: center;
justify-content: center;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
`;
const dialogContent = document.createElement("div");
dialogContent.style.cssText = `
background: white;
padding: 25px;
border-radius: 15px;
width: 90%;
max-width: 500px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
`;
const title = document.createElement("h3");
title.textContent = "AI配置";
title.style.cssText = `
margin-top: 0;
margin-bottom: 20px;
color: #333;
text-align: center;
`;
const form = document.createElement("div");
const apiKeyLabel = document.createElement("label");
apiKeyLabel.textContent = "API Key:";
apiKeyLabel.style.display = "block";
apiKeyLabel.style.marginBottom = "5px";
apiKeyLabel.style.color = "#555";
const apiKeyInput = document.createElement("input");
apiKeyInput.type = "text";
apiKeyInput.value = currentConfig.apiKey || "";
apiKeyInput.style.cssText = `
width: 100%;
padding: 10px;
margin-bottom: 15px;
border: 1px solid #ddd;
border-radius: 5px;
box-sizing: border-box;
`;
const apiUrlLabel = document.createElement("label");
apiUrlLabel.textContent = "API URL:";
apiUrlLabel.style.display = "block";
apiUrlLabel.style.marginBottom = "5px";
apiUrlLabel.style.color = "#555";
const apiUrlInput = document.createElement("input");
apiUrlInput.type = "text";
apiUrlInput.value = currentConfig.apiUrl || "";
apiUrlInput.style.cssText = `
width: 100%;
padding: 10px;
margin-bottom: 15px;
border: 1px solid #ddd;
border-radius: 5px;
box-sizing: border-box;
`;
const modelLabel = document.createElement("label");
modelLabel.textContent = "Model:";
modelLabel.style.display = "block";
modelLabel.style.marginBottom = "5px";
modelLabel.style.color = "#555";
const modelInput = document.createElement("input");
modelInput.type = "text";
modelInput.value = currentConfig.model || "";
modelInput.style.cssText = `
width: 100%;
padding: 10px;
margin-bottom: 15px;
border: 1px solid #ddd;
border-radius: 5px;
box-sizing: border-box;
`;
const buttonContainer = document.createElement("div");
buttonContainer.style.cssText = `
display: flex;
justify-content: space-between;
margin-top: 20px;
`;
const saveButton = document.createElement("button");
saveButton.textContent = "保存";
saveButton.style.cssText = `
padding: 10px 20px;
background: #28a745;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
font-weight: 600;
`;
const clearButton = document.createElement("button");
clearButton.textContent = "清除配置";
clearButton.style.cssText = `
padding: 10px 20px;
background: #dc3545;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
font-weight: 600;
`;
const cancelButton = document.createElement("button");
cancelButton.textContent = "取消";
cancelButton.style.cssText = `
padding: 10px 20px;
background: #6c757d;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
font-weight: 600;
`;
saveButton.onclick = function () {
const newConfig = {
apiKey: apiKeyInput.value.trim(),
apiUrl: apiUrlInput.value.trim(),
model: modelInput.value.trim(),
};
if (saveAIConfig(newConfig)) {
document.body.removeChild(dialog);
alert("AI配置已保存!");
}
};
clearButton.onclick = function () {
if (confirm("确定要清除AI配置吗?")) {
if (clearAIConfig()) {
apiKeyInput.value = "";
apiUrlInput.value = "";
modelInput.value = "";
}
}
};
cancelButton.onclick = function () {
document.body.removeChild(dialog);
};
dialog.onclick = function (e) {
if (e.target === dialog) {
document.body.removeChild(dialog);
}
};
form.appendChild(apiKeyLabel);
form.appendChild(apiKeyInput);
form.appendChild(apiUrlLabel);
form.appendChild(apiUrlInput);
form.appendChild(modelLabel);
form.appendChild(modelInput);
buttonContainer.appendChild(saveButton);
buttonContainer.appendChild(clearButton);
buttonContainer.appendChild(cancelButton);
dialogContent.appendChild(title);
dialogContent.appendChild(form);
dialogContent.appendChild(buttonContainer);
dialog.appendChild(dialogContent);
document.body.appendChild(dialog);
}
async function clearAllStorage() {
try {
GM_deleteValue("cameraReplacerSettings");
try {
const allKeys = GM_listValues();
for (const key of allKeys) {
if (key.startsWith("file_")) {
GM_deleteValue(key);
}
}
} catch (e) {
console.warn("无法列出Tampermonkey存储中的键:", e);
}
localStorage.removeItem("cameraReplacerSettings");
const db = await initIndexedDB();
const transaction = db.transaction("videoFiles", "readwrite");
const store = transaction.objectStore("videoFiles");
await store.clear();
state = {
virtualContent: null,
isVideo: false,
loopVideo: true,
virtualStream: null,
fileName: null,
panelCollapsed: config.panelCollapsed,
};
updateFileInfo("");
alert("所有存储数据已清除!(AI配置保留)");
} catch (error) {
console.error("清除存储失败:", error);
alert("清除存储失败,请重试!");
}
}
function createControlPanel() {
const panel = document.createElement("div");
panel.id = "camera-replacer-panel";
panel.style.cssText = `
position: fixed;
top: ${config.panelPosition.top};
left: ${config.panelPosition.left};
background: linear-gradient(135deg, rgba(74, 144, 226, 0.95), rgba(80, 200, 120, 0.95));
color: white;
padding: 20px;
border-radius: 15px;
z-index: 999999;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
font-size: 14px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
min-width: 220px;
${
state.panelCollapsed
? "height: 40px; width: 40px; min-width: unset; padding: 0; background: linear-gradient(135deg, rgba(74, 144, 226, 0.95), rgba(80, 200, 120, 0.95)); border-radius: 50%; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); overflow: visible;"
: ""
}
`;
const toggleBtn = document.createElement("div");
if (state.panelCollapsed) {
toggleBtn.innerHTML = "⚙";
toggleBtn.style.cssText = `
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
cursor: pointer;
font-size: 18px;
color: #fff;
background: rgba(255, 255, 255, 0.2);
border-radius: 50%;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
`;
} else {
toggleBtn.innerHTML = "✕";
toggleBtn.style.cssText = `
position: absolute;
top: 8px;
right: 8px;
cursor: pointer;
font-size: 16px;
color: #fff;
background: rgba(255, 255, 255, 0.2);
border-radius: 50%;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
`;
}
toggleBtn.onclick = togglePanel;
const uploadArea = document.createElement("div");
uploadArea.innerHTML = `
<div style="margin-bottom: 15px; font-weight: 600; font-size: 16px; text-align: center; color: #fff; text-shadow: 0 1px 2px rgba(0,0,0,0.3);">华为考试助手</div>
<input type="file" id="virtual-content-upload" accept="image/*,video/*" style="display: none;">
<button id="upload-btn" style="width: 100%; padding: 10px 5px; margin-bottom: 15px; background: linear-gradient(135deg, #373734ba, #4a5060ba); color: white; border: none; border-radius: 8px; cursor: pointer; font-weight: 600; transition: all 0.3s ease; box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);">选择文件</button>
<div id="file-info" style="font-size: 12px; color: rgba(255, 255, 255, 0.8); margin-bottom: 15px; text-align: center;background: rgba(255, 255, 255, 0.1); border-radius: 8px; min-height: 14px;"></div>
`;
const statusDisplay = document.createElement("div");
statusDisplay.id = "status-display";
statusDisplay.innerHTML = `
<div style="font-size: 12px; color: rgba(255, 255, 255, 0.9); text-align: center; background: rgba(255, 255, 255, 0.1); border-radius: 8px; border-left: 4px solid #4CAF50; ">✅ 虚拟摄像头已启用</div>
`;
const aiConfigButton = document.createElement("button");
aiConfigButton.id = "ai-config-btn";
aiConfigButton.textContent = "AI配置";
aiConfigButton.style.cssText = `
width: 100%;
margin-top: 10px;
margin-bottom: 10px;
padding: 8px;
background: #17a2b8;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
font-weight: 600;
transition: all 0.3s ease;
`;
aiConfigButton.onclick = showAIConfigDialog;
const extractButton = document.createElement("button");
extractButton.id = "extract-exam-btn";
extractButton.textContent = "AI获取答案";
extractButton.style.cssText = `
width: 100%;
margin-top: 10px;
margin-bottom: 10px;
padding: 8px;
background: #6c757d;
color: white;
border: none;
border-radius: 8px;
cursor: not-allowed;
font-weight: 600;
transition: all 0.3s ease;
opacity: 0.6;
`;
extractButton.disabled = true;
const extractExamButton = document.createElement("button");
extractExamButton.id = "extract-questions-btn";
extractExamButton.textContent = "提取试题";
extractExamButton.style.cssText = `
width: 100%;
margin-bottom: 10px;
padding: 8px;
background: #6c757d;
color: white;
border: none;
border-radius: 8px;
cursor: not-allowed;
font-weight: 600;
transition: all 0.3s ease;
opacity: 0.6;
`;
extractExamButton.disabled = true;
const clearButton = document.createElement("button");
clearButton.id = "clear-storage-btn";
clearButton.textContent = "清除存储";
clearButton.style.cssText = `
width: 100%;
margin-bottom: 10px;
padding: 4px;
background: #01ad62;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
font-weight: 600;
transition: all 0.3s ease;
`;
clearButton.onclick = clearAllStorage;
const updateButton = document.createElement("button");
updateButton.id = "check-update-btn";
updateButton.textContent = "检查更新";
updateButton.style.cssText = `
width: 100%;
margin-bottom: 10px;
padding: 4px;
background: #007bff;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
font-weight: 600;
transition: all 0.3s ease;
`;
updateButton.onclick = () => checkForUpdates(true);
const contentContainer = document.createElement("div");
contentContainer.id = "panel-content";
contentContainer.style.cssText = state.panelCollapsed
? "display: none;"
: "";
contentContainer.appendChild(uploadArea);
contentContainer.appendChild(clearButton);
contentContainer.appendChild(statusDisplay);
contentContainer.appendChild(aiConfigButton);
contentContainer.appendChild(extractButton);
contentContainer.appendChild(extractExamButton);
contentContainer.appendChild(updateButton);
const copyright = document.createElement("div");
copyright.id = "panel-copyright";
copyright.style.cssText =
"margin-top:15px;text-align:center;font-size:11px;color:rgba(255,255,255,0.8);line-height:1.4;padding:8px;background:rgba(255,255,255,0.1);border-radius:6px;border:1px solid rgba(255,255,255,0.2)";
copyright.innerHTML =
'本脚本免费使用<br><a href="https://blog.yt" target="_blank" style="color:#fff;text-decoration:underline;font-weight:bold;">https://blog.yt</a>';
contentContainer.appendChild(copyright);
panel.appendChild(toggleBtn);
panel.appendChild(contentContainer);
document.body.appendChild(panel);
document.getElementById("upload-btn").onclick = () => {
document.getElementById("virtual-content-upload").click();
};
document.getElementById("virtual-content-upload").onchange =
handleFileUpload;
updatePanelState();
}
async function handleFileUpload(event) {
const file = event.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = async function (e) {
state.virtualContent = e.target.result;
state.isVideo = file.type.startsWith("video/");
state.fileName = file.name;
await saveVideoToIndexedDB(file.name, e.target.result);
try {
GM_setValue(`file_${file.name}`, e.target.result);
} catch (error) {
console.warn("文件太大,无法保存到Tampermonkey存储:", error);
}
const settings = {
loopVideo: state.loopVideo,
panelCollapsed: state.panelCollapsed,
virtualContent: null,
isVideo: state.isVideo,
fileName: state.fileName,
};
try {
GM_setValue("cameraReplacerSettings", JSON.stringify(settings));
} catch (error) {
console.error("保存设置到Tampermonkey存储失败:", error);
localStorage.setItem(
"cameraReplacerSettings",
JSON.stringify(settings)
);
}
updateFileInfo(file.name);
createVirtualStream();
};
reader.readAsDataURL(file);
}
function togglePanel() {
state.panelCollapsed = !state.panelCollapsed;
saveSettings();
updatePanelState();
}
function updatePanelState() {
const panel = document.getElementById("camera-replacer-panel");
const toggleBtn = panel.querySelector('div[style*="position: absolute"]');
const contentContainer = document.getElementById("panel-content");
const uploadBtn = document.getElementById("upload-btn");
const fileInfo = document.getElementById("file-info");
const loopToggleLabel = document.querySelector('label[for="loop-toggle"]');
const statusDisplayDiv = document.querySelector("#status-display > div");
const clearStorageBtn = document.getElementById("clear-storage-btn");
if (state.panelCollapsed) {
panel.style.height = "40px";
panel.style.width = "40px";
panel.style.minWidth = "unset";
panel.style.padding = "0";
panel.style.background =
"linear-gradient(135deg, rgba(74, 144, 226, 0.95), rgba(80, 200, 120, 0.95))";
panel.style.borderRadius = "50%";
panel.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.2)";
panel.style.overflow = "visible";
toggleBtn.innerHTML = "⚙";
toggleBtn.style.cssText = `
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
cursor: pointer;
font-size: 18px;
color: #fff;
background: rgba(255, 255, 255, 0.2);
border-radius: 50%;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
`;
contentContainer.style.display = "none";
} else {
panel.style.height = "auto";
panel.style.width = "220px";
panel.style.minWidth = "220px";
panel.style.maxWidth = "220px";
panel.style.padding = "20px";
panel.style.background =
"linear-gradient(135deg, rgba(74, 144, 226, 0.95), rgba(80, 200, 120, 0.95))";
panel.style.borderRadius = "15px";
panel.style.boxShadow = "0 8px 32px rgba(0, 0, 0, 0.2)";
panel.style.overflow = "visible";
panel.style.boxSizing = "border-box";
toggleBtn.innerHTML = "✕";
toggleBtn.style.cssText = `
position: absolute;
top: 8px;
right: 8px;
cursor: pointer;
font-size: 16px;
color: #fff;
background: rgba(255, 255, 255, 0.2);
border-radius: 50%;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
`;
contentContainer.style.display = "block";
contentContainer.style.width = "100%";
contentContainer.style.boxSizing = "border-box";
}
}
function updateFileInfo(filename) {
const fileInfo = document.getElementById("file-info");
if (fileInfo) {
fileInfo.textContent = `已选择: ${filename}`;
}
}
function createVirtualStream() {
if (!state.virtualContent) return null;
return new Promise((resolve) => {
if (state.isVideo) {
const video = document.createElement("video");
video.src = state.virtualContent;
video.loop = true;
video.muted = true;
video.playsInline = true;
video.onloadedmetadata = () => {
video.play().then(() => {
const canvas = document.createElement("canvas");
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
const ctx = canvas.getContext("2d");
const stream = canvas.captureStream(30);
state.virtualStream = stream;
function drawFrame() {
if (!video.paused && !video.ended) {
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
requestAnimationFrame(drawFrame);
}
}
drawFrame();
resolve(stream);
});
};
} else {
const img = new Image();
img.onload = () => {
const canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
const stream = canvas.captureStream(30);
state.virtualStream = stream;
resolve(stream);
};
img.src = state.virtualContent;
}
});
}
function interceptGetUserMedia() {
const originalGetUserMedia = navigator.mediaDevices.getUserMedia.bind(
navigator.mediaDevices
);
navigator.mediaDevices.getUserMedia = async function (constraints) {
if (!constraints.video) {
return originalGetUserMedia(constraints);
}
if (state.virtualStream) {
return state.virtualStream;
}
if (state.virtualContent) {
const stream = await createVirtualStream();
if (stream) {
return stream;
}
}
alert("请先上传视频或者图片!");
throw new Error("No virtual content available");
};
}
const targetUrl =
"https://svc.connect.huaweicloud.com/svc/innovation/userapi/exam2d/so/servlet/getExamPaper";
let capturedData = null;
function formatAnswerTable(answerContent) {
if (answerContent.includes("|") && answerContent.includes("题号")) {
const lines = answerContent.split("\n");
let tableHtml =
'<table style="width: 100%; border-collapse: collapse; font-size: 14px;">';
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
if (line.startsWith("|") && line.endsWith("|")) {
const cells = line
.split("|")
.slice(1, -1)
.map((cell) => cell.trim());
if (cells.length >= 4) {
if (cells[0].includes("------")) continue;
if (cells[0] === "题号") {
tableHtml +=
'<thead><tr style="background-color: #f8f9fa; border-bottom: 2px solid #dee2e6;">';
cells.forEach((cell) => {
tableHtml += `<th style="padding: 12px 8px; text-align: left; font-weight: bold; border: 1px solid #dee2e6;">${cell}</th>`;
});
tableHtml += "</tr></thead><tbody>";
} else {
tableHtml += '<tr style="border-bottom: 1px solid #dee2e6;">';
cells.forEach((cell, index) => {
if (index === 3) {
tableHtml += `<td style="padding: 12px 8px; border: 1px solid #dee2e6;"><strong style="color: #dc3545;">${cell}</strong></td>`;
} else {
tableHtml += `<td style="padding: 12px 8px; border: 1px solid #dee2e6;">${cell}</td>`;
}
});
tableHtml += "</tr>";
}
}
}
}
tableHtml += "</tbody></table>";
return tableHtml;
}
return answerContent;
}
function createAIAnswerPopup(answerContent) {
let existingPopup = document.getElementById("ai-answer-popup");
if (existingPopup) {
const content = existingPopup.querySelector("#ai-answer-content");
if (content) {
content.innerHTML = formatAnswerTable(answerContent);
}
return;
}
const popup = document.createElement("div");
popup.id = "ai-answer-popup";
popup.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
width: 500px;
max-height: 80vh;
background: white;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0,0,0,0.3);
z-index: 10001;
display: flex;
flex-direction: column;
border: 2px solid #007bff;
`;
const header = document.createElement("div");
header.style.cssText =
"padding:15px 20px;border-bottom:1px solid #ddd;display:flex;justify-content:space-between;align-items:center;background:#007bff;color:white";
header.innerHTML =
'<h3 style="margin:0;font-size:16px;">AI答案</h3><button style="border:none;background:none;font-size:20px;cursor:pointer;color:white">×</button>';
const content = document.createElement("div");
content.id = "ai-answer-content";
content.style.cssText =
"flex:1;padding:20px;overflow-y:auto;max-height:60vh;font-size:14px;line-height:2.0";
content.innerHTML = formatAnswerTable(answerContent);
const footer = document.createElement("div");
footer.style.cssText =
"padding:15px 20px;border-top:1px solid #ddd;text-align:right;background:#f8f9fa";
const closeBtn = document.createElement("button");
closeBtn.style.cssText =
"padding:6px 16px;background:#dc3545;color:white;border:none;border-radius:4px;cursor:pointer;font-size:12px";
closeBtn.textContent = "关闭";
closeBtn.onclick = () => {
popup.remove();
};
footer.appendChild(closeBtn);
const copyright = document.createElement("div");
copyright.style.cssText =
"margin-top:10px;text-align:center;font-size:12px;color:#666;border-top:1px solid #eee;padding-top:10px";
copyright.innerHTML =
'本脚本免费使用<br><a href="https://blog.yt" target="_blank" style="color:#007bff;text-decoration:none;">https://blog.yt</a>';
footer.appendChild(copyright);
popup.appendChild(header);
popup.appendChild(content);
popup.appendChild(footer);
document.body.appendChild(popup);
header.querySelector("button").onclick = () => {
popup.remove();
};
makeDraggable(popup, header);
}
function makeDraggable(element, handle) {
let pos1 = 0,
pos2 = 0,
pos3 = 0,
pos4 = 0;
handle.onmousedown = dragMouseDown;
function dragMouseDown(e) {
e = e || window.event;
e.preventDefault();
pos3 = e.clientX;
pos4 = e.clientY;
document.onmouseup = closeDragElement;
document.onmousemove = elementDrag;
}
function elementDrag(e) {
e = e || window.event;
e.preventDefault();
pos1 = pos3 - e.clientX;
pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
element.style.top = element.offsetTop - pos2 + "px";
element.style.left = element.offsetLeft - pos1 + "px";
}
function closeDragElement() {
document.onmouseup = null;
document.onmousemove = null;
}
}
function formatQuestionsForExtraction(examData) {
let result = "";
let questions = [];
if (typeof examData === "object" && examData !== null) {
if (examData.result && examData.result.questions) {
questions = examData.result.questions;
} else if (examData.examPaper && examData.examPaper.questions) {
questions = examData.examPaper.questions;
} else if (Array.isArray(examData)) {
questions = examData;
} else {
result = JSON.stringify(examData, null, 2);
return result;
}
questions.forEach((question, index) => {
const qType = getQuestionType(question.type);
const qContent = question.content || question.questionText || "";
const qOptions = [];
if (question.options && Array.isArray(question.options)) {
question.options.forEach((option) => {
const optionText = option.optionContent || option.optionText || "";
if (optionText) {
qOptions.push(optionText);
}
});
}
const formattedQuestion = {
num: index + 1,
type: qType,
question: qContent,
options: qOptions,
};
result += JSON.stringify(formattedQuestion) + ",\n";
});
if (result.endsWith(",\n")) {
result = "[\n" + result.substring(0, result.length - 2) + "\n]";
}
} else {
result = String(examData);
}
return result;
}
function getQuestionType(typeCode) {
switch (typeCode) {
case 2:
return "判断";
case 0:
return "单选";
case 1:
return "多选";
default:
return "未知";
}
}
function extractQuestionsFromPage() {
let questions = [];
let questionIndex = 1;
const questionContainers = document.querySelectorAll(
'.exam-question, .question-container, [class*="question"], [id*="question"], .exam-content'
);
if (questionContainers.length > 0) {
questionContainers.forEach((container) => {
if (
container.textContent.length < 10 ||
container.classList.contains("processed")
) {
return;
}
const questionText =
container.querySelector(".question-text, .question-title, h3, h4") ||
container;
if (questionText && questionText.textContent.trim()) {
const qText = questionText.textContent.trim();
let qType = "未知";
let qOptions = [];
if (
qText.includes("判断") ||
qText.includes("对错") ||
qText.includes("正确错误")
) {
qType = "判断";
} else if (qText.includes("多选") || qText.includes("多项选择")) {
qType = "多选";
} else {
qType = "单选";
}
const options = container.querySelectorAll(
'.option, .answer-option, [class*="option"], [id*="option"], li'
);
if (options.length > 0) {
options.forEach((option) => {
if (option.textContent.trim()) {
let optionText = option.textContent.trim();
optionText = optionText.replace(/^[A-Z][\.、]\s*/, "");
if (optionText) {
qOptions.push(optionText);
}
}
});
}
questions.push({
num: questionIndex,
type: qType,
question: qText,
options: qOptions,
});
questionIndex++;
container.classList.add("processed");
}
});
}
if (questions.length === 0) {
const bodyText = document.body.textContent;
const lines = bodyText.split("\n");
const questionLines = lines.filter((line) => {
const trimmedLine = line.trim();
return /^\d+[\.、\)]/.test(trimmedLine) && trimmedLine.length > 5;
});
if (questionLines.length > 0) {
questionLines.forEach((line, index) => {
let qType = "未知";
let qOptions = [];
if (
line.includes("判断") ||
line.includes("对错") ||
line.includes("正确错误")
) {
qType = "判断";
} else if (line.includes("多选") || line.includes("多项选择")) {
qType = "多选";
} else {
qType = "单选";
}
questions.push({
num: index + 1,
type: qType,
question: line.trim(),
options: qOptions,
});
});
}
}
return questions.length > 0 ? JSON.stringify(questions) : null;
}
function createQuestionsPopup(questionsContent) {
let existingPopup = document.getElementById("questions-popup");
if (existingPopup) {
const content = existingPopup.querySelector("#questions-content");
const downloadBtn = existingPopup.querySelector(
"#download-questions-btn"
);
if (content) {
try {
const questionsData = JSON.parse(questionsContent);
content.innerHTML = formatQuestionsForDisplay(questionsData);
} catch (e) {
content.textContent = questionsContent;
}
}
if (downloadBtn) {
downloadBtn.onclick = () => downloadQuestions(questionsContent);
}
return;
}
const popup = document.createElement("div");
popup.id = "questions-popup";
popup.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
width: 500px;
max-height: 80vh;
background: white;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0,0,0,0.3);
z-index: 10001;
display: flex;
flex-direction: column;
border: 2px solid #28a745;
`;
const header = document.createElement("div");
header.style.cssText =
"padding:15px 20px;border-bottom:1px solid #ddd;display:flex;justify-content:space-between;align-items:center;background:#28a745;color:white";
header.innerHTML =
'<h3 style="margin:0;font-size:16px;">提取的试题</h3><button style="border:none;background:none;font-size:20px;cursor:pointer;color:white">×</button>';
const content = document.createElement("div");
content.id = "questions-content";
content.style.cssText =
"flex:1;padding:20px;overflow-y:auto;max-height:60vh;font-size:14px;line-height:1.6;";
try {
const questionsData = JSON.parse(questionsContent);
content.innerHTML = formatQuestionsForDisplay(questionsData);
} catch (e) {
content.textContent = questionsContent;
}
const footer = document.createElement("div");
footer.style.cssText =
"padding:15px 20px;border-top:1px solid #ddd;text-align:right;background:#f8f9fa";
const downloadBtn = document.createElement("button");
downloadBtn.id = "download-questions-btn";
downloadBtn.style.cssText =
"padding:6px 16px;background:#17a2b8;color:white;border:none;border-radius:4px;cursor:pointer;font-size:12px;margin-right:10px";
downloadBtn.textContent = "下载试题(json格式)";
downloadBtn.onclick = () => downloadQuestions(questionsContent);
const copyBtn = document.createElement("button");
copyBtn.style.cssText =
"padding:6px 16px;background:#007bff;color:white;border:none;border-radius:4px;cursor:pointer;font-size:12px;margin-right:10px";
copyBtn.textContent = "复制试题(json格式)";
copyBtn.onclick = () => {
GM_setClipboard(questionsContent);
alert("试题已复制到剪贴板!");
};
const closeBtn = document.createElement("button");
closeBtn.style.cssText =
"padding:6px 16px;background:#dc3545;color:white;border:none;border-radius:4px;cursor:pointer;font-size:12px";
closeBtn.textContent = "关闭";
closeBtn.onclick = () => {
popup.remove();
};
footer.appendChild(downloadBtn);
footer.appendChild(copyBtn);
footer.appendChild(closeBtn);
popup.appendChild(header);
popup.appendChild(content);
popup.appendChild(footer);
document.body.appendChild(popup);
header.querySelector("button").onclick = () => {
popup.remove();
};
makeDraggable(popup, header);
}
function formatQuestionsForDisplay(questionsData) {
let html = "";
let questions = [];
if (Array.isArray(questionsData)) {
questions = questionsData;
} else if (questionsData && typeof questionsData === "object") {
if (questionsData.result && questionsData.result.questions) {
questions = questionsData.result.questions;
} else if (questionsData.examPaper && questionsData.examPaper.questions) {
questions = questionsData.examPaper.questions;
} else {
questions = [questionsData];
}
}
questions.forEach((question, index) => {
const num = question.num || index + 1;
const type = question.type || "未知";
const qText =
question.question || question.content || question.questionText || "";
const options = question.options || [];
html += `<div style="margin-bottom: 20px; padding-bottom: 15px; border-bottom: 1px solid #eee;">`;
html += `<div style="font-weight: bold; margin-bottom: 8px;">第${num}题 (${type})</div>`;
html += `<div style="margin-bottom: 10px;">${qText}</div>`;
if (options.length > 0) {
html += `<div style="margin-left: 20px;">`;
options.forEach((option, optIndex) => {
const optionLabel = String.fromCharCode(65 + optIndex);
html += `<div style="margin-bottom: 5px;">${optionLabel}. ${option}</div>`;
});
html += `</div>`;
}
html += `</div>`;
});
return html || "<div>没有可显示的试题内容</div>";
}
function downloadQuestions(content) {
let questionsData;
if (typeof content === "string") {
try {
questionsData = JSON.parse(content);
} catch (e) {
console.error("试题数据解析失败:", e);
questionsData = { raw: content };
}
} else {
questionsData = content;
}
let formattedData;
if (Array.isArray(questionsData)) {
formattedData = questionsData;
} else if (questionsData && typeof questionsData === "object") {
if (questionsData.result && questionsData.result.questions) {
formattedData = questionsData.result.questions;
} else if (questionsData.examPaper && questionsData.examPaper.questions) {
formattedData = questionsData.examPaper.questions;
} else {
formattedData = [questionsData];
}
} else {
formattedData = [{ raw: questionsData }];
}
const jsonString = JSON.stringify(formattedData, null, 2);
const blob = new Blob([jsonString], { type: "application/json" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = `华为考试试题_${new Date()
.toLocaleString("zh-CN")
.replace(/[/:]/g, "-")}.json`;
document.body.appendChild(a);
a.click();
setTimeout(() => {
document.body.removeChild(a);
URL.revokeObjectURL(url);
}, 100);
console.log("试题下载完成");
}
async function callHwaiAPI(examData, extractBtn) {
console.log("开始调用Hwai API,试题数据:", examData);
let simplifiedData;
try {
if (typeof examData === "string") {
simplifiedData = JSON.parse(examData);
} else {
simplifiedData = examData;
}
} catch (error) {
console.error("试题数据解析失败:", error);
throw new Error("试题数据格式错误");
}
if (simplifiedData.result && simplifiedData.result.questions) {
let tempData = [];
simplifiedData.result.questions.forEach((question, index) => {
const qType = getQuestionType(question.type);
const qContent = question.content || question.questionText || "";
const qOptions = [];
if (question.options && Array.isArray(question.options)) {
question.options.forEach((option) => {
const optionText = option.optionContent || option.optionText || "";
if (optionText) {
qOptions.push(optionText);
}
});
}
tempData.push({
num: index + 1,
type: qType,
question: qContent,
options: qOptions,
});
});
simplifiedData = tempData;
}
if (!Array.isArray(simplifiedData)) {
simplifiedData = [simplifiedData];
}
const aiConfig = getAIConfig();
if (!aiConfig.apiKey || !aiConfig.apiUrl || !aiConfig.model) {
throw new Error('AI配置不完整,请先点击"AI配置"按钮进行配置');
}
const API_KEY = aiConfig.apiKey;
const API_URL = aiConfig.apiUrl;
const MODEL = aiConfig.model;
try {
console.log("发送API请求到:", API_URL);
const response = await fetch(API_URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${API_KEY}`,
},
body: JSON.stringify({
model: MODEL,
messages: [
{
role: "system",
content:
'你是一个专业的AI答题助手,擅长解答各种类型的题目。请根据提供的试题数据,为每道题目提供简洁准确的答案。答案需要按照以下格式返回:\n\n| 题号 | 题干关键词 | 题型 | 答案 |\n|------|----------|------|------|\n| 1 | JavaScript基本数据类型 | 单选题 | A |\n| 2 | 前端框架 | 多选题 | ABC |\n| 3 | HTML5本地存储 | 判断题 | true |\n\n请确保表格格式正确,每道题目一行,答案要简洁准确。从题干中提取关键词,不要显示完整题目内容。\n\n重要:对于单选题和多选题,请按照options数组的顺序,将选项标记为A、B、C、D等,并在答案列中只返回对应的字母。例如,如果正确答案是第一个选项,则返回"A";如果是第一和第三个选项,则返回"AC"。对于判断题,请直接返回true或false,不要使用字母。',
},
{
role: "user",
content: `请为以下试题提供答案:\n\n${JSON.stringify(
simplifiedData
)}`,
},
],
temperature: 0.7,
max_tokens: 4000,
}),
});
console.log("API响应状态:", response.status);
if (!response.ok) {
const errorText = await response.text();
console.error("API请求失败:", response.status, errorText);
throw new Error(`API请求失败: ${response.status} ${errorText}`);
}
const data = await response.json();
console.log("API返回数据:", data);
if (data.choices && data.choices[0] && data.choices[0].message) {
return data.choices[0].message.content;
} else {
throw new Error("API返回数据格式错误");
}
} catch (error) {
console.error("Hwai API调用失败:", error);
console.log("返回模拟答案");
return `| 题号 | 题干关键词 | 题型 | 答案 |\n|------|----------|------|------|\n| 1 | API调用失败,请检查API配置 | 判断题 | true |\n| 2 | 本工具免费使用 | 单选题 | A |\n| 3 | Script.cool | 多选题 | ABCD |`;
}
}
function isExamExtractPage() {
return (
window.location.href ===
"https://connect.huaweicloud.com/courses/exam/page/sp:cloudEdu_"
);
}
function updateExtractButton() {
const extractBtn = document.getElementById("extract-exam-btn");
if (!extractBtn) return;
if (isExamExtractPage()) {
extractBtn.style.background = "#dc3545";
extractBtn.style.cursor = "pointer";
extractBtn.style.opacity = "1";
extractBtn.disabled = false;
} else {
extractBtn.style.background = "#6c757d";
extractBtn.style.cursor = "not-allowed";
extractBtn.style.opacity = "0.6";
extractBtn.disabled = true;
}
}
function updateExtractQuestionsButton() {
const extractQuestionsBtn = document.getElementById(
"extract-questions-btn"
);
if (!extractQuestionsBtn) return;
if (isExamExtractPage()) {
extractQuestionsBtn.style.background = "#28a745";
extractQuestionsBtn.style.cursor = "pointer";
extractQuestionsBtn.style.opacity = "1";
extractQuestionsBtn.disabled = false;
} else {
extractQuestionsBtn.style.background = "#6c757d";
extractQuestionsBtn.style.cursor = "not-allowed";
extractQuestionsBtn.style.opacity = "0.6";
extractQuestionsBtn.disabled = true;
}
}
function interceptExamRequests() {
const originalSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function (data) {
const self = this;
this.addEventListener("readystatechange", function () {
if (
self.readyState === 4 &&
self.status === 200 &&
self.responseURL === targetUrl
) {
capturedData = self.responseText;
updateExtractButton();
}
});
originalSend.call(this, data);
};
const originalFetch = window.fetch;
window.fetch = function (url, ...args) {
if (url === targetUrl) {
return originalFetch.apply(this, arguments).then((response) => {
response
.clone()
.text()
.then((data) => {
capturedData = data;
updateExtractButton();
});
return response;
});
}
return originalFetch.apply(this, arguments);
};
}
async function init() {
await loadSettings();
createControlPanel();
interceptGetUserMedia();
interceptExamRequests();
// 初始化时检查一次更新
setTimeout(() => {
autoCheckForUpdates();
}, 5000); // 延迟5秒检查更新,避免页面加载时过于繁忙
setTimeout(() => {
if (state.fileName && !state.virtualContent && state.isVideo) {
updateFileInfo(state.fileName + " (刷新页面后需要重新上传)");
}
}, 100);
setTimeout(() => {
updateExtractButton();
updateExtractQuestionsButton();
}, 200);
setTimeout(() => {
const extractBtn = document.getElementById("extract-exam-btn");
if (extractBtn) {
extractBtn.onclick = async () => {
if (capturedData && isExamExtractPage()) {
const startTime = Date.now();
let elapsedTime = 0;
let timerInterval;
// 创建耗时显示元素
const elapsedTimeElement = document.createElement('div');
elapsedTimeElement.id = 'elapsed-time-display';
elapsedTimeElement.style.cssText = `
margin-top: 5px;
font-size: 12px;
color: #666;
text-align: center;
`;
extractBtn.parentNode.insertBefore(elapsedTimeElement, extractBtn.nextSibling);
// 更新耗时显示
const updateElapsedTime = () => {
elapsedTime = Math.floor((Date.now() - startTime) / 1000);
elapsedTimeElement.textContent = `已经耗时 ${elapsedTime} 秒`;
};
// 立即更新一次
updateElapsedTime();
// 每秒更新一次耗时显示
timerInterval = setInterval(updateElapsedTime, 1000);
extractBtn.textContent = "处理中...";
extractBtn.disabled = true;
try {
const aiConfig = getAIConfig();
if (!aiConfig.apiKey || !aiConfig.apiUrl || !aiConfig.model) {
// 清除计时器
clearInterval(timerInterval);
// 更新耗时显示元素
const elapsedTimeElement = document.getElementById('elapsed-time-display');
if (elapsedTimeElement) {
elapsedTimeElement.textContent = "配置错误";
}
alert('请先点击"AI配置"按钮配置AI参数!');
extractBtn.textContent = "AI获取答案";
extractBtn.disabled = false;
return;
}
let examData;
try {
examData = JSON.parse(capturedData);
} catch (e) {
examData = capturedData;
}
const formattedData = formatQuestionsForExtraction(examData);
const aiAnswer = await callHwaiAPI(formattedData, extractBtn);
// 清除计时器
clearInterval(timerInterval);
// 计算总耗时
const totalTime = Math.floor((Date.now() - startTime) / 1000);
createAIAnswerPopup(aiAnswer);
// 更新耗时显示元素
const elapsedTimeElement = document.getElementById('elapsed-time-display');
if (elapsedTimeElement) {
elapsedTimeElement.textContent = `本次耗时 ${totalTime} 秒`;
}
extractBtn.textContent = "重新获取答案";
extractBtn.disabled = false;
} catch (error) {
// 清除计时器
clearInterval(timerInterval);
// 计算总耗时
const totalTime = Math.floor((Date.now() - startTime) / 1000);
console.error("AI答案处理失败:", error);
if (error.message.includes("AI配置不完整")) {
alert('请先点击"AI配置"按钮配置AI参数!');
} else {
alert("AI答案处理失败,请重试!");
}
// 更新耗时显示元素
const elapsedTimeElement = document.getElementById('elapsed-time-display');
if (elapsedTimeElement) {
elapsedTimeElement.textContent = `本次耗时 ${totalTime} 秒`;
}
extractBtn.textContent = "重新获取答案";
extractBtn.disabled = false;
}
}
};
}
}, 200);
setTimeout(() => {
const extractQuestionsBtn = document.getElementById(
"extract-questions-btn"
);
if (extractQuestionsBtn) {
extractQuestionsBtn.onclick = async () => {
const originalText = extractQuestionsBtn.textContent;
extractQuestionsBtn.textContent = "提取中...";
extractQuestionsBtn.disabled = true;
try {
if (capturedData) {
let examData;
try {
examData = JSON.parse(capturedData);
} catch (e) {
examData = capturedData;
}
const formattedQuestions = formatQuestionsForExtraction(examData);
createQuestionsPopup(formattedQuestions);
} else {
const pageQuestions = extractQuestionsFromPage();
if (pageQuestions) {
createQuestionsPopup(pageQuestions);
} else {
alert("未找到试题数据,请确保在考试页面!");
}
}
} catch (error) {
console.error("提取试题失败:", error);
alert("提取试题失败,请重试!");
} finally {
extractQuestionsBtn.textContent = originalText;
extractQuestionsBtn.disabled = false;
}
};
}
}, 200);
setInterval(() => {
updateExtractButton();
updateExtractQuestionsButton();
}, 1000);
console.log("摄像头内容替换器已加载");
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", () => init());
} else {
init();
}
})();