[{"bookSourceComment":"小雨的世界 · 起点中文\n1.源作者：小雨天天开心\n2.官方反馈群：1104270100\n3.本书源是免费书源，算法和接口均来自https:\/\/m.tb.cn\/h.8JLfUDo?tk=q2U8TiO1D0y\n登录界面可配置评论样式与正文凭证。\n游客 Token 需在登录界面手动填写源作者、官方反馈群和临时口令；普通账号注册不受影响。\n用户中心：https:\/\/full.hnxianxin.cn\/qd\/account.php（游客 14 天外，普通 7 天外）","bookSourceGroup":"起点","bookSourceName":"小雨的世界","bookSourceType":0,"bookSourceUrl":"小雨的世界","customButton":false,"customOrder":1,"enabled":true,"enabledCookieJar":true,"enabledExplore":true,"eventListener":false,"exploreUrl":"@js:\nresult = xyExplore(this);","jsLib":"var XY_API_ROOT = \"https:\/\/full.hnxianxin.cn\/qd\/\";\nvar XY_COVER_ROOT = \"https:\/\/bookcover.yuewen.com\/qdbimg\/349573\/\";\n\nvar XY_TEXT_SITES = { 11: 1, 12: 1, 4: 1 };\nvar XY_QUERY_NAMES = {\n  categoryid: \"category_id\",\n  subcategoryid: \"subcategory_id\",\n  classid: \"class_id\",\n  actionstatus: \"action_status\",\n  wordscount: \"words_count\",\n  paidmode: \"paid_mode\",\n  tagtype: \"tag_type\",\n  great: \"great\",\n  endure: \"endure\",\n  tagid: \"tag_ids\",\n};\nvar XY_FILTER_TITLES = {\n  actionstatus: \"连载状态\",\n  wordscount: \"字数范围\",\n  paidmode: \"付费模式\",\n  tagtype: \"标签匹配\",\n  classid: \"标签筛选\",\n  great: \"精品筛选\",\n  endure: \"耐看筛选\",\n};\n\nfunction xyBridge(ctx) {\n  if (ctx && ctx.java) return ctx.java;\n  if (typeof java !== \"undefined\") return java;\n  return null;\n}\n\nfunction xySource(ctx) {\n  if (ctx && ctx.source) return ctx.source;\n  if (typeof source !== \"undefined\") return source;\n  return null;\n}\n\nfunction xyState(ctx) {\n  var holder = xySource(ctx);\n  var state = {};\n  try {\n    state = JSON.parse(holder.getVariable() || \"{}\");\n  } catch (error) {\n    state = {};\n  }\n  if (!state || typeof state !== \"object\") state = {};\n  if (state.xySite === \"男站\") state.xySite = \"男生\";\n  if (state.xySite === \"女站\") state.xySite = \"女生\";\n  if (!state.xySite || !XY_TEXT_SITES[String(xySiteIdByName(state.xySite))])\n    state.xySite = \"男生\";\n  if (!state.xyOrderName || state.xyOrderName === \"默认榜单\")\n    state.xyOrderName = \"人气最高\";\n  if (!state.xyCategoryName) state.xyCategoryName = \"全部\";\n  if (!state.xyActionStatusName) state.xyActionStatusName = \"不限\";\n  if (!state.xyWordsCountName) state.xyWordsCountName = \"不限\";\n  if (!state.xyPaidModeName) state.xyPaidModeName = \"不限\";\n  if (!state.xyTagTypeName) state.xyTagTypeName = \"包含任意已选标签\";\n  if (!state.xyGreatName) state.xyGreatName = \"不限\";\n  if (!state.xyEndureName) state.xyEndureName = \"不限\";\n  if (!state.xyTagValue) state.xyTagValue = \"\";\n  if (state.xyShowPara !== false) state.xyShowPara = true;\n  if (state.xyShowChapter !== false) state.xyShowChapter = true;\n  if (state.xyLargeBubble !== false) state.xyLargeBubble = true;\n  if (state.xyFullComment !== true) state.xyFullComment = false;\n  if (state.xyCustomMode !== true) state.xyCustomMode = false;\n  state.xyYwkey = String(state.xyYwkey || \"\");\n  state.xyYwguid = String(state.xyYwguid || \"\");\n  state.xyUserToken = String(state.xyUserToken || \"\");\n  state.xyAndroidId = String(state.xyAndroidId || \"\");\n  state.xyUserName = String(state.xyUserName || \"\");\n  state.xyUserLevel = String(state.xyUserLevel || \"\");\n  return state;\n}\n\nfunction xySiteIdByName(name) {\n  if (name === \"女生\") return 12;\n  if (name === \"出版\") return 4;\n  return 11;\n}\n\nfunction xyGet(ctx, key, fallback) {\n  var state = xyState(ctx);\n  return Object.prototype.hasOwnProperty.call(state, key)\n    ? state[key]\n    : fallback;\n}\n\nfunction xyAjax(ctx, url) {\n  var bridge = xyBridge(ctx);\n  if (!bridge) throw new Error(\"java context is unavailable\");\n  return bridge.ajax(url);\n}\n\nfunction xyJson(ctx, url) {\n  return JSON.parse(xyAjax(ctx, url));\n}\n\nfunction xySearchUrl(ctx, keyword, page) {\n  var value = String(keyword || \"\").replace(\/^\\s+|\\s+$\/g, \"\");\n  var pageNumber = Math.max(1, parseInt(String(page || \"1\"), 10) || 1);\n  return (\n    XY_API_ROOT +\n    \"search.php?keyword=\" +\n    encodeURIComponent(value) +\n    \"&site=1&page=\" +\n    pageNumber +\n    \"&page_size=20\"\n  );\n}\n\nfunction xyDetailUrl(ctx, id) {\n  return (\n    XY_API_ROOT + \"detail.php?bookId=\" + encodeURIComponent(String(id || \"\"))\n  );\n}\n\nfunction xyCatalogUrl(ctx, id) {\n  return (\n    XY_API_ROOT + \"catalog.php?bookId=\" + encodeURIComponent(String(id || \"\"))\n  );\n}\n\nfunction xyCoverUrl(ctx, id) {\n  return XY_COVER_ROOT + encodeURIComponent(String(id || \"\")) + \"\/180\";\n}\n\nfunction xyNormalizeBase64(value) {\n  var encoded = String(value || \"\").replace(\/\\s+\/g, \"\");\n  encoded = encoded.replace(\/-\/g, \"+\").replace(\/_\/g, \"\/\");\n  while (encoded.length % 4) encoded += \"=\";\n  return encoded;\n}\n\nfunction xyDecodeChapter(ctx, value) {\n  var text = String(value || \"\");\n  try {\n    text = decodeURIComponent(text);\n  } catch (error) {}\n  if (text.charAt(0) === \"{\") return JSON.parse(text);\n  var match = text.match(\/base64,([A-Za-z0-9+\\\/=_%-]+)\/i);\n  var encoded = match ? match[1] : text;\n  if (!encoded) throw new Error(\"章节地址无效\");\n  var separator = encoded.indexOf(\",\");\n  if (separator >= 0) encoded = encoded.substring(0, separator);\n  var json = xyBridge(ctx).base64Decode(xyNormalizeBase64(encoded));\n  var data = JSON.parse(json);\n  if (\n    !data ||\n    !data.bookId ||\n    data.chapterId === undefined ||\n    data.chapterId === null ||\n    data.chapterId === \"\"\n  ) {\n    throw new Error(\"章节信息无效\");\n  }\n  if (\n    !Object.prototype.hasOwnProperty.call(data, \"time\") ||\n    !\/^\\d+$\/.test(String(data.time))\n  ) {\n    throw new Error(\"章节时间戳无效，请刷新目录\");\n  }\n  if (String(data.time) === \"0\") {\n    throw new Error(\"章节时间戳无效，请刷新目录\");\n  }\n  return data;\n}\n\nfunction xyChapterPayload(ctx, value) {\n  return String(xyBridge(ctx).hexDecodeToString(String(value || \"\")) || \"\");\n}\n\nfunction xyCustomHeaders(ctx) {\n  var state = xyState(ctx);\n  if (!state.xyCustomMode || !state.xyYwkey || !state.xyYwguid) return null;\n  return { Ywkey: String(state.xyYwkey), Ywguid: String(state.xyYwguid) };\n}\nfunction xyUserAndroidId(ctx, state) {\n  var value = String(state.xyAndroidId || \"\").replace(\/^\\s+|\\s+$\/g, \"\");\n  if (value) return value;\n  try { if (xyBridge(ctx).androidId) value = String(xyBridge(ctx).androidId() || \"\"); } catch (error) {}\n  if (!value) {\n    var chars = \"0123456789abcdef\";\n    for (var index = 0; index < 16; index++) value += chars.charAt(Math.floor(Math.random() * 16));\n  }\n  return value.toUpperCase();\n}\nfunction xyEnsureUser(ctx) {\n  var state = xyState(ctx);\n  if (state.xyCustomMode || state.xyUserToken) return state;\n  var androidId = xyUserAndroidId(ctx, state);\n  var options = {method:\"POST\",headers:{\"Content-Type\":\"application\/json\",Accept:\"application\/json\"},body:JSON.stringify({android_id:androidId})};\n  var response = JSON.parse(xyAjax(ctx, XY_API_ROOT + \"user_api.php?action=guest,\" + JSON.stringify(options)));\n  if (response.error) throw new Error(response.error);\n  var user = response.user || {};\n  state.xyAndroidId = androidId;\n  state.xyUserToken = String(response.token || \"\");\n  state.xyUserName = String(user.username || \"\");\n  state.xyUserLevel = String(user.level || \"guest\");\n  var holder = xySource(ctx);\n  if (holder && holder.setVariable) holder.setVariable(JSON.stringify(state));\n  return state;\n}\nfunction xyUserHeaders(ctx) {\n  var custom = xyCustomHeaders(ctx);\n  if (custom) return custom;\n  var state = xyEnsureUser(ctx);\n  if (!state.xyUserToken) return null;\n  return {\"X-Sec-Token\":String(state.xyUserToken),\"X-Android-Id\":String(state.xyAndroidId || \"\")};\n}\nfunction xyContentUrl(ctx, value) {\n  var data = xyDecodeChapter(ctx, value);\n  var vip = Number(data.v !== undefined ? data.v : data.vip) ? 1 : 0;\n  var epub = Number(data.epub) ? 1 : 0;\n  var url = XY_API_ROOT + \"content.php?bookId=\" + encodeURIComponent(String(data.bookId)) + \"&chapterId=\" + encodeURIComponent(String(data.chapterId)) + \"&t=\" + encodeURIComponent(String(data.time)) + \"&epub=\" + epub + \"&v=\" + vip;\n  var headers = xyUserHeaders(ctx);\n  return headers ? url + \",\" + JSON.stringify({headers:headers}) : url;\n}\n\nfunction xyQuery(ctx, url, name) {\n  var match = String(url || \"\").match(\n    new RegExp(\"[?&]\" + name + \"=([^&]+)\", \"i\"),\n  );\n  if (!match) return \"\";\n  try {\n    return decodeURIComponent(match[1]);\n  } catch (error) {\n    return match[1];\n  }\n}\n\nfunction xyReviewUrl(ctx, value) {\n  var bookId = xyQuery(ctx, value, \"bookId\");\n  var chapterId = xyQuery(ctx, value, \"chapterId\");\n  var page = xyQuery(ctx, value, \"page\") || \"1\";\n  return (\n    XY_API_ROOT +\n    \"review.php?bookId=\" +\n    encodeURIComponent(bookId) +\n    \"&chapterId=\" +\n    encodeURIComponent(chapterId) +\n    \"&page=\" +\n    encodeURIComponent(page)\n  );\n}\n\nfunction xyCleanText(value) {\n  var images = [];\n  var text = String(value || \"\").replace(\/<img\\b[^>]*>\/gi, function (tag) {\n    images.push(tag);\n    return \"\\n@@XY_IMG_\" + (images.length - 1) + \"@@\\n\";\n  });\n  text = text\n    .replace(\/<br\\s*\\\/?>\/gi, \"\\n\")\n    .replace(\/<\\\/p>\/gi, \"\\n\")\n    .replace(\/<[^>]+>\/g, \"\")\n    .replace(\/&nbsp;\/g, \" \")\n    .replace(\/&amp;\/g, \"&\")\n    .replace(\/&lt;\/g, \"<\")\n    .replace(\/&gt;\/g, \">\")\n    .replace(\/\\r\\n?\/g, \"\\n\");\n  return text.replace(\/@@XY_IMG_(\\d+)@@\/g, function (all, index) {\n    return images[Number(index)] || \"\";\n  });\n}\n\nfunction xyH5Url(bookId, chapterId, paragraphId) {\n  var url =\n    XY_API_ROOT +\n    \"index.html?bookId=\" +\n    encodeURIComponent(String(bookId)) +\n    \"&chapterId=\" +\n    encodeURIComponent(String(chapterId));\n  if (paragraphId !== undefined && paragraphId !== null && paragraphId !== \"\") {\n    url += \"&paragraphIndex=\" + encodeURIComponent(String(paragraphId));\n  }\n  return url;\n}\n\nfunction xyCommentBrowserConfig(ctx) {\n  var fullScreen = xyGet(ctx, \"xyFullComment\", false);\n  var config = {\n    state: fullScreen ? 3 : 4,\n    isHideable: true,\n    skipCollapsed: true,\n    hardwareAccelerated: true,\n    widthPercentage: 1,\n    maxWidth: -1,\n    expandedCornersRadius: fullScreen ? 0 : 14,\n    dismissOnTouchOutside: true,\n    isGestureInsetBottomIgnored: true,\n    webViewInitialScale: 100,\n  };\n  if (fullScreen) {\n    config.dialogHeight = -1;\n    config.setFitToContents = false;\n    config.setExpandedOffset = 0;\n  } else {\n    config.heightPercentage = 0.7;\n  }\n  return config;\n}\n\nfunction xyShowComment(bridge, url, title) {\n  if (!bridge || !url) return;\n  if (bridge.showBrowser) {\n    bridge.showBrowser(\n      String(url),\n      null,\n      null,\n      JSON.stringify(xyCommentBrowserConfig(this)),\n    );\n    return;\n  }\n  bridge.startBrowser(String(url), String(title || \"评论\"));\n}\n\nfunction xyCommentCounts(ctx, bookId, chapterId) {\n  var map = {};\n  var chapterCount = 0;\n  try {\n    var response = xyJson(\n      ctx,\n      XY_API_ROOT +\n        \"review.php?bookId=\" +\n        encodeURIComponent(String(bookId)) +\n        \"&chapterId=\" +\n        encodeURIComponent(String(chapterId)),\n    );\n    var root = response.data || response.Data || {};\n    var list = (root.Getparagraphscommentcounts || {}).DataList || [];\n    for (var index = 0; index < list.length; index++) {\n      var item = list[index];\n      var paragraph = Number(item.ParagraphId);\n      var count = Number(item.CommentCount);\n      if (paragraph === -1) {\n        chapterCount = count;\n      } else if (paragraph >= 0 && count > 0) {\n        map[String(paragraph)] = {\n          count: count,\n          hot: Number(item.HasHotComment || item.hasHotComment || 0) === 1,\n        };\n      }\n    }\n  } catch (error) {\n    try {\n      xyBridge(ctx).log(\"评论数量获取失败：\" + error);\n    } catch (ignored) {}\n  }\n  return { map: map, chapter: chapterCount };\n}\n\nfunction xyCommentBubble(ctx, count, bookId, chapterId, paragraphId, hot) {\n  var bridge = xyBridge(ctx);\n  if (!bridge || !bridge.base64Encode) return \"\";\n  var display = Number(count) > 99 ? \"99+\" : String(Number(count) || 0);\n  var font = \"system-ui,-apple-system,Arial,sans-serif\";\n  var svg =\n    '<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"5 14 45 36\" width=\"180\" height=\"144\">' +\n    '<path d=\"M44 48 Q48 48 48 44 L48 20 Q48 16 44 16 L20 16 Q16 16 16 20 L16 24 S16 28 10 30 Q6 32 10 34 Q16 36 16 38 L16 44 Q16 48 20 48 Z\" fill=\"none\" stroke=\"#666666\" stroke-width=\"0.7\"\/>' +\n    '<text x=\"32\" y=\"36.5\" text-anchor=\"middle\" font-family=\"' +\n    font +\n    '\" font-weight=\"600\" font-size=\"11\" fill=\"#666666\">' +\n    display +\n    \"<\/text><\/svg>\";\n  var options = {\n    style: xyGet(ctx, \"xyLargeBubble\", true) ? \"TEXT\" : \"text\",\n    type: \"xy\",\n    status: hot ? \"emphasis\" : \"normal\",\n    click:\n      \"xyShowComment.call(this, java,\" +\n      JSON.stringify(xyH5Url(bookId, chapterId, paragraphId)) +\n      ',\"段评\")',\n    marker: \"xyComment:\" + bookId + \":\" + chapterId + \":\" + paragraphId,\n  };\n  return (\n    '<img src=\"data:' +\n    \"image\/svg+xml;base64,\" +\n    bridge.base64Encode(svg) +\n    \",\" +\n    JSON.stringify(options) +\n    '\">'\n  );\n}\n\nfunction xyXmlEscape(text) {\n  return String(text || \"\")\n    .replace(\/&\/g, \"&amp;\")\n    .replace(\/<\/g, \"&lt;\")\n    .replace(\/>\/g, \"&gt;\")\n    .replace(\/\"\/g, \"&quot;\");\n}\n\nfunction xyCommentCard(ctx, count, bookId, chapterId) {\n  var bridge = xyBridge(ctx);\n  if (!bridge || !bridge.base64Encode || Number(count) <= 0) return \"\";\n  var width = 1080,\n    height = 150,\n    left = 60,\n    right = 60;\n  var svg =\n    '<svg width=\"' +\n    width +\n    '\" height=\"' +\n    height +\n    '\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" xmlns:xlink=\"http:\/\/www.w3.org\/1999\/xlink\">' +\n    '<rect x=\"2\" y=\"2\" width=\"' +\n    (width - 4) +\n    '\" height=\"' +\n    (height - 4) +\n    '\" fill=\"rgba(255,255,255,0.25)\" rx=\"59\" stroke=\"#888\" stroke-width=\"1\"\/>' +\n    '<text x=\"' +\n    left +\n    '\" y=\"88\" font-size=\"46\" font-family=\"Segoe UI,Arial,sans-serif\" fill=\"#333333\">本章说<\/text>' +\n    '<text x=\"' +\n    (width - right) +\n    '\" y=\"86\" font-size=\"38\" text-anchor=\"end\" font-family=\"Segoe UI,Arial,sans-serif\" fill=\"#666666\">' +\n    xyXmlEscape(Number(count) + \"条评论 ❯\") +\n    \"<\/text>\" +\n    \"<\/svg>\";\n  var options = {\n    style: \"FULL\",\n    click:\n      \"xyShowComment.call(this, java,\" +\n      JSON.stringify(xyH5Url(bookId, chapterId)) +\n      ',\"本章评论\")',\n    marker: \"xyCommentTail:\" + bookId + \":\" + chapterId,\n  };\n  return (\n    '<img src=\"data:' +\n    \"image\/svg+xml;base64,\" +\n    bridge.base64Encode(svg) +\n    \",\" +\n    JSON.stringify(options) +\n    '\">'\n  );\n}\n\nfunction xyFormatContent(ctx, body, baseUrl) {\n  var response;\n  try {\n    response = JSON.parse(body);\n  } catch (error) {\n    throw new Error(\"正文响应不是JSON：\" + String(body).substring(0, 120));\n  }\n  if (response.error) throw new Error(response.error);\n  if (response.detail) throw new Error(response.detail);\n  if (\n    response.code !== undefined &&\n    Number(response.code) !== 0 &&\n    response.msg\n  )\n    throw new Error(response.msg);\n\n  var content =\n    response.content ||\n    response.Content ||\n    (response.Data && response.Data.Content) ||\n    \"\";\n  if (!content) throw new Error(\"正文内容为空\");\n\n  var bookId = xyQuery(ctx, baseUrl, \"bookId\");\n  var chapterId = xyQuery(ctx, baseUrl, \"chapterId\");\n  var state = xyState(ctx);\n  var counts =\n    state.xyShowPara || state.xyShowChapter\n      ? xyCommentCounts(ctx, bookId, chapterId)\n      : { map: {}, chapter: 0 };\n  var lines = xyCleanText(content).split(\"\\n\");\n  var output = [];\n  var paragraphIndex = 1;\n\n  for (var index = 0; index < lines.length; index++) {\n    var line = lines[index].replace(\/^\\s+|\\s+$\/g, \"\");\n    if (!line) continue;\n    if (\/<img\\b\/i.test(line)) {\n      output.push(line);\n      continue;\n    }\n    var idea = counts.map[String(paragraphIndex)];\n    if (\n      state.xyShowPara &&\n      idea &&\n      idea.count > 0 &&\n      line.indexOf(\n        \"xyComment:\" + bookId + \":\" + chapterId + \":\" + paragraphIndex,\n      ) < 0\n    ) {\n      line +=\n        \" \" +\n        xyCommentBubble(\n          ctx,\n          idea.count,\n          bookId,\n          chapterId,\n          paragraphIndex,\n          idea.hot,\n        );\n    }\n    output.push(line);\n    paragraphIndex++;\n  }\n\n  if (state.xyShowChapter) {\n    var card = xyCommentCard(ctx, counts.chapter, bookId, chapterId);\n    if (card) output.push(\"\", card);\n  }\n  return output.join(\"\\n\");\n}\nfunction xySearchItems(ctx, body) {\n  var response = JSON.parse(body);\n  var cards = (response.Data || {}).CardList || [];\n  var output = [];\n  for (var cardIndex = 0; cardIndex < cards.length; cardIndex++) {\n    var rows = cards[cardIndex].Body || [];\n    for (var rowIndex = 0; rowIndex < rows.length; rowIndex++) {\n      var item = rows[rowIndex].ItemData;\n      if (item && Number(item.BookId) > 0 && Number(item.BookType) === 1) {\n        output.push(JSON.stringify(item));\n      }\n    }\n  }\n  return output;\n}\n\nfunction xyExploreItems(ctx, body) {\n  var list = (JSON.parse(body).Data || {}).Books || [];\n  var output = [];\n  for (var index = 0; index < list.length; index++)\n    output.push(JSON.stringify(list[index]));\n  return output;\n}\n\nfunction xyKind(data) {\n  var values = [\n    data.CategoryName,\n    data.SubCategoryName,\n    data.ActionStatusString,\n    data.BookStatus,\n    data.WordsCount,\n  ];\n  var tags = data.BookTags || [];\n  for (var tagIndex = 0; tagIndex < tags.length && tagIndex < 4; tagIndex++)\n    values.push(tags[tagIndex]);\n  var output = [];\n  for (var index = 0; index < values.length; index++) {\n    var value = String(values[index] || \"\").replace(\/^\\s+|\\s+$\/g, \"\");\n    if (value && value !== \"0\") output.push(value);\n  }\n  return output.join(\"·\");\n}\n\nfunction xyFallbackConfig() {\n  return {\n    sites: [\n      { Id: 11, Name: \"男生\" },\n      { Id: 12, Name: \"女生\" },\n      { Id: 4, Name: \"出版\" },\n    ],\n    orders: [{ Id: 11, Name: \"人气最高\" }],\n    lines: [],\n    filterOne: [],\n    valid: false,\n  };\n}\n\nfunction xyConfigLine(lines, tag) {\n  for (var index = 0; index < (lines || []).length; index++) {\n    if (lines[index].Tag === tag) return lines[index];\n  }\n  return {};\n}\n\nfunction xyOptions(line) {\n  return line && line.FilterUnions ? line.FilterUnions : [];\n}\n\nfunction xyNames(options) {\n  var output = [];\n  for (var index = 0; index < (options || []).length; index++)\n    output.push(String(options[index].Name));\n  return output;\n}\n\nfunction xySelected(options, name) {\n  for (var index = 0; index < (options || []).length; index++) {\n    if (String(options[index].Name) === String(name)) return options[index];\n  }\n  return options && options.length ? options[0] : null;\n}\n\nfunction xyOptional(options, name) {\n  if (!name) return null;\n  for (var index = 0; index < (options || []).length; index++) {\n    if (String(options[index].Name) === String(name)) return options[index];\n  }\n  return null;\n}\n\nfunction xyOptionId(options, name, fallback) {\n  var selected = xySelected(options, name);\n  return selected && selected.Id !== undefined ? selected.Id : fallback;\n}\n\nfunction xyLoadConfig(ctx, state) {\n  var config = xyFallbackConfig();\n  var siteId = xyOptionId(config.sites, state.xySite, 11);\n  try {\n    var response = xyJson(\n      ctx,\n      XY_API_ROOT + \"ranking.php?action=config&site_id=\" + siteId,\n    );\n    var data = response.Data || response.data || {};\n    var resultCode =\n      response.Result !== undefined\n        ? Number(response.Result)\n        : Number(response.result || 0);\n    if (resultCode === 0 && data) {\n      var sites = (data.SiteList || []).filter(function (item) {\n        return XY_TEXT_SITES[String(Number(item.Id))] === 1;\n      });\n      if (sites.length) config.sites = sites;\n      config.orders = data.Orders || [];\n      config.lines = data.FiltrLines || [];\n      config.filterOne = data.FilterOne || [];\n      config.valid = true;\n    }\n  } catch (error) {\n    try {\n      xyBridge(ctx).log(\"发现配置加载失败：\" + error);\n    } catch (ignored) {}\n  }\n  if (!config.orders.length) config.orders = xyFallbackConfig().orders;\n  return config;\n}\n\nfunction xySelectAction(key, title, clearKeys, syncInfo) {\n  var clear = \"\";\n  if (clearKeys && clearKeys.length) {\n    for (var index = 0; index < clearKeys.length; index++) {\n      clear += \"delete d[\" + JSON.stringify(clearKeys[index]) + \"];\";\n    }\n  }\n  var sync = syncInfo\n    ? \"xySyncExploreInfo(infoMap, \" + JSON.stringify(syncInfo) + \");\"\n    : \"\";\n  return (\n    \"var v=infoMap[\" +\n    JSON.stringify(title) +\n    \"]||(infoMap.get&&infoMap.get(\" +\n    JSON.stringify(title) +\n    \"));var d={};try{d=JSON.parse(source.getVariable()||'{}')}catch(e){}\" +\n    clear +\n    \"d.\" +\n    key +\n    \"=String(v||'');source.setVariable(JSON.stringify(d));\" +\n    sync +\n    \"try{source.refreshExplore()}catch(e){}try{java.refreshExplore()}catch(e){}\"\n  );\n}\n\nfunction xyHeader(rows, title) {\n  rows.push({\n    title: \"—— \" + title + \" ——\",\n    url: \"\",\n    style: { layout_flexGrow: 1, layout_flexBasisPercent: 1 },\n  });\n}\n\nfunction xyEntry(rows, title, url, basis) {\n  rows.push({\n    title: title,\n    url: url,\n    style: {\n      layout_flexGrow: 1,\n      layout_flexBasisPercent: basis === undefined ? -1 : basis,\n    },\n  });\n}\n\nfunction xyActionButton(rows, title, action, basis) {\n  rows.push({\n    title: title,\n    url: \"\",\n    type: \"button\",\n    action: action,\n    style: {\n      layout_flexGrow: 1,\n      layout_flexBasisPercent: basis === undefined ? -1 : basis,\n    },\n  });\n}\n\nfunction xyChip(rows, title, url, selected) {\n  rows.push({\n    title: (selected ? \"✅ \" : \"\") + title,\n    url: url,\n    style: { layout_flexGrow: 1, layout_flexBasisPercent: 0.25 },\n  });\n}\n\nfunction xySelect(rows, title, options, selected, action, viewName) {\n  var row = {\n    title: title,\n    url: \"\",\n    type: \"select\",\n    chars: xyNames(options),\n    default: String(selected ? selected.Name : \"\"),\n    action: action,\n    style: { layout_flexGrow: 1, layout_flexBasisPercent: 0.45 },\n  };\n  if (viewName) row.viewName = \"'\" + viewName + \"'\";\n  rows.push(row);\n}\n\nfunction xyFilterTitle(tag) {\n  return XY_FILTER_TITLES[tag] || tag;\n}\n\nfunction xyQueryName(tag) {\n  return XY_QUERY_NAMES[tag] || tag;\n}\n\nfunction xyWithState(state, key, value) {\n  var next = {};\n  for (var name in state) {\n    if (Object.prototype.hasOwnProperty.call(state, name))\n      next[name] = state[name];\n  }\n  if (value === undefined || value === null || value === \"\") delete next[key];\n  else next[key] = String(value);\n  return next;\n}\n\nfunction xyBlankOptions(options) {\n  return [{ Name: \"不限\" }].concat(options || []);\n}\n\nfunction xySelectedOption(line, name) {\n  if (!name || name === \"不限\") return null;\n  return xyOptional(xyOptions(line), name);\n}\n\nfunction xySelectedFilterOne(config, state, tag) {\n  var lines = config.filterOne || [];\n  for (var index = 0; index < lines.length; index++) {\n    if (lines[index].Tag !== tag) continue;\n    var name = tag === \"great\" ? state.xyGreatName : state.xyEndureName;\n    return xySelectedOption(lines[index], name);\n  }\n  return null;\n}\n\nfunction xySelectedTag(config, state) {\n  var groups = xyOptions(xyConfigLine(config.lines, \"classid\"));\n  var value = String(state.xyTagValue || \"\");\n  for (var groupIndex = 0; groupIndex < groups.length; groupIndex++) {\n    var group = groups[groupIndex];\n    var tags = group.Extvalue || [];\n    for (var tagIndex = 0; tagIndex < tags.length; tagIndex++) {\n      if (value === String(group.Id) + \":\" + String(tags[tagIndex].Id)) {\n        return { id: String(tags[tagIndex].Id), classId: String(group.Id) };\n      }\n    }\n  }\n  return null;\n}\n\nfunction xyResetExtendedState(state) {\n  var next = {};\n  for (var key in state) {\n    if (!Object.prototype.hasOwnProperty.call(state, key)) continue;\n    if (\n      key === \"xyActionStatusName\" ||\n      key === \"xyWordsCountName\" ||\n      key === \"xyPaidModeName\" ||\n      key === \"xyTagTypeName\" ||\n      key === \"xyGreatName\" ||\n      key === \"xyEndureName\" ||\n      key === \"xyTagValue\" ||\n      \/^xyTag_\/.test(key) ||\n      \/^xyFilterOne_\/.test(key)\n    )\n      continue;\n    next[key] = state[key];\n  }\n  return next;\n}\n\nfunction xySyncExploreInfo(infoMap, values) {\n  if (!infoMap) return;\n  for (var name in values) {\n    if (!Object.prototype.hasOwnProperty.call(values, name)) continue;\n    var value = values[name];\n    try {\n      if (value === undefined || value === null || value === \"\")\n        infoMap.remove(name);\n      else infoMap.put(name, String(value));\n    } catch (error) {}\n  }\n  try {\n    infoMap.saveNow();\n  } catch (error) {}\n}\n\nfunction xyResetMainExploreState(state) {\n  var next = {};\n  for (var key in state) {\n    if (!Object.prototype.hasOwnProperty.call(state, key)) continue;\n    if (\n      key === \"xySite\" ||\n      key === \"xyOrderName\" ||\n      key === \"xyCategoryName\" ||\n      key === \"xySubCategoryName\"\n    )\n      continue;\n    next[key] = state[key];\n  }\n  return next;\n}\n\nfunction xyResetMainExploreFilters(holder, bridge, infoMap) {\n  holder.setVariable(\n    JSON.stringify(xyResetMainExploreState(xyState({ source: holder }))),\n  );\n  xySyncExploreInfo(infoMap, {\n    站点: \"男生\",\n    榜单: \"人气最高\",\n    分类: \"全部\",\n    子分类: null,\n  });\n  try {\n    holder.refreshExplore();\n  } catch (error) {}\n  try {\n    bridge.refreshExplore();\n  } catch (error) {}\n}\n\nfunction xyResetExtendedExploreFilters(holder, bridge, infoMap) {\n  holder.setVariable(\n    JSON.stringify(xyResetExtendedState(xyState({ source: holder }))),\n  );\n  xySyncExploreInfo(infoMap, {\n    连载状态: \"不限\",\n    字数范围: \"不限\",\n    付费模式: \"不限\",\n    标签匹配: \"包含任意已选标签\",\n    精品: \"不限\",\n    耐看: \"不限\",\n  });\n  try {\n    holder.refreshExplore();\n  } catch (error) {}\n  try {\n    bridge.refreshExplore();\n  } catch (error) {}\n}\n\nfunction xyRankingUrl(config, state, page, extra) {\n  var site = xySelected(config.sites, state.xySite);\n  var order = xySelected(config.orders, state.xyOrderName);\n  var categoryLine = xyConfigLine(config.lines, \"categoryid\");\n  var categories = xyOptions(categoryLine);\n  var category = xySelected(categories, state.xyCategoryName);\n  var subcategory = xyOptional(\n    category && category.Extvalue,\n    state.xySubCategoryName,\n  );\n  var actionStatus = xySelectedOption(\n    xyConfigLine(config.lines, \"actionstatus\"),\n    state.xyActionStatusName,\n  );\n  var wordsCount = xySelectedOption(\n    xyConfigLine(config.lines, \"wordscount\"),\n    state.xyWordsCountName,\n  );\n  var paidMode = xySelectedOption(\n    xyConfigLine(config.lines, \"paidmode\"),\n    state.xyPaidModeName,\n  );\n  var selectedTag = xySelectedTag(config, state);\n  var tagType = selectedTag\n    ? xySelectedOption(\n        xyConfigLine(config.lines, \"tagtype\"),\n        state.xyTagTypeName,\n      )\n    : null;\n  var great = xySelectedFilterOne(config, state, \"great\");\n  var endure = xySelectedFilterOne(config, state, \"endure\");\n\n  var params = {\n    action: \"ranking\",\n    site_id: String(site ? site.Id : 11),\n    order: String(order ? order.Id : 11),\n    page: String(page),\n    page_size: \"20\",\n    category_id: String(category ? category.Id : 0),\n  };\n  if (\n    subcategory &&\n    subcategory.Id !== undefined &&\n    String(subcategory.Id) !== \"0\"\n  ) {\n    params.subcategory_id = String(subcategory.Id);\n  }\n  if (actionStatus) params.action_status = String(actionStatus.Id);\n  if (wordsCount) params.words_count = String(wordsCount.Id);\n  if (paidMode) params.paid_mode = String(paidMode.Id);\n  if (selectedTag) {\n    params.class_id = selectedTag.classId;\n    params.tag_ids = selectedTag.id;\n  }\n  if (tagType) params.tag_type = String(tagType.Id);\n  if (great) params.great = String(great.Id);\n  if (endure) params.endure = String(endure.Id);\n\n  if (extra) {\n    for (var key in extra) {\n      if (!Object.prototype.hasOwnProperty.call(extra, key)) continue;\n      var value = extra[key];\n      if (value === undefined || value === null || value === \"\") continue;\n      params[key] = String(value);\n    }\n  }\n\n  var query = [];\n  for (var name in params) {\n    if (!Object.prototype.hasOwnProperty.call(params, name)) continue;\n    if (name === \"subcategory_id\" && params[name] === \"0\") continue;\n    var encoded =\n      name === \"page\" ? params[name] : encodeURIComponent(params[name]);\n    query.push(name + \"=\" + encoded);\n  }\n  return XY_API_ROOT + \"ranking.php?\" + query.join(\"&\");\n}\n\nfunction xyAddTagChips(rows, config, state) {\n  var groups = xyOptions(xyConfigLine(config.lines, \"classid\"));\n  if (!groups.length) return;\n  xyHeader(rows, \"标签筛选 · 单选\");\n  for (var groupIndex = 0; groupIndex < groups.length; groupIndex++) {\n    var group = groups[groupIndex];\n    var tags = group.Extvalue || [];\n    if (!tags.length) continue;\n    xyHeader(rows, String(group.Name));\n    for (var tagIndex = 0; tagIndex < tags.length; tagIndex++) {\n      var tag = tags[tagIndex];\n      var value = String(group.Id) + \":\" + String(tag.Id);\n      var selected = String(state.xyTagValue) === value;\n      var next = xyWithState(state, \"xyTagValue\", selected ? \"\" : value);\n      xyChip(\n        rows,\n        String(tag.Name),\n        xyRankingUrl(config, next, \"{{page}}\"),\n        selected,\n      );\n    }\n  }\n}\n\nfunction xySquareSiteId(state) {\n  return xySiteIdByName(state.xySite) === 12 ? 12 : 11;\n}\n\nfunction xySquareJumpQuery(jumpUrl) {\n  var text = String(jumpUrl || \"\");\n  var match = text.match(\/[?&]query=([^&]+)\/i);\n  if (!match) return {};\n  var raw = match[1];\n  try {\n    if (raw.indexOf(\"%7B\") === 0 || raw.indexOf(\"%7b\") === 0)\n      raw = decodeURIComponent(raw);\n    var parsed = JSON.parse(raw);\n    return parsed && typeof parsed === \"object\" ? parsed : {};\n  } catch (error) {\n    return {};\n  }\n}\n\nfunction xySquareRankingUrl(siteId, order, filter, category, page) {\n  var params = {\n    action: \"ranking\",\n    site_id: String(siteId),\n    order: String(order),\n    page: String(page),\n    page_size: \"20\",\n  };\n  if (\n    category !== undefined &&\n    category !== null &&\n    String(category) !== \"-1\"\n  ) {\n    params.category_id = String(category);\n  }\n  if (filter) params.filter = String(filter).replace(\/=\/g, \":\");\n  var query = [];\n  for (var name in params) {\n    if (!Object.prototype.hasOwnProperty.call(params, name)) continue;\n    query.push(\n      name +\n        \"=\" +\n        (name === \"page\" ? params[name] : encodeURIComponent(params[name])),\n    );\n  }\n  return XY_API_ROOT + \"ranking.php?\" + query.join(\"&\");\n}\n\nfunction xySquareListUrl(itemId, page) {\n  return (\n    XY_API_ROOT +\n    \"ranking.php?action=list&item_id=\" +\n    encodeURIComponent(String(itemId)) +\n    \"&page=\" +\n    page +\n    \"&page_size=100\"\n  );\n}\n\nfunction xySquareBlockUrl(name, data, state) {\n  var siteId = xySquareSiteId(state);\n  var title = data.Title || {};\n  var common = data.CommonExt || {};\n  var query = xySquareJumpQuery(title.JumpActionUrl);\n  var lowerName = String(name || \"\").toLowerCase();\n  var column = String(common.ColumnName || \"\");\n  if (query.itemId !== undefined && query.itemId !== null) {\n    return xySquareListUrl(query.itemId, \"{{page}}\");\n  }\n  if (query.filter !== undefined && query.filter !== null) {\n    return xySquareRankingUrl(\n      siteId,\n      1,\n      query.filter,\n      query.categoryId,\n      \"{{page}}\",\n    );\n  }\n  if (\n    String(title.JumpActionUrl || \"\").indexOf(\"SanJiang\") >= 0 ||\n    lowerName.indexOf(\"新书\") >= 0 ||\n    lowerName.indexOf(\"潜力\") >= 0 ||\n    column.indexOf(\"xinshu\") >= 0\n  ) {\n    return xySquareRankingUrl(siteId, 15, \"\", query.categoryId, \"{{page}}\");\n  }\n  if (lowerName.indexOf(\"免费\") >= 0) {\n    return xySquareRankingUrl(siteId, 11, \"paidmode:1\", 0, \"{{page}}\");\n  }\n  if (lowerName.indexOf(\"完本\") >= 0) {\n    return xySquareRankingUrl(siteId, 11, \"actionstatus:1\", 0, \"{{page}}\");\n  }\n  return xySquareRankingUrl(siteId, 11, \"\", query.categoryId, \"{{page}}\");\n}\n\nfunction xyLoadSquare(ctx, state) {\n  var sourceSiteId = xySiteIdByName(state.xySite);\n  if (sourceSiteId !== 11 && sourceSiteId !== 12) return [];\n  var siteId = xySquareSiteId(state);\n  var blocks = [];\n  try {\n    var response = xyJson(\n      ctx,\n    XY_API_ROOT +\n        \"ranking.php?action=square&site=\" +\n        siteId +\n        \"&page=1&page_size=20\",\n    );\n    var resultCode =\n      response.Result !== undefined\n        ? Number(response.Result)\n        : Number(response.result || 0);\n    var items = (response.Data || response.data || {}).Items || [];\n    if (resultCode !== 0 || !items.length) return blocks;\n    for (var index = 0; index < items.length; index++) {\n      var data = items[index].Data || {};\n      var name = String((data.Title || {}).Name || \"\").replace(\n        \/^\\s+|\\s+$\/g,\n        \"\",\n      );\n      if (!name) continue;\n      var hasBooks = false;\n      var listKeys = [\"List\", \"BookList\", \"DataList\", \"Items\", \"ItemList\"];\n      for (var keyIndex = 0; keyIndex < listKeys.length; keyIndex++) {\n        var value = data[listKeys[keyIndex]];\n        if (value && value.length) hasBooks = true;\n      }\n      if (!hasBooks) continue;\n      blocks.push({ name: name, url: xySquareBlockUrl(name, data, state) });\n    }\n  } catch (error) {\n    try {\n      xyBridge(ctx).log(\"起点书城分块加载失败：\" + error);\n    } catch (ignored) {}\n  }\n  return blocks;\n}\n\nfunction xyAddSquareBlocks(ctx, rows, state) {\n  var blocks = xyLoadSquare(ctx, state);\n  if (!blocks.length) return;\n  xyHeader(rows, \"起点书城 · \" + state.xySite);\n  for (var index = 0; index < blocks.length; index++) {\n    xyChip(rows, blocks[index].name, blocks[index].url, false);\n  }\n}\n\nfunction xyExplore(ctx) {\n  var state = xyState(ctx);\n  var config = xyLoadConfig(ctx, state);\n  var categoryLine = xyConfigLine(config.lines, \"categoryid\");\n  var categories = xyOptions(categoryLine);\n  var site = xySelected(config.sites, state.xySite);\n  var order = xySelected(config.orders, state.xyOrderName);\n  var category = xySelected(categories, state.xyCategoryName);\n  var actionStatusLine = xyConfigLine(config.lines, \"actionstatus\");\n  var wordsCountLine = xyConfigLine(config.lines, \"wordscount\");\n  var paidModeLine = xyConfigLine(config.lines, \"paidmode\");\n  var tagTypeLine = xyConfigLine(config.lines, \"tagtype\");\n  var rows = [];\n\n  xyAddSquareBlocks(ctx, rows, state);\n  xyHeader(rows, \"主筛选 · 起点发现\");\n  xySelect(\n    rows,\n    \"站点\",\n    config.sites,\n    site,\n    xySelectAction(\"xySite\", \"站点\", [\"xyCategoryName\", \"xySubCategoryName\"], {\n      分类: \"全部\",\n      子分类: null,\n    }),\n    \"站点\",\n  );\n  xySelect(\n    rows,\n    \"榜单\",\n    config.orders,\n    order,\n    xySelectAction(\"xyOrderName\", \"榜单\"),\n    \"榜单\",\n  );\n  xySelect(\n    rows,\n    \"分类\",\n    categories,\n    category,\n    xySelectAction(\"xyCategoryName\", \"分类\", [\"xySubCategoryName\"], {\n      子分类: null,\n    }),\n    \"分类\",\n  );\n  var subcategoryOptions = xyBlankOptions(\n    (category && category.Extvalue) || [],\n  );\n  if (subcategoryOptions.length > 1) {\n    xySelect(\n      rows,\n      \"子分类\",\n      subcategoryOptions,\n      xySelected(subcategoryOptions, state.xySubCategoryName),\n      xySelectAction(\"xySubCategoryName\", \"子分类\"),\n      \"子类\",\n    );\n  }\n  xyActionButton(\n    rows,\n    \"重置主筛选（恢复默认）\",\n    \"xyResetMainExploreFilters(source, java, infoMap)\",\n    1,\n  );\n\n  xyHeader(rows, \"扩展筛选\");\n  xySelect(\n    rows,\n    \"连载状态\",\n    xyBlankOptions(xyOptions(actionStatusLine)),\n    { Name: state.xyActionStatusName },\n    xySelectAction(\"xyActionStatusName\", \"连载状态\"),\n    \"状态\",\n  );\n  xySelect(\n    rows,\n    \"字数范围\",\n    xyBlankOptions(xyOptions(wordsCountLine)),\n    { Name: state.xyWordsCountName },\n    xySelectAction(\"xyWordsCountName\", \"字数范围\"),\n    \"字数\",\n  );\n  xySelect(\n    rows,\n    \"付费模式\",\n    xyBlankOptions(xyOptions(paidModeLine)),\n    { Name: state.xyPaidModeName },\n    xySelectAction(\"xyPaidModeName\", \"付费模式\"),\n    \"付费\",\n  );\n  xySelect(\n    rows,\n    \"标签匹配\",\n    xyOptions(tagTypeLine),\n    { Name: state.xyTagTypeName },\n    xySelectAction(\"xyTagTypeName\", \"标签匹配\"),\n    \"匹配\",\n  );\n  xySelect(\n    rows,\n    \"精品\",\n    xyBlankOptions(xyOptions(xyConfigLine(config.filterOne, \"great\"))),\n    { Name: state.xyGreatName },\n    xySelectAction(\"xyGreatName\", \"精品\"),\n    \"精品\",\n  );\n  xySelect(\n    rows,\n    \"耐看\",\n    xyBlankOptions(xyOptions(xyConfigLine(config.filterOne, \"endure\"))),\n    { Name: state.xyEndureName },\n    xySelectAction(\"xyEndureName\", \"耐看\"),\n    \"耐看\",\n  );\n  xyActionButton(\n    rows,\n    \"重置扩展筛选\",\n    \"xyResetExtendedExploreFilters(source, java, infoMap)\",\n    1,\n  );\n\n  xyHeader(rows, \"结果操作\");\n  xyEntry(rows, \"当前结果\", xyRankingUrl(config, state, \"{{page}}\"), 1);\n\n  xyAddTagChips(rows, config, state);\n\n  if (!config.valid) {\n    xyHeader(rows, \"配置接口暂不可用，当前使用默认榜单\");\n  }\n  return JSON.stringify(rows);\n}\r\n","lastUpdateTime":"1789280787917","loginUi":"@js:\nresult = xyLoginUi(this);","loginUrl":"function login(){}\nfunction xyLoginState() {\n  var state = {};\n  try {\n    state = JSON.parse(source.getVariable() || \"{}\");\n  } catch (error) {\n    state = {};\n  }\n  if (!state || typeof state !== \"object\") state = {};\n  if (state.xySite === \"男站\") state.xySite = \"男生\";\n  if (state.xySite === \"女站\") state.xySite = \"女生\";\n  if (\n    state.xySite !== \"男生\" &&\n    state.xySite !== \"女生\" &&\n    state.xySite !== \"出版\"\n  )\n    state.xySite = \"男生\";\n  if (!state.xyOrderName || state.xyOrderName === \"默认榜单\")\n    state.xyOrderName = \"人气最高\";\n  if (!state.xyCategoryName) state.xyCategoryName = \"全部\";\n  if (state.xyShowPara !== false) state.xyShowPara = true;\n  if (state.xyShowChapter !== false) state.xyShowChapter = true;\n  if (state.xyLargeBubble !== false) state.xyLargeBubble = true;\n  if (state.xyFullComment !== true) state.xyFullComment = false;\n  if (state.xyCustomMode !== true) state.xyCustomMode = false;\n  state.xyYwkey = String(state.xyYwkey || \"\");\n  state.xyYwguid = String(state.xyYwguid || \"\");\n  state.xyUserToken = String(state.xyUserToken || \"\");\n  state.xyGuestAuthor = String(state.xyGuestAuthor || \"\");\n  state.xyGuestGroup = String(state.xyGuestGroup || \"\");\n  state.xyGuestPass = String(state.xyGuestPass || \"\");\n  state.xyGuestVerified = state.xyGuestVerified === true;\n  state.xyAndroidId = String(state.xyAndroidId || \"\");\n  state.xyUserName = String(state.xyUserName || \"\");\n  state.xyUserLevel = String(state.xyUserLevel || \"\");\n  return state;\n}\n\nfunction xyLoginSave(state) {\n  source.setVariable(JSON.stringify(state));\n  try {\n    java.reLoginView();\n  } catch (error) {}\n}\n\n\nfunction xyLoginAndroidId(state) {\n  var value = \"\";\n  try { value = String(java.androidId() || \"\").replace(\/^\\s+|\\s+$\/g, \"\"); } catch (error) {}\n  if (!value) value = String((state && state.xyAndroidId) || \"\").replace(\/^\\s+|\\s+$\/g, \"\");\n  if (!value) {\n    var chars = \"0123456789abcdef\";\n    for (var index = 0; index < 16; index++) value += chars.charAt(Math.floor(Math.random() * 16));\n  }\n  return value.toUpperCase();\n}\nfunction xyLoginRequest(action, data) {\n  var options = {method:\"POST\",headers:{\"Content-Type\":\"application\/json\",Accept:\"application\/json\"},body:JSON.stringify(data || {})};\n  var response = JSON.parse(java.ajax(XY_API_ROOT + \"user_api.php?action=\" + action + \",\" + JSON.stringify(options)));\n  if (response.error) throw new Error(response.error);\n  return response;\n}\nfunction xyLoginStoreAuth(state, response, androidId) {\n  var user = response.user || {};\n  state.xyAndroidId = String(androidId || xyLoginAndroidId(state));\n  state.xyUserToken = String(response.token || \"\");\n  state.xyUserName = String(user.username || \"\");\n  state.xyUserLevel = String(user.level || \"guest\");\n  state.xyCustomMode = false;\n  xyLoginSave(state);\n  return user;\n}\nfunction xyLoginGuest() {\n  var state = xyLoginState();\n  var sourceAuthor = xyLoginFieldValue(\"源作者\");\n  var feedbackGroup = xyLoginFieldValue(\"官方反馈群\");\n  var temporaryPassword = xyLoginFieldValue(\"临时口令\");\n  state.xyGuestAuthor = sourceAuthor;\n  state.xyGuestGroup = feedbackGroup;\n  state.xyGuestPass = temporaryPassword;\n  source.setVariable(JSON.stringify(state));\n  xyLoginSaveGuestFields(sourceAuthor, feedbackGroup, temporaryPassword);\n  if (!sourceAuthor || !feedbackGroup || !temporaryPassword)\n    return java.toast(\"请手动填写源作者、官方反馈群和临时口令\");\n  var androidId = xyLoginAndroidId(state);\n  var response;\n  try {\n    response = xyLoginRequest(\"guest\", {\n      source_author: sourceAuthor,\n      feedback_group: feedbackGroup,\n      temporary_password: temporaryPassword,\n      android_id: androidId\n    });\n  } catch (error) {\n    return java.toast(String((error && error.message) || error));\n  }\n  state.xyGuestVerified = true;\n  var user = xyLoginStoreAuth(state, response, androidId);\n  java.toast(\"游客授权成功：\" + String(user.privilege || \"\"));\n}\nfunction xyLoginFieldValue(name) {\n  return String((result && result.get(name)) || \"\").replace(\/^\\s+|\\s+$\/g, \"\");\n}\n\nfunction xyLoginSaveGuestFields(sourceAuthor, feedbackGroup, temporaryPassword) {\n  try {\n    var loginInfo = {};\n    try {\n      loginInfo = JSON.parse(source.getLoginInfo() || \"{}\");\n    } catch (error) {}\n    loginInfo[\"源作者\"] = sourceAuthor;\n    loginInfo[\"官方反馈群\"] = feedbackGroup;\n    loginInfo[\"临时口令\"] = temporaryPassword;\n    source.putLoginInfo(JSON.stringify(loginInfo));\n    try {\n      java.upLoginData(loginInfo);\n    } catch (error) {}\n  } catch (error) {}\n}\n\nfunction xyLoginWeb() { \n  var state = xyLoginState();\n  var androidId = xyLoginAndroidId(state);\n  state.xyAndroidId = androidId;\n  xyLoginSave(state);\n  var preload = [\n    \"window.xySaveLegadoAuth = function(payload) {\",\n    \"  var state = {};\",\n    \"  try { state = JSON.parse(source.getVariable() || '{}'); } catch (error) { state = {}; }\",\n    \"  var user = (payload && payload.user) || {};\",\n    \"  state.xyUserToken = String((payload && payload.token) || '');\",\n    \"  state.xyAndroidId = String((payload && payload.android_id) || state.xyAndroidId || '');\",\n    \"  state.xyUserName = String(user.username || '');\",\n    \"  state.xyUserLevel = String(user.level || 'guest');\",\n    \"  state.xyCustomMode = false;\",\n    \"  var encoded = JSON.stringify(state);\",\n    \"  try { source.putVariable(encoded); } catch (error) { try { source.setVariable(encoded); } catch (ignored) {} }\",\n    \"  setTimeout(function(){ try { if (java.reLoginView) java.reLoginView(true); } catch (error) {} try { window.close(); } catch (error) {} }, 1500);\",\n    \"  return true;\",\n    \"};\"\n  ].join(\"\\n\");\n  java.showBrowser(XY_API_ROOT + \"auth.php?mode=legado&android_id=\" + encodeURIComponent(androidId), null, preload);\n\n}\nfunction xyLoginUserCenter() {\n  var state = xyLoginState();\n  var token = String(state.xyUserToken || \"\");\n  var androidId = String(state.xyAndroidId || \"\");\n  var preload = [\n    \"try { localStorage.setItem('qidian-user-token', \" + JSON.stringify(token) + \"); } catch (e) {}\",\n    \"try { localStorage.setItem('qidian-android-id', \" + JSON.stringify(androidId) + \"); } catch (e) {}\"\n  ].join(\"\\n\");\n  try { java.showBrowser(XY_API_ROOT + \"account.php\", null, preload); }\n  catch (error) { java.toast(\"无法打开用户中心：\" + String(error)); }\n}\nfunction xyLoginSyncAuth() {\n  var state = xyLoginState();\n  if (!state.xyUserToken) {\n    xyLoginSave(state);\n    java.toast(\"当前尚未授权\");\n    return;\n  }\n  var options = {method:\"GET\",headers:{Accept:\"application\/json\",\"X-Sec-Token\":state.xyUserToken,\"X-Android-Id\":state.xyAndroidId}};\n  var response;\n  try {\n    response = JSON.parse(java.ajax(XY_API_ROOT + \"user_api.php?action=me,\" + JSON.stringify(options)));\n  } catch (error) {\n    xyLoginSave(state);\n    java.toast(\"授权状态同步失败，请稍后重试\");\n    return;\n  }\n  if (response.error) {\n    state.xyUserToken = \"\";\n    state.xyUserName = \"\";\n    state.xyUserLevel = \"\";\n    state.xyGuestVerified = false;\n    xyLoginSave(state);\n    java.toast(\"授权已失效，请重新登录\");\n    return;\n  }\n  var user = response.user || {};\n  state.xyUserToken = String(response.token || state.xyUserToken);\n  state.xyUserName = String(user.username || \"\");\n  state.xyUserLevel = String(user.level || \"guest\");\n  xyLoginSave(state);\n  java.toast(\"授权状态已同步：\" + (user.username || \"游客用户\"));\n}\nfunction xyLoginUsage() {\n  var state = xyLoginState();\n  if (!state.xyUserToken) return java.toast(\"请先申请游客 Token 或登录账号\");\n  var options = {method:\"GET\",headers:{Accept:\"application\/json\",\"X-Sec-Token\":state.xyUserToken,\"X-Android-Id\":state.xyAndroidId}};\n  var response = JSON.parse(java.ajax(XY_API_ROOT + \"user_api.php?action=me,\" + JSON.stringify(options)));\n  if (response.error) return java.toast(response.error);\n  var user = response.user || {};\n  java.longToast(\"\\n用户：\" + (user.username || \"游客用户\") + \"\\n等级：\" + (user.level_name || \"--\") + \"\\n权益：\" + (user.privilege || \"--\") + \"\\n今日：\" + Number(user.today_count || 0) + \"\\n昨日：\" + Number(user.yesterday_count || 0) + \"\\n累计：\" + Number(user.total_count || 0));\n}\nfunction xyLoginExit() {\n  var state = xyLoginState();\n  if (!state.xyUserToken) return;\n  xyLoginRequest(\"logout\", {});\n  state.xyUserToken = \"\";\n  state.xyUserName = \"\";\n  state.xyUserLevel = \"\";\n  xyLoginSave(state);\n  java.toast(\"已退出用户系统\");\n}\n\nfunction xyLoginTogglePara() {\n  var state = xyLoginState();\n  state.xyShowPara = !state.xyShowPara;\n  xyLoginSave(state);\n  java.toast(\n    \"段评已\" + (state.xyShowPara ? \"开启\" : \"关闭\") + \"，请刷新当前章节\",\n  );\n}\n\nfunction xyLoginToggleChapter() {\n  var state = xyLoginState();\n  state.xyShowChapter = !state.xyShowChapter;\n  xyLoginSave(state);\n  java.toast(\n    \"章评已\" + (state.xyShowChapter ? \"开启\" : \"关闭\") + \"，请刷新当前章节\",\n  );\n}\n\nfunction xyLoginToggleLargeBubble() {\n  var state = xyLoginState();\n  state.xyLargeBubble = !state.xyLargeBubble;\n  xyLoginSave(state);\n  java.toast(\n    \"段评气泡已切换为\" +\n      (state.xyLargeBubble ? \"大气泡\" : \"小气泡\") +\n      \"，请刷新当前章节\",\n  );\n}\n\nfunction xyLoginToggleCommentScreen() {\n  var state = xyLoginState();\n  state.xyFullComment = !state.xyFullComment;\n  xyLoginSave(state);\n  java.toast(\"段评已切换为\" + (state.xyFullComment ? \"全屏\" : \"半屏\"));\n}\n\nfunction xyLoginSaveCredential() {\n  var state = xyLoginState();\n  state.xyYwkey = String((result && result.get(\"ywkey\")) || \"\").replace(\n    \/^\\s+|\\s+$\/g,\n    \"\",\n  );\n  state.xyYwguid = String((result && result.get(\"ywguid\")) || \"\").replace(\n    \/^\\s+|\\s+$\/g,\n    \"\",\n  );\n  xyLoginSave(state);\n  java.toast(\"自订凭证已保存\");\n}\n\nfunction xyLoginToggleCustom() {\n  var state = xyLoginState();\n  if (!state.xyCustomMode && (!state.xyYwkey || !state.xyYwguid)) {\n    java.toast(\"请先填写并保存 ywkey 与 ywguid\");\n    return;\n  }\n  state.xyCustomMode = !state.xyCustomMode;\n  xyLoginSave(state);\n  java.toast(\n    \"自订模式已\" +\n      (state.xyCustomMode ? \"开启\" : \"关闭\") +\n      \"，请刷新目录后生效\",\n  );\n}\n\nfunction xyLoginUi() {\n  var state = xyLoginState();\n  var rows = [];\n\n  function button(name, action, fullWidth) {\n    rows.push({\n      title: \"\",\n      url: \"\",\n      name: name,\n      type: \"button\",\n      action: action,\n      style: {\n        layout_flexGrow: 1,\n        layout_flexBasisPercent: fullWidth ? 1 : 0.45,\n      },\n    });\n  }\n\n  function label(name) {\n    button(\"—— \" + name + \" ——\", \"void 0;\", true);\n  }\n\n  label(\"正文用户系统\");\n  rows.push({title:\"\",url:\"\",name:\"源作者\",type:\"text\",default:state.xyGuestAuthor || \"\",style:{layout_flexGrow:1,layout_flexBasisPercent:1}});\n  rows.push({title:\"\",url:\"\",name:\"官方反馈群\",type:\"text\",default:state.xyGuestGroup || \"\",style:{layout_flexGrow:1,layout_flexBasisPercent:1}});\n  rows.push({title:\"\",url:\"\",name:\"临时口令\",type:\"text\",default:state.xyGuestPass || \"\",style:{layout_flexGrow:1,layout_flexBasisPercent:1}});\n  button(state.xyUserToken ? \"✅ 已授权：\" + (state.xyUserName || state.xyUserLevel || \"guest\") : \"⬜ 尚未授权\", \"xyLoginSyncAuth()\");\n  button(\"申请游客Token\", \"xyLoginGuest()\");\n  button(\"网页注册\/登录\", \"xyLoginWeb()\");\n  button(\"查看使用次数\", \"xyLoginUsage()\");\n  button(\"用户中心\", \"xyLoginUserCenter()\");\n  button(\"同步授权状态\", \"xyLoginSyncAuth()\");\n  button(\"退出用户系统\", \"xyLoginExit()\");\n\n  label(\"自订正文凭证\");\n  rows.push({\n    title: \"\",\n    url: \"\",\n    name: \"ywkey\",\n    type: \"text\",\n    default: state.xyYwkey || \"\",\n    style: { layout_flexGrow: 1, layout_flexBasisPercent: 1 },\n  });\n  rows.push({\n    title: \"\",\n    url: \"\",\n    name: \"ywguid\",\n    type: \"text\",\n    default: state.xyYwguid || \"\",\n    style: { layout_flexGrow: 1, layout_flexBasisPercent: 1 },\n  });\n  button(\n    state.xyCustomMode ? \"✅ 自订模式已开启\" : \"⬜ 自订模式已关闭\",\n    \"xyLoginToggleCustom()\",\n  );\n  button(\"保存自订凭证\", \"xyLoginSaveCredential()\");\n\n  label(\"阅读评论\");\n  button(\n    state.xyShowPara ? \"✅ 段评气泡\" : \"⬜ 段评气泡\",\n    \"xyLoginTogglePara()\",\n  );\n  button(\n    state.xyLargeBubble ? \"✅ 大气泡（TEXT）\" : \"⬜ 小气泡（text）\",\n    \"xyLoginToggleLargeBubble()\",\n  );\n  button(\n    state.xyShowChapter ? \"✅ 章末评论\" : \"⬜ 章末评论\",\n    \"xyLoginToggleChapter()\",\n  );\n  button(\n    state.xyFullComment ? \"✅ 全屏段评\" : \"📱 半屏段评\",\n    \"xyLoginToggleCommentScreen()\",\n  );\n\n  return JSON.stringify(rows);\n}\r\n","respondTime":180000,"ruleBookInfo":{"author":"@js:\nvar d=JSON.parse(result).Data||{};result=(d.AuthorInfo&&d.AuthorInfo.Author)||d.Author||'';","coverUrl":"@js:\nvar d=JSON.parse(result).Data||{};var b=d.BaseBookInfo||d;result=xyCoverUrl(this, b.BookId||d.BookId);","intro":"@js:\nvar d=JSON.parse(result).Data||{};var b=d.BaseBookInfo||d;result=b.Description||d.Description||'';","kind":"@js:\nvar root=JSON.parse(result).Data||{};var d=root.BaseBookInfo||root;var blacklist=(d.FreshManSimple||root.FreshManSimple)&&Number((d.FreshManSimple||root.FreshManSimple).IsInBlackList||0);result=xyKind(d)+'·'+(blacklist?'非限免':'限免');","lastChapter":"@js:\nvar root=JSON.parse(result).Data||{};var d=root.BaseBookInfo||root;var c=d.ChapterInfo||d;result=c.LastVipUpdateChapterName||c.LastUpdateChapterName||root.LastVipUpdateChapterName||root.LastUpdateChapterName||'';","name":"@js:\nvar d=JSON.parse(result).Data||{};var b=d.BaseBookInfo||d;result=b.BookName||d.BookName||'';","tocUrl":"@js:\nvar d=JSON.parse(result).Data||{};var b=d.BaseBookInfo||d;result=xyCatalogUrl(this, b.BookId||d.BookId);","wordCount":"@js:\nvar d=JSON.parse(result).Data||{};var b=d.BaseBookInfo||d;result=b.WordsCount||b.WordsCnt||d.WordsCount||d.WordsCnt||'';"},"ruleContent":{"content":"@js:\nvar xyPayload = xyChapterPayload(this, result);\nvar xyUrl = xyContentUrl(this, xyPayload);\nvar xyBody = xyAjax(this, xyUrl);\nresult = xyFormatContent(this, xyBody, xyUrl);","imageStyle":"DEFAULT"},"ruleExplore":{"author":"$.AuthorName","bookList":"$.Data.Books[*]||$.Data.Data.Items[*]","bookUrl":"@js:\nresult = xyDetailUrl(this, result.BookId);","coverUrl":"@js:\nresult = xyCoverUrl(this, result.BookId);","intro":"$.Description||$.Desc","kind":"@js:\nresult = xyKind(result);","lastChapter":"$.LastChapterName","name":"$.BookName","wordCount":"@js:\nresult = String(result.WordsCount || result.WordsCnt || '');"},"ruleSearch":{"author":"$.AuthorName","bookList":"$.Data.CardList[*].Body[*].ItemData","bookUrl":"@js:\nresult = xyDetailUrl(this, result.BookId);","coverUrl":"@js:\nresult = xyCoverUrl(this, result.BookId);","intro":"$.Description","kind":"@js:\nresult = xyKind(result);","lastChapter":"$.LastChapterName","name":"$.BookName","wordCount":"@js:\nresult = String(result.WordsCount || result.WordsCnt || '');"},"ruleToc":{"chapterList":"$.data[*]","chapterName":"$.N","chapterUrl":"$.C","isVip":"$.V","isVolume":"$.Vo","updateTime":"$.T"},"searchUrl":"@js:\nresult = xySearchUrl(this, key, page);","weight":0}]