package gateway import "reflect" // This file holds the optional "the adapter needs the owner to re-authorize it" // capability surfaced by GET /api/v1/admin/channels (v0.39). It lives next to — // not inside — the channel summary itself because the summary list is one of // admin.go's larger handlers and the unwrap helper is reusable. // rescanAware is the optional capability a channel adapter exposes when its // session needs owner re-authorization that a reconnect cannot provide. // *weixin_ilink.Adapter implements it (RescanRequired): iLink answering // errcode=-14 means the bot token is dead and the owner must scan a fresh QR. The // core adapter.PlatformAdapter contract is deliberately NOT extended, so adapters // that do not need the capability stay untouched. // // A value is only ever reported when the adapter really produced it — a platform // without the capability keeps the fields omitted rather than shipping a guess. type rescanAware interface { // RescanRequired reports whether a new scan-code login is required, and why // (reason is "" when it is not). RescanRequired() (required bool, reason string) } // asRescanAware reports whether a registered channel value exposes the rescan // capability. The registration wraps an adapter in cmd/cyrene's namedAdapter — an // unexported struct embedding adapter.PlatformAdapter — which promotes only the // interface's methods, so the concrete adapter's extra capability is reachable // only by unwrapping the embedded field. A direct adapter value (tests, // single-account registrations) is accepted as-is. func asRescanAware(v any) (rescanAware, bool) { if ra, ok := v.(rescanAware); ok { return ra, true } inner, ok := unwrapNamedAdapter(v) if !ok { return nil, false } ra, ok := inner.(rescanAware) return ra, ok } // unwrapNamedAdapter returns the adapter a registration wrapper embeds in its // exported PlatformAdapter field. asKookAdmin and asWeixinLoginAdmin perform the // same unwrap for their own capability subsets and could share this helper. func unwrapNamedAdapter(v any) (any, bool) { rv := reflect.ValueOf(v) if !rv.IsValid() { return nil, false } if rv.Kind() == reflect.Ptr { rv = rv.Elem() } if rv.Kind() != reflect.Struct { return nil, false } f := rv.FieldByName("PlatformAdapter") if !f.IsValid() || !f.CanInterface() { return nil, false } return f.Interface(), true }