书旗小说本地源(同人)
https://ocean.shuqireader.com#1
ah-fa-fa (14292)4天前
更新tg群组链接,被盗了。修复了段评和章评只能看50个上限的问题,现在可以完整阅读全部。免费源!书旗纯本地小说书源,除了评论走服务器。如需看评论请看源注释加群或tg,群公告和tg群置顶都有评论apikey,填入保存刷新即可看段评和章评。书评懒得写,有时间再说。
{
"bookSourceComment": "书旗评论api密码加群或者tg拿,免费!\n纯本地书旗源,免费看全部,评论走服务器\n1045801523群号\n纯免费,盗版源\ntg:https:\/\/t.me\/+1F9ippiIxbVmZmM1",
"bookSourceGroup": "api,shuqi",
"bookSourceName": "书旗小说本地源(同人)",
"bookSourceType": 0,
"bookSourceUrl": "https:\/\/ocean.shuqireader.com#1",
"customButton": false,
"customOrder": -1,
"enabled": true,
"enabledCookieJar": false,
"enabledExplore": true,
"eventListener": false,
"exploreUrl": "@js:\nsort=[];\npush=(title,url,type1,type2)=>sort.push({\n\t\ttitle: title,\n\t\turl: url,\n\t\tstyle: {\n\t\t\t\tlayout_flexGrow: type1,\n\t\t\t\tlayout_flexBasisPercent: type2\n\t\t\t}\n\t});\n\t\n \n\t\t\n\t\tfunction category(groupKey) {\t\t\t\t\t\t\t\n\t\t\t params = \"groupKey=\"+groupKey+\"&source=1\";\n path = \"\/api\/bcsbizai\/category\/column\/allitems\";\n url = GetUrl(path, params);\t\t\t\n return JSON.parse(java.ajax(url)).data.items; \t\t\n\t\t}\n\n\nconst categorizedData = (i) => {\n return category(i).reduce((acc, curr) => {\n const { itemKey, itemSubType, itemName, itemId } = curr; \n const originalKey = itemSubType.includes(itemKey) ? itemKey : itemSubType;\n iTag = itemKey == \"TAG\"?\"tagIds\":itemSubType.includes(\"SECOND\")?\"secondCategoryId\":\"firstCategoryId\";\n \n \/\/ 键名映射规则\n const keyMap = {\n \"CATEGORY\": \"分类\",\n \"MAIN_LINE\": \"题材\",\n \"PLOT\": \"情节\",\n \"ROLE\": \"角色\",\n \"STYLE\": \"风格\"\n };\n \/\/ 应用映射后的键名\n const categoryKey = keyMap[originalKey] || originalKey;\n \n \/\/ 检查分类是否已经存在\n let category = acc.find(item => item[0] === categoryKey);\n \/\/ 如果分类不存在,创建新的分类项\n if (category) {\n category[1].push([itemName, itemId, iTag]);\n } else {\n acc.push([categoryKey, [[itemName, itemId, iTag]]]);\n }\n return acc;\n }, []);\n};\n\t\t\n\t\tcategoryList = (i) =>{\n \t categoryList = categorizedData(i).map([title,list]=>{\t\t\t\n\t\t push(title, null, 1, 1);\n\t\t \n\t list.map(([title,itemId, iTag],index)=>{\t\t\n\t url = `@js:\n params = \"curPage=\"+page+\"&pageSize=10&${iTag}=${itemId}\";\n \n path = \"\/api\/bcsbizai\/xapi\/categoryAndTag\/books\/h5\";\n url = GetUrl(path, params);`;\n\t\t\t\tif(index +1<= list.length - list.length%3)\n\t\t\t {\tpush(title, url, 1, 0.25);}\n\t\t\t\telse{ push(title, url, 0, 0.29);}\n\t\t\t\t});\n\t});\n\treturn categoryList;\n} \t \n\t\t\n\t\t\n\t\teval(String(source.loginUrl));\n\t\tlogin();\n var v = source.getVariable();\n var obj = JSON.parse(v)[0].fl[0];\n var keys = Object.keys(obj);\n\t\tkeys.forEach(key => {\n\t\t\t\tname = key;\n\t\t \ttype = obj[key]});\n\t push(name, null, 1, 1);\n categoryList(type);\n\nJSON.stringify(sort);",
"header": "{\n \"accept\": \"application\/json, text\/plain, *\/*\",\n \"user-agent\": \"Mozilla\/5.0 (Linux; Android 9; Pixel 4 Build\/PQ3A.190801.002; wv) AppleWebKit\/537.36 (KHTML, like Gecko) Version\/4.0 Chrome\/81.0.4044.117 Mobile Safari\/537.36\",\n \"content-type\": \"application\/x-www-form-urlencoded\",\n \"origin\": \"https:\/\/t.shuqi.com\",\n \"x-requested-with\": \"mark.via.gp\",\n \"sec-fetch-site\": \"cross-site\",\n \"sec-fetch-mode\": \"cors\",\n \"sec-fetch-dest\": \"empty\",\n \"accept-language\": \"zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7\"\n}",
"jsLib": "let timeoffset = 2 * 60 * 60 * 1000;\n\nfunction sortedStr(str, i) {\t\n \t let obj = {};\n let arr = String(str).split('&');\n arr.forEach(item => {\n let [key, value] = item.split('=');\n obj[key] = value;\n });\n var result = Object.keys(obj).sort();\n if(\/platform=0\/.test(str)){\n result = result\n .map(key=>key + \"=\" + obj[key]).join(\"&\");\n }else{\n \tresult = result\n \t.map(key=> obj[key]).join(\"\");\n } \n return result\n}\n\nfunction Token() {\t\n \t const { java, source } = this;\n let origin = \"https:\/\/t.shuqi.com\"; \t \n \tvar token = java.get(origin,{}).cookies()\n .toString().match(\/shuqi_token=([^,]*)\/)[1];\n var head = {\n \t\t\"authorization\": \"Bearer \"+token\n \t}; \t\n token = source.putLoginHeader(\n \tJSON.stringify(head));\n return token\n}\n\nfunction GetUrl(path, params, sourceUrl) {\n \t const { java, source } = this;\n \t let sourceUrl = sourceUrl || \"https:\/\/ocean.shuqireader.com\";\n var v = source.getVariable();\n try{\n var userId = JSON.parse(v)[0].uid[0].userId;\n }catch(e){\n \t var userId = '12345678';\n \t}\n \n var t = Date.now();\n var time = ~~(t \/ 1e3);\n \n var gateway = \"sq_h5_gateway\";\n var params0 = \"sqSv=1.0\";\n \t var params1 = `user_id=${userId}×tamp=${time}`;\n \t \n \t \n \t \n \t if (\/info\/.test(path)) {\n \t var skey = \"37e81a9d8f02596e1b895d07c171d5c9\";\n \t var params = params1 + \"&\" + params;\t \n \t var sign = java.md5Encode(sortedStr(params) + skey); \n var body = params + \"&platform=0&needFreeStack=1&sign=\" + sign;\n \t \n \t}else{\n \t\tvar skey = \"eefc4798f28ea41622487ad80ef7e81c\";\n \t\tvar params = params0 + \"&\" + params1 + \"&platform=0&\" + params;\n \t var sign = java.md5Encode(sortedStr(params) + \"&skey=\" + skey );\n \t var body = params + \"&key=\" + gateway + \"&sign=\" + sign;\n \n \t\t}\n \n if (source.getLoginHeaderMap() == null) {\n \t \tthis.Token();\n }\n \n var url = sourceUrl + path + \",\" + JSON.stringify({\n \"body\": body,\n \"method\": \"POST\"\n});\n\n return url;\n}\n\n\/\/ SQ_COMMENT_API_V1_BEGIN\nvar SQ_COMMENT_API_BASE = \"https:\/\/shuqi.aadcn.cn\";\nvar SQ_COMMENT_API_KEY = \"tongrenshuqi134679\";\nvar _sqRenderExtKey = \"\";\nvar _sqRenderExtValue = null;\nvar _sqMetaMapCache = null;\nvar _sqShortMapCache = null;\n\nfunction sqSource() {\n try { if (this && this.source) return this.source; } catch(e) {}\n try { if (typeof source !== \"undefined\") return source; } catch(e2) {}\n return null;\n}\n\nfunction sqJava() {\n try { if (this && this.java) return this.java; } catch(e) {}\n try { if (typeof java !== \"undefined\") return java; } catch(e2) {}\n return null;\n}\n\nfunction sqLoginValue(key, defaultValue) {\n try {\n var s = sqSource.call(this);\n var map = s && s.getLoginInfoMap ? s.getLoginInfoMap() : null;\n var value = map && (map.get ? map.get(key) : map[key]);\n if (value !== null && value !== undefined && String(value).trim() !== \"\") return String(value);\n } catch(e) {}\n return defaultValue == null ? \"\" : String(defaultValue);\n}\n\nfunction sqToggle(key, defaultValue) {\n var value = sqLoginValue.call(this, key, \"\");\n if (!value) return !!defaultValue;\n value = String(value).trim().toLowerCase();\n return value === \"✅\" || value === \"1\" || value === \"true\" || value === \"开\" || value === \"开启\";\n}\n\nfunction sqCommentConfig() {\n var style = sqLoginValue.call(this, \"◎ 模板\", \"起点\");\n var templateKey = String(style).replace(\"样式\", \"◎ 气泡\");\n return {\n paragraph: sqToggle.call(this, \"段评开关\", false),\n chapter: sqToggle.call(this, \"章评开关\", false),\n color: sqLoginValue.call(this, \"段评气泡颜色\", \"#666666\"),\n colorSplit: sqToggle.call(this, \"段评分色\", false),\n splitColor: sqLoginValue.call(this, \"分色气泡颜色\", \"#e23b3b\"),\n splitCount: Number(sqLoginValue.call(this, \"分色评论数\", \"45\")) || 45,\n hotColor: sqLoginValue.call(this, \"热门气泡颜色\", \"#f06060\"),\n theme: sqLoginValue.call(this, \"评论主题\", \"自动\"),\n height: sqLoginValue.call(this, \"评论窗口高度\", \"80\"),\n bubbleStyle: style,\n bubbleSize: sqLoginValue.call(this, \"◎ 大小\", \"text\"),\n bubbleTemplate: sqLoginValue.call(this, templateKey, \"\")\n };\n}\n\nfunction sqApiKey() {\n return sqLoginValue.call(this, \"书旗评论 API Key\", \"\") || SQ_COMMENT_API_KEY;\n}\n\nfunction sqPost(path, body) {\n var j = sqJava.call(this);\n if (!j) return { ok: false, error: \"java unavailable\" };\n var payload = body || {};\n payload.apiKey = sqApiKey.call(this);\n var option = {\n method: \"POST\",\n body: JSON.stringify(payload),\n headers: { \"Content-Type\": \"application\/json\", \"Accept\": \"application\/json\" }\n };\n try {\n var raw = j.ajax(SQ_COMMENT_API_BASE + path + \",\" + JSON.stringify(option));\n var text = String(raw || \"{}\").replace(\/^\\uFEFF\/, \"\").trim();\n return JSON.parse(text || \"{}\");\n } catch(e) {\n try { j.log(\"书旗评论接口请求失败 \" + path + \": \" + e); } catch(_) {}\n return { ok: false, error: String(e) };\n }\n}\n\nfunction sqList(value) {\n if (!value) return [];\n if (Array.isArray(value)) return value;\n if (value.size && value.get) {\n var result = [];\n for (var i = 0; i < value.size(); i++) result.push(value.get(i));\n return result;\n }\n return [];\n}\n\nfunction sqRenderExt(bookId, chapterId) {\n var key = String(bookId || \"\") + \":\" + String(chapterId || \"\");\n if (key && key === _sqRenderExtKey && _sqRenderExtValue) return _sqRenderExtValue;\n var response = sqPost.call(this, \"\/v1\/reader\/render-ext\", {\n bookId: String(bookId || \"\"),\n chapterId: String(chapterId || \"\")\n });\n if (response && response.ok) {\n _sqRenderExtKey = key;\n _sqRenderExtValue = response;\n }\n return response || { ok: false };\n}\n\nfunction sqParagraphList(response) {\n try {\n var outer = response && response.data || {};\n var data = outer.data || outer.Data || {};\n var ext = data.bookChapterExtInfo || data.BookChapterExtInfo || {};\n return sqList(ext.paragraphList || ext.ParagraphList);\n } catch(e) {\n return [];\n }\n}\n\nfunction sqCommentList(response) {\n try {\n var outer = response && response.data || {};\n var data = outer.data || outer.Data || {};\n return sqList(data.commentList || data.CommentList);\n } catch(e) {\n return [];\n }\n}\n\nfunction sqReadMap(key, cacheName) {\n if (cacheName === \"meta\" && _sqMetaMapCache !== null) return _sqMetaMapCache;\n if (cacheName === \"short\" && _sqShortMapCache !== null) return _sqShortMapCache;\n var map = {};\n try {\n var j = sqJava.call(this);\n var raw = j && j.get ? j.get(key) : \"\";\n if (raw) map = JSON.parse(String(raw)) || {};\n } catch(e) { map = {}; }\n if (cacheName === \"meta\") _sqMetaMapCache = map;\n if (cacheName === \"short\") _sqShortMapCache = map;\n return map;\n}\n\nfunction sqLookupChapterMap(map, url) {\n map = map || {};\n var raw = String(url || \"\");\n var optionAt = raw.lastIndexOf(\",{\");\n if (optionAt > 0) raw = raw.substring(0, optionAt);\n var candidates = [raw];\n var queryAt = raw.indexOf(\"?\");\n if (queryAt >= 0) candidates.push(raw.substring(queryAt));\n var count = candidates.length;\n for (var i = 0; i < count; i++) {\n candidates.push(candidates[i].replace(\/0$\/, \"1\"));\n candidates.push(candidates[i].replace(\/1$\/, \"0\"));\n }\n for (var j = 0; j < candidates.length; j++) {\n if (Object.prototype.hasOwnProperty.call(map, candidates[j])) return map[candidates[j]];\n }\n return null;\n}\n\nfunction sqGetShortMap(ctx) {\n return sqReadMap.call(ctx || this, \"sqShortMap\", \"short\");\n}\n\nfunction sqLookupMap(map, base) {\n var value = sqLookupChapterMap(map, base);\n return value == null ? \"\" : String(value);\n}\n\nfunction sqGetChapterMeta(url) {\n var raw = String(url || \"\");\n try {\n var bidMatch = raw.match(\/[#&?]sqBid=([^&#]+)\/);\n var cidMatch = raw.match(\/[#&?]sqCid=([^&#]+)\/);\n if (bidMatch && cidMatch) {\n return {\n bookId: decodeURIComponent(bidMatch[1]),\n chapterId: decodeURIComponent(cidMatch[1])\n };\n }\n } catch(eHash) {}\n var value = sqLookupChapterMap(sqReadMap.call(this, \"sqMetaMap\", \"meta\"), raw);\n if (!value) return null;\n if (typeof value === \"string\") {\n try { value = JSON.parse(value); } catch(e) { return null; }\n }\n return value && value.bookId && value.chapterId ? value : null;\n}\n\nfunction sqGetChapterShortUrl(url) {\n var value = sqLookupChapterMap(sqReadMap.call(this, \"sqShortMap\", \"short\"), url);\n return value == null ? \"\" : String(value);\n}\n\nfunction sqSafeJs(value) {\n return String(value == null ? \"\" : value)\n .replace(\/\\\\\/g, \"\\\\\\\\\").replace(\/'\/g, \"\\\\'\").replace(\/[\\r\\n]+\/g, \"\");\n}\n\nfunction sqQuoteText(value) {\n var text = String(value == null ? \"\" : value)\n .replace(\/<br\\s*\\\/?>(?=.)\/gi, \"\\n\")\n .replace(\/<img\\b[^>]*>\/gi, \"\")\n .replace(\/<[^>]+>\/g, \"\")\n .replace(\/ \/gi, \" \")\n .replace(\/&\/gi, \"&\")\n .replace(\/"\/gi, '\"')\n .replace(\/'\/gi, \"'\")\n .replace(\/<\/gi, \"<\")\n .replace(\/>\/gi, \">\")\n .replace(\/[\\s\\u00a0\\u3000]+\/g, \" \")\n .trim();\n if (text.length > 160) text = text.slice(0, 160) + \"…\";\n return text;\n}\nfunction sqBubbleData(count, bookId, chapterId, paragraphId, hot, kind, paraText) {\n var j = sqJava.call(this);\n var number = Number(count) || 0;\n if (!j || !j.base64Encode || number <= 0) return \"\";\n var cfg = sqCommentConfig.call(this);\n var color = cfg.color;\n if (cfg.colorSplit) {\n if (hot) color = cfg.hotColor;\n else if (number >= cfg.splitCount) color = cfg.splitColor;\n }\n if (!\/^#[0-9a-fA-F]{6}$\/.test(color)) color = \"#666666\";\n var displayText = number > 999 ? \"999\" : String(number);\n var font = \"Arial\";\n var template = String(cfg.bubbleTemplate || \"\").trim();\n var svg;\n if (cfg.bubbleStyle !== \"起点\" && template.indexOf(\"<svg\") >= 0) {\n svg = template\n .replace(\/\\$\\{qpyscolor\\}\/g, color)\n .replace(\/\\$\\{qpwzcolor\\}\/g, color)\n .replace(\/\\$\\{font\\}\/g, font)\n .replace(\/\\$\\{displayText\\}\/g, displayText);\n } else {\n svg = '<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 var click;\n if (kind === \"chapter\") {\n click = \"showSqChapterComments('\" + sqSafeJs(bookId) + \"','\" + sqSafeJs(chapterId) + \"','\" + sqSafeJs(paragraphId) + \"','\" + sqSafeJs(sqQuoteText(paraText)) + \"')\";\n } else {\n click = \"showSqComments('\" + sqSafeJs(bookId) + \"','\" + sqSafeJs(chapterId) + \"','\" + sqSafeJs(paragraphId) + \"','\" + sqSafeJs(sqQuoteText(paraText)) + \"')\";\n }\n return \"data:image\/svg+xml;base64,\" + j.base64Encode(svg) + \",\" + JSON.stringify({\n style: cfg.bubbleSize === \"TEXT\" ? \"TEXT\" : \"text\",\n type: \"sq\",\n click: click\n });\n}\n\nfunction sqBubbleImage(count, bookId, chapterId, paragraphId, hot, paraText) {\n var data = sqBubbleData.call(this, count, bookId, chapterId, paragraphId, hot, \"paragraph\", paraText);\n return data ? '<img src=\"' + data.replace(\/\"\/g, \""\") + '\">' : \"\";\n}\n\nfunction sqDecorateContent(content, bookId, chapterId, chapterTitle) {\n var cfg = sqCommentConfig.call(this);\n var sourceRef = sqSource.call(this);\n var titleKey = \"sq_title_bubble_\" + String(chapterId || \"\");\n function saveTitle(value) {\n try { if (sourceRef && sourceRef.put && chapterId) sourceRef.put(titleKey, String(value || \"\")); } catch(e) {}\n }\n if ((!cfg.paragraph && !cfg.chapter) || !bookId || !chapterId) {\n saveTitle(\"\");\n return content;\n }\n\n var response = sqRenderExt.call(this, bookId, chapterId);\n var paragraphs = sqParagraphList(response);\n if (!paragraphs.length) {\n saveTitle(\"\");\n return content;\n }\n\n var titleItem = null;\n var bodyItems = [];\n for (var p = 0; p < paragraphs.length; p++) {\n var item = paragraphs[p] || {};\n var order = Number(item.orderId !== undefined ? item.orderId : item.OrderId);\n var count = Number(item.commentCount || item.CommentCount || 0);\n if (count <= 0) continue;\n if (order === 0) titleItem = item;\n else if (order > 0) bodyItems.push(item);\n }\n\n if (cfg.chapter && titleItem) {\n var titleCount = Number(titleItem.commentCount || titleItem.CommentCount || 0);\n var titlePid = titleItem.paragraphId || titleItem.ParagraphId || \"p000\";\n var titleText = String(titleItem.text || titleItem.Text || titleItem.summaryText || titleItem.SummaryText || chapterTitle || \"\");\n saveTitle(sqBubbleData.call(this, titleCount, bookId, chapterId, titlePid, !!(titleItem.isHotParagraph || titleItem.IsHotParagraph), \"chapter\", titleText));\n } else {\n saveTitle(\"\");\n }\n if (!cfg.paragraph) return content;\n\n var pieces = String(content || \"\").split(\/(<br\\s*\\\/?>)\/i);\n var textIndexes = [];\n for (var i = 0; i < pieces.length; i++) {\n if (!\/^<br\\s*\\\/?>$\/i.test(pieces[i]) && String(pieces[i]).trim()) textIndexes.push(i);\n }\n for (var b = 0; b < bodyItems.length; b++) {\n var bodyItem = bodyItems[b] || {};\n var bodyOrder = Number(bodyItem.orderId !== undefined ? bodyItem.orderId : bodyItem.OrderId);\n var bodyCount = Number(bodyItem.commentCount || bodyItem.CommentCount || 0);\n var bodyPid = bodyItem.paragraphId || bodyItem.ParagraphId || (\"p\" + String(bodyOrder));\n var target = textIndexes[bodyOrder - 1];\n if (target !== undefined) {\n var bodyText = String(bodyItem.text || bodyItem.Text || bodyItem.summaryText || bodyItem.SummaryText || pieces[target] || \"\");\n pieces[target] += sqBubbleImage.call(this, bodyCount, bookId, chapterId, bodyPid, !!(bodyItem.isHotParagraph || bodyItem.IsHotParagraph), bodyText);\n }\n }\n return pieces.join(\"\");\n}\n\nfunction sqEscapeHtml(value) {\n return String(value == null ? \"\" : value)\n .replace(\/&\/g, \"&\").replace(\/<\/g, \"<\").replace(\/>\/g, \">\")\n .replace(\/\"\/g, \""\").replace(\/'\/g, \"'\");\n}\n\nfunction sqRichText(comment) {\n var raw = String(comment && (comment.richText || comment.text) || \"\");\n var images = [];\n raw = raw.replace(\/<img\\b[^>]*>\/gi, function(tag) {\n var match = tag.match(\/src=[\"'](data:image\\\/(?:webp|png|gif);base64,[A-Za-z0-9+\/=]+)[\"']\/i);\n if (!match) return \"\";\n var token = \"__SQ_EMOJI_\" + images.length + \"__\";\n images.push('<img class=\"emoji\" src=\"' + match[1] + '\" alt=\"\">');\n return token;\n });\n raw = sqEscapeHtml(raw).replace(\/\\r?\\n\/g, \"<br>\");\n for (var i = 0; i < images.length; i++) raw = raw.replace(\"__SQ_EMOJI_\" + i + \"__\", images[i]);\n return raw;\n}\n\nfunction sqFormatTime(value) {\n var time = Number(value || 0);\n if (!time) return \"\";\n try { return sqJava.call(this).timeFormatUTC(time, \"yyyy-MM-dd HH:mm\", 28800000); } catch(e) { return \"\"; }\n}\n\nfunction sqReplyHtml(reply) {\n var name = sqEscapeHtml(reply && reply.nickname || \"书友\");\n var target = reply && reply.repliedUserNickname ? \" 回复 \" + sqEscapeHtml(reply.repliedUserNickname) : \"\";\n return '<div class=\"reply\"><b>' + name + '<\/b>' + target + ':' + sqRichText(reply) + '<\/div>';\n}\n\nvar _sqReplyDomSeq = 0;\nfunction sqCommentCard(comment) {\n comment = comment || {};\n var avatar = String(comment.userPhoto || \"\");\n var replyData = comment.replies || {};\n var replies = sqList(replyData.commentList);\n var replyHtml = \"\";\n for (var i = 0; i < replies.length; i++) replyHtml += sqReplyHtml(replies[i]);\n var badges = comment.isAuthor ? '<span class=\"badge\">作者<\/span>' : \"\";\n if (comment.fanCard && comment.fanCard.levelName) badges += '<span class=\"badge muted\">' + sqEscapeHtml(comment.fanCard.levelName) + '<\/span>';\n\n var replyNum = Number(comment.replyNum || 0);\n var nextIndex = replyData.nextItemIndex;\n var hasMore = replyNum > replies.length && nextIndex !== null && nextIndex !== undefined && String(nextIndex) !== \"\";\n var safeMid = String(comment.mid || \"comment\").replace(\/[^A-Za-z0-9_-]\/g, \"_\");\n var replyTarget = \"sq_replies_\" + safeMid + \"_\" + (_sqReplyDomSeq++);\n var moreButton = hasMore\n ? '<button class=\"reply-more\" type=\"button\" data-target=\"' + replyTarget + '\" data-mid=\"' + sqEscapeHtml(comment.mid || \"\") + '\" data-cursor=\"' + sqEscapeHtml(nextIndex) + '\" data-author=\"' + sqEscapeHtml(comment.authorId || \"0\") + '\" onclick=\"sqLoadReplies(this)\">展开更多回复<\/button>'\n : \"\";\n\n return '<article class=\"comment\">' +\n '<div class=\"author\">' + (avatar ? '<img class=\"avatar\" src=\"' + sqEscapeHtml(avatar) + '\">' : '<span class=\"avatar fallback\"><\/span>') +\n '<div class=\"identity\"><div><b>' + sqEscapeHtml(comment.nickname || \"书友\") + '<\/b>' + badges + '<\/div><time>' + sqFormatTime.call(this, comment.pubTime) + '<\/time><\/div>' +\n '<span class=\"likes\">赞 ' + Number(comment.likes || 0) + '<\/span><\/div>' +\n '<div class=\"text\">' + sqRichText(comment) + '<\/div>' +\n ((replyHtml || moreButton) ? '<div class=\"replies\"><div id=\"' + replyTarget + '\">' + replyHtml + '<\/div>' + moreButton + '<\/div>' : '') +\n '<\/article>';\n}\n\nfunction sqCards(list) {\n list = sqList(list);\n if (!list.length) return '<div class=\"empty\">暂无评论<\/div>';\n var html = \"\";\n for (var i = 0; i < list.length; i++) html += sqCommentCard.call(this, list[i]);\n return html;\n}\n\nfunction sqBrowserConfig() {\n var cfg = sqCommentConfig.call(this);\n var height = Number(cfg.height || 80);\n if (height > 1) height = height \/ 100;\n if (!(height > 0 && height <= 1)) height = 0.8;\n return {\n state: 4,\n isHideable: true,\n heightPercentage: height,\n expandedCornersRadius: 16,\n dismissOnTouchOutside: true,\n widthPercentage: 1.0,\n maxWidth: -1,\n skipCollapsed: true,\n hardwareAccelerated: true,\n webViewInitialScale: 100\n };\n}\n\nfunction sqReplyBrowserScript() {\n return [\n '<script>',\n '(function(){',\n 'function replyNode(item){',\n 'var row=document.createElement(\"div\");row.className=\"reply dynamic-reply\";',\n 'var name=document.createElement(\"b\");name.textContent=String(item.nickname||\"书友\");row.appendChild(name);',\n 'var text=\"\";if(item.repliedUserNickname)text+=\" 回复 \"+item.repliedUserNickname;text+=\":\"+String(item.text||\"\");',\n 'row.appendChild(document.createTextNode(text));return row;',\n '}',\n 'window.sqLoadReplies=function(button){',\n 'if(!button||button.disabled)return;var cfg=window.sqReplyRequest||{};var box=document.getElementById(button.getAttribute(\"data-target\"));if(!box)return;',\n 'button.disabled=true;button.textContent=\"加载中...\";',\n 'try{',\n 'var body={apiKey:cfg.apiKey,bookId:String(cfg.bookId||\"\"),chapterId:String(cfg.chapterId||\"\"),mid:String(button.getAttribute(\"data-mid\")||\"\"),itemIndex:Number(button.getAttribute(\"data-cursor\")||0),size:20,authorId:String(button.getAttribute(\"data-author\")||\"0\")};',\n 'var option={method:\"POST\",body:JSON.stringify(body),headers:{\"Content-Type\":\"application\/json\",\"Accept\":\"application\/json\"}};',\n 'var raw=window.java.ajax(cfg.apiBase+\"\/v1\/comments\/chapter-replies,\"+JSON.stringify(option));var response=JSON.parse(String(raw||\"{}\").replace(\/^\\\\uFEFF\/,\"\"));',\n 'if(!response.ok)throw new Error(response.error||\"回复加载失败\");var outer=response.data||{},data=outer.data||{},list=data.commentList||[];',\n 'for(var i=0;i<list.length;i++)box.appendChild(replyNode(list[i]));',\n 'var next=data.nextItemIndex;if(next===null||next===undefined)next=response.pagination&&response.pagination.nextItemIndex;',\n 'var more=data.hasMore;if(more===null||more===undefined)more=response.pagination&&response.pagination.hasMore;',\n 'if(more&&list.length&&next!==null&&next!==undefined){button.setAttribute(\"data-cursor\",String(next));button.disabled=false;button.textContent=\"展开更多回复\";}else{button.disabled=true;button.className=\"reply-more ended\";button.textContent=\"已展开全部回复\";}',\n '}catch(error){button.disabled=false;button.textContent=\"加载失败,点击重试\";}',\n '};',\n '})();',\n '<\/script>'\n ].join(\"\\n\");\n}\n\nfunction sqBrowserPreload(bookId, chapterId, mode, paragraphId, quoteText) {\n return \"window.sqPageInit=\" + JSON.stringify({\n apiBase: SQ_COMMENT_API_BASE,\n apiKey: sqApiKey.call(this),\n bookId: String(bookId || \"\"),\n chapterId: String(chapterId || \"\"),\n paragraphId: String(paragraphId || \"\"),\n mode: String(mode || \"paragraph\"),\n quoteText: sqQuoteText(quoteText)\n }).replace(\/<\/g, \"\\\\u003c\") + \";try{window.java=java;}catch(e){}\";\n}\n\nfunction sqHtmlShell(title, subtitle, content, tabs) {\n var theme = sqCommentConfig.call(this).theme;\n var forced = theme === \"深色\" ? \"dark\" : (theme === \"浅色\" ? \"light\" : \"auto\");\n var css = ':root{--bg:#f5f5f5;--panel:#fff;--text:#202124;--muted:#73777f;--line:#e6e7e9;--accent:#df3f32;--reply:#f7f7f8}' +\n '@media(prefers-color-scheme:dark){:root{--bg:#111214;--panel:#1b1c1f;--text:#eceef1;--muted:#a3a7ae;--line:#303238;--accent:#ff7165;--reply:#24262b}}' +\n 'html.dark{--bg:#111214;--panel:#1b1c1f;--text:#eceef1;--muted:#a3a7ae;--line:#303238;--accent:#ff7165;--reply:#24262b}' +\n 'html.light{--bg:#f5f5f5;--panel:#fff;--text:#202124;--muted:#73777f;--line:#e6e7e9;--accent:#df3f32;--reply:#f7f7f8}' +\n '*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",\"PingFang SC\",\"Microsoft YaHei\",sans-serif}' +\n 'header{position:sticky;top:0;z-index:3;background:var(--panel);border-bottom:1px solid var(--line);padding:15px 16px 12px}h1{font-size:18px;margin:0 0 4px}header p{font-size:12px;color:var(--muted);margin:0}' +\n '.tabs{display:flex;gap:22px;margin-top:12px}.tab{border:0;background:none;color:var(--muted);font-size:14px;padding:6px 0;border-bottom:2px solid transparent}.tab.active{color:var(--accent);border-color:var(--accent);font-weight:600}' +\n 'main{max-width:760px;margin:0 auto;padding:10px}.comment,.section{background:var(--panel);border:1px solid var(--line);border-radius:8px;margin-bottom:8px;padding:14px}' +\n '.author{display:flex;align-items:center;gap:10px}.avatar{width:38px;height:38px;border-radius:50%;object-fit:cover;background:var(--reply)}.identity{min-width:0;flex:1}.identity b{font-size:14px}.identity time{display:block;color:var(--muted);font-size:11px;margin-top:3px}.likes{color:var(--muted);font-size:12px}.text{font-size:15px;line-height:1.72;margin:11px 0 2px;word-break:break-word}.emoji{width:24px;height:24px;vertical-align:middle}' +\n '.badge{font-size:10px;color:#fff;background:var(--accent);border-radius:3px;padding:1px 4px;margin-left:5px}.badge.muted{background:#767b85}.replies{background:var(--reply);border-radius:6px;padding:8px 10px;margin-top:10px}.reply{font-size:13px;line-height:1.65;margin:5px 0}.reply b{color:var(--accent)}.more,.empty{color:var(--muted);font-size:12px;margin-top:5px}.empty{text-align:center;padding:40px 10px}' +\n '.reply-more{min-height:30px;margin-top:8px;padding:4px 13px;border:0;border-radius:15px;background:var(--panel);color:var(--accent);font-size:12px;font-weight:600}.reply-more:disabled{opacity:.7}.reply-more.ended{color:var(--muted)}' +\n '.section h2{font-size:14px;margin:0 0 10px;color:var(--accent)}.section .comment{border:0;border-top:1px solid var(--line);border-radius:0;margin:0;padding-left:0;padding-right:0}.section .comment:first-of-type{border-top:0}' +\n '.panel[hidden]{display:none}';\n var tabScript = tabs ? '<script>function sqTab(name){var ps=document.querySelectorAll(\".panel\"),bs=document.querySelectorAll(\".tab\");for(var i=0;i<ps.length;i++)ps[i].hidden=ps[i].id!==name;for(var j=0;j<bs.length;j++)bs[j].className=\"tab\"+(bs[j].getAttribute(\"data-tab\")===name?\" active\":\"\")}<\/script>' : \"\";\n return '<!doctype html><html class=\"' + forced + '\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"><style>' + css + '<\/style><\/head><body>' +\n '<header><h1>' + sqEscapeHtml(title) + '<\/h1><p>' + sqEscapeHtml(subtitle) + '<\/p>' + (tabs || \"\") + '<\/header><main>' + content + '<\/main>' + tabScript + sqReplyBrowserScript() + '<\/body><\/html>';\n}\n\nfunction sqLiveCommentHtml() {\n return [\n '<!doctype html><html><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">',\n '<style>',\n ':root{--bg:#f5f5f5;--panel:#fff;--text:#202124;--muted:#73777f;--line:#e6e7e9;--accent:#df3f32;--reply:#f7f7f8}@media(prefers-color-scheme:dark){:root{--bg:#111214;--panel:#1b1c1f;--text:#eceef1;--muted:#a3a7ae;--line:#303238;--accent:#ff7165;--reply:#24262b}}',\n '*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",\"PingFang SC\",\"Microsoft YaHei\",sans-serif}header{position:sticky;top:0;z-index:3;background:var(--panel);border-bottom:1px solid var(--line);padding:14px 16px 10px}h1{font-size:18px;margin:0 0 4px}.quote{display:none;margin:8px 0 6px;padding:9px 12px 9px 14px;border-left:3px solid var(--accent);background:var(--reply);border-radius:0 6px 6px 0;color:var(--text);font-size:14px;line-height:1.65;white-space:pre-wrap;word-break:break-word}#subtitle{font-size:12px;color:var(--muted)}.tabs{display:flex;gap:22px;margin-top:10px}.tab{border:0;background:none;color:var(--muted);font-size:14px;padding:6px 0;border-bottom:2px solid transparent}.tab.active{color:var(--accent);border-color:var(--accent);font-weight:600}main{max-width:760px;margin:0 auto;padding:10px}',\n '.comment,.section{background:var(--panel);border:1px solid var(--line);border-radius:8px;margin-bottom:8px;padding:14px}.author-row{display:flex;align-items:center;gap:10px}.avatar{width:38px;height:38px;border-radius:50%;object-fit:cover;background:var(--reply)}.identity{min-width:0;flex:1}.identity b{font-size:14px}.identity time{display:block;color:var(--muted);font-size:11px;margin-top:3px}.likes{color:var(--muted);font-size:12px}.text{font-size:15px;line-height:1.72;margin:11px 0 2px;word-break:break-word}.emoji{width:24px;height:24px;vertical-align:middle}.replies{background:var(--reply);border-radius:6px;padding:8px 10px;margin-top:10px}.reply{font-size:13px;line-height:1.65;margin:5px 0}.reply b{color:var(--accent)}.reply-more{min-height:30px;margin-top:8px;padding:4px 13px;border:0;border-radius:15px;background:var(--panel);color:var(--accent);font-size:12px;font-weight:600}.reply-more:disabled{opacity:.7}.section h2{font-size:14px;margin:0 0 10px;color:var(--accent)}.section .comment{border:0;border-top:1px solid var(--line);border-radius:0;margin:0;padding-left:0;padding-right:0}.empty,.loading,.error{text-align:center;color:var(--muted);padding:34px 10px}.error{color:#b42318}',\n '.skeleton{height:112px;border-radius:8px;margin-bottom:8px;background:linear-gradient(90deg,var(--panel),var(--reply),var(--panel));background-size:200% 100%;animation:shine 1.2s infinite}@keyframes shine{to{background-position:-200% 0}}',\n '<\/style><\/head><body>',\n '<header><h1 id=\"title\">段评<\/h1><div id=\"quote\" class=\"quote\"><\/div><div id=\"subtitle\">加载中...<\/div><div id=\"tabs\" class=\"tabs\"><button class=\"tab active\" data-order=\"hot\">最热<\/button><button class=\"tab\" data-order=\"new\">最新<\/button><\/div><\/header>',\n '<main id=\"list\"><div class=\"skeleton\"><\/div><div class=\"skeleton\"><\/div><\/main>',\n '<script>',\n '(function(){',\n 'var init=window.sqPageInit||{},API=String(init.apiBase||\"\"),KEY=String(init.apiKey||\"\"),BID=String(init.bookId||\"\"),CID=String(init.chapterId||\"\"),PID=String(init.paragraphId||\"\"),MODE=String(init.mode||\"paragraph\"),QUOTE=String(init.quoteText||\"\"),seq=0;',\n 'var list=document.getElementById(\"list\"),title=document.getElementById(\"title\"),quote=document.getElementById(\"quote\"),subtitle=document.getElementById(\"subtitle\"),tabs=document.getElementById(\"tabs\");if(QUOTE){quote.textContent=QUOTE;quote.style.display=\"block\";}',\n 'function esc(v){return String(v==null?\"\":v).replace(\/&\/g,\"&\").replace(\/<\/g,\"<\").replace(\/>\/g,\">\").replace(\/\"\/g,\""\");}',\n 'function rich(c){var r=String(c&&c.richText||\"\");if(r.indexOf(\"<img\")>=0)return r;return esc(c&&c.text||\"\");}',\n 'function api(path,body,done){body=body||{};body.apiKey=KEY;var payload=JSON.stringify(body),finished=false;function finish(err,value){if(finished)return;finished=true;done(err,value);}try{var xhr=new XMLHttpRequest();xhr.open(\"POST\",API+path,true);xhr.setRequestHeader(\"Content-Type\",\"application\/json\");xhr.onload=function(){try{var root=JSON.parse(String(xhr.responseText||\"{}\").replace(\/^\\\\uFEFF\/,\"\"));if(!root.ok)throw new Error(root.error||\"接口错误\");finish(null,root);}catch(e){finish(e);}};xhr.onerror=function(){fallback();};xhr.send(payload);}catch(e){fallback();}function fallback(){try{var option={method:\"POST\",body:payload,headers:{\"Content-Type\":\"application\/json\",\"Accept\":\"application\/json\"}};var raw=window.java.ajax(API+path+\",\"+JSON.stringify(option));var root=JSON.parse(String(raw||\"{}\"));if(!root.ok)throw new Error(root.error||\"接口错误\");finish(null,root);}catch(e2){finish(e2);}}}',\n 'function replyHtml(r){return \"<div class=\\\\\"reply\\\\\"><b>\"+esc(r.nickname||\"书友\")+\"<\/b>\"+(r.repliedUserNickname?(\" 回复 \"+esc(r.repliedUserNickname)):\"\")+\":\"+rich(r)+\"<\/div>\";}',\n 'function replyBox(c){var rd=c.replies||{},arr=rd.commentList||[],html=\"\",id=\"sq_reply_\"+(seq++),i;for(i=0;i<arr.length;i++)html+=replyHtml(arr[i]);var next=rd.nextItemIndex,total=Number(c.replyNum||0);var more=total>arr.length&&next!==null&&next!==undefined&&String(next)!==\"\";return (html||more)?(\"<div class=\\\\\"replies\\\\\"><div id=\\\\\"\"+id+\"\\\\\">\"+html+\"<\/div>\"+(more?(\"<button class=\\\\\"reply-more\\\\\" data-target=\\\\\"\"+id+\"\\\\\" data-mid=\\\\\"\"+esc(c.mid||\"\")+\"\\\\\" data-cursor=\\\\\"\"+esc(next)+\"\\\\\" data-author=\\\\\"\"+esc(c.authorId||0)+\"\\\\\">展开更多回复<\/button>\"):\"\")+\"<\/div>\"):\"\";}',\n 'function cardHtml(c){var av=String(c.userPhoto||\"\"),avatar=av?(\"<img class=\\\\\"avatar\\\\\" src=\\\\\"\"+esc(av)+\"\\\\\">\"):\"<span class=\\\\\"avatar\\\\\"><\/span>\";return \"<article class=\\\\\"comment\\\\\"><div class=\\\\\"author-row\\\\\">\"+avatar+\"<div class=\\\\\"identity\\\\\"><b>\"+esc(c.nickname||\"书友\")+\"<\/b><time>\"+esc(c.pubTime||\"\")+\"<\/time><\/div><span class=\\\\\"likes\\\\\">赞 \"+Number(c.likes||0)+\"<\/span><\/div><div class=\\\\\"text\\\\\">\"+rich(c)+\"<\/div>\"+replyBox(c)+\"<\/article>\";}',\n 'function wire(root){var buttons=root.querySelectorAll(\".reply-more\");for(var i=0;i<buttons.length;i++)buttons[i].onclick=function(){loadReplies(this);};}',\n 'function loadReplies(button){if(button.disabled)return;button.disabled=true;button.textContent=\"加载中...\";api(\"\/v1\/comments\/chapter-replies\",{bookId:BID,chapterId:CID,mid:String(button.getAttribute(\"data-mid\")||\"\"),itemIndex:Number(button.getAttribute(\"data-cursor\")||0),size:20,authorId:String(button.getAttribute(\"data-author\")||0)},function(err,root){if(err){button.disabled=false;button.textContent=\"加载失败,点击重试\";return;}var outer=root.data||{},d=outer.data||{},arr=d.commentList||[],box=document.getElementById(button.getAttribute(\"data-target\"));for(var i=0;i<arr.length;i++){var holder=document.createElement(\"div\");holder.innerHTML=replyHtml(arr[i]);if(holder.firstChild)box.appendChild(holder.firstChild);}var next=d.nextItemIndex;if(next===null||next===undefined)next=root.pagination&&root.pagination.nextItemIndex;var more=d.hasMore;if(more===null||more===undefined)more=root.pagination&&root.pagination.hasMore;if(more&&arr.length&&next!==null&&next!==undefined){button.setAttribute(\"data-cursor\",String(next));button.disabled=false;button.textContent=\"展开更多回复\";}else{button.textContent=\"已展开全部回复\";}});}',\n 'function showError(err){list.innerHTML=\"<div class=\\\\\"error\\\\\">加载失败:\"+esc(err&&err.message||err)+\"<\/div>\";}',\n \"function sqCommentPage(root) {\\n var outer = root && root.data || {};\\n var data = outer.data || outer.Data || {};\\n var pagination = root && root.pagination || outer.pagination || data.pagination || {};\\n var list = data.commentList || data.CommentList || [];\\n var next = data.nextItemIndex;\\n if (next === null || next === undefined) next = pagination.nextItemIndex;\\n var more = data.hasMore;\\n if (more === null || more === undefined) more = pagination.hasMore;\\n if (typeof more === \\\"string\\\") more = more.toLowerCase() !== \\\"false\\\";\\n return { list: Array.isArray(list) ? list : [], next: next, more: more };\\n}\\n\\nfunction sqCommentFooter(text, ended) {\\n var node = document.getElementById(\\\"sq_comment_footer\\\");\\n if (!node) {\\n node = document.createElement(\\\"div\\\");\\n node.id = \\\"sq_comment_footer\\\";\\n node.className = \\\"empty\\\";\\n list.appendChild(node);\\n }\\n node.textContent = text;\\n node.className = ended ? \\\"empty\\\" : \\\"loading\\\";\\n list.appendChild(node);\\n}\\n\\nvar sqPageOrder = \\\"\\\";\\nvar sqPageCursor = null;\\nvar sqPageLoading = false;\\nvar sqPageEnded = false;\\nvar sqPageLoaded = 0;\\nvar sqPageSize = 20;\\n\\nfunction sqMaybeLoadComments() {\\n if (sqPageEnded || sqPageLoading || !sqPageOrder) return;\\n if (window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - 220) loadParagraph(sqPageOrder, false);\\n}\\n\\nwindow.addEventListener(\\\"scroll\\\", sqMaybeLoadComments);\\n\",\n \"function loadParagraph(order, reset) {\\n if (sqPageLoading) return;\\n if (reset || sqPageOrder !== order) {\\n sqPageOrder = order;\\n sqPageCursor = null;\\n sqPageEnded = false;\\n sqPageLoaded = 0;\\n list.innerHTML = '<div class=\\\"skeleton\\\"><\/div><div class=\\\"skeleton\\\"><\/div>';\\n }\\n if (sqPageEnded) return;\\n sqPageLoading = true;\\n var body = { bookId: BID, chapterId: CID, paragraphId: PID, order: order, size: sqPageSize };\\n if (sqPageCursor !== null && sqPageCursor !== undefined && String(sqPageCursor) !== \\\"\\\") body.itemIndex = Number(sqPageCursor);\\n api(\\\"\/v1\/comments\/chapter-list\\\", body, function(err, root) {\\n sqPageLoading = false;\\n if (err) { showError(err); return; }\\n var page = sqCommentPage(root);\\n var arr = page.list;\\n if (sqPageLoaded === 0) list.innerHTML = \\\"\\\";\\n if (arr.length) {\\n var html = \\\"\\\";\\n for (var i = 0; i < arr.length; i++) html += cardHtml(arr[i]);\\n list.insertAdjacentHTML(\\\"beforeend\\\", html);\\n sqPageLoaded += arr.length;\\n wire(list);\\n }\\n var next = page.next;\\n var noNext = next === null || next === undefined || String(next) === \\\"\\\" || (sqPageCursor !== null && String(next) === String(sqPageCursor));\\n sqPageEnded = !arr.length || page.more === false || noNext;\\n if (sqPageEnded) {\\n if (!sqPageLoaded) list.innerHTML = '<div class=\\\"empty\\\">暂无评论<\/div>';\\n sqCommentFooter(\\\"已到底(共 \\\" + sqPageLoaded + \\\" 条)\\\", true);\\n } else {\\n sqPageCursor = next;\\n sqCommentFooter(\\\"继续下滑加载\\\", false);\\n setTimeout(sqMaybeLoadComments, 50);\\n }\\n subtitle.textContent = MODE === \\\"chapterTitle\\\" ? (\\\"章节标题 · \\\" + sqPageLoaded + \\\" 条已加载\\\") : (\\\"第 \\\" + Number(String(PID).replace(\/\\\\D\/g, \\\"\\\")) + \\\" 段 · \\\" + sqPageLoaded + \\\" 条已加载\\\");\\n });\\n}\",\n 'function loadChapter(){title.textContent=\"章评\";tabs.style.display=\"none\";api(\"\/v1\/reader\/render-ext\",{bookId:BID,chapterId:CID},function(err,root){if(err){showError(err);return;}var outer=root.data||{},d=outer.data||{},ext=d.bookChapterExtInfo||{},ps=(ext.paragraphList||[]).slice(0),total=0;for(var i=0;i<ps.length;i++)total+=Number(ps[i].commentCount||0);subtitle.textContent=\"本章共 \"+total+\" 条段评\";ps.sort(function(a,b){return Number(b.commentCount||0)-Number(a.commentCount||0);});ps=ps.slice(0,6);if(!ps.length){list.innerHTML=\"<div class=\\\\\"empty\\\\\">本章暂无评论<\/div>\";return;}list.innerHTML=\"\";for(var x=0;x<ps.length;x++)(function(item){var section=document.createElement(\"section\"),pid=String(item.paragraphId||(\"p\"+Number(item.orderId||0))),count=Number(item.commentCount||0);section.className=\"section\";section.innerHTML=\"<h2>第 \"+(Number(item.orderId||0)+1)+\" 段 · \"+count+\" 条<\/h2><div class=\\\\\"loading\\\\\">加载中...<\/div>\";list.appendChild(section);api(\"\/v1\/comments\/chapter-list\",{bookId:BID,chapterId:CID,paragraphId:pid,order:\"hot\",size:5},function(e,res){if(e){section.innerHTML+=\"<div class=\\\\\"error\\\\\">加载失败<\/div>\";return;}var o=res.data||{},dd=o.data||{},arr=dd.commentList||[],html=section.querySelector(\"h2\").outerHTML;for(var z=0;z<arr.length;z++)html+=cardHtml(arr[z]);section.innerHTML=html;wire(section);});})(ps[x]);});}',\n 'var tabButtons=tabs.querySelectorAll(\".tab\");for(var i=0;i<tabButtons.length;i++)tabButtons[i].onclick=function(){for(var j=0;j<tabButtons.length;j++)tabButtons[j].className=\"tab\";this.className=\"tab active\";loadParagraph(this.getAttribute(\"data-order\"));};',\n 'if(MODE===\"chapter\")loadChapter();else{title.textContent=MODE===\"chapterTitle\"?\"章评\":\"段评\";loadParagraph(\"hot\");}',\n '})();',\n '<\/script><\/body><\/html>'\n ].join(\"\\n\");\n}\n\nfunction showSqComments(bookId, chapterId, paragraphId, quoteText) {\n var j = sqJava.call(this);\n if (!j || !j.showBrowser) return;\n j.showBrowser(SQ_COMMENT_API_BASE + \"\/paragraph\", sqLiveCommentHtml(), sqBrowserPreload.call(this, bookId, chapterId, \"paragraph\", paragraphId, quoteText), JSON.stringify(sqBrowserConfig.call(this)));\n}\n\nfunction showSqChapterComments(bookId, chapterId, paragraphId, quoteText) {\n var j = sqJava.call(this);\n if (!j || !j.showBrowser) return;\n j.showBrowser(SQ_COMMENT_API_BASE + \"\/paragraph\", sqLiveCommentHtml(), sqBrowserPreload.call(this, bookId, chapterId, \"chapterTitle\", paragraphId || \"p000\", quoteText), JSON.stringify(sqBrowserConfig.call(this)));\n}\n\n\/\/ SQ_COMMENT_API_V1_END",
"lastUpdateTime": "1786458318985",
"loginCheckJs": "if (JSON.parse(result.body()).message == \"Api Gateway Auth ERROR\") {\n \t\tToken();\n }else{\n \tresult;\n }",
"loginUi": "@js:\nvar fullStyle = { layout_flexGrow: 1, layout_flexBasisPercent: 1 };\nvar halfStyle = { layout_flexGrow: 1, layout_flexBasisPercent: 0.4 };\nvar thirdStyle = { layout_flexGrow: 1, layout_flexBasisPercent: 0.3 };\nresult = JSON.stringify([\n { name: \" 📛 当前分类 📛 \", type: \"button\", action: \"login()\", style: fullStyle },\n { name: \"⭕男频\", type: \"button\", action: \"set(0)\", style: halfStyle },\n { name: \"⭕女频\", type: \"button\", action: \"set(1)\", style: halfStyle },\n { name: \"userId\", type: \"text\", default: \"12345678\", style: fullStyle },\n { name: \"💬 评论设置\", type: \"button\", action: \"commentRefreshTip()\", style: fullStyle },\n { name: \"段评开关\", type: \"toggle\", action: \"commentRefreshTip()\", chars: [\"🔳\", \"✅\"], default: \"🔳\", style: halfStyle },\n { name: \"章评开关\", type: \"toggle\", action: \"commentRefreshTip()\", chars: [\"🔳\", \"✅\"], default: \"🔳\", style: halfStyle },\n { name: \"段评分色\", type: \"toggle\", action: \"commentRefreshTip()\", chars: [\"🔳\", \"✅\"], default: \"🔳\", style: halfStyle },\n { name: \"段评气泡颜色\", type: \"text\", action: \"saveCommentSetting('段评气泡颜色')\", default: \"#666666\", style: thirdStyle },\n { name: \"分色气泡颜色\", type: \"text\", action: \"saveCommentSetting('分色气泡颜色')\", default: \"#e23b3b\", style: thirdStyle },\n { name: \"热门气泡颜色\", type: \"text\", action: \"saveCommentSetting('热门气泡颜色')\", default: \"#f06060\", style: thirdStyle },\n { name: \"分色评论数\", type: \"text\", action: \"saveCommentSetting('分色评论数')\", default: \"45\", style: thirdStyle },\n { name: \"评论窗口高度\", type: \"text\", action: \"saveCommentSetting('评论窗口高度')\", default: \"80\", style: thirdStyle },\n { name: \"评论主题\", type: \"select\", chars: [\"自动\", \"浅色\", \"深色\"], default: \"自动\", style: halfStyle },\n { name: \"◎ 模板\", type: \"select\", chars: [\"起点\", \"样式一\", \"样式二\", \"样式三\"], default: \"起点\", style: halfStyle },\n { name: \"◎ 大小\", type: \"select\", chars: [\"TEXT\", \"text\"], default: \"text\", style: halfStyle },\n { name: \"🍋 自定义气泡 🍋\", type: \"button\", style: fullStyle },\n { name: \"◎ 气泡一\", type: \"text\", action: \"saveCommentSetting('◎ 气泡一')\", style: fullStyle },\n { name: \"◎ 气泡二\", type: \"password\", action: \"saveCommentSetting('◎ 气泡二')\", style: fullStyle },\n { name: \"◎ 气泡三\", type: \"password\", action: \"saveCommentSetting('◎ 气泡三')\", style: fullStyle },\n { name: \"书旗评论 API Key\", type: \"password\", action: \"saveCommentSetting('书旗评论 API Key')\", style: fullStyle }\n]);",
"loginUrl": "genderList = [{\"男频\":\"male\"},{\"女频\":\"female\"}];\n\n\/\/放置登入头\nfunction put(json){\n\t\treturn source.setVariable(\n\t\t\tJSON.stringify(json)\n\t\t);\n\t}\n\t\nfunction login() { \t \t\n\t \tvar userId = source.getLoginInfoMap() || {};\n\t \t userId = userId[\"userId\"];\n\t\t\t userId = !\/^\\d+\/.test(userId) ? 12345678 : userId; \n var v = source.getVariable();\n if(v == \"\"){ set(0); }\n v = JSON.parse(source.getVariable()); \n var uid = [{\"uid\":[{\"userId\":userId}]}];\n var mergedObj = Object.assign({}, v[0], uid[0]);\n var con = [mergedObj];\n java.longToast(JSON.stringify(con));\n return put(con);\n }\n \nfunction set(i){\n\t var gender = genderList[i];\n var keys = Object.keys(gender);\n keys.forEach(key => {\n java.toast(\"设置成功!\"+ key);\n });\n\treturn put([{\"fl\":[gender]}]);\n\t}\n\n\/\/ SQ_COMMENT_LOGIN_V1\nfunction commentRefreshTip() {\n java.longToast(\"\\n评论设置已修改\\n请返回阅读页并刷新正文后生效\");\n}\n\nfunction saveCommentSetting(key) {\n var value = String(result[key] || \"\").trim();\n var map = new Packages.java.util.HashMap();\n map.put(key, value);\n java.upLoginData(map);\n try { source.putLoginInfo(JSON.stringify(result)); } catch(e) {}\n java.toast(key + \"已保存\");\n}",
"respondTime": 180000,
"ruleBookInfo": {
"author": "authorName",
"coverUrl": "imgUrl",
"init": "$.data",
"intro": "<br>\n🕰 更新:{{java.timeFormatUTC(java.getString(\"$.lastInsTime\")*1000,'yyyy-MM-dd HH:mm', timeoffset)}}\n­\n🔖 标签:{{$.tag[*].tagName##\\n##,}}\n🔗 版权:{{$.cpName}}\n­\n📂 简介:{{$.desc}}\n@js:result.replace(\/.+:\\s\/g,'')",
"kind": "{{java.getString('$.freeStack')=='1'?'':'💲VIP'}}\n{{$.monthlyBuyTip}}\n{{$.className}}\n{{java.getString('$.state')=='1'?'连载':'完结'}}",
"lastChapter": "lastChapter.chapterName",
"name": "bookName",
"tocUrl": "@js:\nbookId = \"{{$.bookId}}\";\nparams = `bookId=${bookId}`;\npath = \"\/webapi\/bcspub\/openapi\/book\/chapterlist\";\nurl = GetUrl(path, params, 'https:\/\/ocean.shuqireader.com');",
"wordCount": "{{$.wordCount}}万字"
},
"ruleContent": {
"content": "@js:\n(function () {\n var raw = String(src || \"\").trim();\n if (!raw) return \"获取正文失败:响应为空\";\n if (\/^data:text\\\/plain;base64,\/i.test(raw)) {\n try {\n var dataMatch = raw.match(\/^data:text\\\/plain;base64,([^#,\\s]+)\/i);\n if (dataMatch && dataMatch[1]) raw = String(java.base64Decode(dataMatch[1]) || \"\").trim();\n } catch (eDataUrl) { return \"获取正文失败:data URL 解码失败\"; }\n }\n var requestUrl = \"\";\n var responseText = raw;\n if (\/^https?:\\\/\\\/\/i.test(raw)) {\n requestUrl = raw;\n try { responseText = String(java.ajax(requestUrl) || \"\"); }\n catch (eRequest) { return \"获取正文失败:章节请求失败\"; }\n }\n var obj;\n try { obj = JSON.parse(responseText || \"{}\"); }\n catch (eParse) { return \"获取正文失败:响应格式错误\"; }\n var enc = \"\";\n if (obj.message == \"success\" && obj.ChapterContent) {\n enc = obj.ChapterContent;\n } else {\n var shortUrl = \"\";\n try {\n var lookupUrl = requestUrl || String(baseUrl || \"\");\n var optionAt = lookupUrl.lastIndexOf(\",{\");\n if (optionAt > 0) lookupUrl = lookupUrl.substring(0, optionAt);\n shortUrl = sqLookupMap(sqGetShortMap(this), lookupUrl);\n if (!shortUrl && java && java.get) {\n shortUrl = String(java.get(\"sqShort@\" + lookupUrl) || java.get(\"shortUrl\") || \"\");\n }\n } catch (eShortMap) {\n try { shortUrl = String(java.get(\"shortUrl\") || \"\"); } catch (eLegacy) {}\n }\n if (!shortUrl) return \"付费章节地址缺失,请刷新目录\";\n try {\n var shortObj = JSON.parse(String(java.ajax(shortUrl) || \"{}\"));\n enc = shortObj.ChapterContent || \"\";\n } catch (eShort) { return \"付费章节加载失败\"; }\n }\n if (!enc) return \"章节内容为空\";\n function p(e) {\n return String(e || \"\").split(\"\").map(function (ch) {\n return ch.match(\/[A-Za-z]\/)\n ? ((c = Math.floor(ch.charCodeAt(0) \/ 97),\n k = (ch.toLowerCase().charCodeAt(0) - 83) % 26 || 26,\n String.fromCharCode(k + (0 == c ? 64 : 96))))\n : ch;\n }).join(\"\");\n }\n var text = java.base64Decode(p(enc));\n var meta = sqGetChapterMeta.call(this, baseUrl);\n if (!meta && requestUrl) meta = sqGetChapterMeta.call(this, requestUrl);\n if (meta && meta.bookId && meta.chapterId) {\n var chapterQuote = \"\";\n try { if (typeof chapter !== \"undefined\" && chapter) chapterQuote = String(chapter.title || chapter.chapterName || \"\"); } catch (eChapterQuote) {}\n text = sqDecorateContent.call(this, text, meta.bookId, meta.chapterId, chapterQuote);\n }\n return text;\n})();",
"title": "@js:\nvar sqTitleText = String(title || result || \"\");\ntry { chapter.putImgUrl(null); } catch(eClearSqTitle) {}\nvar sqTitleBubble = \"\";\nvar sqCfg = sqCommentConfig.call(this);\nvar sqMeta = sqGetChapterMeta.call(this, baseUrl);\nif (sqCfg.chapter && sqMeta && sqMeta.chapterId) {\n try {\n var sqTitleSource = sqSource.call(this);\n if (sqTitleSource && sqTitleSource.get) sqTitleBubble = String(sqTitleSource.get(\"sq_title_bubble_\" + String(sqMeta.chapterId)) || \"\");\n } catch(eSqTitle) { sqTitleBubble = \"\"; }\n}\nsqTitleText + sqTitleBubble;"
},
"ruleExplore": {},
"ruleSearch": {
"author": "$.authorName||$.author",
"bookList": ".[?(@.bookName)]||.[?(@.title)]",
"bookUrl": "@js:\nbookId = \"{{$.bookId||$.bid}}\";\nparams = `bookId=${bookId}`;\npath = \"\/webapi\/bcspub\/openapi\/book\/info\";\nurl = GetUrl(path, params);",
"checkKeyWord": "御兽:只有我能看见隐藏进化路线",
"coverUrl": "$.imgUrl||$.cover",
"intro": "$.desc",
"kind": "@js:\nc = \"{{$.className||$.category}}\";\ns1 = \"{{$.status}}\";\ns2 = \"{{$.state}}\";\nif(s1!=\"\"){\n\t s = s1 == '0'?'连载':'完结';\n\t}else{\n\t\t s = s2 == '1'?'连载':'完结';\n }\nresult = c + \",\" + s;",
"lastChapter": "$.latest_chapter.cname",
"name": "$.bookName||$.title",
"wordCount": "$.wordCount||$.words"
},
"ruleToc": {
"chapterList": "<js>\nlist = [];\ndata = JSON.parse(src).data;\nchargeContUrlPrefix = data.chargeContUrlPrefix;\nfreeContUrlPrefix = data.freeContUrlPrefix;\nshortContUrlPrefix = data.shortContUrlPrefix;\nchapterList = data.chapterList;\nvar sqMetaMap = {};\nvar sqShortMap = {};\nvar sqCatalogBookId = \"\";\ntry { sqCatalogBookId = String(bookId || \"\"); } catch(e) {}\nif (!sqCatalogBookId) {\n var sqBookMatch = String(baseUrl || \"\").match(\/bookId=(\\d+)\/);\n if (sqBookMatch) sqCatalogBookId = sqBookMatch[1];\n}\nfunction sqFmtCatalogTime(ts) {\n var d = new Date(Number(ts || 0) * 1000);\n if (!Number(ts || 0)) return \"\";\n function pad(n) { return n < 10 ? \"0\" + n : String(n); }\n return d.getFullYear() + \"-\" + pad(d.getMonth() + 1) + \"-\" + pad(d.getDate()) + \" \" + pad(d.getHours()) + \":\" + pad(d.getMinutes());\n}\nfor (i in chapterList) {\n volumeName = chapterList[i].volumeName;\n list.push({ chapterName: \"🏮〖\" + volumeName + \"〗🏮\", isVolume: true });\n chapterList[i].volumeList.forEach($ => {\n var suffix = String($.contUrlSuffix || \"\");\n var realChapterUrl = ($.isFreeRead ? freeContUrlPrefix : chargeContUrlPrefix) + suffix;\n realChapterUrl = String(realChapterUrl || \"\").replace(\/0$\/, \"1\");\n var currentBookId = String(sqCatalogBookId || \"\");\n var currentChapterId = String($.chapterId || \"\");\n chapterUrl = realChapterUrl;\n shortUrl = shortContUrlPrefix + String($.shortContUrlSuffix || \"\");\n time = sqFmtCatalogTime($.chapterUpdateTime);\n wordCount = $.wordCount + \"字\";\n chapterPrice = $.chapterPrice != \"0\" && !$.isFreeRead ? $.chapterPrice + \"书豆\" : \"\";\n if (suffix) {\n sqMetaMap[suffix] = { bookId: currentBookId, chapterId: currentChapterId };\n if (shortUrl) sqShortMap[suffix] = shortUrl;\n }\n list.push({\n chapterName: $.chapterName,\n chapterUrl: chapterUrl,\n isVip: !$.isFreeRead,\n isPay: $.isBuy,\n chapterInfo: [volumeName, time, wordCount, chapterPrice].join(\"丨\").replace(\/丨$\/, \"\")\n });\n });\n}\ntry {\n java.put(\"sqMetaMap\", JSON.stringify(sqMetaMap));\n java.put(\"sqShortMap\", JSON.stringify(sqShortMap));\n _sqMetaMapCache = null;\n _sqShortMapCache = null;\n} catch(eMap) {}\nlist\n<\/js>",
"chapterName": "chapterName",
"chapterUrl": "chapterUrl",
"isPay": "isPay",
"isVip": "isVip",
"isVolume": "isVolume",
"updateTime": "chapterInfo"
},
"searchUrl": "https:\/\/read.xiaoshuo1-sm.com\/novel\/i.php?do=is_search&isNewBind=0&platform=3&&user_id=8000000&q={{key}}&page={{page}}&size=10&uid=8000000&filterMigu=1&ver=&p=3×tamp={{~~(Date.now()\/1e3)}}",
"weight": 0
}