mirror of
https://github.com/multica-ai/multica.git
synced 2026-08-15 00:04:58 +02:00
Adds a DingTalk (钉钉) bot integration on the bring-your-own-app model: a workspace admin creates their own Stream-mode robot and pastes its AppKey / AppSecret, so no public webhook or OAuth redirect is required. Each agent gets its own bot identity, so several agents can be distinct, separately @-mentionable contacts in one DingTalk organization. Supports DMs, @-mentions in groups, inbound images, /issue quick-create, and /new. Built on the shared channel engine (ForceFresh/BareFresh, MediaResolver / MediaRef and the intent ledger) rather than a private implementation. Off unless MULTICA_DINGTALK_SECRET_KEY is set. Docs in en/zh/ja/ko. Closes #4791. Community-maintained: @yyclaw is the code owner for server/internal/integrations/dingtalk/.
68 lines
2.2 KiB
Go
68 lines
2.2 KiB
Go
package dingtalk
|
|
|
|
// DingTalk Stream frames are JSON envelopes over the WebSocket. This file models
|
|
// the inbound DataFrame and the DataFrameResponse we write back, plus the small
|
|
// set of type/topic discriminators the chatbot connection uses. It replaces the
|
|
// former dependency on the vendor stream SDK's payload package.
|
|
|
|
const (
|
|
frameTypeSystem = "SYSTEM"
|
|
frameTypeCallback = "CALLBACK"
|
|
|
|
systemTopicPing = "ping"
|
|
systemTopicDisconnect = "disconnect"
|
|
botMessageTopic = "/v1.0/im/bot/messages/get"
|
|
|
|
frameResponseCodeOK = 200
|
|
)
|
|
|
|
// dataFrame is an inbound Stream frame. The gateway carries the routing key in
|
|
// headers.topic and the correlation id in headers.messageId; data is the frame
|
|
// body as a JSON string (a bot callback for the bot-message topic).
|
|
type dataFrame struct {
|
|
Type string `json:"type"`
|
|
Headers map[string]string `json:"headers"`
|
|
Data string `json:"data"`
|
|
}
|
|
|
|
func (f *dataFrame) topic() string { return f.Headers["topic"] }
|
|
func (f *dataFrame) messageID() string { return f.Headers["messageId"] }
|
|
|
|
// dataFrameResponse is the ack we write back for every frame. The gateway
|
|
// correlates it to the delivered frame by the echoed messageId header, so that
|
|
// header is load-bearing; data stays empty for a plain callback ack (the actual
|
|
// reply is delivered out-of-band over the Open API).
|
|
type dataFrameResponse struct {
|
|
Code int `json:"code"`
|
|
Headers map[string]string `json:"headers"`
|
|
Message string `json:"message"`
|
|
Data string `json:"data"`
|
|
}
|
|
|
|
// newAckResponse builds a 200 ack echoing the frame's messageId.
|
|
func newAckResponse(messageID string) dataFrameResponse {
|
|
return dataFrameResponse{
|
|
Code: frameResponseCodeOK,
|
|
Message: "",
|
|
Data: "",
|
|
Headers: map[string]string{
|
|
"messageId": messageID,
|
|
"contentType": "application/json",
|
|
},
|
|
}
|
|
}
|
|
|
|
// newPongResponse answers a SYSTEM ping. It echoes the ping's messageId and its
|
|
// data body, mirroring the gateway's expected pong shape.
|
|
func newPongResponse(messageID, data string) dataFrameResponse {
|
|
return dataFrameResponse{
|
|
Code: frameResponseCodeOK,
|
|
Message: "ok",
|
|
Data: data,
|
|
Headers: map[string]string{
|
|
"messageId": messageID,
|
|
"contentType": "application/json",
|
|
},
|
|
}
|
|
}
|