[
  {
    "bookSourceComment": "建议登录\n\n酷安 @吉王义昊\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\n原整理修改：GitHub@ZWolken\n原作者：酷安户山香澄Official\n\n2024.5.1\n酷安 @吉王义昊\n更改登录URL\n书源内链接改用相对URL\n更改发现\n更改部分页面显示效果\n2024.7.19\n酷安 @吉王义昊\n空白封面时返回无封面以便使用阅读默认封面\n2026.3.5\n酷安 @吉王义昊\n添加R18发现分类\n小说详情页添加评论显示功能\n2026.3.6\n酷安 @吉王义昊\n添加章节评论功能\n2025.3.21 @Dark-Tide\n简介兼容Sigma版阅读html渲染",
    "bookSourceGroup": "轻小说",
    "bookSourceName": "ESJ Zone",
    "bookSourceType": 0,
    "bookSourceUrl": "https://www.esjzone.one",
    "bookUrlPattern": "https?://www.esjzone.one/detail/.*",
    "customButton": false,
    "customOrder": 253,
    "enabled": true,
    "enabledCookieJar": true,
    "enabledExplore": true,
    "eventListener": false,
    "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': `/my/favorite/{{page}}`,\n    'style':{\n      layout_flexGrow:2,\n      layout_flexBasisPercent:0.25\n    \t}\n  },\n  {\n    'title': \"观看记录\",\n    'url': `/my/view/{{page}}`,\n    'style':{\n      layout_flexGrow:2,\n      layout_flexBasisPercent:0.25\n    \t}\n  }\n]\nhtml=org.jsoup.Jsoup.parse(java.ajax(source.bookSourceUrl+\"/list-01/\"))\ncategorys=html.select(\"#category option\")\nsortings=html.select(\"#sorting option\")\ncategorys.forEach(category => {\n  result.push({\n    'title': '>> ' + java.t2s(category.text()) + ' <<',\n    'url': \"\",\n    'style':{\n      layout_flexGrow:1,\n      layout_flexBasisPercent:1\n    }\n  })\n  sortings.forEach(sorting => {\n    result.push({\n      'title': java.t2s(sorting.text()),\n      'url':`/list-${category.attr('value')}${sorting.attr('value')}/{{page}}.html`,\n      'style':{\n        layout_flexGrow:2,\n        layout_flexBasisPercent:0.25\n      \t}\n    })\n  })\n})\nresult.push({\n  'title': '>> R18 <<',\n  'url': \"\",\n  'style':{\n    layout_flexGrow:1,\n    layout_flexBasisPercent:1\n  }\n})\nsortings.forEach(sorting => {\n  result.push({\n    'title': java.t2s(sorting.text()),\n    'url':`/tags-2${sorting.attr('value')}/R18/{{page}}.html`,\n    'style':{\n      layout_flexGrow:2,\n      layout_flexBasisPercent:0.25\n    \t}\n  })\n})\nJSON.stringify(result)",
    "jsLib": "const reviewHtmlTemplate = `<!doctype html>\n<html lang=\"zh-CN\">\n<head><meta charset=\"utf-8\" /><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\"><meta name=\"color-scheme\" content=\"light dark\"><style>\nhtml,body{margin:0;padding:0;height:100%;overflow:hidden}body{font-family:sans-serif;display:flex;flex-direction:column}\nul{list-style:none;margin:0;padding:0}\n.comment-item{margin:15px 0;border-bottom:1px solid rgba(128,128,128,.2);padding-bottom:15px}\n.comment-item:last-child{border-bottom:none}\n.comment-header{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:6px}\n.comment-author{font-weight:bold;font-size:14px}.comment-author-guest{font-weight:normal;font-size:14px;color:#888;font-style:italic}\n.comment-meta{font-size:12px;color:#999;text-align:right}\n.comment-text{font-size:15px;margin:6px 0;line-height:1.5}\n.comment-quote{background:rgba(128,128,128,.1);border-left:3px solid rgba(128,128,128,.4);padding:5px 10px;margin:6px 0;font-size:13px;color:#999;border-radius:2px}\n.no-data{width:100%;height:300px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px}\n.no-data-icon{font-size:56px;line-height:1;opacity:.6}\n.no-data-title{font-size:16px;color:#808080}\n.loading{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}\n.loader{width:50px;height:50px;border:6px solid #f3f3f3;border-top:6px solid #3498db;border-radius:50%;animation:spin 1s linear infinite}\n@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}\n.hidden{display:none}\n.post-section{padding:10px 15px;border-bottom:1px solid rgba(128,128,128,.2)}\n#scroll-area{flex:1;overflow-y:auto;padding:0 15px 10px;position:relative;min-height:0}\n.post-section-title{font-size:14px;font-weight:bold;margin-bottom:10px;color:inherit;opacity:.8}\n.reply-hint{font-size:12px;color:#3498db;margin-bottom:8px;display:none}\n.comment-textarea{width:100%;box-sizing:border-box;border:1px solid rgba(128,128,128,.3);border-radius:8px;padding:8px 10px;font-size:14px;min-height:80px;resize:vertical;font-family:sans-serif;background:transparent;color:inherit;outline:none}\n.comment-textarea:focus{border-color:rgba(52,152,219,.6)}\n.post-footer{display:flex;justify-content:space-between;align-items:center;margin-top:8px}\n.post-status{font-size:12px;color:#999}\n.btn-submit{background:#3498db;color:#fff;border:none;border-radius:16px;padding:6px 16px;font-size:13px;cursor:pointer}\n.btn-submit:disabled{opacity:.5;cursor:default}\n.btn-reply{background:none;border:none;color:#3498db;font-size:12px;cursor:pointer;padding:2px 0}\n.btn-cancel{background:none;border:none;color:#999;font-size:12px;cursor:pointer;margin-right:6px}\n.comment-footer{margin-top:4px}\n.comment-text img,.comment-quote img{max-height:40px;vertical-align:middle}\n</style></head>\n<body>\n<div id=\"post-section\" class=\"post-section\">\n<div class=\"post-section-title\">你的回應</div>\n<div id=\"reply-hint\" class=\"reply-hint\"></div>\n<textarea id=\"comment-textarea\" class=\"comment-textarea\" placeholder=\"輸入你的回應...\"></textarea>\n<div class=\"post-footer\"><span id=\"post-status\" class=\"post-status\"></span><div><button id=\"btn-cancel-reply\" class=\"btn-cancel\" style=\"display:none\" onclick=\"cancelReply()\">取消回覆</button><button id=\"btn-submit\" class=\"btn-submit\" onclick=\"submitComment()\">送出</button></div></div>\n</div>\n<div id=\"scroll-area\">\n<div id=\"loading\" class=\"loading\"><div class=\"loader\"></div></div>\n<div id=\"no_data\" class=\"no-data hidden\">\n<div class=\"no-data-icon\">💬</div>\n<div class=\"no-data-title\">暂无评论</div></div>\n<ul id=\"comment-list\"></ul>\n</div>$2\n<script>\nvar _replyData=null,_chapterUrl=\"\",_rawParams=\"\",_scrollAfterLoad=false;\nfunction updateView(params){\n    window.run(\"getChapterComments(\"+params+\")\").then(function(result){\n        document.getElementById(\"loading\").classList.add(\"hidden\");\n        if(result&&result!==\"\"){\n            var tpl=document.createElement(\"template\");\n            tpl.innerHTML=result;\n            document.getElementById(\"comment-list\").appendChild(tpl.content);\n        }else{\n            document.getElementById(\"no_data\").classList.remove(\"hidden\");\n        }\n        if(_scrollAfterLoad){_scrollAfterLoad=false;var sa=document.getElementById(\"scroll-area\");sa.scrollTop=sa.scrollHeight;}\n    }).catch(function(){\n        document.getElementById(\"loading\").classList.add(\"hidden\");\n        document.getElementById(\"no_data\").classList.remove(\"hidden\");\n    });\n}\nfunction showReplyForm(btn){\n    _replyData=btn.getAttribute(\"data-comment\");\n    var hint=document.getElementById(\"reply-hint\");\n    hint.textContent=\"回覆 \"+btn.getAttribute(\"data-author\");\n    hint.style.display=\"block\";\n    document.getElementById(\"btn-cancel-reply\").style.display=\"\";\n    document.getElementById(\"comment-textarea\").focus();\n}\nfunction cancelReply(){\n    _replyData=null;\n    document.getElementById(\"reply-hint\").style.display=\"none\";\n    document.getElementById(\"btn-cancel-reply\").style.display=\"none\";\n}\nfunction refreshComments(){document.getElementById(\"comment-list\").innerHTML=\"\";document.getElementById(\"no_data\").classList.add(\"hidden\");document.getElementById(\"loading\").classList.remove(\"hidden\");_scrollAfterLoad=true;updateView(_rawParams);}\nfunction submitComment(){\n    var content=document.getElementById(\"comment-textarea\").value.trim();\n    if(!content){document.getElementById(\"comment-textarea\").focus();return;}\n    content=content.split(String.fromCharCode(10)).join(\"<br>\");\n    var btn=document.getElementById(\"btn-submit\");\n    var st=document.getElementById(\"post-status\");\n    btn.disabled=true;st.textContent=\"送出中...\";\n    window.run(\"postForumReply(\"+JSON.stringify(_chapterUrl)+\",\"+JSON.stringify(content)+\",\"+JSON.stringify(_replyData||\"\")+\")\").then(function(res){\n        btn.disabled=false;\n        try{\n            var r=JSON.parse(res);\n            if(r.status===200){\n                document.getElementById(\"comment-textarea\").value=\"\";\n                cancelReply();\n                st.textContent=\"已送出！\";\n                setTimeout(function(){st.textContent=\"\";refreshComments();},1500);\n            }else{st.textContent=r.msg||\"送出失敗\";}\n        }catch(e){st.textContent=\"送出失敗\";}\n    }).catch(function(){btn.disabled=false;st.textContent=\"網路錯誤\";});\n}\nvar el=document.getElementById(\"params\");\nif(el){\n    var raw=el.innerText.trim();\n    _chapterUrl=raw.charAt(0)===\"'\"?raw.slice(1,-1):raw;\n    _rawParams=raw;\n    updateView(raw);\n}\n</script></body>\n</html>`;\n\nfunction showChapterComments() {\n    const { java, chapter } = this;\n    const url = chapter.url;\n    const paramsHtml = '<div class=\"hidden\" id=\"params\">\\'' + url + '\\'</div>';\n    java.showBrowser(\"\", reviewHtmlTemplate.replace(\"$2\", paramsHtml), \"window.run=run\", '{\"heightPercentage\":0.8,\"expandedCornersRadius\":20,\"hideFriction\":0.9}');\n}\n\nfunction escapeHtml(str) {\n    return String(str)\n        .replace(/&/g, '&amp;')\n        .replace(/</g, '&lt;')\n        .replace(/>/g, '&gt;')\n        .replace(/\"/g, '&quot;');\n}\n\nfunction fixRelativeUrls(html, origin) {\n    if (html == null) return '';\n    html = String(html);\n    if (!html || !origin) return html;\n    return html.replace(/\\bsrc=\"([^\"]+)\"/g, function(m, url) {\n        if (/^https?:\\/\\//i.test(url) || /^data:/i.test(url)) return m;\n        if (url.charAt(0) === '/') return 'src=\"' + origin + url + '\"';\n        return 'src=\"' + origin + '/' + url + '\"';\n    });\n}\nfunction getChapterComments(chapterUrl) {\n    var html = this.java.ajax(chapterUrl);\n    var doc = org.jsoup.Jsoup.parse(html);\n    var commentArr = doc.select(\"section.comments-section div.comment\").toArray();\n    if (commentArr.length === 0) return \"\";\n    var originMatch = /^(https?:\\/\\/[^\\/]+)/.exec(chapterUrl);\n    var origin = originMatch ? originMatch[1] : '';\n    var result = [];\n    for (var i = 0; i < commentArr.length; i++) {\n        var c = commentArr[i];\n        var isRegistered = c.select(\"span.comment-title a\").size() > 0;\n        var author = escapeHtml(c.select(\"span.comment-title\").text());\n        var floor = escapeHtml(c.select(\"span.comment-floor\").text());\n        var date = escapeHtml(c.select(\"span.comment-meta:not(.comment-floor)\").text());\n        var quoteEl = c.select(\"blockquote p\");\n        var quote = quoteEl.size() > 0 ? fixRelativeUrls(quoteEl.html(), origin) : \"\";\n        var textEl = c.select(\"p.comment-text\");\n        var text = textEl.size() > 0 ? fixRelativeUrls(textEl.html(), origin) : \"\";\n        var replyLink = c.select(\"a.forum_reply\");\n        var replyData = replyLink.size() > 0 ? escapeHtml(replyLink.attr(\"data-comment\")) : \"\";\n        var authorClass = isRegistered ? \"comment-author\" : \"comment-author-guest\";\n        var item = '<li class=\"comment-item\">';\n        item += '<div class=\"comment-header\"><span class=\"' + authorClass + '\">' + author + '</span><span class=\"comment-meta\">' + floor + ' ' + date + '</span></div>';\n        if (quote) item += '<div class=\"comment-quote\">' + quote + '</div>';\n        item += '<div class=\"comment-text\">' + text + '</div>';\n        if (replyData) {\n            item += '<div class=\"comment-footer\"><button class=\"btn-reply\" data-comment=\"' + replyData + '\" data-author=\"' + author + '\" onclick=\"showReplyForm(this)\">回覆</button></div>';\n        }\n        item += '</li>';\n        result.push(item);\n    }\n    return result.join(\"\");\n}\nfunction postForumReply(chapterUrl, content, replyData) {\n    var tokenBody;\n    try {\n        tokenBody = this.java.post(\n            chapterUrl,\n            'plxf=getAuthToken',\n            {'Content-Type': 'application/x-www-form-urlencoded'}\n        ).body();\n    } catch(e) {\n        return JSON.stringify({status: 500, msg: '無法連接伺服器'});\n    }\n    var tokenMatch = /<JinJing>(.*?)<\\/JinJing>/.exec(tokenBody);\n    if (!tokenMatch) {\n        return JSON.stringify({status: 500, msg: '獲取令牌失敗'});\n    }\n    var token = tokenMatch[1];\n    var originMatch = /^(https?:\\/\\/[^\\/]+)/.exec(chapterUrl);\n    var origin = originMatch ? originMatch[1] : source.bookSourceUrl;\n    var forumMatch = /\\/(\\d+)\\.html/.exec(chapterUrl);\n    if (!forumMatch) {\n        return JSON.stringify({status: 500, msg: '無法解析章節ID'});\n    }\n    var forumId = forumMatch[1];\n    var postBody = 'data=forum&forum_id=' + encodeURIComponent(forumId) + '&content=' + encodeURIComponent(content);\n    if (replyData) {\n        postBody += '&reply=' + encodeURIComponent(replyData);\n    }\n    var responseBody;\n    try {\n        responseBody = this.java.post(\n            origin + '/inc/forum_reply.php',\n            postBody,\n            {\n                'Content-Type': 'application/x-www-form-urlencoded',\n                'Authorization': token\n            }\n        ).body();\n    } catch(e) {\n        return JSON.stringify({status: 500, msg: '網路錯誤'});\n    }\n    return responseBody;\n}\n",
    "lastUpdateTime": 1774345798864,
    "loginUrl": "/my/profile.html",
    "respondTime": 2438,
    "ruleBookInfo": {
      "author": "class.list-unstyled mb-2 book-detail@tag.li.1@tag.a@text\n@js:\njava.t2s(result)",
      "canReName": "true",
      "coverUrl": "@css:div.col-md-3 > div > a > img@src",
      "intro": "<p>{{@@section.m-t-20@a.tag@text##(^|[\\n])##🏷️}}</p>\n{{'&lrm;'}}\n<br>\n{{@@div.description@html}}\n@js:\nvar intro = java.t2s(result);\ntry {\n  var commentsHtml = java.getString(\"@@.comments-section@html\");\n  if (commentsHtml && commentsHtml.trim() !== \"\") {\n    var doc = org.jsoup.Jsoup.parse(\"<div>\" + commentsHtml + \"</div>\");\n    var comments = doc.select(\"div.comment\");\n    var total = comments.size();\n    if (total > 0) {\n      var commentText = \"\\n<br><p>☕【评论】\\n评论数：\" + total + \"</p>\";\n      for (var i = 0; i < comments.size(); i++) {\n        var c = comments.get(i);\n        var name = c.select(\"span.comment-title a\").text();\n        var floor = c.select(\"span.comment-floor\").text();\n        var date = c.select(\"span.comment-meta:not(.comment-floor)\").text();\n        var quote = c.select(\"blockquote p\").text();\n        var text = c.select(\"p.comment-text\").text();\n        commentText += \"\\n<p>\\u200e<br>[\" + date + \"] \" + floor + \" 【\" + name + \"】</p>\";\n        if (quote && quote.trim()) commentText += \"\\n<p>> \" + quote + \"</p>\";\n        if (text && text.trim()) commentText += \"\\n<p>\" + text + '</p>';\n      }\n      intro += java.t2s(commentText)\n    }\n  }\n} catch(e) {}\nresult = '<usehtml>' + intro.replaceAll(/<a[^>]*?href=[\"'](.*?)[\"'][^>]*?>/g,\n\t(match, href) => {\n    const fullUrl = href.startsWith('/') ? `https://www.esjzone.one${href}` : href;\n    if (/^https?:\\/\\/www\\.esjzone\\.one\\/detail\\//.test(fullUrl)) {\n      return `<a href=\"legado://import/addToBookshelf?src=${fullUrl},{'origin':'${source.key}'}\">`;\n    }\n    return `<a href=\"${fullUrl}\">`;\n  }\n) + '</usehtml>';",
      "kind": "关注：{{@css:.book-detail label:has(.icon-eye)@text}},喜欢：{{@css:.book-detail label:has(.icon-heart)@text}}\n{{@@class.list-unstyled mb-2 book-detail@tag.li.-2@text##.+: }}\n{{@@class.list-unstyled mb-2 book-detail@tag.li.-1@text## .+}}",
      "lastChapter": "@css:div[id = chapterList] >a:last-child@text\n@js:\njava.t2s(result)",
      "name": "@css:div.col-md-9.book-detail > h2@text\n@js:\njava.t2s(result)",
      "wordCount": "@css: .book-detail label:has(.icon-file-text)@text##,##"
    },
    "ruleContent": {
      "content": "@css:div.forum-content.mt-3,div[class =d_post_content j_d_post_content]@all\n@js:\njava.t2s(result)",
      "subContent": "@js:\nif (!chapter.isVolume()) {\n    var svg = '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1000\" height=\"80\"><rect width=\"1000\" height=\"80\" fill=\"#f0f0f0\" rx=\"15\"/><text x=\"500\" y=\"52\" font-family=\"Arial\" font-size=\"40\" fill=\"#666\" text-anchor=\"middle\">💬 章节评论</text></svg>';\n    result = '<img src=\"data:image/svg+xml;base64,' + java.base64Encode(svg) + ',{\\\"click\\\":\\\"showChapterComments()\\\"}\"/>';\n} else {\n    result = '';\n}"
    },
    "ruleExplore": {},
    "ruleSearch": {
      "author": "class.card-author@tag.a@text\n@js:\njava.t2s(result)",
      "bookList": "@css: div.col-xl-9.col-lg-8.p-r-30 > div.row > div || .product-item",
      "bookUrl": "@css:div> div > div > h5 > a, .product-title > a@href",
      "coverUrl": "class.main-img@class.lazyload@data-src\n@js:\nresult.match(\"empty\") ? \"\" : result",
      "intro": "class.book-ep@div.-1@text\n@js:\njava.t2s(result)",
      "kind": "{{@css:.column:has(.icon-star-s)@text}},关注：{{@css:.column:has(.icon-eye)@text}},喜欢：{{@css:.column:has(.icon-heart)@text}},羽毛：{{@css:.column:has(.icon-feather)@text}},评论：{{@css:.column:has(.icon-message-square)@text}}##(\\([^()]+)##星$1人\n@js:\n!!result.split(\",\")[0] ? result : java.getString(\"class.book-update@text\")",
      "lastChapter": "@css:div>div>div[class=card-ep mt-2], .book-ep a@text\n@js:\njava.t2s(result)",
      "name": "@css: .card-title a, .product-title@text\n@js:\njava.t2s(result)",
      "wordCount": "@css: .column:has(.icon-file-text)@text##,##"
    },
    "ruleToc": {
      "chapterList": "#chapterList@a[data-title], p.non, summary",
      "chapterName": "text\n@js:\njava.t2s(result)",
      "chapterUrl": "href",
      "isVolume": "p.non@text||summary@text"
    },
    "searchUrl": "/tags/{{key}}/{{page}}.html",
    "weight": 0
  }
]