1. 前言
最近研究OlivOS的源码和具体实现过程,简单研究了一下basic.json的部分配置,准备写一篇文章来做一些技术讨论。个人见解,难免错漏,求大佬轻喷,也欢迎大家一起讨论🧐
1.1 注意事项
**注意!!!本次的研究基于的 OlivOS 版本号为 0.11.13 (133)
**
具体版本为 2023年7月26日发布的滚动版本 50e8e80 如需查阅具体版本,请移步github
本文仅为个人对于OlivOS的一些理解和技术讨论,看懂可能需要有较高的python基础和一定的OlivOS相关开发经验。
本文并非任何形式的文档或说明,难免会有错误,烦请各位多多交流指正!
1.2. OlivOS 整体工作原理
首先,我们必须明确一个事实,OlivOS是一个 多进程
/多线程
的复杂系统,里面每个模块各司其职以实现整个功能。
当某平台消息发出后,OlivOS中大致经历了以下过程(仅为示意图):
1.3. 线程 or 进程?
注:具体是线程模式还是进程模式需要在 basic.json 中指定,默认好像是线程模式(?)。在之后的文章中,可能会出现进程、线程混用,可能需要各位根据源代码自行理解
线程和进程模式判断的源代码,摘自 OlivOS/bootAPI.py
141 ~ 149行:
tmp_proc_mode_raw = 'auto'
tmp_proc_mode = 'threading'
if 'proc_mode' in basic_conf['system']:
tmp_proc_mode_raw = basic_conf['system']['proc_mode']
if 'proc_mode' in basic_conf_models_this:
tmp_proc_mode_raw = basic_conf['system']['proc_mode']
tmp_proc_mode = tmp_proc_mode_raw
if 'auto' == tmp_proc_mode_raw:
tmp_proc_mode = 'threading'
一些常见的进程(线程)类型:
- 主进程:运行环境(主要就是
OlivOS/bootAPI.py
中的那个大循环)
- 日志进程 [logger]: 负责所有OlivOS的日志记录(不论是输出到终端,还是GUI中)
- 插件托盘进程 [plugin]:负责装载各个插件(
plugin/app
文件夹中的所有 .opk
和文件夹
形式插件),在发生各种Event的时候依次运行各个插件
- 当
account.json
中填写了某个平台后,还会相应的构建一个负责那个平台的消息接收端服务器
(不同平台不同实现,有些是基于对平台服务器主动进行轮询,也可能是在平台服务器端(如gcq上)设置post然后做一个本地服务器去接收post请求……)
- 其他进程还有如OlivOS自动更新,Windows下的窗口GUI线程,自动调用的gocqhttp等第三方exe等
这张截图显示了在 WSL(Linux)
下,一个完全空白的 OlivOS 在运行过程中的进程信息,可以看出这是一个有很多个PID的进程树
2. basic.json
简要介绍
在当前版本的配置文件 basic.json
中,主要分以下三个部分
{
"system": {
......
},
"queue": [
......
],
"models": {
......
}
}
2.1. system
部分
具体数据如下:
system": {
"name": "OlivOS",
"init": [
"OlivOS_logger",
"OlivOS_account_config",
"OlivOS_multiLoginUI",
"OlivOS_account_fix",
"OlivOS_account_config_save",
"OlivOS_account_config",
"OlivOS_nativeWinUIAPI",
"OlivOS_gocqhttp_lib_exe_model",
"OlivOS_walleq_lib_exe_model",
"OlivOS_cwcb_lib_exe_model",
"OlivOS_hackChat_link",
"OlivOS_plugin",
"OlivOS_virtual_terminal_link",
"OlivOS_flask_post_rx",
"OlivOS_onebotV12_link",
"OlivOS_qqGuild_link",
"OlivOS_discord_link",
"OlivOS_telegram_poll",
"OlivOS_fanbook_poll",
"OlivOS_kaiheila_link",
"OlivOS_dodo_link",
"OlivOS_biliLive_link",
"OlivOS_update_check"
],
"event": {
"account_edit": [
"OlivOS_account_config",
"OlivOS_multiLoginUI",
"OlivOS_account_fix",
"OlivOS_account_config_save",
"OlivOS_account_config",
"OlivOS_account_config_update"
],
"account_edit_asayc_start": [
"OlivOS_account_config"
],
"account_edit_asayc_do": [
"OlivOS_multiLoginUI_asayc"
],
"account_edit_asayc_end": [
"OlivOS_account_fix",
"OlivOS_account_config_save",
"OlivOS_account_config",
"OlivOS_account_config_update"
],
"account_update": [
"OlivOS_gocqhttp_lib_exe_model",
"OlivOS_walleq_lib_exe_model",
"OlivOS_cwcb_lib_exe_model",
"OlivOS_hackChat_link",
"OlivOS_virtual_terminal_link",
"OlivOS_flask_post_rx",
"OlivOS_onebotV12_link",
"OlivOS_qqGuild_link",
"OlivOS_discord_link",
"OlivOS_telegram_poll",
"OlivOS_fanbook_poll",
"OlivOS_kaiheila_link",
"OlivOS_dodo_link",
"OlivOS_biliLive_link"
]
},
"type_event": {
"account_update": [
"gocqhttp_lib_exe_model",
"walleq_lib_exe_model",
"cwcb_lib_exe_model",
"hackChat_link",
"terminal_link",
"flask_post_rx",
"onebotV12_link",
"qqGuild_link",
"discord_link",
"telegram_poll",
"fanbook_poll",
"kaiheila_link",
"dodo_link",
"biliLive_link"
]
},
"control_queue": "OlivOS_control_queue",
"interval": 0.2,
"proc_mode": "auto"
},
2.1.1 init
列表
这个列表中定义了当OlivOS开始运行时的初始化顺序,一共可以分为以下几步:
- init队列读取前操作
详见OlivOS/bootAPI.py
中,进入大循环前的部分
在这个阶段,主要操作有:
- 打印欢迎横幅
- 尝试读取
basic.json
的内容或使用默认配置
- 建立所有后续需要用到的进程间通讯队列
- 解析运行指令参数(目前仅有
--noblock
一个可选参数,使用后会跳过初始化阶段的账户控制gui界面)
- 日志初始化
具体包含以下过程
1 - "OlivOS_logger",
首先建立日志进程,在建立过后所有的日志全部由日志模块进行统一输出处理
(在这之前的日志,通过preLoadPrint函数实现,具体目前处理方式就是直接print,后续可能会修改)
2.账号初始化
具体包含以下过程
1 - "OlivOS_account_config",
2 - "OlivOS_multiLoginUI",
3 - "OlivOS_account_fix",
4 - "OlivOS_account_config_save",
5 - "OlivOS_account_config",
这个过程中,每个过程的具体用途如下
序号 | 名称 | 解释 |
1 | OlivOS_account_config | 第一次读取 account.json 中的数据,获取保存的各平台账号密码信息 |
2 | OlivOS_multiLoginUI | 在windows界面下,若–noblock参数未选择,则使用刚刚读取的数据,构建账号管理器 |
3 | OlivOS_account_fix | 检查账号信息,(1)部分平台通过api获取真实bot id信息;(2)gcq中自动修改 device.json 的协议、补齐其中设备信息配置;(3)端口自动分配开启时,检查端口分配情况,避免发生端口冲突 |
4 | OlivOS_account_config_save | 保存覆写 account.json |
5 | OlivOS_account_config | 重新读取各个平台的账号信息 |
在以前版本中,还有一个account_config_safe的步骤,用于获取不包含密码的纯账号信息,提供给插件使用(以避免插件对密码等敏感数据的误读)。似乎在最近一次更新basic.json的过程中,这个步骤删除了
- windows下的gui界面生成
具体包含以下过程
1 - "OlivOS_nativeWinUIAPI",
这个步骤,指的是在 Windows 系统下,OlivOS的主要界面生成,日志窗口、右下角系统托盘等一系列gui内容都是这次统一进行初始化的。
- 导入运行需要的第三方的各种模块
具体包含以下过程
1 - "OlivOS_gocqhttp_lib_exe_model",
2 - "OlivOS_walleq_lib_exe_model",
3 - "OlivOS_cwcb_lib_exe_model",
这个步骤,指的是在 Windows 系统下,启动内置内置QQ、微信等部分平台的第三方客户端
序号 | 名称 | 解释 |
1 | OlivOS_gocqhttp_lib_exe_model | go-cqhttp 大家的老熟人了,如果不是分离部署的话,OlivOS 就是通过这个步骤打开gcq的 |
2 | OlivOS_walleq_lib_exe_model | walleq,类似gcq、mirai的另外一个框架?没具体用过没有发言权 |
3 | OlivOS_cwcb_lib_exe_model | 微信平台框架,做微信机器人的 |
❓问题:OlivOS_hackChat_link 为何在这时候初始化,而不是在后续各平台过程中初始化。看着不像是调用第三方客户端软件,会不会是为了后续支持私有源搭建做准备?
仑质 OlivOS_hackChat_link的初始化为何会在OlivOS_plugin之前
这其实恰恰是你所遗漏的部分,Hack.Chat平台的协议对接更类似onebotV11中正向websocket的模式,这带来一个明显的逻辑特点,就是收发消息需要在同一个websocket连接中完成,对于OlivOS来说,这与其它的诸如KOOK与Discord的平台相比,它要求从OlivOS_plugin中发起的接口调用(如发送消息、获取用户信息等),经由全局的事件路由重新回到OlivOS_hackChat_link中,再经由相关的SDK转换后从websocket会话中发出。这个过程中的关键在于OlivOS的全局事件路由,OlivOS_plugin将会把事件调用以类似广播的方式发送到主要事件通道中,发送的目标的匹配条件为type为hackChat_link且hash与账号实例一致,在这个过程中如果OlivOS_hackChat_link在此时没有完成启动,则会导致该次调用丢失,这会给插件设计带来额外的成本,并且目前来看,前置这一个模块的加载没有明确的弊端,所以这里决定了这样做。
5.插件托盘初始化,插件载入
具体包含以下过程
1 - "OlivOS_plugin",
这个步骤是正常开发 OlivOS 插件过程中,插件与 OlivOS 的第一次交互。
值得注意的是,插件的初始化阶段其实有两个事件 init
和 init_after
。
init
事件的运行不保证插件优先级顺序,同时.opk
打包状态下资源文件还没有自动解压到data目录下。在那里适合做插件内部的各种环境初始化,但不适合做与外界的交互(也就是说,这个阶段应该都是运行确定的python语句)。
init_after
事件的运行顺序严格按照插件优先级,同时确保 opk
打包中的 data 目录已经完成释放;在这个阶段,各个插件的第一轮 init
也已经完成。
在这里,适合做对外部内容的处理,比如:对opk
内置资源文件的操作、与其他插件尝试联动
OlivOS_hackChat_link
之外的各平台初始化
具体包含以下过程
1 - "OlivOS_virtual_terminal_link",
2 - "OlivOS_flask_post_rx",
3 - "OlivOS_onebotV12_link",
4 - "OlivOS_qqGuild_link",
5 - "OlivOS_discord_link",
6 - "OlivOS_telegram_poll",
7 - "OlivOS_fanbook_poll",
8 - "OlivOS_kaiheila_link",
9 - "OlivOS_dodo_link",
10 - "OlivOS_biliLive_link",
这一块就是OlivOS支持的各个平台,没啥可说的,没多支持一个平台就会多一行。
还是多提一嘴,这些模块只有在 account.json 中真正出现了相应账号后才会运行
- 新版本检查
具体包含以下过程
1 - "OlivOS_update_check",
检查OlvOS是否存在更新的发行版,在这里仅作检查并不会自动更新
注意:这个是指 OlivOS
框架的更新,和 OlivaDice
等各种插件的自动更新无关
2.2. queue
部分
这里定义了所有进程间通讯的队列
个人感觉就是,这里列举了在后续model中使用到的进程间
2.3. models
部分
3. 简单修改实例
3.1. 日志模块(如开启debug信息)
配置文件 183 ~ 197行(位置可能变化):
{
"OlivOS_logger": {
"enable": true,
"name": "OlivOS_logger",
"type": "logger",
"interval": 0.002,
"dead_interval": 1,
"proc_mode": "auto",
"rx_queue": "OlivOS_logger_queue",
"control_queue": "OlivOS_control_queue",
"mode": [
"console_color",
"logfile",
"native"
],
"fliter": [2, 3, 4, 5]
}
}
在这里,主要可以修改的是 filter
(和mode
)
3.1.1. 日志输出等级控制
filter
主要控制输出的日志等级(默认在info)
具体日志等级如下:
Log Level | Log Type | Log Color |
-1 | TRACE | #666666 |
0 | DEBUG | green |
1 | NOTE | black |
2 | INFO | black |
3 | WARN | #E6992C |
4 | ERROR | red |
5 | FATAL | red |
通过修改filter
列表,可以打开 DEBUG 等 log 信息,对开发过程还是很有帮助的
3.1.2. 日志输出格式控制
mode
主要控制日志输出的类型
OlivOS目前支持以下几种模式
输出模式 | 解释 |
console | 控制台黑白日志输出(上古版本的日志输出,现在默认关闭改为彩色日志输出) |
console_color | 控制台彩色输出,不同等级的日志用不同颜色标注 |
logfile | 日志保存到本地文件中,默认位置在 ./logfile 文件夹中 |
native | 在windows系统下,在原生tk的gui中渲染日志(就是现在的gui版本日志) |
如果需要自己增加对日志的处理逻辑(如自动导入到数据库中),只需要在 OlivOS/diagnoseAPI.py
中简单增加一下自己的处理逻辑后,在 mode 列表中打开这一项你实现的那一项即可,非常简单(确信)
具体源代码,改这里就行:
3.2. 插件托盘(如控制托盘重载频率,插件运行方式)
配置文件 199 ~ 215行(位置可能变化):
"OlivOS_plugin": {
"enable": true,
"name": "OlivOS_plugin",
"type": "plugin",
"interval": 0.002,
"dead_interval": 1,
"rx_queue": "OlivOS_rx_queue",
"tx_queue": [
"OlivOS_dodobot_rx_queue"
],
"control_queue": "OlivOS_control_queue",
"logger_proc": "OlivOS_logger",
"treading_mode": "full",
"restart_gate": 50000,
"enable_auto_restart": true,
"debug": false
},
3.2.1. 插件重载控制
enable_auto_restart
是否开启插件托盘的自动重载功能(true
/false
),开启后每过一段时间,OlivOS会自动重载所有插件,减少由于个别插件代码质量问题导致的内存泄露等情况,进而使得OlivOS可以在服务器中长期稳定持续运行不变慢
restart_gate
插件托盘从的事件次数,默认每经过50000次事件重载一次所有插件。
注意:这里的事件是指插件的Event中可以触发的任何事件,简单而言就是包括私聊、群聊、群通知消息在内的一切消息(也包括来自于OlivOS框架的事件),具体种类详见OlivOS文档 。
无论有无插件真正处理了这个事件,重载计数器都会增加。
通过控制 restart_gate
大小,可以人为实现短周期重载,在开发阶段可以方便的测试插件在自动重载过程中的行为。
同时如果是私有机器人,一年到头没有几条消息,也可以人为降低阈值加快重载频率。
3.2.2. 插件运行模式控制
treading_mode
控制插件运行的模式,默认为"full"
有以下选择:
treading_mode | 解释 |
“full” | 每个OlivOS事件(每条消息)用一个独立的线程处理,(消息之间可以有一定系统调用层次的并行)。每个线程中,消息还是严格按照插件优先级依次调用 |
“none” | 所有OlivOS事件(每条消息)都在插件托盘所在的主线程/进程中执行 |
相关源代码在 OlivOS/pluginAPI.py 中,具体如下图所示
4. QQ分离部署相关
(有时间有兴趣再写,只是先po一张之前做的分离部署示意图)