Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: OneBot v12 #108

Merged
merged 59 commits into from
Oct 17, 2021
Merged

WIP: OneBot v12 #108

merged 59 commits into from
Oct 17, 2021

Conversation

stdrc
Copy link
Member

@stdrc stdrc commented Jun 19, 2021

No description provided.

@stdrc stdrc changed the title OneBot v12 WIP: OneBot v12 Jun 19, 2021
@stdrc
Copy link
Member Author

stdrc commented Jun 19, 2021

贴一下和 @crazywhalecc 讨论得到的 v12 大致思路。

OneBot v12 大致思路

总体

背景、动机移到 README,specs 里面只保留真正的规范说明。

使用 mkdocs 制作新版文档。

规范仍然包括四个部分:

  • 通信
  • 消息
  • 动作(Action,也就是目前叫的 API)
  • 事件(Event)

通信

仍然保留目前的四种通信方式:

  • HTTP
  • HTTP Webhook(原来的 HTTP POST)
  • 正向 WebSocket(OneBot 作为服务端)
  • 反向 WebSocket(OneBot 作为客户端)

考虑新增一个 HTTP Polling 方式获取事件,也就相当于在第一种通信方式中
增加一个特殊动作——get_latest_events

考虑在 HTTP 通信方式中支持单个 endpoint 的动作请求,也就是所有动作请求都发到 /,这种情况下,使用 JSON 来表示动作请求会和 WebSocket 一致。如果支持这个方式,那旧的还需不需要保留是个问题。

可以进一步统一通信方式,把上述通信方式统一称作 OneBot RPC,首先定义 RPC 传输的数据格式(也就是 JSON 结构),然后允许多种连接方式。

HTTP Webhook 的响应快速操作感觉可以丢弃,如果需要对事件进行处理,应该手动或由 SDK 请求对应动作。

HTTP 通信方式中,下面两种同时支持(第一种主要是为了支持 GET 直接传入 query 参数,方便在浏览器调试):

POST /send_msg
{
  "user_id": "123"
}
POST /
{
  "action": "send_msg",
  "params": {
    "user_id": "123"
  },
  "echo": {}
}

前端测试网页 nonebot/plugin-test、koishi webui,如果前端测试网页能做的不错的话,HTTP 通信的第一种似乎可以丢弃,使每种通信方式传输数据格式相同。

消息

消息格式只保留数组格式:

[
  {"type": "text", "data": {"text": "blah"}},
  {"type": "image", "data": {"media_id": "blahblah"}}
]

在 LibOneBot 中,实现者要构造 MessageSegment,需使用下面的接口

MessageSegment::image()
MessageSegment::extended("qq", "redbag", data) // 扩展消息段,实际构成 type 为 qq_redbad

事件 alt_message 字段:消息替代表示, 跟 array 格式不完全对等,OneBot 实现自由将消息表示成适当的字符串形式。

LibOneBot v11 兼容层可以对 v11 的 string 消息格式做支持。

动作

核心动作集(Core Action Set)

  • send_message
  • ……

LibOneBot 中实现方式:

register_action(CoreActions::SendMessage, my_send_message);

扩展动作(Extended Actions)

例如 qq_get_group_system_msg,LibOneBot 中实现方式:

register_extented_action("qq", "get_group_system_msg", my_get_group_system_msg);

生成的动作实际名字为 qq_get_group_system_msg,和扩展消息段类似。

理想情况下,OneBot 实现自己的文档中只需要给出扩展动作的文档。

动作结果(Action Result)

格式仍然和之前一致:

{
    "status": "ok",
    "retcode": 0,
    "data": {
        "id": 123456,
        "nickname": "滑稽"
    }
}

发生错误时,可选地添加 message 字段,用自然语言描述错误信息。

{
    "status": "failed",
    "retcode": 11002,
    "message": "动作 `blahblah` 不存在"
}

对于 HTTP 方式的动作请求,只要收到请求,HTTP 状态码都返回 200,通过 retcode 字段具体表达错误种类。

规范 retcode 值,定义一些区段表达特定含义,其余(或划分一块)由实现自行定义。需要在标准中定义的一些例子:

  • 0:成功
  • 11xxx:动作请求错误
    • 11001:请求格式不正确(不是正确的 JSON 对象等)
    • 11002:请求的动作不存在
    • 11003:请求参数缺失
    • 11004:请求参数类型或格式错误(消息格式错误属于此类)
    • ……
  • 12xxx:动作执行错误
    • 121xx:数据库访问错误
      • 可细分
    • 122xx:文件访问错误
    • 123xx:聊天平台 API 请求发生意外错误(QQ 服务器没有正确响应等)
    • 124xx:动作本身逻辑错误(尝试退出自己并不在的群等)
  • 如有需要,定义其它
  • 9xxxx:OneBot 实现自行定义的错误

事件

核心事件集(Core Event Set)

HTTP header 里面加上聊天平台名称。

{
  "user_id": "12234",
  "self_id": "123234",
  "messsage": "sajghds",
  "type": "message", // message | notice | request | meta_event
  "detail_type": "", // group | private | other impl specific
  "sub_type": "",
  "extended": { // 扩展字段
    ...
  },
}

核心事件集中的事件,如果 OneBot 实现有自己的扩展字段,放在 extended 里面;不在核心事件集的扩展事件,由 OneBot 实现自行定义事件字段,但需要保证有最通用的几个:type detail_type self_id 等。

扩展事件(Extended Events)

和扩展动作和扩展消息段类似,扩展事件中的 detail_type 需要有前缀。

历史包袱

  • user_id message_id 改成 string

@b11p
Copy link
Contributor

b11p commented Jun 21, 2021

HTTP Webhook 的响应快速操作感觉可以丢弃,如果需要对事件进行处理,应该手动或由 SDK 请求对应动作。

好!

@stdrc
Copy link
Member Author

stdrc commented Jun 23, 2021

补充一个文件传输相关的。

文件

发送图片、文件等时:

  • 第一步调用 upload_image 动作(名字可能得再想想),通过 base64 或 url 或直接传输文件二进制(如果可能的话)的方式把文件传输到 OneBot 实现所在位置,返回一个 media_id,这个 media_id 对于 telegram 等平台,可以是平台返回的,对于本来不需要分两步的平台,需要由 OneBot 实现来管理一个映射
  • 第二步发送一个消息段,传入 file_id 作为参数,比如 {"type": "image", "data": {"media_id": "xxx"}}

接收消息中的图片:

  • 首先收到 media_id
  • 然后调用 get_image 动作,通过 media_id 拿到文件

这里的主要原因在于下面两点:

  1. 把上传文件和发送文件分离,可以简化 cache 问题,用户自行决定要不要更新文件
  2. 把接受文件 id 和获取文件二进制分离,可以避免在不需要查看文件的情况下的网络传输开销

@wdvxdr1123
Copy link
Contributor

考虑新增一个 HTTP Polling 方式获取事件,也就相当于在第一种通信方式中
增加一个特殊动作——get_latest_events

这种方式似乎不太好应对多后端的情景。

@stdrc
Copy link
Member Author

stdrc commented Jun 24, 2021

这种方式似乎不太好应对多后端的情景。

Telegram 的类似接口,是谁先请求到就是谁的,我觉得问题不大,多后端不用这个接口就好,webhook 更合适

@GreyElaina
Copy link
Contributor

强烈建议给临时消息(群私聊消息)的 sender 加上 group_id 字段, 或者加在根里也行...

@crazywhalecc
Copy link
Member

有关 get_latest_events 是否需要参数和需要哪些参数,还没有确定,如果有问题或建议可以提出。

@ilharp
Copy link

ilharp commented Oct 7, 2021

个人观点 不太建议

引入了 type 作为保留关键字,降低与 v11 的兼容性

(v12 确实是全新的协议,但比 v11 更新的协议却增加了在数据字段上的特殊限制(v11 在 data 内无任何限制),个人觉得不太好

有可能 会使序列化变得更加困难,特别是一些静态类型语言

比如反序列化,使用现有格式可以在使用 type 得到反序列化的目标类型之后直接交由 Serializer 进行反序列化处理;但使用这样的结构则需要先从数据内得到 type,再把除 type 以外的内容交给 Serializer 处理,可能会更加复杂

@hmqgg
Copy link

hmqgg commented Oct 7, 2021

兼容性

确实如此,这是一个不可避免的 Breaking Change。但库与机器人同时升级 v12 协议后,个人认为对于这样的改动是可容忍的。

正反序列化

对于 JSON 库来说, 序列化/反序列化的 Polymorphism 支持属于基础功能之一。除非是现有的 OneBot 库本身重复造了 JSON 的轮子,否则很难想象会在此遇到困难。

@ilharp
Copy link

ilharp commented Oct 7, 2021

兼容性

确实如此,这是一个不可避免的 Breaking Change。但库与机器人同时升级 v12 协议后,个人认为对于这样的改动是可容忍的。

正反序列化

对于 JSON 库来说, 序列化/反序列化的 Polymorphism 支持属于基础功能之一。除非是现有的 OneBot 库本身重复造了 JSON 的轮子,否则很难想象会在此遇到困难。

虽然还是不太建议,但对于这两点确实同意。这样的改动多数情况下应该不会有影响,并且现在的 JSON 库应该都有对应功能(虽然可能依旧会增加消息处理时的逻辑代码成本,不过考虑到 v12 协议下相关内容会由 LibOneBot 实现那问题也就不大)

可以等等看看其他人的看法。

@stdrc
Copy link
Member Author

stdrc commented Oct 8, 2021

关于 Message 的消息段,是否可以考虑将 data 拉平?

比如原本一个消息段:

{
	"type": "text",
	"data": {
		"text": "Hello"
	}
}

变为:

{
	"type": "text",
	"text": "Hello"
}

其实 CQHTTP 在刚引入数组格式的时候就考虑过平级的表示法,后来考虑的问题基本上跟 @Afanyiyu 提出的差不多,然后决定了用 data 字段。

我个人觉得两个都可以,实际上平级和专门一个字段表示法在目前的标准里都存在——事件是平级表示的,动作请求和响应是专门 paramsdata 字段表示的,很难说孰优孰劣。

但我目前看不到为了把 seg['data']['text']seg.data['text'] 变成 seg['text'](甚至可能还是 seg.data['text'])而引入 breaking change 的其它好处。欢迎更多讨论和建议~

@super1207
Copy link
Contributor

我没看到拉平有什么明显的优势,但是既然有人提出来,说明可能的确在某些特殊场合有这个需要。
我更倾向于增加一条规则:data里面不可以用"type"作为字段,或者不允许在内外使用相同的字段。以防止某些SDK在自己拉平的时候出现问题。
比如:
{ "type": "text", "data": { "type": "yellow", "text": "Hello" } }
这种不被视为正确的onebot实现。

@Tamce
Copy link

Tamce commented Oct 10, 2021

补充一个文件传输相关的。

文件

发送图片、文件等时:

  • 第一步调用 upload_image 动作(名字可能得再想想),通过 base64 或 url 或直接传输文件二进制(如果可能的话)的方式把文件传输到 OneBot 实现所在位置,返回一个 media_id,这个 media_id 对于 telegram 等平台,可以是平台返回的,对于本来不需要分两步的平台,需要由 OneBot 实现来管理一个映射
  • 第二步发送一个消息段,传入 file_id 作为参数,比如 {"type": "image", "data": {"media_id": "xxx"}}

接收消息中的图片:

  • 首先收到 media_id
  • 然后调用 get_image 动作,通过 media_id 拿到文件

这里的主要原因在于下面两点:

  1. 把上传文件和发送文件分离,可以简化 cache 问题,用户自行决定要不要更新文件
  2. 把接受文件 id 和获取文件二进制分离,可以避免在不需要查看文件的情况下的网络传输开销

关于 get_image 接口的问题,这里在实现的时候发现:

  1. 标准并没有规定 get_image 下载图片的物理存储位置(更准确点叫生命周期)。
  2. 标准并没有规定 clean_cache 所指的“缓存”包括什么,不包括什么

因此,有些实现可能会将下载的文件放到 cache 目录作为缓存的一部分,进一步导致调用 clean_cache 时意外删除掉对应的文件(非预期行为)。

因此这里我建议对 get_image 接口的功能/约束进行更为详细的定义,避免上述问题的出现。

看了之前的讨论,这里看来会在 v12 的时候改为 upload_file / download_file 来对这类资源做统一管理,但也是建议对由 libonebot 管理的资源生命周期做明确规定,并且逻辑代码可通过 api 对这些资源进行管理(便于逻辑代码与 libonebot 分离部署时,逻辑代码无法直接操作机器上的文件的场景)。

这样逻辑代码就可以安全地使用 libonebot 返回的资源路径(不用担心被诸如 clean_cache 之类的接口清理掉),而不用自己想办法再 mv 一遍保存。

@hmqgg
Copy link

hmqgg commented Oct 16, 2021

提议

动作请求echo 字段必须string 类型。

@stdrc stdrc merged commit 2cba4db into master Oct 17, 2021
@stdrc stdrc deleted the v12-draft branch October 17, 2021 12:47
@stdrc
Copy link
Member Author

stdrc commented Oct 17, 2021

关于 get_image 接口的问题,这里在实现的时候发现:

  1. 标准并没有规定 get_image 下载图片的物理存储位置(更准确点叫生命周期)。
  2. 标准并没有规定 clean_cache 所指的“缓存”包括什么,不包括什么

因此,有些实现可能会将下载的文件放到 cache 目录作为缓存的一部分,进一步导致调用 clean_cache 时意外删除掉对应的文件(非预期行为)。

因此这里我建议对 get_image 接口的功能/约束进行更为详细的定义,避免上述问题的出现。

看了之前的讨论,这里看来会在 v12 的时候改为 upload_file / download_file 来对这类资源做统一管理,但也是建议对由 libonebot 管理的资源生命周期做明确规定,并且逻辑代码可通过 api 对这些资源进行管理(便于逻辑代码与 libonebot 分离部署时,逻辑代码无法直接操作机器上的文件的场景)。

这样逻辑代码就可以安全地使用 libonebot 返回的资源路径(不用担心被诸如 clean_cache 之类的接口清理掉),而不用自己想办法再 mv 一遍保存。

目前 OneBot 12 标准里删去了 clean_cache 接口,因为不想约束 OneBot 实现管理文件目录的方式,另外就是允许在某些聊天平台上直接把 upload_file 对接到平台提供的 API。如果需要的话,也许实现可以自行提供 clean_cache 接口。

@stdrc
Copy link
Member Author

stdrc commented Oct 17, 2021

现在 OneBot 12 标准草案已经合并到了 master 分支,部署在 https://12.1bot.dev,OneBot 官网(包括之前的生态列表)部署在 https://1bot.dev

目前草案仍然接受小型改进建议,预计会在若干个真实平台的 OneBot 实现出现后发布正式标准版本。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.