AI写作智能体 自主规划任务,支持联网查询和网页读取,多模态高效创作各类分析报告、商业计划、营销方案、教学内容等。 广告
## 搜索源介绍 MyACG 是基于搜索源提供的网络搜索、网络发现。 通过添加搜索源,可以为 MyACG 增加和提升更强大的搜索能力,而且这些搜索源完全可以由你来进行编写。 ## 目录 [TOC] ## 一、快速开始 ### 创建搜索源文件 创建一个文件,除后缀必须为 js 以外文件名随便命名,这里以 **demo.js** 为例 ![](https://img.kancloud.cn/da/59/da59d1f9ee7198ae6ccd2319611ecd70_101x117.png) ### 添加清单 打开文件并添加 **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 内剪贴板导入它,只是这个搜索源没有任何功能。 ![](https://img.kancloud.cn/ce/cc/cecc136d1a81fd008be8582d3310b11b_1116x1260.jpg) ### 实现搜索功能 添加 **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 内剪贴板导入它 ![](https://img.kancloud.cn/a1/77/a177dbdb2cfbb02ad40edbad97aa33cf_1116x1376.jpg) 此时权限一栏出现了搜索,点击进入搜索页面,随便搜索一个关键字就会出现结果 ![](https://img.kancloud.cn/15/9a/159aaab697c4f45c66f3155f86532922_1116x888.jpg) 点击搜索结果便会跳转至对应网页 ![](https://img.kancloud.cn/53/97/539779c75917832d3d83e4a473d77309_1116x1156.jpg) ## 二、在 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" } ~~~ ![](https://img.kancloud.cn/15/47/15478cd410eb4b37ef095b055baf4709_540x373.jpg =540x373) - **分组** `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