微信读书二合一本地源(同人)

https://i.weread.qq.com

ah-fa-fa (14292)4天前

需要有微信读书会员才能阅读全部,但是部分付费网文不行,自行判断。游客登录无法阅读付费上🔒章节和听书,不建议自行抓包,直接微信扫码登录即可。

二维码导入
{
    "bookSourceComment": "需要有微信读书会员才能阅读全部,但是部分付费网文不行,自行判断。游客登录无法阅读付费上🔒章节和听书,不建议自行抓包,直接微信扫码登录即可。\n1045801523群号\n纯免费,盗版源\ntg:https:\/\/t.me\/+1F9ippiIxbVmZmM1",
    "bookSourceGroup": "app,api,微信读书",
    "bookSourceName": "微信读书二合一本地源(同人)",
    "bookSourceType": 0,
    "bookSourceUrl": "https:\/\/i.weread.qq.com",
    "customButton": false,
    "customOrder": -125,
    "enabled": true,
    "enabledCookieJar": false,
    "enabledExplore": true,
    "eventListener": false,
    "exploreUrl": "@js:wrExploreUrl(source);",
    "header": "@js:wrHeaderJson(source);",
    "jsLib": "\nvar WR_APP_VERSION = \"10.2.1.10167607\";\nvar WR_CAPTCHA_APP_ID = \"2044038556\";\nvar WR_WECHAT_APP_ID = \"wxab9b71ad2b90ff34\";\nvar WR_DEFAULT_USER_AGENT = \"WeRead\/10.2.1 WRBrand\/xiaomi Dalvik\/2.1.0 (Linux; U; Android 16; 2509FPN0BC Build\/BP2A.250605.031.A3)\";\nvar WR_REMAP_HEX = \"34ca55401db693c63130293532a7b811c2b516fa8bb124a4109004e908f83b8a9c8c44f9bc5c69e2a1dad2d37589f71e2d5056d77253bf22fb200f012e45876e6648f2e0cdfe67a943f49451cea54aee13268eccaa33145d0e39bbcf912b814dea99ec1a2c85c5d936744b18e1f13d9d419fb4170dd64cbedcaf972877f062ff71c1c8278f6c68a89be6591c1b1209984e3f063700ba1f0a192fc9d5d057496ffd25e4610c42cb96645fdbad60238d9a6dc3c45e3eb9926abd5b077f7695ed4fab847a80e778c7e5eb73836bfc38467d4765b352633a05d1efa3a6de9e3c02aeb27ba0f6f32ac0ac86035a540bf582d47ee3dfb0d8dd21e87c88a2795870b715\";\n\nfunction wrValue(src, name) {\n    var info = src.getLoginInfoMap();\n    var value = info == null ? null : info.get(name);\n    return value == null ? \"\" : String(value).trim();\n}\n\n\nfunction wrCoverUrl(value) {\n    value = String(value || \"\").trim();\n    if (value.indexOf(\"\/\/\") == 0) return \"https:\" + value;\n    if (value.indexOf(\"http:\/\/\") == 0) return \"https:\/\/\" + value.substring(7);\n    return value;\n}\n\nfunction wrLoginHeaderValue(src, name) {\n    try {\n        var headers = src.getLoginHeaderMap();\n        if (headers != null && typeof headers.get == \"function\") {\n            var value = headers.get(name);\n            if (value != null) return String(value).trim();\n        }\n        if (headers != null && headers[name] != null) return String(headers[name]).trim();\n    } catch (ignoreHeader) {}\n    return \"\";\n}\nfunction wrCredentials(src) {\n    var credentials = {\n        vid: wrValue(src, \"vid\") || wrLoginHeaderValue(src, \"vid\"),\n        accessToken: wrValue(src, \"accessToken\") || wrLoginHeaderValue(src, \"accessToken\"),\n        userAgent: wrValue(src, \"User-Agent\") || wrLoginHeaderValue(src, \"User-Agent\")\n    };\n    if (!credentials.vid || !credentials.accessToken || !credentials.userAgent) {\n        throw new Error(\"缺少 APP 登录参数。请先点击游客登录或微信扫码登录;抓包登录则需填写同一请求的 vid、accessToken 和完整 User-Agent\");\n    }\n    return credentials;\n}\nfunction wrHeaderObject(src) {\n    var credentials = wrCredentials(src);\n    return {\n        \"vid\": credentials.vid,\n        \"accessToken\": credentials.accessToken,\n        \"User-Agent\": credentials.userAgent,\n        \"baseapi\": \"36\",\n        \"appver\": WR_APP_VERSION,\n        \"basever\": WR_APP_VERSION,\n        \"osver\": \"16\",\n        \"channelId\": \"0\"\n    };\n}\nfunction wrHeaderJson(src) {\n    return JSON.stringify(wrHeaderObject(src));\n}\n\nfunction wrHeaderMap(src) {\n    var object = wrHeaderObject(src);\n    var map = new Packages.java.util.HashMap();\n    for (var name in object) {\n        if (Object.prototype.hasOwnProperty.call(object, name)) map.put(name, String(object[name]));\n    }\n    return map;\n}\n\nfunction wrCheckLogin(j, src) {\n    var url = \"https:\/\/i.weread.qq.com\/store\/search?count=1&type=0&keyword=%E6%B5%8B%E8%AF%95&v=2&scope=17&maxIdx=0\";\n    var response = j.get(url, wrHeaderMap(src), 20000);\n    if (response.statusCode() != 200) {\n        throw new Error(\"APP 登录校验失败,HTTP \" + response.statusCode() + \"。请重新从同一次 APP 抓包复制三项参数\");\n    }\n    var data = JSON.parse(String(response.body()));\n    if (data.errcode != null && Number(data.errcode) != 0) {\n        throw new Error(\"APP 登录校验失败,接口错误码 \" + data.errcode);\n    }\n    src.putLoginHeader(JSON.stringify({\n        \"vid\": wrValue(src, \"vid\"),\n        \"accessToken\": wrValue(src, \"accessToken\"),\n        \"User-Agent\": wrValue(src, \"User-Agent\")\n    }));\n    var message = \"验证成功:微信读书 APP 登录参数有效\";\n    j.longToast(message);\n    return message;\n}\n\nfunction wrU8(bytes, index) {\n    return bytes[index] & 255;\n}\n\nfunction wrLe16(bytes, index) {\n    return wrU8(bytes, index) | (wrU8(bytes, index + 1) << 8);\n}\n\nfunction wrLe32(bytes, index) {\n    return (wrU8(bytes, index) | (wrU8(bytes, index + 1) << 8) |\n        (wrU8(bytes, index + 2) << 16) | (wrU8(bytes, index + 3) << 24)) >>> 0;\n}\n\nfunction wrImul(a, b) {\n    var ah = (a >>> 16) & 65535;\n    var al = a & 65535;\n    var bh = (b >>> 16) & 65535;\n    var bl = b & 65535;\n    return (al * bl + (((ah * bl + al * bh) & 65535) << 16)) | 0;\n}\n\nfunction wrCrcTable() {\n    var table = [];\n    for (var i = 0; i < 256; i++) {\n        var value = i;\n        for (var bit = 0; bit < 8; bit++) {\n            value = (value >>> 1) ^ ((value & 1) ? 0xedb88320 : 0);\n        }\n        table[i] = value >>> 0;\n    }\n    return table;\n}\n\nfunction wrCrcStep(table, crc, value) {\n    return ((crc >>> 8) ^ table[(crc ^ value) & 255]) >>> 0;\n}\n\nfunction wrZipCryptoDecrypt(input, password) {\n    var table = wrCrcTable();\n    var key0 = 0x12345678;\n    var key1 = 0x23456789;\n    var key2 = 0x34567890;\n    function update(value) {\n        key0 = wrCrcStep(table, key0, value);\n        key1 = (wrImul((key1 + (key0 & 255)) | 0, 134775813) + 1) >>> 0;\n        key2 = wrCrcStep(table, key2, key1 >>> 24);\n    }\n    var i;\n    for (i = 0; i < password.length; i++) update(wrU8(password, i));\n    var output = Packages.java.util.Arrays.copyOf(input, input.length);\n    for (i = 0; i < input.length; i++) {\n        var temp = (key2 | 2) >>> 0;\n        var plain = wrU8(input, i) ^ ((wrImul(temp, temp ^ 1) >>> 8) & 255);\n        output[i] = plain > 127 ? plain - 256 : plain;\n        update(plain);\n    }\n    return output;\n}\n\nfunction wrDecryptZipPassword(j, src, encryptedKey) {\n    var credentials = wrCredentials(src);\n    var remap = j.hexDecodeToByteArray(WR_REMAP_HEX);\n    var vidBytes = new Packages.java.lang.String(credentials.vid).getBytes(\"UTF-8\");\n    var keyIv = Packages.java.nio.ByteBuffer.allocate(32).array();\n    for (var i = 0; i < 32; i++) {\n        var mapped = wrU8(remap, wrU8(vidBytes, i % vidBytes.length));\n        keyIv[i] = mapped > 127 ? mapped - 256 : mapped;\n    }\n    var key = Packages.java.util.Arrays.copyOfRange(keyIv, 0, 16);\n    var iv = Packages.java.util.Arrays.copyOfRange(keyIv, 16, 32);\n    var cipher = Packages.javax.crypto.Cipher.getInstance(\"AES\/CBC\/PKCS5Padding\");\n    cipher.init(\n        Packages.javax.crypto.Cipher.DECRYPT_MODE,\n        new Packages.javax.crypto.spec.SecretKeySpec(key, \"AES\"),\n        new Packages.javax.crypto.spec.IvParameterSpec(iv)\n    );\n    return cipher.doFinal(j.base64DecodeToByteArray(String(encryptedKey)));\n}\n\nfunction wrInflateRaw(input) {\n    var inflater = new Packages.java.util.zip.Inflater(true);\n    var stream = new Packages.java.util.zip.InflaterInputStream(\n        new Packages.java.io.ByteArrayInputStream(input), inflater\n    );\n    var output = new Packages.java.io.ByteArrayOutputStream();\n    var buffer = Packages.java.nio.ByteBuffer.allocate(8192).array();\n    var count;\n    try {\n        while ((count = stream.read(buffer)) != -1) output.write(buffer, 0, count);\n        return output.toByteArray();\n    } finally {\n        stream.close();\n        inflater.end();\n        output.close();\n    }\n}\n\nfunction wrFindEocd(bytes) {\n    var minimum = Math.max(0, bytes.length - 65557);\n    for (var i = bytes.length - 22; i >= minimum; i--) {\n        if (wrLe32(bytes, i) == 0x06054b50) return i;\n    }\n    return -1;\n}\n\nfunction wrExtractChapterHtml(zipBytes, password, j, src, bookId, chapterUid, paragraphMarkers) {\n    var eocd = wrFindEocd(zipBytes);\n    if (eocd < 0) throw new Error(\"章节 ZIP 目录损坏\");\n    var entryCount = wrLe16(zipBytes, eocd + 10);\n    var offset = wrLe32(zipBytes, eocd + 16);\n    var parts = [];\n    var infoText = \"\";\n    var entryNames = [];\n    for (var entryIndex = 0; entryIndex < entryCount; entryIndex++) {\n        if (wrLe32(zipBytes, offset) != 0x02014b50) throw new Error(\"章节 ZIP 中央目录损坏\");\n        var flags = wrLe16(zipBytes, offset + 8);\n        var method = wrLe16(zipBytes, offset + 10);\n        var compressedSize = wrLe32(zipBytes, offset + 20);\n        var nameLength = wrLe16(zipBytes, offset + 28);\n        var extraLength = wrLe16(zipBytes, offset + 30);\n        var commentLength = wrLe16(zipBytes, offset + 32);\n        var localOffset = wrLe32(zipBytes, offset + 42);\n        var nameBytes = Packages.java.util.Arrays.copyOfRange(zipBytes, offset + 46, offset + 46 + nameLength);\n        var name = String(new Packages.java.lang.String(nameBytes, \"UTF-8\"));\n        entryNames.push(name);\n        if (wrLe32(zipBytes, localOffset) != 0x04034b50) throw new Error(\"章节 ZIP 本地目录损坏\");\n        var localNameLength = wrLe16(zipBytes, localOffset + 26);\n        var localExtraLength = wrLe16(zipBytes, localOffset + 28);\n        var dataStart = localOffset + 30 + localNameLength + localExtraLength;\n        var compressed = Packages.java.util.Arrays.copyOfRange(zipBytes, dataStart, dataStart + compressedSize);\n        if ((flags & 1) != 0) {\n            compressed = wrZipCryptoDecrypt(compressed, password);\n            if (compressed.length < 12) throw new Error(\"章节 ZIP 密码头损坏\");\n            compressed = Packages.java.util.Arrays.copyOfRange(compressed, 12, compressed.length);\n        }\n        var isInfo = \/(^|\\\/)info\\.txt$\/i.test(name);\n        var isHtml = \/\\.(xhtml|html|htm)$\/i.test(name);\n        var isText = \/\\.txt$\/i.test(name) && !isInfo;\n        var baseName = name.substring(name.lastIndexOf(\"\/\") + 1);\n        var isWebNovelText = baseName && baseName.indexOf(\".\") < 0 && name.charAt(name.length - 1) != \"\/\";\n        if (isInfo || isHtml || isText || isWebNovelText) {\n            var plain;\n            if (method == 0) plain = compressed;\n            else if (method == 8) plain = wrInflateRaw(compressed);\n            else throw new Error(\"暂不支持的 ZIP 压缩方法:\" + method);\n            var text = String(new Packages.java.lang.String(plain, \"UTF-8\"));\n            if (isInfo) {\n                infoText = text;\n            } else {\n                if (isHtml) {\n                    var replacements = [];\n                    text = wrInsertParagraphBubbles(j, src, text, bookId, chapterUid, paragraphMarkers, replacements);\n                    text = wrFilterUnsupportedFootnotes(text);\n                    \/\/ Preserve the EPUB's original media tags and URLs. A Jsoup round-trip can\n                    \/\/ rewrite XHTML image markup after paragraph bubbles have been inserted.\n                    var bodyMatch = text.match(\/<body\\b[^>]*>([\\s\\S]*?)<\\\/body>\/i);\n                    var bodyHtml = bodyMatch ? bodyMatch[1] : text;\n                    for (var replacementIndex = 0; replacementIndex < replacements.length; replacementIndex++) {\n                        var replacement = replacements[replacementIndex];\n                        bodyHtml = bodyHtml.split(replacement.token).join(replacement.html);\n                    }\n                    var textOnly = bodyHtml.replace(\/<[^>]+>\/g, \"\").replace(\/&(?:nbsp|#160);\/gi, \" \").trim();\n                    var hasMedia = \/<(?:img|image|svg|video|audio)\\b\/i.test(bodyHtml);\n                    if (textOnly || hasMedia || replacements.length) parts.push(bodyHtml);\n                } else {\n                    text = wrInsertParagraphBubbles(j, src, text, bookId, chapterUid, paragraphMarkers, null);\n                    if (text.trim()) parts.push(text);\n                }\n            }\n        }\n        offset += 46 + nameLength + extraLength + commentLength;\n    }\n    if (parts.length == 0) {\n        var detail = \"章节包只有信息文件,账号无正文权限或登录已经失效\";\n        try {\n            var info = infoText ? JSON.parse(infoText) : null;\n            var chapter = info && info.chapters && info.chapters.length ? info.chapters[0] : null;\n            if (chapter && Number(chapter.price || 0) > 0 && Number(chapter.paid || 0) == 0) {\n                detail = \"当前章节是付费内容,服务器没有返回正文;请确认账号已购买或具有有效会员权限\";\n            }\n        } catch (ignoreInfoError) {}\n        throw new Error(detail + \"(包内文件:\" + entryNames.join(\", \") + \")\");\n    }\n    return parts.join(\"\\n\");\n}\nfunction wrDownloadChapter(j, src, apiUrl) {\n    apiUrl = String(apiUrl || \"\");\n    if (apiUrl.indexOf(\"https:\/\/i.weread.qq.com\/book\/chapterdownload?\") != 0) {\n        throw new Error(\"章节下载地址无效\");\n    }\n    var response = j.get(apiUrl, wrHeaderMap(src), 60000);\n    if (response.statusCode() != 200) throw new Error(\"章节下载失败,HTTP \" + response.statusCode());\n    var encryptedKey = response.header(\"encryptkey\");\n    if (!encryptedKey) throw new Error(\"章节响应缺少 encryptkey\");\n    var bookMatch = apiUrl.match(\/[?&]bookId=([^&]+)\/);\n    var chapterMatch = apiUrl.match(\/[?&]chapters=([^&]+)\/);\n    var bookId = bookMatch ? decodeURIComponent(bookMatch[1]) : \"\";\n    var chapterUid = chapterMatch ? decodeURIComponent(chapterMatch[1]) : \"\";\n    var modes = wrCommentModes(src);\n    var paragraphMarkers = modes.paragraph && bookId && chapterUid\n        ? wrParagraphSummaries(j, src, bookId, chapterUid) : [];\n    var password = wrDecryptZipPassword(j, src, encryptedKey);\n    var html = wrExtractChapterHtml(response.bodyAsBytes(), password, j, src, bookId, chapterUid, paragraphMarkers);\n    if (bookId && chapterUid && (modes.chapter || modes.book)) {\n        html += wrReviewEntrySvg(j, src, bookId, chapterUid, modes);\n    }\n    return html;\n}\nfunction wrCaptureHelp(j) {\n    var message = \"抓包要求:微信读书 Android 10.2.1(10167607)。在 Reqable 中找到状态为 200 的 https:\/\/i.weread.qq.com\/store\/search 或 \/book\/info 请求,从同一条请求头复制 vid、accessToken、完整 User-Agent。网页 Cookie 不能代替 APP 参数。\";\n    j.longToast(message);\n    return message;\n}\n\nfunction wrSetExploreMode(j, src, mode) {\n    src.setVariable(mode == \"audio\" ? \"audio\" : \"novel\");\n    j.refreshExplore();\n    var message = mode == \"audio\" ? \"已切换为听书发现\" : \"已切换为小说发现\";\n    j.longToast(message);\n    return message;\n}\n\nfunction wrExploreSection(items, title) {\n    items.push({\n        title: title,\n        url: null,\n        style: {layout_flexGrow: 1, layout_flexBasisPercent: 1, layout_justifySelf: \"flex_start\"}\n    });\n}\n\nfunction wrExploreItem(items, title, url, basis) {\n    items.push({\n        title: title,\n        url: url,\n        style: {layout_flexGrow: 1, layout_flexBasisPercent: basis || 0.31}\n    });\n}\n\nfunction wrMarketUrl(categoryId, filterCategoryId) {\n    var url = \"https:\/\/i.weread.qq.com\/market\/category?count={{page==1?20:15}}&rank=1&filterIdx=1\" +\n        \"&categoryId=\" + categoryId + \"&maxIdx={{page==1?0:20+(page-2)*15}}\";\n    if (filterCategoryId) url += \"&filterCategoryId=\" + filterCategoryId;\n    return url;\n}\n\nfunction wrCategoryDataUrl(categoryId) {\n    return \"https:\/\/i.weread.qq.com\/category\/data?wordcount_begin=0&synckey=0\" +\n        \"&maxIdx={{page==1?0:10+(page-2)*15}}&count={{page==1?10:15}}\" +\n        \"&finish=0&sort=1&wordcount_end=0&paytype=0&categoryId=\" + encodeURIComponent(categoryId);\n}\n\nfunction wrNovelTagUrl(tag) {\n    return \"https:\/\/i.weread.qq.com\/market\/category?count={{page==1?20:15}}&rank=1&filterIdx=1\" +\n        \"&filterTag=\" + encodeURIComponent(tag) + \"&categoryId=novel_top_rate\" +\n        \"&maxIdx={{page==1?0:20+(page-2)*15}}\";\n}\nfunction wrAudioSearchUrl(keyword) {\n    return \"https:\/\/i.weread.qq.com\/store\/search?rnVersion=6&v=3&scope=14&type=0&count=15\" +\n        \"&keyword=\" + encodeURIComponent(keyword) + \"&maxIdx={{(page-1)*15}}\";\n}\n\nfunction wrAudioSessionId() {\n    var alphabet = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_\";\n    var value = \"session_\";\n    for (var i = 0; i < 16; i++) value += alphabet.charAt(Math.floor(Math.random() * alphabet.length));\n    return value;\n}\nfunction wrWaterfallExploreUrl(sessionId) {\n    return \"https:\/\/i.weread.qq.com\/waterfall\/explore?v=3&clickIds=&count=20&tabs=8&sessionId=\" +\n        encodeURIComponent(sessionId) + \"&membercard=2&utm_source=waterfallExplore\";\n}\nfunction wrToplistUrl(id) {\n    return \"https:\/\/i.weread.qq.com\/toplist\/recommend?count=20&id=\" + encodeURIComponent(id) +\n        \"&synckey=0&maxIdx={{(page-1)*20}}\";\n}\nfunction wrAlbumRecommendUrl(sessionId) {\n    return \"https:\/\/i.weread.qq.com\/album\/recommendlist?count=18&sessionId=\" + encodeURIComponent(sessionId);\n}\nfunction wrExploreUrl(src) {\n    var items = [];\n\n\n\n\n\n\n\n\n\n\n\n\n\n    if (String(src.getVariable() || \"novel\") == \"audio\") {\n        var audioSessionId = wrAudioSessionId();\n        wrExploreSection(items, \"听书首页\");\n        wrExploreItem(items, \"为你推荐\", wrWaterfallExploreUrl(audioSessionId), 0.47);\n        wrExploreItem(items, \"猜你喜欢\", wrAlbumRecommendUrl(audioSessionId), 0.47);\n        wrExploreItem(items, \"热听榜\", wrMarketUrl(\"album_all_rising\", \"\"), 0.47);\n\n        wrExploreSection(items, \"最近更新\");\n        wrExploreItem(items, \"最近上新\", wrToplistUrl(\"HOT_REDISTEXT_cG9vbF83MjVfMV~mnIDov5HkuIrmlrA\"), 0.47);\n        wrExploreItem(items, \"最近热听\", wrToplistUrl(\"HOT_REDISTEXT_cG9vbF80NjdfMV~mnIDov5Hng63lkKw\"), 0.47);\n\n        wrExploreSection(items, \"有声小说\");\n        wrExploreItem(items, \"精品小说\", wrToplistUrl(\"HOT_TAG_cG9vbF8wX-eyvuWTgeWwj-ivtA\"));\n        wrExploreItem(items, \"男生小说\", wrToplistUrl(\"HOT_TAG_cG9vbF8wX-eUt-eUn-Wwj-ivtA\"));\n        wrExploreItem(items, \"女生小说\", wrToplistUrl(\"HOT_TAG_cG9vbF8wX-Wls-eUn-Wwj-ivtA\"));\n\n        wrExploreSection(items, \"知识与人文\");\n        wrExploreItem(items, \"历史\", wrToplistUrl(\"HOT_TAG_cG9vbF8wX-WOhuWPsg\"));\n        wrExploreItem(items, \"心理学\", wrToplistUrl(\"HOT_TAG_cG9vbF8wX-W~g-eQhuWtpg\"));\n        wrExploreItem(items, \"个人成长\", wrToplistUrl(\"HOT_TAG_cG9vbF8wX-S4quS6uuaIkOmVvw\"));\n        wrExploreItem(items, \"商业经济\", wrToplistUrl(\"HOT_TAG_cG9vbF8wX-WVhuS4mue7j-a1jg\"));\n        wrExploreItem(items, \"文学\", wrToplistUrl(\"HOT_TAG_cG9vbF8wX-aWh-Wtpg\"));\n        wrExploreItem(items, \"哲学\", wrToplistUrl(\"HOT_TAG_cG9vbF8wX-WTsuWtpg\"));\n\n        wrExploreSection(items, \"生活与文化\");\n        wrExploreItem(items, \"艺术设计\", wrToplistUrl(\"HOT_TAG_cG9vbF8wX-iJuuacr-iuvuiuoQ\"));\n        wrExploreItem(items, \"生活\", wrToplistUrl(\"HOT_TAG_cG9vbF8wX-eUn-a0uw\"));\n        wrExploreItem(items, \"文化\", wrToplistUrl(\"HOT_TAG_cG9vbF8wX-aWh-WMlg\"));\n        wrExploreItem(items, \"政治军事\", wrToplistUrl(\"HOT_TAG_cG9vbF8wX-aUv-ayu-WGm-S6iw\"));\n        wrExploreItem(items, \"曲艺杂谈\", wrToplistUrl(\"HOT_TAG_cG9vbF8wX-absuiJuuadguiwiA\"));\n        wrExploreItem(items, \"儿童教育\", wrToplistUrl(\"HOT_TAG_cG9vbF8wX-WEv-erpeaVmeiCsg\"));\n        return JSON.stringify(items);\n    }\n    wrExploreSection(items, \"热门榜单\");\n    wrExploreItem(items, \"飙升榜\", wrMarketUrl(\"rising\", \"\"));\n    wrExploreItem(items, \"热搜榜\", wrMarketUrl(\"hot_search\", \"\"));\n    wrExploreItem(items, \"新书榜\", wrMarketUrl(\"newbook\", \"\"));\n    wrExploreItem(items, \"神作榜\", wrMarketUrl(\"newrating_publish\", \"\"));\n    wrExploreItem(items, \"潜力神作\", wrMarketUrl(\"newrating_potential_publish\", \"\"));\n    wrExploreItem(items, \"出版总榜\", wrMarketUrl(\"all\", \"\"));\n\n    wrExploreSection(items, \"网文榜单\");\n    wrExploreItem(items, \"网文飙升\", wrMarketUrl(\"general_novel_rising\", \"\"));\n    wrExploreItem(items, \"男频飙升\", wrMarketUrl(\"novel_male_rising\", \"\"));\n    wrExploreItem(items, \"男频新书\", wrMarketUrl(\"newbook_novel_male\", \"\"));\n    wrExploreItem(items, \"男频全部\", wrMarketUrl(\"novel_male\", \"\"));\n    wrExploreItem(items, \"女频飙升\", wrMarketUrl(\"novel_female_rising\", \"\"));\n    wrExploreItem(items, \"女频新书\", wrMarketUrl(\"newbook_novel_female\", \"\"));\n    wrExploreItem(items, \"女频全部\", wrMarketUrl(\"novel_female\", \"\"));\n    wrExploreItem(items, \"影视原著\", wrMarketUrl(\"filmorigin\", \"\"));\n\n    wrExploreSection(items, \"网文题材\");\n    var novelTags = [\n        [\"异界超能\", \"西方玄幻\"], [\"男频东玄\", \"东方玄幻\"], [\"修仙修真\", \"修仙修真\"],\n        [\"历史架空\", \"历史架空\"], [\"都市小说\", \"都市小说\"], [\"民俗志怪\", \"民俗志怪\"],\n        [\"游戏竞技\", \"游戏竞技\"], [\"同人衍生\", \"同人二次元\"], [\"年代幻想\", \"年代幻想\"],\n        [\"历史小说\", \"历史小说\"], [\"悬疑推理\", \"悬疑推理\"], [\"科幻小说\", \"科幻小说\"],\n        [\"职场纪实\", \"职场小说\"], [\"仕途小说\", \"官场小说\"], [\"武侠小说\", \"武侠小说\"],\n        [\"社会小说\", \"社会小说\"], [\"战争军旅\", \"战争军旅\"], [\"古代言情\", \"古代言情\"],\n        [\"现代言情\", \"都市言情\"], [\"女频玄幻\", \"玄幻仙侠\"], [\"女性小说\", \"女性小说\"]\n    ];\n    for (var tagIndex = 0; tagIndex < novelTags.length; tagIndex++) {\n        wrExploreItem(items, novelTags[tagIndex][0], wrNovelTagUrl(novelTags[tagIndex][1]));\n    }\n\n    wrExploreSection(items, \"出版小说\");\n    var publishFiction = [[\"精品小说\",\"100000\"],[\"影视原著\",\"100012\"],[\"社会小说\",\"100001\"],\n        [\"悬疑推理\",\"100008\"],[\"玄幻小说\",\"100006\"],[\"情感小说\",\"100002\"],[\"科幻小说\",\"100004\"]];\n    for (var pf = 0; pf < publishFiction.length; pf++) wrExploreItem(items, publishFiction[pf][0], wrCategoryDataUrl(publishFiction[pf][1]));\n\n    wrExploreSection(items, \"人文社科\");\n    var humanities = [[\"文学\",\"300000\"],[\"历史\",\"200000\"],[\"人物传记\",\"500000\"],[\"社会文化\",\"900000\"],\n        [\"哲学宗教\",\"600000\"],[\"政治军事\",\"1200000\"],[\"原版书\",\"1800000\"],[\"期刊杂志\",\"1700000\"]];\n    for (var hm = 0; hm < humanities.length; hm++) wrExploreItem(items, humanities[hm][0], wrCategoryDataUrl(humanities[hm][1]));\n\n    wrExploreSection(items, \"成长与生活\");\n    var life = [[\"个人成长\",\"1000000\"],[\"心理\",\"800000\"],[\"经济理财\",\"1100000\"],\n        [\"医学健康\",\"2100000\"],[\"生活百科\",\"1600000\"],[\"童书\",\"1300000\"]];\n    for (var lf = 0; lf < life.length; lf++) wrExploreItem(items, life[lf][0], wrCategoryDataUrl(life[lf][1]));\n\n    wrExploreSection(items, \"科技教育与其他\");\n    var other = [[\"计算机\",\"700000\"],[\"科学技术\",\"1500000\"],[\"教育学习\",\"1400000\"],\n        [\"艺术\",\"400000\"],[\"漫画\",\"2400000\"],[\"会员专享\",\"9000100\"]];\n    for (var ot = 0; ot < other.length; ot++) wrExploreItem(items, other[ot][0], wrCategoryDataUrl(other[ot][1]));\n    return JSON.stringify(items);\n}\nfunction wrSwitchEnabled(src, name) {\n    var value = wrValue(src, name).toLowerCase();\n    return value == \"开启\" || value == \"✅\" || value == \"1\" || value == \"true\" || value == \"开\";\n}\n\nfunction wrCommentModes(src) {\n    var legacy = wrSwitchEnabled(src, \"小说章节书评入口\");\n    var modes = {\n        paragraph: wrSwitchEnabled(src, \"段评开关\"),\n        chapter: wrSwitchEnabled(src, \"章评开关\"),\n        book: wrSwitchEnabled(src, \"书评开关\") || legacy,\n        author: wrSwitchEnabled(src, \"作者评论开关\")\n    };\n    modes.any = modes.paragraph || modes.chapter || modes.book || modes.author;\n    return modes;\n}\n\nfunction wrReviewsEnabled(src) {\n    return wrCommentModes(src).any;\n}\nfunction wrCommentSvgImage(j, src, displayText, click, title, large) {\n    var color = String(wrValue(src, \"◎ 段评颜色\") || \"#23745B\");\n    if (!\/^#[0-9a-f]{6}$\/i.test(color)) color = \"#23745B\";\n    displayText = String(displayText || \"\");\n    var svg;\n    var imageStyle;\n    if (large) {\n        imageStyle = \"FULL\";\n        svg = '<svg viewBox=\"0 0 320 78\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"640\" height=\"156\">' +\n            '<rect x=\"3\" y=\"3\" width=\"314\" height=\"72\" rx=\"10\" fill=\"none\" stroke=\"' + color + '\" stroke-width=\"3\"\/>' +\n            '<path d=\"M25 25h26v22H35l-10 9v-9z\" fill=\"none\" stroke=\"' + color + '\" stroke-width=\"2.5\" stroke-linejoin=\"round\"\/>' +\n            '<text x=\"184\" y=\"49\" text-anchor=\"middle\" font-family=\"Arial,Microsoft YaHei\" font-weight=\"700\" font-size=\"28\" fill=\"' + color + '\">' + displayText + '<\/text><\/svg>';\n    } else {\n        var template = String(wrValue(src, \"◎ 模板\") || \"起点\");\n        var customName = template.replace(\"样式\", \"◎ 气泡\");\n        var custom = template == \"起点\" ? \"\" : String(wrValue(src, customName) || \"\").trim();\n        imageStyle = \"TEXT\";\n        svg = custom ? custom\n            .replace(\/\\$\\{qpyscolor\\}\/g, color)\n            .replace(\/\\$\\{qpwzcolor\\}\/g, color)\n            .replace(\/\\$\\{font\\}\/g, \"Arial\")\n            .replace(\/\\$\\{displayText\\}\/g, displayText)\n            : '<svg viewBox=\"5 14 45 36\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"180\" height=\"144\">' +\n              '<path d=\"M44 48 Q48 48 48 44 L48 20 Q48 16 44 16 L20 16 Q16 16 16 20 L16 24 S16 28 10 30 Q6 32 10 34 Q16 36 16 38 L16 44 Q16 48 20 48 Z\" fill=\"none\" stroke=\"' + color + '\" stroke-width=\"0.7\"\/>' +\n              '<text x=\"32\" y=\"38\" text-anchor=\"middle\" font-family=\"Arial\" font-weight=\"600\" font-size=\"18\" fill=\"' + color + '\" dominant-baseline=\"middle\">' + displayText + '<\/text><\/svg>';\n    }\n    var encoded = String(j.base64Encode(svg)).replace(\/\\s\/g, \"\");\n    var option = JSON.stringify({click:String(click), style:imageStyle, type:\"wr\", title:String(title || \"微信读书评论\")});\n    return \"<img src='data:image\/svg+xml;base64,\" + encoded + \",\" + option + \"'>\";\n}\n\nfunction wrFilterUnsupportedFootnotes(text) {\n    \/\/ WeRead EPUB footnotes use a relative note.png with the note hidden in alt.\n    \/\/ Filter only this unsupported image after paragraph placement has completed.\n    return String(text || \"\").replace(\n        \/<img\\b[^>]*\\bclass\\s*=\\s*[\"'][^\"']*\\bqqreader-footnote\\b[^\"']*[\"'][^>]*\\\/?\\s*>\/gi,\n        \"\"\n    );\n}\n\nfunction wrParagraphSummaries(j, src, bookId, chapterUid) {\n    var markers = [];\n    try {\n        var underlineUrl = \"https:\/\/i.weread.qq.com\/book\/underlines?bookId=\" + encodeURIComponent(bookId) +\n            \"&chapterUid=\" + encodeURIComponent(chapterUid) + \"&synckey=0\";\n        var response = j.get(underlineUrl, wrHeaderMap(src), 30000);\n        if (Number(response.statusCode()) != 200) throw new Error(\"段落范围 HTTP \" + response.statusCode());\n        var data = JSON.parse(String(response.body()));\n        var underlines = data.underlines || [];\n        var seen = {};\n        var requests = [];\n        for (var i = 0; i < underlines.length; i++) {\n            var range = String((underlines[i] || {}).range || \"\");\n            if (!\/^\\d+-\\d+$\/.test(range) || seen[range]) continue;\n            seen[range] = true;\n            requests.push({count:5, range:range, synckey:0});\n        }\n        requests.sort(function(a, b) { return Number(a.range.split(\"-\")[0]) - Number(b.range.split(\"-\")[0]); });\n        for (var offset = 0; offset < requests.length; offset += 10) {\n            var headers = wrHeaderMap(src);\n            headers.put(\"Content-Type\", \"application\/json; charset=UTF-8\");\n            var body = JSON.stringify({\n                bookId:String(bookId),\n                chapterUid:isFinite(Number(chapterUid)) ? Number(chapterUid) : String(chapterUid),\n                cht2sMode:\"\",\n                reviews:requests.slice(offset, offset + 10)\n            });\n            var reviewResponse = j.post(\"https:\/\/i.weread.qq.com\/book\/readreviews\", body, headers, 30000);\n            if (Number(reviewResponse.statusCode()) != 200) throw new Error(\"段评摘要 HTTP \" + reviewResponse.statusCode());\n            var groups = JSON.parse(String(reviewResponse.body())).reviews || [];\n            for (var groupIndex = 0; groupIndex < groups.length; groupIndex++) {\n                var group = groups[groupIndex] || {};\n                var total = Number(group.totalCount || 0);\n                if (total <= 0 || !\/^\\d+-\\d+$\/.test(String(group.range || \"\"))) continue;\n                var reviews = group.pageReviews || [];\n                var hot = false;\n                for (var reviewIndex = 0; reviewIndex < reviews.length; reviewIndex++) {\n                    if (Number((reviews[reviewIndex] || {}).likesCount || 0) > 0) { hot = true; break; }\n                }\n                markers.push({range:String(group.range), total:total, hot:hot, inserted:false});\n            }\n        }\n    } catch (error) {\n        try { j.log(\"微信读书正文段评摘要加载失败:\" + error); } catch (ignoreLog) {}\n    }\n    return markers;\n}\n\nfunction wrParagraphBubbleIndex(text, end) {\n    if (!isFinite(end) || end <= 0 || end > text.length) return -1;\n    var before = text.substring(0, end);\n    var lastOpen = before.lastIndexOf(\"<\");\n    var lastClose = before.lastIndexOf(\">\");\n    if (lastOpen > lastClose) return -1;\n    var paragraphStart = before.toLowerCase().lastIndexOf(\"<p\");\n    if (paragraphStart >= 0) {\n        var openingEnd = text.indexOf(\">\", paragraphStart);\n        var paragraphEnd = text.toLowerCase().indexOf(\"<\/p>\", end);\n        var nextParagraph = text.toLowerCase().indexOf(\"<p\", end);\n        if (openingEnd >= 0 && openingEnd < end && paragraphEnd >= end &&\n            (nextParagraph < 0 || paragraphEnd < nextParagraph)) return paragraphEnd;\n    }\n    if (\/<\\\/?(?:html|body|p|div|span|h[1-6])\\b\/i.test(text)) return -1;\n    var lineEnd = text.indexOf(\"\\n\", end);\n    if (lineEnd < 0) return text.length;\n    return lineEnd > 0 && text.charAt(lineEnd - 1) == \"\\r\" ? lineEnd - 1 : lineEnd;\n}\n\nfunction wrInsertParagraphBubbles(j, src, text, bookId, chapterUid, markers, replacements) {\n    text = String(text || \"\");\n    if (!markers || !markers.length) return text;\n    var grouped = {};\n    for (var i = 0; i < markers.length; i++) {\n        var marker = markers[i] || {};\n        if (marker.inserted) continue;\n        var values = String(marker.range || \"\").split(\"-\");\n        var end = Number(values[1]);\n        var index = wrParagraphBubbleIndex(text, end);\n        if (index < 0) continue;\n        var key = String(index);\n        if (!grouped[key]) grouped[key] = {index:index, markers:[], ranges:[], total:0};\n        grouped[key].markers.push(marker);\n        grouped[key].ranges.push(String(marker.range));\n        grouped[key].total += Number(marker.total || 0);\n    }\n    var insertions = [];\n    var keys = Object.keys(grouped);\n    for (var groupIndex = 0; groupIndex < keys.length; groupIndex++) {\n        var group = grouped[keys[groupIndex]];\n        group.ranges.sort(function(a, b) { return Number(a.split(\"-\")[0]) - Number(b.split(\"-\")[0]); });\n        var display = group.ranges.length > 1 ? \"评\" : (group.total > 999 ? \"999+\" : String(group.total));\n        var rangeValue = group.ranges.join(\"|\");\n        var click = \"wrOpenComments(\" + JSON.stringify(String(bookId)) + \",\" +\n            JSON.stringify(String(chapterUid)) + \",\\\"paragraph\\\",\" + JSON.stringify(rangeValue) + \")\";\n        var image = wrCommentSvgImage(j, src, display, click, \"段评 \" + group.ranges.join(\", \"), false);\n        var html = image;\n        if (replacements) {\n            var token = \"WR_PARAGRAPH_BUBBLE_\" + replacements.length + \"_TOKEN\";\n            replacements.push({token:token, html:image, range:rangeValue, ranges:group.ranges.slice()});\n            html = token;\n        }\n        insertions.push({index:group.index, html:html, markers:group.markers});\n    }\n    insertions.sort(function(a, b) { return b.index - a.index; });\n    for (var insertionIndex = 0; insertionIndex < insertions.length; insertionIndex++) {\n        var insertion = insertions[insertionIndex];\n        text = text.substring(0, insertion.index) + insertion.html + text.substring(insertion.index);\n        for (var markerIndex = 0; markerIndex < insertion.markers.length; markerIndex++) {\n            insertion.markers[markerIndex].inserted = true;\n        }\n    }\n    return text;\n}\nfunction wrReviewEntrySvg(j, src, bookId, chapterUid, modes) {\n    var entries = [\n        [\"chapter\", \"章评\", \"章评\"],\n        [\"book\", \"书评\", \"书评\"]\n    ];\n    var images = [];\n    for (var i = 0; i < entries.length; i++) {\n        var entry = entries[i];\n        if (!modes[entry[0]]) continue;\n        var click = \"wrOpenComments(\" + JSON.stringify(String(bookId)) + \",\" +\n            JSON.stringify(String(chapterUid)) + \",\" + JSON.stringify(entry[0]) + \",\\\"\\\")\";\n        images.push(wrCommentSvgImage(j, src, entry[1], click, entry[2], true));\n    }\n    return images.length ? \"<br><br>\" + images.join(\"<br>\") : \"\";\n}\nfunction wrEscapeHtml(value) {\n    return String(value == null ? \"\" : value)\n        .replace(\/&\/g, \"&amp;\")\n        .replace(\/<\/g, \"&lt;\")\n        .replace(\/>\/g, \"&gt;\")\n        .replace(\/\\\"\/g, \"&quot;\")\n        .replace(\/'\/g, \"&#39;\");\n}\n\nfunction wrReviewRecords(payload) {\n    var reviews = payload && payload.reviews ? payload.reviews : [];\n    var records = [];\n    var seen = {};\n    for (var i = 0; i < reviews.length; i++) {\n        var item = reviews[i] || {};\n        var wrapper = item;\n        var review = item.review && typeof item.review == \"object\" ? item.review : item;\n        if (review.review && typeof review.review == \"object\") review = review.review;\n        var id = String(wrapper.reviewId || review.reviewId || i);\n        if (seen[id]) continue;\n        seen[id] = true;\n        records.push({wrapper: wrapper, review: review});\n    }\n    return records;\n}\n\nfunction wrParagraphRecords(payload) {\n    var groups = payload && payload.reviews ? payload.reviews : [];\n    var records = [];\n    var seen = {};\n    for (var i = 0; i < groups.length; i++) {\n        var group = groups[i] || {};\n        var pageReviews = group.pageReviews || [];\n        var range = String(group.range || \"\");\n        var rangeTotal = Number(group.totalCount || 0);\n        for (var j = 0; j < pageReviews.length; j++) {\n            var item = pageReviews[j] || {};\n            var review = item.review || item;\n            var id = String(item.reviewId || review.reviewId || (i + \"-\" + j));\n            if (seen[id]) continue;\n            seen[id] = true;\n            records.push({wrapper:item, review:review, range:range || String(review.range || \"\"),\n                rangeTotal:rangeTotal, rangeMaxIdx:Number(group.maxIdx || 0),\n                rangeHasMore:Boolean(group.hasMore), rangeSynckey:String(group.synckey || \"0\")});\n        }\n    }\n    return records;\n}\nfunction wrLoadReviewReplies(j, src, records, maxReviews) {\n    var loadedReviews = 0;\n    maxReviews = Number(maxReviews || 5);\n    for (var i = 0; i < records.length && loadedReviews < maxReviews; i++) {\n        var record = records[i] || {};\n        var wrapper = record.wrapper || {};\n        var review = record.review || {};\n        var reviewId = String(wrapper.reviewId || review.reviewId || \"\");\n        var total = Number(wrapper.commentsCount || review.commentsCount || 0);\n        var current = wrapper.hotComments || review.hotComments || [];\n        if (!reviewId || total <= current.length) continue;\n        var replies = [];\n        var seen = {};\n        for (var oldIndex = 0; oldIndex < current.length; oldIndex++) {\n            var oldReply = current[oldIndex] || {};\n            var oldId = String(oldReply.commentId || oldReply.reviewId || oldIndex);\n            if (!seen[oldId]) { seen[oldId] = true; replies.push(oldReply); }\n        }\n        var cursor = 0;\n        for (var page = 0; page < 3; page++) {\n            try {\n                var url = \"https:\/\/i.weread.qq.com\/review\/commentloadmore?reviewId=\" + encodeURIComponent(reviewId) +\n                    \"&maxIdx=\" + cursor + \"&count=100&isExpandAll=0\";\n                var response = j.get(url, wrHeaderMap(src), 30000);\n                if (response.statusCode() != 200) break;\n                var data = JSON.parse(String(response.body()));\n                var comments = data.comments || [];\n                var added = 0;\n                for (var replyIndex = 0; replyIndex < comments.length; replyIndex++) {\n                    var reply = comments[replyIndex] || {};\n                    var id = String(reply.commentId || reply.reviewId || (cursor + replyIndex));\n                    if (!seen[id]) { seen[id] = true; replies.push(reply); added++; }\n                }\n                if (!data.commentsHasMore || !comments.length || !added) break;\n                cursor += comments.length;\n            } catch (replyError) {\n                try { j.log(\"微信读书回复加载失败:\" + replyError); } catch (ignoreReplyLog) {}\n                break;\n            }\n        }\n        wrapper.hotComments = replies;\n        loadedReviews++;\n    }\n    return loadedReviews;\n}\nfunction wrCommentCards(records, emptyText, sectionIndex) {\n    var cards = [];\n    var lastRange = \"\";\n    for (var i = 0; i < records.length; i++) {\n        var record = records[i] || {};\n        var wrapper = record.wrapper || {};\n        var review = record.review || {};\n        var author = review.author || {};\n        var authorName = author.name || author.nick || \"微信读书用户\";\n        var avatarUrl = String(author.avatar || \"\");\n        var avatar = \/^https:\\\/\\\/\/i.test(avatarUrl)\n            ? \"<img class='avatar' loading='lazy' src='\" + wrEscapeHtml(avatarUrl) + \"' alt=''>\"\n            : \"<span class='avatar avatar-fallback'>\" + wrEscapeHtml(String(authorName).charAt(0) || \"读\") + \"<\/span>\";\n        var content = review.content || review.htmlContent || review.abstract || review.title || \"\";\n        var context = review.abstract || review.contextAbstract || \"\";\n        var rangeValue = String(record.range || review.range || \"\");\n        var rangeHeading = \"\";\n        if (rangeValue && rangeValue != lastRange) {\n            lastRange = rangeValue;\n            var rangeTotal = Number(record.rangeTotal || 0);\n            rangeHeading = \"<div class='range-heading'><div><strong>正文位置 \" +\n                wrEscapeHtml(rangeValue) + \"<\/strong>\" + (rangeTotal > 0 ? \"<span>本段共 \" + rangeTotal + \" 条段评<\/span>\" : \"\") +\n                \"<\/div>\" + (context ? \"<p>\" + wrEscapeHtml(context) + \"<\/p>\" : \"\") + \"<\/div>\";\n        }\n        var created = \"\";\n        if (Number(review.createTime) > 0) {\n            var date = new Date(Number(review.createTime) * 1000);\n            created = date.getFullYear() + \"-\" + (date.getMonth() + 1) + \"-\" + date.getDate();\n        }\n        var likes = Number(wrapper.likesCount || 0);\n        var comments = Number(wrapper.commentsCount || 0);\n        var meta = [];\n        if (rangeValue) meta.push(\"位置 \" + rangeValue);\n        if (review.chapterName || review.chapterTitle) meta.push(String(review.chapterName || review.chapterTitle));\n        var badges = [];\n        if (review.isAuthorComment) badges.push(\"作者回复\");\n        if (review.isAuthorLike) badges.push(\"作者赞过\");\n        if (author.isDeepV) badges.push(author.deepVTitle || \"资深读者\");\n        var badgeHtml = [];\n        for (var badgeIndex = 0; badgeIndex < badges.length; badgeIndex++) {\n            badgeHtml.push(\"<span class='badge'>\" + wrEscapeHtml(badges[badgeIndex]) + \"<\/span>\");\n        }\n        var scoreHtml = \"\";\n        if (Number(review.star || 0) > 0) {\n            var score = Math.round(Number(review.star) \/ 20 * 10) \/ 10;\n            scoreHtml = \"<span class='score'>\" + score + \" 分<\/span>\";\n        }\n        var extra = \"\";\n        if (!rangeHeading && context && context != content) extra = \"<blockquote>\" + wrEscapeHtml(context) + \"<\/blockquote>\";\n        var pictures = review.pictures || wrapper.pictures || [];\n        var pictureHtml = [];\n        for (var pictureIndex = 0; pictureIndex < pictures.length; pictureIndex++) {\n            var picture = pictures[pictureIndex] || {};\n            var pictureUrl = String(picture.url || \"\");\n            if (\/^https:\\\/\\\/\/i.test(pictureUrl)) {\n                pictureHtml.push(\"<img class='review-picture' loading='lazy' src='\" +\n                    wrEscapeHtml(pictureUrl) + \"' alt='评论图片'>\");\n            }\n        }\n        var replies = wrapper.hotComments || review.hotComments || [];\n        var replyHtml = [];\n        for (var replyIndex = 0; replyIndex < replies.length; replyIndex++) {\n            var reply = replies[replyIndex] || {};\n            var replyAuthor = reply.author || {};\n            replyHtml.push(\"<div class='reply'><b>\" + wrEscapeHtml(replyAuthor.name || replyAuthor.nick || \"回复\") + \"<\/b>:\" +\n                wrEscapeHtml(reply.content || \"\") + \"<\/div>\");\n        }\n        cards.push(\"<article class='comment-item\" + (i >= 20 ? \" batch-hidden\" : \"\") + \"'>\" + rangeHeading + \"<header>\" + avatar + \"<div class='identity'><div><strong>\" + wrEscapeHtml(authorName) +\n            \"<\/strong>\" + badgeHtml.join(\"\") + \"<\/div><span>\" + wrEscapeHtml(created) + \"<\/span><\/div>\" +\n            scoreHtml + \"<\/header>\" + extra + \"<div class='content'>\" +\n            wrEscapeHtml(content).replace(\/\\r?\\n\/g, \"<br>\") + \"<\/div>\" + pictureHtml.join(\"\") + replyHtml.join(\"\") +\n            \"<footer>\" + wrEscapeHtml(meta.join(\" · \")) + (meta.length ? \" \" : \"\") +\n            \"赞 \" + likes + \" 回复 \" + comments + \"<\/footer><\/article>\");\n    }\n    if (cards.length == 0) cards.push(\"<div class='empty'>\" + wrEscapeHtml(emptyText || \"暂时没有可显示的评论\") + \"<\/div>\");\n    return cards.join(\"\");\n}\nfunction wrReviewPage(bookName, bookId, chapterUid, enabledModes, initialMode, requestedRange, chapterName, options) {\n    options = options || {};\n    var accent = \/^#[0-9a-f]{6}$\/i.test(String(options.accent || \"\")) ? String(options.accent) : \"#23745B\";\n    var pageMode = String(options.mode || \"美化\");\n    var bodyClass = pageMode == \"深色\" ? \"force-dark\" : (pageMode == \"默认\" ? \"force-light\" : \"auto-theme\");\n    var payload = JSON.stringify({\n        bookName:String(bookName || \"微信读书评论\"),\n        bookId:String(bookId || \"\"),\n        chapterUid:String(chapterUid || \"\"),\n        chapterName:String(chapterName || \"\"),\n        modes:enabledModes || [],\n        initialMode:String(initialMode || \"\"),\n        range:String(requestedRange || \"\")\n    }).replace(\/<\/g, \"\\\\u003c\");\n    var html = [];\n    html.push(\"<!doctype html><html><head><meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1'>\");\n    html.push(\"<title>微信读书评论<\/title><style>\");\n    html.push(\":root{--bg:#f4f6f5;--surface:#fff;--text:#19221f;--muted:#6d7874;--line:#dbe2df;--soft:#eef3f1;--brand:\" + accent + \";--brandText:#fff;--warnBg:#fff8df;--warnText:#67561b;--warnLine:#ead99e}\");\n    html.push(\"*{box-sizing:border-box}html,body{background:var(--bg)}body{margin:0;color:var(--text);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','Microsoft YaHei',sans-serif;letter-spacing:0}\");\n    html.push(\"button{font:inherit;letter-spacing:0}.top{position:sticky;top:0;background:rgba(255,255,255,.97);border-bottom:1px solid var(--line);padding:11px 12px 9px;z-index:5;backdrop-filter:blur(12px)}\");\n    html.push(\".heading{display:flex;align-items:center;justify-content:space-between;gap:12px;margin:0 3px 8px}.title-wrap{min-width:0}.eyebrow{color:var(--brand);font-size:12px;font-weight:700}.top h1{font-size:17px;line-height:1.35;margin:1px 0 0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.chapter{color:var(--muted);font-size:12px;max-width:42%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\");\n    html.push(\".mode-tabs,.filter-row{display:flex;gap:7px;overflow-x:auto;scrollbar-width:none}.mode-tabs::-webkit-scrollbar,.filter-row::-webkit-scrollbar{display:none}.mode-tab,.filter{flex:0 0 auto;min-height:34px;border:1px solid #cbd5d1;background:var(--surface);color:#3d4c47;border-radius:6px;padding:6px 11px;font-size:13px;font-weight:600;white-space:nowrap}.mode-tab.active,.filter.active{background:var(--brand);color:var(--brandText);border-color:var(--brand)}\");\n    html.push(\".filter-area{max-width:760px;margin:auto;padding:11px 12px 0}.filter-label{font-size:11px;color:var(--muted);margin:0 2px 6px}.filter-row+ .filter-label{margin-top:10px}.filter small{font-size:10px;opacity:.78;margin-left:3px}\");\n    html.push(\"main{padding:11px 12px 28px;max-width:760px;margin:auto}.status{text-align:center;color:var(--muted);padding:54px 16px}.notice{background:var(--warnBg);border:1px solid var(--warnLine);color:var(--warnText);border-radius:6px;padding:10px 12px;margin:0 0 10px;font-size:13px;line-height:1.55}\");\n    html.push(\"article{background:var(--surface);border:1px solid var(--line);border-radius:6px;padding:15px;margin:0 0 10px}article header{display:flex;align-items:center;gap:11px;min-height:42px}.avatar{width:40px;height:40px;flex:0 0 40px;border-radius:50%;object-fit:cover;background:var(--soft)}.avatar-fallback{display:grid;place-items:center;background:#dceae5;color:var(--brand);font-weight:700}.identity{min-width:0;flex:1}.identity>div{display:flex;align-items:center;gap:6px;flex-wrap:wrap}.identity strong{font-size:14px;line-height:1.35;overflow-wrap:anywhere}.identity>span,footer{color:var(--muted);font-size:12px}\");\n    html.push(\".badge{display:inline-block;color:var(--brand);background:#e6f2ed;border-radius:4px;padding:2px 5px;font-size:10px;font-weight:600}.score{flex:0 0 auto;color:#946c00;background:#fff4c7;border-radius:4px;padding:4px 6px;font-size:11px}.context{margin:11px 0 0;padding:8px 10px;border-left:3px solid #8eb4a7;background:var(--soft);color:#56635f;font-size:13px;line-height:1.6}.content{font-size:16px;line-height:1.75;margin:12px 0;overflow-wrap:anywhere}.review-picture{display:block;width:100%;height:auto;max-height:70vh;object-fit:contain;background:var(--soft);border-radius:4px;margin:8px 0}footer{border-top:1px solid var(--line);padding-top:9px;margin-top:10px;display:flex;justify-content:space-between;gap:8px;align-items:center;flex-wrap:wrap}\");\n    html.push(\".reply-toggle,.reply-more{display:inline-flex;align-items:center;gap:6px;border:1px solid var(--line);background:var(--surface);color:var(--brand);border-radius:5px;min-height:34px;padding:6px 10px;font-size:12px;font-weight:700}.reply-toggle:disabled{opacity:.62}.reply-count{display:inline-grid;place-items:center;min-width:20px;height:20px;padding:0 5px;border-radius:10px;background:var(--soft);font-size:11px}.reply-thread{display:none;width:100%;margin:13px 0 0;padding:0 0 0 13px;border-left:2px solid #a8c5bb;clear:both}.reply-thread.open{display:block}.reply-heading{color:var(--muted);font-size:11px;font-weight:700;padding:1px 0 4px}.reply-item{display:flex;align-items:flex-start;gap:9px;width:100%;padding:11px 0;border-bottom:1px solid var(--line)}.reply-item:last-child{border-bottom:0}.reply-avatar{width:30px;height:30px;flex:0 0 30px;border-radius:50%;object-fit:cover;background:var(--soft)}.reply-main{min-width:0;flex:1}.reply-head{display:flex;align-items:baseline;gap:5px;flex-wrap:wrap}.reply-name{font-size:13px;font-weight:700;color:var(--text)}.reply-to{font-size:12px;color:var(--brand)}.reply-content{font-size:14px;line-height:1.62;margin-top:4px;overflow-wrap:anywhere}.reply-meta{font-size:11px;color:var(--muted);margin-top:5px}.end{text-align:center;color:var(--muted);font-size:12px;padding:14px}.empty{text-align:center;color:var(--muted);padding:70px 20px}@media(max-width:420px){article{padding:13px}.reply-thread{padding-left:10px}}\");\n    html.push(\"body.force-dark{--bg:#121614;--surface:#1b211f;--text:#e5ebe8;--muted:#9aa6a1;--line:#303936;--soft:#252d2a;--brandText:#102019;--warnBg:#2c2819;--warnText:#e3d293;--warnLine:#544b29}body.force-dark .top{background:rgba(27,33,31,.97)}body.force-dark .mode-tab,body.force-dark .filter{color:var(--text);border-color:var(--line)}\");\n    html.push(\"@media(prefers-color-scheme:dark){body.auto-theme{--bg:#121614;--surface:#1b211f;--text:#e5ebe8;--muted:#9aa6a1;--line:#303936;--soft:#252d2a;--brandText:#102019;--warnBg:#2c2819;--warnText:#e3d293;--warnLine:#544b29}body.auto-theme .top{background:rgba(27,33,31,.97)}body.auto-theme .mode-tab,body.auto-theme .filter{color:var(--text);border-color:var(--line)}}\");\n    html.push(\"<\/style><\/head><body class='\" + bodyClass + \"'><div class='top'><div class='heading'><div class='title-wrap'><div class='eyebrow'>微信读书评论<\/div><h1 id='book-title'><\/h1><\/div><div class='chapter' id='chapter-title'><\/div><\/div><div class='mode-tabs' id='mode-tabs'><\/div><\/div><div class='filter-area' id='filter-area'><\/div><main><div id='notice'><\/div><div id='list'><div class='status'>正在加载评论...<\/div><\/div><div id='end'><\/div><\/main>\");\n    html.push(\"<script>window.WR_PAGE=\" + payload + \";<\/script><script>(function(){\");\n    html.push(\"var cfg=window.WR_PAGE;var labels={paragraph:'段评',chapter:'章评',book:'书评'};var paragraphRanges=String(cfg.range||'').split('|').filter(function(v){return \/^\\\\d+-\\\\d+$\/.test(v);});var state={mode:cfg.initialMode||cfg.modes[0],local:'all',category:0,tag:'',cursor:0,hasMore:false,loading:false,total:0,records:[],tags:[],reply:{},rangeState:{},error:''};\");\n    html.push(\"var list=document.getElementById('list'),end=document.getElementById('end'),notice=document.getElementById('notice'),filterArea=document.getElementById('filter-area');document.getElementById('book-title').textContent=cfg.bookName;document.getElementById('chapter-title').textContent=cfg.chapterName;\");\n    html.push(\"function esc(v){return String(v==null?'':v).replace(\/[&<>\\\"']\/g,function(c){return{'&':'&amp;','<':'&lt;','>':'&gt;','\\\"':'&quot;',\\\"'\\\":'&#39;'}[c];});}\");\n    html.push(\"function bridge(){try{if(typeof java!=='undefined'&&java&&typeof java.ajax==='function')return java;}catch(e){}try{if(window.java&&typeof window.java.ajax==='function')return window.java;}catch(e2){}throw new Error('当前阅读 WebView 不支持评论网络桥接');}\");\n    html.push(\"function parse(text,label){var data;try{data=JSON.parse(String(text));}catch(e){throw new Error(label+'返回了无效数据');}if(data&&data.errcode!=null&&Number(data.errcode)!==0)throw new Error(label+'失败:'+(data.errmsg||data.message||'错误码 '+data.errcode));return data;}\");\n    html.push(\"function getJson(url,label){return parse(bridge().ajax(url),label);}\");\n    html.push(\"function postJson(url,body,label){var request=url+','+JSON.stringify({method:'POST',body:JSON.stringify(body),headers:{'Content-Type':'application\/json; charset=UTF-8'}});return parse(bridge().ajax(request),label);}\");\n    html.push(\"function normalize(item){var outer=item||{},middle=outer.review&&typeof outer.review==='object'?outer.review:outer,review=middle.review&&typeof middle.review==='object'?middle.review:middle;return{id:String(middle.reviewId||outer.reviewId||review.reviewId||''),review:review,wrapper:middle,likes:Number(middle.likesCount||outer.likesCount||0),comments:Number(middle.commentsCount||outer.commentsCount||review.commentsCount||0)};}\");\n    html.push(\"function dedupe(items,append){var base=append?state.records.slice():[],seen={};base.forEach(function(x){seen[x.id]=1;});(items||[]).map(normalize).forEach(function(x){if(x.id&&!seen[x.id]){seen[x.id]=1;base.push(x);}});state.records=base;}\");\n    html.push(\"function dateText(value){var n=Number(value||0);if(!n)return '';var d=new Date(n*1000);return d.getFullYear()+'-'+(d.getMonth()+1)+'-'+d.getDate();}\");\n    html.push(\"function picturesOf(record){var r=record.review||{},w=record.wrapper||{};return r.pictures||w.pictures||[];}\");\n    html.push(\"function filtered(){var rows=state.records.slice(),key=state.local;if(key==='hot')rows.sort(function(a,b){return b.likes-a.likes;});else if(key==='latest')rows.sort(function(a,b){return Number(b.review.createTime||0)-Number(a.review.createTime||0);});else if(key==='replies')rows=rows.filter(function(x){return x.comments>0;});else if(key==='pictures')rows=rows.filter(function(x){return picturesOf(x).length>0;});else if(key==='author')rows=rows.filter(function(x){var r=x.review||{};return r.isAuthorComment||r.isAuthorLike||r.author&&r.author.isV;});return rows;}\");\n    html.push(\"function renderModes(){var box=document.getElementById('mode-tabs');box.innerHTML=(cfg.modes||[]).map(function(mode){return '<button class=\\\"mode-tab '+(state.mode===mode?'active':'')+'\\\" data-mode=\\\"'+esc(mode)+'\\\">'+esc(labels[mode]||mode)+'<\/button>';}).join('');Array.prototype.forEach.call(box.querySelectorAll('button'),function(button){button.onclick=function(){setMode(button.getAttribute('data-mode'));};});}\");\n    html.push(\"var localFilters=[['all','全部'],['hot','热门'],['latest','最新'],['replies','有回复'],['pictures','有图片'],['author','作者互动']];var bookCategories=[[0,'热门'],[3,'最新'],[1,'推荐'],[4,'一般'],[2,'不推荐'],[8,'资深读者']];\");\n    html.push(\"function renderFilters(){if(state.mode==='book'){var c='<div class=\\\"filter-label\\\">书评分区<\/div><div class=\\\"filter-row\\\">'+bookCategories.map(function(x){return '<button class=\\\"filter '+(!state.tag&&state.category===x[0]?'active':'')+'\\\" data-category=\\\"'+x[0]+'\\\">'+x[1]+'<\/button>';}).join('')+'<\/div>';if(state.tags.length)c+='<div class=\\\"filter-label\\\">书友标签<\/div><div class=\\\"filter-row\\\">'+state.tags.map(function(t){return '<button class=\\\"filter '+(state.tag===t.name?'active':'')+'\\\" data-tag=\\\"'+esc(t.name)+'\\\">'+esc(t.name)+'<small>'+Number(t.count||0)+'<\/small><\/button>';}).join('')+'<\/div>';filterArea.innerHTML=c;Array.prototype.forEach.call(filterArea.querySelectorAll('[data-category]'),function(b){b.onclick=function(){state.tag='';state.category=Number(b.getAttribute('data-category'));loadCurrent(true);};});Array.prototype.forEach.call(filterArea.querySelectorAll('[data-tag]'),function(b){b.onclick=function(){state.tag=b.getAttribute('data-tag');loadCurrent(true);};});}else{filterArea.innerHTML='<div class=\\\"filter-label\\\">'+esc(labels[state.mode]||'评论')+'分类<\/div><div class=\\\"filter-row\\\">'+localFilters.map(function(x){return '<button class=\\\"filter '+(state.local===x[0]?'active':'')+'\\\" data-local=\\\"'+x[0]+'\\\">'+x[1]+'<\/button>';}).join('')+'<\/div>';Array.prototype.forEach.call(filterArea.querySelectorAll('[data-local]'),function(b){b.onclick=function(){state.local=b.getAttribute('data-local');renderFilters();renderRecords();};});}}\");\n    html.push(\"var renderFiltersBase=renderFilters;renderFilters=function(){if(state.mode!=='book'){filterArea.innerHTML='';return;}renderFiltersBase();};\");\n    html.push(\"function avatar(author,cls){var url=String(author.avatar||'');if(\/^https:\\\\\/\\\\\/\/i.test(url))return '<img class=\\\"'+cls+'\\\" loading=\\\"lazy\\\" src=\\\"'+esc(url)+'\\\" alt=\\\"\\\">';return '<span class=\\\"'+cls+' avatar-fallback\\\">'+esc(String(author.name||author.nick||'读').charAt(0))+'<\/span>';}\");\n    html.push(\"function renderRecords(){var rows=filtered(),messages=[];if(state.error)messages.push('<div class=\\\"notice\\\">'+esc(state.error)+'<\/div>');if(state.mode==='paragraph'&&cfg.range)messages.push('<div class=\\\"notice\\\">正文位置 '+esc(cfg.range)+' · 共 '+state.total+' 条段评<\/div>');notice.innerHTML=messages.join('');if(!rows.length){list.innerHTML='<div class=\\\"empty\\\">'+(state.error?'评论加载失败,请检查登录状态后重试':'暂时没有符合当前分类的评论')+'<\/div>';end.innerHTML='';return;}list.innerHTML=rows.map(function(x){var r=x.review||{},a=r.author||{},name=a.name||a.nick||'微信读书用户',content=r.content||r.htmlContent||r.abstract||r.title||'',context=r.contextAbstract||r.abstract||'',badges=[];if(r.isAuthorComment)badges.push('作者回复');if(r.isAuthorLike)badges.push('作者赞过');if(a.isDeepV)badges.push(a.deepVTitle||'资深读者');var badge=badges.map(function(v){return '<span class=\\\"badge\\\">'+esc(v)+'<\/span>';}).join(''),score=Number(r.star||0)>0?'<span class=\\\"score\\\">'+(Math.round(Number(r.star)\/2)\/10)+' 分<\/span>':'',pics=picturesOf(x).map(function(p){var u=String((p||{}).url||'');return \/^https:\\\\\/\\\\\/\/i.test(u)?'<img class=\\\"review-picture\\\" loading=\\\"lazy\\\" src=\\\"'+esc(u)+'\\\" alt=\\\"评论图片\\\">':'';}).join(''),meta=[];if(r.range)meta.push('位置 '+r.range);if(r.chapterName||r.chapterTitle)meta.push(r.chapterName||r.chapterTitle);var replyButton=x.comments>0?'<button class=\\\"reply-toggle\\\" data-review=\\\"'+esc(x.id)+'\\\">展开 '+x.comments+' 条回复<\/button>':'',replyThread=x.comments>0?'<div class=\\\"reply-thread\\\"><\/div>':'';return '<article><header>'+avatar(a,'avatar')+'<div class=\\\"identity\\\"><div><strong>'+esc(name)+'<\/strong>'+badge+'<\/div><span>'+esc(dateText(r.createTime))+'<\/span><\/div>'+score+'<\/header>'+(context&&context!==content?'<div class=\\\"context\\\">'+esc(context)+'<\/div>':'')+'<div class=\\\"content\\\">'+esc(content).replace(\/\\\\r?\\\\n\/g,'<br>')+'<\/div>'+pics+'<footer><span>'+esc(meta.join(' · '))+(meta.length?' ':'')+'赞 '+x.likes+' 回复 '+x.comments+'<\/span>'+replyButton+'<\/footer>'+replyThread+'<\/article>';}).join('');Array.prototype.forEach.call(list.querySelectorAll('.reply-toggle'),function(button){button.onclick=function(){toggleReplies(button);};});end.innerHTML=state.hasMore?'<div class=\\\"end\\\">继续下滑加载更多<\/div>':'<div class=\\\"end\\\">已加载当前分类全部内容<\/div>';}\");\n    html.push(\"var renderRecordsBase=renderRecords;renderRecords=function(){renderRecordsBase();if(state.mode==='paragraph'&&paragraphRanges.length){var summary='<div class=\\\"notice\\\">本段包含 '+paragraphRanges.length+' 个评论选区 · 已加载 '+state.records.length+' 条不重复段评<\/div>';notice.innerHTML=(state.error?notice.innerHTML:'')+summary;}Array.prototype.forEach.call(list.querySelectorAll('.reply-toggle'),function(button){var id=button.getAttribute('data-review'),record=state.records.find(function(item){return item.id===id;});if(record)button.innerHTML='<span>查看回复<\/span><span class=\\\"reply-count\\\">'+record.comments+'<\/span>';});};\");\n    html.push(\"function replyRow(reply){reply=reply||{};var a=reply.author||{},to=reply.replyUser||{},pic=String(reply.imageUrl||reply.pictureUrl||''),image=\/^https:\\\\\/\\\\\/\/i.test(pic)?'<img class=\\\"review-picture\\\" src=\\\"'+esc(pic)+'\\\" alt=\\\"回复图片\\\">':'';return '<div class=\\\"reply-item\\\">'+avatar(a,'reply-avatar')+'<div class=\\\"reply-main\\\"><div class=\\\"reply-head\\\"><span class=\\\"reply-name\\\">'+esc(a.name||a.nick||'微信读书用户')+'<\/span>'+(to.name?'<span class=\\\"reply-to\\\">回复 '+esc(to.name)+'<\/span>':'')+'<\/div><div class=\\\"reply-content\\\">'+esc(reply.content||'').replace(\/\\\\r?\\\\n\/g,'<br>')+'<\/div>'+image+'<div class=\\\"reply-meta\\\">'+esc(dateText(reply.createTime))+' · 赞 '+Number(reply.likesCount||0)+'<\/div><\/div><\/div>';}\");\n    html.push(\"function appendReplyTree(box,comments){var out=[];(comments||[]).forEach(function(reply){out.push(replyRow(reply,false));(reply.subComments||[]).forEach(function(sub){out.push(replyRow(sub,true));});});box.insertAdjacentHTML('beforeend',out.join(''));}\");\n    html.push(\"function loadReplyPage(button,thread,store){button.disabled=true;button.textContent='正在加载回复...';try{var url='https:\/\/i.weread.qq.com\/review\/commentloadmore?reviewId='+encodeURIComponent(store.id)+'&maxIdx='+store.cursor+'&count=100&isExpandAll=0',data=getJson(url,'回复');var comments=data.comments||[];if(!store.loaded)thread.innerHTML='<div class=\\\"reply-heading\\\">回复讨论<\/div>';appendReplyTree(thread,comments);store.cursor+=comments.length;store.hasMore=Boolean(data.commentsHasMore)&&comments.length>0;store.loaded=true;thread.classList.add('open');button.disabled=false;button.textContent=store.hasMore?'加载更多回复':'收起回复';if(store.hasMore){button.onclick=function(){loadReplyPage(button,thread,store);};}else{button.onclick=function(){var open=thread.classList.toggle('open');button.textContent=open?'收起回复':'查看回复';};}}catch(error){button.disabled=false;button.textContent='回复加载失败,点击重试';showError(error);}}\");\n    html.push(\"function toggleReplies(button){var id=button.getAttribute('data-review'),article=button.closest?button.closest('article'):button.parentNode.parentNode,thread=article.querySelector('.reply-thread'),store=state.reply[id]||(state.reply[id]={id:id,cursor:0,loaded:false,hasMore:true});if(store.loaded){var open=thread.classList.toggle('open');button.textContent=open?(store.hasMore?'加载更多回复':'收起回复'):'查看回复';return;}loadReplyPage(button,thread,store);}\");\n    html.push(\"function showError(error){state.error=error&&error.message?error.message:String(error);notice.innerHTML='<div class=\\\"notice\\\">'+esc(state.error)+'<\/div>';}\");\n    html.push(\"function setLoading(value){state.loading=value;if(value&&state.records.length===0)list.innerHTML='<div class=\\\"status\\\">正在加载评论...<\/div>';}\");\n    html.push(\"function loadTags(){if(state.tags.length)return;try{var data=postJson('https:\/\/i.weread.qq.com\/book\/reviewtag',{includeReviews:0,bookId:cfg.bookId,synckey:0},'书评标签');state.tags=(data.tags||[]).filter(function(t){return t&&t.name;});}catch(error){showError(error);}}\");\n    html.push(\"function loadParagraph(reset){if(!paragraphRanges.length)throw new Error('段评缺少正文 range');if(reset)state.rangeState={};var requests=[];paragraphRanges.forEach(function(range){var saved=state.rangeState[range]||{cursor:0,hasMore:true,total:0};if(!reset&&!saved.hasMore)return;var item={count:20,range:range};if(!reset&&saved.cursor>0)item.maxIdx=saved.cursor;else item.synckey=0;requests.push(item);});if(!requests.length){state.hasMore=false;return;}var before=state.records.length,data=postJson('https:\/\/i.weread.qq.com\/book\/readreviews',{bookId:cfg.bookId,chapterUid:Number(cfg.chapterUid),cht2sMode:'',reviews:requests},'段评'),all=[];(data.reviews||[]).forEach(function(group){var range=String(group.range||''),saved=state.rangeState[range]||{cursor:0,hasMore:false,total:0},previous=saved.cursor,next=Number(group.maxIdx||0);saved.hasMore=Boolean(group.hasMore)&&next>0&&next!==previous;saved.cursor=next;saved.total=Number(group.totalCount||saved.total||0);state.rangeState[range]=saved;all=all.concat(group.pageReviews||[]);});dedupe(all,!reset);state.total=paragraphRanges.reduce(function(sum,range){return sum+Number(state.rangeState[range]&&state.rangeState[range].total||0);},0);state.hasMore=state.records.length>before&&paragraphRanges.some(function(range){return state.rangeState[range]&&state.rangeState[range].hasMore;});}\");\n    html.push(\"function loadChapter(reset){var previous=state.cursor,before=state.records.length,data=getJson('https:\/\/i.weread.qq.com\/book\/chapterreviewlist?count=20&chapterUid='+encodeURIComponent(cfg.chapterUid)+'&bookId='+encodeURIComponent(cfg.bookId)+'&maxIdx='+(reset?0:previous),'章评');dedupe(data.reviews||[],!reset);state.total=Number(data.totalCount||state.records.length);var next=Number(data.maxIdx||0);state.hasMore=state.records.length>before&&Boolean(data.hasMore)&&next>0&&next!==previous;state.cursor=next;}\");\n    html.push(\"function loadBook(reset){loadTags();if(state.tag){if(!reset){state.hasMore=false;return;}var tagged=postJson('https:\/\/i.weread.qq.com\/book\/reviewtag',{includeReviews:1,filterTag:state.tag,bookId:cfg.bookId,synckey:0},'标签书评'),group=(tagged.tags||[])[0]||{};dedupe(group.reviews||[],false);state.total=Number(group.count||state.records.length);state.hasMore=false;return;}var previous=state.cursor,before=state.records.length,data=getJson('https:\/\/i.weread.qq.com\/book\/podcasts?count=100&listType=2&reviewListType='+state.category+'&bookId='+encodeURIComponent(cfg.bookId)+'&synckey='+(reset?0:previous),'书评');dedupe(data.reviews||[],!reset);state.total=Number(data.reviewsCnt||state.records.length);var next=Number(data.synckey||0);state.hasMore=state.records.length>before&&Boolean(data.reviewsHasMore)&&next>0&&next!==previous;state.cursor=next;}\");\n    html.push(\"function loadCurrent(reset){if(state.loading)return;if(reset){state.cursor=0;state.records=[];state.total=0;state.hasMore=false;state.reply={};state.rangeState={};}state.error='';setLoading(true);try{if(state.mode==='paragraph')loadParagraph(reset);else if(state.mode==='chapter')loadChapter(reset);else if(state.mode==='book')loadBook(reset);else throw new Error('未知评论类型');}catch(error){showError(error);}setLoading(false);renderModes();renderFilters();renderRecords();}\");\n    html.push(\"function setMode(mode){state.mode=mode;state.local='all';state.tag='';state.category=0;loadCurrent(true);}\");\n    html.push(\"window.addEventListener('scroll',function(){if(state.loading||!state.hasMore)return;if(window.innerHeight+window.scrollY<document.documentElement.scrollHeight-260)return;loadCurrent(false);},{passive:true});renderModes();renderFilters();loadCurrent(true);\");\n    html.push(\"})();<\/script><\/body><\/html>\");\n    return html.join(\"\");\n}\nfunction wrOpenComments(bookId, chapterUid, requestedMode, requestedRange) {\n    var j = null;\n    var src = null;\n    var currentBook = null;\n    var currentChapter = null;\n    try { j = this && this.java ? this.java : null; } catch (ignoreThisJava) {}\n    try { src = this && this.source ? this.source : null; } catch (ignoreThisSource) {}\n    try { currentBook = this && this.book ? this.book : null; } catch (ignoreThisBook) {}\n    try { currentChapter = this && this.chapter ? this.chapter : null; } catch (ignoreThisChapter) {}\n    try { if (!j && typeof java != \"undefined\") j = java; } catch (ignoreGlobalJava) {}\n    try { if (!src && typeof source != \"undefined\") src = source; } catch (ignoreGlobalSource) {}\n    try { if (!currentBook && typeof book != \"undefined\") currentBook = book; } catch (ignoreGlobalBook) {}\n    try { if (!currentChapter && typeof chapter != \"undefined\") currentChapter = chapter; } catch (ignoreGlobalChapter) {}\n    try {\n        bookId = String(bookId || \"\");\n        chapterUid = String(chapterUid || \"\");\n        requestedMode = String(requestedMode || \"\");\n        requestedRange = String(requestedRange || \"\");\n        if (!bookId) {\n            var match = String(currentBook && currentBook.bookUrl || \"\").match(\/[?&]bookId=([^&,]+)\/);\n            if (match) bookId = decodeURIComponent(match[1]);\n        }\n        if (!j || !src) throw new Error(\"评论点击上下文不可用,请重新导入书源后刷新正文\");\n        if (!bookId) throw new Error(\"无法识别 bookId\");\n        var enabled = wrCommentModes(src);\n        var modeOrder = [\"paragraph\", \"chapter\", \"book\"];\n        var enabledModes = [];\n        for (var i = 0; i < modeOrder.length; i++) if (enabled[modeOrder[i]]) enabledModes.push(modeOrder[i]);\n        if (!enabledModes.length) throw new Error(\"请先在登录页开启至少一个评论开关,并刷新正文\");\n        if (requestedMode && !enabled[requestedMode]) throw new Error(\"该评论开关尚未开启\");\n        if (!requestedMode) requestedMode = enabledModes[0];\n        if (requestedMode == \"paragraph\" && !requestedRange) throw new Error(\"段评入口缺少正文位置,请刷新本章后重试\");\n        var pageMode = String(wrValue(src, \"◎ 段评美化\") || \"美化\");\n        var accent = String(wrValue(src, \"◎ 段评颜色\") || \"#23745B\");\n        if (!\/^#[0-9a-f]{6}$\/i.test(accent)) accent = \"#23745B\";\n        var html = wrReviewPage(String(currentBook && currentBook.name || \"微信读书评论\"), bookId, chapterUid,\n            enabledModes, requestedMode, requestedRange, String(currentChapter && currentChapter.title || \"\"),\n            {mode:pageMode, accent:accent});\n        var heightValue = Number(wrValue(src, \"◎ 半屏高度\") || 88);\n        var height = heightValue > 1 ? heightValue \/ 100 : heightValue;\n        if (!isFinite(height) || height < 0.5 || height > 1) height = 0.88;\n        var browserConfig = {state:4, isHideable:true, heightPercentage:height,\n            expandedCornersRadius:12, dismissOnTouchOutside:true, widthPercentage:1,\n            skipCollapsed:true, hardwareAccelerated:true, webViewInitialScale:100};\n        var showUrl = \"https:\/\/i.weread.qq.com\/book\/info?bookId=\" + encodeURIComponent(bookId);\n        j.showBrowser(showUrl, html, \"window.java=java;\", JSON.stringify(browserConfig));\n        return \"已打开微信读书\" + ({paragraph:\"段评\",chapter:\"章评\",book:\"书评\"}[requestedMode] || \"评论\");\n    } catch (error) {\n        var message = \"打开微信读书评论失败:\" + error;\n        try { if (j) j.longToast(message); } catch (ignoreToast) {}\n        return message;\n    }\n}\nfunction wrOpenBookReviews(j, src, currentBook) {\n    return wrOpenComments(j, src, currentBook, \"\", \"\");\n}\nfunction wrMap(object) {\n    var map = new Packages.java.util.HashMap();\n    for (var name in object) {\n        if (Object.prototype.hasOwnProperty.call(object, name) && object[name] != null) {\n            map.put(name, String(object[name]));\n        }\n    }\n    return map;\n}\n\nfunction wrInputValue(data, name) {\n    if (data != null && typeof data.get == \"function\") {\n        var value = data.get(name);\n        if (value != null) return String(value).trim();\n    }\n    return \"\";\n}\n\nfunction wrBaseHeaderObject(userAgent) {\n    return {\n        \"baseapi\": \"36\",\n        \"appver\": WR_APP_VERSION,\n        \"User-Agent\": userAgent || WR_DEFAULT_USER_AGENT,\n        \"osver\": \"16\",\n        \"channelId\": \"0\",\n        \"basever\": WR_APP_VERSION\n    };\n}\n\nfunction wrLoginHeaderMap(userAgent, ticket, randstr) {\n    var headers = wrBaseHeaderObject(userAgent);\n    headers[\"Content-Type\"] = \"application\/json; charset=UTF-8\";\n    if (ticket) headers.wr_ticket = ticket;\n    if (randstr) headers.wr_randstr = randstr;\n    return wrMap(headers);\n}\n\nfunction wrRandomDigits(random, length) {\n    var value = \"\";\n    while (value.length < length) value += String(random.nextInt(10));\n    return value;\n}\n\nfunction wrDeviceState(src, data) {\n    var random = new Packages.java.security.SecureRandom();\n    var oldDeviceId = wrInputValue(data, \"_wrOldDeviceId\") || wrValue(src, \"_wrOldDeviceId\");\n    var installId = wrInputValue(data, \"_wrInstallId\") || wrValue(src, \"_wrInstallId\");\n    var newDeviceId = wrInputValue(data, \"_wrNewDeviceId\") || wrValue(src, \"_wrNewDeviceId\");\n    if (!\/^\\d{28}$\/.test(oldDeviceId)) oldDeviceId = wrRandomDigits(random, 28);\n    if (!\/^\\d{28}$\/.test(installId)) installId = wrRandomDigits(random, 28);\n    if (!\/^\\d{38}$\/.test(newDeviceId)) newDeviceId = wrRandomDigits(random, 38);\n    return {\n        random: random,\n        oldDeviceId: oldDeviceId,\n        installId: installId,\n        newDeviceId: newDeviceId\n    };\n}\n\nfunction wrBytes(values) {\n    var bytes = Packages.java.nio.ByteBuffer.allocate(values.length).array();\n    for (var i = 0; i < values.length; i++) {\n        var value = values[i] & 255;\n        bytes[i] = value > 127 ? value - 256 : value;\n    }\n    return bytes;\n}\n\nfunction wrUtf8Values(value) {\n    var bytes = new Packages.java.lang.String(String(value)).getBytes(\"UTF-8\");\n    var values = [];\n    for (var i = 0; i < bytes.length; i++) values.push(wrU8(bytes, i));\n    return values;\n}\n\nfunction wrRemapValues(j, value) {\n    var table = j.hexDecodeToByteArray(WR_REMAP_HEX);\n    var input = new Packages.java.lang.String(String(value)).getBytes(\"UTF-8\");\n    var output = [];\n    for (var i = 0; i < input.length; i++) output.push(wrU8(table, wrU8(input, i)));\n    return output;\n}\n\nfunction wrCompareBytes(left, right) {\n    var length = Math.min(left.length, right.length);\n    for (var i = 0; i < length; i++) {\n        if (left[i] != right[i]) return left[i] - right[i];\n    }\n    return left.length - right.length;\n}\n\nfunction wrRotateByXor(values) {\n    if (values.length == 0) return [];\n    var xor = 0;\n    for (var i = 0; i < values.length; i++) xor ^= values[i];\n    var shift = xor % 11;\n    var output = [];\n    for (i = 0; i < values.length; i++) output[(i + shift) % values.length] = values[i];\n    return output;\n}\n\nfunction wrSha256Hex(values) {\n    var digest = Packages.java.security.MessageDigest.getInstance(\"SHA-256\").digest(wrBytes(values));\n    var hex = \"\";\n    for (var i = 0; i < digest.length; i++) {\n        var item = (wrU8(digest, i)).toString(16);\n        hex += item.length == 1 ? \"0\" + item : item;\n    }\n    return hex;\n}\n\nfunction wrGuestSignature(j, token, random, timestamp, oldDeviceId) {\n    var parts = [\n        wrRemapValues(j, token),\n        wrRemapValues(j, String(random)),\n        wrRemapValues(j, String(timestamp)),\n        wrRemapValues(j, oldDeviceId),\n        wrUtf8Values(\"5a6f1\")\n    ];\n    parts.sort(wrCompareBytes);\n    var joined = [];\n    for (var i = 0; i < parts.length; i++) joined = joined.concat(parts[i]);\n    var first = wrSha256Hex(wrRotateByXor(joined));\n    return wrSha256Hex(wrRotateByXor(wrUtf8Values(first)));\n}\n\nfunction wrLoginSignature(timestamp, deviceId, random) {\n    return wrSha256Hex(wrUtf8Values(String(timestamp) + deviceId + String(random)));\n}\n\nfunction wrResponseJson(response, label, allowedStatus) {\n    var status = Number(response.statusCode());\n    var data;\n    try {\n        data = JSON.parse(String(response.body()));\n    } catch (error) {\n        throw new Error(label + \"返回了无效数据,HTTP \" + status);\n    }\n    if (allowedStatus == null && status != 200) {\n        throw new Error(label + \"失败,HTTP \" + status + (data.errcode != null ? \",错误码 \" + data.errcode : \"\"));\n    }\n    return {status: status, data: data};\n}\n\nfunction wrCaptchaHtml(cacheKey) {\n    var safeCacheKey = JSON.stringify(String(cacheKey || \"\"));\n    return \"<!doctype html><html><head><meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1'>\" +\n        \"<title>微信读书安全验证<\/title><style>body{margin:0;background:#f4f6f5;color:#17211e;font-family:sans-serif}\" +\n        \".wrap{max-width:520px;margin:auto;padding:28px 20px}.panel{background:#fff;border:1px solid #d9dfdc;border-radius:6px;padding:22px}\" +\n        \"h1{font-size:20px;margin:0 0 10px}p{font-size:15px;line-height:1.7;color:#52615c;margin:8px 0}\" +\n        \"#status{margin-top:18px;color:#28785f;font-weight:bold}#complete{display:none;margin:18px 0 0;padding:12px 20px;border:0;border-radius:4px;background:#28785f;color:#fff;font-size:16px}\" +\n        \"#wr-result{display:none}<\/style><script src='https:\/\/captcha.gtimg.com\/TCaptcha.js'><\/script><\/head><body><div class='wrap'><div class='panel'>\" +\n        \"<h1>微信读书安全验证<\/h1><p>完成腾讯验证码后会自动返回并继续游客登录。<\/p>\" +\n        \"<div id='status'>正在载入验证码...<\/div><button id='complete' type='button'>返回并完成游客登录<\/button>\" +\n        \"<div id='wr-result' data-result=''><\/div><\/div><\/div><script>(function(){var cacheKey=\" + safeCacheKey + \";\" +\n        \"function requestClose(){var keys=[];try{keys=Object.getOwnPropertyNames(window);}catch(ignoreKeys){}\" +\n        \"for(var i=0;i<keys.length;i++){try{var bridge=window[keys[i]];if(bridge&&typeof bridge.onCloseRequested==='function'){bridge.onCloseRequested();return true;}}catch(ignoreBridge){}}\" +\n        \"try{window.close();return true;}catch(ignoreClose){return false;}}\" +\n        \"function saveResult(r){var s=document.getElementById('status');if(r&&r.ret===0&&r.ticket&&r.randstr){\" +\n        \"var raw=JSON.stringify({ticket:r.ticket,randstr:r.randstr}),encoded=btoa(unescape(encodeURIComponent(raw))),node=document.getElementById('wr-result');\" +\n        \"node.textContent=encoded;node.setAttribute('data-result',encoded);document.body.setAttribute('data-wr-captcha',encoded);\" +\n        \"try{if(cacheKey&&typeof cache!=='undefined'&&cache&&typeof cache.putMemory==='function')cache.putMemory(cacheKey,encoded);}catch(ignoreCache){}\" +\n        \"var button=document.getElementById('complete');button.style.display='block';button.onclick=requestClose;\" +\n        \"s.textContent='验证成功,正在继续游客登录...';setTimeout(requestClose,300);setTimeout(function(){if(document.visibilityState!=='hidden')requestClose();},1300);}\" +\n        \"else{s.textContent='验证未完成,请重试';}}\" +\n        \"window.onload=function(){try{window.wrCaptcha=new TencentCaptcha('\" + WR_CAPTCHA_APP_ID + \"',saveResult,{enableDarkMode:true}).show();}\" +\n        \"catch(e){document.getElementById('status').textContent='验证码载入失败,请返回重试';}};})();<\/script><\/body><\/html>\";\n}\nfunction wrGetCaptcha(j) {\n    var cacheKey = \"wr_guest_captcha_\" + String(Packages.java.lang.System.currentTimeMillis());\n    try { cache.deleteMemory(cacheKey); } catch (ignoreOldCaptchaCache) {}\n    var page = null, awaitError = null;\n    try {\n        page = j.startBrowserAwait(\"https:\/\/captcha.gtimg.com\/\", \"微信读书安全验证\", false, wrCaptchaHtml(cacheKey));\n    } catch (error) {\n        awaitError = error;\n    }\n    var encoded = \"\";\n    try {\n        encoded = String(cache.getFromMemory(cacheKey) || \"\").trim();\n        cache.deleteMemory(cacheKey);\n    } catch (ignoreCaptchaCache) {}\n    var html = \"\";\n    if (page != null) {\n        try {\n            var body = page.body;\n            if (typeof body == \"function\") body = page.body();\n            html = String(body || \"\");\n        } catch (ignoreCaptchaBody) {}\n    }\n    if (!encoded) {\n        var match = html.match(\/data-wr-captcha=[\\\"']([^\\\"']+)\/i) ||\n            html.match(\/id=[\\\"']wr-result[\\\"'][^>]*data-result=[\\\"']([^\\\"']+)\/i) ||\n            html.match(\/id=[\\\"']wr-result[\\\"'][^>]*>([^<]+)\/i);\n        if (match && match[1]) encoded = String(match[1]).trim();\n    }\n    if (!encoded) {\n        var detail = awaitError && awaitError.message ? \":\" + awaitError.message : \"\";\n        throw new Error(\"验证码页面已返回,但未取得 ticket\/randstr\" + detail + \"。请重新验证一次\");\n    }\n    var raw = new Packages.java.lang.String(j.base64DecodeToByteArray(encoded), \"UTF-8\");\n    var data = JSON.parse(String(raw));\n    if (!data.ticket || !data.randstr) throw new Error(\"腾讯验证码结果不完整\");\n    return data;\n}\nfunction wrQrHtml(base64, mime, uuid, cacheKey) {\n    var safeUuid = JSON.stringify(String(uuid));\n    var safeCacheKey = JSON.stringify(String(cacheKey || \"\"));\n    return \"<!doctype html><html><head><meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1'>\" +\n        \"<title>微信扫码登录<\/title><style>body{margin:0;background:#f4f6f5;color:#17211e;font-family:sans-serif;text-align:center}\" +\n        \".wrap{max-width:520px;margin:auto;padding:24px 18px}.qr{background:#fff;border:1px solid #d9dfdc;border-radius:6px;padding:20px}\" +\n        \"img{display:block;width:min(82vw,350px);height:auto;margin:auto}h1{font-size:20px;margin:0 0 14px}\" +\n        \"p{font-size:14px;line-height:1.65;color:#52615c;margin:12px 0 0}#status{color:#28785f;font-weight:bold}\" +\n        \"#complete{display:none;margin:18px auto 0;padding:12px 20px;border:0;border-radius:4px;background:#28785f;color:#fff;font-size:16px}\" +\n        \"#wr-qr-result{display:none}<\/style><\/head><body><div class='wrap'><div class='qr'>\" +\n        \"<h1>微信扫码登录<\/h1><img alt='微信登录二维码' src='data:\" + (mime || \"image\/jpeg\") + \";base64,\" + base64 + \"'>\" +\n        \"<p>请截图后用微信扫一扫,或用另一台设备扫码。<\/p><p id='status'>正在等待扫码...<\/p>\" +\n        \"<button id='complete' type='button'>返回并完成登录<\/button><div id='wr-qr-result' data-result=''><\/div><\/div><\/div><script>(function(){\" +\n        \"var uuid=\" + safeUuid + \",cacheKey=\" + safeCacheKey + \",last='',started=Date.now(),stopped=false,failures=0,statusNode=document.getElementById('status');\" +\n        \"function setStatus(text,bad){statusNode.textContent=text;statusNode.style.color=bad?'#b33a3a':'#28785f';}\" +\n        \"function requestClose(){var keys=[];try{keys=Object.getOwnPropertyNames(window);}catch(ignoreKeys){}\" +\n        \"for(var i=0;i<keys.length;i++){try{var bridge=window[keys[i]];if(bridge&&typeof bridge.onCloseRequested==='function'){bridge.onCloseRequested();return true;}}catch(ignoreBridge){}}\" +\n        \"try{window.close();return true;}catch(ignoreClose){return false;}}\" +\n        \"function finish(code){var encoded=btoa(String(code)),node=document.getElementById('wr-qr-result');stopped=true;\" +\n        \"node.textContent=encoded;node.setAttribute('data-result',encoded);document.body.setAttribute('data-wr-code',encoded);\" +\n        \"try{if(cacheKey&&typeof cache!=='undefined'&&cache&&typeof cache.putMemory==='function')cache.putMemory(cacheKey,encoded);}catch(ignoreCache){}\" +\n        \"var button=document.getElementById('complete');button.style.display='block';button.onclick=requestClose;\" +\n        \"setStatus('微信已确认,正在返回并写入登录参数...',false);setTimeout(requestClose,300);setTimeout(function(){if(document.visibilityState!=='hidden')requestClose();},1300);}\" +\n        \"function parse(raw){try{return JSON.parse(String(raw||''));}catch(e){throw new Error('扫码状态返回无效');}}\" +\n        \"async function poll(){if(stopped)return;if(Date.now()-started>280000){setStatus('二维码等待超时,请返回后重新生成',true);return;}\" +\n        \"var url='https:\/\/long.open.weixin.qq.com\/connect\/l\/qrconnect?f=json&uuid='+encodeURIComponent(uuid)+(last?'&last='+last:'');\" +\n        \"try{var response=await fetch(url,{method:'GET',cache:'no-store',credentials:'omit'});if(!response.ok)throw new Error('HTTP '+response.status);var raw=await response.text();failures=0;\" +\n        \"var data=parse(raw),code=Number(data.wx_errcode);if(code===405&&data.wx_code){finish(data.wx_code);return;}\" +\n        \"if(code===404){last='404';setStatus('已扫码,请在微信中确认登录',false);setTimeout(poll,80);return;}\" +\n        \"if(code===408){last='408';setStatus('正在等待扫码...',false);setTimeout(poll,80);return;}\" +\n        \"if(code===402||code===403){setStatus('二维码已失效或登录被拒绝,请返回重试',true);return;}\" +\n        \"setStatus('扫码状态 '+code+',正在重试...',true);setTimeout(poll,1200);}\" +\n        \"catch(e){failures++;setStatus('扫码状态请求失败,正在重试('+failures+'):'+String(e&&e.message||e),true);setTimeout(poll,1500);}}setTimeout(poll,300);})();<\/script><\/body><\/html>\";\n}\nfunction wrInputToggle(input, src, name, fallback) {\n    var value = wrInputValue(input, name) || wrValue(src, name);\n    return value || fallback || \"关闭\";\n}\n\nfunction wrApplyCommentPreferences(input, src, saved) {\n    var preferences = [\n        [\"◎ 半屏高度\", \"88\"],\n        [\"◎ 段评颜色\", \"#23745B\"],\n        [\"◎ 段评美化\", \"美化\"],\n        [\"◎ 模板\", \"起点\"],\n        [\"◎ 大小\", \"TEXT\"],\n        [\"◎ 气泡一\", \"\"],\n        [\"◎ 气泡二\", \"\"],\n        [\"◎ 气泡三\", \"\"]\n    ];\n    for (var i = 0; i < preferences.length; i++) {\n        var name = preferences[i][0];\n        saved[name] = wrInputValue(input, name) || wrValue(src, name) || preferences[i][1];\n    }\n    return saved;\n}\nfunction wrUpdateCommentSettings(j, src, input, closeAll) {\n    var names = [\"段评开关\", \"章评开关\", \"书评开关\", \"作者评论开关\"];\n    var saved = {};\n    var current = null;\n    try { current = src.getLoginInfoMap(); } catch (ignoreLoginInfo) {}\n    if (current) {\n        try {\n            var iterator = current.entrySet().iterator();\n            while (iterator.hasNext()) {\n                var entry = iterator.next();\n                saved[String(entry.getKey())] = String(entry.getValue());\n            }\n        } catch (ignoreJavaMap) {\n            try {\n                current.forEach(function(value, key) { saved[String(key)] = String(value); });\n            } catch (ignoreJsMap) {}\n        }\n    }\n    var updates = new Packages.java.util.HashMap();\n    for (var i = 0; i < names.length; i++) {\n        var value = closeAll ? \"🔳\" : wrInputToggle(input, src, names[i], \"🔳\");\n        saved[names[i]] = value;\n        updates.put(names[i], value);\n    }\n    wrApplyCommentPreferences(input, src, saved);\n    src.putLoginInfo(JSON.stringify(saved));\n    j.upLoginData(updates);\n    try { j.refreshContent(); } catch (ignoreRefresh) {}\n    j.longToast(closeAll ? \"全部评论已关闭,正文已刷新\" : \"评论设置已保存,正文已刷新\");\n    return closeAll ? \"全部评论已关闭\" : \"评论设置已保存\";\n}\nfunction wrSaveGuestLogin(j, src, input, state, userAgent, guest, captcha) {\n    var saved = {\n        \"vid\": String(guest.vid),\n        \"accessToken\": String(guest.accessToken),\n        \"User-Agent\": userAgent,\n        \"段评开关\": wrInputToggle(input, src, \"段评开关\", \"🔳\"),\n        \"章评开关\": wrInputToggle(input, src, \"章评开关\", \"🔳\"),\n        \"书评开关\": wrInputToggle(input, src, \"书评开关\", \"🔳\"),\n        \"_wrLoginMode\": \"guest\",\n        \"_wrOldDeviceId\": state.oldDeviceId,\n        \"_wrInstallId\": state.installId,\n        \"_wrNewDeviceId\": state.newDeviceId,\n        \"_wrGuestVid\": String(guest.vid),\n        \"_wrGuestAccessToken\": String(guest.accessToken),\n        \"_wrCaptchaTicket\": String(captcha.ticket || \"\"),\n        \"_wrCaptchaRandstr\": String(captcha.randstr || \"\"),\n        \"_wrCaptchaSavedAt\": String(Packages.java.lang.System.currentTimeMillis())\n    };\n    wrApplyCommentPreferences(input, src, saved);\n    src.putLoginHeader(JSON.stringify({\n        \"vid\": saved.vid,\n        \"accessToken\": saved.accessToken,\n        \"User-Agent\": saved[\"User-Agent\"],\n        \"baseapi\": \"36\",\n        \"appver\": WR_APP_VERSION,\n        \"basever\": WR_APP_VERSION,\n        \"osver\": \"16\",\n        \"channelId\": \"0\"\n    }));\n    if (!src.putLoginInfo(JSON.stringify(saved))) throw new Error(\"游客登录状态保存失败\");\n    j.upLoginData(wrMap(saved));\n    return saved;\n}\nfunction wrAcquireGuestSession(j, src, input, userAgent, state, allowCaptcha) {\n    var featureResponse = j.get(\"https:\/\/i.weread.qq.com\/feature?synckey=0\", wrMap(wrBaseHeaderObject(userAgent)), 30000);\n    var feature = wrResponseJson(featureResponse, \"游客配置\").data;\n    var guestToken = feature && feature.feature ? String(feature.feature.guest_token || \"\") : \"\";\n    if (!guestToken) throw new Error(\"游客配置缺少 guest_token\");\n    var guestTimestamp = Math.floor(Number(Packages.java.lang.System.currentTimeMillis()) \/ 1000);\n    var guestRandom = state.random.nextInt(2147483647);\n    var guestBody = {\n        appFirstInstall:0,\n        deviceId:state.oldDeviceId,\n        installId:state.installId,\n        newDeviceId:state.newDeviceId,\n        random:guestRandom,\n        signature:wrGuestSignature(j, guestToken, guestRandom, guestTimestamp, state.oldDeviceId),\n        timestamp:guestTimestamp,\n        virtualChannelId:\"\"\n    };\n    var guestBodyText = JSON.stringify(guestBody);\n    var guest = null;\n    var captcha = {ticket:\"\", randstr:\"\"};\n    var challenged = false;\n    try {\n        var preflight = j.post(\"https:\/\/i.weread.qq.com\/guestLogin\", guestBodyText,\n            wrLoginHeaderMap(userAgent, \"\", \"\"), 30000);\n        var preflightResult = wrResponseJson(preflight, \"游客预登录\", true);\n        if (preflightResult.status == 200) {\n            guest = preflightResult.data;\n        } else if (preflightResult.status == 499 && Number(preflightResult.data.errcode) == -2041) {\n            challenged = true;\n        } else {\n            throw new Error(\"游客预登录失败,HTTP \" + preflightResult.status +\n                (preflightResult.data.errcode != null ? \",错误码 \" + preflightResult.data.errcode : \"\"));\n        }\n    } catch (preflightError) {\n        var detail = String(preflightError && preflightError.message ? preflightError.message : preflightError);\n        if (detail.indexOf(\"499\") >= 0 || detail.indexOf(\"-2041\") >= 0) challenged = true;\n        else throw preflightError;\n    }\n    if (guest == null || guest.vid == null || !guest.accessToken) {\n        if (!challenged) throw new Error(\"游客预登录没有返回登录态,也没有触发验证码\");\n        if (allowCaptcha === false) throw new Error(\"扫码临时会话被要求安全验证,请稍后重新扫码\");\n        j.longToast(\"已触发游客安全验证,验证成功后会自动继续登录\");\n        captcha = wrGetCaptcha(j);\n        j.longToast(\"已取得验证码结果,正在完成游客登录...\");\n        var loginResponse = j.post(\"https:\/\/i.weread.qq.com\/guestLogin\", guestBodyText,\n            wrLoginHeaderMap(userAgent, captcha.ticket, captcha.randstr), 30000);\n        guest = wrResponseJson(loginResponse, \"游客登录\").data;\n    }\n    if (guest.vid == null || !guest.accessToken) throw new Error(\"游客登录没有返回 vid\/accessToken\");\n    return {guest:guest, captcha:captcha};\n}\nfunction wrGuestLogin(j, src, input) {\n    var userAgent = wrInputValue(input, \"User-Agent\") || wrValue(src, \"User-Agent\") || WR_DEFAULT_USER_AGENT;\n    var state = wrDeviceState(src, input);\n    var session = wrAcquireGuestSession(j, src, input, userAgent, state);\n    var guest = session.guest, captcha = session.captcha;\n    var verifyHeaders = wrBaseHeaderObject(userAgent);\n    verifyHeaders.vid = String(guest.vid);\n    verifyHeaders.accessToken = String(guest.accessToken);\n    if (captcha.ticket) verifyHeaders.wr_ticket = String(captcha.ticket);\n    if (captcha.randstr) verifyHeaders.wr_randstr = String(captcha.randstr);\n    var verifyUrl = \"https:\/\/i.weread.qq.com\/store\/search?count=1&type=0&keyword=%E6%B5%8B%E8%AF%95&v=2&scope=17&maxIdx=0\";\n    var verifyResponse = j.get(verifyUrl, wrMap(verifyHeaders), 30000);\n    var verified = wrResponseJson(verifyResponse, \"游客阅读状态\").data;\n    if (verified.errcode != null && Number(verified.errcode) != 0) {\n        throw new Error(\"游客阅读状态失败,错误码 \" + verified.errcode);\n    }\n    var saved = wrSaveGuestLogin(j, src, input, state, userAgent, guest, captcha);\n    var persistedResponse = j.get(verifyUrl, wrHeaderMap(src), 30000);\n    var persisted = wrResponseJson(persistedResponse, \"游客登录保存验证\").data;\n    if (persisted.errcode != null && Number(persisted.errcode) != 0) {\n        throw new Error(\"游客登录保存验证失败,错误码 \" + persisted.errcode);\n    }\n    j.upLoginData(wrMap({vid:saved.vid, accessToken:saved.accessToken, \"User-Agent\":saved[\"User-Agent\"],\n        \"游客登录(需验证码)\":\"游客登录验证成功\"}));\n    var message = \"游客登录验证成功,可以阅读正文\";\n    try { j.toast(message); } catch (ignoreShortToast) {}\n    j.longToast(message);\n    return message;\n}\nfunction wrSaveQrLogin(j, src, input, state, userAgent, loginData) {\n    var saved = {\n        \"vid\": String(loginData.vid),\n        \"accessToken\": String(loginData.accessToken),\n        \"User-Agent\": userAgent,\n        \"段评开关\": wrInputToggle(input, src, \"段评开关\", \"🔳\"),\n        \"章评开关\": wrInputToggle(input, src, \"章评开关\", \"🔳\"),\n        \"书评开关\": wrInputToggle(input, src, \"书评开关\", \"🔳\"),\n        \"_wrLoginMode\": \"wechat\",\n        \"_wrOldDeviceId\": state.oldDeviceId,\n        \"_wrInstallId\": state.installId,\n        \"_wrNewDeviceId\": state.newDeviceId\n    };\n    if (loginData.refreshToken) saved._wrRefreshToken = String(loginData.refreshToken);\n    wrApplyCommentPreferences(input, src, saved);\n    if (!src.putLoginInfo(JSON.stringify(saved))) throw new Error(\"微信扫码登录参数保存失败\");\n    src.putLoginHeader(JSON.stringify({\n        \"vid\": saved.vid,\n        \"accessToken\": saved.accessToken,\n        \"User-Agent\": saved[\"User-Agent\"],\n        \"baseapi\": \"36\",\n        \"appver\": WR_APP_VERSION,\n        \"basever\": WR_APP_VERSION,\n        \"osver\": \"16\",\n        \"channelId\": \"0\"\n    }));\n    j.upLoginData(wrMap(saved));\n    if (wrValue(src, \"vid\") !== saved.vid || wrValue(src, \"accessToken\") !== saved.accessToken ||\n        wrValue(src, \"User-Agent\") !== saved[\"User-Agent\"]) {\n        throw new Error(\"微信扫码登录参数写入后读取不一致\");\n    }\n    return saved;\n}\nfunction wrQrLogin(j, src, input) {\n    var userAgent = wrInputValue(input, \"User-Agent\") || wrValue(src, \"User-Agent\") || WR_DEFAULT_USER_AGENT;\n    var state = wrDeviceState(src, input);\n    var loginHeaders = wrLoginHeaderMap(userAgent, \"\", \"\");\n    j.longToast(\"正在获取微信登录二维码...\");\n    var wxTicket = wrResponseJson(\n        j.get(\"https:\/\/i.weread.qq.com\/wxticket?nonceStr=weread\", loginHeaders, 30000),\n        \"微信二维码票据\"\n    ).data;\n    if (!wxTicket.signature || !wxTicket.timeStamp) throw new Error(\"微信二维码票据不完整\");\n    var qrUrl = \"https:\/\/open.weixin.qq.com\/connect\/sdk\/qrconnect?appid=\" + WR_WECHAT_APP_ID +\n        \"&noncestr=weread&timestamp=\" + wxTicket.timeStamp +\n        \"&scope=snsapi_userinfo%2Csnsapi_timeline%2Csnsapi_friend&signature=\" + wxTicket.signature;\n    var qrData = wrResponseJson(j.get(qrUrl, new Packages.java.util.HashMap(), 30000), \"微信二维码\").data;\n    if (Number(qrData.errcode) != 0 || !qrData.uuid || !qrData.qrcode || !qrData.qrcode.qrcodebase64) {\n        throw new Error(\"微信二维码生成失败,错误码 \" + qrData.errcode);\n    }\n    var qrBase64 = String(qrData.qrcode.qrcodebase64 || \"\").replace(\/^data:image\\\/[a-zA-Z0-9.+-]+;base64,\/, \"\").replace(\/\\s\/g, \"\");\n    if (!qrBase64) throw new Error(\"微信二维码图片为空\");\n    var qrMime = qrBase64.indexOf(\"\/9j\/\") == 0 ? \"image\/jpeg\" :\n        (qrBase64.indexOf(\"iVBOR\") == 0 ? \"image\/png\" :\n        (qrBase64.indexOf(\"UklGR\") == 0 ? \"image\/webp\" : \"image\/jpeg\"));\n    var qrCacheKey = \"wr_qr_code_\" + String(qrData.uuid);\n    try { cache.deleteMemory(qrCacheKey); } catch (ignoreOldQrCache) {}\n    var qrPage = null, qrAwaitError = null;\n    try {\n        qrPage = j.startBrowserAwait(\"https:\/\/long.open.weixin.qq.com\/\", \"微信扫码登录\", false,\n            wrQrHtml(qrBase64, qrMime, qrData.uuid, qrCacheKey));\n    } catch (awaitError) {\n        qrAwaitError = awaitError;\n    }\n    var encodedCode = \"\";\n    try {\n        encodedCode = String(cache.getFromMemory(qrCacheKey) || \"\").trim();\n        cache.deleteMemory(qrCacheKey);\n    } catch (ignoreQrCache) {}\n    var qrHtml = \"\";\n    if (qrPage != null) {\n        try {\n            var qrBody = qrPage.body;\n            if (typeof qrBody == \"function\") qrBody = qrPage.body();\n            qrHtml = String(qrBody || \"\");\n        } catch (ignoreQrBody) {}\n    }\n    if (!encodedCode) {\n        var codeMatch = qrHtml.match(\/data-wr-code=[\\\"']([^\\\"']+)\/i) ||\n            qrHtml.match(\/id=[\\\"']wr-qr-result[\\\"'][^>]*data-result=[\\\"']([^\\\"']+)\/i) ||\n            qrHtml.match(\/id=[\\\"']wr-qr-result[\\\"'][^>]*>([^<]+)\/i);\n        if (codeMatch && codeMatch[1]) encodedCode = String(codeMatch[1]).trim();\n    }\n    if (!encodedCode) {\n        var awaitDetail = qrAwaitError && qrAwaitError.message ? \":\" + qrAwaitError.message : \"\";\n        throw new Error(\"未取得微信确认结果\" + awaitDetail + \"。请确认页面显示微信已确认后再返回\");\n    }\n    var wxCode = String(new Packages.java.lang.String(j.base64DecodeToByteArray(encodedCode), \"UTF-8\"));\n    if (!wxCode) throw new Error(\"微信确认结果为空\");\n    j.longToast(\"已取得微信确认,正在换取正式登录参数...\");\n\n    var loginTimestamp = Number(Packages.java.lang.System.currentTimeMillis());\n    var loginRandom = state.random.nextInt(1000);\n    var loginBody = {\n        appFirstInstall: 0,\n        code: wxCode,\n        deviceId: state.newDeviceId,\n        deviceName: \"xiaomi\",\n        deviceType: 1,\n        installId: state.installId,\n        isAutoLogout: 0,\n        isFromQrcode: 1,\n        random: loginRandom,\n        signature: wrLoginSignature(loginTimestamp, state.newDeviceId, loginRandom),\n        timestamp: loginTimestamp,\n        trackId: \"\",\n        virtualChannelId: \"\",\n        wxToken: 1\n    };\n    var loginData = wrResponseJson(\n        j.post(\"https:\/\/i.weread.qq.com\/login\", JSON.stringify(loginBody), loginHeaders, 30000),\n        \"微信读书正式登录\"\n    ).data;\n    if (loginData.vid == null || !loginData.accessToken) throw new Error(\"正式登录没有返回 vid\/accessToken\");\n    var saved = wrSaveQrLogin(j, src, input, state, userAgent, loginData);\n    var message = \"微信扫码登录成功,正式 APP 登录态和 refreshToken 已保存\";\n    j.upLoginData(wrMap({vid:saved.vid, accessToken:saved.accessToken, \"User-Agent\":saved[\"User-Agent\"],\n        \"微信扫码登录\":\"微信扫码登录成功\"}));\n    try { j.toast(message); } catch (ignoreShortToast) {}\n    j.longToast(message);\n    return message;\n}\nfunction wrSaveManualLogin(j, src, input) {\n    var vid = wrInputValue(input, \"vid\");\n    var accessToken = wrInputValue(input, \"accessToken\");\n    var userAgent = wrInputValue(input, \"User-Agent\") || WR_DEFAULT_USER_AGENT;\n    if (!vid || !accessToken) throw new Error(\"抓包登录请填写 vid 和 accessToken;也可以改用游客或微信扫码登录\");\n    var saved = {\n        \"vid\": vid,\n        \"accessToken\": accessToken,\n        \"User-Agent\": userAgent,\n        \"小说章节书评入口\": wrInputValue(input, \"小说章节书评入口\") || wrValue(src, \"小说章节书评入口\") || \"关闭\",\n        \"段评开关\": wrInputToggle(input, src, \"段评开关\", \"关闭\"),\n        \"章评开关\": wrInputToggle(input, src, \"章评开关\", \"关闭\"),\n        \"书评开关\": wrInputToggle(input, src, \"书评开关\", \"关闭\"),\n        \"作者评论开关\": wrInputToggle(input, src, \"作者评论开关\", \"关闭\"),\n        \"_wrLoginMode\": \"capture\"\n    };\n    var hiddenNames = [\"_wrOldDeviceId\", \"_wrInstallId\", \"_wrNewDeviceId\", \"_wrRefreshToken\",\n        \"_wrGuestVid\", \"_wrGuestAccessToken\", \"_wrCaptchaTicket\", \"_wrCaptchaRandstr\", \"_wrCaptchaSavedAt\"];\n    for (var i = 0; i < hiddenNames.length; i++) {\n        var hiddenValue = wrInputValue(input, hiddenNames[i]) || wrValue(src, hiddenNames[i]);\n        if (hiddenValue) saved[hiddenNames[i]] = hiddenValue;\n    }\n    wrApplyCommentPreferences(input, src, saved);\n    src.putLoginInfo(JSON.stringify(saved));\n    j.upLoginData(wrMap(saved));\n    return wrCheckLogin(j, src);\n}\n\n",
    "lastUpdateTime": "1786458709601",
    "loginUi": "[{\"name\":\"游客登录(需验证码)\",\"type\":\"button\",\"action\":\"guestLogin()\",\"style\":{\"layout_flexGrow\":1,\"layout_flexBasisPercent\":0.47}},{\"name\":\"微信扫码登录\",\"type\":\"button\",\"action\":\"qrLogin()\",\"style\":{\"layout_flexGrow\":1,\"layout_flexBasisPercent\":0.47}},{\"name\":\"APP 10.2.1 抓包参数说明\",\"type\":\"button\",\"action\":\"showCaptureHelp()\",\"style\":{\"layout_flexGrow\":1,\"layout_flexBasisPercent\":1}},{\"name\":\"vid\",\"type\":\"text\"},{\"name\":\"accessToken\",\"type\":\"password\"},{\"name\":\"User-Agent\",\"type\":\"text\",\"default\":\"WeRead\/10.2.1 WRBrand\/xiaomi Dalvik\/2.1.0 (Linux; U; Android 16; 2509FPN0BC Build\/BP2A.250605.031.A3)\"},{\"name\":\"抓包参数登录并验证\",\"type\":\"button\",\"action\":\"checkLogin()\",\"style\":{\"layout_flexGrow\":1,\"layout_flexBasisPercent\":1}},{\"name\":\"切换小说发现\",\"type\":\"button\",\"action\":\"setNovelExplore()\",\"style\":{\"layout_flexGrow\":1,\"layout_flexBasisPercent\":0.47}},{\"name\":\"切换听书发现\",\"type\":\"button\",\"action\":\"setAudioExplore()\",\"style\":{\"layout_flexGrow\":1,\"layout_flexBasisPercent\":0.47}},{\"name\":\"评论设置(开关自动保存并刷新)\",\"type\":\"button\",\"action\":\"saveCommentSettings()\",\"style\":{\"layout_flexGrow\":1,\"layout_flexBasisPercent\":1}},{\"name\":\"段评开关\",\"type\":\"toggle\",\"chars\":[\"🔳\",\"✅\"],\"default\":\"🔳\",\"action\":\"saveCommentSettings()\",\"style\":{\"layout_flexGrow\":1,\"layout_flexBasisPercent\":0.31}},{\"name\":\"章评开关\",\"type\":\"toggle\",\"chars\":[\"🔳\",\"✅\"],\"default\":\"🔳\",\"action\":\"saveCommentSettings()\",\"style\":{\"layout_flexGrow\":1,\"layout_flexBasisPercent\":0.31}},{\"name\":\"书评开关\",\"type\":\"toggle\",\"chars\":[\"🔳\",\"✅\"],\"default\":\"🔳\",\"action\":\"saveCommentSettings()\",\"style\":{\"layout_flexGrow\":1,\"layout_flexBasisPercent\":0.31}},{\"name\":\"◎ 半屏高度\",\"type\":\"text\",\"default\":\"88\",\"style\":{\"layout_flexGrow\":1,\"layout_flexBasisPercent\":0.47}},{\"name\":\"◎ 段评颜色\",\"type\":\"text\",\"default\":\"#23745B\",\"style\":{\"layout_flexGrow\":1,\"layout_flexBasisPercent\":0.47}},{\"name\":\"◎ 段评美化\",\"type\":\"select\",\"chars\":[\"美化\",\"默认\",\"深色\"],\"default\":\"美化\",\"style\":{\"layout_flexGrow\":1,\"layout_flexBasisPercent\":0.47}},{\"name\":\"◎ 模板\",\"type\":\"select\",\"chars\":[\"起点\",\"样式一\",\"样式二\",\"样式三\"],\"default\":\"起点\",\"style\":{\"layout_flexGrow\":1,\"layout_flexBasisPercent\":0.47}},{\"name\":\"◎ 大小\",\"type\":\"select\",\"chars\":[\"TEXT\",\"FULL\"],\"default\":\"TEXT\",\"style\":{\"layout_flexGrow\":1,\"layout_flexBasisPercent\":0.47}},{\"name\":\"自定义气泡\",\"type\":\"button\",\"action\":\"saveCommentSettings()\",\"style\":{\"layout_flexGrow\":1,\"layout_flexBasisPercent\":1}},{\"name\":\"◎ 气泡一\",\"type\":\"text\",\"default\":\"\",\"style\":{\"layout_flexGrow\":1,\"layout_flexBasisPercent\":1}},{\"name\":\"◎ 气泡二\",\"type\":\"password\",\"default\":\"\",\"style\":{\"layout_flexGrow\":1,\"layout_flexBasisPercent\":1}},{\"name\":\"◎ 气泡三\",\"type\":\"password\",\"default\":\"\",\"style\":{\"layout_flexGrow\":1,\"layout_flexBasisPercent\":1}},{\"name\":\"保存评论设置并刷新正文\",\"type\":\"button\",\"action\":\"saveCommentSettings()\",\"style\":{\"layout_flexGrow\":1,\"layout_flexBasisPercent\":1}},{\"name\":\"全部关闭评论并刷新\",\"type\":\"button\",\"action\":\"closeAllComments()\",\"style\":{\"layout_flexGrow\":1,\"layout_flexBasisPercent\":1}}]",
    "loginUrl": "@js:\nfunction wrLoginAction(name, action) {\n    try {\n        return action();\n    } catch (error) {\n        var detail = error && error.message ? String(error.message) : String(error);\n        var message = name + \"失败:\" + detail;\n        java.longToast(message);\n        return message;\n    }\n}\nfunction login() { return wrLoginAction(\"抓包参数登录\", function() { return wrSaveManualLogin(java, source, result); }); }\nfunction checkLogin() { return wrLoginAction(\"抓包参数登录\", function() { return wrSaveManualLogin(java, source, result); }); }\nfunction guestLogin() { return wrLoginAction(\"游客登录\", function() { return wrGuestLogin(java, source, result); }); }\nfunction qrLogin() { return wrLoginAction(\"微信扫码登录\", function() { return wrQrLogin(java, source, result); }); }\nfunction showCaptureHelp() { return wrCaptureHelp(java); }\nfunction setNovelExplore() { return wrSetExploreMode(java, source, \"novel\"); }\nfunction setAudioExplore() { return wrSetExploreMode(java, source, \"audio\"); }\n\"\";\nfunction saveCommentSettings() { return wrUpdateCommentSettings(java, source, result); }\n\nfunction closeAllComments() { return wrUpdateCommentSettings(java, source, result, true); }\n",
    "respondTime": 600000,
    "ruleBookInfo": {
        "author": "$.author",
        "coverUrl": "@js:wrCoverUrl(java.getString(\"$.cover\"));",
        "init": "@js:\nvar root = JSON.parse(result);\nvar out = {};\nif (root.albumInfo) {\n    var album = root.albumInfo;\n    book.type = 32;\n    out._media = \"audio\";\n    out.name = album.name;\n    out.author = album.authorName || album.bookAuthor;\n    out.intro = album.intro;\n    out.kind = album.themeTag || \"听书\";\n    out.cover = album.cover;\n    out.wordCount = String(album.trackCount || \"\") + \"集\";\n    out.lastChapter = album.firstTrackTitle;\n    out.updateTime = album.updateTime;\n    out.tocUrl = \"https:\/\/i.weread.qq.com\/album\/list?albumId=\" + album.albumId + \"&synckey=0\";\n} else {\n    book.type = 8;\n    out._media = \"novel\";\n    out.name = root.title;\n    out.author = root.author;\n    out.intro = root.intro;\n    out.kind = root.category;\n    out.cover = root.cover;\n    out.wordCount = root.totalWords;\n    out.lastChapter = root.lastChapterIdx;\n    out.updateTime = root.updateTime;\n    var body = JSON.stringify({bookIds:[String(root.bookId)], synckeys:[0], teenmode:0});\n    out.tocUrl = \"https:\/\/i.weread.qq.com\/book\/chapterInfos,\" + JSON.stringify({\n        method:\"POST\", body:body, headers:{\"Content-Type\":\"application\/json\"}\n    });\n}\nJSON.stringify(out);",
        "intro": "$.intro",
        "kind": "$.kind",
        "lastChapter": "$.lastChapter",
        "name": "$.name",
        "tocUrl": "$.tocUrl",
        "wordCount": "$.wordCount"
    },
    "ruleContent": {
        "content": "@js:\nif (String(baseUrl).indexOf(\"\/getplayinfo?\") >= 0) {\n    var play = JSON.parse(result);\n    if (!play.url) throw new Error(\"听书接口没有返回播放地址\");\n    String(play.url);\n} else {\n    var apiUrl = String(java.hexDecodeToString(result));\n    wrDownloadChapter(java, source, apiUrl);\n}"
    },
    "ruleExplore": {
        "author": "$.author",
        "bookList": "<js>\nvar root = JSON.parse(result);\nvar list = [];\nvar seen = {};\nfunction addNovel(items) {\n    for (var i = 0; i < items.length; i++) {\n        var info = items[i].bookInfo || items[i];\n        if (!info || !info.bookId) continue;\n        list.push({_media:\"novel\", id:String(info.bookId), title:info.title, author:info.author,\n            intro:info.intro, kind:info.category, cover:info.cover, lastChapter:info.lastChapterIdx});\n    }\n}\nfunction addAudio(items) {\n    for (var i = 0; i < items.length; i++) {\n        var info = items[i].albumInfo || items[i];\n        if (!info || !info.albumId || seen[String(info.albumId)]) continue;\n        seen[String(info.albumId)] = true;\n        list.push({_media:\"audio\", id:String(info.albumId), title:info.name, author:info.authorName || info.bookAuthor,\n            intro:info.intro, kind:info.themeTag || \"听书\", cover:info.cover,\n            lastChapter:info.firstTrackTitle, trackCount:info.trackCount});\n    }\n}\nif (root.books) addNovel(root.books);\nelse {\n    var firstAudioPage = typeof page == 'undefined' || Number(page) <= 1;\n    if (root.albums) addAudio(root.albums);\n    if (root.items) addAudio(root.items);\n    if (firstAudioPage && root.ranklist && root.ranklist.itemList) addAudio(root.ranklist.itemList);\n    if (firstAudioPage && root.recommendList) addAudio(root.recommendList);\n    if (root.itemList) addAudio(root.itemList);\n    if (root.results) {\n        for (var resultIndex = 0; resultIndex < root.results.length; resultIndex++) {\n            var group = root.results[resultIndex] || {};\n            if (group.albums) addAudio(group.albums);\n        }\n    }\n}\nresult = JSON.stringify(list);\n<\/js>$[*]",
        "bookUrl": "@js:\nvar media = java.getString(\"$._media\");\nvar id = java.getString(\"$.id\");\nif (media == \"audio\") {\n    book.type = 32;\n    \"https:\/\/i.weread.qq.com\/album\/info?albumId=\" + id;\n} else {\n    book.type = 8;\n    \"https:\/\/i.weread.qq.com\/book\/info?bookId=\" + id + \"&teenmode=0\";\n}",
        "coverUrl": "@js:wrCoverUrl(java.getString(\"$.cover\"));",
        "intro": "$.intro",
        "kind": "$.kind",
        "lastChapter": "$.lastChapter",
        "name": "$.title",
        "wordCount": "$.trackCount"
    },
    "ruleSearch": {
        "author": "$.bookInfo.author",
        "bookList": "$.books[*]",
        "bookUrl": "@js:\nvar id = java.getString(\"$.bookInfo.bookId\");\n\"https:\/\/i.weread.qq.com\/book\/info?bookId=\" + id + \"&teenmode=0\";",
        "coverUrl": "@js:wrCoverUrl(java.getString(\"$.bookInfo.cover\"));",
        "intro": "$.bookInfo.intro",
        "name": "$.bookInfo.title"
    },
    "ruleToc": {
        "chapterList": "<js>\nvar root = JSON.parse(result);\nvar list = [];\nif (root.trackList) {\n    book.type = 32;\n    for (var i = 0; i < root.trackList.length; i++) {\n        var item = root.trackList[i] || {};\n        var track = item.trackInfo || {};\n        var extra = item.trackInfoExtra || {};\n        list.push({_media:\"audio\", title:track.title, updateTime:track.updateTime,\n            trackId:track.trackId, payType:track.payType, paid:extra.paid,\n            trackIdx:track.trackIdx, duration:track.duration});\n    }\n} else {\n    book.type = 8;\n    var data = root.data && root.data.length ? root.data[0] : null;\n    list = data && data.updated ? data.updated : [];\n    for (var j = 0; j < list.length; j++) {\n        list[j]._media = \"novel\";\n        list[j]._bookId = String(data.bookId);\n        list[j]._format = data.book && data.book.format ? String(data.book.format) : \"epub\";\n        list[j]._synckey = list[j]._format == \"txt\" ? \"0\" : String(data.synckey);\n    }\n}\nresult = JSON.stringify(list);\n<\/js>$[*]",
        "chapterName": "$.title",
        "chapterUrl": "@js:\nvar media = java.getString(\"$._media\");\nif (media == \"audio\") {\n    var trackId = java.getString(\"$.trackId\");\n    \"https:\/\/i.weread.qq.com\/getplayinfo?trackId=\" + java.encodeURI(trackId) + \"&type=0&quality=0\";\n} else {\n    var bookId = java.getString(\"$._bookId\");\n    var chapterUid = java.getString(\"$.chapterUid\");\n    var bookVersion = java.getString(\"$._synckey\");\n    var bookType = java.getString(\"$._format\") || \"epub\";\n    var apiUrl = \"https:\/\/i.weread.qq.com\/book\/chapterdownload?bookId=\" + java.encodeURI(bookId) +\n        \"&chapters=\" + java.encodeURI(chapterUid) +\n        \"&pf=wechat_wx-2001-android-100-weread&pfkey=pfKey&zoneId=1\" +\n        \"&bookVersion=\" + java.encodeURI(bookVersion) +\n        \"&bookType=\" + java.encodeURI(bookType) +\n        \"&quote=&release=1&stopAutoPayWhenBNE=0&preload=2&preview=0&offline=0\" +\n        \"&giftPayingCard=0&enVersion=10.2.1&modernVersion=10.2.1&teenmode=0\";\n    var encoded = String(java.base64Encode(apiUrl)).replace(\/\\s\/g, \"\");\n    \"data:application\/octet-stream;base64,\" + encoded + \",{\\\"type\\\":\\\"bin\\\"}\";\n}",
        "isPay": "@js:Number(java.getString(\"$.paid\")) > 0;",
        "isVip": "@js:\nvar media = java.getString(\"$._media\");\nmedia == \"audio\" ? (Number(java.getString(\"$.payType\")) > 0 && Number(java.getString(\"$.paid\")) == 0) : Number(java.getString(\"$.price\")) > 0;",
        "updateTime": "$.updateTime"
    },
    "searchUrl": "@js:\nwrCredentials(source);\n\"https:\/\/i.weread.qq.com\/store\/search?count=20&type=0&keyword=\" + java.encodeURI(key) +\n    \"&v=2&scope=17&maxIdx=\" + ((page - 1) * 20);",
    "weight": 0
}
广告