[
  {
    "path": ".eslintignore",
    "content": "node_modules\r\ndist\r\nlib\r\npackage-lock.json\r\npnpm-lock.yaml\r\npublic\r\nstats.html\r\ntests/*\r\n.md"
  },
  {
    "path": ".eslintrc.json",
    "content": "{\n\t\"env\": {\n\t\t\"node\": true,\n\t\t\"commonjs\": true,\n\t\t\"browser\": true,\n\t\t\"es6\": true,\n\t\t\"vue/setup-compiler-macros\": true\n\t},\n\t\"extends\": [\"standard\", \"plugin:vue/vue3-recommended\", \"prettier\"],\n\t\"parserOptions\": {\n\t\t\"ecmaVersion\": \"latest\",\n\t\t\"parser\": \"@typescript-eslint/parser\",\n\t\t\"sourceType\": \"module\"\n\t},\n\t\"plugins\": [\"vue\", \"@typescript-eslint\", \"eslint-plugin-vue\"],\n\t\"rules\": {\n\t\t\"max-len\": [\n\t\t\t2,\n\t\t\t{\n\t\t\t\t\"code\": 120,\n\t\t\t\t\"tabWidth\": 2,\n\t\t\t\t\"ignoreComments\": true,\n\t\t\t\t\"ignoreStrings\": true\n\t\t\t}\n\t\t],\n\t\t\"indent\": \"off\",\n\t\t\"camelcase\": \"off\",\n\t\t\"vue/multi-word-component-names\": \"off\",\n\t\t\"vue/valid-attribute-name\": \"off\",\n\t\t\"space-before-function-paren\": \"off\",\n\t\t\"func-call-spacing\": \"off\",\n\t\t\"no-redeclare\": \"off\"\n\t},\n\n\t\"globals\": {\n\t\t\"core\": \"readonly\",\n\t\t\"scripts\": \"readonly\"\n\t}\n}\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: ['enncy'] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]\npatreon: # Replace with a single Patreon username\nopen_collective: # Replace with a single Open Collective username\nko_fi: # Replace with a single Ko-fi username\ntidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\ncommunity_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\nliberapay: # Replace with a single Liberapay username\nissuehunt: # Replace with a single IssueHunt username\notechie: # Replace with a single Otechie username\nlfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry\ncustom: ['https://afdian.net/@enncy'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug.md",
    "content": "---\nname: bug\nabout: 报告/提交漏洞\ntitle: '[bug] : BUG xxx'\nlabels: ''\nassignees: enncy\n---\n\n**环境描述**\n\n系统: win11/mac/linux/...   \n浏览器：谷歌/edge/fire fox/...      \nOCS版本：xx.xx.xx      \n页面链接: https://example.com/xxxxxxxxxxxxxxx     \n\n**BUG 描述**\n\n...\n\n**重现方法**\n\n1.  进入 '...'\n2.  点击 '....'\n3.  滚动到 '....'\n4.  看到此 BUG\n\n**预期行为**\n\n应该是 xxx 动作，而不是 xxx 动作。\n\n**屏幕截图**\n\n如果可以请附带截图。\n\n**解决方案**\n\n如果可以，请提供您的解决思路\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feat.md",
    "content": "---\nname: feat\nabout: 建议或者新功能新想法\ntitle: '[feat] : 新特性XXX'\nlabels: ''\nassignees: enncy\n---\n\n**特性说明**\n\n**实现方案**\n\n**备选方案**\n"
  },
  {
    "path": ".github/workflows/auto-release.yml",
    "content": "# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node\n# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs\n\nname: Userscript Auto Releases\n\non:\n  push:\n    tags:\n      - \"*\"\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: write\n    strategy:\n      matrix:\n        node-version: [18.x]\n    steps:\n      - uses: actions/checkout@v3\n      - name: Use Node.js ${{ matrix.node-version }}\n        uses: actions/setup-node@v3\n        with:\n          node-version: ${{ matrix.node-version }}\n      - name: Setup env\n        run: |\n          echo 'BUILD_PATH=\"../release\"' > ./scripts/.env \n          echo 'VITE_BUILD_PATH=\"../../release\"' > ./packages/core/.env\n          echo 'VITE_BUILD_PATH=\"../../release\"' > ./packages/scripts/.env\n          echo \"BUILDTIME=$(TZ=Asia/Shanghai date)\" >> $GITHUB_ENV\n          VERSION=$(jq -r .version package.json)\n          echo \"VERSION=$VERSION\" >> $GITHUB_ENV\n          echo \"DOWNLOAD_URL=https://github.com/ocsjs/ocsjs/releases/download/$VERSION/\" >> $GITHUB_ENV\n      - name: Install Dependencies\n        run: |\n          npm install pnpm -g\n          pnpm install -w\n          cd packages/scripts\n          pnpm install\n          cd ../../\n          npm run tsc\n      - name: Build ocs scripts\n        run: npm run build\n\n      - name: Build ChangeLogs\n        run: |\n          npm run changelog\n          npm run changelog:simplify\n          npm run changelog:current\n\n      - name: Fetch Change logs\n        if: runner.os == 'Linux'\n        run: |\n          echo \"pwd: $(pwd)\"\n          echo \"files:\"\n          ls -la\n          echo \"Displaying CHANGELOG_CURRENT.md content:\"\n          cat CHANGELOG_CURRENT.md\n          if [ -f \"CHANGELOG_CURRENT.md\" ]; then\n            CHANGE_LOGS=$(cat CHANGELOG_CURRENT.md)\n            echo \"CHANGE_LOGS<<EOF\" >> $GITHUB_ENV\n            echo \"$CHANGE_LOGS\" >> $GITHUB_ENV\n            echo \"EOF\" >> $GITHUB_ENV\n          else\n            echo \"CHANGELOG_CURRENT.md file not found\"\n          fi\n        shell: bash\n      - name: Generate release.txt\n        if: runner.os == 'Linux'\n        run: |\n          if [ -z \"$CHANGE_LOGS\" ]; then\n            echo \"No change logs found, using default message\"\n            CHANGE_LOGS=\"More new features are now supported. Check for detailed changelog soon.\"\n          else\n            echo \"Using found change logs\"\n          fi\n\n          cat > release.txt << EOF\n          $CHANGE_LOGS\n\n          ## 资源下载列表\n\n          - [用户脚本](${{ env.DOWNLOAD_URL }}ocs.user.js)\n          - [用户脚本(全域名通用版)](${{ env.DOWNLOAD_URL }}ocs.common.user.js)\n          - [核心库](${{ env.DOWNLOAD_URL }}core.js)\n          - [简洁更新日志](${{ env.DOWNLOAD_URL }}CHANGELOG_SIMPLIFIED.md)\n\n          ### 快捷访问\n          - [官网](https://docs.ocsjs.com/)\n\n          Created at ${{ env.BUILDTIME }}.\n          EOF\n\n      - name: Create release\n        uses: ncipollo/release-action@v1\n        with:\n          artifactContentType: \"html/text\"\n          artifacts: \"./release/ocs.user.js,./release/ocs.common.user.js,./release/core.js,./release/index.js,./CHANGELOG_SIMPLIFIED.md\"\n          bodyFile: \"./release.txt\"\n"
  },
  {
    "path": ".gitignore",
    "content": "# customize\r\n \r\n\r\n**/*.zip\r\n\r\n# tsc\r\n\r\nlib/\r\n\r\n# Logs\r\nlogs\r\n*.log\r\nnpm-debug.log*\r\nyarn-debug.log*\r\nyarn-error.log*\r\nlerna-debug.log*\r\n.pnpm-debug.log*\r\n\r\n# Diagnostic reports (https://nodejs.org/api/report.html)\r\nreport.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json\r\n\r\n# Runtime data\r\npids\r\n*.pid\r\n*.seed\r\n*.pid.lock\r\n\r\n# Directory for instrumented libs generated by jscoverage/JSCover\r\nlib-cov\r\n\r\n# Coverage directory used by tools like istanbul\r\ncoverage\r\n*.lcov\r\n\r\n# nyc test coverage\r\n.nyc_output\r\n\r\n# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)\r\n.grunt\r\n\r\n# Bower dependency directory (https://bower.io/)\r\nbower_components\r\n\r\n# node-waf configuration\r\n.lock-wscript\r\n\r\n# Compiled binary addons (https://nodejs.org/api/addons.html)\r\nbuild/Release\r\n\r\n# Dependency directories\r\nnode_modules/\r\njspm_packages/\r\n\r\n# Snowpack dependency directory (https://snowpack.dev/)\r\nweb_modules/\r\n\r\n# TypeScript cache\r\n*.tsbuildinfo\r\n\r\n# Optional npm cache directory\r\n.npm\r\n\r\n# Optional eslint cache\r\n.eslintcache\r\n\r\n# Optional stylelint cache\r\n.stylelintcache\r\n\r\n# Microbundle cache\r\n.rpt2_cache/\r\n.rts2_cache_cjs/\r\n.rts2_cache_es/\r\n.rts2_cache_umd/\r\n\r\n# Optional REPL history\r\n.node_repl_history\r\n\r\n# Output of 'npm pack'\r\n*.tgz\r\n\r\n# Yarn Integrity file\r\n.yarn-integrity\r\n\r\n# dotenv environment variable files\r\n.env\r\n.env.development.local\r\n.env.test.local\r\n.env.production.local\r\n.env.local\r\n\r\n# parcel-bundler cache (https://parceljs.org/)\r\n.cache\r\n.parcel-cache\r\n\r\n# Next.js build output\r\n.next\r\nout\r\n\r\n# Nuxt.js build / generate output\r\n.nuxt\r\ndist/\r\n\r\n# Gatsby files\r\n.cache/\r\n# Comment in the public line in if your project uses Gatsby and not Next.js\r\n# https://nextjs.org/blog/next-9-1#public-directory-support\r\n# public\r\n\r\n# vuepress build output\r\n.vuepress/dist\r\n\r\n# vuepress v2.x temp and cache directory\r\n.temp\r\n.cache\r\n\r\n# Docusaurus cache and generated files\r\n.docusaurus\r\n\r\n# Serverless directories\r\n.serverless/\r\n\r\n# FuseBox cache\r\n.fusebox/\r\n\r\n# DynamoDB Local files\r\n.dynamodb/\r\n\r\n# TernJS port file\r\n.tern-port\r\n\r\n# Stores VSCode versions used for testing VSCode extensions\r\n.vscode-test\r\n\r\n# yarn v2\r\n.yarn/cache\r\n.yarn/unplugged\r\n.yarn/build-state.yml\r\n.yarn/install-state.gz\r\n.pnp.*"
  },
  {
    "path": ".npmrc",
    "content": "node-linker=hoisted\r\n"
  },
  {
    "path": ".prettierignore",
    "content": "node_modules\r\ndist\r\nlib\r\npackage-lock.json\r\npnpm-lock.yaml\r\npublic\r\nstats.html\r\n.md"
  },
  {
    "path": ".prettierrc.json",
    "content": "{\n\t\"$schema\": \"https://json.schemastore.org/prettierrc.json\",\n\t\"semi\": true,\n\t\"singleQuote\": true,\n\t\"bracketSpacing\": true,\n\t\"bracketSameLine\": false,\n\t\"jsxSingleQuote\": false,\n\t\"printWidth\": 120,\n\t\"tabWidth\": 2,\n\t\"useTabs\": true,\n\t\"vueIndentScriptAndStyle\": false,\n\t\"arrowParens\": \"always\",\n\t\"proseWrap\": \"preserve\",\n\t\"htmlWhitespaceSensitivity\": \"css\",\n\t\"endOfLine\": \"auto\",\n\t\"trailingComma\": \"none\",\n\t\"singleAttributePerLine\": true\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n\t\"cSpell.words\": [\n\t\t\"axios\",\n\t\t\"historychange\",\n\t\t\"icve\",\n\t\t\"isclose\",\n\t\t\"isstop\",\n\t\t\"jsdom\",\n\t\t\"nomove\",\n\t\t\"ocsjs\",\n\t\t\"ondefault\",\n\t\t\"ondone\",\n\t\t\"onhistorychange\",\n\t\t\"onprevent\",\n\t\t\"onrender\",\n\t\t\"onrun\",\n\t\t\"scripterror\",\n\t\t\"sider\",\n\t\t\"Tesseract\",\n\t\t\"traineddata\",\n\t\t\"Userscript\",\n\t\t\"VITE\",\n\t\t\"webrtc\",\n\t\t\"xmlhttp\",\n\t\t\"zhihuishu\"\n\t],\n\t\"eslint.validate\": [\"javascript\", \"javascriptreact\", \"vue\"],\n\t\"editor.formatOnSave\": true,\n\t\"editor.codeActionsOnSave\": {\n\t\t\"source.fixAll.eslint\": \"explicit\"\n\t},\n\t\"editor.defaultFormatter\": \"esbenp.prettier-vscode\",\n\t/** 样式自动生成 */\n\t\"less.compile\": {\n\t\t\"compress\": false,\n\t\t\"sourceMap\": false,\n\t\t\"out\": \"../css/\"\n\t},\n\t/** 鼠标中键代码缩进 */\n\t\"editor.mouseWheelZoom\": true,\n\t/** 鼠标滚动速度 */\n\t\"editor.mouseWheelScrollSensitivity\": 2,\n\t/** 不生成 @return 的 jsdoc */\n\t\"typescript.suggest.jsdoc.generateReturns\": false,\n\t\"[typescript]\": {\n\t\t\"editor.defaultFormatter\": \"esbenp.prettier-vscode\"\n\t},\n\t\"editor.wordBasedSuggestions\": \"off\"\n}\n"
  },
  {
    "path": ".vscode/tasks.json",
    "content": "{\n\t// See https://go.microsoft.com/fwlink/?LinkId=733558\n\t// for the documentation about the tasks.json format\n\t\"version\": \"2.0.0\",\n\t\"tasks\": [\n\t\t{\n\t\t\t\"label\": \"echo\",\n\t\t\t\"type\": \"shell\",\n\t\t\t\"command\": \"echo Hello\"\n\t\t}\n\t]\n}\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "## [4.13.4](https://github.com/ocsjs/ocsjs/compare/4.13.2...4.13.4) (2026-04-23)\n\n\n### Features\n\n* **script:** 兼容超星长时阅读任务点（限制翻页时间） ([ac34ba2](https://github.com/ocsjs/ocsjs/commit/ac34ba2d543afe9dc09c1803b558f7b37dbce095))\n* **script:** 添加超星域名支持：jnzyjsxy.cn ([a27cb49](https://github.com/ocsjs/ocsjs/commit/a27cb49b9111e3317120c33eff75981de4b19ed8))\n\n\n\n## [4.13.2](https://github.com/ocsjs/ocsjs/compare/4.12.39...4.13.2) (2026-04-22)\n\n\n### Bug Fixes\n\n* **script:** 修复智慧树26年上半年studywisdomh5复习模式错误的问题 ([2e82a0a](https://github.com/ocsjs/ocsjs/commit/2e82a0a8ea4cca4265ba592336a2762bf156ec08))\n\n\n### Features\n\n* **script:** 兼容雨课堂AI学伴自动学习 ([c96f4db](https://github.com/ocsjs/ocsjs/commit/c96f4db7d949df6f3fc983a6ae4783ece9b19783))\n\n\n\n## [4.12.39](https://github.com/ocsjs/ocsjs/compare/4.12.38...4.12.39) (2026-04-20)\n\n\n### Bug Fixes\n\n* 修复智慧职教题目识别丢失img的src问题 ([482ac64](https://github.com/ocsjs/ocsjs/commit/482ac64f6d9ee0dc54a8bef1fe210ccb4b02d25a))\n* 修复智慧职教题目识别丢失img的src问题,使用与学习通页面相同的方法 ([488f467](https://github.com/ocsjs/ocsjs/commit/488f467a8c212e17a866f0746e86a6c0084f1e2b))\n* 修复智慧职教题目识别丢失img的src问题,使用与学习通页面相同的方法,且考虑纯图片无法识别问题 ([3a23e04](https://github.com/ocsjs/ocsjs/commit/3a23e04d89bb7913638ec880e9817aaa013143ba))\n* 修复智慧职教题目识别丢失img的src问题,使用与学习通页面相同的方法,且考虑纯图片无法识别问题 ([d020954](https://github.com/ocsjs/ocsjs/commit/d0209548c56f6205b7c9edf4fe7ef84f334d03ec))\n\n\n\n## [4.12.38](https://github.com/ocsjs/ocsjs/compare/4.12.37...4.12.38) (2026-04-20)\n\n\n### Bug Fixes\n\n* **script:** 兼容26年上半年智慧树新版studywisdomh5网课更新 ([cf52bf0](https://github.com/ocsjs/ocsjs/commit/cf52bf019b3f9c36c82bc5b90e5e3125510ff3cb))\n\n\n\n## [4.12.37](https://github.com/ocsjs/ocsjs/compare/4.12.32...4.12.37) (2026-04-15)\n\n\n### Bug Fixes\n\n* **script:** 修复超星某些章节测试无法自动答题的BUG ([62b0aaf](https://github.com/ocsjs/ocsjs/commit/62b0aaf86416a07051513e7fbebcce94f29c12cc))\n\n\n### Features\n\n* **script:** 添加智慧树2026上学期新版HIKE AI教学中心视频学习功能 ([392a1a5](https://github.com/ocsjs/ocsjs/commit/392a1a59d5c352e0ca893bf72799cce9333e7f08))\n\n\n### Performance Improvements\n\n* **script:** 适配超星积分课的阅读任务不会自动开始阅读的问题 ([9b8ff4f](https://github.com/ocsjs/ocsjs/commit/9b8ff4f4e34b3e9de6e4c863b10b24bc7d7cf607))\n* **script:** 添加超星阅读界面的使用提示 ([a8aa869](https://github.com/ocsjs/ocsjs/commit/a8aa8695efe8fb1c74baaab106c4c2401f86de53))\n* **script:** 优化超星跳转时间太长的问题，改成智能等待时间 ([6314616](https://github.com/ocsjs/ocsjs/commit/631461620821559ea1bf068ba2445308df4995f1))\n\n\n\n## [4.12.32](https://github.com/ocsjs/ocsjs/compare/4.12.30...4.12.32) (2026-04-09)\n\n\n### Bug Fixes\n\n* **script:** 尝试修复智慧树AI学伴课程过一段时间才会播放的BUG ([7c56dc9](https://github.com/ocsjs/ocsjs/commit/7c56dc925284dd8ac5237da0a6fd9b7dae643264))\n\n\n### Performance Improvements\n\n* **script:** 优化超星闯关模式重复提示 ([fd83815](https://github.com/ocsjs/ocsjs/commit/fd83815150eced1e8caf8d11493ab9841187c49c))\n\n\n\n## [4.12.30](https://github.com/ocsjs/ocsjs/compare/4.12.29...4.12.30) (2026-03-20)\n\n\n### Performance Improvements\n\n* **script:** 添加智慧树新智慧形态考试菜单进入菜单栏 ([d9d362b](https://github.com/ocsjs/ocsjs/commit/d9d362bdc5d9bbd3de04019dede94c856d68ac77))\n\n\n\n## [4.12.29](https://github.com/ocsjs/ocsjs/compare/4.12.26...4.12.29) (2026-03-20)\n\n\n### Bug Fixes\n\n* **script:** 兼容职教云xls任务点自动学习 ([7a3a174](https://github.com/ocsjs/ocsjs/commit/7a3a1748d85d617c740ce631b5d2b52cf5cec80f))\n* **script:** 修复智慧树-新形态课程链接任务点无法跳过的BUG ([51a639d](https://github.com/ocsjs/ocsjs/commit/51a639d4b80a8851e347e1190f94d1cdc5f7f4a5))\n\n\n### Features\n\n* **script:** 添加智慧树-新形态课程自动考试功能 ([587a045](https://github.com/ocsjs/ocsjs/commit/587a045ff454e20b5ecef7d509c0fb25f43af4db))\n\n\n\n## [4.12.26](https://github.com/ocsjs/ocsjs/compare/4.12.23...4.12.26) (2026-03-18)\n\n\n### Bug Fixes\n\n* **script:** 修复职教云PPT仅有2页时无法完成的BUG ([018462f](https://github.com/ocsjs/ocsjs/commit/018462f2746816f70439270e669756aa87240b03))\n* **script:** 修复智慧职教AI测验答题时填空功能失效的BUG ([d850bea](https://github.com/ocsjs/ocsjs/commit/d850bea91b70780d23706eea433756812c6c6016))\n\n\n### Features\n\n* **script:** 添加新版职教云自动跳转旧版功能 ([55a9f1e](https://github.com/ocsjs/ocsjs/commit/55a9f1e198a71d8e8dd163cda4fdadfa8106c925))\n\n\n\n## [4.12.23](https://github.com/ocsjs/ocsjs/compare/4.12.22...4.12.23) (2026-03-17)\n\n\n### Bug Fixes\n\n* 修复上个版本无法打包的BUG ([97608d8](https://github.com/ocsjs/ocsjs/commit/97608d81991ce1e0f262e2e09f1252a4839996af))\n\n\n\n## [4.12.22](https://github.com/ocsjs/ocsjs/compare/4.12.16...4.12.22) (2026-03-17)\n\n\n### Bug Fixes\n\n* **script:** 添加智慧树-AI教学中心-题目作业自动答题功能 ([2a630aa](https://github.com/ocsjs/ocsjs/commit/2a630aade1c71b78e2fd1275f1f9713b2bbf21f6))\n* **script:** 修复图片题中的图片在解析时重复出现的BUG ([84c697e](https://github.com/ocsjs/ocsjs/commit/84c697e1b681aa11c397cf335cd2730d00e7160c))\n* **script:** 修复职教云资源库无法跳过测验的问题 ([34e4f38](https://github.com/ocsjs/ocsjs/commit/34e4f38310fc887b27cb71c5b8d1b6132c3b9f03))\n* **script:** 修复智慧职教AI测验答题时无法填空的BUG ([edb492a](https://github.com/ocsjs/ocsjs/commit/edb492af72d6d8cd09e2146b6e39ecfe68c135f7))\n\n\n### Performance Improvements\n\n* 更新API，添加新API $msg，可同时显示气泡和打印日志 ([099907a](https://github.com/ocsjs/ocsjs/commit/099907ae72be2cc50874162667528e1c6d643b8b))\n* 添加答题日志输出开关功能 ([d041373](https://github.com/ocsjs/ocsjs/commit/d04137339f023270f390c9344be74df854008181))\n\n\n\n## [4.12.16](https://github.com/ocsjs/ocsjs/compare/4.12.15...4.12.16) (2026-03-06)\n\n\n### Bug Fixes\n\n* **script:** 修复智慧课程AI伴学的视频无法检测，以及掌握度只有一题的情况无法答题的BUG ([8ff88f2](https://github.com/ocsjs/ocsjs/commit/8ff88f2ddf2a16852082b5ca7d84575d5215bb6a))\n* **script:** 优化 eslint 报错 ([036644f](https://github.com/ocsjs/ocsjs/commit/036644fc351cbdba3cca55655200bf1cd421dc92))\n\n\n\n## [4.12.15](https://github.com/ocsjs/ocsjs/compare/4.12.14...4.12.15) (2026-03-04)\n\n\n### Bug Fixes\n\n* **script:** 修复2026上学期智慧树-新智慧课程无法自动下一章的BUG ([437696d](https://github.com/ocsjs/ocsjs/commit/437696da2017549c40e10584d964c82be77bd16e))\n\n\n\n## [4.12.14](https://github.com/ocsjs/ocsjs/compare/4.12.13...4.12.14) (2026-02-28)\n\n\n### Features\n\n* **script:** 添加ZE题库域名白名单授权 ([a5bdb91](https://github.com/ocsjs/ocsjs/commit/a5bdb919b28f87023e79230930f46245326aef6d))\n\n\n\n## [4.12.13](https://github.com/ocsjs/ocsjs/compare/4.12.12...4.12.13) (2026-02-25)\n\n\n### Bug Fixes\n\n* **script:** 适配 2026 上学期智慧树-新智慧学习界面 ([6f449cc](https://github.com/ocsjs/ocsjs/commit/6f449ccfc25c6d99d287ae39a6434a8a28a07f58))\n\n\n\n## [4.12.12](https://github.com/ocsjs/ocsjs/compare/4.12.11...4.12.12) (2025-12-29)\n\n\n### Bug Fixes\n\n* 修复 github workflow 运行后 release 不显示更新内容的BUG ([96065e7](https://github.com/ocsjs/ocsjs/commit/96065e70fcea1226ffc97f6759ec4b10e0e6783c))\n\n\n\n## [4.12.11](https://github.com/ocsjs/ocsjs/compare/4.12.10...4.12.11) (2025-12-29)\n\n\n### Bug Fixes\n\n* 添加 git workflow 输出 ([467d53d](https://github.com/ocsjs/ocsjs/commit/467d53d5f96c44cb3629723ebf471a13b65e4b49))\n\n\n\n## [4.12.10](https://github.com/ocsjs/ocsjs/compare/4.12.9...4.12.10) (2025-12-29)\n\n\n### Bug Fixes\n\n* 修复 github workflow 运行后 release 不显示更新内容的BUG ([addb25a](https://github.com/ocsjs/ocsjs/commit/addb25a5aceb6b0ae72ef1c8718797d60f595df0))\n\n\n\n## [4.12.9](https://github.com/ocsjs/ocsjs/compare/4.12.8...4.12.9) (2025-12-29)\n\n\n### Bug Fixes\n\n* 修复 github workflow 运行后 release 不显示更新内容的BUG ([2bacbfd](https://github.com/ocsjs/ocsjs/commit/2bacbfd0dcfe4f35d8058aee92aec07a376b96c4))\n\n\n\n## [4.12.8](https://github.com/ocsjs/ocsjs/compare/4.12.7...4.12.8) (2025-12-29)\n\n\n### Bug Fixes\n\n* 持续修复 github workflow 运行后 release 不显示更新内容的BUG ([ecfa203](https://github.com/ocsjs/ocsjs/commit/ecfa20390b3648522a0b5383fcc6cae6d8cc4223))\n\n\n\n## [4.12.7](https://github.com/ocsjs/ocsjs/compare/4.12.6...4.12.7) (2025-12-29)\n\n\n### Bug Fixes\n\n* 修复 github workflow 运行后 release 不显示更新内容的BUG ([676c7ed](https://github.com/ocsjs/ocsjs/commit/676c7ed2dffc2f5a547a496b9fcf20ff3ab3dc14))\n\n\n\n## [4.12.6](https://github.com/ocsjs/ocsjs/compare/4.12.4...4.12.6) (2025-12-29)\n\n\n### Bug Fixes\n\n* **script:** 适配2025-12月智慧树新智慧学习域名 ([acc2c79](https://github.com/ocsjs/ocsjs/commit/acc2c79ccb5c1542f92f1811d84e543caebd1fe8))\n* **script:** 修复超星学习通没配置题库无法跳过章节测试的BUG ([e27d978](https://github.com/ocsjs/ocsjs/commit/e27d978f3620a90e961c79f523d6cce3b58f6380))\n\n\n\n## [4.12.4](https://github.com/ocsjs/ocsjs/compare/4.12.3...4.12.4) (2025-12-16)\n\n\n### Bug Fixes\n\n* **core:** 修复答案为圆圈数字时无法答题的BUG ([676f437](https://github.com/ocsjs/ocsjs/commit/676f4374745effe0187460560e097bc5c2e84c14))\n\n\n\n## [4.12.3](https://github.com/ocsjs/ocsjs/compare/4.12.0...4.12.3) (2025-12-03)\n\n\n### Bug Fixes\n\n* **script:** 添加职教云资源库修复功能按钮 ([898bb3b](https://github.com/ocsjs/ocsjs/commit/898bb3b31ca18740b4cf4072369adf45150f1b3d))\n* **script:** 修复中国大学MOOC新版考试无法运行的BUG ([8f0a188](https://github.com/ocsjs/ocsjs/commit/8f0a18824ec7839a288620cf9c58cd79ec11ca57))\n\n\n### Performance Improvements\n\n* **script:** 加强智慧树作业考试自动保存警告 ([7a44eb7](https://github.com/ocsjs/ocsjs/commit/7a44eb7a70ca23547f55419fa3c7e69755c0c038))\n\n\n\n# [4.12.0](https://github.com/ocsjs/ocsjs/compare/4.11.98...4.12.0) (2025-11-27)\n\n\n### Bug Fixes\n\n* 修复超星答题时无法点击快速定位到题库配置界面问题 ([855e8e9](https://github.com/ocsjs/ocsjs/commit/855e8e92a647d886a578f658aa105f8686b26e49))\n\n\n### Features\n\n* **core:** 添加软件辅助鼠标在空闲状态下执行辅助点击等功能，防止与用户抢夺控制权 ([e5b3b1e](https://github.com/ocsjs/ocsjs/commit/e5b3b1e2527a1930452164d7cd1fda77eafaa8a8))\n* **core:** 添加自定义答题器，可自定义题目类型、答题器等参数，自由度更高 ([f1f7813](https://github.com/ocsjs/ocsjs/commit/f1f7813849f7c7211d630f5dacbe629d74fc5b02))\n* **script:** 兼容超星繁体字判断题适配 ([a81cd57](https://github.com/ocsjs/ocsjs/commit/a81cd577b1e4d9f3b42a8bed55fab4a28d151036))\n\n\n### Performance Improvements\n\n* **script:** 优化 waitForElement API ([4cf2191](https://github.com/ocsjs/ocsjs/commit/4cf2191be1d36a61d78c878cfd45e282ad7f095b))\n\n\n\n## [4.11.98](https://github.com/ocsjs/ocsjs/compare/4.11.97...4.11.98) (2025-11-24)\n\n\n### Bug Fixes\n\n* **core:** 修复上个版本无法打包的BUG ([089aaaf](https://github.com/ocsjs/ocsjs/commit/089aaafcfefcdb1ae69fde9cdb0e1ad7aed376d3))\n\n\n\n## [4.11.97](https://github.com/ocsjs/ocsjs/compare/4.11.96...4.11.97) (2025-11-24)\n\n\n### Bug Fixes\n\n* **script:** 修复高级设置无法打开的BUG ([81dbc12](https://github.com/ocsjs/ocsjs/commit/81dbc1254939a5996ff384c70a49169b7189d6b4))\n\n\n\n## [4.11.96](https://github.com/ocsjs/ocsjs/compare/4.11.95...4.11.96) (2025-11-24)\n\n\n### Bug Fixes\n\n* **script:** 修复中国大学MOOC视频题目重复答题的BUG ([7c6fd88](https://github.com/ocsjs/ocsjs/commit/7c6fd8852af43022c18e21cb6582e8388eb2587e))\n\n\n\n## [4.11.95](https://github.com/ocsjs/ocsjs/compare/4.11.94...4.11.95) (2025-11-23)\n\n\n### Performance Improvements\n\n* aPI优化 ([f988878](https://github.com/ocsjs/ocsjs/commit/f988878dfb65c5feb95aaa771efe05eaa39944c0))\n\n\n\n## [4.11.94](https://github.com/ocsjs/ocsjs/compare/4.11.86...4.11.94) (2025-11-23)\n\n\n### Bug Fixes\n\n* **common:** 题库缓存清空后弹窗数量文字归零 ([7a1ae7d](https://github.com/ocsjs/ocsjs/commit/7a1ae7d7508ba4d352fc4374aec8463a284cadf2))\n* **script:** 修复超星默认跳转模式无法自动滚动页面到当前章节列表问题 ([5600bdd](https://github.com/ocsjs/ocsjs/commit/5600bdd761d05c081c383e34b215abdfe909fcf0))\n\n\n### Features\n\n* **core:** 对移动端进行拖动面板适配 ([d253f8b](https://github.com/ocsjs/ocsjs/commit/d253f8b77bfb7006b3cb94338602476e65aa2961))\n\n\n### Performance Improvements\n\n* **core:** 优化题库搜索题目显示，可兼容长题目例如阅读理解上下文显示 ([2e62f4c](https://github.com/ocsjs/ocsjs/commit/2e62f4c35404ef8558456aa34c1309afb059d2fa))\n\n\n\n## [4.11.86](https://github.com/ocsjs/ocsjs/compare/4.11.85...4.11.86) (2025-11-20)\n\n\n### Bug Fixes\n\n* **script:** 修复上个版本类型报错 ([f472a0e](https://github.com/ocsjs/ocsjs/commit/f472a0e8ffc63b67ab03d56e19857a38d58d57bf))\n\n\n\n## [4.11.85](https://github.com/ocsjs/ocsjs/compare/4.11.82...4.11.85) (2025-11-20)\n\n\n### Bug Fixes\n\n* **script:** 将超星环境调整脚本整合到内部，防止全局污染 ([b8a2a20](https://github.com/ocsjs/ocsjs/commit/b8a2a207a679eefd6647a9b058ea4f1dd39509fa))\n* **script:** 修复中国大学MOOC作业答题错乱问题 ([3dfcef1](https://github.com/ocsjs/ocsjs/commit/3dfcef1074d0e8258adee054af93ebb5d9373c35))\n\n\n\n## [4.11.82](https://github.com/ocsjs/ocsjs/compare/4.11.81...4.11.82) (2025-11-14)\n\n\n\n## [4.11.81](https://github.com/ocsjs/ocsjs/compare/4.11.78...4.11.81) (2025-11-14)\n\n\n### Bug Fixes\n\n* **script:** 修复职教云资源库附件视频任务无法完成的BUG ([e8635e5](https://github.com/ocsjs/ocsjs/commit/e8635e5e090243ba04aee82887c9f95190e32bcc))\n* **script:** 修复职教云资源库总是无法学满进度的BUG ([b06dc44](https://github.com/ocsjs/ocsjs/commit/b06dc44b5be6847dbdc238fc69fb9a732f1e6178))\n\n\n\n## [4.11.78](https://github.com/ocsjs/ocsjs/compare/4.11.77...4.11.78) (2025-11-13)\n\n\n### Bug Fixes\n\n* **script:** 修改职教云资源库视频PPT跳转间隔为5秒 ([32504fa](https://github.com/ocsjs/ocsjs/commit/32504fa2553925fae3439a769650aaf9b6734095))\n\n\n\n## [4.1.76](https://github.com/ocsjs/ocsjs/compare/4.11.75...4.1.76) (2025-11-12)\n\n\n### Bug Fixes\n\n* **script:** 修复职教云资源库PPT总是会返回第一页才结束的BUG ([5aab29b](https://github.com/ocsjs/ocsjs/commit/5aab29b9f67f743cef71ab2e99f3eec31d87c47a))\n\n\n\n## [4.11.77](https://github.com/ocsjs/ocsjs/compare/4.1.76...4.11.77) (2025-11-12)\n\n\n### Bug Fixes\n\n* **script:** 修复职教云资源库PPT总是会返回第一页才结束的BUG ([e665f55](https://github.com/ocsjs/ocsjs/commit/e665f554d6b261cc176d38acc788e80f492923f1))\n\n\n\n## [4.1.76](https://github.com/ocsjs/ocsjs/compare/4.11.75...4.1.76) (2025-11-12)\n\n\n### Bug Fixes\n\n* **script:** 修复职教云资源库PPT总是会返回第一页才结束的BUG ([5aab29b](https://github.com/ocsjs/ocsjs/commit/5aab29b9f67f743cef71ab2e99f3eec31d87c47a))\n\n\n\n## [4.11.75](https://github.com/ocsjs/ocsjs/compare/4.11.74...4.11.75) (2025-11-12)\n\n\n### Bug Fixes\n\n* **script:** 修复超星在章节列表页面提示已进入学习界面的BUG ([9e899e8](https://github.com/ocsjs/ocsjs/commit/9e899e82c70bfaf530d42e1bdaf489e0680723c5))\n\n\n\n## [4.11.74](https://github.com/ocsjs/ocsjs/compare/4.11.73...4.11.74) (2025-11-12)\n\n\n### Bug Fixes\n\n* **script:** 修复超星视频加载失败无法重启的BUG ([385d8a3](https://github.com/ocsjs/ocsjs/commit/385d8a32e40cc91dcb156f8c48238bedb060152b))\n\n\n\n## [4.11.73](https://github.com/ocsjs/ocsjs/compare/4.11.69...4.11.73) (2025-11-12)\n\n\n### Bug Fixes\n\n* **script:** 使用软件辅助模拟输入填空适配智慧树智慧课程掌握度填空题 ([a57be37](https://github.com/ocsjs/ocsjs/commit/a57be3706f408f68401b80a0672d6599959aaa05))\n* **script:** 添加智慧树倍速最高风险说明 ([cffcbc9](https://github.com/ocsjs/ocsjs/commit/cffcbc981f28a91964ee2f7f17f170272c875ad0))\n* **script:** 修复配置多个题库其中某个超时无法答题的BUG ([dd569dc](https://github.com/ocsjs/ocsjs/commit/dd569dce9cb1697d0ee084d28f76eeafca9e52c3)), closes [#270](https://github.com/ocsjs/ocsjs/issues/270)\n\n\n\n## [4.11.69](https://github.com/ocsjs/ocsjs/compare/4.11.64...4.11.69) (2025-11-09)\n\n\n### Bug Fixes\n\n* **core:** 修复隐藏窗口按钮无法使用的BUG ([c028a8f](https://github.com/ocsjs/ocsjs/commit/c028a8f9a921a4e1ce1ca01924c6d9f6dc0befcd))\n* **script:** 添加中国大学MOOC空白页自动跳转的功能 ([c562d2c](https://github.com/ocsjs/ocsjs/commit/c562d2ce4ccb2a1e2fce15adec1f56a9b913e76c))\n* **script:** 修复中国大学MOOC视频答题和章节测试冲突的问题 ([a61a8e0](https://github.com/ocsjs/ocsjs/commit/a61a8e02bb3c5c7054d99a90224d9bcfacec312e))\n\n\n### Features\n\n* **script:** 适配智慧树-AI教学中心-智慧课程作业答题功能 ([3d124af](https://github.com/ocsjs/ocsjs/commit/3d124af156fa640099a7fb7d94fe5c0a684a9685))\n* **script:** 修复智慧树倍速失效的BUG ([26a9a6d](https://github.com/ocsjs/ocsjs/commit/26a9a6dd73106f956acc5f6a299651522a77b24a))\n\n\n\n## [4.11.64](https://github.com/ocsjs/ocsjs/compare/4.11.61...4.11.64) (2025-11-07)\n\n\n### Bug Fixes\n\n* **core:** 答案匹配时移除标点符号保证匹配更加准确 ([3f02c30](https://github.com/ocsjs/ocsjs/commit/3f02c307de1baef626de8c2ad83830904fc519b7))\n* **scirpt:** 修复上个版本中国大学MOCC考试部分题目答题错误问题 ([bcf21b3](https://github.com/ocsjs/ocsjs/commit/bcf21b362c122d9414589d658cafc9e2ff19e398))\n\n\n\n## [4.11.61](https://github.com/ocsjs/ocsjs/compare/4.11.51...4.11.61) (2025-11-06)\n\n\n### Bug Fixes\n\n* **script:** 修复职教云资源库点击PPT过快的BUG ([454ee5d](https://github.com/ocsjs/ocsjs/commit/454ee5d564c1e3f45728e56f779da5ea313abdcd))\n* **script:** 修复智慧树新形态课程（智慧课程）某些外链无法完成并跳过的BUG ([d9bbd50](https://github.com/ocsjs/ocsjs/commit/d9bbd501049a23605e94ea899618207734341a11))\n* **script:** 优化超星非整卷预览考试流畅度，添加搜索结果对于部分答页面题过程可控的菜单 ([f1f9082](https://github.com/ocsjs/ocsjs/commit/f1f9082c557007b86e22312f450b7530012fef5d))\n\n\n### Features\n\n* **script:** 支持搜索结果界面可控制答题进程功能 ([98573b5](https://github.com/ocsjs/ocsjs/commit/98573b5d00817040f859dc20fb337ed5e590687d))\n* **script:** 支持中国大学MOOC自动考试功能 ([40fbfdd](https://github.com/ocsjs/ocsjs/commit/40fbfdd763496176fc65b83280b351ac4a741052))\n\n\n### Performance Improvements\n\n* **script:** 添加超星凌晨刷课文案提示 ([f17992c](https://github.com/ocsjs/ocsjs/commit/f17992c778a14f67e652f2a9faefde6e55c42f87))\n* **script:** 添加多个答题页面菜单自动注册功能 ([b101424](https://github.com/ocsjs/ocsjs/commit/b10142401d8bb3a8874d6322f017881be7035d0f))\n\n\n\n## [4.11.51](https://github.com/ocsjs/ocsjs/compare/4.11.47...4.11.51) (2025-11-05)\n\n\n### Bug Fixes\n\n* **core:** 兼容搜题结果格式A#B#C#D解析为多选题 ([0d29813](https://github.com/ocsjs/ocsjs/commit/0d29813370b06580e37bce5bb68b659618e38747))\n\n\n### Features\n\n* **script:** 兼容职教云在线课程内容单独任务点连续学习功能，添加PPT翻阅速度调整选项 ([9883897](https://github.com/ocsjs/ocsjs/commit/9883897d39c8d10729c65084b13aff58711aee9e))\n\n\n### Performance Improvements\n\n* **script:** 添加智慧树共享课作业开启前阅读须知功能 ([7e7657b](https://github.com/ocsjs/ocsjs/commit/7e7657b4ec91f43eb4f91a2b1cae5df64e98d14d))\n\n\n\n## [4.11.47](https://github.com/ocsjs/ocsjs/compare/4.11.22...4.11.47) (2025-11-05)\n\n\n### Bug Fixes\n\n* 优化智慧树卡巴斯基文案 ([99253d6](https://github.com/ocsjs/ocsjs/commit/99253d643be17f4e73f5718c410d1da1b8a21e1b))\n* **core:** 修复窗口反复闪烁的BUG ([217b5e1](https://github.com/ocsjs/ocsjs/commit/217b5e103b0c01eb7f89e12a343847430ff687fc))\n* **icourse:** 修复中国大学MOOC无法完成富文本任务的BUG ([5dc3c06](https://github.com/ocsjs/ocsjs/commit/5dc3c0674290b3dd40a2e6a339b6d1574dbe644a))\n* **script:** 持续修复超星跳转未完成任务点模式无法使用的BUG ([66a74a2](https://github.com/ocsjs/ocsjs/commit/66a74a234ac4fd3810fed08eda99d10e7412d4f7))\n* **script:** 兼容职教云资源库新版PPT ([4919cef](https://github.com/ocsjs/ocsjs/commit/4919ceff58eef18336c8cc8c18bbf900cdd6cc23))\n* **script:** 兼容智慧树-AI助教课程PPT和文档功能 ([7c456ed](https://github.com/ocsjs/ocsjs/commit/7c456edb9149b2705d3e207740a80467225560f3))\n* **script:** 修复超星视频暂停后长时间才播放的BUG ([a26c03b](https://github.com/ocsjs/ocsjs/commit/a26c03b9cf54c2eb3a495ec9eec7343c623095c4))\n* **script:** 修复超星跳转未完成任务点模式无法使用的BUG ([a1ed883](https://github.com/ocsjs/ocsjs/commit/a1ed883152c406b2e041f96f1e6398ae37a8e87d))\n* **script:** 修复智慧树-AI助教倍速无法点击的BUG ([8ffa983](https://github.com/ocsjs/ocsjs/commit/8ffa983d23b55a21b66636789cba91a6a557d7ce))\n* **script:** 修复智慧树-AI助教课程无法自动跳转小节和PPT的BUG ([fe76e0e](https://github.com/ocsjs/ocsjs/commit/fe76e0e1a08d418cd532e5ba31f5bf3810413872))\n* **script:** 优化多选题纯答案无法解析的BUG ([97d8655](https://github.com/ocsjs/ocsjs/commit/97d8655ec656c6838f45a13ed0000be988862264))\n* **script:** 优化智慧树音量开始播放后没有立即调整的问题 ([eff3c71](https://github.com/ocsjs/ocsjs/commit/eff3c71b9a1e5b0f1ae4b831413ddd983145dc84))\n\n\n### Features\n\n* **script:** 软件配置同步后添加脚本双击配置页面强制取消功能 ([99948e9](https://github.com/ocsjs/ocsjs/commit/99948e9c180fd93b247af8de910cb1104986ee19))\n* **script:** 添加超星下一章切换后自动滚动到该元素的功能 ([c5c0726](https://github.com/ocsjs/ocsjs/commit/c5c0726075d52be3e1a8f5273d1e50a97df16566))\n* **script:** 添加超星页面加载后自动滚动到当前任务点的功能 ([16232df](https://github.com/ocsjs/ocsjs/commit/16232dfe8270224141068489bf5fe51c8a31d23c))\n* **script:** 添加超星章节测试答题时自动显示搜索结果 ([967c495](https://github.com/ocsjs/ocsjs/commit/967c495023b7671eee1581fae89a3795fb820568))\n* **script:** 添加全局快捷菜单自动注册功能 ([11403f6](https://github.com/ocsjs/ocsjs/commit/11403f67c80e9d033e868485cff37e1517f907cd))\n* **script:** 添加最小化/切屏窗口警告功能，优化文案 ([22028e4](https://github.com/ocsjs/ocsjs/commit/22028e470448b62ce6d305117831ab2a04ca7ccf))\n* **script:** 优化超星章节测试答题时自动打开搜索结果功能 ([7cc0046](https://github.com/ocsjs/ocsjs/commit/7cc0046a1a8d4f9caa7a03bee37f362cc42e52fa))\n* **script:** 支持中国大学MOOC视频内弹窗答题 ([8af7ec2](https://github.com/ocsjs/ocsjs/commit/8af7ec24affd9ea2db3410ee7774d4308df675f7))\n\n\n### Performance Improvements\n\n* **script:** 降低超星倍速警告阈值为2倍速，优化倍速警告 ([5aababb](https://github.com/ocsjs/ocsjs/commit/5aababba9a73ac860fe23147cd1c5a6f45d6a62b))\n* **script:** 添加智慧树作业禁止同时打开多个界面答题文案 ([f1cd8cb](https://github.com/ocsjs/ocsjs/commit/f1cd8cbbc32491d3d80ef386dadd109c19d67f70))\n* **script:** 修改搜题超时默认时间为2分钟，最大值为3分钟 ([a2a662f](https://github.com/ocsjs/ocsjs/commit/a2a662f339352672fc2a825912f42d1c150b455b))\n\n\n\n## [4.11.22](https://github.com/ocsjs/ocsjs/compare/4.11.21...4.11.22) (2025-10-16)\n\n\n### Bug Fixes\n\n* **script:** 兼容2025下半年超星PPT新版无法完成的问题 ([cbe876e](https://github.com/ocsjs/ocsjs/commit/cbe876ef653ab24108b6dc471c2a6cd3231c3537))\n\n\n\n## [4.11.21](https://github.com/ocsjs/ocsjs/compare/4.11.20...4.11.21) (2025-10-10)\n\n\n### Bug Fixes\n\n* 优化软件同步功能 ([2c1ecfc](https://github.com/ocsjs/ocsjs/commit/2c1ecfc59be7f5fb1a212a9e271eb619b90ac383))\n\n\n\n## [4.11.20](https://github.com/ocsjs/ocsjs/compare/4.11.19...4.11.20) (2025-10-07)\n\n\n### Bug Fixes\n\n* **script:** 修复软件开启同步后，其他非自动化辅助网页里面的脚本依然会同步的问题，造成用户以为当前页面可以使用软件辅助的错觉 ([65920e2](https://github.com/ocsjs/ocsjs/commit/65920e232a7ca9eaae7c163eea2254d350357c8b))\n\n\n\n## [4.11.19](https://github.com/ocsjs/ocsjs/compare/4.11.18...4.11.19) (2025-09-26)\n\n\n### Bug Fixes\n\n* **core:** 修复题库请求时无法传递类型为0或者false的值 ([6c8a26f](https://github.com/ocsjs/ocsjs/commit/6c8a26f62c0bd31e3e7c22fe88bc689a420b508d))\n\n\n\n## [4.11.18](https://github.com/ocsjs/ocsjs/compare/4.11.17...4.11.18) (2025-09-25)\n\n\n### Bug Fixes\n\n* **script:** 修复上个版本智慧树会长时间不播放的BUG ([aa7c66f](https://github.com/ocsjs/ocsjs/commit/aa7c66f05a31d72223331efada8dec5d29fe88cc))\n\n\n\n## [4.11.17](https://github.com/ocsjs/ocsjs/compare/4.11.8...4.11.17) (2025-09-25)\n\n\n### Bug Fixes\n\n* **script:** 兼容超星整卷预览禁用后的作业和答题 ([07139d7](https://github.com/ocsjs/ocsjs/commit/07139d753584449950e4d8fbc79657fedca92355))\n* **script:** 修复搜索结果AI标识重复出现的BUG ([2857f2f](https://github.com/ocsjs/ocsjs/commit/2857f2ff14267eff62b4de5be75b6b7948c4eab3))\n* **script:** 优化旧版智慧树视频容易黑屏的问题 ([e2535ad](https://github.com/ocsjs/ocsjs/commit/e2535ad3b92241793a14e87ed59fe133aca9b27c))\n* **script:** 智慧树答题时移动页面至左上角防止点击出错 ([7f835c0](https://github.com/ocsjs/ocsjs/commit/7f835c0b49e09c617617c1c6919ec00a8f83beac))\n\n\n### Features\n\n* **script:** 添加对智慧树2025-9月份教学空间-AI智慧学习支持 ([da9d229](https://github.com/ocsjs/ocsjs/commit/da9d229843bf2bf858a711ddfb3327a99bb6091f))\n\n\n### Performance Improvements\n\n* **script:** 修复中国大学MOOC无法提交讨论的BUG，优化中国大学MOOC日志显示 ([5b1ab57](https://github.com/ocsjs/ocsjs/commit/5b1ab57984bcbea691331a6d697a06b68f6bee96))\n* **script:** 优化题库配置文案和第三方连接文案 ([e66d399](https://github.com/ocsjs/ocsjs/commit/e66d3991099fa0d7d245c1cf9757a84ccff80b13))\n* **script:** 优化智慧树学习提示文案 ([5bc0545](https://github.com/ocsjs/ocsjs/commit/5bc0545e576120c260809fd3af9e0c4b6dc9b667))\n\n\n\n## [4.11.8](https://github.com/ocsjs/ocsjs/compare/4.11.5...4.11.8) (2025-09-15)\n\n\n### Features\n\n* **script:** - 添加搜题结果AI标注显示 ([7783cee](https://github.com/ocsjs/ocsjs/commit/7783cee4d1898ab1029189a8dd112f70ad45ed5a))\n\n\n\n## [4.11.5](https://github.com/ocsjs/ocsjs/compare/4.11.4...4.11.5) (2025-09-09)\n\n\n### Bug Fixes\n\n* 尝试修复Git打包错误的BUG ([30cbbbc](https://github.com/ocsjs/ocsjs/commit/30cbbbc8726b18c6508a18ef75b500206e8e4647))\n\n\n\n## [4.11.4](https://github.com/ocsjs/ocsjs/compare/4.11.3...4.11.4) (2025-09-09)\n\n\n### Bug Fixes\n\n* 尝试修复Git打包错误的BUG ([abb6e7b](https://github.com/ocsjs/ocsjs/commit/abb6e7b4f919de3930186da9927827513a4f3390))\n\n\n\n## [4.11.3](https://github.com/ocsjs/ocsjs/compare/4.11.2...4.11.3) (2025-09-09)\n\n\n### Bug Fixes\n\n* 对全部项目的依赖进行版本锁定 ([f760f6d](https://github.com/ocsjs/ocsjs/commit/f760f6d9ff78c3c8366b4c5b2eebb0b957abba19))\n\n\n\n## [4.11.2](https://github.com/ocsjs/ocsjs/compare/4.11.1...4.11.2) (2025-09-09)\n\n\n### Bug Fixes\n\n* **script:** 优化代码 ([bdbcb47](https://github.com/ocsjs/ocsjs/commit/bdbcb472fed208246333d35cc375a955c631023b))\n\n\n\n## [4.11.1](https://github.com/ocsjs/ocsjs/compare/4.11.0...4.11.1) (2025-09-09)\n\n\n### Bug Fixes\n\n* **script:** 优化代码 ([c08eece](https://github.com/ocsjs/ocsjs/commit/c08eece1984c6c3f6eda8e086e18b7150e46d3a9))\n\n\n\n# [4.11.0](https://github.com/ocsjs/ocsjs/compare/4.10.0...4.11.0) (2025-09-09)\n\n\n### Bug Fixes\n\n* **core:** 优化Playwright软件辅助底层对接，添加软件辅助点击可视化，修复元素滚动后点击错位的BUG ([e5daaae](https://github.com/ocsjs/ocsjs/commit/e5daaaef9fc088cb6ac419ca95fedc03924da264))\n\n\n### Features\n\n* **script:** 添加超星对 jxgmxy.com 域名的支持 ([b75b714](https://github.com/ocsjs/ocsjs/commit/b75b714eeb580d5fafc5e04004bab58cc87bbb70))\n* **script:** 新增智慧树2025-9月份最新课程支持-新智慧课程 ([4f3780a](https://github.com/ocsjs/ocsjs/commit/4f3780a6af864ad4f7fb6c1ca435c9dce4b87c09))\n\n\n\n# [4.10.0](https://github.com/ocsjs/ocsjs/compare/4.9.86...4.10.0) (2025-09-01)\n\n\n### Features\n\n* **script:** 添加对2025-8月份智慧树新掌握度页面进行适配 ([07cab10](https://github.com/ocsjs/ocsjs/commit/07cab10e6593611680681983b9de12c4101f07de))\n* **script:** 新增智慧职教AI课程学习和作业脚本 ([27ec1fb](https://github.com/ocsjs/ocsjs/commit/27ec1fbb33772294e07e72f9e3c16b7d3ee6f629))\n\n\n\n## [4.9.86](https://github.com/ocsjs/ocsjs/compare/4.9.85...4.9.86) (2025-06-08)\n\n\n### Bug Fixes\n\n* **script:** 添加职教云卡死提示解决方法 ([05493be](https://github.com/ocsjs/ocsjs/commit/05493be36f61db90f1fa1d007dc8ef4b63f38a71))\n\n\n\n## [4.9.85](https://github.com/ocsjs/ocsjs/compare/4.9.84...4.9.85) (2025-06-08)\n\n\n### Bug Fixes\n\n* **script:** 修复职教云获取数据错误的问题 ([5565799](https://github.com/ocsjs/ocsjs/commit/55657994c8716647ae62f3ce510ba9f91747665d))\n\n\n\n## [4.9.84](https://github.com/ocsjs/ocsjs/compare/4.9.83...4.9.84) (2025-06-07)\n\n\n### Bug Fixes\n\n* **script:** 修复智慧职教自定义全局倍速后导致其他脚本倍速选项也被修改的BUG ([5877a6c](https://github.com/ocsjs/ocsjs/commit/5877a6cd7a68589909544b5b04d197de694adadd))\n\n\n\n## [4.9.83](https://github.com/ocsjs/ocsjs/compare/4.9.81...4.9.83) (2025-06-07)\n\n\n### Bug Fixes\n\n* 修改脚本更新链接到教程官网的更新教程 ([dd24c40](https://github.com/ocsjs/ocsjs/commit/dd24c4091f93a34d5fe10e4542dda426e4953ffb))\n* **script:** 修复职教云卡在获取数据弹窗 ([8acf60d](https://github.com/ocsjs/ocsjs/commit/8acf60d90a8033dd7ba2c8ec1c7ae16b391eb737))\n\n\n\n## [4.9.81](https://github.com/ocsjs/ocsjs/compare/4.9.79...4.9.81) (2025-05-29)\n\n\n### Bug Fixes\n\n* **script:** 兼容职教云添加章节层级名字后无法获取数据的问题 ([afa37cf](https://github.com/ocsjs/ocsjs/commit/afa37cf4ed47da77a2cc2711b240b449c69f7a2a))\n* **script:** 添加智慧树智慧课程跳转模式选择 ([822ab99](https://github.com/ocsjs/ocsjs/commit/822ab995421c1abe1b6997f5a8da3d807a5de3cd))\n\n\n\n## [4.9.79](https://github.com/ocsjs/ocsjs/compare/4.9.78...4.9.79) (2025-05-24)\n\n\n### Bug Fixes\n\n* **script:** 修复职教云章节列表前面有章节序号时无法读取数据的BUG ([6ffd21e](https://github.com/ocsjs/ocsjs/commit/6ffd21e42c0c87bddd5d54c396465647985bb6aa))\n\n\n\n## [4.9.78](https://github.com/ocsjs/ocsjs/compare/4.9.77...4.9.78) (2025-05-18)\n\n\n### Bug Fixes\n\n* **script:** 修复职教云获取数据时跳转其他网页的BUG ([4237698](https://github.com/ocsjs/ocsjs/commit/4237698017e7d1bc03a3f91d91c2ad0f0280e9bb))\n\n\n\n## [4.9.77](https://github.com/ocsjs/ocsjs/compare/4.9.76...4.9.77) (2025-05-11)\n\n\n### Bug Fixes\n\n* 修复职教云0资源库无法下一章的BUG，兼容职教云部分PPT任务点 ([69230c4](https://github.com/ocsjs/ocsjs/commit/69230c4fc73af9f5e23ae908a66eeb58581d6063))\n\n\n\n## [4.9.76](https://github.com/ocsjs/ocsjs/compare/4.9.71...4.9.76) (2025-05-09)\n\n\n### Bug Fixes\n\n* **script:** 优化智慧树翻转课（校内课） ([af04c36](https://github.com/ocsjs/ocsjs/commit/af04c36824516f342152f96559af31101745187b))\n\n\n### Features\n\n* 优化支持职教云最新版页面，支持自动切换下一章（包含大章节切换） ([c73a1cc](https://github.com/ocsjs/ocsjs/commit/c73a1ccb2e66b99c4f9cb6b93efea7c1940cfc81))\n\n\n### Performance Improvements\n\n* 持续优化软件辅助安装提示 ([90d9cd1](https://github.com/ocsjs/ocsjs/commit/90d9cd13d7cce7fccdb5b385d2dca98ab912591b))\n* 优化倍速设置选项，减少不必要的选项数量 ([566ff84](https://github.com/ocsjs/ocsjs/commit/566ff845cc04dc45f8a8bcb47bcb88de274b4690))\n* 优化超星随机答题文案，提示只有在搜不到答案的时候才使用随机答题 ([3e07067](https://github.com/ocsjs/ocsjs/commit/3e07067f6d86d35c99c0359fc202152905468109))\n\n\n\n## [4.9.71](https://github.com/ocsjs/ocsjs/compare/4.9.70...4.9.71) (2025-05-07)\n\n\n### Bug Fixes\n\n* **script:** 修复新版智慧树无法关闭弹窗的BUG ([7b356d3](https://github.com/ocsjs/ocsjs/commit/7b356d3b79f4e44111283e5579ae25d2a2bb3cc3))\n\n\n\n## [4.9.70](https://github.com/ocsjs/ocsjs/compare/4.9.59...4.9.70) (2025-05-05)\n\n\n### Bug Fixes\n\n* **script:** 修复超星搜题时无法读取选项内图片链接的BUG ([d57cb8f](https://github.com/ocsjs/ocsjs/commit/d57cb8fbd7fd191bad3deb05b988d01651f67f28))\n* **script:** 修复超星章节测试多行题目第二行存在题目类型时无法解析题目的BUG ([3cdfa3c](https://github.com/ocsjs/ocsjs/commit/3cdfa3c8ccd1c25053c83a252a0b973c10c608a5))\n* **script:** 修复超星自动答题时填空题选项读取到额外冗余文字的BUG：（段落格式，字号，字体） ([f95f2ea](https://github.com/ocsjs/ocsjs/commit/f95f2ea4d2e9f69f8053a2e18ab1d0b2e7cd7442))\n* **script:** 修复智慧树-智慧课程无法自动切换下一个的BUG ([b02d864](https://github.com/ocsjs/ocsjs/commit/b02d864eacd667428da73a6db68d810e55e4f39d))\n* **script:** 修复智慧树-智慧课程无法自动下一章的BUG ([48b8567](https://github.com/ocsjs/ocsjs/commit/48b856794b46ab64db102a4a1563cce3e21593ea))\n* **script:** 修复智慧树-智慧职教无法学习文本任务点的问题，新增智慧职教已完成任务点过滤功能 ([aaf7774](https://github.com/ocsjs/ocsjs/commit/aaf7774215a703569ea80458f10937c4d9e1c330))\n* **script:** 优化智慧树-智慧课程播放逻辑 ([8ea5248](https://github.com/ocsjs/ocsjs/commit/8ea5248851a61b1355b49d9ca26f6d571b8688d2))\n\n\n### Features\n\n* **script:** 对题库配置做唯一化处理 ([4bc0112](https://github.com/ocsjs/ocsjs/commit/4bc011222e16723e6cdcbb123789891a6a0d80fc))\n* **script:** 适配智慧树智慧课程学习和作业（包括AI助教和智慧课程），适配智慧树新版课程页面，适配智慧课程掌握度自动答题 ([7cd9dfd](https://github.com/ocsjs/ocsjs/commit/7cd9dfd28f02b1316283b4b331525939ec546fa7))\n* **script:** 支持智慧树智慧课程链接任务点 ([285140f](https://github.com/ocsjs/ocsjs/commit/285140ff3c73aae41ffa7213d3daf3ca76e4c72a))\n\n\n### Performance Improvements\n\n* **script:** 修改软件辅助启动提示 ([0021b5d](https://github.com/ocsjs/ocsjs/commit/0021b5dd85fd5dd9b0f45dfd94175a7c3b34dd68))\n\n\n\n## [4.9.59](https://github.com/ocsjs/ocsjs/compare/4.9.57...4.9.59) (2025-03-31)\n\n\n### Bug Fixes\n\n* typo ([57d4077](https://github.com/ocsjs/ocsjs/commit/57d4077bb0951648fba6fe4a9f30cee7a4567a05))\n\n\n### Features\n\n* 添加超星 ccqmxx.com 域名适配支持 ([79eb947](https://github.com/ocsjs/ocsjs/commit/79eb94744e442a73022d59d1dca67b13035f30aa))\n* **script:** 添加题库配置相同检测，如果相同则提示 ([9acb07e](https://github.com/ocsjs/ocsjs/commit/9acb07e0bf5fbf6cf0d6d02f47fdce7a21e4a235))\n\n\n\n## [4.9.57](https://github.com/ocsjs/ocsjs/compare/4.9.55...4.9.57) (2025-03-14)\n\n\n### Features\n\n* 添加智慧树-智慧课程掌握度答题功能 ([316eb6d](https://github.com/ocsjs/ocsjs/commit/316eb6d6b6cd4d3fe98fa13be6833779fb77fdf2))\n\n\n### Performance Improvements\n\n* 添加智慧树-智慧课程掌握度学习提示 ([908df79](https://github.com/ocsjs/ocsjs/commit/908df793965358d19c5cf87b792a7f47f2446a7d))\n\n\n\n## [4.9.55](https://github.com/ocsjs/ocsjs/compare/4.9.54...4.9.55) (2025-03-14)\n\n\n### Bug Fixes\n\n* 超星任务点检测优化 ([ce6c3ac](https://github.com/ocsjs/ocsjs/commit/ce6c3ac829f21b075592d0ad7cea7d670c7e132c))\n\n\n\n## [4.9.54](https://github.com/ocsjs/ocsjs/compare/4.9.52...4.9.54) (2025-03-13)\n\n\n### Bug Fixes\n\n* 优化智慧树智慧课程视频检测，修复智慧树智慧课程检测不到任务点问题 ([2f3777d](https://github.com/ocsjs/ocsjs/commit/2f3777d4d072da3163ed21cb630488f3fd89ed61))\n\n\n\n## [4.9.52](https://github.com/ocsjs/ocsjs/compare/4.9.51...4.9.52) (2025-03-12)\n\n\n### Bug Fixes\n\n* 修复 4.9.51 版本题库配置解析问题 ([b3d3e32](https://github.com/ocsjs/ocsjs/commit/b3d3e32e8b61f1bef7e7b94d631eccd47838a6dc))\n\n\n\n## [4.9.51](https://github.com/ocsjs/ocsjs/compare/4.9.46...4.9.51) (2025-03-12)\n\n\n\n## [4.9.46](https://github.com/ocsjs/ocsjs/compare/4.9.45...4.9.46) (2024-12-22)\n\n\n### Performance Improvements\n\n* 优化智慧树考试提示文案 ([b1e92a3](https://github.com/ocsjs/ocsjs/commit/b1e92a31b0a667d7f3cc1ae9cf462ba0277dcc2b))\n\n\n\n## [4.9.45](https://github.com/ocsjs/ocsjs/compare/4.9.44...4.9.45) (2024-12-21)\n\n\n### Bug Fixes\n\n* **script:** 修复上个版本图片链接处理导致的无法选择BUG ([baaed06](https://github.com/ocsjs/ocsjs/commit/baaed067f08f164821019c7b71287305f842b4d9))\n\n\n\n## [4.9.44](https://github.com/ocsjs/ocsjs/compare/4.9.43...4.9.44) (2024-12-21)\n\n\n### Bug Fixes\n\n* **core:** 修复图片题解析错误导致的URL重复，图片重复显示等问题 ([f5cc59f](https://github.com/ocsjs/ocsjs/commit/f5cc59f8e6a87a7144d0540f64a4a586594c78ef))\n\n\n\n## [4.9.43](https://github.com/ocsjs/ocsjs/compare/4.9.42...4.9.43) (2024-12-20)\n\n\n\n## [4.9.42](https://github.com/ocsjs/ocsjs/compare/4.9.41...4.9.42) (2024-12-06)\n\n\n\n## [4.9.41](https://github.com/ocsjs/ocsjs/compare/4.9.40...4.9.41) (2024-12-03)\n\n\n\n## [4.9.40](https://github.com/ocsjs/ocsjs/compare/4.9.39...4.9.40) (2024-11-10)\n\n\n### Bug Fixes\n\n* **script:** 优化职教云资源库无法答题的问题 ([c94c41e](https://github.com/ocsjs/ocsjs/commit/c94c41eb46ace8fa63d0296d8199941a58a46314))\n\n\n\n## [4.9.39](https://github.com/ocsjs/ocsjs/compare/4.9.37...4.9.39) (2024-11-09)\n\n\n### Bug Fixes\n\n* 修复 select 选择框显示问题 ([9f5b741](https://github.com/ocsjs/ocsjs/commit/9f5b741147cb100848517ebd4d7d131159a3689e))\n\n\n\n## [4.9.37](https://github.com/ocsjs/ocsjs/compare/4.9.36...4.9.37) (2024-11-06)\n\n\n### Features\n\n* 添加超星自动跳转未完成任务点功能 ([24927e6](https://github.com/ocsjs/ocsjs/commit/24927e63e765d990ef6f0971b40121f8507463fd))\n\n\n\n## [4.9.36](https://github.com/ocsjs/ocsjs/compare/4.9.35...4.9.36) (2024-11-06)\n\n\n### Bug Fixes\n\n* **script:** 修复选择输入框中默认值匹配错误导致的超星自动保存选择'save'，但是默认值依旧是80的问题 ([a2b639f](https://github.com/ocsjs/ocsjs/commit/a2b639fd5c22ef94a5d352cf6a62d79af6261ac7))\n\n\n\n## [4.9.35](https://github.com/ocsjs/ocsjs/compare/4.9.34...4.9.35) (2024-10-21)\n\n\n\n## [4.9.34](https://github.com/ocsjs/ocsjs/compare/4.9.33...4.9.34) (2024-10-18)\n\n\n\n## [4.9.33](https://github.com/ocsjs/ocsjs/compare/4.9.32...4.9.33) (2024-10-17)\n\n\n\n## [4.9.32](https://github.com/ocsjs/ocsjs/compare/4.9.31...4.9.32) (2024-10-09)\n\n\n\n## [4.9.31](https://github.com/ocsjs/ocsjs/compare/4.9.29...4.9.31) (2024-09-13)\n\n\n\n## [4.9.29](https://github.com/ocsjs/ocsjs/compare/4.9.28...4.9.29) (2024-08-10)\n\n\n### Bug Fixes\n\n* **scirpt:** 修复资源库最新版链接类名改变问题 ([341b37f](https://github.com/ocsjs/ocsjs/commit/341b37f42c815fc7e401e23636a2388ab592cbe5))\n\n\n\n## [4.9.28](https://github.com/ocsjs/ocsjs/compare/4.9.25...4.9.28) (2024-07-14)\n\n\n### Bug Fixes\n\n* **script:** 解决超星部分套壳网站无法自动下一章的BUG ([e8f05b6](https://github.com/ocsjs/ocsjs/commit/e8f05b6b8741d88903755910d072020fcf6a695a))\n* **script:** 优化对超星视频下载错误的处理，将自动跳过错误视频 ([b4f32c8](https://github.com/ocsjs/ocsjs/commit/b4f32c8cccf9a7d97107d2570b2d36e3e187809f))\n\n\n### Performance Improvements\n\n* **script:** 优化版本更新验证，不符合版本书写规范的话向用户提示报错 ([70dd13b](https://github.com/ocsjs/ocsjs/commit/70dd13b5c5cf58ccaf09cdc513c31985445023fe))\n\n\n\n## [4.9.25](https://github.com/ocsjs/ocsjs/compare/4.9.24...4.9.25) (2024-07-13)\n\n\n### Bug Fixes\n\n* **script:** 新增智慧职教 webtrn.cn 套壳网站支持，新增对30分钟学习弹窗的关闭功能 ([b17a19f](https://github.com/ocsjs/ocsjs/commit/b17a19f0c87e94579d2abba09afdadd98bce8e7b))\n\n\n\n## [4.9.24](https://github.com/ocsjs/ocsjs/compare/4.9.23...4.9.24) (2024-06-25)\n\n\n\n## [4.9.23](https://github.com/ocsjs/ocsjs/compare/4.9.21...4.9.23) (2024-06-25)\n\n\n### Bug Fixes\n\n* **script:** 修复职教云资源库，课件类型为NAN的BUG ([8b8768e](https://github.com/ocsjs/ocsjs/commit/8b8768e311b41717cb7df9d73417cf2a700c107a))\n* **script:** 修复职教云资源库重复下一章的BUG ([b566a5c](https://github.com/ocsjs/ocsjs/commit/b566a5ccd66b285b994f57f05dc6e454b21ed059))\n\n\n\n## [4.9.21](https://github.com/ocsjs/ocsjs/compare/4.9.20...4.9.21) (2024-06-19)\n\n\n### Bug Fixes\n\n* **core:** 修复题库配置 AnswererWrapper 自定义字段解析 handler 函数调用两次的BUG ([1e15377](https://github.com/ocsjs/ocsjs/commit/1e15377e0ed7b51f9777221607fa047800e7f765))\n* **script:** 修复搜索时换行被删除导致，两行合并造成的文字合并，导致搜索引擎可能出现的的搜索问题 ([b318c1e](https://github.com/ocsjs/ocsjs/commit/b318c1e227ebc0fe06c567139e1a09f307c106ec))\n\n\n\n## [4.9.20](https://github.com/ocsjs/ocsjs/compare/4.9.19...4.9.20) (2024-06-13)\n\n\n### Features\n\n* **script:** 新增 中国大学MOOC 考试功能 ([7286fa4](https://github.com/ocsjs/ocsjs/commit/7286fa47105a1ab8e06f7b4cca1c71354e59d015))\n\n\n\n## [4.9.19](https://github.com/ocsjs/ocsjs/compare/4.9.18...4.9.19) (2024-06-05)\n\n\n### Bug Fixes\n\n* **core:** 修复使用精确匹配时，脚本的多选题搜索结果会全部显示已完成的BUG ([65cfc48](https://github.com/ocsjs/ocsjs/commit/65cfc4832bd1bb070169b02ba7672f3aa347c494))\n* **script:** 添加完成率百分比符号 ([beec370](https://github.com/ocsjs/ocsjs/commit/beec3707cf1de48685a7cbb899d70b0e0003ce98))\n\n\n\n## [4.9.18](https://github.com/ocsjs/ocsjs/compare/4.9.17...4.9.18) (2024-06-04)\n\n\n### Bug Fixes\n\n* **script:** 修复全域名版本无法显示BUG ([6d52ec4](https://github.com/ocsjs/ocsjs/commit/6d52ec4983ce452fc072774abc358936380f4166))\n\n\n\n## [4.9.17](https://github.com/ocsjs/ocsjs/compare/4.9.16...4.9.17) (2024-05-24)\n\n\n### Bug Fixes\n\n* **script:** 修复上个版本任务点检测不到的BUG ([8bf7551](https://github.com/ocsjs/ocsjs/commit/8bf7551b41cd543bd3f345ec63320c8fa661a4e6))\n\n\n\n## [4.9.16](https://github.com/ocsjs/ocsjs/compare/4.9.12...4.9.16) (2024-05-23)\n\n\n### Bug Fixes\n\n* **script:** 优化超星人脸识别检测警告 ([311d8ad](https://github.com/ocsjs/ocsjs/commit/311d8adaf8c46eb66bb6373dae645ceb8856a70b))\n* **script:** 优化超星题库配置为空警告 ([9257770](https://github.com/ocsjs/ocsjs/commit/92577708f5737b445c986090470d78bd58d27999))\n\n\n### Features\n\n* **script:** 新增对超星链接任务点，音频PPT任务点的支持，优化代码，删除视频错误检测功能。 ([e90f1bb](https://github.com/ocsjs/ocsjs/commit/e90f1bbc3f13b966b020303e0498bd73e65844fd))\n\n\n\n## [4.9.12](https://github.com/ocsjs/ocsjs/compare/4.9.8...4.9.12) (2024-05-11)\n\n\n### Bug Fixes\n\n* **script:** 添加超星积分课的使用提示 ([ea74563](https://github.com/ocsjs/ocsjs/commit/ea7456324d60d41df699946207a6b1b73bf7b516))\n* **script:** 修复超星课程，章节测试偶尔出现卡死的BUG ([24b499d](https://github.com/ocsjs/ocsjs/commit/24b499d36c143810a49d6bb000e7d345317ac131))\n* **script:** 修复超星课程进入时需要人脸识别，但是疯狂跳转新版本导致的人脸识别疯狂刷新的BUG ([4fb764b](https://github.com/ocsjs/ocsjs/commit/4fb764bd53411006c435a591167cb48bb0ec4a74))\n* **script:** 修复超星英语判断题的文本BUG ([5c785ab](https://github.com/ocsjs/ocsjs/commit/5c785ab3828897604d861fb521a2b17c9afbd4b4))\n\n\n\n## [4.9.8](https://github.com/ocsjs/ocsjs/compare/4.9.7...4.9.8) (2024-05-10)\n\n\n\n## [4.9.7](https://github.com/ocsjs/ocsjs/compare/4.9.6...4.9.7) (2024-05-10)\n\n\n### Bug Fixes\n\n* **script:** 修复 '\"@ocsjs/core\"' has no exported member 'createQuestionTitleExtra'. BUG ([9f06abe](https://github.com/ocsjs/ocsjs/commit/9f06abee6747192c9fbe4b29ae82003e56481734))\n\n\n\n## [4.9.6](https://github.com/ocsjs/ocsjs/compare/4.9.4...4.9.6) (2024-05-10)\n\n\n### Bug Fixes\n\n* 更新 easy-us API ([0150b49](https://github.com/ocsjs/ocsjs/commit/0150b49ca661920a6a9c723a37af237c53d62b95))\n* 删除无用API ([8c8425f](https://github.com/ocsjs/ocsjs/commit/8c8425f337c7f001bbdc3bb74ef8e3849c413e65))\n* **script:** 修复英语前缀 【True or False】 的BUG ([01e1418](https://github.com/ocsjs/ocsjs/commit/01e141884cdd4b9ef65a6c489286aacc99cb2198))\n* **script:** 优化题库配置的开启和关闭按钮 ([b9197c6](https://github.com/ocsjs/ocsjs/commit/b9197c6eab4558061d8167b4fe7d5b1098bf83a4))\n* **script:** 优化题库配置的开启和关闭按钮 ([39b3046](https://github.com/ocsjs/ocsjs/commit/39b304671fb2991d7fbeac2ee1fabb757ebc50c4))\n\n\n\n## [4.9.4](https://github.com/ocsjs/ocsjs/compare/4.9.3...4.9.4) (2024-05-04)\n\n\n### Bug Fixes\n\n* **script:** 修复无法移动面板至边缘导致自动登录失效的BUG ([a05d0a1](https://github.com/ocsjs/ocsjs/commit/a05d0a1cb1e354dccbb52a567b858a4eb7276e63))\n\n\n\n## [4.9.3](https://github.com/ocsjs/ocsjs/compare/4.9.2...4.9.3) (2024-05-04)\n\n\n\n## [4.9.2](https://github.com/ocsjs/ocsjs/compare/4.9.1...4.9.2) (2024-05-04)\n\n\n### Bug Fixes\n\n* 导出 $elelment 对象，便于可修改 $modal 第二个参数 ([13a1b6d](https://github.com/ocsjs/ocsjs/commit/13a1b6d9d8e211e9b92558b950fbb0e96b0d6174))\n\n\n\n## [4.9.1](https://github.com/ocsjs/ocsjs/compare/4.9.0...4.9.1) (2024-05-04)\n\n\n\n# [4.9.0](https://github.com/ocsjs/ocsjs/compare/4.8.30...4.9.0) (2024-05-03)\n\n\n### Features\n\n* **all:** 优化API，分离UI层为新项目 easy-us ([ecc4ff6](https://github.com/ocsjs/ocsjs/commit/ecc4ff6a56577d40a59aea47418138ad9b6c04f1))\n\n\n\n## [4.8.30](https://github.com/ocsjs/ocsjs/compare/4.8.29...4.8.30) (2024-05-01)\n\n\n### Bug Fixes\n\n* **script:** 解决超星视频播放时出现： The play() request was interrupted by a call to pause() BUG ([4791bd8](https://github.com/ocsjs/ocsjs/commit/4791bd8c4a8f06890079bf748c0f547fd728ec94))\n\n\n### Features\n\n* **script:** 新增对职教云资源库的 spocjob 作业的支持 ([007d39a](https://github.com/ocsjs/ocsjs/commit/007d39af4fbfb474e47f180b8670479c0d7e9b4b))\n\n\n\n## [4.8.29](https://github.com/ocsjs/ocsjs/compare/4.8.27...4.8.29) (2024-04-20)\n\n\n\n## [4.8.27](https://github.com/ocsjs/ocsjs/compare/4.8.26...4.8.27) (2024-03-31)\n\n\n\n## [4.8.26](https://github.com/ocsjs/ocsjs/compare/4.8.25...4.8.26) (2024-03-29)\n\n\n### Features\n\n* **script:** 新增超星 cugbonline.cn 域名支持 ([359425a](https://github.com/ocsjs/ocsjs/commit/359425a92b93fc7fd93a3d55b69526d29db38d22))\n\n\n\n## [4.8.25](https://github.com/ocsjs/ocsjs/compare/4.8.24...4.8.25) (2024-03-28)\n\n\n### Bug Fixes\n\n* **script:** 修复添加额外菜单栏后，窗口无法正常最大化/最小化的BUG ([22c45bc](https://github.com/ocsjs/ocsjs/commit/22c45bc15d31ab1999761182d6ddbffec499e0ef))\n\n\n\n## [4.8.24](https://github.com/ocsjs/ocsjs/compare/4.8.13...4.8.24) (2024-03-28)\n\n\n### Bug Fixes\n\n* **core:** 修复窗口隐藏/显示 快捷键无效的BUG ([8002c82](https://github.com/ocsjs/ocsjs/commit/8002c8246339f936cfbafc677f9c053a6e0cfcbc))\n* **core:** 修复单选题答案回调内容不一致的BUG ([4265263](https://github.com/ocsjs/ocsjs/commit/4265263821e781cd3636c275120f8b987c63bbbf))\n* **core:** 修复设置超出最大最小值，导致变更时，本地的值没有同步导致刷新后依然没有发生变化的BUG ([0b05507](https://github.com/ocsjs/ocsjs/commit/0b05507875340c57689b5825f2d5efd8a4e6ebef))\n* **core:** 修复新版答题算法中搜题线程参数无效的BUG，始终只有一个搜题线程在运行 ([9e052f8](https://github.com/ocsjs/ocsjs/commit/9e052f85909b947f8fd53851233cdee93c47ca9a))\n* **core:** 修复已经在暂停状态，但是依然在答题的BUG ([3f04e21](https://github.com/ocsjs/ocsjs/commit/3f04e21fdaef3ee5972aa259399e5384b11f51a7))\n* **core:** 优化搜索结果的显示BUG，已搜到答案的题目显示未匹配到正确答案的BUG，以及答题进度不一致的显示BUG ([2fd9a72](https://github.com/ocsjs/ocsjs/commit/2fd9a72fb493bfe26593055346bcfe089feda8b9))\n* **script:** 全面优化答题速度，优化存在题库缓存时无需执行搜题间隔等待，优化特定网课勾选答案时需要停顿，正常网课无需停顿的情况，极大加速了答案勾选过程。 ([1ca3448](https://github.com/ocsjs/ocsjs/commit/1ca34480d7010c176f79d471f2ba264716782561))\n* **script:** 修复智慧树答题时题库缓存未正确存储的BUG ([978d927](https://github.com/ocsjs/ocsjs/commit/978d927f71b1f7e764d97257206615b0f7719791))\n* **script:** 修复重新答题按钮点击后，答题完成后的一些操作依然在进行的BUG ([e7148a3](https://github.com/ocsjs/ocsjs/commit/e7148a368144ab55b6708c4e03d23f7de6262b8b))\n\n\n### Features\n\n* **core:** 新增额外菜单栏功能，新增超星额外菜单栏快捷页面跳转按钮 ([c2066a0](https://github.com/ocsjs/ocsjs/commit/c2066a056439270f41a2f8cb0eee61a73690717e))\n* **script:** 新增【职教云】资源库作业支持 ([3cf9968](https://github.com/ocsjs/ocsjs/commit/3cf996824d1ff647499b3f8a12e6084eaf7ddf2d))\n\n\n\n## [4.8.13](https://github.com/ocsjs/ocsjs/compare/4.8.11...4.8.13) (2024-03-14)\n\n\n### Bug Fixes\n\n* **core:** 全局设置中部分元素的值为空时恢复默认值 ([9d539d0](https://github.com/ocsjs/ocsjs/commit/9d539d045df7db40035cf00eba157f10b389e623))\n* **core:** 优化答题模块算法 ([545d9bc](https://github.com/ocsjs/ocsjs/commit/545d9bc5c98ff1b342789de9ec681e2e34efed0e))\n\n\n\n## [4.8.11](https://github.com/ocsjs/ocsjs/compare/4.8.8...4.8.11) (2024-03-13)\n\n\n### Bug Fixes\n\n* **script:** 修复请求记录开启后无法读取内容的BUG ([31d2a85](https://github.com/ocsjs/ocsjs/commit/31d2a854e9ccbbef22fa1ad0ca0256f4fe493561))\n\n\n### Features\n\n* **script:** 对 TikuAdapter 做适配提示 ([cf2fe10](https://github.com/ocsjs/ocsjs/commit/cf2fe109faf5990086de68fb1f7213f0d4807500))\n\n\n\n## [4.8.8](https://github.com/ocsjs/ocsjs/compare/4.8.7...4.8.8) (2024-03-05)\n\n\n### Bug Fixes\n\n* **script:** 优化超星视频重复播放时没有重置视频进度的问题 ([c167ff0](https://github.com/ocsjs/ocsjs/commit/c167ff094ffe4287002cd3e1f0b08b92a0a82ceb))\n\n\n\n## [4.8.7](https://github.com/ocsjs/ocsjs/compare/4.8.6...4.8.7) (2024-03-01)\n\n\n### Bug Fixes\n\n* **script:** 修复2倍速被删除的BUG ([a861685](https://github.com/ocsjs/ocsjs/commit/a861685bea01f3bedad32c2828f2e2d3f38f94dd))\n\n\n\n## [4.8.6](https://github.com/ocsjs/ocsjs/compare/4.8.5...4.8.6) (2024-02-28)\n\n\n### Bug Fixes\n\n* **script:** 修改软件辅助返回字段 ([270f104](https://github.com/ocsjs/ocsjs/commit/270f104d4b490c55e2b6fc5d0b58d4cf5518aa80))\n\n\n\n## [4.8.5](https://github.com/ocsjs/ocsjs/compare/4.8.0...4.8.5) (2024-02-28)\n\n\n### Bug Fixes\n\n* **script:** 修复智慧树无法使用软件辅助，无法自动答题看视频的BUG ([a3cdb6f](https://github.com/ocsjs/ocsjs/commit/a3cdb6f13904879e0d3effd3874c567f34f468f2))\n\n\n### Features\n\n* **core:** 新增答案匹配模式选项 ([aab04af](https://github.com/ocsjs/ocsjs/commit/aab04af086a50aa72d2992087ecba69f60ad1447))\n* **script:** 添加题库配置自动读取剪贴板功能 ([fe19557](https://github.com/ocsjs/ocsjs/commit/fe19557c7cc91c7909a62c3b1dc93aac5c641106))\n\n\n### Performance Improvements\n\n* **core:** 优化 tooltip ([9655442](https://github.com/ocsjs/ocsjs/commit/96554423807d329e712b08998798d3ca5a309e7f))\n\n\n\n# [4.8.0](https://github.com/ocsjs/ocsjs/compare/4.7.46...4.8.0) (2024-02-26)\n\n\n### Bug Fixes\n\n* **core:** 新答题器的旧字段搜索结果显示修改 ([d8511a0](https://github.com/ocsjs/ocsjs/commit/d8511a023f87d4e8c10c4aabacf4d5118ac75177))\n* **core:** 修复当数字输入框是空格后无法正确读取的BUG ([fa7f2d5](https://github.com/ocsjs/ocsjs/commit/fa7f2d593fb42897352958d5a48c78a147671af1))\n* **core:** 修复新版答题器当线程为1时，无法搜题的BUG ([bd2bd53](https://github.com/ocsjs/ocsjs/commit/bd2bd5384e788f4f1c521e766a1f9c25c90b96a7))\n* **core:** 优化答题器算法 ([d29c74c](https://github.com/ocsjs/ocsjs/commit/d29c74c2ab2a470630567fad9a47605baa3e2d99))\n* **core:** 优化跨域通信只在顶部页面刷新时删除临时监听变量，优化代码添加注释 ([0f07dc4](https://github.com/ocsjs/ocsjs/commit/0f07dc47500f8b527f1612731c2cf0d5fe8e36aa))\n* **core:** 优化搜索结果显示，内部算法报错依然可以显示搜索结果 ([e002e3d](https://github.com/ocsjs/ocsjs/commit/e002e3d45dd4609d9401b9534ab8be396e979a4a))\n* **script:** 添加超星英文判断题的冗余删除 ([35b8f07](https://github.com/ocsjs/ocsjs/commit/35b8f07990d365ee239056e2281c0390200809f5))\n* **script:** 添加题库缓存开关 ([35226d3](https://github.com/ocsjs/ocsjs/commit/35226d34298fc0b39b773f612f99f39d8169b4dd))\n* **script:** 修复解除复制粘贴限制功能无效的BUG ([3f560a8](https://github.com/ocsjs/ocsjs/commit/3f560a83bd472c073c7de91e51720b78119fdf06))\n* **script:** 修复通知时间很短就消失的BUG ([cda009f](https://github.com/ocsjs/ocsjs/commit/cda009f4679fb7cfdce121f2379b68c0d46008b1))\n* **script:** 优化超星闯关模式以及解锁模式的重复进入检测算法 ([b148232](https://github.com/ocsjs/ocsjs/commit/b14823248aa4b5620756a4525d56a5f5aacbc7c8))\n* **script:** 优化超星英文判断题的解析 ([78be04e](https://github.com/ocsjs/ocsjs/commit/78be04e3dfb76fda8d5cbe5d850a0131a7d1bb08))\n\n\n### Features\n\n* 新增 answer_separator 答案分隔符设置 ([a742f80](https://github.com/ocsjs/ocsjs/commit/a742f808f43c9e6d9be010258b7d36cde83caf0e))\n* **script:** 添加超星 gdhkmooc.com 域名支持 ([c63e6d6](https://github.com/ocsjs/ocsjs/commit/c63e6d69e903483754895c4b14e2266f07788ce5))\n* **script:** 添加超星完成全部任务点后重新从开头学习功能 ([86454d3](https://github.com/ocsjs/ocsjs/commit/86454d31f227940298c866759e071622c76097bb))\n* **script:** 添加对智慧树自动保存答案的解释 ([53f0846](https://github.com/ocsjs/ocsjs/commit/53f0846b3134453cf8d9bf7c11d777407e7a111a))\n* **script:** 添加开发人员调试：显示Tab变量功能 ([a779b35](https://github.com/ocsjs/ocsjs/commit/a779b35f5e422d30a81d551db1ba84528b052d74))\n* **script:** 添加职教云 flv 格式支持 ([08d6719](https://github.com/ocsjs/ocsjs/commit/08d6719695d6ca293cb3333197eaf4981195678f))\n* **script:** 添加职教云的作业考试【客观填空题】的支持 ([92c5133](https://github.com/ocsjs/ocsjs/commit/92c51332130b31a2e1ece5deabc023a90fde57c3))\n* **script:** 添加智慧职教MOOC的单行填空题支持 ([5361ec4](https://github.com/ocsjs/ocsjs/commit/5361ec40a59d0cdb399324dcec484468b5e8d639))\n* **script:** 新增 $message 跨域调用功能，新增 $modal duration 参数，修复由于删除cors监听key导致的 undefined BUG ([320903f](https://github.com/ocsjs/ocsjs/commit/320903fdd9e1b82d47ae9ab475aae0eac0d0e2bc))\n* **script:** 新增新版职教云作业填空题支持 ([a09dcac](https://github.com/ocsjs/ocsjs/commit/a09dcac71cda6e95a24f6ce5e22ba6c3830f0c3b))\n* **script:** 新增职教云考试支持，txt文档查看支持 ([f95e8f8](https://github.com/ocsjs/ocsjs/commit/f95e8f84e03a45dd4afee2f517800512b112efa1))\n* **script:** 优化题库配置文案，优化超星文案，并添加运行时的反馈消息提示 ([d61d3ee](https://github.com/ocsjs/ocsjs/commit/d61d3ee051f6c5b1f6af849987776592159d21bb))\n* **script:** 在拓展应用中添加OCS全部配置的导入导出功能 ([0237e88](https://github.com/ocsjs/ocsjs/commit/0237e88c1157201911133405058c2b9eb3a43be6))\n\n\n### Performance Improvements\n\n* **core:** update remote-playwright-log ([3578781](https://github.com/ocsjs/ocsjs/commit/35787819b390d2bed230c79ad30283456a7d57b2))\n* **script and core:** 优化软件辅助类型提示以及代码 ([3394ada](https://github.com/ocsjs/ocsjs/commit/3394ada9acb234ee987ae58ac75c2cf96eacde21))\n* **script:** 简化 $console 生成的堆栈信息，减少存储量 ([2669525](https://github.com/ocsjs/ocsjs/commit/2669525fd942fa9a2e34e8166785e36891e1c5a3))\n* **script:** 添加更多的倍速选项 ([adf7e3d](https://github.com/ocsjs/ocsjs/commit/adf7e3dfb03838cd5dfaf3ebe1b55691685c5a09))\n\n\n\n## [4.7.46](https://github.com/ocsjs/ocsjs/compare/4.7.35...4.7.46) (2023-12-20)\n\n\n### Bug Fixes\n\n* **scrpt:** 优化题库配置在软件上的域名检测 ([04fba87](https://github.com/ocsjs/ocsjs/commit/04fba87036a8a7884fb40f7f5ab816778a453744))\n\n\n### Performance Improvements\n\n* **core:** type  update ([3fd3864](https://github.com/ocsjs/ocsjs/commit/3fd3864ea536038f616f2e357d1eee377a130d03))\n\n\n\n## [4.7.35](https://github.com/ocsjs/ocsjs/compare/4.7.34...4.7.35) (2023-12-19)\n\n\n### Features\n\n* **script:** 修复超星人脸验证时疯狂刷新的问题，新增超星人脸验证通知功能 ([e75786c](https://github.com/ocsjs/ocsjs/commit/e75786c890fc0db560a75db406195ead7ad81783))\n\n\n### Performance Improvements\n\n* **script:** 添加题库停用状态的开启提示 ([8e6aaed](https://github.com/ocsjs/ocsjs/commit/8e6aaedb13a3b3db51ffd0737ef037d628dc7033))\n\n\n\n## [4.7.34](https://github.com/ocsjs/ocsjs/compare/4.7.33...4.7.34) (2023-12-13)\n\n\n### Bug Fixes\n\n* **script:** 优化 TikuAdapter 提示 ([43622ba](https://github.com/ocsjs/ocsjs/commit/43622ba2b48ead377c03fb52ee7c2778256b8002))\n\n\n\n## [4.7.33](https://github.com/ocsjs/ocsjs/compare/4.7.32...4.7.33) (2023-12-13)\n\n\n### Bug Fixes\n\n* **script:** 修复部分脚本管理器无法读取 [@connect](https://github.com/connect) 头部元数据的问题 ([69cec2f](https://github.com/ocsjs/ocsjs/commit/69cec2f98ee5835baceb7796284d654ffbad5720))\n\n\n\n## [4.7.32](https://github.com/ocsjs/ocsjs/compare/4.7.30...4.7.32) (2023-12-13)\n\n\n### Bug Fixes\n\n* **build:** 修复全域名通用版本不带官方域名的BUG ([ce042af](https://github.com/ocsjs/ocsjs/commit/ce042af9d4deb95753a4c76b94a9d4baea7f8a2a))\n\n\n### Performance Improvements\n\n* **script:** 添加 TikuAdapter 配置域名白名单提示 ([24d8026](https://github.com/ocsjs/ocsjs/commit/24d802626c5cf8f763f463b1d598227454d8e1e3))\n\n\n\n## [4.7.30](https://github.com/ocsjs/ocsjs/compare/4.7.29...4.7.30) (2023-12-11)\n\n\n### Bug Fixes\n\n* **script:** 修改TikuAdapter type 类型解析 ([9aaeb7d](https://github.com/ocsjs/ocsjs/commit/9aaeb7d5b862eb7e848f0022dc1272bd543b71be))\n\n\n\n## [4.7.29](https://github.com/ocsjs/ocsjs/compare/4.7.27...4.7.29) (2023-12-11)\n\n\n### Bug Fixes\n\n* **script:** 添加多版本脚本的更新模块适配 ([4a7aa19](https://github.com/ocsjs/ocsjs/commit/4a7aa19889c7d5f1a412de7e91491d6f52ace0c0))\n\n\n### Performance Improvements\n\n* **script:** 添加 TikuAdapter 解析器说明 ([d200b22](https://github.com/ocsjs/ocsjs/commit/d200b22212300cb5b055471a4767b13a428660f4))\n\n\n\n## [4.7.27](https://github.com/ocsjs/ocsjs/compare/4.7.25...4.7.27) (2023-12-11)\n\n\n### Bug Fixes\n\n* **script:** 修复请求模块的重大BUG ([4b121bb](https://github.com/ocsjs/ocsjs/commit/4b121bb20c66e704bf32e09906a42bb7cf8566be))\n\n\n### Features\n\n* **script:** 新增 TikuAdapter 题库配置解析器，优化题库配置解析 ([1fc0d59](https://github.com/ocsjs/ocsjs/commit/1fc0d59313c2b2fe7f296b51340f3ff5d741e32e))\n\n\n\n## [4.7.25](https://github.com/ocsjs/ocsjs/compare/4.7.24...4.7.25) (2023-12-08)\n\n\n### Bug Fixes\n\n* **core:** 复上个版本无法输入任何文字的BUG ([133dd69](https://github.com/ocsjs/ocsjs/commit/133dd69f700c2fd4bebaad37afc9fa69060038ec))\n\n\n\n## [4.7.24](https://github.com/ocsjs/ocsjs/compare/4.7.21...4.7.24) (2023-12-08)\n\n\n### Bug Fixes\n\n* **core:** 优化对 headers 中 content-type 解析而实现传递不同的 data 数据 ([fcd962b](https://github.com/ocsjs/ocsjs/commit/fcd962bd27de2c9fbc5c3e57af9abd54ec6db5a3))\n* **script:** 将中国大学MOOC的页面切换信息设置为警告信息 ([84f1d6e](https://github.com/ocsjs/ocsjs/commit/84f1d6e7881eaf886ec626952f159232d809acfc))\n\n\n### Features\n\n* **core:** 添加快捷键可以显示/隐藏 窗口功能 ([f33e67f](https://github.com/ocsjs/ocsjs/commit/f33e67f157d2ee6b1d8dfcfa3d783ddbc842a985))\n\n\n\n## [4.7.21](https://github.com/ocsjs/ocsjs/compare/4.7.20...4.7.21) (2023-12-05)\n\n\n### Bug Fixes\n\n* **build:** 修复打包时不自动上传文件的BUG ([4983788](https://github.com/ocsjs/ocsjs/commit/4983788703e021d157b7562d0672848f00e04901))\n\n\n\n## [4.7.20](https://github.com/ocsjs/ocsjs/compare/4.7.19...4.7.20) (2023-12-05)\n\n\n\n## [4.7.19](https://github.com/ocsjs/ocsjs/compare/4.7.18...4.7.19) (2023-12-05)\n\n\n\n## [4.7.18](https://github.com/ocsjs/ocsjs/compare/4.7.17...4.7.18) (2023-12-05)\n\n\n### Bug Fixes\n\n* **build:** 修复上个版本无法自动打包的BUG，优化打包代码 ([74a97cd](https://github.com/ocsjs/ocsjs/commit/74a97cd4f187f694f9b63df581a40f6c4a1382b8))\n\n\n\n## [4.7.17](https://github.com/ocsjs/ocsjs/compare/4.7.14...4.7.17) (2023-12-05)\n\n\n### Features\n\n* **build:** 添加全域名通用脚本打包 user.common.user.js ([81f2cfa](https://github.com/ocsjs/ocsjs/commit/81f2cfa8e38f267f49930c1cc4dec402b8154835))\n\n\n### Performance Improvements\n\n* **script:** 添加请求记录方法过滤选项 HEAD ([63b58d5](https://github.com/ocsjs/ocsjs/commit/63b58d5354d5b086479a9d1a63bdadf797a71e51))\n* **script:** 添加在线搜题题库配置为空的提示 ([7caf525](https://github.com/ocsjs/ocsjs/commit/7caf5252f9c7c35e69e9102bf70cdd554eae8e46))\n\n\n\n## [4.7.14](https://github.com/ocsjs/ocsjs/compare/4.7.7...4.7.14) (2023-12-05)\n\n\n### Bug Fixes\n\n* **core:** 修复 attrs 属性的 style 特殊字段不生效的BUG， 将每个文本框设置最小的宽高 ([fdcc2ca](https://github.com/ocsjs/ocsjs/commit/fdcc2caf60f761d49fd8f0d22cf8e10cdf1803cc))\n* **core:** 修复下拉选择框只有value值时不显示的BUG ([2d5f371](https://github.com/ocsjs/ocsjs/commit/2d5f371cca889c1b6ccf7acced557b0b9ae4f32f))\n* **script:** 修复搜索结果序号过多时，序号页面超出页面的BUG ([8958910](https://github.com/ocsjs/ocsjs/commit/89589102791225401391f466bc36f1e7d2cd9506))\n* **script:** 修复中国大学MOOC课堂测验答题完成后没有等待暂停时间步骤的BUG ([3bc76b1](https://github.com/ocsjs/ocsjs/commit/3bc76b1ef981d1fc63f51b4270a4b8af4ec924ef))\n* **script:** 优化在线搜题功能，缓存搜索题目，优化划词功能 ([c4093b1](https://github.com/ocsjs/ocsjs/commit/c4093b1b64245c07db356a64094890eae314247c))\n\n\n### Features\n\n* **core:** 新增题库配置的字段解析器功能 ([f596488](https://github.com/ocsjs/ocsjs/commit/f59648871a8648ca50a15d3d162f2e01eb5a52ef))\n* **script:** 添加超星 hnvist.cn fjlecb.cn 域名支持 ([afd86d6](https://github.com/ocsjs/ocsjs/commit/afd86d62425fd1140c48f764bf11102dcf1dac32))\n* **script:** 新增超星旧版学习页面自动转换新版功能 ([9992a03](https://github.com/ocsjs/ocsjs/commit/9992a03597d4954049187b5d2ccce5cfe0430f9f))\n* **script:** 新增开发人员请求记录调试页面 ([5baac27](https://github.com/ocsjs/ocsjs/commit/5baac2770e03a0222b8f800789d3743c1234e125))\n\n\n### Performance Improvements\n\n* **core:** request 模块的 gm http 的 data 使用 JSON.stringify 进行转换 ([7d4f660](https://github.com/ocsjs/ocsjs/commit/7d4f6602ecd7d0268b2e888c51a907f0a2cc4268))\n* **script:** 删除超星多余日志 ([7be1071](https://github.com/ocsjs/ocsjs/commit/7be10712c36bd35c21175862eb7be915971d7b52))\n\n\n\n## [4.7.7](https://github.com/ocsjs/ocsjs/compare/4.7.1...4.7.7) (2023-11-30)\n\n\n### Bug Fixes\n\n* **script:** 将超星提交功能设置为3秒延时 ([2161fa4](https://github.com/ocsjs/ocsjs/commit/2161fa461e0383278ff31b60475daa62702fdb3e))\n* **script:** 新增题目冗余字段 ([596101f](https://github.com/ocsjs/ocsjs/commit/596101ff46b5fbd85bb7725fb9b9caeab6aa4169))\n* **script:** 修复超星学习通章节测验已完成，但依然开始答题的BUG ([169eb4a](https://github.com/ocsjs/ocsjs/commit/169eb4aedc1e3351de6546891de9050510fe5ffc))\n* **script:** 优化超星作业考试/章节测验 中 type 传递是 undefined 的BUG ([c2a1640](https://github.com/ocsjs/ocsjs/commit/c2a16402a5f8b97417cb3254faeccc2929d6ee53))\n* **script:** 优化职教云-资源库：新增对 mp3 的支持，新增自动跳过测验功能 ([78634b4](https://github.com/ocsjs/ocsjs/commit/78634b4a35600d7baf48f4329e4970ce90c75b27))\n\n\n### Features\n\n* **core:** 优化答题uploadHandler API，新增 commonWork.options.onWorkerCreated 选项 ([c4154df](https://github.com/ocsjs/ocsjs/commit/c4154df1dbf4e2dfb94c92f2b4b83fd1c4c4d7c4))\n* **script:** 新增中国大学MOOC，随堂测验自动答题功能 ([f79fb08](https://github.com/ocsjs/ocsjs/commit/f79fb08a93b928127d1e710d31ed0b0a5cf64f38))\n\n\n### Performance Improvements\n\n* **script:** 导出 ICourseProject 变量，开发者调试功能中可用 ([13d8787](https://github.com/ocsjs/ocsjs/commit/13d8787feafba0593f463246fb1fac4b707e1922))\n* **script:** 增加超星任务点关闭后的开启提示 ([fe11e6e](https://github.com/ocsjs/ocsjs/commit/fe11e6ef7c1bc6360c1c8ea58cc7fe82e4552713))\n\n\n\n## [4.7.1](https://github.com/ocsjs/ocsjs/compare/4.7.0...4.7.1) (2023-11-25)\n\n\n### Bug Fixes\n\n* **script:** 修复上个版本 职教云资源库获取课程信息失败 bug ([d97e538](https://github.com/ocsjs/ocsjs/commit/d97e538601a3d015c0e66609a5397033ab643a9c))\n\n\n\n# [4.7.0](https://github.com/ocsjs/ocsjs/compare/4.6.32...4.7.0) (2023-11-25)\n\n\n### Bug Fixes\n\n* **core:** 添加题库配置 method 大小写适配 ([d496be7](https://github.com/ocsjs/ocsjs/commit/d496be7cc5253c4b7ac8ba171dbd508ba9038d2a))\n* **core:** 修复选项元素在没有 title 时显示为 undefined 的 BUG ([12ff0dc](https://github.com/ocsjs/ocsjs/commit/12ff0dc8c623ca0d4e38f230946d950ce18e26db))\n* **core:** 优化选择下拉框设置，优化代码，修改之前由于无法获取value而使用的 $creator.selectOptions 的BUG ([2cf5318](https://github.com/ocsjs/ocsjs/commit/2cf5318030eb9427811db65973efe5b36eac2692))\n* **script:** 将软件辅助的点击设置成点击元素中心点 ([a0e421d](https://github.com/ocsjs/ocsjs/commit/a0e421df0ceeba369bb9c64c95e0de7e21e2ff7b))\n* **script:** 添加智慧树题目类型判断，修复某些题目不自动填写的BUG ([7bce433](https://github.com/ocsjs/ocsjs/commit/7bce4331ec9f504b6d887ec5e3d1443613200ce1))\n* **script:** 修复职教云更新后-资源库不自动学习的BUG ([2d4d026](https://github.com/ocsjs/ocsjs/commit/2d4d0269d86e525ed288d322e231a4422572e461))\n* **script:** 优化脚本字间距，搜索结果字间距和窗口间距 ([bcc4d87](https://github.com/ocsjs/ocsjs/commit/bcc4d87acbfd6aa78dce50d81bc561992f5fe7ab))\n* **script:** 优化软件辅助错误提示 ([7f42cba](https://github.com/ocsjs/ocsjs/commit/7f42cbafa069d6350c74fe07a06e58d8782abe54))\n* **script:** 优化智慧树学习提示 ([ee44189](https://github.com/ocsjs/ocsjs/commit/ee44189adac7b50bb2e35b9e7aca5eb1c4f73937))\n* **script:** 优化智慧职教填空题 ([586b1fc](https://github.com/ocsjs/ocsjs/commit/586b1fc66468d3b55d4c359b84d1a4ec36f89f73))\n\n\n### Features\n\n* **script:** 添加超星学习通 视频内题目随机答题功能 ([e2d9d5d](https://github.com/ocsjs/ocsjs/commit/e2d9d5d84a11c62d5e21a2dba54be76f3f7e3ff4))\n* **script:** 添加超星学习通-章节页面自动切换脚本 ([c0ac30c](https://github.com/ocsjs/ocsjs/commit/c0ac30c0b60a72de097f53dcdb94cd9247bd0e60))\n* **script:** 添加职教云遇到讨论课件时自动跳过 ([994ba3e](https://github.com/ocsjs/ocsjs/commit/994ba3e9055581b14a2fb61aa680bb9f2327eb2c))\n* **script:** 新增网课 中国大学MOOC 的学习、作业脚本支持 ([17445e5](https://github.com/ocsjs/ocsjs/commit/17445e5478f398d3580bc561eee2542804872478))\n* **script:** 新增智慧职教-学习中心自动学习功能 ([0a8de35](https://github.com/ocsjs/ocsjs/commit/0a8de35bd99d74a43064a5f0213069e1d9ad80a1))\n* **script:** 优化全部网课平台的系统通知功能，添加任务点完成提示，添加智慧树验证码提示 ([7e30125](https://github.com/ocsjs/ocsjs/commit/7e30125fd0858bd9c2067de0e7f3ad95bf639ff9))\n\n\n\n## [4.6.32](https://github.com/ocsjs/ocsjs/compare/4.6.29...4.6.32) (2023-11-01)\n\n\n### Bug Fixes\n\n* **script:** 新增超星其他题，类型支持 ([ee2cf4d](https://github.com/ocsjs/ocsjs/commit/ee2cf4dfd987e9adf62f7d2767c7dff3560def53))\n\n\n### Features\n\n* **core:** 添加优先级选项，排序特定脚本的执行速度 ([049234c](https://github.com/ocsjs/ocsjs/commit/049234c280835363d1e56581cbe782d8e98424e6))\n* **script:** 新增职教云资源库 pdf 支持，新增职教云作业脚本 ([f703288](https://github.com/ocsjs/ocsjs/commit/f703288641ecf794635b50922a0dab3b717f3446))\n\n\n\n## [4.6.28](https://github.com/ocsjs/ocsjs/compare/4.6.27...4.6.28) (2023-10-24)\n\n\n\n## [4.6.29](https://github.com/ocsjs/ocsjs/compare/4.6.28...4.6.29) (2023-10-24)\n\n\n### Bug Fixes\n\n* **script:** 修复软件配置同步失败问题 ([98eeb3d](https://github.com/ocsjs/ocsjs/commit/98eeb3de94e6a434bf84f1d64a2036624203df19))\n\n\n\n## [4.6.28](https://github.com/ocsjs/ocsjs/compare/4.6.27...4.6.28) (2023-10-24)\n\n\n### Bug Fixes\n\n* **script:** 修复上个版本无法答题的BUG ([7918a3d](https://github.com/ocsjs/ocsjs/commit/7918a3d7bf4b7e0bfb78fc4a7ac1a9e6058445d7))\n\n\n\n## [4.6.27](https://github.com/ocsjs/ocsjs/compare/4.6.25...4.6.27) (2023-10-24)\n\n\n### Bug Fixes\n\n* **script:** 修复超星章节测试题库被禁用的时候依然使用的BUG ([fff8cc4](https://github.com/ocsjs/ocsjs/commit/fff8cc4d41e49cf8c4f0d8f13f9cde3143505434))\n* **script:** 修复智慧树检测习惯分出错 ([436eedd](https://github.com/ocsjs/ocsjs/commit/436eedd449c15d74be8b4b07fdd33d7c01e3db0e))\n\n\n\n## [4.6.25](https://github.com/ocsjs/ocsjs/compare/4.6.23...4.6.25) (2023-10-22)\n\n\n### Bug Fixes\n\n* **script:** 深度优化智慧树弹窗BUG，以及倍速清晰度不选择BUG ([f61498c](https://github.com/ocsjs/ocsjs/commit/f61498c4640cbe8583b33d6d46a922341c3a57ae))\n\n\n### Features\n\n* **script:** 新增可以设置不被软件配置同步覆盖的设置，修复智慧树学习记录刷新后清空的BUG ([b35f87f](https://github.com/ocsjs/ocsjs/commit/b35f87fc5fd01a9de37fdc2855da2f6dfd8fc9df))\n\n\n\n## [4.6.23](https://github.com/ocsjs/ocsjs/compare/4.6.22...4.6.23) (2023-10-20)\n\n\n### Bug Fixes\n\n* **core:** 优化核心域名匹配逻辑 ([8cbcc94](https://github.com/ocsjs/ocsjs/commit/8cbcc9412726c673e9f1ad8898a46ded1f6502c4))\n* **script:** 持续优化智慧树倍速和清晰度选择功能 ([86d133b](https://github.com/ocsjs/ocsjs/commit/86d133bab4fc3063dc39335f61bf7a6f88226fd1))\n* **script:** 将软件同步功能加快到 onactive ([db44761](https://github.com/ocsjs/ocsjs/commit/db4476158b39765c28cfe5491cacfedc4a193c2b))\n* **script:** 优化超星新课程页面不显示使用提示的问题 ([3daf262](https://github.com/ocsjs/ocsjs/commit/3daf2623a97cd1a6d22aa3c3fd6bfc1e2b960f39))\n* **script:** 优化智慧树弹窗答题 ([e7a7a1f](https://github.com/ocsjs/ocsjs/commit/e7a7a1f12246da929d106e69be50aca60d519117))\n\n\n\n## [4.6.22](https://github.com/ocsjs/ocsjs/compare/4.6.19...4.6.22) (2023-10-19)\n\n\n### Bug Fixes\n\n* **script:** 修复智慧树需要调整窗口的BUG ([45c5f0b](https://github.com/ocsjs/ocsjs/commit/45c5f0b6a834b151d8955d05bf62cfa4ecb228c0))\n\n\n### Features\n\n* **script:** 兼容智慧职教套壳网站 courshare.cn ([7a2a088](https://github.com/ocsjs/ocsjs/commit/7a2a08832cafaefc70cefc7a3cec63131796655a))\n* **script:** 收录超星套壳域名 qutjxjy.cn ynny.cn ([2d9b6fc](https://github.com/ocsjs/ocsjs/commit/2d9b6fcc81fa8ddce44fcc24d1756a6ef07624af))\n\n\n\n## [4.6.19](https://github.com/ocsjs/ocsjs/compare/4.6.10...4.6.19) (2023-10-15)\n\n\n### Bug Fixes\n\n* **script:** 补充答题冗余字段删除数据 ([6d3544d](https://github.com/ocsjs/ocsjs/commit/6d3544d8fb49db88e943cdccfa9fb171792c1078))\n* **script:** 修复软件配置同步后，日志页面全锁定的BUG ([e6f0610](https://github.com/ocsjs/ocsjs/commit/e6f0610e42c7bb0137cfc7beb79921a3ef1e869b))\n* **script:** 修复使用软件同步时，智慧树学习自动暂停无效的BUG，以及存在验证码时自动暂停的优化。 ([8aa4bcf](https://github.com/ocsjs/ocsjs/commit/8aa4bcfd7f85e6c3c00afbb99fb361b4a70f1a73))\n* **script:** 优化窗口大小自动调整，优化窗口大小警告模块 ([6882026](https://github.com/ocsjs/ocsjs/commit/6882026b2b9f2441d689add73895bed8797774f6))\n\n\n### Features\n\n* **script:** 将软件辅助警告修改成弹窗 ([6b88c29](https://github.com/ocsjs/ocsjs/commit/6b88c298befbee314e3bda309cdd426d04d2dc41))\n* **script:** 添加超星强制答题提示 ([0067efc](https://github.com/ocsjs/ocsjs/commit/0067efc7e7dc7f09018709ad0b5f0ac636807eef))\n* **script:** 添加对自动调整窗口开关的重启提示 ([46b957b](https://github.com/ocsjs/ocsjs/commit/46b957b6706bf501d8a22b3b0f336e5f83deb31f))\n* **script:** 添加职教云【资源库】支持 ([da6f135](https://github.com/ocsjs/ocsjs/commit/da6f135276ace1394c4072f2a4cd02625753a689))\n\n\n\n## [4.6.10](https://github.com/ocsjs/ocsjs/compare/4.6.9...4.6.10) (2023-10-02)\n\n\n\n## [4.6.9](https://github.com/ocsjs/ocsjs/compare/4.6.7...4.6.9) (2023-10-02)\n\n\n### Bug Fixes\n\n* **script:** 修复智慧树考试脚本无法使用的BUG ([5f3f972](https://github.com/ocsjs/ocsjs/commit/5f3f9729687f93a5d2e36161883b822d14075518))\n\n\n### Features\n\n* **script:** 新增超星阅读任务无限阅读功能 ([137f739](https://github.com/ocsjs/ocsjs/commit/137f7397cef6554260db10f0d2a7535ca3db8073))\n\n\n\n## [4.6.7](https://github.com/ocsjs/ocsjs/compare/4.6.5...4.6.7) (2023-09-26)\n\n\n### Bug Fixes\n\n* **script:** 微调智慧树窗口大小要求，宽2200，高1200 ([f438b79](https://github.com/ocsjs/ocsjs/commit/f438b79512463396cba1ca59aa93422b06ead276))\n\n\n### Features\n\n* **script:** 新增功能：超星强制答题功能，没有黄色任务点的章节测试也可以运行自动答题。 ([453d254](https://github.com/ocsjs/ocsjs/commit/453d254aa575b23ad7571b811dc618a467ae1826))\n\n\n\n## [4.6.5](https://github.com/ocsjs/ocsjs/compare/4.6.4...4.6.5) (2023-09-25)\n\n\n### Bug Fixes\n\n* **script:** 修复智慧树调成窗口大于最小值后依然说不对的BUG ([be94200](https://github.com/ocsjs/ocsjs/commit/be9420097c81574b9b6b4a910808f27f19cbdf23))\n\n\n\n## [4.6.4](https://github.com/ocsjs/ocsjs/compare/4.6.3...4.6.4) (2023-09-24)\n\n\n### Features\n\n* **script:** 添加智慧树窗口自动调节功能的选项按钮 ([0543ca1](https://github.com/ocsjs/ocsjs/commit/0543ca1e6a8acd92bdc2b1889beb7a4d8c0a2caf))\n\n\n\n## [4.6.3](https://github.com/ocsjs/ocsjs/compare/4.6.2...4.6.3) (2023-09-24)\n\n\n### Bug Fixes\n\n* **script:** 添加智慧树窗口检测提示 ([849e28d](https://github.com/ocsjs/ocsjs/commit/849e28ddf7c57a5b7457d6f2b63a250a66f3792a))\n\n\n### Features\n\n* **script:** 添加自动设置窗口大小功能，避免元素无法点击 ([6aed250](https://github.com/ocsjs/ocsjs/commit/6aed250ea05a65c39ff5b5c36783510b2210781d))\n\n\n\n## [4.6.2](https://github.com/ocsjs/ocsjs/compare/4.6.1...4.6.2) (2023-09-22)\n\n\n### Bug Fixes\n\n* **script:** 修复上个版本智慧树作业BUG ([e782403](https://github.com/ocsjs/ocsjs/commit/e7824039f22862ebbd412f43c37b248bea4b0232))\n\n\n\n## [4.6.1](https://github.com/ocsjs/ocsjs/compare/4.6.0...4.6.1) (2023-09-22)\n\n\n### Bug Fixes\n\n* **script:** 优化智慧树刷课逻辑，增加流畅度 ([3a93157](https://github.com/ocsjs/ocsjs/commit/3a9315773fab3b7b8561ff57e189d58ff1a68150))\n\n\n\n# [4.6.0](https://github.com/ocsjs/ocsjs/compare/4.5.8...4.6.0) (2023-09-22)\n\n\n### Bug Fixes\n\n* **script:** 新增软件辅助功能，全面优化智慧树，共享课学习作业考试以及学分课视频 ([18a2725](https://github.com/ocsjs/ocsjs/commit/18a2725328b7917b8417ff8111e8a09fb4e09052))\n* **script:** 优化超星最新考试页面支持 ([9e71239](https://github.com/ocsjs/ocsjs/commit/9e712393e26b8faced900a4e56bc8c4f0dc00294))\n\n\n\n## [4.5.8](https://github.com/ocsjs/ocsjs/compare/4.5.5...4.5.8) (2023-09-15)\n\n\n### Bug Fixes\n\n* **script:** 修复部分课程不显示超星阅读任务的提示页面 ([907bb63](https://github.com/ocsjs/ocsjs/commit/907bb634a82889aa933a6989ab9c3d46cbba01c3))\n* **script:** 修复在中国大学MOOC中点击事件监听被修改的问题，将 addEventListener('click') 改成 onlick ([b1eba8f](https://github.com/ocsjs/ocsjs/commit/b1eba8ff742738ff412f94b2f0e6ba83819250a4))\n* **script:** 修复智慧树最新版脚本被检测的问题 ([adfe7a0](https://github.com/ocsjs/ocsjs/commit/adfe7a0cee1994396714cd3e01868cad409476c5))\n\n\n\n## [4.5.5](https://github.com/ocsjs/ocsjs/compare/4.5.4...4.5.5) (2023-09-12)\n\n\n### Features\n\n* **script:** 使更新模块可视化 ([0e27628](https://github.com/ocsjs/ocsjs/commit/0e27628d319db704f9849926a9602d1406f43e63))\n\n\n\n## [4.5.4](https://github.com/ocsjs/ocsjs/compare/4.5.3...4.5.4) (2023-09-12)\n\n\n### Bug Fixes\n\n* **core and script:** 修复上个版本出现的弹窗底部消失的问题，优化超星自动答题后台日志 ([37e6749](https://github.com/ocsjs/ocsjs/commit/37e6749a5bbef7788b2bd3b6efd2aba98581ceba))\n\n\n\n## [4.5.3](https://github.com/ocsjs/ocsjs/compare/4.5.0...4.5.3) (2023-09-12)\n\n\n### Features\n\n* **core:** 添加跳过版本的功能 ([63913b2](https://github.com/ocsjs/ocsjs/commit/63913b222e53b128a26d8aa3b4dc5dee654b67ae))\n* **script:** 添加新版职教云音频的播放 ([b7b47b9](https://github.com/ocsjs/ocsjs/commit/b7b47b9c0cb884e00cc98a6473cbbcdbdc933138))\n* **script:** 添加新版职教云DOC文档学习功能 ([9a5babd](https://github.com/ocsjs/ocsjs/commit/9a5babd3379bc15e6c9cc0fe8c5f91a7d10bd5cb))\n\n\n\n# [4.5.0](https://github.com/ocsjs/ocsjs/compare/4.4.35...4.5.0) (2023-09-09)\n\n\n### Features\n\n* **script:** 重写职教云刷课逻辑 ([1b50d5b](https://github.com/ocsjs/ocsjs/commit/1b50d5b330db5676b94777245657793f5c55162d))\n\n\n\n## [4.4.35](https://github.com/ocsjs/ocsjs/compare/4.4.34...4.4.35) (2023-09-04)\n\n\n### Bug Fixes\n\n* 优化自动发布的说明文档 ([cc84599](https://github.com/ocsjs/ocsjs/commit/cc84599a36f6afd8a10efd13d0b9decf6ca6608c))\n* **script:** 修复超星选择题选中的答案被取消的BUG ([808709a](https://github.com/ocsjs/ocsjs/commit/808709a7503c3d34c2463d3156c1fde26deb8267))\n* test ([c70ec9b](https://github.com/ocsjs/ocsjs/commit/c70ec9b94d166c5f6ab4a4aa649f7ca8f004199a))\n\n\n\n## [4.4.34](https://github.com/ocsjs/ocsjs/compare/4.4.33...4.4.34) (2023-08-25)\n\n\n### Bug Fixes\n\n* 优化打包文件 ([3fd8657](https://github.com/ocsjs/ocsjs/commit/3fd86577271cd49746b64bae9a7131f7dc99c83f))\n\n\n\n## [4.4.33](https://github.com/ocsjs/ocsjs/compare/4.4.32...4.4.33) (2023-08-25)\n\n\n### Bug Fixes\n\n* 优化github actions ([9f6608c](https://github.com/ocsjs/ocsjs/commit/9f6608cd775ac121ce5fcb070ba0b1b699a54cc6))\n\n\n\n## [4.4.32](https://github.com/ocsjs/ocsjs/compare/4.4.31...4.4.32) (2023-08-25)\n\n\n### Bug Fixes\n\n* **script:** 优化智慧树最新异常检测 ([b634a46](https://github.com/ocsjs/ocsjs/commit/b634a4694a1fcf88956f6dcde9f8e7a8b891fccd))\n\n\n\n## [4.4.31](https://github.com/ocsjs/ocsjs/compare/4.4.30...4.4.31) (2023-08-05)\n\n\n### Bug Fixes\n\n* **script:** 修复超星判断题更新后，文字优化不兼容的问题 ([f7beff9](https://github.com/ocsjs/ocsjs/commit/f7beff9e109c5c07a2f27df567c7cff319a4caff))\n\n\n\n## [4.4.30](https://github.com/ocsjs/ocsjs/compare/4.4.29...4.4.30) (2023-06-22)\n\n\n### Bug Fixes\n\n* **script:** 新增超星分录题支持 ([68c007b](https://github.com/ocsjs/ocsjs/commit/68c007bb7ad890e57cec8cd5c39ff30b00ee599f))\n\n\n\n## [4.4.29](https://github.com/ocsjs/ocsjs/compare/4.4.25...4.4.29) (2023-06-22)\n\n\n### Bug Fixes\n\n* **build:** 对脚本打包的 match 元数据进行去重 ([9de7d2c](https://github.com/ocsjs/ocsjs/commit/9de7d2c99a97c0786a3aa3dc9981643eedfe407d))\n* **script:** 将题库配置为空提醒设置为一直显示 ([d07bdbf](https://github.com/ocsjs/ocsjs/commit/d07bdbfd0735b4ccd4bb9fabef6a6f5f15a73707))\n* **script:** 新增超星题目类型支持：资料题 ([b6d3a00](https://github.com/ocsjs/ocsjs/commit/b6d3a006fb64b1f5aa58f806a68cfc56980ea340))\n* **script:** 修复超星作业考试文本框无法自动保存BUG ([ec58efe](https://github.com/ocsjs/ocsjs/commit/ec58efedc5d047ddb1c66b32023df0e24709f315))\n\n\n\n## [4.4.25](https://github.com/ocsjs/ocsjs/compare/4.4.24...4.4.25) (2023-05-25)\n\n\n### Bug Fixes\n\n* **core:** 修复上个版本题库连接超时问题 ([1c045ef](https://github.com/ocsjs/ocsjs/commit/1c045ef549a75717981bd99e97b4ecbee9d6f20d))\n\n\n\n## [4.4.24](https://github.com/ocsjs/ocsjs/compare/4.4.23...4.4.24) (2023-05-25)\n\n\n### Bug Fixes\n\n* **script and core:** 修复题库状态检测无限执行的BUG，优化 onrender 中的监听器执行逻辑 ([cbe7f0b](https://github.com/ocsjs/ocsjs/commit/cbe7f0b9a1a2bdb47f0dff09739b41270b556c70))\n* **script:** 优化智慧职教使用提示 ([ca5d4b3](https://github.com/ocsjs/ocsjs/commit/ca5d4b3ea4f29a265497ae0b401f3e28a575b081))\n\n\n\n## [4.4.23](https://github.com/ocsjs/ocsjs/compare/4.4.22...4.4.23) (2023-05-20)\n\n\n\n## [4.4.22](https://github.com/ocsjs/ocsjs/compare/4.4.21...4.4.22) (2023-05-20)\n\n\n### Bug Fixes\n\n* **build:** update build file ([c5d50b1](https://github.com/ocsjs/ocsjs/commit/c5d50b14bc4d834a3d5b8c82931abae8b15fca70))\n\n\n\n## [4.4.21](https://github.com/ocsjs/ocsjs/compare/4.4.20...4.4.21) (2023-05-20)\n\n\n\n## [4.4.20](https://github.com/ocsjs/ocsjs/compare/4.4.19...4.4.20) (2023-05-20)\n\n\n\n## [4.4.19](https://github.com/ocsjs/ocsjs/compare/4.4.15...4.4.19) (2023-05-20)\n\n\n### Bug Fixes\n\n* **all:** 修复添加配置分隔线后，OCS配置同步锁的样式显示出错 ([a257084](https://github.com/ocsjs/ocsjs/commit/a25708419505672e40617ccc2a9088a6038ce971))\n* **script:** 添加软件自动登录辅助页面 ([a2bfa20](https://github.com/ocsjs/ocsjs/commit/a2bfa20014c84d8ee7d59032eca01bac225d5d0e))\n\n\n### Features\n\n* **all:** 添加配置分割线，便于设置区域分组 ([980d591](https://github.com/ocsjs/ocsjs/commit/980d591c92466c79d2a35f40c8dacd6b633ed7a7))\n\n\n\n## [4.4.15](https://github.com/ocsjs/ocsjs/compare/4.4.14...4.4.15) (2023-05-16)\n\n\n### Bug Fixes\n\n* **core:** 规范搜题请求处理，修复在url中存在变量时多加一个问号的BUG ([c0f561d](https://github.com/ocsjs/ocsjs/commit/c0f561d8c2ec6577f6594f0920bde9c686c23a22)), closes [#97](https://github.com/ocsjs/ocsjs/issues/97)\n\n\n\n## [4.4.14](https://github.com/ocsjs/ocsjs/compare/4.4.13...4.4.14) (2023-05-16)\n\n\n### Bug Fixes\n\n* **script:** 修复智慧职教多层级任务检测 ([64aaf13](https://github.com/ocsjs/ocsjs/commit/64aaf138762d21c3916c7dc7003a4ae3040b3655))\n* **script:** 优化题库状态停用显示 ([a012a56](https://github.com/ocsjs/ocsjs/commit/a012a569a5817f2af8ec655755d78b23447b48e8))\n\n\n\n## [4.4.13](https://github.com/ocsjs/ocsjs/compare/4.4.12...4.4.13) (2023-05-12)\n\n\n### Bug Fixes\n\n* **script:** 修复题库缓存不是最新的BUG ([a7a89b9](https://github.com/ocsjs/ocsjs/commit/a7a89b98d7ee672b47dc63fb36ba6eaa9e37c87d))\n\n\n\n## [4.4.12](https://github.com/ocsjs/ocsjs/compare/4.4.11...4.4.12) (2023-05-12)\n\n\n### Bug Fixes\n\n* **script:** 修复题库配置清空后会留下两个小括号的bug ([0993a52](https://github.com/ocsjs/ocsjs/commit/0993a52d52dca43951279076783ca047a10dc234))\n\n\n\n## [4.4.11](https://github.com/ocsjs/ocsjs/compare/4.4.10...4.4.11) (2023-05-12)\n\n\n### Bug Fixes\n\n* **script:** 继续优化智慧树作业考试检测 ([2a337bb](https://github.com/ocsjs/ocsjs/commit/2a337bb56c3a5f5a157ff80c213c14521d8f0c4c))\n\n\n\n## [4.4.10](https://github.com/ocsjs/ocsjs/compare/4.4.9...4.4.10) (2023-05-11)\n\n\n\n## [4.4.9](https://github.com/ocsjs/ocsjs/compare/4.4.8...4.4.9) (2023-05-11)\n\n\n\n## [4.4.8](https://github.com/ocsjs/ocsjs/compare/4.4.7...4.4.8) (2023-05-11)\n\n\n\n## [4.4.7](https://github.com/ocsjs/ocsjs/compare/4.4.5...4.4.7) (2023-05-11)\n\n\n### Bug Fixes\n\n* **script:** 将更新通知的请求方法改成油猴跨域请求，防止有些页面的 safe 策略阻止请求。 ([6613973](https://github.com/ocsjs/ocsjs/commit/6613973f00fbbda9b5d5f3591475fecd7e6a47c4))\n* **script:** 优化智慧树考试作业提示 ([3703e97](https://github.com/ocsjs/ocsjs/commit/3703e976a19ba835aa1d6f7d7c5f860134802950))\n\n\n### Features\n\n* **script:** 新增题库开关功能 ([07f3cfc](https://github.com/ocsjs/ocsjs/commit/07f3cfcba517edd86cd38f4c0fcabd99ef3ed4fa))\n\n\n\n## [4.4.5](https://github.com/ocsjs/ocsjs/compare/4.4.4...4.4.5) (2023-05-10)\n\n\n\n## [4.4.4](https://github.com/ocsjs/ocsjs/compare/4.4.3...4.4.4) (2023-05-10)\n\n\n### Bug Fixes\n\n* **script:** 修复智慧树考试作业进入后不自动开始，而是需要刷新才能开始的BUG ([8f197ec](https://github.com/ocsjs/ocsjs/commit/8f197ec3d3418663698e9495de4909c1b2123ee5))\n\n\n\n## [4.4.3](https://github.com/ocsjs/ocsjs/compare/4.4.2...4.4.3) (2023-05-09)\n\n\n\n## [4.4.2](https://github.com/ocsjs/ocsjs/compare/4.4.1...4.4.2) (2023-05-09)\n\n\n### Bug Fixes\n\n* **script:** 修复智慧树作业考试阅读理解小题读取失败的BUG ([37e42f1](https://github.com/ocsjs/ocsjs/commit/37e42f10adfb3198dbb4738dbfd232d0bd736421))\n\n\n\n## [4.4.1](https://github.com/ocsjs/ocsjs/compare/4.4.0...4.4.1) (2023-05-09)\n\n\n### Bug Fixes\n\n* **script:** 优化没有题库配置的提示 ([dc55244](https://github.com/ocsjs/ocsjs/commit/dc552440b5a15a1ff2639b64af851da6582056a0))\n\n\n\n# [4.4.0](https://github.com/ocsjs/ocsjs/compare/4.3.7...4.4.0) (2023-05-09)\n\n\n### Bug Fixes\n\n* **core:** 修复搜索结果图片显示问题 ([0ebc3f9](https://github.com/ocsjs/ocsjs/commit/0ebc3f9c057438611a4791818fd2f5030c16eb65))\n* **core:** 优化单选题选项ABCD冗余并没有去掉的BUG ([f88fd86](https://github.com/ocsjs/ocsjs/commit/f88fd8698fc9a265df5ab3c9a3205fe9a81b4be4))\n* **script:** 全面优化自动答题逻辑，并将搜索结果直接显示在各自的脚本面板下，无需反复跳转查看。 ([8712b24](https://github.com/ocsjs/ocsjs/commit/8712b24ee47bda38598e057e44188acd6f5a46fa))\n* **script:** 修复智慧树图片题的BUG ([5adda0b](https://github.com/ocsjs/ocsjs/commit/5adda0be86a1530697539ac412a714d112d6962e))\n* **script:** 优化日志显示 ([bc7521a](https://github.com/ocsjs/ocsjs/commit/bc7521a2313fc64d8be56be4b34956e06736dd4c))\n* **script:** 优化智慧树视频加载缓慢时无法自动播放的BUG ([640bc72](https://github.com/ocsjs/ocsjs/commit/640bc72f70d9cebd9d9b2f110f11b99515a4fe33))\n* **script:** 优化智慧树校内课作业 ([c877ed6](https://github.com/ocsjs/ocsjs/commit/c877ed6551569883afff330c37255982c3511424))\n* **script:** 优化智慧职教MOOC的自动学习逻辑 ([9694773](https://github.com/ocsjs/ocsjs/commit/96947732d8da48018fb4ce9c8979941a859cb55b))\n* **script:** 智慧树考试强制添加保存弹窗，并从头开始每题保存，防止用户切换题目导致保存失败 ([f6c0e22](https://github.com/ocsjs/ocsjs/commit/f6c0e22ebb610c6b59079e8cedd07ba8cd3e8438))\n* **script:** update build script ([4aa68f1](https://github.com/ocsjs/ocsjs/commit/4aa68f16c4cf68487db298ca91e66d3b9ba90c51))\n\n\n### Features\n\n* **script:** 增加搜索结果与题目同步显示功能 ([f793d43](https://github.com/ocsjs/ocsjs/commit/f793d431cd51ca54dd984ff36d1ef1884833b8c2))\n* **script:** 增加职教云考试功能 ([c9bc7ad](https://github.com/ocsjs/ocsjs/commit/c9bc7ad5cdf07c292b380cedc273fc407329e02e))\n\n\n\n## [4.3.7](https://github.com/ocsjs/ocsjs/compare/4.3.5...4.3.7) (2023-04-24)\n\n\n### Bug Fixes\n\n* **script:** 修复智慧职教MOOC学院自动答题无法处理判断题的BUG ([b573684](https://github.com/ocsjs/ocsjs/commit/b5736845e38ec1e80281b66fc1f8bb152ec703dd))\n\n\n### Features\n\n* **script:** 添加职教云作业自动答题 ([4db11a4](https://github.com/ocsjs/ocsjs/commit/4db11a40b7cfbac1e7eda26e50e2bde88d4ee6a5))\n\n\n### Performance Improvements\n\n* **core:** add woker.onElementSearched args[1] : root ([c0244e9](https://github.com/ocsjs/ocsjs/commit/c0244e998f7893761557d7354d232de9438e00c2))\n\n\n\n## [4.3.5](https://github.com/ocsjs/ocsjs/compare/4.3.3...4.3.5) (2023-04-24)\n\n\n### Bug Fixes\n\n* **script:** 修复题库缓存搜索时每题只出一个结果的BUG ([222735a](https://github.com/ocsjs/ocsjs/commit/222735a98cf94f09e6b69eb313c98283e42561a9))\n* **script:** 修复智慧职教MOOC自动答题的填空题无法填空的BUG ([565f879](https://github.com/ocsjs/ocsjs/commit/565f8799518912a65c818baf55a8671576af659f))\n\n\n\n## [4.3.3](https://github.com/ocsjs/ocsjs/compare/4.3.2...4.3.3) (2023-04-24)\n\n\n### Bug Fixes\n\n* **script:** 撤回智慧树图片识别，否则导致作业考试无法使用。 ([fe5b197](https://github.com/ocsjs/ocsjs/commit/fe5b197f1ab4a004d69c44c3460fc94ed7a4e1d6))\n\n\n\n## [4.3.2](https://github.com/ocsjs/ocsjs/compare/4.3.1...4.3.2) (2023-04-24)\n\n\n### Bug Fixes\n\n* **script:** 修复一些显示BUG ([eb4cbc1](https://github.com/ocsjs/ocsjs/commit/eb4cbc1067a97afa4fb81424a9332d393e591982))\n\n\n\n## [4.3.1](https://github.com/ocsjs/ocsjs/compare/4.3.0...4.3.1) (2023-04-24)\n\n\n### Bug Fixes\n\n* **script:** 帮助智慧树修复图片题无法显示的BUG ([bb4c5aa](https://github.com/ocsjs/ocsjs/commit/bb4c5aa49d46f5c077a3855b392ba036db6672e6))\n\n\n\n# [4.3.0](https://github.com/ocsjs/ocsjs/compare/4.2.31...4.3.0) (2023-04-24)\n\n\n### Bug Fixes\n\n* **app:** 优化浏览器环境问题 ([090c7af](https://github.com/ocsjs/ocsjs/commit/090c7af154e34fa7aa0833a586cab7e709cc16c8))\n* **core:** 修复题库搜题时出现 Cannot convert object to primitive value 问题 ([9af5f3f](https://github.com/ocsjs/ocsjs/commit/9af5f3ff5ab93fcd87deefbf7af9963636f342ea))\n* **scirpt:** 优化搜索结果的显示，并且添加快捷百度一下按钮 ([e074a95](https://github.com/ocsjs/ocsjs/commit/e074a95c647e58281648a0ba83c17c069e4de015))\n* **script:** 添加超星自动答题后暂停提示 ([0635db9](https://github.com/ocsjs/ocsjs/commit/0635db94916fcace2e44ef5f7d3fa74114021aa0))\n* **script:** 新增超星匹配域名： hnsyu.net ([481445f](https://github.com/ocsjs/ocsjs/commit/481445f46903ef018f125cd17a33e431a979b267))\n* **script:** 新增超星视频加载失败检测功能 ([1b1e9b8](https://github.com/ocsjs/ocsjs/commit/1b1e9b8b7697b89f0f5c2aef28aff064d42db606))\n* **script:** 修复上个版本智慧职教MOOC学院中作业自动答题题目为空的BUG ([e095493](https://github.com/ocsjs/ocsjs/commit/e095493a3fbd767c2d0d0e90131d6ddc1ec0481f))\n* **script:** 修复手贱导致的判断题乱选的BUG ([4cfa3c8](https://github.com/ocsjs/ocsjs/commit/4cfa3c86b948115819ec3e8e9c5a7ebed0b7ebd0))\n* **script:** 优化 $modal API ，修复 onClose 执行逻辑 ([37cd819](https://github.com/ocsjs/ocsjs/commit/37cd819f27d7b750c76d0db652b35ad5d604a7aa))\n* **script:** 优化超星编辑框复制粘贴问题 ([b263146](https://github.com/ocsjs/ocsjs/commit/b26314689183338e3b4ee3db73258e4e1d538df6))\n* **script:** 优化脚本教程，优化搜索结果，删除独立的通知提示和版本日志，转移到脚本首页中。 ([12d9cef](https://github.com/ocsjs/ocsjs/commit/12d9cef9937fd11c73d2b0180f59d4f067777822))\n* **script:** 优化屏蔽复制粘贴限制 ([3c124c3](https://github.com/ocsjs/ocsjs/commit/3c124c300233592224f127df8ddcf6cbe65363ce))\n* **script:** 优化搜索结果空白的BUG ([5de62f5](https://github.com/ocsjs/ocsjs/commit/5de62f5f9f590c4afe4db2a9b91af8f125481847))\n* **script:** 优化智慧树学习逻辑，用户手动切换视频时脚本可以重新生效。 ([ad5757c](https://github.com/ocsjs/ocsjs/commit/ad5757c81fa7e5139e4097c6326996172e0e12a3))\n\n\n### Features\n\n* **core:** 优化 el API 方便自定义样式 ([e3468f7](https://github.com/ocsjs/ocsjs/commit/e3468f7762b71976338b7a7e74eb920e797e069e))\n* **script:** 将题库缓存储域切换成本地存储 ([6969b52](https://github.com/ocsjs/ocsjs/commit/6969b523cf8a6cc8436f101be7b44ae2ce2ca41d))\n* **script:** 添加题库缓存功能 ([38bde15](https://github.com/ocsjs/ocsjs/commit/38bde1561acfc5e1cf2961846d55ae5705b46514))\n* **script:** 添加智慧职教MOOC学院的作业自动答题功能，优化刷课逻辑。 ([e2954f4](https://github.com/ocsjs/ocsjs/commit/e2954f412e726af2b26f153d0cb980c2d8273608))\n\n\n\n## [4.2.31](https://github.com/ocsjs/ocsjs/compare/4.2.29...4.2.31) (2023-04-20)\n\n\n### Bug Fixes\n\n* **script:** 修复超星图片题选择BUG ([93c520c](https://github.com/ocsjs/ocsjs/commit/93c520cfcea739cb12d3d0c11d05065e0f1e947a))\n* **script:** 优化智慧树共享课和校内课的作业和考试 ([c80b08c](https://github.com/ocsjs/ocsjs/commit/c80b08cec8099c335b7a2c0a38cbacf1585f5610))\n\n\n\n## [4.2.29](https://github.com/ocsjs/ocsjs/compare/4.2.26...4.2.29) (2023-04-20)\n\n\n### Bug Fixes\n\n* **script:** 优化超星繁体字识别，优化http网站下复制粘贴问题 ([f98f2c4](https://github.com/ocsjs/ocsjs/commit/f98f2c4f439f02b6a0ef045c8b9a5f61625d8c8e))\n* **script:** 优化智慧树作业考试文字识别BUG ([317f136](https://github.com/ocsjs/ocsjs/commit/317f136611b4175955f0fe8c646100aac0d925cb))\n* **scripts:** 修复超星倍速提示没有显示的BUG ([ec02677](https://github.com/ocsjs/ocsjs/commit/ec02677a56181b760015e79f9446cbdd2a58b485))\n\n\n\n## [4.2.26](https://github.com/ocsjs/ocsjs/compare/4.2.15...4.2.26) (2023-04-19)\n\n\n### Bug Fixes\n\n* **app:** 修复软件批量创建中无法导出模板的BUG ([7ba565e](https://github.com/ocsjs/ocsjs/commit/7ba565e569d003ac91d124bc09c001816df97edc))\n* **app:** 修复OCR初始化软件路径读取问题 ([2ef18e0](https://github.com/ocsjs/ocsjs/commit/2ef18e0e92dd65abda4516df9b9ee7a2f12f4d1c))\n* **scirpt:** 在OCS软件中不显示软件配置同步的面板 ([baf3815](https://github.com/ocsjs/ocsjs/commit/baf3815ec8670fed7d27250c4deb9a4c0094676a))\n* **script and app:** 修复脚本题库检测BUG，优化软件更新提示 ([7e217d1](https://github.com/ocsjs/ocsjs/commit/7e217d1a68ce840275bdf94ba3fa2265a73d3f13))\n* **script:** - 优化超星章节测试题目解析 ([6b39e40](https://github.com/ocsjs/ocsjs/commit/6b39e401cf7c9ad1af58651b1067b722c55236c0))\n* **script:** 对超星繁体字库加载进行异常处理 ([7a0c825](https://github.com/ocsjs/ocsjs/commit/7a0c825aec72d1af1cf7e9b33eb78660aea17713))\n* **script:** 删除清空答案选项 ([06936a5](https://github.com/ocsjs/ocsjs/commit/06936a558f67f1e9e80d9369e52697ed0f557e2f))\n* **script:** 修复超星输入框无法复制粘贴的BUG ([d8a3c30](https://github.com/ocsjs/ocsjs/commit/d8a3c300bcb7f25a186bb3e857f9d0e3c8aa5b9f))\n* **script:** 修复超星章节测试出现年份丢失的BUG ([941c0e9](https://github.com/ocsjs/ocsjs/commit/941c0e9ceeefc7270b1f6740f1061e7aed952e39))\n* **script:** 修复超星章节测试出现年份丢失的BUG ([2bc1ce1](https://github.com/ocsjs/ocsjs/commit/2bc1ce11bc6ecb0e34300611c3e0c2360624a2fc))\n* **script:** 修复搜索结果答案是图片但没有显示的BUG ([860d1fe](https://github.com/ocsjs/ocsjs/commit/860d1feb2fd480a8ba8cc6fcdfe9eda9ca06004a))\n* **script:** 修复智慧树考试无法识别题目的BUG ([5140d95](https://github.com/ocsjs/ocsjs/commit/5140d95aeb46ba13244eae73e3d3eb595c3cee1d))\n* **script:** 优化软件同步设置，优化题库状态显示 ([4e3cc96](https://github.com/ocsjs/ocsjs/commit/4e3cc967fc653f333cbb897404ac1f8f8ae65ab8))\n* **scripts:** 每次渲染强制更新通知和版本日志 ([7cc5347](https://github.com/ocsjs/ocsjs/commit/7cc53479e00205c1fae88c6ae3e353874bdff413))\n\n\n### Features\n\n* **script:** 添加题目冗余字段自定义删除功能 ([198e240](https://github.com/ocsjs/ocsjs/commit/198e2408fa90325731b8eae8e5b8e6e42609d102))\n\n\n\n## [4.2.15](https://github.com/ocsjs/ocsjs/compare/4.2.11...4.2.15) (2023-04-04)\n\n\n### Bug Fixes\n\n* **all:** 修复软件配置同步导致的各种问题 ([6040d70](https://github.com/ocsjs/ocsjs/commit/6040d7098e5c0ca38b27064bd2fdbaf71ac83fbe))\n* **script:** 修复搜索结果超时后全部题库都显示超时的BUG ([a11b2a5](https://github.com/ocsjs/ocsjs/commit/a11b2a5fa65bc52aca28dd731c954ee36ded2f6d))\n* **script:** 修复通知和版本日志在有些页面访问不了的BUG ([7fa694a](https://github.com/ocsjs/ocsjs/commit/7fa694ad6cce5b3edc5cbf4c50bfada3b95aa7a7))\n* **script:** 优化全局设置题库检测，增加防抖 ([a1de6ab](https://github.com/ocsjs/ocsjs/commit/a1de6ab38ab8e08313eba3f50572b7d9659e9e59))\n\n\n\n## [4.2.11](https://github.com/ocsjs/ocsjs/compare/3.13.0...4.2.11) (2023-04-03)\n\n\n### Bug Fixes\n\n* **all:** 修改项目链接 enncy/online-course-script => ocsjs/ocsjs ([e7f0229](https://github.com/ocsjs/ocsjs/commit/e7f02292548e89517585e7a3a96e6063078eecd6))\n* **app:** 使用JSON消除自定义脚本的响应式特性，修复每个浏览器自定义脚数据相同的问题。 ([af7b97e](https://github.com/ocsjs/ocsjs/commit/af7b97e2c5e0a426813a231e02b66072c57db8ee))\n* **app:** 新增软件 zip 打包方式，修复工作区丢失的BUG ([8fe1357](https://github.com/ocsjs/ocsjs/commit/8fe13574285871f56234223264b1ae64a0c27a77))\n* **app:** 修复部分用户加载不出导航页的BUG ([19f4d10](https://github.com/ocsjs/ocsjs/commit/19f4d102edbacaff21cd324189599941e7e78739))\n* **app:** 修复软件标题栏重载时不自动切换的BUG ([25f45ce](https://github.com/ocsjs/ocsjs/commit/25f45ce643c3e30a32d2f9e89a425e7ec6e57d8b))\n* **app:** 修复软件浏览器标签输入时不自动提示的BUG ([6aececb](https://github.com/ocsjs/ocsjs/commit/6aececb4511929732a5e03a4c41c2d8172bc633c))\n* **app:** 修复软件运行途中删除文件会导致所有浏览器关闭的BUG ([45a2c22](https://github.com/ocsjs/ocsjs/commit/45a2c226ca1033f2ac9c95f3498772eb06856529))\n* **app:** 修改app加载文件以及油猴插件路径问题 ([cbf6930](https://github.com/ocsjs/ocsjs/commit/cbf69306236d0b91d822ea27345cae3e4579a1ab))\n* **app:** 优化 app remote 模块，修改成异步通信，修复同步通信导致的页面卡死问题。 ([9a81721](https://github.com/ocsjs/ocsjs/commit/9a817212ae1a57e88a8738425de8c51218bfeefa))\n* **app:** 优化软件导航页，将ocs-app接口代理删除，全局使用15319端口进行访问，修改浏览器启动选项，使浏览器环境更接近真实浏览器 ([9c8d3fd](https://github.com/ocsjs/ocsjs/commit/9c8d3fd6f1986b5a1834696a9b8362109125132b))\n* **app:** 优化软件更新程序，并修复更新通知抽搐问题 ([460e79a](https://github.com/ocsjs/ocsjs/commit/460e79a07f45e26f8d64136bf40f42ab2f3a83e7))\n* **app:** 优化软件监控问题，以及启动脚本，和浏览器状态处理优化。 ([7b7475d](https://github.com/ocsjs/ocsjs/commit/7b7475df86b3c3e509dfb8e1e44d4770fff00dfd))\n* **app:** 优化软件自动化脚本，添加类型声明 ([b3f1565](https://github.com/ocsjs/ocsjs/commit/b3f1565505a5331f2761e333c06dcd971c41f800))\n* **app:** app version update ([5ddce81](https://github.com/ocsjs/ocsjs/commit/5ddce8114f8121d42a76917d0b34013d86eb68bc))\n* **app:** remove public build ([d3e4340](https://github.com/ocsjs/ocsjs/commit/d3e4340b82381e78daf56d02f5ccb2ae8e8da7ee))\n* **core:** 持续优化超星直播回放脚本 ([1d3b399](https://github.com/ocsjs/ocsjs/commit/1d3b399e790616cfade8aed6e3d81dcc2402da5d))\n* **core:** 调整职教云任务读取速度 ([b2adafb](https://github.com/ocsjs/ocsjs/commit/b2adafbade5b894f7c723b3e197ea44a3919cdcd))\n* **core:** 更改  $gm 和 $ 修改后的代码 ([941ea30](https://github.com/ocsjs/ocsjs/commit/941ea307206c2e197134b8499977e3568332b6bf))\n* **core:** 更新超星未完成章节会出现提示框的问题 ([ebd75d1](https://github.com/ocsjs/ocsjs/commit/ebd75d17500effb477363a48db00e60b36825ed8))\n* **core:** 更新教程链接 ([f07fe39](https://github.com/ocsjs/ocsjs/commit/f07fe3906a1bbfe360d5174cf2bfacbfe319bb88))\n* **core:** 更新智慧树脚本 ([dc04328](https://github.com/ocsjs/ocsjs/commit/dc04328309092e4630d16d1b7111936354d85d6c))\n* **core:** 继续修复上个版本问题 ([41ca48c](https://github.com/ocsjs/ocsjs/commit/41ca48c83966035f536de822f8d25a58a0f7ac68))\n* **core:** 将 dropdown 的 opiton 元素改成 div 元素 ([5a8877e](https://github.com/ocsjs/ocsjs/commit/5a8877e531b19a344d58e11b1956c787186dc005))\n* **core:** 将 unsafeWindow 全局变量封装成 useUnsafeWindow ([1c0f0bb](https://github.com/ocsjs/ocsjs/commit/1c0f0bbf71661249b81bfed12472fd7bc389ebf2))\n* **core:** 将超星视频进度步数调整为0.25，倍速颗粒度控制更高。 ([1702aa9](https://github.com/ocsjs/ocsjs/commit/1702aa9484bc5007a8f04cb0f1600af72ab28477))\n* **core:** 将整个项目修改成跨域响应式模式 ([10aaf0a](https://github.com/ocsjs/ocsjs/commit/10aaf0ae4d415d68e3a22bba303962c17f43355f))\n* **core:** 脚本学习核心修改，适配 useSettings 和 useContext 两个 API ([b4e38b1](https://github.com/ocsjs/ocsjs/commit/b4e38b144cdd23504432a17950aafcbbcd6723c2))\n* **core:** 解决cx任务点未完成出现弹窗的问题 ([d9a6738](https://github.com/ocsjs/ocsjs/commit/d9a67389dabee1dde144f96df4cf45909b290072))\n* **core:** 删除超星在作业考试中题目多余内容导致的正确率下降 ([5ff27e5](https://github.com/ocsjs/ocsjs/commit/5ff27e5558500671d9cb30d96d1175be86e48dd1))\n* **core:** 删除全局变量 OCS ([c74195d](https://github.com/ocsjs/ocsjs/commit/c74195d0d3192e6854c8da0d32262634f4470de2))\n* **core:** 添加本地存储初始化时删除无用字段 ([dc57a4a](https://github.com/ocsjs/ocsjs/commit/dc57a4a33f20ccd11922d806425acdeb30a8a45a))\n* **core:** 为所有参数提供默认值，防止页面空白或者头部空白。 ([baf6086](https://github.com/ocsjs/ocsjs/commit/baf6086567d1cc4b1681fcdf5d818260ccbe8dd6))\n* **core:** 限制请求超时时间最小为10 ([f3fc59e](https://github.com/ocsjs/ocsjs/commit/f3fc59e211b6f86c70c4a9a80a6c0bb3b4b82a0d))\n* **core:** 新增ICVE字段 ([abbcfec](https://github.com/ocsjs/ocsjs/commit/abbcfec64d25622415c863233029c68878d1d4a5))\n* **core:** 修复 store 环境检测问题 ([cd14eb0](https://github.com/ocsjs/ocsjs/commit/cd14eb07e8b33746fc72b1d35e2aaba85a85b07f))\n* **core:** 修复 StringUtils 导入问题 ([50b83dd](https://github.com/ocsjs/ocsjs/commit/50b83dd3942cf6801da203b168c55ae70d63c3be))\n* **core:** 修复$message不能永久显示bug ([3eec4b8](https://github.com/ocsjs/ocsjs/commit/3eec4b8b386c241c40a4df2813e45f10c62f45d2))\n* **core:** 修复不能关闭路线切换的BUG ([a5694f1](https://github.com/ocsjs/ocsjs/commit/a5694f1acecb7962c0e39d5b6890b5b86b365d03))\n* **core:** 修复部分页面存在不执行 interactive 生命周期的问题 ([1f9a8cc](https://github.com/ocsjs/ocsjs/commit/1f9a8cc32b47c9a46a6c77089f5d1b1dd192f625))\n* **core:** 修复超星复习模式自动切换的BUG ([64c63cc](https://github.com/ocsjs/ocsjs/commit/64c63cc19bf0f97de7409a1f3c87ab4321726365))\n* **core:** 修复超星视频答题永远只选2个选项的BUG ([1209ee7](https://github.com/ocsjs/ocsjs/commit/1209ee7362a3bd10e7745393a944f686b0fca431))\n* **core:** 修复超星音频任务不能播放的BUG ([2777c69](https://github.com/ocsjs/ocsjs/commit/2777c691322b8d511b56e44e08384ed15f29827e))\n* **core:** 修复答题结果中存在答案但是不选的BUG ([8129842](https://github.com/ocsjs/ocsjs/commit/8129842fbdc6187ab93a07ba4e34a38f66fed998))\n* **core:** 修复打包后文件中文变成Unicode编码的问题 ([ea6da2c](https://github.com/ocsjs/ocsjs/commit/ea6da2cea453efbf6cd7da3d15f30ff7f1aa7961))\n* **core:** 修复代码中带有特殊字符时，unicode 转换失败 ([fb0cb21](https://github.com/ocsjs/ocsjs/commit/fb0cb211a6f87de37fe4f0ea9d2cb5956e9857b2))\n* **core:** 修复跨域问题 ([8ad21bf](https://github.com/ocsjs/ocsjs/commit/8ad21bfcbd4130ab71a15813beabeec045223ef2))\n* **core:** 修复每次页面加载都要删除core监听队列的bug ([230d43a](https://github.com/ocsjs/ocsjs/commit/230d43a0e9a3fe047c787c588a3e1285f46638e8))\n* **core:** 修复日志面板不会实时滚动的BUG ([a0b9fff](https://github.com/ocsjs/ocsjs/commit/a0b9fffb6967a34397eef6597483ccdff8772319))\n* **core:** 修复视频频繁停止导致的频繁验证码 ([62f708e](https://github.com/ocsjs/ocsjs/commit/62f708e9e1a3e15582efd0618ca085b9aa578535))\n* **core:** 修复数字输入可以超过范围的BUG ([edbd3bc](https://github.com/ocsjs/ocsjs/commit/edbd3bce0385f404d9dcad58d5b831c61c7f9545))\n* **core:** 修复题库配置报错异常未捕获的BUG ([4e04da0](https://github.com/ocsjs/ocsjs/commit/4e04da0ccfd938e58d4f0239be861a882fb01d56))\n* **core:** 修复职教云进度不显示的BUG ([480cfa3](https://github.com/ocsjs/ocsjs/commit/480cfa3a71d91a2252e73f9ae09db6221a97c8a0))\n* **core:** 修复职教云任务获取出现子节点BUG ([ba63921](https://github.com/ocsjs/ocsjs/commit/ba639211dedd4d50b56e7a8ea1e02d1c25c73346))\n* **core:** 修复职教云子节点读取的问题，优化任务列表，优化学习脚本 ([5a11f13](https://github.com/ocsjs/ocsjs/commit/5a11f138151bc0a8b0297b1f09ed17e8c2589488))\n* **core:** 修复智慧树倍速失效的BUG ([7943442](https://github.com/ocsjs/ocsjs/commit/79434421cc1b9c28c547b7c45b08ea762459dd26))\n* **core:** 修复智慧树视脚本的频路径匹配 ([b03dd08](https://github.com/ocsjs/ocsjs/commit/b03dd08d178ac9e89fb042a5bf97520eda1de10e))\n* **core:** 修复智慧树视频测验弹窗无法关闭的BUG ([fc0741f](https://github.com/ocsjs/ocsjs/commit/fc0741f7903992d877ee4b8b1b04e272f81b7a4d))\n* **core:** 修复select,range等对value不显示的bug ([201b3ad](https://github.com/ocsjs/ocsjs/commit/201b3add0ffcbfbdb41157071e1b8d660cb38ac4))\n* **core:** 修复zhs弹窗关闭的问题 ([a688768](https://github.com/ocsjs/ocsjs/commit/a688768c39984561cee8d2ecfee7f6ad07d2f78b))\n* **core:** 修改 $creator.tooltip 的卡死BUG，很多元素时会导致卡顿，这里将 tooltip 变成单个全局元素。 ([da59c28](https://github.com/ocsjs/ocsjs/commit/da59c2899bb9ef37aca5aee2000e00311cad1d73))\n* **core:** 修改 userjs.templete 模板文件，兼容跨域响应式，并修改 homepage 等字段 ([f945a80](https://github.com/ocsjs/ocsjs/commit/f945a8043e5e62f4b92f8650fc332278cdfb974b))\n* **core:** 修改生命周期执行规则 ([99b66fb](https://github.com/ocsjs/ocsjs/commit/99b66fb884f2b88eef72fa402e932bfa40e71148))\n* **core:** 修改搜索结果文案 ([00f185e](https://github.com/ocsjs/ocsjs/commit/00f185e2cba817cbe4d58e28c1d8248bdd656b9d))\n* **core:** 修改元素是否存在的判断 ([345fbcc](https://github.com/ocsjs/ocsjs/commit/345fbccd675dee604e3b4fd21d6c787a38c6575d))\n* **core:** 修改cdn地址 ([013e3ba](https://github.com/ocsjs/ocsjs/commit/013e3ba037b38b88a4728c4cbd478cd4e518953e))\n* **core:** 移除页面反调试脚本至超星 ([77cb19c](https://github.com/ocsjs/ocsjs/commit/77cb19c73df4fdacaac7120bdd748a49a06c8091))\n* **core:** 因全局变量删除，且需要封装 store 中多个变量的处理，以及防止变量污染，新增了 useContext 和 useSettings 的API，相应涉及代码同步更新。 ([6d9fa51](https://github.com/ocsjs/ocsjs/commit/6d9fa5112e81ce278f16a8248c3f9dfe3fed8e13))\n* **core:** 优化 start 主函数，防止脚本重新执行 ([bef652d](https://github.com/ocsjs/ocsjs/commit/bef652d3407ebd07033c2eeb88b1cef9d3d14190))\n* **core:** 优化超星直播回放 ([0727bea](https://github.com/ocsjs/ocsjs/commit/0727bea2c3d9c549770cfa9f5f305afdea91825f))\n* **core:** 优化登录脚本，非空判断 ([684c441](https://github.com/ocsjs/ocsjs/commit/684c441401969c10172bf3c613058c750b5248d2))\n* **core:** 优化获取远程本地软件题库配置功能 ([09986b5](https://github.com/ocsjs/ocsjs/commit/09986b5acaa066c2b259a0e1b69f38ece2898b0c))\n* **core:** 优化题库解析器 ([67b50a4](https://github.com/ocsjs/ocsjs/commit/67b50a47fdc9fb5730e83d295249e93e046c12f7))\n* **core:** 优化题库配置字段 ([d674f00](https://github.com/ocsjs/ocsjs/commit/d674f007c62c3882ad9426d8ee93c8360eef9347))\n* **core:** 优化图片识别时图片链接显示的问题 ([d9778f6](https://github.com/ocsjs/ocsjs/commit/d9778f6268a4986a4ff3eed7542ee54f9e97242b))\n* **core:** 优化响应式存储 ([b307f74](https://github.com/ocsjs/ocsjs/commit/b307f74140d382243c46dc80dafa372cf1b3ca47))\n* **core:** 优化页面通讯以及构建 ([7a20041](https://github.com/ocsjs/ocsjs/commit/7a20041ce081f9675b447847ad8cf9324bf7305d))\n* **core:** 优化智慧树脚本，适配 useContext 和 useSettings 两个 API ([a24d991](https://github.com/ocsjs/ocsjs/commit/a24d991e8a289dd524ae577036a4e165b92afbd1))\n* **core:** 优化自动答题逻辑，修复有答案不选的BUG ([23df64d](https://github.com/ocsjs/ocsjs/commit/23df64d1d77a9556a1d105d72bc9810fb4c06c18))\n* **core:** 优化OCS环境加载问题 ([2742ed4](https://github.com/ocsjs/ocsjs/commit/2742ed47892308879560742b1aa2bf2a08dbe6cf))\n* **core:** 暂时删除视频答题功能 ([b8eeba5](https://github.com/ocsjs/ocsjs/commit/b8eeba52b2d8a8fdd16a90a9f7a3f73d9d8cc722))\n* **core:** 增加答题器的 await 等待机制，多选题选择时需要 await 等待 ([853a30d](https://github.com/ocsjs/ocsjs/commit/853a30d0b9ebac9dca8721e0ce8ce838056e2e11))\n* **root:** 修复 release.sh 打包文件错误时仍然执行的BUG ([6f14bd8](https://github.com/ocsjs/ocsjs/commit/6f14bd854954f1415705d4086d41eb852d1854d0))\n* **root:** 优化构建发布release.sh文件 ([a855740](https://github.com/ocsjs/ocsjs/commit/a855740f0b8ffa107f5f580f2123c70baedb16f7))\n* **scirpt:** 修复搜索结果显示与搜题不一致的BUG，优化各脚本，持续优化学习通任务检测算法。 ([f49737a](https://github.com/ocsjs/ocsjs/commit/f49737aa6227be382d608b08f079cf46ef3f8649))\n* **script:** 持续修复超星问题 ([f2f08e2](https://github.com/ocsjs/ocsjs/commit/f2f08e20eb94466f5dd81dbb5a63ae7f33026911))\n* **script:** 登录时将脚本至于左上方防止挡住软件操作 ([dc2bf1d](https://github.com/ocsjs/ocsjs/commit/dc2bf1d4added39f3967276dc6a05490115251e1))\n* **script:** 删除超星的强制答题选项 ([60bf03c](https://github.com/ocsjs/ocsjs/commit/60bf03c6ecddb7a2faaee6667ac3033f4a6f4403))\n* **script:** 删除脚本展开功能 ([ec1f527](https://github.com/ocsjs/ocsjs/commit/ec1f52798f4bf3b7fb1c4800445e3939a64b660b))\n* **script:** 删除浏览器窗口检测，经过测试全屏游戏中也会进行误报。 ([bf30309](https://github.com/ocsjs/ocsjs/commit/bf30309f7cb331b239dc74d3733fa433c8dbcf2c))\n* **script:** 使用固定端口与桌面软件通讯 ([7bc6de4](https://github.com/ocsjs/ocsjs/commit/7bc6de47bb0686f2353fbcbe56ed75bc93a5b95e))\n* **script:** 添加环境检测，优化职教云逻辑 ([709e673](https://github.com/ocsjs/ocsjs/commit/709e673a4ed5efe755238b45645f4878d079117c))\n* **script:** 修复超星多个视频同时播放的BUG ([51ea1b0](https://github.com/ocsjs/ocsjs/commit/51ea1b0a43c32d596450c39d3940e9f767287583))\n* **script:** 修复超星频繁验证码的BUG ([8544e87](https://github.com/ocsjs/ocsjs/commit/8544e876d4ef63a7f6c3a918a9294f5feb8c5109))\n* **script:** 修复超星章节测试填空题不填的问题 ([05a3cd1](https://github.com/ocsjs/ocsjs/commit/05a3cd11dadaa1700032b357727a4cf3ea807262))\n* **script:** 修复超星BUG ([8fe0a40](https://github.com/ocsjs/ocsjs/commit/8fe0a4049d504d91e004c9a42ad3ac9ddea560b7))\n* **script:** 修复窗口移除页面问题 ([bf2017a](https://github.com/ocsjs/ocsjs/commit/bf2017ae7d54c44cc8843ad5e361cd24751017b8))\n* **script:** 修复答题器出现错误时会一直卡死 ([0f02745](https://github.com/ocsjs/ocsjs/commit/0f02745f020920b2f2fe7d8ab15f0e53509ae38c))\n* **script:** 修复复制粘贴解除限制的BUG ([ebded93](https://github.com/ocsjs/ocsjs/commit/ebded937fa9a47926d7a977bcaae6f55597868b3))\n* **script:** 修复通用-全局设置中题库状态检测BUG ([ebd6536](https://github.com/ocsjs/ocsjs/commit/ebd6536e26369eefc5d3ad8aa471e087f4f95b06))\n* **script:** 修复学习通刷课逻辑 ([831f1d3](https://github.com/ocsjs/ocsjs/commit/831f1d33c7a9b350fe44c3121c6120f27720a2e4))\n* **script:** 修复智慧树答题后不保存的BUG ([c56b54f](https://github.com/ocsjs/ocsjs/commit/c56b54f701d63feb3a47a2d53890348264e68be8))\n* **script:** 修复智慧树作业答题完成后选择BUG ([8abf168](https://github.com/ocsjs/ocsjs/commit/8abf168d9efd5e8e170e8faa85b3285d7c847305))\n* **script:** 优化播放函数 ([4204594](https://github.com/ocsjs/ocsjs/commit/4204594bc70fdd91d93f41bfd3975fba2a99e47a))\n* **script:** 优化超星和智慧树脚本 ([cb5fe71](https://github.com/ocsjs/ocsjs/commit/cb5fe710be11d5652414b5470912a70a8bd2cc15))\n* **script:** 优化窗口加载问题 ([e5cba2c](https://github.com/ocsjs/ocsjs/commit/e5cba2c809e6c385345e46121dc34afbacecc243))\n\n\n### Features\n\n* **all:** 脚本2.0与软件4.0更新完毕 ([6e04369](https://github.com/ocsjs/ocsjs/commit/6e043692f0fcfd4b5d56d785c2787c1bbdd22c9d))\n* **all:** 脚本更新至 4.0 ， 软件更新至 2.0 ([7cb995f](https://github.com/ocsjs/ocsjs/commit/7cb995f6d062ba79e09852f42be3565b8f107cd0))\n* **app and web:** 将 electron 升级到 23.0.0 ， 删除 electron 原生窗口样式 frame: false，自定义 titlebar ，并优化主题切换。 ([961235f](https://github.com/ocsjs/ocsjs/commit/961235fab116575b88aa5f71f0ca6aac87f71c0c))\n* **app:** 将OCR识别模块整合打包，修复OCR路径存在空格执行失败的BUG ([9609af6](https://github.com/ocsjs/ocsjs/commit/9609af6d6f53b72b010de69e9df89294d0b27faa))\n* **app:** 删除超星智慧树的其他登录，新增自定义链接进入。 ([5851511](https://github.com/ocsjs/ocsjs/commit/58515115728dfacfee83e1c67c3bb1efcb4ddb59))\n* **app:** 文件中新增图片预览一栏，定时更新页面图像，无需在多个浏览器中切换查看。 ([526dbd6](https://github.com/ocsjs/ocsjs/commit/526dbd63e7bfa57828d1950517644801021db91b))\n* **app:** 新增网页打开脚本，将ocr模块一同打包 ([7686cf1](https://github.com/ocsjs/ocsjs/commit/7686cf1a5a9d462e0ce0b8a3ff7c0e6235da562d))\n* **app:** 新增仪表盘功能，新增浏览器拓展管理功能，新增新手教程功能，新增批量运行文件功能，并重写文件管理系统。 ([8adfede](https://github.com/ocsjs/ocsjs/commit/8adfede9f790cdeae588c95a3e5d7134802fffb1))\n* **app:** 新增用户脚本，可自定义网络脚本添加到本地脚本，并自动载入本地脚本，极大拓展软件功能。 ([7926b35](https://github.com/ocsjs/ocsjs/commit/7926b354ffc9f14dc2404f45dd869da96495a7f2))\n* **app:** 新增资源加载器，优化各种资源加载问题，移除原有的OCR模块打包方式。 ([1b83d8a](https://github.com/ocsjs/ocsjs/commit/1b83d8af6388fa568d83521c06e9322736c31b48))\n* **app:** 修改app主进程从 commonjs 改成 ts ([3e29f82](https://github.com/ocsjs/ocsjs/commit/3e29f821cd1f5414c68ae0f944c512f8e94bdfb3))\n* **app:** 优化 script 工作线程，规范代码，修复人工错误导致的无限递归。 ([156b2f7](https://github.com/ocsjs/ocsjs/commit/156b2f71ed4d42fae620b89c179f3b86ebaf2064))\n* **core and app:** 新增软件服务端，使脚本可读取软件信息。 ([6f4ff54](https://github.com/ocsjs/ocsjs/commit/6f4ff54bb3a83fcb273a48ef41b5ba973d6d2f01))\n* **core:** - 拆分 core ，- 添加多线程答题 - 添加超星作业考试功能 - 将 core/utils 全部进行 $ 前缀声明以便区分 - 优化搜题结果元素。 ([a252e85](https://github.com/ocsjs/ocsjs/commit/a252e855fb03d8335c28cb563c60b96a9317a0c1))\n* **core:** 4.0 init ([4a43ee9](https://github.com/ocsjs/ocsjs/commit/4a43ee9bbb4af37ebf4317f87f5508067acd5139))\n* **core:** 4.0 init ([7151eb3](https://github.com/ocsjs/ocsjs/commit/7151eb3643054021feadcdfb512871ba3916e4e1))\n* **core:** 持续优化zhs答题功能 ([b120ce7](https://github.com/ocsjs/ocsjs/commit/b120ce718ea84db17a6d5fc3f43ad3214139b7de))\n* **core:** 处理 onbeforeunload 执行结果 ([9e0fc87](https://github.com/ocsjs/ocsjs/commit/9e0fc87fad7652ab57bc3ce1080c3cf1ecb294e7))\n* **core:** 给 script 的全部声明周期同样添加相同的事件触发 ([289c0d8](https://github.com/ocsjs/ocsjs/commit/289c0d8c746f60b9e69bc0dc22a1516505592db9))\n* **core:** 将 OCSWoker 和 Script 都变成 EventEmitter 对象，实现内部的事件分发 ([2e0872c](https://github.com/ocsjs/ocsjs/commit/2e0872cf6cf2faa6088dbd1235aa32933b2b466f))\n* **core:** 删除窗口关闭按钮，用户可以通过“窗口设置”进行隐藏窗口。 ([4a447e4](https://github.com/ocsjs/ocsjs/commit/4a447e4d5deb60dc2078862f4fb3dc36d92e9a67))\n* **core:** 搜索结果页面新增复制题目按钮 ([4cbd032](https://github.com/ocsjs/ocsjs/commit/4cbd03217678a9426806f98828d76f2da664ba20))\n* **core:** 添加 $creator 元素创建工具类变量，并且重写登录脚本，使用动态添加元素的方式重写。 ([ba94b16](https://github.com/ocsjs/ocsjs/commit/ba94b165eaefe2b95d5b349ce60fa88aec0d7f19))\n* **core:** 添加 $modal.onClose 不管关闭还是确认和取消都会触发此函数 ([5cec198](https://github.com/ocsjs/ocsjs/commit/5cec1984bbf4da4e2bf8f518f9ba858a0c1bc340))\n* **core:** 添加 onhistorychange 钩子 ([e8c1ecb](https://github.com/ocsjs/ocsjs/commit/e8c1ecb9ecbe7ec1a842952a0f7be58ac1d7964f))\n* **core:** 添加 Script.configs 参数生产后 this.cfg 的代码提示 ([d31704e](https://github.com/ocsjs/ocsjs/commit/d31704e2716ca298e134a87cdc58189372e7b834))\n* **core:** 添加 script.onrender 钩子 ([a81404b](https://github.com/ocsjs/ocsjs/commit/a81404b1071be98d486c8331dc066b99f17fa004))\n* **core:** 添加 SearchResultsElement 元素 ([d66dffb](https://github.com/ocsjs/ocsjs/commit/d66dffb79ccab8726a4b6a8d90dc140f7637017e))\n* **core:** 添加 StringUtils 工具类 ([4f1341a](https://github.com/ocsjs/ocsjs/commit/4f1341a7d57d4b667b0c07a71f7cd1a3a31b0fa8))\n* **core:** 添加超星登录脚本 ([9b2e086](https://github.com/ocsjs/ocsjs/commit/9b2e086698cd2765d568538debcac75c6d701514))\n* **core:** 添加面板选择器分组功能 ([226d1eb](https://github.com/ocsjs/ocsjs/commit/226d1eb8378d62bcb799e0c98b2a34e96fc9c446))\n* **core:** 添加全局跨域通讯对象,并使用此技术重写模态框调出方法 ([81b1daf](https://github.com/ocsjs/ocsjs/commit/81b1daff19850cbb084a5eb6564e88ef2be4db7e))\n* **core:** 添加使用教程, 重载 el 函数, 修改cors跨域模块使用 setTab, getTab 进行cors跨域标签分区. 优化页面选择逻辑 ([eaa6ae5](https://github.com/ocsjs/ocsjs/commit/eaa6ae5030fcfc31db905ed24969995d06014af3))\n* **core:** 添加页面复制粘贴限制解除脚本 ([4acdfd0](https://github.com/ocsjs/ocsjs/commit/4acdfd018e8dd55360e78f66547d6a56541c4ffe))\n* **core:** 添加在线搜题功能 ([dc26b2e](https://github.com/ocsjs/ocsjs/commit/dc26b2e0e52d59d015546b960e533c09f2d39e27))\n* **core:** 添加智慧树自动答题功能 ([642901d](https://github.com/ocsjs/ocsjs/commit/642901d470a3b69f826dc8c9f331cbd306ebd713))\n* **core:** 完成4.0基本架构 ([23129f4](https://github.com/ocsjs/ocsjs/commit/23129f43581acab6e367b6ff38f0fe4b66f04708))\n* **core:** 完成脚本内容的元素和数值同步 ([4d1dc60](https://github.com/ocsjs/ocsjs/commit/4d1dc60985b5b9623dd61a351babd8640f5a4259))\n* **core:** 完成智慧树登录脚本,学习脚本,修复BUG ([f07dd0f](https://github.com/ocsjs/ocsjs/commit/f07dd0fdaa468df626ba5fa03a503bfc8cb72bd5))\n* **core:** 新增 $creator 多个API ([d245726](https://github.com/ocsjs/ocsjs/commit/d245726df9f4847b8a8df47375a13e03b35d7fb4))\n* **core:** 新增 $message 和 $model 方法进行用户交互 ([c6d365c](https://github.com/ocsjs/ocsjs/commit/c6d365ce13f046ed93971d9991dfcfad3990b419))\n* **core:** 新增 通用-搜索结果 显示 ([9d9ead8](https://github.com/ocsjs/ocsjs/commit/9d9ead85747ccd94f922d7f50573ff99d9b9761a))\n* **core:** 新增 智慧职教mooc脚本 ， 优化职教云脚本 ([0e44978](https://github.com/ocsjs/ocsjs/commit/0e44978c57e81afcaf28b55c1adfdb4eac297db0))\n* **core:** 新增 Script.methods 方法可自定义对外暴露函数，优化搜索结果的显示。 ([53043d1](https://github.com/ocsjs/ocsjs/commit/53043d1590f09c557ae0059233806dd43c06653e))\n* **core:** 新增 SCript.pin 方法，置顶某个面板 ([766f87e](https://github.com/ocsjs/ocsjs/commit/766f87e3f03c6216706b67a1cd6801c55c08f78b))\n* **core:** 新增 unsafeWindow 全局变量 ([9438a1e](https://github.com/ocsjs/ocsjs/commit/9438a1efc4451adf5160ca9067407deaa9707c5b))\n* **core:** 新增超星直播回放视频脚本 ([3788a6f](https://github.com/ocsjs/ocsjs/commit/3788a6fc5bb852b4847025b4df88a5b9499ce4de))\n* **core:** 新增跨域响应式特性 ([d7f967e](https://github.com/ocsjs/ocsjs/commit/d7f967ec27fd3d67de9d8132dd60100765727384))\n* **core:** 新增智慧树：清晰度选择，定时停止，视频总时长计算，等功能 ([7204f26](https://github.com/ocsjs/ocsjs/commit/7204f26f7b2c3c6700df1f7d9d08a43371cd8c35))\n* **core:** 新增智慧树的视频画质选择功能 ([290ce51](https://github.com/ocsjs/ocsjs/commit/290ce51032e2a8b773b61275ada58002ade0aa22))\n* **core:** 新增智慧树视频反反混淆脚本 ([5de857f](https://github.com/ocsjs/ocsjs/commit/5de857fb12f229fbed1284d507bac87500e5b6ce))\n* **core:** 新增智慧树验证码检测功能 ([dcf73b3](https://github.com/ocsjs/ocsjs/commit/dcf73b317cb7492a971a74c77af03e91a13eb1bb))\n* **core:** 新增智慧职教（职教云）脚本 ([59c0cc2](https://github.com/ocsjs/ocsjs/commit/59c0cc2b66fa1af2418e682bec014342f3ed0e7f))\n* **core:** 新增j脚本热更新功能，大大提升开发效率 ([42ba110](https://github.com/ocsjs/ocsjs/commit/42ba110b2cc8e54e043b73979216a8f22c9017fe))\n* **core:** 修改脚本声明写法，修改 project.scripts 由数组变成对象声明，好处是可以由 project.scripts.[脚本名].cfg.xxx 进行类型推断实现类型提示。 ([b73fdb0](https://github.com/ocsjs/ocsjs/commit/b73fdb021f9b050278f321c7d353f81e8869d964))\n* **core:** 优化 cx 的 Project 新写法 ([61f466a](https://github.com/ocsjs/ocsjs/commit/61f466a0e4e85a59985bb59c84ca7efc04975213))\n* **core:** 优化使用$creator ([23efe42](https://github.com/ocsjs/ocsjs/commit/23efe4274f1b6604e85189a39bc47cdc7e5542b1))\n* **core:** 优化渲染工程 ([31b5728](https://github.com/ocsjs/ocsjs/commit/31b5728843b44f244bb3abbf4f5f72f79259350e))\n* **core:** 优化智慧树文字识别脚本 ([608e760](https://github.com/ocsjs/ocsjs/commit/608e76076d63d3b22c55c0baaf2df008e0b32313))\n* **core:** 增加多线程查题功能 ([be3b85c](https://github.com/ocsjs/ocsjs/commit/be3b85cdf382c9add8e5b155c2b1a97dbbb46bd4))\n* **core:** 增加智慧树习惯分检测，学习记录查询，答题手动控制 ([e753da9](https://github.com/ocsjs/ocsjs/commit/e753da9cee93b0873e5d2be7b09c7770451cef7a))\n* **core:** 支持跨域调出模态框 ([f1f4271](https://github.com/ocsjs/ocsjs/commit/f1f427166d21622fed299399456e235386a69cb6))\n* **core:** 智慧职教（职教云）发布 ([f0eb02f](https://github.com/ocsjs/ocsjs/commit/f0eb02f2ada42fb64a5f3767689ddf7af0c303e9))\n* **script and core:** 添加全局错误捕获功能 ([476483d](https://github.com/ocsjs/ocsjs/commit/476483da346104b63766810953fa4a254ed0a72f))\n* **script:** 超星刷课逻辑重写，兼容旧版浏览器CSS样式 ([184e0e9](https://github.com/ocsjs/ocsjs/commit/184e0e9910aa1eccf55b89d6a360e9a218d648d8))\n* **script:** 添加浏览器版本检测 ([5c81815](https://github.com/ocsjs/ocsjs/commit/5c81815d81ad1dad5ed998a5dfe14a4db0935021))\n* **script:** 添加页面关闭提示 ([477fb01](https://github.com/ocsjs/ocsjs/commit/477fb01e8e92e14c71382daa24ce9852d7bd9c4a))\n* **script:** 添加智慧职教音频支持 ([0e6402c](https://github.com/ocsjs/ocsjs/commit/0e6402c019afbaeca24da2900c3a81771faa5249))\n* **script:** 新增【职教云】和【智慧职教】脚本 ([0cf366a](https://github.com/ocsjs/ocsjs/commit/0cf366a5ef082496a0108902fd58dfa874e9eb98))\n* **script:** 新增浏览器最小化检测脚本 ([4934f65](https://github.com/ocsjs/ocsjs/commit/4934f6535ed2a7c1d0f078ba136b5213f5d8ba12))\n* **script:** 修改浏览器下载链接，新增脚本版本更新检测 ([9265c2b](https://github.com/ocsjs/ocsjs/commit/9265c2ba09639121c417e9042fcd0230ca3da00a))\n* **script:** version release 4.0.5 ([d7c4574](https://github.com/ocsjs/ocsjs/commit/d7c4574faa2da036a5b04f4a48b6840dd74461b9))\n* **script:** version update to 4.1.0 ([216596f](https://github.com/ocsjs/ocsjs/commit/216596fde62d1591696dde101a49eff27619085c))\n* **utils:** 新增utils包，其内置各种实用工具。其中新增脚本打包器，可对打包流程进行优化。 ([01879dd](https://github.com/ocsjs/ocsjs/commit/01879dd251cdb299df799631262c268a3de827af))\n\n\n### Performance Improvements\n\n* **core:** 更新 MoelElement 参数以及实现 ([219ca6f](https://github.com/ocsjs/ocsjs/commit/219ca6faab611da9c9aa9c6afd68cdc5bcb3fa71))\n* **core:** 添加 SearchResultsElement 元素映射 ([6f86f6f](https://github.com/ocsjs/ocsjs/commit/6f86f6f4dffc42d28983e902c677372703defa53))\n* **core:** 添加defineScript中的domain和hide字段，实现动态修改脚本头部信息的功能。 ([a13ab50](https://github.com/ocsjs/ocsjs/commit/a13ab5008ff34f589d9ddd0184c5e320afc2180a))\n\n\n\n# [3.13.0](https://github.com/ocsjs/ocsjs/compare/3.12.3...3.13.0) (2022-05-23)\n\n\n### Features\n\n* **core:** 添加页面反调试脚本 ([1174617](https://github.com/ocsjs/ocsjs/commit/1174617cee99dd4d2e546d79279160ba1afea40e))\n* **core:** 新增超星视频中答题功能 ([3f925cb](https://github.com/ocsjs/ocsjs/commit/3f925cba72910fce0353df421dec75a137f24e4e))\n* **core:** 新增网课视频选项：显示视频进度 ([b6086df](https://github.com/ocsjs/ocsjs/commit/b6086df349fa50434e199c88c8fff6414bed4c14))\n\n\n\n## [3.12.3](https://github.com/ocsjs/ocsjs/compare/3.12.2...3.12.3) (2022-05-22)\n\n\n### Bug Fixes\n\n* **core:** 修复误删最大长宽导致的超出页面范围 ([9b0bcd4](https://github.com/ocsjs/ocsjs/commit/9b0bcd4d0f26fb8591950e73563be78a5c3d876d))\n\n\n\n## [3.12.2](https://github.com/ocsjs/ocsjs/compare/3.12.0...3.12.2) (2022-05-21)\n\n\n### Bug Fixes\n\n* **core:** 修复某些填空题识别不出的BUG ([c2c1c3d](https://github.com/ocsjs/ocsjs/commit/c2c1c3d2a7fafbe569b25531070b1803e07ccfe6))\n\n\n### Features\n\n* **core:** 新增自动答题选项：强制提交 ([e0ff3a2](https://github.com/ocsjs/ocsjs/commit/e0ff3a2c64d9e9de061889d2ad145ed29a492cb1))\n\n\n\n# [3.12.0](https://github.com/ocsjs/ocsjs/compare/3.11.0...3.12.0) (2022-05-21)\n\n\n### Bug Fixes\n\n* **core:** 删除多余输出 ([8cee017](https://github.com/ocsjs/ocsjs/commit/8cee0173c670633d665bddd24c4884fa3ad3f621))\n* **core:** 修改userjs打包代码未加分号报错 BUG ([e0ec319](https://github.com/ocsjs/ocsjs/commit/e0ec319150ec2f42a4a1256ae9250a3bfd1ae779))\n* **core:** 优化多选题答案分割判断 ([7da6f92](https://github.com/ocsjs/ocsjs/commit/7da6f92074a4a9ced316be312aa664ea3f75af52))\n\n\n### Features\n\n* **core:** 新增随机作答功能 ([ecc2a87](https://github.com/ocsjs/ocsjs/commit/ecc2a87c24e2e81e82a6f1d4fe737b3ccab69b8c))\n* **core:** 新增图片题识别脚本，新增搜索结果显示题目图片和答案图片 ([fd483c3](https://github.com/ocsjs/ocsjs/commit/fd483c3bbbe9af689e3b006c21ae12cdeccdd73a))\n\n\n\n# [3.11.0](https://github.com/ocsjs/ocsjs/compare/3.10.6...3.11.0) (2022-05-21)\n\n\n### Bug Fixes\n\n* **core:** 新增未经压缩代码的打包 ([4099fc4](https://github.com/ocsjs/ocsjs/commit/4099fc428d6cfa3b835bf7c89f29bb63b11ad090))\n\n\n### Features\n\n* **core:** 新增userjs未经压缩代码打包 ([42badc8](https://github.com/ocsjs/ocsjs/commit/42badc85967abb2486c3a6895b8ffd8f9155f05a))\n\n\n\n## [3.10.6](https://github.com/ocsjs/ocsjs/compare/3.10.4...3.10.6) (2022-05-20)\n\n\n### Bug Fixes\n\n* **core:** 修复题库配置解析器BUG ([3986a49](https://github.com/ocsjs/ocsjs/commit/3986a49a1323bc17f8cca54763586c23f6e03907))\n\n\n\n## [3.10.4](https://github.com/ocsjs/ocsjs/compare/3.10.2...3.10.4) (2022-05-20)\n\n\n### Bug Fixes\n\n* **app:** 修复浏览器报错BUG ([fd3780c](https://github.com/ocsjs/ocsjs/commit/fd3780c2d9fdb58016b06f090696056934888f89))\n* **core:** 修复超星考试页面样式问题 ([03fa9be](https://github.com/ocsjs/ocsjs/commit/03fa9bed5e176377e921deffbacf0212ccd85e34))\n* **core:** 修复题库配置BUG ([2b8c939](https://github.com/ocsjs/ocsjs/commit/2b8c9391cecb7aed7e55b47694772feaed2e45fc))\n\n\n\n## [3.10.2](https://github.com/ocsjs/ocsjs/compare/3.10.1...3.10.2) (2022-05-19)\n\n\n### Bug Fixes\n\n* **core:** 还原文件，修改 release 执行错误但继续打包发布的BUG ([201ae0f](https://github.com/ocsjs/ocsjs/commit/201ae0f6face34db36c9edffe0e323e744ea0106))\n\n\n\n## [3.10.1](https://github.com/ocsjs/ocsjs/compare/3.10.0...3.10.1) (2022-05-19)\n\n\n### Bug Fixes\n\n* **app:** 修复软件浏览器选择BUG，并停止火狐浏览器使用。 ([5e2559b](https://github.com/ocsjs/ocsjs/commit/5e2559bfdea087806246120da566410b54a39c0b))\n* **core:** 修改 typr 库，减少部分打包体积。 ([51e26de](https://github.com/ocsjs/ocsjs/commit/51e26debe37c9ad45e181a9d67528244863e7b33))\n\n\n\n# [3.10.0](https://github.com/ocsjs/ocsjs/compare/3.9.6...3.10.0) (2022-05-17)\n\n\n### Features\n\n* **core:** 新增超星繁体字识别选项 - 字典识别 ([2a241d6](https://github.com/ocsjs/ocsjs/commit/2a241d6fe987316b335e57dd9b8b19be188f1805))\n* **core:** 新增超星强制答题功能 ([e7c4fc0](https://github.com/ocsjs/ocsjs/commit/e7c4fc060c30b341c17fefa0148fde5170069c87))\n\n\n\n## [3.9.6](https://github.com/ocsjs/ocsjs/compare/3.9.5...3.9.6) (2022-05-16)\n\n\n### Bug Fixes\n\n* 修复日志记录问题 ([c9f2147](https://github.com/ocsjs/ocsjs/commit/c9f21473a755a9af6e7bbeed4095f2209d64cb6e))\n* **app:** 修复软件文件重命名时，运行文件名不同步的BUG ([28a1af1](https://github.com/ocsjs/ocsjs/commit/28a1af141217c83cc891952731b585153d6d3d59))\n* **app:** 修复智慧树登录后白屏错误的BUG ([9622f7e](https://github.com/ocsjs/ocsjs/commit/9622f7e4c809e64be76c2cd4309e01f1ba9a4e44))\n\n\n### Features\n\n* **app:** 新增软件自动选择浏览器路径功能 ([eb56f67](https://github.com/ocsjs/ocsjs/commit/eb56f67e6885badcfa252fc716ac6e203560fc2a))\n* **app:** 新增自定义脚本载入路径功能, 路径将托管到官方服务器 ([289cefe](https://github.com/ocsjs/ocsjs/commit/289cefecae8b7c3ed900bba1cb99150438a0a842))\n\n\n\n## [3.9.5](https://github.com/ocsjs/ocsjs/compare/3.8.0...3.9.5) (2022-05-16)\n\n\n### Bug Fixes\n\n* 修复新增 shadowroot 后，复制粘贴脚本失效的BUG ([081a99e](https://github.com/ocsjs/ocsjs/commit/081a99ee62a47f427e02b33e3497e5f95e6dedfa))\n* 优化超星识别时可能遇到选项按钮被删除的BUG ([ef396b4](https://github.com/ocsjs/ocsjs/commit/ef396b4a3bc9f10284529c3cbb857edba9c927ca))\n\n\n### Features\n\n* 新增 AnswererWrapper 参数: headers 和 type ([8c970bb](https://github.com/ocsjs/ocsjs/commit/8c970bb52a9b6ef83618b7f1dc2d55fa26045024))\n* 新增题库配置跨域模块，可对不同域名的服务器进行跨域访问，并且新增 root 环境变量，可访问元素题目的跟节点元素对象。 ([4e8ea1c](https://github.com/ocsjs/ocsjs/commit/4e8ea1c6bdc84a1b0ce84b24af48a91cff0830af))\n\n\n\n# [3.8.0](https://github.com/ocsjs/ocsjs/compare/3.7.4...3.8.0) (2022-05-11)\n\n\n### Bug Fixes\n\n* 避免重复劫持函数导致页面内存移除 ([067ee58](https://github.com/ocsjs/ocsjs/commit/067ee58e6ffa74657a8adf213ad32fcda0799243))\n* 修复智慧树倍速不能立刻改变的BUG ([39d7402](https://github.com/ocsjs/ocsjs/commit/39d7402d804ad5adb8556150d58ae9277f97229f))\n\n\n### Features\n\n* + 新增消息提示 + 修复火狐底部版本不显示的BUG  + 优化eslint代码 + 增加文字识别错误提示 + 将原有弹出框修改为消息提示 + 增加API:message ([534bee3](https://github.com/ocsjs/ocsjs/commit/534bee3b6e449b9c725cbdec9efa27979c6545ed))\n* 使用ShadowRoot对脚本进行加固 ([346d9d1](https://github.com/ocsjs/ocsjs/commit/346d9d109499e303704f7a05414631d9c7e3b11c))\n\n\n\n## [3.7.4](https://github.com/ocsjs/ocsjs/compare/3.7.3...3.7.4) (2022-05-09)\n\n\n\n## [3.7.3](https://github.com/ocsjs/ocsjs/compare/3.7.2...3.7.3) (2022-05-08)\n\n\n\n## [3.7.2](https://github.com/ocsjs/ocsjs/compare/3.7.0...3.7.2) (2022-05-07)\n\n\n### Bug Fixes\n\n* 修改 store 初始化位置 ([808eb08](https://github.com/ocsjs/ocsjs/commit/808eb080a50e75ecef151010b74891ce272938c9))\n\n\n### Features\n\n* 添加智慧树文本识别脚本和屏蔽视频检测脚本 ([85ddac1](https://github.com/ocsjs/ocsjs/commit/85ddac119874d5b6877dfc8fa29ecdff70fec4ed))\n\n\n\n# [3.7.0](https://github.com/ocsjs/ocsjs/compare/3.6.4...3.7.0) (2022-05-04)\n\n\n### Features\n\n* 添加答题等待时间，方便检查或者使用其他答题工具。 ([12f2960](https://github.com/ocsjs/ocsjs/commit/12f2960a47f3eb22cc9bbf91bf49c2ace54ea89e))\n\n\n\n## [3.6.4](https://github.com/ocsjs/ocsjs/compare/3.6.2...3.6.4) (2022-05-04)\n\n\n### Bug Fixes\n\n* 深度优化OCR ([a21a8ad](https://github.com/ocsjs/ocsjs/commit/a21a8adf3b209ae5e65aff26c134da2a7b1f0fd2))\n* 修复填空题多个填空不填的BUG ([4a0f031](https://github.com/ocsjs/ocsjs/commit/4a0f031b244a800b1e6ff39d3b1b99160372e66d))\n\n\n\n## [3.6.2](https://github.com/ocsjs/ocsjs/compare/3.6.1...3.6.2) (2022-04-30)\n\n\n### Bug Fixes\n\n* 修改 OCR 脚本加载路径, 确保能够访问 work 和 core 脚本 ([9b1544a](https://github.com/ocsjs/ocsjs/commit/9b1544a8e2b002d78471d387e507967e20281020))\n\n\n\n## [3.6.1](https://github.com/ocsjs/ocsjs/compare/3.6.0...3.6.1) (2022-04-30)\n\n\n### Bug Fixes\n\n* 修改环境依赖 ([9c69d35](https://github.com/ocsjs/ocsjs/commit/9c69d3581a84a8e50d87dd7a5d7ed3f446e45295))\n\n\n\n# [3.6.0](https://github.com/ocsjs/ocsjs/compare/3.5.5...3.6.0) (2022-04-29)\n\n\n### Features\n\n* 新增智慧树共享课考试脚本 ([5ba2022](https://github.com/ocsjs/ocsjs/commit/5ba2022e084f7af9ef01309e7d88f8d42436732a))\n\n\n\n## [3.5.5](https://github.com/ocsjs/ocsjs/compare/3.5.4...3.5.5) (2022-04-28)\n\n\n### Bug Fixes\n\n* 适当增大了文本便于识别 ([483d68b](https://github.com/ocsjs/ocsjs/commit/483d68b44720babd1a2f8432661712d869433ad4))\n\n\n\n## [3.5.4](https://github.com/ocsjs/ocsjs/compare/3.5.3...3.5.4) (2022-04-28)\n\n\n### Bug Fixes\n\n* 优化OCR空格问题，还有上个版本OCR锁初始化的问题 ([82a84d7](https://github.com/ocsjs/ocsjs/commit/82a84d778aa99849f26fdd87447f41c7bdda72e2))\n\n\n\n## [3.5.3](https://github.com/ocsjs/ocsjs/compare/3.5.2...3.5.3) (2022-04-28)\n\n\n### Bug Fixes\n\n* 优化 OCR ， 解决题目选项没有识别的BUG ([e534658](https://github.com/ocsjs/ocsjs/commit/e534658665be9863d19fc52ee787f7cad696bdd8))\n\n\n\n## [3.5.2](https://github.com/ocsjs/ocsjs/compare/3.5.1...3.5.2) (2022-04-27)\n\n\n### Bug Fixes\n\n* 优化 OCR 加载逻辑 ([9dd69dc](https://github.com/ocsjs/ocsjs/commit/9dd69dce3073b137999c753b50f7fec8ee03a8a2))\n\n\n\n## [3.5.1](https://github.com/ocsjs/ocsjs/compare/3.5.0...3.5.1) (2022-04-27)\n\n\n### Bug Fixes\n\n* 优化 OCR 数据加载问题 ([ed958a9](https://github.com/ocsjs/ocsjs/commit/ed958a9a47de90763daf7b59c7c1fa3abd698b7f))\n\n\n\n# [3.5.0](https://github.com/ocsjs/ocsjs/compare/3.4.5...3.5.0) (2022-04-27)\n\n\n### Bug Fixes\n\n* 修复超星有时不能自动下一章的BUG ([7c5516f](https://github.com/ocsjs/ocsjs/commit/7c5516f731295ccc2fd73129bf659dd8b339d2f1))\n\n\n### Features\n\n* 繁体字乱码识别功能 ([ec01cd1](https://github.com/ocsjs/ocsjs/commit/ec01cd168a4f0f4111e7cb6e3c1597b07d7dfd14))\n* 开发智慧树倍速选项 ([83015b7](https://github.com/ocsjs/ocsjs/commit/83015b73bdb0bda4f23af04b3814b8cd7e21d721))\n\n\n\n## [3.4.5](https://github.com/ocsjs/ocsjs/compare/3.4.4...3.4.5) (2022-04-23)\n\n\n### Bug Fixes\n\n* 修复软件重命名有时会无效的BUG ([48fb520](https://github.com/ocsjs/ocsjs/commit/48fb520263fabb4740138705bf55d5086e425907))\n* 优化软件启动加载 ([be866d1](https://github.com/ocsjs/ocsjs/commit/be866d1eb966a64aefe5fd95ffaf2c1c53ae76d8))\n\n\n\n## [3.4.4](https://github.com/ocsjs/ocsjs/compare/3.4.3...3.4.4) (2022-04-22)\n\n\n### Bug Fixes\n\n* 修复提交设置的BUG ([612dccc](https://github.com/ocsjs/ocsjs/commit/612dcccc40bec2871cccf92bb46c2210da76623e))\n\n\n\n## [3.4.3](https://github.com/ocsjs/ocsjs/compare/3.4.2...3.4.3) (2022-04-21)\n\n\n### Bug Fixes\n\n* 删除无用的选项：搜题错误时暂停 ([e30446c](https://github.com/ocsjs/ocsjs/commit/e30446c2627e628a8aa1c4bb7843c32734131e80))\n\n\n\n## [3.4.2](https://github.com/ocsjs/ocsjs/compare/3.4.1...3.4.2) (2022-04-19)\n\n\n### Bug Fixes\n\n* 修复软件设置空白的BUG ([f757a3b](https://github.com/ocsjs/ocsjs/commit/f757a3b8ae6233eee6fd187471d866b7ec25028a))\n* 修复自动答题提交设置保存不了的BUG ([fb9bd39](https://github.com/ocsjs/ocsjs/commit/fb9bd394c246b7446173fba0d956431c300b6577))\n* app version upate ([505ce22](https://github.com/ocsjs/ocsjs/commit/505ce22fc11c799da5c22b6f183182ea0b43eb6d))\n\n\n\n## [3.4.1](https://github.com/ocsjs/ocsjs/compare/3.4.0...3.4.1) (2022-04-17)\n\n\n### Bug Fixes\n\n* 修复ABCD纯答案直接点击的BUG ([9df1222](https://github.com/ocsjs/ocsjs/commit/9df1222dfa7569ee0995284cbc988ddda7c292bc))\n\n\n\n# [3.4.0](https://github.com/ocsjs/ocsjs/compare/3.3.14...3.4.0) (2022-04-17)\n\n\n### Features\n\n* 新增智慧树学分课作业脚本 ([978bf47](https://github.com/ocsjs/ocsjs/commit/978bf47feec507910ae62e0be60a79ffd8d46941))\n\n\n\n## [3.3.14](https://github.com/ocsjs/ocsjs/compare/3.3.13...3.3.14) (2022-04-15)\n\n\n### Bug Fixes\n\n* 修复 release.sh 版本命令 ([652a022](https://github.com/ocsjs/ocsjs/commit/652a022b1f895e0ec848dc0b54f238d4f8192772))\n\n\n\n## [3.3.13](https://github.com/ocsjs/ocsjs/compare/v3.3.12...3.3.13) (2022-04-15)\n\n\n\n## [3.3.12](https://github.com/ocsjs/ocsjs/compare/v3.3.11...v3.3.12) (2022-04-13)\n\n\n\n## [3.3.11](https://github.com/ocsjs/ocsjs/compare/v3.3.10...v3.3.11) (2022-04-13)\n\n\n### Bug Fixes\n\n* 修复 paste 和 input 共存导致的粘贴BUG ([370e46f](https://github.com/ocsjs/ocsjs/commit/370e46fe3eecffbb5b63c5576dfe6447e79860bd))\n\n\n\n## [3.3.10](https://github.com/ocsjs/ocsjs/compare/v3.3.9...v3.3.10) (2022-04-13)\n\n\n### Bug Fixes\n\n* 修复不能右键复制粘贴的BUG ([9af5480](https://github.com/ocsjs/ocsjs/commit/9af5480038ba11bd47937bff889ee084c72fb04b))\n\n\n\n## [3.3.9](https://github.com/ocsjs/ocsjs/compare/v3.3.8...v3.3.9) (2022-04-13)\n\n\n### Bug Fixes\n\n* 修复上个版本store加载问题 ([48fdc1a](https://github.com/ocsjs/ocsjs/commit/48fdc1a57fbcfa587f2dc42f338249a7b3222985))\n\n\n\n## [3.3.8](https://github.com/ocsjs/ocsjs/compare/v3.3.7...v3.3.8) (2022-04-13)\n\n\n### Bug Fixes\n\n* app 兼容OCS助手最新版的响应式特性 ([eaa66dd](https://github.com/ocsjs/ocsjs/commit/eaa66dd9a1c647f12602363f08003c9254ba0f7d))\n\n\n### Features\n\n* 新增全局存储功能，使用油猴自带API实现。 ([be13a5b](https://github.com/ocsjs/ocsjs/commit/be13a5bd71884ef9f9913a8e2c391da9ecedef4b))\n\n\n\n## [3.3.7](https://github.com/ocsjs/ocsjs/compare/v3.3.6...v3.3.7) (2022-04-13)\n\n\n### Bug Fixes\n\n* 支持纯浏览器端的加载，仅用于核心API的调用。 ([cf9dbe8](https://github.com/ocsjs/ocsjs/commit/cf9dbe8d779c05ba804aab43c242ddde1af8e7c6))\n\n\n### Features\n\n* 删除调试输出 ([a096447](https://github.com/ocsjs/ocsjs/commit/a096447a45573e6bfb05d249aad374e2e56530dd))\n\n\n\n## [3.3.6](https://github.com/ocsjs/ocsjs/compare/v3.3.5...v3.3.6) (2022-04-13)\n\n\n### Bug Fixes\n\n* 修复打包压缩选项 ([9425289](https://github.com/ocsjs/ocsjs/commit/9425289ed6cc864f95f59414783f88db1968d140))\n\n\n### Features\n\n* 新增超星 : 屏蔽作业考试填空简答题粘贴限制 功能 ([591971e](https://github.com/ocsjs/ocsjs/commit/591971eea5065b4097e6b24516cb6d5a36497f76))\n\n\n\n## [3.2.20](https://github.com/ocsjs/ocsjs/compare/v3.2.19...v3.2.20) (2022-04-09)\n\n\n### Bug Fixes\n\n* 延长一点视频暂停后启动的时间，防止超星鬼畜。 ([837d873](https://github.com/ocsjs/ocsjs/commit/837d873d29146de7741e9cfb4b4e988633767551))\n\n\n\n## [3.3.5](https://github.com/ocsjs/ocsjs/compare/v3.3.4...v3.3.5) (2022-04-13)\n\n\n### Bug Fixes\n\n* 修复搜索结果 undefined 的 BUG, 修复超星章节测试填空题BUG ([97500c5](https://github.com/ocsjs/ocsjs/commit/97500c591315eb4fdc97b799e77e788ccdf32def))\n\n\n\n## [3.3.4](https://github.com/ocsjs/ocsjs/compare/v3.3.3...v3.3.4) (2022-04-13)\n\n\n### Bug Fixes\n\n* 修复超星考试作业页面不能复制粘贴的BUG ([4c64c00](https://github.com/ocsjs/ocsjs/commit/4c64c009f6bf87ad63d5b7f717daef339782bdc4))\n* 修复上个版本响应式导致的倍速，音量失效的BUG ([5182d02](https://github.com/ocsjs/ocsjs/commit/5182d02d0fc1a9e11f7b7b2bc5ee3e0387c2b74e))\n* 修复智慧树复习模式的BUG，修复已经播放完的视频但没有完成却跳过的BUG ([3cd12a7](https://github.com/ocsjs/ocsjs/commit/3cd12a72312faec10200a1a4bb700fe17e3e3682))\n\n\n\n## [3.3.3](https://github.com/ocsjs/ocsjs/compare/v3.3.2...v3.3.3) (2022-04-12)\n\n\n### Bug Fixes\n\n* 兼容库模式，并且优化自动答题答案显示 ([fcd2311](https://github.com/ocsjs/ocsjs/commit/fcd2311b3b2564a3a06b2da102cdf841fbd19fc1))\n\n\n### Features\n\n* 新增自动答题答案预览功能 ([29b17e8](https://github.com/ocsjs/ocsjs/commit/29b17e8d055272882aebf672dbf76006f5509fc1))\n\n\n\n## [3.3.2](https://github.com/ocsjs/ocsjs/compare/v3.3.1...v3.3.2) (2022-04-12)\n\n\n### Bug Fixes\n\n* 修复闯关模式因为刷新太快任务点没有出现导致重复的BUG ([dad5eb3](https://github.com/ocsjs/ocsjs/commit/dad5eb3db14c112e7c8f65c6214402502f2a8764))\n* 修复答题配置输入后会消失的BUG， 新增音量设置 ([34cd585](https://github.com/ocsjs/ocsjs/commit/34cd585203623e606e459fd7761f77001a0b6911))\n\n\n\n## [3.3.1](https://github.com/ocsjs/ocsjs/compare/v3.3.0...v3.3.1) (2022-04-11)\n\n\n\n# [3.3.0](https://github.com/ocsjs/ocsjs/compare/v3.2.20...v3.3.0) (2022-04-11)\n\n\n### Bug Fixes\n\n* 修复各种问题，并且兼容了响应式特性 ([0c42947](https://github.com/ocsjs/ocsjs/commit/0c42947afa70f75f57435d5bebdd1dd48141754a))\n* 修改打包方式为压缩打包 ([333661f](https://github.com/ocsjs/ocsjs/commit/333661f4c3a5338721e56227ef0e6fcb53ea872f))\n\n\n### Features\n\n* 切换网络路线 ([0a0a5dd](https://github.com/ocsjs/ocsjs/commit/0a0a5dd20ce80cb503f3c57d8b6aa2c25e261b8d))\n* 响应式特性 ([5e21f0a](https://github.com/ocsjs/ocsjs/commit/5e21f0a1c17b1aca8ba22692c6649c6bae397d51))\n* vnode 重构成 tsx , 并且新增数据响应式特性 ([18c5a78](https://github.com/ocsjs/ocsjs/commit/18c5a7836cd11222f8301687fff930b5e93583c0))\n\n\n\n## [3.2.20](https://github.com/ocsjs/ocsjs/compare/v3.2.19...v3.2.20) (2022-04-09)\n\n\n### Bug Fixes\n\n* 延长一点视频暂停后启动的时间，防止超星鬼畜。 ([837d873](https://github.com/ocsjs/ocsjs/commit/837d873d29146de7741e9cfb4b4e988633767551))\n\n\n\n## [3.2.19](https://github.com/ocsjs/ocsjs/compare/v3.2.18...v3.2.19) (2022-04-08)\n\n\n### Bug Fixes\n\n* 修复切换路线后倍速无效的BUG ([4e3f78b](https://github.com/ocsjs/ocsjs/commit/4e3f78b78f5ae3467d4546fc23d4aba0aa8fda4e))\n\n\n\n## [3.2.18](https://github.com/ocsjs/ocsjs/compare/v3.2.16...v3.2.18) (2022-04-08)\n\n\n### Features\n\n* 新增超星视频路线切换功能 ([8721e48](https://github.com/ocsjs/ocsjs/commit/8721e4895c46393b210a1f707b61f5b7a446ecee))\n\n\n\n## [3.2.16](https://github.com/ocsjs/ocsjs/compare/v3.2.14...v3.2.16) (2022-04-08)\n\n\n### Bug Fixes\n\n* 修复快捷键有时候失效的问题 ([0d5df16](https://github.com/ocsjs/ocsjs/commit/0d5df16cbd18936180935d02642cf68faea8c22f))\n\n\n\n## [3.2.14](https://github.com/ocsjs/ocsjs/compare/v3.2.13...v3.2.14) (2022-04-08)\n\n\n### Features\n\n* 新增隐藏按钮 ([dce116e](https://github.com/ocsjs/ocsjs/commit/dce116e6f268bffe5ef1e178980c4318b47ea755))\n* 新增ocs快捷键，可重置位置，优化面板初始位置 ([af7d231](https://github.com/ocsjs/ocsjs/commit/af7d231d9af520ace066190981196975a4d08213))\n\n\n\n## [3.2.13](https://github.com/ocsjs/ocsjs/compare/v3.2.12...v3.2.13) (2022-04-07)\n\n\n### Features\n\n* 彻底修复超星验证码问题 ([0cf69f2](https://github.com/ocsjs/ocsjs/commit/0cf69f22f070d47ed72cd4c1d5b898b6ad3e2e14))\n\n\n\n## [3.2.12](https://github.com/ocsjs/ocsjs/compare/v3.2.11...v3.2.12) (2022-04-05)\n\n\n### Features\n\n* 新增超星支持域名 edu.cn ， 修复多个视频播放时，播放完成继续播放的BUG ([959cc9b](https://github.com/ocsjs/ocsjs/commit/959cc9b2b38e6b573823ee5c9976f92089bdc5bb))\n\n\n\n## [3.2.11](https://github.com/ocsjs/ocsjs/compare/v3.2.10...v3.2.11) (2022-04-04)\n\n\n### Bug Fixes\n\n* 修改点击间隔 ([176d83e](https://github.com/ocsjs/ocsjs/commit/176d83eaaa210b562ab38458ee15a4969c6b080e))\n\n\n\n## [3.2.10](https://github.com/ocsjs/ocsjs/compare/v3.2.9...v3.2.10) (2022-04-04)\n\n\n### Bug Fixes\n\n* 继续优化答题问题 ([2dfd14d](https://github.com/ocsjs/ocsjs/commit/2dfd14d57e195615a257ac21b0db6fa167714768))\n\n\n\n## [3.2.9](https://github.com/ocsjs/ocsjs/compare/v3.2.8...v3.2.9) (2022-04-04)\n\n\n### Bug Fixes\n\n* 修复超星重复暂停的BUG ([c5c7681](https://github.com/ocsjs/ocsjs/commit/c5c768176e53c7266c9e90b97ec351943627143a))\n\n\n\n## [3.2.8](https://github.com/ocsjs/ocsjs/compare/v3.2.7...v3.2.8) (2022-04-04)\n\n\n### Features\n\n* 新增解除右键，复制粘贴限制的功能， 修复超星播放视频重复卡死的BUG ([fe9b59a](https://github.com/ocsjs/ocsjs/commit/fe9b59ac28baded93f30539daf262a18870fedf0))\n\n\n\n## [3.2.7](https://github.com/ocsjs/ocsjs/compare/v3.2.6...v3.2.7) (2022-04-04)\n\n\n### Features\n\n* 答案判断优化 ([801e4f3](https://github.com/ocsjs/ocsjs/commit/801e4f3a0ffe66c6979833bae113f868bbba3f38))\n\n\n\n## [3.2.6](https://github.com/ocsjs/ocsjs/compare/v3.2.5...v3.2.6) (2022-04-03)\n\n\n### Bug Fixes\n\n* 修复软件1.2.0自动更新BUG ([512b61c](https://github.com/ocsjs/ocsjs/commit/512b61c26f8b72828cd875b9ecf867dcc879f3b7))\n* 修复智慧树作业重复关闭的BUG ([2288d61](https://github.com/ocsjs/ocsjs/commit/2288d6119af7efa97761e2bc8cca3393b62e507f))\n\n\n### Features\n\n* 新增 common 包 ， 修复脚本执行BUG ， 修复通知 ， 新增打包脚本 scripts 文件夹 ([0831102](https://github.com/ocsjs/ocsjs/commit/083110233cc5462ed5d7d2c44bfc7141f9c8f9e8))\n* aPP版本更新 1.2.0 ([4ebd3cb](https://github.com/ocsjs/ocsjs/commit/4ebd3cbdc5775c7c4f60fd6c00f621297fed7d00))\n\n\n\n## [3.2.5](https://github.com/ocsjs/ocsjs/compare/v3.2.4...v3.2.5) (2022-04-01)\n\n\n### Bug Fixes\n\n* 修复答题时多选不全的BUG， 修复答题时答案为空报错的BUG ([75177b5](https://github.com/ocsjs/ocsjs/commit/75177b553b269282b16e812c6e65e1ffb8edad01))\n* 修改远程信息获取路径 ([b80d091](https://github.com/ocsjs/ocsjs/commit/b80d0914bd91482d2e75978d3d6676a92717d91e))\n\n\n### Features\n\n* 自动更新功能， scripts 分包 ， 浏览器端修改为 core 文件夹 ([f536614](https://github.com/ocsjs/ocsjs/commit/f53661497d92911c4daea12ae2936471169b3e5a))\n\n\n\n## [3.2.4](https://github.com/ocsjs/ocsjs/compare/v3.2.3...v3.2.4) (2022-03-31)\n\n\n### Bug Fixes\n\n* 修复上个版本视频跳过问题 ([1719bbd](https://github.com/ocsjs/ocsjs/commit/1719bbdfba8cc21bb10cad8c16ec7ae6127ae6af))\n* 修复油猴脚本更新BUG ([fd043dd](https://github.com/ocsjs/ocsjs/commit/fd043ddc3a6d9d3a3863c158379d7e1afd37a1d6))\n\n\n\n## [3.2.3](https://github.com/ocsjs/ocsjs/compare/v3.2.2...v3.2.3) (2022-03-30)\n\n\n### Features\n\n* 添加任务点是否完成检测，不重复执行已完成任务点， 修复判断题不选择的BUG ([dbc1a86](https://github.com/ocsjs/ocsjs/commit/dbc1a86c5793cecfbd5521ee24d4373da3b3ee5e))\n\n\n\n## [3.2.2](https://github.com/ocsjs/ocsjs/compare/v3.2.1...v3.2.2) (2022-03-27)\n\n\n### Features\n\n* 添加智慧树学分课脚本 ([37b112a](https://github.com/ocsjs/ocsjs/commit/37b112ae8f1488b4b07ce4a986ac798df8d0bd9e))\n\n\n\n## [3.2.1](https://github.com/ocsjs/ocsjs/compare/3.2.0...v3.2.1) (2022-03-27)\n\n\n### Features\n\n* 添加禁止弹窗脚本 ([2b6be41](https://github.com/ocsjs/ocsjs/commit/2b6be413847fa55367d72ee5b16570d8ef8a218b))\n\n\n\n# [3.2.0](https://github.com/ocsjs/ocsjs/compare/v3.1.11...3.2.0) (2022-03-26)\n\n\n### Features\n\n* **app:** 软件更新，支持超星学习作业考试，支持脚本自动更新 ([f500119](https://github.com/ocsjs/ocsjs/commit/f500119bed4d05d775b635de672c3b3e16e5a363))\n\n\n\n## [3.1.11](https://github.com/ocsjs/ocsjs/compare/v3.1.10...v3.1.11) (2022-03-26)\n\n\n### Features\n\n* 更新软件，加载时自动更新ocs脚本 ([e16d954](https://github.com/ocsjs/ocsjs/commit/e16d954ff5bb4a084fa4dd6ff4e6dc6b9643f483))\n\n\n\n## [3.1.10](https://github.com/ocsjs/ocsjs/compare/v3.1.9...v3.1.10) (2022-03-26)\n\n\n### Bug Fixes\n\n* 修复脚本只能在油猴环境下执行的BUG ([4f081c9](https://github.com/ocsjs/ocsjs/commit/4f081c90ee20e67a7f4e2afb43098e0c4308201e))\n\n\n\n## [3.1.9](https://github.com/ocsjs/ocsjs/compare/v3.1.8...v3.1.9) (2022-03-26)\n\n\n\n## [3.1.8](https://github.com/ocsjs/ocsjs/compare/v3.1.7...v3.1.8) (2022-03-26)\n\n\n### Bug Fixes\n\n* 修改答案结果判断bug ([a2808d0](https://github.com/ocsjs/ocsjs/commit/a2808d09b6ffa37376b25682cc4c8cd1cf0be5d7))\n\n\n\n## [3.1.7](https://github.com/ocsjs/ocsjs/compare/3.0.0-beta.9...v3.1.7) (2022-03-25)\n\n\n### Bug Fixes\n\n* 修改题库配置设置，取消必选，添加判断，如果没有题库设置，则不开始自动答题。 ([b25ef93](https://github.com/ocsjs/ocsjs/commit/b25ef9323d0fe5c9d8437e3f50551a5e47e20dfa))\n* 修改油猴配置 ([db8a733](https://github.com/ocsjs/ocsjs/commit/db8a733adaadb263fa2ed05233bd0b03f93c2e68))\n* **cx:** 兼容cx选项获取不到，以及设置保存bug ([5a2e672](https://github.com/ocsjs/ocsjs/commit/5a2e67214d45053475574f4e16d3023909f6b132))\n* **script:** 修复答题解析器bug， 更新做题标题获取，修复学习时章节测验类型获取失败bug， 更新 app 的脚本 ([659955b](https://github.com/ocsjs/ocsjs/commit/659955b818b41b7f25506e93caa888392e37aba1))\n* **script:** 修改答题配置解析器 ([6e4332f](https://github.com/ocsjs/ocsjs/commit/6e4332f3855319e59f0bf5981fb713b7463cb531))\n* **style:** 修改样式引入 ([8a6ab90](https://github.com/ocsjs/ocsjs/commit/8a6ab903ef661b89bc81bdc2298cdb28c2f40f9e))\n* **url:** 修改 url cdn 资源 ([5cad7c1](https://github.com/ocsjs/ocsjs/commit/5cad7c19b6976d5825c982ccbede011d157b94a8))\n\n\n\n# [3.0.0-beta.9](https://github.com/ocsjs/ocsjs/compare/3.0.0-beta.6...3.0.0-beta.9) (2022-03-24)\n\n\n### Bug Fixes\n\n* **index:** 修改数据路径 ([c97bdaf](https://github.com/ocsjs/ocsjs/commit/c97bdaf00aef72ce9c71068573cd4d9933d15590))\n\n\n### Features\n\n* 版本更新至 beta.6 ([cc63a07](https://github.com/ocsjs/ocsjs/commit/cc63a076ddb79235f8ca12c0c30b500c5d8be43e))\n* 新增答题器，新增题库配置选项，新增zhs作业功能 ([449c008](https://github.com/ocsjs/ocsjs/commit/449c008af2f961c6a6ca12788309fabcc8334cfc))\n* 修改为单进程软件，支持.ocs文件的点击加载，新增软件标题栏，删除原生标题栏 ([8427936](https://github.com/ocsjs/ocsjs/commit/8427936e931ca8bfcc86240393d40c7583f95abc))\n* **all:** 版本更新 ([3b17a92](https://github.com/ocsjs/ocsjs/commit/3b17a9242d6cc12330e8bbb5ff15c88957507ce2))\n* **scripts:** 添加超星学习，考试，作业脚本。 添加日志，搜题结果面板。 ([94d99d4](https://github.com/ocsjs/ocsjs/commit/94d99d4fb32ce9b58e88e0fa8f0812adea7f89a6))\n\n\n\n# [3.0.0-beta.6](https://github.com/ocsjs/ocsjs/compare/3.0.0-beta.5...3.0.0-beta.6) (2022-03-15)\n\n\n### Features\n\n* **app | script:** 新增 script/browser 端的面板显示，替换之前的油猴头部信息加载模式，添加app主进程端的脚本调用 ([f9cf10f](https://github.com/ocsjs/ocsjs/commit/f9cf10f6757a45d520c1c2ff5532bceaa9298f07))\n* **web:** 添加文件关闭编辑功能，取消页面所有动画效果，删除'关于'页面，新增'帮助'一栏。 ([1f4d4ad](https://github.com/ocsjs/ocsjs/commit/1f4d4ad7da78d97282524dd9325fc9c4ac1468f5))\n\n\n\n# [3.0.0-beta.5](https://github.com/ocsjs/ocsjs/compare/0675ac6a631e8946a52e7e4e655b28faee8248d4...3.0.0-beta.5) (2022-03-07)\n\n\n### Bug Fixes\n\n* **package.json:** fix dependency security ([948662a](https://github.com/ocsjs/ocsjs/commit/948662a580aab60a3ff70c64111aa36c40b5a4ce))\n* **web:** 优化侧边栏： 优化文件列表头部，优化文件列表搜索 ([84acc62](https://github.com/ocsjs/ocsjs/commit/84acc62870db173211165408c8fe665df556ebf0))\n\n\n### Features\n\n* add glup ([15c0015](https://github.com/ocsjs/ocsjs/commit/15c0015141f4104888813dda905fc723843f66ca))\n* **all:** 添加终端显示，添加脚本执行，添加文件属性 ([cf5dc9a](https://github.com/ocsjs/ocsjs/commit/cf5dc9a2d17057c5ac2db7df43e9e2a5b26e664f))\n* **app and web:** 添加文件夹管理，添加右键菜单，初始化设置和关于页面 ([24930ad](https://github.com/ocsjs/ocsjs/commit/24930ad1e32508e227fb36b7008fb2981438cbb7))\n* **app and web:** add elctron-builder in app , init  page view in web ([fe60df6](https://github.com/ocsjs/ocsjs/commit/fe60df65dfeed1607ab89a941fc6b6eb627132fc))\n* **init:** init project ([0675ac6](https://github.com/ocsjs/ocsjs/commit/0675ac6a631e8946a52e7e4e655b28faee8248d4))\n* **packages:** init packages : web app scripts ([24e5386](https://github.com/ocsjs/ocsjs/commit/24e5386ec86dec33cc696fda6b5956785e2c1359))\n* **script|web|app:** add commander line support, and update web view ([35efb39](https://github.com/ocsjs/ocsjs/commit/35efb39f34e4dc0d4e4914516f95d0ff467f8f37))\n* **script:** add cx and zhs login ([8d69e16](https://github.com/ocsjs/ocsjs/commit/8d69e166fd73cf2ae2d700aa772b78159efbdeaa))\n* **script:** change folder name, and add browser script ([c540500](https://github.com/ocsjs/ocsjs/commit/c540500b0cef50e7c2944d3ea6331e93c3e00e52))\n* **scripts-tempaermonkey:** add tempermonkey support, update browser export ([456af02](https://github.com/ocsjs/ocsjs/commit/456af0250ff33fada0930f01270d1e147f27e2f7))\n* **scripts/cx:** add new login : phone-code login ([494e523](https://github.com/ocsjs/ocsjs/commit/494e523ffda8ddf5c9320be5861b42234fab4d91))\n* **scripts:** 修改 package.json ， 调整登录api ([614c628](https://github.com/ocsjs/ocsjs/commit/614c628a749bc6dd4e1556fd9fbdb026c82d6937))\n* **scripts:** add script package ([6f03fb4](https://github.com/ocsjs/ocsjs/commit/6f03fb4d3545f02f8ccfeb14fa7b5d8169c15348))\n* **test and cx login:** update tests README.md and add new login way of cx : phone-code-login ([ab46a1d](https://github.com/ocsjs/ocsjs/commit/ab46a1df98c73351518847a629ddaeb7f51a2d4d))\n* **web:** 添加文件解析，使用懒加载进行文件的显示 ([0da0f02](https://github.com/ocsjs/ocsjs/commit/0da0f024741191ce408354ffc148c83dc7bea4f3))\n* **web:** 添加文件拖拽，文件搜索功能 ([fb1b7c9](https://github.com/ocsjs/ocsjs/commit/fb1b7c93c1679e484769362724c2818c68898145))\n* **web:** 添加重命名功能，添加目录展开记录保存，添加帮助页面 ([0b90cc2](https://github.com/ocsjs/ocsjs/commit/0b90cc20d33c21b406b7d6426df812aed03eec1c))\n* **web:** 文件编辑，文件拓展 ([9f2f4fb](https://github.com/ocsjs/ocsjs/commit/9f2f4fb6b4fd37b7526239bacf45135ff5874cbc))\n* **web:** 支持文件（夹）拖拽放置，文件（夹）的创建，删除 ([705cc21](https://github.com/ocsjs/ocsjs/commit/705cc210bf382a3a92962f03526366edc0e18398))\n\n\n\n"
  },
  {
    "path": "CHANGELOG_CURRENT.md",
    "content": "## [4.13.4](https://github.com/ocsjs/ocsjs/compare/4.13.2...4.13.4) (2026-04-23)\n\n\n### Features\n\n* **script:** 兼容超星长时阅读任务点（限制翻页时间） ([ac34ba2](https://github.com/ocsjs/ocsjs/commit/ac34ba2d543afe9dc09c1803b558f7b37dbce095))\n* **script:** 添加超星域名支持：jnzyjsxy.cn ([a27cb49](https://github.com/ocsjs/ocsjs/commit/a27cb49b9111e3317120c33eff75981de4b19ed8))\n\n\n\n"
  },
  {
    "path": "CHANGELOG_SIMPLIFIED.md",
    "content": "## 4.13.4 (2026-04-23)\n\n\n### ✨ 更新内容\n\n* 兼容超星长时阅读任务点（限制翻页时间） <a href=\"https://github.com/ocsjs/ocsjs/commit/ac34ba2d543afe9dc09c1803b558f7b37dbce095\">></a>\n* 添加超星域名支持：jnzyjsxy.cn <a href=\"https://github.com/ocsjs/ocsjs/commit/a27cb49b9111e3317120c33eff75981de4b19ed8\">></a>\n\n\n\n## 4.13.2 (2026-04-22)\n\n\n### 🔧 修复内容\n\n* 修复智慧树26年上半年studywisdomh5复习模式错误的问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/2e82a0a8ea4cca4265ba592336a2762bf156ec08\">></a>\n\n\n### ✨ 更新内容\n\n* 兼容雨课堂AI学伴自动学习 <a href=\"https://github.com/ocsjs/ocsjs/commit/c96f4db7d949df6f3fc983a6ae4783ece9b19783\">></a>\n\n\n\n## 4.12.39 (2026-04-20)\n\n\n### 🔧 修复内容\n\n* 修复智慧职教题目识别丢失img的src问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/482ac64f6d9ee0dc54a8bef1fe210ccb4b02d25a\">></a>\n* 修复智慧职教题目识别丢失img的src问题,使用与学习通页面相同的方法 <a href=\"https://github.com/ocsjs/ocsjs/commit/488f467a8c212e17a866f0746e86a6c0084f1e2b\">></a>\n* 修复智慧职教题目识别丢失img的src问题,使用与学习通页面相同的方法,且考虑纯图片无法识别问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/3a23e04d89bb7913638ec880e9817aaa013143ba\">></a>\n* 修复智慧职教题目识别丢失img的src问题,使用与学习通页面相同的方法,且考虑纯图片无法识别问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/d0209548c56f6205b7c9edf4fe7ef84f334d03ec\">></a>\n\n\n\n## 4.12.38 (2026-04-20)\n\n\n### 🔧 修复内容\n\n* 兼容26年上半年智慧树新版studywisdomh5网课更新 <a href=\"https://github.com/ocsjs/ocsjs/commit/cf52bf019b3f9c36c82bc5b90e5e3125510ff3cb\">></a>\n\n\n\n## 4.12.37 (2026-04-15)\n\n\n### 🔧 修复内容\n\n* 修复超星某些章节测试无法自动答题的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/62b0aaf86416a07051513e7fbebcce94f29c12cc\">></a>\n\n\n### ✨ 更新内容\n\n* 添加智慧树2026上学期新版HIKE AI教学中心视频学习功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/392a1a59d5c352e0ca893bf72799cce9333e7f08\">></a>\n\n\n### ⚡ 优化提升\n\n* 适配超星积分课的阅读任务不会自动开始阅读的问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/9b8ff4f4e34b3e9de6e4c863b10b24bc7d7cf607\">></a>\n* 添加超星阅读界面的使用提示 <a href=\"https://github.com/ocsjs/ocsjs/commit/a8aa8695efe8fb1c74baaab106c4c2401f86de53\">></a>\n* 优化超星跳转时间太长的问题，改成智能等待时间 <a href=\"https://github.com/ocsjs/ocsjs/commit/631461620821559ea1bf068ba2445308df4995f1\">></a>\n\n\n\n## 4.12.32 (2026-04-09)\n\n\n### 🔧 修复内容\n\n* 尝试修复智慧树AI学伴课程过一段时间才会播放的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/7c56dc925284dd8ac5237da0a6fd9b7dae643264\">></a>\n\n\n### ⚡ 优化提升\n\n* 优化超星闯关模式重复提示 <a href=\"https://github.com/ocsjs/ocsjs/commit/fd83815150eced1e8caf8d11493ab9841187c49c\">></a>\n\n\n\n## 4.12.30 (2026-03-20)\n\n\n### ⚡ 优化提升\n\n* 添加智慧树新智慧形态考试菜单进入菜单栏 <a href=\"https://github.com/ocsjs/ocsjs/commit/d9d362bdc5d9bbd3de04019dede94c856d68ac77\">></a>\n\n\n\n## 4.12.29 (2026-03-20)\n\n\n### 🔧 修复内容\n\n* 兼容职教云xls任务点自动学习 <a href=\"https://github.com/ocsjs/ocsjs/commit/7a3a1748d85d617c740ce631b5d2b52cf5cec80f\">></a>\n* 修复智慧树-新形态课程链接任务点无法跳过的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/51a639d4b80a8851e347e1190f94d1cdc5f7f4a5\">></a>\n\n\n### ✨ 更新内容\n\n* 添加智慧树-新形态课程自动考试功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/587a045ff454e20b5ecef7d509c0fb25f43af4db\">></a>\n\n\n\n## 4.12.26 (2026-03-18)\n\n\n### 🔧 修复内容\n\n* 修复职教云PPT仅有2页时无法完成的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/018462f2746816f70439270e669756aa87240b03\">></a>\n* 修复智慧职教AI测验答题时填空功能失效的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/d850bea91b70780d23706eea433756812c6c6016\">></a>\n\n\n### ✨ 更新内容\n\n* 添加新版职教云自动跳转旧版功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/55a9f1e198a71d8e8dd163cda4fdadfa8106c925\">></a>\n\n\n\n## 4.12.23 (2026-03-17)\n\n\n### 🔧 修复内容\n\n* 修复上个版本无法打包的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/97608d81991ce1e0f262e2e09f1252a4839996af\">></a>\n\n\n\n## 4.12.22 (2026-03-17)\n\n\n### 🔧 修复内容\n\n* 添加智慧树-AI教学中心-题目作业自动答题功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/2a630aade1c71b78e2fd1275f1f9713b2bbf21f6\">></a>\n* 修复图片题中的图片在解析时重复出现的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/84c697e1b681aa11c397cf335cd2730d00e7160c\">></a>\n* 修复职教云资源库无法跳过测验的问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/34e4f38310fc887b27cb71c5b8d1b6132c3b9f03\">></a>\n* 修复智慧职教AI测验答题时无法填空的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/edb492af72d6d8cd09e2146b6e39ecfe68c135f7\">></a>\n\n\n### ⚡ 优化提升\n\n* 更新API，添加新API $msg，可同时显示气泡和打印日志 <a href=\"https://github.com/ocsjs/ocsjs/commit/099907ae72be2cc50874162667528e1c6d643b8b\">></a>\n* 添加答题日志输出开关功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/d04137339f023270f390c9344be74df854008181\">></a>\n\n\n\n## 4.12.16 (2026-03-06)\n\n\n### 🔧 修复内容\n\n* 修复智慧课程AI伴学的视频无法检测，以及掌握度只有一题的情况无法答题的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/8ff88f2ddf2a16852082b5ca7d84575d5215bb6a\">></a>\n* 优化 eslint 报错 <a href=\"https://github.com/ocsjs/ocsjs/commit/036644fc351cbdba3cca55655200bf1cd421dc92\">></a>\n\n\n\n## 4.12.15 (2026-03-04)\n\n\n### 🔧 修复内容\n\n* 修复2026上学期智慧树-新智慧课程无法自动下一章的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/437696da2017549c40e10584d964c82be77bd16e\">></a>\n\n\n\n## 4.12.14 (2026-02-28)\n\n\n### ✨ 更新内容\n\n* 添加ZE题库域名白名单授权 <a href=\"https://github.com/ocsjs/ocsjs/commit/a5bdb919b28f87023e79230930f46245326aef6d\">></a>\n\n\n\n## 4.12.13 (2026-02-25)\n\n\n### 🔧 修复内容\n\n* 适配 2026 上学期智慧树-新智慧学习界面 <a href=\"https://github.com/ocsjs/ocsjs/commit/6f449ccfc25c6d99d287ae39a6434a8a28a07f58\">></a>\n\n\n\n## 4.12.12 (2025-12-29)\n\n\n### 🔧 修复内容\n\n* 修复 github workflow 运行后 release 不显示更新内容的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/96065e70fcea1226ffc97f6759ec4b10e0e6783c\">></a>\n\n\n\n## 4.12.11 (2025-12-29)\n\n\n### 🔧 修复内容\n\n* 添加 git workflow 输出 <a href=\"https://github.com/ocsjs/ocsjs/commit/467d53d5f96c44cb3629723ebf471a13b65e4b49\">></a>\n\n\n\n## 4.12.10 (2025-12-29)\n\n\n### 🔧 修复内容\n\n* 修复 github workflow 运行后 release 不显示更新内容的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/addb25a5aceb6b0ae72ef1c8718797d60f595df0\">></a>\n\n\n\n## 4.12.9 (2025-12-29)\n\n\n### 🔧 修复内容\n\n* 修复 github workflow 运行后 release 不显示更新内容的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/2bacbfd0dcfe4f35d8058aee92aec07a376b96c4\">></a>\n\n\n\n## 4.12.8 (2025-12-29)\n\n\n### 🔧 修复内容\n\n* 持续修复 github workflow 运行后 release 不显示更新内容的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/ecfa20390b3648522a0b5383fcc6cae6d8cc4223\">></a>\n\n\n\n## 4.12.7 (2025-12-29)\n\n\n### 🔧 修复内容\n\n* 修复 github workflow 运行后 release 不显示更新内容的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/676c7ed2dffc2f5a547a496b9fcf20ff3ab3dc14\">></a>\n\n\n\n## 4.12.6 (2025-12-29)\n\n\n### 🔧 修复内容\n\n* 适配2025-12月智慧树新智慧学习域名 <a href=\"https://github.com/ocsjs/ocsjs/commit/acc2c79ccb5c1542f92f1811d84e543caebd1fe8\">></a>\n* 修复超星学习通没配置题库无法跳过章节测试的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/e27d978f3620a90e961c79f523d6cce3b58f6380\">></a>\n\n\n\n## 4.12.4 (2025-12-16)\n\n\n### 🔧 修复内容\n\n* 修复答案为圆圈数字时无法答题的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/676f4374745effe0187460560e097bc5c2e84c14\">></a>\n\n\n\n## 4.12.3 (2025-12-03)\n\n\n### 🔧 修复内容\n\n* 添加职教云资源库修复功能按钮 <a href=\"https://github.com/ocsjs/ocsjs/commit/898bb3b31ca18740b4cf4072369adf45150f1b3d\">></a>\n* 修复中国大学MOOC新版考试无法运行的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/8f0a18824ec7839a288620cf9c58cd79ec11ca57\">></a>\n\n\n### ⚡ 优化提升\n\n* 加强智慧树作业考试自动保存警告 <a href=\"https://github.com/ocsjs/ocsjs/commit/7a44eb7a70ca23547f55419fa3c7e69755c0c038\">></a>\n\n\n\n# 4.12.0 (2025-11-27)\n\n\n### 🔧 修复内容\n\n* 修复超星答题时无法点击快速定位到题库配置界面问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/855e8e92a647d886a578f658aa105f8686b26e49\">></a>\n\n\n### ✨ 更新内容\n\n* 添加软件辅助鼠标在空闲状态下执行辅助点击等功能，防止与用户抢夺控制权 <a href=\"https://github.com/ocsjs/ocsjs/commit/e5b3b1e2527a1930452164d7cd1fda77eafaa8a8\">></a>\n* 添加自定义答题器，可自定义题目类型、答题器等参数，自由度更高 <a href=\"https://github.com/ocsjs/ocsjs/commit/f1f7813849f7c7211d630f5dacbe629d74fc5b02\">></a>\n* 兼容超星繁体字判断题适配 <a href=\"https://github.com/ocsjs/ocsjs/commit/a81cd577b1e4d9f3b42a8bed55fab4a28d151036\">></a>\n\n\n### ⚡ 优化提升\n\n* 优化 waitForElement API <a href=\"https://github.com/ocsjs/ocsjs/commit/4cf2191be1d36a61d78c878cfd45e282ad7f095b\">></a>\n\n\n\n## 4.11.98 (2025-11-24)\n\n\n### 🔧 修复内容\n\n* 修复上个版本无法打包的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/089aaafcfefcdb1ae69fde9cdb0e1ad7aed376d3\">></a>\n\n\n\n## 4.11.97 (2025-11-24)\n\n\n### 🔧 修复内容\n\n* 修复高级设置无法打开的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/81dbc1254939a5996ff384c70a49169b7189d6b4\">></a>\n\n\n\n## 4.11.96 (2025-11-24)\n\n\n### 🔧 修复内容\n\n* 修复中国大学MOOC视频题目重复答题的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/7c6fd8852af43022c18e21cb6582e8388eb2587e\">></a>\n\n\n\n## 4.11.95 (2025-11-23)\n\n\n### ⚡ 优化提升\n\n* aPI优化 <a href=\"https://github.com/ocsjs/ocsjs/commit/f988878dfb65c5feb95aaa771efe05eaa39944c0\">></a>\n\n\n\n## 4.11.94 (2025-11-23)\n\n\n### 🔧 修复内容\n\n* 题库缓存清空后弹窗数量文字归零 <a href=\"https://github.com/ocsjs/ocsjs/commit/7a1ae7d7508ba4d352fc4374aec8463a284cadf2\">></a>\n* 修复超星默认跳转模式无法自动滚动页面到当前章节列表问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/5600bdd761d05c081c383e34b215abdfe909fcf0\">></a>\n\n\n### ✨ 更新内容\n\n* 对移动端进行拖动面板适配 <a href=\"https://github.com/ocsjs/ocsjs/commit/d253f8b77bfb7006b3cb94338602476e65aa2961\">></a>\n\n\n### ⚡ 优化提升\n\n* 优化题库搜索题目显示，可兼容长题目例如阅读理解上下文显示 <a href=\"https://github.com/ocsjs/ocsjs/commit/2e62f4c35404ef8558456aa34c1309afb059d2fa\">></a>\n\n\n\n## 4.11.86 (2025-11-20)\n\n\n### 🔧 修复内容\n\n* 修复上个版本类型报错 <a href=\"https://github.com/ocsjs/ocsjs/commit/f472a0e8ffc63b67ab03d56e19857a38d58d57bf\">></a>\n\n\n\n## 4.11.85 (2025-11-20)\n\n\n### 🔧 修复内容\n\n* 将超星环境调整脚本整合到内部，防止全局污染 <a href=\"https://github.com/ocsjs/ocsjs/commit/b8a2a207a679eefd6647a9b058ea4f1dd39509fa\">></a>\n* 修复中国大学MOOC作业答题错乱问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/3dfcef1074d0e8258adee054af93ebb5d9373c35\">></a>\n\n\n\n\n\n\n\n## 4.11.81 (2025-11-14)\n\n\n### 🔧 修复内容\n\n* 修复职教云资源库附件视频任务无法完成的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/e8635e5e090243ba04aee82887c9f95190e32bcc\">></a>\n* 修复职教云资源库总是无法学满进度的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/b06dc44b5be6847dbdc238fc69fb9a732f1e6178\">></a>\n\n\n\n## 4.11.78 (2025-11-13)\n\n\n### 🔧 修复内容\n\n* 修改职教云资源库视频PPT跳转间隔为5秒 <a href=\"https://github.com/ocsjs/ocsjs/commit/32504fa2553925fae3439a769650aaf9b6734095\">></a>\n\n\n\n## 4.1.76 (2025-11-12)\n\n\n### 🔧 修复内容\n\n* 修复职教云资源库PPT总是会返回第一页才结束的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/5aab29b9f67f743cef71ab2e99f3eec31d87c47a\">></a>\n\n\n\n## 4.11.77 (2025-11-12)\n\n\n### 🔧 修复内容\n\n* 修复职教云资源库PPT总是会返回第一页才结束的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/e665f554d6b261cc176d38acc788e80f492923f1\">></a>\n\n\n\n## 4.1.76 (2025-11-12)\n\n\n### 🔧 修复内容\n\n* 修复职教云资源库PPT总是会返回第一页才结束的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/5aab29b9f67f743cef71ab2e99f3eec31d87c47a\">></a>\n\n\n\n## 4.11.75 (2025-11-12)\n\n\n### 🔧 修复内容\n\n* 修复超星在章节列表页面提示已进入学习界面的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/9e899e82c70bfaf530d42e1bdaf489e0680723c5\">></a>\n\n\n\n## 4.11.74 (2025-11-12)\n\n\n### 🔧 修复内容\n\n* 修复超星视频加载失败无法重启的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/385d8a32e40cc91dcb156f8c48238bedb060152b\">></a>\n\n\n\n## 4.11.73 (2025-11-12)\n\n\n### 🔧 修复内容\n\n* 使用软件辅助模拟输入填空适配智慧树智慧课程掌握度填空题 <a href=\"https://github.com/ocsjs/ocsjs/commit/a57be3706f408f68401b80a0672d6599959aaa05\">></a>\n* 添加智慧树倍速最高风险说明 <a href=\"https://github.com/ocsjs/ocsjs/commit/cffcbc981f28a91964ee2f7f17f170272c875ad0\">></a>\n* 修复配置多个题库其中某个超时无法答题的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/dd569dce9cb1697d0ee084d28f76eeafca9e52c3\">></a>, closes [#270](https://github.com/ocsjs/ocsjs/issues/270)\n\n\n\n## 4.11.69 (2025-11-09)\n\n\n### 🔧 修复内容\n\n* 修复隐藏窗口按钮无法使用的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/c028a8f9a921a4e1ce1ca01924c6d9f6dc0befcd\">></a>\n* 添加中国大学MOOC空白页自动跳转的功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/c562d2ce4ccb2a1e2fce15adec1f56a9b913e76c\">></a>\n* 修复中国大学MOOC视频答题和章节测试冲突的问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/a61a8e02bb3c5c7054d99a90224d9bcfacec312e\">></a>\n\n\n### ✨ 更新内容\n\n* 适配智慧树-AI教学中心-智慧课程作业答题功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/3d124af156fa640099a7fb7d94fe5c0a684a9685\">></a>\n* 修复智慧树倍速失效的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/26a9a6dd73106f956acc5f6a299651522a77b24a\">></a>\n\n\n\n## 4.11.64 (2025-11-07)\n\n\n### 🔧 修复内容\n\n* 答案匹配时移除标点符号保证匹配更加准确 <a href=\"https://github.com/ocsjs/ocsjs/commit/3f02c307de1baef626de8c2ad83830904fc519b7\">></a>\n* 修复上个版本中国大学MOCC考试部分题目答题错误问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/bcf21b362c122d9414589d658cafc9e2ff19e398\">></a>\n\n\n\n## 4.11.61 (2025-11-06)\n\n\n### 🔧 修复内容\n\n* 修复职教云资源库点击PPT过快的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/454ee5d564c1e3f45728e56f779da5ea313abdcd\">></a>\n* 修复智慧树新形态课程（智慧课程）某些外链无法完成并跳过的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/d9bbd501049a23605e94ea899618207734341a11\">></a>\n* 优化超星非整卷预览考试流畅度，添加搜索结果对于部分答页面题过程可控的菜单 <a href=\"https://github.com/ocsjs/ocsjs/commit/f1f9082c557007b86e22312f450b7530012fef5d\">></a>\n\n\n### ✨ 更新内容\n\n* 支持搜索结果界面可控制答题进程功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/98573b5d00817040f859dc20fb337ed5e590687d\">></a>\n* 支持中国大学MOOC自动考试功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/40fbfdd763496176fc65b83280b351ac4a741052\">></a>\n\n\n### ⚡ 优化提升\n\n* 添加超星凌晨刷课文案提示 <a href=\"https://github.com/ocsjs/ocsjs/commit/f17992c778a14f67e652f2a9faefde6e55c42f87\">></a>\n* 添加多个答题页面菜单自动注册功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/b10142401d8bb3a8874d6322f017881be7035d0f\">></a>\n\n\n\n## 4.11.51 (2025-11-05)\n\n\n### 🔧 修复内容\n\n* 兼容搜题结果格式A#B#C#D解析为多选题 <a href=\"https://github.com/ocsjs/ocsjs/commit/0d29813370b06580e37bce5bb68b659618e38747\">></a>\n\n\n### ✨ 更新内容\n\n* 兼容职教云在线课程内容单独任务点连续学习功能，添加PPT翻阅速度调整选项 <a href=\"https://github.com/ocsjs/ocsjs/commit/9883897d39c8d10729c65084b13aff58711aee9e\">></a>\n\n\n### ⚡ 优化提升\n\n* 添加智慧树共享课作业开启前阅读须知功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/7e7657b4ec91f43eb4f91a2b1cae5df64e98d14d\">></a>\n\n\n\n## 4.11.47 (2025-11-05)\n\n\n### 🔧 修复内容\n\n* 优化智慧树卡巴斯基文案 <a href=\"https://github.com/ocsjs/ocsjs/commit/99253d643be17f4e73f5718c410d1da1b8a21e1b\">></a>\n* 修复窗口反复闪烁的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/217b5e103b0c01eb7f89e12a343847430ff687fc\">></a>\n* 修复中国大学MOOC无法完成富文本任务的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/5dc3c0674290b3dd40a2e6a339b6d1574dbe644a\">></a>\n* 持续修复超星跳转未完成任务点模式无法使用的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/66a74a234ac4fd3810fed08eda99d10e7412d4f7\">></a>\n* 兼容职教云资源库新版PPT <a href=\"https://github.com/ocsjs/ocsjs/commit/4919ceff58eef18336c8cc8c18bbf900cdd6cc23\">></a>\n* 兼容智慧树-AI助教课程PPT和文档功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/7c456edb9149b2705d3e207740a80467225560f3\">></a>\n* 修复超星视频暂停后长时间才播放的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/a26c03b9cf54c2eb3a495ec9eec7343c623095c4\">></a>\n* 修复超星跳转未完成任务点模式无法使用的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/a1ed883152c406b2e041f96f1e6398ae37a8e87d\">></a>\n* 修复智慧树-AI助教倍速无法点击的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/8ffa983d23b55a21b66636789cba91a6a557d7ce\">></a>\n* 修复智慧树-AI助教课程无法自动跳转小节和PPT的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/fe76e0e1a08d418cd532e5ba31f5bf3810413872\">></a>\n* 优化多选题纯答案无法解析的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/97d8655ec656c6838f45a13ed0000be988862264\">></a>\n* 优化智慧树音量开始播放后没有立即调整的问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/eff3c71b9a1e5b0f1ae4b831413ddd983145dc84\">></a>\n\n\n### ✨ 更新内容\n\n* 软件配置同步后添加脚本双击配置页面强制取消功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/99948e9c180fd93b247af8de910cb1104986ee19\">></a>\n* 添加超星下一章切换后自动滚动到该元素的功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/c5c0726075d52be3e1a8f5273d1e50a97df16566\">></a>\n* 添加超星页面加载后自动滚动到当前任务点的功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/16232dfe8270224141068489bf5fe51c8a31d23c\">></a>\n* 添加超星章节测试答题时自动显示搜索结果 <a href=\"https://github.com/ocsjs/ocsjs/commit/967c495023b7671eee1581fae89a3795fb820568\">></a>\n* 添加全局快捷菜单自动注册功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/11403f67c80e9d033e868485cff37e1517f907cd\">></a>\n* 添加最小化/切屏窗口警告功能，优化文案 <a href=\"https://github.com/ocsjs/ocsjs/commit/22028e470448b62ce6d305117831ab2a04ca7ccf\">></a>\n* 优化超星章节测试答题时自动打开搜索结果功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/7cc0046a1a8d4f9caa7a03bee37f362cc42e52fa\">></a>\n* 支持中国大学MOOC视频内弹窗答题 <a href=\"https://github.com/ocsjs/ocsjs/commit/8af7ec24affd9ea2db3410ee7774d4308df675f7\">></a>\n\n\n### ⚡ 优化提升\n\n* 降低超星倍速警告阈值为2倍速，优化倍速警告 <a href=\"https://github.com/ocsjs/ocsjs/commit/5aababba9a73ac860fe23147cd1c5a6f45d6a62b\">></a>\n* 添加智慧树作业禁止同时打开多个界面答题文案 <a href=\"https://github.com/ocsjs/ocsjs/commit/f1cd8cbbc32491d3d80ef386dadd109c19d67f70\">></a>\n* 修改搜题超时默认时间为2分钟，最大值为3分钟 <a href=\"https://github.com/ocsjs/ocsjs/commit/a2a662f339352672fc2a825912f42d1c150b455b\">></a>\n\n\n\n## 4.11.22 (2025-10-16)\n\n\n### 🔧 修复内容\n\n* 兼容2025下半年超星PPT新版无法完成的问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/cbe876ef653ab24108b6dc471c2a6cd3231c3537\">></a>\n\n\n\n## 4.11.21 (2025-10-10)\n\n\n### 🔧 修复内容\n\n* 优化软件同步功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/2c1ecfc59be7f5fb1a212a9e271eb619b90ac383\">></a>\n\n\n\n## 4.11.20 (2025-10-07)\n\n\n### 🔧 修复内容\n\n* 修复软件开启同步后，其他非自动化辅助网页里面的脚本依然会同步的问题，造成用户以为当前页面可以使用软件辅助的错觉 <a href=\"https://github.com/ocsjs/ocsjs/commit/65920e232a7ca9eaae7c163eea2254d350357c8b\">></a>\n\n\n\n## 4.11.19 (2025-09-26)\n\n\n### 🔧 修复内容\n\n* 修复题库请求时无法传递类型为0或者false的值 <a href=\"https://github.com/ocsjs/ocsjs/commit/6c8a26f62c0bd31e3e7c22fe88bc689a420b508d\">></a>\n\n\n\n## 4.11.18 (2025-09-25)\n\n\n### 🔧 修复内容\n\n* 修复上个版本智慧树会长时间不播放的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/aa7c66f05a31d72223331efada8dec5d29fe88cc\">></a>\n\n\n\n## 4.11.17 (2025-09-25)\n\n\n### 🔧 修复内容\n\n* 兼容超星整卷预览禁用后的作业和答题 <a href=\"https://github.com/ocsjs/ocsjs/commit/07139d753584449950e4d8fbc79657fedca92355\">></a>\n* 修复搜索结果AI标识重复出现的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/2857f2ff14267eff62b4de5be75b6b7948c4eab3\">></a>\n* 优化旧版智慧树视频容易黑屏的问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/e2535ad3b92241793a14e87ed59fe133aca9b27c\">></a>\n* 智慧树答题时移动页面至左上角防止点击出错 <a href=\"https://github.com/ocsjs/ocsjs/commit/7f835c0b49e09c617617c1c6919ec00a8f83beac\">></a>\n\n\n### ✨ 更新内容\n\n* 添加对智慧树2025-9月份教学空间-AI智慧学习支持 <a href=\"https://github.com/ocsjs/ocsjs/commit/da9d229843bf2bf858a711ddfb3327a99bb6091f\">></a>\n\n\n### ⚡ 优化提升\n\n* 修复中国大学MOOC无法提交讨论的BUG，优化中国大学MOOC日志显示 <a href=\"https://github.com/ocsjs/ocsjs/commit/5b1ab57984bcbea691331a6d697a06b68f6bee96\">></a>\n* 优化题库配置文案和第三方连接文案 <a href=\"https://github.com/ocsjs/ocsjs/commit/e66d3991099fa0d7d245c1cf9757a84ccff80b13\">></a>\n* 优化智慧树学习提示文案 <a href=\"https://github.com/ocsjs/ocsjs/commit/5bc0545e576120c260809fd3af9e0c4b6dc9b667\">></a>\n\n\n\n## 4.11.8 (2025-09-15)\n\n\n### ✨ 更新内容\n\n* - 添加搜题结果AI标注显示 <a href=\"https://github.com/ocsjs/ocsjs/commit/7783cee4d1898ab1029189a8dd112f70ad45ed5a\">></a>\n\n\n\n## 4.11.5 (2025-09-09)\n\n\n### 🔧 修复内容\n\n* 尝试修复Git打包错误的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/30cbbbc8726b18c6508a18ef75b500206e8e4647\">></a>\n\n\n\n## 4.11.4 (2025-09-09)\n\n\n### 🔧 修复内容\n\n* 尝试修复Git打包错误的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/abb6e7b4f919de3930186da9927827513a4f3390\">></a>\n\n\n\n## 4.11.3 (2025-09-09)\n\n\n### 🔧 修复内容\n\n* 对全部项目的依赖进行版本锁定 <a href=\"https://github.com/ocsjs/ocsjs/commit/f760f6d9ff78c3c8366b4c5b2eebb0b957abba19\">></a>\n\n\n\n## 4.11.2 (2025-09-09)\n\n\n### 🔧 修复内容\n\n* 优化代码 <a href=\"https://github.com/ocsjs/ocsjs/commit/bdbcb472fed208246333d35cc375a955c631023b\">></a>\n\n\n\n## 4.11.1 (2025-09-09)\n\n\n### 🔧 修复内容\n\n* 优化代码 <a href=\"https://github.com/ocsjs/ocsjs/commit/c08eece1984c6c3f6eda8e086e18b7150e46d3a9\">></a>\n\n\n\n# 4.11.0 (2025-09-09)\n\n\n### 🔧 修复内容\n\n* 优化Playwright软件辅助底层对接，添加软件辅助点击可视化，修复元素滚动后点击错位的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/e5daaaef9fc088cb6ac419ca95fedc03924da264\">></a>\n\n\n### ✨ 更新内容\n\n* 添加超星对 jxgmxy.com 域名的支持 <a href=\"https://github.com/ocsjs/ocsjs/commit/b75b714eeb580d5fafc5e04004bab58cc87bbb70\">></a>\n* 新增智慧树2025-9月份最新课程支持-新智慧课程 <a href=\"https://github.com/ocsjs/ocsjs/commit/4f3780a6af864ad4f7fb6c1ca435c9dce4b87c09\">></a>\n\n\n\n# 4.10.0 (2025-09-01)\n\n\n### ✨ 更新内容\n\n* 添加对2025-8月份智慧树新掌握度页面进行适配 <a href=\"https://github.com/ocsjs/ocsjs/commit/07cab10e6593611680681983b9de12c4101f07de\">></a>\n* 新增智慧职教AI课程学习和作业脚本 <a href=\"https://github.com/ocsjs/ocsjs/commit/27ec1fbb33772294e07e72f9e3c16b7d3ee6f629\">></a>\n\n\n\n## 4.9.86 (2025-06-08)\n\n\n### 🔧 修复内容\n\n* 添加职教云卡死提示解决方法 <a href=\"https://github.com/ocsjs/ocsjs/commit/05493be36f61db90f1fa1d007dc8ef4b63f38a71\">></a>\n\n\n\n## 4.9.85 (2025-06-08)\n\n\n### 🔧 修复内容\n\n* 修复职教云获取数据错误的问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/55657994c8716647ae62f3ce510ba9f91747665d\">></a>\n\n\n\n## 4.9.84 (2025-06-07)\n\n\n### 🔧 修复内容\n\n* 修复智慧职教自定义全局倍速后导致其他脚本倍速选项也被修改的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/5877a6cd7a68589909544b5b04d197de694adadd\">></a>\n\n\n\n## 4.9.83 (2025-06-07)\n\n\n### 🔧 修复内容\n\n* 修改脚本更新链接到教程官网的更新教程 <a href=\"https://github.com/ocsjs/ocsjs/commit/dd24c4091f93a34d5fe10e4542dda426e4953ffb\">></a>\n* 修复职教云卡在获取数据弹窗 <a href=\"https://github.com/ocsjs/ocsjs/commit/8acf60d90a8033dd7ba2c8ec1c7ae16b391eb737\">></a>\n\n\n\n## 4.9.81 (2025-05-29)\n\n\n### 🔧 修复内容\n\n* 兼容职教云添加章节层级名字后无法获取数据的问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/afa37cf4ed47da77a2cc2711b240b449c69f7a2a\">></a>\n* 添加智慧树智慧课程跳转模式选择 <a href=\"https://github.com/ocsjs/ocsjs/commit/822ab995421c1abe1b6997f5a8da3d807a5de3cd\">></a>\n\n\n\n## 4.9.79 (2025-05-24)\n\n\n### 🔧 修复内容\n\n* 修复职教云章节列表前面有章节序号时无法读取数据的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/6ffd21e42c0c87bddd5d54c396465647985bb6aa\">></a>\n\n\n\n## 4.9.78 (2025-05-18)\n\n\n### 🔧 修复内容\n\n* 修复职教云获取数据时跳转其他网页的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/4237698017e7d1bc03a3f91d91c2ad0f0280e9bb\">></a>\n\n\n\n## 4.9.77 (2025-05-11)\n\n\n### 🔧 修复内容\n\n* 修复职教云0资源库无法下一章的BUG，兼容职教云部分PPT任务点 <a href=\"https://github.com/ocsjs/ocsjs/commit/69230c4fc73af9f5e23ae908a66eeb58581d6063\">></a>\n\n\n\n## 4.9.76 (2025-05-09)\n\n\n### 🔧 修复内容\n\n* 优化智慧树翻转课（校内课） <a href=\"https://github.com/ocsjs/ocsjs/commit/af04c36824516f342152f96559af31101745187b\">></a>\n\n\n### ✨ 更新内容\n\n* 优化支持职教云最新版页面，支持自动切换下一章（包含大章节切换） <a href=\"https://github.com/ocsjs/ocsjs/commit/c73a1ccb2e66b99c4f9cb6b93efea7c1940cfc81\">></a>\n\n\n### ⚡ 优化提升\n\n* 持续优化软件辅助安装提示 <a href=\"https://github.com/ocsjs/ocsjs/commit/90d9cd13d7cce7fccdb5b385d2dca98ab912591b\">></a>\n* 优化倍速设置选项，减少不必要的选项数量 <a href=\"https://github.com/ocsjs/ocsjs/commit/566ff845cc04dc45f8a8bcb47bcb88de274b4690\">></a>\n* 优化超星随机答题文案，提示只有在搜不到答案的时候才使用随机答题 <a href=\"https://github.com/ocsjs/ocsjs/commit/3e07067f6d86d35c99c0359fc202152905468109\">></a>\n\n\n\n## 4.9.71 (2025-05-07)\n\n\n### 🔧 修复内容\n\n* 修复新版智慧树无法关闭弹窗的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/7b356d3b79f4e44111283e5579ae25d2a2bb3cc3\">></a>\n\n\n\n## 4.9.70 (2025-05-05)\n\n\n### 🔧 修复内容\n\n* 修复超星搜题时无法读取选项内图片链接的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/d57cb8fbd7fd191bad3deb05b988d01651f67f28\">></a>\n* 修复超星章节测试多行题目第二行存在题目类型时无法解析题目的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/3cdfa3c8ccd1c25053c83a252a0b973c10c608a5\">></a>\n* 修复超星自动答题时填空题选项读取到额外冗余文字的BUG：（段落格式，字号，字体） <a href=\"https://github.com/ocsjs/ocsjs/commit/f95f2ea4d2e9f69f8053a2e18ab1d0b2e7cd7442\">></a>\n* 修复智慧树-智慧课程无法自动切换下一个的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/b02d864eacd667428da73a6db68d810e55e4f39d\">></a>\n* 修复智慧树-智慧课程无法自动下一章的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/48b856794b46ab64db102a4a1563cce3e21593ea\">></a>\n* 修复智慧树-智慧职教无法学习文本任务点的问题，新增智慧职教已完成任务点过滤功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/aaf7774215a703569ea80458f10937c4d9e1c330\">></a>\n* 优化智慧树-智慧课程播放逻辑 <a href=\"https://github.com/ocsjs/ocsjs/commit/8ea5248851a61b1355b49d9ca26f6d571b8688d2\">></a>\n\n\n### ✨ 更新内容\n\n* 对题库配置做唯一化处理 <a href=\"https://github.com/ocsjs/ocsjs/commit/4bc011222e16723e6cdcbb123789891a6a0d80fc\">></a>\n* 适配智慧树智慧课程学习和作业（包括AI助教和智慧课程），适配智慧树新版课程页面，适配智慧课程掌握度自动答题 <a href=\"https://github.com/ocsjs/ocsjs/commit/7cd9dfd28f02b1316283b4b331525939ec546fa7\">></a>\n* 支持智慧树智慧课程链接任务点 <a href=\"https://github.com/ocsjs/ocsjs/commit/285140ff3c73aae41ffa7213d3daf3ca76e4c72a\">></a>\n\n\n### ⚡ 优化提升\n\n* 修改软件辅助启动提示 <a href=\"https://github.com/ocsjs/ocsjs/commit/0021b5dd85fd5dd9b0f45dfd94175a7c3b34dd68\">></a>\n\n\n\n## 4.9.59 (2025-03-31)\n\n\n### 🔧 修复内容\n\n* typo <a href=\"https://github.com/ocsjs/ocsjs/commit/57d4077bb0951648fba6fe4a9f30cee7a4567a05\">></a>\n\n\n### ✨ 更新内容\n\n* 添加超星 ccqmxx.com 域名适配支持 <a href=\"https://github.com/ocsjs/ocsjs/commit/79eb94744e442a73022d59d1dca67b13035f30aa\">></a>\n* 添加题库配置相同检测，如果相同则提示 <a href=\"https://github.com/ocsjs/ocsjs/commit/9acb07e0bf5fbf6cf0d6d02f47fdce7a21e4a235\">></a>\n\n\n\n## 4.9.57 (2025-03-14)\n\n\n### ✨ 更新内容\n\n* 添加智慧树-智慧课程掌握度答题功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/316eb6d6b6cd4d3fe98fa13be6833779fb77fdf2\">></a>\n\n\n### ⚡ 优化提升\n\n* 添加智慧树-智慧课程掌握度学习提示 <a href=\"https://github.com/ocsjs/ocsjs/commit/908df793965358d19c5cf87b792a7f47f2446a7d\">></a>\n\n\n\n## 4.9.55 (2025-03-14)\n\n\n### 🔧 修复内容\n\n* 超星任务点检测优化 <a href=\"https://github.com/ocsjs/ocsjs/commit/ce6c3ac829f21b075592d0ad7cea7d670c7e132c\">></a>\n\n\n\n## 4.9.54 (2025-03-13)\n\n\n### 🔧 修复内容\n\n* 优化智慧树智慧课程视频检测，修复智慧树智慧课程检测不到任务点问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/2f3777d4d072da3163ed21cb630488f3fd89ed61\">></a>\n\n\n\n## 4.9.52 (2025-03-12)\n\n\n### 🔧 修复内容\n\n* 修复 4.9.51 版本题库配置解析问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/b3d3e32e8b61f1bef7e7b94d631eccd47838a6dc\">></a>\n\n\n\n\n\n\n\n## 4.9.46 (2024-12-22)\n\n\n### ⚡ 优化提升\n\n* 优化智慧树考试提示文案 <a href=\"https://github.com/ocsjs/ocsjs/commit/b1e92a31b0a667d7f3cc1ae9cf462ba0277dcc2b\">></a>\n\n\n\n## 4.9.45 (2024-12-21)\n\n\n### 🔧 修复内容\n\n* 修复上个版本图片链接处理导致的无法选择BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/baaed067f08f164821019c7b71287305f842b4d9\">></a>\n\n\n\n## 4.9.44 (2024-12-21)\n\n\n### 🔧 修复内容\n\n* 修复图片题解析错误导致的URL重复，图片重复显示等问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/f5cc59f8e6a87a7144d0540f64a4a586594c78ef\">></a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n## 4.9.40 (2024-11-10)\n\n\n### 🔧 修复内容\n\n* 优化职教云资源库无法答题的问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/c94c41eb46ace8fa63d0296d8199941a58a46314\">></a>\n\n\n\n## 4.9.39 (2024-11-09)\n\n\n### 🔧 修复内容\n\n* 修复 select 选择框显示问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/9f5b741147cb100848517ebd4d7d131159a3689e\">></a>\n\n\n\n## 4.9.37 (2024-11-06)\n\n\n### ✨ 更新内容\n\n* 添加超星自动跳转未完成任务点功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/24927e63e765d990ef6f0971b40121f8507463fd\">></a>\n\n\n\n## 4.9.36 (2024-11-06)\n\n\n### 🔧 修复内容\n\n* 修复选择输入框中默认值匹配错误导致的超星自动保存选择'save'，但是默认值依旧是80的问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/a2b639fd5c22ef94a5d352cf6a62d79af6261ac7\">></a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n## 4.9.29 (2024-08-10)\n\n\n### 🔧 修复内容\n\n* 修复资源库最新版链接类名改变问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/341b37f42c815fc7e401e23636a2388ab592cbe5\">></a>\n\n\n\n## 4.9.28 (2024-07-14)\n\n\n### 🔧 修复内容\n\n* 解决超星部分套壳网站无法自动下一章的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/e8f05b6b8741d88903755910d072020fcf6a695a\">></a>\n* 优化对超星视频下载错误的处理，将自动跳过错误视频 <a href=\"https://github.com/ocsjs/ocsjs/commit/b4f32c8cccf9a7d97107d2570b2d36e3e187809f\">></a>\n\n\n### ⚡ 优化提升\n\n* 优化版本更新验证，不符合版本书写规范的话向用户提示报错 <a href=\"https://github.com/ocsjs/ocsjs/commit/70dd13b5c5cf58ccaf09cdc513c31985445023fe\">></a>\n\n\n\n## 4.9.25 (2024-07-13)\n\n\n### 🔧 修复内容\n\n* 新增智慧职教 webtrn.cn 套壳网站支持，新增对30分钟学习弹窗的关闭功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/b17a19f0c87e94579d2abba09afdadd98bce8e7b\">></a>\n\n\n\n\n\n\n\n## 4.9.23 (2024-06-25)\n\n\n### 🔧 修复内容\n\n* 修复职教云资源库，课件类型为NAN的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/8b8768e311b41717cb7df9d73417cf2a700c107a\">></a>\n* 修复职教云资源库重复下一章的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/b566a5ccd66b285b994f57f05dc6e454b21ed059\">></a>\n\n\n\n## 4.9.21 (2024-06-19)\n\n\n### 🔧 修复内容\n\n* 修复题库配置 AnswererWrapper 自定义字段解析 handler 函数调用两次的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/1e15377e0ed7b51f9777221607fa047800e7f765\">></a>\n* 修复搜索时换行被删除导致，两行合并造成的文字合并，导致搜索引擎可能出现的的搜索问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/b318c1e227ebc0fe06c567139e1a09f307c106ec\">></a>\n\n\n\n## 4.9.20 (2024-06-13)\n\n\n### ✨ 更新内容\n\n* 新增 中国大学MOOC 考试功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/7286fa47105a1ab8e06f7b4cca1c71354e59d015\">></a>\n\n\n\n## 4.9.19 (2024-06-05)\n\n\n### 🔧 修复内容\n\n* 修复使用精确匹配时，脚本的多选题搜索结果会全部显示已完成的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/65cfc4832bd1bb070169b02ba7672f3aa347c494\">></a>\n* 添加完成率百分比符号 <a href=\"https://github.com/ocsjs/ocsjs/commit/beec3707cf1de48685a7cbb899d70b0e0003ce98\">></a>\n\n\n\n## 4.9.18 (2024-06-04)\n\n\n### 🔧 修复内容\n\n* 修复全域名版本无法显示BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/6d52ec4983ce452fc072774abc358936380f4166\">></a>\n\n\n\n## 4.9.17 (2024-05-24)\n\n\n### 🔧 修复内容\n\n* 修复上个版本任务点检测不到的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/8bf7551b41cd543bd3f345ec63320c8fa661a4e6\">></a>\n\n\n\n## 4.9.16 (2024-05-23)\n\n\n### 🔧 修复内容\n\n* 优化超星人脸识别检测警告 <a href=\"https://github.com/ocsjs/ocsjs/commit/311d8adaf8c46eb66bb6373dae645ceb8856a70b\">></a>\n* 优化超星题库配置为空警告 <a href=\"https://github.com/ocsjs/ocsjs/commit/92577708f5737b445c986090470d78bd58d27999\">></a>\n\n\n### ✨ 更新内容\n\n* 新增对超星链接任务点，音频PPT任务点的支持，优化代码，删除视频错误检测功能。 <a href=\"https://github.com/ocsjs/ocsjs/commit/e90f1bbc3f13b966b020303e0498bd73e65844fd\">></a>\n\n\n\n## 4.9.12 (2024-05-11)\n\n\n### 🔧 修复内容\n\n* 添加超星积分课的使用提示 <a href=\"https://github.com/ocsjs/ocsjs/commit/ea7456324d60d41df699946207a6b1b73bf7b516\">></a>\n* 修复超星课程，章节测试偶尔出现卡死的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/24b499d36c143810a49d6bb000e7d345317ac131\">></a>\n* 修复超星课程进入时需要人脸识别，但是疯狂跳转新版本导致的人脸识别疯狂刷新的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/4fb764bd53411006c435a591167cb48bb0ec4a74\">></a>\n* 修复超星英语判断题的文本BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/5c785ab3828897604d861fb521a2b17c9afbd4b4\">></a>\n\n\n\n\n\n\n\n## 4.9.7 (2024-05-10)\n\n\n### 🔧 修复内容\n\n* 修复 '\"@ocsjs/core\"' has no exported member 'createQuestionTitleExtra'. BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/9f06abee6747192c9fbe4b29ae82003e56481734\">></a>\n\n\n\n## 4.9.6 (2024-05-10)\n\n\n### 🔧 修复内容\n\n* 更新 easy-us API <a href=\"https://github.com/ocsjs/ocsjs/commit/0150b49ca661920a6a9c723a37af237c53d62b95\">></a>\n* 删除无用API <a href=\"https://github.com/ocsjs/ocsjs/commit/8c8425f337c7f001bbdc3bb74ef8e3849c413e65\">></a>\n* 修复英语前缀 【True or False】 的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/01e141884cdd4b9ef65a6c489286aacc99cb2198\">></a>\n* 优化题库配置的开启和关闭按钮 <a href=\"https://github.com/ocsjs/ocsjs/commit/b9197c6eab4558061d8167b4fe7d5b1098bf83a4\">></a>\n* 优化题库配置的开启和关闭按钮 <a href=\"https://github.com/ocsjs/ocsjs/commit/39b304671fb2991d7fbeac2ee1fabb757ebc50c4\">></a>\n\n\n\n## 4.9.4 (2024-05-04)\n\n\n### 🔧 修复内容\n\n* 修复无法移动面板至边缘导致自动登录失效的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/a05d0a1cb1e354dccbb52a567b858a4eb7276e63\">></a>\n\n\n\n\n\n\n\n## 4.9.2 (2024-05-04)\n\n\n### 🔧 修复内容\n\n* 导出 $elelment 对象，便于可修改 $modal 第二个参数 <a href=\"https://github.com/ocsjs/ocsjs/commit/13a1b6d9d8e211e9b92558b950fbb0e96b0d6174\">></a>\n\n\n\n\n\n\n\n# 4.9.0 (2024-05-03)\n\n\n### ✨ 更新内容\n\n* 优化API，分离UI层为新项目 easy-us <a href=\"https://github.com/ocsjs/ocsjs/commit/ecc4ff6a56577d40a59aea47418138ad9b6c04f1\">></a>\n\n\n\n## 4.8.30 (2024-05-01)\n\n\n### 🔧 修复内容\n\n* 解决超星视频播放时出现： The play() request was interrupted by a call to pause() BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/4791bd8c4a8f06890079bf748c0f547fd728ec94\">></a>\n\n\n### ✨ 更新内容\n\n* 新增对职教云资源库的 spocjob 作业的支持 <a href=\"https://github.com/ocsjs/ocsjs/commit/007d39af4fbfb474e47f180b8670479c0d7e9b4b\">></a>\n\n\n\n\n\n\n\n\n\n\n\n## 4.8.26 (2024-03-29)\n\n\n### ✨ 更新内容\n\n* 新增超星 cugbonline.cn 域名支持 <a href=\"https://github.com/ocsjs/ocsjs/commit/359425a92b93fc7fd93a3d55b69526d29db38d22\">></a>\n\n\n\n## 4.8.25 (2024-03-28)\n\n\n### 🔧 修复内容\n\n* 修复添加额外菜单栏后，窗口无法正常最大化/最小化的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/22c45bc15d31ab1999761182d6ddbffec499e0ef\">></a>\n\n\n\n## 4.8.24 (2024-03-28)\n\n\n### 🔧 修复内容\n\n* 修复窗口隐藏/显示 快捷键无效的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/8002c8246339f936cfbafc677f9c053a6e0cfcbc\">></a>\n* 修复单选题答案回调内容不一致的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/4265263821e781cd3636c275120f8b987c63bbbf\">></a>\n* 修复设置超出最大最小值，导致变更时，本地的值没有同步导致刷新后依然没有发生变化的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/0b05507875340c57689b5825f2d5efd8a4e6ebef\">></a>\n* 修复新版答题算法中搜题线程参数无效的BUG，始终只有一个搜题线程在运行 <a href=\"https://github.com/ocsjs/ocsjs/commit/9e052f85909b947f8fd53851233cdee93c47ca9a\">></a>\n* 修复已经在暂停状态，但是依然在答题的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/3f04e21fdaef3ee5972aa259399e5384b11f51a7\">></a>\n* 优化搜索结果的显示BUG，已搜到答案的题目显示未匹配到正确答案的BUG，以及答题进度不一致的显示BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/2fd9a72fb493bfe26593055346bcfe089feda8b9\">></a>\n* 全面优化答题速度，优化存在题库缓存时无需执行搜题间隔等待，优化特定网课勾选答案时需要停顿，正常网课无需停顿的情况，极大加速了答案勾选过程。 <a href=\"https://github.com/ocsjs/ocsjs/commit/1ca34480d7010c176f79d471f2ba264716782561\">></a>\n* 修复智慧树答题时题库缓存未正确存储的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/978d927f71b1f7e764d97257206615b0f7719791\">></a>\n* 修复重新答题按钮点击后，答题完成后的一些操作依然在进行的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/e7148a368144ab55b6708c4e03d23f7de6262b8b\">></a>\n\n\n### ✨ 更新内容\n\n* 新增额外菜单栏功能，新增超星额外菜单栏快捷页面跳转按钮 <a href=\"https://github.com/ocsjs/ocsjs/commit/c2066a056439270f41a2f8cb0eee61a73690717e\">></a>\n* 新增【职教云】资源库作业支持 <a href=\"https://github.com/ocsjs/ocsjs/commit/3cf996824d1ff647499b3f8a12e6084eaf7ddf2d\">></a>\n\n\n\n## 4.8.13 (2024-03-14)\n\n\n### 🔧 修复内容\n\n* 全局设置中部分元素的值为空时恢复默认值 <a href=\"https://github.com/ocsjs/ocsjs/commit/9d539d045df7db40035cf00eba157f10b389e623\">></a>\n* 优化答题模块算法 <a href=\"https://github.com/ocsjs/ocsjs/commit/545d9bc5c98ff1b342789de9ec681e2e34efed0e\">></a>\n\n\n\n## 4.8.11 (2024-03-13)\n\n\n### 🔧 修复内容\n\n* 修复请求记录开启后无法读取内容的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/31d2a854e9ccbbef22fa1ad0ca0256f4fe493561\">></a>\n\n\n### ✨ 更新内容\n\n* 对 TikuAdapter 做适配提示 <a href=\"https://github.com/ocsjs/ocsjs/commit/cf2fe109faf5990086de68fb1f7213f0d4807500\">></a>\n\n\n\n## 4.8.8 (2024-03-05)\n\n\n### 🔧 修复内容\n\n* 优化超星视频重复播放时没有重置视频进度的问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/c167ff094ffe4287002cd3e1f0b08b92a0a82ceb\">></a>\n\n\n\n## 4.8.7 (2024-03-01)\n\n\n### 🔧 修复内容\n\n* 修复2倍速被删除的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/a861685bea01f3bedad32c2828f2e2d3f38f94dd\">></a>\n\n\n\n## 4.8.6 (2024-02-28)\n\n\n### 🔧 修复内容\n\n* 修改软件辅助返回字段 <a href=\"https://github.com/ocsjs/ocsjs/commit/270f104d4b490c55e2b6fc5d0b58d4cf5518aa80\">></a>\n\n\n\n## 4.8.5 (2024-02-28)\n\n\n### 🔧 修复内容\n\n* 修复智慧树无法使用软件辅助，无法自动答题看视频的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/a3cdb6f13904879e0d3effd3874c567f34f468f2\">></a>\n\n\n### ✨ 更新内容\n\n* 新增答案匹配模式选项 <a href=\"https://github.com/ocsjs/ocsjs/commit/aab04af086a50aa72d2992087ecba69f60ad1447\">></a>\n* 添加题库配置自动读取剪贴板功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/fe19557c7cc91c7909a62c3b1dc93aac5c641106\">></a>\n\n\n### ⚡ 优化提升\n\n* 优化 tooltip <a href=\"https://github.com/ocsjs/ocsjs/commit/96554423807d329e712b08998798d3ca5a309e7f\">></a>\n\n\n\n# 4.8.0 (2024-02-26)\n\n\n### 🔧 修复内容\n\n* 新答题器的旧字段搜索结果显示修改 <a href=\"https://github.com/ocsjs/ocsjs/commit/d8511a023f87d4e8c10c4aabacf4d5118ac75177\">></a>\n* 修复当数字输入框是空格后无法正确读取的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/fa7f2d593fb42897352958d5a48c78a147671af1\">></a>\n* 修复新版答题器当线程为1时，无法搜题的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/bd2bd5384e788f4f1c521e766a1f9c25c90b96a7\">></a>\n* 优化答题器算法 <a href=\"https://github.com/ocsjs/ocsjs/commit/d29c74c2ab2a470630567fad9a47605baa3e2d99\">></a>\n* 优化跨域通信只在顶部页面刷新时删除临时监听变量，优化代码添加注释 <a href=\"https://github.com/ocsjs/ocsjs/commit/0f07dc47500f8b527f1612731c2cf0d5fe8e36aa\">></a>\n* 优化搜索结果显示，内部算法报错依然可以显示搜索结果 <a href=\"https://github.com/ocsjs/ocsjs/commit/e002e3d45dd4609d9401b9534ab8be396e979a4a\">></a>\n* 添加超星英文判断题的冗余删除 <a href=\"https://github.com/ocsjs/ocsjs/commit/35b8f07990d365ee239056e2281c0390200809f5\">></a>\n* 添加题库缓存开关 <a href=\"https://github.com/ocsjs/ocsjs/commit/35226d34298fc0b39b773f612f99f39d8169b4dd\">></a>\n* 修复解除复制粘贴限制功能无效的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/3f560a83bd472c073c7de91e51720b78119fdf06\">></a>\n* 修复通知时间很短就消失的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/cda009f4679fb7cfdce121f2379b68c0d46008b1\">></a>\n* 优化超星闯关模式以及解锁模式的重复进入检测算法 <a href=\"https://github.com/ocsjs/ocsjs/commit/b14823248aa4b5620756a4525d56a5f5aacbc7c8\">></a>\n* 优化超星英文判断题的解析 <a href=\"https://github.com/ocsjs/ocsjs/commit/78be04e3dfb76fda8d5cbe5d850a0131a7d1bb08\">></a>\n\n\n### ✨ 更新内容\n\n* 新增 answer_separator 答案分隔符设置 <a href=\"https://github.com/ocsjs/ocsjs/commit/a742f808f43c9e6d9be010258b7d36cde83caf0e\">></a>\n* 添加超星 gdhkmooc.com 域名支持 <a href=\"https://github.com/ocsjs/ocsjs/commit/c63e6d69e903483754895c4b14e2266f07788ce5\">></a>\n* 添加超星完成全部任务点后重新从开头学习功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/86454d31f227940298c866759e071622c76097bb\">></a>\n* 添加对智慧树自动保存答案的解释 <a href=\"https://github.com/ocsjs/ocsjs/commit/53f0846b3134453cf8d9bf7c11d777407e7a111a\">></a>\n* 添加开发人员调试：显示Tab变量功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/a779b35f5e422d30a81d551db1ba84528b052d74\">></a>\n* 添加职教云 flv 格式支持 <a href=\"https://github.com/ocsjs/ocsjs/commit/08d6719695d6ca293cb3333197eaf4981195678f\">></a>\n* 添加职教云的作业考试【客观填空题】的支持 <a href=\"https://github.com/ocsjs/ocsjs/commit/92c51332130b31a2e1ece5deabc023a90fde57c3\">></a>\n* 添加智慧职教MOOC的单行填空题支持 <a href=\"https://github.com/ocsjs/ocsjs/commit/5361ec40a59d0cdb399324dcec484468b5e8d639\">></a>\n* 新增 $message 跨域调用功能，新增 $modal duration 参数，修复由于删除cors监听key导致的 undefined BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/320903fdd9e1b82d47ae9ab475aae0eac0d0e2bc\">></a>\n* 新增新版职教云作业填空题支持 <a href=\"https://github.com/ocsjs/ocsjs/commit/a09dcac71cda6e95a24f6ce5e22ba6c3830f0c3b\">></a>\n* 新增职教云考试支持，txt文档查看支持 <a href=\"https://github.com/ocsjs/ocsjs/commit/f95e8f84e03a45dd4afee2f517800512b112efa1\">></a>\n* 优化题库配置文案，优化超星文案，并添加运行时的反馈消息提示 <a href=\"https://github.com/ocsjs/ocsjs/commit/d61d3ee051f6c5b1f6af849987776592159d21bb\">></a>\n* 在拓展应用中添加OCS全部配置的导入导出功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/0237e88c1157201911133405058c2b9eb3a43be6\">></a>\n\n\n### ⚡ 优化提升\n\n* update remote-playwright-log <a href=\"https://github.com/ocsjs/ocsjs/commit/35787819b390d2bed230c79ad30283456a7d57b2\">></a>\n* 优化软件辅助类型提示以及代码 <a href=\"https://github.com/ocsjs/ocsjs/commit/3394ada9acb234ee987ae58ac75c2cf96eacde21\">></a>\n* 简化 $console 生成的堆栈信息，减少存储量 <a href=\"https://github.com/ocsjs/ocsjs/commit/2669525fd942fa9a2e34e8166785e36891e1c5a3\">></a>\n* 添加更多的倍速选项 <a href=\"https://github.com/ocsjs/ocsjs/commit/adf7e3dfb03838cd5dfaf3ebe1b55691685c5a09\">></a>\n\n\n\n## 4.7.46 (2023-12-20)\n\n\n### 🔧 修复内容\n\n* 优化题库配置在软件上的域名检测 <a href=\"https://github.com/ocsjs/ocsjs/commit/04fba87036a8a7884fb40f7f5ab816778a453744\">></a>\n\n\n### ⚡ 优化提升\n\n* type  update <a href=\"https://github.com/ocsjs/ocsjs/commit/3fd3864ea536038f616f2e357d1eee377a130d03\">></a>\n\n\n\n## 4.7.35 (2023-12-19)\n\n\n### ✨ 更新内容\n\n* 修复超星人脸验证时疯狂刷新的问题，新增超星人脸验证通知功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/e75786c890fc0db560a75db406195ead7ad81783\">></a>\n\n\n### ⚡ 优化提升\n\n* 添加题库停用状态的开启提示 <a href=\"https://github.com/ocsjs/ocsjs/commit/8e6aaedb13a3b3db51ffd0737ef037d628dc7033\">></a>\n\n\n\n## 4.7.34 (2023-12-13)\n\n\n### 🔧 修复内容\n\n* 优化 TikuAdapter 提示 <a href=\"https://github.com/ocsjs/ocsjs/commit/43622ba2b48ead377c03fb52ee7c2778256b8002\">></a>\n\n\n\n## 4.7.33 (2023-12-13)\n\n\n### 🔧 修复内容\n\n* 修复部分脚本管理器无法读取 [@connect](https://github.com/connect) 头部元数据的问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/69cec2f98ee5835baceb7796284d654ffbad5720\">></a>\n\n\n\n## 4.7.32 (2023-12-13)\n\n\n### 🔧 修复内容\n\n* 修复全域名通用版本不带官方域名的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/ce042af9d4deb95753a4c76b94a9d4baea7f8a2a\">></a>\n\n\n### ⚡ 优化提升\n\n* 添加 TikuAdapter 配置域名白名单提示 <a href=\"https://github.com/ocsjs/ocsjs/commit/24d802626c5cf8f763f463b1d598227454d8e1e3\">></a>\n\n\n\n## 4.7.30 (2023-12-11)\n\n\n### 🔧 修复内容\n\n* 修改TikuAdapter type 类型解析 <a href=\"https://github.com/ocsjs/ocsjs/commit/9aaeb7d5b862eb7e848f0022dc1272bd543b71be\">></a>\n\n\n\n## 4.7.29 (2023-12-11)\n\n\n### 🔧 修复内容\n\n* 添加多版本脚本的更新模块适配 <a href=\"https://github.com/ocsjs/ocsjs/commit/4a7aa19889c7d5f1a412de7e91491d6f52ace0c0\">></a>\n\n\n### ⚡ 优化提升\n\n* 添加 TikuAdapter 解析器说明 <a href=\"https://github.com/ocsjs/ocsjs/commit/d200b22212300cb5b055471a4767b13a428660f4\">></a>\n\n\n\n## 4.7.27 (2023-12-11)\n\n\n### 🔧 修复内容\n\n* 修复请求模块的重大BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/4b121bb20c66e704bf32e09906a42bb7cf8566be\">></a>\n\n\n### ✨ 更新内容\n\n* 新增 TikuAdapter 题库配置解析器，优化题库配置解析 <a href=\"https://github.com/ocsjs/ocsjs/commit/1fc0d59313c2b2fe7f296b51340f3ff5d741e32e\">></a>\n\n\n\n## 4.7.25 (2023-12-08)\n\n\n### 🔧 修复内容\n\n* 复上个版本无法输入任何文字的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/133dd69f700c2fd4bebaad37afc9fa69060038ec\">></a>\n\n\n\n## 4.7.24 (2023-12-08)\n\n\n### 🔧 修复内容\n\n* 优化对 headers 中 content-type 解析而实现传递不同的 data 数据 <a href=\"https://github.com/ocsjs/ocsjs/commit/fcd962bd27de2c9fbc5c3e57af9abd54ec6db5a3\">></a>\n* 将中国大学MOOC的页面切换信息设置为警告信息 <a href=\"https://github.com/ocsjs/ocsjs/commit/84f1d6e7881eaf886ec626952f159232d809acfc\">></a>\n\n\n### ✨ 更新内容\n\n* 添加快捷键可以显示/隐藏 窗口功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/f33e67f157d2ee6b1d8dfcfa3d783ddbc842a985\">></a>\n\n\n\n## 4.7.21 (2023-12-05)\n\n\n### 🔧 修复内容\n\n* 修复打包时不自动上传文件的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/4983788703e021d157b7562d0672848f00e04901\">></a>\n\n\n\n\n\n\n\n\n\n\n\n## 4.7.18 (2023-12-05)\n\n\n### 🔧 修复内容\n\n* 修复上个版本无法自动打包的BUG，优化打包代码 <a href=\"https://github.com/ocsjs/ocsjs/commit/74a97cd4f187f694f9b63df581a40f6c4a1382b8\">></a>\n\n\n\n## 4.7.17 (2023-12-05)\n\n\n### ✨ 更新内容\n\n* 添加全域名通用脚本打包 user.common.user.js <a href=\"https://github.com/ocsjs/ocsjs/commit/81f2cfa8e38f267f49930c1cc4dec402b8154835\">></a>\n\n\n### ⚡ 优化提升\n\n* 添加请求记录方法过滤选项 HEAD <a href=\"https://github.com/ocsjs/ocsjs/commit/63b58d5354d5b086479a9d1a63bdadf797a71e51\">></a>\n* 添加在线搜题题库配置为空的提示 <a href=\"https://github.com/ocsjs/ocsjs/commit/7caf5252f9c7c35e69e9102bf70cdd554eae8e46\">></a>\n\n\n\n## 4.7.14 (2023-12-05)\n\n\n### 🔧 修复内容\n\n* 修复 attrs 属性的 style 特殊字段不生效的BUG， 将每个文本框设置最小的宽高 <a href=\"https://github.com/ocsjs/ocsjs/commit/fdcc2caf60f761d49fd8f0d22cf8e10cdf1803cc\">></a>\n* 修复下拉选择框只有value值时不显示的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/2d5f371cca889c1b6ccf7acced557b0b9ae4f32f\">></a>\n* 修复搜索结果序号过多时，序号页面超出页面的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/89589102791225401391f466bc36f1e7d2cd9506\">></a>\n* 修复中国大学MOOC课堂测验答题完成后没有等待暂停时间步骤的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/3bc76b1ef981d1fc63f51b4270a4b8af4ec924ef\">></a>\n* 优化在线搜题功能，缓存搜索题目，优化划词功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/c4093b1b64245c07db356a64094890eae314247c\">></a>\n\n\n### ✨ 更新内容\n\n* 新增题库配置的字段解析器功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/f59648871a8648ca50a15d3d162f2e01eb5a52ef\">></a>\n* 添加超星 hnvist.cn fjlecb.cn 域名支持 <a href=\"https://github.com/ocsjs/ocsjs/commit/afd86d62425fd1140c48f764bf11102dcf1dac32\">></a>\n* 新增超星旧版学习页面自动转换新版功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/9992a03597d4954049187b5d2ccce5cfe0430f9f\">></a>\n* 新增开发人员请求记录调试页面 <a href=\"https://github.com/ocsjs/ocsjs/commit/5baac2770e03a0222b8f800789d3743c1234e125\">></a>\n\n\n### ⚡ 优化提升\n\n* request 模块的 gm http 的 data 使用 JSON.stringify 进行转换 <a href=\"https://github.com/ocsjs/ocsjs/commit/7d4f6602ecd7d0268b2e888c51a907f0a2cc4268\">></a>\n* 删除超星多余日志 <a href=\"https://github.com/ocsjs/ocsjs/commit/7be10712c36bd35c21175862eb7be915971d7b52\">></a>\n\n\n\n## 4.7.7 (2023-11-30)\n\n\n### 🔧 修复内容\n\n* 将超星提交功能设置为3秒延时 <a href=\"https://github.com/ocsjs/ocsjs/commit/2161fa461e0383278ff31b60475daa62702fdb3e\">></a>\n* 新增题目冗余字段 <a href=\"https://github.com/ocsjs/ocsjs/commit/596101ff46b5fbd85bb7725fb9b9caeab6aa4169\">></a>\n* 修复超星学习通章节测验已完成，但依然开始答题的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/169eb4aedc1e3351de6546891de9050510fe5ffc\">></a>\n* 优化超星作业考试/章节测验 中 type 传递是 undefined 的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/c2a16402a5f8b97417cb3254faeccc2929d6ee53\">></a>\n* 优化职教云-资源库：新增对 mp3 的支持，新增自动跳过测验功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/78634b4a35600d7baf48f4329e4970ce90c75b27\">></a>\n\n\n### ✨ 更新内容\n\n* 优化答题uploadHandler API，新增 commonWork.options.onWorkerCreated 选项 <a href=\"https://github.com/ocsjs/ocsjs/commit/c4154df1dbf4e2dfb94c92f2b4b83fd1c4c4d7c4\">></a>\n* 新增中国大学MOOC，随堂测验自动答题功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/f79fb08a93b928127d1e710d31ed0b0a5cf64f38\">></a>\n\n\n### ⚡ 优化提升\n\n* 导出 ICourseProject 变量，开发者调试功能中可用 <a href=\"https://github.com/ocsjs/ocsjs/commit/13d8787feafba0593f463246fb1fac4b707e1922\">></a>\n* 增加超星任务点关闭后的开启提示 <a href=\"https://github.com/ocsjs/ocsjs/commit/fe11e6ef7c1bc6360c1c8ea58cc7fe82e4552713\">></a>\n\n\n\n## 4.7.1 (2023-11-25)\n\n\n### 🔧 修复内容\n\n* 修复上个版本 职教云资源库获取课程信息失败 bug <a href=\"https://github.com/ocsjs/ocsjs/commit/d97e538601a3d015c0e66609a5397033ab643a9c\">></a>\n\n\n\n# 4.7.0 (2023-11-25)\n\n\n### 🔧 修复内容\n\n* 添加题库配置 method 大小写适配 <a href=\"https://github.com/ocsjs/ocsjs/commit/d496be7cc5253c4b7ac8ba171dbd508ba9038d2a\">></a>\n* 修复选项元素在没有 title 时显示为 undefined 的 BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/12ff0dc8c623ca0d4e38f230946d950ce18e26db\">></a>\n* 优化选择下拉框设置，优化代码，修改之前由于无法获取value而使用的 $creator.selectOptions 的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/2cf5318030eb9427811db65973efe5b36eac2692\">></a>\n* 将软件辅助的点击设置成点击元素中心点 <a href=\"https://github.com/ocsjs/ocsjs/commit/a0e421df0ceeba369bb9c64c95e0de7e21e2ff7b\">></a>\n* 添加智慧树题目类型判断，修复某些题目不自动填写的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/7bce4331ec9f504b6d887ec5e3d1443613200ce1\">></a>\n* 修复职教云更新后-资源库不自动学习的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/2d4d0269d86e525ed288d322e231a4422572e461\">></a>\n* 优化脚本字间距，搜索结果字间距和窗口间距 <a href=\"https://github.com/ocsjs/ocsjs/commit/bcc4d87acbfd6aa78dce50d81bc561992f5fe7ab\">></a>\n* 优化软件辅助错误提示 <a href=\"https://github.com/ocsjs/ocsjs/commit/7f42cbafa069d6350c74fe07a06e58d8782abe54\">></a>\n* 优化智慧树学习提示 <a href=\"https://github.com/ocsjs/ocsjs/commit/ee44189adac7b50bb2e35b9e7aca5eb1c4f73937\">></a>\n* 优化智慧职教填空题 <a href=\"https://github.com/ocsjs/ocsjs/commit/586b1fc66468d3b55d4c359b84d1a4ec36f89f73\">></a>\n\n\n### ✨ 更新内容\n\n* 添加超星学习通 视频内题目随机答题功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/e2d9d5d84a11c62d5e21a2dba54be76f3f7e3ff4\">></a>\n* 添加超星学习通-章节页面自动切换脚本 <a href=\"https://github.com/ocsjs/ocsjs/commit/c0ac30c0b60a72de097f53dcdb94cd9247bd0e60\">></a>\n* 添加职教云遇到讨论课件时自动跳过 <a href=\"https://github.com/ocsjs/ocsjs/commit/994ba3e9055581b14a2fb61aa680bb9f2327eb2c\">></a>\n* 新增网课 中国大学MOOC 的学习、作业脚本支持 <a href=\"https://github.com/ocsjs/ocsjs/commit/17445e5478f398d3580bc561eee2542804872478\">></a>\n* 新增智慧职教-学习中心自动学习功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/0a8de35bd99d74a43064a5f0213069e1d9ad80a1\">></a>\n* 优化全部网课平台的系统通知功能，添加任务点完成提示，添加智慧树验证码提示 <a href=\"https://github.com/ocsjs/ocsjs/commit/7e30125fd0858bd9c2067de0e7f3ad95bf639ff9\">></a>\n\n\n\n## 4.6.32 (2023-11-01)\n\n\n### 🔧 修复内容\n\n* 新增超星其他题，类型支持 <a href=\"https://github.com/ocsjs/ocsjs/commit/ee2cf4dfd987e9adf62f7d2767c7dff3560def53\">></a>\n\n\n### ✨ 更新内容\n\n* 添加优先级选项，排序特定脚本的执行速度 <a href=\"https://github.com/ocsjs/ocsjs/commit/049234c280835363d1e56581cbe782d8e98424e6\">></a>\n* 新增职教云资源库 pdf 支持，新增职教云作业脚本 <a href=\"https://github.com/ocsjs/ocsjs/commit/f703288641ecf794635b50922a0dab3b717f3446\">></a>\n\n\n\n\n\n\n\n## 4.6.29 (2023-10-24)\n\n\n### 🔧 修复内容\n\n* 修复软件配置同步失败问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/98eeb3de94e6a434bf84f1d64a2036624203df19\">></a>\n\n\n\n## 4.6.28 (2023-10-24)\n\n\n### 🔧 修复内容\n\n* 修复上个版本无法答题的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/7918a3d7bf4b7e0bfb78fc4a7ac1a9e6058445d7\">></a>\n\n\n\n## 4.6.27 (2023-10-24)\n\n\n### 🔧 修复内容\n\n* 修复超星章节测试题库被禁用的时候依然使用的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/fff8cc4d41e49cf8c4f0d8f13f9cde3143505434\">></a>\n* 修复智慧树检测习惯分出错 <a href=\"https://github.com/ocsjs/ocsjs/commit/436eedd449c15d74be8b4b07fdd33d7c01e3db0e\">></a>\n\n\n\n## 4.6.25 (2023-10-22)\n\n\n### 🔧 修复内容\n\n* 深度优化智慧树弹窗BUG，以及倍速清晰度不选择BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/f61498c4640cbe8583b33d6d46a922341c3a57ae\">></a>\n\n\n### ✨ 更新内容\n\n* 新增可以设置不被软件配置同步覆盖的设置，修复智慧树学习记录刷新后清空的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/b35f87fc5fd01a9de37fdc2855da2f6dfd8fc9df\">></a>\n\n\n\n## 4.6.23 (2023-10-20)\n\n\n### 🔧 修复内容\n\n* 优化核心域名匹配逻辑 <a href=\"https://github.com/ocsjs/ocsjs/commit/8cbcc9412726c673e9f1ad8898a46ded1f6502c4\">></a>\n* 持续优化智慧树倍速和清晰度选择功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/86d133bab4fc3063dc39335f61bf7a6f88226fd1\">></a>\n* 将软件同步功能加快到 onactive <a href=\"https://github.com/ocsjs/ocsjs/commit/db4476158b39765c28cfe5491cacfedc4a193c2b\">></a>\n* 优化超星新课程页面不显示使用提示的问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/3daf2623a97cd1a6d22aa3c3fd6bfc1e2b960f39\">></a>\n* 优化智慧树弹窗答题 <a href=\"https://github.com/ocsjs/ocsjs/commit/e7a7a1f12246da929d106e69be50aca60d519117\">></a>\n\n\n\n## 4.6.22 (2023-10-19)\n\n\n### 🔧 修复内容\n\n* 修复智慧树需要调整窗口的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/45c5f0b6a834b151d8955d05bf62cfa4ecb228c0\">></a>\n\n\n### ✨ 更新内容\n\n* 兼容智慧职教套壳网站 courshare.cn <a href=\"https://github.com/ocsjs/ocsjs/commit/7a2a08832cafaefc70cefc7a3cec63131796655a\">></a>\n* 收录超星套壳域名 qutjxjy.cn ynny.cn <a href=\"https://github.com/ocsjs/ocsjs/commit/2d9b6fcc81fa8ddce44fcc24d1756a6ef07624af\">></a>\n\n\n\n## 4.6.19 (2023-10-15)\n\n\n### 🔧 修复内容\n\n* 补充答题冗余字段删除数据 <a href=\"https://github.com/ocsjs/ocsjs/commit/6d3544d8fb49db88e943cdccfa9fb171792c1078\">></a>\n* 修复软件配置同步后，日志页面全锁定的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/e6f0610e42c7bb0137cfc7beb79921a3ef1e869b\">></a>\n* 修复使用软件同步时，智慧树学习自动暂停无效的BUG，以及存在验证码时自动暂停的优化。 <a href=\"https://github.com/ocsjs/ocsjs/commit/8aa4bcfd7f85e6c3c00afbb99fb361b4a70f1a73\">></a>\n* 优化窗口大小自动调整，优化窗口大小警告模块 <a href=\"https://github.com/ocsjs/ocsjs/commit/6882026b2b9f2441d689add73895bed8797774f6\">></a>\n\n\n### ✨ 更新内容\n\n* 将软件辅助警告修改成弹窗 <a href=\"https://github.com/ocsjs/ocsjs/commit/6b88c298befbee314e3bda309cdd426d04d2dc41\">></a>\n* 添加超星强制答题提示 <a href=\"https://github.com/ocsjs/ocsjs/commit/0067efc7e7dc7f09018709ad0b5f0ac636807eef\">></a>\n* 添加对自动调整窗口开关的重启提示 <a href=\"https://github.com/ocsjs/ocsjs/commit/46b957b6706bf501d8a22b3b0f336e5f83deb31f\">></a>\n* 添加职教云【资源库】支持 <a href=\"https://github.com/ocsjs/ocsjs/commit/da6f135276ace1394c4072f2a4cd02625753a689\">></a>\n\n\n\n\n\n\n\n## 4.6.9 (2023-10-02)\n\n\n### 🔧 修复内容\n\n* 修复智慧树考试脚本无法使用的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/5f3f9729687f93a5d2e36161883b822d14075518\">></a>\n\n\n### ✨ 更新内容\n\n* 新增超星阅读任务无限阅读功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/137f7397cef6554260db10f0d2a7535ca3db8073\">></a>\n\n\n\n## 4.6.7 (2023-09-26)\n\n\n### 🔧 修复内容\n\n* 微调智慧树窗口大小要求，宽2200，高1200 <a href=\"https://github.com/ocsjs/ocsjs/commit/f438b79512463396cba1ca59aa93422b06ead276\">></a>\n\n\n### ✨ 更新内容\n\n* 新增功能：超星强制答题功能，没有黄色任务点的章节测试也可以运行自动答题。 <a href=\"https://github.com/ocsjs/ocsjs/commit/453d254aa575b23ad7571b811dc618a467ae1826\">></a>\n\n\n\n## 4.6.5 (2023-09-25)\n\n\n### 🔧 修复内容\n\n* 修复智慧树调成窗口大于最小值后依然说不对的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/be9420097c81574b9b6b4a910808f27f19cbdf23\">></a>\n\n\n\n## 4.6.4 (2023-09-24)\n\n\n### ✨ 更新内容\n\n* 添加智慧树窗口自动调节功能的选项按钮 <a href=\"https://github.com/ocsjs/ocsjs/commit/0543ca1e6a8acd92bdc2b1889beb7a4d8c0a2caf\">></a>\n\n\n\n## 4.6.3 (2023-09-24)\n\n\n### 🔧 修复内容\n\n* 添加智慧树窗口检测提示 <a href=\"https://github.com/ocsjs/ocsjs/commit/849e28ddf7c57a5b7457d6f2b63a250a66f3792a\">></a>\n\n\n### ✨ 更新内容\n\n* 添加自动设置窗口大小功能，避免元素无法点击 <a href=\"https://github.com/ocsjs/ocsjs/commit/6aed250ea05a65c39ff5b5c36783510b2210781d\">></a>\n\n\n\n## 4.6.2 (2023-09-22)\n\n\n### 🔧 修复内容\n\n* 修复上个版本智慧树作业BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/e7824039f22862ebbd412f43c37b248bea4b0232\">></a>\n\n\n\n## 4.6.1 (2023-09-22)\n\n\n### 🔧 修复内容\n\n* 优化智慧树刷课逻辑，增加流畅度 <a href=\"https://github.com/ocsjs/ocsjs/commit/3a9315773fab3b7b8561ff57e189d58ff1a68150\">></a>\n\n\n\n# 4.6.0 (2023-09-22)\n\n\n### 🔧 修复内容\n\n* 新增软件辅助功能，全面优化智慧树，共享课学习作业考试以及学分课视频 <a href=\"https://github.com/ocsjs/ocsjs/commit/18a2725328b7917b8417ff8111e8a09fb4e09052\">></a>\n* 优化超星最新考试页面支持 <a href=\"https://github.com/ocsjs/ocsjs/commit/9e712393e26b8faced900a4e56bc8c4f0dc00294\">></a>\n\n\n\n## 4.5.8 (2023-09-15)\n\n\n### 🔧 修复内容\n\n* 修复部分课程不显示超星阅读任务的提示页面 <a href=\"https://github.com/ocsjs/ocsjs/commit/907bb634a82889aa933a6989ab9c3d46cbba01c3\">></a>\n* 修复在中国大学MOOC中点击事件监听被修改的问题，将 addEventListener('click') 改成 onlick <a href=\"https://github.com/ocsjs/ocsjs/commit/b1eba8ff742738ff412f94b2f0e6ba83819250a4\">></a>\n* 修复智慧树最新版脚本被检测的问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/adfe7a0cee1994396714cd3e01868cad409476c5\">></a>\n\n\n\n## 4.5.5 (2023-09-12)\n\n\n### ✨ 更新内容\n\n* 使更新模块可视化 <a href=\"https://github.com/ocsjs/ocsjs/commit/0e27628d319db704f9849926a9602d1406f43e63\">></a>\n\n\n\n## 4.5.4 (2023-09-12)\n\n\n### 🔧 修复内容\n\n* 修复上个版本出现的弹窗底部消失的问题，优化超星自动答题后台日志 <a href=\"https://github.com/ocsjs/ocsjs/commit/37e6749a5bbef7788b2bd3b6efd2aba98581ceba\">></a>\n\n\n\n## 4.5.3 (2023-09-12)\n\n\n### ✨ 更新内容\n\n* 添加跳过版本的功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/63913b222e53b128a26d8aa3b4dc5dee654b67ae\">></a>\n* 添加新版职教云音频的播放 <a href=\"https://github.com/ocsjs/ocsjs/commit/b7b47b9c0cb884e00cc98a6473cbbcdbdc933138\">></a>\n* 添加新版职教云DOC文档学习功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/9a5babd3379bc15e6c9cc0fe8c5f91a7d10bd5cb\">></a>\n\n\n\n# 4.5.0 (2023-09-09)\n\n\n### ✨ 更新内容\n\n* 重写职教云刷课逻辑 <a href=\"https://github.com/ocsjs/ocsjs/commit/1b50d5b330db5676b94777245657793f5c55162d\">></a>\n\n\n\n## 4.4.35 (2023-09-04)\n\n\n### 🔧 修复内容\n\n* 优化自动发布的说明文档 <a href=\"https://github.com/ocsjs/ocsjs/commit/cc84599a36f6afd8a10efd13d0b9decf6ca6608c\">></a>\n* 修复超星选择题选中的答案被取消的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/808709a7503c3d34c2463d3156c1fde26deb8267\">></a>\n* test <a href=\"https://github.com/ocsjs/ocsjs/commit/c70ec9b94d166c5f6ab4a4aa649f7ca8f004199a\">></a>\n\n\n\n## 4.4.34 (2023-08-25)\n\n\n### 🔧 修复内容\n\n* 优化打包文件 <a href=\"https://github.com/ocsjs/ocsjs/commit/3fd86577271cd49746b64bae9a7131f7dc99c83f\">></a>\n\n\n\n## 4.4.33 (2023-08-25)\n\n\n### 🔧 修复内容\n\n* 优化github actions <a href=\"https://github.com/ocsjs/ocsjs/commit/9f6608cd775ac121ce5fcb070ba0b1b699a54cc6\">></a>\n\n\n\n## 4.4.32 (2023-08-25)\n\n\n### 🔧 修复内容\n\n* 优化智慧树最新异常检测 <a href=\"https://github.com/ocsjs/ocsjs/commit/b634a4694a1fcf88956f6dcde9f8e7a8b891fccd\">></a>\n\n\n\n## 4.4.31 (2023-08-05)\n\n\n### 🔧 修复内容\n\n* 修复超星判断题更新后，文字优化不兼容的问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/f7beff9e109c5c07a2f27df567c7cff319a4caff\">></a>\n\n\n\n## 4.4.30 (2023-06-22)\n\n\n### 🔧 修复内容\n\n* 新增超星分录题支持 <a href=\"https://github.com/ocsjs/ocsjs/commit/68c007bb7ad890e57cec8cd5c39ff30b00ee599f\">></a>\n\n\n\n## 4.4.29 (2023-06-22)\n\n\n### 🔧 修复内容\n\n* 对脚本打包的 match 元数据进行去重 <a href=\"https://github.com/ocsjs/ocsjs/commit/9de7d2c99a97c0786a3aa3dc9981643eedfe407d\">></a>\n* 将题库配置为空提醒设置为一直显示 <a href=\"https://github.com/ocsjs/ocsjs/commit/d07bdbfd0735b4ccd4bb9fabef6a6f5f15a73707\">></a>\n* 新增超星题目类型支持：资料题 <a href=\"https://github.com/ocsjs/ocsjs/commit/b6d3a006fb64b1f5aa58f806a68cfc56980ea340\">></a>\n* 修复超星作业考试文本框无法自动保存BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/ec58efedc5d047ddb1c66b32023df0e24709f315\">></a>\n\n\n\n## 4.4.25 (2023-05-25)\n\n\n### 🔧 修复内容\n\n* 修复上个版本题库连接超时问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/1c045ef549a75717981bd99e97b4ecbee9d6f20d\">></a>\n\n\n\n## 4.4.24 (2023-05-25)\n\n\n### 🔧 修复内容\n\n* 修复题库状态检测无限执行的BUG，优化 onrender 中的监听器执行逻辑 <a href=\"https://github.com/ocsjs/ocsjs/commit/cbe7f0b9a1a2bdb47f0dff09739b41270b556c70\">></a>\n* 优化智慧职教使用提示 <a href=\"https://github.com/ocsjs/ocsjs/commit/ca5d4b3ea4f29a265497ae0b401f3e28a575b081\">></a>\n\n\n\n\n\n\n\n## 4.4.22 (2023-05-20)\n\n\n### 🔧 修复内容\n\n* update build file <a href=\"https://github.com/ocsjs/ocsjs/commit/c5d50b14bc4d834a3d5b8c82931abae8b15fca70\">></a>\n\n\n\n\n\n\n\n\n\n\n\n## 4.4.19 (2023-05-20)\n\n\n### 🔧 修复内容\n\n* 修复添加配置分隔线后，OCS配置同步锁的样式显示出错 <a href=\"https://github.com/ocsjs/ocsjs/commit/a25708419505672e40617ccc2a9088a6038ce971\">></a>\n* 添加软件自动登录辅助页面 <a href=\"https://github.com/ocsjs/ocsjs/commit/a2bfa20014c84d8ee7d59032eca01bac225d5d0e\">></a>\n\n\n### ✨ 更新内容\n\n* 添加配置分割线，便于设置区域分组 <a href=\"https://github.com/ocsjs/ocsjs/commit/980d591c92466c79d2a35f40c8dacd6b633ed7a7\">></a>\n\n\n\n## 4.4.15 (2023-05-16)\n\n\n### 🔧 修复内容\n\n* 规范搜题请求处理，修复在url中存在变量时多加一个问号的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/c0f561d8c2ec6577f6594f0920bde9c686c23a22\">></a>, closes [#97](https://github.com/ocsjs/ocsjs/issues/97)\n\n\n\n## 4.4.14 (2023-05-16)\n\n\n### 🔧 修复内容\n\n* 修复智慧职教多层级任务检测 <a href=\"https://github.com/ocsjs/ocsjs/commit/64aaf138762d21c3916c7dc7003a4ae3040b3655\">></a>\n* 优化题库状态停用显示 <a href=\"https://github.com/ocsjs/ocsjs/commit/a012a569a5817f2af8ec655755d78b23447b48e8\">></a>\n\n\n\n## 4.4.13 (2023-05-12)\n\n\n### 🔧 修复内容\n\n* 修复题库缓存不是最新的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/a7a89b98d7ee672b47dc63fb36ba6eaa9e37c87d\">></a>\n\n\n\n## 4.4.12 (2023-05-12)\n\n\n### 🔧 修复内容\n\n* 修复题库配置清空后会留下两个小括号的bug <a href=\"https://github.com/ocsjs/ocsjs/commit/0993a52d52dca43951279076783ca047a10dc234\">></a>\n\n\n\n## 4.4.11 (2023-05-12)\n\n\n### 🔧 修复内容\n\n* 继续优化智慧树作业考试检测 <a href=\"https://github.com/ocsjs/ocsjs/commit/2a337bb56c3a5f5a157ff80c213c14521d8f0c4c\">></a>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n## 4.4.7 (2023-05-11)\n\n\n### 🔧 修复内容\n\n* 将更新通知的请求方法改成油猴跨域请求，防止有些页面的 safe 策略阻止请求。 <a href=\"https://github.com/ocsjs/ocsjs/commit/6613973f00fbbda9b5d5f3591475fecd7e6a47c4\">></a>\n* 优化智慧树考试作业提示 <a href=\"https://github.com/ocsjs/ocsjs/commit/3703e976a19ba835aa1d6f7d7c5f860134802950\">></a>\n\n\n### ✨ 更新内容\n\n* 新增题库开关功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/07f3cfcba517edd86cd38f4c0fcabd99ef3ed4fa\">></a>\n\n\n\n\n\n\n\n## 4.4.4 (2023-05-10)\n\n\n### 🔧 修复内容\n\n* 修复智慧树考试作业进入后不自动开始，而是需要刷新才能开始的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/8f197ec3d3418663698e9495de4909c1b2123ee5\">></a>\n\n\n\n\n\n\n\n## 4.4.2 (2023-05-09)\n\n\n### 🔧 修复内容\n\n* 修复智慧树作业考试阅读理解小题读取失败的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/37e42f10adfb3198dbb4738dbfd232d0bd736421\">></a>\n\n\n\n## 4.4.1 (2023-05-09)\n\n\n### 🔧 修复内容\n\n* 优化没有题库配置的提示 <a href=\"https://github.com/ocsjs/ocsjs/commit/dc552440b5a15a1ff2639b64af851da6582056a0\">></a>\n\n\n\n# 4.4.0 (2023-05-09)\n\n\n### 🔧 修复内容\n\n* 修复搜索结果图片显示问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/0ebc3f9c057438611a4791818fd2f5030c16eb65\">></a>\n* 优化单选题选项ABCD冗余并没有去掉的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/f88fd8698fc9a265df5ab3c9a3205fe9a81b4be4\">></a>\n* 全面优化自动答题逻辑，并将搜索结果直接显示在各自的脚本面板下，无需反复跳转查看。 <a href=\"https://github.com/ocsjs/ocsjs/commit/8712b24ee47bda38598e057e44188acd6f5a46fa\">></a>\n* 修复智慧树图片题的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/5adda0be86a1530697539ac412a714d112d6962e\">></a>\n* 优化日志显示 <a href=\"https://github.com/ocsjs/ocsjs/commit/bc7521a2313fc64d8be56be4b34956e06736dd4c\">></a>\n* 优化智慧树视频加载缓慢时无法自动播放的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/640bc72f70d9cebd9d9b2f110f11b99515a4fe33\">></a>\n* 优化智慧树校内课作业 <a href=\"https://github.com/ocsjs/ocsjs/commit/c877ed6551569883afff330c37255982c3511424\">></a>\n* 优化智慧职教MOOC的自动学习逻辑 <a href=\"https://github.com/ocsjs/ocsjs/commit/96947732d8da48018fb4ce9c8979941a859cb55b\">></a>\n* 智慧树考试强制添加保存弹窗，并从头开始每题保存，防止用户切换题目导致保存失败 <a href=\"https://github.com/ocsjs/ocsjs/commit/f6c0e22ebb610c6b59079e8cedd07ba8cd3e8438\">></a>\n* update build script <a href=\"https://github.com/ocsjs/ocsjs/commit/4aa68f16c4cf68487db298ca91e66d3b9ba90c51\">></a>\n\n\n### ✨ 更新内容\n\n* 增加搜索结果与题目同步显示功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/f793d431cd51ca54dd984ff36d1ef1884833b8c2\">></a>\n* 增加职教云考试功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/c9bc7ad5cdf07c292b380cedc273fc407329e02e\">></a>\n\n\n\n## 4.3.7 (2023-04-24)\n\n\n### 🔧 修复内容\n\n* 修复智慧职教MOOC学院自动答题无法处理判断题的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/b5736845e38ec1e80281b66fc1f8bb152ec703dd\">></a>\n\n\n### ✨ 更新内容\n\n* 添加职教云作业自动答题 <a href=\"https://github.com/ocsjs/ocsjs/commit/4db11a40b7cfbac1e7eda26e50e2bde88d4ee6a5\">></a>\n\n\n### ⚡ 优化提升\n\n* add woker.onElementSearched args[1] : root <a href=\"https://github.com/ocsjs/ocsjs/commit/c0244e998f7893761557d7354d232de9438e00c2\">></a>\n\n\n\n## 4.3.5 (2023-04-24)\n\n\n### 🔧 修复内容\n\n* 修复题库缓存搜索时每题只出一个结果的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/222735a98cf94f09e6b69eb313c98283e42561a9\">></a>\n* 修复智慧职教MOOC自动答题的填空题无法填空的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/565f8799518912a65c818baf55a8671576af659f\">></a>\n\n\n\n## 4.3.3 (2023-04-24)\n\n\n### 🔧 修复内容\n\n* 撤回智慧树图片识别，否则导致作业考试无法使用。 <a href=\"https://github.com/ocsjs/ocsjs/commit/fe5b197f1ab4a004d69c44c3460fc94ed7a4e1d6\">></a>\n\n\n\n## 4.3.2 (2023-04-24)\n\n\n### 🔧 修复内容\n\n* 修复一些显示BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/eb4cbc1067a97afa4fb81424a9332d393e591982\">></a>\n\n\n\n## 4.3.1 (2023-04-24)\n\n\n### 🔧 修复内容\n\n* 帮助智慧树修复图片题无法显示的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/bb4c5aa49d46f5c077a3855b392ba036db6672e6\">></a>\n\n\n\n# 4.3.0 (2023-04-24)\n\n\n### 🔧 修复内容\n\n* 优化浏览器环境问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/090c7af154e34fa7aa0833a586cab7e709cc16c8\">></a>\n* 修复题库搜题时出现 Cannot convert object to primitive value 问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/9af5f3ff5ab93fcd87deefbf7af9963636f342ea\">></a>\n* 优化搜索结果的显示，并且添加快捷百度一下按钮 <a href=\"https://github.com/ocsjs/ocsjs/commit/e074a95c647e58281648a0ba83c17c069e4de015\">></a>\n* 添加超星自动答题后暂停提示 <a href=\"https://github.com/ocsjs/ocsjs/commit/0635db94916fcace2e44ef5f7d3fa74114021aa0\">></a>\n* 新增超星匹配域名： hnsyu.net <a href=\"https://github.com/ocsjs/ocsjs/commit/481445f46903ef018f125cd17a33e431a979b267\">></a>\n* 新增超星视频加载失败检测功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/1b1e9b8b7697b89f0f5c2aef28aff064d42db606\">></a>\n* 修复上个版本智慧职教MOOC学院中作业自动答题题目为空的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/e095493a3fbd767c2d0d0e90131d6ddc1ec0481f\">></a>\n* 修复手贱导致的判断题乱选的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/4cfa3c86b948115819ec3e8e9c5a7ebed0b7ebd0\">></a>\n* 优化 $modal API ，修复 onClose 执行逻辑 <a href=\"https://github.com/ocsjs/ocsjs/commit/37cd819f27d7b750c76d0db652b35ad5d604a7aa\">></a>\n* 优化超星编辑框复制粘贴问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/b26314689183338e3b4ee3db73258e4e1d538df6\">></a>\n* 优化脚本教程，优化搜索结果，删除独立的通知提示和版本日志，转移到脚本首页中。 <a href=\"https://github.com/ocsjs/ocsjs/commit/12d9cef9937fd11c73d2b0180f59d4f067777822\">></a>\n* 优化屏蔽复制粘贴限制 <a href=\"https://github.com/ocsjs/ocsjs/commit/3c124c300233592224f127df8ddcf6cbe65363ce\">></a>\n* 优化搜索结果空白的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/5de62f5f9f590c4afe4db2a9b91af8f125481847\">></a>\n* 优化智慧树学习逻辑，用户手动切换视频时脚本可以重新生效。 <a href=\"https://github.com/ocsjs/ocsjs/commit/ad5757c81fa7e5139e4097c6326996172e0e12a3\">></a>\n\n\n### ✨ 更新内容\n\n* 优化 el API 方便自定义样式 <a href=\"https://github.com/ocsjs/ocsjs/commit/e3468f7762b71976338b7a7e74eb920e797e069e\">></a>\n* 将题库缓存储域切换成本地存储 <a href=\"https://github.com/ocsjs/ocsjs/commit/6969b523cf8a6cc8436f101be7b44ae2ce2ca41d\">></a>\n* 添加题库缓存功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/38bde1561acfc5e1cf2961846d55ae5705b46514\">></a>\n* 添加智慧职教MOOC学院的作业自动答题功能，优化刷课逻辑。 <a href=\"https://github.com/ocsjs/ocsjs/commit/e2954f412e726af2b26f153d0cb980c2d8273608\">></a>\n\n\n\n## 4.2.31 (2023-04-20)\n\n\n### 🔧 修复内容\n\n* 修复超星图片题选择BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/93c520cfcea739cb12d3d0c11d05065e0f1e947a\">></a>\n* 优化智慧树共享课和校内课的作业和考试 <a href=\"https://github.com/ocsjs/ocsjs/commit/c80b08cec8099c335b7a2c0a38cbacf1585f5610\">></a>\n\n\n\n## 4.2.29 (2023-04-20)\n\n\n### 🔧 修复内容\n\n* 优化超星繁体字识别，优化http网站下复制粘贴问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/f98f2c4f439f02b6a0ef045c8b9a5f61625d8c8e\">></a>\n* 优化智慧树作业考试文字识别BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/317f136611b4175955f0fe8c646100aac0d925cb\">></a>\n* 修复超星倍速提示没有显示的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/ec02677a56181b760015e79f9446cbdd2a58b485\">></a>\n\n\n\n## 4.2.26 (2023-04-19)\n\n\n### 🔧 修复内容\n\n* 修复软件批量创建中无法导出模板的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/7ba565e569d003ac91d124bc09c001816df97edc\">></a>\n* 修复OCR初始化软件路径读取问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/2ef18e0e92dd65abda4516df9b9ee7a2f12f4d1c\">></a>\n* 在OCS软件中不显示软件配置同步的面板 <a href=\"https://github.com/ocsjs/ocsjs/commit/baf3815ec8670fed7d27250c4deb9a4c0094676a\">></a>\n* 修复脚本题库检测BUG，优化软件更新提示 <a href=\"https://github.com/ocsjs/ocsjs/commit/7e217d1a68ce840275bdf94ba3fa2265a73d3f13\">></a>\n* - 优化超星章节测试题目解析 <a href=\"https://github.com/ocsjs/ocsjs/commit/6b39e401cf7c9ad1af58651b1067b722c55236c0\">></a>\n* 对超星繁体字库加载进行异常处理 <a href=\"https://github.com/ocsjs/ocsjs/commit/7a0c825aec72d1af1cf7e9b33eb78660aea17713\">></a>\n* 删除清空答案选项 <a href=\"https://github.com/ocsjs/ocsjs/commit/06936a558f67f1e9e80d9369e52697ed0f557e2f\">></a>\n* 修复超星输入框无法复制粘贴的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/d8a3c300bcb7f25a186bb3e857f9d0e3c8aa5b9f\">></a>\n* 修复超星章节测试出现年份丢失的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/941c0e9ceeefc7270b1f6740f1061e7aed952e39\">></a>\n* 修复超星章节测试出现年份丢失的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/2bc1ce11bc6ecb0e34300611c3e0c2360624a2fc\">></a>\n* 修复搜索结果答案是图片但没有显示的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/860d1feb2fd480a8ba8cc6fcdfe9eda9ca06004a\">></a>\n* 修复智慧树考试无法识别题目的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/5140d95aeb46ba13244eae73e3d3eb595c3cee1d\">></a>\n* 优化软件同步设置，优化题库状态显示 <a href=\"https://github.com/ocsjs/ocsjs/commit/4e3cc967fc653f333cbb897404ac1f8f8ae65ab8\">></a>\n* 每次渲染强制更新通知和版本日志 <a href=\"https://github.com/ocsjs/ocsjs/commit/7cc53479e00205c1fae88c6ae3e353874bdff413\">></a>\n\n\n### ✨ 更新内容\n\n* 添加题目冗余字段自定义删除功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/198e2408fa90325731b8eae8e5b8e6e42609d102\">></a>\n\n\n\n## 4.2.15 (2023-04-04)\n\n\n### 🔧 修复内容\n\n* 修复软件配置同步导致的各种问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/6040d7098e5c0ca38b27064bd2fdbaf71ac83fbe\">></a>\n* 修复搜索结果超时后全部题库都显示超时的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/a11b2a5fa65bc52aca28dd731c954ee36ded2f6d\">></a>\n* 修复通知和版本日志在有些页面访问不了的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/7fa694ad6cce5b3edc5cbf4c50bfada3b95aa7a7\">></a>\n* 优化全局设置题库检测，增加防抖 <a href=\"https://github.com/ocsjs/ocsjs/commit/a1de6ab38ab8e08313eba3f50572b7d9659e9e59\">></a>\n\n\n\n## 4.2.11 (2023-04-03)\n\n\n### 🔧 修复内容\n\n* 修改项目链接 enncy/online-course-script => ocsjs/ocsjs <a href=\"https://github.com/ocsjs/ocsjs/commit/e7f02292548e89517585e7a3a96e6063078eecd6\">></a>\n* 使用JSON消除自定义脚本的响应式特性，修复每个浏览器自定义脚数据相同的问题。 <a href=\"https://github.com/ocsjs/ocsjs/commit/af7b97e2c5e0a426813a231e02b66072c57db8ee\">></a>\n* 新增软件 zip 打包方式，修复工作区丢失的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/8fe13574285871f56234223264b1ae64a0c27a77\">></a>\n* 修复部分用户加载不出导航页的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/19f4d102edbacaff21cd324189599941e7e78739\">></a>\n* 修复软件标题栏重载时不自动切换的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/25f45ce643c3e30a32d2f9e89a425e7ec6e57d8b\">></a>\n* 修复软件浏览器标签输入时不自动提示的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/6aececb4511929732a5e03a4c41c2d8172bc633c\">></a>\n* 修复软件运行途中删除文件会导致所有浏览器关闭的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/45a2c226ca1033f2ac9c95f3498772eb06856529\">></a>\n* 修改app加载文件以及油猴插件路径问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/cbf69306236d0b91d822ea27345cae3e4579a1ab\">></a>\n* 优化 app remote 模块，修改成异步通信，修复同步通信导致的页面卡死问题。 <a href=\"https://github.com/ocsjs/ocsjs/commit/9a817212ae1a57e88a8738425de8c51218bfeefa\">></a>\n* 优化软件导航页，将ocs-app接口代理删除，全局使用15319端口进行访问，修改浏览器启动选项，使浏览器环境更接近真实浏览器 <a href=\"https://github.com/ocsjs/ocsjs/commit/9c8d3fd6f1986b5a1834696a9b8362109125132b\">></a>\n* 优化软件更新程序，并修复更新通知抽搐问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/460e79a07f45e26f8d64136bf40f42ab2f3a83e7\">></a>\n* 优化软件监控问题，以及启动脚本，和浏览器状态处理优化。 <a href=\"https://github.com/ocsjs/ocsjs/commit/7b7475df86b3c3e509dfb8e1e44d4770fff00dfd\">></a>\n* 优化软件自动化脚本，添加类型声明 <a href=\"https://github.com/ocsjs/ocsjs/commit/b3f1565505a5331f2761e333c06dcd971c41f800\">></a>\n* app version update <a href=\"https://github.com/ocsjs/ocsjs/commit/5ddce8114f8121d42a76917d0b34013d86eb68bc\">></a>\n* remove public build <a href=\"https://github.com/ocsjs/ocsjs/commit/d3e4340b82381e78daf56d02f5ccb2ae8e8da7ee\">></a>\n* 持续优化超星直播回放脚本 <a href=\"https://github.com/ocsjs/ocsjs/commit/1d3b399e790616cfade8aed6e3d81dcc2402da5d\">></a>\n* 调整职教云任务读取速度 <a href=\"https://github.com/ocsjs/ocsjs/commit/b2adafbade5b894f7c723b3e197ea44a3919cdcd\">></a>\n* 更改  $gm 和 $ 修改后的代码 <a href=\"https://github.com/ocsjs/ocsjs/commit/941ea307206c2e197134b8499977e3568332b6bf\">></a>\n* 更新超星未完成章节会出现提示框的问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/ebd75d17500effb477363a48db00e60b36825ed8\">></a>\n* 更新教程链接 <a href=\"https://github.com/ocsjs/ocsjs/commit/f07fe3906a1bbfe360d5174cf2bfacbfe319bb88\">></a>\n* 更新智慧树脚本 <a href=\"https://github.com/ocsjs/ocsjs/commit/dc04328309092e4630d16d1b7111936354d85d6c\">></a>\n* 继续修复上个版本问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/41ca48c83966035f536de822f8d25a58a0f7ac68\">></a>\n* 将 dropdown 的 opiton 元素改成 div 元素 <a href=\"https://github.com/ocsjs/ocsjs/commit/5a8877e531b19a344d58e11b1956c787186dc005\">></a>\n* 将 unsafeWindow 全局变量封装成 useUnsafeWindow <a href=\"https://github.com/ocsjs/ocsjs/commit/1c0f0bbf71661249b81bfed12472fd7bc389ebf2\">></a>\n* 将超星视频进度步数调整为0.25，倍速颗粒度控制更高。 <a href=\"https://github.com/ocsjs/ocsjs/commit/1702aa9484bc5007a8f04cb0f1600af72ab28477\">></a>\n* 将整个项目修改成跨域响应式模式 <a href=\"https://github.com/ocsjs/ocsjs/commit/10aaf0ae4d415d68e3a22bba303962c17f43355f\">></a>\n* 脚本学习核心修改，适配 useSettings 和 useContext 两个 API <a href=\"https://github.com/ocsjs/ocsjs/commit/b4e38b144cdd23504432a17950aafcbbcd6723c2\">></a>\n* 解决cx任务点未完成出现弹窗的问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/d9a67389dabee1dde144f96df4cf45909b290072\">></a>\n* 删除超星在作业考试中题目多余内容导致的正确率下降 <a href=\"https://github.com/ocsjs/ocsjs/commit/5ff27e5558500671d9cb30d96d1175be86e48dd1\">></a>\n* 删除全局变量 OCS <a href=\"https://github.com/ocsjs/ocsjs/commit/c74195d0d3192e6854c8da0d32262634f4470de2\">></a>\n* 添加本地存储初始化时删除无用字段 <a href=\"https://github.com/ocsjs/ocsjs/commit/dc57a4a33f20ccd11922d806425acdeb30a8a45a\">></a>\n* 为所有参数提供默认值，防止页面空白或者头部空白。 <a href=\"https://github.com/ocsjs/ocsjs/commit/baf6086567d1cc4b1681fcdf5d818260ccbe8dd6\">></a>\n* 限制请求超时时间最小为10 <a href=\"https://github.com/ocsjs/ocsjs/commit/f3fc59e211b6f86c70c4a9a80a6c0bb3b4b82a0d\">></a>\n* 新增ICVE字段 <a href=\"https://github.com/ocsjs/ocsjs/commit/abbcfec64d25622415c863233029c68878d1d4a5\">></a>\n* 修复 store 环境检测问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/cd14eb07e8b33746fc72b1d35e2aaba85a85b07f\">></a>\n* 修复 StringUtils 导入问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/50b83dd3942cf6801da203b168c55ae70d63c3be\">></a>\n* 修复$message不能永久显示bug <a href=\"https://github.com/ocsjs/ocsjs/commit/3eec4b8b386c241c40a4df2813e45f10c62f45d2\">></a>\n* 修复不能关闭路线切换的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/a5694f1acecb7962c0e39d5b6890b5b86b365d03\">></a>\n* 修复部分页面存在不执行 interactive 生命周期的问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/1f9a8cc32b47c9a46a6c77089f5d1b1dd192f625\">></a>\n* 修复超星复习模式自动切换的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/64c63cc19bf0f97de7409a1f3c87ab4321726365\">></a>\n* 修复超星视频答题永远只选2个选项的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/1209ee7362a3bd10e7745393a944f686b0fca431\">></a>\n* 修复超星音频任务不能播放的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/2777c691322b8d511b56e44e08384ed15f29827e\">></a>\n* 修复答题结果中存在答案但是不选的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/8129842fbdc6187ab93a07ba4e34a38f66fed998\">></a>\n* 修复打包后文件中文变成Unicode编码的问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/ea6da2cea453efbf6cd7da3d15f30ff7f1aa7961\">></a>\n* 修复代码中带有特殊字符时，unicode 转换失败 <a href=\"https://github.com/ocsjs/ocsjs/commit/fb0cb211a6f87de37fe4f0ea9d2cb5956e9857b2\">></a>\n* 修复跨域问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/8ad21bfcbd4130ab71a15813beabeec045223ef2\">></a>\n* 修复每次页面加载都要删除core监听队列的bug <a href=\"https://github.com/ocsjs/ocsjs/commit/230d43a0e9a3fe047c787c588a3e1285f46638e8\">></a>\n* 修复日志面板不会实时滚动的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/a0b9fffb6967a34397eef6597483ccdff8772319\">></a>\n* 修复视频频繁停止导致的频繁验证码 <a href=\"https://github.com/ocsjs/ocsjs/commit/62f708e9e1a3e15582efd0618ca085b9aa578535\">></a>\n* 修复数字输入可以超过范围的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/edbd3bce0385f404d9dcad58d5b831c61c7f9545\">></a>\n* 修复题库配置报错异常未捕获的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/4e04da0ccfd938e58d4f0239be861a882fb01d56\">></a>\n* 修复职教云进度不显示的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/480cfa3a71d91a2252e73f9ae09db6221a97c8a0\">></a>\n* 修复职教云任务获取出现子节点BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/ba639211dedd4d50b56e7a8ea1e02d1c25c73346\">></a>\n* 修复职教云子节点读取的问题，优化任务列表，优化学习脚本 <a href=\"https://github.com/ocsjs/ocsjs/commit/5a11f138151bc0a8b0297b1f09ed17e8c2589488\">></a>\n* 修复智慧树倍速失效的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/79434421cc1b9c28c547b7c45b08ea762459dd26\">></a>\n* 修复智慧树视脚本的频路径匹配 <a href=\"https://github.com/ocsjs/ocsjs/commit/b03dd08d178ac9e89fb042a5bf97520eda1de10e\">></a>\n* 修复智慧树视频测验弹窗无法关闭的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/fc0741f7903992d877ee4b8b1b04e272f81b7a4d\">></a>\n* 修复select,range等对value不显示的bug <a href=\"https://github.com/ocsjs/ocsjs/commit/201b3add0ffcbfbdb41157071e1b8d660cb38ac4\">></a>\n* 修复zhs弹窗关闭的问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/a688768c39984561cee8d2ecfee7f6ad07d2f78b\">></a>\n* 修改 $creator.tooltip 的卡死BUG，很多元素时会导致卡顿，这里将 tooltip 变成单个全局元素。 <a href=\"https://github.com/ocsjs/ocsjs/commit/da59c2899bb9ef37aca5aee2000e00311cad1d73\">></a>\n* 修改 userjs.templete 模板文件，兼容跨域响应式，并修改 homepage 等字段 <a href=\"https://github.com/ocsjs/ocsjs/commit/f945a8043e5e62f4b92f8650fc332278cdfb974b\">></a>\n* 修改生命周期执行规则 <a href=\"https://github.com/ocsjs/ocsjs/commit/99b66fb884f2b88eef72fa402e932bfa40e71148\">></a>\n* 修改搜索结果文案 <a href=\"https://github.com/ocsjs/ocsjs/commit/00f185e2cba817cbe4d58e28c1d8248bdd656b9d\">></a>\n* 修改元素是否存在的判断 <a href=\"https://github.com/ocsjs/ocsjs/commit/345fbccd675dee604e3b4fd21d6c787a38c6575d\">></a>\n* 修改cdn地址 <a href=\"https://github.com/ocsjs/ocsjs/commit/013e3ba037b38b88a4728c4cbd478cd4e518953e\">></a>\n* 移除页面反调试脚本至超星 <a href=\"https://github.com/ocsjs/ocsjs/commit/77cb19c73df4fdacaac7120bdd748a49a06c8091\">></a>\n* 因全局变量删除，且需要封装 store 中多个变量的处理，以及防止变量污染，新增了 useContext 和 useSettings 的API，相应涉及代码同步更新。 <a href=\"https://github.com/ocsjs/ocsjs/commit/6d9fa5112e81ce278f16a8248c3f9dfe3fed8e13\">></a>\n* 优化 start 主函数，防止脚本重新执行 <a href=\"https://github.com/ocsjs/ocsjs/commit/bef652d3407ebd07033c2eeb88b1cef9d3d14190\">></a>\n* 优化超星直播回放 <a href=\"https://github.com/ocsjs/ocsjs/commit/0727bea2c3d9c549770cfa9f5f305afdea91825f\">></a>\n* 优化登录脚本，非空判断 <a href=\"https://github.com/ocsjs/ocsjs/commit/684c441401969c10172bf3c613058c750b5248d2\">></a>\n* 优化获取远程本地软件题库配置功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/09986b5acaa066c2b259a0e1b69f38ece2898b0c\">></a>\n* 优化题库解析器 <a href=\"https://github.com/ocsjs/ocsjs/commit/67b50a47fdc9fb5730e83d295249e93e046c12f7\">></a>\n* 优化题库配置字段 <a href=\"https://github.com/ocsjs/ocsjs/commit/d674f007c62c3882ad9426d8ee93c8360eef9347\">></a>\n* 优化图片识别时图片链接显示的问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/d9778f6268a4986a4ff3eed7542ee54f9e97242b\">></a>\n* 优化响应式存储 <a href=\"https://github.com/ocsjs/ocsjs/commit/b307f74140d382243c46dc80dafa372cf1b3ca47\">></a>\n* 优化页面通讯以及构建 <a href=\"https://github.com/ocsjs/ocsjs/commit/7a20041ce081f9675b447847ad8cf9324bf7305d\">></a>\n* 优化智慧树脚本，适配 useContext 和 useSettings 两个 API <a href=\"https://github.com/ocsjs/ocsjs/commit/a24d991e8a289dd524ae577036a4e165b92afbd1\">></a>\n* 优化自动答题逻辑，修复有答案不选的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/23df64d1d77a9556a1d105d72bc9810fb4c06c18\">></a>\n* 优化OCS环境加载问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/2742ed47892308879560742b1aa2bf2a08dbe6cf\">></a>\n* 暂时删除视频答题功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/b8eeba52b2d8a8fdd16a90a9f7a3f73d9d8cc722\">></a>\n* 增加答题器的 await 等待机制，多选题选择时需要 await 等待 <a href=\"https://github.com/ocsjs/ocsjs/commit/853a30d0b9ebac9dca8721e0ce8ce838056e2e11\">></a>\n* 修复 release.sh 打包文件错误时仍然执行的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/6f14bd854954f1415705d4086d41eb852d1854d0\">></a>\n* 优化构建发布release.sh文件 <a href=\"https://github.com/ocsjs/ocsjs/commit/a855740f0b8ffa107f5f580f2123c70baedb16f7\">></a>\n* 修复搜索结果显示与搜题不一致的BUG，优化各脚本，持续优化学习通任务检测算法。 <a href=\"https://github.com/ocsjs/ocsjs/commit/f49737aa6227be382d608b08f079cf46ef3f8649\">></a>\n* 持续修复超星问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/f2f08e20eb94466f5dd81dbb5a63ae7f33026911\">></a>\n* 登录时将脚本至于左上方防止挡住软件操作 <a href=\"https://github.com/ocsjs/ocsjs/commit/dc2bf1d4added39f3967276dc6a05490115251e1\">></a>\n* 删除超星的强制答题选项 <a href=\"https://github.com/ocsjs/ocsjs/commit/60bf03c6ecddb7a2faaee6667ac3033f4a6f4403\">></a>\n* 删除脚本展开功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/ec1f52798f4bf3b7fb1c4800445e3939a64b660b\">></a>\n* 删除浏览器窗口检测，经过测试全屏游戏中也会进行误报。 <a href=\"https://github.com/ocsjs/ocsjs/commit/bf30309f7cb331b239dc74d3733fa433c8dbcf2c\">></a>\n* 使用固定端口与桌面软件通讯 <a href=\"https://github.com/ocsjs/ocsjs/commit/7bc6de47bb0686f2353fbcbe56ed75bc93a5b95e\">></a>\n* 添加环境检测，优化职教云逻辑 <a href=\"https://github.com/ocsjs/ocsjs/commit/709e673a4ed5efe755238b45645f4878d079117c\">></a>\n* 修复超星多个视频同时播放的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/51ea1b0a43c32d596450c39d3940e9f767287583\">></a>\n* 修复超星频繁验证码的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/8544e876d4ef63a7f6c3a918a9294f5feb8c5109\">></a>\n* 修复超星章节测试填空题不填的问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/05a3cd11dadaa1700032b357727a4cf3ea807262\">></a>\n* 修复超星BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/8fe0a4049d504d91e004c9a42ad3ac9ddea560b7\">></a>\n* 修复窗口移除页面问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/bf2017ae7d54c44cc8843ad5e361cd24751017b8\">></a>\n* 修复答题器出现错误时会一直卡死 <a href=\"https://github.com/ocsjs/ocsjs/commit/0f02745f020920b2f2fe7d8ab15f0e53509ae38c\">></a>\n* 修复复制粘贴解除限制的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/ebded937fa9a47926d7a977bcaae6f55597868b3\">></a>\n* 修复通用-全局设置中题库状态检测BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/ebd6536e26369eefc5d3ad8aa471e087f4f95b06\">></a>\n* 修复学习通刷课逻辑 <a href=\"https://github.com/ocsjs/ocsjs/commit/831f1d33c7a9b350fe44c3121c6120f27720a2e4\">></a>\n* 修复智慧树答题后不保存的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/c56b54f701d63feb3a47a2d53890348264e68be8\">></a>\n* 修复智慧树作业答题完成后选择BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/8abf168d9efd5e8e170e8faa85b3285d7c847305\">></a>\n* 优化播放函数 <a href=\"https://github.com/ocsjs/ocsjs/commit/4204594bc70fdd91d93f41bfd3975fba2a99e47a\">></a>\n* 优化超星和智慧树脚本 <a href=\"https://github.com/ocsjs/ocsjs/commit/cb5fe710be11d5652414b5470912a70a8bd2cc15\">></a>\n* 优化窗口加载问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/e5cba2c809e6c385345e46121dc34afbacecc243\">></a>\n\n\n### ✨ 更新内容\n\n* 脚本2.0与软件4.0更新完毕 <a href=\"https://github.com/ocsjs/ocsjs/commit/6e043692f0fcfd4b5d56d785c2787c1bbdd22c9d\">></a>\n* 脚本更新至 4.0 ， 软件更新至 2.0 <a href=\"https://github.com/ocsjs/ocsjs/commit/7cb995f6d062ba79e09852f42be3565b8f107cd0\">></a>\n* 将 electron 升级到 23.0.0 ， 删除 electron 原生窗口样式 frame: false，自定义 titlebar ，并优化主题切换。 <a href=\"https://github.com/ocsjs/ocsjs/commit/961235fab116575b88aa5f71f0ca6aac87f71c0c\">></a>\n* 将OCR识别模块整合打包，修复OCR路径存在空格执行失败的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/9609af6d6f53b72b010de69e9df89294d0b27faa\">></a>\n* 删除超星智慧树的其他登录，新增自定义链接进入。 <a href=\"https://github.com/ocsjs/ocsjs/commit/58515115728dfacfee83e1c67c3bb1efcb4ddb59\">></a>\n* 文件中新增图片预览一栏，定时更新页面图像，无需在多个浏览器中切换查看。 <a href=\"https://github.com/ocsjs/ocsjs/commit/526dbd63e7bfa57828d1950517644801021db91b\">></a>\n* 新增网页打开脚本，将ocr模块一同打包 <a href=\"https://github.com/ocsjs/ocsjs/commit/7686cf1a5a9d462e0ce0b8a3ff7c0e6235da562d\">></a>\n* 新增仪表盘功能，新增浏览器拓展管理功能，新增新手教程功能，新增批量运行文件功能，并重写文件管理系统。 <a href=\"https://github.com/ocsjs/ocsjs/commit/8adfede9f790cdeae588c95a3e5d7134802fffb1\">></a>\n* 新增用户脚本，可自定义网络脚本添加到本地脚本，并自动载入本地脚本，极大拓展软件功能。 <a href=\"https://github.com/ocsjs/ocsjs/commit/7926b354ffc9f14dc2404f45dd869da96495a7f2\">></a>\n* 新增资源加载器，优化各种资源加载问题，移除原有的OCR模块打包方式。 <a href=\"https://github.com/ocsjs/ocsjs/commit/1b83d8af6388fa568d83521c06e9322736c31b48\">></a>\n* 修改app主进程从 commonjs 改成 ts <a href=\"https://github.com/ocsjs/ocsjs/commit/3e29f821cd1f5414c68ae0f944c512f8e94bdfb3\">></a>\n* 优化 script 工作线程，规范代码，修复人工错误导致的无限递归。 <a href=\"https://github.com/ocsjs/ocsjs/commit/156b2f71ed4d42fae620b89c179f3b86ebaf2064\">></a>\n* 新增软件服务端，使脚本可读取软件信息。 <a href=\"https://github.com/ocsjs/ocsjs/commit/6f4ff54bb3a83fcb273a48ef41b5ba973d6d2f01\">></a>\n* - 拆分 core ，- 添加多线程答题 - 添加超星作业考试功能 - 将 core/utils 全部进行 $ 前缀声明以便区分 - 优化搜题结果元素。 <a href=\"https://github.com/ocsjs/ocsjs/commit/a252e855fb03d8335c28cb563c60b96a9317a0c1\">></a>\n* 4.0 init <a href=\"https://github.com/ocsjs/ocsjs/commit/4a43ee9bbb4af37ebf4317f87f5508067acd5139\">></a>\n* 4.0 init <a href=\"https://github.com/ocsjs/ocsjs/commit/7151eb3643054021feadcdfb512871ba3916e4e1\">></a>\n* 持续优化zhs答题功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/b120ce718ea84db17a6d5fc3f43ad3214139b7de\">></a>\n* 处理 onbeforeunload 执行结果 <a href=\"https://github.com/ocsjs/ocsjs/commit/9e0fc87fad7652ab57bc3ce1080c3cf1ecb294e7\">></a>\n* 给 script 的全部声明周期同样添加相同的事件触发 <a href=\"https://github.com/ocsjs/ocsjs/commit/289c0d8c746f60b9e69bc0dc22a1516505592db9\">></a>\n* 将 OCSWoker 和 Script 都变成 EventEmitter 对象，实现内部的事件分发 <a href=\"https://github.com/ocsjs/ocsjs/commit/2e0872cf6cf2faa6088dbd1235aa32933b2b466f\">></a>\n* 删除窗口关闭按钮，用户可以通过“窗口设置”进行隐藏窗口。 <a href=\"https://github.com/ocsjs/ocsjs/commit/4a447e4d5deb60dc2078862f4fb3dc36d92e9a67\">></a>\n* 搜索结果页面新增复制题目按钮 <a href=\"https://github.com/ocsjs/ocsjs/commit/4cbd03217678a9426806f98828d76f2da664ba20\">></a>\n* 添加 $creator 元素创建工具类变量，并且重写登录脚本，使用动态添加元素的方式重写。 <a href=\"https://github.com/ocsjs/ocsjs/commit/ba94b165eaefe2b95d5b349ce60fa88aec0d7f19\">></a>\n* 添加 $modal.onClose 不管关闭还是确认和取消都会触发此函数 <a href=\"https://github.com/ocsjs/ocsjs/commit/5cec1984bbf4da4e2bf8f518f9ba858a0c1bc340\">></a>\n* 添加 onhistorychange 钩子 <a href=\"https://github.com/ocsjs/ocsjs/commit/e8c1ecb9ecbe7ec1a842952a0f7be58ac1d7964f\">></a>\n* 添加 Script.configs 参数生产后 this.cfg 的代码提示 <a href=\"https://github.com/ocsjs/ocsjs/commit/d31704e2716ca298e134a87cdc58189372e7b834\">></a>\n* 添加 script.onrender 钩子 <a href=\"https://github.com/ocsjs/ocsjs/commit/a81404b1071be98d486c8331dc066b99f17fa004\">></a>\n* 添加 SearchResultsElement 元素 <a href=\"https://github.com/ocsjs/ocsjs/commit/d66dffb79ccab8726a4b6a8d90dc140f7637017e\">></a>\n* 添加 StringUtils 工具类 <a href=\"https://github.com/ocsjs/ocsjs/commit/4f1341a7d57d4b667b0c07a71f7cd1a3a31b0fa8\">></a>\n* 添加超星登录脚本 <a href=\"https://github.com/ocsjs/ocsjs/commit/9b2e086698cd2765d568538debcac75c6d701514\">></a>\n* 添加面板选择器分组功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/226d1eb8378d62bcb799e0c98b2a34e96fc9c446\">></a>\n* 添加全局跨域通讯对象,并使用此技术重写模态框调出方法 <a href=\"https://github.com/ocsjs/ocsjs/commit/81b1daff19850cbb084a5eb6564e88ef2be4db7e\">></a>\n* 添加使用教程, 重载 el 函数, 修改cors跨域模块使用 setTab, getTab 进行cors跨域标签分区. 优化页面选择逻辑 <a href=\"https://github.com/ocsjs/ocsjs/commit/eaa6ae5030fcfc31db905ed24969995d06014af3\">></a>\n* 添加页面复制粘贴限制解除脚本 <a href=\"https://github.com/ocsjs/ocsjs/commit/4acdfd018e8dd55360e78f66547d6a56541c4ffe\">></a>\n* 添加在线搜题功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/dc26b2e0e52d59d015546b960e533c09f2d39e27\">></a>\n* 添加智慧树自动答题功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/642901d470a3b69f826dc8c9f331cbd306ebd713\">></a>\n* 完成4.0基本架构 <a href=\"https://github.com/ocsjs/ocsjs/commit/23129f43581acab6e367b6ff38f0fe4b66f04708\">></a>\n* 完成脚本内容的元素和数值同步 <a href=\"https://github.com/ocsjs/ocsjs/commit/4d1dc60985b5b9623dd61a351babd8640f5a4259\">></a>\n* 完成智慧树登录脚本,学习脚本,修复BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/f07dd0fdaa468df626ba5fa03a503bfc8cb72bd5\">></a>\n* 新增 $creator 多个API <a href=\"https://github.com/ocsjs/ocsjs/commit/d245726df9f4847b8a8df47375a13e03b35d7fb4\">></a>\n* 新增 $message 和 $model 方法进行用户交互 <a href=\"https://github.com/ocsjs/ocsjs/commit/c6d365ce13f046ed93971d9991dfcfad3990b419\">></a>\n* 新增 通用-搜索结果 显示 <a href=\"https://github.com/ocsjs/ocsjs/commit/9d9ead85747ccd94f922d7f50573ff99d9b9761a\">></a>\n* 新增 智慧职教mooc脚本 ， 优化职教云脚本 <a href=\"https://github.com/ocsjs/ocsjs/commit/0e44978c57e81afcaf28b55c1adfdb4eac297db0\">></a>\n* 新增 Script.methods 方法可自定义对外暴露函数，优化搜索结果的显示。 <a href=\"https://github.com/ocsjs/ocsjs/commit/53043d1590f09c557ae0059233806dd43c06653e\">></a>\n* 新增 SCript.pin 方法，置顶某个面板 <a href=\"https://github.com/ocsjs/ocsjs/commit/766f87e3f03c6216706b67a1cd6801c55c08f78b\">></a>\n* 新增 unsafeWindow 全局变量 <a href=\"https://github.com/ocsjs/ocsjs/commit/9438a1efc4451adf5160ca9067407deaa9707c5b\">></a>\n* 新增超星直播回放视频脚本 <a href=\"https://github.com/ocsjs/ocsjs/commit/3788a6fc5bb852b4847025b4df88a5b9499ce4de\">></a>\n* 新增跨域响应式特性 <a href=\"https://github.com/ocsjs/ocsjs/commit/d7f967ec27fd3d67de9d8132dd60100765727384\">></a>\n* 新增智慧树：清晰度选择，定时停止，视频总时长计算，等功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/7204f26f7b2c3c6700df1f7d9d08a43371cd8c35\">></a>\n* 新增智慧树的视频画质选择功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/290ce51032e2a8b773b61275ada58002ade0aa22\">></a>\n* 新增智慧树视频反反混淆脚本 <a href=\"https://github.com/ocsjs/ocsjs/commit/5de857fb12f229fbed1284d507bac87500e5b6ce\">></a>\n* 新增智慧树验证码检测功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/dcf73b317cb7492a971a74c77af03e91a13eb1bb\">></a>\n* 新增智慧职教（职教云）脚本 <a href=\"https://github.com/ocsjs/ocsjs/commit/59c0cc2b66fa1af2418e682bec014342f3ed0e7f\">></a>\n* 新增j脚本热更新功能，大大提升开发效率 <a href=\"https://github.com/ocsjs/ocsjs/commit/42ba110b2cc8e54e043b73979216a8f22c9017fe\">></a>\n* 修改脚本声明写法，修改 project.scripts 由数组变成对象声明，好处是可以由 project.scripts.[脚本名].cfg.xxx 进行类型推断实现类型提示。 <a href=\"https://github.com/ocsjs/ocsjs/commit/b73fdb021f9b050278f321c7d353f81e8869d964\">></a>\n* 优化 cx 的 Project 新写法 <a href=\"https://github.com/ocsjs/ocsjs/commit/61f466a0e4e85a59985bb59c84ca7efc04975213\">></a>\n* 优化使用$creator <a href=\"https://github.com/ocsjs/ocsjs/commit/23efe4274f1b6604e85189a39bc47cdc7e5542b1\">></a>\n* 优化渲染工程 <a href=\"https://github.com/ocsjs/ocsjs/commit/31b5728843b44f244bb3abbf4f5f72f79259350e\">></a>\n* 优化智慧树文字识别脚本 <a href=\"https://github.com/ocsjs/ocsjs/commit/608e76076d63d3b22c55c0baaf2df008e0b32313\">></a>\n* 增加多线程查题功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/be3b85cdf382c9add8e5b155c2b1a97dbbb46bd4\">></a>\n* 增加智慧树习惯分检测，学习记录查询，答题手动控制 <a href=\"https://github.com/ocsjs/ocsjs/commit/e753da9cee93b0873e5d2be7b09c7770451cef7a\">></a>\n* 支持跨域调出模态框 <a href=\"https://github.com/ocsjs/ocsjs/commit/f1f427166d21622fed299399456e235386a69cb6\">></a>\n* 智慧职教（职教云）发布 <a href=\"https://github.com/ocsjs/ocsjs/commit/f0eb02f2ada42fb64a5f3767689ddf7af0c303e9\">></a>\n* 添加全局错误捕获功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/476483da346104b63766810953fa4a254ed0a72f\">></a>\n* 超星刷课逻辑重写，兼容旧版浏览器CSS样式 <a href=\"https://github.com/ocsjs/ocsjs/commit/184e0e9910aa1eccf55b89d6a360e9a218d648d8\">></a>\n* 添加浏览器版本检测 <a href=\"https://github.com/ocsjs/ocsjs/commit/5c81815d81ad1dad5ed998a5dfe14a4db0935021\">></a>\n* 添加页面关闭提示 <a href=\"https://github.com/ocsjs/ocsjs/commit/477fb01e8e92e14c71382daa24ce9852d7bd9c4a\">></a>\n* 添加智慧职教音频支持 <a href=\"https://github.com/ocsjs/ocsjs/commit/0e6402c019afbaeca24da2900c3a81771faa5249\">></a>\n* 新增【职教云】和【智慧职教】脚本 <a href=\"https://github.com/ocsjs/ocsjs/commit/0cf366a5ef082496a0108902fd58dfa874e9eb98\">></a>\n* 新增浏览器最小化检测脚本 <a href=\"https://github.com/ocsjs/ocsjs/commit/4934f6535ed2a7c1d0f078ba136b5213f5d8ba12\">></a>\n* 修改浏览器下载链接，新增脚本版本更新检测 <a href=\"https://github.com/ocsjs/ocsjs/commit/9265c2ba09639121c417e9042fcd0230ca3da00a\">></a>\n* version release 4.0.5 <a href=\"https://github.com/ocsjs/ocsjs/commit/d7c4574faa2da036a5b04f4a48b6840dd74461b9\">></a>\n* version update to 4.1.0 <a href=\"https://github.com/ocsjs/ocsjs/commit/216596fde62d1591696dde101a49eff27619085c\">></a>\n* 新增utils包，其内置各种实用工具。其中新增脚本打包器，可对打包流程进行优化。 <a href=\"https://github.com/ocsjs/ocsjs/commit/01879dd251cdb299df799631262c268a3de827af\">></a>\n\n\n### ⚡ 优化提升\n\n* 更新 MoelElement 参数以及实现 <a href=\"https://github.com/ocsjs/ocsjs/commit/219ca6faab611da9c9aa9c6afd68cdc5bcb3fa71\">></a>\n* 添加 SearchResultsElement 元素映射 <a href=\"https://github.com/ocsjs/ocsjs/commit/6f86f6f4dffc42d28983e902c677372703defa53\">></a>\n* 添加defineScript中的domain和hide字段，实现动态修改脚本头部信息的功能。 <a href=\"https://github.com/ocsjs/ocsjs/commit/a13ab5008ff34f589d9ddd0184c5e320afc2180a\">></a>\n\n\n\n# 3.13.0 (2022-05-23)\n\n\n### ✨ 更新内容\n\n* 添加页面反调试脚本 <a href=\"https://github.com/ocsjs/ocsjs/commit/1174617cee99dd4d2e546d79279160ba1afea40e\">></a>\n* 新增超星视频中答题功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/3f925cba72910fce0353df421dec75a137f24e4e\">></a>\n* 新增网课视频选项：显示视频进度 <a href=\"https://github.com/ocsjs/ocsjs/commit/b6086df349fa50434e199c88c8fff6414bed4c14\">></a>\n\n\n\n## 3.12.3 (2022-05-22)\n\n\n### 🔧 修复内容\n\n* 修复误删最大长宽导致的超出页面范围 <a href=\"https://github.com/ocsjs/ocsjs/commit/9b0bcd4d0f26fb8591950e73563be78a5c3d876d\">></a>\n\n\n\n## 3.12.2 (2022-05-21)\n\n\n### 🔧 修复内容\n\n* 修复某些填空题识别不出的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/c2c1c3d2a7fafbe569b25531070b1803e07ccfe6\">></a>\n\n\n### ✨ 更新内容\n\n* 新增自动答题选项：强制提交 <a href=\"https://github.com/ocsjs/ocsjs/commit/e0ff3a2c64d9e9de061889d2ad145ed29a492cb1\">></a>\n\n\n\n# 3.12.0 (2022-05-21)\n\n\n### 🔧 修复内容\n\n* 删除多余输出 <a href=\"https://github.com/ocsjs/ocsjs/commit/8cee0173c670633d665bddd24c4884fa3ad3f621\">></a>\n* 修改userjs打包代码未加分号报错 BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/e0ec319150ec2f42a4a1256ae9250a3bfd1ae779\">></a>\n* 优化多选题答案分割判断 <a href=\"https://github.com/ocsjs/ocsjs/commit/7da6f92074a4a9ced316be312aa664ea3f75af52\">></a>\n\n\n### ✨ 更新内容\n\n* 新增随机作答功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/ecc2a87c24e2e81e82a6f1d4fe737b3ccab69b8c\">></a>\n* 新增图片题识别脚本，新增搜索结果显示题目图片和答案图片 <a href=\"https://github.com/ocsjs/ocsjs/commit/fd483c3bbbe9af689e3b006c21ae12cdeccdd73a\">></a>\n\n\n\n# 3.11.0 (2022-05-21)\n\n\n### 🔧 修复内容\n\n* 新增未经压缩代码的打包 <a href=\"https://github.com/ocsjs/ocsjs/commit/4099fc428d6cfa3b835bf7c89f29bb63b11ad090\">></a>\n\n\n### ✨ 更新内容\n\n* 新增userjs未经压缩代码打包 <a href=\"https://github.com/ocsjs/ocsjs/commit/42badc85967abb2486c3a6895b8ffd8f9155f05a\">></a>\n\n\n\n## 3.10.6 (2022-05-20)\n\n\n### 🔧 修复内容\n\n* 修复题库配置解析器BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/3986a49a1323bc17f8cca54763586c23f6e03907\">></a>\n\n\n\n## 3.10.4 (2022-05-20)\n\n\n### 🔧 修复内容\n\n* 修复浏览器报错BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/fd3780c2d9fdb58016b06f090696056934888f89\">></a>\n* 修复超星考试页面样式问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/03fa9bed5e176377e921deffbacf0212ccd85e34\">></a>\n* 修复题库配置BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/2b8c9391cecb7aed7e55b47694772feaed2e45fc\">></a>\n\n\n\n## 3.10.2 (2022-05-19)\n\n\n### 🔧 修复内容\n\n* 还原文件，修改 release 执行错误但继续打包发布的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/201ae0f6face34db36c9edffe0e323e744ea0106\">></a>\n\n\n\n## 3.10.1 (2022-05-19)\n\n\n### 🔧 修复内容\n\n* 修复软件浏览器选择BUG，并停止火狐浏览器使用。 <a href=\"https://github.com/ocsjs/ocsjs/commit/5e2559bfdea087806246120da566410b54a39c0b\">></a>\n* 修改 typr 库，减少部分打包体积。 <a href=\"https://github.com/ocsjs/ocsjs/commit/51e26debe37c9ad45e181a9d67528244863e7b33\">></a>\n\n\n\n# 3.10.0 (2022-05-17)\n\n\n### ✨ 更新内容\n\n* 新增超星繁体字识别选项 - 字典识别 <a href=\"https://github.com/ocsjs/ocsjs/commit/2a241d6fe987316b335e57dd9b8b19be188f1805\">></a>\n* 新增超星强制答题功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/e7c4fc060c30b341c17fefa0148fde5170069c87\">></a>\n\n\n\n## 3.9.6 (2022-05-16)\n\n\n### 🔧 修复内容\n\n* 修复日志记录问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/c9f21473a755a9af6e7bbeed4095f2209d64cb6e\">></a>\n* 修复软件文件重命名时，运行文件名不同步的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/28a1af141217c83cc891952731b585153d6d3d59\">></a>\n* 修复智慧树登录后白屏错误的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/9622f7e4c809e64be76c2cd4309e01f1ba9a4e44\">></a>\n\n\n### ✨ 更新内容\n\n* 新增软件自动选择浏览器路径功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/eb56f67e6885badcfa252fc716ac6e203560fc2a\">></a>\n* 新增自定义脚本载入路径功能, 路径将托管到官方服务器 <a href=\"https://github.com/ocsjs/ocsjs/commit/289cefecae8b7c3ed900bba1cb99150438a0a842\">></a>\n\n\n\n## 3.9.5 (2022-05-16)\n\n\n### 🔧 修复内容\n\n* 修复新增 shadowroot 后，复制粘贴脚本失效的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/081a99ee62a47f427e02b33e3497e5f95e6dedfa\">></a>\n* 优化超星识别时可能遇到选项按钮被删除的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/ef396b4a3bc9f10284529c3cbb857edba9c927ca\">></a>\n\n\n### ✨ 更新内容\n\n* 新增 AnswererWrapper 参数: headers 和 type <a href=\"https://github.com/ocsjs/ocsjs/commit/8c970bb52a9b6ef83618b7f1dc2d55fa26045024\">></a>\n* 新增题库配置跨域模块，可对不同域名的服务器进行跨域访问，并且新增 root 环境变量，可访问元素题目的跟节点元素对象。 <a href=\"https://github.com/ocsjs/ocsjs/commit/4e8ea1c6bdc84a1b0ce84b24af48a91cff0830af\">></a>\n\n\n\n# 3.8.0 (2022-05-11)\n\n\n### 🔧 修复内容\n\n* 避免重复劫持函数导致页面内存移除 <a href=\"https://github.com/ocsjs/ocsjs/commit/067ee58e6ffa74657a8adf213ad32fcda0799243\">></a>\n* 修复智慧树倍速不能立刻改变的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/39d7402d804ad5adb8556150d58ae9277f97229f\">></a>\n\n\n### ✨ 更新内容\n\n* + 新增消息提示 + 修复火狐底部版本不显示的BUG  + 优化eslint代码 + 增加文字识别错误提示 + 将原有弹出框修改为消息提示 + 增加API:message <a href=\"https://github.com/ocsjs/ocsjs/commit/534bee3b6e449b9c725cbdec9efa27979c6545ed\">></a>\n* 使用ShadowRoot对脚本进行加固 <a href=\"https://github.com/ocsjs/ocsjs/commit/346d9d109499e303704f7a05414631d9c7e3b11c\">></a>\n\n\n\n\n\n\n\n\n\n\n\n## 3.7.2 (2022-05-07)\n\n\n### 🔧 修复内容\n\n* 修改 store 初始化位置 <a href=\"https://github.com/ocsjs/ocsjs/commit/808eb080a50e75ecef151010b74891ce272938c9\">></a>\n\n\n### ✨ 更新内容\n\n* 添加智慧树文本识别脚本和屏蔽视频检测脚本 <a href=\"https://github.com/ocsjs/ocsjs/commit/85ddac119874d5b6877dfc8fa29ecdff70fec4ed\">></a>\n\n\n\n# 3.7.0 (2022-05-04)\n\n\n### ✨ 更新内容\n\n* 添加答题等待时间，方便检查或者使用其他答题工具。 <a href=\"https://github.com/ocsjs/ocsjs/commit/12f2960a47f3eb22cc9bbf91bf49c2ace54ea89e\">></a>\n\n\n\n## 3.6.4 (2022-05-04)\n\n\n### 🔧 修复内容\n\n* 深度优化OCR <a href=\"https://github.com/ocsjs/ocsjs/commit/a21a8adf3b209ae5e65aff26c134da2a7b1f0fd2\">></a>\n* 修复填空题多个填空不填的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/4a0f031b244a800b1e6ff39d3b1b99160372e66d\">></a>\n\n\n\n## 3.6.2 (2022-04-30)\n\n\n### 🔧 修复内容\n\n* 修改 OCR 脚本加载路径, 确保能够访问 work 和 core 脚本 <a href=\"https://github.com/ocsjs/ocsjs/commit/9b1544a8e2b002d78471d387e507967e20281020\">></a>\n\n\n\n## 3.6.1 (2022-04-30)\n\n\n### 🔧 修复内容\n\n* 修改环境依赖 <a href=\"https://github.com/ocsjs/ocsjs/commit/9c69d3581a84a8e50d87dd7a5d7ed3f446e45295\">></a>\n\n\n\n# 3.6.0 (2022-04-29)\n\n\n### ✨ 更新内容\n\n* 新增智慧树共享课考试脚本 <a href=\"https://github.com/ocsjs/ocsjs/commit/5ba2022e084f7af9ef01309e7d88f8d42436732a\">></a>\n\n\n\n## 3.5.5 (2022-04-28)\n\n\n### 🔧 修复内容\n\n* 适当增大了文本便于识别 <a href=\"https://github.com/ocsjs/ocsjs/commit/483d68b44720babd1a2f8432661712d869433ad4\">></a>\n\n\n\n## 3.5.4 (2022-04-28)\n\n\n### 🔧 修复内容\n\n* 优化OCR空格问题，还有上个版本OCR锁初始化的问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/82a84d778aa99849f26fdd87447f41c7bdda72e2\">></a>\n\n\n\n## 3.5.3 (2022-04-28)\n\n\n### 🔧 修复内容\n\n* 优化 OCR ， 解决题目选项没有识别的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/e534658665be9863d19fc52ee787f7cad696bdd8\">></a>\n\n\n\n## 3.5.2 (2022-04-27)\n\n\n### 🔧 修复内容\n\n* 优化 OCR 加载逻辑 <a href=\"https://github.com/ocsjs/ocsjs/commit/9dd69dce3073b137999c753b50f7fec8ee03a8a2\">></a>\n\n\n\n## 3.5.1 (2022-04-27)\n\n\n### 🔧 修复内容\n\n* 优化 OCR 数据加载问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/ed958a9a47de90763daf7b59c7c1fa3abd698b7f\">></a>\n\n\n\n# 3.5.0 (2022-04-27)\n\n\n### 🔧 修复内容\n\n* 修复超星有时不能自动下一章的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/7c5516f731295ccc2fd73129bf659dd8b339d2f1\">></a>\n\n\n### ✨ 更新内容\n\n* 繁体字乱码识别功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/ec01cd168a4f0f4111e7cb6e3c1597b07d7dfd14\">></a>\n* 开发智慧树倍速选项 <a href=\"https://github.com/ocsjs/ocsjs/commit/83015b73bdb0bda4f23af04b3814b8cd7e21d721\">></a>\n\n\n\n## 3.4.5 (2022-04-23)\n\n\n### 🔧 修复内容\n\n* 修复软件重命名有时会无效的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/48fb520263fabb4740138705bf55d5086e425907\">></a>\n* 优化软件启动加载 <a href=\"https://github.com/ocsjs/ocsjs/commit/be866d1eb966a64aefe5fd95ffaf2c1c53ae76d8\">></a>\n\n\n\n## 3.4.4 (2022-04-22)\n\n\n### 🔧 修复内容\n\n* 修复提交设置的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/612dcccc40bec2871cccf92bb46c2210da76623e\">></a>\n\n\n\n## 3.4.3 (2022-04-21)\n\n\n### 🔧 修复内容\n\n* 删除无用的选项：搜题错误时暂停 <a href=\"https://github.com/ocsjs/ocsjs/commit/e30446c2627e628a8aa1c4bb7843c32734131e80\">></a>\n\n\n\n## 3.4.2 (2022-04-19)\n\n\n### 🔧 修复内容\n\n* 修复软件设置空白的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/f757a3b8ae6233eee6fd187471d866b7ec25028a\">></a>\n* 修复自动答题提交设置保存不了的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/fb9bd394c246b7446173fba0d956431c300b6577\">></a>\n* app version upate <a href=\"https://github.com/ocsjs/ocsjs/commit/505ce22fc11c799da5c22b6f183182ea0b43eb6d\">></a>\n\n\n\n## 3.4.1 (2022-04-17)\n\n\n### 🔧 修复内容\n\n* 修复ABCD纯答案直接点击的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/9df1222dfa7569ee0995284cbc988ddda7c292bc\">></a>\n\n\n\n# 3.4.0 (2022-04-17)\n\n\n### ✨ 更新内容\n\n* 新增智慧树学分课作业脚本 <a href=\"https://github.com/ocsjs/ocsjs/commit/978bf47feec507910ae62e0be60a79ffd8d46941\">></a>\n\n\n\n## 3.3.14 (2022-04-15)\n\n\n### 🔧 修复内容\n\n* 修复 release.sh 版本命令 <a href=\"https://github.com/ocsjs/ocsjs/commit/652a022b1f895e0ec848dc0b54f238d4f8192772\">></a>\n\n\n\n\n\n\n\n\n\n\n\n## 3.3.11 (2022-04-13)\n\n\n### 🔧 修复内容\n\n* 修复 paste 和 input 共存导致的粘贴BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/370e46fe3eecffbb5b63c5576dfe6447e79860bd\">></a>\n\n\n\n## 3.3.10 (2022-04-13)\n\n\n### 🔧 修复内容\n\n* 修复不能右键复制粘贴的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/9af5480038ba11bd47937bff889ee084c72fb04b\">></a>\n\n\n\n## 3.3.9 (2022-04-13)\n\n\n### 🔧 修复内容\n\n* 修复上个版本store加载问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/48fdc1a57fbcfa587f2dc42f338249a7b3222985\">></a>\n\n\n\n## 3.3.8 (2022-04-13)\n\n\n### 🔧 修复内容\n\n* app 兼容OCS助手最新版的响应式特性 <a href=\"https://github.com/ocsjs/ocsjs/commit/eaa66dd9a1c647f12602363f08003c9254ba0f7d\">></a>\n\n\n### ✨ 更新内容\n\n* 新增全局存储功能，使用油猴自带API实现。 <a href=\"https://github.com/ocsjs/ocsjs/commit/be13a5bd71884ef9f9913a8e2c391da9ecedef4b\">></a>\n\n\n\n## 3.3.7 (2022-04-13)\n\n\n### 🔧 修复内容\n\n* 支持纯浏览器端的加载，仅用于核心API的调用。 <a href=\"https://github.com/ocsjs/ocsjs/commit/cf9dbe8d779c05ba804aab43c242ddde1af8e7c6\">></a>\n\n\n### ✨ 更新内容\n\n* 删除调试输出 <a href=\"https://github.com/ocsjs/ocsjs/commit/a096447a45573e6bfb05d249aad374e2e56530dd\">></a>\n\n\n\n## 3.3.6 (2022-04-13)\n\n\n### 🔧 修复内容\n\n* 修复打包压缩选项 <a href=\"https://github.com/ocsjs/ocsjs/commit/9425289ed6cc864f95f59414783f88db1968d140\">></a>\n\n\n### ✨ 更新内容\n\n* 新增超星 : 屏蔽作业考试填空简答题粘贴限制 功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/591971eea5065b4097e6b24516cb6d5a36497f76\">></a>\n\n\n\n## 3.2.20 (2022-04-09)\n\n\n### 🔧 修复内容\n\n* 延长一点视频暂停后启动的时间，防止超星鬼畜。 <a href=\"https://github.com/ocsjs/ocsjs/commit/837d873d29146de7741e9cfb4b4e988633767551\">></a>\n\n\n\n## 3.3.5 (2022-04-13)\n\n\n### 🔧 修复内容\n\n* 修复搜索结果 undefined 的 BUG, 修复超星章节测试填空题BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/97500c591315eb4fdc97b799e77e788ccdf32def\">></a>\n\n\n\n## 3.3.4 (2022-04-13)\n\n\n### 🔧 修复内容\n\n* 修复超星考试作业页面不能复制粘贴的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/4c64c009f6bf87ad63d5b7f717daef339782bdc4\">></a>\n* 修复上个版本响应式导致的倍速，音量失效的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/5182d02d0fc1a9e11f7b7b2bc5ee3e0387c2b74e\">></a>\n* 修复智慧树复习模式的BUG，修复已经播放完的视频但没有完成却跳过的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/3cd12a72312faec10200a1a4bb700fe17e3e3682\">></a>\n\n\n\n## 3.3.3 (2022-04-12)\n\n\n### 🔧 修复内容\n\n* 兼容库模式，并且优化自动答题答案显示 <a href=\"https://github.com/ocsjs/ocsjs/commit/fcd2311b3b2564a3a06b2da102cdf841fbd19fc1\">></a>\n\n\n### ✨ 更新内容\n\n* 新增自动答题答案预览功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/29b17e8d055272882aebf672dbf76006f5509fc1\">></a>\n\n\n\n## 3.3.2 (2022-04-12)\n\n\n### 🔧 修复内容\n\n* 修复闯关模式因为刷新太快任务点没有出现导致重复的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/dad5eb3db14c112e7c8f65c6214402502f2a8764\">></a>\n* 修复答题配置输入后会消失的BUG， 新增音量设置 <a href=\"https://github.com/ocsjs/ocsjs/commit/34cd585203623e606e459fd7761f77001a0b6911\">></a>\n\n\n\n\n\n\n\n# 3.3.0 (2022-04-11)\n\n\n### 🔧 修复内容\n\n* 修复各种问题，并且兼容了响应式特性 <a href=\"https://github.com/ocsjs/ocsjs/commit/0c42947afa70f75f57435d5bebdd1dd48141754a\">></a>\n* 修改打包方式为压缩打包 <a href=\"https://github.com/ocsjs/ocsjs/commit/333661f4c3a5338721e56227ef0e6fcb53ea872f\">></a>\n\n\n### ✨ 更新内容\n\n* 切换网络路线 <a href=\"https://github.com/ocsjs/ocsjs/commit/0a0a5dd20ce80cb503f3c57d8b6aa2c25e261b8d\">></a>\n* 响应式特性 <a href=\"https://github.com/ocsjs/ocsjs/commit/5e21f0a1c17b1aca8ba22692c6649c6bae397d51\">></a>\n* vnode 重构成 tsx , 并且新增数据响应式特性 <a href=\"https://github.com/ocsjs/ocsjs/commit/18c5a7836cd11222f8301687fff930b5e93583c0\">></a>\n\n\n\n## 3.2.20 (2022-04-09)\n\n\n### 🔧 修复内容\n\n* 延长一点视频暂停后启动的时间，防止超星鬼畜。 <a href=\"https://github.com/ocsjs/ocsjs/commit/837d873d29146de7741e9cfb4b4e988633767551\">></a>\n\n\n\n## 3.2.19 (2022-04-08)\n\n\n### 🔧 修复内容\n\n* 修复切换路线后倍速无效的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/4e3f78b78f5ae3467d4546fc23d4aba0aa8fda4e\">></a>\n\n\n\n## 3.2.18 (2022-04-08)\n\n\n### ✨ 更新内容\n\n* 新增超星视频路线切换功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/8721e4895c46393b210a1f707b61f5b7a446ecee\">></a>\n\n\n\n## 3.2.16 (2022-04-08)\n\n\n### 🔧 修复内容\n\n* 修复快捷键有时候失效的问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/0d5df16cbd18936180935d02642cf68faea8c22f\">></a>\n\n\n\n## 3.2.14 (2022-04-08)\n\n\n### ✨ 更新内容\n\n* 新增隐藏按钮 <a href=\"https://github.com/ocsjs/ocsjs/commit/dce116e6f268bffe5ef1e178980c4318b47ea755\">></a>\n* 新增ocs快捷键，可重置位置，优化面板初始位置 <a href=\"https://github.com/ocsjs/ocsjs/commit/af7d231d9af520ace066190981196975a4d08213\">></a>\n\n\n\n## 3.2.13 (2022-04-07)\n\n\n### ✨ 更新内容\n\n* 彻底修复超星验证码问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/0cf69f22f070d47ed72cd4c1d5b898b6ad3e2e14\">></a>\n\n\n\n## 3.2.12 (2022-04-05)\n\n\n### ✨ 更新内容\n\n* 新增超星支持域名 edu.cn ， 修复多个视频播放时，播放完成继续播放的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/959cc9b2b38e6b573823ee5c9976f92089bdc5bb\">></a>\n\n\n\n## 3.2.11 (2022-04-04)\n\n\n### 🔧 修复内容\n\n* 修改点击间隔 <a href=\"https://github.com/ocsjs/ocsjs/commit/176d83eaaa210b562ab38458ee15a4969c6b080e\">></a>\n\n\n\n## 3.2.10 (2022-04-04)\n\n\n### 🔧 修复内容\n\n* 继续优化答题问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/2dfd14d57e195615a257ac21b0db6fa167714768\">></a>\n\n\n\n## 3.2.9 (2022-04-04)\n\n\n### 🔧 修复内容\n\n* 修复超星重复暂停的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/c5c768176e53c7266c9e90b97ec351943627143a\">></a>\n\n\n\n## 3.2.8 (2022-04-04)\n\n\n### ✨ 更新内容\n\n* 新增解除右键，复制粘贴限制的功能， 修复超星播放视频重复卡死的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/fe9b59ac28baded93f30539daf262a18870fedf0\">></a>\n\n\n\n## 3.2.7 (2022-04-04)\n\n\n### ✨ 更新内容\n\n* 答案判断优化 <a href=\"https://github.com/ocsjs/ocsjs/commit/801e4f3a0ffe66c6979833bae113f868bbba3f38\">></a>\n\n\n\n## 3.2.6 (2022-04-03)\n\n\n### 🔧 修复内容\n\n* 修复软件1.2.0自动更新BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/512b61c26f8b72828cd875b9ecf867dcc879f3b7\">></a>\n* 修复智慧树作业重复关闭的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/2288d6119af7efa97761e2bc8cca3393b62e507f\">></a>\n\n\n### ✨ 更新内容\n\n* 新增 common 包 ， 修复脚本执行BUG ， 修复通知 ， 新增打包脚本 scripts 文件夹 <a href=\"https://github.com/ocsjs/ocsjs/commit/083110233cc5462ed5d7d2c44bfc7141f9c8f9e8\">></a>\n* aPP版本更新 1.2.0 <a href=\"https://github.com/ocsjs/ocsjs/commit/4ebd3cbdc5775c7c4f60fd6c00f621297fed7d00\">></a>\n\n\n\n## 3.2.5 (2022-04-01)\n\n\n### 🔧 修复内容\n\n* 修复答题时多选不全的BUG， 修复答题时答案为空报错的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/75177b553b269282b16e812c6e65e1ffb8edad01\">></a>\n* 修改远程信息获取路径 <a href=\"https://github.com/ocsjs/ocsjs/commit/b80d0914bd91482d2e75978d3d6676a92717d91e\">></a>\n\n\n### ✨ 更新内容\n\n* 自动更新功能， scripts 分包 ， 浏览器端修改为 core 文件夹 <a href=\"https://github.com/ocsjs/ocsjs/commit/f53661497d92911c4daea12ae2936471169b3e5a\">></a>\n\n\n\n## 3.2.4 (2022-03-31)\n\n\n### 🔧 修复内容\n\n* 修复上个版本视频跳过问题 <a href=\"https://github.com/ocsjs/ocsjs/commit/1719bbdfba8cc21bb10cad8c16ec7ae6127ae6af\">></a>\n* 修复油猴脚本更新BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/fd043ddc3a6d9d3a3863c158379d7e1afd37a1d6\">></a>\n\n\n\n## 3.2.3 (2022-03-30)\n\n\n### ✨ 更新内容\n\n* 添加任务点是否完成检测，不重复执行已完成任务点， 修复判断题不选择的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/dbc1a86c5793cecfbd5521ee24d4373da3b3ee5e\">></a>\n\n\n\n## 3.2.2 (2022-03-27)\n\n\n### ✨ 更新内容\n\n* 添加智慧树学分课脚本 <a href=\"https://github.com/ocsjs/ocsjs/commit/37b112ae8f1488b4b07ce4a986ac798df8d0bd9e\">></a>\n\n\n\n## 3.2.1 (2022-03-27)\n\n\n### ✨ 更新内容\n\n* 添加禁止弹窗脚本 <a href=\"https://github.com/ocsjs/ocsjs/commit/2b6be413847fa55367d72ee5b16570d8ef8a218b\">></a>\n\n\n\n# 3.2.0 (2022-03-26)\n\n\n### ✨ 更新内容\n\n* 软件更新，支持超星学习作业考试，支持脚本自动更新 <a href=\"https://github.com/ocsjs/ocsjs/commit/f500119bed4d05d775b635de672c3b3e16e5a363\">></a>\n\n\n\n## 3.1.11 (2022-03-26)\n\n\n### ✨ 更新内容\n\n* 更新软件，加载时自动更新ocs脚本 <a href=\"https://github.com/ocsjs/ocsjs/commit/e16d954ff5bb4a084fa4dd6ff4e6dc6b9643f483\">></a>\n\n\n\n## 3.1.10 (2022-03-26)\n\n\n### 🔧 修复内容\n\n* 修复脚本只能在油猴环境下执行的BUG <a href=\"https://github.com/ocsjs/ocsjs/commit/4f081c90ee20e67a7f4e2afb43098e0c4308201e\">></a>\n\n\n\n\n\n\n\n## 3.1.8 (2022-03-26)\n\n\n### 🔧 修复内容\n\n* 修改答案结果判断bug <a href=\"https://github.com/ocsjs/ocsjs/commit/a2808d09b6ffa37376b25682cc4c8cd1cf0be5d7\">></a>\n\n\n\n## 3.1.7 (2022-03-25)\n\n\n### 🔧 修复内容\n\n* 修改题库配置设置，取消必选，添加判断，如果没有题库设置，则不开始自动答题。 <a href=\"https://github.com/ocsjs/ocsjs/commit/b25ef9323d0fe5c9d8437e3f50551a5e47e20dfa\">></a>\n* 修改油猴配置 <a href=\"https://github.com/ocsjs/ocsjs/commit/db8a733adaadb263fa2ed05233bd0b03f93c2e68\">></a>\n* 兼容cx选项获取不到，以及设置保存bug <a href=\"https://github.com/ocsjs/ocsjs/commit/5a2e67214d45053475574f4e16d3023909f6b132\">></a>\n* 修复答题解析器bug， 更新做题标题获取，修复学习时章节测验类型获取失败bug， 更新 app 的脚本 <a href=\"https://github.com/ocsjs/ocsjs/commit/659955b818b41b7f25506e93caa888392e37aba1\">></a>\n* 修改答题配置解析器 <a href=\"https://github.com/ocsjs/ocsjs/commit/6e4332f3855319e59f0bf5981fb713b7463cb531\">></a>\n* 修改样式引入 <a href=\"https://github.com/ocsjs/ocsjs/commit/8a6ab903ef661b89bc81bdc2298cdb28c2f40f9e\">></a>\n* 修改 url cdn 资源 <a href=\"https://github.com/ocsjs/ocsjs/commit/5cad7c19b6976d5825c982ccbede011d157b94a8\">></a>\n\n\n\n# 3.0.0-beta.9 (2022-03-24)\n\n\n### 🔧 修复内容\n\n* 修改数据路径 <a href=\"https://github.com/ocsjs/ocsjs/commit/c97bdaf00aef72ce9c71068573cd4d9933d15590\">></a>\n\n\n### ✨ 更新内容\n\n* 版本更新至 beta.6 <a href=\"https://github.com/ocsjs/ocsjs/commit/cc63a076ddb79235f8ca12c0c30b500c5d8be43e\">></a>\n* 新增答题器，新增题库配置选项，新增zhs作业功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/449c008af2f961c6a6ca12788309fabcc8334cfc\">></a>\n* 修改为单进程软件，支持.ocs文件的点击加载，新增软件标题栏，删除原生标题栏 <a href=\"https://github.com/ocsjs/ocsjs/commit/8427936e931ca8bfcc86240393d40c7583f95abc\">></a>\n* 版本更新 <a href=\"https://github.com/ocsjs/ocsjs/commit/3b17a9242d6cc12330e8bbb5ff15c88957507ce2\">></a>\n* 添加超星学习，考试，作业脚本。 添加日志，搜题结果面板。 <a href=\"https://github.com/ocsjs/ocsjs/commit/94d99d4fb32ce9b58e88e0fa8f0812adea7f89a6\">></a>\n\n\n\n# 3.0.0-beta.6 (2022-03-15)\n\n\n### ✨ 更新内容\n\n* 新增 script/browser 端的面板显示，替换之前的油猴头部信息加载模式，添加app主进程端的脚本调用 <a href=\"https://github.com/ocsjs/ocsjs/commit/f9cf10f6757a45d520c1c2ff5532bceaa9298f07\">></a>\n* 添加文件关闭编辑功能，取消页面所有动画效果，删除'关于'页面，新增'帮助'一栏。 <a href=\"https://github.com/ocsjs/ocsjs/commit/1f4d4ad7da78d97282524dd9325fc9c4ac1468f5\">></a>\n\n\n\n# 3.0.0-beta.5 (2022-03-07)\n\n\n### 🔧 修复内容\n\n* fix dependency security <a href=\"https://github.com/ocsjs/ocsjs/commit/948662a580aab60a3ff70c64111aa36c40b5a4ce\">></a>\n* 优化侧边栏： 优化文件列表头部，优化文件列表搜索 <a href=\"https://github.com/ocsjs/ocsjs/commit/84acc62870db173211165408c8fe665df556ebf0\">></a>\n\n\n### ✨ 更新内容\n\n* add glup <a href=\"https://github.com/ocsjs/ocsjs/commit/15c0015141f4104888813dda905fc723843f66ca\">></a>\n* 添加终端显示，添加脚本执行，添加文件属性 <a href=\"https://github.com/ocsjs/ocsjs/commit/cf5dc9a2d17057c5ac2db7df43e9e2a5b26e664f\">></a>\n* 添加文件夹管理，添加右键菜单，初始化设置和关于页面 <a href=\"https://github.com/ocsjs/ocsjs/commit/24930ad1e32508e227fb36b7008fb2981438cbb7\">></a>\n* add elctron-builder in app , init  page view in web <a href=\"https://github.com/ocsjs/ocsjs/commit/fe60df65dfeed1607ab89a941fc6b6eb627132fc\">></a>\n* init project <a href=\"https://github.com/ocsjs/ocsjs/commit/0675ac6a631e8946a52e7e4e655b28faee8248d4\">></a>\n* init packages : web app scripts <a href=\"https://github.com/ocsjs/ocsjs/commit/24e5386ec86dec33cc696fda6b5956785e2c1359\">></a>\n* add commander line support, and update web view <a href=\"https://github.com/ocsjs/ocsjs/commit/35efb39f34e4dc0d4e4914516f95d0ff467f8f37\">></a>\n* add cx and zhs login <a href=\"https://github.com/ocsjs/ocsjs/commit/8d69e166fd73cf2ae2d700aa772b78159efbdeaa\">></a>\n* change folder name, and add browser script <a href=\"https://github.com/ocsjs/ocsjs/commit/c540500b0cef50e7c2944d3ea6331e93c3e00e52\">></a>\n* add tempermonkey support, update browser export <a href=\"https://github.com/ocsjs/ocsjs/commit/456af0250ff33fada0930f01270d1e147f27e2f7\">></a>\n* add new login : phone-code login <a href=\"https://github.com/ocsjs/ocsjs/commit/494e523ffda8ddf5c9320be5861b42234fab4d91\">></a>\n* 修改 package.json ， 调整登录api <a href=\"https://github.com/ocsjs/ocsjs/commit/614c628a749bc6dd4e1556fd9fbdb026c82d6937\">></a>\n* add script package <a href=\"https://github.com/ocsjs/ocsjs/commit/6f03fb4d3545f02f8ccfeb14fa7b5d8169c15348\">></a>\n* update tests README.md and add new login way of cx : phone-code-login <a href=\"https://github.com/ocsjs/ocsjs/commit/ab46a1df98c73351518847a629ddaeb7f51a2d4d\">></a>\n* 添加文件解析，使用懒加载进行文件的显示 <a href=\"https://github.com/ocsjs/ocsjs/commit/0da0f024741191ce408354ffc148c83dc7bea4f3\">></a>\n* 添加文件拖拽，文件搜索功能 <a href=\"https://github.com/ocsjs/ocsjs/commit/fb1b7c93c1679e484769362724c2818c68898145\">></a>\n* 添加重命名功能，添加目录展开记录保存，添加帮助页面 <a href=\"https://github.com/ocsjs/ocsjs/commit/0b90cc20d33c21b406b7d6426df812aed03eec1c\">></a>\n* 文件编辑，文件拓展 <a href=\"https://github.com/ocsjs/ocsjs/commit/9f2f4fb6b4fd37b7526239bacf45135ff5874cbc\">></a>\n* 支持文件（夹）拖拽放置，文件（夹）的创建，删除 <a href=\"https://github.com/ocsjs/ocsjs/commit/705cc210bf382a3a92962f03526366edc0e18398\">></a>\n\n\n\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\r\n\r\nCopyright (c) 2022 enncy\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy\r\nof this software and associated documentation files (the \"Software\"), to deal\r\nin the Software without restriction, including without limitation the rights\r\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\ncopies of the Software, and to permit persons to whom the Software is\r\nfurnished to do so, subject to the following conditions:\r\n\r\nThe above copyright notice and this permission notice shall be included in all\r\ncopies or substantial portions of the Software.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r\nSOFTWARE."
  },
  {
    "path": "README.md",
    "content": "<div align=\"center\">\n\n<div style=\"padding:8px;border-radius:100%;background:white;width:124px;height:124px\">\n<img src=\"https://cdn.ocsjs.com/resources/img/logo.png\" width=124 height=124  >\n</div>\n\n# OCS 网课助手\n\n> OCS (Online Course Script) 网课刷课脚本，帮助大学生解决网课难题\n\n![GitHub Repo stars](https://img.shields.io/github/stars/ocsjs/ocsjs)\n![npm](https://img.shields.io/npm/v/ocsjs?color=red)\n![NPM](https://img.shields.io/npm/l/ocsjs)\n![今日安装](https://img.shields.io/badge/dynamic/json?color=orange&label=今日安装&query=$.data.today_install&url=https://scriptcat.org/api/v2/scripts/367)\n![总共安装](https://img.shields.io/badge/dynamic/json?color=red&label=总共安装&query=$.data.total_install&url=https://scriptcat.org/api/v2/scripts/367)\n\n</div>\n \n<div align=\"center\">\n\n## 官网及教程 [https://docs.ocsjs.com](https://docs.ocsjs.com)\n\n</div>\n"
  },
  {
    "path": "package.json",
    "content": "{\n\t\"name\": \"ocsjs\",\n\t\"version\": \"4.13.4\",\n\t\"description\": \"ocs - online course script 在线网络课程辅助工具\",\n\t\"types\": \"./lib/src/core/index.d.ts\",\n\t\"main\": \"./lib/src/core/index.js\",\n\t\"files\": [\n\t\t\"lib\",\n\t\t\"dist\"\n\t],\n\t\"scripts\": {\n\t\t\"dev\": \"gulp -f ./scripts/dev-core.js & npm run changelog:simplify\",\n\t\t\"build\": \"gulp -f ./scripts/build-core.js\",\n\t\t\"tsc\": \"pnpm lint && gulp -f ./scripts/tsc.js\",\n\t\t\"lint\": \"pnpm format && eslint  ./packages --ext .ts,.tsx,.js,.jsx --fix\",\n\t\t\"format\": \"prettier -c ./.prettierrc.json  **/*.ts  **/*.js  **/*.css  --write\",\n\t\t\"release\": \"sh ./scripts/release.sh\",\n\t\t\"pub:minor\": \"npm version minor && pnpm build:core && npm publish\",\n\t\t\"pub:major\": \"npm version major && pnpm build:core && npm publish\",\n\t\t\"changelog:simplify\": \"gulp -f ./scripts/simplify_changelog.js\",\n\t\t\"changelog\": \"conventional-changelog -p angular -i CHANGELOG.md --same-file -r 0\",\n\t\t\"changelog:current\": \"conventional-changelog -p angular -o CHANGELOG_CURRENT.md -r 1\",\n\t\t\"init-commitizen\": \"commitizen init cz-conventional-changelog --save --save-exact\"\n\t},\n\t\"devDependencies\": {\n\t\t\"@html-eslint/eslint-plugin\": \"^0.15.0\",\n\t\t\"@types/node\": \"^17.0.16\",\n\t\t\"@typescript-eslint/eslint-plugin\": \"^5.19.0\",\n\t\t\"@typescript-eslint/parser\": \"^5.19.0\",\n\t\t\"browser-env\": \"^3.3.0\",\n\t\t\"conventional-changelog-cli\": \"^2.2.2\",\n\t\t\"cz-conventional-changelog\": \"^3.3.0\",\n\t\t\"del\": \"^6.0.0\",\n\t\t\"eslint\": \"^7.32.0\",\n\t\t\"eslint-config-prettier\": \"^8.5.0\",\n\t\t\"eslint-config-standard\": \"^16.0.3\",\n\t\t\"eslint-plugin-import\": \"^2.22.1\",\n\t\t\"eslint-plugin-node\": \"^11.1.0\",\n\t\t\"eslint-plugin-prettier\": \"^4.0.0\",\n\t\t\"eslint-plugin-promise\": \"^4.2.1\",\n\t\t\"eslint-plugin-vue\": \"^9.8.0\",\n\t\t\"gulp\": \"^4.0.2\",\n\t\t\"gulp-cli\": \"^2.3.0\",\n\t\t\"gulp-zip\": \"^5.1.0\",\n\t\t\"prettier\": \"^2.6.2\",\n\t\t\"typescript\": \"^4.5.5\"\n\t},\n\t\"repository\": {\n\t\t\"type\": \"git\",\n\t\t\"url\": \"git+https://github.com/ocsjs/ocsjs.git\"\n\t},\n\t\"keywords\": [\n\t\t\"ocs\",\n\t\t\"script\",\n\t\t\"playwright\",\n\t\t\"puppeteer\",\n\t\t\"electron\",\n\t\t\"vue\",\n\t\t\"ant-design-vue\",\n\t\t\"typescript\"\n\t],\n\t\"author\": \"enncy\",\n\t\"license\": \"MIT\",\n\t\"bugs\": {\n\t\t\"url\": \"https://github.com/ocsjs/ocsjs/issues\"\n\t},\n\t\"homepage\": \"https://github.com/ocsjs/ocsjs#readme\",\n\t\"config\": {\n\t\t\"commitizen\": {\n\t\t\t\"path\": \"./node_modules/cz-conventional-changelog\"\n\t\t}\n\t},\n\t\"dependencies\": {\n\t\t\"@microsoft/tsdoc\": \"^0.14.2\"\n\t}\n}\n"
  },
  {
    "path": "packages/core/.gitignore",
    "content": "# customize\n\ntest/\nstats.html\n\nsrc/assets/css\n\ndocs/\n\n# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\nlerna-debug.log*\n.pnpm-debug.log*\n\n# Diagnostic reports (https://nodejs.org/api/report.html)\nreport.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n*.lcov\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Bower dependency directory (https://bower.io/)\nbower_components\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (https://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules/\njspm_packages/\n\n# Snowpack dependency directory (https://snowpack.dev/)\nweb_modules/\n\n# TypeScript cache\n*.tsbuildinfo\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Optional stylelint cache\n.stylelintcache\n\n# Microbundle cache\n.rpt2_cache/\n.rts2_cache_cjs/\n.rts2_cache_es/\n.rts2_cache_umd/\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variable files\n.env\n.env.development.local\n.env.test.local\n.env.production.local\n.env.local\n\n# parcel-bundler cache (https://parceljs.org/)\n.cache\n.parcel-cache\n\n# Next.js build output\n.next\nout\n\n# Nuxt.js build / generate output\n.nuxt\ndist\n\n# Gatsby files\n.cache/\n# Comment in the public line in if your project uses Gatsby and not Next.js\n# https://nextjs.org/blog/next-9-1#public-directory-support\n# public\n\n# vuepress build output\n.vuepress/dist\n\n# vuepress v2.x temp and cache directory\n.temp\n.cache\n\n# Docusaurus cache and generated files\n.docusaurus\n\n# Serverless directories\n.serverless/\n\n# FuseBox cache\n.fusebox/\n\n# DynamoDB Local files\n.dynamodb/\n\n# TernJS port file\n.tern-port\n\n# Stores VSCode versions used for testing VSCode extensions\n.vscode-test\n\n# yarn v2\n.yarn/cache\n.yarn/unplugged\n.yarn/build-state.yml\n.yarn/install-state.gz\n.pnp.*"
  },
  {
    "path": "packages/core/.npmignore",
    "content": "# customize\n\ntest/\nstats.html\n\nassets/\nsrc/\n  \n# Dependency directories\nnode_modules/\n "
  },
  {
    "path": "packages/core/package.json",
    "content": "{\n\t\"name\": \"@ocsjs/core\",\n\t\"version\": \"4.0.4\",\n\t\"description\": \"core package of ocs\",\n\t\"main\": \"./lib/index.js\",\n\t\"types\": \"./lib/index.d.ts\",\n\t\"files\": [\n\t\t\"lib\",\n\t\t\"dist\"\n\t],\n\t\"repository\": {\n\t\t\"type\": \"git\",\n\t\t\"url\": \"git+https://github.com/ocsjs/ocsjs.git\"\n\t},\n\t\"keywords\": [\n\t\t\"ocs\",\n\t\t\"script\",\n\t\t\"playwright\",\n\t\t\"puppeteer\",\n\t\t\"electron\",\n\t\t\"vue\",\n\t\t\"ant-design-vue\",\n\t\t\"typescript\"\n\t],\n\t\"author\": \"enncy\",\n\t\"license\": \"MIT\",\n\t\"bugs\": {\n\t\t\"url\": \"https://github.com/ocsjs/ocsjs/issues\"\n\t},\n\t\"homepage\": \"https://github.com/ocsjs/ocsjs#readme\",\n\t\"devDependencies\": {\n\t\t\"@types/event-emitter\": \"^0.3.3\",\n\t\t\"@types/lodash\": \"^4.14.194\",\n\t\t\"@types/string-similarity\": \"^4.0.0\",\n\t\t\"@types/tampermonkey\": \"^4.0.5\",\n\t\t\"dotenv\": \"^16.0.3\",\n\t\t\"rollup-plugin-visualizer\": \"^5.9.0\",\n\t\t\"typedoc\": \"^0.23.23\",\n\t\t\"vite\": \"^2.9.15\",\n\t\t\"vite-plugin-banner\": \"^0.6.1\"\n\t},\n\t\"dependencies\": {\n\t\t\"easy-us\": \"^0.0.60\",\n\t\t\"events\": \"^3.3.0\",\n\t\t\"lodash\": \"^4.17.21\",\n\t\t\"playwright-core\": \"^1.40.1\",\n\t\t\"semver\": \"^7.3.5\",\n\t\t\"string-similarity\": \"^4.0.4\"\n\t}\n}\n"
  },
  {
    "path": "packages/core/src/core/answer-wrapper/answer.wrapper.handler.ts",
    "content": "import { AnswererWrapper, SearchInformation, Result } from './interface';\nimport { request } from '../utils/request';\nimport { $ } from '../../utils';\n\nexport const AnswerWrapperHandlerConfig = {\n\t// 超时时间，单位毫秒\n\ttimeout_seconds: 60\n};\n\n/**\n *\n * 默认题库配置解析器\n *\n * @example\n *\n * ```js\n *\n * // 假设有一个接口 : https://example.com/search?title=1+2,2+3\n * // 此接口返回 {code: 1, data: { answers: [3 , 5] , title:'1+2' }, msg:'成功'}\n *\n * defaultAnswerWrapperHandler({\n *      titleElements: Array.from(document.querySelector('.title'))\n * },\n * [\n *  // 可以有多个构造器，最终通过 answerPath 一起合并到一个列表并返回\n *  {\n *      url: 'https://example.com/search',\n *      method: 'get',\n *      answerPath: 'data.answers',\n *      data:{\n *          title: 'titleElements[0]' // 1+2,2+3\n *      }\n *  }\n * ]) // [3 , 5]\n *\n *\n * ```\n *\n * @param elements 题目元素\n * @param answererWrappers 题库配置器数组\n * @returns\n */\nexport async function defaultAnswerWrapperHandler(\n\tanswererWrappers: AnswererWrapper[],\n\t// 上下文解析环境\n\tenv: {\n\t\ttitle?: string;\n\t\toptions?: string;\n\t\ttype?: string;\n\t\t[x: string]: any;\n\t}\n): Promise<SearchInformation[]> {\n\tconst searchInfos: SearchInformation[] = [];\n\tconst temp: AnswererWrapper[] = JSON.parse(JSON.stringify(answererWrappers));\n\tif (temp.length === 0) {\n\t\tthrow new Error('题库配置不能为空，请配置后重新开始自动答题。');\n\t}\n\t// 多线程请求\n\tawait Promise.all(\n\t\ttemp.map(async (wrapper) => {\n\t\t\t// 解构数据，并赋初始值\n\t\t\tconst {\n\t\t\t\tname = '未知题库',\n\t\t\t\thomepage = '#',\n\t\t\t\tmethod = 'get',\n\t\t\t\ttype = 'fetch',\n\t\t\t\tcontentType = 'json',\n\t\t\t\theaders = {},\n\t\t\t\tdata: wrapperData = {},\n\t\t\t\thandler = 'return (res)=> [JSON.stringify(res), undefined]'\n\t\t\t} = wrapper;\n\t\t\ttry {\n\t\t\t\t// 答案列表\n\t\t\t\tlet results: Result[] = [];\n\t\t\t\t// 请求数据\n\t\t\t\tlet requestData;\n\t\t\t\t// 请求地址\n\t\t\t\tlet url: URL;\n\t\t\t\tif (method.toLocaleLowerCase() === 'get') {\n\t\t\t\t\turl = new URL(resolvePlaceHolder(wrapper.url, { encodeURI: true }));\n\t\t\t\t\t/**\n\t\t\t\t\t * 如果 data 存在数据并且 method 为 get，则将 data 数据拼接到 url 上，覆盖原有的  url 同名参数\n\t\t\t\t\t * data 参数的优先级高于 url 参数\n\t\t\t\t\t */\n\t\t\t\t\tObject.keys(wrapperData).forEach((key) => {\n\t\t\t\t\t\t// searchParams.set 方法会自动编码，所以不需要 encodeURI: true\n\t\t\t\t\t\turl.searchParams.set(key, resolvePlaceHolder(wrapperData[key]));\n\t\t\t\t\t});\n\t\t\t\t\t// get 的请求数据为空\n\t\t\t\t\trequestData = {};\n\t\t\t\t} else if (method.toLocaleLowerCase() === 'post') {\n\t\t\t\t\turl = new URL(wrapper.url);\n\t\t\t\t\t// 构造请求数据\n\t\t\t\t\tconst data: Record<string, string> = Object.create({});\n\t\t\t\t\t/** 构造一个请求数据 */\n\t\t\t\t\tObject.keys(wrapperData).forEach((key) => {\n\t\t\t\t\t\t// 如果存在字段解析器\n\t\t\t\t\t\tif (typeof (wrapperData as any)[key] === 'object' && Reflect.has((wrapperData as any)[key], 'handler')) {\n\t\t\t\t\t\t\t// eslint-disable-next-line no-new-func\n\t\t\t\t\t\t\tconst handler = Function(Reflect.get((wrapperData as any)[key], 'handler'))();\n\t\t\t\t\t\t\tif (typeof handler !== 'function') {\n\t\t\t\t\t\t\t\tthrow new Error('data 字段解析器必须返回一个函数');\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst result = handler(env);\n\t\t\t\t\t\t\tReflect.set(data, key, result);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// 解析data数据\n\t\t\t\t\t\t\tReflect.set(data, key, resolvePlaceHolder(wrapperData[key]));\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\n\t\t\t\t\trequestData = data;\n\t\t\t\t} else {\n\t\t\t\t\tthrow new Error('不支持的请求方式');\n\t\t\t\t}\n\n\t\t\t\t// 发送请求\n\t\t\t\tconst responseData = await Promise.race([\n\t\t\t\t\trequest(url.toString(), {\n\t\t\t\t\t\tmethod,\n\t\t\t\t\t\t// 历史遗留的命名问题\n\t\t\t\t\t\tresponseType: contentType,\n\t\t\t\t\t\tdata: requestData,\n\t\t\t\t\t\ttype,\n\t\t\t\t\t\theaders: JSON.parse(JSON.stringify(headers || {}))\n\t\t\t\t\t}),\n\t\t\t\t\t$.sleep((AnswerWrapperHandlerConfig.timeout_seconds ?? 60) * 1000)\n\t\t\t\t]);\n\t\t\t\tif (responseData === undefined) {\n\t\t\t\t\tthrow new Error('题库请求超时，可能是题库问题，或者请检查网络或者重试。');\n\t\t\t\t}\n\n\t\t\t\t/** 从 handler 获取搜索到的题目和回答 */\n\n\t\t\t\t// eslint-disable-next-line no-new-func\n\t\t\t\tconst responseHandler = Function(handler)();\n\t\t\t\tif (typeof responseHandler !== 'function') {\n\t\t\t\t\tthrow new Error('handler 响应处理器必须返回一个函数');\n\t\t\t\t}\n\t\t\t\tconst info = responseHandler(responseData);\n\t\t\t\tif (info && Array.isArray(info)) {\n\t\t\t\t\t/** 如果返回一个二维数组 */\n\t\t\t\t\tif (info.every((item: any) => Array.isArray(item))) {\n\t\t\t\t\t\tresults = results.concat(\n\t\t\t\t\t\t\tinfo.map((item: any) => ({\n\t\t\t\t\t\t\t\tquestion: item[0],\n\t\t\t\t\t\t\t\tanswer: item[1],\n\t\t\t\t\t\t\t\textra_data: item[2] || {}\n\t\t\t\t\t\t\t}))\n\t\t\t\t\t\t);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tresults.push({\n\t\t\t\t\t\t\tquestion: info[0],\n\t\t\t\t\t\t\tanswer: info[1],\n\t\t\t\t\t\t\textra_data: info[2] || {}\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tsearchInfos.push({\n\t\t\t\t\turl: wrapper.url,\n\t\t\t\t\tname,\n\t\t\t\t\thomepage,\n\t\t\t\t\tresults,\n\t\t\t\t\tresponse: responseData,\n\t\t\t\t\tdata: requestData\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(error);\n\t\t\t\tsearchInfos.push({\n\t\t\t\t\turl: wrapper.url,\n\t\t\t\t\tname,\n\t\t\t\t\thomepage,\n\t\t\t\t\tresults: [],\n\t\t\t\t\tresponse: undefined,\n\t\t\t\t\tdata: undefined,\n\t\t\t\t\terror: (error as any)?.message || '题库连接失败'\n\t\t\t\t});\n\t\t\t}\n\t\t})\n\t);\n\n\t// 替换占位符\n\tfunction resolvePlaceHolder(data: any, options?: { encodeURI?: boolean }) {\n\t\tif (typeof data === 'string') {\n\t\t\tconst matches = data.match(/\\${(.*?)}/g) || [];\n\t\t\tmatches.forEach((placeHolder) => {\n\t\t\t\t/** 获取占位符的值 */\n\t\t\t\tconst value: any = env[placeHolder.replace(/\\${(.*)}/, '$1')];\n\t\t\t\tdata = data.replace(placeHolder, options?.encodeURI ? encodeURIComponent(value) : value);\n\t\t\t});\n\t\t} else if (typeof data === 'object') {\n\t\t\t// 递归替换\n\t\t\tconst keys = Object.keys(data);\n\t\t\tfor (const key of keys) {\n\t\t\t\tdata[key] = resolvePlaceHolder(data[key], options);\n\t\t\t}\n\t\t}\n\t\treturn data;\n\t}\n\n\treturn searchInfos;\n}\n"
  },
  {
    "path": "packages/core/src/core/answer-wrapper/answer.wrapper.parser.ts",
    "content": "import { AnswererWrapper } from './interface';\nimport { request } from '../utils/request';\n\n/**\n * 解析题库配置 数据来源可以是 url , base64 , json , json-string\n */\nexport class AnswerWrapperParser {\n\t/** 从 json 字符串中解析 */\n\tstatic fromObject(json: any): AnswererWrapper[] {\n\t\tconst aw: AnswererWrapper[] = json;\n\n\t\tif (aw && Array.isArray(aw)) {\n\t\t\tif (aw.length) {\n\t\t\t\tfor (let i = 0; i < aw.length; i++) {\n\t\t\t\t\tconst item = aw[i];\n\t\t\t\t\tif (typeof item.name !== 'string') {\n\t\t\t\t\t\tthrow new Error(`第 ${i + 1} 个题库的 名字(name) 为空`);\n\t\t\t\t\t}\n\t\t\t\t\tif (typeof item.url !== 'string') {\n\t\t\t\t\t\tthrow new Error(`第 ${i + 1} 个题库的 接口地址(url) 为空`);\n\t\t\t\t\t}\n\t\t\t\t\tif (typeof item.handler !== 'string') {\n\t\t\t\t\t\tthrow new Error(`第 ${i + 1} 个题库的 解析器(handler) 为空`);\n\t\t\t\t\t}\n\t\t\t\t\tif (item.headers && typeof item.headers !== 'object') {\n\t\t\t\t\t\tthrow new Error(`第 ${i + 1} 个题库的 头部信息(header) 应为 对象 格式`);\n\t\t\t\t\t}\n\t\t\t\t\tif (item.data && typeof item.data !== 'object') {\n\t\t\t\t\t\tthrow new Error(`第 ${i + 1} 个题库的 提交数据(data) 应为 对象 格式`);\n\t\t\t\t\t}\n\t\t\t\t\tconst contentTypes = ['json', 'text'] as AnswererWrapper['contentType'][];\n\t\t\t\t\tif (item.contentType && contentTypes.every((i) => i !== item.contentType)) {\n\t\t\t\t\t\tthrow new Error(`第 ${i + 1} 个题库的 contentType 必须为以下选项中的一个  ${contentTypes.join(', ')}`);\n\t\t\t\t\t}\n\t\t\t\t\tconst methods = ['post', 'get'] as AnswererWrapper['method'][];\n\t\t\t\t\tif (item.method && methods.every((i) => i !== item.method)) {\n\t\t\t\t\t\tthrow new Error(`第 ${i + 1} 个题库的 method 必须为以下选项中的一个  ${methods.join(', ')}`);\n\t\t\t\t\t}\n\t\t\t\t\tconst types = ['fetch', 'GM_xmlhttpRequest'] as AnswererWrapper['type'][];\n\t\t\t\t\tif (item.type && types.every((i) => i !== item.type)) {\n\t\t\t\t\t\tthrow new Error(`第 ${i + 1} 个题库的 type 必须为以下选项中的一个  ${types.join(', ')}`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn aw;\n\t\t\t} else {\n\t\t\t\tthrow new Error('题库为空！');\n\t\t\t}\n\t\t} else {\n\t\t\tthrow new Error('题库配置格式错误！');\n\t\t}\n\t}\n\n\tstatic fromJSONString(json: string) {\n\t\tconst raw = json.toString();\n\t\ttry {\n\t\t\treturn JSON.parse(raw);\n\t\t} catch {\n\t\t\tthrow new Error(`格式错误，必须为：json字符串 或 题库配置链接`);\n\t\t}\n\t}\n\n\t/** 从 url 中解析 */\n\tstatic async fromURL(url: string) {\n\t\tconst text = await request(url, {\n\t\t\tresponseType: 'text',\n\t\t\tmethod: 'get',\n\t\t\ttype: 'fetch'\n\t\t});\n\t\treturn this.fromJSONString(text);\n\t}\n\n\t/** 从 base64 解析 */\n\tstatic fromBase64(base64: string) {\n\t\treturn this.fromJSONString(Buffer.from(base64, 'base64').toString('utf8'));\n\t}\n\n\t/**\n\t * 解析题库配置 数据来源可以是 url , base64 , json , json-string\n\t */\n\tstatic from(value: any): AnswererWrapper[] | Promise<AnswererWrapper[]> {\n\t\tif (typeof value === 'string') {\n\t\t\tif (value.startsWith('http')) {\n\t\t\t\treturn this.fromURL(value);\n\t\t\t} else {\n\t\t\t\treturn this.fromJSONString(value);\n\t\t\t}\n\t\t} else {\n\t\t\treturn this.fromObject(value);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "packages/core/src/core/answer-wrapper/index.ts",
    "content": "export * from './answer.wrapper.handler';\nexport * from './answer.wrapper.parser';\nexport * from './interface';\n"
  },
  {
    "path": "packages/core/src/core/answer-wrapper/interface.ts",
    "content": "/** 题目答案 */\nexport interface Result {\n\tquestion: string;\n\tanswer: string;\n\textra_data?: object;\n}\n\n/** 题库查询信息 */\nexport interface SearchInformation {\n\tresults: Result[];\n\tname: string;\n\turl?: string;\n\t/** 主页 */\n\thomepage?: string;\n\t/** 题目答案 */\n\t/** 请求响应内容 */\n\tresponse?: any;\n\t/** 请求发起内容 */\n\tdata?: any;\n\t/** 错误数据 */\n\terror?: string;\n}\n\n/**\n * 题库配置器\n */\nexport interface AnswererWrapper {\n\t/** 答题器请求路径 */\n\turl: string;\n\t/** 题库名字 */\n\tname: string;\n\t/** 题库网址 */\n\thomepage?: string;\n\t/** 请求数据 */\n\tdata?: Record<string, any>;\n\t/** 请求方法 */\n\tmethod: 'post' | 'get';\n\t/** 定义 handler 中的参数类型 */\n\tcontentType: 'json' | 'text';\n\t/** 请求模式 */\n\ttype: 'fetch' | 'GM_xmlhttpRequest';\n\t/** 附带请求头 */\n\theaders: Record<string, string>;\n\t/**\n\t * 此选项是个字符串， 使用 [Function(string)](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function) 构造方法进行解析生成方法\n\t *\n\t * 方法传入一个参数 : 请求获取到的文本 ，可以使用 contentType 定义文本类型\n\t *\n\t * 对返回的数据进行自定义解析\n\t *\n\t * 并且返回一个数组 : `[题目, 答案]`\n\t *\n\t * 或者二维数据 : `[[题目1, 答案1],[题目2, 答案2], ...`\n\t *\n\t * 如果搜不到则返回 undefined\n\t *\n\t * @example\n\t *\n\t * ```js\n\t * {\n\t *      handler: `return (res)=> res.code === 0 ? undefined : [res.question, undefined]`\n\t * }\n\t * ```\n\t *\n\t */\n\thandler: string;\n}\n"
  },
  {
    "path": "packages/core/src/core/utils/dom.ts",
    "content": "import { RawElements, SearchedElements } from '../worker/interface';\n\n/**\n * 与 {@link domSearchAll } 相同，区别是这个只返回单个元素，而不是一个元素数组\n * @param root\n * @param wrapper\n * @returns\n */\nexport function domSearch<E extends RawElements>(\n\t/** 搜索构造器 */\n\twrapper: E,\n\troot: HTMLElement | Document = window.document\n): SearchedElements<E, HTMLElement | null> {\n\tconst obj = Object.create({});\n\tReflect.ownKeys(wrapper).forEach((key) => {\n\t\tconst item = wrapper[key.toString()];\n\t\tReflect.set(\n\t\t\tobj,\n\t\t\tkey,\n\t\t\ttypeof item === 'string'\n\t\t\t\t? root.querySelector(item)\n\t\t\t\t: typeof item === 'function'\n\t\t\t\t? item(root)\n\t\t\t\t: item.map((fun) => fun(root))\n\t\t);\n\t});\n\treturn obj;\n}\n\n/**\n * 元素搜索\n *\n * @example\n *\n * const { title , btn , arr } = domSearch(document.body,{\n *      title: '.title'\n *      btn: ()=> '.btn',\n *      arr: ()=> Array.from(document.body.querySelectorAll('.function-arr'))\n * })\n *\n * console.log(title) // 等价于 Array.from(document.body.querySelectorAll('.title'))\n * console.log(btn)// 等价于 Array.from(document.body.querySelectorAll('.btn'))\n */\nexport function domSearchAll<E extends RawElements>(\n\t/** 搜索构造器 */\n\twrapper: E,\n\troot: HTMLElement | Document = window.document\n): SearchedElements<E, HTMLElement[]> {\n\tconst obj = Object.create({});\n\tReflect.ownKeys(wrapper).forEach((key) => {\n\t\tconst item = wrapper[key.toString()];\n\n\t\tReflect.set(\n\t\t\tobj,\n\t\t\tkey,\n\t\t\ttypeof item === 'string'\n\t\t\t\t? Array.from(root.querySelectorAll(item))\n\t\t\t\t: typeof item === 'function'\n\t\t\t\t? item(root)\n\t\t\t\t: item.map((fun) => fun(root))\n\t\t);\n\t});\n\treturn obj;\n}\n"
  },
  {
    "path": "packages/core/src/core/utils/index.ts",
    "content": "export * from './dom';\nexport * from './string';\nexport * from './request';\n"
  },
  {
    "path": "packages/core/src/core/utils/request.ts",
    "content": "import { $ } from '../../utils/common';\n\n/**\n * 发起请求\n * @param url 请求地址\n * @param opts 请求参数\n */\nexport function request<T extends 'json' | 'text'>(\n\turl: string,\n\topts: {\n\t\ttype: 'fetch' | 'GM_xmlhttpRequest';\n\t\tmethod?: 'get' | 'post' | 'head';\n\t\tresponseType?: T;\n\t\theaders?: Record<string, string>;\n\t\tdata?: Record<string, any>;\n\t}\n): Promise<T extends 'json' ? any : string> {\n\treturn new Promise((resolve, reject) => {\n\t\ttry {\n\t\t\t/** 默认参数 */\n\t\t\tconst { responseType = 'json', method = 'get', type = 'fetch', data = {}, headers = {} } = opts || {};\n\t\t\t/** 环境变量 */\n\t\t\tconst env = $.isInBrowser() ? 'browser' : 'node';\n\n\t\t\t/** 如果是跨域模式并且是浏览器环境 */\n\t\t\tif (type === 'GM_xmlhttpRequest' && env === 'browser') {\n\t\t\t\tif (typeof GM_xmlhttpRequest !== 'undefined') {\n\t\t\t\t\tconst contentType = headers['Content-Type'] || headers['content-type'];\n\t\t\t\t\tconst requestData =\n\t\t\t\t\t\tcontentType === 'application/x-www-form-urlencoded'\n\t\t\t\t\t\t\t? new URLSearchParams(data).toString()\n\t\t\t\t\t\t\t: Object.keys(data).length\n\t\t\t\t\t\t\t? JSON.stringify(data)\n\t\t\t\t\t\t\t: undefined;\n\t\t\t\t\t// eslint-disable-next-line no-undef\n\t\t\t\t\tGM_xmlhttpRequest({\n\t\t\t\t\t\turl,\n\t\t\t\t\t\tmethod: method.toUpperCase() as 'GET' | 'HEAD' | 'POST',\n\t\t\t\t\t\tdata: requestData,\n\t\t\t\t\t\theaders: Object.keys(headers).length ? headers : undefined,\n\t\t\t\t\t\tresponseType: responseType === 'json' ? 'json' : undefined,\n\t\t\t\t\t\tonload: (response) => {\n\t\t\t\t\t\t\tif (response.status === 200) {\n\t\t\t\t\t\t\t\tif (responseType === 'json') {\n\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\tresolve(JSON.parse(response.responseText));\n\t\t\t\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\t\t\t\treject(error);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tresolve(response.responseText || '');\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\treject(response.responseText);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\tonerror: (err) => {\n\t\t\t\t\t\t\tconsole.error('GM_xmlhttpRequest error', err);\n\t\t\t\t\t\t\treject(err);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\treject(new Error('GM_xmlhttpRequest is not defined'));\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tconst fet: typeof fetch = env === 'node' ? require('node-fetch').default : fetch;\n\n\t\t\t\tfet(url, { body: method === 'post' ? JSON.stringify(data) : undefined, method, headers })\n\t\t\t\t\t.then((response) => {\n\t\t\t\t\t\tif (responseType === 'json') {\n\t\t\t\t\t\t\tresponse.json().then(resolve).catch(reject);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// @ts-ignore\n\t\t\t\t\t\t\tresponse.text().then(resolve).catch(reject);\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t\t.catch((error) => {\n\t\t\t\t\t\treject(new Error(error));\n\t\t\t\t\t});\n\t\t\t}\n\t\t} catch (error) {\n\t\t\treject(error);\n\t\t}\n\t});\n}\n"
  },
  {
    "path": "packages/core/src/core/utils/string.ts",
    "content": "import { findBestMatch, Rating } from 'string-similarity';\n\n/**\n * 删除特殊字符, 并且全部转小写，只保留英文，数字，中文\n * @param str\n * @returns\n */\nexport function clearString(str: string, ...exclude: string[]) {\n\texclude.push(...['①②③④⑤⑥⑦⑧⑨']);\n\treturn str\n\t\t.trim()\n\t\t.toLocaleLowerCase()\n\t\t.replace(RegExp(`[^\\\\u2E80-\\\\u9FFFA-Za-z0-9${exclude.join('')}]*`, 'g'), '');\n}\n\n/**\n * 答案相似度匹配 , 返回相似度对象列表 Array<{@link Rating}>\n *\n * 相似度计算算法 : https://www.npmjs.com/package/string-similarity\n *\n * @param answers 答案列表\n * @param options 选项列表\n *\n *\n * @example\n *\n * ```js\n *\n * answerSimilar( ['3'], ['1+2','3','4','错误的选项'] ) // [0, 1, 0, 0]\n *\n * answerSimilar( ['hello world','console.log(\"hello world\")'], ['console.log(\"hello world\")','hello world','1','错误的选项'] ) // [1, 1, 0, 0]\n *\n * ```\n *\n */\nexport function answerSimilar(answers: string[], options: string[]): Rating[] {\n\tconst _answers = answers.map(removeRedundant).map((a) => clearString(a));\n\tconst _options = options.map(removeRedundant).map((o) => clearString(o));\n\n\tconst similar =\n\t\t_answers.length !== 0\n\t\t\t? _options.map((option) => {\n\t\t\t\t\tif (option.trim() === '') {\n\t\t\t\t\t\treturn { rating: 0, target: '' };\n\t\t\t\t\t}\n\t\t\t\t\treturn findBestMatch(option, _answers).bestMatch;\n\t\t\t  })\n\t\t\t: _options.map(() => ({ rating: 0, target: '' }));\n\n\treturn similar;\n}\n\n/**\n * \t精准匹配模式，返回符合的选项字符串列表\n * @param answers\t答案列表\n * @param options\t选项列表\n */\nexport function answerExactMatch(answers: string[], options: string[]): string[] {\n\tconst _answers = answers.map(removeRedundant);\n\tconst _options = options.map(removeRedundant);\n\n\tconst result =\n\t\t_answers.length !== 0\n\t\t\t? _options.filter((option) => {\n\t\t\t\t\treturn _answers.find((answer) => answer.trim() === option.trim());\n\t\t\t  })\n\t\t\t: [];\n\n\treturn result;\n}\n\n/**\n * 删除题目选项中开头的冗余字符串\n */\nexport function removeRedundant(str: string) {\n\treturn str?.trim().replace(/[A-Z]{1}[^A-Za-z0-9\\u2E80-\\u9FFF]+([A-Za-z0-9\\u2E80-\\u9FFF]+)/, '$1') || '';\n}\n"
  },
  {
    "path": "packages/core/src/core/worker/index.ts",
    "content": "export * from './interface';\nexport * from './utils';\nexport { OCSWorker, CustomOCSWorker } from './worker';\nexport { createDefaultQuestionResolver } from './question.resolver';\n"
  },
  {
    "path": "packages/core/src/core/worker/interface.ts",
    "content": "import { SearchInformation } from '../answer-wrapper/interface';\n\nexport type ElementResolver<R> = (root: HTMLElement | Document) => R;\nexport type RawElements = Record<\n\tstring | symbol,\n\tstring | ElementResolver<HTMLElement[]> | ElementResolver<HTMLElement>[]\n> & {\n\t/** 题目元素选择器 */\n\ttitle?: string | ElementResolver<HTMLElement[]> | ElementResolver<HTMLElement>[];\n\t/** 题目选项的元素选择器 */\n\toptions?: string | ElementResolver<HTMLElement[]> | ElementResolver<HTMLElement>[];\n};\n\nexport type SearchedElements<E, T> = Record<keyof E, T> & {\n\t/** 题目元素选择器 */\n\ttitle?: T extends Array<infer ArrayType> ? (undefined | ArrayType)[] : T;\n\t/** 题目选项的元素选择器 */\n\toptions?: T extends Array<infer ArrayType> ? (undefined | ArrayType)[] : T;\n};\n\n/** 题目类型 */\nexport type QuestionTypes = 'single' | 'multiple' | 'completion' | 'judgement' | undefined;\n\nexport type AnswerMatchMode = 'exact' | 'similar';\n\n/** 答题器上下文 */\nexport interface WorkContext<E> {\n\troot: HTMLElement;\n\telements: SearchedElements<E, HTMLElement[]>;\n\tsearchInfos: SearchInformation[];\n\ttype: QuestionTypes;\n\t/** 答案分隔符 */\n\tanswerSeparators?: string[];\n\t/**\n\t * 答案匹配模式\n\t * exact : 精准匹配模式, 只有答案相同才匹配\n\t * similar : 相似匹配, 只要答案相似就匹配\n\t */\n\tanswerMatchMode: AnswerMatchMode;\n}\n\n/** 答案题目处理器结果 */\nexport interface ResolverResult {\n\t[x: string]: any;\n\tfinish: boolean;\n}\n\n/** 答题结果 */\nexport interface WorkResult<E extends RawElements> {\n\t/** 查题完毕 */\n\trequested: boolean;\n\t/** 答题完毕 */\n\tresolved: boolean;\n\tresult?: ResolverResult;\n\terror?: string;\n\tctx?: WorkContext<E>;\n}\n\n/**\n * 简化的答题结果 一般用于存储到本地\n *\n * 为什么不直接用 {@link WorkResult} ，因为对象里太多嵌套对象，一旦结果超过10个以上，可能导致 I/O 变慢，并且页面卡顿。\n */\nexport interface SimplifyWorkResult {\n\t/** 题目 */\n\tquestion: string;\n\t/** 题目类型 */\n\ttype: QuestionTypes;\n\t/** 答题错误信息 */\n\terror?: string;\n\t/** 是否完成 */\n\tfinish?: boolean;\n\t/** 正在等待 查题 线程处理 */\n\trequested: boolean;\n\t/** 正在等待 答题 线程处理 */\n\tresolved: boolean;\n\t/** 查题信息 */\n\tsearchInfos: {\n\t\t/** 题目名 */\n\t\tname: SearchInformation['name'];\n\t\t/** 题库链接 */\n\t\thomepage?: SearchInformation['homepage'];\n\t\t/** 题库搜索错误信息 */\n\t\terror?: string;\n\t\t/** 搜索结果 [题目，答案，额外数据] */\n\t\tresults: [string, string, object][];\n\t}[];\n}\n\n/** 答案题目处理器 */\nexport type QuestionResolver<E> = (\n\t/** 查题信息 */\n\tsearchInfos: SearchInformation[],\n\t/** 选项 */\n\toptions: HTMLElement[],\n\thandler: (\n\t\ttype: QuestionTypes,\n\t\tanswer: string,\n\t\toption: HTMLElement,\n\t\tctx: WorkContext<SearchedElements<E, HTMLElement[]>>\n\t) => void | Promise<void>\n) => Promise<ResolverResult>;\n\n/**\n * 使用默认工作器\n *\n * 需要自定义 handler\n */\nexport interface DefaultWork<E extends RawElements> {\n\t/** 工作器的题目类型 */\n\ttype?: QuestionTypes | { (ctx: WorkContext<E>): QuestionTypes };\n\t/**\n     * 处理器， 每个题目的处理器， 实例可看默认的 zhs `作业脚本` 写法 : https://github.com/ocsjs/ocsjs/blob/3.0/packages/scripts/src/browser/zhs/work.ts\n     *\n     *\n     * @param type 题目类型\n     * @param answer 根据 查题器 解析出来的正确答案，\n     * @param element 目标选项的dom对象\n     *\n     * @example\n     *\n     * ```js\n    {\n        elements:{\n            // 必须提供 options 元素选择器，代表题目的子选项\n            options: '.subject .option'\n        },\n        work: {\n            // 自定义处理器例子：\n            handler(type, answer, option, ctx) {\n                // 如果是选择题，则获取目标按钮，并点击\n                if (type === \"judgement\" || type === \"single\" || type === \"multiple\") {\n                    if (!option.querySelector(\"input\")?.checked) {\n                        option.click();\n                    }\n                }\n                // 如果是填空题，则获取 textarea 元素并输入答案\n                else if (type === \"completion\") {\n                    const text = option.querySelector(\"textarea\");\n                    if (text) {\n                        text.value = answer;\n                    }\n                }\n            },\n        },\n    }\n     * ```\n     *\n     */\n\thandler: (\n\t\ttype: QuestionTypes,\n\t\tanswer: string,\n\t\toption: HTMLElement,\n\t\tctx: WorkContext<SearchedElements<E, HTMLElement[]>>\n\t) => void;\n}\n/**\n * 自定义工作器\n *\n * 如果默认工作器不满足需求，可以自定义\n *\n * ```js\n *\n * {\n *      elements:{\n *          inputs: 'input',\n *      },\n *      // 简单例子\n *      work({ root, elements, searchResults}){\n *          for(const input of elements.inputs){\n *              if(searchResults.map(res=>res.answers.map(ans=>ans.answer)).includes(input.value)){\n *                  input.click()\n *                  return true\n *              }\n *          }\n *          return false\n *      }\n *\n * }\n *\n * ```\n *\n */\nexport type CustomWork<E extends RawElements> = (ctx: WorkContext<E>) => Promise<ResolverResult>;\n\n/**  查题器的类型  */\n\nexport type AnswererType<E> = (\n\telements: SearchedElements<E, HTMLElement[]>,\n\tctx: WorkContext<SearchedElements<E, HTMLElement[]>>\n) => SearchInformation[] | Promise<SearchInformation[]>;\n\n/**\n * 答题器参数\n */\nexport interface WorkOptions<E extends RawElements> {\n\t/** 父元素 */\n\troot: string | HTMLElement[];\n\t/** dom元素解析器，可以在 WorkContext.elements 中使用解析后的元素 */\n\telements: E;\n\t/** 查题器 */\n\tanswerer: AnswererType<E>;\n\t/** 工作器 */\n\twork: DefaultWork<E> | CustomWork<E>;\n\t/** 多线程数量（个） */\n\tthread?: number;\n\t/** 分隔符 */\n\tanswerSeparators?: string[];\n\t/** 答案匹配模式 */\n\tanswerMatchMode?: AnswerMatchMode;\n\t/** 当元素被搜索到 */\n\tonElementSearched?: (elements: SearchedElements<E, HTMLElement[]>, root: HTMLElement) => void | Promise<void>;\n\t/** 监听搜题结果 */\n\tonAnswerSearched?: (\n\t\tsearchInfo: SearchInformation,\n\t\tcurrentResult: WorkResult<E>,\n\t\tcurrentIndex: number\n\t) => void | Promise<void>;\n\t/** 监听答题结果 */\n\tonResultsUpdate?: (currentResult: WorkResult<E>, currentIndex: number, res: WorkResult<E>[]) => void | Promise<void>;\n}\n\nexport interface CustomWorkOptions {\n\tperiod: number;\n\tquestions: () => { text: string; type: QuestionTypes }[] | Promise<{ text: string; type: QuestionTypes }[]>;\n\tanswerer: (question: string) => SearchInformation[] | Promise<SearchInformation[]>;\n\tresolver: (searchInfos: SearchInformation[]) => ResolverResult | Promise<ResolverResult>;\n\n\t/** 监听答题结果 */\n\tonResultsUpdate?: (\n\t\tcurrentResult: SimplifyWorkResult,\n\t\tcurrentIndex: number,\n\t\tres: SimplifyWorkResult[]\n\t) => void | Promise<void>;\n}\n\nexport type WorkUploadType = 'save' | 'nomove' | 'force' | number;\n\nexport type WorkerEvents = {\n\t/** 答题开始 */\n\tstart: () => void;\n\t/** 答题结果 */\n\tdone: () => void;\n\t/** 关闭答题 */\n\tclose: () => void;\n\t/** 暂停答题 */\n\tstop: () => void;\n\t/** 继续答题 */\n\tcontinuate: () => void;\n};\n"
  },
  {
    "path": "packages/core/src/core/worker/question.resolver.ts",
    "content": "import { QuestionResolver, WorkContext } from './interface';\nimport { resolvePlainAnswer, splitAnswer } from './utils';\nimport { answerSimilar, removeRedundant, clearString, answerExactMatch } from '../utils/string';\nimport { StringUtils } from '../../utils/string';\nimport { Rating } from 'string-similarity';\n\n/** 默认答案题目处理器 */\nexport function createDefaultQuestionResolver<E>(\n\tctx: WorkContext<E>\n): Record<'single' | 'multiple' | 'completion' | 'judgement', QuestionResolver<E>> {\n\treturn {\n\t\t/**\n\t\t * 单选题处理器\n\t\t *\n\t\t * 在多个题库给出的答案中，找出最相似的答案\n\t\t */\n\t\tasync single(infos, options, handler) {\n\t\t\tconst allAnswer = infos\n\t\t\t\t.map((res) => res.results.map((res) => splitAnswer(res.answer, ctx.answerSeparators)).flat())\n\t\t\t\t.flat();\n\t\t\tconst optionStrings = options.map((o) => removeRedundant(o.innerText));\n\t\t\tlet ratings: Rating[] = [];\n\t\t\tif (ctx.answerMatchMode === 'similar') {\n\t\t\t\t/** 配对选项的相似度 */\n\t\t\t\tratings = answerSimilar(allAnswer, optionStrings);\n\t\t\t\t/**  找出最相似的选项 */\n\t\t\t\tlet index = -1;\n\t\t\t\tlet max = 0;\n\t\t\t\tlet ans = '';\n\t\t\t\tratings.forEach((rating, i) => {\n\t\t\t\t\tif (rating.rating > max) {\n\t\t\t\t\t\tmax = rating.rating;\n\t\t\t\t\t\tindex = i;\n\t\t\t\t\t\tans = rating.target;\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\t// 存在选项，并且相似度超过 60 %\n\t\t\t\tif (index !== -1 && max > 0.6) {\n\t\t\t\t\t/** 经自定义的处理器进行处理 */\n\t\t\t\t\tawait handler('single', ans, options[index], ctx);\n\t\t\t\t\treturn {\n\t\t\t\t\t\tfinish: true,\n\t\t\t\t\t\tratings: ratings.map((r) => r.rating)\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t} else if (ctx.answerMatchMode === 'exact') {\n\t\t\t\tconst result = answerExactMatch(allAnswer, optionStrings);\n\t\t\t\tconst index = optionStrings.findIndex((option) => result.includes(option));\n\t\t\t\tif (result.length) {\n\t\t\t\t\tawait handler('single', options[index].innerText, options[index], ctx);\n\t\t\t\t\treturn {\n\t\t\t\t\t\tfinish: true\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// 是否为纯ABCD答案\n\t\t\tfor (const info of infos) {\n\t\t\t\tfor (const res of info.results) {\n\t\t\t\t\tconst ans = StringUtils.nowrap(res.answer, '').trim();\n\t\t\t\t\tif (ans.length === 1 && /[A-Z]/.test(ans)) {\n\t\t\t\t\t\tconst index = ans.charCodeAt(0) - 65;\n\t\t\t\t\t\tif (options[index] === undefined) {\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tawait handler('single', options[index].innerText, options[index], ctx);\n\t\t\t\t\t\treturn { finish: true, option: options[index] };\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn { finish: false, allAnswer, ratings: ratings.map((r) => r.rating), options: optionStrings };\n\t\t},\n\t\t/**\n\t\t * 多选题处理器\n\t\t *\n\t\t * 匹配每个题库的答案，找出匹配数量最多的题库，并且选择\n\t\t */\n\t\tasync multiple(infos, options, handler) {\n\t\t\t/** 最终的回答列表 */\n\t\t\tconst targetAnswers: string[][] = [];\n\t\t\t/** 最终的选项 */\n\t\t\tconst targetOptions: HTMLElement[][] = [];\n\n\t\t\ttype SimilarResult = {\n\t\t\t\t/** 匹配的选项 */\n\t\t\t\toptions: HTMLElement[];\n\t\t\t\t/** 匹配的答案 */\n\t\t\t\tanswers: string[];\n\t\t\t\tratings: number[];\n\t\t\t\t/** 总匹配度 */\n\t\t\t\tsimilarSum: number;\n\t\t\t\t/** 匹配数量 */\n\t\t\t\tsimilarCount: number;\n\t\t\t};\n\n\t\t\tconst similar_list: SimilarResult[] = [];\n\n\t\t\tconst exact_list: HTMLElement[][] = [];\n\n\t\t\tconst results = infos.map((info) => info.results).flat();\n\n\t\t\t/**\n\t\t\t * 遍历题库结果\n\t\t\t * 选出结果中包含答案最多的一个\n\t\t\t */\n\t\t\tfor (let i = 0; i < results.length; i++) {\n\t\t\t\tconst result = results[i];\n\t\t\t\t// 每个答案可能存在多个选项需要分割\n\t\t\t\tconst answers = splitAnswer(result.answer.trim(), ctx.answerSeparators);\n\n\t\t\t\tif (ctx.answerMatchMode === 'similar') {\n\t\t\t\t\tconst matchResult: SimilarResult = { options: [], answers: [], ratings: [], similarSum: 0, similarCount: 0 };\n\t\t\t\t\t// 判断选项是否完全存在于答案里面\n\t\t\t\t\tfor (const option of options) {\n\t\t\t\t\t\tconst ans = answers.find((answer) =>\n\t\t\t\t\t\t\tanswer.includes(removeRedundant(option.textContent || option.innerText))\n\t\t\t\t\t\t);\n\t\t\t\t\t\tif (ans) {\n\t\t\t\t\t\t\tmatchResult.options.push(option);\n\t\t\t\t\t\t\tmatchResult.answers.push(ans);\n\t\t\t\t\t\t\tmatchResult.ratings.push(1);\n\t\t\t\t\t\t\tmatchResult.similarSum += 1;\n\t\t\t\t\t\t\tmatchResult.similarCount += 1;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tconst ratingResult: SimilarResult = { options: [], answers: [], ratings: [], similarSum: 0, similarCount: 0 };\n\t\t\t\t\t// 相似度匹配\n\t\t\t\t\tconst ratings = answerSimilar(\n\t\t\t\t\t\tanswers,\n\t\t\t\t\t\toptions.map((o) => removeRedundant(o.innerText))\n\t\t\t\t\t);\n\t\t\t\t\tfor (let j = 0; j < ratings.length; j++) {\n\t\t\t\t\t\tconst rating = ratings[j];\n\t\t\t\t\t\tif (rating.rating > 0.6) {\n\t\t\t\t\t\t\tratingResult.options.push(options[j]);\n\t\t\t\t\t\t\tratingResult.answers.push(ratings[j].target);\n\t\t\t\t\t\t\tratingResult.ratings.push(ratings[j].rating);\n\t\t\t\t\t\t\tratingResult.similarSum += rating.rating;\n\t\t\t\t\t\t\tratingResult.similarCount += 1;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// 如果全匹配大于 相似度匹配\n\t\t\t\t\tif (matchResult.similarSum > ratingResult.similarSum) {\n\t\t\t\t\t\tsimilar_list[i] = matchResult;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tsimilar_list[i] = ratingResult;\n\t\t\t\t\t}\n\t\t\t\t} else if (ctx.answerMatchMode === 'exact') {\n\t\t\t\t\texact_list[i] = answerExactMatch(\n\t\t\t\t\t\tanswers,\n\t\t\t\t\t\toptions.map((o) => removeRedundant(o.innerText))\n\t\t\t\t\t)\n\t\t\t\t\t\t.map((option) => options.find((o) => removeRedundant(o.innerText) === option))\n\t\t\t\t\t\t.filter(Boolean) as HTMLElement[];\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (ctx.answerMatchMode === 'similar') {\n\t\t\t\tconst sorted_similar_list = similar_list\n\t\t\t\t\t.filter((i) => i.similarCount !== 0)\n\t\t\t\t\t.sort((a, b) => {\n\t\t\t\t\t\tconst bsc = b.similarCount * 100;\n\t\t\t\t\t\tconst asc = a.similarCount * 100;\n\t\t\t\t\t\tconst bss = b.similarSum;\n\t\t\t\t\t\tconst ass = a.similarSum;\n\n\t\t\t\t\t\t// similarCount 由于是匹配的数量，其结果决定排序，\n\t\t\t\t\t\t// similarSum 是匹配精度，其结果决定同样数量的情况下，哪一个的精度更高\n\n\t\t\t\t\t\t// 高到低排序\n\t\t\t\t\t\treturn bsc + bss - asc + ass;\n\t\t\t\t\t});\n\n\t\t\t\tif (sorted_similar_list[0]) {\n\t\t\t\t\tfor (let i = 0; i < sorted_similar_list[0].options.length; i++) {\n\t\t\t\t\t\tawait handler('multiple', sorted_similar_list[0].answers[i], sorted_similar_list[0].options[i], ctx);\n\t\t\t\t\t}\n\n\t\t\t\t\treturn { finish: true, sorted_similar_list, targetOptions, targetAnswers };\n\t\t\t\t}\n\t\t\t} else if (ctx.answerMatchMode === 'exact') {\n\t\t\t\tconst sorted_exact_list = exact_list.sort((a, b) => b.length - a.length);\n\t\t\t\tif (sorted_exact_list[0]?.length) {\n\t\t\t\t\tfor (let i = 0; i < sorted_exact_list[0].length; i++) {\n\t\t\t\t\t\tawait handler('multiple', sorted_exact_list[0][i].innerText, sorted_exact_list[0][i], ctx);\n\t\t\t\t\t}\n\n\t\t\t\t\treturn {\n\t\t\t\t\t\tfinish: true,\n\t\t\t\t\t\tsorted_exact_list: sorted_exact_list.map((i) => i.map((e) => e.innerText)),\n\t\t\t\t\t\ttargetOptions,\n\t\t\t\t\t\ttargetAnswers\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// 如果都没找到答案\n\n\t\t\tconst plainOptions = [];\n\t\t\t// 纯ABCD答案\n\t\t\tfor (const result of results) {\n\t\t\t\tconst ans = StringUtils.nowrap(result.answer, '').trim();\n\t\t\t\tconst plainAnswer = resolvePlainAnswer(ans);\n\t\t\t\tif (plainAnswer) {\n\t\t\t\t\tfor (const char of ans) {\n\t\t\t\t\t\tconst index = char.charCodeAt(0) - 65;\n\t\t\t\t\t\tif (options[index] === undefined) {\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tawait handler('multiple', options[index].innerText, options[index], ctx);\n\t\t\t\t\t\tplainOptions.push(options[index]);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (plainOptions.length) {\n\t\t\t\treturn { finish: true, plainOptions };\n\t\t\t} else {\n\t\t\t\treturn { finish: false };\n\t\t\t}\n\t\t},\n\t\t/** 判断题处理器 */\n\t\tasync judgement(infos, options, handler) {\n\t\t\tfor (const answers of infos.map((info) => info.results.map((res) => res.answer))) {\n\t\t\t\tconst correctWords = [\n\t\t\t\t\t'是',\n\t\t\t\t\t'对',\n\t\t\t\t\t'正确',\n\t\t\t\t\t'确定',\n\t\t\t\t\t'√',\n\t\t\t\t\t'对的',\n\t\t\t\t\t'是的',\n\t\t\t\t\t'正确的',\n\t\t\t\t\t'true',\n\t\t\t\t\t'True',\n\t\t\t\t\t'T',\n\t\t\t\t\t'yes',\n\t\t\t\t\t'1'\n\t\t\t\t];\n\t\t\t\tconst incorrectWords = [\n\t\t\t\t\t'非',\n\t\t\t\t\t'否',\n\t\t\t\t\t'错',\n\t\t\t\t\t'错误',\n\t\t\t\t\t'×',\n\t\t\t\t\t'X',\n\t\t\t\t\t'错的',\n\t\t\t\t\t'不对',\n\t\t\t\t\t'不正确的',\n\t\t\t\t\t'不正确',\n\t\t\t\t\t'不是',\n\t\t\t\t\t'不是的',\n\t\t\t\t\t'false',\n\t\t\t\t\t'False',\n\t\t\t\t\t'F',\n\t\t\t\t\t'no',\n\t\t\t\t\t'0'\n\t\t\t\t];\n\n\t\t\t\t/** 答案显示正确 */\n\t\t\t\tconst answerShowCorrect = answers.find((answer) => matches(answer, correctWords));\n\t\t\t\t/** 答案显示错误 */\n\t\t\t\tconst answerShowIncorrect = answers.find((answer) => matches(answer, incorrectWords));\n\n\t\t\t\tif (answerShowCorrect || answerShowIncorrect) {\n\t\t\t\t\tlet option: HTMLElement | undefined;\n\t\t\t\t\tfor (const el of options) {\n\t\t\t\t\t\t/** 选项显示正确 */\n\t\t\t\t\t\tconst textShowCorrect = matches(el.innerText, correctWords);\n\t\t\t\t\t\t/** 选项显示错误 */\n\t\t\t\t\t\tconst textShowIncorrect = matches(el.innerText, incorrectWords);\n\n\t\t\t\t\t\tif (answerShowCorrect && textShowCorrect) {\n\t\t\t\t\t\t\toption = el;\n\t\t\t\t\t\t\tawait handler('judgement', answerShowCorrect, el, ctx);\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (answerShowIncorrect && textShowIncorrect) {\n\t\t\t\t\t\t\toption = el;\n\t\t\t\t\t\t\tawait handler('judgement', answerShowIncorrect, el, ctx);\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\treturn { finish: true, option };\n\t\t\t\t}\n\n\t\t\t\tfunction matches(target: string, options: string[]) {\n\t\t\t\t\treturn options.some(\n\t\t\t\t\t\t(option) =>\n\t\t\t\t\t\t\tclearString(removeRedundant(option), '√', '×') === clearString(removeRedundant(target), '√', '×')\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn { finish: false };\n\t\t},\n\t\t/** 填空题处理器 */\n\t\tasync completion(infos, options, handler) {\n\t\t\tfor (const answers of infos.map((info) => info.results.map((res) => res.answer))) {\n\t\t\t\t// 排除空答案\n\t\t\t\tlet ans = answers.filter((ans) => ans);\n\t\t\t\tif (ans.length === 1) {\n\t\t\t\t\tans = splitAnswer(ans[0], ctx.answerSeparators);\n\t\t\t\t}\n\n\t\t\t\tif (\n\t\t\t\t\tans.length !== 0 &&\n\t\t\t\t\t/** 答案数量要和文本框数量一致，或者文本框只有一个 */\n\t\t\t\t\t(ans.length === options.length || options.length === 1)\n\t\t\t\t) {\n\t\t\t\t\tif (ans.length === options.length) {\n\t\t\t\t\t\tfor (let index = 0; index < options.length; index++) {\n\t\t\t\t\t\t\tconst element = options[index];\n\t\t\t\t\t\t\tawait handler('completion', ans[index], element, ctx);\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn { finish: true };\n\t\t\t\t\t} else if (options.length === 1) {\n\t\t\t\t\t\tawait handler('completion', ans.join(' '), options[0], ctx);\n\t\t\t\t\t\treturn { finish: true };\n\t\t\t\t\t}\n\n\t\t\t\t\treturn { finish: false };\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn { finish: false };\n\t\t}\n\t};\n}\n"
  },
  {
    "path": "packages/core/src/core/worker/utils.ts",
    "content": "import { QuestionTypes, WorkContext } from './interface';\n\n/** 默认题目类型解析器 */\nexport function defaultWorkTypeResolver(ctx: WorkContext<any>): QuestionTypes | undefined {\n\tfunction count(selector: string) {\n\t\tlet c = 0;\n\t\tfor (const option of ctx.elements.options || []) {\n\t\t\tif (option?.querySelector(selector) !== null) {\n\t\t\t\tc++;\n\t\t\t}\n\t\t}\n\t\treturn c;\n\t}\n\treturn count('[type=\"radio\"]') === 2\n\t\t? 'judgement'\n\t\t: count('[type=\"radio\"]') > 2\n\t\t? 'single'\n\t\t: count('[type=\"checkbox\"]') > 2\n\t\t? 'multiple'\n\t\t: count('textarea') >= 1\n\t\t? 'completion'\n\t\t: undefined;\n}\n\n/** 判断答案是否为A-Z的文本, 并且字符序号依次递增, 并且 每个字符是否都只出现了一次 */\nexport function isPlainAnswer(answer: string) {\n\tanswer = answer.trim();\n\tif (answer.length > 8 || !/[A-Z]/.test(answer)) {\n\t\treturn false;\n\t}\n\tconst counter: any = {};\n\tlet min = 0;\n\tfor (let i = 0; i < answer.length; i++) {\n\t\tif (answer.charCodeAt(i) < min) {\n\t\t\treturn false;\n\t\t}\n\t\tmin = answer.charCodeAt(i);\n\t\tcounter[min] = (counter[min] || 0) + 1;\n\t}\n\t// 判断每个字符是否都只出现了一次\n\tfor (const key in counter) {\n\t\tif (counter[key] !== 1) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn true;\n}\n\n/**\n * 判断是否为纯ABCD多选答案，但是中间存在分隔符\n * @param answer  答案\n */\nexport function resolvePlainAnswer(answer: string) {\n\tconst resolve = answer\n\t\t.trim()\n\t\t.replace(/[,，、 #]/g, '')\n\t\t.trim();\n\tif (isPlainAnswer(resolve)) {\n\t\treturn resolve;\n\t}\n}\n\n/** 分割答案 */\nexport function splitAnswer(answer: string, separators = ['===', '#', '---', '###', '|', ';', '；']) {\n\tanswer = answer.trim();\n\tif (answer.length === 0) {\n\t\treturn [];\n\t}\n\tseparators = separators.length === 0 ? ['===', '#', '---', '###', '|', ';', '；'] : separators;\n\tseparators = separators.filter((el) => el.trim().length > 0);\n\n\ttry {\n\t\t// 如果是 json 格式的多选答案\n\t\tconst json = JSON.parse(answer);\n\t\tif (Array.isArray(json)) {\n\t\t\treturn json.map(String).filter((el) => el.trim().length > 0);\n\t\t}\n\t} catch {\n\t\tfor (const sep of separators) {\n\t\t\tif (answer.split(sep).length > 1) {\n\t\t\t\treturn answer.split(sep).filter((el) => el.trim().length > 0);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn [answer];\n}\n"
  },
  {
    "path": "packages/core/src/core/worker/worker.ts",
    "content": "import { $, CommonEventEmitter } from 'easy-us';\nimport { domSearchAll } from '../utils/dom';\nimport {\n\tCustomWorkOptions,\n\tRawElements,\n\tResolverResult,\n\tSimplifyWorkResult,\n\tWorkContext,\n\tWorkerEvents,\n\tWorkOptions,\n\tWorkResult,\n\tWorkUploadType\n} from './interface';\nimport { createDefaultQuestionResolver } from './question.resolver';\nimport { defaultWorkTypeResolver } from './utils';\nimport { AnswerWrapperHandlerConfig } from '../answer-wrapper';\n\n/**\n * 自动答题器， 传入一些指定的配置， 就可以进行自动答题。\n *\n * @param work      工作器, 传入一个方法可自定义工作器，或者使用默认的工作器，详情： {@link WorkOptions.work}\n * @param answerer  查题器, : 默认是 {@link defaultAnswerWrapperHandler}\n *\n */\nexport class OCSWorker<E extends RawElements = RawElements> extends CommonEventEmitter<WorkerEvents> {\n\topts: WorkOptions<E>;\n\tisRunning = false;\n\tisClose = false;\n\tisStop = false;\n\ttotalQuestionCount = 0;\n\n\tconstructor(opts: WorkOptions<E>) {\n\t\tsuper();\n\t\tthis.opts = opts;\n\t}\n\n\t/** 启动答题器  */\n\tasync doWork(options?: { enable_debug?: boolean }) {\n\t\tthis.emit('start');\n\t\tthis.isRunning = true;\n\n\t\tthis.once('close', () => {\n\t\t\tthis.isClose = true;\n\t\t});\n\n\t\tthis.on('stop', () => {\n\t\t\tthis.isStop = true;\n\t\t});\n\n\t\tthis.on('continuate', () => {\n\t\t\tthis.isStop = false;\n\t\t});\n\n\t\t/** 寻找题目父节点 */\n\t\tconst questionRoots: HTMLElement[] | null =\n\t\t\ttypeof this.opts.root === 'string' ? Array.from(document.querySelectorAll(this.opts.root)) : this.opts.root;\n\n\t\tthis.totalQuestionCount += questionRoots.length;\n\n\t\tif (options?.enable_debug) {\n\t\t\tconsole.debug('开始答题', this);\n\t\t\tconsole.debug('题目数量: ', questionRoots.length);\n\t\t\tconsole.debug('父节点列表: ', questionRoots);\n\t\t}\n\n\t\t/** 答题结果 */\n\t\tconst results: WorkResult<E>[] = [];\n\n\t\tif (questionRoots.length === 0) {\n\t\t\tthrow new Error('未找到任何题目，答题结束。');\n\t\t}\n\n\t\t/** 搜索元素 */\n\t\tfor (const questionRoot of questionRoots) {\n\t\t\t// 初始化上下文\n\t\t\tconst ctx: WorkContext<E> = {\n\t\t\t\tsearchInfos: [],\n\t\t\t\troot: questionRoot,\n\t\t\t\telements: domSearchAll<E>(this.opts.elements, questionRoot),\n\t\t\t\ttype: undefined,\n\t\t\t\tanswerSeparators: this.opts.answerSeparators,\n\t\t\t\tanswerMatchMode: this.opts.answerMatchMode || 'similar'\n\t\t\t};\n\n\t\t\t/** 执行元素搜索钩子 */\n\t\t\tawait this.opts.onElementSearched?.(ctx.elements, questionRoot);\n\t\t\t/** 排除掉 null 的元素 */\n\t\t\tctx.elements.title = ctx.elements.title?.filter(Boolean) as HTMLElement[];\n\t\t\tctx.elements.options = ctx.elements.options?.filter(Boolean) as HTMLElement[];\n\n\t\t\t/** 获取题目类型 */\n\t\t\tif (typeof this.opts.work === 'object') {\n\t\t\t\tctx.type =\n\t\t\t\t\tthis.opts.work.type === undefined\n\t\t\t\t\t\t? // 使用默认解析器\n\t\t\t\t\t\t  defaultWorkTypeResolver(ctx)\n\t\t\t\t\t\t: // 自定义解析器\n\t\t\t\t\t\ttypeof this.opts.work.type === 'string'\n\t\t\t\t\t\t? this.opts.work.type\n\t\t\t\t\t\t: this.opts.work.type(ctx);\n\t\t\t}\n\n\t\t\tresults.push({\n\t\t\t\trequested: false,\n\t\t\t\tresolved: false,\n\t\t\t\tctx: ctx\n\t\t\t});\n\t\t}\n\n\t\tif (options?.enable_debug) {\n\t\t\tconsole.debug('上下文已初始化: ', results);\n\t\t}\n\n\t\t/** 请求答案的线程 */\n\t\tconst requestThread = async (index: number) => {\n\t\t\tlet error: string | undefined;\n\t\t\tconst result = results[index];\n\t\t\tconst ctx = result.ctx || ({} as WorkContext<E>);\n\n\t\t\t/** 强行关闭 */\n\t\t\tif (this.isClose === true) {\n\t\t\t\tthis.isRunning = false;\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t/** 检查是否暂停中 */\n\t\t\tif (this.isStop) {\n\t\t\t\tawait waitForContinuate(() => this.isStop);\n\t\t\t}\n\n\t\t\t/** 查找答案 */\n\t\t\tctx.searchInfos = [];\n\n\t\t\tif (options?.enable_debug) {\n\t\t\t\tconsole.groupEnd();\n\t\t\t\tconsole.group(\n\t\t\t\t\t'开始搜题: ',\n\t\t\t\t\tctx.elements.title\n\t\t\t\t\t\t?.map((t) => t?.innerText)\n\t\t\t\t\t\t.filter(Boolean)\n\t\t\t\t\t\t.join(', ')\n\t\t\t\t\t\t.slice(0, 20)\n\t\t\t\t);\n\t\t\t\tconsole.log('ctx', result.ctx);\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tctx.searchInfos = (await this.opts.answerer(ctx.elements, ctx)) || [];\n\n\t\t\t\t// 答案为 undefined 的情况， 需要赋值给一个空字符串，因为可能传回的题目中带有其他提示信息，或者题目里包含答案。\n\t\t\t\tctx.searchInfos.forEach((info) => {\n\t\t\t\t\tinfo.results = info.results.map((ans) => {\n\t\t\t\t\t\tans.answer = ans.answer ? ans.answer.trim() : '';\n\t\t\t\t\t\treturn ans;\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t} catch (err) {\n\t\t\t\terror = String(err);\n\t\t\t}\n\n\t\t\tresult.ctx = ctx;\n\t\t\tresult.requested = true;\n\t\t\tresult.error = error;\n\n\t\t\tif (options?.enable_debug) {\n\t\t\t\tconsole.log('搜题结果: ', ctx.searchInfos);\n\t\t\t}\n\t\t\t/** 回调 */\n\t\t\tawait this.opts.onResultsUpdate?.(results[index], index, results);\n\t\t};\n\n\t\tconst waitForRequested = async (result: WorkResult<E>) => {\n\t\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\t\tconst interval = setInterval(() => {\n\t\t\t\t\tif (result?.requested === true) {\n\t\t\t\t\t\tclearInterval(interval);\n\t\t\t\t\t\tclearTimeout(timeout);\n\t\t\t\t\t\tresolve();\n\t\t\t\t\t}\n\t\t\t\t}, 200);\n\n\t\t\t\tconst timeout = setTimeout(() => {\n\t\t\t\t\tclearInterval(interval);\n\t\t\t\t\treject(new Error('答题超时！'));\n\t\t\t\t}, (AnswerWrapperHandlerConfig.timeout_seconds + 10) * 1000);\n\t\t\t});\n\t\t};\n\n\t\t/** 答题线程， */\n\t\tconst resolverThread = async () => {\n\t\t\tfor (let index = 0; index < results.length; index++) {\n\t\t\t\tconst result = results[index];\n\n\t\t\t\tlet error: string | undefined;\n\t\t\t\tlet res: ResolverResult | undefined;\n\t\t\t\t/** 强行关闭 */\n\t\t\t\tif (this.isClose === true) {\n\t\t\t\t\tthis.isRunning = false;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\ttry {\n\t\t\t\t\t/** 检查是否暂停中 */\n\t\t\t\t\tif (this.isStop) {\n\t\t\t\t\t\tawait waitForContinuate(() => this.isStop);\n\t\t\t\t\t}\n\t\t\t\t\t/** 等待搜题完毕 */\n\t\t\t\t\tawait waitForRequested(result);\n\t\t\t\t} catch (err) {\n\t\t\t\t\t// 超时错误\n\t\t\t\t}\n\n\t\t\t\ttry {\n\t\t\t\t\tif (result.ctx && result.ctx.searchInfos.length !== 0) {\n\t\t\t\t\t\t/** 开始处理 */\n\t\t\t\t\t\tif (typeof this.opts.work === 'object') {\n\t\t\t\t\t\t\tif (result.ctx.elements.options) {\n\t\t\t\t\t\t\t\t/** 使用默认处理器 */\n\n\t\t\t\t\t\t\t\tif (result.ctx.type) {\n\t\t\t\t\t\t\t\t\tconst resolver = createDefaultQuestionResolver(result.ctx)[result.ctx.type];\n\t\t\t\t\t\t\t\t\tconst handler = this.opts.work.handler;\n\t\t\t\t\t\t\t\t\tres = await resolver(result.ctx.searchInfos, result.ctx.elements.options as HTMLElement[], handler);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\terror = '题目类型解析失败, 请自行提供解析器, 或者忽略此题。';\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\terror = 'elements.options 为空 ! 使用默认处理器, 必须提供题目选项的选择器。';\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t/** 使用自定义处理器 */\n\t\t\t\t\t\t\tconst work = this.opts.work;\n\t\t\t\t\t\t\tres = await work(result.ctx);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\terror = '搜索不到答案, 请重新运行, 或者忽略此题。';\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\terror = (err as any)?.message || err;\n\t\t\t\t}\n\n\t\t\t\tresult.error = error;\n\n\t\t\t\t/** 修改答题结果 */\n\t\t\t\tresult.result = res || { finish: false };\n\t\t\t\t/** 设置答题完成 */\n\t\t\t\tresult.resolved = true;\n\n\t\t\t\tif (options?.enable_debug) {\n\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t'答题完成: ',\n\t\t\t\t\t\tresult.ctx?.elements.title\n\t\t\t\t\t\t\t?.map((t) => t?.innerText)\n\t\t\t\t\t\t\t.join(', ')\n\t\t\t\t\t\t\t.slice(0, 20),\n\t\t\t\t\t\tresult\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\t/** 回调 */\n\t\t\t\tawait this.opts.onResultsUpdate?.(result, index, results);\n\t\t\t}\n\t\t};\n\n\t\t/**\n\t\t * 搜题和答题分为两个线程\n\t\t */\n\t\t/** 多线程搜题 */\n\t\tconst requestThreadHandler = async () => {\n\t\t\t/** 线程锁 */\n\t\t\tconst locks: number[] = [];\n\n\t\t\tconst waitForLock = () => {\n\t\t\t\treturn new Promise<number>((resolve, reject) => {\n\t\t\t\t\tconst interval = setInterval(() => {\n\t\t\t\t\t\tif (locks.length > 0) {\n\t\t\t\t\t\t\tconst lock = locks.shift();\n\t\t\t\t\t\t\tif (lock) {\n\t\t\t\t\t\t\t\tresolve(lock);\n\t\t\t\t\t\t\t\tclearInterval(interval);\n\t\t\t\t\t\t\t\tclearTimeout(timeout);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}, 100);\n\n\t\t\t\t\tconst timeout = setTimeout(() => {\n\t\t\t\t\t\tclearInterval(interval);\n\t\t\t\t\t\treject(new Error('获取线程锁超时！'));\n\t\t\t\t\t}, 3 * 60 * 1000);\n\t\t\t\t});\n\t\t\t};\n\n\t\t\tconst requestThreads: Function[] = [];\n\t\t\tfor (let index = 0; index < results.length; index++) {\n\t\t\t\trequestThreads.push(() => requestThread(index));\n\t\t\t}\n\n\t\t\tfor (let index = 0; index < (this.opts.thread || 1); index++) {\n\t\t\t\tlocks.push(index + 1);\n\t\t\t}\n\t\t\tlet requestFinished = 0;\n\n\t\t\tconst promises: Function[] = [];\n\t\t\tfor (let index = 0; index < (this.opts.thread || 1); index++) {\n\t\t\t\tpromises.push(async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\twhile (requestFinished < results.length && requestThreads.length > 0 && this.isClose === false) {\n\t\t\t\t\t\t\tconst thread = requestThreads.shift();\n\t\t\t\t\t\t\tif (thread) {\n\t\t\t\t\t\t\t\tconst lock = await waitForLock();\n\t\t\t\t\t\t\t\tawait thread();\n\t\t\t\t\t\t\t\trequestFinished++;\n\t\t\t\t\t\t\t\tlocks.push(lock);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tconsole.error(err);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tawait Promise.all(promises.map((f) => f()));\n\t\t};\n\n\t\t/** 答题线程 */\n\t\tawait Promise.all([resolverThread(), requestThreadHandler()]);\n\n\t\tthis.isRunning = false;\n\t\treturn results;\n\t}\n\n\t/** 答题结果处理器 */\n\tuploadHandler(options: {\n\t\t// doWork 的返回值结果\n\t\tresults: WorkResult<E>[];\n\t\t// 提交类型\n\t\ttype: WorkUploadType;\n\t\t/**\n\t\t * 是否上传处理器\n\t\t *\n\t\t * @param  uploadable  是否可以上传\n\t\t * @param finishedRate 完成率\n\t\t */\n\t\tcallback: (finishedRate: number, uploadable: boolean) => void | Promise<void>;\n\t}) {\n\t\tconst { results, type, callback } = options;\n\t\tif (type !== 'nomove') {\n\t\t\tlet finished = 0;\n\t\t\tfor (const result of results) {\n\t\t\t\tif (result.result?.finish) {\n\t\t\t\t\tfinished++;\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst rate = results.length === 0 ? 0 : (finished / results.length) * 100;\n\t\t\tif (type === 'force') {\n\t\t\t\treturn callback(rate, true);\n\t\t\t} else {\n\t\t\t\treturn callback(rate, type === 'save' ? false : rate >= parseFloat(type.toString()));\n\t\t\t}\n\t\t}\n\t}\n}\n\nexport class CustomOCSWorker extends CommonEventEmitter<WorkerEvents> {\n\topts: CustomWorkOptions;\n\tisRunning = false;\n\tisClose = false;\n\tisStop = false;\n\n\tconstructor(opts: CustomWorkOptions) {\n\t\tsuper();\n\t\tthis.opts = opts;\n\t}\n\n\t/** 启动答题器  */\n\tasync doWork(options?: { enable_debug?: boolean }) {\n\t\tthis.emit('start');\n\t\tthis.isRunning = true;\n\n\t\tthis.once('close', () => {\n\t\t\tthis.isClose = true;\n\t\t});\n\n\t\tthis.on('stop', () => {\n\t\t\tthis.isStop = true;\n\t\t});\n\n\t\tthis.on('continuate', () => {\n\t\t\tthis.isStop = false;\n\t\t});\n\n\t\tconst questions = await this.opts.questions?.();\n\n\t\tif (options?.enable_debug) {\n\t\t\tconsole.debug('开始答题', this);\n\t\t\tconsole.debug('题目数量: ', this.opts.questions.length);\n\t\t}\n\t\tconst results: SimplifyWorkResult[] = [];\n\n\t\tfor (let index = 0; index < questions.length; index++) {\n\t\t\t/** 强行关闭 */\n\t\t\tif (this.isClose === true) {\n\t\t\t\tthis.isRunning = false;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t/** 检查是否暂停中 */\n\t\t\tif (this.isStop) {\n\t\t\t\tawait waitForContinuate(() => this.isStop);\n\t\t\t}\n\n\t\t\tconst question = questions[index];\n\t\t\tresults[index] = {\n\t\t\t\tquestion: question.text,\n\t\t\t\trequested: false,\n\t\t\t\tresolved: false,\n\t\t\t\tsearchInfos: [],\n\t\t\t\ttype: question.type,\n\t\t\t\tfinish: false,\n\t\t\t\terror: ''\n\t\t\t};\n\n\t\t\ttry {\n\t\t\t\tconst infos = await this.opts.answerer(question.text);\n\t\t\t\tresults[index].searchInfos = infos.map((i) => ({\n\t\t\t\t\tname: i.name,\n\t\t\t\t\thomepage: i.homepage,\n\t\t\t\t\tresults: i.results.map((r) => [r.question, r.answer, r.extra_data || {}]),\n\t\t\t\t\terror: i.error\n\t\t\t\t}));\n\t\t\t\tresults[index].requested = true;\n\t\t\t\tthis.opts.onResultsUpdate?.(results[index], index, results);\n\n\t\t\t\ttry {\n\t\t\t\t\tconst resolved = await this.opts.resolver(infos);\n\t\t\t\t\tresults[index].finish = resolved.finish;\n\t\t\t\t\tresults[index].error = resolved.error;\n\t\t\t\t\tresults[index].resolved = true;\n\t\t\t\t} catch (err) {\n\t\t\t\t\tresults[index].finish = false;\n\t\t\t\t\tresults[index].error = err instanceof Error ? err.message : String(err);\n\t\t\t\t\tresults[index].resolved = true;\n\t\t\t\t}\n\t\t\t\tthis.opts.onResultsUpdate?.(results[index], index, results);\n\t\t\t} catch (err) {\n\t\t\t\tresults[index].requested = true;\n\t\t\t\tresults[index].resolved = false;\n\t\t\t\tresults[index].finish = true;\n\t\t\t\tresults[index].error = err instanceof Error ? err.message : String(err);\n\t\t\t\tthis.opts.onResultsUpdate?.(results[index], index, results);\n\t\t\t}\n\n\t\t\tawait $.sleep(this.opts.period);\n\t\t}\n\t}\n}\n\nasync function waitForContinuate(isStopping: () => boolean) {\n\tif (isStopping()) {\n\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\tconst interval = setInterval(() => {\n\t\t\t\tif (isStopping() === false) {\n\t\t\t\t\tclearInterval(interval);\n\t\t\t\t\tresolve();\n\t\t\t\t}\n\t\t\t}, 200);\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "packages/core/src/index.ts",
    "content": "export * from './utils';\nexport * from './core/utils';\nexport * from './core/worker';\nexport * from './core/answer-wrapper';\n"
  },
  {
    "path": "packages/core/src/utils/common.ts",
    "content": "import debounce from 'lodash/debounce';\n\n/**\n * 公共的工具库\n */\nexport const $ = {\n\t/** 创建唯一id ， 不带横杠 */\n\tuuid() {\n\t\treturn 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function (c) {\n\t\t\tconst r = (Math.random() * 16) | 0;\n\t\t\tconst v = c === 'x' ? r : (r & 0x3) | 0x8;\n\t\t\treturn v.toString(16);\n\t\t});\n\t},\n\n\t/**\n\t * 生成随机数， 使用 Math.round 取整\n\t * @param min 最小值\n\t * @param max 最大值\n\t */\n\trandom(min: number, max: number) {\n\t\treturn Math.round(Math.random() * (max - min)) + min;\n\t},\n\n\t/**\n\t * 暂停\n\t * @param period 毫秒\n\t */\n\tasync sleep(period: number): Promise<void> {\n\t\treturn new Promise((resolve) => {\n\t\t\tsetTimeout(resolve, period);\n\t\t});\n\t},\n\n\t/**\n\t * 当前是否处于浏览器环境\n\t */\n\tisInBrowser(): boolean {\n\t\treturn typeof window !== 'undefined' && typeof window.document !== 'undefined';\n\t},\n\n\t/**\n\t * 使元素变成纯文本对象，（跨域时对象上下文会被销毁）\n\t * @param el 元素\n\t */\n\telementToRawObject(el: HTMLElement | undefined | null) {\n\t\treturn {\n\t\t\tinnerText: el?.innerText,\n\t\t\tinnerHTML: el?.innerHTML,\n\t\t\ttextContent: el?.textContent\n\t\t} as any;\n\t},\n\n\t/**\n\t * 监听页面宽度变化\n\t * @param el 任意元素，如果此元素被移除，则不执行 resize 回调\n\t * @param handler resize 回调\n\t */\n\tonresize<E extends HTMLElement>(el: E, handler: (el: E) => void) {\n\t\tconst resize = debounce(() => {\n\t\t\t/**\n\t\t\t * 如果元素被删除，则移除监听器\n\t\t\t * 不使用 el.parentElement 是因为如果是顶级元素，例如 shadowRoot 中的一级子元素， el.parentNode 不为空，但是 el.parentElement 为空\n\t\t\t */\n\t\t\tif (el.parentNode === null) {\n\t\t\t\twindow.removeEventListener('resize', resize);\n\t\t\t} else {\n\t\t\t\thandler(el);\n\t\t\t}\n\t\t}, 200);\n\t\tresize();\n\t\twindow.addEventListener('resize', resize);\n\t},\n\t/** 是否处于顶级 window ，而不是子 iframe */\n\tisInTopWindow() {\n\t\treturn self === top;\n\t},\n\t/**\n\t * 创建弹出窗口\n\t * @param url 地址\n\t * @param winName 窗口名\n\t * @param w 宽\n\t * @param h 高\n\t * @param scroll 滚动条\n\t */\n\tcreateCenteredPopupWindow(\n\t\turl: string,\n\t\twinName: string,\n\t\topts: {\n\t\t\twidth: number;\n\t\t\theight: number;\n\t\t\tscrollbars: boolean;\n\t\t\tresizable: boolean;\n\t\t}\n\t) {\n\t\tconst { width, height, scrollbars, resizable } = opts;\n\t\tconst LeftPosition = screen.width ? (screen.width - width) / 2 : 0;\n\t\tconst TopPosition = screen.height ? (screen.height - height) / 2 : 0;\n\n\t\tconst settings =\n\t\t\t'height=' +\n\t\t\theight +\n\t\t\t',width=' +\n\t\t\twidth +\n\t\t\t',top=' +\n\t\t\tTopPosition +\n\t\t\t',left=' +\n\t\t\tLeftPosition +\n\t\t\t',scrollbars=' +\n\t\t\t(scrollbars ? 'yes' : 'no') +\n\t\t\t',resizable=' +\n\t\t\t(resizable ? 'yes' : 'no');\n\n\t\treturn window.open(url, winName, settings);\n\t},\n\ttransition: async (\n\t\tel: HTMLElement,\n\t\tproperties: keyof CSSStyleDeclaration,\n\t\tduration_ms: number,\n\t\tval: any,\n\t\toptions?: {\n\t\t\treset_ms?: number;\n\t\t\ttiming_function?: string;\n\t\t}\n\t) => {\n\t\treturn new Promise<void>((resolve) => {\n\t\t\tconst original_val = Reflect.get(el.style, properties) || '';\n\t\t\tel.style.transition = `${String(properties)} ${duration_ms}s ${options?.timing_function || 'ease-in-out'}`;\n\t\t\tReflect.set(el.style, properties, val);\n\t\t\tel.addEventListener('transitionend', function handler() {\n\t\t\t\tel.removeEventListener('transitionend', handler);\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\tReflect.set(el.style, properties, original_val);\n\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\tel.style.transition = '';\n\t\t\t\t\t}, duration_ms * 1000);\n\t\t\t\t}, (options?.reset_ms || 0) * 1000);\n\t\t\t\tresolve();\n\t\t\t});\n\t\t});\n\t}\n};\n"
  },
  {
    "path": "packages/core/src/utils/const.ts",
    "content": "export const $const = {\n\tTAB_UID: '_uid_',\n\tTAB_URLS: '_urls_',\n\tTAB_CURRENT_PANEL_NAME: '_current_panel_name_'\n};\n"
  },
  {
    "path": "packages/core/src/utils/index.ts",
    "content": "export * from './common';\nexport * from './string';\nexport * from './const';\nexport * from './playwright';\n"
  },
  {
    "path": "packages/core/src/utils/playwright.ts",
    "content": "import type { Page } from 'playwright-core';\nimport { request } from '../core/utils';\nimport { $ } from './common';\nimport { $elements, $message } from 'easy-us';\n\nexport type Base64 = string;\n\ninterface ClickOptions {\n\t/**\n\t * Defaults to `left`.\n\t */\n\tbutton?: 'left' | 'right' | 'middle';\n\n\t/**\n\t * defaults to 1. See [UIEvent.detail].\n\t */\n\tclickCount?: number;\n\n\t/**\n\t * Time to wait between `mousedown` and `mouseup` in milliseconds. Defaults to 0.\n\t */\n\tdelay?: number;\n\n\t/**\n\t * Whether to bypass the [actionability](https://playwright.dev/docs/actionability) checks. Defaults to `false`.\n\t */\n\tforce?: boolean;\n\n\t/**\n\t * Modifier keys to press. Ensures that only these modifiers are pressed during the operation, and then restores\n\t * current modifiers back. If not specified, currently pressed modifiers are used.\n\t */\n\tmodifiers?: Array<'Alt' | 'Control' | 'Meta' | 'Shift'>;\n\n\t/**\n\t * Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You\n\t * can opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as\n\t * navigating to inaccessible pages. Defaults to `false`.\n\t */\n\tnoWaitAfter?: boolean;\n\n\t/**\n\t * A point to use relative to the top-left corner of element padding box. If not specified, uses some visible point of\n\t * the element.\n\t */\n\tposition?: {\n\t\tx: number;\n\n\t\ty: number;\n\t};\n\n\t/**\n\t * When true, the call requires selector to resolve to a single element. If given selector resolves to more than one\n\t * element, the call throws an exception.\n\t */\n\tstrict?: boolean;\n\n\t/**\n\t * Maximum time in milliseconds. Defaults to `0` - no timeout. The default value can be changed via `actionTimeout`\n\t * option in the config, or by using the\n\t * [browserContext.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-browsercontext#browser-context-set-default-timeout)\n\t * or [page.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-page#page-set-default-timeout) methods.\n\t */\n\ttimeout?: number;\n\n\t/**\n\t * When set, this method only performs the [actionability](https://playwright.dev/docs/actionability) checks and skips the action. Defaults\n\t * to `false`. Useful to wait until the element is ready for the action without performing it.\n\t */\n\ttrial?: boolean;\n}\n\nexport interface RemotePage {\n\tclick: (selectorOrElement: string | Element, options?: ClickOptions) => Promise<void>;\n\tcheck: Page['check'];\n\tdblclick: Page['dblclick'];\n\tbringToFront: Page['bringToFront'];\n\tdragAndDrop: Page['dragAndDrop'];\n\tfill: Page['fill'];\n\tfocus: Page['focus'];\n\thover: Page['hover'];\n\tscreenshot: Page['screenshot'];\n\tselectOption: Page['selectOption'];\n\tsetInputFiles: Page['setInputFiles'];\n\ttap: Page['tap'];\n\tpress: Page['press'];\n\treload: (...args: Parameters<Page['reload']>) => Promise<Base64>;\n\twaitForRequest(...args: Parameters<Page['waitForRequest']>): Promise<{\n\t\turl: string;\n\t\tmethod: string;\n\t\theaders: Record<string, string>;\n\t\tpostData: string;\n\t}>;\n\twaitForResponse(...args: Parameters<Page['waitForResponse']>): Promise<{\n\t\tbody: string;\n\t\theaders: Record<string, string>;\n\t\tstatus: number;\n\t\turl: string;\n\t}>;\n\twaitForSelector(...args: Parameters<Page['waitForSelector']>): Promise<void>;\n\t['keyboard.type']: Page['keyboard']['type'];\n\t['keyboard.press']: Page['keyboard']['press'];\n\t['mouse.wheel']: Page['mouse']['wheel'];\n\t['mouse.click']: Page['mouse']['click'];\n\t['mouse.dblclick']: Page['mouse']['dblclick'];\n\t['mouse.down']: Page['mouse']['down'];\n\t['mouse.up']: Page['mouse']['up'];\n\t['mouse.move']: Page['mouse']['move'];\n}\n\nconst ListOfActions = [\n\t'click',\n\t'check',\n\t'dblclick',\n\t'bringToFront',\n\t'dragAndDrop',\n\t'fill',\n\t'focus',\n\t'hover',\n\t'screenshot',\n\t'selectOption',\n\t'setInputFiles',\n\t'tap',\n\t'press',\n\t'reload',\n\t'waitForRequest',\n\t'waitForResponse',\n\t'waitForSelector',\n\t'keyboard.type',\n\t'keyboard.press',\n\t'mouse.wheel',\n\t'mouse.click',\n\t'mouse.dblclick',\n\t'mouse.down',\n\t'mouse.up',\n\t'mouse.move'\n];\n\nconst mouseIdleRequireActions = [\n\t'click',\n\t'dblclick',\n\t'dragAndDrop',\n\t'fill',\n\t'hover',\n\t'tap',\n\t'press',\n\t'keyboard.type',\n\t'keyboard.press',\n\t'mouse.wheel',\n\t'mouse.click',\n\t'mouse.dblclick',\n\t'mouse.down',\n\t'mouse.up',\n\t'mouse.move'\n];\n\nexport class RemotePlaywright {\n\tprivate static authToken = '';\n\tprivate static currentPage: RemotePage | undefined = undefined;\n\n\tstatic async getRemotePage(\n\t\tshow_debug_cursor?: boolean,\n\t\tlogger?: (...args: any[]) => void\n\t): Promise<RemotePage | undefined> {\n\t\tif (this.currentPage) {\n\t\t\treturn this.currentPage;\n\t\t}\n\t\t/**\n\t\t * OCS桌面端后端无法拦截 GM_xmlhttpRequest ，所以这里使用 fetch 请求动作执行，然后后端根据key判断是否允许执行\n\t\t */\n\t\tif (!this.authToken) {\n\t\t\ttry {\n\t\t\t\tthis.authToken = await request('http://localhost:15319/get-actions-key', {\n\t\t\t\t\ttype: 'GM_xmlhttpRequest',\n\t\t\t\t\tmethod: 'get',\n\t\t\t\t\tresponseType: 'text'\n\t\t\t\t});\n\t\t\t\tthis.currentPage = this.createRemotePage(this.authToken, { show_debug_cursor, logger });\n\t\t\t\treturn this.currentPage;\n\t\t\t} catch (e) {\n\t\t\t\tconsole.log(e);\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t} else {\n\t\t\tthis.currentPage = this.createRemotePage(this.authToken, { show_debug_cursor, logger });\n\t\t\treturn this.currentPage;\n\t\t}\n\t}\n\n\tprivate static createRemotePage(\n\t\tauthToken: string,\n\t\tconfigs?: { show_debug_cursor?: boolean; logger?: (...args: any[]) => void }\n\t) {\n\t\tconst page = Object.create({});\n\t\tconfigs = configs || {};\n\t\tconfigs.logger = configs.logger || console.debug;\n\t\tfor (const property of ListOfActions) {\n\t\t\tReflect.set(page, property, async (...args: any[]) => {\n\t\t\t\tlet data;\n\n\t\t\t\tif (mouseIdleRequireActions.includes(property)) {\n\t\t\t\t\t// 等待鼠标静止/空闲\n\t\t\t\t\tawait waitForMouseIdle();\n\t\t\t\t}\n\n\t\t\t\tif (property === 'click') {\n\t\t\t\t\tif (args[0] instanceof Element) {\n\t\t\t\t\t\tconst el = args[0] as HTMLElement;\n\t\t\t\t\t\tconst options = (args[1] || {}) as ClickOptions;\n\n\t\t\t\t\t\tawait scrollToElement(el);\n\t\t\t\t\t\t// 如果是传入的元素对象，那么就解析元素的坐标进行点击\n\t\t\t\t\t\t// 这里滑动的时间可能会比较长，取决于页面的长度，所以这里多等待一点时间\n\t\t\t\t\t\tawait $.sleep(500);\n\t\t\t\t\t\tconst rect = el.getBoundingClientRect();\n\n\t\t\t\t\t\t// 移动可能阻挡点击的脚本面板\n\t\t\t\t\t\tconst elFromPoint = $elements.root?.elementFromPoint(\n\t\t\t\t\t\t\trect.left + rect.width / 2,\n\t\t\t\t\t\t\trect.top + rect.height / 2\n\t\t\t\t\t\t);\n\t\t\t\t\t\tif (elFromPoint && $elements.root && $elements.root.contains(elFromPoint)) {\n\t\t\t\t\t\t\t// 如果元素在根节点内，则隐藏面板\n\t\t\t\t\t\t\tconst panel = $elements.root.querySelector<HTMLElement>('container-element');\n\n\t\t\t\t\t\t\tif (panel) {\n\t\t\t\t\t\t\t\t$message.info({ content: '检测到脚本阻挡点击位置，已自动移开', duration: 2 });\n\t\t\t\t\t\t\t\tawait $.transition(panel, 'left', 0.1, rect.left + rect.width / 2 + 100 + 'px', { reset_ms: 1 });\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// 显示鼠标位置\n\t\t\t\t\t\tif (configs?.show_debug_cursor) {\n\t\t\t\t\t\t\tshowMousePointer(el);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tdata = {\n\t\t\t\t\t\t\tpage: window.location.href,\n\t\t\t\t\t\t\tproperty: 'mouse.click',\n\t\t\t\t\t\t\targs: [\n\t\t\t\t\t\t\t\trect.left + rect.width / 2,\n\t\t\t\t\t\t\t\trect.top + rect.height / 2,\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tbutton: options.button,\n\t\t\t\t\t\t\t\t\tclickCount: options.clickCount,\n\t\t\t\t\t\t\t\t\tdelay: options.delay\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t};\n\t\t\t\t\t} else if (typeof args[0] === 'string') {\n\t\t\t\t\t\tconst el = document.querySelector(args[0]) as HTMLElement;\n\t\t\t\t\t\tif (el) {\n\t\t\t\t\t\t\tawait scrollToElement(el);\n\t\t\t\t\t\t\t// 显示鼠标位置\n\t\t\t\t\t\t\tif (configs?.show_debug_cursor) {\n\t\t\t\t\t\t\t\tshowMousePointer(el);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (!data) {\n\t\t\t\t\tdata = { page: window.location.href, property: property, args: args };\n\t\t\t\t}\n\n\t\t\t\tconfigs?.logger?.('[RP]: ', JSON.stringify(data));\n\n\t\t\t\ttry {\n\t\t\t\t\t// 这里为什么不写前缀 http://localhost:15319，因为有 Content-Security-Policy ， 这里我们借用后台的URL代理去进行处理，只要包含 ocs-script-actions 即可轻松绕过 Content-Security-Policy 限制\n\t\t\t\t\tconst res = await request('/ocs-script-actions', {\n\t\t\t\t\t\ttype: 'fetch',\n\t\t\t\t\t\tmethod: 'post',\n\t\t\t\t\t\tresponseType: ['waitForRequest', 'waitForResponse', 'reload'].includes(property) ? 'json' : 'text',\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t'auth-token': authToken\n\t\t\t\t\t\t},\n\t\t\t\t\t\tdata: data\n\t\t\t\t\t});\n\t\t\t\t\treturn res;\n\t\t\t\t} catch (e) {\n\t\t\t\t\tconfigs?.logger?.('[RP-ERROR]: ', JSON.stringify(data));\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t\tconsole.log(page);\n\n\t\treturn page;\n\t}\n}\n\nfunction scrollToElement(el: HTMLElement) {\n\tel.scrollIntoView({ behavior: 'smooth', block: 'center' });\n\t// 等待动作完成\n\treturn $.sleep(200);\n}\n\nfunction showMousePointer(el: HTMLElement) {\n\tsetTimeout(() => {\n\t\tconst rect = el.getBoundingClientRect();\n\t\t// 显示鼠标位置\n\t\tconst div = document.createElement('div');\n\t\tdiv.textContent = '';\n\t\tdiv.style.position = 'fixed';\n\t\tdiv.style.zIndex = '99999';\n\t\tdiv.style.width = '20px';\n\t\tdiv.style.height = '20px';\n\t\tdiv.style.border = '2px solid red';\n\t\tdiv.style.borderRadius = '50%';\n\t\tdiv.style.left = rect.left + rect.width / 2 - 11 + 'px';\n\t\tdiv.style.top = rect.top + rect.height / 2 - 11 + 'px';\n\t\tdocument.body.append(div);\n\t\tsetTimeout(() => {\n\t\t\tdiv.remove();\n\t\t}, 500);\n\t}, 100);\n}\n\nfunction waitForMouseIdle(timeout: number = 200): Promise<void> {\n\treturn new Promise((resolve) => {\n\t\tlet timer: any;\n\t\tconst default_timer = setTimeout(() => {\n\t\t\twindow.removeEventListener('mousemove', onMouseMove);\n\t\t\tresolve();\n\t\t}, timeout); // 最多等timeout + 1000ms\n\t\tfunction onMouseMove() {\n\t\t\tclearTimeout(default_timer);\n\t\t\tif (timer) {\n\t\t\t\tclearTimeout(timer);\n\t\t\t}\n\t\t\ttimer = setTimeout(() => {\n\t\t\t\twindow.removeEventListener('mousemove', onMouseMove);\n\t\t\t\tresolve();\n\t\t\t}, timeout);\n\t\t}\n\t\twindow.addEventListener('mousemove', onMouseMove);\n\t});\n}\n"
  },
  {
    "path": "packages/core/src/utils/string.ts",
    "content": "/**\n * 字符串工具库\n */\nexport const $string = {\n\t/**\n\t * 驼峰转目标字符串\n\t * @param value\n\t */\n\thumpToTarget(value: string, target: string) {\n\t\treturn value\n\t\t\t.replace(/([A-Z])/g, target + '$1')\n\t\t\t.toLowerCase()\n\t\t\t.split(target)\n\t\t\t.slice(1)\n\t\t\t.join(target);\n\t}\n};\n\nexport class StringUtils {\n\t_text: string;\n\tconstructor(_text: string) {\n\t\tthis._text = _text;\n\t}\n\n\t/**\n\t * 删除换行符\n\t * @param replace_str 替代的字符串\n\t */\n\tstatic nowrap(str: string, replace_str: string) {\n\t\treturn str?.replace(/\\n/g, replace_str) || '';\n\t}\n\n\t/**\n\t * 删除换行符\n\t * @param replace_str 替代的字符串\n\t */\n\tnowrap(replace_str: string) {\n\t\tthis._text = StringUtils.nowrap(this._text, replace_str);\n\t\treturn this;\n\t}\n\n\t/** 删除空格，多个空格只留一个 */\n\tstatic nospace(str?: string) {\n\t\treturn str?.replace(/ +/g, ' ') || '';\n\t}\n\n\t/** 删除空格，多个空格只留一个 */\n\tnospace() {\n\t\tthis._text = StringUtils.nospace(this._text);\n\t\treturn this;\n\t}\n\n\t/** 删除特殊字符 */\n\tstatic noSpecialChar(str?: string) {\n\t\treturn str?.replace(/[^\\w\\s]/gi, '') || '';\n\t}\n\n\t/** 删除特殊字符 */\n\tnoSpecialChar() {\n\t\tthis._text = StringUtils.noSpecialChar(this._text);\n\t\treturn this;\n\t}\n\n\t/** 最大长度，剩余显示省略号 */\n\tstatic max(str: string, len: number) {\n\t\treturn str.length > len ? str.substring(0, len) + '...' : str;\n\t}\n\n\t/** 最大长度，剩余显示省略号 */\n\tmax(len: number) {\n\t\tthis._text = StringUtils.max(this._text, len);\n\t\treturn this;\n\t}\n\n\t/** 隐藏字符串 */\n\tstatic hide(str: string, start: number, end: number, replacer: string = '*') {\n\t\t// 从 start 到 end 中间的字符串全部替换成 replacer\n\t\treturn str.substring(0, start) + str.substring(start, end).replace(/./g, replacer) + str.substring(end);\n\t}\n\n\t/** 隐藏字符串 */\n\thide(start: number, end: number, replacer: string = '*') {\n\t\tthis._text = StringUtils.hide(this._text, start, end, replacer);\n\t\treturn this;\n\t}\n\n\t/**\n\t * 根据字符串创建 StringUtils 对象\n\t * @param text  字符串\n\t */\n\tstatic of(text: string) {\n\t\treturn new StringUtils(text);\n\t}\n\n\ttoString() {\n\t\treturn this._text;\n\t}\n}\n"
  },
  {
    "path": "packages/core/tsconfig.json",
    "content": "{\n\t\"compilerOptions\": {\n\t\t\"target\": \"es6\",\n\t\t\"module\": \"commonjs\",\n\t\t\"outDir\": \"./lib\",\n\t\t\"declaration\": true,\n\t\t\"strict\": true,\n\t\t\"esModuleInterop\": true,\n\t\t\"skipLibCheck\": true,\n\t\t\"forceConsistentCasingInFileNames\": true,\n\t\t\"lib\": [\"DOM\", \"ES2020\"],\n\t\t\"jsx\": \"preserve\",\n\t\t\"resolveJsonModule\": true,\n\t\t\"downlevelIteration\": true\n\t},\n\t\"include\": [\"./src/**/*.ts\"]\n}\n"
  },
  {
    "path": "packages/core/typedoc.json",
    "content": "{\n\t// Comments are supported, like tsconfig.json\n\t\"$schema\": \"https://typedoc.org/schema.json\",\n\t\"entryPoints\": [\"./src/elements/index.ts\", \"./src/utils/index.ts\"],\n\t\"out\": \"docs\",\n\t\"cleanOutputDir\": true,\n\t\"darkHighlightTheme\": \"github-dark\",\n\t\"lightHighlightTheme\": \"github-light\",\n\t\"name\": \"OCS - API\",\n\t\"readme\": \"../../README.md\",\n\t\"customCss\": \"./assets/docs.css\",\n\t\"kindSortOrder\": [\"Variable\", \"Property\", \"Method\", \"Function\"]\n}\n"
  },
  {
    "path": "packages/core/vite.config.ts",
    "content": "import { visualizer } from 'rollup-plugin-visualizer';\nimport { defineConfig } from 'vite';\nimport banner from 'vite-plugin-banner';\nimport { author, description, homepage, license, name } from '../../package.json';\nimport dotenv from 'dotenv';\n\nconst bannerContent = `\n/*!\n * ${name} ( ${homepage} )\n * ${description}\n * copyright ${author}\n * license ${license}\n */\n`;\n\ndotenv.config();\n\n// https://vitejs.dev/config/\nexport default defineConfig({\n\tbuild: {\n\t\t/** 取消css代码分离 */\n\t\tcssCodeSplit: false,\n\t\t/** 输出路径 */\n\t\toutDir: process.env.VITE_BUILD_PATH,\n\t\t/** 清空输出路径 */\n\t\temptyOutDir: false,\n\t\t/** 是否压缩代码 */\n\t\tminify: false,\n\t\t/** 打包库， 全局名字为 OCS */\n\t\tlib: {\n\t\t\tentry: './src/index.ts',\n\t\t\tname: 'OCS',\n\t\t\tfileName: () => 'core.js',\n\t\t\tformats: ['umd']\n\t\t}\n\t},\n\n\tplugins: [visualizer(), banner(bannerContent)]\n});\n"
  },
  {
    "path": "packages/scripts/.eslintrc.json",
    "content": "{\n\t\"extends\": [\"../../.eslintrc.json\"],\n\t\"globals\": {\n\t\t\"core\": \"readonly\",\n\t\t\"scripts\": \"readonly\"\n\t}\n}\n"
  },
  {
    "path": "packages/scripts/.gitignore",
    "content": "# customize\n\ntest/\nstats.html\n\nsrc/assets/css\n\n# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\nlerna-debug.log*\n.pnpm-debug.log*\n\n# Diagnostic reports (https://nodejs.org/api/report.html)\nreport.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n*.lcov\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Bower dependency directory (https://bower.io/)\nbower_components\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (https://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules/\njspm_packages/\n\n# Snowpack dependency directory (https://snowpack.dev/)\nweb_modules/\n\n# TypeScript cache\n*.tsbuildinfo\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Optional stylelint cache\n.stylelintcache\n\n# Microbundle cache\n.rpt2_cache/\n.rts2_cache_cjs/\n.rts2_cache_es/\n.rts2_cache_umd/\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variable files\n.env\n.env.development.local\n.env.test.local\n.env.production.local\n.env.local\n\n# parcel-bundler cache (https://parceljs.org/)\n.cache\n.parcel-cache\n\n# Next.js build output\n.next\nout\n\n# Nuxt.js build / generate output\n.nuxt\ndist\n\n# Gatsby files\n.cache/\n# Comment in the public line in if your project uses Gatsby and not Next.js\n# https://nextjs.org/blog/next-9-1#public-directory-support\n# public\n\n# vuepress build output\n.vuepress/dist\n\n# vuepress v2.x temp and cache directory\n.temp\n.cache\n\n# Docusaurus cache and generated files\n.docusaurus\n\n# Serverless directories\n.serverless/\n\n# FuseBox cache\n.fusebox/\n\n# DynamoDB Local files\n.dynamodb/\n\n# TernJS port file\n.tern-port\n\n# Stores VSCode versions used for testing VSCode extensions\n.vscode-test\n\n# yarn v2\n.yarn/cache\n.yarn/unplugged\n.yarn/build-state.yml\n.yarn/install-state.gz\n.pnp.*"
  },
  {
    "path": "packages/scripts/assets/css/style.css",
    "content": "/** 默认字体 */\n/** 输入框默认边距 */\nul,\nol {\n\tline-height: 26px;\n\tpadding-left: 22px;\n\tmargin: 0px;\n}\na {\n\tcolor: #1890ff;\n}\nhr {\n\tborder-style: solid;\n\tborder-color: #63636346;\n\tborder-width: 0px;\n\tborder-bottom: 1px solid #63636346;\n\tmargin-block-start: 1em;\n\tmargin-block-end: 1em;\n}\n.base-style-active-form-control {\n\tborder: 1px solid #ffffff00;\n}\n.base-style-active-form-control:focus {\n\tborder: 1px solid #0e8de290;\n\tbox-shadow: 0px 0px 4px #0e8de252;\n}\n.base-style-active-form-control:focus:not([type='checkbox'], [type='radio']) {\n\tborder: 1px solid #0e8de290;\n\tbox-shadow: 0px 0px 4px #0e8de252;\n\tbackground-color: white !important;\n}\n.base-style-active-form-control:hover {\n\tbackground-color: #ebeef4;\n}\n.base-style-input {\n\toutline: none;\n\tborder: 1px solid #ffffff00;\n\tpadding: 2px 8px;\n\tmargin: 0px;\n\tbackground-color: #eef2f7;\n\tborder-radius: 2px;\n\tcolor: black;\n}\n.base-style-input::placeholder {\n\tcolor: #bababa;\n}\n.base-style-switch {\n\tappearance: none;\n\t-moz-appearance: none;\n\t-webkit-appearance: none;\n\twidth: fit-content;\n\tmin-width: 36px;\n\theight: 20px;\n\tborder-radius: 100px;\n\tdisplay: flex;\n\talign-items: center;\n\tpadding: 2px 4px;\n\ttransition: all 0.2s ease-in-out;\n\twidth: auto;\n\tbackground: gainsboro;\n}\n.base-style-switch:checked {\n\tbackground: #1890ff;\n}\n.base-style-switch:disabled {\n\tbackground-color: #f7f7f78b;\n}\n.base-style-switch:checked::before {\n\ttransform: translate(100%, 0px);\n}\n.base-style-switch::before {\n\tbackground-color: #fff;\n\tborder-radius: 9px;\n\tbox-shadow: 0 2px 4px #00230b33;\n\twidth: 14px;\n\theight: 14px;\n\tcontent: '';\n}\n.base-style-button {\n\tappearance: none;\n\t-moz-appearance: none;\n\t-webkit-appearance: none;\n\tborder-radius: 4px;\n\tbackground-color: white;\n\tborder: 1px solid #2c92ff;\n\tcolor: #409eff;\n\tcursor: pointer !important;\n\tmargin-bottom: 4px;\n}\n.base-style-button:active {\n\tbox-shadow: 0px 0px 8px #0e8de2a5;\n}\n.base-style-button + .base-style-button {\n\tmargin-left: 12px;\n}\n.base-style-button:hover {\n\tbackground-color: #7abbff24;\n}\n.base-style-button.danger:hover {\n\tbackground-color: #ffdede86;\n}\n.base-style-button:disabled {\n\tbackground-color: white;\n\tborder: 1px solid #c0c0c0;\n\tcolor: #aeaeae;\n\tcursor: not-allowed;\n}\n.base-style-button.danger {\n\tcolor: #f36669;\n\tborder-color: #dd5a5d;\n}\n.base-style-button:disabled:active {\n\tbox-shadow: none;\n}\n.base-style-button-secondary {\n\tappearance: none;\n\t-moz-appearance: none;\n\t-webkit-appearance: none;\n\tborder-radius: 4px;\n\tborder: 1px solid #2c92ff;\n\tcolor: #409eff;\n\tcursor: pointer !important;\n\tmargin-bottom: 4px;\n\tcolor: gray;\n\tbackground-color: white;\n\tborder: 1px solid #dcdcdc;\n}\n.base-style-button-secondary:active {\n\tbox-shadow: 0px 0px 8px #0e8de2a5;\n}\n.base-style-button-secondary + .base-style-button-secondary {\n\tmargin-left: 12px;\n}\n.base-style-button-secondary:hover {\n\tbackground-color: #7abbff24;\n}\n.base-style-button-secondary.danger:hover {\n\tbackground-color: #ffdede86;\n}\n.base-style-button-secondary:disabled {\n\tbackground-color: white;\n\tborder: 1px solid #c0c0c0;\n\tcolor: #aeaeae;\n\tcursor: not-allowed;\n}\n.base-style-button-secondary.danger {\n\tcolor: #f36669;\n\tborder-color: #dd5a5d;\n}\n.base-style-button-secondary:disabled:active {\n\tbox-shadow: none;\n}\ncontainer-element.hidden {\n\tdisplay: none;\n}\ncontainer-element.minimize {\n\tmin-width: unset;\n}\ncontainer-element {\n\tposition: fixed;\n\ttop: 10%;\n\tleft: 10%;\n\tz-index: 99999;\n\ttext-align: left;\n\tmin-width: 300px;\n\t-webkit-font-smoothing: antialiased;\n\t-moz-osx-font-smoothing: grayscale;\n\tcolor: #636363;\n\tbox-shadow: 0 0 24px -12px #3f3f3f;\n\tborder-radius: 8px;\n\tletter-spacing: 0.5px;\n\tborder: 1px solid #c1c1c1;\n}\nheader-element {\n\tdisplay: flex;\n\talign-items: center;\n\tbackground-color: white;\n\tborder-radius: 8px 8px 0px 0px;\n\tuser-select: none;\n\tpadding: 4px;\n\tpadding-bottom: 0px;\n}\nheader-element .extra-menu-bar {\n\twidth: 100%;\n\tpadding: 4px;\n\tpadding-bottom: 0px;\n\tmargin-top: 4px;\n\tborder-top: 1px solid #e8e8e8;\n\t/** 默认隐藏，一直到需要激活的时候再更改 */\n\tdisplay: none;\n}\nheader-element .extra-menu-bar .script-panel-link {\n\tappearance: none;\n\t-moz-appearance: none;\n\t-webkit-appearance: none;\n\tborder-radius: 4px;\n\tborder: 1px solid #2c92ff;\n\tcolor: #409eff;\n\tcursor: pointer !important;\n\tmargin-bottom: 4px;\n\tcolor: gray;\n\tbackground-color: white;\n\tborder: 1px solid #dcdcdc;\n\tpadding-bottom: 2px;\n\tmargin-bottom: 0px;\n}\nheader-element .extra-menu-bar .script-panel-link:active {\n\tbox-shadow: 0px 0px 8px #0e8de2a5;\n}\nheader-element .extra-menu-bar .script-panel-link + header-element .extra-menu-bar .script-panel-link {\n\tmargin-left: 12px;\n}\nheader-element .extra-menu-bar .script-panel-link:hover {\n\tbackground-color: #7abbff24;\n}\nheader-element .extra-menu-bar .script-panel-link.danger:hover {\n\tbackground-color: #ffdede86;\n}\nheader-element .extra-menu-bar .script-panel-link:disabled {\n\tbackground-color: white;\n\tborder: 1px solid #c0c0c0;\n\tcolor: #aeaeae;\n\tcursor: not-allowed;\n}\nheader-element .extra-menu-bar .script-panel-link.danger {\n\tcolor: #f36669;\n\tborder-color: #dd5a5d;\n}\nheader-element .extra-menu-bar .script-panel-link:disabled:active {\n\tbox-shadow: none;\n}\nheader-element .extra-menu-bar .script-panel-link.active {\n\tbackground-color: #1890ff1a;\n\tborder-color: #1890ff;\n\tcolor: #1890ff;\n}\nheader-element .extra-menu-bar .script-panel-link + .script-panel-link {\n\tmargin-left: 4px;\n}\nheader-element .profile {\n\tflex: 1;\n\tcursor: move;\n}\nheader-element .switch:hover,\nheader-element .dropdown:hover {\n\tbackground-color: #f3f3f3;\n}\nheader-element .close:hover {\n\tbackground-color: #ff000038;\n}\nheader-element .switch,\nheader-element .close {\n\tcursor: pointer;\n}\nheader-element .dropdown {\n\tline-height: 24px;\n\ttext-decoration: underline;\n}\nheader-element .switch,\nheader-element .close,\nheader-element .profile {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tpadding: 0px 8px;\n}\n.logo {\n\twidth: 18px;\n\theight: 18px;\n\tcursor: pointer;\n}\n.project-selector {\n\tdisplay: flex;\n\talign-items: center;\n}\n.project-selector select {\n\tbackground: #ffffff00;\n\tborder-radius: 4px;\n\tborder: 1px solid #63636346;\n\tpadding: 4px;\n}\n.project-selector.expand-all {\n\tdisplay: none;\n}\n.body {\n\toverflow: auto;\n\twidth: auto;\n\theight: 100%;\n}\nscript-panel-element {\n\tdisplay: block;\n\tbackground-color: white;\n\tborder-radius: 0px 0px 8px 8px;\n\tpadding: 0px 8px 12px 8px;\n\toverflow: auto;\n}\nscript-panel-element .script-panel-body {\n\tpadding: 0px 8px;\n}\nscript-panel-element + script-panel-element {\n\tmargin-top: 12px;\n}\n.card + .card {\n\tmargin-top: 12px;\n}\n.card {\n\tbackground-color: white;\n\tborder-radius: 2px;\n\tpadding: 0px 8px;\n}\n.notes {\n\tbackground: #0099ff0e;\n\tborder-left: 4px solid #0099ff65;\n\twidth: -webkit-fill-available;\n\tmargin: 0px 8px;\n\tline-height: 26px;\n\tletter-spacing: 1px;\n}\n.secondary {\n\tfont-size: 12px;\n\tcolor: #8b8b8b;\n}\n.tooltip-container {\n\tz-index: 99999999999999;\n\tmargin: 12px 0px 0px 12px;\n\tpadding: 4px;\n\tcolor: black;\n\tbackground: #f0f0f0;\n\tbox-shadow: 0px 0px 4px #949494;\n\tposition: fixed;\n\twhite-space: normal;\n\tmax-width: 200px;\n\theight: auto;\n\tborder-radius: 2px;\n\tline-height: 18px;\n}\n.configs-container.lock {\n\tfilter: blur(1px);\n\tuser-select: none;\n\t-webkit-user-select: none;\n\t-moz-user-select: none;\n\t-ms-user-select: none;\n}\n.configs-container .lock-wrapper {\n\tcursor: not-allowed !important;\n\tborder-radius: 4px;\n\tposition: absolute;\n\tleft: 0px;\n\tz-index: 1;\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n}\n.configs-container .lock-message {\n\tbackground-color: #ffffff7d;\n\tborder-radius: 4px;\n\tbox-shadow: 0px 0px 12px #6a6a6a98;\n\tpadding: 2px;\n}\n.configs {\n\tdisplay: table;\n\tbackground: #e1e1e107;\n\twidth: -webkit-fill-available;\n}\n.configs .configs-body {\n\tdisplay: table-row-group;\n}\n.configs .configs-body config-element + config-element label {\n\tpadding-top: 4px;\n}\n.configs .configs-body config-element + config-element .config-wrapper {\n\tpadding-top: 4px;\n}\n.configs .configs-body config-element {\n\twidth: 100%;\n\tdisplay: table-row;\n\tline-height: 26px;\n}\n.configs .configs-body config-element label {\n\twhite-space: nowrap;\n\tcolor: #4e5969;\n\tdisplay: table-cell;\n\tpadding-right: 12px;\n\ttext-align: left;\n\tvertical-align: top;\n\tmargin-right: 12px;\n}\n.configs .configs-body config-element .config-wrapper {\n\tdisplay: table-cell;\n\tvertical-align: middle;\n\t/** check box 的样式 */\n}\n.configs .configs-body config-element .config-wrapper select {\n\toutline: none;\n\tborder: none;\n\tborder: 1px solid #e4e4e4;\n\tborder-radius: 4px;\n\tpadding: 2px 8px;\n\tborder: 1px solid #ffffff00;\n}\n.configs .configs-body config-element .config-wrapper select:focus {\n\tborder: 1px solid #0e8de290;\n\tbox-shadow: 0px 0px 4px #0e8de252;\n}\n.configs .configs-body config-element .config-wrapper select:focus:not([type='checkbox'], [type='radio']) {\n\tborder: 1px solid #0e8de290;\n\tbox-shadow: 0px 0px 4px #0e8de252;\n\tbackground-color: white !important;\n}\n.configs .configs-body config-element .config-wrapper select:hover {\n\tbackground-color: #ebeef4;\n}\n.configs .configs-body config-element .config-wrapper textarea {\n\tpadding: 2px 8px;\n\toutline: none;\n\tborder: none;\n\tborder: 1px solid #ffffff00;\n}\n.configs .configs-body config-element .config-wrapper textarea:focus {\n\tborder: 1px solid #0e8de290;\n\tbox-shadow: 0px 0px 4px #0e8de252;\n}\n.configs .configs-body config-element .config-wrapper textarea:focus:not([type='checkbox'], [type='radio']) {\n\tborder: 1px solid #0e8de290;\n\tbox-shadow: 0px 0px 4px #0e8de252;\n\tbackground-color: white !important;\n}\n.configs .configs-body config-element .config-wrapper textarea:hover {\n\tbackground-color: #ebeef4;\n}\n.configs .configs-body config-element .config-wrapper input:not([type='button']) {\n\toutline: none;\n\tpadding: 2px 8px;\n\tmargin: 0px;\n\tbackground-color: #eef2f7;\n\tborder-radius: 2px;\n\tcolor: black;\n\tborder: 1px solid #ffffff00;\n}\n.configs .configs-body config-element .config-wrapper input:not([type='button'])::placeholder {\n\tcolor: #bababa;\n}\n.configs .configs-body config-element .config-wrapper input:not([type='button']):focus {\n\tborder: 1px solid #0e8de290;\n\tbox-shadow: 0px 0px 4px #0e8de252;\n}\n.configs\n\t.configs-body\n\tconfig-element\n\t.config-wrapper\n\tinput:not([type='button']):focus:not([type='checkbox'], [type='radio']) {\n\tborder: 1px solid #0e8de290;\n\tbox-shadow: 0px 0px 4px #0e8de252;\n\tbackground-color: white !important;\n}\n.configs .configs-body config-element .config-wrapper input:not([type='button']):hover {\n\tbackground-color: #ebeef4;\n}\n.configs .configs-body config-element .config-wrapper input[type='range'] {\n\tpadding: 0px;\n}\n.configs .configs-body config-element .config-wrapper input[type='button'] {\n\tappearance: none;\n\t-moz-appearance: none;\n\t-webkit-appearance: none;\n\tborder-radius: 4px;\n\tbackground-color: white;\n\tborder: 1px solid #2c92ff;\n\tcolor: #409eff;\n\tcursor: pointer !important;\n\tmargin-bottom: 4px;\n}\n.configs .configs-body config-element .config-wrapper input[type='button']:active {\n\tbox-shadow: 0px 0px 8px #0e8de2a5;\n}\n.configs\n\t.configs-body\n\tconfig-element\n\t.config-wrapper\n\tinput[type='button']\n\t+ .configs\n\t.configs-body\n\tconfig-element\n\t.config-wrapper\n\tinput[type='button'] {\n\tmargin-left: 12px;\n}\n.configs .configs-body config-element .config-wrapper input[type='button']:hover {\n\tbackground-color: #7abbff24;\n}\n.configs .configs-body config-element .config-wrapper input[type='button'].danger:hover {\n\tbackground-color: #ffdede86;\n}\n.configs .configs-body config-element .config-wrapper input[type='button']:disabled {\n\tbackground-color: white;\n\tborder: 1px solid #c0c0c0;\n\tcolor: #aeaeae;\n\tcursor: not-allowed;\n}\n.configs .configs-body config-element .config-wrapper input[type='button'].danger {\n\tcolor: #f36669;\n\tborder-color: #dd5a5d;\n}\n.configs .configs-body config-element .config-wrapper input[type='button']:disabled:active {\n\tbox-shadow: none;\n}\n.configs .configs-body config-element .config-wrapper input[type='checkbox'] {\n\tappearance: none;\n\t-moz-appearance: none;\n\t-webkit-appearance: none;\n\twidth: fit-content;\n\tmin-width: 36px;\n\theight: 20px;\n\tborder-radius: 100px;\n\tdisplay: flex;\n\talign-items: center;\n\tpadding: 2px 4px;\n\ttransition: all 0.2s ease-in-out;\n\twidth: auto;\n\tbackground: gainsboro;\n}\n.configs .configs-body config-element .config-wrapper input[type='checkbox']:checked {\n\tbackground: #1890ff;\n}\n.configs .configs-body config-element .config-wrapper input[type='checkbox']:disabled {\n\tbackground-color: #f7f7f78b;\n}\n.configs .configs-body config-element .config-wrapper input[type='checkbox']:checked::before {\n\ttransform: translate(100%, 0px);\n}\n.configs .configs-body config-element .config-wrapper input[type='checkbox']::before {\n\tbackground-color: #fff;\n\tborder-radius: 9px;\n\tbox-shadow: 0 2px 4px #00230b33;\n\twidth: 14px;\n\theight: 14px;\n\tcontent: '';\n}\n.configs .configs-body config-element .config-wrapper input:not([type='checkbox'], [type='radio']),\n.configs .configs-body config-element .config-wrapper textarea,\n.configs .configs-body config-element .config-wrapper select {\n\twidth: -webkit-fill-available;\n\tfont-size: inherit;\n}\n.configs .configs-body config-element .config-wrapper input[type='checkbox'],\n.configs .configs-body config-element .config-wrapper input[type='radio'],\n.configs .configs-body config-element .config-wrapper input[type='range'] {\n\taccent-color: #0e8ee2;\n}\n.configs .configs-body config-element .config-wrapper > *:not(.tooltip) {\n\tbackground-color: #eef2f7;\n\tborder-radius: 2px;\n\tcolor: black;\n\tfloat: right;\n}\n.configs .configs-body config-element .config-wrapper > *:disabled {\n\tcursor: not-allowed;\n\tbackground-color: #f7f7f78b;\n}\n.message-container {\n\tmargin-bottom: 4px;\n\tposition: absolute;\n\tbottom: 100%;\n\tleft: 50%;\n\twidth: 100%;\n\ttransform: translate(-50%, 0px);\n\tmin-width: 300px;\n}\n.message-container message-element {\n\tdisplay: flex;\n\tborder-radius: 4px;\n\tpadding: 4px 12px;\n\tmargin-bottom: 4px;\n}\n.message-container message-element .message-content-container {\n\tmargin-right: 8px;\n\tflex: auto;\n}\n.message-container message-element .message-text {\n\tletter-spacing: 1px;\n\tfont-weight: bold;\n}\n.message-container message-element .message-closer {\n\twidth: 18px;\n\tmin-width: 18px;\n\tcursor: pointer;\n\tbackground-color: #ffffffb3;\n\tcolor: #a1a1a1;\n\tborder-radius: 100%;\n\ttext-align: center;\n\theight: 18px;\n\tvertical-align: middle;\n\tfont-size: 12px;\n}\n.message-container message-element.error {\n\tbackground-color: #ffe6e6;\n\tcolor: #c70208;\n\tborder: 1px solid #ff6b6ded;\n}\n.message-container message-element.info {\n\tbackground-color: #c9e7ff;\n\tcolor: #004d95;\n\tborder: 1px solid #1890ff69;\n}\n.message-container message-element.success {\n\tbackground-color: #e8ffe0;\n\tcolor: #3e8d0d;\n\tborder: 1px solid #6fd91d;\n}\n.message-container message-element.warn {\n\tbackground-color: #ffefc8;\n\tcolor: #9b7400;\n\tborder: 1px solid #ffc107;\n}\nmodal-element {\n\tposition: absolute;\n\ttop: 50%;\n\tleft: 50%;\n\tbackground-color: white;\n\tborder-radius: 4px;\n\tbox-shadow: 0px 0px 24px -12px black;\n\tborder: 1px solid #929292;\n\theight: fit-content;\n\ttransform: translate(-50%, -50%);\n\tpadding: 12px 18px 18px 18px;\n\tfont-family: Menlo, Monaco, Consolas, 'Courier New', monospace;\n\tz-index: 99999999999;\n\tline-height: 24px;\n}\nmodal-element .modal-profile {\n\tzoom: 0.9;\n\tcolor: #969696;\n\tuser-select: none;\n\tmargin-bottom: 4px;\n}\nmodal-element .modal-title {\n\tfont-size: 18px;\n\tfont-weight: bold;\n\tuser-select: none;\n}\nmodal-element .modal-body {\n\tmargin: 12px 0px;\n\toverflow: auto;\n}\nmodal-element .modal-footer {\n\tdisplay: flex;\n\twhite-space: nowrap;\n\tjustify-content: end;\n\talign-items: end;\n}\nmodal-element .modal-footer > * + * {\n\tmargin-left: 12px;\n}\nmodal-element .modal-input {\n\toutline: none;\n\tpadding: 2px 8px;\n\tmargin: 0px;\n\tbackground-color: #eef2f7;\n\tborder-radius: 2px;\n\tcolor: black;\n\tborder: 1px solid #ffffff00;\n\twidth: -webkit-fill-available;\n}\nmodal-element .modal-input::placeholder {\n\tcolor: #bababa;\n}\nmodal-element .modal-input:focus {\n\tborder: 1px solid #0e8de290;\n\tbox-shadow: 0px 0px 4px #0e8de252;\n}\nmodal-element .modal-input:focus:not([type='checkbox'], [type='radio']) {\n\tborder: 1px solid #0e8de290;\n\tbox-shadow: 0px 0px 4px #0e8de252;\n\tbackground-color: white !important;\n}\nmodal-element .modal-input:hover {\n\tbackground-color: #ebeef4;\n}\nmodal-element .modal-cancel-button {\n\tappearance: none;\n\t-moz-appearance: none;\n\t-webkit-appearance: none;\n\tborder-radius: 4px;\n\tborder: 1px solid #2c92ff;\n\tcolor: #409eff;\n\tcursor: pointer !important;\n\tmargin-bottom: 4px;\n\tcolor: gray;\n\tbackground-color: white;\n\tborder: 1px solid #dcdcdc;\n}\nmodal-element .modal-cancel-button:active {\n\tbox-shadow: 0px 0px 8px #0e8de2a5;\n}\nmodal-element .modal-cancel-button + modal-element .modal-cancel-button {\n\tmargin-left: 12px;\n}\nmodal-element .modal-cancel-button:hover {\n\tbackground-color: #7abbff24;\n}\nmodal-element .modal-cancel-button.danger:hover {\n\tbackground-color: #ffdede86;\n}\nmodal-element .modal-cancel-button:disabled {\n\tbackground-color: white;\n\tborder: 1px solid #c0c0c0;\n\tcolor: #aeaeae;\n\tcursor: not-allowed;\n}\nmodal-element .modal-cancel-button.danger {\n\tcolor: #f36669;\n\tborder-color: #dd5a5d;\n}\nmodal-element .modal-cancel-button:disabled:active {\n\tbox-shadow: none;\n}\nmodal-element .modal-confirm-button {\n\tappearance: none;\n\t-moz-appearance: none;\n\t-webkit-appearance: none;\n\tborder-radius: 4px;\n\tbackground-color: white;\n\tborder: 1px solid #2c92ff;\n\tcolor: #409eff;\n\tcursor: pointer !important;\n\tmargin-bottom: 4px;\n}\nmodal-element .modal-confirm-button:active {\n\tbox-shadow: 0px 0px 8px #0e8de2a5;\n}\nmodal-element .modal-confirm-button + modal-element .modal-confirm-button {\n\tmargin-left: 12px;\n}\nmodal-element .modal-confirm-button:hover {\n\tbackground-color: #7abbff24;\n}\nmodal-element .modal-confirm-button.danger:hover {\n\tbackground-color: #ffdede86;\n}\nmodal-element .modal-confirm-button:disabled {\n\tbackground-color: white;\n\tborder: 1px solid #c0c0c0;\n\tcolor: #aeaeae;\n\tcursor: not-allowed;\n}\nmodal-element .modal-confirm-button.danger {\n\tcolor: #f36669;\n\tborder-color: #dd5a5d;\n}\nmodal-element .modal-confirm-button:disabled:active {\n\tbox-shadow: none;\n}\nmodal-element.alert .modal-input,\nmodal-element.alert .modal-cancel-button {\n\tdisplay: none;\n}\nmodal-element.alert .modal-confirm-button {\n\tmargin: 0;\n}\nmodal-element.prompt .modal-input,\nmodal-element.prompt .modal-cancel-button,\nmodal-element.prompt .modal-confirm-button {\n\tdisplay: block;\n}\nmodal-element.confirm .modal-input {\n\tdisplay: none;\n}\n.modal-wrapper {\n\twidth: 100%;\n\theight: 100%;\n\tz-index: 9999;\n\tposition: fixed;\n\ttop: 0px;\n\tleft: 0px;\n\tz-index: 9999999;\n\tbackground-color: rgba(0, 0, 0, 0.265);\n\tcolor: #636363;\n\tfont: 14px Menlo, Monaco, Consolas, 'Courier New', monospace;\n}\n.pointer {\n\tcursor: pointer;\n}\n.separator {\n\tdisplay: flex;\n\talign-items: center;\n\ttext-align: center;\n\tpadding: 4px 0px 8px 0px;\n}\n.separator::before,\n.separator::after {\n\tcontent: '';\n\tflex: 1;\n\tborder-bottom: 1px solid #63636346;\n}\n.separator:not(:empty)::before {\n\tmargin-right: 0.25em;\n}\n.separator:not(:empty)::after {\n\tmargin-left: 0.25em;\n}\ncontainer-element.minimize .body,\ncontainer-element.minimize header-element .dropdown,\ncontainer-element.minimize .footer {\n\tdisplay: none;\n}\ncontainer-element.minimize header-element {\n\tpadding: 8px;\n\tborder-radius: 8px;\n\tbox-shadow: 0px 0px 24px -12px black;\n}\n.user-guide > li {\n\tpadding: 4px 0px;\n}\n.search-infos-num {\n\twidth: 26px;\n\tmargin: 2px;\n\theight: 20px;\n\tborder-radius: 4px;\n\tdisplay: inline-block;\n\tbackground-color: white;\n\ttext-align: center;\n\tcursor: pointer;\n\tborder: 1px solid #b6b6b6;\n}\n.search-infos-num.requested {\n\tborder: 1px solid #63b4ff;\n\tcolor: #63b4ff;\n}\n.search-infos-num.active {\n\tbackground-color: #127de1 !important;\n\tcolor: white;\n}\n.search-infos-num.error {\n\tborder: 1px solid #ff8789ed;\n\tbackground-color: #ff6b6ded;\n\tcolor: white;\n}\n.search-infos-num.finish {\n\tbackground-color: #63b4ff;\n\tborder: 1px solid #63b4ff;\n\tcolor: white;\n}\nsearch-infos-element {\n\tdisplay: block;\n\toverflow: auto;\n}\nsearch-infos-element .search-result {\n\tmargin-bottom: 12px;\n\tpadding-left: 12px;\n}\nsearch-infos-element .search-result .question {\n\tfont-weight: bold;\n\tmax-height: 200px;\n\toverflow: auto;\n}\nsearch-infos-element .search-result .answer {\n\tcolor: #7c7c7c;\n}\nsearch-infos-element .search-result .answer code {\n\tborder-bottom: 1px solid #dcdcdc;\n\tpadding: 2px 0px;\n\tborder-radius: 2px;\n\tmargin: 4px;\n\tline-height: 22px;\n}\nsearch-infos-element .search-result .answer code + code {\n\tmargin-left: 4px;\n}\nsearch-infos-element .search-result .search-result-answer-tag {\n\tpadding: 2px 6px;\n\tborder-radius: 2px;\n\tfont-size: 12px;\n\tcursor: pointer;\n\tmargin-right: 6px;\n}\nsearch-infos-element .search-result .search-result-answer-tag + .search-result-answer-tag {\n\tmargin-left: 4px;\n}\nsearch-infos-element .search-result .search-result-answer-tag.blue {\n\tbackground-color: #e6f7ff;\n\tborder: 1px solid #91d5ff;\n\tcolor: #1890ff;\n}\nsearch-infos-element .search-result .search-result-answer-tag.green {\n\tbackground-color: #f6ffed;\n\tborder: 1px solid #b7eb8f;\n\tcolor: #52c41a;\n}\nsearch-infos-element .search-result .search-result-answer-tag.gray {\n\tbackground-color: #fafafa;\n\tborder: 1px solid #d9d9d9;\n\tcolor: #595959;\n}\nsearch-infos-element .search-result .search-result-answer-tag.red {\n\tbackground-color: #fff1f0;\n\tborder: 1px solid #ffa39e;\n\tcolor: #ff4d4f;\n}\nsearch-infos-element .search-result .search-result-answer-tag.yellow {\n\tbackground-color: #fffbe6;\n\tborder: 1px solid #ffe58f;\n\tcolor: #faad14;\n}\nsearch-infos-element .search-result-question-type {\n\tbackground-color: #e6f7ff;\n\tborder: 1px solid #91d5ff;\n\tcolor: #1890ff;\n\tmargin-right: 8px;\n\tpadding: 0px 4px;\n\tborder-radius: 4px;\n}\nsearch-infos-element .error {\n\tcolor: #ff6b6ded;\n\tdisplay: inline-block;\n\tpadding-left: 12px;\n}\n.copy,\n.question-title-extra-btn {\n\tmargin-left: 4px;\n\tpadding: 2px 4px;\n\tborder-radius: 2px;\n\tbox-shadow: 0 0 4px #b1b1b1;\n\tcursor: pointer !important;\n\tfont-weight: normal;\n\tfont-size: 12px;\n}\n.work-result-question-container {\n\tposition: absolute;\n\twidth: 400px;\n\tleft: -100%;\n\ttop: 0px;\n\tbackground: white;\n\tborder: 1px solid #cbcbcb;\n\tborder-radius: 4px;\n\tbox-shadow: 0px 0px 12px #d1cfcf;\n\tpadding: 12px;\n}\n.work-result-question-container .close-search-result {\n\tfont-size: 12px;\n\tmargin-left: 8px;\n\ttext-decoration: underline;\n\tcolor: gray;\n\tcursor: pointer;\n}\n.work-result-list {\n\tmax-height: 400px;\n\toverflow: auto;\n\tmargin: 12px 0px;\n\tpadding: 6px;\n\tborder: 1px solid #e1e1e1;\n\tborder-radius: 4px;\n}\n.search-info-title {\n\tborder: 1px solid #e1e1e1;\n\tborder-radius: 4px;\n\tpadding: 8px 12px;\n\tmargin-bottom: 12px;\n\tline-height: 20px;\n\tmax-height: 400px;\n\toverflow: auto;\n}\n.search-info-details {\n\tmargin-left: 4px;\n}\n.console {\n\tmax-height: 300px;\n\tmax-width: 400px;\n\toverflow: auto;\n\tbackground-color: #292929;\n\tpadding: 12px 6px;\n\tcolor: #ececec;\n\tfont-size: 12px;\n}\n.console .item {\n\tpadding: 3px 0px;\n\tborder-radius: 2px;\n}\n.console .item .time {\n\tcolor: #757575;\n}\n.console .item .info {\n\tbackground-color: #2196f3a3;\n}\n.console .item .warn {\n\tbackground-color: #ffc107db;\n}\n.console .item .error {\n\tbackground-color: #f36c71cc;\n}\n.console .item .debug,\n.console .item .log {\n\tbackground-color: #9e9e9ec4;\n}\n.console *::selection {\n\tbackground-color: #ffffff6b;\n}\n.markdown {\n\tmax-width: 400px;\n\tmax-height: 50vh;\n\toverflow: auto;\n}\n.markdown code {\n\tpadding: 2px 4px;\n\tbackground-color: #f0f0f0;\n\tborder-radius: 6px;\n\tfont-size: 12px;\n}\n.markdown blockquote {\n\tpadding: 4px 4px 4px 12px;\n\tmargin: 0px;\n\tcolor: #b5b5b5;\n\tborder-left: #ababab 2px solid;\n}\n.markdown blockquote p {\n\tmargin: 0px;\n}\n.markdown h1,\n.markdown h2,\n.markdown h3,\n.markdown h4,\n.markdown h5,\n.markdown h6,\n.markdown p {\n\tmargin: 8px 0px;\n}\n.dropdown {\n\tposition: relative;\n\tdisplay: inline-block;\n}\n.dropdown.active .dropdown-trigger-element {\n\tcolor: #1890ff;\n}\n.dropdown-trigger-element {\n\tcursor: pointer;\n}\n.dropdown-content {\n\tdisplay: none;\n\tposition: absolute;\n\tbackground-color: #ffffff;\n\toverflow: auto;\n\tbox-shadow: 0px 8px 16px 0px #00000033;\n\tz-index: 1;\n\tborder-radius: 4px;\n\tpadding: 8px 12px;\n\tmin-width: 120px;\n}\n.dropdown-content.show {\n\tdisplay: block;\n}\n.dropdown-content {\n\tcursor: pointer;\n\tz-index: 999;\n}\n.dropdown-content .dropdown-option {\n\tpadding-left: 4px;\n\twhite-space: nowrap;\n}\n.dropdown-content .dropdown-option:hover {\n\tbackground-color: #f3f3f3;\n}\n.dropdown-content .dropdown-option.active {\n\tbackground-color: #1890ff1a;\n\tcolor: #1890ff;\n}\n.space {\n\tdisplay: inline-flex;\n}\n.config-details {\n\tanimation: fade-in 0.5s;\n}\n.config-details label {\n\tpadding-left: 12px;\n}\n.alert-info-wrapper {\n\tmargin-bottom: 8px;\n}\n.alert-info-wrapper .result-info {\n\tpadding: 12px;\n\ttext-align: center;\n\tborder-radius: 6px;\n}\n.alert-info-wrapper .unresolved {\n\tcolor: #a1a1a1;\n\tbackground-color: #f7f7f7;\n}\n.alert-info-wrapper .no-answer {\n\tcolor: #a1a1a1;\n\tbackground-color: #f7f7f7;\n}\n.alert-info-wrapper .error {\n\tcolor: #ff4d4f;\n\tbackground-color: #fff1f0;\n}\nmessage-element {\n\tanimation: show 0.5s;\n}\nscript-panel-element > div,\nscript-panel-link,\ncontainer-element,\nmodal-element {\n\tanimation: fade-in 0.3s;\n}\n@keyframes show {\n\t0% {\n\t\ttransform: translateY(20px);\n\t\topacity: 0;\n\t}\n\t100% {\n\t\ttransform: translateY(0);\n\t\topacity: 1;\n\t}\n}\n@keyframes fade-in {\n\t0% {\n\t\topacity: 0;\n\t}\n\t100% {\n\t\topacity: 1;\n\t}\n}\n@keyframes fade-out {\n\t0% {\n\t\topacity: 1;\n\t}\n\t100% {\n\t\topacity: 0;\n\t}\n}\n.checkbox-label {\n\tdisplay: inline-block !important;\n\tposition: relative;\n\tcursor: pointer;\n\tfont-size: 16px;\n\tcolor: #2c3e50;\n}\n/* 隐藏原始复选框 */\n.checkbox-input {\n\tposition: absolute;\n\topacity: 0;\n\twidth: 0;\n\theight: 0;\n}\n/* 自定义按钮样式 */\n.checkbox-label::after {\n\tcontent: '';\n\tdisplay: inline-block;\n\tborder-radius: 50%;\n\ttransition: all 0.1s ease;\n\tposition: relative;\n\tmargin-left: 4px;\n\tvertical-align: middle;\n}\n/* 向下箭头（未选中状态） */\n.checkbox-label::before {\n\tcontent: '🔽';\n\tposition: absolute;\n\twidth: 0;\n\theight: 0;\n\tright: 8px;\n\ttransition: all 0.1s ease;\n\tz-index: 2;\n}\n/* 向上箭头（选中状态） */\n.checked .checkbox-label::before {\n\tcontent: '🔼';\n}\n"
  },
  {
    "path": "packages/scripts/assets/less/style.less",
    "content": "/** 默认字体 */\n@base-font-family: Menlo, Monaco, Consolas, 'Courier New', monospace;\n/** 输入框默认边距 */\n@form-control-padding: 2px 8px;\n@form-control-bg: #eef2f7;\n\nul,\nol {\n\tline-height: 26px;\n\tpadding-left: 22px;\n\tmargin: 0px;\n}\n\na {\n\tcolor: #1890ff;\n}\n\nhr {\n\tborder-style: solid;\n\tborder-color: #63636346;\n\tborder-width: 0px;\n\tborder-bottom: 1px solid #63636346;\n\tmargin-block-start: 1em;\n\tmargin-block-end: 1em;\n}\n\n.base-style-active-form-control {\n\tborder: 1px solid #ffffff00;\n\t&:focus {\n\t\tborder: 1px solid #0e8de290;\n\t\tbox-shadow: 0px 0px 4px #0e8de252;\n\t}\n\n\t&:focus:not([type='checkbox'], [type='radio']) {\n\t\tborder: 1px solid #0e8de290;\n\t\tbox-shadow: 0px 0px 4px #0e8de252;\n\t\tbackground-color: white !important;\n\t}\n\n\t&:hover {\n\t\tbackground-color: #ebeef4;\n\t}\n}\n\n.base-style-input {\n\toutline: none;\n\tborder: 1px solid #ffffff00;\n\tpadding: @form-control-padding;\n\tmargin: 0px;\n\tbackground-color: @form-control-bg;\n\tborder-radius: 2px;\n\tcolor: black;\n\n\t&::placeholder {\n\t\tcolor: #bababa;\n\t}\n}\n\n.base-style-switch {\n\tappearance: none;\n\t-moz-appearance: none;\n\t-webkit-appearance: none;\n\n\twidth: fit-content;\n\tmin-width: 36px;\n\theight: 20px;\n\tborder-radius: 100px;\n\tdisplay: flex;\n\talign-items: center;\n\tpadding: 2px 4px;\n\ttransition: all 0.2s ease-in-out;\n\twidth: auto;\n\tbackground: gainsboro;\n\n\t&:checked {\n\t\tbackground: #1890ff;\n\t}\n\t&:disabled {\n\t\tbackground-color: #f7f7f78b;\n\t}\n\n\t&:checked::before {\n\t\ttransform: translate(100%, 0px);\n\t}\n\n\t&::before {\n\t\tbackground-color: #fff;\n\t\tborder-radius: 9px;\n\t\tbox-shadow: 0 2px 4px #00230b33;\n\t\twidth: 14px;\n\t\theight: 14px;\n\t\tcontent: '';\n\t}\n}\n\n.base-style-button {\n\tappearance: none;\n\t-moz-appearance: none;\n\t-webkit-appearance: none;\n\n\tborder-radius: 4px;\n\tbackground-color: white;\n\tborder: 1px solid #2c92ff;\n\tcolor: #409eff;\n\tcursor: pointer !important;\n\tmargin-bottom: 4px;\n\n\t&:active {\n\t\tbox-shadow: 0px 0px 8px #0e8de2a5;\n\t}\n\n\t& + & {\n\t\tmargin-left: 12px;\n\t}\n\n\t&:hover {\n\t\tbackground-color: #7abbff24;\n\t}\n\n\t&.danger:hover {\n\t\tbackground-color: #ffdede86;\n\t}\n\n\t&:disabled {\n\t\tbackground-color: white;\n\t\tborder: 1px solid #c0c0c0;\n\t\tcolor: #aeaeae;\n\t\tcursor: not-allowed;\n\t}\n\n\t&.danger {\n\t\tcolor: #f36669;\n\t\tborder-color: #dd5a5d;\n\t}\n\n\t&:disabled:active {\n\t\tbox-shadow: none;\n\t}\n}\n\n.base-style-button-secondary {\n\t.base-style-button();\n\n\tcolor: gray;\n\tbackground-color: white;\n\tborder: 1px solid #dcdcdc;\n}\n\ncontainer-element.hidden {\n\tdisplay: none;\n}\n\ncontainer-element.minimize {\n\tmin-width: unset;\n}\n\ncontainer-element {\n\tposition: fixed;\n\ttop: 10%;\n\tleft: 10%;\n\tz-index: 99999;\n\ttext-align: left;\n\tmin-width: 300px;\n\t-webkit-font-smoothing: antialiased;\n\t-moz-osx-font-smoothing: grayscale;\n\tcolor: #636363;\n\tbox-shadow: 0 0 24px -12px #3f3f3f;\n\tborder-radius: 8px;\n\tletter-spacing: 0.5px;\n\tborder: 1px solid #c1c1c1;\n}\n\n@base-height: 32px;\n@size: 18px;\n\nheader-element {\n\tdisplay: flex;\n\talign-items: center;\n\tbackground-color: white;\n\tborder-radius: 8px 8px 0px 0px;\n\tuser-select: none;\n\tpadding: 4px;\n\tpadding-bottom: 0px;\n\n\t.extra-menu-bar {\n\t\twidth: 100%;\n\t\tpadding: 4px;\n\t\tpadding-bottom: 0px;\n\t\tmargin-top: 4px;\n\t\tborder-top: 1px solid #e8e8e8;\n\t\t/** 默认隐藏，一直到需要激活的时候再更改 */\n\t\tdisplay: none;\n\n\t\t.script-panel-link {\n\t\t\t.base-style-button-secondary();\n\n\t\t\tpadding-bottom: 2px;\n\t\t\tmargin-bottom: 0px;\n\t\t}\n\n\t\t.script-panel-link.active {\n\t\t\tbackground-color: #1890ff1a;\n\t\t\tborder-color: #1890ff;\n\t\t\tcolor: #1890ff;\n\t\t}\n\n\t\t.script-panel-link + .script-panel-link {\n\t\t\tmargin-left: 4px;\n\t\t}\n\t}\n\n\t.profile {\n\t\tflex: 1;\n\t\tcursor: move;\n\t}\n\n\t.switch:hover,\n\t.dropdown:hover {\n\t\tbackground-color: #f3f3f3;\n\t}\n\n\t.close:hover {\n\t\tbackground-color: #ff000038;\n\t}\n\n\t.switch,\n\t.close {\n\t\tcursor: pointer;\n\t}\n\n\t.dropdown {\n\t\tline-height: 24px;\n\t\ttext-decoration: underline;\n\t}\n\n\t.switch,\n\t.close,\n\t.profile {\n\t\tdisplay: inline-flex;\n\t\talign-items: center;\n\t\tpadding: 0px 8px;\n\t}\n}\n\n.logo {\n\twidth: 18px;\n\theight: 18px;\n\tcursor: pointer;\n}\n\n.project-selector {\n\tdisplay: flex;\n\talign-items: center;\n\n\tselect {\n\t\tbackground: #ffffff00;\n\t\tborder-radius: 4px;\n\t\tborder: 1px solid #63636346;\n\t\tpadding: 4px;\n\t}\n}\n.project-selector.expand-all {\n\tdisplay: none;\n}\n\n.body {\n\toverflow: auto;\n\twidth: auto;\n\theight: 100%;\n}\n\nscript-panel-element {\n\tdisplay: block;\n\tbackground-color: white;\n\tborder-radius: 0px 0px 8px 8px;\n\tpadding: 0px 8px 12px 8px;\n\toverflow: auto;\n\n\t.script-panel-body {\n\t\tpadding: 0px 8px;\n\t}\n}\n\nscript-panel-element + script-panel-element {\n\tmargin-top: 12px;\n}\n\n.card + .card {\n\tmargin-top: 12px;\n}\n\n.card {\n\tbackground-color: white;\n\tborder-radius: 2px;\n\tpadding: 0px 8px;\n}\n\n.notes {\n\tbackground: #0099ff0e;\n\tborder-left: 4px solid #0099ff65;\n\twidth: -webkit-fill-available;\n\tmargin: 0px 8px;\n\tline-height: 26px;\n\tletter-spacing: 1px;\n}\n\n.secondary {\n\tfont-size: 12px;\n\tcolor: #8b8b8b;\n}\n\n.tooltip-container {\n\tz-index: 99999999999999;\n\tmargin: 12px 0px 0px 12px;\n\tpadding: 4px;\n\tcolor: black;\n\tbackground: #f0f0f0;\n\tbox-shadow: 0px 0px 4px #949494;\n\tposition: fixed;\n\twhite-space: normal;\n\tmax-width: 200px;\n\theight: auto;\n\tborder-radius: 2px;\n\tline-height: 18px;\n}\n\n.configs-container {\n\t&.lock {\n\t\tfilter: blur(1px);\n\t\tuser-select: none;\n\t\t-webkit-user-select: none;\n\t\t-moz-user-select: none;\n\t\t-ms-user-select: none;\n\t}\n\t.lock-wrapper {\n\t\tcursor: not-allowed !important;\n\t\tborder-radius: 4px;\n\t\tposition: absolute;\n\t\tleft: 0px;\n\t\tz-index: 1;\n\t\tdisplay: inline-flex;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t}\n\n\t.lock-message {\n\t\tbackground-color: #ffffff7d;\n\t\tborder-radius: 4px;\n\t\tbox-shadow: 0px 0px 12px #6a6a6a98;\n\t\tpadding: 2px;\n\t}\n}\n\n.configs {\n\tdisplay: table;\n\tbackground: #e1e1e107;\n\twidth: -webkit-fill-available;\n\n\t.configs-body {\n\t\tdisplay: table-row-group;\n\n\t\tconfig-element + config-element {\n\t\t\tlabel {\n\t\t\t\tpadding-top: 4px;\n\t\t\t}\n\t\t\t.config-wrapper {\n\t\t\t\tpadding-top: 4px;\n\t\t\t}\n\t\t}\n\n\t\tconfig-element {\n\t\t\twidth: 100%;\n\t\t\tdisplay: table-row;\n\t\t\tline-height: 26px;\n\n\t\t\tlabel {\n\t\t\t\twhite-space: nowrap;\n\t\t\t\tcolor: #4e5969;\n\t\t\t\tdisplay: table-cell;\n\t\t\t\tpadding-right: 12px;\n\t\t\t\ttext-align: left;\n\t\t\t\tvertical-align: top;\n\t\t\t\tmargin-right: 12px;\n\t\t\t}\n\n\t\t\t.config-wrapper {\n\t\t\t\tdisplay: table-cell;\n\t\t\t\tvertical-align: middle;\n\n\t\t\t\tselect {\n\t\t\t\t\toutline: none;\n\t\t\t\t\tborder: none;\n\t\t\t\t\tborder: 1px solid #e4e4e4;\n\t\t\t\t\tborder-radius: 4px;\n\t\t\t\t\tpadding: @form-control-padding;\n\n\t\t\t\t\t.base-style-active-form-control();\n\t\t\t\t}\n\n\t\t\t\ttextarea {\n\t\t\t\t\tpadding: @form-control-padding;\n\t\t\t\t\toutline: none;\n\t\t\t\t\tborder: none;\n\t\t\t\t\t.base-style-active-form-control();\n\t\t\t\t}\n\n\t\t\t\tinput:not([type='button']) {\n\t\t\t\t\t.base-style-input();\n\t\t\t\t\t.base-style-active-form-control();\n\t\t\t\t}\n\t\t\t\tinput[type='range'] {\n\t\t\t\t\t// 取消左右间隔，否则滚动条不能滚到头尾\n\t\t\t\t\tpadding: 0px;\n\t\t\t\t}\n\n\t\t\t\tinput[type='button'] {\n\t\t\t\t\t.base-style-button();\n\t\t\t\t}\n\n\t\t\t\t/** check box 的样式 */\n\t\t\t\tinput[type='checkbox'] {\n\t\t\t\t\t.base-style-switch();\n\t\t\t\t}\n\n\t\t\t\tinput:not([type='checkbox'], [type='radio']),\n\t\t\t\ttextarea,\n\t\t\t\tselect {\n\t\t\t\t\twidth: -webkit-fill-available;\n\t\t\t\t\tfont-size: inherit;\n\t\t\t\t}\n\n\t\t\t\tinput[type='checkbox'],\n\t\t\t\tinput[type='radio'],\n\t\t\t\tinput[type='range'] {\n\t\t\t\t\taccent-color: #0e8ee2;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t.config-wrapper > *:not(.tooltip) {\n\t\t\t\tbackground-color: @form-control-bg;\n\t\t\t\tborder-radius: 2px;\n\t\t\t\tcolor: black;\n\t\t\t\tfloat: right;\n\t\t\t}\n\t\t\t.config-wrapper > *:disabled {\n\t\t\t\tcursor: not-allowed;\n\t\t\t\tbackground-color: #f7f7f78b;\n\t\t\t}\n\t\t}\n\t}\n}\n\n// 消息推送\n\n.message-container {\n\tmargin-bottom: 4px;\n\tposition: absolute;\n\tbottom: 100%;\n\tleft: 50%;\n\twidth: 100%;\n\ttransform: translate(-50%, 0px);\n\tmin-width: 300px;\n\n\t// 提示内容\n\tmessage-element {\n\t\tdisplay: flex;\n\t\tborder-radius: 4px;\n\t\tpadding: 4px 12px;\n\t\tmargin-bottom: 4px;\n\n\t\t.message-content-container {\n\t\t\tmargin-right: 8px;\n\t\t\tflex: auto;\n\t\t}\n\n\t\t.message-text {\n\t\t\tletter-spacing: 1px;\n\t\t\tfont-weight: bold;\n\t\t}\n\n\t\t// 关闭按钮\n\t\t.message-closer {\n\t\t\twidth: 18px;\n\t\t\tmin-width: 18px;\n\t\t\tcursor: pointer;\n\t\t\tbackground-color: #ffffffb3;\n\t\t\tcolor: #a1a1a1;\n\t\t\tborder-radius: 100%;\n\t\t\ttext-align: center;\n\t\t\theight: 18px;\n\t\t\tvertical-align: middle;\n\t\t\tfont-size: 12px;\n\t\t}\n\n\t\t&.error {\n\t\t\tbackground-color: #ffe6e6;\n\t\t\tcolor: #c70208;\n\t\t\tborder: 1px solid #ff6b6ded;\n\t\t}\n\n\t\t&.info {\n\t\t\tbackground-color: #c9e7ff;\n\t\t\tcolor: #004d95;\n\t\t\tborder: 1px solid #1890ff69;\n\t\t}\n\n\t\t&.success {\n\t\t\tbackground-color: #e8ffe0;\n\t\t\tcolor: #3e8d0d;\n\t\t\tborder: 1px solid #6fd91d;\n\t\t}\n\n\t\t&.warn {\n\t\t\tbackground-color: #ffefc8;\n\t\t\tcolor: #9b7400;\n\t\t\tborder: 1px solid #ffc107;\n\t\t}\n\t}\n}\n\nmodal-element {\n\tposition: absolute;\n\ttop: 50%;\n\tleft: 50%;\n\tbackground-color: white;\n\tborder-radius: 4px;\n\tbox-shadow: 0px 0px 24px -12px black;\n\tborder: 1px solid #929292;\n\theight: fit-content;\n\ttransform: translate(-50%, -50%);\n\tpadding: 12px 18px 18px 18px;\n\tfont-family: @base-font-family;\n\tz-index: 99999999999;\n\tline-height: 24px;\n\n\t.modal-profile {\n\t\tzoom: 0.9;\n\t\tcolor: #969696;\n\t\tuser-select: none;\n\t\tmargin-bottom: 4px;\n\t}\n\n\t.modal-title {\n\t\tfont-size: 18px;\n\t\tfont-weight: bold;\n\t\tuser-select: none;\n\t}\n\n\t.modal-body {\n\t\tmargin: 12px 0px;\n\t\toverflow: auto;\n\t}\n\n\t.modal-footer {\n\t\tdisplay: flex;\n\t\twhite-space: nowrap;\n\t\tjustify-content: end;\n\t\talign-items: end;\n\n\t\t> * + * {\n\t\t\tmargin-left: 12px;\n\t\t}\n\t}\n\n\t.modal-input {\n\t\t.base-style-input();\n\t\t.base-style-active-form-control();\n\t\twidth: -webkit-fill-available;\n\t}\n\n\t.modal-cancel-button {\n\t\t.base-style-button();\n\n\t\tcolor: gray;\n\t\tbackground-color: white;\n\t\tborder: 1px solid #dcdcdc;\n\t}\n\t.modal-confirm-button {\n\t\t.base-style-button();\n\t}\n}\n\nmodal-element.alert {\n\t.modal-input,\n\t.modal-cancel-button {\n\t\tdisplay: none;\n\t}\n\t.modal-confirm-button {\n\t\tmargin: 0;\n\t}\n}\n\nmodal-element.prompt {\n\t.modal-input,\n\t.modal-cancel-button,\n\t.modal-confirm-button {\n\t\tdisplay: block;\n\t}\n}\n\nmodal-element.confirm {\n\t.modal-input {\n\t\tdisplay: none;\n\t}\n}\n\n.modal-wrapper {\n\twidth: 100%;\n\theight: 100%;\n\tz-index: 9999;\n\tposition: fixed;\n\ttop: 0px;\n\tleft: 0px;\n\tz-index: 9999999;\n\tbackground-color: rgba(0, 0, 0, 0.265);\n\tcolor: #636363;\n\tfont: 14px @base-font-family;\n}\n\n.pointer {\n\tcursor: pointer;\n}\n\n.separator {\n\tdisplay: flex;\n\talign-items: center;\n\ttext-align: center;\n\tpadding: 4px 0px 8px 0px;\n\n\t&::before,\n\t&::after {\n\t\tcontent: '';\n\t\tflex: 1;\n\t\tborder-bottom: 1px solid #63636346;\n\t}\n\n\t&:not(:empty)::before {\n\t\tmargin-right: 0.25em;\n\t}\n\n\t&:not(:empty)::after {\n\t\tmargin-left: 0.25em;\n\t}\n}\n\ncontainer-element.minimize {\n\t.body,\n\theader-element .dropdown,\n\t.footer {\n\t\tdisplay: none;\n\t}\n}\n\ncontainer-element.minimize header-element {\n\tpadding: 8px;\n\tborder-radius: 8px;\n\tbox-shadow: 0px 0px 24px -12px black;\n}\n\n.user-guide {\n\t& > li {\n\t\tpadding: 4px 0px;\n\t}\n}\n\n.search-infos-num {\n\twidth: 26px;\n\tmargin: 2px;\n\theight: 20px;\n\tborder-radius: 4px;\n\tdisplay: inline-block;\n\tbackground-color: white;\n\ttext-align: center;\n\tcursor: pointer;\n\tborder: 1px solid #b6b6b6;\n\n\t&.requested {\n\t\tborder: 1px solid #63b4ff;\n\t\tcolor: #63b4ff;\n\t}\n\n\t&.active {\n\t\tbackground-color: #127de1 !important;\n\t\tcolor: white;\n\t}\n\n\t&.error {\n\t\tborder: 1px solid #ff8789ed;\n\t\tbackground-color: #ff6b6ded;\n\t\tcolor: white;\n\t}\n\n\t&.finish {\n\t\tbackground-color: #63b4ff;\n\t\tborder: 1px solid #63b4ff;\n\t\tcolor: white;\n\t}\n}\n\nsearch-infos-element {\n\tdisplay: block;\n\toverflow: auto;\n\n\t.search-result {\n\t\tmargin-bottom: 12px;\n\t\tpadding-left: 12px;\n\n\t\t.question {\n\t\t\tfont-weight: bold;\n\t\t\tmax-height: 200px;\n\t\t\toverflow: auto;\n\t\t}\n\t\t.answer {\n\t\t\tcolor: #7c7c7c;\n\n\t\t\tcode {\n\t\t\t\t// background-color: #f3f3f3;\n\t\t\t\tborder-bottom: 1px solid #dcdcdc;\n\t\t\t\tpadding: 2px 0px;\n\t\t\t\tborder-radius: 2px;\n\t\t\t\tmargin: 4px;\n\t\t\t\tline-height: 22px;\n\t\t\t}\n\n\t\t\tcode + code {\n\t\t\t\tmargin-left: 4px;\n\t\t\t}\n\t\t}\n\n\t\t.search-result-answer-tag {\n\t\t\tpadding: 2px 6px;\n\t\t\tborder-radius: 2px;\n\t\t\tfont-size: 12px;\n\t\t\tcursor: pointer;\n\t\t\tmargin-right: 6px;\n\t\t}\n\t\t.search-result-answer-tag + .search-result-answer-tag {\n\t\t\tmargin-left: 4px;\n\t\t}\n\n\t\t.search-result-answer-tag.blue {\n\t\t\tbackground-color: #e6f7ff;\n\t\t\tborder: 1px solid #91d5ff;\n\t\t\tcolor: #1890ff;\n\t\t}\n\n\t\t.search-result-answer-tag.green {\n\t\t\tbackground-color: #f6ffed;\n\t\t\tborder: 1px solid #b7eb8f;\n\t\t\tcolor: #52c41a;\n\t\t}\n\n\t\t.search-result-answer-tag.gray {\n\t\t\tbackground-color: #fafafa;\n\t\t\tborder: 1px solid #d9d9d9;\n\t\t\tcolor: #595959;\n\t\t}\n\n\t\t.search-result-answer-tag.red {\n\t\t\tbackground-color: #fff1f0;\n\t\t\tborder: 1px solid #ffa39e;\n\t\t\tcolor: #ff4d4f;\n\t\t}\n\n\t\t.search-result-answer-tag.yellow {\n\t\t\tbackground-color: #fffbe6;\n\t\t\tborder: 1px solid #ffe58f;\n\t\t\tcolor: #faad14;\n\t\t}\n\t}\n\n\t.search-result-question-type {\n\t\tbackground-color: #e6f7ff;\n\t\tborder: 1px solid #91d5ff;\n\t\tcolor: #1890ff;\n\t\tmargin-right: 8px;\n\t\tpadding: 0px 4px;\n\t\tborder-radius: 4px;\n\t}\n\n\t.error {\n\t\tcolor: #ff6b6ded;\n\t\tdisplay: inline-block;\n\t\tpadding-left: 12px;\n\t}\n}\n\n.copy,\n.question-title-extra-btn {\n\tmargin-left: 4px;\n\tpadding: 2px 4px;\n\tborder-radius: 2px;\n\tbox-shadow: 0 0 4px #b1b1b1;\n\tcursor: pointer !important;\n\tfont-weight: normal;\n\tfont-size: 12px;\n}\n\n.work-result-question-container {\n\tposition: absolute;\n\twidth: 400px;\n\tleft: -100%;\n\ttop: 0px;\n\tbackground: white;\n\tborder: 1px solid #cbcbcb;\n\tborder-radius: 4px;\n\tbox-shadow: 0px 0px 12px #d1cfcf;\n\tpadding: 12px;\n\n\t.close-search-result {\n\t\tfont-size: 12px;\n\t\tmargin-left: 8px;\n\t\ttext-decoration: underline;\n\t\tcolor: gray;\n\t\tcursor: pointer;\n\t}\n}\n\n.work-result-list {\n\tmax-height: 400px;\n\toverflow: auto;\n\tmargin: 12px 0px;\n\tpadding: 6px;\n\tborder: 1px solid #e1e1e1;\n\tborder-radius: 4px;\n}\n\n.search-info-title {\n\tborder: 1px solid #e1e1e1;\n\tborder-radius: 4px;\n\tpadding: 8px 12px;\n\tmargin-bottom: 12px;\n\tline-height: 20px;\n\tmax-height: 400px;\n\toverflow: auto;\n}\n\n.search-info-details {\n\tmargin-left: 4px;\n}\n\n.console {\n\tmax-height: 300px;\n\tmax-width: 400px;\n\toverflow: auto;\n\tbackground-color: #292929;\n\tpadding: 12px 6px;\n\tcolor: #ececec;\n\tfont-size: 12px;\n\n\t.item {\n\t\tpadding: 3px 0px;\n\t\tborder-radius: 2px;\n\n\t\t.time {\n\t\t\tcolor: #757575;\n\t\t}\n\n\t\t.info {\n\t\t\tbackground-color: #2196f3a3;\n\t\t}\n\n\t\t.warn {\n\t\t\tbackground-color: #ffc107db;\n\t\t}\n\n\t\t.error {\n\t\t\tbackground-color: #f36c71cc;\n\t\t}\n\n\t\t.debug,\n\t\t.log {\n\t\t\tbackground-color: #9e9e9ec4;\n\t\t}\n\t}\n\n\t// 文字选中样式\n\t& *::selection {\n\t\tbackground-color: #ffffff6b;\n\t}\n}\n\n.markdown {\n\tmax-width: 400px;\n\tmax-height: 50vh;\n\toverflow: auto;\n\n\tcode {\n\t\tpadding: 2px 4px;\n\t\tbackground-color: #f0f0f0;\n\t\tborder-radius: 6px;\n\t\tfont-size: 12px;\n\t}\n\n\tblockquote {\n\t\tpadding: 4px 4px 4px 12px;\n\t\tmargin: 0px;\n\t\tcolor: #b5b5b5;\n\t\tborder-left: #ababab 2px solid;\n\n\t\tp {\n\t\t\tmargin: 0px;\n\t\t}\n\t}\n\n\th1,\n\th2,\n\th3,\n\th4,\n\th5,\n\th6,\n\tp {\n\t\tmargin: 8px 0px;\n\t}\n}\n\n.dropdown {\n\tposition: relative;\n\tdisplay: inline-block;\n\n\t&.active {\n\t\t.dropdown-trigger-element {\n\t\t\tcolor: #1890ff;\n\t\t}\n\t}\n}\n\n.dropdown-trigger-element {\n\tcursor: pointer;\n}\n\n.dropdown-content {\n\tdisplay: none;\n\tposition: absolute;\n\tbackground-color: #ffffff;\n\toverflow: auto;\n\tbox-shadow: 0px 8px 16px 0px #00000033;\n\tz-index: 1;\n\tborder-radius: 4px;\n\tpadding: 8px 12px;\n\tmin-width: 120px;\n\n\t&.show {\n\t\tdisplay: block;\n\t}\n}\n\n.dropdown-content {\n\tcursor: pointer;\n\tz-index: 999;\n\n\t.dropdown-option {\n\t\tpadding-left: 4px;\n\t\twhite-space: nowrap;\n\t}\n\n\t.dropdown-option:hover {\n\t\tbackground-color: #f3f3f3;\n\t}\n\n\t.dropdown-option.active {\n\t\tbackground-color: #1890ff1a;\n\t\tcolor: #1890ff;\n\t}\n}\n\n.space {\n\tdisplay: inline-flex;\n}\n\n.config-details {\n\tlabel {\n\t\tpadding-left: 12px;\n\t}\n\n\tanimation: fade-in 0.5s;\n}\n\n.alert-info-wrapper {\n\tmargin-bottom: 8px;\n\n\t.result-info {\n\t\tpadding: 12px;\n\t\ttext-align: center;\n\t\tborder-radius: 6px;\n\t}\n\n\t.unresolved {\n\t\tcolor: #a1a1a1;\n\t\tbackground-color: #f7f7f7;\n\t}\n\n\t.no-answer {\n\t\tcolor: #a1a1a1;\n\t\tbackground-color: #f7f7f7;\n\t}\n\n\t.error {\n\t\tcolor: #ff4d4f;\n\t\tbackground-color: #fff1f0;\n\t}\n}\n\nmessage-element {\n\tanimation: show 0.5s;\n}\n\nscript-panel-element > div,\nscript-panel-link,\ncontainer-element,\nmodal-element {\n\tanimation: fade-in 0.3s;\n}\n\n@keyframes show {\n\t0% {\n\t\ttransform: translateY(20px);\n\t\topacity: 0;\n\t}\n\t100% {\n\t\ttransform: translateY(0);\n\t\topacity: 1;\n\t}\n}\n\n@keyframes fade-in {\n\t0% {\n\t\topacity: 0;\n\t}\n\t100% {\n\t\topacity: 1;\n\t}\n}\n\n@keyframes fade-out {\n\t0% {\n\t\topacity: 1;\n\t}\n\t100% {\n\t\topacity: 0;\n\t}\n}\n\n.checkbox-label {\n\tdisplay: inline-block !important;\n\tposition: relative;\n\tcursor: pointer;\n\tfont-size: 16px;\n\tcolor: #2c3e50;\n}\n\n/* 隐藏原始复选框 */\n.checkbox-input {\n\tposition: absolute;\n\topacity: 0;\n\twidth: 0;\n\theight: 0;\n}\n\n/* 自定义按钮样式 */\n.checkbox-label::after {\n\tcontent: '';\n\tdisplay: inline-block;\n\tborder-radius: 50%;\n\ttransition: all 0.1s ease;\n\tposition: relative;\n\tmargin-left: 4px;\n\tvertical-align: middle;\n}\n\n/* 向下箭头（未选中状态） */\n.checkbox-label::before {\n\tcontent: '🔽';\n\tposition: absolute;\n\twidth: 0;\n\theight: 0;\n\tright: 8px;\n\t// border-left: 10px solid transparent;\n\t// border-right: 10px solid transparent;\n\t// border-top: 15px solid rgb(0, 0, 0);\n\ttransition: all 0.1s ease;\n\tz-index: 2;\n}\n\n/* 向上箭头（选中状态） */\n.checked .checkbox-label::before {\n\tcontent: '🔼';\n}\n"
  },
  {
    "path": "packages/scripts/entry.common.js",
    "content": "/* eslint-disable no-undef */\n/// <reference path=\"./global.d.ts\" />\n\n// 环境检测\nif (\n\t[\n\t\t'GM_getTab',\n\t\t'GM_saveTab',\n\t\t'GM_setValue',\n\t\t'GM_getValue',\n\t\t'unsafeWindow',\n\t\t'GM_listValues',\n\t\t'GM_deleteValue',\n\t\t'GM_notification',\n\t\t'GM_xmlhttpRequest',\n\t\t'GM_getResourceText',\n\t\t'GM_addValueChangeListener',\n\t\t'GM_removeValueChangeListener'\n\t].some((api) => typeof Reflect.get(globalThis, api) === 'undefined')\n) {\n\tconst open = confirm(\n\t\t`OCS网课脚本不支持当前的脚本管理器（${GM_info.scriptHandler}）。` +\n\t\t\t'请前往 https://docs.ocsjs.com/docs/script 下载指定的脚本管理器，例如 “Scriptcat 脚本猫” 或者 “Tampermonkey 油猴”'\n\t);\n\n\tif (open) {\n\t\twindow.location.href = 'https://docs.ocsjs.com/docs/script';\n\t}\n\treturn;\n}\n\nconst { start, definedProjects, CommonProject, RenderScript } = OCS;\n\nconst infos = GM_info;\n\n(function () {\n\t'use strict';\n\n\tconst projects = definedProjects();\n\n\t// 运行脚本\n\tstart({\n\t\tprojects: projects,\n\t\trenderConfig: {\n\t\t\trenderScript: RenderScript,\n\t\t\tstyles: [STYLE],\n\t\t\tdefaultPanelName: CommonProject.scripts.guide.namespace,\n\t\t\ttitle: `OCS-全域名通用版-${infos.script.version}`\n\t\t},\n\t\tupdatePage:\n\t\t\tGM_info.scriptHandler === 'Tampermonkey'\n\t\t\t\t? 'https://greasyfork.org/zh-CN/scripts/481438'\n\t\t\t\t: 'https://scriptcat.org/zh-CN/script-show-page/1398'\n\t});\n})();\n"
  },
  {
    "path": "packages/scripts/entry.dev.js",
    "content": "/* eslint-disable no-undef */\n/// <reference path=\"./global.d.ts\" />\n\n// 环境检测\nif (\n\t[\n\t\t'GM_getTab',\n\t\t'GM_saveTab',\n\t\t'GM_setValue',\n\t\t'GM_getValue',\n\t\t'unsafeWindow',\n\t\t'GM_listValues',\n\t\t'GM_deleteValue',\n\t\t'GM_notification',\n\t\t'GM_xmlhttpRequest',\n\t\t'GM_getResourceText',\n\t\t'GM_addValueChangeListener',\n\t\t'GM_removeValueChangeListener'\n\t].some((api) => typeof Reflect.get(globalThis, api) === 'undefined')\n) {\n\tconst open = confirm(\n\t\t`OCS网课脚本不支持当前的脚本管理器（${GM_info.scriptHandler}）。` +\n\t\t\t'请前往 https://docs.ocsjs.com/docs/script 下载指定的脚本管理器，例如 “Scriptcat 脚本猫” 或者 “Tampermonkey 油猴”'\n\t);\n\n\tif (open) {\n\t\twindow.location.href = 'https://docs.ocsjs.com/docs/script';\n\t}\n\treturn;\n}\n\nconst { start, definedProjects, CommonProject, RenderScript } = OCS;\n\nconst infos = GM_info;\n\n(function () {\n\t'use strict';\n\n\tconst projects = definedProjects();\n\n\t// 运行脚本\n\tstart({\n\t\tprojects: projects,\n\t\trenderConfig: {\n\t\t\trenderScript: RenderScript,\n\t\t\tstyles: [GM_getResourceText('STYLE')],\n\t\t\tdefaultPanelName: CommonProject.scripts.guide.namespace,\n\t\t\ttitle: `OCS DEV-${infos.script.version}`\n\t\t},\n\t\tupdatePage: 'https://docs.ocsjs.com/docs/update'\n\t});\n})();\n"
  },
  {
    "path": "packages/scripts/entry.js",
    "content": "/* eslint-disable no-undef */\n/// <reference path=\"./global.d.ts\" />\n\n// 环境检测\nif (\n\t[\n\t\t'GM_getTab',\n\t\t'GM_saveTab',\n\t\t'GM_setValue',\n\t\t'GM_getValue',\n\t\t'unsafeWindow',\n\t\t'GM_listValues',\n\t\t'GM_deleteValue',\n\t\t'GM_notification',\n\t\t'GM_xmlhttpRequest',\n\t\t'GM_getResourceText',\n\t\t'GM_addValueChangeListener',\n\t\t'GM_removeValueChangeListener'\n\t].some((api) => typeof Reflect.get(globalThis, api) === 'undefined')\n) {\n\tconst open = confirm(\n\t\t`OCS网课脚本不支持当前的脚本管理器（${GM_info.scriptHandler}）。` +\n\t\t\t'请前往 https://docs.ocsjs.com/docs/script 下载指定的脚本管理器，例如 “Scriptcat 脚本猫” 或者 “Tampermonkey 油猴”'\n\t);\n\n\tif (open) {\n\t\twindow.location.href = 'https://docs.ocsjs.com/docs/script';\n\t}\n\treturn;\n}\n\nconst { start, definedProjects, CommonProject, RenderScript } = OCS;\n\nconst infos = GM_info;\n\n(function () {\n\t'use strict';\n\n\tconst projects = definedProjects();\n\n\t// 运行脚本\n\tstart({\n\t\tprojects: projects,\n\t\trenderConfig: {\n\t\t\trenderScript: RenderScript,\n\t\t\tstyles: [STYLE],\n\t\t\tdefaultPanelName: CommonProject.scripts.guide.namespace,\n\t\t\ttitle: `OCS-${infos.script.version}`\n\t\t},\n\t\tupdatePage: 'https://docs.ocsjs.com/docs/update'\n\t});\n})();\n"
  },
  {
    "path": "packages/scripts/global.d.ts",
    "content": "import * as Core from './src/index';\n\ndeclare global {\n\texport declare const OCS: typeof Core;\n\texport declare const STYLE: string;\n}\n\ndeclare module 'dom-to-image-more' {\n\timport domToImage = require('dom-to-image');\n\texport = domToImage;\n}\n"
  },
  {
    "path": "packages/scripts/global.js",
    "content": "/// <reference path=\"./global.d.ts\" />\n/// <reference path=\"../../node_modules/@types/tampermonkey/index.d.ts\" />\n"
  },
  {
    "path": "packages/scripts/package.json",
    "content": "{\n\t\"name\": \"@ocsjs/scripts\",\n\t\"version\": \"4.0.1\",\n\t\"description\": \"core package of ocs\",\n\t\"main\": \"./lib/index.js\",\n\t\"types\": \"./lib/index.d.ts\",\n\t\"files\": [\n\t\t\"lib\"\n\t],\n\t\"repository\": {\n\t\t\"type\": \"git\",\n\t\t\"url\": \"git+https://github.com/ocsjs/ocsjs.git\"\n\t},\n\t\"keywords\": [\n\t\t\"ocs\",\n\t\t\"script\",\n\t\t\"playwright\",\n\t\t\"puppeteer\",\n\t\t\"electron\",\n\t\t\"vue\",\n\t\t\"ant-design-vue\",\n\t\t\"typescript\"\n\t],\n\t\"author\": \"enncy\",\n\t\"license\": \"MIT\",\n\t\"bugs\": {\n\t\t\"url\": \"https://github.com/ocsjs/ocsjs/issues\"\n\t},\n\t\"homepage\": \"https://github.com/ocsjs/ocsjs#readme\",\n\t\"devDependencies\": {\n\t\t\"@types/marked\": \"^4.0.8\",\n\t\t\"@types/md5\": \"^2.3.2\",\n\t\t\"@types/semver\": \"^7.5.0\",\n\t\t\"@types/tampermonkey\": \"^4.0.5\",\n\t\t\"dotenv\": \"^16.0.3\",\n\t\t\"rollup-plugin-visualizer\": \"^5.9.0\",\n\t\t\"vite\": \"^2.9.15\",\n\t\t\"vite-plugin-banner\": \"^0.6.1\"\n\t},\n\t\"dependencies\": {\n\t\t\"@ocsjs/core\": \"workspace:*\",\n\t\t\"@rollup/plugin-commonjs\": \"^24.0.0\",\n\t\t\"browser-env\": \"^3.3.0\",\n\t\t\"easy-us\": \"^0.0.60\",\n\t\t\"lodash\": \"^4.17.21\",\n\t\t\"markdown-it\": \"^13.0.1\",\n\t\t\"marked\": \"^4.2.12\",\n\t\t\"md5\": \"^2.3.0\",\n\t\t\"semver\": \"^7.3.5\",\n\t\t\"typr.js\": \"^1.0.0\"\n\t}\n}\n"
  },
  {
    "path": "packages/scripts/src/elements/search.infos.ts",
    "content": "import { SimplifyWorkResult, splitAnswer, $, QuestionTypes } from '@ocsjs/core';\nimport { $ui, h } from 'easy-us';\nimport { createQuestionTitleExtra } from '../utils';\n\n/**\n * 判断是否有图片链接，如果有则使用 <img> 标签包裹，但如果已经被 <img> 包裹则不处理\n */\nconst transformImgLinkOfQuestion = (question: string) => {\n\t// 防止题目中包含 img 标签元素，所以先统一吧 img 标签替换成链接\n\tconst dom = new DOMParser().parseFromString(question, 'text/html');\n\tfor (const img of Array.from(dom.querySelectorAll('img'))) {\n\t\timg.replaceWith(img.src);\n\t}\n\t// 最后将所有图片链接替换成 img 标签\n\treturn dom.documentElement.innerHTML.replace(/https?:\\/\\/.+?\\.(png|jpg|jpeg|gif)/g, (img) => {\n\t\treturn `<img src=\"${img}\" />`;\n\t});\n};\n\n/**\n * 搜索结果元素\n */\nexport class SearchInfosElement extends HTMLElement {\n\t/** 搜索结果 [题目，答案] */\n\tinfos: SimplifyWorkResult['searchInfos'] = [];\n\t/** 当前的题目 */\n\tquestion: string = '';\n\n\ttype: QuestionTypes;\n\n\tconnectedCallback() {\n\t\tconst question = transformImgLinkOfQuestion(this.question || '无');\n\n\t\tconst type_text = {\n\t\t\tsingle: '单选题',\n\t\t\tmultiple: '多选题',\n\t\t\tjudgement: '判断题',\n\t\t\tcompletion: '填空题'\n\t\t};\n\t\tconst type_label = this.type ? Reflect.get(type_text, this.type) : '';\n\n\t\tthis.append(\n\t\t\th(\n\t\t\t\t'div',\n\t\t\t\t[\n\t\t\t\t\t...(type_label ? [h('span', { className: 'search-result-question-type' }, type_label)] : []),\n\t\t\t\t\th('span', { innerHTML: question }),\n\t\t\t\t\tcreateQuestionTitleExtra(this.question)\n\t\t\t\t],\n\t\t\t\t(div) => {\n\t\t\t\t\tdiv.className = 'search-info-title';\n\t\t\t\t}\n\t\t\t)\n\t\t);\n\n\t\tthis.append(\n\t\t\t...this.infos.map((info) => {\n\t\t\t\treturn h('details', { open: true, className: 'search-info-details' }, [\n\t\t\t\t\th('summary', [h('a', { href: info.homepage, innerText: info.name, target: '_blank' })]),\n\n\t\t\t\t\t...(info.error\n\t\t\t\t\t\t? /** 显示错误信息 */\n\t\t\t\t\t\t  [h('span', { className: 'error' }, [info.error || '网络错误或者未知错误'])]\n\t\t\t\t\t\t: /** 显示结果列表 */\n\t\t\t\t\t\t  []\n\t\t\t\t\t).concat([\n\t\t\t\t\t\t...info.results.map((ans) => {\n\t\t\t\t\t\t\tconst title = transformImgLinkOfQuestion(ans[0] || this.question || '无');\n\t\t\t\t\t\t\tconst answer = transformImgLinkOfQuestion(ans[1] || '无');\n\t\t\t\t\t\t\tconst extra_data = JSON.parse(JSON.stringify(ans[2] || {}));\n\n\t\t\t\t\t\t\tif (extra_data.ai) {\n\t\t\t\t\t\t\t\textra_data.tags = extra_data.tags || [];\n\t\t\t\t\t\t\t\textra_data.tags.push({\n\t\t\t\t\t\t\t\t\ttext: 'AI',\n\t\t\t\t\t\t\t\t\ttitle: '此答案由 AI 生成，仅供参考',\n\t\t\t\t\t\t\t\t\tcolor: 'blue'\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif (extra_data.cache) {\n\t\t\t\t\t\t\t\textra_data.tags = extra_data.tags || [];\n\t\t\t\t\t\t\t\textra_data.tags.push({\n\t\t\t\t\t\t\t\t\ttext: '题库缓存',\n\t\t\t\t\t\t\t\t\ttitle:\n\t\t\t\t\t\t\t\t\t\t'此答案来自本地缓存，由在线题库搜索后保存在本地。\\n- 清空缓存：请前往通用-拓展应用-题库缓存\\n- 关闭缓存：请前往通用-全局设置-题库缓存',\n\t\t\t\t\t\t\t\t\tcolor: 'gray'\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\treturn h('div', { className: 'search-result' }, [\n\t\t\t\t\t\t\t\t/** 题目 */\n\t\t\t\t\t\t\t\th('div', { className: 'question' }, [h('span', { innerHTML: title })]),\n\t\t\t\t\t\t\t\t/** 答案 */\n\t\t\t\t\t\t\t\th('div', { className: 'answer' }, [\n\t\t\t\t\t\t\t\t\th('span', '答案：'),\n\t\t\t\t\t\t\t\t\t...(extra_data.tags\n\t\t\t\t\t\t\t\t\t\t? extra_data.tags.map((tag: { text: string; title: string; color: string }) =>\n\t\t\t\t\t\t\t\t\t\t\t\t$ui.tooltip(\n\t\t\t\t\t\t\t\t\t\t\t\t\th('span', {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tclassName: 'search-result-answer-tag ' + tag.color,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tinnerHTML: tag.text,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttitle: tag.title,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tdataset: { title: tag.title }\n\t\t\t\t\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\t  )\n\t\t\t\t\t\t\t\t\t\t: []),\n\t\t\t\t\t\t\t\t\t...splitAnswer(answer).map((a) => h('code', { innerHTML: a }))\n\t\t\t\t\t\t\t\t])\n\t\t\t\t\t\t\t]);\n\t\t\t\t\t\t})\n\t\t\t\t\t])\n\t\t\t\t]);\n\t\t\t})\n\t\t);\n\n\t\t$.onresize(this, (sr) => {\n\t\t\tsr.style.maxHeight = window.innerHeight / 2 + 'px';\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "packages/scripts/src/index.ts",
    "content": "import { Project } from 'easy-us';\nimport { CommonProject } from './projects/common';\nimport { ZHSProject } from './projects/zhs';\nimport { CXProject } from './projects/cx';\nimport { BackgroundProject } from './projects/background';\nimport { IcveMoocProject } from './projects/icve';\nimport { ZJYProject } from './projects/zjy';\nimport { ICourseProject } from './projects/icourse';\nimport { YKTProject } from './projects/yuketang';\n\n/** 导出所有的 OCS 核心模块 */\nexport * from '@ocsjs/core';\n/** 导出启动函数，以及全局对象 */\nexport { start, $elements, $store } from 'easy-us';\n/** 导出本包的核心脚本工程，开发者调试的时候使用 BackgroundProject 中的注入脚本，访问脚本 window 上下文 */\nexport { BackgroundProject } from './projects/background';\nexport { CommonProject } from './projects/common';\nexport { ZHSProject } from './projects/zhs';\nexport { CXProject } from './projects/cx';\nexport { ZJYProject } from './projects/zjy';\nexport { IcveMoocProject } from './projects/icve';\nexport { ICourseProject } from './projects/icourse';\nexport { YKTProject } from './projects/yuketang';\nexport { RenderScript } from './render';\n\nexport function definedProjects(): Project[] {\n\treturn [\n\t\tZHSProject,\n\t\tCXProject,\n\t\tIcveMoocProject,\n\t\tZJYProject,\n\t\tICourseProject,\n\t\tYKTProject,\n\t\tCommonProject,\n\t\tBackgroundProject\n\t];\n}\n"
  },
  {
    "path": "packages/scripts/src/projects/background.ts",
    "content": "import { RemotePlaywright, request } from '@ocsjs/core';\nimport {\n\t$ui,\n\t$gm,\n\t$message,\n\t$modal,\n\t$store,\n\tProject,\n\tScript,\n\tStoreListenerType,\n\th,\n\t$,\n\tMessageElement,\n\t$menu\n} from 'easy-us';\nimport semver_gt from 'semver/functions/gt';\nimport semver_valid from 'semver/functions/valid';\nimport { CommonProject } from './common';\nimport { CXProject, definedProjects, ICourseProject, IcveMoocProject, YKTProject, ZHSProject, ZJYProject } from '..';\nimport { RenderScript } from '../render';\nimport { SearchInfosElement } from '../elements/search.infos';\nimport { $render } from '../utils/render';\n\nconst state = {\n\tconsole: {\n\t\tlistenerIds: {\n\t\t\tlogs: 0 as StoreListenerType\n\t\t}\n\t},\n\tapp: {\n\t\tlistenerIds: {\n\t\t\tsync: 0 as StoreListenerType,\n\t\t\tconnected: 0 as StoreListenerType,\n\t\t\tcloseSync: 0 as StoreListenerType\n\t\t}\n\t}\n};\n\nexport type LogType = 'log' | 'info' | 'debug' | 'warn' | 'error';\n\ntype RequestList = {\n\tid: string;\n\turl: string;\n\tmethod: string;\n\ttype: string;\n\tdata: any;\n\theaders: any;\n\tresponse?: string;\n\terror?: string;\n\ttime: number;\n}[];\n\n/** 后台进程，处理与PC软件端的通讯，以及其他后台操作 */\nexport const BackgroundProject = Project.create({\n\tname: '后台',\n\tdomains: [],\n\tscripts: {\n\t\telementRegister: new Script({\n\t\t\tname: '🔗 元素注册',\n\t\t\thideInPanel: true,\n\t\t\tmatches: [['所有页面', /.*/]],\n\t\t\tonstart() {\n\t\t\t\t// 注册自定义元素\n\t\t\t\t$.loadCustomElements([SearchInfosElement]);\n\t\t\t}\n\t\t}),\n\t\tconsole: new Script({\n\t\t\tname: '📄 日志输出',\n\t\t\tmatches: [['所有', /.*/]],\n\t\t\tnamespace: 'render.console',\n\t\t\tconfigs: {\n\t\t\t\tlogs: {\n\t\t\t\t\tdefaultValue: [] as { type: LogType; content: string; time: number; stack: string }[]\n\t\t\t\t}\n\t\t\t},\n\t\t\tonrender({ panel }) {\n\t\t\t\tconst getTypeDesc = (type: LogType) =>\n\t\t\t\t\ttype === 'info'\n\t\t\t\t\t\t? '信息'\n\t\t\t\t\t\t: type === 'error'\n\t\t\t\t\t\t? '错误'\n\t\t\t\t\t\t: type === 'warn'\n\t\t\t\t\t\t? '警告'\n\t\t\t\t\t\t: type === 'debug'\n\t\t\t\t\t\t? '调试'\n\t\t\t\t\t\t: '日志';\n\n\t\t\t\tconst createLog = (log: { type: LogType; content: string; time: number; stack: string }) => {\n\t\t\t\t\tconst date = new Date(log.time);\n\t\t\t\t\tconst item = h(\n\t\t\t\t\t\t'div',\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttitle: '双击复制日志信息',\n\t\t\t\t\t\t\tclassName: 'item'\n\t\t\t\t\t\t},\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\th(\n\t\t\t\t\t\t\t\t'span',\n\t\t\t\t\t\t\t\t{ className: 'time' },\n\t\t\t\t\t\t\t\t`${date.getHours().toFixed(0).padStart(2, '0')}:${date.getMinutes().toFixed(0).padStart(2, '0')} `\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\th('span', { className: log.type }, `[${getTypeDesc(log.type)}]`),\n\t\t\t\t\t\t\th('span', ':' + log.content)\n\t\t\t\t\t\t]\n\t\t\t\t\t);\n\n\t\t\t\t\titem.addEventListener('dblclick', () => {\n\t\t\t\t\t\tnavigator.clipboard.writeText(\n\t\t\t\t\t\t\tObject.keys(log)\n\t\t\t\t\t\t\t\t.map((k) => `${k}: ${(log as any)[k]}`)\n\t\t\t\t\t\t\t\t.join('\\n')\n\t\t\t\t\t\t);\n\t\t\t\t\t});\n\n\t\t\t\t\treturn item;\n\t\t\t\t};\n\n\t\t\t\tconst showLogs = () => {\n\t\t\t\t\tconst div = h('div', { className: 'card console' });\n\n\t\t\t\t\tconst logs = this.cfg.logs.map((log) => createLog(log));\n\t\t\t\t\tif (logs.length) {\n\t\t\t\t\t\tdiv.replaceChildren(...logs);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdiv.replaceChildren(\n\t\t\t\t\t\t\th('div', '暂无任何日志', (div) => {\n\t\t\t\t\t\t\t\tdiv.style.textAlign = 'center';\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\treturn { div, logs };\n\t\t\t\t};\n\n\t\t\t\t/**\n\t\t\t\t * 判断滚动条是否滚到底部\n\t\t\t\t */\n\t\t\t\tconst isScrollBottom = (div: HTMLElement) => {\n\t\t\t\t\tconst { scrollHeight, scrollTop, clientHeight } = div;\n\t\t\t\t\treturn scrollTop + clientHeight + 50 > scrollHeight;\n\t\t\t\t};\n\n\t\t\t\tconst { div, logs } = showLogs();\n\n\t\t\t\tthis.offConfigChange(state.console.listenerIds.logs);\n\t\t\t\tstate.console.listenerIds.logs = this.onConfigChange('logs', (logs) => {\n\t\t\t\t\tconst log = createLog(logs[logs.length - 1]);\n\t\t\t\t\tdiv.append(log);\n\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\tif (isScrollBottom(div)) {\n\t\t\t\t\t\t\tlog.scrollIntoView();\n\t\t\t\t\t\t}\n\t\t\t\t\t}, 10);\n\t\t\t\t});\n\n\t\t\t\tconst show = () => {\n\t\t\t\t\tpanel.body.replaceChildren(div);\n\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\tlogs[logs.length - 1]?.scrollIntoView();\n\t\t\t\t\t}, 10);\n\t\t\t\t};\n\n\t\t\t\tshow();\n\t\t\t}\n\t\t}),\n\t\tappConfigSync: new Script({\n\t\t\tname: '🔄️ 软件配置同步',\n\t\t\tnamespace: 'background.app',\n\t\t\tmatches: [['所有页面', /./]],\n\t\t\t// 如果是在OCS软件中则不显示此页面\n\t\t\thideInPanel: $gm.getInfos() === undefined,\n\t\t\tconfigs: {\n\t\t\t\tnotes: {\n\t\t\t\t\tdefaultValue: $ui.notes([\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\th('span', [\n\t\t\t\t\t\t\t\t'如果您使用',\n\t\t\t\t\t\t\t\th('a', { href: 'https://docs.ocsjs.com/docs/app', target: '_blank' }, 'OCS桌面软件'),\n\t\t\t\t\t\t\t\t'启动浏览器，并使用此脚本，'\n\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t\t'我们会同步软件中的配置到此脚本上，方便多个浏览器的管理。',\n\t\t\t\t\t\t\t'窗口设置以及后台面板所有设置不会进行同步。'\n\t\t\t\t\t\t],\n\t\t\t\t\t\t'如果不是，您可以忽略此脚本。'\n\t\t\t\t\t]).outerHTML\n\t\t\t\t},\n\t\t\t\tsync_status: {\n\t\t\t\t\tdefaultValue: 'unconnect' as 'not_playwright_environment' | 'unconnect' | 'not_open_sync' | 'synced'\n\t\t\t\t},\n\t\t\t\tcloseSync: {\n\t\t\t\t\tdefaultValue: false,\n\t\t\t\t\tlabel: '关闭同步',\n\t\t\t\t\tattrs: {\n\t\t\t\t\t\ttype: 'checkbox'\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\n\t\t\tonrender({ panel }) {\n\t\t\t\t// 同步面板不会被锁定\n\t\t\t\tpanel.lockWrapper.remove();\n\t\t\t\tpanel.configsContainer.classList.remove('lock');\n\n\t\t\t\tconst update = () => {\n\t\t\t\t\tif (this.cfg.closeSync) {\n\t\t\t\t\t\tconst tip = h('div', { className: 'notes card' }, ['已关闭同步。']);\n\t\t\t\t\t\tpanel.body.replaceChildren(h('hr'), tip);\n\t\t\t\t\t} else if (this.cfg.sync_status === 'synced') {\n\t\t\t\t\t\tconst tip = h('div', { className: 'notes card' }, [`已成功同步软件中的配置.`]);\n\t\t\t\t\t\tpanel.body.replaceChildren(h('hr'), tip);\n\t\t\t\t\t} else if (this.cfg.sync_status === 'unconnect') {\n\t\t\t\t\t\tconst tip = h('div', { className: 'notes card' }, ['未同步软件配置，可能是桌面软件未启动。']);\n\t\t\t\t\t\tpanel.body.replaceChildren(h('hr'), tip);\n\t\t\t\t\t} else if (this.cfg.sync_status === 'not_playwright_environment') {\n\t\t\t\t\t\tconst tip = h('div', { className: 'notes card' }, ['当前浏览器不是由桌面端软件启动，无法同步配置。']);\n\t\t\t\t\t\tpanel.body.replaceChildren(h('hr'), tip);\n\t\t\t\t\t} else if (this.cfg.sync_status === 'not_open_sync') {\n\t\t\t\t\t\tconst tip = h('div', { className: 'notes card' }, ['桌面端软件未开启配置同步功能']);\n\t\t\t\t\t\tpanel.body.replaceChildren(h('hr'), tip);\n\t\t\t\t\t} else if (this.cfg.sync_status === 'empty_config') {\n\t\t\t\t\t\tconst tip = h('div', { className: 'notes card' }, ['已成功连接到软件，但配置为空。']);\n\t\t\t\t\t\tpanel.body.replaceChildren(h('hr'), tip);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst tip = h('div', { className: 'notes card' }, ['同步状态未知，请稍后重试。']);\n\t\t\t\t\t\tpanel.body.replaceChildren(h('hr'), tip);\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tupdate();\n\n\t\t\t\tthis.offConfigChange(state.app.listenerIds.sync);\n\t\t\t\tthis.offConfigChange(state.app.listenerIds.connected);\n\t\t\t\tthis.offConfigChange(state.app.listenerIds.closeSync);\n\t\t\t\tstate.app.listenerIds.connected = this.onConfigChange('sync_status', update);\n\t\t\t\tstate.app.listenerIds.closeSync = this.onConfigChange('closeSync', (closeSync) => {\n\t\t\t\t\tif (closeSync) {\n\t\t\t\t\t\tthis.cfg.sync_status = 'not_open_sync';\n\t\t\t\t\t\t$message.success({ content: '已关闭同步，刷新页面后生效' });\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t},\n\t\t\tasync onactive() {\n\t\t\t\tif ($.isInTopWindow()) {\n\t\t\t\t\tif (this.cfg.closeSync) {\n\t\t\t\t\t\t$console.log('配置同步已关闭');\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tthis.cfg.sync_status = 'unconnect';\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst res = await request('http://localhost:15319/browser', {\n\t\t\t\t\t\t\ttype: 'GM_xmlhttpRequest',\n\t\t\t\t\t\t\tmethod: 'get',\n\t\t\t\t\t\t\tresponseType: 'json'\n\t\t\t\t\t\t});\n\t\t\t\t\t\tif (!res) {\n\t\t\t\t\t\t\tthis.cfg.sync_status = 'unconnect';\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst open_sync = await request('http://localhost:15319/is-browser-config-sync', {\n\t\t\t\t\t\t\ttype: 'GM_xmlhttpRequest',\n\t\t\t\t\t\t\tmethod: 'get',\n\t\t\t\t\t\t\tresponseType: 'text'\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tif (open_sync !== 'true') {\n\t\t\t\t\t\t\tthis.cfg.sync_status = 'not_open_sync';\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (Object.keys(res).length === 0) {\n\t\t\t\t\t\t\tthis.cfg.sync_status = 'not_open_sync';\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// 自OCS软件 2.8.21 版本后特殊字段，用于标记不进行同步的字段\n\t\t\t\t\t\t// 通过OCS playwright 启动的浏览器会自动返回数据\n\t\t\t\t\t\t// 不使用 http 防止某些 Content-Security-Policy 限制\n\t\t\t\t\t\tconst environment_res = await request('/ocs-environment', {\n\t\t\t\t\t\t\ttype: 'fetch',\n\t\t\t\t\t\t\tmethod: 'get'\n\t\t\t\t\t\t});\n\t\t\t\t\t\tconst environment = environment_res?.environment;\n\t\t\t\t\t\tif (!environment || environment !== 'playwright') {\n\t\t\t\t\t\t\tthis.cfg.sync_status = 'not_playwright_environment';\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// 排除几个特殊的设置\n\t\t\t\t\t\tfor (const key in res) {\n\t\t\t\t\t\t\tif (Object.prototype.hasOwnProperty.call(res, key)) {\n\t\t\t\t\t\t\t\t// 排除渲染脚本的设置\n\t\t\t\t\t\t\t\tif (RenderScript.namespace && key.startsWith(RenderScript.namespace)) {\n\t\t\t\t\t\t\t\t\tReflect.deleteProperty(res, key);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t// 排除后台脚本的设置\n\t\t\t\t\t\t\t\tfor (const scriptKey in BackgroundProject.scripts) {\n\t\t\t\t\t\t\t\t\tif (Object.prototype.hasOwnProperty.call(BackgroundProject.scripts, scriptKey)) {\n\t\t\t\t\t\t\t\t\t\tconst script: Script = Reflect.get(BackgroundProject.scripts, scriptKey);\n\t\t\t\t\t\t\t\t\t\tif (script.namespace && key.startsWith(script.namespace)) {\n\t\t\t\t\t\t\t\t\t\t\tReflect.deleteProperty(res, key);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// 排除那些不用同步的配置\n\t\t\t\t\t\tfor (const project of definedProjects()) {\n\t\t\t\t\t\t\tfor (const key in project.scripts) {\n\t\t\t\t\t\t\t\tif (Object.prototype.hasOwnProperty.call(project.scripts, key)) {\n\t\t\t\t\t\t\t\t\tconst script = project.scripts[key];\n\t\t\t\t\t\t\t\t\tfor (const ck in script.configs) {\n\t\t\t\t\t\t\t\t\t\tif (Object.prototype.hasOwnProperty.call(script.configs, ck)) {\n\t\t\t\t\t\t\t\t\t\t\tif (script.configs[ck].extra?.appConfigSync === false) {\n\t\t\t\t\t\t\t\t\t\t\t\tReflect.deleteProperty(res, $.namespaceKey(script.namespace, ck));\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// 同步所有的配置\n\t\t\t\t\t\tfor (const key in res) {\n\t\t\t\t\t\t\tif (Object.prototype.hasOwnProperty.call(res, key)) {\n\t\t\t\t\t\t\t\t$store.set(key, res[key]);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// 锁定面板\n\t\t\t\t\t\tfor (const project of definedProjects()) {\n\t\t\t\t\t\t\t// 排除后台脚本的锁定\n\t\t\t\t\t\t\tif (project.name === BackgroundProject.name) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tfor (const key in project.scripts) {\n\t\t\t\t\t\t\t\tif (Object.prototype.hasOwnProperty.call(project.scripts, key)) {\n\t\t\t\t\t\t\t\t\tconst script = project.scripts[key];\n\t\t\t\t\t\t\t\t\tconst originalRender = script.onrender;\n\t\t\t\t\t\t\t\t\t// 重新定义渲染函数。在渲染后添加锁定面板的代码\n\t\t\t\t\t\t\t\t\tscript.onrender = ({ panel, header }) => {\n\t\t\t\t\t\t\t\t\t\toriginalRender?.({ panel, header });\n\t\t\t\t\t\t\t\t\t\tif (panel.configsContainer.children.length) {\n\t\t\t\t\t\t\t\t\t\t\tpanel.configsContainer.classList.add('lock');\n\t\t\t\t\t\t\t\t\t\t\tpanel.lockWrapper.style.width = (panel.configsContainer.clientWidth ?? panel.clientWidth) + 'px';\n\t\t\t\t\t\t\t\t\t\t\tpanel.lockWrapper.style.height =\n\t\t\t\t\t\t\t\t\t\t\t\t(panel.configsContainer.clientHeight ?? panel.clientHeight) + 'px';\n\t\t\t\t\t\t\t\t\t\t\tpanel.configsContainer.prepend(panel.lockWrapper);\n\n\t\t\t\t\t\t\t\t\t\t\tpanel.lockWrapper.title =\n\t\t\t\t\t\t\t\t\t\t\t\t'🚫已同步OCS桌面版软件配置，如需修改请在桌面版软件的左侧栏设置-通用设置-OCS配置，中进行修改。\\n\\n或者前往脚本悬浮窗:后台-软件配置同步 关闭配置同步功能。\\n\\n可双击强制修改，并关闭同步配置';\n\t\t\t\t\t\t\t\t\t\t\tpanel.lockWrapper = $ui.tooltip(panel.lockWrapper);\n\t\t\t\t\t\t\t\t\t\t\tpanel.lockWrapper.addEventListener('dblclick', () => {\n\t\t\t\t\t\t\t\t\t\t\t\tpanel.configsContainer.classList.remove('lock');\n\t\t\t\t\t\t\t\t\t\t\t\tpanel.lockWrapper.remove();\n\t\t\t\t\t\t\t\t\t\t\t\tscript.onrender = originalRender;\n\t\t\t\t\t\t\t\t\t\t\t\t$message.warn({\n\t\t\t\t\t\t\t\t\t\t\t\t\tcontent: '已解除配置同步，可正常修改配置。想开启同步请前往：后台-软件配置同步',\n\t\t\t\t\t\t\t\t\t\t\t\t\tduration: 10\n\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\t\tthis.cfg.closeSync = true;\n\t\t\t\t\t\t\t\t\t\t\t\tif (script.panel && script.header) {\n\t\t\t\t\t\t\t\t\t\t\t\t\tscript.onrender?.({ panel: script.panel, header: script.header });\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t// 重新执行渲染\n\t\t\t\t\t\t\t\t\tif (script.panel && script.header) {\n\t\t\t\t\t\t\t\t\t\tscript.onrender({ panel: script.panel, header: script.header });\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tthis.cfg.sync_status = 'synced';\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\tconsole.error(e);\n\t\t\t\t\t\tthis.cfg.sync_status = 'unconnect';\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}),\n\t\tupdate: new Script({\n\t\t\tname: '📥 更新模块',\n\t\t\tmatches: [['所有页面', /.*/]],\n\t\t\tnamespace: 'background.update',\n\t\t\tconfigs: {\n\t\t\t\tnotes: {\n\t\t\t\t\tdefaultValue: '脚本自动更新模块，如果有新的版本会自动通知。'\n\t\t\t\t},\n\t\t\t\tautoNotify: {\n\t\t\t\t\tdefaultValue: true,\n\t\t\t\t\tlabel: '开启更新通知',\n\t\t\t\t\tattrs: { type: 'checkbox', title: '当有最新的版本时自动弹窗通知，默认开启' }\n\t\t\t\t},\n\t\t\t\tnotToday: {\n\t\t\t\t\tdefaultValue: -1\n\t\t\t\t},\n\t\t\t\tignoreVersions: {\n\t\t\t\t\tdefaultValue: [] as string[]\n\t\t\t\t}\n\t\t\t},\n\t\t\tmethods() {\n\t\t\t\treturn {\n\t\t\t\t\tgetLastVersion: async () => {\n\t\t\t\t\t\treturn (await request('https://cdn.ocsjs.com/ocs-version.json?t=' + Date.now(), {\n\t\t\t\t\t\t\tmethod: 'get',\n\t\t\t\t\t\t\ttype: 'GM_xmlhttpRequest'\n\t\t\t\t\t\t})) as { 'last-version': string; resource: Record<string, string>; notes: string[] };\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t},\n\t\t\tasync onrender({ panel }) {\n\t\t\t\tconst version = await this.methods.getLastVersion();\n\t\t\t\tconst infos = $gm.getInfos();\n\n\t\t\t\tif (!infos) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst changeLog = h('button', { className: 'base-style-button-secondary' }, '📄查看更新日志');\n\t\t\t\tchangeLog.onclick = () => CommonProject.scripts.apps.methods.showChangelog();\n\t\t\t\tconst updatePage = this.startConfig?.updatePage || '';\n\t\t\t\tpanel.body.replaceChildren(\n\t\t\t\t\th('div', { className: 'card' }, [\n\t\t\t\t\t\th('hr'),\n\t\t\t\t\t\th('div', ['最新版本：' + version['last-version'] + ' - ', changeLog]),\n\t\t\t\t\t\th('hr'),\n\t\t\t\t\t\th('div', '当前版本：' + infos.script.version),\n\t\t\t\t\t\th('div', '脚本管理器：' + infos.scriptHandler),\n\t\t\t\t\t\th('div', ['脚本更新链接：', h('a', { target: '_blank', href: updatePage }, [updatePage || '无'])])\n\t\t\t\t\t])\n\t\t\t\t);\n\t\t\t\tconsole.log('versions', {\n\t\t\t\t\tnotToday: this.cfg.notToday,\n\t\t\t\t\tignoreVersions: this.cfg.ignoreVersions,\n\t\t\t\t\tversion: version\n\t\t\t\t});\n\t\t\t},\n\t\t\toncomplete() {\n\t\t\t\tif (this.cfg.autoNotify && $.isInTopWindow()) {\n\t\t\t\t\tif (this.cfg.notToday === -1 || this.cfg.notToday !== new Date().getDate()) {\n\t\t\t\t\t\tconst infos = $gm.getInfos();\n\t\t\t\t\t\tif (infos) {\n\t\t\t\t\t\t\t// 版本表达式验证\n\t\t\t\t\t\t\tif (!!semver_valid(infos.script.version) === false) {\n\t\t\t\t\t\t\t\t$message.error(`当前版本号 (${infos.script.version}) 不符合semver版本书写规范，请重新修改版本。`);\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// 避免阻挡用户操作，这里等页面运行一段时间后再进行更新提示\n\t\t\t\t\t\t\tsetTimeout(async () => {\n\t\t\t\t\t\t\t\tconst version = await this.methods.getLastVersion();\n\t\t\t\t\t\t\t\tconst last = version['last-version'];\n\n\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\t// 跳过主动忽略的版本\n\t\t\t\t\t\t\t\t\tthis.cfg.ignoreVersions.includes(last) === false &&\n\t\t\t\t\t\t\t\t\t// 版本比较\n\t\t\t\t\t\t\t\t\tsemver_gt(last, infos.script.version)\n\t\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t\tconst updatePage = this.startConfig?.updatePage || '';\n\t\t\t\t\t\t\t\t\tconst modal = $modal.confirm({\n\t\t\t\t\t\t\t\t\t\tmaskCloseable: false,\n\t\t\t\t\t\t\t\t\t\twidth: 600,\n\t\t\t\t\t\t\t\t\t\tcontent: $ui.notes([`检测到新版本发布 ${last} ：`, [...(version.notes || [])]]),\n\t\t\t\t\t\t\t\t\t\tfooter: h('div', [\n\t\t\t\t\t\t\t\t\t\t\th('button', { className: 'base-style-button-secondary', innerText: '跳过此版本' }, (btn) => {\n\t\t\t\t\t\t\t\t\t\t\t\tbtn.onclick = () => {\n\t\t\t\t\t\t\t\t\t\t\t\t\tthis.cfg.ignoreVersions = [...this.cfg.ignoreVersions, last];\n\t\t\t\t\t\t\t\t\t\t\t\t\tmodal?.remove();\n\t\t\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t\t\t\th('button', { className: 'base-style-button-secondary', innerText: '今日不再提示' }, (btn) => {\n\t\t\t\t\t\t\t\t\t\t\t\tbtn.onclick = () => {\n\t\t\t\t\t\t\t\t\t\t\t\t\tthis.cfg.notToday = new Date().getDate();\n\t\t\t\t\t\t\t\t\t\t\t\t\tmodal?.remove();\n\t\t\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t\t\t\th('button', { className: 'base-style-button', innerText: '前往更新' }, (btn) => {\n\t\t\t\t\t\t\t\t\t\t\t\tbtn.onclick = () => {\n\t\t\t\t\t\t\t\t\t\t\t\t\tif (updatePage) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\twindow.open(updatePage, '_blank');\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tmodal?.remove();\n\t\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t$message.error({ content: '无法前往更新页面，更新链接为空' });\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t\t])\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}, 5 * 1000);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}),\n\t\tdev: new Script({\n\t\t\tname: '🛠️ 开发者调试',\n\t\t\tnamespace: 'background.dev',\n\t\t\tmatches: [['所有页面', /./]],\n\t\t\tconfigs: {\n\t\t\t\tnotes: {\n\t\t\t\t\tdefaultValue: '开发人员调试用。<br>注入OCS_CONTEXT全局变量。用户可忽略此页面。'\n\t\t\t\t},\n\t\t\t\tshow_debug_cursor: {\n\t\t\t\t\tdefaultValue: true,\n\t\t\t\t\tlabel: '软件辅助点击时显示鼠标位置',\n\t\t\t\t\tattrs: { type: 'checkbox' }\n\t\t\t\t},\n\t\t\t\tenable_answerer_debug: {\n\t\t\t\t\tdefaultValue: true,\n\t\t\t\t\tlabel: '开启答题日志输出',\n\t\t\t\t\tattrs: { type: 'checkbox' }\n\t\t\t\t}\n\t\t\t},\n\t\t\tmethods() {\n\t\t\t\treturn {\n\t\t\t\t\tgetRemotePlaywrightCurrentPage: () => {\n\t\t\t\t\t\treturn RemotePlaywright.getRemotePage(this.cfg.show_debug_cursor, console.debug);\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t},\n\t\t\tonrender({ panel }) {\n\t\t\t\tconst injectBtn = h('button', { className: 'base-style-button' }, '点击注入全局变量');\n\t\t\t\tinjectBtn.addEventListener('click', () => {\n\t\t\t\t\t$gm.unsafeWindow.OCS_CONTEXT = self;\n\t\t\t\t});\n\n\t\t\t\tconst showTabDataBtn = h('button', { className: 'base-style-button' }, '显示Tab存储');\n\t\t\t\t$gm.getTab((tab) => {\n\t\t\t\t\tconst els: HTMLElement[] = [];\n\t\t\t\t\tfor (const key in tab) {\n\t\t\t\t\t\tif (Object.prototype.hasOwnProperty.call(tab, key)) {\n\t\t\t\t\t\t\tels.push(h('div', [h('b', key + ' : '), h('code', JSON.stringify(tab[key]))]));\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tshowTabDataBtn.addEventListener('click', () => {\n\t\t\t\t\t\t$modal.simple({\n\t\t\t\t\t\t\tcontent: h('div', els),\n\t\t\t\t\t\t\twidth: window.document.documentElement.clientWidth / 2\n\t\t\t\t\t\t});\n\t\t\t\t\t});\n\t\t\t\t});\n\n\t\t\t\tpanel.body.replaceChildren(h('div', { className: 'card' }, [h('hr'), injectBtn, showTabDataBtn]));\n\t\t\t}\n\t\t}),\n\t\tappLoginHelper: new Script({\n\t\t\tname: '软件登录辅助',\n\t\t\tmatches: [\n\t\t\t\t['超星登录', 'passport2.chaoxing.com/login'],\n\t\t\t\t['智慧树登录', 'passport.zhihuishu.com/login'],\n\t\t\t\t['职教云登录', 'zjy2.icve.com.cn/portal/login.html'],\n\t\t\t\t['智慧职教登录', 'sso.icve.com.cn/sso/auth']\n\t\t\t],\n\t\t\thideInPanel: true,\n\t\t\toncomplete() {\n\t\t\t\t// 将面板移动至左侧顶部，防止挡住软件登录\n\t\t\t\tif ($.isInTopWindow()) {\n\t\t\t\t\t$render.moveToEdge();\n\t\t\t\t}\n\t\t\t}\n\t\t}),\n\n\t\terrorHandle: new Script({\n\t\t\tname: '全局错误捕获',\n\t\t\tmatches: [['', /.*/]],\n\t\t\thideInPanel: true,\n\t\t\tonstart() {\n\t\t\t\tconst projects = definedProjects();\n\t\t\t\tfor (const project of projects) {\n\t\t\t\t\tfor (const key in project.scripts) {\n\t\t\t\t\t\tif (Object.prototype.hasOwnProperty.call(project.scripts, key)) {\n\t\t\t\t\t\t\tconst script = project.scripts[key];\n\t\t\t\t\t\t\tscript.on('scripterror', (err) => {\n\t\t\t\t\t\t\t\tconst msg = `[${project.name} - ${script.name}] : ${err}`;\n\t\t\t\t\t\t\t\tconsole.error(msg);\n\t\t\t\t\t\t\t\t$console.error(msg);\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}),\n\t\trequestList: new Script({\n\t\t\tname: '📄 请求记录',\n\t\t\tmatches: [['', /.*/]],\n\t\t\tpriority: 99,\n\t\t\tconfigs: {\n\t\t\t\tnotes: {\n\t\t\t\t\tdefaultValue: $ui.notes([\n\t\t\t\t\t\t'开发人员请求调试记录页面，小白勿入，最多只记录最近的100个请求数据',\n\t\t\t\t\t\t'可打开F12控制台查看请求日志，或者下方的请求列表'\n\t\t\t\t\t]).outerHTML\n\t\t\t\t},\n\t\t\t\tenable: {\n\t\t\t\t\tlabel: '开启请求记录',\n\t\t\t\t\tattrs: { type: 'checkbox' },\n\t\t\t\t\tdefaultValue: false\n\t\t\t\t},\n\t\t\t\tmethodFilter: {\n\t\t\t\t\tlabel: '方法过滤',\n\t\t\t\t\ttag: 'select',\n\t\t\t\t\tattrs: { placeholder: '选择选项' },\n\t\t\t\t\toptions: [['none', '无'], ['GET'], ['POST'], ['OPTIONS'], ['HEAD']],\n\t\t\t\t\tdefaultValue: 'none'\n\t\t\t\t},\n\t\t\t\ttypeFilter: {\n\t\t\t\t\tlabel: '类型过滤',\n\t\t\t\t\ttag: 'select',\n\t\t\t\t\tattrs: { placeholder: '选择选项' },\n\t\t\t\t\toptions: [\n\t\t\t\t\t\t['none', '无'],\n\t\t\t\t\t\t['gmxhr', '油猴API请求（gmxhr）'],\n\t\t\t\t\t\t['fetch', '普通请求（fetch）']\n\t\t\t\t\t],\n\t\t\t\t\tdefaultValue: 'none'\n\t\t\t\t},\n\t\t\t\tsearchValue: {\n\t\t\t\t\tlabel: '内容搜索',\n\t\t\t\t\tattrs: { placeholder: '搜索 URL/请求体/响应' },\n\t\t\t\t\tdefaultValue: ''\n\t\t\t\t},\n\t\t\t\tlist: {\n\t\t\t\t\tdefaultValue: [] as RequestList\n\t\t\t\t}\n\t\t\t},\n\t\t\tmethods() {\n\t\t\t\tconst render = (list: RequestList) => {\n\t\t\t\t\tthis.panel?.body.replaceChildren();\n\t\t\t\t\tthis.panel?.body.append(\n\t\t\t\t\t\th('div', { className: 'card' }, [\n\t\t\t\t\t\t\th('div', { style: { padding: '8px 0px', textAlign: 'end' } }, [\n\t\t\t\t\t\t\t\th(\n\t\t\t\t\t\t\t\t\t'button',\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tclassName: 'base-style-button-secondary',\n\t\t\t\t\t\t\t\t\t\tstyle: { marginRight: '12px' },\n\t\t\t\t\t\t\t\t\t\tinnerText: '🗑️清空记录'\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t(btn) => {\n\t\t\t\t\t\t\t\t\t\tbtn.onclick = () => {\n\t\t\t\t\t\t\t\t\t\t\tthis.cfg.list = [];\n\t\t\t\t\t\t\t\t\t\t\trender(this.cfg.list);\n\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\th('button', { className: 'base-style-button', innerText: '🔍执行搜索' }, (btn) => {\n\t\t\t\t\t\t\t\t\tbtn.onclick = () => {\n\t\t\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\t\t\tthis.cfg.methodFilter === 'none' &&\n\t\t\t\t\t\t\t\t\t\t\tthis.cfg.typeFilter === 'none' &&\n\t\t\t\t\t\t\t\t\t\t\tthis.cfg.searchValue === ''\n\t\t\t\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t\t\t\trender(this.cfg.list);\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\tconst list = this.cfg.list\n\t\t\t\t\t\t\t\t\t\t\t\t.filter((item) => {\n\t\t\t\t\t\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis.cfg.methodFilter !== 'none' &&\n\t\t\t\t\t\t\t\t\t\t\t\t\t\titem.method.toLowerCase() !== this.cfg.methodFilter.toLowerCase()\n\t\t\t\t\t\t\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t\t\t\t.filter((item) => {\n\t\t\t\t\t\t\t\t\t\t\t\t\tif (this.cfg.typeFilter !== 'none' && item.type !== this.cfg.typeFilter) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t\t\t\t.filter((item) => {\n\t\t\t\t\t\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t(this.cfg.searchValue && item.url.includes(this.cfg.searchValue)) ||\n\t\t\t\t\t\t\t\t\t\t\t\t\t\titem.data?.includes(this.cfg.searchValue) ||\n\t\t\t\t\t\t\t\t\t\t\t\t\t\titem.response?.includes(this.cfg.searchValue)\n\t\t\t\t\t\t\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\trender(list);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t\th(\n\t\t\t\t\t\t\t\t'div',\n\t\t\t\t\t\t\t\t{ style: { backgroundColor: '#292929', overflow: 'auto', maxHeight: window.innerHeight / 2 + 'px' } },\n\t\t\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t\t...(list.length === 0\n\t\t\t\t\t\t\t\t\t\t? [h('div', { style: { color: 'white', textAlign: 'center' } }, '暂无数据')]\n\t\t\t\t\t\t\t\t\t\t: []),\n\t\t\t\t\t\t\t\t\t...list.map((item) =>\n\t\t\t\t\t\t\t\t\t\t// @ts-ignore\n\t\t\t\t\t\t\t\t\t\th(\n\t\t\t\t\t\t\t\t\t\t\t'div',\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\ttitle: Object.entries(item)\n\t\t\t\t\t\t\t\t\t\t\t\t\t.map(([key, val]) =>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tkey === 'time'\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t? `${key} : ${new Date(val).toLocaleString().replace(/\\//g, '-')}`\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t: `${key} : ${val}`\n\t\t\t\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\t\t\t\t.join('\\n'),\n\t\t\t\t\t\t\t\t\t\t\t\tstyle: {\n\t\t\t\t\t\t\t\t\t\t\t\t\tmaxWidth: '800px',\n\t\t\t\t\t\t\t\t\t\t\t\t\tpadding: '4px 0px',\n\t\t\t\t\t\t\t\t\t\t\t\t\tmargin: '4px 0px',\n\t\t\t\t\t\t\t\t\t\t\t\t\t// @ts-ignore\n\t\t\t\t\t\t\t\t\t\t\t\t\ttextWrap: 'nowrap'\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t\t\t\t\th('div', [\n\t\t\t\t\t\t\t\t\t\t\t\t\th('span', { style: { marginRight: '8px' } }, new Date(item.time).toLocaleTimeString()),\n\t\t\t\t\t\t\t\t\t\t\t\t\th(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t'span',\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tstyle: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tbackgroundColor: '#2196f3a3',\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcolor: '#ececec',\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tmarginRight: '8px',\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tpadding: '0px 2px'\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\titem.method\n\t\t\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\t\t\t\th(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t'span',\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ style: { color: item.response ? '#4eb74e' : '#eb6262', marginRight: '8px' } },\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t'●'\n\t\t\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\t\t\t\th(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t'div',\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ style: { display: 'inline-block', color: '#ececec' } },\n\t\t\t\t\t\t\t\t\t\t\t\t\t\titem.url ? (item.url.length > 100 ? item.url.slice(0, 100) + '...' : item.url) : '-'\n\t\t\t\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t\t\t\t\t\t\th(\n\t\t\t\t\t\t\t\t\t\t\t\t\t'div',\n\t\t\t\t\t\t\t\t\t\t\t\t\t{ style: { overflow: 'hidden', fontSize: '12px', color: '#8f8f8f' } },\n\t\t\t\t\t\t\t\t\t\t\t\t\titem.data ? 'data: ' + item.data : ''\n\t\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\t\t\th(\n\t\t\t\t\t\t\t\t\t\t\t\t\t'div',\n\t\t\t\t\t\t\t\t\t\t\t\t\t{ style: { overflow: 'hidden', fontSize: '12px', color: '#8f8f8f' } },\n\t\t\t\t\t\t\t\t\t\t\t\t\titem.response ? 'resp: ' + item.response : item.error ? 'err : ' + item.error : ''\n\t\t\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t])\n\t\t\t\t\t);\n\t\t\t\t};\n\t\t\t\treturn {\n\t\t\t\t\trender: render\n\t\t\t\t};\n\t\t\t},\n\t\t\tonrender() {\n\t\t\t\tthis.methods.render(this.cfg.list);\n\t\t\t},\n\t\t\tonstart() {\n\t\t\t\t// 兼容其他环境\n\t\t\t\tif ($gm.isInGMContext() === false) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t/* global GM_xmlhttpRequest  RequestInfo RequestInit */\n\t\t\t\t/* eslint-disable no-global-assign */\n\t\t\t\tconst gmRequest = GM_xmlhttpRequest;\n\t\t\t\tconst originalFetch = fetch;\n\n\t\t\t\tconst getId = () => Math.random().toString(16).slice(2);\n\n\t\t\t\tconst addRecord = (item: (typeof this.cfg.list)[number]) => {\n\t\t\t\t\tthis.cfg.list = [item, ...this.cfg.list];\n\t\t\t\t\tif (this.cfg.list.length > 100) {\n\t\t\t\t\t\tthis.cfg.list = this.cfg.list.slice(0, 100);\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\tconst setItem = (id: string, response: string | undefined, error: string | undefined) => {\n\t\t\t\t\tconst list: typeof this.cfg.list = JSON.parse(JSON.stringify(this.cfg.list));\n\t\t\t\t\tconst index = list.findIndex((item) => item.id === id);\n\t\t\t\t\tif (index !== -1) {\n\t\t\t\t\t\tlist[index].response = response;\n\t\t\t\t\t\tlist[index].error = error;\n\t\t\t\t\t}\n\t\t\t\t\tthis.cfg.list = list;\n\t\t\t\t};\n\n\t\t\t\t// @ts-ignore\n\t\t\t\tGM_xmlhttpRequest = (details: any) => {\n\t\t\t\t\tif (this.cfg.enable) {\n\t\t\t\t\t\tconst id = getId();\n\t\t\t\t\t\tconst data = {\n\t\t\t\t\t\t\tid: id,\n\t\t\t\t\t\t\turl: details.url,\n\t\t\t\t\t\t\tmethod: details.method || 'unknown',\n\t\t\t\t\t\t\ttype: 'gmxhr',\n\t\t\t\t\t\t\tdata: details.data,\n\t\t\t\t\t\t\theaders: details.headers,\n\t\t\t\t\t\t\tresponse: '',\n\t\t\t\t\t\t\terror: '',\n\t\t\t\t\t\t\ttime: Date.now()\n\t\t\t\t\t\t};\n\t\t\t\t\t\taddRecord(data);\n\t\t\t\t\t\tconst onload = details.onload;\n\t\t\t\t\t\tconst onerror = details.onerror;\n\n\t\t\t\t\t\tdetails.onload = function (response: any) {\n\t\t\t\t\t\t\tsetItem(id, response.responseText, '');\n\t\t\t\t\t\t\tdata.response = details.responseType === 'json' ? response.response : response.responseText;\n\t\t\t\t\t\t\tconsole.log('%c [请求成功]', 'color: green; font-weight: bold', data.url, data);\n\t\t\t\t\t\t\tonload?.apply(this, [response]);\n\t\t\t\t\t\t};\n\t\t\t\t\t\tdetails.onerror = function (response: any) {\n\t\t\t\t\t\t\tsetItem(id, '', response.error);\n\t\t\t\t\t\t\tdata.error = response.error;\n\t\t\t\t\t\t\tconsole.log('%c [请求失败]', 'color: red; font-weight: bold', data.url, data);\n\t\t\t\t\t\t\tonerror?.apply(this, [response]);\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\n\t\t\t\t\treturn gmRequest.apply(this, [details as any]);\n\t\t\t\t};\n\t\t\t\t// @ts-ignore\n\t\t\t\tfetch = (input: URL | RequestInfo, init?: RequestInit | undefined) => {\n\t\t\t\t\tif (this.cfg.enable) {\n\t\t\t\t\t\tconst id = getId();\n\t\t\t\t\t\tconst data = {\n\t\t\t\t\t\t\tid: id,\n\t\t\t\t\t\t\turl: typeof input === 'string' ? input : input instanceof URL ? input.href : input.url,\n\t\t\t\t\t\t\tmethod: init?.method || 'unknown',\n\t\t\t\t\t\t\ttype: 'fetch',\n\t\t\t\t\t\t\tdata: init?.body,\n\t\t\t\t\t\t\theaders: init?.headers,\n\t\t\t\t\t\t\tresponse: '',\n\t\t\t\t\t\t\terror: '',\n\t\t\t\t\t\t\ttime: Date.now()\n\t\t\t\t\t\t};\n\t\t\t\t\t\taddRecord(data);\n\t\t\t\t\t\tconst res = originalFetch.apply(this, [input, init]);\n\t\t\t\t\t\tres\n\t\t\t\t\t\t\t.then((result) => {\n\t\t\t\t\t\t\t\treturn result.clone().text();\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t.then((result) => {\n\t\t\t\t\t\t\t\tsetItem(id, result, '');\n\t\t\t\t\t\t\t\tdata.response = result;\n\t\t\t\t\t\t\t\tconsole.log('%c [请求成功]', 'color: green; font-weight: bold', data.url, data);\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\tres.catch((err) => {\n\t\t\t\t\t\t\tsetItem(id, '', String(err));\n\t\t\t\t\t\t\tdata.error = String(err);\n\t\t\t\t\t\t\tconsole.log('%c [请求失败]', 'color: red; font-weight: bold', data.url, data);\n\t\t\t\t\t\t});\n\t\t\t\t\t\treturn res;\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn originalFetch.apply(this, [input, init]);\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t}\n\t\t}),\n\t\tenvironmentDetect: new Script({\n\t\t\tname: '🤖 环境检测',\n\t\t\tmatches: [['所有页面', /.*/]],\n\t\t\thideInPanel: true,\n\t\t\toncomplete() {\n\t\t\t\tif (self !== top) return;\n\n\t\t\t\tconst matches = [\n\t\t\t\t\tCXProject.scripts.studyDispatcher.matches,\n\t\t\t\t\tZHSProject.scripts['gxk-study'].matches,\n\t\t\t\t\tZHSProject.scripts.hike.matches,\n\t\t\t\t\tZHSProject.scripts['smart-study'].matches,\n\t\t\t\t\tZHSProject.scripts['wisdom-study'].matches,\n\t\t\t\t\tZHSProject.scripts['xnk-study'].matches,\n\t\t\t\t\tICourseProject.scripts.study.matches,\n\t\t\t\t\tIcveMoocProject.scripts.study.matches,\n\t\t\t\t\tZJYProject.scripts.study.matches\n\t\t\t\t]\n\t\t\t\t\t.flat()\n\t\t\t\t\t.map((m) => (Array.isArray(m) ? m[1] : m));\n\n\t\t\t\tconst url = window.location.href;\n\t\t\t\tconst match = matches.some((regex) => {\n\t\t\t\t\treturn typeof regex === 'string' ? url.includes(regex) : regex.test(url);\n\t\t\t\t});\n\t\t\t\tif (!match) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tlet messageElement: MessageElement | undefined;\n\t\t\t\tvisibleDetect();\n\n\t\t\t\tfunction visibleDetect() {\n\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\tif (!messageElement?.isConnected) messageElement = undefined;\n\n\t\t\t\t\t\tif (document.visibilityState === 'hidden' && !messageElement) {\n\t\t\t\t\t\t\tmessageElement = $message.warn({\n\t\t\t\t\t\t\t\tcontent:\n\t\t\t\t\t\t\t\t\t'⚠️检测到浏览器最小化/切屏，脚本可能无法正常运行，请保持网课页面在前台！（如果您正在全屏游戏中可以忽略此警告）',\n\t\t\t\t\t\t\t\tduration: 0\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\tvisibleDetect();\n\t\t\t\t\t}, 1000);\n\t\t\t\t}\n\t\t\t}\n\t\t}),\n\t\tmenus: new Script({\n\t\t\tname: '📁 菜单管理',\n\t\t\thideInPanel: true,\n\t\t\tmatches: [['所有页面', /.*/]],\n\t\t\tasync onactive() {\n\t\t\t\tconst currentStudyScript = [\n\t\t\t\t\t[CXProject.scripts.studyDispatcher, CXProject.scripts.study],\n\t\t\t\t\tCXProject.scripts.work,\n\t\t\t\t\tCXProject.scripts.autoRead,\n\t\t\t\t\tZHSProject.scripts['gxk-study'],\n\t\t\t\t\tZHSProject.scripts['xnk-study'],\n\t\t\t\t\tZHSProject.scripts.hike,\n\t\t\t\t\tZHSProject.scripts['smart-study'],\n\t\t\t\t\tZHSProject.scripts['wisdom-study'],\n\t\t\t\t\tZHSProject.scripts['xnk-study'],\n\t\t\t\t\tZHSProject.scripts['gxk-work'],\n\t\t\t\t\tZHSProject.scripts['xnk-work'],\n\t\t\t\t\tZHSProject.scripts['hike-work'],\n\t\t\t\t\tZHSProject.scripts['smart-work'],\n\t\t\t\t\tZHSProject.scripts['smart-exam'],\n\t\t\t\t\tZHSProject.scripts['xnk-work'],\n\t\t\t\t\t[ICourseProject.scripts.dispatcher, ICourseProject.scripts.study],\n\t\t\t\t\tICourseProject.scripts.work,\n\t\t\t\t\t[ZJYProject.scripts.dispatcher, ZJYProject.scripts.study],\n\t\t\t\t\tZJYProject.scripts.work,\n\t\t\t\t\tIcveMoocProject.scripts.study,\n\t\t\t\t\tIcveMoocProject.scripts.work,\n\t\t\t\t\tYKTProject.scripts.ai\n\t\t\t\t]\n\t\t\t\t\t.map((m) => {\n\t\t\t\t\t\tconst url = window.location.href;\n\n\t\t\t\t\t\tconst data = {\n\t\t\t\t\t\t\tmatches: Array.isArray(m) ? m[0].matches : m.matches,\n\t\t\t\t\t\t\ttarget: Array.isArray(m) ? m[1] : m\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tdata.matches.some((regexp) => {\n\t\t\t\t\t\t\t\tconst r = Array.isArray(regexp) ? regexp[1] : regexp;\n\t\t\t\t\t\t\t\treturn typeof r === 'string' ? url.includes(r) : r.test(url);\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\treturn data.target;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t})\n\t\t\t\t\t.find((m) => m !== undefined);\n\n\t\t\t\t// 注册快捷菜单\n\t\t\t\tawait $menu('🏠', { scriptPanelLink: CommonProject.scripts.guide });\n\t\t\t\tif (currentStudyScript) await $menu('🖥️', { scriptPanelLink: currentStudyScript });\n\t\t\t\tawait $menu('🔎', { scriptPanelLink: CommonProject.scripts.workResults });\n\t\t\t\tawait $menu('⚙️', { scriptPanelLink: CommonProject.scripts.settings });\n\t\t\t\tawait $menu('📥', { scriptPanelLink: BackgroundProject.scripts.update });\n\t\t\t\tawait $menu('📄', { scriptPanelLink: BackgroundProject.scripts.console });\n\t\t\t}\n\t\t})\n\t}\n});\n\ntype Console = Record<LogType, (...msg: any[]) => void>;\n\n/** 日志对象，存储日志并显示在日志面板 */\nexport const $console: Console = new Proxy({} as Console, {\n\tget(target, key) {\n\t\treturn (...msg: any[]) => {\n\t\t\tlet logs = BackgroundProject.scripts.console.cfg.logs;\n\t\t\tif (logs.length > 50) {\n\t\t\t\tlogs = logs.slice(-50);\n\t\t\t}\n\n\t\t\tconst stack_str = Error().stack || '';\n\n\t\t\t// 简化堆栈信息\n\t\t\tconst stacks = stack_str\n\t\t\t\t.replace('Error', '')\n\t\t\t\t.match(/at (.*) \\(.+:\\/\\/.+:(.+):(.+)\\)/g)\n\t\t\t\t?.map((s) => {\n\t\t\t\t\tconst match = s.match(/at (.*) \\(.+:\\/\\/.+:(.+):(.+)\\)/) || [];\n\t\t\t\t\treturn [match[1], match[2], match[3]];\n\t\t\t\t});\n\n\t\t\tlogs = logs.concat({\n\t\t\t\ttype: key.toString() as LogType,\n\t\t\t\tcontent: msg.join(' '),\n\t\t\t\ttime: Date.now(),\n\t\t\t\tstack: JSON.stringify([stack_str.split('\\n')[0], ...(stacks || [])])\n\t\t\t});\n\n\t\t\tBackgroundProject.scripts.console.cfg.logs = logs;\n\t\t};\n\t}\n});\n"
  },
  {
    "path": "packages/scripts/src/projects/common.ts",
    "content": "import debounce from 'lodash/debounce';\nimport {\n\tdefaultAnswerWrapperHandler,\n\tAnswerWrapperParser,\n\trequest,\n\tSimplifyWorkResult,\n\t$,\n\tWorkUploadType,\n\tAnswerWrapperHandlerConfig\n} from '@ocsjs/core';\nimport { $message, h, $gm, $store, Project, Script, $modal, StoreListenerType, $ui } from 'easy-us';\nimport type { AnswerMatchMode, AnswererWrapper, SearchInformation } from '@ocsjs/core';\nimport { CXProject, ICourseProject, IcveMoocProject, ZHSProject, ZJYProject } from '../index';\nimport { markdown } from '../utils/markdown';\nimport { enableCopy } from '../utils';\nimport { SearchInfosElement } from '../elements/search.infos';\nimport { RenderScript } from '../render';\nimport { dropdownStyle } from '../utils/configs';\n\nconst TAB_WORK_RESULTS_KEY = 'common.work-results.results';\n\nconst state = {\n\tworkResult: {\n\t\t/**\n\t\t * 题目位置同步处理器\n\t\t */\n\t\tquestionPositionSyncHandler: {\n\t\t\tcx: (index: number) => {\n\t\t\t\tconst el = document.querySelectorAll<HTMLElement>('[id*=\"sigleQuestionDiv\"], .questionLi')?.item(index);\n\t\t\t\tif (el) {\n\t\t\t\t\twindow.scrollTo({\n\t\t\t\t\t\ttop: el.getBoundingClientRect().top + window.pageYOffset - 50,\n\t\t\t\t\t\tbehavior: 'smooth'\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t},\n\t\t\t'zhs-gxk': (index: number) => {\n\t\t\t\tdocument.querySelectorAll<HTMLElement>('.answerCard_list ul li').item(index)?.click();\n\t\t\t},\n\t\t\t'zhs-xnk': (index: number) => {\n\t\t\t\tdocument.querySelectorAll<HTMLElement>('.jobclassallnumber-div li[questionid]').item(index)?.click();\n\t\t\t},\n\t\t\t'zhs-smart': (index: number) => {\n\t\t\t\tdocument.querySelectorAll<HTMLElement>('[role=\"treeitem\"] .font-sec-style-node').item(index)?.click();\n\t\t\t},\n\t\t\t'zhs-fusion': (index: number) => {\n\t\t\t\tdocument.querySelectorAll<HTMLElement>('.right-box .list .item').item(index)?.click();\n\t\t\t},\n\t\t\t'zhs-hike': (index: number) => {\n\t\t\t\tdocument.querySelectorAll<HTMLElement>('.q_main_right .card_ul .card_li').item(index)?.click();\n\t\t\t},\n\t\t\ticve: (index: number) => {\n\t\t\t\tdocument.querySelectorAll<HTMLElement>(`.sheet_nums [id*=\"sheetSeq\"]`).item(index)?.click();\n\t\t\t},\n\t\t\tzjy: (index: number) => {\n\t\t\t\tdocument\n\t\t\t\t\t.querySelectorAll<HTMLElement>('.subjectDet')\n\t\t\t\t\t.item(index)\n\t\t\t\t\t?.scrollIntoView({ behavior: 'smooth', block: 'center' });\n\t\t\t},\n\t\t\ticourse: (index: number) => {\n\t\t\t\tdocument\n\t\t\t\t\t.querySelectorAll<HTMLElement>('.u-questionItem,[class*=questionBody]')\n\t\t\t\t\t.item(index)\n\t\t\t\t\t?.scrollIntoView({ behavior: 'smooth', block: 'center' });\n\t\t\t}\n\t\t}\n\t},\n\tsetting: {\n\t\tlistenerIds: {\n\t\t\taw: 0 as StoreListenerType\n\t\t}\n\t}\n};\n\n/**\n * 题库缓存类型\n */\ntype QuestionCache = { title: string; answer: string; from: string; homepage: string; ai?: boolean };\n\nexport const CommonProject = Project.create({\n\tname: '通用',\n\tdomains: [],\n\tscripts: {\n\t\tguide: new Script({\n\t\t\tname: '🏠 使用教程',\n\t\t\tmatches: [['所有页面', /.*/]],\n\t\t\tnamespace: 'common.guide',\n\t\t\tconfigs: {\n\t\t\t\tnotes: {\n\t\t\t\t\tdefaultValue: $ui.notes([\n\t\t\t\t\t\t'打开任意网课平台，进入视频、作业页面等待脚本运行，',\n\t\t\t\t\t\t'任何疑问请查看上方交流群，进群后带截图进行反馈。',\n\t\t\t\t\t\t'温馨提示: ',\n\t\t\t\t\t\t'⚠️ 禁止与其他脚本一起使用，否则会不兼容导致无法运行！',\n\t\t\t\t\t\t'⚠️ 禁止最小化浏览器、切屏，否则可能导致脚本无法运行！'\n\t\t\t\t\t]).outerHTML\n\t\t\t\t}\n\t\t\t},\n\t\t\tonrender({ panel }) {\n\t\t\t\tconst guide = createGuide();\n\t\t\t\tguide.style.width = '480px';\n\t\t\t\tpanel.body.replaceChildren(guide);\n\t\t\t}\n\t\t}),\n\t\tsettings: new Script({\n\t\t\tname: '⚙️ 全局设置',\n\t\t\tmatches: [['所有页面', /.*/]],\n\t\t\tnamespace: 'common.settings',\n\t\t\tconfigs: {\n\t\t\t\tnotes: {\n\t\t\t\t\tdefaultValue: $ui.notes([\n\t\t\t\t\t\t'✨鼠标移动到按钮或者输入框，可以看到提示！',\n\t\t\t\t\t\t'想要自动答题必须设置 “题库配置” ',\n\t\t\t\t\t\t'设置后进入章节测试，作业，考试页面即可自动答题。'\n\t\t\t\t\t]).outerHTML\n\t\t\t\t},\n\t\t\t\tanswererWrappers: {\n\t\t\t\t\tseparator: '自动答题设置',\n\t\t\t\t\tdefaultValue: [] as AnswererWrapper[]\n\t\t\t\t},\n\t\t\t\t/**\n\t\t\t\t * 禁用的题库\n\t\t\t\t */\n\t\t\t\tdisabledAnswererWrapperNames: {\n\t\t\t\t\tdefaultValue: [] as string[]\n\t\t\t\t},\n\t\t\t\tanswererWrappersButton: {\n\t\t\t\t\tlabel: '题库配置',\n\t\t\t\t\tdefaultValue: '点击配置',\n\t\t\t\t\tattrs: {\n\t\t\t\t\t\ttype: 'button'\n\t\t\t\t\t},\n\t\t\t\t\tonload() {\n\t\t\t\t\t\tconst aws: any[] = CommonProject.scripts.settings.cfg.answererWrappers || [];\n\t\t\t\t\t\tthis.value = aws.length ? '当前有' + aws.length + '个可用题库，点击重新配置' : '点击配置';\n\n\t\t\t\t\t\tthis.onclick = () => {\n\t\t\t\t\t\t\tconst aw: any[] = CommonProject.scripts.settings.cfg.answererWrappers || [];\n\t\t\t\t\t\t\tconst copy = $ui.copy('复制题库配置', JSON.stringify(aw, null, 4));\n\n\t\t\t\t\t\t\tconst list = h('div', [\n\t\t\t\t\t\t\t\th('div', aw.length ? ['以下是已经解析过的题库配置：', copy] : ''),\n\t\t\t\t\t\t\t\t...createAnswererWrapperList(aw)\n\t\t\t\t\t\t\t]);\n\t\t\t\t\t\t\tconst textarea = h(\n\t\t\t\t\t\t\t\t'textarea',\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tclassName: 'modal-input',\n\t\t\t\t\t\t\t\t\tstyle: { minHeight: '250px', width: 'calc(100% - 20px)', maxWidth: '100%' },\n\t\t\t\t\t\t\t\t\tplaceholder: aw.length ? '重新输入题库配置' : '输入你的题库配置...，不会请看上方填写教程'\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\taw.length === 0 ? '' : JSON.stringify(aw, null, 4)\n\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\tconst select = $ui.tooltip(\n\t\t\t\t\t\t\t\th(\n\t\t\t\t\t\t\t\t\t'select',\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tclassName: 'base-style-active-form-control',\n\t\t\t\t\t\t\t\t\t\tstyle: { backgroundColor: '#eef2f7', borderRadius: '2px', padding: '2px 8px' }\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t\t\th('option', '默认'),\n\t\t\t\t\t\t\t\t\t\th(\n\t\t\t\t\t\t\t\t\t\t\t'option',\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\ttitle:\n\t\t\t\t\t\t\t\t\t\t\t\t\t'大学生网课题库接口适配器: 将不同的题库整合为一个API接口。详细查看 https://github.com/DokiDoki1103/tikuAdapter'\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t'TikuAdapter'\n\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\tconst modal = $modal.prompt({\n\t\t\t\t\t\t\t\twidth: 600,\n\t\t\t\t\t\t\t\tmaskCloseable: false,\n\t\t\t\t\t\t\t\tcontent: $ui.notes([\n\t\t\t\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t\t\th('div', [\n\t\t\t\t\t\t\t\t\t\t\t'题库配置填写教程：',\n\t\t\t\t\t\t\t\t\t\t\th('a', { href: 'https://docs.ocsjs.com/docs/work' }, 'https://docs.ocsjs.com/docs/work')\n\t\t\t\t\t\t\t\t\t\t])\n\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t\t\th('div', [\n\t\t\t\t\t\t\t\t\t\t\t'⚠️ 如果无法粘贴，请点->：',\n\t\t\t\t\t\t\t\t\t\t\th('button', '读取剪贴板', (btn) => {\n\t\t\t\t\t\t\t\t\t\t\t\tbtn.classList.add('base-style-button');\n\t\t\t\t\t\t\t\t\t\t\t\tbtn.onclick = () => {\n\t\t\t\t\t\t\t\t\t\t\t\t\tnavigator.clipboard.readText().then((result) => {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttextarea.value = result;\n\t\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t\t\t\t'，并同意浏览器上方的剪贴板读取申请。'\n\t\t\t\t\t\t\t\t\t\t])\n\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t['⚠️ 如果想添加多个不同的题库配置，请在每个配置之间使用三个井号隔开: ###。'],\n\t\t\t\t\t\t\t\t\t['⚠️ 配置第三方题库出现网页弹窗的，点击永久允许连接。'],\n\t\t\t\t\t\t\t\t\t...(aw.length ? [list] : [])\n\t\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t\t\tfooter: h('div', { style: { width: '100%' } }, [\n\t\t\t\t\t\t\t\t\ttextarea,\n\t\t\t\t\t\t\t\t\th('div', { style: { display: 'flex', flexWrap: 'wrap', marginTop: '12px', fontSize: '12px' } }, [\n\t\t\t\t\t\t\t\t\t\th('div', ['解析器：', select], (div) => {\n\t\t\t\t\t\t\t\t\t\t\tdiv.style.marginRight = '12px';\n\t\t\t\t\t\t\t\t\t\t\tdiv.style.flex = '1';\n\t\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t\t\th('div', { style: { flex: '1', display: 'flex', flexWrap: 'wrap', justifyContent: 'end' } }, [\n\t\t\t\t\t\t\t\t\t\t\th('button', '清空题库配置', (btn) => {\n\t\t\t\t\t\t\t\t\t\t\t\tbtn.className = 'modal-cancel-button';\n\t\t\t\t\t\t\t\t\t\t\t\tbtn.style.marginRight = '48px';\n\t\t\t\t\t\t\t\t\t\t\t\tbtn.onclick = () => {\n\t\t\t\t\t\t\t\t\t\t\t\t\t$modal.confirm({\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcontent: '确定要清空题库配置吗？',\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tonConfirm: () => {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t$message.success({ content: '已清空，在答题前请记得重新配置。' });\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tmodal?.remove();\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tCommonProject.scripts.settings.cfg.answererWrappers = [];\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis.value = '点击配置';\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t\t\t\th('button', '关闭', (btn) => {\n\t\t\t\t\t\t\t\t\t\t\t\tbtn.className = 'modal-cancel-button';\n\t\t\t\t\t\t\t\t\t\t\t\tbtn.style.marginRight = '12px';\n\t\t\t\t\t\t\t\t\t\t\t\tbtn.onclick = () => modal?.remove();\n\t\t\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t\t\t\th('button', '保存配置', (btn) => {\n\t\t\t\t\t\t\t\t\t\t\t\tbtn.className = 'modal-confirm-button';\n\t\t\t\t\t\t\t\t\t\t\t\tbtn.onclick = async () => {\n\t\t\t\t\t\t\t\t\t\t\t\t\tconst connects: string[] = $gm.getMetadataFromScriptHead('connect');\n\n\t\t\t\t\t\t\t\t\t\t\t\t\tconst value = textarea.value;\n\n\t\t\t\t\t\t\t\t\t\t\t\t\tif (!value) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t$modal.alert({\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcontent: h('div', '不能为空！')\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tif (value.includes('adapter-service/search') && (select.value === 'TikuAdapter') === false) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t$modal.alert({\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcontent: h('div', [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t'检测到您可能正在使用 ',\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\th(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t'a',\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ href: 'https://github.com/DokiDoki1103/tikuAdapter#readme' },\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t'TikuAdapter 题库'\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t'，但是您选择的解析器不是 TikuAdapter，请选择 TikuAdapter 解析器，并填写接口地址即可，例如：http://localhost:8060/adapter-service/search，或者忽略此警告。'\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconfirmButtonText: '切换至 TikuAdapter 解析器，并识别接口地址',\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tonConfirm() {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst origin =\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttextarea.value.match(/http:\\/\\/(.+)\\/adapter-service\\/search/)?.[1] || '';\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttextarea.value = `http://${origin}/adapter-service/search`;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tselect.value = 'TikuAdapter';\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tlet awsResult: AnswererWrapper[] = [];\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (select.value === 'TikuAdapter') {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (value.startsWith('http') === false) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t$modal.alert({\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcontent: h('div', [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t'格式错误，TikuAdapter解析器只能解析 url 链接，请重新输入！或者查看：',\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\th(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t'a',\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{ href: 'https://github.com/DokiDoki1103/tikuAdapter#readme' },\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t'https://github.com/DokiDoki1103/tikuAdapter#readme'\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t])\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tselect.value = '默认';\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tawsResult.push({\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tname: 'TikuAdapter题库',\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\turl: value,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\thomepage: 'https://github.com/DokiDoki1103/tikuAdapter',\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tmethod: 'post',\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: 'GM_xmlhttpRequest',\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcontentType: 'json',\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\theaders: {},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tdata: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t// eslint-disable-next-line no-template-curly-in-string\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tquestion: '${title}',\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\toptions: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\thandler: \"return (env)=>env.options?.split('\\\\n')\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\thandler:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\" return (env)=> env.type === 'single' ? 0 : env.type === 'multiple' ? 1 : env.type === 'completion' ? 3 : env.type === 'judgement' ? 4 : undefined\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\thandler: \"return (res)=>res.answer.allAnswer.map(i=>([res.question,i.join('#')]))\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst contents = value\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t.split('###')\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t.map((i) => i.trim())\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t.filter(Boolean);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tfor (const content of contents) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tawsResult.push(...(await AnswerWrapperParser.from(content)));\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t// 为空判断\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (awsResult.length === 0) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t$modal.alert({ content: '题库配置不能为空，请重新配置。' });\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t// 唯一化处理\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst result_set: AnswererWrapper[] = [];\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tfor (const res of awsResult) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (result_set.find((i) => JSON.stringify(i) === JSON.stringify(res))) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tresult_set.push(res);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tawsResult = result_set;\n\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t// 判断新旧是否一致，如果一致则提示\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tJSON.stringify(CommonProject.scripts.settings.cfg.answererWrappers) ===\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tJSON.stringify(awsResult)\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t$modal.alert({ content: h('div', ['题库配置没有变化，请重新配置！']) });\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tCommonProject.scripts.settings.cfg.answererWrappers = awsResult;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis.value = '当前有' + awsResult.length + '个可用题库';\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t$modal.confirm({\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\twidth: 600,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcontent: h('div', [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\th('div', [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t'🎉 配置成功，',\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\th('b', ' 刷新网页后 '),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t'重新进入',\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\th('b', ' 答题页面 '),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t'即可。',\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t'解析到的题库如下所示:'\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t...createAnswererWrapperList(awsResult)\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tonConfirm: () => {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif ($gm.isInGMContext()) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttop?.document.location.reload();\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t...($gm.isInGMContext()\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconfirmButtonText: '立即刷新',\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcancelButtonText: '稍后刷新'\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  }\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t: {})\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t// 格式化文本\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttextarea.value = JSON.stringify(awsResult, null, 4);\n\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t// 检测 connects.length 是因为 如果在软件的软件设置全局配置中，上下文的 GM_info 会变成空\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (connects.length) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t// 检测是否有域名白名单\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst notAllowed: string[] = [];\n\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t// 如果是通用版本，则不检测\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (connects.includes('*')) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tfor (const aw of awsResult) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (connects.some((connect) => new URL(aw.url).hostname.includes(connect)) === false) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tnotAllowed.push(aw.url);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (notAllowed.length) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t$modal.alert({\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\twidth: 600,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tmaskCloseable: false,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttitle: '⚠️警告',\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcontent: h('div', [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\th('div', [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t'配置成功，但检测到以下 域名/ip 不在脚本的白名单中，请安装 : ',\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\th(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t'a',\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\thref: 'https://docs.ocsjs.com/docs/other/api#全域名通用版本'\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t'OCS全域名通用版本'\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t'，或者手动添加 @connect ，否则无法进行请求。',\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\th(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t'ul',\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tnotAllowed.map((url) => h('li', new URL(url).hostname))\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t])\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t])\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t} catch (e: any) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t$modal.alert({\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tcontent: h('div', [h('div', '解析失败，原因如下 :'), h('div', e.message)])\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t\t])\n\t\t\t\t\t\t\t\t\t])\n\t\t\t\t\t\t\t\t])\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tupload: {\n\t\t\t\t\tlabel: '答题完成后',\n\t\t\t\t\ttag: 'select',\n\t\t\t\t\tdefaultValue: 80 as WorkUploadType,\n\t\t\t\t\toptions: [\n\t\t\t\t\t\t['save', '自动保存', '完成后自动保存答案, 注意如果你开启了随机作答, 有可能分辨不出答案是否正确。'],\n\t\t\t\t\t\t['nomove', '不保存也不提交', '等待时间过后将会自动下一节, 适合在测试脚本时使用。'],\n\t\t\t\t\t\t...([10, 20, 30, 40, 50, 60, 70, 80, 90].map((rate) => [\n\t\t\t\t\t\t\trate,\n\t\t\t\t\t\t\t`搜到${rate}%的题目则自动提交`,\n\t\t\t\t\t\t\t`例如: 100题中查询到 ${rate} 题的答案,（答案不一定正确）, 则会自动提交。`\n\t\t\t\t\t\t]) as [any, string, string][]),\n\t\t\t\t\t\t['100', '每个题目都查到答案才自动提交', '答案不一定正确'],\n\t\t\t\t\t\t['force', '强制自动提交', '不管答案是否正确直接强制自动提交，如需开启，请配合随机作答谨慎使用。']\n\t\t\t\t\t],\n\t\t\t\t\tattrs: {\n\t\t\t\t\t\ttitle:\n\t\t\t\t\t\t\t'自动答题完成后的设置，目前仅在 超星学习通的章节测试 中生效, 鼠标悬浮在选项上可以查看每个选项的具体解释。'\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tthread: {\n\t\t\t\t\tlabel: '线程数量（个）',\n\t\t\t\t\tattrs: {\n\t\t\t\t\t\ttype: 'number',\n\t\t\t\t\t\tmin: 1,\n\t\t\t\t\t\tstep: 1,\n\t\t\t\t\t\tmax: 3,\n\t\t\t\t\t\ttitle:\n\t\t\t\t\t\t\t'同一时间内答题线程工作的数量（例子：三个线程则代表一秒内同时搜索三道题），过多可能导致题库服务器压力过大，请适当调低。'\n\t\t\t\t\t},\n\t\t\t\t\tdefaultValue: 1\n\t\t\t\t},\n\t\t\t\t'work-when-no-job': {\n\t\t\t\t\tdefaultValue: false,\n\t\t\t\t\tlabel: '(仅超星)强制答题',\n\t\t\t\t\tattrs: {\n\t\t\t\t\t\ttype: 'checkbox',\n\t\t\t\t\t\ttitle:\n\t\t\t\t\t\t\t'当章节测试左上角并没有黄色任务点的时候依然进行答题（没有任务点说明此作业可能不计入总成绩，如果老师要求则可以开启）'\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t'randomWork-choice': {\n\t\t\t\t\tdefaultValue: false,\n\t\t\t\t\tlabel: '(仅超星)随机选择',\n\t\t\t\t\tattrs: { type: 'checkbox', title: '题库搜索不到答案时，随机选择任意一个选项' }\n\t\t\t\t},\n\t\t\t\t'randomWork-complete': {\n\t\t\t\t\tdefaultValue: false,\n\t\t\t\t\tlabel: '(仅超星)随机填空',\n\t\t\t\t\tattrs: { type: 'checkbox', title: '题库搜索不到答案时，随机填写以下任意一个文案' }\n\t\t\t\t},\n\t\t\t\t'randomWork-completeTexts-textarea': {\n\t\t\t\t\tdefaultValue: ['不会', '不知道', '不清楚', '不懂', '不会写'].join('\\n'),\n\t\t\t\t\tlabel: '(仅超星)随机填空文案',\n\t\t\t\t\ttag: 'textarea',\n\t\t\t\t\tshowIf: 'common.settings.randomWork-complete',\n\t\t\t\t\tattrs: { title: '每行一个，随机填入', style: { minWidth: '200px', minHeight: '50px' } },\n\t\t\t\t\tonload(el) {\n\t\t\t\t\t\tel.addEventListener('change', () => {\n\t\t\t\t\t\t\tif (String(el.value).trim() === '') {\n\t\t\t\t\t\t\t\tel.value = el.defaultValue;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tadvancedSettings: {\n\t\t\t\t\t...dropdownStyle,\n\t\t\t\t\tdefaultValue: false,\n\t\t\t\t\tlabel: '高级设置',\n\t\t\t\t\tattrs: { type: 'checkbox', title: '请谨慎使用高级设置，可能会影响答题效果，小白在未理解的情况下谨慎调整。' }\n\t\t\t\t},\n\t\t\t\tanswerWrapperHandlerTimeout: {\n\t\t\t\t\tshowIf: 'common.settings.advancedSettings',\n\t\t\t\t\telementClassName: 'config-details',\n\t\t\t\t\tlabel: '搜题最大耗时（秒）',\n\t\t\t\t\tattrs: {\n\t\t\t\t\t\ttype: 'number',\n\t\t\t\t\t\tmin: 10,\n\t\t\t\t\t\tstep: 1,\n\t\t\t\t\t\tmax: 3 * 60,\n\t\t\t\t\t\ttitle: '搜题超时时间，单位为秒，超过这个时间直接放弃，进行下一题搜索。'\n\t\t\t\t\t},\n\t\t\t\t\tdefaultValue: 120\n\t\t\t\t},\n\t\t\t\tstopSecondWhenFinish: {\n\t\t\t\t\tshowIf: 'common.settings.advancedSettings',\n\t\t\t\t\telementClassName: 'config-details',\n\t\t\t\t\tlabel: '答题结束后暂停（秒）',\n\t\t\t\t\tattrs: {\n\t\t\t\t\t\ttype: 'number',\n\t\t\t\t\t\tmin: 3,\n\t\t\t\t\t\tstep: 1,\n\t\t\t\t\t\tmax: 9999,\n\t\t\t\t\t\ttitle: '自动答题脚本结束后暂停的时间（方便查看和检查）。'\n\t\t\t\t\t},\n\t\t\t\t\tdefaultValue: 3\n\t\t\t\t},\n\t\t\t\tperiod: {\n\t\t\t\t\tshowIf: 'common.settings.advancedSettings',\n\t\t\t\t\telementClassName: 'config-details',\n\t\t\t\t\tlabel: '搜题间隔（秒）',\n\t\t\t\t\tattrs: {\n\t\t\t\t\t\ttype: 'number',\n\t\t\t\t\t\tmin: 1,\n\t\t\t\t\t\tstep: 1,\n\t\t\t\t\t\tmax: 60,\n\t\t\t\t\t\ttitle: '每道题的搜题间隔时间，不建议太低，避免增加服务器压力。'\n\t\t\t\t\t},\n\t\t\t\t\tdefaultValue: 3\n\t\t\t\t},\n\t\t\t\tanswerSeparators: {\n\t\t\t\t\tshowIf: 'common.settings.advancedSettings',\n\t\t\t\t\telementClassName: 'config-details',\n\t\t\t\t\tlabel: '答案分隔符',\n\t\t\t\t\tattrs: {\n\t\t\t\t\t\ttitle: \"分隔答案的符号，例如：答案1#答案2#答案3，分隔符为 #， 使用英文逗号进行隔开 : ',' \"\n\t\t\t\t\t},\n\t\t\t\t\tdefaultValue: ['===', '#', '---', '###', '|', ';', '；'].join(','),\n\t\t\t\t\tonload(el) {\n\t\t\t\t\t\tel.addEventListener('change', () => {\n\t\t\t\t\t\t\tif (String(el.value).trim() === '') {\n\t\t\t\t\t\t\t\tel.value = el.defaultValue;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tanswerMatchMode: {\n\t\t\t\t\tshowIf: 'common.settings.advancedSettings',\n\t\t\t\t\telementClassName: 'config-details',\n\t\t\t\t\tlabel: '答案匹配模式',\n\t\t\t\t\ttag: 'select',\n\t\t\t\t\tdefaultValue: 'similar' as AnswerMatchMode,\n\t\t\t\t\toptions: [\n\t\t\t\t\t\t['similar', '相似匹配', '答案相似度达到60%以上就匹配'],\n\t\t\t\t\t\t['exact', '精确匹配', '答案必须完全一致才匹配']\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\tredundanceWordsText: {\n\t\t\t\t\tshowIf: 'common.settings.advancedSettings',\n\t\t\t\t\telementClassName: 'config-details',\n\t\t\t\t\tdefaultValue: [\n\t\t\t\t\t\t'单选题(必考)',\n\t\t\t\t\t\t'填空题(必考)',\n\t\t\t\t\t\t'多选题(必考)',\n\t\t\t\t\t\t'(单选题)',\n\t\t\t\t\t\t'(多选题)',\n\t\t\t\t\t\t'(判断题)',\n\t\t\t\t\t\t'(填空题)',\n\t\t\t\t\t\t'【单选题】',\n\t\t\t\t\t\t'【多选题】',\n\t\t\t\t\t\t'【填空题】',\n\t\t\t\t\t\t'【判断题】',\n\t\t\t\t\t\t'【單選题】',\n\t\t\t\t\t\t'【多選题】',\n\t\t\t\t\t\t'【判斷题】',\n\t\t\t\t\t\t'【Single Choice】',\n\t\t\t\t\t\t'【Multiple Choice】',\n\t\t\t\t\t\t'【single choice】',\n\t\t\t\t\t\t'【multiple choice】',\n\t\t\t\t\t\t'【True or False】'\n\t\t\t\t\t].join('\\n'),\n\t\t\t\t\tlabel: '题目冗余字段自动删除',\n\t\t\t\t\ttag: 'textarea',\n\t\t\t\t\tattrs: {\n\t\t\t\t\t\ttitle: '在搜题的时候自动删除多余的文字，以便提高搜题的准确度，每行一个。',\n\t\t\t\t\t\tstyle: { minWidth: '200px', minHeight: '50px' }\n\t\t\t\t\t},\n\t\t\t\t\tonload(el) {\n\t\t\t\t\t\tel.addEventListener('change', () => {\n\t\t\t\t\t\t\tif (String(el.value).trim() === '') {\n\t\t\t\t\t\t\t\tel.value = el.defaultValue;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tnotification: {\n\t\t\t\t\tseparator: '其他设置',\n\t\t\t\t\tlabel: '系统通知',\n\t\t\t\t\tattrs: {\n\t\t\t\t\t\ttitle:\n\t\t\t\t\t\t\t'允许脚本发送系统通知，只有重要事情发生时会发送系统通知，尽量避免用户受到骚扰（在电脑屏幕右侧显示通知弹窗，例如脚本执行完毕，图形验证码，版本更新等通知）。'\n\t\t\t\t\t},\n\t\t\t\t\ttag: 'select',\n\t\t\t\t\tdefaultValue: 'only-notify' as 'only-notify' | 'notify-and-voice' | 'all' | 'no-notify',\n\t\t\t\t\toptions: [\n\t\t\t\t\t\t['only-notify', '只显示右下角通知'],\n\t\t\t\t\t\t['notify-and-voice', '通知以及提示音（叮的一声）'],\n\t\t\t\t\t\t['all', '通知，提示音，以及任务栏闪烁提示'],\n\t\t\t\t\t\t['no-notify', '关闭系统通知']\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\tnotificationWebhooks: {\n\t\t\t\t\tlabel: '通知回调',\n\t\t\t\t\tattrs: {\n\t\t\t\t\t\ttitle:\n\t\t\t\t\t\t\t// eslint-disable-next-line no-template-curly-in-string\n\t\t\t\t\t\t\t'发送系统通知时发送回调请求，用于专业开发人员对接其他通知系统。（每行填写一个URL，顺序发送GET请求，${message} 为消息占位符，可用于消息变量替换）'\n\t\t\t\t\t},\n\t\t\t\t\ttag: 'textarea',\n\t\t\t\t\tdefaultValue: ''\n\t\t\t\t},\n\t\t\t\tenableQuestionCaches: {\n\t\t\t\t\tlabel: '题库缓存功能',\n\t\t\t\t\tdefaultValue: true,\n\t\t\t\t\tattrs: { type: 'checkbox', title: '详情请前往 通用-其他应用-题库拓展查看。' }\n\t\t\t\t}\n\t\t\t},\n\t\t\tmethods() {\n\t\t\t\treturn {\n\t\t\t\t\t/**\n\t\t\t\t\t * 获取自动答题配置，包括题库配置\n\t\t\t\t\t */\n\t\t\t\t\tgetWorkOptions: () => {\n\t\t\t\t\t\t// 使用 json 深拷贝，防止修改原始配置\n\t\t\t\t\t\tconst workOptions: typeof this.cfg = JSON.parse(JSON.stringify(this.cfg));\n\n\t\t\t\t\t\t/**\n\t\t\t\t\t\t * 过滤掉被禁用的题库\n\t\t\t\t\t\t */\n\t\t\t\t\t\tworkOptions.answererWrappers = workOptions.answererWrappers.filter(\n\t\t\t\t\t\t\t(aw) => this.cfg.disabledAnswererWrapperNames.find((daw) => daw === aw.name) === undefined\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\treturn workOptions;\n\t\t\t\t\t},\n\t\t\t\t\t/**\n\t\t\t\t\t * 根据全局设置的配置，发起通知\n\t\t\t\t\t * @param content\n\t\t\t\t\t * @param opts\n\t\t\t\t\t */\n\t\t\t\t\tnotificationBySetting: (\n\t\t\t\t\t\tcontent: string,\n\t\t\t\t\t\topts?: {\n\t\t\t\t\t\t\textraTitle?: string;\n\t\t\t\t\t\t\t/** 显示时间，单位为秒，默认为 30 秒， 0 则表示一直存在 */\n\t\t\t\t\t\t\tduration?: number;\n\t\t\t\t\t\t\t/** 通知点击时 */\n\t\t\t\t\t\t\tonclick?: () => void;\n\t\t\t\t\t\t\t/** 通知关闭时 */\n\t\t\t\t\t\t\tondone?: () => void;\n\t\t\t\t\t\t}\n\t\t\t\t\t) => {\n\t\t\t\t\t\tif (this.cfg.notification !== 'no-notify') {\n\t\t\t\t\t\t\t$gm.notification(content, {\n\t\t\t\t\t\t\t\textraTitle: opts?.extraTitle,\n\t\t\t\t\t\t\t\tduration: opts?.duration ?? 30,\n\t\t\t\t\t\t\t\timportant: this.cfg.notification === 'all',\n\t\t\t\t\t\t\t\tsilent: this.cfg.notification === 'only-notify'\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\tconst message = (opts?.extraTitle ? opts?.extraTitle + '：' : '') + content;\n\n\t\t\t\t\t\t\tconst webhooks = this.cfg.notificationWebhooks\n\t\t\t\t\t\t\t\t.split('\\n')\n\t\t\t\t\t\t\t\t.map((i) => i.trim())\n\t\t\t\t\t\t\t\t.filter(Boolean);\n\n\t\t\t\t\t\t\tfor (const webhook of webhooks) {\n\t\t\t\t\t\t\t\tlet resolved_webhook = webhook;\n\t\t\t\t\t\t\t\t// eslint-disable-next-line no-template-curly-in-string\n\t\t\t\t\t\t\t\tresolved_webhook = webhook.replace('${message}', encodeURIComponent(message));\n\t\t\t\t\t\t\t\trequest(resolved_webhook, {\n\t\t\t\t\t\t\t\t\tmethod: 'get',\n\t\t\t\t\t\t\t\t\ttype: 'GM_xmlhttpRequest'\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t.then((result) => {\n\t\t\t\t\t\t\t\t\t\tconsole.debug('通知回调成功', { webhook: resolved_webhook, result });\n\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t.catch((err) => {\n\t\t\t\t\t\t\t\t\t\tconsole.debug('通知回调失败', { webhook: resolved_webhook, err });\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t},\n\t\t\t// 实时更新内部设置\n\t\t\toncomplete() {\n\t\t\t\tAnswerWrapperHandlerConfig.timeout_seconds = this.cfg.answerWrapperHandlerTimeout;\n\t\t\t\tthis.onConfigChange('answerWrapperHandlerTimeout', (sec) => {\n\t\t\t\t\tAnswerWrapperHandlerConfig.timeout_seconds = sec;\n\t\t\t\t});\n\t\t\t},\n\t\t\tonrender({ panel }) {\n\t\t\t\t// 因为需要用到 GM_xhr 所以判断是否处于用户脚本环境\n\t\t\t\tif ($gm.isInGMContext()) {\n\t\t\t\t\tpanel.body.replaceChildren(...(this.cfg.answererWrappers.length ? [h('hr')] : []));\n\t\t\t\t\tconst testNotification = h(\n\t\t\t\t\t\t'button',\n\t\t\t\t\t\t{ className: 'base-style-button', disabled: this.cfg.answererWrappers.length === 0 },\n\t\t\t\t\t\t'📢测试系统通知'\n\t\t\t\t\t);\n\t\t\t\t\ttestNotification.onclick = () => {\n\t\t\t\t\t\tthis.methods.notificationBySetting('这是一条测试通知');\n\t\t\t\t\t};\n\t\t\t\t\tconst refresh = h(\n\t\t\t\t\t\t'button',\n\t\t\t\t\t\t{ className: 'base-style-button', disabled: this.cfg.answererWrappers.length === 0 },\n\t\t\t\t\t\t'🔄️刷新题库状态'\n\t\t\t\t\t);\n\t\t\t\t\trefresh.onclick = () => {\n\t\t\t\t\t\tupdateState();\n\t\t\t\t\t};\n\t\t\t\t\tconst tableContainer = h('div');\n\t\t\t\t\trefresh.style.display = 'none';\n\t\t\t\t\ttableContainer.style.display = 'none';\n\t\t\t\t\tpanel.body.append(h('div', { style: { display: 'flex' } }, [testNotification, refresh]), tableContainer);\n\n\t\t\t\t\t// 更新题库状态\n\t\t\t\t\tconst updateState = async () => {\n\t\t\t\t\t\t// 清空元素\n\t\t\t\t\t\ttableContainer.replaceChildren();\n\t\t\t\t\t\tlet loadedCount = 0;\n\n\t\t\t\t\t\tif (this.cfg.answererWrappers.length) {\n\t\t\t\t\t\t\trefresh.style.display = 'block';\n\t\t\t\t\t\t\ttableContainer.style.display = 'block';\n\t\t\t\t\t\t\trefresh.textContent = '🚫正在加载题库状态...';\n\t\t\t\t\t\t\trefresh.setAttribute('disabled', 'true');\n\n\t\t\t\t\t\t\tconst table = h('table');\n\t\t\t\t\t\t\ttable.style.width = '100%';\n\t\t\t\t\t\t\tthis.cfg.answererWrappers.forEach(async (item) => {\n\t\t\t\t\t\t\t\tconst t = Date.now();\n\t\t\t\t\t\t\t\tlet success = false;\n\t\t\t\t\t\t\t\tlet error;\n\t\t\t\t\t\t\t\tconst isDisabled = this.cfg.disabledAnswererWrapperNames.find((name) => name === item.name);\n\n\t\t\t\t\t\t\t\tconst res = isDisabled\n\t\t\t\t\t\t\t\t\t? false\n\t\t\t\t\t\t\t\t\t: await Promise.race([\n\t\t\t\t\t\t\t\t\t\t\t(async () => {\n\t\t\t\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\t\t\t\treturn await request(new URL(item.url).origin + '/?t=' + t, {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\ttype: 'GM_xmlhttpRequest',\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tmethod: 'head',\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tresponseType: 'text'\n\t\t\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\t\t\t\t\t\t\terror = err;\n\t\t\t\t\t\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t})(),\n\t\t\t\t\t\t\t\t\t\t\t(async () => {\n\t\t\t\t\t\t\t\t\t\t\t\tawait $.sleep(10 * 1000);\n\t\t\t\t\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t\t\t\t\t})()\n\t\t\t\t\t\t\t\t\t  ]);\n\t\t\t\t\t\t\t\tif (typeof res === 'string') {\n\t\t\t\t\t\t\t\t\tsuccess = true;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tsuccess = false;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tconst body = h('tbody');\n\t\t\t\t\t\t\t\tbody.append(h('td', item.name));\n\t\t\t\t\t\t\t\tbody.append(\n\t\t\t\t\t\t\t\t\th('td', [\n\t\t\t\t\t\t\t\t\t\t$ui.tooltip(\n\t\t\t\t\t\t\t\t\t\t\th(\n\t\t\t\t\t\t\t\t\t\t\t\t'span',\n\t\t\t\t\t\t\t\t\t\t\t\t{ title: isDisabled ? '题目已经被停用，请在上方题库配置中点击开启。' : '' },\n\t\t\t\t\t\t\t\t\t\t\t\tsuccess ? '连接成功🟢' : isDisabled ? '已停用⚪' : error ? '连接失败🔴' : '连接超时🟡'\n\t\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t])\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\tbody.append(h('td', `延迟 : ${success ? Date.now() - t : '---'}/ms`));\n\t\t\t\t\t\t\t\ttable.append(body);\n\t\t\t\t\t\t\t\tloadedCount++;\n\n\t\t\t\t\t\t\t\tif (loadedCount === this.cfg.answererWrappers.length) {\n\t\t\t\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\t\t\t\trefresh.textContent = '🔄️刷新题库状态';\n\t\t\t\t\t\t\t\t\t\trefresh.removeAttribute('disabled');\n\t\t\t\t\t\t\t\t\t}, 2000);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\ttableContainer.append(table);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\trefresh.style.display = 'none';\n\t\t\t\t\t\t\ttableContainer.style.display = 'none';\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\n\t\t\t\t\tupdateState();\n\n\t\t\t\t\tthis.offConfigChange(state.setting.listenerIds.aw);\n\t\t\t\t\tstate.setting.listenerIds.aw = this.onConfigChange('answererWrappers', (_, __, remote) => {\n\t\t\t\t\t\tif (remote === false) {\n\t\t\t\t\t\t\tupdateState();\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}),\n\t\tworkResults: new Script({\n\t\t\tname: '🔎 搜索结果',\n\t\t\tmatches: [['所有页面', /.*/]],\n\t\t\tnamespace: 'common.work-results',\n\t\t\tconfigs: {\n\t\t\t\tnotes: {\n\t\t\t\t\tdefaultValue: $ui.notes(['点击题目序号，查看搜索结果', '如果没有搜到，可能是题库没有收录该题目答案'])\n\t\t\t\t\t\t.outerHTML\n\t\t\t\t},\n\t\t\t\t/**\n\t\t\t\t * 显示类型\n\t\t\t\t * list: 显示为题目列表\n\t\t\t\t * numbers: 显示为序号列表\n\t\t\t\t */\n\t\t\t\ttype: {\n\t\t\t\t\tlabel: '显示类型',\n\t\t\t\t\ttag: 'select',\n\t\t\t\t\toptions: [\n\t\t\t\t\t\t['numbers', '序号列表'],\n\t\t\t\t\t\t['questions', '题目列表']\n\t\t\t\t\t],\n\t\t\t\t\tattrs: {\n\t\t\t\t\t\ttitle: '使用题目列表可能会造成页面卡顿。'\n\t\t\t\t\t},\n\t\t\t\t\tdefaultValue: 'numbers' as 'questions' | 'numbers'\n\t\t\t\t},\n\t\t\t\ttotalQuestionCount: {\n\t\t\t\t\tdefaultValue: 0\n\t\t\t\t},\n\t\t\t\trequestedCount: {\n\t\t\t\t\tdefaultValue: 0\n\t\t\t\t},\n\t\t\t\tresolvedCount: {\n\t\t\t\t\tdefaultValue: 0\n\t\t\t\t},\n\t\t\t\tcurrentResultIndex: {\n\t\t\t\t\tdefaultValue: 0\n\t\t\t\t},\n\t\t\t\tquestionPositionSyncHandlerType: {\n\t\t\t\t\tdefaultValue: undefined as keyof typeof state.workResult.questionPositionSyncHandler | undefined\n\t\t\t\t}\n\t\t\t},\n\t\t\tmethods() {\n\t\t\t\treturn {\n\t\t\t\t\t/**\n\t\t\t\t\t * 从搜索结果中计算状态，并更新\n\t\t\t\t\t */\n\t\t\t\t\tupdateWorkStateByResults: (results: { requested: boolean; resolved: boolean }[]) => {\n\t\t\t\t\t\tthis.cfg.totalQuestionCount = results.length;\n\t\t\t\t\t\tthis.cfg.requestedCount = results.filter((result) => result.requested).length;\n\t\t\t\t\t\tthis.cfg.resolvedCount = results.filter((result) => result.resolved).length;\n\t\t\t\t\t},\n\t\t\t\t\t/**\n\t\t\t\t\t * 更新状态\n\t\t\t\t\t */\n\t\t\t\t\tupdateWorkState: (state: { totalQuestionCount: number; requestedCount: number; resolvedCount: number }) => {\n\t\t\t\t\t\tthis.cfg.totalQuestionCount = state.totalQuestionCount;\n\t\t\t\t\t\tthis.cfg.requestedCount = state.requestedCount;\n\t\t\t\t\t\tthis.cfg.resolvedCount = state.resolvedCount;\n\t\t\t\t\t},\n\t\t\t\t\t/**\n\t\t\t\t\t * 刷新状态\n\t\t\t\t\t */\n\t\t\t\t\trefreshState: () => {\n\t\t\t\t\t\tthis.cfg.totalQuestionCount = 0;\n\t\t\t\t\t\tthis.cfg.requestedCount = 0;\n\t\t\t\t\t\tthis.cfg.resolvedCount = 0;\n\t\t\t\t\t},\n\t\t\t\t\t/**\n\t\t\t\t\t * 清空搜索结果\n\t\t\t\t\t */\n\t\t\t\t\tclearResults: () => {\n\t\t\t\t\t\treturn $store.setTab(TAB_WORK_RESULTS_KEY, []);\n\t\t\t\t\t},\n\t\t\t\t\tgetResults(): Promise<SimplifyWorkResult[]> | undefined {\n\t\t\t\t\t\treturn $store.getTab(TAB_WORK_RESULTS_KEY) || undefined;\n\t\t\t\t\t},\n\t\t\t\t\tsetResults(results: SimplifyWorkResult[]) {\n\t\t\t\t\t\treturn $store.setTab(TAB_WORK_RESULTS_KEY, results);\n\t\t\t\t\t},\n\t\t\t\t\tasync appendResults(results: SimplifyWorkResult[]) {\n\t\t\t\t\t\tconst data = (await $store.getTab(TAB_WORK_RESULTS_KEY)) || [];\n\t\t\t\t\t\tdata.push(...results);\n\t\t\t\t\t\treturn $store.setTab(TAB_WORK_RESULTS_KEY, data);\n\t\t\t\t\t},\n\t\t\t\t\t/**\n\t\t\t\t\t * 刷新搜索结果状态，清空搜索结果，置顶搜索结果面板\n\t\t\t\t\t */\n\t\t\t\t\tinit(opts?: { questionPositionSyncHandlerType?: keyof typeof state.workResult.questionPositionSyncHandler }) {\n\t\t\t\t\t\tCommonProject.scripts.workResults.cfg.questionPositionSyncHandlerType =\n\t\t\t\t\t\t\topts?.questionPositionSyncHandlerType;\n\t\t\t\t\t\t// 刷新搜索结果状态\n\t\t\t\t\t\tCommonProject.scripts.workResults.methods.refreshState();\n\t\t\t\t\t\t// 清空搜索结果\n\t\t\t\t\t\tCommonProject.scripts.workResults.methods.clearResults();\n\t\t\t\t\t},\n\t\t\t\t\t/**\n\t\t\t\t\t * 创建搜索结果面板\n\t\t\t\t\t * @param mount 挂载点\n\t\t\t\t\t */\n\t\t\t\t\tcreateWorkResultsPanel: (mount?: HTMLElement) => {\n\t\t\t\t\t\tconst container = mount || h('div');\n\t\t\t\t\t\tcontainer.style.width = '400px';\n\t\t\t\t\t\t/** 记录滚动高度 */\n\t\t\t\t\t\tlet scrollPercent = 0;\n\n\t\t\t\t\t\t/** 列表 */\n\t\t\t\t\t\tconst list = h('div', { className: 'work-result-list' });\n\n\t\t\t\t\t\t/** 是否悬浮在题目上 */\n\t\t\t\t\t\tlet mouseoverIndex = -1;\n\n\t\t\t\t\t\tlist.onscroll = () => {\n\t\t\t\t\t\t\tscrollPercent = list.scrollTop / list.scrollHeight;\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\t/** 给序号设置样式 */\n\t\t\t\t\t\tconst setNumStyle = (result: SimplifyWorkResult, num: HTMLElement, index: number) => {\n\t\t\t\t\t\t\tif (result.requested) {\n\t\t\t\t\t\t\t\tnum.classList.add('requested');\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif (index === this.cfg.currentResultIndex) {\n\t\t\t\t\t\t\t\tnum.classList.add('active');\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif (result.finish) {\n\t\t\t\t\t\t\t\tnum.classList.add('finish');\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\tresult.requested &&\n\t\t\t\t\t\t\t\t\tresult.resolved &&\n\t\t\t\t\t\t\t\t\t(result.error?.trim().length !== 0 || result.searchInfos.length === 0 || result.finish === false)\n\t\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t\tnum.classList.add('error');\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\t/** 渲染结果面板 */\n\t\t\t\t\t\tconst render = debounce(async () => {\n\t\t\t\t\t\t\tconst results: SimplifyWorkResult[] | undefined =\n\t\t\t\t\t\t\t\tawait CommonProject.scripts.workResults.methods.getResults();\n\n\t\t\t\t\t\t\tif (results?.length) {\n\t\t\t\t\t\t\t\t// 如果序号指向的结果为空，则代表已经被清空，则重新让index变成0\n\t\t\t\t\t\t\t\tif (results[this.cfg.currentResultIndex] === undefined) {\n\t\t\t\t\t\t\t\t\tthis.cfg.currentResultIndex = 0;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t// 渲染序号或者题目列表\n\t\t\t\t\t\t\t\tif (this.cfg.type === 'numbers') {\n\t\t\t\t\t\t\t\t\tconst resultContainer = h('div', { className: 'work-result-container' });\n\n\t\t\t\t\t\t\t\t\tlist.style.marginBottom = '12px';\n\t\t\t\t\t\t\t\t\tlist.style.overflow = 'auto';\n\t\t\t\t\t\t\t\t\tlist.style.maxHeight = '300px';\n\n\t\t\t\t\t\t\t\t\t/** 渲染序号 */\n\t\t\t\t\t\t\t\t\tconst nums = results.map((result, index) => {\n\t\t\t\t\t\t\t\t\t\treturn h('span', { className: 'search-infos-num', innerText: (index + 1).toString() }, (num) => {\n\t\t\t\t\t\t\t\t\t\t\tsetNumStyle(result, num, index);\n\n\t\t\t\t\t\t\t\t\t\t\tnum.onclick = () => {\n\t\t\t\t\t\t\t\t\t\t\t\tfor (const n of nums) {\n\t\t\t\t\t\t\t\t\t\t\t\t\tn.classList.remove('active');\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tnum.classList.add('active');\n\t\t\t\t\t\t\t\t\t\t\t\t// 更新显示序号\n\t\t\t\t\t\t\t\t\t\t\t\tthis.cfg.currentResultIndex = index;\n\t\t\t\t\t\t\t\t\t\t\t\t// 重新渲染结果列表\n\t\t\t\t\t\t\t\t\t\t\t\tresultContainer.replaceChildren(createResult(result));\n\t\t\t\t\t\t\t\t\t\t\t\t// 触发页面题目元素同步器\n\t\t\t\t\t\t\t\t\t\t\t\tif (this.cfg.questionPositionSyncHandlerType) {\n\t\t\t\t\t\t\t\t\t\t\t\t\tstate.workResult.questionPositionSyncHandler[this.cfg.questionPositionSyncHandlerType]?.(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tindex\n\t\t\t\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\t\t\tlist.replaceChildren(...nums);\n\t\t\t\t\t\t\t\t\t// 初始显示指定序号的结果\n\t\t\t\t\t\t\t\t\tresultContainer.replaceChildren(createResult(results[this.cfg.currentResultIndex]));\n\n\t\t\t\t\t\t\t\t\tcontainer.replaceChildren(list, resultContainer);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t/** 左侧题目列表 */\n\n\t\t\t\t\t\t\t\t\tlist.style.overflow = 'auto';\n\t\t\t\t\t\t\t\t\tlist.style.maxHeight = window.innerHeight / 2 + 'px';\n\n\t\t\t\t\t\t\t\t\t/** 右侧结果 */\n\t\t\t\t\t\t\t\t\tconst resultContainer = h('div', { className: 'work-result-question-container' });\n\t\t\t\t\t\t\t\t\tconst nums: HTMLSpanElement[] = [];\n\t\t\t\t\t\t\t\t\t/** 左侧渲染题目列表 */\n\t\t\t\t\t\t\t\t\tconst questions = results.map((result, index) => {\n\t\t\t\t\t\t\t\t\t\t/** 左侧序号 */\n\t\t\t\t\t\t\t\t\t\tconst num = h(\n\t\t\t\t\t\t\t\t\t\t\t'span',\n\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\tclassName: 'search-infos-num',\n\t\t\t\t\t\t\t\t\t\t\t\tinnerHTML: (index + 1).toString()\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t(num) => {\n\t\t\t\t\t\t\t\t\t\t\t\tnum.style.marginRight = '12px';\n\t\t\t\t\t\t\t\t\t\t\t\tnum.style.display = 'inline-block';\n\t\t\t\t\t\t\t\t\t\t\t\tsetNumStyle(result, num, index);\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t\t\t\tnums.push(num);\n\n\t\t\t\t\t\t\t\t\t\treturn h(\n\t\t\t\t\t\t\t\t\t\t\t'div',\n\n\t\t\t\t\t\t\t\t\t\t\t[num, result.question],\n\t\t\t\t\t\t\t\t\t\t\t(question) => {\n\t\t\t\t\t\t\t\t\t\t\t\tquestion.onmouseover = () => {\n\t\t\t\t\t\t\t\t\t\t\t\t\tmouseoverIndex = index;\n\t\t\t\t\t\t\t\t\t\t\t\t\t// 重新渲染结果列表\n\t\t\t\t\t\t\t\t\t\t\t\t\tresultContainer.replaceChildren(createResult(result));\n\t\t\t\t\t\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\t\t\t\t\t\tquestion.onmouseleave = () => {\n\t\t\t\t\t\t\t\t\t\t\t\t\tmouseoverIndex = -1;\n\t\t\t\t\t\t\t\t\t\t\t\t\t// 重新显示指定序号的结果\n\t\t\t\t\t\t\t\t\t\t\t\t\tresultContainer.replaceChildren(createResult(results[this.cfg.currentResultIndex]));\n\t\t\t\t\t\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\t\t\t\t\t\tquestion.onclick = () => {\n\t\t\t\t\t\t\t\t\t\t\t\t\tfor (const n of nums) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tn.classList.remove('active');\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tfor (const q of questions) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tq.classList.remove('active');\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tnums[index].classList.add('active');\n\t\t\t\t\t\t\t\t\t\t\t\t\tquestion.classList.add('active');\n\t\t\t\t\t\t\t\t\t\t\t\t\t// 更新显示序号\n\t\t\t\t\t\t\t\t\t\t\t\t\tthis.cfg.currentResultIndex = index;\n\t\t\t\t\t\t\t\t\t\t\t\t\t// 重新渲染结果列表\n\t\t\t\t\t\t\t\t\t\t\t\t\tresultContainer.replaceChildren(createResult(result));\n\t\t\t\t\t\t\t\t\t\t\t\t\t// 触发页面题目元素同步器\n\t\t\t\t\t\t\t\t\t\t\t\t\tif (this.cfg.questionPositionSyncHandlerType) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tstate.workResult.questionPositionSyncHandler[this.cfg.questionPositionSyncHandlerType]?.(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tindex\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\t\t\tlist.replaceChildren(...questions);\n\t\t\t\t\t\t\t\t\t// 初始显示指定序号的结果\n\t\t\t\t\t\t\t\t\tif (mouseoverIndex === -1) {\n\t\t\t\t\t\t\t\t\t\tresultContainer.replaceChildren(createResult(results[this.cfg.currentResultIndex]));\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tresultContainer.replaceChildren(createResult(results[mouseoverIndex]));\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\tcontainer.replaceChildren(\n\t\t\t\t\t\t\t\t\t\th('div', [list, h('div', {}, [resultContainer])], (div) => {\n\t\t\t\t\t\t\t\t\t\t\tdiv.style.display = 'flex';\n\t\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tcontainer.replaceChildren(\n\t\t\t\t\t\t\t\t\th('div', { className: 'alert-info-wrapper' }, [\n\t\t\t\t\t\t\t\t\t\th('div', '暂无任何搜索结果~', (div) => {\n\t\t\t\t\t\t\t\t\t\t\tdiv.style.marginTop = '12px';\n\t\t\t\t\t\t\t\t\t\t\tdiv.className = 'result-info no-answer';\n\t\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t])\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t/** 恢复高度 */\n\t\t\t\t\t\t\tlist.scrollTo({\n\t\t\t\t\t\t\t\ttop: scrollPercent * list.scrollHeight,\n\t\t\t\t\t\t\t\tbehavior: 'auto'\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\tconst tip = h('div', [\n\t\t\t\t\t\t\t\th('div', { className: 'search-infos-num' }, '1'),\n\t\t\t\t\t\t\t\t' 表示等待处理中',\n\t\t\t\t\t\t\t\th('br'),\n\t\t\t\t\t\t\t\th('div', { className: 'search-infos-num requested' }, '1'),\n\t\t\t\t\t\t\t\t' 表示已完成搜索 ',\n\t\t\t\t\t\t\t\th('br'),\n\t\t\t\t\t\t\t\th('div', { className: 'search-infos-num finish' }, '1'),\n\t\t\t\t\t\t\t\t' 表示已搜索已答题 '\n\t\t\t\t\t\t\t]);\n\n\t\t\t\t\t\t\t/** 添加信息 */\n\t\t\t\t\t\t\tcontainer.prepend(\n\t\t\t\t\t\t\t\th('hr'),\n\t\t\t\t\t\t\t\th(\n\t\t\t\t\t\t\t\t\t'div',\n\t\t\t\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t\t\t$ui.space(\n\t\t\t\t\t\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t\t\t\t\th('span', `已搜题: ${this.cfg.requestedCount}/${this.cfg.totalQuestionCount}`),\n\t\t\t\t\t\t\t\t\t\t\t\th('span', `已答题: ${this.cfg.resolvedCount}/${this.cfg.totalQuestionCount}`),\n\t\t\t\t\t\t\t\t\t\t\t\th('a', '提示', (btn) => {\n\t\t\t\t\t\t\t\t\t\t\t\t\tbtn.style.cursor = 'pointer';\n\t\t\t\t\t\t\t\t\t\t\t\t\tbtn.onclick = () => {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t$modal.confirm({ content: tip, footer: undefined });\n\t\t\t\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t\t\t\t\t$ui.tooltip(\n\t\t\t\t\t\t\t\t\t\t\t\t\th('a', '清空结果', (btn) => {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tbtn.title = '仅用于不会自动清空搜索结果的场景，例如超星非整卷预览模式';\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tbtn.style.cursor = 'pointer';\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tbtn.onclick = () => {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tthis.methods.clearResults();\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tconst { panel, header } = CXProject.scripts.work;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (panel && header) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tCXProject.scripts.work.onrender?.({ panel, header });\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tCommonProject.scripts.workResults.onrender?.({ panel, header });\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\t\t{ separator: '|' }\n\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t(div) => {\n\t\t\t\t\t\t\t\t\t\tdiv.style.textAlign = 'center';\n\t\t\t\t\t\t\t\t\t\tdiv.style.fontSize = '12px';\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}, 100);\n\n\t\t\t\t\t\t/** 渲染结果列表 */\n\t\t\t\t\t\tconst createResult = (result: SimplifyWorkResult | undefined) => {\n\t\t\t\t\t\t\tif (result) {\n\t\t\t\t\t\t\t\tlet info: HTMLElement | null = null;\n\n\t\t\t\t\t\t\t\tif (result.requested === false && result.resolved === false) {\n\t\t\t\t\t\t\t\t\tinfo = h('div', { className: 'result-info unresolved' }, '等待搜索中... 🔍');\n\t\t\t\t\t\t\t\t} else if (result.error) {\n\t\t\t\t\t\t\t\t\tinfo = h('div', { className: 'result-info error' }, '❌ ' + result.error);\n\t\t\t\t\t\t\t\t} else if (result.searchInfos.length === 0) {\n\t\t\t\t\t\t\t\t\tinfo = h('div', { className: 'result-info no-answer' }, '❌ 题库没搜索到答案');\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tinfo = result.finish\n\t\t\t\t\t\t\t\t\t\t? null\n\t\t\t\t\t\t\t\t\t\t: result.resolved === false\n\t\t\t\t\t\t\t\t\t\t? h('div', { className: 'result-info unresolved' }, '等待顺序答题中... ⏱️')\n\t\t\t\t\t\t\t\t\t\t: h('div', { className: 'result-info error' }, '❌ 此题未完成, 可能是没有匹配的选项。');\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\treturn h('div', [\n\t\t\t\t\t\t\t\t\th('div', { className: 'alert-info-wrapper' }, [info ?? h('div')]),\n\t\t\t\t\t\t\t\t\th(SearchInfosElement, {\n\t\t\t\t\t\t\t\t\t\tinfos: result.searchInfos,\n\t\t\t\t\t\t\t\t\t\tquestion: result.question,\n\t\t\t\t\t\t\t\t\t\ttype: result.type\n\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t]);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\treturn h('div', 'undefined');\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\trender();\n\t\t\t\t\t\tthis.onConfigChange('type', render);\n\t\t\t\t\t\tthis.onConfigChange('requestedCount', render);\n\t\t\t\t\t\tthis.onConfigChange('resolvedCount', render);\n\t\t\t\t\t\t$store.addChangeListener(TAB_WORK_RESULTS_KEY, render);\n\n\t\t\t\t\t\treturn container;\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t},\n\t\t\tonrender({ panel }) {\n\t\t\t\tpanel.body.replaceChildren(this.methods.createWorkResultsPanel());\n\t\t\t}\n\t\t}),\n\t\tonlineSearch: new Script({\n\t\t\tname: '🔎 在线搜题',\n\t\t\tmatches: [['所有页面', /.*/]],\n\t\t\tnamespace: 'common.online-search',\n\t\t\tconfigs: {\n\t\t\t\tnotes: {\n\t\t\t\t\tdefaultValue: '查题前请在 “通用-全局设置” 中设置题库配置，才能进行在线搜题。'\n\t\t\t\t},\n\n\t\t\t\tselectSearch: {\n\t\t\t\t\tlabel: '划词搜索',\n\t\t\t\t\tdefaultValue: true,\n\t\t\t\t\tattrs: { type: 'checkbox', title: '使用鼠标滑动选择页面中的题目进行搜索。' }\n\t\t\t\t},\n\t\t\t\tsearchValue: {\n\t\t\t\t\tsync: true,\n\t\t\t\t\tlabel: '搜索题目',\n\t\t\t\t\ttag: 'textarea',\n\t\t\t\t\tattrs: {\n\t\t\t\t\t\tplaceholder: '输入题目，请尽量保证题目完整，不要漏字',\n\t\t\t\t\t\tstyle: {\n\t\t\t\t\t\t\tminWidth: '300px',\n\t\t\t\t\t\t\tminHeight: '64px'\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\tdefaultValue: ''\n\t\t\t\t}\n\t\t\t},\n\t\t\toncomplete() {\n\t\t\t\tdocument.addEventListener(\n\t\t\t\t\t'selectionchange',\n\t\t\t\t\tdebounce(() => {\n\t\t\t\t\t\tif (this.cfg.selectSearch) {\n\t\t\t\t\t\t\tconst val = document.getSelection()?.toString() || '';\n\t\t\t\t\t\t\tif (val) {\n\t\t\t\t\t\t\t\tthis.cfg.searchValue = val;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}, 500)\n\t\t\t\t);\n\t\t\t},\n\t\t\tonrender({ panel }) {\n\t\t\t\tconst content = h('div', '', (content) => {\n\t\t\t\t\tcontent.style.marginBottom = '12px';\n\t\t\t\t});\n\n\t\t\t\tconst search = async (value: string) => {\n\t\t\t\t\tif (CommonProject.scripts.settings.cfg.answererWrappers.length === 0) {\n\t\t\t\t\t\t$modal.alert({ content: '请先在 通用-全局设置 配置题库，才能进行在线搜题。' });\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tcontent.replaceChildren(h('span', '搜索中...'));\n\n\t\t\t\t\tif (value) {\n\t\t\t\t\t\tconst t = Date.now();\n\t\t\t\t\t\tconst infos = await defaultAnswerWrapperHandler(CommonProject.scripts.settings.cfg.answererWrappers, {\n\t\t\t\t\t\t\ttitle: value\n\t\t\t\t\t\t});\n\t\t\t\t\t\t// 耗时计算\n\t\t\t\t\t\tconst resume = ((Date.now() - t) / 1000).toFixed(2);\n\n\t\t\t\t\t\tcontent.replaceChildren(\n\t\t\t\t\t\t\th(\n\t\t\t\t\t\t\t\t'div',\n\t\t\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t\th('hr'),\n\t\t\t\t\t\t\t\t\th(\n\t\t\t\t\t\t\t\t\t\t'div',\n\t\t\t\t\t\t\t\t\t\t{ style: { color: '#a1a1a1' } },\n\t\t\t\t\t\t\t\t\t\t`搜索到 ${infos.map((i) => i.results).flat().length} 个结果，共耗时 ${resume} 秒`\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\th(SearchInfosElement, {\n\t\t\t\t\t\t\t\t\t\tinfos: infos.map((info) => ({\n\t\t\t\t\t\t\t\t\t\t\tresults: info.results.map(\n\t\t\t\t\t\t\t\t\t\t\t\t(res) => [res.question, res.answer, res.extra_data] as [string, string, object]\n\t\t\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t\t\t\thomepage: info.homepage,\n\t\t\t\t\t\t\t\t\t\t\tname: info.name\n\t\t\t\t\t\t\t\t\t\t})),\n\t\t\t\t\t\t\t\t\t\tquestion: value\n\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t(div) => {\n\t\t\t\t\t\t\t\t\tdiv.classList.add('card');\n\t\t\t\t\t\t\t\t\tdiv.style.width = '480px';\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tcontent.replaceChildren(h('span', '题目不能为空！'));\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\tconst button = h('button', '搜索', (button) => {\n\t\t\t\t\tbutton.className = 'base-style-button';\n\t\t\t\t\tbutton.style.width = '120px';\n\t\t\t\t\tbutton.onclick = () => {\n\t\t\t\t\t\tsearch(this.cfg.searchValue);\n\t\t\t\t\t};\n\t\t\t\t});\n\t\t\t\tconst searchContainer = h('div', { style: { textAlign: 'end' } }, [button]);\n\n\t\t\t\tpanel.body.append(h('div', [content, searchContainer]));\n\t\t\t}\n\t\t}),\n\t\t/** 渲染脚本，窗口渲染主要脚本 */\n\t\trender: RenderScript,\n\t\thack: new Script({\n\t\t\tname: '页面复制粘贴限制解除',\n\t\t\tmatches: [['所有页面', /.*/]],\n\t\t\thideInPanel: true,\n\t\t\tonactive() {\n\t\t\t\tenableCopy([document, document.body]);\n\t\t\t},\n\t\t\toncomplete() {\n\t\t\t\tenableCopy([document, document.body]);\n\t\t\t\tinsertCopyableStyle();\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\tenableCopy([document, document.body]);\n\t\t\t\t\tinsertCopyableStyle();\n\t\t\t\t}, 3000);\n\t\t\t}\n\t\t}),\n\t\tdisableDialog: new Script({\n\t\t\tname: '禁止弹窗',\n\t\t\tmatches: [['所有页面', /.*/]],\n\t\t\thideInPanel: true,\n\t\t\tpriority: 1,\n\t\t\tonstart() {\n\t\t\t\tfunction disableDialog(msg: string) {\n\t\t\t\t\t$modal.alert({\n\t\t\t\t\t\tprofile: '弹窗来自：' + location.origin,\n\t\t\t\t\t\tcontent: msg\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\ttry {\n\t\t\t\t\t$gm.unsafeWindow.alert = disableDialog;\n\t\t\t\t\twindow.alert = disableDialog;\n\t\t\t\t} catch (e) {\n\t\t\t\t\tconsole.error(e);\n\t\t\t\t}\n\t\t\t}\n\t\t}),\n\t\tapps: new Script({\n\t\t\tname: '📱 拓展应用',\n\t\t\tmatches: [['', /.*/]],\n\t\t\tnamespace: 'common.apps',\n\t\t\tconfigs: {\n\t\t\t\tnotes: {\n\t\t\t\t\tdefaultValue: '这里是一些其他的应用或者拓展功能。'\n\t\t\t\t},\n\t\t\t\t/**\n\t\t\t\t * 题库缓存\n\t\t\t\t */\n\t\t\t\tlocalQuestionCaches: {\n\t\t\t\t\tdefaultValue: [] as QuestionCache[],\n\t\t\t\t\textra: {\n\t\t\t\t\t\tappConfigSync: false\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\tmethods() {\n\t\t\t\treturn {\n\t\t\t\t\t/**\n\t\t\t\t\t * 添加题库缓存\n\t\t\t\t\t */\n\t\t\t\t\taddQuestionCache: async (...questionCacheItems: QuestionCache[]) => {\n\t\t\t\t\t\tconst questionCaches: QuestionCache[] = this.cfg.localQuestionCaches;\n\t\t\t\t\t\tfor (const item of questionCacheItems) {\n\t\t\t\t\t\t\t// 去重\n\t\t\t\t\t\t\tif (questionCaches.find((c) => c.title === item.title && c.answer === item.answer) === undefined) {\n\t\t\t\t\t\t\t\tquestionCaches.unshift(item);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// 限制数量\n\t\t\t\t\t\tquestionCaches.splice(200);\n\t\t\t\t\t\tthis.cfg.localQuestionCaches = questionCaches;\n\t\t\t\t\t},\n\t\t\t\t\taddQuestionCacheFromWorkResult(swr: SimplifyWorkResult[]) {\n\t\t\t\t\t\tCommonProject.scripts.apps.methods.addQuestionCache(\n\t\t\t\t\t\t\t...swr\n\t\t\t\t\t\t\t\t.map((r) =>\n\t\t\t\t\t\t\t\t\tr.searchInfos\n\t\t\t\t\t\t\t\t\t\t.map((i) =>\n\t\t\t\t\t\t\t\t\t\t\ti.results\n\t\t\t\t\t\t\t\t\t\t\t\t.filter((res) => res[1])\n\t\t\t\t\t\t\t\t\t\t\t\t.map((res) => ({\n\t\t\t\t\t\t\t\t\t\t\t\t\ttitle: r.question,\n\t\t\t\t\t\t\t\t\t\t\t\t\tanswer: res[1],\n\t\t\t\t\t\t\t\t\t\t\t\t\tfrom: i.name.replace(/【题库缓存】/g, ''),\n\t\t\t\t\t\t\t\t\t\t\t\t\thomepage: i.homepage || ''\n\t\t\t\t\t\t\t\t\t\t\t\t}))\n\t\t\t\t\t\t\t\t\t\t\t\t.flat()\n\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\t.flat()\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t.flat()\n\t\t\t\t\t\t);\n\t\t\t\t\t},\n\t\t\t\t\t/**\n\t\t\t\t\t * 将题库缓存作为题库并进行题目搜索\n\t\t\t\t\t * @param title 题目\n\t\t\t\t\t * @param whenSearchEmpty 当搜索结果为空，或者题库缓存功能被关闭时执行的函数\n\t\t\t\t\t */\n\t\t\t\t\tsearchAnswerInCaches: async (\n\t\t\t\t\t\ttitle: string,\n\t\t\t\t\t\twhenSearchEmpty: () => SearchInformation[] | Promise<SearchInformation[]>\n\t\t\t\t\t): Promise<SearchInformation[]> => {\n\t\t\t\t\t\tif (CommonProject.scripts.settings.cfg.enableQuestionCaches === false) {\n\t\t\t\t\t\t\treturn await whenSearchEmpty();\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tlet results: SearchInformation[] = [];\n\t\t\t\t\t\tconst caches = this.cfg.localQuestionCaches;\n\t\t\t\t\t\tfor (const cache of caches) {\n\t\t\t\t\t\t\tif (cache.title.trim() === title.trim()) {\n\t\t\t\t\t\t\t\tresults.push({\n\t\t\t\t\t\t\t\t\tname: cache.from,\n\t\t\t\t\t\t\t\t\thomepage: cache.homepage,\n\t\t\t\t\t\t\t\t\tresults: [{ answer: cache.answer, question: cache.title, extra_data: { ai: cache.ai, cache: true } }]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (results.length === 0) {\n\t\t\t\t\t\t\tresults = await whenSearchEmpty();\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn results;\n\t\t\t\t\t},\n\t\t\t\t\t/**\n\t\t\t\t\t * 查看更新日志\n\t\t\t\t\t */\n\t\t\t\t\tasync showChangelog() {\n\t\t\t\t\t\tconst changelog = h('div', {\n\t\t\t\t\t\t\tclassName: 'markdown card',\n\t\t\t\t\t\t\tinnerHTML: '加载中...',\n\t\t\t\t\t\t\tstyle: { maxWidth: '600px' }\n\t\t\t\t\t\t});\n\t\t\t\t\t\t$modal.simple({\n\t\t\t\t\t\t\twidth: 600,\n\t\t\t\t\t\t\tcontent: h('div', [\n\t\t\t\t\t\t\t\th('div', { className: 'notes card' }, [\n\t\t\t\t\t\t\t\t\t$ui.notes(['此页面实时更新，遇到问题可以查看最新版本是否修复。'])\n\t\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t\t\tchangelog\n\t\t\t\t\t\t\t])\n\t\t\t\t\t\t});\n\t\t\t\t\t\tconst md = await request('https://cdn.ocsjs.com/articles/ocs/changelog.md?t=' + Date.now(), {\n\t\t\t\t\t\t\ttype: 'GM_xmlhttpRequest',\n\t\t\t\t\t\t\tresponseType: 'text',\n\t\t\t\t\t\t\tmethod: 'get'\n\t\t\t\t\t\t});\n\t\t\t\t\t\tchangelog.innerHTML = markdown(md);\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t},\n\t\t\tonrender({ panel }) {\n\t\t\t\tconst btnStyle: Partial<CSSStyleDeclaration> = {\n\t\t\t\t\tpadding: '6px 12px',\n\t\t\t\t\tmargin: '4px',\n\t\t\t\t\tmarginBottom: '8px',\n\t\t\t\t\tboxShadow: '0px 0px 4px #bebebe',\n\t\t\t\t\tborderRadius: '8px',\n\t\t\t\t\tcursor: 'pointer'\n\t\t\t\t};\n\n\t\t\t\tconst cachesBtn = h('div', { innerText: '💾 题库缓存', style: btnStyle }, (btn) => {\n\t\t\t\t\tbtn.onclick = () => {\n\t\t\t\t\t\tconst questionCaches = this.cfg.localQuestionCaches;\n\n\t\t\t\t\t\tconst list = questionCaches.map((c) =>\n\t\t\t\t\t\t\th(\n\t\t\t\t\t\t\t\t'div',\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tclassName: 'question-cache',\n\t\t\t\t\t\t\t\t\tstyle: {\n\t\t\t\t\t\t\t\t\t\tmargin: '8px',\n\t\t\t\t\t\t\t\t\t\tborder: '1px solid lightgray',\n\t\t\t\t\t\t\t\t\t\tborderRadius: '4px',\n\t\t\t\t\t\t\t\t\t\tpadding: '8px'\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t\th('div', { className: 'title' }, [\n\t\t\t\t\t\t\t\t\t\t$ui.tooltip(\n\t\t\t\t\t\t\t\t\t\t\th(\n\t\t\t\t\t\t\t\t\t\t\t\t'span',\n\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\ttitle: `来自：${c.from || '未知题库'}\\n主页：${c.homepage || '未知主页'}`,\n\t\t\t\t\t\t\t\t\t\t\t\t\tstyle: { fontWeight: 'bold' }\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\tc.title\n\t\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t\t\t\th('div', { className: 'answer', style: { marginTop: '6px' } }, c.answer)\n\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\tconst countEl = h('span', ['当前缓存数量：' + questionCaches.length]);\n\n\t\t\t\t\t\t$modal.simple({\n\t\t\t\t\t\t\twidth: 800,\n\t\t\t\t\t\t\tcontent: h('div', [\n\t\t\t\t\t\t\t\th('div', { className: 'notes card' }, [\n\t\t\t\t\t\t\t\t\t$ui.notes([\n\t\t\t\t\t\t\t\t\t\t'题库缓存是将题库的题目和答案保存在内存，在重复使用时可以直接从内存获取，不需要再次请求题库。',\n\t\t\t\t\t\t\t\t\t\t'以下是当前存储的题库，默认存储200题，当前页面关闭后会自动清除。'\n\t\t\t\t\t\t\t\t\t])\n\t\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t\t\th('div', { className: 'card' }, [\n\t\t\t\t\t\t\t\t\t$ui.space(\n\t\t\t\t\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t\t\t\tcountEl,\n\t\t\t\t\t\t\t\t\t\t\t$ui.button('清空题库缓存', {}, (btn) => {\n\t\t\t\t\t\t\t\t\t\t\t\tbtn.onclick = () => {\n\t\t\t\t\t\t\t\t\t\t\t\t\tthis.cfg.localQuestionCaches = [];\n\t\t\t\t\t\t\t\t\t\t\t\t\tcountEl.innerText = '当前缓存数量：0';\n\t\t\t\t\t\t\t\t\t\t\t\t\tlist.forEach((el) => el.remove());\n\t\t\t\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\t{ separator: '|' }\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t]),\n\n\t\t\t\t\t\t\t\th(\n\t\t\t\t\t\t\t\t\t'div',\n\t\t\t\t\t\t\t\t\tquestionCaches.length === 0 ? [h('div', { style: { textAlign: 'center' } }, '暂无题库缓存')] : list\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t])\n\t\t\t\t\t\t});\n\t\t\t\t\t};\n\t\t\t\t});\n\n\t\t\t\tconst exportSetting = $ui.tooltip(\n\t\t\t\t\th(\n\t\t\t\t\t\t'div',\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tinnerText: '📤 导出全部设置',\n\t\t\t\t\t\t\tstyle: btnStyle,\n\t\t\t\t\t\t\ttitle: '导出全部页面的设置，包括全局设置，题库配置，学习设置等等。（文件后缀名为：.ocssetting）'\n\t\t\t\t\t\t},\n\t\t\t\t\t\t(btn) => {\n\t\t\t\t\t\t\tbtn.onclick = () => {\n\t\t\t\t\t\t\t\tconst setting = Object.create({});\n\t\t\t\t\t\t\t\tfor (const key of $store.list()) {\n\t\t\t\t\t\t\t\t\tconst val = $store.get(key);\n\t\t\t\t\t\t\t\t\tif (val) {\n\t\t\t\t\t\t\t\t\t\tReflect.set(setting, key, val);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tconst blob = new Blob([JSON.stringify(setting, null, 2)], { type: 'text/plain' });\n\t\t\t\t\t\t\t\tconst url = URL.createObjectURL(blob);\n\t\t\t\t\t\t\t\tconst a = h('a', { href: url, download: 'ocs-setting-export.ocssetting' });\n\t\t\t\t\t\t\t\ta.click();\n\t\t\t\t\t\t\t\tURL.revokeObjectURL(url);\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t)\n\t\t\t\t);\n\n\t\t\t\tconst importSetting = $ui.tooltip(\n\t\t\t\t\th(\n\t\t\t\t\t\t'div',\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tinnerText: '📥 导入全部设置',\n\t\t\t\t\t\t\tstyle: btnStyle,\n\t\t\t\t\t\t\ttitle: '导入并且覆盖当前的全部设置。（文件后缀名为：.ocssetting）'\n\t\t\t\t\t\t},\n\t\t\t\t\t\t(btn) => {\n\t\t\t\t\t\t\tbtn.onclick = () => {\n\t\t\t\t\t\t\t\tconst input = h('input', { type: 'file', accept: '.ocssetting' });\n\t\t\t\t\t\t\t\tinput.onchange = async () => {\n\t\t\t\t\t\t\t\t\tconst file = input.files?.[0];\n\t\t\t\t\t\t\t\t\tif (file) {\n\t\t\t\t\t\t\t\t\t\tconst setting = await file.text();\n\t\t\t\t\t\t\t\t\t\tconst obj = JSON.parse(setting);\n\t\t\t\t\t\t\t\t\t\tfor (const key of Object.keys(obj)) {\n\t\t\t\t\t\t\t\t\t\t\t$store.set(key, obj[key]);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t$message.success({ content: '设置导入成功，页面即将刷新。', duration: 3 });\n\t\t\t\t\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\t\t\t\t\tlocation.reload();\n\t\t\t\t\t\t\t\t\t\t}, 3000);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\tinput.click();\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t)\n\t\t\t\t);\n\n\t\t\t\t[cachesBtn, exportSetting, importSetting].forEach((btn) => {\n\t\t\t\t\tbtn.onmouseover = () => {\n\t\t\t\t\t\tbtn.style.boxShadow = '0px 0px 4px #0099ff9c';\n\t\t\t\t\t};\n\t\t\t\t\tbtn.onmouseout = () => {\n\t\t\t\t\t\tbtn.style.boxShadow = '0px 0px 4px #bebebe';\n\t\t\t\t\t};\n\t\t\t\t});\n\n\t\t\t\tconst sep = (text: string) => h('div', { className: 'separator', style: { padding: '4px 0px' } }, text);\n\n\t\t\t\tpanel.body.replaceChildren(\n\t\t\t\t\th('div', [sep('题库拓展'), cachesBtn, sep('其他功能'), exportSetting, importSetting])\n\t\t\t\t);\n\t\t\t}\n\t\t})\n\t}\n});\n\nfunction insertCopyableStyle() {\n\tconst style = document.createElement('style');\n\tstyle.innerHTML = `\n\t\thtml * {\n\t\t  -webkit-user-select: text !important;\n\t\t  -khtml-user-select: text !important;\n\t\t  -moz-user-select: text !important;\n\t\t  -ms-user-select: text !important;\n\t\t  user-select: text !important;\n\t\t}`;\n\n\tdocument.head.append(style);\n}\n\nfunction createAnswererWrapperList(aw: AnswererWrapper[]) {\n\treturn aw.map((item) =>\n\t\th(\n\t\t\t'details',\n\t\t\t[\n\t\t\t\th('summary', [\n\t\t\t\t\t$ui.space([\n\t\t\t\t\t\t(() => {\n\t\t\t\t\t\t\tlet isDisabled = CommonProject.scripts.settings.cfg.disabledAnswererWrapperNames.includes(item.name);\n\n\t\t\t\t\t\t\tconst checkbox = h('input', { type: 'checkbox', checked: !isDisabled, className: 'base-style-switch' });\n\n\t\t\t\t\t\t\tcheckbox.onclick = () => {\n\t\t\t\t\t\t\t\tisDisabled = !isDisabled;\n\t\t\t\t\t\t\t\tif (isDisabled) {\n\t\t\t\t\t\t\t\t\tCommonProject.scripts.settings.cfg.disabledAnswererWrapperNames = [\n\t\t\t\t\t\t\t\t\t\t...CommonProject.scripts.settings.cfg.disabledAnswererWrapperNames,\n\t\t\t\t\t\t\t\t\t\titem.name\n\t\t\t\t\t\t\t\t\t];\n\t\t\t\t\t\t\t\t\t$message.warn({\n\t\t\t\t\t\t\t\t\t\tcontent: '题库：' + item.name + ' 已被停用，如需开启请在：通用-全局设置-题库配置中开启。',\n\t\t\t\t\t\t\t\t\t\tduration: 30\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tCommonProject.scripts.settings.cfg.disabledAnswererWrapperNames =\n\t\t\t\t\t\t\t\t\t\tCommonProject.scripts.settings.cfg.disabledAnswererWrapperNames.filter(\n\t\t\t\t\t\t\t\t\t\t\t(name) => name !== item.name\n\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t$message.success({\n\t\t\t\t\t\t\t\t\t\tcontent: '题库：' + item.name + ' 已启用。',\n\t\t\t\t\t\t\t\t\t\tduration: 3\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\tcheckbox.title = '点击停用或者启用题库，停用题库后将无法在自动答题中查询题目';\n\n\t\t\t\t\t\t\treturn $ui.tooltip(checkbox);\n\t\t\t\t\t\t})(),\n\t\t\t\t\t\th('span', item.name)\n\t\t\t\t\t])\n\t\t\t\t]),\n\t\t\t\th('ul', [\n\t\t\t\t\th('li', ['名字\\t', item.name]),\n\t\t\t\t\th('li', { innerHTML: `官网\\t<a target=\"_blank\" href=${item.homepage}>${item.homepage || '无'}</a>` }),\n\t\t\t\t\th('li', ['接口\\t', item.url]),\n\t\t\t\t\th('li', ['请求方法\\t', item.method]),\n\t\t\t\t\th('li', ['请求类型\\t', item.type]),\n\t\t\t\t\th('li', ['请求头\\t', JSON.stringify(item.headers, null, 4) || '无']),\n\t\t\t\t\th('li', ['请求体\\t', JSON.stringify(item.data, null, 4) || '无'])\n\t\t\t\t])\n\t\t\t],\n\t\t\t(details) => {\n\t\t\t\tdetails.style.paddingLeft = '12px';\n\t\t\t}\n\t\t)\n\t);\n}\n\nconst createGuide = () => {\n\tconst showProjectDetails = (project: Project) => {\n\t\t$modal.simple({\n\t\t\ttitle: project.name,\n\t\t\twidth: 800,\n\t\t\tcontent: h('div', [\n\t\t\t\th('div', [\n\t\t\t\t\t'运行域名：',\n\t\t\t\t\t...(project.domains || []).map((d) =>\n\t\t\t\t\t\th(\n\t\t\t\t\t\t\t'a',\n\t\t\t\t\t\t\t{ href: d.startsWith('http') ? d : 'https://' + d, target: '_blank', style: { margin: '0px 4px' } },\n\t\t\t\t\t\t\td\n\t\t\t\t\t\t)\n\t\t\t\t\t)\n\t\t\t\t]),\n\t\t\t\th('div', '脚本列表：'),\n\t\t\t\th(\n\t\t\t\t\t'ul',\n\t\t\t\t\tObject.keys(project.scripts)\n\t\t\t\t\t\t.sort((a, b) => (project.scripts[b].hideInPanel ? -1 : 1))\n\t\t\t\t\t\t.map((key) => {\n\t\t\t\t\t\t\tconst script = project.scripts[key];\n\t\t\t\t\t\t\treturn h(\n\t\t\t\t\t\t\t\t'li',\n\t\t\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t\th('b', script.name),\n\t\t\t\t\t\t\t\t\t$ui.notes([\n\t\t\t\t\t\t\t\t\t\th('span', ['操作面板：', script.hideInPanel ? '隐藏' : '显示']),\n\n\t\t\t\t\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t\t\t\t'运行页面：',\n\t\t\t\t\t\t\t\t\t\t\th(\n\t\t\t\t\t\t\t\t\t\t\t\t'ul',\n\t\t\t\t\t\t\t\t\t\t\t\tscript.matches\n\t\t\t\t\t\t\t\t\t\t\t\t\t.map((m) => (Array.isArray(m) ? m : (['无描述', m] as [string, string | RegExp])))\n\t\t\t\t\t\t\t\t\t\t\t\t\t.map((i) =>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\th('li', [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ti[0],\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t'：',\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ti[1] instanceof RegExp ? i[1].toString().replace(/\\\\/g, '').slice(1, -1) : h('span', i[1])\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t])\n\t\t\t\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t\t])\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t(li) => {\n\t\t\t\t\t\t\t\t\tli.style.marginBottom = '12px';\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}),\n\t\t\t\t\t(ul) => {\n\t\t\t\t\t\tul.style.padding = '12px 24px';\n\t\t\t\t\t\tul.style.border = '1px solid #e1e1e1';\n\t\t\t\t\t\tul.style.borderRadius = '4px';\n\t\t\t\t\t\tul.style.maxHeight = '400px';\n\t\t\t\t\t\tul.style.overflow = 'auto';\n\t\t\t\t\t\tul.style.paddingLeft = '42px';\n\t\t\t\t\t}\n\t\t\t\t)\n\t\t\t])\n\t\t});\n\t};\n\n\tconst gotoHome = h('button', { className: 'base-style-button-secondary' }, '🏡官网教程');\n\tgotoHome.onclick = () => window.open('https://docs.ocsjs.com', '_blank');\n\n\tconst contactUs = h('button', { className: 'base-style-button-secondary' }, '🗨️交流群');\n\tcontactUs.onclick = () => window.open('https://docs.ocsjs.com/docs/about#交流方式', '_blank');\n\n\tconst changeLog = h('button', { className: 'base-style-button-secondary' }, '📄更新日志');\n\tchangeLog.onclick = () => CommonProject.scripts.apps.methods.showChangelog();\n\n\tconst cardStyle: Partial<CSSStyleDeclaration> = {\n\t\tborder: '1px solid #eee',\n\t\tborderRadius: '4px',\n\t\tpadding: '8px',\n\t\tpaddingTop: '4px'\n\t};\n\n\treturn h('div', { className: 'user-guide' }, [\n\t\th('div', { style: cardStyle }, [\n\t\t\th('div', { style: { marginBottom: '4px', fontWeight: 'bold' } }, [\n\t\t\t\t'✨兼容的网课平台：',\n\t\t\t\th('span', { className: 'secondary', style: { fontWeight: 'normal' } }, '（未适配的平台将无法运行，请等待适配）')\n\t\t\t]),\n\n\t\t\th('div', [\n\t\t\t\t...[CXProject, ZHSProject, ZJYProject, IcveMoocProject, ICourseProject].map((project) => {\n\t\t\t\t\tconst btn = h('button', { className: 'base-style-button-secondary', style: { margin: '4px' } }, [\n\t\t\t\t\t\tproject.name\n\t\t\t\t\t]);\n\t\t\t\t\tbtn.onclick = () => {\n\t\t\t\t\t\tshowProjectDetails(project);\n\t\t\t\t\t};\n\t\t\t\t\treturn btn;\n\t\t\t\t})\n\t\t\t])\n\t\t]),\n\t\th('div', { style: { ...cardStyle, marginTop: '12px' } }, [\n\t\t\th('div', { style: { marginBottom: '8px', fontWeight: 'bold' } }, '🌐快捷访问：'),\n\t\t\tgotoHome,\n\t\t\tcontactUs,\n\t\t\tchangeLog\n\t\t])\n\t]);\n};\n"
  },
  {
    "path": "packages/scripts/src/projects/cx.ts",
    "content": "/** global Ext videojs getTeacherAjax jobs */\n\nimport {\n\tOCSWorker,\n\tdefaultAnswerWrapperHandler,\n\t$,\n\tStringUtils,\n\trequest,\n\tcreateDefaultQuestionResolver,\n\tDefaultWork,\n\tsplitAnswer,\n\tdomSearch,\n\tdomSearchAll,\n\tSearchInformation\n} from '@ocsjs/core';\nimport { $modal, h, $store, MessageElement, Project, Script, $el, $gm, $$el, $ui, cors, $message } from 'easy-us';\n\nimport { CommonProject } from './common';\nimport { workNotes, volume, playbackRate, dropdownStyle } from '../utils/configs';\nimport {\n\tanswerWrapperEmptyWarning,\n\tcommonWork,\n\toptimizationElementWithImage,\n\tremoveRedundantWords,\n\tsimplifyWorkResult\n} from '../utils/work';\nimport md5 from 'md5';\n// @ts-ignore\nimport Typr from 'typr.js';\nimport { $console, BackgroundProject } from './background';\nimport { CommonWorkOptions, playMedia } from '../utils';\nimport { waitForElement, waitForMedia } from '../utils/study';\n\n// @ts-ignore\nlet top: Window = globalThis.top;\n\ntry {\n\t/**\n\t *\n\t *  将繁体字映射载入内存。\n\t *  为什么不存 localStorage 和 GM_setValue\n\t *  localStorage: 存在被检测风险，谁都能访问\n\t *  GM_setValue: 文件太大影响I/O速度\n\t */\n\t// @ts-ignore\n\ttop.typrMapping = top.typrMapping || undefined;\n\n\t// @ts-ignore 任务点\n\ttop.jobs = top.jobs || [];\n\n\t// @ts-ignore 当前视频\n\ttop.currentMedia = top.currentMedia || undefined;\n\n\t// 加 try 是因为跨域面板无法操作\n} catch {}\n\nconst state = {\n\tstudy: {\n\t\tvideojs: Object.create({}),\n\t\thacked: false,\n\t\tanswererWrapperUnsetMessage: undefined as MessageElement | undefined,\n\t\tplaybackRateWarningListenerId: 0\n\t}\n};\n\ntype VideoQuizStrategy = 'random' | 'ignore';\n\ntype Attachment = {\n\t/** 只有当 module 为 音视频（并且已经播放完成）时才会有这个属性 */\n\tisPassed: boolean | undefined;\n\t/** 是否为任务点（音视频播放完成后此属性不存在） */\n\tjob: boolean | undefined;\n\t/** 这里注意，如果当前章节测试不是任务点，则没有 jobid 或者未空字符串 */\n\tjobid?: string;\n\tproperty: {\n\t\tmid: string;\n\t\t/** 任务点id，固定存在 */\n\t\t_jobid: string;\n\t\tmodule: 'insertbook' | 'insertdoc' | 'insertflash' | 'work' | 'insertaudio' | 'insertvideo';\n\t\tname?: string;\n\t\tauthor?: string;\n\t\tbookname?: string;\n\t\tpublisher?: string;\n\t\ttitle?: string;\n\t};\n};\n\ntype Job = {\n\tmid: string;\n\tattachment: Attachment;\n\tfunc: { (): Promise<void> } | undefined;\n};\nexport const CXProject = Project.create({\n\tname: '超星学习通',\n\tdomains: [\n\t\t'chaoxing.com',\n\t\t'edu.cn',\n\t\t'org.cn',\n\t\t// 学银在线\n\t\t'xueyinonline.com',\n\t\t/** 其他域名 */\n\t\t'hnsyu.net',\n\t\t'qutjxjy.cn',\n\t\t'ynny.cn',\n\t\t'hnvist.cn',\n\t\t'fjlecb.cn',\n\t\t'gdhkmooc.com',\n\t\t'cugbonline.cn',\n\t\t'zjelib.cn',\n\t\t'cqrspx.cn',\n\t\t'neauce.com',\n\t\t'zhihui-yun.com',\n\t\t'cqie.cn',\n\t\t'ccqmxx.com',\n\t\t'jxgmxy.com',\n\t\t'jnzyjsxy.cn',\n\t\t// 超星学习通PPT，2025下半年更新的PTT图书新域名\n\t\t'sslibrary.com'\n\t],\n\tscripts: {\n\t\t/**\n\t\t * 创建超星独立脚本防止污染其他脚本环境\n\t\t */\n\t\tenv: new Script({\n\t\t\tname: '环境准备脚本',\n\t\t\tmatches: [['所有页面', /.*/]],\n\t\t\thideInPanel: true,\n\t\t\tonstart() {\n\t\t\t\t/**\n\t\t\t\t * 于 4.9.20 后更新，出现顶层套壳页面跨域 :\n\t\t\t\t * top : zjelib.cn <body>\n\t\t\t\t * iframe : mooc1.xxx.zjelib.cn/.../mycourse/studentstudy/...  <iframe src=....>\n\t\t\t\t * 导致top指向zjelib跨域无法访问，所以这里尝试寻找真正的top窗口对象，只有域名中包含 /mycourse/studentstudy 才是可操作的 top\n\t\t\t\t */\n\t\t\t\ttry {\n\t\t\t\t\tlet _self = self;\n\t\t\t\t\tlet _try_count = 10;\n\t\t\t\t\twhile (_self.parent !== undefined && _try_count > 0) {\n\t\t\t\t\t\tif (_self.location.href.includes('/mycourse/studentstudy')) {\n\t\t\t\t\t\t\ttop = _self;\n\t\t\t\t\t\t\tconsole.log('[ocsjs] top change to ' + top.location.href);\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t_try_count--;\n\t\t\t\t\t\t\t// @ts-ignore\n\t\t\t\t\t\t\t_self = _self.parent;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} catch (e) {\n\t\t\t\t\tconsole.warn('[ocsjs] fail of find top');\n\t\t\t\t\tconsole.warn(e);\n\t\t\t\t\t// @ts-ignore\n\t\t\t\t\ttop = globalThis.top;\n\t\t\t\t}\n\t\t\t}\n\t\t}),\n\t\tguide: new Script({\n\t\t\tname: '💡 使用提示',\n\t\t\tmatches: [\n\t\t\t\t['首页', 'https://www.chaoxing.com'],\n\t\t\t\t['旧版个人首页', 'chaoxing.com/space/index'],\n\t\t\t\t['新版个人首页', 'chaoxing.com/base'],\n\t\t\t\t['学习页面', 'chaoxing.com/mycourse'],\n\t\t\t\t['新版学习页面', 'chaoxing.com/mooc2-ans/mycourse']\n\t\t\t],\n\t\t\tnamespace: 'cx.guide',\n\t\t\tconfigs: {\n\t\t\t\tnotes: {\n\t\t\t\t\tdefaultValue: `请手动进入视频、作业、考试页面，脚本会自动运行。`\n\t\t\t\t}\n\t\t\t},\n\t\t\toncomplete() {\n\t\t\t\tif (['mycourse/studentstudy'].some((path) => location.href.includes(path))) {\n\t\t\t\t\t$message.success('已进入学习页面，请等待自动运行...');\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t$message.info('请手动进入视频、作业、考试页面，脚本会自动运行。');\n\t\t\t}\n\t\t}),\n\t\tstudy: new Script({\n\t\t\tname: '🖥️ 课程学习',\n\t\t\tnamespace: 'cx.new.study',\n\t\t\tmatches: [\n\t\t\t\t['任务点页面', '/knowledge/cards'],\n\t\t\t\t['阅读任务点', '/readsvr/book/mooc']\n\t\t\t\t// 旧版浏览器好像不能识别二级 iframe ， 所以不能使用 'work/doHomeWorkNew' 以及其他二级 iframe 来触发路由\n\t\t\t],\n\t\t\tconfigs: {\n\t\t\t\tnotes: {\n\t\t\t\t\tdefaultValue: $ui.notes([\n\t\t\t\t\t\t['任务点不是顺序执行，如果某一个任务没有动', '请查看是否有其他任务正在学习，耐心等待即可。'],\n\t\t\t\t\t\t'闯关模式请注意题库如果没完成，需要自己完成才能解锁章节。',\n\t\t\t\t\t\t'请勿凌晨刷课，部分学校课程可能会清空进度。',\n\t\t\t\t\t\t['⚠️目前超星倍速风控严重，如果高倍速', '完成后被清空还原，请调到1-2倍速学习！']\n\t\t\t\t\t]).outerHTML\n\t\t\t\t},\n\t\t\t\tplaybackRate: playbackRate,\n\t\t\t\tvolume: volume,\n\t\t\t\tvideoQuizStrategy: {\n\t\t\t\t\tlabel: '视频内题目',\n\t\t\t\t\ttag: 'select',\n\t\t\t\t\toptions: [\n\t\t\t\t\t\t['random', '随机答题'],\n\t\t\t\t\t\t['ignore', '忽略']\n\t\t\t\t\t],\n\t\t\t\t\tattrs: {\n\t\t\t\t\t\ttitle:\n\t\t\t\t\t\t\t'视频有时在学习过程中会弹出题目，这个好像并不计算在分数内，所以可以忽略，视频可以正常观看，这里提供几个方法处理题目'\n\t\t\t\t\t},\n\t\t\t\t\tdefaultValue: 'random' as VideoQuizStrategy\n\t\t\t\t},\n\t\t\t\tmode: {\n\t\t\t\t\tlabel: '跳转模式',\n\t\t\t\t\ttag: 'select',\n\t\t\t\t\toptions: [\n\t\t\t\t\t\t['next', '完成后跳转下一节', '完成小节后，自动点击下一节按钮'],\n\t\t\t\t\t\t['job', '完成后跳转未完成任务点', '如果未找到任务点，则会直接结束脚本运行，目前处于试验阶段。'],\n\t\t\t\t\t\t['manually', '完成后暂停，等待手动跳转', '适用于自己手动运行']\n\t\t\t\t\t],\n\t\t\t\t\tdefaultValue: 'next' as 'next' | 'job' | 'manually'\n\t\t\t\t},\n\t\t\t\trestudy: {\n\t\t\t\t\tlabel: '复习模式',\n\t\t\t\t\tattrs: { title: '已经完成的视频继续学习，并从当前的章节往下开始学习', type: 'checkbox' },\n\t\t\t\t\tdefaultValue: false\n\t\t\t\t},\n\t\t\t\tforceLearn: {\n\t\t\t\t\tlabel: '强制学习',\n\t\t\t\t\tattrs: {\n\t\t\t\t\t\ttitle: '视频一般分为：非任务点、任务点、和已完成任务点，当遇到“非任务点”时需要开启此选项才会进行学习',\n\t\t\t\t\t\ttype: 'checkbox'\n\t\t\t\t\t},\n\t\t\t\t\tdefaultValue: false\n\t\t\t\t},\n\t\t\t\tbackToFirstWhenFinish: {\n\t\t\t\t\tlabel: '完成全部后重新学习',\n\t\t\t\t\tattrs: {\n\t\t\t\t\t\ttype: 'checkbox',\n\t\t\t\t\t\ttitle: '当章节已经学习完成至最后一章时，跳转到第一个章节重新开始学习。'\n\t\t\t\t\t},\n\t\t\t\t\tdefaultValue: false\n\t\t\t\t},\n\t\t\t\tshowTextareaWhenEdit: {\n\t\t\t\t\tlabel: '编辑时显示自定义编辑框',\n\t\t\t\t\tattrs: {\n\t\t\t\t\t\ttype: 'checkbox',\n\t\t\t\t\t\ttitle:\n\t\t\t\t\t\t\t'超星默认禁止在编辑框中复制粘贴，开启此选项可以在文本框编辑时生成一个自定义编辑框进行编辑，脚本会将内容同步到编辑框中。'\n\t\t\t\t\t},\n\t\t\t\t\tdefaultValue: true\n\t\t\t\t},\n\t\t\t\tnotifyWhenHasFaceRecognition: {\n\t\t\t\t\tlabel: '出现人脸识别时通知我',\n\t\t\t\t\tattrs: {\n\t\t\t\t\t\ttype: 'checkbox'\n\t\t\t\t\t},\n\t\t\t\t\tdefaultValue: true\n\t\t\t\t},\n\t\t\t\tenables: {\n\t\t\t\t\t...dropdownStyle,\n\t\t\t\t\tlabel: '高级设置',\n\t\t\t\t\tattrs: { type: 'checkbox' },\n\t\t\t\t\tdefaultValue: false\n\t\t\t\t},\n\t\t\t\t/**\n\t\t\t\t *\n\t\t\t\t * 开启的任务点\n\t\t\t\t *\n\t\t\t\t * media : 音视频\n\t\t\t\t * ppt : 文档和书籍翻阅\n\t\t\t\t * test : 章节测试\n\t\t\t\t * read : 阅读\n\t\t\t\t * live : 直播课\n\t\t\t\t *\n\t\t\t\t */\n\t\t\t\tenableMedia: {\n\t\t\t\t\telementClassName: 'config-details',\n\t\t\t\t\tshowIf: 'cx.new.study.enables',\n\t\t\t\t\tlabel: '视频/音频自动播放',\n\t\t\t\t\tattrs: { type: 'checkbox', title: '开启：音频和视频的自动播放' },\n\t\t\t\t\tdefaultValue: true\n\t\t\t\t},\n\t\t\t\tenablePPT: {\n\t\t\t\t\telementClassName: 'config-details',\n\t\t\t\t\tshowIf: 'cx.new.study.enables',\n\t\t\t\t\tlabel: 'PPT/书籍自动完成',\n\t\t\t\t\tattrs: { type: 'checkbox', title: '开启：PPT/书籍自动翻阅' },\n\t\t\t\t\tdefaultValue: true\n\t\t\t\t},\n\t\t\t\tenableChapterTest: {\n\t\t\t\t\telementClassName: 'config-details',\n\t\t\t\t\tshowIf: 'cx.new.study.enables',\n\t\t\t\t\tlabel: '章节测试自动答题',\n\t\t\t\t\tattrs: { type: 'checkbox', title: '开启：章节测试自动答题' },\n\t\t\t\t\tdefaultValue: true\n\t\t\t\t},\n\t\t\t\tenableHyperlink: {\n\t\t\t\t\telementClassName: 'config-details',\n\t\t\t\t\tshowIf: 'cx.new.study.enables',\n\t\t\t\t\tlabel: '链接任务自动完成',\n\t\t\t\t\tattrs: { type: 'checkbox', title: '开启：链接任务自动完成' },\n\t\t\t\t\tdefaultValue: true\n\t\t\t\t}\n\t\t\t},\n\t\t\tonrender({ panel }) {\n\t\t\t\tif (!CommonProject.scripts.settings.cfg.answererWrappers?.length) {\n\t\t\t\t\tanswerWrapperEmptyWarning(10);\n\t\t\t\t}\n\n\t\t\t\t// 高倍速警告\n\t\t\t\tthis.offConfigChange(state.study.playbackRateWarningListenerId);\n\t\t\t\tstate.study.playbackRateWarningListenerId =\n\t\t\t\t\tthis.onConfigChange('playbackRate', (playbackRate) => {\n\t\t\t\t\t\tif (playbackRate > 2) {\n\t\t\t\t\t\t\t$modal.alert({\n\t\t\t\t\t\t\t\ttitle: '⚠️高倍速警告',\n\t\t\t\t\t\t\t\tcontent: $ui.notes([\n\t\t\t\t\t\t\t\t\t'⚠️高倍速可能导致学习记录清空/回退',\n\t\t\t\t\t\t\t\t\t'⚠️超星后台可以看到学习时长，请谨慎设置',\n\t\t\t\t\t\t\t\t\t'⚠️如已清空/回退，请降低倍速至1-2倍'\n\t\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t\t\tmaskCloseable: false,\n\t\t\t\t\t\t\t\tconfirmButtonText: '我已知晓风险'\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t}) || 0;\n\t\t\t},\n\t\t\t// 这里不使用 oncompelete ，如果某个资源一直在加载中，就会导致 oncomplete 一直无法触发，导致脚本无法运行，所以改为 onactive 只要匹配上就会触发\n\t\t\tasync onactive() {\n\t\t\t\t/** iframe 跨域问题， 必须在 iframe 中执行 ， 所以脱离学习脚本运行。 */\n\t\t\t\tif (/\\/readsvr\\/book\\/mooc/.test(location.href)) {\n\t\t\t\t\t// 26年上半学期新阅读任务点，计时翻页，达到一定时间才能翻下一页，这里等待时间然后执行翻页完成任务。\n\t\t\t\t\tif (document.querySelector('#reader')) {\n\t\t\t\t\t\t// @ts-ignore\n\t\t\t\t\t\trequire(['reader'], (_reader) => {\n\t\t\t\t\t\t\twaitForElement('.readerPager').then(() => {\n\t\t\t\t\t\t\t\tsetTimeout(async () => {\n\t\t\t\t\t\t\t\t\tconst jumper = document.querySelector<HTMLSelectElement>('#pagejump');\n\t\t\t\t\t\t\t\t\tif (_reader?.goPage && jumper) {\n\t\t\t\t\t\t\t\t\t\t// 等待时间\n\t\t\t\t\t\t\t\t\t\tconst timing = parseInt(new URL(location.href).searchParams.get('timing')?.toString() || '60') + 3;\n\t\t\t\t\t\t\t\t\t\tconsole.log(timing);\n\t\t\t\t\t\t\t\t\t\tawait $.sleep(timing * 1000);\n\t\t\t\t\t\t\t\t\t\t/**\n\t\t\t\t\t\t\t\t\t\t\t *  1:书名页\n\t\t\t\t\t\t\t\t\t\t\t\t2:版权页\n\t\t\t\t\t\t\t\t\t\t\t\t3:前言页\n\t\t\t\t\t\t\t\t\t\t\t\t4:目录页\n\t\t\t\t\t\t\t\t\t\t\t\t5:正文275页\n\t\t\t\t\t\t\t\t\t\t\t\t7:封底页\n\t\t\t\t\t\t\t\t\t\t\t*/\n\t\t\t\t\t\t\t\t\t\tjumper.value = '5';\n\t\t\t\t\t\t\t\t\t\tjumper.dispatchEvent(new Event('change'));\n\t\t\t\t\t\t\t\t\t\tconsole.log('已跳转正文页');\n\t\t\t\t\t\t\t\t\t\tawait $.sleep(timing * 1000);\n\t\t\t\t\t\t\t\t\t\tjumper.value = '7';\n\t\t\t\t\t\t\t\t\t\tjumper.dispatchEvent(new Event('change'));\n\t\t\t\t\t\t\t\t\t\tconsole.log('已跳转封底页');\n\t\t\t\t\t\t\t\t\t\tawait $.sleep(timing * 1000);\n\t\t\t\t\t\t\t\t\t\tArray.from(document.querySelectorAll<HTMLElement>('.readerPager'))\n\t\t\t\t\t\t\t\t\t\t\t.filter((el) => el.style.zIndex === '101')[0]\n\t\t\t\t\t\t\t\t\t\t\t.click();\n\t\t\t\t\t\t\t\t\t\tconsole.log('阅读完成');\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}, 3000);\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\t// 普通阅读任务点，id 是 #ReadWeb\n\t\t\t\t\telse {\n\t\t\t\t\t\t$console.log('正在完成书籍/PPT...');\n\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\t// @ts-ignore\n\t\t\t\t\t\t\t// eslint-disable-next-line no-undef\n\t\t\t\t\t\t\treadweb.goto(epage);\n\t\t\t\t\t\t}, 5000);\n\t\t\t\t\t}\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// 主要处理\n\t\t\t\tif (/\\/knowledge\\/cards/.test(location.href)) {\n\t\t\t\t\tconst updateMediaState = () => {\n\t\t\t\t\t\t// @ts-ignore\n\t\t\t\t\t\tif (top.currentMedia) {\n\t\t\t\t\t\t\t// @ts-ignore 倍速设置\n\t\t\t\t\t\t\ttop.currentMedia.playbackRate = parseFloat(this.cfg.playbackRate.toString());\n\t\t\t\t\t\t\t// @ts-ignore 音量设置\n\t\t\t\t\t\t\ttop.currentMedia.volume = this.cfg.volume;\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\n\t\t\t\t\tthis.onConfigChange('playbackRate', updateMediaState);\n\t\t\t\t\tthis.onConfigChange('volume', updateMediaState);\n\n\t\t\t\t\tawait study({\n\t\t\t\t\t\t...this.cfg,\n\t\t\t\t\t\tplaybackRate: parseFloat(this.cfg.playbackRate.toString()),\n\t\t\t\t\t\tworkOptions: CommonProject.scripts.settings.methods.getWorkOptions()\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}),\n\t\twork: new Script({\n\t\t\tname: '✍️ 作业考试脚本',\n\t\t\tmatches: [\n\t\t\t\t['作业页面', '/mooc2/work/dowork'],\n\t\t\t\t['考试整卷预览页面', '/mooc2/exam/preview']\n\t\t\t],\n\t\t\tnamespace: 'cx.new.work',\n\t\t\tconfigs: { notes: workNotes },\n\t\t\tasync oncomplete() {\n\t\t\t\tconst isExam = /\\/exam\\/preview/.test(location.href);\n\t\t\t\tcommonWork(this, {\n\t\t\t\t\tworkerProvider: (opts) => workOrExam(isExam ? 'exam' : 'work', { ...opts, preview_mode: true }),\n\t\t\t\t\tenable_control_panel: true\n\t\t\t\t});\n\t\t\t}\n\t\t}),\n\t\tautoRead: new Script({\n\t\t\tname: '🖥️ 自动阅读',\n\t\t\tmatches: [\n\t\t\t\t['阅读页面', '/ztnodedetailcontroller/visitnodedetail'],\n\t\t\t\t['课程目录', /chaoxing.com\\/course\\/\\d+\\.html/],\n\t\t\t\t['课程目录', /chaoxing.com\\/mooc-ans\\/course\\/\\d+\\.html/],\n\t\t\t\t['积分课阅读课程目录', '/mooc-ans/zt/portal']\n\t\t\t],\n\t\t\tnamespace: 'cx.new.auto-read',\n\t\t\tconfigs: {\n\t\t\t\tnotes: {\n\t\t\t\t\tdefaultValue: $ui.notes(['点击任意章节开始自动阅读', '阅读任务次日才会统计阅读时长']).outerHTML\n\t\t\t\t},\n\t\t\t\trestartAfterFinish: {\n\t\t\t\t\tlabel: '无限阅读',\n\t\t\t\t\tattrs: { type: 'checkbox', title: '阅读完成最后一章后从头第一章继续阅读' },\n\t\t\t\t\tdefaultValue: false\n\t\t\t\t}\n\t\t\t},\n\t\t\toncomplete() {\n\t\t\t\t// 置顶\n\t\t\t\tCommonProject.scripts.render.methods.pin(this);\n\t\t\t\t$message.info('请手动点击任意章节开始自动阅读');\n\n\t\t\t\t// 自动进入章节功能，如果不是阅读页面则自动进入\n\t\t\t\tif (location.href.includes('/ztnodedetailcontroller/visitnodedetail') === false) {\n\t\t\t\t\tstartAtFirst();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tlet top = 0;\n\t\t\t\tconst interval = setInterval(() => {\n\t\t\t\t\ttop += (document.documentElement.scrollHeight - window.innerHeight) / 60;\n\t\t\t\t\twindow.scrollTo({\n\t\t\t\t\t\tbehavior: 'smooth',\n\t\t\t\t\t\ttop: top\n\t\t\t\t\t});\n\t\t\t\t}, 1000);\n\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\tclearInterval(interval);\n\t\t\t\t\t// 下一页\n\t\t\t\t\tconst next = $el('.nodeItem.r i');\n\t\t\t\t\tif (next) {\n\t\t\t\t\t\tnext.click();\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif (this.cfg.restartAfterFinish) {\n\t\t\t\t\t\t\tsetTimeout(() => startAtFirst(), 3000);\n\t\t\t\t\t\t\t$message.info({ content: '即将重新从头开始阅读', duration: 10 });\n\t\t\t\t\t\t\t$console.log('即将重新从头开始阅读');\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t$message.success({ content: '阅读任务已完成', duration: 0 });\n\t\t\t\t\t\t\t$console.log('未检测到下一页');\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}, (60 + 3) * 1000);\n\n\t\t\t\t// 点击第一个章节\n\t\t\t\tfunction startAtFirst() {\n\t\t\t\t\tconst texts = $$el('.course_section .chapterText');\n\t\t\t\t\tif (texts.length) {\n\t\t\t\t\t\ttexts[0].click();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}),\n\t\t/**\n\t\t * 有时候进入课程会默认在，任务页面，会出现任务为空，部分用户会以为没有章节任务，所以添加此脚本\n\t\t */\n\t\tpageRedirect: new Script({\n\t\t\tname: '章节页面自动切换脚本',\n\t\t\tmatches: [['课程任务页面', 'pageHeader=0']],\n\t\t\thideInPanel: true,\n\t\t\tasync oncomplete() {\n\t\t\t\tif (top === window) {\n\t\t\t\t\tconst a = document.querySelector<HTMLElement>('a[title=\"章节\"]');\n\t\t\t\t\tif (a) {\n\t\t\t\t\t\tawait $.sleep(1000);\n\t\t\t\t\t\t// 跳转到最新版本的超星\n\t\t\t\t\t\ta.click();\n\t\t\t\t\t\t$message.info({\n\t\t\t\t\t\t\tcontent: '已经为您自动切换到章节列表页面，手动进入任意章节即可开始自动学习！'\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}),\n\t\tversionRedirect: new Script({\n\t\t\tname: '版本切换脚本',\n\t\t\tmatches: [\n\t\t\t\t['', 'mooc2=0'],\n\t\t\t\t['', 'mycourse/studentcourse'],\n\t\t\t\t['', 'work/getAllWork'],\n\t\t\t\t['', 'work/doHomeWorkNew'],\n\t\t\t\t['', 'exam/test\\\\?'],\n\t\t\t\t['', 'mooc-ans/mycourse/studentstudy']\n\t\t\t],\n\t\t\thideInPanel: true,\n\t\t\tasync oncomplete() {\n\t\t\t\tif (top === window) {\n\t\t\t\t\t$message.warn({\n\t\t\t\t\t\tcontent:\n\t\t\t\t\t\t\t'OCS网课助手不支持旧版超星, 即将切换到超星新版, 如有其他第三方插件请关闭, 可能有兼容问题导致频繁切换。',\n\t\t\t\t\t\tduration: 0\n\t\t\t\t\t});\n\t\t\t\t\t// 跳转到最新版本的超星\n\t\t\t\t\tawait $.sleep(2000);\n\n\t\t\t\t\t// 检测是否有人脸识别\n\t\t\t\t\tawait waitForFaceRecognition();\n\n\t\t\t\t\tconst experience = document.querySelector('.experience') as HTMLElement;\n\t\t\t\t\tif (experience) {\n\t\t\t\t\t\texperience.click();\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst newUrl = new URL(window.location.href);\n\t\t\t\t\t\tif (window.location.href.includes('mooc-ans/mycourse/studentstudy')) {\n\t\t\t\t\t\t\tnewUrl.pathname = '/mycourse/studentstudy';\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst params = newUrl.searchParams;\n\t\t\t\t\t\tlet changed = false;\n\t\t\t\t\t\tif (params.get('mooc2') !== '1') {\n\t\t\t\t\t\t\tparams.set('mooc2', '1');\n\t\t\t\t\t\t\tchanged = true;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// 兼容考试切换\n\t\t\t\t\t\tif (params.get('newMooc') !== 'true') {\n\t\t\t\t\t\t\tparams.set('newMooc', 'true');\n\t\t\t\t\t\t\tchanged = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (changed) window.location.replace(newUrl);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}),\n\t\texamRedirect: new Script({\n\t\t\tname: '考试整卷预览脚本',\n\t\t\tmatches: [\n\t\t\t\t['新版考试页面', 'exam-ans/exam/test/reVersionTestStartNew'],\n\t\t\t\t// 2023/9月 新增\n\t\t\t\t['新版考试页面2', 'mooc-ans/exam/test/reVersionTestStartNew']\n\t\t\t],\n\t\t\thideInPanel: true,\n\t\t\toncomplete() {\n\t\t\t\tif ($gm.unsafeWindow.document.querySelector('.mark_info')?.textContent?.includes('不允许整卷预览')) {\n\t\t\t\t\t$message.warn({\n\t\t\t\t\t\tcontent: $ui.notes([\n\t\t\t\t\t\t\t'由于当前考试禁止整卷预览，各题为独立新页面，只能一个个答题',\n\t\t\t\t\t\t\t'在考完前禁止手动切换题目，否则会导致重复答题！',\n\t\t\t\t\t\t\t'完成后或者开考前请手动删除搜索结果！',\n\t\t\t\t\t\t\t'想加快速度请更改通用-全局设置-高级设置-搜题间隔，设置为 1-3 秒即可。'\n\t\t\t\t\t\t]),\n\t\t\t\t\t\tduration: 0\n\t\t\t\t\t});\n\t\t\t\t\tconst isExam = /\\/exam\\/test/.test(location.href);\n\t\t\t\t\tconst workOptions = CommonProject.scripts.settings.methods.getWorkOptions();\n\t\t\t\t\tcommonWork(CXProject.scripts.work, {\n\t\t\t\t\t\t// 因为超星是每个题目一个页面，这里加快开始速度，避免等待，默认5秒，这里加快为默认3秒间隔\n\t\t\t\t\t\tstart_delay_seconds: workOptions.period,\n\t\t\t\t\t\tenable_control_panel: true,\n\t\t\t\t\t\tworkerProvider: (opts) => workOrExam(isExam ? 'exam' : 'work', { ...opts, preview_mode: false, thread: 1 })\n\t\t\t\t\t});\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t$message.info({ content: '即将跳转到整卷预览页面进行考试。' });\n\t\t\t\tsetTimeout(() => $gm.unsafeWindow.topreview(), 3000);\n\t\t\t}\n\t\t}),\n\t\trateHack: new Script({\n\t\t\tname: '屏蔽倍速限制',\n\t\t\thideInPanel: true,\n\t\t\tmatches: [['', '/ananas/modules/video/']],\n\t\t\tonstart() {\n\t\t\t\trateHack();\n\t\t\t}\n\t\t}),\n\t\tcopyHack: new Script({\n\t\t\tname: '屏蔽复制粘贴限制',\n\t\t\thideInPanel: true,\n\t\t\tmatches: [['所有页面', /.*/]],\n\t\t\tmethods() {\n\t\t\t\treturn {\n\t\t\t\t\t/** 解除输入框无法复制粘贴 */\n\t\t\t\t\thackEditorPaste() {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst instants = $gm.unsafeWindow?.UE?.instants || [];\n\t\t\t\t\t\t\tfor (const key in instants) {\n\t\t\t\t\t\t\t\tconst ue = instants[key];\n\n\t\t\t\t\t\t\t\t/**\n\t\t\t\t\t\t\t\t * 新建一个文本框给用户编辑，然后同步到超星编辑器，防止http下浏览器无法读取剪贴板\n\t\t\t\t\t\t\t\t */\n\n\t\t\t\t\t\t\t\t// eslint-disable-next-line no-proto\n\t\t\t\t\t\t\t\tif (ue?.textarea) {\n\t\t\t\t\t\t\t\t\tue.body.addEventListener('click', async () => {\n\t\t\t\t\t\t\t\t\t\t// http 下无法读取剪贴板，通过弹窗让用户输入然后同步到编辑器\n\t\t\t\t\t\t\t\t\t\tif (CXProject.scripts.study.cfg.showTextareaWhenEdit) {\n\t\t\t\t\t\t\t\t\t\t\tconst defaultText = h('span', { innerHTML: ue.textarea.value }).textContent;\n\t\t\t\t\t\t\t\t\t\t\t$modal.prompt({\n\t\t\t\t\t\t\t\t\t\t\t\tcontent:\n\t\t\t\t\t\t\t\t\t\t\t\t\t'请在此文本框进行编辑，防止超星无法复制粘贴。(如需关闭请前往设置: 课程学习-编辑时显示自定义编辑框)',\n\t\t\t\t\t\t\t\t\t\t\t\twidth: 800,\n\t\t\t\t\t\t\t\t\t\t\t\tinputDefaultValue: defaultText || '',\n\t\t\t\t\t\t\t\t\t\t\t\tmodalInputType: 'textarea',\n\t\t\t\t\t\t\t\t\t\t\t\tonConfirm: (val = '') => {\n\t\t\t\t\t\t\t\t\t\t\t\t\tue.setContent(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tval\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t.split('\\n')\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t.map((line) => `<p>${line}</p>`)\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t.join('')\n\t\t\t\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\t\t\tif ($gm.unsafeWindow.editorPaste) {\n\t\t\t\t\t\t\t\t\t\tue.removeListener('beforepaste', $gm.unsafeWindow.editorPaste);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tif ($gm.unsafeWindow.myEditor_paste) {\n\t\t\t\t\t\t\t\t\t\tue.removeListener('beforepaste', $gm.unsafeWindow.myEditor_paste);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch {}\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t},\n\t\t\toncomplete() {\n\t\t\t\tconst hackInterval = setInterval(() => {\n\t\t\t\t\tif (typeof $gm.unsafeWindow.UE !== 'undefined') {\n\t\t\t\t\t\tclearInterval(hackInterval);\n\t\t\t\t\t\tthis.methods.hackEditorPaste();\n\t\t\t\t\t\tconsole.log('已解除输入框无法复制粘贴限制');\n\t\t\t\t\t}\n\t\t\t\t}, 500);\n\t\t\t}\n\t\t}),\n\t\tstudyDispatcher: new Script({\n\t\t\tname: '课程学习调度器',\n\t\t\tmatches: [['课程学习页面', '/mycourse/studentstudy']],\n\t\t\tnamespace: 'cx.new.study-dispatcher',\n\t\t\thideInPanel: true,\n\t\t\tasync oncomplete() {\n\t\t\t\t// 开始任务切换\n\t\t\t\tconst restudy = CXProject.scripts.study.cfg.restudy;\n\n\t\t\t\tCommonProject.scripts.render.methods.pin(CXProject.scripts.study);\n\n\t\t\t\tlet chapters = await CXAnalyses.waitForChapterInfos();\n\n\t\t\t\tif (!restudy) {\n\t\t\t\t\t// 如果不是复习模式，则寻找需要运行的任务\n\t\t\t\t\tconst params = new URLSearchParams(window.location.href);\n\t\t\t\t\tconst mooc = params.get('mooc2');\n\t\t\t\t\t/** 切换新版 */\n\t\t\t\t\tif (mooc === null) {\n\t\t\t\t\t\tparams.set('mooc2', '1');\n\t\t\t\t\t\twindow.location.replace(decodeURIComponent(params.toString()));\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// 过滤掉已完成的章节\n\t\t\t\t\tchapters = chapters.filter((chapter) => chapter.unFinishCount !== 0);\n\n\t\t\t\t\tif (chapters.length === 0) {\n\t\t\t\t\t\t$message.warn({ content: '页面任务点数量为空! 请刷新重试!' });\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst params = new URLSearchParams(window.location.href);\n\t\t\t\t\t\tconst courseId = params.get('courseId');\n\t\t\t\t\t\tconst classId = params.get('clazzid');\n\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\t//  进入需要进行的章节，并且当前章节未被选中\n\t\t\t\t\t\t\tif ($$el(`.posCatalog_active[id=\"cur${chapters[0].chapterId}\"]`).length === 0) {\n\t\t\t\t\t\t\t\t$gm.unsafeWindow.getTeacherAjax(courseId, classId, chapters[0].chapterId);\n\t\t\t\t\t\t\t\t// 自动滚动\n\t\t\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\t\t\tCXAnalyses.scrollToActiveChapter();\n\t\t\t\t\t\t\t\t}, 1000);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}, 1000);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// 自动滚动\n\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\tCXAnalyses.scrollToActiveChapter();\n\t\t\t\t\t}, 1000);\n\t\t\t\t}\n\t\t\t}\n\t\t}),\n\t\tcxSecretFontRecognize: new Script({\n\t\t\tname: '繁体字识别',\n\t\t\thideInPanel: true,\n\t\t\tmatches: [\n\t\t\t\t['题目页面', 'work/doHomeWorkNew'],\n\t\t\t\t['考试整卷预览', '/mooc2/exam/preview'],\n\t\t\t\t['作业', '/mooc2/work/dowork']\n\t\t\t],\n\t\t\tasync oncomplete() {\n\t\t\t\tawait mappingRecognize();\n\t\t\t}\n\t\t}),\n\t\t// 积分课提示\n\t\tjfkGuide: new Script({\n\t\t\tname: '💡 积分课使用提示',\n\t\t\tmatches: [['积分课页面', '/plaza']],\n\t\t\tnamespace: 'cx.jfk.guide',\n\t\t\tconfigs: {\n\t\t\t\tnotes: {\n\t\t\t\t\tdefaultValue: $ui.notes([\n\t\t\t\t\t\t'积分课请进入课程后，开启复习模式，并且关闭自动下一章',\n\t\t\t\t\t\t'课程完成后请手动切换，如果由脚本进行自动跳转会出现乱跳转的可能。'\n\t\t\t\t\t]).outerHTML\n\t\t\t\t}\n\t\t\t},\n\t\t\toncomplete(...args) {\n\t\t\t\tCommonProject.scripts.render.methods.pin(this);\n\t\t\t}\n\t\t})\n\t}\n});\n\nfunction workOrExam(\n\ttype: 'work' | 'exam' = 'work',\n\t{\n\t\tanswererWrappers,\n\t\tperiod,\n\t\tthread,\n\t\tredundanceWordsText,\n\t\tanswerSeparators,\n\t\tanswerMatchMode,\n\t\tpreview_mode\n\t}: CommonWorkOptions & {\n\t\t// 整卷预览模式\n\t\tpreview_mode: boolean;\n\t}\n) {\n\t$message.info(`开始${type === 'work' ? '作业' : '考试'}`);\n\n\tif (preview_mode) {\n\t\tCommonProject.scripts.workResults.methods.init({\n\t\t\tquestionPositionSyncHandlerType: 'cx'\n\t\t});\n\t}\n\n\t// 处理作业和考试题目的方法\n\tconst workOrExamQuestionTitleTransform = (titles: (HTMLElement | undefined)[]) => {\n\t\tconst optimizationTitle = titles\n\t\t\t.map((titleElement) => {\n\t\t\t\tif (titleElement) {\n\t\t\t\t\tconst titleCloneEl = titleElement.cloneNode(true) as HTMLElement;\n\t\t\t\t\tconst childNodes = titleCloneEl.childNodes;\n\t\t\t\t\t// 删除序号\n\t\t\t\t\tchildNodes[0].remove();\n\t\t\t\t\t// 删除题型\n\t\t\t\t\tchildNodes[0].remove();\n\t\t\t\t\t// 显示图片链接在题目中\n\t\t\t\t\treturn optimizationElementWithImage(titleCloneEl, true).innerText;\n\t\t\t\t}\n\t\t\t\treturn '';\n\t\t\t})\n\t\t\t.join(',');\n\n\t\treturn removeRedundantWords(\n\t\t\tStringUtils.of(optimizationTitle).nowrap(' ').nospace().toString().trim(),\n\t\t\tredundanceWordsText.split('\\n')\n\t\t);\n\t};\n\n\t/** 新建答题器 */\n\tconst worker = new OCSWorker({\n\t\troot: '.questionLi',\n\t\telements: {\n\t\t\ttitle: [\n\t\t\t\t/** 题目标题 */\n\t\t\t\t(root) => $el('h3', root) as HTMLElement\n\t\t\t\t// /** 连线题第一组 */\n\t\t\t\t// (root) => $el('.line_wid_half.fl', root),\n\t\t\t\t// /** 连线题第二组 */\n\t\t\t\t// (root) => $el('.line_wid_half.fr', root)\n\t\t\t],\n\t\t\toptions: '.answerBg .answer_p, .textDIV, .eidtDiv',\n\t\t\ttype: type === 'exam' ? 'input[name^=\"type\"]' : 'input[id^=\"answertype\"]',\n\t\t\tlineAnswerInput: '.line_answer input[name^=answer]',\n\t\t\tlineSelectBox: '.line_answer_ct .selectBox ',\n\t\t\t/** 阅读理解 */\n\t\t\treading: '.reading_answer',\n\t\t\t/** 完形填空 */\n\t\t\tfilling: '.filling_answer'\n\t\t},\n\t\tthread: thread ?? 1,\n\t\tanswerSeparators: answerSeparators.split(',').map((s) => s.trim()),\n\t\tanswerMatchMode: answerMatchMode,\n\t\t/** 默认搜题方法构造器 */\n\t\tanswerer: (elements, ctx) => {\n\t\t\tif (elements.title) {\n\t\t\t\t// 处理作业和考试题目\n\t\t\t\tconst title = workOrExamQuestionTitleTransform(elements.title);\n\t\t\t\tif (title) {\n\t\t\t\t\tconst typeInput = elements.type[0] as HTMLInputElement;\n\t\t\t\t\treturn CommonProject.scripts.apps.methods.searchAnswerInCaches(title, async () => {\n\t\t\t\t\t\tawait $.sleep((period ?? 3) * 1000);\n\t\t\t\t\t\treturn defaultAnswerWrapperHandler(answererWrappers, {\n\t\t\t\t\t\t\ttype: (typeInput ? getQuestionType(parseInt(typeInput.value)) : undefined) || 'unknown',\n\t\t\t\t\t\t\ttitle,\n\t\t\t\t\t\t\toptions:\n\t\t\t\t\t\t\t\tctx.type === 'completion'\n\t\t\t\t\t\t\t\t\t? ''\n\t\t\t\t\t\t\t\t\t: ctx.elements.options.map((o) => optimizationElementWithImage(o, true).innerText).join('\\n')\n\t\t\t\t\t\t});\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\tthrow new Error('题目为空，请查看题目是否为空，或者忽略此题');\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tthrow new Error('题目为空，请查看题目是否为空，或者忽略此题');\n\t\t\t}\n\t\t},\n\n\t\twork: async (ctx) => {\n\t\t\tconst { elements, searchInfos } = ctx;\n\t\t\tconst typeInput = elements.type[0] as HTMLInputElement;\n\t\t\tconst type = getQuestionType(parseInt(typeInput.value));\n\n\t\t\tif (type && (type === 'completion' || type === 'multiple' || type === 'judgement' || type === 'single')) {\n\t\t\t\tconst resolver = createDefaultQuestionResolver(ctx)[type];\n\t\t\t\treturn await resolver(\n\t\t\t\t\tsearchInfos,\n\t\t\t\t\telements.options.map((option) => optimizationElementWithImage(option)),\n\t\t\t\t\tasync (type, answer, option) => {\n\t\t\t\t\t\t// 如果存在已经选择的选项\n\t\t\t\t\t\tif (type === 'judgement' || type === 'single' || type === 'multiple') {\n\t\t\t\t\t\t\tif (option?.parentElement && $$el('[class*=\"check_answer\"]', option.parentElement).length === 0) {\n\t\t\t\t\t\t\t\toption.click();\n\t\t\t\t\t\t\t\tawait $.sleep(500);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (type === 'completion' && answer.trim()) {\n\t\t\t\t\t\t\tconst text = option?.querySelector('textarea');\n\t\t\t\t\t\t\tconst textareaFrame = option?.querySelector('iframe');\n\t\t\t\t\t\t\tif (text) {\n\t\t\t\t\t\t\t\ttext.value = answer;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (textareaFrame?.contentDocument) {\n\t\t\t\t\t\t\t\ttextareaFrame.contentDocument.body.innerHTML = answer;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (option?.parentElement?.parentElement) {\n\t\t\t\t\t\t\t\t/** 如果存在保存按钮则点击 */\n\t\t\t\t\t\t\t\t$el('[onclick*=saveQuestion]', option?.parentElement?.parentElement)?.click();\n\t\t\t\t\t\t\t\tawait $.sleep(500);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t);\n\t\t\t}\n\t\t\t// 连线题自定义处理\n\t\t\telse if (type && type === 'line') {\n\t\t\t\tfor (const answers of searchInfos.map((info) => info.results.map((res) => res.answer))) {\n\t\t\t\t\tlet ans = answers;\n\t\t\t\t\tif (ans.length === 1) {\n\t\t\t\t\t\tans = splitAnswer(ans[0]);\n\t\t\t\t\t}\n\t\t\t\t\tif (ans.filter(Boolean).length !== 0 && elements.lineAnswerInput) {\n\t\t\t\t\t\t//  选择答案\n\t\t\t\t\t\tfor (let index = 0; index < elements.lineSelectBox.length; index++) {\n\t\t\t\t\t\t\tconst box = elements.lineSelectBox[index];\n\t\t\t\t\t\t\tif (ans[index]) {\n\t\t\t\t\t\t\t\t$el(`li[data=${ans[index]}] a`, box)?.click();\n\t\t\t\t\t\t\t\tawait $.sleep(200);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn { finish: true };\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn { finish: false };\n\t\t\t}\n\t\t\t// 完形填空\n\t\t\telse if (type && type === 'fill') {\n\t\t\t\treturn readerAndFillHandle(searchInfos, elements.filling);\n\t\t\t}\n\t\t\t// 阅读理解\n\t\t\telse if (type && type === 'reader') {\n\t\t\t\treturn readerAndFillHandle(searchInfos, elements.reading);\n\t\t\t}\n\n\t\t\treturn { finish: false };\n\t\t},\n\n\t\t/** 完成答题后 */\n\t\tasync onResultsUpdate(current, _, res) {\n\t\t\t// 非预览模式，直接追加，想要清楚只能手动清空\n\t\t\tif (!preview_mode) {\n\t\t\t\tif (current.result?.finish) {\n\t\t\t\t\tawait CommonProject.scripts.workResults.methods.appendResults(\n\t\t\t\t\t\tsimplifyWorkResult(res, workOrExamQuestionTitleTransform)\n\t\t\t\t\t);\n\t\t\t\t\tCommonProject.scripts.apps.methods.addQuestionCacheFromWorkResult(\n\t\t\t\t\t\tsimplifyWorkResult([current], workOrExamQuestionTitleTransform)\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tCommonProject.scripts.workResults.methods.setResults(simplifyWorkResult(res, workOrExamQuestionTitleTransform));\n\t\t\tCommonProject.scripts.workResults.methods.updateWorkStateByResults(res);\n\t\t\tif (current.result?.finish) {\n\t\t\t\tCommonProject.scripts.apps.methods.addQuestionCacheFromWorkResult(\n\t\t\t\t\tsimplifyWorkResult([current], workOrExamQuestionTitleTransform)\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t});\n\n\tif (preview_mode) {\n\t\tworker\n\t\t\t.doWork()\n\t\t\t.then(() => {\n\t\t\t\t$message.info({ content: '作业/考试完成，请自行检查后保存或提交。', duration: 0 });\n\t\t\t\tworker.emit('done');\n\t\t\t})\n\t\t\t.catch((err) => {\n\t\t\t\tconsole.error(err);\n\t\t\t\t$message.error('答题程序发生错误 : ' + err.message);\n\t\t\t});\n\t} else {\n\t\tconst getNextBtn = () => document.querySelector('[onclick=\"getTheNextQuestion(1)\"]') as HTMLElement;\n\t\tlet next = getNextBtn();\n\n\t\t(async () => {\n\t\t\twhile (next && worker.isClose === false) {\n\t\t\t\tawait worker.doWork({ enable_debug: BackgroundProject.scripts.dev.cfg.enable_answerer_debug });\n\t\t\t\tawait $.sleep(1000);\n\t\t\t\tnext = getNextBtn();\n\t\t\t\tnext?.click();\n\t\t\t\tawait $.sleep(1000);\n\t\t\t}\n\n\t\t\t$message.success({ content: '作业/考试完成，请自行检查后保存或提交。', duration: 0 });\n\t\t\tworker.emit('done');\n\t\t\t// 搜索完成后才会同步答案与题目的显示，防止题目错乱\n\t\t\tCommonProject.scripts.workResults.cfg.questionPositionSyncHandlerType = 'cx';\n\t\t})();\n\t}\n\n\treturn worker;\n}\n\n/**\n * 繁体字识别-字典匹配\n * @see 参考 https://bbs.tampermonkey.net.cn/thread-2303-1-1.html\n */\nasync function mappingRecognize(doc: Document = document) {\n\tlet typrMapping = Object.create({});\n\ttry {\n\t\t// @ts-ignore\n\t\ttop.typrMapping = top.typrMapping || (await loadTyprMapping());\n\t\t// @ts-ignore\n\t\ttyprMapping = top.typrMapping;\n\t} catch {\n\t\t// 超星考试可能嵌套其他平台中，所以会存在跨域，这里需要处理一下跨域情况，如果是跨域直接在当前页面加载字库\n\t\ttyprMapping = await loadTyprMapping();\n\t}\n\n\t/** 判断是否有繁体字 */\n\tconst fontFaceEl = Array.from(doc.head.querySelectorAll('style')).find((style) =>\n\t\tstyle.textContent?.includes('font-cxsecret')\n\t);\n\n\tconst base64ToUint8Array = (base64: string) => {\n\t\tconst data = window.atob(base64);\n\t\tconst buffer = new Uint8Array(data.length);\n\t\tfor (let i = 0; i < data.length; ++i) {\n\t\t\tbuffer[i] = data.charCodeAt(i);\n\t\t}\n\t\treturn buffer;\n\t};\n\n\tconst fontMap = typrMapping;\n\tif (fontFaceEl && Object.keys(fontMap).length > 0) {\n\t\t// 解析font-cxsecret字体\n\t\tconst font = fontFaceEl.textContent?.match(/base64,([\\w\\W]+?)'/)?.[1];\n\n\t\tif (font) {\n\t\t\t$console.log('正在识别繁体字');\n\n\t\t\tconst code = Typr.parse(base64ToUint8Array(font));\n\n\t\t\t// 匹配解密字体\n\t\t\tconst match: any = {};\n\t\t\tfor (let i = 19968; i < 40870; i++) {\n\t\t\t\t// 中文[19968, 40869]\n\t\t\t\tconst Glyph = Typr.U.codeToGlyph(code, i);\n\t\t\t\tif (!Glyph) continue;\n\t\t\t\tconst path = Typr.U.glyphToPath(code, Glyph);\n\t\t\t\tconst hex = md5(JSON.stringify(path)).slice(24); // 8位即可区分\n\t\t\t\tmatch[i.toString()] = fontMap[hex];\n\t\t\t}\n\t\t\tconst fonts = CXAnalyses.getSecretFont(doc);\n\t\t\t// 替换加密字体\n\t\t\tfonts.forEach((el, index) => {\n\t\t\t\tlet html = el.innerHTML;\n\t\t\t\tfor (const key in match) {\n\t\t\t\t\tconst word = String.fromCharCode(parseInt(key));\n\t\t\t\t\tconst value = String.fromCharCode(match[key]);\n\n\t\t\t\t\t// 如果相同，则不需要替换\n\t\t\t\t\tif (word === value) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\twhile (html.indexOf(word) !== -1) {\n\t\t\t\t\t\thtml = html.replace(word, value);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tel.innerHTML = html;\n\t\t\t\tel.classList.remove('font-cxsecret'); // 移除字体加密\n\t\t\t});\n\n\t\t\t$console.log('识别繁体字完成。');\n\t\t} else {\n\t\t\t$console.log('未检测到繁体字。');\n\t\t}\n\t}\n}\n\nasync function loadTyprMapping() {\n\ttry {\n\t\t$console.log('正在加载繁体字库。');\n\t\treturn await request('https://cdn.ocsjs.com/resources/font/table.json', {\n\t\t\ttype: 'GM_xmlhttpRequest',\n\t\t\tmethod: 'get',\n\t\t\tresponseType: 'json'\n\t\t});\n\t} catch (err) {\n\t\t$console.error('载繁体字库加载失败，请刷新页面重试：', String(err));\n\t}\n}\n\n/**\n * cx分析工具\n */\nconst CXAnalyses = {\n\t/** 是否处于闯关模式或者解锁模式 */\n\tisInSpecialMode() {\n\t\treturn Array.from(top?.document.querySelectorAll('.catalog_points_sa,.catalog_points_er') || []).length !== 0;\n\t},\n\t/** 是否为闯关模式，并且当前章节卡在最后一个待完成的任务点 */\n\tasync isStuckInBreakingMode() {\n\t\tif (this.isInSpecialMode()) {\n\t\t\tconst chapter = top?.document.querySelector<HTMLElement>('.posCatalog_active');\n\t\t\tif (chapter) {\n\t\t\t\tconst id = chapter.getAttribute('id');\n\t\t\t\tif (id) {\n\t\t\t\t\t// 超星好像会重绘组件，导致无法绑定属性到元素中，所以这里使用页面全局变量存储\n\t\t\t\t\tconst counter = (await $store.getTab('chapter_counter')) || Object.create({});\n\t\t\t\t\tlet count = Reflect.get(counter, id);\n\t\t\t\t\tcount = count ? count + 1 : 1;\n\t\t\t\t\tReflect.set(counter, id, count);\n\n\t\t\t\t\tlet res = false;\n\t\t\t\t\tif (count >= 3) {\n\t\t\t\t\t\tReflect.set(counter, id, 1);\n\t\t\t\t\t\tres = true;\n\t\t\t\t\t}\n\t\t\t\t\tawait $store.setTab('chapter_counter', counter);\n\t\t\t\t\treturn res;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t},\n\t/**\n\t * 是否处于最后一小节\n\t * 当小节为0，返回 true\n\t */\n\tisInFinalTab() {\n\t\t// 上方小节任务栏\n\t\tconst tabs = Array.from<HTMLElement>(top?.document.querySelectorAll('.prev_ul li') || []);\n\t\tif (tabs.length === 0) {\n\t\t\treturn true;\n\t\t}\n\t\treturn tabs[tabs.length - 1].classList.contains('active');\n\t},\n\t/** 是否处于最后一个章节 */\n\tisInFinalChapter() {\n\t\treturn Array.from(top?.document.querySelectorAll('.posCatalog_select') || [])\n\t\t\t.pop()\n\t\t\t?.classList.contains('posCatalog_active');\n\t},\n\t/** 是否完成全部章节 */\n\tisFinishedAllChapters() {\n\t\treturn this.getChapterInfos().every((chapter) => chapter.unFinishCount === 0);\n\t},\n\t/** 获取所有章节信息 */\n\tgetChapterInfos() {\n\t\treturn Array.from(top?.document.querySelectorAll('[onclick^=\"getTeacherAjax\"]') || []).map((el) => ({\n\t\t\telement: el as HTMLElement,\n\t\t\tchapterId: el.getAttribute('onclick')?.match(/\\('(.*)','(.*)','(.*)'\\)/)?.[3],\n\t\t\t// @ts-ignore\n\t\t\tunFinishCount: parseInt(el.parentElement.querySelector('.jobUnfinishCount')?.value || '0')\n\t\t}));\n\t},\n\tscrollToActiveChapter() {\n\t\tconst activeChapter = top?.document.querySelector<HTMLElement>('.posCatalog_active');\n\t\tif (activeChapter) {\n\t\t\tactiveChapter.scrollIntoView({ behavior: 'smooth', block: 'center' });\n\t\t}\n\t},\n\t/**\n\t * 等待并获取章节信息，直到获取到章节信息为止\n\t * - 可设置超时时间（单位秒），默认10秒\n\t * - 超时后返回空数组\n\t */\n\twaitForChapterInfos(timeout = 10) {\n\t\treturn new Promise<any[]>((resolve, reject) => {\n\t\t\tconst interval = setInterval(() => {\n\t\t\t\tconst res = this.getChapterInfos();\n\t\t\t\tif (res.length > 0) {\n\t\t\t\t\tclearInterval(interval);\n\t\t\t\t\tclearInterval(to);\n\t\t\t\t\tresolve(res);\n\t\t\t\t}\n\t\t\t}, 1000);\n\n\t\t\tconst to = setTimeout(() => {\n\t\t\t\tclearInterval(interval);\n\t\t\t\tresolve([]);\n\t\t\t}, timeout * 1000);\n\t\t});\n\t},\n\t/** 检测页面是否使用字体加密 */\n\tgetSecretFont(doc: Document = document) {\n\t\treturn Array.from(doc.querySelectorAll('.font-cxsecret')).map((font) => {\n\t\t\t// 这里吧选项按钮和文字分离，如果不分离的话 .font-cxsecret 元素下面还包含选项按钮时，替换时会吧按钮也删除掉导致选项按钮不可用\n\t\t\tconst after = font.querySelector('.after');\n\t\t\treturn after === null ? font : after;\n\t\t}) as HTMLElement[];\n\t},\n\t/**\n\t * 检测当前章节是否完成\n\t */\n\tisCurrentChapterFinished() {\n\t\tconst job = top?.document.querySelector('.posCatalog_active');\n\t\tif (job) {\n\t\t\tif (job.querySelector('.icon_Completed') !== null) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n};\n\n/**\n * 屏蔽倍速限制\n */\nfunction rateHack() {\n\tstate.study.hacked = false;\n\tlet dragCount = 0;\n\ttry {\n\t\thack();\n\t\twindow.document.addEventListener('readystatechange', hack);\n\t\twindow.addEventListener('load', hack);\n\t} catch (e) {\n\t\tconsole.error(e);\n\t}\n\n\tfunction hack() {\n\t\tconst videojs = $gm.unsafeWindow.videojs;\n\t\tconst Ext = $gm.unsafeWindow.Ext;\n\n\t\tif (typeof videojs !== 'undefined' && typeof Ext !== 'undefined') {\n\t\t\tif (state.study.hacked) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tstate.study.hacked = true;\n\n\t\t\tconst _origin = videojs.getPlugin('seekBarControl');\n\t\t\tconst plugin = videojs.extend(videojs.getPlugin('plugin'), {\n\t\t\t\tconstructor: function (videoExt: any, data: any) {\n\t\t\t\t\tconst _sendLog = data.sendLog;\n\t\t\t\t\tdata.sendLog = (...args: any[]) => {\n\t\t\t\t\t\tif (args[1] === 'drag') {\n\t\t\t\t\t\t\tdragCount++;\n\t\t\t\t\t\t\t// 开始播放的时候偶尔会卡顿，导致一直触发 drag 事件（超星的BUG）\n\t\t\t\t\t\t\t// 这里如果卡顿太多，尝试暂停视频，然后等待视频自动开始。\n\t\t\t\t\t\t\tif (dragCount > 100) {\n\t\t\t\t\t\t\t\tdragCount = 0;\n\t\t\t\t\t\t\t\t$el('video')?.pause();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t_sendLog.apply(data, args);\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\n\t\t\t\t\t_origin.apply(_origin.prototype, [videoExt, data]);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tvideojs.registerPlugin('seekBarControl', plugin);\n\n\t\t\t// 重写超星视频插件\n\t\t\tExt.define('ans.VideoJs', {\n\t\t\t\toverride: 'ans.VideoJs',\n\t\t\t\tconstructor: function (data: any) {\n\t\t\t\t\tthis.addEvents(['seekstart']);\n\t\t\t\t\tthis.mixins.observable.constructor.call(this, data);\n\t\t\t\t\tconst vjs = videojs(data.videojs, this.params2VideoOpt(data.params), function () {});\n\t\t\t\t\tExt.fly(data.videojs).on('contextmenu', function (f: any) {\n\t\t\t\t\t\tf.preventDefault();\n\t\t\t\t\t});\n\t\t\t\t\tExt.fly(data.videojs).on('keydown', function (f: any) {\n\t\t\t\t\t\tif (f.keyCode === 32 || f.keyCode === 37 || f.keyCode === 39 || f.keyCode === 107) {\n\t\t\t\t\t\t\tf.preventDefault();\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\n\t\t\t\t\t// 保存清晰度设置\n\t\t\t\t\tif (vjs.videoJsResolutionSwitcher) {\n\t\t\t\t\t\tvjs.on('resolutionchange', function () {\n\t\t\t\t\t\t\tconst cr = vjs.currentResolution();\n\t\t\t\t\t\t\tconst re = cr.sources ? cr.sources[0].res : false;\n\t\t\t\t\t\t\tExt.setCookie('resolution', re);\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\n\t\t\t\t\t// 保存公网设置\n\t\t\t\t\tif (vjs.videoJsPlayLine) {\n\t\t\t\t\t\tvjs.on('playlinechange', function () {\n\t\t\t\t\t\t\tconst cp = vjs.currentPlayline();\n\t\t\t\t\t\t\tExt.setCookie('net', cp.net);\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\n\t\t\t\t\t// 下面连着一个倍速限制方法，这里直接不写，实现可以倍速\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t}\n}\n\n/**\n * cx 任务学习\n */\nexport async function study(\n\topts: typeof CXProject.scripts.study.cfg & {\n\t\tworkOptions: CommonWorkOptions;\n\t}\n) {\n\tawait $.sleep(3000);\n\n\tconst searchedJobs: Job[] = [];\n\n\tlet searching = true;\n\n\tlet attachmentCount: number = $gm.unsafeWindow.attachments?.length || 0;\n\n\tconst wait_timeout = 3 + attachmentCount * 2;\n\n\t/** 考虑到网速级慢的同学，所以10秒后如果还没有任务点才停止 */\n\tsetTimeout(() => {\n\t\tsearching = false;\n\t}, Math.min(wait_timeout, 10) * 1000);\n\n\t/**\n\t * 递归运行任务点，一旦有新的任务点被检测到直接开始\n\t * 如果10秒内既没有任务点，也暂停了搜索，则当前则没有任务点\n\t */\n\tconst runJobs = async () => {\n\t\tconst job = searchJob(opts, searchedJobs);\n\t\t// 如果存在任务点\n\t\tif (job && job.func) {\n\t\t\ttry {\n\t\t\t\tawait job.func();\n\t\t\t} catch (e) {\n\t\t\t\t$console.error('未知错误', e);\n\t\t\t}\n\n\t\t\tawait $.sleep(1000);\n\t\t\tawait runJobs();\n\t\t}\n\t\t// 每次 search 一次，就减少一次文件数量\n\t\t// 如果不加这个判断，三个任务中，中间的任务不是任务点，则会导致下面的任务全部不执行。\n\t\telse if (attachmentCount > 0) {\n\t\t\tattachmentCount--;\n\t\t\tawait $.sleep(1000);\n\t\t\tawait runJobs();\n\t\t}\n\t\t// 或者正在搜索\n\t\telse if (searching) {\n\t\t\tawait $.sleep(1000);\n\t\t\tawait runJobs();\n\t\t}\n\t};\n\n\tawait runJobs();\n\n\t// @ts-ignore\n\ttop._preChapterId = '';\n\n\t// 下一章\n\tconst next = async () => {\n\t\tif (CXAnalyses.isInFinalTab()) {\n\t\t\tif (await CXAnalyses.isStuckInBreakingMode()) {\n\t\t\t\treturn $modal.alert({\n\t\t\t\t\tcontent:\n\t\t\t\t\t\t'检测到当前课程为闯关模式（有小旗帜图标）, 但是已经重复进入多次，应该是章节测试没有完成，或者其他情况导致，请手动完成章节测试，然后手动点击下一章。'\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tif (CXAnalyses.isInFinalChapter()) {\n\t\t\tlet content = '';\n\n\t\t\tif (opts.backToFirstWhenFinish) {\n\t\t\t\tcontent = '已经抵达最后一个章节，10秒后返回第一个章节重新开始。';\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\ttop?.document.querySelector<HTMLElement>('.posCatalog_name')?.click();\n\t\t\t\t}, 10 * 1000);\n\n\t\t\t\t$message.info({ content, duration: 30 });\n\t\t\t} else {\n\t\t\t\tif (CXAnalyses.isFinishedAllChapters()) {\n\t\t\t\t\tcontent = '全部任务点已完成！';\n\t\t\t\t} else {\n\t\t\t\t\tcontent = '已经抵达最后一个章节！但仍然有任务点未完成，请手动切换至未完成的章节。';\n\t\t\t\t}\n\n\t\t\t\t$modal.alert({ content: content });\n\t\t\t}\n\n\t\t\tCommonProject.scripts.settings.methods.notificationBySetting(content, {\n\t\t\t\tduration: 0,\n\t\t\t\textraTitle: '超星学习通学习脚本'\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\tif (CXProject.scripts.study.cfg.mode === 'job') {\n\t\t\t// 检测当前章节是否完成，如果已经完成则下一章\n\t\t\t// 如果没有需要完成的章节，则暂停运行\n\n\t\t\tif (CXAnalyses.isInFinalTab()) {\n\t\t\t\t// 找到未完成\n\t\t\t\tconst elements = CXAnalyses.getChapterInfos()\n\t\t\t\t\t.filter((el) => el.unFinishCount > 0 || el.element.parentElement?.classList.contains('posCatalog_active'))\n\t\t\t\t\t.map((el) => el.element.parentElement as HTMLElement);\n\t\t\t\tif (elements.length === 0) {\n\t\t\t\t\tconst content = '全部任务点已完成！';\n\t\t\t\t\t$modal.alert({ content: content });\n\t\t\t\t\tCommonProject.scripts.settings.methods.notificationBySetting(content, {\n\t\t\t\t\t\tduration: 0,\n\t\t\t\t\t\textraTitle: '超星学习通学习脚本'\n\t\t\t\t\t});\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tlet nextChapter = elements[0];\n\t\t\t\t// 如果当前章节未完成，则跳转到下一个未完成章节\n\t\t\t\tconst currentIndex = elements.findIndex((el) => el.classList.contains('posCatalog_active'));\n\t\t\t\tif (currentIndex !== -1 && currentIndex + 1 < elements.length) {\n\t\t\t\t\tnextChapter = elements[currentIndex + 1];\n\t\t\t\t\tCXAnalyses.scrollToActiveChapter();\n\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\tnextChapter.querySelector<HTMLElement>('.posCatalog_name')?.click();\n\t\t\t\t\t}, 1000);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (CXProject.scripts.study.cfg.mode === 'next') {\n\t\t\tconst curCourseId = $el<HTMLInputElement>('#curCourseId', top?.document);\n\t\t\tconst curChapterId = $el<HTMLInputElement>('#curChapterId', top?.document);\n\t\t\tconst curClazzId = $el<HTMLInputElement>('#curClazzId', top?.document);\n\t\t\tconst count = $$el('#prev_tab .prev_ul li', top?.document);\n\n\t\t\t// 自动下一个小节（点击下一节）\n\t\t\tif (curChapterId && curCourseId && curClazzId) {\n\t\t\t\t// @ts-ignore\n\t\t\t\ttop._preChapterId = curChapterId.value;\n\t\t\t\tCXAnalyses.scrollToActiveChapter();\n\t\t\t\t// 等待跳转动画完成\n\t\t\t\tawait $.sleep(200);\n\n\t\t\t\t/**\n\t\t\t\t * count, chapterId, courseId, clazzid, knowledgestr, checkType\n\t\t\t\t * checkType 就是询问当前章节还有任务点未完成，是否完成，这里直接不传，默认下一章\n\t\t\t\t */\n\t\t\t\t// @ts-ignore\n\t\t\t\ttop?.PCount.next(count.length.toString(), curChapterId.value, curCourseId.value, curClazzId.value, '');\n\t\t\t} else {\n\t\t\t\t$console.warn('参数错误，无法跳转下一章，请尝试手动切换。');\n\t\t\t}\n\t\t} else {\n\t\t\t$console.warn('未知的跳转模式，请联系作者反馈');\n\t\t}\n\t};\n\n\tif (CXProject.scripts.study.cfg.mode !== 'manually') {\n\t\tconst msg = '页面任务点已完成，即将跳转。';\n\t\t$message.success({ content: msg });\n\t\t$console.info(msg);\n\t\tawait $.sleep(5000);\n\t\tnext();\n\t} else {\n\t\tconst msg = '页面任务点已完成，自动跳转已关闭，请手动跳转。';\n\t\t$message.warn({ content: msg, duration: 0 });\n\t\t$console.warn(msg);\n\t}\n}\n\nfunction searchIFrame(root: Document) {\n\tlet list = Array.from(root.querySelectorAll('iframe'));\n\tconst result: HTMLIFrameElement[] = [];\n\twhile (list.length) {\n\t\tconst frame = list.shift();\n\n\t\ttry {\n\t\t\tif (frame && frame?.contentWindow?.document) {\n\t\t\t\tresult.push(frame);\n\t\t\t\tconst frames = frame?.contentWindow?.document.querySelectorAll('iframe');\n\t\t\t\tlist = list.concat(Array.from(frames || []));\n\t\t\t}\n\t\t} catch (e) {\n\t\t\t// @ts-ignore\n\t\t\tconsole.log(e.message);\n\t\t}\n\t}\n\treturn result;\n}\n\n/**\n * 搜索任务点\n */\nfunction searchJob(\n\topts: typeof CXProject.scripts.study.cfg & {\n\t\tworkOptions: CommonWorkOptions;\n\t},\n\tsearchedJobs: Job[]\n): Job | undefined {\n\tconst knowCardWin = $gm.unsafeWindow;\n\n\tconst searchJobElement = (root: HTMLIFrameElement) => {\n\t\treturn domSearch(\n\t\t\t{\n\t\t\t\tvideojs: '#video,#audio',\n\t\t\t\tchapterTest: '.TiMu',\n\t\t\t\tread: '#img.imglook',\n\t\t\t\tpptWithAudio: '.swiper-container',\n\t\t\t\thyperlink: '#hyperlink',\n\t\t\t\ttimereader: 'iframe[name=\"bookifame\"][src*=\"timing\"]'\n\t\t\t},\n\t\t\troot.contentWindow!.document\n\t\t);\n\t};\n\n\tconst search = (root: HTMLIFrameElement): Job | undefined => {\n\t\tconst win = root.contentWindow;\n\n\t\tconst { videojs, read, chapterTest, hyperlink, pptWithAudio, timereader } = searchJobElement(root);\n\n\t\tif (win && (videojs || read || chapterTest || hyperlink || pptWithAudio || timereader)) {\n\t\t\t// 获取任务点数据字符串\n\t\t\tconst frame_data_str =\n\t\t\t\twin.frameElement?.getAttribute('data') ||\n\t\t\t\t// 带音频的PPT，套了两层iframe\n\t\t\t\t(win.frameElement as HTMLIFrameElement)?.contentWindow?.parent.frameElement?.getAttribute('data') ||\n\t\t\t\t'{}';\n\t\t\tconst frame_data = JSON.parse(frame_data_str);\n\t\t\tconst target_jobid = frame_data.jobid || frame_data._jobid;\n\t\t\tif (!target_jobid) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// 获取任务点数据\n\t\t\tconst attachment: Attachment | undefined = (knowCardWin.attachments as any[]).find((attachment) => {\n\t\t\t\tconst attachment_jobid = attachment.jobid || attachment.property._jobid;\n\t\t\t\tif (!attachment_jobid) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\treturn String(attachment_jobid) === String(target_jobid);\n\t\t\t});\n\n\t\t\t// 任务点去重\n\t\t\tif (attachment && searchedJobs.find((job) => job.mid === attachment.property.mid) === undefined) {\n\t\t\t\tconst { name, title, bookname, author } = attachment.property;\n\t\t\t\tconst jobName = name || title || (bookname ? bookname + author : undefined) || '未知任务';\n\n\t\t\t\tconst work_type = attachment.job ? 'job' : attachment.isPassed ? 'finished' : 'not-job';\n\n\t\t\t\tlet func: { (): Promise<any> } | undefined;\n\n\t\t\t\tif (videojs) {\n\t\t\t\t\tif (!CXProject.scripts.study.cfg.enableMedia) {\n\t\t\t\t\t\tconst msg = `音视频自动学习功能已被关闭（在上方菜单栏，超星学习通-课程学习中开启）。${jobName} 即将跳过`;\n\t\t\t\t\t\t$message.warn({ content: msg, duration: 10 });\n\t\t\t\t\t\t$console.warn(msg);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t// 未完成\n\t\t\t\t\t\t\twork_type === 'job' ||\n\t\t\t\t\t\t\t// 重复学习\n\t\t\t\t\t\t\t(work_type === 'finished' && opts.restudy) ||\n\t\t\t\t\t\t\t// 强制学习\n\t\t\t\t\t\t\t(work_type === 'not-job' && opts.forceLearn)\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tfunc = () => {\n\t\t\t\t\t\t\t\tconst msg =\n\t\t\t\t\t\t\t\t\t`即将${\n\t\t\t\t\t\t\t\t\t\twork_type === 'finished' && opts.restudy\n\t\t\t\t\t\t\t\t\t\t\t? '重新'\n\t\t\t\t\t\t\t\t\t\t\t: work_type === 'not-job' && opts.forceLearn\n\t\t\t\t\t\t\t\t\t\t\t? '强制'\n\t\t\t\t\t\t\t\t\t\t\t: ''\n\t\t\t\t\t\t\t\t\t}播放 : ` + jobName;\n\t\t\t\t\t\t\t\t$message.info({ content: msg });\n\t\t\t\t\t\t\t\t$console.log(msg);\n\t\t\t\t\t\t\t\treturn JobRunner.media(opts, win.document);\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else if (chapterTest) {\n\t\t\t\t\tif (!CXProject.scripts.study.cfg.enableChapterTest) {\n\t\t\t\t\t\tconst msg = `章节测试自动答题功能已被关闭（在上方菜单栏，超星学习通-课程学习中开启）。${jobName} 即将跳过`;\n\t\t\t\t\t\t$message.warn({ content: msg, duration: 10 });\n\t\t\t\t\t\t$console.warn(msg);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst status = win.document.querySelector<HTMLElement>('.testTit_status');\n\n\t\t\t\t\t\t// 已完成\n\t\t\t\t\t\tif (status?.classList.contains('testTit_status_complete')) {\n\t\t\t\t\t\t\tconst msg = `章节测试已完成 : ` + jobName;\n\t\t\t\t\t\t\t$message.success({ content: msg });\n\t\t\t\t\t\t\t$console.log(msg);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t// 未完成\n\t\t\t\t\t\t\t\twork_type === 'job' ||\n\t\t\t\t\t\t\t\t// / 强制学习\n\t\t\t\t\t\t\t\t(work_type === 'not-job' && CommonProject.scripts.settings.cfg['work-when-no-job'])\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tif (opts.workOptions.answererWrappers === undefined || opts.workOptions.answererWrappers.length === 0) {\n\t\t\t\t\t\t\t\t\tanswerWrapperEmptyWarning(0);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tfunc = () => {\n\t\t\t\t\t\t\t\t\t\tconst msg = `开始答题 : ` + jobName;\n\t\t\t\t\t\t\t\t\t\t$message.info({ content: msg });\n\t\t\t\t\t\t\t\t\t\t$console.log(msg);\n\t\t\t\t\t\t\t\t\t\treturn JobRunner.chapter(root, opts.workOptions);\n\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (work_type === 'not-job' && CommonProject.scripts.settings.cfg['work-when-no-job'] === false) {\n\t\t\t\t\t\t\t\tconst msg = `当前作业 ${jobName} 不是任务点，但待完成，如需开启自动答题请前往：通用-全局设置，开启强制答题。`;\n\t\t\t\t\t\t\t\t$message.warn({ content: msg });\n\t\t\t\t\t\t\t\t$console.warn(msg);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else if (read || pptWithAudio || timereader) {\n\t\t\t\t\tif (!CXProject.scripts.study.cfg.enablePPT) {\n\t\t\t\t\t\tconst msg = `PPT/书籍阅读功能已被关闭（在上方菜单栏，超星学习通-课程学习中开启）。${jobName} 即将跳过`;\n\t\t\t\t\t\t$message.warn({ content: msg, duration: 10 });\n\t\t\t\t\t\t$console.warn(msg);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif (attachment.job) {\n\t\t\t\t\t\t\tfunc = () => {\n\t\t\t\t\t\t\t\tconst msg = `正在学习 : ` + jobName;\n\t\t\t\t\t\t\t\t$message.info({ content: msg });\n\t\t\t\t\t\t\t\t$console.log(msg);\n\t\t\t\t\t\t\t\tif (read) {\n\t\t\t\t\t\t\t\t\treturn JobRunner.read(win);\n\t\t\t\t\t\t\t\t} else if (timereader) {\n\t\t\t\t\t\t\t\t\treturn JobRunner.timereader(timereader as HTMLIFrameElement);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\treturn JobRunner.readPPTWithAudio(win);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else if (hyperlink) {\n\t\t\t\t\tif (!CXProject.scripts.study.cfg.enableHyperlink) {\n\t\t\t\t\t\tconst msg = `链接任务点已被关闭（在上方菜单栏，超星学习通-课程学习中开启）。${jobName} 即将跳过`;\n\t\t\t\t\t\t$message.warn({ content: msg, duration: 10 });\n\t\t\t\t\t\t$console.warn(msg);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif (attachment.job) {\n\t\t\t\t\t\t\tfunc = () => {\n\t\t\t\t\t\t\t\tconst msg = `正在完成链接阅读任务 : ` + jobName;\n\t\t\t\t\t\t\t\t$message.info({ content: msg });\n\t\t\t\t\t\t\t\t$console.log(msg);\n\t\t\t\t\t\t\t\treturn JobRunner.hyperlink(hyperlink);\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst job = {\n\t\t\t\t\tmid: attachment.property.mid,\n\t\t\t\t\tattachment: attachment,\n\t\t\t\t\tfunc: func\n\t\t\t\t};\n\n\t\t\t\tsearchedJobs.push(job);\n\n\t\t\t\treturn job;\n\t\t\t}\n\t\t}\n\t};\n\n\tlet job;\n\n\tfor (const iframe of searchIFrame(knowCardWin.document)) {\n\t\tjob = search(iframe);\n\t\tif (job) {\n\t\t\treturn job;\n\t\t}\n\t}\n\n\treturn job;\n}\n\n/**\n * 永久固定显示视频进度\n */\nexport function fixedVideoProgress() {\n\tif (state.study.videojs) {\n\t\tconst { bar } = domSearch({ bar: '.vjs-control-bar' }, state.study.videojs as any);\n\t\tif (bar) {\n\t\t\tbar.style.opacity = '1';\n\t\t}\n\t}\n}\n\n/**\n * 任务点运行器\n */\nconst JobRunner = {\n\t/**\n\t * 播放视频和音频\n\t */\n\tasync media(\n\t\tsetting: {\n\t\t\tplaybackRate: number;\n\t\t\tvolume: number;\n\t\t\tvideoQuizStrategy: VideoQuizStrategy;\n\t\t},\n\t\tdoc: Document\n\t) {\n\t\tconst { playbackRate = 1, volume = 0 } = setting;\n\n\t\tconst media = await waitForMedia({ root: doc });\n\n\t\t// @ts-ignore\n\t\tconst { videojs } = domSearch({ videojs: '#video,#audio' }, doc);\n\n\t\tif (!videojs || !media) {\n\t\t\t$console.error('视频检测不到，请尝试刷新或者手动切换下一章。');\n\t\t\treturn;\n\t\t}\n\n\t\tstate.study.videojs = videojs;\n\t\t// @ts-ignore\n\t\ttop.currentMedia = media;\n\n\t\t// 固定视频进度\n\t\tfixedVideoProgress();\n\n\t\t// 随机作答视频内题目\n\t\tif (setting.videoQuizStrategy === 'random') {\n\t\t\tconst loop = async () => {\n\t\t\t\tconst submitBtn = () => doc.querySelector<HTMLElement>('#videoquiz-submit');\n\t\t\t\tif (submitBtn()) {\n\t\t\t\t\tconst list = Array.from(doc.querySelectorAll<HTMLElement>('.ans-videoquiz-opt label'));\n\t\t\t\t\tconst answer = list[Math.floor(Math.random() * list.length)];\n\t\t\t\t\tanswer?.click();\n\t\t\t\t\tsubmitBtn()?.click();\n\t\t\t\t\tawait $.sleep(3000);\n\t\t\t\t\t// 隐藏视频内题目元素\n\t\t\t\t\tconst container = doc.querySelector<HTMLElement>('#video .ans-videoquiz');\n\t\t\t\t\tconst components = Array.from(doc.querySelectorAll<HTMLElement>('.x-component-default'));\n\t\t\t\t\tif (container) {\n\t\t\t\t\t\tcontainer.remove();\n\t\t\t\t\t}\n\t\t\t\t\tif (components.length) {\n\t\t\t\t\t\tfor (const com of components) {\n\t\t\t\t\t\t\tcom.style.display = 'none';\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tawait $.sleep(3000);\n\t\t\t\tawait loop();\n\t\t\t};\n\t\t\tloop();\n\t\t}\n\n\t\t/**\n\t\t * 视频播放\n\t\t */\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\t// 检测视频\n\t\t\tconst reloadInterval = setInterval(() => {\n\t\t\t\tconst errorDiv = doc.querySelector<HTMLElement>('.vjs-modal-dialog-content');\n\t\t\t\tif (\n\t\t\t\t\t['视频文件损坏', '网络错误导致视频下载中途失败', '视频因格式不支持', '网络的问题无法加载'].some((s) =>\n\t\t\t\t\t\terrorDiv?.innerText.includes(s)\n\t\t\t\t\t)\n\t\t\t\t) {\n\t\t\t\t\t$console.error('检测到视频加载失败，即将跳过视频。');\n\t\t\t\t\t$message.error('检测到视频加载失败，即将跳过视频。');\n\t\t\t\t\tsetTimeout(resolve, 3000);\n\t\t\t\t}\n\t\t\t}, 3000);\n\n\t\t\tconst playFunction = async () => {\n\t\t\t\t// 这里先判断，再检测，否则后续添加多个 await 会导致视频启动等待时间过长，导致用户误认为脚本失效\n\t\t\t\tif (hasFaceRecognition()) await waitForFaceRecognition();\n\t\t\t\tif (hasNewFaceRecognition()) await waitForNewFaceRecognition();\n\t\t\t\tif (media.ended === false) {\n\t\t\t\t\tawait $.sleep(1000);\n\t\t\t\t\tmedia.play();\n\t\t\t\t\tmedia.playbackRate = playbackRate;\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tmedia.addEventListener('pause', playFunction);\n\n\t\t\tmedia.addEventListener('ended', () => {\n\t\t\t\tmedia.removeEventListener('pause', playFunction);\n\t\t\t\t$console.log('视频播放完毕');\n\t\t\t\tclearInterval(reloadInterval);\n\t\t\t\tresolve();\n\t\t\t});\n\n\t\t\t$console.log('视频开始播放');\n\t\t\tmedia.volume = volume;\n\n\t\t\t// 重置视频进度\n\t\t\tmedia.currentTime = 0;\n\n\t\t\t// 使用 setTimeout 解决 The play() request was interrupted by a call to pause() 问题\n\t\t\tsetTimeout(() => {\n\t\t\t\tplayMedia(() => media.play())\n\t\t\t\t\t.then(() => {\n\t\t\t\t\t\tmedia.playbackRate = playbackRate;\n\t\t\t\t\t})\n\t\t\t\t\t.catch(reject);\n\t\t\t}, 200);\n\t\t});\n\t},\n\t/**\n\t * 阅读，PPT\n\t */\n\tasync read(win: Window & { finishJob?: Function }) {\n\t\tconst finishJob = win.finishJob;\n\t\tif (finishJob) finishJob();\n\t\tawait $.sleep(3000);\n\t},\n\t/**\n\t * 时间阅读\n\t */\n\tasync timereader(iframe: HTMLIFrameElement) {\n\t\tconst src = iframe.getAttribute('src')?.toString() || '';\n\t\tconst timing = src ? parseInt(new URL(src).searchParams.get('timing')?.toString() || '60') : 60;\n\t\t$message.info({\n\t\t\tcontent: `正在学习长时阅读任务，请稍等，不要切换..（预计${(timing + 3) * 3}秒）`,\n\t\t\tduration: (timing + 3) * 3\n\t\t});\n\t\t// 这里依靠 onactive 第一个跨域处理程序，等待处理后继续即可\n\t\t// 一共有三步，第一步等待timing，然后切换正文页，然后是封底页，最后点击一次PPT文档切换界面，即可完成任务\n\t\tawait $.sleep((timing + 3) * 3 * 1000);\n\t\t$message.success('长时阅读任务完成！');\n\t\tawait $.sleep(5000);\n\t},\n\t/**\n\t * 章节测验\n\t */\n\tasync chapter(\n\t\tframe: HTMLIFrameElement,\n\t\t{\n\t\t\tanswererWrappers,\n\t\t\tperiod,\n\t\t\tupload,\n\t\t\tthread,\n\t\t\tstopSecondWhenFinish,\n\t\t\tredundanceWordsText,\n\t\t\tanswerSeparators,\n\t\t\tanswerMatchMode\n\t\t}: CommonWorkOptions\n\t) {\n\t\tif (answererWrappers === undefined || answererWrappers.length === 0) {\n\t\t\treturn answerWrapperEmptyWarning(0);\n\t\t}\n\n\t\t$console.info('开始章节测试');\n\t\tconst visual_state = CommonProject.scripts.render.cfg.visual;\n\n\t\tconst frameWindow = frame.contentWindow;\n\t\tconst { TiMu } = domSearchAll({ TiMu: '.TiMu' }, frameWindow!.document);\n\n\t\t// 最大化面板\n\t\tCORSUtils.panelNormal();\n\t\tCommonProject.scripts.workResults.methods.init();\n\t\t// 固定显示答题结果面板\n\t\tCORSUtils.pinWorkPanel();\n\n\t\tconst chapterTestTaskQuestionTitleTransform = (titles: (HTMLElement | undefined)[]) => {\n\t\t\tconst removed = removeRedundantWords(\n\t\t\t\ttitles.map((t) => (t ? optimizationElementWithImage(t, true).innerText : '')).join(','),\n\t\t\t\tredundanceWordsText.split('\\n')\n\t\t\t);\n\n\t\t\treturn (\n\t\t\t\tremoved\n\t\t\t\t\t.trim()\n\t\t\t\t\t/** 超星旧版作业题目冗余数据 */\n\t\t\t\t\t.replace(/^\\d+[。、.]/, '')\n\t\t\t\t\t.replace(/（\\d+\\.\\d+分）/, '')\n\t\t\t\t\t.replace(/\\(..题, \\d+?分\\)/, '')\n\t\t\t\t\t.replace(/\\(..题, \\d+\\.\\d+分\\)/, '')\n\t\t\t\t\t.replace(/[[(【（](..题|名词解释|完形填空|阅读理解)[\\])】）]/, '')\n\t\t\t\t\t.trim()\n\t\t\t);\n\t\t};\n\n\t\t/** 新建答题器 */\n\t\tconst worker = new OCSWorker({\n\t\t\troot: TiMu,\n\t\t\telements: {\n\t\t\t\ttitle: '.Zy_TItle .clearfix',\n\t\t\t\t/**\n\t\t\t\t * 兼容各种选项\n\t\t\t\t *\n\t\t\t\t * ul li .after 单选多选\n\t\t\t\t * ul li label:not(.after) 判断题\n\t\t\t\t * ul li textarea 填空题\n\t\t\t\t */\n\t\t\t\toptions: 'ul li .after,ul li textarea,ul textarea,ul li label:not(.before)',\n\t\t\t\ttype: 'input[id^=\"answertype\"]',\n\t\t\t\tlineAnswerInput: '.line_answer input[name^=answer]',\n\t\t\t\tlineSelectBox: '.line_answer_ct .selectBox '\n\t\t\t},\n\t\t\tthread: thread ?? 1,\n\t\t\tanswerSeparators: answerSeparators.split(',').map((s) => s.trim()),\n\t\t\tanswerMatchMode: answerMatchMode,\n\t\t\t/** 默认搜题方法构造器 */\n\t\t\tanswerer: (elements, ctx) => {\n\t\t\t\tconst title = chapterTestTaskQuestionTitleTransform(elements.title);\n\t\t\t\tif (title) {\n\t\t\t\t\tconst typeInput = elements.type[0] as HTMLInputElement;\n\n\t\t\t\t\treturn CommonProject.scripts.apps.methods.searchAnswerInCaches(title, async () => {\n\t\t\t\t\t\tawait $.sleep((period ?? 3) * 1000);\n\t\t\t\t\t\treturn defaultAnswerWrapperHandler(answererWrappers, {\n\t\t\t\t\t\t\ttype: (typeInput ? getQuestionType(parseInt(typeInput.value)) : undefined) || 'unknown',\n\t\t\t\t\t\t\ttitle,\n\t\t\t\t\t\t\toptions:\n\t\t\t\t\t\t\t\tctx.type === 'completion'\n\t\t\t\t\t\t\t\t\t? ''\n\t\t\t\t\t\t\t\t\t: ctx.elements.options.map((o) => optimizationElementWithImage(o, true).innerText).join('\\n')\n\t\t\t\t\t\t});\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\tthrow new Error('题目为空，请查看题目是否为空，或者忽略此题');\n\t\t\t\t}\n\t\t\t},\n\n\t\t\twork: async (ctx) => {\n\t\t\t\tconst { elements, searchInfos } = ctx;\n\t\t\t\tconst typeInput = elements.type[0] as HTMLInputElement;\n\t\t\t\tconst type = typeInput ? getQuestionType(parseInt(typeInput.value)) : undefined;\n\n\t\t\t\tif (type && (type === 'completion' || type === 'multiple' || type === 'judgement' || type === 'single')) {\n\t\t\t\t\tconst resolver = createDefaultQuestionResolver(ctx)[type];\n\n\t\t\t\t\tconst handler: DefaultWork<any>['handler'] = (type, answer, option, ctx) => {\n\t\t\t\t\t\tif (type === 'judgement' || type === 'single' || type === 'multiple') {\n\t\t\t\t\t\t\t// 检查是否已经选择\n\t\t\t\t\t\t\tconst checked =\n\t\t\t\t\t\t\t\toption?.parentElement?.querySelector('label input')?.getAttribute('checked') === 'checked' ||\n\t\t\t\t\t\t\t\t// 适配2023/9月最新版本\n\t\t\t\t\t\t\t\toption?.parentElement?.getAttribute('aria-checked') === 'true';\n\t\t\t\t\t\t\tif (checked) {\n\t\t\t\t\t\t\t\t// 跳过\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\toption?.click();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (type === 'completion' && answer.trim()) {\n\t\t\t\t\t\t\tconst text = option?.parentElement?.querySelector('textarea');\n\t\t\t\t\t\t\tconst textareaFrame = option?.parentElement?.querySelector('iframe');\n\t\t\t\t\t\t\tif (text) {\n\t\t\t\t\t\t\t\ttext.value = answer;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (textareaFrame?.contentDocument) {\n\t\t\t\t\t\t\t\ttextareaFrame.contentDocument.body.innerHTML = answer;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (option?.parentElement?.parentElement) {\n\t\t\t\t\t\t\t\t/** 如果存在保存按钮则点击 */\n\t\t\t\t\t\t\t\t$el('[onclick*=saveQuestion]', option.parentElement.parentElement)?.click();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\n\t\t\t\t\treturn await resolver(\n\t\t\t\t\t\tsearchInfos,\n\t\t\t\t\t\telements.options.map((option) => optimizationElementWithImage(option)),\n\t\t\t\t\t\thandler\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\t// 连线题自定义处理\n\t\t\t\telse if (type && type === 'line') {\n\t\t\t\t\tfor (const answers of searchInfos.map((info) => info.results.map((res) => res.answer))) {\n\t\t\t\t\t\tlet ans = answers;\n\t\t\t\t\t\tif (ans.length === 1) {\n\t\t\t\t\t\t\tans = splitAnswer(ans[0]);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (ans.filter(Boolean).length !== 0 && elements.lineAnswerInput) {\n\t\t\t\t\t\t\t//  选择答案\n\t\t\t\t\t\t\tfor (let index = 0; index < elements.lineSelectBox.length; index++) {\n\t\t\t\t\t\t\t\tconst box = elements.lineSelectBox[index];\n\t\t\t\t\t\t\t\tif (ans[index]) {\n\t\t\t\t\t\t\t\t\t$el(`li[data=${ans[index]}] a`, box)?.click();\n\t\t\t\t\t\t\t\t\tawait $.sleep(200);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\treturn { finish: true };\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\treturn { finish: false };\n\t\t\t\t}\n\n\t\t\t\treturn { finish: false };\n\t\t\t},\n\n\t\t\t/** 完成答题后 */\n\t\t\tasync onResultsUpdate(curr, _, res) {\n\t\t\t\tCommonProject.scripts.workResults.methods.setResults(\n\t\t\t\t\tsimplifyWorkResult(res, chapterTestTaskQuestionTitleTransform)\n\t\t\t\t);\n\n\t\t\t\tif (curr.result?.finish) {\n\t\t\t\t\tCommonProject.scripts.apps.methods.addQuestionCacheFromWorkResult(\n\t\t\t\t\t\tsimplifyWorkResult([curr], chapterTestTaskQuestionTitleTransform)\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tCommonProject.scripts.workResults.methods.updateWorkStateByResults(res);\n\n\t\t\t\t// 没有完成时随机作答\n\t\t\t\tif (curr.result?.finish === false && curr.resolved === true) {\n\t\t\t\t\tconst options = curr.ctx?.elements?.options || [];\n\n\t\t\t\t\tconst typeInput = curr.ctx?.elements?.type[0] as HTMLInputElement | undefined;\n\t\t\t\t\tconst type = typeInput ? getQuestionType(parseInt(typeInput.value)) : undefined;\n\n\t\t\t\t\tconst commonSetting = CommonProject.scripts.settings.cfg;\n\n\t\t\t\t\tif (\n\t\t\t\t\t\tcommonSetting['randomWork-choice'] &&\n\t\t\t\t\t\t(type === 'judgement' || type === 'single' || type === 'multiple')\n\t\t\t\t\t) {\n\t\t\t\t\t\t$console.log('正在随机作答');\n\n\t\t\t\t\t\tconst option = options[Math.floor(Math.random() * options.length)];\n\t\t\t\t\t\t// @ts-ignore 随机选择选项\n\t\t\t\t\t\toption?.parentElement?.querySelector('a,label')?.click();\n\t\t\t\t\t} else if (commonSetting['randomWork-complete'] && type === 'completion') {\n\t\t\t\t\t\t$console.log('正在随机作答');\n\n\t\t\t\t\t\t// 随机填写答案\n\t\t\t\t\t\tfor (const option of options) {\n\t\t\t\t\t\t\tconst textarea = option?.parentElement?.querySelector('textarea');\n\t\t\t\t\t\t\tconst completeTexts = commonSetting['randomWork-completeTexts-textarea'].split('\\n').filter(Boolean);\n\t\t\t\t\t\t\tconst text = completeTexts[Math.floor(Math.random() * completeTexts.length)];\n\t\t\t\t\t\t\tconst textareaFrame = option?.parentElement?.querySelector('iframe');\n\n\t\t\t\t\t\t\tif (text) {\n\t\t\t\t\t\t\t\tif (textarea) {\n\t\t\t\t\t\t\t\t\ttextarea.value = text;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (textareaFrame?.contentDocument) {\n\t\t\t\t\t\t\t\t\ttextareaFrame.contentDocument.body.innerHTML = text;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t$console.error('请设置随机填空的文案');\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tawait $.sleep(500);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\tasync onElementSearched(elements) {\n\t\t\t\tconst typeInput = elements.type[0] as HTMLInputElement;\n\t\t\t\tconst type = typeInput ? getQuestionType(parseInt(typeInput.value)) : undefined;\n\n\t\t\t\t/** 判断题转换成文字，以便于答题程序判断 */\n\t\t\t\tif (type === 'judgement') {\n\t\t\t\t\telements.options.forEach((option) => {\n\t\t\t\t\t\tconst opt = option?.textContent?.trim() || '';\n\t\t\t\t\t\tif (opt.includes('对') || opt.includes('错')) {\n\t\t\t\t\t\t\t// 2023/8/5日后超星已修复判断题，将图片修改成文字，如果已经有对错的文本，则不需要再转换\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// 如果是英语的对错题目，他是一个英文单词 True,False\n\t\t\t\t\t\telse if (opt === 'True') {\n\t\t\t\t\t\t\toption.textContent = '√';\n\t\t\t\t\t\t} else if (opt === 'False') {\n\t\t\t\t\t\t\toption.textContent = 'x';\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// 支持香港地区的繁体字\n\t\t\t\t\t\telse if (opt === '對') {\n\t\t\t\t\t\t\toption.textContent = '√';\n\t\t\t\t\t\t} else if (opt === '錯') {\n\t\t\t\t\t\t\toption.textContent = 'x';\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tconst ri = option.querySelector('.ri');\n\t\t\t\t\t\t\tconst span = document.createElement('span');\n\t\t\t\t\t\t\tspan.innerText = ri ? '√' : '×';\n\t\t\t\t\t\t\toption.appendChild(span);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\tconst results = await worker.doWork();\n\n\t\tconst msg = `答题完成，将等待 ${stopSecondWhenFinish} 秒后进行保存或提交。`;\n\t\t$console.info(msg);\n\t\t$message.info({ content: msg, duration: stopSecondWhenFinish });\n\t\tawait $.sleep(stopSecondWhenFinish * 1000);\n\n\t\t// 处理提交\n\t\tawait worker.uploadHandler({\n\t\t\ttype: upload,\n\t\t\tresults,\n\t\t\tasync callback(finishedRate, uploadable) {\n\t\t\t\tconst msg = `完成率 ${finishedRate.toFixed(2)}% :  ${uploadable ? '3秒后将自动提交' : '3秒后将自动保存'} `;\n\t\t\t\t$console.info(msg);\n\t\t\t\t$message.success({ content: msg, duration: 3 });\n\n\t\t\t\tawait $.sleep(3000);\n\n\t\t\t\tif (uploadable) {\n\t\t\t\t\t// @ts-ignore 提交\n\t\t\t\t\tframeWindow.btnBlueSubmit();\n\n\t\t\t\t\tawait $.sleep(3000);\n\t\t\t\t\t/** 确定按钮 */\n\t\t\t\t\t// @ts-ignore 确定\n\t\t\t\t\tframeWindow.submitCheckTimes();\n\t\t\t\t\t// @ts-ignore 2024/4 更新后上方函数无法关闭弹窗，需要手动关闭确定弹窗\n\t\t\t\t\ttop.$('#workpop').hide();\n\t\t\t\t} else {\n\t\t\t\t\t// @ts-ignore 禁止弹窗\n\t\t\t\t\tframeWindow.alert = () => {};\n\t\t\t\t\t// @ts-ignore 暂时保存\n\t\t\t\t\tframeWindow.noSubmit();\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\t// 还原尺寸状态\n\t\tif (visual_state === 'minimize' && CommonProject.scripts.render.cfg.visual !== 'minimize') {\n\t\t\tCORSUtils.panelMinimize();\n\t\t}\n\n\t\tworker.emit('done');\n\t},\n\t/**\n\t * 带音频的PPT\n\t */\n\tasync readPPTWithAudio(win: Window & { swiperNext?: Function }) {\n\t\t// 关闭音视频声音\n\t\twin.document.querySelectorAll('audio').forEach((audio) => {\n\t\t\taudio.addEventListener('play', () => {\n\t\t\t\taudio.muted = true;\n\t\t\t});\n\t\t});\n\n\t\t// 阅读PPT\n\t\tconst len = win.document.querySelectorAll('.swiper-container .swiper-slide').length;\n\t\tfor (let index = 0; index < len; index++) {\n\t\t\twin.swiperNext?.();\n\t\t\tawait $.sleep(1000);\n\t\t}\n\t\tawait $.sleep(3000);\n\t},\n\t/**\n\t * 链接任务点\n\t */\n\tasync hyperlink(a: HTMLElement) {\n\t\t// 修改点击事件，防止出现弹窗\n\t\tconst _click = a.onclick;\n\t\ta.onclick = () => false;\n\t\t// 点击完成\n\t\ta.click();\n\t\t// 还原点击事件\n\t\ta.onclick = _click;\n\t\tawait $.sleep(3000);\n\t}\n};\n\n/**\n * cx 题目类型 ：\n * 0 单选题\n * 1 多选题\n * 2 简答题\n * 3 判断题\n * 4 填空题\n * 5 名词解释\n * 6 论述题\n * 7 计算题\n * 8 其他题(大概率是填空题)\n * 9 分录题\n * 10 资料题\n * 11 连线题\n * 14 完形填空\n * 15 阅读理解\n */\nfunction getQuestionType(\n\tval: number\n): 'single' | 'multiple' | 'judgement' | 'completion' | 'line' | 'fill' | 'reader' | undefined {\n\treturn val === 0\n\t\t? 'single'\n\t\t: val === 1\n\t\t? 'multiple'\n\t\t: val === 3\n\t\t? 'judgement'\n\t\t: [2, 4, 5, 6, 7, 8, 9, 10].some((t) => t === val)\n\t\t? 'completion'\n\t\t: val === 11\n\t\t? 'line'\n\t\t: val === 14\n\t\t? 'fill'\n\t\t: val === 15\n\t\t? 'reader'\n\t\t: undefined;\n}\n\n/** 阅读理解和完形填空的共同处理器 */\nasync function readerAndFillHandle(searchInfos: SearchInformation[], list: HTMLElement[]) {\n\tfor (const answers of searchInfos.map((info) => info.results.map((res) => res.answer))) {\n\t\tlet ans = answers;\n\n\t\tif (ans.length === 1) {\n\t\t\tans = splitAnswer(ans[0]);\n\t\t}\n\n\t\tif (ans.filter(Boolean).length !== 0 && list.length !== 0) {\n\t\t\tfor (let index = 0; index < ans.length; index++) {\n\t\t\t\tconst item = list[index];\n\t\t\t\tif (item) {\n\t\t\t\t\t/** 获取每个小题中的准确答案选项 并点击 */\n\t\t\t\t\t$el(`span.saveSingleSelect[data=\"${ans[index]}\"]`, item)?.click();\n\t\t\t\t\tawait $.sleep(200);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn { finish: true };\n\t\t}\n\t}\n\n\treturn { finish: false };\n}\n\nfunction hasFaceRecognition() {\n\t// 人脸元素有时候 src 属性为空字符串，所以这里需要判断 src 是否为空字符串，如是则人脸识别会出现。\n\tconst faces = $$el<HTMLImageElement>('#fcqrimg', top?.document);\n\tlet active = false;\n\tfor (const face of faces) {\n\t\tconst src = face.getAttribute('src');\n\t\tif (src) {\n\t\t\tactive = true;\n\t\t\tbreak;\n\t\t}\n\t}\n\treturn active;\n}\n\nfunction hasNewFaceRecognition() {\n\tconst faces = $$el<HTMLImageElement>('.chapterVideoFaceMaskDiv', top?.document);\n\tlet active = false;\n\tfor (const face of faces) {\n\t\tif (face.style.display !== 'none') {\n\t\t\tactive = true;\n\t\t\tbreak;\n\t\t}\n\t}\n\treturn active;\n}\n\n/**\n * 等待新版人脸识别，视频开头会出现的人脸识别\n */\nfunction waitForNewFaceRecognition() {\n\tlet notified = false;\n\n\treturn new Promise<void>((resolve) => {\n\t\tconst interval = setInterval(() => {\n\t\t\t// 人脸元素有时候 src 属性为空字符串，所以这里需要判断 src 是否为空字符串，如是则人脸识别会出现。\n\t\t\tconst active = hasNewFaceRecognition();\n\t\t\tif (active) {\n\t\t\t\tif (!notified) {\n\t\t\t\t\tnotified = true;\n\t\t\t\t\tconst msg = '检测到人脸识别，请手动进行识别后脚本才会继续运行。';\n\t\t\t\t\tif (CXProject.scripts.study.cfg.notifyWhenHasFaceRecognition) {\n\t\t\t\t\t\tCommonProject.scripts.settings.methods.notificationBySetting(msg, { duration: 0 });\n\t\t\t\t\t}\n\t\t\t\t\t$message.warn({ content: msg, duration: 0 });\n\t\t\t\t\t$console.warn(msg);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tclearInterval(interval);\n\t\t\t\tresolve();\n\t\t\t}\n\t\t}, 3000);\n\t});\n}\n/**\n * 等待人脸识别\n */\nfunction waitForFaceRecognition() {\n\tlet notified = false;\n\n\treturn new Promise<void>((resolve) => {\n\t\tconst interval = setInterval(() => {\n\t\t\t// 人脸元素有时候 src 属性为空字符串，所以这里需要判断 src 是否为空字符串，如是则人脸识别会出现。\n\t\t\tconst active = hasFaceRecognition();\n\t\t\tif (active) {\n\t\t\t\tif (!notified) {\n\t\t\t\t\tnotified = true;\n\t\t\t\t\tconst msg = '检测到人脸识别，请手动进行识别后脚本才会继续运行。';\n\t\t\t\t\tif (CXProject.scripts.study.cfg.notifyWhenHasFaceRecognition) {\n\t\t\t\t\t\tCommonProject.scripts.settings.methods.notificationBySetting(msg, { duration: 0 });\n\t\t\t\t\t}\n\t\t\t\t\t$message.warn({ content: msg, duration: 0 });\n\t\t\t\t\t$console.warn(msg);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tclearInterval(interval);\n\t\t\t\tresolve();\n\t\t\t}\n\t\t}, 3000);\n\t});\n}\n\n/**\n * 答题程序位于其他 iframe 中，而 methods.pin 等 是依赖于 setTab 方法的，所以需要重新定义一个顶层函数来调用 pin 方法\n * 跨域调用\n */\n\nconst CORSUtils = {\n\tpinWorkPanel: cors.defineTopFunction(() => {\n\t\tCommonProject.scripts.render.methods.pin(CommonProject.scripts.workResults);\n\t}),\n\tpanelNormal: cors.defineTopFunction(() => {\n\t\tCommonProject.scripts.render.methods.normal();\n\t}),\n\tpanelMinimize: cors.defineTopFunction(() => {\n\t\tCommonProject.scripts.render.methods.minimize();\n\t})\n};\n"
  },
  {
    "path": "packages/scripts/src/projects/icourse.ts",
    "content": "import { $, OCSWorker, RemotePage, defaultAnswerWrapperHandler } from '@ocsjs/core';\nimport { $message, Project, Script, $ui, $store } from 'easy-us';\nimport { CommonWorkOptions, playMedia } from '../utils';\nimport { CommonProject } from './common';\nimport { commonWork, optimizationElementWithImage, removeRedundantWords, simplifyWorkResult } from '../utils/work';\nimport { $console, BackgroundProject } from './background';\nimport { $playwright } from '../utils/app';\nimport { waitForElement, waitForMedia } from '../utils/study';\nimport { playbackRate, volume, workNotes } from '../utils/configs';\nimport { $render } from '../utils/render';\n\nconst $msg_and_log = (type: 'info' | 'warn' | 'error', msg: string) => {\n\t$message[type](msg);\n\t$console[type](msg);\n};\n\nconst state = {\n\tcurrentMedia: undefined as HTMLMediaElement | undefined,\n\tcurrentUrlHash: '',\n\tcurrentRunningScriptName: '',\n\tcurrent_job_id: ''\n};\n\nexport const ICourseProject = Project.create({\n\tname: '中国大学MOOC',\n\tdomains: ['icourse163.org'],\n\tscripts: {\n\t\tdispatcher: new Script({\n\t\t\tname: '调度器',\n\t\t\thideInPanel: true,\n\t\t\tmatches: [['所有页面', 'icourse163.org']],\n\t\t\toncomplete() {\n\t\t\t\tsetInterval(() => {\n\t\t\t\t\tconst hash = new URL(window.location.href).hash;\n\t\t\t\t\tif (state.currentUrlHash !== hash) {\n\t\t\t\t\t\tstate.currentRunningScriptName = '';\n\t\t\t\t\t}\n\t\t\t\t\tstate.currentUrlHash = hash;\n\n\t\t\t\t\tfor (const key in ICourseProject.scripts) {\n\t\t\t\t\t\tif (Object.prototype.hasOwnProperty.call(ICourseProject.scripts, key)) {\n\t\t\t\t\t\t\tconst script = (ICourseProject.scripts as any)[key] as Script<{\n\t\t\t\t\t\t\t\trunAtHash: { defaultValue: string | string[] };\n\t\t\t\t\t\t\t}>;\n\t\t\t\t\t\t\tconst runAtHash = Array.isArray(script.cfg.runAtHash) ? script.cfg.runAtHash : [script.cfg.runAtHash];\n\t\t\t\t\t\t\tif (runAtHash.length && runAtHash.some((h) => state.currentUrlHash.includes(h))) {\n\t\t\t\t\t\t\t\tif (state.currentRunningScriptName !== script.name) {\n\t\t\t\t\t\t\t\t\tstate.currentRunningScriptName = script.name;\n\t\t\t\t\t\t\t\t\tstate.current_job_id = Math.random().toString(16).slice(2);\n\t\t\t\t\t\t\t\t\tscript.methods?.main?.({\n\t\t\t\t\t\t\t\t\t\tcanRun: () => {\n\t\t\t\t\t\t\t\t\t\t\treturn state.currentUrlHash && runAtHash.some((h) => state.currentUrlHash.includes(h));\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\tjob_id: state.current_job_id\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}, 1000);\n\t\t\t}\n\t\t}),\n\t\tguide: new Script({\n\t\t\tname: '💡 使用提示',\n\t\t\tmatches: [['', 'icourse163.org']],\n\t\t\t// 添加版本号是因为只有 notes 会强制更新，其他配置项不会，如果需要修改 runAtHash ，需要更新版本号\n\t\t\tnamespace: 'icourse.guide-v1',\n\t\t\tconfigs: {\n\t\t\t\tnotes: {\n\t\t\t\t\tdefaultValue: $ui.notes(['手动进入任意课程里的课件/作业，即可开始自动学习']).outerHTML\n\t\t\t\t},\n\t\t\t\trunAtHash: {\n\t\t\t\t\t// 在没有进入学习页面前，都显示提示\n\t\t\t\t\tdefaultValue: ['/home/course']\n\t\t\t\t}\n\t\t\t},\n\t\t\tmethods() {\n\t\t\t\treturn {\n\t\t\t\t\tmain: async () => {\n\t\t\t\t\t\tconsole.log(state, this.cfg.runAtHash);\n\n\t\t\t\t\t\tCommonProject.scripts.render.methods.pin(this);\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t}\n\t\t}),\n\t\tstudy: new Script({\n\t\t\tname: '🖥️ 学习脚本',\n\t\t\t// 添加版本号是因为只有 notes 会强制更新，其他配置项不会，如果需要修改 runAtHash ，需要更新版本号\n\t\t\tnamespace: 'icourse.study-v1',\n\t\t\tmatches: [\n\t\t\t\t['MOOC作业页面', 'icourse163.org/learn'],\n\t\t\t\t['SPOC作业页面', 'icourse163.org/spoc/learn']\n\t\t\t],\n\t\t\tconfigs: {\n\t\t\t\tnotes: {\n\t\t\t\t\tdefaultValue: $ui.notes([\n\t\t\t\t\t\t'请勿在使用过程中最小化浏览器',\n\t\t\t\t\t\t'自动讨论默认关闭，如需开启请在下方设置中设置',\n\t\t\t\t\t\t'作业请完成课程后手动进入'\n\t\t\t\t\t]).outerHTML\n\t\t\t\t},\n\t\t\t\trunAtHash: {\n\t\t\t\t\tdefaultValue: '/learn/content?type=detail'\n\t\t\t\t},\n\t\t\t\tplaybackRate: playbackRate,\n\t\t\t\tvolume: volume,\n\t\t\t\treadSpeed: {\n\t\t\t\t\tlabel: 'PPT翻阅速度（秒）',\n\t\t\t\t\tattrs: { type: 'number', step: '1', min: '1', max: '10' },\n\t\t\t\t\tdefaultValue: 1\n\t\t\t\t},\n\t\t\t\tdiscussionStrategy: {\n\t\t\t\t\tlabel: '讨论自动回复方式',\n\t\t\t\t\ttag: 'select',\n\t\t\t\t\tdefaultValue: 'not-reply' as 'not-reply' | 'max-show-up' | 'max-fav' | 'use-newest',\n\t\t\t\t\toptions: [\n\t\t\t\t\t\t['not-reply', '不讨论回复'],\n\t\t\t\t\t\t['max-show-up', '获取出现最多的评论进行回复'],\n\t\t\t\t\t\t['max-fav', '获取最多点赞的评论进行回复'],\n\t\t\t\t\t\t['use-newest', '获取最新的评论进行回复']\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\tenableChapterTest: {\n\t\t\t\t\tlabel: '随堂测验自动答题',\n\t\t\t\t\tattrs: {\n\t\t\t\t\t\ttype: 'checkbox',\n\t\t\t\t\t\ttitle: '是否开启随堂测验自动答题，默认关闭，测试时只需点击即可完成测验，但这里保留选项防止需要开启。'\n\t\t\t\t\t},\n\t\t\t\t\tdefaultValue: false\n\t\t\t\t}\n\t\t\t},\n\t\t\toncomplete() {\n\t\t\t\tthis.onConfigChange('playbackRate', (playbackRate) => {\n\t\t\t\t\tstate.currentMedia && (state.currentMedia.playbackRate = parseFloat(playbackRate.toString()));\n\t\t\t\t});\n\t\t\t\tthis.onConfigChange('volume', (v) => state.currentMedia && (state.currentMedia.volume = v));\n\t\t\t},\n\t\t\tmethods() {\n\t\t\t\treturn {\n\t\t\t\t\tmain: async ({ canRun, job_id }: { canRun: () => boolean; job_id: string }) => {\n\t\t\t\t\t\tCommonProject.scripts.render.methods.pin(this);\n\n\t\t\t\t\t\tconst remotePage = await BackgroundProject.scripts.dev.methods.getRemotePlaywrightCurrentPage();\n\t\t\t\t\t\t// 检查是否为软件环境\n\t\t\t\t\t\tif (!remotePage) {\n\t\t\t\t\t\t\treturn $playwright.showError();\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// 移动窗口到边缘\n\t\t\t\t\t\t$render.moveToEdge();\n\n\t\t\t\t\t\t/**\n\t\t\t\t\t\t * 处理视频弹窗题目\n\t\t\t\t\t\t */\n\t\t\t\t\t\tconst handleVideoTest = async () => {\n\t\t\t\t\t\t\tif (!canRun() || job_id !== state.current_job_id) return;\n\t\t\t\t\t\t\tsetTimeout(async () => {\n\t\t\t\t\t\t\t\tconst question = document.querySelector('.u-questionItem');\n\t\t\t\t\t\t\t\tconst media = document.querySelector('video,audio');\n\t\t\t\t\t\t\t\tif (question && media) {\n\t\t\t\t\t\t\t\t\t$msg_and_log('info', '检测到视频弹窗测验，开始答题');\n\t\t\t\t\t\t\t\t\tawait new Promise<void>((resolve) => {\n\t\t\t\t\t\t\t\t\t\tICourseProject.scripts.work.methods.start('chapter-test', canRun, (worker) => {\n\t\t\t\t\t\t\t\t\t\t\tconsole.log('worker', worker);\n\t\t\t\t\t\t\t\t\t\t\tworker.once('done', resolve);\n\t\t\t\t\t\t\t\t\t\t\tworker.once('close', resolve);\n\t\t\t\t\t\t\t\t\t\t\tworker.once('stop', resolve);\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\tawait $.sleep(1000);\n\t\t\t\t\t\t\t\t\t// 点击继续学习\n\t\t\t\t\t\t\t\t\tawait remotePage.click('.j-unitctBox .u-btn-default.j-continue');\n\t\t\t\t\t\t\t\t\t$msg_and_log('info', '测验完成');\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\thandleVideoTest();\n\t\t\t\t\t\t\t}, 3000);\n\t\t\t\t\t\t};\n\t\t\t\t\t\thandleVideoTest();\n\n\t\t\t\t\t\tconst study = async () => {\n\t\t\t\t\t\t\tconst lessonName = document.querySelector('.j-lesson .j-up')?.textContent;\n\t\t\t\t\t\t\tconst currentUnitItem = document.querySelector('.j-unitslist  li.current');\n\t\t\t\t\t\t\tconst unitName = currentUnitItem?.querySelector('.unit-name')?.textContent;\n\n\t\t\t\t\t\t\t$msg_and_log('info', `正在学习：${lessonName || ''} - ${unitName || ''}`);\n\n\t\t\t\t\t\t\tconst isJob = (iconName: string) => currentUnitItem?.querySelector(`[class*=${iconName}]`);\n\n\t\t\t\t\t\t\tlet hasJob = true;\n\n\t\t\t\t\t\t\tif (isJob('u-icon-video')) {\n\t\t\t\t\t\t\t\tawait waitForElement('video, audio');\n\t\t\t\t\t\t\t\tawait watchMedia(this.cfg.playbackRate, this.cfg.volume);\n\t\t\t\t\t\t\t\t$msg_and_log('info', '视频学习完成');\n\t\t\t\t\t\t\t} else if (isJob('u-icon-doc')) {\n\t\t\t\t\t\t\t\tawait waitForElement('.ux-pdf-reader');\n\t\t\t\t\t\t\t\tawait readPPT(remotePage, this.cfg.readSpeed);\n\t\t\t\t\t\t\t\t$msg_and_log('info', 'PPT完成');\n\t\t\t\t\t\t\t} else if (isJob('u-icon-discuss')) {\n\t\t\t\t\t\t\t\tawait waitForElement('.j-reply-all');\n\t\t\t\t\t\t\t\tawait discussion(remotePage, this.cfg.discussionStrategy);\n\t\t\t\t\t\t\t\t$msg_and_log('info', '讨论完成');\n\t\t\t\t\t\t\t} else if (isJob('u-icon-test')) {\n\t\t\t\t\t\t\t\tconst replay = await waitForElement('.j-replay');\n\t\t\t\t\t\t\t\tif (replay?.style.display === 'none') {\n\t\t\t\t\t\t\t\t\tif (this.cfg.enableChapterTest) {\n\t\t\t\t\t\t\t\t\t\tawait new Promise<void>((resolve) => {\n\t\t\t\t\t\t\t\t\t\t\tICourseProject.scripts.work.methods.start('chapter-test', canRun, (worker) => {\n\t\t\t\t\t\t\t\t\t\t\t\tconsole.log('worker', worker);\n\n\t\t\t\t\t\t\t\t\t\t\t\tworker.once('done', resolve);\n\t\t\t\t\t\t\t\t\t\t\t\tworker.once('close', resolve);\n\t\t\t\t\t\t\t\t\t\t\t\tworker.once('stop', resolve);\n\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\t\t\t\t$msg_and_log('info', '测验完成');\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t$msg_and_log(\n\t\t\t\t\t\t\t\t\t\t\t'warn',\n\t\t\t\t\t\t\t\t\t\t\t'随堂测验自动答题功能已关闭（上方菜单栏-中国大学MOOC-学习脚本中开启），即将跳过。'\n\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t$msg_and_log('info', '随堂测验已完成，即将跳过。');\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else if (isJob('u-icon-text')) {\n\t\t\t\t\t\t\t\tconst key = 'text-job-reload';\n\t\t\t\t\t\t\t\tif ((await $store.getTab(key)) === '1') {\n\t\t\t\t\t\t\t\t\t$store.setTab(key, '0');\n\t\t\t\t\t\t\t\t\t$msg_and_log('info', '文档已完成，即将跳过。');\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t// 需要刷新才能完成富文本文档任务点\n\t\t\t\t\t\t\t\t\t$store.setTab(key, '1');\n\t\t\t\t\t\t\t\t\t// 文档无需处理\n\t\t\t\t\t\t\t\t\t$msg_and_log('info', '文档无需处理，将在刷新完成后跳过。');\n\t\t\t\t\t\t\t\t\tawait $.sleep(3000);\n\t\t\t\t\t\t\t\t\twindow.location.reload();\n\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\thasJob = false;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tawait $.sleep(3000);\n\n\t\t\t\t\t\t\t// 跳转下一章，然后通过URL变化，调度器会重新执行此 main 函数\n\t\t\t\t\t\t\tif (canRun()) {\n\t\t\t\t\t\t\t\tif (hasJob) {\n\t\t\t\t\t\t\t\t\t$msg_and_log('info', '准备跳转下一章');\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t$msg_and_log('warn', '未找到学习内容，或者此章节不支持自动学习！即将跳过本章节');\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tawait gotoNextJob();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tstudy();\n\n\t\t\t\t\t\tasync function gotoNextJob() {\n\t\t\t\t\t\t\tconst list = await next();\n\t\t\t\t\t\t\tfor (const item of list) {\n\t\t\t\t\t\t\t\tconst el = typeof item === 'function' ? item() : item;\n\t\t\t\t\t\t\t\tif (el) {\n\t\t\t\t\t\t\t\t\tawait remotePage?.click(el);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (list.length === 0) {\n\t\t\t\t\t\t\t\t$message.success({ content: '所有章节学习完成！', duration: 0 });\n\t\t\t\t\t\t\t\t$console.info('所有章节学习完成！');\n\t\t\t\t\t\t\t\tCommonProject.scripts.settings.methods.notificationBySetting('所有章节学习完成！', {\n\t\t\t\t\t\t\t\t\tduration: 0,\n\t\t\t\t\t\t\t\t\textraTitle: '中国大学MOOC学习脚本'\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tasync function next() {\n\t\t\t\t\t\t\tconst nextEl = document.querySelector('.unitslist .current')?.nextElementSibling;\n\t\t\t\t\t\t\t// 判断小节\n\t\t\t\t\t\t\tif (nextEl) {\n\t\t\t\t\t\t\t\treturn [nextEl.querySelector('.unit-name')];\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// 判断章节\n\t\t\t\t\t\t\tconst getName = (node?: Node | null) => node?.textContent?.replace(/\\s/g, '');\n\t\t\t\t\t\t\tconst lessonName = getName(document.querySelector('.j-lesson .j-up'));\n\t\t\t\t\t\t\tif (!lessonName) {\n\t\t\t\t\t\t\t\tthrow Error('无法读取章节名!');\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tconst lessonList = Array.from(document.querySelectorAll('.j-lesson .j-list .list'));\n\t\t\t\t\t\t\tlet nextLesson = undefined as Element | undefined;\n\t\t\t\t\t\t\tfor (const item of lessonList) {\n\t\t\t\t\t\t\t\tconst itemName = getName(item);\n\t\t\t\t\t\t\t\tif (itemName === lessonName) {\n\t\t\t\t\t\t\t\t\tif (item.nextElementSibling) {\n\t\t\t\t\t\t\t\t\t\tnextLesson = item.nextElementSibling;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif (nextLesson) {\n\t\t\t\t\t\t\t\treturn [\n\t\t\t\t\t\t\t\t\t// 点击展开章节列表\n\t\t\t\t\t\t\t\t\tdocument.querySelector('.j-lesson'),\n\t\t\t\t\t\t\t\t\t// 点击章节\n\t\t\t\t\t\t\t\t\tnextLesson\n\t\t\t\t\t\t\t\t];\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// 判断单元\n\t\t\t\t\t\t\tconst chapterName = getName(document.querySelector('.j-chapter .j-up'));\n\t\t\t\t\t\t\tif (!chapterName) {\n\t\t\t\t\t\t\t\tthrow Error('无法读取单元名!');\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tconst chapterList = Array.from(document.querySelectorAll('.j-chapter .j-list .list'));\n\t\t\t\t\t\t\tlet nextChapter = undefined as Element | undefined;\n\t\t\t\t\t\t\tfor (const item of chapterList) {\n\t\t\t\t\t\t\t\tconst itemName = getName(item);\n\t\t\t\t\t\t\t\tif (itemName === chapterName) {\n\t\t\t\t\t\t\t\t\tif (item.nextElementSibling) {\n\t\t\t\t\t\t\t\t\t\tnextChapter = item.nextElementSibling;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif (nextChapter) {\n\t\t\t\t\t\t\t\treturn [\n\t\t\t\t\t\t\t\t\t// 点击展开单元列表\n\t\t\t\t\t\t\t\t\t() => document.querySelector('.j-chapter'),\n\t\t\t\t\t\t\t\t\t// 点击单元\n\t\t\t\t\t\t\t\t\t() => nextChapter,\n\t\t\t\t\t\t\t\t\t// 点击展开章节列表\n\t\t\t\t\t\t\t\t\t() => document.querySelector('.j-lesson'),\n\t\t\t\t\t\t\t\t\t// 点击第一个章节\n\t\t\t\t\t\t\t\t\t() => document.querySelectorAll('.j-lesson .j-list .list')[0]\n\t\t\t\t\t\t\t\t];\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\treturn [];\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t}\n\t\t}),\n\t\twork: new Script({\n\t\t\tname: '✍️ 作业考试脚本',\n\t\t\t// 添加版本号是因为只有 notes 会强制更新，其他配置项不会，如果需要修改 runAtHash ，需要更新版本号\n\t\t\tnamespace: 'icourse.work-v2',\n\t\t\tmatches: [\n\t\t\t\t['MOOC作业页面', 'icourse163.org/learn'],\n\t\t\t\t['SPOC作业页面', 'icourse163.org/spoc/learn'],\n\t\t\t\t['考试页面', 'icourse163.org/mooc/main/newExam']\n\t\t\t],\n\t\t\tconfigs: {\n\t\t\t\tnotes: workNotes,\n\t\t\t\trunAtHash: {\n\t\t\t\t\tdefaultValue: ['/learn/quiz', '/learn/examObject']\n\t\t\t\t}\n\t\t\t},\n\t\t\tmethods() {\n\t\t\t\tconst start = async (\n\t\t\t\t\ttype: 'chapter-test' | 'work' | 'exam',\n\t\t\t\t\tcanRun: () => boolean,\n\t\t\t\t\tonWorkerCreated?: (worker: any) => void\n\t\t\t\t) => {\n\t\t\t\t\t// 检查是否为软件环境\n\t\t\t\t\tconst remotePage = await BackgroundProject.scripts.dev.methods.getRemotePlaywrightCurrentPage();\n\t\t\t\t\t// 检查是否为软件环境\n\t\t\t\t\tif (!remotePage) {\n\t\t\t\t\t\treturn $playwright.showError();\n\t\t\t\t\t}\n\n\t\t\t\t\t// 等待加载题目\n\t\t\t\t\tawait waitForQuestion();\n\n\t\t\t\t\tCommonProject.scripts.render.methods.pin(this);\n\t\t\t\t\tCommonProject.scripts.render.methods.normal();\n\n\t\t\t\t\t$msg_and_log('info', '开始答题');\n\t\t\t\t\tCommonProject.scripts.render.methods.pin(this);\n\t\t\t\t\tcommonWork(this, {\n\t\t\t\t\t\tworkerProvider: (opts) => {\n\t\t\t\t\t\t\tconst worker = workAndExam(remotePage, type, opts);\n\t\t\t\t\t\t\tworker.once('close', () => {\n\t\t\t\t\t\t\t\tclearInterval(interval);\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tconst interval = setInterval(() => {\n\t\t\t\t\t\t\t\tif (canRun() === false) {\n\t\t\t\t\t\t\t\t\t$msg_and_log('warn', '检测到页面切换，无法继续答题，将关闭自动答题。');\n\t\t\t\t\t\t\t\t\tclearInterval(interval);\n\t\t\t\t\t\t\t\t\tworker.emit('close');\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}, 1000);\n\t\t\t\t\t\t\treturn worker;\n\t\t\t\t\t\t},\n\t\t\t\t\t\tonWorkerCreated: onWorkerCreated,\n\t\t\t\t\t\tenable_control_panel: true,\n\t\t\t\t\t\tstart_delay_seconds: 3\n\t\t\t\t\t});\n\t\t\t\t};\n\t\t\t\treturn {\n\t\t\t\t\tmain: async ({ canRun }: { canRun: () => boolean; job_id: string }) => {\n\t\t\t\t\t\tif (location.hash.includes('learn/quizscore')) {\n\t\t\t\t\t\t\t$message.success('当前作业已完成，自动答题关闭。');\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn start('work', canRun);\n\t\t\t\t\t},\n\t\t\t\t\tstart: start\n\t\t\t\t};\n\t\t\t},\n\t\t\t// 考试是新开一个界面所以会直接触发\n\t\t\toncomplete() {\n\t\t\t\tif (\n\t\t\t\t\t(location.href.includes('/learn/examObject') &&\n\t\t\t\t\t\t// 考试成绩解析页面\n\t\t\t\t\t\t!location.href.includes('learn/examObjectScore')) ||\n\t\t\t\t\t// 新版考试界面\n\t\t\t\t\tlocation.href.includes('/mooc/main/newExam')\n\t\t\t\t) {\n\t\t\t\t\tthis.methods.start('exam', () => true);\n\t\t\t\t}\n\t\t\t}\n\t\t}),\n\t\tpassportRedirect: new Script({\n\t\t\tname: '登录重定向修复',\n\t\t\tmatches: [['登录重定向', 'passport/logingate/changeCookie.htm']],\n\t\t\tconfigs: {\n\t\t\t\tnotes: {\n\t\t\t\t\tdefaultValue: $ui.notes(['检测到页面重定向到空白页面', '程序将会自动修复']).outerHTML\n\t\t\t\t}\n\t\t\t},\n\t\t\thideInPanel: true,\n\t\t\toncomplete(...args) {\n\t\t\t\tCommonProject.scripts.render.methods.pin(this);\n\t\t\t\t$message.info('检测到中国大学MOOC空白页面，即将重定向修复...');\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\tlocation.href = 'https://www.icourse163.org/';\n\t\t\t\t}, 3000);\n\t\t\t}\n\t\t})\n\t}\n});\n\nfunction waitForQuestion() {\n\treturn new Promise<void>((resolve, reject) => {\n\t\tconst interval = setInterval(() => {\n\t\t\tif (document.querySelector('.u-questionItem,[class*=questionBody]')) {\n\t\t\t\tclearInterval(interval);\n\t\t\t\tresolve();\n\t\t\t}\n\t\t}, 1000);\n\t});\n}\n\nfunction workAndExam(\n\tremotePage: RemotePage,\n\ttype: 'chapter-test' | 'work' | 'exam',\n\t{\n\t\tanswererWrappers,\n\t\tperiod,\n\t\tthread,\n\t\tredundanceWordsText,\n\t\tupload,\n\t\tstopSecondWhenFinish,\n\t\tanswerSeparators,\n\t\tanswerMatchMode\n\t}: CommonWorkOptions\n) {\n\tCommonProject.scripts.workResults.methods.init({\n\t\tquestionPositionSyncHandlerType: 'icourse'\n\t});\n\n\tconst titleTransform = (titles: (HTMLElement | undefined)[]) => {\n\t\treturn removeRedundantWords(\n\t\t\ttitles\n\t\t\t\t.map((t) => (t ? optimizationElementWithImage(t, true).innerText : ''))\n\t\t\t\t.filter((t) => t.trim() !== '')\n\t\t\t\t.join(',')\n\t\t\t\t// /\\u200B/g 排除不可见的空格\n\t\t\t\t.replace(/[\\u200A-\\u200F]/g, ''),\n\t\t\tredundanceWordsText.split('\\n')\n\t\t);\n\t};\n\tconst work_type = type;\n\n\t/** 新建答题器 */\n\tconst worker = new OCSWorker({\n\t\troot: type === 'exam' ? '[class*=questionBody]' : '.u-questionItem',\n\t\telements: {\n\t\t\ttitle: type === 'exam' ? '[class*=questionInfo]' : '.j-title .j-richTxt',\n\t\t\toptions: type === 'exam' ? '[class*=index-module__optionBody]' : '.choices li,.inputArea'\n\t\t},\n\t\tthread: thread ?? 1,\n\t\tanswerSeparators: answerSeparators.split(',').map((s) => s.trim()),\n\t\tanswerMatchMode: answerMatchMode,\n\t\t/** 默认搜题方法构造器 */\n\t\tanswerer: (elements, ctx) => {\n\t\t\tconst title = titleTransform(elements.title);\n\t\t\tif (title) {\n\t\t\t\treturn CommonProject.scripts.apps.methods.searchAnswerInCaches(title, async () => {\n\t\t\t\t\tawait $.sleep((period ?? 3) * 1000);\n\t\t\t\t\treturn defaultAnswerWrapperHandler(answererWrappers, {\n\t\t\t\t\t\ttype: ctx.type || 'unknown',\n\t\t\t\t\t\ttitle,\n\t\t\t\t\t\toptions: ctx.elements.options.map((o) => o.innerText).join('\\n')\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tthrow new Error('题目为空，请查看题目是否为空，或者忽略此题');\n\t\t\t}\n\t\t},\n\t\twork: {\n\t\t\t/** 自定义处理器 */\n\t\t\tasync handler(type, answer, option) {\n\t\t\t\tif (type === 'judgement' || type === 'single' || type === 'multiple') {\n\t\t\t\t\tif (work_type === 'exam') {\n\t\t\t\t\t\tconst input = option.querySelector('input');\n\t\t\t\t\t\tif (input && !input?.checked) {\n\t\t\t\t\t\t\tawait $.sleep(200);\n\t\t\t\t\t\t\treturn input.click();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tconst text = option.querySelector('.f-richEditorText');\n\t\t\t\t\tconst input = option.querySelector('input');\n\t\t\t\t\tif (input && !input?.checked && text) {\n\t\t\t\t\t\tawait $.sleep(200);\n\t\t\t\t\t\tawait remotePage.click(text);\n\t\t\t\t\t}\n\t\t\t\t} else if (type === 'completion' && answer.trim()) {\n\t\t\t\t\tconst text = option.querySelector('textarea');\n\n\t\t\t\t\tif (text) {\n\t\t\t\t\t\ttext.value = answer.trim();\n\t\t\t\t\t\tawait remotePage.fill('textarea', answer.trim());\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tonElementSearched(elements, root) {\n\t\t\telements.options.forEach((el) => {\n\t\t\t\tconst correct = el.querySelector<HTMLElement>('.u-icon-correct');\n\t\t\t\tconst wrong = el.querySelector<HTMLElement>('.u-icon-wrong');\n\t\t\t\tif (correct) {\n\t\t\t\t\tcorrect.replaceWith('对');\n\t\t\t\t}\n\t\t\t\tif (wrong) {\n\t\t\t\t\twrong.replaceWith('错');\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\t\t/** 完成答题后 */\n\t\tonResultsUpdate(curr, _, res) {\n\t\t\tCommonProject.scripts.workResults.methods.setResults(simplifyWorkResult(res, titleTransform));\n\n\t\t\tif (curr.result?.finish) {\n\t\t\t\tCommonProject.scripts.apps.methods.addQuestionCacheFromWorkResult(simplifyWorkResult([curr], titleTransform));\n\t\t\t}\n\t\t\tCommonProject.scripts.workResults.methods.updateWorkStateByResults(res);\n\t\t}\n\t});\n\n\tworker\n\t\t.doWork({ enable_debug: BackgroundProject.scripts.dev.cfg.enable_answerer_debug })\n\t\t.then(async (results) => {\n\t\t\tif (worker.isClose) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (type === 'chapter-test') {\n\t\t\t\t$msg_and_log('info', `答题完成，将等待 ${stopSecondWhenFinish} 秒后进行保存或提交。`);\n\t\t\t\tawait $.sleep(stopSecondWhenFinish * 1000);\n\t\t\t\tif (worker.isClose) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t// 处理提交\n\t\t\t\tawait worker.uploadHandler({\n\t\t\t\t\ttype: upload,\n\t\t\t\t\tresults,\n\t\t\t\t\tasync callback(finishedRate, uploadable) {\n\t\t\t\t\t\tconst content = `完成率 ${finishedRate.toFixed(2)}% :  ${\n\t\t\t\t\t\t\tuploadable ? '3秒后将自动提交' : '3秒后将自动跳过（没保存按钮）'\n\t\t\t\t\t\t} `;\n\t\t\t\t\t\t$console.info(content);\n\t\t\t\t\t\t$message.success({ content: content, duration: type === 'chapter-test' ? 10 : 0 });\n\n\t\t\t\t\t\tawait $.sleep(3000);\n\t\t\t\t\t\tif (worker.isClose) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (uploadable) {\n\t\t\t\t\t\t\tconst sumbit = document.querySelector('.j-submit');\n\t\t\t\t\t\t\tif (sumbit) {\n\t\t\t\t\t\t\t\tawait remotePage.click(sumbit);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t$msg_and_log('warn', '没有找到提交按钮，将跳过提交。');\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\t$message.success({ content: '作业/考试完成，请自行检查后保存或提交。', duration: 0 });\n\t\t\t}\n\n\t\t\tworker.emit('done');\n\t\t})\n\t\t.catch((err) => {\n\t\t\t$message.error({ content: '答题程序发生错误 : ' + err.message, duration: 0 });\n\t\t});\n\n\treturn worker;\n}\n\nasync function watchMedia(playbackRate: number, volume: number) {\n\treturn new Promise<void>((resolve, reject) => {\n\t\t// 部分用户视频加载很慢，这里等待一下\n\t\twaitForMedia()\n\t\t\t.then((video) => {\n\t\t\t\tvideo.playbackRate = playbackRate;\n\t\t\t\tvideo.volume = volume;\n\n\t\t\t\tstate.currentMedia = video;\n\n\t\t\t\tplayMedia(() => video?.play());\n\n\t\t\t\tvideo.onpause = async () => {\n\t\t\t\t\tif (!video?.ended) {\n\t\t\t\t\t\tvideo?.play();\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\tvideo.onended = () => {\n\t\t\t\t\tresolve();\n\t\t\t\t};\n\t\t\t})\n\t\t\t.catch(reject);\n\t});\n}\n\nasync function readPPT(remotePage: RemotePage, readSpeed: number) {\n\tconst reader = document.querySelector('.ux-pdf-reader');\n\tif (reader) {\n\t\tconst total = parseInt(\n\t\t\tdocument\n\t\t\t\t.querySelector('.ux-h5pdfreader_container_footer_pages_total')\n\t\t\t\t?.childNodes[1]?.textContent?.replace(/\\s/, '') || '0'\n\t\t);\n\t\tconst start = parseInt(\n\t\t\tdocument.querySelector<HTMLInputElement>('.ux-h5pdfreader_container_footer_pages_in')?.value || '1'\n\t\t);\n\t\tfor (let index = start; index < total + 1; index++) {\n\t\t\tconst next = document.querySelector<HTMLElement>('.ux-h5pdfreader_container_footer_pages_next');\n\t\t\tif (next) {\n\t\t\t\tawait remotePage.click(next);\n\t\t\t} else {\n\t\t\t\t$msg_and_log('error', '未找到PPT的下一页按钮！');\n\t\t\t}\n\t\t\tawait $.sleep(readSpeed * 1000);\n\t\t}\n\t}\n}\n\nasync function discussion(\n\tremotePage: RemotePage,\n\tdiscussionStrategy: typeof ICourseProject.scripts.study.cfg.discussionStrategy\n) {\n\tif (discussionStrategy === 'not-reply') {\n\t\treturn $msg_and_log('warn', '讨论自动回复功能已关闭（上方菜单栏-中国大学MOOC-学习脚本中开启）。');\n\t}\n\n\tlet res = '';\n\n\tif (discussionStrategy === 'max-show-up') {\n\t\tconst list = Array.from(document.querySelectorAll('.j-reply-all .f-pr .j-content'));\n\t\tconst mapping = new Map();\n\t\tfor (const item of list) {\n\t\t\tmapping.set(item.textContent, (mapping.get(item.textContent) || 0) + 1);\n\t\t}\n\t\tconst content = [...mapping.entries()].sort((a, b) => b[1] - a[1])?.[0]?.[0];\n\t\tif (!content) {\n\t\t\t$msg_and_log('error', '读取出现最多评论失败！');\n\t\t}\n\t\tres = content;\n\t} else if (discussionStrategy === 'max-fav') {\n\t\tconst list = Array.from(document.querySelectorAll('.j-reply-all .f-pr'));\n\t\tlet max = 0;\n\t\tlet maxEl = undefined as Element | undefined;\n\t\tfor (const item of list) {\n\t\t\tconst num = parseInt(item.querySelector('.bar .num')?.textContent || '0');\n\t\t\tif (num > max) {\n\t\t\t\tmax = num;\n\t\t\t\tmaxEl = item;\n\t\t\t}\n\t\t}\n\t\tconst content = maxEl?.querySelector('.j-content')?.textContent || '';\n\t\tif (!content) {\n\t\t\t$msg_and_log('error', '读取最多点赞评论失败！');\n\t\t}\n\t\tres = content;\n\t} else if (discussionStrategy === 'use-newest') {\n\t\tconst content = document.querySelector('.j-reply-all .f-pr .first .j-content')?.textContent || '';\n\t\tif (!content) {\n\t\t\t$msg_and_log('error', '读取最新评论失败！');\n\t\t}\n\t\tres = content;\n\t}\n\n\tconst p = document.querySelector<HTMLDivElement>('.j-reply-add div.ql-editor.ql-blank p');\n\tif (p) {\n\t\tp.innerText = res;\n\t\tawait $.sleep(1000);\n\t\tconst submit = document.querySelector('.j-reply-add .editbtn');\n\t\tif (submit) {\n\t\t\tawait remotePage.click(submit);\n\t\t\t$message.info('提交回复成功！');\n\t\t} else {\n\t\t\t$msg_and_log('error', '获取提交按钮失败！');\n\t\t}\n\t\tawait $.sleep(2000);\n\t} else {\n\t\t$msg_and_log('error', '获取评论输入框失败！');\n\t}\n}\n"
  },
  {
    "path": "packages/scripts/src/projects/icve.ts",
    "content": "import {\n\t$,\n\tSimplifyWorkResult,\n\tdefaultAnswerWrapperHandler,\n\tOCSWorker,\n\tcreateDefaultQuestionResolver,\n\tsplitAnswer,\n\tQuestionTypes\n} from '@ocsjs/core';\nimport { $gm, cors, $message, $$el, $modal, $el, Project, Script, $ui, h } from 'easy-us';\nimport { optimizationElementWithImage, commonWork, simplifyWorkResult } from '../utils/work';\nimport { playbackRate, restudy, volume } from '../utils/configs';\nimport { CommonWorkOptions, playMedia } from '../utils';\nimport { CommonProject } from './common';\n\nimport { $console, BackgroundProject } from './background';\nimport { waitForElement, waitForMedia } from '../utils/study';\n\nconst state = {\n\tstudy: {\n\t\tcurrentMedia: undefined as HTMLMediaElement | undefined,\n\t\tcurrentStudyLockId: 0,\n\t\tplaybackRateWarningListenerId: 0,\n\t\tcourseLengthListenerId: 0\n\t}\n};\n\nconst $msg_and_log = (type: 'info' | 'warn' | 'error', msg: string) => {\n\t$message[type](msg);\n\t$console[type](msg);\n};\n\n/**\n * 学习锁，用于判断是否可以学习，防止学习函数被多次调用\n */\nclass StudyLock {\n\tstatic auto_inc: number = 0;\n\tid: number;\n\tconstructor() {\n\t\tStudyLock.auto_inc++;\n\t\tthis.id = StudyLock.auto_inc;\n\t\tstate.study.currentStudyLockId = this.id;\n\t}\n\n\tcanStudy() {\n\t\treturn this.id === state.study.currentStudyLockId;\n\t}\n\n\tstatic getLock() {\n\t\treturn new StudyLock();\n\t}\n}\n\nexport const IcveMoocProject = Project.create({\n\tname: '智慧职教',\n\tdomains: [\n\t\t'icve.com.cn',\n\t\t'ai.icve.com.cn',\n\t\t'course.icve.com.cn',\n\t\t// 智慧职教套壳\n\t\t'courshare.cn',\n\t\t'webtrn.cn'\n\t],\n\tscripts: {\n\t\tguide: new Script({\n\t\t\tname: '💡 使用提示',\n\t\t\tmatches: [\n\t\t\t\t['个人首页', 'icve.com.cn/studycenter'],\n\t\t\t\t['学习页面', 'icve.com.cn/study/directory'],\n\t\t\t\t['MOOC学院-个人首页', 'user.icve.com.cn'],\n\t\t\t\t['MOOC学院-首页', 'mooc.icve.com.cn']\n\t\t\t],\n\t\t\tnamespace: 'icve.guide',\n\t\t\tconfigs: {\n\t\t\t\tnotes: {\n\t\t\t\t\tdefaultValue: $ui.notes(['请点击任意课程进入', '进入课程后点击任意章节进入，即可自动学习']).outerHTML\n\t\t\t\t}\n\t\t\t},\n\t\t\toncomplete() {\n\t\t\t\tCommonProject.scripts.render.methods.pin(this);\n\t\t\t}\n\t\t}),\n\t\t/** 智慧职教学习中心 */\n\t\tstudyCenter: new Script({\n\t\t\tname: '🖥️ 智慧职教-学习中心',\n\t\t\tnamespace: 'icve.study.center',\n\t\t\tmatches: [\n\t\t\t\t['学习中心页面', '/study/directory/dir_course.html'],\n\t\t\t\t['课程列表', 'icve.com.cn/study/directory/directory_list.html']\n\t\t\t],\n\t\t\tconfigs: {\n\t\t\t\tplaybackRate: playbackRate,\n\t\t\t\tvolume,\n\t\t\t\t/** 章节列表 */\n\t\t\t\tcurrentCourseUrlList: {\n\t\t\t\t\tdefaultValue: [] as string[]\n\t\t\t\t}\n\t\t\t},\n\t\t\tasync oncomplete() {\n\t\t\t\tif (location.href.includes('icve.com.cn/study/directory/directory_list.html')) {\n\t\t\t\t\tawait waitForElement('.h_cells a');\n\t\t\t\t\tthis.cfg.currentCourseUrlList = Array.from(document.querySelectorAll<HTMLAnchorElement>('.h_cells a')).map(\n\t\t\t\t\t\t(a) => a.href\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (this.cfg.currentCourseUrlList.length === 0) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst url =\n\t\t\t\t\t\t\t'https://www.icve.com.cn/study/directory/directory_list.html?courseId=' +\n\t\t\t\t\t\t\tnew URL(location.href).searchParams.get('courseId');\n\t\t\t\t\t\tconst res = await fetch(url).then((res) => res.text());\n\t\t\t\t\t\tconst doc = new DOMParser().parseFromString(res, 'text/html');\n\t\t\t\t\t\tthis.cfg.currentCourseUrlList = Array.from(doc.querySelectorAll<HTMLAnchorElement>('.h_cells a')).map(\n\t\t\t\t\t\t\t(a) => a.href\n\t\t\t\t\t\t);\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\tconsole.error(e);\n\t\t\t\t\t\t$message.error('课程列表获取失败，请刷新页面重试。');\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tCommonProject.scripts.render.methods.pin(this);\n\n\t\t\t\tthis.onConfigChange('playbackRate', (playbackRate) => {\n\t\t\t\t\tstate.study.currentMedia && (state.study.currentMedia.playbackRate = parseFloat(playbackRate.toString()));\n\t\t\t\t});\n\t\t\t\tthis.onConfigChange('volume', (v) => state.study.currentMedia && (state.study.currentMedia.volume = v));\n\n\t\t\t\tconst study = async () => {\n\t\t\t\t\tconst res = await Promise.race([waitForElement('video, audio'), waitForElement('.docBox')]);\n\t\t\t\t\tif (res) {\n\t\t\t\t\t\tconst jobName = document.querySelector('.tabsel.seled')?.getAttribute('title') || '-';\n\t\t\t\t\t\t$message.info('开始任务：' + jobName);\n\t\t\t\t\t\t$console.log(`任务 ${jobName} 开始。`);\n\t\t\t\t\t\tif (document.querySelector('video, audio')) {\n\t\t\t\t\t\t\tconst media = await waitForMedia();\n\n\t\t\t\t\t\t\tstate.study.currentMedia = media;\n\t\t\t\t\t\t\tmedia.volume = this.cfg.volume;\n\n\t\t\t\t\t\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\tconsole.log(document.hasFocus());\n\t\t\t\t\t\t\t\t\twindow.focus();\n\t\t\t\t\t\t\t\t\t// @ts-ignore\n\t\t\t\t\t\t\t\t\t$gm.unsafeWindow.jwplayer().onComplete(async () => {\n\t\t\t\t\t\t\t\t\t\t$console.log('视频/音频播放完成。');\n\t\t\t\t\t\t\t\t\t\tawait $.sleep(3000);\n\t\t\t\t\t\t\t\t\t\tresolve();\n\t\t\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\t\t\tconst play = () => {\n\t\t\t\t\t\t\t\t\t\t$gm.unsafeWindow.jwplayer().play();\n\t\t\t\t\t\t\t\t\t\t$gm.unsafeWindow.jwplayer().play();\n\t\t\t\t\t\t\t\t\t\tmedia.playbackRate = parseFloat(this.cfg.playbackRate.toString());\n\t\t\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\t\t\tmedia.addEventListener('pause', async () => {\n\t\t\t\t\t\t\t\t\t\tif (!media.ended) {\n\t\t\t\t\t\t\t\t\t\t\tawait $.sleep(1000);\n\t\t\t\t\t\t\t\t\t\t\tplayMedia(play);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t// 开始播放\n\t\t\t\t\t\t\t\t\tplayMedia(play);\n\t\t\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\t\t\treject(err);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\t$message.success(`任务 ${jobName} 完成，三秒后下一章`);\n\t\t\t\t\t\t$console.log(`任务 ${jobName} 完成，三秒后下一章`);\n\t\t\t\t\t} else {\n\t\t\t\t\t\t$console.error(`不支持的任务页面，请跟作者进行反馈。三秒后下一章`);\n\t\t\t\t\t}\n\n\t\t\t\t\tawait $.sleep(3000);\n\n\t\t\t\t\tnext();\n\t\t\t\t};\n\n\t\t\t\tconst next = () => {\n\t\t\t\t\tfor (let index = 0; index < this.cfg.currentCourseUrlList.length; index++) {\n\t\t\t\t\t\tconst url = this.cfg.currentCourseUrlList[index];\n\t\t\t\t\t\tconst nextUrl = this.cfg.currentCourseUrlList[index + 1];\n\t\t\t\t\t\tif (new URL(url).hash === new URL(location.href).hash) {\n\t\t\t\t\t\t\tif (!nextUrl) {\n\t\t\t\t\t\t\t\t$modal.alert({ content: '全部任务已完成' });\n\t\t\t\t\t\t\t\tCommonProject.scripts.settings.methods.notificationBySetting('全部任务点已完成！', {\n\t\t\t\t\t\t\t\t\tduration: 0,\n\t\t\t\t\t\t\t\t\textraTitle: '智慧职教学习脚本'\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\twindow.location.href = this.cfg.currentCourseUrlList[index + 1];\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\tstudy();\n\t\t\t}\n\t\t}),\n\t\t/** MOOC 学院 */\n\t\tstudy: new Script({\n\t\t\tname: '🖥️ MOOC学院-课程学习',\n\t\t\tnamespace: 'icve.study.main',\n\t\t\tmatches: [['课程学习页面', '/learnspace/learn/learn/templateeight/index.action']],\n\t\t\tconfigs: {\n\t\t\t\tnotes: {\n\t\t\t\t\tdefaultValue: $ui.notes([\n\t\t\t\t\t\t'如果视频无法播放，可以手动点击其他任务跳过视频。',\n\t\t\t\t\t\t'经过测试视频倍速最多二倍，否则会判定无效。',\n\t\t\t\t\t\t'手动进入作业页面才能使用自动答题。'\n\t\t\t\t\t]).outerHTML\n\t\t\t\t},\n\t\t\t\tplaybackRate: playbackRate,\n\t\t\t\tvolume,\n\t\t\t\trestudy,\n\t\t\t\tshowScrollBar: {\n\t\t\t\t\tlabel: '显示右侧滚动条',\n\t\t\t\t\tattrs: { type: 'checkbox' },\n\t\t\t\t\tdefaultValue: true\n\t\t\t\t},\n\t\t\t\texpandAll: {\n\t\t\t\t\tlabel: '展开所有章节',\n\t\t\t\t\tattrs: { type: 'checkbox' },\n\t\t\t\t\tdefaultValue: true\n\t\t\t\t},\n\t\t\t\tswitchPeriod: {\n\t\t\t\t\tlabel: '下一章节切换间隔（秒）',\n\t\t\t\t\tdefaultValue: 10,\n\t\t\t\t\tattrs: {\n\t\t\t\t\t\ttype: 'number',\n\t\t\t\t\t\tmin: 0,\n\t\t\t\t\t\tmax: 999,\n\t\t\t\t\t\tstep: 1\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\tonrender() {\n\t\t\t\t// 高倍速警告\n\t\t\t\tthis.offConfigChange(state.study.playbackRateWarningListenerId);\n\t\t\t\tstate.study.playbackRateWarningListenerId =\n\t\t\t\t\tthis.onConfigChange('playbackRate', (playbackRate) => {\n\t\t\t\t\t\tif (playbackRate > 4) {\n\t\t\t\t\t\t\t$modal.alert({\n\t\t\t\t\t\t\t\ttitle: '⚠️高倍速警告',\n\t\t\t\t\t\t\t\tcontent: $ui.notes(['高倍速可能导致视频无法完成！'])\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t}) || 0;\n\t\t\t},\n\t\t\tasync oncomplete() {\n\t\t\t\tCommonProject.scripts.render.methods.pin(this);\n\n\t\t\t\tawait $.sleep(3000);\n\n\t\t\t\tthis.onConfigChange('volume', (v) => state.study.currentMedia && (state.study.currentMedia.volume = v));\n\t\t\t\tthis.onConfigChange(\n\t\t\t\t\t'playbackRate',\n\t\t\t\t\t(r) => state.study.currentMedia && (state.study.currentMedia.playbackRate = parseFloat(r.toString()))\n\t\t\t\t);\n\n\t\t\t\tconst mainContentWin = $el<HTMLIFrameElement>('#mainContent')?.contentWindow as Window & { [x: string]: any };\n\n\t\t\t\tif (mainContentWin) {\n\t\t\t\t\t// 弹窗强制用户点击，防止视频无法自动播放\n\t\t\t\t\t$modal.confirm({\n\t\t\t\t\t\tcontent: h('div', [\n\t\t\t\t\t\t\t'是否开始自动学习当前章节？',\n\t\t\t\t\t\t\th('br'),\n\t\t\t\t\t\t\t'你也可以选择任意的章节进行点击，脚本会自动学习，并一直往下寻找章节。'\n\t\t\t\t\t\t]),\n\t\t\t\t\t\tcancelButtonText: '我想手动选择章节',\n\t\t\t\t\t\tconfirmButtonText: '开始学习',\n\t\t\t\t\t\tasync onConfirm() {\n\t\t\t\t\t\t\tstudy(StudyLock.getLock());\n\t\t\t\t\t\t\tscrollToJob();\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tif (this.cfg.showScrollBar) {\n\t\t\t\t\tconst bar = $el('.dumascroll_area', mainContentWin.document);\n\t\t\t\t\tbar && (bar.style.overflow = 'auto');\n\t\t\t\t}\n\n\t\t\t\tif (this.cfg.expandAll) {\n\t\t\t\t\t$$el('.s_sectionlist,.s_sectionwrap', mainContentWin.document).forEach((el) => (el.style.display = 'block'));\n\t\t\t\t}\n\n\t\t\t\tfor (const job of $$el('.s_point[itemtype]', mainContentWin.document)) {\n\t\t\t\t\tjob.addEventListener('click', (e) => {\n\t\t\t\t\t\tconst lock = StudyLock.getLock();\n\t\t\t\t\t\t// 如果是用户点击\n\t\t\t\t\t\tif (e.isTrusted) {\n\t\t\t\t\t\t\tif (job.getAttribute('itemtype') === 'exam') {\n\t\t\t\t\t\t\t\treturn $message.info({\n\t\t\t\t\t\t\t\t\tduration: 60,\n\t\t\t\t\t\t\t\t\tcontent: '检测到您手动选择了作业/考试章节，将不会自动跳转，请完成后手动选择其他章节，脚本会自动学习。'\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t$message.info('检测到章节切换，即将自动学习...');\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\tstudy(lock);\n\t\t\t\t\t\t}, 3000);\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tconst scrollToJob = () =>\n\t\t\t\t\t$el('.s_pointerct', mainContentWin.document)?.scrollIntoView({ behavior: 'smooth', block: 'center' });\n\n\t\t\t\t/** 学习 */\n\t\t\t\tconst study = async (studyLock: StudyLock) => {\n\t\t\t\t\tconst iframe = $el<HTMLIFrameElement>('iframe', mainContentWin.document);\n\t\t\t\t\tconst win = iframe?.contentWindow;\n\t\t\t\t\tif (win) {\n\t\t\t\t\t\tconst doc = win.document;\n\t\t\t\t\t\tif (iframe.src.includes('content_video.action') || iframe.src.includes('content_audio.action')) {\n\t\t\t\t\t\t\t// 视频\n\t\t\t\t\t\t\t$console.log('视频/音频播放中...');\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tconst media = await waitForMedia({ root: doc });\n\n\t\t\t\t\t\t\t\tstate.study.currentMedia = media;\n\t\t\t\t\t\t\t\tmedia.playbackRate = parseFloat(this.cfg.playbackRate.toString());\n\t\t\t\t\t\t\t\tmedia.volume = this.cfg.volume;\n\t\t\t\t\t\t\t\tmedia.currentTime = 0;\n\n\t\t\t\t\t\t\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\t// @ts-ignore\n\t\t\t\t\t\t\t\t\t\twin.jwplayer().onComplete(async () => {\n\t\t\t\t\t\t\t\t\t\t\t$console.log('视频/音频播放完成。');\n\t\t\t\t\t\t\t\t\t\t\tawait $.sleep(3000);\n\t\t\t\t\t\t\t\t\t\t\tresolve();\n\t\t\t\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\t\t\t\tmedia.addEventListener('pause', async () => {\n\t\t\t\t\t\t\t\t\t\t\tif (!media.ended) {\n\t\t\t\t\t\t\t\t\t\t\t\tawait Promise.race([\n\t\t\t\t\t\t\t\t\t\t\t\t\t// 测验弹窗\n\t\t\t\t\t\t\t\t\t\t\t\t\twaitForPopupQuestion(doc),\n\t\t\t\t\t\t\t\t\t\t\t\t\t// 30分钟是否继续学习弹窗\n\t\t\t\t\t\t\t\t\t\t\t\t\thandleContinueDialog()\n\t\t\t\t\t\t\t\t\t\t\t\t]);\n\t\t\t\t\t\t\t\t\t\t\t\tawait $.sleep(1000);\n\t\t\t\t\t\t\t\t\t\t\t\tplayMedia(() => media.play());\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t// 开始播放\n\t\t\t\t\t\t\t\t\t\tplayMedia(() => media.play());\n\t\t\t\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\t\t\t\treject(err);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\t\t$message.error(String(err));\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (iframe.src.includes('content_doc.action')) {\n\t\t\t\t\t\t\t// 文档只需点击就算完成，等待5秒下一个\n\t\t\t\t\t\t\tawait $.sleep(5000);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// 如果为 null 证明跨域\n\t\t\t\t\t}\n\t\t\t\t\t$console.log(this.cfg.switchPeriod + ' 秒后切换下一章节。');\n\t\t\t\t\tawait $.sleep(this.cfg.switchPeriod * 1000);\n\n\t\t\t\t\tif (studyLock.canStudy()) {\n\t\t\t\t\t\tlet nextEl;\n\t\t\t\t\t\t// 是否处于当前章节之后\n\t\t\t\t\t\tlet isBellowCurrentJob = false;\n\t\t\t\t\t\tconst jobs = $$el('.s_point[itemtype]', mainContentWin.document);\n\t\t\t\t\t\tfor (let index = 0; index < jobs.length; index++) {\n\t\t\t\t\t\t\tconst job = jobs[index];\n\t\t\t\t\t\t\tif (job.classList.contains('s_pointerct')) {\n\t\t\t\t\t\t\t\tisBellowCurrentJob = true;\n\t\t\t\t\t\t\t} else if (isBellowCurrentJob) {\n\t\t\t\t\t\t\t\tif (job.querySelector('.done_icon_show') === null || this.cfg.restudy) {\n\t\t\t\t\t\t\t\t\t$console.log('下一章：', job.title || $el('.s_pointti', job)?.title || '未知');\n\t\t\t\t\t\t\t\t\tnextEl = job;\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (nextEl) {\n\t\t\t\t\t\t\tnextEl.click();\n\t\t\t\t\t\t\tscrollToJob();\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t$modal.alert({ content: '全部任务已完成' });\n\t\t\t\t\t\t\tCommonProject.scripts.settings.methods.notificationBySetting('全部任务点已完成！', {\n\t\t\t\t\t\t\t\tduration: 0,\n\t\t\t\t\t\t\t\textraTitle: '智慧职教学习脚本'\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t}\n\t\t}),\n\n\t\twork: new Script({\n\t\t\tname: '✍️ 作业考试脚本',\n\t\t\tmatches: [['作业考试页面', '/exam']],\n\t\t\tnamespace: 'icve.work',\n\t\t\tconfigs: {\n\t\t\t\tnotes: {\n\t\t\t\t\tdefaultValue: $ui.notes([\n\t\t\t\t\t\t'自动答题前请在 “通用-全局设置” 中设置题库配置。',\n\t\t\t\t\t\t'可以搭配 “通用-在线搜题” 一起使用。',\n\t\t\t\t\t\t'请手动进入作业考试页面才能使用自动答题。'\n\t\t\t\t\t]).outerHTML\n\t\t\t\t}\n\t\t\t},\n\t\t\tasync oncomplete() {\n\t\t\t\t$message.warn({ content: '自动答题时请勿切换题目，否则可能导致重复搜题或者脚本卡主。', duration: 0 });\n\n\t\t\t\t// 回到第一题\n\t\t\t\tconst resetToBegin = () => {\n\t\t\t\t\tdocument.querySelectorAll<HTMLElement>(`.sheet_nums [id*=\"sheetSeq\"]`).item(0)?.click();\n\t\t\t\t};\n\n\t\t\t\tcommonWork(this, {\n\t\t\t\t\tworkerProvider: work,\n\t\t\t\t\tbeforeRunning: async () => {\n\t\t\t\t\t\tresetToBegin();\n\t\t\t\t\t\tawait $.sleep(1000);\n\t\t\t\t\t},\n\t\t\t\t\tonRestart: () => resetToBegin()\n\t\t\t\t});\n\t\t\t}\n\t\t}),\n\t\tworkDispatcher: new Script({\n\t\t\tname: '作业调度脚本',\n\t\t\tmatches: [\n\t\t\t\t['作业进入页面', '/platformwebapi/student/exam/'],\n\t\t\t\t['确认作业页面', '/student/exam/studentExam_studentInfo.action']\n\t\t\t],\n\t\t\thideInPanel: true,\n\t\t\toncomplete() {\n\t\t\t\tif (/\\/platformwebapi\\/student\\/exam/.test(window.location.href)) {\n\t\t\t\t\tcors.on('icve-work-start', () => {\n\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\t$gm.unsafeWindow.openExamInfo();\n\t\t\t\t\t\t}, 3000);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tif (/\\/student\\/exam\\/studentExam_studentInfo.action/.test(window.location.href)) {\n\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t// 确认答题后，OCS会自动执行 ICVE.scripts.work 的 oncomplete 然后开始答题\n\t\t\t\t\t\t$gm.unsafeWindow.enterExamPage();\n\t\t\t\t\t}, 3000);\n\t\t\t\t}\n\t\t\t}\n\t\t}),\n\t\t'ai-study': new Script({\n\t\t\tname: '🖥️ AI课程',\n\t\t\tnamespace: 'icve.ai.study',\n\t\t\tmatches: [\n\t\t\t\t['课程页面', 'ai.icve.com.cn/app/coursedetails-excellent'],\n\t\t\t\t['学习页面', 'ai.icve.com.cn/excellent-study']\n\t\t\t],\n\t\t\tconfigs: {\n\t\t\t\tnotes: {\n\t\t\t\t\tdefaultValue: $ui.notes([\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\t'如果脚本卡死或者您不想学习，',\n\t\t\t\t\t\t\t'可以点击其他任意章节继续进行学习。',\n\t\t\t\t\t\t\t'PPT请勿加快点击，否则可能无法记录学习进度。'\n\t\t\t\t\t\t]\n\t\t\t\t\t]).outerHTML\n\t\t\t\t},\n\t\t\t\tvolume: volume,\n\t\t\t\tplaybackRate: {\n\t\t\t\t\tlabel: '视频倍速',\n\t\t\t\t\ttag: 'select',\n\t\t\t\t\toptions: [1, 1.25, 1.5, 1.75, 2, 2.25, 2.5, 2.75, 3, 3.5, 4, 6, 8, 16].map((rate) => [\n\t\t\t\t\t\trate.toString(),\n\t\t\t\t\t\trate + ' x'\n\t\t\t\t\t]),\n\t\t\t\t\tdefaultValue: '1'\n\t\t\t\t},\n\t\t\t\tautoOpenAllChapter: {\n\t\t\t\t\tlabel: '自动打开全部章节',\n\t\t\t\t\tattrs: {\n\t\t\t\t\t\ttitle: '如果没有打开全部章节，那么当任务点达到当前章节最后一个时将无法跳转到其他章节列表！',\n\t\t\t\t\t\ttype: 'checkbox'\n\t\t\t\t\t},\n\t\t\t\t\tdefaultValue: true\n\t\t\t\t},\n\t\t\t\trestudy\n\t\t\t},\n\t\t\t// historychange 不知道为什么会触发很多次在 kcnr 课程页面，这里直接 reload，让脚本加载 oncomplete 函数\n\t\t\tonhistorychange(type) {\n\t\t\t\tif (type !== 'replace') {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (location.href.includes('kcnr')) {\n\t\t\t\t\tlocation.reload();\n\t\t\t\t}\n\t\t\t},\n\t\t\tasync oncomplete(type) {\n\t\t\t\t// 置顶页面\n\t\t\t\tCommonProject.scripts.render.methods.pin(this);\n\n\t\t\t\tthis.onConfigChange('volume', (val) => {\n\t\t\t\t\tif (state.study.currentMedia) {\n\t\t\t\t\t\tstate.study.currentMedia.volume = parseFloat(val.toString());\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\tthis.onConfigChange('playbackRate', (val) => {\n\t\t\t\t\tif (state.study.currentMedia) {\n\t\t\t\t\t\tstate.study.currentMedia.playbackRate = parseFloat(val.toString());\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\t// 等待加载\n\t\t\t\tconst waitForLoad = () => {\n\t\t\t\t\treturn new Promise<void>((resolve) => {\n\t\t\t\t\t\tconst check = () => {\n\t\t\t\t\t\t\tif (document.querySelector('.contentBox')) {\n\t\t\t\t\t\t\t\tresolve();\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tsetTimeout(check, 100);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t};\n\t\t\t\t\t\tcheck();\n\t\t\t\t\t});\n\t\t\t\t};\n\n\t\t\t\t// 删除是否继续学习的弹窗\n\t\t\t\tconst closeStudyContinueDialog = () => {\n\t\t\t\t\treturn new Promise<void>((resolve) => {\n\t\t\t\t\t\tlet stop = false;\n\t\t\t\t\t\tconst check = () => {\n\t\t\t\t\t\t\tif (document.querySelector('.el-message-box__wrapper')) {\n\t\t\t\t\t\t\t\t$el('.el-message-box__wrapper')?.remove();\n\t\t\t\t\t\t\t\t$el('.v-modal')?.remove();\n\t\t\t\t\t\t\t\tresolve();\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t!stop && setTimeout(check, 100);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t};\n\t\t\t\t\t\tcheck();\n\n\t\t\t\t\t\t// 超时\n\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\tstop = true;\n\t\t\t\t\t\t\tresolve();\n\t\t\t\t\t\t}, 3 * 1000);\n\t\t\t\t\t});\n\t\t\t\t};\n\t\t\t\tawait waitForLoad();\n\t\t\t\tawait closeStudyContinueDialog();\n\t\t\t\tawait waitForLoad();\n\t\t\t\tawait $.sleep(3000);\n\n\t\t\t\t$msg_and_log('info', '即将打开全部章节列表，请稍等');\n\t\t\t\t// 打开全部章节列表\n\t\t\t\tconst openAllChapter = async () => {\n\t\t\t\t\tconst model = $modal.simple({\n\t\t\t\t\t\tmaskCloseable: false,\n\t\t\t\t\t\tfooter: undefined,\n\t\t\t\t\t\tcontent: '正在展开全部章节列表，请耐心等待不要操作...'\n\t\t\t\t\t});\n\n\t\t\t\t\t// 选择未展开的章节\n\t\t\t\t\tconst titles = Array.from(document.querySelectorAll<HTMLElement>('.one-title')).filter(\n\t\t\t\t\t\t(el) => !el.querySelector('.zhankai')\n\t\t\t\t\t);\n\t\t\t\t\tconst waitForChapterOpen = (title: HTMLElement) => {\n\t\t\t\t\t\treturn new Promise<void>((resolve) => {\n\t\t\t\t\t\t\tlet stop = false;\n\t\t\t\t\t\t\tconst check = () => {\n\t\t\t\t\t\t\t\tconst parent = title.parentElement?.parentElement;\n\t\t\t\t\t\t\t\tconst content = parent?.querySelector<HTMLElement>('.panel-content');\n\t\t\t\t\t\t\t\tif (content?.style.display !== 'none' && (content?.querySelectorAll('.node').length || 0) > 0) {\n\t\t\t\t\t\t\t\t\tresolve();\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t!stop && setTimeout(check, 100);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tcheck();\n\t\t\t\t\t\t\t// 超时\n\t\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\t\tstop = true;\n\t\t\t\t\t\t\t\tresolve();\n\t\t\t\t\t\t\t}, 10 * 1000);\n\t\t\t\t\t\t});\n\t\t\t\t\t};\n\t\t\t\t\tfor (const title of titles) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\ttitle.querySelector<HTMLElement>('.jiantou')?.click();\n\t\t\t\t\t\t\ttitle.focus();\n\t\t\t\t\t\t\ttitle.scrollIntoView({ behavior: 'smooth', block: 'center' });\n\t\t\t\t\t\t\tawait waitForChapterOpen(title);\n\t\t\t\t\t\t\tawait $.sleep(1000);\n\t\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\t\t$console.error('打开章节失败', e);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tmodel?.remove();\n\t\t\t\t};\n\n\t\t\t\tif (this.cfg.autoOpenAllChapter) await openAllChapter();\n\n\t\t\t\tlet study_id = '';\n\n\t\t\t\tdocument.querySelectorAll('.node').forEach((el) => {\n\t\t\t\t\tel.addEventListener('click', () => {\n\t\t\t\t\t\tstudy((study_id = Math.random().toString(36).substr(2, 9)));\n\t\t\t\t\t});\n\t\t\t\t});\n\n\t\t\t\tconst study = async (id: string) => {\n\t\t\t\t\t$msg_and_log('info', '即将开始学习：' + ($el('.contentBox')?.__vue__.nrdata.name || '未知任务点'));\n\t\t\t\t\tawait $.sleep(3000);\n\n\t\t\t\t\tawait (async () => {\n\t\t\t\t\t\tconst active = document.querySelector<HTMLElement>('.panelList .node.active');\n\t\t\t\t\t\tactive?.focus();\n\t\t\t\t\t\tactive?.scrollIntoView({ behavior: 'smooth', block: 'center' });\n\n\t\t\t\t\t\tif (active?.querySelector('.wc') && !this.cfg.restudy) {\n\t\t\t\t\t\t\treturn $msg_and_log('info', '当前任务已完成，即将跳过');\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst vue = $el('.FilePreview')?.__vue__;\n\t\t\t\t\t\tconst img = $el('.ql-editor');\n\t\t\t\t\t\tconst work = $el('.shiti');\n\t\t\t\t\t\tif (work) {\n\t\t\t\t\t\t\t// 做作业\n\t\t\t\t\t\t\treturn $msg_and_log('warn', '检测到当前为作业任务，请完成课程后手动进入自动答题。');\n\t\t\t\t\t\t} else if (img) {\n\t\t\t\t\t\t\t// 做作业\n\t\t\t\t\t\t\treturn $msg_and_log('warn', '检测到当前为图片任务，即将跳过');\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tif (!vue) {\n\t\t\t\t\t\t\t\treturn $message.error({ content: '获取课程数据失败，或者未知任务点，即将跳过' });\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tconst watchOffice = async () => {\n\t\t\t\t\t\t\t\tconst total = vue.photoList.length;\n\t\t\t\t\t\t\t\tfor (let index = 0; index < total + 1; index++) {\n\t\t\t\t\t\t\t\t\tif (id !== study_id) return;\n\t\t\t\t\t\t\t\t\tvue.next();\n\t\t\t\t\t\t\t\t\tawait $.sleep(3000);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\t$message.info('开始学习');\n\t\t\t\t\t\t\tif (['video', 'audio'].includes(vue.curType)) {\n\t\t\t\t\t\t\t\tawait closeStudyContinueDialog();\n\t\t\t\t\t\t\t\tawait watchMedia();\n\t\t\t\t\t\t\t} else if (['office', 'ppt'].includes(vue.curType)) {\n\t\t\t\t\t\t\t\tawait watchOffice();\n\t\t\t\t\t\t\t\tif (id !== study_id) return;\n\t\t\t\t\t\t\t\tawait $.sleep(1000);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t$msg_and_log('warn', '未知的任务点，即将跳过');\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t})();\n\t\t\t\t\tif (id !== study_id) return;\n\n\t\t\t\t\tconst next = getNext();\n\t\t\t\t\tif (!next) {\n\t\t\t\t\t\treturn $msg_and_log('warn', '没有找到下一章节！');\n\t\t\t\t\t}\n\t\t\t\t\t$msg_and_log('info', '即将进入下一章节');\n\t\t\t\t\tawait $.sleep(3000);\n\t\t\t\t\tif (id !== study_id) return;\n\t\t\t\t\tnext.click();\n\t\t\t\t};\n\n\t\t\t\tconst getNext = () => {\n\t\t\t\t\tconst list = Array.from(document.querySelectorAll('.panelList .node'));\n\t\t\t\t\tfor (let index = 0; index < list.length; index++) {\n\t\t\t\t\t\tconst element = list[index];\n\n\t\t\t\t\t\tif (element.classList.contains('active')) {\n\t\t\t\t\t\t\treturn list[index + 1] as HTMLElement | undefined;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\tstudy(study_id);\n\t\t\t}\n\t\t}),\n\t\t'ai-work': new Script({\n\t\t\tname: '✍️ AI作业',\n\t\t\tnamespace: 'icve.ai.work',\n\t\t\tmatches: [['作业页面', 'ai.icve.com.cn/preview-exam']],\n\t\t\tconfigs: {\n\t\t\t\tnotes: {\n\t\t\t\t\tdefaultValue: $ui.notes([\n\t\t\t\t\t\t'自动答题前请在 “通用-全局设置” 中设置题库配置。',\n\t\t\t\t\t\t'可以搭配 “通用-在线搜题” 一起使用。',\n\t\t\t\t\t\t'请手动进入作业考试页面才能使用自动答题。',\n\t\t\t\t\t\t'自动答题时请勿切换题目，否则可能导致重复搜题或者脚本卡主！'\n\t\t\t\t\t]).outerHTML\n\t\t\t\t}\n\t\t\t},\n\t\t\toncomplete() {\n\t\t\t\t$message.warn({ content: '自动答题时请勿切换题目，否则可能导致重复搜题或者脚本卡主。', duration: 0 });\n\n\t\t\t\t// 回到第一题\n\t\t\t\tconst resetToBegin = () => {\n\t\t\t\t\tdocument.querySelectorAll<HTMLElement>(`.list-box span`).item(0)?.click();\n\t\t\t\t};\n\n\t\t\t\tcommonWork(this, {\n\t\t\t\t\tworkerProvider: aiWork,\n\t\t\t\t\tbeforeRunning: async () => {\n\t\t\t\t\t\tresetToBegin();\n\t\t\t\t\t\tawait $.sleep(1000);\n\t\t\t\t\t},\n\t\t\t\t\tonRestart: () => resetToBegin()\n\t\t\t\t});\n\t\t\t}\n\t\t})\n\t}\n});\n\nasync function watchMedia() {\n\tconst media = await waitForMedia();\n\tmedia.volume = parseFloat(IcveMoocProject.scripts['ai-study'].cfg.volume.toString());\n\tmedia.playbackRate = parseFloat(IcveMoocProject.scripts['ai-study'].cfg.playbackRate.toString());\n\tstate.study.currentMedia = media;\n\tconst success = await playMedia(() => media.play());\n\tif (!success) {\n\t\treturn;\n\t}\n\n\treturn new Promise<void>((resolve, reject) => {\n\t\tmedia.addEventListener('ended', () => {\n\t\t\tresolve();\n\t\t});\n\n\t\tmedia.addEventListener('pause', () => {\n\t\t\tsetTimeout(() => {\n\t\t\t\tif (media.ended) {\n\t\t\t\t\tresolve();\n\t\t\t\t} else if (media.paused) {\n\t\t\t\t\tmedia.play();\n\t\t\t\t\tmedia.volume = parseFloat(IcveMoocProject.scripts['ai-study'].cfg.volume.toString());\n\t\t\t\t\tmedia.playbackRate = parseFloat(IcveMoocProject.scripts['ai-study'].cfg.playbackRate.toString());\n\t\t\t\t}\n\t\t\t}, 1000);\n\t\t});\n\t});\n}\n\nfunction work({ answererWrappers, period, thread, answerSeparators, answerMatchMode }: CommonWorkOptions) {\n\t$message.info('开始作业');\n\tCommonProject.scripts.workResults.methods.init();\n\n\tconsole.log({ answererWrappers, period, thread });\n\n\tconst titleTransform = (titles: (HTMLElement | undefined)[]) => {\n\t\treturn titles\n\t\t\t.filter((t) => t?.innerText)\n\t\t\t.map((t) => {\n\t\t\t\tif (t) {\n\t\t\t\t\tconst title = t.cloneNode(true) as HTMLElement;\n\t\t\t\t\ttitle.querySelector('[name*=\"questionIndex\"]')?.remove();\n\t\t\t\t\ttitle.querySelector('.q_score')?.remove();\n\t\t\t\t\treturn title.innerText.trim().replace(/^、/, '') || '';\n\t\t\t\t}\n\t\t\t\treturn '';\n\t\t\t})\n\t\t\t.join(',');\n\t};\n\n\tconst workResults: SimplifyWorkResult[] = [];\n\tlet totalQuestionCount = 0;\n\tlet requestedCount = 0;\n\tlet resolvedCount = 0;\n\n\tfunction getType(options: HTMLElement[]) {\n\t\tconst radio_len = options\n\t\t\t.map((o) => o.querySelector('[type=\"radio\"]'))\n\t\t\t.reduce((a, b) => {\n\t\t\t\treturn a + (b ? 1 : 0);\n\t\t\t}, 0);\n\n\t\treturn radio_len > 0\n\t\t\t? radio_len === 2\n\t\t\t\t? 'judgement'\n\t\t\t\t: 'single'\n\t\t\t: options.some((o) => o.querySelector('[type=\"checkbox\"]'))\n\t\t\t? 'multiple'\n\t\t\t: options.some((o) => o.querySelector('textarea'))\n\t\t\t? 'completion'\n\t\t\t: options.some((o) => o.querySelector('.fillblank_input input'))\n\t\t\t? 'fill-blank'\n\t\t\t: undefined;\n\t}\n\n\tconst worker = new OCSWorker({\n\t\troot: '.q_content',\n\t\telements: {\n\t\t\ttitle:\n\t\t\t\t'.divQuestionTitle, ' +\n\t\t\t\t// 单行填空题\n\t\t\t\t'[name=\"fillblankTitle\"]',\n\t\t\toptions:\n\t\t\t\t'.questionOptions .q_option, .questionOptions.divTextarea, ' +\n\t\t\t\t// 单行填空题\n\t\t\t\t'.answerOption'\n\t\t},\n\t\tthread: thread ?? 1,\n\t\tanswerSeparators: answerSeparators.split(',').map((s) => s.trim()),\n\t\tanswerMatchMode: answerMatchMode,\n\t\t/** 默认搜题方法构造器 */\n\t\tanswerer: (elements, ctx) => {\n\t\t\tconst title = titleTransform(elements.title);\n\t\t\tif (title) {\n\t\t\t\treturn CommonProject.scripts.apps.methods.searchAnswerInCaches(title, async () => {\n\t\t\t\t\tawait $.sleep((period ?? 3) * 1000);\n\t\t\t\t\treturn defaultAnswerWrapperHandler(answererWrappers, {\n\t\t\t\t\t\ttype: getType(ctx.elements.options) || 'unknown',\n\t\t\t\t\t\ttitle,\n\t\t\t\t\t\toptions: ctx.elements.options.map((o) => o.innerText).join('\\n')\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tthrow new Error('题目为空，请查看题目是否为空，或者忽略此题');\n\t\t\t}\n\t\t},\n\t\tasync work(ctx) {\n\t\t\tconst options = ctx.elements.options;\n\n\t\t\tconst type = getType(options);\n\t\t\tif (!type) {\n\t\t\t\tthrow new Error('无法获取题目类型！');\n\t\t\t}\n\n\t\t\tif (type === 'fill-blank') {\n\t\t\t\tconst inputs = options\n\t\t\t\t\t.map((o) => Array.from(o.querySelectorAll<HTMLInputElement>('.fillblank_input input')))\n\t\t\t\t\t.flat();\n\n\t\t\t\tfor (const searchInfo of ctx.searchInfos) {\n\t\t\t\t\tfor (const result of searchInfo.results) {\n\t\t\t\t\t\tconst answers = splitAnswer(result.answer);\n\t\t\t\t\t\tif (answers.length === inputs.length) {\n\t\t\t\t\t\t\tfor (let index = 0; index < inputs.length; index++) {\n\t\t\t\t\t\t\t\tinputs[index].value = answers[index];\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn { finish: true };\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tconst resolver = createDefaultQuestionResolver(ctx)[type];\n\t\t\t\tconst res = await resolver(ctx.searchInfos, ctx.elements.options, (type, answer, option) => {\n\t\t\t\t\tif (type === 'judgement' || type === 'single' || type === 'multiple') {\n\t\t\t\t\t\t// 这里只用判断多选题是否选中，如果选中就不用再点击了，单选题是 radio，所以不用判断。\n\t\t\t\t\t\tif (option.querySelector('.checkbox_on') === null) {\n\t\t\t\t\t\t\t$el('div', option)?.click();\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (type === 'completion' && answer.trim()) {\n\t\t\t\t\t\tconst text = option.querySelector('textarea');\n\t\t\t\t\t\tconst textIframe = option.querySelector<HTMLIFrameElement>('iframe[id*=\"ueditor\"]');\n\t\t\t\t\t\tif (text) {\n\t\t\t\t\t\t\ttext.value = answer;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (textIframe) {\n\t\t\t\t\t\t\tconst view = textIframe.contentWindow?.document.querySelector<HTMLElement>('body.view > p');\n\t\t\t\t\t\t\tif (view) {\n\t\t\t\t\t\t\t\tview.innerText = answer;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\treturn res;\n\t\t\t}\n\n\t\t\treturn { finish: false };\n\t\t},\n\t\tonElementSearched(elements, root) {\n\t\t\tconsole.log('elements', elements);\n\t\t},\n\n\t\t/**\n\t\t * 因为校内课的考试和作业都是一题一题做的，不像其他自动答题一样可以获取全部试卷内容。\n\t\t * 所以只能根据自定义的状态进行搜索结果的显示。\n\t\t */\n\t\tonResultsUpdate(currentResult) {\n\t\t\tif (currentResult.resolved) {\n\t\t\t\tworkResults.push(...simplifyWorkResult([currentResult], titleTransform));\n\t\t\t\tCommonProject.scripts.workResults.methods.setResults(workResults);\n\t\t\t\ttotalQuestionCount++;\n\t\t\t\trequestedCount++;\n\t\t\t\tresolvedCount++;\n\n\t\t\t\tif (currentResult.result?.finish) {\n\t\t\t\t\tCommonProject.scripts.apps.methods.addQuestionCacheFromWorkResult(\n\t\t\t\t\t\tsimplifyWorkResult([currentResult], titleTransform)\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tCommonProject.scripts.workResults.methods.updateWorkState({\n\t\t\t\t\ttotalQuestionCount,\n\t\t\t\t\trequestedCount,\n\t\t\t\t\tresolvedCount\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t});\n\n\tconst getNextBtn = () => document.querySelector('.paging_next') as HTMLElement;\n\tlet next = getNextBtn();\n\n\t(async () => {\n\t\twhile (next && worker.isClose === false) {\n\t\t\tawait worker.doWork({ enable_debug: BackgroundProject.scripts.dev.cfg.enable_answerer_debug });\n\t\t\tawait $.sleep(1000);\n\t\t\tnext = getNextBtn();\n\t\t\tif (next.style.display === 'none') {\n\t\t\t\tbreak;\n\t\t\t} else {\n\t\t\t\tnext?.click();\n\t\t\t\tawait $.sleep(1000);\n\t\t\t}\n\t\t}\n\n\t\t$message.success({ content: '作业/考试完成，请自行检查后保存或提交。', duration: 0 });\n\t\tworker.emit('done');\n\t\t// 搜索完成后才会同步答案与题目的显示，防止题目错乱\n\t\tCommonProject.scripts.workResults.cfg.questionPositionSyncHandlerType = 'icve';\n\t})();\n\n\treturn worker;\n}\n\nfunction aiWork({ answererWrappers, period, thread, answerSeparators, answerMatchMode }: CommonWorkOptions) {\n\t$message.info('开始作业');\n\tCommonProject.scripts.workResults.methods.init();\n\n\tconsole.log({ answererWrappers, period, thread });\n\n\tconst titleTransform = (titles: (HTMLElement | undefined)[]) => {\n\t\treturn titles\n\t\t\t.filter((t) => t?.innerText || t?.querySelector('img'))\n\t\t\t.map((t) => {\n\t\t\t\tif (t) {\n\t\t\t\t\tconst el = optimizationElementWithImage(t, true);\n\t\t\t\t\t// 使用 textContent 而非 innerText，因为 innerText 受 CSS 影响，\n\t\t\t\t\t// fontSize: 0px 的隐藏 span 中的图片 URL 不会被 innerText 获取\n\t\t\t\t\treturn (el.textContent || '').replace(/\\s+/g, ' ').trim() || '';\n\t\t\t\t}\n\t\t\t\treturn '';\n\t\t\t})\n\t\t\t.join(',');\n\t};\n\n\tconst workResults: SimplifyWorkResult[] = [];\n\tlet totalQuestionCount = 0;\n\tlet requestedCount = 0;\n\tlet resolvedCount = 0;\n\n\tfunction getType(options: HTMLElement[]) {\n\t\tconst radio_len = options\n\t\t\t.map((o) => o.querySelector('[type=\"radio\"]'))\n\t\t\t.reduce((a, b) => {\n\t\t\t\treturn a + (b ? 1 : 0);\n\t\t\t}, 0);\n\n\t\treturn radio_len > 0\n\t\t\t? radio_len === 2\n\t\t\t\t? 'judgement'\n\t\t\t\t: 'single'\n\t\t\t: options.some((o) => o.querySelector('[type=\"checkbox\"]'))\n\t\t\t? 'multiple'\n\t\t\t: options.some((o) => o.querySelector('textarea')) || options.some((o) => o.classList.contains('ivu-input'))\n\t\t\t? 'completion'\n\t\t\t: options.some((o) => o.querySelector('.fillblank_input input'))\n\t\t\t? 'fill-blank'\n\t\t\t: undefined;\n\t}\n\n\tconst worker = new OCSWorker({\n\t\troot: '.content-item',\n\t\telements: {\n\t\t\ttitle: '.questions-content [class*=title-content]',\n\t\t\toptions: 'label[class*=group-item],.ivu-input-wrapper input'\n\t\t},\n\t\tthread: thread ?? 1,\n\t\tanswerSeparators: answerSeparators.split(',').map((s) => s.trim()),\n\t\tanswerMatchMode: answerMatchMode,\n\t\t/** 默认搜题方法构造器 */\n\t\tanswerer: (elements, ctx) => {\n\t\t\tconst title = titleTransform(elements.title);\n\t\t\tif (title) {\n\t\t\t\treturn CommonProject.scripts.apps.methods.searchAnswerInCaches(title, async () => {\n\t\t\t\t\tawait $.sleep((period ?? 3) * 1000);\n\t\t\t\t\treturn defaultAnswerWrapperHandler(answererWrappers, {\n\t\t\t\t\t\ttype: getType(ctx.elements.options) || 'unknown',\n\t\t\t\t\t\ttitle,\n\t\t\t\t\t\toptions: ctx.elements.options.map((o) => optimizationElementWithImage(o, true).innerText).join('\\n')\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tthrow new Error('题目为空，请查看题目是否为空，或者忽略此题');\n\t\t\t}\n\t\t},\n\n\t\twork: {\n\t\t\ttype: (ctx) => {\n\t\t\t\treturn getType(ctx.elements.options) as QuestionTypes;\n\t\t\t},\n\t\t\tasync handler(type, answer, option, ctx) {\n\t\t\t\tif (type === 'judgement' || type === 'single' || type === 'multiple') {\n\t\t\t\t\t// 这里只用判断多选题是否选中，如果选中就不用再点击了，单选题是 radio，所以不用判断。\n\t\t\t\t\tif (option.querySelector('.ivu-radio-checked') === null) {\n\t\t\t\t\t\toption?.click();\n\t\t\t\t\t}\n\t\t\t\t} else if (type === 'completion' && answer.trim()) {\n\t\t\t\t\tif (option.tagName === 'INPUT') {\n\t\t\t\t\t\toption.focus();\n\t\t\t\t\t\tawait $.sleep(100);\n\t\t\t\t\t\t// @ts-ignore\n\t\t\t\t\t\toption.value = answer.trim();\n\t\t\t\t\t\tawait $.sleep(100);\n\t\t\t\t\t\toption.dispatchEvent(new Event('input', { bubbles: true }));\n\t\t\t\t\t\tawait $.sleep(100);\n\t\t\t\t\t\toption.blur();\n\t\t\t\t\t\tawait $.sleep(100);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tonElementSearched(elements, root) {\n\t\t\tconsole.log('elements', elements);\n\t\t\t// 对选项元素进行图片优化，使默认 resolver 的 innerText 匹配也能获取到图片链接\n\t\t\telements.options?.forEach((option) => optimizationElementWithImage(option));\n\t\t},\n\n\t\t/**\n\t\t * 因为校内课的考试和作业都是一题一题做的，不像其他自动答题一样可以获取全部试卷内容。\n\t\t * 所以只能根据自定义的状态进行搜索结果的显示。\n\t\t */\n\t\tonResultsUpdate(currentResult) {\n\t\t\tif (currentResult.resolved) {\n\t\t\t\tworkResults.push(...simplifyWorkResult([currentResult], titleTransform));\n\t\t\t\tCommonProject.scripts.workResults.methods.setResults(workResults);\n\t\t\t\ttotalQuestionCount++;\n\t\t\t\trequestedCount++;\n\t\t\t\tresolvedCount++;\n\n\t\t\t\tif (currentResult.result?.finish) {\n\t\t\t\t\tCommonProject.scripts.apps.methods.addQuestionCacheFromWorkResult(\n\t\t\t\t\t\tsimplifyWorkResult([currentResult], titleTransform)\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tCommonProject.scripts.workResults.methods.updateWorkState({\n\t\t\t\t\ttotalQuestionCount,\n\t\t\t\t\trequestedCount,\n\t\t\t\t\tresolvedCount\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t});\n\n\tconst getNextBtn = () => document.querySelector('div.center_btn > button:nth-child(2)') as HTMLElement;\n\tlet next = getNextBtn();\n\n\t(async () => {\n\t\twhile (next && worker.isClose === false) {\n\t\t\tawait worker.doWork({ enable_debug: BackgroundProject.scripts.dev.cfg.enable_answerer_debug });\n\t\t\tawait $.sleep(1000);\n\t\t\tnext = getNextBtn();\n\t\t\tif (next.getAttribute('disabled')) {\n\t\t\t\tbreak;\n\t\t\t} else {\n\t\t\t\tnext?.click();\n\t\t\t\tawait $.sleep(1000);\n\t\t\t}\n\t\t}\n\n\t\t$message.success({ content: '作业/考试完成，请自行检查后保存或提交。', duration: 0 });\n\t\tworker.emit('done');\n\t\t// 搜索完成后才会同步答案与题目的显示，防止题目错乱\n\t\tCommonProject.scripts.workResults.cfg.questionPositionSyncHandlerType = 'icve';\n\t})();\n\n\treturn worker;\n}\n\n/**\n * 等待弹出的答题框，并点击确定\n */\nfunction waitForPopupQuestion(dom: Document) {\n\treturn new Promise<void>((resolve) => {\n\t\tconst interval = setInterval(() => {\n\t\t\tconst el = $el('.popup-test', dom);\n\t\t\tif (el) {\n\t\t\t\tclearInterval(interval);\n\t\t\t\tconst right_answer = $el<HTMLInputElement>('#right_answer', el)?.value || 'A';\n\t\t\t\tfor (const answer of right_answer.split('')) {\n\t\t\t\t\tconst item = $el(`li.test-item-cell[curval=\"${answer}\"]`, el);\n\t\t\t\t\titem?.click();\n\t\t\t\t}\n\n\t\t\t\t$el('[name=\"save_btn\"]', el)?.click();\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t$el('[name=\"continue_btn\"]', el)?.click();\n\t\t\t\t\tresolve();\n\t\t\t\t}, 3000);\n\t\t\t}\n\t\t}, 1000);\n\n\t\tsetTimeout(() => {\n\t\t\tclearInterval(interval);\n\t\t\tresolve();\n\t\t\tconsole.log('未找到弹窗，继续执行');\n\t\t}, 60 * 1000);\n\t});\n}\n\nfunction handleContinueDialog() {\n\treturn new Promise<void>((resolve, reject) => {\n\t\tconst interval = setInterval(() => {\n\t\t\tconst el = document.querySelector<HTMLElement>('.layui-layer-btn0');\n\t\t\tif (el) {\n\t\t\t\tel.click();\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\tclearInterval(interval);\n\t\t\t\t\tresolve();\n\t\t\t\t}, 1000);\n\t\t\t}\n\t\t}, 3000);\n\n\t\tsetTimeout(() => {\n\t\t\tclearInterval(interval);\n\t\t\tresolve();\n\t\t\tconsole.log('未找到弹窗，继续执行');\n\t\t}, 60 * 1000);\n\t});\n}\n"
  },
  {
    "path": "packages/scripts/src/projects/yuketang.ts",
    "content": "import { $, $elements, Project, Script, $message, $modal, $el } from 'easy-us';\nimport { $msg, playMedia } from '../utils';\nimport { request } from '@ocsjs/core';\nimport { restudy, volume } from '../utils/configs';\nimport { waitForElement } from '../utils/study';\nimport { CommonProject } from './common';\n\nconst state = {\n\tstudy: {\n\t\tcurrentMedia: undefined as HTMLMediaElement | undefined\n\t}\n};\n\nexport const YKTProject = Project.create({\n\tname: '雨课堂',\n\tdomains: ['yuketang.cn'],\n\tscripts: {\n\t\tguide: new Script({\n\t\t\tname: '🖥️ 使用提示',\n\t\t\tmatches: [['雨课堂课程列表', 'https://www.yuketang.cn/v2/web/index']],\n\t\t\tnamespace: 'yuketang.study.guide',\n\t\t\tconfigs: {\n\t\t\t\tnotes: {\n\t\t\t\t\tdefaultValue: '请点击课程里面任意章节，进入学习。'\n\t\t\t\t}\n\t\t\t}\n\t\t}),\n\t\tglobal: new Script({\n\t\t\tname: '全局脚本',\n\t\t\tmatches: [['全部界面', /.*/]],\n\t\t\thideInPanel: true,\n\t\t\tonstart(...args) {\n\t\t\t\t// 雨课堂反混淆，雨课堂修改了 attachShadow 方法\n\t\t\t\t// 这里重写removeChild方法，防止删除wrapper元素\n\t\t\t\tconst _removeChild = Element.prototype.removeChild;\n\t\t\t\tElement.prototype.removeChild = function (e) {\n\t\t\t\t\tif (e.nodeName === 'DIV') {\n\t\t\t\t\t\tif ($elements.wrapper && e === ($elements.wrapper as Node)) {\n\t\t\t\t\t\t\t($elements.wrapper as HTMLElement).removeAttribute('style');\n\t\t\t\t\t\t\treturn e;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t_removeChild.call(this, e);\n\t\t\t\t\treturn e;\n\t\t\t\t};\n\t\t\t}\n\t\t}),\n\t\tai: new Script({\n\t\t\tname: '🤖 AI学伴',\n\t\t\tmatches: [['AI学伴课程界面', 'https://www.yuketang.cn/ai-workspace/lms-graph']],\n\t\t\tnamespace: 'yuketang.study.ai',\n\t\t\tconfigs: {\n\t\t\t\tnotes: {\n\t\t\t\t\tdefaultValue: '请点击任意章节，进入学习。'\n\t\t\t\t},\n\t\t\t\trestudy: restudy,\n\t\t\t\treloadWhenError: {\n\t\t\t\t\tlabel: '黑屏自动刷新',\n\t\t\t\t\tattrs: { title: '视频黑屏或者检测不到视频时自动刷新页面', type: 'checkbox' },\n\t\t\t\t\tdefaultValue: true\n\t\t\t\t},\n\t\t\t\tvolume: volume,\n\t\t\t\tplaybackRate: {\n\t\t\t\t\tlabel: '视频倍速',\n\t\t\t\t\ttag: 'select',\n\t\t\t\t\tdefaultValue: 1,\n\t\t\t\t\toptions: [\n\t\t\t\t\t\t['1', '1 x'],\n\t\t\t\t\t\t['1.25', '1.25 x'],\n\t\t\t\t\t\t['1.5', '1.5 x'],\n\t\t\t\t\t\t['2.0', '2.0 x']\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t},\n\t\t\tasync oncomplete() {\n\t\t\t\tawait $.sleep(3000);\n\t\t\t\tCommonProject.scripts.render.methods.pin(this);\n\n\t\t\t\t// 监听音量\n\t\t\t\tthis.onConfigChange('volume', (curr) => {\n\t\t\t\t\tstate.study.currentMedia && (state.study.currentMedia.volume = curr);\n\t\t\t\t});\n\n\t\t\t\t// 监听速度\n\t\t\t\tthis.onConfigChange('playbackRate', (curr) => {\n\t\t\t\t\tstate.study.currentMedia && (state.study.currentMedia.playbackRate = curr);\n\t\t\t\t});\n\n\t\t\t\t// // 展开5次章节，确保所有章节都被展开\n\t\t\t\tconst max_level = 5;\n\t\t\t\tfor (let i = 0; i < max_level; i++) {\n\t\t\t\t\tdocument.querySelectorAll<HTMLElement>('.expand-icon:not(.is-expanded )').forEach((el) => el.click());\n\t\t\t\t\tawait $.sleep(100);\n\t\t\t\t}\n\n\t\t\t\tconst getJobs = () => Array.from(document.querySelectorAll<HTMLElement>('div.leaf-item'));\n\t\t\t\tconst getJobName = () =>\n\t\t\t\t\tdocument.querySelector('.leaf-item.is-active .leaf-item-title')?.textContent || '未知任务点';\n\t\t\t\tconst getNextJob = () => {\n\t\t\t\t\tlet jobs = getJobs();\n\t\t\t\t\tconst active_index = jobs.findIndex((job) => job.classList.contains('is-active'));\n\n\t\t\t\t\t// 不是复习模式，过滤掉已经完成的\n\t\t\t\t\tif (!this.cfg.restudy) {\n\t\t\t\t\t\tjobs = jobs.splice(active_index);\n\t\t\t\t\t\tjobs = jobs.filter((el) => !el.querySelector('.icon-yuanquangou'));\n\t\t\t\t\t\tjobs = jobs.filter((el) => !(el.querySelector('.leaf-item-tag')?.textContent || '').includes('自测'));\n\t\t\t\t\t}\n\t\t\t\t\tconst new_active_index = jobs.findIndex((job) => job.classList.contains('is-active'));\n\t\t\t\t\treturn jobs[new_active_index + 1];\n\t\t\t\t};\n\n\t\t\t\ttry {\n\t\t\t\t\t$msg.info('等待任务加载中...');\n\t\t\t\t\tawait waitForElement('.detail-container', {\n\t\t\t\t\t\ttimeout_seconds: 10 * 1000\n\t\t\t\t\t});\n\t\t\t\t\t$msg.info('即将开始自动学习');\n\t\t\t\t} catch (e) {\n\t\t\t\t\t$message.error('元素加载失败，请刷新界面重试。');\n\t\t\t\t}\n\n\t\t\t\tconst study = async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tif ($el('.detail-container video')) {\n\t\t\t\t\t\t\t$msg.info('即将开始视频学习：' + getJobName());\n\t\t\t\t\t\t\tawait watch({\n\t\t\t\t\t\t\t\tvolume: this.cfg.volume,\n\t\t\t\t\t\t\t\tplaybackRate: this.cfg.playbackRate\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t$msg.success('视频学习完成');\n\t\t\t\t\t\t\tawait $.sleep(3000);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif ($el('.detail-container .problem-common')) {\n\t\t\t\t\t\t\t$msg.warn('自测任务暂未支持，请联系作者反馈：' + getJobName());\n\t\t\t\t\t\t\tawait $.sleep(3000);\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\t$message.error(`当前任务点无法完成，即将跳转下一节（${e}）`);\n\t\t\t\t\t}\n\t\t\t\t\tconst next = getNextJob();\n\t\t\t\t\tif (!next) {\n\t\t\t\t\t\treturn $modal.alert({\n\t\t\t\t\t\t\tcontent: '检测到当前视频全部播放完毕，如果还有未完成的视频请刷新重试，或者打开复习模式。'\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\tnext.click();\n\t\t\t\t\tawait $.sleep(200);\n\t\t\t\t\tnext.scrollIntoView({ behavior: 'smooth', block: 'center' });\n\t\t\t\t\tawait $.sleep(3000);\n\t\t\t\t\tstudy();\n\t\t\t\t};\n\n\t\t\t\tstudy();\n\t\t\t}\n\t\t}),\n\t\t// TODO 作业\n\t\t'font-decrypt': new Script({\n\t\t\tname: '🔤 字体解密',\n\t\t\tmatches: [['AI伴学自测界面', '/v2/web/iframe-self-test']],\n\t\t\tasync oncomplete() {\n\t\t\t\tconst mapping = await loadFontMapping();\n\n\t\t\t\tconsole.log(mapping);\n\n\t\t\t\tconst els = Array.from(document.querySelectorAll('.xuetangx-com-encrypted-font'));\n\t\t\t\tfor (const el of els) {\n\t\t\t\t\t// 替换\n\t\t\t\t\tfor (const _char in mapping) {\n\t\t\t\t\t\tif (el.textContent?.includes(_char)) {\n\t\t\t\t\t\t\tel.textContent = el.textContent.replace(new RegExp(_char, 'g'), mapping[_char]);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconsole.log('字体替换完成');\n\t\t\t}\n\t\t})\n\t}\n});\n\nasync function loadFontMapping() {\n\ttry {\n\t\t$msg.info('正在解析字体');\n\t\treturn await request('https://cdn.ocsjs.com/resources/font/yuketang_font_map.json', {\n\t\t\ttype: 'GM_xmlhttpRequest',\n\t\t\tmethod: 'get',\n\t\t\tresponseType: 'json'\n\t\t});\n\t} catch (err) {\n\t\t$msg.error('载繁体字库加载失败，请刷新页面重试：' + String(err));\n\t}\n}\n\n/**\n * 观看视频\n * @param setting\n * @returns\n */\nasync function watch(options: { volume: number; playbackRate: number }) {\n\tconst set = async () => {\n\t\t// 上面操作会导致元素刷新，这里重新获取视频\n\t\tawait $.sleep(1000);\n\t\tconst media = (await waitForElement('.detail-container video', {\n\t\t\ttimeout_seconds: 10 * 1000\n\t\t})) as HTMLMediaElement;\n\t\tconsole.log('media', media);\n\t\tawait $.sleep(1000);\n\t\tstate.study.currentMedia = media;\n\n\t\tif (media) {\n\t\t\t// 如果已经播放完了，则重置视频进度\n\t\t\tmedia.currentTime = 1;\n\t\t\t// 音量\n\t\t\tmedia.volume = options.volume;\n\t\t\tmedia.playbackRate = options.playbackRate;\n\t\t}\n\t\treturn state.study.currentMedia;\n\t};\n\t$message.info('开始播放');\n\tconst video = await set();\n\n\tif (!video) {\n\t\tthrow new Error('video not found!');\n\t}\n\n\treturn new Promise<void>((resolve, reject) => {\n\t\tconst videoCheckInterval = setInterval(async () => {\n\t\t\t// 如果视频元素无法访问，证明已经切换了视频\n\t\t\tif (video?.isConnected === false) {\n\t\t\t\tclearInterval(videoCheckInterval);\n\t\t\t\t$message.info({ content: '检测到视频切换中...' });\n\t\t\t\t/**\n\t\t\t\t * 元素无法访问证明用户切换视频了\n\t\t\t\t * 所以不往下播放视频，而是重新播放用户当前选中的视频\n\t\t\t\t */\n\t\t\t\tresolve();\n\t\t\t}\n\t\t}, 3000);\n\n\t\tplayMedia(() => video?.play());\n\n\t\tvideo.onpause = async () => {\n\t\t\tif (!video?.ended) {\n\t\t\t\tawait $.sleep(1000);\n\t\t\t\tvideo?.play();\n\t\t\t}\n\t\t};\n\n\t\tvideo.onended = () => {\n\t\t\tclearInterval(videoCheckInterval);\n\t\t\t// 正常切换下一个视频\n\t\t\tresolve();\n\t\t};\n\t});\n}\n"
  },
  {
    "path": "packages/scripts/src/projects/zhs.ts",
    "content": "import { $ui, Project, Script, $el, h, $$el, $message, $, $modal, MessageElement, $store, $gm } from 'easy-us';\nimport { RemotePage, SimplifyWorkResult, OCSWorker, defaultAnswerWrapperHandler } from '@ocsjs/core';\nimport { CommonProject } from './common';\nimport { workNotes, definition, volume, restudy } from '../utils/configs';\nimport {\n\tcommonWork,\n\tcreateUnVisibleTextOfImage,\n\toptimizationElementWithImage,\n\tremoveRedundantWords,\n\tsimplifyWorkResult\n} from '../utils/work';\nimport { CommonWorkOptions, playMedia } from '../utils';\nimport { $console, BackgroundProject } from './background';\nimport { waitForMedia, waitForElement } from '../utils/study';\nimport { $playwright } from '../utils/app';\nimport { $render } from '../utils/render';\n\nconst state = {\n\tstudy: {\n\t\t/**\n\t\t * 学习是否暂停\n\t\t */\n\t\tstop: false,\n\t\tcurrentMedia: undefined as HTMLMediaElement | undefined,\n\t\tstopInterval: 0 as any,\n\t\tstopMessage: undefined as MessageElement | undefined\n\t}\n};\n\n/**\n * 需要软件辅助的掌握度页面\n */\nconst remote_not_required_pages = ['fusioncourseh5.zhihuishu.com', 'studywisdomh5.zhihuishu.com'];\n\nconst gxk_read_notes = [\n\t'⚠️ 如果未开始答题，请尝试刷新页面。',\n\t'⚠️ 禁止同时打开多个作业/考试页面。',\n\t['⚠️ 答题中请勿进行任何操作，如需暂停答题', '请等待全部题目搜索完成并执行自动保存功能后才能操作。'],\n\t['⚠️ 暂停后手动操作请确保每个题目都点击下一题', '进行答案保存（不然不会保存，提交没分）']\n];\n\n/**\n * 2024 下半年智慧树更新至两个版本，其中一个改进了部分UI，所以这里使用两个处理器 StudyVideoH5,FusionCourseH5 对不同UI进行处理\n */\ninterface ZHSProcessor {\n\tremotePage: RemotePage | undefined | void;\n\tinit?(): void;\n\tgetCourseName(): string;\n\tgetChapterName(root: HTMLElement): string;\n\thasJob(): boolean;\n\tgetNext(opts: { next: boolean; restudy: boolean }): HTMLElement | undefined;\n\thideDialog(): void;\n\thandleTestDialog(remotePage?: RemotePage): void | Promise<void>;\n\tswitchPlaybackRate(rate: number, remotePage?: RemotePage): void | Promise<void>;\n\tswitchLine(definition: 'line1bq' | 'line1gq', remotePage?: RemotePage): void | Promise<void>;\n}\n\nclass StudyVideoH5 implements ZHSProcessor {\n\tremotePage: RemotePage | undefined = undefined;\n\tasync init() {\n\t\tthis.remotePage = await BackgroundProject.scripts.dev.methods.getRemotePlaywrightCurrentPage();\n\t\t// 检查是否为软件环境\n\t\tif (!this.remotePage) {\n\t\t\tthrow $playwright.showError();\n\t\t}\n\t}\n\n\tgetCourseName() {\n\t\treturn $el('.source-name')?.textContent || '无名称';\n\t}\n\n\tgetChapterName(root: HTMLElement): string {\n\t\treturn root.querySelector('.catalogue_title')?.textContent || '未知章节';\n\t}\n\n\tgetNext(opts: { next: boolean; restudy: boolean }) {\n\t\tlet videoItems = Array.from(document.querySelectorAll<HTMLElement>('.clearfix.video'));\n\t\t// 如果不是复习模式，则排除掉已经完成的任务\n\t\tif (!opts.restudy) {\n\t\t\tvideoItems = videoItems.filter((el) => el.querySelector('.time_icofinish') === null);\n\t\t}\n\n\t\tfor (let i = 0; i < videoItems.length; i++) {\n\t\t\tconst item = videoItems[i];\n\t\t\tif (item.classList.contains('current_play')) {\n\t\t\t\treturn videoItems[i + (opts.next ? 1 : 0)];\n\t\t\t}\n\t\t}\n\n\t\treturn videoItems[0];\n\t}\n\n\tasync switchPlaybackRate(rate: number) {\n\t\tconst controlsBar = $el('.controlsBar');\n\t\tconst sl = $el('.speedList');\n\t\tif (controlsBar && sl) {\n\t\t\tcontrolsBar.style.display = 'block';\n\t\t\tsl.style.display = 'block';\n\t\t\t/**\n\t\t\t * 兼容 1.0 和 1 的两个属性值匹配\n\t\t\t */\n\t\t\tconst rate_parsed = parseFloat(String(rate));\n\t\t\tconst selector = `.speedList [rate=\"${rate_parsed === 1 ? '1.0' : rate}\"],.speedList [rate=\"${\n\t\t\t\trate_parsed === 1 ? '1' : rate\n\t\t\t}\"]`;\n\t\t\tif (this.remotePage) {\n\t\t\t\tawait this.remotePage.click(selector);\n\t\t\t} else {\n\t\t\t\tdocument.querySelector<HTMLElement>(selector)?.click();\n\t\t\t}\n\t\t}\n\t}\n\n\tasync switchLine(definition: 'line1bq' | 'line1gq') {\n\t\tconst controlsBar = $el('.controlsBar');\n\t\tconst dl = $el('.definiLines');\n\n\t\tif (controlsBar && dl) {\n\t\t\tcontrolsBar.style.display = 'block';\n\t\t\tdl.style.display = 'block';\n\t\t\t// :not(.active) ： 如果已经是激活状态则不点击\n\t\t\tconst selector = `.definiLines .${definition}:not(.active)`;\n\t\t\tconst el = document.querySelector<HTMLElement>(selector);\n\t\t\tif (el) {\n\t\t\t\tif (this.remotePage) {\n\t\t\t\t\tawait this.remotePage.click(selector);\n\t\t\t\t} else {\n\t\t\t\t\tel.click();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\thasJob() {\n\t\treturn $$el('.clearfix.video')?.length > 0;\n\t}\n\n\thideDialog() {\n\t\t/** 隐藏通知弹窗 */\n\t\t$$el('.el-dialog__wrapper').forEach((dialog) => {\n\t\t\tdialog.remove();\n\t\t});\n\t}\n\n\tasync handleTestDialog() {\n\t\tconst items = $$el('#playTopic-dialog .el-pager .number');\n\t\tif (items.length) {\n\t\t\tfor (const item of items) {\n\t\t\t\tif (item.classList.contains('active') === false) {\n\t\t\t\t\titem.click();\n\t\t\t\t\tawait $.sleep(500);\n\t\t\t\t}\n\n\t\t\t\tconst options = $$el('#playTopic-dialog ul .topic-item');\n\t\t\t\tif (options.length !== 0) {\n\t\t\t\t\tawait waitForCaptcha();\n\t\t\t\t\t// 最小化脚本窗口\n\t\t\t\t\t$render.moveToEdge();\n\t\t\t\t\t// 随机选\n\t\t\t\t\tconst random = Math.floor(Math.random() * options.length);\n\t\t\t\t\tawait $.sleep(1000);\n\t\t\t\t\t// nth-child 从1开始\n\t\t\t\t\tconst btn_selector = `#playTopic-dialog .topic .radio ul > li:nth-child(${random + 1})`;\n\t\t\t\t\tif (this.remotePage) {\n\t\t\t\t\t\tawait this.remotePage.click(btn_selector);\n\t\t\t\t\t} else {\n\t\t\t\t\t\t$el(btn_selector)?.click();\n\t\t\t\t\t}\n\n\t\t\t\t\tawait $.sleep(1000);\n\t\t\t\t}\n\t\t\t}\n\t\t\tawait $.sleep(1000);\n\t\t\t// 关闭弹窗\n\t\t\tconst close_btn_selector = '#playTopic-dialog .close-btn,#playTopic-dialog .btn';\n\t\t\tif (this.remotePage) {\n\t\t\t\tawait this.remotePage.click(close_btn_selector);\n\t\t\t} else {\n\t\t\t\t$el(close_btn_selector)?.click();\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * 每过三秒递归检测是否有弹窗\n\t\t */\n\t\tawait $.sleep(3000);\n\t\tawait this.handleTestDialog();\n\t}\n}\n\n/**\n * AI 助教课程，为智慧课程分类中的新课程\n */\nclass FusionCourseH5 extends StudyVideoH5 implements ZHSProcessor {\n\tgetCourseName() {\n\t\t// 无法读取课程名称\n\t\treturn '智慧课程-AI';\n\t}\n\n\tgetChapterName(root: HTMLElement): string {\n\t\t// AI 助教课有两种进度模式，一个是百分百，一个是必学项目进度条\n\t\tconst is_resource_box_mode = !!document.querySelector('.resource-box');\n\t\tif (is_resource_box_mode) {\n\t\t\t// 小节名称\n\t\t\tconst card_name = root.querySelector('.file-name');\n\t\t\tif (card_name) {\n\t\t\t\treturn card_name.textContent || '未知章节';\n\t\t\t}\n\t\t}\n\t\treturn super.getChapterName(root);\n\t}\n\n\tgetNext(opts: { next: boolean; restudy: boolean }) {\n\t\t// AI 助教课有两种进度模式，一个是百分百，一个是必学项目进度条\n\t\tconst is_resource_box_mode = !!document.querySelector('.resource-box');\n\t\tif (is_resource_box_mode) {\n\t\t\t// 检测小节\n\n\t\t\tconst getNextCard = () => {\n\t\t\t\tconst cards = Array.from(document.querySelectorAll('.resources-item'));\n\n\t\t\t\tlet target_el;\n\t\t\t\tlet start = false;\n\t\t\t\tfor (let index = 0; index < cards.length; index++) {\n\t\t\t\t\tconst card = cards[index];\n\t\t\t\t\tif (start) {\n\t\t\t\t\t\tif (opts.restudy) {\n\t\t\t\t\t\t\ttarget_el = card;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tif (card.querySelector('.isFinish')) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\ttarget_el = card;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (card.className.includes('active')) {\n\t\t\t\t\t\tstart = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn target_el;\n\t\t\t};\n\n\t\t\tconst next_card = getNextCard();\n\t\t\tif (next_card) {\n\t\t\t\treturn next_card as HTMLElement;\n\t\t\t}\n\t\t}\n\n\t\tlet videoItems = Array.from(document.querySelectorAll<HTMLElement>('.clearfix.video')).filter((el) => {\n\t\t\tif (is_resource_box_mode) {\n\t\t\t\treturn !!el.querySelector('.resource-box');\n\t\t\t}\n\t\t\treturn true;\n\t\t});\n\n\t\tconsole.log(videoItems);\n\n\t\t// 如果不是复习模式，则排除掉已经完成的任务\n\t\tif (!opts.restudy) {\n\t\t\tif (is_resource_box_mode) {\n\t\t\t\tvideoItems = videoItems.filter((el) => {\n\t\t\t\t\tconst text = el.querySelector('.resource-text')?.textContent || '';\n\t\t\t\t\tconst [progress, total] = text\n\t\t\t\t\t\t.replace('必学', '')\n\t\t\t\t\t\t.trim()\n\t\t\t\t\t\t.split('/')\n\t\t\t\t\t\t.map((s) => parseInt(s));\n\t\t\t\t\treturn progress < total;\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tvideoItems = videoItems.filter((el) => {\n\t\t\t\t\tconst num_el = el.querySelector('.progress-num');\n\t\t\t\t\treturn num_el === null || num_el.textContent !== '100%';\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tfor (let i = 0; i < videoItems.length; i++) {\n\t\t\tconst item = videoItems[i];\n\n\t\t\tif (is_resource_box_mode) {\n\t\t\t\tif (item.classList.contains('activeNode')) {\n\t\t\t\t\treturn videoItems[i + (opts.next ? 1 : 0)];\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif (item.classList.contains('current_play')) {\n\t\t\t\t\treturn videoItems[i + (opts.next ? 1 : 0)];\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn videoItems[0];\n\t}\n}\n\n/**\n * 新共享课页面，为智慧课程分类中的新课程\n */\nclass StudyPlusH5 extends StudyVideoH5 implements ZHSProcessor {\n\tgetCourseName() {\n\t\treturn $el('.top-back-box > span:nth-child(2)')?.textContent?.match(/课程名称：(.+)/)?.[1] || '无名称';\n\t}\n\n\tgetChapterName(item: HTMLElement): string {\n\t\treturn item.parentElement?.textContent || '未知章节';\n\t}\n\n\thasJob() {\n\t\treturn $$el('.child-main')?.length > 0;\n\t}\n\n\tgetNext(opts: { next: boolean; restudy: boolean }) {\n\t\tlet videoItems = Array.from(document.querySelectorAll<HTMLElement>('.child-main')).filter((el) =>\n\t\t\tel.parentElement?.querySelector('.child-time')\n\t\t);\n\t\tconsole.log(videoItems);\n\t\t// 如果不是复习模式，则排除掉已经完成的任务\n\t\tif (!opts.restudy) {\n\t\t\tvideoItems = videoItems.filter((el) => {\n\t\t\t\tif (el.parentElement?.querySelector('.finish-icon')) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t\t});\n\t\t}\n\n\t\tfor (let i = 0; i < videoItems.length; i++) {\n\t\t\tconst item = videoItems[i];\n\t\t\tif (item.classList.contains('current')) {\n\t\t\t\treturn videoItems[i + (opts.next ? 1 : 0)];\n\t\t\t}\n\t\t}\n\t\treturn videoItems[0];\n\t}\n\n\thideDialog() {\n\t\t/** 隐藏通知弹窗 */\n\t\t$$el('.el-overlay,.el-dialog').forEach((dialog) => {\n\t\t\tdialog.style.display = 'none';\n\t\t});\n\t}\n\n\tasync handleTestDialog(remotePage?: RemotePage) {\n\t\tconst question_box = $el('.ai-test-question-wrapper');\n\t\tconst done = $el('.ai-test-question-wrapper .done');\n\t\tif (question_box) {\n\t\t\t$message.info('正在关闭弹窗测验...');\n\t\t\tconst close_btn = $el('.close-box', question_box);\n\t\t\tif (done) {\n\t\t\t\tif (close_btn) {\n\t\t\t\t\tif (remotePage) {\n\t\t\t\t\t\tawait remotePage.click(close_btn);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tclose_btn.click();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tconst options = $$el('.options .option', question_box);\n\t\t\t\tif (options.length !== 0) {\n\t\t\t\t\tawait waitForCaptcha();\n\t\t\t\t\t// 最小化脚本窗口\n\t\t\t\t\t$render.moveToEdge();\n\t\t\t\t\t// 随机选\n\t\t\t\t\tconst random = Math.floor(Math.random() * options.length);\n\t\t\t\t\tawait $.sleep(1000);\n\t\t\t\t\tif (remotePage) {\n\t\t\t\t\t\tawait remotePage.click(options[random]);\n\t\t\t\t\t} else {\n\t\t\t\t\t\toptions[random].click();\n\t\t\t\t\t}\n\n\t\t\t\t\tawait $.sleep(1000);\n\t\t\t\t}\n\t\t\t\tawait $.sleep(1000);\n\t\t\t\tconst submit_btn = $el('.submit-btn .submits');\n\t\t\t\tif (submit_btn) {\n\t\t\t\t\tif (remotePage) {\n\t\t\t\t\t\tawait remotePage.click(submit_btn);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tsubmit_btn.click();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tawait $.sleep(1000);\n\n\t\t\t\tif (close_btn) {\n\t\t\t\t\tif (remotePage) {\n\t\t\t\t\t\tawait remotePage.click(close_btn);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tclose_btn.click();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * 每过三秒递归检测是否有弹窗\n\t\t */\n\t\tawait $.sleep(3000);\n\t\tawait this.handleTestDialog(remotePage);\n\t}\n}\n\n/**\n * 2025-9月份新智慧学习页面，为智慧课程分类中的新课程\n */\nclass WishdomH5 extends StudyVideoH5 implements ZHSProcessor {\n\tgetCourseName() {\n\t\treturn $el('.course-name')?.textContent?.match(/课程名称：(.+)/)?.[1] || '无名称';\n\t}\n\n\tgetChapterName(item: HTMLElement): string {\n\t\treturn item.textContent || '未知章节';\n\t}\n\n\thasJob() {\n\t\treturn $$el('.chapter-content .chapter-content-second')?.length > 0;\n\t}\n\n\tgetNext(opts: { next: boolean; restudy: boolean }) {\n\t\tlet jobs = Array.from(document.querySelectorAll<HTMLElement>('.chapter-content .chapter-item'));\n\n\t\tjobs = jobs\n\t\t\t.map((el) => {\n\t\t\t\tconst children = el.querySelectorAll<HTMLElement>('.chapter-content-second');\n\t\t\t\tif (children.length > 0) {\n\t\t\t\t\treturn Array.from(children);\n\t\t\t\t} else {\n\t\t\t\t\treturn [el];\n\t\t\t\t}\n\t\t\t})\n\t\t\t.flat();\n\n\t\tconsole.log(jobs);\n\t\t// 如果不是复习模式，则排除掉已经完成的任务\n\t\tif (!opts.restudy) {\n\t\t\tjobs = jobs.filter((el) => {\n\t\t\t\tif (el.querySelector('.finish-icon')) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t\t});\n\t\t}\n\n\t\tfor (let i = 0; i < jobs.length; i++) {\n\t\t\tconst item = jobs[i];\n\t\t\tif (item.classList.contains('current')) {\n\t\t\t\treturn jobs[i + (opts.next ? 1 : 0)];\n\t\t\t}\n\t\t}\n\t\treturn jobs[0];\n\t}\n\n\thideDialog() {\n\t\t/** 隐藏通知弹窗 */\n\t\t$$el('.el-overlay,.el-dialog').forEach((dialog) => {\n\t\t\tdialog.style.display = 'none';\n\t\t});\n\t}\n\n\tasync handleTestDialog(remotePage?: RemotePage) {\n\t\tconst question_box = $el('.ai-class-exercise-dialog');\n\t\tif (question_box) {\n\t\t\tconst options = $$el('.ques-list .item .option');\n\t\t\t$message.info('正在关闭弹窗测验...');\n\t\t\tif (options.length !== 0) {\n\t\t\t\tawait waitForCaptcha();\n\t\t\t\t// 最小化脚本窗口\n\t\t\t\tCommonProject.scripts.render.methods.minimize();\n\t\t\t\tCommonProject.scripts.render.methods.setPosition(100, 200);\n\t\t\t\t// 随机选\n\t\t\t\tconst random = Math.floor(Math.random() * options.length);\n\t\t\t\tawait $.sleep(1000);\n\t\t\t\tif (remotePage) {\n\t\t\t\t\tawait remotePage.click(options[random]);\n\t\t\t\t} else {\n\t\t\t\t\toptions[random].click();\n\t\t\t\t}\n\n\t\t\t\tawait $.sleep(1000);\n\t\t\t}\n\t\t\tawait $.sleep(1000);\n\n\t\t\tconst submit_btn = $el('.ai-class-exercise-dialog .el-dialog__footer .el-button.btn');\n\t\t\tif (submit_btn) {\n\t\t\t\tif (remotePage) {\n\t\t\t\t\tawait remotePage.hover('.ai-class-exercise-dialog .el-dialog__footer .el-button.btn');\n\t\t\t\t\tawait remotePage.click('.ai-class-exercise-dialog .el-dialog__footer .el-button.btn');\n\t\t\t\t} else {\n\t\t\t\t\tsubmit_btn.click();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst close_btn = $el('.ai-class-exercise-dialog .header-icon');\n\t\t\tif (close_btn) {\n\t\t\t\tif (remotePage) {\n\t\t\t\t\tawait remotePage.hover('.ai-class-exercise-dialog .header-icon');\n\t\t\t\t\tawait remotePage.click('.ai-class-exercise-dialog .header-icon');\n\t\t\t\t} else {\n\t\t\t\t\tclose_btn.click();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// 如果无法关闭弹窗，后续版本只能强制删除\n\t\t\t// await $.sleep(1000);\n\t\t\t// document.querySelector('.components-box.has-compoent')?.remove();\n\t\t\t// document.querySelector('.transparent-mask')?.remove();\n\t\t\t$message.info('弹窗测验已关闭');\n\t\t}\n\n\t\t/**\n\t\t * 每过三秒递归检测是否有弹窗\n\t\t */\n\t\tawait $.sleep(3000);\n\t\tawait this.handleTestDialog(remotePage);\n\t}\n}\n\n/**\n * 2025-9月份教学空间-AI智慧学习页面，新系统\n */\nclass Hike extends StudyVideoH5 implements ZHSProcessor {\n\tgetCourseName() {\n\t\treturn '无名称';\n\t}\n\n\tgetChapterName(item: HTMLElement): string {\n\t\treturn item.textContent || '未知章节';\n\t}\n\n\thasJob() {\n\t\treturn document.querySelectorAll('.source-icon')?.length > 0;\n\t}\n\n\tgetNext(opts: { next: boolean; restudy: boolean }) {\n\t\tlet jobs = Array.from(document.querySelectorAll<HTMLElement>('.source-icon')).map(\n\t\t\t(el) => el.parentElement?.parentElement as HTMLElement\n\t\t);\n\t\tconsole.log(jobs);\n\t\t// 如果不是复习模式，则排除掉已经完成的任务\n\t\tif (!opts.restudy) {\n\t\t\tjobs = jobs.filter((el) => {\n\t\t\t\tif (el.querySelector('i.select')) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t\t});\n\t\t}\n\n\t\tfor (let i = 0; i < jobs.length; i++) {\n\t\t\tconst item = jobs[i];\n\t\t\tif (item.querySelector('.active-file')) {\n\t\t\t\treturn jobs[i + (opts.next ? 1 : 0)];\n\t\t\t}\n\t\t}\n\t\treturn jobs[0];\n\t}\n\n\thideDialog() {\n\t\t/** 隐藏通知弹窗 */\n\t\t$$el('.el-overlay,.el-dialog').forEach((dialog) => {\n\t\t\tdialog.style.display = 'none';\n\t\t});\n\t}\n}\n\nclass HikeV2 extends StudyVideoH5 implements ZHSProcessor {\n\tgetCourseName() {\n\t\treturn document.querySelector('.header-title-wrap')?.textContent || '无名称';\n\t}\n\n\tgetChapterName(): string {\n\t\treturn document.querySelector('.el-tree-node.is-current .file-name')?.textContent || '未知章节';\n\t}\n\n\thasJob() {\n\t\treturn document.querySelectorAll('.el-tree-node')?.length > 0;\n\t}\n\n\tgetNext(opts: { next: boolean; restudy: boolean }) {\n\t\tlet jobs = Array.from(document.querySelectorAll<HTMLElement>('.el-tree-node')).filter(\n\t\t\t(e) => e.querySelector('.el-tree-node__children')?.children.length === 0\n\t\t);\n\t\tconsole.log(jobs);\n\t\t// 如果不是复习模式，则排除掉已经完成的任务\n\t\tif (!opts.restudy) {\n\t\t\tjobs = jobs.filter((el) => {\n\t\t\t\tif (el.querySelector('label.success')) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t\t});\n\t\t}\n\n\t\tfor (let i = 0; i < jobs.length; i++) {\n\t\t\tconst item = jobs[i];\n\t\t\tif (item.classList.contains('.is-current')) {\n\t\t\t\treturn jobs[i + (opts.next ? 1 : 0)];\n\t\t\t}\n\t\t}\n\t\treturn jobs[0];\n\t}\n\n\thideDialog() {\n\t\t/** 隐藏通知弹窗 */\n\t\t$$el('.el-overlay,.el-dialog').forEach((dialog) => {\n\t\t\tdialog.style.display = 'none';\n\t\t});\n\t}\n}\n\n/** 工程导出 */\nexport const ZHSProject = Project.create({\n\tname: '知到智慧树',\n\tdomains: [\n\t\t'zhihuishu.com',\n\t\t// 2025下半学期新系统: AI智慧课程\n\t\t'hike-teaching-center.polymas.com'\n\t],\n\tscripts: {\n\t\tguide: new Script({\n\t\t\tname: '💡 使用提示',\n\t\t\tmatches: [\n\t\t\t\t['学习首页', 'https://onlineweb.zhihuishu.com/onlinestuh5'],\n\t\t\t\t['首页', 'https://www.zhihuishu.com/']\n\t\t\t],\n\t\t\tnamespace: 'zhs.guide',\n\t\t\tconfigs: {\n\t\t\t\tnotes: {\n\t\t\t\t\tdefaultValue: $ui.notes([\n\t\t\t\t\t\t'请手动进入视频、作业、考试页面，脚本会自动运行。',\n\t\t\t\t\t\t'兴趣课会自动下一个，所以不提供脚本。'\n\t\t\t\t\t]).outerHTML\n\t\t\t\t}\n\t\t\t},\n\t\t\toncomplete() {\n\t\t\t\t// 置顶\n\t\t\t\tCommonProject.scripts.render.methods.pin(this);\n\t\t\t}\n\t\t}),\n\t\t'gxk-study': new Script({\n\t\t\tname: '🖥️ 共享课-学习脚本',\n\t\t\tmatches: [\n\t\t\t\t['共享课学习页面', 'studyvideoh5.zhihuishu.com'],\n\t\t\t\t['新共享课学习页面', 'studyplush5.zhihuishu.com'],\n\t\t\t\t['新版AI课页面', 'fusioncourseh5.zhihuishu.com/stuStudy'],\n\t\t\t\t['2025-9月新智慧共享课学习页面', 'studywisdomh5.zhihuishu.com/study/index']\n\t\t\t],\n\t\t\tnamespace: 'zhs.gxk.study',\n\t\t\tconfigs: {\n\t\t\t\tnotes: {\n\t\t\t\t\tdefaultValue: $ui.notes([\n\t\t\t\t\t\t'章节测试请大家观看完视频后手动打开。',\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\t'请大家仔细打开视频上方的”学前必读“，查看成绩分布。',\n\t\t\t\t\t\t\t'如果 “平时成绩-学习习惯成绩” 占比多的话，就需要规律学习。',\n\t\t\t\t\t\t\t'每天定时半小时可获得一分习惯分。',\n\t\t\t\t\t\t\t'如果不想要习惯分可忽略。'\n\t\t\t\t\t\t],\n\t\t\t\t\t\t'请使用时关闭卡巴斯基软件，否则会被检测出异常脚本。',\n\t\t\t\t\t\t'不要最小化浏览器，可能导致脚本暂停。',\n\t\t\t\t\t\t'运行中请将浏览器缩放调整至适合的大小，避免元素遮挡，无法点击',\n\t\t\t\t\t\t'例如：调整缩放到 50%，然后刷新页面即可'\n\t\t\t\t\t]).outerHTML\n\t\t\t\t},\n\t\t\t\t/** 学习记录 []  */\n\t\t\t\tstudyRecord: {\n\t\t\t\t\tdefaultValue: [] as {\n\t\t\t\t\t\t/** 学习日期 */\n\t\t\t\t\t\tdate: number;\n\t\t\t\t\t\tcourses: {\n\t\t\t\t\t\t\t/** 课程名 */\n\t\t\t\t\t\t\tname: string;\n\t\t\t\t\t\t\t/** 学习时间 */\n\t\t\t\t\t\t\ttime: number;\n\t\t\t\t\t\t}[];\n\t\t\t\t\t}[],\n\t\t\t\t\textra: {\n\t\t\t\t\t\tappConfigSync: false\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tstopTime: {\n\t\t\t\t\tlabel: '定时停止',\n\t\t\t\t\ttag: 'select',\n\t\t\t\t\tattrs: { title: '到时间后自动暂停脚本' },\n\t\t\t\t\tdefaultValue: '0',\n\t\t\t\t\toptions: [\n\t\t\t\t\t\t['0', '关闭'],\n\t\t\t\t\t\t['0.5', '半小时后'],\n\t\t\t\t\t\t['1', '一小时后'],\n\t\t\t\t\t\t['2', '两小时后']\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\trestudy: restudy,\n\t\t\t\treloadWhenError: {\n\t\t\t\t\tlabel: '黑屏自动刷新',\n\t\t\t\t\tattrs: { title: '视频黑屏或者检测不到视频时自动刷新页面', type: 'checkbox' },\n\t\t\t\t\tdefaultValue: true\n\t\t\t\t},\n\t\t\t\tvolume: volume,\n\t\t\t\tdefinition: definition,\n\t\t\t\tplaybackRate: {\n\t\t\t\t\tlabel: '视频倍速',\n\t\t\t\t\ttag: 'select',\n\t\t\t\t\tattrs: { title: '目前智慧树倍速最高只能1.5x，超出有封号风险' },\n\t\t\t\t\tdefaultValue: 1,\n\t\t\t\t\toptions: [\n\t\t\t\t\t\t['1', '1 x'],\n\t\t\t\t\t\t['1.25', '1.25 x'],\n\t\t\t\t\t\t['1.5', '1.5 x']\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t},\n\t\t\tmethods() {\n\t\t\t\treturn {\n\t\t\t\t\t/**\n\t\t\t\t\t * 增加学习时间\n\t\t\t\t\t * @param courseName 课程名\n\t\t\t\t\t * @param val 增加的时间\n\t\t\t\t\t */\n\t\t\t\t\tincreaseStudyTime: (courseName: string, val: number) => {\n\t\t\t\t\t\tconst records = this.cfg.studyRecord;\n\t\t\t\t\t\t// 查找是否存在今天的记录\n\t\t\t\t\t\tconst record = records.find(\n\t\t\t\t\t\t\t(r) => new Date(r.date).toLocaleDateString() === new Date().toLocaleDateString()\n\t\t\t\t\t\t);\n\t\t\t\t\t\tlet courses: {\n\t\t\t\t\t\t\tname: string;\n\t\t\t\t\t\t\ttime: number;\n\t\t\t\t\t\t}[] = [];\n\t\t\t\t\t\tif (record) {\n\t\t\t\t\t\t\tcourses = record.courses;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\trecords.push({ date: Date.now(), courses: courses });\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// 查找是否存在课程记录\n\t\t\t\t\t\tconst course = courses.find((c) => c.name === courseName);\n\t\t\t\t\t\tif (course) {\n\t\t\t\t\t\t\t// 存在则累加时间\n\t\t\t\t\t\t\tcourse.time = course.time + val;\n\t\t\t\t\t\t\t// 历史遗留问题，之前的倍速没有转换为数字，导致可能显示为字符串\n\t\t\t\t\t\t\tif (typeof course.time === 'string') {\n\t\t\t\t\t\t\t\tcourse.time = parseFloat(course.time);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// 不存在则新建\n\t\t\t\t\t\t\tcourses.push({ name: courseName, time: 0 });\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tthis.cfg.studyRecord = records;\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t},\n\t\t\tonrender({ panel }) {\n\t\t\t\tpanel.body.replaceChildren(\n\t\t\t\t\th('hr'),\n\t\t\t\t\t$ui.button('⏰检测是否需要规律学习', {}, (btn) => {\n\t\t\t\t\t\tbtn.style.marginRight = '12px';\n\t\t\t\t\t\tbtn.onclick = () => {\n\t\t\t\t\t\t\tconst href = document.querySelector('[href*=stuLearnReportNew]')?.getAttribute('href') || '';\n\t\t\t\t\t\t\tif (href) {\n\t\t\t\t\t\t\t\t$modal.alert({\n\t\t\t\t\t\t\t\t\ttitle: '规律学习检测',\n\t\t\t\t\t\t\t\t\tcontent: `自动检测功能已失效，<a href=\"${href}\"> -> 点击此处 <- </a> 前往成绩分析页面，点击 <b>“学习习惯”</b> 即可查看习惯分详情。`\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t$modal.alert({\n\t\t\t\t\t\t\t\t\ttitle: '提示',\n\t\t\t\t\t\t\t\t\tcontent: '自动检测功能已失效，请自行前往成绩分析页面，点击学习习惯即可查看习惯分详情。'\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t};\n\t\t\t\t\t}),\n\t\t\t\t\t$ui.button('📘查看学习记录', {}, (btn) => {\n\t\t\t\t\t\tbtn.onclick = () => {\n\t\t\t\t\t\t\t$modal.alert({\n\t\t\t\t\t\t\t\ttitle: '学习记录',\n\t\t\t\t\t\t\t\tcontent: $ui.notes(\n\t\t\t\t\t\t\t\t\tthis.cfg.studyRecord.map((r) => {\n\t\t\t\t\t\t\t\t\t\tconst date = new Date(r.date);\n\t\t\t\t\t\t\t\t\t\treturn [\n\t\t\t\t\t\t\t\t\t\t\t`${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date\n\t\t\t\t\t\t\t\t\t\t\t\t.getDate()\n\t\t\t\t\t\t\t\t\t\t\t\t.toString()\n\t\t\t\t\t\t\t\t\t\t\t\t.padStart(2, '0')}`,\n\t\t\t\t\t\t\t\t\t\t\t$ui.notes(r.courses.map((course) => `${course.name} - ${optimizeSecond(course.time)}`))\n\t\t\t\t\t\t\t\t\t\t];\n\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t};\n\t\t\t\t\t})\n\t\t\t\t);\n\t\t\t},\n\t\t\tonactive() {\n\t\t\t\t// 重置时间\n\t\t\t\tthis.cfg.stopTime = '0';\n\t\t\t\tif (this.cfg.playbackRate) {\n\t\t\t\t\t// 转换为数字\n\t\t\t\t\tthis.cfg.playbackRate = parseFloat(this.cfg.playbackRate.toString());\n\t\t\t\t}\n\t\t\t},\n\t\t\tasync oncomplete() {\n\t\t\t\t// 置顶当前脚本\n\t\t\t\tCommonProject.scripts.render.methods.pin(this);\n\n\t\t\t\tconst type = location.href.includes('fusioncourseh5')\n\t\t\t\t\t? 'AI课程'\n\t\t\t\t\t: location.href.includes('studyplush5')\n\t\t\t\t\t? '新共享课'\n\t\t\t\t\t: location.href.includes('studywisdomh5')\n\t\t\t\t\t? '新智慧共享课'\n\t\t\t\t\t: '共享课';\n\n\t\t\t\tconst ProcessorConstructor = location.href.includes('fusioncourseh5')\n\t\t\t\t\t? FusionCourseH5\n\t\t\t\t\t: location.href.includes('studyplush5') || location.href.includes('studywisdomh5')\n\t\t\t\t\t? StudyPlusH5\n\t\t\t\t\t: StudyVideoH5;\n\n\t\t\t\tconst processor = new ProcessorConstructor();\n\n\t\t\t\t// 10秒后还没加载出来，则结束\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\tif (processor.hasJob() === false) {\n\t\t\t\t\t\tfinishAlert();\n\t\t\t\t\t}\n\t\t\t\t}, 10 * 1000);\n\n\t\t\t\tconst waitForVideoJob = () => {\n\t\t\t\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\t\t\t\tif (processor.hasJob()) {\n\t\t\t\t\t\t\tresolve();\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\t\tresolve(waitForVideoJob());\n\t\t\t\t\t\t\t}, 200);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t};\n\n\t\t\t\tawait waitForVideoJob();\n\n\t\t\t\t// 初始化处理器\n\t\t\t\tawait processor.init();\n\n\t\t\t\t// 监听定时停止\n\t\t\t\tthis.onConfigChange('stopTime', (stopTime) => {\n\t\t\t\t\tif (stopTime === '0') {\n\t\t\t\t\t\t$message.info({ content: '定时停止已关闭' });\n\t\t\t\t\t} else {\n\t\t\t\t\t\tautoStop(stopTime);\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\t// 监听音量\n\t\t\t\tthis.onConfigChange('volume', (curr) => {\n\t\t\t\t\tstate.study.currentMedia && (state.study.currentMedia.volume = curr);\n\t\t\t\t});\n\n\t\t\t\t// 监听速度\n\t\t\t\tthis.onConfigChange('playbackRate', (curr) => {\n\t\t\t\t\tif (typeof curr === 'string') {\n\t\t\t\t\t\tthis.cfg.playbackRate = parseFloat(curr);\n\t\t\t\t\t}\n\t\t\t\t\tprocessor.switchPlaybackRate(curr);\n\t\t\t\t});\n\n\t\t\t\t// 监听清晰度\n\t\t\t\tthis.onConfigChange('definition', (curr) => {\n\t\t\t\t\tprocessor.switchLine(curr);\n\t\t\t\t});\n\n\t\t\t\t// 循环记录学习时间\n\t\t\t\tconst recordStudyTimeLoop = () => {\n\t\t\t\t\tthis.methods.increaseStudyTime(processor.getCourseName(), this.cfg.playbackRate);\n\t\t\t\t\tsetTimeout(recordStudyTimeLoop, 1000);\n\t\t\t\t};\n\n\t\t\t\t// 检测是否需要学前必读\n\t\t\t\tcloseDialogRead();\n\t\t\t\t// 循环记录学习时间\n\t\t\t\trecordStudyTimeLoop();\n\t\t\t\t// 自动暂停任务\n\t\t\t\tautoStop(this.cfg.stopTime);\n\t\t\t\t// 自动隐藏弹窗\n\t\t\t\tprocessor.hideDialog();\n\t\t\t\t// 自动过弹窗测验\n\t\t\t\tprocessor.handleTestDialog(processor.remotePage);\n\n\t\t\t\tsetInterval(async () => {\n\t\t\t\t\t// 删除遮罩层\n\t\t\t\t\t$$el('.v-modal,.mask').forEach((modal) => {\n\t\t\t\t\t\tmodal.remove();\n\t\t\t\t\t});\n\t\t\t\t\t// 定时显示进度条，防止消失\n\t\t\t\t\tfixProcessBar();\n\t\t\t\t}, 3000);\n\n\t\t\t\t$message.info({ content: '3秒后开始学习', duration: 3 });\n\n\t\t\t\tconst remotePage = processor.remotePage;\n\n\t\t\t\tconst study = async (opts: { next: boolean }) => {\n\t\t\t\t\tif (state.study.stop === false) {\n\t\t\t\t\t\tconst item = processor.getNext({ next: opts.next, restudy: this.cfg.restudy });\n\t\t\t\t\t\tif (item) {\n\t\t\t\t\t\t\tconst msg = '即将学习：' + processor.getChapterName(item);\n\t\t\t\t\t\t\t$message.info({ content: msg });\n\t\t\t\t\t\t\t$console.log(msg);\n\t\t\t\t\t\t\tawait $.sleep(3000);\n\t\t\t\t\t\t\t// 最小化脚本窗口\n\t\t\t\t\t\t\t$render.moveToEdge();\n\t\t\t\t\t\t\t// 点击侧边栏任务\n\t\t\t\t\t\t\tif (remotePage) {\n\t\t\t\t\t\t\t\tif (type === '新共享课') {\n\t\t\t\t\t\t\t\t\tawait remotePage.click('.title-box');\n\t\t\t\t\t\t\t\t\tawait $.sleep(200);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tawait remotePage.click(item);\n\t\t\t\t\t\t\t\tawait $.sleep(1000);\n\t\t\t\t\t\t\t\t// 两次点击修复黑屏问题\n\t\t\t\t\t\t\t\tawait remotePage.click(item);\n\t\t\t\t\t\t\t\tawait $.sleep(1000);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\titem.click();\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// 适配AI助教课程PPT跳过\n\t\t\t\t\t\t\tif (type === 'AI课程' && document.querySelector('.preview-warp .doc-box,.preview-warp .ppt-box')) {\n\t\t\t\t\t\t\t\t$message.info({ content: '检测到PPT资源，即将跳过...' });\n\t\t\t\t\t\t\t\tawait $.sleep(2000);\n\t\t\t\t\t\t\t\tstudy({ next: true });\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\twatch(\n\t\t\t\t\t\t\t\tprocessor,\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\treloadWhenError: this.cfg.reloadWhenError,\n\t\t\t\t\t\t\t\t\tvolume: this.cfg.volume,\n\t\t\t\t\t\t\t\t\tplaybackRate: this.cfg.playbackRate,\n\t\t\t\t\t\t\t\t\tdefinition: this.cfg.definition\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\treload() {\n\t\t\t\t\t\t\t\t\t\t// 旧共享课页面点击右侧栏就能重新加载\n\t\t\t\t\t\t\t\t\t\tif (type === '共享课') {\n\t\t\t\t\t\t\t\t\t\t\tstudy({ next: false });\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\tlocation.reload();\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tonended({ next }) {\n\t\t\t\t\t\t\t\t\t\tstudy({ next });\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tfinishAlert();\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst msg = '检测到当前视频全部播放完毕，如果还有未完成的视频请刷新重试，或者打开复习模式。';\n\t\t\t\t\t\t$message.warn({ content: msg });\n\t\t\t\t\t\tCommonProject.scripts.settings.methods.notificationBySetting(msg, {\n\t\t\t\t\t\t\tduration: 0,\n\t\t\t\t\t\t\textraTitle: '知道智慧树学习脚本'\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\t// 当页面初始化时无需切换下一个视频，直接播放当前的。\n\t\t\t\tstudy({ next: false });\n\t\t\t}\n\t\t}),\n\t\t'gxk-work': new Script({\n\t\t\tname: '✍️ 共享课-作业考试脚本',\n\t\t\tmatches: [\n\t\t\t\t['共享课作业页面', 'zhihuishu.com/stuExamWeb.html#/webExamList/dohomework'],\n\t\t\t\t['共享课考试页面', 'zhihuishu.com/stuExamWeb.html#/webExamList/doexamination'],\n\t\t\t\t['作业考试列表', 'zhihuishu.com/stuExamWeb.html#/webExamList\\\\?']\n\t\t\t],\n\t\t\tnamespace: 'zhs.gxk.work',\n\t\t\tconfigs: {\n\t\t\t\tnotes: {\n\t\t\t\t\tdefaultValue: $ui.notes([\n\t\t\t\t\t\t'自动答题前请在 “通用-全局设置” 中设置题库配置。',\n\t\t\t\t\t\t'可以搭配 “通用-在线搜题” 一起使用。',\n\t\t\t\t\t\t...gxk_read_notes\n\t\t\t\t\t]).outerHTML\n\t\t\t\t},\n\t\t\t\tworkDelay: {\n\t\t\t\t\tlabel: '作业答题开始时间延迟（秒）',\n\t\t\t\t\tdefaultValue: 3,\n\t\t\t\t\tattrs: { type: 'number', min: 1, step: 1, max: 10 }\n\t\t\t\t},\n\t\t\t\treadNotes: {\n\t\t\t\t\tdefaultValue: false\n\t\t\t\t}\n\t\t\t},\n\t\t\tmethods() {\n\t\t\t\tasync function getWorkInfo(remotePage: RemotePage) {\n\t\t\t\t\tconst isExam = location.href.includes('doexamination');\n\t\t\t\t\tlet url = '';\n\t\t\t\t\tif (isExam) {\n\t\t\t\t\t\turl = '/taurusExam/gateway/t/v1/student/doExam';\n\t\t\t\t\t} else {\n\t\t\t\t\t\turl = '/studentExam/gateway/t/v1/student/doHomework';\n\t\t\t\t\t}\n\t\t\t\t\treturn JSON.parse((await remotePage.waitForResponse(url)).body);\n\t\t\t\t}\n\n\t\t\t\treturn {\n\t\t\t\t\tgetWorkInfo: getWorkInfo,\n\t\t\t\t\twork: async () => {\n\t\t\t\t\t\t// 检查是否为软件环境\n\t\t\t\t\t\tconst remotePage = await BackgroundProject.scripts.dev.methods.getRemotePlaywrightCurrentPage();\n\t\t\t\t\t\t// 检查是否为软件环境\n\t\t\t\t\t\tif (!remotePage) {\n\t\t\t\t\t\t\treturn $playwright.showError();\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// 等待试卷加载\n\t\t\t\t\t\tconst isExam = location.href.includes('doexamination');\n\t\t\t\t\t\tconst isWork = location.href.includes('dohomework');\n\n\t\t\t\t\t\tif (isExam || isWork) {\n\t\t\t\t\t\t\tconst workInfo = await getWorkInfo(remotePage);\n\n\t\t\t\t\t\t\t// 移动到边缘\n\t\t\t\t\t\t\t$render.moveToEdge();\n\t\t\t\t\t\t\tCommonProject.scripts.render.methods.normal();\n\t\t\t\t\t\t\t// 固定脚本\n\t\t\t\t\t\t\tCommonProject.scripts.render.methods.pin(this);\n\n\t\t\t\t\t\t\t// 阅读须知\n\t\t\t\t\t\t\tif (this.cfg.readNotes === false) {\n\t\t\t\t\t\t\t\tconst notes = $ui.notes(gxk_read_notes).outerHTML;\n\t\t\t\t\t\t\t\tconst start = await new Promise<boolean>((resolve, reject) => {\n\t\t\t\t\t\t\t\t\t$modal.confirm({\n\t\t\t\t\t\t\t\t\t\ttitle: isExam ? '脚本考前须知' : '脚本作业须知',\n\t\t\t\t\t\t\t\t\t\tcontent: notes,\n\t\t\t\t\t\t\t\t\t\tconfirmButtonText: '我已知晓，开始自动答题',\n\t\t\t\t\t\t\t\t\t\tcancelButtonText: '取消答题',\n\t\t\t\t\t\t\t\t\t\tmaskCloseable: false,\n\t\t\t\t\t\t\t\t\t\tonConfirm: () => {\n\t\t\t\t\t\t\t\t\t\t\tthis.cfg.readNotes = true;\n\t\t\t\t\t\t\t\t\t\t\tresolve(true);\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\tonCancel() {\n\t\t\t\t\t\t\t\t\t\t\tresolve(false);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tif (!start) {\n\t\t\t\t\t\t\t\t\t$message.info({ content: '已取消答题，如需答题请刷新页面重新开始。' });\n\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\t\t$message.info({ content: `开始${isExam ? '考试' : '作业'}` });\n\t\t\t\t\t\t\t\tcommonWork(this, {\n\t\t\t\t\t\t\t\t\tworkerProvider: (opts) => gxkWorkAndExam(workInfo, opts),\n\t\t\t\t\t\t\t\t\tstart_delay_seconds: this.cfg.workDelay ?? 3\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}, 1000);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t$message.info({ content: '📢 请手动进入作业/考试，如果未开始答题，请尝试刷新页面。', duration: 0 });\n\n\t\t\t\t\t\t\tCommonProject.scripts.render.methods.pin(this);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t},\n\t\t\tasync onactive() {\n\t\t\t\tthis.methods.work();\n\t\t\t\t/**\n\t\t\t\t * 当页面从作业考试列表跳转到作业考试页面时，触发的是onhistorychange事件，而不是oncomplete事件。\n\t\t\t\t */\n\t\t\t\tthis.on('historychange', () => {\n\t\t\t\t\tthis.methods.work();\n\t\t\t\t});\n\t\t\t}\n\t\t}),\n\t\t'smart-study': new Script({\n\t\t\tname: '🖥️ 新形态课程-学习脚本',\n\t\t\tmatches: [\n\t\t\t\t['新形态课程学习页面', 'smartcoursestudent.zhihuishu.com/learnPage'],\n\t\t\t\t['新形态课程新域名学习页面', 'ai-smart-course-student-pro.zhihuishu.com/learnPage'],\n\t\t\t\t['新形态课程首页', 'smartcoursestudent.zhihuishu.com/singleCourse'],\n\t\t\t\t['新形态课程新域名课程首页', 'ai-smart-course-student-pro.zhihuishu.com/singleCourse']\n\t\t\t],\n\t\t\tnamespace: 'zhs.smart.study',\n\t\t\tconfigs: {\n\t\t\t\tnotes: {\n\t\t\t\t\tdefaultValue: $ui.notes([\n\t\t\t\t\t\t'掌握度和作业请视频看完后自行手动进入',\n\t\t\t\t\t\t'不要最小化浏览器/关闭电脑屏幕，可能导致脚本暂停。',\n\t\t\t\t\t\t'任意选择一个章节，脚本会自动往下学“必学”课程。',\n\t\t\t\t\t\t'运行中请将浏览器缩放调整至适合的大小，避免元素遮挡，无法点击',\n\t\t\t\t\t\t'例如：调整缩放到 50%，然后刷新页面即可'\n\t\t\t\t\t]).outerHTML\n\t\t\t\t},\n\t\t\t\tswitchMode: {\n\t\t\t\t\tlabel: '跳转模式',\n\t\t\t\t\ttag: 'select',\n\t\t\t\t\tdefaultValue: 'job' as 'job' | 'all',\n\t\t\t\t\toptions: [\n\t\t\t\t\t\t['job', '只跳转必学章节', '章节后面有必学，并且必学数量未完成的章节，如果全部完成将停止学习'],\n\t\t\t\t\t\t['all', '顺序跳转']\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\trestudy: restudy,\n\t\t\t\tvolume: volume,\n\t\t\t\tdefinition: definition,\n\t\t\t\tplaybackRate: {\n\t\t\t\t\tlabel: '视频倍速',\n\t\t\t\t\ttag: 'select',\n\t\t\t\t\tdefaultValue: 1,\n\t\t\t\t\tattrs: { title: '目前智慧树倍速最高只能1.5x，超出有封号风险' },\n\t\t\t\t\toptions: [\n\t\t\t\t\t\t['1', '1 x'],\n\t\t\t\t\t\t['1.25', '1.25 x'],\n\t\t\t\t\t\t['1.5', '1.5 x']\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t},\n\t\t\toncomplete() {\n\t\t\t\tthis.methods.start();\n\t\t\t},\n\t\t\tonhistorychange(type, ...args) {\n\t\t\t\tif (type === 'push') {\n\t\t\t\t\tthis.methods.start();\n\t\t\t\t}\n\t\t\t},\n\t\t\tmethods() {\n\t\t\t\treturn {\n\t\t\t\t\tstart: async () => {\n\t\t\t\t\t\tif (location.href.includes('singleCourse')) {\n\t\t\t\t\t\t\t$message.info({ content: '请点击任意章节开始进行自动学习', duration: 60 });\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// 置顶当前脚本\n\t\t\t\t\t\tCommonProject.scripts.render.methods.pin(this);\n\t\t\t\t\t\tconst processor = new StudyVideoH5();\n\n\t\t\t\t\t\tconst getInfos = () => Array.from(document.querySelectorAll<HTMLElement>('.section-item-collapse-info'));\n\t\t\t\t\t\tconst getChapterName = () =>\n\t\t\t\t\t\t\t(document.querySelector('.point-title-text')?.textContent || '未知章节') +\n\t\t\t\t\t\t\t'-' +\n\t\t\t\t\t\t\t(document.querySelector('[class*=\"card-container\"].active .video-title')?.textContent || '未知小节');\n\n\t\t\t\t\t\t// 需点击的任务点，其他是是外部链接或者未知任务点\n\t\t\t\t\t\tconst include_jobs = ['video', 'book', /** 一般是PPT */ 'other', /** 一般是文档 */ 'text'];\n\n\t\t\t\t\t\tconst getNextJob = () => {\n\t\t\t\t\t\t\tconst cards = Array.from(document.querySelectorAll('[class*=\"card-container\"]'));\n\n\t\t\t\t\t\t\t// 如果没有正在选中的章节，证明第一个就是外链模式，此时默认点击第一个\n\t\t\t\t\t\t\tif (cards.some((card) => card.classList.contains('active')) === false) {\n\t\t\t\t\t\t\t\treturn cards[0];\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tlet target_el;\n\t\t\t\t\t\t\tconst start_index = cards.findIndex((c) => c.classList.contains('active')) || 0;\n\t\t\t\t\t\t\tfor (let index = start_index + 1; index < cards.length; index++) {\n\t\t\t\t\t\t\t\tconst card = cards[index];\n\n\t\t\t\t\t\t\t\tif (this.cfg.restudy) {\n\t\t\t\t\t\t\t\t\ttarget_el = card;\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tif (card.querySelector('.finished-icon')?.textContent?.includes('已完成')) {\n\t\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\ttarget_el = card;\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn target_el;\n\t\t\t\t\t\t};\n\t\t\t\t\t\tconst getNext = () => {\n\t\t\t\t\t\t\tconst infos = getInfos();\n\n\t\t\t\t\t\t\tconst works: { info: HTMLElement; progress: number; total: number }[] = [];\n\n\t\t\t\t\t\t\tfor (let index = 0; index < infos.length; index++) {\n\t\t\t\t\t\t\t\tconst info = infos[index];\n\t\t\t\t\t\t\t\tconst text = info.querySelector('.collapse-info-progress .progress-text')?.textContent || '';\n\n\t\t\t\t\t\t\t\tif (this.cfg.switchMode === 'all') {\n\t\t\t\t\t\t\t\t\tworks.push({\n\t\t\t\t\t\t\t\t\t\tinfo,\n\t\t\t\t\t\t\t\t\t\tprogress: 0,\n\t\t\t\t\t\t\t\t\t\ttotal: 1\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tconst [progress, total] = text\n\t\t\t\t\t\t\t\t\t\t.replace('必学', '')\n\t\t\t\t\t\t\t\t\t\t.trim()\n\t\t\t\t\t\t\t\t\t\t.split('/')\n\t\t\t\t\t\t\t\t\t\t.map((s) => parseInt(s));\n\t\t\t\t\t\t\t\t\tif (progress < total) {\n\t\t\t\t\t\t\t\t\t\tworks.push({ progress, total, info });\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tlet start = false;\n\t\t\t\t\t\t\tfor (let index = 0; index < works.length; index++) {\n\t\t\t\t\t\t\t\tconst work = works[index];\n\t\t\t\t\t\t\t\tif (start) {\n\t\t\t\t\t\t\t\t\tif (work.progress < work.total) {\n\t\t\t\t\t\t\t\t\t\treturn works[index].info;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (work.info.classList.contains('active')) {\n\t\t\t\t\t\t\t\t\tif (this.cfg.switchMode === 'all') {\n\t\t\t\t\t\t\t\t\t\treturn works[index + 1].info;\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tstart = true;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn works[0]?.info;\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\t// 监听音量\n\t\t\t\t\t\tthis.onConfigChange('volume', (curr) => {\n\t\t\t\t\t\t\tstate.study.currentMedia && (state.study.currentMedia.volume = curr);\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\t// 监听速度\n\t\t\t\t\t\tthis.onConfigChange('playbackRate', (curr) => {\n\t\t\t\t\t\t\tif (typeof curr === 'string') {\n\t\t\t\t\t\t\t\tthis.cfg.playbackRate = parseFloat(curr);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tprocessor.switchPlaybackRate(curr);\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\t// 监听清晰度\n\t\t\t\t\t\tthis.onConfigChange('definition', (curr) => {\n\t\t\t\t\t\t\tprocessor.switchLine(curr);\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tconst doWork = async () => {\n\t\t\t\t\t\t\tawait $.sleep(5 * 1000);\n\n\t\t\t\t\t\t\tconst next = async () => {\n\t\t\t\t\t\t\t\tconst nextJob = getNextJob();\n\n\t\t\t\t\t\t\t\tif (nextJob) {\n\t\t\t\t\t\t\t\t\tconst nextJobTitle = nextJob.querySelector('.common-text') as HTMLElement;\n\n\t\t\t\t\t\t\t\t\tif (include_jobs.some((job) => nextJob.querySelector('.icon-box')?.classList.contains(job))) {\n\t\t\t\t\t\t\t\t\t\tnextJobTitle.click();\n\t\t\t\t\t\t\t\t\t\tawait doWork();\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t// 链接任务\n\t\t\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\t\t\t/**\n\t\t\t\t\t\t\t\t\t\t * 链接任务点不会自动取消 active 样式，导致无法获取下一个任务点\n\t\t\t\t\t\t\t\t\t\t * 这里手动移除 active 样式，避免影响获取下一个任务点\n\t\t\t\t\t\t\t\t\t\t */\n\t\t\t\t\t\t\t\t\t\tdocument\n\t\t\t\t\t\t\t\t\t\t\t.querySelectorAll('[class*=\"card-container\"].active')\n\t\t\t\t\t\t\t\t\t\t\t.forEach((el) => el.classList.remove('active'));\n\n\t\t\t\t\t\t\t\t\t\t// 链接任务点不会自动附加 active 样式，这里手动添加\n\t\t\t\t\t\t\t\t\t\tnextJob?.classList.add('active');\n\n\t\t\t\t\t\t\t\t\t\tawait $.sleep(1000);\n\n\t\t\t\t\t\t\t\t\t\tconst _open = $gm.unsafeWindow.open;\n\t\t\t\t\t\t\t\t\t\t$gm.unsafeWindow.open = () => null;\n\t\t\t\t\t\t\t\t\t\tnextJobTitle.click();\n\n\t\t\t\t\t\t\t\t\t\tconst msg = '链接任务完成，即将自动下一节！';\n\t\t\t\t\t\t\t\t\t\t$message.info(msg);\n\t\t\t\t\t\t\t\t\t\tsetTimeout(async () => {\n\t\t\t\t\t\t\t\t\t\t\t$gm.unsafeWindow.open = _open;\n\t\t\t\t\t\t\t\t\t\t\t// 直接下一个\n\t\t\t\t\t\t\t\t\t\t\tawait next();\n\t\t\t\t\t\t\t\t\t\t}, 3000);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tconst nextEl = getNext();\n\t\t\t\t\t\t\t\tif (nextEl) {\n\t\t\t\t\t\t\t\t\tnextEl.click();\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tfinishAlert();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tconst wrapper = document.querySelector<HTMLElement>('.video-player-wrapper');\n\t\t\t\t\t\t\t\tif (!wrapper || wrapper.style.display === 'none') {\n\t\t\t\t\t\t\t\t\tthrow new Error('视频加载失败');\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tawait waitForMedia({\n\t\t\t\t\t\t\t\t\ttimeout: 5 * 1000,\n\t\t\t\t\t\t\t\t\tfilter: (m) => m.src.length !== 0\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\tconst msg = '未找到学习视频，即将自动下一节！';\n\t\t\t\t\t\t\t\t$message.error(msg);\n\t\t\t\t\t\t\t\t$console.error(msg);\n\t\t\t\t\t\t\t\tawait $.sleep(3000);\n\t\t\t\t\t\t\t\tawait next();\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tconst set = async () => {\n\t\t\t\t\t\t\t\t// 上面操作会导致元素刷新，这里重新获取视频\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t// 设置清晰度\n\t\t\t\t\t\t\t\t\tawait processor.switchLine(this.cfg.definition || 'line1bq');\n\t\t\t\t\t\t\t\t\tawait $.sleep(1000);\n\t\t\t\t\t\t\t\t\t// 设置播放速度\n\t\t\t\t\t\t\t\t\tawait processor.switchPlaybackRate(this.cfg.playbackRate);\n\t\t\t\t\t\t\t\t\tawait $.sleep(1000);\n\t\t\t\t\t\t\t\t\tconst media = await waitForMedia({ timeout: 5 * 1000, filter: (m) => m.src.length !== 0 });\n\t\t\t\t\t\t\t\t\tawait $.sleep(1000);\n\t\t\t\t\t\t\t\t\tstate.study.currentMedia = media;\n\t\t\t\t\t\t\t\t\tif (media) {\n\t\t\t\t\t\t\t\t\t\t// 如果已经播放完了，则重置视频进度\n\t\t\t\t\t\t\t\t\t\tmedia.currentTime = 1;\n\t\t\t\t\t\t\t\t\t\t// 音量\n\t\t\t\t\t\t\t\t\t\tmedia.volume = this.cfg.volume;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\treturn state.study.currentMedia;\n\t\t\t\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\t\t\t\t$console.log('视频加载失败，请尝试刷新页面！：' + e);\n\t\t\t\t\t\t\t\t\t$message.error({ content: '视频加载失败，请尝试刷新页面！：' + e, duration: 0 });\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\tconst video = await set();\n\t\t\t\t\t\t\tif (!video) {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tplayMedia(() => video?.play()).then(() => {\n\t\t\t\t\t\t\t\tconst cn = getChapterName();\n\t\t\t\t\t\t\t\t$message.info({ content: '正在学习：' + cn });\n\t\t\t\t\t\t\t\t$console.log('正在学习：' + cn);\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\tvideo.onpause = async () => {\n\t\t\t\t\t\t\t\tif (!video?.ended && state.study.stop === false) {\n\t\t\t\t\t\t\t\t\tawait $.sleep(1000);\n\t\t\t\t\t\t\t\t\tvideo?.play();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\tvideo.onended = async () => {\n\t\t\t\t\t\t\t\t$message.info({ content: '即将自动跳转下一节' });\n\t\t\t\t\t\t\t\tawait $.sleep(3000);\n\n\t\t\t\t\t\t\t\tawait next();\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tdoWork();\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t}\n\t\t}),\n\t\t'smart-work': new Script({\n\t\t\tname: '✍️ 新形态课程-作业/考试/掌握度脚本',\n\t\t\tmatches: [\n\t\t\t\t['新形态课程作业页面', 'smartcourseexam.zhihuishu.com/ReviewExam'],\n\t\t\t\t['新形态课程-掌握提升页面', 'studentexamcomh5.zhihuishu.com/studentReviewTestOrExam'],\n\t\t\t\t['新形态课程-AI助教掌握度', 'fusioncourseh5.zhihuishu.com/exam'],\n\t\t\t\t['新形态课程-新AI助教掌握度', 'studywisdomh5.zhihuishu.com/exam']\n\t\t\t],\n\t\t\tnamespace: 'zhs.smart.work',\n\t\t\tconfigs: {\n\t\t\t\tnotes: {\n\t\t\t\t\tdefaultValue: $ui.notes([\n\t\t\t\t\t\t'自动答题前请在 “通用-全局设置” 中设置题库配置。',\n\t\t\t\t\t\t'可以搭配 “通用-在线搜题” 一起使用。',\n\t\t\t\t\t\t'⚠️ 如果没开始答题，请尝试刷新页面。',\n\t\t\t\t\t\t'⚠️ 禁止一次性打开多个作业/考试页面。',\n\t\t\t\t\t\t...(remote_not_required_pages.some((domain) => location.href.includes(domain))\n\t\t\t\t\t\t\t? []\n\t\t\t\t\t\t\t: ['⚠️ 答题中请勿进行任何操作，如需暂停答题', '请等待全部题目搜索完成并执行自动保存功能后才能操作。'])\n\t\t\t\t\t]).outerHTML\n\t\t\t\t},\n\t\t\t\tworkDelay: {\n\t\t\t\t\tlabel: '作业答题开始时间延迟（秒）',\n\t\t\t\t\tdefaultValue: 3,\n\t\t\t\t\tattrs: { type: 'number', min: 1, step: 1, max: 10 }\n\t\t\t\t}\n\t\t\t},\n\t\t\tmethods() {\n\t\t\t\treturn {\n\t\t\t\t\tstart: async () => {\n\t\t\t\t\t\t// 检查是否为软件环境\n\t\t\t\t\t\tCommonProject.scripts.render.methods.pin(this);\n\n\t\t\t\t\t\tlet remotePage: RemotePage | undefined;\n\t\t\t\t\t\t// 掌握度\n\t\t\t\t\t\tconst remote_not_required = remote_not_required_pages.some((domain) => location.href.includes(domain));\n\n\t\t\t\t\t\tremote_not_required ? await waitForElement('.exam-item') : await waitForElement('.questionContent');\n\n\t\t\t\t\t\tif (remote_not_required) {\n\t\t\t\t\t\t\t// 这两个页面不需要软件辅助\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tremotePage = await BackgroundProject.scripts.dev.methods.getRemotePlaywrightCurrentPage();\n\t\t\t\t\t\t\t// 检查是否为软件环境\n\t\t\t\t\t\t\tif (!remotePage) {\n\t\t\t\t\t\t\t\treturn $playwright.showError();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// 移动到左上角\n\t\t\t\t\t\t\t$render.moveToEdge();\n\t\t\t\t\t\t\t$message.warn({ content: '答题完毕之前请勿操作页面！', duration: 0 });\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tcommonWork(this, {\n\t\t\t\t\t\t\tworkerProvider: (opts) => {\n\t\t\t\t\t\t\t\tif (remote_not_required) {\n\t\t\t\t\t\t\t\t\treturn fusioncourseWork(remotePage, opts);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\treturn smartWork(remotePage, opts);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tstart_delay_seconds: this.cfg.workDelay ?? 3\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t},\n\t\t\toncomplete() {\n\t\t\t\tthis.methods.start();\n\t\t\t},\n\t\t\tonhistorychange(type, ...args) {\n\t\t\t\tif (type === 'push') {\n\t\t\t\t\tthis.methods.start();\n\t\t\t\t}\n\t\t\t}\n\t\t}),\n\t\t'smart-exam': new Script({\n\t\t\tname: '✍️ 新形态课程-考试脚本',\n\t\t\tmatches: [['新形态课程-考试界面', 'examloop.zhihuishu.com/exam']],\n\t\t\tconfigs: {\n\t\t\t\tnotes: {\n\t\t\t\t\tdefaultValue: $ui.notes([\n\t\t\t\t\t\t'自动答题前请在 “通用-全局设置” 中设置题库配置。',\n\t\t\t\t\t\t'可以搭配 “通用-在线搜题” 一起使用。',\n\t\t\t\t\t\t'⚠️ 如果没开始答题，请尝试刷新页面。',\n\t\t\t\t\t\t'⚠️ 禁止一次性打开多个作业/考试页面。'\n\t\t\t\t\t]).outerHTML\n\t\t\t\t}\n\t\t\t},\n\t\t\tmethods() {\n\t\t\t\treturn {\n\t\t\t\t\tstart: async () => {\n\t\t\t\t\t\t// 检查是否为软件环境\n\t\t\t\t\t\tCommonProject.scripts.render.methods.pin(this);\n\t\t\t\t\t\tawait waitForElement('.question-area-content');\n\t\t\t\t\t\tcommonWork(this, {\n\t\t\t\t\t\t\tworkerProvider: (opts) => {\n\t\t\t\t\t\t\t\treturn smartExam(undefined, opts);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t},\n\t\t\toncomplete() {\n\t\t\t\tthis.methods.start();\n\t\t\t}\n\t\t}),\n\t\t'xnk-study': new Script({\n\t\t\tname: '🖥️ 校内课（翻转课）-学习脚本',\n\t\t\tmatches: [['校内课学习页面', 'zhihuishu.com/aidedteaching/sourceLearning']],\n\t\t\tnamespace: 'zhs.xnk.study',\n\t\t\tconfigs: {\n\t\t\t\tnotes: {\n\t\t\t\t\tdefaultValue: $ui.notes(['章节测试请大家观看完视频后手动打开。', '此课程不能使用倍速。']).outerHTML\n\t\t\t\t},\n\t\t\t\trestudy: restudy,\n\t\t\t\tvolume: volume\n\t\t\t},\n\t\t\toncomplete() {\n\t\t\t\t// 置顶当前脚本\n\t\t\t\tCommonProject.scripts.render.methods.pin(this);\n\n\t\t\t\tconst finish = () => {\n\t\t\t\t\t$modal.alert({\n\t\t\t\t\t\tcontent: '检测到当前视频全部播放完毕，如果还有未完成的视频请刷新重试，或者打开复习模式。'\n\t\t\t\t\t});\n\t\t\t\t\tCommonProject.scripts.settings.methods.notificationBySetting(\n\t\t\t\t\t\t'检测到当前视频全部播放完毕，如果还有未完成的视频请刷新重试，或者打开复习模式。',\n\t\t\t\t\t\t{ duration: 0, extraTitle: '知道智慧树学习脚本' }\n\t\t\t\t\t);\n\t\t\t\t};\n\n\t\t\t\t// 监听音量\n\t\t\t\tthis.onConfigChange('volume', (curr) => {\n\t\t\t\t\tstate.study.currentMedia && (state.study.currentMedia.volume = curr);\n\t\t\t\t});\n\n\t\t\t\tconst nextElement = () => {\n\t\t\t\t\tconst list = document.querySelectorAll<HTMLElement>('.file-item');\n\n\t\t\t\t\tlet passActive = false;\n\t\t\t\t\tfor (let index = 0; index < list.length; index++) {\n\t\t\t\t\t\tconst item = list[index];\n\t\t\t\t\t\tconst finish = !!item.querySelector('.icon-finish');\n\t\t\t\t\t\t// 判断是否需要学习\n\t\t\t\t\t\tconst needsStudy = !finish || (finish && this.cfg.restudy);\n\n\t\t\t\t\t\tif (item.classList.contains('active')) {\n\t\t\t\t\t\t\tif (needsStudy) {\n\t\t\t\t\t\t\t\treturn list[index + 1];\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tpassActive = true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (passActive && needsStudy) {\n\t\t\t\t\t\t\treturn item;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\tconst interval = setInterval(async () => {\n\t\t\t\t\t/** 查找任务 */\n\t\t\t\t\tconst next = nextElement();\n\n\t\t\t\t\tif (next) {\n\t\t\t\t\t\tclearInterval(interval);\n\n\t\t\t\t\t\tif (document.querySelector('#mediaPlayer')) {\n\t\t\t\t\t\t\tdocument.querySelector('.file-item.active')?.scrollIntoView();\n\t\t\t\t\t\t\tconst name = next.querySelector('#sourceTit')?.textContent || '未知视频';\n\t\t\t\t\t\t\t$message.info('正在学习：' + name);\n\t\t\t\t\t\t\twatchXnk({ volume: this.cfg.volume }, () => {\n\t\t\t\t\t\t\t\t$message.info('视频完成播放，正在自动跳转下一节！');\n\t\t\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\t\t\t/** 下一章 */\n\t\t\t\t\t\t\t\t\tconst next = nextElement();\n\t\t\t\t\t\t\t\t\tif (next) next.click();\n\t\t\t\t\t\t\t\t}, 3000);\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\t\tconst msg = '未找到学习视频，即将自动下一节！';\n\t\t\t\t\t\t\t\t$message.warn(msg);\n\t\t\t\t\t\t\t\t$console.warn(msg);\n\t\t\t\t\t\t\t\t/** 下一章 */\n\t\t\t\t\t\t\t\tconst next = nextElement();\n\t\t\t\t\t\t\t\tif (next) next.click();\n\t\t\t\t\t\t\t}, 3000);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}, 1000);\n\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\tif (!nextElement()) {\n\t\t\t\t\t\tfinish();\n\t\t\t\t\t\tclearInterval(interval);\n\t\t\t\t\t}\n\t\t\t\t}, 10 * 1000);\n\t\t\t}\n\t\t}),\n\t\t'xnk-work': new Script({\n\t\t\tname: '✍️ 校内课-作业考试脚本',\n\t\t\tmatches: [\n\t\t\t\t['校内课作业页面', 'zhihuishu.com/atHomeworkExam/stu/homeworkQ/exerciseList'],\n\t\t\t\t['校内课考试页面', 'zhihuishu.com/atHomeworkExam/stu/examQ/examexercise']\n\t\t\t],\n\t\t\tnamespace: 'zhs.xnk.work',\n\t\t\tconfigs: { notes: workNotes },\n\t\t\tasync oncomplete() {\n\t\t\t\tcommonWork(this, {\n\t\t\t\t\tworkerProvider: xnkWork\n\t\t\t\t});\n\t\t\t}\n\t\t}),\n\t\t'wisdom-study': new Script({\n\t\t\tname: '🖥️ 新智慧学习-学习脚本',\n\t\t\tmatches: [['2025-12月新智慧学习页面', 'wisdom-mooc.zhihuishu.com/study/index']],\n\t\t\tnamespace: 'zhs.wisdom.study',\n\t\t\tconfigs: {\n\t\t\t\tnotes: {\n\t\t\t\t\tdefaultValue: $ui.notes([\n\t\t\t\t\t\t'掌握度和作业请视频看完后自行手动进入',\n\t\t\t\t\t\t'不要最小化浏览器/关闭电脑屏幕，可能导致脚本暂停。',\n\t\t\t\t\t\t'请使用时关闭卡巴斯基软件，否则会被检测出异常脚本。',\n\t\t\t\t\t\t'运行中请将浏览器缩放调整至适合的大小，避免元素遮挡，无法点击',\n\t\t\t\t\t\t'例如：调整缩放到 50%，然后刷新页面即可'\n\t\t\t\t\t]).outerHTML\n\t\t\t\t},\n\t\t\t\trestudy: restudy,\n\t\t\t\tskipStudyTimeWarnDialog: {\n\t\t\t\t\tlabel: '忽略习惯分弹窗',\n\t\t\t\t\tattrs: {\n\t\t\t\t\t\ttitle: '如果课程有习惯分需要自行控制学习时长，如果忽略习惯分提示，则可能没有习惯分。',\n\t\t\t\t\t\ttype: 'checkbox'\n\t\t\t\t\t},\n\t\t\t\t\tdefaultValue: false\n\t\t\t\t},\n\t\t\t\treloadWhenError: {\n\t\t\t\t\tlabel: '视频黑屏时自动刷新',\n\t\t\t\t\tattrs: { type: 'checkbox', title: '当视频出现加载失败，或者黑屏等异常时，自动刷新页面3次尝试修复' },\n\t\t\t\t\tdefaultValue: true\n\t\t\t\t},\n\t\t\t\tvolume: volume,\n\t\t\t\tdefinition: definition,\n\t\t\t\tplaybackRate: {\n\t\t\t\t\tlabel: '视频倍速',\n\t\t\t\t\ttag: 'select',\n\t\t\t\t\tdefaultValue: 1,\n\t\t\t\t\tattrs: { title: '目前智慧树倍速最高只能1.5x，超出有封号风险' },\n\t\t\t\t\toptions: [\n\t\t\t\t\t\t['1', '1 x'],\n\t\t\t\t\t\t['1.25', '1.25 x'],\n\t\t\t\t\t\t['1.5', '1.5 x']\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t},\n\t\t\tasync oncomplete() {\n\t\t\t\t// 置顶当前脚本\n\t\t\t\tCommonProject.scripts.render.methods.pin(this);\n\t\t\t\tconst processor = new WishdomH5();\n\n\t\t\t\t// // 点击显示进度条，否则无法进行倍速，清晰度等操作\n\t\t\t\t// const showControlBar = async () => {\n\t\t\t\t// \tawait processor.remotePage?.hover('.videoArea');\n\t\t\t\t// \tawait processor.remotePage?.click('.videoArea', { button: 'middle' });\n\t\t\t\t// \tawait processor.remotePage?.hover('.speedBox');\n\t\t\t\t// \t// 需要点击一下屏幕中心，否则视频控制菜单无法正常弹出\n\t\t\t\t// \tawait processor.remotePage?.click('.speedBox', { button: 'middle' });\n\t\t\t\t// };\n\n\t\t\t\t// 监听音量\n\t\t\t\tthis.onConfigChange('volume', (curr) => {\n\t\t\t\t\tstate.study.currentMedia && (state.study.currentMedia.volume = curr);\n\t\t\t\t});\n\n\t\t\t\t// 监听速度\n\t\t\t\tthis.onConfigChange('playbackRate', async (curr) => {\n\t\t\t\t\tif (typeof curr === 'string') {\n\t\t\t\t\t\tthis.cfg.playbackRate = parseFloat(curr);\n\t\t\t\t\t}\n\t\t\t\t\tfixProcessBar();\n\t\t\t\t\tprocessor.switchPlaybackRate(curr);\n\t\t\t\t});\n\n\t\t\t\t// 监听清晰度\n\t\t\t\tthis.onConfigChange('definition', async (curr) => {\n\t\t\t\t\tfixProcessBar();\n\t\t\t\t\tprocessor.switchLine(curr);\n\t\t\t\t});\n\n\t\t\t\tconst next = async () => {\n\t\t\t\t\tconst hidden = $el('.chapter-wrapper.hidden');\n\t\t\t\t\tif (hidden) {\n\t\t\t\t\t\tif (processor.remotePage) {\n\t\t\t\t\t\t\tawait processor.remotePage.click('.collapse-box');\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tawait hidden.click();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// 全部章节下拉展开\n\t\t\t\t\tdocument.querySelectorAll<HTMLElement>('.el-collapse-item__wrap').forEach((e) => (e.style.display = ''));\n\t\t\t\t\tawait $.sleep(200);\n\n\t\t\t\t\tconst nextJob = processor.getNext({ next: true, restudy: this.cfg.restudy });\n\t\t\t\t\tif (nextJob) {\n\t\t\t\t\t\tif (!this.cfg.skipStudyTimeWarnDialog && hasStudyTimeWarnDialog()) {\n\t\t\t\t\t\t\t$message.warn({\n\t\t\t\t\t\t\t\tcontent:\n\t\t\t\t\t\t\t\t\t'检测到习惯分弹窗，当天学习时间已满，如需继续学习，请手动关闭后，刷新页面重新运行脚本！（如想强制学习请前往脚本设置忽略弹窗）',\n\t\t\t\t\t\t\t\tduration: 0\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tawait closeStudyTimeWarnDialog();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tawait waitForMasteryLevelDialogClose();\n\t\t\t\t\t\t// 展开章节\n\t\t\t\t\t\tawait processor.remotePage?.click(nextJob);\n\t\t\t\t\t\tnextJob.scrollIntoView({ behavior: 'smooth', block: 'center' });\n\t\t\t\t\t\tdoWork();\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfinishAlert();\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\t// 实时检测关闭掌握度页面\n\t\t\t\tconst closeMasteryLevelDialog = () => {\n\t\t\t\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\t\t\t\tconst check = () => {\n\t\t\t\t\t\t\tif (document.querySelector('.masterylevel-body')) {\n\t\t\t\t\t\t\t\tprocessor.remotePage?.click('.header-box .close-box');\n\t\t\t\t\t\t\t\t$message.info('掌握度页面已关闭，即将继续学习！');\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tsetTimeout(check, 3000);\n\t\t\t\t\t\t};\n\t\t\t\t\t\tcheck();\n\t\t\t\t\t});\n\t\t\t\t};\n\n\t\t\t\tconst hasStudyTimeWarnDialog = () => {\n\t\t\t\t\treturn !!Array.from(document.querySelectorAll('.el-overlay .content')).find((el) =>\n\t\t\t\t\t\tel.textContent?.includes('保持良好的学习习惯')\n\t\t\t\t\t);\n\t\t\t\t};\n\n\t\t\t\tconst closeStudyTimeWarnDialog = () => {\n\t\t\t\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\t\t\t\tconst interval = setInterval(() => {\n\t\t\t\t\t\t\tconst dialog = Array.from(document.querySelectorAll('.el-overlay')).find((el) =>\n\t\t\t\t\t\t\t\tel.textContent?.includes('保持良好的学习习惯')\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tif (dialog) {\n\t\t\t\t\t\t\t\tconst btn = dialog.querySelector('.el-dialog__header img');\n\t\t\t\t\t\t\t\tif (btn) processor.remotePage?.click(btn);\n\t\t\t\t\t\t\t\tclearInterval(interval);\n\t\t\t\t\t\t\t\tresolve();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}, 1000);\n\n\t\t\t\t\t\t// 3秒后自动结束\n\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\tclearInterval(interval);\n\t\t\t\t\t\t\tresolve();\n\t\t\t\t\t\t}, 3 * 1000);\n\t\t\t\t\t});\n\t\t\t\t};\n\n\t\t\t\tconst waitForMasteryLevelDialogClose = async () => {\n\t\t\t\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\t\t\t\tconst check = () => {\n\t\t\t\t\t\t\tif (!document.querySelector('.masterylevel-body')) {\n\t\t\t\t\t\t\t\tresolve();\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tsetTimeout(check, 200);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t};\n\t\t\t\t\t\tcheck();\n\t\t\t\t\t});\n\t\t\t\t};\n\n\t\t\t\tconst waitForLoad = () => {\n\t\t\t\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\t\t\t\tconst check = () => {\n\t\t\t\t\t\t\tif (document.querySelector('.video-play')) {\n\t\t\t\t\t\t\t\tresolve();\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tsetTimeout(check, 3000);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t};\n\t\t\t\t\t\tcheck();\n\t\t\t\t\t});\n\t\t\t\t};\n\n\t\t\t\tawait waitForLoad();\n\n\t\t\t\tawait $.sleep(3000);\n\t\t\t\tcloseMasteryLevelDialog();\n\t\t\t\t// 自动隐藏弹窗\n\t\t\t\tprocessor.hideDialog();\n\n\t\t\t\t// 初始化处理器\n\t\t\t\tawait processor.init();\n\t\t\t\t// 自动过弹窗测验\n\t\t\t\tprocessor.handleTestDialog(processor.remotePage);\n\n\t\t\t\tsetInterval(() => {\n\t\t\t\t\t// 定时显示进度条，防止消失\n\t\t\t\t\tfixProcessBar();\n\t\t\t\t}, 1000);\n\n\t\t\t\t$message.success({ content: '即将开始自动学习！' });\n\n\t\t\t\tconst reload = async (e: any) => {\n\t\t\t\t\t$console.error(e);\n\t\t\t\t\tif (this.cfg.reloadWhenError) {\n\t\t\t\t\t\tconst msg = '视频加载失败，即将刷新页面。';\n\t\t\t\t\t\tconst reload_count = await $store.getTab('reload-count');\n\t\t\t\t\t\tif (reload_count && reload_count > 3) {\n\t\t\t\t\t\t\tconst msg = '视频加载失败/黑屏导致重新加载页面次数超过3次，请尝试关闭页面重新打开，或者检查网络连接！';\n\t\t\t\t\t\t\tawait $store.setTab('reload-count', 0);\n\t\t\t\t\t\t\t$message.error({ content: msg, duration: 0 });\n\t\t\t\t\t\t\t$console.log(msg);\n\t\t\t\t\t\t\tCommonProject.scripts.settings.methods.notificationBySetting(msg, {\n\t\t\t\t\t\t\t\tduration: 0,\n\t\t\t\t\t\t\t\textraTitle: '知道智慧树学习脚本'\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tawait $store.setTab('reload-count', (reload_count ?? 0) + 1);\n\t\t\t\t\t\t$message.error(msg);\n\t\t\t\t\t\t$console.log(msg);\n\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\tlocation.reload();\n\t\t\t\t\t\t}, 3000);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst msg = '视频加载失败，即将跳过。';\n\t\t\t\t\t\t$message.error(msg);\n\t\t\t\t\t\t$console.log(msg);\n\t\t\t\t\t\tnext();\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\tconst doWork = async () => {\n\t\t\t\t\tawait waitForCaptcha();\n\n\t\t\t\t\tconst set = async () => {\n\t\t\t\t\t\t// 上面操作会导致元素刷新，这里重新获取视频\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait $.sleep(1000);\n\t\t\t\t\t\t\t// 设置清晰度\n\t\t\t\t\t\t\tawait processor.switchLine(this.cfg.definition || 'line1bq');\n\t\t\t\t\t\t\tawait $.sleep(1000);\n\t\t\t\t\t\t\t// 设置播放速度\n\t\t\t\t\t\t\tawait processor.switchPlaybackRate(this.cfg.playbackRate);\n\t\t\t\t\t\t\tawait $.sleep(1000);\n\t\t\t\t\t\t\tconst media = await waitForMedia({ timeout: 5 * 1000, filter: (m) => m.src.length !== 0 });\n\t\t\t\t\t\t\tawait $.sleep(1000);\n\t\t\t\t\t\t\tstate.study.currentMedia = media;\n\t\t\t\t\t\t\tif (media) {\n\t\t\t\t\t\t\t\t// 如果已经播放完了，则重置视频进度\n\t\t\t\t\t\t\t\tmedia.currentTime = 1;\n\t\t\t\t\t\t\t\t// 音量\n\t\t\t\t\t\t\t\tmedia.volume = this.cfg.volume;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn state.study.currentMedia;\n\t\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\t\treload(e);\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\n\t\t\t\t\t// 部分用户视频加载很慢，这里等待一下\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst media = await waitForMedia({\n\t\t\t\t\t\t\ttimeout: 10 * 1000,\n\t\t\t\t\t\t\tfilter: (m) => m.src.length !== 0\n\t\t\t\t\t\t});\n\t\t\t\t\t\tmedia.pause();\n\t\t\t\t\t\t// 固定进度条便于下方点击音量等按钮\n\t\t\t\t\t\tfixProcessBar();\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\treload(e);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tawait waitForCaptcha();\n\t\t\t\t\tconst video = await set();\n\t\t\t\t\tif (!video) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// 如果视频元素无法访问，证明已经切换了视频\n\t\t\t\t\tconst videoCheckInterval = setInterval(async () => {\n\t\t\t\t\t\tif (!video?.isConnected) {\n\t\t\t\t\t\t\tclearInterval(videoCheckInterval);\n\t\t\t\t\t\t\t$message.info({ content: '检测到视频切换中...' });\n\t\t\t\t\t\t\t/**\n\t\t\t\t\t\t\t * 元素无法访问证明用户切换视频了\n\t\t\t\t\t\t\t * 所以不往下播放视频，而是重新播放用户当前选中的视频\n\t\t\t\t\t\t\t */\n\t\t\t\t\t\t\tdoWork();\n\t\t\t\t\t\t}\n\t\t\t\t\t}, 3000);\n\n\t\t\t\t\t$message.info('开始播放');\n\t\t\t\t\tplayMedia(() => video?.play()).then(() => {\n\t\t\t\t\t\tconst current = document.querySelector<HTMLElement>(\n\t\t\t\t\t\t\t'.chapter-item.current , .chapter-content-second.current'\n\t\t\t\t\t\t);\n\t\t\t\t\t\tconst cn = current ? processor.getChapterName(current) : '未知章节';\n\t\t\t\t\t\t$message.info({ content: '正在学习：' + cn });\n\t\t\t\t\t\t$console.log('正在学习：' + cn);\n\t\t\t\t\t});\n\n\t\t\t\t\tvideo.onpause = async () => {\n\t\t\t\t\t\tif (!video?.isConnected) return;\n\t\t\t\t\t\tif (!video?.ended && state.study.stop === false) {\n\t\t\t\t\t\t\tawait $.sleep(1000);\n\t\t\t\t\t\t\tvideo?.play();\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\n\t\t\t\t\tvideo.onended = async () => {\n\t\t\t\t\t\tif (!video?.isConnected) return;\n\t\t\t\t\t\t$message.info('即将自动跳转下一节');\n\t\t\t\t\t\t$console.info('即将自动跳转下一节');\n\t\t\t\t\t\tclearInterval(videoCheckInterval);\n\t\t\t\t\t\tawait $.sleep(3000);\n\t\t\t\t\t\tawait next();\n\t\t\t\t\t};\n\t\t\t\t};\n\n\t\t\t\tdoWork();\n\t\t\t}\n\t\t}),\n\t\thike: new Script({\n\t\t\tname: '🖥️ 教学空间-AI智慧课程-学习脚本',\n\t\t\tmatches: [\n\t\t\t\t['学习首页', 'hike-teaching-center.polymas.com/stu-hike/agent-course-hike/ai-course-center'],\n\t\t\t\t['学习页面', 'tools-hike/studentStudyResource']\n\t\t\t],\n\t\t\tnamespace: 'zhs.hike.study',\n\t\t\tconfigs: {\n\t\t\t\tnotes: {\n\t\t\t\t\tdefaultValue: $ui.notes(['请手动进入视频、作业、考试页面，脚本会自动运行。']).outerHTML\n\t\t\t\t},\n\t\t\t\trestudy: restudy,\n\t\t\t\treloadWhenError: {\n\t\t\t\t\tlabel: '视频黑屏时自动刷新',\n\t\t\t\t\tattrs: { type: 'checkbox', title: '当视频出现加载失败，或者黑屏等异常时，自动刷新页面3次尝试修复' },\n\t\t\t\t\tdefaultValue: true\n\t\t\t\t},\n\t\t\t\tvolume: volume,\n\t\t\t\tdefinition: definition,\n\t\t\t\tplaybackRate: {\n\t\t\t\t\tlabel: '视频倍速',\n\t\t\t\t\ttag: 'select',\n\t\t\t\t\tdefaultValue: 1,\n\t\t\t\t\tattrs: { title: '目前智慧树倍速最高只能1.5x，超出有封号风险' },\n\t\t\t\t\toptions: [\n\t\t\t\t\t\t['1', '1 x'],\n\t\t\t\t\t\t['1.25', '1.25 x'],\n\t\t\t\t\t\t['1.5', '1.5 x']\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t},\n\t\t\tasync oncomplete(...args) {\n\t\t\t\t// 置顶当前脚本\n\t\t\t\tCommonProject.scripts.render.methods.pin(this);\n\t\t\t\tif (location.href.includes('stu-hike/agent-course-hike/ai-course-center')) {\n\t\t\t\t\t$message.info({ content: '请手动进入视频、作业、考试页面，脚本会自动运行。', duration: 60 });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst processor = new Hike();\n\t\t\t\t// 监听音量\n\t\t\t\tthis.onConfigChange('volume', (curr) => {\n\t\t\t\t\tstate.study.currentMedia && (state.study.currentMedia.volume = curr);\n\t\t\t\t});\n\n\t\t\t\t// 监听速度\n\t\t\t\tthis.onConfigChange('playbackRate', async (curr) => {\n\t\t\t\t\tif (typeof curr === 'string') {\n\t\t\t\t\t\tthis.cfg.playbackRate = parseFloat(curr);\n\t\t\t\t\t}\n\t\t\t\t\tfixProcessBar();\n\t\t\t\t\tprocessor.switchPlaybackRate(curr);\n\t\t\t\t});\n\n\t\t\t\t// 监听清晰度\n\t\t\t\tthis.onConfigChange('definition', async (curr) => {\n\t\t\t\t\tfixProcessBar();\n\t\t\t\t\tprocessor.switchLine(curr);\n\t\t\t\t});\n\n\t\t\t\tconst getChapterName = () => document.querySelector('.active-file')?.textContent?.trim() || '未知章节';\n\n\t\t\t\tconst next = async () => {\n\t\t\t\t\tconst nextJob = processor.getNext({ next: true, restudy: this.cfg.restudy });\n\t\t\t\t\tif (nextJob) {\n\t\t\t\t\t\tnextJob.click();\n\t\t\t\t\t\tawait $.sleep(3000);\n\t\t\t\t\t\tdoWork();\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfinishAlert();\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tconst waitForLoad = () => {\n\t\t\t\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\t\t\t\tconst check = () => {\n\t\t\t\t\t\t\tif (processor.hasJob()) {\n\t\t\t\t\t\t\t\tresolve();\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tsetTimeout(check, 1000);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t};\n\t\t\t\t\t\tcheck();\n\t\t\t\t\t});\n\t\t\t\t};\n\n\t\t\t\tawait waitForLoad();\n\n\t\t\t\tawait $.sleep(3000);\n\n\t\t\t\tsetInterval(() => {\n\t\t\t\t\t// 定时显示进度条，防止消失\n\t\t\t\t\tfixProcessBar();\n\t\t\t\t}, 1000);\n\n\t\t\t\t$message.success({ content: '即将开始自动学习！' });\n\n\t\t\t\tconst reload = async (e: any) => {\n\t\t\t\t\t$console.error(e);\n\t\t\t\t\tif (this.cfg.reloadWhenError) {\n\t\t\t\t\t\tconst msg = '视频加载失败，即将刷新页面。';\n\t\t\t\t\t\tconst reload_count = await $store.getTab('reload-count');\n\t\t\t\t\t\tif (reload_count && reload_count > 3) {\n\t\t\t\t\t\t\tconst msg = '视频加载失败/黑屏导致重新加载页面次数超过3次，请尝试关闭页面重新打开，或者检查网络连接！';\n\t\t\t\t\t\t\tawait $store.setTab('reload-count', 0);\n\t\t\t\t\t\t\t$message.error({ content: msg, duration: 0 });\n\t\t\t\t\t\t\t$console.log(msg);\n\t\t\t\t\t\t\tCommonProject.scripts.settings.methods.notificationBySetting(msg, {\n\t\t\t\t\t\t\t\tduration: 0,\n\t\t\t\t\t\t\t\textraTitle: '知道智慧树学习脚本'\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tawait $store.setTab('reload-count', (reload_count ?? 0) + 1);\n\t\t\t\t\t\t$message.error(msg);\n\t\t\t\t\t\t$console.log(msg);\n\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\tlocation.reload();\n\t\t\t\t\t\t}, 3000);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst msg = '视频加载失败，即将跳过。';\n\t\t\t\t\t\t$message.error(msg);\n\t\t\t\t\t\t$console.log(msg);\n\t\t\t\t\t\tnext();\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\tconst doWork = async () => {\n\t\t\t\t\tawait waitForCaptcha();\n\n\t\t\t\t\tif (!document.querySelector('.active-file')?.parentElement?.parentElement?.querySelector('.icon-movie')) {\n\t\t\t\t\t\t$message.warn('当前章节不支持学习，即将跳转下一节');\n\t\t\t\t\t\tawait $.sleep(3000);\n\t\t\t\t\t\tawait next();\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst set = async () => {\n\t\t\t\t\t\t// 上面操作会导致元素刷新，这里重新获取视频\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t// 设置清晰度\n\t\t\t\t\t\t\tawait processor.switchLine(this.cfg.definition || 'line1bq');\n\t\t\t\t\t\t\tawait $.sleep(1000);\n\t\t\t\t\t\t\t// 设置播放速度\n\t\t\t\t\t\t\tawait processor.switchPlaybackRate(this.cfg.playbackRate);\n\t\t\t\t\t\t\tawait $.sleep(1000);\n\t\t\t\t\t\t\tconst media = await waitForMedia({ timeout: 5 * 1000, filter: (m) => m.src.length !== 0 });\n\t\t\t\t\t\t\tawait $.sleep(1000);\n\t\t\t\t\t\t\tstate.study.currentMedia = media;\n\t\t\t\t\t\t\tif (media) {\n\t\t\t\t\t\t\t\t// 如果已经播放完了，则重置视频进度\n\t\t\t\t\t\t\t\tmedia.currentTime = 1;\n\t\t\t\t\t\t\t\t// 音量\n\t\t\t\t\t\t\t\tmedia.volume = this.cfg.volume;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn state.study.currentMedia;\n\t\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\t\treload(e);\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\n\t\t\t\t\t$message.info('开始播放');\n\t\t\t\t\t// 部分用户视频加载很慢，这里等待一下\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst media = await waitForMedia({\n\t\t\t\t\t\t\ttimeout: 10 * 1000,\n\t\t\t\t\t\t\tfilter: (m) => m.src.length !== 0\n\t\t\t\t\t\t});\n\t\t\t\t\t\tmedia.pause();\n\t\t\t\t\t\t// 固定进度条便于下方点击音量等按钮\n\t\t\t\t\t\tfixProcessBar();\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\treload(e);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tawait waitForCaptcha();\n\t\t\t\t\tconst video = await set();\n\t\t\t\t\tif (!video) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// 如果视频元素无法访问，证明已经切换了视频\n\t\t\t\t\tconst videoCheckInterval = setInterval(async () => {\n\t\t\t\t\t\tif (!video?.isConnected) {\n\t\t\t\t\t\t\tclearInterval(videoCheckInterval);\n\t\t\t\t\t\t\t$message.info({ content: '检测到视频切换中...' });\n\t\t\t\t\t\t\t/**\n\t\t\t\t\t\t\t * 元素无法访问证明用户切换视频了\n\t\t\t\t\t\t\t * 所以不往下播放视频，而是重新播放用户当前选中的视频\n\t\t\t\t\t\t\t */\n\t\t\t\t\t\t\tdoWork();\n\t\t\t\t\t\t}\n\t\t\t\t\t}, 3000);\n\n\t\t\t\t\tplayMedia(() => video?.play()).then(() => {\n\t\t\t\t\t\tconst cn = getChapterName();\n\t\t\t\t\t\t$message.info({ content: '正在学习：' + cn });\n\t\t\t\t\t\t$console.log('正在学习：' + cn);\n\t\t\t\t\t});\n\n\t\t\t\t\tvideo.onpause = async () => {\n\t\t\t\t\t\tif (!video?.isConnected) return;\n\t\t\t\t\t\tif (!video?.ended && state.study.stop === false) {\n\t\t\t\t\t\t\tawait $.sleep(1000);\n\t\t\t\t\t\t\tvideo?.play();\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\n\t\t\t\t\tvideo.onended = async () => {\n\t\t\t\t\t\tif (!video?.isConnected) return;\n\t\t\t\t\t\t$message.info('即将自动跳转下一节');\n\t\t\t\t\t\t$console.info('即将自动跳转下一节');\n\t\t\t\t\t\tclearInterval(videoCheckInterval);\n\t\t\t\t\t\tawait $.sleep(3000);\n\t\t\t\t\t\tawait next();\n\t\t\t\t\t};\n\t\t\t\t};\n\n\t\t\t\tdoWork();\n\t\t\t}\n\t\t}),\n\t\thike_v2: new Script({\n\t\t\tname: '🖥️ 教学空间-AI智慧课程-学习脚本',\n\t\t\tmatches: [\n\t\t\t\t['学习首页', /polymas.com\\/stu-hike\\/agent-course-full\\/.*\\/stu\\/(course-home|study)/],\n\t\t\t\t['学习页面', '/stu/study/resource-detail']\n\t\t\t],\n\t\t\tnamespace: 'zhs.hike_v2.study',\n\t\t\tconfigs: {\n\t\t\t\tnotes: {\n\t\t\t\t\tdefaultValue: $ui.notes(['请手动进入视频、作业、考试页面，脚本会自动运行。']).outerHTML\n\t\t\t\t},\n\t\t\t\trestudy: restudy,\n\t\t\t\treloadWhenError: {\n\t\t\t\t\tlabel: '视频黑屏时自动刷新',\n\t\t\t\t\tattrs: { type: 'checkbox', title: '当视频出现加载失败，或者黑屏等异常时，自动刷新页面3次尝试修复' },\n\t\t\t\t\tdefaultValue: true\n\t\t\t\t},\n\t\t\t\tvolume: volume\n\t\t\t},\n\t\t\toncomplete(...args) {\n\t\t\t\tthis.onhistorychange?.('push', ...args);\n\t\t\t},\n\t\t\tasync onhistorychange(type) {\n\t\t\t\tif (type !== 'push') {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// 置顶当前脚本\n\t\t\t\tif (!location.href.includes('stu/study/resource-detail')) {\n\t\t\t\t\t$message.info({ content: '请手动进入视频、作业、考试页面，脚本会自动运行。', duration: 60 });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst processor = new HikeV2();\n\t\t\t\t// 监听音量\n\t\t\t\tthis.onConfigChange('volume', (curr) => {\n\t\t\t\t\tstate.study.currentMedia && (state.study.currentMedia.volume = curr);\n\t\t\t\t});\n\n\t\t\t\tconst next = async () => {\n\t\t\t\t\t// 打开章节列表\n\t\t\t\t\tdocument.querySelector('.drawer-panel')?.classList.add('active');\n\n\t\t\t\t\tconst nextJob = processor.getNext({ next: true, restudy: this.cfg.restudy });\n\t\t\t\t\tif (nextJob) {\n\t\t\t\t\t\tnextJob.scrollIntoView({ behavior: 'smooth', block: 'center' });\n\t\t\t\t\t\tawait $.sleep(200);\n\t\t\t\t\t\tnextJob.click();\n\t\t\t\t\t\tawait $.sleep(3000);\n\t\t\t\t\t\tdoJob();\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfinishAlert();\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tconst waitForLoad = () => {\n\t\t\t\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\t\t\t\tconst check = () => {\n\t\t\t\t\t\t\tif (processor.hasJob()) {\n\t\t\t\t\t\t\t\tresolve();\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tsetTimeout(check, 1000);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t};\n\t\t\t\t\t\tcheck();\n\t\t\t\t\t});\n\t\t\t\t};\n\n\t\t\t\tawait waitForLoad();\n\t\t\t\tawait $.sleep(3000);\n\n\t\t\t\t$message.success({ content: '即将开始自动学习！' });\n\n\t\t\t\tconst reload = async (e: any) => {\n\t\t\t\t\t$console.error(e);\n\t\t\t\t\tif (this.cfg.reloadWhenError) {\n\t\t\t\t\t\tconst msg = '视频加载失败，即将刷新页面。';\n\t\t\t\t\t\tconst reload_count = await $store.getTab('reload-count');\n\t\t\t\t\t\tif (reload_count && reload_count > 3) {\n\t\t\t\t\t\t\tconst msg = '视频加载失败/黑屏导致重新加载页面次数超过3次，请尝试关闭页面重新打开，或者检查网络连接！';\n\t\t\t\t\t\t\tawait $store.setTab('reload-count', 0);\n\t\t\t\t\t\t\t$message.error({ content: msg, duration: 0 });\n\t\t\t\t\t\t\t$console.log(msg);\n\t\t\t\t\t\t\tCommonProject.scripts.settings.methods.notificationBySetting(msg, {\n\t\t\t\t\t\t\t\tduration: 0,\n\t\t\t\t\t\t\t\textraTitle: '知道智慧树学习脚本'\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tawait $store.setTab('reload-count', (reload_count ?? 0) + 1);\n\t\t\t\t\t\t$message.error(msg);\n\t\t\t\t\t\t$console.log(msg);\n\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\tlocation.reload();\n\t\t\t\t\t\t}, 3000);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst msg = '视频加载失败，即将跳过。';\n\t\t\t\t\t\t$message.error(msg);\n\t\t\t\t\t\t$console.log(msg);\n\t\t\t\t\t\tnext();\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\tconst doJob = async () => {\n\t\t\t\t\tawait waitForCaptcha();\n\n\t\t\t\t\tif (!document.querySelector('.video-js')) {\n\t\t\t\t\t\t$message.warn('当前章节不支持学习，即将跳转下一节');\n\t\t\t\t\t\tawait $.sleep(3000);\n\t\t\t\t\t\tawait next();\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst set = async () => {\n\t\t\t\t\t\t// 上面操作会导致元素刷新，这里重新获取视频\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait $.sleep(1000);\n\t\t\t\t\t\t\tconst media = await waitForMedia({ timeout: 5 * 1000, filter: (m) => m.src.length !== 0 });\n\t\t\t\t\t\t\tawait $.sleep(1000);\n\t\t\t\t\t\t\tstate.study.currentMedia = media;\n\t\t\t\t\t\t\tif (media) {\n\t\t\t\t\t\t\t\t// 如果已经播放完了，则重置视频进度\n\t\t\t\t\t\t\t\tmedia.currentTime = 1;\n\t\t\t\t\t\t\t\t// 音量\n\t\t\t\t\t\t\t\tmedia.volume = this.cfg.volume;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn state.study.currentMedia;\n\t\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\t\treload(e);\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\n\t\t\t\t\t$message.info('开始播放');\n\t\t\t\t\tawait waitForCaptcha();\n\t\t\t\t\tconst video = await set();\n\t\t\t\t\tif (!video) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tconst video_src = video.src;\n\n\t\t\t\t\t// 如果视频元素无法访问，证明已经切换了视频\n\t\t\t\t\tconst videoCheckInterval = setInterval(async () => {\n\t\t\t\t\t\tif (!video?.isConnected || video.src !== video_src) {\n\t\t\t\t\t\t\tclearInterval(videoCheckInterval);\n\t\t\t\t\t\t\t$message.info({ content: '检测到视频切换中...' });\n\t\t\t\t\t\t\t/**\n\t\t\t\t\t\t\t * 元素无法访问证明用户切换视频了\n\t\t\t\t\t\t\t * 所以不往下播放视频，而是重新播放用户当前选中的视频\n\t\t\t\t\t\t\t */\n\t\t\t\t\t\t\tdoJob();\n\t\t\t\t\t\t}\n\t\t\t\t\t}, 3000);\n\n\t\t\t\t\tplayMedia(() => video?.play()).then(() => {\n\t\t\t\t\t\tconst cn = processor.getChapterName();\n\t\t\t\t\t\t$message.info({ content: '正在学习：' + cn });\n\t\t\t\t\t\t$console.log('正在学习：' + cn);\n\t\t\t\t\t});\n\n\t\t\t\t\tvideo.onpause = async () => {\n\t\t\t\t\t\tif (!video?.isConnected) return;\n\t\t\t\t\t\tif (!video?.ended && state.study.stop === false) {\n\t\t\t\t\t\t\tawait $.sleep(1000);\n\t\t\t\t\t\t\tvideo?.play();\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\n\t\t\t\t\tvideo.onended = async () => {\n\t\t\t\t\t\tif (!video?.isConnected) return;\n\t\t\t\t\t\t$message.info('即将自动跳转下一节');\n\t\t\t\t\t\t$console.info('即将自动跳转下一节');\n\t\t\t\t\t\tclearInterval(videoCheckInterval);\n\t\t\t\t\t\tawait $.sleep(3000);\n\t\t\t\t\t\tawait next();\n\t\t\t\t\t};\n\t\t\t\t};\n\n\t\t\t\tdoJob();\n\t\t\t}\n\t\t}),\n\t\t'hike-work': new Script({\n\t\t\tmatches: [['AI教学中心-作业任务页面', '/stu-hike/stuHomeworkDo']],\n\t\t\tname: '✍️ 教学空间-AI智慧课程-作业考试脚本',\n\t\t\tnamespace: 'zhs.hike.work',\n\t\t\tconfigs: {\n\t\t\t\tnotes: workNotes,\n\t\t\t\tworkDelay: {\n\t\t\t\t\tlabel: '作业答题开始时间延迟（秒）',\n\t\t\t\t\tdefaultValue: 3,\n\t\t\t\t\tattrs: { type: 'number', min: 1, step: 1, max: 10 }\n\t\t\t\t}\n\t\t\t},\n\t\t\tasync oncomplete() {\n\t\t\t\t// 检查是否为软件环境\n\t\t\t\tCommonProject.scripts.render.methods.pin(this);\n\n\t\t\t\tawait waitForElement('.q_main');\n\n\t\t\t\tcommonWork(this, {\n\t\t\t\t\tworkerProvider: (opts) => {\n\t\t\t\t\t\treturn hikeWork(undefined, opts);\n\t\t\t\t\t},\n\t\t\t\t\tstart_delay_seconds: this.cfg.workDelay ?? 3\n\t\t\t\t});\n\t\t\t}\n\t\t}),\n\t\t'hike-homework': new Script({\n\t\t\tmatches: [['AI教学中心-题目作业', '/stu/answer-homework']],\n\t\t\tname: '✍️ 教学空间-AI智慧课程-题目作业脚本',\n\t\t\tnamespace: 'zhs.hike.homework',\n\t\t\tconfigs: {\n\t\t\t\tnotes: workNotes,\n\t\t\t\tworkDelay: {\n\t\t\t\t\tlabel: '作业答题开始时间延迟（秒）',\n\t\t\t\t\tdefaultValue: 3,\n\t\t\t\t\tattrs: { type: 'number', min: 1, step: 1, max: 10 }\n\t\t\t\t}\n\t\t\t},\n\t\t\tasync oncomplete() {\n\t\t\t\t// 检查是否为软件环境\n\t\t\t\tCommonProject.scripts.render.methods.pin(this);\n\n\t\t\t\tawait waitForElement('.question-item');\n\n\t\t\t\tcommonWork(this, {\n\t\t\t\t\tworkerProvider: (opts) => {\n\t\t\t\t\t\treturn hikeHomework(undefined, opts);\n\t\t\t\t\t},\n\t\t\t\t\tstart_delay_seconds: this.cfg.workDelay ?? 3\n\t\t\t\t});\n\t\t\t}\n\t\t})\n\t}\n});\n\n/**\n * 观看视频\n * @param setting\n * @returns\n */\nasync function watch(\n\tprocessor: ZHSProcessor,\n\toptions: { reloadWhenError: boolean; volume: number; playbackRate: number; definition?: 'line1bq' | 'line1gq' },\n\tactions: {\n\t\tonended: (opts: { next: boolean }) => void;\n\t\treload: () => void;\n\t}\n) {\n\tconst reload = async (e: any) => {\n\t\t$console.error(e);\n\t\tif (options.reloadWhenError) {\n\t\t\tconst msg = '视频加载失败，即将刷新页面。';\n\t\t\tconst reload_count = await $store.getTab('reload-count');\n\t\t\tif (reload_count && reload_count > 3) {\n\t\t\t\tconst msg = '视频加载失败导致重新加载页面次数超过3次，请尝试关闭页面重新打开，或者检查网络连接！';\n\t\t\t\tawait $store.setTab('reload-count', 0);\n\t\t\t\t$message.error({ content: msg, duration: 0 });\n\t\t\t\t$console.log(msg);\n\t\t\t\tCommonProject.scripts.settings.methods.notificationBySetting(msg, {\n\t\t\t\t\tduration: 0,\n\t\t\t\t\textraTitle: '知道智慧树学习脚本'\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tawait $store.setTab('reload-count', (reload_count ?? 0) + 1);\n\t\t\t$message.error(msg);\n\t\t\t$console.log(msg);\n\t\t\tsetTimeout(() => {\n\t\t\t\tactions.reload();\n\t\t\t}, 5000);\n\t\t} else {\n\t\t\tconst msg = '视频加载失败，即将跳过。';\n\t\t\t$message.error(msg);\n\t\t\t$console.log(msg);\n\t\t\tactions.onended({ next: true });\n\t\t}\n\t};\n\n\tconst set = async () => {\n\t\t// 上面操作会导致元素刷新，这里重新获取视频\n\t\ttry {\n\t\t\tawait $.sleep(1000);\n\t\t\tfixProcessBar();\n\t\t\t// 设置清晰度\n\t\t\tawait processor.switchLine(options.definition || 'line1bq');\n\t\t\tawait $.sleep(1000);\n\t\t\tfixProcessBar();\n\t\t\t// 设置播放速度\n\t\t\tawait processor.switchPlaybackRate(options.playbackRate);\n\t\t\tawait $.sleep(1000);\n\t\t\tconst media = await waitForMedia({ timeout: 10 * 1000 });\n\t\t\tawait $.sleep(1000);\n\t\t\tstate.study.currentMedia = media;\n\n\t\t\tif (media) {\n\t\t\t\t// 如果已经播放完了，则重置视频进度\n\t\t\t\tmedia.currentTime = 1;\n\t\t\t\t// 音量\n\t\t\t\tmedia.volume = options.volume;\n\t\t\t}\n\t\t\treturn state.study.currentMedia;\n\t\t} catch (e) {\n\t\t\treturn await reload(e);\n\t\t}\n\t};\n\n\t$message.info('开始播放');\n\n\t// 部分用户视频加载很慢，这里等待一下\n\ttry {\n\t\tconst media = await waitForMedia({ timeout: 10 * 1000 });\n\t\tmedia.volume = options.volume;\n\t\t// 如果已经播放完了，则重置视频进度\n\t\tmedia.pause();\n\t\t// 固定进度条便于下方点击音量等按钮\n\t\tfixProcessBar();\n\t} catch (e) {\n\t\treturn await reload(e);\n\t}\n\tawait $.sleep(1000);\n\n\tconst video = await set();\n\tif (!video) {\n\t\treturn await reload('视频加载失败');\n\t}\n\n\tconst videoCheckInterval = setInterval(async () => {\n\t\t// 如果视频元素无法访问，证明已经切换了视频\n\t\tif (video?.isConnected === false) {\n\t\t\tclearInterval(videoCheckInterval);\n\t\t\t$message.info({ content: '检测到视频切换中...' });\n\t\t\t/**\n\t\t\t * 元素无法访问证明用户切换视频了\n\t\t\t * 所以不往下播放视频，而是重新播放用户当前选中的视频\n\t\t\t */\n\t\t\tactions.onended({ next: false });\n\t\t}\n\t}, 3000);\n\n\tplayMedia(() => video?.play());\n\n\tvideo.onpause = async () => {\n\t\tif (!video?.ended && state.study.stop === false) {\n\t\t\tawait waitForCaptcha();\n\t\t\tawait $.sleep(1000);\n\t\t\tvideo?.play();\n\t\t}\n\t};\n\n\tvideo.onended = () => {\n\t\tclearInterval(videoCheckInterval);\n\t\t// 正常切换下一个视频\n\t\tactions.onended({ next: true });\n\t};\n}\n\n/**\n * 观看校内课\n */\nasync function watchXnk(options: { volume: number }, onended: () => void) {\n\t// 部分用户视频加载很慢，这里等待一下\n\tconst media = await waitForMedia();\n\tmedia.volume = options.volume;\n\tmedia.currentTime = 1;\n\tstate.study.currentMedia = media;\n\n\tplayMedia(() => media?.play());\n\n\tmedia.onpause = async () => {\n\t\tif (!media?.ended) {\n\t\t\tawait $.sleep(1000);\n\t\t\tmedia?.play();\n\t\t}\n\t};\n\n\tmedia.onended = () => {\n\t\t// 正常切换下一个视频\n\t\tonended();\n\t};\n}\n/**\n * 检测是否有验证码，并等待验证\n */\n\nfunction checkForCaptcha(update: (hasCaptcha: boolean) => void) {\n\tlet modal: HTMLDivElement | undefined;\n\tlet notified = false;\n\treturn setInterval(() => {\n\t\tif ($el('.yidun_popup')) {\n\t\t\tupdate(true);\n\t\t\t// 如果弹窗不存在，则显示\n\t\t\tif (modal === undefined) {\n\t\t\t\tmodal = $modal.alert({ content: '当前检测到验证码，请输入后方可继续运行。' });\n\t\t\t}\n\t\t\t// 如果没有通知过，则通知\n\t\t\tif (!notified) {\n\t\t\t\tnotified = true;\n\t\t\t\tCommonProject.scripts.settings.methods.notificationBySetting(\n\t\t\t\t\t'智慧树脚本：当前检测到验证码，请输入后方可继续运行。',\n\t\t\t\t\t{ duration: 0 }\n\t\t\t\t);\n\t\t\t}\n\t\t} else {\n\t\t\tif (modal) {\n\t\t\t\tupdate(false);\n\t\t\t\t// 关闭弹窗\n\t\t\t\tmodal.remove();\n\t\t\t\tmodal = undefined;\n\t\t\t}\n\t\t}\n\t}, 1000);\n}\n\nfunction waitForCaptcha(): void | Promise<void> {\n\tconst popup = getPopupCaptcha();\n\tif (popup) {\n\t\tconst message = $message.warn({ content: '当前检测到验证码，请输入后方可继续运行。', duration: 0 });\n\t\tCommonProject.scripts.settings.methods.notificationBySetting(\n\t\t\t'智慧树脚本：当前检测到验证码，请输入后方可继续运行。',\n\t\t\t{ duration: 0 }\n\t\t);\n\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\tconst interval = setInterval(() => {\n\t\t\t\tconst popup = getPopupCaptcha();\n\t\t\t\tif (popup === null) {\n\t\t\t\t\tmessage?.remove();\n\t\t\t\t\tclearInterval(interval);\n\t\t\t\t\tresolve();\n\t\t\t\t}\n\t\t\t}, 1000);\n\t\t});\n\t}\n}\n\nfunction getPopupCaptcha() {\n\treturn document.querySelector('.yidun_popup');\n}\n\n/**\n * 共享课的作业和考试\n */\nfunction gxkWorkAndExam(\n\tworkInfo: any,\n\t{\n\t\tanswererWrappers,\n\t\tperiod,\n\t\tthread,\n\t\tstopSecondWhenFinish,\n\t\tredundanceWordsText,\n\t\tanswerSeparators,\n\t\tanswerMatchMode\n\t}: CommonWorkOptions\n) {\n\tCommonProject.scripts.workResults.methods.init({\n\t\tquestionPositionSyncHandlerType: 'zhs-gxk'\n\t});\n\n\t/**\n\t * workExamParts 是个列表\n\t * 里面包括一个题目类型的列表，第一个是单选，第二个是多选，第三个是判断\n\t * 所以这里直接扁平化数组方便处理\n\t */\n\tconst allExamParts =\n\t\t((workInfo?.rt?.examBase?.workExamParts as any[]) || [])?.map((p) => p.questionDtos).flat() || [];\n\n\tconst titleTransform = (_: any, index: number) => {\n\t\tconst div = h('div');\n\n\t\tdiv.innerHTML = allExamParts[index]?.name || '题目读取失败';\n\t\treturn removeRedundantWords(\n\t\t\toptimizationElementWithImage(div, true).innerText || '',\n\t\t\tredundanceWordsText.split('\\n')\n\t\t);\n\t};\n\tlet request_index = 0;\n\t/** 新建答题器 */\n\tconst worker = new OCSWorker({\n\t\troot: '.examPaper_subject',\n\t\telements: {\n\t\t\t/**\n\t\t\t * .subject_describe > div: 选择题题目\n\t\t\t * .smallStem_describe > div:nth-child(2): 阅读理解小题题目\n\t\t\t */\n\t\t\ttitle: '.subject_describe > div,.smallStem_describe > div:nth-child(2)',\n\t\t\t// 选项中图片识别\n\t\t\toptions: (root) =>\n\t\t\t\t$$el('.subject_node .nodeLab', root).map((t) => {\n\t\t\t\t\tfor (const img of Array.from(t.querySelectorAll<HTMLImageElement>('.node_detail img'))) {\n\t\t\t\t\t\t// zhs选项中如果已显示的图片则不存在 data-src，如果未显示则存在 data-src\n\t\t\t\t\t\tif (img.dataset.src) {\n\t\t\t\t\t\t\timg.src = img.dataset.src;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// 不使用 optimizationElementWithImage 是因为zhs的选项按钮也是一个图片\n\t\t\t\t\t\tcreateUnVisibleTextOfImage(img);\n\t\t\t\t\t}\n\t\t\t\t\treturn t;\n\t\t\t\t})\n\t\t},\n\t\tthread: thread ?? 1,\n\t\tanswerSeparators: answerSeparators.split(',').map((s) => s.trim()),\n\t\tanswerMatchMode: answerMatchMode,\n\t\t/** 默认搜题方法构造器 */\n\t\tanswerer: (elements, ctx) => {\n\t\t\tconst title = titleTransform(undefined, request_index++);\n\t\t\tif (title) {\n\t\t\t\treturn CommonProject.scripts.apps.methods.searchAnswerInCaches(title, async () => {\n\t\t\t\t\tawait $.sleep((period ?? 3) * 1000);\n\t\t\t\t\treturn defaultAnswerWrapperHandler(answererWrappers, {\n\t\t\t\t\t\ttype: ctx.type || 'unknown',\n\t\t\t\t\t\ttitle,\n\t\t\t\t\t\toptions: ctx.elements.options.map((o) => o.innerText).join('\\n')\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tthrow new Error('题目为空，请查看题目是否为空，或者忽略此题');\n\t\t\t}\n\t\t},\n\t\twork: {\n\t\t\ttype(ctx) {\n\t\t\t\tconst type = ctx.elements.title[0].parentElement?.parentElement\n\t\t\t\t\t?.querySelector('.subject_type')\n\t\t\t\t\t?.textContent?.trim();\n\t\t\t\tif (type?.includes('单选题')) {\n\t\t\t\t\treturn 'single';\n\t\t\t\t} else if (type?.includes('多选题')) {\n\t\t\t\t\treturn 'multiple';\n\t\t\t\t} else if (type?.includes('判断题')) {\n\t\t\t\t\treturn 'judgement';\n\t\t\t\t} else if (type?.includes('填空题')) {\n\t\t\t\t\treturn 'completion';\n\t\t\t\t} else {\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t},\n\t\t\t/** 自定义处理器 */\n\t\t\tasync handler(type, answer, option) {\n\t\t\t\tif (type === 'judgement' || type === 'single' || type === 'multiple') {\n\t\t\t\t\tif (!option.querySelector('input')?.checked) {\n\t\t\t\t\t\toption.click();\n\t\t\t\t\t\tawait $.sleep(200);\n\t\t\t\t\t}\n\t\t\t\t} else if (type === 'completion' && answer.trim()) {\n\t\t\t\t\tconst text = option.querySelector('textarea');\n\t\t\t\t\tif (text) {\n\t\t\t\t\t\ttext.value = answer;\n\t\t\t\t\t\tawait $.sleep(200);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t/** 完成答题后 */\n\t\tonResultsUpdate(curr, index, res) {\n\t\t\tCommonProject.scripts.workResults.methods.setResults(simplifyWorkResult(res, titleTransform));\n\n\t\t\tif (curr.result?.finish) {\n\t\t\t\tconst title = allExamParts[index]?.name;\n\t\t\t\tif (title) {\n\t\t\t\t\tCommonProject.scripts.apps.methods.addQuestionCacheFromWorkResult(\n\t\t\t\t\t\tsimplifyWorkResult([curr], (_: any, __: number) => title)\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\tCommonProject.scripts.workResults.methods.updateWorkStateByResults(res);\n\t\t}\n\t});\n\n\tcheckForCaptcha((hasCaptcha) => {\n\t\tif (hasCaptcha) {\n\t\t\tworker.emit('stop');\n\t\t} else {\n\t\t\tworker.emit('continuate');\n\t\t}\n\t});\n\n\tworker\n\t\t.doWork()\n\t\t.then(async (res) => {\n\t\t\t// 如果被强制关闭，则不进行保存操作\n\t\t\tif (worker.isClose === true) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t$message.success({ content: `答题完成，将等待 ${stopSecondWhenFinish} 秒后进行保存或提交。` });\n\t\t\tawait $.sleep(stopSecondWhenFinish * 1000);\n\t\t\t// @ts-ignore\n\t\t\tif (worker.isClose === true) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t/**\n\t\t\t * 保存题目，不在选择答案后保存的原因是，如果答题线程大于3会导致题目错乱，因为 resolverIndex 并不是顺序递增的\n\t\t\t */\n\t\t\tfor (let index = 0; index < worker.totalQuestionCount; index++) {\n\t\t\t\t// @ts-ignore\n\t\t\t\tif (worker.isClose === true) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst modal = $modal.alert({\n\t\t\t\t\ttitle: '⚠️提示',\n\t\t\t\t\tcontent: `正在自动保存题目中，不然填写的答案将无效，<br>当前进度 ${index}/${worker.totalQuestionCount}<br>保存完毕前请勿操作...`,\n\t\t\t\t\tconfirmButton: null,\n\t\t\t\t\tmaskCloseable: false\n\t\t\t\t});\n\t\t\t\tawait waitForCaptcha();\n\t\t\t\tawait $.sleep(2000);\n\t\t\t\t// 跳转到该题目，防止用户在保存时切换题目\n\t\t\t\tdocument.querySelectorAll<HTMLElement>('.answerCard_list ul li').item(index)?.click();\n\t\t\t\tawait $.sleep(200);\n\t\t\t\t// 下一页\n\t\t\t\tconst next = $el('div.examPaper_box > div.switch-btn-box > button:nth-child(2)');\n\t\t\t\tif (next) {\n\t\t\t\t\tnext.click();\n\t\t\t\t} else {\n\t\t\t\t\t$console.error('未找到下一页按钮。');\n\t\t\t\t}\n\t\t\t\tmodal?.remove();\n\t\t\t}\n\t\t\t$message.info({ content: '作业/考试完成，请自行检查后保存或提交。', duration: 0 });\n\t\t\tworker.emit('done');\n\t\t})\n\t\t.catch((err) => {\n\t\t\t$message.error({ content: '答题程序发生错误 : ' + err.message, duration: 0 });\n\t\t});\n\n\treturn worker;\n}\n\n/**\n * 校内学分课的作业\n */\nfunction xnkWork({ answererWrappers, period, thread, answerSeparators, answerMatchMode }: CommonWorkOptions) {\n\t$message.info({ content: '开始作业' });\n\n\tCommonProject.scripts.workResults.methods.init();\n\n\tconst titleTransform = (titles: (HTMLElement | undefined)[]) => {\n\t\treturn titles\n\t\t\t.filter((t) => t?.innerText)\n\t\t\t.map((t) => (t ? optimizationElementWithImage(t).innerText : ''))\n\t\t\t.join(',');\n\t};\n\n\tconst workResults: SimplifyWorkResult[] = [];\n\tlet totalQuestionCount = 0;\n\tlet requestedCount = 0;\n\tlet resolvedCount = 0;\n\n\tconst worker = new OCSWorker({\n\t\troot: '.questionBox',\n\t\telements: {\n\t\t\ttitle: '.questionContent',\n\t\t\toptions: '.optionUl label',\n\t\t\tquestionTit: '.questionTit'\n\t\t},\n\t\tthread: thread ?? 1,\n\t\tanswerSeparators: answerSeparators.split(',').map((s) => s.trim()),\n\t\tanswerMatchMode: answerMatchMode,\n\t\t/** 默认搜题方法构造器 */\n\t\tanswerer: (elements, ctx) => {\n\t\t\tconst title = titleTransform(elements.title);\n\t\t\tif (title) {\n\t\t\t\treturn CommonProject.scripts.apps.methods.searchAnswerInCaches(title, async () => {\n\t\t\t\t\tawait $.sleep((period ?? 3) * 1000);\n\t\t\t\t\treturn defaultAnswerWrapperHandler(answererWrappers, {\n\t\t\t\t\t\ttype: ctx.type || 'unknown',\n\t\t\t\t\t\ttitle,\n\t\t\t\t\t\toptions: ctx.elements.options.map((o) => o.innerText).join('\\n')\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tthrow new Error('题目为空，请查看题目是否为空，或者忽略此题');\n\t\t\t}\n\t\t},\n\t\twork: {\n\t\t\t/** 自定义处理器 */\n\t\t\tasync handler(type, answer, option, ctx) {\n\t\t\t\tif (type === 'judgement' || type === 'single' || type === 'multiple') {\n\t\t\t\t\tif (option.querySelector('input')?.checked === false) {\n\t\t\t\t\t\toption.click();\n\t\t\t\t\t\tawait $.sleep(200);\n\t\t\t\t\t}\n\t\t\t\t} else if (type === 'completion' && answer.trim()) {\n\t\t\t\t\tconst text = option.querySelector('textarea');\n\t\t\t\t\tif (text) {\n\t\t\t\t\t\ttext.value = answer;\n\t\t\t\t\t\tawait $.sleep(200);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * 因为校内课的考试和作业都是一题一题做的，不像其他自动答题一样可以获取全部试卷内容。\n\t\t * 所以只能根据自定义的状态进行搜索结果的显示。\n\t\t */\n\t\tonResultsUpdate(current, _, res) {\n\t\t\tif (current.result) {\n\t\t\t\tworkResults.push(...simplifyWorkResult([current], titleTransform));\n\t\t\t\tCommonProject.scripts.workResults.methods.setResults(workResults);\n\t\t\t\ttotalQuestionCount++;\n\t\t\t\trequestedCount++;\n\t\t\t\tresolvedCount++;\n\t\t\t}\n\n\t\t\tif (current.result?.finish) {\n\t\t\t\tCommonProject.scripts.apps.methods.addQuestionCacheFromWorkResult(\n\t\t\t\t\tsimplifyWorkResult([current], titleTransform)\n\t\t\t\t);\n\t\t\t}\n\t\t\tCommonProject.scripts.workResults.methods.updateWorkState({\n\t\t\t\ttotalQuestionCount,\n\t\t\t\trequestedCount,\n\t\t\t\tresolvedCount\n\t\t\t});\n\t\t}\n\t});\n\n\tconst getBtn = () => document.querySelector('span.Topicswitchingbtn:nth-child(2)') as HTMLElement;\n\tlet next = getBtn();\n\n\t(async () => {\n\t\twhile (next && worker.isClose === false) {\n\t\t\tawait worker.doWork();\n\t\t\tawait $.sleep(1000);\n\t\t\tnext = getBtn();\n\t\t\tnext?.click();\n\t\t\tawait $.sleep(1000);\n\t\t}\n\n\t\t$message.info({ content: '作业/考试完成，请自行检查后保存或提交。', duration: 0 });\n\t\tworker.emit('done');\n\t\t// 答题完成后，题库选项点击才会同步题目，否则会导致题目错乱\n\t\tCommonProject.scripts.workResults.cfg.questionPositionSyncHandlerType = 'zhs-xnk';\n\t})();\n\n\treturn worker;\n}\n\n/**\n * 智慧课程的作业\n */\nfunction smartWork(\n\tremotePage: RemotePage | undefined,\n\t{ answererWrappers, period, thread, answerSeparators, answerMatchMode }: CommonWorkOptions\n) {\n\t$message.info({ content: '开始作业' });\n\t$message.warn({\n\t\tcontent: '⚠️ 答题中请勿进行任何操作，如需暂停答题，请等待全部题目搜索完成并执行自动保存功能后才能操作。',\n\t\tduration: 0,\n\t\tcloseable: false\n\t});\n\n\tCommonProject.scripts.workResults.methods.init();\n\n\tconst titleTransform = (titles: (HTMLElement | undefined)[]) => {\n\t\treturn titles\n\t\t\t.filter((t) => t?.innerText)\n\t\t\t.map((t) => (t ? optimizationElementWithImage(t).innerText : ''))\n\t\t\t.join(',');\n\t};\n\n\tconst workResults: SimplifyWorkResult[] = [];\n\tlet totalQuestionCount = 0;\n\tlet requestedCount = 0;\n\tlet resolvedCount = 0;\n\n\tconst worker = new OCSWorker({\n\t\troot: '.questionContent',\n\t\telements: {\n\t\t\ttitle: '.questionName .centent-pre',\n\t\t\toptions: '.radio-view li.clearfix, .checkbox-views label.el-checkbox, .fillAnswer'\n\t\t},\n\t\tthread: thread ?? 1,\n\t\tanswerSeparators: answerSeparators.split(',').map((s) => s.trim()),\n\t\tanswerMatchMode: answerMatchMode,\n\t\t/** 默认搜题方法构造器 */\n\t\tanswerer: (elements, ctx) => {\n\t\t\tconst title = titleTransform(elements.title);\n\t\t\tif (title) {\n\t\t\t\treturn CommonProject.scripts.apps.methods.searchAnswerInCaches(title, async () => {\n\t\t\t\t\tawait $.sleep((period ?? 3) * 1000);\n\t\t\t\t\treturn defaultAnswerWrapperHandler(answererWrappers, {\n\t\t\t\t\t\ttype: ctx.type || 'unknown',\n\t\t\t\t\t\ttitle,\n\t\t\t\t\t\toptions: ctx.elements.options.map((o) => o.innerText).join('\\n')\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tthrow new Error('题目为空，请查看题目是否为空，或者忽略此题');\n\t\t\t}\n\t\t},\n\t\twork: {\n\t\t\ttype(ctx) {\n\t\t\t\tconst type = ctx.elements.title[0]?.parentElement?.querySelector('.letterSortNum')?.textContent;\n\t\t\t\tif (type?.includes('单选题')) {\n\t\t\t\t\treturn 'single';\n\t\t\t\t} else if (type?.includes('多选题')) {\n\t\t\t\t\treturn 'multiple';\n\t\t\t\t} else if (type?.includes('判断题')) {\n\t\t\t\t\treturn 'judgement';\n\t\t\t\t} else if (type?.includes('填空')) {\n\t\t\t\t\treturn 'completion';\n\t\t\t\t} else {\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t},\n\t\t\t/** 自定义处理器 */\n\t\t\tasync handler(type, answer, option, ctx) {\n\t\t\t\tif (type === 'judgement' || type === 'single' || type === 'multiple') {\n\t\t\t\t\tconst opt = option.querySelector<HTMLElement>(\n\t\t\t\t\t\t'.el-checkbox__input:not(.is-checked),i.iconfont:not(.checkedIcon)'\n\t\t\t\t\t);\n\n\t\t\t\t\tif (opt) {\n\t\t\t\t\t\tif (remotePage) {\n\t\t\t\t\t\t\tawait remotePage.click(opt);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\topt.click();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tawait $.sleep(200);\n\t\t\t\t\t}\n\t\t\t\t} else if (type === 'completion') {\n\t\t\t\t\tconst opt = option.querySelector<HTMLInputElement>('input');\n\t\t\t\t\tif (opt && answer.trim()) {\n\t\t\t\t\t\tif (remotePage) {\n\t\t\t\t\t\t\tawait remotePage.click(opt);\n\t\t\t\t\t\t\topt.value = '';\n\t\t\t\t\t\t\tawait remotePage['keyboard.type'](answer, { delay: Math.floor(Math.random() * 100) });\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\topt.value = answer;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tawait $.sleep(200);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t/**\n\t\t * 作业都是一题一题做的，不像其他自动答题一样可以获取全部试卷内容。\n\t\t * 所以只能根据自定义的状态进行搜索结果的显示。\n\t\t */\n\t\tonResultsUpdate(current, _, res) {\n\t\t\tif (current.result) {\n\t\t\t\tworkResults.push(...simplifyWorkResult([current], titleTransform));\n\t\t\t\tCommonProject.scripts.workResults.methods.setResults(workResults);\n\t\t\t\ttotalQuestionCount++;\n\t\t\t\trequestedCount++;\n\t\t\t\tresolvedCount++;\n\t\t\t}\n\n\t\t\tif (current.result?.finish) {\n\t\t\t\tCommonProject.scripts.apps.methods.addQuestionCacheFromWorkResult(\n\t\t\t\t\tsimplifyWorkResult([current], titleTransform)\n\t\t\t\t);\n\t\t\t}\n\t\t\tCommonProject.scripts.workResults.methods.updateWorkState({\n\t\t\t\ttotalQuestionCount,\n\t\t\t\trequestedCount,\n\t\t\t\tresolvedCount\n\t\t\t});\n\t\t}\n\t});\n\n\tconst getNextBtn = () => document.querySelector<HTMLElement>('.next-topic.next-t');\n\tlet next = null as HTMLElement | null;\n\n\t(async () => {\n\t\t// 从第一题开始\n\t\tconst first = document.querySelector<HTMLElement>('[role=\"treeitem\"] .font-sec-style-node');\n\t\tif (first) {\n\t\t\tif (remotePage) await remotePage.click(first);\n\t\t\telse first.click();\n\t\t\tawait $.sleep(3000);\n\t\t}\n\t\tlet count = 0;\n\t\twhile (worker.isClose === false) {\n\t\t\tawait worker.doWork({ enable_debug: BackgroundProject.scripts.dev.cfg.enable_answerer_debug });\n\t\t\tnext = getNextBtn();\n\t\t\tif (!next) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tawait $.sleep(1000);\n\t\t\tif (remotePage) await remotePage.click(next);\n\t\t\telse next.click();\n\t\t\t// 等待题目加载\n\t\t\tawait $.sleep(1000);\n\t\t\tcount++;\n\t\t}\n\n\t\t$message.info({\n\t\t\tcontent: '作业/考试完成，请自行检查后保存或提交。',\n\t\t\tduration:\n\t\t\t\t// 题目过多则不自动关闭\n\t\t\t\tcount > 10 ? 0 : 30\n\t\t});\n\t\tworker.emit('done');\n\t\t// 答题完成后，题库选项点击才会同步题目，否则会导致题目错乱\n\t\tCommonProject.scripts.workResults.cfg.questionPositionSyncHandlerType = 'zhs-smart';\n\t})();\n\treturn worker;\n}\n\nfunction smartExam(\n\tremotePage: RemotePage | undefined,\n\t{ answererWrappers, period, thread, answerSeparators, answerMatchMode }: CommonWorkOptions\n) {\n\t$message.info({ content: '开始作业' });\n\t$message.warn({\n\t\tcontent: '⚠️ 答题中请勿进行任何操作，如需暂停答题，请等待全部题目搜索完成并执行自动保存功能后才能操作。',\n\t\tduration: 0,\n\t\tcloseable: false\n\t});\n\n\tCommonProject.scripts.workResults.methods.init();\n\n\tconst titleTransform = (titles: (HTMLElement | undefined)[]) => {\n\t\treturn titles\n\t\t\t.filter((t) => t?.innerText)\n\t\t\t.map((t) => (t ? optimizationElementWithImage(t).innerText : ''))\n\t\t\t.join(',');\n\t};\n\n\tconst workResults: SimplifyWorkResult[] = [];\n\tlet totalQuestionCount = 0;\n\tlet requestedCount = 0;\n\tlet resolvedCount = 0;\n\n\tconst worker = new OCSWorker({\n\t\troot: '.question-area-content',\n\t\telements: {\n\t\t\ttype: 'div.flex.items-center.mb-\\\\[16px\\\\]',\n\t\t\ttitle: 'div.flex-1 .mb-\\\\[32px\\\\] .text-mainText.font-medium',\n\t\t\toptions: 'label.user-select.group'\n\t\t},\n\t\tthread: thread ?? 1,\n\t\tanswerSeparators: answerSeparators.split(',').map((s) => s.trim()),\n\t\tanswerMatchMode: answerMatchMode,\n\t\t/** 默认搜题方法构造器 */\n\t\tanswerer: (elements, ctx) => {\n\t\t\tconst title = titleTransform(elements.title);\n\t\t\tif (title) {\n\t\t\t\treturn CommonProject.scripts.apps.methods.searchAnswerInCaches(title, async () => {\n\t\t\t\t\tawait $.sleep((period ?? 3) * 1000);\n\t\t\t\t\treturn defaultAnswerWrapperHandler(answererWrappers, {\n\t\t\t\t\t\ttype: ctx.type || 'unknown',\n\t\t\t\t\t\ttitle,\n\t\t\t\t\t\toptions: ctx.elements.options.map((o) => o.innerText).join('\\n')\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tthrow new Error('题目为空，请查看题目是否为空，或者忽略此题');\n\t\t\t}\n\t\t},\n\t\twork: {\n\t\t\ttype(ctx) {\n\t\t\t\tconst type = ctx.elements.type[0].textContent;\n\t\t\t\tif (type?.includes('单选题')) {\n\t\t\t\t\treturn 'single';\n\t\t\t\t} else if (type?.includes('多选题')) {\n\t\t\t\t\treturn 'multiple';\n\t\t\t\t} else if (type?.includes('判断题')) {\n\t\t\t\t\treturn 'judgement';\n\t\t\t\t} else if (type?.includes('填空')) {\n\t\t\t\t\treturn 'completion';\n\t\t\t\t} else {\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t},\n\t\t\t/** 自定义处理器 */\n\t\t\tasync handler(type, answer, option, ctx) {\n\t\t\t\tif (type === 'judgement' || type === 'single' || type === 'multiple') {\n\t\t\t\t\t// mainBg 为已经选择的选项的背景色，未选择的选项没有这个类\n\t\t\t\t\tif (option.querySelector<HTMLElement>('div.bg-mainBg') === null) {\n\t\t\t\t\t\tif (remotePage) {\n\t\t\t\t\t\t\tawait remotePage.click(option);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\toption.click();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tawait $.sleep(200);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t/**\n\t\t * 作业都是一题一题做的，不像其他自动答题一样可以获取全部试卷内容。\n\t\t * 所以只能根据自定义的状态进行搜索结果的显示。\n\t\t */\n\t\tonResultsUpdate(current, _, res) {\n\t\t\tif (current.result) {\n\t\t\t\tworkResults.push(...simplifyWorkResult([current], titleTransform));\n\t\t\t\tCommonProject.scripts.workResults.methods.setResults(workResults);\n\t\t\t\ttotalQuestionCount++;\n\t\t\t\trequestedCount++;\n\t\t\t\tresolvedCount++;\n\t\t\t}\n\n\t\t\tif (current.result?.finish) {\n\t\t\t\tCommonProject.scripts.apps.methods.addQuestionCacheFromWorkResult(\n\t\t\t\t\tsimplifyWorkResult([current], titleTransform)\n\t\t\t\t);\n\t\t\t}\n\t\t\tCommonProject.scripts.workResults.methods.updateWorkState({\n\t\t\t\ttotalQuestionCount,\n\t\t\t\trequestedCount,\n\t\t\t\tresolvedCount\n\t\t\t});\n\t\t}\n\t});\n\n\tconst getNextBtn = () =>\n\t\tArray.from(document.querySelectorAll('button')).find((btn) =>\n\t\t\tbtn.textContent?.includes('下一题')\n\t\t) as HTMLElement | null;\n\tlet next = null as HTMLElement | null;\n\n\t(async () => {\n\t\t// 从第一题开始\n\t\tconst first = document.querySelector<HTMLElement>('.grid.grid-cols-7 > button');\n\t\tif (first) {\n\t\t\tif (remotePage) await remotePage.click(first);\n\t\t\telse first.click();\n\t\t\tawait $.sleep(3000);\n\t\t}\n\t\tlet count = 0;\n\t\twhile (worker.isClose === false) {\n\t\t\tawait worker.doWork({ enable_debug: BackgroundProject.scripts.dev.cfg.enable_answerer_debug });\n\t\t\tnext = getNextBtn();\n\t\t\tif (!next) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tawait $.sleep(1000);\n\t\t\tif (remotePage) await remotePage.click(next);\n\t\t\telse next.click();\n\t\t\t// 等待题目加载\n\t\t\tawait $.sleep(1000);\n\t\t\tcount++;\n\t\t}\n\n\t\t$message.info({\n\t\t\tcontent: '作业/考试完成，请自行检查后保存或提交。',\n\t\t\tduration:\n\t\t\t\t// 题目过多则不自动关闭\n\t\t\t\tcount > 10 ? 0 : 30\n\t\t});\n\t\tworker.emit('done');\n\t\t// 答题完成后，题库选项点击才会同步题目，否则会导致题目错乱\n\t\tCommonProject.scripts.workResults.cfg.questionPositionSyncHandlerType = 'zhs-smart';\n\t})();\n\treturn worker;\n}\n\nfunction fusioncourseWork(\n\tremotePage: RemotePage | undefined,\n\t{ answererWrappers, period, thread, answerSeparators, answerMatchMode }: CommonWorkOptions\n) {\n\t$message.info({ content: '开始作业' });\n\n\tCommonProject.scripts.workResults.methods.init({\n\t\tquestionPositionSyncHandlerType: 'zhs-fusion'\n\t});\n\n\tconst titleTransform = (titles: (HTMLElement | undefined)[]) => {\n\t\treturn titles\n\t\t\t.filter((t) => t?.innerText)\n\t\t\t.map((t) => (t ? optimizationElementWithImage(t).innerText : ''))\n\t\t\t.join(',');\n\t};\n\n\tconst worker = new OCSWorker({\n\t\troot: '.exam-item',\n\t\telements: {\n\t\t\ttype: '.quest-type',\n\t\t\ttitle: '.quest-title .option-name',\n\t\t\toptions: 'label'\n\t\t},\n\t\tthread: thread ?? 1,\n\t\tanswerSeparators: answerSeparators.split(',').map((s) => s.trim()),\n\t\tanswerMatchMode: answerMatchMode,\n\t\t/** 默认搜题方法构造器 */\n\t\tanswerer: (elements, ctx) => {\n\t\t\tconst title = titleTransform(elements.title);\n\t\t\tif (title) {\n\t\t\t\treturn CommonProject.scripts.apps.methods.searchAnswerInCaches(title, async () => {\n\t\t\t\t\tawait $.sleep((period ?? 3) * 1000);\n\t\t\t\t\treturn defaultAnswerWrapperHandler(answererWrappers, {\n\t\t\t\t\t\ttype: ctx.type || 'unknown',\n\t\t\t\t\t\ttitle,\n\t\t\t\t\t\toptions: ctx.elements.options.map((o) => o.innerText).join('\\n')\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tthrow new Error('题目为空，请查看题目是否为空，或者忽略此题');\n\t\t\t}\n\t\t},\n\t\twork: {\n\t\t\ttype(ctx) {\n\t\t\t\tconst type = ctx.elements.type[0].textContent;\n\t\t\t\tif (type?.includes('单选题')) {\n\t\t\t\t\treturn 'single';\n\t\t\t\t} else if (type?.includes('多选题')) {\n\t\t\t\t\treturn 'multiple';\n\t\t\t\t} else if (type?.includes('判断题')) {\n\t\t\t\t\treturn 'judgement';\n\t\t\t\t} else if (type?.includes('填空题')) {\n\t\t\t\t\treturn 'completion';\n\t\t\t\t} else {\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t},\n\t\t\t/** 自定义处理器 */\n\t\t\tasync handler(type, answer, option, ctx) {\n\t\t\t\tif (type === 'judgement' || type === 'single' || type === 'multiple') {\n\t\t\t\t\tconst opt = option.querySelector<HTMLElement>(\n\t\t\t\t\t\t'.el-checkbox__input:not(.is-checked),.el-radio__input:not(.is-checked)'\n\t\t\t\t\t);\n\n\t\t\t\t\tif (opt) {\n\t\t\t\t\t\tif (remotePage) {\n\t\t\t\t\t\t\tawait remotePage.click(opt);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\topt.click();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tawait $.sleep(200);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * 作业都是一题一题做的，不像其他自动答题一样可以获取全部试卷内容。\n\t\t * 所以只能根据自定义的状态进行搜索结果的显示。\n\t\t */\n\t\tonResultsUpdate(current, _, res) {\n\t\t\tif (current.result) {\n\t\t\t\tCommonProject.scripts.workResults.methods.setResults(simplifyWorkResult(res, titleTransform));\n\t\t\t}\n\n\t\t\tif (current.result?.finish) {\n\t\t\t\tCommonProject.scripts.apps.methods.addQuestionCacheFromWorkResult(\n\t\t\t\t\tsimplifyWorkResult([current], titleTransform)\n\t\t\t\t);\n\t\t\t}\n\t\t\tCommonProject.scripts.workResults.methods.updateWorkStateByResults(res);\n\t\t}\n\t});\n\n\tworker\n\t\t.doWork()\n\t\t.then(async (res) => {\n\t\t\t// 如果被强制关闭，则不进行保存操作\n\t\t\tif (worker.isClose === true) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// @ts-ignore\n\t\t\tif (worker.isClose === true) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t$message.info({\n\t\t\t\tcontent: '作业/考试完成，请自行检查后保存或提交。',\n\t\t\t\t// 题目过多则不自动关闭\n\t\t\t\tduration: res.length > 10 ? 0 : 30\n\t\t\t});\n\t\t\tworker.emit('done');\n\t\t})\n\t\t.catch((err) => {\n\t\t\t$message.error({ content: '答题程序发生错误 : ' + err.message, duration: 0 });\n\t\t});\n\n\treturn worker;\n}\n\nfunction hikeWork(\n\tremotePage: RemotePage | undefined,\n\t{ answererWrappers, period, thread, answerSeparators, answerMatchMode }: CommonWorkOptions\n) {\n\t$message.info({ content: '开始作业' });\n\n\tCommonProject.scripts.workResults.methods.init({\n\t\tquestionPositionSyncHandlerType: 'zhs-hike'\n\t});\n\n\tconst titleTransform = (titles: (HTMLElement | undefined)[]) => {\n\t\treturn titles\n\t\t\t.filter((t) => t?.innerText)\n\t\t\t.map((t) => (t ? optimizationElementWithImage(t).innerText : ''))\n\t\t\t.join(',');\n\t};\n\n\tconst worker = new OCSWorker({\n\t\troot: '.q_main',\n\t\telements: {\n\t\t\ttype: '.question_score',\n\t\t\ttitle: '.question-topic',\n\t\t\toptions: 'label'\n\t\t},\n\t\tthread: thread ?? 1,\n\t\tanswerSeparators: answerSeparators.split(',').map((s) => s.trim()),\n\t\tanswerMatchMode: answerMatchMode,\n\t\t/** 默认搜题方法构造器 */\n\t\tanswerer: (elements, ctx) => {\n\t\t\tconst title = titleTransform(elements.title);\n\t\t\tif (title) {\n\t\t\t\treturn CommonProject.scripts.apps.methods.searchAnswerInCaches(title, async () => {\n\t\t\t\t\tawait $.sleep((period ?? 3) * 1000);\n\t\t\t\t\treturn defaultAnswerWrapperHandler(answererWrappers, {\n\t\t\t\t\t\ttype: ctx.type || 'unknown',\n\t\t\t\t\t\ttitle,\n\t\t\t\t\t\toptions: ctx.elements.options.map((o) => o.innerText).join('\\n')\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tthrow new Error('题目为空，请查看题目是否为空，或者忽略此题');\n\t\t\t}\n\t\t},\n\t\twork: {\n\t\t\ttype(ctx) {\n\t\t\t\tconst type = ctx.elements.type[0].textContent;\n\t\t\t\tif (type?.includes('单选题')) {\n\t\t\t\t\treturn 'single';\n\t\t\t\t} else if (type?.includes('多选题')) {\n\t\t\t\t\treturn 'multiple';\n\t\t\t\t} else if (type?.includes('判断题')) {\n\t\t\t\t\treturn 'judgement';\n\t\t\t\t} else if (type?.includes('填空题')) {\n\t\t\t\t\treturn 'completion';\n\t\t\t\t} else {\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t},\n\t\t\t/** 自定义处理器 */\n\t\t\tasync handler(type, answer, option, ctx) {\n\t\t\t\tif (type === 'judgement' || type === 'single' || type === 'multiple') {\n\t\t\t\t\tconst opt = option.querySelector<HTMLElement>(\n\t\t\t\t\t\t'.el-checkbox__input:not(.is-checked),.el-radio__input:not(.is-checked)'\n\t\t\t\t\t);\n\n\t\t\t\t\tif (opt) {\n\t\t\t\t\t\tif (remotePage) {\n\t\t\t\t\t\t\tawait remotePage.click(opt);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\topt.click();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tawait $.sleep(200);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * 作业都是一题一题做的，不像其他自动答题一样可以获取全部试卷内容。\n\t\t * 所以只能根据自定义的状态进行搜索结果的显示。\n\t\t */\n\t\tonResultsUpdate(current, _, res) {\n\t\t\tif (current.result) {\n\t\t\t\tCommonProject.scripts.workResults.methods.setResults(simplifyWorkResult(res, titleTransform));\n\t\t\t}\n\n\t\t\tif (current.result?.finish) {\n\t\t\t\tCommonProject.scripts.apps.methods.addQuestionCacheFromWorkResult(\n\t\t\t\t\tsimplifyWorkResult([current], titleTransform)\n\t\t\t\t);\n\t\t\t}\n\t\t\tCommonProject.scripts.workResults.methods.updateWorkStateByResults(res);\n\t\t}\n\t});\n\n\tconst getNextBtn = () => document.querySelector<HTMLElement>('.check_btn:not(.is-disabled)');\n\tlet next = getNextBtn();\n\tlet count = 0;\n\n\t(async () => {\n\t\t// 从第一题开始\n\t\tconst first = document.querySelector<HTMLElement>('.card_ul .card_li');\n\t\tif (first) {\n\t\t\tif (remotePage) await remotePage.click(first);\n\t\t\telse first.click();\n\t\t\tawait $.sleep(3000);\n\t\t}\n\n\t\twhile (next && worker.isClose === false) {\n\t\t\tawait worker.doWork({ enable_debug: BackgroundProject.scripts.dev.cfg.enable_answerer_debug });\n\t\t\tnext = getNextBtn();\n\t\t\tif (next) {\n\t\t\t\tawait $.sleep(1000);\n\t\t\t\tif (remotePage) await remotePage.click(next);\n\t\t\t\telse next.click();\n\t\t\t\t// 等待题目加载\n\t\t\t\tawait $.sleep(1000);\n\t\t\t\tcount++;\n\t\t\t}\n\t\t}\n\n\t\t$message.info({ content: '作业/考试完成，请自行检查后保存或提交。', duration: count > 10 ? 0 : 30 });\n\t\tworker.emit('done');\n\t\t// 答题完成后，题库选项点击才会同步题目，否则会导致题目错乱\n\t\tCommonProject.scripts.workResults.cfg.questionPositionSyncHandlerType = 'zhs-hike';\n\t})();\n\n\treturn worker;\n}\n\nfunction hikeHomework(\n\tremotePage: RemotePage | undefined,\n\t{ answererWrappers, period, thread, answerSeparators, answerMatchMode }: CommonWorkOptions\n) {\n\t$message.info({ content: '开始作业' });\n\n\t// CommonProject.scripts.workResults.methods.init({\n\t// \tquestionPositionSyncHandlerType: 'zhs-hike'\n\t// });\n\n\tconst titleTransform = (titles: (HTMLElement | undefined)[]) => {\n\t\treturn titles\n\t\t\t.filter((t) => t?.innerText)\n\t\t\t.map((t) => (t ? optimizationElementWithImage(t).innerText : ''))\n\t\t\t.join(',');\n\t};\n\n\tconst worker = new OCSWorker({\n\t\troot: '.question-item',\n\t\telements: {\n\t\t\ttype: '.title-box,.combination-title',\n\t\t\ttitle: '.qeustion-content > span, .combination-content > span',\n\t\t\toptions: '.option-item, .vditor-content'\n\t\t},\n\t\tthread: thread ?? 1,\n\t\tanswerSeparators: answerSeparators.split(',').map((s) => s.trim()),\n\t\tanswerMatchMode: answerMatchMode,\n\t\t/** 默认搜题方法构造器 */\n\t\tanswerer: (elements, ctx) => {\n\t\t\tconst title = titleTransform(elements.title);\n\t\t\tif (title) {\n\t\t\t\treturn CommonProject.scripts.apps.methods.searchAnswerInCaches(title, async () => {\n\t\t\t\t\tawait $.sleep((period ?? 3) * 1000);\n\t\t\t\t\treturn defaultAnswerWrapperHandler(answererWrappers, {\n\t\t\t\t\t\ttype: ctx.type || 'unknown',\n\t\t\t\t\t\ttitle,\n\t\t\t\t\t\toptions: ctx.elements.options.map((o) => o.innerText).join('\\n')\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tthrow new Error('题目为空，请查看题目是否为空，或者忽略此题');\n\t\t\t}\n\t\t},\n\t\twork: {\n\t\t\ttype(ctx) {\n\t\t\t\tconst type = ctx.elements.type[0].textContent;\n\t\t\t\tif (type?.includes('单选')) {\n\t\t\t\t\treturn 'single';\n\t\t\t\t} else if (type?.includes('多选')) {\n\t\t\t\t\treturn 'multiple';\n\t\t\t\t} else if (type?.includes('判断')) {\n\t\t\t\t\treturn 'judgement';\n\t\t\t\t} else if (type?.includes('问答')) {\n\t\t\t\t\treturn 'completion';\n\t\t\t\t} else {\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t},\n\t\t\t/** 自定义处理器 */\n\t\t\tasync handler(type, answer, option, ctx) {\n\t\t\t\tif (type === 'judgement' || type === 'single' || type === 'multiple') {\n\t\t\t\t\tif (remotePage) {\n\t\t\t\t\t\tawait remotePage.click(option);\n\t\t\t\t\t} else {\n\t\t\t\t\t\toption.click();\n\t\t\t\t\t}\n\t\t\t\t\tawait $.sleep(200);\n\t\t\t\t} else if (type === 'completion') {\n\t\t\t\t\tconst textarea = option.querySelector('.vditor-reset');\n\t\t\t\t\tif (textarea) textarea.innerHTML = `<p data-block=\"0\">${answer.trim()}</p>`;\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * 作业都是一题一题做的，不像其他自动答题一样可以获取全部试卷内容。\n\t\t * 所以只能根据自定义的状态进行搜索结果的显示。\n\t\t */\n\t\tonResultsUpdate(current, _, res) {\n\t\t\tif (current.result) {\n\t\t\t\tCommonProject.scripts.workResults.methods.setResults(simplifyWorkResult(res, titleTransform));\n\t\t\t}\n\n\t\t\tif (current.result?.finish) {\n\t\t\t\tCommonProject.scripts.apps.methods.addQuestionCacheFromWorkResult(\n\t\t\t\t\tsimplifyWorkResult([current], titleTransform)\n\t\t\t\t);\n\t\t\t}\n\t\t\tCommonProject.scripts.workResults.methods.updateWorkStateByResults(res);\n\t\t}\n\t});\n\n\tworker.doWork({ enable_debug: BackgroundProject.scripts.dev.cfg.enable_answerer_debug }).then(() => {\n\t\t$message.info({ content: '作业/考试完成，请自行检查后保存或提交。', duration: 0 });\n\t\tworker.emit('done');\n\t\t// 答题完成后，题库选项点击才会同步题目，否则会导致题目错乱\n\t\tCommonProject.scripts.workResults.cfg.questionPositionSyncHandlerType = 'zhs-hike';\n\t});\n\n\treturn worker;\n}\n\n/**\n * 将秒数转换为小时或分钟\n * @param second 秒\n */\nfunction optimizeSecond(second: number) {\n\tif (second > 3600) {\n\t\treturn `${Math.floor(second / 3600)}小时${Math.floor((second % 3600) / 60)}分钟`;\n\t} else if (second > 60) {\n\t\treturn `${Math.floor(second / 60)}分钟${second % 60}秒`;\n\t} else {\n\t\treturn `${second}秒`;\n\t}\n}\n\nfunction autoStop(stopTime: string) {\n\tclearInterval(state.study.stopInterval);\n\tstate.study.stopMessage?.remove();\n\tif (stopTime !== '0') {\n\t\tlet stopCount = parseFloat(stopTime) * 60 * 60;\n\t\tstate.study.stopInterval = setInterval(() => {\n\t\t\tif (stopCount > 0) {\n\t\t\t\t// 如果有弹窗验证码则暂停自动停止的计时\n\t\t\t\tif (getPopupCaptcha() === null) {\n\t\t\t\t\tstopCount--;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tclearInterval(state.study.stopInterval);\n\t\t\t\tstate.study.stop = true;\n\t\t\t\t$el<HTMLVideoElement>('video')?.pause();\n\t\t\t\t$modal.alert({ content: '脚本暂停，已获得今日平时分，如需继续观看，请刷新页面。' });\n\t\t\t}\n\t\t}, 1000);\n\t\tconst val = ZHSProject.scripts['gxk-study'].configs!.stopTime.options.find((t) => t[0] === stopTime)?.[0] || '0';\n\t\tconst date = new Date();\n\t\tdate.setMinutes(date.getMinutes() + parseFloat(val) * 60);\n\t\tstate.study.stopMessage = $message.info({\n\t\t\tduration: 0,\n\t\t\tcontent: `在 ${date.toLocaleTimeString()} 脚本将自动暂停`\n\t\t});\n\t}\n}\n/** 固定视频进度 */\nfunction fixProcessBar() {\n\tconst bar = document.querySelector<HTMLElement>('.controlsBar');\n\tif (bar) {\n\t\tbar.style.display = 'block';\n\t\t// 适配 wisdomh5\n\t\tbar.style.zIndex = '2';\n\t\tbar.style.overflow = '';\n\t}\n}\n\nfunction closeDialogRead() {\n\tconst div = document.querySelector<HTMLElement>('.dialog-read');\n\tif (div) {\n\t\tdiv.style.display = 'none';\n\t}\n}\n\nfunction finishAlert() {\n\t$modal.alert({\n\t\tcontent: '检测到当前视频全部播放完毕，如果还有未完成的视频请刷新重试，或者打开复习模式。'\n\t});\n}\n"
  },
  {
    "path": "packages/scripts/src/projects/zjy.ts",
    "content": "import { $, OCSWorker, defaultAnswerWrapperHandler } from '@ocsjs/core';\nimport { Project, Script, $ui, $el, $message, $modal, h } from 'easy-us';\nimport { volume } from '../utils/configs';\nimport { waitForMedia, waitForElement } from '../utils/study';\nimport { $msg, CommonWorkOptions, playMedia } from '../utils';\nimport { $console, BackgroundProject } from './background';\nimport { CommonProject } from './common';\nimport { commonWork, simplifyWorkResult } from '../utils/work';\n\ntype CourseType = {\n\tlevelName: string;\n\tfileType: string;\n\tid: string;\n\tcourseDesignId: string;\n\tname: string;\n};\n\nconst state = {\n\tstudying: false,\n\tstudyingId: '',\n\tmedia: null as HTMLMediaElement | null\n};\n\nconst work_pages: [string, string][] = [\n\t// 暂时不知道为什么资源库作业有两个不一样的链接\n\t['资源库keep作业页面', 'study/spockeepTest'],\n\t['资源库job作业页面', 'study/spocjobTest'],\n\t['资源库考试', 'study/spoctest'],\n\t['作业页面', 'icve-study/coursePreview/jobTes'],\n\t['考试页面', 'icve-study/coursePreview/test'],\n\t['考试页面', 'icve-study/test'],\n\t['资源库测验页面', 'icve-study/coursePreview/keepTest']\n];\n\nconst isWork = () => {\n\treturn (\n\t\twindow.location.href.includes('icve-study/coursePreview/jobTes') ||\n\t\twindow.location.href.includes('icve-study/coursePreview/keepTest') ||\n\t\twindow.location.href.includes('study/spockeepTest') ||\n\t\twindow.location.href.includes('study/spocjobTest')\n\t);\n};\nconst isExam = () => {\n\treturn (\n\t\twindow.location.href.includes('icve-study/coursePreview/test') ||\n\t\twindow.location.href.includes('icve-study/test') ||\n\t\twindow.location.href.includes('study/spoctest')\n\t);\n};\n\n/**\n * 职教云网课\n *\n * 因为存在子 iframe 并且 ppt 跨域的情况\n * 所以采用新建小窗口的形式，通过子 window 以及 opener 的形式进行互相回调调用\n * 所以核心逻辑代码可能会比较绕。\n *\n * 为什么不在学习页面写脚本，而是 课程学习 和 学习页面 两个脚本进行交互运行？\n * 因为学习页面无法获取学习进度，这样可能导致已学课程重复学习。\n *\n */\nexport const ZJYProject = Project.create({\n\tname: '职教云',\n\tdomains: ['icve.com.cn', 'zjy2.icve.com.cn', 'zyk.icve.com.cn'],\n\tscripts: {\n\t\tguide: new Script({\n\t\t\tname: '🖥️ 使用提示',\n\t\t\tmatches: [\n\t\t\t\t['学习页面', 'zjy2.icve.com.cn/study'],\n\t\t\t\t['资源库', 'zyk.icve.com.cn/icve-study/']\n\t\t\t],\n\t\t\tnamespace: 'zjy.study.guide',\n\t\t\tconfigs: {\n\t\t\t\tnotes: {\n\t\t\t\t\tdefaultValue: '请点击任意章节，进入学习。'\n\t\t\t\t}\n\t\t\t}\n\t\t}),\n\t\tv2: new Script({\n\t\t\tname: '旧版切换器',\n\t\t\tmatches: [['新版智慧职教', 'zjy2.icve.com.cn/study/v2/']],\n\t\t\thideInPanel: true,\n\t\t\toncomplete() {\n\t\t\t\t$msg.info('脚本只支持旧版职教云，即将跳转到旧版职教云页面...');\n\t\t\t\t$modal.alert({\n\t\t\t\t\ttitle: '提示',\n\t\t\t\t\tcontent: '脚本只支持旧版职教云，即将跳转到<b>旧版</b>职教云页面...',\n\t\t\t\t\tmaskCloseable: false\n\t\t\t\t});\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\tlocation.href = '/study/index';\n\t\t\t\t}, 5000);\n\t\t\t}\n\t\t}),\n\t\tdispatcher: new Script({\n\t\t\tname: '调度器',\n\t\t\tmatches: [\n\t\t\t\t['学习页面', 'zjy2.icve.com.cn/study'],\n\t\t\t\t['资源库', 'zyk.icve.com.cn/icve-study/'],\n\t\t\t\t/**\n\t\t\t\t * 这个页面需要手动选择时间查找并进入，课程里面无连串课程查找，只能在当前页面整理\n\t\t\t\t */\n\t\t\t\t['内容资源页面', 'zjy2.icve.com.cn/study/studentFast/classroomNow'],\n\t\t\t\t['在线课堂学习页面', 'zjy2.icve.com.cn/study/studentFast/courseware']\n\t\t\t],\n\t\t\thideInPanel: true,\n\t\t\tmethods() {\n\t\t\t\treturn {\n\t\t\t\t\tdispatch: async () => {\n\t\t\t\t\t\tif (['zjy2.icve.com.cn/study/studentFast/classroomNow'].some((i) => window.location.href.includes(i))) {\n\t\t\t\t\t\t\t/**\n\t\t\t\t\t\t\t * 先在在线课堂内容获取课程数据，然后用户进入课程后看完视频读取数据进行下一章\n\t\t\t\t\t\t\t */\n\t\t\t\t\t\t\tawait waitForElement('.classroom_activities .active_list');\n\t\t\t\t\t\t\tconst courseData = getCourseDataInClassroomNowPage();\n\t\t\t\t\t\t\tconsole.log(courseData);\n\t\t\t\t\t\t\t// @ts-ignore\n\t\t\t\t\t\t\tconst courseId = document.querySelector('.teacherLayout')?.__vue__?.courseInfo?.id || '';\n\t\t\t\t\t\t\tif (!courseData || !courseId) {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tZJYProject.scripts.study.cfg.currentCourseId = courseId;\n\t\t\t\t\t\t\tZJYProject.scripts.study.cfg.courseList = courseData;\n\t\t\t\t\t\t\t$message.success('课程数据获取成功，请点击课程章节开始学习');\n\t\t\t\t\t\t} else if (\n\t\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t'zyk.icve.com.cn/icve-study/coursePreview/courseware',\n\t\t\t\t\t\t\t\t'zjy2.icve.com.cn/study/coursePreview/spoccourseIndex/courseware',\n\t\t\t\t\t\t\t\t'zjy2.icve.com.cn/study/studentFast/courseware'\n\t\t\t\t\t\t\t].some((i) => window.location.href.includes(i))\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tconst isClassroomNowStudy = location.href.includes('zjy2.icve.com.cn/study/studentFast/courseware');\n\t\t\t\t\t\t\tZJYProject.scripts.study.methods.main(isClassroomNowStudy ? 'classroomNow' : 'normal');\n\t\t\t\t\t\t} else if (work_pages.map(([_, p]) => p).some((i) => window.location.href.includes(i))) {\n\t\t\t\t\t\t\tZJYProject.scripts.work.methods.main();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t},\n\t\t\t/**\n\t\t\t *\n\t\t\t * 新版职教云采用VUE技术路由，所以这里需要使用 onhistorychange 监听路由变化，然后脚本中自行判断相应的路由执行情况\n\t\t\t */\n\t\t\tonhistorychange(type) {\n\t\t\t\tif (type === 'push') {\n\t\t\t\t\tthis.methods.dispatch();\n\t\t\t\t}\n\t\t\t},\n\t\t\toncomplete() {\n\t\t\t\tthis.methods.dispatch();\n\t\t\t}\n\t\t}),\n\t\tstudy: new Script({\n\t\t\tmatches: [\n\t\t\t\t['学习页面', 'zjy2.icve.com.cn/study/coursePreview/spoccourseIndex/courseware'],\n\t\t\t\t/** classroomNow */\n\t\t\t\t['在线课堂学习页面', 'zjy2.icve.com.cn/studentFast/courseware'],\n\t\t\t\t['资源库学习页面', 'zyk.icve.com.cn/icve-study/coursePreview/courseware']\n\t\t\t],\n\t\t\tname: '✍️ 课程学习',\n\t\t\tnamespace: 'zjy.study.main',\n\t\t\tconfigs: {\n\t\t\t\tnotes: {\n\t\t\t\t\tdefaultValue: $ui.notes([\n\t\t\t\t\t\t['如果脚本卡死或者您不想学习，', '可以点击其他任意章节继续进行学习。'],\n\t\t\t\t\t\t'提示：职教云无法使用倍速。'\n\t\t\t\t\t]).outerHTML\n\t\t\t\t},\n\t\t\t\tvolume: volume,\n\t\t\t\tplaybackRate: {\n\t\t\t\t\tlabel: '视频倍速',\n\t\t\t\t\ttag: 'select',\n\t\t\t\t\toptions: [1, 1.25, 1.5, 1.75, 2, 2.25, 2.5, 2.75, 3, 3.5, 4, 6, 8].map((rate) => [\n\t\t\t\t\t\trate.toString(),\n\t\t\t\t\t\trate + ' x'\n\t\t\t\t\t]),\n\t\t\t\t\tdefaultValue: '1'\n\t\t\t\t},\n\t\t\t\tpptReadPeriod: {\n\t\t\t\t\tlabel: 'PPT 阅读每页停留时间（秒）',\n\t\t\t\t\tdefaultValue: 1,\n\t\t\t\t\tattrs: { type: 'number', min: 1, step: 1, max: 10 }\n\t\t\t\t},\n\t\t\t\tcurrentCourseId: {\n\t\t\t\t\tdefaultValue: ''\n\t\t\t\t},\n\t\t\t\tcourseList: {\n\t\t\t\t\tdefaultValue: [] as CourseType[]\n\t\t\t\t}\n\t\t\t},\n\t\t\tmethods() {\n\t\t\t\treturn {\n\t\t\t\t\tmain: async (type: 'classroomNow' | 'normal') => {\n\t\t\t\t\t\tconst id = new URL(window.location.href).searchParams.get(type === 'classroomNow' ? 'activityId' : 'id');\n\n\t\t\t\t\t\tif (!id) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (state.studying && id === state.studyingId) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tstate.studyingId = id;\n\t\t\t\t\t\tstate.studying = true;\n\n\t\t\t\t\t\t// 置顶页面\n\t\t\t\t\t\tCommonProject.scripts.render.methods.pin(this);\n\n\t\t\t\t\t\tthis.onConfigChange('volume', (val) => {\n\t\t\t\t\t\t\tif (state.media) {\n\t\t\t\t\t\t\t\tstate.media.volume = parseFloat(val.toString());\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tthis.onConfigChange('playbackRate', (val) => {\n\t\t\t\t\t\t\tif (state.media) {\n\t\t\t\t\t\t\t\tstate.media.playbackRate = parseFloat(val.toString());\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tawait waitForLoad();\n\n\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\t// 删除是否继续学习的弹窗\n\t\t\t\t\t\t\t$el('.el-message-box__wrapper')?.remove();\n\t\t\t\t\t\t\t$el('.v-modal')?.remove();\n\t\t\t\t\t\t}, 3000);\n\n\t\t\t\t\t\tawait waitForLoad();\n\t\t\t\t\t\t// 加载课程数据\n\t\t\t\t\t\tif (type === 'normal') {\n\t\t\t\t\t\t\tconst courseId = getUniqueCourseId();\n\t\t\t\t\t\t\tif (!courseId) {\n\t\t\t\t\t\t\t\t$message.error({ content: '获取课程数据失败，请手动刷新页面' });\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst not_same_class =\n\t\t\t\t\t\t\t\t!ZJYProject.scripts.study.cfg.currentCourseId ||\n\t\t\t\t\t\t\t\tZJYProject.scripts.study.cfg.currentCourseId !== courseId;\n\n\t\t\t\t\t\t\t// 如果课程不一致，或者没有课程数据，则重新获取课程数据\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\tnot_same_class ||\n\t\t\t\t\t\t\t\t!ZJYProject.scripts.study.cfg.courseList ||\n\t\t\t\t\t\t\t\tZJYProject.scripts.study.cfg.courseList.length === 0\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tconst courseData = await getCourseData();\n\t\t\t\t\t\t\t\tif (!courseData) {\n\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tZJYProject.scripts.study.cfg.currentCourseId = courseId;\n\t\t\t\t\t\t\t\tZJYProject.scripts.study.cfg.courseList = courseData;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst courseInfo = ZJYProject.scripts.study.cfg.courseList.find((i) => i.id === id);\n\t\t\t\t\t\tif (!courseInfo) {\n\t\t\t\t\t\t\tconst btn = h('button', { className: 'base-style-button' }, '修复数据');\n\t\t\t\t\t\t\tbtn.onclick = async () => {\n\t\t\t\t\t\t\t\tconst courseId = getUniqueCourseId();\n\t\t\t\t\t\t\t\tif (!courseId) {\n\t\t\t\t\t\t\t\t\t$message.error({ content: '获取课程数据失败！' });\n\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tconst courseData = await getCourseData();\n\t\t\t\t\t\t\t\tif (!courseData) {\n\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tZJYProject.scripts.study.cfg.currentCourseId = courseId;\n\t\t\t\t\t\t\t\tZJYProject.scripts.study.cfg.courseList = courseData;\n\t\t\t\t\t\t\t\t$modal.simple({\n\t\t\t\t\t\t\t\t\ttitle: '提示',\n\t\t\t\t\t\t\t\t\tcontent: '数据已修复完毕，请刷新页面重新尝试运行。'\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tconst err = '获取课程信息失败，请手动刷新页面，或者尝试修复数据：';\n\t\t\t\t\t\t\t$message.error({ content: h('span', [err, btn]), duration: 0 });\n\t\t\t\t\t\t\t$console.error(err);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t/**\n\t\t\t\t\t\t * courseType 在类型为文件夹+附件形式（附件为视频）时，显示混乱类型比如：courseType: 知识点讲解\n\t\t\t\t\t\t * 此时从页面获取的 curType 反而是正确的 video 类型\n\t\t\t\t\t\t */\n\t\t\t\t\t\tconst vue = getVueBindElement();\n\t\t\t\t\t\tconst courseType = vue.curType === 'video' ? 'video' : courseInfo?.fileType || '';\n\n\t\t\t\t\t\tconst started_url = window.location.href;\n\t\t\t\t\t\tlet msg = '开始学习：' + courseType + '-' + courseInfo.name;\n\t\t\t\t\t\t$message.success(msg);\n\t\t\t\t\t\t$console.info(msg);\n\t\t\t\t\t\tif (['ppt', 'doc', 'pptx', 'docx', 'pdf', 'txt', 'ppt文档', 'xls', 'xlsx'].some((i) => courseType === i)) {\n\t\t\t\t\t\t\tawait watchFile(this.cfg.pptReadPeriod);\n\t\t\t\t\t\t} else if (['video', 'audio', 'mp4', 'mp3', 'flv', '视频'].some((i) => courseType === i)) {\n\t\t\t\t\t\t\tconst text = $el('.guide')?.textContent || '';\n\t\t\t\t\t\t\tmsg = `任务点 ${courseInfo.name}，不支持播放。`;\n\t\t\t\t\t\t\tif (text.includes('很抱歉，您的浏览器不支持播放此类文件') || text.includes('此视频暂无法播放')) {\n\t\t\t\t\t\t\t\tmsg = `任务点 ${courseInfo.name}，不支持播放。`;\n\t\t\t\t\t\t\t\t$message.error(msg);\n\t\t\t\t\t\t\t\t$console.error(msg);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tawait watchMedia();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (['png', 'jpg', '图片'].some((i) => courseType === i)) {\n\t\t\t\t\t\t\tmsg = `已查看图片任务点 ${courseInfo.name}，即将跳过。`;\n\t\t\t\t\t\t\t$message.info(msg);\n\t\t\t\t\t\t\t$console.info(msg);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tmsg = `未知的任务点 ${courseInfo.name}，类型 ${courseType}，请跟作者进行反馈。`;\n\t\t\t\t\t\t\t$message.error(msg);\n\t\t\t\t\t\t\t$console.error(msg);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (started_url === window.location.href) {\n\t\t\t\t\t\t\tmsg = '任务点结束，五秒后下一章';\n\t\t\t\t\t\t\t$message.warn('如果职教云一直卡在显示：“资源类型无法学习，请核对数据！” 请手动切换下一章。');\n\t\t\t\t\t\t\t$message.info(msg);\n\t\t\t\t\t\t\t$console.info(msg);\n\t\t\t\t\t\t\tawait $.sleep(5000);\n\t\t\t\t\t\t\tawait next(type);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t}\n\t\t}),\n\t\twork: new Script({\n\t\t\tmatches: work_pages,\n\t\t\tname: '✍️ 作业考试脚本',\n\t\t\tnamespace: 'zjy.work.main',\n\t\t\tconfigs: {\n\t\t\t\tnotes: {\n\t\t\t\t\tdefaultValue: $ui.notes([\n\t\t\t\t\t\t'自动答题前请在 “通用-全局设置” 中设置题库配置。',\n\t\t\t\t\t\t'可以搭配 “通用-在线搜题” 一起使用。',\n\t\t\t\t\t\t'请手动进入作业考试页面才能使用自动答题。'\n\t\t\t\t\t]).outerHTML\n\t\t\t\t}\n\t\t\t},\n\t\t\tmethods() {\n\t\t\t\treturn {\n\t\t\t\t\tmain: async () => {\n\t\t\t\t\t\tif (isWork() || isExam()) {\n\t\t\t\t\t\t\tawait waitForQuestions();\n\n\t\t\t\t\t\t\tcommonWork(this, {\n\t\t\t\t\t\t\t\tworkerProvider: (opt) => workOrExam(isWork() ? 'work' : 'exam', opt)\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t}\n\t\t})\n\t}\n});\n\nasync function watchMedia() {\n\tconst media = await waitForMedia();\n\tmedia.volume = parseFloat(ZJYProject.scripts.study.cfg.volume.toString());\n\tmedia.playbackRate = parseFloat(ZJYProject.scripts.study.cfg.playbackRate.toString());\n\tstate.media = media;\n\tconst success = await playMedia(() => media.play());\n\tif (!success) {\n\t\treturn;\n\t}\n\n\treturn new Promise<void>((resolve, reject) => {\n\t\tmedia.addEventListener('ended', () => {\n\t\t\tresolve();\n\t\t});\n\n\t\tmedia.addEventListener('pause', () => {\n\t\t\tsetTimeout(() => {\n\t\t\t\tif (media.ended) {\n\t\t\t\t\tresolve();\n\t\t\t\t} else if (media.paused) {\n\t\t\t\t\tmedia.play();\n\t\t\t\t\tmedia.volume = parseFloat(ZJYProject.scripts.study.cfg.volume.toString());\n\t\t\t\t\tmedia.playbackRate = parseFloat(ZJYProject.scripts.study.cfg.playbackRate.toString());\n\t\t\t\t}\n\t\t\t}, 1000);\n\t\t});\n\t});\n}\n\nasync function watchFile(pptReadPeriod: number) {\n\tconst vue = getPPTVueBindElement();\n\tif (!vue) {\n\t\treturn;\n\t}\n\n\twhile (true) {\n\t\tconst [current, total] =\n\t\t\tdocument\n\t\t\t\t.querySelector('.preview .page')\n\t\t\t\t?.textContent?.trim()\n\t\t\t\t// 旧版PPT任务，新版没有上一页和下一页\n\t\t\t\t.replace('上一页', '')\n\t\t\t\t.replace('下一页', '')\n\t\t\t\t.split('/')\n\t\t\t\t.map((i) => parseInt(i.trim())) || [];\n\t\tif (!current || !total) {\n\t\t\tbreak;\n\t\t}\n\t\tif (current >= total) {\n\t\t\tbreak;\n\t\t}\n\t\t// 旧版PPT任务，新版使用 skip\n\t\ttry {\n\t\t\tvue.next && vue.next();\n\t\t} catch {}\n\t\ttry {\n\t\t\tvue.skip && vue.skip();\n\t\t} catch {}\n\n\t\tawait $.sleep(pptReadPeriod * 1000);\n\t}\n}\n\n// 资源库和新职教云的数据都一样的\n// 资源库的课程可直接获取\n// 新职教云的课程数据需要每个列表展开才能读取到\nfunction getUniqueCourseId() {\n\t// @ts-ignore\n\treturn document.querySelector('.coursePreviewIndex')?.__vue__?.list?.[0]?.courseId || '';\n}\n\nfunction isZyk() {\n\treturn location.href.includes('zyk.icve.com.cn');\n}\n\nfunction getVueBindElement() {\n\treturn $el('.guide')?.__vue__ || $el('.teach')?.__vue__;\n}\n\nfunction getPPTVueBindElement() {\n\t/**\n\t * 2025/11月新PPT，使用 FilePreview 获取\n\t */\n\n\treturn $el('.FilePreview')?.__vue__ || $el('.guide')?.__vue__ || $el('.teach')?.__vue__;\n}\n\nasync function next(type: 'classroomNow' | 'normal') {\n\t/**\n\t * activityId 属于老师在线课堂 classroomNow 页面跳转参数\n\t */\n\tconst field = type === 'classroomNow' ? 'activityId' : 'id';\n\tconst id = new URL(window.location.href).searchParams.get(field);\n\tlet nextObject: CourseType | undefined;\n\tconst data = ZJYProject.scripts.study.cfg.courseList;\n\tconst start_index = data.findIndex((i) => i.id === id);\n\tfor (let index = start_index + 1; index < data.length; index++) {\n\t\tconst item = data[index];\n\t\t// 跳过讨论\n\t\tif (['测验', '讨论'].some((i) => item.fileType === i)) {\n\t\t\tcontinue;\n\t\t}\n\t\tnextObject = item;\n\t\tbreak;\n\t}\n\n\tif (id && nextObject) {\n\t\t// .teach 是新职教云思维导图任务点页面的VUE数据绑定点，无法通过 .guide 获取\n\t\tconst vue = getVueBindElement();\n\t\t// 如果有 nextObj 数据则代表可以点击下一节按钮，否则需要根据全局数据去进行查找跳转\n\t\t// 使用自带的跳转功能更加兼容，防止数据错乱（使用url跳转学习记录可能会不一致）\n\t\t// 这里根据判断ID是否相同，否则强制跳过讨论或者测验\n\t\tif (vue?.nextObj?.id && nextObject.id === vue.nextObj.id) {\n\t\t\tvue.preNext(vue.nextObj);\n\t\t\treturn;\n\t\t}\n\n\t\tawait $.sleep(3000);\n\t\tconst url = new URL(window.location.href);\n\t\tif (nextObject.courseDesignId) {\n\t\t\turl.searchParams.set('courseDesignId', nextObject.courseDesignId);\n\t\t}\n\t\turl.searchParams.set(field, nextObject.id);\n\t\twindow.location.replace(url.href);\n\t} else {\n\t\t$message.success({\n\t\t\tduration: 0,\n\t\t\tcontent: '全部任务已完成。'\n\t\t});\n\t\t$console.info('全部任务已完成。');\n\t\tCommonProject.scripts.settings.methods.notificationBySetting('全部任务点已完成！', {\n\t\t\tduration: 0,\n\t\t\textraTitle: '职教云学习脚本'\n\t\t});\n\t\tstate.studying = false;\n\t}\n}\n\nfunction getCourseDataInClassroomNowPage() {\n\t// @ts-ignore\n\tconst list = document.querySelector('.classroom_activities')?.__vue__?.activeList || [];\n\tconst data: CourseType[] = [];\n\tconst temp = JSON.parse(JSON.stringify(list));\n\twhile (temp.length > 0) {\n\t\tconst item = temp.shift();\n\n\t\tif (item?.children?.length > 0) {\n\t\t\ttemp.unshift(...item.children);\n\t\t} else {\n\t\t\tdata.push({\n\t\t\t\tname: item.title,\n\t\t\t\tid: item.activityId,\n\t\t\t\tfileType: item.fileType,\n\t\t\t\tcourseDesignId: item.courseDesignId,\n\t\t\t\tlevelName: item.levelName || ''\n\t\t\t});\n\t\t}\n\t}\n\treturn data;\n}\n\nasync function getCourseData() {\n\tconst getDataList = () => {\n\t\t// @ts-ignore\n\t\tconst list = document.querySelector('.coursePreviewIndex')?.__vue__?.list || [];\n\t\tconst data: CourseType[] = [];\n\t\tconst temp = JSON.parse(JSON.stringify(list));\n\t\twhile (temp.length > 0) {\n\t\t\tconst item = temp.shift();\n\t\t\tif (item?.children?.length > 0) {\n\t\t\t\ttemp.unshift(...item.children);\n\t\t\t} else {\n\t\t\t\tdata.push({\n\t\t\t\t\tname: item.name,\n\t\t\t\t\tid: item.id,\n\t\t\t\t\tfileType: item.fileType,\n\t\t\t\t\tlevelName: item.levelName || '',\n\t\t\t\t\tcourseDesignId: item.courseDesignId || ''\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\treturn data;\n\t};\n\n\t// 资源库的课程可直接获取\n\t// 新职教云的课程数据需要每个列表展开才能读取到\n\tif (isZyk() === false) {\n\t\tconst progress = h('div');\n\t\tconst modal_content = h('div', [\n\t\t\th('div', { className: 'notes card' }, [\n\t\t\t\t$ui.notes([\n\t\t\t\t\t'职教云由于大章节之间无自动下一节按钮，需要在课程开始前',\n\t\t\t\t\t'由程序读取全部章节数据，这样才能自动运行',\n\t\t\t\t\t'数据只需读取一遍即可，后续无需重新读取'\n\t\t\t\t])\n\t\t\t]),\n\t\t\tprogress\n\t\t]);\n\t\tlet force_pause = false;\n\t\tconst modal = $modal.confirm({\n\t\t\tcontent: modal_content,\n\t\t\tmaskCloseable: false,\n\t\t\ttitle: '正在获取课程数据中，请勿操作...',\n\t\t\tconfirmButton: null,\n\t\t\tcancelButtonText: '强制暂停',\n\t\t\tonCancel() {\n\t\t\t\tforce_pause = true;\n\t\t\t}\n\t\t});\n\n\t\tconst kejianListEl = document.querySelector<HTMLElement>('.kejianList');\n\t\tif (!kejianListEl) {\n\t\t\t$message.error({ content: '获取课程数据失败，请手动刷新页面' });\n\t\t\treturn undefined;\n\t\t}\n\t\tif (kejianListEl.style.display === 'none') {\n\t\t\tArray.from(document.querySelectorAll<HTMLElement>('.courseBtn div.customBtn'))\n\t\t\t\t.find((el) => el.textContent?.includes('课件目录'))\n\t\t\t\t?.click();\n\t\t\tawait $.sleep(1000);\n\t\t}\n\n\t\t// 持续递归获取课程数据，直到获取完成为止\n\t\tconst folders: HTMLElement[] = [];\n\t\twhile (true) {\n\t\t\tconst itemsElList = Array.from(document.querySelectorAll<HTMLElement>('.items'));\n\t\t\tconst unsaved = itemsElList.find((item) => folders.includes(item) === false);\n\t\t\tif (!unsaved) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tconst list = getDataList();\n\t\t\t// 不是文件夹不点\n\t\t\tconst course_info = list.find(\n\t\t\t\t(item) =>\n\t\t\t\t\t// 子章节中间有空格拼接， 大章节没有\n\t\t\t\t\t`${item.levelName || ''}${item.name}`.replace(/\\s/g, '') ===\n\t\t\t\t\t(unsaved.textContent?.trim().replace(/\\s/g, '') || '')\n\t\t\t);\n\n\t\t\tif (!course_info || ['父节点', '子节点'].includes(course_info.fileType) === false) {\n\t\t\t\tfolders.push(unsaved);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (force_pause) {\n\t\t\t\tconst err = '已强制暂停，请手动刷新页面后才能重新运行';\n\t\t\t\t$message.error({ content: err, duration: 0 });\n\t\t\t\t$modal.alert({ content: err });\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t\tfolders.push(unsaved);\n\t\t\tif (modal) {\n\t\t\t\t// iChild 为资源库类\n\t\t\t\t// fIteml 为职教云类\n\t\t\t\tprogress.innerHTML = '<br><b>当前已获取 ' + document.querySelectorAll('.fIteml,.iChild').length + ' 个小节</b>';\n\t\t\t}\n\t\t\tunsaved.click();\n\n\t\t\tawait $.sleep(1000);\n\t\t}\n\t\tmodal?.remove();\n\t}\n\n\treturn getDataList();\n}\n\nfunction waitForLoad() {\n\treturn waitForElement(() => getVueBindElement());\n}\n\n/**\n * 等待试卷作业加载\n */\nasync function waitForQuestions() {\n\treturn waitForElement('.subjectList');\n}\n\nfunction workOrExam(\n\ttype: 'work' | 'exam',\n\t{ answererWrappers, period, thread, answerSeparators, answerMatchMode }: CommonWorkOptions\n) {\n\t$message.info({ content: '开始作业' });\n\tCommonProject.scripts.workResults.methods.init({\n\t\tquestionPositionSyncHandlerType: 'zjy'\n\t});\n\n\tconst titleTransform = (titles: (HTMLElement | undefined)[]) => {\n\t\treturn titles\n\t\t\t.filter((t) => t?.innerText)\n\t\t\t.map((t) => t?.innerText)\n\t\t\t.join(',');\n\t};\n\n\tconst worker = new OCSWorker({\n\t\troot: '.subjectDet',\n\t\telements: {\n\t\t\ttitle: type === 'work' ? 'h2,h3,h4,h5,h6' : '.titleTest span:not(.xvhao)',\n\t\t\toptions: '.optionList div , .tkInput .el-input, .tkInput .el-textarea'\n\t\t},\n\t\tthread: thread ?? 1,\n\t\tanswerSeparators: answerSeparators.split(',').map((s) => s.trim()),\n\t\tanswerMatchMode: answerMatchMode,\n\t\t/** 默认搜题方法构造器 */\n\t\tanswerer: (elements, ctx) => {\n\t\t\tconst title = titleTransform(elements.title);\n\t\t\tif (title) {\n\t\t\t\treturn CommonProject.scripts.apps.methods.searchAnswerInCaches(title, async () => {\n\t\t\t\t\tawait $.sleep((period ?? 3) * 1000);\n\t\t\t\t\treturn defaultAnswerWrapperHandler(answererWrappers, {\n\t\t\t\t\t\ttype: ctx.type || 'unknown',\n\t\t\t\t\t\ttitle,\n\t\t\t\t\t\toptions: ctx.elements.options.map((o) => o.innerText).join('\\n')\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tthrow new Error('题目为空，请查看题目是否为空，或者忽略此题');\n\t\t\t}\n\t\t},\n\t\twork: {\n\t\t\ttype(ctx) {\n\t\t\t\tconst options = ctx.elements.options;\n\n\t\t\t\tconst radio_len = options\n\t\t\t\t\t.map((o) => o.querySelector('[type=\"radio\"]'))\n\t\t\t\t\t.reduce((a, b) => {\n\t\t\t\t\t\treturn a + (b ? 1 : 0);\n\t\t\t\t\t}, 0);\n\n\t\t\t\treturn radio_len > 0\n\t\t\t\t\t? radio_len === 2\n\t\t\t\t\t\t? 'judgement'\n\t\t\t\t\t\t: 'single'\n\t\t\t\t\t: options.some((o) => o.querySelector('[type=\"checkbox\"]'))\n\t\t\t\t\t? 'multiple'\n\t\t\t\t\t: options.some((o) => o.querySelector('[type=\"text\"]')) || options.some((o) => o.querySelector('textarea'))\n\t\t\t\t\t? 'completion'\n\t\t\t\t\t: undefined;\n\t\t\t},\n\t\t\t/** 自定义处理器 */\n\t\t\thandler(type, answer, option, ctx) {\n\t\t\t\tif (type === 'judgement' || type === 'single' || type === 'multiple') {\n\t\t\t\t\t// 这里只用判断多选题是否选中，如果选中就不用再点击了，单选题是 radio，所以不用判断。\n\t\t\t\t\tif (option.querySelector('input')?.checked !== true) {\n\t\t\t\t\t\toption.querySelector('label')?.click();\n\t\t\t\t\t}\n\t\t\t\t} else if (type === 'completion' && answer.trim()) {\n\t\t\t\t\tconst text = option.querySelector<HTMLInputElement>('input[type=\"text\"]');\n\t\t\t\t\tconst textarea = option.querySelector<HTMLTextAreaElement>('textarea');\n\t\t\t\t\tif (text) {\n\t\t\t\t\t\ttext.value = answer;\n\t\t\t\t\t\ttext.dispatchEvent(new Event('input', { bubbles: true }));\n\t\t\t\t\t} else if (textarea) {\n\t\t\t\t\t\ttextarea.value = answer;\n\t\t\t\t\t\ttextarea.dispatchEvent(new Event('input', { bubbles: true }));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t/** 完成答题后 */\n\t\tonResultsUpdate(curr, _, res) {\n\t\t\tCommonProject.scripts.workResults.methods.setResults(simplifyWorkResult(res, titleTransform));\n\n\t\t\tif (curr.result?.finish) {\n\t\t\t\tCommonProject.scripts.apps.methods.addQuestionCacheFromWorkResult(simplifyWorkResult([curr], titleTransform));\n\t\t\t}\n\t\t\tCommonProject.scripts.workResults.methods.updateWorkStateByResults(res);\n\t\t}\n\t});\n\n\tworker\n\t\t.doWork({ enable_debug: BackgroundProject.scripts.dev.cfg.enable_answerer_debug })\n\t\t.then(() => {\n\t\t\t$message.info({ content: '作业/考试完成，请自行检查后保存或提交。', duration: 0 });\n\t\t\tworker.emit('done');\n\t\t})\n\t\t.catch((err) => {\n\t\t\t$message.error({ content: `作业/考试失败: ${err}`, duration: 0 });\n\t\t});\n\n\treturn worker;\n}\n"
  },
  {
    "path": "packages/scripts/src/render.ts",
    "content": "import { createRenderScript } from 'easy-us';\n\nexport const RenderScript = createRenderScript({\n\tname: '🖼️ 窗口设置'\n});\n"
  },
  {
    "path": "packages/scripts/src/utils/app.ts",
    "content": "import { $message, $modal, h } from 'easy-us';\n\nexport const $playwright = {\n\tshowError: () => {\n\t\tconst href = 'https://docs.ocsjs.com/docs/script-helper';\n\t\tconst errorEl = h('div', [\n\t\t\t'当前页面需要下载OCS桌面端，并在桌面端中新建浏览器，在新建的浏览器中才能进行正常刷课，点击链接查看详情 => ',\n\t\t\th('a', { href: href, target: '_blank' }, href)\n\t\t]);\n\t\t$modal.alert({\n\t\t\tmaskCloseable: false,\n\t\t\ttitle: '⛔ 错误',\n\t\t\tconfirmButtonText: '查看详情',\n\t\t\tcontent: errorEl.cloneNode(true),\n\t\t\tonConfirm() {\n\t\t\t\twindow.open(href, '_blank');\n\t\t\t}\n\t\t});\n\t\t$message.error({ content: errorEl.cloneNode(true), duration: 0 });\n\t}\n};\n"
  },
  {
    "path": "packages/scripts/src/utils/configs.ts",
    "content": "import { $ui, Config } from 'easy-us';\nimport { createRangeTooltip } from '.';\n\n/**\n * 可复用的配置参数\n */\n\n/**\n * 倍速设置\n */\nexport const playbackRate: Config = {\n\tlabel: '视频倍速',\n\ttag: 'select',\n\toptions: [1, 1.25, 1.5, 1.75, 2, 2.25, 2.5, 2.75, 3, 3.5, 4, 6, 8, 16].map((rate) => [rate.toString(), rate + ' x']),\n\tdefaultValue: '1'\n};\n\n/** 音量调节配置  */\nexport const volume: Config<any, number> = {\n\tlabel: '音量调节',\n\tattrs: { type: 'range', step: '0.05', min: '0', max: '1' },\n\tdefaultValue: 0,\n\tonload() {\n\t\tcreateRangeTooltip(this, '0', (val) => `${parseFloat(val) * 100}%`);\n\t}\n};\n\n/** 复习模式配置 */\nexport const restudy: Config<any, boolean> = {\n\tlabel: '复习模式',\n\tattrs: { title: '已经完成的视频继续学习', type: 'checkbox' },\n\tdefaultValue: false\n};\n\n/** 清晰度配置 */\nexport const definition: Config<any, 'line1bq' | 'line1gq'> = {\n\tlabel: '清晰度',\n\ttag: 'select',\n\tdefaultValue: 'line1bq',\n\toptions: [\n\t\t['line1bq', '流畅'],\n\t\t['line1gq', '高清']\n\t]\n};\n\n/** 开启自动答题 */\nexport const auto: Config<any, boolean> = {\n\tlabel: '开启自动答题',\n\tattrs: { type: 'checkbox' },\n\tdefaultValue: false\n};\n\n/** 答题提示 */\nexport const workNotes: Config<any, string> = {\n\tdefaultValue: $ui.notes([\n\t\t'自动答题前请在 “通用-全局设置” 中设置题库配置。',\n\t\t'可以搭配 “通用-在线搜题” 一起使用。',\n\t\t'⚠️禁止同时开多个作业/考试页面。'\n\t]).outerHTML\n};\n\nexport const dropdownStyle: Omit<Config<any, string>, 'defaultValue'> = {\n\tlabelClassName: 'checkbox-label',\n\tproviderClassName: 'checkbox-input',\n\tenableForAttribute: true,\n\tonload(el) {\n\t\t// @ts-ignore\n\t\tconst checked = this.checked;\n\t\tel.classList.toggle('checked', checked);\n\t\tthis.addEventListener('change', () => {\n\t\t\t// @ts-ignore\n\t\t\tel.classList.toggle('checked', this.checked);\n\t\t});\n\t}\n};\n"
  },
  {
    "path": "packages/scripts/src/utils/index.ts",
    "content": "import { $, AnswerMatchMode, AnswererWrapper, WorkUploadType } from '@ocsjs/core';\nimport { $ui, $message, $modal, MessageElement, h } from 'easy-us';\nimport { $console } from '../projects/background';\nimport { answerWrapperEmptyWarning } from './work';\nimport { MessageAttrs } from 'easy-us/lib/interfaces/custom-window';\n\nexport interface CommonWorkOptions {\n\tperiod: number;\n\tthread: number;\n\tupload: WorkUploadType;\n\tanswererWrappers: AnswererWrapper[];\n\tstopSecondWhenFinish: number;\n\tredundanceWordsText: string;\n\tanswerSeparators: string;\n\tanswerMatchMode: AnswerMatchMode;\n}\n\n/** 创建答题预处理信息 */\nexport function workPreCheckMessage(\n\toptions: CommonWorkOptions & {\n\t\tstart_delay_seconds?: number;\n\t\tonrun: (opts: CommonWorkOptions) => void;\n\t\t/**\n\t\t * 当没有题库配置时的回调\n\t\t */\n\t\tonNoAnswererWrappers?: (opts: CommonWorkOptions) => void;\n\t\t/**\n\t\t * 手动关闭时的回调\n\t\t */\n\t\tonclose?: (opts: CommonWorkOptions, closedMessage: MessageElement) => void;\n\t}\n) {\n\tconst { onrun, onNoAnswererWrappers, onclose, ...opts } = options;\n\n\tif (opts.answererWrappers.length === 0) {\n\t\tonNoAnswererWrappers?.(opts);\n\t\treturn answerWrapperEmptyWarning(0);\n\t} else {\n\t\toptions.start_delay_seconds = options.start_delay_seconds ?? 5;\n\t\treturn $message.info({\n\t\t\tduration: options.start_delay_seconds,\n\t\t\tcontent: h('span', [\n\t\t\t\t`${options.start_delay_seconds}秒后自动答题，`,\n\t\t\t\t$ui.preventText({\n\t\t\t\t\tname: '点击取消',\n\t\t\t\t\tdelay: options.start_delay_seconds,\n\t\t\t\t\tondefault: (span) => {\n\t\t\t\t\t\tonrun(opts);\n\t\t\t\t\t},\n\t\t\t\t\tonprevent(span) {\n\t\t\t\t\t\tconst closedMessage = $message.warn({\n\t\t\t\t\t\t\tcontent: '已关闭此次的自动答题，请手动开启或者忽略此警告。',\n\t\t\t\t\t\t\tduration: 0\n\t\t\t\t\t\t});\n\t\t\t\t\t\tif (closedMessage) {\n\t\t\t\t\t\t\tonclose?.(opts, closedMessage);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t])\n\t\t});\n\t}\n}\n\n/**\n * 创造范围选择器的提示\n */\nexport function createRangeTooltip(\n\tinput: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement,\n\tdefaultValue: string,\n\ttransform: (val: string) => string\n) {\n\tinput.addEventListener('change', () => {\n\t\tinput.setAttribute('data-title', transform(input.value || input.getAttribute('value') || defaultValue));\n\t});\n\tinput.setAttribute('data-title', transform(input.value || input.getAttribute('value') || defaultValue));\n}\n\n// 有些网课会改变 media.play 方法，所以可能不是一个 promise\nexport async function playMedia(playFunction: () => Promise<void> | undefined | void): Promise<boolean> {\n\t//  尝试播放\n\tconst tryPlayMedia = () => {\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\ttry {\n\t\t\t\tconst playRes = playFunction();\n\t\t\t\tif (playRes) {\n\t\t\t\t\tplayRes.then(resolve).catch(reject);\n\t\t\t\t} else {\n\t\t\t\t\tresolve();\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\treject(err);\n\t\t\t}\n\t\t});\n\t};\n\n\ttry {\n\t\tawait tryPlayMedia();\n\t\treturn true;\n\t} catch (err) {\n\t\tconsole.error(err);\n\t\tif (String(err).includes(`failed because the user didn't interact with the document first`)) {\n\t\t\t$modal.alert({\n\t\t\t\tcontent:\n\t\t\t\t\t'播放音视频失败，由于浏览器的用户隐私保护措施，如果要播放带有音量的视频，或者某些无法自动播放音视频的网站，您必须先点击一次页面上的任意位置脚本才能进行音视频的播放，后续无需重新点击。',\n\t\t\t\tonClose: async () => {\n\t\t\t\t\tawait tryPlayMedia();\n\t\t\t\t}\n\t\t\t});\n\t\t\treturn true;\n\t\t} else if (String(err).includes('The element has no supported sources')) {\n\t\t\t$console.error('当前视频无法播放。');\n\t\t} else {\n\t\t\t$console.error('播放视频时发生未知错误：' + String(err));\n\t\t}\n\t\treturn false;\n\t}\n}\n\n/**\n * \t解除复制限制功能\n * @param elements 要开启复制功能的元素\n */\nexport function enableCopy(elements: (HTMLElement | Document)[]) {\n\t// 将页面上的所有选择方法劫持，并强制返回 true\n\tfunction hackSelect(target: HTMLElement | Document) {\n\t\tif (target) {\n\t\t\tconst _original_select = target.onselectstart;\n\t\t\tconst _original_oncopy = target.oncopy;\n\t\t\tconst _original_onpaste = target.onpaste;\n\t\t\tconst _original_onkeydown = target.onkeydown;\n\n\t\t\ttarget.onselectstart = (e: any) => {\n\t\t\t\t_original_select?.apply(target, [e]);\n\t\t\t\te.stopPropagation();\n\t\t\t\te.returnValue = true;\n\t\t\t\treturn true;\n\t\t\t};\n\t\t\ttarget.oncopy = (e: any) => {\n\t\t\t\t_original_oncopy?.apply(target, [e]);\n\t\t\t\te.stopPropagation();\n\t\t\t\te.returnValue = true;\n\t\t\t\treturn true;\n\t\t\t};\n\t\t\ttarget.onpaste = (e: any) => {\n\t\t\t\t_original_onpaste?.apply(target, [e]);\n\t\t\t\te.stopPropagation();\n\t\t\t\te.returnValue = true;\n\t\t\t\treturn true;\n\t\t\t};\n\t\t\ttarget.onkeydown = (e: any) => {\n\t\t\t\t_original_onkeydown?.apply(target, [e]);\n\t\t\t\te.stopPropagation();\n\t\t\t\te.returnValue = true;\n\t\t\t\treturn true;\n\t\t\t};\n\t\t}\n\t}\n\n\tfor (const el of elements) {\n\t\thackSelect(el);\n\t}\n}\n\nlet popupWin: Window | null;\nwindow.addEventListener('beforeunload', () => {\n\tpopupWin?.close();\n});\n/**\n * 创建关于问题题目的拓展功能按钮，包括复制和百度一下\n * @param question 问题\n */\nexport function createQuestionTitleExtra(question: string) {\n\tconst space = $ui.space(\n\t\t[\n\t\t\t$ui.copy('复制', question),\n\t\t\th('span', { className: 'question-title-extra-btn', innerText: '🌏百度一下' }, (btn) => {\n\t\t\t\tbtn.onclick = () => {\n\t\t\t\t\tpopupWin?.close();\n\t\t\t\t\tpopupWin = $.createCenteredPopupWindow(`https://www.baidu.com/s?wd=${question}`, '百度搜索', {\n\t\t\t\t\t\twidth: 1000,\n\t\t\t\t\t\theight: 800,\n\t\t\t\t\t\tresizable: true,\n\t\t\t\t\t\tscrollbars: true\n\t\t\t\t\t});\n\t\t\t\t};\n\t\t\t})\n\t\t],\n\t\t{ x: 4 }\n\t);\n\tspace.style.marginTop = '6px';\n\tspace.style.textAlign = 'right';\n\treturn h('div', { style: { textAlign: 'right' } }, [space]);\n}\n\nfunction msg(type: keyof typeof $message, attrs: MessageAttrs) {\n\t$message[type](attrs);\n\tif (type === 'success') {\n\t\ttype = 'info';\n\t}\n\tlet content = '';\n\tif (typeof attrs === 'string') {\n\t\tcontent = attrs;\n\t} else {\n\t\tif (attrs.content instanceof HTMLElement) {\n\t\t\tcontent = attrs.content.innerText;\n\t\t} else if (typeof attrs.content === 'string') {\n\t\t\tcontent = attrs.content.toString();\n\t\t}\n\t}\n\t$console[type](content);\n}\n\nexport const $msg = {\n\t/**  输出气泡消息以及日志记录  */\n\tinfo: (attrs: MessageAttrs) => msg('info', attrs),\n\t/**  输出气泡消息以及日志记录  */\n\twarn: (attrs: MessageAttrs) => msg('warn', attrs),\n\t/**  输出气泡消息以及日志记录  */\n\terror: (attrs: MessageAttrs) => msg('error', attrs),\n\t/**  输出气泡消息以及日志记录  */\n\tsuccess: (attrs: MessageAttrs) => msg('success', attrs)\n};\n"
  },
  {
    "path": "packages/scripts/src/utils/markdown.ts",
    "content": "import { marked } from 'marked';\n\nexport function markdown(md: string) {\n\treturn marked.parse(md);\n}\n"
  },
  {
    "path": "packages/scripts/src/utils/render.ts",
    "content": "import { CommonProject } from '../projects/common';\n\nexport const $render = {\n\t/**\n\t * 移动到边缘\n\t */\n\tmoveToEdge(x = 80, y = 100) {\n\t\tCommonProject.scripts.render.methods.minimize();\n\t\tCommonProject.scripts.render.methods.setPosition(x, y);\n\t}\n};\n"
  },
  {
    "path": "packages/scripts/src/utils/study.ts",
    "content": "import { $ } from '@ocsjs/core';\n\n/**\n * 等待视频加载并获取视频\n */\nexport async function waitForMedia(options?: {\n\t/**\n\t * 视频选择器\n\t */\n\tvideoSelector?: string;\n\t/**\n\t * 音频选择器\n\t */\n\taudioSelector?: string;\n\t/**\n\t * 根元素\n\t */\n\troot?: HTMLElement | Document;\n\ttimeout?: number;\n\tfilter?: (video: HTMLVideoElement | HTMLAudioElement) => boolean;\n}) {\n\tconst res = await Promise.race([\n\t\tnew Promise<HTMLVideoElement | HTMLAudioElement>((resolve, reject) => {\n\t\t\tconst interval = setInterval(() => {\n\t\t\t\tconst video = (options?.root || document).querySelector<HTMLVideoElement | HTMLAudioElement>(\n\t\t\t\t\t`${options?.videoSelector || 'video'},${options?.audioSelector || 'audio'}`\n\t\t\t\t);\n\t\t\t\tif (video && (!options?.filter || options.filter(video))) {\n\t\t\t\t\tclearInterval(interval);\n\t\t\t\t\tresolve(video);\n\t\t\t\t}\n\t\t\t}, 200);\n\t\t}),\n\t\t$.sleep(options?.timeout ?? 3 * 60 * 1000)\n\t]);\n\tif (res) {\n\t\treturn res;\n\t} else {\n\t\tthrow new Error('视频/音频未找到，或者加载超时。');\n\t}\n}\n\nexport function waitForElement(\n\tselector: string | { (): HTMLElement | undefined },\n\topts?: { timeout_seconds?: number; check_period_ms?: number }\n) {\n\treturn waitFor(() => {\n\t\treturn typeof selector === 'function' ? selector() : document.querySelector<HTMLElement>(selector);\n\t}, opts);\n}\n\nexport function waitFor<T>(predicate: () => T, opts?: { timeout_seconds?: number; check_period_ms?: number }) {\n\treturn new Promise<T>((resolve, reject) => {\n\t\tlet timeout: any;\n\t\tconst interval = setInterval(() => {\n\t\t\tconst result = predicate();\n\t\t\tif (result) {\n\t\t\t\tclearInterval(interval);\n\t\t\t\ttimeout && clearTimeout(timeout);\n\t\t\t\tresolve(result);\n\t\t\t}\n\t\t}, opts?.check_period_ms || 1000);\n\n\t\t// 超时跳过\n\t\tif (opts?.timeout_seconds) {\n\t\t\ttimeout = setTimeout(() => {\n\t\t\t\tclearInterval(interval);\n\t\t\t\tresolve(undefined as any);\n\t\t\t}, (opts?.timeout_seconds || 10) * 1000);\n\t\t}\n\t});\n}\n"
  },
  {
    "path": "packages/scripts/src/utils/work.ts",
    "content": "import { SimplifyWorkResult, WorkerEvents, WorkResult } from '@ocsjs/core';\nimport { $ui, $message, MessageElement, Script, h, CommonEventEmitter, cors, $elements } from 'easy-us';\nimport { CommonProject } from '../projects/common';\nimport { CommonWorkOptions, workPreCheckMessage } from '.';\n\nexport let globalControlPanel: HTMLElement | null = null;\n\n/**\n * 通用作业考试工具方法\n */\nexport function commonWork(\n\tscript: Script,\n\toptions: {\n\t\tstart_delay_seconds?: number;\n\t\tenable_control_panel?: boolean;\n\t\tworkerProvider: (opts: CommonWorkOptions) => CommonEventEmitter<WorkerEvents> | undefined;\n\t\tbeforeRunning?: () => void | Promise<void>;\n\t\tonRestart?: () => void | Promise<void>;\n\t\tonWorkerCreated?: (worker: CommonEventEmitter<WorkerEvents>) => void | Promise<void>;\n\t}\n) {\n\t// 置顶当前脚本\n\tCommonProject.scripts.render.methods.pin(script);\n\tlet worker: CommonEventEmitter<WorkerEvents> | undefined;\n\n\t/**\n\t * 是否已经按下了开始按钮\n\t */\n\tlet startBtnPressed = false;\n\t/**\n\t * 是否检查失败\n\t */\n\tlet checkFailed = false;\n\n\t/**\n\t * 是否正在运行\n\t */\n\tlet running = false;\n\n\t/** 显示答题控制按钮 */\n\tconst createWorkControlPanel = () => {\n\t\tconst { controlBtn, restartBtn, startBtn } = createWorkerControl({\n\t\t\tworkerProvider: () => worker,\n\t\t\tonStart: async () => {\n\t\t\t\tstartBtnPressed = true;\n\t\t\t\tif (checkMessage instanceof MessageElement) {\n\t\t\t\t\tcheckMessage.remove();\n\t\t\t\t}\n\t\t\t\tawait closeAnswerWrapperEmptyWarning();\n\t\t\t\tstart();\n\t\t\t},\n\t\t\tonRestart: async () => {\n\t\t\t\tworker?.emit('close');\n\t\t\t\tawait options.onRestart?.();\n\t\t\t\tstart();\n\t\t\t}\n\t\t});\n\n\t\tstartBtn.style.flex = '1';\n\t\tstartBtn.style.padding = '4px';\n\t\trestartBtn.style.flex = '1';\n\t\trestartBtn.style.padding = '4px';\n\t\tcontrolBtn.style.flex = '1';\n\t\tcontrolBtn.style.padding = '4px';\n\n\t\tconst container = h(\n\t\t\t'div',\n\t\t\t{ style: { marginTop: '12px', display: 'flex' } },\n\t\t\trunning ? [controlBtn, restartBtn] : [startBtn]\n\t\t);\n\n\t\tglobalControlPanel = container;\n\n\t\treturn { container, startBtn, restartBtn, controlBtn };\n\t};\n\tconst workResultPanel = () => CommonProject.scripts.workResults.methods.createWorkResultsPanel();\n\n\tconst sync_script = [script];\n\tif (options.enable_control_panel) {\n\t\tsync_script.push(CommonProject.scripts.workResults);\n\t}\n\n\tfor (const script of sync_script) {\n\t\tscript.on('render', () => {\n\t\t\tlet gotoSettingsBtnContainer: string | HTMLElement = '';\n\t\t\tif (checkFailed) {\n\t\t\t\tconst gotoSettingsBtn = $ui.button('👉 前往设置题库配置', {\n\t\t\t\t\tclassName: 'base-style-button',\n\t\t\t\t\tstyle: { flex: '1', padding: '4px' }\n\t\t\t\t});\n\t\t\t\tgotoSettingsBtn.style.flex = '1';\n\t\t\t\tgotoSettingsBtn.style.padding = '4px';\n\t\t\t\tgotoSettingsBtn.onclick = () => {\n\t\t\t\t\tCommonProject.scripts.render.methods.pin(CommonProject.scripts.settings);\n\t\t\t\t};\n\t\t\t\tgotoSettingsBtnContainer = h('div', { style: { display: 'flex' } }, [gotoSettingsBtn]);\n\t\t\t}\n\n\t\t\tscript.panel?.body?.replaceChildren(\n\t\t\t\th('div', { style: { marginTop: '12px' } }, [\n\t\t\t\t\tgotoSettingsBtnContainer,\n\t\t\t\t\t...(options.enable_control_panel ? [globalControlPanel || createWorkControlPanel().container] : []),\n\t\t\t\t\tworkResultPanel()\n\t\t\t\t])\n\t\t\t);\n\t\t});\n\t}\n\n\tconst workOptions = CommonProject.scripts.settings.methods.getWorkOptions();\n\n\t/**\n\t * 检查题库是否配置，并询问是否开始答题\n\t */\n\tlet checkMessage = workPreCheckMessage({\n\t\tonrun: () => startBtnPressed === false && start(),\n\t\tonclose: (_, closedMsg) => (checkMessage = closedMsg),\n\t\tonNoAnswererWrappers: () => {\n\t\t\tcheckFailed = true;\n\t\t},\n\t\t...workOptions,\n\t\tstart_delay_seconds: options.start_delay_seconds\n\t});\n\n\tconst start = async () => {\n\t\tawait options.beforeRunning?.();\n\t\trunning = true;\n\t\tworker = options.workerProvider(workOptions);\n\n\t\tif (worker) {\n\t\t\toptions.onWorkerCreated?.(worker);\n\t\t}\n\n\t\tconst { container, controlBtn } = createWorkControlPanel();\n\t\t// 更新状态\n\t\tscript.panel?.body?.replaceChildren(container, workResultPanel());\n\n\t\tworker?.once('done', () => {\n\t\t\trunning = false;\n\t\t\tglobalControlPanel = null;\n\t\t\tcontrolBtn.disabled = true;\n\t\t});\n\t};\n}\n\n/**\n * 答题控制\n */\nexport function createWorkerControl(options: {\n\tworkerProvider: () => CommonEventEmitter<WorkerEvents> | undefined;\n\tonStart: () => void;\n\tonRestart: () => void;\n}) {\n\tlet stop = false;\n\tlet stopMessage: MessageElement | undefined;\n\tconst startBtn = $ui.button('▶️开始答题');\n\tconst restartBtn = $ui.button('🔃重新答题');\n\tconst controlBtn = $ui.button('⏸暂停');\n\n\tstartBtn.onclick = () => {\n\t\tstartBtn.remove();\n\t\toptions.onStart();\n\t};\n\trestartBtn.onclick = () => {\n\t\t// 重新答题时，清除暂停提示\n\t\tstopMessage?.remove();\n\t\toptions.onRestart();\n\t};\n\tcontrolBtn.onclick = () => {\n\t\tstop = !stop;\n\t\tconst worker = options.workerProvider();\n\t\tworker?.emit?.(stop ? 'stop' : 'continuate');\n\t\tcontrolBtn.value = stop ? '▶️继续' : '⏸️暂停';\n\t\tif (stop) {\n\t\t\tstopMessage = $message.warn({ duration: 0, content: '暂停中...' });\n\t\t} else {\n\t\t\tstopMessage?.remove();\n\t\t}\n\t};\n\n\treturn { startBtn, restartBtn, controlBtn };\n}\n\n/**\n * 图片识别，将图片链接追加到 text 中\n * 返回一个克隆的节点\n */\nexport function optimizationElementWithImage(root: HTMLElement, clone_node: boolean = false): HTMLElement {\n\tconst clone = clone_node ? (root.cloneNode(true) as HTMLElement) : root;\n\tfor (const img of Array.from(clone.querySelectorAll('img'))) {\n\t\t// 如果已经存在识别结果，则不处理\n\t\tif (\n\t\t\tArray.from(img.parentElement!.querySelectorAll('span')).some(\n\t\t\t\t(e) => e.style.fontSize === '0px' && e.textContent?.includes(img.src)\n\t\t\t)\n\t\t) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst src = document.createElement('span');\n\t\tsrc.innerText = img.src;\n\t\t// 隐藏图片，但不影响 innerText 的获取\n\t\tsrc.style.fontSize = '0px';\n\t\timg.after(src);\n\t}\n\treturn clone;\n}\n\n/**\n * 创建一个不可见的文本节点，追加到图片后面，便于文本获取\n */\nexport function createUnVisibleTextOfImage(img: HTMLImageElement) {\n\tconst src = document.createElement('span');\n\tsrc.innerText = img.src;\n\t// 隐藏图片，但不影响 innerText 的获取\n\tsrc.style.fontSize = '0px';\n\timg.after(src);\n}\n\n/** 将 {@link WorkResult} 转换成 {@link SimplifyWorkResult} */\nexport function simplifyWorkResult(\n\tresults: WorkResult<any>[],\n\t/**\n\t * 标题处理方法\n\t * 在答题时使用相同的处理方法，可以使答题结果显示的题目与搜题的题目保持一致\n\t */\n\ttitleTransform?: (title: (HTMLElement | undefined)[], index: number) => string\n): SimplifyWorkResult[] {\n\tconst res: SimplifyWorkResult[] = [];\n\tlet i = 0;\n\tfor (const wr of results) {\n\t\tconst ques =\n\t\t\ttitleTransform?.(wr.ctx?.elements.title || [], i) ||\n\t\t\twr.ctx?.elements.title\n\t\t\t\t?.map((e) => e?.innerText.trim())\n\t\t\t\t.filter(Boolean)\n\t\t\t\t.join('<br>') ||\n\t\t\t'';\n\t\tres.push({\n\t\t\trequested: wr.requested,\n\t\t\tresolved: wr.resolved,\n\t\t\terror: wr.error,\n\t\t\ttype: wr.ctx?.type,\n\t\t\tquestion: ques,\n\t\t\tfinish: wr.result?.finish,\n\t\t\tsearchInfos:\n\t\t\t\twr.ctx?.searchInfos.map((sr) => ({\n\t\t\t\t\terror: sr.error,\n\t\t\t\t\tname: sr.name,\n\t\t\t\t\thomepage: sr.homepage,\n\t\t\t\t\tresults: sr.results.map((ans) => [ans.question, ans.answer, ans.extra_data || {}])\n\t\t\t\t})) || []\n\t\t});\n\t\ti++;\n\t}\n\n\treturn res;\n}\n\n/**\n * 从题目中移除指定的冗余词\n */\nexport function removeRedundantWords(str: string, words: string[]) {\n\tfor (const word of words.map((w) => w.trim())) {\n\t\tstr = str.replace(word, '');\n\t}\n\treturn str;\n}\n\nlet answererWrapperUnsetMessage: MessageElement | undefined;\n\nexport const answerWrapperEmptyWarning = cors.defineTopFunction((duration: number) => {\n\tconst setting = h('button', { className: 'base-style-button-secondary' }, '通用-全局设置');\n\tsetting.onclick = () => {\n\t\tCommonProject.scripts.render.methods.pin(CommonProject.scripts.settings);\n\t\tsetTimeout(() => {\n\t\t\t$elements.root?.querySelector<HTMLElement>('[value=\"点击配置\"]')?.click();\n\t\t}, 500);\n\t};\n\n\tanswererWrapperUnsetMessage?.remove();\n\tanswererWrapperUnsetMessage = $message.warn({\n\t\tcontent: h('span', {}, ['你还没设置题库，无法自动答题，请切换到 ', setting, ' 页面进行配置。']),\n\t\tduration: duration\n\t});\n});\n\nexport const closeAnswerWrapperEmptyWarning = cors.defineTopFunction(() => {\n\tanswererWrapperUnsetMessage?.remove();\n\tanswererWrapperUnsetMessage = undefined;\n});\n"
  },
  {
    "path": "packages/scripts/tsconfig.json",
    "content": "{\n\t\"compilerOptions\": {\n\t\t\"target\": \"es6\",\n\t\t\"module\": \"commonjs\",\n\t\t\"outDir\": \"./lib\",\n\t\t\"declaration\": true,\n\t\t\"strict\": true,\n\t\t\"esModuleInterop\": true,\n\t\t\"skipLibCheck\": true,\n\t\t\"forceConsistentCasingInFileNames\": true,\n\t\t\"lib\": [\"DOM\", \"ES2020\"],\n\t\t\"jsx\": \"preserve\",\n\t\t\"resolveJsonModule\": true\n\t},\n\t\"include\": [\"./src/**/*.ts\"],\n\t\"files\": [\"./global.d.ts\"]\n}\n"
  },
  {
    "path": "packages/scripts/vite.config.ts",
    "content": "import { visualizer } from 'rollup-plugin-visualizer';\nimport { defineConfig } from 'vite';\nimport banner from 'vite-plugin-banner';\nimport { author, description, homepage, license, name } from '../../package.json';\nimport dotenv from 'dotenv';\n\nconst bannerContent = `\n/*!\n * ${name} ( ${homepage} )\n * ${description}\n * copyright ${author}\n * license ${license}\n */\n`;\n\ndotenv.config();\n\n// https://vitejs.dev/config/\nexport default defineConfig({\n\tresolve: {\n\t\talias: {\n\t\t\t'@ocsjs/core': '../core/src/index.ts'\n\t\t}\n\t},\n\tesbuild: {\n\t\tcharset: 'utf8'\n\t},\n\tbuild: {\n\t\t/** 取消css代码分离 */\n\t\tcssCodeSplit: false,\n\t\t/** @ts-ignore 输出路径 */\n\t\toutDir: process.env.VITE_BUILD_PATH,\n\t\t/** 清空输出路径 */\n\t\temptyOutDir: false,\n\t\t/** 是否压缩代码 */\n\t\tminify: false,\n\n\t\t/** 打包库， 全局名字为 OCS */\n\t\tlib: {\n\t\t\tentry: './src/index.ts',\n\t\t\tname: 'OCS',\n\t\t\tfileName: () => 'index.js',\n\t\t\tformats: ['umd']\n\t\t}\n\t},\n\n\tplugins: [\n\t\t// commonjs(),\n\t\tvisualizer(),\n\t\tbanner(bannerContent)\n\t]\n});\n"
  },
  {
    "path": "packages/utils/.gitignore",
    "content": "# customize\r\n\r\ntests/\r\n\r\npnpm-lock.yaml\r\npackage-lock.json\r\n\r\n# vue build\r\n\r\npublic\r\n\r\n# electron build\r\n\r\ndist\r\n\r\n# Logs\r\nlogs\r\n*.log\r\nnpm-debug.log*\r\nyarn-debug.log*\r\nyarn-error.log*\r\nlerna-debug.log*\r\n.pnpm-debug.log*\r\n\r\n# Diagnostic reports (https://nodejs.org/api/report.html)\r\nreport.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json\r\n\r\n# Runtime data\r\npids\r\n*.pid\r\n*.seed\r\n*.pid.lock\r\n\r\n# Directory for instrumented libs generated by jscoverage/JSCover\r\nlib-cov\r\n\r\n# Coverage directory used by tools like istanbul\r\ncoverage\r\n*.lcov\r\n\r\n# nyc test coverage\r\n.nyc_output\r\n\r\n# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)\r\n.grunt\r\n\r\n# Bower dependency directory (https://bower.io/)\r\nbower_components\r\n\r\n# node-waf configuration\r\n.lock-wscript\r\n\r\n# Compiled binary addons (https://nodejs.org/api/addons.html)\r\nbuild/Release\r\n\r\n# Dependency directories\r\nnode_modules/\r\njspm_packages/\r\n\r\n# Snowpack dependency directory (https://snowpack.dev/)\r\nweb_modules/\r\n\r\n# TypeScript cache\r\n*.tsbuildinfo\r\n\r\n# Optional npm cache directory\r\n.npm\r\n\r\n# Optional eslint cache\r\n.eslintcache\r\n\r\n# Optional stylelint cache\r\n.stylelintcache\r\n\r\n# Microbundle cache\r\n.rpt2_cache/\r\n.rts2_cache_cjs/\r\n.rts2_cache_es/\r\n.rts2_cache_umd/\r\n\r\n# Optional REPL history\r\n.node_repl_history\r\n\r\n# Output of 'npm pack'\r\n*.tgz\r\n\r\n# Yarn Integrity file\r\n.yarn-integrity\r\n\r\n# dotenv environment variable files\r\n.env\r\n.env.development.local\r\n.env.test.local\r\n.env.production.local\r\n.env.local\r\n\r\n# parcel-bundler cache (https://parceljs.org/)\r\n.cache\r\n.parcel-cache\r\n\r\n# Next.js build output\r\n.next\r\nout\r\n\r\n# Nuxt.js build / generate output\r\n.nuxt\r\ndist\r\n\r\n# Gatsby files\r\n.cache/\r\n# Comment in the public line in if your project uses Gatsby and not Next.js\r\n# https://nextjs.org/blog/next-9-1#public-directory-support\r\n# public\r\n\r\n# vuepress build output\r\n.vuepress/dist\r\n\r\n# vuepress v2.x temp and cache directory\r\n.temp\r\n.cache\r\n\r\n# Docusaurus cache and generated files\r\n.docusaurus\r\n\r\n# Serverless directories\r\n.serverless/\r\n\r\n# FuseBox cache\r\n.fusebox/\r\n\r\n# DynamoDB Local files\r\n.dynamodb/\r\n\r\n# TernJS port file\r\n.tern-port\r\n\r\n# Stores VSCode versions used for testing VSCode extensions\r\n.vscode-test\r\n\r\n# yarn v2\r\n.yarn/cache\r\n.yarn/unplugged\r\n.yarn/build-state.yml\r\n.yarn/install-state.gz\r\n.pnp.*"
  },
  {
    "path": "packages/utils/package.json",
    "content": "{\n\t\"name\": \"@ocsjs/utils\",\n\t\"version\": \"1.0.0\",\n\t\"description\": \"utils package of ocs\",\n\t\"main\": \"./lib/index.js\",\n\t\"types\": \"./lib/index.d.ts\",\n\t\"scripts\": {},\n\t\"repository\": {\n\t\t\"type\": \"git\",\n\t\t\"url\": \"git+https://github.com/ocsjs/ocsjs.git\"\n\t},\n\t\"keywords\": [\n\t\t\"ocs\",\n\t\t\"script\",\n\t\t\"playwright\",\n\t\t\"puppeteer\",\n\t\t\"electron\",\n\t\t\"vue\",\n\t\t\"ant-design-vue\",\n\t\t\"typescript\"\n\t],\n\t\"author\": \"enncy\",\n\t\"license\": \"MIT\",\n\t\"bugs\": {\n\t\t\"url\": \"https://github.com/ocsjs/ocsjs/issues\"\n\t},\n\t\"homepage\": \"https://github.com/ocsjs/ocsjs#readme\",\n\t\"devDependencies\": {\n\t\t\"@types/node-fetch\": \"^2.6.2\"\n\t},\n\t\"dependencies\": {\n\t\t\"node-fetch\": \"^2.6.7\"\n\t}\n}\n"
  },
  {
    "path": "packages/utils/src/common/index.ts",
    "content": "export * from './script.builder';\n"
  },
  {
    "path": "packages/utils/src/common/script.builder.ts",
    "content": "import { readFileSync, writeFileSync } from 'fs';\nimport fetch from 'node-fetch';\n\nexport type MetaDataType = string | string[];\n\n/**\n * 脚本头部信息格式\n */\nexport interface MetaDataFormatter {\n\theader: string;\n\tfooter: string;\n\tprefix: string;\n\tsymbol: string;\n\tgap: string;\n}\n\n/**\n * 脚本头部信息\n */\nexport interface Metadata {\n\t[x: string]: any;\n\tname: string;\n\tversion: string;\n\tdescription: string;\n\tauthor: MetaDataType;\n\tlicense: string;\n\tnamespace: string;\n\thomepage: string;\n\tsource: string;\n\ticon: string;\n\tconnect: MetaDataType;\n\tmatch: MetaDataType;\n\tgrant: MetaDataType;\n\trequire: MetaDataType;\n\tresource: MetaDataType;\n\t'run-at': 'document-start' | 'document-end' | 'document-idle' | 'document-body' | 'document-body';\n}\n\n/**\n * 创建脚本参数\n */\nexport interface CreateOptions {\n\tparseRequire: boolean;\n\tparseResource: boolean;\n\tresourceBuilder: (key: string, value: string) => string;\n\tmetaDataFormatter: MetaDataFormatter;\n\tentry: string;\n\tdist: string;\n\tmetadata: Metadata;\n}\n\nexport const DEFAULT_METADATA: Partial<Metadata> = {\n\tname: 'New Userscript',\n\tversion: '0.1',\n\tdescription: 'try to take over the world!',\n\tauthor: ['You'],\n\tlicense: 'MIT',\n\tmatch: ['http://*/*'],\n\tgrant: 'none',\n\t'run-at': 'document-idle'\n};\n\n/**\n * 创建脚本头部信息\n * @param metadata\n * @returns\n */\nexport function createUserScriptMetadata(formatter: MetaDataFormatter, metadata: Metadata) {\n\tconst contents = [];\n\t// 最长字段\n\tconst maxLength = Object.keys(metadata).reduce<number>((pre, cur) => Math.max(cur.length, pre), 0);\n\n\tconst lineBuilder = (key: string, value: string) =>\n\t\t[formatter.symbol, key.padEnd(maxLength, ' '), formatter.gap, value || ''].join('');\n\n\tfor (const key in metadata) {\n\t\tif (Object.prototype.hasOwnProperty.call(metadata, key)) {\n\t\t\tconst element = metadata[key];\n\t\t\tif (Array.isArray(element)) {\n\t\t\t\tcontents.push(...element.map((el) => lineBuilder(key, el)));\n\t\t\t} else {\n\t\t\t\tcontents.push(lineBuilder(key, element));\n\t\t\t}\n\t\t}\n\t}\n\treturn [formatter.header, ...contents, formatter.footer].map((line) => formatter.prefix + line).join('\\n');\n}\n\n/**\n * 解析脚本头部信息\n * @param require 外部依赖\n */\nexport async function parseMetaDataRequires(require: Metadata['require'] = []) {\n\tconst requires: string[] = [];\n\tfor (const value of Array.isArray(require) ? require : [require]) {\n\t\tif (value) {\n\t\t\tconst text = value.startsWith('http')\n\t\t\t\t? await fetch(value).then((res) => res.text())\n\t\t\t\t: readFileSync(value).toString();\n\t\t\trequires.push(text);\n\t\t}\n\t}\n\treturn requires.join('\\n');\n}\n\n/**\n * 解析脚本头部信息\n * @param resource 资源信息\n * @param resourceBuilder 变量声明器\n */\nexport async function parseMetaDataResources(\n\tresource: Metadata['resource'] = [],\n\tresourceBuilder: (key: string, value: string) => string\n) {\n\tconst resources: string[] = [];\n\tfor (const line of Array.isArray(resource) ? resource : [resource]) {\n\t\tconst values = line.replace(/ /g, ' ').split(' ');\n\n\t\tconst text = values[1].startsWith('http')\n\t\t\t? await fetch(values[1]).then((res) => res.text())\n\t\t\t: readFileSync(values[1]).toString();\n\t\tresources.push(resourceBuilder(values[0], text));\n\t}\n\treturn resources.join('\\n');\n}\n\n/**\n * 创建用户脚本\n */\nexport async function createUserScript(opts: CreateOptions) {\n\tlet requires = '';\n\tlet resources = '';\n\n\tif (opts.parseRequire) {\n\t\t// 解析外部依赖\n\t\trequires = await parseMetaDataRequires(opts.metadata.require);\n\t\topts.metadata.require = [];\n\t}\n\tif (opts.parseResource) {\n\t\t// 解析资源文件\n\t\tresources = await parseMetaDataResources(opts.metadata.resource, opts.resourceBuilder);\n\t\topts.metadata.resource = [];\n\t}\n\tconst content = [\n\t\t// 创建脚本头部信息\n\t\tcreateUserScriptMetadata(opts.metaDataFormatter, Object.assign(DEFAULT_METADATA, opts.metadata)),\n\t\trequires,\n\t\tresources,\n\t\t// 合并入口文件\n\t\treadFileSync(opts.entry).toString()\n\t].join('\\n'.repeat(2));\n\n\treturn writeFileSync(opts.dist, content);\n}\n"
  },
  {
    "path": "packages/utils/src/index.ts",
    "content": "export * from './common';\n"
  },
  {
    "path": "packages/utils/tsconfig.json",
    "content": "{\n\t\"compilerOptions\": {\n\t\t\"target\": \"es5\",\n\t\t\"module\": \"commonjs\",\n\t\t\"strict\": true,\n\t\t\"outDir\": \"./lib\",\n\t\t\"esModuleInterop\": true,\n\t\t\"skipLibCheck\": true,\n\t\t\"forceConsistentCasingInFileNames\": true,\n\t\t\"declaration\": true\n\t}\n}\n"
  },
  {
    "path": "pnpm-workspace.yaml",
    "content": "packages:\n  - 'packages/**'\n  - '!**/dist/**'\n"
  },
  {
    "path": "scripts/build-core.js",
    "content": "// @ts-check\n\nconst { series } = require('gulp');\nconst del = require('del');\nconst util = require('util');\nconst { version } = require('../package.json');\nconst execOut = util.promisify(require('./utils').execOut);\nconst { createUserScript } = require('../packages/utils');\nconst path = require('path');\nconst dotenv = require('dotenv');\nconst fs = require('fs');\n\ndotenv.config();\n\nconst distPath = process.env.BUILD_PATH || '../dist';\nconsole.log('BUILD_PATH: ', distPath);\nconst distResolvedPath = path.resolve(__dirname, distPath);\n\nfunction cleanOutput() {\n\treturn del([distPath, '../lib'], { force: true });\n}\n\nasync function buildPackages() {\n\t// @ts-ignore\n\tawait execOut('tsc', { cwd: '../packages/core' });\n\t// @ts-ignore\n\tawait execOut('vite build', { cwd: '../packages/core' });\n\t// @ts-ignore\n\tawait execOut('tsc', { cwd: '../packages/scripts' });\n\t// @ts-ignore\n\tawait execOut('vite build', { cwd: '../packages/scripts' });\n}\n\nasync function createUserJs() {\n\t/** 模拟浏览器环境 */\n\trequire('browser-env')();\n\n\t// @ts-ignore\n\tglobalThis.unsafeWindow = {};\n\n\t/** @type {import('../packages/scripts/src/index')} */\n\t// @ts-ignore\n\tconst ocs = require(path.join(distPath, 'index.js'));\n\n\t/** @return {import('../packages/utils').CreateOptions} */\n\tconst createOptions = () => {\n\t\tconst { CXProject, ZHSProject, ZJYProject, IcveMoocProject, ICourseProject, YKTProject } = ocs;\n\t\tconst projectList = [CXProject, ZHSProject, ZJYProject, IcveMoocProject, ICourseProject, YKTProject]\n\t\t\t.map((s) => `【${s.name}】`)\n\t\t\t.join(' ');\n\n\t\tconst matchMetadata = Array.from(\n\t\t\tnew Set(\n\t\t\t\tocs\n\t\t\t\t\t.definedProjects()\n\t\t\t\t\t.map((p) => (p.domains || []).map((d) => `*://*.${d}/*`))\n\t\t\t\t\t.flat()\n\t\t\t)\n\t\t);\n\n\t\treturn {\n\t\t\tparseRequire: true,\n\t\t\tparseResource: true,\n\t\t\tresourceBuilder: (key, value) => `const ${key} = \\`${value}\\`;`,\n\t\t\tmetaDataFormatter: {\n\t\t\t\theader: '==UserScript==',\n\t\t\t\tfooter: '==/UserScript==',\n\t\t\t\tprefix: '// ',\n\t\t\t\tsymbol: '@',\n\t\t\t\tgap: '\\t'.repeat(4)\n\t\t\t},\n\t\t\tmetadata: {\n\t\t\t\tname: 'OCS 网课助手',\n\t\t\t\tversion: version,\n\t\t\t\tdescription: [\n\t\t\t\t\t'OCS(online-course-script) 网课助手，官网 https://docs.ocsjs.com ，专注于帮助大学生从网课中释放出来',\n\t\t\t\t\t'让自己的时间把握在自己的手中，拥有人性化的操作页面，流畅的步骤提示，支持 ',\n\t\t\t\t\tprojectList,\n\t\t\t\t\t'等网课的学习，作业。具体的功能请查看脚本悬浮窗中的教程页面。'\n\t\t\t\t].join(' '),\n\t\t\t\tauthor: 'enncy',\n\t\t\t\tlicense: 'MIT',\n\t\t\t\tnamespace: 'https://enncy.cn',\n\t\t\t\thomepage: 'https://docs.ocsjs.com',\n\t\t\t\tsource: 'https://github.com/ocsjs/ocsjs',\n\t\t\t\ticon: 'https://cdn.ocsjs.com/logo.png',\n\t\t\t\tconnect: ['enncy.cn', 'icodef.com', 'ocsjs.com', 'zaizhexue.top', 'localhost', '127.0.0.1'],\n\t\t\t\tmatch: matchMetadata,\n\t\t\t\tgrant: [\n\t\t\t\t\t'GM_info',\n\t\t\t\t\t'GM_getTab',\n\t\t\t\t\t'GM_saveTab',\n\t\t\t\t\t'GM_setValue',\n\t\t\t\t\t'GM_getValue',\n\t\t\t\t\t'unsafeWindow',\n\t\t\t\t\t'GM_listValues',\n\t\t\t\t\t'GM_deleteValue',\n\t\t\t\t\t'GM_notification',\n\t\t\t\t\t'GM_xmlhttpRequest',\n\t\t\t\t\t'GM_getResourceText',\n\t\t\t\t\t'GM_addValueChangeListener',\n\t\t\t\t\t'GM_removeValueChangeListener'\n\t\t\t\t],\n\t\t\t\trequire: [path.join(__dirname, distPath, 'index.js')],\n\t\t\t\tresource: [`STYLE ${path.join(__dirname, '../packages/scripts/assets/css/style.css')}`],\n\t\t\t\t'run-at': 'document-start',\n\t\t\t\tantifeature: 'payment'\n\t\t\t},\n\t\t\tentry: path.join(__dirname, '../packages/scripts/entry.js'),\n\t\t\tdist: path.join(__dirname, distPath, 'ocs.user.js')\n\t\t};\n\t};\n\n\tconst officialOpts = createOptions();\n\tconsole.log('CreateUserScript: ', officialOpts.metadata.name, officialOpts.dist);\n\tawait createUserScript(officialOpts);\n\n\t/** 创建调试脚本 */\n\tconst devOpts = createOptions();\n\tdevOpts.parseRequire = false;\n\tdevOpts.parseResource = false;\n\tdevOpts.metadata.name = devOpts.metadata.name + '(dev)';\n\tdevOpts.metadata.require = ['file:///' + path.join(distResolvedPath, 'index.js')];\n\tdevOpts.metadata.resource = [`STYLE file:///${path.join(__dirname, '../packages/scripts/assets/css/style.css')}`];\n\tdevOpts.entry = path.join(__dirname, '../packages/scripts/entry.dev.js');\n\tdevOpts.dist = path.join(distResolvedPath, 'ocs.dev.user.js');\n\t/** 导出样式文件 */\n\tfs.copyFileSync(\n\t\tpath.join(__dirname, '../packages/scripts/assets/css/style.css'),\n\t\tpath.join(distResolvedPath, 'style.css')\n\t);\n\tconsole.log('createUserScript: ', devOpts.metadata.name, devOpts.dist);\n\tawait createUserScript(devOpts);\n\n\t/** 创建全Connect域名通用脚本 */\n\tconst commonOpts = createOptions();\n\tcommonOpts.metadata.name = commonOpts.metadata.name + ' - 全域名通用版';\n\tconst connect = Array.isArray(commonOpts.metadata.connect) ? commonOpts.metadata.connect : [];\n\tconnect.push('*');\n\tcommonOpts.metadata.connect = connect;\n\tcommonOpts.entry = path.join(__dirname, '../packages/scripts/entry.common.js');\n\tcommonOpts.dist = path.join(distResolvedPath, 'ocs.common.user.js');\n\n\tconsole.log('createUserScript: ', commonOpts.metadata.name, commonOpts.dist);\n\tawait createUserScript(commonOpts);\n}\n\nexports.default = series(cleanOutput, buildPackages, createUserJs);\n"
  },
  {
    "path": "scripts/dev-core.js",
    "content": "const { series } = require('gulp');\nconst { execOut } = require('./utils');\n\nexports.default = series(() => execOut('vite build -w --emptyOutDir false', { cwd: '../packages/scripts' }));\n"
  },
  {
    "path": "scripts/release.sh",
    "content": "#!/usr/bin/env bash\n\n# 自动发布npm包\n\n# 从控制台获取需要发布的版本\nread -p \"请输入需要发布的版本(例如: 0.0.1): \" version\n# 判断是否为空\nif [ -z \"$version\" ]; then\n    echo \"版本号不能为空!\"\n    exit 1\nfi\n# 确认是否发布版本\nread -p \"确认发布版本 $version ? [y/n]: \" isRelease\n# 判断是发布，还是取消发布\nif [ \"$isRelease\" = \"y\" ]; then\n    # 发布\n    echo \"版本发布 $version\"\n    \n    # 代码检查\n    npm run lint &&\n    # 更新版本\n    npm version \"$version\" --no-git-tag-version &&\n    # 本地构建\n    echo \"本地构建\" && \n    npm run build &&\n    # 更新日志\n    npm run changelog &&\n    # 更新日志\n    npm run changelog:simplify &&\n        # 更新日志\n    npm run changelog:current &&\n    # 保存\n    git add package.json CHANGELOG.md CHANGELOG_SIMPLIFIED.md CHANGELOG_CURRENT.md &&\n    git commit -m \"version release $version\" &&\n    git tag \"$version\" &&\n    echo \"开始发布\" &&\n    # 发布到Github\n    git push origin 4.0 --tags &&\n    # 发布到npm\n    npm publish &&\n    echo \"$version 发布成功\"\n    elif [ \"$isRelease\" = \"n\" ]; then\n    echo \"取消发布\"\nelse\n    echo \"输入有误\"\nfi\n\n\n"
  },
  {
    "path": "scripts/simplify_changelog.js",
    "content": "const { readFileSync, writeFileSync } = require('fs');\nconst { series } = require('gulp');\nconst path = require('path');\n\nexports.default = series((/** @type {Function} */ cb) => {\n\tlet changelog = readFileSync(path.resolve(__dirname, '../CHANGELOG.md'), 'utf-8');\n\n\t// 移除链接部分\n\tchangelog = changelog.replace(/\\(\\[.+\\]\\((.+)\\)\\)/g, '<a href=\"$1\">></a>');\n\n\t// 移除空更新内容\n\tchangelog = changelog.replace(/(#+)\\s+\\[(.+)\\]\\(.+\\)\\s\\((.+)\\)(?=\\n+(#+)\\s+\\[(.+)\\]\\(.+\\)\\s\\((.+)\\))/g, '');\n\n\t// 简化标题格式\n\tchangelog = changelog.replace(/(#+)\\s+\\[(.+)\\]\\(.+\\)\\s\\((.+)\\)/g, '$1 $2 ($3)');\n\n\t// 移除多余的Scope说明\n\tchangelog = changelog.replace(/\\*\\s+\\*\\*.+\\*\\*\\s/g, '* ');\n\n\tchangelog = changelog.replace(/Bug Fixes/g, '🔧 修复内容');\n\tchangelog = changelog.replace(/Features/g, '✨ 更新内容');\n\tchangelog = changelog.replace(/Performance Improvements/g, '⚡ 优化提升');\n\n\twriteFileSync(path.resolve(__dirname, '../CHANGELOG_SIMPLIFIED.md'), changelog, 'utf-8');\n\tcb();\n});\n"
  },
  {
    "path": "scripts/tsc.js",
    "content": "const { series } = require('gulp');\nconst { execOut } = require('./utils');\n\nexports.default = series(\n\tseries(\n\t\t() => execOut('tsc', { cwd: '../packages/utils' }),\n\t\t() => execOut('tsc', { cwd: '../packages/core' })\n\t)\n);\n"
  },
  {
    "path": "scripts/utils.js",
    "content": "const { exec } = require('child_process');\n\n/** 将 exec 错误信息输出到 stdout */\nfunction execOut(command, ...opts) {\n\tconst cmd = exec(command, ...opts);\n\tcmd.stdout.pipe(process.stdout);\n\tcmd.stderr.pipe(process.stdout);\n\treturn cmd;\n}\n\nexports.execOut = execOut;\n"
  }
]