- kook.Config 加 ReceiveMode("ws"默认/"webhook") + WebhookPath(默认/webhook/kook) + WebhookVerifyToken + WebhookEncryptKey(AES-256-CBC)
- 按权威文档(Reference/kaiheila webhook.md)实现: 无X-Signature HMAC, 真实校验=d.verify_token比对(不匹配403)+可选AES解密+zlib解压+challenge回显
- webhook.go: InboundHandler+WebhookPath, POST→zlib解压→AES解密→verify_token校验→challenge回显→归一UnifiedMessage→SetMessageHandler; WS模式Connect不拨网关仅标记connected
- channelbridge/webhookAdapter 接口改用 WebhookPath(); WebhookHandlers 收KOOK WH(ws模式跳过); 既有webhook通道补WebhookPath照常挂载
- namedAdapter 转发InboundHandler/WebhookPath; kook env兜底 +4个CHANNEL_KOOK_*键
- 测试: verify_token 403/challenge/zlib/AES往返/WebhookPath按模式/WH不拨gateway/WebhookHandlers收KOOK; 全仓build/vet/test绿
- 待真token端到端实测(需主人提供CHANNEL_KOOK_TOKEN+开发者后台切WebHook配回调URL)
99 lines
4.4 KiB
Go
99 lines
4.4 KiB
Go
// Package kook implements the built-in KOOK (KaiHeiLa) channel adapter for
|
|
// Cyrene v2. KOOK is a stateful channel with two mutually exclusive inbound
|
|
// modes: a WebSocket-gateway mode (events over a gateway WebSocket with
|
|
// hello/heartbeat/event/resume signaling) and a Webhook mode (KOOK POSTs event
|
|
// callbacks to a Cyrene gateway route). Outbound messages are always sent over a
|
|
// bearer-authenticated REST API.
|
|
//
|
|
// The adapter conforms to internal/channel/adapter.PlatformAdapter, translating
|
|
// native KOOK events into the normalized adapter.UnifiedMessage model and
|
|
// delivering replies via /api/v3/message/create (channels) and
|
|
// /api/v3/direct-message/create (private chat). Identity is carried as channel
|
|
// actor metadata (author id + nickname) only — owner/member resolution is the
|
|
// channel layer's job, never a prompt-text heuristic.
|
|
//
|
|
// The design authority is DevDocs/design/08-渠道接入.md §4 (kook) and §7
|
|
// (framework reusability). It depends on internal/channel/adapter (including the
|
|
// shared wsutil and httpclient primitives), internal/logger, and the standard
|
|
// library. The bot token, verify token and encrypt key are never logged.
|
|
package kook
|
|
|
|
import (
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// Defaults and knobs for the KOOK integration.
|
|
const (
|
|
// defaultBaseURL is the KOOK REST API root.
|
|
defaultBaseURL = "https://www.kookapp.cn/api/v3"
|
|
// defaultHTTPTimeout bounds a single REST request.
|
|
defaultHTTPTimeout = 30 * time.Second
|
|
// gatewayPath is the gateway-address fetch endpoint relative to baseURL.
|
|
gatewayPath = "/gateway/index"
|
|
// maxMessageLen is KOOK's documented per-message content limit.
|
|
maxMessageLen = 8000
|
|
// heartbeatInterval is how often the client sends a ping (KOOK: 30s).
|
|
heartbeatInterval = 30 * time.Second
|
|
// helloTimeout bounds the wait for the handshake (hello/resume ack).
|
|
helloTimeout = 6 * time.Second
|
|
)
|
|
|
|
// Inbound receive modes. KOOK's webhook and websocket modes are mutually
|
|
// exclusive (DevDocs/design/08 §4): selecting webhook means the gateway WebSocket
|
|
// no longer delivers events, and vice versa. ReceiveMode decides which path the
|
|
// adapter uses; the two never run together.
|
|
const (
|
|
// modeWS is the default: connect the KOOK gateway WebSocket and receive events.
|
|
modeWS = "ws"
|
|
// modeWebhook exposes a Cyrene gateway route that KOOK POSTs callbacks to.
|
|
modeWebhook = "webhook"
|
|
// defaultWebhookPath is used when Config.WebhookPath is empty in webhook mode.
|
|
defaultWebhookPath = "/webhook/kook"
|
|
// defaultMaxBodyBytes caps an inbound webhook payload at 1 MiB.
|
|
defaultMaxBodyBytes = 1 << 20
|
|
)
|
|
|
|
// Config holds the settings required to reach the KOOK API.
|
|
type Config struct {
|
|
// BotToken is the secret bot token issued by the KOOK developer center. It is
|
|
// surfaced through Config() but never written to logs.
|
|
BotToken string `json:"bot_token"`
|
|
// BaseURL is the KOOK REST API root; empty uses the production default.
|
|
BaseURL string `json:"base_url"`
|
|
// ReceiveMode selects the inbound delivery path: "ws" (default) connects the
|
|
// KOOK gateway WebSocket; "webhook" exposes a gateway route that KOOK POSTs
|
|
// events to. The two are mutually exclusive.
|
|
ReceiveMode string `json:"receive_mode"`
|
|
// WebhookPath is the route the Cyrene gateway mounts the KOOK webhook handler
|
|
// at when ReceiveMode == "webhook". Empty uses defaultWebhookPath.
|
|
WebhookPath string `json:"webhook_path"`
|
|
// WebhookVerifyToken is the KOOK developer-center verify token. When set, every
|
|
// webhook callback's d.verify_token must match or it is rejected with 403.
|
|
WebhookVerifyToken string `json:"webhook_verify_token"`
|
|
// WebhookEncryptKey is the KOOK Encrypt Key. When set, an encrypted callback
|
|
// body ({"encrypt": ...}) is decrypted with AES-256-CBC before parsing.
|
|
WebhookEncryptKey string `json:"webhook_encrypt_key"`
|
|
}
|
|
|
|
// normalizedReceiveMode returns ReceiveMode, defaulting empty to "ws".
|
|
func (c Config) normalizedReceiveMode() string {
|
|
if strings.TrimSpace(c.ReceiveMode) == "" {
|
|
return modeWS
|
|
}
|
|
return strings.TrimSpace(c.ReceiveMode)
|
|
}
|
|
|
|
// IsWebhook reports whether the adapter should run in webhook mode.
|
|
func (c Config) IsWebhook() bool { return c.normalizedReceiveMode() == modeWebhook }
|
|
|
|
// Validate checks the configuration for values needed to connect. The bot token
|
|
// is required in both modes because outbound (reply) delivery always uses the
|
|
// bearer REST API.
|
|
func (c Config) Validate() error {
|
|
if strings.TrimSpace(c.BotToken) == "" {
|
|
return ErrMissingToken
|
|
}
|
|
return nil
|
|
}
|