可乐小说(使用webView环境)

https://www.kelexs.com/

jianghubailei (2226)08/18 09:57

本书源 来源于laizhiyu大佬

本次修改尽量减少webView搜索太慢问题,硬编码html,去掉不必要的dom渲染及.js、 .css网络拉取。每日轮转的签名函数做了缓存,定时拉取。

注:cache对象的超时参数有点bug,到了设定时间不会自动失效,只有杀了阅读app后台才生效。
二维码导入
{
    "bookSourceComment": "本书源 来源于laizhiyu大佬\n\n本次修改尽量减少webView搜索太慢问题,硬编码html,去掉不必要的dom渲染及.js、 .css网络拉取。每日轮转的签名函数做了缓存,定时拉取。\n\n注:cache对象的超时参数有点bug,到了设定时间不会自动失效,只有杀了阅读app后台才生效。",
    "bookSourceGroup": "小说",
    "bookSourceName": "可乐小说(使用webView环境)",
    "bookSourceType": 0,
    "bookSourceUrl": "https:\/\/www.kelexs.com\/",
    "bookUrlPattern": "https?:\/\/www\\.kelexs\\.com\/book\/\\w+\\.html",
    "customButton": false,
    "customOrder": 1,
    "enabled": true,
    "enabledCookieJar": true,
    "enabledExplore": true,
    "eventListener": false,
    "exploreUrl": "@js:\nconst groups = {\n  \"二次元\": [\n    {title:\"原生幻想\", url:\"\/list-25-{{page}}\/\"},\n    {title:\"恋爱日常\", url:\"\/list-26-{{page}}\/\"},\n    {title:\"衍生同人\", url:\"\/list-27-{{page}}\/\"},\n    {title:\"搞笑吐槽\", url:\"\/list-28-{{page}}\/\"}\n  ],\n  \"男生\": [\n    {title:\"玄幻小说\", url:\"\/list-4-{{page}}\/\"},\n    {title:\"奇幻小说\", url:\"\/list-5-{{page}}\/\"},\n    {title:\"武侠小说\", url:\"\/list-6-{{page}}\/\"},\n    {title:\"仙侠小说\", url:\"\/list-7-{{page}}\/\"},\n    {title:\"都市小说\", url:\"\/list-8-{{page}}\/\"},\n    {title:\"军事小说\", url:\"\/list-9-{{page}}\/\"},\n    {title:\"历史小说\", url:\"\/list-10-{{page}}\/\"},\n    {title:\"游戏小说\", url:\"\/list-11-{{page}}\/\"},\n    {title:\"体育小说\", url:\"\/list-12-{{page}}\/\"},\n    {title:\"科幻小说\", url:\"\/list-13-{{page}}\/\"},\n    {title:\"诸天无限\", url:\"\/list-14-{{page}}\/\"},\n    {title:\"悬疑小说\", url:\"\/list-15-{{page}}\/\"}\n  ],\n  \"女生\": [\n    {title:\"古代言情\", url:\"\/list-16-{{page}}\/\"},\n    {title:\"仙侠奇缘\", url:\"\/list-17-{{page}}\/\"},\n    {title:\"现代言情\", url:\"\/list-18-{{page}}\/\"},\n    {title:\"浪漫青春\", url:\"\/list-19-{{page}}\/\"},\n    {title:\"玄幻言情\", url:\"\/list-20-{{page}}\/\"},\n    {title:\"悬疑推理\", url:\"\/list-21-{{page}}\/\"},\n    {title:\"科幻空间\", url:\"\/list-22-{{page}}\/\"},\n    {title:\"游戏竞技\", url:\"\/list-23-{{page}}\/\"},\n    {title:\"短篇言情\", url:\"\/list-24-{{page}}\/\"},\n    {title:\"轻小说\", url:\"\/list-42-{{page}}\/\"}\n  ]\n};\n\nconst list = [];\n\nlist.push({\n  title: \"频道\",\n  type: \"select\",\n  chars: [\"男生\", \"女生\", \"二次元\"],\n  default: \"男生\",\n  action: \"java.refreshExplore()\",\n  style: {layout_flexBasisPercent: 1}\n});\n\nconst selected = infoMap[\"频道\"] || \"男生\";\nconst items = groups[selected];\n\nitems.forEach(item => {\n  list.push({\n    title: item.title,\n    url: item.url,\n    style: {layout_flexBasisPercent: 0.29}\n  });\n});\n\ninfoMap.save();\nJSON.stringify(list);",
    "header": "{\n  \"User-Agent\": \"Mozilla\/5.0 (Linux; Android 12; Pixel 6) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/120.0.0.0 Mobile Safari\/537.36\",\n  \"Referer\": \"https:\/\/www.kelexs.com\/\"\n}",
    "lastUpdateTime": "1787018221187",
    "loginUrl": "https:\/\/www.rrssk.com\/0005.html",
    "respondTime": 180000,
    "ruleBookInfo": {
        "author": "[property$=author]@content",
        "coverUrl": "[property=og:image]@content",
        "intro": "[property=og:description]@content",
        "kind": "[property$=category]@content&&\n[property$=status]@content&&\n[property$=update_time]@content",
        "lastChapter": "[property$=latest_chapter_name]@content",
        "name": "[property$=book_name]@content",
        "tocUrl": "@js:\nbaseUrl.replace(\"\/book\/\", \"\/chapter\/\")"
    },
    "ruleContent": {
        "content": ".content > p@text\n<js>\nconst chars = \"内暴强情缝肏插逼操潮喷阴艳裸乳荡穴鸡淫苞奸胸射嫩肉骚性蒂茎陰騷奶屄成人药春迷尿粉肛龟头A精屌口狂蜜处女唇道阳虐虫妇液色颜熟交爽臀爆舔做爱欲吹自慰点gG玉幼援抽Bb水母狗户部要了奴小脚痒酥死我兽揉搓捏扣抠棍棒洞密美少摩腿春乱伦露漏浪开巨根菊巴黑豪好肥送艳被干拔乱理白刺体下面诱感快货比日妈你娘婊子配仙火叫在里子宫给聊照流群屁眼马无码友拳洞片黄偷禁套弄位脱光舒服溜舞丢还糜妻换妓招嫖娼aA毛Hh一夜本热推图书娃男抓浑圆饱满润湿滑丰腴韵尤物横陈存娇喘息麻桃透风腹长粗肿胀人紧夹吸撅起来雪发翘硕大胴双草坐挲跨柔软臂瘫媚秘破\";\nconst replacementChars = chars.split(\"\");\nreplacementChars[43] = \"AV\";\n\nvar content = String(result).replace(\/<i class=\"icon icon-uni([0-9a-fA-F]{4})\"><\\\/i>\/g, (_, p1) =>\n  String.fromCharCode(parseInt(p1, 16))\n);\n\ncontent = content.replace(\/[\\uE001-\\uE101]\/g, char => {\n  let code = char.charCodeAt(0);\n  return replacementChars[code - 0xE001];\n});\n<\/js>",
        "nextContentUrl": ".btnW a:contains(下一页)@href",
        "title": "h1@text"
    },
    "ruleExplore": {
        "author": "a[href*=author]@text",
        "bookList": ".dList li",
        "bookUrl": ".name a@href",
        "coverUrl": "img@src",
        "intro": ".intro@text",
        "kind": ".dlS.1@dd@text",
        "lastChapter": ".infoM2 dt:contains(更新:) + dd@text",
        "name": ".name a@text"
    },
    "ruleSearch": {
        "author": ".book-card__meta-item:contains(作者:)@ownText",
        "bookList": ".book-card@js:\nif(!result || result.size() === 0){\n\t \/\/ 搜索失败,立即清空所有缓存\n  cache.delete(\"home_html_data\");\n  cache.delete(\"home_html_timestamp\");\n  cache.delete(\"daily_signjs_value\");\n  cache.delete(\"daily_signjs_timestamp\");\n}\n\nresult;",
        "bookUrl": ".book-card@onclick\n@js:\nconst window = {\n  open(url){\n    let t = url.match(\/t=([\\w%]+)\/)?.[1];\n    if(!t) return \"\";\n    return java.base64Decode(decodeURIComponent(t))\n  }\n};\n\neval(result)",
        "coverUrl": "img@src",
        "intro": ".book-card__desc@text",
        "kind": ".book-tag@text&&.book-card__update@text##更新:",
        "name": ".book-card__title@text"
    },
    "ruleToc": {
        "chapterList": "@js:\nconst chapters = [];\nif(\/\\\/chapter\\\/\/.test(baseUrl)) {\n  let doc = org.jsoup.Jsoup.parse(result);\n  let elements = doc.select(\".chapListBody a\");\n  elements.forEach(a => {\n    let text = a.text().trim();\n    let href = a.attr(\"href\");\n    chapters.push({text, href})\n  })\n} else {\n  try {\n    let json = JSON.parse(result);\n    if (json.data && json.data.length > 0){\n      json.data.forEach(ch => {\n        chapters.push({\n          text: ch.chaptername,\n          href: ch.chapterurl\n        })\n      })\n    }\n  } catch (e) {\n    java.longToast(e.message)\n  }\n}\n\nchapters",
        "chapterName": "text",
        "chapterUrl": "href",
        "nextTocUrl": "@js:\n(() => {\nconst nextpageList = java.getStringList(\"@@.dropDown li:not(.cur)@data-p\");\nconst jsUrl = result.match(\/src=\"(\\\/templates\\\/js\\\/[A-Za-z0-9]+\\.js)\"\/)?.[1];\nif(nextpageList.size() == 0 || !jsUrl) return [];\n\nconst host = java.toURL(baseUrl).origin;\nconst cryptHtml = java.ajax(host+jsUrl);\nconst key = cryptHtml.match(\/new AESCrypt\\('([A-Za-z0-9]{32})'\\)\/)?.[1];\nconst iv = cryptHtml.match(\/CryptoJS\\.enc\\.Utf8\\.parse\\('(\\d{16})'\\)\/)?.[1];\nif(!key || !iv) return [];\n\nconst id = baseUrl.match(\/chapter\\\/(.*?)\\.html\/)?.[1];\nconst nextpages = Array.from(nextpageList, (page) => {\n  let postBody = java.createSymmetricCrypto('AES\/CBC\/PKCS5Padding', key, iv).encryptBase64(JSON.stringify({\n    id: id,\n    page: page\n  }));\n  return host + '\/index.php?action=loadChapterPage,' + JSON.stringify({\n\t   \"method\": \"POST\",\n\t   \"body\": \"data=\"+encodeURIComponent(postBody)\n  })\n});\n\nreturn nextpages\n})()"
    },
    "searchUrl": "@js:\n\/**\n * 对齐网站搜索规则前置校验\n * 1. 去除首尾空格\n * 2. 字数限制:2~8个字\n * 3. 拦截高危特殊符号,只允许常规中文、字母、数字、常用中文标点\n * 非法特殊符号:引号、斜杠、括号、@#$^&*{}[]等脚本危险字符\n *\/\nfunction checkKeywordLegal(word) {\n    const trimWord = String(word).trim();\n    const kwLen = trimWord.length;\n    \/\/ 空值拦截\n    if (kwLen === 0) {\n        java.longToast(\"搜索关键词不能为空\");\n        throw new Error(\"搜索关键词不能为空\");\n    }\n    \/\/ 字数校验 2~8位\n    if (kwLen < 2 || kwLen > 8) {\n    \t   java.longToast(\"搜索关键字只允许2~8个字之间\");\n        throw new Error(\"搜索关键字只允许2~8个字之间\");\n    }\n    \/\/ 禁止高危特殊符号正则\n    const forbidReg = \/[\\\\\\\"\\(\\)\\{\\}\\[\\]<>@#$%^&*;:|~]\/;\n    if (forbidReg.test(trimWord)) {\n    \t   java.longToast(\"关键词包含非法特殊符号,请重新输入\");\n        throw new Error(\"关键词包含非法特殊符号,请重新输入\");\n    }\n}\n\n\/\/ JS字符串转义兜底,防止注入语法报错\nfunction jsStrEscape(str) {\n    if (str == null) return \"\";\n    return String(str)\n        .replace(\/\\\\\/g, \"\\\\\\\\\")\n        .replace(\/\"\/g, '\\\\\"')\n        .replace(\/\\n\/g, \"\\\\n\")\n        .replace(\/\\r\/g, \"\\\\r\")\n        .replace(\/\\t\/g, \"\\\\t\");\n}\n\/\/ 最先执行关键词校验,不合法直接抛出,终止全部后续流程\ncheckKeywordLegal(key);\n\nvar host = \"https:\/\/www.rrssk.com\";\nvar homeUrl = host + \"\/0005.html\";\n\n\/\/ 缓存Key定义\nconst KEY_HOME_HTML = \"home_html_data\";\nconst KEY_HOME_STAMP = \"home_html_timestamp\";\nconst KEY_SIGNJS_VAL = \"daily_signjs_value\";\nconst KEY_SIGNJS_STAMP = \"daily_signjs_timestamp\";\n\n\/\/ 时效配置(单位:秒)\nconst HOME_TTL = 120;\nconst SIGNJS_TTL = 21600;\n\n\/\/ public.js 固定硬编码\nconst publicJsCode = `\n\/\/ rrssk public.js v11\nfunction __enc(x) {\n    return encodeURIComponent(x).replace(\/[!'()*]\/g, function(c) {\n        return '%' + c.charCodeAt(0).toString(16).toUpperCase();\n    });\n}\nfunction __signData(kw, t) {\n    if (!window.__SIGN || typeof window.__SIGN.f !== 'function') return null;\n    var ts = Math.floor(Date.now() \/ 1000);\n    return { ts: ts, s: window.__SIGN.f(__enc(kw), t, ts) };\n}\n`;\n\nconst nowSecond = Math.floor(Date.now() \/ 1000);\n\n\/\/ 读取首页缓存,双重校验过期\nlet homeHtmlRaw = null;\nconst cacheHome = cache.get(KEY_HOME_HTML);\nconst cacheHomeStamp = Number(cache.get(KEY_HOME_STAMP) || 0);\n\/\/ 缓存存在 + 手动时间未超时才复用\nif (cacheHome && cacheHomeStamp && (nowSecond - cacheHomeStamp < HOME_TTL)) {\n    homeHtmlRaw = cacheHome;\n}\n\n\/\/ 过期|无缓存,重新拉取\nif (!homeHtmlRaw) {\n    homeHtmlRaw = String(java.ajax(homeUrl));\n    cache.put(KEY_HOME_HTML, homeHtmlRaw, HOME_TTL);\n    cache.put(KEY_HOME_STAMP, String(nowSecond), HOME_TTL);\n}\n\n\/\/ 解析页面关键内容\nconst tMatch = homeHtmlRaw.match(\/name=\"_t\" value=\"([^\"]+)\"\/);\nconst apiMatch = homeHtmlRaw.match(\/action=[\"'](\\\/\\?action=\\w+)[\"']\/);\nconst signJsLinkMatch = homeHtmlRaw.match(\/<script src=\"(\\\/\\?action=signJs[^\"]+)\"\/);\n\nif (!tMatch) {\n\t   java.longToast(\"页面解析失败:未获取_t签名令牌\");\n    throw new Error(\"页面解析失败:未获取_t签名令牌\");\n}\nif (!apiMatch) {\n\t   java.longToast(\"页面解析失败:未获取搜索接口地址\");\n    throw new Error(\"页面解析失败:未获取搜索接口地址\");\n}\nif (!signJsLinkMatch) {\n\t   java.longToast(\"页面解析失败:未匹配signJs脚本链接\");\n    throw new Error(\"页面解析失败:未匹配signJs脚本链接\");\n};\n\nconst tokenT = tMatch[1];\nconst searchApi = apiMatch[1];\n\n\/\/ signJs 双重过期校验\nlet signJsCode = null;\nconst cacheSignJsVal = cache.get(KEY_SIGNJS_VAL);\nconst cacheSignJsStamp = Number(cache.get(KEY_SIGNJS_STAMP) || 0);\nif (cacheSignJsVal && cacheSignJsStamp && (nowSecond - cacheSignJsStamp < SIGNJS_TTL)) {\n    signJsCode = cacheSignJsVal;\n}\n\nif (!signJsCode) {\n    const signJsUrl = host + signJsLinkMatch[1];\n    signJsCode = String(java.ajax(signJsUrl));\n    cache.put(KEY_SIGNJS_VAL, signJsCode, SIGNJS_TTL);\n    cache.put(KEY_SIGNJS_STAMP, String(nowSecond), SIGNJS_TTL);\n}\n\n\/\/ 简化页面\nlet renderHtml = homeHtmlRaw\n    .replace(\/<link[^>]*>\/gi, '')\n    .replace(\/<img[^>]*>\/gi, '')\n    .replace(\/<svg[\\s\\S]*?<\\\/svg>\/gi, '')\n    .replace(\/<footer[\\s\\S]*?<\\\/footer>\/gi, '')\n    .replace(\/<script src=\"\\\/templates\\\/js\\\/jquery[\\s\\S]*?<\\\/script>\/, '')\n    .replace(\/<script src=\"\\\/templates\\\/js\\\/public\\.js[\\s\\S]*?<\\\/script>\/, `<script>${publicJsCode}<\/script>`)\n    .replace(\/<script src=\"\\\/\\?action=signJs[\\s\\S]*?<\\\/script>\/, `<script>${signJsCode}<\/script>`);\n\n\/\/ 执行签名\nconst safeKey = jsStrEscape(key);\nconst injectScript = `const res = __signData(\"${safeKey}\",\"${tokenT}\"); res ? res.s : null;`;\nconst signResult = String(java.webView(renderHtml, homeUrl, injectScript, false));\n\nif (!signResult || signResult === \"00000000\") {\n\t   java.longToast(\"签名失败:WebView环境指纹校验不通过\");\n    throw new Error(\"签名失败:WebView环境指纹校验不通过\");\n}\n\n\/\/ 拼装POST参数\nconst postBody = \"_t=\" + encodeURIComponent(tokenT)\n    + \"&_ts=\" + Math.floor(Date.now() \/ 1000)\n    + \"&_s=\" + encodeURIComponent(signResult)\n    + \"&keyword=\" + encodeURIComponent(key);\n\nhost + searchApi + \",\" + JSON.stringify({\n    method: \"POST\",\n    body: postBody\n});",
    "weight": 0
}
广告