禁忌书屋·书贴聚合

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
}
广告