脚本开发
脚本插件适用于“需要登录、动态刷新 token、需要流程编排”的来源。
插件目录结构
text
my-plugin/
plugin.json
schema.json
scripts/
fetch.lua
login.lua (可选)
refresh.lua (可选)最小可导入要求:
plugin.json必填:plugin_id、spec_version、name、version、type、config_schematype必须为"script"entrypoints.fetch必填且非空schema.json必须可解析且满足受限 JSON Schema 子集
说明:fetch.lua 文件存在性在脚本执行阶段校验(导入阶段只校验 entrypoints.fetch 字段本身)。
常见导入报错对应关系:
plugin.json 解析失败:missing field 'name':缺少必填字段(如name、version、config_schema)script 插件必须提供 entrypoints.fetch:未声明entrypoints.fetch或值为空schema 顶层字段不支持:...:schema.json使用了当前不支持的关键字(如oneOf)
plugin.json 示例(可直接作为起点)
json
{
"plugin_id": "vendor.example.dynamic-sub",
"spec_version": "1.0",
"name": "Dynamic Subscription",
"version": "1.0.0",
"type": "script",
"config_schema": "schema.json",
"entrypoints": {
"login": "scripts/login.lua",
"refresh": "scripts/refresh.lua",
"fetch": "scripts/fetch.lua"
},
"capabilities": ["http", "cookie", "json", "html", "base64", "secret", "log", "time"],
"secret_fields": ["password"],
"network_profile": "browser_chrome"
}manifest 字段规则(与当前代码一致)
必填字段:
plugin_id:不能为空,且不能包含..、/、\spec_version:仅支持1.xname:不能为空version:不能为空type:"script"config_schema:不能为空,且路径必须位于插件目录内
脚本类型额外要求:
entrypoints.fetch必填且非空entrypoints.login/entrypoints.refresh可选
可选字段与默认值:
secret_fields:默认[],每个字段必须出现在schema.propertiesentrypoints:默认空对象capabilities:默认[],若填写仅允许:http / cookie / json / html / base64 / secret / log / timenetwork_profile:默认standard,可选:standard / browser_chrome / browser_firefox / webview_assistedanti_bot_level:默认low
入口函数契约
login(ctx, config, state) -> { ok, state?, error? }refresh(ctx, config, state) -> { ok, state?, error? }fetch(ctx, config, state) -> { ok, subscription?, state?, error? }
subscription 支持两种返回:
{ url = "https://..." }{ content = "base64 or uri lines text" }
推荐约定:
- 失败统一
ok = false并返回结构化error。 - 非敏感上下文写入
state,敏感值写入secretAPI。
schema.json 约束(导入时校验)
顶层仅支持这些 key:
$schematyperequiredpropertiesadditionalProperties
properties.<field> 仅支持:
type、title、description、default、enumformat、minLength、maxLength、minimum、maximum、patternx-ui(仅支持widget、placeholder、help、group、order)
额外限制:
schema.type必须为objectschema.properties不能为空- 字段类型仅支持
string / number / integer / boolean required中的字段必须在properties中定义
Runtime API 白名单
http.request({ method, url, headers, body, timeout_ms })cookie.get(name)/cookie.set(name, value, attrs)json.parse(str)/json.stringify(obj)html.query(html, selector)base64.encode(str)/base64.decode(str)secret.get(key)/secret.set(key, value)log.info(msg)/log.warn(msg)/log.error(msg)time.now()
http.request 额外约定:
method可省略,默认GET。- 返回状态码非 2xx 时会直接抛出运行时错误(不会返回
resp.status让脚本自行判断)。
不允许:
- 系统命令
- 文件系统访问
- 任意 socket
- 动态模块加载
运行限制(MVP)
- 单次脚本执行超时:
20s - 单次
http.request超时:15s - 单次执行最大 HTTP 请求数:
20 - 单次执行内存上限:
64MB - 单次 HTTP 响应体上限:
5MB
常见错误码:
E_SCRIPT_TIMEOUTE_SCRIPT_LIMITE_SCRIPT_RUNTIME
SSRF 与安全边界
http.request 会在 DNS 解析后做 IP 校验,命中内网/回环网段会直接拒绝。
示例(会被拒绝):
lua
http.request({ method = "GET", url = "http://127.0.0.1:18118/health" })此外,插件只能访问自身 plugin_id 的 secret 命名空间,不能读取其他插件或系统密钥。
示例:最小 fetch.lua
lua
function fetch(ctx, config, state)
local ok, resp = pcall(http.request, {
method = "GET",
url = config.subscription_url,
timeout_ms = 10000
})
if not ok then
return { ok = false, error = { code = "E_HTTP", message = tostring(resp) } }
end
return {
ok = true,
subscription = { content = resp.body },
state = state
}
end调试建议
- 先在 mock 服务上验证
login -> refresh -> fetch。 log.*只记录必要上下文,避免输出敏感字段。- 对 429/403 场景设计可恢复重试,不要写死无限循环。