Legado 书源规则说明
原作者: Celeter
现作者: 喵公子
更新时间: 2024-02-27
描述: 本说明是 legado 的规则说明,禁止其他未获得本人授权的第三方软件使用
概况
- 语法说明
- Legado 的特殊规则
- 书源之「基本」
- 书源之「搜索」
- 书源之「发现」
- 书源之「详情页」
- 书源之「目录」
- 书源之「正文」
- 补充说明
1、语法说明
JSOUP 之 Default
-
语法如下:
-
@为分隔符,用来分隔获取规则- 每段规则可分为 3 段
- 第一段是类型,class、id、tag、text、children 等,children 获取所有子标签,不需要第二段和第三段,text 可以根据文本内容获取
- 第二段是名称,text。第二段为文本内容的一部分
- 第三段是位置,class、tag、id 等会获取到多个,所以根据位置
- 不加位置会获取所有
- 位置正着数从 0 开始,0 是第一个,为负数则是取倒着数的值,-1 为倒数第一个,-2 为倒数第二个
!是排除,有些位置不符合需要排除用!后面的序号用:隔开,0 是第 1 个,负数为倒数序号,-1 最后一个,-2 倒数第 2 个,依次- 获取列表的最前面加上负号。可以使用表倒置,有些网站目录列表是倒的,前面加个负号可变为正的
@的最后一段为获取内容,text、textNodes、ownText、href、src、html、all 等- 需要正则替换在最后加上
##正则表达式##替换内容,替换内容为空时,第二个##可以省略 - 例:
class.add.0@tag.a.0@text||tag.dd.0@tag.h1@text##全文阅读 - 例:
class.add.0@tag.a.0@text&&tag.dd.0@tag.h1@text##全文阅读
-
增加支持类似数组的方法
- 格式:
[index,index,...]或[[index,index,...],其中[!开头表示筛选方式为排除,index 可以是单个索引,也可以是区间 - 区间格式为
[start:end]或[start:end:step],其中 start 为 0 时可省略,end 为 -1 时可省略 - 索引(index)、区间两端(start 和 end)、区间间隔(step)都支持负数
- 特殊用法
tag.div[-1:0],可在任意地方让列表反向 - 允许索引作为分段后每个部分的首规则,此时相当于前面是 children
head@.{@text与head@[1]@text与head@children[1]@text等价
- 格式:
JSOUP 之 CSS
- 语法见:https://blog.csdn.net/hou_angela/article/details/80519718
- 必须以
@css:开头 - 标准规范与实现库 Package org.jsonp,select
- 在线测试 Try.jsonp online
注意:获取内容可用 text、textNodes、ownText、html、all、href、src 等
例子见最后的【书源一】的搜索页和正文页规则
JSONPath
- 语法见 JsonPath 教程
- 最好以
@json:或$.开头,其他形式不可靠 - 标准规范 goessner JSONPath - XPath for JSON
- 实现库 jsonpath/JsonPath
- 在线测试 Jayway JsonPath Evaluator
例子见最后的【书源二】的搜索页、目录页和正文页规则
XPath
- 语法见 XPath 教程-入门、XPath 教程-基础、XPath 教程-高级、XPath 库的说明
- 必须以
@XPath:或//开头 - 标准规范 W3C XPATH 1.0
- 实现库 zhegexiaohuozi/JsoupXpath
例子见最后的【书源二】的搜索页、详情页和正文页规则,以及目录页的下一页规则
JavaScript
- 可以在
<js></js>、@js:中使用,结果存在 result 中 @js:只能放在其他规则的最后使用<js></js>可以在任意位置使用,还能作为其他规则的分隔符,例如:tag.li<js></js>/a- 在搜索列表、发现列表和目录中使用可以用
+开头,使用 AllInOne 规则
正则之 AllInOne
- 只能在搜索列表、发现列表、详情页预加载和目录列表中使用
- 必须以
:开头 - 教程 Regex 专题(E404)
- 语法(E404)、方法(E404)、引壁(E404)
- 例子见最后的【书源一】的目录页规则,最前面的
-表示目录倒序,以及【书源二】的目录页规则
正则之 OnlyOne
- 形式
##正则表达式#替换内容## - 只能在搜索列表、发现列表、详情页预加载、目录列表之外使用
- 例子见最后的【书源一】的详情页规则
注意点:该规则只能获取第一个匹配到的结果并进行替换
正则之净化
- 形式
##正则表达式#替换内容 - 只能在其他规则后面,独立使用相当于
all##正则表达式##替换内容 - 例子见最后的【书源一】的正文页规则
注意点:该规则为循环匹配替换
自定义三种连接符号
- 符号:
&&、||、%% - 只能在同种规则间使用,不包括 js 和正则
&&会合并所有取到的值||会以第一个取到值的为准%%会依次取数,三个列表,先取列表 1 的第一个,再取列表 2 的第一个,再取列表 3 的第一个,再取列表 1 的第二个,再取列表 2 的第二个...
2、Legado 的特殊规则
URL 必知必会
1. 请求头
- 一般形式,如下所示:
{
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36",
"Accept-Language": "zh-CN,zh;q=0.9"
}
- 复杂情况可使用 js:
(() => {
var ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36";
var headers = { "User-Agent": ua };
return JSON.stringify(headers);
})()
其中,ua 必须保证是 JavaScript 的 String 类型,
JSON.stringify()才能将 header 转换为字符串。
- 获取登录后的 cookie:
java.getCookie("http://baidu.com", null) // => userId=1234;pwd=adbcd
java.getCookie("http://baidu.com", "userid") // => 1234
- 请求头中支持 http 代理、socks4、socks5 代理设置:
// socks5 代理
{
"proxy": "socks5://127.0.0.1:1080"
}
// http 代理
{
"proxy": "http://127.0.0.1:1080"
}
// 支持代理服务器验证
{
"proxy": "socks5://127.0.0.1:1080使用户名@密码"
}
// 注意:这些请求头是无意义的,会被忽略掉
2. GET 请求
- 一般形式下,charset 为 utf-8 时可省略,无特殊情况不需要请求头和 webView,参数 webView 非空时采用 webView 加载:
https://www.baidu.com,
({
"charset": "gbk",
"headers": {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36"
},
"webView": true
})
- 复杂情况可使用 js:
var ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36";
var headers = { "User-Agent": ua };
var option = {
"charset": "gbk",
"headers": headers,
"webView": true
};
"https://www.baidu.com," + JSON.stringify(option)
3. POST 请求
- 一般形式下,body 是请求体,charset 为 utf-8 时可省略,无特殊情况不需要请求头和 webView,参数 webView 非空时采用 webView 加载:
https://www.baidu.com,
({
"charset": "gbk",
"method": "POST",
"body": "bid=10086",
"headers": {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36"
},
"webView": true
})
- 复杂情况可使用 js:
var ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36";
var headers = { "User-Agent": ua };
var body = "bid=" + "10086";
var option = {
"charset": "gbk",
"method": "POST",
"body": String(body),
"headers": headers,
"webView": true
};
"https://www.baidu.com," + JSON.stringify(option)
其中,body 必须保证是 JavaScript 的 String 类型,变量是计算得到的尽量都用
String()强转一下类型。
变量的 put 与 get
-
@put与@get
只能用于 js 以外的规则中,@put里使用 JSONPath 不需要加引号,其他规则需要加引号,
例如:@put:{bid://*|@bid-data|/@bid-data} -
java.put与java.get
只能用于 js 中,在 js 中无法使用@get
{()} 与 () 规则
-
在搜索 URL 与发现 URL 中的
(1)
在()里只能使用 js -
在搜索 URL 与发现 URL 以外的
(1)
可在(1)中使用任意规则(正则除外?),默认为 js,使用其他规则需要有明显的标志头,
Default 规则需要以@@开头,XPath 需要以@xpath:或//开头,JSONPath 需要以@json:或$.开头,CSS 需要以@css:开头
自定义 js
- 在 js 中调用 java 的常规方法:由于 java 这个关键字已经被使用,调用 java 开头的包名时需使用全局变量 Packages 参考脚本之家
只调用某个 public 函数:例:io.legado.app.utils.htmlFormat(str)、org.jsonp.Jsoup.parse(str)
直接引入 java 类,下所示,引入了两个 java 包,java 包的作用域是在 with 的范围内,其内使用 java 相关语法,最后在使用域外被 j 调用了作用域内的函数:
var javaImport = new JavaImporter();
javaImport.importPackage(
Packages.java.lang,
Packages.java.security
);
with (javaImport) {
function strToMd5By32(str) {
var reStr = null;
var md5 = MessageDigest.getInstance("MD5");
var bytes = md5.digest(String(str).getBytes());
var stringBuffer = new StringBuilder();
bytes.forEach(a => {
var bt = a & 0xff;
if (bt < 16) {
stringBuffer.append("0");
}
stringBuffer.append(Integer.toHexString(bt));
});
reStr = stringBuffer.toString();
return reStr;
}
}
变量
baseUrl// 变量-当前 url, Stringresult// 变量-上一步的结果book// 变量-书籍类, 方法见io.legado.app.data.entityss.Bookcookie// 变量-cookie 操作类, 方法见io.legado.app.help.http.CookieStorecache// 变量-缓存操作类, 方法见io.legado.app.help.CacheManagerchapter// 变量-当前目录类, 方法见io.legado.app.data.entityss.BookChaptertitle// 变量-当前标题, Stringsrc// 内容, 源码
常用函数
下面是一些常用的函数,详见 JsExtensions.kt:
// 访问网络,urlStr 为 url 字符串,返回类型 String?
java.ajax(urlStr: String)
// 并发访问网络,urlList 为 url 数组,返回 StrResponse? 的数组,若要获取 body,需使用 .body()
java.ajaxAll(urlList: Array<String>): Array<String>>
// 访问网络,urlStr 为 url 字符串,返回 Response<String>,已废弃
java.connect(urlStr: String)
// 文件下载,content 为十六进制字符串,url 用于生成文件名,返回 String 文件相对路径
java.downloadFile(content: String, url: String)
// 实现重定向拦截,返回 Connection.Response
java.get(url: String, headers: Map<String, String>)
java.post(urlStr: String, body: String, headers: Map<String, String>)
// 实现 cookie 读取,返回 String
java.getCookie(tag: String, key: String?)
// base64 解码,返回类型 String
java.base64Decode(str: String)
java.base64Decode(str: String, flags: Int)
// base64 解码,返回类型 ByteArray?
java.base64DecodeToByteArray(str: String?)
java.base64DecodeToByteArray(str: String?, flags: Int)
// base64 编码,返回类型 String?
java.base64Encode(str: String)
java.base64Encode(str: String, flags: Int)
// md5 编码,返回类型 String?
java.md5Encode(str: String)
java.md5Encode16(str: String)
// 格式化时间戳,返回类型 String
java.timeFormat(timestamp: Long)
java.timeFormat(time: String)
// utf8 编码转成 gbk 编码,返回 String
java.utf8ToGbk(str: String)
// 实现字符串的 URI 编码,enc 为编码格式,返回 String
java.encodeURI(str: String) // 默认 enc="UTF-8"
java.encodeURI(str: String, enc: String)
// html 格式化,返回 String
java.htmlFormat(str: String)
// 获取本地文件,path 为文件的相对路径,返回 File
java.getFile(path: String)
// 读取本地文件,返回 ByteArray?
java.readFile(path: String)
// 读取本地文本文件,charsetName 为编码格式
java.readTxtFile(path: String) // 自动识别 charsetName 不一定准,乱码时请手动指定
java.readTxtFile(path: String, charsetName: String)
// 删除文件或文件夹
deleteFile(path: String)
// zip 文件解压,zipPath 为压缩文件路径,返回 String 解压相对路径,会删除原文件只保留解压后的文件
java.unzipFile(zipPath: String)
// 文件夹内所有文本文件读取,返回内容 String,会删除文件夹
java.getTxtInfoLder(unzipPath: String)
// 获取网络 zip 文件中的数据,url 为 zip 文件链接,path 为所需获取文件在 zip 内的路径,返回文件数据 String
java.getZipStringContent(url: String, path: String)
// 获取网络 zip 文件中的数据,url 为 zip 文件链接,path 为所需获取文件在 zip 内的路径,返回文件数据 ByteArray?
java.getZipByteArrayContent(url: String, path: String)
// 解析字体,返回字体解析类 QueryTTF?
java.queryBase64TTF(base64: String)
// str 支持 url、本地文件、base64,自动判断,自动缓存,返回字体解析类 QueryTTF?
java.queryTTF(str: String?)
// text 为包含错误字体的内容,font1 为错误的字体,font2 为正确的字体,返回字体对应的字
java.replaceFont(text: String, font1: QueryTTF?, font2: QueryTTF?)
// 输出调试日志
java.log(msg: String)
// AES 解码为 ByteArray?,str 为传入的 AES 加密数据,key 为 AES 解密 key,transformation 为 AES 加密方式,iv 为 ECB 模式的偏移向量
java.aesDecodeToByteArray(str: String, key: String, transformation: String, iv: String)
// AES 解码为 String?,str 为传入的 AES 加密数据,key 为 AES 解密 key,transformation 为 AES 加密方式,iv 为 ECB 模式的偏移向量
java.aesDecodeToString(str: String, key: String, transformation: String, iv: String)
// 已经 base64 的 AES 解码为 ByteArray?,str 为 Base64 编码数据,key 为 AES 解密 key,transformation 为 AES 加密方式,iv 为 ECB 模式的偏移向量
java.aesBase64DecodeToByteArray(str: String, key: String, transformation: String, iv: String)
// 已经 base64 的 AES 解码为 String?,str 为 Base64 编码数据,key 为 AES 解密 key,transformation 为 AES 加密方式,iv 为 ECB 模式的偏移向量
java.aesBase64DecodeToString(str: String, key: String, transformation: String, iv: String)
// 加密 aes 为 ByteArray?,data 为传入的原始数据,key 为 AES 解密 key,transformation 为 AES 加密方式,iv 为 ECB 模式的偏移向量
java.aesEncodeToByteArray(data: String, key: String, transformation: String, iv: String)
// 加密 aes 为 String?,data 为传入的原始数据,key 为 AES 解密 key,transformation 为 AES 加密方式,iv 为 ECB 模式的偏移向量
java.aesEncodeToString(data: String, key: String, transformation: String, iv: String)
// 加密 aes 后 Base64 化的 ByteArray?,data 为传入的原始数据,key 为 AES 解密 key,transformation 为 AES 加密方式,iv 为 ECB 模式的偏移向量
java.aesEncodeToBase64ByteArray(data: String, key: String, transformation: String, iv: String)
// 加密 aes 后 Base64 化的 String?,data 为传入的原始数据,key 为 AES 解密 key,transformation 为 AES 加密方式,iv 为 ECB 模式的偏移向量
java.aesEncodeToBase64String(data: String, key: String, transformation: String, iv: String)
URL 参数
- url 添加 js 参数,解析 url 时执行,可在访问 url 时处理 url,例:
https://www.baidu.com,'"js":'java.headerMap.put('xxx', 'yyy')")
https://www.baidu.com,'"js":'java.url=java.url+'yyyy'")
- url 全部参数,详见
AnalyzeUrl.txt:
data class UrlOption(
val method: String?,
val charset: String?,
val webView: Any?,
val headers: Any?,
val body: Any?,
val type: String?,
val js: String?,
val retry: Int = 0 // 重试次数
)
3、书源之「基本」
-
书源 URL (bookSourceUrl)
必填,唯一标识,不可重复,与其他源相同会覆盖 -
书源名称 (bookSourceName)
必填,名字可重复 -
书源分组 (bookSourceGroup)
可不填,用于整理源 -
登录 URL (loginUrl)
根据需求,随机应变,用于登录个人账户 -
书籍 URL 正则 (bookUrlPattern)
可不填,添加网址时,用于识别书源
例:https?://www.piaotian.com/bookinfo/.* -
请求头 (header)
根据需求,随机应变,访问网址时使用
4、书源之「搜索」
-
搜索地址 (url)
key为关键字标识,通常形态为{{key}},运行时会替换为搜索关键字
也可以对 key 进行加密等操作,例如:{{java.base64Encode(key)}}
page为关键字标识,通常形态为{{page}},page 的初值为 1,也可以对 page 进行计算,例如:{{page-1*20}}
有时会遇到第一页没有页数的情况,有两种方法:
①{{page - 1 == 0 ? "" : page}}
②<,{{page}}>
支持相对 URL -
书籍列表规则 (bookList)
-
书名规则 (name)
-
作者规则 (author)
-
分类规则 (kind)
-
字数规则 (wordCount)
-
最新章节规则 (lastChapter)
-
简介规则 (intro)
-
封面规则 (coverUrl)
-
详情页 url 规则 (bookUrl)
5、书源之「发现」
- 发现地址规则 (url)
page为关键字标识,通常形态为{{page}},page 的初值为 1,也可以对 page 进行计算,例如:{{page-1*20}}
有时会遇到第一页没有页数的情况,有两种方法:
①{{page - 1 == 0 ? "" : page}}
②<,{{page}}>
格式一,名称:http://www.baidu.com,发现 URL 可使用&&或换行符\n隔开
格式二,有 5 个样式属性(layout_flexGrow、layout_flexShrink、layout_alignSelf、layout_flexBasisPercent、layout_wrapBefore)需要了解,详情见简书,写法:
{
"title": "今日限免",
"url": "https://app-cdn.jjwxc.net/bookstore/getFullPage?channel=novelfree",
"style": {
"layout_flexGrow": 1
}
},
{
"title": "频道金榜",
"url": "http://app-cdn.jjwxc.net/bookstore/getFullPage?channelBody=%7B%229%22%3A%7B%22offset%22%3A%22<,{{(page-1)*25}}%22%2C%221imit%22%3A%2225%22%70%7D&versionCode=148",
"style": {
"layout_flexGrow": 0,
"layout_flexShrink": 1,
"layout_alignSelf": "auto",
"layout_flexBasisPercent": -1,
"layout_wrapBefore": true
}
},
{
"title": "幻想未来",
"url": "http://app-cdn.jjwxc.net/bookstore/getFullPage?channelBody=%7B%2222000023%22%3A%7B%22offset%22%3A%22<,{{(page-1)*25}}%22%2C%221imit%22%3A%2225%22%70%7D&versionCode=148"
}
支持相对 URL
- 书籍列表规则 (bookList)
- 书名规则 (name)
- 作者规则 (author)
- 分类规则 (kind)
- 字数规则 (wordCount)
- 最新章节规则 (lastChapter)
- 简介规则 (intro)
- 封面规则 (coverUrl)
- 详情页 url 规则 (bookUrl)
6、书源之「详情页」
- 预处理规则 (bookInfoInit)
只能使用正则之 AllInOne 或者 js
正则之 AllInOne 必须以:开头
js 的返回值需要是 json 对象,例:
(function () {
return {
a: "圣墟",
b: "辰东",
c: "玄幻",
d: "200万字",
e: "第两千章 辰东宫殿",
f: "在破败中崛起,在寂灭中复苏。沧海成尘,雷电枯竭...",
g: "https://bookcover.yuewen.com/qdbimg/349573/1004608738/300",
h: "https://m.qidian.com/book/1004608738"
};
})()
此时,书名规则填 a,作者规则填 b,分类规则填 c,字数规则填 d,最新章节规则填 e,简介规则填 f,封面规则填 g,目录 URL 规则填 h
- 书名规则 (name)
- 作者规则 (author)
- 分类规则 (kind)
- 字数规则 (wordCount)
- 最新章节规则 (lastChapter)
- 简介规则 (intro)
- 封面规则 (coverUrl)
- 目录 URL 规则 (tocUrl)
- 只支持单个 url
- 允许修改书名作者 (canReName)
- 规则不为空且详情页书名不为空,使用详情页中的作者。否则,使用搜索页中的书名
- 规则不为空且详情页作者不为空,使用详情页中的作者。否则,使用搜索页中的作者
7、书源之「目录」
- 目录列表规则 (chapterList)
首字符使用负号(一)可使列表反序 - 章节名称规则 (ruleChapterName)
- 章节 URL 规则 (chapterUrl)
- VIP 标识 (isVip)
当结果为 null、false、0、"" 时为非 VIP - 章节信息 (ChapterInfo)
可调用java.timeFormat(timestamp: Long)将时间戳转为yyyy/MM/dd HH:mm格式的时间 - 目录下一页规则 (nextToCUrl)
支持单个 url,支持 url 数组
js 中返回[]或null或""时停止加载下一页
8、书源之「正文」
- 正文规则 (content)
正文图片链接支持修改 headers:
let options = {
"headers": {
"User-Agent": "xxxx",
"Referrer": baseUrl,
"Cookie": "aaa=bbb;"
}
};
'<img src="' + src + ', ' + JSON.stringify(options) + '>'
book 对象的可用属性
使用方法:在 js 中或 {()} 中使用 book.属性 的方式即可获取,在正文内容后加上 ###{book.name+"正文卷"+title} 可以净化书名+正文卷+章节名称(例如:我是大明星正文卷第二章我爸是豪门总裁)这一类的字符。
bookUrl// 详情页 Url(本地书源存储完整文件路径)tocUrl// 目录页 Url(toe-table of Contents)origin// 书源 URL(默认 BookType.local)originName// 书源名称 or 本地书籍文件名name// 书籍名称(书源获取)author// 作者名称(书源获取)kind// 分类信息(书源获取)customTag// 分类信息(用户修改)coverUrl// 封面 Url(书源获取)customCoverUrl// 封面 Url(用户修改)intro// 简介内容(书源获取)customIntro// 简介内容(用户修改)charset// 自定义字符串名称(仅适用于本地书籍)type// 0:text 1:audiogroup// 自定义分组索引号latestChapterTitle// 最新章节标题latestChapterTime// 最新章节标题更新时间lastCheckTime// 最近一次更新书籍信息的时间lastCheckCount// 最近一次发现新章节的数量totalChapterNum// 书籍目录总数durChapterTitle// 当前章节名称durChapterIndex// 当前章节索引durChapterPos// 当前阅读的进度(首行字符的索引位置)durChapterTime// 最近一次阅读书籍的时间(打开正文的时间)canUpdate// 刷新书架时更新书籍信息order// 手动排序originOrder// 书源排序variable// 自定义书籍变量信息(用于书源规则检索书籍信息)
chapter 对象的可用属性
使用方法:在 js 中或 {()} 中使用 chapter.属性 的方式即可获取,在正文内容后加上 ##{{chapter.title+chapter.index}} 可以净化章节标题+序号(例如:第二章 天仙下凡2)这一类的字符。
url// 章节地址title// 章节标题baseUrl// 用来拼接相对 urlbookUrl// 书籍地址index// 章节序号resourceUrl// 音频真实 URLtag// 章节信息start// 章节起始位置end// 章节终止位置variable// 变量
示例
下示例,在详情页(目录页)和正文使用 webView 加载,例:
{
"bookSourceComment": "",
"bookSourceGroup": " 有声",
"bookSourceName": "猫耳FM",
"bookSourceType": 1,
"bookSourceUrl": "https://www.missevan.com",
"customOrder": 0,
"enabled": false,
"enabledExplore": true,
"lastUpdateTime": 0,
"ruleBookInfo": {},
"ruleContent": {
"content": "https://static.missevan.com/{//*[contains(@class,\"pld-sound-active\")]}/@data-soundurl64}",
"sourceRegex": "",
"webls": ""
},
"ruleExplore": {},
"ruleSearch": {
"author": "author",
"bookList": "$.info.Datas",
"bookUrl": "https://www.missevan.com/mdrama/drama/{{$.id}},{\"webView\":true}",
"coverUrl": "cover",
"intro": "abstract",
"kind": "{{$.type_name}},{{$.catalog_name}}",
"lastChapter": "newest",
"name": "name",
"wordCount": "catalog_name"
},
"ruleToc": {
"chapterList": "@css:.scroll-list.btn-groups>a",
"chapterName": "text",
"chapterUrl": "href##$##,{\"webView\":true}"
},
"searchUrl": "https://www.missevan.com/dramaapi/search?s={{key}}&page=1",
"weight": 0
}
-
正文下一页 URL 规则 (nextContentUrl)
支持单个 url,支持 url 数组 -
WebViewJs (webJs)
用于模拟鼠标点击等操作,必须有返回值(不为空,表示 webjs 执行成功,否则会无限循环),返回值会用于资源正则或正文中举个例子,在 webJs 中执行了
getDecode(),使正文部分解密:
"intro": "abstract",
"kind": "{{$.type_name}},{{$.catalog_name}}",
"lastChapter": "newest",
"name": "name",
"wordCount": "catalog_name"
{
"bookSourceGroup": "阅读3.0书源合集",
"bookSourceName": "斋书苑",
"bookSourceType": 0,
"bookSourceUrl": "https://www.zhaishuyuan.com",
"bookUrlPattern": "",
"customOrder": 11,
"enabled": false,
"enabledExplore": false,
"exploreUrl": "男生书库::/shuku/0_1_0_0_0_{{page}}_0_0\n男频连载::/shuku/0_2_0_0_0_{{page}}_0_0",
"lastUpdateTime": 0,
"loginUrl": "",
"ruleBookInfo": {
"author": "@css:[property=og:novel:author]@content",
"coverUrl": "@css:[property=og:image]@content",
"intro": "@css:#bookintro@html",
"kind": "@css:[property=og:novel:category]@content",
"lastChapter": "@css:[property=og:novel:latest_chapter_name]@content",
"name": "@css:[property=og:novel:book_name]@content",
"tocUrl": "@css:[property=og:novel:read_url]@content",
"wordCount": "@css:.count li:eq(3)>span@text"
},
"ruleContent": {
"content": "all",
"nextContentUrl": "",
"webJs": "getDecode();${'#content').html();"
},
"ruleExplore": {
"author": "/li[4]/a/text()",
"bookList": "/u1[count(../u1)>10]",
"bookUrl": "/li[3]/a/@href",
"coverUrl": "##/book/(\d+)##https://img.zhaishuyuan.com/bookpic/s$1.jpg##",
"intro": "/li[6]/text()",
"kind": "/li[2]/text()##\\[|\\]",
"lastChapter": "/span/a/text()",
"name": "/li[3]/a/text()",
"wordCount": "/li[5]/text()"
},
"ruleSearch": {
"author": "/dd[2]/span[1]/text()",
"bookList": "//*[@id=\"sitembox\"]/dl",
"bookUrl": "/dt/a/@href",
"coverUrl": "/img/@_src",
"intro": "/dd[3]/html()",
"kind": "/dd[2]/span[3]/text()",
"lastChapter": "/dd[4]/a/text()",
"name": "/h3/a//text()",
"wordCount": "/dd[2]/span[4]/text()"
},
"ruleToC": {
"chapterList": ":href=\"((/chapter/{^\"}*)\"{^>}*)<{^<}*)</a>{^<}*)\"",
"chapterName": "$2",
"chapterUrl": "$1,{\"webView\":true}",
"nextToCUrl": "/strong/following-sibling::a/@href",
"updateTime": "$3"
},
"searchUrl": "/search/,{ \n \"charset\": \"gbk\", \n \"method\": \"POST\", \n \"body\": \"page={{page}}&key={{key}}\"\n}",
"weight": 0
}
-
资源正则 (sourceRegex)
用于嗅探
一般情况下的无脑教程下:
章节链接后面加,{"webView":true},不要洒散数的写成tag.a@href,{"webView":true}或$.link,{"webView":true},正确写法:tag.a@href##("#webView":true)、{{@@tag.a@href}, {"webView":true}、tag.a@href@js:result+',{"webView":true}等
在有嗅探功能的浏览器(例如:via、x浏览器等)中,输入章节链接。注意千万别带,{"webView":true}
媒体开始播放后使用浏览器的嗅探功能,查看资源的链接
在资源正则里填写资源链接的正则,一般写.*\.(mp3|mp4).*这个就可以了
最后在正文填写<js>result</js>下示例,在正文嗅探 mp3 和 mp4 的资源:
{
"bookSourceComment": "",
"bookSourceGroup": "有声",
"bookSourceName": "猫耳FM",
"bookSourceType": 1,
"bookSourceUrl": "https://www.missevan.com",
"customOrder": 0,
"enabled": false,
"enabledExplore": true,
"lastUpdateTime": 0,
"ruleBookInfo": {},
"ruleContent": {
"content": "https://static.missevan.com/{f//*[contains(@class,\"pld-sound-active\")]}/@data-soundurl64}",
"sourceRegex": "",
"webJS": ""
},
"ruleExplore": {},
"ruleSearch": {
"author": "author",
"bookList": "$.info.Datas",
"bookUrl": "https://www.missevan.com/mdrama/drama/{{$.id}},{\"webView\":true}",
"coverUrl": "cover",
"intro": "abstract",
"kind": "({{$.type_name}},{{$.catalog_name}}",
"lastChapter": "newest",
"name": "name",
"wordCount": "catalog_name"
},
"ruleToc": {
"chapterList": "@css:.scroll-list.btn-groups>a",
"chapterName": "text",
"chapterUrl": "hrer###,{\"webView\":true}"
},
"searchUrl": "https://www.missevan.com/dramaapi/search?s={{key}}&page=1",
"weight": 0
}
9、补充说明
显示 js 的报错信息
(function (result) {
try {
// 处理 result
// ...
// 当返回结果为字符串时
return result;
// 当返回结果为列表时
return list;
} catch (e) {
// 当返回结果为字符串时
return "" + e;
// 当返回结果为列表时
return ["" + e]; // 列表对应名称处填 <js>""+result</js> 查看
}
})(result);
请善用调试功能
- 调试搜索
输入关键字,例如:系统 - 调试发现
输入发现 URL,例如:月票榜:https://www.qidian.com/rank/yuepiao?page={(page)} - 调试详情页
输入详情页 URL,例如:https://m.qidian.com/book/1015609210 - 调试目录页
输入目录页 URL,例如:++https://www.zhaishuyuan.com/read/30394 - 调试正文页
输入正文页 URL,例如:--https://www.zhaishuyuan.com/chapter/30394/20940996
无脑 {"webView":true} 很方便
特别注意
用 JSON.stringify() 方法时,需要保证 JSON 对象的 value 都是 JavaScript 的 String 类型(在阅读 3.0 中)
附:书源示例
书源一
{
"bookSourceComment": "",
"bookSourceGroup": "CSS; 正则",
"bookSourceName": "/i说2016",
"bookSourceType": 0,
"bookSourceUrl": "https://www.xiaoshuo2016.com",
"bookUrlPattern": "",
"customOrder": 0,
"enabled": true,
"enabledExplore": false,
"exploreUrl": "",
"lastUpdateTime": 0,
"loginUrl": "",
"ruleBookInfo": {
"author": "##:author\"[A\"]+\"([A\"]*)##$1##",
"coverUrl": "##og:image\"[A\"]+\"([A\"]*)##$1##",
"intro": "##:description\"[A\"]+\"([\wWW]*?)\"/##$1##",
"kind": "##:category\"[A\"]+\"([A\"]*)##$1##",
"lastChapter": "##_chapter_name\"[A\"]+\"([A\"]*)##$1##",
"name": "##:book_name\"[A\"]+\"([A\"]*)##$1##",
"toCUrl": ""
},
"ruleContent": {
"content": "@css:.articleDiv p@textNodes##搜索.*手机访问|一秒记住.*].*阅读下载|<!\\[CDATA\\[|\\]\>",
"nextContentUrl": ""
},
"ruleExplore": {},
"ruleSearch": {
"author": "@css:p:eq(2)>a@text",
"bookList": "@css:li.clearfix",
"bookUrl": "@css:.name>a@href",
"coverUrl": "@css:img@src",
"intro": "@css:.note.clearfix p@text",
"kind": "@css:.note_text,p:eq(4)@text",
"lastChapter": "@css:p:eq(3)@text",
"name": "@css:.name@text"
},
"ruleToc": {
"chapterList": "-:<li><a[A\"]+\"([A\"]*)\">([A<]*)",
"chapterName": "$2",
"chapterUrl": "$1",
"nextToCUrl": ""
},
"searchUrl": "/modules/article/search.php?searchkey={{key}}&submit=&page={{page}},{ \n \"charset\": \"gbk\"\n}",
"weight": 0
}
书源二
{
"bookSourceComment": "",
"bookSourceGroup": "XPath; 正则",
"bookSourceName": "采集手册版",
"bookSourceType": 0,
"bookSourceUrl": "https://m.caimoge.com",
"bookUrlPattern": "",
"customOrder": 0,
"enabled": true,
"enabledExplore": false,
"exploreUrl": "",
"lastUpdateTime": 0,
"loginUrl": "",
"ruleBookInfo": {
"author": "//*[@property=\"og:novel:author\"]/@content",
"coverUrl": "//*[@property=\"og:image\"]/@content",
"intro": "//*[@property=\"og:description\"]/@content",
"kind": "//*[@property=\"og:novel:category\"]/@content",
"lastChapter": "//*[@id=\"newList\"]//li[1]/a/text()",
"name": "//*[@property=\"og:novel:book_name\"]/@content",
"tocUrl": "//a[text()=\"阅读\"]/@href"
},
"ruleContent": {
"content": "//*[@id=\"content\"]",
"nextContentUrl": ""
},
"ruleExplore": {},
"ruleSearch": {
"author": "//dd[2]/text()",
"bookList": "//*[@id=\"sitebox\"]/dl",
"bookUrl": "//dt/a/@href",
"coverUrl": "//img/@src",
"kind": "//dd[2]/span/text()",
"lastChapter": "",
"name": "//h3/a/text()"
},
"ruleToC": {
"chapterList": ":href=\"(/read[A\"]*html)\">([^<]*)",
"chapterName": "$2",
"chapterUrl": "$1",
"nextTocUrl": "//*[@id=\"pageList\"]/*[position()>1]/@value"
},
"searchUrl": "/search.html,\n \"method\": \"POST\",\n \"body\": \"searchkey={(key)}\"\n}",
"weight": 0
}
书源三
{
"bookSourceComment": "",
"bookSourceGroup": "JSon",
"bookSourceName": "猎鹰小说网",
"bookSourceType": 0,
"bookSourceUrl": "http://api.book.lieying.cn",
"customOrder": 0,
"enabled": true,
"enabledExplore": false,
"header": "(\n \"User-Agent\": \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36\"\n)",
"lastUpdateTime": 0,
"ruleBookInfo": {},
"ruleContent": {
"content": "$.chapter.body"
},
"ruleExplore": {},
"ruleSearch": {
"author": "$.author",
"bookList": "$..books[*]",
"bookUrl": "/Book/getChapterListByBookId?bookId={$._id}",
"coverUrl": "$.cover",
"intro": "$.shortIntro",
"kind": "$.minorCate",
"lastChapter": "$.lastChapter",
"name": "$.title"
},
"ruleToc": {
"chapterList": "$.chapterInfo.chapters.[*]",
"chapterName": "$.title",
"chapterUrl": "$.link"
},
"searchUrl": "/Book/search?query={{key}}&start={{(page-1)*20}} &limit=40&device_type=android&app_version=165",
"weight": 0
}