禁忌书屋·书贴聚合
https://www.cool18.com/bbs4/
source-of-eternity (14832)09/20 18:37
禁忌书屋原来的搜索合并书源挂掉了,参考原来rga大神的代码,使用deepseek V4-flash(梁圣) vibe coding解决了原来的问题,重新可以根据书名将同一书贴聚合在一起
{
"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
}