📖书海阁
https://book.washai.net/
zhangyiqiu819 (3655)11/05 09:59
添加发现
{
"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
}