跳转到内容

用户脚本指南

StarX 从 1.5.4 开始内置用户脚本系统:用 JavaScript 扩展模块行为,不需要等模块发版。

  • 在 全局 / 考试 / 作业 / 随堂小测 / 章节测验 / 视频 / 签到 任意位置运行,也可以同时挂在多个位置
  • 直接调用模块已经配置好的能力:AI 直答、题库查询、答案查询(含图片)
  • 在题目下发的一瞬间自己作答,优先级高于 AI 与题库,考试链路同样适用
  • 用 StarX.http 发自己的请求,用 StarX.storage 持久化自己的数据
  • 注册自己的原生 Hook(要求 Xposed 框架 API ≥ 102)
  • 脚本之间通过事件总线互相通信

脚本运行在 Rhino 里(ES6 语言级别、纯解释执行),看不到任何 Java 类,只能用本文档列出的 API。

/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 直接改文件都可以,不需要重启学习通。
  • 索引缺失或损坏时一律按「未启用」处理,不会误跑脚本。
  1. 打开 StarX → 底部 用户脚本 页
  2. 点 新建脚本
  3. 填「脚本名称」,在下方文本框里写代码(模板已带好元数据头与示例)
  4. 点 保存,模块写入 scripts/ 并提示「学习通 2 秒内热重载」

脚本卡片上可以:启用 / 停用、编辑、日志、删除。点 使用说明 可随时查看作用域与事件速查。

在 scripts/ 目录新建 xxx.js,写好元数据头与代码。文件出现后 2 秒内模块会加载它——新脚本默认启用。

把 .js 文件放进 scripts/ 即可。脚本只能调用本文档列出的 API,看不到 Java 类;但它仍然可以发起任意网络请求、读写剪贴板、读写自己的存储文件,因此只运行你信任的脚本。

头部沿用 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保留字段

匹配规则:

  • * 匹配任意长度,? 匹配单个字符
  • 用 /正则/ 包裹则直接按正则处理(正则非法时退化为字面匹配)

@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(名称, 回调) 取消。

题目下发时触发,回调收到:

{
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 / 题库。
  • 回调是同步执行的,运行在调用方线程(考试 / 作业的解析线程)上。不要长时间阻塞。

WebView 打开新页面时触发,负载 { url, ts }。

捕获到签到活动时触发:{ activeId, url, source, ts },source 为 okhttp 或 webview。

视频进度自然上报被监听时触发:{ objectId, passed, source, preview, ts }。

tick 每 5 秒一次,负载 { ts },只发给声明了 global(或 all)的脚本。

脚本之间可以互相通信:

StarX.emit('my-event', { hello: 1 }); // 广播给其它脚本(不回送自己)
StarX.on('my-event', function (p) { StarX.log(p.hello); });
值题型
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);
});

不写 @grant 时全部 API 放行(方便本地调试)。一旦写了,就只放行列出的分组:

分组覆盖的 API
aiStarX.ai.*
tikuStarX.tiku.*
answerStarX.answer.*
httpStarX.http.*
storageStarX.storage.*
clipboardStarX.clipboard.*
hookStarX.hook.*
pageStarX.page.*
questionStarX.question.*
timersetTimeout / setInterval / clearTimeout / clearInterval
uiStarX.toast

被拒绝的调用返回 null,并在脚本日志里留下一条 denied by @grant。

  • 改文件:2 秒内自动重载,脚本重新执行,不需要重启任何东西。
  • 改模块设置:模块 App 会尝试调用框架热重载;框架不支持时提示重启学习通。
  • 脚本运行环境与框架版本无关——Rhino 跑在模块进程里,API < 102 也能用。只有 StarX.hook.* 要求框架 API ≥ 102。用 StarX.hook.available() 判断。
  • StarX.log(...)、StarX.console.log / warn / error / debug(...) 写入 scripts/logs/<id>.log,同时进 logcat(tag StarX)。
  • 脚本卡片上的 日志 按钮可直接查看最近日志并清空。
  • 脚本加载失败(语法错误等)时错误显示在脚本卡片上,其它脚本不受影响。
  • 事件回调抛出的异常会被捕获并记一条 event xxx handler failed: ...,不中断业务链路。

安全模型(事实):

  • 每个脚本一个独立 Rhino 作用域、一条独立的单线程执行器,脚本之间不能互相访问变量。
  • ClassShutter 拒绝一切 Java 类,脚本无法通过 Rhino 反射 Java。
  • 脚本可见的全部原生能力就是本文档列出的操作清单,没有别的入口。
  • 脚本异常被隔离,不影响主业务链路和别的脚本。

边界(事实):

  • 脚本仍可发起任意网络请求、读写剪贴板、读写自己的 data/<id>.json,只运行你信任的脚本。
  • @run-at、@noframes 当前只作元数据,未参与注入调度。
  • chapter 作用域目前只有 page 事件;章节测验题目走 work 作用域。
  • 没有 GM_* 兼容层,脚本按本文档的 StarX.* 写。
  • @grant 只裁剪 API 分组,不做网络域名限制。