## 搜索源介绍
MyACG 是基于搜索源提供的网络搜索、网络发现。
通过添加搜索源,可以为 MyACG 增加和提升更强大的搜索能力,而且这些搜索源完全可以由你来进行编写。
## 目录
[TOC]
## 一、快速开始
### 创建搜索源文件
创建一个文件,除后缀必须为 js 以外文件名随便命名,这里以 **demo.js** 为例

### 添加清单
打开文件并添加 **manifest** 函数编辑以下内容
~~~js
function manifest() {
return JSON.stringify({
//搜索源 ID 标识,设置后不可更改
//可前往https://tool.lu/timestamp/ 生成时间戳(精确到秒)
id: 1706236843,
//最低兼容 MyACG 版本(高版本无法安装在低版本MyACG中)
minMyACG: 20240122,
//搜索源名称
name: "demo",
//搜索源版本号,低版本搜索源无法覆盖安装高版本搜索源
version: 1,
//默认为1,类别(1:网页,2:图库,3:视频,4:书籍,5:音频,6:图片)
type: 1,
//内容处理方式: -1: 搜索相似,0:对网址处理并调用外部APP访问,1:对网址处理,2:对内部浏览器拦截
contentProcessType: 1,
//分组
group: ["demo"],
//详情页的基本网址
baseUrl: "http://www.example.com",
});
}
~~~
这样我们就完成了一个最简单的搜索源,你可以复制代码并在 MyACG 内剪贴板导入它,只是这个搜索源没有任何功能。

### 实现搜索功能
添加 **search** 函数编辑以下内容
~~~js
/**
* 搜索
* @param {string} key
* @return {[{name, author, lastChapterName, lastUpdateTime, summary, coverUrl, url}]}
*/
function search(key) {
var result = [];
result.push({
//名称
name: "一个搜索结果",
//网址
url: "http://www.example.com/detail"
});
return JSON.stringify(result);
}
~~~
我们再次复制全部代码并在 MyACG 内剪贴板导入它

此时权限一栏出现了搜索,点击进入搜索页面,随便搜索一个关键字就会出现结果

点击搜索结果便会跳转至对应网页

## 二、在 js 中调用 java
### Java
调用 java 类需使用完整包名,例如`new java.lang.String(),
org.jsoup.Jsoup.parse(html)`
例子
~~~js
function encrypt3DES(data, key, transformation, iv) {
var skeySpec = new javax.crypto.spec.SecretKeySpec(new java.lang.String(key).getBytes(), "DESede");
var ivParameterSpec = new javax.crypto.spec.IvParameterSpec(new java.lang.String(iv).getBytes());
var cipher = javax.crypto.Cipher.getInstance(transformation);
cipher.init(javax.crypto.Cipher.DECRYPT_MODE, skeySpec, ivParameterSpec);
return cipher.doFinal(data);
}
~~~
### Jsoup
~~~js
org.jsoup:jsoup:1.16.2
~~~
- 语法见 [https://blog.csdn.net/hou\_angela/article/details/80519718](https://blog.csdn.net/hou_angela/article/details/80519718)
- 标准规范与实现库 [Package org.jsoup.select](https://jsoup.org/apidocs/org/jsoup/select/Selector.html)
- [获取 CSS 选择器语法](获取CSS选择器语法.md)
- 在线测试 [Try jsoup online](https://try.jsoup.org/)
~~~js
例子:
var document = org.jsoup.Jsoup.parse(html: String)
document.select(cssQuery: String)
注意:
需要使用完整包名,也可将完整包名赋值到变量名
var Jsoup = org.jsoup.Jsoup;
var document = Jsoup.parse(html: String)
~~~
### JsonPath
~~~js
com.jayway.jsonpath:json-path:2.8.0
~~~
- 语法见 [JsonPath教程](https://blog.csdn.net/koflance/article/details/63262484)
- 标准规范 [goessner JSONPath - XPath for JSON](https://goessner.net/articles/JsonPath/)
- 实现库 [json-path/JsonPath](https://github.com/json-path/JsonPath)
- 在线测试 [Jayway JsonPath Evaluator](http://jsonpath.herokuapp.com/)
~~~js
例子:
var document = com.jayway.jsonpath.JsonPath.parse(String json)
document.read(String path);
注意:
需要使用完整包名,也可将完整包名赋值到变量名
var JsonPath= com.jayway.jsonpath.JsonPath;
var document = JsonPath.parse(String json)
~~~
### XPath
~~~js
cn.wanghaomiao:JsoupXpath:2.5.3
~~~
- 语法见 [XPath教程-入门](https://www.w3school.com.cn/xpath/index.asp)、[XPath教程-基础](https://zhuanlan.zhihu.com/p/29436838)、[XPath教程-高级](https://zhuanlan.zhihu.com/p/32187820)、[XPath库的说明](https://github.com/zhegexiaohuozi/JsoupXpath/blob/master/README.md)
- 标准规范 [W3C XPATH 1.0](https://www.w3.org/TR/1999/REC-xpath-19991116/)
- 实现库 [zhegexiaohuozi/JsoupXpath](https://github.com/zhegexiaohuozi/JsoupXpath)
~~~js
例子:
var document = org.seimicrawler.xpath.JXDocument.create(html: String)
document.sel(xpath: String)
注意:
需要使用完整包名,也可将完整包名赋值到变量名
var JXDocument= org.seimicrawler.xpath.JXDocument;
var document = JXDocument.create(html: String)
~~~
### JavaUtils
~~~
/**
* base32 解码
* @param data 待处理数据
*/
base32Decode(byte[] data):byte[]
base32Decode(String data):byte[]
base32DecodeToString(byte[] data):String
base32DecodeToString(String data):String
/**
* base32 编码
* @param data 待处理数据
*/
base32Encode(byte[] data):byte[]
base32Encode(String data):byte[]
base32EncodeToString(byte[] data):String
base32EncodeToString(String data):String
/**
* base64 解码
* @param data 待处理数据
*/
base64Decode(byte[] data):byte[]
base64Decode(String data):byte[]
base64DecodeToString(byte[] data):String
base64DecodeToString(String data):String
/**
* base64 编码
* @param data 待处理数据
*/
base64Encode(byte[] data):byte[]
base64Encode(String data):byte[]
base64EncodeToString(byte[] data):String
base64EncodeToString(String data):String
/**
* byte[] 拼接
* @param prefix 前部分
* @param suffix 后部分
*/
bytesJoin(byte[] prefix, byte[] suffix):byte[]
/**
* byte[] 转十六进制字符
* @param bytes 待处理字节集
* @param isUpperCase 是否大写
*/
bytesToHexString(byte[] bytes):String
bytesToHexString(byte[] bytes, boolean isUpperCase):String
/**
* byte[] 转为 String
* @param bytes 待处理字节集
* @param charsetName 自定义编码
*/
bytesToStr(byte[] bytes):String
bytesToStr(byte[] bytes, String charsetName):String
/**
* 返回与具有给定字符串名称的类或接口关联的 Class 对象
* @param clazz 类
* @param className 类名
* @param isArray 是否输出为数组
*/
classForName(Class<?> clazz, boolean isArray):Class<?>
classForName(String className):Class<?>
classForName(String className, boolean isArray):Class<?>
/**
* 清理 Html 结构
* @param input 待处理数据
*/
cleanHtml(String input):String
/**
* URI 解码
* @param data 待处理数据
* @param charsetName 自定义编码
*/
decodeURI(String data):String
decodeURI(String data, String charsetName):String
/**
* 3DES 解密
* @param data 待处理数据
* @param key 密钥
* @param transformation 转换的名称,例如:DES/CBC/PKCS5Padding
* @param iv 带有 IV 的缓冲区
*/
decrypt3DES(byte[] data, byte[] key, String transformation):byte[]
decrypt3DES(byte[] data, byte[] key, String transformation, byte[] iv):byte[]
decrypt3DES(byte[] data, byte[] key, String transformation, String iv):byte[]
decrypt3DES(byte[] data, String key, String transformation):byte[]
decrypt3DES(byte[] data, String key, String transformation, byte[] iv):byte[]
decrypt3DES(byte[] data, String key, String transformation, String iv):byte[]
/**
* AES 解密
* @param data 待处理数据
* @param key 密钥
* @param transformation 转换的名称,例如:DES/CBC/PKCS5Padding
* @param iv 带有 IV 的缓冲区
*/
decryptAES(byte[] data, byte[] key, String transformation):byte[]
decryptAES(byte[] data, byte[] key, String transformation, byte[] iv):byte[]
decryptAES(byte[] data, byte[] key, String transformation, String iv):byte[]
decryptAES(byte[] data, String key, String transformation):byte[]
decryptAES(byte[] data, String key, String transformation, byte[] iv):byte[]
decryptAES(byte[] data, String key, String transformation, String iv):byte[]
/**
* DES 解密
* @param data 待处理数据
* @param key 密钥
* @param transformation 转换的名称,例如:DES/CBC/PKCS5Padding
* @param iv 带有 IV 的缓冲区
*/
decryptDES(byte[] data, byte[] key, String transformation):byte[]
decryptDES(byte[] data, byte[] key, String transformation, byte[] iv):byte[]
decryptDES(byte[] data, byte[] key, String transformation, String iv):byte[]
decryptDES(byte[] data, String key, String transformation):byte[]
decryptDES(byte[] data, String key, String transformation, byte[] iv):byte[]
decryptDES(byte[] data, String key, String transformation, String iv):byte[]
/**
* RSA 解密
* @param data 待处理数据
* @param privateKey 私钥
* @param keySize 密钥的大小,例如 1024, 2048...
* @param transformation 转换的名称,例如:RSA/CBC/PKCS1Padding
*/
decryptRSA(byte[] data, byte[] privateKey, int keySize, String transformation):byte[]
decryptRSA(byte[] data, String privateKey, int keySize, String transformation):byte[]
/**
* 检测编码
* @param data 待处理数据
* @param defaultCharset 默认编码
*/
detectCharset(byte[] data):String
detectCharset(byte[] data, String defaultCharset):String
/**
* URI 编码
* @param data 待处理数据
* @param charsetName 自定义编码
*/
encodeURI(String data):String
encodeURI(String data, String charsetName):String
encodeURIComponent(String data):String
encodeURIComponent(String data, String charsetName):String
/**
* 3DES 加密
* @param data 待处理数据
* @param key 密钥
* @param transformation 转换的名称,例如:DES/CBC/PKCS5Padding
* @param iv 带有 IV 的缓冲区
*/
encrypt3DES(byte[] data, byte[] key, String transformation):byte[]
encrypt3DES(byte[] data, byte[] key, String transformation, byte[] iv):byte[]
encrypt3DES(byte[] data, byte[] key, String transformation, String iv):byte[]
encrypt3DES(byte[] data, String key, String transformation):byte[]
encrypt3DES(byte[] data, String key, String transformation, byte[] iv):byte[]
encrypt3DES(byte[] data, String key, String transformation, String iv):byte[]
/**
* AES 加密
* @param data 待处理数据
* @param key 密钥
* @param transformation 转换的名称,例如:DES/CBC/PKCS5Padding
* @param iv 带有 IV 的缓冲区
*/
encryptAES(byte[] data, byte[] key, String transformation):byte[]
encryptAES(byte[] data, byte[] key, String transformation, byte[] iv):byte[]
encryptAES(byte[] data, byte[] key, String transformation, String iv):byte[]
encryptAES(byte[] data, String key, String transformation):byte[]
encryptAES(byte[] data, String key, String transformation, byte[] iv):byte[]
encryptAES(byte[] data, String key, String transformation, String iv):byte[]
/**
* DES 加密
* @param data 待处理数据
* @param key 密钥
* @param transformation 转换的名称,例如:DES/CBC/PKCS5Padding
* @param iv 带有 IV 的缓冲区
*/
encryptDES(byte[] data, byte[] key, String transformation):byte[]
encryptDES(byte[] data, byte[] key, String transformation, byte[] iv):byte[]
encryptDES(byte[] data, byte[] key, String transformation, String iv):byte[]
encryptDES(byte[] data, String key, String transformation):byte[]
encryptDES(byte[] data, String key, String transformation, byte[] iv):byte[]
encryptDES(byte[] data, String key, String transformation, String iv):byte[]
/**
* MD5 加密
* @param data 待处理数据
*/
encryptMD5(byte[] data):byte[]
encryptMD5(String data):byte[]
encryptMD5ToHexString(byte[] data):String
encryptMD5ToHexString(String data):String
/**
* RSA 加密
* @param data 待处理数据
* @param publicKey 公钥
* @param keySize 密钥的大小,例如 1024, 2048...
* @param transformation 转换的名称,例如:RSA/CBC/PKCS1Padding
*/
encryptRSA(byte[] data, byte[] publicKey, int keySize, String transformation):byte[]
encryptRSA(byte[] data, String publicKey, int keySize, String transformation):byte[]
/**
* MD5 加密
* @param data 待处理数据
*/
encryptSHA1(byte[] data):byte[]
encryptSHA1(String data):byte[]
encryptSHA1ToHexString(byte[] data):String
encryptSHA1ToHexString(String data):String
/**
* SHA224 加密
* @param data 待处理数据
*/
encryptSHA224(byte[] data):byte[]
encryptSHA224(String data):byte[]
encryptSHA224ToHexString(byte[] data):String
encryptSHA224ToHexString(String data):String
/**
* SHA256 加密
* @param data 待处理数据
*/
encryptSHA256(byte[] data):byte[]
encryptSHA256(String data):byte[]
encryptSHA224ToHexString(byte[] data):String
encryptSHA224ToHexString(String data):String
/**
* SHA384 加密
* @param data 待处理数据
*/
encryptSHA384(byte[] data):byte[]
encryptSHA384(String data):byte[]
encryptSHA384ToHexString(byte[] data):String
encryptSHA384ToHexString(String data):String
/**
* SHA512 加密
* @param data 待处理数据
*/
encryptSHA512(byte[] data):byte[]
encryptSHA512(String data):byte[]
encryptSHA512ToHexString(byte[] data):String
encryptSHA512ToHexString(String data):String
/**
* 字符串对比
*/
equals(String str1, String str2):boolean
/**
* 获取 URL 基础网址
* @param url 网址
*/
getBaseUrl(String url):String
/**
* Cookie 读取
* @param url 网址
* @param key 键
*/
getCookie(String url):String
getCookie(String url, String key):String
/**
* 获取设备语言
*/
getLanguage():String
/**
* 获取清单
*/
getManifest():Manifest
{
/**
* 获取搜索源唯一 id
*/
getId():long
/**
* 获取搜索源最低兼容版本
*/
getMinMyACG():int
/**
* 获取搜索源优先级
*/
getPriority():int
/**
* 搜索源是否列为否失效
*/
isEnableInvalid():boolean
/**
* 获取搜索源名称
*/
getName():String
/**
* 获取搜索源作者
*/
getAuthor():String
/**
* 获取搜索源邮箱
*/
getEmail():String
/**
* 获取搜索源版本
*/
getVersion():int
/**
* 获取搜索源最近更新时间
*/
getLastUpdateTime():String
/**
* 获取搜索源类型
*/
getType():int
/**
* 获取搜索源内容处理类型
*/
getContentProcessType():int
/**
* 获取搜索源基础网址
*/
getBaseUrl():String
/**
* 搜索源是否启用账号登录
*/
isEnableUserLogin():boolean
/**
* 获取搜索源用户账号登录状态
*/
isUserLoginStatus():boolean
/**
* 设置搜索源用户账号登录状态
* @param userLoginStatus 用户登录状态
*/
setUserLoginStatus(boolean userLoginStatus)
/**
* 搜索源是否启用
*/
isEnabled():boolean
/**
* 设置搜索源基础网址
* @param url 基础网址
*/
setBaseUrl(String baseUrl)
/**
* 设置搜索源用户登录网址
* @param userLoginUrl 用户登录网址
*/
setUserLoginUrl(String userLoginUrl)
/**
* 设置是否启用用户登录
* @param enableUserLogin 启用用户登录
*/
setEnableUserLogin(boolean enableUserLogin)
}
/**
* 获取首选项
*/
getPreference():Preference
{
getString(String key):String
getString(String key, String defValue):String
getBoolean(String key):boolean
getBoolean(String key, boolean defValue):boolean
getInt(String key):int
getInt(String key, int defValue):int
getLong(String key):long
etLong(String key, long defValue):long
getFloat(String key):float
getFloat(String key, float defValue):float
getDouble(String key):double
getDouble(String key, double defValue):double
/**
* 获取编辑对象
*/
edit():Editor
{
putString(String key, String value):Editor
putBoolean(String key, boolean value):Editor
putInt(String key, boolean value):Editor
putLong(String key, long value):Editor
putFloat(String key, float value):Editor
putDouble(String key, double value):Editor
remove(String key):Editor
/**
* 应用
*/
apply()
}
}
/**
* 获取网址文件名
* @param urlStr 网址
*/
getUrlFileName(String url):String
/**
* 获取网址 Scheme
* @param url 网址
*/
getUrlScheme(String url):String
getUrlScheme(String url):String
/**
* 十六进制字符转 byte[]
* @param hexString 十六进制字符
*/
hexStringToBytes(String hexString):byte[]
/**
* HTTP 请求
*/
httpRequest(String url):Response
{
/**
* 获取请求网址
*/
getUrl():String
url():String
/**
* 获取请求原始网址
*/
getOriginalUrl():String
originalUrl():String
/**
* 获取异常
*/
getError():String
error():String
/**
* 响应码
*/
getCode():int
code():int
/**
* 获取响应时间
*/
getTimeout():long
timeout():long
/**
* 获取响应体
*/
body():Body
{
/**
* 获取字符串类型数据
*/
getString():String
string():String
/**
* 获取字节集类型数据
*/
getBytes():byte[]
bytes():byte[]
/**
* 获取内容类型
*/
getContentTypeString():String
contentTypeString():String
/**
* 获取内容长度
*/
getContentLength():long
contentLength():long
/**
* 获取 org.jsoup.nodes.Document 对象
*/
getCssDocument():Document
cssDocument():Document
/**
* 获取 com.jayway.jsonpath.DocumentContext 对象
*/
getJsonDocument():DocumentContext
jsonDocument():DocumentContext
/**
* 获取 org.seimicrawler.xpath.JXDocument 对象
*/
getXpathDocument():JXDocument
xpathDocument():JXDocument
}
}
/**
* 判断是否为 Base64
* @param data 待处理数据
*/
isBase64(byte[] data):boolean
isBase64(String data):boolean
isBase64(String data):boolean
/**
* 判断字符串是否为空
* @param str 字符串
*/
isEmpty(String str):boolean
/**
* 判断网址是否为网络网址
* @param str 字符串
*/
isHttpsUrl(String url):boolean
isHttpUrl(String url):boolean
isNetworkUrl(String url):boolean
/**
* 判断字符串是否为空白
* @param str 字符串
*/
isSpace(String str):boolean
/**
* 判断字符串是否为有效的 URL
* @param str 字符串
*/
isValidUrl(String url):boolean
/**
* 调试输出
* @param msg 提示信息
*/
log(String msg):int
/**
* RC4 加密解密
* @param data 待处理数据
* @param key 密钥
*/
rc4(byte[] data, byte[] key):byte[]
rc4(byte[] data, String key):byte[]
/**
* 删除 Cookie
* @param url 网址
* @param key 键
*/
removeCookie(String url, String key)
/**
* 设置 Cookie
* @param url 网址
* @param values 值
*/
setCookie(String url, String values)
/**
* 字符串转时间
* @param time 时间
* @param patterns 时间格式,默认:yyyy-MM-dd HH:mm:ss
*/
stringToTime(String time):long
stringToTime(String time, String... patterns):long
/**
* 字符串转字节集
* @param str 字符串
* @param charsetName 自定义编码
*/
strToBytes(String str):byte[]
strToBytes(String str, String charsetName):byte[]
/**
* 字符串转 Unicode
* @param str 字符串
*/
strToUnicode(String str):String
/**
* 优先开头取字符串中间
* @param str1 左边字符串
* @param str2 右边字符串
* @param include1 左边字符串是否包括自身
* @param include2 右边字符串是否包括自身
*/
substring(String str, String str1):String
substring(String str, String str1, String str2):String
substring(String str, String str1, String str2, boolean include1, boolean include2):String
/**
* 字符串之间取字符串
* @param str1 左边字符串
* @param str2 右边字符串
* @param include1 左边字符串是否包括自身
* @param include2 右边字符串是否包括自身
*/
substringBetween(String str, String str1):String
substringBetween(String str, String str1, String str2):String
substringBetween(String str, String str1, String str2, boolean include1, boolean include2):String
/**
* 优先结尾取字符串中间
* @param str1 左边字符串
* @param str2 右边字符串
* @param include1 左边字符串是否包括自身
* @param include2 右边字符串是否包括自身
*/
substringLast(String str, String str1):String
substringLast(String str, String str1, String str2):String
substringLast(String str, String str1, String str2, boolean include1, boolean include2):String
/**
* 格式化时间字符串的毫秒数
* @param millis 毫秒
* @param pattern 时间格式,默认:yyyy-MM-dd HH:mm:ss
*/
timeFormat(long millis):String
timeFormat(long millis, String pattern):String
/**
* 去除字符串两边的空白字符
* @param str 字符串
*/
trim(String str):String
/**
* 构建完整的绝对 URL
* @param str 字符串
*/
urlJoin(String curl, String file):String
/**
* 使用 WebView 执行 js
* @param data 数据
* @param mimeType 数据的MIME类型,默认:text/html
* @param url 网址
* @param js JavaScript 代码
* @param isStart 代码执行时机,默认为false:待网页加载完成执行
* @param timeout 超时时间,默认10秒
*/
webViewEvalJS(byte[] data, String url, String js):String
webViewEvalJS(byte[] data, String url, String js, boolean isStart):String
webViewEvalJS(byte[] data, String url, String js, boolean isStart, long timeout):String
webViewEvalJS(byte[] data, String url, String js, long timeout):String
webViewEvalJS(byte[] data, String mimeType, String url, String js):String
webViewEvalJS(byte[] data, String mimeType, String url, String js, boolean isStart):String
webViewEvalJS(byte[] data, String mimeType, String url, String js, boolean isStart, long timeout):String
webViewEvalJS(byte[] data, String mimeType, String url, String js, long timeout):String
webViewEvalJS(String url, String js):String
webViewEvalJS(String url, String js, boolean isStart):String
webViewEvalJS(String url, String js, boolean isStart, long timeout):String
webViewEvalJS(String url, String js, long timeout):String
webViewEvalJS(String data, String url, String js):String
webViewEvalJS(String data, String url, String js, boolean isStart):String
webViewEvalJS(String data, String url, String js, boolean isStart, long timeout):String
webViewEvalJS(String data, String url, String js, long timeout):String
webViewEvalJS(String data, String mimeType, String url, String js):String
webViewEvalJS(String data, String mimeType, String url, String js, boolean isStart):String
webViewEvalJS(String data, String mimeType, String url, String js, boolean isStart, long timeout):String
webViewEvalJS(String data, String mimeType, String url, String js, long timeout):String
~~~
※在处理返回类型`String`时,尽量使用`JavaScript`的`String()`强转一下。
## 三、操作符
### 1.网络请求操作符
~~~
//POST 请求
url@post->data
//指定 IP 地址
url@ip->127.0.0.1
//指定代理
url@proxy->http://127.0.0.1:1080
url@proxy->socks5://127.0.0.1:1080
url@proxy->socks5://username:password@127.0.0.1:1080
//启用框架源码模式(内部使用 WebView 实现),非必要不建议使用
//优势:获取的网页源码更全
//劣势:由于是基于 WebView,占用系统资源更多,耗时更长等问题,仅支持单线程
url@enableFrameSource->true
//添加请求头
url@header->referer:https://www.example.com
url@header->user-agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1661.54
//请求头中支持自定义 UA 系统、平台
url@header->user-agent-system:Win64
//自定义平台仅作用于 WebView
url@header->user-agent-platform:Win32
//注意:当被执行请求时,会被忽略掉
//实际使用中允许组合:
url@post->data@ip->127.0.0.1@header->referer:https://www.example.com@header->user-agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1661.54
实例:
GET 请求
JavaUtils.httpRequest("https://www.example.com");
POST 请求
JavaUtils.httpRequest("https://www.example.com@post->data");
添加请求头
JavaUtils.httpRequest("https://www.example.com@header->user-agent:referer:https://www.example.com");
添加多个请求头
JavaUtils.httpRequest("https://www.example.com@header->user-agent:referer:https://www.example.com@header->Host:www.baidu.com");
~~~
| 操作符| 是否可以复用 |
| :-: | :-: |
| @post-> | × |
| @ip-> | × |
| @proxy-> | × |
| @header-> | ✔ |
### 2.章节网址操作符
~~~
{N}:从 N 开始递增的序号,其中的 N 必须为一个具体的数字,例如 {0}、{1}
{zN}:与 {N} 类似,区别是会以 z 作为 0 的数量进行补零对齐,z 必须为 0 ,例如 {008} 运行时会以 {008}、{009}、{010}...
在实际使用中网址例子:
https://www.example.com/100_{001}.html
添加多个:
https://www.example.com/100_{01}_{001}.html
~~~
| 操作符| 是否可以复用 |
| :-: | :-: |
| {N} | ✔ |
| {zN} | ✔ |
搜索源实例:[爱米推漫画](https://gitcode.net/Cynric_Yx/MyACGSourceRepository/-/raw/master/sources/爱米推漫画.js)
### 3.图片网址操作符
~~~
//指定图片呈现宽高,仅适用于图库 #案例:解决包子漫画图片变形
//宽度
srcUrl@imageWidth->100
//高度
srcUrl@imageHeight->100
//图片解码方法名
srcUrl@imageDecoderFunctionName->functionName
~~~
| 操作符| 是否可以复用 |
| :-: | :-: |
| @imageWidth-> | × |
| @imageHeight-> | × |
| @imageDecoderFunctionName-> | × |
搜索源实例:[包子漫画](https://gitcode.net/Cynric_Yx/MyACGSourceRepository/-/raw/master/sources/包子漫画.js)、[36漫画](https://gitcode.net/Cynric_Yx/MyACGSourceRepository/-/raw/master/sources/36%E6%BC%AB%E7%94%BB.js)
## 四、搜索源 - 清单(manifest)
*请勿在 manifest 内执行耗时操作代码
- **搜索源标识** `id`
- 类型 `long`
- 必填
- 唯一标识,不可重复
- 与其他搜索源相同会被覆盖
- 获取方式:[前往生成](https://tool.lu/timestamp/)(十位数,精确到秒)
- **最低兼容 MyACG 版本** `minMyACG`
- 类型 `int`
- 必填
- 建议填写 `20240122`
- 高版本无法安装在低版本 MyACG 中
- **优先级** `priority`
- 类型 `int`
- 非必填
- 范围(1~100),数值越大越靠前
- **启用失效** `enableInvalid`
- 类型 `boolean`
- 非必填
- 默认 `false`
- `true`: 无法安装,并且已安装的变灰,用于解决失效源
- **搜索源名称** `name`
- 类型 `String`
- 必填
- **搜索源制作人** `author`
- 类型 `String`
- 非必填
- **电子邮箱** `email`
- 类型 `String`
- 非必填
- **搜索源版本号** `version`
- 类型 `int`
- 非必填
- **自述文件网址列表** `readmeUrlList`
- 非必填
- **同步网址列表** `syncList`
- 非必填
- **最近更新时间** `lastUpdateTime`
- 类型 `String` `long`
- 非必填
- **搜索源类型** `type `
- 类型 `int`
- 必填
- 范围:(1:网页,2:图库,3:视频,4:书籍,5:音频,6:图片)
- **内容处理方式** `contentProcessType`
- 类型 `int`
- 必填
- 范围:(-1: 搜索相似,0:对网址处理并调用外部APP访问,1:对网址处理,2:对内部浏览器拦截)
- **内容加载选项** `contentLoadingOptionList`
- 非必填
- **翻页模式** `pageMode`
- 类型 `int`
- 非必填
- **翻页模式 - 倒序** `enablePageModeReverseOrder`
- 类型 `boolean`
- 非必填
~~~js
function manifest() {
return JSON.stringify({
...
contentLoadingOptionList: {
pageMode: 1,
enablePageModeReverseOrder: false
}
...
}
}
~~~
- **首选项列表** `preferenceList`
- 非必填
- **类型** `type`
- 类型 `int`
- 必填
- 范围:(1:文本框,2:开关,3:单选框,4:编辑框,5:跳转网址)
- **键** `key`
- 类型 `String`
- 如果(type = 2,type = 3,type = 4):必填
- **名称** `name`
- 类型 `String`
- 必填
- **概览** `summary`
- 类型 `String`
- 非必填
- **项目列表** `itemList`
- 类型 `String`
- 非必填
- **默认值** `defaultValue`
- 类型 type = 2 :`boolean` 、type = 3 : `int` 、type = 4 : `String`
- 非必填
简单编写
~~~js
function manifest() {
return JSON.stringify({
...
preferenceList: [
{
type: 1,
locationList: ["sourceDetail","detail"],
bindDetail: false,
name: "按钮例子"
},
{
type: 2,
key: "switch",
locationList: ["sourceDetail","detail"],
bindDetail: false,
name: "开关例子",
defaultValue: true
},
{
type: 3,
key: "radio",
locationList: ["sourceDetail","detail"],
bindDetail: false,
name: "单选框例子",
itemList: {
"www.example.com": "https://www.example.com",
},
defaultValue: 0
},
{
type: 4,
key: "edit",
locationList: ["sourceDetail","detail"],
bindDetail: false,
name: "编辑框例子",
defaultValue: "默认内容"
},
{
type: 5,
locationList: ["sourceDetail","detail"],
bindDetail: false,
name: "跳转网址",
defaultValue: "https://www.example.com"
}
]
...
}
}
~~~
进阶编写
~~~js
function manifest() {
return JSON.stringify({
...
preferenceList: [
{
type: 1,
locationList: ["sourceDetail","detail"],
bindDetail: false,
name: "按钮例子"
functionName: "clickListener",
},
{
type: 2,
key: "switch",
locationList: ["sourceDetail","detail"],
bindDetail: false,
name: "开关例子",
defaultValue: true
functionName: "clickListener",
},
{
type: 3,
key: "radio",
locationList: ["sourceDetail","detail"],
bindDetail: false,
name: "单选框例子",
functionName: "getItemList"
},
{
type: 4,
key: "edit",
locationList: ["sourceDetail","detail"],
bindDetail: false,
name: "编辑框例子",
defaultValue: "默认内容"
functionName: "clickListener"
},
{
type: 5,
locationList: ["sourceDetail","detail"],
bindDetail: false,
name: "跳转网址",
functionName: "getUrl"
}
]
...
}
}
/**
* 获取项目列表
* @param {string} url 详情页url,仅在详情页、阅读页调用时不为 null
*/
function getItemList(url) {
return JSON.stringify({
//默认值
defaultValue: "二",
//项目列表
itemList: [
{
//名称
name: "选项一",
//概览
summary: "这是选项一",
//值
value: "一"
},
{
//名称
name: "选项二",
//概览
summary: "这是选项二",
//函数名
functionName: "clickListener",
//值
value: "二"
}
]
});
}
/**
* 点击事件
* @param {string} url 详情页url,仅在详情页、阅读页调用时不为 null
* @param {string} value 传入的值,没有则为 null
*/
function clickListener(url, value) {
...
}
/**
* 获取跳转网址
* @param {string} url 详情页url,仅在详情页、阅读页调用时不为 null
*/
function getUrl(url) {
return "https://www.example.com"
}
~~~

- **分组** `group`
- 必填
- **基本网址** `baseUrl`
- 类型 `String`
- 必填
- **全局 http 请求头列表** `httpRequestHeaderList`
- 非必填
~~~js
function manifest() {
return JSON.stringify({
...
httpRequestHeaderList: {
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1661.54"
}
...
}
}
~~~
- **主机域名映射列表** `hostsList`
- 非必填
~~~js
function manifest() {
return JSON.stringify({
...
hostsList: {
"www.example.com": "127.0.0.1",
}
...
}
}
~~~
- **网络限流** `networkRateLimitList`
- 非必填
- **正则网址** `regexUrl`
- 必填
- **在指定的时间内允许的请求数量** `maxRequests`
- 必填
- **时间周期** `period`
- 必填
~~~js
function manifest() {
return JSON.stringify({
...
//网络限流 - 如果{regexUrl}匹配网址,则限制其{period}毫秒内仅允许{maxRequests}个请求
networkRateLimitList: [
{
regexUrl: "colamanhua\.com",//表示需要限流的 Url,使用正则表达式格式(不允许为空)
maxRequests: 1,//在指定的时间内允许的请求数量(必须 >= 0 才会生效)
period: 5000,//时间周期,毫秒(必须 > 0 才会生效)
}
]
...
}
}
~~~
- **发现列表** `findList`
- 非必填
- **分类** `category`
- **标签**(`default` 或 其他自定义标签名)使用 `default` 表示默认不设标签
- **方法名** `functionName`
- 类型 `String`
- **参数** `param`~~[tips:实际 functionName 固定外, param 不做硬性要求]~~
- 类型 `String`
简单编写
~~~js
function manifest() {
return JSON.stringify({
...
findList: {
"日本动漫": {
"最近更新": {
function: "find",
param: "www.example.com"
}
}
}
...
}
}
/**
* 发现
* @return {[{name, summary, coverUrl, url}]}
*/
function find(param) {
...
}
~~~
进阶编写
~~~js
function manifest() {
return JSON.stringify({
...
findList: {
category: {
"state": {
"全部": "all",
"连载中": "serial",
"已完结": "pub"
},
"label": {
"全部": "all",
"修仙": "xiuxian"
},
"year": ["2023","2022","2021"]//如果 key 与 value 相同,可以简化这样
},
//使用 default 表示默认不设标签
default: {
functionName: "find",
param: ["state","label"]
},
"日本动漫": {
"最近更新": {
functionName: "find2",
param: ["state","label","year"]
},
"新番推荐": "www.example.com" //默认 functionName 为 find
}
}
...
}
}
/**
* 发现
* @return {[{name, summary, coverUrl, url}]}
*/
function find(state, label) {
...
}
/**
* 发现
* @return {[{name, summary, coverUrl, url}]}
*/
function find2(state, label, year) {
...
}
~~~
- **启用用户登录** `enableUserLogin`
- 类型 `boolean`
- 非必填
- **用户登录网址** `userLoginUrl`
- 类型 `String`
- 如果`enableUserLogin: true`,则必填,否则非必填
- **需要用户登录列表** `requiresUserLoginList`
- 如果`enableUserLogin: true`,则必填,否则非必填
- 范围:(search,detail,content,find)
~~~js
function manifest() {
return JSON.stringify({
...
requiresUserLoginList: ["search","detail","content"],
...
}
}
~~~
范例:
~~~js
function manifest() {
return JSON.stringify({
id: 1694436908,
minMyACG: 20240122,
name: "demo",
type: 1,
contentProcessType: 1,
group: ["demo"],
baseUrl: "http://www.example.com",
});
}
~~~
搜索源实例:[COCO漫画](https://gitcode.net/Cynric_Yx/MyACGSourceRepository/-/raw/master/sources/cocoManga.js)、[36漫画](https://gitcode.net/Cynric_Yx/MyACGSourceRepository/-/raw/master/sources/36漫画.js)、[樱花动漫](https://gitcode.net/Cynric_Yx/MyACGSourceRepository/-/raw/master/sources/dmh8樱花动漫.js)
### type 配合 contentProcessType 实现不同操作列出
| 类型(type) | 内容处理方式(contentProcessType) | 实现效果 | 详情是否必要 |
| :-: | --- | --- | :-: |
| 任意类型 | -1:搜索相似 | 搜索相似 | × |
| 网页(1)/音频(5)/图片(6) | 0:对网址处理并调用外部APP访问 | 对内容处理,完成后调用外部浏览器 |× |
| 网页(1) | 1:对网址处理并调用内部功能访问 | 对内容处理,完成后调用内部浏览器 |× |
| 音频(5) | 1:对网址处理并调用内部功能访问 | 对内容处理,完成后进入音频播放器加载 |× |
| 图片(6) | 1:对网址处理并调用内部功能访问 | 对内容处理,完成后进入弹出图片加载 |× |
| 网页(1)/音频(5)/图片(6) | 2:对网页请求拦截 | 进入内部浏览器,对网页请求拦截和注入 js 代码 |× |
| 视频(3)/书籍(4)/图库(2) | 0:对网址处理 | 进入详情页,点击章节,处理内容完成后调用外部 App / 支持复制网址 | ✔ |
| 视频(3)/书籍(4)/图库(2) | 1:对网址处理并调用内部功能访问 | 进入详情页,点击章节,进入解析页对网址进行处理 | ✔ |
| 视频(3)/书籍(4)/图库(2) | 2:对网页处理 | 进入详情页,点击章节,进入内部浏览器加载,对网页请求拦截和注入 js 代码 | ✔ |
## 五、搜索源 - 搜索、发现 `search`
- **名称** `name`
- 类型 `String`
- 必填
- **名称图片网址** `nameImgUrl`
- 类型 `String`
- 非必填
- 支持 [网络请求操作符](#1_836)、[图片网址操作符](#3_898)
- 搜索源实例 [EDD动漫](https://gitcode.net/Cynric_Yx/MyACGSourceRepository/-/raw/master/sources/EDD动漫.js)
- **作者** `author`
- 类型 `String`
- 非必填
- **最后章节名称** `lastChapterName`
- 类型 `String`
- 非必填
- **最近更新时间** `lastUpdateTime`
- 类型 `String` `long`
- 非必填
- **概览** `summary`
- 非必填
- **搜索源子名称** `sourceSubName`
- 类型 `String`
- 非必填
- **封面网址** `coverUrl`
- 类型 `String`
- 必填
- 支持 [网络请求操作符](#1_836)、[图片网址操作符](#3_898)
- **网址** `url`
- 类型 `String`
- 必填
- 支持 [网络请求操作符](#1_836)
范例:
```js
/**
* 搜索
* @param {string} key
* @return {[{name, author, lastChapterName, lastUpdateTime, summary, coverUrl, url}]}
*/
function search(key) {
var url = JavaUtils.urlJoin(baseUrl, '/search?&query=' + encodeURI(key));
var result = [];
const response = JavaUtils.httpRequest(url);
if(response.code() == 200){
const document = response.body().cssDocument();
var elements = document.select("#cata_video_list > div > div");
for (var i = 0;i < elements.size();i++) {
var element = elements.get(i);
result.push({
//名称
name: element.selectFirst('.card-title').text(),
//最后章节名称
lastChapterName: element.selectFirst('.video_play_status').text(),
//最近更新时间
lastUpdateTime: element.selectFirst('.lastUpdateTime').text(),
//概览
summary: element.selectFirst('.desc > :matchText').text(),
//封面网址
coverUrl: element.selectFirst('div.video_cover > div > a > img').absUrl('data-original'),
//网址
url: element.selectFirst('.card-title > a').absUrl('href')
});
}
}
return JSON.stringify(result);
}
```
```js
/**
* 发现
* @return {[{name, author, lastChapterName, lastUpdateTime, summary, coverUrl, url}]}
*/
function find(url) {
var url = JavaUtils.urlJoin(baseUrl, url);
var result = [];
const response = JavaUtils.httpRequest(url);
if(response.code() == 200){
const document = response.body().cssDocument();
var elements = document.select("div.video_item");
for (var i = 0;i < elements.size();i++) {
var element = elements.get(i);
result.push({
//名称
name: element.selectFirst('.video_item-title > a').text(),
//最后章节名称
lastChapterName: element.selectFirst('.video_item--info').text(),
//最近更新时间
lastUpdateTime: element.selectFirst('.lastUpdateTime').text(),
//概览
summary: element.selectFirst('.desc > :matchText').text(),
//封面网址
coverUrl: element.selectFirst('.video_item--image > img').absUrl('data-original'),
//网址
url: element.selectFirst('.video_item-title > a').absUrl('href')
});
}
}
return JSON.stringify(result);
}
```
搜索源实例:[熔岩番剧库](https://gitcode.net/Cynric_Yx/MyACGSourceRepository/-/raw/master/sources/熔岩番剧库.js)、[36漫画](https://gitcode.net/Cynric_Yx/MyACGSourceRepository/-/raw/master/sources/36%E6%BC%AB%E7%94%BB.js)、[AGE动漫](https://gitcode.net/Cynric_Yx/MyACGSourceRepository/-/raw/master/sources/AGE动漫.js)
## 六、搜索源 - 详情 `detail`
- **名称** `name`
- 类型 `String`
- 非必填
- **作者** `author`
- 类型 `String`
- 非必填
- **最近更新时间** `lastUpdateTime`
- 类型 `String` `long`
- 非必填
- **概览** `summary`
- 类型 `String`
- 非必填
- **搜索源子名称** `sourceSubName`
- 类型 `String`
- 非必填
- **封面网址** `coverUrl`
- 类型 `String`
- 非必填
- 支持 [网络请求操作符](#1_836)、[图片网址操作符](#3_898)
- **启用将章节置为倒序** `enableChapterReverseOrder`
- 非必填
- **目录列表** `tocs`
- 必填
- **目录名称** `name`
- 类型 `String`
- 必填
- **章节列表** `chapters`
- 必填
- **章节名称** `name`
- 类型 `String`
- 必填
- **章节网址** `url`
- 类型 `String`
- 必填
- 支持 [网络请求操作符](#1_836)、[章节网址操作符](#2_882)
范例:
```js
/**
* 详情
* @return {[{name, author, lastUpdateTime, summary, coverUrl, enableChapterReverseOrder, tocs:{[{name, chapter:{[{name, url}]}}]}}]}
*/
function detail(url) {
const response = JavaUtils.httpRequest(url);
if(response.code() == 200){
const document = response.body().cssDocument();
return JSON.stringify({
//标题
name: document.selectFirst('.video_detail_title').text(),
//作者
author: document.selectFirst('li:nth-child(5) > span.detail_imform_value').text(),
//最近更新时间
lastUpdateTime: document.selectFirst('li:nth-child(7) > span.detail_imform_value').text(),
//概览
summary: document.selectFirst('div.video_detail_desc').text(),
//封面网址
coverUrl: document.selectFirst('div.video_detail_cover > img').absUrl('data-original'),
//启用章节反向顺序
enableChapterReverseOrder: false,
//目录加载
tocs: tocs(document)
});
}
return null;
}
/**
* 目录
* @returns {[{name, chapters:{[{name, url}]}}]}
*/
function tocs(document) {
//目录标签元素选择器
const tagElements = document.select('.nav-item');
//目录元素选择器
const tocElements = document.select('.tab-pane');
//创建目录数组
var newTocs = [];
for (var i = 0;i < tocElements.size();i++) {
//创建章节数组
var newChapters = [];
//章节元素选择器
var chapterElements = tocElements.get(i).select('ul > li');
for (var i2 = 0;i2 < chapterElements.size();i2++) {
var chapterElement = chapterElements.get(i2);
newChapters.push({
//章节名称
name: chapterElement.selectFirst('a').text(),
//章节网址
url: chapterElement.selectFirst('a').absUrl('href')
});
}
newTocs.push({
//目录名称
name: tagElements.get(i).selectFirst('li').text(),
//章节
chapters : newChapters
});
}
return newTocs;
}
```
搜索源实例:[熔岩番剧库](https://gitcode.net/Cynric_Yx/MyACGSourceRepository/-/raw/master/sources/熔岩番剧库.js)、[36漫画](https://gitcode.net/Cynric_Yx/MyACGSourceRepository/-/raw/master/sources/36%E6%BC%AB%E7%94%BB.js)、[AGE动漫](https://gitcode.net/Cynric_Yx/MyACGSourceRepository/-/raw/master/sources/AGE动漫.js)
## 七、搜索源 - 内容 `content`
### 对网址处理 `contentProcessType == 0 || contentProcessType == 1`
- 图库`type = 2`
- 支持 [网络请求操作符](#1_836)、[图片网址操作符](#3_898)
~~~js
/**
* 对网址处理(contentProcessType == 0 || contentProcessType == 1)
* @return {string} {[{url}]}
*/
function content(url) {
return JSON.stringify(null);
}
~~~
- 书籍`type = 4`
~~~js
/**
* 对网址处理(contentProcessType == 0 || contentProcessType == 1)
* @return {string} str
*/
function content(url) {
return null;
}
~~~
- 其他`type = 3, type = 5, type = 6`
~~~js
/**
* 对网址处理(contentProcessType == 0 || contentProcessType == 1)
* @return {string} url
*/
function content(url) {
return null;
}
~~~
### 图库内容解码`contentProcessType == 1`
- 图库`type = 2`
- 非必填
```js
/*
* 图库内容解码
* @param {byte[]} bytes
* @return {byte[]} bytes
*/
function galleryContentDecoder(bytes) {
return null;
}
```
搜索源实例:[36漫画](https://gitcode.net/Cynric_Yx/MyACGSourceRepository/-/raw/master/sources/36%E6%BC%AB%E7%94%BB.js)
### 对网页请求拦截`contentProcessType == 2`
~~~js
/**
* 对网页请求拦截`contentProcessType == 2`
* @return {string} url
*/
function content(url) {
return null;
}
~~~
搜索源实例:[樱花动漫](https://gitcode.net/Cynric_Yx/MyACGSourceRepository/-/raw/master/sources/dmh8%E6%A8%B1%E8%8A%B1%E5%8A%A8%E6%BC%AB.js)
### 对网页注入 JS 脚本`contentProcessType == 2`
- 非必填
~~~js
/**
* 对网页注入 JS 脚本(contentProcessType == 2)
* @return {string} url
* @param {boolean} isStart:运行时机{true:页面加载前,false:页面加载完成后}
* @return {string} js 代码
*/
function webPageLoadJavaScript(url, isStart) {
return null;
}
~~~
## 八、搜索源 - 登录检查
### 是否完成登录 `isUserLoggedIn`
- 非必填,如果`enableUserLogin == true, userLoginUrl != null`则必填
```js
/*
* 是否完成登录
* @param {string} url 网址
* @param {string} responseHtml 响应源码
* @return {boolean} 登录结果
*/
function isUserLoggedIn(url, responseHtml) {
return false;
}
```
### 验证完成登录 `verifyUserLoggedIn`
- 非必填,如果`enableUserLogin == true, userLoginUrl != null`则必填
```js
/*
* 验证完成登录
* @return {boolean} 登录结果
*/
function verifyUserLoggedIn() {
return false;
}
```
搜索源实例:[熔岩番剧库](https://gitcode.net/Cynric_Yx/MyACGSourceRepository/-/raw/master/sources/熔岩番剧库.js)
更多实例:
https://github.com/ylk2534246654/MyACGSourceRepository.git
https://jihulab.com/ylk2534246654/MyACGSourceRepository.git
https://gitlab.com/ylk2534246654/MyACGSourceRepository.git
https://gitcode.net/Cynric_Yx/MyACGSourceRepository.git
https://gitlink.org.cn/ylk2534246654/MyACGSourceRepository.git
