哔咔漫画〔自包含〕
https://picaapi.picacomic.com##防同
laizhiyu (6509)10/24 22:56
测试软件:sigma,Legado-team其他自测
AI制作
参考了XH大佬的源,他那个我用不了,所以自己用做了个,很久远的网站了。
需要登录才能用
{
"bookSourceComment": "\/\/ ===== 哔咔漫画 自包含书源 v84 =====\n\/\/ 官方 App 接口 picaapi.picacomic.com,\n\/\/ 签名:HMAC-SHA256(path+time+nonce+method+apiKey, secret),raw 转小写\n\/\/\n\/\/ 【重要】本站已强制登录:未登录时连 categories 都返回 401 error 1005\n\/\/ 首次使用:长按书源→登录→填账号密码→点「🔑登录」\n\/\/\n\/\/ 【v84 新增 1:封面修复】\n\/\/ 根因:封面走 Glide(OkHttpStreamFetcher → AnalyzeUrl.getGlideUrl),\n\/\/ getGlideUrl 只保留 url+header,dnsIp 选项被丢弃,\n\/\/ 所以正文能显示而封面全灰;官方 storage-b 域名在部分网络下 IPv6 优先且不可达。\n\/\/ 方案:把 tobeimg 的 base64 解出真实 \/static\/ 路径(避开 302 跳 img.picacomic.com),\n\/\/ 再用 http:\/\/<CF-IP><path> + {\"headers\":{\"Host\":\"storage-b.picacomic.com\"}} 直连,\n\/\/ Host 头在 Glide\/OkHttp 链路中保留(BridgeInterceptor 只在缺失时补 Host),\n\/\/ 不依赖系统 DNS;登录头只发给同站,不会外泄。\n\/\/ 三种封面模式可切换(登录面板「封面模式」):\n\/\/ 🚀IP直连 = http:\/\/<IP>+Host 头(默认,抗 IPv6\/DNS 问题)\n\/\/ 🌐域名直连 = https:\/\/storage-b...(网络正常时用)\n\/\/ 🪞镜像站 = 参考源同款 s3.go2778.com\n\/\/\n\/\/ 【v84 新增 2:源站更新 + 选源站】\n\/\/ 「🔄源站更新」= 拉官方 http:\/\/68.183.234.72\/init,取 addresses 存 picab_official_ips,\n\/\/ 按当前线路重建 IP 池 picab_pool,并刷新发现页签名。\n\/\/ 「①官方init \/ ②Cloudflare \/ ③系统DNS」= 切换线路:\n\/\/ ①官方init = 官方 init IP 优先 + CF 池兜底\n\/\/ ②Cloudflare = 10 个实测可连通 CF IP\n\/\/ ③系统DNS = 不设 dnsIp,走系统解析(IPv6 正常时用)\n\/\/ 「📋查看当前」= 显示当前线路 \/ IP 池 \/ 官方 init \/ 封面模式。\n\/\/ 线路存 picab_station(0\/1\/2),池存 picab_pool(\"none\"=系统DNS),删书源会清空。\n\/\/\n\/\/ 【正文图片】src 自带 10 个 Cloudflare IPv4 联合池并按图轮换首选顺序;\n\/\/ Legado 的 retry 只重试非 2xx,对 IOException 无效。\n\/\/ 旧书修复:长按书籍 → 删除 → 重新搜索加入(正文缓存按书重建)。\n\/\/\n\/\/ 【已知遗留】check_source 的域名可访问性探测与 debug_source 的首次裸请求走系统 DNS,\n\/\/ IPv6 超时会显示「网站失效」——这是校验器\/调试器限制,\n\/\/ 真实使用(搜索\/详情\/目录\/正文\/发现页)走书源规则,正常。\n",
"bookSourceGroup": "漫画,AI,R18",
"bookSourceName": "哔咔漫画〔自包含〕",
"bookSourceType": 2,
"bookSourceUrl": "https:\/\/picaapi.picacomic.com##防同",
"bookUrlPattern": ".*:\\\/\\\/.*\\\/comics\\\/[a-z0-9]+",
"concurrentRate": "1\/1200",
"customButton": false,
"customOrder": 1,
"enabled": true,
"enabledCookieJar": false,
"enabledExplore": true,
"eventListener": false,
"exploreUrl": "<js>\nvar _out = [];\nvar _at = String.fromCharCode(64,106,115,58);\nvar _sg = '(function(){ var K=\"C69BAF41DA5ABD1FFEDC6D2FEA56B\"; var S=\"~d}$Q7$eIni=V)9\"+String.fromCharCode(92)+\"RK\/P.RM4;9[7|@\/CA}b~OW!3?EV\"+String.fromCharCode(96,58,60,62)+\"M7pddUBL5n|0\/*Cn\"; var PL=\"\"; try{PL=String(source.get(\"picab_pool\")||\"\");}catch(e0){} if(PL===\"none\"){PL=\"\";}else if(!PL){PL=\"172.67.194.19,172.67.80.1,104.21.235.3,104.21.235.4,104.21.91.145,104.19.53.76,104.21.80.1,104.21.81.1,104.16.0.1,104.17.0.1\";} var pre=String(result); var a=pre.substring(pre.indexOf(\":\/\/\")+3); var p=a.substring(a.indexOf(\"\/\")); if(p.charAt(0)===\"\/\"){p=p.substring(1);} var ts=String(Math.floor(Date.now()\/1000)); var nc=\"\"; for(var i=0;i<32;i++)nc+=\"0123456789abcdef\".charAt(Math.floor(Math.random()*16)); var raw=(p+ts+nc+\"GET\"+K).toLowerCase(); var m=Packages.javax.crypto.Mac.getInstance(\"HmacSHA256\"); m.init(new Packages.javax.crypto.spec.SecretKeySpec(new Packages.java.lang.String(S).getBytes(\"UTF-8\"),\"HmacSHA256\")); var b=m.doFinal(new Packages.java.lang.String(raw).getBytes(\"UTF-8\")); var sg=\"\"; for(var i=0;i<b.length;i++){var x=b[i]&0xff;sg+=(x<16?\"0\":\"\")+x.toString(16);} var h={\"User-Agent\":\"okhttp\/3.8.1\",\"accept\":\"application\/vnd.picacomic.com.v1+json\",\"api-key\":K,\"app-channel\":\"2\",\"app-version\":\"2.2.1.2.3.3\",\"app-uuid\":\"defaultUuid\",\"app-platform\":\"android\",\"app-build-version\":\"44\",\"image-quality\":\"original\",\"time\":ts,\"nonce\":nc,\"signature\":sg}; var au=\"\"; try{au=String(source.get(\"picab_token\"));}catch(e){} if(au&&au!==\"null\"&&au!==\"\"){h[\"authorization\"]=au;} var opt={method:\"GET\",timeout:25000,retry:0,headers:h}; if(PL){opt.dnsIp=PL;} return \"https:\/\/picaapi.picacomic.com\/\"+p+\",\"+JSON.stringify(opt); })()';\nfunction _mk(path) {\n return \"https:\/\/picaapi.picacomic.com\/\" + path + _at + _sg;\n}\nfunction push(title, path) {\n var t = title;\n try {\n var st = String(source.get(\"picaTC\") || \"1\");\n if (st === \"1\") { t = java.t2s(t); }\n } catch (e0) {}\n _out.push({ title: t, url: _mk(path),\n style: { layout_flexGrow: 1, layout_flexBasisPercent: 0.25 } });\n}\nfunction group(title) {\n _out.push({ title: title, url: \"\",\n style: { layout_flexGrow: 1, layout_flexBasisPercent: 1 } });\n}\nfunction sortName() {\n var o = String(source.get(\"picaSort\") || \"0\");\n return ({ \"0\": \"dd\", \"1\": \"da\", \"2\": \"ld\", \"3\": \"vd\" })[o] || \"dd\";\n}\nvar _cats = JSON.parse(picac_get(\"categories\", source, java)).data.categories || [];\ngroup(\"▂▂ 分 类 ▂▂\");\n_cats.forEach(function (c) {\n if (c.isWeb) { return; }\n push(c.title, \"comics?page=1&c=\" + encodeURIComponent(c.title) + \"&s=\" + sortName());\n});\ngroup(\"▂▂ 排 行 榜 ▂▂\");\npush(\"24小时热榜\", \"comics\/leaderboard?tt=H24&ct=VC\");\npush(\"本周热榜\", \"comics\/leaderboard?tt=D7&ct=VC\");\npush(\"本月热榜\", \"comics\/leaderboard?tt=D30&ct=VC\");\ngroup(\"▂▂ 全 站 排 序 ▂▂\");\npush(\"最新发布\", \"comics?page=1&s=dd\");\npush(\"最早发布\", \"comics?page=1&s=da\");\npush(\"最多爱心\", \"comics?page=1&s=ld\");\npush(\"最多观看\", \"comics?page=1&s=vd\");\ngroup(\"▂▂ 更 多 ▂▂\");\npush(\"🎲随机漫画\", \"comics\/random\");\npush(\"❤我的收藏\", \"users\/favourite?s=\" + sortName() + \"&page=1\");\npush(\"📚本子馆藏\", \"collections\");\npush(\"👑骑士榜\", \"comics\/knight-leaderboard\");\nString(JSON.stringify(_out));\n<\/js>",
"header": "{\n \"User-Agent\": \"okhttp\/3.8.1\",\n \"accept\": \"application\/vnd.picacomic.com.v1+json\",\n \"api-key\": \"C69BAF41DA5ABD1FFEDC6D2FEA56B\",\n \"app-channel\": \"2\",\n \"app-version\": \"2.2.1.2.3.3\",\n \"app-uuid\": \"defaultUuid\",\n \"app-platform\": \"android\",\n \"app-build-version\": \"44\",\n \"image-quality\": \"original\"\n}",
"jsLib": "\/\/ ===== 哔咔漫画 自包含库 (v84) =====\n\/\/ 作用域铁律:jsLib 在独立 ScriptBindings 求值,只有函数定义被 chainTo 共享;\n\/\/ source\/java\/baseUrl\/result 在此不存在 => 一律参数注入 (SRC, JV),禁止裸引用。\n\/\/ 本次全部改名(picab_ -> picac_ \/ PICAB_ -> PICAC_),规避 Rhino SharedJsScope 编译缓存。\nvar PICA = {\n API_KEY: \"C69BAF41DA5ABD1FFEDC6D2FEA56B\",\n SECRET: \"~d}$Q7$eIni=V)9\" + String.fromCharCode(92) + \"RK\/P.RM4;9[7|@\/CA}b~OW!3?EV\" + String.fromCharCode(96, 58, 60, 62) + \"M7pddUBL5n|0\/*Cn\",\n BASE: \"https:\/\/picaapi.picacomic.com\/\",\n DNS_POOL: [\"172.67.194.19\", \"172.67.80.1\", \"104.21.235.3\",\n \"104.21.235.4\", \"104.21.91.145\", \"104.19.53.76\",\n \"104.21.80.1\", \"104.21.81.1\", \"104.16.0.1\", \"104.17.0.1\"],\n INIT_URL: \"http:\/\/68.183.234.72\/init\",\n MIRROR: \"https:\/\/s3.go2778.com\"\n};\n\nfunction picac_hash(s) {\n var h = 0;\n s = String(s || \"\");\n for (var i = 0; i < s.length; i++) {\n h = ((h << 5) - h + s.charCodeAt(i)) | 0;\n }\n return Math.abs(h);\n}\n\nfunction picac_bytes(str) {\n return new Packages.java.lang.String(str).getBytes(\"UTF-8\");\n}\n\nfunction picac_hmac(raw, key) {\n var m = Packages.javax.crypto.Mac.getInstance(\"HmacSHA256\");\n m.init(new Packages.javax.crypto.spec.SecretKeySpec(picac_bytes(key), \"HmacSHA256\"));\n var b = m.doFinal(picac_bytes(raw));\n var s = \"\";\n for (var i = 0; i < b.length; i++) {\n var x = b[i] & 0xff;\n s += (x < 16 ? \"0\" : \"\") + x.toString(16);\n }\n return s;\n}\n\nfunction picac_nonce() {\n var s = \"\";\n for (var i = 0; i < 32; i++) {\n s += \"0123456789abcdef\".charAt(Math.floor(Math.random() * 16));\n }\n return s;\n}\n\nfunction picac_token(SRC) {\n try {\n var h = String(SRC.getLoginHeader() || \"\");\n if (h && h !== \"null\") {\n var m = h.match(new RegExp('\"authorization\"\\\\s*:\\\\s*\"([^\"]+)\"'));\n if (m) { return m[1]; }\n return h;\n }\n } catch (e0) {}\n try { return String(SRC.get(\"picab_token\") || \"\"); } catch (e1) { return \"\"; }\n}\n\nfunction picac_official(SRC) {\n try { return String(SRC.get(\"picab_official_ips\") || \"\"); } catch (e0) { return \"\"; }\n}\n\nfunction picac_pool(SRC) {\n var p = \"\";\n try { p = String(SRC.get(\"picab_pool\") || \"\"); } catch (e0) {}\n if (p === \"none\") { return \"\"; }\n if (p) { return p; }\n return PICA.DNS_POOL.join(\",\");\n}\n\nfunction picac_stationName(idx) {\n return ({ \"0\": \"官方init\", \"1\": \"Cloudflare\", \"2\": \"系统DNS直连\" })[String(idx)] || \"未知\";\n}\n\nfunction picac_currentStation(SRC) {\n try { return String(SRC.get(\"picab_station\") || \"1\"); } catch (e0) { return \"1\"; }\n}\n\nfunction picac_coverMode(SRC) {\n try {\n var m = String(SRC.get(\"picab_cover_mode\") || \"ip\");\n return (m === \"dns\" || m === \"mirror\") ? m : \"ip\";\n } catch (e0) { return \"ip\"; }\n}\n\nfunction picac_buildPool(station, official) {\n var out = [];\n function add(ip) {\n ip = String(ip || \"\").trim();\n if (ip && ip.indexOf(\".\") > 0 && out.indexOf(ip) < 0) { out.push(ip); }\n }\n if (station === \"0\") {\n String(official || \"\").split(\",\").forEach(add);\n PICA.DNS_POOL.forEach(add);\n } else if (station === \"1\") {\n PICA.DNS_POOL.forEach(add);\n } else {\n return \"none\";\n }\n return out.join(\",\");\n}\n\nfunction picac_headers(path, method, SRC, JV) {\n var ts = String(Math.floor(Date.now() \/ 1000));\n var nonce = picac_nonce();\n var raw = (path + ts + nonce + method + PICA.API_KEY).toLowerCase();\n var sig = picac_hmac(raw, PICA.SECRET);\n var h = {\n \"User-Agent\": \"okhttp\/3.8.1\",\n \"accept\": \"application\/vnd.picacomic.com.v1+json\",\n \"api-key\": PICA.API_KEY,\n \"app-channel\": \"2\",\n \"app-version\": \"2.2.1.2.3.3\",\n \"app-uuid\": \"defaultUuid\",\n \"app-platform\": \"android\",\n \"app-build-version\": \"44\",\n \"image-quality\": \"original\",\n \"time\": ts,\n \"nonce\": nonce,\n \"signature\": sig\n };\n var tk = picac_token(SRC);\n if (tk && tk !== \"null\" && tk !== \"\") { h[\"authorization\"] = tk; }\n return h;\n}\n\nfunction picac_opt(path, opt, SRC, JV) {\n opt = opt || {};\n opt.headers = picac_headers(path, opt.method || \"GET\", SRC, JV);\n if ((opt.method || \"GET\") === \"POST\") {\n opt.headers[\"Content-Type\"] = \"application\/json; charset=UTF-8\";\n }\n var dp = picac_pool(SRC);\n if (dp && !opt.dnsIp) { opt.dnsIp = dp; }\n if (!opt.retry) { opt.retry = 4; }\n if (!opt.timeout) { opt.timeout = 25000; }\n return PICA.BASE + path + \",\" + JSON.stringify(opt);\n}\n\nfunction picac_get(path, SRC, JV) {\n var last = \"\";\n for (var i = 0; i < 3; i++) {\n last = String(JV.ajax(picac_opt(path, { method: \"GET\" }, SRC, JV)) || \"\");\n if (last.charAt(0) === \"{\") { return last; }\n if (i < 2) {\n try { Packages.java.lang.Thread.sleep(1500); } catch (e0) {}\n }\n }\n return last;\n}\n\nfunction picac_post(path, bodyObj, SRC, JV) {\n var last = \"\";\n for (var i = 0; i < 3; i++) {\n last = String(JV.ajax(picac_opt(path, { method: \"POST\", body: JSON.stringify(bodyObj) }, SRC, JV)) || \"\");\n if (last.charAt(0) === \"{\") { return last; }\n if (i < 2) {\n try { Packages.java.lang.Thread.sleep(1500); } catch (e0) {}\n }\n }\n return last;\n}\n\/\/ ---------- 封面 ----------\n\/* 解析 thumb:\n tobeimg\/<hash>\/rs:fill:W:H:0\/g:sm\/<base64>[.jpg] → base64 解出真实图片 URL\n tobs\/<file> → \/static\/<file>(去掉 tobs\/ 前缀)\n <file> → \/static\/<file>\n 返回 {host, path, raw, real, isSB} *\/\nfunction picac_parseThumb(media) {\n var out = { host: \"\", path: \"\", raw: \"\", real: \"\", isSB: false };\n if (!media) { return out; }\n var fs = String(media.fileServer || \"\").replace(new RegExp(\"\/$\"), \"\");\n var path = String(media.path || \"\").replace(new RegExp(\"^\/?\"), \"\");\n out.raw = path;\n if (!path) { return out; }\n if (path.indexOf(\"tobeimg\/\") === 0) {\n var idx = path.indexOf(\"\/g:sm\/\");\n if (idx >= 0) {\n var b64 = path.substring(idx + 6);\n var m = b64.match(new RegExp(\"^([A-Za-z0-9+\/=]+)(\\\\.[A-Za-z0-9]+)?$\"));\n if (m) {\n try {\n var pay = m[1];\n while (pay.length % 4 !== 0) { pay += \"=\"; }\n var real = String(new Packages.java.lang.String(\n Packages.android.util.Base64.decode(pay, 0), \"UTF-8\"));\n var i2 = real.indexOf(\"\/static\/\");\n if (i2 >= 0) {\n out.real = real;\n out.host = real.substring(0, i2).replace(new RegExp(\"^https?:\/\/\"), \"\");\n out.path = real.substring(i2);\n out.isSB = out.host.indexOf(\"storage-b\") >= 0;\n return out;\n }\n } catch (e0) {}\n }\n }\n }\n out.host = fs.replace(new RegExp(\"^https?:\/\/\"), \"\");\n if (path.indexOf(\"tobs\/\") === 0) { path = path.substring(5); }\n out.path = \"\/static\/\" + path;\n out.isSB = out.host.indexOf(\"storage-b\") >= 0;\n return out;\n}\n\n\/* 图片 URL(封面与正文共用一套规则)\n 关键:漫画浏览模式走 Glide → AnalyzeUrl.getGlideUrl(),\n 该方法【只保留 url + headerMap,丢弃 dnsIp】⇒\n 域名直连在 IPv6 优先的网络里必然超时。\n 所以图片 URL 必须自带 IP 直连能力:\n storage-b → http:\/\/<CF-IP><path> + {\"headers\":{\"Host\":\"storage-b.picacomic.com\"}}\n Host 头在 OkHttp BridgeInterceptor 只在缺失时补 ⇒ 保留;\n 同时对普通浏览模式(getByteArrayAwait,dnsIp 生效)也完全兼容。\n 其它域(storage1 等)真机不可达 ⇒ 回退域名直连。 *\/\nfunction picac_imgUrl(media, SRC) {\n var t = picac_parseThumb(media);\n if (!t.path) { return \"\"; }\n var mode = \"ip\";\n try { mode = picac_coverMode(SRC); } catch (e0) {}\n if (mode === \"dns\") {\n return \"https:\/\/\" + t.host + t.path;\n }\n if (t.isSB) {\n var ips = PICA.DNS_POOL;\n var ip = ips[picac_hash(t.path) % ips.length];\n return \"http:\/\/\" + ip + t.path\n + ',{\"method\":\"GET\",\"timeout\":30000,\"headers\":{\"Host\":\"' + t.host + '\"}}';\n }\n return \"https:\/\/\" + t.host + t.path;\n}\n\n\/* 封面 URL(三种模式,由 picab_cover_mode 控制):\n ip → storage-b 用 IP+Host 头;其它域域名直连\n dns → 全部域名直连\n mirror → 参考源同款镜像 s3.go2778.com(保留原始 tobeimg 路径) *\/\nfunction picac_cover(media, SRC) {\n var t = picac_parseThumb(media);\n if (!t.raw) { return \"\"; }\n var mode = picac_coverMode(SRC);\n if (mode === \"mirror\") {\n return PICA.MIRROR + \"\/static\/\" + t.raw;\n }\n if (!t.host) { return \"\"; }\n if (mode === \"dns\") {\n return \"https:\/\/\" + t.host + t.path;\n }\n if (t.isSB) {\n var ips = PICA.DNS_POOL;\n var ip = ips[picac_hash(t.path) % ips.length];\n return \"http:\/\/\" + ip + t.path\n + ',{\"method\":\"GET\",\"timeout\":20000,\"headers\":{\"Host\":\"' + t.host + '\"}}';\n }\n return \"https:\/\/\" + t.host + t.path;\n}\n\n\/* 正文图片 URL:与封面同规则(IP+Host 头),保证漫画浏览模式也能加载 *\/\nfunction picac_img(media, SRC) {\n return picac_imgUrl(media, SRC);\n}\n\nfunction picac_bookid(u) {\n var s = String(u || \"\");\n var c = s.indexOf(\",{\");\n if (c >= 0) { s = s.substring(0, c); }\n var m = s.match(new RegExp(\"comics\/([0-9a-fA-F]{24})\"));\n if (m) { return m[1]; }\n m = s.match(new RegExp(\"^([0-9a-fA-F]{24})$\"));\n return m ? m[1] : \"\";\n}\n\nfunction picac_item(c, SRC, JV) {\n if (!c) { return null; }\n return {\n name: c.title || \"\",\n author: c.author || \"\",\n cover: picac_cover(c.thumb, SRC),\n intro: \"\",\n kind: [(c.__group || \"\"),\n String(c.categories || []).replace(\/,\/g, \" \"),\n c.finished ? \"完结\" : \"连载\",\n c.likesCount ? c.likesCount + \"♥\" : \"\"].filter(Boolean).join(\" · \"),\n latest: \"\",\n word: \"\",\n url: PICA.BASE + \"comics\/\" + (c._id || \"\")\n + ',{\"method\":\"GET\",\"retry\":3,\"timeout\":20000,'\n + '\"headers\":' + JSON.stringify(picac_headers(\"comics\/\" + (c._id || \"\"), \"GET\", SRC))\n + ',\"dnsIp\":\"' + picac_pool(SRC) + '\"}'\n };\n}\n\nfunction picac_list(data) {\n var d = data || {};\n if (d.collections) {\n var flat = [];\n (d.collections || []).forEach(function (g) {\n (g.comics || []).forEach(function (c) {\n c.__group = g.title;\n flat.push(c);\n });\n });\n return flat;\n }\n var list = d.comics ? (d.comics.docs || d.comics) : (d.docs || d);\n if (!Array.isArray(list)) { list = []; }\n return list;\n}\n\nfunction picac_say(jv, msg) {\n var stamped = \"[\" + new Date().toTimeString().substring(0, 8) + \"] \" + msg;\n try { jv.upLoginData({ \"登录状态\": stamped }); } catch (e1) {}\n try { jv.toast(stamped); } catch (e2) {}\n try { jv.longToast(stamped); } catch (e3) {}\n try { jv.reLoginView(false); } catch (e4) {}\n}\nfunction PICAC_checkLogin(jv, SRC) {\n var msg = \"\";\n try {\n var txt = String(jv.ajax(picac_opt(\"users\/profile\", { method: \"GET\" }, SRC, jv)) || \"\");\n if (!txt) { msg = \"检查失败: 无响应\"; }\n else if (txt.charAt(0) !== \"{\") {\n msg = \"❌ 检查失败: 网络超时(试试切换源站)\";\n picac_say(jv, msg); return;\n } else {\n var jo = null;\n try { jo = JSON.parse(txt); } catch (e3) {}\n if (jo && jo.code === 200 && jo.data && jo.data.user) {\n var u = jo.data.user;\n msg = \"✅ 已登录: \" + (u.name || u.email || \"未知\")\n + \" (Lv.\" + (u.level || \"?\") + \")\"\n + (u.isPunched ? \" [今日已签到]\" : \" [今日未签到]\");\n } else if (jo && (jo.code === 401 || jo.code === 403)) {\n msg = \"❌ 未登录,请填账号密码后点右上角✓登录\";\n } else {\n msg = \"⚠️ 状态未知: \" + txt.substring(0, 60);\n }\n }\n } catch (e) { msg = \"检查失败: \" + e; }\n picac_say(jv, msg);\n}\n\nfunction PICAC_punch(jv, SRC) {\n var msg = \"\";\n try {\n var txt = String(jv.ajax(picac_opt(\"users\/punch-in\",\n { method: \"POST\", body: \"{}\" }, SRC, jv)) || \"\");\n if (!txt) { msg = \"签到失败: 无响应\"; }\n else if (txt.charAt(0) !== \"{\") {\n var why = txt.indexOf(\"SocketTimeout\") >= 0 ? \"网络超时\" : \"网络错误\";\n msg = \"❌ 签到失败: \" + why + \"(可试「🔄源站更新」或切换源站)\";\n picac_say(jv, msg); return;\n }\n var jo = null;\n try { jo = JSON.parse(txt); } catch (e0) {}\n if (jo && jo.code === 200 && jo.data && jo.data.res) {\n var st = String(jo.data.res.status || \"\");\n if (st === \"ok\") { msg = \"✅ 签到成功 (最近签到日 \" + (jo.data.res.punchInLastDay || \"?\") + \")\"; }\n else if (st === \"fail\") { msg = \"📌 今日已签到,无需重复\"; }\n else { msg = \"签到状态: \" + st; }\n } else if (jo && (jo.code === 401 || jo.code === 403)) {\n msg = \"❌ 未登录,请先登录再签到\";\n } else {\n msg = \"❌ 签到失败: \" + (jo && jo.message ? jo.message : txt.substring(0, 80));\n }\n } catch (e) { msg = \"签到异常: \" + e; }\n picac_say(jv, msg);\n}\n\nfunction PICAC_logout(jv, SRC) {\n try { SRC.putLoginHeader(\"\"); } catch (e0) {}\n try { SRC.put(\"picab_token\", \"\"); } catch (e5) {}\n picac_say(jv, \"已退出登录(凭据已清除)\");\n}\n\nfunction PICAC_refreshExplore(jv, SRC) {\n var msg = \"\";\n try {\n SRC.refreshExplore();\n msg = \"✅ 发现页已刷新(重新生成签名,请回到发现页)\";\n } catch (e) { msg = \"刷新失败: \" + e; }\n picac_say(jv, msg);\n}\n\n\/* 源站更新:拉官方 init 地址 → 写入 IP 池 → 刷新发现页 *\/\nfunction PICAC_updateStation(jv, SRC) {\n var msg = \"\";\n var r = \"\";\n try {\n r = String(jv.ajax(PICA.INIT_URL + \"?t=\" + String(Math.floor(Date.now() \/ 1000))) || \"\");\n } catch (e) { r = \"\"; }\n if (!r) {\n msg = \"❌ init 无响应(官方IP服务器不可达),当前源站未变更\";\n } else if (r.charAt(0) !== \"{\") {\n var reason = r.indexOf(\"SocketTimeout\") >= 0 ? \"超时\"\n : r.indexOf(\"UnknownHost\") >= 0 ? \"域名解析失败\"\n : r.indexOf(\"ConnectException\") >= 0 ? \"连接被拒\"\n : \"网络错误\";\n msg = \"❌ init \" + reason + \",当前源站未变更\";\n } else {\n try {\n var jo = JSON.parse(r);\n var ips = (jo && jo.addresses) || [];\n if (ips.length) {\n var pool = ips.join(\",\");\n SRC.put(\"picab_official_ips\", pool);\n var st = picac_currentStation(SRC);\n SRC.put(\"picab_pool\", picac_buildPool(st, pool));\n try { SRC.refreshExplore(); } catch (e2) {}\n msg = \"✅ 源站更新完成: \" + pool + \"(当前线路 \" + picac_stationName(st) + \")\";\n } else {\n msg = \"⚠️ init 未返回 IP,当前源站未变更\";\n }\n } catch (e3) { msg = \"❌ init 返回格式异常,当前源站未变更\"; }\n }\n picac_say(jv, msg);\n}\n\n\/* 选源站:0=官方init 1=Cloudflare 2=系统DNS直连 *\/\nfunction PICAC_selectStation(jv, SRC, idx) {\n idx = String(idx);\n try {\n SRC.put(\"picab_station\", idx);\n SRC.put(\"picab_pool\", picac_buildPool(idx, picac_official(SRC)));\n try { SRC.refreshExplore(); } catch (e2) {}\n var pool = picac_pool(SRC);\n picac_say(jv, \"✅ 已切换源站: \" + picac_stationName(idx)\n + (pool ? \"(池 \" + pool.split(\",\").length + \" 个IP)\" : \"(走系统DNS)\"));\n } catch (e) {\n picac_say(jv, \"切换失败: \" + e);\n }\n}\n\nfunction PICAC_showStation(jv, SRC) {\n var st = picac_currentStation(SRC);\n var pool = picac_pool(SRC);\n var off = picac_official(SRC);\n var mode = picac_coverMode(SRC);\n var mname = ({ \"ip\": \"IP直连(Host头)\", \"dns\": \"域名直连\", \"mirror\": \"镜像站\" })[mode] || mode;\n var msg = \"当前源站: \" + picac_stationName(st)\n + \"\\nIP池: \" + (pool ? pool : \"(系统DNS,不设dnsIp)\")\n + \"\\n官方init: \" + (off ? off : \"(未更新)\")\n + \"\\n封面模式: \" + mname;\n picac_say(jv, msg);\n}\n\n\/* 封面模式:ip \/ dns \/ mirror *\/\nfunction PICAC_setCoverMode(jv, SRC, mode) {\n mode = String(mode);\n try {\n if (mode !== \"ip\" && mode !== \"dns\" && mode !== \"mirror\") {\n picac_say(jv, \"❌ 未知封面模式: \" + mode);\n return;\n }\n SRC.put(\"picab_cover_mode\", mode);\n var mname = ({ \"ip\": \"IP直连(Host头)\", \"dns\": \"域名直连\", \"mirror\": \"镜像站\" })[mode];\n picac_say(jv, \"✅ 封面模式已切换: \" + mname + \"(重新搜索\/刷新详情后生效)\");\n } catch (e) {\n picac_say(jv, \"切换失败: \" + e);\n }\n}\n\nfunction PICAC_setSort(jv, SRC, idx) {\n try { SRC.put(\"picaSort\", String(idx)); } catch (e0) {}\n var names = { \"0\": \"最新发布\", \"1\": \"最早发布\", \"2\": \"最多爱心\", \"3\": \"最多观看\" };\n picac_say(jv, \"✅ 分类排序已切换: \" + names[String(idx)]);\n}\n\nfunction PICAC_setTC(jv, SRC, on) {\n try { SRC.put(\"picaTC\", on ? \"1\" : \"0\"); } catch (e0) {}\n picac_say(jv, on ? \"✅ 分类\/标签显示已切换: 简体\" : \"✅ 分类\/标签显示已切换: 繁体\");\n}\n\nfunction PICAC_diag(jv, SRC) {\n var lines = [];\n try {\n var t0 = Date.now();\n var r = String(jv.ajax(picac_opt(\"categories\", { method: \"GET\" }, SRC, jv)) || \"\");\n var ok1 = r.indexOf('\"code\"') >= 0 && r.indexOf(\"categories\") >= 0 && r.indexOf(\"200\") >= 0;\n lines.push(\"1.API签名+IP: \" + (ok1 ? \"✅\" : \"❌\") + \" \" + (Date.now() - t0) + \"ms\" + (ok1 ? \"\" : \" \" + r.substring(0, 40).replace(\/\\n\/g, \" \")));\n } catch (e) { lines.push(\"1.API签名+IP: ❌ \" + String(e).substring(0, 40)); }\n var pool = picac_pool(SRC);\n lines.push(\"2.当前源站: \" + picac_stationName(picac_currentStation(SRC)) + \" 池: \" + (pool ? pool.substring(0, 46) : \"系统DNS\"));\n try {\n var t1 = Date.now();\n var cov = picac_cover({ fileServer: \"https:\/\/storage-b.picacomic.com\",\n path: \"tobeimg\/NLrAacfzHYyka85InF9J3kEyr5-kShySh5Ztk5HbIuA\/rs:fill:300:400:0\/g:sm\/aHR0cHM6Ly9zdG9yYWdlLWIucGljYWNvbWljLmNvbS9zdGF0aWMvc3ViX3N0b3JhZ2VfMS9kNC83Mi9kNDcyYmRhYS1iZGFlLTRjNDctYmFlNS03NDM3NmZlNDI3MTIuanBn.jpg\" }, SRC);\n var r1 = String(jv.ajax(cov) || \"\");\n lines.push(\"3.封面(\" + picac_coverMode(SRC) + \"): \" + (r1.length > 10000 ? \"✅\" : \"❌\") + \" \" + (Date.now() - t1) + \"ms \" + cov.substring(0, 60));\n } catch (e) { lines.push(\"3.封面: ❌ \" + String(e).substring(0, 40)); }\n try {\n var t2 = Date.now();\n var u2 = \"https:\/\/storage-b.picacomic.com\/static\/sub_storage_1\/b4\/b5\/b4b5042d-4a5c-4cbb-8a2c-13060b05b990.jpg\";\n var r2 = String(jv.ajax(u2 + (pool ? ',{\"method\":\"GET\",\"timeout\":15000,\"dnsIp\":\"' + pool + '\"}' : \"\")) || \"\");\n lines.push(\"4.正文图(带IP): \" + (r2.length > 10000 ? \"✅\" : \"❌\") + \" \" + (Date.now() - t2) + \"ms\");\n } catch (e) { lines.push(\"4.正文图(带IP): ❌ 超时\"); }\n var msg = lines.join(\"\\n\");\n try { jv.upLoginData({ \"登录状态\": msg }); } catch (e1) {}\n try { jv.toast(msg); } catch (e2) {}\n try { jv.longToast(msg); } catch (e3) {}\n}\n",
"lastUpdateTime": "1792853819944",
"loginUi": "[{\"name\":\"账号\",\"type\":\"text\"},{\"name\":\"密码\",\"type\":\"password\"},{\"name\":\"🔑登录\",\"type\":\"button\",\"action\":\"login()\",\"style\":{\"layout_flexGrow\":1,\"layout_flexBasisPercent\":1}},{\"name\":\"①检查登录状态\",\"type\":\"button\",\"action\":\"PICAC_checkLogin(java, source)\",\"style\":{\"layout_flexGrow\":1,\"layout_flexBasisPercent\":0.45}},{\"name\":\"②每日签到\",\"type\":\"button\",\"action\":\"PICAC_punch(java, source)\",\"style\":{\"layout_flexGrow\":1,\"layout_flexBasisPercent\":0.45}},{\"name\":\"③退出登录\",\"type\":\"button\",\"action\":\"PICAC_logout(java, source)\",\"style\":{\"layout_flexGrow\":1,\"layout_flexBasisPercent\":0.45}},{\"name\":\"④刷新发现页\",\"type\":\"button\",\"action\":\"PICAC_refreshExplore(java, source)\",\"style\":{\"layout_flexGrow\":1,\"layout_flexBasisPercent\":0.45}},{\"name\":\"分类排序\",\"type\":\"text\"},{\"name\":\"最新\",\"type\":\"button\",\"action\":\"PICAC_setSort(java, source, 0)\",\"style\":{\"layout_flexGrow\":1,\"layout_flexBasisPercent\":0.25}},{\"name\":\"最早\",\"type\":\"button\",\"action\":\"PICAC_setSort(java, source, 1)\",\"style\":{\"layout_flexGrow\":1,\"layout_flexBasisPercent\":0.25}},{\"name\":\"爱心\",\"type\":\"button\",\"action\":\"PICAC_setSort(java, source, 2)\",\"style\":{\"layout_flexGrow\":1,\"layout_flexBasisPercent\":0.25}},{\"name\":\"观看\",\"type\":\"button\",\"action\":\"PICAC_setSort(java, source, 3)\",\"style\":{\"layout_flexGrow\":1,\"layout_flexBasisPercent\":0.25}},{\"name\":\"简繁显示\",\"type\":\"text\"},{\"name\":\"简体\",\"type\":\"button\",\"action\":\"PICAC_setTC(java, source, true)\",\"style\":{\"layout_flexGrow\":1,\"layout_flexBasisPercent\":0.5}},{\"name\":\"繁体\",\"type\":\"button\",\"action\":\"PICAC_setTC(java, source, false)\",\"style\":{\"layout_flexGrow\":1,\"layout_flexBasisPercent\":0.5}},{\"name\":\"源 站 管 理\",\"type\":\"text\"},{\"name\":\"🔄源站更新\",\"type\":\"button\",\"action\":\"PICAC_updateStation(java, source)\",\"style\":{\"layout_flexGrow\":1,\"layout_flexBasisPercent\":0.45}},{\"name\":\"📋查看当前\",\"type\":\"button\",\"action\":\"PICAC_showStation(java, source)\",\"style\":{\"layout_flexGrow\":1,\"layout_flexBasisPercent\":0.45}},{\"name\":\"①官方init\",\"type\":\"button\",\"action\":\"PICAC_selectStation(java, source, 0)\",\"style\":{\"layout_flexGrow\":1,\"layout_flexBasisPercent\":0.33}},{\"name\":\"②Cloudflare\",\"type\":\"button\",\"action\":\"PICAC_selectStation(java, source, 1)\",\"style\":{\"layout_flexGrow\":1,\"layout_flexBasisPercent\":0.34}},{\"name\":\"③系统DNS\",\"type\":\"button\",\"action\":\"PICAC_selectStation(java, source, 2)\",\"style\":{\"layout_flexGrow\":1,\"layout_flexBasisPercent\":0.33}},{\"name\":\"封 面 模 式\",\"type\":\"text\"},{\"name\":\"🚀IP直连\",\"type\":\"button\",\"action\":\"PICAC_setCoverMode(java, source, 'ip')\",\"style\":{\"layout_flexGrow\":1,\"layout_flexBasisPercent\":0.33}},{\"name\":\"🌐域名直连\",\"type\":\"button\",\"action\":\"PICAC_setCoverMode(java, source, 'dns')\",\"style\":{\"layout_flexGrow\":1,\"layout_flexBasisPercent\":0.34}},{\"name\":\"🪞镜像站\",\"type\":\"button\",\"action\":\"PICAC_setCoverMode(java, source, 'mirror')\",\"style\":{\"layout_flexGrow\":1,\"layout_flexBasisPercent\":0.33}},{\"name\":\"🔍一键诊断\",\"type\":\"button\",\"action\":\"PICAC_diag(java, source)\",\"style\":{\"layout_flexGrow\":1,\"layout_flexBasisPercent\":1}},{\"name\":\"登录状态\",\"type\":\"text\"}]",
"loginUrl": "\/\/ 登录:只有 loginUrl 的 login() 绑定 result(= 登录面板各输入)\nfunction login() {\n var u = String(result[\"账号\"] || \"\").replace(\/^\\s+|\\s+$\/g, \"\");\n var p = String(result[\"密码\"] || \"\").replace(\/^\\s+|\\s+$\/g, \"\");\n if (!u) { java.longToast(\"请填写账号\"); return; }\n if (!p) { java.longToast(\"请填写密码\"); return; }\n var msg = \"\";\n try {\n var path = \"auth\/sign-in\";\n var h = picac_headers(path, \"POST\", source, java);\n h[\"Content-Type\"] = \"application\/json; charset=UTF-8\";\n var opt = { method: \"POST\", body: JSON.stringify({ email: u, password: p }),\n headers: h, timeout: 30000, retry: 2 };\n var dp = picac_pool(source);\n if (dp) { opt.dnsIp = dp; }\n var resp = java.ajax(\"https:\/\/picaapi.picacomic.com\/\" + path + \",\" + JSON.stringify(opt));\n var txt = String(resp || \"\");\n var jo = null;\n try { jo = JSON.parse(txt); } catch (e0) {}\n if (jo && jo.code === 200 && jo.data && jo.data.token) {\n source.putLoginHeader(jo.data.token);\n try { source.put(\"picab_token\", jo.data.token); } catch (e5) {}\n try { source.refreshExplore(); } catch (e6) {}\n msg = \"✅ 登录成功: \" + u;\n } else {\n msg = \"❌ 登录失败: \" + (jo && jo.message ? jo.message : txt.substring(0, 80));\n }\n } catch (e) { msg = \"登录异常: \" + e; }\n try { java.upLoginData({ \"登录状态\": msg }); } catch (e1) {}\n try { java.toast(msg); } catch (e2) {}\n try { java.longToast(msg); } catch (e3) {}\n}\n",
"respondTime": 180000,
"ruleBookInfo": {
"author": "author",
"coverUrl": "cover",
"init": "<js>\nvar _id = picac_bookid(baseUrl);\nif (!_id) { throw new Error(\"invalid baseUrl: \" + baseUrl); }\nvar _j = JSON.parse(picac_get(\"comics\/\" + _id, source, java));\nif (_j.code !== 200 || !_j.data || !_j.data.comic) {\n throw new Error(\"详情获取失败: \" + (_j.message || _j.code));\n}\nvar _c = _j.data.comic;\nvar _it = {\n name: _c.title || \"\",\n author: _c.author || \"\",\n cover: picac_cover(_c.thumb, source),\n intro: _c.description || \"\",\n kind: [String(_c.categories || []).replace(\/,\/g, \" \"),\n String(_c.tags || []).replace(\/,\/g, \" \"),\n _c.chineseTeam ? \"汉化组: \" + _c.chineseTeam : \"\",\n _c.finished ? \"完结\" : \"连载\",\n _c.viewsCount ? _c.viewsCount + \"次观看\" : \"\",\n _c.likesCount ? _c.likesCount + \"人喜欢\" : \"\"].filter(Boolean).join(\"\\n\"),\n tocUrl: picac_opt(\"comics\/\" + _id + \"\/eps?page=1\", { method: \"GET\" }, source, java),\n lastChapter: \"\",\n wordCount: (_c.pagesCount || \"\") + \"P\"\n};\nbook.putVariable(\"picab_bookid\", _id);\nJSON.stringify(_it);\n<\/js>",
"intro": "intro",
"kind": "kind",
"lastChapter": "lastChapter",
"name": "name",
"tocUrl": "tocUrl",
"wordCount": "wordCount"
},
"ruleContent": {
"content": "<js>\nvar _j = JSON.parse(result);\nif (_j.code !== 200) { throw new Error(\"正文获取失败: \" + (_j.message || _j.code)); }\nvar _d = (_j.data && _j.data.pages) ? (_j.data.pages.docs || []) : [];\nvar _contentOut = \"\";\nif (!_d.length) {\n java.log(\"哔咔正文为空: \" + String(result).substring(0, 200));\n _contentOut = \"<p>正文获取失败:本书请长按→删除→重新搜索加入。<\/p>\";\n} else {\nvar _poolBase = [\n \"172.67.194.19\",\"172.67.80.1\",\"104.21.235.3\",\"104.21.235.4\",\"104.21.91.145\",\n \"104.19.53.76\",\"104.21.80.1\",\"104.21.81.1\",\"104.16.0.1\",\"104.17.0.1\"\n];\n\/* 当前源站 IP 池优先;官方 init IP 追加在前 *\/\ntry {\n var _manual = String(source.get(\"picab_pool\") || \"\");\n if (_manual === \"none\") { _manual = \"\"; }\n else if (!_manual) {\n try { _manual = String(source.get(\"picab_official_ips\") || \"\"); } catch (e1) {}\n }\n if (_manual) {\n _manual.split(\",\").forEach(function(ip) {\n ip = ip.trim(); if (ip && _poolBase.indexOf(ip) < 0) _poolBase.unshift(ip);\n });\n }\n} catch(e0) {}\n\/* 图片 URL 由 picac_img 统一生成(IP+Host 头直连),\n 不再在外层追加 dnsIp —— 漫画浏览模式走 Glide.getGlideUrl() 会丢弃 dnsIp,\n 只有 URL 里自带 IP + Host 头才能在两种模式下都加载成功。 *\/\nvar _out = _d.map(function (x, idx) {\n var u = picac_img(x.media, source);\n return '<img src=\"' + u + '\">';\n});\nif (_out.length) {\n java.log(\"PICADIAG v84 图数=\" + _out.length + \" 池数=\" + _poolBase.length + \" 首src=\" + _out[0].substring(9, 260));\n}\nvar _diag = \"<p style=\\\"font-size:11px\\\">[诊断] 图数:\" + _out.length + \" 池:\" + _poolBase.length + \" IP轮换:开<\/p>\";\n_contentOut = _out.join(\"\\n\") + \"\\n\\u200b\" + String(Math.floor(Date.now() \/ 60000)) + _diag;\n}\n_contentOut;\n<\/js>",
"imageStyle": "FULL",
"nextContentUrl": "<js>\nvar _j = JSON.parse(result);\nvar _p = (_j.data && _j.data.pages) ? _j.data.pages : null;\nvar _next = \"javascript:void(0)\";\nif (_p) {\n var _cur = (_p.page === null || _p.page === undefined) ? 1 : _p.page;\n var _total = (_p.pages === null || _p.pages === undefined) ? 1 : _p.pages;\n if (_total > 1 && _cur < _total) {\n var _ord = String(baseUrl || \"\").match(new RegExp(\"order\/(\\\\d+)\"));\n if (_ord) {\n var _bid = picac_bookid(baseUrl);\n var _at = String.fromCharCode(64,106,115,58);\n var _sg = '(function(){ var K=\"C69BAF41DA5ABD1FFEDC6D2FEA56B\"; var S=\"~d}$Q7$eIni=V)9\"+String.fromCharCode(92)+\"RK\/P.RM4;9[7|@\/CA}b~OW!3?EV\"+String.fromCharCode(96,58,60,62)+\"M7pddUBL5n|0\/*Cn\"; var PL=\"\"; try{PL=String(source.get(\"picab_pool\")||\"\");}catch(e0){} if(PL===\"none\"){PL=\"\";}else if(!PL){PL=\"172.67.194.19,172.67.80.1,104.21.235.3,104.21.235.4,104.21.91.145,104.19.53.76,104.21.80.1,104.21.81.1,104.16.0.1,104.17.0.1\";} var pre=String(result); var a=pre.substring(pre.indexOf(\":\/\/\")+3); var p=a.substring(a.indexOf(\"\/\")); if(p.charAt(0)===\"\/\"){p=p.substring(1);} var ts=String(Math.floor(Date.now()\/1000)); var nc=\"\"; for(var i=0;i<32;i++)nc+=\"0123456789abcdef\".charAt(Math.floor(Math.random()*16)); var raw=(p+ts+nc+\"GET\"+K).toLowerCase(); var m=Packages.javax.crypto.Mac.getInstance(\"HmacSHA256\"); m.init(new Packages.javax.crypto.spec.SecretKeySpec(new Packages.java.lang.String(S).getBytes(\"UTF-8\"),\"HmacSHA256\")); var b=m.doFinal(new Packages.java.lang.String(raw).getBytes(\"UTF-8\")); var sg=\"\"; for(var i=0;i<b.length;i++){var x=b[i]&0xff;sg+=(x<16?\"0\":\"\")+x.toString(16);} var h={\"User-Agent\":\"okhttp\/3.8.1\",\"accept\":\"application\/vnd.picacomic.com.v1+json\",\"api-key\":K,\"app-channel\":\"2\",\"app-version\":\"2.2.1.2.3.3\",\"app-uuid\":\"defaultUuid\",\"app-platform\":\"android\",\"app-build-version\":\"44\",\"image-quality\":\"original\",\"time\":ts,\"nonce\":nc,\"signature\":sg}; var au=\"\"; try{au=String(source.get(\"picab_token\"));}catch(e){} if(au&&au!==\"null\"&&au!==\"\"){h[\"authorization\"]=au;} var opt={method:\"GET\",timeout:25000,retry:0,headers:h}; if(PL){opt.dnsIp=PL;} return \"https:\/\/picaapi.picacomic.com\/\"+p+\",\"+JSON.stringify(opt); })()';\n _next = \"https:\/\/picaapi.picacomic.com\/comics\/\" + _bid + \"\/order\/\" + _ord[1] + \"\/pages?page=\" + (_cur + 1) + _at + _sg;\n }\n }\n}\n_next;\n<\/js>"
},
"ruleExplore": {
"author": "author",
"bookList": "<js>\n\/\/ ============ 发现页解析(完全内联,不调用 jsLib) ============\nvar __API_KEY = \"C69BAF41DA5ABD1FFEDC6D2FEA56B\";\nvar __SECRET = \"~d}$Q7$eIni=V)9\" + String.fromCharCode(92) + \"RK\/P.RM4;9[7|@\/CA}b~OW!3?EV\" + String.fromCharCode(96, 58, 60, 62) + \"M7pddUBL5n|0\/*Cn\";\nvar __BASE = \"https:\/\/picaapi.picacomic.com\/\";\nvar __POOL = [\"172.67.194.19\", \"172.67.80.1\", \"104.21.235.3\",\n \"104.21.235.4\", \"104.21.91.145\", \"104.19.53.76\",\n \"104.21.80.1\", \"104.21.81.1\", \"104.16.0.1\", \"104.17.0.1\"];\nfunction __coverParse(media) {\n var out = { host: \"\", path: \"\", raw: \"\", isSB: false };\n if (!media) { return out; }\n var fs = String(media.fileServer || \"\").replace(\/\\\/$\/, \"\");\n var path = String(media.path || \"\").replace(\/^\\\/?\/, \"\");\n out.raw = path;\n if (!path) { return out; }\n if (path.indexOf(\"tobeimg\/\") === 0) {\n var idx = path.indexOf(\"\/g:sm\/\");\n if (idx >= 0) {\n var b64 = path.substring(idx + 6);\n var m = b64.match(\/^([A-Za-z0-9+\/=]+)(\\.[A-Za-z0-9]+)?$\/);\n if (m) {\n try {\n var pay = m[1];\n while (pay.length % 4 !== 0) { pay += \"=\"; }\n var real = String(new Packages.java.lang.String(\n Packages.android.util.Base64.decode(pay, 0), \"UTF-8\"));\n var i2 = real.indexOf(\"\/static\/\");\n if (i2 >= 0) {\n out.host = real.substring(0, i2).replace(\/^https?:\\\/\\\/\/, \"\");\n out.path = real.substring(i2);\n out.isSB = out.host.indexOf(\"storage-b\") >= 0;\n return out;\n }\n } catch (e0) {}\n }\n }\n }\n out.host = fs.replace(\/^https?:\\\/\\\/\/, \"\");\n if (path.indexOf(\"tobs\/\") === 0) { path = path.substring(5); }\n out.path = \"\/static\/\" + path;\n out.isSB = out.host.indexOf(\"storage-b\") >= 0;\n return out;\n}\nfunction __hash(s) {\n var h = 0; s = String(s || \"\");\n for (var i = 0; i < s.length; i++) { h = ((h << 5) - h + s.charCodeAt(i)) | 0; }\n return Math.abs(h);\n}\nfunction __pool() {\n var p = \"\";\n try { p = String(source.get(\"picab_pool\") || \"\"); } catch (e0) {}\n if (p === \"none\") { return \"\"; }\n if (p) { return p; }\n return __POOL.join(\",\");\n}\nfunction __coverMode() {\n try {\n var m = String(source.get(\"picab_cover_mode\") || \"ip\");\n return (m === \"dns\" || m === \"mirror\") ? m : \"ip\";\n } catch (e0) { return \"ip\"; }\n}\nfunction __cover(media) {\n var t = __coverParse(media);\n if (!t.raw) { return \"\"; }\n var mode = __coverMode();\n if (mode === \"mirror\") { return \"https:\/\/s3.go2778.com\/static\/\" + t.raw; }\n if (!t.host) { return \"\"; }\n if (mode === \"dns\") { return \"https:\/\/\" + t.host + t.path; }\n if (t.isSB) {\n var ips = __POOL;\n var ip = ips[__hash(t.path) % ips.length];\n return \"http:\/\/\" + ip + t.path\n + ',{\"method\":\"GET\",\"timeout\":20000,\"headers\":{\"Host\":\"' + t.host + '\"}}';\n }\n return \"https:\/\/\" + t.host + t.path;\n}\n\nvar __token = \"\";\ntry {\n var __lh = String(source.getLoginHeader() || \"\");\n if (__lh && __lh !== \"null\") {\n var __m = __lh.match(new RegExp('\"authorization\"\\\\s*:\\\\s*\"([^\"]+)\"'));\n __token = __m ? __m[1] : __lh;\n }\n} catch (eT0) {}\nif (!__token) {\n try { __token = String(source.get(\"picab_token\") || \"\"); } catch (eT1) {}\n}\n\nfunction __hmac(raw, key) {\n var m = Packages.javax.crypto.Mac.getInstance(\"HmacSHA256\");\n m.init(new Packages.javax.crypto.spec.SecretKeySpec(\n new Packages.java.lang.String(key).getBytes(\"UTF-8\"), \"HmacSHA256\"));\n var b = m.doFinal(new Packages.java.lang.String(raw).getBytes(\"UTF-8\"));\n var s = \"\";\n for (var i = 0; i < b.length; i++) {\n var x = b[i] & 0xff;\n s += (x < 16 ? \"0\" : \"\") + x.toString(16);\n }\n return s;\n}\n\nfunction __hdr(path, method) {\n var ts = String(Math.floor(Date.now() \/ 1000));\n var nc = \"\";\n for (var i = 0; i < 32; i++) {\n nc += \"0123456789abcdef\".charAt(Math.floor(Math.random() * 16));\n }\n var raw = (path + ts + nc + method + __API_KEY).toLowerCase();\n var h = {\n \"User-Agent\": \"okhttp\/3.8.1\",\n \"accept\": \"application\/vnd.picacomic.com.v1+json\",\n \"api-key\": __API_KEY,\n \"app-channel\": \"2\",\n \"app-version\": \"2.2.1.2.3.3\",\n \"app-uuid\": \"defaultUuid\",\n \"app-platform\": \"android\",\n \"app-build-version\": \"44\",\n \"image-quality\": \"original\",\n \"time\": ts,\n \"nonce\": nc,\n \"signature\": __hmac(raw, __SECRET)\n };\n if (__token && __token !== \"null\") { h[\"authorization\"] = __token; }\n return h;\n}\n\nvar _j = JSON.parse(result);\nif (_j.code !== 200) { throw new Error(\"发现页失败: \" + (_j.message || _j.code)); }\n\nvar _list = [];\ntry {\n if (String(baseUrl).indexOf(\"knight-leaderboard\") >= 0) {\n var _us = _j.data.users || [];\n _list = _us.map(function (u) {\n var __t = \"\";\n try {\n __t = String(u.avatar ? (String(u.avatar.fileServer || \"\").replace(\/\\\/$\/, \"\") + \"\/static\/\" + String(u.avatar.path || \"\")) : \"\");\n } catch (eA) {}\n return {\n name: java.t2s(String(u.name || \"\")),\n author: \"Lv.\" + (u.level || \"?\") + \" · \" + (u.title || \"\"),\n cover: __t,\n intro: (u.slogan || \"\") + \"\\n[骑士档案]\",\n kind: \"骑士榜\",\n latest: \"\", word: \"\",\n url: __BASE + \"comics?page=1&ca=\" + (u._id || \"\")\n + ',{\"method\":\"GET\",\"retry\":3,\"timeout\":20000,'\n + '\"headers\":' + JSON.stringify(__hdr(\"comics?page=1&ca=\" + (u._id || \"\"), \"GET\"))\n + ',\"dnsIp\":\"' + __pool() + '\"}'\n };\n });\n } else {\n var _d = _j.data || {};\n var __docs = [];\n if (_d.collections) {\n (_d.collections || []).forEach(function (g) {\n var arr = (g.comics && g.comics.docs) ? g.comics.docs : (g.comics || []);\n arr.forEach(function (c) { c.__group = g.title; __docs.push(c); });\n });\n } else if (_d.comics) {\n __docs = _d.comics.docs || _d.comics || [];\n } else if (_d.docs) {\n __docs = _d.docs;\n } else if (Array.isArray(_d)) {\n __docs = _d;\n }\n if (!Array.isArray(__docs)) { __docs = []; }\n _list = __docs.map(function (c) {\n if (!c) { return null; }\n return {\n name: c.title || \"\",\n author: c.author || \"\",\n cover: __cover(c.thumb),\n intro: \"\",\n kind: [(c.__group || \"\"),\n String(c.categories || []).replace(\/,\/g, \" \"),\n c.finished ? \"完结\" : \"连载\",\n c.likesCount ? c.likesCount + \"♥\" : \"\"].filter(Boolean).join(\" · \"),\n latest: \"\", word: \"\",\n url: __BASE + \"comics\/\" + (c._id || \"\")\n + ',{\"method\":\"GET\",\"retry\":3,\"timeout\":20000,'\n + '\"headers\":' + JSON.stringify(__hdr(\"comics\/\" + (c._id || \"\"), \"GET\"))\n + ',\"dnsIp\":\"' + __pool() + '\"}'\n };\n });\n }\n} catch (eItem) {\n _list = [{\n name: \"[ERR] \" + String(eItem).substring(0, 130),\n author: \"诊断行\", cover: \"\", intro: \"\", kind: \"err\", latest: \"\", word: \"\", url: \"\"\n }];\n}\nif (!_list.length) {\n var _tail = String(baseUrl);\n _tail = _tail.substring(_tail.lastIndexOf(\"\/\") + 1).split(\",\")[0].substring(0, 40);\n _list = [{ name: \"[空] 此列表暂无内容 (\" + _tail + \")\",\n author: \"接口返回 code=\" + _j.code + \" 但解析为空\",\n cover: \"\", intro: \"换个分类试试,或稍后再来\", kind: \"空列表\", latest: \"\", word: \"\", url: \"\" }];\n}\n_list.filter(function (x) { return x; });\n<\/js>\n",
"bookUrl": "url",
"coverUrl": "cover",
"intro": "intro",
"kind": "kind",
"lastChapter": "latest",
"name": "name"
},
"ruleSearch": {
"author": "author",
"bookList": "<js>\nvar _j = JSON.parse(result);\nif (_j.code !== 200) { throw new Error(\"搜索失败: \" + (_j.message || _j.code)); }\nvar _d = picac_list(_j.data);\n_d.map(function (x) { return picac_item(x, source, java); }).filter(function (x) { return x; });\n<\/js>",
"bookUrl": "url",
"coverUrl": "cover",
"intro": "intro",
"kind": "kind",
"lastChapter": "latest",
"name": "name"
},
"ruleToc": {
"chapterList": "<js>\nvar _j = JSON.parse(result);\nif (_j.code !== 200) { throw new Error(\"目录获取失败: \" + (_j.message || _j.code)); }\nvar _e = (_j.data && _j.data.eps) ? (_j.data.eps.docs || []) : [];\nvar _bid = picac_bookid(baseUrl);\nvar _at = String.fromCharCode(64,106,115,58);\nvar _sg = '(function(){ var K=\"C69BAF41DA5ABD1FFEDC6D2FEA56B\"; var S=\"~d}$Q7$eIni=V)9\"+String.fromCharCode(92)+\"RK\/P.RM4;9[7|@\/CA}b~OW!3?EV\"+String.fromCharCode(96,58,60,62)+\"M7pddUBL5n|0\/*Cn\"; var PL=\"\"; try{PL=String(source.get(\"picab_pool\")||\"\");}catch(e0){} if(PL===\"none\"){PL=\"\";}else if(!PL){PL=\"172.67.194.19,172.67.80.1,104.21.235.3,104.21.235.4,104.21.91.145,104.19.53.76,104.21.80.1,104.21.81.1,104.16.0.1,104.17.0.1\";} var pre=String(result); var a=pre.substring(pre.indexOf(\":\/\/\")+3); var p=a.substring(a.indexOf(\"\/\")); if(p.charAt(0)===\"\/\"){p=p.substring(1);} var ts=String(Math.floor(Date.now()\/1000)); var nc=\"\"; for(var i=0;i<32;i++)nc+=\"0123456789abcdef\".charAt(Math.floor(Math.random()*16)); var raw=(p+ts+nc+\"GET\"+K).toLowerCase(); var m=Packages.javax.crypto.Mac.getInstance(\"HmacSHA256\"); m.init(new Packages.javax.crypto.spec.SecretKeySpec(new Packages.java.lang.String(S).getBytes(\"UTF-8\"),\"HmacSHA256\")); var b=m.doFinal(new Packages.java.lang.String(raw).getBytes(\"UTF-8\")); var sg=\"\"; for(var i=0;i<b.length;i++){var x=b[i]&0xff;sg+=(x<16?\"0\":\"\")+x.toString(16);} var h={\"User-Agent\":\"okhttp\/3.8.1\",\"accept\":\"application\/vnd.picacomic.com.v1+json\",\"api-key\":K,\"app-channel\":\"2\",\"app-version\":\"2.2.1.2.3.3\",\"app-uuid\":\"defaultUuid\",\"app-platform\":\"android\",\"app-build-version\":\"44\",\"image-quality\":\"original\",\"time\":ts,\"nonce\":nc,\"signature\":sg}; var au=\"\"; try{au=String(source.get(\"picab_token\"));}catch(e){} if(au&&au!==\"null\"&&au!==\"\"){h[\"authorization\"]=au;} var opt={method:\"GET\",timeout:25000,retry:0,headers:h}; if(PL){opt.dnsIp=PL;} return \"https:\/\/picaapi.picacomic.com\/\"+p+\",\"+JSON.stringify(opt); })()';\nvar _out = _e.map(function (x) {\n return {\n title: x.title || (\"第\" + x.order + \" 话\"),\n url: \"https:\/\/picaapi.picacomic.com\/comics\/\" + _bid + \"\/order\/\" + x.order + \"\/pages?page=1\" + _at + _sg\n };\n});\n_out;\n<\/js>",
"chapterName": "title",
"chapterUrl": "url"
},
"searchUrl": "<js>\npicac_opt(\"comics\/advanced-search?page=\" + (page || 1),\n { method: \"POST\", body: JSON.stringify({ keyword: key, sort: \"dd\" }),\n timeout: 60000, retry: 2 },\n source, java);\n<\/js>",
"variableComment": "picaToken=登录后的 authorization(登录成功后自动写入,也可手动粘贴)",
"weight": 0
}