Tachiyomi插件编写的一点总结
2024-11-4|2024-11-20
AlphaBoom
type
status
date
slug
summary
tags
category
icon
password
最近一段时间在Tachiyomi和Aniyomi上写了一些插件,虽然没有特别的技术难点但是其中有些东西如果没有经验其实是蛮花费时间的。想了下由于涉及的功能非常零碎,如果过几个月还想再编写插件可能就忘记了,遂决定写一篇文章作为记录。
Tachiyomi与其插件
Tachiyomi是一个漫画软件,它支持安装不同的插件来支持阅读不同源的漫画内容。一言以蔽之就是一个多合一盗版漫画阅读软件,现在Tachiyomi已经不复存在不过有相当多的继承者,目前精神继承者为Mihon,依然处于活跃的开发维护状态。
这类软件的流行的核心逻辑在与其插件系统,由于漫画最重要的就是漫画资源,能够持续提供可用的插件生态才是这类软件存在的核心。国内也有一些插件类的漫画软件,不过由于作者最终转向商业化以及贡献者几乎只有作者最终都没什么声音了,所以就算国内比较闭塞的生态下也被迫接轨了国际环境。
扯了些没用的开始进入正题,首先要知道这些插件做了什么才好决定如何开发插件,以及需要预先知道哪些知识。
插件需要提供的能力
支持搜索
提供一个途径去发现漫画
- 通过关键字搜索漫画
- 热门作品列表
- 最近更新列表
- 高级搜索,例如支持筛选
获取章节列表
漫画的重要组成部分,例如连载漫画都是分章节的。
获取图片列表
漫画的核心组成部分。
其他信息获取
作者信息,漫画描述信息以及封面等等。
获取资源
需要先分析站点资源,确定如何获取插件需要的数据才好开始插件开发。数据源分为两类Web端以及App端,一般来说数据源也都是盗版站点只提供Web端的比较多。其他的一般同时提供Web和App,只有很小的部分只支持App端。Web端的优势是分析需要准备的工具要求几乎没有,难度也相对较低(差不多是有心破解的话就能破解出来),但是相对更新频率较高变化较大意味着插件也很可能需要相对高频率的更新。App端的缺点是分析准备工具较多难度也偏高,不过好处是几乎不会更新也很少强制做验证码限制。
通过Web端获取资源
Web端的操作全部在浏览器上,打开浏览器输入目标网址开搞就好了。
简单Web应用流程
简单的Web应用就是所有的内容都会在Html中呈现,所以只要解析Html内容提取有效信息就好了。另外其实浏览器本身就是Web应用的调试工具,直接使用浏览器自带的功能可以更快速的分析出内容位置。
- 打开开发者工具
默认窗口是嵌入当前页面右侧的,为了方便后续调试最好把窗口单独拿出来,点击开发者工具右上角三个点修改为Dock side。
- 查找目标资源位置,选择工具左上角的箭头图标。之后在Web界面移动就可以定位到对应Html位置。
- 之后需要花费一点点时间学习CSS 选择器,验证选择器的效果可以通过在Console里输入
$(".cy_list_mh")
之类的代码来执行,鼠标去选择元素也会同步在Web界面上展示选择效果。
注意这里$是来自于JQuery库,虽然大部分网站都会引入该库但是也可能不引入,使用不了可以尝试$$(来自其他常用库)。如果都不行可以调用内置方法
document.querySelectorAll(".cy_list_mh")
,不过这样有些高级特性会测试不了例如contains(这个在实际编写中也算比较常用的)。基础流程就是以上,不过CSS selector规则可能显得过于复杂,可以先记忆以下几种方式遇到不满足再去查文档:
选择器 | 示例 | 说明 |
.class | .cy_list_mh | 例如选择该节点<div class=”cy_list_mh”> |
#id | .abc | 例如选择该节点<div id=”abc”> |
element | p | 选择所有<p>元素 |
element,element | div,p | 选择所有<div>元素和 <p> 元素 |
element element | div p | 选择<div>元素内的所有<p>元素 |
element>element | div>p | 选择所有父级是 <div> 元素的 <p> 元素 |
[attribute] | [target] | 选择所有带有target属性元素,例如:<div target=”xxx”> |
然后以上是可以组合的例如:
div#list1.abc div.abc a[herf]
这段就是说选择<div id="list1" class="abc">
里面的<div class="abc">
里面的<a herf="xxxx">
的意思。插件场景其实用的并不复杂,一般通过组合.class和element就可以定位到相关元素。最后搜索功能的话会稍有不同因为存在变化的部分,这种变化一般会体现在url地址上也就是不同搜索关键字对应不同网页,按照规律构建网页链接就可以了。
包含动态变化的Web应用
一些网站的资源列表是会通过Javascript代码动态修改HTML元素的,由于这个HTML是动态变化的所以很可能通过选择器解析不到对应的元素。以下有两种方案选择:
- 使用设备的Webview加载网页,等待网页加载完毕再使用选择器去查找元素。
- 分析Javascript代码,直接获取到数据源而不依赖与通过选择器获取的数据源。
1的方式看起来较通用,但是面临以下问题:
- 资源加载数量较多,耗时较长
- 网页加载完毕并不意味对应的数据也已经渲染完成
- 网页可能存在一个操作进行阻拦,这个使用一般的判断渲染完成的措施也会失效
所以1有很多场景会回退到需要2的方式,另外在Tachiyomi插件中没有提供一个比较完善的1类解决策略基础组件,所以一般都是直接使用2的方式。
→判断是否为动态变化的网页内容
在开发者工具中选择Network页面,查看请求网页接口的返回数据是否包含需要的元素,如果不存在那就说明内容会动态修改。(没有数据的情况刷新一下当前页面)
→判断是否通过发起另一个请求获取的数据
将筛选设置为Fetch/XHR查看是否有请求
如果确认返回的数据是有价值的,可以确认下请求携带的参数。如果参数没有特别的携带内容就可以直接使用,如果存在特别的携带内容就需要去排查Javascript代码是如何生成这些参数的。将请求页面的tab移动到Initator就可以看到是哪里的代码调用了请求。
点击就可以调转到对应的Javascript代码进行代码逻辑分析,如果直接看逻辑比较复杂也可以设置断点Debug。
→非请求类的动态变化
这类一般是图片懒加载,一般会在图片的自定义属性上设置图片地址,然后按需把图片地址设置为src。这种情况观察网络请求返回的HTML内容作为基础,修改为正确的选择器就好了。
还有一类是把数据加密后放在HTML中,之后由Javascript代码解密数据后更新HTML渲染出真实的界面。关于如何处理这部分内容,请查阅受保护的Web应用部分。
受保护的Web应用
一些网站会通过加密、混淆代码等方式来防止资源被盗取。这类主要的障碍是在混淆部分,因为加密逻辑只要通过分析Javascript代码就可以知道了。
混淆代码的去混淆可以尝试以下方式:
- 贴给ChatGPT进行分析(虽然可能感觉不靠谱,不过这个方式确实是有效的)
如果以上行不通,还可以尝试断点Debug。不过做了防护一般也会做反Debug,如果选择不去解决反Debug的问题,也可以尝试如下策略,因为混淆的代码只要能运行起来就会变得好分析。在开发者工具中按
ctrl+shif+p
开启命令行输入框,输入Create new snippet来创建一段可执行的Javascript代码。这些Javascript由于可以运行在当前网站环境下,不需要处理额外的资源依赖非常方便。下面给一个处理混淆文件的例子,在这里我发现其中一个函数可以获取可读的名字,再通过一个脚本把原本混淆的名字进行替换,之后就可以更容易定位到关键函数位置:
像这样操作下去就可以不断根据自己排查的信息来让混淆文件变得可读。
→受Cloudflare保护的
如果是Cloudflare自动跳转的话,Tachiyomi中已经做了内置支持。如果是需要额外验证操作的话,这部分并没有很好的解决方案。由于实现复杂不建议在这里多耗时间,对于这类情况可以开启Webview,让用户进行手动验证。
其他
在解析出网络请求之后,也可能在实现中由于携带请求头不合法被服务器拒绝。这个时候可以通过Postman类的软件来模拟请求,测试哪些字段是会被服务端验证的,确认后保证自己构建的请求包含这些字段,通常来说要注意Cookie和Referer的请求头信息。
通过App端获取资源
App端仅对Android端进行分析,主要分为两类:
- 原生Android应用
- Flutter应用
App端由于权限设置,所以在开始分析之前请务必准备一个已经Root的手机。如果没有,使用模拟器也可以不过后续会有些限制,当你想进一步分析的时候模拟器会面临许多难以甚至无法解决问题。
抓包准备
不像Web端浏览器一个就可以处理所有问题,App端需要借助其他工具才能开始分析。这里的基础就是准备好抓包工具,具体步骤就不赘述了,不过由于要抓Https包请务必准备一台有Root权限的设备。
这里注意如果是Flutter应用,由于其不会走系统代理所以默认是抓不到的。这里最好通过一个VPN应用来把请求转发到抓包工具上,我这里基于Tunproxy打了个包也可以考虑使用该软件进行转发。
未加密应用
仿照抓包获取的结果构建数据即可
加密应用-Android原生
使用jadx反编译Android代码,如果排查后发现加密算法在so文件中,则使用IDA来分析so文件逻辑。IDA大致路线就是找可疑字符串,定位到可疑函数,然后判断使用了什么加密算法。另外so文件也可能被加密,可以尝试内存dump的方式或者查找加固特征寻找可以破解的工具。
加密应用-Flutter
如果只是无法抓包,可以通过reFlutter重新打包应用来去除Certificate Pining的逻辑。如果请求包含了内部加密逻辑,可以依然借助reFlutter获取函数调用地址。如果reFlutter获取的信息依然不能满足,可以尝试使用blutter。
虽然有以上工具可以进行Flutter反编译,但是并没有很好的办法处理混淆后不可读的问题。以上工具虽然可以获取具体的函数地址,但是如果是做过混淆的应用依然不包含有效的名字信息。并且由于Dart虚拟机的存在,使用IDA进行分析时,无法借助字符串来定位调用函数位置。目前想的可行方案是通过Frida去hook Flutter部分网络请求的关键函数,通过调用栈来查找到具体的应用内函数。不过感觉挺麻烦的没尝试,如果有心要破解的话这里注意使用已Root的真机,模拟器这里Frida调试会存在问题。
另外截止‣ Blutter会丢失混淆后APP的部分方法
关于加密应用建议
APP端如果是包含加密逻辑的话,解析难度较高。上面虽然没写多少内容,实际配置这些工具也要消耗不少时间。有闲功夫的话可以尝试下上述步骤,如果没有得到很好的结果,建议搁置不要过多花费时间。
编写插件
这里的话就是先阅读贡献引导文档,然后参考其他人写的插件进行代码编写。文档比较长可能会忽略一些部分,这里简要提几个需要注意的点:
- Kotlin:如果Kotlin不熟的话,最好过一遍Kotlin文档这个是基础。
- Android:除了项目结构和Android没啥关系,参考其他插件实现就够了。
- OkHttp:这是项目内发起网络请求的库,除了发请求相关注意下这个库提供的Interceptor,这个在解决一些问题时非常有效。
- Jsoup:这个是对应CSS selector的,除了正常的selector支持也有一些高级用法最好注意下,可以省不少力。
- Icon Generator:生成APP图标,我确实有为了App图标花费不少功夫的体验,所以还是注意下有相关工具吧。
- Multi-source themes 如果引导文档中这部分是代码生成方式(Generator),那么已经过时了请忽略掉这部分说明文档。
插件调试
由于Debug需要安装Debug版本的Tachiyomi或其他同类软件,我一般就Logcat也够用了。另外抓包最好配置好,问题一般都集中在此处,嫌麻烦不配置抓包可能反倒花费更多的时间。
另外如果插件由于Bug在创建时就发生报错的情况,这个时候Tachiyomi上是不会显示对应插件,遇到这种情况其实会蛮困扰的,尽量多使用lazy来延迟初始化属性变量(例如初始化Regex报错)。
杂项
- 设置baseUrl的时候不要包含结尾的/可能会有多余的/产生,虽然大部分场景都可以正常请求,不过不能保证会不会奇怪的Bug产生。
- 注意baseUrl确实是要使用的url,因为在构建各种数据时设置的url是按照相对url进行设计的。如果传入的url和baseUrl无法进行匹配,软件会拼接成错误的url进行请求。
为类似软件编写插件
本文章主要记录的是实现这类插件所需能力的方法,这里也有些非Tachiyomi类插件的应用,如果觉得Tachiyomi已经过于全面没什么可写的插件的话,也可以试试给其他插件仓库贡献代码。
- mangayomi:基于Dart动态编译代码的Flutter漫画软件
- MediaBox:基于插件源的视频播放软件
- aniyomi:基于Tachiyomi插件的视频漫画一体的阅读器
- miru:基于动态读取Js代码的Flutter视频漫画一体的阅读器
- Twikoo