起点限免(同人小说网)
https://m.qidian.com#同人小说网
ah-fa-fa (14292)08/06 09:06
纯免费书源,遇到收费直接删。 目前上涨到日5000章节,不定期提升 2天外阅读,同人小说网,盗版资源。邀请码注册,登录页输入邀请码点击注册。看原注释 加群或tg私聊拿邀请码
{
"bookSourceComment": "1045801523群号\n纯免费,盗版源\ntg:https:\/\/t.me\/+SCsvr5O7YIc3NmIx",
"bookSourceGroup": "trxs,api,优质,盗版",
"bookSourceName": "起点限免(同人小说网)",
"bookSourceType": 0,
"bookSourceUrl": "https:\/\/m.qidian.com#同人小说网",
"concurrentRate": "1200",
"customButton": false,
"customOrder": -3,
"enabled": true,
"enabledCookieJar": true,
"enabledExplore": true,
"eventListener": false,
"exploreUrl": "@js:\n\/\/全部玄幻::@js:getApiUrl('\/explore\/store', {site_type:11,category_id:21,sub_category_id:0,page})\n\/\/排行::@js:getApiUrl('\/explore\/rank', {site_type:2,category_id:-1,top_id:2060,page})\nJSON.stringify(requestApiUrl(\n '\/explore\/init',\n {\n type: String(source.get('type') || 'all'),\n gender: String(source.get('gender') || '1'),\n }\n))",
"header": "{\n \"User-Agent\": \"Mozilla\/5.0 (Linux; Android 10; K) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/116.0.0.0 Mobile Safari\/537.36\"\n}",
"jsLib": "const api = 'https:\/\/www.trxs666.com'\n\nfunction getApiUrl(path, data = {}) {\n data = Object.entries(data).map(v => v.join('=')).join('&')\n let option = {\n method: \"GET\",\n headers: {\n 'authorization': `Bearer ${this.source.getVariable() || ''}`,\n }\n }\n let url = api + path + '?' + data + ',' + JSON.stringify(option)\n return url\n}\n\nfunction requestApiUrl(path, data = {}) {\n let url = getApiUrl.call(this, path, data)\n try {\n let res = JSON.parse(this.java.ajax(url))\n if (res.msg && res.msg != 'success') {\n this.java.longToast(`\\n操作失败,code${res.code}:\\n${res.msg}`)\n }\n if (res.data) {\n return res.data\n }\n } catch (e) {\n this.java.log(`请求共享接口${path}异常:` + e.message)\n }\n return null\n}\n\nfunction getCsrfToken(that) {\n let { java, cookie } = that\n let bsu = \"https:\/\/m.qidian.com\"\n let csrf = cookie.getKey(bsu, \"_csrfToken\")\n let getStr = length => {\n let chars = \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\"\n return Array(length)\n .fill(\"\")\n .map(() => chars[Math.floor(62 * Math.random())])\n .join(\"\")\n }\n if (!String(csrf)) {\n csrf = getStr(40)\n cookie.replaceCookie(bsu, \"_csrfToken=\" + csrf)\n }\n return \"_csrfToken=\" + csrf\n}\n\nfunction Map(k){\n var s = null;\n try { s = this && this.source ? this.source : null; } catch(e) {}\n try { if (!s && typeof source !== \"undefined\") s = source; } catch(e2) {}\n var m = s && s.getLoginInfoMap ? s.getLoginInfoMap() : null;\n if(k==null) return m;\n var v=m && (m.get ? m.get(k) : m[k]);\n if ((v === null || v === undefined || String(v) === \"\") && s && s.get) {\n try { v = s.get(k); } catch(e3) {}\n }\n return v==null?\"\":String(v);\n}\n\nfunction qdToggle(key, defaultValue) {\n var value = Map.call(this, key);\n if (value === \"\") return !!defaultValue;\n value = String(value).replace(\/^\\s+|\\s+$\/g, \"\").toLowerCase();\n return value === \"✅\" || value === \"1\" || value === \"true\" || value === \"开\" || value === \"开启\";\n}\n\nfunction getCfg() {\n var dp = qdToggle.call(this, \"段评开关\", true);\n var zm = qdToggle.call(this, \"章名段评\", true);\n var tl = qdToggle.call(this, \"本章讨论\", true);\n var zz = qdToggle.call(this, \"作者评论\", true);\n var rp = qdToggle.call(this, \"热门评论\", true);\n return { fs: qdToggle.call(this, \"段评分色\", false), dp: dp, zm: zm, tl: tl, zz: zz, rp: rp, pl: dp || zm || tl || zz || rp };\n}\n\nfunction qdLoginVal(key, def) {\n try {\n var s = null;\n try { s = this && this.source ? this.source : null; } catch(e0) {}\n try { if (!s && typeof source !== \"undefined\") s = source; } catch(e1) {}\n var m = s && s.getLoginInfoMap ? s.getLoginInfoMap() : null;\n var v = m && (m.get ? m.get(key) : m[key]);\n if (v !== null && v !== undefined && String(v) !== \"\") return String(v);\n } catch(e) {}\n return def == null ? \"\" : String(def);\n}\n\nfunction qdApiBase() {\n return \"https:\/\/pl.001122.top\/api\/qidian_full_api.php\";\n}\nfunction qdApiUrl(action, params, compat) {\n params = params || {};\n params.action = action;\n if (compat) params.compat = 1;\n var qs = [];\n for (var k in params) {\n if (!params.hasOwnProperty(k)) continue;\n if (params[k] === undefined || params[k] === null || params[k] === \"\") continue;\n qs.push(encodeURIComponent(k) + \"=\" + encodeURIComponent(String(params[k])));\n }\n return qdApiBase() + \"?\" + qs.join(\"&\");\n}\nfunction qdJsonParse(raw) {\n var text = String(raw || \"{}\").trim();\n text = text.replace(\/(\"(?:MidpageId|MidPageId|PageId)\"\\s*:\\s*)(\\d{16,})\/g, '$1\"$2\"');\n return JSON.parse(text || \"{}\");\n}\n\nfunction qdApi(action, params) {\n const { java } = this;\n var url = qdApiUrl.call(this, action, params || {}, false);\n var raw = \"\";\n try {\n raw = java && java.ajax ? java.ajax(url) : java.get(url, {}).body();\n return qdJsonParse(raw);\n } catch(e) {\n try { if (java && java.log) java.log(\"起点评论接口请求失败:\" + e); } catch(_) {}\n return { code: -1, message: String(e), data: {}, rawText: raw };\n }\n}\n\nfunction qdMidpageEnabled() {\n return Map.call(this, \"\\u5f69\\u86cb\\u7ae0\\u5f00\\u5173\") == \"✅\";\n}\n\nfunction qdMidpageChapters(bid) {\n try {\n var res = qdApi.call(this, \"midpage_chapters\", {book_id: bid});\n var data = res && (res.data || res.Data || {}) || {};\n var list = data.Chapters || data.chapters || data.DataList || data.MidpageChapters || data.MidPageChapters || data;\n return qdList(list);\n } catch(e) {\n return [];\n }\n}\n\nfunction setMidpageUrl(bid, cid, midpageId) {\n var eggKey = \"midpage:\" + String(bid) + \":\" + String(cid) + \":\" + String(midpageId || \"\");\n return \"data:;base64,\" + java.base64Encode(String(eggKey)) + \",{\\\"type\\\":\\\"novel\\\",\\\"novelId\\\":\\\"\" + String(bid) + \"\\\",\\\"chapterId\\\":\\\"\" + String(cid) + \"\\\",\\\"qimoType\\\":\\\"midpage\\\",\\\"midpageId\\\":\\\"\" + String(midpageId || \"\") + \"\\\"}\";\n}\n\n\nfunction fetchMidpageContent(bid, cid, midpageId) {\n var html = \"\";\n var runtimeJava = null;\n try { runtimeJava = this && this.java ? this.java : java; } catch(e0) {}\n try {\n var res = qdApi.call(this, \"midpage_pageinfo\", {book_id: bid, chapter_id: cid, needAdv: 0});\n var data = res && (res.data || res.Data || {}) || {};\n var midList = qdList(data.MidPageList || data.MidpageList || data.midPageList || data.Pages || data);\n if (midpageId) {\n var filtered = [];\n for (var fi = 0; fi < midList.length; fi++) {\n var item = midList[fi] || {};\n var cfg = item.MidPageConfig || item.MidpageConfig || {};\n var pid = cfg.PageId || cfg.MidPageId || cfg.MidpageId || item.PageId || item.MidPageId || item.MidpageId || \"\";\n if (String(pid) === String(midpageId)) filtered.push(item);\n }\n if (filtered.length) midList = filtered;\n }\n\n function pickText(o) {\n if (!o) return \"\";\n var value = o.Text || o.text || o.Title || o.title || o.Desc || o.desc || o.Content || o.content || \"\";\n return String(value || \"\")\n .replace(\/^\\s*彩蛋章(?:[\\s·::—-]+)?\/, \"\")\n .replace(\/^\\s+|\\s+$\/g, \"\");\n }\n\n function pickVideoUrl(o) {\n if (!o) return \"\";\n var direct = o.VideoUrl || o.videoUrl || o.VideoURL || o.videoURL || o.PlayUrl || o.playUrl || \"\";\n if (direct) return String(direct);\n\n var actionUrl = String(o.ActionUrl || o.actionUrl || \"\");\n if (!actionUrl) return \"\";\n\n var jsonMatch = actionUrl.match(\/[\"']playUrl[\"']\\s*:\\s*[\"']([^\"']+)[\"']\/i);\n if (jsonMatch && jsonMatch[1]) return jsonMatch[1].replace(\/\\\\\\\/\/g, \"\/\");\n\n var queryMatch = actionUrl.match(\/[?&]playUrl=([^&#]+)\/i);\n if (queryMatch && queryMatch[1]) {\n try { return decodeURIComponent(queryMatch[1]); } catch(e1) { return queryMatch[1]; }\n }\n\n var queryPos = actionUrl.indexOf(\"query=\");\n if (queryPos >= 0) {\n try {\n var query = actionUrl.substring(queryPos + 6);\n var parsed = JSON.parse(decodeURIComponent(query));\n return String(parsed.playUrl || parsed.PlayUrl || parsed.videoUrl || parsed.VideoUrl || \"\");\n } catch(e2) {}\n }\n return \"\";\n }\n\n function mediaPlayAction(url, title, style) {\n var mediaUrl = String(url || \"\");\n var mediaTitle = String(title || \"彩蛋章\");\n var safeUrl = mediaUrl.replace(\/\\\\\/g, \"\\\\\\\\\").replace(\/'\/g, \"\\\\'\").replace(\/[\\r\\n]+\/g, \"\");\n var safeTitle = mediaTitle.replace(\/\\\\\/g, \"\\\\\\\\\").replace(\/'\/g, \"\\\\'\").replace(\/[\\r\\n]+\/g, \"\");\n return {\n style: style || \"TEXT\",\n type: \"qd\",\n click: \"playMidpageMedia('\" + safeUrl + \"','\" + safeTitle + \"')\"\n };\n }\n\n function mediaImage(url, cover, title) {\n var mediaUrl = String(url || \"\");\n var coverUrl = String(cover || \"\");\n if (!mediaUrl) return \"\";\n if (!coverUrl) return \"<usehtml><p>\" + qdEsc(mediaUrl) + \"<\/p><\/usehtml>\\n\";\n return '<img src=\"' + coverUrl.replace(\/\"\/g, \""\") + ',' + JSON.stringify(mediaPlayAction(mediaUrl, title, \"FULL\")) + '\">\\n';\n }\n\n function mediaButton(url, label, title) {\n if (!runtimeJava || !runtimeJava.base64Encode) return \"\";\n var mediaUrl = String(url || \"\");\n if (!mediaUrl) return \"\";\n var buttonText = String(label || \"点击播放\");\n var svg = '<svg viewBox=\"0 0 1080 240\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"1080\" height=\"240\">' +\n '<rect x=\"4\" y=\"4\" width=\"1072\" height=\"232\" rx=\"28\" fill=\"#e34d3f\"\/>' +\n '<path d=\"M82 56 L82 184 L184 120 Z\" fill=\"#ffffff\"\/>' +\n '<text x=\"238\" y=\"145\" font-family=\"Arial, sans-serif\" font-size=\"68\" font-weight=\"600\" fill=\"#ffffff\">' + qdEsc(buttonText) + '<\/text>' +\n '<\/svg>';\n var clickObj = mediaPlayAction(mediaUrl, title, \"FULL\");\n return '<img src=\"data:image\/svg+xml;base64,' + runtimeJava.base64Encode(svg) + ',' + JSON.stringify(clickObj) + '\">\\n';\n }\n\n for (var i = 0; i < midList.length; i++) {\n var mid = midList[i] || {};\n var widgets = qdList(mid.Widgets || mid.WidgetList || mid.widgets || mid.Items || mid.items);\n for (var j = 0; j < widgets.length; j++) {\n var w = widgets[j] || {};\n var d = w.Data || w.data || w;\n if (!d) continue;\n\n var widgetCfg = w.WidgetConfig || w.widgetConfig || {};\n var widgetType = Number(widgetCfg.Type || widgetCfg.type || 0);\n var widgetName = String(widgetCfg.Name || widgetCfg.name || \"\").toLowerCase();\n var isVideo = widgetType === 4 || widgetType === 20 || widgetName.indexOf(\"video_widget\") >= 0 || widgetName.indexOf(\"video_card_widget\") >= 0;\n var isAudio = widgetType === 21 || widgetName.indexOf(\"audio_card_widget\") >= 0;\n var isImage = widgetType === 22 || widgetName.indexOf(\"image_card_widget\") >= 0;\n\n var text = pickText(d);\n if (text) html += \"<usehtml><p>\" + qdEsc(text) + \"<\/p><\/usehtml>\\n\";\n\n if (isVideo) {\n var video = pickVideoUrl(d);\n var videoCover = d.VideoCoverUrl || d.videoCoverUrl || d.CoverUrl || d.coverUrl || d.ImgUrl || d.imgUrl || \"\";\n if (video) html += mediaImage(video, videoCover, \"彩蛋视频\") + mediaButton(video, \"点击播放视频\", \"彩蛋视频\");\n else if (videoCover) html += \"<img src='\" + String(videoCover).replace(\/'\/g, \"\\\\'\") + \",{\\\"style\\\":\\\"FULL\\\"}'>\\n\";\n continue;\n }\n\n if (isAudio) {\n var audio = d.AudioUrl || d.audioUrl || d.AudioURL || d.audioURL || \"\";\n var audioCover = d.AudioCoverUrl || d.audioCoverUrl || d.CoverUrl || d.coverUrl || d.ImgUrl || d.imgUrl || \"\";\n if (audio) html += mediaImage(audio, audioCover, \"彩蛋音频\") + mediaButton(audio, \"点击播放音频\", \"彩蛋音频\");\n else if (audioCover) html += \"<img src='\" + String(audioCover).replace(\/'\/g, \"\\\\'\") + \",{\\\"style\\\":\\\"FULL\\\"}'>\\n\";\n continue;\n }\n\n var img = d.ImgUrl || d.imgUrl || d.ImageUrl || d.imageUrl || \"\";\n if ((isImage || img) && img) {\n html += \"<img src='\" + String(img).replace(\/'\/g, \"\\\\'\") + \",{\\\"style\\\":\\\"FULL\\\"}'>\\n\";\n }\n }\n }\n } catch(e) {\n html = \"获取彩蛋内容失败:\" + qdEsc(e.message || e);\n }\n return html || \"该彩蛋内容为空。\";\n}\n\nfunction playMidpageMedia(url, title) {\n var j = null;\n try { j = this && this.java ? this.java : null; } catch(e) {}\n if (!j) { try { j = java; } catch(e2) {} }\n if (!j) return;\n\n var mediaUrl = String(url || \"\");\n var mediaTitle = String(title || \"彩蛋章\");\n var requestUrl = mediaUrl + ',{\"headers\":{\"User-Agent\":\"Mozilla\/5.0\",\"Referer\":\"https:\/\/www.qidian.com\/\"}}';\n try {\n j.openVideoPlayer(requestUrl, mediaTitle);\n } catch(e3) {\n try {\n j.openUrl(mediaUrl);\n } catch(e4) {}\n }\n}\n\nfunction videoh(url) {\n var requestUrl = String(url || \"\") + ',{\"headers\":{\"User-Agent\":\"Mozilla\/5.0\",\"Referer\":\"https:\/\/www.qidian.com\/\"}}';\n return playMidpageMedia.call(this, requestUrl, \"彩蛋章\");\n}\n\nvar _qdSummaryCacheKey = \"\";\nvar _qdSummaryCacheValue = null;\nfunction qdCommentCache() {\n try {\n if (this && this.cache) return this.cache;\n } catch(e0) {}\n try {\n if (typeof cache !== \"undefined\") return cache;\n } catch(e1) {}\n return null;\n}\nfunction qdGetParagraphSummary(bid, cid) {\n var key = String(bid || \"\") + \":\" + String(cid || \"\");\n var memoryKey = \"qdreader-paragraph-summary:\" + key;\n var memory = qdCommentCache.call(this);\n\n if (memory && memory.getFromMemory) {\n try {\n var cachedRaw = memory.getFromMemory(memoryKey);\n if (cachedRaw !== null && cachedRaw !== undefined && String(cachedRaw) !== \"\") {\n var cached = typeof cachedRaw === \"string\" ? JSON.parse(cachedRaw) : JSON.parse(String(cachedRaw));\n _qdSummaryCacheKey = key;\n _qdSummaryCacheValue = cached;\n return cached;\n }\n } catch(e2) {}\n }\n if (key && key === _qdSummaryCacheKey && _qdSummaryCacheValue !== null) {\n return _qdSummaryCacheValue;\n }\n\n var value = qdApi.call(this, \"paragraph_summary\", {\n book_id: bid,\n chapter_id: cid\n });\n if (value && Number(value.code || 0) === 0) {\n _qdSummaryCacheKey = key;\n _qdSummaryCacheValue = value;\n if (memory && memory.putMemory) {\n try {\n var latestKeyName = \"qdreader-paragraph-summary:latest\";\n var oldKey = memory.getFromMemory ? memory.getFromMemory(latestKeyName) : null;\n if (oldKey && String(oldKey) !== memoryKey && memory.deleteMemory) {\n memory.deleteMemory(String(oldKey));\n }\n memory.putMemory(memoryKey, JSON.stringify(value));\n memory.putMemory(latestKeyName, memoryKey);\n } catch(e3) {}\n }\n }\n return value || { code: -1, data: {} };\n}\n\nfunction qdList(v) {\n if (!v) return [];\n if (Array.isArray(v)) return v;\n if (v.size && v.get) {\n var arr = [];\n for (var i = 0; i < v.size(); i++) arr.push(v.get(i));\n return arr;\n }\n return [];\n}\nfunction qdEsc(s) {\n return String(s == null ? \"\" : s)\n .replace(\/&\/g, \"&\").replace(\/<\/g, \"<\").replace(\/>\/g, \">\")\n .replace(\/\"\/g, \""\").replace(\/'\/g, \"'\");\n}\nfunction qdColor(count, hot) {\n var c1 = qdLoginVal.call(this, \"◎ 段评颜色\", \"#666666\");\n var c2 = qdLoginVal.call(this, \"◎ 段评分色\", \"#e23b3b\");\n var c3 = qdLoginVal.call(this, \"◎ 热评颜色\", \"#f06060\");\n var n1 = parseInt(qdLoginVal.call(this, \"◎ 分色数值\", \"45\"));\n var cfg = getCfg.call(this);\n if (!cfg.fs) return c1;\n if (hot) return c3;\n return Number(count) >= n1 ? c2 : c1;\n}\nfunction qdMakeBubble(number, bid, cid, para, hot, type) {\n const { java } = this;\n var n = Number(number) || 0;\n if (n <= 0) return \"\";\n var displayText = n > 999 ? \"999\" : String(n);\n var color = qdColor.call(this, n, hot);\n var font = \"Arial\";\n var size = qdLoginVal.call(this, \"◎ 大小\", \"text\");\n var style = qdLoginVal.call(this, \"◎ 模板\", \"起点\");\n var tpl = qdLoginVal.call(this, style.replace(\"样式\", \"◎ 气泡\"), \"\");\n var svg = (style !== \"起点\" && tpl.trim())\n ? tpl.replace(\/\\$\\{qpyscolor\\}\/g, color).replace(\/\\$\\{qpwzcolor\\}\/g, color).replace(\/\\$\\{font\\}\/g, font).replace(\/\\$\\{displayText\\}\/g, displayText)\n : '<svg viewBox=\"5 14 45 36\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" 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=\"' + color + '\" stroke-width=\"0.7\"\/>' +\n '<text x=\"32\" y=\"38\" text-anchor=\"middle\" font-family=\"' + font + '\" font-weight=\"600\" font-size=\"18\" fill=\"' + color + '\" dominant-baseline=\"middle\">' + displayText + '<\/text>' +\n '<\/svg>';\n var clickObj = { style: size, type: \"qd\", click: \"showCmt('\" + bid + \"','\" + cid + \"','\" + para + \"',\" + Date.now() + \",'\" + (type || \"dp\") + \"')\" };\n return '<img src=\"data:image\/svg+xml;base64,' + java.base64Encode(svg) + ',' + JSON.stringify(clickObj) + '\">';\n}\n\nconst EMOJI_MAP = { 1:\"👏\",2:\"🌹\",3:\"🤝\",4:\"😁\",5:\"😄\",6:\"🥺\",7:\"🙂\",8:\"😏\",9:\"😙\",10:\"👆🏻🐽\",11:\"🙄\",12:\"😭\",13:\"😵\",14:\"😥\",15:\"🖕🏻\",16:\"🥵\",17:\"😓\",18:\"🤫\",19:\"😂\",20:\"😢\",21:\"😍\",22:\"🤕🔨\",23:\"😑\",24:\"😫\",25:\"🤗\",26:\"🤪\",27:\"🙏\",28:\"😣\",29:\"💪\",30:\"💀\",31:\"😳\",32:\"😎\",33:\"🤭\",34:\"😄👏\",35:\"👍🏻\",36:\"🤓\",37:\"😡\",38:\"🙁\",39:\"😄❓\",40:\"😞\",41:\"😧\",42:\"💋\",43:\"☺️\",44:\"🤬\",45:\"😴\",46:\"🤠🚬\",47:\"😱\",48:\"🐷\",49:\"😪\",50:\"🤐\",51:\"🥴\",52:\"🌙\",53:\"❤️\",54:\"🔪\",55:\"🎁\",56:\"💔\",57:\"👊🏻\",58:\"😒\",59:\"✌🏻️\",60:\"😮\",61:\"🤨\",62:\"😴\",63:\"👏🏻\",64:\"🐲\",65:\"⭐\",66:\"🌧️\",67:\"🍉\",68:\"🍵\",69:\"🔥\",70:\"💯\" };\n\nfunction qdCreateGodHtml(c, bid, cid, para) {\n const { java } = this;\n var text = qdEsc(qdCommentText(c)).replace(\/https?:\\\/\\\/[^\\s]+\/g, \"\").replace(\/\\[fn=(\\d+)\\]\/g, (match, num) => EMOJI_MAP[num] || '').trim();\n if (!text) return \"\";\n var display = text.length > 18 ? text.substring(0, 18) + \"⋯\" : text;\n var width = 1000, boxHeight = 100, marginBottom = 20, padding = 32, tagWidth = 140, tagHeight = 56;\n var canvasHeight = boxHeight + marginBottom;\n var centerY = boxHeight \/ 2 + 14;\n var font = \"Segoe UI, Arial, Helvetica, sans-serif\";\n var svg = '<svg width=\"' + width + '\" height=\"' + canvasHeight + '\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\">' +\n '<rect x=\"2\" y=\"0\" width=\"' + (width - 4) + '\" height=\"' + boxHeight + '\" fill=\"rgba(255,255,255,0.25)\" rx=\"35\" stroke=\"#888\" stroke-width=\"1\"\/>' +\n '<rect x=\"' + padding + '\" y=\"' + ((boxHeight - tagHeight) \/ 2) + '\" width=\"' + tagWidth + '\" height=\"' + tagHeight + '\" rx=\"20\" fill=\"#FF4444\"\/>' +\n '<text x=\"' + (padding + tagWidth \/ 2) + '\" y=\"' + centerY + '\" font-size=\"32\" font-family=\"' + font + '\" fill=\"#FFF\" text-anchor=\"middle\" font-weight=\"bold\">热评<\/text>' +\n '<text x=\"' + (padding + tagWidth + 24) + '\" y=\"' + centerY + '\" font-size=\"38\" font-family=\"' + font + '\" fill=\"#000\" font-weight=\"bold\">' + display + '<\/text>' +\n '<\/svg>';\n var clickObj = { style: \"FULL\", click: \"showCmt('\" + bid + \"','\" + cid + \"','\" + para + \"',\" + Date.now() + \",'dp')\" };\n return '<img src=\"data:image\/svg+xml;base64,' + java.base64Encode(svg) + ',' + JSON.stringify(clickObj) + '\">';\n}\n\nfunction qdCommentUser(c) {\n var u = c.user_info || c.UserInfo || c.User || {};\n return {\n name: u.user_name || u.UserName || u.NickName || \"匿名用户\",\n avatar: u.user_avatar || u.UserHeadIcon || u.Avatar || \"\"\n };\n}\n\nfunction qdCommentText(c) {\n var t = c.text || c.ReviewContent || c.Content || c.content || c.Body || c.Subject || \"\";\n if (typeof t == \"object\") t = t.Text || t.text || JSON.stringify(t);\n return String(t || \"\");\n}\n\nfunction qdCommentTime(c) {\n var ts = Number(c.create_timestamp || c.CreateTime || c.CreateTimestamp || 0);\n if (!ts) return \"\";\n if (String(ts).length <= 10) ts *= 1000;\n try { return java.timeFormatUTC(ts, \"yyyy-MM-dd HH:mm\", 28800000); } catch(e) { return \"\"; }\n}\n\nfunction qdCreateChapterCmtHtml(list, total, bid, cid) {\n const { java } = this;\n list = qdList(list).filter(function(item) {\n return qdCommentText(item).trim() !== \"\";\n }).slice(0, 2);\n if (!list.length && !total) return \"\";\n var svg = \"\", y = 0;\n var w = 1080, lp = 60, rp = 60, ufs = 42, cfs = 42, lh = 60, th = 120, sp = 35, gr = 60;\n var font = \"Segoe UI, Arial, Helvetica, sans-serif\";\n svg += '<text x=\"' + lp + '\" y=\"' + (y + 75) + '\" font-size=\"44\" font-family=\"' + font + '\" fill=\"#000\">本章说<\/text>' +\n '<text x=\"' + (w - rp) + '\" y=\"' + (y + 75) + '\" font-size=\"36\" text-anchor=\"end\" font-family=\"' + font + '\" fill=\"#000\">' + (total || list.length || 0) + '条评论 ❯<\/text>';\n y += th;\n for (var j = 0; j < list.length; j++) {\n var c = list[j] || {};\n var u = qdCommentUser(c);\n var like = c.digg_count || c.LikeCount || c.DiggCount || c.AgreeAmount || 0;\n var txt = qdEsc(qdCommentText(c).replace(\/https?:\\\/\\\/[^\\s]+\/g, \"\").replace(\/qdd\\.gg\\\/[^\\s]+\/g, \"\").replace(\/\\[fn=(\\d+)\\]\/g, (match, num) => EMOJI_MAP[num] || '')).trim();\n if (!txt) continue;\n var max = Math.floor((w - lp - rp) \/ cfs);\n var txtLines = [], s = txt;\n while (s) { txtLines.push(s.slice(0, max)); s = s.slice(max); }\n if (txtLines.length > 4) {\n txtLines = txtLines.slice(0, 4);\n txtLines[3] = txtLines[3].slice(0, max - 1) + \"…\";\n }\n var baseY = y + 40;\n svg += '<text x=\"' + lp + '\" y=\"' + baseY + '\" font-weight=\"bold\" font-size=\"' + ufs + '\" font-family=\"' + font + '\" fill=\"#000\">' + qdEsc(u.name) + '<\/text>';\n var capsuleWidth = 140;\n var capsuleHeight = 56;\n var capsuleRx = 28;\n var capsuleX = w - rp - capsuleWidth;\n var capsuleY = baseY - capsuleHeight\/2 + 8;\n svg += '<rect x=\"' + capsuleX + '\" y=\"' + capsuleY + '\" width=\"' + capsuleWidth + '\" height=\"' + capsuleHeight + '\" rx=\"' + capsuleRx + '\" fill=\"rgba(0,0,0,0.06)\" stroke=\"#b0b0b0\" stroke-width=\"1\"\/>';\n var centerY = capsuleY + capsuleHeight \/ 2;\n var iconSize = 28;\n var iconX = capsuleX + 16;\n var iconY = centerY - iconSize \/ 2;\n svg += '<svg x=\"' + iconX + '\" y=\"' + iconY + '\" width=\"' + iconSize + '\" height=\"' + iconSize + '\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#333\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">' +\n '<path d=\"M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3\"\/>' +\n '<\/svg>';\n var textX = iconX + iconSize + 6;\n var textY = centerY + 8;\n svg += '<text x=\"' + textX + '\" y=\"' + textY + '\" font-size=\"28\" font-family=\"' + font + '\" fill=\"#333\">' + like + '<\/text>';\n var ly = baseY + ufs + sp;\n for (var k = 0; k < txtLines.length; k++) {\n svg += '<text x=\"' + lp + '\" y=\"' + ly + '\" font-size=\"' + cfs + '\" font-family=\"' + font + '\" fill=\"#000\">' + txtLines[k] + '<\/text>';\n ly += lh;\n }\n y += ufs + lh * txtLines.length + sp * 2.5;\n }\n var finalSvg = '<svg width=\"' + w + '\" height=\"' + y + '\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\">' +\n '<rect x=\"2\" y=\"2\" width=\"' + (w - 4) + '\" height=\"' + (y - 4) + '\" fill=\"rgba(255,255,255,0.25)\" rx=\"' + (gr - 1) + '\" stroke=\"#888\" stroke-width=\"1\"\/>' +\n svg +\n '<\/svg>';\n var clickObj = { style: \"FULL\", click: \"showCmt('\" + bid + \"','\" + cid + \"','-1',\" + Date.now() + \",'zp')\" };\n return '<img src=\"data:image\/svg+xml;base64,' + java.base64Encode(finalSvg) + ',' + JSON.stringify(clickObj) + '\">';\n}\n\nfunction qdAuthorSayText(authorSay) {\n if (authorSay == null) return \"\";\n var obj = authorSay;\n if (typeof obj === \"string\") {\n var rawText = obj.trim();\n if (!rawText) return \"\";\n try {\n var parsed = JSON.parse(rawText);\n if (parsed && typeof parsed === \"object\") obj = parsed;\n else return rawText;\n } catch(e) {\n return rawText;\n }\n }\n if (!obj || typeof obj !== \"object\") return String(obj || \"\");\n\n var value =\n obj.AuthorComments ||\n obj.AuthorComment ||\n obj.AuthorSay ||\n obj.author_say ||\n obj.authorSay ||\n (obj.AskMonthTicket && (obj.AskMonthTicket.AuthorExpectText || obj.AskMonthTicket.author_expect_text)) ||\n (obj.ask_month_ticket && (obj.ask_month_ticket.AuthorExpectText || obj.ask_month_ticket.author_expect_text)) ||\n obj.ChapterAuthorSay ||\n obj.chapterAuthorSay ||\n obj.Content ||\n obj.Text ||\n obj.content ||\n obj.text ||\n obj.Comment ||\n obj.comment ||\n obj.Say ||\n obj.say ||\n obj.AuthorWords ||\n obj.authorWords ||\n obj.WriterSay ||\n obj.writerSay ||\n \"\";\n\n if (value === obj) return \"\";\n if (value && typeof value === \"object\") return qdAuthorSayText(value);\n return String(value || \"\");\n}\n\nfunction qdCreateAuthorSayHtml(authorSay, bid, cid) {\n const { java } = this;\n if (!authorSay) return \"\";\n\n var obj = authorSay;\n var content = \"\";\n\n try {\n if (typeof obj === \"string\") {\n try {\n var parsed = JSON.parse(obj);\n if (parsed && typeof parsed === \"object\") obj = parsed;\n else content = obj;\n } catch(e0) {\n content = obj;\n }\n }\n\nif (!content && obj && typeof obj === \"object\") {\n content =\n obj.AuthorComments ||\n obj.AuthorComment ||\n obj.AuthorSay ||\n obj.author_say ||\n obj.authorSay ||\n (obj.AskMonthTicket && obj.AskMonthTicket.AuthorExpectText) ||\n obj.ChapterAuthorSay ||\n obj.chapterAuthorSay ||\n obj.Content ||\n obj.Text ||\n obj.content ||\n obj.text ||\n obj.Comment ||\n obj.comment ||\n obj.Say ||\n obj.say ||\n obj.AuthorWords ||\n obj.authorWords ||\n obj.WriterSay ||\n obj.writerSay ||\n \"\";\n}\n\n if (typeof content === \"object\") {\n content =\n content.Text ||\n content.Content ||\n content.text ||\n content.content ||\n content.Value ||\n content.value ||\n JSON.stringify(content);\n }\n } catch(e) {}\n\n content = String(content || \"\")\n .replace(\/https?:\\\/\\\/[^\\s]+\/g, \"\")\n .replace(\/\\[fn=(\\d+)\\]\/g, function(match, num) {\n return EMOJI_MAP[num] || \"\";\n })\n .trim();\n\n content = qdEsc(content);\n if (!content) return \"\";\n\n var ask = {};\n var authorName = \"\";\n\n try {\n if (obj && typeof obj === \"object\") {\n ask = obj.AskMonthTicket || obj.ask_month_ticket || {};\n authorName = qdEsc(\n obj.AuthorName ||\n obj.author_name ||\n obj.NickName ||\n obj.nickname ||\n obj.Author ||\n obj.author ||\n \"\"\n );\n }\n } catch(e2) {}\n\n var svg = \"\", y = 0;\n var w = 1080, lp = 60, rp = 60;\n var cfs = 38, lh = 56, sp = 40, gr = 60;\n var font = \"Segoe UI, Arial, Helvetica, sans-serif\";\n\n var tagX = w - 110, tagY2 = 0, tagWidth = 110;\n var tagPath =\n \"M \" + (tagX + 26) + \" \" + tagY2 +\n \" C \" + (tagX - 36) + \" \" + tagY2 +\n \" \" + (tagX - 60) + \" \" + (tagY2 + 1) +\n \" \" + (tagX - 114) + \" \" + (tagY2 - 32) +\n \" C \" + (tagX - 84) + \" \" + (tagY2 - 12) +\n \" \" + (tagX - 68) + \" \" + (tagY2 + 6) +\n \" \" + (tagX - 66) + \" \" + (tagY2 + 22) +\n \" C \" + (tagX - 64) + \" \" + (tagY2 + 44) +\n \" \" + (tagX - 54) + \" \" + (tagY2 + 56) +\n \" \" + (tagX - 34) + \" \" + (tagY2 + 56) +\n \" L \" + (tagX + tagWidth) + \" \" + (tagY2 + 56) +\n \" L \" + (tagX + tagWidth) + \" \" + (tagY2 + 26) +\n \" Q \" + (tagX + tagWidth) + \" \" + tagY2 +\n \" \" + (tagX + tagWidth - 26) + \" \" + tagY2 + \" Z\";\n\n svg += '<path d=\"' + tagPath + '\" fill=\"rgba(74,116,228,0.90)\"\/>';\n svg += '<text x=\"' + (tagX + 28) + '\" y=\"40\" font-size=\"34\" fill=\"#fff\" font-weight=\"bold\" text-anchor=\"middle\" font-family=\"system-ui, sans-serif\">作者说<\/text>';\n\n if (authorName) {\n svg += '<text x=\"' + lp + '\" y=\"' + (y + 70) + '\" font-family=\"' + font + '\" font-size=\"44\" fill=\"#000\">' + authorName + '<\/text>';\n }\n\n y += 150;\n\n var max = Math.floor((w - lp - rp) \/ cfs);\n var txtLines = [], s = content;\n\n while (s) {\n txtLines.push(s.slice(0, max));\n s = s.slice(max);\n }\n\n for (var i = 0; i < txtLines.length; i++) {\n svg += '<text x=\"' + lp + '\" y=\"' + y + '\" font-family=\"' + font + '\" font-size=\"' + cfs + '\" fill=\"#333\">' + txtLines[i] + '<\/text>';\n y += lh;\n }\n\n y += sp;\n\n var expect = \"\";\n try {\n expect = qdEsc(ask.AuthorExpectText || ask.author_expect_text || \"\");\n } catch(e3) {}\n\n if (authorName && expect) {\n svg += '<text x=\"' + lp + '\" y=\"' + y + '\" font-family=\"' + font + '\" font-size=\"38\"><tspan fill=\"#000\" font-weight=\"bold\">' + authorName + ':<\/tspan><tspan fill=\"#000\">' + expect + '<\/tspan><\/text>';\n y += lh;\n }\n\n y += 10;\n\n var finalSvg =\n '<svg width=\"' + w + '\" height=\"' + y + '\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\">' +\n '<rect x=\"2\" y=\"2\" width=\"' + (w - 4) + '\" height=\"' + (y - 4) + '\" fill=\"rgba(255,255,255,0.25)\" rx=\"' + gr + '\" stroke=\"#888\" stroke-width=\"1\"\/>' +\n svg +\n '<\/svg>';\n\n var clickObj = {\n style: \"FULL\",\n click: \"showCmt('\" + bid + \"','\" + cid + \"','-10',\" + Date.now() + \",'zp')\"\n };\n\n return '<img src=\"data:image\/svg+xml;base64,' + java.base64Encode(finalSvg) + ',' + JSON.stringify(clickObj) + '\">';\n}\n\nfunction getComments(content, bid, cid, type, chapData) {\n let { java, source } = this;\n\n var cfg;\n try { cfg = getCfg.call(this); } catch(eCfg) { cfg = { dp:false, zm:false, tl:false, zz:false, rp:false, pl:false }; }\n if (!cfg.pl) { try { source.put(cid, \"\"); } catch(eAllCommentsOff) {} return content; }\n\n try {\n var summary = [];\n var hotReviews = [];\n if (cfg.dp || cfg.rp || cfg.zm) {\n var summaryRes = qdGetParagraphSummary.call(this, bid, cid);\n var data = summaryRes.data || summaryRes.Data || {};\n summary = qdList(data.summary || data.Summary);\n hotReviews = qdList(data.reviews || data.Reviews);\n }\n\n var lines = content.split(\"\\n\");\n var paraMap = {};\n var chapterCount = 0;\n if (!cfg.zm) { try { source.put(cid, \"\"); } catch(eTitleOff) {} }\n\n \/\/ 正文图片不计入文字段落,避免段评气泡错位。\n var textLineIndexes = [];\n for (var li = 0; li < lines.length; li++) {\n var trimLine = lines[li].trim();\n if (trimLine && !(\/^<\\s*img\\s\/i.test(trimLine)) && trimLine.indexOf(\"data:image\/\") === -1) {\n textLineIndexes.push(li);\n }\n }\n\n for (var i = 0; i < summary.length; i++) {\n var item = summary[i] || {};\n var pid = Number(\n item.ParagraphId || item.paragraphId || item.paragraph_id ||\n item.ParaId || item.paraId || 0\n );\n var count = Number(\n item.CommentCount || item.commentCount || item.TextCount ||\n item.textCount || item.Count || item.count || 0\n );\n var hot = Number(item.HasHotComment || item.hasHotComment || 0) !== 0;\n if (pid > 0 && count > 0) {\n var idx = textLineIndexes[pid - 1];\n if (idx !== undefined && idx >= 0 && idx < lines.length) {\n paraMap[pid] = idx;\n if (cfg.dp) lines[idx] += qdMakeBubble.call(this, count, bid, cid, pid, hot, \"dp\");\n }\n } else if (pid === -1 && count > 0) {\n chapterCount = count;\n if (cfg.zm) {\n try { source.put(cid, createSvg.call(this, count, bid, cid, -1, false, true, \"dp\")); } catch(eTitle) {}\n }\n }\n }\n\n if (cfg.rp) {\n for (var g = 0; g < hotReviews.length; g++) {\n var gc = hotReviews[g] || {};\n var gpid = Number(gc.paragraph_id || gc.ParagraphId || gc.paragraphId || 0);\n var gi = paraMap[gpid];\n if (gi === undefined) gi = textLineIndexes[gpid - 1];\n if (gi !== undefined && gi >= 0 && gi < lines.length) {\n lines[gi] += \"\\n\" + qdCreateGodHtml.call(this, gc, bid, cid, gpid);\n }\n }\n }\n\n var tail = \"\";\n if (cfg.zz) {\n var authorSay = null;\n try {\n var cd = chapData || {};\n authorSay =\n cd.author_say || cd.authorSay || cd.AuthorSay || cd.Author_say ||\n cd.author_comment || cd.authorComment || cd.AuthorComment || cd.AuthorComments ||\n cd.chapter_author_say || cd.chapterAuthorSay || cd.ChapterAuthorSay ||\n cd.authorWords || cd.AuthorWords || cd.writerSay || cd.WriterSay || cd.say || cd.Say || null;\n if (!qdAuthorSayText(authorSay).trim()) authorSay = null;\n } catch(eA) { authorSay = null; }\n\n if (!authorSay) {\n try {\n var act = qdApi.call(this, \"chapter_activity\", {\n book_id: bid,\n chapter_id: cid\n });\n var actData = act.data || act.Data || {};\n authorSay =\n actData.author_say || actData.authorSay || actData.AuthorSay || actData.Author_say ||\n actData.author_comment || actData.authorComment || actData.AuthorComment || actData.AuthorComments ||\n actData.chapter_author_say || actData.chapterAuthorSay || actData.ChapterAuthorSay ||\n actData.authorWords || actData.AuthorWords || actData.writerSay || actData.WriterSay ||\n actData.say || actData.Say || null;\n if (!qdAuthorSayText(authorSay).trim()) authorSay = null;\n } catch(eB) { authorSay = null; }\n }\n if (authorSay) tail += qdCreateAuthorSayHtml.call(this, authorSay, bid, cid);\n }\n\n if (cfg.tl || cfg.zm) {\n var cc = qdApi.call(this, \"chapter_comments\", {\n book_id: bid,\n chapter_id: cid,\n page: 1,\n page_size: 20\n });\n var ccData = cc.data || cc.Data || {};\n var ccTotal = Number(ccData.total || ccData.Total || 0);\n if (!chapterCount && ccTotal > 0 && cfg.zm) {\n try { source.put(cid, createSvg.call(this, ccTotal, bid, cid, -1, false, true, \"dp\")); } catch(eTitleFallback) {}\n }\n if (cfg.tl) tail += qdCreateChapterCmtHtml.call(this, ccData.comments || ccData.Comments || [], ccTotal, bid, cid);\n }\n\n return lines.join(\"\\n\") + (tail ? \"\\n\" + tail : \"\");\n } catch (e) {\n try { java.log(\"qidian_api错误: \" + e); } catch(_) {}\n return content;\n }\n}\n\nfunction createSvg(number, bid, cid, para, hot, isTitle, fs) {\n const { java } = this;\n try {\n var n = Number(number) || 0;\n if (n <= 0) return \"\";\n\n var displayText = n > 999 ? \"999\" : String(n);\n\n var size = \"TEXT\";\n try {\n var s = qdLoginVal.call(this, \"◎ 大小\", \"TEXT\");\n if (s) size = String(s);\n } catch(e) {}\n\n var color = qdColor.call(this, n, hot);\n var font = \"Arial\";\n var style = qdLoginVal.call(this, \"◎ 模板\", \"起点\");\n var tpl = qdLoginVal.call(this, String(style).replace(\"样式\", \"◎ 气泡\"), \"\");\n\n var svg = (style !== \"起点\" && String(tpl).trim())\n ? String(tpl)\n .replace(\/\\$\\{qpyscolor\\}\/g, color)\n .replace(\/\\$\\{qpwzcolor\\}\/g, color)\n .replace(\/\\$\\{font\\}\/g, font)\n .replace(\/\\$\\{displayText\\}\/g, displayText)\n : '<svg viewBox=\"5 14 45 36\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" 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=\"' + color + '\" stroke-width=\"0.7\"\/>' +\n '<text x=\"32\" y=\"38\" text-anchor=\"middle\" font-family=\"' + font + '\" font-weight=\"600\" font-size=\"18\" fill=\"' + color + '\" dominant-baseline=\"middle\">' + displayText + '<\/text>' +\n '<\/svg>';\n\n \/\/ fs 用来区分打开方式:\n \/\/ dp = 段评,包括章名段评 paragraph_id = -1\n \/\/ zp = 本章说 \/ 章评\n var cmtType = fs || \"dp\";\n\n var click =\n \"showCmt('\" + String(bid).replace(\/'\/g, \"\\\\'\") +\n \"','\" + String(cid).replace(\/'\/g, \"\\\\'\") +\n \"','\" + String(para).replace(\/'\/g, \"\\\\'\") +\n \"',\" + Date.now() +\n \",'\" + String(cmtType).replace(\/'\/g, \"\\\\'\") + \"')\";\n\n return \"data:image\/svg+xml;base64,\" + java.base64Encode(svg) + \",\" + JSON.stringify({\n style: size,\n type: \"qd\",\n click: click\n });\n } catch(e) {\n try { java.log(\"createSvg错误:\" + e); } catch(_) {}\n return \"\";\n }\n}\nvar QD_PARAGRAPH_CONFIG = { browser: { isHideable: true, expandedCornersRadius: 12, skipCollapsed: true, hardwareAccelerated: true, heightPercentage: 0.8 }, clickType: 'qd' };\nvar _qdJ = null; var _qdC = null; var _qdS = null;\n\n\n\n\nfunction getBuiltInCommentHtml() {\n return `<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\">\n <title>段评 · 起点<\/title>\n <style>\n \/* 本地轻量样式兼容层 *\/\n *{box-sizing:border-box}\n .hidden{display:none!important}.block{display:block}.flex{display:flex}.inline-flex{display:inline-flex}\n .flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}\n .relative{position:relative}.absolute{position:absolute}.fixed{position:fixed}.inset-0{inset:0}.z-10{z-index:10}.z-50{z-index:50}.overflow-hidden{overflow:hidden}\n .min-h-screen{min-height:100vh}.w-4{width:1rem}.h-4{height:1rem}.w-8{width:2rem}.h-8{height:2rem}.w-12{width:3rem}.h-12{height:3rem}.w-full{width:100%}.max-w-4xl{max-width:56rem}.max-w-md{max-width:28rem}.min-w-0{min-width:0}\n .mx-auto{margin-left:auto;margin-right:auto}.mt-0\\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-6{margin-top:1.5rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mr-1{margin-right:.25rem}.mr-2{margin-right:.5rem}.ml-2{margin-left:.5rem}\n .p-3{padding:.75rem}.px-3{padding-left:.75rem;padding-right:.75rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pt-4{padding-top:1rem}.pb-6{padding-bottom:1.5rem}\n .space-y-2>*+*{margin-top:.5rem}.space-y-3>*+*{margin-top:.75rem}.gap-1{gap:.25rem}.gap-2{gap:.5rem}\n .rounded-full{border-radius:9999px}.rounded-md{border-radius:.375rem}.rounded-lg{border-radius:.5rem}\n .border-b{border-bottom-width:1px;border-bottom-style:solid}.border-t{border-top-width:1px;border-top-style:solid}.border-gray-100{border-color:#f3f4f6}\n .bg-gray-50{background-color:var(--bg-primary,#f9fafb)}.bg-red-100{background-color:var(--error-bg,#fef2f2)}.bg-black\\\/90{background-color:rgba(0,0,0,.9)}\n .text-center{text-align:center}.text-xs{font-size:.75rem}.text-sm{font-size:.875rem}.text-base{font-size:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}\n .text-gray-800{color:var(--text-primary,#1f2937)}.text-gray-700{color:var(--text-secondary,#374151)}.text-gray-500{color:var(--text-tertiary,#6b7280)}.text-gray-400{color:#9ca3af}.text-red-700{color:var(--error-text,#b91c1c)}\n .leading-relaxed{line-height:1.625}.leading-tight{line-height:1.25}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.object-cover{object-fit:cover}.object-contain{object-fit:contain}.cursor-pointer{cursor:pointer}.transition-colors{transition:color .2s,background-color .2s}.pointer-events-none{pointer-events:none}\n .max-w-\\[60vw\\]{max-width:60vw}.w-auto{width:auto}\n .bg-white{background:#fff}.border{border-width:1px;border-style:solid}.shadow-sm{box-shadow:0 1px 3px rgba(15,23,42,.08)}.shadow-md{box-shadow:0 6px 14px rgba(15,23,42,.10)}\n .p-4{padding:1rem}.px-4{padding-left:1rem;padding-right:1rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.pt-1{padding-top:.25rem}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mt-4{margin-top:1rem}.mx-3{margin-left:.75rem;margin-right:.75rem}\n .max-w-sm{max-width:24rem}.max-w-\\[60vw\\]{max-width:60vw}.text-blue-500{color:#3b82f6}.text-gray-600{color:#4b5563}.text-gray-900{color:#111827}\n .text-\\[clamp\\(1\\.25rem\\,3vw\\,1\\.5rem\\)\\]{font-size:clamp(1.25rem,3vw,1.5rem)}.text-lg{font-size:1.125rem}.font-semibold{font-weight:600}\n .transition-shadow{transition:box-shadow .2s ease}.hover\\:shadow-md:hover{box-shadow:0 6px 14px rgba(15,23,42,.10)}\n \/* ========================================== *\/\n \/* ?? ????????? (????????) *\/\n \/* ========================================== *\/\n #commentList > div > .flex-1 > div:first-child .font-medium { font-size: 16px !important; }\n #commentList > div > .flex-1 > .mt-1 > p { font-size: 16px !important; line-height: 1.6 !important; }\n .reply-item .font-medium { font-size: 14px !important; }\n .reply-item > .flex-1 > .mt-1:not(.flex) { font-size: 14px !important; line-height: 1.5 !important; }\n :root {\n --badge-slot-h: 1.5em !important;\n --bg-primary: #f9fafb;\n --text-primary: #1f2937; --text-secondary: #1f2937; --text-tertiary: #9ca3af;\n --border-color: #f3f4f6; --error-bg: #fef2f2; --error-text: #dc2626; --like-svg-color: rgb(0,0,0); --badge-gap: 0.25rem;\n }\n img.badge-img { height: var(--badge-slot-h) !important; max-height: 24px !important; }\n \/* ========================================== *\/\n \/* ? ????????????????????????????? ? *\/\n body:not(.is-beautified) { padding-top: 56px; }\n body:not(.is-beautified) #commentCount { display: none !important; }\n \/* ?????????????? *\/\n body:not(.is-beautified) #title {\n display: block !important;\n background: #ffffff;\n padding: 16px 16px 16px 22px;\n border-radius: 10px;\n box-shadow: 0 1px 4px rgba(0,0,0,0.04);\n position: relative;\n margin-top: 8px;\n margin-bottom: 12px;\n font-size: 16px !important;\n font-weight: 700 !important;\n color: #1f2937;\n text-indent: 0 !important;\n line-height: 1.6 !important;\n border: 1px solid #f3f4f6;\n }\n body:not(.is-beautified) #title::before {\n content: ''; position: absolute; left: 0; top: 16px; bottom: 16px; width: 4px; background: #9ca3af; border-radius: 0 3px 3px 0;\n }\n body:not(.is-beautified) #stickyWrapper {\n position: fixed; top: 0; left: 0; right: 0;\n background-color: var(--bg-primary);\n z-index: 999; padding: 12px 16px 0 16px;\n box-shadow: 0 1px 4px rgba(0,0,0,0.05); box-sizing: border-box;\n }\n \/* ???????????????? *\/\n @media (prefers-color-scheme: dark) {\n body:not(.is-beautified) #stickyWrapper { background-color: #121212; border-bottom: 1px solid #2c2c2e; box-shadow: none; }\n body:not(.is-beautified) #title { background: #1c1c1e; color: #e5e7eb; border-color: #2c2c2e; }\n body:not(.is-beautified) #title::before { background: #4b5563; }\n }\n body:not(.is-beautified) #tabContainer, body:not(.is-beautified) #roleTabContainer {\n width: 100%; max-width: 56rem; padding-left: 0.75rem; padding-right: 0.75rem; box-sizing: border-box; margin: 0 auto;\n }\n .tab-bar { display: flex; gap: 1.5rem; font-size: 15px; color: #6b7280; padding-bottom: 6px; border-bottom: none; align-items: baseline; }\n .tab-item { cursor: pointer; position: relative; display: flex; align-items: baseline; gap: 4px; transition: all 0.2s; }\n .tab-item.active { color: #111827; font-weight: 800; font-size: 18px; }\n .tab-item.active::after { content: ''; position: absolute; bottom: -6px; left: 50%; transform: translateX(-50%); width: 16px; height: 3px; background-color: #ef4444; border-radius: 2px; }\n .tab-num { font-size: 12px; color: #9ca3af; font-weight: 500; transition: color 0.2s; }\n .tab-item.active .tab-num { color: #111827; font-weight: bold; }\n \/* ???????? *\/\n .role-tab-bar { display: flex; gap: 10px; overflow-x: auto; margin-top: 4px; padding-bottom: 8px; white-space: nowrap; border-bottom: 1px solid transparent; -webkit-overflow-scrolling: touch; scrollbar-width: none; }\n .role-tab-bar::-webkit-scrollbar { display: none; }\n .role-tab-item { padding: 4px 14px; font-size: 13px; background: #f3f4f6; border-radius: 999px; cursor: pointer; color: #4b5563; border: 1px solid transparent; transition: all 0.2s; }\n .role-tab-item.active { background: #fef2f2 !important; color: #ef4444 !important; border-color: #fca5a5 !important; font-weight: bold !important; }\n \/* ========================================== *\/\n \/* ?????????????? *\/\n audio::-webkit-media-controls-panel { background-color: #f5f5f5; border-radius: 0.5rem; }\n audio::-webkit-media-controls-play-button { background-color: #ef4444; border-radius: 50%; }\n audio::-webkit-media-controls-play-button:hover { background-color: #dc2626; }\n body { background-color: var(--bg-primary); transition: background-color .3s ease; }\n .bg-gray-50 { background-color: var(--bg-primary); } .text-gray-800 { color: var(--text-primary); } .text-gray-700 { color: var(--text-secondary); } .text-gray-500 { color: var(--text-tertiary); } .text-gray-400 { color: var(--text-tertiary); } .border-gray-100 { border-color: var(--border-color); } .bg-red-100 { background-color: var(--error-bg); } .text-red-700 { color: var(--error-text); } .text-blue-500 { color: #3b82f6; } svg path { fill: var(--like-svg-color); }\n .badge-wrap { display: inline-flex; align-items: center; gap: var(--badge-gap); margin-left: 0.25rem; height: var(--badge-slot-h); flex: 0 0 auto; line-height: 1; }\n .badge-slot { display: inline-flex; align-items: center; height: var(--badge-slot-h); flex: 0 0 auto; }\n .badge-img { height: 100%; width: auto; object-fit: contain; display: none; }\n #fullscreenImg { transform-origin: center center; cursor: grab; }\n .god-stamp-header { padding-right: 45px; }\n .comment-card .god-stamp-header { padding-right: 24px !important; }\n\/* ?????? *\/\n.reply-toggle, .reply-more:not(.hidden), #loadMore:not(.hidden) { display: inline-flex; align-items: center; justify-content: center; min-height: 28px; margin-top: 9px; padding: 0 14px; color: #475569 !important; background: #f1f5f9 !important; border: none; border-radius: 14px; font-size: 13px !important; cursor: pointer; font-weight: 500; transition: background 0.2s ease; text-decoration: none !important; }\n.reply-toggle:active, .reply-more:not(.hidden):active, #loadMore:not(.hidden):active { background: #e2e8f0 !important; }\n#loadMore { width: fit-content; margin-left: auto; margin-right: auto; min-width: 128px; }\n\/* ????????? *\/\n.replies-container { margin: 2px 0 0 0; width: 100%; padding: 0; background: transparent; border-radius: 8px; box-sizing: border-box; opacity: 0; transform: translateY(-3px); transition: opacity .18s ease, transform .18s ease, margin .18s ease; will-change: opacity, transform; contain: content; }\n.replies-container.expanded { margin: 12px 0 0 -56px; width: calc(100% + 56px); padding: 12px 10px 12px 56px; background: var(--reply-panel-bg, #f8fafc); opacity: 1; transform: translateY(0); }\nhtml.qd-dark-preload, html.qd-dark-preload body { background:#121212 !important; color:#e5e7eb !important; }\nhtml.qd-dark-preload { --bg-primary:#121212; --text-primary:#e5e7eb; --text-secondary:#dddddd; --text-tertiary:#9ca3af; --border-color:#2c2c2e; --reply-panel-bg:#18181a; --error-bg:#2b1618; --error-text:#fca5a5; }\nhtml.qd-dark-preload .replies-container.expanded, body.qd-dark-preload .replies-container.expanded { background:#18181a !important; }\nhtml.qd-dark-preload .reply-loading, body.qd-dark-preload .reply-loading { background:#18181a !important; color:#9ca3af !important; }\n@media (prefers-color-scheme: dark) {\n :root { --bg-primary:#121212; --text-primary:#e5e7eb; --text-secondary:#dddddd; --text-tertiary:#9ca3af; --border-color:#2c2c2e; --reply-panel-bg:#18181a; --error-bg:#2b1618; --error-text:#fca5a5; }\n html, body { background:#121212 !important; color:#e5e7eb !important; }\n .replies-container.expanded { background:#18181a !important; }\n .reply-loading { background:#18181a !important; color:#9ca3af !important; }\n .reply-toggle, .reply-more:not(.hidden), #loadMore:not(.hidden) { background:#2c2c2e !important; color:#9ca3af !important; }\n}\n@keyframes replyFadeIn { 0% { opacity: 0; transform: translateY(-4px); } 100% { opacity: 1; transform: translateY(0); } }\n.reply-item { animation: replyFadeIn .18s ease forwards; opacity: 0; }\n.reply-item:nth-child(n) { animation-delay: 0s; }\n.reply-loading { display:flex; align-items:center; justify-content:center; min-height:32px; padding:8px 10px; border-radius:8px; background:var(--reply-panel-bg,#f8fafc); color:var(--text-tertiary,#9ca3af); }\n.end-card { margin: 12px 0 16px 0; padding: 16px 18px; background: #ffffff; border-radius: 12px; color: #94a3b8; font-size: 13px; text-align: center; box-shadow: 0 2px 12px rgba(0,0,0,0.03); border: 1px solid #f1f5f9; }\n\/* ???????????? *\/\nbody:not(.is-beautified) #commentList > div { background: #ffffff; border-radius: 10px; margin-bottom: 8px; padding: 14px 12px; box-shadow: 0 1px 4px rgba(0,0,0,0.03); border: 1px solid #f3f4f6; }\nbody:not(.is-beautified) #commentList > div:last-child { margin-bottom: 0; }\n\/* ???????????? *\/\n.comment-img { margin-top: 8px !important; border-radius: 8px; object-fit: cover; max-width: 160px; width: 160px; height: 160px; display: block; }\n body:not(.is-beautified) .comment-img { width: 140px !important; height: 140px !important; max-width: 140px !important; }\n .reply-item .comment-img { width: 100px !important; height: 100px !important; max-width: 100px !important; }\n img.badge-img { height: var(--badge-slot-h) !important; max-height: 24px !important; }\n .avatar { border-radius: 50% !important; object-fit: cover; }\n .reply-avatar { border-radius: 50% !important; object-fit: cover; }\naudio { margin-top: 8px; width: 100%; max-width: 28rem; }\n\/* ???????? *\/\n.comment-content-wrapper > p { margin: 0 0 4px 0; }\n\/* ??????????? *\/\n.badge-wrap { margin-left: 4px; }\n\/* ???????? *\/\n#loadMore { display: flex; align-items: center; justify-content: center; margin: 10px auto; min-width: 120px; }\n\n.loading-inline{display:flex;align-items:center;justify-content:center;gap:7px;width:fit-content;margin:20px auto;padding:8px 12px;color:#89827a;font-size:13px;line-height:1}\n.loading-spinner{width:16px;height:16px;border:2px solid rgba(137,130,122,.25);border-top-color:#e58a18;border-radius:50%;animation:qd-spin .78s linear infinite;flex:0 0 auto}\n.loading-text{color:#89827a;font-size:13px;line-height:1}\n@keyframes qd-spin{to{transform:rotate(360deg)}}\n <\/style>\n<\/head>\n<body class=\"bg-gray-50 min-h-screen\">\n <div id=\"stickyWrapper\">\n <div id=\"tabContainer\" class=\"tab-bar hidden\">\n <div class=\"tab-item active\" data-type=\"all\"><span>全部<\/span><span class=\"tab-num\" id=\"num-all\"><\/span><\/div>\n <div class=\"tab-item\" data-type=\"image\" style=\"display:none;\"><span>配图<\/span><span class=\"tab-num\" id=\"num-img\"><\/span><\/div>\n <div class=\"tab-item\" data-type=\"audio\" style=\"display:none;\"><span>配音<\/span><span class=\"tab-num\" id=\"num-audio\"><\/span><\/div>\n <\/div>\n <div id=\"roleTabContainer\" class=\"role-tab-bar hidden\"><\/div>\n <\/div>\n <div class=\"max-w-4xl mx-auto px-3 pb-6\">\n <h1 id=\"title\" class=\"text-[clamp(1.25rem,3vw,1.5rem)] font-bold text-gray-800 mb-6 mt-6\" style=\"text-indent:2em; display:none;\">加载中...<\/h1>\n <div id=\"commentCount\" class=\"text-gray-500 text-sm mb-4\" style=\"display:none !important;\"><\/div>\n <div id=\"errorAlert\" class=\"hidden bg-red-100 text-red-700 p-3 rounded-lg mt-2 mb-4\"><\/div>\n <div id=\"commentList\" class=\"space-y-2 hidden mt-2\"><\/div>\n <div id=\"loadMore\" class=\"hidden\">????<\/div>\n <div id=\"loading\" class=\"text-center text-gray-500 py-4 mt-2\">加载中...<\/div>\n <\/div>\n <div id=\"fullscreenOverlay\" class=\"fixed inset-0 bg-black\/90 flex justify-center items-center z-50 hidden\">\n <img id=\"fullscreenImg\" src=\"\" alt=\"全屏图片\">\n <\/div>\n<script>\n(function() {\n if (typeof java === 'undefined') { window.java = {}; }\n if (typeof cache === 'undefined') { window.cache = { _store: {}, get: function(k) { return this._store[k] }, put: function(k, v) { this._store[k] = v } }; }\n function debugEnvCheck(){}\n function getUrlParams() {\n if (window.qdBid) return { bookId: decodeURIComponent(window.qdBid || ''), chapterId: decodeURIComponent(window.qdCid || ''), paragraphId: decodeURIComponent(window.qdPara || '') };\n const params = new URLSearchParams(window.location.search);\n return { bookId: params.get('bookId') || params.get('book_id'), chapterId: params.get('chapterId') || params.get('chapter_id'), paragraphId: params.get('paragraphId') || params.get('paragraph_id') };\n }\nconst { bookId, chapterId, paragraphId } = getUrlParams();\n\nconst openType = String(window.qdCmtType || 'dp').toLowerCase();\nconst isAuthorSay = openType === 'author' || paragraphId === '-10';\n\n\/\/ -1 + dp 是章名段评;-1 + zp 是本章说;-10 是作者说。\nconst hasParagraph =\n paragraphId &&\n paragraphId !== '' &&\n !isAuthorSay &&\n !(paragraphId === '-1' && openType === 'zp');\n\nconst commentTypeText = isAuthorSay ? '作者说' : (hasParagraph ? '段评' : '章评');\n document.title = commentTypeText + '页';\n let commentPage = 1; let isCommentEnd = false; let isLoading = false; let hasAutoLoadedSecondPage = false;\n let currentTab = 'all'; let currentRoleId = '0';\n let totalAll = 0, totalImg = 0, totalAudio = 0;\n let isLazyLoaded = false;\n let audioRolesCached = null; \n const errorAlert = document.getElementById('errorAlert'); const loading = document.getElementById('loading'); const loadMore = document.getElementById('loadMore'); const commentCountElement = document.getElementById('commentCount'); const commentList = document.getElementById('commentList'); const fullscreenOverlay = document.getElementById('fullscreenOverlay'); const fullscreenImg = document.getElementById('fullscreenImg'); const titleElement = document.getElementById('title');\n titleElement.textContent = commentTypeText;\n if(hasParagraph) document.getElementById('tabContainer').classList.remove('hidden');\n if (window.ResizeObserver) {\n new ResizeObserver(() => {\n if (!document.body.classList.contains('is-beautified')) {\n const wrap = document.getElementById('stickyWrapper');\n if (wrap) document.body.style.paddingTop = wrap.offsetHeight + 'px';\n }\n }).observe(document.getElementById('stickyWrapper'));\n }\n \/\/ ✨:配合隐形支撑,完美解决清空列表导致高度坍塌,以及智能判断吸顶状态\n function resetScrollForTabs() {\n var sticky = document.getElementById('stickyWrapper');\n \/\/ 计算真正的吸顶状态\n if (sticky && document.body.classList.contains('is-beautified')) {\n var offset = 50; \/\/ topBar 高度\n var rect = sticky.getBoundingClientRect();\n if (rect.top <= offset + 5) {\n \/\/ 此时已经处于吸顶状态,计算并强制锁定在原文下方的临界点,绝不露出原文\n var prev = sticky.previousElementSibling;\n if (prev) {\n var targetY = window.scrollY + prev.getBoundingClientRect().bottom - offset;\n window.scrollTo(0, targetY);\n }\n } else {\n \/\/ 没有吸附时,正常滚到最顶端\n window.scrollTo(0, 0);\n }\n } else {\n window.scrollTo(0, 0); \/\/ 保持原有逻辑\n }\n }\n document.querySelectorAll('.tab-item').forEach(tab => {\n tab.addEventListener('click', () => {\n if (currentTab === tab.dataset.type) return; \n document.querySelectorAll('.tab-item').forEach(t => t.classList.remove('active'));\n tab.classList.add('active');\n currentTab = tab.dataset.type;\n if(currentTab === 'audio') {\n currentRoleId = '0'; \n document.getElementById('roleTabContainer').classList.remove('hidden');\n if (audioRolesCached) renderRoleTabs(audioRolesCached); \n } else {\n document.getElementById('roleTabContainer').classList.add('hidden');\n }\n commentPage = 1; isCommentEnd = false; window._seenCmtIds = {}; window._actualLoadedCount = 0;\n resetScrollForTabs(); \/\/ 拦截清空前的高度并修正滚动\n document.getElementById('commentList').innerHTML = '';\n loadComments();\n });\n });\n function renderRoleTabs(roles) {\n const rContainer = document.getElementById('roleTabContainer');\n if(!roles || roles.length === 0) { rContainer.classList.add('hidden'); return; }\n rContainer.innerHTML = '<div class=\"role-tab-item' + (currentRoleId === '0' ? ' active' : '') + '\" data-role-id=\"0\">全部配音<\/div>';\n roles.forEach(role => {\n rContainer.innerHTML += '<div class=\"role-tab-item' + (currentRoleId === String(role.AudioRoleId) ? ' active' : '') + '\" data-role-id=\"' + role.AudioRoleId + '\">' + (role.AudioRoleName || '未知') + '<\/div>';\n });\n rContainer.querySelectorAll('.role-tab-item').forEach(item => {\n item.addEventListener('click', () => {\n if (currentRoleId === item.dataset.roleId) return; \n rContainer.querySelectorAll('.role-tab-item').forEach(t => t.classList.remove('active'));\n item.classList.add('active');\n currentRoleId = item.dataset.roleId;\n commentPage = 1; isCommentEnd = false; window._seenCmtIds = {}; window._actualLoadedCount = 0;\n resetScrollForTabs(); \/\/ 拦截清空前的高度并修正滚动\n document.getElementById('commentList').innerHTML = '';\n loadComments();\n });\n });\n rContainer.classList.remove('hidden');\n }\n function lazyLoadCountAfterPageRender() {\n if (!hasParagraph || isLazyLoaded) return;\n isLazyLoaded = true;\n setTimeout(function() {\n try {\n fetchTabCount('paragraph_image_comments', 'image');\n fetchTabCount('paragraph_audio_comments', 'audio');\n } catch(e) {}\n }, 350);\n }\n function fetchTabCount(action, tabType) {\n var apiBase = window.qdApiBase || 'https:\/\/pl.001122.top\/api\/qidian_full_api.php';\n var url = apiBase + '?action=' + action +\n '&book_id=' + encodeURIComponent(bookId) +\n '&chapter_id=' + encodeURIComponent(chapterId) +\n '¶graph_id=' + encodeURIComponent(paragraphId || '') +\n '&page=1&page_size=20';\n var data = safeParseBigInt(httpGet(url));\n if (!data || data.code !== 0) return;\n var remote = data.data || {};\n var count = Number(remote.total || 0);\n if (!count) {\n var list = remote.comments || [];\n count = list.length || 0;\n }\n if (tabType === 'image') {\n totalImg = count;\n var tabImg = document.querySelector('.tab-item[data-type=\"image\"]');\n var numImg = document.getElementById('num-img');\n if (tabImg && count > 0) tabImg.style.display = 'flex';\n if (numImg && count > 0) numImg.textContent = count;\n } else if (tabType === 'audio') {\n totalAudio = count;\n var tabAudio = document.querySelector('.tab-item[data-type=\"audio\"]');\n var numAudio = document.getElementById('num-audio');\n if (tabAudio && count > 0) tabAudio.style.display = 'flex';\n if (numAudio && count > 0) numAudio.textContent = count;\n var roles = remote.audio_roles || (remote.raw && (remote.raw.AudioRole || remote.raw.AudioRoles)) || [];\n if (roles && roles.length) audioRolesCached = roles;\n }\n }\n function ensureMediaTabsFromList(list) {\n if (!hasParagraph || !list || !list.length) return;\n var imgCount = 0, audioCount = 0;\n for (var i = 0; i < list.length; i++) {\n var c = adaptComment(list[i]);\n if (c.imageUrl) imgCount++;\n if (c.audioUrl) audioCount++;\n }\n if (imgCount > 0) {\n var tabImg = document.querySelector('.tab-item[data-type=\"image\"]');\n var numImg = document.getElementById('num-img');\n if (tabImg) tabImg.style.display = 'flex';\n if (numImg && !numImg.textContent) numImg.textContent = totalImg || imgCount;\n }\n if (audioCount > 0) {\n var tabAudio = document.querySelector('.tab-item[data-type=\"audio\"]');\n var numAudio = document.getElementById('num-audio');\n if (tabAudio) tabAudio.style.display = 'flex';\n if (numAudio && !numAudio.textContent) numAudio.textContent = totalAudio || audioCount;\n }\n }\n function updateLoadMore() {\n if (!loadMore) return;\n if (!isLoading && !isCommentEnd) { loadMore.textContent = '加载更多'; loadMore.classList.remove('hidden'); }\n else { loadMore.classList.add('hidden'); }\n }\n function updateContextTitle(validComments) {\n if (!titleElement) return;\n if (isAuthorSay) { titleElement.textContent = '作者说'; return; }\n if (hasParagraph && validComments && validComments.length > 0) {\n var first = validComments[0] || {};\n var raw = first.reffer_content || first.RefferContent || first.ReferContent || first.referContent || first.refferContent || first.ParagraphContent || first.paragraphContent || (first.raw && (first.raw.RefferContent || first.raw.ReferContent || first.raw.reffer_content || first.raw.ParagraphContent)) || '';\n if (raw) { var paraText = String(raw).replace(\/[?\\s]\/g, ''); if (paraText.length > 50) paraText = paraText.slice(0, 50) + '?'; titleElement.textContent = paraText; return; }\n titleElement.textContent = '??'; return;\n }\n var dispTitle = String(window.chapterName || '??').split(',{\"style\"')[0];\n titleElement.textContent = (window.bookName ? '《' + window.bookName + '》' : '') + dispTitle;\n }\n function setCountText(total, loaded) {\n if (!commentCountElement) return;\n total = Number(total || 0);\n loaded = Number(loaded || 0);\n if (total > 0 && loaded > total) total = loaded;\n var shown;\n if (total === 0 && loaded === 0) {\n shown = '暂无' + commentTypeText;\n } else {\n var displayTotal = Math.max(total, loaded);\n shown = displayTotal + '条' + commentTypeText + ',已加载' + loaded + '条';\n }\n commentCountElement.textContent = shown;\n commentCountElement.style.display = 'block';\n if (typeof window._updateTopBar === 'function') {\n window._updateTopBar(shown);\n }\n}\n if (loadMore) loadMore.addEventListener('click', function() { if (!isLoading && !isCommentEnd) loadComments(); });\n\n function getLikeSvg() { const fillColor = 'rgb(0,0,0)'; return '<svg class=\"w-4 h-4 mr-1\" fill=\"none\" viewBox=\"0 0 24 24\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\"><g clip-rule=\"evenodd\" fill=\"' + fillColor + '\" fill-rule=\"evenodd\"><path d=\"m11.6934 5.05855c.6491-.9737 1.7419-1.55855 2.9122-1.55855h.2139c.9269 0 1.632.8323 1.4796 1.7466l-.6118 3.6712c-.0508.30477.1842.5822.4932.5822h2.3799c1.5776 0 2.7608 1.4433 2.4514 2.9903l-1.2 6c-.2337 1.1686-1.2597 2.0097-2.4514 2.0097h-11.3604c-1.38071 0-2.5-1.1193-2.5-2.5v-8c0-.27614.22386-.5.5-.5h3.92963c.50153 0 .96988-.25065 1.24808-.66795zm2.9122-.55855c-.8359 0-1.6165-.41775-2.0802 1.11325l-2.5156 3.7735c-.46371.69545-1.24428 1.11325-2.08017 1.11325h-2.92963c-.27614 0-.5.2239-.5.5v7c0 .8284.67157 1.5 1.5 1.5h11.3604c.715 0 1.3306-.5047 1.4709-1.2058l1.2-6c.1856-.9282-.5243-1.7942-1.4709-1.7942h-3.5604c-.147 0-.2865-.0647-.3815-.1768-.095-.1122-.1359-.2604-.1117-.4054l.8059-4.8356c.0508-.30477.1842-.5822.4932-.5822z\"\/><path d=\"m8 20c-.27614 0-.5-.2239-.5-9c0-.2761.22386-.5.5-.5.27614 0 .5.2239.5.5v9c0 .2761-.22386.5-.5.5z\"\/><\/g><\/svg>'; }\n \/* ⚠️ 核心修复:添加双重转义,确保在浏览器里正确生效! ⚠️ *\/\n const EMOJI_REG = \/\\\\[fn=(\\\\d+)\\\\]\/g;\n const EMOJI_MAP = { 1:\"👏\",2:\"🌹\",3:\"🤝\",4:\"😁\",5:\"😄\",6:\"🥺\",7:\"🙂\",8:\"😏\",9:\"😙\",10:\"👆🏻🐽\",11:\"🙄\",12:\"😭\",13:\"😵\",14:\"😥\",15:\"🖕🏻\",16:\"🥵\",17:\"😓\",18:\"🤫\",19:\"😂\",20:\"😢\",21:\"😍\",22:\"🤕🔨\",23:\"😑\",24:\"😫\",25:\"🤗\",26:\"🤪\",27:\"🙏\",28:\"😣\",29:\"💪\",30:\"💀\",31:\"😳\",32:\"😎\",33:\"🤭\",34:\"😄👏\",35:\"👍🏻\",36:\"🤓\",37:\"😡\",38:\"🙁\",39:\"😄❓\",40:\"😞\",41:\"😧\",42:\"💋\",43:\"☺️\",44:\"🤬\",45:\"😴\",46:\"🤠🚬\",47:\"😱\",48:\"🐷\",49:\"😪\",50:\"🤐\",51:\"🥴\",52:\"🌙\",53:\"❤️\",54:\"🔪\",55:\"🎁\",56:\"💔\",57:\"👊🏻\",58:\"😒\",59:\"✌🏻️\",60:\"😮\",61:\"🤨\",62:\"😴\",63:\"👏🏻\",64:\"🐲\",65:\"⭐\",66:\"🌧️\",67:\"🍉\",68:\"🍵\",69:\"🔥\",70:\"💯\" };\n function formatEmoji(str) { return str ? str.replace(EMOJI_REG, (m, n) => EMOJI_MAP[n] || '💬') : ''; }\n function escapeCommentHtml(str) {\n return String(str == null ? '' : str)\n .replace(\/&\/g, '&')\n .replace(\/<\/g, '<')\n .replace(\/>\/g, '>')\n .replace(\/\"\/g, '"')\n .replace(\/'\/g, ''');\n }\n function formatCommentHtml(str) {\n return escapeCommentHtml(formatEmoji(String(str == null ? '' : str)))\n .replace(\/\\\\\\\\\\\\\\\\n|\\\\\\\\n|\\\\r\\\\n|\\\\r|\\\\n\/g, '<br>');\n }\n function buildMetaText(createTime, floor, ipAddress) {\n let floorStr = floor ? ' · 第' + floor + '楼' : '';\n let ipStr = ipAddress ? ' · ' + ipAddress : '';\n let fullDateStr = createTime || '';\n let dateOnlyStr = fullDateStr.split(' ')[0] || fullDateStr;\n let metaSuffix = floorStr + ipStr;\n let testStr = fullDateStr + metaSuffix;\n let visualLen = 0;\n for (let i = 0; i < testStr.length; i++) {\n visualLen += testStr.charCodeAt(i) > 255 ? 2 : 1;\n }\n return visualLen > 28 ? (dateOnlyStr + metaSuffix) : testStr;\n }\n function truncateUsername(username) { return username || '匿名'; }\n function createBadgeSlot(titleObj) {\n const slot = document.createElement('span');\n slot.className = 'badge-slot';\n if (titleObj.type === 'img') {\n const img = document.createElement('img');\n img.className = 'badge-img';\n img.src = titleObj.val;\n img.referrerPolicy = 'no-referrer';\n img.onload = function() { img.style.display = 'block'; };\n img.onerror = function() { slot.remove(); };\n slot.appendChild(img);\n } else if (titleObj.type === 'text') {\n const txtWrap = document.createElement('span'); \n const innerTxt = document.createElement('span'); \n const colorMap = {\n 1: { bg: 'linear-gradient(180deg, #e0e7ff 0%, #c7d2fe 100%)', textBg: 'linear-gradient(180deg, #4338ca 0%, #312e81 100%)', shadow: 'rgba(67, 56, 202, 0.15)' },\n 2: { bg: 'linear-gradient(180deg, #ffe4e6 0%, #fecdd3 100%)', textBg: 'linear-gradient(180deg, #e11d48 0%, #be123c 100%)', shadow: 'rgba(225, 29, 72, 0.15)' },\n 3: { bg: 'linear-gradient(180deg, #f3e8ff 0%, #e9d8fd 100%)', textBg: 'linear-gradient(180deg, #9955dd 0%, #803bb8 100%)', shadow: 'rgba(153, 85, 221, 0.15)' },\n 9: { bg: 'linear-gradient(180deg, #f1f5f9 0%, #e2e8f0 100%)', textBg: 'linear-gradient(180deg, #475569 0%, #334155 100%)', shadow: 'rgba(71, 85, 105, 0.15)' }\n };\n const allowedKeys = [1, 2, 3, 9];\n const randomKey = allowedKeys[Math.floor(Math.random() * allowedKeys.length)];\n const style = colorMap[randomKey];\n txtWrap.style.cssText = 'display: inline-flex; align-items: center; justify-content: center; padding: 0.2rem 0.35rem; border-radius: 4px; margin-right: 2px; background: ' + style.bg + '; box-shadow: 0 1px 3px ' + style.shadow + '; box-sizing: border-box;';\n innerTxt.style.cssText = 'font-size: 0.65rem; line-height: 1; font-weight: 600; white-space: nowrap; background: ' + style.textBg + '; -webkit-background-clip: text; background-clip: text; color: transparent; text-shadow: 0 0 8px ' + style.shadow + ';';\n innerTxt.textContent = titleObj.val;\n txtWrap.appendChild(innerTxt);\n slot.appendChild(txtWrap);\n }\n return slot;\n }\n let scrollX = 0, scrollY = 0;\n function showError(message) { scrollX = window.scrollX; scrollY = window.scrollY; isLoading = true; loading.classList.add('hidden'); commentList.classList.add('hidden'); errorAlert.classList.remove('hidden'); refreshError(message, 3); }\n function refreshError(msg, leftSeconds) { if (leftSeconds <= 0) { errorAlert.textContent = ''; errorAlert.classList.add('hidden'); commentList.classList.remove('hidden'); window.scrollTo(scrollX, scrollY); isLoading = false; return; } errorAlert.textContent = msg + \" \" + leftSeconds + \"s后自动返回……\"; setTimeout(() => refreshError(msg, leftSeconds - 1), 1000); }\n function initFullscreenImage() {\n function closePreview(pushBack){var p=document.getElementById(\"preview\");if(!p||p.style.display!==\"flex\")return false;p.style.display=\"none\";document.body.style.overflow=\"\";var img=p.getElementsByTagName(\"img\")[0];if(img)img.style.transform=\"scale(1) translate(0px, 0px)\";if(pushBack&&location.hash===\"#qdPreview\"){history.back()}return true}\n window.addEventListener(\"hashchange\",function(){if(location.hash!==\"#qdPreview\")closePreview(false)});\n window.addEventListener(\"popstate\",function(){if(location.hash!==\"#qdPreview\")closePreview(false)});\n function showPreview(src,idx){\n var urls=Array.isArray(src)?src:[src];\n var cur=Math.max(0,Math.min(Number(idx||0),urls.length-1));\n if(!urls.length||!urls[cur])return;\n var p=document.getElementById(\"preview\"),img;\n if(!p){\n p=document.createElement(\"div\");p.id=\"preview\";p.className=\"preview\";p.style.cssText=\"position:fixed;inset:0;z-index:99999;background:rgba(0,0,0,0.92);display:none;align-items:center;justify-content:center;overflow:hidden;touch-action:none;overscroll-behavior:none;user-select:none;-webkit-user-select:none;-webkit-touch-callout:none;\";\n img=document.createElement(\"img\");img.style.cssText=\"position:absolute;max-width:95vw;max-height:95vh;object-fit:contain;transition:transform 0.15s ease;transform:translate(0px,0px) scale(1);will-change:transform;\";\n var imgNext=document.createElement(\"img\");imgNext.style.cssText=\"position:absolute;max-width:95vw;max-height:95vh;object-fit:contain;transition:transform 0.15s ease;transform:translate(100vw,0) scale(1);will-change:transform;display:none;\";\n var indicator=document.createElement(\"div\");indicator.style.cssText=\"position:absolute;left:18px;bottom:16px;padding:3px 9px;border-radius:999px;background:rgba(0,0,0,0.45);color:#fff;font-size:12px;line-height:18px;z-index:2;\";\n p.appendChild(imgNext);p.appendChild(img);p.appendChild(indicator);document.body.appendChild(p);\n var urlsState=[],curIndex=0,scale=1,startX=0,startY=0,moveX=0,moveY=0,baseX=0,baseY=0,overX=0,overY=0,isDragging=false,startDist=0,startScale=1,swipeTarget=-1,swipeDir=0;\n function setIndicator(){indicator.textContent=urlsState.length>1?(curIndex+1)+\" \/ \"+urlsState.length:\"\";indicator.style.display=urlsState.length>1?\"block\":\"none\"}\n function setImg(){img.src=urlsState[curIndex]||\"\";setIndicator()}\n function update(){img.style.transform=\"translate(\"+moveX+\"px,\"+moveY+\"px) scale(\"+scale+\")\"}\n function reset(){scale=1;moveX=0;moveY=0;baseX=0;baseY=0;overX=0;overY=0;swipeTarget=-1;swipeDir=0;img.style.transition=\"transform 0.15s ease\";imgNext.style.transition=\"transform 0.15s ease\";imgNext.style.display=\"none\";p.style.background=\"rgba(0,0,0,0.92)\";update()}\n function dist(ts){var dx=ts[0].clientX-ts[1].clientX,dy=ts[0].clientY-ts[1].clientY;return Math.sqrt(dx*dx+dy*dy)}\n function limitPan(v,max){return Math.max(-max,Math.min(max,v))}\n function panLimitX(){return Math.max(0,(scale-1)*window.innerWidth*0.45)}\n function panLimitY(){return Math.max(0,(scale-1)*window.innerHeight*0.45)}\n function prepSwipe(dx){if(scale!==1||urlsState.length<=1)return;var dir=dx<0?1:-1,target=(curIndex+dir+urlsState.length)%urlsState.length;if(swipeTarget!==target){swipeTarget=target;swipeDir=dir;imgNext.src=urlsState[target];imgNext.style.display=\"block\";imgNext.style.transition=\"none\"}imgNext.style.transform=\"translate(\"+(dx+dir*window.innerWidth)+\"px,0) scale(1)\"}\n p.onclick=function(e){if(e.target===p){closePreview(true);reset()}};\n img.onclick=function(e){e.stopPropagation()};\n img.ondblclick=function(e){e.stopPropagation();scale=scale>1?1:2.5;moveX=0;moveY=0;baseX=0;baseY=0;update()};\n p.addEventListener(\"touchstart\",function(e){if(p.style.display!==\"flex\")return;e.stopPropagation();img.style.transition=\"none\";imgNext.style.transition=\"none\";if(e.touches.length===2){isDragging=false;startDist=dist(e.touches);startScale=scale;return}if(e.touches.length!==1)return;isDragging=true;startX=e.touches[0].clientX;startY=e.touches[0].clientY;baseX=moveX;baseY=moveY;overX=0;overY=0;if(scale===1){moveX=0;moveY=0;swipeTarget=-1;swipeDir=0;imgNext.style.display=\"none\"}},{passive:true});\n p.addEventListener(\"touchmove\",function(e){if(p.style.display!==\"flex\")return;e.stopPropagation();if(e.cancelable)e.preventDefault();if(e.touches.length===2&&startDist){scale=Math.min(4,Math.max(1,startScale*(dist(e.touches)\/startDist)));if(scale===1){moveX=0;moveY=0;baseX=0;baseY=0}update();return}if(!isDragging||e.touches.length!==1)return;var dx=e.touches[0].clientX-startX,dy=e.touches[0].clientY-startY;if(scale>1){var maxX=panLimitX(),maxY=panLimitY(),rawX=baseX+dx,rawY=baseY+dy;moveX=limitPan(rawX,maxX);moveY=limitPan(rawY,maxY);overX=rawX-moveX;overY=rawY-moveY;if(Math.abs(overX)>Math.abs(overY)*1.2)prepSwipe(overX);else imgNext.style.display=\"none\";p.style.background=\"rgba(0,0,0,\"+(0.92-Math.min(0.55,Math.abs(overY)\/360))+\")\";update();return}moveX=dx;moveY=dy;if(Math.abs(dx)>Math.abs(dy)*1.2)prepSwipe(dx);else imgNext.style.display=\"none\";update();p.style.background=\"rgba(0,0,0,\"+(0.92-Math.min(0.55,Math.abs(moveY)\/360))+\")\"},{passive:false});\n p.addEventListener(\"touchend\",function(e){if(e)e.stopPropagation();img.style.transition=\"transform 0.18s ease\";imgNext.style.transition=\"transform 0.18s ease\";if(e&&e.touches&&e.touches.length===0)startDist=0;if(!isDragging){if(scale<=1)reset();return}isDragging=false;var ax=Math.abs(moveX),ay=Math.abs(moveY),w=window.innerWidth;if(scale>1){var ox=Math.abs(overX),oy=Math.abs(overY);if(oy>90&&oy>ox*1.2){closePreview(true);reset();return}if(swipeTarget>=0&&ox>70&&ox>oy*1.2){var zdir=swipeDir,zw=window.innerWidth;img.style.transform=\"translate(\"+(-zdir*zw)+\"px,0) scale(1)\";imgNext.style.transition=\"transform 0.18s ease\";imgNext.style.transform=\"translate(0,0) scale(1)\";setTimeout(function(){curIndex=swipeTarget;setImg();reset()},190);return}}if(scale===1&&ay>90&&ay>ax*1.2){closePreview(true);reset();return}if(scale===1&&swipeTarget>=0&&ax>70&&ax>ay*1.2){var dir=swipeDir;img.style.transform=\"translate(\"+(-dir*w)+\"px,0) scale(1)\";imgNext.style.transform=\"translate(0,0) scale(1)\";setTimeout(function(){curIndex=swipeTarget;setImg();moveX=0;moveY=0;img.style.transition=\"none\";img.style.transform=\"translate(0,0) scale(1)\";imgNext.style.display=\"none\";swipeTarget=-1;swipeDir=0},190);return}if(scale===1){moveX=0;moveY=0;imgNext.style.display=\"none\"}else{baseX=moveX;baseY=moveY;overX=0;overY=0;imgNext.style.display=\"none\"}p.style.background=\"rgba(0,0,0,0.92)\";update()},{passive:true});\n p._setPreview=function(list,index){urlsState=list||[];curIndex=Math.max(0,Math.min(Number(index||0),urlsState.length-1));reset();setImg()}\n }else{img=p.getElementsByTagName(\"img\")[1]||p.getElementsByTagName(\"img\")[0]}\n p._setPreview(urls,cur);p.style.display=\"flex\";document.body.style.overflow=\"hidden\";\n try{if(location.hash!==\"#qdPreview\")location.hash=\"qdPreview\"}catch(e){}\n }\n document.addEventListener('click', (e) => {\n const target = e.target;\n if (target.classList && target.classList.contains('post-img')) {\n let parent = target.closest('.post-img-list');\n if (parent) {\n let imgs = Array.from(parent.querySelectorAll('.post-img'));\n let urls = imgs.map(img => img.src);\n let idx = imgs.indexOf(target);\n showPreview(urls, idx);\n } else {\n showPreview([target.src], 0);\n }\n } else if (target.classList && (target.classList.contains('avatar') || target.classList.contains('comment-img'))) {\n showPreview([target.src], 0);\n }\n });\n }\n \/* ⚠️ 核心修复:防止引号解析失效导致的数据解析崩溃 ⚠️ *\/\n function safeParseBigInt(jsonStr) { \n if (typeof jsonStr !== 'string') return jsonStr; \n if (jsonStr.indexOf('java.') === 0 || jsonStr.indexOf('{') === -1) {\n return { Result: -1, Message: \"网络波动导致获取失败,请重试\" };\n }\n try {\n jsonStr = jsonStr.replace(\/(\"rootReviewId\" *: *)([0-9]{16,})\/g, '$1\"$2\"'); \n jsonStr = jsonStr.replace(\/(\"([A-Za-z_]*Id|[A-Za-z_]*ID|[A-Za-z_]*IdList)\" *: *)([0-9]{16,})\/g, '$1\"$3\"'); \n return JSON.parse(String(jsonStr).trim()); \n } catch (e) {\n return { Result: -1, Message: \"数据解析异常\" };\n }\n }\n function qdTextOf(v) {\n if (v == null) return '';\n if (typeof v === 'string') {\n var s = v;\n try {\n var p = JSON.parse(s);\n if (Array.isArray(p) || (p && typeof p === 'object')) return qdTextOf(p);\n } catch(e) {}\n return s;\n }\n if (Array.isArray(v)) return v.map(function(a){ return qdTextOf(a); }).join('');\n if (typeof v === 'object') {\n return qdTextOf(v.Text || v.text || v.Content || v.content || v.Body || v.body || v.Value || v.value || v.Txt || v.txt || v.Items || v.items || v.List || v.list || '');\n }\n return String(v || '');\n }\n function qdPick(obj, keys, def) {\n obj = obj || {};\n for (var i = 0; i < keys.length; i++) {\n var v = obj[keys[i]];\n if (v !== undefined && v !== null && v !== '') return v;\n }\n return def == null ? '' : def;\n }\n function qdImageOf(v) {\n var list = qdImageList(v);\n return list.length ? list[0] : '';\n }\n function qdImageList(v) {\n if (!v) return [];\n if (typeof v === 'string') {\n try {\n var p = JSON.parse(v);\n if (p && typeof p === 'object') return qdImageList(p);\n } catch(e) {}\n return v ? [v] : [];\n }\n if (Array.isArray(v)) {\n var out = [];\n v.forEach(function(item) { out = out.concat(qdImageList(item)); });\n return out.filter(function(u, idx, arr) { return !!u && arr.indexOf(u) === idx; });\n }\n if (typeof v === 'object') {\n var direct = v.Url || v.url || v.ImageUrl || v.imageUrl || v.ThumbUrl || v.thumbUrl || v.image_url || '';\n if (direct) return [direct];\n return qdImageList(v.ImageDetail || v.ImageList || v.Images || v.PicList || v.List || v.list || v.Items || v.items || '');\n }\n return [];\n }\n function qdAudioOf(v) {\n if (!v) return '';\n if (typeof v === 'string') {\n try {\n var p = JSON.parse(v);\n if (p && typeof p === 'object') return qdAudioOf(p);\n } catch(e) {}\n return v;\n }\n if (Array.isArray(v)) {\n for (var i = 0; i < v.length; i++) {\n var u = qdAudioOf(v[i]);\n if (u) return u;\n }\n return '';\n }\n if (typeof v === 'object') {\n return v.Url || v.url || v.AudioUrl || v.audioUrl || v.audio_url || v.VoiceUrl || v.voiceUrl || v.Src || v.src || qdAudioOf(v.Audio || v.audio || v.Voice || v.voice || '');\n }\n return '';\n }\n function adaptComment(item) {\n item = item || {};\n var raw = item.raw || item;\n var user = item.user_info || raw.UserInfo || raw.User || raw.user || {};\n var content = qdTextOf(item.text || item.content || raw.Content || raw.content || raw.ReviewContent || raw.Body || raw.PostBody || raw.PostContent || raw.Subject || raw.Title);\n var ts = Number(item.create_timestamp || raw.CreateTime || raw.CreateTimestamp || raw.PostTime || raw.UpdateTime || 0);\n if (ts && String(ts).length <= 10) ts *= 1000;\n let createTime = ts ? new Date(ts) : null;\n let timeStr = createTime ? (createTime.getFullYear() + \"-\" + (createTime.getMonth() + 1).toString().padStart(2, '0') + \"-\" + createTime.getDate().toString().padStart(2, '0') + \" \" + createTime.getHours().toString().padStart(2, '0') + \":\" + createTime.getMinutes().toString().padStart(2, '0')) : (raw.PostDate || raw.CreateTimeStr || '');\n return {\n reviewId: String(item.comment_id || raw.Id || raw.CommentId || raw.ReviewId || raw.PostId || raw.PostID || ''),\n nickname: user.user_name || user.UserName || user.NickName || user.nickname || raw.UserName || raw.NickName || '匿名',\n avatarUrl: user.user_avatar || user.UserHeadIcon || user.Avatar || user.avatar || raw.UserHeadIcon || raw.Avatar || '',\n content: String(content || ''),\n createTime: timeStr,\n ipAddress: raw.IpLocation || raw.IPLocation || raw.Address || '',\n likeCount: item.digg_count || raw.AgreeAmount || raw.LikeCount || raw.DiggCount || raw.StarCount || raw.PraiseCount || 0,\n replyCount: item.reply_count || raw.ReviewCount || raw.ReplyCount || raw.PostCount || raw.CommentCount || 0,\n titles: (function() {\n var ts = [];\n var tl = raw.TitleInfoList || item.TitleInfoList || [];\n if (Array.isArray(tl)) {\n for (var i = 0; i < tl.length; i++) {\n var t = tl[i] || {};\n if (t.TitleImage) ts.push({ type: 'img', val: t.TitleImage });\n else if (t.TitleName) ts.push({ type: 'text', val: t.TitleName, titleType: t.TitleType || 0 });\n }\n }\n return ts;\n })(),\n floor: raw.Floor || raw.FloorNo || 0,\n imageUrl: qdImageOf(item.image_url || raw.image_url || raw.ImageDetail || raw.ImageList || raw.Images || raw.PicList),\n audioUrl: qdAudioOf(item.audio_url || raw.audio_url || raw.AudioUrl || raw.audioUrl || raw.Audio || raw.VoiceUrl || raw.voiceUrl),\n frameUrl: item.frame_url || raw.FrameUrl || raw.UserHeadFrame || '',\n replyToName: raw.RelatedUser || raw.ReplyUserName || '',\n replyToUserId: raw.RelatedUserId ? String(raw.RelatedUserId) : '',\n isGod: raw.EssenceType === 2 || raw.IsEssence === true\n };\n }\n function renderComment(comment) {\n const commentItem = document.createElement('div'); commentItem.className = 'flex py-2 border-b border-gray-100 leading-relaxed text-base relative overflow-hidden'; commentItem.setAttribute('data-review-id', comment.reviewId);\n const avatarContainer = document.createElement('div'); avatarContainer.className = 'relative w-12 h-12 flex items-center justify-center flex-shrink-0';\n if (comment.frameUrl) { const frameImg = document.createElement('img'); frameImg.className = 'absolute inset-0 w-full h-full object-contain'; frameImg.src = comment.frameUrl; frameImg.alt = '头像框'; frameImg.referrerPolicy = 'no-referrer'; frameImg.onerror = () => frameImg.classList.add('hidden'); avatarContainer.appendChild(frameImg); }\n \/* 头像加入懒加载与异步解码 *\/\n const avatar = document.createElement('img'); avatar.className = 'avatar w-8 h-8 rounded-full object-cover cursor-pointer relative z-10 flex-shrink-0'; avatar.src = comment.avatarUrl || 'https:\/\/qidian.gtimg.com\/qd\/images\/ico\/default_user.0.2.png'; avatar.alt = comment.nickname || '用户头像'; avatar.referrerPolicy = 'no-referrer'; avatar.loading = 'lazy'; avatar.decoding = 'async'; avatarContainer.appendChild(avatar); commentItem.appendChild(avatarContainer);\n const commentContent = document.createElement('div'); commentContent.className = 'flex-1 min-w-0 z-10 relative'; commentItem.appendChild(commentContent);\n const commentHeader = document.createElement('div'); commentHeader.className = 'flex flex-nowrap items-center';\n const userInfo = document.createElement('div'); \n userInfo.style.cssText = 'display: flex; align-items: center; min-width: 0; flex: 1;';\n const nickname = document.createElement('span'); \n nickname.className = 'font-medium text-gray-800 text-base'; \n nickname.style.cssText = 'display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 0 1 auto; min-width: 0; margin-right: 4px;'; \n nickname.textContent = truncateUsername(comment.nickname); \n nickname.title = comment.nickname || '匿名'; \n userInfo.appendChild(nickname);\n const badgeWrap = document.createElement('span'); \n badgeWrap.className = 'badge-wrap';\n (comment.titles || []).slice(0, 3).forEach(function(titleUrl) {\n badgeWrap.appendChild(createBadgeSlot(titleUrl));\n });\n userInfo.appendChild(badgeWrap);\n commentHeader.appendChild(userInfo); commentContent.appendChild(commentHeader);\n const commentBody = document.createElement('div'); commentBody.className = 'mt-1 relative z-10';\n const content = document.createElement('p'); content.className = 'text-gray-700 leading-relaxed'; \n \/* ⚠️ 核心修复:确保能够正确处理换行而不被转义吞噬 ⚠️ *\/\n content.innerHTML = formatCommentHtml(comment.content); commentBody.appendChild(content);\n \/* ==== 主图切除过渡动画,保留原比例,增加防塌陷占位背景 ==== *\/\n if (comment.imageUrl) { \n const img = document.createElement('img'); \n img.className = 'comment-img mt-2 rounded-md max-w-[60vw] w-auto cursor-pointer'; \n img.style.cssText = 'min-height: 120px; background-color: #f3f4f6;';\n img.src = comment.imageUrl; \n img.alt = '评论图片'; \n img.referrerPolicy = 'no-referrer'; \n img.loading = 'lazy';\n img.decoding = 'async';\n commentBody.appendChild(img); \n }\n if (comment.audioUrl) { const audio = document.createElement('audio'); audio.className = 'mt-2 w-full max-w-md'; audio.src = comment.audioUrl; audio.controls = true; commentBody.appendChild(audio); }\n commentContent.appendChild(commentBody);\n const commentMeta = document.createElement('div'); commentMeta.className = 'mt-2 flex justify-between items-center text-sm relative z-10';\n const metaInfo = document.createElement('div'); \n metaInfo.className = 'truncate flex-1 mr-2'; \n metaInfo.textContent = buildMetaText(comment.createTime, comment.floor, comment.ipAddress);\n const likeBtn = document.createElement('div'); likeBtn.className = 'flex items-center text-gray-500 transition-colors flex-shrink-0'; likeBtn.innerHTML = getLikeSvg() + \" \" + comment.likeCount;\n commentMeta.appendChild(metaInfo); commentMeta.appendChild(likeBtn); commentContent.appendChild(commentMeta);\n const replyWrapper = document.createElement('div'); replyWrapper.className = 'mt-2 relative z-10'; commentContent.appendChild(replyWrapper);\n if (comment.replyCount > 0) { const replyToggle = document.createElement('div'); replyToggle.className = 'reply-toggle'; replyToggle.textContent = \"展开\" + comment.replyCount + \"条回复\"; replyToggle.addEventListener('click', () => { loadReplies(comment, replyWrapper, replyToggle); }); replyWrapper.appendChild(replyToggle); }\n const repliesContainer = document.createElement('div'); repliesContainer.className = 'replies-container mt-3 space-y-3 hidden'; replyWrapper.appendChild(repliesContainer);\n const replyMore = document.createElement('div'); replyMore.className = 'reply-more hidden'; replyWrapper.appendChild(replyMore);\n if (comment.isGod) {\n var godStamp = document.createElement('div');\n godStamp.className = 'absolute pointer-events-none z-50';\n godStamp.style.cssText = 'top: 0px; right: 2px; opacity: 0.9;'; \n var svgStr = '<svg width=\"42\" height=\"42\" viewBox=\"0 0 100 100\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\">' +\n '<g transform=\"rotate(15 50 50)\">' +\n '<circle cx=\"50\" cy=\"50\" r=\"44\" stroke=\"#B89047\" stroke-width=\"2\" fill=\"none\" \/>' +\n '<circle cx=\"50\" cy=\"50\" r=\"38\" stroke=\"#B89047\" stroke-width=\"1.5\" stroke-dasharray=\"4 3\" fill=\"none\" \/>' +\n '<text x=\"50\" y=\"55\" font-family=\"STXingkai, 华文行楷, cursive, serif\" font-size=\"24\" font-weight=\"bold\" fill=\"#B89047\" text-anchor=\"middle\" letter-spacing=\"1\">神评论<\/text>' +\n '<text x=\"50\" y=\"72\" font-family=\"sans-serif\" font-size=\"8\" fill=\"#B89047\" letter-spacing=\"1\" font-weight=\"bold\" text-anchor=\"middle\">QIDIANDUSHU<\/text>' +\n '<\/g><\/svg>';\n godStamp.innerHTML = svgStr;\n commentItem.appendChild(godStamp);\n if (typeof commentHeader !== 'undefined') {\n commentHeader.classList.add('god-stamp-header');\n }\n }\n return commentItem;\n }\n function renderComments(comments) { \n if (!comments || comments.length === 0) { \n if (commentPage === 1) { commentList.innerHTML = '<div class=\"text-center text-gray-500 py-8\">暂无评论<\/div>'; } \n return; \n } \n comments.forEach(function(comment) { commentList.appendChild(renderComment(comment)); }); \n }\n function httpGet(url) {\n if (window.java && typeof window.java.ajax === 'function') {\n return window.java.ajax(url);\n }\n try {\n if (window.java && typeof window.java.get === 'function') {\n var res = window.java.get(url, {});\n if (res && typeof res.body === 'function') return res.body();\n if (res != null) return String(res);\n }\n } catch(e) {}\n throw new Error(\"当前阅读 WebView 不支持 java.ajax\/java.get\");\n }\n function buildAuthorSayComments(remoteData) {\n remoteData = remoteData || {};\n var obj = remoteData.author_say || remoteData.authorSay || remoteData.AuthorSay || remoteData.Author_say || remoteData;\n if (typeof obj === 'string') {\n try {\n var parsed = JSON.parse(obj);\n if (parsed && typeof parsed === 'object') obj = parsed;\n } catch(e) {}\n }\n if (!obj || typeof obj !== 'object') obj = { AuthorComments: String(obj || '') };\n\n var value = qdPick(obj, [\n 'AuthorComments', 'AuthorComment', 'AuthorSay', 'author_say', 'authorSay',\n 'ChapterAuthorSay', 'chapterAuthorSay', 'Content', 'Text', 'content', 'text',\n 'Comment', 'comment', 'Say', 'say', 'AuthorWords', 'authorWords', 'WriterSay', 'writerSay'\n ], '');\n var content = qdTextOf(value).trim();\n if (!content) {\n var ask = obj.AskMonthTicket || obj.ask_month_ticket || {};\n content = qdTextOf(qdPick(ask, ['AuthorExpectText', 'author_expect_text'], '')).trim();\n }\n if (!content) return [];\n\n var authorName = qdPick(obj, ['AuthorName', 'author_name', 'NickName', 'nickname', 'UserName', 'Author', 'author'], '作者');\n var avatar = qdPick(obj, ['HeadImageUrl', 'UserHeadIcon', 'Avatar', 'avatar', 'AuthorAvatar', 'author_avatar'], '');\n var createTime = qdPick(obj, ['CreateTime', 'CreateTimestamp', 'UpdateTime', 'PostTime'], 0);\n return [{\n comment_id: 'author-' + String(chapterId || ''),\n text: content,\n create_timestamp: createTime,\n digg_count: 0,\n reply_count: 0,\n user_info: { user_name: authorName, user_avatar: avatar },\n raw: { UserName: authorName, UserHeadIcon: avatar, CreateTime: createTime }\n }];\n }\n async function loadComments() {\n if (isLoading || isCommentEnd) return;\n isLoading = true; loading.classList.remove('hidden'); updateLoadMore();\n try {\n debugEnvCheck();\n if (!bookId || !chapterId) { throw new Error('参数错误:缺少书籍ID(bookId)或章节ID(chapterId)'); }\n if (currentTab !== 'all' && !hasParagraph) { currentTab = 'all'; }\n var apiAction = isAuthorSay ? 'chapter_activity' : (hasParagraph ? 'paragraph_comments' : 'chapter_comments');\n if (hasParagraph && currentTab === 'image') apiAction = 'paragraph_image_comments';\n if (hasParagraph && currentTab === 'audio') apiAction = 'paragraph_audio_comments';\n var apiBase = window.qdApiBase || 'https:\/\/pl.001122.top\/api\/qidian_full_api.php';\n var apiUrl = apiBase + '?action=' + apiAction +\n '&book_id=' + encodeURIComponent(bookId) +\n '&chapter_id=' + encodeURIComponent(chapterId) +\n (hasParagraph ? '¶graph_id=' + encodeURIComponent(paragraphId || '') : '') +\n (currentTab === 'audio' ? '&role_id=' + encodeURIComponent(currentRoleId || '0') : '') +\n '&page=' + commentPage + '&page_size=20';\n var response = httpGet(apiUrl);\n var data = safeParseBigInt(response);\n if (!data || typeof data !== 'object') { throw new Error('返回数据格式异常'); }\n if (data.code !== 0) { throw new Error(data.message || '接口错误'); }\n var remoteData = data.data || {};\n var rawList = isAuthorSay ? buildAuthorSayComments(remoteData) : (remoteData.comments || []);\n if (isAuthorSay) remoteData.total = rawList.length;\n ensureMediaTabsFromList(rawList);\n if (commentPage === 1) {\n var tabTotal = Number(remoteData.total || rawList.length || 0);\n if (currentTab === 'all') {\n totalAll = tabTotal;\n var nAll = document.getElementById('num-all');\n if (nAll) nAll.textContent = totalAll;\n lazyLoadCountAfterPageRender();\n } else if (currentTab === 'image') {\n totalImg = tabTotal;\n var nImg = document.getElementById('num-img');\n var tImg = document.querySelector('.tab-item[data-type=\"image\"]');\n if (tImg) tImg.style.display = 'flex';\n if (nImg) nImg.textContent = totalImg;\n } else if (currentTab === 'audio') {\n totalAudio = tabTotal;\n var nAudio = document.getElementById('num-audio');\n var tAudio = document.querySelector('.tab-item[data-type=\"audio\"]');\n if (tAudio) tAudio.style.display = 'flex';\n if (nAudio) nAudio.textContent = totalAudio;\n var roles = remoteData.audio_roles || (remoteData.raw && (remoteData.raw.AudioRole || remoteData.raw.AudioRoles)) || [];\n if (roles && roles.length) { audioRolesCached = roles; renderRoleTabs(audioRolesCached); }\n else if (audioRolesCached && audioRolesCached.length) { renderRoleTabs(audioRolesCached); }\n }\n }\n if (commentPage === 1) {\n window._seenCmtIds = {};\n window._actualLoadedCount = 0;\n }\n window._seenCmtIds = window._seenCmtIds || {};\n const validComments = rawList.filter(function(item) {\n var id = item.comment_id || (item.raw && (item.raw.Id || item.raw.CommentId || item.raw.ReviewId)) || (item.create_timestamp + '_' + (item.text || ''));\n if (window._seenCmtIds[id]) return false;\n window._seenCmtIds[id] = true;\n return true;\n });\n var pageSize = 20;\n window._actualLoadedCount += validComments.length;\n var activeTotal = currentTab === 'image' ? totalImg : (currentTab === 'audio' ? totalAudio : totalAll);\n if (!activeTotal) activeTotal = Number(remoteData.total || remoteData.TotalCount || remoteData.ReviewTotalCount || remoteData.ChapterReviewCount || 0);\n isCommentEnd = rawList.length < pageSize || (activeTotal > 0 && window._actualLoadedCount >= activeTotal);\n loading.classList.add('hidden');\n if (commentPage === 1) {\n commentList.classList.remove('hidden');\n var cCount = document.getElementById('commentCount');\n if (cCount) cCount.classList.remove('hidden');\n }\n if (commentPage === 1) updateContextTitle(validComments);\n if (validComments.length === 0 && commentPage === 1) {\n commentList.innerHTML = '<div class=\"text-center text-gray-500 py-8\">暂无评论<\/div>';\n } else {\n validComments.map(function(i) { return adaptComment(i); }).forEach(function(c) {\n commentList.appendChild(renderComment(c));\n });\n }\n setCountText(activeTotal, window._actualLoadedCount);\n if (!isCommentEnd) {\n commentPage++;\n } else if (!commentList.querySelector('.end-card')) {\n const endDiv = document.createElement('div');\n endDiv.className = 'end-card';\n endDiv.textContent = '已经到底了~';\n commentList.appendChild(endDiv);\n }\n } catch (error) {\n loading.classList.add('hidden');\n showError(error.message || String(error));\n } finally {\n isLoading = false;\n updateLoadMore();\n }\n }\n function renderReply(reply) {\n const replyItem = document.createElement('div'); replyItem.className = 'reply-item flex pt-4 border-t border-gray-100';\n \/* 头像加入懒加载与异步解码 *\/\n const avatar = document.createElement('img'); avatar.className = 'avatar w-8 h-8 rounded-full object-cover cursor-pointer flex-shrink-0'; avatar.src = reply.avatarUrl || 'https:\/\/qidian.gtimg.com\/qd\/images\/ico\/default_user.0.2.png'; avatar.alt = reply.nickname || '用户头像'; avatar.referrerPolicy = 'no-referrer'; avatar.loading = 'lazy'; avatar.decoding = 'async'; replyItem.appendChild(avatar);\n const replyContent = document.createElement('div'); replyContent.className = 'flex-1 min-w-0 ml-2'; replyItem.appendChild(replyContent);\n const replyHeader = document.createElement('div'); replyHeader.className = 'flex flex-nowrap items-center';\n const replyLeft = document.createElement('div'); \n replyLeft.style.cssText = 'display: flex; align-items: center; min-width: 0; flex: 1;';\n const nickname = document.createElement('span'); \n nickname.className = 'font-medium text-gray-800 text-sm'; \n nickname.style.cssText = 'display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 0 1 auto; min-width: 0; margin-right: 4px;'; \n nickname.textContent = truncateUsername(reply.nickname); \n nickname.title = reply.nickname || '匿名'; \n replyLeft.appendChild(nickname);\n const badgeWrap = document.createElement('span'); \n badgeWrap.className = 'badge-wrap';\n (reply.titles || []).slice(0, 3).forEach(function(titleUrl) {\n badgeWrap.appendChild(createBadgeSlot(titleUrl));\n });\n replyLeft.appendChild(badgeWrap); \n replyHeader.appendChild(replyLeft);\n replyContent.appendChild(replyHeader);\n if (reply.replyToName) { const replyTo = document.createElement('div'); replyTo.className = 'text-gray-500 text-xs leading-tight mt-0.5'; replyTo.textContent = \"回复@\" + truncateUsername(reply.replyToName); replyTo.title = reply.replyToName; replyContent.appendChild(replyTo); }\n const replyBody = document.createElement('div'); replyBody.className = 'mt-1 text-sm'; \n \/* ⚠️ 核心修复:同样解决楼中楼的换行符吞噬问题 ⚠️ *\/\n replyBody.innerHTML = formatCommentHtml(reply.content || '');\n \/* ==== 楼中楼图切除过渡动画,保留原比例,增加防塌陷占位背景 ==== *\/\n if (reply.imageUrl) { \n const img = document.createElement('img'); \n img.className = 'comment-img mt-1 rounded-md max-w-[60vw] w-auto cursor-pointer'; \n img.style.cssText = 'min-height: 80px; background-color: #f3f4f6;';\n img.src = reply.imageUrl; \n img.alt = '回复图片'; \n img.referrerPolicy = 'no-referrer'; \n img.loading = 'lazy';\n img.decoding = 'async';\n replyBody.appendChild(img); \n }\n if (reply.audioUrl) { const audio = document.createElement('audio'); audio.className = 'mt-1 w-full max-w-sm'; audio.src = reply.audioUrl; audio.controls = true; replyBody.appendChild(audio); }\n replyContent.appendChild(replyBody);\n const replyMeta = document.createElement('div'); replyMeta.className = 'mt-1 flex justify-between items-center text-sm text-gray-500';\n const metaInfo = document.createElement('div'); \n metaInfo.className = 'truncate flex-1 mr-2'; \n metaInfo.textContent = buildMetaText(reply.createTime, reply.floor, reply.ipAddress);\n const likeBtn = document.createElement('div'); likeBtn.className = 'flex items-center flex-shrink-0'; likeBtn.innerHTML = \" \" + getLikeSvg() + \" \" + (reply.likeCount || 0);\n replyMeta.appendChild(metaInfo); replyMeta.appendChild(likeBtn); replyContent.appendChild(replyMeta);\n return replyItem;\n }\n async function loadReplies(comment, replyWrapper, toggleEl) {\n const reviewId = comment.reviewId;\n const repliesContainer = replyWrapper.querySelector('.replies-container');\n const replyMore = replyWrapper.querySelector('.reply-more');\n let replyPage = 1;\n let loadedCount = 0;\n const totalReplies = comment.replyCount || 0;\n if (!reviewId) { toggleEl.textContent = '回复ID缺失'; return; }\n toggleEl.classList.add('hidden');\n repliesContainer.classList.remove('hidden');\n repliesContainer.classList.add('expanded');\n repliesContainer.innerHTML = '';\n const loadingEl = document.createElement('div');\n loadingEl.className = 'reply-loading text-gray-500 text-sm mt-2';\n loadingEl.textContent = '加载中...';\n repliesContainer.appendChild(loadingEl);\n function replyUrl(page) {\n var apiBase = window.qdApiBase || 'https:\/\/pl.001122.top\/api\/qidian_full_api.php';\n return apiBase + '?action=comment_replies' +\n '&book_id=' + encodeURIComponent(bookId) +\n '&chapter_id=' + encodeURIComponent(chapterId) +\n (hasParagraph ? '¶graph_id=' + encodeURIComponent(paragraphId || '') : '') +\n '&root_review_id=' + encodeURIComponent(reviewId) +\n '&page=' + page + '&page_size=20';\n }\n function addEndMark() {\n const endMark = document.createElement('div');\n endMark.className = 'flex items-center text-xs mt-3 mb-1 w-full';\n endMark.style.color = '#9ca3af';\n endMark.innerHTML = '<div class=\"flex-1 border-t\" style=\"border-color: currentColor; opacity: 0.2;\"><\/div><div class=\"mx-3\" style=\"opacity: 0.9;\">已加载全部回复<\/div><div class=\"flex-1 border-t\" style=\"border-color: currentColor; opacity: 0.2;\"><\/div>';\n repliesContainer.appendChild(endMark);\n }\n async function fetchAndRender(page, append) {\n var data = safeParseBigInt(httpGet(replyUrl(page)));\n if (!data || typeof data !== 'object') throw new Error('回复返回数据格式异常');\n if (data.code !== 0) throw new Error(data.message || '获取回复失败');\n var rawList = (data.data && data.data.comments) || [];\n var valid = rawList.map(function(i) { return adaptComment(i); });\n if (!append) repliesContainer.innerHTML = '';\n valid.forEach(function(r) { repliesContainer.appendChild(renderReply(r)); });\n loadedCount += valid.length;\n return valid.length;\n }\n try {\n var firstCount = await fetchAndRender(replyPage, false);\n if (loadingEl.parentNode) loadingEl.parentNode.removeChild(loadingEl);\n repliesContainer.classList.remove('hidden');\n repliesContainer.classList.add('expanded');\n var isReplyEnd = (loadedCount >= totalReplies) || firstCount < 20;\n if (!isReplyEnd) {\n replyMore.textContent = '展开更多回复';\n replyMore.classList.remove('hidden');\n replyMore.onclick = async function() {\n if (isReplyEnd) return;\n replyMore.classList.add('hidden');\n const moreLoading = document.createElement('div');\n moreLoading.className = 'reply-loading text-gray-500 text-sm mt-2';\n moreLoading.textContent = '加载中...';\n repliesContainer.appendChild(moreLoading);\n try {\n replyPage++;\n var moreCount = await fetchAndRender(replyPage, true);\n if (moreLoading.parentNode) moreLoading.parentNode.removeChild(moreLoading);\n isReplyEnd = (loadedCount >= totalReplies) || moreCount < 20;\n if (!isReplyEnd) replyMore.classList.remove('hidden');\n else addEndMark();\n } catch(err) {\n if (moreLoading.parentNode) moreLoading.parentNode.removeChild(moreLoading);\n showError(err.message || String(err));\n }\n };\n } else if (firstCount > 0) {\n addEndMark();\n }\n } catch(error) {\n if (loadingEl.parentNode) loadingEl.parentNode.removeChild(loadingEl);\n showError(error.message || String(error));\n toggleEl.classList.remove('hidden');\n }\n }\n function initInfiniteScroll() { let ticking = false; const PRELOAD_THRESHOLD = 600; window.addEventListener('scroll', () => { if (!ticking) { window.requestAnimationFrame(() => { const scrollY = window.scrollY || document.documentElement.scrollTop || document.body.scrollTop || 0; const windowHeight = window.innerHeight || document.documentElement.clientHeight || 0; const documentHeight = Math.max(document.documentElement.scrollHeight, document.body.scrollHeight, document.documentElement.offsetHeight); const remaining = documentHeight - (scrollY + windowHeight); if (remaining <= PRELOAD_THRESHOLD && !isLoading && !isCommentEnd) { loadComments(); } ticking = false; }); ticking = true; } }); }\n function init() { if (!bookId || !chapterId) { showError('参数错误:缺少书籍ID(bookId)或章节ID(chapterId)'); return; } initFullscreenImage(); initInfiniteScroll(); setTimeout(function() { loadComments(); }, 200); }\n if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); }\n})();\n<\/script>\n<\/body>\n<\/html>`;\n}\n\/\/ ================= 新增:内置美化评论 HTML =================\n\n\n\n\n\n\n\nfunction getBuiltInBeautifyHtml() {\n return `<script>(function(){function applyBeautify(){if(window._hasBeautified)return;var list=document.getElementById('commentList');if(!list)return;window._hasBeautified=true;document.body.classList.add('is-beautified');var style=document.createElement('style');style.textContent=\"*{-webkit-tap-highlight-color:transparent!important;outline:none!important}.max-w-4xl{background:transparent!important;box-shadow:none!important;padding:0!important;margin-top:0!important;width:100%!important;box-sizing:border-box!important;overflow:visible!important}.comment-card .flex.py-2.border-b{display:flex!important;gap:12px!important;position:relative!important;background:#ffffff!important;padding:18px!important;border-radius:12px!important;box-shadow:0 1px 6px rgba(0,0,0,0.03)!important;margin-bottom:12px!important;border:none!important;cursor:pointer!important}img.avatar{width:40px!important;height:40px!important;flex-shrink:0!important;border-radius:50%!important;border:1px solid rgba(0,0,0,0.05)!important;background:#f0f0f0!important;object-fit:cover!important}.comment-card .font-medium{color:#000!important;font-weight:bold!important;display:block!important;overflow:hidden!important;text-overflow:ellipsis!important;white-space:nowrap!important}.comment-card .flex.py-2.border-b>.flex-1>div:first-child .font-medium{font-size:15px!important}.comment-card .flex.py-2.border-b>.flex-1>.mt-1:not(.flex){font-size:16px!important;line-height:1.6!important;margin-top:4px!important}.comment-card .comment-img{width:160px!important;height:160px!important;max-width:160px!important;border-radius:8px!important;object-fit:cover!important}.replies-container{background:#f8fafc!important;padding:8px!important;border-radius:8px!important;margin-top:12px!important;margin-left:-60px!important;width:calc(100% + 60px)!important}.reply-item{display:flex!important;gap:8px!important;margin-bottom:2px!important;padding:8px 8px 8px 6px!important;border-bottom:none!important;align-items:flex-start!important;border-radius:6px!important}.reply-item img.avatar{width:30px!important;height:30px!important;margin-top:2px!important}body.dark-mode{--bg-primary:#121212!important;background-color:#121212!important}body.dark-mode .comment-card .flex.py-2.border-b{background:#1c1c1e!important;color:#e5e7eb!important;border:1px solid #2c2c2e!important}body.dark-mode .replies-container{background:#18181a!important}\";document.head.appendChild(style)}var checkTimer=setInterval(function(){if(document.querySelector('.max-w-4xl')||document.getElementById('commentList')){applyBeautify();clearInterval(checkTimer)}},50);setTimeout(function(){clearInterval(checkTimer);applyBeautify()},3000)})();<\\\/script>`;\n}\n\n\n\n\n\n\n\n\n\n\n\n\nfunction getBuiltInDarkHtml() {\n var html = [\n \"<script>\",\n \"\/* 核心:把深色CSS像钢钉一样打进 <head>,绝不会被App的异步刷新删掉! *\/\",\n \"(function() {\",\n \" var darkStyle = document.createElement('style');\",\n \" darkStyle.id = 'immortal-dark-css';\",\n \" darkStyle.textContent = `\",\n \" \/* 强制根部黑底 *\/\",\n \" html, body { background-color: #121212 !important; }\",\n \" :root { \",\n \" --x-bg:#121212; --x-card:#1c1c1e; --x-border:#2c2c2e; --x-text:#e5e7eb; --x-subtext:#9ca3af; \",\n \" --x-top-bg:rgba(28,28,30,0.98); --x-top-border:#2c2c2e; --x-replies-bg:#18181a; \",\n \" --x-btn-bg:#2c2c2e; --x-tab-def:#9ca3af; --x-tab-act:#e5e7eb; --x-role-bg:#2c2c2e; \",\n \" }\",\n \"\",\n \" \/* 暴力接管所有可能被 App 异步插入的原生白色类名,只要它敢出现,物理层面瞬间变黑 *\/\",\n \" [style*='background-color: rgb(255, 255, 255)'], [style*='background-color: #fff'], [style*='background: white'] { background-color: transparent !important; color: var(--x-text) !important; }\",\n \" .bg-white, [class*='bg-white'] { background-color: var(--x-card) !important; border-color: var(--x-border) !important; color: var(--x-text) !important; }\",\n \" .bg-gray-50, [class*='bg-gray-50'] { background-color: var(--x-bg) !important; border-color: var(--x-border) !important; color: var(--x-text) !important; }\",\n \" .text-gray-800, [class*='text-gray-8'] { color: var(--x-text) !important; }\",\n \" .text-gray-700, [class*='text-gray-7'] { color: #ddd !important; }\",\n \" .text-gray-500, .text-gray-400, .text-xs, .reply-loading, [class*='text-gray-5'], [class*='text-gray-4'] { color: var(--x-subtext) !important; }\",\n \" .border-gray-100 { border-color: var(--x-border) !important; }\",\n \"\",\n \" \/* ================= 原版美化的 CSS 排版 ================= *\/\",\n \" html, body, #beautify-wrap { background-color: var(--x-bg) !important; color: var(--x-text) !important; }\",\n \" .comment-card .flex.py-2.border-b, .end-card { background-color: var(--x-card) !important; border-color: var(--x-border) !important; }\",\n \" .replies-container { background-color: var(--x-replies-bg) !important; }\",\n \" .reply-toggle, .reply-more:not(.hidden) { background-color: var(--x-btn-bg) !important; color: var(--x-subtext) !important; }\",\n \" .tab-bar, .tab-num { color: var(--x-tab-def) !important; }\",\n \" .tab-item.active, .tab-item.active .tab-num { color: var(--x-tab-act) !important; font-weight: 800 !important; }\",\n \" .tab-item.active::after { background-color: #f87171 !important; }\",\n \" .role-tab-item { background: var(--x-role-bg) !important; color: var(--x-subtext) !important; border-color: transparent !important; }\",\n \" .role-tab-item.active { background: rgba(239, 68, 68, 0.15) !important; color: #f87171 !important; border-color: rgba(248, 113, 113, 0.3) !important; font-weight: bold !important; }\",\n \" .flex.items-center.flex-shrink-0 svg path, .comment-action svg path { fill: var(--x-text) !important; }\",\n \"\",\n \" * { -webkit-tap-highlight-color: transparent !important; outline: none !important; }\",\n \" #themeToggle, #commentCount { display: none !important; }\",\n \" .max-w-4xl { background: transparent !important; box-shadow: none !important; padding: 0 !important; margin-top: 0 !important; width: 100% !important; box-sizing: border-box !important; overflow: visible !important; }\",\n \"\",\n \" \/* 原文框透明化:不管 App 异步刷新多少次塞进什么框,它永远没有背景色,彻底杜绝白块 *\/\",\n \" .content-card { position: relative !important; background: transparent !important; padding: 18px 20px 18px 24px !important; border-radius: 0 !important; box-shadow: none !important; margin-bottom: 0px !important; margin-top: 0px !important; display: flex !important; align-items: center !important; min-height: 60px !important; width: 100% !important; box-sizing: border-box !important; border: none !important; }\",\n \" .content-card::before { content: '' !important; position: absolute !important; left: 0 !important; top: 18px !important; bottom: 18px !important; width: 4px !important; background: #4b5563 !important; border-radius: 0 4px 4px 0 !important; }\",\n \" .content-card > *, #title, #title * { margin: 0 !important; font-size: 16px !important; font-weight: 800 !important; text-align: left !important; text-indent: 0 !important; color: var(--x-text) !important; line-height: 1.6 !important; letter-spacing: 0.5px !important; word-break: break-word !important; display: block !important; background: transparent !important; border: none !important; box-shadow: none !important; }\",\n \" #title::before, #title *::before { display: none !important; }\",\n \"\",\n \" body.is-beautified .tab-bar { padding-bottom: 0px !important; }\",\n \" body.is-beautified .role-tab-bar { margin-top: 4px !important; padding-bottom: 2px !important; }\",\n \" .comment-card { background: transparent !important; padding: 0 !important; border-radius: 0 !important; box-shadow: none !important; margin: 0 !important; width: 100% !important; box-sizing: border-box !important; min-height: 100vh !important; }\",\n \" .comment-card .flex.py-2.border-b { display: flex !important; gap: 12px !important; position: relative !important; background: var(--x-card) !important; padding: 18px !important; border-radius: 12px !important; box-shadow: 0 1px 6px rgba(0,0,0,0.03) !important; margin-bottom: 12px !important; border: 1px solid var(--x-border) !important; cursor: pointer !important; }\",\n \" .comment-card .flex.py-2.border-b > .flex-1, .reply-item > .flex-1 { flex: 1 !important; min-width: 0 !important; }\",\n \" img.avatar { width: 40px !important; height: 40px !important; flex-shrink: 0 !important; border-radius: 50% !important; box-shadow: none !important; border: 1px solid var(--x-border) !important; background: #2c2c2e !important; object-fit: cover !important; }\",\n \" .comment-card .font-medium, .reply-item .font-medium { display: block !important; overflow: hidden !important; text-overflow: ellipsis !important; white-space: nowrap !important; }\",\n \" .comment-card .flex.py-2.border-b > .flex-1 > div:first-child .font-medium { font-size: 15px !important; }\",\n \" .comment-card .flex.py-2.border-b > .flex-1 > .mt-1:not(.flex) { font-size: 16px !important; line-height: 1.6 !important; margin-top: 4px !important; }\",\n \" .reply-item img.avatar { width: 30px !important; height: 30px !important; margin-top: 2px !important; border: 1px solid var(--x-border) !important; }\",\n \" .reply-item .font-medium { font-size: 14px !important; }\",\n \" .reply-item > .flex-1 > .mt-1:not(.flex) { font-size: 15px !important; line-height: 1.5 !important; margin-top: 4px !important; }\",\n \" :root { --badge-slot-h: 1.5em !important; }\",\n \" img.badge-img { height: var(--badge-slot-h) !important; max-height: 24px !important; }\",\n \" .badge-slot > span { background: #2c2c2e !important; box-shadow: none !important; border: 1px solid #4b5563 !important; }\",\n \" .badge-slot > span > span { background: none !important; color: #9ca3af !important; -webkit-text-fill-color: #9ca3af !important; text-shadow: none !important; }\",\n \" .replies-container { background: var(--x-replies-bg) !important; padding: 8px !important; border-radius: 8px !important; margin-top: 12px !important; margin-left: -60px !important; width: calc(100% + 60px) !important; border-left: none !important; position: relative !important; box-sizing: border-box !important; }\",\n \" .reply-item { display: flex !important; gap: 8px !important; margin-bottom: 2px !important; padding: 8px 8px 8px 6px !important; border-bottom: none !important; align-items: flex-start !important; border-radius: 6px !important; }\",\n \" `;\",\n \" if(document.head) document.head.appendChild(darkStyle);\",\n \" else document.documentElement.appendChild(darkStyle);\",\n \"})();\",\n \"<\/script>\",\n \"<script>\",\n \"function applyDarkBeautify() {\",\n \" var parent = document.querySelector('.max-w-4xl');\",\n \" if (!parent) return;\",\n \" document.body.classList.add('is-beautified');\",\n \" function createElementWithStyle(tag,style,content){\",\n \" var el=document.createElement(tag); if(content)el.innerHTML=content;\",\n \" for(var k in style)el.style[k]=style[k]; return el;\",\n \" }\",\n \" var wrapContainer = document.getElementById('beautify-wrap');\",\n \" if (!wrapContainer) {\",\n \" wrapContainer = createElementWithStyle('div',{width:'100%',maxWidth:'96%',margin:'0 auto',background:'transparent',minHeight:'100vh',borderRadius:'16px',boxShadow:'none',padding:'60px 8px 30px 8px',boxSizing:'border-box',position:'relative',zIndex:1});\",\n \" wrapContainer.id = 'beautify-wrap';\",\n \" var children=Array.from(document.body.children);\",\n \" for(var i=0;i<children.length;i++){\",\n \" var el=children[i];\",\n \" if(el.tagName!=='SCRIPT'&&el.tagName!=='STYLE'&&el.id!=='fullscreenOverlay') wrapContainer.appendChild(el);\",\n \" }\",\n \" document.body.appendChild(wrapContainer);\",\n \" document.body.style.backgroundColor='#121212';\",\n \" document.body.style.overflowX='hidden';\",\n \" document.body.style.paddingTop='0';\",\n \" }\",\n \" var title = document.getElementById('title');\",\n \" var list = document.getElementById('commentList');\",\n \" var stickyWrapper = document.getElementById('stickyWrapper');\",\n \" var count = document.getElementById('commentCount');\",\n \" var err = document.getElementById('errorAlert');\",\n \" var loading = document.getElementById('loading');\",\n \" \/\/ 如果原代码已经乖乖待在我们建好的黑框里,就不重复处理\",\n \" if (title && title.parentElement && title.parentElement.classList.contains('content-card')) { return; }\",\n \"\",\n \" var contentCard = createElementWithStyle('div'); \",\n \" contentCard.className = 'content-card';\",\n \" if (title) { title.style.display = 'block'; contentCard.appendChild(title); }\",\n \" if (count) contentCard.appendChild(count);\",\n \" var commentCard = createElementWithStyle('div'); commentCard.className = 'comment-card';\",\n \" if (err) commentCard.appendChild(err);\",\n \" if (list) commentCard.appendChild(list);\",\n \" if (loading) commentCard.appendChild(loading);\",\n \" \",\n \" while (parent.firstChild) parent.removeChild(parent.firstChild);\",\n \" parent.style.overflow = 'visible';\",\n \" parent.appendChild(contentCard);\",\n \" if (stickyWrapper) {\",\n \" stickyWrapper.style.position = 'sticky';\",\n \" stickyWrapper.style.top = '50px';\",\n \" stickyWrapper.style.zIndex = '90';\",\n \" stickyWrapper.style.padding = '6px 12px 0 12px';\",\n \" stickyWrapper.style.margin = '0 -4px 6px -4px';\",\n \" stickyWrapper.style.backgroundColor = '#121212';\",\n \" parent.appendChild(stickyWrapper);\",\n \" }\",\n \" parent.appendChild(commentCard);\",\n \"\",\n \" var topBar = document.getElementById('dark-top-bar');\",\n \" if (!topBar) {\",\n \" topBar = createElementWithStyle('div', {\",\n \" position:'fixed', top:'0', left:'0', width:'100%', height:'50px',\",\n \" display:'flex', alignItems:'center', justifyContent:'space-between',\",\n \" padding:'0 18px', boxSizing:'border-box', zIndex:9999,\",\n \" background:'rgba(28,28,30,0.98)', borderBottom:'1px solid #2c2c2e'\",\n \" });\",\n \" topBar.className = 'top-bar';\",\n \" topBar.id = 'dark-top-bar';\",\n \" var cmtCountTop = createElementWithStyle('span', {fontSize:'13px', color:'#9ca3af', fontWeight:'500'}, '💬 加载中...');\",\n \" cmtCountTop.id = 'cmtCountTop';\",\n \" var titleEl = createElementWithStyle('span', {fontSize:'16px', fontWeight:600, color:'#e5e7eb'}, '◎ 段评详情');\",\n \" titleEl.id = 'titleEl';\",\n \" var topRightWrapper = createElementWithStyle('div', {display:'flex', alignItems:'center', gap:'10px'});\",\n \" topRightWrapper.appendChild(titleEl); topBar.appendChild(cmtCountTop); topBar.appendChild(topRightWrapper);\",\n \" document.body.appendChild(topBar);\",\n \" function syncCommentCount() {\",\n \" var e = document.getElementById('commentCount');\",\n \" if (!e || !e.textContent.trim()) return;\",\n \" cmtCountTop.textContent = '💬 ' + e.textContent.trim();\",\n \" }\",\n \" syncCommentCount();\",\n \" var syncTimer = null;\",\n \" function debouncedSync() {\",\n \" if (syncTimer) clearTimeout(syncTimer);\",\n \" syncTimer = setTimeout(syncCommentCount, 300);\",\n \" }\",\n \" if (typeof MutationObserver !== 'undefined') {\",\n \" new MutationObserver(debouncedSync).observe(document.body, { characterData: true, childList: true, subtree: true });\",\n \" }\",\n \" window._updateTopBar = function(txt) { if (cmtCountTop) cmtCountTop.textContent = '💬 ' + txt; };\",\n \" }\",\n \"}\",\n \"\",\n \"applyDarkBeautify();\",\n \"\/* 核心二:永不罢工的 DOM 擒拿手!只要App敢异步重新注入带有白底框的 #title,立刻触发重新排版将它按回去! *\/\",\n \"if (typeof MutationObserver !== 'undefined') {\",\n \" var domObserver = new MutationObserver(function(mutations) {\",\n \" for(var i=0; i<mutations.length; i++) {\",\n \" if (mutations[i].addedNodes.length > 0) applyDarkBeautify();\",\n \" }\",\n \" });\",\n \" domObserver.observe(document.body, { childList: true, subtree: true });\",\n \"}\",\n \"<\/script>\"\n ];\n return html.join(\"\\n\");\n}\nfunction getConfig() {\n var _map = this.source ? this.source.getLoginInfoMap() : (_qdS ? _qdS.getLoginInfoMap() : null);\n var v = _map && (_map.get ? _map.get(\"◎ 半屏高度\") : _map[\"◎ 半屏高度\"]);\n var ratio = 0.8;\n if (v != null && String(v).trim() !== \"\") {\n var parsed = Number(v);\n if (!isNaN(parsed)) {\n if (parsed > 1 && parsed <= 100) {\n ratio = parsed \/ 100;\n } else if (parsed > 0 && parsed <= 1) {\n ratio = parsed;\n }\n }\n }\n return {\n state: 4,\n isHideable: true,\n heightPercentage: ratio,\n expandedCornersRadius: 20,\n dismissOnTouchOutside: true,\n widthPercentage: 1.0,\n maxWidth: -1,\n isGestureInsetBottomIgnored: true,\n skipCollapsed: true,\n hardwareAccelerated: true,\n webViewInitialScale: 100\n };\n}\n\n\n\nfunction showCmt(bid, cid, para, time, cmtType) {\n var j = null;\n var s = null;\n var c = null;\n\n try { j = this && this.java ? this.java : null; } catch(e) {}\n try { s = this && this.source ? this.source : null; } catch(e) {}\n try { c = this && this.cache ? this.cache : null; } catch(e) {}\n\n if (!j && typeof java !== \"undefined\") j = java;\n if (!c && typeof cache !== \"undefined\") c = cache;\n if (!s && typeof source !== \"undefined\") s = source;\n\n _qdJ = j;\n _qdC = c;\n _qdS = s;\n\n if (!j) return;\n\ncmtType = cmtType || \"dp\";\n\nvar url = \"https:\/\/druidv6.if.qidian.com\/paragraph-comment?bookId=\" +\n encodeURIComponent(bid) +\n \"&chapterId=\" + encodeURIComponent(cid) +\n \"¶graphId=\" + encodeURIComponent(para || \"\") +\n \"&type=\" + encodeURIComponent(cmtType);\n\n var html = getBuiltInCommentHtml();\n\n var dpTheme = \"\";\n try {\n var m = s && s.getLoginInfoMap ? s.getLoginInfoMap() : null;\n var v = m && (m.get ? m.get(\"◎ 段评美化\") : m[\"◎ 段评美化\"]);\n if (v) dpTheme = String(v);\n } catch(e) {}\n\n if (dpTheme.indexOf(\"美化\") !== -1) {\n html += getBuiltInBeautifyHtml();\n } else if (dpTheme.indexOf(\"深色\") !== -1) {\n var darkPrimer = \"<script>document.documentElement.classList.add('qd-dark-preload');document.addEventListener('DOMContentLoaded',function(){document.body.classList.add('qd-dark-preload');});<\/script><style>:root{--bg-primary:#121212;--text-primary:#e5e7eb;--text-secondary:#dddddd;--text-tertiary:#9ca3af;--border-color:#2c2c2e;--reply-panel-bg:#18181a;}html,body{background:#121212!important;color:#e5e7eb!important;}*{color:#e5e7eb!important;}<\/style>\";\n html = darkPrimer + html + getBuiltInDarkHtml();\n }\n\n var bName = \"\";\n var cName = \"\";\n try {\n bName = this.book && this.book.name ? String(this.book.name) : \"\";\n cName = this.chapter && this.chapter.title ? String(this.chapter.title) : \"\";\n } catch(e) {}\n\n bName = bName.replace(\/(['\"\\\\])\/g, \"\\\\$1\").replace(\/\\n|\\r\/g, \"\");\n cName = cName.replace(\/(['\"\\\\])\/g, \"\\\\$1\").replace(\/\\n|\\r\/g, \"\");\n\n var apiBase = qdApiBase();\n\nvar preloadJs =\n \"window.java=java;\" +\n \"window.cache=cache;\" +\n \"window.qdApiBase='\" + apiBase + \"';\" +\n \"window.bookName='\" + bName + \"';\" +\n \"window.chapterName='\" + cName + \"';\" +\n \"window.qdBid='\" + encodeURIComponent(bid) + \"';\" +\n \"window.qdCid='\" + encodeURIComponent(cid) + \"';\" +\n \"window.qdPara='\" + encodeURIComponent(para || \"\") + \"';\" +\n \"window.qdCmtType='\" + String(cmtType || \"dp\").replace(\/(['\\\"\\\\])\/g, \"\\\\$1\") + \"';\";\n\nj.showBrowser(url, html, preloadJs, JSON.stringify(getConfig.call(this)));\n}\n",
"lastUpdateTime": "1785978367892",
"loginUi": "@js:\nconst fullStyle = {\n layout_flexGrow: 1,\n layout_flexBasisPercent: 1\n};\n\nconst commonStyle = {\n layout_flexGrow: 1,\n layout_flexBasisPercent: 0.4\n};\n\nconst smallStyle = {\n layout_flexGrow: 1,\n layout_flexBasisPercent: 0.27\n};\n\nconst inviteStyle = {\n layout_flexGrow: 1,\n layout_flexBasisPercent: 0.8\n};\n\nconst midStyle = {\n layout_flexGrow: 1,\n layout_flexBasisPercent: 0.33\n};\n\nresult = JSON.stringify([\n {\n name: \"🍋 账号 🍋\",\n type: \"button\",\n style: fullStyle\n },\n {\n name: \"账号管理\",\n type: \"button\",\n action: \"_login()\",\n style: commonStyle\n },\n {\n name: \"账号信息\",\n type: \"button\",\n action: \"showUserInfo()\",\n style: commonStyle\n },\n {\n name: \"邀请码\",\n type: \"text\",\n style: fullStyle\n },\n {\n name: \"邀请码注册Token\",\n type: \"button\",\n action: \"registerByInvite()\",\n style: inviteStyle\n },\n {\n name: \"查询Token\",\n type: \"button\",\n action: \"queryToken()\",\n style: commonStyle\n },\n {\n name: \"数据库统计\",\n type: \"button\",\n action: \"showDb()\",\n style: commonStyle\n },\n\n {\n name: \"🍋 发现页模式 🍋\",\n type: \"button\",\n style: fullStyle\n },\n {\n name: \"全部\",\n type: \"button\",\n action: \"setType('all')\",\n style: commonStyle\n },\n {\n name: \"小说\",\n type: \"button\",\n action: \"setType('novel')\",\n style: commonStyle\n },\n {\n name: \"漫画\",\n type: \"button\",\n action: \"setType('comic')\",\n style: commonStyle\n },\n {\n name: \"有声\",\n type: \"button\",\n action: \"setType('audio')\",\n style: commonStyle\n },\n\n {\n name: \"🍋 发现页小说频道 🍋\",\n type: \"button\",\n style: fullStyle\n },\n {\n name: \"男频\",\n type: \"button\",\n action: \"setGender(1)\",\n style: commonStyle\n },\n {\n name: \"女频\",\n type: \"button\",\n action: \"setGender(0)\",\n style: commonStyle\n },\n\n {\n name: \"🍋 搜索、发现排序 🍋\",\n type: \"button\",\n style: fullStyle\n },\n {\n name: \"人气最高\",\n type: \"button\",\n action: \"setOrder(11, '人气最高')\",\n style: commonStyle\n },\n {\n name: \"最近更新\",\n type: \"button\",\n action: \"setOrder(1, '最近更新')\",\n style: commonStyle\n },\n {\n name: \"收藏最多\",\n type: \"button\",\n action: \"setOrder(3, '收藏最多')\",\n style: commonStyle\n },\n {\n name: \"月票最多\",\n type: \"button\",\n action: \"setOrder(4, '月票最多')\",\n style: commonStyle\n },\n {\n name: \"字数最多\",\n type: \"button\",\n action: \"setOrder(6, '字数最多')\",\n style: commonStyle\n },\n {\n name: \"最新入V\",\n type: \"button\",\n action: \"setOrder(14, '最新入V')\",\n style: commonStyle\n },\n {\n name: \"最新发布\",\n type: \"button\",\n action: \"setOrder(15, '最新发布')\",\n style: commonStyle\n },\n\n {\n name: \"🍋 评论设置 🍋\",\n action: \"commentRefreshTip()\",\n type: \"button\",\n style: fullStyle\n },\n {\n name: \"段评开关\",\n viewName: \"' 段评开关'\",\n type: \"toggle\",\n action: \"commentRefreshTip()\",\n chars: [\"🔳\", \"✅\"],\n default: \"🔳\",\n style: smallStyle\n },\n {\n name: \"章名段评\",\n viewName: \"' 章名段评'\",\n type: \"toggle\",\n action: \"commentRefreshTip()\",\n chars: [\"🔳\", \"✅\"],\n default: \"🔳\",\n style: smallStyle\n },\n {\n name: \"本章讨论\",\n viewName: \"' 本章讨论'\",\n type: \"toggle\",\n action: \"commentRefreshTip()\",\n chars: [\"🔳\", \"✅\"],\n default: \"🔳\",\n style: smallStyle\n },\n {\n name: \"作者评论\",\n viewName: \"' 作者评论'\",\n type: \"toggle\",\n action: \"commentRefreshTip()\",\n chars: [\"🔳\", \"✅\"],\n default: \"🔳\",\n style: smallStyle\n },\n {\n name: \"热门评论\",\n viewName: \"' 热门评论'\",\n type: \"toggle\",\n action: \"commentRefreshTip()\",\n chars: [\"🔳\", \"✅\"],\n default: \"🔳\",\n style: smallStyle\n },\n {\n name: \"段评分色\",\n viewName: \"' 段评分色'\",\n type: \"toggle\",\n action: \"commentRefreshTip()\",\n chars: [\"🔳\", \"✅\"],\n default: \"🔳\",\n style: smallStyle\n },\n {\n name: \"彩蛋章开关\",\n viewName: \"' 彩蛋章开关'\",\n type: \"toggle\",\n action: \"eggRefreshTip()\",\n chars: [\"🔳\", \"✅\"],\n default: \"🔳\",\n style: smallStyle\n },\n\n {\n name: \"◎ 段评颜色\",\n type: \"text\",\n action: \"check('◎ 段评颜色')\",\n style: commonStyle\n },\n {\n name: \"◎ 段评分色\",\n type: \"text\",\n action: \"check('◎ 段评分色')\",\n style: commonStyle\n },\n {\n name: \"◎ 热评颜色\",\n type: \"text\",\n action: \"check('◎ 热评颜色')\",\n style: commonStyle\n },\n {\n name: \"◎ 分色数值\",\n type: \"text\",\n action: \"check('◎ 分色数值')\",\n style: commonStyle\n },\n {\n name: \"◎ 半屏高度\",\n type: \"text\",\n action: \"check('◎ 半屏高度')\",\n style: commonStyle\n },\n {\n name: \"◎ 模板\",\n type: \"select\",\n chars: [\"起点\", \"样式一\", \"样式二\", \"样式三\"],\n default: \"起点\",\n style: commonStyle\n },\n {\n name: \"◎ 大小\",\n type: \"select\",\n chars: [\"TEXT\", \"text\"],\n default: \"text\",\n style: commonStyle\n },\n {\n name: \"◎ 段评美化\",\n type: \"select\",\n chars: [\"默认\", \"美化\", \"深色\"],\n default: \"美化\",\n style: commonStyle\n },\n\n {\n name: \"🍋 自定义气泡 🍋\",\n type: \"button\",\n style: fullStyle\n },\n {\n name: \"◎ 气泡一\",\n type: \"text\",\n action: \"check('◎ 气泡一')\",\n style: fullStyle\n },\n {\n name: \"◎ 气泡二\",\n type: \"password\",\n action: \"check('◎ 气泡二')\",\n style: fullStyle\n },\n {\n name: \"◎ 气泡三\",\n type: \"password\",\n action: \"check('◎ 气泡三')\",\n style: fullStyle\n },\n {\n name: \"🍋 书籍下载、购买 🍋\",\n type: \"button\",\n style: fullStyle\n },\n {\n name: \"开关购买章节自动使用章节卡\",\n type: \"button\",\n action: \"switchSetting('章节卡')\",\n style: midStyle\n },\n {\n name: \"书籍ID\",\n type: \"text\"\n },\n {\n name: \"全本后台下载\",\n type: \"button\",\n action: \"downloadNovel()\",\n style: fullStyle\n },\n {\n name: \"🛒 购买全本 🛒\",\n type: \"button\",\n action: \"buy(true)\",\n style: fullStyle\n },\n {\n name: \"购买数量\",\n type: \"text\",\n style: fullStyle\n },\n {\n name: \"🛒 购买指定数量章节(默认5) 🛒\",\n type: \"button\",\n action: \"buy(false)\",\n style: fullStyle\n }\n]);\n",
"loginUrl": "function login() { }\n\nfunction changeMenu(menuTag) {\n source.put(\"menuTag\", menuTag);\n java.reLoginView();\n}\n\nfunction check(key) {\n var v = String(result[key] || \"\").trim();\n var t = function(m) {\n java.toast(\"\\n\\n\" + m);\n };\n\n if (!v) return t(key + \"空值需要√保存\");\n\n var rule = {\n 6: [\"邮箱\", \"密码\", \"颜色\"],\n n: [\"数值\", \"高度\", \"数量\"]\n };\n\n if (rule[6].some(function(k) { return key.indexOf(k) >= 0; }) && v.length < 6) {\n return t(\"⛔️至少6位\");\n }\n\n if (rule.n.some(function(k) { return key.indexOf(k) >= 0; }) && !\/^\\d+(\\.\\d+)?$\/.test(v)) {\n return t(\"⛔️必须是数字\");\n }\n\n var map = new Packages.java.util.HashMap();\n map.put(key, v);\n java.upLoginData(map);\n\n try {\n source.putLoginInfo(JSON.stringify(result));\n } catch(e) {}\n\n t(key + \"已保存\");\n}\n\nfunction _login() {\n let token = String(source.getVariable());\n if (!token) {\n java.longToast('\\n❌请先于【源变量】处填写共享Token');\n return;\n }\n java.startBrowser(`${api}\/console\/login?token=${token}`, '登录页');\n}\n\nfunction queryToken() {\n let token = String(source.getVariable());\n if (token.length <= 60) {\n java.toast('请先于【源变量】处填写共享Token');\n } else {\n let info = requestApiUrl('\/user\/test');\n if (!info) return;\n java.longToast(`\\n✅查询成功\\n${info.msg}`);\n }\n}\n\nfunction registerByInvite() {\n let code = String(result['邀请码'])\n if (code == '') {\n return java.longToast('请输入邀请码')\n }\n let info = requestApiUrl('\/user\/invite', { id: java.androidId(), code: code }, this)\n if (!info) return\n let token = info.token\n source.setVariable(token)\n java.longToast(`\\n✅邀请码注册成功\\n用户:${info.id}\\n日下载剩余:${info.left}\\n创建时间:${info.createTime}\\n到期时间:${info.ddlTime}`)\n source.putLoginInfo(JSON.stringify(result))\n}\n\nfunction showDb() {\n let data = requestApiUrl('\/system\/dbinfo');\n if (!data) return;\n java.longToast(`\\n✅${data.msg}`);\n}\n\nfunction showUserInfo() {\n let data = requestApiUrl('\/console\/info');\n if (!data) return;\n java.longToast(`\\n✅查询成功\\n${data.msg}`);\n}\n\nfunction setType(type) {\n source.put('type', type);\n java.longToast(`\\n✅发现页模式已切换为:${type}\\n刷新发现页后生效`);\n}\n\nfunction setOrder(order, desc) {\n source.put('order', order);\n java.longToast(`\\n✅搜索、发现页排序已切换为:${desc}\\n实时生效`);\n}\n\nfunction setGender(gender) {\n source.put('gender', gender);\n java.longToast(`\\n✅发现页小说频道已切换为:${gender ? '男频' : '女频'}\\n刷新发现页后生效`);\n}\n\nfunction commentRefreshTip() {\n java.longToast(\"\\n评论开关不会实时生效\\n修改后请返回阅读页,手动刷新正文\");\n}\n\nfunction eggRefreshTip() {\n java.longToast(\"\\n彩蛋章开关已修改\\n请返回书籍目录并刷新后生效\");\n}\n\nfunction switchSetting(key, tip) {\n let old = source.get(key) || '0';\n let now = old == '1' ? '0' : '1';\n source.put(key, now);\n java.longToast(`\\n✅${key}已${now == '1' ? '开启' : '关闭'}${tip ? `\\n${tip}` : ''}`);\n}\n\nfunction downloadNovel() {\n let novelId = +result['书籍ID'];\n if (!novelId) {\n return java.toast('请输入有效书籍ID');\n }\n\n let data = requestApiUrl('\/novel\/download', { novelId });\n if (!data) return;\n\n java.longToast(`\\n✅${data.msg}`);\n\n try {\n source.putLoginInfo(JSON.stringify(result));\n } catch(e) {}\n}\n\nfunction change_paragraph_comment_svg_color() {\n let color = String(result['段评气泡颜色'] || \"\").trim();\n\n if (!\/^#([0-9a-fA-F]{6})$\/.test(color)) {\n return java.longToast('\\n❌请输入有效的颜色值,如:#FF0000');\n }\n\n source.put('paragraph_comment_svg_color', color);\n\n try {\n source.putLoginInfo(JSON.stringify(result));\n } catch(e) {}\n\n java.longToast(`\\n✅段评气泡颜色已设置为:${color}`);\n}\n\nfunction buy(all) {\n let novelId = +String(result['书籍ID']);\n if (!novelId) {\n return java.longToast('\\n❌请输入有效书籍ID');\n }\n\n let withChapterCard = source.get('章节卡') == '1';\n let data;\n\n if (all) {\n data = requestApiUrl('\/novel\/buy_novel', {\n novelId,\n withChapterCard\n });\n } else {\n let num = +String(result['购买数量']);\n if (!num || num <= 0) {\n num = 5;\n }\n\n data = requestApiUrl('\/novel\/buy_chapter', {\n novelId,\n num,\n withChapterCard\n });\n }\n\n if (!data) return;\n java.longToast('\\n✅' + data.msg);\n}",
"respondTime": 180000,
"ruleBookInfo": {
"author": "author",
"coverUrl": "cover",
"init": "data",
"intro": "desc",
"kind": "{{$.status}}\n{{$.category}}\n{{$.subCategory}}",
"lastChapter": "lastChapter",
"name": "name",
"tocUrl": "@js:\n`data:;base64,${java.base64Encode(java.getString('id'))},{\"type\":\"${java.getString('type')}\"}`",
"wordCount": "wordsCount"
},
"ruleContent": {
"content": "@js:\nvar decodedId = java.hexDecodeToString(result);\nvar urlObj = JSON.parse(baseUrl.slice(baseUrl.indexOf('{')));\nvar chapId = urlObj.chapterId || decodedId;\nvar type = urlObj.type;\nvar novelId = urlObj.novelId;\n\nif (type == \"novel\" && urlObj.qimoType === \"midpage\") {\n fetchMidpageContent.call(this, novelId, chapId, urlObj.midpageId || \"\");\n} else {\n var data = requestApiUrl.call(this, `\/${type}\/chap`, {\n novelId: novelId,\n chapId: chapId\n });\n\n if (!data) {\n \"获取内容失败\";\n } else if (type == \"novel\") {\n getComments.call(this, String(data.content || \"\"), novelId, chapId, \"dp\", data);\n } else if (type == \"comic\") {\n ((data.images || [])).map(function(x) {\n return `< img src=\"${x}\">`;\n }).join(\"\\n\");\n } else if (type == \"audio\") {\n data.url || \"\";\n } else {\n \"\";\n }\n}",
"imageDecode": "result",
"payAction": "\/\/java.log(chapter.url)\nlet novelId = chapter.url.match(\/\"novelId\":\\s*\"(\\d+?)\"\/)[1]\nlet type = chapter.url.match(\/\"type\":\\s*\"(\\S+?)\"\/)[1]\nlet chapId = java.base64Decode(chapter.url.match(\/base64,(.*?),\/)[1])\n\/\/java.log(JSON.stringify({ novelId, chapId, type }))\nif (type != 'novel') {\n false\n} else {\n let withChapterCard = source.get('章节卡') == '1'\n let data = requestApiUrl('\/novel\/buy_chapter', { novelId, chapId, withChapterCard })\n if (data && data.msg) {\n java.longToast(data.msg)\n true\n } else {\n false\n }\n}",
"title": "@js:\nvar text = String(title || result || \"\");\nvar cid = \"\";\nvar bid = \"\";\n\ntry {\n var base64 = baseUrl.match(\/data:;base64,([^,]+)\/)[1];\n cid = java.base64Decode(base64);\n var urlObj = JSON.parse(baseUrl.slice(baseUrl.indexOf(\"{\")));\n bid = urlObj.novelId || \"\";\n} catch(e) {\n cid = \"\";\n bid = \"\";\n}\n\nvar bubble = \"\";\nvar cfg;\ntry { cfg = getCfg.call(this); } catch(eC) { cfg = { zm: false }; }\n\n\/\/ imgUrl is persisted in the chapter database; clear it before recalculating the title bubble.\ntry { chapter.putImgUrl(null); } catch(eClearTitleImage) {}\n\nif (cfg.zm && cid && bid) {\n try {\n var count = 0;\n var hot = false;\n var summaryRes = qdGetParagraphSummary.call(this, bid, cid);\n var sd = summaryRes.data || summaryRes.Data || {};\n var summary = qdList(sd.summary || sd.Summary);\n for (var i = 0; i < summary.length; i++) {\n var item = summary[i] || {};\n var pid = Number(\n item.ParagraphId || item.paragraphId || item.paragraph_id ||\n item.ParaId || item.paraId || 0\n );\n if (pid === -1) {\n count = Number(\n item.CommentCount || item.commentCount || item.TextCount ||\n item.textCount || item.Count || item.count || 0\n );\n hot = Number(item.HasHotComment || item.hasHotComment || 0) !== 0;\n break;\n }\n }\n if (count > 0) bubble = createSvg.call(this, count, bid, cid, -1, hot, true, \"dp\");\n } catch(e) {\n bubble = \"\";\n }\n}\ntext + bubble;"
},
"ruleSearch": {
"author": "author",
"bookList": "$.data.books[*]",
"bookUrl": "@js:\ngetApiUrl(`\/${java.getString('type')}\/info`, { novelId: java.getString('id')})",
"checkKeyWord": "捞尸人",
"coverUrl": "cover",
"intro": "$.desc\n@js:\n\" \" + result",
"kind": "{{$.status}}\n{{$.category}}\n{{$.subCategory}}",
"lastChapter": "lastChapter",
"name": "name",
"wordCount": "wordsCount"
},
"ruleToc": {
"chapterList": "<js>\n(function() {\nlet novelId = java.hexDecodeToString(result)\nlet type = JSON.parse(baseUrl.slice(baseUrl.indexOf('{'))).type\n\nif (type == 'novel') {\n book.type = 8\n let loginInfo = JSON.parse(source.getLoginInfo() || '{}')\n loginInfo.书籍ID = novelId\n source.putLoginInfo(JSON.stringify(loginInfo))\n} else if (type == 'comic') {\n book.type = 64\n} else if (type == 'audio') {\n book.type = 32\n}\n\n\/\/ 主目录和普通章节仍走 qd.vv9v.cn;彩蛋只走 qdApi\/qdList。\nlet catalog = requestApiUrl(`\/${type}\/catalog`, { novelId }) || []\n\nlet midpageEnabled = false\nlet midpageEntries = []\ntry {\n let m = source.getLoginInfoMap ? source.getLoginInfoMap() : null\n let v = m && (m.get ? m.get(\"\\u5f69\\u86cb\\u7ae0\\u5f00\\u5173\") : m[\"\\u5f69\\u86cb\\u7ae0\\u5f00\\u5173\"])\n midpageEnabled = type === 'novel' && v != null && String(v) === \"✅\"\n let listFn = typeof qdList === 'function' ? qdList : function(x) { return Array.isArray(x) ? x : [] }\n if (midpageEnabled && typeof qdApi === 'function') {\n let midRes = qdApi.call(this, 'midpage_chapters', { book_id: novelId }) || {}\n let midData = midRes.data || midRes.Data || (midRes.raw && (midRes.raw.Data || midRes.raw.data)) || {}\n let midpageList = listFn(midData.Chapters || midData.chapters || midData.DataList || midData.MidpageChapters || midData.MidPageChapters)\n for (let mp of midpageList) {\n mp = mp || {}\n let targetId = mp.ChapterId || mp.C || mp.chapterId\n if (!targetId) continue\n let eggs = listFn(mp.MidpageList || mp.MidPageList || mp.PageList || mp.Pages || mp.List)\n if (!eggs.length) continue\n for (let egg of eggs) {\n try {\n egg = egg || {}\n let cfg = egg.MidPageConfig || egg.MidpageConfig || {}\n let eggName = egg.MidPageName || egg.MidpageName || egg.Title || egg.RoleName || cfg.PageName || cfg.Title || \"彩蛋内容\"\n eggName = String(eggName).replace(\/[\\r\\n]+\/g, ' ').replace(\/^彩蛋章[ ·]*\/, '')\n let midpageId = egg.PageId || egg.MidPageId || egg.MidpageId || cfg.PageId || cfg.MidPageId || ''\n if (!midpageId) continue\n let chapterId = String(targetId)\n midpageId = String(midpageId)\n let eggKey = 'midpage:' + String(novelId) + ':' + chapterId + ':' + midpageId\n let eggUrl = `data:;base64,${java.base64Encode(String(eggKey))},{\"type\":\"novel\",\"novelId\":\"${String(novelId)}\",\"chapterId\":\"${chapterId}\",\"qimoType\":\"midpage\",\"midpageId\":\"${midpageId}\"}`\n midpageEntries.push({\n id: eggKey,\n name: '彩蛋章 · ' + eggName,\n url: String(eggUrl),\n info: '官方插图\/视频',\n isVolume: false,\n isVip: false,\n isPay: false\n })\n } catch (entryError) {\n try { java.log('忽略异常彩蛋数据: ' + entryError.message) } catch (_) {}\n }\n }\n }\n }\n} catch (e) {\n midpageEnabled = false\n midpageEntries = []\n try { java.log('彩蛋 qdApi 处理失败,忽略彩蛋:' + e.message) } catch (_) {}\n}\n\nlet cache = (requestApiUrl(`\/${type}\/cache`, { novelId }) || []).reduce((acc, d) => {\n acc[d.chapId] = d\n return acc\n}, {})\n\nfunction fmtTime(timestamp) {\n let date = new Date(timestamp)\n let year = date.getFullYear()\n let month = String(date.getMonth() + 1).padStart(2, '0')\n let day = String(date.getDate()).padStart(2, '0')\n let hours = String(date.getHours()).padStart(2, '0')\n let minutes = String(date.getMinutes()).padStart(2, '0')\n return `${year}-${month}-${day} ${hours}:${minutes}`\n}\n\nlet chaps = []\nif (type === 'novel') {\n for (let volumeIndex = 0; volumeIndex < catalog.length; volumeIndex++) {\n let volume = catalog[volumeIndex] || {}\n let volumeKey = 'volume:' + String(novelId) + ':' + String(volumeIndex)\n chaps.push({\n id: volumeKey,\n name: volume.volumeName,\n url: `data:;base64,${java.base64Encode(volumeKey)},{\"type\":\"volume\"}`,\n isVolume: true\n })\n for (let chapter of (volume.chapters || [])) {\n let { chapId, chapName, charCount, isVip, isBuy, displayTime } = chapter\n let isShare = cache[chapId]\n chaps.push({\n id: chapId,\n name: (isVip ? (isBuy ? ' 🔑 ' : isShare ? ' 🍋 ' : '') : '') + chapName,\n url: `data:;base64,${java.base64Encode(chapId)},{\"type\":\"${type}\",\"novelId\":\"${novelId}\"}`,\n info: `${fmtTime(displayTime)} | ${charCount || 0}字 ${isShare ? ' 🍋 ' : isBuy ? ' 🔑 ' : ''}`,\n isVip: !isShare && isVip,\n isPay: !!isBuy,\n })\n\n }\n }\n}\n\nif (midpageEnabled && midpageEntries.length) {\n let midpageVolumeKey = 'volume:' + String(novelId) + ':midpage'\n chaps.push({\n id: midpageVolumeKey,\n name: '彩蛋章',\n url: `data:;base64,${java.base64Encode(midpageVolumeKey)},{\"type\":\"volume\"}`,\n isVolume: true\n })\n chaps = chaps.concat(midpageEntries)\n}\n\nreturn JSON.stringify(chaps)\n})();\n<\/js>\n$.[*]",
"chapterName": "$.name",
"chapterUrl": "$.url",
"isPay": "$.isPay",
"isVip": "$.isVip",
"isVolume": "$.isVolume",
"updateTime": "$.info"
},
"searchUrl": "@js:\nlet keyword = key\nlet type = 'novel'\nlet s = undefined\nif (\/^g(:|:)\/i.test(keyword)) {\n keyword = keyword.slice(2).trim()\n} else if (\/^a(:|:)\/i.test(keyword)) {\n keyword = keyword.slice(2).trim()\n type = 'audio'\n} else if (\/^c(:|:)\/i.test(keyword)) {\n keyword = keyword.slice(2).trim()\n type = 'comic'\n} else {\n s = 1\n}\ngetApiUrl(`\/${type}\/search`, { keyword, s, page })",
"variableComment": "于此处填写共享Token",
"weight": 0
}