可乐小说
https://www.kelexs.com
laizhiyu (6509)6天前
第一次让AI两次超过30分钟,才搞完的书源
{
"bookSourceComment": "PS:目录加载时间很长,AI被人机验证限制了使用这种方法。正文替换用大佬们的方案。\n\n可乐小说 (kelexs.com) 书源技术说明\n\n【三套AES加密】\n1. 搜索关键词加密:AES-CBC,密钥 lzxHpH8PLGXcrCIQ 补零至32字节\n2. 搜索结果URL解密:动态密钥 = MD5(UA.toLowerCase() + num),num从页面JS提取\n3. 章节数据加密:AES-CBC,密钥 TVeQQFGz3gMncEh7a0b0Xm4ruSjvF4QM\n\n【目录获取 - 内容页爬取法】\nCDN封锁AJAX分页接口(403),改用 Jsoup.connect() 逐个请求内容页 \/book\/{ID}-{N}.html\n提取 <h1> 标题,分批处理(每批30章),Math.min(maxKnown, totalPages*100) 避免多余章节\n\n【正文字体反爬解码】\n敏感词替换为 <i class=\"icon icon-uniEXXX\"><\/i>,通过自定义字体 fonteditor.ttf 显示\n解码:正则提取PUA码位 → source字符串索引替换(257字符,位置43特殊映射\"AV\")\nES5兼容:var替代const\/let,function替代箭头函数,for循环替代Array.from\n\n【选择器优化】\n详情页:所有选择器限定 .chapBox 容器,排除推荐区域\n搜索列表:.info:not(.infoM2) + dl:nth-child(N) 排除重复信息\n书名:a:not(.btnAdd) 排除\"加入书架\"按钮\n\n【跨域搜索】\nsearchUrl 指向 rrssk.com,bookSourceUrl 保持 kelexs.com\n搜索结果URL解密后得到 kelexs.com 书籍路径",
"bookSourceGroup": "AI,18加",
"bookSourceName": "可乐小说",
"bookSourceType": 0,
"bookSourceUrl": "https:\/\/www.kelexs.com",
"bookUrlPattern": "https?:\/\/www\\.kelexs\\.com\/book\/\\w+\\.html",
"customButton": false,
"customOrder": 4,
"enabled": true,
"enabledCookieJar": true,
"enabledExplore": true,
"eventListener": false,
"exploreUrl": "男生::https:\/\/www.kelexs.com\/list-1-{{page}}\/\n女生::https:\/\/www.kelexs.com\/list-2-{{page}}\/\n二次元::https:\/\/www.kelexs.com\/list-3-{{page}}\/\n玄幻::https:\/\/www.kelexs.com\/list-4-{{page}}\/\n奇幻::https:\/\/www.kelexs.com\/list-5-{{page}}\/\n武侠::https:\/\/www.kelexs.com\/list-6-{{page}}\/\n仙侠::https:\/\/www.kelexs.com\/list-7-{{page}}\/\n都市::https:\/\/www.kelexs.com\/list-8-{{page}}\/\n军事::https:\/\/www.kelexs.com\/list-9-{{page}}\/\n历史::https:\/\/www.kelexs.com\/list-10-{{page}}\/\n游戏::https:\/\/www.kelexs.com\/list-11-{{page}}\/\n体育::https:\/\/www.kelexs.com\/list-12-{{page}}\/\n科幻::https:\/\/www.kelexs.com\/list-13-{{page}}\/\n诸天::https:\/\/www.kelexs.com\/list-14-{{page}}\/",
"header": "{\"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\"}",
"jsLib": "function encSearchKey(keyword) {\n var keyStr = 'lzxHpH8PLGXcrCIQ';\n var keyBytes = new Packages.java.lang.String(keyStr).getBytes('UTF-8');\n var keyPadded = Packages.java.util.Arrays.copyOf(keyBytes, 32);\n var ivBytes = new Packages.java.lang.String(keyStr).getBytes('UTF-8');\n var ivPadded = Packages.java.util.Arrays.copyOf(ivBytes, 16);\n var Cipher = Packages.javax.crypto.Cipher;\n var SecretKeySpec = Packages.javax.crypto.spec.SecretKeySpec;\n var IvParameterSpec = Packages.javax.crypto.spec.IvParameterSpec;\n var secretKey = new SecretKeySpec(keyPadded, 'AES');\n var ivSpec = new IvParameterSpec(ivPadded);\n var cipher = Cipher.getInstance('AES\/CBC\/PKCS5Padding');\n cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);\n var encrypted = cipher.doFinal(new Packages.java.lang.String(keyword).getBytes('UTF-8'));\n return String(Packages.android.util.Base64.encodeToString(encrypted, 2));\n}\n\nfunction decSearchUrl(encStr, numStr) {\n var ua = 'Mozilla\/5.0 (Linux; Android 12; Pixel 6) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/120.0.0.0 Mobile Safari\/537.36';\n var keyStr = String(java.md5Encode(ua.toLowerCase() + numStr)).toLowerCase();\n var keyBytes = new Packages.java.lang.String(keyStr).getBytes('UTF-8');\n var Base64 = Packages.android.util.Base64;\n var allBytes = Base64.decode(encStr, 0);\n var iv = Packages.java.util.Arrays.copyOfRange(allBytes, 0, 16);\n var ct = Packages.java.util.Arrays.copyOfRange(allBytes, 16, allBytes.length);\n var Cipher = Packages.javax.crypto.Cipher;\n var SecretKeySpec = Packages.javax.crypto.spec.SecretKeySpec;\n var IvParameterSpec = Packages.javax.crypto.spec.IvParameterSpec;\n var secretKey = new SecretKeySpec(keyBytes, 'AES');\n var ivSpec = new IvParameterSpec(iv);\n var cipher = Cipher.getInstance('AES\/CBC\/PKCS5Padding');\n cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec);\n var decrypted = cipher.doFinal(ct);\n return String(new Packages.java.lang.String(decrypted, 'UTF-8'));\n}\n\nfunction encChapterData(data) {\n var keyStr = 'TVeQQFGz3gMncEh7a0b0Xm4ruSjvF4QM';\n var ivStr = '9532332736812400';\n var keyBytes = new Packages.java.lang.String(keyStr).getBytes('UTF-8');\n var ivBytes = new Packages.java.lang.String(ivStr).getBytes('UTF-8');\n var Cipher = Packages.javax.crypto.Cipher;\n var SecretKeySpec = Packages.javax.crypto.spec.SecretKeySpec;\n var IvParameterSpec = Packages.javax.crypto.spec.IvParameterSpec;\n var secretKey = new SecretKeySpec(keyBytes, 'AES');\n var ivSpec = new IvParameterSpec(ivBytes);\n var cipher = Cipher.getInstance('AES\/CBC\/PKCS5Padding');\n cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);\n var encrypted = cipher.doFinal(new Packages.java.lang.String(data).getBytes('UTF-8'));\n return String(Packages.android.util.Base64.encodeToString(encrypted, 2));\n}",
"lastUpdateTime": "1783504386636",
"respondTime": 180000,
"ruleBookInfo": {
"author": ".chapBox .infoM dl:nth-child(1) dd@text",
"coverUrl": ".chapBox .picb .pic img@src",
"intro": ".chapBox .txtb .intro@text",
"kind": ".tit.pcs .name@text",
"lastChapter": ".chapBox .infoM dl:nth-child(2) dd@text",
"name": ".chapBox .txtb .name a:not(.btnAdd)@text",
"tocUrl": "text.章节目录@href",
"wordCount": ".chapBox .about span:nth-child(3)@text"
},
"ruleContent": {
"content": "<js>\nvar html = String(result);\nvar source = \"内暴强情缝贪插逼操潮喷阴艳裸乳荡穴鸡淫苞奸胸射嫩肉骚性蒂茎陰騷奶屄成人药春迷尿粉肛龟头A精屌口狂蜜处女唇道阳虐虫妇液色颜熟交爽臀爆舔做爱欲吹自慰点gG玉幼援抽Bb水母狗户部要了奴小脚痒酥死我兽揉搓捏扣抠棍棒洞密美少摩腿春乱伦露漏浪开巨根菊巴黑豪好肥送艳被干拔乱理白刺体下面诱感快货比日妈你娘婊子配仙火叫在里子宫给聊照流群屁眼马无码友拳洞片黄偷禁套弄位脱光舒服溜舞丢还糜妻换妓招嫖娼aA毛Hh一夜本热推图书娃男抓浑圆饱满润湿滑丰腴韵尤物横陈存娇喘息麻桃透风腹长粗肿胀人紧夹吸撅起来雪发翘硕大胴双草坐挲跨柔软臂瘫媚秘破\";\nvar replacementChars = source.split(\"\");\nreplacementChars[43] = \"AV\";\nhtml = html.replace(\/<i class=\"icon icon-uni([0-9a-fA-F]{4})\"><\\\/i>\/g, function(_, p1) {\n return String.fromCharCode(parseInt(p1, 16));\n});\nvar decoded = [];\nfor (var i = 0; i < html.length; i++) {\n var code = html.charCodeAt(i);\n if (code >= 0xE001 && code <= 0xE101) {\n decoded.push(replacementChars[code - 0xE001]);\n } else {\n decoded.push(html.charAt(i));\n }\n}\nhtml = decoded.join('');\nvar doc = Packages.org.jsoup.Jsoup.parse(html);\nvar contentDiv = doc.selectFirst('div.content');\nvar lines = [];\nif (contentDiv) {\n var ps = contentDiv.select('p');\n for (var i = 0; i < ps.size(); i++) {\n var t = String(ps.get(i).text()).replace(\/\\u00a0\/g, ' ').trim();\n if (t) lines.push(t);\n }\n var startIdx = -1;\n for (var i = 0; i < lines.length; i++) {\n if (\/^第\\d+章|^序章|^楔子|^引子\/.test(lines[i])) {\n startIdx = i;\n break;\n }\n }\n if (startIdx > 0) {\n lines = lines.slice(startIdx);\n } else if (startIdx === -1) {\n lines = lines.filter(function(l) {\n return !\/本书由.*?发行|版权所有|侵权必究|^\\s*·\\s*$\/.test(l);\n });\n }\n}\nlines.join('\\n');\n<\/js>",
"nextContentUrl": "text.下一页@href"
},
"ruleExplore": {
"author": ".txtb .info:not(.infoM2) dl:nth-child(1) dd@text",
"bookList": ".rankIBox .list.dList li",
"bookUrl": ".picb .pic a@href",
"coverUrl": ".picb .pic a img@src",
"intro": ".txtb .intro@text",
"kind": ".txtb .info:not(.infoM2) dl:nth-child(2) dd@text",
"name": ".txtb .name a@text"
},
"ruleSearch": {
"author": ".txtb .info:not(.infoM2) dl:nth-child(1) dd@text",
"bookList": "<js>\nvar html = String(result);\nvar doc = Packages.org.jsoup.Jsoup.parse(html);\nvar numStr = '';\nvar scripts = doc.select('script');\nfor (var i = 0; i < scripts.size(); i++) {\n var sc = String(scripts.get(i).data());\n var idx = sc.indexOf('num');\n if (idx >= 0) {\n var eqIdx = sc.indexOf('=', idx);\n if (eqIdx >= 0) {\n var q1 = sc.indexOf(\"'\", eqIdx);\n var q2 = sc.indexOf('\"', eqIdx);\n var qi = -1;\n if (q1 >= 0 && q2 >= 0) qi = Math.min(q1, q2);\n else if (q1 >= 0) qi = q1;\n else if (q2 >= 0) qi = q2;\n if (qi >= 0) {\n var q = sc.charAt(qi);\n var qe = sc.indexOf(q, qi + 1);\n if (qe >= 0) {\n var b64 = sc.substring(qi + 1, qe);\n var bytes = Packages.android.util.Base64.decode(b64, 0);\n numStr = String(new Packages.java.lang.String(bytes, 'UTF-8'));\n break;\n }\n }\n }\n }\n}\nif (numStr) {\n var ua = 'Mozilla\/5.0 (Linux; Android 12; Pixel 6) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/120.0.0.0 Mobile Safari\/537.36';\n var md = Packages.java.security.MessageDigest.getInstance('MD5');\n md.update(new Packages.java.lang.String(ua.toLowerCase() + numStr).getBytes('UTF-8'));\n var digest = md.digest();\n var keyHex = '';\n for (var j = 0; j < digest.length; j++) {\n var b = digest[j] >= 0 ? digest[j] : digest[j] + 256;\n keyHex += (b < 16 ? '0' : '') + b.toString(16);\n }\n var keyBytes = new Packages.java.lang.String(keyHex).getBytes('UTF-8');\n var SecretKeySpec = Packages.javax.crypto.spec.SecretKeySpec;\n var IvParameterSpec = Packages.javax.crypto.spec.IvParameterSpec;\n var JCipher = Packages.javax.crypto.Cipher;\n var B64 = Packages.android.util.Base64;\n var Arrays = Packages.java.util.Arrays;\n var secretKey = new SecretKeySpec(keyBytes, 'AES');\n var links = doc.select('a[onclick*=toUrl]');\n for (var k = 0; k < links.size(); k++) {\n var a = links.get(k);\n var onclick = String(a.attr('onclick'));\n var q1 = onclick.indexOf(\"'\");\n if (q1 < 0) continue;\n var q2 = onclick.indexOf(\"'\", q1 + 1);\n if (q2 < 0) continue;\n var encUrl = onclick.substring(q1 + 1, q2);\n try {\n var allBytes = B64.decode(encUrl, 0);\n var iv = Arrays.copyOfRange(allBytes, 0, 16);\n var ct = Arrays.copyOfRange(allBytes, 16, allBytes.length);\n var ivSpec = new IvParameterSpec(iv);\n var cipher = JCipher.getInstance('AES\/CBC\/PKCS5Padding');\n cipher.init(JCipher.DECRYPT_MODE, secretKey, ivSpec);\n var decrypted = cipher.doFinal(ct);\n var u = String(new Packages.java.lang.String(decrypted, 'UTF-8'));\n a.attr('href', u);\n } catch(e) {}\n }\n}\nString(doc.html());\n<\/js>\n.searchIBox .list li",
"bookUrl": ".txtb .name a@href",
"coverUrl": ".picb .pic a img@src",
"intro": ".txtb .intro@text",
"kind": ".txtb .info:not(.infoM2) dl:nth-child(2) dd@text",
"lastChapter": ".txtb .info:not(.infoM2) dl:nth-child(3) dd@text",
"name": ".txtb .name a@text"
},
"ruleToc": {
"chapterList": "<js>\nvar html = String(result);\nvar doc = Packages.org.jsoup.Jsoup.parse(html);\nvar chapters = [];\nvar chapterMap = {};\n\nvar chapterLinks = doc.select('.chapListBody li a');\nfor (var i = 0; i < chapterLinks.size(); i++) {\n var a = chapterLinks.get(i);\n var chUrl = String(a.attr('href'));\n var chName = String(a.text());\n if (chUrl && chName) {\n chapters.push('<li><a href=\"' + chUrl + '\">' + chName + '<\/a><\/li>');\n var numMatch = chUrl.match(\/-(\\d+)\\.html\/);\n if (numMatch) {\n chapterMap[parseInt(numMatch[1])] = chName;\n }\n }\n}\n\nvar latestLinks = doc.select('.chapList:not(.chapListBody) li a');\nfor (var i = 0; i < latestLinks.size(); i++) {\n var a2 = latestLinks.get(i);\n var chUrl2 = String(a2.attr('href'));\n var chName2 = String(a2.text());\n var numMatch2 = chUrl2.match(\/-(\\d+)\\.html\/);\n if (numMatch2) {\n chapterMap[parseInt(numMatch2[1])] = chName2;\n }\n}\n\nvar dropDown = doc.selectFirst('.dropDown');\nif (dropDown) {\n var aid = String(dropDown.attr('data-aid'));\n var pageItems = doc.select('.dropDown li[data-p]');\n var totalPages = pageItems.size();\n var knownNums = Object.keys(chapterMap).map(Number);\n var maxKnown = Math.max.apply(null, knownNums);\n var maxChapter = Math.min(maxKnown, totalPages * 100);\n java.log('kelexs: aid=' + aid + ' maxCh=' + maxChapter + ' known=' + Object.keys(chapterMap).length);\n\n var missing = [];\n for (var n = 1; n <= maxChapter; n++) {\n if (!chapterMap[n]) {\n missing.push(n);\n }\n }\n java.log('kelexs: missing=' + missing.length + ' chapters');\n\n var batchSize = 30;\n for (var b = 0; b < missing.length; b += batchSize) {\n var batch = missing.slice(b, b + batchSize);\n var futures = [];\n for (var bi = 0; bi < batch.length; bi++) {\n var chNum = batch[bi];\n var chUrl3 = '\/book\/' + aid + '-' + chNum + '.html';\n var fullUrl = 'https:\/\/www.kelexs.com' + chUrl3;\n try {\n var conn = Packages.org.jsoup.Jsoup.connect(fullUrl)\n .userAgent('Mozilla\/5.0 (Linux; Android 12; Pixel 6) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/120.0.0.0 Mobile Safari\/537.36')\n .timeout(5000)\n .get();\n var h1 = conn.selectFirst('h1');\n var title = h1 ? String(h1.text()).trim() : '';\n if (title) {\n chapterMap[chNum] = title;\n }\n } catch(e) {\n }\n }\n java.log('kelexs: batch ' + Math.floor(b \/ batchSize) + '\/' + Math.ceil(missing.length \/ batchSize) + ' done');\n }\n\n chapters = [];\n var nums = Object.keys(chapterMap).map(Number).sort(function(a, b) { return a - b; });\n for (var ni = 0; ni < nums.length; ni++) {\n var num = nums[ni];\n var url = '\/book\/' + aid + '-' + num + '.html';\n var name = chapterMap[num] || ('第' + num + '章');\n chapters.push('<li><a href=\"' + url + '\">' + name + '<\/a><\/li>');\n }\n}\n\n'<html><body><ul>' + chapters.join('') + '<\/ul><\/body><\/html>';\n<\/js>\nul li",
"chapterName": "a@text",
"chapterUrl": "a@href"
},
"searchUrl": "@js:var e=encSearchKey(key);var u='https:\/\/www.rrssk.com\/k-'+encodeURIComponent(e);if(page>1){u+='-'+page;}u+'.html'",
"weight": 0
}