// ==UserScript== // @name 学习通学神助手|超星·智慧树全能学习助手|学神助手|AI智能辅助学习|自动刷课|视频倍速|作业考试 // @namespace IPYIWEI // @version 5.3.4 // @updateURL https://raw.githubusercontent.com/byyiwei/xueshen-helper/main/scripts/xueshen-sc.js // @downloadURL https://raw.githubusercontent.com/byyiwei/xueshen-helper/main/scripts/xueshen-sc.js // @author IPYIWEI // @description 超星学习通/智慧树自动学习助手|DeepSeek AI智能辅助学习|视频倍速学习|自动切换任务点|作业考试全自动|字体加密解析|海量题库每日更新 // @description:zh-CN 超星学习通/智慧树自动学习助手|DeepSeek AI智能辅助学习|视频倍速学习|自动切换任务点|作业考试全自动|字体加密解析|海量题库每日更新 // @homepageURL https://xs.openget.cn/ // @supportURL https://xs.openget.cn/user.html // @license Proprietary // @changelog v5.3.4 更新内容: // @changelog 1. 修复无扩展名图片URL(超星部分CDN图)无法匹配填充的问题:图片比对改用URL末段哈希,兼容有无扩展名 // @changelog 2. 图片匹配加固:短哈希仅精确比对防误选,http/https与查询串差异不再影响匹配 // @changelog v5.3.3 更新内容: // @changelog 1. 彻底修复多选题填充:新增字母答案(A/ABD)按选项序号直接映射,考试页与超星/智慧树作业页全覆盖 // @changelog 2. 后端多选答案改返回结构化数组,支持E及更多选项字母,修复5个及以上选项无法匹配的问题 // @changelog v5.3.2 更新内容: // @changelog 1. 彻底修复多选题答案拆分问题:不再依赖分隔符切割,改用子串匹配,答案含任何标点都能正确全选 // @changelog v5.3.1 更新内容: // @changelog 1. 修复多选题答案无分隔符(如ABCD)无法全选的问题 // @changelog 2. 修复图片选项匹配失败:改用文件名提取比较,解决URL被normalize清空导致无法匹配 // @changelog 3. 修复月卡用户答题时显示"包月权益生效"而非正确答案的问题 // @changelog v5.3.0 更新内容: // @changelog 1. 修复含图片题目 AI 无法识别的问题:答题时自动提取题目中的图片并转 base64 发送给 AI 模型 // @changelog 2. 后端同步优化:图片下载失败时自动跳过并提示,不再因无效图片链接导致 AI 报错 // @changelog v5.2.9 更新内容: // @changelog 1. 新增脚本自动检测更新功能,发现新版本顶部提示一键更新 // @changelog 2. 兼容 Tampermonkey / ScriptCat / Greasemonkey 三大脚本管理器 // @changelog v5.2.8 更新内容: // @changelog 1. 修复包月用户答题菜单答案显示"包月权益生效,本题不扣点"的问题,后端返回200但无答案时正确提示"未找到答案" // @changelog v5.2.7 更新内容: // @changelog 1. 网页搜题未命中提示优化:后端返回 200 但无答案时,提示"未找到答案"而非"本地题库未命中" // @changelog v5.2.6 更新内容: // @changelog 1. 网页搜题升级:搜题结果返回题库中所有相关题目与答案(多结果展示),与后台查询一致,解决只返回单条的问题 // @changelog 2. 网页搜题免费、登录后可用、每日限 30 次(由服务端统一控制) // @changelog v5.2.5 更新内容: // @changelog 1. 优化 AI 答题超时处理:后端自动重试/切换模型耗时较长时,前端答题请求不再提前超时(120s→240s、180s→300s),重试成功后自动填充答案 // @changelog v5.2.4 更新内容: // @changelog 1. 账号登录移至标题栏下方常驻,切换任意页面Tab登录状态均可见 // @changelog 2. 视频倍速支持最高16倍,可在设置中自定义,设置后即时生效无需刷新页面 // @changelog 3. 首页新增"QQ频道"入口,一键加入腾讯频道 // @changelog 4. 最小化悬浮球及旋转光晕缩小,界面更精简 // @changelog 5. 修复视频倍速设置偶发失效问题 // @changelog v5.2.3 更新内容: // @changelog 1. 账号自动登录稳定性优化(仅 401/403 清除登录记忆,网络异常保留登录状态) // @changelog 2. 登录 Token 有效期延长至 180 天,减少频繁重新登录 // @changelog v5.2.2 更新内容: // @changelog 1. 优化账号登录记忆:仅在 Token 真正失效时才要求重新登录,网络波动不再误清登录状态 // @changelog 2. 后端登录 Token 有效期延长至 180 天,减少频繁重新登录 // @changelog v5.2.1 更新内容: // @changelog 1. 新增江西开放大学继续教育平台(jxrtvu.com)域名支持 // @changelog v5.2.0 更新内容: // @changelog 1. 首页新增"推广返利"入口,一键跳转推广赚钱页面 // @changelog 2. 账号登录区移除推广按钮,精简界面 // @changelog 3. 推广返利说明动态读取后台配置(佣金比例/冷却天数/最低提现) // @changelog 4. 后台推广数据统计新增已提现/待审核金额卡片 // @changelog 5. 提现申请列表新增今日/已通过/待审核金额合计 // @changelog 6. 收款信息美化:支付宝/微信双卡片布局,单图替换模式 // @changelog 7. 提现申请强制校验收款二维码,未上传不可提交 // @changelog 8. 修复admin页面推广返利模块渲染异常 // @icon https://xs.openget.cn/static/icon.jpg // @match *://*.chaoxing.com/* // @match *://*.neauce.com/* // @match *://*.zhihuishu.com/* // @match *://*.edu.cn/* // @match *://*.jxrtvu.com/* // @match *://*.o-learn.cn/* // @require https://xs.openget.cn/libs/vue/3.4.31/vue.global.prod.js // @require https://xs.openget.cn/libs/vue-demi/0.14.7/index.iife.js // @require data:application/javascript,window.Vue%3DVue%3B // @require https://xs.openget.cn/libs/pinia/2.1.7/pinia.iife.min.js // @require https://xs.openget.cn/libs/dayjs/1.11.10/dayjs.min.js // @require https://xs.openget.cn/libs/dompurify/3.1.6/purify.min.js // @require https://xs.openget.cn/libs/dayjs/1.11.10/plugin/customParseFormat.js // @require https://xs.openget.cn/libs/dayjs/1.11.10/plugin/weekday.js // @require https://xs.openget.cn/libs/dayjs/1.11.10/plugin/localeData.js // @require https://xs.openget.cn/libs/dayjs/1.11.10/plugin/weekOfYear.js // @require https://xs.openget.cn/libs/dayjs/1.11.10/plugin/weekYear.js // @require https://xs.openget.cn/libs/dayjs/1.11.10/plugin/advancedFormat.js // @require https://xs.openget.cn/libs/dayjs/1.11.10/plugin/quarterOfYear.js // @require https://xs.openget.cn/libs/ant-design-vue/4.2.6/antd.min.js // @require https://xs.openget.cn/libs/blueimp-md5/2.19.0/js/md5.min.js // @require https://xs.openget.cn/libs/rxjs/7.8.1/rxjs.umd.min.js // @require https://xs.openget.cn/libs/marked/16.1.1/lib/marked.umd.min.js // @resource ttf https://xs.openget.cn/libs/ttf/table.json // @resource ttf2 https://xs.openget.cn/libs/ttf2/table.json // @connect chaoxing.com // @connect zhihuishu.com // @connect xs.openget.cn // @connect *.o-learn.cn // @connect *.webtrn.cn // @connect 127.0.0.1 // @connect localhost // @grant GM_getResourceText // @grant GM_getValue // @grant GM_info // @grant GM_setValue // @grant GM_xmlhttpRequest // @grant GM_openInTab // @grant GM.openInTab // @grant unsafeWindow // @run-at document-start // @tag 自动学习 // @tag AI答题 // @tag 超星学习通 // @tag 智慧树 // @tag 网课 // ==/UserScript== (e=>{document.__abcHelperCSS=e})(' .theme-light{--t-bg:#f5f6f8;--t-surface:#fff;--t-surface2:#f0f2f5;--t-border:#e2e5ea;--t-border2:#d1d5db;--t-text:#1a1a2e;--t-text2:#4b5563;--t-text3:#9ca3af;--t-primary:#2563eb;--t-primary-bg:#eff6ff;--t-primary-border:#bfdbfe;--t-accent:#7c3aed;--t-success:#16a34a;--t-warning:#d97706;--t-danger:#dc2626;--t-info:#0891b2;--t-pink:#db2777;--t-shadow:rgba(0,0,0,.08);--t-shadow2:rgba(0,0,0,.04)}.theme-dark{--t-bg:#0d1117;--t-surface:#161b22;--t-surface2:#1c2330;--t-border:#30363d;--t-border2:#484f58;--t-text:#e6edf3;--t-text2:#9198a1;--t-text3:#6e7681;--t-primary:#3b82f6;--t-primary-bg:rgba(59,130,246,.1);--t-primary-border:rgba(59,130,246,.3);--t-accent:#a78bfa;--t-success:#3fb950;--t-warning:#d29922;--t-danger:#f85149;--t-info:#22d3ee;--t-pink:#f472b6;--t-shadow:rgba(0,0,0,.3);--t-shadow2:rgba(0,0,0,.15)}.log-wrap{font:13px "Segoe UI",system-ui,-apple-system,sans-serif}.log-wrap{display:flex;flex-direction:column;height:100%}.log-wrap .console{flex:1;min-height:200px;max-height:calc(100vh - 120px);width:100%;box-sizing:border-box;overflow-y:scroll;background:var(--t-surface2);padding:10px;color:var(--t-text2);font-size:12px;border-radius:6px;border:1px solid var(--t-border)}.log-wrap .console div{padding:2px 0}.log-wrap .console div span{margin:0 3px}.log-wrap .console div span .module{background:var(--t-primary);color:#fff;padding:1px 5px;border-radius:3px;font-size:10px;font-weight:600}.log-wrap .console .log{color:var(--t-text3)}.log-wrap .console .warning{color:var(--t-warning)}.home-wrap[data-v-137ea562]{font:14px "Segoe UI",system-ui,-apple-system,sans-serif;color:var(--t-text)}.home-wrap a{color:var(--t-primary);text-decoration:none}.home-wrap a:hover{text-decoration:underline}.home-wrap ol li{line-height:26px;list-style-type:decimal;list-style-position:inside;color:var(--t-text2)}.home-wrap .tips ul li span{color:var(--t-warning)}.home-wrap .hero{padding:16px 14px;margin:8px;background:linear-gradient(135deg,var(--t-primary),var(--t-accent));border-radius:8px;color:#fff;text-align:center}.home-wrap .hero .hero-title{font-size:18px;font-weight:700;margin-bottom:4px}.home-wrap .hero .hero-sub{font-size:12px;opacity:.85}.home-wrap .home-section{margin:8px;padding:12px 14px;background:var(--t-surface);border:1px solid var(--t-border);border-radius:6px}.home-wrap .home-section .section-title{font-size:13px;font-weight:700;color:var(--t-text);margin-bottom:8px;display:flex;align-items:center;gap:4px}.home-wrap .home-section ul{list-style:none;padding:0;margin:0}.home-wrap .home-section ul li{list-style:none;padding:4px 0;color:var(--t-text2);font-size:12px;line-height:1.6}.collapsible-section{cursor:pointer;user-select:none;transition:background .15s}.collapsible-section:hover{background:var(--t-surface2)}.collapsible-section .section-title{justify-content:space-between;gap:8px;margin-bottom:0}.collapse-icon{font-size:14px;color:var(--t-text3);font-weight:700;width:22px;height:22px;display:flex;align-items:center;justify-content:center;border-radius:4px;background:var(--t-surface2);transition:all .15s}.collapsible-section:hover .collapse-icon{background:var(--t-primary-bg);color:var(--t-primary)}.home-card-btn{display:block;margin-top:8px;padding:10px 12px;background:linear-gradient(135deg,var(--t-primary),var(--t-accent));border-radius:6px;color:#fff!important;text-align:center;font-size:13px;font-weight:600;cursor:pointer;text-decoration:none;transition:opacity .15s,transform .15s}.home-card-btn:hover{opacity:.9;transform:translateY(-1px);color:#fff!important}.home-wrap .login-box{display:flex;gap:6px;flex-wrap:wrap;align-items:center;margin-bottom:8px}.home-wrap .login-box input{height:30px;width:140px;padding:0 8px;border:1px solid var(--t-border2);border-radius:4px;background:var(--t-surface);color:var(--t-text);font-size:12px;outline:none}.home-wrap .login-box input:focus{border-color:var(--t-primary)}.home-wrap .login-box button{height:30px;padding:0 12px;border:none;border-radius:4px;font-size:12px;cursor:pointer;font-weight:500;transition:opacity .15s}.home-wrap .login-box button:hover{opacity:.85}.home-wrap .login-box .btn-primary{background:var(--t-primary);color:#fff}.home-wrap .login-box .btn-ghost{background:var(--t-surface2);color:var(--t-text2);border:1px solid var(--t-border2)}.home-wrap .login-box .btn-outline{background:transparent;color:var(--t-primary);border:1px solid var(--t-primary)}.home-wrap .login-meta{display:flex;gap:8px;align-items:center;margin-top:6px;color:var(--t-text3);font-size:11px;flex-wrap:wrap}.home-wrap .login-msg{margin-top:6px;font-size:12px}.answer-wrap .search-info{-webkit-user-select:none;user-select:none;box-sizing:border-box;display:flex;align-items:center;justify-content:center;width:22px;height:22px;border-radius:4px;border:1px solid var(--t-border2);margin-right:4px;font-weight:600;font-size:11px;background:var(--t-surface2);color:var(--t-text3);flex-shrink:0}.answer-wrap .search-info.default{box-shadow:none}.answer-wrap .search-info.searching{box-shadow:0 0 6px 1px var(--t-primary-border);border-color:var(--t-primary);color:var(--t-primary);animation:pulse 1.2s ease infinite}.answer-wrap .search-info.success{color:#fff;border:0;background:var(--t-success)}.answer-wrap .search-info.ai-success{color:#fff;border:0;background:var(--t-accent)}.answer-wrap .search-info.warning{color:var(--t-warning);border:1px solid var(--t-warning)}.answer-wrap .answer-tips{font-size:12px;color:var(--t-text3)}.answer-wrap .answer-tips span{margin-right:4px}.answer-wrap .answer-tips .box-tips{display:flex;align-items:center}.answer-wrap .answer-tips .box-tips div{margin:0 4px 0 0!important}.answer-wrap .answer-tips .box-tips span{margin-right:8px}.answer-wrap .answer-list{flex-wrap:wrap;color:var(--t-text);display:flex}.answer-wrap .answer-list .search-info{cursor:pointer!important}.answer-wrap .answer-list div{margin:0 8px 8px 0!important;cursor:pointer!important}.answer-wrap .answer-detail{position:relative;max-height:200px;overflow-y:scroll;scroll-behavior:smooth}.answer-wrap .answer-detail .answer-item{margin-bottom:8px}.answer-wrap .answer-detail .answer-content{margin-top:4px;padding:8px 10px;border-radius:4px;background:var(--t-surface2);font-weight:600;color:var(--t-text);border-left:2px solid var(--t-primary);font-size:13px}.answer-wrap .answer-detail .answer-content .module{padding:1px 4px;border-radius:3px;color:var(--t-text3);border:1px solid var(--t-border2);font-size:10px}.ant-tooltip,.ant-select-dropdown{z-index:99999!important}.config-wrap{min-width:420px!important;width:420px!important;max-width:480px;color:var(--t-text);font-size:13px;padding:10px 16px;box-sizing:border-box}.config-wrap .config{margin-bottom:14px;display:flex;align-items:flex-start;gap:12px;min-height:32px}.config-wrap .config .config-name{display:flex;align-items:flex-start;width:120px;flex-shrink:0;font-size:12px;color:var(--t-text2);line-height:1.5;padding-top:6px}.config-wrap .config .config-input{flex:1;display:flex;align-items:center;justify-content:flex-start;gap:6px;min-width:0;padding-top:2px}.config-wrap .config .config-input .ant-select{width:100%!important;max-width:260px}.config-wrap .config .config-input .ant-input{width:100%!important;max-width:260px}.config-wrap .config .config-input .ant-input-number{width:100px!important}.config-wrap .config .config-input .ant-slider{flex:1;max-width:180px!important;margin:0 10px 0 0}.config-wrap .buy-btn{background:var(--t-primary);padding:5px 12px;border-radius:4px;height:28px;color:#fff;cursor:pointer;font-weight:600;border:none;font-size:12px;white-space:nowrap}.config-wrap .buy-btn a{color:#fff}.config-wrap .switch-item{display:inline-flex!important;width:calc(50% - 6px)!important;margin-right:12px;margin-bottom:12px!important;vertical-align:top;box-sizing:border-box;gap:8px}.config-wrap .switch-item .config-name{flex:1;width:auto!important;padding-top:0;line-height:1.5;word-break:break-word}.config-wrap .switch-item .config-input{width:auto!important;flex:none;padding-top:0}.config-wrap .switch-item .ant-switch{margin-top:0!important}.config-wrap .switch-item:nth-of-type(2n){margin-right:0}.ant-message{z-index:999999!important}.online-search-wrap{color:var(--t-text);width:420px;box-sizing:border-box}.online-search-wrap .config{padding:8px 10px 4px;display:flex}.online-search-wrap .config .config-name{width:150px}.online-search-wrap .config .config-input{display:flex;justify-content:right;flex:1}.online-search-wrap .searth-input-wrap{padding:8px 10px 4px;display:flex}.online-search-wrap .searth-input-wrap .title{padding-top:5px}.online-search-wrap .searth-input-wrap .searth-input{margin-left:16px;flex:1}.online-search-wrap .search-btn{padding-right:10px;display:flex;justify-content:right}.online-search-wrap .result-list{margin-top:8px;max-height:calc(100vh - 200px);overflow:auto}.online-search-wrap .result-list .result-item{margin-bottom:8px}.online-search-wrap .result-list .result-item em{font-weight:600;color:var(--t-warning)}.online-search-wrap .result-list .result-answer{margin-top:4px;padding:8px 10px;border-radius:4px;background:var(--t-surface2);font-weight:600;color:var(--t-text);border-left:2px solid var(--t-primary)}div{margin:0}.ai-wrapper{width:420px}.ai-message-role-avatar{margin:0}.ai-message-content{margin:0;padding:8px 10px;border-radius:6px;background:var(--t-surface2);font-size:14px;-webkit-user-select:text;user-select:text;word-break:break-word;border:1px solid var(--t-border);position:relative;transition:all .2s ease}.ai-message-content pre{white-space:pre-wrap;background:var(--t-bg);color:var(--t-text);padding:10px;border-radius:4px;border:1px solid var(--t-border);font-size:13px}.ai-message-container{display:flex;flex-direction:column;align-items:flex-start}.ai-message-container.user-message{align-items:flex-end}.ai-message-list{height:calc(100vh - 160px);max-height:360px;min-height:200px;overflow-y:auto;display:flex;flex-direction:column;gap:8px}.ai-message-item{width:100%}.user-avatar{display:flex;align-items:center;justify-content:center;width:26px;height:26px;border-radius:4px;border:1px solid var(--t-border);background:var(--t-primary-bg);margin-bottom:4px;font-size:13px}.ai-input-wrapper{padding:8px;margin-top:8px;border-radius:6px;border:1px solid var(--t-border);display:flex;align-items:center;justify-content:center;background:var(--t-surface)}.ai-input-wrapper textarea{flex:1;height:46px;border:none;margin-right:8px;background:transparent;color:var(--t-text);resize:none;outline:none}.send-message-btn{display:flex;align-items:center;justify-content:center;height:34px;border-radius:4px;border:none;padding:0 12px;background:var(--t-primary);color:#fff;cursor:pointer!important;gap:4px;font-weight:600;font-size:13px}.send-message-btn:hover{opacity:.85}.loading-container{display:flex;align-items:center;justify-content:center;gap:4px}.record-wrap{padding-bottom:6px}.record-tip{display:flex;align-items:center;justify-content:space-between;padding:0 8px 6px;color:var(--t-text3);font-size:11px}.record-filter{display:flex;gap:6px;padding:0 8px 8px;flex-wrap:wrap}.filter-btn{padding:3px 8px;color:var(--t-text2);background:var(--t-surface2);border:1px solid var(--t-border);border-radius:3px;cursor:pointer;transition:all .15s ease;font-size:11px}.filter-btn:hover{color:var(--t-primary);border-color:var(--t-primary-border);background:var(--t-primary-bg)}.filter-btn.active{color:var(--t-primary);border-color:var(--t-primary);background:var(--t-primary-bg)}.record-list{display:flex;flex-direction:column;gap:6px;padding:0 8px 6px;max-height:calc(100vh - 160px);overflow-y:auto}.clear-btn{color:var(--t-text2);border-color:var(--t-border);background:var(--t-surface2);border-radius:3px;font-size:12px}.clear-btn:hover,.clear-btn:focus{color:var(--t-danger);border-color:var(--t-danger);background:rgba(220,38,38,.06)}.record-item{padding:8px 10px;border:1px solid var(--t-border);border-radius:4px;background:var(--t-surface);transition:border-color .15s}.record-item:hover{border-color:var(--t-border2)}.record-row{display:flex;align-items:flex-start;line-height:20px;font-size:12px}.record-label{width:60px;flex-shrink:0;color:var(--t-primary);font-weight:600;font-size:11px}.record-value{flex:1;color:var(--t-text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tab-content{flex:1!important;min-width:420px!important;width:420px!important;min-height:320px;max-width:480px;max-height:calc(100vh - 96px);overflow-y:auto;padding:8px 12px;box-sizing:border-box}.ant-tabs-nav-operations{display:none!important}.box-content{min-width:300px;display:flex!important;flex-direction:row!important;gap:0!important}.separator{display:flex;align-items:center;padding:14px 0 10px;font-size:13px;font-weight:700;color:var(--t-text);border-top:1px solid var(--t-border);margin-top:6px}.separator:first-child{border-top:none;margin-top:0}.tips{padding:10px 12px;background:rgba(217,119,6,.06);border:1px solid rgba(217,119,6,.15);border-radius:6px;width:-webkit-fill-available;margin:4px 0 14px;line-height:22px;letter-spacing:.2px;color:var(--t-warning);font-size:12px;box-sizing:border-box}.tips button{background:var(--t-warning)!important;border-color:var(--t-warning)!important;color:#fff!important}.tips a{color:var(--t-primary);text-decoration:underline}.backend-dropdown-overlay{z-index:100001!important}.ant-dropdown-menu{background:var(--t-surface)!important;border:1px solid var(--t-border)!important;border-radius:4px!important;box-shadow:0 4px 16px var(--t-shadow)!important;padding:4px 0!important}.ant-dropdown-menu-item{color:var(--t-text)!important;font-size:12px!important;padding:6px 12px!important}.ant-dropdown-menu-item:hover{background:var(--t-primary-bg)!important;color:var(--t-primary)!important}.ant-dropdown-menu-item-divider{background-color:var(--t-border)!important}.main-wrap{position:fixed;top:0;z-index:99999!important;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;pointer-events:auto}.message-container{margin-bottom:4px;position:absolute;bottom:100%;left:50%;width:100%;transform:translate(-50%);min-width:280px}.main-box{padding:0;font:13px "Segoe UI",system-ui,-apple-system,sans-serif;color:var(--t-text);background:var(--t-bg);box-shadow:0 8px 32px var(--t-shadow),0 0 0 1px var(--t-border);border-radius:8px;letter-spacing:0;overflow:hidden;transition:box-shadow .2s;display:flex;flex-direction:column;max-height:calc(100vh - 60px)}.main-box:hover{box-shadow:0 12px 32px var(--t-shadow),0 0 0 1px var(--t-border2)}.main-box .box-header{display:flex;justify-content:space-between;align-items:center;height:36px;cursor:move;padding:0 10px;background:var(--t-surface);border-bottom:1px solid var(--t-border);flex-shrink:0}.main-box .box-header div{margin:0}.main-box .box-header .header-left{display:flex;align-items:center;gap:6px}.main-box .box-header .script-info{-webkit-user-select:none!important;user-select:none!important;font-weight:700;color:var(--t-primary);letter-spacing:0;font-size:13px;display:flex;align-items:center;gap:4px}.main-box .box-header .script-info:before{content:"";display:inline-block;width:6px;height:6px;border-radius:50%;background:var(--t-success);box-shadow:0 0 4px var(--t-success);margin-right:2px}.main-box .box-header .backend-entry{text-decoration:none;color:var(--t-text2);padding:3px 8px;border-radius:4px;cursor:pointer;-webkit-user-select:none!important;user-select:none!important;transition:all .15s ease;font-size:11px;background:var(--t-surface2);border:1px solid var(--t-border)}.main-box .box-header .backend-entry.active,.main-box .box-header .backend-entry:hover{color:var(--t-primary);background:var(--t-primary-bg);border-color:var(--t-primary-border)}.main-box .box-header .theme-toggle{width:28px;height:28px;border-radius:4px;border:1px solid var(--t-border);background:var(--t-surface2);color:var(--t-text2);cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:14px;transition:all .15s ease}.main-box .box-header .theme-toggle:hover{color:var(--t-primary);border-color:var(--t-primary-border);background:var(--t-primary-bg)}.main-box .box-header .shrink-btn{width:28px;height:28px;border-radius:4px;border:1px solid var(--t-border);background:var(--t-surface2);color:var(--t-text2);cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:14px;transition:all .15s ease}.main-box .box-header .shrink-btn:hover{color:var(--t-danger);border-color:var(--t-danger)}.main-box .box-content{display:flex;flex:1;overflow:hidden;min-height:0}.main-box .box-content.mini{display:none}.mini-ball{position:fixed;display:flex;align-items:center;gap:13px;height:64px;padding:0 30px 0 12px;border-radius:32px;background:linear-gradient(135deg,#0d1117 0%,#161b22 20%,#1a1a2e 40%,#2d1b4e 60%,#1a1a2e 80%,#0d1117 100%);background-size:400% 400%;box-shadow:0 10px 24px rgba(0,0,0,.55),0 0 0 1.5px rgba(139,92,246,.2),0 0 10px rgba(139,92,246,.15),0 0 18px rgba(124,58,237,.09),0 0 26px rgba(139,92,246,.06),inset 0 1px 2px rgba(255,255,255,.08);cursor:pointer;z-index:9999999;user-select:none;transition:transform .3s cubic-bezier(.34,1.56,.64,1),box-shadow .3s ease;animation:miniBallGradient 5s ease infinite,miniBallGlow 2.5s ease-in-out infinite}.mini-ball:hover{transform:scale(1.1) translateY(-4px);box-shadow:0 16px 44px rgba(0,0,0,.65),0 0 0 3px rgba(139,92,246,.35),0 0 20px rgba(139,92,246,.25),0 0 40px rgba(124,58,237,.2),0 0 64px rgba(139,92,246,.15),inset 0 1px 2px rgba(255,255,255,.12)}.mini-ball:active{transform:scale(.94)}.mini-ball-icon{width:48px;height:48px;border-radius:50%;background:rgba(139,92,246,.15);display:flex;flex-direction:column;align-items:center;justify-content:center;color:#fff;font-size:14px;font-weight:800;letter-spacing:1px;backdrop-filter:blur(10px);box-shadow:inset 0 1px 2px rgba(255,255,255,.15),0 0 18px rgba(139,92,246,.15);animation:miniBallIconPulse 2s ease-in-out infinite;gap:1px}.mini-ball-count{font-size:8px;font-weight:700;color:#a78bfa;text-shadow:0 0 6px rgba(167,139,250,.5);line-height:1}.mini-ball:hover .mini-ball-icon{animation:miniBallIconWiggle .5s ease}.mini-ball-body{display:flex;flex-direction:column;align-items:flex-start;gap:3px}.mini-ball-text{color:#fff;font-size:15px;font-weight:800;white-space:nowrap;text-shadow:0 1px 4px rgba(0,0,0,.3);animation:miniBallTextSlide .5s cubic-bezier(.22,1,.36,1)}.mini-ball-sub{color:rgba(255,255,255,.88);font-size:11px;font-weight:500;white-space:nowrap;text-shadow:0 1px 3px rgba(0,0,0,.25);animation:miniBallTextSlide .5s .1s cubic-bezier(.22,1,.36,1) both}.mini-ball-shine{position:absolute;inset:0;border-radius:32px;overflow:hidden;pointer-events:none}.mini-ball-shine:before{content:"";position:absolute;top:0;left:-100%;width:80%;height:100%;background:linear-gradient(90deg,transparent,rgba(255,255,255,.2),transparent);transform:skewX(-20deg);animation:miniBallShine 3.5s ease-in-out infinite}.mini-ball-glow{position:absolute;inset:12px;border-radius:20px;background:linear-gradient(135deg,#1a1a2e,#2d1b4e,#533483,#7c3aed);filter:blur(5px);opacity:.1;z-index:-1;animation:miniBallGlowRotate 10s linear infinite}.mini-ball-orbits{position:absolute;inset:0;border-radius:32px;overflow:hidden;pointer-events:none}.mini-ball-orbits i{position:absolute;width:6px;height:6px;border-radius:50%;background:#a78bfa;box-shadow:0 0 10px #a78bfa;opacity:.65}.mini-ball-orbits i:first-child{top:10px;right:20px;animation:miniBallOrbit1 2.5s linear infinite}.mini-ball-orbits i:last-child{bottom:10px;left:24px;animation:miniBallOrbit2 3s linear infinite}@keyframes miniBallGradient{0%{background-position:0% 50%}50%{background-position:100% 50%}100%{background-position:0% 50%}}@keyframes miniBallGlow{0%,100%{box-shadow:0 9px 22px rgba(0,0,0,.45),0 0 0 1.5px rgba(139,92,246,.18),0 0 10px rgba(139,92,246,.12),0 0 18px rgba(124,58,237,.09),0 0 26px rgba(139,92,246,.06),inset 0 1px 2px rgba(255,255,255,.06)}50%{box-shadow:0 10px 26px rgba(0,0,0,.5),0 0 0 1.5px rgba(139,92,246,.2),0 0 14px rgba(139,92,246,.16),0 0 24px rgba(139,92,246,.1),0 0 32px rgba(124,58,237,.08),inset 0 1px 2px rgba(255,255,255,.08)}}@keyframes miniBallGlowRotate{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}@keyframes miniBallIconPulse{0%,100%{box-shadow:inset 0 1px 2px rgba(255,255,255,.15),0 0 0 0 rgba(139,92,246,.2)}50%{box-shadow:inset 0 1px 2px rgba(255,255,255,.15),0 0 0 14px rgba(139,92,246,0)}}@keyframes miniBallIconWiggle{0%,100%{transform:rotate(0)}25%{transform:rotate(-12deg)}75%{transform:rotate(12deg)}}@keyframes miniBallTextSlide{0%{opacity:0;transform:translateX(-14px)}100%{opacity:1;transform:translateX(0)}}@keyframes miniBallShine{0%,100%{left:-100%}50%,70%{left:150%}}@keyframes miniBallOrbit1{0%{transform:translate(0,0) scale(1);opacity:.6}50%{transform:translate(-18px,14px) scale(.6);opacity:.2}100%{transform:translate(0,0) scale(1);opacity:.6}}@keyframes miniBallOrbit2{0%{transform:translate(0,0) scale(.6);opacity:.2}50%{transform:translate(14px,-12px) scale(1);opacity:.6}100%{transform:translate(0,0) scale(.6);opacity:.2}}.ant-btn{border-radius:4px!important;font-size:12px!important}.ant-btn-primary{background:var(--t-primary)!important;border-color:var(--t-primary)!important}.ant-btn-primary:hover{opacity:.85!important}.ant-input,.ant-input-affix-wrapper{background:var(--t-surface)!important;border-color:var(--t-border2)!important;border-radius:4px!important;color:var(--t-text)!important;font-size:12px!important}.ant-input:focus,.ant-input-affix-wrapper-focused{border-color:var(--t-primary)!important;box-shadow:0 0 0 2px var(--t-primary-border)!important}.ant-input:hover,.ant-input-affix-wrapper:hover{border-color:var(--t-border2)!important}.ant-input-number{background:var(--t-surface)!important;border-color:var(--t-border2)!important;border-radius:4px!important;color:var(--t-text)!important;font-size:12px!important}.ant-input-number-input{color:var(--t-text)!important;background:transparent!important}.ant-input-number-focused{border-color:var(--t-primary)!important;box-shadow:0 0 0 2px var(--t-primary-border)!important}.ant-input-number-handler-wrap{background:var(--t-surface2)!important;border-left-color:var(--t-border)!important}.ant-input-number-handler span{color:var(--t-text2)!important}.ant-select-selector{background:var(--t-surface)!important;border-color:var(--t-border2)!important;border-radius:4px!important;color:var(--t-text)!important;font-size:12px!important;min-height:30px!important}.config-wrap .ant-select{width:100%!important;max-width:220px}.ant-select-selection-item{color:var(--t-text)!important}.ant-select-arrow{color:var(--t-text3)!important}.ant-switch-checked{background:var(--t-primary)!important}.config-wrap .ant-switch{margin-top:2px}.ant-card{background:var(--t-surface)!important;border-color:var(--t-border)!important;border-radius:6px!important;color:var(--t-text)!important;font-size:12px!important}.ant-modal-content{background:var(--t-surface)!important;border:1px solid var(--t-border)!important;border-radius:6px!important}.ant-modal-header{background:var(--t-surface)!important;border-bottom:1px solid var(--t-border)!important}.ant-modal-title{color:var(--t-text)!important;font-size:14px!important}.ant-modal-close-icon{color:var(--t-text3)!important}.ant-modal-body{color:var(--t-text)!important}.ant-checkbox-checked .ant-checkbox-inner{background:var(--t-primary)!important;border-color:var(--t-primary)!important}.ant-radio-checked .ant-radio-inner{border-color:var(--t-primary)!important}.ant-radio-inner:after{background:var(--t-primary)!important}.ant-message-notice-content{background:var(--t-surface)!important;border:1px solid var(--t-border)!important;border-radius:4px!important;box-shadow:0 4px 16px var(--t-shadow)!important;color:var(--t-text)!important}.ant-tooltip-inner{background:var(--t-surface)!important;border:1px solid var(--t-border)!important;border-radius:3px!important;font-size:11px!important;color:var(--t-text)!important}.ant-alert{border-radius:6px!important;font-size:12px!important;margin-bottom:12px!important;padding:10px 12px!important}.ant-alert-message{color:var(--t-text)!important;line-height:1.8!important}.ant-alert .ant-btn-primary{background:var(--t-warning)!important;border-color:var(--t-warning)!important}.ant-tabs{display:flex!important;flex-direction:column!important;width:80px!important;flex-shrink:0;border-right:1px solid var(--t-border)!important;background:var(--t-surface)!important;margin:0!important;height:auto!important}.ant-tabs-nav{margin:0!important;width:80px!important;border:none!important}.ant-tabs-nav::before{border:none!important}.ant-tabs-nav-wrap{width:80px!important}.ant-tabs-nav-list{flex-direction:column!important;width:80px!important;gap:0!important}.ant-tabs-tab{padding:10px 4px!important;margin:0!important;justify-content:center!important;width:80px!important;font-size:12px!important;border:none!important;background:transparent!important;border-radius:0!important}.ant-tabs-tab .ant-tabs-tab-btn{font-size:12px!important;text-align:center!important;padding:0!important;color:var(--t-text)!important}.ant-tabs-tab:hover{background:var(--t-primary-bg)!important}.ant-tabs-tab-active{background:var(--t-primary-bg)!important;position:relative}.ant-tabs-tab-active:after{content:"";position:absolute;left:0;top:0;bottom:0;width:2px;background:var(--t-primary)}.ant-tabs-tab-active .ant-tabs-tab-btn{color:var(--t-primary)!important;font-weight:600!important}.ant-tabs-ink-bar{display:none!important}.ant-tabs-content-holder{display:none!important}.ant-tabs-content{display:none!important}.ant-tabs-top>.ant-tabs-nav::before{border:none!important}@keyframes pulse{0%,100%{opacity:1}50%{opacity:.5}}@keyframes floatPulse{0%,100%{box-shadow:0 4px 16px var(--t-shadow),0 0 0 1px rgba(255,255,255,.1)}50%{box-shadow:0 4px 24px var(--t-shadow),0 0 0 3px var(--t-primary-border)}}@keyframes floatRing{0%{opacity:.6;transform:scale(1)}100%{opacity:0;transform:scale(1.3)}} '); (function (vue, Antd, pinia, marked, rxjs, md5, DOMPurify) { 'use strict'; var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); function bound01(n, max) { if (isOnePointZero(n)) { n = "100%"; } var isPercent = isPercentage(n); n = max === 360 ? n : Math.min(max, Math.max(0, parseFloat(n))); if (isPercent) { n = parseInt(String(n * max), 10) / 100; } if (Math.abs(n - max) < 1e-6) { return 1; } if (max === 360) { n = (n < 0 ? n % max + max : n % max) / parseFloat(String(max)); } else { n = n % max / parseFloat(String(max)); } return n; } function isOnePointZero(n) { return typeof n === "string" && n.indexOf(".") !== -1 && parseFloat(n) === 1; } function isPercentage(n) { return typeof n === "string" && n.indexOf("%") !== -1; } function boundAlpha(a) { a = parseFloat(a); if (isNaN(a) || a < 0 || a > 1) { a = 1; } return a; } function convertToPercentage(n) { if (n <= 1) { return "".concat(Number(n) * 100, "%"); } return n; } function pad2(c) { return c.length === 1 ? "0" + c : String(c); } function rgbToRgb(r, g, b) { return { r: bound01(r, 255) * 255, g: bound01(g, 255) * 255, b: bound01(b, 255) * 255 }; } function hue2rgb(p, q, t) { if (t < 0) { t += 1; } if (t > 1) { t -= 1; } if (t < 1 / 6) { return p + (q - p) * (6 * t); } if (t < 1 / 2) { return q; } if (t < 2 / 3) { return p + (q - p) * (2 / 3 - t) * 6; } return p; } function hslToRgb(h2, s, l) { var r; var g; var b; h2 = bound01(h2, 360); s = bound01(s, 100); l = bound01(l, 100); if (s === 0) { g = l; b = l; r = l; } else { var q = l < 0.5 ? l * (1 + s) : l + s - l * s; var p = 2 * l - q; r = hue2rgb(p, q, h2 + 1 / 3); g = hue2rgb(p, q, h2); b = hue2rgb(p, q, h2 - 1 / 3); } return { r: r * 255, g: g * 255, b: b * 255 }; } function rgbToHsv(r, g, b) { r = bound01(r, 255); g = bound01(g, 255); b = bound01(b, 255); var max = Math.max(r, g, b); var min = Math.min(r, g, b); var h2 = 0; var v = max; var d = max - min; var s = max === 0 ? 0 : d / max; if (max === min) { h2 = 0; } else { switch (max) { case r: h2 = (g - b) / d + (g < b ? 6 : 0); break; case g: h2 = (b - r) / d + 2; break; case b: h2 = (r - g) / d + 4; break; } h2 /= 6; } return { h: h2, s, v }; } function hsvToRgb(h2, s, v) { h2 = bound01(h2, 360) * 6; s = bound01(s, 100); v = bound01(v, 100); var i = Math.floor(h2); var f = h2 - i; var p = v * (1 - s); var q = v * (1 - f * s); var t = v * (1 - (1 - f) * s); var mod = i % 6; var r = [v, q, p, p, t, v][mod]; var g = [t, v, v, q, p, p][mod]; var b = [p, p, t, v, v, q][mod]; return { r: r * 255, g: g * 255, b: b * 255 }; } function rgbToHex(r, g, b, allow3Char) { var hex = [ pad2(Math.round(r).toString(16)), pad2(Math.round(g).toString(16)), pad2(Math.round(b).toString(16)) ]; return hex.join(""); } function convertHexToDecimal(h2) { return parseIntFromHex(h2) / 255; } function parseIntFromHex(val) { return parseInt(val, 16); } var names = { aliceblue: "#f0f8ff", antiquewhite: "#faebd7", aqua: "#00ffff", aquamarine: "#7fffd4", azure: "#f0ffff", beige: "#f5f5dc", bisque: "#ffe4c4", black: "#000000", blanchedalmond: "#ffebcd", blue: "#0000ff", blueviolet: "#8a2be2", brown: "#a52a2a", burlywood: "#deb887", cadetblue: "#5f9ea0", chartreuse: "#7fff00", chocolate: "#d2691e", coral: "#ff7f50", cornflowerblue: "#6495ed", cornsilk: "#fff8dc", crimson: "#dc143c", cyan: "#00ffff", darkblue: "#00008b", darkcyan: "#008b8b", darkgoldenrod: "#b8860b", darkgray: "#a9a9a9", darkgreen: "#006400", darkgrey: "#a9a9a9", darkkhaki: "#bdb76b", darkmagenta: "#8b008b", darkolivegreen: "#556b2f", darkorange: "#ff8c00", darkorchid: "#9932cc", darkred: "#8b0000", darksalmon: "#e9967a", darkseagreen: "#8fbc8f", darkslateblue: "#483d8b", darkslategray: "#2f4f4f", darkslategrey: "#2f4f4f", darkturquoise: "#00ced1", darkviolet: "#9400d3", deeppink: "#ff1493", deepskyblue: "#00bfff", dimgray: "#696969", dimgrey: "#696969", dodgerblue: "#1e90ff", firebrick: "#b22222", floralwhite: "#fffaf0", forestgreen: "#228b22", fuchsia: "#ff00ff", gainsboro: "#dcdcdc", ghostwhite: "#f8f8ff", goldenrod: "#daa520", gold: "#ffd700", gray: "#808080", green: "#008000", greenyellow: "#adff2f", grey: "#808080", honeydew: "#f0fff0", hotpink: "#ff69b4", indianred: "#cd5c5c", indigo: "#4b0082", ivory: "#fffff0", khaki: "#f0e68c", lavenderblush: "#fff0f5", lavender: "#e6e6fa", lawngreen: "#7cfc00", lemonchiffon: "#fffacd", lightblue: "#add8e6", lightcoral: "#f08080", lightcyan: "#e0ffff", lightgoldenrodyellow: "#fafad2", lightgray: "#d3d3d3", lightgreen: "#90ee90", lightgrey: "#d3d3d3", lightpink: "#ffb6c1", lightsalmon: "#ffa07a", lightseagreen: "#20b2aa", lightskyblue: "#87cefa", lightslategray: "#778899", lightslategrey: "#778899", lightsteelblue: "#b0c4de", lightyellow: "#ffffe0", lime: "#00ff00", limegreen: "#32cd32", linen: "#faf0e6", magenta: "#ff00ff", maroon: "#800000", mediumaquamarine: "#66cdaa", mediumblue: "#0000cd", mediumorchid: "#ba55d3", mediumpurple: "#9370db", mediumseagreen: "#3cb371", mediumslateblue: "#7b68ee", mediumspringgreen: "#00fa9a", mediumturquoise: "#48d1cc", mediumvioletred: "#c71585", midnightblue: "#191970", mintcream: "#f5fffa", mistyrose: "#ffe4e1", moccasin: "#ffe4b5", navajowhite: "#ffdead", navy: "#000080", oldlace: "#fdf5e6", olive: "#808000", olivedrab: "#6b8e23", orange: "#ffa500", orangered: "#ff4500", orchid: "#da70d6", palegoldenrod: "#eee8aa", palegreen: "#98fb98", paleturquoise: "#afeeee", palevioletred: "#db7093", papayawhip: "#ffefd5", peachpuff: "#ffdab9", peru: "#cd853f", pink: "#ffc0cb", plum: "#dda0dd", powderblue: "#b0e0e6", purple: "#800080", rebeccapurple: "#663399", red: "#ff0000", rosybrown: "#bc8f8f", royalblue: "#4169e1", saddlebrown: "#8b4513", salmon: "#fa8072", sandybrown: "#f4a460", seagreen: "#2e8b57", seashell: "#fff5ee", sienna: "#a0522d", silver: "#c0c0c0", skyblue: "#87ceeb", slateblue: "#6a5acd", slategray: "#708090", slategrey: "#708090", snow: "#fffafa", springgreen: "#00ff7f", steelblue: "#4682b4", tan: "#d2b48c", teal: "#008080", thistle: "#d8bfd8", tomato: "#ff6347", turquoise: "#40e0d0", violet: "#ee82ee", wheat: "#f5deb3", white: "#ffffff", whitesmoke: "#f5f5f5", yellow: "#ffff00", yellowgreen: "#9acd32" }; function inputToRGB(color) { var rgb = { r: 0, g: 0, b: 0 }; var a = 1; var s = null; var v = null; var l = null; var ok = false; var format = false; if (typeof color === "string") { color = stringInputToObject(color); } if (typeof color === "object") { if (isValidCSSUnit(color.r) && isValidCSSUnit(color.g) && isValidCSSUnit(color.b)) { rgb = rgbToRgb(color.r, color.g, color.b); ok = true; format = String(color.r).substr(-1) === "%" ? "prgb" : "rgb"; } else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.v)) { s = convertToPercentage(color.s); v = convertToPercentage(color.v); rgb = hsvToRgb(color.h, s, v); ok = true; format = "hsv"; } else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.l)) { s = convertToPercentage(color.s); l = convertToPercentage(color.l); rgb = hslToRgb(color.h, s, l); ok = true; format = "hsl"; } if (Object.prototype.hasOwnProperty.call(color, "a")) { a = color.a; } } a = boundAlpha(a); return { ok, format: color.format || format, r: Math.min(255, Math.max(rgb.r, 0)), g: Math.min(255, Math.max(rgb.g, 0)), b: Math.min(255, Math.max(rgb.b, 0)), a }; } var CSS_INTEGER = "[-\\+]?\\d+%?"; var CSS_NUMBER = "[-\\+]?\\d*\\.\\d+%?"; var CSS_UNIT = "(?:".concat(CSS_NUMBER, ")|(?:").concat(CSS_INTEGER, ")"); var PERMISSIVE_MATCH3 = "[\\s|\\(]+(".concat(CSS_UNIT, ")[,|\\s]+(").concat(CSS_UNIT, ")[,|\\s]+(").concat(CSS_UNIT, ")\\s*\\)?"); var PERMISSIVE_MATCH4 = "[\\s|\\(]+(".concat(CSS_UNIT, ")[,|\\s]+(").concat(CSS_UNIT, ")[,|\\s]+(").concat(CSS_UNIT, ")[,|\\s]+(").concat(CSS_UNIT, ")\\s*\\)?"); var matchers = { CSS_UNIT: new RegExp(CSS_UNIT), rgb: new RegExp("rgb" + PERMISSIVE_MATCH3), rgba: new RegExp("rgba" + PERMISSIVE_MATCH4), hsl: new RegExp("hsl" + PERMISSIVE_MATCH3), hsla: new RegExp("hsla" + PERMISSIVE_MATCH4), hsv: new RegExp("hsv" + PERMISSIVE_MATCH3), hsva: new RegExp("hsva" + PERMISSIVE_MATCH4), hex3: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/, hex6: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/, hex4: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/, hex8: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/ }; function stringInputToObject(color) { color = color.trim().toLowerCase(); if (color.length === 0) { return false; } var named = false; if (names[color]) { color = names[color]; named = true; } else if (color === "transparent") { return { r: 0, g: 0, b: 0, a: 0, format: "name" }; } var match = matchers.rgb.exec(color); if (match) { return { r: match[1], g: match[2], b: match[3] }; } match = matchers.rgba.exec(color); if (match) { return { r: match[1], g: match[2], b: match[3], a: match[4] }; } match = matchers.hsl.exec(color); if (match) { return { h: match[1], s: match[2], l: match[3] }; } match = matchers.hsla.exec(color); if (match) { return { h: match[1], s: match[2], l: match[3], a: match[4] }; } match = matchers.hsv.exec(color); if (match) { return { h: match[1], s: match[2], v: match[3] }; } match = matchers.hsva.exec(color); if (match) { return { h: match[1], s: match[2], v: match[3], a: match[4] }; } match = matchers.hex8.exec(color); if (match) { return { r: parseIntFromHex(match[1]), g: parseIntFromHex(match[2]), b: parseIntFromHex(match[3]), a: convertHexToDecimal(match[4]), format: named ? "name" : "hex8" }; } match = matchers.hex6.exec(color); if (match) { return { r: parseIntFromHex(match[1]), g: parseIntFromHex(match[2]), b: parseIntFromHex(match[3]), format: named ? "name" : "hex" }; } match = matchers.hex4.exec(color); if (match) { return { r: parseIntFromHex(match[1] + match[1]), g: parseIntFromHex(match[2] + match[2]), b: parseIntFromHex(match[3] + match[3]), a: convertHexToDecimal(match[4] + match[4]), format: named ? "name" : "hex8" }; } match = matchers.hex3.exec(color); if (match) { return { r: parseIntFromHex(match[1] + match[1]), g: parseIntFromHex(match[2] + match[2]), b: parseIntFromHex(match[3] + match[3]), format: named ? "name" : "hex" }; } return false; } function isValidCSSUnit(color) { return Boolean(matchers.CSS_UNIT.exec(String(color))); } var hueStep = 2; var saturationStep = 0.16; var saturationStep2 = 0.05; var brightnessStep1 = 0.05; var brightnessStep2 = 0.15; var lightColorCount = 5; var darkColorCount = 4; var darkColorMap = [{ index: 7, opacity: 0.15 }, { index: 6, opacity: 0.25 }, { index: 5, opacity: 0.3 }, { index: 5, opacity: 0.45 }, { index: 5, opacity: 0.65 }, { index: 5, opacity: 0.85 }, { index: 4, opacity: 0.9 }, { index: 3, opacity: 0.95 }, { index: 2, opacity: 0.97 }, { index: 1, opacity: 0.98 }]; function toHsv(_ref) { var r = _ref.r, g = _ref.g, b = _ref.b; var hsv = rgbToHsv(r, g, b); return { h: hsv.h * 360, s: hsv.s, v: hsv.v }; } function toHex(_ref2) { var r = _ref2.r, g = _ref2.g, b = _ref2.b; return "#".concat(rgbToHex(r, g, b)); } function mix(rgb1, rgb2, amount) { var p = amount / 100; var rgb = { r: (rgb2.r - rgb1.r) * p + rgb1.r, g: (rgb2.g - rgb1.g) * p + rgb1.g, b: (rgb2.b - rgb1.b) * p + rgb1.b }; return rgb; } function getHue(hsv, i, light) { var hue; if (Math.round(hsv.h) >= 60 && Math.round(hsv.h) <= 240) { hue = light ? Math.round(hsv.h) - hueStep * i : Math.round(hsv.h) + hueStep * i; } else { hue = light ? Math.round(hsv.h) + hueStep * i : Math.round(hsv.h) - hueStep * i; } if (hue < 0) { hue += 360; } else if (hue >= 360) { hue -= 360; } return hue; } function getSaturation(hsv, i, light) { if (hsv.h === 0 && hsv.s === 0) { return hsv.s; } var saturation; if (light) { saturation = hsv.s - saturationStep * i; } else if (i === darkColorCount) { saturation = hsv.s + saturationStep; } else { saturation = hsv.s + saturationStep2 * i; } if (saturation > 1) { saturation = 1; } if (light && i === lightColorCount && saturation > 0.1) { saturation = 0.1; } if (saturation < 0.06) { saturation = 0.06; } return Number(saturation.toFixed(2)); } function getValue(hsv, i, light) { var value; if (light) { value = hsv.v + brightnessStep1 * i; } else { value = hsv.v - brightnessStep2 * i; } if (value > 1) { value = 1; } return Number(value.toFixed(2)); } function generate$1(color) { var opts = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {}; var patterns = []; var pColor = inputToRGB(color); for (var i = lightColorCount; i > 0; i -= 1) { var hsv = toHsv(pColor); var colorString = toHex(inputToRGB({ h: getHue(hsv, i, true), s: getSaturation(hsv, i, true), v: getValue(hsv, i, true) })); patterns.push(colorString); } patterns.push(toHex(pColor)); for (var _i = 1; _i <= darkColorCount; _i += 1) { var _hsv = toHsv(pColor); var _colorString = toHex(inputToRGB({ h: getHue(_hsv, _i), s: getSaturation(_hsv, _i), v: getValue(_hsv, _i) })); patterns.push(_colorString); } if (opts.theme === "dark") { return darkColorMap.map(function(_ref3) { var index = _ref3.index, opacity = _ref3.opacity; var darkColorString = toHex(mix(inputToRGB(opts.backgroundColor || "#141414"), inputToRGB(patterns[index]), opacity * 100)); return darkColorString; }); } return patterns; } var presetPrimaryColors = { red: "#F5222D", volcano: "#FA541C", orange: "#FA8C16", gold: "#FAAD14", yellow: "#FADB14", lime: "#A0D911", green: "#52C41A", cyan: "#13C2C2", blue: "#1890FF", geekblue: "#2F54EB", purple: "#722ED1", magenta: "#EB2F96", grey: "#666666" }; var presetPalettes = {}; var presetDarkPalettes = {}; Object.keys(presetPrimaryColors).forEach(function(key) { presetPalettes[key] = generate$1(presetPrimaryColors[key]); presetPalettes[key].primary = presetPalettes[key][5]; presetDarkPalettes[key] = generate$1(presetPrimaryColors[key], { theme: "dark", backgroundColor: "#141414" }); presetDarkPalettes[key].primary = presetDarkPalettes[key][5]; }); var blue = presetPalettes.blue; var contextKey = Symbol("iconContext"); var useInjectIconContext = function useInjectIconContext2() { return vue.inject(contextKey, { prefixCls: vue.ref("anticon"), rootClassName: vue.ref(""), csp: vue.ref() }); }; function canUseDom() { return !!(typeof window !== "undefined" && window.document && window.document.createElement); } function contains(root, n) { if (!root) { return false; } if (root.contains) { return root.contains(n); } return false; } var APPEND_ORDER = "data-vc-order"; var MARK_KEY = "vc-icon-key"; var containerCache = /* @__PURE__ */ new Map(); function getMark() { var _ref = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {}, mark = _ref.mark; if (mark) { return mark.startsWith("data-") ? mark : "data-".concat(mark); } return MARK_KEY; } function getContainer(option) { if (option.attachTo) { return option.attachTo; } var head = document.querySelector("head"); return head || document.body; } function getOrder(prepend) { if (prepend === "queue") { return "prependQueue"; } return prepend ? "prepend" : "append"; } function findStyles(container) { return Array.from((containerCache.get(container) || container).children).filter(function(node) { return node.tagName === "STYLE"; }); } function injectCSS(css) { var option = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {}; if (!canUseDom()) { return null; } var csp = option.csp, prepend = option.prepend; var styleNode = document.createElement("style"); styleNode.setAttribute(APPEND_ORDER, getOrder(prepend)); if (csp && csp.nonce) { styleNode.nonce = csp.nonce; } styleNode.innerHTML = css; var container = getContainer(option); var firstChild = container.firstChild; if (prepend) { if (prepend === "queue") { var existStyle = findStyles(container).filter(function(node) { return ["prepend", "prependQueue"].includes(node.getAttribute(APPEND_ORDER)); }); if (existStyle.length) { container.insertBefore(styleNode, existStyle[existStyle.length - 1].nextSibling); return styleNode; } } container.insertBefore(styleNode, firstChild); } else { container.appendChild(styleNode); } return styleNode; } function findExistNode(key) { var option = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {}; var container = getContainer(option); return findStyles(container).find(function(node) { return node.getAttribute(getMark(option)) === key; }); } function syncRealContainer(container, option) { var cachedRealContainer = containerCache.get(container); if (!cachedRealContainer || !contains(document, cachedRealContainer)) { var placeholderStyle = injectCSS("", option); var parentNode = placeholderStyle.parentNode; containerCache.set(container, parentNode); container.removeChild(placeholderStyle); } } function updateCSS(css, key) { var option = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : {}; var container = getContainer(option); syncRealContainer(container, option); var existNode = findExistNode(key, option); if (existNode) { if (option.csp && option.csp.nonce && existNode.nonce !== option.csp.nonce) { existNode.nonce = option.csp.nonce; } if (existNode.innerHTML !== css) { existNode.innerHTML = css; } return existNode; } var newNode = injectCSS(css, option); newNode.setAttribute(getMark(option), key); return newNode; } function _objectSpread$8(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? Object(arguments[i]) : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === "function") { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function(sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function(key) { _defineProperty$8(target, key, source[key]); }); } return target; } function _defineProperty$8(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function warning(valid, message2) { } function isIconDefinition(target) { return typeof target === "object" && typeof target.name === "string" && typeof target.theme === "string" && (typeof target.icon === "object" || typeof target.icon === "function"); } function generate(node, key, rootProps) { if (!rootProps) { return vue.h(node.tag, _objectSpread$8({ key }, node.attrs), (node.children || []).map(function(child, index) { return generate(child, "".concat(key, "-").concat(node.tag, "-").concat(index)); })); } return vue.h(node.tag, _objectSpread$8({ key }, rootProps, node.attrs), (node.children || []).map(function(child, index) { return generate(child, "".concat(key, "-").concat(node.tag, "-").concat(index)); })); } function getSecondaryColor(primaryColor) { return generate$1(primaryColor)[0]; } function normalizeTwoToneColors(twoToneColor) { if (!twoToneColor) { return []; } return Array.isArray(twoToneColor) ? twoToneColor : [twoToneColor]; } var iconStyles = "\n.anticon {\n display: inline-block;\n color: inherit;\n font-style: normal;\n line-height: 0;\n text-align: center;\n text-transform: none;\n vertical-align: -0.125em;\n text-rendering: optimizeLegibility;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\n.anticon > * {\n line-height: 1;\n}\n\n.anticon svg {\n display: inline-block;\n}\n\n.anticon::before {\n display: none;\n}\n\n.anticon .anticon-icon {\n display: block;\n}\n\n.anticon[tabindex] {\n cursor: pointer;\n}\n\n.anticon-spin::before,\n.anticon-spin {\n display: inline-block;\n -webkit-animation: loadingCircle 1s infinite linear;\n animation: loadingCircle 1s infinite linear;\n}\n\n@-webkit-keyframes loadingCircle {\n 100% {\n -webkit-transform: rotate(360deg);\n transform: rotate(360deg);\n }\n}\n\n@keyframes loadingCircle {\n 100% {\n -webkit-transform: rotate(360deg);\n transform: rotate(360deg);\n }\n}\n"; function getRoot(ele) { return ele && ele.getRootNode && ele.getRootNode(); } function inShadow(ele) { if (!canUseDom()) { return false; } return getRoot(ele) instanceof ShadowRoot; } function getShadowRoot(ele) { return inShadow(ele) ? getRoot(ele) : null; } var useInsertStyles = function useInsertStyles2() { var _useInjectIconContext = useInjectIconContext(), prefixCls = _useInjectIconContext.prefixCls, csp = _useInjectIconContext.csp; var instance = vue.getCurrentInstance(); var mergedStyleStr = iconStyles; if (prefixCls) { mergedStyleStr = mergedStyleStr.replace(/anticon/g, prefixCls.value); } vue.nextTick(function() { if (!canUseDom()) { return; } var ele = instance.vnode.el; var shadowRoot = getShadowRoot(ele); updateCSS(mergedStyleStr, "@ant-design-vue-icons", { prepend: true, csp: csp.value, attachTo: shadowRoot }); }); }; var _excluded$1 = ["icon", "primaryColor", "secondaryColor"]; function _objectWithoutProperties$1(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose$1(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; } function _objectWithoutPropertiesLoose$1(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } function _objectSpread$7(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? Object(arguments[i]) : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === "function") { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function(sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function(key) { _defineProperty$7(target, key, source[key]); }); } return target; } function _defineProperty$7(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } var twoToneColorPalette = vue.reactive({ primaryColor: "#333", secondaryColor: "#E6E6E6", calculated: false }); function setTwoToneColors(_ref) { var primaryColor = _ref.primaryColor, secondaryColor = _ref.secondaryColor; twoToneColorPalette.primaryColor = primaryColor; twoToneColorPalette.secondaryColor = secondaryColor || getSecondaryColor(primaryColor); twoToneColorPalette.calculated = !!secondaryColor; } function getTwoToneColors() { return _objectSpread$7({}, twoToneColorPalette); } var IconBase = function IconBase2(props, context) { var _props$context$attrs = _objectSpread$7({}, props, context.attrs), icon = _props$context$attrs.icon, primaryColor = _props$context$attrs.primaryColor, secondaryColor = _props$context$attrs.secondaryColor, restProps = _objectWithoutProperties$1(_props$context$attrs, _excluded$1); var colors = twoToneColorPalette; if (primaryColor) { colors = { primaryColor, secondaryColor: secondaryColor || getSecondaryColor(primaryColor) }; } warning(isIconDefinition(icon)); if (!isIconDefinition(icon)) { return null; } var target = icon; if (target && typeof target.icon === "function") { target = _objectSpread$7({}, target, { icon: target.icon(colors.primaryColor, colors.secondaryColor) }); } return generate(target.icon, "svg-".concat(target.name), _objectSpread$7({}, restProps, { "data-icon": target.name, width: "1em", height: "1em", fill: "currentColor", "aria-hidden": "true" })); }; IconBase.props = { icon: Object, primaryColor: String, secondaryColor: String, focusable: String }; IconBase.inheritAttrs = false; IconBase.displayName = "IconBase"; IconBase.getTwoToneColors = getTwoToneColors; IconBase.setTwoToneColors = setTwoToneColors; function _slicedToArray$1(arr, i) { return _arrayWithHoles$1(arr) || _iterableToArrayLimit$1(arr, i) || _unsupportedIterableToArray$1(arr, i) || _nonIterableRest$1(); } function _nonIterableRest$1() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray$1(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray$1(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray$1(o, minLen); } function _arrayLikeToArray$1(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } function _iterableToArrayLimit$1(arr, i) { var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"]; if (_i == null) return; var _arr = []; var _n = true; var _d = false; var _s, _e; try { for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } function _arrayWithHoles$1(arr) { if (Array.isArray(arr)) return arr; } function setTwoToneColor(twoToneColor) { var _normalizeTwoToneColo = normalizeTwoToneColors(twoToneColor), _normalizeTwoToneColo2 = _slicedToArray$1(_normalizeTwoToneColo, 2), primaryColor = _normalizeTwoToneColo2[0], secondaryColor = _normalizeTwoToneColo2[1]; return IconBase.setTwoToneColors({ primaryColor, secondaryColor }); } function getTwoToneColor() { var colors = IconBase.getTwoToneColors(); if (!colors.calculated) { return colors.primaryColor; } return [colors.primaryColor, colors.secondaryColor]; } var InsertStyles = vue.defineComponent({ name: "InsertStyles", setup: function setup() { useInsertStyles(); return function() { return null; }; } }); var _excluded = ["class", "icon", "spin", "rotate", "tabindex", "twoToneColor", "onClick"]; function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } function _iterableToArrayLimit(arr, i) { var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"]; if (_i == null) return; var _arr = []; var _n = true; var _d = false; var _s, _e; try { for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } function _objectSpread$6(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? Object(arguments[i]) : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === "function") { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function(sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function(key) { _defineProperty$6(target, key, source[key]); }); } return target; } function _defineProperty$6(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; } function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } setTwoToneColor(blue.primary); var Icon = function Icon2(props, context) { var _classObj; var _props$context$attrs = _objectSpread$6({}, props, context.attrs), cls = _props$context$attrs["class"], icon = _props$context$attrs.icon, spin = _props$context$attrs.spin, rotate = _props$context$attrs.rotate, tabindex = _props$context$attrs.tabindex, twoToneColor = _props$context$attrs.twoToneColor, onClick = _props$context$attrs.onClick, restProps = _objectWithoutProperties(_props$context$attrs, _excluded); var _useInjectIconContext = useInjectIconContext(), prefixCls = _useInjectIconContext.prefixCls, rootClassName = _useInjectIconContext.rootClassName; var classObj = (_classObj = {}, _defineProperty$6(_classObj, rootClassName.value, !!rootClassName.value), _defineProperty$6(_classObj, prefixCls.value, true), _defineProperty$6(_classObj, "".concat(prefixCls.value, "-").concat(icon.name), Boolean(icon.name)), _defineProperty$6(_classObj, "".concat(prefixCls.value, "-spin"), !!spin || icon.name === "loading"), _classObj); var iconTabIndex = tabindex; if (iconTabIndex === void 0 && onClick) { iconTabIndex = -1; } var svgStyle = rotate ? { msTransform: "rotate(".concat(rotate, "deg)"), transform: "rotate(".concat(rotate, "deg)") } : void 0; var _normalizeTwoToneColo = normalizeTwoToneColors(twoToneColor), _normalizeTwoToneColo2 = _slicedToArray(_normalizeTwoToneColo, 2), primaryColor = _normalizeTwoToneColo2[0], secondaryColor = _normalizeTwoToneColo2[1]; return vue.createVNode("span", _objectSpread$6({ "role": "img", "aria-label": icon.name }, restProps, { "onClick": onClick, "class": [classObj, cls], "tabindex": iconTabIndex }), [vue.createVNode(IconBase, { "icon": icon, "primaryColor": primaryColor, "secondaryColor": secondaryColor, "style": svgStyle }, null), vue.createVNode(InsertStyles, null, null)]); }; Icon.props = { spin: Boolean, rotate: Number, icon: Object, twoToneColor: [String, Array] }; Icon.displayName = "AntdIcon"; Icon.inheritAttrs = false; Icon.getTwoToneColor = getTwoToneColor; Icon.setTwoToneColor = setTwoToneColor; var BulbOutlined$1 = { "icon": { "tag": "svg", "attrs": { "viewBox": "64 64 896 896", "focusable": "false" }, "children": [{ "tag": "path", "attrs": { "d": "M632 888H392c-4.4 0-8 3.6-8 8v32c0 17.7 14.3 32 32 32h192c17.7 0 32-14.3 32-32v-32c0-4.4-3.6-8-8-8zM512 64c-181.1 0-328 146.9-328 328 0 121.4 66 227.4 164 284.1V792c0 17.7 14.3 32 32 32h264c17.7 0 32-14.3 32-32V676.1c98-56.7 164-162.7 164-284.1 0-181.1-146.9-328-328-328zm127.9 549.8L604 634.6V752H420V634.6l-35.9-20.8C305.4 568.3 256 484.5 256 392c0-141.4 114.6-256 256-256s256 114.6 256 256c0 92.5-49.4 176.3-128.1 221.8z" } }] }, "name": "bulb", "theme": "outlined" }; function _objectSpread$5(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? Object(arguments[i]) : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === "function") { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function(sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function(key) { _defineProperty$5(target, key, source[key]); }); } return target; } function _defineProperty$5(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } var BulbOutlined = function BulbOutlined2(props, context) { var p = _objectSpread$5({}, props, context.attrs); return vue.createVNode(Icon, _objectSpread$5({}, p, { "icon": BulbOutlined$1 }), null); }; BulbOutlined.displayName = "BulbOutlined"; BulbOutlined.inheritAttrs = false; var ExpandOutlined$1 = { "icon": { "tag": "svg", "attrs": { "viewBox": "64 64 896 896", "focusable": "false" }, "children": [{ "tag": "defs", "attrs": {}, "children": [{ "tag": "style", "attrs": {} }] }, { "tag": "path", "attrs": { "d": "M342 88H120c-17.7 0-32 14.3-32 32v224c0 8.8 7.2 16 16 16h48c8.8 0 16-7.2 16-16V168h174c8.8 0 16-7.2 16-16v-48c0-8.8-7.2-16-16-16zm578 576h-48c-8.8 0-16 7.2-16 16v176H682c-8.8 0-16 7.2-16 16v48c0 8.8 7.2 16 16 16h222c17.7 0 32-14.3 32-32V680c0-8.8-7.2-16-16-16zM342 856H168V680c0-8.8-7.2-16-16-16h-48c-8.8 0-16 7.2-16 16v224c0 17.7 14.3 32 32 32h222c8.8 0 16-7.2 16-16v-48c0-8.8-7.2-16-16-16zM904 88H682c-8.8 0-16 7.2-16 16v48c0 8.8 7.2 16 16 16h174v176c0 8.8 7.2 16 16 16h48c8.8 0 16-7.2 16-16V120c0-17.7-14.3-32-32-32z" } }] }, "name": "expand", "theme": "outlined" }; function _objectSpread$4(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? Object(arguments[i]) : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === "function") { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function(sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function(key) { _defineProperty$4(target, key, source[key]); }); } return target; } function _defineProperty$4(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } var ExpandOutlined = function ExpandOutlined2(props, context) { var p = _objectSpread$4({}, props, context.attrs); return vue.createVNode(Icon, _objectSpread$4({}, p, { "icon": ExpandOutlined$1 }), null); }; ExpandOutlined.displayName = "ExpandOutlined"; ExpandOutlined.inheritAttrs = false; var LoadingOutlined$1 = { "icon": { "tag": "svg", "attrs": { "viewBox": "0 0 1024 1024", "focusable": "false" }, "children": [{ "tag": "path", "attrs": { "d": "M988 548c-19.9 0-36-16.1-36-36 0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 00-94.3-139.9 437.71 437.71 0 00-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.3C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3.1 19.9-16 36-35.9 36z" } }] }, "name": "loading", "theme": "outlined" }; function _objectSpread$3(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? Object(arguments[i]) : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === "function") { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function(sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function(key) { _defineProperty$3(target, key, source[key]); }); } return target; } function _defineProperty$3(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } var LoadingOutlined = function LoadingOutlined2(props, context) { var p = _objectSpread$3({}, props, context.attrs); return vue.createVNode(Icon, _objectSpread$3({}, p, { "icon": LoadingOutlined$1 }), null); }; LoadingOutlined.displayName = "LoadingOutlined"; LoadingOutlined.inheritAttrs = false; var SendOutlined$1 = { "icon": { "tag": "svg", "attrs": { "viewBox": "64 64 896 896", "focusable": "false" }, "children": [{ "tag": "defs", "attrs": {}, "children": [{ "tag": "style", "attrs": {} }] }, { "tag": "path", "attrs": { "d": "M931.4 498.9L94.9 79.5c-3.4-1.7-7.3-2.1-11-1.2a15.99 15.99 0 00-11.7 19.3l86.2 352.2c1.3 5.3 5.2 9.6 10.4 11.3l147.7 50.7-147.6 50.7c-5.2 1.8-9.1 6-10.3 11.3L72.2 926.5c-.9 3.7-.5 7.6 1.2 10.9 3.9 7.9 13.5 11.1 21.5 7.2l836.5-417c3.1-1.5 5.6-4.1 7.2-7.1 3.9-8 .7-17.6-7.2-21.6zM170.8 826.3l50.3-205.6 295.2-101.3c2.3-.8 4.2-2.6 5-5 1.4-4.2-.8-8.7-5-10.2L221.1 403 171 198.2l628 314.9-628.2 313.2z" } }] }, "name": "send", "theme": "outlined" }; function _objectSpread$2(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? Object(arguments[i]) : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === "function") { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function(sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function(key) { _defineProperty$2(target, key, source[key]); }); } return target; } function _defineProperty$2(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } var SendOutlined = function SendOutlined2(props, context) { var p = _objectSpread$2({}, props, context.attrs); return vue.createVNode(Icon, _objectSpread$2({}, p, { "icon": SendOutlined$1 }), null); }; SendOutlined.displayName = "SendOutlined"; SendOutlined.inheritAttrs = false; var SettingOutlined$1 = { "icon": { "tag": "svg", "attrs": { "viewBox": "64 64 896 896", "focusable": "false" }, "children": [{ "tag": "path", "attrs": { "d": "M924.8 625.7l-65.5-56c3.1-19 4.7-38.4 4.7-57.8s-1.6-38.8-4.7-57.8l65.5-56a32.03 32.03 0 009.3-35.2l-.9-2.6a443.74 443.74 0 00-79.7-137.9l-1.8-2.1a32.12 32.12 0 00-35.1-9.5l-81.3 28.9c-30-24.6-63.5-44-99.7-57.6l-15.7-85a32.05 32.05 0 00-25.8-25.7l-2.7-.5c-52.1-9.4-106.9-9.4-159 0l-2.7.5a32.05 32.05 0 00-25.8 25.7l-15.8 85.4a351.86 351.86 0 00-99 57.4l-81.9-29.1a32 32 0 00-35.1 9.5l-1.8 2.1a446.02 446.02 0 00-79.7 137.9l-.9 2.6c-4.5 12.5-.8 26.5 9.3 35.2l66.3 56.6c-3.1 18.8-4.6 38-4.6 57.1 0 19.2 1.5 38.4 4.6 57.1L99 625.5a32.03 32.03 0 00-9.3 35.2l.9 2.6c18.1 50.4 44.9 96.9 79.7 137.9l1.8 2.1a32.12 32.12 0 0035.1 9.5l81.9-29.1c29.8 24.5 63.1 43.9 99 57.4l15.8 85.4a32.05 32.05 0 0025.8 25.7l2.7.5a449.4 449.4 0 00159 0l2.7-.5a32.05 32.05 0 0025.8-25.7l15.7-85a350 350 0 0099.7-57.6l81.3 28.9a32 32 0 0035.1-9.5l1.8-2.1c34.8-41.1 61.6-87.5 79.7-137.9l.9-2.6c4.5-12.3.8-26.3-9.3-35zM788.3 465.9c2.5 15.1 3.8 30.6 3.8 46.1s-1.3 31-3.8 46.1l-6.6 40.1 74.7 63.9a370.03 370.03 0 01-42.6 73.6L721 702.8l-31.4 25.8c-23.9 19.6-50.5 35-79.3 45.8l-38.1 14.3-17.9 97a377.5 377.5 0 01-85 0l-17.9-97.2-37.8-14.5c-28.5-10.8-55-26.2-78.7-45.7l-31.4-25.9-93.4 33.2c-17-22.9-31.2-47.6-42.6-73.6l75.5-64.5-6.5-40c-2.4-14.9-3.7-30.3-3.7-45.5 0-15.3 1.2-30.6 3.7-45.5l6.5-40-75.5-64.5c11.3-26.1 25.6-50.7 42.6-73.6l93.4 33.2 31.4-25.9c23.7-19.5 50.2-34.9 78.7-45.7l37.9-14.3 17.9-97.2c28.1-3.2 56.8-3.2 85 0l17.9 97 38.1 14.3c28.7 10.8 55.4 26.2 79.3 45.8l31.4 25.8 92.8-32.9c17 22.9 31.2 47.6 42.6 73.6L781.8 426l6.5 39.9zM512 326c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm79.2 255.2A111.6 111.6 0 01512 614c-29.9 0-58-11.7-79.2-32.8A111.6 111.6 0 01400 502c0-29.9 11.7-58 32.8-79.2C454 401.6 482.1 390 512 390c29.9 0 58 11.6 79.2 32.8A111.6 111.6 0 01624 502c0 29.9-11.7 58-32.8 79.2z" } }] }, "name": "setting", "theme": "outlined" }; function _objectSpread$1(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? Object(arguments[i]) : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === "function") { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function(sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function(key) { _defineProperty$1(target, key, source[key]); }); } return target; } function _defineProperty$1(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } var SettingOutlined = function SettingOutlined2(props, context) { var p = _objectSpread$1({}, props, context.attrs); return vue.createVNode(Icon, _objectSpread$1({}, p, { "icon": SettingOutlined$1 }), null); }; SettingOutlined.displayName = "SettingOutlined"; SettingOutlined.inheritAttrs = false; var ShrinkOutlined$1 = { "icon": { "tag": "svg", "attrs": { "viewBox": "64 64 896 896", "focusable": "false" }, "children": [{ "tag": "path", "attrs": { "d": "M881.7 187.4l-45.1-45.1a8.03 8.03 0 00-11.3 0L667.8 299.9l-54.7-54.7a7.94 7.94 0 00-13.5 4.7L576.1 439c-.6 5.2 3.7 9.5 8.9 8.9l189.2-23.5c6.6-.8 9.3-8.8 4.7-13.5l-54.7-54.7 157.6-157.6c3-3 3-8.1-.1-11.2zM439 576.1l-189.2 23.5c-6.6.8-9.3 8.9-4.7 13.5l54.7 54.7-157.5 157.5a8.03 8.03 0 000 11.3l45.1 45.1c3.1 3.1 8.2 3.1 11.3 0l157.6-157.6 54.7 54.7a7.94 7.94 0 0013.5-4.7L447.9 585a7.9 7.9 0 00-8.9-8.9z" } }] }, "name": "shrink", "theme": "outlined" }; function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? Object(arguments[i]) : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === "function") { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function(sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function(key) { _defineProperty(target, key, source[key]); }); } return target; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } var ShrinkOutlined = function ShrinkOutlined2(props, context) { var p = _objectSpread({}, props, context.attrs); return vue.createVNode(Icon, _objectSpread({}, p, { "icon": ShrinkOutlined$1 }), null); }; ShrinkOutlined.displayName = "ShrinkOutlined"; ShrinkOutlined.inheritAttrs = false; var _GM_getValue = /* @__PURE__ */ (() => typeof GM_getValue != "undefined" ? GM_getValue : void 0)(); var _GM_info = /* @__PURE__ */ (() => typeof GM_info != "undefined" ? GM_info : void 0)(); var _GM_setValue = /* @__PURE__ */ (() => typeof GM_setValue != "undefined" ? GM_setValue : void 0)(); var _unsafeWindow = /* @__PURE__ */ (() => typeof unsafeWindow != "undefined" ? unsafeWindow : void 0)(); const getScriptInfo = () => { return { name: _GM_info.script.name, author: _GM_info.script.author, namespace: _GM_info.script.namespace, version: _GM_info.script.version, description: _GM_info.script.description }; }; const LOCAL_USER_CENTER_URL = "https://xs.openget.cn/user?section=account"; const localBackendJson = (url, data, token) => { return new Promise((resolve) => { GM_xmlhttpRequest({ url, method: data ? "POST" : "GET", headers: { "Content-Type": "application/json", ...token ? { "Authorization": "Bearer " + token } : {} }, data: data ? JSON.stringify(data) : void 0, timeout: 2e4, onload: (response) => { try { resolve(JSON.parse(response.responseText || "{}")); } catch (_) { resolve({ code: 500, msg: "本地后端响应解析失败" }); } }, onerror: () => resolve({ code: 500, msg: "无法连接本地后端" }), ontimeout: () => resolve({ code: 500, msg: "本地后端请求超时" }) }); }); }; const sha256Text = async (text) => { const encoder = new TextEncoder(); const hashBuf = await crypto.subtle.digest("SHA-256", encoder.encode(text || "")); return Array.from(new Uint8Array(hashBuf)).map((b) => b.toString(16).padStart(2, "0")).join(""); }; const getRememberedLogin = () => { try { return JSON.parse(_GM_getValue("localBackendLogin") || "{}") || {}; } catch (_) { return {}; } }; const setRememberedLogin = (data) => { try { _GM_setValue("localBackendLogin", JSON.stringify(data || {})); } catch (_) { } }; const syncLoginTokenToScriptKey = async (setting, loginToken) => { const keyRes = await localBackendJson("https://xs.openget.cn/api/user/script-key", null, loginToken); if (keyRes.code !== 200 || !keyRes.script_key) { return { ok: false, msg: keyRes.msg || "脚本密钥获取失败" }; } setting.config.basicConfig.token.value = keyRes.script_key; try { abcCloudApplying = true; await fetchAndApplyAbcCloudConfig(setting, keyRes.script_key); } catch (_) { } finally { abcCloudApplying = false; } const profileRes = await localBackendJson("https://xs.openget.cn/api/user/profile", null, keyRes.script_key); setting.userInfo.remainCount = profileRes.code === 200 && profileRes.profile ? getProfileRemainingCount(profileRes.profile) : "已配置"; if (profileRes.code === 200 && profileRes.profile) { setting.userInfo.activeMember = !!profileRes.profile.active_member; setting.userInfo.memberUntil = profileRes.profile.member_until || ""; setting.userInfo.pointsBalance = Number(profileRes.profile.points_balance || 0); } persistSettingState(setting); return { ok: true, msg: "已自动登录并同步密钥" }; }; const persistSettingState = (setting) => { try { _GM_setValue("setting", JSON.stringify(setting.$state || setting)); } catch (_) { } }; const serializeAbcCloudConfig = (setting) => { const config = {}; ["basicConfig", "examConfig", "cxConfig", "searchConfig"].forEach((section) => { config[section] = {}; const group = setting.config && setting.config[section] || {}; Object.keys(group).forEach((key) => { if (key === "token") return; if (group[key] && Object.prototype.hasOwnProperty.call(group[key], "value")) { config[section][key] = group[key].value; } }); }); return config; }; const applyAbcCloudConfig = (setting, cloudConfig) => { if (!cloudConfig || typeof cloudConfig !== "object") return false; let changed = false; ["basicConfig", "examConfig", "cxConfig", "searchConfig"].forEach((section) => { const group = setting.config && setting.config[section]; const source = cloudConfig[section] || {}; if (!group || !source) return; Object.keys(source).forEach((key) => { if (key === "token") return; if (group[key] && Object.prototype.hasOwnProperty.call(group[key], "value") && group[key].value !== source[key]) { group[key].value = source[key]; changed = true; } }); }); return changed; }; const fetchAndApplyAbcCloudConfig = async (setting, token) => { if (!token) return false; const res = await localBackendJson("https://xs.openget.cn/api/user/settings", null, token); if (res.code === 200 && res.settings && res.settings.abcConfig) { const changed = applyAbcCloudConfig(setting, res.settings.abcConfig); if (changed) persistSettingState(setting); return changed; } return false; }; let abcCloudSaveTimer = null; let abcCloudApplying = false; const scheduleSaveAbcCloudConfig = (setting, token) => { if (!token || abcCloudApplying) return; if (abcCloudSaveTimer) clearTimeout(abcCloudSaveTimer); abcCloudSaveTimer = setTimeout(async () => { const current = await localBackendJson("https://xs.openget.cn/api/user/settings", null, token); const settings = current.code === 200 && current.settings && typeof current.settings === "object" ? current.settings : {}; settings.abcConfig = serializeAbcCloudConfig(setting); await localBackendJson("https://xs.openget.cn/api/user/settings", { settings }, token); }, 800); }; let scriptLogQueue = []; let scriptLogFlushTimer = null; const plainLogText = (text) => { const div = document.createElement("div"); div.innerHTML = String(text || ""); return (div.textContent || div.innerText || String(text || "")).slice(0, 4000); }; const getScriptLogToken = () => { try { const setting = useSettingStore(); const scriptKey = setting.config && setting.config.basicConfig && setting.config.basicConfig.token && setting.config.basicConfig.token.value || ""; if (scriptKey) return scriptKey; const remembered = getRememberedLogin(); if (remembered && remembered.token) return remembered.token; return ""; } catch (_) { try { const remembered = getRememberedLogin(); if (remembered && remembered.token) return remembered.token; } catch (e2) {} return ""; } }; const flushScriptLogs = async () => { const token = getScriptLogToken(); if (!token || !scriptLogQueue.length) return; const logs = scriptLogQueue.splice(0, 80); const res = await localBackendJson("https://xs.openget.cn/api/script-logs", { logs }, token); if (!res || res.code !== 200) { scriptLogQueue = logs.concat(scriptLogQueue).slice(-200); } }; const enqueueScriptLog = (logItem) => { scriptLogQueue.push({ event_type: logItem.type === "warning" ? "warning" : "log", level: logItem.type === "warning" ? "warning" : "info", message: plainLogText(logItem.text), page_url: location.href, extra: { tabIndex: logItem.tabIndex, time: logItem.time, script_version: getScriptInfo().version, raw_html: String(logItem.text || "").slice(0, 1000) }, timestamp: Date.now() }); if (scriptLogQueue.length > 200) scriptLogQueue = scriptLogQueue.slice(-200); if (scriptLogFlushTimer) clearTimeout(scriptLogFlushTimer); scriptLogFlushTimer = setTimeout(flushScriptLogs, 1200); }; const formatDateTime = (dt) => { dt.getFullYear(); dt.getMonth() + 1; dt.getDate(); let hours = dt.getHours(); let minutes = dt.getMinutes(); let seconds = dt.getSeconds(); return `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`; }; const pad = (n) => { return n < 10 ? "0" + n : n.toString(); }; const getDateTime = () => { let now = /* @__PURE__ */ new Date(); return formatDateTime(now); }; const useLogStore = pinia.defineStore("log", { state: () => ({ logList: [] }), actions: { insertLog(text, type = null, tabIndex = null) { const logItem = { text, time: getDateTime(), type, tabIndex }; this.logList.push(logItem); enqueueScriptLog(logItem); } } }); const _hoisted_1$8 = { class: "log-wrap" }; const _hoisted_2$7 = ["innerHTML"]; const _hoisted_3$6 = { key: 0 }; const _sfc_main$9 = { __name: "UserLog", setup(__props) { const log = useLogStore(); const logList = vue.ref(log.logList); const logDom = vue.ref(null); vue.watch( () => log.logList, () => { vue.nextTick(() => { logDom.value.scrollTop = logDom.value.scrollHeight; }); }, { deep: true } ); return (_ctx, _cache) => { return vue.openBlock(), vue.createElementBlock("div", _hoisted_1$8, [ vue.createElementVNode("div", { class: "console", ref_key: "logDom", ref: logDom }, [ (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(logList.value, (item, index) => { return vue.openBlock(), vue.createElementBlock("div", { key: index }, [ vue.createElementVNode("span", null, vue.toDisplayString(item.time), 1), vue.createElementVNode("span", { class: vue.normalizeClass(item.type == null ? "log" : "warning") }, "[" + vue.toDisplayString(item.type == null ? "日志" : "警告") + "]", 3), vue.createElementVNode("span", { innerHTML: item.text }, null, 8, _hoisted_2$7) ]); }), 128)), logList.value.length == 0 ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_3$6, _cache[0] || (_cache[0] = [ vue.createElementVNode("span", { class: "log" }, "[日志]", -1), vue.createElementVNode("span", null, "等待用户进入正确页面..", -1) ]))) : vue.createCommentVNode("", true) ], 512) ]); }; } }; function deepMerge(target, source) { const result = { ...target }; for (const key of Object.keys(target)) { if (!(key in source)) continue; const tVal = target[key]; const sVal = source[key]; if (tVal !== null && typeof tVal === "object" && !Array.isArray(tVal) && sVal !== null && typeof sVal === "object" && !Array.isArray(sVal)) { result[key] = deepMerge(tVal, sVal); } else { result[key] = sVal; } } return result; } const useSettingStore = pinia.defineStore("setting", { state: () => { const scriptInfo = getScriptInfo(); const defaultTop = typeof window !== "undefined" ? `${Math.round(window.innerHeight * 0.2)}px` : "120px"; const defaultSetting = { tabIndex: "0", version: scriptInfo.version, position: { right: "0px", top: defaultTop }, shopLink: "", latestVersion: "", chatWebUrl: "", config: { cxConfig: { showCustomEditor: { text: "编辑时显示自定义编辑框", value: true, type: "switch", tips: "开启后,编辑时会显示自定义编辑框" }, faceRecognitionNotify: { text: "出现人脸识别时通知我", value: true, type: "switch", tips: "开启后,出现人脸识别时会发送通知" } }, basicConfig: { token: { text: "本地后端密钥", value: "", type: "input", tips: "点击上方“点我获取密钥”打开用户中心,登录后复制脚本密钥并填入这里;填写后刷新页面生效。", placeholder: "请粘贴用户中心生成的脚本密钥" }, answerMode: { text: "答题模式", value: "questionBankAndAI", type: "select", tips: "纯题库:仅题库搜索;纯AI:仅AI答题;题库+AI:题库优先,无结果时AI答题", options: [ { value: "questionBank", label: "纯题库" }, { value: "ai", label: "纯AI" }, { value: "questionBankAndAI", label: "题库+AI" } ] }, videoPlayrate: { text: "视频倍速", value: 2, type: "input-number", min: 1, max: 16, step: 0.5, precision: 1, tips: "❗❗❗视频观看倍速(支持自定义,0.5 递增),高倍速可能面临进度清空风险,请慎重选择" }, reqIntervalTime: { text: "搜题间隔(秒)", value: 2, type: "input-number", min: 1, max: 3, precision: 0, tips: "每题搜题间隔,不建议太低,避免服务器压力过大" }, accuracy: { text: "正确率", value: 80, type: "slider", tips: "作业、测验、考试满足设定的正确率才会提交,不满足自动保存,建议80%" }, autoSubmit: { text: "章节、作业自动提交", value: true, type: "switch", tips: "(建议开启)开启后,满足正确率则自动提交作业、章节,手动答题时建议关闭此功能" }, autoChangeChapter: { text: "自动切换章节", value: true, type: "switch", tips: "(建议开启)开启后,自动切换章节,手动答题时,建议关闭" }, autoAnswer: { text: "自动答题", value: true, type: "switch", tips: "手动答题时,建议关闭,学习时,建议开启" }, autoRefresh: { text: "定时刷新(防卡死)", value: true, type: "switch", tips: "(建议开启)每30分钟刷新页面,防止脚本卡死" } }, examConfig: { autoSubmit: { value: false, text: "自动交卷", type: "switch", tips: "手动考试时建议关闭;自动考试答题完成后,将自动点击交卷。" } }, searchConfig: { searchBySelectedText: { value: false, text: "划词搜题" } } }, userInfo: { remainCount: 0, activeMember: false, memberUntil: "", pointsBalance: 0 } }; try { const localStore = JSON.parse(_GM_getValue("setting")); if (localStore) { const merged = deepMerge(defaultSetting, localStore); const vpConfig = merged.config && merged.config.basicConfig && merged.config.basicConfig.videoPlayrate; if (vpConfig) { vpConfig.type = "input-number"; delete vpConfig.options; if (vpConfig.min === undefined) vpConfig.min = 1; if (vpConfig.max === undefined) vpConfig.max = 16; if (vpConfig.step === undefined) vpConfig.step = 0.5; if (vpConfig.precision === undefined) vpConfig.precision = 1; } merged.version = scriptInfo.version; return merged; } } catch (error) { console.log("localStore error:", error); } return defaultSetting; } }); const _export_sfc = (sfc, props) => { const target = sfc.__vccOpts || sfc; for (const [key, val] of props) { target[key] = val; } return target; }; const _hoisted_1$7 = { class: "home-wrap" }; const _hoisted_2$6 = { class: "tips" }; const _hoisted_3$5 = { key: 0 }; const _hoisted_4$5 = ["innerHTML"]; const _sfc_main$8 = { __name: "ScriptHome", setup(__props) { const scriptInfo = getScriptInfo(); const setting = useSettingStore(); const { latestVersion, chatWebUrl, shopLink } = pinia.storeToRefs(setting); const showTips = vue.ref(false); const showTutorial = vue.ref(false); const announce = vue.ref(null); vue.onMounted(async () => { const res = await localBackendJson("https://xs.openget.cn/api/v1/announce"); if (res && res.code === 200 && res.data && res.data.enabled && res.data.text) { announce.value = res.data; } }); return (_ctx, _cache) => { return vue.openBlock(), vue.createElementBlock("div", _hoisted_1$7, [ announce.value ? (vue.openBlock(), vue.createElementBlock("div", { key: "announce", style: { "margin": "8px", "padding": "10px 12px", "background": "linear-gradient(135deg,#ffa940,#ff6a00)", "border": "1px solid rgba(255,255,255,.35)", "border-radius": "8px", "color": "#fff", "font-size": "13px", "line-height": "1.6", "white-space": "pre-line" } }, [ vue.createElementVNode("div", { style: { "font-weight": "700", "font-size": "14px", "margin-bottom": "4px", "display": "flex", "align-items": "center", "gap": "6px" } }, [ vue.createTextVNode("📢 "), vue.createTextVNode(vue.toDisplayString(announce.value.title), 1) ]), vue.createTextVNode(vue.toDisplayString(announce.value.text), 1) ])) : vue.createCommentVNode("", true), vue.createElementVNode("div", { class: "home-section", style: {"margin-top":"8px"} }, [ vue.createElementVNode("div", { class: "section-title" }, "推广返利"), vue.createElementVNode("a", { class: "home-card-btn", href: "https://xs.openget.cn/user?section=promotion", target: "_blank" }, "💰 去推广赚钱") ]), vue.createElementVNode("div", { class: "home-section", style: {"margin-top":"8px"} }, [ vue.createElementVNode("div", { class: "section-title" }, "QQ频道"), vue.createElementVNode("a", { class: "home-card-btn", href: "https://pd.qq.com/s/ga2bgmmf8", target: "_blank", style: { "background": "linear-gradient(135deg,#12b7f5,#0a6bff)", "border": "1px solid rgba(255,255,255,.35)" } }, [ vue.createTextVNode("💬 加入腾讯频道【学神助手】"), vue.createElementVNode("span", { style: { "display": "block", "font-size": "11px", "font-weight": "400", "opacity": ".9", "margin-top": "2px" } }, "频道号 pd22434767 · 点击立即加入") ]) ]), vue.createElementVNode("div", { class: "home-section collapsible-section", style: {"margin-top":"8px"} }, [ vue.createElementVNode("div", { class: "section-title", title: "点击展开/收起" }, [ vue.createTextVNode("📌 使用提示"), vue.createElementVNode("span", { class: "collapse-icon", onClick: () => showTips.value = !showTips.value }, vue.toDisplayString(showTips.value ? "−" : "+"), 1) ]) ]), showTips.value ? (vue.openBlock(), vue.createElementBlock("div", { key: "tips-content", style: { "margin": "0 8px 10px", "padding": "10px 12px", "background": "var(--t-surface)", "border": "1px solid var(--t-border)", "border-radius": "6px" } }, [ vue.createElementVNode("ul", null, [ vue.unref(scriptInfo).version !== vue.unref(latestVersion) ? (vue.openBlock(), vue.createElementBlock("li", _hoisted_3$5, _cache[0] || (_cache[0] = [ vue.createTextVNode("检测到脚本非最新版本,"), vue.createElementVNode("a", { href: "https://scriptcat.org/zh-CN/script-show-page/2827", target: "_blank" }, "点我更新", -1) ]))) : vue.createCommentVNode("", true), _cache[1] || (_cache[1] = vue.createElementVNode("li", null, "请手动进入视频、作业、考试页面,脚本会自动运行。", -1)), _cache[2] || (_cache[2] = vue.createElementVNode("li", null, [ vue.createTextVNode("如脚本无法正常运行,请使用 "), vue.createElementVNode("a", { target: "_blank", href: "https://www.google.com/intl/zh-CN/chrome/" }, "Chrome"), vue.createTextVNode(" 、 "), vue.createElementVNode("a", { target: "_blank", href: "https://www.firefox.com/" }, "火狐浏览器") ], -1)), vue.unref(chatWebUrl) ? (vue.openBlock(), vue.createElementBlock("li", { key: 1, innerHTML: vue.unref(chatWebUrl) }, null, 8, _hoisted_4$5)) : vue.createCommentVNode("", true) ]) ])) : vue.createCommentVNode("", true), vue.createElementVNode("div", { class: "home-section collapsible-section", style: {"margin-top":"8px"} }, [ vue.createElementVNode("div", { class: "section-title", title: "点击展开/收起" }, [ vue.createTextVNode("📋 使用教程"), vue.createElementVNode("span", { class: "collapse-icon", onClick: () => showTutorial.value = !showTutorial.value }, vue.toDisplayString(showTutorial.value ? "−" : "+"), 1) ]) ]), showTutorial.value ? (vue.openBlock(), vue.createElementBlock("div", { key: "tutorial-content", style: { "margin": "0 8px 10px", "padding": "10px 12px", "background": "var(--t-surface)", "border": "1px solid var(--t-border)", "border-radius": "6px" } }, [ vue.createElementVNode("ol", null, [ vue.createElementVNode("li", null, "打开任意网课平台,等待脚本加载。"), vue.createElementVNode("li", null, "如果自动答题搜索不到,可尝试在线搜索,答案基本都有。"), vue.createElementVNode("li", null, "如果不支持当前网课,则不会有相应的提示以及设置面板。"), vue.createElementVNode("li", null, [ vue.createElementVNode("span", null, "最后温馨提示: "), vue.createElementVNode("div", null, "- 禁止与其他脚本一起使用,否则出现答案选不上或者页面卡死,无限刷新,等问题一律后果自负。"), vue.createElementVNode("div", null, "- 请将浏览器页面保持最大化,或者缩小窗口,不能最小化,否则可能导致脚本卡死!") ]) ]) ])) : vue.createCommentVNode("", true), ]); }; } }; const ScriptHome = /* @__PURE__ */ _export_sfc(_sfc_main$8, [["__scopeId", "data-v-137ea562"]]); const useQuestionStore = pinia.defineStore("questionStore", { state: () => ({ questionList: [], currentQuestionIndex: 0, accuracy: -1, searchText: "", fuzzySearchResult: [] }), actions: { clearQuestion() { this.questionList = []; }, clearFuzzySearchResult() { this.fuzzySearchResult = []; } } }); const _hoisted_1$6 = { class: "answer-wrap" }; const _hoisted_2$5 = { class: "tips" }; const _hoisted_3$4 = { class: "answer-tips" }; const _hoisted_4$4 = { style: { "margin-bottom": "10px" } }; const _hoisted_5$4 = { key: 0 }; const _hoisted_6$4 = { key: 1 }; const _hoisted_7$4 = { key: 2 }; const _hoisted_8$4 = { key: 0, style: { "text-align": "center" } }; const _hoisted_9$4 = { key: 1 }; const _hoisted_10$2 = { class: "answer-list" }; const _hoisted_11$2 = ["onClick"]; const _hoisted_12$2 = { key: 0 }; const _hoisted_13$1 = ["innerHTML"]; const _hoisted_14$1 = { class: "answer-content" }; const _hoisted_15 = ["innerHTML"]; const _hoisted_16 = { key: 0, style: { "color": "#f94f17" } }; const _sfc_main$7 = { __name: "AutoAnswer", emits: ["changeTab"], setup(__props, { emit: __emit }) { const codeMap = { 1: "success", "-1": "default", 2: "ai-success" }; const questionStore = useQuestionStore(); const selectedIndex = vue.ref(0); const answerDom = vue.ref(null); const setting = useSettingStore(); const { questionList, currentQuestionIndex: currentIndex, accuracy } = pinia.storeToRefs(questionStore); const { userInfo } = pinia.storeToRefs(setting); const emit = __emit; const goSetting = () => { window.open(setting.shopLink || "https://xs.openget.cn/user?section=account", "_blank"); }; const tokenReady = vue.computed(() => { const remain = String(setting.userInfo.remainCount || ""); return !!setting.config.basicConfig.token.value && remain && !["密钥未配置", "密钥无效", "null"].includes(remain); }); const checkAnswer = (index) => { if (index > currentIndex.value) return; const dom = answerDom.value.getElementsByClassName("answer-item")[index]; answerDom.value.scrollTo({ top: dom.offsetTop }); selectedIndex.value = index; }; return (_ctx, _cache) => { const _component_a_button = vue.resolveComponent("a-button"); return vue.openBlock(), vue.createElementBlock("div", _hoisted_1$6, [ vue.createElementVNode("div", _hoisted_2$5, [ vue.createElementVNode("ul", null, [ vue.createElementVNode("li", null, [ _cache[1] || (_cache[1] = vue.createTextVNode(" 使用自动答题功能,请先 ")), vue.createVNode(_component_a_button, { onClick: goSetting, size: "small", type: "primary", danger: tokenReady.value }, { icon: vue.withCtx(() => [ vue.createVNode(vue.unref(SettingOutlined)) ]), default: vue.withCtx(() => [ vue.createTextVNode(vue.toDisplayString(tokenReady.value ? "已配置密钥" : "登录用户中心获取密钥"), 1) ]), _: 1 }) ]), _cache[2] || (_cache[2] = vue.createElementVNode("li", null, "如需手动答题,建议关闭自动切换功能", -1)), _cache[3] || (_cache[3] = vue.createElementVNode("li", null, "每次自动答题开始前,都会清空上一次的搜索结果", -1)) ]) ]), _cache[10] || (_cache[10] = vue.createElementVNode("div", { class: "separator", style: { "margin": "5px 0" } }, null, -1)), vue.createElementVNode("div", _hoisted_3$4, [ vue.createElementVNode("div", _hoisted_4$4, [ vue.createElementVNode("span", null, "答题进度:" + vue.toDisplayString(vue.unref(currentIndex)) + " / " + vue.toDisplayString(vue.unref(questionList).length), 1), _cache[5] || (_cache[5] = vue.createElementVNode("span", null, "|", -1)), vue.createElementVNode("span", null, [ _cache[4] || (_cache[4] = vue.createTextVNode(" 准确率: ")), vue.unref(questionList).length == 0 ? (vue.openBlock(), vue.createElementBlock("span", _hoisted_5$4, "未答题")) : vue.unref(accuracy) != -1 ? (vue.openBlock(), vue.createElementBlock("span", _hoisted_6$4, vue.toDisplayString(vue.unref(accuracy)) + "%", 1)) : (vue.openBlock(), vue.createElementBlock("span", _hoisted_7$4, "作业中..")) ]), _cache[6] || (_cache[6] = vue.createElementVNode("span", null, "|", -1)), vue.createElementVNode("span", null, "剩余次数:" + vue.toDisplayString(vue.unref(userInfo).remainCount), 1) ]), _cache[7] || (_cache[7] = vue.createStaticVNode('
搜索中
题库答案
AI答案
异常
', 1)) ]), _cache[11] || (_cache[11] = vue.createElementVNode("div", { class: "separator", style: { "margin": "5px 0" } }, null, -1)), vue.unref(questionList).length == 0 ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_8$4, " ❗️ 未检测到试题 ")) : (vue.openBlock(), vue.createElementBlock("div", _hoisted_9$4, [ vue.createElementVNode("div", _hoisted_10$2, [ (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(vue.unref(questionList), (question, index) => { var _a; return vue.openBlock(), vue.createElementBlock("div", { onClick: ($event) => checkAnswer(index), class: vue.normalizeClass(`search-info ${index === vue.unref(currentIndex) ? "searching" : ""} ${codeMap[(_a = question.answer) == null ? void 0 : _a.code] || "warning"}`), key: index }, vue.toDisplayString(index + 1), 11, _hoisted_11$2); }), 128)) ]), _cache[9] || (_cache[9] = vue.createElementVNode("div", { class: "separator", style: { "margin": "5px 0" } }, null, -1)), vue.unref(questionList).length != 0 ? (vue.openBlock(), vue.createElementBlock("div", { key: 0, ref_key: "answerDom", ref: answerDom, class: "answer-detail" }, [ (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(vue.unref(currentIndex), (index) => { var _a, _b, _c; return vue.openBlock(), vue.createElementBlock("div", { class: "answer-item", key: index }, [ index <= vue.unref(questionList).length ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_12$2, [ vue.createElementVNode("div", null, [ vue.createElementVNode("span", null, vue.toDisplayString(index) + ".", 1), vue.createElementVNode("div", { style: { "display": "inline-block" }, innerHTML: vue.unref(questionList)[index - 1].title }, null, 8, _hoisted_13$1) ]), vue.createElementVNode("div", _hoisted_14$1, [ _cache[8] || (_cache[8] = vue.createElementVNode("span", null, "答案:", -1)), vue.createElementVNode("div", { innerHTML: (_b = (_a = vue.unref(questionList)[index - 1].answer) == null ? void 0 : _a.answer) == null ? void 0 : _b.join() }, null, 8, _hoisted_15), ((_c = vue.unref(questionList)[index - 1].answer) == null ? void 0 : _c.code) === 0 ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_16, "有答案(不完全匹配),请手动选择")) : vue.createCommentVNode("", true) ]) ])) : vue.createCommentVNode("", true) ]); }), 128)) ], 512)) : vue.createCommentVNode("", true) ])) ]); }; } }; const _hoisted_1$5 = { class: "config-wrap" }; const _hoisted_2$4 = { class: "tips" }; const _hoisted_3$3 = { class: "buy-btn" }; const _hoisted_4$3 = ["href"]; const _hoisted_5$3 = { class: "separator" }; const _hoisted_6$3 = { key: 0, class: "config" }; const _hoisted_7$3 = { class: "config-name" }; const _hoisted_8$3 = { class: "config-input" }; const _hoisted_9$3 = { key: 5 }; const _sfc_main$6 = { __name: "UserConfig", emits: ["autoRefreshPage"], setup(__props, { emit: __emit }) { const setting = useSettingStore(); const { shopLink } = pinia.storeToRefs(setting); const emit = __emit; const configSections = vue.computed(() => [ { label: "基本设置", configs: setting.config.basicConfig, showRemainCount: true }, { label: "考试设置", configs: setting.config.examConfig }, { label: "超星设置", configs: setting.config.cxConfig } ]); const remainDisplay = vue.computed(() => { const ui = setting.userInfo; if (ui.activeMember && ui.memberUntil) { return { label: "包月到期:", value: ui.memberUntil.split(" ")[0], isMember: true }; } const balance = Number(ui.pointsBalance || ui.remainCount || 0); if (balance >= 999999 || String(ui.remainCount) === "无限") { return { label: "剩余次数:", value: "无限", isMember: false }; } return { label: "剩余次数:", value: balance + " 次", isMember: false }; }); vue.watch( () => setting.config.basicConfig.token, (newVal) => { try { _GM_setValue("setting", JSON.stringify(setting.$state || setting)); } catch (_) { } setTimeout(() => window.location.reload(), 350); }, { deep: true } ); vue.watch( () => setting.config.basicConfig.videoPlayrate, (newVal) => { const rate = newVal && newVal.value; if (rate) { applyVideoRateToPage(rate); useLogStore().insertLog(`已更新视频倍速为 ${rate}x(无需刷新页面)`); } }, { deep: true } ); vue.watch( () => setting.config.basicConfig.autoRefresh, (newVal) => { emit("autoRefreshPage"); }, { deep: true } ); const formatter = (value) => { return `${value}%`; }; const closeAllAuto = () => { const hasAnyEnabled = setting.config.basicConfig.autoSubmit.value || setting.config.basicConfig.autoChangeChapter.value || setting.config.basicConfig.autoAnswer.value || setting.config.examConfig.autoSubmit.value; const newState = !hasAnyEnabled; setting.config.basicConfig.autoSubmit.value = newState; setting.config.basicConfig.autoChangeChapter.value = newState; setting.config.basicConfig.autoAnswer.value = newState; setting.config.examConfig.autoSubmit.value = newState; }; return (_ctx, _cache) => { const _component_a_slider = vue.resolveComponent("a-slider"); const _component_a_input_number = vue.resolveComponent("a-input-number"); const _component_a_switch = vue.resolveComponent("a-switch"); const _component_a_input = vue.resolveComponent("a-input"); const _component_a_select = vue.resolveComponent("a-select"); return vue.openBlock(), vue.createElementBlock("div", _hoisted_1$5, [ vue.createElementVNode("div", _hoisted_2$4, [ vue.createElementVNode("ul", null, [ vue.createElementVNode("li", null, [ _cache[0] || (_cache[0] = vue.createTextVNode("自动答题功能需填写密钥, ")), vue.createElementVNode("span", _hoisted_3$3, [ vue.createVNode(vue.unref(BulbOutlined), { style: { "color": "#fff", "margin-right": "2px" } }), vue.createElementVNode("a", { href: vue.unref(shopLink), target: "_blank" }, "点我登录用户中心获取密钥 ", 8, _hoisted_4$3) ]) ]), _cache[6] || (_cache[6] = vue.createElementVNode("li", null, "设置后进入章节测试,作业,考试页面即可自动答题", -1)), vue.createElementVNode("li", null, [ _cache[1] || (_cache[1] = vue.createTextVNode("使用")), _cache[2] || (_cache[2] = vue.createElementVNode("span", { style: { "color": "#ef4444" } }, "AI、在线搜题", -1)), _cache[3] || (_cache[3] = vue.createTextVNode(",")), _cache[4] || (_cache[4] = vue.createElementVNode("span", { style: { "color": "#ef4444" } }, "关闭所有自动操作", -1)), _cache[5] || (_cache[5] = vue.createTextVNode(",防止冲突。 ")), vue.createElementVNode("span", { onClick: closeAllAuto, class: "buy-btn" }, "一键关闭/开启") ]), _cache[7] || (_cache[7] = vue.createElementVNode("li", null, [ vue.createTextVNode("⚠️视频中出现题目,可以忽略,"), vue.createElementVNode("span", { style: { "color": "#ef4444" } }, "不影响学习进度") ], -1)) ]) ]), (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(configSections.value, (section) => { return vue.openBlock(), vue.createElementBlock(vue.Fragment, { key: section.label }, [ vue.createElementVNode("div", _hoisted_5$3, vue.toDisplayString(section.label), 1), vue.createElementVNode("div", null, [ section.showRemainCount ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_6$3, [ vue.createElementVNode("div", { style: { "color": "var(--t-text2)" } }, vue.toDisplayString(remainDisplay.value.label), 1), vue.createElementVNode("a", { href: "https://xs.openget.cn/user?section=billing", target: "_blank", style: { "color": remainDisplay.value.isMember ? "var(--t-primary)" : "var(--t-text)", "text-decoration": "none" } }, vue.toDisplayString(remainDisplay.value.value), 1) ])) : vue.createCommentVNode("", true), (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(section.configs, (config2) => { return vue.openBlock(), vue.createElementBlock("div", { key: config2 }, [ vue.createElementVNode("div", { class: vue.normalizeClass(["config", config2.type === "switch" ? "switch-item" : ""]) }, [ vue.createElementVNode("div", _hoisted_7$3, vue.toDisplayString(config2.text), 1), vue.createElementVNode("div", _hoisted_8$3, [ config2.type === "slider" ? (vue.openBlock(), vue.createBlock(_component_a_slider, { key: 0, value: config2.value, "onUpdate:value": ($event) => config2.value = $event, "tip-formatter": formatter }, null, 8, ["value", "onUpdate:value"])) : config2.type === "input-number" ? (vue.openBlock(), vue.createBlock(_component_a_input_number, { key: 1, precision: config2.precision, step: config2.step, value: config2.value, onChange: ($event) => { if ($event !== null && $event !== undefined) config2.value = $event; }, min: config2.min, max: config2.max }, null, 8, ["value", "onChange", "min", "max", "precision", "step"])) : config2.type === "switch" ? (vue.openBlock(), vue.createBlock(_component_a_switch, { key: 2, size: "small", checked: config2.value, "onUpdate:checked": ($event) => config2.value = $event }, null, 8, ["checked", "onUpdate:checked"])) : config2.type === "input" ? (vue.openBlock(), vue.createBlock(_component_a_input, { key: 3, value: config2.value, "onUpdate:value": ($event) => config2.value = $event, valueModifiers: { lazy: true, trim: true }, placeholder: config2.placeholder }, null, 8, ["value", "onUpdate:value", "placeholder"])) : config2.type === "select" ? (vue.openBlock(), vue.createBlock(_component_a_select, { key: 4, style: { "width": "100px" }, ref_for: true, ref: "select", value: config2.value, "onUpdate:value": ($event) => config2.value = $event, options: config2.options }, null, 8, ["value", "onUpdate:value", "options"])) : (vue.openBlock(), vue.createElementBlock("span", _hoisted_9$3, vue.toDisplayString(config2.value), 1)) ]) ], 2) ]); }), 128)) ]) ], 64); }), 128)) ]); }; } }; const sleep = (ms) => { return new Promise((resolve) => setTimeout(resolve, ms * 1e3)); }; const STORAGE_KEY = "answerRecords"; const MAX_RECORDS = 200; const normalizeText = (value = "") => String(value).replace(/\s+/g, " ").trim(); const normalizeOptions = (options = []) => [...Array.isArray(options) ? options : []].map((item) => normalizeText(item)).filter(Boolean).sort(); const formatRequestTime = (timestamp) => { const date = new Date(timestamp); const pad3 = (value) => String(value).padStart(2, "0"); return `${date.getFullYear()}-${pad3(date.getMonth() + 1)}-${pad3(date.getDate())} ${pad3(date.getHours())}:${pad3(date.getMinutes())}:${pad3(date.getSeconds())}`; }; const createAnswerRecordCacheKey = (question) => JSON.stringify({ title: normalizeText(question == null ? void 0 : question.title), type: normalizeText(question == null ? void 0 : question.type), optionsText: normalizeOptions(question == null ? void 0 : question.optionsText) }); const loadRecords = () => { try { const records = JSON.parse(_GM_getValue(STORAGE_KEY) || "[]"); return Array.isArray(records) ? records : []; } catch (error) { console.log("load answer records error:", error); return []; } }; const useAnswerRecordStore = pinia.defineStore("answerRecord", { state: () => ({ records: loadRecords() }), getters: { displayRecords: (state) => state.records }, actions: { persistRecords() { _GM_setValue(STORAGE_KEY, JSON.stringify(this.records)); }, insertRecord({ question, answer, token, source }) { const timestamp = Date.now(); const record = { cacheKey: createAnswerRecordCacheKey(question), token: token || "", title: (question == null ? void 0 : question.title) || "", requestTime: formatRequestTime(timestamp), timestamp, answer: Array.isArray(answer) ? answer : [], type: (question == null ? void 0 : question.type) || "", optionsText: Array.isArray(question == null ? void 0 : question.optionsText) ? question.optionsText : [], workType: (question == null ? void 0 : question.workType) || "", refer: (question == null ? void 0 : question.refer) || "", source: source || "accurate" }; this.records.unshift(record); this.records = this.records.slice(0, MAX_RECORDS); this.persistRecords(); return record; }, clearRecords() { this.records = []; this.persistRecords(); } } }); const getApiBaseUrl = () => { return "https://xs.openget.cn"; }; const LOCAL_BACKEND_ANSWER_URL = "https://xs.openget.cn/api/v1/cx"; const LOCAL_BACKEND_SEARCH_URL = "https://xs.openget.cn/api/web/search"; const TIMEOUT_ERROR_MSG$1 = "请求超时,请前往设置尝试切换节点.."; const SERVER_MAINTENANCE_ERROR_MSG$1 = "服务器维护中,请前往设置尝试切换节点,自动学习功能正常使用.."; const handleError$1 = (msg) => ({ code: 10003, data: { answer: [], num: "", usenum: "" }, msg }); const getCookieValue = (name) => { const pageWindow = _unsafeWindow || window; if (typeof pageWindow.getCookie === "function") { return pageWindow.getCookie(name) || ""; } const match = document.cookie.match(new RegExp(`(?:^|; )${name}=([^;]*)`)); return match ? decodeURIComponent(match[1]) : ""; }; const getLocalBackendToken = () => { const keys = [ "GPTJsSetting.token", "chaoxing.com:GPTJsSetting.token", "mooc1.chaoxing.com:GPTJsSetting.token" ]; for (const key of keys) { try { const v = localStorage.getItem(key); if (v) return v; } catch (_) { } try { const v = _GM_getValue ? _GM_getValue(key) : ""; if (v) return v; } catch (_) { } } try { const raw = localStorage.getItem("GPTJsSetting.userInfo") || localStorage.getItem("chaoxing.com:GPTJsSetting.userInfo"); const obj = raw ? JSON.parse(raw) : null; if (obj && obj.token) return obj.token; } catch (_) { } return ""; }; const normalizeLocalBackendAnswers = (answer) => { if (Array.isArray(answer)) return answer.map((x) => String(x).trim()).filter(Boolean); const text = String(answer || "").replace(/。$/, "").trim(); if (!text) return []; try { const parsed = JSON.parse(text); if (Array.isArray(parsed)) return parsed.map((x) => String(x).trim()).filter(Boolean); } catch (_) { } // 处理无分隔符的连续字母序列,如 "ABCD" → ["A","B","C","D"] if (/^[A-Da-d]{2,}$/.test(text)) { return text.toUpperCase().split("").filter(Boolean); } if (/^[A-Z](?:[\s,,、;;|]+[A-Z])+$/.test(text.toUpperCase())) { return text.toUpperCase().split(/[\s,,、;;|]+/).filter(Boolean); } // 文本答案不再按分隔符拆分(选项内容可能包含任何标点),保留完整字符串由 fillChoice 子串匹配 return [text]; }; const buildLocalBackendQuestionPayload = (question) => { return JSON.stringify({ question: question.title || "", options: question.optionsText || [], type: question.type || "", platform: "abc-helper", refer: question.refer || location.href }); }; const getProfileRemainingCount = (profile) => { if (!profile) return 0; if (profile.active_member) return "无限"; return Number(profile.points_balance || profile.remaining || 0); }; const updateUserInfoFromProfile = (profile) => { try { const setting = useSettingStore(); if (profile) { setting.userInfo.activeMember = !!profile.active_member; setting.userInfo.memberUntil = profile.member_until || ""; setting.userInfo.pointsBalance = Number(profile.points_balance || 0); } } catch (_) {} }; const formatLocalRemainCount = (value) => { if (value === "无限") return "无限"; const n = Number(value || 0); return n >= 999999 ? "无限" : n; }; const fetchLocalBackendProfile = async (token) => { token = token || ""; if (!token) return null; return new Promise((resolve) => { GM_xmlhttpRequest({ url: "https://xs.openget.cn/api/user/profile?v=xs-5.0", method: "GET", headers: { "Authorization": "Bearer " + token }, timeout: 15e3, onload: (response) => { try { const obj = JSON.parse(response.responseText || "{}"); resolve(obj.code === 200 ? obj.profile : null); } catch (_) { resolve(null); } }, onerror: () => resolve(null), ontimeout: () => resolve(null) }); }); }; const _downloadImg = (url) => new Promise((resolve) => { GM_xmlhttpRequest({ url, method: 'GET', responseType: 'arraybuffer', timeout: 15000, onload: (res) => { try { const bytes = new Uint8Array(res.response); let binary = ''; const chunk = 0x8000; for (let i = 0; i < bytes.length; i += chunk) binary += String.fromCharCode.apply(null, bytes.subarray(i, i + chunk)); const type = (res.responseHeaders.match(/content-type:\s*([^\s;\r\n]+)/i) || [])[1] || 'image/jpeg'; resolve('data:' + type + ';base64,' + btoa(binary)); } catch (e) { resolve(null); } }, onerror: () => resolve(null), ontimeout: () => resolve(null), }); }); const callLocalBackendAnswer = async (question) => { const setting = useSettingStore(); const token = setting.config.basicConfig.token.value || getLocalBackendToken() || ""; // 提取题目中的图片并下载为 base64 const images = []; const el = question.element; if (el && el.querySelectorAll) { const imgEls = el.querySelectorAll('img'); for (const img of imgEls) { const src = img.src || img.getAttribute('data-src') || ''; if (src) { const dataUrl = await _downloadImg(src); if (dataUrl) images.push(dataUrl); } } } // 兜底:从 title HTML 中提取 URL(element 不可用时) if (!images.length && question.title) { const titleImgs = []; let m; const re = /]+src=["']([^"']+)["']/gi; while ((m = re.exec(question.title))) { if (m[1] && m[1].startsWith('http')) titleImgs.push(m[1]); } for (const src of titleImgs) { const dataUrl = await _downloadImg(src); if (dataUrl) images.push(dataUrl); } } const payloadObj = { question: question.title || "", options: question.optionsText || [], type: question.type || "", platform: "abc-helper", refer: question.refer || location.href }; if (images.length) payloadObj.images = images; const payload = JSON.stringify(payloadObj); const data = "question=" + encodeURIComponent(payload) + "&u=" + encodeURIComponent(getCookieValue("_uid") || getCookieValue("UID") || "") + "&model_mode=auto"; await sleep(setting.config.basicConfig.reqIntervalTime.value); return new Promise((resolve) => { GM_xmlhttpRequest({ url: LOCAL_BACKEND_ANSWER_URL + "?v=xs-5.0", method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8", "Authorization": token ? "Bearer " + token : "" }, data, timeout: 24e4, onload: (response) => { try { const obj = JSON.parse(response.responseText || "{}"); if (obj.code === 200 && obj.data && obj.data.answer) { resolve({ code: 1, data: { answer: normalizeLocalBackendAnswers(obj.data.answer), remainCount: obj.data.remainCount !== void 0 ? formatLocalRemainCount(obj.data.remainCount) : getProfileRemainingCount(obj.data.profile) }, msg: obj.msg || (obj.data.bank ? "题库命中" : obj.data.cache ? "缓存命中" : "本地后端返回") }); if (obj.data.profile) updateUserInfoFromProfile(obj.data.profile); } else if (response.status === 401 || obj.code === 401) { resolve(handleError$1("本地后端未登录或登录态失效,请先登录用户中心")); } else { const is200NoAnswer = obj.code === 200 && !(obj.data && obj.data.answer); resolve({ code: -1004, data: { answer: [], remainCount: 0 }, msg: is200NoAnswer ? "未找到答案" : (obj.msg || "本地题库未命中") }); } } catch (e) { resolve(handleError$1("本地后端响应解析失败")); } }, onerror: () => resolve(handleError$1("无法连接本地后端 https://xs.openget.cn")), ontimeout: () => resolve(handleError$1("本地后端请求超时,请检查后端模型配置或稍后重试")) }); }); }; const saveAnswerRecord = ({ question, answer, source = "accurate" }) => { const setting = useSettingStore(); const answerRecordStore = useAnswerRecordStore(); return answerRecordStore.insertRecord({ question, answer, token: setting.config.basicConfig.token.value, source }); }; const getAccurateAnswer = async (question) => { return callLocalBackendAnswer(question); }; const fetchFuzzySearch = async (question) => { const setting = useSettingStore(); const token = setting.config.basicConfig.token.value || getLocalBackendToken() || ""; const data = "question=" + encodeURIComponent(question); return new Promise((resolve) => { GM_xmlhttpRequest({ url: LOCAL_BACKEND_SEARCH_URL, method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8", "Authorization": token ? "Bearer " + token : "" }, data, timeout: 24e4, onload: (response) => { try { const obj = JSON.parse(response.responseText || "{}"); if (obj.code === 200 && obj.data && obj.data.found) { const matches = obj.data.matches || []; const items = matches.map((m) => ({ title: m.question || question, options: JSON.stringify(m.options || []), answer: JSON.stringify([m.answer || ""]), type: String(m.type || "8") })); resolve({ code: 1, data: { items }, msg: obj.msg || "success" }); } else if (response.status === 401 || obj.code === 401) { resolve(handleError$1("本地后端未登录或登录态失效,请先登录用户中心")); } else { const miss = obj.data && obj.data.found === false; resolve({ code: obj.code || -1004, data: { items: [] }, msg: miss ? "题库暂未收录这道题" : (obj.msg || "未查询到答案") }); } } catch (e) { resolve({ code: -1004, data: { items: [] }, msg: "搜索响应解析失败" }); } }, onerror: () => resolve({ code: -1004, data: { items: [] }, msg: "无法连接本地后端" }), ontimeout: () => resolve({ code: -1004, data: { items: [] }, msg: "搜索请求超时,请稍后重试" }) }); }); }; const getAIAnswer = async (question) => { return callLocalBackendAnswer(question); }; const _hoisted_1$4 = { class: "online-search-wrap" }; const _hoisted_2$3 = { class: "tips" }; const _hoisted_3$2 = { class: "config" }; const _hoisted_4$2 = { class: "config-input" }; const _hoisted_5$2 = { class: "searth-input-wrap" }; const _hoisted_6$2 = { class: "searth-input" }; const _hoisted_7$2 = { class: "search-btn" }; const _hoisted_8$2 = { class: "result-list" }; const _hoisted_9$2 = ["innerHTML"]; const _hoisted_10$1 = ["innerHTML"]; const _hoisted_11$1 = ["innerHTML"]; const _hoisted_12$1 = { key: 0, style: { "text-align": "center", "font-size": "15px" } }; const _sfc_main$5 = { __name: "OnlineSearch", emits: ["changeTab"], setup(__props, { emit: __emit }) { const [messageApi, contextHolder] = Antd.message.useMessage(); const setting = useSettingStore(); const questionStore = useQuestionStore(); const { searchText } = pinia.storeToRefs(questionStore); const errInfo = vue.ref(""); const isLoading = vue.ref(false); const isComposing = vue.ref(false); const emit = __emit; const goSetting = () => { window.open(setting.shopLink || "https://xs.openget.cn/user?section=account", "_blank"); }; const tokenReady = vue.computed(() => { const remain = String(setting.userInfo.remainCount || ""); return !!setting.config.basicConfig.token.value && remain && !["密钥未配置", "密钥无效", "null"].includes(remain); }); const typeMap = { 单选题: "0", 多选题: "1", 填空题: "2", 判断题: "3", 简答题: "4", 问答题: "4", 名词解释: "5", 论述题: "6", 计算题: "7", 分录题: "9", 资料题: "10", 连线题: "11", 匹配题: "11", 排序题: "13", 完型填空: "14", 完形填空题: "14", 阅读理解: "15", 程序题: "17", 口语题: "18", 听力题: "19", 共用选项题: "20", 测评题: "21", 钟表题: "23", 选词填空: "24", 选做题: "25", 其它: "8" }; const getTypeByCode = vue.computed(() => { return function(type) { let key = Object.keys(typeMap).find((key2) => typeMap[key2] === String(type)); return key || ""; }; }); const answerJoin = vue.computed(() => { return function(answerList) { try { return JSON.parse(answerList).join(";"); } catch { return answerList; } }; }); const { fuzzySearchResult } = pinia.storeToRefs(questionStore); const fuzzySearch = async (e) => { e.preventDefault(); if (!setting.config.basicConfig.token.value) { messageApi.info("未配置密钥,请先前往“设置”模块填写密钥.."); return; } if (!searchText.value || isComposing.value) return; if (searchText.value) { errInfo.value = ""; questionStore.clearFuzzySearchResult(); isLoading.value = true; const res = await fetchFuzzySearch(searchText.value); isLoading.value = false; if (res.code === 1) { const { data: { items } } = res; questionStore.fuzzySearchResult = items; } else { errInfo.value = res.msg; } } else { messageApi.info("请输入题目.."); } }; return (_ctx, _cache) => { const _component_a_button = vue.resolveComponent("a-button"); const _component_a_switch = vue.resolveComponent("a-switch"); const _component_a_textarea = vue.resolveComponent("a-textarea"); return vue.openBlock(), vue.createElementBlock("div", _hoisted_1$4, [ vue.createVNode(vue.unref(contextHolder)), vue.createElementVNode("div", _hoisted_2$3, [ vue.createElementVNode("ul", null, [ vue.createElementVNode("li", null, [ _cache[5] || (_cache[5] = vue.createTextVNode(" 使用在线搜题前,请先前往 ")), vue.createVNode(_component_a_button, { onClick: goSetting, size: "small", type: "primary", danger: tokenReady.value }, { icon: vue.withCtx(() => [ vue.createVNode(vue.unref(SettingOutlined)) ]), default: vue.withCtx(() => [ vue.createTextVNode(vue.toDisplayString(tokenReady.value ? "已配置密钥" : "登录用户中心获取密钥"), 1) ]), _: 1 }) ]), _cache[6] || (_cache[6] = vue.createElementVNode("li", null, "如需手动答题,建议关闭自动切换功能,防止操作冲突", -1)) ]) ]), vue.createElementVNode("div", _hoisted_3$2, [ _cache[7] || (_cache[7] = vue.createElementVNode("div", { class: "config-name" }, "划词搜题", -1)), vue.createElementVNode("div", _hoisted_4$2, [ vue.createVNode(_component_a_switch, { size: "small", checked: vue.unref(setting).config.searchConfig.searchBySelectedText.value, "onUpdate:checked": _cache[0] || (_cache[0] = ($event) => vue.unref(setting).config.searchConfig.searchBySelectedText.value = $event) }, null, 8, ["checked"]) ]) ]), vue.createElementVNode("div", _hoisted_5$2, [ _cache[8] || (_cache[8] = vue.createElementVNode("div", { class: "title" }, "搜索题目", -1)), vue.createElementVNode("div", _hoisted_6$2, [ vue.createVNode(_component_a_textarea, { onKeydown: vue.withKeys(vue.withModifiers(fuzzySearch, ["exact"]), ["enter"]), onCompositionstart: _cache[1] || (_cache[1] = ($event) => isComposing.value = true), onCompositionend: _cache[2] || (_cache[2] = ($event) => isComposing.value = false), value: vue.unref(searchText), "onUpdate:value": _cache[3] || (_cache[3] = ($event) => vue.isRef(searchText) ? searchText.value = $event : null), placeholder: "请输入题目", rows: 4 }, null, 8, ["onKeydown", "value"]) ]) ]), vue.createElementVNode("div", _hoisted_7$2, [ vue.createVNode(_component_a_button, { onClick: fuzzySearch, style: { "width": "120px" }, type: "primary", size: "small" }, { default: vue.withCtx(() => _cache[9] || (_cache[9] = [ vue.createTextVNode("搜索") ])), _: 1 }) ]), vue.createElementVNode("div", _hoisted_8$2, [ vue.createElementVNode("div", { innerHTML: errInfo.value }, null, 8, _hoisted_9$2), (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(vue.unref(fuzzySearchResult), (result, index) => { return vue.openBlock(), vue.createElementBlock("div", { class: "result-item", key: index }, [ vue.createElementVNode("div", null, [ vue.createElementVNode("span", null, vue.toDisplayString(`${index + 1}.【${getTypeByCode.value(result.type)}】`), 1), vue.createElementVNode("span", { innerHTML: result.title }, null, 8, _hoisted_10$1) ]), vue.createElementVNode("div", null, [ (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(JSON.parse(result.options), (option, index2) => { return vue.openBlock(), vue.createElementBlock("div", { key: index2 }, [ vue.createElementVNode("span", null, vue.toDisplayString(String.fromCharCode(65 + index2)) + ".", 1), vue.createElementVNode("span", null, vue.toDisplayString(option), 1) ]); }), 128)) ]), vue.createElementVNode("div", { class: "result-answer", innerHTML: answerJoin.value(result.answer) }, null, 8, _hoisted_11$1) ]); }), 128)), isLoading.value ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_12$1, [ _cache[10] || (_cache[10] = vue.createElementVNode("span", { style: { "margin-right": "10px" } }, "搜索中", -1)), vue.createVNode(vue.unref(LoadingOutlined)) ])) : vue.createCommentVNode("", true) ]) ]); }; } }; const _hoisted_1$3 = { class: "ai-wrapper" }; const _hoisted_2$2 = { class: "ai-message-role-avatar" }; const _hoisted_3$1 = { key: 0, fill: "#333", "fill-rule": "evenodd", height: "30", style: { "flex": "0 0 auto", "line-height": "1" }, viewBox: "0 0 30 30", width: "30", xmlns: "http://www.w3.org/2000/svg", class: "user-avatar" }; const _hoisted_4$1 = { key: 1, class: "user-avatar" }; const _hoisted_5$1 = { class: "ai-message-content" }; const _hoisted_6$1 = { key: 0, class: "loading-container" }; const _hoisted_7$1 = ["innerHTML"]; const _hoisted_8$1 = { class: "ai-input-wrapper" }; const _hoisted_9$1 = ["onKeydown"]; const _sfc_main$4 = { __name: "AiHome", setup(__props) { const [messageApi, contextHolder] = Antd.message.useMessage(); const setting = useSettingStore(); const currentAbortController = vue.ref(null); const isLoading = vue.ref(false); const messages = vue.ref([{ role: "system", content: "你好,有什么可以帮你的吗?" }]); const input = vue.ref(""); const messageContainer = vue.ref(null); const isComposing = vue.ref(false); const scrollToBottom = () => { vue.nextTick(() => { if (messageContainer.value) { messageContainer.value.scrollTop = messageContainer.value.scrollHeight; } }); setTimeout(() => { if (messageContainer.value) { messageContainer.value.scrollTop = messageContainer.value.scrollHeight; } }, 100); }; const formatMessage = (message2) => { return marked.marked(message2); }; const triggerAi = async (event) => { event.preventDefault(); if (isLoading.value) return; if (!input.value || isComposing.value) return; if (!setting.config.basicConfig.token.value) { messageApi.info("未配置密钥,请先前往“设置”模块填写密钥.."); return; } const inputValue = input.value; input.value = ""; messages.value.push({ role: "user", content: inputValue }); messages.value.push({ role: "system", content: "思考中" }); scrollToBottom(); try { const requestData = { content: inputValue, token: setting.config.basicConfig.token.value }; const controller = new AbortController(); const signal = controller.signal; currentAbortController.value = controller; isLoading.value = true; const response = await fetch("https://she.aichathelper.top/ai/ds", { method: "POST", signal, headers: { "Content-Type": "application/json" }, body: JSON.stringify(requestData) }); if (!response.ok) { messages.value[messages.value.length - 1].content = "发送消息失败,请重试"; currentAbortController.value = null; isLoading.value = false; return; } const reader = response.body.getReader(); const decoder = new TextDecoder("utf-8"); while (true) { const { done, value } = await reader.read(); if (done) break; const chunk = decoder.decode(value); console.log("收到数据块:", chunk); const lines = chunk.split("\n").filter((line) => line.trim() !== ""); const jsonStr = lines.find((line) => line.startsWith("{") && line.endsWith("}")); if (jsonStr) { const { code, msg } = JSON.parse(jsonStr); if (code) { messages.value[messages.value.length - 1].content = msg; currentAbortController.value = null; isLoading.value = false; return; } } for (const line of lines) { if (line.startsWith("0:")) { const jsonStr2 = line.slice(2); try { const content = JSON.parse(jsonStr2); console.log("解析的内容:", content); const lastMessage = messages.value[messages.value.length - 1]; lastMessage.content = lastMessage.content === "思考中" ? content : lastMessage.content + content; scrollToBottom(); } catch (e) { console.error("解析错误:", e, "行内容:", line); } } } } isLoading.value = false; } catch (error) { if (error.name === "AbortError") { console.log("请求被终止"); } else { console.error("发送消息错误:", error); messages.value[messages.value.length - 1].content = "发送消息失败,请重试"; } currentAbortController.value = null; isLoading.value = false; } }; return (_ctx, _cache) => { return vue.openBlock(), vue.createElementBlock("div", _hoisted_1$3, [ vue.createVNode(vue.unref(contextHolder)), vue.createElementVNode("div", { class: "ai-message-list", ref_key: "messageContainer", ref: messageContainer }, [ (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(messages.value, (m, index) => { return vue.openBlock(), vue.createElementBlock("div", { class: "ai-message-item", key: index }, [ vue.createElementVNode("div", { class: vue.normalizeClass(["ai-message-container", { "user-message": m.role === "user" }]) }, [ vue.createElementVNode("div", _hoisted_2$2, [ m.role === "system" ? (vue.openBlock(), vue.createElementBlock("svg", _hoisted_3$1, _cache[3] || (_cache[3] = [ vue.createElementVNode("rect", { width: "30", height: "30", fill: "#E7F8FF", rx: "6" }, null, -1), vue.createElementVNode("path", { d: "M24.55 13.004a5.416 5.416 0 0 0-.478-4.501c-1.217-2.09-3.662-3.166-6.05-2.66A5.59 5.59 0 0 0 13.831 4c-2.441-.005-4.607 1.546-5.358 3.838a5.553 5.553 0 0 0-3.713 2.658 5.487 5.487 0 0 0 .691 6.5 5.416 5.416 0 0 0 .477 4.502c1.217 2.09 3.662 3.165 6.05 2.66A5.586 5.586 0 0 0 16.168 26c2.443.006 4.61-1.546 5.361-3.84a5.553 5.553 0 0 0 3.715-2.66 5.488 5.488 0 0 0-.693-6.497v.001zm-8.381 11.558a4.199 4.199 0 0 1-2.675-.954c.034-.018.093-.05.132-.074l4.44-2.53a.71.71 0 0 0 .364-.623v-6.176l1.877 1.069c.02.01.033.029.036.05v5.115c-.003 2.274-1.87 4.118-4.174 4.123zM7.192 20.78a4.059 4.059 0 0 1-.498-2.763c.032.02.09.055.131.078l4.44 2.53c.225.13.504.13.73 0l5.42-3.088v2.138a.068.068 0 0 1-.027.057L12.9 22.288c-1.999 1.136-4.552.46-5.707-1.51h-.001zm-1.169-9.564A4.15 4.15 0 0 1 8.198 9.41l-.002.151v5.06a.711.711 0 0 0 .364.624l5.42 3.087-1.876 1.07a.067.067 0 0 1-.063.005l-4.489-2.559c-1.995-1.14-2.679-3.658-1.53-5.63h.001zm15.417 3.54-5.42-3.088 1.876-1.068a.067.067 0 0 1 .063-.006l4.489 2.557c1.998 1.14 2.683 3.662 1.529 5.633a4.163 4.163 0 0 1-2.174 1.807V15.38a.71.71 0 0 0-.363-.623zm1.867-2.773a6.04 6.04 0 0 0-.132-.078l-4.44-2.53a.731.731 0 0 0-.729 0l-5.42 3.088v-2.138a.068.068 0 0 1 .027-.057L17.1 7.713c2-1.137 4.555-.46 5.707 1.513.487.833.664 1.809.499 2.757h.001zm-11.741 3.81-1.877-1.068a.065.065 0 0 1-.036-.051V9.559c.001-2.277 1.873-4.122 4.181-4.12.976 0 1.92.338 2.671.954-.034.018-.092.05-.131.073l-4.44 2.53a.71.71 0 0 0-.365.623l-.003 6.173v.002zm1.02-2.168L15 12.25l2.414 1.375v2.75L15 17.75l-2.415-1.375v-2.75z" }, null, -1) ]))) : (vue.openBlock(), vue.createElementBlock("div", _hoisted_4$1, " 🙂 ")) ]), vue.createElementVNode("div", _hoisted_5$1, [ m.content === "思考中" ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_6$1, [ _cache[4] || (_cache[4] = vue.createElementVNode("span", null, "思考中", -1)), vue.createVNode(vue.unref(LoadingOutlined), { class: "loading-icon" }) ])) : (vue.openBlock(), vue.createElementBlock("div", { key: 1, innerHTML: formatMessage(m.content) }, null, 8, _hoisted_7$1)) ]) ], 2) ]); }), 128)) ], 512), vue.createElementVNode("div", _hoisted_8$1, [ vue.withDirectives(vue.createElementVNode("textarea", { "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => input.value = $event), placeholder: "请输入你的问题...", rows: "2", onKeydown: vue.withKeys(vue.withModifiers(triggerAi, ["exact"]), ["enter"]), maxlength: "500", onCompositionstart: _cache[1] || (_cache[1] = ($event) => isComposing.value = true), onCompositionend: _cache[2] || (_cache[2] = ($event) => isComposing.value = false) }, null, 40, _hoisted_9$1), [ [vue.vModelText, input.value] ]), vue.createElementVNode("div", { class: "send-message-btn", onClick: triggerAi }, [ _cache[5] || (_cache[5] = vue.createElementVNode("span", null, "发送", -1)), vue.createVNode(vue.unref(SendOutlined)) ]) ]) ]); }; } }; const _hoisted_1$2 = { class: "record-wrap" }; const _hoisted_2$1 = { class: "record-tip" }; const _hoisted_3 = { class: "record-filter" }; const _hoisted_4 = ["onClick"]; const _hoisted_5 = { key: 0, class: "record-list" }; const _hoisted_6 = { class: "record-row" }; const _hoisted_7 = ["title"]; const _hoisted_8 = { class: "record-row" }; const _hoisted_9 = ["title"]; const _hoisted_10 = { class: "record-row" }; const _hoisted_11 = ["title"]; const _hoisted_12 = { class: "record-row" }; const _hoisted_13 = ["title"]; const _hoisted_14 = { key: 1, class: "tips" }; const _sfc_main$3 = { __name: "AnswerRecord", setup(__props) { const answerRecordStore = useAnswerRecordStore(); const { displayRecords } = pinia.storeToRefs(answerRecordStore); const activeFilter = vue.ref("all"); const filterOptions = [ { label: "所有", value: "all" }, { label: "今天", value: "today" }, { label: "昨天", value: "yesterday" }, { label: "近一周", value: "week" }, { label: "近一月", value: "month" } ]; const getDayStart = (date = /* @__PURE__ */ new Date()) => new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime(); const filteredRecords = vue.computed(() => { const now = Date.now(); const todayStart = getDayStart(); const yesterdayStart = todayStart - 24 * 60 * 60 * 1e3; return displayRecords.value.filter((record) => { const timestamp = record.timestamp || 0; if (activeFilter.value === "all") { return true; } if (activeFilter.value === "today") { return timestamp >= todayStart; } if (activeFilter.value === "yesterday") { return timestamp >= yesterdayStart && timestamp < todayStart; } if (activeFilter.value === "week") { return timestamp >= now - 7 * 24 * 60 * 60 * 1e3; } if (activeFilter.value === "month") { return timestamp >= now - 30 * 24 * 60 * 60 * 1e3; } return true; }); }); const clearRecords = () => { answerRecordStore.clearRecords(); }; const formatAnswer = (answer) => { if (!Array.isArray(answer) || answer.length === 0) return "-"; return answer.join(";"); }; return (_ctx, _cache) => { const _component_a_button = vue.resolveComponent("a-button"); return vue.openBlock(), vue.createElementBlock("div", _hoisted_1$2, [ _cache[5] || (_cache[5] = vue.createElementVNode("div", { class: "separator" }, "▤ 记录", -1)), vue.createElementVNode("div", _hoisted_2$1, [ vue.createElementVNode("span", null, "最多保留 200 条答题记录,当前 " + vue.toDisplayString(filteredRecords.value.length) + " 条", 1), vue.createVNode(_component_a_button, { class: "clear-btn", size: "small", onClick: clearRecords }, { default: vue.withCtx(() => _cache[0] || (_cache[0] = [ vue.createTextVNode("清空记录") ])), _: 1 }) ]), vue.createElementVNode("div", _hoisted_3, [ (vue.openBlock(), vue.createElementBlock(vue.Fragment, null, vue.renderList(filterOptions, (item) => { return vue.createElementVNode("button", { key: item.value, class: vue.normalizeClass(["filter-btn", { active: activeFilter.value === item.value }]), onClick: ($event) => activeFilter.value = item.value }, vue.toDisplayString(item.label), 11, _hoisted_4); }), 64)) ]), filteredRecords.value.length ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_5, [ (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(filteredRecords.value, (record) => { return vue.openBlock(), vue.createElementBlock("div", { key: `${record.cacheKey}-${record.timestamp}`, class: "record-item" }, [ vue.createElementVNode("div", _hoisted_6, [ _cache[1] || (_cache[1] = vue.createElementVNode("span", { class: "record-label" }, "密钥", -1)), vue.createElementVNode("span", { class: "record-value", title: record.token || "未配置" }, vue.toDisplayString(record.token || "未配置"), 9, _hoisted_7) ]), vue.createElementVNode("div", _hoisted_8, [ _cache[2] || (_cache[2] = vue.createElementVNode("span", { class: "record-label" }, "题目", -1)), vue.createElementVNode("span", { class: "record-value", title: record.title }, vue.toDisplayString(record.title), 9, _hoisted_9) ]), vue.createElementVNode("div", _hoisted_10, [ _cache[3] || (_cache[3] = vue.createElementVNode("span", { class: "record-label" }, "答案", -1)), vue.createElementVNode("span", { class: "record-value", title: formatAnswer(record.answer) }, vue.toDisplayString(formatAnswer(record.answer)), 9, _hoisted_11) ]), vue.createElementVNode("div", _hoisted_12, [ _cache[4] || (_cache[4] = vue.createElementVNode("span", { class: "record-label" }, "请求时间", -1)), vue.createElementVNode("span", { class: "record-value", title: record.requestTime }, vue.toDisplayString(record.requestTime), 9, _hoisted_13) ]) ]); }), 128)) ])) : (vue.openBlock(), vue.createElementBlock("div", _hoisted_14, " 当前筛选条件下暂无答题记录。成功搜到答案并扣次后,会自动写入本地缓存。 ")) ]); }; } }; const useMessageStore = pinia.defineStore("messageStore", { state: () => ({ messageList: [] }), actions: { insertMessage(text, type = "success") { const message2 = { text, type }; this.messageList.push(message2); }, clearMessage() { this.messageList = []; } } }); const TIMEOUT_ERROR_MSG = "请求超时,请前往设置尝试切换节点.."; const SERVER_MAINTENANCE_ERROR_MSG = "服务器维护中,请前往设置尝试切换节点,自动学习功能正常使用.."; const handleError = (msg) => ({ code: 10003, data: { answer: [], num: "", usenum: "" }, msg }); const fetchBasicInfo = async () => { const scriptInfo = getScriptInfo(); return { code: 1, data: { shopLink: "https://xs.openget.cn/user?section=account", version: scriptInfo.version, chatWebUrl: "已接入 AI 智能答题云服务" } }; }; const getTokenInfo = async () => { let token = ""; try { const setting = useSettingStore(); token = setting.config.basicConfig.token.value || getLocalBackendToken() || ""; } catch (_) { token = getLocalBackendToken() || ""; } const profile = await fetchLocalBackendProfile(token); const remainCount = getProfileRemainingCount(profile); return { code: profile ? 1 : 10003, data: { count: remainCount, usedCount: 0, remainCount }, msg: profile ? "本地后端已同步题数" : "密钥无效或已过期,请到用户中心重新生成脚本密钥" }; }; const selectTextSearchLogic = async (iframeDocument, iframeWindow) => { const questionStore = useQuestionStore(); const setting = useSettingStore(); _unsafeWindow.document.getSelection = function() { return { removeAllRanges: function() { } }; }; _unsafeWindow.document.onselectstart = true; _unsafeWindow.document.oncontextmenu = true; _unsafeWindow.document.oncut = true; _unsafeWindow.document.oncopy = true; _unsafeWindow.document.onpaste = true; const getSelectedText = () => { if (!setting.config.searchConfig.searchBySelectedText.value) return; let selectedText = ""; if (iframeWindow.getSelection) { selectedText = iframeWindow.getSelection().toString(); } else if (iframeDocument.selection) { selectedText = iframeDocument.selection.createRange().text; } if (selectedText != "") { questionStore.searchText = selectedText; setting.tabIndex = "3"; } }; iframeDocument.addEventListener("mouseup", getSelectedText); }; var Typr$1 = {}; var Typr = {}; Typr.parse = function(buff) { var bin = Typr._bin; var data = new Uint8Array(buff); var tag = bin.readASCII(data, 0, 4); if (tag == "ttcf") { var offset = 4; bin.readUshort(data, offset); offset += 2; bin.readUshort(data, offset); offset += 2; var numF = bin.readUint(data, offset); offset += 4; var fnts = []; for (var i = 0; i < numF; i++) { var foff = bin.readUint(data, offset); offset += 4; fnts.push(Typr._readFont(data, foff)); } return fnts; } else return [Typr._readFont(data, 0)]; }; Typr._readFont = function(data, offset) { var bin = Typr._bin; var ooff = offset; bin.readFixed(data, offset); offset += 4; var numTables = bin.readUshort(data, offset); offset += 2; bin.readUshort(data, offset); offset += 2; bin.readUshort(data, offset); offset += 2; bin.readUshort(data, offset); offset += 2; var tags = [ "cmap", "head", "hhea", "maxp", "hmtx", "name", "OS/2", "post", //"cvt", //"fpgm", "loca", "glyf", "kern", //"prep" //"gasp" "CFF ", "GPOS", "GSUB", "SVG " //"VORG", ]; var obj = { _data: data, _offset: ooff }; var tabs = {}; for (var i = 0; i < numTables; i++) { var tag = bin.readASCII(data, offset, 4); offset += 4; bin.readUint(data, offset); offset += 4; var toffset = bin.readUint(data, offset); offset += 4; var length = bin.readUint(data, offset); offset += 4; tabs[tag] = { offset: toffset, length }; } for (var i = 0; i < tags.length; i++) { var t = tags[i]; if (tabs[t]) obj[t.trim()] = Typr[t.trim()].parse(data, tabs[t].offset, tabs[t].length, obj); } return obj; }; Typr._tabOffset = function(data, tab, foff) { var bin = Typr._bin; var numTables = bin.readUshort(data, foff + 4); var offset = foff + 12; for (var i = 0; i < numTables; i++) { var tag = bin.readASCII(data, offset, 4); offset += 4; bin.readUint(data, offset); offset += 4; var toffset = bin.readUint(data, offset); offset += 4; bin.readUint(data, offset); offset += 4; if (tag == tab) return toffset; } return 0; }; Typr._bin = { readFixed: function(data, o) { return (data[o] << 8 | data[o + 1]) + (data[o + 2] << 8 | data[o + 3]) / (256 * 256 + 4); }, readF2dot14: function(data, o) { var num = Typr._bin.readShort(data, o); return num / 16384; }, readInt: function(buff, p) { return Typr._bin._view(buff).getInt32(p); }, readInt8: function(buff, p) { return Typr._bin._view(buff).getInt8(p); }, readShort: function(buff, p) { return Typr._bin._view(buff).getInt16(p); }, readUshort: function(buff, p) { return Typr._bin._view(buff).getUint16(p); }, readUshorts: function(buff, p, len) { var arr = []; for (var i = 0; i < len; i++) arr.push(Typr._bin.readUshort(buff, p + i * 2)); return arr; }, readUint: function(buff, p) { return Typr._bin._view(buff).getUint32(p); }, readUint64: function(buff, p) { return Typr._bin.readUint(buff, p) * (4294967295 + 1) + Typr._bin.readUint(buff, p + 4); }, readASCII: function(buff, p, l) { var s = ""; for (var i = 0; i < l; i++) s += String.fromCharCode(buff[p + i]); return s; }, readUnicode: function(buff, p, l) { var s = ""; for (var i = 0; i < l; i++) { var c = buff[p++] << 8 | buff[p++]; s += String.fromCharCode(c); } return s; }, _tdec: typeof window !== "undefined" && window["TextDecoder"] ? new window["TextDecoder"]() : null, readUTF8: function(buff, p, l) { var tdec = Typr._bin._tdec; if (tdec && p == 0 && l == buff.length) return tdec["decode"](buff); return Typr._bin.readASCII(buff, p, l); }, readBytes: function(buff, p, l) { var arr = []; for (var i = 0; i < l; i++) arr.push(buff[p + i]); return arr; }, readASCIIArray: function(buff, p, l) { var s = []; for (var i = 0; i < l; i++) s.push(String.fromCharCode(buff[p + i])); return s; }, _view: function(buff) { return buff._dataView || (buff._dataView = buff.buffer ? new DataView(buff.buffer, buff.byteOffset, buff.byteLength) : new DataView(new Uint8Array(buff).buffer)); } }; Typr._lctf = {}; Typr._lctf.parse = function(data, offset, length, font, subt) { var bin = Typr._bin; var obj = {}; var offset0 = offset; bin.readFixed(data, offset); offset += 4; var offScriptList = bin.readUshort(data, offset); offset += 2; var offFeatureList = bin.readUshort(data, offset); offset += 2; var offLookupList = bin.readUshort(data, offset); offset += 2; obj.scriptList = Typr._lctf.readScriptList(data, offset0 + offScriptList); obj.featureList = Typr._lctf.readFeatureList(data, offset0 + offFeatureList); obj.lookupList = Typr._lctf.readLookupList(data, offset0 + offLookupList, subt); return obj; }; Typr._lctf.readLookupList = function(data, offset, subt) { var bin = Typr._bin; var offset0 = offset; var obj = []; var count = bin.readUshort(data, offset); offset += 2; for (var i = 0; i < count; i++) { var noff = bin.readUshort(data, offset); offset += 2; var lut = Typr._lctf.readLookupTable(data, offset0 + noff, subt); obj.push(lut); } return obj; }; Typr._lctf.readLookupTable = function(data, offset, subt) { var bin = Typr._bin; var offset0 = offset; var obj = { tabs: [] }; obj.ltype = bin.readUshort(data, offset); offset += 2; obj.flag = bin.readUshort(data, offset); offset += 2; var cnt = bin.readUshort(data, offset); offset += 2; var ltype = obj.ltype; for (var i = 0; i < cnt; i++) { var noff = bin.readUshort(data, offset); offset += 2; var tab = subt(data, ltype, offset0 + noff, obj); obj.tabs.push(tab); } return obj; }; Typr._lctf.numOfOnes = function(n) { var num = 0; for (var i = 0; i < 32; i++) if ((n >>> i & 1) != 0) num++; return num; }; Typr._lctf.readClassDef = function(data, offset) { var bin = Typr._bin; var obj = []; var format = bin.readUshort(data, offset); offset += 2; if (format == 1) { var startGlyph = bin.readUshort(data, offset); offset += 2; var glyphCount = bin.readUshort(data, offset); offset += 2; for (var i = 0; i < glyphCount; i++) { obj.push(startGlyph + i); obj.push(startGlyph + i); obj.push(bin.readUshort(data, offset)); offset += 2; } } if (format == 2) { var count = bin.readUshort(data, offset); offset += 2; for (var i = 0; i < count; i++) { obj.push(bin.readUshort(data, offset)); offset += 2; obj.push(bin.readUshort(data, offset)); offset += 2; obj.push(bin.readUshort(data, offset)); offset += 2; } } return obj; }; Typr._lctf.getInterval = function(tab, val) { for (var i = 0; i < tab.length; i += 3) { var start = tab[i], end = tab[i + 1]; tab[i + 2]; if (start <= val && val <= end) return i; } return -1; }; Typr._lctf.readCoverage = function(data, offset) { var bin = Typr._bin; var cvg = {}; cvg.fmt = bin.readUshort(data, offset); offset += 2; var count = bin.readUshort(data, offset); offset += 2; if (cvg.fmt == 1) cvg.tab = bin.readUshorts(data, offset, count); if (cvg.fmt == 2) cvg.tab = bin.readUshorts(data, offset, count * 3); return cvg; }; Typr._lctf.coverageIndex = function(cvg, val) { var tab = cvg.tab; if (cvg.fmt == 1) return tab.indexOf(val); if (cvg.fmt == 2) { var ind = Typr._lctf.getInterval(tab, val); if (ind != -1) return tab[ind + 2] + (val - tab[ind]); } return -1; }; Typr._lctf.readFeatureList = function(data, offset) { var bin = Typr._bin; var offset0 = offset; var obj = []; var count = bin.readUshort(data, offset); offset += 2; for (var i = 0; i < count; i++) { var tag = bin.readASCII(data, offset, 4); offset += 4; var noff = bin.readUshort(data, offset); offset += 2; var feat = Typr._lctf.readFeatureTable(data, offset0 + noff); feat.tag = tag.trim(); obj.push(feat); } return obj; }; Typr._lctf.readFeatureTable = function(data, offset) { var bin = Typr._bin; var offset0 = offset; var feat = {}; var featureParams = bin.readUshort(data, offset); offset += 2; if (featureParams > 0) { feat.featureParams = offset0 + featureParams; } var lookupCount = bin.readUshort(data, offset); offset += 2; feat.tab = []; for (var i = 0; i < lookupCount; i++) feat.tab.push(bin.readUshort(data, offset + 2 * i)); return feat; }; Typr._lctf.readScriptList = function(data, offset) { var bin = Typr._bin; var offset0 = offset; var obj = {}; var count = bin.readUshort(data, offset); offset += 2; for (var i = 0; i < count; i++) { var tag = bin.readASCII(data, offset, 4); offset += 4; var noff = bin.readUshort(data, offset); offset += 2; obj[tag.trim()] = Typr._lctf.readScriptTable(data, offset0 + noff); } return obj; }; Typr._lctf.readScriptTable = function(data, offset) { var bin = Typr._bin; var offset0 = offset; var obj = {}; var defLangSysOff = bin.readUshort(data, offset); offset += 2; if (defLangSysOff > 0) { obj["default"] = Typr._lctf.readLangSysTable(data, offset0 + defLangSysOff); } var langSysCount = bin.readUshort(data, offset); offset += 2; for (var i = 0; i < langSysCount; i++) { var tag = bin.readASCII(data, offset, 4); offset += 4; var langSysOff = bin.readUshort(data, offset); offset += 2; obj[tag.trim()] = Typr._lctf.readLangSysTable(data, offset0 + langSysOff); } return obj; }; Typr._lctf.readLangSysTable = function(data, offset) { var bin = Typr._bin; var obj = {}; bin.readUshort(data, offset); offset += 2; obj.reqFeature = bin.readUshort(data, offset); offset += 2; var featureCount = bin.readUshort(data, offset); offset += 2; obj.features = bin.readUshorts(data, offset, featureCount); return obj; }; Typr.CFF = {}; Typr.CFF.parse = function(data, offset, length) { var bin = Typr._bin; data = new Uint8Array(data.buffer, offset, length); offset = 0; data[offset]; offset++; data[offset]; offset++; data[offset]; offset++; data[offset]; offset++; var ninds = []; offset = Typr.CFF.readIndex(data, offset, ninds); var names2 = []; for (var i = 0; i < ninds.length - 1; i++) names2.push(bin.readASCII(data, offset + ninds[i], ninds[i + 1] - ninds[i])); offset += ninds[ninds.length - 1]; var tdinds = []; offset = Typr.CFF.readIndex(data, offset, tdinds); var topDicts = []; for (var i = 0; i < tdinds.length - 1; i++) topDicts.push(Typr.CFF.readDict(data, offset + tdinds[i], offset + tdinds[i + 1])); offset += tdinds[tdinds.length - 1]; var topdict = topDicts[0]; var sinds = []; offset = Typr.CFF.readIndex(data, offset, sinds); var strings = []; for (var i = 0; i < sinds.length - 1; i++) strings.push(bin.readASCII(data, offset + sinds[i], sinds[i + 1] - sinds[i])); offset += sinds[sinds.length - 1]; Typr.CFF.readSubrs(data, offset, topdict); if (topdict.CharStrings) { offset = topdict.CharStrings; var sinds = []; offset = Typr.CFF.readIndex(data, offset, sinds); var cstr = []; for (var i = 0; i < sinds.length - 1; i++) cstr.push(bin.readBytes(data, offset + sinds[i], sinds[i + 1] - sinds[i])); topdict.CharStrings = cstr; } if (topdict.ROS) { offset = topdict.FDArray; var fdind = []; offset = Typr.CFF.readIndex(data, offset, fdind); topdict.FDArray = []; for (var i = 0; i < fdind.length - 1; i++) { var dict = Typr.CFF.readDict(data, offset + fdind[i], offset + fdind[i + 1]); Typr.CFF._readFDict(data, dict, strings); topdict.FDArray.push(dict); } offset += fdind[fdind.length - 1]; offset = topdict.FDSelect; topdict.FDSelect = []; var fmt = data[offset]; offset++; if (fmt == 3) { var rns = bin.readUshort(data, offset); offset += 2; for (var i = 0; i < rns + 1; i++) { topdict.FDSelect.push(bin.readUshort(data, offset), data[offset + 2]); offset += 3; } } else throw fmt; } if (topdict.Encoding) topdict.Encoding = Typr.CFF.readEncoding(data, topdict.Encoding, topdict.CharStrings.length); if (topdict.charset) topdict.charset = Typr.CFF.readCharset(data, topdict.charset, topdict.CharStrings.length); Typr.CFF._readFDict(data, topdict, strings); return topdict; }; Typr.CFF._readFDict = function(data, dict, ss) { var offset; if (dict.Private) { offset = dict.Private[1]; dict.Private = Typr.CFF.readDict(data, offset, offset + dict.Private[0]); if (dict.Private.Subrs) Typr.CFF.readSubrs(data, offset + dict.Private.Subrs, dict.Private); } for (var p in dict) if (["FamilyName", "FontName", "FullName", "Notice", "version", "Copyright"].indexOf(p) != -1) dict[p] = ss[dict[p] - 426 + 35]; }; Typr.CFF.readSubrs = function(data, offset, obj) { var bin = Typr._bin; var gsubinds = []; offset = Typr.CFF.readIndex(data, offset, gsubinds); var bias, nSubrs = gsubinds.length; if (nSubrs < 1240) bias = 107; else if (nSubrs < 33900) bias = 1131; else bias = 32768; obj.Bias = bias; obj.Subrs = []; for (var i = 0; i < gsubinds.length - 1; i++) obj.Subrs.push(bin.readBytes(data, offset + gsubinds[i], gsubinds[i + 1] - gsubinds[i])); }; Typr.CFF.tableSE = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 0, 111, 112, 113, 114, 0, 115, 116, 117, 118, 119, 120, 121, 122, 0, 123, 0, 124, 125, 126, 127, 128, 129, 130, 131, 0, 132, 133, 0, 134, 135, 136, 137, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 138, 0, 139, 0, 0, 0, 0, 140, 141, 142, 143, 0, 0, 0, 0, 0, 144, 0, 0, 0, 145, 0, 0, 146, 147, 148, 149, 0, 0, 0, 0 ]; Typr.CFF.glyphByUnicode = function(cff, code) { for (var i = 0; i < cff.charset.length; i++) if (cff.charset[i] == code) return i; return -1; }; Typr.CFF.glyphBySE = function(cff, charcode) { if (charcode < 0 || charcode > 255) return -1; return Typr.CFF.glyphByUnicode(cff, Typr.CFF.tableSE[charcode]); }; Typr.CFF.readEncoding = function(data, offset, num) { Typr._bin; var array = [".notdef"]; var format = data[offset]; offset++; if (format == 0) { var nCodes = data[offset]; offset++; for (var i = 0; i < nCodes; i++) array.push(data[offset + i]); } else throw "error: unknown encoding format: " + format; return array; }; Typr.CFF.readCharset = function(data, offset, num) { var bin = Typr._bin; var charset = [".notdef"]; var format = data[offset]; offset++; if (format == 0) { for (var i = 0; i < num; i++) { var first = bin.readUshort(data, offset); offset += 2; charset.push(first); } } else if (format == 1 || format == 2) { while (charset.length < num) { var first = bin.readUshort(data, offset); offset += 2; var nLeft = 0; if (format == 1) { nLeft = data[offset]; offset++; } else { nLeft = bin.readUshort(data, offset); offset += 2; } for (var i = 0; i <= nLeft; i++) { charset.push(first); first++; } } } else throw "error: format: " + format; return charset; }; Typr.CFF.readIndex = function(data, offset, inds) { var bin = Typr._bin; var count = bin.readUshort(data, offset) + 1; offset += 2; var offsize = data[offset]; offset++; if (offsize == 1) for (var i = 0; i < count; i++) inds.push(data[offset + i]); else if (offsize == 2) for (var i = 0; i < count; i++) inds.push(bin.readUshort(data, offset + i * 2)); else if (offsize == 3) for (var i = 0; i < count; i++) inds.push(bin.readUint(data, offset + i * 3 - 1) & 16777215); else if (count != 1) throw "unsupported offset size: " + offsize + ", count: " + count; offset += count * offsize; return offset - 1; }; Typr.CFF.getCharString = function(data, offset, o) { var bin = Typr._bin; var b0 = data[offset], b1 = data[offset + 1]; data[offset + 2]; data[offset + 3]; data[offset + 4]; var vs = 1; var op = null, val = null; if (b0 <= 20) { op = b0; vs = 1; } if (b0 == 12) { op = b0 * 100 + b1; vs = 2; } if (21 <= b0 && b0 <= 27) { op = b0; vs = 1; } if (b0 == 28) { val = bin.readShort(data, offset + 1); vs = 3; } if (29 <= b0 && b0 <= 31) { op = b0; vs = 1; } if (32 <= b0 && b0 <= 246) { val = b0 - 139; vs = 1; } if (247 <= b0 && b0 <= 250) { val = (b0 - 247) * 256 + b1 + 108; vs = 2; } if (251 <= b0 && b0 <= 254) { val = -(b0 - 251) * 256 - b1 - 108; vs = 2; } if (b0 == 255) { val = bin.readInt(data, offset + 1) / 65535; vs = 5; } o.val = val != null ? val : "o" + op; o.size = vs; }; Typr.CFF.readCharString = function(data, offset, length) { var end = offset + length; var bin = Typr._bin; var arr = []; while (offset < end) { var b0 = data[offset], b1 = data[offset + 1]; data[offset + 2]; data[offset + 3]; data[offset + 4]; var vs = 1; var op = null, val = null; if (b0 <= 20) { op = b0; vs = 1; } if (b0 == 12) { op = b0 * 100 + b1; vs = 2; } if (b0 == 19 || b0 == 20) { op = b0; vs = 2; } if (21 <= b0 && b0 <= 27) { op = b0; vs = 1; } if (b0 == 28) { val = bin.readShort(data, offset + 1); vs = 3; } if (29 <= b0 && b0 <= 31) { op = b0; vs = 1; } if (32 <= b0 && b0 <= 246) { val = b0 - 139; vs = 1; } if (247 <= b0 && b0 <= 250) { val = (b0 - 247) * 256 + b1 + 108; vs = 2; } if (251 <= b0 && b0 <= 254) { val = -(b0 - 251) * 256 - b1 - 108; vs = 2; } if (b0 == 255) { val = bin.readInt(data, offset + 1) / 65535; vs = 5; } arr.push(val != null ? val : "o" + op); offset += vs; } return arr; }; Typr.CFF.readDict = function(data, offset, end) { var bin = Typr._bin; var dict = {}; var carr = []; while (offset < end) { var b0 = data[offset], b1 = data[offset + 1]; data[offset + 2]; data[offset + 3]; data[offset + 4]; var vs = 1; var key = null, val = null; if (b0 == 28) { val = bin.readShort(data, offset + 1); vs = 3; } if (b0 == 29) { val = bin.readInt(data, offset + 1); vs = 5; } if (32 <= b0 && b0 <= 246) { val = b0 - 139; vs = 1; } if (247 <= b0 && b0 <= 250) { val = (b0 - 247) * 256 + b1 + 108; vs = 2; } if (251 <= b0 && b0 <= 254) { val = -(b0 - 251) * 256 - b1 - 108; vs = 2; } if (b0 == 255) { val = bin.readInt(data, offset + 1) / 65535; vs = 5; throw "unknown number"; } if (b0 == 30) { var nibs = []; vs = 1; while (true) { var b = data[offset + vs]; vs++; var nib0 = b >> 4, nib1 = b & 15; if (nib0 != 15) nibs.push(nib0); if (nib1 != 15) nibs.push(nib1); if (nib1 == 15) break; } var s = ""; var chars = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ".", "e", "e-", "reserved", "-", "endOfNumber"]; for (var i = 0; i < nibs.length; i++) s += chars[nibs[i]]; val = parseFloat(s); } if (b0 <= 21) { var keys = [ "version", "Notice", "FullName", "FamilyName", "Weight", "FontBBox", "BlueValues", "OtherBlues", "FamilyBlues", "FamilyOtherBlues", "StdHW", "StdVW", "escape", "UniqueID", "XUID", "charset", "Encoding", "CharStrings", "Private", "Subrs", "defaultWidthX", "nominalWidthX" ]; key = keys[b0]; vs = 1; if (b0 == 12) { var keys = [ "Copyright", "isFixedPitch", "ItalicAngle", "UnderlinePosition", "UnderlineThickness", "PaintType", "CharstringType", "FontMatrix", "StrokeWidth", "BlueScale", "BlueShift", "BlueFuzz", "StemSnapH", "StemSnapV", "ForceBold", 0, 0, "LanguageGroup", "ExpansionFactor", "initialRandomSeed", "SyntheticBase", "PostScript", "BaseFontName", "BaseFontBlend", 0, 0, 0, 0, 0, 0, "ROS", "CIDFontVersion", "CIDFontRevision", "CIDFontType", "CIDCount", "UIDBase", "FDArray", "FDSelect", "FontName" ]; key = keys[b1]; vs = 2; } } if (key != null) { dict[key] = carr.length == 1 ? carr[0] : carr; carr = []; } else carr.push(val); offset += vs; } return dict; }; Typr.cmap = {}; Typr.cmap.parse = function(data, offset, length) { data = new Uint8Array(data.buffer, offset, length); offset = 0; var bin = Typr._bin; var obj = {}; bin.readUshort(data, offset); offset += 2; var numTables = bin.readUshort(data, offset); offset += 2; var offs = []; obj.tables = []; for (var i = 0; i < numTables; i++) { var platformID = bin.readUshort(data, offset); offset += 2; var encodingID = bin.readUshort(data, offset); offset += 2; var noffset = bin.readUint(data, offset); offset += 4; var id = "p" + platformID + "e" + encodingID; var tind = offs.indexOf(noffset); if (tind == -1) { tind = obj.tables.length; var subt; offs.push(noffset); var format = bin.readUshort(data, noffset); if (format == 0) subt = Typr.cmap.parse0(data, noffset); else if (format == 4) subt = Typr.cmap.parse4(data, noffset); else if (format == 6) subt = Typr.cmap.parse6(data, noffset); else if (format == 12) subt = Typr.cmap.parse12(data, noffset); else console.warn("unknown format: " + format, platformID, encodingID, noffset); obj.tables.push(subt); } if (obj[id] != null) throw "multiple tables for one platform+encoding"; obj[id] = tind; } return obj; }; Typr.cmap.parse0 = function(data, offset) { var bin = Typr._bin; var obj = {}; obj.format = bin.readUshort(data, offset); offset += 2; var len = bin.readUshort(data, offset); offset += 2; bin.readUshort(data, offset); offset += 2; obj.map = []; for (var i = 0; i < len - 6; i++) obj.map.push(data[offset + i]); return obj; }; Typr.cmap.parse4 = function(data, offset) { var bin = Typr._bin; var offset0 = offset; var obj = {}; obj.format = bin.readUshort(data, offset); offset += 2; var length = bin.readUshort(data, offset); offset += 2; bin.readUshort(data, offset); offset += 2; var segCountX2 = bin.readUshort(data, offset); offset += 2; var segCount = segCountX2 / 2; obj.searchRange = bin.readUshort(data, offset); offset += 2; obj.entrySelector = bin.readUshort(data, offset); offset += 2; obj.rangeShift = bin.readUshort(data, offset); offset += 2; obj.endCount = bin.readUshorts(data, offset, segCount); offset += segCount * 2; offset += 2; obj.startCount = bin.readUshorts(data, offset, segCount); offset += segCount * 2; obj.idDelta = []; for (var i = 0; i < segCount; i++) { obj.idDelta.push(bin.readShort(data, offset)); offset += 2; } obj.idRangeOffset = bin.readUshorts(data, offset, segCount); offset += segCount * 2; obj.glyphIdArray = []; while (offset < offset0 + length) { obj.glyphIdArray.push(bin.readUshort(data, offset)); offset += 2; } return obj; }; Typr.cmap.parse6 = function(data, offset) { var bin = Typr._bin; var obj = {}; obj.format = bin.readUshort(data, offset); offset += 2; bin.readUshort(data, offset); offset += 2; bin.readUshort(data, offset); offset += 2; obj.firstCode = bin.readUshort(data, offset); offset += 2; var entryCount = bin.readUshort(data, offset); offset += 2; obj.glyphIdArray = []; for (var i = 0; i < entryCount; i++) { obj.glyphIdArray.push(bin.readUshort(data, offset)); offset += 2; } return obj; }; Typr.cmap.parse12 = function(data, offset) { var bin = Typr._bin; var obj = {}; obj.format = bin.readUshort(data, offset); offset += 2; offset += 2; bin.readUint(data, offset); offset += 4; bin.readUint(data, offset); offset += 4; var nGroups = bin.readUint(data, offset); offset += 4; obj.groups = []; for (var i = 0; i < nGroups; i++) { var off = offset + i * 12; var startCharCode = bin.readUint(data, off + 0); var endCharCode = bin.readUint(data, off + 4); var startGlyphID = bin.readUint(data, off + 8); obj.groups.push([startCharCode, endCharCode, startGlyphID]); } return obj; }; Typr.glyf = {}; Typr.glyf.parse = function(data, offset, length, font) { var obj = []; for (var g = 0; g < font.maxp.numGlyphs; g++) obj.push(null); return obj; }; Typr.glyf._parseGlyf = function(font, g) { var bin = Typr._bin; var data = font._data; var offset = Typr._tabOffset(data, "glyf", font._offset) + font.loca[g]; if (font.loca[g] == font.loca[g + 1]) return null; var gl = {}; gl.noc = bin.readShort(data, offset); offset += 2; gl.xMin = bin.readShort(data, offset); offset += 2; gl.yMin = bin.readShort(data, offset); offset += 2; gl.xMax = bin.readShort(data, offset); offset += 2; gl.yMax = bin.readShort(data, offset); offset += 2; if (gl.xMin >= gl.xMax || gl.yMin >= gl.yMax) return null; if (gl.noc > 0) { gl.endPts = []; for (var i = 0; i < gl.noc; i++) { gl.endPts.push(bin.readUshort(data, offset)); offset += 2; } var instructionLength = bin.readUshort(data, offset); offset += 2; if (data.length - offset < instructionLength) return null; gl.instructions = bin.readBytes(data, offset, instructionLength); offset += instructionLength; var crdnum = gl.endPts[gl.noc - 1] + 1; gl.flags = []; for (var i = 0; i < crdnum; i++) { var flag = data[offset]; offset++; gl.flags.push(flag); if ((flag & 8) != 0) { var rep = data[offset]; offset++; for (var j = 0; j < rep; j++) { gl.flags.push(flag); i++; } } } gl.xs = []; for (var i = 0; i < crdnum; i++) { var i8 = (gl.flags[i] & 2) != 0, same = (gl.flags[i] & 16) != 0; if (i8) { gl.xs.push(same ? data[offset] : -data[offset]); offset++; } else { if (same) gl.xs.push(0); else { gl.xs.push(bin.readShort(data, offset)); offset += 2; } } } gl.ys = []; for (var i = 0; i < crdnum; i++) { var i8 = (gl.flags[i] & 4) != 0, same = (gl.flags[i] & 32) != 0; if (i8) { gl.ys.push(same ? data[offset] : -data[offset]); offset++; } else { if (same) gl.ys.push(0); else { gl.ys.push(bin.readShort(data, offset)); offset += 2; } } } var x = 0, y = 0; for (var i = 0; i < crdnum; i++) { x += gl.xs[i]; y += gl.ys[i]; gl.xs[i] = x; gl.ys[i] = y; } } else { var ARG_1_AND_2_ARE_WORDS = 1 << 0; var ARGS_ARE_XY_VALUES = 1 << 1; var WE_HAVE_A_SCALE = 1 << 3; var MORE_COMPONENTS = 1 << 5; var WE_HAVE_AN_X_AND_Y_SCALE = 1 << 6; var WE_HAVE_A_TWO_BY_TWO = 1 << 7; var WE_HAVE_INSTRUCTIONS = 1 << 8; gl.parts = []; var flags; do { flags = bin.readUshort(data, offset); offset += 2; var part = { m: { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 }, p1: -1, p2: -1 }; gl.parts.push(part); part.glyphIndex = bin.readUshort(data, offset); offset += 2; if (flags & ARG_1_AND_2_ARE_WORDS) { var arg1 = bin.readShort(data, offset); offset += 2; var arg2 = bin.readShort(data, offset); offset += 2; } else { var arg1 = bin.readInt8(data, offset); offset++; var arg2 = bin.readInt8(data, offset); offset++; } if (flags & ARGS_ARE_XY_VALUES) { part.m.tx = arg1; part.m.ty = arg2; } else { part.p1 = arg1; part.p2 = arg2; } if (flags & WE_HAVE_A_SCALE) { part.m.a = part.m.d = bin.readF2dot14(data, offset); offset += 2; } else if (flags & WE_HAVE_AN_X_AND_Y_SCALE) { part.m.a = bin.readF2dot14(data, offset); offset += 2; part.m.d = bin.readF2dot14(data, offset); offset += 2; } else if (flags & WE_HAVE_A_TWO_BY_TWO) { part.m.a = bin.readF2dot14(data, offset); offset += 2; part.m.b = bin.readF2dot14(data, offset); offset += 2; part.m.c = bin.readF2dot14(data, offset); offset += 2; part.m.d = bin.readF2dot14(data, offset); offset += 2; } } while (flags & MORE_COMPONENTS); if (flags & WE_HAVE_INSTRUCTIONS) { var numInstr = bin.readUshort(data, offset); offset += 2; gl.instr = []; for (var i = 0; i < numInstr; i++) { gl.instr.push(data[offset]); offset++; } } } return gl; }; Typr.GPOS = {}; Typr.GPOS.parse = function(data, offset, length, font) { return Typr._lctf.parse(data, offset, length, font, Typr.GPOS.subt); }; Typr.GPOS.subt = function(data, ltype, offset, ltable) { var bin = Typr._bin, offset0 = offset, tab = {}; tab.fmt = bin.readUshort(data, offset); offset += 2; if (ltype == 1 || ltype == 2 || ltype == 3 || ltype == 7 || ltype == 8 && tab.fmt <= 2) { var covOff = bin.readUshort(data, offset); offset += 2; tab.coverage = Typr._lctf.readCoverage(data, covOff + offset0); } if (ltype == 1 && tab.fmt == 1) { var valFmt1 = bin.readUshort(data, offset); offset += 2; var ones1 = Typr._lctf.numOfOnes(valFmt1); if (valFmt1 != 0) tab.pos = Typr.GPOS.readValueRecord(data, offset, valFmt1); } else if (ltype == 2 && tab.fmt >= 1 && tab.fmt <= 2) { var valFmt1 = bin.readUshort(data, offset); offset += 2; var valFmt2 = bin.readUshort(data, offset); offset += 2; var ones1 = Typr._lctf.numOfOnes(valFmt1); var ones2 = Typr._lctf.numOfOnes(valFmt2); if (tab.fmt == 1) { tab.pairsets = []; var psc = bin.readUshort(data, offset); offset += 2; for (var i = 0; i < psc; i++) { var psoff = offset0 + bin.readUshort(data, offset); offset += 2; var pvc = bin.readUshort(data, psoff); psoff += 2; var arr = []; for (var j = 0; j < pvc; j++) { var gid2 = bin.readUshort(data, psoff); psoff += 2; var value1, value2; if (valFmt1 != 0) { value1 = Typr.GPOS.readValueRecord(data, psoff, valFmt1); psoff += ones1 * 2; } if (valFmt2 != 0) { value2 = Typr.GPOS.readValueRecord(data, psoff, valFmt2); psoff += ones2 * 2; } arr.push({ gid2, val1: value1, val2: value2 }); } tab.pairsets.push(arr); } } if (tab.fmt == 2) { var classDef1 = bin.readUshort(data, offset); offset += 2; var classDef2 = bin.readUshort(data, offset); offset += 2; var class1Count = bin.readUshort(data, offset); offset += 2; var class2Count = bin.readUshort(data, offset); offset += 2; tab.classDef1 = Typr._lctf.readClassDef(data, offset0 + classDef1); tab.classDef2 = Typr._lctf.readClassDef(data, offset0 + classDef2); tab.matrix = []; for (var i = 0; i < class1Count; i++) { var row = []; for (var j = 0; j < class2Count; j++) { var value1 = null, value2 = null; if (valFmt1 != 0) { value1 = Typr.GPOS.readValueRecord(data, offset, valFmt1); offset += ones1 * 2; } if (valFmt2 != 0) { value2 = Typr.GPOS.readValueRecord(data, offset, valFmt2); offset += ones2 * 2; } row.push({ val1: value1, val2: value2 }); } tab.matrix.push(row); } } } else if (ltype == 9 && tab.fmt == 1) { var extType = bin.readUshort(data, offset); offset += 2; var extOffset = bin.readUint(data, offset); offset += 4; if (ltable.ltype == 9) { ltable.ltype = extType; } else if (ltable.ltype != extType) { throw "invalid extension substitution"; } return Typr.GPOS.subt(data, ltable.ltype, offset0 + extOffset); } else console.warn("unsupported GPOS table LookupType", ltype, "format", tab.fmt); return tab; }; Typr.GPOS.readValueRecord = function(data, offset, valFmt) { var bin = Typr._bin; var arr = []; arr.push(valFmt & 1 ? bin.readShort(data, offset) : 0); offset += valFmt & 1 ? 2 : 0; arr.push(valFmt & 2 ? bin.readShort(data, offset) : 0); offset += valFmt & 2 ? 2 : 0; arr.push(valFmt & 4 ? bin.readShort(data, offset) : 0); offset += valFmt & 4 ? 2 : 0; arr.push(valFmt & 8 ? bin.readShort(data, offset) : 0); offset += valFmt & 8 ? 2 : 0; return arr; }; Typr.GSUB = {}; Typr.GSUB.parse = function(data, offset, length, font) { return Typr._lctf.parse(data, offset, length, font, Typr.GSUB.subt); }; Typr.GSUB.subt = function(data, ltype, offset, ltable) { var bin = Typr._bin, offset0 = offset, tab = {}; tab.fmt = bin.readUshort(data, offset); offset += 2; if (ltype != 1 && ltype != 4 && ltype != 5 && ltype != 6) return null; if (ltype == 1 || ltype == 4 || ltype == 5 && tab.fmt <= 2 || ltype == 6 && tab.fmt <= 2) { var covOff = bin.readUshort(data, offset); offset += 2; tab.coverage = Typr._lctf.readCoverage(data, offset0 + covOff); } if (ltype == 1 && tab.fmt >= 1 && tab.fmt <= 2) { if (tab.fmt == 1) { tab.delta = bin.readShort(data, offset); offset += 2; } else if (tab.fmt == 2) { var cnt = bin.readUshort(data, offset); offset += 2; tab.newg = bin.readUshorts(data, offset, cnt); offset += tab.newg.length * 2; } } else if (ltype == 4) { tab.vals = []; var cnt = bin.readUshort(data, offset); offset += 2; for (var i = 0; i < cnt; i++) { var loff = bin.readUshort(data, offset); offset += 2; tab.vals.push(Typr.GSUB.readLigatureSet(data, offset0 + loff)); } } else if (ltype == 5 && tab.fmt == 2) { if (tab.fmt == 2) { var cDefOffset = bin.readUshort(data, offset); offset += 2; tab.cDef = Typr._lctf.readClassDef(data, offset0 + cDefOffset); tab.scset = []; var subClassSetCount = bin.readUshort(data, offset); offset += 2; for (var i = 0; i < subClassSetCount; i++) { var scsOff = bin.readUshort(data, offset); offset += 2; tab.scset.push(scsOff == 0 ? null : Typr.GSUB.readSubClassSet(data, offset0 + scsOff)); } } } else if (ltype == 6 && tab.fmt == 3) { if (tab.fmt == 3) { for (var i = 0; i < 3; i++) { var cnt = bin.readUshort(data, offset); offset += 2; var cvgs = []; for (var j = 0; j < cnt; j++) cvgs.push(Typr._lctf.readCoverage(data, offset0 + bin.readUshort(data, offset + j * 2))); offset += cnt * 2; if (i == 0) tab.backCvg = cvgs; if (i == 1) tab.inptCvg = cvgs; if (i == 2) tab.ahedCvg = cvgs; } var cnt = bin.readUshort(data, offset); offset += 2; tab.lookupRec = Typr.GSUB.readSubstLookupRecords(data, offset, cnt); } } else if (ltype == 7 && tab.fmt == 1) { var extType = bin.readUshort(data, offset); offset += 2; var extOffset = bin.readUint(data, offset); offset += 4; if (ltable.ltype == 9) { ltable.ltype = extType; } else if (ltable.ltype != extType) { throw "invalid extension substitution"; } return Typr.GSUB.subt(data, ltable.ltype, offset0 + extOffset); } else console.warn("unsupported GSUB table LookupType", ltype, "format", tab.fmt); return tab; }; Typr.GSUB.readSubClassSet = function(data, offset) { var rUs = Typr._bin.readUshort, offset0 = offset, lset = []; var cnt = rUs(data, offset); offset += 2; for (var i = 0; i < cnt; i++) { var loff = rUs(data, offset); offset += 2; lset.push(Typr.GSUB.readSubClassRule(data, offset0 + loff)); } return lset; }; Typr.GSUB.readSubClassRule = function(data, offset) { var rUs = Typr._bin.readUshort, rule = {}; var gcount = rUs(data, offset); offset += 2; var scount = rUs(data, offset); offset += 2; rule.input = []; for (var i = 0; i < gcount - 1; i++) { rule.input.push(rUs(data, offset)); offset += 2; } rule.substLookupRecords = Typr.GSUB.readSubstLookupRecords(data, offset, scount); return rule; }; Typr.GSUB.readSubstLookupRecords = function(data, offset, cnt) { var rUs = Typr._bin.readUshort; var out = []; for (var i = 0; i < cnt; i++) { out.push(rUs(data, offset), rUs(data, offset + 2)); offset += 4; } return out; }; Typr.GSUB.readChainSubClassSet = function(data, offset) { var bin = Typr._bin, offset0 = offset, lset = []; var cnt = bin.readUshort(data, offset); offset += 2; for (var i = 0; i < cnt; i++) { var loff = bin.readUshort(data, offset); offset += 2; lset.push(Typr.GSUB.readChainSubClassRule(data, offset0 + loff)); } return lset; }; Typr.GSUB.readChainSubClassRule = function(data, offset) { var bin = Typr._bin, rule = {}; var pps = ["backtrack", "input", "lookahead"]; for (var pi = 0; pi < pps.length; pi++) { var cnt = bin.readUshort(data, offset); offset += 2; if (pi == 1) cnt--; rule[pps[pi]] = bin.readUshorts(data, offset, cnt); offset += rule[pps[pi]].length * 2; } var cnt = bin.readUshort(data, offset); offset += 2; rule.subst = bin.readUshorts(data, offset, cnt * 2); offset += rule.subst.length * 2; return rule; }; Typr.GSUB.readLigatureSet = function(data, offset) { var bin = Typr._bin, offset0 = offset, lset = []; var lcnt = bin.readUshort(data, offset); offset += 2; for (var j = 0; j < lcnt; j++) { var loff = bin.readUshort(data, offset); offset += 2; lset.push(Typr.GSUB.readLigature(data, offset0 + loff)); } return lset; }; Typr.GSUB.readLigature = function(data, offset) { var bin = Typr._bin, lig = { chain: [] }; lig.nglyph = bin.readUshort(data, offset); offset += 2; var ccnt = bin.readUshort(data, offset); offset += 2; for (var k = 0; k < ccnt - 1; k++) { lig.chain.push(bin.readUshort(data, offset)); offset += 2; } return lig; }; Typr.head = {}; Typr.head.parse = function(data, offset, length) { var bin = Typr._bin; var obj = {}; bin.readFixed(data, offset); offset += 4; obj.fontRevision = bin.readFixed(data, offset); offset += 4; bin.readUint(data, offset); offset += 4; bin.readUint(data, offset); offset += 4; obj.flags = bin.readUshort(data, offset); offset += 2; obj.unitsPerEm = bin.readUshort(data, offset); offset += 2; obj.created = bin.readUint64(data, offset); offset += 8; obj.modified = bin.readUint64(data, offset); offset += 8; obj.xMin = bin.readShort(data, offset); offset += 2; obj.yMin = bin.readShort(data, offset); offset += 2; obj.xMax = bin.readShort(data, offset); offset += 2; obj.yMax = bin.readShort(data, offset); offset += 2; obj.macStyle = bin.readUshort(data, offset); offset += 2; obj.lowestRecPPEM = bin.readUshort(data, offset); offset += 2; obj.fontDirectionHint = bin.readShort(data, offset); offset += 2; obj.indexToLocFormat = bin.readShort(data, offset); offset += 2; obj.glyphDataFormat = bin.readShort(data, offset); offset += 2; return obj; }; Typr.hhea = {}; Typr.hhea.parse = function(data, offset, length) { var bin = Typr._bin; var obj = {}; bin.readFixed(data, offset); offset += 4; obj.ascender = bin.readShort(data, offset); offset += 2; obj.descender = bin.readShort(data, offset); offset += 2; obj.lineGap = bin.readShort(data, offset); offset += 2; obj.advanceWidthMax = bin.readUshort(data, offset); offset += 2; obj.minLeftSideBearing = bin.readShort(data, offset); offset += 2; obj.minRightSideBearing = bin.readShort(data, offset); offset += 2; obj.xMaxExtent = bin.readShort(data, offset); offset += 2; obj.caretSlopeRise = bin.readShort(data, offset); offset += 2; obj.caretSlopeRun = bin.readShort(data, offset); offset += 2; obj.caretOffset = bin.readShort(data, offset); offset += 2; offset += 4 * 2; obj.metricDataFormat = bin.readShort(data, offset); offset += 2; obj.numberOfHMetrics = bin.readUshort(data, offset); offset += 2; return obj; }; Typr.hmtx = {}; Typr.hmtx.parse = function(data, offset, length, font) { var bin = Typr._bin; var obj = {}; obj.aWidth = []; obj.lsBearing = []; var aw = 0, lsb = 0; for (var i = 0; i < font.maxp.numGlyphs; i++) { if (i < font.hhea.numberOfHMetrics) { aw = bin.readUshort(data, offset); offset += 2; lsb = bin.readShort(data, offset); offset += 2; } obj.aWidth.push(aw); obj.lsBearing.push(lsb); } return obj; }; Typr.kern = {}; Typr.kern.parse = function(data, offset, length, font) { var bin = Typr._bin; var version = bin.readUshort(data, offset); offset += 2; if (version == 1) return Typr.kern.parseV1(data, offset - 2, length, font); var nTables = bin.readUshort(data, offset); offset += 2; var map2 = { glyph1: [], rval: [] }; for (var i = 0; i < nTables; i++) { offset += 2; var length = bin.readUshort(data, offset); offset += 2; var coverage = bin.readUshort(data, offset); offset += 2; var format = coverage >>> 8; format &= 15; if (format == 0) offset = Typr.kern.readFormat0(data, offset, map2); else throw "unknown kern table format: " + format; } return map2; }; Typr.kern.parseV1 = function(data, offset, length, font) { var bin = Typr._bin; bin.readFixed(data, offset); offset += 4; var nTables = bin.readUint(data, offset); offset += 4; var map2 = { glyph1: [], rval: [] }; for (var i = 0; i < nTables; i++) { bin.readUint(data, offset); offset += 4; var coverage = bin.readUshort(data, offset); offset += 2; bin.readUshort(data, offset); offset += 2; var format = coverage >>> 8; format &= 15; if (format == 0) offset = Typr.kern.readFormat0(data, offset, map2); else throw "unknown kern table format: " + format; } return map2; }; Typr.kern.readFormat0 = function(data, offset, map2) { var bin = Typr._bin; var pleft = -1; var nPairs = bin.readUshort(data, offset); offset += 2; bin.readUshort(data, offset); offset += 2; bin.readUshort(data, offset); offset += 2; bin.readUshort(data, offset); offset += 2; for (var j = 0; j < nPairs; j++) { var left = bin.readUshort(data, offset); offset += 2; var right = bin.readUshort(data, offset); offset += 2; var value = bin.readShort(data, offset); offset += 2; if (left != pleft) { map2.glyph1.push(left); map2.rval.push({ glyph2: [], vals: [] }); } var rval = map2.rval[map2.rval.length - 1]; rval.glyph2.push(right); rval.vals.push(value); pleft = left; } return offset; }; Typr.loca = {}; Typr.loca.parse = function(data, offset, length, font) { var bin = Typr._bin; var obj = []; var ver = font.head.indexToLocFormat; var len = font.maxp.numGlyphs + 1; if (ver == 0) for (var i = 0; i < len; i++) obj.push(bin.readUshort(data, offset + (i << 1)) << 1); if (ver == 1) for (var i = 0; i < len; i++) obj.push(bin.readUint(data, offset + (i << 2))); return obj; }; Typr.maxp = {}; Typr.maxp.parse = function(data, offset, length) { var bin = Typr._bin; var obj = {}; var ver = bin.readUint(data, offset); offset += 4; obj.numGlyphs = bin.readUshort(data, offset); offset += 2; if (ver == 65536) { obj.maxPoints = bin.readUshort(data, offset); offset += 2; obj.maxContours = bin.readUshort(data, offset); offset += 2; obj.maxCompositePoints = bin.readUshort(data, offset); offset += 2; obj.maxCompositeContours = bin.readUshort(data, offset); offset += 2; obj.maxZones = bin.readUshort(data, offset); offset += 2; obj.maxTwilightPoints = bin.readUshort(data, offset); offset += 2; obj.maxStorage = bin.readUshort(data, offset); offset += 2; obj.maxFunctionDefs = bin.readUshort(data, offset); offset += 2; obj.maxInstructionDefs = bin.readUshort(data, offset); offset += 2; obj.maxStackElements = bin.readUshort(data, offset); offset += 2; obj.maxSizeOfInstructions = bin.readUshort(data, offset); offset += 2; obj.maxComponentElements = bin.readUshort(data, offset); offset += 2; obj.maxComponentDepth = bin.readUshort(data, offset); offset += 2; } return obj; }; Typr.name = {}; Typr.name.parse = function(data, offset, length) { var bin = Typr._bin; var obj = {}; bin.readUshort(data, offset); offset += 2; var count = bin.readUshort(data, offset); offset += 2; bin.readUshort(data, offset); offset += 2; var names2 = [ "copyright", "fontFamily", "fontSubfamily", "ID", "fullName", "version", "postScriptName", "trademark", "manufacturer", "designer", "description", "urlVendor", "urlDesigner", "licence", "licenceURL", "---", "typoFamilyName", "typoSubfamilyName", "compatibleFull", "sampleText", "postScriptCID", "wwsFamilyName", "wwsSubfamilyName", "lightPalette", "darkPalette" ]; var offset0 = offset; for (var i = 0; i < count; i++) { var platformID = bin.readUshort(data, offset); offset += 2; var encodingID = bin.readUshort(data, offset); offset += 2; var languageID = bin.readUshort(data, offset); offset += 2; var nameID = bin.readUshort(data, offset); offset += 2; var slen = bin.readUshort(data, offset); offset += 2; var noffset = bin.readUshort(data, offset); offset += 2; var cname = names2[nameID]; var soff = offset0 + count * 12 + noffset; var str; if (platformID == 0) str = bin.readUnicode(data, soff, slen / 2); else if (platformID == 3 && encodingID == 0) str = bin.readUnicode(data, soff, slen / 2); else if (encodingID == 0) str = bin.readASCII(data, soff, slen); else if (encodingID == 1) str = bin.readUnicode(data, soff, slen / 2); else if (encodingID == 3) str = bin.readUnicode(data, soff, slen / 2); else if (platformID == 1) { str = bin.readASCII(data, soff, slen); console.warn("reading unknown MAC encoding " + encodingID + " as ASCII"); } else throw "unknown encoding " + encodingID + ", platformID: " + platformID; var tid = "p" + platformID + "," + languageID.toString(16); if (obj[tid] == null) obj[tid] = {}; obj[tid][cname !== void 0 ? cname : nameID] = str; obj[tid]._lang = languageID; } for (var p in obj) if (obj[p].postScriptName != null && obj[p]._lang == 1033) return obj[p]; for (var p in obj) if (obj[p].postScriptName != null && obj[p]._lang == 0) return obj[p]; for (var p in obj) if (obj[p].postScriptName != null && obj[p]._lang == 3084) return obj[p]; for (var p in obj) if (obj[p].postScriptName != null) return obj[p]; var tname; for (var p in obj) { tname = p; break; } console.warn("returning name table with languageID " + obj[tname]._lang); return obj[tname]; }; Typr["OS/2"] = {}; Typr["OS/2"].parse = function(data, offset, length) { var bin = Typr._bin; var ver = bin.readUshort(data, offset); offset += 2; var obj = {}; if (ver == 0) Typr["OS/2"].version0(data, offset, obj); else if (ver == 1) Typr["OS/2"].version1(data, offset, obj); else if (ver == 2 || ver == 3 || ver == 4) Typr["OS/2"].version2(data, offset, obj); else if (ver == 5) Typr["OS/2"].version5(data, offset, obj); else throw "unknown OS/2 table version: " + ver; return obj; }; Typr["OS/2"].version0 = function(data, offset, obj) { var bin = Typr._bin; obj.xAvgCharWidth = bin.readShort(data, offset); offset += 2; obj.usWeightClass = bin.readUshort(data, offset); offset += 2; obj.usWidthClass = bin.readUshort(data, offset); offset += 2; obj.fsType = bin.readUshort(data, offset); offset += 2; obj.ySubscriptXSize = bin.readShort(data, offset); offset += 2; obj.ySubscriptYSize = bin.readShort(data, offset); offset += 2; obj.ySubscriptXOffset = bin.readShort(data, offset); offset += 2; obj.ySubscriptYOffset = bin.readShort(data, offset); offset += 2; obj.ySuperscriptXSize = bin.readShort(data, offset); offset += 2; obj.ySuperscriptYSize = bin.readShort(data, offset); offset += 2; obj.ySuperscriptXOffset = bin.readShort(data, offset); offset += 2; obj.ySuperscriptYOffset = bin.readShort(data, offset); offset += 2; obj.yStrikeoutSize = bin.readShort(data, offset); offset += 2; obj.yStrikeoutPosition = bin.readShort(data, offset); offset += 2; obj.sFamilyClass = bin.readShort(data, offset); offset += 2; obj.panose = bin.readBytes(data, offset, 10); offset += 10; obj.ulUnicodeRange1 = bin.readUint(data, offset); offset += 4; obj.ulUnicodeRange2 = bin.readUint(data, offset); offset += 4; obj.ulUnicodeRange3 = bin.readUint(data, offset); offset += 4; obj.ulUnicodeRange4 = bin.readUint(data, offset); offset += 4; obj.achVendID = [bin.readInt8(data, offset), bin.readInt8(data, offset + 1), bin.readInt8(data, offset + 2), bin.readInt8(data, offset + 3)]; offset += 4; obj.fsSelection = bin.readUshort(data, offset); offset += 2; obj.usFirstCharIndex = bin.readUshort(data, offset); offset += 2; obj.usLastCharIndex = bin.readUshort(data, offset); offset += 2; obj.sTypoAscender = bin.readShort(data, offset); offset += 2; obj.sTypoDescender = bin.readShort(data, offset); offset += 2; obj.sTypoLineGap = bin.readShort(data, offset); offset += 2; obj.usWinAscent = bin.readUshort(data, offset); offset += 2; obj.usWinDescent = bin.readUshort(data, offset); offset += 2; return offset; }; Typr["OS/2"].version1 = function(data, offset, obj) { var bin = Typr._bin; offset = Typr["OS/2"].version0(data, offset, obj); obj.ulCodePageRange1 = bin.readUint(data, offset); offset += 4; obj.ulCodePageRange2 = bin.readUint(data, offset); offset += 4; return offset; }; Typr["OS/2"].version2 = function(data, offset, obj) { var bin = Typr._bin; offset = Typr["OS/2"].version1(data, offset, obj); obj.sxHeight = bin.readShort(data, offset); offset += 2; obj.sCapHeight = bin.readShort(data, offset); offset += 2; obj.usDefault = bin.readUshort(data, offset); offset += 2; obj.usBreak = bin.readUshort(data, offset); offset += 2; obj.usMaxContext = bin.readUshort(data, offset); offset += 2; return offset; }; Typr["OS/2"].version5 = function(data, offset, obj) { var bin = Typr._bin; offset = Typr["OS/2"].version2(data, offset, obj); obj.usLowerOpticalPointSize = bin.readUshort(data, offset); offset += 2; obj.usUpperOpticalPointSize = bin.readUshort(data, offset); offset += 2; return offset; }; Typr.post = {}; Typr.post.parse = function(data, offset, length) { var bin = Typr._bin; var obj = {}; obj.version = bin.readFixed(data, offset); offset += 4; obj.italicAngle = bin.readFixed(data, offset); offset += 4; obj.underlinePosition = bin.readShort(data, offset); offset += 2; obj.underlineThickness = bin.readShort(data, offset); offset += 2; return obj; }; Typr.SVG = {}; Typr.SVG.parse = function(data, offset, length) { var bin = Typr._bin; var obj = { entries: [] }; var offset0 = offset; bin.readUshort(data, offset); offset += 2; var svgDocIndexOffset = bin.readUint(data, offset); offset += 4; bin.readUint(data, offset); offset += 4; offset = svgDocIndexOffset + offset0; var numEntries = bin.readUshort(data, offset); offset += 2; for (var i = 0; i < numEntries; i++) { var startGlyphID = bin.readUshort(data, offset); offset += 2; var endGlyphID = bin.readUshort(data, offset); offset += 2; var svgDocOffset = bin.readUint(data, offset); offset += 4; var svgDocLength = bin.readUint(data, offset); offset += 4; var sbuf = new Uint8Array(data.buffer, offset0 + svgDocOffset + svgDocIndexOffset, svgDocLength); var svg = bin.readUTF8(sbuf, 0, sbuf.length); for (var f = startGlyphID; f <= endGlyphID; f++) { obj.entries[f] = svg; } } return obj; }; Typr.SVG.toPath = function(str) { var pth = { cmds: [], crds: [] }; if (str == null) return pth; var prsr = new DOMParser(); var doc = prsr["parseFromString"](str, "image/svg+xml"); var svg = doc.firstChild; while (svg.tagName != "svg") svg = svg.nextSibling; var vb = svg.getAttribute("viewBox"); if (vb) vb = vb.trim().split(" ").map(parseFloat); else vb = [0, 0, 1e3, 1e3]; Typr.SVG._toPath(svg.children, pth); for (var i = 0; i < pth.crds.length; i += 2) { var x = pth.crds[i], y = pth.crds[i + 1]; x -= vb[0]; y -= vb[1]; y = -y; pth.crds[i] = x; pth.crds[i + 1] = y; } return pth; }; Typr.SVG._toPath = function(nds, pth, fill) { for (var ni = 0; ni < nds.length; ni++) { var nd = nds[ni], tn = nd.tagName; var cfl = nd.getAttribute("fill"); if (cfl == null) cfl = fill; if (tn == "g") Typr.SVG._toPath(nd.children, pth, cfl); else if (tn == "path") { pth.cmds.push(cfl ? cfl : "#000000"); var d = nd.getAttribute("d"); var toks = Typr.SVG._tokens(d); Typr.SVG._toksToPath(toks, pth); pth.cmds.push("X"); } else if (tn == "defs") ; else console.warn(tn, nd); } }; Typr.SVG._tokens = function(d) { var ts = [], off = 0, rn = false, cn = ""; while (off < d.length) { var cc = d.charCodeAt(off), ch = d.charAt(off); off++; var isNum = 48 <= cc && cc <= 57 || ch == "." || ch == "-"; if (rn) { if (ch == "-") { ts.push(parseFloat(cn)); cn = ch; } else if (isNum) cn += ch; else { ts.push(parseFloat(cn)); if (ch != "," && ch != " ") ts.push(ch); rn = false; } } else { if (isNum) { cn = ch; rn = true; } else if (ch != "," && ch != " ") ts.push(ch); } } if (rn) ts.push(parseFloat(cn)); return ts; }; Typr.SVG._toksToPath = function(ts, pth) { var i = 0, x = 0, y = 0, ox = 0, oy = 0; var pc = { "M": 2, "L": 2, "H": 1, "V": 1, "S": 4, "C": 6 }; var cmds = pth.cmds, crds = pth.crds; while (i < ts.length) { var cmd = ts[i]; i++; if (cmd == "z") { cmds.push("Z"); x = ox; y = oy; } else { var cmu = cmd.toUpperCase(); var ps = pc[cmu], reps = Typr.SVG._reps(ts, i, ps); for (var j = 0; j < reps; j++) { var xi = 0, yi = 0; if (cmd != cmu) { xi = x; yi = y; } if (cmu == "M") { x = xi + ts[i++]; y = yi + ts[i++]; cmds.push("M"); crds.push(x, y); ox = x; oy = y; } else if (cmu == "L") { x = xi + ts[i++]; y = yi + ts[i++]; cmds.push("L"); crds.push(x, y); } else if (cmu == "H") { x = xi + ts[i++]; cmds.push("L"); crds.push(x, y); } else if (cmu == "V") { y = yi + ts[i++]; cmds.push("L"); crds.push(x, y); } else if (cmu == "C") { var x1 = xi + ts[i++], y1 = yi + ts[i++], x2 = xi + ts[i++], y2 = yi + ts[i++], x3 = xi + ts[i++], y3 = yi + ts[i++]; cmds.push("C"); crds.push(x1, y1, x2, y2, x3, y3); x = x3; y = y3; } else if (cmu == "S") { var co = Math.max(crds.length - 4, 0); var x1 = x + x - crds[co], y1 = y + y - crds[co + 1]; var x2 = xi + ts[i++], y2 = yi + ts[i++], x3 = xi + ts[i++], y3 = yi + ts[i++]; cmds.push("C"); crds.push(x1, y1, x2, y2, x3, y3); x = x3; y = y3; } else console.warn("Unknown SVG command " + cmd); } } } }; Typr.SVG._reps = function(ts, off, ps) { var i = off; while (i < ts.length) { if (typeof ts[i] == "string") break; i += ps; } return (i - off) / ps; }; if (Typr == null) Typr = {}; if (Typr.U == null) Typr.U = {}; Typr.U.codeToGlyph = function(font, code) { var cmap = font.cmap; for (var _i = 0, _a = [cmap.p0e4, cmap.p3e1, cmap.p3e10, cmap.p0e3, cmap.p1e0]; _i < _a.length; _i++) { var tind = _a[_i]; if (tind == null) continue; var tab = cmap.tables[tind]; if (tab.format == 0) { if (code >= tab.map.length) continue; return tab.map[code]; } else if (tab.format == 4) { var sind = -1; for (var i = 0; i < tab.endCount.length; i++) { if (code <= tab.endCount[i]) { sind = i; break; } } if (sind == -1) continue; if (tab.startCount[sind] > code) continue; var gli = 0; if (tab.idRangeOffset[sind] != 0) { gli = tab.glyphIdArray[code - tab.startCount[sind] + (tab.idRangeOffset[sind] >> 1) - (tab.idRangeOffset.length - sind)]; } else { gli = code + tab.idDelta[sind]; } return gli & 65535; } else if (tab.format == 12) { if (code > tab.groups[tab.groups.length - 1][1]) continue; for (var i = 0; i < tab.groups.length; i++) { var grp = tab.groups[i]; if (grp[0] <= code && code <= grp[1]) return grp[2] + (code - grp[0]); } continue; } else { throw "unknown cmap table format " + tab.format; } } return 0; }; Typr.U.glyphToPath = function(font, gid) { var path = { cmds: [], crds: [] }; if (font.SVG && font.SVG.entries[gid]) { var p = font.SVG.entries[gid]; if (p == null) return path; if (typeof p == "string") { p = Typr.SVG.toPath(p); font.SVG.entries[gid] = p; } return p; } else if (font.CFF) { var state = { x: 0, y: 0, stack: [], nStems: 0, haveWidth: false, width: font.CFF.Private ? font.CFF.Private.defaultWidthX : 0, open: false }; var cff = font.CFF, pdct = font.CFF.Private; if (cff.ROS) { var gi = 0; while (cff.FDSelect[gi + 2] <= gid) gi += 2; pdct = cff.FDArray[cff.FDSelect[gi + 1]].Private; } Typr.U._drawCFF(font.CFF.CharStrings[gid], state, cff, pdct, path); } else if (font.glyf) { Typr.U._drawGlyf(gid, font, path); } return path; }; Typr.U._drawGlyf = function(gid, font, path) { var gl = font.glyf[gid]; if (gl == null) gl = font.glyf[gid] = Typr.glyf._parseGlyf(font, gid); if (gl != null) { if (gl.noc > -1) { Typr.U._simpleGlyph(gl, path); } else { Typr.U._compoGlyph(gl, font, path); } } }; Typr.U._simpleGlyph = function(gl, p) { for (var c = 0; c < gl.noc; c++) { var i0 = c == 0 ? 0 : gl.endPts[c - 1] + 1; var il = gl.endPts[c]; for (var i = i0; i <= il; i++) { var pr = i == i0 ? il : i - 1; var nx = i == il ? i0 : i + 1; var onCurve = gl.flags[i] & 1; var prOnCurve = gl.flags[pr] & 1; var nxOnCurve = gl.flags[nx] & 1; var x = gl.xs[i], y = gl.ys[i]; if (i == i0) { if (onCurve) { if (prOnCurve) { Typr.U.P.moveTo(p, gl.xs[pr], gl.ys[pr]); } else { Typr.U.P.moveTo(p, x, y); continue; } } else { if (prOnCurve) { Typr.U.P.moveTo(p, gl.xs[pr], gl.ys[pr]); } else { Typr.U.P.moveTo(p, (gl.xs[pr] + x) / 2, (gl.ys[pr] + y) / 2); } } } if (onCurve) { if (prOnCurve) Typr.U.P.lineTo(p, x, y); } else { if (nxOnCurve) { Typr.U.P.qcurveTo(p, x, y, gl.xs[nx], gl.ys[nx]); } else { Typr.U.P.qcurveTo(p, x, y, (x + gl.xs[nx]) / 2, (y + gl.ys[nx]) / 2); } } } Typr.U.P.closePath(p); } }; Typr.U._compoGlyph = function(gl, font, p) { for (var j = 0; j < gl.parts.length; j++) { var path = { cmds: [], crds: [] }; var prt = gl.parts[j]; Typr.U._drawGlyf(prt.glyphIndex, font, path); var m = prt.m; for (var i = 0; i < path.crds.length; i += 2) { var x = path.crds[i], y = path.crds[i + 1]; p.crds.push(x * m.a + y * m.b + m.tx); p.crds.push(x * m.c + y * m.d + m.ty); } for (var i = 0; i < path.cmds.length; i++) { p.cmds.push(path.cmds[i]); } } }; Typr.U._getGlyphClass = function(g, cd) { var intr = Typr._lctf.getInterval(cd, g); return intr == -1 ? 0 : cd[intr + 2]; }; Typr.U.getPairAdjustment = function(font, g1, g2) { var hasGPOSkern = false; if (font.GPOS) { var gpos = font["GPOS"]; var llist = gpos.lookupList, flist = gpos.featureList; var tused = []; for (var i = 0; i < flist.length; i++) { var fl = flist[i]; if (fl.tag != "kern") continue; hasGPOSkern = true; for (var ti = 0; ti < fl.tab.length; ti++) { if (tused[fl.tab[ti]]) continue; tused[fl.tab[ti]] = true; var tab = llist[fl.tab[ti]]; for (var j = 0; j < tab.tabs.length; j++) { if (tab.tabs[j] == null) continue; var ltab = tab.tabs[j], ind; if (ltab.coverage) { ind = Typr._lctf.coverageIndex(ltab.coverage, g1); if (ind == -1) continue; } if (tab.ltype == 1) ; else if (tab.ltype == 2) { var adj = null; if (ltab.fmt == 1) { var right = ltab.pairsets[ind]; for (var i = 0; i < right.length; i++) { if (right[i].gid2 == g2) adj = right[i]; } } else if (ltab.fmt == 2) { var c1 = Typr.U._getGlyphClass(g1, ltab.classDef1); var c2 = Typr.U._getGlyphClass(g2, ltab.classDef2); adj = ltab.matrix[c1][c2]; } if (adj) { var offset = 0; if (adj.val1 && adj.val1[2]) offset += adj.val1[2]; if (adj.val2 && adj.val2[0]) offset += adj.val2[0]; return offset; } } } } } } if (font.kern && !hasGPOSkern) { var ind1 = font.kern.glyph1.indexOf(g1); if (ind1 != -1) { var ind2 = font.kern.rval[ind1].glyph2.indexOf(g2); if (ind2 != -1) return font.kern.rval[ind1].vals[ind2]; } } return 0; }; Typr.U.stringToGlyphs = function(font, str) { var gls = []; for (var i = 0; i < str.length; i++) { var cc = str.codePointAt(i); if (cc > 65535) i++; gls.push(Typr.U.codeToGlyph(font, cc)); } for (var i = 0; i < str.length; i++) { var cc = str.codePointAt(i); if (cc == 2367) { var t = gls[i - 1]; gls[i - 1] = gls[i]; gls[i] = t; } if (cc > 65535) i++; } var gsub = font["GSUB"]; if (gsub == null) return gls; var llist = gsub.lookupList, flist = gsub.featureList; var cligs = [ "rlig", "liga", "mset", "isol", "init", "fina", "medi", "half", "pres", "blws" /* Tibetan fonts like Himalaya.ttf */ ]; var tused = []; for (var fi = 0; fi < flist.length; fi++) { var fl = flist[fi]; if (cligs.indexOf(fl.tag) == -1) continue; for (var ti = 0; ti < fl.tab.length; ti++) { if (tused[fl.tab[ti]]) continue; tused[fl.tab[ti]] = true; var tab = llist[fl.tab[ti]]; for (var ci = 0; ci < gls.length; ci++) { var feat = Typr.U._getWPfeature(str, ci); if ("isol,init,fina,medi".indexOf(fl.tag) != -1 && fl.tag != feat) continue; Typr.U._applySubs(gls, ci, tab, llist); } } } return gls; }; Typr.U._getWPfeature = function(str, ci) { var wsep = '\n " ,.:;!?() ،'; var R = "آأؤإاةدذرزوٱٲٳٵٶٷڈډڊڋڌڍڎڏڐڑڒړڔڕږڗژڙۀۃۄۅۆۇۈۉۊۋۍۏےۓەۮۯܐܕܖܗܘܙܞܨܪܬܯݍݙݚݛݫݬݱݳݴݸݹࡀࡆࡇࡉࡔࡧࡩࡪࢪࢫࢬࢮࢱࢲࢹૅેૉ૊૎૏ૐ૑૒૝ૡ૤૯஁ஃ஄அஉ஌எஏ஑னப஫஬"; var L = "ꡲ્૗"; var slft = ci == 0 || wsep.indexOf(str[ci - 1]) != -1; var srgt = ci == str.length - 1 || wsep.indexOf(str[ci + 1]) != -1; if (!slft && R.indexOf(str[ci - 1]) != -1) slft = true; if (!srgt && R.indexOf(str[ci]) != -1) srgt = true; if (!srgt && L.indexOf(str[ci + 1]) != -1) srgt = true; if (!slft && L.indexOf(str[ci]) != -1) slft = true; var feat = null; if (slft) { feat = srgt ? "isol" : "init"; } else { feat = srgt ? "fina" : "medi"; } return feat; }; Typr.U._applySubs = function(gls, ci, tab, llist) { var rlim = gls.length - ci - 1; for (var j = 0; j < tab.tabs.length; j++) { if (tab.tabs[j] == null) continue; var ltab = tab.tabs[j], ind; if (ltab.coverage) { ind = Typr._lctf.coverageIndex(ltab.coverage, gls[ci]); if (ind == -1) continue; } if (tab.ltype == 1) { gls[ci]; if (ltab.fmt == 1) gls[ci] = gls[ci] + ltab.delta; else gls[ci] = ltab.newg[ind]; } else if (tab.ltype == 4) { var vals = ltab.vals[ind]; for (var k = 0; k < vals.length; k++) { var lig = vals[k], rl = lig.chain.length; if (rl > rlim) continue; var good = true, em1 = 0; for (var l = 0; l < rl; l++) { while (gls[ci + em1 + (1 + l)] == -1) em1++; if (lig.chain[l] != gls[ci + em1 + (1 + l)]) good = false; } if (!good) continue; gls[ci] = lig.nglyph; for (var l = 0; l < rl + em1; l++) gls[ci + l + 1] = -1; break; } } else if (tab.ltype == 5 && ltab.fmt == 2) { var cind = Typr._lctf.getInterval(ltab.cDef, gls[ci]); var cls = ltab.cDef[cind + 2], scs = ltab.scset[cls]; for (var i = 0; i < scs.length; i++) { var sc = scs[i], inp = sc.input; if (inp.length > rlim) continue; var good = true; for (var l = 0; l < inp.length; l++) { var cind2 = Typr._lctf.getInterval(ltab.cDef, gls[ci + 1 + l]); if (cind == -1 && ltab.cDef[cind2 + 2] != inp[l]) { good = false; break; } } if (!good) continue; var lrs = sc.substLookupRecords; for (var k = 0; k < lrs.length; k += 2) { lrs[k]; lrs[k + 1]; } } } else if (tab.ltype == 6 && ltab.fmt == 3) { if (!Typr.U._glsCovered(gls, ltab.backCvg, ci - ltab.backCvg.length)) continue; if (!Typr.U._glsCovered(gls, ltab.inptCvg, ci)) continue; if (!Typr.U._glsCovered(gls, ltab.ahedCvg, ci + ltab.inptCvg.length)) continue; var lr = ltab.lookupRec; for (var i = 0; i < lr.length; i += 2) { var cind = lr[i], tab2 = llist[lr[i + 1]]; Typr.U._applySubs(gls, ci + cind, tab2, llist); } } } }; Typr.U._glsCovered = function(gls, cvgs, ci) { for (var i = 0; i < cvgs.length; i++) { var ind = Typr._lctf.coverageIndex(cvgs[i], gls[ci + i]); if (ind == -1) return false; } return true; }; Typr.U.glyphsToPath = function(font, gls, clr) { var tpath = { cmds: [], crds: [] }; var x = 0; for (var i = 0; i < gls.length; i++) { var gid = gls[i]; if (gid == -1) continue; var gid2 = i < gls.length - 1 && gls[i + 1] != -1 ? gls[i + 1] : 0; var path = Typr.U.glyphToPath(font, gid); for (var j = 0; j < path.crds.length; j += 2) { tpath.crds.push(path.crds[j] + x); tpath.crds.push(path.crds[j + 1]); } if (clr) tpath.cmds.push(clr); for (var j = 0; j < path.cmds.length; j++) tpath.cmds.push(path.cmds[j]); if (clr) tpath.cmds.push("X"); x += font.hmtx.aWidth[gid]; if (i < gls.length - 1) x += Typr.U.getPairAdjustment(font, gid, gid2); } return tpath; }; Typr.U.pathToSVG = function(path, prec) { if (prec == null) prec = 5; var out = [], co = 0, lmap = { "M": 2, "L": 2, "Q": 4, "C": 6 }; for (var i = 0; i < path.cmds.length; i++) { var cmd = path.cmds[i], cn = co + (lmap[cmd] ? lmap[cmd] : 0); out.push(cmd); while (co < cn) { var c = path.crds[co++]; out.push(parseFloat(c.toFixed(prec)) + (co == cn ? "" : " ")); } } return out.join(""); }; Typr.U.pathToContext = function(path, ctx) { var c = 0, crds = path.crds; for (var j = 0; j < path.cmds.length; j++) { var cmd = path.cmds[j]; if (cmd == "M") { ctx.moveTo(crds[c], crds[c + 1]); c += 2; } else if (cmd == "L") { ctx.lineTo(crds[c], crds[c + 1]); c += 2; } else if (cmd == "C") { ctx.bezierCurveTo(crds[c], crds[c + 1], crds[c + 2], crds[c + 3], crds[c + 4], crds[c + 5]); c += 6; } else if (cmd == "Q") { ctx.quadraticCurveTo(crds[c], crds[c + 1], crds[c + 2], crds[c + 3]); c += 4; } else if (cmd.charAt(0) == "#") { ctx.beginPath(); ctx.fillStyle = cmd; } else if (cmd == "Z") { ctx.closePath(); } else if (cmd == "X") { ctx.fill(); } } }; Typr.U.P = {}; Typr.U.P.moveTo = function(p, x, y) { p.cmds.push("M"); p.crds.push(x, y); }; Typr.U.P.lineTo = function(p, x, y) { p.cmds.push("L"); p.crds.push(x, y); }; Typr.U.P.curveTo = function(p, a, b, c, d, e, f) { p.cmds.push("C"); p.crds.push(a, b, c, d, e, f); }; Typr.U.P.qcurveTo = function(p, a, b, c, d) { p.cmds.push("Q"); p.crds.push(a, b, c, d); }; Typr.U.P.closePath = function(p) { p.cmds.push("Z"); }; Typr.U._drawCFF = function(cmds, state, font, pdct, p) { var stack = state.stack; var nStems = state.nStems, haveWidth = state.haveWidth, width = state.width, open = state.open; var i = 0; var x = state.x, y = state.y, c1x = 0, c1y = 0, c2x = 0, c2y = 0, c3x = 0, c3y = 0, c4x = 0, c4y = 0, jpx = 0, jpy = 0; var o = { val: 0, size: 0 }; while (i < cmds.length) { Typr.CFF.getCharString(cmds, i, o); var v = o.val; i += o.size; if (v == "o1" || v == "o18") { var hasWidthArg; hasWidthArg = stack.length % 2 !== 0; if (hasWidthArg && !haveWidth) { width = stack.shift() + pdct.nominalWidthX; } nStems += stack.length >> 1; stack.length = 0; haveWidth = true; } else if (v == "o3" || v == "o23") { var hasWidthArg; hasWidthArg = stack.length % 2 !== 0; if (hasWidthArg && !haveWidth) { width = stack.shift() + pdct.nominalWidthX; } nStems += stack.length >> 1; stack.length = 0; haveWidth = true; } else if (v == "o4") { if (stack.length > 1 && !haveWidth) { width = stack.shift() + pdct.nominalWidthX; haveWidth = true; } if (open) Typr.U.P.closePath(p); y += stack.pop(); Typr.U.P.moveTo(p, x, y); open = true; } else if (v == "o5") { while (stack.length > 0) { x += stack.shift(); y += stack.shift(); Typr.U.P.lineTo(p, x, y); } } else if (v == "o6" || v == "o7") { var count = stack.length; var isX = v == "o6"; for (var j = 0; j < count; j++) { var sval = stack.shift(); if (isX) { x += sval; } else { y += sval; } isX = !isX; Typr.U.P.lineTo(p, x, y); } } else if (v == "o8" || v == "o24") { var count = stack.length; var index = 0; while (index + 6 <= count) { c1x = x + stack.shift(); c1y = y + stack.shift(); c2x = c1x + stack.shift(); c2y = c1y + stack.shift(); x = c2x + stack.shift(); y = c2y + stack.shift(); Typr.U.P.curveTo(p, c1x, c1y, c2x, c2y, x, y); index += 6; } if (v == "o24") { x += stack.shift(); y += stack.shift(); Typr.U.P.lineTo(p, x, y); } } else if (v == "o11") { break; } else if (v == "o1234" || v == "o1235" || v == "o1236" || v == "o1237") { if (v == "o1234") { c1x = x + stack.shift(); c1y = y; c2x = c1x + stack.shift(); c2y = c1y + stack.shift(); jpx = c2x + stack.shift(); jpy = c2y; c3x = jpx + stack.shift(); c3y = c2y; c4x = c3x + stack.shift(); c4y = y; x = c4x + stack.shift(); Typr.U.P.curveTo(p, c1x, c1y, c2x, c2y, jpx, jpy); Typr.U.P.curveTo(p, c3x, c3y, c4x, c4y, x, y); } if (v == "o1235") { c1x = x + stack.shift(); c1y = y + stack.shift(); c2x = c1x + stack.shift(); c2y = c1y + stack.shift(); jpx = c2x + stack.shift(); jpy = c2y + stack.shift(); c3x = jpx + stack.shift(); c3y = jpy + stack.shift(); c4x = c3x + stack.shift(); c4y = c3y + stack.shift(); x = c4x + stack.shift(); y = c4y + stack.shift(); stack.shift(); Typr.U.P.curveTo(p, c1x, c1y, c2x, c2y, jpx, jpy); Typr.U.P.curveTo(p, c3x, c3y, c4x, c4y, x, y); } if (v == "o1236") { c1x = x + stack.shift(); c1y = y + stack.shift(); c2x = c1x + stack.shift(); c2y = c1y + stack.shift(); jpx = c2x + stack.shift(); jpy = c2y; c3x = jpx + stack.shift(); c3y = c2y; c4x = c3x + stack.shift(); c4y = c3y + stack.shift(); x = c4x + stack.shift(); Typr.U.P.curveTo(p, c1x, c1y, c2x, c2y, jpx, jpy); Typr.U.P.curveTo(p, c3x, c3y, c4x, c4y, x, y); } if (v == "o1237") { c1x = x + stack.shift(); c1y = y + stack.shift(); c2x = c1x + stack.shift(); c2y = c1y + stack.shift(); jpx = c2x + stack.shift(); jpy = c2y + stack.shift(); c3x = jpx + stack.shift(); c3y = jpy + stack.shift(); c4x = c3x + stack.shift(); c4y = c3y + stack.shift(); if (Math.abs(c4x - x) > Math.abs(c4y - y)) { x = c4x + stack.shift(); } else { y = c4y + stack.shift(); } Typr.U.P.curveTo(p, c1x, c1y, c2x, c2y, jpx, jpy); Typr.U.P.curveTo(p, c3x, c3y, c4x, c4y, x, y); } } else if (v == "o14") { if (stack.length > 0 && !haveWidth) { width = stack.shift() + font.nominalWidthX; haveWidth = true; } if (stack.length == 4) { var adx = stack.shift(); var ady = stack.shift(); var bchar = stack.shift(); var achar = stack.shift(); var bind2 = Typr.CFF.glyphBySE(font, bchar); var aind = Typr.CFF.glyphBySE(font, achar); Typr.U._drawCFF(font.CharStrings[bind2], state, font, pdct, p); state.x = adx; state.y = ady; Typr.U._drawCFF(font.CharStrings[aind], state, font, pdct, p); } if (open) { Typr.U.P.closePath(p); open = false; } } else if (v == "o19" || v == "o20") { var hasWidthArg; hasWidthArg = stack.length % 2 !== 0; if (hasWidthArg && !haveWidth) { width = stack.shift() + pdct.nominalWidthX; } nStems += stack.length >> 1; stack.length = 0; haveWidth = true; i += nStems + 7 >> 3; } else if (v == "o21") { if (stack.length > 2 && !haveWidth) { width = stack.shift() + pdct.nominalWidthX; haveWidth = true; } y += stack.pop(); x += stack.pop(); if (open) Typr.U.P.closePath(p); Typr.U.P.moveTo(p, x, y); open = true; } else if (v == "o22") { if (stack.length > 1 && !haveWidth) { width = stack.shift() + pdct.nominalWidthX; haveWidth = true; } x += stack.pop(); if (open) Typr.U.P.closePath(p); Typr.U.P.moveTo(p, x, y); open = true; } else if (v == "o25") { while (stack.length > 6) { x += stack.shift(); y += stack.shift(); Typr.U.P.lineTo(p, x, y); } c1x = x + stack.shift(); c1y = y + stack.shift(); c2x = c1x + stack.shift(); c2y = c1y + stack.shift(); x = c2x + stack.shift(); y = c2y + stack.shift(); Typr.U.P.curveTo(p, c1x, c1y, c2x, c2y, x, y); } else if (v == "o26") { if (stack.length % 2) { x += stack.shift(); } while (stack.length > 0) { c1x = x; c1y = y + stack.shift(); c2x = c1x + stack.shift(); c2y = c1y + stack.shift(); x = c2x; y = c2y + stack.shift(); Typr.U.P.curveTo(p, c1x, c1y, c2x, c2y, x, y); } } else if (v == "o27") { if (stack.length % 2) { y += stack.shift(); } while (stack.length > 0) { c1x = x + stack.shift(); c1y = y; c2x = c1x + stack.shift(); c2y = c1y + stack.shift(); x = c2x + stack.shift(); y = c2y; Typr.U.P.curveTo(p, c1x, c1y, c2x, c2y, x, y); } } else if (v == "o10" || v == "o29") { var obj = v == "o10" ? pdct : font; if (stack.length == 0) { console.warn("error: empty stack"); } else { var ind = stack.pop(); var subr = obj.Subrs[ind + obj.Bias]; state.x = x; state.y = y; state.nStems = nStems; state.haveWidth = haveWidth; state.width = width; state.open = open; Typr.U._drawCFF(subr, state, font, pdct, p); x = state.x; y = state.y; nStems = state.nStems; haveWidth = state.haveWidth; width = state.width; open = state.open; } } else if (v == "o30" || v == "o31") { var count, count1 = stack.length; var index = 0; var alternate = v == "o31"; count = count1 & ~2; index += count1 - count; while (index < count) { if (alternate) { c1x = x + stack.shift(); c1y = y; c2x = c1x + stack.shift(); c2y = c1y + stack.shift(); y = c2y + stack.shift(); if (count - index == 5) { x = c2x + stack.shift(); index++; } else { x = c2x; } alternate = false; } else { c1x = x; c1y = y + stack.shift(); c2x = c1x + stack.shift(); c2y = c1y + stack.shift(); x = c2x + stack.shift(); if (count - index == 5) { y = c2y + stack.shift(); index++; } else { y = c2y; } alternate = true; } Typr.U.P.curveTo(p, c1x, c1y, c2x, c2y, x, y); index += 4; } } else if ((v + "").charAt(0) == "o") { console.warn("Unknown operation: " + v, cmds); throw v; } else stack.push(v); } state.x = x; state.y = y; state.nStems = nStems; state.haveWidth = haveWidth; state.width = width; state.open = open; }; Typr$1.Typr = Typr; var Typr_js_1 = Typr$1; var friendlyTags = { "aalt": "Access All Alternates", "abvf": "Above-base Forms", "abvm": "Above - base Mark Positioning", "abvs": "Above - base Substitutions", "afrc": "Alternative Fractions", "akhn": "Akhands", "blwf": "Below - base Forms", "blwm": "Below - base Mark Positioning", "blws": "Below - base Substitutions", "calt": "Contextual Alternates", "case": "Case - Sensitive Forms", "ccmp": "Glyph Composition / Decomposition", "cfar": "Conjunct Form After Ro", "cjct": "Conjunct Forms", "clig": "Contextual Ligatures", "cpct": "Centered CJK Punctuation", "cpsp": "Capital Spacing", "cswh": "Contextual Swash", "curs": "Cursive Positioning", "c2pc": "Petite Capitals From Capitals", "c2sc": "Small Capitals From Capitals", "dist": "Distances", "dlig": "Discretionary Ligatures", "dnom": "Denominators", "dtls": "Dotless Forms", "expt": "Expert Forms", "falt": "Final Glyph on Line Alternates", "fin2": "Terminal Forms #2", "fin3": "Terminal Forms #3", "fina": "Terminal Forms", "flac": "Flattened accent forms", "frac": "Fractions", "fwid": "Full Widths", "half": "Half Forms", "haln": "Halant Forms", "halt": "Alternate Half Widths", "hist": "Historical Forms", "hkna": "Horizontal Kana Alternates", "hlig": "Historical Ligatures", "hngl": "Hangul", "hojo": "Hojo Kanji Forms(JIS X 0212 - 1990 Kanji Forms)", "hwid": "Half Widths", "init": "Initial Forms", "isol": "Isolated Forms", "ital": "Italics", "jalt": "Justification Alternates", "jp78": "JIS78 Forms", "jp83": "JIS83 Forms", "jp90": "JIS90 Forms", "jp04": "JIS2004 Forms", "kern": "Kerning", "lfbd": "Left Bounds", "liga": "Standard Ligatures", "ljmo": "Leading Jamo Forms", "lnum": "Lining Figures", "locl": "Localized Forms", "ltra": "Left - to - right alternates", "ltrm": "Left - to - right mirrored forms", "mark": "Mark Positioning", "med2": "Medial Forms #2", "medi": "Medial Forms", "mgrk": "Mathematical Greek", "mkmk": "Mark to Mark Positioning", "mset": "Mark Positioning via Substitution", "nalt": "Alternate Annotation Forms", "nlck": "NLC Kanji Forms", "nukt": "Nukta Forms", "numr": "Numerators", "onum": "Oldstyle Figures", "opbd": "Optical Bounds", "ordn": "Ordinals", "ornm": "Ornaments", "palt": "Proportional Alternate Widths", "pcap": "Petite Capitals", "pkna": "Proportional Kana", "pnum": "Proportional Figures", "pref": "Pre - Base Forms", "pres": "Pre - base Substitutions", "pstf": "Post - base Forms", "psts": "Post - base Substitutions", "pwid": "Proportional Widths", "qwid": "Quarter Widths", "rand": "Randomize", "rclt": "Required Contextual Alternates", "rkrf": "Rakar Forms", "rlig": "Required Ligatures", "rphf": "Reph Forms", "rtbd": "Right Bounds", "rtla": "Right - to - left alternates", "rtlm": "Right - to - left mirrored forms", "ruby": "Ruby Notation Forms", "rvrn": "Required Variation Alternates", "salt": "Stylistic Alternates", "sinf": "Scientific Inferiors", "size": "Optical size", "smcp": "Small Capitals", "smpl": "Simplified Forms", "ssty": "Math script style alternates", "stch": "Stretching Glyph Decomposition", "subs": "Subscript", "sups": "Superscript", "swsh": "Swash", "titl": "Titling", "tjmo": "Trailing Jamo Forms", "tnam": "Traditional Name Forms", "tnum": "Tabular Figures", "trad": "Traditional Forms", "twid": "Third Widths", "unic": "Unicase", "valt": "Alternate Vertical Metrics", "vatu": "Vattu Variants", "vert": "Vertical Writing", "vhal": "Alternate Vertical Half Metrics", "vjmo": "Vowel Jamo Forms", "vkna": "Vertical Kana Alternates", "vkrn": "Vertical Kerning", "vpal": "Proportional Alternate Vertical Metrics", "vrt2": "Vertical Alternates and Rotation", "vrtr": "Vertical Alternates for Rotation", "zero": "Slashed Zero" }; var Font = ( /** @class */ function() { function Font2(data) { var obj = Typr_js_1.Typr.parse(data); if (!obj.length || typeof obj[0] !== "object" || typeof obj[0].hasOwnProperty !== "function") { throw "unable to parse font"; } for (var n in obj[0]) { this[n] = obj[0][n]; } this.enabledGSUB = {}; } Font2.prototype.getFamilyName = function() { return this.name && (this.name.typoFamilyName || this.name.fontFamily) || ""; }; Font2.prototype.getSubFamilyName = function() { return this.name && (this.name.typoSubfamilyName || this.name.fontSubfamily) || ""; }; Font2.prototype.glyphToPath = function(gid) { return Typr_js_1.Typr.U.glyphToPath(this, gid); }; Font2.prototype.getPairAdjustment = function(gid1, gid2) { return Typr_js_1.Typr.U.getPairAdjustment(this, gid1, gid2); }; Font2.prototype.stringToGlyphs = function(str) { return Typr_js_1.Typr.U.stringToGlyphs(this, str); }; Font2.prototype.glyphsToPath = function(gls) { return Typr_js_1.Typr.U.glyphsToPath(this, gls); }; Font2.prototype.pathToSVG = function(path, prec) { return Typr_js_1.Typr.U.pathToSVG(path, prec); }; Font2.prototype.pathToContext = function(path, ctx) { return Typr_js_1.Typr.U.pathToContext(path, ctx); }; Font2.prototype.lookupFriendlyName = function(table, feature) { if (this[table] !== void 0) { var tbl = this[table]; var feat = tbl.featureList[feature]; return this.featureFriendlyName(feat); } return ""; }; Font2.prototype.featureFriendlyName = function(feature) { if (friendlyTags[feature.tag]) { return friendlyTags[feature.tag]; } if (feature.tag.match(/ss[0-2][0-9]/)) { var name_1 = "Stylistic Set " + Number(feature.tag.substr(2, 2)).toString(); if (feature.featureParams) { var version = Typr_js_1.Typr._bin.readUshort(this._data, feature.featureParams); if (version === 0) { var nameID = Typr_js_1.Typr._bin.readUshort(this._data, feature.featureParams + 2); if (this.name && this.name[nameID] !== void 0) { return name_1 + " - " + this.name[nameID]; } } } return name_1; } if (feature.tag.match(/cv[0-9][0-9]/)) { return "Character Variant " + Number(feature.tag.substr(2, 2)).toString(); } return ""; }; Font2.prototype.enableGSUB = function(featureNumber) { if (this.GSUB) { var feature = this.GSUB.featureList[featureNumber]; if (feature) { for (var i = 0; i < feature.tab.length; ++i) { this.enabledGSUB[feature.tab[i]] = (this.enabledGSUB[feature.tab[i]] || 0) + 1; } } } }; Font2.prototype.disableGSUB = function(featureNumber) { if (this.GSUB) { var feature = this.GSUB.featureList[featureNumber]; if (feature) { for (var i = 0; i < feature.tab.length; ++i) { if (this.enabledGSUB[feature.tab[i]] > 1) { --this.enabledGSUB[feature.tab[i]]; } else { delete this.enabledGSUB[feature.tab[i]]; } } } } }; Font2.prototype.codeToGlyph = function(code) { var g = Typr_js_1.Typr.U.codeToGlyph(this, code); if (this.GSUB) { var gls = [g]; for (var n in this.enabledGSUB) { var l = this.GSUB.lookupList[n]; Typr_js_1.Typr.U._applySubs(gls, 0, l, this.GSUB.lookupList); } if (gls.length === 1) return gls[0]; } return g; }; return Font2; }() ); var Font_1 = Font; const base64ToUint8Array = (base64) => { const data = window.atob(base64); const buffer = new Uint8Array(data.length); for (let i = 0; i < data.length; i++) { buffer[i] = data.charCodeAt(i); } return buffer; }; const parseResourceText = (resourceName) => { try { return JSON.parse(GM_getResourceText(resourceName)); } catch (e) { console.log("parseResourceText error:", e); return null; } }; const findEncryptedFontStyle = (document2) => { const styles = Array.from(document2.querySelectorAll("style")); return styles.find((style) => { var _a; return (_a = style.textContent) == null ? void 0 : _a.includes("font-cxsecret"); }); }; const buildDecryptionTable = (font, lookupTable) => { const match = {}; const CHINESE_CHAR_START = 19968; const CHINESE_CHAR_END = 40870; for (let charCode = CHINESE_CHAR_START; charCode < CHINESE_CHAR_END; charCode++) { const glyph = font.codeToGlyph(charCode); if (!glyph) continue; const path = font.glyphToPath(glyph); const hash = md5(JSON.stringify(path)).slice(24); match[charCode] = lookupTable[hash]; } return match; }; const replaceEncryptedText = (elements, decryptionTable) => { elements.forEach((el) => { let html = el.innerHTML; Object.entries(decryptionTable).forEach(([key, value]) => { const encryptedChar = String.fromCharCode(Number(key)); const decryptedChar = String.fromCharCode(value); html = html.replace(new RegExp(encryptedChar, "g"), decryptedChar); }); el.classList.remove("font-cxsecret"); el.innerHTML = html; }); }; function decrypt(iframeDocument) { var _a, _b; const fontStyle = findEncryptedFontStyle(iframeDocument); if (!fontStyle) return; const fontData = (_b = (_a = fontStyle.textContent) == null ? void 0 : _a.match(/base64,([\w\W]+?)'/)) == null ? void 0 : _b[1]; if (!fontData) return; const fontArray = base64ToUint8Array(fontData); const font = new Font_1(fontArray); const lookupTable = parseResourceText("ttf") || parseResourceText("ttf2"); if (!lookupTable) return; const decryptionTable = buildDecryptionTable(font, lookupTable); const elements = iframeDocument.querySelectorAll(".font-cxsecret"); replaceEncryptedText(elements, decryptionTable); } function isFunction(value) { return typeof value === "function"; } function hasLift(source) { return isFunction(source === null || source === void 0 ? void 0 : source.lift); } function operate(init) { return function(source) { if (hasLift(source)) { return source.lift(function(liftedSource) { try { return init(liftedSource, this); } catch (err) { this.error(err); } }); } throw new TypeError("Unable to lift unknown Observable type"); }; } var extendStatics = function(d, b) { extendStatics = Object.setPrototypeOf || { __proto__: [] } instanceof Array && function(d2, b2) { d2.__proto__ = b2; } || function(d2, b2) { for (var p in b2) if (Object.prototype.hasOwnProperty.call(b2, p)) d2[p] = b2[p]; }; return extendStatics(d, b); }; function __extends(d, b) { if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); } function __awaiter(thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function(resolve) { resolve(value); }); } return new (P || (P = Promise))(function(resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, [])).next()); }); } function __generator(thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function(v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || op[1] > t[0] && op[1] < t[3])) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } } function __values(o) { var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; if (m) return m.call(o); if (o && typeof o.length === "number") return { next: function() { if (o && i >= o.length) o = void 0; return { value: o && o[i++], done: !o }; } }; throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); } function __read(o, n) { var m = typeof Symbol === "function" && o[Symbol.iterator]; if (!m) return o; var i = m.call(o), r, ar = [], e; try { while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); } catch (error) { e = { error }; } finally { try { if (r && !r.done && (m = i["return"])) m.call(i); } finally { if (e) throw e.error; } } return ar; } function __spreadArray(to, from2, pack) { if (arguments.length === 2) for (var i = 0, l = from2.length, ar; i < l; i++) { if (ar || !(i in from2)) { if (!ar) ar = Array.prototype.slice.call(from2, 0, i); ar[i] = from2[i]; } } return to.concat(ar || Array.prototype.slice.call(from2)); } function __await(v) { return this instanceof __await ? (this.v = v, this) : new __await(v); } function __asyncGenerator(thisArg, _arguments, generator) { if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); var g = generator.apply(thisArg, _arguments || []), i, q = []; return i = Object.create((typeof AsyncIterator === "function" ? AsyncIterator : Object).prototype), verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function() { return this; }, i; function awaitReturn(f) { return function(v) { return Promise.resolve(v).then(f, reject); }; } function verb(n, f) { if (g[n]) { i[n] = function(v) { return new Promise(function(a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } } function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } } function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); } function fulfill(value) { resume("next", value); } function reject(value) { resume("throw", value); } function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); } } function __asyncValues(o) { if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); var m = o[Symbol.asyncIterator], i; return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function() { return this; }, i); function verb(n) { i[n] = o[n] && function(v) { return new Promise(function(resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; } function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v2) { resolve({ value: v2, done: d }); }, reject); } } typeof SuppressedError === "function" ? SuppressedError : function(error, suppressed, message2) { var e = new Error(message2); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }; var isArrayLike = function(x) { return x && typeof x.length === "number" && typeof x !== "function"; }; function isPromise(value) { return isFunction(value === null || value === void 0 ? void 0 : value.then); } function createErrorClass(createImpl) { var _super = function(instance) { Error.call(instance); instance.stack = new Error().stack; }; var ctorFunc = createImpl(_super); ctorFunc.prototype = Object.create(Error.prototype); ctorFunc.prototype.constructor = ctorFunc; return ctorFunc; } var UnsubscriptionError = createErrorClass(function(_super) { return function UnsubscriptionErrorImpl(errors) { _super(this); this.message = errors ? errors.length + " errors occurred during unsubscription:\n" + errors.map(function(err, i) { return i + 1 + ") " + err.toString(); }).join("\n ") : ""; this.name = "UnsubscriptionError"; this.errors = errors; }; }); function arrRemove(arr, item) { if (arr) { var index = arr.indexOf(item); 0 <= index && arr.splice(index, 1); } } var Subscription = function() { function Subscription2(initialTeardown) { this.initialTeardown = initialTeardown; this.closed = false; this._parentage = null; this._finalizers = null; } Subscription2.prototype.unsubscribe = function() { var e_1, _a, e_2, _b; var errors; if (!this.closed) { this.closed = true; var _parentage = this._parentage; if (_parentage) { this._parentage = null; if (Array.isArray(_parentage)) { try { for (var _parentage_1 = __values(_parentage), _parentage_1_1 = _parentage_1.next(); !_parentage_1_1.done; _parentage_1_1 = _parentage_1.next()) { var parent_1 = _parentage_1_1.value; parent_1.remove(this); } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (_parentage_1_1 && !_parentage_1_1.done && (_a = _parentage_1.return)) _a.call(_parentage_1); } finally { if (e_1) throw e_1.error; } } } else { _parentage.remove(this); } } var initialFinalizer = this.initialTeardown; if (isFunction(initialFinalizer)) { try { initialFinalizer(); } catch (e) { errors = e instanceof UnsubscriptionError ? e.errors : [e]; } } var _finalizers = this._finalizers; if (_finalizers) { this._finalizers = null; try { for (var _finalizers_1 = __values(_finalizers), _finalizers_1_1 = _finalizers_1.next(); !_finalizers_1_1.done; _finalizers_1_1 = _finalizers_1.next()) { var finalizer = _finalizers_1_1.value; try { execFinalizer(finalizer); } catch (err) { errors = errors !== null && errors !== void 0 ? errors : []; if (err instanceof UnsubscriptionError) { errors = __spreadArray(__spreadArray([], __read(errors)), __read(err.errors)); } else { errors.push(err); } } } } catch (e_2_1) { e_2 = { error: e_2_1 }; } finally { try { if (_finalizers_1_1 && !_finalizers_1_1.done && (_b = _finalizers_1.return)) _b.call(_finalizers_1); } finally { if (e_2) throw e_2.error; } } } if (errors) { throw new UnsubscriptionError(errors); } } }; Subscription2.prototype.add = function(teardown) { var _a; if (teardown && teardown !== this) { if (this.closed) { execFinalizer(teardown); } else { if (teardown instanceof Subscription2) { if (teardown.closed || teardown._hasParent(this)) { return; } teardown._addParent(this); } (this._finalizers = (_a = this._finalizers) !== null && _a !== void 0 ? _a : []).push(teardown); } } }; Subscription2.prototype._hasParent = function(parent) { var _parentage = this._parentage; return _parentage === parent || Array.isArray(_parentage) && _parentage.includes(parent); }; Subscription2.prototype._addParent = function(parent) { var _parentage = this._parentage; this._parentage = Array.isArray(_parentage) ? (_parentage.push(parent), _parentage) : _parentage ? [_parentage, parent] : parent; }; Subscription2.prototype._removeParent = function(parent) { var _parentage = this._parentage; if (_parentage === parent) { this._parentage = null; } else if (Array.isArray(_parentage)) { arrRemove(_parentage, parent); } }; Subscription2.prototype.remove = function(teardown) { var _finalizers = this._finalizers; _finalizers && arrRemove(_finalizers, teardown); if (teardown instanceof Subscription2) { teardown._removeParent(this); } }; Subscription2.EMPTY = function() { var empty = new Subscription2(); empty.closed = true; return empty; }(); return Subscription2; }(); Subscription.EMPTY; function isSubscription(value) { return value instanceof Subscription || value && "closed" in value && isFunction(value.remove) && isFunction(value.add) && isFunction(value.unsubscribe); } function execFinalizer(finalizer) { if (isFunction(finalizer)) { finalizer(); } else { finalizer.unsubscribe(); } } var config = { Promise: void 0, useDeprecatedNextContext: false }; var timeoutProvider = { setTimeout: function(handler, timeout) { var args = []; for (var _i = 2; _i < arguments.length; _i++) { args[_i - 2] = arguments[_i]; } return setTimeout.apply(void 0, __spreadArray([handler, timeout], __read(args))); }, clearTimeout: function(handle) { return clearTimeout(handle); }, delegate: void 0 }; function reportUnhandledError(err) { timeoutProvider.setTimeout(function() { { throw err; } }); } function noop() { } function errorContext(cb) { { cb(); } } var Subscriber = function(_super) { __extends(Subscriber2, _super); function Subscriber2(destination) { var _this = _super.call(this) || this; _this.isStopped = false; if (destination) { _this.destination = destination; if (isSubscription(destination)) { destination.add(_this); } } else { _this.destination = EMPTY_OBSERVER; } return _this; } Subscriber2.create = function(next, error, complete) { return new SafeSubscriber(next, error, complete); }; Subscriber2.prototype.next = function(value) { if (this.isStopped) ; else { this._next(value); } }; Subscriber2.prototype.error = function(err) { if (this.isStopped) ; else { this.isStopped = true; this._error(err); } }; Subscriber2.prototype.complete = function() { if (this.isStopped) ; else { this.isStopped = true; this._complete(); } }; Subscriber2.prototype.unsubscribe = function() { if (!this.closed) { this.isStopped = true; _super.prototype.unsubscribe.call(this); this.destination = null; } }; Subscriber2.prototype._next = function(value) { this.destination.next(value); }; Subscriber2.prototype._error = function(err) { try { this.destination.error(err); } finally { this.unsubscribe(); } }; Subscriber2.prototype._complete = function() { try { this.destination.complete(); } finally { this.unsubscribe(); } }; return Subscriber2; }(Subscription); var _bind = Function.prototype.bind; function bind(fn, thisArg) { return _bind.call(fn, thisArg); } var ConsumerObserver = function() { function ConsumerObserver2(partialObserver) { this.partialObserver = partialObserver; } ConsumerObserver2.prototype.next = function(value) { var partialObserver = this.partialObserver; if (partialObserver.next) { try { partialObserver.next(value); } catch (error) { handleUnhandledError(error); } } }; ConsumerObserver2.prototype.error = function(err) { var partialObserver = this.partialObserver; if (partialObserver.error) { try { partialObserver.error(err); } catch (error) { handleUnhandledError(error); } } else { handleUnhandledError(err); } }; ConsumerObserver2.prototype.complete = function() { var partialObserver = this.partialObserver; if (partialObserver.complete) { try { partialObserver.complete(); } catch (error) { handleUnhandledError(error); } } }; return ConsumerObserver2; }(); var SafeSubscriber = function(_super) { __extends(SafeSubscriber2, _super); function SafeSubscriber2(observerOrNext, error, complete) { var _this = _super.call(this) || this; var partialObserver; if (isFunction(observerOrNext) || !observerOrNext) { partialObserver = { next: observerOrNext !== null && observerOrNext !== void 0 ? observerOrNext : void 0, error: error !== null && error !== void 0 ? error : void 0, complete: complete !== null && complete !== void 0 ? complete : void 0 }; } else { var context_1; if (_this && config.useDeprecatedNextContext) { context_1 = Object.create(observerOrNext); context_1.unsubscribe = function() { return _this.unsubscribe(); }; partialObserver = { next: observerOrNext.next && bind(observerOrNext.next, context_1), error: observerOrNext.error && bind(observerOrNext.error, context_1), complete: observerOrNext.complete && bind(observerOrNext.complete, context_1) }; } else { partialObserver = observerOrNext; } } _this.destination = new ConsumerObserver(partialObserver); return _this; } return SafeSubscriber2; }(Subscriber); function handleUnhandledError(error) { { reportUnhandledError(error); } } function defaultErrorHandler(err) { throw err; } var EMPTY_OBSERVER = { closed: true, next: noop, error: defaultErrorHandler, complete: noop }; var observable = function() { return typeof Symbol === "function" && Symbol.observable || "@@observable"; }(); function identity(x) { return x; } function pipeFromArray(fns) { if (fns.length === 0) { return identity; } if (fns.length === 1) { return fns[0]; } return function piped(input) { return fns.reduce(function(prev, fn) { return fn(prev); }, input); }; } var Observable = function() { function Observable2(subscribe) { if (subscribe) { this._subscribe = subscribe; } } Observable2.prototype.lift = function(operator) { var observable2 = new Observable2(); observable2.source = this; observable2.operator = operator; return observable2; }; Observable2.prototype.subscribe = function(observerOrNext, error, complete) { var _this = this; var subscriber = isSubscriber(observerOrNext) ? observerOrNext : new SafeSubscriber(observerOrNext, error, complete); errorContext(function() { var _a = _this, operator = _a.operator, source = _a.source; subscriber.add(operator ? operator.call(subscriber, source) : source ? _this._subscribe(subscriber) : _this._trySubscribe(subscriber)); }); return subscriber; }; Observable2.prototype._trySubscribe = function(sink) { try { return this._subscribe(sink); } catch (err) { sink.error(err); } }; Observable2.prototype.forEach = function(next, promiseCtor) { var _this = this; promiseCtor = getPromiseCtor(promiseCtor); return new promiseCtor(function(resolve, reject) { var subscriber = new SafeSubscriber({ next: function(value) { try { next(value); } catch (err) { reject(err); subscriber.unsubscribe(); } }, error: reject, complete: resolve }); _this.subscribe(subscriber); }); }; Observable2.prototype._subscribe = function(subscriber) { var _a; return (_a = this.source) === null || _a === void 0 ? void 0 : _a.subscribe(subscriber); }; Observable2.prototype[observable] = function() { return this; }; Observable2.prototype.pipe = function() { var operations = []; for (var _i = 0; _i < arguments.length; _i++) { operations[_i] = arguments[_i]; } return pipeFromArray(operations)(this); }; Observable2.prototype.toPromise = function(promiseCtor) { var _this = this; promiseCtor = getPromiseCtor(promiseCtor); return new promiseCtor(function(resolve, reject) { var value; _this.subscribe(function(x) { return value = x; }, function(err) { return reject(err); }, function() { return resolve(value); }); }); }; Observable2.create = function(subscribe) { return new Observable2(subscribe); }; return Observable2; }(); function getPromiseCtor(promiseCtor) { var _a; return (_a = promiseCtor !== null && promiseCtor !== void 0 ? promiseCtor : config.Promise) !== null && _a !== void 0 ? _a : Promise; } function isObserver(value) { return value && isFunction(value.next) && isFunction(value.error) && isFunction(value.complete); } function isSubscriber(value) { return value && value instanceof Subscriber || isObserver(value) && isSubscription(value); } function isInteropObservable(input) { return isFunction(input[observable]); } function isAsyncIterable(obj) { return Symbol.asyncIterator && isFunction(obj === null || obj === void 0 ? void 0 : obj[Symbol.asyncIterator]); } function createInvalidObservableTypeError(input) { return new TypeError("You provided " + (input !== null && typeof input === "object" ? "an invalid object" : "'" + input + "'") + " where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable."); } function getSymbolIterator() { if (typeof Symbol !== "function" || !Symbol.iterator) { return "@@iterator"; } return Symbol.iterator; } var iterator = getSymbolIterator(); function isIterable(input) { return isFunction(input === null || input === void 0 ? void 0 : input[iterator]); } function readableStreamLikeToAsyncGenerator(readableStream) { return __asyncGenerator(this, arguments, function readableStreamLikeToAsyncGenerator_1() { var reader, _a, value, done; return __generator(this, function(_b) { switch (_b.label) { case 0: reader = readableStream.getReader(); _b.label = 1; case 1: _b.trys.push([1, , 9, 10]); _b.label = 2; case 2: return [4, __await(reader.read())]; case 3: _a = _b.sent(), value = _a.value, done = _a.done; if (!done) return [3, 5]; return [4, __await(void 0)]; case 4: return [2, _b.sent()]; case 5: return [4, __await(value)]; case 6: return [4, _b.sent()]; case 7: _b.sent(); return [3, 2]; case 8: return [3, 10]; case 9: reader.releaseLock(); return [7]; case 10: return [2]; } }); }); } function isReadableStreamLike(obj) { return isFunction(obj === null || obj === void 0 ? void 0 : obj.getReader); } function innerFrom(input) { if (input instanceof Observable) { return input; } if (input != null) { if (isInteropObservable(input)) { return fromInteropObservable(input); } if (isArrayLike(input)) { return fromArrayLike(input); } if (isPromise(input)) { return fromPromise(input); } if (isAsyncIterable(input)) { return fromAsyncIterable(input); } if (isIterable(input)) { return fromIterable(input); } if (isReadableStreamLike(input)) { return fromReadableStreamLike(input); } } throw createInvalidObservableTypeError(input); } function fromInteropObservable(obj) { return new Observable(function(subscriber) { var obs = obj[observable](); if (isFunction(obs.subscribe)) { return obs.subscribe(subscriber); } throw new TypeError("Provided object does not correctly implement Symbol.observable"); }); } function fromArrayLike(array) { return new Observable(function(subscriber) { for (var i = 0; i < array.length && !subscriber.closed; i++) { subscriber.next(array[i]); } subscriber.complete(); }); } function fromPromise(promise) { return new Observable(function(subscriber) { promise.then(function(value) { if (!subscriber.closed) { subscriber.next(value); subscriber.complete(); } }, function(err) { return subscriber.error(err); }).then(null, reportUnhandledError); }); } function fromIterable(iterable) { return new Observable(function(subscriber) { var e_1, _a; try { for (var iterable_1 = __values(iterable), iterable_1_1 = iterable_1.next(); !iterable_1_1.done; iterable_1_1 = iterable_1.next()) { var value = iterable_1_1.value; subscriber.next(value); if (subscriber.closed) { return; } } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (iterable_1_1 && !iterable_1_1.done && (_a = iterable_1.return)) _a.call(iterable_1); } finally { if (e_1) throw e_1.error; } } subscriber.complete(); }); } function fromAsyncIterable(asyncIterable) { return new Observable(function(subscriber) { process(asyncIterable, subscriber).catch(function(err) { return subscriber.error(err); }); }); } function fromReadableStreamLike(readableStream) { return fromAsyncIterable(readableStreamLikeToAsyncGenerator(readableStream)); } function process(asyncIterable, subscriber) { var asyncIterable_1, asyncIterable_1_1; var e_2, _a; return __awaiter(this, void 0, void 0, function() { var value, e_2_1; return __generator(this, function(_b) { switch (_b.label) { case 0: _b.trys.push([0, 5, 6, 11]); asyncIterable_1 = __asyncValues(asyncIterable); _b.label = 1; case 1: return [4, asyncIterable_1.next()]; case 2: if (!(asyncIterable_1_1 = _b.sent(), !asyncIterable_1_1.done)) return [3, 4]; value = asyncIterable_1_1.value; subscriber.next(value); if (subscriber.closed) { return [2]; } _b.label = 3; case 3: return [3, 1]; case 4: return [3, 11]; case 5: e_2_1 = _b.sent(); e_2 = { error: e_2_1 }; return [3, 11]; case 6: _b.trys.push([6, , 9, 10]); if (!(asyncIterable_1_1 && !asyncIterable_1_1.done && (_a = asyncIterable_1.return))) return [3, 8]; return [4, _a.call(asyncIterable_1)]; case 7: _b.sent(); _b.label = 8; case 8: return [3, 10]; case 9: if (e_2) throw e_2.error; return [7]; case 10: return [7]; case 11: subscriber.complete(); return [2]; } }); }); } function createOperatorSubscriber(destination, onNext, onComplete, onError, onFinalize) { return new OperatorSubscriber(destination, onNext, onComplete, onError, onFinalize); } var OperatorSubscriber = function(_super) { __extends(OperatorSubscriber2, _super); function OperatorSubscriber2(destination, onNext, onComplete, onError, onFinalize, shouldUnsubscribe) { var _this = _super.call(this, destination) || this; _this.onFinalize = onFinalize; _this.shouldUnsubscribe = shouldUnsubscribe; _this._next = onNext ? function(value) { try { onNext(value); } catch (err) { destination.error(err); } } : _super.prototype._next; _this._error = onError ? function(err) { try { onError(err); } catch (err2) { destination.error(err2); } finally { this.unsubscribe(); } } : _super.prototype._error; _this._complete = onComplete ? function() { try { onComplete(); } catch (err) { destination.error(err); } finally { this.unsubscribe(); } } : _super.prototype._complete; return _this; } OperatorSubscriber2.prototype.unsubscribe = function() { var _a; if (!this.shouldUnsubscribe || this.shouldUnsubscribe()) { var closed_1 = this.closed; _super.prototype.unsubscribe.call(this); !closed_1 && ((_a = this.onFinalize) === null || _a === void 0 ? void 0 : _a.call(this)); } }; return OperatorSubscriber2; }(Subscriber); function map(project, thisArg) { return operate(function(source, subscriber) { var index = 0; source.subscribe(createOperatorSubscriber(subscriber, function(value) { subscriber.next(project.call(thisArg, value, index++)); })); }); } function mergeInternals(source, subscriber, project, concurrent, onBeforeNext, expand, innerSubScheduler, additionalFinalizer) { var buffer = []; var active = 0; var index = 0; var isComplete = false; var checkComplete = function() { if (isComplete && !buffer.length && !active) { subscriber.complete(); } }; var outerNext = function(value) { return active < concurrent ? doInnerSub(value) : buffer.push(value); }; var doInnerSub = function(value) { active++; var innerComplete = false; innerFrom(project(value, index++)).subscribe(createOperatorSubscriber(subscriber, function(innerValue) { { subscriber.next(innerValue); } }, function() { innerComplete = true; }, void 0, function() { if (innerComplete) { try { active--; var _loop_1 = function() { var bufferedValue = buffer.shift(); if (innerSubScheduler) ; else { doInnerSub(bufferedValue); } }; while (buffer.length && active < concurrent) { _loop_1(); } checkComplete(); } catch (err) { subscriber.error(err); } } })); }; source.subscribe(createOperatorSubscriber(subscriber, outerNext, function() { isComplete = true; checkComplete(); })); return function() { }; } function mergeMap(project, resultSelector, concurrent) { if (concurrent === void 0) { concurrent = Infinity; } if (isFunction(resultSelector)) { return mergeMap(function(a, i) { return map(function(b, ii) { return resultSelector(a, b, i, ii); })(innerFrom(project(a, i))); }, concurrent); } else if (typeof resultSelector === "number") { concurrent = resultSelector; } return operate(function(source, subscriber) { return mergeInternals(source, subscriber, project, concurrent); }); } function mergeAll(concurrent) { return mergeMap(identity, concurrent); } function concatAll() { return mergeAll(1); } function concatMap(project, resultSelector) { return isFunction(resultSelector) ? mergeMap(project, resultSelector, 1) : mergeMap(project, 1); } class BasicQuestionHandler { constructor() { this._document = document; this._window = _unsafeWindow; this.questions = []; this.correctNum = 0; this.setting = useSettingStore(); this.log = useLogStore(); this.questionStore = useQuestionStore(); this.parseHtml = () => { throw new Error("请使用继承类的重写方法"); }; this.fillQuestion = (question) => { throw new Error("请使用继承类的重写方法"); }; this.questionType = { "A1型选择题": "0", "X型选择题": "0", "单选题": "0", "单项选择题": "0", "单项选择": "0", "A1型题": "0", "多选题": "1", "多项选择题": "1", "多项选择": "1", "X型题": "1", "填空题": "2", "判断题": "3", "是非题": "3", "简答题": "4", "名词解释": "5", "论述题": "6", "计算题": "7", "连线题": "11", "匹配题": "11", "完型填空": "14", "完形填空题": "14", "阅读理解": "15", "单选": "0", "A1型": "0", "多选": "1", "填空": "2", "判断": "3", "简答": "4", "论述": "6", "计算": "7", "思政元素": "2" }; this.questionTypeKeywordRules = [ { keywords: ["单项选择", "单选"], type: "0" }, { keywords: ["多项选择", "多选"], type: "1" }, { keywords: ["填空", "思政元素"], type: "2" }, { keywords: ["判断", "是非"], type: "3" }, { keywords: ["简答"], type: "4" }, { keywords: ["名词解释"], type: "5" }, { keywords: ["论述"], type: "6" }, { keywords: ["计算"], type: "7" }, { keywords: ["连线", "匹配"], type: "11" }, { keywords: ["完型填空", "完形填空"], type: "14" }, { keywords: ["阅读理解"], type: "15" } ]; this.removeHtml = (htmlStr, replacen = true) => { const textArea = document.createElement("textarea"); textArea.innerHTML = htmlStr; htmlStr = textArea.value; htmlStr = htmlStr.replace(/[\t\r\xa0]/g, " "); htmlStr = htmlStr.replace(/[\u2000-\u200a]/g, " "); htmlStr = htmlStr.replace(//g, "\n"); if (replacen) { htmlStr = htmlStr.replace(/<(\/)?(p|div).*?>/g, "\n"); } htmlStr = htmlStr.replace(/ {2,}/g, " "); htmlStr = htmlStr.replace(/\n{2,}/g, "\n"); htmlStr = htmlStr.replace(//g, "
");
        htmlStr = htmlStr.replace(/<\/xmp>/g, "
"); htmlStr = DOMPurify.sanitize(htmlStr, { ALLOWED_TAGS: ["img", "br", "sub", "sup"], ALLOWED_ATTR: ["src", "href"], ALLOW_DATA_ATTR: false, KEEP_CONTENT: true }); htmlStr = htmlStr.replace(/<((?!img|sub|sup|br)[^>]+)>/g, "").replace(//g, "\n"); let imgReg = //g; let imgArr = htmlStr.match(imgReg); if (imgArr) { htmlStr = htmlStr.replace(new RegExp("]*)(?", "g"), ""); imgArr.forEach((item) => { let src = item.match(/src="(.*?)"/); if (src) { if (src[1].indexOf("http") == -1 && !src[1].includes("data:image")) { if (src[1].startsWith("/")) { htmlStr = htmlStr.replace(src[1], location.origin + src[1]); } else { const fullURL = new URL(src[1], document.baseURI).href; htmlStr = htmlStr.replace(src[1], fullURL); } } } }); } return htmlStr.trim(); }; this.removeAllTags = (htmlStr) => { htmlStr = DOMPurify.sanitize(htmlStr, { ALLOWED_TAGS: ["img"], ALLOWED_ATTR: ["src", "href"], ALLOW_DATA_ATTR: false, KEEP_CONTENT: true }); let imgReg = //g; let imgArr = htmlStr.match(imgReg); if (imgArr) { htmlStr = htmlStr.replace(new RegExp("]*)(?", "g"), ""); imgArr.forEach((item) => { let src = item.match(/src="(.*?)"/); if (src) { if (src[1].indexOf("http") == -1 && !src[1].includes("data:image")) { if (src[1].startsWith("/")) { htmlStr = htmlStr.replace(src[1], location.origin + src[1]); } else { const fullURL = new URL(src[1], document.baseURI).href; htmlStr = htmlStr.replace(src[1], fullURL); } } } }); } else { htmlStr = htmlStr.replace(/[\p{P}\s]/gu, "").toLowerCase(); } htmlStr = htmlStr.replace(/ |nbsp/g, ""); return htmlStr.trim(); }; this.clean = (str) => { return str.replace(/^【.*?】\s*/, "").replace(/\s*(\d+\.\d+分)$/, ""); }; this.normalizeQuestionTypeText = (text = "") => { let normalizedText = String(text).normalize("NFKC").trim(); normalizedText = normalizedText.replace(/<[^>]+>/g, "").replace(/[【】\[\]()()]/g, "").replace(/[::]/g, "").replace(/\s+/g, "").replace(/第\s*\d+\s*题/g, "").replace(/共\s*\d+(?:\.\d+)?\s*分/g, "").replace(/本题/g, "").replace(/题型/g, "").trim(); const canonicalQuestionTypeMap = { "单项选择题": "单项选择", "单项选择": "单项选择", "单选题": "单项选择", "单选": "单项选择", "多项选择题": "多项选择", "多项选择": "多项选择", "多选题": "多项选择", "多选": "多项选择", "判断题": "判断", "判断": "判断", "是非题": "判断", "是非": "判断", "填空题": "填空", "填空": "填空", "简答题": "简答", "简答": "简答", "论述题": "论述", "论述": "论述", "计算题": "计算", "计算": "计算", "连线题": "连线", "连线": "连线", "匹配题": "匹配", "匹配": "匹配", "完型填空": "完形填空", "完形填空题": "完形填空", "完形填空": "完形填空", "阅读理解": "阅读理解" }; return canonicalQuestionTypeMap[normalizedText] || normalizedText; }; this.resolveQuestionType = (text = "") => { const normalizedText = this.normalizeQuestionTypeText(text); const exactQuestionTypeMap = Object.entries(this.questionType).reduce((result, [key, value]) => { result[this.normalizeQuestionTypeText(key)] = value; return result; }, {}); if (exactQuestionTypeMap[normalizedText]) { return exactQuestionTypeMap[normalizedText]; } const matchedRule = this.questionTypeKeywordRules.find( (rule) => rule.keywords.some((keyword) => normalizedText.includes(keyword)) ); return (matchedRule == null ? void 0 : matchedRule.type) || "999"; }; this.similarCharMap = { "ɑ": "α", // 国际音标 ɑ (U+0251) → 希腊字母 α (U+03B1) "Α": "A", // 希腊大写 Α → 拉丁大写 A "Β": "B", // 希腊大写 Β → 拉丁大写 B "Ε": "E", // 希腊大写 Ε → 拉丁大写 E "Ζ": "Z", // 希腊大写 Ζ → 拉丁大写 Z "Η": "H", // 希腊大写 Η → 拉丁大写 H "Ι": "I", // 希腊大写 Ι → 拉丁大写 I "Κ": "K", // 希腊大写 Κ → 拉丁大写 K "Μ": "M", // 希腊大写 Μ → 拉丁大写 M "Ν": "N", // 希腊大写 Ν → 拉丁大写 N "Ο": "O", // 希腊大写 Ο → 拉丁大写 O "Ρ": "P", // 希腊大写 Ρ → 拉丁大写 P "Τ": "T", // 希腊大写 Τ → 拉丁大写 T "Υ": "Y", // 希腊大写 Υ → 拉丁大写 Y "Χ": "X", // 希腊大写 Χ → 拉丁大写 X "ο": "o", // 希腊小写 ο → 拉丁小写 o "ν": "v" // 希腊小写 ν → 拉丁小写 v (有时相似) }; this.clearMark = (str) => { let result = str; for (const [similar, standard] of Object.entries(this.similarCharMap)) { result = result.replace(new RegExp(similar, "g"), standard); } return result.replace(/[\p{P}\p{S}]/gu, ""); }; } } let QuestionHandler$1 = class QuestionHandler extends BasicQuestionHandler { constructor(type, iframe) { super(); __publicField(this, "type"); __publicField(this, "AI_ALLOW_QUESTION_TYPE", ["0", "1", "2", "3", "4", "5"]); __publicField(this, "answerSeparators", ["===", "#", "---", "###", "|", ";", ";"]); __publicField(this, "init", async () => { var _that = this; this.parseHtml(); if (this.questions.length) { this.log.insertLog( `成功解析到${this.questions.length}个题目,开始答题..` ); setTimeout(() => { _that.setting.tabIndex = "2"; }, 1e3); const { answerMode } = this.setting.config.basicConfig; const mode = answerMode.value; for (const [index, question] of this.questions.entries()) { this.questionStore.currentQuestionIndex = index; if (mode !== "ai") { const answerData = await getAccurateAnswer(question); if (answerData.code === 1) { question.answer = { code: 1, answer: answerData.data.answer }; this.setting.userInfo.remainCount = formatLocalRemainCount(answerData.data.remainCount); saveAnswerRecord({ question, answer: answerData.data.answer, source: "accurate" }); const fillSuccess = this.fillQuestion(question); if (!fillSuccess) { question.answer.code = 0; if (mode === "questionBank") { continue; } } else { this.correctNum += 1; continue; } } if (mode === "questionBank" || answerData.code !== -1004) { question.answer = { code: answerData.code, answer: [] }; continue; } } if (this.AI_ALLOW_QUESTION_TYPE.includes(question.type)) { const answerData = await getAIAnswer(question); if (answerData.code === 1) { question.answer = { code: 2, answer: answerData.data.answer }; this.setting.userInfo.remainCount = formatLocalRemainCount(answerData.data.remainCount); saveAnswerRecord({ question, answer: answerData.data.answer, source: "ai" }); const fillSuccess = this.fillQuestion(question); if (!fillSuccess) { question.answer.code = 0; continue; } this.correctNum += 1; } else { question.answer = { code: answerData.code, answer: [] }; } } else { question.answer = { code: -1, answer: ["该题型不支持AI答题"] }; } } this.questionStore.currentQuestionIndex += 1; } else this.log.insertLog( "没有解析到题目,请检查是否进入正确页面...", "warning" ); setTimeout(() => { _that.setting.tabIndex = "1"; }, 2e3); if (this.questions.length === 0) return Promise.resolve("0"); return Promise.resolve( (this.correctNum / this.questions.length * 100).toFixed(1) ); }); __publicField(this, "parseHtml", () => { this.questionStore.currentQuestionIndex = 0; if (!this._document) return []; if (["zj"].includes(this.type)) { const questionElements = this._document.querySelectorAll(".TiMu"); this.addQuestions(questionElements); } else if (["zy", "ks"].includes(this.type)) { const questionElements = this._document.querySelectorAll(".questionLi"); this.addQuestions(questionElements); } this.questionStore.questionList = this.questions; }); __publicField(this, "getChoiceOptionContainer", (optionElement) => { if (!optionElement) return null; if (["ks"].includes(this.type)) { return optionElement.closest(".answerBg") || optionElement; } return optionElement; }); __publicField(this, "getCxQuestionTypeFromCode", (code) => { if (Number.isNaN(code)) return ""; if (code === 0) return "0"; if (code === 1) return "1"; if (code === 3) return "3"; if ([2, 4, 5, 6, 7, 8, 9, 10].includes(code)) return "2"; if (code === 11) return "11"; if (code === 14) return "14"; if (code === 15) return "15"; return ""; }); __publicField(this, "getCxQuestionTypeInput", (element) => { if (!element) return null; if (["ks"].includes(this.type)) { return element.querySelector('input[name^="type"]'); } return element.querySelector('input[id^="answertype"]'); }); __publicField(this, "resolveCxQuestionType", (element, questionTypeText) => { const typeInput = this.getCxQuestionTypeInput(element); const typeFromCode = this.getCxQuestionTypeFromCode( Number.parseInt((typeInput == null ? void 0 : typeInput.value) || "", 10) ); if (typeFromCode) return typeFromCode; return this.resolveQuestionType(questionTypeText); }); __publicField(this, "isChoiceOptionSelected", (optionElement) => { const optionContainer = this.getChoiceOptionContainer(optionElement); if (!optionContainer) return false; if (["zj", "zy"].includes(this.type)) { return optionContainer.getAttribute("aria-checked") === "true"; } if (["ks"].includes(this.type)) { return Boolean( optionContainer.querySelector(".check_answer, .check_answer_dx") ); } return false; }); __publicField(this, "selectChoiceOption", (optionElement) => { const optionContainer = this.getChoiceOptionContainer(optionElement); if (!optionContainer) return false; if (this.isChoiceOptionSelected(optionContainer)) return true; optionContainer == null ? void 0 : optionContainer.click(); return true; }); __publicField(this, "splitAnswer", (answer = "") => { const normalizedAnswer = String(answer || "").trim(); if (!normalizedAnswer) return []; for (const separator of this.answerSeparators) { if (normalizedAnswer.includes(separator)) { return normalizedAnswer.split(separator).map((item) => item.trim()).filter(Boolean); } } return [normalizedAnswer]; }); __publicField(this, "buildAnswerGroups", (answers = []) => { if (!Array.isArray(answers)) return [this.splitAnswer(answers)]; if (answers.length === 1) return [this.splitAnswer(answers[0])]; const normalizedAnswers = answers.map((answer) => String(answer || "").trim()).filter(Boolean); const groups = [normalizedAnswers]; normalizedAnswers.forEach((answer) => { const splitAnswers = this.splitAnswer(answer); if (splitAnswers.length > 1) groups.push(splitAnswers); }); return groups; }); __publicField(this, "findElementByAttr", (root, selector, attrName, attrValue) => { return [...root.querySelectorAll(selector)].find( (element) => element.getAttribute(attrName) === attrValue ); }); __publicField(this, "fillLineQuestion", (question) => { const answerGroups = this.buildAnswerGroups(question.answer.answer); const selectBoxes = question.element.querySelectorAll( ".line_answer_ct .selectBox, .thirdUlList .dept_select" ); const answerInputs = question.element.querySelectorAll( ".line_answer input[name^='answer']" ); if (!selectBoxes.length) return false; for (const answers of answerGroups) { if (!answers.length) continue; if (answerInputs.length && answers.length !== selectBoxes.length) continue; if (!answerInputs.length && answers.length !== selectBoxes.length) continue; let filledCount = 0; selectBoxes.forEach((box, index) => { var _a, _b; const answer = answers[index]; if (!answer) return; const optionItem = this.findElementByAttr(box, "li[data]", "data", answer); const optionLink = optionItem == null ? void 0 : optionItem.querySelector("a"); if (optionLink) { optionLink.click(); filledCount += 1; return; } const selectOption = this.findElementByAttr( box, "option[value]", "value", answer ); if (!selectOption) return; (_a = box.querySelector("option[selected]")) == null ? void 0 : _a.removeAttribute("selected"); selectOption.setAttribute("selected", ""); box.value = answer; const selectedText = (_b = box.parentElement) == null ? void 0 : _b.querySelector(".chosen-single span"); if (selectedText) selectedText.textContent = answer; filledCount += 1; }); if (filledCount === selectBoxes.length) return true; } return false; }); __publicField(this, "fillCompositeChoiceQuestion", (question, containerSelector) => { const answerGroups = this.buildAnswerGroups(question.answer.answer); const containers = question.element.querySelectorAll(containerSelector); if (!containers.length) return false; for (const answers of answerGroups) { if (!answers.length) continue; let filledCount = 0; answers.forEach((answer, index) => { const container = containers[index]; if (!container || !answer) return; const option = this.findElementByAttr( container, "span.saveSingleSelect[data]", "data", answer ); if (!option) return; option.click(); filledCount += 1; }); if (filledCount > 0) return true; } return false; }); __publicField(this, "fillQuestion", (question) => { var _a; if (!this._window) return false; let filled = false; if (question.type === "0" || question.type === "1") { const optionKeys = Object.keys(question.options); question.answer.answer.forEach((answer) => { const clearAnswer = this.removeHtml(answer); let isSelected = false; // 字母答案映射:A=第1个选项(兼容数组项 ["A","B","D"] 与紧凑串 "ABD") const letterText = String(clearAnswer).trim(); const isCompactLetters = /^[A-Za-z]{2,10}$/.test(letterText) && !optionKeys.some((k) => this.clearMark(k) === this.clearMark(letterText)); const lettersToMap = /^[A-Za-z]$/.test(letterText) ? [letterText.toUpperCase()] : isCompactLetters ? letterText.toUpperCase().split("") : []; for (const c of lettersToMap) { const idx = c.charCodeAt(0) - 65; if (idx >= 0 && idx < optionKeys.length) { if (this.selectChoiceOption(question.options[optionKeys[idx]])) isSelected = true; } } if (!isSelected) { for (const key in question.options) { if (key === clearAnswer) { isSelected = this.selectChoiceOption(question.options[key]); break; } } } if (!isSelected) { for (const key in question.options) { const clearKey = this.clearMark(key); if (clearKey === this.clearMark(clearAnswer)) { isSelected = this.selectChoiceOption(question.options[key]); break; } } } if (isSelected) filled = true; }); } else if (question.type === "2") { const textareaElements = question.element.querySelectorAll("textarea"); if (textareaElements.length === 0) return false; textareaElements.forEach((textareaElement, index) => { try { const ueditor = this._window.UE.getEditor(textareaElement.name); ueditor.setContent(question.answer.answer[index]); filled = true; } catch (e) { textareaElement.value = ""; } }); } else if (question.type === "3") { let answer = "true"; if (question.answer.answer[0].match( /(^|,)(正确|是|对|√|T|ri|right|true)(,|$)/ )) { answer = "true"; } else if (question.answer.answer[0].match( /(^|,)(错误|否|错|×|F|wr|wrong|false)(,|$)/ )) { answer = "false"; } const trueOrFalse = { true: "对", false: "错" }; for (const key in question.options) { if (["zj", "zy"].includes(this.type)) { if ((_a = question.options[key].getAttribute("aria-label")) == null ? void 0 : _a.includes(`${trueOrFalse[answer]}选择`)) { filled = this.selectChoiceOption(question.options[key]); break; } } else if (["ks"].includes(this.type)) { const optionElement = question.options[key].querySelector( `span[data='${answer}']` ); if (!optionElement) continue; filled = this.selectChoiceOption(optionElement); break; } } } else if (question.type === "4" || question.type === "5" || question.type === "6") { const textareaElement = question.element.querySelector("textarea"); if (!textareaElement) return false; const ueditor = this._window.UE.getEditor(textareaElement.name); ueditor.setContent(question.answer.answer[0]); filled = true; } else if (question.type === "11") { filled = this.fillLineQuestion(question); } else if (question.type === "14") { filled = this.fillCompositeChoiceQuestion(question, ".filling_answer"); } else if (question.type === "15") { filled = this.fillCompositeChoiceQuestion(question, ".reading_answer"); } else { this.log.insertLog( `异常题型:type:${question.type},请联系客服..`, "warning" ); this.setting.tabIndex = "1"; return false; } return filled; }); __publicField(this, "addQuestions", (questionElements) => { questionElements.forEach((element) => { var _a, _b, _c, _d; let questionTitle = ""; let questionTypeText = ""; let optionElements; let optionsObject = {}; let optionTexts = []; if (["zj"].includes(this.type)) { questionTitle = this.removeHtml( ((_a = element.querySelector(".fontLabel")) == null ? void 0 : _a.innerHTML) || "" ); questionTypeText = this.removeHtml( ((_b = element.querySelector(".newZy_TItle")) == null ? void 0 : _b.innerHTML) || "" ); optionElements = element.querySelectorAll('[class*="before-after"]'); [optionsObject, optionTexts] = this.extractOptions( optionElements, ".fl.after" ); } else if (["zy", "ks"].includes(this.type)) { const titleElement = ((_c = element == null ? void 0 : element.querySelector("h3")) == null ? void 0 : _c.innerHTML) || ""; const colorShallowElement = ((_d = element.querySelector(".colorShallow")) == null ? void 0 : _d.outerHTML) || ""; if (["zy"].includes(this.type)) { questionTypeText = (element == null ? void 0 : element.getAttribute("typename")) || ""; } else if (["ks"].includes(this.type)) { questionTypeText = this.removeHtml(colorShallowElement).slice(1, 4) || ""; } questionTitle = this.removeHtml( titleElement.split(colorShallowElement || "")[1] || "" ); optionElements = element.querySelectorAll(".answerBg"); [optionsObject, optionTexts] = this.extractOptions( optionElements, ".answer_p" ); } const resolvedQuestionType = this.resolveCxQuestionType( element, questionTypeText ); if (["14", "15"].includes(resolvedQuestionType)) { [optionsObject, optionTexts] = this.extractCompositeChoiceOptions( element, resolvedQuestionType ); } if (resolvedQuestionType === "999") { this.log.insertLog( `未识别题型:${questionTypeText || "空题型"},请反馈给开发者补充规则..`, "warning" ); } this.questions.push({ element, type: resolvedQuestionType, title: this.clean(questionTitle), optionsText: optionTexts, options: optionsObject, answer: { code: -1, answer: [] }, workType: this.type, refer: this._window.location.href }); }); }); this.type = type; if (iframe) { this._document = iframe.contentDocument; this._window = iframe.contentWindow; } } extractOptions(optionElements, optionSelector) { const optionsObject = {}; const optionTexts = []; optionElements.forEach((optionElement) => { var _a; const optionTextContent = this.removeHtml( ((_a = optionElement.querySelector(optionSelector)) == null ? void 0 : _a.innerHTML) || "" ); optionsObject[optionTextContent] = optionElement; optionTexts.push(optionTextContent); }); return [optionsObject, optionTexts]; } extractCompositeChoiceOptions(element, questionType) { const optionsObject = {}; const optionTexts = []; const containerSelector = questionType === "14" ? ".filling_answer" : ".reading_answer"; const optionElements = element.querySelectorAll( `${containerSelector} span.saveSingleSelect` ); optionElements.forEach((optionElement) => { const optionTextContent = this.removeHtml(optionElement.innerHTML || ""); if (!optionTextContent) return; optionsObject[optionTextContent] = optionElement; optionTexts.push(optionTextContent); }); return [optionsObject, optionTexts]; } }; class IframeUtils { static getIframes(element) { return Array.from(element.querySelectorAll("iframe")); } static getAllNestedIframes(element) { const iframes = IframeUtils.getIframes(element); if (iframes.length === 0) { return rxjs.of([]); } else { return rxjs.from(iframes).pipe( mergeMap( (iframe) => new rxjs.Observable((subscriber) => { if (iframe.contentDocument) { const iframeDocument = iframe.contentDocument; const nestedIframes = IframeUtils.getAllNestedIframes( iframeDocument.documentElement ); nestedIframes.subscribe((nestedIframes2) => { subscriber.next([iframe, ...nestedIframes2]); subscriber.complete(); }); } else { subscriber.next([]); subscriber.complete(); } }) ), concatAll(), rxjs.toArray() ); } } } const hookVedio = (video, playbackRate = 2) => { const log = useLogStore(); function applyHookToVideo(video2) { try { video2.playbackRate = playbackRate; } catch (e) { log.insertLog(`[hook] 设置播放速度失败: ${e.message}`); } try { Object.defineProperty(video2, "playbackRate", { configurable: true, get: () => playbackRate, set: () => { } // 阻止外部修改 }); } catch (e) { log.insertLog(`[hook] 无法锁定 playbackRate: ${e.message}`); } } applyHookToVideo(video); }; const applyVideoRateToPage = (rate) => { if (!rate || isNaN(rate)) return; const applyOnDoc = (doc) => { if (!doc) return; doc.querySelectorAll("video, audio").forEach((el) => { try { delete el.playbackRate; } catch (e) { } try { el.playbackRate = rate; } catch (e) { } try { Object.defineProperty(el, "playbackRate", { configurable: true, get: () => rate, set: () => { } }); } catch (e) { } }); }; const collectFrames = (doc, depth) => { if (depth > 3 || !doc) return; doc.querySelectorAll("iframe").forEach((frame) => { try { if (frame.contentDocument) { applyOnDoc(frame.contentDocument); collectFrames(frame.contentDocument, depth + 1); } } catch (e) { } }); }; applyOnDoc(document); collectFrames(document, 0); }; const FACE_CHECK_INTERVAL_SECONDS = 3; const FACE_RECOGNITION_TEXTS = [ "人脸信息采集", "采集人脸信息", "请使用手机APP采集人脸信息后继续学习", "请扫描下方二维码进入" ]; const GENERIC_FACE_RECOGNITION_TEXTS = [ "人脸识别", "继续学习" ]; let activeWaitPromise = null; const getTopDocument = () => { var _a, _b; try { return ((_a = window.top) == null ? void 0 : _a.document) || document; } catch { try { if (typeof unsafeWindow !== "undefined") { return ((_b = unsafeWindow.top) == null ? void 0 : _b.document) || document; } } catch { return document; } } return document; }; const isVisible$1 = (element) => { var _a; if (!element) return false; const style = (_a = element.ownerDocument.defaultView) == null ? void 0 : _a.getComputedStyle(element); if (style && (style.display === "none" || style.visibility === "hidden")) { return false; } return element.offsetParent !== null || element.getClientRects().length > 0; }; const getSearchDocuments = (extraDocuments = []) => { const docs = []; const queue = [getTopDocument(), document, ...extraDocuments].filter(Boolean); const seen = /* @__PURE__ */ new Set(); while (queue.length) { const doc = queue.shift(); if (!doc || seen.has(doc)) continue; seen.add(doc); docs.push(doc); try { Array.from(doc.querySelectorAll("iframe")).forEach((iframe) => { if (iframe.contentDocument) queue.push(iframe.contentDocument); }); } catch { } } return docs; }; const hasLegacyFaceRecognition = (doc) => { return Array.from(doc.querySelectorAll("#fcqrimg")).some((element) => { return Boolean(element.getAttribute("src")) && isVisible$1(element); }); }; const hasNewFaceRecognition = (doc) => { return Array.from(doc.querySelectorAll(".chapterVideoFaceMaskDiv")).some(isVisible$1); }; const hasFaceRecognitionText = (doc) => { var _a, _b; const bodyText = ((_a = doc.body) == null ? void 0 : _a.innerText) || ((_b = doc.body) == null ? void 0 : _b.textContent) || ""; if (FACE_RECOGNITION_TEXTS.some((keyword) => bodyText.includes(keyword))) { return true; } const visibleElements = Array.from( doc.querySelectorAll("div, section, article, body") ).filter(isVisible$1); return visibleElements.some((element) => { const text = element.innerText || element.textContent || ""; const hasFaceText = GENERIC_FACE_RECOGNITION_TEXTS.some( (keyword) => text.includes(keyword) ); const hasQRCode = Boolean(element.querySelector("canvas, img, svg")); return hasFaceText && (hasQRCode || text.includes("人脸")); }); }; const hasCxFaceRecognition = (extraDocuments = []) => { return getSearchDocuments(extraDocuments).some((doc) => { return hasLegacyFaceRecognition(doc) || hasNewFaceRecognition(doc) || hasFaceRecognitionText(doc); }); }; const waitForCxFaceRecognition = async ({ log, setting, messageStore, extraDocuments = [] } = {}) => { if (!hasCxFaceRecognition(extraDocuments)) return; if (activeWaitPromise) return activeWaitPromise; activeWaitPromise = (async () => { var _a, _b, _c; const message2 = "检测到人脸识别,请手动完成后脚本会继续运行。"; log == null ? void 0 : log.insertLog(message2, "warning", 4); if ((_c = (_b = (_a = setting == null ? void 0 : setting.config) == null ? void 0 : _a.cxConfig) == null ? void 0 : _b.faceRecognitionNotify) == null ? void 0 : _c.value) { messageStore == null ? void 0 : messageStore.insertMessage(message2, "warning"); } while (hasCxFaceRecognition(extraDocuments)) { await sleep(FACE_CHECK_INTERVAL_SECONDS); } log == null ? void 0 : log.insertLog("人脸识别已完成,继续处理当前任务。"); })(); try { return await activeWaitPromise; } finally { activeWaitPromise = null; } }; const cxChapterLogic = () => { const log = useLogStore(); const init = () => { const currentUrl = window.location.href; if (!currentUrl.includes("&mooc2=1")) { window.location.href = currentUrl + "&mooc2=1"; } }; log.insertLog("进入学习页面,脚本开始工作中.."); const setting = useSettingStore(); const questionStore = useQuestionStore(); const messageStore = useMessageStore(); const setupFaceRecognitionWatcher = () => { setInterval(async () => { await waitForCxFaceRecognition({ log, setting, messageStore }); }, 3e3); }; const processIframeTask = () => { const documentElement = document.documentElement; const iframe = documentElement.querySelector("iframe"); if (!iframe) { console.warn("No iframe found."); return; } watchIframe(documentElement); iframe.addEventListener("load", function() { watchIframe(documentElement); }); }; const waitIframeLoad = async (iframe) => { return new Promise((resolve) => { const intervalId = setInterval(async () => { var _a; if (iframe.contentDocument && ((_a = iframe.contentDocument) == null ? void 0 : _a.readyState) == "complete") { resolve(); clearInterval(intervalId); } }, 500); }); }; const hideCxWorkConfirmPopup = () => { var _a, _b, _c; try { const topWindow = typeof unsafeWindow !== "undefined" ? unsafeWindow.top : window.top; const topDocument = (topWindow == null ? void 0 : topWindow.document) || document; (_c = (_b = (_a = topWindow == null ? void 0 : topWindow.$) == null ? void 0 : _a.call(topWindow, "#workpop")) == null ? void 0 : _b.hide) == null ? void 0 : _c.call(_b); const workPopup = topDocument.querySelector("#workpop"); if (workPopup) { workPopup.style.display = "none"; } } catch (error) { console.warn("hide work popup failed.", error); } }; const processWork = async (iframe, iframeDocument, iframeWindow) => { var _a; const workName = ((_a = iframeDocument.documentElement.querySelector( "#RightCon > div.radiusBG > div > div.ceyan_name>h3" )) == null ? void 0 : _a.innerText) || "未知测验"; log.insertLog(`发现测验:${workName},正在解析..`); return new Promise(async (resolve) => { if (!iframeDocument) return resolve(); if (iframeDocument.documentElement.innerText.includes("已完成") || iframeDocument.documentElement.innerText.includes("待批阅")) { log.insertLog("测验已经完成,跳转中.."); return resolve(); } decrypt(iframeDocument); if (!setting.config.basicConfig.autoAnswer.value) { log.insertLog( `自动答题已关闭,请前往设置模块中更改` ); return resolve(); } questionStore.accuracy = -1; const accuracy = await new QuestionHandler$1("zj", iframe).init(); questionStore.accuracy = accuracy; iframeWindow.alert = () => { }; if (setting.config.basicConfig.autoSubmit.value) { log.insertLog("自动提交已开启,提交中..."); if (accuracy < Number(setting.config.basicConfig.accuracy.value)) { log.insertLog( `正确率小于${setting.config.basicConfig.accuracy.value}%,暂存,如需调整正确率请前往设置模块中更改`, "warning", 4 ); await iframeWindow.noSubmit(); } else { log.insertLog( `正确率大于${setting.config.basicConfig.accuracy.value}%,提交中...` ); await iframeWindow.btnBlueSubmit(); await sleep(setting.config.basicConfig.reqIntervalTime.value / 2); await iframeWindow.submitCheckTimes(); hideCxWorkConfirmPopup(); log.insertLog("提交成功"); } } else { log.insertLog( "自动提交未开启,已暂存,可前往设置模块中更改", "warning", 4 ); await iframeWindow.noSubmit(); } return resolve(); }); }; const setupInterceptor = () => { let currentUrl = window.location.href; setInterval(() => { if (currentUrl !== window.location.href) { currentUrl = window.location.href; processIframeTask(); } }, 2e3); }; const changeToNextChapter = async (documentElement) => { var _a, _b; const nextBtn = documentElement.querySelector("#prevNextFocusNext"); if (!nextBtn || nextBtn.style.display === "none") { log.insertLog(`已是最后一个章节,无法跳转`); return; } await sleep(setting.config.basicConfig.reqIntervalTime.value); try { const topWindow = (unsafeWindow == null ? void 0 : unsafeWindow.top) || window.top; const topDocument = (topWindow == null ? void 0 : topWindow.document) || document; const curCourseId = topDocument.querySelector("#curCourseId"); const curChapterId = topDocument.querySelector("#curChapterId"); const curClazzId = topDocument.querySelector("#curClazzId"); const taskTabs = topDocument.querySelectorAll("#prev_tab .prev_ul li"); if (((_a = topWindow == null ? void 0 : topWindow.PCount) == null ? void 0 : _a.next) && (curCourseId == null ? void 0 : curCourseId.value) && (curChapterId == null ? void 0 : curChapterId.value) && (curClazzId == null ? void 0 : curClazzId.value)) { topWindow._preChapterId = curChapterId.value; (_b = topDocument.querySelector(".posCatalog_active")) == null ? void 0 : _b.scrollIntoView({ behavior: "smooth", block: "center" }); await sleep(0.2); topWindow.PCount.next( taskTabs.length.toString(), curChapterId.value, curCourseId.value, curClazzId.value, "" ); return; } } catch (error) { console.warn("PCount.next failed, fallback to nextChapter button.", error); } const nextChapterBtn = document.querySelector( ".jb_btn.jb_btn_92.fr.fs14.nextChapter" ); if (nextChapterBtn) { nextChapterBtn.click(); } else { log.insertLog(`未找到下一章节按钮,请尝试手动切换`, "warning", 4); } }; let currentWatchIframeTaskId = 0; const watchIframe = (documentElement) => { const thisTaskId = ++currentWatchIframeTaskId; IframeUtils.getAllNestedIframes(documentElement).subscribe((allIframes) => { rxjs.from(allIframes).pipe(concatMap((iframe) => processIframe(iframe))).subscribe({ complete: async () => { var _a, _b; const chapterName = ((_a = documentElement.querySelector( ".posCatalog_select.posCatalog_active>.posCatalog_name" )) == null ? void 0 : _a.innerText) || "未知章节"; const currentTaskName = ((_b = document.querySelector(".prev_ul > li.active .spanText")) == null ? void 0 : _b.textContent.trim()) || ""; if (thisTaskId === currentWatchIframeTaskId) { log.insertLog(`任务点 ${chapterName}-${currentTaskName} 已完成,即将跳转`); if (setting.config.basicConfig.autoChangeChapter.value) { await changeToNextChapter(documentElement); } else { log.insertLog( `自动切换章节未开启,前往设置模块中更改`, "warning", 4 ); } } } }); }); }; const processPpt = (iframeWindow) => { return new Promise((resolve) => { const iframe = iframeWindow.document.querySelector("#panView"); log.insertLog("发现一个PPT,正在解析..."); const startScrolling = async () => { const pptWindow = iframe.contentWindow; const scrollHeight = pptWindow.document.body.scrollHeight; await pptWindow.scrollTo({ top: scrollHeight, behavior: "smooth" }); log.insertLog("阅读完成"); resolve(); }; const checkReadyState = () => { if (iframe.contentWindow.document.readyState === "complete") { startScrolling(); } else { setTimeout(checkReadyState, 100); } }; checkReadyState(); }); }; const processBook = async (iframeWindow) => { log.insertLog("发现一个电子书,正在解析"); unsafeWindow.top.onchangepage(iframeWindow.getFrameAttr("end")); log.insertLog("阅读完成"); return Promise.resolve(); }; const setupVideoQuizHandler = (iframeDocument) => { const intervalId = setInterval(() => { const quizContainer = iframeDocument.querySelector(".ans-timelineobjects"); if (!quizContainer || quizContainer.offsetParent === null) return; const submitBtn = iframeDocument.querySelector("#videoquiz-submit"); if (!submitBtn || submitBtn.style.display === "none" || submitBtn.offsetParent === null) return; quizContainer.style.display = "none"; const quizBg = iframeDocument.querySelector(".ans-timelineobjectsbg"); if (quizBg) quizBg.style.display = "none"; log.insertLog("检测到视频弹题,已跳过。"); const video = iframeDocument.querySelector("video"); if (video) video.play(); }, 2e3); return intervalId; }; const isVideoPlaybackRateDisabled = (iframeDocument) => { const menuItems = iframeDocument.querySelectorAll( ".vjs-playback-rate .vjs-menu-content .vjs-menu-item" ); return menuItems.length === 0; }; const processMedia = async (mediaType, iframeDocument, iframe) => { return new Promise((resolve) => { const userRate = setting.config.basicConfig.videoPlayrate.value; const isRateDisabled = mediaType === "video" && isVideoPlaybackRateDisabled(iframeDocument); const finalRate = isRateDisabled ? 1 : userRate; log.insertLog(`发现一个${mediaType},正在播放${mediaType}..`); let isExecuted = false; let isSpeedRestored = false; let hasPlaybackEnded = false; let isResolved = false; let observer = null; let quizHandlerId = null; if (isRateDisabled && userRate > 1) { log.insertLog( "视频禁止倍速,已临时调整为1倍速,强制倍速回导致任务点无法完成。" ); } log.insertLog(`播放成功,当前视频播放倍速为${finalRate}x.`); const intervalId = setInterval(async () => { const mediaElement = iframeDocument.documentElement.querySelector(mediaType); if (mediaElement && !isExecuted) { isExecuted = true; await waitForCxFaceRecognition({ log, setting, messageStore, extraDocuments: [iframeDocument] }); await mediaElement.pause(); mediaElement.muted = true; await mediaElement.play(); hookVedio(mediaElement, finalRate); if (mediaType === "video") { quizHandlerId = setupVideoQuizHandler(iframeDocument); } const listener = async () => { if (hasPlaybackEnded || isResolved) return; await waitForCxFaceRecognition({ log, setting, messageStore, extraDocuments: [iframeDocument] }); await sleep(1); await mediaElement.play(); }; mediaElement.addEventListener("pause", listener); const timeUpdateHandler = () => { if (!isSpeedRestored && mediaElement.duration - mediaElement.currentTime < 10) { isSpeedRestored = true; delete mediaElement.playbackRate; hookVedio(mediaElement, 1); } }; if (finalRate > 1) { mediaElement.addEventListener("timeupdate", timeUpdateHandler); } const cleanup = () => { mediaElement.removeEventListener("pause", listener); mediaElement.removeEventListener("timeupdate", timeUpdateHandler); if (quizHandlerId) { clearInterval(quizHandlerId); quizHandlerId = null; } if (observer) { observer.disconnect(); observer = null; } }; const finishTask = () => { if (isResolved) return; isResolved = true; cleanup(); resolve(); }; mediaElement.addEventListener("ended", async () => { hasPlaybackEnded = true; log.insertLog(`${mediaType}已播放完成,等待任务点完成`); }); if (iframe && iframe.parentElement) { observer = new MutationObserver(() => { const parentElement = iframe.parentElement; if (parentElement.classList.contains("ans-job-finished")) { log.insertLog(`${mediaType}任务点已完成,停止播放`); mediaElement.pause(); finishTask(); } }); observer.observe(iframe.parentElement, { attributes: true, attributeFilter: ["class"], subtree: true }); if (iframe.parentElement.classList.contains("ans-job-finished")) { log.insertLog(`${mediaType}任务点已完成,停止播放`); mediaElement.pause(); finishTask(); } } clearInterval(intervalId); } }, 2500); }); }; const processRead = async (iframe, iframeWindow) => { try { const triggerFinish = () => { try { if (iframeWindow && typeof iframeWindow.finishJob === "function" && iframeWindow.jobid) { iframeWindow.finishJob(iframeWindow.jobid); return true; } } catch (e) { } return false; }; if (!triggerFinish()) { const subFrame = (iframe.contentDocument == null ? void 0 : iframe.contentDocument.querySelector('iframe[src*="readjob"]')) || null; const subWin = (subFrame == null ? void 0 : subFrame.contentWindow) || null; try { if (subWin && subWin.parent && typeof subWin.parent.finishJob === "function" && subWin.data && subWin.data.jobid) { subWin.parent.finishJob(subWin.data.jobid); } } catch (e) { } } let waited = 0; while (waited < 15000) { const parentEl = iframe.parentElement; if (parentEl && parentEl.className.includes("ans-job-finished")) return Promise.resolve(); await sleep(1000); waited += 1000; } log.insertLog("阅读任务完成超时,请手动检查", "warning", 4); return Promise.resolve(); } catch (e) { return Promise.resolve(); } }; const processLive = async (iframe, iframeWindow) => { try { if (iframeWindow) { const bodyTxt = (iframeWindow.document.body == null ? void 0 : iframeWindow.document.body.innerText) || ""; if (bodyTxt.includes("已完成") && /观看|任务/.test(bodyTxt)) { log.insertLog("直播任务已完成,自动继续", "success", 4); return Promise.resolve(); } } if (iframeWindow && !iframeWindow.__xsLiveOpened) { try { const card = (iframeWindow == null ? void 0 : iframeWindow.document) == null ? void 0 : iframeWindow.document.querySelector(".liveHref"); if (card) { card.click(); iframeWindow.__xsLiveOpened = true; log.insertLog("已自动打开直播回放,观看时长达到90%后将自动完成并跳转", "info", 4); } else { log.insertLog("当前直播未开播或不可回放", "warning", 4); return Promise.resolve(); } } catch (e) { } } const readLiveProgress = () => { try { const info = iframeWindow.document.querySelector(".LiveInfo2"); const txt = (info == null ? void 0 : info.textContent) || ""; const m = txt.match(/观看进度为\s*([\d.]+)\s*%/); return m ? parseFloat(m[1]) : null; } catch (e) { return null; } }; let waited = 0; while (waited < 3 * 60 * 60 * 1000) { const parentEl = iframe.parentElement; if (parentEl && parentEl.className.includes("ans-job-finished")) { log.insertLog("直播任务已完成,自动继续", "success", 4); return Promise.resolve(); } const pct = readLiveProgress(); if (pct !== null && pct >= 90) { log.insertLog("直播观看进度已达90%,刷新页面确认任务完成", "success", 4); try { iframeWindow.top.location.reload(); } catch (e) { location.reload(); } return Promise.resolve(); } await sleep(30000); waited += 30000; if (waited % 600000 === 0) { log.insertLog(`直播挂机中,已等待 ${Math.round(waited / 60000)} 分钟(进度${pct === null ? "未知" : pct + "%"}),请保持回放页在前台播放`, "warning", 4); } } log.insertLog("直播任务超时未完成,请手动检查", "warning", 4); return Promise.resolve(); } catch (e) { return Promise.resolve(); } }; const processIframe = async (iframe) => { var _a, _b; const iframeSrc = iframe.src; const iframeDocument = iframe.contentDocument; const iframeWindow = iframe.contentWindow; if (!iframeDocument || !iframeWindow) return Promise.resolve(); if (iframeSrc.includes("javascript:")) return Promise.resolve(); await waitIframeLoad(iframe); const parentClass = ((_a = iframe.parentElement) == null ? void 0 : _a.className) || ""; if (parentClass.includes("ans-job-finished")) ; else { if (iframeSrc.includes("modules/read")) { return processRead(iframe, iframeWindow); } if (iframeSrc.includes("modules/live")) { return processLive(iframe, iframeWindow); } if (iframeSrc.includes("api/work")) { selectTextSearchLogic(iframeDocument, iframeWindow); return processWork(iframe, iframeDocument, iframeWindow); } const ansJobIcon = (_b = iframe.parentElement) == null ? void 0 : _b.querySelector(".ans-job-icon"); if (ansJobIcon) { if (iframeSrc.includes("video")) { return processMedia("video", iframeDocument, iframe); } else if (iframeSrc.includes("audio")) { return processMedia("audio", iframeDocument, iframe); } else if (["ppt", "doc", "pptx", "docx", "pdf"].some( (type) => iframeSrc.includes("modules/" + type) )) { return processPpt(iframeWindow); } else if (["innerbook"].some((type) => iframeSrc.includes("modules/" + type))) { return processBook(iframeWindow); } } } return Promise.resolve(); }; init(); setupFaceRecognitionWatcher(); processIframeTask(); setupInterceptor(); }; const cxLiveReplayLogic = () => { const log = useLogStore(); log.insertLog("进入直播回放页,脚本正在控制视频播放.."); let notified = false; const getVideo = () => document.querySelector("video"); const ensurePlaying = async () => { const v = getVideo(); if (!v) return; if (v.paused || v.ended) { v.muted = true; try { await v.play(); } catch (e) { } } }; const isWatchedEnough = () => { const v = getVideo(); if (!v || typeof watchTime === "undefined") return false; return watchTime >= v.duration * 0.9; }; const checkTask = async () => { await ensurePlaying(); if (notified || !isWatchedEnough()) return; notified = true; log.insertLog("直播观看进度已达90%,正在返回课程页确认完成", "success", 4); try { const opener = window.opener; if (opener && !opener.closed) { try { opener.location.reload(); } catch (e) { } } } catch (e) { } setTimeout(() => { try { window.close(); } catch (e) { } }, 1500); }; setInterval(checkTask, 5000); setTimeout(checkTask, 1500); }; const cxHomeworkLogic = async () => { const log = useLogStore(); const questionStore = useQuestionStore(); const setting = useSettingStore(); log.insertLog(`进入新版作业页面,开始准备答题..`); if (!setting.config.basicConfig.autoAnswer.value) { log.insertLog( `自动答题已关闭,请前往设置模块中更改` ); return; } questionStore.accuracy = -1; const accuracy = await new QuestionHandler$1("zy").init(); questionStore.accuracy = accuracy; log.insertLog( `答题完毕,正确率为${accuracy}%,详情请前往答题模块查看..` ); }; const isPreviewDisabled = () => { var _a, _b; return (_b = (_a = document.querySelector(".mark_info")) == null ? void 0 : _a.textContent) == null ? void 0 : _b.includes("不允许整卷预览"); }; const isVisible = (element) => { if (!element) return false; const style = window.getComputedStyle(element); const rect = element.getBoundingClientRect(); return style.display !== "none" && style.visibility !== "hidden" && rect.width > 0 && rect.height > 0; }; const getVisibleElement = (selector) => [...document.querySelectorAll(selector)].find(isVisible); const getNextQuestionBtn = () => getVisibleElement('[onclick="getTheNextQuestion(1)"]'); const getSubmitConfirmBtn = () => { const selectors = [ ".popBottom a[onclick*='answerDirectSubmit']", "a[onclick*='answerDirectSubmit']", "a.confirm[onclick*='finalSubmit']" ]; for (const selector of selectors) { const confirmBtn = getVisibleElement(selector); if (confirmBtn) return confirmBtn; } return null; }; const waitForSubmitConfirmBtn = async () => { for (let i = 0; i < 10; i++) { const confirmBtn = getSubmitConfirmBtn(); if (confirmBtn) return confirmBtn; await sleep(0.5); } return null; }; const submitCxExam = async (log, setting) => { if (!setting.config.examConfig.autoSubmit.value) { log.insertLog( "自动交卷未开启,请前往设置模块中更改", "warning", 4 ); return; } log.insertLog("自动交卷已开启,正在提交试卷"); await sleep(setting.config.basicConfig.reqIntervalTime.value); const submitBtn = getVisibleElement("div.sub-button.fr a.completeBtn"); if (!submitBtn) { log.insertLog("未找到交卷按钮,请手动交卷", "warning", 4); return; } submitBtn.click(); const confirmBtn = await waitForSubmitConfirmBtn(); if (confirmBtn) { confirmBtn.click(); log.insertLog("已确认提交试卷"); return; } log.insertLog("未找到提交确认按钮,请手动确认交卷", "warning", 4); }; const runSingleQuestionExamLogic = async (log, setting) => { log.insertLog( "当前考试不允许整卷预览,将按单题页面顺序处理,请勿手动切换题目", "warning", 4 ); while (true) { await new QuestionHandler$1("ks").init(); const nextBtn = getNextQuestionBtn(); if (!nextBtn) break; nextBtn.click(); log.insertLog("已切换到下一题,继续处理.."); await sleep(1); } await submitCxExam(log, setting); }; const enterCxExamPreviewLogic = async () => { const log = useLogStore(); const setting = useSettingStore(); if (!setting.config.basicConfig.autoAnswer.value) { log.insertLog( `自动答题已关闭,请前往设置模块中更改` ); return; } if (isPreviewDisabled()) { await runSingleQuestionExamLogic(log, setting); return; } log.insertLog("进入考试开始页,正在切换到整卷预览.."); for (let i = 0; i < 10; i++) { const previewBtn = document.querySelector("div.sub-button a"); if (previewBtn) { previewBtn.click(); log.insertLog("已点击整卷预览,正在进入答题页面.."); return; } await sleep(0.5); } log.insertLog("未找到整卷预览按钮,请手动点击", "warning", 4); }; const cxExamLogic = async () => { const log = useLogStore(); const setting = useSettingStore(); log.insertLog(`进入新版考试页面,开始准备答题..`); if (!setting.config.basicConfig.autoAnswer.value) { log.insertLog( `自动答题已关闭,请前往设置模块中更改` ); return; } await new QuestionHandler$1("ks").init(); await submitCxExam(log, setting); }; const cxRoutes = { match: [ { keyword: "zhibo.chaoxing.com", tab: "0", logic: cxLiveReplayLogic }, { keyword: "/base", tab: "0" }, { keyword: "/space/index", tab: "0" }, // 积分课程 { keyword: "/plaza/?courseId", tab: "0" }, { keyword: "mycourse/stu?courseid", tab: "0" }, { keyword: "/mycourse/studentstudy", tab: "1", logic: cxChapterLogic }, { keyword: "/mooc2/work/dowork", tab: "1", logic: cxHomeworkLogic }, { keyword: "/examcode/examnotes", tab: "0", log: "等待用户进入答题页面中.." }, { keyword: "exam-ans/exam/test/reVersionTestStartNew", tab: "1", logic: enterCxExamPreviewLogic }, { keyword: "mooc-ans/exam/test/reVersionTestStartNew", tab: "1", logic: enterCxExamPreviewLogic }, { keyword: "/exam/test/reVersionTestStartNew", tab: "1", logic: enterCxExamPreviewLogic }, { keyword: "/mooc2/exam/preview", tab: "1", logic: cxExamLogic }, { keyword: "exam/preview", tab: "1", logic: cxExamLogic } ], defaultRoute: { show: true, tab: "0" } }; class QuestionHandler2 extends BasicQuestionHandler { constructor(type, iframe) { super(); __publicField(this, "type"); __publicField(this, "AI_ALLOW_QUESTION_TYPE", ["单选题", "多选题", "判断题"]); __publicField(this, "init", async () => { var _that = this; this.questions = []; this.parseHtml(); if (this.questions.length) { this.log.insertLog( `成功解析到${this.questions.length}个题目,开始答题..` ); setTimeout(() => { _that.setting.tabIndex = "2"; }, 1e3); const { answerMode } = this.setting.config.basicConfig; const mode = answerMode.value; for (const [index, question] of this.questions.entries()) { this.questionStore.currentQuestionIndex = index; let fillFailed = false; let handled = false; if (mode !== "ai") { const answerData = await getAccurateAnswer(question); if (answerData.code === 1) { question.answer = { code: 1, answer: answerData.data.answer }; this.setting.userInfo.remainCount = formatLocalRemainCount(answerData.data.remainCount); saveAnswerRecord({ question, answer: answerData.data.answer, source: "accurate" }); const errorStatus = !this.fillQuestion(question, index); if (errorStatus) { question.answer.code = 0; fillFailed = true; if (mode === "questionBank") { handled = true; } } else { this.correctNum += 1; handled = true; } } else if (mode === "questionBank" || answerData.code !== -1004) { question.answer = { code: answerData.code, answer: [] }; handled = true; } } if (!handled) { if (this.AI_ALLOW_QUESTION_TYPE.includes(question.type)) { const aiQuestion = { ...question, type: this.questionType[question.type] || question.type }; const aiAnswerData = await getAIAnswer(aiQuestion); if (aiAnswerData.code === 1) { question.answer = { code: 2, answer: aiAnswerData.data.answer }; this.setting.userInfo.remainCount = formatLocalRemainCount(aiAnswerData.data.remainCount); saveAnswerRecord({ question, answer: aiAnswerData.data.answer, source: "ai" }); const errorStatus = !this.fillQuestion(question, index); if (errorStatus) { question.answer.code = 0; fillFailed = true; } else { this.correctNum += 1; } } else { question.answer = { code: aiAnswerData.code, answer: [] }; } } else { question.answer = { code: -1, answer: ["该题型不支持AI答题"] }; } } if (!fillFailed && this.type == "zy") { const nextBtn = this._document.querySelectorAll( "div.switch-btn-box > button:nth-child(2) > span" )[0]; if (nextBtn.innerText === "下一题") { nextBtn.click(); } } } this.questionStore.currentQuestionIndex += 1; } else this.log.insertLog( "没有解析到题目,请检查是否进入正确页面...", "warning" ); setTimeout(() => { _that.setting.tabIndex = "1"; }, 2e3); return Promise.resolve( (this.correctNum / this.questions.length * 100).toFixed(1) ); }); __publicField(this, "parseHtml", () => { this.questionStore.currentQuestionIndex = 0; if (!this._document) return []; this.addQuestions(this.questionStore.questionList); this.questionStore.questionList = this.questions; }); __publicField(this, "fillQuestion", (question, index) => { let isSelected = false; if (!this._window) return; const optionsDOM = document.querySelectorAll(".subject_node")[index].querySelectorAll(".node_detail"); const normalizeOptText = (str) => String(str ?? "").replace(/^[A-Z][.、.]\s*/i, "").replace(/\s+/g, "").replace(/[^\p{L}\p{N}√×]/gu, "").toLowerCase(); if (question.type === "单选题" || question.type === "多选题") { question.answer.answer.forEach((answer) => { // 字母答案映射:A=第1个选项 const letterText = String(answer || "").trim(); const letterIdx = /^[A-Za-z]$/.test(letterText) ? letterText.toUpperCase().charCodeAt(0) - 65 : -1; optionsDOM.forEach((optionDOM, optIdx) => { if (letterIdx >= 0) { if (optIdx === letterIdx) { isSelected = true; optionDOM.click(); } return; } const optionText = this.removeHtml(optionDOM.innerHTML); const nAnswer = normalizeOptText(answer); const nOption = normalizeOptText(optionText); if (answer == optionText || this.removeAllTags(answer) == this.removeAllTags(optionText) || (nAnswer.length >= 2 && nOption && nOption.includes(nAnswer))) { isSelected = true; optionDOM.click(); } }); }); } else if (question.type === "判断题") { let answer = "对"; if (question.answer.answer[0].match( /(^|,)(正确|是|对|√|T|ri|right|true)(,|$)/ )) { answer = "对"; } else if (question.answer.answer[0].match( /(^|,)(错误|否|错|×|F|wr|wrong|false)(,|$)/ )) { answer = "错"; } optionsDOM.forEach((optionDOM) => { if (answer == this.removeHtml(optionDOM.innerHTML)) { isSelected = true; optionDOM.click(); } }); } else ; return isSelected; }); __publicField(this, "extractOptions", (questionOptions, questionType) => { const optionsObject = {}; const optionTexts = []; if (!questionOptions) return [optionsObject, optionTexts]; questionOptions.forEach((questionOption) => { const optionTextContent = this.removeHtml(questionOption.content); optionsObject[optionTextContent] = questionOption.content; if (questionType !== "判断题") optionTexts.push(optionTextContent); }); return [optionsObject, optionTexts]; }); __publicField(this, "addQuestions", (questions) => { questions.forEach((question) => { const questionTitle = this.removeHtml(question.name); const questionType = question.questionType.name; const [optionsObject, optionTexts] = this.extractOptions( question.questionOptions, questionType ); this.questions.push({ element: JSON.stringify(questions), type: questionType, title: questionTitle, optionsText: optionTexts, options: optionsObject, answer: { code: -1, answer: [] }, workType: "zhs", refer: this._window.location.href, id: "zhs" }); }); }); this.type = type; if (iframe) { this._document = iframe.contentDocument; this._window = iframe.contentWindow; } } } const ANSWER_SHEET_EVENT = "ABC_ZHS_ANSWER_SHEET"; const ANSWER_SHEET_URL = "getAnswerSheetInformation"; const saveAnswerSheetPayload = (payload) => { const pageWindow = _unsafeWindow || window; pageWindow.__ABC_ZHS_ANSWER_SHEET__ = payload; pageWindow.dispatchEvent(new CustomEvent(ANSWER_SHEET_EVENT, { detail: payload })); }; const parseResponseText = (text) => { try { return JSON.parse(text); } catch { return text; } }; const getRequestUrl = (input) => { if (typeof input === "string") return input; if (input == null ? void 0 : input.url) return input.url; return input ? String(input) : ""; }; const installAnswerSheetHook = () => { const pageWindow = _unsafeWindow || window; if (pageWindow.__ABC_ZHS_ANSWER_SHEET_HOOKED__) return; pageWindow.__ABC_ZHS_ANSWER_SHEET_HOOKED__ = true; const originalFetch = pageWindow.fetch; if (typeof originalFetch === "function") { pageWindow.fetch = async function(...args) { const response = await originalFetch.apply(this, args); const requestUrl = getRequestUrl(args[0]); if (requestUrl.includes(ANSWER_SHEET_URL)) { response.clone().text().then((text) => { console.info("[ABC] 捕获智慧树考试接口(fetch):", requestUrl); saveAnswerSheetPayload(parseResponseText(text)); }).catch(() => { }); } return response; }; } const OriginalXHR = pageWindow.XMLHttpRequest; if (typeof OriginalXHR === "function" && !OriginalXHR.__ABC_ZHS_ANSWER_SHEET_HOOKED__) { const originalOpen = OriginalXHR.prototype.open; const originalSend = OriginalXHR.prototype.send; OriginalXHR.__ABC_ZHS_ANSWER_SHEET_HOOKED__ = true; OriginalXHR.prototype.open = function(method, url2, ...rest) { this.__abcRequestUrl = url2 || ""; return originalOpen.call(this, method, url2, ...rest); }; OriginalXHR.prototype.send = function(...args) { this.addEventListener("load", function() { const requestUrl = getRequestUrl(this.responseURL || this.__abcRequestUrl); if (requestUrl.includes(ANSWER_SHEET_URL)) { console.info("[ABC] 捕获智慧树考试接口(xhr):", requestUrl); saveAnswerSheetPayload(parseResponseText(this.responseText)); } }); return originalSend.apply(this, args); }; } }; const fetchHook = (url2, callback) => { const questionStore = useQuestionStore(); function addXMLRequestCallback(callback2) { var oldSend, i; if (XMLHttpRequest.callbacks) { XMLHttpRequest.callbacks.push(callback2); } else { XMLHttpRequest.callbacks = [callback2]; oldSend = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.send = function() { for (i = 0; i < XMLHttpRequest.callbacks.length; i++) { XMLHttpRequest.callbacks[i](this); } oldSend.apply(this, arguments); }; } } addXMLRequestCallback(function(xhr) { xhr.addEventListener("load", function() { if (xhr.readyState == 4 && xhr.status == 200) { if (xhr.responseURL.includes(url2)) { const keywords = ["doExam", "doHomework"]; if (keywords.some((keyword) => xhr.responseURL.includes(keyword))) { let questions = []; const questionsParts = JSON.parse(xhr.response).rt.examBase.workExamParts; questionsParts.forEach((part) => { console.log("part", part.questionDtos); questions = questions.concat(part.questionDtos); }); questionStore.questionList = questions; } callback(); } } }); }); }; const autoAnswerLogic = async () => { const log = useLogStore(); const questionStore = useQuestionStore(); log.insertLog("进入作业页面,脚本开始工作中.."); questionStore.accuracy = -1; const handleFetchHook = async () => { const accuracy = await new QuestionHandler2("zy").init(); questionStore.accuracy = accuracy; log.insertLog(`答题完毕,正确率为${accuracy}%,详情请前往答题模块查看..`); }; ["doHomework", "doExam"].forEach((type) => { fetchHook(type, () => handleFetchHook()); }); }; const videoStudyLogic = () => { const log = useLogStore(); const setting = useSettingStore(); let videoEl = null; let pollIntervalId = null; let isAnsweringQuiz = false; const injectQuizHelper = () => { const script = document.createElement("script"); script.textContent = ` (function() { window.addEventListener("message", function(e) { if (!e.data || e.data.type !== "ABC_ZHS_QUIZ_ANSWER") return; try { var dialog = document.querySelector("#playTopic-dialog"); if (!dialog || !dialog.__vue__) { window.postMessage({ type: "ABC_ZHS_QUIZ_RESULT", success: false }, "*"); return; } var vm = dialog.__vue__.$parent.$parent; if (!vm || !vm.topicInfo) { window.postMessage({ type: "ABC_ZHS_QUIZ_RESULT", success: false }, "*"); return; } var dto = vm.topicInfo.lessonTestQuestionUseInterfaceDtos; if (!dto || !dto[0]) { window.postMessage({ type: "ABC_ZHS_QUIZ_RESULT", success: false }, "*"); return; } var options = dto[0].testQuestion.questionOptions; var answers = dto[0].answerUs; if (!options || !answers) { window.postMessage({ type: "ABC_ZHS_QUIZ_RESULT", success: false }, "*"); return; } var count = 0; options.forEach(function(opt, i) { if (answers.indexOf(opt.sortUs) !== -1) { setTimeout(function() { vm.topicClickQot(options[i]); }, 800 * count); count++; } }); var closeDelay = Math.max(count * 800 + 2000, 5000); setTimeout(function() { vm.testDialog = false; window.postMessage({ type: "ABC_ZHS_QUIZ_RESULT", success: true, count: count }, "*"); }, closeDelay); } catch (err) { window.postMessage({ type: "ABC_ZHS_QUIZ_RESULT", success: false, error: err.message }, "*"); } }); })(); `; (document.head || document.documentElement).appendChild(script); script.remove(); }; const clearOverlays = () => { var _a, _b; (_a = document.querySelector(".v-modal")) == null ? void 0 : _a.remove(); (_b = document.querySelector(".brush-warning")) == null ? void 0 : _b.remove(); }; const isChapterCompleted = (el) => !!(el == null ? void 0 : el.querySelector(".time_icofinish")); const waitForVideo = (timeout = 15e3) => { return new Promise((resolve, reject) => { const startTime = Date.now(); const check = setInterval(() => { const video = document.querySelector("video"); if (video) { clearInterval(check); resolve(video); return; } if (Date.now() - startTime > timeout) { clearInterval(check); reject(new Error("等待视频元素超时")); } }, 500); }); }; const clickEl = (el) => { if (el) { el.dispatchEvent(new MouseEvent("click", { bubbles: true })); } }; const getCurrentChapterTitle = () => { var _a, _b; return ((_b = (_a = document.querySelector("li.video.current_play .catalogue_title")) == null ? void 0 : _a.innerText) == null ? void 0 : _b.trim()) || "未知小节"; }; const getChapterLabel = (chapterEl) => { var _a, _b, _c, _d; const title = ((_b = (_a = chapterEl.querySelector(".catalogue_title")) == null ? void 0 : _a.innerText) == null ? void 0 : _b.trim()) || ""; const num = ((_d = (_c = chapterEl.querySelector(".hour")) == null ? void 0 : _c.textContent) == null ? void 0 : _d.trim()) || ""; return num ? `${num} ${title}` : title; }; const playVideo = () => { clearOverlays(); videoEl = document.querySelector("#vjs_container_html5_api"); if (!videoEl) return; videoEl.muted = true; videoEl.play().catch(() => { }); }; const startVideo = () => { playVideo(); if (!videoEl) return; videoEl.addEventListener("play", () => { const title = getCurrentChapterTitle(); log.insertLog(`${title} - 视频开始播放`); hookVedio(videoEl, 1.5); log.insertLog(`已设置播放倍速: 1.5x`); }, { once: true }); videoEl.addEventListener("playing", () => { clearOverlays(); }); videoEl.addEventListener("pause", () => { if (videoEl.ended || isAnsweringQuiz) return; const dialog = document.querySelector("#playTopic-dialog"); if (!dialog) { log.insertLog("视频被暂停,将在下次轮询中恢复.."); } }); videoEl.addEventListener("ended", () => { log.insertLog("当前视频播放完毕"); nextTask(); }); }; const handlePopupQuiz = async () => { var _a; if (isAnsweringQuiz) return; isAnsweringQuiz = true; await sleep(1); const dialog = document.querySelector("#playTopic-dialog"); if (!dialog) { isAnsweringQuiz = false; return; } const headerText = ((_a = dialog.querySelector(".el-dialog__header h4")) == null ? void 0 : _a.innerText) || ""; if (!headerText.includes("给你出题啦") && !headerText.includes("弹题测验")) { isAnsweringQuiz = false; return; } const radioEl = dialog.querySelector(".radio"); const topicTitle = radioEl == null ? void 0 : radioEl.querySelector(".topic-title"); if (topicTitle && (topicTitle.querySelector(".right") || topicTitle.querySelector(".error"))) { log.insertLog("弹题已作答过,关闭弹窗.."); await sleep(2); closeQuizDialog(); isAnsweringQuiz = false; await sleep(1); playVideo(); return; } log.insertLog("检测到弹题测验,尝试自动作答.."); return new Promise((resolve) => { const timeout = setTimeout(() => { window.removeEventListener("message", handler); log.insertLog("弹题自动作答超时,关闭弹窗..", "warning"); closeQuizDialog(); isAnsweringQuiz = false; setTimeout(playVideo, 1e3); resolve(); }, 2e4); const handler = (e) => { var _a2; if (((_a2 = e.data) == null ? void 0 : _a2.type) !== "ABC_ZHS_QUIZ_RESULT") return; clearTimeout(timeout); window.removeEventListener("message", handler); if (e.data.success) { log.insertLog(`弹题已自动作答完成(${e.data.count}题),继续播放视频`); } else { log.insertLog( `弹题自动作答失败${e.data.error ? ": " + e.data.error : ""},关闭弹窗..`, "warning" ); closeQuizDialog(); } isAnsweringQuiz = false; setTimeout(playVideo, 1e3); resolve(); }; window.addEventListener("message", handler); window.postMessage({ type: "ABC_ZHS_QUIZ_ANSWER" }, "*"); }); }; const closeQuizDialog = () => { const dialog = document.querySelector("#playTopic-dialog"); const closeBtn = dialog == null ? void 0 : dialog.querySelector(".el-dialog__headerbtn"); clickEl(closeBtn); }; const handleMidwayDialogs = () => { const dialogs = document.querySelectorAll(".el-dialog"); const visibleDialog = Array.from(dialogs).find( (d) => { var _a; return ((_a = d == null ? void 0 : d.parentElement) == null ? void 0 : _a.style.display) !== "none"; } ); if (!visibleDialog) { clearOverlays(); return; } if (visibleDialog.ariaLabel === "温馨提示") { const btn = visibleDialog.querySelector(".el-button.btn"); if ((btn == null ? void 0 : btn.textContent) === "我知道了") { clickEl(btn); log.insertLog("已关闭「温馨提示」弹窗"); setTimeout(playVideo, 500); return; } } if (visibleDialog.ariaLabel === "弹题测验") { handlePopupQuiz(); } }; const findNextChapter = () => { const allChapters = Array.from(document.querySelectorAll("li.video")); const currentChapter = document.querySelector("li.video.current_play"); if (!currentChapter || allChapters.length === 0) return null; const currentIndex = allChapters.indexOf(currentChapter); const ordered = [ ...allChapters.slice(currentIndex + 1), ...allChapters.slice(0, currentIndex) ]; return ordered.find((el) => !isChapterCompleted(el)); }; const nextTask = async () => { var _a; if (!setting.config.basicConfig.autoChangeChapter.value) { log.insertLog( "自动切换章节未开启,前往设置模块中更改", "warning" ); return; } const currentTitle = ((_a = document.querySelector("li.video.current_play .catalogue_title")) == null ? void 0 : _a.innerText) || "当前章节"; log.insertLog(`「${currentTitle}」已完成`); const next = findNextChapter(); if (next) { const nextLabel = getChapterLabel(next); log.insertLog(`跳转到: ${nextLabel}`); clickEl(next); await sleep(5); initVideoTask(); } else { log.insertLog("所有章节视频已完成!"); clearPollInterval(); } }; const startPolling = () => { let lastHour = null; pollIntervalId = setInterval(() => { var _a; const currentEl = document.querySelector(".video.current_play"); if (!currentEl) return; const hour = (_a = currentEl.querySelector(".hour")) == null ? void 0 : _a.textContent; if (hour && lastHour !== null && hour !== lastHour) { lastHour = hour; setTimeout(startVideo, 3e3); } if (hour) lastHour = hour; if (isChapterCompleted(currentEl)) { nextTask(); return; } const video = document.querySelector("#vjs_container_html5_api"); if ((video == null ? void 0 : video.paused) && !video.ended && !isAnsweringQuiz) { handleMidwayDialogs(); } }, 2e3); }; const clearPollInterval = () => { if (pollIntervalId) { clearInterval(pollIntervalId); pollIntervalId = null; } }; const initVideoTask = async () => { try { await waitForVideo(); startVideo(); } catch (e) { log.insertLog(`${e.message},尝试跳转下一节..`, "warning"); nextTask(); } }; const start = async () => { var _a; log.insertLog("进入智慧树视频学习页面,脚本开始工作.."); setting.tabIndex = "1"; injectQuizHelper(); await sleep(5); const noteClose = document.querySelector(".dialog-read .iconguanbi"); if (noteClose) { const parent = (_a = noteClose.closest) == null ? void 0 : _a.call(noteClose, ".dialog-read"); if (parent && getComputedStyle(parent).display !== "none") { clickEl(noteClose); log.insertLog("已关闭阅读提示弹窗"); } } handleMidwayDialogs(); startPolling(); initVideoTask(); }; start(); }; const learnPageLogic = () => { const log = useLogStore(); const setting = useSettingStore(); let videoEl = null; let currentResourceIndex = 0; let resourceItems = []; const waitFor = (selector, timeout = 1e4) => { return new Promise((resolve, reject) => { const startTime = Date.now(); const check = setInterval(() => { const el = document.querySelector(selector); if (el) { clearInterval(check); resolve(el); return; } if (Date.now() - startTime > timeout) { clearInterval(check); reject(new Error(`等待元素 ${selector} 超时`)); } }, 300); }); }; const clickEl = (el) => { if (el) el.dispatchEvent(new MouseEvent("click", { bubbles: true })); }; const RESOURCE_TYPES = { VIDEO: "video", BOOK: "book", PPT: "other" }; const getResourceType = (resourceEl) => { const iconBox = resourceEl == null ? void 0 : resourceEl.querySelector(".icon-box"); if (!iconBox) return "unknown"; const classes = iconBox.className; if (classes.includes("video")) return RESOURCE_TYPES.VIDEO; if (classes.includes("book")) return RESOURCE_TYPES.BOOK; if (classes.includes("other") || classes.includes("ppt")) return RESOURCE_TYPES.PPT; return "unknown"; }; const getAllResourceEls = () => Array.from(document.querySelectorAll(".resources-list .resources-item")); const getCurrentResourceEl = () => { const items = getAllResourceEls(); const idx = resourceItems[currentResourceIndex]; return items[idx] ?? null; }; const detectCurrentResourceType = () => { const el = getCurrentResourceEl(); el ? getResourceType(el) : "unknown"; }; const onResourceComplete = async () => { currentResourceIndex++; detectCurrentResourceType(); await processNextResource(); }; const processVideo = async () => { log.insertLog("发现视频资源,正在播放.."); try { await sleep(5); videoEl = document.querySelector(".preview-section .video-player-wrapper video") || document.querySelector(".preview-section video"); if (!videoEl) { log.insertLog("未找到视频元素,跳过..", "warning"); onResourceComplete(); return; } videoEl.muted = true; videoEl.play().catch(() => { }); const userRate = Number(setting.config.basicConfig.videoPlayrate.value) || 1; const rates = [userRate, 2, 1.75, 1.5, 1.25]; for (const rate of rates) { try { videoEl.playbackRate = rate; if (videoEl.playbackRate === rate) { log.insertLog(`已设置播放倍速: ${rate}x`); break; } } catch { } } videoEl.addEventListener("pause", () => { if (videoEl.ended) return; if (videoEl.duration > 0 && videoEl.duration - videoEl.currentTime <= 1) { log.insertLog("视频播放完毕"); onResourceComplete(); return; } setTimeout(() => { if (videoEl && !videoEl.ended && videoEl.paused) { videoEl.play().catch(() => { }); } }, 2e3); }); videoEl.addEventListener("ended", () => { log.insertLog("视频播放完毕"); onResourceComplete(); }, { once: true }); } catch (e) { log.insertLog(`视频处理出错: ${e.message}`, "warning"); onResourceComplete(); } }; const processPpt = async () => { var _a; log.insertLog("发现PPT资源,正在自动翻页.."); try { await sleep(2); const totalText = (_a = document.querySelector( ".preview-section .ppt-preview-container .page-total" )) == null ? void 0 : _a.innerText; const totalPages = parseInt(totalText, 10) || 0; if (!totalPages) { log.insertLog("无法获取PPT页数,跳过..", "warning"); onResourceComplete(); return; } const containers = document.querySelectorAll( ".catalogue-container .div-img-container" ); let currentPage = Array.from(containers).findIndex((c) => c.classList.contains("choose")) + 1 || 1; log.insertLog(`PPT共 ${totalPages} 页,从第 ${currentPage} 页开始`); const flipPage = async (page) => { if (page >= totalPages) { log.insertLog("PPT翻页完成"); onResourceComplete(); return; } const nextBtn = document.querySelector( ".preview-section .ppt-preview-container .next-page-btn" ); clickEl(nextBtn); await sleep(5); flipPage(page + 1); }; flipPage(currentPage); } catch (e) { log.insertLog(`PPT处理出错: ${e.message}`, "warning"); onResourceComplete(); } }; const processBook = async () => { log.insertLog("发现电子书资源,正在自动阅读.."); try { await waitFor(".preview-section .book-mark .book-content"); await waitFor(".preview-section .book-mark .canvas-wrapper"); const pages = document.querySelectorAll( ".preview-section .book-mark .canvas-wrapper" ); if (!pages.length) { log.insertLog("未找到电子书页面,跳过..", "warning"); onResourceComplete(); return; } const firstRect = pages[0].getBoundingClientRect(); const marginTop = parseInt(getComputedStyle(pages[0]).marginTop, 10) || 0; const pageHeight = Math.max(firstRect.height + marginTop, 100); const container = document.querySelector( ".preview-section .book-mark .book-container" ); if (!container) { log.insertLog("未找到电子书滚动容器,跳过..", "warning"); onResourceComplete(); return; } const scrollStep = async () => { const target = Math.min( container.scrollTop + pageHeight, container.scrollHeight - container.clientHeight ); container.scrollTo({ top: target, behavior: "smooth" }); await sleep(1.5); if (container.scrollTop + container.clientHeight >= container.scrollHeight - 2) { log.insertLog("电子书阅读完成"); onResourceComplete(); } else { scrollStep(); } }; scrollStep(); } catch (e) { log.insertLog(`电子书处理出错: ${e.message}`, "warning"); onResourceComplete(); } }; const processNextResource = async () => { if (currentResourceIndex >= resourceItems.length) { log.insertLog("当前小节所有资源已完成"); nextSection(); return; } const el = getCurrentResourceEl(); if (!el) { log.insertLog("未找到资源元素,跳过..", "warning"); onResourceComplete(); return; } if (currentResourceIndex > 0) { const clickTarget = el.querySelector(".basic-info-video-card-container"); clickEl(clickTarget || el); await sleep(3); } const type = getResourceType(el); log.insertLog( `处理资源 [${currentResourceIndex + 1}/${resourceItems.length}] 类型: ${type}` ); switch (type) { case RESOURCE_TYPES.VIDEO: processVideo(); break; case RESOURCE_TYPES.BOOK: processBook(); break; case RESOURCE_TYPES.PPT: processPpt(); break; default: log.insertLog(`不支持的资源类型 (${type}),跳过`); onResourceComplete(); } }; const nextSection = async () => { var _a, _b, _c, _d; if (!setting.config.basicConfig.autoChangeChapter.value) { log.insertLog( "自动切换章节未开启,前往设置模块中更改", "warning" ); return; } const sections = document.querySelectorAll(".section-item-collapse-info"); const active = document.querySelector(".section-item-collapse-info.active"); if (!active) { log.insertLog("未找到当前活跃章节"); return; } const currentTitle = ((_b = (_a = active.querySelector(".section-item-collapse-title")) == null ? void 0 : _a.innerText) == null ? void 0 : _b.trim()) || "当前章节"; log.insertLog(`「${currentTitle}」所有资源已完成`); const arr = Array.from(sections); const nextIdx = arr.indexOf(active) + 1; if (nextIdx < arr.length) { const next = arr[nextIdx]; const nextTitle = ((_d = (_c = next.querySelector(".section-item-collapse-title")) == null ? void 0 : _c.innerText) == null ? void 0 : _d.trim()) || "下一节"; log.insertLog(`跳转到: ${nextTitle}`); clickEl(next); await sleep(5); initResources(); } else { log.insertLog("所有章节已完成!"); } }; const initResources = async () => { currentResourceIndex = 0; resourceItems = []; videoEl = null; try { await waitFor(".resources-detail-card .resources-list .resources-item", 15e3); const items = getAllResourceEls(); if (!items.length) { log.insertLog("未找到学习资源,尝试跳转下一节..", "warning"); nextSection(); return; } resourceItems = items.map((_, i) => i); log.insertLog(`发现 ${resourceItems.length} 个学习资源`); detectCurrentResourceType(); await processNextResource(); } catch (e) { log.insertLog(`初始化资源列表失败: ${e.message}`, "warning"); nextSection(); } }; const watchUrlChange = () => { let prevUrl = location.href; setInterval(() => { if (location.href === prevUrl) return; const wasLearnPage = prevUrl.includes("/learnPage"); prevUrl = location.href; if (location.href.includes("/learnPage") && !wasLearnPage) { log.insertLog("检测到页面切换,重新初始化.."); setTimeout(initResources, 3e3); } }, 1500); }; const start = async () => { log.insertLog("进入智慧树新版学习页面,脚本开始工作.."); setting.tabIndex = "1"; await sleep(6); const noteDialog = document.querySelector(".read-note-dialog"); if (noteDialog && getComputedStyle(noteDialog).display !== "none") { const btn = noteDialog.querySelector("button"); if (btn) { clickEl(btn); log.insertLog("已关闭阅读提示弹窗"); } } try { await waitFor(".preview-content", 15e3); } catch { log.insertLog("等待页面内容加载超时..", "warning"); } watchUrlChange(); initResources(); }; start(); }; const aiSmartLogic = () => { const log = useLogStore(); const setting = useSettingStore(); let videoEl = null; let currentResourceIndex = 0; let resourceItems = []; let currentResourceType = ""; let isStopped = false; let chapterObserver = null; let resourceObserver = null; const waitFor = (selector, timeout = 1e4) => { return new Promise((resolve, reject) => { const startTime = Date.now(); const check = setInterval(() => { const el = document.querySelector(selector); if (el) { clearInterval(check); resolve(el); return; } if (Date.now() - startTime > timeout) { clearInterval(check); reject(new Error(`等待元素 ${selector} 超时`)); } }, 300); }); }; const waitForHeight = (selector, timeout = 15e3) => { return new Promise((resolve, reject) => { const startTime = Date.now(); const check = () => { const el = document.querySelector(selector); if (el && el.offsetHeight > 0) { resolve(el); return; } if (Date.now() - startTime > timeout) { reject(new Error("等待元素高度超时")); return; } setTimeout(check, 100); }; check(); }); }; const clickEl = (el) => { if (el) el.dispatchEvent(new MouseEvent("click", { bubbles: true })); }; const RESOURCE_TYPES = { VIDEO: "video", PPT: "ppt" }; const getResourceType = (resourceEl) => { var _a; const fileType = resourceEl == null ? void 0 : resourceEl.querySelector(".file-type"); if (!fileType) return "unknown"; const secondClass = fileType.classList[1]; if (!secondClass) return "unknown"; const type = (_a = secondClass.split("-")) == null ? void 0 : _a[1]; if (type === "video") return RESOURCE_TYPES.VIDEO; if (type === "ppt" || type === "other") return RESOURCE_TYPES.PPT; return type || "unknown"; }; const getAllResourceEls = () => Array.from(document.querySelectorAll(".resources-list .resources-item")); const getCurrentResourceEl = () => { const items = getAllResourceEls(); const idx = resourceItems[currentResourceIndex]; return items[idx] ?? null; }; const detectCurrentResourceType = () => { const el = getCurrentResourceEl(); currentResourceType = el ? getResourceType(el) : "unknown"; }; const findVideoElement = async (retries = 3, interval = 500) => { for (let i = 0; i < retries; i++) { const video = document.querySelector("#container #vjs_container video"); if (video) return video; await new Promise((r) => setTimeout(r, interval)); } return null; }; const cleanupVideo = () => { if (videoEl) { videoEl.removeEventListener("ended", onVideoEnded); videoEl.removeEventListener("playing", onVideoPlaying); videoEl.removeEventListener("pause", onVideoPause); videoEl = null; } }; const onVideoPlaying = () => { log.insertLog("视频正在播放中.."); }; const onVideoEnded = () => { log.insertLog("视频播放完毕"); currentResourceIndex++; detectCurrentResourceType(); processNextResource(); }; const onVideoPause = () => { if (!videoEl || videoEl.ended) return; log.insertLog("视频被暂停,尝试恢复.."); setTimeout(() => { if (videoEl && !videoEl.ended && videoEl.paused) { videoEl.play().catch(() => { }); } }, 2e3); }; const processVideo = async () => { var _a, _b; log.insertLog("发现视频资源,正在播放.."); try { cleanupVideo(); videoEl = await findVideoElement(); if (!videoEl) { log.insertLog("未找到视频元素,跳过..", "warning"); currentResourceIndex++; detectCurrentResourceType(); processNextResource(); return; } videoEl.muted = true; const muteHandler = () => { if (videoEl) videoEl.muted = false; }; (_a = document.body) == null ? void 0 : _a.addEventListener("click", muteHandler, { once: true }); (_b = document.querySelector("#container")) == null ? void 0 : _b.addEventListener("click", muteHandler, { once: true }); const userRate = Number(setting.config.basicConfig.videoPlayrate.value) || 1; const rates = [userRate, 2, 1.75, 1.5, 1.25]; for (const rate of rates) { try { videoEl.playbackRate = rate; if (videoEl.playbackRate === rate) { log.insertLog(`已设置播放倍速: ${rate}x`); break; } } catch { } } videoEl.play().catch(() => { videoEl.muted = true; videoEl.play().catch(() => { }); }); videoEl.addEventListener("playing", onVideoPlaying, { once: true }); videoEl.addEventListener("pause", onVideoPause); videoEl.addEventListener("ended", onVideoEnded, { once: true }); } catch (e) { log.insertLog(`视频处理出错: ${e.message}`, "warning"); currentResourceIndex++; detectCurrentResourceType(); processNextResource(); } }; const getPptPageInfo = () => { var _a, _b; const container = document.querySelector(".ppt-btn-container"); if (!container) return []; const current = parseInt( (_a = container.querySelector(".page-content .page-current")) == null ? void 0 : _a.innerText, 10 ) || 0; const total = parseInt( (_b = container.querySelector(".page-content .page-total")) == null ? void 0 : _b.innerText, 10 ) || 0; return current && total ? [current, total] : []; }; const processPpt = async () => { log.insertLog("发现PPT资源,正在自动翻页.."); try { const pptContainer = document.querySelector( ".ppt-box .ppt-preview-container" ); if (!pptContainer) { log.insertLog("未找到PPT容器,跳过..", "warning"); currentResourceIndex++; detectCurrentResourceType(); processNextResource(); return; } let [currentPage, totalPages] = getPptPageInfo(); if (!currentPage || !totalPages) { log.insertLog("无法获取PPT页数,跳过..", "warning"); currentResourceIndex++; detectCurrentResourceType(); processNextResource(); return; } if (currentPage >= totalPages) { log.insertLog("PPT已翻到最后一页"); await sleep(2); currentResourceIndex++; detectCurrentResourceType(); processNextResource(); return; } log.insertLog(`PPT共 ${totalPages} 页,从第 ${currentPage} 页开始`); while (currentPage < totalPages) { if (isStopped) break; const nextBtn = pptContainer.querySelector(".next-page-btn"); clickEl(nextBtn); await sleep(4); const [newCurrent, newTotal] = getPptPageInfo(); currentPage = newCurrent; totalPages = newTotal; if (currentPage >= totalPages) { log.insertLog("PPT翻页完成"); await sleep(2); currentResourceIndex++; detectCurrentResourceType(); processNextResource(); return; } } } catch (e) { log.insertLog(`PPT处理出错: ${e.message}`, "warning"); currentResourceIndex++; detectCurrentResourceType(); processNextResource(); } }; const processNextResource = async () => { if (currentResourceIndex >= resourceItems.length) { log.insertLog("当前小节所有资源已完成"); nextSection(); return; } const el = getCurrentResourceEl(); if (!el) { log.insertLog("未找到资源元素,跳过..", "warning"); currentResourceIndex++; detectCurrentResourceType(); processNextResource(); return; } if (currentResourceIndex > 0) { const type = getResourceType(el); if (!["video", "ppt"].includes(type)) { log.insertLog(`不支持的资源类型 (${type}),跳过`); currentResourceIndex++; detectCurrentResourceType(); processNextResource(); return; } const clickTarget = el.querySelector(".basic-info-video-card-container"); clickEl(clickTarget || el); await sleep(3); } detectCurrentResourceType(); log.insertLog( `处理资源 [${currentResourceIndex + 1}/${resourceItems.length}] 类型: ${currentResourceType}` ); switch (currentResourceType) { case RESOURCE_TYPES.VIDEO: processVideo(); break; case RESOURCE_TYPES.PPT: processPpt(); break; default: log.insertLog(`不支持的资源类型 (${currentResourceType}),跳过`); currentResourceIndex++; detectCurrentResourceType(); processNextResource(); } }; const getAllChapters = () => Array.from(document.querySelectorAll(".inner-li li.clearfix")).filter( (el) => !el.classList.contains("child-chapter") && !el.classList.contains("forbidClick") ); const nextSection = async () => { var _a, _b, _c, _d; if (!setting.config.basicConfig.autoChangeChapter.value) { log.insertLog( "自动切换章节未开启,前往设置模块中更改", "warning" ); return; } const chapters = getAllChapters(); const active = document.querySelector(".clearfix.video.activeNode"); if (!active) { log.insertLog("未找到当前活跃章节"); return; } const currentTitle = ((_b = (_a = active.querySelector(".title")) == null ? void 0 : _a.innerText) == null ? void 0 : _b.trim()) || "当前章节"; log.insertLog(`「${currentTitle}」所有资源已完成`); const currentIdx = chapters.indexOf(active); const next = chapters[currentIdx + 1]; if (next) { const nextTitle = ((_d = (_c = next.querySelector(".title")) == null ? void 0 : _c.innerText) == null ? void 0 : _d.trim()) || "下一节"; log.insertLog(`跳转到: ${nextTitle}`); next.click(); next.scrollIntoView({ behavior: "smooth" }); } else { log.insertLog("所有章节已完成!"); } }; const observeChapterList = () => { const listEl = document.querySelector(".chapterList-box .list"); if (!listEl) return; chapterObserver = new MutationObserver((mutations) => { for (const mutation of mutations) { if (mutation.type !== "attributes" || mutation.attributeName !== "class") continue; const target = mutation.target; const isActive = target.classList.contains("activeNode") || target.classList.contains("active"); if (isActive) { onChapterChange(); } } }); chapterObserver.observe(listEl, { attributes: true, attributeFilter: ["class"], subtree: true, childList: false }); }; const observeResourceList = () => { const resourceListEl = document.querySelector( ".resource-warp .resources-list" ); if (!resourceListEl) return; resourceObserver = new MutationObserver((mutations) => { for (const mutation of mutations) { if (mutation.type !== "attributes" || mutation.attributeName !== "class") continue; const target = mutation.target; const isActive = target.classList.contains("active"); if (isActive) { onChapterChange(); } } }); resourceObserver.observe(resourceListEl, { attributes: true, attributeFilter: ["class"], subtree: true, childList: false }); }; const onChapterChange = async (isActive) => { { await sleep(5); isStopped = false; await initResourcesFromDom(); } }; const initResourcesFromDom = async () => { if (resourceObserver) resourceObserver.disconnect(); try { await waitFor(".resource-warp .resources-list .resources-item", 15e3); const items = getAllResourceEls(); if (!items.length) { log.insertLog("未找到学习资源,尝试跳转下一节..", "warning"); nextSection(); return; } const activeIdx = Array.from(items).findIndex( (el) => el.classList.contains("active") ); currentResourceIndex = activeIdx >= 0 ? activeIdx : 0; resourceItems = items.map((_, i) => i); log.insertLog(`发现 ${resourceItems.length} 个学习资源`); observeResourceList(); detectCurrentResourceType(); await processNextResource(); } catch (e) { log.insertLog(`初始化资源列表失败: ${e.message}`, "warning"); nextSection(); } }; const dismissPopups = () => { const popup = document.querySelector(".courseRemind.khfaPop"); const closeBtn = popup == null ? void 0 : popup.querySelector( ".header-slot-box .el-icon-error" ); if (closeBtn) { closeBtn.click(); log.insertLog("已关闭课程提醒弹窗"); } }; const watchUrlChange = () => { let prevUrl = location.href; setInterval(() => { if (location.href === prevUrl) return; prevUrl = location.href; if (location.href.includes("ai-smart-course-student-pro.zhihuishu.com") || location.href.includes("fusioncourseh5.zhihuishu.com") || location.href.includes("smartcoursestudent.zhihuishu.com")) { log.insertLog("检测到页面切换,重新初始化.."); setTimeout(() => initAll(), 3e3); } }, 1500); }; const initAll = async () => { try { await waitForHeight(".preview-warp"); } catch { log.insertLog("等待页面内容加载超时..", "warning"); } dismissPopups(); observeChapterList(); await sleep(5); await initResourcesFromDom(); }; const start = async () => { log.insertLog("进入智慧树AI智慧课学习页面,脚本开始工作.."); setting.tabIndex = "1"; await sleep(6); watchUrlChange(); await initAll(); }; start(); }; const smartStudyLogic = () => { const log = useLogStore(); const setting = useSettingStore(); const RESOURCE_TYPES = { VIDEO: "video", BOOK: "book", PPT: "ppt", TEXT: "text", LINK: "link", UNKNOWN: "unknown" }; let currentRunId = 0; let videoEl = null; let urlWatcher = null; const waitFor = (selector, timeout = 1e4) => { return new Promise((resolve, reject) => { const startTime = Date.now(); const check = setInterval(() => { const el = document.querySelector(selector); if (el) { clearInterval(check); resolve(el); return; } if (Date.now() - startTime > timeout) { clearInterval(check); reject(new Error(`等待元素 ${selector} 超时`)); } }, 300); }); }; const waitForMedia = (timeout = 5e3) => { return new Promise((resolve, reject) => { const startTime = Date.now(); const check = setInterval(() => { const media = document.querySelector("video,audio"); if (media && media.src) { clearInterval(check); resolve(media); return; } if (Date.now() - startTime > timeout) { clearInterval(check); reject(new Error("未找到音视频元素")); } }, 200); }); }; const clickEl = (el) => { if (!el) return; if (typeof el.click === "function") { el.click(); return; } el.dispatchEvent(new MouseEvent("click", { bubbles: true })); }; const getVideoRate = () => { const rate = Number(setting.config.basicConfig.videoPlayrate.value); return Math.min(rate || 1, 16); }; const getResourceCards = () => Array.from(document.querySelectorAll('[class*="card-container"]')); const getActiveCard = () => getResourceCards().find((card) => card.classList.contains("active")) || null; const getCardTitleEl = (card) => (card == null ? void 0 : card.querySelector(".common-text")) || (card == null ? void 0 : card.querySelector(".video-title")) || (card == null ? void 0 : card.querySelector(".basic-info-video-card-container")) || card; const getResourceType = (card) => { const iconBox = card == null ? void 0 : card.querySelector(".icon-box"); const classes = (iconBox == null ? void 0 : iconBox.className) || ""; if (classes.includes("video")) return RESOURCE_TYPES.VIDEO; if (classes.includes("book")) return RESOURCE_TYPES.BOOK; if (classes.includes("ppt") || classes.includes("other")) return RESOURCE_TYPES.PPT; if (classes.includes("text")) return RESOURCE_TYPES.TEXT; if (classes.includes("link")) return RESOURCE_TYPES.LINK; return RESOURCE_TYPES.UNKNOWN; }; const isCardFinished = (card) => { var _a; const finishedIcon = card == null ? void 0 : card.querySelector(".finished-icon"); if (!finishedIcon) return false; const text = ((_a = finishedIcon.textContent) == null ? void 0 : _a.trim()) || ""; return !text || text.includes("已完成"); }; const getCurrentChapterName = () => { var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j; const sectionTitle = ((_b = (_a = document.querySelector(".point-title-text")) == null ? void 0 : _a.textContent) == null ? void 0 : _b.trim()) || ((_d = (_c = document.querySelector(".section-item-collapse-info.active .section-item-collapse-title")) == null ? void 0 : _c.textContent) == null ? void 0 : _d.trim()) || "未知章节"; const cardTitle = ((_g = (_f = (_e = getActiveCard()) == null ? void 0 : _e.querySelector(".video-title")) == null ? void 0 : _f.textContent) == null ? void 0 : _g.trim()) || ((_j = (_i = (_h = getActiveCard()) == null ? void 0 : _h.querySelector(".common-text")) == null ? void 0 : _i.textContent) == null ? void 0 : _j.trim()) || "未知小节"; return `${sectionTitle}-${cardTitle}`; }; const getNextCard = () => { const cards = getResourceCards(); if (!cards.length) return null; const activeIndex = cards.findIndex((card) => card.classList.contains("active")); const startIndex = activeIndex >= 0 ? activeIndex + 1 : 0; for (let index = startIndex; index < cards.length; index++) { const card = cards[index]; if (!isCardFinished(card)) return card; } return null; }; const parseRequiredProgress = (section) => { var _a; const text = ((_a = section == null ? void 0 : section.querySelector(".collapse-info-progress .progress-text")) == null ? void 0 : _a.textContent) || ""; const match = text.replace(/\s/g, "").match(/(\d+)\/(\d+)/); if (!match) return null; const progress = parseInt(match[1], 10); const total = parseInt(match[2], 10); if (Number.isNaN(progress) || Number.isNaN(total)) return null; return { progress, total }; }; const getNextSection = () => { var _a; const sections = Array.from( document.querySelectorAll(".section-item-collapse-info") ); const active = sections.find((section) => section.classList.contains("active")); const unfinishedSections = sections.map((section) => ({ section, progress: parseRequiredProgress(section) })).filter(({ progress }) => progress && progress.progress < progress.total); if (!unfinishedSections.length) return null; const activeIndex = unfinishedSections.findIndex( ({ section }) => section === active ); if (activeIndex >= 0) { return ((_a = unfinishedSections[activeIndex + 1]) == null ? void 0 : _a.section) || null; } return unfinishedSections[0].section; }; const setCardActive = (card) => { getResourceCards().forEach((item) => item.classList.remove("active")); card == null ? void 0 : card.classList.add("active"); }; const clickResourceCard = async (card) => { const type = getResourceType(card); const target = getCardTitleEl(card); if (type !== RESOURCE_TYPES.UNKNOWN) { clickEl(target); return; } setCardActive(card); const pageWindow = globalThis.unsafeWindow || window; const originalOpen = pageWindow.open; try { pageWindow.open = () => null; clickEl(target); await sleep(3); } finally { pageWindow.open = originalOpen; } }; const cleanupVideo = () => { if (!videoEl) return; videoEl.onpause = null; videoEl.onended = null; videoEl = null; }; const processNext = async (runId) => { var _a, _b, _c, _d; if (runId !== currentRunId) return; const nextCard = getNextCard(); if (nextCard) { const title = ((_b = (_a = nextCard.querySelector(".common-text")) == null ? void 0 : _a.textContent) == null ? void 0 : _b.trim()) || ((_d = (_c = nextCard.querySelector(".video-title")) == null ? void 0 : _c.textContent) == null ? void 0 : _d.trim()) || "下一资源"; log.insertLog(`跳转到资源: ${title}`); await clickResourceCard(nextCard); await sleep(3); await doWork(); return; } await nextSection(runId); }; const processVideo = async (runId) => { try { const wrapper = document.querySelector(".video-player-wrapper"); if (!wrapper || wrapper.style.display === "none") { throw new Error("视频未加载"); } videoEl = await waitForMedia(); if (runId !== currentRunId) return; videoEl.currentTime = Math.max(videoEl.currentTime, 1); videoEl.volume = 0; videoEl.muted = true; videoEl.playbackRate = getVideoRate(); log.insertLog(`已设置播放倍速: ${videoEl.playbackRate}x`); videoEl.play().catch(() => { videoEl.muted = true; videoEl.play().catch(() => { }); }); log.insertLog(`正在学习: ${getCurrentChapterName()}`); videoEl.onpause = async () => { if (runId !== currentRunId || !videoEl || videoEl.ended) return; await sleep(1); videoEl == null ? void 0 : videoEl.play().catch(() => { }); }; videoEl.onended = async () => { if (runId !== currentRunId) return; log.insertLog("视频播放完毕,即将跳转下一资源"); await sleep(3); await processNext(runId); }; } catch (e) { log.insertLog(`未找到学习视频,即将跳转下一资源: ${e.message}`, "warning"); await sleep(3); await processNext(runId); } }; const processPpt = async (runId) => { try { const container = document.querySelector(".preview-section .ppt-preview-container"); if (!container) { await processPassiveResource(runId, "PPT资源"); return; } const getPageInfo = () => { var _a, _b; const current2 = parseInt((_a = container.querySelector(".page-current")) == null ? void 0 : _a.textContent, 10) || 0; const total2 = parseInt((_b = container.querySelector(".page-total")) == null ? void 0 : _b.textContent, 10) || 0; return [current2, total2]; }; let [current, total] = getPageInfo(); if (!current || !total) { await processPassiveResource(runId, "PPT资源"); return; } log.insertLog(`PPT共 ${total} 页,从第 ${current} 页开始`); while (runId === currentRunId && current < total) { clickEl(container.querySelector(".next-page-btn")); await sleep(4); [current, total] = getPageInfo(); } log.insertLog("PPT翻页完成"); await processNext(runId); } catch (e) { log.insertLog(`PPT处理出错: ${e.message}`, "warning"); await processNext(runId); } }; const processBook = async (runId) => { try { await waitFor(".preview-section .book-mark .book-container", 5e3); const container = document.querySelector( ".preview-section .book-mark .book-container" ); if (!container) { await processPassiveResource(runId, "电子书资源"); return; } log.insertLog("发现电子书资源,正在阅读.."); while (runId === currentRunId && container.scrollTop + container.clientHeight < container.scrollHeight - 2) { container.scrollTo({ top: Math.min( container.scrollTop + Math.max(container.clientHeight * 0.8, 200), container.scrollHeight ), behavior: "smooth" }); await sleep(1.5); } log.insertLog("电子书阅读完成"); await processNext(runId); } catch { await processPassiveResource(runId, "电子书资源"); } }; const processPassiveResource = async (runId, label) => { if (runId !== currentRunId) return; log.insertLog(`${label}已处理,即将跳转下一资源`); await sleep(3); await processNext(runId); }; async function doWork() { const runId = ++currentRunId; cleanupVideo(); await sleep(5); if (runId !== currentRunId) return; let card = getActiveCard(); if (card && isCardFinished(card)) { log.insertLog("当前资源已完成,跳过.."); card = getNextCard(); if (!card) { await nextSection(runId); return; } await clickResourceCard(card); await sleep(3); card = getActiveCard() || card; } if (!card) { card = getNextCard(); if (!card && getResourceCards().length) { await nextSection(runId); return; } if (card) { await clickResourceCard(card); await sleep(3); card = getActiveCard() || card; } } if (!card) { log.insertLog("未找到学习资源,尝试跳转下一章节..", "warning"); await nextSection(runId); return; } const type = getResourceType(card); log.insertLog(`处理资源类型: ${type}`); switch (type) { case RESOURCE_TYPES.VIDEO: await processVideo(runId); break; case RESOURCE_TYPES.BOOK: await processBook(runId); break; case RESOURCE_TYPES.PPT: await processPpt(runId); break; case RESOURCE_TYPES.TEXT: await processPassiveResource(runId, "文本资源"); break; case RESOURCE_TYPES.LINK: case RESOURCE_TYPES.UNKNOWN: default: await processPassiveResource(runId, "当前资源"); } } async function nextSection(runId) { var _a, _b; if (runId !== currentRunId) return; const next = getNextSection(); if (!next) { log.insertLog("所有必学章节已完成!"); return; } const title = ((_b = (_a = next.querySelector(".section-item-collapse-title")) == null ? void 0 : _a.textContent) == null ? void 0 : _b.trim()) || "下一章节"; log.insertLog(`跳转到: ${title}`); clickEl(next); await sleep(5); await doWork(); } const dismissPopups = () => { const noteDialog = document.querySelector(".read-note-dialog"); if (noteDialog && getComputedStyle(noteDialog).display !== "none") { const btn = noteDialog.querySelector("button"); if (btn) { clickEl(btn); log.insertLog("已关闭阅读提示弹窗"); } } }; const watchUrlChange = () => { if (urlWatcher) clearInterval(urlWatcher); let prevUrl = location.href; urlWatcher = setInterval(() => { if (location.href === prevUrl) return; prevUrl = location.href; if (location.href.includes("/learnPage")) { log.insertLog("检测到页面切换,重新初始化.."); doWork(); } }, 1500); }; const start = async () => { log.insertLog("进入智慧树新形态课程学习页面,脚本开始工作.."); setting.tabIndex = "1"; if (location.href.includes("/singleCourse")) { log.insertLog("请点击任意章节开始学习,进入学习页后将自动处理..", "warning"); return; } await sleep(6); dismissPopups(); try { await waitFor(".section-item-collapse-info", 15e3); } catch { log.insertLog("等待章节列表加载超时..", "warning"); } watchUrlChange(); await doWork(); }; start(); }; const studyPlusLogic = () => { const log = useLogStore(); const setting = useSettingStore(); let videoEl = null; let quizTimer = null; let finishTimer = null; let visibilityTimer = null; let isSwitching = false; let isHandlingQuiz = false; let visibilityWarned = false; const waitFor = (target, timeout = 1e4, interval = 300) => { return new Promise((resolve, reject) => { const startTime = Date.now(); const check = setInterval(() => { const result = typeof target === "function" ? target() : document.querySelector(target); if (result) { clearInterval(check); resolve(result); return; } if (Date.now() - startTime > timeout) { clearInterval(check); reject(new Error("等待页面内容加载超时")); } }, interval); }); }; const clickEl = async (el) => { var _a, _b, _c; if (!el) return false; (_a = el.scrollIntoView) == null ? void 0 : _a.call(el, { behavior: "smooth", block: "center" }); await sleep(0.2); (_b = el.click) == null ? void 0 : _b.call(el); (_c = el.dispatchEvent) == null ? void 0 : _c.call(el, new MouseEvent("click", { bubbles: true })); return true; }; const getRate = () => { const rate = Number(setting.config.basicConfig.videoPlayrate.value); return Math.min(rate || 1, 16); }; const getCourseName = () => { var _a, _b; const text = (_b = (_a = document.querySelector(".top-back-box > span:nth-child(2), .course-name")) == null ? void 0 : _a.textContent) == null ? void 0 : _b.trim(); return (text == null ? void 0 : text.replace(/^课程名称[::]\s*/, "")) || "未知课程"; }; const getChapterItems = () => Array.from(document.querySelectorAll(".child-main")).filter( (el) => { var _a; return (_a = el.parentElement) == null ? void 0 : _a.querySelector(".child-time"); } ); const getCurrentChapter = () => getChapterItems().find((el) => el.classList.contains("current")); const getChapterName = (item) => { var _a, _b; return ((_b = (_a = item == null ? void 0 : item.parentElement) == null ? void 0 : _a.textContent) == null ? void 0 : _b.replace(/\s+/g, " ").trim()) || "未知章节"; }; const isChapterFinished = (item) => { var _a; return !!((_a = item == null ? void 0 : item.parentElement) == null ? void 0 : _a.querySelector(".finish-icon")); }; const getNextChapter = () => { const items = getChapterItems().filter((item) => !isChapterFinished(item)); if (!items.length) return null; const current = getCurrentChapter(); const currentIndex = items.indexOf(current); return currentIndex >= 0 ? items[currentIndex + 1] : items[0]; }; const cleanupVideo = () => { if (!videoEl) return; videoEl.onpause = null; videoEl.onended = null; videoEl = null; }; const handleQuizDialog = async () => { if (isHandlingQuiz) return; const dialog = document.querySelector(".ai-test-question-wrapper"); if (!dialog) return; isHandlingQuiz = true; try { const done = dialog.querySelector(".done"); const closeBtn = dialog.querySelector(".close-box"); if (done) { await clickEl(closeBtn); return; } const options = Array.from(dialog.querySelectorAll(".options .option")); if (options.length) { const option = options[Math.floor(Math.random() * options.length)]; await clickEl(option); await sleep(1); } await clickEl(document.querySelector(".submit-btn .submits")); await sleep(1); await clickEl(closeBtn); log.insertLog("随堂练习弹窗已处理"); } finally { await sleep(1); isHandlingQuiz = false; } }; const fixControls = () => { const bar = document.querySelector(".controlsBar"); if (!bar) return; bar.style.display = "block"; bar.style.zIndex = "2"; }; const playVideo = async () => { cleanupVideo(); try { fixControls(); videoEl = await waitFor(() => { const media = document.querySelector("video, audio"); return (media == null ? void 0 : media.src) ? media : null; }, 15e3); videoEl.currentTime = Math.max(videoEl.currentTime, 1); videoEl.volume = 0; videoEl.muted = true; videoEl.playbackRate = getRate(); log.insertLog(`正在学习:${getChapterName(getCurrentChapter())}`); log.insertLog(`已设置播放倍速: ${videoEl.playbackRate}x`); videoEl.play().catch(() => { }); videoEl.onpause = () => { if (!videoEl || videoEl.ended || isSwitching || isHandlingQuiz) return; setTimeout(() => { var _a; return (_a = videoEl == null ? void 0 : videoEl.play) == null ? void 0 : _a.call(videoEl).catch(() => { }); }, 1e3); }; videoEl.onended = async () => { if (isSwitching) return; await sleep(2); await nextChapter(); }; } catch (error) { log.insertLog(`${error.message || "视频加载失败"},尝试切换下一节..`, "warning"); await sleep(2); await nextChapter(); } }; const nextChapter = async () => { if (isSwitching) return; if (!setting.config.basicConfig.autoChangeChapter.value) { log.insertLog( "自动切换章节未开启,前往设置模块中更改", "warning" ); return; } isSwitching = true; cleanupVideo(); const next = getNextChapter(); if (!next) { log.insertLog("所有章节视频已完成!"); isSwitching = false; return; } log.insertLog(`跳转到: ${getChapterName(next)}`); await clickEl(next); await sleep(5); isSwitching = false; playVideo(); }; const startPolling = () => { quizTimer = setInterval(handleQuizDialog, 3e3); finishTimer = setInterval(() => { if (isSwitching) return; const current = getCurrentChapter(); if (current && isChapterFinished(current)) nextChapter(); }, 2e3); visibilityTimer = setInterval(() => { if (document.visibilityState === "hidden" && !visibilityWarned) { visibilityWarned = true; log.insertLog("页面未保持前台,学习流程可能无法正常推进..", "warning"); } else if (document.visibilityState === "visible") { visibilityWarned = false; } }, 1e3); }; const start = async () => { log.insertLog(`进入智慧树新共享课学习页面(${getCourseName()}),脚本开始工作..`); setting.tabIndex = "1"; await sleep(5); try { await waitFor(".child-main", 2e4); } catch { log.insertLog("等待章节列表加载超时,尝试继续处理..", "warning"); } document.querySelectorAll(".el-overlay,.el-dialog").forEach((dialog) => { dialog.style.display = "none"; }); startPolling(); playVideo(); }; start(); return () => { cleanupVideo(); if (quizTimer) clearInterval(quizTimer); if (finishTimer) clearInterval(finishTimer); if (visibilityTimer) clearInterval(visibilityTimer); }; }; const TYPE_TEXT_MAP = [ { keywords: ["多选", "多项"], type: "多选题" }, { keywords: ["判断", "是非"], type: "判断题" }, { keywords: ["填空"], type: "填空题" }, { keywords: ["简答"], type: "简答题" }, { keywords: ["论述"], type: "论述题" }, { keywords: ["计算"], type: "计算题" }, { keywords: ["单选", "单项"], type: "单选题" } ]; const QUESTION_TYPE_CODE_MAP = { 单选题: "0", 单项选择题: "0", 单选: "0", 多选题: "1", 多项选择题: "1", 多选: "1", 填空题: "2", 填空: "2", 判断题: "3", 判断: "3", 是非题: "3", 简答题: "4", 简答: "4", 名词解释: "5", 论述题: "6", 论述: "6", 计算题: "7", 计算: "7" }; const PROFILES = [ { name: "新形态正式考试", root: ".question-area-content", type: [ "div.flex.items-center.mb-\\[16px\\]", "[class*='question-type']", "[class*='mb-[16px]']" ], title: [ "div.flex-1 .mb-\\[32px\\] .text-mainText.font-medium", ".text-mainText.font-medium", "[class*='text-mainText'][class*='font-medium']", "[class*='question'][class*='title']" ], options: [ "label.user-select.group", "label[class*='user-select']", "label.group", "[role='radio']", "[role='checkbox']" ], beforeParse: async () => { var _a; const firstButton = document.querySelector(".grid.grid-cols-7 > button") || Array.from(document.querySelectorAll("button")).find( (button) => { var _a2; return ((_a2 = button.textContent) == null ? void 0 : _a2.trim()) === "1"; } ); if (firstButton) { firstButton.click(); } const startTime = Date.now(); while (Date.now() - startTime < 1e4) { const root = document.querySelector(".question-area-content"); if ((_a = root == null ? void 0 : root.textContent) == null ? void 0 : _a.trim()) return; await sleep(0.3); } }, nextButton: () => Array.from(document.querySelectorAll("button")).find( (button) => { var _a; return (_a = button.textContent) == null ? void 0 : _a.includes("下一题"); } ), useAnswerSheet: true }, { name: "新形态作业考试", root: ".questionBox", type: ".questionTit", title: ".questionContent", options: ".optionUl label", nextButton: "span.Topicswitchingbtn:nth-child(2)" }, { name: "新形态掌握度", root: ".questionContent", type: ".questionName", title: ".questionName .centent-pre", options: ".radio-view li.clearfix, .checkbox-views label.el-checkbox, .fillAnswer" }, { name: "AI掌握度", root: ".exam-item", type: ".quest-type", title: ".quest-title .option-name", options: "label" } ]; const smartAnswerLogic = async () => { var _a, _b; const log = useLogStore(); const setting = useSettingStore(); const questionStore = useQuestionStore(); if (!setting.config.basicConfig.autoAnswer.value) { log.insertLog( `自动答题已关闭,请前往设置模块中更改` ); return; } const cleanText = (text = "") => String(text ?? "").replace(/<[^>]+>/g, "").replace(/ /g, " ").replace(/\s+/g, " ").trim(); const normalize = (text = "") => cleanText(text).replace(/^[A-Z][.、.]\s*/i, "").replace(/[^\p{L}\p{N}√×]/gu, "").toLowerCase(); const htmlOf = (el) => (el == null ? void 0 : el.innerHTML) || (el == null ? void 0 : el.textContent) || ""; const getByKeys = (obj, keys) => { if (!obj || typeof obj !== "object") return ""; const key = keys.find((item) => obj[item] != null && obj[item] !== ""); return key ? obj[key] : ""; }; const parseJsonLike = (value) => { if (typeof value !== "string") return value; const text = value.trim(); if (!/^[\[{]/.test(text)) return value; try { return JSON.parse(text); } catch { return value; } }; const normalizePayload = (payload) => { const visited = /* @__PURE__ */ new WeakSet(); const normalizeNode = (node) => { const parsed = parseJsonLike(node); if (!parsed || typeof parsed !== "object") return parsed; if (visited.has(parsed)) return parsed; visited.add(parsed); if (Array.isArray(parsed)) return parsed.map(normalizeNode); Object.keys(parsed).forEach((key) => { parsed[key] = normalizeNode(parsed[key]); }); return parsed; }; return normalizeNode(payload); }; const normalizeOption = (option) => ({ text: cleanText( getByKeys(option, [ "option", "content", "optionContent", "optionText", "optionName", "optionTitle", "answer", "answerContent", "answerText", "name", "text", "title", "label", "value" ]) ), html: String( getByKeys(option, ["content", "optionContent", "optionName", "name", "text"]) || "" ) }); const extractOptionList = (obj) => { const optionList = getByKeys(obj, [ "questionOptions", "questionOption", "options", "optionList", "questionOptionList", "questionOptionDTOList", "questionOptionsList", "optionContentList", "answerOptions", "answerOptionList", "answerList", "answerDtos", "answerDTOList", "answers", "optionVos", "optionVOList", "items" ]); if (!Array.isArray(optionList)) return []; return optionList.map(normalizeOption).filter((option) => option.text); }; const normalizeApiType = (typeText) => { const text = String(typeText || ""); const matched = TYPE_TEXT_MAP.find( ({ keywords }) => keywords.some((keyword) => text.includes(keyword)) ); if (matched) return matched.type; if (QUESTION_TYPE_CODE_MAP[text]) return text; const codeMap = { 1: "单选题", 2: "多选题", 3: "判断题", 4: "填空题", 5: "简答题", 14: "判断题" }; return codeMap[text] || "单选题"; }; const collectAnswerSheetMetaQuestions = (payload) => { var _a2; const normalizedPayload = normalizePayload(payload); const parts = (_a2 = normalizedPayload == null ? void 0 : normalizedPayload.data) == null ? void 0 : _a2.partSheetVos; if (!Array.isArray(parts)) return []; return parts.flatMap( (part) => (part.questionSheetVos || []).map((question) => ({ type: normalizeApiType(question.questionType || part.name), sort: Number(question.sort) || 0, questionId: question.questionId, version: question.version, partName: part.name })) ).sort((a, b) => a.sort - b.sort); }; const collectAnswerSheetQuestions = (payload) => { const metaQuestions = collectAnswerSheetMetaQuestions(payload); if (metaQuestions.length) return metaQuestions; const questions2 = []; const visited = /* @__PURE__ */ new WeakSet(); const normalizedPayload = normalizePayload(payload); const titleKeys = [ "questionName", "questionTitle", "questionContent", "questionContentText", "questionText", "questionStem", "questionStemContent", "questionStemRich", "questionStemRichText", "questionDescribe", "questionDesc", "questionBody", "stemContent", "stemText", "stem", "title", "name", "content", "contentText", "topicContent", "topicName" ]; const typeKeys = [ "questionTypeName", "questionType", "questionTypeStr", "questionTypeText", "questionTypeCode", "questionTypeId", "questionTypeDto", "questionTypeDTO", "typeName", "type" ]; const walk = (node) => { if (!node || typeof node !== "object") return; if (visited.has(node)) return; visited.add(node); if (Array.isArray(node)) { node.forEach(walk); return; } const rawTitle = getByKeys(node, titleKeys); const title = cleanText( typeof rawTitle === "object" ? getByKeys(rawTitle, ["name", "title", "content", "text", "value"]) : rawTitle ); const options = extractOptionList(node); if (title && (options.length || title.length > 6)) { const optionsObject = {}; options.forEach((option) => { optionsObject[option.text] = option.html || option.text; }); questions2.push({ type: normalizeApiType( (() => { const typeValue = getByKeys(node, typeKeys); return typeof typeValue === "object" ? getByKeys(typeValue, ["name", "title", "text", "value", "code", "id"]) : typeValue; })() ), title, optionsText: options.map((option) => option.text), options: optionsObject }); } Object.values(node).forEach(walk); }; walk(normalizedPayload); const seen = /* @__PURE__ */ new Set(); return questions2.filter((question) => { const key = `${question.title}|${question.optionsText.join("|")}`; if (seen.has(key)) return false; seen.add(key); return true; }); }; const waitForAnswerSheetQuestions = async () => { const pageWindow = _unsafeWindow || window; if (pageWindow.__ABC_ZHS_ANSWER_SHEET__) { return collectAnswerSheetQuestions(pageWindow.__ABC_ZHS_ANSWER_SHEET__); } return new Promise((resolve) => { const timer = setTimeout(() => resolve([]), 1e4); pageWindow.addEventListener( "ABC_ZHS_ANSWER_SHEET", (event) => { clearTimeout(timer); resolve(collectAnswerSheetQuestions(event.detail)); }, { once: true } ); }); }; const describePayloadShape = (payload) => { const normalizedPayload = normalizePayload(payload); const topKeys = normalizedPayload && typeof normalizedPayload === "object" ? Object.keys(normalizedPayload).slice(0, 12) : []; const candidateKeys = []; const visited = /* @__PURE__ */ new WeakSet(); const walk = (node) => { if (!node || typeof node !== "object" || candidateKeys.length >= 5) return; if (visited.has(node)) return; visited.add(node); if (Array.isArray(node)) { node.slice(0, 5).forEach(walk); return; } const keys = Object.keys(node); if (keys.some((key) => /question|option|answer|stem|title|content/i.test(key))) { candidateKeys.push(keys.slice(0, 16).join(",")); } Object.values(node).forEach(walk); }; walk(normalizedPayload); return { topKeys, candidateKeys }; }; const toSelectorList = (selectors) => Array.isArray(selectors) ? selectors : [selectors].filter(Boolean); const queryAllSafe = (root, selectors) => { const results = []; toSelectorList(selectors).forEach((selector) => { try { results.push(...root.querySelectorAll(selector)); } catch { } }); return Array.from(new Set(results)); }; const queryOneSafe = (root, selectors) => queryAllSafe(root, selectors)[0] || null; const resolveType = (root, profile2) => { var _a2; const rawText = ((_a2 = queryOneSafe(root, profile2.type)) == null ? void 0 : _a2.textContent) || root.textContent || ""; const matched = TYPE_TEXT_MAP.find( ({ keywords }) => keywords.some((keyword) => rawText.includes(keyword)) ); return (matched == null ? void 0 : matched.type) || "单选题"; }; const toApiQuestion = (question) => ({ ...question, type: QUESTION_TYPE_CODE_MAP[question.type] || question.type || "999" }); const getActiveProfile = async () => { const startTime = Date.now(); while (Date.now() - startTime < 2e4) { const profile2 = PROFILES.find((item) => document.querySelector(item.root)); if (profile2) return profile2; await sleep(0.3); } return null; }; const parseQuestions = (profile2) => { const roots = Array.from(document.querySelectorAll(profile2.root)); return roots.map((root) => { const optionEls = queryAllSafe(root, profile2.options).filter( (option) => cleanText(option.textContent) ); const optionsText = optionEls.map((option) => cleanText(option.textContent)); const options = {}; optionEls.forEach((option, index) => { options[optionsText[index]] = htmlOf(option); }); const titleEl = queryOneSafe(root, profile2.title); let title = cleanText(htmlOf(titleEl)); if (!title) { const optionTextSet = new Set(optionsText.map((option) => normalize(option))); title = queryAllSafe(root, "div, p, span").map((el) => cleanText(el.textContent)).filter((text) => text.length > 4).filter((text) => !optionTextSet.has(normalize(text))).filter( (text) => !TYPE_TEXT_MAP.some( ({ keywords }) => keywords.some((keyword) => text === keyword || text === `${keyword}题`) ) ).sort((a, b) => b.length - a.length)[0] || ""; } return { element: root, type: resolveType(root, profile2), title, optionsText, options, optionEls, answer: { code: -1, answer: [] }, workType: "zhs", refer: location.href, id: "zhs" }; }); }; const isChoiceType = (type) => ["单选题", "多选题", "判断题"].includes(type); const fillBlank = (question, answers) => { const inputs = Array.from( question.element.querySelectorAll("input, textarea, .fillAnswer input, .fillAnswer textarea") ); if (!inputs.length) return false; inputs.forEach((input, index) => { const value = answers[index] || answers[0] || ""; input.value = value; input.dispatchEvent(new Event("input", { bubbles: true })); input.dispatchEvent(new Event("change", { bubbles: true })); }); return true; }; const fillChoice = (question, answers) => { var _a2; const optionEls = ((_a2 = question.optionEls) == null ? void 0 : _a2.length) ? question.optionEls : Array.from( question.element.querySelectorAll( ".optionUl label, label.user-select.group, .radio-view li.clearfix, .checkbox-views label.el-checkbox, label, [role='radio'], [role='checkbox']" ) ); let selected = false; const normalizedAnswers = answers.map((answer) => normalize(answer)); const judgementAnswer = normalizedAnswers[0]; // 收集答案中的图片 URL:提取 (超星 CDN 部分图 URL 无扩展名)+ 带扩展名的裸 URL const answerImgs = []; answers.forEach(a => { const s = String(a || ""); const imgSrcRe = /]+src=["']([^"']+)["']/gi; let imgM; while ((imgM = imgSrcRe.exec(s))) { if (!answerImgs.includes(imgM[1])) answerImgs.push(imgM[1]); } const bareImg = s.match(/https?:\/\/[^\s"'<>]+\.(?:jpg|jpeg|png|gif|webp)/i); if (bareImg && !answerImgs.includes(bareImg[0])) answerImgs.push(bareImg[0]); }); // 保留原始答案文本用于子串匹配 const rawAnswers = answers.map(a => String(a).trim()).filter(Boolean); // 纯字母答案收集:["A","B","D"] 或紧凑串 ["ABD"] → 按索引映射(A=第1个选项) // 紧凑串仅在不等于任何选项文本时才按字母拆,避免把单词选项误拆成字母 const letterAnswers = []; answers.forEach(a => { const t = String(a || "").trim(); if (/^[A-Za-z]$/.test(t)) { if (!letterAnswers.includes(t.toUpperCase())) letterAnswers.push(t.toUpperCase()); } else if (/^[A-Za-z]{2,10}$/.test(t)) { const isOptionText = optionEls.some(op => normalize(op.textContent) === normalize(t)); if (!isOptionText) t.toUpperCase().split("").forEach(c => { if (!letterAnswers.includes(c)) letterAnswers.push(c); }); } }); const maxLetterIdx = letterAnswers.length ? Math.max(...letterAnswers.map(c => c.charCodeAt(0) - 65)) : -1; const letterMappingOk = letterAnswers.length > 0 && maxLetterIdx < optionEls.length; optionEls.forEach((option, idx) => { const optionText = normalize(option.textContent); const isLetterMatched = letterMappingOk && letterAnswers.includes(String.fromCharCode(65 + idx)); const isJudgementMatched = question.type === "判断题" && (/^(正确|是|对|√|t|true|right)$/i.test(judgementAnswer) && /^(正确|是|对|√|t|true|right)$/i.test(optionText) || /^(错误|否|错|×|f|false|wrong)$/i.test(judgementAnswer) && /^(错误|否|错|×|f|false|wrong)$/i.test(optionText)); // 精确匹配 / 字母索引匹配 if (isLetterMatched || normalizedAnswers.includes(optionText) || isJudgementMatched) { selected = true; option.click(); option.dispatchEvent(new MouseEvent("click", { bubbles: true })); } // 子串匹配兜底:答案可能含任何标点无法拆分,用选项文本在答案中查找包含关系 else if (optionText && optionText.length >= 2 && rawAnswers.some(raw => normalize(raw).includes(optionText))) { selected = true; option.click(); option.dispatchEvent(new MouseEvent("click", { bubbles: true })); } // 图片匹配:按 URL 最后一段(去查询串/扩展名)比对,兼容超星无扩展名 CDN 图 else if (answerImgs.length > 0) { const getImgKey = (url) => { const clean = String(url || "").split(/[?#]/)[0].replace(/\/+$/, ""); const seg = (clean.split("/").pop() || clean).replace(/\.(jpg|jpeg|png|gif|webp)$/i, ""); return seg.toLowerCase(); }; // 短 key(如 latex 公式图的数字段)只允许完全相等,防止 includes 误撞(912 vs 9120) const imgKeyMatch = (a, b) => a === b || (a.length >= 8 && b.length >= 8 && (a.includes(b) || b.includes(a))); const optionImgs = Array.from(option.querySelectorAll('img')).map(img => img.src || img.getAttribute('data-src') || '').filter(Boolean); let matched = false; for (const answerImg of answerImgs) { if (matched) break; const answerKey = getImgKey(answerImg); for (const optionImg of optionImgs) { const optionKey = getImgKey(optionImg); if (answerKey && optionKey && imgKeyMatch(answerKey, optionKey)) { matched = true; break; } } } if (matched) { selected = true; option.click(); option.dispatchEvent(new MouseEvent("click", { bubbles: true })); } } }); return selected; }; const fillQuestion = (question) => { var _a2; const answers = ((_a2 = question.answer) == null ? void 0 : _a2.answer) || []; if (!answers.length) return false; if (isChoiceType(question.type)) return fillChoice(question, answers); return fillBlank(question, answers); }; const searchAndFill = async (question) => { const { answerMode } = setting.config.basicConfig; const mode = answerMode.value; let handled = false; let bankFilled = false; const apiQuestion = toApiQuestion(question); if (mode !== "ai") { const answerData = await getAccurateAnswer(apiQuestion); if (answerData.code === 1) { question.answer = { code: 1, answer: answerData.data.answer }; setting.userInfo.remainCount = formatLocalRemainCount(answerData.data.remainCount); saveAnswerRecord({ question: apiQuestion, answer: answerData.data.answer, source: "accurate" }); if (fillQuestion(question)) { bankFilled = true; handled = true; } else if (mode === "questionBank") { question.answer.code = 0; handled = true; } else { question.answer.code = 0; } } else if (mode === "questionBank" || answerData.code !== -1004) { // 错误情况:不将 msg 设为 answer,避免将"包月权益生效"等提示当作答案显示 question.answer = { code: answerData.code, answer: [] }; handled = true; } } if (!handled) { if (isChoiceType(question.type)) { const aiAnswerData = await getAIAnswer(apiQuestion); if (aiAnswerData.code === 1) { question.answer = { code: 2, answer: aiAnswerData.data.answer }; setting.userInfo.remainCount = formatLocalRemainCount(aiAnswerData.data.remainCount); saveAnswerRecord({ question: apiQuestion, answer: aiAnswerData.data.answer, source: "ai" }); } else { // 错误情况:不将 msg 设为 answer,避免将错误提示当作答案显示 question.answer = { code: aiAnswerData.code, answer: [] }; } } else { question.answer = { code: -1, answer: ["该题型不支持AI答题"] }; } } if (bankFilled) return true; if ([1, 2].includes(question.answer.code)) { const filled = fillQuestion(question); if (!filled) question.answer.code = 0; return filled; } return false; }; const profile = await getActiveProfile(); if (!profile) { log.insertLog("未识别到支持的题目区域,请确认页面已加载完成..", "warning"); return; } log.insertLog(`进入${profile.name}页面,脚本开始工作..`); setting.tabIndex = "1"; questionStore.accuracy = -1; await ((_a = profile.beforeParse) == null ? void 0 : _a.call(profile)); const answerSheetQuestions = profile.useAnswerSheet ? await waitForAnswerSheetQuestions() : []; if (answerSheetQuestions.length) { log.insertLog(`已从接口读取到${answerSheetQuestions.length}个题目元信息..`); } else if (profile.useAnswerSheet) { const answerSheetPayload = (_unsafeWindow || window).__ABC_ZHS_ANSWER_SHEET__; const { topKeys, candidateKeys } = answerSheetPayload ? describePayloadShape(answerSheetPayload) : { topKeys: [], candidateKeys: [] }; log.insertLog( answerSheetPayload ? `已捕获考试接口但未提取到题目,顶层字段:${topKeys.join(",") || "无"};候选字段:${candidateKeys.join(" | ") || "无"}` : "未捕获到考试题目信息接口,将回退到页面 DOM 解析..", "warning" ); } const questions = []; let correctNum = 0; const handleCurrentPage = async () => { var _a2; let rawQuestions = parseQuestions(profile); if (answerSheetQuestions.length) { const apiQuestion = answerSheetQuestions[questions.length]; const domQuestion = rawQuestions[0] || {}; rawQuestions = apiQuestion ? [ { ...domQuestion, ...apiQuestion, title: apiQuestion.title || domQuestion.title, optionsText: ((_a2 = apiQuestion.optionsText) == null ? void 0 : _a2.length) ? apiQuestion.optionsText : domQuestion.optionsText || [], options: Object.keys(apiQuestion.options || {}).length ? apiQuestion.options : domQuestion.options || {}, element: domQuestion.element || document, optionEls: domQuestion.optionEls || [], answer: { code: -1, answer: [] }, workType: "zhs", refer: location.href, id: apiQuestion.questionId || "zhs" } ] : []; } const pageQuestions = rawQuestions.filter((question) => question.title); if (!pageQuestions.length) { const rootTexts = Array.from(document.querySelectorAll(profile.root)).map( (root) => { var _a3; return (_a3 = root.textContent) == null ? void 0 : _a3.replace(/\s+/g, " ").trim().slice(0, 80); } ); const buttons = Array.from(document.querySelectorAll("button")).map((button) => { var _a3; return (_a3 = button.textContent) == null ? void 0 : _a3.replace(/\s+/g, " ").trim(); }).filter(Boolean).slice(0, 20); log.insertLog( `题目区域数量:${rawQuestions.length},选项数量:${rawQuestions.reduce( (sum, question) => sum + question.optionsText.length, 0 )},题目区域文本:${rootTexts.join(" | ") || "空"},按钮:${buttons.join(" / ") || "无"}`, "warning" ); } if (!pageQuestions.length) return false; questions.push(...pageQuestions); questionStore.questionList = [...questions]; log.insertLog(`当前页面解析到${pageQuestions.length}个题目..`); for (const question of pageQuestions) { const questionIndex = questions.indexOf(question); questionStore.currentQuestionIndex = questionIndex; questionStore.questionList = [...questions]; const filled = await searchAndFill(question); if (filled) correctNum += 1; questionStore.currentQuestionIndex = questionIndex + 1; questionStore.questionList = [...questions]; await sleep(0.5); } return true; }; setting.tabIndex = "2"; let previousTitle = ""; while (true) { const parsed = await handleCurrentPage(); if (!parsed) break; if (!profile.nextButton) break; const nextButton = typeof profile.nextButton === "function" ? profile.nextButton() : document.querySelector(profile.nextButton); if (!nextButton || nextButton.classList.contains("disabled")) break; const currentTitle = ((_b = questions[questions.length - 1]) == null ? void 0 : _b.title) || ""; if (currentTitle && currentTitle === previousTitle) break; previousTitle = currentTitle; nextButton.click(); await sleep(1); } if (!questions.length) { log.insertLog("没有解析到题目,请检查是否进入正确页面...", "warning"); return; } questionStore.currentQuestionIndex = questions.length; questionStore.accuracy = (correctNum / questions.length * 100).toFixed(1); log.insertLog( `答题完毕,正确率为${questionStore.accuracy}%,详情请前往答题模块查看..` ); setTimeout(() => { setting.tabIndex = "1"; }, 2e3); }; const specialStudyLogic = () => { const log = useLogStore(); const setting = useSettingStore(); let videoEl = null; let finishTimer = null; let visibilityTimer = null; let isSwitching = false; let visibilityWarned = false; const pageProfiles = [ { name: "校内课", list: ".file-item", current: ".file-item.active", title: "#sourceTit", done: ".icon-finish", media: "#mediaPlayer, video, audio", needsStudy: (item) => !item.querySelector(".icon-finish") }, { name: "教学空间AI智慧课程", list: ".source-icon", current: ".active-file", title: ".active-file", done: "i.select", media: "video, audio", mapItem: (icon) => { var _a; return (_a = icon.parentElement) == null ? void 0 : _a.parentElement; }, needsStudy: (item) => !item.querySelector("i.select") }, { name: "教学空间AI智慧课程", list: ".el-tree-node", current: ".el-tree-node.is-current", title: ".file-name", done: "label.success", media: "video, audio", filter: (item) => { var _a; return !((_a = item.querySelector(".el-tree-node__children")) == null ? void 0 : _a.children.length); }, needsStudy: (item) => !item.querySelector("label.success") } ]; const waitFor = (target, timeout = 1e4, interval = 300) => { return new Promise((resolve, reject) => { const startTime = Date.now(); const check = setInterval(() => { const result = typeof target === "function" ? target() : document.querySelector(target); if (result) { clearInterval(check); resolve(result); return; } if (Date.now() - startTime > timeout) { clearInterval(check); reject(new Error("等待页面内容加载超时")); } }, interval); }); }; const clickEl = async (el) => { var _a, _b, _c; if (!el) return false; (_a = el.scrollIntoView) == null ? void 0 : _a.call(el, { behavior: "smooth", block: "center" }); await sleep(0.2); (_b = el.click) == null ? void 0 : _b.call(el); (_c = el.dispatchEvent) == null ? void 0 : _c.call(el, new MouseEvent("click", { bubbles: true })); return true; }; const getProfile = () => pageProfiles.find((profile) => document.querySelector(profile.list)); const getItems = (profile) => Array.from(document.querySelectorAll(profile.list)).map((item) => { var _a; return ((_a = profile.mapItem) == null ? void 0 : _a.call(profile, item)) || item; }).filter(Boolean).filter((item) => !profile.filter || profile.filter(item)); const getCurrentItem = (profile) => { var _a; return ((_a = document.querySelector(profile.current)) == null ? void 0 : _a.closest(profile.list)) || getItems(profile).find((item) => { var _a2; return (_a2 = item.matches) == null ? void 0 : _a2.call(item, profile.current); }) || null; }; const getTitle = (item, profile) => { var _a, _b, _c, _d, _e; return ((_b = (_a = item == null ? void 0 : item.querySelector(profile.title)) == null ? void 0 : _a.textContent) == null ? void 0 : _b.trim()) || ((_d = (_c = document.querySelector(profile.title)) == null ? void 0 : _c.textContent) == null ? void 0 : _d.trim()) || ((_e = item == null ? void 0 : item.textContent) == null ? void 0 : _e.trim()) || "未知章节"; }; const getReadableDocuments = () => { const docs = [document]; Array.from(document.querySelectorAll("iframe")).forEach((iframe) => { try { if (iframe.contentDocument) docs.push(iframe.contentDocument); } catch { } }); return docs; }; const isMediaElement = (el) => ["VIDEO", "AUDIO"].includes(el == null ? void 0 : el.tagName); const hasPlayableSource = (media) => { var _a; return Boolean( (media == null ? void 0 : media.currentSrc) || (media == null ? void 0 : media.src) || ((_a = media == null ? void 0 : media.querySelector) == null ? void 0 : _a.call(media, "source")) || (media == null ? void 0 : media.readyState) > 0 ); }; const findMediaElement = (selector) => { var _a; for (const doc of getReadableDocuments()) { const candidates = Array.from(doc.querySelectorAll(selector)); for (const candidate of candidates) { const media = isMediaElement(candidate) ? candidate : (_a = candidate.querySelector) == null ? void 0 : _a.call(candidate, "video, audio"); if (media && (hasPlayableSource(media) || isMediaElement(media))) { return media; } } const fallback = doc.querySelector("video, audio"); if (fallback && (hasPlayableSource(fallback) || isMediaElement(fallback))) { return fallback; } } return null; }; const getNextItem = (profile) => { const items = getItems(profile).filter((item) => profile.needsStudy(item)); if (!items.length) return null; const current = getCurrentItem(profile); const currentIndex = items.indexOf(current); return currentIndex >= 0 ? items[currentIndex + 1] : items[0]; }; const cleanupVideo = () => { if (!videoEl) return; videoEl.onpause = null; videoEl.onended = null; videoEl = null; }; const playMedia = async (profile) => { var _a; cleanupVideo(); try { videoEl = await waitFor(() => { return findMediaElement(profile.media); }, 15e3); videoEl.volume = 0; videoEl.muted = true; log.insertLog(`正在学习:${getTitle(getCurrentItem(profile), profile)}`); (_a = videoEl.play) == null ? void 0 : _a.call(videoEl).catch(() => { }); videoEl.onpause = () => { if (!videoEl || videoEl.ended || isSwitching) return; setTimeout(() => { var _a2; return (_a2 = videoEl == null ? void 0 : videoEl.play) == null ? void 0 : _a2.call(videoEl).catch(() => { }); }, 1e3); }; videoEl.onended = async () => { await sleep(3); await nextItem(profile); }; } catch (error) { log.insertLog(`${error.message || "未找到音视频"},尝试切换下一节..`, "warning"); await sleep(3); await nextItem(profile); } }; const nextItem = async (profile) => { if (isSwitching) return; if (!setting.config.basicConfig.autoChangeChapter.value) { log.insertLog( "自动切换章节未开启,前往设置模块中更改", "warning" ); return; } isSwitching = true; cleanupVideo(); const next = getNextItem(profile); if (!next) { log.insertLog("所有章节视频已完成!"); isSwitching = false; return; } log.insertLog(`跳转到: ${getTitle(next, profile)}`); await clickEl(next); await sleep(5); isSwitching = false; await playMedia(profile); }; const startPolling = (profile) => { finishTimer = setInterval(() => { if (isSwitching) return; const current = getCurrentItem(profile); if (current && !profile.needsStudy(current)) nextItem(profile); }, 2e3); visibilityTimer = setInterval(() => { if (document.visibilityState === "hidden" && !visibilityWarned) { visibilityWarned = true; log.insertLog("页面未保持前台,学习流程可能无法正常推进..", "warning"); } else if (document.visibilityState === "visible") { visibilityWarned = false; } }, 1e3); }; const start = async () => { setting.tabIndex = "1"; if (location.href.includes("ai-course-center") || !location.href.includes("resource") && location.href.includes("tools-hike")) { log.insertLog("请手动进入具体学习资源页面,进入后将自动处理..", "warning"); return; } await sleep(5); const profile = getProfile(); if (!profile) { log.insertLog("未识别到支持的章节列表,请确认页面已加载完成..", "warning"); return; } log.insertLog(`进入智慧树${profile.name}学习页面,脚本开始工作..`); startPolling(profile); await playMedia(profile); }; start(); return () => { cleanupVideo(); if (finishTimer) clearInterval(finishTimer); if (visibilityTimer) clearInterval(visibilityTimer); }; }; const wisdomStudyLogic = () => { const log = useLogStore(); const setting = useSettingStore(); let videoEl = null; let videoCheckTimer = null; let quizCheckTimer = null; let progressBarTimer = null; let visibilityTimer = null; let finishMonitorTimer = null; let lastReplayAt = 0; let visibilityWarned = false; let isSwitching = false; let isHandlingQuiz = false; let isStoppedByStudyTimeWarn = false; let hasLoggedPlaybackRate = false; const RELOAD_COUNT_KEY = "abc-zhs-wisdom-study-reload-count"; const waitFor = (target, timeout = 1e4, interval = 300) => { return new Promise((resolve, reject) => { const startTime = Date.now(); const check = setInterval(() => { const result = typeof target === "function" ? target() : document.querySelector(target); if (result) { clearInterval(check); resolve(result); return; } if (Date.now() - startTime > timeout) { clearInterval(check); reject(new Error("等待页面内容加载超时")); } }, interval); }); }; const clickEl = async (el) => { var _a; if (!el) return false; (_a = el.scrollIntoView) == null ? void 0 : _a.call(el, { behavior: "smooth", block: "center" }); await sleep(0.2); const rect = el.getBoundingClientRect(); const x = rect.left + rect.width / 2; const y = rect.top + rect.height / 2; const target = document.elementFromPoint(x, y) || el; const eventOptions = { bubbles: true, cancelable: true, clientX: x, clientY: y, button: 0, buttons: 1 }; target.dispatchEvent(new MouseEvent("mousemove", eventOptions)); target.dispatchEvent(new MouseEvent("mousedown", eventOptions)); target.dispatchEvent(new MouseEvent("mouseup", { ...eventOptions, buttons: 0 })); target.dispatchEvent(new MouseEvent("click", { ...eventOptions, buttons: 0 })); return true; }; const clickNativeFirst = async (el) => { var _a, _b, _c; if (!el) return false; (_a = el.scrollIntoView) == null ? void 0 : _a.call(el, { behavior: "smooth", block: "center" }); await sleep(0.2); const beforeClass = el.className; const beforeChecked = (_b = el.querySelector("input")) == null ? void 0 : _b.checked; el.click(); await sleep(0.2); const afterChecked = (_c = el.querySelector("input")) == null ? void 0 : _c.checked; if (el.className !== beforeClass || afterChecked !== beforeChecked) return true; return clickEl(el); }; const getConfiguredRate = () => { const rate = Number(setting.config.basicConfig.videoPlayrate.value); return Math.min(rate || 1, 16); }; const getReloadCount = () => Number(sessionStorage.getItem(RELOAD_COUNT_KEY) || 0); const setReloadCount = (count) => { sessionStorage.setItem(RELOAD_COUNT_KEY, String(count)); }; const fixProcessBar = () => { const bar = document.querySelector(".controlsBar"); if (!bar) return; bar.style.display = "block"; bar.style.zIndex = "2"; bar.style.overflow = ""; }; const waitForCaptcha = async () => { if (!document.querySelector(".yidun_popup")) return true; log.insertLog("检测到验证码,请完成验证后继续..", "warning"); try { await waitFor(() => !document.querySelector(".yidun_popup"), 5 * 60 * 1e3, 1e3); log.insertLog("验证码已完成,继续学习.."); return true; } catch { log.insertLog("等待验证码完成超时,请手动刷新页面后继续..", "warning"); return false; } }; const getCourseName = () => { var _a, _b; const text = (_b = (_a = document.querySelector(".course-name")) == null ? void 0 : _a.textContent) == null ? void 0 : _b.trim(); return (text == null ? void 0 : text.replace(/^课程名称[::]\s*/, "")) || "未知课程"; }; const getChapterName = (item) => { var _a; return ((_a = item == null ? void 0 : item.textContent) == null ? void 0 : _a.trim()) || "未知章节"; }; const getChapterItems = () => { let items = Array.from(document.querySelectorAll(".chapter-content .chapter-item")); items = items.map((el) => { const children = el.querySelectorAll(".chapter-content-second"); return children.length ? Array.from(children) : [el]; }).flat(); return items.filter((el) => !el.querySelector(".finish-icon")); }; const getCurrentChapter = () => document.querySelector(".chapter-content-second.current, .chapter-item.current"); const getNextChapter = () => { const items = getChapterItems(); if (!items.length) return null; const current = getCurrentChapter(); const currentIndex = items.indexOf(current); return currentIndex >= 0 ? items[currentIndex + 1] : items[0]; }; const closeMasteryDialog = async () => { const dialog = document.querySelector(".masterylevel-body"); if (!dialog) return false; await clickEl(document.querySelector(".header-box .close-box")); try { await waitFor(() => !document.querySelector(".masterylevel-body"), 1e4, 200); } catch { log.insertLog("等待掌握度弹窗关闭超时,尝试继续切换..", "warning"); } return true; }; const hasStudyTimeWarnDialog = () => Array.from(document.querySelectorAll(".el-overlay .content")).some( (el) => { var _a; return (_a = el.textContent) == null ? void 0 : _a.includes("保持良好的学习习惯"); } ); const handleStudyTimeWarnDialog = () => { if (!hasStudyTimeWarnDialog()) return false; isStoppedByStudyTimeWarn = true; log.insertLog( "检测到学习习惯提示弹窗,已暂停自动切换,请手动处理后刷新页面继续..", "warning" ); return true; }; const handleQuizDialog = async () => { if (isHandlingQuiz) return; const dialog = document.querySelector(".ai-class-exercise-dialog"); if (!dialog) return; isHandlingQuiz = true; try { const options = Array.from(dialog.querySelectorAll(".ques-list .item .option")); log.insertLog(`检测到随堂练习弹窗`); if (options.length) { const captchaPassed = await waitForCaptcha(); if (!captchaPassed) return; const optionIndex = Math.floor(Math.random() * options.length); const option = options[optionIndex]; await clickNativeFirst(option); await sleep(1); } const submitBtn = dialog.querySelector(".el-dialog__footer .el-button.btn"); if (submitBtn) { await clickNativeFirst(submitBtn); await sleep(1); } const closeBtn = dialog.querySelector(".header-icon"); if (closeBtn) await clickNativeFirst(closeBtn); log.insertLog("随堂练习弹窗已完成"); } finally { await sleep(1); isHandlingQuiz = false; } }; const switchPlaybackRate = async (rate) => { if (!videoEl) return; videoEl.playbackRate = rate; if (!hasLoggedPlaybackRate) { hasLoggedPlaybackRate = true; log.insertLog(`已设置播放倍速: ${rate}x`); } }; const switchDefinition = async () => { const controlsBar = document.querySelector(".controlsBar"); if (controlsBar) controlsBar.style.display = "block"; }; const cleanupVideo = () => { if (videoCheckTimer) { clearInterval(videoCheckTimer); videoCheckTimer = null; } if (videoEl) { videoEl.onpause = null; videoEl.onended = null; videoEl = null; } }; const replayCurrentVideo = async () => { if (!(videoEl == null ? void 0 : videoEl.isConnected) || !videoEl.ended) return; const now = Date.now(); if (now - lastReplayAt < 8e3) return; lastReplayAt = now; try { videoEl.currentTime = 1; await switchPlaybackRate(getConfiguredRate()); await videoEl.play(); log.insertLog("当前节点尚未出现完成标记,已重新播放当前视频..", "warning"); } catch { log.insertLog("重新播放当前视频失败,等待下次检查..", "warning"); } }; const handleLoadFailure = async (error) => { log.insertLog(`${error.message || "视频加载失败"},即将刷新页面重试..`, "warning"); const reloadCount = getReloadCount(); if (reloadCount >= 3) { setReloadCount(0); log.insertLog("视频加载多次失败,请检查网络或手动刷新页面..", "warning"); return; } setReloadCount(reloadCount + 1); setTimeout(() => { location.reload(); }, 3e3); }; const playVideo = async () => { if (isStoppedByStudyTimeWarn) return; const captchaPassed = await waitForCaptcha(); if (!captchaPassed) return; cleanupVideo(); try { fixProcessBar(); await switchDefinition(); await sleep(1); videoEl = await waitFor(() => { const media = document.querySelector("video, audio"); return (media == null ? void 0 : media.src) ? media : null; }, 15e3, 300); setReloadCount(0); lastReplayAt = 0; videoEl.currentTime = Math.max(videoEl.currentTime, 1); videoEl.volume = 0; videoEl.muted = true; await switchPlaybackRate(getConfiguredRate()); const current = getCurrentChapter(); log.insertLog(`正在学习:${getChapterName(current)}`); videoEl.play().catch(() => { }); videoCheckTimer = setInterval(() => { if (!(videoEl == null ? void 0 : videoEl.isConnected)) { cleanupVideo(); setTimeout(playVideo, 1500); } }, 3e3); videoEl.onpause = () => { if (!videoEl || videoEl.ended || isSwitching) return; setTimeout(() => { var _a; return (_a = videoEl == null ? void 0 : videoEl.play) == null ? void 0 : _a.call(videoEl).catch(() => { }); }, 1e3); }; videoEl.onended = async () => { if (isSwitching) return; log.insertLog("当前视频播放完毕,等待平台标记完成.."); }; } catch (error) { await handleLoadFailure(error); } }; const nextChapter = async () => { if (isSwitching || isStoppedByStudyTimeWarn) return; if (!setting.config.basicConfig.autoChangeChapter.value) { log.insertLog( "自动切换章节未开启,前往设置模块中更改", "warning" ); return; } isSwitching = true; cleanupVideo(); const hidden = document.querySelector(".chapter-wrapper.hidden"); if (hidden) await clickEl(document.querySelector(".collapse-box") || hidden); document.querySelectorAll(".el-collapse-item__wrap").forEach((el) => { el.style.display = ""; }); await sleep(0.2); if (handleStudyTimeWarnDialog()) { isSwitching = false; return; } await closeMasteryDialog(); const next = getNextChapter(); if (!next) { log.insertLog("所有章节视频已完成!"); isSwitching = false; return; } await clickEl(next); next.scrollIntoView({ behavior: "smooth", block: "center" }); await sleep(5); isSwitching = false; playVideo(); }; const startPolling = () => { quizCheckTimer = setInterval(async () => { await closeMasteryDialog(); await handleQuizDialog(); }, 3e3); progressBarTimer = setInterval(fixProcessBar, 1e3); finishMonitorTimer = setInterval(() => { if (isSwitching || isStoppedByStudyTimeWarn) return; const current = getCurrentChapter(); if (!(current == null ? void 0 : current.querySelector(".finish-icon"))) { replayCurrentVideo(); return; } nextChapter(); }, 2e3); visibilityTimer = setInterval(() => { if (document.visibilityState === "hidden" && !visibilityWarned) { visibilityWarned = true; log.insertLog("页面未保持前台,学习流程可能无法正常推进..", "warning"); } else if (document.visibilityState === "visible") { visibilityWarned = false; } }, 1e3); }; const start = async () => { log.insertLog(`进入智慧树新智慧学习页面(${getCourseName()}),脚本开始工作..`); setting.tabIndex = "1"; await sleep(5); try { await waitFor(".video-play", 2e4); } catch { log.insertLog("等待视频区域加载超时,尝试继续处理..", "warning"); } document.querySelectorAll(".el-overlay,.el-dialog").forEach((dialog) => { dialog.style.display = "none"; }); startPolling(); playVideo(); }; start(); return () => { cleanupVideo(); if (quizCheckTimer) clearInterval(quizCheckTimer); if (progressBarTimer) clearInterval(progressBarTimer); if (finishMonitorTimer) clearInterval(finishMonitorTimer); if (visibilityTimer) clearInterval(visibilityTimer); }; }; const zhsRoutes = { match: [ { keyword: "/studyvideoh5.zhihuishu.com", tab: "1", logic: videoStudyLogic }, { keyword: "ai-smart-course-student-pro.zhihuishu.com/learnPage", tab: "1", logic: smartStudyLogic }, { keyword: "ai-smart-course-student-pro.zhihuishu.com/singleCourse", tab: "1", logic: smartStudyLogic }, { keyword: "smartcoursestudent.zhihuishu.com/learnPage", tab: "1", logic: smartStudyLogic }, { keyword: "smartcoursestudent.zhihuishu.com/singleCourse", tab: "1", logic: smartStudyLogic }, { keyword: "studyplush5.zhihuishu.com", tab: "1", logic: studyPlusLogic }, { keyword: "studywisdomh5.zhihuishu.com/study/index", tab: "1", logic: studyPlusLogic }, { keyword: "smartcourseexam.zhihuishu.com/ReviewExam", tab: "1", logic: smartAnswerLogic }, { keyword: "studentexamcomh5.zhihuishu.com/studentReviewTestOrExam", tab: "1", logic: smartAnswerLogic }, { keyword: "fusioncourseh5.zhihuishu.com/exam", tab: "1", logic: smartAnswerLogic }, { keyword: "studywisdomh5.zhihuishu.com/exam", tab: "1", logic: smartAnswerLogic }, { keyword: "wisdom-mooc.zhihuishu.com/exam", tab: "1", logic: smartAnswerLogic }, { keyword: "examloop.zhihuishu.com/exam", tab: "1", logic: smartAnswerLogic }, { keyword: "zhihuishu.com/atHomeworkExam/stu/homeworkQ/exerciseList", tab: "1", logic: smartAnswerLogic }, { keyword: "zhihuishu.com/atHomeworkExam/stu/examQ/examexercise", tab: "1", logic: smartAnswerLogic }, { keyword: "zhihuishu.com/aidedteaching/sourceLearning", tab: "1", logic: specialStudyLogic }, { keyword: "hike-teaching-center.polymas.com/stu-hike/agent-course-hike/ai-course-center", tab: "1", logic: specialStudyLogic }, { keyword: "tools-hike/studentStudyResource", tab: "1", logic: specialStudyLogic }, { keyword: "fusioncourseh5.zhihuishu.com", tab: "1", logic: aiSmartLogic }, { keyword: "zhihuishu.com/learnPage", tab: "1", logic: learnPageLogic }, { keyword: "wisdom-mooc.zhihuishu.com/study/index", tab: "1", logic: wisdomStudyLogic }, { keyword: "/webExamList?", tab: "0" }, { keyword: "/stuExamWeb.html", tab: "1", logic: autoAnswerLogic } ], defaultRoute: { show: true, tab: "0" } }; const _hoisted_1$1 = { class: "box-content" }; const _sfc_main$2 = { __name: "index", emits: ["customEvent"], setup(__props, { emit: __emit }) { const isShow = vue.ref(false); const log = useLogStore(); const setting = useSettingStore(); const emit = __emit; const url2 = window.location.href; const hostname = window.location.hostname; const messageStore = useMessageStore(); const platformConfigs = [ { key: "cx", name: "超星学习通", domains: [ "chaoxing.com", "edu.cn", "org.cn", "xueyinonline.com", "hnsyu.net", "qutjxjy.cn", "ynny.cn", "hnvist.cn", "fjlecb.cn", "gdhkmooc.com", "cugbonline.cn", "zjelib.cn", "cqrspx.cn", "neauce.com", "zhihui-yun.com", "cqie.cn", "ccqmxx.com", "jxgmxy.com", "sslibrary.com" ], routes: cxRoutes, showAi: true }, { key: "zhs", name: "智慧树", domains: ["zhihuishu.com", "polymas.com"], routes: zhsRoutes, showAi: false } ]; const currentPlatform = platformConfigs.find( (item) => item.domains.some((domain) => hostname === domain || hostname.endsWith("." + domain)) ) ?? null; const settingTabKey = "4"; const contentMap = [ { key: "0", tab: "◉ 首页", component: ScriptHome }, { key: "1", tab: "▤ 日志", component: _sfc_main$9 }, { key: "record", tab: "▣ 记录", component: _sfc_main$3 }, { key: "2", tab: "◈ 答题", component: _sfc_main$7 }, { key: "3", tab: "◎ 搜题", component: _sfc_main$5 }, { key: settingTabKey, tab: "◆ 设置", component: _sfc_main$6 } ]; const availableKeys = contentMap.map((item) => item.key); const normalizeTabKey = (tabKey) => availableKeys.includes(tabKey) ? tabKey : "0"; const activeKey = vue.ref(normalizeTabKey(setting.tabIndex)); const visibleContentMap = vue.computed(() => contentMap.filter((item) => !item.hidden)); const currentContent = vue.computed(() => contentMap.find((item) => item.key === activeKey.value)); const refreshTimer = vue.ref(null); const examIframeTimer = vue.ref(null); const examIframeKeywords = [ "/examcode/examnotes", "/exam/test/examcode", "/exam/test/reVersionTestStartNew", "exam-ans/exam/test/reVersionTestStartNew", "mooc-ans/exam/test/reVersionTestStartNew", "/mooc2/exam/preview", "exam/preview" ]; const normalizeIframeUrl = (iframeSrc) => { if (!iframeSrc) return ""; try { return new URL(iframeSrc, window.location.href).href; } catch (error) { return iframeSrc; } }; const tryOpenExamIframe = () => { if (!url2.includes("/base")) return false; const examIframeUrl = Array.from(document.querySelectorAll("iframe")).map((iframe) => normalizeIframeUrl(iframe.getAttribute("src") || iframe.src)).find( (iframeUrl) => examIframeKeywords.some((keyword) => iframeUrl.includes(keyword)) ); if (!examIframeUrl) return false; const storageKey = `abc-opened-exam-iframe:${examIframeUrl}`; if (sessionStorage.getItem(storageKey)) return true; sessionStorage.setItem(storageKey, "1"); window.open(examIframeUrl, "_blank"); log.insertLog("检测到考试页面已嵌入 iframe,已尝试在新窗口打开.."); return true; }; const startExamIframeWatcher = () => { if (!url2.includes("/base") || examIframeTimer.value) return; tryOpenExamIframe(); examIframeTimer.value = setInterval(() => { tryOpenExamIframe(); }, 1e3); }; const chooseLogicByUrl = () => { var _a; const platformRoutes = currentPlatform == null ? void 0 : currentPlatform.routes; if (!platformRoutes) { isShow.value = false; return; } const route = platformRoutes.match.find((item) => url2.includes(item.keyword)); const shouldShow = route || ((_a = platformRoutes.defaultRoute) == null ? void 0 : _a.show); if (!shouldShow) { isShow.value = false; return; } fetchBasicInfo().then((res) => { console.log("fetchBasicInfo", res); if (res.code) { const { shopLink, version } = res.data; setting.shopLink = shopLink; setting.latestVersion = version; } }); updateTokenInfo(); startExamIframeWatcher(); if (route) { if (!setting.config.basicConfig.token.value) { messageStore.insertMessage( "检测到未配置密钥,无法使用自动答题功能,请前往 “设置” 模块配置", "warning" ); } setting.tabIndex = route.tab; if (route.log) log.insertLog(route.log); if (route.logic) route.logic(); if (setting.config.searchConfig.searchBySelectedText.value) { log.insertLog("划词搜题已开启.."); } autoRefreshPage(); selectTextSearchLogic(document, window); } else { setting.tabIndex = platformRoutes.defaultRoute.tab; } isShow.value = true; }; const updateTokenInfo = () => { if (setting.config.basicConfig.token.value) { getTokenInfo().then((res) => { if (res.code && res.code === 1) { const { count, usedCount, remainCount } = res.data; console.log("getTokenInfo", res.data); setting.userInfo.remainCount = remainCount !== void 0 ? formatLocalRemainCount(remainCount) : formatLocalRemainCount(count - usedCount); } else { setting.userInfo.remainCount = "密钥无效"; messageStore.insertMessage( (res && res.msg) || "密钥无效或已过期,请到用户中心重新生成脚本密钥", "warning" ); } }); } else { setting.userInfo.remainCount = "密钥未配置"; } }; const changeTabIndex = (newActiveKey) => { const nextTabKey = normalizeTabKey(newActiveKey); activeKey.value = nextTabKey; setting.tabIndex = nextTabKey; }; vue.watch( () => setting.tabIndex, (newVal) => { const nextTabKey = normalizeTabKey(newVal); activeKey.value = nextTabKey; if (nextTabKey === settingTabKey) { updateTokenInfo(); } } ); const getPageContexts = () => { const contexts = [{ document, window }]; const collectIframeContexts = (rootDocument) => { const iframes = Array.from(rootDocument.querySelectorAll("iframe")); for (const iframe of iframes) { try { if (iframe.contentDocument && iframe.contentWindow) { contexts.push({ document: iframe.contentDocument, window: iframe.contentWindow }); collectIframeContexts(iframe.contentDocument); } } catch (error) { console.warn("读取页面框架失败", error); } } }; collectIframeContexts(document); return contexts; }; const tryChangeNewVersion = () => { var _a; for (const pageContext of getPageContexts()) { const changeBtn = pageContext.document.querySelector("a.experience"); if (!changeBtn) continue; const onclickText = changeBtn.getAttribute("onclick") || ""; const isNewVersionBtn = ((_a = changeBtn.textContent) == null ? void 0 : _a.trim().includes("体验新版")) || onclickText.includes("toNewFyCourseList"); if (!isNewVersionBtn) continue; const pageWindow = pageContext.window; if (typeof pageWindow.toNewFyCourseList === "function") { pageWindow.toNewFyCourseList(); } else if (typeof changeBtn.onclick === "function") { changeBtn.onclick.call(changeBtn); } else { changeBtn.click(); } return true; } return false; }; const changeNewVersion = async () => { for (let i = 0; i < 10; i++) { if (tryChangeNewVersion()) return; await sleep(0.5); } }; const autoRefreshPage = () => { const timerStatus = setting.config.basicConfig.autoRefresh.value; if (timerStatus) { refreshTimer.value = setInterval(() => { window.location.reload(); }, 18e5); } else { clearInterval(refreshTimer.value); } }; changeNewVersion(); chooseLogicByUrl(); emit("customEvent", isShow.value); return (_ctx, _cache) => { const _component_a_tab_pane = vue.resolveComponent("a-tab-pane"); const _component_a_tabs = vue.resolveComponent("a-tabs"); return vue.openBlock(), vue.createElementBlock("div", _hoisted_1$1, [ vue.createVNode(_component_a_tabs, { activeKey: activeKey.value, "onUpdate:activeKey": _cache[0] || (_cache[0] = ($event) => activeKey.value = $event), onChange: changeTabIndex }, { default: vue.withCtx(() => [ (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(visibleContentMap.value, (item) => { return vue.openBlock(), vue.createBlock(_component_a_tab_pane, { tab: item.tab, key: item.key }, null, 8, ["tab"]); }), 128)) ]), _: 1 }, 8, ["activeKey"]), currentContent.value ? (vue.openBlock(), vue.createBlock(vue.resolveDynamicComponent(currentContent.value.component), { key: 0, class: "tab-content", onAutoRefreshPage: autoRefreshPage, onChangeTab: changeTabIndex }, null, 32)) : vue.createCommentVNode("", true) ]); }; } }; const _hoisted_1 = { key: 0, class: "message-container" }; const _hoisted_2 = { class: "script-info" }; const _sfc_main$1 = { __name: "index", setup(__props) { const scriptInfo = getScriptInfo(); const isMini = vue.ref(false); const mainWrapRef = vue.ref(null); const headerLeftRef = vue.ref(null); const boxSize = vue.ref({ width: 400, height: 700 }); const setting = useSettingStore(); const remembered = getRememberedLogin(); const loginUser = vue.ref(remembered.username || ""); const loginPwd = vue.ref(""); const loginMsg = vue.ref(""); const loginBusy = vue.ref(false); const rememberLogin = vue.ref(remembered.remember !== false); const isAutoLoggedIn = vue.ref(!!remembered.token); const openUserCenter = () => { const token = (remembered && remembered.token) || ""; const user = (remembered && remembered.user) || ""; const userStr = typeof user === "string" ? user : JSON.stringify(user); const url = token ? `${LOCAL_USER_CENTER_URL}&token=${encodeURIComponent(token)}&user=${encodeURIComponent(userStr)}` : setting.shopLink || LOCAL_USER_CENTER_URL; window.open(url, "_blank"); }; const openPricing = () => { const token = (remembered && remembered.token) || ""; const user = (remembered && remembered.user) || ""; const userStr = typeof user === "string" ? user : JSON.stringify(user); const baseUrl = "https://xs.openget.cn/user?section=billing"; const url = token ? `${baseUrl}&token=${encodeURIComponent(token)}&user=${encodeURIComponent(userStr)}` : baseUrl; window.open(url, "_blank"); }; const openRegister = () => { window.open("https://xs.openget.cn/register", "_blank"); }; const openForgot = () => { window.open("https://xs.openget.cn/forgot", "_blank"); }; const loginLocalBackend = async () => { if (!loginUser.value.trim() || !loginPwd.value.trim()) { loginMsg.value = "请填写用户名/邮箱和密码"; return; } loginBusy.value = true; loginMsg.value = "正在登录用户中心..."; try { const password_hash = await sha256Text(loginPwd.value.trim()); const loginRes = await localBackendJson("https://xs.openget.cn/api/auth/login", { username: loginUser.value.trim(), password_hash }); if (loginRes.code !== 200 || !loginRes.token) { loginMsg.value = loginRes.msg || "登录失败"; return; } const syncRes = await syncLoginTokenToScriptKey(setting, loginRes.token); if (syncRes.ok) { if (rememberLogin.value) { setRememberedLogin({ remember: true, username: loginUser.value.trim(), token: loginRes.token, savedAt: Date.now() }); } else { setRememberedLogin({ remember: false, username: loginUser.value.trim() }); } isAutoLoggedIn.value = true; loginMsg.value = "登录成功,已记住登录状态并同步密钥"; } else { loginMsg.value = syncRes.msg || "登录成功,但脚本密钥获取失败,请点用户中心手动复制"; } } catch (e) { loginMsg.value = "登录失败,请检查本地后端是否运行"; } finally { loginBusy.value = false; } }; const autoLoginByRememberedToken = async () => { if (!remembered.token) return; loginBusy.value = true; loginMsg.value = "正在恢复上次登录状态..."; const profile = await localBackendJson("https://xs.openget.cn/api/user/profile", null, remembered.token); if (profile.code === 401 || profile.code === 403) { loginMsg.value = "登录状态已过期,请重新登录"; setRememberedLogin({ remember: rememberLogin.value, username: remembered.username || "" }); isAutoLoggedIn.value = false; loginBusy.value = false; return; } if (profile.code !== 200) { loginMsg.value = "网络异常,暂未恢复登录状态(已保留登录记忆)"; isAutoLoggedIn.value = false; loginBusy.value = false; return; } const syncRes = await syncLoginTokenToScriptKey(setting, remembered.token); loginMsg.value = syncRes.ok ? "已恢复登录状态并同步密钥" : syncRes.msg || "自动登录失败"; loginBusy.value = false; }; const clearRememberedLogin = () => { setRememberedLogin({ remember: rememberLogin.value, username: loginUser.value.trim() }); isAutoLoggedIn.value = false; loginMsg.value = "已清除脚本页登录记忆"; }; vue.onMounted(autoLoginByRememberedToken); const isShow = vue.ref(true); const messageStore = useMessageStore(); const messageList = vue.ref(messageStore.messageList); const backendTabKeys = ["1", "record"]; const isBackendActive = vue.computed(() => backendTabKeys.includes(setting.tabIndex)); const themeMode = vue.ref(GM_getValue("themeMode") || "dark"); const toggleTheme = () => { themeMode.value = themeMode.value === "dark" ? "light" : "dark"; GM_setValue("themeMode", themeMode.value); }; const themeClass = vue.computed(() => themeMode.value === "dark" ? "theme-dark" : "theme-light"); const isPinned = vue.ref(GM_getValue("isPinned") || false); const togglePin = () => { isPinned.value = !isPinned.value; GM_setValue("isPinned", isPinned.value); }; const getPopupContainer = () => { return headerLeftRef.value || mainWrapRef.value || document.body; }; const handleBackendMenuClick = ({ key }) => { if (key === "log") { setting.tabIndex = "1"; return; } if (key === "record") { setting.tabIndex = "record"; } }; vue.watch( setting, (newVal) => { GM_setValue("setting", JSON.stringify(newVal)); const token = setting.config && setting.config.basicConfig && setting.config.basicConfig.token && setting.config.basicConfig.token.value; scheduleSaveAbcCloudConfig(setting, token); }, { deep: true } ); const position = vue.ref({ right: setting.position.right || "0px", top: setting.position.top || "0px" }); const isDragging = vue.ref(false); const dragOffset = vue.ref({ x: 0, y: 0 }); const persistPosition = () => { setting.position.right = position.value.right; setting.position.top = position.value.top; }; const syncPositionWithRect = () => { if (!mainWrapRef.value) return; const rect = mainWrapRef.value.getBoundingClientRect(); position.value = { right: Math.max(0, window.innerWidth - rect.right) + "px", top: Math.max(0, rect.top) + "px" }; }; const changeBoxSize = () => { isMini.value = !isMini.value; }; const miniBallStatus = vue.ref({ text: "待命中", icon: "💤" }); const miniBallLogSummary = vue.ref("暂无日志"); const updateMiniBallInfo = () => { let lastLogText = ""; try { const logStore2 = useLogStore(); const logs = logStore2.logList; if (logs.length > 0) { const last = logs[logs.length - 1]; lastLogText = String(last.text || ""); miniBallLogSummary.value = lastLogText.length > 18 ? lastLogText.slice(0, 18) + "..." : lastLogText; } else { miniBallLogSummary.value = "暂无日志"; } } catch (e) { miniBallLogSummary.value = "暂无日志"; } try { if (/视频|播放|倍速|video/.test(lastLogText)) { miniBallStatus.value = { text: "视频播放中", icon: "🎬" }; } else if (/答题|测验|作业|考试|交卷|提交答案|quiz|homework|exam/.test(lastLogText)) { miniBallStatus.value = { text: "答题中", icon: "✏️" }; } else if (/直播|live|zhibo/.test(lastLogText)) { miniBallStatus.value = { text: "直播中", icon: "📡" }; } else if (/阅读|PPT|电子书|document|ebook/.test(lastLogText)) { miniBallStatus.value = { text: "阅读中", icon: "📖" }; } else { const url = location.href; if (/zhibo\.chaoxing\.com/.test(url)) { miniBallStatus.value = { text: "直播中", icon: "📡" }; } else if (/chaoxing\.com/.test(url)) { miniBallStatus.value = { text: "学习中", icon: "📚" }; } else { miniBallStatus.value = { text: "待命中", icon: "💤" }; } } } catch (e) { miniBallStatus.value = { text: "待命中", icon: "💤" }; } }; let miniBallTimer = null; vue.onMounted(() => { updateMiniBallInfo(); miniBallTimer = setInterval(updateMiniBallInfo, 1500); }); vue.onUnmounted(() => { if (miniBallTimer) clearInterval(miniBallTimer); }); const miniBallRemainCount = vue.computed(() => { try { const rc = setting.userInfo.remainCount; if (rc === "无限" || rc === Infinity) return "∞"; const n = Number(rc); if (isNaN(n) || n <= 0) return String(rc || ""); return n > 9999 ? "∞" : String(n); } catch (e) { return ""; } }); const startDrag = (event) => { if (isPinned.value) return; event.preventDefault(); if (!mainWrapRef.value) return; const rect = mainWrapRef.value.getBoundingClientRect(); boxSize.value = { width: rect.width, height: rect.height }; isDragging.value = true; dragOffset.value = { x: rect.right - event.clientX, y: event.clientY - rect.top }; document.addEventListener("mousemove", drag); document.addEventListener("mouseup", endDrag); }; const drag = (e) => { if (!isDragging.value) return; let newRight = window.innerWidth - e.clientX - dragOffset.value.x; let newY = e.clientY - dragOffset.value.y; if (newRight < 0) { newRight = 0; } if (newRight + boxSize.value.width > window.innerWidth) { newRight = window.innerWidth - boxSize.value.width; } if (newY < 0) { newY = 0; } if (newY + boxSize.value.height > window.innerHeight) { newY = window.innerHeight - boxSize.value.height; } position.value = { right: newRight + "px", top: newY + "px" }; }; const endDrag = () => { isDragging.value = false; persistPosition(); document.removeEventListener("mousemove", drag); document.removeEventListener("mouseup", endDrag); }; const updateBoxSize = () => { if (mainWrapRef.value) { const rect = mainWrapRef.value.getBoundingClientRect(); boxSize.value = { width: rect.width, height: rect.height }; } }; const checkAndFixPosition = () => { if (!mainWrapRef.value) return; const rect = mainWrapRef.value.getBoundingClientRect(); const currentRight = parseInt(position.value.right) || 0; const currentTop = parseInt(position.value.top) || 0; let needFix = false; let newRight = currentRight; let newTop = currentTop; if (currentRight + rect.width > window.innerWidth) { newRight = Math.max(0, window.innerWidth - rect.width); needFix = true; } if (currentRight < 0) { newRight = 0; needFix = true; } if (currentTop < 0) { newTop = 0; needFix = true; } if (currentTop + rect.height > window.innerHeight) { newTop = Math.max(0, window.innerHeight - rect.height); needFix = true; } if (needFix) { position.value = { right: newRight + "px", top: newTop + "px" }; persistPosition(); console.log("位置已修正,防止超出屏幕边界"); } }; vue.onMounted(async () => { await vue.nextTick(); const token = setting.config && setting.config.basicConfig && setting.config.basicConfig.token && setting.config.basicConfig.token.value; if (token) { abcCloudApplying = true; await fetchAndApplyAbcCloudConfig(setting, token); abcCloudApplying = false; } updateBoxSize(); syncPositionWithRect(); persistPosition(); checkAndFixPosition(); if (window.ResizeObserver) { const resizeObserver = new ResizeObserver(() => { updateBoxSize(); checkAndFixPosition(); }); if (mainWrapRef.value) { resizeObserver.observe(mainWrapRef.value); } } }); return (_ctx, _cache) => { const _component_a_alert = vue.resolveComponent("a-alert"); const _component_a_menu_item = vue.resolveComponent("a-menu-item"); const _component_a_menu = vue.resolveComponent("a-menu"); const _component_a_dropdown = vue.resolveComponent("a-dropdown"); return isShow.value ? (vue.openBlock(), vue.createElementBlock("div", { key: 0, class: "main-wrap", style: vue.normalizeStyle(position.value), ref_key: "mainWrapRef", ref: mainWrapRef }, [ isMini.value ? (vue.openBlock(), vue.createElementBlock("div", { key: 0, class: "mini-ball", onClick: vue.withModifiers(changeBoxSize, ["stop"]), onMousedown: _cache[2] || (_cache[2] = vue.withModifiers(startDrag, ["stop"])) }, [ vue.createElementVNode("div", { class: "mini-ball-icon" }, [ miniBallRemainCount.value ? (vue.openBlock(), vue.createElementBlock("span", { key: 0, class: "mini-ball-count" }, vue.toDisplayString(miniBallRemainCount.value), 1)) : vue.createCommentVNode("", true), vue.createTextVNode(" 学神") ]), vue.createElementVNode("div", { class: "mini-ball-body" }, [ vue.createElementVNode("span", { class: "mini-ball-text" }, vue.toDisplayString(miniBallStatus.value.icon + " " + miniBallStatus.value.text), 1), vue.createElementVNode("span", { class: "mini-ball-sub" }, vue.toDisplayString(miniBallLogSummary.value), 1) ]), vue.createElementVNode("div", { class: "mini-ball-shine" }), vue.createElementVNode("div", { class: "mini-ball-glow" }), vue.createElementVNode("div", { class: "mini-ball-orbits" }, [ vue.createElementVNode("i", null, " "), vue.createElementVNode("i", null, " ") ]) ], 32)) : (vue.openBlock(), vue.createElementBlock("div", { key: 1, class: vue.normalizeClass(["main-box", themeClass.value]) }, [ vue.createElementVNode("div", { class: "box-header", onMousedown: startDrag }, [ vue.createElementVNode("div", { class: "header-left", ref_key: "headerLeftRef", ref: headerLeftRef }, [ vue.createElementVNode("span", _hoisted_2, "学神助手 " + vue.toDisplayString(vue.unref(scriptInfo).version), 1), !isMini.value ? (vue.openBlock(), vue.createBlock(_component_a_dropdown, { key: 0, placement: "bottomLeft", trigger: ["hover"], overlayClassName: "backend-dropdown-overlay", getPopupContainer }, { overlay: vue.withCtx(() => [ vue.createVNode(_component_a_menu, { onClick: handleBackendMenuClick }, { default: vue.withCtx(() => [ vue.createVNode(_component_a_menu_item, { key: "log" }, { default: vue.withCtx(() => _cache[4] || (_cache[4] = [ vue.createTextVNode("≡ 日志") ])), _: 1 }), vue.createVNode(_component_a_menu_item, { key: "record" }, { default: vue.withCtx(() => _cache[5] || (_cache[5] = [ vue.createTextVNode("▤ 记录") ])), _: 1 }) ]), _: 1 }) ]), default: vue.withCtx(() => [ vue.createElementVNode("span", { class: vue.normalizeClass(["backend-entry", { active: isBackendActive.value }]), onClick: _cache[0] || (_cache[0] = vue.withModifiers(() => { }, ["stop"])), onMousedown: _cache[1] || (_cache[1] = vue.withModifiers(() => { }, ["stop"])) }, " ▤ ", 34) ]), _: 1 })) : vue.createCommentVNode("", true) ], 512), vue.createElementVNode("div", { style: { "display": "flex", "gap": "4px", "align-items": "center" } }, [ vue.createElementVNode("div", { class: vue.normalizeClass(["pin-btn", { pinned: isPinned.value }]), onClick: vue.withModifiers(togglePin, ["stop"]), onMousedown: _cache[6] || (_cache[6] = vue.withModifiers(() => { }, ["stop"])), title: isPinned.value ? "解除固定" : "固定位置" }, vue.toDisplayString(isPinned.value ? "📌" : "📍"), 9, ["title"]), vue.createElementVNode("div", { class: "theme-toggle", onClick: vue.withModifiers(toggleTheme, ["stop"]), onMousedown: _cache[6] || (_cache[6] = vue.withModifiers(() => { }, ["stop"])), title: themeMode.value === "dark" ? "切换亮色" : "切换暗色" }, vue.toDisplayString(themeMode.value === "dark" ? "☀" : "☾"), 9, ["title"]), vue.createElementVNode("div", { class: "shrink-btn", onClick: vue.withModifiers(changeBoxSize, ["stop"]), onMousedown: _cache[7] || (_cache[7] = vue.withModifiers(() => { }, ["stop"])), title: "收起" }, "—") ]), ], 32), !isMini.value ? (vue.openBlock(), vue.createElementBlock("div", { key: 1, class: "login-bar", style: { "padding": "8px 10px", "background": "var(--t-surface2)", "border-bottom": "1px solid var(--t-border)", "display": "flex", "flex-direction": "column", "gap": "6px" } }, [ vue.createElementVNode("div", { style: { "display": "flex", "gap": "6px", "align-items": "center" } }, [ vue.createElementVNode("span", { style: { "font-size": "12px", "color": "var(--t-text3)", "font-weight": "600", "flex": "none" } }, "🔑"), vue.createElementVNode("input", { value: loginUser.value, onInput: ($event) => loginUser.value = $event.target.value, placeholder: "用户名/邮箱", style: { "flex": "1", "min-width": "0", "height": "28px", "padding": "0 8px", "border": "1px solid var(--t-border2)", "border-radius": "4px", "background": "var(--t-surface)", "color": "var(--t-text)", "font-size": "12px" } }, null, 40, ["value"]), vue.createElementVNode("input", { value: loginPwd.value, onInput: ($event) => loginPwd.value = $event.target.value, onKeydown: vue.withKeys(loginLocalBackend, ["enter"]), type: "password", placeholder: "密码", style: { "flex": "1", "min-width": "0", "height": "28px", "padding": "0 8px", "border": "1px solid var(--t-border2)", "border-radius": "4px", "background": "var(--t-surface)", "color": "var(--t-text)", "font-size": "12px" } }, null, 40, ["value"]), vue.createElementVNode("button", { onClick: loginLocalBackend, disabled: loginBusy.value, style: { "height": "28px", "padding": "0 12px", "border": "none", "border-radius": "4px", "background": "var(--t-primary)", "color": "#fff", "cursor": "pointer", "font-size": "12px", "font-weight": "500", "flex": "none" } }, vue.toDisplayString(loginBusy.value ? "登录中..." : "登录"), 9, ["disabled"]) ]), vue.createElementVNode("div", { style: { "display": "flex", "gap": "10px", "align-items": "center", "color": "var(--t-text3)", "font-size": "11px", "flex-wrap": "wrap" } }, [ vue.createElementVNode("label", { style: { "display": "inline-flex", "align-items": "center", "gap": "4px", "cursor": "pointer" } }, [ vue.createElementVNode("input", { type: "checkbox", checked: rememberLogin.value, onChange: ($event) => { rememberLogin.value = $event.target.checked; if (!rememberLogin.value) clearRememberedLogin(); } }, null, 40, ["checked"]), vue.createTextVNode("记住登录") ]), vue.createElementVNode("a", { href: "javascript:void(0)", onClick: vue.withModifiers(openForgot, ["stop", "prevent"]), style: { "color": "var(--t-primary)", "cursor": "pointer", "text-decoration": "none" } }, "找回密码"), vue.createElementVNode("a", { href: "javascript:void(0)", onClick: vue.withModifiers(openRegister, ["stop", "prevent"]), style: { "color": "var(--t-primary)", "cursor": "pointer", "text-decoration": "none" } }, "注册"), vue.createElementVNode("a", { href: "javascript:void(0)", onClick: vue.withModifiers(openPricing, ["stop", "prevent"]), style: { "color": "var(--t-primary)", "cursor": "pointer", "text-decoration": "none" } }, "套餐"), isAutoLoggedIn.value ? (vue.openBlock(), vue.createElementBlock("button", { key: 0, onClick: clearRememberedLogin, style: { "height": "20px", "padding": "0 6px", "border": "1px solid var(--t-danger)", "border-radius": "4px", "background": "var(--t-surface2)", "color": "var(--t-danger)", "cursor": "pointer", "font-size": "11px" } }, "清除记忆")) : vue.createCommentVNode("", true), loginMsg.value ? (vue.openBlock(), vue.createElementBlock("span", { key: 2, style: { "color": loginMsg.value.includes("成功") ? "var(--t-success)" : "var(--t-danger)" } }, vue.toDisplayString(loginMsg.value), 1)) : vue.createCommentVNode("", true) ]) ])) : vue.createCommentVNode("", true), vue.createVNode(_sfc_main$2, { class: vue.normalizeClass(`box-content ${isMini.value ? "mini" : ""}`), onCustomEvent: _cache[8] || (_cache[8] = (newValue) => isShow.value = newValue) }, null, 8, ["class"]) ], 2)) ], 4)) : vue.createCommentVNode("", true); }; } }; const _sfc_main = /* @__PURE__ */ vue.defineComponent({ __name: "App", setup(__props) { const shadowRoot = vue.inject("shadowRoot"); const getPopupContainer = () => { var _a; return ((_a = shadowRoot == null ? void 0 : shadowRoot.querySelector) == null ? void 0 : _a.call(shadowRoot, "#abc-helper-app")) ?? document.body; }; return (_ctx, _cache) => { const _component_a_style_provider = vue.resolveComponent("a-style-provider"); const _component_a_config_provider = vue.resolveComponent("a-config-provider"); return vue.openBlock(), vue.createBlock(_component_a_config_provider, { getPopupContainer }, { default: vue.withCtx(() => [ vue.createVNode(_component_a_style_provider, { container: vue.unref(shadowRoot) }, { default: vue.withCtx(() => [ vue.createVNode(_sfc_main$1) ]), _: 1 }, 8, ["container"]) ]), _: 1 }); }; } }); const hookError = () => { console.log("hookError"); const oldset = _unsafeWindow.setInterval; const oldout = _unsafeWindow.setTimeout; _unsafeWindow.setInterval = function(...args) { const err = new Error(""); if (err.stack && err.stack.indexOf("checkoutNotTrustScript") !== -1) { return -1; } return oldset.call(this, ...args); }; _unsafeWindow.setTimeout = function(...args) { const err = new Error(""); if (err.stack && err.stack.indexOf("checkoutNotTrustScript") !== -1) { return -1; } return oldout.call(this, ...args); }; }; const url = _unsafeWindow.location.href; const isTopWindow = _unsafeWindow.self === _unsafeWindow.top; if (_unsafeWindow.__abcHelperLoaded) ; else { let injectCSSIntoShadow = function(shadow) { const pendingCSS = document.__abcHelperCSS; if (pendingCSS) { const style = document.createElement("style"); style.textContent = pendingCSS; shadow.appendChild(style); delete document.__abcHelperCSS; } }, observeHeadStyles = function(shadow) { const clonedMap = /* @__PURE__ */ new WeakMap(); const cloneStyle = (node) => { if (!(node instanceof HTMLStyleElement)) return; if (node.dataset.abcIgnore) return; if (clonedMap.has(node)) return; const clone = node.cloneNode(true); clone.dataset.abcIgnore = "true"; shadow.appendChild(clone); clonedMap.set(node, clone); new MutationObserver(() => { clone.textContent = node.textContent; }).observe(node, { childList: true, characterData: true, subtree: true }); }; new MutationObserver((mutations) => { for (const m of mutations) { for (const node of m.addedNodes) cloneStyle(node); } }).observe(document.head, { childList: true }); }; _unsafeWindow.__abcHelperLoaded = true; if (url.includes("zhihuishu.com") && isTopWindow) { hookError(); installAnswerSheetHook(); } const timer = setInterval(() => { if (document.readyState === "complete") { clearInterval(timer); if (!isTopWindow) return; if (document.getElementById("abc-helper-root")) return; const container = document.createElement("div"); container.id = "abc-helper-root"; container.style.cssText = "all:initial!important;position:static!important;display:block!important;width:0!important;height:0!important;overflow:visible!important;opacity:1!important;visibility:visible!important;transform:none!important;filter:none!important;pointer-events:none!important"; document.body.appendChild(container); const shadow = container.attachShadow({ mode: "open" }); const resetStyle = document.createElement("style"); resetStyle.textContent = [ ":host{all:initial!important;display:block!important}", ".no-select{-webkit-user-select:none!important;-moz-user-select:none!important;user-select:none!important}", "input{outline:none;border:none}", "div{box-sizing:border-box}", "ul,ol{padding-inline-start:20px}", ".pin-btn{width:28px;height:28px;display:flex;align-items:center;justify-content:center;border-radius:4px;cursor:pointer;font-size:14px;opacity:0.5;transition:all 0.2s;user-select:none}", ".pin-btn:hover{opacity:0.8}", ".pin-btn.pinned{opacity:1;color:var(--t-primary,#1677ff)}", ".update-notify-bar{display:flex;align-items:center;gap:8px;padding:8px 12px;background:linear-gradient(135deg,#3b82f6,#6366f1);color:#fff;font-size:13px;border-radius:8px 8px 0 0;pointer-events:auto}", ".update-notify-btn{padding:3px 12px;background:rgba(255,255,255,.25);border-radius:4px;cursor:pointer;white-space:nowrap;font-weight:600;transition:background .2s}", ".update-notify-btn:hover{background:rgba(255,255,255,.4)}", ".update-dismiss-btn{cursor:pointer;opacity:.6;font-size:16px;padding:0 4px;transition:opacity .2s}", ".update-dismiss-btn:hover{opacity:1}" ].join("\n"); shadow.appendChild(resetStyle); injectCSSIntoShadow(shadow); observeHeadStyles(shadow); const appDiv = document.createElement("div"); appDiv.id = "abc-helper-app"; shadow.appendChild(appDiv); const app = vue.createApp(_sfc_main); const pinia$1 = pinia.createPinia(); app.provide("shadowRoot", shadow); app.use(pinia$1); app.use(Antd); app.mount(appDiv); // === 自动检测更新 + 顶部提示条 === (function() { var _showUpdateBar = function(remote) { var _insertBar = function() { if (shadow.querySelector('.update-notify-bar')) return; var mainBox = shadow.querySelector('.main-box'); if (!mainBox) return false; var bar = document.createElement('div'); bar.className = 'update-notify-bar'; bar.innerHTML = '🎉 发现新版本 v' + remote.version + ',点击右侧按钮更新' + '立即更新' + ''; mainBox.insertBefore(bar, mainBox.firstChild); bar.querySelector('.update-notify-btn').addEventListener('click', function(e) { e.stopPropagation(); if (typeof GM_openInTab === 'function') { GM_openInTab(remote.download_url, { active: true, insert: true }); } else if (typeof GM !== 'undefined' && typeof GM.openInTab === 'function') { GM.openInTab(remote.download_url, { active: true }); } else { window.open(remote.download_url, '_blank'); } }); bar.querySelector('.update-dismiss-btn').addEventListener('click', function(e) { e.stopPropagation(); bar.remove(); }); return true; }; if (_insertBar()) return; var _obs = new MutationObserver(function(mutations, obs) { if (_insertBar()) obs.disconnect(); }); _obs.observe(shadow, { childList: true, subtree: true }); }; var currentVersion = (_GM_info && _GM_info.script && _GM_info.script.version) || "0.0.0"; GM_xmlhttpRequest({ method: 'GET', url: 'https://xs.openget.cn/scripts/version.json?t=' + Date.now(), onload: function(res) { try { var data = JSON.parse(res.responseText); var remote = data['xueshen-sc']; if (!remote) return; var rv = remote.version.split('.').map(Number); var lv = currentVersion.split('.').map(Number); var newer = false; for (var i = 0; i < Math.max(rv.length, lv.length); i++) { var r = rv[i] || 0, l = lv[i] || 0; if (r > l) { newer = true; break; } if (r < l) break; } if (newer) _showUpdateBar(remote); } catch(e) {} }, onerror: function() {} }); })(); } }, 100); } })(Vue, antd, Pinia, marked, rxjs, md5, DOMPurify); /* ==================== 景德镇艺术职业学院(o-learn.cn)课程助手 ==================== */ (function () { 'use strict'; if (!/o-learn\.cn$/i.test(location.hostname)) return; /* ======================== 配置 ======================== */ const CONFIG = { defaultSpeed: 2, // 默认倍速 autoPlay: true, // 默认开启自动播放 autoMute: true, // 默认静音播放(浏览器策略要求,播放后自动取消) autoNext: true, // 视频结束自动进入下一节 retryInterval: 3000, // 定期扫描兜底间隔(ms) speedLockInterval: 1000, // 倍速锁定间隔(ms) playRetryMax: 10, // 自动播放最大重试次数 playRetryDelay: 1000, // 自动播放重试间隔(ms) quizInterval: 2000, // 答题轮询间隔(ms) logMax: 200, // 日志面板最大条数 backendBase: 'https://xs.openget.cn', }; /* ======================== 持久化存储 ======================== */ const STORE_KEY = 'jdz-art-settings-v4'; let store = { account: { username: '', token: '', scriptKey: '' }, tasks: { exam: true, quiz: true, video: true }, // 任务开关:考试 / 答题 / 视频 speed: CONFIG.defaultSpeed, autoPlay: CONFIG.autoPlay, autoMute: CONFIG.autoMute, autoNext: CONFIG.autoNext, }; function loadStore() { try { const raw = GM_getValue(STORE_KEY, ''); if (raw) store = Object.assign(store, JSON.parse(raw)); } catch (e) { /* ignore */ } } function saveStore() { try { GM_setValue(STORE_KEY, JSON.stringify(store)); } catch (e) { /* ignore */ } } /* ======================== 状态 ======================== */ let state = { currentVideo: null, examStarted: false, examAnswered: false, }; const SPEED_PRESETS = [0.5, 0.75, 1, 1.25, 1.5, 2, 2.5, 3, 4, 8, 16]; /* ======================== 日志 ======================== */ const logStore = { list: [] }; function log(...args) { const msg = args.map(a => { if (typeof a === 'string') return a; if (a instanceof Element) { const id = a.id ? `#${a.id}` : ''; const cls = typeof a.className === 'string' && a.className.trim() ? `.${a.className.trim().split(/\s+/).join('.')}` : ''; return `<${a.tagName.toLowerCase()}${id}${cls}>`; } if (a instanceof Error) return `${a.name}: ${a.message}`; if (a && typeof a === 'object') { const s = JSON.stringify(a); return s && s.length > 120 ? s.slice(0, 120) + '…' : s; } return String(a); }).join(' '); logStore.list.push({ time: new Date(), msg }); if (logStore.list.length > CONFIG.logMax) logStore.list.shift(); console.log('%c[景德镇艺术助手]', 'color:#4CAF50;font-weight:bold', msg); renderLogPanel(); } /* ======================== 学习记录 ======================== */ const RECORD_KEY = 'jdz-art-records'; let records = []; function loadRecords() { try { records = JSON.parse(localStorage.getItem(RECORD_KEY)) || []; } catch (e) { records = []; } } function saveRecords() { try { localStorage.setItem(RECORD_KEY, JSON.stringify(records)); } catch (e) { /* ignore */ } } function getRecordKey(video) { return video.currentSrc || video.src || location.href; } function ensureRecord(video) { const key = getRecordKey(video); let rec = records.find(r => r.key === key); if (!rec) { rec = { key, title: document.title || key, firstTime: Date.now(), watched: 0, done: false, lastSeen: Date.now(), }; records.unshift(rec); saveRecords(); } else { rec.lastSeen = Date.now(); } return rec; } function formatDuration(sec) { sec = Math.floor(sec); const h = Math.floor(sec / 3600); const m = Math.floor((sec % 3600) / 60); const s = sec % 60; return h ? `${h}时${m}分` : m ? `${m}分${s}秒` : `${s}秒`; } function formatTime(ts) { const d = new Date(ts); return `${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`; } function safeText(s) { return String(s).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); } /* ======================== 后端 API ======================== */ function backendRequest(path, { method = 'GET', data = null, token = '' } = {}) { return new Promise((resolve) => { const headers = { 'Content-Type': 'application/json' }; if (token) headers['Authorization'] = 'Bearer ' + token; GM_xmlhttpRequest({ url: CONFIG.backendBase + path, method, headers, data: data ? JSON.stringify(data) : undefined, timeout: 20000, onload: (res) => { try { resolve(JSON.parse(res.responseText || '{}')); } catch (e) { resolve({ code: 500, msg: '响应解析失败' }); } }, onerror: () => resolve({ code: 500, msg: '无法连接本地后端' }), ontimeout: () => resolve({ code: 500, msg: '后端请求超时' }), }); }); } async function sha256Text(text) { const enc = new TextEncoder(); const buf = await crypto.subtle.digest('SHA-256', enc.encode(text || '')); return Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join(''); } async function backendLogin(username, password) { const hash = await sha256Text(password); const res = await backendRequest('/api/auth/login', { method: 'POST', data: { username, password_hash: hash } }); if (res.code !== 200 || !res.token) return { ok: false, msg: res.msg || '登录失败' }; // 同步脚本密钥 const keyRes = await backendRequest('/api/user/script-key', { token: res.token }); if (keyRes.code === 200 && keyRes.script_key) { store.account = { username, token: res.token, scriptKey: keyRes.script_key }; saveStore(); return { ok: true, msg: '登录成功并已同步密钥', scriptKey: keyRes.script_key }; } return { ok: false, msg: '登录成功但密钥获取失败,请到用户中心手动复制' }; } async function backendProfile() { const token = store.account.scriptKey || store.account.token; if (!token) return null; const res = await backendRequest('/api/user/profile', { token }); if (res.code === 200 && res.profile) return res.profile; return null; } async function refreshAccountBadge() { try { const prof = await backendProfile(); if (prof) { store.account.profile = { points_balance: prof.active_member ? null : Number(prof.points_balance || 0), active_member: !!prof.active_member, member_until: prof.member_until || '', }; saveStore(); } } catch (e) { /* ignore */ } if (loginStatusEl) updateLoginStatus(); } function formatQuotaText() { const p = store.account.profile; if (!p) return ''; if (p.active_member) return ` · 🎉包月不限题数`; if (p.points_balance === null || p.points_balance === undefined) return ''; return ` · 剩余 ${p.points_balance} 题`; } function callAnswer(questionPayload, { u = '' } = {}) { return new Promise((resolve) => { const token = store.account.scriptKey || store.account.token || ''; const data = 'question=' + encodeURIComponent(JSON.stringify(questionPayload)) + '&u=' + encodeURIComponent(u) + '&model_mode=auto'; GM_xmlhttpRequest({ url: CONFIG.backendBase + '/api/v1/cx?v=xs-5.0', method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8', 'Authorization': token ? 'Bearer ' + token : '', }, data, timeout: 300000, onload: (res) => { try { const obj = JSON.parse(res.responseText || '{}'); if (obj.code === 200 && obj.data) { // 同步权益信息(剩余题数 / 包月状态),无需额外请求 if (obj.data.remainCount !== undefined || obj.data.profile) { const prof = obj.data.profile || {}; const isActive = !!(prof.active_member !== undefined ? prof.active_member : store.account.profile && store.account.profile.active_member); store.account.profile = { points_balance: isActive ? null : Number(prof.points_balance !== undefined ? prof.points_balance : obj.data.remainCount), active_member: isActive, member_until: prof.member_until || (store.account.profile && store.account.profile.member_until) || '', }; saveStore(); if (loginStatusEl) updateLoginStatus(); } resolve({ ok: true, data: obj.data, msg: obj.msg || (obj.data.bank ? '题库命中' : obj.data.cache ? '缓存命中' : 'AI回答') }); } else if (res.status === 401 || obj.code === 401) { resolve({ ok: false, msg: '登录态失效,请重新登录用户中心' }); } else { resolve({ ok: false, msg: obj.msg || '未命中' }); } } catch (e) { resolve({ ok: false, msg: '响应解析失败' }); } }, onerror: () => resolve({ ok: false, msg: '无法连接后端' }), ontimeout: () => resolve({ ok: false, msg: '后端请求超时' }), }); }); } /* ======================== 图片转 base64 ======================== */ function downloadImageAsDataURL(url) { return new Promise((resolve) => { GM_xmlhttpRequest({ url, method: 'GET', responseType: 'arraybuffer', timeout: 30000, onload: (res) => { try { const bytes = new Uint8Array(res.response); let binary = ''; const chunk = 0x8000; for (let i = 0; i < bytes.length; i += chunk) { binary += String.fromCharCode.apply(null, bytes.subarray(i, i + chunk)); } const type = (res.responseHeaders.match(/content-type:\s*([^\s;\r\n]+)/i) || [])[1] || 'image/jpeg'; resolve('data:' + type + ';base64,' + btoa(binary)); } catch (e) { resolve(null); } }, onerror: () => resolve(null), ontimeout: () => resolve(null), }); }); } /* ======================== 倍速控制 ======================== */ function applySpeed(video, rate) { if (!video) return; try { if (video.__rateHijacked) restorePlaybackRate(video); video.playbackRate = rate; video.defaultPlaybackRate = rate; } catch (e) { /* ignore */ } hijackPlaybackRate(video); if (video.__vjsPlayer) { try { video.__vjsPlayer.playbackRate(rate); } catch (e) { /* ignore */ } } } let speedLockTimer = null; function startSpeedLock(video) { stopSpeedLock(); speedLockTimer = setInterval(() => { if (!video || !state.currentVideo) return; try { if (video.__realRate !== store.speed) { video.__realRate = store.speed; video.defaultPlaybackRate = store.speed; try { video.playbackRate = store.speed; } catch (e) {} } } catch (e) { /* ignore */ } }, CONFIG.speedLockInterval); } function stopSpeedLock() { if (speedLockTimer) { clearInterval(speedLockTimer); speedLockTimer = null; } } function hijackPlaybackRate(video) { if (video.__rateHijacked) return; video.__rateHijacked = true; video.__realRate = store.speed; try { const desc = Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'playbackRate'); Object.defineProperty(video, 'playbackRate', { configurable: true, get: function () { return video.__realRate; }, set: function (v) { const numV = Number(v); if (isNaN(numV)) return; if (numV !== store.speed) { log('🛡️ 网页想修改倍速,已保持为', store.speed + 'x'); if (desc && desc.set) desc.set.call(video, store.speed); video.__realRate = store.speed; return; } if (desc && desc.set) desc.set.call(video, numV); video.__realRate = numV; } }); } catch (e) { log('⚠️ 倍速锁定遇到小问题,已自动改用备用方式', e); } } function restorePlaybackRate(video) { if (!video.__rateHijacked) return; try { delete video.playbackRate; } catch (e) { /* ignore */ } video.__rateHijacked = false; } /* ======================== 拦截 pause ======================== */ function hijackPause(video) { if (video.__pauseHijacked) return; video.__pauseHijacked = true; const originalPause = video.pause.bind(video); video.__originalPause = originalPause; video.pause = function () { if (store.autoPlay && !video.ended && !video.__userPaused) { log('🛡️ 网页想暂停视频,已拦截,继续播放'); return; } return originalPause(); }; video.addEventListener('click', () => { video.__userPaused = video.paused; }); } /* ======================== 自动播放 ======================== */ function autoPlayVideo(video, fromUserGesture) { if (!video || !store.autoPlay) return; let retryCount = 0; const allowSound = fromUserGesture || store.autoMute === false; function attemptPlay() { if (!store.autoPlay || !video || video.ended) return; if (retryCount >= CONFIG.playRetryMax) { log('⚠️ 自动播放多次失败,请在页面上手动点击一次播放按钮'); statusEl && (statusEl.textContent = '请手动点击播放一次'); return; } retryCount++; log(`▶️ 自动播放尝试 #${retryCount}(共 ${CONFIG.playRetryMax} 次)`); if (!video.src && !video.currentSrc) { log('⏳ 视频还在加载中,稍等一下...'); return; } video.muted = !allowSound; video.volume = allowSound ? 1 : 0; const doPlay = (target) => { const p = target.play(); return p || Promise.reject(new Error('no promise')); }; doPlay(video) .then(() => { log('✅ 自动播放成功' + (allowSound ? '(带声)' : '(静音)')); setTimeout(() => { if (allowSound) { video.muted = false; video.volume = 1; } }, 500); applySpeed(video, store.speed); updateUI(); }) .catch((e) => { log(`❌ 浏览器阻止了自动播放,正在尝试其他方式...`); if (video.__vjsPlayer) { try { video.__vjsPlayer.play().then(() => { log('✅ 自动播放成功' + (allowSound ? '(带声)' : '(静音)')); applySpeed(video, store.speed); updateUI(); }).catch(() => { fallbackMuted(); }); return; } catch (err) { /* ignore */ } } fallbackMuted(); }); function fallbackMuted() { if (!allowSound) { setTimeout(attemptPlay, CONFIG.playRetryDelay); return; } video.muted = true; video.volume = 0; const p = (video.__vjsPlayer ? video.__vjsPlayer.play() : video.play()); if (p && p.then) { p.then(() => log('✅ 静音播放成功')) .catch(() => { log('静音播放也失败,等待用户交互后重试'); setTimeout(attemptPlay, CONFIG.playRetryDelay); }); } else { setTimeout(attemptPlay, CONFIG.playRetryDelay); } } } const events = ['loadedmetadata', 'loadeddata', 'canplay', 'canplaythrough', 'durationchange']; const handler = (e) => { if (store.autoPlay && video.paused) { log('▶️ 视频已就绪,自动开始播放'); attemptPlay(); } }; events.forEach(evt => video.addEventListener(evt, handler)); attemptPlay(); const retryTimer = setInterval(() => { if (video.ended || !store.autoPlay) { clearInterval(retryTimer); return; } if (video.paused && video.readyState >= 2) { attemptPlay(); } else if (!video.paused) { clearInterval(retryTimer); } }, CONFIG.playRetryDelay); } /* ======================== 绑定视频 ======================== */ function getVjsPlayer(video) { try { if (window.videojs) { const id = (video.id || '').replace(/_html5_api$/, ''); if (window.videojs.getPlayer) { const p = window.videojs.getPlayer(id); if (p) return p; } const players = window.videojs.getAllPlayers ? window.videojs.getAllPlayers() : []; for (const p of players) { try { if (p.tech_ && p.tech_.el && p.tech_.el() === video) return p; if (p.el && p.el() && p.el().contains(video)) return p; } catch (e) { /* ignore */ } } } } catch (e) { /* ignore */ } return null; } function bindVideo(video) { if (video.__bound) return; video.__bound = true; state.currentVideo = video; video.__vjsPlayer = getVjsPlayer(video); video.__record = ensureRecord(video); log('🎬 发现视频,开始接管', video.__vjsPlayer ? '' : '', `时长=${video.duration ? formatDuration(video.duration) : '未知'}`); video.autoplay = true; video.preload = 'auto'; hijackPause(video); hijackPlaybackRate(video); applySpeed(video, store.speed); startSpeedLock(video); if (store.autoPlay) autoPlayVideo(video); video.addEventListener('pause', () => { if (store.autoPlay && !video.ended && !video.__userPaused) { log('🔄 视频被暂停了,马上自动恢复播放'); setTimeout(() => { if (store.autoPlay && !video.ended) { video.muted = true; video.play().catch(() => {}); } }, 800); } }); video.addEventListener('ended', () => { log('🏁 视频播放结束'); if (video.__record) { video.__record.done = true; saveRecords(); renderRecordPanel(); } if (store.autoNext) gotoNextCourseware(); }); video.addEventListener('ratechange', () => { if (video.__realRate !== store.speed) { log('🔁 网页改了播放速度,已自动改回', store.speed + 'x'); applySpeed(video, store.speed); } }); updateUI(); } /* ======================== 自动下一节 ======================== */ function gotoNextCourseware() { log('🔜 视频播完了,正在自动进入下一节...'); const nextTexts = ['下一节', '下一个', '下一页', '下一章', 'next', 'Next', '继续学习']; const allClickable = document.querySelectorAll('button, a, [role="button"], [class*="next"], [class*="Next"]'); for (const btn of allClickable) { const text = (btn.textContent || '').trim(); if (text && nextTexts.some(t => text.includes(t))) { log('📍 找到"下一节"按钮,自动点击:', text); btn.click(); return; } } const outlineItems = document.querySelectorAll( '[class*="courseware"] li, [class*="chapter"] li, [class*="lesson"] li, [class*="catalog"] li, [class*="tree"] li, [class*="outline"] li, [class*="menu-item"], [class*="node"]' ); let foundCurrent = false; for (const item of outlineItems) { if (foundCurrent) { log('📍 从课程目录中点击下一项:', item.textContent?.trim()?.substring(0, 20)); item.click(); return; } if (item.classList.contains('active') || item.classList.contains('current') || item.getAttribute('aria-current') === 'page' || item.querySelector('.active, .current, [class*="active"], [class*="current"]')) { foundCurrent = true; } } log('⌨️ 没找到"下一节"按钮,尝试用键盘切换'); document.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight', keyCode: 39, bubbles: true })); } /* ======================== 考试自动答题 ======================== */ const JUDGE_OPTIONS = { 0: '正确', 1: '错误' }; function isExamPage() { return /examflow_index|examflow|examPaper|examQuestion/i.test(location.href) || !!document.getElementById('paperSheet') || !!document.querySelector('.exam_page, #examPage'); } /* ---- 答案解析:把后端返回文本解析为 字母列表 或 判断题 目标值 ---- */ function parseAnswerText(text, type) { text = String(text || '').trim(); if (!text) return null; const isJudge = type === '判断题' || type === 'judge'; if (isJudge) { const correct = /正确|true|对|yes|right|^1\b|^1$/.test(text); const wrong = /错误|false|错|no|^0\b|^0$/.test(text); const target = correct && !wrong ? '0' : wrong ? '1' : null; return target === null ? null : { judge: target }; } // 多选题/单选题:提取字母,兼容 "答案:ABC"、"A、文本,B、文本"、"A,B,C" const letters = text.match(/[A-F]/gi) || []; if (letters.length) { const unique = []; for (const l of letters) { const u = l.toUpperCase(); if (!unique.includes(u)) unique.push(u); } return { letters: unique }; } // 数字索引 1,2,3 const nums = text.match(/\d+/g) || []; if (nums.length) return { letters: nums.map(n => String.fromCharCode(64 + parseInt(n, 10))) }; return null; } /* ---- 双结构题目解析 ---- 结构A(纯文本): #qContent{N} > .divQuestionTitle .q-title-text + .questionOptions .q_option(input + .a11y-option) 结构B(图片/旧): #paperSheet > .set-group > .set-item (.score + a.option / label.exam_radio + 题干图片) */ function getExamQuestions() { const result = []; // 结构A:qContent{N} const qContents = document.querySelectorAll('.exam_page .q_content, #examPage .q_content, div.q_content[id^="qContent"]'); if (qContents.length) { qContents.forEach(qc => { const idMatch = (qc.id || '').match(/qContent(\d+)/); const titleEl = qc.querySelector('.q-title-text'); const title = titleEl ? (titleEl.textContent || '').replace(/\s+/g, ' ').trim() : ''; const qidInput = qc.querySelector('input[name="quesId"]'); const qid = qidInput ? qidInput.value : ''; const typeEl = qc.querySelector('.divQuestionTitle'); const typeName = typeEl ? (typeEl.getAttribute('data-q-type-name') || '') : ''; const opts = []; qc.querySelectorAll('.q_option').forEach(op => { const input = op.querySelector('input[type=radio], input[type=checkbox]'); const clickable = op.querySelector('.a11y-option') || op.querySelector('.radio_off') || op.querySelector('.checkbox_off') || op; const labelEl = clickable; const labelText = (labelEl.textContent || '').replace(/\s+/g, ' ').trim(); const optLetter = clickable.getAttribute && (clickable.getAttribute('data-option-label') || ''); const inputVal = input ? input.value : ''; const checked = input ? !!input.checked : false; opts.push({ text: labelText, el: clickable, input, value: inputVal, letter: optLetter, checked }); }); const imgs = Array.from(qc.querySelectorAll('img')); const imgSrcs = imgs.map(img => img.src || img.getAttribute('data-src') || '').filter(Boolean); if (idMatch) { result.push({ num: parseInt(idMatch[1], 10), type: typeName, title, qid, opts, judges: typeName === '判断题' ? opts.map((o, i) => ({ value: o.value, text: o.text, el: o.el })) : [], stemText: title, imgSrcs, el: qc, mode: 'A', isJudge: typeName === '判断题', }); } }); if (result.length) return result; } // 结构B:paperSheet set-item const sheet = document.getElementById('paperSheet'); if (!sheet) return []; const items = sheet.querySelectorAll('.set-item'); items.forEach((item, i) => { const scoreEl = item.querySelector('.score'); const numMatch = scoreEl ? (scoreEl.textContent || '').match(/(\d+)/) : null; if (!numMatch) return; const num = parseInt(numMatch[1], 10); const opts = []; item.querySelectorAll('a.option').forEach(o => { opts.push({ text: (o.textContent || '').trim(), el: o, letter: (o.getAttribute('data-option-label') || (o.textContent || '').trim()).toUpperCase() }); }); const radioEls = item.querySelectorAll('label.exam_radio'); const judges = []; radioEls.forEach(label => { const input = label.querySelector('input[type=radio]'); judges.push({ value: input ? input.value : '', text: (label.textContent || '').trim(), el: label }); }); const stemEl = item.querySelector('.stem, [class*="stem"], .fl, .question-content, [class*="question"]'); const stemText = stemEl ? (stemEl.textContent || '').replace(/\s+/g, ' ').trim() : ''; const imgs = item.querySelectorAll('img'); const imgSrcs = Array.from(imgs).map(img => img.src || img.getAttribute('data-src') || '').filter(Boolean); result.push({ num, opts, judges, stemText, imgSrcs, el: item, mode: 'B', isJudge: judges.length > 0, type: judges.length ? '判断题' : (opts.length ? '单选题' : '') }); }); return result; } /* ---- 选项文本匹配:模型可能直接返回选项内容(如"发展")而非字母 ---- */ function matchOptionTextToLetters(q, answer) { const clean = (s) => String(s || '') .replace(/^[A-F][、..))::]\s*/i, '') .replace(/^答案[::]\s*/i, '') .replace(/^选项[::]\s*/i, '') .replace(/\s+/g, '').trim(); const ans = clean(answer).toLowerCase(); if (!ans) return []; const isSingle = /单选/.test(q.type || '') || (q.opts[0] && q.opts[0].input && q.opts[0].input.type === 'radio'); const found = []; q.opts.forEach((o, idx) => { const optText = clean(o.text || o.textContent || '').toLowerCase(); if (!optText) return; const hit = optText === ans || optText.includes(ans) || (ans.length >= 2 && ans.includes(optText)); if (hit) { const letter = o.letter ? o.letter.toUpperCase() : String.fromCharCode(65 + idx); if (!found.includes(letter)) found.push(letter); } }); return isSingle ? found.slice(0, 1) : found; } /* ---- 单题填答:兼容两套结构 ---- */ function fillExamAnswer(q, answer) { const parsed = parseAnswerText(answer, q.type); // 判断题 if (q.isJudge) { let targetValue = parsed ? parsed.judge : null; // 模型可能返回字母(A=正确/第一个选项,B=错误/第二个选项),按选项顺序映射 if (targetValue === null) { const letterMatch = String(answer || '').match(/\b([A-F])\b/i); if (letterMatch) { const idx = letterMatch[1].toUpperCase().charCodeAt(0) - 65; const list = q.mode === 'A' ? q.opts : q.judges; if (list && list[idx] !== undefined) targetValue = String(list[idx].value); } } if (targetValue === null) return false; if (q.mode === 'A') { // 结构A: 通过全局 renderCheckboxOrRadioByLabel(num, qid, idx) 或点击 let clicked = false; q.opts.forEach((o, idx) => { if (String(o.value) === targetValue && !o.input.checked) { const fn = window.renderCheckboxOrRadioByLabel; if (typeof fn === 'function') { try { fn(String(q.num), q.qid, String(idx)); clicked = true; } catch (e) {} } else { o.el.click(); clicked = true; } } }); return clicked; } // 结构B for (const j of q.judges) { if (String(j.value) === targetValue) { j.el.classList.add('r_on'); if (typeof renderCheckboxOrRadio === 'function') { try { renderCheckboxOrRadio(q.num, q.el.querySelector('.wrap') ? q.el.querySelector('.wrap').id : '', JSON.stringify(targetValue)); } catch (e) {} } const input = j.el.querySelector('input[type=radio]'); if (input) input.checked = true; return true; } } return false; } // 选择题(单选/多选) let letters = parsed ? (parsed.letters || []) : []; // 答案没含字母时,尝试按选项文本匹配(模型可能直接返回选项内容) if (!letters.length) { letters = matchOptionTextToLetters(q, answer); } if (!letters.length) return false; let clicked = 0; q.opts.forEach((o, idx) => { // 计算该选项对应的字母(数据里可能已有 letter,否则用索引) const letter = o.letter ? o.letter.toUpperCase() : String.fromCharCode(65 + idx); const want = letters.includes(letter); if (want && !o.checked) { if (q.mode === 'A' && typeof window.renderCheckboxOrRadioByLabel === 'function') { try { window.renderCheckboxOrRadioByLabel(String(q.num), q.qid, String(idx)); clicked++; } catch (e) {} } else { o.el.click(); clicked++; } } }); return clicked >= 0; } function formatAnswerForLog(raw) { // 清理答案文本:只保留有效字母(或 正确/错误),去掉 "答案:" 等前缀 const s = String(raw || '').trim(); if (!s) return ''; const judge = s.match(/正确|错误|对|错/); if (judge) return judge[0]; const letters = s.match(/[A-F]/gi); if (letters && letters.length) return letters.map(l => l.toUpperCase()).join(''); return s.slice(0, 20); } function formatModelForLog(model) { const m = String(model || ''); if (!m) return ''; // 去掉厂商前缀 tencent/ qwen/ deepseek/ 等,只留模型名 const seg = m.split('/').pop(); return seg.slice(0, 24); } async function answerExamQuestion(q, index) { let payload = { question: q.stemText || q.title, type: q.type || (q.isJudge ? 'judge' : 'single') }; if (q.opts.length) payload.options = q.opts.map(o => o.text); // 图片题:下载图片 const dataURLs = []; for (const src of q.imgSrcs) { const dataUrl = await downloadImageAsDataURL(src); if (dataUrl) dataURLs.push(dataUrl); } if (dataURLs.length) payload.images = dataURLs; if (!q.stemText && !dataURLs.length) return { ok: false, msg: '题目为空' }; log(`📝 [${q.num}题] 调后端获取答案...`); let res = await callAnswer(payload, { u: store.account.username || '' }); // 超时/网络失败时自动重试一次(后端冷却机制会让下一次换模型) if (!res.ok && /超时|无法连接|未命中|响应解析/.test(res.msg || '')) { log(`⚠️ [${q.num}题] ${res.msg},3秒后自动重试...`); await new Promise(r => setTimeout(r, 3000)); res = await callAnswer(payload, { u: store.account.username || '' }); if (res.ok) log(`✅ [${q.num}题] 重试成功`); } if (!res.ok) return res; return { ok: true, answer: res.data.answer, model: res.data.model, msg: res.msg || '' }; } /* ---- 整页图片试卷模式 ---- 试卷题干是整页大图(#docImagesViewArea 下 .cont > img),一张图含多道题。 逐张图调用后端视觉模型,返回 JSON: [{"num":1,"answer":"A"}, ...],再逐题填答。 扣点按实际 API 调用次数(每张图一次调用扣1点,包月不扣)。 */ function extractImagePages() { const area = document.getElementById('docImagesViewArea'); if (!area) return []; const srcs = []; area.querySelectorAll('img').forEach(img => { const s = img.src || img.getAttribute('data-src') || ''; if (s && /(\.jpe?g|\.png|\.gif|\.webp)(\?|$)/i.test(s)) srcs.push(s); }); // 若 docImagesViewArea 为空,退回找页面里非主题的整页大图 if (!srcs.length) { document.querySelectorAll('img').forEach(img => { const s = img.src || ''; if (s && /preview-office-convert|resourcePath|upload/.test(s) && img.naturalWidth > 300) srcs.push(s); }); } return srcs; } function parseImagePageAnswers(text) { // 兼容: [{"num":1,"answer":"A"},...] / {1:"A",...} / 逐行 "1.A" const raw = String(text || '').trim(); if (!raw) return null; try { const arr = JSON.parse(raw); if (Array.isArray(arr)) { const map = {}; arr.forEach(item => { const num = parseInt(item.num !== undefined ? item.num : item.index, 10); if (num && item.answer !== undefined) map[num] = String(item.answer).trim(); }); return map; } if (typeof arr === 'object') { const map = {}; Object.keys(arr).forEach(k => { const num = parseInt(k, 10); if (num) map[num] = String(arr[k]).trim(); }); return map; } } catch (e) { /* not pure json */ } const map = {}; // 兼容 "1.A 2.B 3.C" 或 "1、A" 等 const re = /(\d{1,3})\s*[.、::))]\s*([A-F]+|正确|错误)/gi; let m; while ((m = re.exec(raw))) map[parseInt(m[1], 10)] = m[2].toUpperCase(); return Object.keys(map).length ? map : null; } async function answerImagePages() { const pages = extractImagePages(); if (!pages.length) return false; log(`🖼️ 检测到整页图片型试卷,共 ${pages.length} 张整页图,开始识别...`); // 等待 set-item 渲染(填答需要) let items = []; for (let i = 0; i < 10; i++) { items = getExamQuestions(); if (items.length) break; await new Promise(r => setTimeout(r, 1000)); } if (!items.length) { log('⚠️ 图片已找到但题目选项未渲染'); return false; } const byNum = {}; items.forEach(q => { byNum[q.num] = q; }); let filledCount = 0; for (let p = 0; p < pages.length; p++) { log(`🖼️ [第${p + 1}/${pages.length}张图] 下载并识别...`); const dataUrl = await downloadImageAsDataURL(pages[p]); if (!dataUrl) { log(`❌ 第${p + 1}张图下载失败`); continue; } const payload = { question: '请识别这张试卷图片中的全部题目(题干、选项),并按题号逐题给出答案。只输出 JSON 数组,格式:[{"num":1,"answer":"A"},{"num":2,"answer":"B,C"},...]。判断题用"正确"或"错误"。不要输出其他任何内容。', images: [dataUrl], type: 'image', platform: 'jdz-exam-image', refer: location.href, }; const res = await callAnswer(payload, { u: store.account.username || '' }); if (!res.ok) { log(`❌ 第${p + 1}张图识别失败: ${res.msg}`); continue; } const answers = parseImagePageAnswers(res.data.answer); if (!answers) { log(`⚠️ 第${p + 1}张图返回无法解析: ${String(res.data.answer).slice(0, 120)}`); continue; } const nums = Object.keys(answers).map(Number); log(`✅ 第${p + 1}张图识别出 ${nums.length} 题答案 (${res.msg})`); for (const num of nums) { const q = byNum[num]; if (!q) { log(`⚠️ 题${num}未在页面中找到选项,跳过`); continue; } const ok = fillExamAnswer(q, answers[num]); if (ok) filledCount++; log(`${ok ? '✅' : '⚠️'} [${num}题] ${ok ? '已填写' : '未能填写'} 答案=${formatAnswerForLog(answers[num]) || answers[num]}`); await new Promise(r => setTimeout(r, 800)); } await new Promise(r => setTimeout(r, 1500)); } log(`🏁 整页图片答题完成,共填写 ${filledCount} 题`); return filledCount > 0; } async function runExamAutoAnswer() { if (state.examStarted) return; state.examStarted = true; log('📖 检测到考试页面,开始自动答题'); // 整页图片型试卷优先处理 if (extractImagePages().length) { const ok = await answerImagePages(); state.examStarted = false; if (ok && store.tasks.examAutoSubmit) { setTimeout(() => { trySubmitExam(); }, 2000); } return; } // 等待题目渲染 for (let i = 0; i < 10; i++) { const qs = getExamQuestions(); if (qs.length >= 1) break; await new Promise(r => setTimeout(r, 1000)); } const questions = getExamQuestions(); if (!questions.length) { log('⚠️ 未解析到题目,可能是整页图片型试卷,跳过'); state.examStarted = false; return; } log(`🔍 解析到 ${questions.length} 道题 (${questions[0].mode === 'A' ? '文本结构' : '图片结构'})`); for (let i = 0; i < questions.length; i++) { const q = questions[i]; const answered = q.isJudge ? q.opts.some(o => o.checked) : q.opts.some(o => o.checked); if (answered) continue; const res = await answerExamQuestion(q, i); if (!res.ok) { log(`❌ [${q.num}题] 获取失败: ${res.msg}`); continue; } const ok = fillExamAnswer(q, res.answer); const ansShow = formatAnswerForLog(res.answer); const hit = res.msg && /命中/.test(res.msg) ? ` ${res.msg}` : ''; log(`${ok ? '✅' : '⚠️'} [${q.num}题] ${ok ? '已填写' : '未能填写'} 答案=${ansShow || '?'}${hit}`); await new Promise(r => setTimeout(r, 1500)); } state.examStarted = false; log('🏁 考试自动答题完成'); if (store.tasks.examAutoSubmit) { setTimeout(() => { trySubmitExam(); }, 2000); } } function trySubmitExam() { const links = document.querySelectorAll('a, button'); for (const el of links) { const t = (el.textContent || '').trim(); if (t === '交卷') { log('📤 自动点击交卷'); el.click(); setTimeout(() => { document.querySelectorAll('button').forEach(b => { const bt = (b.textContent || '').trim(); if (bt === '交卷') b.click(); }); }, 1000); return; } } } function setupExamWatcher() { setInterval(() => { if (!store.tasks.exam) return; if (isExamPage() && !state.examStarted) { runExamAutoAnswer(); } }, 3000); } /* ======================== 作业/测验自动答题 ======================== */ function extractWorkQuestions() { // 通用提取:页面上可见的题目容器 const selectors = [ '[class*="question"]', '[class*="exam"]', '[class*="work"]', '[class*="quiz"]', '.set-item', '[class*="stem"]' ]; const seen = new Set(); const questions = []; document.querySelectorAll(selectors.join(',')).forEach(el => { if (seen.has(el)) return; seen.add(el); const text = (el.textContent || '').trim(); if (text.length < 4) return; // 选项 const opts = []; el.querySelectorAll('input[type=radio], input[type=checkbox]').forEach(inp => { const label = inp.closest('label'); const optText = label ? (label.textContent || '').replace(/\s+/g, ' ').trim() : ''; if (optText) opts.push({ text: optText, el: label || inp, input: inp }); }); questions.push({ el, text, opts }); }); return questions; } async function runWorkAutoAnswer() { const questions = extractWorkQuestions(); if (!questions.length) return; log(`📝 检测到作业/测验 ${questions.length} 个题目容器`); for (let i = 0; i < questions.length; i++) { const q = questions[i]; const opts = q.opts; if (!opts.length) continue; const already = opts.some(o => o.input && o.input.checked); if (already) continue; log(`📝 作业第${i + 1}题 调后端...`); const payload = { question: q.text.slice(0, 300), options: opts.map(o => o.text), type: 'single' }; const res = await callAnswer(payload, { u: store.account.username || '' }); if (!res.ok) { log(`❌ 作业第${i + 1}题失败: ${res.msg}`); continue; } const answer = String(res.data.answer || '').trim(); const letters = answer.match(/[A-E]/gi) || []; let clicked = 0; opts.forEach((o, idx) => { const letter = String.fromCharCode(65 + idx); if (letters.some(l => l.toUpperCase() === letter)) { if (o.input && !o.input.checked) { o.el.click(); clicked++; } } }); log(`${clicked ? '✅' : '⚠️'} 作业第${i + 1}题 已处理 (${res.msg})`); await new Promise(r => setTimeout(r, 1500)); } } function setupWorkWatcher() { setInterval(() => { if (!store.tasks.quiz) return; if (/work|homework|quiz|test|作业|测验/.test(location.href)) { runWorkAutoAnswer(); } }, 5000); } /* ======================== 视频检测(含 Shadow DOM) ======================== */ function findVideosInShadow(root) { const results = []; try { root.querySelectorAll('video').forEach(v => results.push(v)); root.querySelectorAll('*').forEach(el => { if (el.shadowRoot) { el.shadowRoot.querySelectorAll('video').forEach(v => results.push(v)); findVideosInShadow(el.shadowRoot).forEach(v => results.push(v)); } }); } catch (e) { /* ignore */ } return results; } function scanForVideos() { if (!store.tasks.video) return; let videos = findVideosInShadow(document); document.querySelectorAll('iframe').forEach((iframe) => { try { const iframeVideos = findVideosInShadow(iframe.contentDocument); iframeVideos.forEach(v => { if (!videos.includes(v)) videos.push(v); }); } catch (e) { /* 跨域 */ } }); if (videos.length > 0) { videos.forEach(bindVideo); if (!state.currentVideo || !document.contains(state.currentVideo)) { state.currentVideo = videos[videos.length - 1]; } updateUI(); } } /* ======================== SPA 路由监听 ======================== */ let lastUrl = location.href; function watchRouteChange() { const check = () => { if (location.href !== lastUrl) { lastUrl = location.href; log('🔀 页面已切换,正在重新检测视频...'); state.currentVideo = null; stopSpeedLock(); setTimeout(scanForVideos, 1500); setTimeout(scanForVideos, 3000); setTimeout(scanForVideos, 5000); } }; window.addEventListener('hashchange', check); window.addEventListener('popstate', check); const originalPushState = history.pushState; const originalReplaceState = history.replaceState; history.pushState = function (...args) { originalPushState.apply(this, args); setTimeout(check, 200); }; history.replaceState = function (...args) { originalReplaceState.apply(this, args); setTimeout(check, 200); }; } /* ======================== MutationObserver ======================== */ function setupGlobalObserver() { const observer = new MutationObserver((mutations) => { for (const mutation of mutations) { for (const node of mutation.addedNodes) { if (node.nodeType !== 1) continue; if (node.tagName === 'VIDEO') { bindVideo(node); } else if (node.querySelector) { try { const vids = node.querySelectorAll('video'); vids.forEach(v => { if (!v.__bound) bindVideo(v); }); } catch (e) { /* ignore */ } } } } }); const start = () => { observer.observe(document.documentElement, { childList: true, subtree: true }); log('✅ 视频自动检测已开启,持续监测页面变化'); }; if (document.documentElement) start(); else document.addEventListener('DOMContentLoaded', start); } /* ======================== Page Visibility ======================== */ function setupVisibilityHandler() { document.addEventListener('visibilitychange', () => { if (!document.hidden && store.autoPlay && state.currentVideo) { if (state.currentVideo.paused && !state.currentVideo.ended) { log('🔄 回到页面了,恢复播放'); state.currentVideo.__userPaused = false; state.currentVideo.muted = true; state.currentVideo.play().catch(() => {}); } } }); } /* ======================== 用户交互解锁 ======================== */ function setupUserInteractionUnlock() { const unlock = () => { if (store.autoPlay && state.currentVideo && state.currentVideo.paused) { log('✅ 检测到你的操作,开始播放'); state.currentVideo.play().catch(() => {}); } }; document.addEventListener('click', unlock, { once: false }); document.addEventListener('keydown', unlock, { once: false }); } /* ======================== UI ======================== */ let panelEl = null; let speedDisplayEl = null; let statusEl = null; let homeTitleEl = null; let logListEl = null; let recordListEl = null; let activePage = 'home'; let loginStatusEl = null; function switchPage(name) { activePage = name; document.querySelectorAll('.olvh-tab').forEach(t => t.classList.toggle('active', t.dataset.page === name)); document.querySelectorAll('.olvh-page').forEach(p => p.classList.toggle('active', p.id === 'olvh-page-' + name)); } function renderLogPanel() { if (!logListEl) return; if (!logStore.list.length) { logListEl.innerHTML = '
暂无日志
'; return; } const html = logStore.list.slice().reverse().map(item => { const t = item.time; const ts = `${String(t.getHours()).padStart(2, '0')}:${String(t.getMinutes()).padStart(2, '0')}:${String(t.getSeconds()).padStart(2, '0')}`; return `
${ts}${safeText(item.msg)}
`; }).join(''); logListEl.innerHTML = html; logListEl.scrollTop = 0; } function renderRecordPanel() { if (!recordListEl) return; if (!records.length) { recordListEl.innerHTML = '
暂无学习记录
'; return; } const html = records.map(r => { const badge = r.done ? '已完成' : '学习中'; return `
${safeText(r.title)}
${badge} · 已学 ${formatDuration(r.watched)} · ${formatTime(r.lastSeen)}
`; }).join(''); recordListEl.innerHTML = html; } function updateLoginStatus() { if (!loginStatusEl) return; if (store.account.scriptKey) { loginStatusEl.innerHTML = `✅ 已登录:${safeText(store.account.username || '')}${formatQuotaText()}`; } else if (store.account.token) { loginStatusEl.innerHTML = `✅ 已登录(未同步密钥):${safeText(store.account.username || '')}`; } else { loginStatusEl.innerHTML = '❌ 未登录,请填写账号密码'; } } function createPanel() { if (window.top !== window.self) return; if (document.getElementById('olvh-panel')) return; if (!document.body) { document.addEventListener('DOMContentLoaded', createPanel); return; } panelEl = document.createElement('div'); panelEl.id = 'olvh-panel'; panelEl.innerHTML = `
🎨 景德镇艺术-学习助手
首页
答题
日志
记录
设置
🔑 账号登录
登录后自动获取脚本密钥,用于云端AI答题。也支持用户名或邮箱登录。
尚未检测到视频
等待检测视频...
📋 自动任务
📖 考试自动答题
进入考试页自动识别题目并填写答案
📝 作业/测验自动答题
进入作业/测验页自动填写答案
🎬 视频自动处理
自动播放、倍速、自动下一节
勾选的任务才会执行,未勾选则跳过。
暂无日志
暂无学习记录
视频倍数 ${store.speed}x
${SPEED_PRESETS.map(s => `
${s}x
`).join('')}
自动播放
静音播放
自动下一节
考试自动交卷
`; document.body.appendChild(panelEl); speedDisplayEl = document.getElementById('olvh-speed-display'); statusEl = document.getElementById('olvh-status-text'); homeTitleEl = document.getElementById('olvh-home-title'); logListEl = document.getElementById('olvh-log-list'); recordListEl = document.getElementById('olvh-record-list'); loginStatusEl = document.getElementById('olvh-login-status'); bindUIEvents(); makeDraggable(); renderLogPanel(); renderRecordPanel(); updateLoginStatus(); } function bindUIEvents() { const toggleBtn = document.getElementById('olvh-toggle'); const body = document.getElementById('olvh-body'); toggleBtn.addEventListener('click', () => { body.classList.toggle('collapsed'); toggleBtn.textContent = body.classList.contains('collapsed') ? '+' : '−'; }); document.querySelectorAll('.olvh-tab').forEach(tab => { tab.addEventListener('click', () => switchPage(tab.dataset.page)); }); document.getElementById('olvh-speed-grid').addEventListener('click', (e) => { const btn = e.target.closest('.olvh-speed-btn'); if (!btn) return; setSpeed(parseFloat(btn.dataset.speed)); }); const customInput = document.getElementById('olvh-custom-input'); const customBtn = document.getElementById('olvh-custom-btn'); const applyCustom = () => { const val = parseFloat(customInput.value); if (val > 0 && val <= 16) setSpeed(val); else { customInput.value = ''; customInput.placeholder = '0.1~16'; } }; customBtn.addEventListener('click', applyCustom); customInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') applyCustom(); }); // 登录 const loginBtn = document.getElementById('olvh-login-btn'); const userInput = document.getElementById('olvh-login-user'); const pwdInput = document.getElementById('olvh-login-pwd'); const doLogin = async () => { const u = userInput.value.trim(); const p = pwdInput.value; if (!u || !p) { loginStatusEl.textContent = '请填写用户名和密码'; return; } loginBtn.disabled = true; loginBtn.textContent = '登录中...'; const res = await backendLogin(u, p); loginStatusEl.textContent = res.msg; if (res.ok) { userInput.value = ''; pwdInput.value = ''; document.getElementById('olvh-logout-btn').style.display = ''; updateLoginStatus(); log('🔑', res.msg); refreshAccountBadge(); } loginBtn.disabled = false; loginBtn.textContent = '登录并同步密钥'; }; loginBtn.addEventListener('click', doLogin); pwdInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') doLogin(); }); document.getElementById('olvh-usercenter-btn').addEventListener('click', () => { window.open(CONFIG.backendBase + '/user', '_blank'); }); document.getElementById('olvh-logout-btn').addEventListener('click', () => { store.account = { username: '', token: '', scriptKey: '' }; saveStore(); document.getElementById('olvh-logout-btn').style.display = 'none'; updateLoginStatus(); log('👋 已退出登录'); }); // 任务开关 const bindSwitch = (id, key, logText) => { const el = document.getElementById(id); if (!el) return; el.addEventListener('click', () => { store.tasks[key] = !store.tasks[key]; el.classList.toggle('on', store.tasks[key]); saveStore(); log(logText, store.tasks[key] ? '开启' : '关闭'); }); }; bindSwitch('olvh-task-exam', 'exam', '考试自动答题'); bindSwitch('olvh-task-quiz', 'quiz', '作业/测验自动答题'); bindSwitch('olvh-task-video', 'video', '视频自动处理'); document.getElementById('olvh-autoplay-switch').addEventListener('click', function () { store.autoPlay = !store.autoPlay; saveStore(); this.classList.toggle('on', store.autoPlay); if (store.autoPlay && state.currentVideo) { state.currentVideo.__userPaused = false; autoPlayVideo(state.currentVideo); } log('自动播放:', store.autoPlay); }); document.getElementById('olvh-mute-switch').addEventListener('click', function () { store.autoMute = !store.autoMute; saveStore(); this.classList.toggle('on', store.autoMute); if (state.currentVideo) { state.currentVideo.muted = store.autoMute; state.currentVideo.volume = store.autoMute ? 0 : 1; } log('静音播放:', store.autoMute); }); document.getElementById('olvh-autonext-switch').addEventListener('click', function () { store.autoNext = !store.autoNext; saveStore(); this.classList.toggle('on', store.autoNext); log('自动下一节:', store.autoNext); }); document.getElementById('olvh-autosubmit-switch').addEventListener('click', function () { store.tasks.examAutoSubmit = !store.tasks.examAutoSubmit; saveStore(); this.classList.toggle('on', store.tasks.examAutoSubmit); log('考试自动交卷:', store.tasks.examAutoSubmit); }); document.getElementById('olvh-log-clear').addEventListener('click', () => { logStore.list = []; renderLogPanel(); log('日志已清空'); }); document.getElementById('olvh-record-clear').addEventListener('click', () => { records = []; saveRecords(); renderRecordPanel(); log('学习记录已清空'); }); document.getElementById('olvh-force-play').addEventListener('click', () => { log('手动触发强制播放'); if (state.currentVideo) { const v = state.currentVideo; v.__userPaused = false; const wantSound = store.autoMute === false; v.muted = !wantSound; v.volume = wantSound ? 1 : 0; const target = v.__vjsPlayer || v; target.play().then(() => { log('✅ 强制播放成功' + (wantSound ? '(带声)' : '(静音)')); if (wantSound) setTimeout(() => { v.muted = false; v.volume = 1; }, 400); }).catch(e => { v.muted = true; v.volume = 0; v.play().then(() => log('✅ 备用静音播放成功')).catch(err => log('强制播放失败:', err)); }); applySpeed(v, store.speed); } else { log('未检测到视频,重新扫描...'); scanForVideos(); } }); } function setSpeed(rate) { store.speed = rate; saveStore(); log('设置倍速:', rate); if (state.currentVideo) { applySpeed(state.currentVideo, rate); } updateUI(); } function updateUI() { if (!panelEl) return; if (speedDisplayEl) speedDisplayEl.textContent = store.speed + 'x'; document.querySelectorAll('.olvh-speed-btn').forEach(btn => { btn.classList.toggle('active', parseFloat(btn.dataset.speed) === store.speed); }); if (statusEl) { const dot = document.getElementById('olvh-status-dot'); if (state.currentVideo) { const playing = !state.currentVideo.paused; const rate = state.currentVideo.__realRate || store.speed; statusEl.textContent = playing ? `播放中 · ${rate}x · ${Math.round(state.currentVideo.currentTime)}s` : `已检测 · 暂停中 · ${rate}x`; if (dot) dot.className = 'dot ' + (playing ? 'green' : 'red'); } else { statusEl.textContent = '等待检测视频...'; if (dot) dot.className = 'dot gray'; } } if (homeTitleEl) { if (state.currentVideo) { homeTitleEl.textContent = `📺 ${document.title || '当前视频'}`; } else { homeTitleEl.textContent = '尚未检测到视频'; } } } function startUIRefresh() { setInterval(() => { if (state.currentVideo) { if (!state.currentVideo.paused && !state.currentVideo.ended && state.currentVideo.__record) { state.currentVideo.__record.watched += 1; state.currentVideo.__record.lastSeen = Date.now(); saveRecords(); } updateUI(); } }, 1000); } function makeDraggable() { const header = document.getElementById('olvh-header'); const panel = document.getElementById('olvh-panel'); let isDragging = false, startX, startY, startLeft, startTop; header.addEventListener('mousedown', (e) => { if (e.target.id === 'olvh-toggle') return; isDragging = true; startX = e.clientX; startY = e.clientY; const rect = panel.getBoundingClientRect(); startLeft = rect.left; startTop = rect.top; panel.style.right = 'auto'; panel.style.left = startLeft + 'px'; panel.style.top = startTop + 'px'; e.preventDefault(); }); document.addEventListener('mousemove', (e) => { if (!isDragging) return; panel.style.left = (startLeft + e.clientX - startX) + 'px'; panel.style.top = (startTop + e.clientY - startY) + 'px'; }); document.addEventListener('mouseup', () => { isDragging = false; }); } /* ======================== 启动 ======================== */ function init() { loadStore(); loadRecords(); log('景德镇艺术-学习助手 v4.1.0 启动'); setupExamWatcher(); setupWorkWatcher(); setupGlobalObserver(); const start = () => { createPanel(); watchRouteChange(); setupVisibilityHandler(); setupUserInteractionUnlock(); startUIRefresh(); if (isExamPage()) { log('📖 检测到考试页面'); if (store.tasks.exam) runExamAutoAnswer(); } scanForVideos(); setTimeout(scanForVideos, 1500); setTimeout(scanForVideos, 3000); setTimeout(scanForVideos, 5000); setTimeout(scanForVideos, 8000); setInterval(scanForVideos, CONFIG.retryInterval); }; if (document.body) start(); else document.addEventListener('DOMContentLoaded', start); } const __jdzInit = init; init = function () { try { if (!store.account.token && !store.account.scriptKey) { const raw = GM_getValue('localBackendLogin', ''); if (raw) { const saved = JSON.parse(raw); if (saved && saved.token) { store.account.username = saved.username || ''; store.account.token = saved.token; saveStore(); log('🔑 已同步学神助手登录态,可直接答题'); } } } } catch (e) {} __jdzInit(); }; init(); })();