Home
avatar

YoungYet

近几天,只干了两件事

如果你是来更新华为考试助手的,请滑动到底部,查看代码复制切换即可!

联通500G流量

联通云盘有个专属的500G流量,需要花费10元开通一个网盘的会员,然后去领取。这领取的500G流量是显示0元的自动续订,查看业务到期时间也符合长期。十元的会员开通之后,马上退订了,一切等着9月份的到来,看看500G流量会不会持续。

这个500G流量虽然是联通云盘的专属流量,但是可以通过节点免流。而且牛逼的大佬找到了很多host。通过伪装这些host就能实现走这个500G流量。

host

我还探索着用自己的小水管搭建了节点,也是成功的免流~跳点也不是很高。

华为考试助手

以前嫌弃华为的这个考试活动麻烦,但是这次的活动我参加了。

我第一次参见考试,发现需要摄像头,但是我的笔记本电脑没有自带摄像头,所以我通过安卓手机下载了一个叫做DroidCam的应用,然后用手机连接到电脑上,就可以使用摄像头了。

考试过程中我发现,检测的只是有没有人脸,并没有检测动态或者静态。

于是有了第一个脚本,功能实现的只是,上传一段视频,替代浏览器的摄像头,和obs的功能类似。

用这个脚本考试,通过豆包什么的开视频。一个题一个题的让豆包回答,但是效率太慢。后面我去参考别人的考试过程,发现了防止切屏和提取试题的两个脚本。

于是我在我的脚本里面集成了这两个功能,就可以实现考试摄像头检测、取消切屏限制、提取试题的功能。我直接内置AI接口,通过api接口直接实现获取试题的答案,通过悬浮窗的形式返给我。

本来想实现自动答题的功能,也做了,但是只实现了对于判断题和单选题的自动答题,多选题也实现过,但是当时的自动答题并非自动执行,而是需要手动点击 自动答题 按钮,点一次做一道题,只需要一直点击就能做完。但是想着完全自动,导致出现错误,且手动点击的版本也丢失了,加上我已经做完所有的考试,积分也够了,并且每次测试都要消耗token,导致我欠费了2元。50wtoken很快用完,然后还欠费了。后面发现是上传的试题消耗token太多,现在精简了上传的试题,只上传必要的信息,减少了token的消耗。

上面的版本的脚本 AI的配置是固定在脚本里面,因为欠费的原因,我更换了其他家的AI,测试发现很多的AI都适用,于是将其单独拿出来放到了脚本的设置页面。用户使用AI需要先自己配置AI,然后才能使用获取答案。如果不配置,可以使用提取试题的功能,将试题直接发给像是Chatgpt、豆包、deepseek等的ai,让它们给答案。免去了配置AI的步骤。而且通过api对接获取答案的快慢和模型有关系。我不直到现在的版本获取速度如何,反正我后面测试的速度慢了很多可能与ai模型有关,因为之前用的ai欠费了,没法做对比,之前的ai返回答案10几秒吧。

AI配置

脚本代码如下,使用方法就不介绍了,如果可能会单独出一篇使用教程(大概率不会了)

脚本转载麻烦声明一下本文地址,麻烦保留来源

更新记录

1.0 初始版本 下载地址

1.0

1.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();
  }
})();
笔记 日常