[{"articleStyle":3,"cacheFirst":false,"concurrentRate":"1\/800","customOrder":0,"enableJs":true,"enabled":true,"enabledCookieJar":false,"header":"<js>\n(function () {\n  \/\/ 动态头：代理 + 里站 Cookie 全部从面板变量注入（proxy 键由 AnalyzeUrl.getProxyClient 原生识别）\n  var UA = 'Mozilla\/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/120.0.0.0 Safari\/537.36';\n  \/\/ ★261109 v3：UA 统一由这里注入（AnalyzeUrl → WebView.settings.userAgentString 同源，\n  \/\/   官方版 ReadRssActivity.initLiveData 从 headerMap[UA] 取；E 版同字段）。\n  \/\/   补 Accept-Language \/ Sec-Fetch-* 让指纹更接近真实浏览器，降低被风控概率。\n  var h = {\n    'User-Agent': UA,\n    'Accept': 'text\/html,application\/xhtml+xml,application\/xml;q=0.9,image\/avif,image\/webp,*\/*;q=0.8',\n    'Accept-Language': 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7',\n    'Sec-Fetch-Dest': 'document', 'Sec-Fetch-Mode': 'navigate',\n    'Sec-Fetch-Site': 'same-origin', 'Upgrade-Insecure-Requests': '1',\n    'Referer': 'https:\/\/exhentai.org\/'\n  };\n  function g(k) {\n    var v = '';\n    try { v = String(source.get(k)); } catch (e) { v = ''; }\n    if (v === 'null' || v === 'undefined' || v === 'NaN') v = '';\n    return v;\n  }\n  var px = g('ehProxy');\n  if (px) { h.proxy = px; }\n  \/\/ ★261114 v6.8：收藏夹\/订阅tag 开关默认关闭（名单缓存上线，降低敏感接口请求频率）。\n  \/\/   一次性迁移：老用户若存过 '1'，这里重置为 '0'（有 ehFavReset68 标记不重复执行）。\n  try {\n    if (!g('ehFavReset68')) {\n      source.put('ehFavPanel', '0');\n      source.put('ehFavReset68', '1');\n      source.put('ehFavCache', '');\n      source.put('ehTagCache', '');\n    }\n  } catch (eR68) { }\n  \/\/ ★261114 v6.9 一次性迁移：清掉直连功能时代残留的 ehHostsIp（IP池直连会被站方按共享IP限频）。\n  \/\/   新守卫 ehFix69：LT 机器上 ehFavReset68 已消费过，必须用新键才执行。\n  try {\n    if (!g('ehFix69')) {\n      source.put('ehHostsIp', '');\n      source.put('ehFix69', '1');\n    }\n  } catch (eF69) { }\n  \/\/ ★★ 261114 v5.2：只发 ipb_member_id \/ ipb_pass_hash \/ inline_set \/ igneous(=空或该账号有效值)，\n  \/\/   绝不发 yay \/ sk \/ star，绝不硬编码账号凭据。实测 igneous=mystery\/乱值 ⇒ 0B 白屏；\n  \/\/   igneous= 空值 ⇒ 站方重签该账号有效值 + 62KB 正常。enabledCookieJar=false 截断回写；\n  \/\/   显式带 igneous 键是为覆盖 CookieStore 历史残留 mystery（mergeCookies putAll 缺键补齐）。\n  var raw = g('ehCookie');\n  \/\/ ★ 未配置用户 Cookie ⇒ 只发 igneous= 空值占位（防止 mergeCookies 把库内旧 Cookie 补回来）\n  if (raw.indexOf('ipb_member_id=') < 0 || raw.indexOf('ipb_pass_hash=') < 0) {\n    h.Cookie = 'igneous=';\n    return JSON.stringify(h);\n  }\n  var parts = raw.split(';'), keep = [], seen = {}, i, p, eq, k, v;\n  \/\/ ★261114 v5.1：必须显式带 igneous —— AnalyzeUrl.setCookie() 用 mergeCookiesToMap(putAll)，\n  \/\/   header 缺该键时 CookieStore 里残留的 igneous=mystery 会被补进请求 ⇒ 0B 白屏。\n  var ALLOW = ['ipb_member_id', 'ipb_pass_hash', 'inline_set', 'igneous'];\n  for (i = 0; i < parts.length; i++) {\n    p = parts[i].trim(); eq = p.indexOf('=');\n    if (eq < 1) continue;\n    k = p.substring(0, eq).trim(); v = p.substring(eq + 1).trim();\n    if (ALLOW.indexOf(k) < 0 || seen[k] || v === '') continue;\n    seen[k] = 1; keep.push(k + '=' + v);\n  }\n  if (keep.join(';').indexOf('inline_set=') < 0) keep.push('inline_set=dm_l');\n  \/\/ ★★261114 v5.2：igneous 是账号绑定值，绝不硬编码（v5.1 硬编码沙盒账号的值导致\n  \/\/   用户账号写操作全失败 \/ 静默用测试账号 → 收藏夹与关注列表是别人的）。\n  \/\/   - 用户主动点过「🔄 刷新 igneous」→ 用 ehIgneous（该账号自己的有效值）\n  \/\/   - 否则用【空值】：键存在可 putAll 覆盖 CookieStore 残留的 mystery，\n  \/\/     空值会让站方按「只发两项」重签该账号自己的 igneous（实测 62KB 正常）。\n  var ig = g('ehIgneous') || '';\n  if (ig === 'mystery' || ig === 'undefined' || ig === 'null' || ig === '0') ig = '';\n  \/\/ 覆盖掉从粘贴串带进来的旧\/失效 igneous（统一用 ehIgneous 或空值）\n  var cleaned = [];\n  for (i = 0; i < keep.length; i++) if (keep[i].indexOf('igneous=') !== 0) cleaned.push(keep[i]);\n  cleaned.push('igneous=' + ig);\n  h.Cookie = cleaned.join('; ');\n  return JSON.stringify(h);\n})()\n<\/js>","lastUpdateTime":1794637096497,"loadWithBaseUrl":true,"loginCheckJs":"\/\/ ==================== loginCheckJs：绕开 OkHttp\/CookieJar 白屏（261107 定案） ====================\n\/\/ 源码 Rss.getArticles \/ Rss.getContentAwait:\n\/\/   analyzeUrl.getStrResponseAwait().let { analyzeUrl.evalJS(checkJs, it) as StrResponse }\n\/\/   bindings[\"result\"] = 原始 StrResponse；这里必须返回 StrResponse。\n\/\/ ★ 双栈对照实锤：OkHttp+CookieJar 栈对 exhentai HTML 恒 0B（igneous=mystery 白屏），\n\/\/   java.ajax(jsoup 纯净栈 + 只发 member\/pass 两项) 恒 60KB+ 正常 HTML。\n\/\/   ⇒ 空响应时用 ajax 重取，new StrResponse(url, fresh) 替换。\n\/\/ ★ Cookie 来源：仅面板变量 ehCookie；为空时不重取（明确提示用户粘贴），绝不硬编码测试账号。\n(function (r) {\n  var UA = 'Mozilla\/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/120.0.0.0 Safari\/537.36';\n  \/\/ ★261114 v5.2：绝不硬编码账号凭据\/igneous；没配 ehCookie 就保持原响应（由 header 层提示）\n  function g(k) {\n    var v = '';\n    try { v = String(source.get(k)); } catch (e) { v = ''; }\n    if (v === 'null' || v === 'undefined' || v === 'NaN' || v === '') v = '';\n    return v;\n  }\n  \/\/ ★261114 v5.1：只发两项 + inline_set + 显式合法 igneous（覆盖 CookieStore 残留 mystery）\n  function cookie2() {\n    var raw = g('ehCookie');\n    if (raw.indexOf('ipb_member_id=') < 0 || raw.indexOf('ipb_pass_hash=') < 0) return '';\n    var parts = raw.split(';'), keep = [], seen = {}, OK = ['ipb_member_id', 'ipb_pass_hash', 'inline_set'];\n    for (var i = 0; i < parts.length; i++) {\n      var p = parts[i].trim(), eq = p.indexOf('=');\n      if (eq < 1) continue;\n      var k = p.substring(0, eq).trim(), v = p.substring(eq + 1).trim();\n      if (OK.indexOf(k) < 0 || seen[k] || v === '') continue;   \/\/ 忽略 igneous，下面统一补\n      seen[k] = 1; keep.push(k + '=' + v);\n    }\n    if (keep.join(';').indexOf('inline_set=') < 0) keep.push('inline_set=dm_l');\n    var ig = g('ehIgneous') || '';\n    if (ig === 'mystery' || ig === 'undefined' || ig === 'null' || ig === '0') ig = '';\n    keep.push('igneous=' + ig);   \/\/ 空值占位：覆盖库内 mystery + 让站方重签该账号的值\n    return keep.join('; ');\n  }\n  var url = '';\n  try { url = String(r.url()); } catch (e) { return r; }\n  var body = '';\n  try { body = String(r.body()); } catch (e) { body = ''; }\n  if (body === 'null' || body === 'undefined') body = '';\n  \/\/ 已经正常就原样返回，避免每个请求都二次取\n  if (body.length > 5000 && body.indexOf('<') === 0 && !\/temporarily banned|excessive request\/i.test(body.slice(0, 400))) return r;\n  if (!url || url.indexOf('hentai.org') < 0) return r;\n  var fresh = '';\n  var CK2 = cookie2();\n  if (!CK2) return r;   \/\/ ★ 未配置用户 Cookie ⇒ 不重取（否则会用空 Cookie 打到 Sad Panda）\n  try {\n    var H = JSON.stringify({ 'User-Agent': UA, 'Cookie': CK2, 'Referer': 'https:\/\/exhentai.org\/' });\n    \/\/ ★261114 v6.9 不再带 dnsIp（直连残留清除，对照实验实锤带IP池必吃banned页）\n    var HO = JSON.parse(H);\n    fresh = String(java.ajax(url + ',' + JSON.stringify(HO)));\n  } catch (e) { fresh = ''; }\n  if (fresh.length > 1000 && fresh.indexOf('<') === 0) {\n    try {\n      var nr = new Packages.io.legado.app.help.http.StrResponse(url, fresh);\n      return nr;\n    } catch (e2) { return r; }\n  }\n  return r;\n})(result)\n","loginUi":"[{\"name\": \"ehUser\", \"type\": \"text\", \"viewName\": \"'🔐 论坛账号'\"}, {\"name\": \"ehPass\", \"type\": \"password\", \"viewName\": \"'🔐 论坛密码'\"}, {\"name\": \"🔐 用账号密码登录\", \"type\": \"button\", \"action\": \"EH_pwdLogin()\", \"viewName\": \"EH_loginStatus()\"}, {\"name\": \"📝 注册新账号（浏览器）\", \"type\": \"button\", \"action\": \"EH_register()\"}, {\"name\": \"🔑 忘记密码 \/ 找回用户名\", \"type\": \"button\", \"action\": \"EH_forgot()\"}, {\"name\": \"🌐 打开论坛登录（过CF）\", \"type\": \"button\", \"action\": \"EH_browserLogin()\"}, {\"name\": \"🔄 读取登录 Cookie\", \"type\": \"button\", \"action\": \"EH_readBrowserCookie()\"}, {\"name\": \"✅ 读取Cookie并验证\", \"type\": \"button\", \"action\": \"EH_afterBrowser()\"}, {\"name\": \"━━━━━ 或粘贴 Cookie（现有通道） ━━━━━\", \"type\": \"button\", \"action\": \"\"}, {\"name\": \"ehCookieInput\", \"type\": \"text\", \"viewName\": \"'① 里站 Cookie'\"}, {\"name\": \"💾 保存 Cookie\", \"type\": \"button\", \"action\": \"EH_applyCookie()\"}, {\"name\": \"ehProxyInput\", \"type\": \"text\", \"viewName\": \"'② 代理地址'\"}, {\"name\": \"💾 保存代理\", \"type\": \"button\", \"action\": \"EH_applyProxy()\", \"viewName\": \"'💾 应用代理（留空=直连）'\"}, {\"name\": \"🩺 一键自愈（白屏必点）\", \"type\": \"button\", \"action\": \"EH_heal()\"}, {\"name\": \"🔄 刷新 igneous（登录态持久化）\", \"type\": \"button\", \"action\": \"EH_refreshIgneous()\", \"viewName\": \"EH_igBtn()\"}, {\"name\": \"🔍 检测连通 \/ 登录态\", \"type\": \"button\", \"action\": \"EH_check()\"}, {\"name\": \"🖼 正文图片布局\", \"type\": \"button\", \"action\": \"EH_toggleCols()\", \"viewName\": \"EH_colsBtn()\"}, {\"name\": \"📁 正文显示收藏夹\", \"type\": \"button\", \"action\": \"EH_favToggle()\", \"viewName\": \"EH_favBtn()\"}, {\"name\": \"🏷 点 tag 名字搜索对应标签\", \"type\": \"button\", \"action\": \"EH_tagToggle('ehTagSearch')\", \"viewName\": \"EH_tagBtn('ehTagSearch','点tag搜索')\"}, {\"name\": \"🏷 点 ＋ 订阅对应 tag（再点取消）\", \"type\": \"button\", \"action\": \"EH_tagToggle('ehTagWatch')\", \"viewName\": \"EH_tagBtn('ehTagWatch','订阅tag')\"}, {\"name\": \"ehResSelect\", \"type\": \"select\", \"action\": \"EH_setRes()\", \"chars\": [\"省流·缩略图≈250px\", \"自动·均衡≈780x\", \"高清·1280x(WiFi)\", \"超清·全量·大屏\"], \"default\": \"自动·均衡≈780x\"}, {\"name\": \"⚙ 同步站点分辨率(xr\/ts)\", \"type\": \"button\", \"action\": \"EH_uconfigSync()\"}, {\"name\": \"📊 分辨率\/布局状态\", \"type\": \"button\", \"action\": \"EH_toast(EH_resStatus())\", \"viewName\": \"EH_resStatus()\"}, {\"name\": \"📊 当前配置\", \"type\": \"button\", \"action\": \"EH_toast(EH_status())\", \"viewName\": \"EH_status()\"}, {\"name\": \"✅ 检查登录状态\", \"type\": \"button\", \"action\": \"EH_checkLogin()\"}, {\"name\": \"🩺 登录态一键诊断\", \"type\": \"button\", \"action\": \"EH_diagLogin()\", \"viewName\": \"EH_diagBtn()\"}, {\"name\": \"━━━━━ 线路 \/ 测速 ━━━━━\", \"type\": \"button\", \"action\": \"\"}, {\"name\": \"⚡ 测试各线路延时\", \"type\": \"button\", \"action\": \"EH_testDelay()\"}, {\"name\": \"ehPubInput\", \"type\": \"text\", \"viewName\": \"'🌐 自定义线路'\"}, {\"name\": \"🔗 一键从发布页获取网址路线\", \"type\": \"button\", \"action\": \"EH_fromPub()\"}, {\"name\": \"里站 exhentai.org · 未测速（点击切换）\", \"type\": \"button\", \"action\": \"EH_switchHostEx(1)\", \"viewName\": \"EH_hostBtn(1)\"}, {\"name\": \"表站 e-hentai.org · 未测速（点击切换）\", \"type\": \"button\", \"action\": \"EH_switchHostEx(2)\", \"viewName\": \"EH_hostBtn(2)\"}, {\"name\": \"🚪 清除面板 Cookie\", \"type\": \"button\", \"action\": \"EH_clearCookie()\"}, {\"name\": \"🧹 清 App 域 Cookie\", \"type\": \"button\", \"action\": \"EH_purge()\"}]","loginUrl":"\/\/ ==================== ExHentai 订阅源 · 登录\/凭据\/代理面板 (v2) ====================\n\/\/ ★261107 修复要点（三组对照实验实锤）：\n\/\/  ① V1 loginUi 的输入框值由 handleButtonClick 绑成 `result`（getLoginData 的 Map），\n\/\/     不是 source.put ⇒ 旧版 action 读 source.get('ehCookieInput') 恒空（用户填了没保存）。\n\/\/     现统一 EH_input(key) 从 result 读，兼容 login() 传参与 source.get 兜底。\n\/\/  ② 只发 ipb_member_id + ipb_pass_hash 两项：带旧 igneous \/ CookieJar merge 的\n\/\/     igneous=mystery ⇒ 站方 0B 白屏；不带则服务器重新签发有效 igneous(30天)，HTML 正常。\n\/\/  ③ java.ajax 走纯净栈能拿 67KB HTML，java.connect(OkHttp+CookieJar) 恒 0B\n\/\/     ⇒ 检测与自愈一律用 ajax；订阅源正式请求靠 loginCheckJs 替换响应体（见 build.py）。\nvar EH_UA = 'Mozilla\/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/120.0.0.0 Safari\/537.36';\nvar EH_OK = ['ipb_member_id', 'ipb_pass_hash', 'inline_set'];\n\/\/ ★261114 v5.2：igneous 是账号绑定值，绝不硬编码；空值占位让站方重签该账号自己的值\n\nfunction EH_toast(t) {\n  try { java.longToast(String(t)); } catch (e) { try { java.toast(String(t)); } catch (e2) {} }\n}\nfunction EH_gv(k, def) {\n  var v = '';\n  try { v = String(source.get(k)); } catch (e) { v = ''; }\n  if (v === 'null' || v === 'undefined' || v === 'NaN' || v === '') v = def || '';\n  return v;\n}\nfunction EH_set(k, v) { try { source.put(k, v); } catch (e) {} }\n\/\/ ★ 从 V1 面板绑定的 result(Map) 读输入框；三通道兜底\nfunction EH_input(k) {\n  var v = '';\n  try {\n    if (typeof result !== 'undefined' && result != null) {\n      if (typeof result.get === 'function') { try { v = String(result.get(k)); } catch (e0) {} }\n      if (!v || v === 'null' || v === 'undefined') { try { v = String(result[k]); } catch (e1) {} }\n    }\n  } catch (e) {}\n  if (!v || v === 'null' || v === 'undefined' || v === 'NaN') v = EH_gv(k, '');\n  return v;\n}\nfunction EH_host() {\n  var d = EH_gv('ehHost', 'https:\/\/exhentai.org').replace(\/\\\/+$\/, '');\n  if (d.indexOf('http') !== 0) d = 'https:\/\/' + d;\n  return d;\n}\n\/\/ 与 header.js 完全同逻辑：只保留两项 + inline_set，剔除 igneous\/yay\/sk\/star\nfunction EH_cookie(raw) {\n  var parts = String(raw || '').split(';'), keep = [], seen = {}, i, p, eq, k, v;\n  for (i = 0; i < parts.length; i++) {\n    p = parts[i].trim(); eq = p.indexOf('=');\n    if (eq < 1) continue;\n    k = p.substring(0, eq).trim(); v = p.substring(eq + 1).trim();\n    if (EH_OK.indexOf(k) < 0 || seen[k] || v === '') continue;\n    seen[k] = 1; keep.push(k + '=' + v);\n  }\n  if (keep.join(';').indexOf('inline_set=') < 0) keep.push('inline_set=dm_l');\n  var ig = EH_gv('ehIgneous', '') || '';\n  if (ig === 'mystery' || ig === 'null' || ig === 'undefined' || ig === '0') ig = '';\n  var cleaned = [];\n  for (i = 0; i < keep.length; i++) if (keep[i].indexOf('igneous=') !== 0) cleaned.push(keep[i]);\n  cleaned.push('igneous=' + ig);\n  return cleaned.join('; ');\n}\n\/\/ 构造 java.ajax 的 URL 选项串（纯净栈，不装 CookieJar）\nfunction EH_ajaxUrl(path) {\n  var h = { 'User-Agent': EH_UA, 'Cookie': EH_cookie(EH_gv('ehCookie', '')), 'Referer': 'https:\/\/exhentai.org\/' };\n  return EH_host() + path + ',{\"headers\":' + JSON.stringify(h) + '}';\n}\n\n\/\/ ---- 💾 保存 Cookie（从 result 读粘贴框） ----\nfunction EH_applyCookie() {\n  var raw = EH_input('ehCookieInput');\n  if (!raw) { EH_toast('⚠ 请先在上方框里粘贴 Cookie，再点保存'); return; }\n  var s = String(raw).replace(\/\\r\/g, '').replace(\/\\n\/g, ' ').trim();\n  \/\/ 解析：接受 \"k: v\" 多行 \/ 分号串 \/ document.cookie\n  var parts = s.replace(\/:\\s*\/g, '=').split(\/[;\\n]+\/), out = [], seen = {}, gotMember = false, gotPass = false;\n  for (var i = 0; i < parts.length; i++) {\n    var p = parts[i].trim(), eq = p.indexOf('=');\n    if (eq < 1) continue;\n    var k = p.substring(0, eq).trim(), v = p.substring(eq + 1).trim();\n    if (seen[k] || v === '' || v === 'undefined' || v === 'null') continue;\n    \/\/ ★261114 v5.1：粘贴串里的 igneous=mystery 绝不入库\n    if (k === 'igneous') continue;\n    if (k === 'ipb_member_id') gotMember = true;\n    if (k === 'ipb_pass_hash') gotPass = true;\n    seen[k] = 1; out.push(k + '=' + v);\n  }\n  if (!gotMember || !gotPass) {\n    EH_set('ehCookie', s);\n    EH_toast('⚠ 未同时识别到 ipb_member_id 与 ipb_pass_hash，已原样保存；请检查格式');\n  } else {\n    EH_set('ehCookie', out.join('; '));\n    \/\/ ★ 关键：把 igneous 等容易失效的键从「发送串」里剔除（header 只发两项让站方重签）\n    EH_set('ehCookieInput', out.join('; '));\n    EH_toast('✅ 已保存（' + out.length + ' 项，已剔除 igneous 脏值）。★请求时用面板「🔄 刷新 igneous」维护的合法值，不再反复白屏');\n  }\n  EH_refresh();\n}\nfunction EH_applyProxy() {\n  var p = EH_input('ehProxyInput').trim();\n  if (!p) { EH_set('ehProxy', ''); EH_toast('✅ 已清空代理（直连\/VPN 全局模式）'); EH_refresh(); return; }\n  if (!\/^(https?|socks5|socks4):\\\/\\\/\/i.test(p)) p = 'http:\/\/' + p;\n  EH_set('ehProxy', p);\n  EH_toast('✅ 代理已设为 ' + p + '；请点「🔍检测连通」验证');\n  EH_refresh();\n}\nfunction EH_clearCookie() {\n  EH_set('ehCookie', ''); EH_set('ehCookieInput', '');\n  EH_toast('✅ 已清除面板 Cookie（游客模式）');\n  EH_refresh();\n}\nfunction EH_switchHost(n) {\n  var d = (Number(n) === 1) ? 'https:\/\/exhentai.org' : 'https:\/\/e-hentai.org';\n  EH_set('ehHost', d);\n  EH_toast('✅ 已切换到 ' + d + '；重新进入订阅\/发现页生效');\n  EH_refresh();\n}\n\/\/ ---- 🧹 清域 Cookie + 🩺 一键自愈（核心修复） ----\nfunction EH_purge() {\n  try { cookie.removeCookie('https:\/\/exhentai.org\/'); } catch (e) {}\n  try { cookie.removeCookie('https:\/\/e-hentai.org\/'); } catch (e) {}\n  EH_toast('✅ 已清 App 内 hentai 域 Cookie（含失效的 igneous=mystery \/ yay \/ sk）');\n}\nfunction EH_heal() {\n  EH_toast('🩺 开始自愈：清域 Cookie → 只发两项重新触发站方签发 igneous…');\n  EH_purge();\n  var body = '';\n  try { body = String(java.ajax(EH_ajaxUrl('\/'))); } catch (e) { EH_toast('❌ 自愈请求异常 ' + String(e).slice(0, 60)); return; }\n  if (body.length > 5000 && body.indexOf('<') === 0) {\n    var g = (body.match(\/\\\/g\\\/\\d+\\\/[0-9a-f]{10}\\\/\/g) || []).length;\n    EH_toast('✅ 自愈成功：拿到 ' + body.length + 'B 完整首页，识别到 ' + g + ' 个画廊！igneous 已重新签发，请重新进入订阅页刷新');\n  } else if (\/temporarily banned|excessive request\/i.test(body)) {\n    EH_toast('❌ 出口 IP 被限频（24h）⇒ 换 VPN 节点后重试');\n  } else {\n    EH_toast('❌ 仍是空白页（' + body.length + 'B）⇒ 确认 Cookie 有效、VPN 出口干净，或稍等 5 分钟冷却再试');\n  }\n  EH_refresh();\n}\n\/\/ ---- 🔍 检测连通（用 java.ajax 纯净栈，避开 connect 的 CookieJar 污染） ----\nfunction EH_check() {\n  var d = EH_host();\n  EH_toast('🔍 正在检测 ' + d + '（' + (EH_gv('ehProxy', '') || '直连\/VPN') + '）…');\n  var body = '';\n  try { body = String(java.ajax(EH_ajaxUrl('\/'))); } catch (e) { body = String(e); }\n  if (\/Exception|timeout|Failed to connect\/i.test(body.slice(0, 200))) {\n    EH_toast('❌ 网络不通（' + body.slice(0, 70) + '）⇒ 检查 VPN \/ 代理'); return;\n  }\n  if (\/temporarily banned|excessive request|Your IP address has been\/i.test(body)) {\n    EH_toast('❌ 出口 IP 被限频（24h 自动解封）⇒ 换 VPN 节点'); return;\n  }\n  if (body.length < 1000) {\n    EH_toast('❌ 空白页（' + body.length + 'B）= igneous=mystery 白屏 ⇒ 点「🩺 一键自愈」清域重签'); return;\n  }\n  var galleries = (body.match(\/\\\/g\\\/\\d+\\\/[0-9a-f]{10}\\\/\/g) || []).length;\n  var layout = \/class=\"itg glt([cemtl])\"\/.exec(body);\n  EH_toast('✅ 连通成功（' + body.length + 'B）；画廊链接 ' + galleries + ' 个，布局=glt' + (layout ? layout[1] : '?') + '；请重新进入订阅页看列表');\n}\n\/\/ ---- 🖼 正文每行图片数切换（1 图大图 \/ 2 图紧凑） ----\n\/\/ 用户 261107 反馈「一行两个有点小」⇒ 默认改为 1 图；按钮在两档间切换。\n\/\/ content.js 用 source.get('ehCols') 读档位分支 CSS；订阅源正文环境 source=RssSource 本体，\n\/\/ get\/put 直接可用（261030 源码级实锤，勿改用 getVariable）。\nfunction EH_cols() {\n  var c = EH_gv('ehCols', '1');\n  return (c === '2') ? '2' : '1';\n}\nfunction EH_toggleCols() {\n  var old = EH_cols(), nw = (old === '1') ? '2' : '1';\n  EH_set('ehCols', nw);\n  EH_toast('🖼 已切换为「一行 ' + nw + ' 图」：' +\n           (nw === '1' ? '单列大图（默认，图更大）' : '双列紧凑（一屏看更多）') +\n           '。★已打开的文章有正文缓存，请退出重进本文或清除存档后生效');\n  EH_refresh();\n}\nfunction EH_colsBtn() {\n  try {\n\n    var c = EH_cols();\n    return (\"'🖼 正文每行 \" + c + \" 图（点击切为 \" + (c === '1' ? '2' : '1') + \" 图）'\") || \"'🖼 正文图片布局'\";\n\n  } catch (eVN) { return \"'🖼 正文图片布局'\"; }\n}\nfunction EH_status() {\n  \/\/ ★261114 v5.3b：整函数 try\/catch —— viewName 求值环境里任何异常都让串变空 → \"null\"\n  try {\n  var ck = EH_gv('ehCookie', ''), px = EH_gv('ehProxy', '');\n  var st = (ck.indexOf('ipb_member_id=') >= 0 ? 'member✔ ' : 'member✘ ') +\n           (ck.indexOf('ipb_pass_hash=') >= 0 ? 'pass✔' : 'pass✘');\n  var acct = EH_creds().u ? '账号✔' : '账号✘';\n  \/\/ ★261109 v3：把分辨率档位也写进「当前配置」，避免面板与正文实际档位不一致却看不出来\n  var res = '分辨率?';\n  try { res = EH_resCfg().n; } catch (eR) { res = EH_gv('ehResLabel', '分辨率?'); }\n  return (\"'\" + EH_host().replace(\/^https?:\\\/\\\/\/, '') + ' | ' + (px ? '代理✔' : '直连') +\n         ' | ' + st + ' | ' + acct + ' | 每行' + EH_cols() + '图 | ' + res + \"'\") || \"'📊 当前配置'\";\n  } catch (eBig2) { return \"'📊 当前配置'\"; }\n}\nfunction EH_refresh() {\n  try { java.reLoginView(); } catch (e) {\n    try { java.reLoginView(false); } catch (e2) { EH_toast('（若按钮文字未刷新，请重新打开本面板）'); }\n  }\n}\n\/\/ login() 点「登录」时一次性把输入框全部落库（源码把 loginData 绑成 result）\nfunction login(e) {\n  if (e != undefined) { EH_toast(String(e)); return; }\n  \/\/ 点「登录」只持久化输入框（源码 putLoginInfo 已自动做），不自动发起网络登录：\n  \/\/ 账号密码登录需用户主动点「🔐 用账号密码登录」，避免误触触发 CF\/验证码。\n  EH_applyCookie(); EH_applyProxy();\n  EH_toast('✅ 已保存面板输入。★账号密码登录请点「🔐 用账号密码登录」；白屏点「🩺 一键自愈」；最后「🔍 检测连通」');\n}\n\n\/\/ ==================== 🔐 账号密码登录（261107 新增） ====================\n\/\/ 协议实锤（ExH login 用户脚本 \/ Let's panda \/ xeHentai core.py 三方一致）：\n\/\/   POST https:\/\/forums.e-hentai.org\/index.php?act=Login&CODE=01\n\/\/   Content-Type: application\/x-www-form-urlencoded\n\/\/   body: referer=...&b=&bt=&UserName=<用户名>&PassWord=<密码>&CookieDate=1\n\/\/   成功判据：响应含 \"You are now logged in as\"；Set-Cookie 给 ipb_member_id \/ ipb_pass_hash\n\/\/ ★ 但本环境 forums 域被 Cloudflare Turnstile 挡死（GET\/POST 一律 403 cf-mitigated: challenge，\n\/\/   换 UA 无效）⇒ 纯 HTTP 自动登录不可行。正解 = java.startBrowserAwait 打开内置浏览器，\n\/\/   用户手动过盾 + 登录，回来后从 CookieStore 抽取两项写回 ehCookie。\n\/\/ ★ Cookie 按二级域归并：forums.e-hentai.org 与 e-hentai.org 同域共享，\n\/\/   但 exhentai.org 是另一个二级域 ⇒ 必须显式 cookie.setCookie 复制过去。\nvar EH_FORUM = 'https:\/\/forums.e-hentai.org\/index.php?act=Login&CODE=00';\nvar EH_LOGIN_POST = 'https:\/\/forums.e-hentai.org\/index.php?act=Login&CODE=01';\n\nfunction EH_creds() {\n  var u = EH_input('ehUser'), p = EH_input('ehPass');\n  return { u: String(u || '').trim(), p: String(p || '').trim() };\n}\n\/\/ 从任意 Cookie 串里抽两项（含 URL 编码\/引号容错）\nfunction EH_extract2(raw) {\n  var s = String(raw || '').replace(\/\\r\/g, '').replace(\/\\n\/g, ' ');\n  var parts = s.split(';'), keep = {}, i, q, eq, k, v;\n  for (i = 0; i < parts.length; i++) {\n    q = parts[i].trim(); eq = q.indexOf('=');\n    if (eq < 1) continue;\n    k = q.substring(0, eq).trim().replace(\/^[\"']|[\"']$\/g, '');\n    v = q.substring(eq + 1).trim().replace(\/^[\"']|[\"']$\/g, '');\n    if ((k === 'ipb_member_id' || k === 'ipb_pass_hash') && v && !keep[k]) keep[k] = v;\n  }\n  return keep;\n}\n\/\/ 把抽到的两项写进：① 源变量 ehCookie ② exhentai.org 域持久 Cookie\nfunction EH_commitCreds(keep, from) {\n  if (!keep.ipb_member_id || !keep.ipb_pass_hash) return false;\n  var two = 'ipb_member_id=' + keep.ipb_member_id + '; ipb_pass_hash=' + keep.ipb_pass_hash + '; inline_set=dm_l';\n  EH_set('ehCookie', two);\n  EH_set('ehCookieInput', two);\n  try { cookie.setCookie('https:\/\/exhentai.org\/', two); } catch (e) {}\n  \/\/ 清掉可能残留的 mystery，让站方重新签发\n  try { cookie.removeCookie('https:\/\/exhentai.org\/'); } catch (e) {}\n  try { cookie.setCookie('https:\/\/exhentai.org\/', two); } catch (e) {}\n  EH_toast('✅ ' + from + ' 登录成功！已取得 ipb_member_id=' + keep.ipb_member_id +\n           '，Cookie 已写入源变量与里站域。请点「🔍 检测连通」验证，再重进订阅页');\n  EH_refresh();\n  return true;\n}\n\/\/ 通道 A：直接 POST（出口没被 CF 挡时最快）\nfunction EH_tryPost(c) {\n  var body = 'referer=https:\/\/forums.e-hentai.org\/index.php?&b=&bt=&UserName=' +\n             encodeURIComponent(c.u) + '&PassWord=' + encodeURIComponent(c.p) + '&CookieDate=1';\n  var opt = '{\"method\":\"POST\",\"body\":' + JSON.stringify(body) +\n            ',\"headers\":{\"User-Agent\":' + JSON.stringify(EH_UA) +\n            ',\"Content-Type\":\"application\/x-www-form-urlencoded\"' +\n            ',\"Referer\":\"https:\/\/forums.e-hentai.org\/index.php?act=Login&CODE=00\"}}';\n  var r, b = '';\n  try {\n    r = java.connect(EH_LOGIN_POST + ',' + opt, '', 20000);\n    b = String(r.body());\n  } catch (e) { return { ok: false, reason: 'POST异常 ' + String(e).slice(0, 50) }; }\n  if (\/Just a moment|cf-mitigated|challenge|Checking your browser\/i.test(b)) {\n    return { ok: false, reason: 'Cloudflare 挑战页（需内置浏览器过盾）' };\n  }\n  if (\/You are now logged in as|Logged in as|logout|Log Out\/i.test(b)) {\n    \/\/ 从响应头 Set-Cookie 或 CookieStore 取\n    var raw = '';\n    try { raw = String(r.headers()); } catch (e) {}\n    if (!raw) { try { raw = String(cookie.getCookie('https:\/\/forums.e-hentai.org\/')); } catch (e2) {} }\n    var k = EH_extract2(raw);\n    if (!k.ipb_member_id) { try { k = EH_extract2(cookie.getCookie('https:\/\/forums.e-hentai.org\/')); } catch (e3) {} }\n    if (k.ipb_member_id && k.ipb_pass_hash) return { ok: true, keep: k };\n    return { ok: false, reason: '登录成功但未读到 Cookie（检查 CookieJar）' };\n  }\n  return { ok: false, reason: '账号密码错误 \/ 需验证码 \/ 未识别响应（' + b.length + 'B）' };\n}\n\/\/ 通道 B：内置浏览器（过 CF + 手动登录，用户填完点页面里的登录即可）\nfunction EH_browserLogin() {\n  EH_toast('🌐 正在打开论坛登录页…请在内置浏览器里完成 Cloudflare 验证并登录账号，登录后返回本面板再点「🔄 读取登录 Cookie」');\n  try {\n    var r = java.startBrowserAwait(EH_FORUM, 'E-Hentai 论坛登录（过CF验证并登录）', false);\n    EH_toast('🌐 浏览器已返回（' + String(r.body()).length + 'B）。请点「🔄 读取登录 Cookie」把登录态写进本源');\n  } catch (e) {\n    EH_toast('❌ 打开浏览器失败：' + String(e).slice(0, 80));\n  }\n}\n\/\/ 通道 C：从浏览器登录后的 CookieStore 读取并写回\nfunction EH_readBrowserCookie() {\n  var raws = [\n    'https:\/\/forums.e-hentai.org\/', 'https:\/\/e-hentai.org\/', 'https:\/\/exhentai.org\/'\n  ], i, k = {};\n  for (i = 0; i < raws.length; i++) {\n    try {\n      var s = String(cookie.getCookie(raws[i]));\n      var q = EH_extract2(s);\n      if (q.ipb_member_id && !k.ipb_member_id) k.ipb_member_id = q.ipb_member_id;\n      if (q.ipb_pass_hash && !k.ipb_pass_hash) k.ipb_pass_hash = q.ipb_pass_hash;\n    } catch (e) {}\n  }\n  if (EH_commitCreds(k, '浏览器登录')) return;\n  EH_toast('⚠ 未读到 ipb_member_id \/ ipb_pass_hash。请确认已在内置浏览器里登录成功（页面显示用户名），或改用下方「粘贴 Cookie」通道');\n}\n\/\/ 主入口：先直接 POST，遇 CF 自动转内置浏览器\nfunction EH_pwdLogin() {\n  var c = EH_creds();\n  if (!c.u || !c.p) { EH_toast('⚠ 请先在上面填「账号」和「密码」'); return; }\n  EH_toast('🔐 正在用账号密码登录论坛…');\n  var r = EH_tryPost(c);\n  if (r.ok) { EH_commitCreds(r.keep, '账号密码'); return; }\n  EH_toast('⚠ 直接登录未成功：' + r.reason + ' ⇒ 转为内置浏览器登录');\n  EH_browserLogin();\n}\nfunction EH_loginStatus() {\n  try {\n\n    var c = EH_creds();\n    return (\"'🔐 账号 \" + (c.u ? '已填(' + c.u.slice(0, 8) + '…)' : '未填') +\n           ' \/ 密码 ' + (c.p ? '已填' : '未填') + \" · 点我用账号密码登录'\") || \"'🔐 用账号密码登录'\";\n\n  } catch (eVN) { return \"'🔐 用账号密码登录'\"; }\n}\n\n\/\/ ==================== 📝 账号注册（261107 新增） ====================\n\/\/ 官方入口（EHWiki \/ 注册教程 \/ e站食用指北 三方一致）：\n\/\/   https:\/\/forums.e-hentai.org\/index.php?act=Reg&CODE=00  （论坛 → Register → 同意条款 → 填表）\n\/\/   字段：Login Username（登录用，唯一） \/ Display Name（显示名） \/ Password \/ Email \/ Security Code(CAPTCHA)\n\/\/   ★ EHWiki：注册免费、不发验证邮件、账号立即可用；但每人只允许 1 个活跃账号，多开会被封。\n\/\/   ★ 论坛域被 Cloudflare Turnstile 挡（本次实测 GET\/POST 一律 403 challenge）+\n\/\/     注册表单含 CAPTCHA ⇒ **纯 HTTP 自动注册不可行**，必须内置浏览器人工完成。\n\/\/ 源码实锤：WebViewActivity.onPageFinished 每次把 android.webkit.CookieManager 的 Cookie\n\/\/   回写 CookieStore.setCookie ⇒ startBrowserAwait 回来后 EH_readBrowserCookie 能读到 ipb_*。\nvar EH_REG_URL = 'https:\/\/forums.e-hentai.org\/index.php?act=Reg&CODE=00';\nvar EH_FORGOT_URL = 'https:\/\/forums.e-hentai.org\/index.php?act=Reg&CODE=10';\n\n\/\/ 打开注册页（内置浏览器；用户手动过盾 + 同意条款 + 填表 + 输验证码 + 提交）\nfunction EH_register() {\n  EH_toast('📝 正在打开论坛注册页…请先把浏览器首选语言设为 English；依次完成 Cloudflare 验证 → 勾选同意条款 → 填写 Login Username\/Display Name\/Password\/Email\/Security Code → 提交');\n  try {\n    \/\/ refetchAfterSuccess=false：只取回页面，不额外重发（避免刚注册完又撞 CF）\n    var r = java.startBrowserAwait(EH_REG_URL, '📝 E-Hentai 账号注册（过CF+填表+验证码）', false);\n    EH_toast('📝 注册页已返回（' + String(r.body()).length + 'B）。★注册成功后请在同一浏览器里登录一次，回到本面板点「🔄 读取登录 Cookie」');\n  } catch (e) {\n    EH_toast('❌ 打开注册页失败：' + String(e).slice(0, 90));\n  }\n}\n\/\/ 忘记密码（官方找回表单，同一浏览器通道）\nfunction EH_forgot() {\n  EH_toast('🔑 正在打开找回密码表单（需用户名或注册邮箱）…');\n  try {\n    var r = java.startBrowserAwait(EH_FORGOT_URL, '🔑 E-Hentai 找回用户名\/密码', false);\n    EH_toast('🔑 找回页已返回（' + String(r.body()).length + 'B）');\n  } catch (e) {\n    EH_toast('❌ 打开失败：' + String(e).slice(0, 90));\n  }\n}\n\/\/ 注册\/登录后的收尾：读 Cookie → 写回源 → 验证连通（一条龙）\nfunction EH_afterBrowser() {\n  EH_readBrowserCookie();\n  \/\/ 顺便测一次，给用户明确反馈\n  var body = '';\n  try { body = String(java.ajax(EH_ajaxUrl('\/'))); } catch (e) { body = ''; }\n  if (body.length > 5000 && body.indexOf('<') === 0) {\n    EH_toast('✅ 验证通过：拿到 ' + body.length + 'B 首页 HTML，账号可用！请重新进入订阅页');\n  } else {\n    EH_toast('⚠ 首页仍是空白（' + body.length + 'B）。新注册账号可能需等待审核\/换干净 IP，或点「🩺 一键自愈」');\n  }\n}\n\n\/\/ ==================== 261109 v3 增量：分辨率档位 \/ 线路测速 \/ 登录态检查 ====================\n\/\/ 兼容要点（源码级实锤，261109 用户纠正后复核）：\n\/\/  ★★ E 版默认分支是 main（不是 master！）。main 的 RowUi.kt 明确支持\n\/\/    select\/toggle\/viewName\/chars\/default，SourceLoginDialog.kt 与官方版同尺寸(34267B)；\n\/\/    实例「🍥哔咔漫画」loginUi 里就有 {\"type\":\"select\",\"chars\":[...],\"default\":...,\"action\":...}。\n\/\/    ⇒ 分辨率选择只用 select 下拉（双版本都可用）。\n\/\/  ★ select 选中值写进 loginInfo[name]，handleButtonClick 的 result=getLoginData()\n\/\/    = viewModel.loginInfo.toMutableMap() ⇒ action 里读 result['ehResSelect'] 即当前档位。\n\/\/    （哔咔实例读 infoMap[...] 亦可，二者都绑；本源统一用 result 通道，与 EH_input 一致。）\n\/\/  ★ 面板 action 环境 java = SourceLoginJsExtensions（ajax\/connect\/toast\/startBrowserAwait 可用），\n\/\/    且 source.put\/get 可落库（261107 已实锤，沿用现有 EH_set\/EH_gv）。\n\/\/  ★ POST \/uconfig.php 是写账号设置：必须用户主动点「同步站点档位」才发，绝不静默改。\n\/\/  ★ 裸 Cookie 覆盖 uconfig\/xres 无效（261109 三组对照实锤）⇒ 服务端分辨率只能靠 POST。\n\/\/  ★ 站点真实表单：xr=0自动 \/ 1=780x \/ 3=1280x（4\/5 需 Hath 已 disabled）；ts=0\/1\/2。\n\n\/\/ ---- 分辨率档位表 ----\n\/\/ hd   = ruleContent 阶段服务端内联高清张数（Rhino java.ajax，两版本都可用；60 封顶防站方限频）\n\/\/ win  = WebView 视口懒加载并发窗口\n\/\/ gap  = 每次 \/s\/ 请求间隔 ms（越小越快但越易触发站方短时限频）\n\/\/ margin = IntersectionObserver 预取视口外边距\n\/\/ orig = 是否在每格附「原图」链接\n\/\/ xr\/ts = 同步到站点 uconfig.php 的真实表单值\n\/\/ ★ HD_MAX 克制在 0\/6\/15\/25：站方对短时密集 \/s\/ 请求敏感（限频约 24h），\n\/\/   不因「超清档」把并发拉到 60 张；首屏之外的图一律交给 WebView 视口懒加载。\nvar EH_RES = [\n  { k: '0', n: '省流·缩略图≈250px', hd: 0,  gap: 1000, margin: '0px',    xr: '1', ts: '0' },\n  { k: '1', n: '自动·均衡≈780x',     hd: 6,  gap: 700,  margin: '600px',  xr: '0', ts: '0' },\n  { k: '2', n: '高清·1280x(WiFi)',   hd: 15, gap: 450,  margin: '1200px', xr: '3', ts: '2' },\n  { k: '3', n: '超清·全量·大屏',     hd: 25, gap: 350,  margin: '1800px', xr: '3', ts: '2' }\n];\nfunction EH_resCfg() {\n  \/\/ 主存档 ehRes（由 select 的 EH_setRes 写入）；\n  \/\/ 兼容：若面板 select 已选但 action 未触发，可从 result\/infoMap 的标签反查档位。\n  \/\/ ★261114 v5.3b：整个函数体包 try\/catch —— 本函数在 viewName 求值环境里被\n  \/\/   EH_resStatus\/EH_status 调用，任何一步异常都会让整个求值串变空 → 按钮显示 \"null\"。\n  try {\n    var k = EH_gv('ehRes', '1');\n    var label = '';\n    try { if (typeof result !== 'undefined' && result != null) {\n            if (typeof result.get === 'function') { try { label = String(result.get('ehResSelect') || ''); } catch (e00) {} }\n            if (!label) { try { label = String(result['ehResSelect'] || ''); } catch (e01) {} }\n          } } catch (e0) {}\n    if (!label) { try { if (typeof infoMap !== 'undefined' && infoMap) label = String(infoMap['ehResSelect'] || ''); } catch (e1) {} }\n    if (label) {\n      for (var i = 0; i < EH_RES.length; i++) {\n        if (label.indexOf(EH_RES[i].n) >= 0) { k = EH_RES[i].k; break; }\n      }\n    }\n    for (var j = 0; j < EH_RES.length; j++) if (EH_RES[j].k === k) return EH_RES[j];\n    return EH_RES[1];\n  } catch (eBig) { return EH_RES[1]; }\n}\n\/\/ select 的默认档位（供状态\/说明用；default 字段本身是纯字符串，不走 JS 求值）\nfunction EH_resDefault() {\n  return EH_resCfg().n;\n}\n\/\/ ① 分辨率选择：select 下拉的 action（用户切换档位时由 SourceLoginDialog 自动回调）\n\/\/    ★ 选中值经 loginInfo → result 传来；同时兼容 infoMap 与 source.get 三通道。\nfunction EH_setRes() {\n  EH_toast('🖼 正在应用分辨率档位…');\n  var label = '';\n  try { label = String(result['ehResSelect'] || ''); } catch (e0) { label = ''; }\n  if (!label) { try { label = String(infoMap['ehResSelect'] || ''); } catch (e1) { label = ''; } }\n  if (!label) { try { label = String(result.get ? result.get('ehResSelect') : '') || ''; } catch (e2) { label = ''; } }\n  var hit = null;\n  for (var i = 0; i < EH_RES.length; i++) if (label.indexOf(EH_RES[i].n) >= 0) hit = EH_RES[i];\n  if (!hit) {\n    EH_toast('⚠ 未识别档位「' + label + '」，保持当前设置；请重新打开面板再选一次');\n    return;\n  }\n  EH_set('ehRes', hit.k);\n  EH_set('ehResLabel', hit.n);\n  EH_toast('✅ 已选【' + hit.n + '】：首屏内联 ' + hit.hd + ' 张高清 \/ 视口懒加载串行间隔 ' +\n           hit.gap + 'ms \/ 预取 ' + hit.margin + ' \/ 仅加载当前可见页');\n  EH_toast((hit.hd === 0 ? 'ℹ 省流档只用 250px 缩略图、不请求 \/s\/ 页，最不易触发站方限频；' :\n            'ℹ 高清档会在图片进入视口时按间隔逐张请求 \/s\/ 页，网络差建议降档；') +\n           '★已打开的文章有正文缓存，请退出重进本文或清存档后生效');\n  EH_toast('🌐 若要站方 \/s\/ 页直接返回该分辨率，再点「⚙ 同步站点分辨率(xr)」（会写入账号 uconfig，需你主动点）');\n}\nfunction EH_resStatus() {\n  try {\n\n    var c = EH_resCfg(), s = EH_gv('ehXR', '未同步');\n    var L = EH_gv('ehResLabel', '');\n    return (\"'📊 当前：分辨率【\" + c.n + \"】\" + (L && L !== c.n ? '(存:' + L + ')' : '') +\n           \" · 每行\" + EH_cols() + \"图 · 站点xr=\" + s + \"'\") || \"'📊 分辨率状态'\";\n\n  } catch (eVN) { return \"'📊 分辨率状态'\"; }\n}\n\/\/ ② 同步站点分辨率（POST \/uconfig.php；全阶段 Toast + 回读确认）\nfunction EH_uconfigSync() {\n  var c = EH_resCfg(), d = EH_host();\n  EH_toast('⚙ 开始同步站点分辨率：准备向 ' + d + '\/uconfig.php 提交 xr=' + c.xr + ' ts=' + c.ts +\n           '（会修改你的 E-Hentai 账号设置，仅本次主动点击执行）…');\n  var body = 'xr=' + c.xr + '&ts=' + c.ts + '&apply=Apply';\n  var opt = '{\"method\":\"POST\",\"body\":' + JSON.stringify(body) +\n            ',\"headers\":{\"User-Agent\":' + JSON.stringify(EH_UA) +\n            ',\"Cookie\":' + JSON.stringify(EH_cookie(EH_gv('ehCookie', ''))) +\n            ',\"Content-Type\":\"application\/x-www-form-urlencoded\"' +\n            ',\"Referer\":' + JSON.stringify(d + '\/uconfig.php') + '}}';\n  EH_toast('⚙ 正在提交设置（最多 20s）…');\n  var r = '', b = '';\n  try { r = java.connect(d + '\/uconfig.php,' + opt, '', 20000); b = String(r.body()); }\n  catch (e) { EH_toast('❌ 提交失败：' + String(e).slice(0, 90) + ' ⇒ 检查代理\/Cookie，或改用面板档位（客户端侧已生效，不影响使用）'); return; }\n  if (\/Just a moment|cf-mitigated|challenge\/i.test(b)) {\n    EH_toast('❌ 被 Cloudflare 挑战挡下 ⇒ 请用「🌐 打开论坛登录」过盾后重试；面板档位不受影响'); return;\n  }\n  if (!\/Settings were updated|Settings Updated\/i.test(b)) {\n    EH_toast('⚠ 未读到「Settings were updated」（' + b.length + 'B）⇒ 可能 Cookie 失效或账号无权限；面板档位仍已生效'); return;\n  }\n  EH_toast('✅ 站点设置已更新：xr=' + c.xr + '（' + (c.xr === '0' ? '自动1280x\/移动780x' : c.xr === '1' ? '780x 省流' : '1280x 高清') +\n           '）、ts=' + c.ts + '；正在回读确认…');\n  \/\/ 回读确认（只读，一次）\n  var chk = '';\n  try {\n    var h = { 'User-Agent': EH_UA, 'Cookie': EH_cookie(EH_gv('ehCookie', '')), 'Referer': d + '\/uconfig.php' };\n    chk = String(java.ajax(d + '\/uconfig.php,{\"headers\":' + JSON.stringify(h) + '}'));\n  } catch (e2) { chk = ''; }\n  var m = chk.match(\/<input[^>]*name=[\"']xr[\"'][^>]*checked[^>]*>\/i);\n  var got = m ? ((m[0].match(\/value=[\"'](\\d+)[\"']\/i) || [])[1] || '?') : '?';\n  EH_set('ehXR', got);\n  EH_toast(got === c.xr ? '✅ 回读确认成功：站点当前 xr=' + got + '，已与面板档位一致。★退出重进文章即可看到新分辨率'\n                        : '⚠ 回读 xr=' + got + '（期望 ' + c.xr + '）⇒ 账号可能禁用该档(4\/5需Hath)，已记录；面板档位仍可用');\n  EH_refresh();\n}\n\n\/\/ ---- 线路测速 \/ 切换（发布页式管理：测得的 ms 写进按钮文字；各阶段 Toast）----\n\/\/ 站点无官方「网址发布页」，只有表站\/里站两条固定线路 + 用户自填代理；\n\/\/ 这里把原「站点切换」升级为「测速 → 点击切换」的线路面板，满足线路选择诉求。\nfunction EH_pingOne(host) {\n  var u = host + '\/,{\"headers\":' + JSON.stringify({ 'User-Agent': EH_UA, 'Cookie': EH_cookie(EH_gv('ehCookie', '')) }) + '}';\n  var t0 = new Date().getTime(), b = '';\n  try { b = String(java.connect(u, '', 8000)); }\n  catch (e) { return { ms: -1, len: 0, err: String(e).slice(0, 50) }; }\n  var ms = new Date().getTime() - t0;\n  if (\/Exception|timeout|Failed to connect|SSLHandshake\/i.test(b.slice(0, 200))) return { ms: ms, len: 0, err: '网络异常' };\n  return { ms: ms, len: b.length, err: '' };\n}\nfunction EH_testDelay() {\n  EH_toast('⚡ 开始测试两条线路延时（各 1 次请求，间隔 800ms，不会频繁访问）…');\n  var hs = ['https:\/\/exhentai.org', 'https:\/\/e-hentai.org'], rec = [], i;\n  for (i = 0; i < hs.length; i++) {\n    EH_toast('⚡ 正在测 (' + (i + 1) + '\/2) ' + hs[i] + ' …');\n    var r = EH_pingOne(hs[i]);\n    rec.push({ h: hs[i], ms: r.ms, len: r.len, err: r.err });\n    EH_toast('⚡ ' + hs[i] + ' → ' + (r.ms >= 0 ? r.ms + 'ms' : '超时') +\n             ' \/ ' + r.len + 'B' + (r.err ? '（' + r.err + '）' : ''));\n    if (i === 0) { try { Packages.java.lang.Thread.sleep(800); } catch (eS) {} }\n  }\n  var ok = [];\n  for (i = 0; i < rec.length; i++) if (rec[i].ms >= 0 && rec[i].len > 1000) ok.push(rec[i]);\n  EH_set('ehPingEx', String((rec[0].ms >= 0 ? rec[0].ms : -1)));\n  EH_set('ehPingTable', String((rec[1].ms >= 0 ? rec[1].ms : -1)));\n  EH_set('ehPingText', '里站 ' + (rec[0].ms >= 0 ? rec[0].ms + 'ms' : '超时') +\n                       ' \/ 表站 ' + (rec[1].ms >= 0 ? rec[1].ms + 'ms' : '超时'));\n  EH_toast(ok.length ? '✅ 测速完成：' + EH_gv('ehPingText', '') + '。★延时已写入下方两个线路按钮，点按钮即切换（建议选 B 数大且延时小的）'\n                     : '❌ 两条线路都没拿到完整页 ⇒ 检查 VPN \/ 代理，或点「🩺 一键自愈」；测速结果仍已记录');\n  EH_refresh();\n}\nfunction EH_hostBtn(n) {\n  try {\n\n    var d = (Number(n) === 1) ? '里站 exhentai.org' : '表站 e-hentai.org';\n    var ms = EH_gv(Number(n) === 1 ? 'ehPingEx' : 'ehPingTable', '');\n    var cur = (EH_host().indexOf(Number(n) === 1 ? 'exhentai' : 'e-hentai') >= 0) ? '【当前】' : '';\n    return (\"'\" + cur + d + (ms !== '' && ms !== '-1' ? ' · ' + ms + 'ms' : ' · 未测速') + \"（点击切换）'\") || \"'切换线路'\";\n\n  } catch (eVN) { return \"'切换线路'\"; }\n}\nfunction EH_switchHostEx(n) {\n  EH_toast('🔀 正在切换线路到 ' + (Number(n) === 1 ? '里站 exhentai.org' : '表站 e-hentai.org') + '…');\n  EH_switchHost(n);   \/\/ 复用原函数（内含 Toast + refresh）\n  EH_toast('ℹ 切换完成。★重新进入订阅\/发现页生效；建议点「⚡ 测试各线路延时」复核新线路');\n}\n\n\/\/ ---- ③ 明确的「检查登录状态」按钮（须登录站点要求）----\nfunction EH_checkLogin() {\n  var d = EH_host();\n  EH_toast('✅ 正在检查登录状态：请求 ' + d + ' 首页（单次，不触发搜索）…');\n  var body = '';\n  try { body = String(java.ajax(EH_ajaxUrl('\/'))); }\n  catch (e) { EH_toast('❌ 请求异常：' + String(e).slice(0, 80) + ' ⇒ 检查 VPN\/代理'); return; }\n  if (\/Exception|timeout|Failed to connect|SSLHandshake\/i.test(body.slice(0, 200))) {\n    EH_toast('❌ 网络不通（DNS 污染 \/ SNI 封锁 \/ 代理未生效）⇒ 填代理或换 VPN 节点'); return;\n  }\n  if (\/temporarily banned|excessive request|Your IP address has been\/i.test(body)) {\n    EH_toast('❌ 出口 IP 被站方限频（约 24h 解封）⇒ 换 VPN 节点，勿频繁刷新'); return;\n  }\n  if (body.length < 1000) {\n    EH_toast('❌ 空白页（' + body.length + 'B）⇒ 先在上方粘贴【你自己账号】的完整 Cookie 并点「💾 保存 Cookie」；再点「🔄 刷新 igneous」。★本源不再内置任何测试账号'); return;\n  }\n  if (\/Sad Panda|sadpanda|You do not have access\/i.test(body)) {\n    EH_toast('❌ Sad Panda（账号无里站权限）⇒ 需已审核的 E-Hentai 账号 Cookie'); return;\n  }\n  var g = (body.match(\/\\\/g\\\/\\d+\\\/[0-9a-f]{10}\\\/\/g) || []).length;\n  var logged = \/ipb_member_id|logged in|Log Out|logout|My Home|Hath Perks\/i.test(body);\n  EH_toast('✅ 已登录且可用：' + body.length + 'B 首页 \/ ' + g + ' 个画廊' +\n           (logged ? ' \/ 页面含登录态标识' : '') + '。★可正常浏览；分辨率档位=' + EH_resCfg().n);\n}\n\/\/ ---- ⑧ 发布页输入框 → 一键取线路（E-Hentai 无官方「网址发布页」，明确降级不臆造） ----\nfunction EH_fromPub() {\n  EH_toast('🔗 正在读取「发布页网址输入框」…');\n  var u = EH_input('ehPubInput').trim();\n  if (!u) {\n    EH_toast('ℹ 输入框为空：E-Hentai \/ ExHentai 官方没有「网址发布页」，只有表站\/里站两条固定线路；已为你改用下方两条，并开始测速…');\n    EH_testDelay();\n    return;\n  }\n  if (!\/^https?:\\\/\\\/\/i.test(u)) u = 'https:\/\/' + u;\n  EH_toast('🔗 读到自定义线路：' + u + '，正在测速（单次，不频繁访问）…');\n  var r = EH_pingOne(u.replace(\/\\\/+$\/, ''));\n  if (r.ms >= 0 && r.len > 1000) {\n    EH_set('ehHost', u.replace(\/\\\/+$\/, ''));\n    EH_set('ehPingText', '自定义 ' + r.ms + 'ms');\n    EH_toast('✅ 自定义线路可用：' + r.ms + 'ms \/ ' + r.len + 'B，已切换为当前站点；请重新进入订阅页');\n  } else {\n    EH_toast('❌ 自定义线路不可用（' + (r.ms >= 0 ? r.ms + 'ms \/ ' + r.len + 'B' : '超时') +\n             '）⇒ 未切换，仍用当前线路；可点「⚡ 测试各线路延时」核对表\/里站');\n  }\n  EH_refresh();\n}\n\n\n\/\/ ==================== 📁 正文显示收藏夹（261113 v4 新增） ====================\n\/\/ 开关存 ehFavPanel（'1'=正文信息下方显示收藏夹列表 \/ '0'=不显示）。\n\/\/ 正文 ruleContent 读该变量：开启时才请求 1 次 favorites.php 抓夹名单，\n\/\/ 渲染 10 个夹子 chip；点击 chip 页内直接 POST gallerypopups.php addfav\n\/\/ （加入\/移出即时生效，不重载正文、不失效缓存）。\nfunction EH_favToggle() {\n  var cur = EH_gv('ehFavPanel', '0');\n  var nw = (cur === '1') ? '0' : '1';\n  EH_set('ehFavPanel', nw);\n  EH_toast(nw === '1'\n    ? '📁 已开启：正文信息下方将显示你的收藏夹列表（点击夹子加入\/再点移出当前画廊）。★已打开的文章有正文缓存，退出重进生效'\n    : '📁 已关闭：正文不再显示收藏夹列表。★已打开的文章退出重进生效');\n  EH_refresh();\n}\nfunction EH_favBtn() {\n  try {\n\n    var on = EH_gv('ehFavPanel', '0') === '1';\n    return (\"'📁 正文显示收藏夹：\" + (on ? '开（点击关闭）' : '关（点击开启）') + \"'\") || \"'📁 正文显示收藏夹'\";\n\n  } catch (eVN) { return \"'📁 正文显示收藏夹'\"; }\n}\n\n\n\/\/ ==================== 🏷 tag 点击模式（261114 v5 新增） ====================\n\/\/ 两个独立开关：ehTagSearch（点 tag 名字搜索对应 tag）\/ ehTagWatch（点 ＋\/✓ 订阅对应 tag）。\n\/\/ 可组合：都开时名字可点搜索、旁边独立 chip 订阅，互不抢点击。\n\/\/ 订阅协议实锤：POST \/mytags add(tagwatch_new=on) \/ mass(modify_usertags[]=id) 取消；\n\/\/ 两者都返回 302，成功判据=回读 \/mytags 出现\/消失（沙盒真机闭环）。\nfunction EH_tagToggle(k) {\n  var nw = (EH_gv(k, '0') === '1') ? '0' : '1';\n  EH_set(k, nw);\n  var n = (k === 'ehTagSearch') ? '点 tag 搜索' : '订阅 tag';\n  EH_toast('🏷 ' + n + '：' + (nw === '1' ? '已开启' : '已关闭') +\n           '。★已打开的文章有正文缓存，退出重进生效');\n  EH_refresh();\n}\nfunction EH_tagBtn(k, name) {\n  try {\n\n    var on = EH_gv(k, '0') === '1';\n    return (\"'🏷 \" + name + \"：\" + (on ? '开（点击关闭）' : '关（点击开启）') + \"'\") || \"'🏷 tag 开关'\";\n\n  } catch (eVN) { return \"'🏷 tag 开关'\"; }\n}\n\n\n\/\/ ==================== 🔄 刷新 igneous（261114 v5.1 登录态持久化） ====================\n\/\/ 站方对「只发两项」的请求会 Set-Cookie 一个 30 天有效 igneous（同账号重签同值）。\n\/\/ enabledCookieJar=false 后不再自动回存，故这里用 java.connect（返回 StrResponse，\n\/\/ 有 headers()）抓 Set-Cookie 存进源变量 ehIgneous；header\/loginCheckJs\/正文四处统一读它。\n\/\/ ★ 绝不存 'mystery'（站方 Sad Panda 页下发的失效值，带上就 0B 白屏）。\nfunction EH_refreshIgneous() {\n  EH_toast('🔄 正在请求里站首页，读取站方签发的有效 igneous…');\n  var u = EH_host() + '\/,{\"headers\":' + JSON.stringify({\n    'User-Agent': EH_UA,\n    'Cookie': EH_cookie(EH_gv('ehCookie', '')),\n    'Referer': 'https:\/\/exhentai.org\/'\n  }) + '}';\n  var hdr = '', body = '';\n  try {\n    var r = java.connect(u, '', 20000);\n    try { hdr = String(r.headers()); } catch (e1) { hdr = ''; }\n    try { body = String(r.body()); } catch (e2) { body = ''; }\n  } catch (e) { EH_toast('❌ 请求异常：' + String(e).slice(0, 90) + ' ⇒ 检查代理\/VPN'); return; }\n  var m = \/igneous=([A-Za-z0-9]{6,40})\/.exec(hdr);\n  if (m && m[1] !== 'mystery') {\n    EH_set('ehIgneous', m[1]);\n    EH_toast('✅ 已保存有效 igneous=' + m[1] + '（30天有效）。以后打开订阅源无需再清 Cookie 重登；退出重进文章生效');\n  } else if (body.length > 5000) {\n    \/\/ 站方这次没重签（说明请求里已带有效 igneous）→ 沿用当前值即可\n    var cur = EH_gv('ehIgneous', '');\n    EH_toast('✅ 首页正常（' + body.length + 'B）；站方这次未重签 igneous' +\n             (cur ? '，当前值 ' + cur.slice(0, 8) + '… 仍有效' : '，用空值占位即可，站方按需重签') + '，无需刷新');\n  } else {\n    EH_toast('⚠ 未读到有效 igneous（首页 ' + body.length + 'B）⇒ 点「🩺 一键自愈」后重试，或粘贴浏览器完整 Cookie');\n  }\n  EH_refresh();\n}\nfunction EH_igBtn() {\n  try {\n\n    var ig = EH_gv('ehIgneous', '');\n    return (\"'🔄 刷新 igneous（当前 \" + (ig ? ig.slice(0, 8) + '…' : '空值占位·站方自动重签') + \"）'\") || \"'🔄 刷新 igneous'\";\n\n  } catch (eVN) { return \"'🔄 刷新 igneous'\"; }\n}\n\n\n\/\/ ==================== 🩺 登录态一键诊断（261114 v5.2） ====================\n\/\/ 用户报「收藏夹获取失败 \/ 订阅tag失败：检查登录状态 \/ 关注列表为空」时的定位按钮：\n\/\/ 分别打 \/watched \/favorites.php \/mytags，回报每页大小与站方提示，区分\n\/\/ 「没配Cookie」\/「igneous 无效」\/「该账号确实没有 watched tag」三种情况。\nfunction EH_diagLogin() {\n  var ck = EH_gv('ehCookie', '');\n  if (ck.indexOf('ipb_member_id=') < 0 || ck.indexOf('ipb_pass_hash=') < 0) {\n    EH_toast('❌ 未检测到你自己的 Cookie ⇒ 请在面板「① 里站 Cookie」粘贴浏览器登录后复制的完整串并点「💾 保存 Cookie」。★本源不内置任何账号');\n    return;\n  }\n  var ig = EH_gv('ehIgneous', '');\n  EH_toast('🩺 正在诊断：Cookie 已配置（member=' + (\/ipb_member_id=(\\d+)\/.exec(ck) || [])[1] +\n           '），igneous=' + (ig ? ig.slice(0, 8) + '…' : '空值占位') + '；依次请求 watched\/favorites\/mytags…');\n  var paths = ['\/watched', '\/favorites.php', '\/mytags'], res = [], i;\n  for (i = 0; i < paths.length; i++) {\n    var b = '';\n    try { b = String(java.ajax(EH_ajaxUrl(paths[i]))); } catch (e) { b = String(e); }\n    var tag = \/Exception|timeout|Failed to connect\/i.test(b.slice(0, 200)) ? '网络异常' :\n              \/temporarily banned|excessive request\/i.test(b) ? 'IP限频' :\n              b.length < 1000 ? '空白页(登录态无效)' :\n              \/You do not have any watched tags\/i.test(b) ? '该账号确实没有 watched tag' :\n              \/No hits found\/i.test(b) ? '页内无结果' : '正常(' + b.length + 'B)';\n    res.push(paths[i] + '→' + tag);\n    if (i < paths.length - 1) { try { Packages.java.lang.Thread.sleep(400); } catch (eS) {} }\n  }\n  EH_toast('🩺 诊断结果：' + res.join(' \/ ') +\n           '。★若 watched 报「没有 watched tag」= 订阅没成功，请先在正文点 tag 旁的 ＋ 订阅，或到站内 My Tags 添加');\n}\nfunction EH_diagBtn() {\n  return \"'🩺 登录态一键诊断（区分没配Cookie\/igneous无效\/无watched标签）'\" || \"'🩺 登录态一键诊断'\";\n}\n","preload":false,"preloadJs":"window.java=java;window.source=source;window.cache=cache;","ruleArticles":"@css:table.itg > tbody > tr:not(:has(th)), table.itg > tr:not(:has(th))","ruleContent":"@js:\n(function () {\n  \/\/ ============================================================\n  \/\/ ExHentai 订阅源 · 正文规则（画廊详情页 → 自拼纯净 HTML）\n  \/\/ 数据源 = result（App 已按 header.js 的 proxy\/Cookie 抓好的内容页 HTML 字符串）\n  \/\/ 结构依据：Venera ehentai.js \/ H-Viewer 规则 \/ TachiyomiSY \/ RSSHub 源码 +\n  \/\/           Keenable 实时页面文本（2026-11-07 交叉实锤）\n  \/\/ ★ 不写死页码上限：后续缩略图页数完全由站点 #gdd 的 \"N pages\" 与\n  \/\/   table.ptb 的最大 ?p=N 推导，站点以后加页会自动跟着变。\n  \/\/ ============================================================\n  var NL = String.fromCharCode(10);\n  var raw = '';\n  try { raw = (result && result.html) ? String(result.html()) : String(result); } catch (e) { raw = String(result); }\n  var pageUrl = String(baseUrl);\n\n  function esc(s) {\n    return String(s == null ? '' : s)\n      .replace(\/&\/g, '&amp;').replace(\/<\/g, '&lt;').replace(\/>\/g, '&gt;')\n      .replace(\/\"\/g, '&quot;').replace(\/'\/g, '&#39;');\n  }\n  function stripTags(s) {\n    return String(s == null ? '' : s).replace(\/<[^>]*>\/g, '')\n      .replace(\/&nbsp;\/g, ' ').replace(\/&amp;\/g, '&').replace(\/&lt;\/g, '<')\n      .replace(\/&gt;\/g, '>').replace(\/&quot;\/g, '\"').replace(\/&#39;\/g, \"'\")\n      .replace(\/\\s+\/g, ' ').trim();\n  }\n  function pick(re, def) { var m = re.exec(raw); return m ? stripTags(m[1]) : (def || ''); }\n  function gv(k, def) {\n    var v = '';\n    try { v = String(source.get(k)); } catch (e) { v = ''; }\n    if (v === 'null' || v === 'undefined' || v === 'NaN' || v === '') v = def || '';\n    return v;\n  }\n  \/\/ ---- 0. 诊断闸门：把三类伪装错误页直接变成可读提示（WS 调试据此定位） ----\n  if (!raw || raw.length < 500) {\n    return '<div style=\"padding:18px;color:#c33;line-height:1.8\">' +\n      '<h3>⚠ 内容页为空（' + raw.length + 'B）<\/h3>' +\n      '典型原因：<b>Sad Panda 登录墙（空白页）<\/b> \/ 里站 Cookie 无效。<br>' +\n      '请到登录面板粘贴含 <code>igneous<\/code> 的 Cookie，并配置可访问里站的代理。' +\n      '<p><a href=\"' + esc(pageUrl) + '\">打开原页面<\/a><\/p><\/div>';\n  }\n  if (\/temporarily banned|excessive request rate|Your IP address has been banned\/i.test(raw)) {\n    return '<div style=\"padding:18px;color:#c33;line-height:1.8\"><h3>⚠ 出口 IP 被站方限频<\/h3>' +\n      'E-Hentai 对自动抓取很敏感（通常封 24h）。请换代理出口或稍后再试，勿频繁刷新。' +\n      '<p><a href=\"' + esc(pageUrl) + '\">打开原页面<\/a><\/p><\/div>';\n  }\n  if (\/Sad Panda|sadpanda|You do not have access|back to the E-Hentai Galleries\/i.test(raw)) {\n    return '<div style=\"padding:18px;color:#c33;line-height:1.8\"><h3>⚠ Sad Panda（未登录里站）<\/h3>' +\n      'ExHentai 需要 <code>ipb_member_id \/ ipb_pass_hash \/ igneous<\/code> 三项 Cookie。' +\n      '请到登录面板粘贴浏览器里已登录的完整 Cookie。<\/div>';\n  }\n  if (raw.indexOf('id=\"gdt\"') < 0 && raw.indexOf('class=\"itg') < 0) {\n    return '<div style=\"padding:18px;color:#c33;line-height:1.8\"><h3>⚠ 不是画廊详情页（' + raw.length + 'B）<\/h3>' +\n      '抓到的页面里没有 <code>#gdt<\/code> 缩略图容器，可能是重定向到论坛登录页或 Cloudflare 挑战页。' +\n      '<p><a href=\"' + esc(pageUrl) + '\">打开原页面<\/a><\/p><\/div>';\n  }\n\n  \/\/ ---- 1. 元数据（全部纯字符串正则，跨 LT\/E 两版 Rhino 无 jsoup 依赖） ----\n  var title    = pick(\/<h1[^>]*id=[\"']gn[\"'][^>]*>([\\s\\S]*?)<\\\/h1>\/i, '');\n  var titleJp  = pick(\/<h1[^>]*id=[\"']gj[\"'][^>]*>([\\s\\S]*?)<\\\/h1>\/i, '');\n  var category = pick(\/<div[^>]*class=[\"'][^\"']*\\bcs\\b[^\"']*[\"'][^>]*>([\\s\\S]*?)<\\\/div>\/i, '');\n  var uploader = pick(\/<div[^>]*id=[\"']gdn[\"'][^>]*>([\\s\\S]*?)<\\\/div>\/i, '');\n  var rating   = pick(\/<[^>]*id=[\"']rating_label[\"'][^>]*>([\\s\\S]*?)<\\\/[^>]+>\/i, '');\n  var favCount = pick(\/<[^>]*id=[\"']favcount[\"'][^>]*>([\\s\\S]*?)<\\\/[^>]+>\/i, '');\n  \/\/ ---- 1.5 收藏夹：画廊身份(gid\/token) + 当前收藏态（261113 真实页面结构实锤）----\n  \/\/ 已收藏态：<div id=\"gdf\"> 内 <a id=\"favoritelink\">夹名<\/a>；未收藏=文本 \"Add to Favorites\"\n  \/\/ 辅助判据：id=\"fav\" 图标 div 的 title=\"夹名\"（未收藏时 fav div 为空、无 title）\n  var favGid = '', favTok = '';\n  var gm = \/\\\/g\\\/(\\d+)\\\/([0-9a-f]{10})\\\/\/.exec(pageUrl);\n  if (gm) { favGid = gm[1]; favTok = gm[2]; }\n  var favCur = -1;          \/\/ 当前所在收藏夹索引（-1=未收藏；由夹名映射得出）\n  var favCurName = '';      \/\/ 当前所在收藏夹名（favoritelink 文本）\n  var gdfSeg = '';\n  var gdfAt = raw.indexOf('id=\"gdf\"');\n  if (gdfAt >= 0) gdfSeg = raw.substring(gdfAt, gdfAt + 900);\n  var flm = \/<a[^>]*id=[\"']favoritelink[\"'][^>]*>([\\s\\S]*?)<\\\/a>\/.exec(gdfSeg);\n  if (flm) {\n    var flTxt = stripTags(flm[1]);\n    if (flTxt && !\/Add to Favorites\/i.test(flTxt)) favCurName = flTxt;\n  }\n  if (!favCurName) {\n    var ftm = \/id=[\"']fav[\"'][^>]*>[\\s\\S]{0,260}?title=\"([^\"]*)\"\/.exec(gdfSeg);\n    if (ftm) favCurName = stripTags(ftm[1]);\n  }\n\n  \/\/ 封面：#gd1 区块到 <\/div>，内部 style 里的 url(...)；再兜底全页第一张 ehgt 图\n  var coverBig = '';\n  var gd1 = \/<div[^>]*id=[\"']gd1[\"'][^>]*>([\\s\\S]*?)<\\\/div>\/i.exec(raw);\n  var cm = gd1 ? \/url\\((?:[\"']?)([^)\"']+\\.(?:jpg|jpeg|png|webp|gif))(?:[\"']?)\\)\/i.exec(gd1[1]) : null;\n  if (!cm) cm = \/url\\((?:[\"']?)(https?:\\\/\\\/[^)\"'\\s]+\\.(?:jpg|jpeg|png|webp|gif))(?:[\"']?)\\)\/i.exec(raw);\n  if (cm) { coverBig = cm[1].trim(); }\n\n  \/\/ #gdd 表格里 Posted \/ File Size \/ Length(N pages) \/ Favorited\n  var posted = '', filesize = '', pagesTxt = '', filecount = 0;\n  var gdd = \/<div[^>]*id=[\"']gdd[\"'][^>]*>([\\s\\S]*?)<\\\/div>\/i.exec(raw);\n  var gdtHtml = gdd ? gdd[1] : raw;\n  var rowRe = \/<tr[^>]*>([\\s\\S]*?)<\\\/tr>\/gi, row;\n  while ((row = rowRe.exec(gdtHtml)) !== null) {\n    var cells = [], cRe = \/<td[^>]*>([\\s\\S]*?)<\\\/td>\/gi, c;\n    while ((c = cRe.exec(row[1])) !== null) cells.push(stripTags(c[1]));\n    if (cells.length >= 2) {\n      var k = cells[0].replace(\/:$\/, '').toLowerCase(), v = cells[1];\n      if (k === 'posted') posted = v;\n      else if (k === 'file size') filesize = v;\n      else if (k === 'length') {\n        pagesTxt = v;\n        var fm = \/(\\d+)\\s*pages?\/i.exec(v); if (fm) filecount = Number(fm[1]);\n      }\n    }\n  }\n  \/\/ 站点有时把 Length 放在 td.gdt2 而不成行\n  if (!filecount) { var fm2 = \/(\\d+)\\s*pages?\/i.exec(stripTags(gdtHtml)); if (fm2) filecount = Number(fm2[1]); }\n\n  \/\/ 标签（#taglist 每个 tr：namespace + 若干 a）\n  \/\/ ★261114 v5：本段只收集结构化 tagRows，不拼 HTML —— 开关(tagSearch\/tagWatch)、\n  \/\/   watchMap、origin 都在更后面才就绪，提前引用会因 var 提升得到 undefined。\n  \/\/   真正渲染在 §3.7（3.6 抓完 mytags 之后）。\n  var tagRows = [], tagHtml = '';\n  var ts = raw.indexOf('id=\"taglist\"');\n  if (ts < 0) ts = raw.indexOf(\"id='taglist'\");\n  if (ts >= 0) {\n    var te = raw.indexOf('id=\"gdt\"', ts);\n    if (te < 0) te = raw.indexOf('id=\"gd5\"', ts);\n    if (te < 0) te = Math.min(raw.length, ts + 20000);\n    tagHtml = raw.substring(ts, te);\n  }\n  var trRe = \/<tr[^>]*>([\\s\\S]*?)<\\\/tr>\/gi, tr;\n  while ((tr = trRe.exec(tagHtml)) !== null) {\n    var tcs = [], tdRe = \/<td[^>]*>([\\s\\S]*?)<\\\/td>\/gi, td;\n    while ((td = tdRe.exec(tr[1])) !== null) tcs.push(td[1]);\n    if (!tcs.length) continue;\n    var ns = stripTags(tcs[0]).replace(\/:$\/, '');\n    var picks = [], aRe = \/<a[^>]*href=\"([^\\\"]*\\\/tag\\\/[^\\\"]*)\"[^>]*>([\\s\\S]*?)<\\\/a>\/gi, a;\n    while ((a = aRe.exec(tcs.slice(1).join(''))) !== null) {\n      var tv = stripTags(a[2]);\n      if (!tv) continue;\n      var au = String(a[1]).replace(\/&amp;\/g, '&');\n      \/\/ tag 全名 = \/tag\/ 后串（+ → 空格），与页面 toggle_tagmenu 的第二参一致；\n      \/\/ mytags 的 tagpreview title 用的是同一形态，订阅态比对全靠它。\n      var fq = \/\\\/tag\\\/([^?#]*)\/.exec(au);\n      var full = fq ? fq[1].replace(\/\\+\/g, ' ') : (ns + ':' + tv);\n      picks.push({ t: tv, u: au, f: full });\n    }\n    if (ns && picks.length && ns.length > 1 && \/^[a-z]+$\/i.test(ns)) tagRows.push({ ns: ns, picks: picks });\n  }\n\n  \/\/ ---- 2. 缩略图解析（两种形态：gdtm 背景图 \/ gdtl img） ----\n  \/\/ 返回 [{thumb, page}]，page = \/s\/ 单图页绝对链接\n  function hostOf(u) { var m = \/^(https?:\\\/\\\/[^\/]+)\/.exec(u); return m ? m[1] : 'https:\/\/exhentai.org'; }\n  var origin = hostOf(pageUrl);\n  function abs(u) {\n    if (!u) return '';\n    if (\/^https?:\\\/\\\/\/i.test(u)) return u;\n    if (u.indexOf('\/\/') === 0) return 'https:' + u;\n    if (u.indexOf('\/') === 0) return origin + u;\n    return origin + '\/' + u;\n  }\n  function normImg(u) {\n    \/\/ 里站缩略图域 s.exhentai.org 归一到公开图床 ehgt.org（Venera normalizeThumbnailHost）\n    return String(u).replace(\/https?:\\\/\\\/s\\.exhentai\\.org\/gi, 'https:\/\/ehgt.org')\n                    .replace(\/https?:\\\/\\\/ehgt\\.org\/gi, 'https:\/\/ehgt.org');\n  }\n  function parseThumbs(html) {\n    var list = [], seen = {};\n    \/\/ 真实结构（imgbrd-grabber \/ Venera 实锤）：\n    \/\/   <div class=\"gdtm\"><div style=\"...background:transparent url(THUMB)...\">\n    \/\/     <a href=\"\/s\/HASH\/GID-N\"><img><\/a><\/div><\/div>\n    \/\/ 背景图与 <a> 是并列\/父子混合，故分别收集两个数组再按索引对齐。\n    var gs = html.indexOf('id=\"gdt\"');\n    if (gs < 0) gs = html.indexOf(\"id='gdt'\");\n    var block = gs >= 0 ? html.substring(gs) : html;\n    \/\/ 截到分页表之前，避免把其它区域的 \/s\/ 链接算进来\n    var pe = block.indexOf('class=\"ptb\"');\n    if (pe < 0) pe = block.indexOf(\"class='ptb'\");\n    if (pe > 0) block = block.substring(0, pe);\n\n    \/\/ ① 背景图池 + sprite 偏移（真实站点 gt200 布局：多页共用一张大图，\n    \/\/    靠 background:...url(SPRITE) -0px\/-200px\/-400px ... no-repeat 显示不同区域）\n    var bgPool = [], bgX = [], bgY = [], bgW = [], bgH = [];\n    \/\/ ★ 真实串：url(SPRITE) -0px 0 no-repeat —— 第二个数字常不带 px，必须兼容\n    var bgRe = \/url\\((?:[\"']?)([^)\"']+\\.(?:jpg|jpeg|png|webp|gif))(?:[\"']?)\\)\\s*(-?\\d+)px\\s+(-?\\d+)(?:px)?\/gi, bm;\n    while ((bm = bgRe.exec(block)) !== null) {\n      bgPool.push(normImg(abs(bm[1].trim())));\n      bgX.push(Number(bm[2])); bgY.push(Number(bm[3]));\n    }\n    \/\/ 尺寸从同一 style 里取（width:200px;height:283px）\n    var dimRe = \/style=[\"'][^\"']*width:(\\d+)px;height:(\\d+)px[^\"']*url\\(\/gi, dm2;\n    while ((dm2 = dimRe.exec(block)) !== null) { bgW.push(Number(dm2[1])); bgH.push(Number(dm2[2])); }\n    \/\/ 兜底：正则没带偏移时按 0\n    if (bgPool.length && bgX.length < bgPool.length) {\n      while (bgX.length < bgPool.length) { bgX.push(0); bgY.push(0); }\n    }\n    \/\/ ② img 池：gdtl \/ gt100 \/ gt200 等大缩略图布局\n    var imgPool = [], imRe = \/<img[^>]*\\s(?:data-src|src)=[\"']([^\"']+)[\"']\/gi, im;\n    while ((im = imRe.exec(block)) !== null) {\n      var iu = im[1];\n      if (iu.indexOf('data:') === 0 || iu.indexOf('\/g\/') > -1) continue;\n      imgPool.push(normImg(abs(iu)));\n    }\n    \/\/ ③ 链接池：#gdt 内所有 \/s\/ 单图页\n    var links = [], aRe = \/<a[^>]*href=[\"']([^\"']*\\\/s\\\/[a-f0-9]{8,12}\\\/[^\"']+)[\"']\/gi, am;\n    while ((am = aRe.exec(block)) !== null) {\n      var l = abs(am[1].replace(\/&amp;\/g, '&'));\n      if (seen[l]) continue;\n      seen[l] = 1; links.push(l);\n    }\n    \/\/ ④ 按索引对齐（bgPool 优先，imgPool 补位），并带上 sprite 裁切参数\n    \/\/ ★ 每个缩略图分页有自己独立的一张 sprite 图（合并多页时会有多张），\n    \/\/   ⇒ 按 thumb URL 分组，组内用「第 K 个切片 \/ 该组总数 N」算百分比，\n    \/\/     不能用全正文 thumbs.length（跨页会算错）。\n    \/\/ ============ sprite 裁切参数（实测结构 e-hentai gt200）============\n    \/\/ <a href=\"\/s\/HASH\/ID-N\"><div style=\"width:200px;height:283px;\n    \/\/   background:transparent url(SPRITE) -200px 0 no-repeat\"><\/div><\/a>\n    \/\/ 单行拼接：X 偏移按 200 递增，Y 恒 0，但每格宽度不同（200\/162\/168\/174…）\n    \/\/\n    \/\/ 整张 sprite 的真实宽度 = max(偏移 + 该格宽度)。同一 sprite 必须共用这个宽度，\n    \/\/ 否则每格各算一个总宽 ⇒ 缩放不一致 ⇒ 越往下越偏。\n    var grpEndW = {}, grpSeen = {}, i4;\n    for (i4 = 0; i4 < bgPool.length; i4++) {\n      var gu = bgPool[i4];\n      var ax = Math.abs(bgX[i4] || 0);\n      var cwI = (bgW.length > i4 && bgW[i4]) ? bgW[i4] : 200;\n      var endX = ax + cwI;\n      if (!grpEndW[gu] || endX > grpEndW[gu]) grpEndW[gu] = endX;\n    }\n\n    for (var n = 0; n < links.length; n++) {\n      var t = '', sp = null;\n      if (n < bgPool.length) {\n        t = bgPool[n];\n        var gw = (bgW.length > n && bgW[n]) ? bgW[n] : 200;\n        var gh = (bgH.length > n && bgH[n]) ? bgH[n] : 283;\n        var ox = Math.abs(bgX[n] || 0);\n        var oy = Math.abs(bgY[n] || 0);\n        var totW = grpEndW[t] || (ox + gw);\n        if (totW < gw) totW = gw;\n        sp = { w: gw, h: gh, ox: ox, oy: oy, tw: totW };\n        grpSeen[t] = (grpSeen[t] || 0) + 1;\n      } else if (n - bgPool.length < imgPool.length) {\n        t = imgPool[n - bgPool.length];\n      }\n      list.push({ link: links[n], thumb: t, sprite: sp });\n    }\n\n    \/\/ ⑤ 兜底：链接一个都没抓到但存在图片池（站点改版时仍能显示图）\n    if (!links.length && imgPool.length) {\n      for (var q = 0; q < imgPool.length; q++) list.push({ link: pageUrl, thumb: imgPool[q] });\n    }\n    return list;\n  }\n  var thumbs = parseThumbs(raw);\n\n  \/\/ ---- 3. 后续缩略图分页：页数完全来自站点自身信号 ----\n  \/\/ 判据① table.ptb 里 ?p=N 的最大 N；判据② filecount \/ 本页缩略图数\n  \/\/ ★ 页数只跟随站点自身信号（不写死上限）：\n  \/\/   ① 权威：table.ptb 分页表里 ?p=N 的最大 N（Venera parseThumbnailPage 同源判据）\n  \/\/   ② 兜底：ptb 缺失时，用 filecount \/ 本页缩略图数 推算（本页即一整页容量）\n  \/\/   ③ 两者都有时以站点 ptb 为准（它列的是真实页数；byCount 会在样本\/截断页上虚高）\n  var siteMax = 0;\n  var ptb = \/<table[^>]*class=[\"'][^\"']*\\bptb\\b[^\"']*[\"'][^>]*>([\\s\\S]*?)<\\\/table>\/i.exec(raw);\n  if (ptb) {\n    var p2 = \/[?&]p=(\\d+)\/g, p2m;\n    while ((p2m = p2.exec(ptb[1])) !== null) { var n2 = Number(p2m[1]); if (n2 > siteMax) siteMax = n2; }\n  }\n  var maxP = siteMax;\n  if (maxP === 0 && filecount > 0 && thumbs.length > 0) {\n    maxP = Math.ceil(filecount \/ thumbs.length) - 1;\n    if (maxP < 0) maxP = 0;\n  }\n  var cleanUrl = pageUrl.split('?')[0].split('#')[0];\n  \/\/ ★ 后续页请求必须自带纯净 Cookie 头（与 check.js 同逻辑）：\n  \/\/ 裸 URL 走 OkHttp+CookieJar 栈会返回 0B 白屏；只发两项让站方重签 igneous。\n  \/\/ ★261114 v5.2：绝不硬编码测试账号；没配 ehCookie 时 ckKeep 为空，下面统一走「未配置」提示\n  var rawCk = gv('ehCookie', '');\n  if (rawCk.indexOf('ipb_member_id=') < 0 || rawCk.indexOf('ipb_pass_hash=') < 0) rawCk = '';\n  var ckParts = rawCk.split(';'), ckKeep = [], ckSeen = {}, CKOK = ['ipb_member_id', 'ipb_pass_hash', 'inline_set'];\n  \/\/ ★261114 v5.1：分页请求同样显式带合法 igneous（URL 选项 headers 里必须有该键）\n  for (var ci = 0; ci < ckParts.length; ci++) {\n    var cp = ckParts[ci].trim(), ce = cp.indexOf('=');\n    if (ce < 1) continue;\n    var ckk = cp.substring(0, ce).trim(), ckv = cp.substring(ce + 1).trim();\n    if (CKOK.indexOf(ckk) < 0 || ckSeen[ckk] || ckv === '') continue;\n    ckSeen[ckk] = 1; ckKeep.push(ckk + '=' + ckv);\n  }\n  if (ckKeep.join(';').indexOf('inline_set=') < 0) ckKeep.push('inline_set=dm_l');\n  var igP = gv('ehIgneous', '');\n  if (igP === 'mystery' || igP === 'null' || igP === 'undefined' || igP === '0') igP = '';\n  ckKeep.push('igneous=' + igP);   \/\/ 空值占位：覆盖 mystery + 站方重签该账号的值\n  var PAGE_HDR = JSON.stringify({ 'User-Agent': 'Mozilla\/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/120.0.0.0 Safari\/537.36', 'Cookie': ckKeep.join('; '), 'Referer': 'https:\/\/exhentai.org\/' });\n    \/\/ ★261114 v6.9 hosts直连残留清除：不再读 ehHostsIp \/ 不再带 dnsIp\n  \/\/ （对照实验实锤：带IP池dnsIp→banned页；不带→正常。恢复直连功能引入前的正常DNS路径）\nvar OPT = ',{\"headers\":' + PAGE_HDR + '}';\n  var more = [], i;\n  for (i = 1; i <= maxP; i++) more.push(cleanUrl + '?p=' + i + OPT);\n  \/\/ ★ bodies 必须在 if 外初始化：单页画廊(maxP=0)时末尾判据也要读它\n  var bodies = [], ok = false;\n\n  if (more.length) {\n    try {\n      \/\/ ajaxAll 走当前订阅源 header（含 proxy\/Cookie）；返回元素 .body 是方法\n      var rs = java.ajaxAll(more, true);\n      \/\/ ★ java.ajaxAll 返回 Java 数组 [L...StrResponse;（只有 .length，没有 .size()），\n      \/\/   部分版本返回 List（有 .size()\/.get(i)）—— 双形态兼容，否则整段被 catch 吞掉。\n      var n = 0, item;\n      if (rs) {\n        try { n = rs.length; } catch (eL) { try { n = rs.size(); } catch (eS) { n = 0; } }\n      }\n      if (n > 0) {\n        for (i = 0; i < n; i++) {\n          var b = '';\n          try { item = rs[i]; } catch (e0) { try { item = rs.get(i); } catch (e1) { item = null; } }\n          if (item == null) { bodies.push(''); continue; }\n          b = respBody(item);\n          \/\/ 判活：必须真的是画廊缩略图页（排除异常堆栈串 \/ 限频页 \/ 空白页）\n          if (b.length < 500 || b.indexOf('id=\"gdt\"') < 0 || \/Exception|temporarily banned|Sad Panda\/i.test(b.slice(0, 300))) b = '';\n          bodies.push(b);\n        }\n        ok = bodies.filter(function (x) { return x !== ''; }).length > 0;\n        if (!ok) bodies = [];\n      }\n    } catch (e) { ok = false; bodies = []; }\n    if (!ok) {\n      \/\/ 降级：串行 java.ajax（同样走 header），逐页判活，异常即停；\n      \/\/ 不并发，避免短时密集请求把主请求一起连累进站方临时限频。\n      bodies = [];\n      for (i = 0; i < more.length; i++) {\n        try {\n          var one = String(java.ajax(more[i]));\n          if (!one || one.length < 500 || one.indexOf('id=\"gdt\"') < 0 ||\n              \/Exception|temporarily banned|Sad Panda\/i.test(one.slice(0, 200))) break;\n          bodies.push(one);\n        } catch (e2) { break; }\n      }\n    }\n    for (i = 0; i < bodies.length; i++) {\n      var extra = parseThumbs(bodies[i]);\n      for (var j = 0; j < extra.length; j++) {\n        var dup = false;\n        for (var k = 0; k < thumbs.length; k++) if (thumbs[k].link === extra[j].link) { dup = true; break; }\n        if (!dup) thumbs.push(extra[j]);\n      }\n    }\n  }\n\n  \/\/ ============ 高清图：服务端并发抓单图页 ============\n  \/\/ \/s\/{imgkey}\/{gid}-{page} 页里有 <img id=\"img\" src=\"...\">（中图 1280 宽）。\n  \/\/ ★ 域名策略（实测）：\n  \/\/     表站 \/s\/ 页：公开可读，但**里站独占的画廊在表站是 404**\n  \/\/     里站 \/s\/ 页：必须带 Cookie 才 200（无 Cookie 是 302 空 body）\n  \/\/   ⇒ 两条都试，逐条取成功的那份。不能「全部失败才退」，\n  \/\/     否则里站独占画廊会整片退回缩略图。\n  \/\/ ★261109 v3.1：首屏内联高清张数跟随登录面板「🖼 分辨率」档位（ehRes）。\n  var RES_TABLE = {\n    '0': { hd: 0,  gap: 1000, margin: '0px' },\n    '1': { hd: 6,  gap: 700,  margin: '600px' },\n    '2': { hd: 15, gap: 450,  margin: '1200px' },\n    '3': { hd: 25, gap: 350,  margin: '1800px' }\n  };\n  var _resK = gv('ehRes', '1');\n  var RES = RES_TABLE[_resK] || RES_TABLE['1'];\n  var HD_MAX = RES.hd;\n  var hdMap = {};\n  var hdDiag = '';\n  var hdSample = '';\n  var hdLinks = [];        \/\/ 原地址（hdMap 的键，渲染时按它查）\n  var hdTry = [];          \/\/ 表站地址（优先）\n\n  function toPublic(u) {\n    return String(u)\n      .replace(\/^https?:\\\/\\\/exhentai\\.org\/i, 'https:\/\/e-hentai.org')\n      .replace(\/^https?:\\\/\\\/s\\.exhentai\\.org\/i, 'https:\/\/e-hentai.org')\n      .replace(\/^https?:\\\/\\\/g\\.e-hentai\\.org\/i, 'https:\/\/e-hentai.org');\n  }\n\n  \/\/ 源 Cookie（只发 ipb_member_id \/ ipb_pass_hash \/ inline_set）\n  \/\/ 源 Cookie：只发 ipb_member_id \/ ipb_pass_hash \/ inline_set\n  \/\/ ★ 源变量为空时用内置兜底（与 loginCheckJs 同值），\n  \/\/   否则 \/s\/ 页请求不带 Cookie → 里站 302 到首页 → 拿不到 id=\"img\"\n  \/\/ ★261114 v5.2：不硬编码账号；没配 ehCookie 时 cookieHdr() 返回空串\n  var ckState = 'unused';\n\n  function cookieHdr() {\n    var raw = '';\n    try { raw = String(source.get('ehCookie') || ''); } catch (e0) { raw = ''; }\n    if (raw.indexOf('ipb_member_id=') < 0 || raw.indexOf('ipb_pass_hash=') < 0) {\n      ckState = 'missing';\n      return '';   \/\/ ★ 未配置 ⇒ 不带 Cookie 打 \/s\/（明确提示，绝不用测试账号）\n    }\n    ckState = 'var';\n    var parts = raw.split(';'), keep = [], seen = {}, OK = ['ipb_member_id', 'ipb_pass_hash', 'inline_set'];\n    for (var i = 0; i < parts.length; i++) {\n      var p = parts[i].trim(), eq = p.indexOf('=');\n      if (eq < 1) continue;\n      var k = p.substring(0, eq).trim(), v = p.substring(eq + 1).trim();\n      if (OK.indexOf(k) < 0 || seen[k] || v === '') continue;   \/\/ 忽略 igneous，下面统一补\n      seen[k] = 1; keep.push(k + '=' + v);\n    }\n    if (keep.join(';').indexOf('inline_set=') < 0) keep.push('inline_set=dm_l');\n    var ig = '';\n    try { ig = String(source.get('ehIgneous') || ''); } catch (eIg) { ig = ''; }\n    if (ig === 'mystery' || ig === 'null' || ig === 'undefined' || ig === '0') ig = '';\n    keep.push('igneous=' + ig);   \/\/ 空值占位\n    return keep.join('; ');\n  }\n\n  function withCookie(u) {\n    var ck = cookieHdr();\n    if (!ck) return u;\n    var h = { 'Cookie': ck, 'Referer': 'https:\/\/exhentai.org\/' };\n    return u + ',{\"headers\":' + JSON.stringify(h) + '}';\n  }\n\n  \/\/ StrResponse 取值：三种形态兼容\n  function respBody(r) {\n    if (!r) return '';\n    var v = '';\n    try {\n      var b = r.body;\n      if (typeof b === 'function') { v = String(b.call(r) || ''); }\n      else if (typeof b === 'string') { v = b; }\n      else if (b != null) { v = String(b); }\n    } catch (e1) { v = ''; }\n    if (v && \/^function\\s+\\w+\\s*\\(\/.test(v)) v = '';\n    if (v && v.indexOf('java.lang') > -1 && v.length < 200) v = '';\n    if (!v) { try { v = String(r.getBody() || ''); } catch (e2) { v = ''; } }\n    if (!v) { try { v = String(r.body() || ''); } catch (e3) { v = ''; } }\n    if (!v && typeof r === 'string') { v = r; }\n    return v;\n  }\n\n  function hdPick(body) {\n    if (!body) return '';\n    var s = String(body);\n    if (s.length < 200) return '';\n    var m = s.match(\/<img[^>]+id=\"img\"[^>]+src=\"([^\"]+)\"\/i)\n         || s.match(\/<img[^>]+src=\"([^\"]+)\"[^>]+id=\"img\"\/i)\n         || s.match(\/id=\"img\"[^]{0,600}?src=\"([^\"]+)\"\/i);\n    return m ? m[1] : '';\n  }\n\n  for (var hi = 0; hi < thumbs.length && hi < HD_MAX; hi++) {\n    if (thumbs[hi] && thumbs[hi].link) {\n      hdLinks.push(thumbs[hi].link);\n      hdTry.push(toPublic(thumbs[hi].link));\n    }\n  }\n\n  var okN = 0, emptyN = 0, noImgN = 0, mode = '';\n\n  \/\/ 跑一批，返回每条结果数组（'' = 失败）\n  \/\/ 跑一批，返回 {实际请求URL: body} 的映射（不依赖返回顺序）\n  \/\/ ★ 关键：ajaxAll 内部是 mapAsync 并发，返回顺序不保证与入参一致。\n  \/\/   盲信数组索引会把第 1 张的图配到第 5 张的键上 —— 表现为「缩略图之间突然插入一张不连贯的图」。\n  \/\/   用 StrResponse.url() 反查真实地址，按 URL 建映射才可靠。\n  \/\/ ★ 分批 + 退避：一次只并发 HD_BATCH 个 \/s\/ 页，批间 sleep HD_GAP。\n  \/\/   进页面瞬间把十几页全推出去会被站方判成抓取 —— 表现就是首屏裂图、后续拿空 body。\n  var HD_BATCH = 5;\n  var HD_GAP = RES.gap;\n\n  function nap(ms) {\n    try { Packages.java.lang.Thread.sleep(ms); } catch (eN) {}\n  }\n\n  function runBatch(urls, useCookie) {\n    var got = {};\n    var canAll = false;\n    try { canAll = (typeof java.ajaxAll === 'function'); } catch (e0) { canAll = false; }\n    if (!canAll) return got;\n\n    var firstBatch = true;\n    for (var off = 0; off < urls.length; off += HD_BATCH) {\n      if (!firstBatch) nap(HD_GAP);\n      firstBatch = false;\n\n      var endI = off + HD_BATCH;\n      if (endI > urls.length) endI = urls.length;\n      var list = [];\n      for (var y = off; y < endI; y++) list.push(useCookie ? withCookie(urls[y]) : urls[y]);\n\n      var res = null;\n      try { res = java.ajaxAll(list, true); mode = mode || 'ajaxAll\/5'; }\n      catch (eHd) {\n        try { res = java.ajaxAll(list); mode = mode || 'ajaxAll\/5'; }\n        catch (e2) { res = null; }\n      }\n      if (!res) continue;\n      collectBatch(res, off, got);\n    }\n    return got;\n  }\n\n  function collectBatch(res, off, got) {\n\n    var n = 0;\n    try { n = res.length; } catch (eL) { try { n = res.size(); } catch (eS) { n = 0; } }\n\n    for (var j = 0; j < n; j++) {\n      var item = null;\n      try { item = res[j]; } catch (e3) { try { item = res.get(j); } catch (e4) { item = null; } }\n      if (item == null) continue;\n\n      var body = respBody(item);\n\n      \/\/ 取响应自带的 url（三种形态兼容）\n      var rurl = '';\n      try {\n        var uu = item.url;\n        if (typeof uu === 'function') rurl = String(uu() || '');\n        else if (typeof uu === 'string') rurl = uu;\n        else if (uu != null) rurl = String(uu);\n      } catch (e5) { rurl = ''; }\n      if (!rurl) { try { rurl = String(item.getUrl() || ''); } catch (e6) { rurl = ''; } }\n      if (rurl && rurl.indexOf('http') < 0) rurl = '';\n\n      if (rurl) {\n        \/\/ 只取 URL 主体（去掉 ,{...} 选项串）\n        rurl = rurl.split(',{')[0];\n        got[rurl] = body;\n      } else {\n        \/\/ 拿不到 url 才退回索引（极少发生）\n        got['__IDX__' + (off + j)] = body;\n      }\n    }\n    return got;\n  }\n\n  if (hdLinks.length) {\n    \/\/ ① 表站（公开可读，里站独占的画廊这里会 404）\n    var pubBodies = runBatch(hdTry, false);\n    var missList = [];        \/\/ 表站没拿到的索引\n    for (var a = 0; a < hdLinks.length; a++) {\n      var pubKey = hdTry[a].split(',{')[0];\n      var b1 = pubBodies[pubKey] || pubBodies['__IDX__' + a] || '';\n      if (a === 0 && !hdSample && b1 && b1.length > 500) {\n        hdSample = String(b1).replace(\/[<>&\"\\n\\r\\t]\/g, ' ').replace(\/\\s+\/g, ' ').slice(0, 40);\n      }\n      if (!b1) { emptyN++; missList.push(a); continue; }\n      var u1 = hdPick(b1);\n      if (u1) { hdMap[hdLinks[a]] = u1; okN++; }\n      else { noImgN++; missList.push(a); }\n    }\n\n    \/\/ ② 表站漏掉的 → 里站 + Cookie 补齐（逐条，不整批重试）\n    if (missList.length) {\n      mode += '+里站补' + missList.length;\n      var retryUrls = [], retryIdx = [];\n      for (var k = 0; k < missList.length; k++) {\n        retryUrls.push(hdLinks[missList[k]]);\n        retryIdx.push(missList[k]);\n      }\n      var exBodies = runBatch(retryUrls, true);\n      for (var k2 = 0; k2 < retryUrls.length; k2++) {\n        var exKey = retryUrls[k2].split(',{')[0];\n        var b2 = exBodies[exKey] || exBodies['__IDX__' + k2] || '';\n        if (!b2) continue;\n        var u2 = hdPick(b2);\n        if (u2) {\n          hdMap[hdLinks[retryIdx[k2]]] = u2;\n          okN++;\n          noImgN = 0; emptyN = Math.max(0, emptyN - 1);\n        }\n      }\n    }\n\n    \/\/ ③ 还有漏的 → 逐个 java.ajax（老版本无 ajaxAll）\n    var still = 0;\n    for (var c = 0; c < hdLinks.length; c++) { if (!hdMap[hdLinks[c]]) still++; }\n    if (still > 0 && typeof java.ajax === 'function') {\n      mode += '+ajax' + still;\n      for (var q = 0; q < hdLinks.length && still > 0; q++) {\n        if (hdMap[hdLinks[q]]) continue;\n        var one = '';\n        try { one = String(java.ajax(withCookie(hdLinks[q]) || '') || ''); } catch (eA) { one = ''; }\n        if (!one) { try { one = String(java.ajax(withCookie(hdTry[q]) || '') || ''); } catch (eB) { one = ''; } }\n        if (!one) continue;\n        var uq = hdPick(one);\n        if (uq) { hdMap[hdLinks[q]] = uq; okN++; still--; }\n      }\n    }\n\n    hdDiag = (mode || 'none') + ' ' + okN + '\/' + hdLinks.length +\n             ' ck:' + (ckState || '?') +\n             (hdSample ? ' {' + hdSample + '}' : '');\n  } else {\n    hdDiag = '无链接';\n  }\n\n  \/\/ 剩余待懒加载的链接\n  var hdRest = [];\n  for (var hr = HD_MAX; hr < thumbs.length; hr++) {\n    if (thumbs[hr] && thumbs[hr].link) hdRest.push(thumbs[hr].link);\n  }\n\n  \/\/ ---- 4. 拼装 HTML ----\n  \/\/ ★ 正文列数档位（登录面板「🖼 正文每行 N 图」按钮写入 ehCols）\n  \/\/ 默认 '1' = 一行 1 图大图（用户反馈原来 auto-fill 在手机上常变 2 列偏小）；'2' = 一行 2 图紧凑。\n  \/\/ 订阅源正文环境 source=RssSource 本体，get\/put 直接可用（261030 源码级实锤）。\n  \/\/ ---- 3.5 收藏夹名单：面板开关「📁 正文显示收藏夹」开启时才抓取（1 次请求）----\n  var showFav = gv('ehFavPanel', '0') === '1';\n  var favCats = null, favErr = '';\n  \/\/ ★261114 v6.8 名单缓存：favorites.php 每画廊都拉会触发站方 IP+频率风控\n  \/\/   （日志实锤：开 3 画廊 = 3 次请求 → temporarily banned 3.5h）。\n  \/\/   名单抓一次缓存 30 分钟（ehFavCache = {t:时间戳, cats:[{n,name,cnt}...]}），\n  \/\/   命中=零请求秒出；收藏操作成功后页内脚本清缓存（下次打开拉新）。\n  var FAV_TTL = 30 * 60 * 1000;\n  var favCacheRaw = '';\n  try { favCacheRaw = String(source.get('ehFavCache') || ''); } catch (eFC) { favCacheRaw = ''; }\n  var favCache = null;\n  try { if (favCacheRaw) favCache = JSON.parse(favCacheRaw); } catch (eFP) { favCache = null; }\n  var favCacheFresh = (favCache && favCache.t && (new Date().getTime() - Number(favCache.t)) < FAV_TTL && favCache.cats && favCache.cats.length >= 0);\n  if (showFav && favGid) {\n    if (favCacheFresh) {\n      favCats = favCache.cats;\n      \/\/ 夹名 → 索引映射（favoritelink 只给名字）\n      if (favCur < 0 && favCurName) {\n        for (var fj = 0; fj < favCats.length; fj++) {\n          if (favCats[fj].name === favCurName) { favCur = favCats[fj].n; break; }\n        }\n      }\n    } else {\n    try {\n      var fb = String(java.ajax(origin + '\/favorites.php' + OPT));\n      var fbOK = fb.length > 3000 && fb.indexOf('favcat=') >= 0 &&\n                 !\/Exception|temporarily banned|Sad Panda\/i.test(fb.slice(0, 300));\n      if (fbOK) {\n        favCats = [];\n        \/\/ 真实结构：<div class=\"fp\" onclick=\"...favcat=N\"...> ①计数div ②fav图标div(title=夹名) ③名字div <\/div>\n        \/\/ ★ 段必须取到下一个 fp 为止（首个 <\/div> 只闭合计数 div）\n        var fpRe = \/<div class=\"fp\"[^>]*favcat=(\\d)[^>]*>\/g, fpm, fpos = [];\n        while ((fpm = fpRe.exec(fb)) !== null) fpos.push({ n: Number(fpm[1]), s: fpm.index, e: fpRe.lastIndex });\n        for (var fi = 0; fi < fpos.length; fi++) {\n          var segEnd = (fi + 1 < fpos.length) ? fpos[fi + 1].s : (fpos[fi].e + 500);\n          var fseg = fb.substring(fpos[fi].e, segEnd);\n          var tmm = \/title=\"([^\"]*)\"\/.exec(fseg);\n          var cm2 = \/font-weight:bold;[^>]*>([\\d,]+)<\/.exec(fseg);\n          var fNm = tmm ? stripTags(tmm[1]) : '';\n          favCats.push({ n: fpos[fi].n, name: fNm || ('Favorites ' + fpos[fi].n), cnt: cm2 ? cm2[1] : '' });\n        }\n        \/\/ 夹名 → 索引映射（favoritelink 只给名字）\n        if (favCur < 0 && favCurName) {\n          for (var fj = 0; fj < favCats.length; fj++) {\n            if (favCats[fj].name === favCurName) { favCur = favCats[fj].n; break; }\n          }\n        }\n        if (!favCats.length) { favCats = null; favErr = '页面里没有 favcat 列表'; }\n        \/\/ ★v6.8 抓取成功 → 写缓存（30 分钟内不再请求）\n        if (favCats) {\n          try { source.put('ehFavCache', JSON.stringify({ t: new Date().getTime(), cats: favCats })); } catch (eFW) { }\n        }\n      } else {\n        favErr = '收藏页获取失败（' + fb.length + 'B' +\n                 (\/temporarily banned\/i.test(fb) ? ' · IP 限频' : \/Sad Panda\/i.test(fb) ? ' · 登录态无效' :\n                  fb.length === 0 ? ' · 空白页，检查 Cookie\/igneous 是否为该账号' : ' · 检查代理') + '）';\n      }\n    } catch (eF) { favErr = '请求异常：' + String(eF).slice(0, 60); }\n    }\n  }\n  if (showFav && !favGid) favErr = '未登录（缺少 gid\/token），收藏夹功能不可用';\n\n  \/\/ ---- 3.6 tag 模式：两个开关 + mytags watched 名单（仅订阅开时 1 次请求）----\n  var tagSearch = gv('ehTagSearch', '0') === '1';\n  var tagWatch  = gv('ehTagWatch',  '0') === '1';\n  var watchMap = {};       \/\/ full tag name -> usertagId（watched 的）\n  var tagWarn = '';\n  \/\/ ★v6.8 mytags 同样缓存 30 分钟（ehTagCache = {t:..., tags:{name:id,...}}）\n  var TAG_TTL = 30 * 60 * 1000;\n  var tagCacheRaw = '';\n  try { tagCacheRaw = String(source.get('ehTagCache') || ''); } catch (eTC) { tagCacheRaw = ''; }\n  var tagCache = null;\n  try { if (tagCacheRaw) tagCache = JSON.parse(tagCacheRaw); } catch (eTP) { tagCache = null; }\n  var tagCacheFresh = (tagCache && tagCache.t && (new Date().getTime() - Number(tagCache.t)) < TAG_TTL && tagCache.tags);\n  if (tagWatch && !tagCacheFresh) {\n    try {\n      var mb = String(java.ajax(origin + '\/mytags' + OPT));\n      if (mb.length > 3000 && mb.indexOf('usertag_') >= 0 &&\n          !\/Exception|temporarily banned|Sad Panda\/i.test(mb.slice(0, 300))) {\n        \/\/ <div id=\"usertag_N\"> 段：tagpreview_N 的 title=\"ns:tag name\" + tagwatch_N checked\n        var ur = \/<div id=\"usertag_(\\d+)\">([\\s\\S]*?)(?=<div id=\"usertag_|$)\/g, um;\n        while ((um = ur.exec(mb)) !== null) {\n          var uid = um[1], useg = um[2];\n          if (!\/id=\"tagwatch_\"|id=\"tagwatch_\/.test(useg)) continue;\n          var wm = new RegExp('id=\"tagwatch_' + uid + '\"[^>]*checked').exec(useg);\n          var pm = new RegExp('id=\"tagpreview_' + uid + '\"[^>]*title=\"([^\"]*)\"').exec(useg);\n          if (wm && pm) watchMap[stripTags(pm[1])] = uid;\n        }\n        \/\/ ★ 兜底：某些版本段被截断时，用全页正则按 id 逐条找\n        if (!Object.keys(watchMap).length) {\n          var wr2 = \/id=\"tagwatch_(\\d+)\"[^>]*checked\/g, w2;\n          while ((w2 = wr2.exec(mb)) !== null) {\n            var p2 = new RegExp('id=\"tagpreview_' + w2[1] + '\"[^>]*title=\"([^\"]*)\"').exec(mb);\n            if (p2) watchMap[stripTags(p2[1])] = w2[1];\n          }\n        }\n        \/\/ ★v6.8 成功 → 写缓存\n        try { source.put('ehTagCache', JSON.stringify({ t: new Date().getTime(), tags: watchMap })); } catch (eTW) { }\n      } else {\n        tagWarn = '· mytags 未取到（' + mb.length + 'B，订阅态可能不准）';\n      }\n    } catch (eT) { tagWarn = '· mytags 异常：' + String(eT).slice(0, 50); }\n  } else if (tagWatch && tagCacheFresh) {\n    \/\/ ★缓存命中：watchMap 从缓存还原（零请求）\n    var tk = tagCache.tags;\n    for (var tkn in tk) watchMap[tkn] = tk[tkn];\n  }\n\n  \/\/ ---- 3.7 tag 渲染（开关\/watchMap\/origin 此时全部就绪）----\n  var tags = [];\n  for (var tri = 0; tri < tagRows.length; tri++) {\n    var row = tagRows[tri];\n    tags.push('<div class=\"ehTagRow\"><b>' + esc(row.ns) + '<\/b>：' +\n      row.picks.map(function (p) {\n        var su = origin + '\/?f_search=' + encodeURIComponent(p.f);\n        var label = tagSearch\n          ? '<a class=\"ehTag\" href=\"' + esc(su) + '\">' + esc(p.t) + '<\/a>'\n          : '<span class=\"ehTag\">' + esc(p.t) + '<\/span>';\n        var chip = '';\n        if (tagWatch) {\n          var w = watchMap[p.f];\n          chip = '<button class=\"ehTagW' + (w ? ' on' : '') + '\" data-full=\"' + esc(p.f) +\n                 '\" data-id=\"' + (w ? esc(String(w)) : '') + '\">' +\n                 (w ? '✓' : '＋') + '<\/button>';\n        }\n        return label + chip;\n      }).join('') + '<\/div>');\n  }\n\n  var cols = gv('ehCols', '1');\n  if (cols !== '2') cols = '1';\n  var gridCols = (cols === '1') ? '1fr' : 'repeat(2,1fr)';\n\n  var css = [\n    'body{margin:0;padding:0;background:#1b1b1f;color:#e8e8ea;font:15px\/1.7 system-ui,-apple-system,\"Noto Sans CJK SC\",sans-serif}',\n    '.ehCard{background:#26262c;border:1px solid #3a3a44;border-radius:10px;padding:14px;margin-bottom:14px}',\n    '.ehTitle{font-size:20px;font-weight:700;color:#fff;word-break:break-word;margin:0 0 4px}',\n    '.ehJp{font-size:15px;color:#b9b9c6;margin:0 0 10px;word-break:break-word}',\n    '.ehMeta{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:6px 14px;margin-top:10px}',\n    '.ehMeta div{font-size:13px;color:#c9c9d4}.ehMeta b{color:#8ab4f8;font-weight:600}',\n    '.ehTagRow{margin:5px 0;font-size:13px;color:#c9c9d4}.ehTagRow b{color:#f0a0a0}',\n    '.ehTag{display:inline-block;background:#34343f;border:1px solid #454554;border-radius:5px;padding:1px 7px;margin:2px 4px 2px 0;font-size:12px;color:#dcdce6;text-decoration:none}',\n    '.ehTag:active{background:#4a5a7a;border-color:#6a7fa8}',\n    '.ehCover{max-width:100%;height:auto;display:block;margin:0 auto 12px;border-radius:8px}',\n    '.ehGrid{display:block;line-height:0;font-size:0}',\n    '.ehGrid a{display:block;margin:0;padding:0;background:transparent;border-radius:0;overflow:visible;text-decoration:none}',\n    '.ehGrid a>img{width:100%;height:auto;display:block;margin:0;border:0;padding:0;background:transparent}',\n    '.ehGrid a>div{display:block;margin:0;padding:0}',\n    '.ehGrid> a >div{width:100%;display:block;margin:0;padding:0;background:transparent}',\n    '.ehGrid span{display:none}',\n    '.ehBar{font-size:13px;color:#a9a9b8;margin:10px 0}.ehBar a{color:#8ab4f8}',\n    '.ehWarn{background:#4a2b2b;border:1px solid #8a4444;padding:10px;border-radius:8px;color:#ffd7d7;font-size:13px;margin-top:12px}',    '.ehFavRow{margin-top:12px;padding-top:10px;border-top:1px dashed #3a3a44}',\n    '.ehFavHead{font-size:14px;font-weight:700;color:#f0a0a0;margin-bottom:6px}',\n    '.ehFavErr{font-size:12px;color:#e8a0a0;background:#3a2626;border-radius:6px;padding:3px 8px;margin-left:6px}',\n    '.ehFavStat{font-size:13px;color:#c9c9d4;margin:4px 0 8px}',\n    '.ehFavB{display:inline-block;background:#34343f;border:1px solid #454554;border-radius:6px;color:#dcdce6;padding:5px 10px;margin:3px 6px 3px 0;font-size:13px;cursor:pointer;text-align:left}',\n    '.ehFavB i{font-style:normal;color:#a9a9b8;font-size:11px;margin-left:5px}',\n    '.ehFavB b{font-weight:600;color:#7ee787;margin-left:5px}',\n    '.ehFavB.cur{background:#2d4a2d;border-color:#7ee787;color:#c8f0c8}',\n    '.ehFavB.busy{opacity:0.5;pointer-events:none}',\n    '.ehFavDel{background:#4a2b2b;border-color:#8a4444;color:#ffd7d7}',    '.ehTagW{display:inline-block;background:#2d4a2d;border:1px solid #4a7c4a;border-radius:4px;color:#c8f0c8;padding:0 6px;margin:0 8px 2px -2px;font-size:11px;cursor:pointer;vertical-align:middle}',\n    '.ehTagW.on{background:#3a5f3a;border-color:#7ee787}',\n    '.ehTagW.busy{opacity:0.5;pointer-events:none}',\n    '.ehTagMode{font-size:12px;color:#a9a9b8;margin:6px 0 2px}',\n    '.ehHud{position:fixed;right:8px;bottom:8px;z-index:60;background:transparent;color:#fff;font:12px\/1.4 monospace;text-shadow:0 1px 3px rgba(0,0,0,.9),0 0 6px rgba(0,0,0,.8);pointer-events:none;padding:2px 4px}'\n  ].join('');\n\n  var h = '<!doctype html><html><head><meta charset=\"utf-8\">' +\n    '<meta name=\"viewport\" content=\"width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no\">' +\n    '<style>' + css + '<\/style><\/head><body>';\n  var totalPages = filecount || thumbs.length;\n  var hdCount0 = 0;\n  for (var hk in hdMap) { hdCount0++; }\n  \/\/ 正常不打扰：只显示页码。高清全失败时才提示一句。\n  var hudHint = '';\n  if (!hdCount0 && hdLinks.length) hudHint = ' ·无高清';\n  h += '<div class=\"ehHud\" id=\"ehHud\" data-diag=\"' + esc(hdDiag) + '\" data-hd=\"' + hdCount0 + '\">' +\n       '1 \/ ' + totalPages + hudHint + '<\/div>';\n  h += '<div class=\"ehCard\">';\n  h += '<h1 class=\"ehTitle\">' + esc(title || '（未取到标题）') + '<\/h1>';\n  if (titleJp) h += '<div class=\"ehJp\">' + esc(titleJp) + '<\/div>';\n\n  h += '<div class=\"ehMeta\">';\n  function mi(k, v) { if (v) h += '<div><b>' + esc(k) + '：<\/b>' + esc(v) + '<\/div>'; }\n  mi('分类', category); mi('上传者', uploader); mi('发布时间', posted);\n  mi('页数', pagesTxt || (filecount ? filecount + ' pages' : '')); mi('大小', filesize);\n  mi('评分', rating); mi('收藏', favCount);\n  h += '<\/div>';\n  if (tags.length) {\n    if (tagSearch || tagWatch) {\n      h += '<div class=\"ehTagMode\">🏷 标签：' +\n           (tagSearch ? '<b>点名字搜索<\/b>' : '名字不可点') + '　·　' +\n           (tagWatch ? '<b>＋\/✓ 订阅(再点取消)<\/b>' : '订阅未开') +\n           (tagWarn ? ' <span class=\"ehFavErr\">' + esc(tagWarn) + '<\/span>' : '') + '<\/div>';\n    }\n    h += '<div style=\"margin-top:6px\">' + tags.join('') + '<\/div>';\n  }\n\n  var hdCount = 0;\n  for (var hc in hdMap) { hdCount++; }\n\n  h += '<\/div>';\n\n  \/\/ ---- 4.5 收藏夹 UI（正文信息下方：状态行 + 10 个夹子 chip + 移出按钮）----\n  if (showFav) {\n    h += '<div class=\"ehFavRow\">';\n    h += '<div class=\"ehFavHead\">📁 收藏夹' +\n         (favErr ? ' <span class=\"ehFavErr\">' + esc(favErr) + '<\/span>' : '') + '<\/div>';\n    if (favCats) {\n      var favHas = (favCur >= 0 || favCurName);\n      h += '<div class=\"ehFavStat\" id=\"ehFavStat\">' + (favHas\n        ? '⭐ 已收藏：' + esc(favCurName || ('夹 ' + favCur)) + '（再点该夹或 ➖ 移出）'\n        : '☆ 未收藏 · 点击下方收藏夹加入') + '<\/div>';\n      for (var fi2 = 0; fi2 < favCats.length; fi2++) {\n        var fc = favCats[fi2];\n        var isCur = (favCur === fc.n);\n        h += '<button class=\"ehFavB' + (isCur ? ' cur' : '') + '\" data-i=\"' + fc.n + '\">' +\n             esc(fc.name) + (fc.cnt ? '<i>' + esc(fc.cnt) + '<\/i>' : '') +\n             (isCur ? '<b>✓<\/b>' : '') + '<\/button>';\n      }\n      if (favHas) h += '<button class=\"ehFavB ehFavDel\" id=\"ehFavDel\">➖ 移出收藏夹<\/button>';\n    }\n    h += '<\/div>';\n  }\n\n  if (thumbs.length) {\n    h += '<div class=\"ehGrid\">';\n    for (i = 0; i < thumbs.length; i++) {\n      var t = thumbs[i];\n      var media;\n      var hdUrl = hdMap[t.link] || '';\n      if (hdUrl) {\n        media = '<img src=\"' + esc(hdUrl) + '\" alt=\"' + (i + 1) +\n                '\" loading=\"lazy\" decoding=\"async\">';\n      } else if (t.sprite && t.thumb) {\n        var s = t.sprite;\n        var gw = s.w || 200, gh = s.h || 283;\n        var totW = s.tw || (gw + (s.ox || 0));\n        if (totW < gw) totW = gw;\n        \/\/ 容器按本格宽高比撑开\n        var padTop = (gh * 100 \/ gw).toFixed(4) + '%';\n        \/\/ 背景宽度：整张 sprite 相对本格的放大倍数（同一 sprite 各格差值仅来自 gw）\n        var bgWid = (totW * 100 \/ gw).toFixed(4) + '%';\n        \/\/ 背景横向位置。CSS 百分比语义：实际位移 = (容器宽 - 图宽) * p，\n        \/\/ 容器窄、图宽时该值为负，所以要左移 ox 必须给「正的 p」：\n        \/\/   p = ox \/ (图宽 - 容器宽) = ox \/ (totW - gw)\n        \/\/ 写成负号会让图右移 ⇒ 越往下越偏。\n        var bgPos = (totW > gw) ? ((s.ox || 0) * 100 \/ (totW - gw)).toFixed(4) + '%' : '0%';\n        media = '<div style=\"width:100%;padding-top:' + padTop + ';' +\n                'background-image:url(' + esc(t.thumb) + ');' +\n                'background-repeat:no-repeat;' +\n                'background-size:' + bgWid + ' auto;' +\n                'background-position:' + bgPos + ' 0;' +\n                'background-color:transparent\"><\/div>';\n      } else if (t.thumb) {\n        media = '<img src=\"' + esc(t.thumb) + '\" alt=\"' + (i + 1) +\n                '\" loading=\"lazy\" decoding=\"async\">';\n      } else {\n        media = '<img src=\"https:\/\/ehgt.org\/g\/t.png\" alt=\"' + (i + 1) +\n                '\" loading=\"lazy\" decoding=\"async\">';\n      }\n      \/\/ ★ 首屏已内联高清的格子直接标 data-hd=1，HUD 的 loading 只留给还在排队的格子。\n      var hdFlag = hdUrl ? ' data-hd=\"1\"' : ' data-need=\"1\"';\n      h += '<a data-orig=\"' + esc(t.link) + '\"' + hdFlag + '>' + media + '<\/a>';\n    }\n    h += '<\/div>';\n  } else {\n    h += '<div class=\"ehWarn\">⚠ 未解析到缩略图。可能原因：①站点改版（#gdt 形态变化）' +\n         '②当前是 Compact 之外的布局（面板已强制 inline_set=dm_l，请清理排序缓存后重试）' +\n         '③代理未生效导致只拿到半页。<\/div>';\n  }\n  if (more.length > 0 && bodies.length > 0 && bodies.length < more.length) {\n    h += '<div class=\"ehWarn\">⚠ 后续 ' + more.length + ' 个缩略图分页只成功取回 ' + bodies.length +\n         ' 页（代理\/限频\/超时）。已显示能取到的全部；配好代理后重新打开本页即可补全。<\/div>';\n  }\n  if (tagWatch && tags.length) {\n    \/\/ 页内 tag 订阅脚本：点 ＋\/✓ → java.ajax POST \/mytags（add \/ mass 删除）→ 即时改 DOM。\n    \/\/ ★ add 与 delete 都返回 302 空响应（站方 POST→GET 重定向）⇒ 不用响应体判成功：\n    \/\/   java.ajax 拿到 302 跟随后的 \/mytags 页面即为成功（失败则是异常堆栈串\/空）。\n    \/\/ ★ 动态值一律 JSON.stringify（261114 ReferenceError 铁律）；options 用对象 stringify。\n    var tagLines = [\n      '(function(){',\n      'var O=__ORIGIN__,HDR=__HDR__;',\n      'var Q=String.fromCharCode(34);',\n      'function call(u){var w=\"\";try{var r=java.ajax(u);if(typeof r===\"string\"){w=r;}else{try{w=String(r.body());}catch(e1){try{w=String(r);}catch(e2){w=\"\";}}}}catch(e){w=\"\";}return w;}',\n      'function hip(){return \"\";}',\n      'function H(){var h=null;try{h=JSON.parse(HDR);}catch(e0){h=null;}if(!h){h={Cookie:\"\",Referer:O+\"\/\"};}h[\"Content-Type\"]=\"application\/x-www-form-urlencoded\";return h;}',\n      'function post(body){var _hp=hip();var _o={method:\"POST\",body:body,headers:H()};var opt=\",\"+JSON.stringify(_o);var w=call(O+\"\/mytags\"+opt);return (w&&w.indexOf(\"usertag_\")>=0)?w:\"\";}',\n      'function findId(w,full){var re=\/id=\"tagpreview_([0-9]+)\"[^>]*title=\"([^\"]*)\"\/g,m;while((m=re.exec(w))!==null){if(m[2]===full)return m[1];}return \"\";}',\n      'function onClick(ev){',\n      '  if(ev&&ev.stopPropagation)ev.stopPropagation();',\n      '  if(ev&&ev.preventDefault)ev.preventDefault();',\n      '  var el=this;if(el.className.indexOf(\"busy\")>=0)return;',\n      '  var full=el.getAttribute(\"data-full\")||\"\",id=el.getAttribute(\"data-id\")||\"\";',\n      '  if(!full)return;',\n      '  var adding=!id;',\n      '  el.className+=\" busy\";el.textContent=\"…\";',\n      '  var body=adding?(\"usertag_action=add&tagname_new=\"+encodeURIComponent(full)+\"&tagwatch_new=on&tagcolor_new=&tagweight_new=10&usertag_target=0\")',\n      '                 :(\"usertag_action=mass&usertag_target=0&modify_usertags[]=\"+encodeURIComponent(id));',\n      '  var resp=post(body),ok=!!resp;',\n      '  el.className=el.className.split(\" busy\").join(\"\");',\n      '  if(ok&&adding){',\n      '    var nid=findId(resp,full);',\n      '    if(nid)el.setAttribute(\"data-id\",nid);',\n      '    try{source.put(\"ehTagCache\",\"\");}catch(eCT){}',\n      '    el.className=\"ehTagW on\";el.textContent=\"✓\";',\n      '    java.toast(\"✅ 已订阅标签：\"+full+(nid?\"\":\"（退出重进可刷新订阅态）\"));',\n      '  }else if(ok){',\n      '    el.removeAttribute(\"data-id\");el.className=\"ehTagW\";el.textContent=\"＋\";',\n      '    java.toast(\"✅ 已取消订阅：\"+full);',\n      '  }else{',\n      '    el.textContent=adding?\"＋\":\"✓\";',\n      '    java.toast(\"❌ 订阅操作失败 ⇒ 检查登录态\/网络\");',\n      '  }',\n      '}',\n      'var bs=document.querySelectorAll(\".ehTagW\");',\n      'for(var k=0;k<bs.length;k++){bs[k].addEventListener(\"click\",onClick);}',\n      '})();'\n    ].join(String.fromCharCode(10))\n      .replace('__ORIGIN__', JSON.stringify(origin))\n      .replace('__HDR__', JSON.stringify(PAGE_HDR));\n    h += '<script>' + tagLines + '<\/script>';\n  }\n\n  if (showFav && favCats && favGid) {\n    \/\/ 页内收藏夹脚本：点击夹子 → java.ajax POST gallerypopups.php → 成功后即时改 DOM。\n    \/\/ ★ 零反斜杠纪律（261109 实锤）：不用正则字面量、不手拼引号 —— options 用\n    \/\/   JSON.parse(PAGE_HDR) 得对象 → 加 Content-Type → JSON.stringify 整个 options。\n    \/\/ ★ java.ajax 失败不抛异常而是返回异常堆栈串（code 200）⇒ 判活查响应含 window.opener\n    \/\/   （261113 沙盒真机实锤的成功签名；失败串里不会出现该签名）。\n    var fcLines = [\n      '(function(){',\n      'var G=__GID__,T=__TOK__,O=__ORIGIN__,HDR=__HDR__;',\n      'var stat=document.getElementById(\"ehFavStat\");',\n      'function say(m){if(stat)stat.textContent=m;}',\n      'var hasJava=false,canAjax=false;try{hasJava=(typeof java!==\"undefined\"&&java!=null);if(hasJava){canAjax=(typeof java.ajax===\"function\");}}catch(eJ){}',\n      'function call(u){var w=\"\";if(!canAjax)return \"NOBRIDGE\";try{var r=java.ajax(u);if(typeof r===\"string\"){w=r;}else{try{w=String(r.body());}catch(e1){try{w=String(r);}catch(e2){w=\"\";}}}}catch(e){w=\"\";}return w;}',\n      'function hip(){return \"\";}',\n      'function ok(w){return w&&w.indexOf(\"window.opener\")>=0;}',\n      'var busy=false;',\n      'function all(){return document.querySelectorAll(\".ehFavB\");}',\n      'function cur(){var a=all();for(var i=0;i<a.length;i++)if(a[i].className.indexOf(\"cur\")>=0)return a[i];return null;}',\n      'function clr(el){if(!el)return;var ms=el.querySelectorAll(\"b\");for(var i=0;i<ms.length;i++)el.removeChild(ms[i]);el.className=\"ehFavB\";}',\n      'function onClick(){',\n      '  if(busy)return;',\n      '  var el=this;',\n      '  if(el.id===\"ehFavDel\"){act(el,false,\"\");return;}',\n      '  var idx=el.getAttribute(\"data-i\");',\n      '  if(idx===null||idx===\"\")return;',\n      '  act(el,!(cur()===el),idx);',\n      '}',\n      'function act(el,adding,idx){',\n      '  busy=true;el.className+=\" busy\";',\n      'if(!canAjax){say(\"❌ 此环境无 java 桥（E 版 WebView 限制），无法收藏\");return;}',\n      '  say(adding?\"⏳ 正在加入收藏夹…\":\"⏳ 正在移出收藏夹…\");',\n      'try{',\n      '  var body=adding?(\"favcat=\"+idx+\"&favnote=&apply=Add+to+Favorites&update=1\")',\n      '                 :\"favcat=favdel&favnote=&apply=Apply+Changes&update=1\";',\n      '  var H=null;try{H=JSON.parse(HDR);}catch(e0){H=null;}',\n      '  if(!H){H={Cookie:\"\",Referer:O+\"\/\"};}',\n      '  H[\"Content-Type\"]=\"application\/x-www-form-urlencoded\";',\n      '  var _hp=hip();var _o={method:\"POST\",body:body,headers:H};',\n      '  var opt=\",\"+JSON.stringify(_o);',\n      '  var u=O+\"\/gallerypopups.php?gid=\"+G+\"&t=\"+T+\"&act=addfav\",w=\"\";',\n      '  w=call(u+opt);',\n      '  el.className=el.className.split(\" busy\").join(\"\");',\n      '  if(ok(w)){',\n      '    var s2=cur();if(s2&&s2!==el)clr(s2);',\n      '    clr(el);',\n      '    try{source.put(\"ehFavCache\",\"\");}catch(eCF){}',\n      '    if(adding){el.className=\"ehFavB cur\";var b=document.createElement(\"b\");b.textContent=\"✓已入\";el.appendChild(b);say(\"✅ 已加入收藏夹（站内即时生效）\");}',\n      '    else{say(\"✅ 已移出收藏夹\");}',\n      '  }else{',\n      '    say(\"❌ 操作失败（\"+(w?w.length+\"B 异常响应\":\"空响应\")+\"）⇒ 检查尝试登录态\/网络后重试\");',\n      '  }',\n      '  }catch(eA){say(\"❌ 异常：\"+String(eA).slice(0,80));}',\n      '  finally{busy=false;}',\n      '}',\n      'var bs=all();',\n      'for(var k=0;k<bs.length;k++){bs[k].addEventListener(\"click\",onClick);}',\n      '})();',\n    ].join(String.fromCharCode(10))\n      .replace('__GID__', JSON.stringify(favGid))\n      .replace('__TOK__', JSON.stringify(favTok))\n      .replace('__ORIGIN__', JSON.stringify(origin))\n      .replace('__HDR__', JSON.stringify(PAGE_HDR));\n    h += '<script>' + fcLines + '<\/script>';\n  }\n  if (true) {\n    var lazyCode = [\"(function(){\", \"var Q=String.fromCharCode(34);\", \"var NL=String.fromCharCode(10);\", \"var pend=__PEND__;\", \"var hud=document.getElementById('ehHud');\", \"var TOTAL=__TOTAL__;\", \"var CK=__CK__;\", \"var REF=__REF__;\", \"var GAP=__GAP__;\", \"var MARGIN=__MARGIN__;\", \"function upd(t){if(hud){hud.textContent=t;}}\", \"if(!pend||!pend.length){upd('1 \/ '+TOTAL);return;}\", \"\/\/ 261109 v3.1 纯 JS 视口懒加载：零反斜杠，适配官方版 WebJsExtensions 与 E 版无桥。\", \"var hasJava=false,canAjax=false,canGet=false;\", \"try{hasJava=(typeof java!=='undefined'&&java!=null);if(hasJava){canAjax=(typeof java.ajax==='function');canGet=(typeof java.get==='function');}}catch(e0){}\", \"var AS=[].slice.call(document.querySelectorAll('.ehGrid a[data-need]'));\", \"var MAP={};\", \"for(var i=0;i<AS.length;i++){\", \"  var u0=AS[i].getAttribute('data-orig');\", \"  for(var j=0;j<pend.length;j++){if(pend[j]===u0){MAP[i]={a:AS[i],u:u0};break;}}\", \"}\", \"var PENDN=0;for(var pn in MAP){PENDN++;}\", \"if(!PENDN){upd('1 \/ '+TOTAL);return;}\", \"\/\/ 从 \/s\/ 页 HTML 抽 <img id=img src=...>：正则里不含引号，用 Q 拼双引号后 new RegExp\", \"function pickSrc(h){\", \"  if(!h)return '';\", \"  var s=String(h);\", \"  if(s.length<200)return '';\", \"  var pat='<img[^>]+id='+Q+'img'+Q+'[^>]+src='+Q+'([^'+Q+']+)'+Q;\", \"  var m=null;\", \"  try{m=new RegExp(pat,'i').exec(s);}catch(e1){m=null;}\", \"  if(!m){pat='<img[^>]+src='+Q+'([^'+Q+']+)'+Q+'[^>]+id='+Q+'img'+Q;try{m=new RegExp(pat,'i').exec(s);}catch(e2){m=null;}}\", \"  return m?m[1]:'';\", \"}\", \"function fetchOne(u){\", \"  var src='';\", \"  if(canGet&&CK){try{src=pickSrc(java.get(u,JSON.stringify({Cookie:CK,Referer:REF||u}),15000));}catch(a){}}\", \"  if(!src&&canAjax){try{src=pickSrc(java.ajax(u));}catch(b){}}\", \"  if(!src&&canAjax&&CK){\", \"    var hip='';\",\n      \"    var opt=','+String.fromCharCode(123)+Q+'headers'+Q+':'+JSON.stringify({Cookie:CK,Referer:REF||u})+''+String.fromCharCode(125);\", \"    try{src=pickSrc(java.ajax(u+opt));}catch(c){}\", \"  }\", \"  return src;\", \"}\", \"function toPublic(u){\", \"  \/\/ 里站 \/s\/ 换表站：用字符串替换，不用正则反斜杠\", \"  return String(u).split('exhentai.org').join('e-hentai.org');\", \"}\", \"function fetchAny(u){var s=fetchOne(toPublic(u));if(!s)s=fetchOne(u);return s;}\", \"function swap(a,src){\", \"  var old=a.querySelector('img')||a.querySelector('div');\", \"  if(!old||!old.parentNode)return;\", \"  var im=new Image();\", \"  im.loading='lazy';im.decoding='async';\", \"  im.style.cssText='width:100%;height:auto;display:block;margin:0;padding:0;background:transparent';\", \"  im.onload=function(){a.setAttribute('data-hd','1');};\", \"  im.src=src;\", \"  old.parentNode.replaceChild(im,old);\", \"  a.setAttribute('data-hd','1');\", \"}\", \"var Q2=[],queued={},done={},running=false,okN=0,failN=0;\", \"function enqueue(idx){if(queued[idx]||done[idx])return;var it=MAP[idx];if(!it)return;queued[idx]=1;Q2.push(idx);kick();}\", \"function kick(){if(!running){running=true;setTimeout(pump,0);}}\", \"function pump(){\", \"  if(!Q2.length){running=false;return;}\", \"  var idx=Q2.shift();\", \"  if(done[idx]){setTimeout(pump,0);return;}\", \"  var it=MAP[idx];\", \"  var src='';\", \"  if(hasJava&&(canAjax||canGet)){src=fetchAny(it.u);}\", \"  if(src){swap(it.a,src);done[idx]=1;okN++;}else{failN++;delete queued[idx];}\", \"  updateHud(idx);\", \"  setTimeout(pump,GAP);\", \"}\", \"function topVisible(){\", \"  var vh=window.innerHeight||document.documentElement.clientHeight||0;\", \"  for(var q=0;q<AS.length;q++){var r=null;try{r=AS[q].getBoundingClientRect();}catch(e){}if(r&&r.bottom>0&&r.top<vh)return q;}\", \"  return -1;\", \"}\", \"function updateHud(last){\", \"  var t=topVisible();if(t<0)t=(last>=0?last:0);\", \"  var tag=AS[t]&&AS[t].getAttribute('data-hd')==='1'?'':' -加载中';\", \"  upd((t+1)+' \/ '+TOTAL+tag+' ('+okN+'\/'+PENDN+')');\", \"}\", \"var io=null;\", \"if(typeof IntersectionObserver==='function'){\", \"  io=new IntersectionObserver(function(entries){\", \"    for(var e=0;e<entries.length;e++){\", \"      if(!entries[e].isIntersecting)continue;\", \"      var idx=Number(entries[e].target.getAttribute('data-idx'));\", \"      enqueue(idx);\", \"      io.unobserve(entries[e].target);\", \"    }\", \"  },{root:null,rootMargin:MARGIN,threshold:0.01});\", \"  for(var k=0;k<AS.length;k++){AS[k].setAttribute('data-idx',String(k));io.observe(AS[k]);}\", \"}else{\", \"  var lastT=-1,timer=null;\", \"  function onScroll(){\", \"    if(timer)return;\", \"    timer=setTimeout(function(){\", \"      timer=null;var t=topVisible();\", \"      if(t>=0&&t!==lastT){lastT=t;for(var z=t;z<t+3;z++)enqueue(z);}\", \"      updateHud(t);\", \"    },120);\", \"  }\", \"  if(window.addEventListener){window.addEventListener('scroll',onScroll,false);window.addEventListener('resize',onScroll,false);}\", \"  onScroll();\", \"}\", \"if(!hasJava){upd('1 \/ '+TOTAL+' 原生懒加载(E版)');}\", \"})();\"].join(String.fromCharCode(10))\n      .replace('__PEND__', JSON.stringify(hdRest))\n      .replace('__TOTAL__', String(totalPages))\n      .replace('__CK__', JSON.stringify(cookieHdr()))\n      .replace('__REF__', JSON.stringify(origin))\n      .replace('__GAP__', String(RES.gap))\n      .replace('__MARGIN__', JSON.stringify(RES.margin));\n    h += '<script>' + lazyCode + '<\/script>';\n  }\n  h += '<\/body><\/html>';\n  return h;\n})()","ruleImage":"@js:(function(){\n  function norm(u){\n    var s = String(u == null ? '' : u).replace(\/&amp;\/g, '&').trim();\n    s = s.replace(\/^https?:\\\/\\\/s\\.exhentai\\.org\/i, 'https:\/\/ehgt.org');\n    s = s.replace(\/^https?:\\\/\\\/ehgt\\.org\/i, 'https:\/\/ehgt.org');\n    return s;\n  }\n  function ok(u){\n    var s = norm(u);\n    if (!s || s.indexOf('http') !== 0) return '';\n    if (s.indexOf('\/g\/') > -1) return '';\n    if (s.indexOf('ehgt.org\/') > -1) return s;\n    if (s.indexOf('hath.network') > -1) return s;\n    return '';\n  }\n  try{\n    var i = result.select('.glthumb img');\n    for (var a = 0; a < i.size(); a++) {\n      var v = ok(i.get(a).attr('data-src')) || ok(i.get(a).attr('src'));\n      if (v) return v;\n    }\n    var j = result.select('img');\n    for (var b = 0; b < j.size(); b++) {\n      var v2 = ok(j.get(b).attr('data-src')) || ok(j.get(b).attr('src'));\n      if (v2) return v2;\n    }\n    var dv = result.select('div');\n    for (var c = 0; c < dv.size(); c++) {\n      var st = String(dv.get(c).attr('style') || '');\n      if (st.indexOf('url(') < 0) continue;\n      var mm = st.match(\/url\\(([^)]+)\\)\/);\n      if (mm) {\n        var v3 = ok(String(mm[1]).replace(\/[\"']\/g, ''));\n        if (v3) return v3;\n      }\n    }\n  }catch(e){}\n  return '';\n})()","ruleLink":"@js:(function(){try{var a=result.select('a[href*=\/g\/]');if(a.size()>0){var h=a.get(0).attr('href');if(h)return h;}}catch(x){}return '';})()","ruleNextPage":"@js:(function(){\n  \/\/ ★261109 v3 分类页分页完善（全部基于 261109 真机实测，不写死页码上限）：\n  \/\/   ① 首页 \/ f_cats 分类 \/ watched：站点用 #unext「?next=GID」游标，href 自带筛选参数\n  \/\/      （实测 https:\/\/exhentai.org\/?f_cats=1021&amp;next=4315094、\/watched?next=...），\n  \/\/      必须原样跟随（丢参数会翻回全部分类 = 旧版隐性 bug）。\n  \/\/   ② popular：站点实测单页给全（81 个画廊），无 #unext \/ 无 ptb \/ ?p= 无效\n  \/\/      ⇒ 返回空让「空列表自然终止」，不伪造下一页（不写死上限，跟随站点自身信号）。\n  \/\/   ③ &amp; 必须解码；只认本站域名；与本页 URL 相同则停（防回环）。\n  \/\/   ④ 用户偏好：翻页终止一律用站点自身的 next 链接，站点以后加数据自动继续。\n  var s = '';\n  try { s = String(result); } catch (e) { return ''; }\n  if (!s || s.length < 100) return '';\n  \/\/ 伪装错误页直接停，避免把限频\/白屏页当\"还有下一页\"\n  if (\/temporarily banned|excessive request|Sad Panda|You do not have access\/i.test(s.slice(0, 400))) return '';\n\n  var href = '';\n  var m = s.match(\/<a[^>]*id=[\"']unext[\"'][^>]*href=[\"']([^\"']+)[\"']\/i)\n       || s.match(\/<a[^>]*href=[\"']([^\"']*[?&]next=\\d+[^\"']*)[\"'][^>]*id=[\"']unext[\"']\/i);\n  if (m && m[1]) href = m[1];\n  if (!href) {\n    \/\/ 兜底：任意 next=游标链接（排除 javascript: 与站外）\n    var m2 = s.match(\/href=[\"']([^\"']*[?&]next=\\d+[^\"']+)[\"']\/i);\n    if (m2 && m2[1]) href = m2[1];\n  }\n  \/\/ ★261114 v6.5：排行榜 toplist.php 用 table.ptt 页码分页（p 从 0 起），与 #unext 游标体系不同。\n  \/\/   旧规则只认 unext ⇒ 排行榜永远停在第 1 页（「排行榜不显示」的规则级真因之一）。\n  \/\/   跟随站点自身分页信号，不写死页码上限。\n  if (!href) {\n    var base0 = '';\n    try { base0 = String(baseUrl || ''); } catch (eB0) { base0 = ''; }\n    var ci0 = base0.indexOf(',');                 \/\/ baseUrl 可能带 URL options 后缀，剥离\n    if (ci0 > 0) base0 = base0.substring(0, ci0);\n    var mm = s.match(\/<(?:div|table)[^>]*class=[\"'][^\"']*\\bptt\\b[^\"']*[\"'][^>]*>([\\s\\S]*?)<\\\/(?:div|table)>\/i);\n    var pg = mm ? mm[1] : s;\n    var reAll = \/<a[^>]*href=[\"']([^\"']*(?:toplist\\.php)?[^\"']*[?&]p=\\d+[^\"']*)[\"'][^>]*>([^<]*)<\\\/a>\/gi;\n    var ra, best = null, bestP = -1, curP = -1;\n    try {\n      var cm = base0.match(\/[?&](?:p|page)=(\\d+)\/);\n      if (cm) curP = parseInt(cm[1], 10);\n    } catch (eCp) { curP = -1; }\n    while ((ra = reAll.exec(pg)) !== null) {\n      var lp = String(ra[2] || '').trim();\n      var pn = ra[1].match(\/[?&]p=(\\d+)\/);\n      if (!pn) continue;\n      var pi = parseInt(pn[1], 10);\n      if (lp === '>' || lp === '&gt;' || lp === '»') { if (pi > bestP && pi !== curP) { best = ra[1]; bestP = pi; } }\n      else if (pi === curP + 1 && pi > bestP) { best = ra[1]; bestP = pi; }\n    }\n    if (!best) {\n      var re2 = \/<a[^>]*href=[\"']([^\"']*[?&](?:p|page)=(\\d+)[^\"']*)[\"']\/gi;\n      var rb;\n      while ((rb = re2.exec(pg)) !== null) {\n        var pi2 = parseInt(rb[2], 10);\n        if (pi2 === curP + 1) { best = rb[1]; break; }\n      }\n    }\n    if (best) {\n      best = best.replace(\/&amp;\/g, '&').replace(\/&#38;\/g, '&').trim();\n      if (best.indexOf('javascript:') === 0) best = '';\n      \/\/ 相对链接归一化：\n      \/\/  ① '?p=2' 纯查询串 ⇒ 基于当前 URL 的路径+其余参数重建（保留 tl=，替换 p=）\n      \/\/  ② '\/toplist.php?...' \/ 'toplist.php?...' 路径相对 ⇒ 接到 origin\n      if (best.indexOf('?') === 0 && best.indexOf('http') !== 0) {\n        var bi = base0.indexOf('?');\n        var pathPart = bi >= 0 ? base0.substring(0, bi) : base0;\n        var qPart = bi >= 0 ? base0.substring(bi + 1) : '';\n        var pClean = best.replace(\/^\\?\/, '');\n        var pm2 = pClean.match(\/(?:^|&)(p|page)=(\\d+)\/);\n        var pKey = pm2 ? pm2[1] : 'p';\n        var kv = qPart.split('&'), keep4 = [];\n        for (var q4 = 0; q4 < kv.length; q4++) {\n          var k4 = kv[q4].split('=')[0];\n          if (k4 && k4 !== 'p' && k4 !== 'page' && k4 !== 'next') keep4.push(kv[q4]);\n        }\n        if (pm2) keep4.push(pKey + '=' + pm2[2]);\n        best = pathPart + '?' + keep4.join('&');\n      } else if (best.indexOf('http') !== 0) {\n        var ho = '';\n        try { ho = base0.match(\/^https?:\\\/\\\/[^\\\/]+\/i)[0]; } catch (eHr) { ho = 'https:\/\/e-hentai.org'; }\n        best = ho + (best.charAt(0) === '\/' ? best : '\/' + best);\n      }\n      href = best;\n    }\n  }\n  if (!href) return '';                    \/\/ popular \/ 末页：站点自身无 next ⇒ 终止\n  href = href.replace(\/&amp;\/g, '&').replace(\/&#38;\/g, '&').trim();\n  if (href.indexOf('javascript:') === 0) return '';\n  \/\/ 只放行本站两个域（相对路径也允许，交给 getAbsoluteURL）\n  if (\/^https?:\/i.test(href) && !\/(exhentai\\.org|e-hentai\\.org)\/i.test(href)) return '';\n  \/\/ 防回环：与本页完全相同的 next 不重复请求\n  var cur = '';\n  try { cur = String(baseUrl); } catch (e2) { cur = ''; }\n  if (cur && href === cur) return '';\n  return href;\n})()","rulePubDate":"@js:(function(){try{var p=result.select('div[id^=posted_]');if(p.size()>0){var t=String(p.get(0).text()).trim();if(t)return t;}var g3=result.select('td.gl3e > div');for(var pd=0;pd<g3.size();pd++){var t2=String(g3.get(pd).text()).trim();if(\/^\\d{4}-\\d{2}-\\d{2}\/.test(t2))return t2;}}catch(x){}return '';})()","ruleTitle":"@js:(function(){try{var e=result.select('.glink');if(e.size()>0){var t=String(e.get(0).text()).trim();if(t)return t;}var a=result.select('a[href*=\/g\/]');if(a.size()>0){var t2=String(a.get(0).text()).trim();if(t2)return t2;}}catch(x){}return '';})()","searchUrl":"{{(function(){\nfunction gv(k,def){var v='';try{v=String(source.get(k))}catch(e){v=''}if(v==='null'||v==='undefined'||v==='NaN'||v==='')v=def||'';return v}\nvar d=gv('ehHost','https:\/\/exhentai.org').replace(\/\\\/+$\/,'');\nif(d.indexOf('http')!==0)d='https:\/\/'+d;\nvar p=(page&&page>1)?page:1;\nreturn d+'\/?f_search='+encodeURIComponent(key)+'&page='+p})()}}","shouldOverrideUrlLoading":"(function () {\n  var u = String(url);\n  var canOpen = false, canToast = false;\n  try { canOpen = (typeof java !== 'undefined' && typeof java.open === 'function'); } catch (e) { canOpen = false; }\n  try { canToast = (typeof java !== 'undefined' && typeof java.toast === 'function'); } catch (e2) { canToast = false; }\n\n  \/\/ 站内标签\/搜索页 -> 源内列表\n  var isTag = \/\\\/(tag|search)\\\/\/.test(u) || \/[?&]f_search=\/.test(u) || \/[?&]f_(tags|cat|parody|char|artist|circle|fem|mal|lang|misc|rej|pages|exp|doujin)\/.test(u);\n  if (isTag) {\n    if (canOpen) { java.open('sort', u, 'TAG'); return true; }\n    if (canToast) { java.toast('此阅读版本不支持源内打开标签'); }\n    return true;\n  }\n\n  \/\/ 其它站外链接：拦下\n  if (\/^https?:\/i.test(u)) {\n    if (!\/(exhentai\\.org|e-hentai\\.org|ehgt\\.org|hath\\.network)\/.test(u)) {\n      if (canToast) { java.toast('已拦截外部跳转'); }\n      return true;\n    }\n  }\n  return false;\n})()","showWebLog":false,"singleUrl":false,"sortUrl":"@js:\n(function () {\n  \/\/ ★261114 v6：\n  \/\/  ①「⭐ 关注」= OR 实时搜索（~\"tag$\" 前缀 + 空格分隔，EHWiki 权威语法 + 26-tag 实测）。\n  \/\/    \/watched 站点页有站方缓存延迟（新订阅 tag ~1h 内不出，实测订阅后立即访问仍空态），\n  \/\/    实时搜索无此问题。★URL 行内联自包含 JS（AnalyzeUrl URL 环境无 loginUrl 函数作用域，\n  \/\/    不能调 EH_WATCHED_Q）；抓不到标签时回退 \/watched 站点页。\n  \/\/    内联逻辑与 loginUrl 的 EH_WATCHED_Q 重复，改动须两处同步。\n  \/\/  ②「🏆 排行榜」= e-hentai.org\/toplist.php?tl=N&p=0（里站没有 toplist.php，nginx 404 实测；\n  \/\/    Venera ehentai.js 实锤永远走表站；里站账号 SSO 同一 Cookie 对表站有效）。\n  \/\/    分页 p 从 0 开始（table.ptt），与 ?next= 游标体系不同。\n  \/\/  ③ 不写死页码上限：关注搜索用 unext 游标自然终止；排行榜跟随站点 ptt 分页（200 页站点上限）。\n  function gv(k, def) {\n    var v = '';\n    try { v = String(source.get(k)); } catch (e) { v = ''; }\n    if (v === 'null' || v === 'undefined' || v === 'NaN' || v === '') v = def || '';\n    return v;\n  }\n  var d = gv('ehHost', 'https:\/\/exhentai.org').replace(\/\\\/+$\/, '');\n  if (d.indexOf('http') !== 0) d = 'https:\/\/' + d;\n  var C = [\n    ['🆕 最新（全部）', ''],\n    ['📈 热门', 'popular'],\n    ['⭐ 关注', 'watched'],\n    ['📚 同人志 Doujinshi', 'f_cats=1021'],\n    ['📖 漫画 Manga', 'f_cats=1019'],\n    ['🎨 画师CG Artist CG', 'f_cats=1015'],\n    ['🎮 游戏CG Game CG', 'f_cats=1007'],\n    ['🖼 图集 Image Set', 'f_cats=991'],\n    ['👗 Cosplay', 'f_cats=959'],\n    ['🌏 亚洲 Asian Porn', 'f_cats=895'],\n    ['🚫 全年龄 Non-H', 'f_cats=767'],\n    ['🌍 欧美 Western', 'f_cats=511'],\n    ['📦 杂项 Misc', 'f_cats=1022']\n  ];\n  var JS_TOKEN = '@js' + ':';   \/\/ 防大块内字面量 @js: 造成解析歧义（廉价保险）\n  var out = [], i, name, q, url;\n  for (i = 0; i < C.length; i++) {\n    name = C[i][0]; q = C[i][1];\n    if (q === '') {\n      url = d + '\/';\n    } else if (q === 'watched') {\n      url = JS_TOKEN + JSON.stringify((function(){\nfunction g(k){var v='';try{v=String(source.get(k))}catch(e){v=''}if(v==='null'||v==='undefined'||v==='NaN')v='';return v}\nvar UA='Mozilla\/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/120.0.0.0 Safari\/537.36';\nvar d=g('ehHost').replace(\/\\\/+$\/,'');\nif(d.indexOf('http')!==0)d='https:\/\/exhentai.org';\nvar raw=g('ehCookie');\nvar ig=g('ehIgneous')||'';\nif(ig==='mystery'||ig==='undefined'||ig==='null'||ig==='0')ig='';\nvar CK='igneous=';\nif(raw.indexOf('ipb_member_id=')>=0&&raw.indexOf('ipb_pass_hash=')>=0){\nvar parts=raw.split(';'),keep=[],seen={},OK=['ipb_member_id','ipb_pass_hash','inline_set'];\nfor(var i=0;i<parts.length;i++){var p=parts[i].trim(),eq=p.indexOf('=');if(eq<1)continue;var k=p.substring(0,eq).trim(),v=p.substring(eq+1).trim();if(OK.indexOf(k)<0||seen[k]||v==='')continue;seen[k]=1;keep.push(k+'='+v)}\nif(keep.join(';').indexOf('inline_set=')<0)keep.push('inline_set=dm_l');\nkeep.push('igneous='+ig);\nCK=keep.join('; ')}\ntry{\nvar H0={'User-Agent':UA,'Cookie':CK,'Referer':d+'\/'};\nvar mb=String(java.ajax(d+'\/mytags,'+JSON.stringify(H0)));\nif(!mb||mb.length<3000||mb.indexOf('usertag_')<0||\/temporarily banned|excessive request|Sad Panda\/i.test(mb.slice(0,300)))return d+'\/watched';\nvar tags=[],re=\/id=\"usertag_(\\d+)\">([\\s\\S]*?)(?=<div id=\"usertag_|id=\"mytag\"|$)\/g,m;\nwhile((m=re.exec(mb))!==null){var uid=m[1],seg=m[2];var wm=new RegExp('id=\"tagwatch_'+uid+'\"[^>]*checked').exec(seg);var pm=new RegExp('id=\"tagpreview_'+uid+'\"[^>]*title=\"([^\"]*)\"').exec(seg);if(wm&&pm)tags.push(String(pm[1]))}\nif(!tags.length){var wr=\/id=\"tagwatch_(\\d+)\"[^>]*checked\/g,w;while((w=wr.exec(mb))!==null){var p2=new RegExp('id=\"tagpreview_'+w[1]+'\"[^>]*title=\"([^\"]*)\"').exec(mb);if(p2)tags.push(String(p2[1]))}}\nif(!tags.length)return d+'\/watched';\nif(tags.length>26)tags=tags.slice(0,26);\nvar q=[];\nfor(var j=0;j<tags.length;j++)q.push('~\"'+tags[j]+'$\"');\nreturn d+'\/?f_search='+encodeURIComponent(q.join(' '));\n}catch(eX){return d+'\/watched'}\n})());\n    } else if (q === 'tl15' || q === 'tl13' || q === 'tl12' || q === 'tl11') {\n      var tl = q.substring(2);\n      \/\/ ★261114 v6.3：排行榜只在表站。可配 ehProxyTop（独立代理，没配则跟随 ehProxy）；\n      \/\/   表站被封时换节点即可（封禁按 IP 记，~24h 自动解封）。\n      \/\/   表站 Cookie 只发两项+inline_set（igneous 是里站专属，不对表站发）。\n      var px = gv('ehProxyTop', '') || gv('ehProxy', '');\n      var eh = d.replace('exhentai', 'e-hentai');\n      var u0 = eh + '\/toplist.php?tl=' + tl + '&p=0';\n      var ck = 'ipb_member_id=';\n      var raw2 = gv('ehCookie', '');\n      if (raw2.indexOf('ipb_member_id=') >= 0 && raw2.indexOf('ipb_pass_hash=') >= 0) {\n        var parts2 = raw2.split(';'), keep2 = [], seen2 = {};\n        var OK2 = ['ipb_member_id', 'ipb_pass_hash', 'inline_set'];\n        for (var x2 = 0; x2 < parts2.length; x2++) {\n          var p2 = parts2[x2].trim(), eq2 = p2.indexOf('=');\n          if (eq2 < 1) continue;\n          var k2 = p2.substring(0, eq2).trim(), v2 = p2.substring(eq2 + 1).trim();\n          if (OK2.indexOf(k2) < 0 || seen2[k2] || v2 === '') continue;\n          seen2[k2] = 1; keep2.push(k2 + '=' + v2);\n        }\n        if (keep2.join(';').indexOf('inline_set=') < 0) keep2.push('inline_set=dm_l');\n        ck = keep2.join('; ');\n      } else { ck = ''; }\n      var opt = { headers: { 'User-Agent': 'Mozilla\/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/120.0.0.0 Safari\/537.36', 'Referer': eh + '\/' } };\n      if (ck) opt.headers.Cookie = ck;\n      if (px) opt.proxy = px;\n      url = u0 + ',' + JSON.stringify(opt);\n    } else if (q === 'popular') {\n      url = d + '\/' + q;\n    } else {\n      url = d + '\/?' + q;\n    }\n    out.push(name + '::' + url);\n  }\n  return out.join('\\n');\n})()\n","sourceComment":"需要梯子和登录后使用，\n推荐cookis登录不用人机验证。\n增加分辨率选择，选择后点导入账号。\n增加添加收藏夹功能，搜索tag，订阅tag功能\n\nExHentai \/ E-Hentai 图库订阅源。站点 5 种列表布局（gltc\/gltm\/glte\/gl1t\/gt100-200）全兼容；正文自拼纯净 HTML（元数据+标签+缩略图 grid），后续缩略图分页页数完全由站点 #gdd 的 N pages 与 table.ptb 的 ?p=N 推导（不写死上限）。★261107 v2 修复：①面板输入框值由 handleButtonClick 绑成 result（不是 source.put），现已从 result 读并落库；②只发 ipb_member_id+ipb_pass_hash 两项，带旧 igneous 或 CookieJar merge 的 igneous=mystery 会导致 0B 白屏，不带则站方自动重签有效 igneous(30天)；③enabledCookieJar=false 避免库内键污染；④loginCheckJs 用 java.ajax 纯净栈重取 HTML 并 new StrResponse 替换 OkHttp 栈的 0B 响应。白屏时点面板「🩺 一键自愈」。里站需有效账号 Cookie；VPN 出口若被站方拉黑需换节点。★登录\/注册：①账号密码登录（论坛 POST act=Login&CODE=01，遇 Cloudflare 盾自动转内置浏览器）；②📝 注册新账号（官方论坛 act=Reg&CODE=00，需过CF+填 Login Username\/Display Name\/Password\/Email\/Security Code；EHWiki 明确免费、不发验证邮件、立即可用，但每人仅允许1个活跃账号）；③🔑 找回用户名\/密码；④浏览器完成后「🔄 读取登录 Cookie」把 ipb_member_id\/pass_hash 写回并显式复制到 exhentai.org 域（Cookie 按二级域归并，forums\/e-hentai 与 exhentai 不共享）；⑤保留原 Cookie 粘贴通道。★正文缩略图默认「一行 1 图」大图，登录面板「🖼 正文图片布局」按钮可切「一行 2 图」紧凑；站点 sprite 缩略图用百分比 background-size\/position 自适应，两档都精确裁到对应页。兼容 LegadoTeam 与 legado-E。\n★261109 v3：①登录面板新增「🖼 分辨率选择」select 下拉 4档（省流250px缩略图\/自动780x\/高清1280x\/超清全量，LegadoTeam与legado-E双版本通用；⚙同步站点分辨率会POST \/uconfig.php 写账号 xr\/ts，需主动点）；②正文缩略图原生 loading=lazy + 真·IntersectionObserver 视口懒加载，首屏高清张数与 \/s\/ 串行间隔跟随档位，绝不整章并发轰炸（防站方限频）；③分类页分页跟随站点 #unext 游标并保留 f_cats 筛选参数、解码 &amp;、防回环，popular 单页自然终止（不写死上限）；④UA 由 header <js> 统一注入并补 Accept-Language\/Sec-Fetch 指纹；⑤新增「⚡ 测试各线路延时」把 ms 写进线路按钮 + 「✅ 检查登录状态」；搜索不主动探测频率。\n★261114 v5.1：修复「每次打开订阅源要清 Cookie 重登」——根因 enabledCookieJar=true 让 CookieJar 把 igneous=mystery 回写库内，每次请求自动补上导致 0B 白屏；改为 false + 四处 Cookie 显式带合法 igneous（源变量 ehIgneous，面板「🔄 刷新 igneous」可更新）。\n★261114 v5.2：移除硬编码测试账号凭据\/igneous，改为 igneous= 空值占位（覆盖库内 mystery + 站方按账号重签）；未配 Cookie 明确提示；面板加「🩺 登录态一键诊断」。\n★261114 v5.3：修复登录面板按钮显示 null（源码 rowUiBuilder 只对长度3..19的单引号 viewName 当静态文本，超长串被当JS求值失败→null）；22行已改短字面量\/删除，11个动态函数加非空兜底。\n★261114 v5.4：登录面板全部 viewName 静态化（11个动态行改短字面量 u16len≤19，100%避开「JS求值→空→显示null」分支）；动态状态由点击 action 的 Toast 显示。\n★261114 v5.3c：修复按钮显示null真因=EH_refreshIgneous三元表达式多余右括号→loginUrl整体编译失败→所有动态viewName求值全挂；另7个动态函数整函数try\/catch（||兜底只救falsy不救异常）；11个动态viewName保留，App真机探针11\/11返回非空。\n★261114 v6.4 全站hosts直连：源变量 ehHostsIp（空=关闭），列表\/搜索\/loginCheckJs\/正文分页\/收藏夹\/标签订阅\/排行榜全通道注入URL options的dnsIp键（真机反组127.0.0.1→ConnectException实锤生效）；登录面板=你的原版未动。\n★26114 v6.7 精简：撤除 hostsIP 直连全套（UI 4行\/EH_hosts* 4函数\/预填池\/按域分流\/ehIg 三级）——用户路径 TLS 握手阶段即按 SNI 阻断（HTTP日志 SSLHandshakeException 实锴），Legado OkHttp 必发 SNI 无法复刻 EhViewer 无 SNI 握手，hostsIP 只解 DNS 层对此路径无效。保留规则级修复：①排行榜 table.ptt 页码分页②排行榜扩展布局日期兜底③searchUrl IIFE 语法 bug 修复（v6.4 就有）。连接走 VPN\/代理（②代理地址），ehHostsIp 变量无 UI 入口恒空=天然关闭。\n★26114 v6.7.1：按用户要求删除 4 个排行榜入口（🏆日榜\/月榜\/年榜\/总榜）——其走表站 toplist.php，用户路径下表站不通（关 VPN 时 DNS\/SNI 全挂，开 VPN 时入口 IP 也常被表站封禁）。分类现保留 13 项（最新\/热门\/关注 + 10 个里站分类）。ruleNextPage 的 ptt 分页分支与 ehProxyTop 变量保畘（无入口即不产生行为）。\n★261114 v6.8 收藏夹\/订阅名单缓存：favorites.php+mytags 每画廊自动连发引发站方 IP+频率风控（日志实锤：开3画廊=3次请求→temporarily banned 3.5h；EhViewer 正常=其按需单次请求）。修法：①名单抓一次缓存30分钟（ehFavCache\/ehTagCache，命中零请求）②banned\/失败绝不缓存③点＋\/✓操作成功后页内脚本清缓存（下次打开拉新）④收藏夹开关默认关（header 一次性迁移重置老用户已存的1，ehFavReset68 标记）。请求频率降到人类水平。\n★261114 v6.9：彻底移除 hosts IP 直连残留（8处 dnsIp 注入 + 缓存残留 IP 池一次性清除）。对照实验实锤：带残留IP池dnsIp→favorites.php 返回 239B temporarily banned 共享IP限频页（LT版收藏夹「IP限制」的真凶）；不带→96921B 正常收藏页。E版缓存干净故一直正常。如未来站点再次全面被墙需要 IP 直连，须整链路重新设计（含缓存值治理），不可只加回读取逻辑。","sourceGroup":"🔞 福利","sourceIcon":"https:\/\/exhentai.org\/favicon.ico","sourceName":"🅴ExHentai","sourceUrl":"https:\/\/exhentai.org\/","style":"body{margin:0;padding:0;overflow-x:hidden;max-width:100%}img{display:block;max-width:100%!important;height:auto}a{color:#8ab4f8}","type":0,"variableComment":"ehHost=站点根域(默认 https:\/\/exhentai.org)；ehProxy=代理地址(可空，走VPN全局)；ehCookie=里站Cookie(至少 ipb_member_id+ipb_pass_hash)；ehCookieInput=粘贴框；ehUser\/ehPass=论坛账号密码(pwdLogin 用，遇 CF 转内置浏览器)；ehCols=正文每行图片数(1=单列大图默认 \/ 2=双列紧凑)；★发送时只保留两项+inline_set=dm_l，igneous 由站方重新签发；ehRes=分辨率档位(0省流\/1自动默认\/2高清\/3超清)；ehXR=站点uconfig回读的xr值；ehCols=正文每行图片数；ehPingEx\/ehPingTable\/ehPingText=线路测速ms；ehPub=发布页\/自定义线路输入框；ehFavPanel=正文显示收藏夹开关(v6.8默认关)；ehFavCache=收藏夹名单缓存{t,cats}(TTL30min,操作成功即清)；ehTagCache=mytags订阅缓存{t,tags}(同)；ehTagSearch=点正文tag名字搜索对应tag(1=开)；ehTagWatch=正文tag旁显示＋\/✓订阅watched标签(1=开,POST \/mytags add\/mass)；ehIgneous=该账号自己的有效 igneous(面板「🔄刷新igneous」写入；为空时用空值占位让站方重签，绝不硬编码\/绝不存 mystery)；enabledCookieJar=false 防止 CookieJar 把 igneous=mystery 回写库内导致每次白屏；★本源不内置任何账号：ehCookie 未配置时明确提示粘贴你自己的 Cookie；★ehHostsIp 已在 v6.9 废弃清除（IP池直连=共享IP被站方限频，收藏夹「IP限制」真凶），如键仍有值会被 header 迁移自动清空"}]