refactor(channels): Implement DingTalk client with card messaging capabilities
This commit is contained in:
parent
81dfdf5f45
commit
1dd9c83ff6
5 changed files with 360 additions and 22 deletions
2
go.mod
2
go.mod
|
|
@ -8,6 +8,7 @@ require (
|
||||||
github.com/bwmarrin/discordgo v0.29.0
|
github.com/bwmarrin/discordgo v0.29.0
|
||||||
github.com/caarlos0/env/v11 v11.3.1
|
github.com/caarlos0/env/v11 v11.3.1
|
||||||
github.com/chzyer/readline v1.5.1
|
github.com/chzyer/readline v1.5.1
|
||||||
|
github.com/ergochat/irc-go v0.5.0
|
||||||
github.com/gdamore/tcell/v2 v2.13.8
|
github.com/gdamore/tcell/v2 v2.13.8
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/gorilla/websocket v1.5.3
|
github.com/gorilla/websocket v1.5.3
|
||||||
|
|
@ -37,7 +38,6 @@ require (
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/elliotchance/orderedmap/v3 v3.1.0 // indirect
|
github.com/elliotchance/orderedmap/v3 v3.1.0 // indirect
|
||||||
github.com/ergochat/irc-go v0.5.0 // indirect
|
|
||||||
github.com/gdamore/encoding v1.0.1 // indirect
|
github.com/gdamore/encoding v1.0.1 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
|
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
|
||||||
|
|
|
||||||
274
pkg/channels/dingtalk/client.go
Normal file
274
pkg/channels/dingtalk/client.go
Normal file
|
|
@ -0,0 +1,274 @@
|
||||||
|
package dingtalk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/open-dingtalk/dingtalk-stream-sdk-go/chatbot"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
endpoint = "https://api.dingtalk.com"
|
||||||
|
accessToken = "/v1.0/oauth2/accessToken"
|
||||||
|
createCardAndDeliver = "/v1.0/card/instances/createAndDeliver"
|
||||||
|
cardStreaming = "/v1.0/card/streaming"
|
||||||
|
privateChatMessages = "/v1.0/robot/privateChatMessages/send"
|
||||||
|
batchSendMessages = "/v1.0/robot/oToMessages/batchSend"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MessageType represents the type of message to be sent.
|
||||||
|
type MessageType string
|
||||||
|
|
||||||
|
func (t MessageType) String() string {
|
||||||
|
return string(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
Markdown = MessageType("sampleMarkdown")
|
||||||
|
Text = MessageType("sampleText")
|
||||||
|
Image = MessageType("sampleImageMsg")
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
clientID string
|
||||||
|
clientSecret string
|
||||||
|
token string
|
||||||
|
// card template id
|
||||||
|
cardTemplateID string
|
||||||
|
// card template message content default content
|
||||||
|
cardTemplateContentKey string
|
||||||
|
|
||||||
|
// robot code default use clientID
|
||||||
|
robotCode string
|
||||||
|
|
||||||
|
expires time.Time
|
||||||
|
client *http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient creates a new Client instance with the provided client ID, client secret, and optional configurations.
|
||||||
|
func NewClient(clientID, clientSecret string, opts ...ClientOption) *Client {
|
||||||
|
client := &Client{clientID: clientID, clientSecret: clientSecret,
|
||||||
|
client: &http.Client{Timeout: time.Second * 30}}
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(client)
|
||||||
|
}
|
||||||
|
if client.robotCode == "" {
|
||||||
|
client.robotCode = client.clientID
|
||||||
|
}
|
||||||
|
if client.cardTemplateContentKey == "" {
|
||||||
|
client.cardTemplateContentKey = "content"
|
||||||
|
}
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetToken retrieves the access token, refreshing it if it has expired.
|
||||||
|
func (c *Client) GetToken() (string, error) {
|
||||||
|
if time.Now().Before(c.expires) {
|
||||||
|
return c.token, nil
|
||||||
|
}
|
||||||
|
data := map[string]any{
|
||||||
|
"appKey": c.clientID,
|
||||||
|
"appSecret": c.clientSecret,
|
||||||
|
}
|
||||||
|
resp := struct {
|
||||||
|
Expires int64 `json:"expireIn"`
|
||||||
|
Token string `json:"accessToken"`
|
||||||
|
}{}
|
||||||
|
err := c.httpRequest(context.Background(), http.MethodPost, accessToken, data, &resp)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
c.expires = time.Now().Add(time.Second * time.Duration(resp.Expires))
|
||||||
|
c.token = resp.Token
|
||||||
|
return c.token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchSendMessages sends a message to multiple users in a batch.
|
||||||
|
func (c *Client) BatchSendMessages(ctx context.Context, msgType MessageType, userIds []string, content string) error {
|
||||||
|
body := map[string]any{
|
||||||
|
"robotCode": c.robotCode,
|
||||||
|
"msgKey": msgType,
|
||||||
|
"userIds": userIds,
|
||||||
|
"msgParam": c.buildSendMessages(msgType, content),
|
||||||
|
}
|
||||||
|
return c.httpRequest(ctx, http.MethodPost, batchSendMessages, body, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CardStreaming updates the content of a card instance identified by cardInstanceId.
|
||||||
|
func (c *Client) CardStreaming(ctx context.Context, cardInstanceId, content string) error {
|
||||||
|
id, err := uuid.NewUUID()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
body := map[string]any{
|
||||||
|
"outTrackId": cardInstanceId,
|
||||||
|
"guid": id.String(),
|
||||||
|
"key": c.cardTemplateContentKey,
|
||||||
|
"content": content,
|
||||||
|
"isFull": true,
|
||||||
|
}
|
||||||
|
return c.httpRequest(ctx, http.MethodPut, cardStreaming, body, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CardCreateAndDeliver creates a card instance and delivers it to the user or group.
|
||||||
|
// It returns the outTrackId of the created card instance, which can be used for subsequent updates via CardStreaming.
|
||||||
|
// If the chatbot is in a group conversation, the card will be delivered to the group; otherwise, it will be delivered to the user.
|
||||||
|
// <a href="https://open.dingtalk.com/document/development/create-and-deliver-cards">Card Delivery API Documentation</a>
|
||||||
|
func (c *Client) CardCreateAndDeliver(ctx context.Context, chatbot *chatbot.BotCallbackDataModel) (string, error) {
|
||||||
|
if c.cardTemplateID == "" {
|
||||||
|
return "", errors.New("cardTemplateId is required")
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
group = chatbot.ConversationType == "2"
|
||||||
|
openSpaceId = "dtv1.card//IM_ROBOT." + chatbot.SenderStaffId
|
||||||
|
imRobotOpenDeliverModel = map[string]any{
|
||||||
|
"spaceType": "IM_ROBOT",
|
||||||
|
}
|
||||||
|
imGroupOpenSpaceModel = map[string]any{
|
||||||
|
"supportForward": true,
|
||||||
|
}
|
||||||
|
imRobotOpenSpaceModel = map[string]any{
|
||||||
|
"supportForward": true,
|
||||||
|
}
|
||||||
|
imGroupOpenDeliverModel = map[string]any{
|
||||||
|
"robotCode": c.clientID,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
id, err := uuid.NewUUID()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if group {
|
||||||
|
openSpaceId = "dtv1.card//IM_GROUP." + chatbot.ConversationId
|
||||||
|
imRobotOpenDeliverModel = map[string]any{}
|
||||||
|
imGroupOpenDeliverModel["robotCode"] = c.clientID
|
||||||
|
}
|
||||||
|
|
||||||
|
body := map[string]any{
|
||||||
|
"cardTemplateId": c.cardTemplateID,
|
||||||
|
"outTrackId": id.String(),
|
||||||
|
"cardData": map[string]any{},
|
||||||
|
"openSpaceId": openSpaceId,
|
||||||
|
"userIdType": 1,
|
||||||
|
"imGroupOpenDeliverModel": imGroupOpenDeliverModel,
|
||||||
|
"imGroupOpenSpaceModel": imGroupOpenSpaceModel,
|
||||||
|
"imRobotOpenSpaceModel": imRobotOpenSpaceModel,
|
||||||
|
"imRobotOpenDeliverModel": imRobotOpenDeliverModel,
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
{
|
||||||
|
"result" : {
|
||||||
|
"deliverResults" : [ {
|
||||||
|
"spaceId" : "manager164",
|
||||||
|
"spaceType" : "IM_ROBOT",
|
||||||
|
"success" : true,
|
||||||
|
"carrierId" : "119X11tauuJlODPiK0wpCjIXPcGpODOnnpHc/uYFFnI=",
|
||||||
|
"errorMsg" : ""
|
||||||
|
} ],
|
||||||
|
"outTrackId" : "f51222b2-1aff-11f1-8a7c-a40c662198be"
|
||||||
|
},
|
||||||
|
"success" : true
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
resp := struct {
|
||||||
|
Result struct {
|
||||||
|
DeliverResults []struct {
|
||||||
|
SpaceId string `json:"spaceId"`
|
||||||
|
SpaceType string `json:"spaceType"`
|
||||||
|
Success bool `json:"success"`
|
||||||
|
CarrierId string `json:"carrierId"`
|
||||||
|
ErrorMsg string `json:"errorMsg"`
|
||||||
|
} `json:"deliverResults"`
|
||||||
|
OutTrackId string `json:"outTrackId"`
|
||||||
|
} `json:"result"`
|
||||||
|
Success bool `json:"success"`
|
||||||
|
}{}
|
||||||
|
if err = c.httpRequest(ctx, http.MethodPost, createCardAndDeliver, body, &resp); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if resp.Success {
|
||||||
|
return resp.Result.OutTrackId, nil
|
||||||
|
}
|
||||||
|
return "", errors.New("failed to create and deliver card instance")
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrivateChatMessages sends a message to a user in a private chat.
|
||||||
|
func (c *Client) PrivateChatMessages(ctx context.Context, msgType MessageType, openConversationId, content string) error {
|
||||||
|
body := map[string]any{
|
||||||
|
"msgKey": msgType,
|
||||||
|
"msgParam": c.buildSendMessages(msgType, content),
|
||||||
|
"openConversationId": openConversationId,
|
||||||
|
"robotCode": c.robotCode,
|
||||||
|
}
|
||||||
|
return c.httpRequest(ctx, http.MethodPost, privateChatMessages, body, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) buildSendMessages(msgType MessageType, content string) string {
|
||||||
|
data := map[string]any{}
|
||||||
|
switch msgType {
|
||||||
|
case Markdown:
|
||||||
|
data = map[string]any{
|
||||||
|
"title": "PicoClaw",
|
||||||
|
"text": content,
|
||||||
|
}
|
||||||
|
case Text:
|
||||||
|
data = map[string]any{
|
||||||
|
"content": content,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
msg, _ := json.Marshal(data)
|
||||||
|
return string(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) httpRequest(ctx context.Context, method, path string, body interface{}, resp interface{}) error {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
token string
|
||||||
|
req *http.Request
|
||||||
|
hc = c.client
|
||||||
|
res *http.Response
|
||||||
|
)
|
||||||
|
if path == accessToken {
|
||||||
|
token = ""
|
||||||
|
} else {
|
||||||
|
token, err = c.GetToken()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
url := endpoint + path
|
||||||
|
if body != nil {
|
||||||
|
data, _ := json.Marshal(body)
|
||||||
|
req, err = http.NewRequestWithContext(ctx, method, url, bytes.NewReader(data))
|
||||||
|
} else {
|
||||||
|
req, err = http.NewRequestWithContext(ctx, method, url, nil)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
if token != "" {
|
||||||
|
req.Header.Set("x-acs-dingtalk-access-token", token)
|
||||||
|
}
|
||||||
|
res, err = hc.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
data, err := io.ReadAll(res.Body)
|
||||||
|
if res.StatusCode != http.StatusOK || err != nil {
|
||||||
|
return fmt.Errorf("API request failed:\n Status: %d\n Body: %s", res.StatusCode, string(data))
|
||||||
|
}
|
||||||
|
if resp == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return json.Unmarshal(data, resp)
|
||||||
|
}
|
||||||
|
|
@ -27,10 +27,13 @@ type DingTalkChannel struct {
|
||||||
clientID string
|
clientID string
|
||||||
clientSecret string
|
clientSecret string
|
||||||
streamClient *client.StreamClient
|
streamClient *client.StreamClient
|
||||||
|
client *Client
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
// Map to store session webhooks for each chat
|
// Map to store session webhooks for each chat
|
||||||
sessionWebhooks sync.Map // chatID -> sessionWebhook
|
sessionWebhooks sync.Map // chatID -> sessionWebhook
|
||||||
|
// chatID -> cardInstanceId
|
||||||
|
cardInstanceIds sync.Map
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDingTalkChannel creates a new DingTalk channel instance
|
// NewDingTalkChannel creates a new DingTalk channel instance
|
||||||
|
|
@ -44,12 +47,18 @@ func NewDingTalkChannel(cfg config.DingTalkConfig, messageBus *bus.MessageBus) (
|
||||||
channels.WithGroupTrigger(cfg.GroupTrigger),
|
channels.WithGroupTrigger(cfg.GroupTrigger),
|
||||||
channels.WithReasoningChannelID(cfg.ReasoningChannelID),
|
channels.WithReasoningChannelID(cfg.ReasoningChannelID),
|
||||||
)
|
)
|
||||||
|
// dingtalk client
|
||||||
|
dingTalkClient := NewClient(cfg.ClientID, cfg.ClientSecret,
|
||||||
|
WithRobotCode(cfg.RobotCode),
|
||||||
|
WithCardTemplateID(cfg.CardTemplateID),
|
||||||
|
WithCardTemplateContentKey(cfg.CardTemplateContentKey))
|
||||||
|
|
||||||
return &DingTalkChannel{
|
return &DingTalkChannel{
|
||||||
BaseChannel: base,
|
BaseChannel: base,
|
||||||
config: cfg,
|
config: cfg,
|
||||||
clientID: cfg.ClientID,
|
clientID: cfg.ClientID,
|
||||||
clientSecret: cfg.ClientSecret,
|
clientSecret: cfg.ClientSecret,
|
||||||
|
client: dingTalkClient,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -103,25 +112,16 @@ func (c *DingTalkChannel) Send(ctx context.Context, msg bus.OutboundMessage) err
|
||||||
if !c.IsRunning() {
|
if !c.IsRunning() {
|
||||||
return channels.ErrNotRunning
|
return channels.ErrNotRunning
|
||||||
}
|
}
|
||||||
|
// Check if we have a card instance ID for this chat (indicating we can send a card reply)
|
||||||
// Get session webhook from storage
|
cardInstanceIdRaw, ok := c.cardInstanceIds.LoadAndDelete(msg.ChatID)
|
||||||
sessionWebhookRaw, ok := c.sessionWebhooks.Load(msg.ChatID)
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("no session_webhook found for chat %s, cannot send message", msg.ChatID)
|
return c.SendDirectReply(ctx, msg)
|
||||||
}
|
}
|
||||||
|
cardInstanceId, ok := cardInstanceIdRaw.(string)
|
||||||
sessionWebhook, ok := sessionWebhookRaw.(string)
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("invalid session_webhook type for chat %s", msg.ChatID)
|
return c.SendDirectReply(ctx, msg)
|
||||||
}
|
}
|
||||||
|
return c.SendCardReply(ctx, cardInstanceId, msg.Content)
|
||||||
logger.DebugCF("dingtalk", "Sending message", map[string]any{
|
|
||||||
"chat_id": msg.ChatID,
|
|
||||||
"preview": utils.Truncate(msg.Content, 100),
|
|
||||||
})
|
|
||||||
|
|
||||||
// Use the session webhook to send the reply
|
|
||||||
return c.SendDirectReply(ctx, sessionWebhook, msg.Content)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// onChatBotMessageReceived implements the IChatBotMessageHandler function signature
|
// onChatBotMessageReceived implements the IChatBotMessageHandler function signature
|
||||||
|
|
@ -154,9 +154,6 @@ func (c *DingTalkChannel) onChatBotMessageReceived(
|
||||||
chatID = data.ConversationId
|
chatID = data.ConversationId
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the session webhook for this chat so we can reply later
|
|
||||||
c.sessionWebhooks.Store(chatID, data.SessionWebhook)
|
|
||||||
|
|
||||||
metadata := map[string]string{
|
metadata := map[string]string{
|
||||||
"sender_name": senderNick,
|
"sender_name": senderNick,
|
||||||
"conversation_id": data.ConversationId,
|
"conversation_id": data.ConversationId,
|
||||||
|
|
@ -196,6 +193,16 @@ func (c *DingTalkChannel) onChatBotMessageReceived(
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := c.tryCardCreateAndDeliver(ctx, chatID, data); err != nil {
|
||||||
|
logger.ErrorCF("dingtalk", "Failed to create or deliver card", map[string]any{
|
||||||
|
"error": err.Error(),
|
||||||
|
"chat_id": chatID,
|
||||||
|
"sender_id": senderID,
|
||||||
|
})
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
// Store the session webhook for this chat so we can reply later
|
||||||
|
c.sessionWebhooks.Store(chatID, data.SessionWebhook)
|
||||||
// Handle the message through the base channel
|
// Handle the message through the base channel
|
||||||
c.HandleMessage(ctx, peer, "", senderID, chatID, content, nil, metadata, sender)
|
c.HandleMessage(ctx, peer, "", senderID, chatID, content, nil, metadata, sender)
|
||||||
|
|
||||||
|
|
@ -205,11 +212,25 @@ func (c *DingTalkChannel) onChatBotMessageReceived(
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendDirectReply sends a direct reply using the session webhook
|
// SendDirectReply sends a direct reply using the session webhook
|
||||||
func (c *DingTalkChannel) SendDirectReply(ctx context.Context, sessionWebhook, content string) error {
|
func (c *DingTalkChannel) SendDirectReply(ctx context.Context, msg bus.OutboundMessage) error {
|
||||||
|
// Get session webhook from storage
|
||||||
|
sessionWebhookRaw, ok := c.sessionWebhooks.LoadAndDelete(msg.ChatID)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("no session_webhook found for chat %s, cannot send message", msg.ChatID)
|
||||||
|
}
|
||||||
|
sessionWebhook, ok := sessionWebhookRaw.(string)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("invalid session_webhook type for chat %s", msg.ChatID)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.DebugCF("dingtalk", "Sending message", map[string]any{
|
||||||
|
"chat_id": msg.ChatID,
|
||||||
|
"preview": utils.Truncate(msg.Content, 100),
|
||||||
|
})
|
||||||
replier := chatbot.NewChatbotReplier()
|
replier := chatbot.NewChatbotReplier()
|
||||||
|
|
||||||
// Convert string content to []byte for the API
|
// Convert string content to []byte for the API
|
||||||
contentBytes := []byte(content)
|
contentBytes := []byte(msg.Content)
|
||||||
titleBytes := []byte("PicoClaw")
|
titleBytes := []byte("PicoClaw")
|
||||||
|
|
||||||
// Send markdown formatted reply
|
// Send markdown formatted reply
|
||||||
|
|
@ -222,6 +243,24 @@ func (c *DingTalkChannel) SendDirectReply(ctx context.Context, sessionWebhook, c
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("dingtalk send: %w", channels.ErrTemporary)
|
return fmt.Errorf("dingtalk send: %w", channels.ErrTemporary)
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DingTalkChannel) SendCardReply(ctx context.Context, cardInstanceId, content string) error {
|
||||||
|
if err := c.client.CardStreaming(ctx, cardInstanceId, content); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DingTalkChannel) tryCardCreateAndDeliver(ctx context.Context, chatID string, data *chatbot.BotCallbackDataModel) error {
|
||||||
|
if c.config.CardTemplateID == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
cardInstanceId, err := c.client.CardCreateAndDeliver(ctx, data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.cardInstanceIds.Store(chatID, cardInstanceId)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
21
pkg/channels/dingtalk/options.go
Normal file
21
pkg/channels/dingtalk/options.go
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
package dingtalk
|
||||||
|
|
||||||
|
type ClientOption func(*Client)
|
||||||
|
|
||||||
|
func WithCardTemplateID(cardTemplateID string) ClientOption {
|
||||||
|
return func(client *Client) {
|
||||||
|
client.cardTemplateID = cardTemplateID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithCardTemplateContentKey(cardTemplateContentKey string) ClientOption {
|
||||||
|
return func(client *Client) {
|
||||||
|
client.cardTemplateContentKey = cardTemplateContentKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithRobotCode(robotCode string) ClientOption {
|
||||||
|
return func(client *Client) {
|
||||||
|
client.robotCode = robotCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -320,6 +320,10 @@ type DingTalkConfig struct {
|
||||||
AllowFrom FlexibleStringSlice `json:"allow_from" env:"PICOCLAW_CHANNELS_DINGTALK_ALLOW_FROM"`
|
AllowFrom FlexibleStringSlice `json:"allow_from" env:"PICOCLAW_CHANNELS_DINGTALK_ALLOW_FROM"`
|
||||||
GroupTrigger GroupTriggerConfig `json:"group_trigger,omitempty"`
|
GroupTrigger GroupTriggerConfig `json:"group_trigger,omitempty"`
|
||||||
ReasoningChannelID string `json:"reasoning_channel_id" env:"PICOCLAW_CHANNELS_DINGTALK_REASONING_CHANNEL_ID"`
|
ReasoningChannelID string `json:"reasoning_channel_id" env:"PICOCLAW_CHANNELS_DINGTALK_REASONING_CHANNEL_ID"`
|
||||||
|
|
||||||
|
RobotCode string `json:"robot_code,omitempty" env:"PICOCLAW_CHANNELS_DINGTALK_ROBOT_CODE"`
|
||||||
|
CardTemplateID string `json:"card_template_id,omitempty" env:"PICOCLAW_CHANNELS_DINGTALK_CARD_TEMPLATE_ID"`
|
||||||
|
CardTemplateContentKey string `json:"card_template_content_key,omitempty" env:"PICOCLAW_CHANNELS_DINGTALK_CARD_TEMPLATE_CONTENT_KEY"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SlackConfig struct {
|
type SlackConfig struct {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue