Files
dingtalk-workspace-cli/internal/helpers/ding.go

327 lines
14 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package helpers
import (
"fmt"
"os"
"strings"
"github.com/spf13/cobra"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd/contract"
)
// ──────────────────────────────────────────────────────────
// dws ding — DING 消息
// ──────────────────────────────────────────────────────────
// remindType: 服务端 API 1=应用内 2=短信 3=电话
var dingRemindTypeMap = map[string]int{"app": 1, "sms": 2, "call": 3}
func newDingCommand() *cobra.Command {
// Product-level Agent routing Decl (migrated from selection/ding.json
// products.ding). Catalog assembly stamps provenance contract_final.
contract.RegisterProductDecl(contract.ProductDecl{
ID: "ding",
Selection: contract.ProductSelectionDecl{
AgentSummary: "以企业机器人发送或撤回应用内/短信/电话 DING",
UseWhen: []string{
"需要机器人身份发送或撤回 DING",
},
AvoidWhen: []string{
"普通聊天消息用 chat;用户身份 DING 不要走机器人命令",
},
},
})
root := &cobra.Command{
Use: "ding",
Short: "DING 消息 / 发送 / 撤回",
Long: `发送和撤回 DING 消息(应用内/短信/电话)。预发环境可用。`,
RunE: groupRunE,
}
dingMessageCmd := &cobra.Command{Use: "message", Short: "DING 消息管理", RunE: groupRunE}
dingMessageSendCmd := &cobra.Command{
Use: "send",
Short: "发送 DING 消息",
Long: `发送 DING 消息。类型:
app = 应用内 DING (默认)
sms = 短信 DING (有成本)
call = 电话 DING (有成本)`,
Example: ` # 查询 userId: dws contact user search --keyword "姓名"
dws ding message send --robot-code <robot-code> --users userId1,userId2 --content "请查看"
dws ding message send --robot-code <robot-code> --type call --users userId1 --content "紧急告警"`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := validateRequiredFlags(cmd, "users", "content"); err != nil {
return err
}
robotCode := mustGetFlag(cmd, "robot-code")
if robotCode == "" {
robotCode = os.Getenv("DINGTALK_DING_ROBOT_CODE")
}
if robotCode == "" {
return fmt.Errorf("flag --robot-code is required (or set DINGTALK_DING_ROBOT_CODE env var)")
}
typeStr := mustGetFlag(cmd, "type")
remindType, ok := dingRemindTypeMap[typeStr]
if !ok {
remindType = 1 // 默认应用内
}
toStr := mustGetFlag(cmd, "users")
var receiverUserIdList []string
for _, uid := range strings.Split(toStr, ",") {
if u := strings.TrimSpace(uid); u != "" {
receiverUserIdList = append(receiverUserIdList, u)
}
}
return callMCPTool("send_ding_message", map[string]any{
"robotCode": robotCode,
"remindType": remindType,
"receiverUserIdList": receiverUserIdList,
"content": mustGetFlag(cmd, "content"),
})
},
}
DeclareLeafMetadata(dingMessageSendCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "write", Risk: "medium",
Confirmation: "not_required", Idempotency: "unknown",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "ding",
Name: "send_ding_message",
CanonicalPath: "ding.send_ding_message",
CLIPath: "ding message send",
PrimaryCLIPath: "ding message send",
},
Description: "以企业机器人发送应用内/短信/电话 DING",
Interface: &contract.InterfaceSpec{
Mode: "mcp",
Availability: "available",
Ref: &contract.InterfaceRefSpec{ProductID: "ding", RPCName: "send_ding_message"},
},
Selection: contract.SelectionSpec{
AgentSummary: "以企业机器人发送应用内/短信/电话 DING",
UseWhen: []string{"需要用企业机器人向指定 userId 发送应用内、短信或电话 DING"},
AvoidWhen: []string{
"普通聊天消息用 chat message send / send-by-bot",
"需要用户身份 DING 时不要用本命令(机器人身份)",
"短信/电话有成本,用户未确认前不要发 call/sms",
},
Examples: []string{
"dws ding message send --robot-code <ROBOT_CODE> --users userId1,userId2 --content \"请查看\" --format json",
"dws ding message send --robot-code <ROBOT_CODE> --type call --users userId1 --content \"紧急告警\" --format json",
},
},
Parameters: []contract.ParamDecl{
{Name: "content", Required: boolPtr(true)},
{Name: "robot-code", Required: boolPtr(true)},
{Name: "type", Property: "remindType"},
{Name: "users", Property: "receiverUserIdList", Required: boolPtr(true), InterfaceType: "array"},
},
},
})
dingMessageRecallCmd := &cobra.Command{
Use: "recall",
Short: "撤回 DING 消息",
Example: ` dws ding message recall --robot-code <robot-code> --id <open-ding-id>`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := validateRequiredFlags(cmd, "id"); err != nil {
return err
}
robotCode := mustGetFlag(cmd, "robot-code")
if robotCode == "" {
robotCode = os.Getenv("DINGTALK_DING_ROBOT_CODE")
}
if robotCode == "" {
return fmt.Errorf("flag --robot-code is required (or set DINGTALK_DING_ROBOT_CODE env var)")
}
return callMCPTool("recall_ding_message", map[string]any{
"robotCode": robotCode,
"openDingId": mustGetFlag(cmd, "id"),
})
},
}
DeclareLeafMetadata(dingMessageRecallCmd, LeafSpec{
Safety: contract.SafetySpec{
Effect: "write", Risk: "medium",
Confirmation: "not_required", Idempotency: "unknown",
},
Contract: LeafContract{
Identity: contract.ToolIdentitySpec{
ProductID: "ding",
Name: "recall_ding_message",
CanonicalPath: "ding.recall_ding_message",
CLIPath: "ding message recall",
PrimaryCLIPath: "ding message recall",
},
Description: "撤回已发送的机器人 DING",
Interface: &contract.InterfaceSpec{
Mode: "mcp",
Availability: "available",
Ref: &contract.InterfaceRefSpec{ProductID: "ding", RPCName: "recall_ding_message"},
},
Selection: contract.SelectionSpec{
AgentSummary: "撤回已发送的机器人 DING",
UseWhen: []string{"已知 openDingId 与同一 robot-code,需要撤回机器人 DING"},
AvoidWhen: []string{"需要以用户身份撤回 DING 时不要使用本命令"},
Examples: []string{"dws ding message recall --robot-code <ROBOT_CODE> --id <OPEN_DING_ID> --format json"},
},
Parameters: []contract.ParamDecl{
{Name: "id", Property: "openDingId"},
},
},
})
dingMessageListCmd := &cobra.Command{
Use: "list",
Short: "查询 DING 消息历史",
Long: `查询当前用户的 DING 消息列表,支持按类型过滤。
--type 支持: ALL(全部)、UNREAD(未读)、SEND(已发)、NEW_COMMENT(新评论)、DELETED(已删除)。
--type 为服务端必填字段,空值会报「type不能为空」;不传时 CLI 默认按 ALL 查询。
列表项会返回 DING 内容,调用方可在同一结果中读取 openDingId、状态与 content,无需再发起详情查询。`,
Example: ` dws ding message list # 默认 --type ALL
dws ding message list --type UNREAD
dws ding message list --type SEND --cursor 10`,
RunE: func(cmd *cobra.Command, args []string) error {
toolArgs := map[string]any{}
if v, _ := cmd.Flags().GetInt64("cursor"); v > 0 {
toolArgs["cursor"] = v
}
// type 是服务端必填,空值会报错;不传或传空时兜底为 ALL。
t, _ := cmd.Flags().GetString("type")
if t == "" {
t = "ALL"
}
toolArgs["type"] = t
return callMCPToolOnServer("im", "list_ding_messages", toolArgs)
},
}
dingMessageReceiverStatusCmd := &cobra.Command{
Use: "receiver-status",
Short: "查看 DING 接收状态",
Long: `查看指定 DING 消息的接收者状态(已读/未读等)。`,
Example: ` dws ding message receiver-status --ding-id <openDingId>
# 查询 dingId: dws ding message list`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := validateRequiredFlags(cmd, "ding-id"); err != nil {
return err
}
return callMCPToolOnServer("im", "list_ding_receiver_status", map[string]any{
"openDingId": mustGetFlag(cmd, "ding-id"),
})
},
}
// ── send-personal: 以用户身份发送 DING ──────────────────────
dingMessageSendPersonalCmd := &cobra.Command{
Use: "send-personal",
Short: "以用户身份发送 DING",
Long: `以当前用户身份(非机器人)发送 DING 消息。提醒类型:
app = 应用内 DING (默认)
sms = 短信 DING (有成本)
call = 电话 DING (有成本)`,
Example: ` # 查询 openDingTalkId: dws contact user search --query "姓名"
dws ding message send-personal --users openDingTalkId1,openDingTalkId2 --content "请查看"
dws ding message send-personal --type call --users openDingTalkId1 --content "紧急告警"`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := validateRequiredFlags(cmd, "users", "content"); err != nil {
return err
}
users := parseCSVValues(mustGetFlag(cmd, "users"))
toolArgs := map[string]any{
"receiverOpenDingTalkIds": users,
"content": mustGetFlag(cmd, "content"),
"remindType": mustGetFlag(cmd, "type"),
}
if v, _ := cmd.Flags().GetString("uuid"); v != "" {
toolArgs["uuid"] = v
}
return callMCPToolOnServer("im", "send_personal_ding", toolArgs)
},
}
// ── send-by-message: 消息转 DING ─────────────────────────────
dingMessageSendByMessageCmd := &cobra.Command{
Use: "send-by-message",
Short: "消息转 DING(将聊天消息转为 DING 通知)",
Long: `将指定聊天消息转为 DING 发送给指定接收者。提醒类型:
app = 应用内 DING (默认)
sms = 短信 DING (有成本)
call = 电话 DING (有成本)`,
Example: ` # 查询 openDingTalkId: dws contact user search --query "姓名"
# 查询 openConversationId: dws chat search --keyword "群名"
dws ding message send-by-message --group <openConversationId> --message-id <openMessageId> --users id1,id2
dws ding message send-by-message --group <openConversationId> --message-id <openMessageId> --users id1 --type sms`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := validateRequiredFlags(cmd, "group", "message-id", "users"); err != nil {
return err
}
users := parseCSVValues(mustGetFlag(cmd, "users"))
toolArgs := map[string]any{
"openConversationId": mustGetFlag(cmd, "group"),
"openMessageId": mustGetFlag(cmd, "message-id"),
"receiverOpenDingTalkIds": users,
"remindType": mustGetFlag(cmd, "type"),
}
if v, _ := cmd.Flags().GetString("uuid"); v != "" {
toolArgs["uuid"] = v
}
return callMCPToolOnServer("im", "send_ding_by_message", toolArgs)
},
}
// ── recall-personal: 以用户身份撤回 DING ────────────────────
dingMessageRecallPersonalCmd := &cobra.Command{
Use: "recall-personal",
Short: "以用户身份撤回 DING",
Long: `以当前用户身份撤回已发送的 DING 消息。需要提供发送时返回的 openDingId。`,
Example: ` dws ding message recall-personal --id <openDingId>
# 查询 openDingId: dws ding message list`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := validateRequiredFlags(cmd, "id"); err != nil {
return err
}
return callMCPToolOnServer("im", "recall_personal_ding", map[string]any{
"openDingId": mustGetFlag(cmd, "id"),
})
},
}
dingMessageSendCmd.Flags().String("robot-code", "", "机器人 ID,发 DING 的机器人编码 (必填,可从 应用管理→机器人 获取,或设 DINGTALK_DING_ROBOT_CODE)")
dingMessageSendCmd.Flags().String("type", "app", "提醒类型: app/sms/call (默认 app)")
dingMessageSendCmd.Flags().String("users", "", "接收人 userId 列表 (必填)")
dingMessageSendCmd.Flags().String("content", "", "消息内容 (必填)")
dingMessageRecallCmd.Flags().String("robot-code", "", "机器人 ID (必填,或设 DINGTALK_DING_ROBOT_CODE)")
dingMessageRecallCmd.Flags().String("id", "", "DING 消息 ID (必填)")
dingMessageListCmd.Flags().Int64("cursor", 0, "分页游标(首次传 0,翻页传返回的 nextCursor)")
dingMessageListCmd.Flags().String("type", "ALL", "消息类型: ALL / UNREAD / SEND / NEW_COMMENT / DELETED(必填,服务端不接受空值;默认 ALL 全部)")
dingMessageReceiverStatusCmd.Flags().String("ding-id", "", "DING 消息 openDingId (必填)")
_ = dingMessageReceiverStatusCmd.MarkFlagRequired("ding-id")
dingMessageSendPersonalCmd.Flags().String("users", "", "接收者 openDingTalkId 列表,逗号分隔 (必填)")
_ = dingMessageSendPersonalCmd.MarkFlagRequired("users")
dingMessageSendPersonalCmd.Flags().String("content", "", "DING 内容 (必填)")
_ = dingMessageSendPersonalCmd.MarkFlagRequired("content")
dingMessageSendPersonalCmd.Flags().String("type", "app", "提醒类型: app/sms/call (默认 app)")
dingMessageSendPersonalCmd.Flags().String("uuid", "", "幂等唯一标识(可选,不传由服务端生成)")
dingMessageRecallPersonalCmd.Flags().String("id", "", "DING 消息 openDingId (必填)")
_ = dingMessageRecallPersonalCmd.MarkFlagRequired("id")
dingMessageSendByMessageCmd.Flags().String("group", "", "原消息所在会话 openConversationId (必填)")
_ = dingMessageSendByMessageCmd.MarkFlagRequired("group")
dingMessageSendByMessageCmd.Flags().String("message-id", "", "原消息 openMessageId (必填)")
_ = dingMessageSendByMessageCmd.MarkFlagRequired("message-id")
dingMessageSendByMessageCmd.Flags().String("users", "", "接收者 openDingTalkId 列表,逗号分隔 (必填)")
_ = dingMessageSendByMessageCmd.MarkFlagRequired("users")
dingMessageSendByMessageCmd.Flags().String("type", "app", "提醒类型: app/sms/call (默认 app)")
dingMessageSendByMessageCmd.Flags().String("uuid", "", "幂等唯一标识(可选,不传由服务端生成)")
dingMessageCmd.AddCommand(dingMessageSendCmd, dingMessageRecallCmd, dingMessageListCmd, dingMessageReceiverStatusCmd, dingMessageSendPersonalCmd, dingMessageRecallPersonalCmd, dingMessageSendByMessageCmd)
root.AddCommand(dingMessageCmd)
return root
}