Greasy Fork

Greasy Fork is available in English.

【学习通刷课助手】||防止鼠标移出暂停|章节结束自动跳转|章节测试[调用AI接口答题,正确率待验证]

学习通课程自动挂机,当前脚本支持课程视频播放完成,自动跳转下一小节,章节测试自动跳过,后台播放防止视频暂停,章节测试刷题。

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         【学习通刷课助手】||防止鼠标移出暂停|章节结束自动跳转|章节测试[调用AI接口答题,正确率待验证]
// @namespace    http://tampermonkey.net/
// @version      3.0.7
// @description  学习通课程自动挂机,当前脚本支持课程视频播放完成,自动跳转下一小节,章节测试自动跳过,后台播放防止视频暂停,章节测试刷题。
// @author       Sweek
// @match        *://*.chaoxing.com/*
// @license      GPLv3
// @icon         https://www.sweek.top/api/preview/avatar.jpg
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @require      https://code.jquery.com/jquery-2.1.4.min.js
// @require      https://update.greasyfork.icu/scripts/522188/1511411/typrmd5_sweek.js
// @resource     Table https://www.sweek.top/api/preview/table.json
// @grant        unsafeWindow
// @grant        GM_getResourceText
// ==/UserScript==



GM_setValue("playbackRate", GM_getValue("playbackRate") || 1);
GM_setValue("volume", GM_getValue("volume") || 0);
GM_setValue("courseImg", GM_getValue("courseImg") || '');
GM_setValue("testType", GM_getValue("testType") || 'save');
GM_setValue("ifTest", GM_getValue("ifTest") || 'skip');
GM_setValue("asyncCourseInterval", GM_getValue("asyncCourseInterval") || '300000');
GM_setValue("taskInterval", GM_getValue("taskInterval") || '6000');
GM_setValue("testInterval", GM_getValue("testInterval") || '4000');
GM_setValue("ifReview", GM_getValue("ifReview") || '0');
GM_setValue("savedEmail", GM_getValue("savedEmail") || '');
GM_setValue("activeTab", GM_getValue("activeTab") || 'tab1');


// 定义全局变量
let readNum = 0 // 已读公告次数
let activeTab = GM_getValue("activeTab") // 当前选中tab
let currentTime = null // 当前视频当前播放节点
let duration = null // 当前视频总长度
let progress = null // 当前视频播放进度
let playbackRate = GM_getValue("playbackRate") // 当前视频播放倍速
let volume = GM_getValue("volume") // 当前视频音量

// 课程章节相关数据
let courseName = null // 当前课程名称
let courseId = null // 当前课程id
let courseImg = GM_getValue("courseImg") // 当前课程封面
let chapterInfo = [] // 当前课程所有章节数据
let currentChapterId = null // 当前所在章节id
let currentChapterName = null // 当前所在章节名称
let allChapterName = []  // 所有章节名称
let videoProgressId = '' // 定时监听页面内容监听事件
let syncCourseId = '' // 定时监听同步任务事件

// 同步课程时间间隔
let asyncCourseInterval = GM_getValue("asyncCourseInterval")

// 任务间隔
let taskInterval = GM_getValue("taskInterval")

// 是否复习模式
let ifReview = GM_getValue("ifReview")


// 章节测试数据
let testDom = null
let ifTest = GM_getValue("ifTest")
let testType = GM_getValue("testType")
let testTasks = []
let testBtnDocument = null

let testInterval = GM_getValue("testInterval")

// 获取当前页面的 URL
url = ''
chapterId = ''


let arrayEchelon = []; // 顺序执行梯队,初始化为空数组
const dealEvent = new Event("redeal", { bubbles: false, cancelable: false });
let testArrayEchelon = []; // 顺序执行梯队,初始化为空数组
const testDealEvent = new Event("testRedeal", { bubbles: false, cancelable: false });

// AI请求接口Url
const fetch_url ='https://www.sweek.top/model/sse/data?text='

// 页面模板部分
// 页面模板部分
// 页面模板部分

// 页面样式
var popCSs = `
#my-window {
  position: fixed;
  top: 5px;
  left: 20px;
  width: 400px;
  height: auto;
  background-color: rgb(255, 255, 255);
  border: 1px solid #ccc;
  border-radius: 5px;
  z-index: 9999;
  overflow: hidden;
  padding: 0 5px 0px 0;
  font-family: fangsong;
  font-weight: bold;

  .header {
    background-color: #248067;
    color: #fff;
    padding: 5px;
    font-size: 16px;
    font-family: 'fangsong';
    font-weight: bold;
    border-radius: 0px;
    cursor: move;
    height: 50px;
    width: 400px;
    padding-left: 10px;

    .btn-content {
      width: 400px;
      display: flex;
      .btn {
        height: 22px;
        width: auto;
        margin-right: 4px;
        background-color: #fff;
        border: 1px solid #fff;
        border-radius: 2px;
        font-size: 12px;
        padding: 0 5px;
        font-family: 'fangsong';
        cursor: pointer;
        font-weight: bold;
        color: #248067;
        i {
          margin-right: 2px;
        }
      }

      .btn:hover {
        background-color: #248067;
        color: #fff;
      }
    }
  }

  .content {
    width: 400px;
    height: auto;
    background-color: rgb(255, 255, 255);
    .tab-bar {
      width: 395px;
      display: flex;
      background-color:rgb(255, 255, 255);
      color: white;
      padding: 5px;
      cursor: pointer;
      .tab {
        flex: 1;
        text-align: center;
        padding: 5px;
        background-color: #555;
        i {
          margin-right: 2px;
        }
      }
      div:hover {
        background-color: #666;
      }
      div.active {
        background-color: #248067;
      }
    }

    .content-title {
      height: 20px;
      width: 380px;
      background-color: #fff;
      color: #248067;
      line-height: 20px;
      padding-left: 5px;
      font-size: 15px;
      font-family: 'fangsong';
      font-weight: bold;
      border-left: 4px solid #248067;
      margin-top: 0px;
      margin-bottom: 0px;
    }

    .content-notice {
      height: 500px;
      width: 380px;
      overflow: auto;
      border: 1px solid #ccc;
      border-radius: 2px;
      padding: 5px;
      margin-top: 8px;
      font-size: 12px;
      font-weight: bold;
      display: block;
    }

    .content-process {
      height: 475px;
      width: 380px;
      overflow: auto;
      border: 1px solid #ccc;
      border-radius: 2px;
      padding: 5px;
      margin-top: 8px;
      display: none;
    }

    .content-log {
      height: 500px;
      width: 380px;
      overflow: auto;
      border: 1px solid #ccc;
      background-color: #222;
      color: #fff;
      border-radius: 2px;
      padding: 5px;
      margin-top: 8px;
      display: none;
      line-height: 22px;
    }

    .content-set {
      height: 500px;
      width: 380px;
      overflow-y: auto;
      overflow-x: hidden;
      border: 1px solid #ccc;
      border-radius: 2px;
      padding: 5px;
      margin-top: 8px;
      display: none;

      .content-set-title {
        font-family: 'Microsoft YaHei';
        font-weight: bold;
        padding: 0 5px;
        font-size: 13px;
        color: #248067;

        i {
          margin-right: 4px;
          font-size: 14px;
        }
      }

      .content-set-tips {
        margin-bottom: 5px;
        font-family: 'fangsong';
        font-weight: bold;
        padding: 5px;
        margin: 5px;
        background-color: #42b88433;
      }

      .content-set-content {
        width: 370px;
        display: flex;
        justify-content: space-between;
        padding: 5px 5px;
        font-size: 13px;

        .control {
          width: 270px;
          height: 22px;
          display: flex;

          #ifReviewSelect,
          #playbackRateSelect,
          #volumeSelect,
          #ifTestSelect,
          #testTypeSelect,
          #taskIntervalSelect,
          #asyncCourseIntervalSelect,
          #testIntervalSelect {
            width: 260px;
            height: 22px;
            border-radius: 3px;
            border: 1px solid gray;
            font-size: 13px;
            font-family: fangsong;
            font-weight: bold;
            cursor: pointer;
          }
        }

        #email-input {
          width: 215px;
          height: 22px;
          border-radius: 3px;
          border: 1px solid gray;
          font-size: 13px;
          font-family: fangsong;
          font-weight: bold;
        }

        #save-btn {
          width: 40px;
          height: 24px;
          border-radius: 3px;
          border: 1px solid #888;
          font-size: 13px;
          font-family: fangsong;
          font-weight: bold;
          cursor: pointer;
          background-color: #fff;
          margin-left: 5px;
        }
        #save-btn:hover {
          background-color: #248067;
          color: #fff;
        }
      }
    }
  }

  .resizer {
    position: absolute;
    bottom: 0;
    right: 0;
    width: 20px;
    height: 20px;
    background-color: #248067;
    cursor: se-resize;
    border-radius: 0px;
    z-index: 1;
  }
}

#hide-btn {
  height: 25px;
  width: auto;
  float: right;
  margin-right: 10px;
  background-color: #fff;
  border: 1px solid #fff;
  border-radius: 4px;
  font-size: 12px;
  padding: 0 5px;
  font-family: 'fangsong';
  cursor: pointer;
  font-weight: bold;

  &:hover {
    background-color: #248067;
    color: #fff;
  }
}

#hide-notice-btn,
#hide-process-btn,
#hide-log-btn,
#hide-set-btn {
  height: 20px;
  line-height: 20px;
  width: auto;
  background-color: #fff;
  border: 1px solid gray;
  border-radius: 3px;
  font-size: 12px;
  padding: 0 5px;
  font-family: 'fangsong';
  cursor: pointer;
  float: right;

  &:hover {
    background-color: gray;
    color: #fff;
  }
}
`

// 页面Html
var popHtml = `
<div class="header">Sweek学习通助手[3.0.7]
  <button id="hide-btn">显示/隐藏</button>
  <div class="btn-content">
    <a href="https://www.sweek.top/usingtutorials" target="_blank">
      <button class="btn"><i class="bi bi-book"></i>使用教程</button>
    </a>
    <a href="https://www.sweek.top/api/scripts/常见问题.pdf" target="_blank">
      <button class="btn"><i class="bi bi-journal-x"></i>常见问题</button>
    </a>
    <a href="https://scriptcat.org/zh-CN/script-show-page/2453/comment" target="_blank">
      <button class="btn"><i class="bi bi-chat-square-heart"></i>给个好评</button>
    </a>
    <a href="https://scriptcat.org/zh-CN/users/150865" target="_blank">
      <button class="btn"><i class="bi bi-star"></i>点个关注</button>
    </a>
  </div>
</div>
<div class="content" id="my-window-content">
  <div class="tab-bar">
    <div id="tab1" class="tab active"><i class="bi bi-clipboard-fill"></i>通知公告</div>
    <div id="tab2" class="tab"><i class="bi bi-graph-up"></i>任务进度</div>
    <div id="tab3" class="tab"><i class="bi bi-code-slash"></i>执行日志</div>
    <div id="tab4" class="tab"><i class="bi bi-gear"></i>脚本配置</div>
  </div>
  <div class="row" style="padding: 5px;">
    <div class="content-title" id="content-title"></div>
    <div id="content-async-time"><span id="async-time">[任务进度同步中...]</span></div>
    <div class="content-notice" id="content-notice"></div>
    <div class="content-process" id="content-process"></div>
    <div class="content-log" id="content-log"></div>
    <div class="content-set" id="content-set">
      <div class="row" style="padding: 5px 0;border-bottom: 2px solid #579572;">
        <div class="content-set-title"><i class="bi bi-1-square-fill"></i>邮箱配置</div>
        <div class="content-set-tips">
          <div>
            <i class="bi bi-info-circle"></i>配置步骤(请按照步骤依次配置)
          </div>
          <div>
            <1>点击下方网站注册链接,跳转到网站SweekWebsite,<span style="text-decoration: underline;">进入首页,点击右上角注册/登录按钮,会弹出登录界面,然后使用邮箱进行注册。</span>
          </div>
          <div>
           [网站注册链接:
            <a href="https://www.sweek.top" style="color: dodgerblue" target="_blank">
              <span><i class="bi bi-link-45deg"></i>点击跳转,前往注册</span>
            </a>]
          </div>
          <div>
            <2>注册完成之后在下方邮箱输入框内输入刚才注册的邮箱地址,<span style="text-decoration: underline;">输入完成之后点击保存,弹出保存成功提示,说明邮箱配置成功。</span>
          </div>
          <div>
            <3>以上操作完成之后,可以正常使用章节测试AI答题,并且课程信息将会同步到网站SweekWebsite,<span style="text-decoration: underline;">可点击下方蓝色字体打开弹窗,微信扫码进入网站使用你的注册邮箱登录,查看课程进度。</span>
          </div>
          <div>
          [微信扫码弹窗:
          <a style="color: dodgerblue;cursor: pointer;" id="Qcode">
            <i class="bi bi-hand-index"></i>点击查看,微信扫码
          </a>]
          </div>
        </div>
        <div class="content-set-tips">
          <div>
            <i class="bi bi-info-circle"></i>邮箱区分大小写。
          </div>
        </div>
        <div class="content-set-content">
          <label for="email-input">[邮箱]</label>
          <div class="control">
            <input type="email" id="email-input" placeholder="请输入邮箱地址">
            <button id="save-btn">保存</button>
          </div>
        </div>
      </div>
       <div class="row" style="padding: 5px 0;border-bottom: 2px solid #579572;">
        <div class="content-set-title"><i class="bi bi-2-square-fill"></i>章节测试配置</div>
        <div class="content-set-tips">
          <div>
            <i class="bi bi-info-circle"></i>启用【处理章节测试】功能之前,必须先配置上方邮箱,否则无法启用。
          </div>
        </div>
        <div class="content-set-tips">
          <div>
            <i class="bi bi-info-circle"></i>由于本脚本暂时还未接入第三方题库,目前采用AI处理章节测试,鉴于AI对于理科类题目正确率过低以及图片类题目无法处理,如果您对于正确率有较高要求,建议在开启【处理章节测试】之后,将【章节测试完成】设置为【完成后暂时保存】。
          </div>
        </div>
        <div class="content-set-tips">
          <div>
            <i class="bi bi-info-circle"></i>任务进度中可查看答题情况,如果出现乱码会导致答题异常,那是由于油猴中使用的题目解密文件访问不通导致,请切换至【脚本猫】使用本脚本,或者【挂梯子】使用本脚本可解决乱码问题。
          </div>
        </div>
        <div class="content-set-content">
          <label for="email-input">[是否章节测试]</label>
          <div class="control">
            <select id="ifTestSelect">
              <option value="take">处理章节测试</option>
              <option value="skip">跳过章节测试</option>
            </select>
          </div>
        </div>
        <div class="content-set-content">
          <label for="email-input">[章节测试完成]</label>
          <div class="control">
            <select id="testTypeSelect">
              <option value="save">完成后暂时保存</option>
              <option value="submit">完成后提交</option>
            </select>
          </div>
        </div>
      </div>
      <div class="row" style="padding: 5px 0;border-bottom: 2px solid #579572;">
        <div class="content-set-title"><i class="bi bi-3-square-fill"></i>功能配置</div>
        <div class="content-set-tips">
          <div>
            <i class="bi bi-info-circle"></i>启用复习模式之后,已完成的视频音频ppt将会加入任务中。
          </div>
        </div>
        <div class="content-set-content">
          <label for="email-input">[复习模式]</label>
          <div class="control">
            <select id="ifReviewSelect">
                <option value="1">启用</option>
                <option value="0">关闭</option>
            </select>
          </div>
        </div>
      </div>
      <div class="row" style="padding: 5px 0;border-bottom: 2px solid #579572;">
        <div class="content-set-title"><i class="bi bi-4-square-fill"></i>视频配置</div>
        <div class="content-set-tips">
          <div>
            <i class="bi bi-info-circle"></i>【视频倍速】作者破解中,目前只能开启视频允许倍速。
          </div>
        </div>
        <div class="content-set-content">
          <label for="email-input">[视频倍速]</label>
          <div class="control">
            <select id="playbackRateSelect">
                <option value="1">1x</option>
                <option value="2">2x[部分场景使用]</option>
                <option value="3">3x[暂时无法使用]</option>
                <option value="4">4x[暂时无法使用]</option>
            </select>
          </div>
        </div>
        <div class="content-set-content">
          <label for="email-input">[视频音量]</label>
          <div class="control">
            <select id="volumeSelect">
              <option value="0">静音</option>
              <option value="0.2">20</option>
              <option value="0.4">40</option>
              <option value="0.6">60</option>
              <option value="0.8">80</option>
              <option value="1">100</option>
            </select>
          </div>
        </div>
      </div>
      <div class="row" style="padding: 5px 0;border-bottom: 2px solid #579572;">
        <div class="content-set-title"><i class="bi bi-5-square-fill"></i>间隔时间配置</div>
        <div class="content-set-tips">
          <div>
            <i class="bi bi-info-circle"></i>【进度同步间隔】为本脚本将课程实时进度同步到邮箱账号的间隔时间。
          </div>
        </div>
        <div class="content-set-content">
          <label for="email-input">[进度同步间隔]</label>
          <div class="control">
            <select id="asyncCourseIntervalSelect">
              <option value="60000">1分钟</option>
              <option value="180000">3分钟</option>
              <option value="300000">5分钟</option>
              <option value="600000">10分钟</option>
            </select>
          </div>
        </div>
        <div class="content-set-content">
          <label for="email-input">[章节任务间隔]</label>
          <div class="control">
            <select id="taskIntervalSelect">
              <option value="4000">4s</option>
              <option value="6000">6s</option>
              <option value="8000">8s</option>
              <option value="10000">10s</option>
            </select>
          </div>
        </div>
        <div class="content-set-content">
          <label for="email-input">[章节测试间隔]</label>
          <div class="control">
            <select id="testIntervalSelect">
              <option value="4000">4s</option>
              <option value="6000">6s</option>
              <option value="8000">8s</option>
              <option value="10000">10s</option>
            </select>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>
`
// 初始化添加页面弹窗以及悬浮球
function initPopup() {
  // 添加CSS样式
  GM_addStyle(popCSs);
  // 创建窗口元素
  const myWindow = document.createElement("div");
  myWindow.id = "my-window";
  myWindow.innerHTML = popHtml;
  // 获取页面body元素
  const body = document.getElementsByTagName("body")[0];
  // 添加窗口和悬浮球到页面
  body.appendChild(myWindow);
  // 绑定隐藏窗口按钮的click事件
  const hideBtn = document.querySelector("#hide-btn");
  hideBtn.addEventListener("click", hideWindow);


  // 绑定隐藏公告按钮的click事件
  const tab1Btn = document.querySelector("#tab1");
  tab1Btn.addEventListener("click", function() {
    switchTab('tab1');
  });

  // 绑定隐藏播放进度按钮的click事件
  const tab2Btn = document.querySelector("#tab2");
  tab2Btn.addEventListener("click", function() {
    switchTab('tab2');
  });

  // 绑定隐藏日志按钮的click事件
  const tab3Btn = document.querySelector("#tab3");
  tab3Btn.addEventListener("click", function() {
    switchTab('tab3');
  });

  // 绑定隐藏设置按钮的click事件
  const tab4Btn = document.querySelector("#tab4");
  tab4Btn.addEventListener("click", function() {
    switchTab('tab4');
  });




  // 获取弹窗内容
  var htmlContent = '<div style="font-size: 16px;line-height: 25px;font-family: fangsong;">1.左侧二维码为网站SweekWebsite,登录可以查看课程进度(微信扫码),右侧二维码为qq脚本交流群(qq扫码)</div><div style="width: 100%; height: 400px;display: flex;justify-content: flex-start;align-items: center;"><img style="width: 400px; height: 400px;" src="https://www.sweek.top/api/preview/Qcode.png" alt=""><img style="height: 400px;" src="https://www.sweek.top/api/preview/Qrcode.jpg" alt=""></div><div style="font-size: 16px;line-height: 25px;font-family: fangsong;margin-top: 20px;">2.以下两张图片为同步的课程进度内容示意图,可以查看当前脚本挂机课程的实时进度信息</div><div><img style="height: 600px;" src="https://www.sweek.top/api/preview/demo1.jpg" alt=""><img style="height: 600px;" src="https://www.sweek.top/api/preview/demo2.jpg" alt=""></div>';

  // 点击链接显示弹窗
  document.getElementById('Qcode').addEventListener('click', function() {
    showCustomPopup(htmlContent, 800, 500);
  });
  

  // 获取头部元素
  const header = myWindow.querySelector('.header');

  // 处理移动事件
  let isDragging = false;
  let mouseX = 0;
  let mouseY = 0;

  header.addEventListener('mousedown', function (e) {
    e.preventDefault();
    isDragging = true;
    mouseX = e.clientX;
    mouseY = e.clientY;
  });

  document.addEventListener('mousemove', function (e) {
    if (isDragging) {
      const deltaX = e.clientX - mouseX;
      const deltaY = e.clientY - mouseY;
      const newLeft = Math.min(
        Math.max(0, myWindow.offsetLeft + deltaX),
        window.innerWidth - myWindow.offsetWidth
      );
      const newTop = Math.min(
        Math.max(0, myWindow.offsetTop + deltaY),
        window.innerHeight - myWindow.offsetHeight
      );
      myWindow.style.left = newLeft + 'px';
      myWindow.style.top = newTop + 'px';
      mouseX = e.clientX;
      mouseY = e.clientY;
    }
  });

  document.addEventListener('mouseup', function () {
    isDragging = false;
  });

  document.getElementById('ifReviewSelect').addEventListener('change', function() {
      updateIfReview(this.value);
  });
  document.getElementById("ifReviewSelect").value = ifReview;
  document.getElementById('playbackRateSelect').addEventListener('change', function() {
      updatePlaybackRate(this.value);
  });
  document.getElementById("playbackRateSelect").value = playbackRate;
  document.getElementById('volumeSelect').addEventListener('change', function() {
      updateVolume(this.value);
  });
  document.getElementById("volumeSelect").value = volume;
  
  document.getElementById('testTypeSelect').addEventListener('change', function() {
      updateTestType(this.value);
  });
  document.getElementById("testTypeSelect").value = testType;

  document.getElementById('ifTestSelect').addEventListener('change', function() {
      updateIfTest(this.value);
  });
  document.getElementById("ifTestSelect").value = ifTest;

  document.getElementById('asyncCourseIntervalSelect').addEventListener('change', function() {
      updateAsyncCourseInterval(this.value);
  });
  document.getElementById("asyncCourseIntervalSelect").value = asyncCourseInterval;

  document.getElementById('taskIntervalSelect').addEventListener('change', function() {
      updateTaskInterval(this.value);
  });
  document.getElementById("taskIntervalSelect").value = taskInterval;

  document.getElementById('testIntervalSelect').addEventListener('change', function() {
      updateTestInterval(this.value);
  });
  document.getElementById("testIntervalSelect").value = testInterval;
}






// 当下拉框的值改变时,更新变量值
function updateIfReview(val) {
  ifReview = val
  GM_setValue("ifReview", ifReview);
  notify(`复习模式已${ifReview == '1' ? '启用' : '关闭'}`, 2500)
}
function updatePlaybackRate(val) {
  playbackRate = val
  GM_setValue("playbackRate", playbackRate);
  // 刷新页面
  location.reload();
}
function updateVolume(val) {
  volume = val
  GM_setValue("volume", volume);
  // 刷新页面
  location.reload();
}
function updateIfTest(val) {
  if(val == "take") {
    // 获取输入的邮箱地址
    const email = GM_getValue("savedEmail") || '';
    // 检查邮箱地址是否有效
    if (!isValidEmail(email)) {
      notify('请输入有效的邮箱地址!', 5000)
      document.getElementById('ifTestSelect').value = 'skip';
      return;
    }
    
    const url = 'https://www.sweek.top/api/checkEmailExists';
    const data = { 
      email
    };
    if(email) {
      fetch(url, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json', // 声明请求主体的内容类型为 JSON
        },
        body: JSON.stringify(data), // 将数据对象转换为 JSON 字符串并作为请求主体
      }).then(response => {
        if (!response.ok) {
          throw new Error('Network response was not ok');
        }
        return response.json(); // 解析 JSON 响应数据
      }).then(data => {
        if(data.exists){
          ifTest = val
          GM_setValue("ifTest", ifTest);
          notify(`已将章节测试设为${ifTest == 'take' ? '处理' : '跳过'}状态`, 2500)
        } else {
          document.getElementById('ifTestSelect').value = 'skip';
          notify('邮箱账号尚未注册,请扫码前往留言信箱网站注册,然后再尝试启用章节测试刷题功能', 5000)
        }
      }).catch(error => {
        // console.error('Error:', error);
      });
    }
  } else if (val == "skip"){
    ifTest = val
    GM_setValue("ifTest", ifTest);
    notify(`已将章节测试设为${ifTest == 'take' ? '处理' : '跳过'}状态`, 2500)
  }
}
function updateTestType(val) {
  testType = val
  GM_setValue("testType", testType);
  notify(`章节测试完成后将会${testType == 'save' ? '暂时保存' : '提交'}`, 2500)
  // 刷新页面
  // location.reload();
}
function updateAsyncCourseInterval(val) {
  asyncCourseInterval = val
  GM_setValue("asyncCourseInterval", asyncCourseInterval);
  notify(`章节任务进度同步间隔已设置为${asyncCourseInterval/1000}秒`, 2500)
  clearInterval(syncCourseId);
  syncCourseId = null;
  // 定时同步课程任务进度
	syncCourseId = setInterval(() => {
    syncCourseData()
	}, asyncCourseInterval);
  // 刷新页面
  // location.reload();
}
function updateTaskInterval(val) {
  taskInterval = val
  GM_setValue("taskInterval", taskInterval);
  notify(`章节任务执行间隔已设置为${taskInterval/1000}秒`, 2500)
  // 刷新页面
  // location.reload();
}
function updateTestInterval(val) {
  testInterval = val
  GM_setValue("testInterval", testInterval);
  notify(`章节测试执行间隔已设置为${testInterval/1000}秒`, 2500)
  // 刷新页面
  // location.reload();
}



function switchTab(tabId) {
  GM_setValue("activeTab", tabId);
  // 移除所有选项卡的活动状态
  const tabs = document.querySelectorAll('.tab');
  tabs.forEach(tab => tab.classList.remove('active'));
  
  // 设置当前选项卡为活动状态
  document.getElementById(tabId).classList.add('active');

  var contentNotice = document.getElementById("content-notice");
  var contentProcess = document.getElementById("content-process");
  var contentLog = document.getElementById("content-log");
  var contentSet = document.getElementById("content-set");
  var contentTitlt = document.getElementById("content-title");
  var contentAsyncTime = document.getElementById("content-async-time");
  switch (tabId) {
    case 'tab1':
      contentTitlt.innerHTML = '通知公告' + `<span style="margin-left: 10px; color: #333;font-size: 10px;">阅读次数:${readNum}</span>`
      contentAsyncTime.style.display = 'none'
      contentNotice.style.display = "block";
      contentProcess.style.display = "none";
      contentLog.style.display = "none";
      contentSet.style.display = "none";
      break;
    case 'tab2':
      contentTitlt.innerHTML = '任务进度'
      contentAsyncTime.style.display = 'block'
      contentNotice.style.display = "none";
      contentProcess.style.display = "block";
      contentLog.style.display = "none";
      contentSet.style.display = "none";
      break;
    case 'tab3':
      contentTitlt.innerHTML = '执行日志'
      contentAsyncTime.style.display = 'none'
      contentNotice.style.display = "none";
      contentProcess.style.display = "none";
      contentLog.style.display = "block";
      contentSet.style.display = "none";
      break;
    case 'tab4':
      contentTitlt.innerHTML = '脚本配置'
      contentAsyncTime.style.display = 'none'
      contentNotice.style.display = "none";
      contentProcess.style.display = "none";
      contentLog.style.display = "none";
      contentSet.style.display = "block";
      break;
  
    default:
      break;
  }
}




// 隐藏窗口函数
function hideWindow() {
  var myWindowContent = document.getElementById("my-window-content");
  var showPop = myWindowContent.style.display
  if (showPop == '' || showPop == 'block') {
    myWindowContent.style.display = "none";
  } else {
    myWindowContent.style.display = "block";
  }
}

function takeEmail() {
  // 获取邮箱输入框和保存按钮
  const emailInput = document.getElementById("email-input");
  const saveBtn = document.getElementById("save-btn");

  // 当保存按钮被点击时
  saveBtn.addEventListener("click", function() {
    // 获取输入的邮箱地址
    const email = emailInput.value.trim();
    // 检查邮箱地址是否有效
    if (!isValidEmail(email)) {
      notify('请输入有效的邮箱地址!', 2500)
      return;
    }
    
    const url = 'https://www.sweek.top/api/checkEmailExists';
    const data = { 
      email
    };
    if(email) {
      fetch(url, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json', // 声明请求主体的内容类型为 JSON
        },
        body: JSON.stringify(data), // 将数据对象转换为 JSON 字符串并作为请求主体
      }).then(response => {
        if (!response.ok) {
          throw new Error('Network response was not ok');
        }
        return response.json(); // 解析 JSON 响应数据
      }).then(data => {
        if(data.exists){
          // 将邮箱地址存储到本地存储中
          GM_setValue("savedEmail", email);
          // 提示保存成功
          notify('保存成功!课程信息将同步到该邮箱账号', 2500)
        } else {
          notify('邮箱账号尚未注册,请扫码前往留言信箱网站注册', 2500)
        }
      }).catch(error => {
        // console.error('Error:', error);
      });
    }

  });

  // 页面加载时,尝试从本地存储中获取已保存的邮箱地址并反显到输入框中
  const savedEmail = GM_getValue("savedEmail") || '';
  if (savedEmail) {
    emailInput.value = savedEmail;
  }
}

// 邮箱地址验证函数
function isValidEmail(email) {
  // 此处可以使用正则表达式等方式进行邮箱地址的验证
  // 这里简单地判断邮箱是否包含 '@' 符号
  return email.includes("@");
}




// 封装页面组件方法
// 封装页面组件方法
// 封装页面组件方法

function showCustomPopup(htmlContent, width = 'auto', height = 'auto') {
  // 禁用页面滚动
  document.body.style.overflow = 'hidden';

  // 创建遮罩层
  var overlay = document.createElement('div');
  overlay.className = 'custom-popup-overlay';
  Object.assign(overlay.style, {
      position: 'fixed',
      top: '0',
      left: '0',
      width: '100%',
      height: '100%',
      backgroundColor: 'rgba(0, 0, 0, 0.5)',
      zIndex: '999998',
      opacity: '0',
      transition: 'opacity 0.3s ease-in-out'
  });

  // 创建弹窗容器
  var popupContainer = document.createElement('div');
  popupContainer.className = 'custom-popup-container';
  Object.assign(popupContainer.style, {
      position: 'fixed',
      top: '50%',
      left: '50%',
      transform: 'translate(-50%, -50%) scale(0.8)',
      width: typeof width === 'number' ? `${width}px` : width,
      height: typeof height === 'number' ? `${height}px` : height,
      maxWidth: '90%',
      maxHeight: '90%',
      backgroundColor: '#fff',
      border: '1px solid #ccc',
      borderRadius: '8px',
      boxShadow: '0px 4px 10px rgba(0, 0, 0, 0.2)',
      display: 'flex',
      flexDirection: 'column',
      overflow: 'hidden',
      opacity: '0',
      transition: 'opacity 0.3s ease-in-out, transform 0.3s ease-in-out',
      zIndex: '999999'
  });

  // 创建关闭按钮
  var closeButton = document.createElement('button');
  closeButton.innerHTML = '×';
  Object.assign(closeButton.style, {
      position: 'absolute',
      top: '10px',
      right: '10px',
      padding: '5px 10px',
      backgroundColor: '#ff4d4f',
      color: '#fff',
      border: 'none',
      borderRadius: '50%',
      cursor: 'pointer',
      fontSize: '16px',
      fontWeight: 'bold',
      width: '30px',
      height: '30px',
      display: 'flex',
      alignItems: 'center',
      justifyContent: 'center',
      transition: 'background-color 0.3s'
  });

  closeButton.addEventListener('mouseenter', () => {
      closeButton.style.backgroundColor = '#ff7875';
  });

  closeButton.addEventListener('mouseleave', () => {
      closeButton.style.backgroundColor = '#ff4d4f';
  });

  // 关闭弹窗方法
  function closePopup() {
      popupContainer.style.opacity = '0';
      popupContainer.style.transform = 'translate(-50%, -50%) scale(0.8)';
      overlay.style.opacity = '0';
      setTimeout(() => {
          document.body.removeChild(popupContainer);
          document.body.removeChild(overlay);
          document.body.style.overflow = ''; // 恢复滚动
          document.removeEventListener('keydown', keyPressHandler);
      }, 300);
  }

  // 关闭按钮点击事件
  closeButton.addEventListener('click', closePopup);

  // 点击遮罩层关闭弹窗
  overlay.addEventListener('click', closePopup);

  // 监听 ESC 按键关闭弹窗
  function keyPressHandler(event) {
      if (event.key === 'Escape') {
          closePopup();
      }
  }
  document.addEventListener('keydown', keyPressHandler);

  // 创建内容容器(防止覆盖按钮)
  var contentContainer = document.createElement('div');
  Object.assign(contentContainer.style, {
      flex: '1',
      padding: '20px',
      overflowY: 'auto'
  });

  // 避免 innerHTML 直接赋值导致 XSS 攻击
  if (typeof htmlContent === 'string') {
      contentContainer.innerHTML = htmlContent;
  } else if (htmlContent instanceof HTMLElement) {
      contentContainer.appendChild(htmlContent);
  }

  // 组合元素
  popupContainer.appendChild(closeButton);
  popupContainer.appendChild(contentContainer);
  document.body.appendChild(overlay);
  document.body.appendChild(popupContainer);

  // 动画显示弹窗
  setTimeout(() => {
      overlay.style.opacity = '1';
      popupContainer.style.opacity = '1';
      popupContainer.style.transform = 'translate(-50%, -50%) scale(1)';
  }, 10);
}



// 页面通知提示
function notify(text, time) {
  // 创建通知元素
  var notification = document.createElement('div');
  notification.className = 'notification';
  // 设置通知内容
  notification.innerHTML = '<div class="notification-content"><h2 style="font-size: 16px;font-weight: bold;color: #307dff;font-family: fangsong;">' + 'Sweek学习通助手提示' + '</h2><p style="font-family: fangsong;font-size: 13px;font-weight: bold;">' + text + '</p></div>';
  // 将通知添加到页面中
  document.body.appendChild(notification);
  // 设置通知样式
  notification.style.position = 'fixed';
  notification.style.top = '50px';
  notification.style.left = '-400px'; // 从左边弹出
  notification.style.transform = 'translateY(-50%)'; // 垂直居中
  notification.style.transition = 'left 0.5s ease-in-out'; // 添加过渡效果
  notification.style.zIndex = '999999';
  notification.style.backgroundColor = '#fff';
  notification.style.border = '1px solid #ccc';
  notification.style.padding = '10px';
  notification.style.borderRadius = '5px';
  notification.style.lineHeight = '25px';

  // 等待一小段时间后,移动通知到左边
  setTimeout(function() {
      notification.style.left = '20px'; // 移动到左边
  }, 100);

  // 设置定时器,在指定时间后移除通知
  setTimeout(function() {
      // 移动通知到左边以外
      notification.style.left = '-400px';
      // 等待过渡效果完成后,移除通知元素
      setTimeout(function() {
      notification.remove();
      }, 500);
  }, time);
}

// 添加执行日志
function addlog(str, color) {
  var _time = new Date().toLocaleTimeString()
  var contentLog = window.top.document.querySelector('.content-log');
  var newContent = '<p style="color: ' + color + ';">[' + _time + ']' + str + '</p><hr>';
  contentLog.innerHTML += newContent;
  // 将滚动条滚动到底部
  contentLog.scrollTop = contentLog.scrollHeight;
}


/** 监听事件-章节任务 */
document.addEventListener("redeal", () => {
    dealAnsEchelon(arrayEchelon);
});

/** 监听事件-章节测试 */
document.addEventListener("testRedeal", () => {
    dealTestEchelon(testArrayEchelon);
});


// 章节任务处理
// 章节任务处理
// 章节任务处理

/** 初始化 */
function initAll() {
  addlog('正在获取当前页面的任务...');
  const arrayAnsAll = document.querySelectorAll(".ans-attach-ct");
  addlog(`当前页面任务数量为: ${Array.from(arrayAnsAll).length}`);
  // 获取当前页面将会处理的任务-[根据是否启用复习模式]
  const arrayAns = ifReview == 1 ? document.querySelectorAll(".ans-attach-ct") : document.querySelectorAll('.ans-attach-ct:not(.ans-job-finished)')
  const taskCount = arrayAns.length;
  // addlog(`找到的任务数量: ${taskCount}`);
  if (arrayAns && taskCount > 0) {
    try {
      const arrayType = getIframesType(arrayAns);
      console.log('任务类型数组:', arrayType);

      const arrayDocument = getAllIframesDocument(arrayAns);
      console.log('任务文档数组:', arrayDocument);

      arrayEchelon = distributeAns(arrayType, arrayDocument);
      // addlog(`当前页面待办任务数量为: ${arrayEchelon.length}`);

      // 触发处理任务的事件
      document.dispatchEvent(dealEvent);
    } catch (error) {
      // console.error("初始化过程中发生错误:", error);
    }
  } else {
    addlog("当前页面没有可处理的任务,直接跳过章节");
    // 如果没有任务,
    skipChapter();
  }
}

// 获取任务属性
function getIframesType(arrayAns) {
  return Array.from(arrayAns)
      .map(ans => {
          const jsonStr = ans.querySelector("iframe")?.getAttribute("data");
          if (!jsonStr) {
              // console.warn("未找到 data 属性或 data 属性为空。");
              return null;
          }
          try {
              const json = JSON.parse(jsonStr);
              // 根据需求获取任务类型
              // 课程类型
              if(json && json.type) {
                return json.type;
              }
              // 测试类型
              if(json && json.worktype) {
                return json.worktype;
              }
          } catch (error) {
              // console.warn("解析 JSON 失败:", error);
              return null;
          }
      })
      .filter(type => type !== null);
}

// 获取任务 document
function getAllIframesDocument(arrayAns) {
return Array.from(arrayAns)
  .map(ans => ans.querySelector("iframe")?.contentWindow?.document)
  .filter(doc => doc !== undefined && doc !== null);
}

// 按任务属性映射处理函数
const handlerMap = {
  ".mp4": videoHandler,
  ".wmv": videoHandler,
  ".avi": videoHandler,
  ".mkv": videoHandler,
  ".flv": videoHandler,
  ".mov": videoHandler,
  ".doc": pptxHandler,
  ".docx": pptxHandler,
  ".pptx": pptxHandler,
  ".pdf": pptxHandler,
  ".ppt": pptxHandler,
  ".mp3": audioHandler,
  ".wav": audioHandler,
  "workA": testHandler
};

// 按任务属性分配执行函数
function distributeAns(arrayType, arrayDocument) {
  return arrayType.map((type, index) => {
      const handler = handlerMap[type] || ignoreAns;
      return { document: arrayDocument[index], handler };
  });
}

// 处理单个任务
function dealSingleAns(singleAns) {
  if (singleAns && singleAns.handler && typeof singleAns.handler === 'function') {
      singleAns.handler(singleAns.document);
  } else {
      // console.warn("无效的任务或处理函数。");
      document.dispatchEvent(dealEvent);
  }
}


// 章节测试处理
// 章节测试处理
// 章节测试处理


// 按章节测试题目属性映射处理函数
const handlerTestMap = {
  "单选题": choiceHandler,
  "多选题": choiceHandler,
  "判断题": choiceHandler
};

// 按任务属性分配执行函数
function distributeTest(arrayType) {
  return arrayType.map((item, index) => {
      const handler = handlerTestMap[item.type] || ignoreTest;
      return { document: item.document, handler, testObj: item };
  });
}

// 处理单个章节测试任务
function dealSingleTest(singleAns) {
  if (singleAns && singleAns.handler && typeof singleAns.handler === 'function') {
      singleAns.handler(singleAns.document, singleAns.testObj);
  } else {
      // console.warn("无效的任务或处理函数。");
      document.dispatchEvent(testDealEvent);
  }
}


/** 工具函数 */
/** 工具函数 */
/** 工具函数 */

// 章节测试执行方法
// 章节测试执行方法
// 章节测试执行方法


// 忽略章节测试题
function ignoreTest() {
	addlog("无法处理, 忽略该章节测试题");
	setTimeout(() => {
			document.dispatchEvent(testDealEvent);
	}, testInterval);
}

// 处理单选题/多选题/判断题
function choiceHandler(idocument, testObj) {
  // console.log('idocument:::+ ', idocument)
  // console.log('testObj:::+ ', testObj)
  addlog(`处理${testObj.type}任务中...`);
  fetch(fetch_url + testObj.question + '请你提供给我正确答案,不需要解析,答案需要精简,返回选项即可')
  .then(response => {
    let model_text = ''
    const reader = response.body.getReader()
    const decoder = new TextDecoder('utf-8')

    const readChunk = () => {
      reader.read().then(({ done, value }) => {
        if (done) {
          console.log('model_text:::+ ', model_text)
          const answer_index = findABCDEFPositions(model_text)
          console.log('answer_index:::+ ', answer_index)
          const trueOptions = Array.from(idocument.querySelectorAll('li'));
          trueOptions.forEach((option, index) => {
            if (answer_index.includes(index)) {
              option.click();
            }
          });
          const answer_text = mapIndexToOption(answer_index)
          console.log('answer_text:::+ ', answer_text)
          const currentTestTask = testTasks.find((task) => task.data == testObj.data)
          if (currentTestTask && answer_index.length > 0) {
            currentTestTask.model_text = model_text
            currentTestTask.answer_text = answer_text
            currentTestTask.status = true
          } else {
            currentTestTask.status = false
          }
          // 使用 findIndex 获取当前任务的索引
          const taskIndex = testTasks.findIndex((task) => task.data === testObj.data);
          const process = (((taskIndex + 1)/testTasks.length)*100).toFixed(2) + '%'
          console.log('testTasks:::+ ', testTasks)
          setProcess(process, taskIndex + 1, testTasks.length, 'test')
          // 同步章节测试题目-AI模型生成
          const email = GM_getValue("savedEmail") || ''
          const course_name = GM_getValue("courseName") || '未获取课程名称';
          const testList = [{ question_id: testObj.data, question: testObj.question, answer: answer_text, type: testObj.type, email: email, course_name: course_name }]
          insertTestModel(testList)
          setTimeout(() => {
            document.dispatchEvent(testDealEvent);
          }, testInterval);
          return
        }
        const decodedValue = decoder.decode(value, { stream: true })
        model_text += decodedValue
        readChunk()
      })
    }
    readChunk()
  })
  .catch(error => {
    // 在这里处理错误
    // console.error('Error:', error);
  });
}

/** 章节测试任务梯队顺序处理 */
function dealTestEchelon(testArrayEchelon) {
  const remainingTasks = testArrayEchelon.length;
  addlog(`待处理章节测试任务数量为: ${remainingTasks}`);
  if (remainingTasks === 0) {
    // 获取章节测试题完成度
    const test_process = getTrueStatusPercentage(testTasks)
    const save_btn = testBtnDocument.querySelector('.btnSave')
    const submit_btn = testBtnDocument.querySelector('.btnSubmit')
    const mask_div = window.top.document.querySelector('.maskDiv')
    const pop_ok = mask_div.querySelector('#popok')
    if(test_process == 100) {
      addlog('章节测试任务已全部完成');
      switch (testType) {
        case 'save':
          save_btn.click()
          addlog('章节测试答案已暂时保存');
          break;
        case 'submit':
          submit_btn.click()
          setTimeout(() => {
            pop_ok.click()
            setTimeout(() => {
              const TiMus = getTestTopics()
              // console.log('TiMus:::+ ', TiMus)
              // 计算正确率
              const rightRate = (TiMus.length/testTasks.length*100).toFixed(2) + '%';
              notify(`章节测试答题情况[${TiMus.length}/${testTasks.length}],章节测试正确率[${rightRate}]`, 5000)
              insertTest(TiMus)
            }, 3000);     
          }, 1000);
          addlog('章节测试答案已提交');
          break;
        default:
          break;
      }
    } else {
      addlog(`章节测试任务完成度为${test_process.toFixed(2)}%,不足100%,章节测试答案将暂时保存`);
      save_btn.click()
    }
    setTimeout(() => {
      if(testType == 'submit') {
        
      }
      addlog("章节测试任务已完成");
      dealAnsEchelon(arrayEchelon);
    }, 10000);
    return;
  }

  const nextTask = testArrayEchelon.shift();
  try {
    dealSingleTest(nextTask);
  } catch (error) {
      // console.error("处理任务时发生错误:", error);
      // 继续处理下一个任务
      dealTestEchelon(testArrayEchelon);
  }
}

// 课程任务执行方法
// 课程任务执行方法
// 课程任务执行方法

// 直接跳过
function skipChapter() {
	addlog("跳过章节");
	const chapterNext = window.top.document.querySelector("#prevNextFocusNext");
	if (chapterNext) {
			chapterNext.click();
	} else {
			// console.warn("未找到 #prevNextFocusNext 元素。");
	}

	setTimeout(() => {
			const tip = document.querySelector(".maskDiv.jobFinishTip.maskFadeOut");
			if (tip) {
					const tipNextChapter = document.querySelector(".jb_btn.jb_btn_92.fr.fs14.nextChapter");
					tipNextChapter?.click();
			} else {
					// console.warn("未找到完成提示或下一章节按钮。");
			}
	}, taskInterval);
}

// 忽略任务
function ignoreAns() {
	addlog("无法处理, 忽略任务");
	setTimeout(() => {
			document.dispatchEvent(dealEvent);
	}, taskInterval);
}

// 处理章节测试
function testHandler(idocument) {
  testDom = idocument
	addlog("处理章节测试任务中...");
  if (ifTest == 'take') {
    const iframe = idocument.querySelector("iframe");
    // console.log('iframe:::+ ', iframe)
    if (iframe) {
      const sDocument = iframe.contentWindow?.document;
      // console.log('sDocument:::+ ', sDocument)
      if(sDocument) {
        const form = sDocument.querySelector("form");
        // 存储章节测试按钮DOM
        testBtnDocument = sDocument.querySelector(".ZY_sub");
        // console.log('form:::+ ', form)
        if (form) {
          const testTemps = Array.from(form.querySelectorAll('.singleQuesId, .newTestType'));
          testTasks = testTemps.map((temp) => {
            // console.log('temp:::+ ', temp)
            return {
              document: temp,
              data: temp.getAttribute('data'),
              class: temp.getAttribute('class'),
              question: removeNewlines(temp.innerText),
              type: extractText(temp.innerText)
            }
          }).filter(item => item.class == "singleQuesId")
          // console.log('testTasks:::+ ', testTasks)
          testArrayEchelon = distributeTest(testTasks);
          // 触发处理章节测试任务的事件
          document.dispatchEvent(testDealEvent);
        }
      }
    }
  } else if (ifTest == 'skip') {
    setTimeout(() => {
      addlog("根据配置选项,跳过章节测试");
      document.dispatchEvent(dealEvent);
    }, taskInterval);
  }
}

// 处理视频
function videoHandler(idocument) {
  addlog("处理视频任务中...");
  
  const video = idocument.querySelector("video");
  const videoPlayButton = idocument.querySelector(".vjs-big-play-button");
  const modalDialog = idocument.querySelector(".vjs-modal-dialog-content");
  const closeButton = idocument.querySelector(".vjs-done-button");
  // console.log('video:::+ ', video)
  // console.log('videoPlayButton:::+ ', videoPlayButton)
  // 检查视频和播放按钮是否存在
  if (!video || !videoPlayButton) {
    addlog("没有找到视频或播放按钮,3s后将刷新页面");
    setTimeout(() => {
      ocation.reload(); // 刷新页面
    }, 3000);
    return;
  }

  const playVideo = () => {    
    if (video && typeof video.play === 'function') {
      console.log('1video.play():::+ ')
      video.muted = true;  // 静音
      video.play().catch((error) => {
        console.error('视频播放失败:', error);
        addlog("视频播放失败,尝试点击播放按钮");
        videoPlayButton.click();
      });
    } else {
      console.log('1videoPlayButton.click():::+ ')
      addlog("视频播放失败,尝试点击播放按钮");
      videoPlayButton.click();
    }
  }
  const clickPlayVideo = () => {  
    if (videoPlayButton && typeof videoPlayButton.click === 'function'){
      console.log('2videoPlayButton.click():::+ ')
      videoPlayButton.click();
    }  else  {
      console.log('2video.play():::+ ')
      video.muted = true;  // 静音
      video.play().catch((error) => {
        console.error('视频播放失败:', error);
        videoPlayButton.click();
      });
    } 
  }

  const closeModalDialog = () => {
    if (modalDialog && closeButton && modalDialog.getAttribute("aria-hidden") !== "true") {
      setTimeout(() => {
        closeButton.click();
      }, 2000);
    }
  };

  const onLoadedData = () => {
    video.volume = volume;
    addlog(`已将视频音量调节为${volume * 100}%`);

    video.playbackRate = playbackRate;
    addlog(`已将视频倍速调节为${playbackRate}X`);
  };

  const handlePause = () => {
    clickPlayVideo();
    closeModalDialog();
  };

  const updateProgress = () => {
    const currentTime = video.currentTime;
    const duration = video.duration;
    const progress = (currentTime / duration) * 100;
    setProcess(progress.toFixed(2) + '%', currentTime.toFixed(0), duration.toFixed(0), 'video');
  };

  video.addEventListener("loadeddata", onLoadedData);
  video.addEventListener("pause", handlePause);
  video.addEventListener("timeupdate", updateProgress);

  video.addEventListener("ended", () => {
    addlog("视频任务已完成");
    document.dispatchEvent(dealEvent);
    video.removeEventListener("pause", handlePause);
    video.removeEventListener("timeupdate", updateProgress);
  }, { once: true });

  setTimeout(() => {
    if (video.paused && !video.ended) {
      playVideo();
      addlog('由于程序出错未自动播放,现重新模拟点击播放按钮...');
    }
  }, 10000);

  playVideo();
}


// 处理音频
function audioHandler(idocument) {
	  addlog("处理音频任务中...");
    const audio = idocument.querySelector("audio");
    const audioPlayButton = idocument.querySelector(".vjs-play-control.vjs-control.vjs-button");

    if (!audio) {
        // console.warn("未找到音频元素。");
        document.dispatchEvent(dealEvent);
        return;
    }

    // 播放音频
    audioPlayButton?.click();

    // 监听音频播放结束事件
    audio.addEventListener("ended", () => {
        addlog("音频任务已完成");
        document.dispatchEvent(dealEvent);
    }, { once: true });

    // 监听音频暂停事件并在暂停时继续播放
    audio.addEventListener("pause", () => {
        // console.log("音频暂停,自动恢复播放...");
        audioPlayButton?.click();
    });
    // 监听音频进度变化
    audio.addEventListener("timeupdate", () => {
      const currentTime = audio.currentTime;
      const duration = audio.duration;
      const progress = (currentTime / duration) * 100;
      setProcess(progress.toFixed(2) + '%', currentTime.toFixed(0), duration.toFixed(0), 'audio');
    });
}

// 处理 PPT & PDF
function pptxHandler(idocument) {
  // 添加日志提示
  addlog("处理 PPT/PDF 任务中...");

  // 获取嵌入的 iframe 元素
  const iframe = idocument.querySelector("iframe");
  if (!iframe) {
    console.warn("未找到嵌入的 iframe 元素。");
    document.dispatchEvent(dealEvent);
    return;
  }

  const sDocument = iframe.contentWindow?.document;
  if (!sDocument) {
    console.warn("无法获取 PPT/PDF 的文档内容。");
    document.dispatchEvent(dealEvent);
    return;
  }

  let finalHeight = sDocument.documentElement.scrollHeight;
  let currentHeight = 0;

  const scrollStep = 5;  // 滚动的步长,较小的步长会让滚动更加平滑
  const maxHeight = finalHeight;  // 目标滚动的最大高度

  // 平滑滚动函数
  function smoothScroll() {
    finalHeight = sDocument.documentElement.scrollHeight; // 动态更新最终高度
    if (currentHeight >= maxHeight) {      
      addlog("PPT/PDF任务已完成");
      document.dispatchEvent(dealEvent); // 完成滚动
      return;
    }

    currentHeight += scrollStep; // 每次滚动小步长
    sDocument.defaultView.scrollTo(0, currentHeight); // 执行滚动

    // 计算滚动进度并更新
    const progress = (currentHeight / maxHeight) * 100;
    setProcess(
      progress.toFixed(2) + '%', 
      currentHeight.toFixed(0), 
      maxHeight.toFixed(0), 
      'pdf'
    );

    // 请求下一帧
    requestAnimationFrame(smoothScroll);
  }

  // 开始滚动动画
  smoothScroll();
}




/** 任务梯队顺序处理 */
function dealAnsEchelon(arrayEchelon) {
    const remainingTasks = arrayEchelon.length;
		addlog(`待处理任务数量为: ${remainingTasks}`);
    if (remainingTasks === 0) {
      addlog('该章节任务已处理完成,即将跳转下一章节')
      setTimeout(() => {
        skipChapter();
      }, taskInterval);
      return;
    }
    const nextTask = arrayEchelon.shift();
    try {
        dealSingleAns(nextTask);
    } catch (error) {
        // console.error("处理任务时发生错误:", error);
        // 继续处理下一个任务
        dealAnsEchelon(arrayEchelon);
    }
}


// 其他执行方法
// 其他执行方法
// 其他执行方法

// 获取章节测试题目-正确的
function getTestTopics() {

  // 获取第一个符合条件的 iframe 并获取其中的子 iframe
  const testIframe = testDom.querySelector("iframe")?.contentWindow?.document.querySelector('.CeYan');
  console.log('testIframe:::+ ', testIframe);

  // 如果没有找到目标元素,返回空数组
  if (!testIframe) {
    console.error('没有找到目标的测试 iframe');
    return [];
  }

  // 获取题目元素
  const TiMusDom = testIframe.querySelectorAll('.TiMu');
  console.log('TiMusDom:::+ ', TiMusDom);

  // 筛选出正确答案的题目
  const rightTiMusDom = Array.from(TiMusDom).filter(TiMu => {
    return TiMu.querySelector('.marking_dui') !== null;
  });
  console.log('rightTiMusDom:::+ ', rightTiMusDom);

  // 提取题目和答案
  const TiMus = Array.from(rightTiMusDom).map(TiMu => {
    const filteredText = Array.from(TiMu.children)
      .filter(child => !child.classList.contains('newAnswerBx') || !child.classList.contains('fl') ) // 过滤掉 class 为 newAnswerBx 的子元素
      .map(child => child.innerText) // 获取剩余子元素的 innerText
      .join(''); // 拼接所有文本内容

    const question = removeNewlines(filteredText);
    const answer = TiMu.querySelector('.answerCon')?.innerText || ''; // 使用 ? 确保 answerCon 存在
    const type = extractText(question)
    const email  = GM_getValue("savedEmail") || ''
    const course_name = GM_getValue("courseName") || '未获取课程名称';
    const question_id = TiMu.getAttribute('data') || ''
    return { question_id, question: question, answer: answer, type: type, email: email, course_name: course_name };
  });

  // console.log('TiMus:::+ ', TiMus);
  return TiMus;
}



// 获取任务成功占比
function getTrueStatusPercentage(arr) {
  // 过滤出status为true的元素
  const trueCount = arr.filter(item => item.status === true).length;

  // 计算占比
  const percentage = (trueCount / arr.length) * 100;

  return percentage;
}

// 转化时间格式-秒转化成MM:ss
function formatTime(seconds) {
  let hrs = Math.floor(seconds / 3600);
  let mins = Math.floor((seconds % 3600) / 60);
  let secs = seconds % 60;

  let formattedTime = [
      hrs > 0 ? String(hrs).padStart(2, '0') : null, 
      String(mins).padStart(2, '0'), 
      String(secs).padStart(2, '0')
  ].filter(Boolean).join(':');

  return formattedTime;
}


// 索引转换选项
function mapIndexToOption(arr) {
  arr = arr.map(item => {
    switch (item) {
      case 0:
        return 'A';
      case 1:
        return 'B';
      case 2:
        return 'C';
      case 3:
        return 'D';
      case 4:
        return 'E';
      case 5:
        return 'F';
      case 6:
        return 'G';
      default:
        break
    }
  })
  return arr.join('');
}

// 根据选项匹配索引
function findABCDEFPositions(str) {
  const result = [];
  const targetChars = ['A', 'B', 'C', 'D', 'E', 'F', 'G'];  // 目标字符集合
  targetChars.forEach((text, index) => {
    if(str.indexOf(text) !== -1) {
      result.push(index);  // 记录该字符的索引
    }
  })
  return result;
}

function removeNewlines(str) {
  // 使用正则表达式去除所有的换行符(\n)
  return str.replace(/\n/g, '');
}

function extractText(str) {
  // 使用正则表达式提取“【多选题】”中的“多选题”部分
  const match = str.match(/【(.*?)】/);
  return match ? match[1] : null;
}

// 获取当前时间,年月日时分秒
function getCurrentDateTime() {
	var now = new Date();
	var year = now.getFullYear();
	var month = (now.getMonth() + 1).toString().padStart(2, '0'); // 月份从0开始,需要加1,并确保两位数格式
	var day = now.getDate().toString().padStart(2, '0'); // 确保两位数格式
	var hours = now.getHours().toString().padStart(2, '0'); // 确保两位数格式
	var minutes = now.getMinutes().toString().padStart(2, '0'); // 确保两位数格式
	var seconds = now.getSeconds().toString().padStart(2, '0'); // 确保两位数格式

	return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}

// 设置播放进度
function setProcess(val1, val2, val3, type) {
  const _process = window.top.document.querySelector('#content-process');
  const _time = getCurrentDateTime();
  let newContent = `<p style="color: #000;">[${_time}]</p><hr>`;
  
  switch (type) {
      case 'video':
      case 'audio':
          const mediaType = type === 'video' ? '视频' : '音频';
          newContent += `<p>播放进度:${val1}</p><hr>`;
          newContent += `<p>播放倍速:${playbackRate}</p><hr>`;
          newContent += `<p>${mediaType}长度:${formatTime(val2)}/${formatTime(val3)}</p>`;
          break;
      
      case 'pdf':
          newContent += `<p>浏览进度:${val1}</p><hr>`;
          newContent += `<p>PDF高度:${val2}px/${val3}px</p>`;
          break;
      
      case 'test':
          newContent += `<p>[答题进度:${val1}] [${val2}/${val3}]</p>`;
          newContent += testTasks.map((item, index) => {
              const color = item.status ? 'green' : 'red';
              const answerText = item.answer_text || '暂无答案';
              return `<p><span style="color: ${color}">[${index + 1}]</span> <span>${answerText}</span></p><p>${item.question}</p>`;
          }).join('');
          break;
  }
  
  _process.innerHTML = newContent;
}


// 获取视频播放进度-定时监听页面内容进行下一步处理
function getVideoProgress() {
  if(location.pathname == '/mycourse/studentstudy') {
    // 脚本运行过程中如果弹出弹窗,发现后关闭-10s执行一次
		const jobFinishTip = document.querySelector(".jobFinishTip");
		const nextChapter = document.querySelector(".nextChapter");
    if (jobFinishTip) {
      const computedStyle = getComputedStyle(jobFinishTip);
      if (computedStyle.display !== 'none') {
        nextChapter.click()
      }
    }
  }
}

// 获取页面url
function getURLInfo() {
  if(location.pathname == '/mycourse/studentstudy') {
    url = location.href
    // 获取问号后面的部分
    var queryString = url.split('?')[1];
    // 将查询字符串拆分为参数对
    var queryParams = queryString.split('&');
    // 创建一个对象来存储参数
    var params = {};
    // 遍历参数对,将它们存储在对象中
    queryParams.forEach(function(queryParam) {
        var parts = queryParam.split('=');
        var key = decodeURIComponent(parts[0]);
        var value = decodeURIComponent(parts[1]);
        params[key] = value;
    });
    chapterId = params['chapterId']
    courseId = params['courseId']
    GM_setValue("courseId", courseId);
  }
}


/**
 * 获取课程所有章节节点数据
 */
function getChapterCodeInfo() {
  if(location.pathname === '/mooc-ans/knowledge/cards') {
    var chapter = window.top.document.querySelectorAll('.posCatalog_select')
    chapterInfo = chapter
    allChapterName=[]

    chapterInfo.forEach(function(item) {
      allChapterName.push({
        id: item.id,
        title: item.innerText,
        active: item.classList.contains('posCatalog_active') ? 1 : 0,
        ifTitle: item.classList.contains('firstLayer') ? 1 : 0,
        status: item.childNodes[3]?.className == 'icon_Completed prevTips' ? 1 : 0
      })
      if (item.classList.contains('posCatalog_active')) {
        currentChapterId = item.id
        GM_setValue("currentChapterId", currentChapterId);
        currentChapterName = item.innerText
        GM_setValue("currentChapterName", currentChapterName);
      }
    });
    GM_setValue("chapterInfo", JSON.parse(JSON.stringify(allChapterName)));
    addlog('当前章节为' + currentChapterName, 'green')
  }
}

// 获取公告数据
async function getBoard() {
  try {
    let response = await fetch('https://www.sweek.top/api/board');
    let data = await response.json();
    
    // 在这里处理接收到的数据
    let notice = document.querySelector('#content-notice');
    notice.innerHTML = data.text;
    readNum = data.num;
  } catch (error) {
    // 处理错误
    console.error('Error:', error);
  }
}

// 同步课程时间
async function syncCourseData() {
  if (location.pathname === "/mycourse/studentstudy") {
    const emailInput = document.getElementById("email-input");
    const email = emailInput.value.trim();
    if (!email) return;

    const checkEmailUrl = "https://www.sweek.top/api/checkEmailExists";
    const courseUrl = "https://www.sweek.top/api/insertOrUpdateCourse";

    try {
      // 检查邮箱是否存在
      const emailResponse = await fetch(checkEmailUrl, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ email }),
      });

      if (!emailResponse.ok) throw new Error("检查邮箱请求失败");

      const emailData = await emailResponse.json();
      if (!emailData.exists) {
        const _async_time = window.top.document.querySelector("#async-time");
        _async_time.innerHTML =
          '<span style="color: #000;">[同步课程信息失败,邮箱未注册]</span>';
        return;
      }

      // 获取课程相关信息
      const savedEmail = GM_getValue("savedEmail", "");
      const courseName = GM_getValue("courseName", "测试");
      const courseId = GM_getValue("courseId", "");
      const courseImg = GM_getValue("courseImg", "");
      const process = document.querySelector("#content-process").innerHTML;
      const chapterInfo = GM_getValue("chapterInfo", "");

      if (!savedEmail || !process) return;

      // 处理章节信息
      const chapters = [...window.top.document.querySelectorAll(".posCatalog_select")];
      const allChapterName = chapters.map((item) => {
        const isActive = item.classList.contains("posCatalog_active");
        const isFirstLayer = item.classList.contains("firstLayer");
        const isCompleted = item.childNodes[3]?.className === "icon_Completed prevTips";

        if (isActive) {
          GM_setValue("currentChapterId", item.id);
          GM_setValue("currentChapterName", item.innerText);
        }

        return {
          id: item.id,
          title: item.innerText,
          active: isActive ? 1 : 0,
          ifTitle: isFirstLayer ? 1 : 0,
          status: isCompleted ? 1 : 0,
        };
      });

      const requestData = {
        email: savedEmail,
        course_id: courseId,
        course_name: courseName,
        course_img: courseImg,
        chapter: JSON.stringify(allChapterName),
        current_chapter: `${GM_getValue("currentChapterId", "")},${GM_getValue("currentChapterName", "")}`,
        process,
      };

      // 发送课程数据同步请求
      const courseResponse = await fetch(courseUrl, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(requestData),
      });

      if (!courseResponse.ok) throw new Error("同步课程数据请求失败");

      const _async_time = window.top.document.querySelector("#async-time");
      const _time = new Date().toLocaleString();
      _async_time.innerHTML = `<span style="color: #000;">[同步时间:${_time}]</span>`;
    } catch (error) {
      console.error("Error:", error.message);
    }
  }
}

// 存储章节测试题目至数据库
async function insertTest(arr) {
  const url = 'https://www.sweek.top/api/insertTest';
  const data = {
    testList: arr
  }
  if (arr.length > 0) {
    try {
      const response = await fetch(url, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(data),
      });

      if (!response.ok) {
        throw new Error('Network response was not ok');
      }
      console.log('题目同步成功!:::+ ', arr)
    } catch (error) {
      console.error('Error:', error.message);
    }
  }
}
// 存储章节测试题目至数据库
async function insertTestModel(arr) {
  const url = 'https://www.sweek.top/api/insertTestModel';
  const data = {
    testList: arr
  }
  if (arr.length > 0) {
    try {
      const response = await fetch(url, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(data),
      });

      if (!response.ok) {
        throw new Error('Network response was not ok');
      }
      console.log('题目同步成功!:::+ ', arr)
    } catch (error) {
      console.error('Error:', error.message);
    }
  }
}




// 方法执行入口
// 方法执行入口
// 方法执行入口
(async function () {
  // 引入Bootstrap Icons
  let link = document.createElement("link");
  link.rel = "stylesheet";
  link.href = "https://cdn.bootcdn.net/ajax/libs/bootstrap-icons/1.10.0/font/bootstrap-icons.css";
  document.head.appendChild(link);
  // console.log('location.pathname:::+ ', location.pathname)
  // 章节测试页面字体解密逻辑
  if (location.pathname == '/mooc-ans/work/doHomeWorkNew') {
    var md5 = md5 || window.md5;

    // 判断是否存在加密字体
    var styleTags = document.querySelectorAll('style');
    var fontStyle = Array.from(styleTags).find(style => style.textContent.includes('font-cxsecret'));
    if (!fontStyle) return;

    // 解析font-cxsecret字体
    var font = fontStyle.textContent.match(/base64,([\w\W]+?)'/)[1];
    console.log('Typr:::+ ', Typr)
    font = Typr.parse(base64ToUint8Array(font))[0];

    // 使用 GM_getResourceText 获取字体映射表
    var table = JSON.parse(GM_getResourceText('Table'));

    // 处理字体解密逻辑
    processFontDecryption(table, font);

    function processFontDecryption(table, font) {
        // 匹配解密字体
        var match = {};
        for (var i = 19968; i < 40870; i++) { // 中文[19968, 40869]
            var glyph = Typr.U.codeToGlyph(font, i);
            if (!glyph) continue;
            var path = Typr.U.glyphToPath(font, glyph);
            var hash = md5(JSON.stringify(path)).slice(24); // 8位即可区分
            match[i] = table[hash];
        }

        // 替换加密字体
        document.querySelectorAll('.font-cxsecret').forEach(element => {
            let html = element.innerHTML;
            for (var key in match) {
                var regex = new RegExp(String.fromCharCode(key), 'g');
                html = html.replace(regex, String.fromCharCode(match[key]));
            }
            element.innerHTML = html;
            element.classList.remove('font-cxsecret'); // 移除字体加密
        });
        console.log('执行解密:::+ ')
    }

    function base64ToUint8Array(base64) {
        var binaryString = window.atob(base64);
        var len = binaryString.length;
        var bytes = new Uint8Array(len);
        for (var i = 0; i < len; i++) {
            bytes[i] = binaryString.charCodeAt(i);
        }
        return bytes;
    }
  }
	// 进入学习通弹出提示
	if(location.pathname == '/base') {
		notify('已进入学习通首页,请进入课程,选择需要学习的课程', 5000)
	}
  // 进入课程弹出提示
  if (location.pathname == '/mooc2-ans/mycourse/stu') {
    let courseName = window.top.document.querySelector('.classDl .colorDeep')?.getAttribute('title');
    let courseImg = window.top.document.querySelector('.classDl').getElementsByTagName("img")[0]?.getAttribute('src');

    if (courseName && courseImg) {
        GM_setValue("courseName", courseName);
        GM_setValue("courseImg", courseImg);
        notify('已进入课程:' + courseName + ',请选择需要学习的章节', 5000);
    } else {
        console.error('课程信息未能获取');
    }
  }

	// 初始化显示页面弹窗
	if(location.pathname == '/mycourse/studentstudy') {
		initPopup()  
		// 获取公告数据
	  await	getBoard()  
		// 邮箱操作
		takeEmail()
    // 默认激活tab
    switchTab(activeTab)
	}
	// 获取页面章节节点数据
	getChapterCodeInfo()
	// 获取页面url信息
	getURLInfo()

	// 定时打印视频播放进度-定时监听页面内容进行下一步处理
	videoProgressId = setInterval(() => {
		getVideoProgress()
	}, 10000);

	// 定时同步课程任务进度
	syncCourseId = setInterval(() => {
    syncCourseData()
	}, asyncCourseInterval);

  if(location.pathname == '/mooc-ans/knowledge/cards') { 
    console.log('触发:::+ ')
    addlog('脚本加载中...')
    // 10秒后执行initAll
    setTimeout(() => {
        initAll();
    }, 5000);
  }
})();