{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "BookSource",
"description": "v2 书源。",
"type": "object",
"required": [
"bookInfo",
"content",
"name",
"schema",
"toc",
"url"
],
"properties": {
"bookInfo": {
"$ref": "#/definitions/BookInfoOp"
},
"concurrentRate": {
"description": "限速:`\"N/ms\"`(N 次/ms)或纯毫秒间隔字符串;为空则用 `http.rateLimit`。",
"type": "string"
},
"content": {
"$ref": "#/definitions/ContentRules"
},
"enabledCookieJar": {
"description": "开启后:响应的 `Set-Cookie` 自动回灌进 cookie 库(按注册域归并持久化)。",
"default": false,
"type": "boolean"
},
"explore": {
"anyOf": [
{
"$ref": "#/definitions/ExploreOp"
},
{
"type": "null"
}
]
},
"group": {
"type": "string"
},
"http": {
"default": {
"charset": "auto",
"cookies": {},
"fetcher": "auto",
"headers": {},
"warmup": []
},
"allOf": [
{
"$ref": "#/definitions/Http"
}
]
},
"loginCheckJs": {
"description": "登录态过期校验脚本:每个网络方法响应后执行(注入 `result`=响应),判失效可提示重登。",
"type": "string"
},
"loginUi": {
"description": "声明式登录表单(TUI 渲染);收集值加密存为 loginInfo,供 `login()` 脚本读取。",
"type": "array",
"items": {
"$ref": "#/definitions/RowUi"
}
},
"loginUrl": {
"description": "登录:普通 URL,或登录脚本(`@js:…` / `<js>…</js>` 包裹,内含 `login()` 函数)。 非空即视为「需要登录」(见 [`BookSource::has_login`]);仅 `js-host` 构建可真正执行脚本登录。",
"type": "string"
},
"name": {
"type": "string"
},
"samples": {
"type": "array",
"items": {
"$ref": "#/definitions/Sample"
}
},
"schema": {
"description": "固定为 `\"trnovel-booksource/v2\"`。",
"type": "string"
},
"search": {
"anyOf": [
{
"$ref": "#/definitions/SearchOp"
},
{
"type": "null"
}
]
},
"toc": {
"$ref": "#/definitions/TocRules"
},
"url": {
"description": "站点基址,用于相对链接解析与 `{{base}}`。",
"type": "string"
}
},
"additionalProperties": false,
"definitions": {
"BookInfoOp": {
"description": "书详情操作:详情字段抽取(同 [`BookRules`])+ 可选前置请求链(见 design D7-bis)。 字段与 `BookRules` 同名同序,故现有 `bookInfo:{...}` JSON 逐字节解析等价;引擎经 [`BookInfoOp::as_book_rules`] 复用既有 `eval_book_info`(不用 `flatten` 以保 `deny_unknown_fields`)。",
"type": "object",
"properties": {
"author": {
"anyOf": [
{
"$ref": "#/definitions/Rule"
},
{
"type": "null"
}
]
},
"bookUrl": {
"anyOf": [
{
"$ref": "#/definitions/Rule"
},
{
"type": "null"
}
]
},
"cover": {
"anyOf": [
{
"$ref": "#/definitions/Rule"
},
{
"type": "null"
}
]
},
"intro": {
"anyOf": [
{
"$ref": "#/definitions/Rule"
},
{
"type": "null"
}
]
},
"kind": {
"anyOf": [
{
"$ref": "#/definitions/Rule"
},
{
"type": "null"
}
]
},
"lastChapter": {
"anyOf": [
{
"$ref": "#/definitions/Rule"
},
{
"type": "null"
}
]
},
"name": {
"anyOf": [
{
"$ref": "#/definitions/Rule"
},
{
"type": "null"
}
]
},
"prelude": {
"description": "详情主请求前的前置请求链;空 = 现状(直接 fetch book_url)。",
"type": "array",
"items": {
"$ref": "#/definitions/PreStep"
}
},
"tocUrl": {
"anyOf": [
{
"$ref": "#/definitions/Rule"
},
{
"type": "null"
}
]
},
"wordCount": {
"anyOf": [
{
"$ref": "#/definitions/Rule"
},
{
"type": "null"
}
]
}
},
"additionalProperties": false
},
"BookRules": {
"description": "一本书的字段抽取规则(均可省略)。",
"type": "object",
"properties": {
"author": {
"anyOf": [
{
"$ref": "#/definitions/Rule"
},
{
"type": "null"
}
]
},
"bookUrl": {
"description": "列表项:指向书详情页的链接(搜索/浏览结果用;bookInfo 阶段忽略)。",
"anyOf": [
{
"$ref": "#/definitions/Rule"
},
{
"type": "null"
}
]
},
"cover": {
"anyOf": [
{
"$ref": "#/definitions/Rule"
},
{
"type": "null"
}
]
},
"intro": {
"anyOf": [
{
"$ref": "#/definitions/Rule"
},
{
"type": "null"
}
]
},
"kind": {
"anyOf": [
{
"$ref": "#/definitions/Rule"
},
{
"type": "null"
}
]
},
"lastChapter": {
"anyOf": [
{
"$ref": "#/definitions/Rule"
},
{
"type": "null"
}
]
},
"name": {
"anyOf": [
{
"$ref": "#/definitions/Rule"
},
{
"type": "null"
}
]
},
"tocUrl": {
"anyOf": [
{
"$ref": "#/definitions/Rule"
},
{
"type": "null"
}
]
},
"wordCount": {
"anyOf": [
{
"$ref": "#/definitions/Rule"
},
{
"type": "null"
}
]
}
},
"additionalProperties": false
},
"ByteEnc": {
"description": "crypto 的 key/iv/输入/输出字节编码。",
"oneOf": [
{
"type": "string",
"enum": [
"utf8",
"base64",
"hex"
]
},
{
"description": "原样字节(等同 utf8 字节,主要用于输入密文已是裸字节串的场景)。",
"type": "string",
"enum": [
"raw"
]
}
]
},
"Capture": {
"description": "一条结构化命名捕获:对**所属请求的响应**用 `value` 规则求一个字符串, 写入 `scope` 指定的作用域层,后续步骤/抽取规则以 `{{name}}` 引用。",
"type": "object",
"required": [
"name",
"value"
],
"properties": {
"name": {
"description": "变量名(后续以 `{{name}}` 引用,走现有模板插值)。",
"type": "string"
},
"scope": {
"description": "写入哪一层作用域;默认 `chapter`(本次调用临时)。",
"default": "chapter",
"allOf": [
{
"$ref": "#/definitions/VarScope"
}
]
},
"value": {
"description": "对响应求值的规则(复用 `Rule` AST,产物是字符串)。",
"allOf": [
{
"$ref": "#/definitions/Rule"
}
]
}
},
"additionalProperties": false
},
"Category": {
"description": "浏览分类。",
"type": "object",
"required": [
"title",
"url"
],
"properties": {
"title": {
"type": "string"
},
"url": {
"$ref": "#/definitions/UrlOrRule"
}
},
"additionalProperties": false
},
"Charset": {
"description": "字符集。",
"type": "string",
"enum": [
"auto",
"utf8",
"gbk",
"gb18030",
"big5"
]
},
"CipherAlgo": {
"description": "对称加密算法。",
"type": "string",
"enum": [
"aes",
"des",
"tripleDes"
]
},
"CipherMode": {
"description": "加密模式。",
"type": "string",
"enum": [
"cbc",
"ecb",
"cfb",
"gcm"
]
},
"CipherOp": {
"description": "加解密方向。",
"type": "string",
"enum": [
"decrypt",
"encrypt"
]
},
"CipherStep": {
"description": "加解密算子。默认值贴合「解密正文」主场景:`op=decrypt`、`inputEnc=base64`、`outputEnc=utf8`。",
"type": "object",
"required": [
"algo",
"key",
"mode"
],
"properties": {
"algo": {
"$ref": "#/definitions/CipherAlgo"
},
"inputEnc": {
"description": "入参密文串→字节;省略时按 `op` 取默认(decrypt→base64,encrypt→utf8)。",
"anyOf": [
{
"$ref": "#/definitions/ByteEnc"
},
{
"type": "null"
}
]
},
"iv": {
"type": [
"string",
"null"
]
},
"ivEnc": {
"default": "utf8",
"allOf": [
{
"$ref": "#/definitions/ByteEnc"
}
]
},
"key": {
"type": "string"
},
"keyEnc": {
"default": "utf8",
"allOf": [
{
"$ref": "#/definitions/ByteEnc"
}
]
},
"mode": {
"$ref": "#/definitions/CipherMode"
},
"op": {
"default": "decrypt",
"allOf": [
{
"$ref": "#/definitions/CipherOp"
}
]
},
"outputEnc": {
"description": "结果字节→串;省略时按 `op` 取默认(decrypt→utf8,encrypt→base64)。",
"anyOf": [
{
"$ref": "#/definitions/ByteEnc"
},
{
"type": "null"
}
]
},
"padding": {
"default": "pkcs7",
"allOf": [
{
"$ref": "#/definitions/Padding"
}
]
}
},
"additionalProperties": false
},
"CleanStep": {
"description": "单步后处理。步内多算子按固定顺序执行: `regex/replace → trim → prepend → append → decode → encode → hash → cipher → fontMap → cn`。",
"type": "object",
"properties": {
"append": {
"type": [
"string",
"null"
]
},
"cipher": {
"description": "对称加解密。",
"anyOf": [
{
"$ref": "#/definitions/CipherStep"
},
{
"type": "null"
}
]
},
"cn": {
"description": "繁简转换。",
"anyOf": [
{
"$ref": "#/definitions/CnConvert"
},
{
"type": "null"
}
]
},
"decode": {
"description": "解码(base64/base64url/hex/url)。",
"anyOf": [
{
"$ref": "#/definitions/Codec"
},
{
"type": "null"
}
]
},
"encode": {
"description": "编码(base64/base64url/hex/url)。",
"anyOf": [
{
"$ref": "#/definitions/Codec"
},
{
"type": "null"
}
]
},
"fontMap": {
"description": "字体反爬还原:私有区(PUA)字符按映射表换回真字。键为码点十六进制(如 `\"E4DE\"` 或 `\"U+E4DE\"`), 值为目标字符;表外字符原样保留。用于番茄等「自定义字体 + PUA」反爬站点——表是数据,由书源内联 提供(引擎不内置任何站点的表),可用 `trn gen-fontmap` 生成。",
"type": [
"object",
"null"
],
"additionalProperties": {
"type": "string"
}
},
"hash": {
"description": "哈希/HMAC。",
"anyOf": [
{
"$ref": "#/definitions/HashStep"
},
{
"type": "null"
}
]
},
"js": {
"description": "JS 后处理(逃生舱;脚本里以当前串为 `result`)。需启用 `js` feature。",
"type": [
"string",
"null"
]
},
"prepend": {
"type": [
"string",
"null"
]
},
"regex": {
"type": [
"string",
"null"
]
},
"replace": {
"type": [
"string",
"null"
]
},
"trim": {
"type": [
"boolean",
"null"
]
}
},
"additionalProperties": false
},
"CnConvert": {
"description": "繁简转换方向。",
"oneOf": [
{
"description": "繁体 → 简体。",
"type": "string",
"enum": [
"t2s"
]
},
{
"description": "简体 → 繁体。",
"type": "string",
"enum": [
"s2t"
]
}
]
},
"Codec": {
"description": "编解码方式(`decode`/`encode` 算子,以及 crypto 的字节↔串编码)。",
"oneOf": [
{
"type": "string",
"enum": [
"base64",
"base64url",
"hex"
]
},
{
"description": "URL 百分号编解码。",
"type": "string",
"enum": [
"url"
]
}
]
},
"ContentRules": {
"description": "正文规则(可选分页)。",
"type": "object",
"required": [
"value"
],
"properties": {
"maxPages": {
"default": 100,
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"nextPage": {
"anyOf": [
{
"$ref": "#/definitions/Rule"
},
{
"type": "null"
}
]
},
"prelude": {
"description": "正文主请求之前按序执行的前置请求链(见 design D7-bis);空 = 现状。",
"type": "array",
"items": {
"$ref": "#/definitions/PreStep"
}
},
"value": {
"$ref": "#/definitions/Rule"
}
},
"additionalProperties": false
},
"Expect": {
"description": "样例期望不变量。",
"type": "object",
"properties": {
"minChapters": {
"type": [
"integer",
"null"
],
"format": "uint",
"minimum": 0.0
},
"minContentChars": {
"type": [
"integer",
"null"
],
"format": "uint",
"minimum": 0.0
},
"name": {
"type": [
"string",
"null"
]
},
"volumes": {
"type": [
"integer",
"null"
],
"format": "uint",
"minimum": 0.0
}
}
},
"ExploreOp": {
"description": "浏览操作。",
"type": "object",
"required": [
"categories",
"item",
"list"
],
"properties": {
"categories": {
"type": "array",
"items": {
"$ref": "#/definitions/Category"
}
},
"item": {
"$ref": "#/definitions/BookRules"
},
"list": {
"$ref": "#/definitions/Rule"
},
"prelude": {
"description": "分类请求之前按序执行的前置请求链(见 design D7-bis);空 = 现状。",
"type": "array",
"items": {
"$ref": "#/definitions/PreStep"
}
}
},
"additionalProperties": false
},
"Extract": {
"description": "取值方式(枚举字符串 或 `{ \"attr\": \"...\" }`)。",
"anyOf": [
{
"$ref": "#/definitions/ExtractOp"
},
{
"type": "object",
"required": [
"attr"
],
"properties": {
"attr": {
"type": "string"
}
}
}
]
},
"ExtractOp": {
"description": "文本/HTML 取值算子。",
"type": "string",
"enum": [
"text",
"ownText",
"html",
"innerHtml",
"outerHtml"
]
},
"FetchMode": {
"description": "取页模式:是否动用浏览器解反爬挑战。 真正是否开浏览器还需 app/用户级授权(两级取交集,见 OpenSpec change `browser-fetcher` D12)。",
"oneOf": [
{
"description": "默认:平时 reqwest,撞挑战才升级浏览器。",
"type": "string",
"enum": [
"auto"
]
},
{
"description": "永不开浏览器,撞挑战即降级。",
"type": "string",
"enum": [
"reqwest"
]
},
{
"description": "整站强制走浏览器(首请求即被挑战 / 整页 JS 渲染)。",
"type": "string",
"enum": [
"browser"
]
}
]
},
"HashAlgo": {
"description": "哈希算法。",
"type": "string",
"enum": [
"md5",
"sha1",
"sha256",
"sha512"
]
},
"HashOut": {
"description": "哈希/HMAC 输出编码。",
"type": "string",
"enum": [
"hex",
"base64"
]
},
"HashStep": {
"description": "哈希算子(可选 HMAC)。",
"type": "object",
"required": [
"algo"
],
"properties": {
"algo": {
"$ref": "#/definitions/HashAlgo"
},
"hmacKey": {
"description": "提供则计算 HMAC(以此为密钥)。",
"type": [
"string",
"null"
]
},
"hmacKeyEnc": {
"default": "utf8",
"allOf": [
{
"$ref": "#/definitions/ByteEnc"
}
]
},
"output": {
"default": "hex",
"allOf": [
{
"$ref": "#/definitions/HashOut"
}
]
}
},
"additionalProperties": false
},
"Http": {
"description": "HTTP 配置块。",
"type": "object",
"properties": {
"charset": {
"default": "auto",
"allOf": [
{
"$ref": "#/definitions/Charset"
}
]
},
"cookies": {
"description": "静态 cookie;也是运行时注入 clearance cookie 的落点。",
"default": {},
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"fetcher": {
"description": "取页模式(auto|reqwest|browser);默认 auto。",
"default": "auto",
"allOf": [
{
"$ref": "#/definitions/FetchMode"
}
]
},
"headers": {
"default": {},
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"rateLimit": {
"anyOf": [
{
"$ref": "#/definitions/RateLimit"
},
{
"type": "null"
}
]
},
"retry": {
"anyOf": [
{
"$ref": "#/definitions/Retry"
},
{
"type": "null"
}
]
},
"timeout": {
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0.0
},
"warmup": {
"description": "先 GET 这些页以预热会话 cookie。",
"default": [],
"type": "array",
"items": {
"type": "string"
}
}
},
"additionalProperties": false
},
"LeafRule": {
"description": "叶子规则:在当前上下文做一次抽取。",
"type": "object",
"properties": {
"clean": {
"type": "array",
"items": {
"$ref": "#/definitions/CleanStep"
}
},
"extract": {
"default": "text",
"allOf": [
{
"$ref": "#/definitions/Extract"
}
]
},
"index": {
"type": [
"integer",
"null"
],
"format": "int64"
},
"select": {
"type": [
"string",
"null"
]
},
"via": {
"default": "css",
"allOf": [
{
"$ref": "#/definitions/Via"
}
]
}
}
},
"Method": {
"description": "HTTP 方法。",
"type": "string",
"enum": [
"GET",
"POST"
]
},
"Padding": {
"description": "填充方式(gcm 忽略)。",
"type": "string",
"enum": [
"pkcs7",
"zero",
"none"
]
},
"PreStep": {
"description": "前置请求链中的一步:一个请求 + 其响应上的有序命名捕获(见 design D7-bis)。 本步 url/headers/body 可引用更早步骤捕获的 `{{name}}`。显式列字段(**不**用 `#[serde(flatten)]` 内嵌 [`Request`]:`Rule` 为 untagged 兜底,flatten 会令 `deny_unknown_fields` 校验失效)。",
"type": "object",
"required": [
"url"
],
"properties": {
"body": {
"anyOf": [
{
"$ref": "#/definitions/UrlOrRule"
},
{
"type": "null"
}
]
},
"capture": {
"description": "本步响应上的有序命名捕获(按数组顺序求值;`capture[i]` 可引用 `capture[0..i]`)。",
"type": "array",
"items": {
"$ref": "#/definitions/Capture"
}
},
"headers": {
"description": "请求头(值支持 `{{name}}` 模板,便于带 `Authorization: Bearer {{token}}`)。",
"default": {},
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"method": {
"default": "GET",
"allOf": [
{
"$ref": "#/definitions/Method"
}
]
},
"skipIfPresent": {
"description": "惰性短路:列出的 key 在作用域内**全部非空**则跳过本步(token 复用,避免每章重抓)。",
"type": "array",
"items": {
"type": "string"
}
},
"url": {
"$ref": "#/definitions/UrlOrRule"
}
},
"additionalProperties": false
},
"RateLimit": {
"description": "速率限制。",
"type": "object",
"required": [
"maxCount",
"perMs"
],
"properties": {
"maxCount": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"perMs": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
}
},
"additionalProperties": false
},
"Request": {
"description": "单个请求。",
"type": "object",
"required": [
"url"
],
"properties": {
"body": {
"anyOf": [
{
"$ref": "#/definitions/UrlOrRule"
},
{
"type": "null"
}
]
},
"headers": {
"default": {},
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"method": {
"default": "GET",
"allOf": [
{
"$ref": "#/definitions/Method"
}
]
},
"url": {
"$ref": "#/definitions/UrlOrRule"
},
"vars": {
"description": "命名捕获:对**本请求响应**每个 `(name, Rule)` 求值,写入 `chapter` 层(等价 `scope=chapter` 的 [`Capture`]),使本 op 的 list/item 与后续步骤以 `{{name}}` 引用。见 design D7-bis。 各条**独立**对响应求值(可引用 `base`/`key`/`page` 与前置 `prelude` 捕获,但**勿互相引用**: 需有序依赖请用 `prelude` 链)。用 `BTreeMap` 保证迭代/落点顺序确定(免哈希随机化幽灵 bug)。",
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/Rule"
}
}
},
"additionalProperties": false
},
"Retry": {
"description": "重试策略。",
"type": "object",
"properties": {
"backoffMs": {
"default": 0,
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"max": {
"default": 0,
"type": "integer",
"format": "uint32",
"minimum": 0.0
}
},
"additionalProperties": false
},
"RowUi": {
"description": "声明式登录表单的一行(TUI 渲染对应控件,收集值加密存为 loginInfo)。",
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"description": "字段名(也是 loginInfo 中的 key)。",
"type": "string"
},
"options": {
"description": "`select` 类型的候选项。",
"type": "array",
"items": {
"type": "string"
}
},
"type": {
"default": "text",
"allOf": [
{
"$ref": "#/definitions/RowUiType"
}
]
}
},
"additionalProperties": false
},
"RowUiType": {
"description": "声明式登录表单项类型。",
"oneOf": [
{
"description": "单行文本。",
"type": "string",
"enum": [
"text"
]
},
{
"description": "密码(TUI 掩码显示)。",
"type": "string",
"enum": [
"password"
]
},
{
"description": "下拉选择(配合 `options`)。",
"type": "string",
"enum": [
"select"
]
},
{
"description": "布尔开关。",
"type": "string",
"enum": [
"toggle"
]
}
]
},
"Rule": {
"description": "一条规则:叶子,或组合子。组合子按其唯一键判别(见 design D1)。\n\n反序列化时按变体顺序尝试:组合子(各有唯一必填键)在前,叶子兜底。",
"anyOf": [
{
"description": "取首个非空子规则结果(回退/自愈)。",
"type": "object",
"required": [
"firstOf"
],
"properties": {
"firstOf": {
"type": "array",
"items": {
"$ref": "#/definitions/Rule"
}
}
}
},
{
"description": "拼接非空子规则结果。",
"type": "object",
"required": [
"concat"
],
"properties": {
"concat": {
"type": "array",
"items": {
"$ref": "#/definitions/Rule"
}
},
"join": {
"default": "",
"type": "string"
}
}
},
{
"description": "字面量。",
"type": "object",
"required": [
"literal"
],
"properties": {
"literal": {
"type": "string"
}
}
},
{
"description": "模板插值(`{{key}}`/`{{page}}`/命名变量)。",
"type": "object",
"required": [
"template"
],
"properties": {
"template": {
"type": "string"
}
}
},
{
"description": "JS 逻辑编排逃生舱(值规则):以当前上下文为 `result`、注入 `baseUrl`/变量 + `crypto` 助手求值,返回字符串。求值需启用 `js` feature(否则返回 `Unsupported(\"js\")`)。 必须置于 `Leaf` 之前——`js` 是其唯一判别键,否则会被全可选的 `Leaf` 吞掉。",
"type": "object",
"required": [
"js"
],
"properties": {
"js": {
"type": "string"
}
}
},
{
"description": "叶子(兜底)。",
"allOf": [
{
"$ref": "#/definitions/LeafRule"
}
]
}
]
},
"Sample": {
"description": "黄金样例。",
"type": "object",
"required": [
"bookUrl"
],
"properties": {
"bookUrl": {
"type": "string"
},
"expect": {
"default": {},
"allOf": [
{
"$ref": "#/definitions/Expect"
}
]
}
},
"additionalProperties": false
},
"SearchOp": {
"description": "搜索操作。",
"type": "object",
"required": [
"item",
"list",
"request"
],
"properties": {
"item": {
"$ref": "#/definitions/BookRules"
},
"list": {
"$ref": "#/definitions/Rule"
},
"prelude": {
"description": "主请求之前按序执行的前置请求链(见 design D7-bis);空 = 单发(现状)。",
"type": "array",
"items": {
"$ref": "#/definitions/PreStep"
}
},
"request": {
"$ref": "#/definitions/Request"
}
},
"additionalProperties": false
},
"TocRules": {
"description": "目录规则(章节 + 分卷 + 可选分页)。",
"type": "object",
"required": [
"list",
"name",
"url"
],
"properties": {
"isVolume": {
"anyOf": [
{
"$ref": "#/definitions/Rule"
},
{
"type": "null"
}
]
},
"list": {
"$ref": "#/definitions/Rule"
},
"maxPages": {
"default": 100,
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"name": {
"$ref": "#/definitions/Rule"
},
"nextPage": {
"anyOf": [
{
"$ref": "#/definitions/Rule"
},
{
"type": "null"
}
]
},
"prelude": {
"description": "目录主请求之前按序执行的前置请求链(见 design D7-bis);空 = 现状。",
"type": "array",
"items": {
"$ref": "#/definitions/PreStep"
}
},
"url": {
"$ref": "#/definitions/Rule"
}
},
"additionalProperties": false
},
"UrlOrRule": {
"description": "URL 字段:可为字符串模板,或一条规则。",
"anyOf": [
{
"type": "string"
},
{
"$ref": "#/definitions/Rule"
}
]
},
"VarScope": {
"description": "多步编排:捕获变量的作用域(三级级联 章节→书籍→书源,见 design D7-bis)。 默认 `Chapter`——最短寿命、零持久、零跨书外溢;`get` 时按 章节→书籍→书源 取第一个非空。",
"oneOf": [
{
"description": "仅本次 op 调用(search/explore/bookInfo/toc/content)内存活,调用结束消亡(默认)。 一次性 csrf/sign/cursor 用它。",
"type": "string",
"enum": [
"chapter"
]
},
{
"description": "随 per-book 快照持久化(由 app 注入·导出);如详情/列表 token 复用到 toc/content。 注意:**search/explore 的 `prelude` 阶段尚无 per-book 载体**(用户选书后才建 per-book 引擎), 在那里用 `book` 会写进会被丢弃的实例 → 静默无效;search 阶段的 token 请用 `source`/`chapter`。",
"type": "string",
"enum": [
"book"
]
},
{
"description": "书源级长存(引擎内跨 op 共享);如全站 API host/版本/全站 csrf。 跨会话持久仅在 app 接线落盘时保证(默认构建为进程内)。",
"type": "string",
"enum": [
"source"
]
}
]
},
"Via": {
"description": "抽取后端(决定 `select` 的语义)。",
"oneOf": [
{
"type": "string",
"enum": [
"css",
"xpath",
"json",
"regex"
]
},
{
"description": "直接使用当前上下文值(只跑 clean)。",
"type": "string",
"enum": [
"raw"
]
}
]
}
}
}