package gateway // 本文件承载管理面读取**适配器连接态三态**的那一层:把适配器的 ConnectOutcome // 映射成面板与线上契约能用的形状,使「点了连接之后到底怎么样了」不再只有一个布尔。 // // 为什么需要它:`Connect` 返回 nil 只说明「这次调用没有致命失败」。ws+client 的首连 // 失败刻意非致命(OneBot 实现可能比 Cyrene 后启动),此时 IsConnected() 为 false —— // 它与「监听已就绪等对端接入」(server 模式,同样 false)在布尔上无从区分,更与 // 「配置错、起不来」分不开。网关**绝不**按适配器名或 transport 去猜:口径属于适配器, // 它才知道自己的 transport × mode 意味着什么;网关只做映射。 // // 与 accountStatusAware / rescanAware / sessionStatusAware 同一契约风格(见 // admin_channel_status.go 与 admin_channels_rescan.go):核心 SDK Channel 契约被刻意 // 不扩展,因此没有这条状态面的平台不受任何改动,也不会凭空多出一个字段。 import ( "git.yeij.top/AskaEth/Cyrene_v2/internal/channel/adapter" "git.yeij.top/AskaEth/Cyrene_v2/pkg/plugins/sdk" ) // connectOutcomeAware 是渠道适配器暴露「Connect 之后处于哪种状态」这一可选能力的接口。 // 当前由 *qq_obv11.Adapter 实现(ConnectOutcome):它的 ws+client 首连失败非致命、 // ws+server 只需监听就绪,两种「没连上」都不是同一件事。 // // 返回的 state 是**闭集**取值(internal/channel/adapter 的 ConnectState* 常量): // // "connected" —— 真连上; // "pending" —— 已进入后台重试、当前未连上,reason 说明原因; // "ready" —— 已绑定监听、就绪等对端接入,不是失败; // "" —— 无意见(尚未启动),调用方退回只看 IsConnected()。 // // 闭集而不是布尔,是因为「没连上」至少还有两种互不相同的现状,而它们对主人的意义 // 完全不同:一种是「还在连」,一种是「就等对面来连」。 type connectOutcomeAware interface { // ConnectOutcome 报告连接态;state 取值见上,reason 在未连上时说明原因 // (如实的中文/技术短句,**绝不含凭据**)。 ConnectOutcome() (state string, reason string) } // asConnectOutcomeAware 报告已注册的渠道值是否暴露连接态三态。与 asRescanAware / // asAccountStatusAware 同一套路:注册时会把适配器包进 cmd/cyrene 的 namedAdapter —— // 一个内嵌 adapter.PlatformAdapter 的未导出结构体 —— 它只提升接口的方法,因此具体 // 适配器多出的能力只能通过拆开被内嵌字段才能抵达。直接的适配器值(测试)按原样接受。 func asConnectOutcomeAware(v any) (connectOutcomeAware, bool) { if co, ok := v.(connectOutcomeAware); ok { return co, true } inner, ok := unwrapNamedAdapter(v) if !ok { return nil, false } co, ok := inner.(connectOutcomeAware) return co, ok } // channelConnectOutcome 是连接态三态在**线上**的投影:三态里只有两种「没连上」需要 // 额外说明(真连上已经由 connected 布尔说完),reason 只在 pending 时非空。 // // 刻意用可选键而不是把三态塞进 connected:connected 的语义已被老客户端当作「连上了 // 没有」在读,改写它等于改契约;新增可选键则谁都不吃亏 —— 零值的三态投影让 // pending / ready / reason **整键缺席**,与 NeedsRescan / LastError 同一口径。 type channelConnectOutcome struct { // Pending 表示「已在后台重试、当前未连上」——面板须把它读作「连接中」, // 绝不是「未连接」,也绝不是「已连接」。 Pending bool // Ready 表示「已就绪、等待对端接入」——同样不是失败。 Ready bool // Reason 是未连上时的原因(已脱敏),仅在 Pending 时可能非空。 Reason string } // channelConnectStateOf 报告某渠道适配器当前的连接态:connected 布尔 + 需要额外说明的 // 三态投影。 // // 适配器实现了 connectOutcomeAware ⇒ **以它为准**:闭集三态优先于 IsConnected() 的 // 单个布尔(pending 与 ready 的 IsConnected() 都是 false,但两者都不是「未连接」这样 // 一个静态结论)。没实现,或返回空串「无意见」⇒ 逐字退回今天的行为(只看 // IsConnected(),三态投影为零值、可选键缺席)。 func channelConnectStateOf(ch sdk.Channel) (bool, channelConnectOutcome) { if ch == nil { return false, channelConnectOutcome{} } aware, ok := asConnectOutcomeAware(ch) if !ok { return ch.IsConnected(), channelConnectOutcome{} } state, reason := aware.ConnectOutcome() switch state { case adapter.ConnectStateConnected: return true, channelConnectOutcome{} case adapter.ConnectStatePending: return false, channelConnectOutcome{Pending: true, Reason: reason} case adapter.ConnectStateReady: return false, channelConnectOutcome{Ready: true} default: return ch.IsConnected(), channelConnectOutcome{} } }