Skip to content

refactor(channels): Implement DingTalk client with card messaging cap…#1251

Closed
zhaoyunxing92 wants to merge 8 commits intosipeed:mainfrom
zhaoyunxing92:channels/dingtalk
Closed

refactor(channels): Implement DingTalk client with card messaging cap…#1251
zhaoyunxing92 wants to merge 8 commits intosipeed:mainfrom
zhaoyunxing92:channels/dingtalk

Conversation

@zhaoyunxing92
Copy link
Copy Markdown

This pull request introduces a new, feature-rich Dingtalk API client and integrates support for sending "card" messages (rich, updatable messages) in addition to the existing direct reply method. The changes include adding a new client implementation, updating the Dingtalk channel to use this client for card delivery, and extending configuration options to support card templates and robot codes.

Key changes:

Dingtalk card messaging support:

  • Added a new Client implementation in pkg/channels/dingtalk/client.go with methods for authentication, sending batch messages, creating and delivering cards, updating card content, and sending private messages. This enables richer, updatable card-based interactions in Dingtalk.
  • Integrated the new client into the DingTalkChannel struct and initialization, enabling card-based replies when a card template is configured. [1] [2]
  • Implemented logic in DingTalkChannel to create and deliver a card when handling incoming messages, store the card instance ID, and use it for subsequent replies. Falls back to direct replies if card delivery is not available. [1] [2] [3] [4]

Configuration and options:

  • Extended DingTalkConfig to include RobotCode, CardTemplateID, and CardTemplateContentKey fields, making card messaging configurable via environment variables or JSON.
  • Added ClientOption functions in pkg/channels/dingtalk/options.go to support flexible client configuration for robot code and card template details.

📝 Description

🗣️ Type of Change

  • 🐞 Bug fix (non-breaking change which fixes an issue)
  • ✨ New feature (non-breaking change which adds functionality)
  • 📖 Documentation update
  • ⚡ Code refactoring (no functional changes, no api changes)

🤖 AI Code Generation

  • 🤖 Fully AI-generated (100% AI, 0% Human)
  • 🛠️ Mostly AI-generated (AI draft, Human verified/modified)
  • 👨‍💻 Mostly Human-written (Human lead, AI assisted or none)

🔗 Related Issue

📚 Technical Context (Skip for Docs)

  • Reference URL:
  • Reasoning:

🧪 Test Environment

  • Hardware:
  • OS:
  • Model/Provider:
  • Channels:

📸 Evidence (Optional)

Click to view Logs/Screenshots

☑️ Checklist

  • My code/docs follow the style of this project.
  • I have performed a self-review of my own changes.
  • I have updated the documentation accordingly.

Copilot AI review requested due to automatic review settings March 8, 2026 15:24
@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Mar 8, 2026

CLA assistant check
All committers have signed the CLA.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a new DingTalk API client and integrates “card” (rich/updatable) messaging into the DingTalk channel, making card delivery configurable via new DingTalk config fields.

Changes:

  • Added a new pkg/channels/dingtalk HTTP client for token management, card creation/delivery, and card streaming updates.
  • Updated the DingTalk channel send path to prefer card replies when configured, with fallback to direct replies.
  • Extended DingTalkConfig with RobotCode, CardTemplateID, and CardTemplateContentKey, plus client option helpers.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
pkg/config/config.go Adds new DingTalk configuration fields for card messaging and robot code.
pkg/channels/dingtalk/options.go Introduces client option setters for card template and robot code.
pkg/channels/dingtalk/dingtalk.go Integrates the new client and implements card-vs-direct reply routing.
pkg/channels/dingtalk/client.go New DingTalk HTTP client implementing token, card, and messaging APIs.
go.mod Promotes github.com/ergochat/irc-go to a direct dependency.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pkg/channels/dingtalk/dingtalk.go
Comment thread pkg/channels/dingtalk/client.go
Comment thread pkg/channels/dingtalk/client.go Outdated
Comment thread pkg/channels/dingtalk/client.go
Comment thread pkg/channels/dingtalk/client.go Outdated
Comment thread pkg/channels/dingtalk/dingtalk.go Outdated
Comment thread pkg/channels/dingtalk/dingtalk.go Outdated
Comment thread pkg/channels/dingtalk/dingtalk.go Outdated
@sipeed-bot sipeed-bot bot added type: enhancement New feature or request domain: channel domain: config go Pull requests that update go code labels Mar 8, 2026
- add mutex to Client to protect token/expires from concurrent refresh
- pass context through GetToken so cancellation/deadlines propagate
- rename Id/Ids identifiers to ID/IDs per Go conventions
- fall back to direct reply instead of dropping message on card delivery failure
- use Load instead of LoadAndDelete for sessionWebhooks and cardInstanceIDs to support multiple outbound messages per turn
Copilot AI review requested due to automatic review settings March 12, 2026 13:26
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment thread pkg/channels/dingtalk/dingtalk.go Outdated
Comment thread pkg/channels/dingtalk/client.go
Comment thread pkg/channels/dingtalk/dingtalk.go
Comment thread pkg/channels/dingtalk/client.go
Comment thread pkg/channels/dingtalk/client.go
Comment thread pkg/channels/dingtalk/dingtalk.go
Comment thread pkg/channels/dingtalk/dingtalk.go
Copilot AI review requested due to automatic review settings March 13, 2026 12:56
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 8 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment thread pkg/channels/dingtalk/dingtalk.go Outdated
Comment thread pkg/channels/dingtalk/dingtalk.go Outdated
// Try to create and deliver card (optional feature)
// If it fails, log the error but continue with normal message handling
if cardID, err := c.tryCardCreateAndDeliver(ctx, data); err != nil {
logger.WarnC("dingtalk", "Failed to create or deliver card, falling back to direct reply")
Comment on lines 248 to 250
if err != nil {
return fmt.Errorf("dingtalk send: %w", channels.ErrTemporary)
}
Comment on lines +225 to +237
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,
}
}
Comment on lines +259 to +280
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))
}
Comment on lines +119 to +128
// Check if we have a card instance ID for this chat (indicating we can send a card reply)
cardInstanceIDRaw, ok := c.cardInstanceIDs.LoadAndDelete(msg.ChatID)
if !ok {
return fmt.Errorf("no session_webhook found for chat %s, cannot send message", msg.ChatID)
return c.SendDirectReply(ctx, msg)
}

sessionWebhook, ok := sessionWebhookRaw.(string)
cardInstanceID, ok := cardInstanceIDRaw.(string)
if !ok {
return fmt.Errorf("invalid session_webhook type for chat %s", msg.ChatID)
return c.SendDirectReply(ctx, msg)
}

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)
return c.SendCardReply(ctx, cardInstanceID, msg.Content)
Comment thread pkg/channels/dingtalk/dingtalk.go Outdated
// Store the session webhook for this chat so we can reply later
c.sessionWebhooks.Store(chatID, data.SessionWebhook)
} else {
chatID = data.MsgId
Comment thread pkg/channels/dingtalk/client.go
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings March 18, 2026 14:17
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 9 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment thread pkg/channels/dingtalk/dingtalk.go
Comment thread pkg/channels/dingtalk/client.go
Comment on lines +119 to +128
// Check if we have a card instance ID for this chat (indicating we can send a card reply)
cardInstanceIDRaw, ok := c.cardInstanceIDs.LoadAndDelete(msg.ChatID)
if !ok {
return fmt.Errorf("no session_webhook found for chat %s, cannot send message", msg.ChatID)
return c.SendDirectReply(ctx, msg)
}

sessionWebhook, ok := sessionWebhookRaw.(string)
cardInstanceID, ok := cardInstanceIDRaw.(string)
if !ok {
return fmt.Errorf("invalid session_webhook type for chat %s", msg.ChatID)
return c.SendDirectReply(ctx, msg)
}

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)
return c.SendCardReply(ctx, cardInstanceID, msg.Content)
Comment on lines +209 to +210
chatID = data.MsgId
c.cardInstanceIDs.Store(chatID, cardID)
Copy link
Copy Markdown
Collaborator

@alexhoshina alexhoshina Mar 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have the same opinion as Copilot #1251 (comment)

Comment thread pkg/channels/dingtalk/client.go Outdated
Comment on lines +113 to +119
id, err := uuid.NewUUID()
if err != nil {
return err
}
body := map[string]any{
"outTrackId": cardInstanceID,
"guid": id.String(),
Comment thread pkg/channels/dingtalk/client.go Outdated
}
)

id, err := uuid.NewUUID()
Comment on lines +274 to +278
return err
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if res.StatusCode != http.StatusOK || err != nil {
}

// Handle the message through the base channel
c.HandleMessage(ctx, peer, "", senderID, chatID, content, nil, metadata, sender)
Comment thread pkg/channels/dingtalk/dingtalk.go Outdated
if c.config.CardTemplateID != "" {
// If it fails, log the error but continue with normal message handling
if cardID, err := c.tryCardCreateAndDeliver(ctx, data); err != nil {
logger.WarnC("dingtalk", "Failed to create or deliver card, falling back to direct reply")
Comment on lines +209 to +210
chatID = data.MsgId
c.cardInstanceIDs.Store(chatID, cardID)
Copy link
Copy Markdown
Collaborator

@alexhoshina alexhoshina Mar 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have the same opinion as Copilot #1251 (comment)

Comment on lines +224 to +225
c.cardInstanceIDs.Store(chatID, cardID)
c.sessionWebhooks.Store(chatID, data.SessionWebhook)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cardInstanceIDs and sessionWebhooks will grow indefinitely during prolonged operation, as these two maps are only cleared when Stop() is called

@sipeed-bot
Copy link
Copy Markdown

sipeed-bot bot commented Apr 2, 2026

@zhaoyunxing92 Hi! This PR has had no activity for over 2 weeks, so I'm closing it for now to keep things tidy. If it's still relevant, feel free to reopen it anytime and we'll pick it back up.

@sipeed-bot sipeed-bot bot closed this Apr 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

domain: channel domain: config go Pull requests that update go code type: enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants