feat(qq_obv11): OneBot v11 四端齐备(裁决 1.4 / 12.4.1)
- 四端=ws×client / ws×server(原有,补语义注释)+ **http×client**(新增 http_client.go:
调 OneBot HTTP API、请求-响应、retcode 判成败、token 只走 Authorization 头)+
**http×server**(新增 http_server.go:监听 listen_addr 收上报、fail-closed 鉴权、10MiB 体上限)
- 配置面:适配器新增 transport / api_base_url / report_path(json tag 沿用既有口径);
env 兜底 CHANNEL_QQ_OBV11_{TRANSPORT,API_BASE_URL,REPORT_PATH};**默认值=现状**(ws+server)
- 出站分流:session.go 发送路径按 transport 走 WS 写帧或 HTTP POST(不改它则 HTTP 端无法出站)
- 端口豁免边界:http+server 沿用 AGENTS §9 已登记的同一豁免(design/13 §0 原文即含 HTTP server),
且 listen_addr 为空时报错、**绝不开端口**(与 ws 侧同名校验成对)
- 关键取舍(已上报):OneBot 的 HTTP 是「动作 + 上报」两半,mode 决定**哪一半必需**、
另一半给齐字段就一并启用 ⇒ 单个适配器即可「收上报 + 回消息」,四端仍各自可独立选择与测试
- 复核:build/vet 绿 + qq_obv11/cmd 包测试绿 + `-race` 干净;10 条新用例(HTTP 两端全部用本地
httptest 假 OneBot,未连真实 QQ);变异 3 条(伪造 retcode 成功 / 去掉上报鉴权门 / 默认值翻 http)已还原
- 未接:get_group_list(后续)、无 token 时放行(与既有 ws_server 一致,如需 fail-closed 请裁决)
This commit is contained in:
1 parent
0e1f163ef9
commit
9253fef92a
9 files changed
+972
-45
No files matched your search
@@ -34,8 +34,10 @@ func (a *Adapter) SendMessage(ctx context.Context, resp adapter.UnifiedResponse)
|
||||
if err := ctx.Err(); err != nil {
|
||||
return fmt.Errorf("qq-obv11: send message: %w", err)
|
||||
}
|
||||
// HTTP 传输没有 ws 连接:只要出站 HTTP API 客户端已装配(http+client 或
|
||||
// http+server 配了 api_base_url)就照样能回消息。
|
||||
conn := a.currentConn()
|
||||
if conn == nil {
|
||||
if conn == nil && a.currentAPI() == nil {
|
||||
return fmt.Errorf("qq-obv11: send message: %w", adapter.ErrNotConnected)
|
||||
}
|
||||
for i, msg := range resp.Messages {
|
||||
@@ -54,7 +56,7 @@ func (a *Adapter) SendProactive(ctx context.Context, msg adapter.UnifiedMessage)
|
||||
return fmt.Errorf("qq-obv11: send proactive: %w", err)
|
||||
}
|
||||
conn := a.currentConn()
|
||||
if conn == nil {
|
||||
if conn == nil && a.currentAPI() == nil {
|
||||
return fmt.Errorf("qq-obv11: send proactive: %w", adapter.ErrNotConnected)
|
||||
}
|
||||
return a.sendMessage(ctx, conn, msg)
|
||||
@@ -81,9 +83,10 @@ func (a *Adapter) Capabilities() adapter.Capabilities {
|
||||
}.Normalize()
|
||||
}
|
||||
|
||||
// ListChannels returns the groups the adapter currently knows about. Enumerating
|
||||
// friends / full group list requires the OneBot HTTP API, which is out of scope
|
||||
// for the WebSocket-only M6 adapter; it is reported as a follow-up.
|
||||
// ListChannels returns the groups the adapter currently knows about (learned from
|
||||
// inbound events). 全量好友/群列表要靠 OneBot 的 get_group_list / get_friend_list
|
||||
// 动作,HTTP 出站客户端(api_base_url)现已具备,但把它接进本方法属后续工作,
|
||||
// 本次四端改造不扩大范围。
|
||||
func (a *Adapter) ListChannels(ctx context.Context) ([]adapter.ChannelInfo, error) {
|
||||
a.groupMu.RLock()
|
||||
defer a.groupMu.RUnlock()
|
||||
@@ -206,8 +209,9 @@ func (a *Adapter) dispatch(ctx context.Context, msg adapter.UnifiedMessage) {
|
||||
}
|
||||
}
|
||||
|
||||
// sendMessage builds and writes a single send_msg request, enforcing the rate
|
||||
// limit and writing the payload to the connection.
|
||||
// sendMessage builds and dispatches a single send_msg request, enforcing the
|
||||
// rate limit first. 出站通道按 transport 分流:WS 两端把 JSON 帧写到长连接上;
|
||||
// HTTP 端把同一条 action 请求 POST 给 OneBot 的 HTTP API 并等响应。
|
||||
func (a *Adapter) sendMessage(ctx context.Context, conn *wsConn, msg adapter.UnifiedMessage) error {
|
||||
segs, err := outboundSegments(msg)
|
||||
if err != nil {
|
||||
@@ -230,9 +234,18 @@ func (a *Adapter) sendMessage(ctx context.Context, conn *wsConn, msg adapter.Uni
|
||||
if err := a.tryAcquireSend(msg); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := conn.WriteMessage(opText, payload); err != nil {
|
||||
a.setDisconnected()
|
||||
return fmt.Errorf("qq-obv11: write send_msg: %w", err)
|
||||
switch api := a.currentAPI(); {
|
||||
case api != nil:
|
||||
// HTTP 端:Cyrene→OneBot 走 OneBot 的 HTTP API,等响应判成败(裁决 1.4)。
|
||||
// 失败不把适配器标记为断开——HTTP 没有会话可重连,下次发送会重试。
|
||||
if err := api.send(ctx, payload); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
if err := conn.WriteMessage(opText, payload); err != nil {
|
||||
a.setDisconnected()
|
||||
return fmt.Errorf("qq-obv11: write send_msg: %w", err)
|
||||
}
|
||||
}
|
||||
a.mu.Lock()
|
||||
a.lastSend = time.Now()
|
||||
|
||||
Reference in new issue
Block a user