Files
AskaEth 4b4173dd42 fix(channel): M41 r1 复审三条 —— telegram 构造分支、QQ 改 MaskQuery、删两处死上限
P2-1 telegram doJSON 的**请求构造**失败分支:http.NewRequestWithContext 解析失败的
err 同样是 *url.Error(里面是整条 /bot<token>/… 地址),而 poll.go 的 long poll
failed 会原样落日志。改成与传输层分支同形:trace.HostOf(endpoint) + callCause。

P2-2 qq_obv11 connectClient 的成功 INFO 从 maskAccessTokenQuery 换成
trace.MaskQuery:后者认得 access%5Ftoken / token / api_key / 签名参数 / userinfo,
且保留 EscapedPath(路径可读)与不截断。maskAccessTokenQuery 仍被
sanitizeConnectReason 使用,未成死代码。

P2-3 删掉 maxEnvelopeMessageRunes / maxDescriptionRunes(各 200):trace.RedactText
内部已 truncate 到 maxSummaryRunes=80,这两层永不命中,注释口径与实际不符。改为
直接 return trace.RedactText(压平空白),注释写明 80 rune 由 RedactText 负责。

用例(全部变异打红,逐条验证):telegram 构造分支 token、QQ 八形态凭据矩阵
(含四种 maskAccessTokenQuery 漏掉的形态 + 路径可读反向断言)、两处 80 rune 上限。
2026-10-26 13:58:19 +08:00

86 lines
3.6 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package qq_obv11
// 本文件实现四个端里的 **ws + client**(裁决 1.4):连接由 Cyrene 发起
// (Cyrene→OneBot),主动拨号 OneBot 实现的「正向 WS」端点(remote_url),建立后
// 收发都走这条长连接。access_token 同时以 Authorization: Bearer 头与
// ?access_token= 查询参数发送,兼容只认其中一种的 OneBot 实现。
//
// 首连失败**不致命**:对面(NapCat 等)可能比 Cyrene 后启动,失败只记 WARN,重试交给
// readLoop 的同一条重连路径(指数退避 + 抖动 + 上限,见 reconnect.go)。
import (
"context"
"errors"
"fmt"
"net/url"
"strings"
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/trace"
)
// errClientConfig 是 client 模式配置缺失(remote_url 为空)的错误哨兵:重试治不好
// 配置错,必须让 Connect 直接失败(渠道桥接器的凭据门同样要求 remoteURL 非空,见
// adapter.go 的 configRemoteURL)。
var errClientConfig = errors.New("qq-obv11: client mode requires remoteURL")
// connectFirstClient 做一次 ws+client 首连(Connect 同步调用)。
//
// 为什么失败不致命:OneBot 实现(NapCat 等)可能比 Cyrene 后启动,或者正在重启——
// 若把首连失败当作渠道起不来,部署顺序就成了硬约束。因此可重试的失败只记一条 WARN
// 就返回 nil,真正的重试由 readLoop 复用同一条重连路径(指数退避 + 抖动 + 上限)
// 在后台进行。只有配置缺失(errClientConfig)才致命:重试治不好配置错,渠道桥接器
// 的凭据门也要求 remote_url 非空。
func (a *Adapter) connectFirstClient(ctx context.Context) error {
// 新一轮启动:退避从初值起步,不继承上一轮会话残留的失败计数。
a.reconnect.reset()
err := a.connectClient(ctx)
switch {
case err == nil:
return nil
case errors.Is(err, errClientConfig):
return err
default:
a.noteConnectFailure(err)
return nil
}
}
// connectClient dials the configured OneBot WebSocket endpoint and installs the
// resulting connection as the active session.
func (a *Adapter) connectClient(ctx context.Context) error {
if a.cfg.RemoteURL == "" {
return errClientConfig
}
target := appendQueryToken(a.cfg.RemoteURL, a.cfg.AccessToken)
conn, err := dialWS(ctx, a.cfg.DialContext, target, a.cfg.AccessToken)
if err != nil {
return fmt.Errorf("qq-obv11: dial onebot ws %s: %w", a.cfg.RemoteURL, err)
}
// 拨号期间可能已被 Disconnect / ctx 取消:此时绝不把连接挂成「已连接」,
// 否则状态撒谎,而且这条连接没有读循环会去消费它。
if cerr := ctx.Err(); cerr != nil {
_ = conn.Close()
return fmt.Errorf("qq-obv11: dial onebot ws %s: %w", a.cfg.RemoteURL, cerr)
}
a.setConn(conn)
// remote_url 按 OneBot 惯例可以直接写着 `?access_token=…`,也可能是把凭据塞进
// `user:pass@` 或别的键名(`token` / `api_key` / 签名参数)。原样 INFO 就是把凭据
// 写进日志,因此只打 trace.MaskQuery 的形态:凭据类 query/fragment 值与 userinfo
// 一律打码,路径原样保留(「连到了哪个端点」是这条 INFO 的全部诊断价值)。
a.log.Info("connected to onebot ws (client mode)", "url", trace.MaskQuery(a.cfg.RemoteURL))
return nil
}
// appendQueryToken adds an access_token query parameter to a URL, preserving any
// existing query string.
func appendQueryToken(rawURL, token string) string {
if token == "" {
return rawURL
}
sep := "?"
if strings.Contains(rawURL, "?") {
sep = "&"
}
return rawURL + sep + "access_token=" + url.QueryEscape(token)
}