{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://yexiyue.github.io/TRNovel/book-source.schema.json",
  "title": "TRNovel BookSource v2",
  "description": "AI 原生的结构化书源格式。无紧凑字符串 DSL;每条规则是显式对象,可被约束解码逐字段保护。",
  "type": "object",
  "additionalProperties": false,
  "required": ["schema", "name", "url", "bookInfo", "toc", "content"],
  "properties": {
    "schema": { "const": "trnovel-booksource/v2" },
    "name": { "type": "string", "description": "书源显示名" },
    "group": { "type": "string", "description": "分组/分类标签" },
    "url": { "type": "string", "description": "站点基址,用于相对链接解析" },
    "http": { "$ref": "#/$defs/http" },
    "search": { "$ref": "#/$defs/listOp", "description": "搜索:request + 结果列表/条目规则" },
    "explore": { "$ref": "#/$defs/exploreOp", "description": "分类浏览" },
    "bookInfo": { "$ref": "#/$defs/bookRules", "description": "书详情页字段抽取" },
    "toc": { "$ref": "#/$defs/toc", "description": "目录(章节 + 分卷)" },
    "content": { "$ref": "#/$defs/content", "description": "正文(可分页)" },
    "samples": { "type": "array", "items": { "$ref": "#/$defs/sample" }, "description": "黄金样例,驱动验证/自愈" }
  },
  "$defs": {
    "rule": {
      "description": "一条抽取规则:叶子 或 组合子(firstOf/concat/literal/template)。",
      "oneOf": [
        { "$ref": "#/$defs/leaf" },
        { "$ref": "#/$defs/firstOf" },
        { "$ref": "#/$defs/concat" },
        { "$ref": "#/$defs/literal" },
        { "$ref": "#/$defs/template" }
      ]
    },
    "leaf": {
      "type": "object",
      "additionalProperties": false,
      "description": "在当前上下文上做一次抽取。HTML 的 select 为 self-or-descendant 语义;省略 select=元素自身。",
      "properties": {
        "via": { "enum": ["css", "xpath", "json", "regex", "raw"], "default": "css", "description": "抽取后端;raw=直接用上下文值" },
        "select": { "type": "string", "description": "css 选择器 / JSONPath / 正则;via=raw 时省略" },
        "index": { "type": "integer", "description": "取第 N 个匹配(值规则);负数从末尾" },
        "extract": { "$ref": "#/$defs/extract" },
        "clean": { "type": "array", "items": { "$ref": "#/$defs/cleanStep" }, "description": "有序后处理流水线" }
      }
    },
    "firstOf": {
      "type": "object",
      "additionalProperties": false,
      "required": ["firstOf"],
      "description": "返回首个非空子规则结果(回退/自愈)",
      "properties": { "firstOf": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/rule" } } }
    },
    "concat": {
      "type": "object",
      "additionalProperties": false,
      "required": ["concat"],
      "description": "拼接非空子规则结果",
      "properties": {
        "concat": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/rule" } },
        "join": { "type": "string", "default": "" }
      }
    },
    "literal": {
      "type": "object",
      "additionalProperties": false,
      "required": ["literal"],
      "description": "字面量值",
      "properties": { "literal": { "type": "string" } }
    },
    "template": {
      "type": "object",
      "additionalProperties": false,
      "required": ["template"],
      "description": "插值 {{key}}/{{page}}/{{pageSize}} 及请求级命名变量",
      "properties": { "template": { "type": "string" } }
    },
    "extract": {
      "description": "取值方式:文本/各种 HTML 形态/属性",
      "default": "text",
      "oneOf": [
        { "enum": ["text", "ownText", "html", "innerHtml", "outerHtml"] },
        {
          "type": "object",
          "additionalProperties": false,
          "required": ["attr"],
          "properties": { "attr": { "type": "string", "description": "属性名,如 href/src/data-src/content" } }
        }
      ]
    },
    "cleanStep": {
      "type": "object",
      "additionalProperties": false,
      "description": "单步后处理(用其中一种)",
      "properties": {
        "regex": { "type": "string", "format": "regex", "description": "匹配模式" },
        "replace": { "type": "string", "description": "替换为(配合 regex);默认空串" },
        "trim": { "type": "boolean" },
        "prepend": { "type": "string" },
        "append": { "type": "string" }
      }
    },
    "urlOrRule": {
      "description": "URL:可为字符串模板(支持 {{key}}/{{page}})或一条规则",
      "oneOf": [{ "type": "string" }, { "$ref": "#/$defs/rule" }]
    },
    "http": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "headers": { "type": "object", "additionalProperties": { "type": "string" } },
        "cookies": { "type": "object", "additionalProperties": { "type": "string" }, "description": "静态 cookie;也是运行时注入 clearance cookie 的落点" },
        "warmup": { "type": "array", "items": { "type": "string" }, "description": "先 GET 这些页以预热会话 cookie" },
        "charset": { "enum": ["auto", "utf-8", "gbk", "gb18030", "big5"], "default": "auto" },
        "timeout": { "type": "integer", "description": "毫秒" },
        "retry": {
          "type": "object", "additionalProperties": false,
          "properties": { "max": { "type": "integer" }, "backoffMs": { "type": "integer" } }
        },
        "rateLimit": {
          "type": "object", "additionalProperties": false,
          "properties": { "maxCount": { "type": "integer" }, "perMs": { "type": "integer" } }
        }
      }
    },
    "request": {
      "type": "object",
      "additionalProperties": false,
      "required": ["url"],
      "properties": {
        "url": { "$ref": "#/$defs/urlOrRule" },
        "method": { "enum": ["GET", "POST"], "default": "GET" },
        "body": { "$ref": "#/$defs/urlOrRule" },
        "headers": { "type": "object", "additionalProperties": { "type": "string" } },
        "vars": { "type": "object", "additionalProperties": { "$ref": "#/$defs/rule" }, "description": "命名捕获,供 template 使用" }
      }
    },
    "bookRules": {
      "type": "object",
      "additionalProperties": false,
      "description": "一本书的字段抽取(均可省略)",
      "properties": {
        "name": { "$ref": "#/$defs/rule" },
        "author": { "$ref": "#/$defs/rule" },
        "cover": { "$ref": "#/$defs/rule" },
        "intro": { "$ref": "#/$defs/rule" },
        "kind": { "$ref": "#/$defs/rule" },
        "lastChapter": { "$ref": "#/$defs/rule" },
        "tocUrl": { "$ref": "#/$defs/rule" },
        "wordCount": { "$ref": "#/$defs/rule" }
      }
    },
    "listOp": {
      "type": "object",
      "additionalProperties": false,
      "required": ["request", "list", "item"],
      "properties": {
        "request": { "$ref": "#/$defs/request" },
        "list": { "$ref": "#/$defs/rule", "description": "列表规则:选中所有结果条目" },
        "item": { "$ref": "#/$defs/bookRules" }
      }
    },
    "exploreOp": {
      "type": "object",
      "additionalProperties": false,
      "required": ["categories", "list", "item"],
      "properties": {
        "categories": {
          "type": "array",
          "items": {
            "type": "object", "additionalProperties": false, "required": ["title", "url"],
            "properties": { "title": { "type": "string" }, "url": { "$ref": "#/$defs/urlOrRule" } }
          }
        },
        "list": { "$ref": "#/$defs/rule" },
        "item": { "$ref": "#/$defs/bookRules" }
      }
    },
    "toc": {
      "type": "object",
      "additionalProperties": false,
      "required": ["list", "name", "url"],
      "properties": {
        "list": { "$ref": "#/$defs/rule", "description": "列表规则:章节与卷节点(按文档顺序)" },
        "name": { "$ref": "#/$defs/rule" },
        "url": { "$ref": "#/$defs/rule" },
        "isVolume": { "$ref": "#/$defs/rule", "description": "非空即视为卷标题" },
        "nextPage": { "$ref": "#/$defs/rule", "description": "下一页 URL;为空即停" },
        "maxPages": { "type": "integer", "default": 100 }
      }
    },
    "content": {
      "type": "object",
      "additionalProperties": false,
      "required": ["value"],
      "properties": {
        "value": { "$ref": "#/$defs/rule", "description": "本页正文" },
        "nextPage": { "$ref": "#/$defs/rule", "description": "下一页 URL;为空即停" },
        "maxPages": { "type": "integer", "default": 100 }
      }
    },
    "sample": {
      "type": "object",
      "additionalProperties": false,
      "required": ["bookUrl"],
      "properties": {
        "bookUrl": { "type": "string" },
        "expect": {
          "type": "object",
          "additionalProperties": true,
          "properties": {
            "name": { "type": "string" },
            "minChapters": { "type": "integer" },
            "volumes": { "type": "integer" },
            "minContentChars": { "type": "integer" }
          }
        }
      }
    }
  }
}
