327 lines
14 KiB
Go
327 lines
14 KiB
Go
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
|
||
}
|