[{"bookSourceComment":"\/\/禁忌书屋(cool18 bbs4)书贴聚合源：搜索关键词后把帖子按【书名】聚合成书(书名写法不同也会并成一本)，点开书按书名再搜一次生成目录(按发帖时间从早到晚\/一层楼一章，主贴和跟帖都算)，正文取帖子正文区。参考 by河鹅和 的旧书源改写，适配 cool18 新版 wap 模板。","bookSourceGroup":"聚合书源","bookSourceName":"禁忌书屋·书贴聚合","bookSourceType":0,"bookSourceUrl":"https:\/\/www.cool18.com\/bbs4\/","concurrentRate":"","customOrder":0,"enabled":true,"enabledCookieJar":true,"enabledExplore":false,"enabledReview":false,"header":"{\"User-Agent\":\"Mozilla\/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/120.0.0.0 Safari\/537.36\"}","jsLib":"","lastUpdateTime":"1789900621006","loginUrl":"","loginUi":"","loginCheckJs":"","respondTime":180000,"ruleBookInfo":{"init":"","name":"","author":"","kind":"","intro":"","coverUrl":"","tocUrl":"","wordCount":"","lastChapter":""},"ruleContent":{"content":"#content-section pre@textNodes||#content-section@html","replaceRegex":"","replaceRule":"","sourceRegex":"","webJs":""},"ruleExplore":[],"ruleReview":{"avatarRule":"","contentRule":"","postTimeRule":"","reviewList":"","userNameRule":""},"ruleSearch":{"author":"$.author","bookList":".l-m1\n<js>\n\/*\n * 禁忌书屋(cool18 bbs4) 搜索列表聚合脚本\n * 把搜索结果里的书贴按【书名】聚合成\"书\", 供阅读(Legado)搜索列表显示\n * 参考旧书源 by河鹅和 的写法改写, 适配 cool18 新版 wap 模板\n *\n * 两个坑:\n * 1) 搜索结果里\"主贴\"的标题是加粗的(<b>), 后面更新的章节经常以\"跟帖\"发出, 标题不加粗,\n *    所以不能只看加粗的, 也不能用 first=1 过滤(那样会把新章节全丢掉);\n * 2) 同一本书的标题写法不统一, 有【仙宫香妃录】、《仙宫香妃录》、【仙宫香妃录(新版)】等,\n *    要按\"去掉书名号+统一括号\"后的书名聚合, 才不会一本书拆成好几本\n *\/\nvar keyList = [];\nvar bookList = [];\ntry {\n    var rawVar = source.getVariable();\n    if (rawVar) {\n        var Variable = JSON.parse(rawVar);\n        if (Variable) {\n            if (Variable.keyList) keyList = Variable.keyList;\n            if (Variable.bookList) bookList = Variable.bookList;\n        }\n    }\n} catch (e) {\n    keyList = [];\n    bookList = [];\n}\n\nfunction trimStr(s) {\n    if (s === null || typeof s == \"undefined\") return \"\";\n    return String(s).replace(\/^\\s+|\\s+$\/g, \"\");\n}\n\nfunction listCount(list) {\n    if (!list) return 0;\n    if (typeof list.length == \"number\") return list.length;\n    return list.size();\n}\n\nfunction listItem(list, i) {\n    var v = null;\n    try { v = list[i]; } catch (e1) { v = null; }\n    if (v === null || typeof v == \"undefined\") {\n        try { v = list.get(i); } catch (e2) { v = null; }\n    }\n    return v;\n}\n\n\/* 取标题里第一对书名号(含括号), 没有则返回空 *\/\nfunction pickRawName(title) {\n    var opens = [\"【\", \"《\", \"[\", \"［\", \"〔\", \"〖\"];\n    var closes = [\"】\", \"》\", \"]\", \"］\", \"〕\", \"〗\"];\n    for (var i = 0; i < opens.length; i++) {\n        var s = title.indexOf(opens[i]);\n        if (s < 0) continue;\n        var e = title.indexOf(closes[i], s + 1);\n        if (e > s) return trimStr(title.substring(s, e + 1));\n    }\n    return \"\";\n}\n\n\/* 显示用书名: 去掉两端的书名号 *\/\nfunction displayName(raw) {\n    var s = trimStr(raw);\n    while (s.length > 0 && \"【《[［〔〖\".indexOf(s.charAt(0)) >= 0) s = s.substring(1);\n    while (s.length > 0 && \"】》]］〕〗\".indexOf(s.charAt(s.length - 1)) >= 0) {\n        s = s.substring(0, s.length - 1);\n    }\n    return trimStr(s);\n}\n\n\/* 聚合用的键: 去掉所有书名号\/空格, 全角括号转半角, 这样同一本书的不同写法会并成一本 *\/\nfunction bookKey(name) {\n    var out = \"\";\n    var drop = \"【】《》[]［］〔〕〖〗 \\u3000\\t\\r\\n\";\n    for (var i = 0; i < name.length; i++) {\n        var ch = name.charAt(i);\n        if (drop.indexOf(ch) >= 0) continue;\n        if (ch === \"（\") out += \"(\";\n        else if (ch === \"）\") out += \")\";\n        else out += ch;\n    }\n    return out;\n}\n\n\/* 放进 URL 参数里用的键(只要不影响 URL 结构即可, 中文由阅读自己转义) *\/\nfunction urlEscape(s) {\n    var out = \"\";\n    for (var i = 0; i < s.length; i++) {\n        var ch = s.charAt(i);\n        if (ch === \"%\") out += \"%25\";\n        else if (ch === \"&\") out += \"%26\";\n        else if (ch === \"#\") out += \"%23\";\n        else if (ch === \"+\") out += \"%2B\";\n        else if (ch === \"=\") out += \"%3D\";\n        else if (ch === \"?\") out += \"%3F\";\n        else out += ch;\n    }\n    return out;\n}\n\n\/*\n * 目录页(按书名再搜一次)的关键词:\n * 实测本站搜索\"多一个字、带空格或括号\"就经常 0 结果, 含\"乱伦\"会被站点过滤,\n * 所以把书名切成片段, 取最长的安全片段(最多7字)当关键词, 目录页再用书名精确过滤\n *\/\nfunction pickKeyword(key) {\n    var s = trimStr(key);\n    var chunks = s.split(\"乱伦\");\n    var best = \"\";\n    for (var i = 0; i < chunks.length; i++) {\n        var segs = chunks[i].split(\/[^\\u4e00-\\u9fa5A-Za-z0-9]+\/);\n        for (var j = 0; j < segs.length; j++) {\n            var seg = trimStr(segs[j]);\n            if (seg.length > best.length) best = seg;\n        }\n    }\n    if (best.length > 7) best = best.substring(0, 7);\n    if (best.length < 2) best = s;\n    if (best.length > 7) best = best.substring(0, 7);\n    return best;\n}\n\n\/* 从标题里取作者, 用字符串查找而不用正则(部分正则在 Rhino 里编译不过) *\/\nfunction pickAuthor(title) {\n    var tags = [\"作者：\", \"作者:\", \"作者 :\", \"作：\", \"作:\", \"作 :\"];\n    var stops = [\" \", \"\\u3000\", \"\\t\", \"，\", \"。\", \"、\", \"）\", \")\", \"】\", \"]\", \"（\", \"(\", \"【\", \"[\"];\n    for (var i = 0; i < tags.length; i++) {\n        var p = title.indexOf(tags[i]);\n        if (p < 0) continue;\n        var rest = title.substring(p + tags[i].length);\n        var end = -1;\n        for (var j = 0; j < stops.length; j++) {\n            var k = rest.indexOf(stops[j]);\n            if (k >= 0 && (end < 0 || k < end)) end = k;\n        }\n        if (end >= 0) rest = rest.substring(0, end);\n        return trimStr(rest);\n    }\n    return \"\";\n}\n\nvar count = listCount(result);\nif (count === 0) {\n    result = \"[]\";\n} else {\n    for (var i = 0; i < count; i++) {\n        var b = listItem(result, i);\n        if (!b) continue;\n        var title = trimStr(java.getString(\"a@text\", b));\n        if (title === \"\") continue;\n        var rawName = pickRawName(title);\n        if (rawName === \"\") {\n            \/* 没书名号的: 只有主贴(标题加粗)才当成一本书, 跟帖直接丢掉, 免得生成垃圾书 *\/\n            var boldTitle = trimStr(java.getString(\"a b@text\", b));\n            if (boldTitle === \"\") continue;\n            rawName = title;\n        }\n        var bookname = displayName(rawName);\n        var bookkey = bookKey(rawName);\n        if (bookname === \"\" || bookkey === \"\") continue;\n        var uploader = trimStr(java.getString(\"font@text\", b));\n        var date = trimStr(java.getString(\"i@text\", b));\n        var author = pickAuthor(title);\n        var idx = keyList.indexOf(bookkey);\n        if (idx === -1) {\n            keyList.push(bookkey);\n            bookList.push({\n                bookname: bookname,\n                key: urlEscape(bookkey),\n                keyword: pickKeyword(bookkey),\n                author: author,\n                uploader: uploader,\n                time: date,\n                sum: 1\n            });\n        } else {\n            bookList[idx].sum = bookList[idx].sum + 1;\n        }\n    }\n    source.setVariable(JSON.stringify({ keyList: keyList, bookList: bookList }));\n    result = JSON.stringify(bookList);\n}\n<\/js>\n$[*]","bookUrl":"index.php?action=search&bbsdr=bbs4&act=threadsearch&app=forum&keywords={{$.keyword}}&submit=%E6%9F%A5%E8%AF%A2&bk={{$.key}}","checkKeyWord":"","coverUrl":"","intro":"共{{$.sum}}章　最新：{{$.time}}　上传者：{{$.uploader}}","kind":"$.time","lastChapter":"","name":"$.bookname","wordCount":""},"ruleToc":{"chapterList":"-ul.thread-list li.l-m1\n<js>\n\/*\n * 目录列表: 用\"书名键\"过滤搜索结果, 只留下这本书的书贴(主贴+跟帖都算, 更新章节常在跟帖里)\n * 过滤是精确匹配(把标题里的书名按同样的规则归一化后比较), 所以【仙宫香妃录】不会串到\n * 【仙宫香妃录（新版）】里; 万一一条都匹配不上, 再逐级放宽, 保证目录不为空\n *\/\nvar tocHitList = [];\nvar tocSoftList = [];\nvar tocAllList = [];\n\nfunction tocTrim(s) {\n    if (s === null || typeof s == \"undefined\") return \"\";\n    return String(s).replace(\/^\\s+|\\s+$\/g, \"\");\n}\n\n\/* 与搜索列表完全一致的书名归一化规则 *\/\nfunction tocPickRawName(title) {\n    var opens = [\"【\", \"《\", \"[\", \"［\", \"〔\", \"〖\"];\n    var closes = [\"】\", \"》\", \"]\", \"］\", \"〕\", \"〗\"];\n    for (var i = 0; i < opens.length; i++) {\n        var s = title.indexOf(opens[i]);\n        if (s < 0) continue;\n        var e = title.indexOf(closes[i], s + 1);\n        if (e > s) return tocTrim(title.substring(s, e + 1));\n    }\n    return \"\";\n}\n\nfunction tocBookKey(name) {\n    var out = \"\";\n    var drop = \"【】《》[]［］〔〕〖〗 \\u3000\\t\\r\\n\";\n    for (var i = 0; i < name.length; i++) {\n        var ch = name.charAt(i);\n        if (drop.indexOf(ch) >= 0) continue;\n        if (ch === \"（\") out += \"(\";\n        else if (ch === \"）\") out += \")\";\n        else out += ch;\n    }\n    return out;\n}\n\nvar tocWant = \"\";\ntry {\n    tocWant = tocBookKey(tocTrim(book.name));\n} catch (e) {\n    tocWant = \"\";\n}\n\nvar tocCount = 0;\nif (result) {\n    tocCount = (typeof result.length == \"number\") ? result.length : result.size();\n}\nfor (var i = 0; i < tocCount; i++) {\n    var tocEl = null;\n    try { tocEl = result[i]; } catch (e1) { tocEl = null; }\n    if (tocEl === null || typeof tocEl == \"undefined\") {\n        try { tocEl = result.get(i); } catch (e2) { tocEl = null; }\n    }\n    if (!tocEl) continue;\n    var tocName = tocTrim(java.getString(\"a@text\", tocEl));\n    var tocUrl = tocTrim(java.getString(\"a@href\", tocEl));\n    if (tocName === \"\" || tocUrl === \"\") continue;\n    var tocItem = { name: tocName, url: tocUrl };\n    tocAllList.push(tocItem);\n    var tocRaw = tocPickRawName(tocName);\n    var tocKey = tocBookKey(tocRaw === \"\" ? tocName : tocRaw);\n    if (tocWant !== \"\" && tocKey === tocWant) {\n        tocHitList.push(tocItem);\n    } else if (tocWant !== \"\" && tocKey.indexOf(tocWant) >= 0) {\n        tocSoftList.push(tocItem);\n    }\n}\n\nif (tocHitList.length > 0) {\n    result = JSON.stringify(tocHitList);\n} else if (tocSoftList.length > 0) {\n    result = JSON.stringify(tocSoftList);\n} else {\n    result = JSON.stringify(tocAllList);\n}\n<\/js>\n$[*]","chapterName":"$.name##\\s*作\\s*者?[:：].*$##","chapterUrl":"$.url","isVip":"","nextTocUrl":"<js>\n\/*\n * 目录下一页: 当前页还有 .l-m1 书贴时才取\"下一页\"链接, 没有就结束翻页\n * (搜索结果最后一页之后站点仍会输出下一页链接, 必须自己判断)\n * 另外最多翻 5 页, 免得关键词太宽泛时一直翻下去\n *\/\nfunction ntStr(v) {\n    if (v === null || typeof v == \"undefined\") return \"\";\n    return String(v).replace(\/^\\s+|\\s+$\/g, \"\");\n}\n\nvar ntCount = 0;\ntry {\n    ntCount = parseInt(java.get(\"tocPageCount\"), 10);\n    if (isNaN(ntCount)) ntCount = 0;\n} catch (e) {\n    ntCount = 0;\n}\nif (ntStr(baseUrl).indexOf(\"&p=\") < 0) ntCount = 0; \/* 回到第一页就重新计数 *\/\nntCount = ntCount + 1;\ntry {\n    java.put(\"tocPageCount\", String(ntCount));\n} catch (e2) {\n}\n\nvar ntHasChapter = ntStr(java.getString(\".l-m1 a@href\"));\nvar ntNextUrl = ntStr(java.getString(\".pagination-bar a.next@href\"));\nif (ntCount < 5 && ntHasChapter !== \"\" && ntNextUrl !== \"\") {\n    result = ntNextUrl;\n} else {\n    result = \"\";\n}\n<\/js>"},"searchUrl":"<js>if(page==1){source.setVariable('')}<\/js>index.php?action=search&bbsdr=bbs4&act=threadsearch&app=forum&keywords={{key}}&submit=%E6%9F%A5%E8%AF%A2&p={{page}}","weight":0}]