[
  {
    "bookSourceComment": "酷安 @吉王义昊\nGitHub：https://github.com/jiwangyihao/source-j-legado\n\n# 关于许可的额外声明（在线版本参见 GitHub，以在线版本为准）\n\n- 当许可证与本声明冲突时，以本声明为准；\n- 对于本仓库中的任意代码片段：按照 `MPL 2.0` 中有关约定执行；\n- 对于本仓库中的某一完整书源的转载或二次开发，需满足以下全部条件：\n  - 在[本仓库](https://github.com/jiwangyihao/source-j-legado)的 `issue` 中提出请求并具体说明转载地址、二次开发后的书源开源地址以及其他必要信息；\n  - 等待原作者（即本仓库的初始所有者和初始代码贡献者 [@jiwangyihao](https://github.com/jiwangyihao)）查看并通过 `issue` 或依据原作者要求更改转载方式或补充更详细的信息。\n  - 考虑到本项目弃坑的可能，新 `issue` 开启后超过 20 个工作日原作者没有回复或者原作者回复要求更改的 `issue` 在更改后超过 20 个工作日原作者没有回复即视为原作者通过该 `issue`：\n    - 此处的 `issue` 仅包括在[本仓库](https://github.com/jiwangyihao/source-j-legado)开启的，处于「开启状态」的 issue。（也就是说，请不要在已经关闭的 issue 中回复）。\n    - 对于此种方式通过的 issue，转载/二次开发者仍应当遵守本声明中已经写明的相关约定。\n  - 不得上传至源仓库或整理至`非轻小说专用`的书源合集中并应当避免其他人将转载/二次开发版本上传至源仓库或整理至`非轻小说专用`的书源合集中：\n    - 关于轻小说的定义的额外说明：不包括国内的原创网络文学作品（如 `SF 轻小说` 中的原创轻小说以及`起点中文网`中标签包含轻小说的作品）。\n    - 轻小说专用的定义：有且仅有想看轻小说的人可能会添加。\n  - 必须在转载/二次开发地址的明显位置完整包含本声明的全部内容。\n  - 必须保留源注释中原有的更改记录。\n\n2024.4.10\n酷安 @吉王义昊\n处理目录中的卷名\n解决了章节中的引号导致加载目录失败的问题\n2024.5.7\n酷安 @吉王义昊\n支持登录\n支持网站阅读历史\n支持收藏（多收藏夹）\n新增发现\n2024.5.31\n酷安 @吉王义昊\n修正优先级序列模式下意外忽略后续翻译源的问题\n2024.6.21\n酷安 @吉王义昊\n跟进最新 API 更改\n2024.7.8\n酷安 @吉王义昊\n跟进源站发现更改\n2024.7.23\n酷安 @吉王义昊\n跟进源站登录更改\n2024.8.10\n酷安 @吉王义昊\n支持添加网址\n2024.10.28\n酷安 @吉王义昊\n支持「Lordly•阅读」\n支持在正文中加载图片\n2025.4.25\n酷安 @吉王义昊\n修复发现页加载\n调整目录章节名规则\n2025.9.5\n酷安 @吉王义昊\n跟进域名更改\n修复发现\n2025.9.27\n@DarkTide\n添加文库本支持\n发现增加文库分类\n2025.12.5\n@DarkTide\n文库本支持切换语言及翻译",
    "bookSourceGroup": "轻小说,机翻",
    "bookSourceName": "轻小说机翻🤖",
    "bookSourceType": 0,
    "bookSourceUrl": "https://n.novelia.cc/",
    "bookUrlPattern": "https?://n.novelia.cc/(api/)?novel/\\w+/\\w+",
    "customOrder": 9,
    "enabled": true,
    "enabledCookieJar": true,
    "enabledExplore": true,
    "exploreUrl": "@js:\nresult=[\n  {\n    'title': '>> 个人中心 <<',\n    'url': \"\",\n    'style':{\n      layout_flexGrow:1,\n      layout_flexBasisPercent:1\n    }\n  },\n  {\n    'title': \"阅读历史\",\n    'url': `/api/user/read-history?page={{page-1}}&pageSize=30`,\n    'style':{\n      layout_flexGrow:2,\n      layout_flexBasisPercent:0.25\n    \t}\n  }\n]\nfavored = String(java.ajax(source.bookSourceUrl + \"/api/user/favored\"))\nif (favored.match(\"Token不合法或者过期\")) {\n  result[0].title = '>> 个人中心 | 未登录 <<'\n} else {\n  favored = JSON.parse(java.ajax(source.bookSourceUrl + \"/api/user/favored\")).favoredWeb\n  favored.forEach(favor => {\n    result.push({\n      'title': favor.title,\n      'url': `/api/user/favored-web/${favor.id}?page={{page-1}}&pageSize=30&query=&provider=kakuyomu%2Csyosetu%2Cnovelup%2Chameln%2Cpixiv%2Calphapolis&type=0&level=0&translate=0&sort=update`,\n      'style':{\n        layout_flexGrow:2,\n        layout_flexBasisPercent:0.25\n      \t}\n    })\n  })\n}\nresult.push({\n    'title': '> > 文库小说 < <',\n    'url': null,\n    'style': {\n        layout_flexGrow: 1,\n        layout_flexBasisPercent: 1\n    }\n});\nString(java.ajax(`https://fastly.jsdelivr.net/gh/FishHawk/auto-novel@main/web/src/pages/list/option.ts`))\n.match(/getWenkuListOptions.*?{([\\s\\S]+?)}[^,;]/i)[1]\n.match(/levels.*?=.*?\\[[^\\]]+\\]/g)\n.flatMap(s => s.match(/'[^']+'/g))\n.map(s => s.replaceAll(\"'\", \"\"))\n.forEach((level, id) => {\n    result.push({\n        'title': level,\n        'url': `/api/wenku?page={{page-1}}&pageSize=24&query=&level=${id}`,\n        'style': {\n            layout_flexGrow: 1,\n            layout_flexBasisPercent: id ? 0.25 : 1\n        }\n    });\n});\nString(java.ajax(`https://fastly.jsdelivr.net/gh/FishHawk/auto-novel@main/web/src/pages/MainLayout.vue, {\n  \"headers\": {\n    \"Authorization\": \"\"\n  }\n}`))\n.match(/label:.?'小说排行'[^\\]]+]/gi)[0]\n.match(/renderLabel\\(.+\\)/gi)\n.forEach((menu,index)=>{\n  if (index!=1) {\n    title=menu.replace(/renderLabel\\('(.+)',.?'(.+)'\\)/gi, \"$1\")\n    href=menu.replace(/renderLabel\\('(.+)',.?'(.+)'\\)/gi, \"$2\")\n    result.push({\n      'title': `>> ${title} <<`,\n      'url': \"\",\n      'style':{\n        layout_flexGrow:1,\n        layout_flexBasisPercent:1\n      }\n    })\n    html = org.jsoup.Jsoup.parse(String(java.webView(null,source.bookSourceUrl + href.replace(/^\\//i,''),null)))\n    sorts = html.select(\".n-layout-scroll-container h1 + div.n-flex > .n-flex\")\n    sorts[0].select(\".n-flex > .n-flex > .n-flex > span\").forEach(tag => {\n      result.push({\n        'title': tag.text(),\n        'url': href.replace(/\\/(.+)\\/(.+)\\/(.+)/gi, \"$3\") != \"2\" ? `/api/novel/rank/${href.replace(/\\/(.+)\\/(.+)\\/(.+)/gi, \"$2\")}?type=${title.replace(/(.+)：(.+)/gi,\"$2\").replace(\"异世界转移/转生\", \"异世界转生/转移\")}&genre=${tag.text()}&range=总计&status=全部&page={{page-1}}` : `/api/novel/rank/${href.replace(/\\/(.+)\\/(.+)\\/(.+)/gi, \"$2\")}?type=${title.replace(/(.+)：(.+)/gi,\"$2\")}&range=${tag.text()}&status=全部&page={{page-1}}`,\n        'style':{\n          layout_flexGrow:2,\n          layout_flexBasisPercent:0.4\n        \t}\n      })\n    \t})\n  }\n})\nJSON.stringify(result)",
    "jsLib": "function getConfig() {\n  const { book, source, java } = this\n  const config = {\n  \t  language: \"中日\",\n  \t  translation: \"SGYB\",\n  \t  translationArr: []\n  \t}\n  \tconst languages = [\n  \t  \"日文\",\n    \"中文\",\n    \"中日\",\n    \"日中\"\n  ]\n  const translations = {\n  \t  B: \"baiduParagraphs\",\n  \t  Y: \"youdaoParagraphs\",\n  \t  G: \"gptParagraphs\",\n  \t  S: \"sakuraParagraphs\"\n  \t}\n  \tfunction applyTran(tranStr) {\n  \t  config.translation=tranStr\n  \t  if (tranStr===\"并列\") tranStr=\"SGYB\"\n  \t  let tranArr = []\n  \t  for (let i=0; i<tranStr.length; i++) {\n  \t  \t  tranArr.push(translations[tranStr.charAt(i)])\n  \t  \t}\n  \t  \tconfig.translationArr=tranArr\n  \t}\n  \tfunction applyLanguage(lang) {\n  \t  if (languages.includes(lang))\n  \t  \t  config.language=lang\n  \t}\n  applyTran(config.translation)\n  const sVar = String(source.getVariable()).replace(/\\s/gi, '').split('-')\n  if (sVar[0]) applyLanguage(sVar[0])\n  if (sVar[1]) applyTran(sVar[1])\n  const bVar = String(book.getVariable(\"custom\")).replace(/\\s/gi, '').split('-')\n  if (bVar[0]) applyLanguage(bVar[0])\n  if (bVar[1]) applyTran(bVar[1])\n  return config\n}",
    "lastUpdateTime": 1765378015603,
    "loginCheckJs": "if (/Token不合法或者过期/.test(result.body()) && source.getLoginHeader()) {\n    eval(String(source.loginUrl));\n    login();\n    java.toast('Token已更新');\n    result = java.connect(result.url() + \",\" + JSON.stringify({\n        \"headers\": {\n            \"Authorization\": source.getLoginHeaderMap().get(\"Authorization\")\n        }\n    })\n    );\n}\nresult;",
    "loginUi": "[\n  {\n    \"name\": \"账号\",\n    \"type\": \"text\"\n  },\n  {\n    \"name\": \"密码\",\n    \"type\": \"password\"\n  }\n]",
    "loginUrl": "function login() {\n  let une = source.getLoginInfoMap().get(\"账号\")\n  let pwd = source.getLoginInfoMap().get(\"密码\")\n  if (une && pwd) {\n    let body = JSON.stringify({\n      username: une,\n      password: pwd,\n      app: \"n\"\n    })\n    let url = source.bookSourceUrl.replace(\"n.\", \"auth.\") + 'api/v1/auth/login'\n    let res = java.post(url, body, {\n      \"Content-Type\": \"application/json\"\n    })\n    if (res.body().match(/错误/gi)) {\n      throw(res.body())\n    }\n    //throw(String(res.body()))\n    //result = JSON.parse(res.body())\n    \n    let header = JSON.stringify({\n      \"Authorization\": \"Bearer \" + res.body()\n    })\n    source.putLoginHeader(header)\n  }\n}",
    "respondTime": 180000,
    "ruleBookInfo": {
      "author": "$.authors.*.name||$.authors",
      "coverUrl": "$.cover",
      "downloadUrls": "@js:\nconst { language, translation } = getConfig();\nif (language === \"日文\") java.toast('\\n文库本无法选择纯日文展现，已切换为默认 中日 展现');\nconst languageMap = {\n    \"中文\": \"zh\",\n    \"日文\": \"zh-jp\",\n    \"中日\": \"zh-jp\",\n    \"日中\": \"jp-zh\"\n};\nconst translationsMap = {\n    'S': 'sakura',\n    'G': 'gpt',\n    'Y': 'youdao',\n    'B': 'baidu'\n};\nconst mode = languageMap[language];\nconst translations = (translation === \"并列\"\n     ? Object.values(translationsMap).map(s => `translations=${s}`)\n     : translation.split('').map(c => `translations=${translationsMap[c]}`)).join('&');\nJSON.parse(src).volumeJp.map(e => `${baseUrl}/file/${e.volumeId}?mode=${mode}&translationsMode=priority&filename=zh-jp.Ysgyb.${e.volumeId}&${translations}`);",
      "init": "@js:\nif (/\\/wenku\\//.test(book.bookUrl)) book.type = 128;\nif (!/api/g.test(book.bookUrl)) {\n  book.bookUrl=book.bookUrl.replace(\"novel\",\"api/novel\")\n  result=java.ajax(book.bookUrl)\n}\nresult=JSON.parse(result)\nresult.points+=' PT'\nresult.visited+=' 浏览'\nJSON.stringify(result)",
      "intro": "{{$.introduction}}\n当前规则：语言展现形式-{{getConfig().language}}，翻译优先级-{{getConfig().translation}}（可在源变量或书籍变量中修改，详情参考变量说明）\n\n{{$.titleJp}}\n总计：{{$.total}} 百度：{{$.baidu}} 有道：{{$.youdao}} GPT：{{$.gpt}} Sakura：{{$.sakura}}\n\n{{$.introductionZh}}\n\n{{$.introductionJp}}",
      "kind": "$.attentions&&$.type&&$.points&&$.visited&&$.keywords",
      "lastChapter": "{{$.toc[-1].titleZh||$.toc[-1].titleJp}}",
      "name": "$.titleZh",
      "wordCount": "$.totalCharacters"
    },
    "ruleContent": {
      "content": "@js:\nresult=JSON.parse(result)\nconst config=getConfig()\nconst resultList=[]\nfor (let ip = 0; ip < result.paragraphs.length; ip++) {\n  let p = result.paragraphs[ip]\n  if (/^<图片>http.+$/.test(p)) {\n    p = '<img src=\"'+p.replace(/^<图片>/i, \"\")+'\">'\n    resultList.push(p)\n    continue\n  }\n  function generateTran(i) {\n    const chars = {\n  \t    baiduParagraphs: \"ᵇ\",\n  \t    youdaoParagraphs: \"ʸ\",\n  \t    gptParagraphs: \"ᵍ\",\n  \t    sakuraParagraphs: \"ˢ\"\n    }\n    if (config.translation===\"并列\") {\n      const processed=[]\n      for (let ii = 0; ii < config.translationArr.length; ii++) {\n        let t = config.translationArr[ii]\n        if (result[t]) {\n          if (/^<图片>http.+$/.test(result[t][i])) {\n            let tmp = '<img src=\"'+result[t][i].replace(/^<图片>/i, \"\")+'\">'\n            if (!processed.includes(tmp))\n              processed.push(tmp)\n            \tcontinue\n          }\n          processed.push((result[t][i]!=\"\" ? chars[t] : \"\") + result[t][i].trim())\n          continue\n        }\n        processed.push(\"\")\n      }\n      return processed.join(\"\\n\")\n    } else {\n      for (let tt=0; tt<config.translationArr.length; tt++) {\n        let t=config.translationArr[tt]\n        if (result[t]) {\n          if (/^<图片>http.+$/.test(result[t][i])) {\n            let tmp = '<img src=\"'+result[t][i].replace(/^<图片>/i, \"\")+'\">'\n            return tmp\n          }\n          return (result[t][i]!=\"\" ? chars[t] : \"\") + result[t][i].trim()\n        }\n      }\n    }\n  \t}\n  switch (String(config.language)) {\n  \t  case \"日文\":\n  \t    resultList.push(p)\n  \t    break\n  \t  case \"中文\":\n  \t    resultList.push(generateTran(ip))\n  \t    break\n  \t  case \"中日\":\n  \t    resultList.push([generateTran(ip), p.trim()].join(\"\\n\"))\n  \t    break\n  \t  case \"日中\":\n  \t    resultList.push([p, generateTran(ip)].join(\"\\n\"))\n  \t    break\n  \t}\n}\nresultList.join(\"\\n\")"
    },
    "ruleExplore": {},
    "ruleSearch": {
      "bookList": "$.items",
      "bookUrl": "@js:\n/\\/wenku\\?/.test(baseUrl) ? `/api/wenku/{{$.id}}` : `/api/novel/{{$.providerId}}/{{$.novelId}}`;",
      "checkKeyWord": "驯兽师少女的逃亡日记",
      "coverUrl": "$.cover",
      "intro": "{{$.titleJp}}\n总计：{{$.total}} 百度：{{$.baidu}} 有道：{{$.youdao}} GPT：{{$.gpt}} Sakura：{{$.sakura}}",
      "kind": "$.attentions&&$.type&&$.keywords",
      "lastChapter": "@js:\n`{{$.total}} - ` + new Date(parseInt({{$.updateAt}}) * 1000).toJSON().replace(/T|\\.\\d+Z/gi, ' ')",
      "name": "$.titleZh||$.titleJp"
    },
    "ruleToc": {
      "chapterList": "$.toc",
      "chapterName": "$.titleZh||$.titleJp",
      "chapterUrl": "@js:\nresult.chapterId ? `{{baseUrl}}/chapter/${result.chapterId}` : ''",
      "isVolume": "@js:\n!result.chapterId",
      "updateTime": "@js:\nresult.chapterId ? (`${result.titleJp} - ` + (result.createAt ? new Date(parseInt(result.createAt) * 1000).toJSON().replace(/T|\\.\\d+Z/gi, ' ') : '暂无')) : ''"
    },
    "searchUrl": "/api/novel?page={{page-1}}&pageSize=20&query={{key}}&provider=kakuyomu%2Csyosetu%2Cnovelup%2Chameln%2Cpixiv%2Calphapolis&level=0&type=0&translate=0&sort=0",
    "variableComment": "设置正文语言展现形式及翻译优先级\n！错误的设置可能导致书源工作不正常\n格式：语言展现形式 - 翻译优先级\n\n语言展现形式：\n- 日文\n- 中文\n- 中日\n- 日中\n翻译优先级：\n- 并列（展示所有存在的翻译）\n- 优先级序列，写在前面的优先采用，B-百度、Y-有道、G-GPT、S-Sakura，如 `SGYB`\n\n示例（默认值）：中日 - SGYB\n示例：中文 - 并列",
    "weight": 0
  }
]