用户脚本指南
Section titled “用户脚本指南”StarX 从 1.5.4 开始内置用户脚本系统:用 JavaScript 扩展模块行为,不需要等模块发版。
1. 能做什么
Section titled “1. 能做什么”- 在 全局 / 考试 / 作业 / 随堂小测 / 章节测验 / 视频 / 签到 任意位置运行,也可以同时挂在多个位置
- 直接调用模块已经配置好的能力:AI 直答、题库查询、答案查询(含图片)
- 在题目下发的一瞬间自己作答,优先级高于 AI 与题库,考试链路同样适用
- 用
StarX.http发自己的请求,用StarX.storage持久化自己的数据 - 注册自己的原生 Hook(要求 Xposed 框架 API ≥ 102)
- 脚本之间通过事件总线互相通信
脚本运行在 Rhino 里(ES6 语言级别、纯解释执行),看不到任何 Java 类,只能用本文档列出的 API。
2. 脚本目录
Section titled “2. 脚本目录”/storage/emulated/0/Download/StarX/└── scripts/ ├── scripts.json 启用状态索引(模块维护,一般不用手改) ├── my_script.js 脚本源码 ├── data/ │ └── my_script.json StarX.storage 的落盘文件 └── logs/ └── my_script.log 运行日志(超过 256 KB 自动轮转)要点:
- 根目录优先
/storage/emulated/0/Download/StarX,不可写时回退/sdcard/Download/StarX。 - 文件名(去掉
.js)就是脚本 ID。ID 只保留a-z A-Z 0-9 . _ -,其它字符替换成_,建议用英文命名文件。 - 文件一改就会在 2 秒内自动热重载。用 MT 管理器、VS Code、电脑 MTP 直接改文件都可以,不需要重启学习通。
- 索引缺失或损坏时一律按「未启用」处理,不会误跑脚本。
3. 三种创建方式
Section titled “3. 三种创建方式”3.1 在模块里新建(推荐)
Section titled “3.1 在模块里新建(推荐)”- 打开 StarX → 底部 用户脚本 页
- 点 新建脚本
- 填「脚本名称」,在下方文本框里写代码(模板已带好元数据头与示例)
- 点 保存,模块写入
scripts/并提示「学习通 2 秒内热重载」
脚本卡片上可以:启用 / 停用、编辑、日志、删除。点 使用说明 可随时查看作用域与事件速查。
3.2 直接用编辑器写文件
Section titled “3.2 直接用编辑器写文件”在 scripts/ 目录新建 xxx.js,写好元数据头与代码。文件出现后 2 秒内模块会加载它——新脚本默认启用。
3.3 安装别人的脚本
Section titled “3.3 安装别人的脚本”把 .js 文件放进 scripts/ 即可。脚本只能调用本文档列出的 API,看不到 Java 类;但它仍然可以发起任意网络请求、读写剪贴板、读写自己的存储文件,因此只运行你信任的脚本。
4. 元数据头
Section titled “4. 元数据头”头部沿用 Tampermonkey 的 ==UserScript== 注释风格,==StarXScript== 是等价别名。
// ==UserScript==// @name 考试截题示例// @namespace local.example// @version 1.0.0// @description 命中关键字就自己作答// @author you// @scope exam// @grant ai,tiku// ==/UserScript==| 字段 | 说明 |
|---|---|
@name | 显示名;不写时用文件名 |
@namespace | 命名空间,仅作标识 |
@version | 版本号,显示在脚本卡片上 |
@description | 一句话说明 |
@author | 作者 |
@scope | 作用域,逗号 / 空格 / 竖线分隔,可写多个,见下一节 |
@match / @include | 页面匹配规则(油猴语义),命中后该脚本会收到 page 事件 |
@exclude | 在 @match 命中里排除某些 URL |
@grant | 声明可用的 API 分组;不写 = 全部放行,写了就只放行声明的分组 |
@require | 注入前先同步加载并执行的远程 JS(单文件上限 512 KB,进程内缓存) |
@run-at | 保留字段,当前仅作元数据展示 |
@noframes | 保留字段 |
匹配规则:
*匹配任意长度,?匹配单个字符- 用
/正则/包裹则直接按正则处理(正则非法时退化为字面匹配)
5. 作用域
Section titled “5. 作用域”@scope 决定脚本能在哪些位置工作。不写 @scope 时默认为 global。
| 值 | 位置 | 现在会收到什么 |
|---|---|---|
global | 全局 | tick(5 秒心跳)、question.publish 的自定义题目、自己 emit 的事件 |
exam | 考试 | question(考试单题页、整卷预览批量作答、复合题子题) |
work | 作业 / 章节测验 | question。章节测验当前由作业链路承载,截获章节测验题目要写 work |
quiz | 随堂小测 | question(批量作答的每一题) |
chapter | 章节页 | page(按 URL 路由,见下) |
video | 视频 | video.report(进度自然上报时) |
sign | 签到 | sign.active(捕获到签到活动 ID 时) |
all | 以上全部 | 等价于把上面所有都写上 |
page 事件的 URL 路由表:
| 作用域 | 命中的 URL 片段 |
|---|---|
sign | /newsign/、/widget/sign/、/sign/ |
exam | /exam |
work | /dohomework、/work/ |
quiz | /inclass、/quiz |
video | /ananas/ |
chapter | /mycourse/studentstudy、/coursedata/、/chapter/ |
声明了 @match 的脚本按 URL 命中接收 page,不需要再写 @scope。
用 StarX.on(名称, 回调) 订阅,StarX.off(名称, 回调) 取消。
6.1 question
Section titled “6.1 question”题目下发时触发,回调收到:
{ scope: 'exam', // exam / work / quiz id: '123456789', // 题号;考试复合题为 qid + "#" + 子题序号 type: 0, // 题型编码,见 6.6 stem: '题干文本', options: ['A选项', 'B选项'], // 部分链路可能为空数组 ts: 1789015379277}在这个回调里调用 StarX.question.answer(...) 即完成作答:
StarX.on('question', function (q) { if (q.stem.indexOf('TCP') >= 0) { StarX.question.answer('A'); // 单选 / 判断 } if (q.type === 1) { StarX.question.answer(['A', 'C']); // 多选:数组归一成 "AC" } if (q.type === 2 || q.type === 4) { StarX.question.answer('三次握手'); // 填空 / 简答 }});- 传数组时:元素全为单字符则拼接成
AC,否则逗号连接。 - 返回
false表示这次没有给出可用答案(等于没作答)。 - 第一个给出非空答案的脚本胜出,模块不再请求 AI / 题库。
- 回调是同步执行的,运行在调用方线程(考试 / 作业的解析线程)上。不要长时间阻塞。
6.2 page
Section titled “6.2 page”WebView 打开新页面时触发,负载 { url, ts }。
6.3 sign.active
Section titled “6.3 sign.active”捕获到签到活动时触发:{ activeId, url, source, ts },source 为 okhttp 或 webview。
6.4 video.report
Section titled “6.4 video.report”视频进度自然上报被监听时触发:{ objectId, passed, source, preview, ts }。
6.5 tick 与自定义事件
Section titled “6.5 tick 与自定义事件”tick 每 5 秒一次,负载 { ts },只发给声明了 global(或 all)的脚本。
脚本之间可以互相通信:
StarX.emit('my-event', { hello: 1 }); // 广播给其它脚本(不回送自己)StarX.on('my-event', function (p) { StarX.log(p.hello); });6.6 题型编码
Section titled “6.6 题型编码”| 值 | 题型 |
|---|---|
| 0 | 单选 |
| 1 | 多选 |
| 2 | 填空 |
| 3 | 判断 |
| 4 | 简答 |
| -1 | 未知 |
7. 完整示例:考试截题自行作答
Section titled “7. 完整示例:考试截题自行作答”// ==UserScript==// @name 考试关键字直答// @version 1.0.0// @description 命中本地规则直接作答,未命中交给 AI// @author you// @scope exam,work// ==/UserScript==
var RULES = { '三次握手': '三次握手', 'OSI 几层': '7'};
StarX.on('question', function (q) { for (var key in RULES) { if (q.stem.indexOf(key) >= 0) { StarX.log('hit rule:', key, '=>', RULES[key]); StarX.question.answer(RULES[key]); return; } } if (!StarX.ai.isConfigured()) return; var ans = StarX.ai.ask(q.stem, q.type, q.options.join('\n')); if (ans) StarX.question.answer(ans);});8. 权限 @grant
Section titled “8. 权限 @grant”不写 @grant 时全部 API 放行(方便本地调试)。一旦写了,就只放行列出的分组:
| 分组 | 覆盖的 API |
|---|---|
ai | StarX.ai.* |
tiku | StarX.tiku.* |
answer | StarX.answer.* |
http | StarX.http.* |
storage | StarX.storage.* |
clipboard | StarX.clipboard.* |
hook | StarX.hook.* |
page | StarX.page.* |
question | StarX.question.* |
timer | setTimeout / setInterval / clearTimeout / clearInterval |
ui | StarX.toast |
被拒绝的调用返回 null,并在脚本日志里留下一条 denied by @grant。
9. 生效与热重载
Section titled “9. 生效与热重载”- 改文件:2 秒内自动重载,脚本重新执行,不需要重启任何东西。
- 改模块设置:模块 App 会尝试调用框架热重载;框架不支持时提示重启学习通。
- 脚本运行环境与框架版本无关——Rhino 跑在模块进程里,API < 102 也能用。只有
StarX.hook.*要求框架 API ≥ 102。用StarX.hook.available()判断。
10. 日志与排错
Section titled “10. 日志与排错”StarX.log(...)、StarX.console.log / warn / error / debug(...)写入scripts/logs/<id>.log,同时进 logcat(tagStarX)。- 脚本卡片上的 日志 按钮可直接查看最近日志并清空。
- 脚本加载失败(语法错误等)时错误显示在脚本卡片上,其它脚本不受影响。
- 事件回调抛出的异常会被捕获并记一条
event xxx handler failed: ...,不中断业务链路。
11. 安全模型与已知边界
Section titled “11. 安全模型与已知边界”安全模型(事实):
- 每个脚本一个独立 Rhino 作用域、一条独立的单线程执行器,脚本之间不能互相访问变量。
ClassShutter拒绝一切 Java 类,脚本无法通过 Rhino 反射 Java。- 脚本可见的全部原生能力就是本文档列出的操作清单,没有别的入口。
- 脚本异常被隔离,不影响主业务链路和别的脚本。
边界(事实):
- 脚本仍可发起任意网络请求、读写剪贴板、读写自己的
data/<id>.json,只运行你信任的脚本。 @run-at、@noframes当前只作元数据,未参与注入调度。chapter作用域目前只有page事件;章节测验题目走work作用域。- 没有
GM_*兼容层,脚本按本文档的StarX.*写。 @grant只裁剪 API 分组,不做网络域名限制。