[{"bookSourceComment":"\/\/需要登录\n2026.11.04 发现添加","bookSourceGroup":"自己使用","bookSourceName":"📖书海阁","bookSourceType":0,"bookSourceUrl":"https:\/\/book.washai.net\/","concurrentRate":"1\/1500","customButton":false,"customOrder":1,"enabled":true,"enabledCookieJar":true,"enabledExplore":true,"eventListener":false,"exploreUrl":"@js:\nvar fx_Url = \"https:\/\/book.washai.net\";\nvar ui = [];\n\n\/\/ 读取折叠状态\nvar st = {};\ntry { st = JSON.parse(source.getVariable()); } catch (e) { st = {}; }\nvar isPhExpanded = (st.phExpanded !== false);\nvar isFlExpanded = (st.flExpanded !== false);\nvar isMyExpanded = (st.myExpanded !== false);\n\nvar push = (arr, title, surl, size) => {\n    if (title !== \"\") {\n        arr.push({ title: title, url: surl, style: { layout_flexGrow: 1, layout_flexBasisPercent: size } });\n    }\n};\n\n\/\/ ==========================================\n\/\/ 提取 Token、用户名 和 组装 Headers\n\/\/ ==========================================\nvar ck = cookie.getCookie('https:\/\/book.washai.net\/user\/read_history.html') || '';\nvar match = ck.match(\/novel_browser_token=([^;]+)\/);\nvar ck_token = match ? match[1] : \"\"; \n\nvar username = \"\";\nvar authMatch = ck.match(\/Authorization=([^;]+)\/) || ck.match(\/FrontRefreshToken=([^;]+)\/);\nif (authMatch) {\n    var parts = authMatch[1].split('.');\n    if (parts.length === 3) {\n        try {\n            var b64 = parts[1].replace(\/-\/g, '+').replace(\/_\/g, '\/');\n            while (b64.length % 4 !== 0) { b64 += '='; }\n            var payload = JSON.parse(base64Decode(b64));\n            username = payload.username || \"\";\n        } catch(e) {}\n    }\n}\nif (username === \"\") { username = \"解析失败\"; }\n\nvar my_headers = {\n    \"User-Agent\": \"Mozilla\/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/142.0.0.0 Safari\/537.36\",\n    \"Accept\": \"application\/json, text\/javascript, *\/*; q=0.01\",\n    \"X-Browser-Token\": ck_token,\n    \"X-Requested-With\": \"XMLHttpRequest\",\n    \"Referer\": \"https:\/\/book.washai.net\/\",\n    \"Origin\": \"https:\/\/book.washai.net\"\n};\nvar obj = { \"headers\": my_headers };\n\n\/\/ ==========================================\n\/\/ 1. 数据收集区\n\/\/ ==========================================\n\/\/ 排行榜\nvar phArr = [];\nvar phurl = `${fx_Url}\/book\/book_ranking.html`;\ntry {\n    var phhtml = java.ajax(phurl);\n    var phdoc = org.jsoup.Jsoup.parse(phhtml);\n    var rankNodes = phdoc.select(\"#rankType li a\");\n    if (rankNodes && rankNodes.size() > 0) {\n        for (var i = 0; i < rankNodes.size(); i++) {\n            var node = rankNodes.get(i);\n            var apiUrl = `${fx_Url}\/book\/listRank?type=${node.attr(\"data-rank-type\")}&limit=30`;\n            push(phArr, node.text(), ImRequest(apiUrl, obj), 0.3); \n        }\n    }\n} catch(e) {}\n\n\/\/ 分类 (组装成带大标题的数据结构)\nvar flArr = [];\nvar workDirection = [[\"全部\", \"\"], [\"男频\", \"0\"], [\"女频\", \"1\"]];\nvar allCats = [[\"全部\", \"\"], [\"玄幻奇幻\", \"1\"], [\"武侠仙侠\", \"2\"], [\"都市言情\", \"3\"], [\"历史军事\", \"4\"], [\"科幻灵异\", \"5\"], [\"网游竞技\", \"6\"], [\"女生频道\", \"7\"], [\"次元同人\", \"8\"]];\nvar maleCats = [[\"全部\", \"\"], [\"玄幻奇幻\", \"1\"], [\"武侠仙侠\", \"2\"], [\"都市言情\", \"3\"], [\"历史军事\", \"4\"], [\"科幻灵异\", \"5\"], [\"网游竞技\", \"6\"], [\"次元同人\", \"8\"]];\nvar femaleCats = [[\"全部\", \"\"], [\"女生频道\", \"7\"]];\n\nfor (var i = 0; i < workDirection.length; i++) {\n    var dName = workDirection[i][0];\n    var dVal  = workDirection[i][1];\n    var currentCats = allCats;\n    if (dName === \"男频\") currentCats = maleCats;\n    if (dName === \"女频\") currentCats = femaleCats;\n    \n    flArr.push({ isHeader: true, title: \"= \" + dName + \" =\", url: \"\", style: { layout_flexGrow: 1, layout_flexBasisPercent: 1 } });\n    \n    for (var j = 0; j < currentCats.length; j++) {\n        var cName = currentCats[j][0];\n        var cVal  = currentCats[j][1];\n        var dirParam = (dVal === \"\") ? \"\" : \"&workDirection=\" + dVal;\n        var catParam = (cVal === \"\") ? \"\" : \"&catId=\" + cVal;\n        var url = `${fx_Url}\/book\/searchByPage?curr={{page}}&limit=30&keyword=${dirParam}${catParam}`;\n        push(flArr, cName, ImRequest(url, obj), 0.3);\n    }\n}\n\n\/\/ 书架\nvar myArr = [];\nvar isLogin = (ck_token !== \"\");\nmyArr.push({ \n    title: isLogin ? `🔵 已登录：${username}` : \"🔴 账户状态：未登录\", \n    url: \"\", type: \"text\", style: { layout_flexBasisPercent: 1 }\n});\n\nif (isLogin) {\n    myArr.push({ \n        title: \"🚪 退出登录\", url: \"\", type: \"button\", \n        action: \"try{cookie.removeCookie('https:\/\/book.washai.net');}catch(e){}; java.toast('✅ 账户已彻底退出'); java.refreshExplore();\", \n        style: { layout_flexBasisPercent: 0.46 } \n    });\n} else {\n    myArr.push({ \n        title: \"🔑 立即登录\", url: \"\", type: \"button\", \n        action: \"java.startBrowserAwait('https:\/\/book.washai.net\/user\/login.html', '请登录后返回'); java.refreshExplore();\", \n        style: { layout_flexBasisPercent: 0.46 } \n    });\n}\nvar sjurl = `${fx_Url}\/user\/listBookShelfByPage?curr={{page}}&limit=10&sortBy=updatetime&order=desc\/read_history.html`;\npush(myArr, \"书架\", ImRequest(sjurl, obj), 0.46);\nmyArr.push({ \n    title: \"🍪 查看Cookie\", url: \"\", type: \"button\", \n    action: \"var ck = cookie.getCookie('https:\/\/book.washai.net\/user\/read_history.html') || '未获取到Cookie'; java.longToast('当前书架Cookie:\\\\n' + ck);\", \n    style: { layout_flexBasisPercent: 0.46 } \n});\nmyArr.push({ \n    title: \"ℹ️ 关于\", url: \"\", type: \"button\", \n    action: \"var comment = source.bookSourceComment;java.longToast(comment);\", \n    style: { layout_flexBasisPercent: 0.46 } \n});\n\n\n\/\/ ==========================================\n\/\/ 3. 组装渲染 (各管各的，互不干涉)\n\/\/ ==========================================\n\n\/\/ ① 我的模块 (两列排版，单独补全)\nui.push({ \n    title: isMyExpanded ? \"▼ 👤 我的 (点击收起)\" : \"▶ 👤 我的 (点击展开)\", \n    url: \"\", type: \"button\", \n    action: \"(function(){var c={};try{c=JSON.parse(source.getVariable())}catch(e){};c.myExpanded=(c.myExpanded===false?true:false);source.setVariable(JSON.stringify(c));})();java.refreshExplore()\", \n    style: { layout_flexGrow: 1, layout_flexBasisPercent: 1 } \n});\nif (isMyExpanded) {\n    myArr.forEach(function(item) { ui.push(item); });\n    if (myArr.length % 2 !== 1) {\n        ui.push({ title: \"　\", url: \"\", style: { layout_flexBasisPercent: 0.19} });\n    }\n}\n\n\/\/ ② 排行榜模块 (三列排版，单独补全)\nui.push({ \n    title: isPhExpanded ? \"▼ 🔥 排行榜 (点击收起)\" : \"▶ 🔥 排行榜 (点击展开)\", \n    url: \"\", type: \"button\", \n    action: \"(function(){var c={};try{c=JSON.parse(source.getVariable())}catch(e){};c.phExpanded=(c.phExpanded===false?true:false);source.setVariable(JSON.stringify(c));})();java.refreshExplore()\", \n    style: { layout_flexGrow: 1, layout_flexBasisPercent: 1 } \n});\nif (isPhExpanded) {\n    phArr.forEach(function(item) {\n        ui.push({ title: item.title, url: item.url, type: item.type, action: item.action, style: { layout_flexBasisPercent: 0.3 } });\n    });\n    if (phArr.length % 3 !== 0) {\n        ui.push({ title: \"　\", url: \"\", style: { layout_flexBasisPercent: 0.00001 }});\n    }\n}\n\n\/\/ ③ 分类模块 (三列排版+大标题，单独补全)\nui.push({ \n    title: isFlExpanded ? \"▼ 📚 分类 (点击收起)\" : \"▶ 📚 分类 (点击展开)\", \n    url: \"\", type: \"button\", \n    action: \"(function(){var c={};try{c=JSON.parse(source.getVariable())}catch(e){};c.flExpanded=(c.flExpanded===false?true:false);source.setVariable(JSON.stringify(c));})();java.refreshExplore()\", \n    style: { layout_flexGrow: 1, layout_flexBasisPercent: 1 } \n});\nif (isFlExpanded) {\n    var tempCount = 0; \/\/ 单独给小分类计数\n    flArr.forEach(function(item) {\n        if (item.isHeader) {\n            \/\/ 遇到大标题，如果上一排小按钮没满，先补位\n            if (tempCount > 0 && tempCount % 3 !== 0) {\n                ui.push({ title: \"　\", url: \"\", style: { layout_flexBasisPercent: 0.1} });\n            }\n            tempCount = 0; \n            \/\/ 推入大标题\n            ui.push({ title: item.title, url: \"\", style: { layout_flexGrow: 1, layout_flexBasisPercent: 1 } });\n        } else {\n            tempCount++;\n            \/\/ 推入小分类\n            ui.push({ title: item.title, url: item.url, style: { layout_flexBasisPercent: 0.3 } });\n        }\n    });\n    \/\/ 收尾补位\n    if (tempCount > 0 && tempCount % 3 !== 1) {\n        ui.push({ title: \"　\", url: \"\", style: { layout_flexBasisPercent: 0.00001} });\n    }\n}\n\n\/\/ 输出\nJSON.stringify(ui);","jsLib":"\/\/JSON安全解析\nfunction ImJson(input) {\n    if (input && typeof input === \"object\") return input;\n    return JSON.parse(String(input || \"{}\"));\n}\n\n\/\/Legado ajax请求参数拼接工具（对齐lmRequest标准模板）\nfunction ImRequest(url, options) {\n    return String(url) + \",\" + JSON.stringify(options || {});\n}\n\n\/\/ 1. 字节数组 -> Hex\nfunction bytesToHex(bytes) {\n    if (!bytes) return \"\";\n    try {\n        var sb = new java.lang.StringBuilder();\n        for(var i = 0; i < bytes.length; i++){\n            var b = bytes[i] & 0xff;\n            var hexStr = java.lang.Integer.toHexString(b);\n            if(hexStr.length === 1){\n                sb.append(\"0\");\n            }\n            sb.append(hexStr);\n        }\n        return sb.toString();\n    } catch(e) { return \"\"; }\n}\n\n\/\/ 2. Hex -> 字节数组\nfunction hexToBytes(hex) {\n    if (!hex || hex.length % 2 !== 0) return null;\n    try {\n        var len = hex.length;\n        var bytes = new Array(len \/ 2);\n        for (var i = 0; i < len; i += 2) {\n            bytes[i \/ 2] = parseInt(hex.substring(i, i + 2), 16);\n        }\n        return bytes;\n    } catch(e) { return null; }\n}\n\n\/\/ 3. 字节数组 -> 普通字符串 (UTF-8)\nfunction bytesToString(bytes) {\n    if (!bytes) return \"\";\n    try {\n        return new java.lang.String(bytes, \"UTF-8\");\n    } catch(e) { return \"\"; }\n}\n\n\/\/ 4. 字节数组 -> Base64\nfunction encodeBase64(bytes) {\n    if (!bytes) return \"\";\n    try {\n        if (typeof android !== 'undefined' && android.util && android.util.Base64) {\n            return android.util.Base64.encodeToString(bytes, 0).replace(\/\\n\/g, \"\");\n        }\n        if (typeof java !== 'undefined' && java.util && java.util.Base64) {\n            return java.util.Base64.getEncoder().encodeToString(bytes);\n        }\n    } catch(e) {}\n    return bytesToHex(bytes);\n}\n\n\/\/ 5. 字符串 Base64 解码 (直接传 Base64 字符串，返回明文)\nfunction decodeB64(str) {\n    if (!str) return \"\";\n    try {\n        var b64 = str.replace(\/-\/g, '+').replace(\/_\/g, '\/');\n        while (b64.length % 4 !== 0) { b64 += '='; }\n        var byteArr = java.base64DecodeToByteArray(b64);\n        return bytesToString(byteArr);\n    } catch(e) {\n        return \"\";\n    }\n}\n\n\/\/ 6. JWT 密文 -> 明文 JSON 对象\nfunction parseJwt(jwtStr) {\n    if (!jwtStr) return null;\n    var parts = jwtStr.split('.');\n    if (parts.length === 3) {\n        var plainText = decodeB64(parts[1]);\n        if (plainText) {\n            try {\n                return JSON.parse(plainText);\n            } catch(e) {}\n        }\n    }\n    return null;\n}\n\n\/\/ Base64解码\n\/\/ 纯 JS 原生 Base64 解码（不依赖 Java，完美支持中文）\nfunction base64Decode(s) {\n    const base64Table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+\/';\n    s = String(s).replace(\/=+$\/, '');\n    let out = '';\n    let bits = 0;\n    let value = 0;\n    for (let i = 0; i < s.length; i++) {\n        let idx = base64Table.indexOf(s[i]);\n        if (idx === -1) continue;\n        value = (value << 6) | idx;\n        bits += 6;\n        if (bits >= 8) {\n            bits -= 8;\n            \/\/ 先把字节推入数组\n            out += '%' + ('00' + ((value >> bits) & 0xFF).toString(16)).slice(-2);\n        }\n    }\n    try {\n        \/\/ 用 decodeURIComponent 把十六进制转回正常中文\n        return decodeURIComponent(out);\n    } catch (e) {\n        return out; \/\/ 如果解析失败，回退原始结果\n    }\n}\n\n\/\/ 纯 JS 原生 Base64 加密（不依赖 Java，完美支持中文）\nfunction base64Encode(str) {\n    if (!str) return \"\";\n    \n    \/\/ 1. 先转 UTF-8 格式（防止中文乱码）\n    var utf8Str = encodeURIComponent(String(str)).replace(\/%([0-9A-F]{2})\/g, function(match, p1) {\n        return String.fromCharCode('0x' + p1);\n    });\n    \n    const base64Table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+\/';\n    let out = '';\n    let i = 0;\n    \n    \/\/ 2. 核心算法：按 6 位分组转字符\n    while (i < utf8Str.length) {\n        let chr1 = utf8Str.charCodeAt(i++);\n        let chr2 = utf8Str.charCodeAt(i++);\n        let chr3 = utf8Str.charCodeAt(i++);\n        \n        let enc1 = chr1 >> 2;\n        let enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);\n        let enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);\n        let enc4 = chr3 & 63;\n        \n        \/\/ 处理剩余字符（不足3个字节）\n        if (isNaN(chr2)) {\n            enc3 = enc4 = 64;\n        } else if (isNaN(chr3)) {\n            enc4 = 64;\n        }\n        \n        out += base64Table.charAt(enc1) + base64Table.charAt(enc2) +\n               (enc3 === 64 ? '=' : base64Table.charAt(enc3)) +\n               (enc4 === 64 ? '=' : base64Table.charAt(enc4));\n    }\n    \n    return out;\n}","lastUpdateTime":"1793843960702","loginUrl":"https:\/\/book.washai.net\/user\/login.html","respondTime":180000,"ruleBookInfo":{"author":"@css:a#bookAuthor@text","coverUrl":"@css:img#bookCoverImg@src","intro":"@css:meta[name='description']@content","kind":"@css:div.nav_sub a:eq(1)@text","lastChapter":"@css:a#lastIndexLink@text","name":"@css:h1#bookName@text","tocUrl":"@css:a#allIndexLink@href","wordCount":"@css:em#bookWordCount@text"},"ruleContent":{"content":"div.readChapterAppendBody p@html"},"ruleExplore":{"author":"$.authorName","bookList":"$.data.list[*]&&$.data","bookUrl":"https:\/\/book.washai.net\/book\/{{$.id||$.bookId}}.html","coverUrl":"https:\/\/book.washai.net{{$.picUrl}}","intro":"{{$.bookDesc}}\n{{$.lastIndexUpdateTime}}","kind":"$.catName&&$.bookStatusText","lastChapter":"$.lastIndexName","name":"$.bookName","wordCount":"$.wordCount"},"ruleSearch":{"author":"@css:p.classBookAuthor>a@text","bookList":"@css:#bookList>article","bookUrl":"@css:h3.classBookTitle>a@href","coverUrl":"@css:span.classBookCover img@src","intro":"@css:p.classBookDesc@text","kind":"@css:a.classCoverCat@text","lastChapter":"@css:p.classBookChapter>a@text","name":"@css:h3.classBookTitle>a@text","wordCount":"@css:p.classBookMeta@text##^[^·]*·\\s*([0-9.]+万字).*##$1###"},"ruleToc":{"chapterList":"@js:\nvar list = [];\nvar base = String(baseUrl);\nvar html = java.ajax(base);\nvar doc = org.jsoup.Jsoup.parse(html);\nvar lis = doc.select(\"ul#bookIndexList > li\");\nvar domain = \"https:\/\/book.washai.net\";\nvar href;\nfor (var i = 0; i < lis.size(); i++) {\n    var li = lis.get(i);\n    if (li.hasClass(\"catalogVolumeHeading\") || li.select(\"h4\").size() > 0) {\n        var t = String(li.select(\"h4\").text()).trim();\n        var name = t || \"正文\";\n        list.push({\n            text: \"🏮〖\" + name + \"〗🏮\",\n            href: href,\n            volume: true\n        });\n    } \n    \/\/ 2. 识别章节\n    else {\n        var a = li.select(\"a\").first();\n        if (!a) continue;\n        var rawText = String(a.text()).trim();\n        href = String(a.attr(\"href\") || \"\");\n        if (!rawText || !href) continue;\n        if (href.indexOf(\"http\") !== 0 && href.indexOf(\"\/\/\") !== 0) {\n            href = domain + (href.charAt(0) === \"\/\" ? href : \"\/\" + href);\n        }\n\n        var symbol = rawText.indexOf(\"免费\") !== -1 ? \"📝 \" : \"🔒 \";\n        var cleanName = rawText.replace(\/\\[免费\\]\/g, \"\")\n                               .replace(\/\\[VIP\\]\/gi, \"\")\n                               .replace(\/免费\/g, \"\")\n                               .replace(\/VIP\/gi, \"\")\n                               .trim();\n\n        \/\/ 推入章节\n        list.push({ text: symbol + cleanName, href: href });\n    }\n}\nlist;","chapterName":"text","chapterUrl":"href","isVolume":"volume","nextTocUrl":"\/\/*[@id=\"indexPager\"]\/\/a[contains(text(),\"下一页\")]\/@href"},"searchUrl":"\/book\/bookclass.html?k={{key}}&page={{page}}","weight":0}]