哔咔漫画〔自包含〕

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
}
广告