Merge pull request #973 from xlb1130/feat/85378080-chat-message-time-defaults
feat(chat): default message query time ranges
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
category: Fixed
|
||||
---
|
||||
|
||||
- **Chat message time defaults** (#973) — default omitted `chat message list-all` time bounds in `Asia/Shanghai` when emitting timezone-less `yyyy-MM-dd HH:mm:ss` values, matching parsing semantics and rejecting reversed windows.
|
||||
+111
-54
@@ -468,22 +468,50 @@ func validateNativeSearchConversationScope(conversationIDs []string) error {
|
||||
}
|
||||
|
||||
func chatMessageListAllArgs(cmd *cobra.Command) (map[string]any, error) {
|
||||
if err := validateRequiredFlags(cmd, "start", "end"); err != nil {
|
||||
startRaw, endRaw, err := defaultChatMessageListAllTimeRange(cmd, 24*time.Hour)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
startMs, err := parseISOTimeToMillis("start", startRaw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
endMs, err := parseISOTimeToMillis("end", endRaw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateTimeRange(startMs, endMs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cursor, _ := cmd.Flags().GetString("cursor")
|
||||
return map[string]any{
|
||||
"startTime": mustGetFlag(cmd, "start"),
|
||||
"endTime": mustGetFlag(cmd, "end"),
|
||||
"startTime": startRaw,
|
||||
"endTime": endRaw,
|
||||
"limit": chatIntFlagOrFallback(cmd, "limit", "size"),
|
||||
"cursor": cursor,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func chatMessageListBySenderArgs(cmd *cobra.Command) (map[string]any, error) {
|
||||
if err := validateRequiredFlags(cmd, "start"); err != nil {
|
||||
return nil, err
|
||||
func defaultChatMessageListAllTimeRange(cmd *cobra.Command, lookback time.Duration) (string, string, error) {
|
||||
startRaw := mustGetFlag(cmd, "start")
|
||||
endRaw := mustGetFlag(cmd, "end")
|
||||
anchor := time.Now()
|
||||
if endRaw == "" {
|
||||
endRaw = formatChatMessageListAllTime(anchor.UnixMilli())
|
||||
} else if startRaw == "" {
|
||||
endMs, err := parseISOTimeToMillis("end", endRaw)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
anchor = time.UnixMilli(endMs)
|
||||
}
|
||||
if startRaw == "" {
|
||||
startRaw = formatChatMessageListAllTime(anchor.Add(-lookback).UnixMilli())
|
||||
}
|
||||
return startRaw, endRaw, nil
|
||||
}
|
||||
|
||||
func chatMessageListBySenderArgs(cmd *cobra.Command) (map[string]any, error) {
|
||||
senderUserID := flagOrFallback(cmd, "sender-user-id", "sender")
|
||||
senderOpenDingTalkID, _ := cmd.Flags().GetString("sender-open-dingtalk-id")
|
||||
if senderUserID != "" && senderOpenDingTalkID != "" {
|
||||
@@ -492,13 +520,13 @@ func chatMessageListBySenderArgs(cmd *cobra.Command) (map[string]any, error) {
|
||||
if senderUserID == "" && senderOpenDingTalkID == "" {
|
||||
return nil, fmt.Errorf("--sender-user-id or --sender-open-dingtalk-id is required")
|
||||
}
|
||||
startMs, err := parseISOTimeToMillis("start", mustGetFlag(cmd, "start"))
|
||||
startRaw, endRaw, err := defaultChatMessageTimeRange(cmd, 7*24*time.Hour)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
endRaw, _ := cmd.Flags().GetString("end")
|
||||
if strings.TrimSpace(endRaw) == "" {
|
||||
endRaw = time.Now().Format(time.RFC3339)
|
||||
startMs, err := parseISOTimeToMillis("start", startRaw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
endMs, err := parseISOTimeToMillis("end", endRaw)
|
||||
if err != nil {
|
||||
@@ -523,14 +551,15 @@ func chatMessageListBySenderArgs(cmd *cobra.Command) (map[string]any, error) {
|
||||
}
|
||||
|
||||
func chatMessageListMentionsArgs(cmd *cobra.Command) (map[string]any, error) {
|
||||
if err := validateRequiredFlags(cmd, "start", "end"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
startMs, err := parseISOTimeToMillis("start", mustGetFlag(cmd, "start"))
|
||||
startRaw, endRaw, err := defaultChatMessageTimeRange(cmd, 7*24*time.Hour)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
endMs, err := parseISOTimeToMillis("end", mustGetFlag(cmd, "end"))
|
||||
startMs, err := parseISOTimeToMillis("start", startRaw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
endMs, err := parseISOTimeToMillis("end", endRaw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -565,14 +594,15 @@ func chatMessageSearchArgs(cmd *cobra.Command) (map[string]any, error) {
|
||||
if err := validateRequiredFlagWithAliases(cmd, "query", "keyword"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateRequiredFlags(cmd, "start", "end"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
startMs, err := parseISOTimeToMillis("start", mustGetFlag(cmd, "start"))
|
||||
startRaw, endRaw, err := defaultChatMessageTimeRange(cmd, 7*24*time.Hour)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
endMs, err := parseISOTimeToMillis("end", mustGetFlag(cmd, "end"))
|
||||
startMs, err := parseISOTimeToMillis("start", startRaw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
endMs, err := parseISOTimeToMillis("end", endRaw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -593,6 +623,33 @@ func chatMessageSearchArgs(cmd *cobra.Command) (map[string]any, error) {
|
||||
return toolArgs, nil
|
||||
}
|
||||
|
||||
func defaultChatMessageTimeRange(cmd *cobra.Command, lookback time.Duration) (string, string, error) {
|
||||
startRaw := mustGetFlag(cmd, "start")
|
||||
endRaw := mustGetFlag(cmd, "end")
|
||||
anchor := time.Now()
|
||||
if endRaw == "" {
|
||||
endRaw = anchor.Format(time.RFC3339)
|
||||
} else if startRaw == "" {
|
||||
endMs, err := parseISOTimeToMillis("end", endRaw)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
anchor = time.UnixMilli(endMs)
|
||||
}
|
||||
if startRaw == "" {
|
||||
startRaw = anchor.Add(-lookback).Format(time.RFC3339)
|
||||
}
|
||||
return startRaw, endRaw, nil
|
||||
}
|
||||
|
||||
func formatChatMessageListAllTime(ms int64) string {
|
||||
return time.UnixMilli(ms).In(shanghaiLocation()).Format("2006-01-02 15:04:05")
|
||||
}
|
||||
|
||||
func defaultChatMessageListTime() string {
|
||||
return time.Now().In(shanghaiLocation()).Format("2006-01-02 15:04:05")
|
||||
}
|
||||
|
||||
func chatMessageSearchAdvancedArgs(cmd *cobra.Command) (map[string]any, error) {
|
||||
toolArgs := map[string]any{}
|
||||
if v := flagOrFallback(cmd, "query", "keyword"); v != "" {
|
||||
@@ -2606,8 +2663,9 @@ func newChatCommand() *cobra.Command {
|
||||
chatMessageListCmd := &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "拉取会话消息内容",
|
||||
Long: `拉取指定群聊或单聊的会话消息内容。输出顶层 messages,稳定字段为 messageId 和 text;兼容保留 openMessageId 和 content。--group 指定群聊,--user 指定单聊用户(userId),--open-dingtalk-id 指定单聊用户(openDingTalkId),三者互斥。推荐使用 --direction newer/older 控制时间方向:newer 表示从给定时间往现在拉,older 表示从给定时间往以前拉。hasMore=true 时用结果中的边界 createTime 作为下次 --time 翻页。引用回复消息会返回 quotedMessage 引用上下文;被引用的原消息是合并转发或图片时,对应的类型与内容也会随引用上下文返回。如果返回的会话消息中包含 openConvThreadId 字段,说明是话题消息,可以调用 dws chat message list-topic-replies 拉取话题回复消息列表,openConvThreadId 作为 topic-id 参数。`,
|
||||
Example: ` dws chat message list --group <openconversation_id> --time "2025-03-01 00:00:00"
|
||||
Long: `拉取指定群聊或单聊的会话消息内容。输出顶层 messages,稳定字段为 messageId 和 text;兼容保留 openMessageId 和 content。--group 指定群聊,--user 指定单聊用户(userId),--open-dingtalk-id 指定单聊用户(openDingTalkId),三者互斥。--time 可选,不传时默认上海时间当前时间并向旧消息拉取。推荐使用 --direction newer/older 控制时间方向:newer 表示从给定时间往现在拉,older 表示从给定时间往以前拉。hasMore=true 时用结果中的边界 createTime 作为下次 --time 翻页。引用回复消息会返回 quotedMessage 引用上下文;被引用的原消息是合并转发或图片时,对应的类型与内容也会随引用上下文返回。如果返回的会话消息中包含 openConvThreadId 字段,说明是话题消息,可以调用 dws chat message list-topic-replies 拉取话题回复消息列表,openConvThreadId 作为 topic-id 参数。`,
|
||||
Example: ` dws chat message list --group <openconversation_id>
|
||||
dws chat message list --group <openconversation_id> --time "2025-03-01 00:00:00"
|
||||
dws chat message list --user <userId> --time "2025-03-01 00:00:00" --limit 50
|
||||
dws chat message list --open-dingtalk-id <openDingTalkId> --time "2025-03-01 00:00:00" --limit 50
|
||||
dws chat message list --group <openconversation_id> --time "2025-03-01 00:00:00" --direction older
|
||||
@@ -2615,9 +2673,6 @@ func newChatCommand() *cobra.Command {
|
||||
# 查询群 ID: dws chat search --query "群名"
|
||||
# 查询 userId: dws contact user search --query "姓名"`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if err := validateRequiredFlags(cmd, "time"); err != nil {
|
||||
return err
|
||||
}
|
||||
groupID := flagOrFallback(cmd, "group", "conversation-id", "id", "chat")
|
||||
userID, _ := cmd.Flags().GetString("user")
|
||||
openDingTalkID, _ := cmd.Flags().GetString("open-dingtalk-id")
|
||||
@@ -2641,11 +2696,16 @@ func newChatCommand() *cobra.Command {
|
||||
openDingTalkID = userID
|
||||
userID = ""
|
||||
}
|
||||
forward, err := resolveMessageForward(cmd, true)
|
||||
timeVal := mustGetFlag(cmd, "time")
|
||||
defaultForward := true
|
||||
if timeVal == "" {
|
||||
timeVal = defaultChatMessageListTime()
|
||||
defaultForward = false
|
||||
}
|
||||
forward, err := resolveMessageForward(cmd, defaultForward)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
timeVal := mustGetFlag(cmd, "time")
|
||||
if groupID != "" {
|
||||
toolArgs := map[string]any{
|
||||
"openconversation_id": groupID,
|
||||
@@ -2696,7 +2756,7 @@ func newChatCommand() *cobra.Command {
|
||||
UseWhen: []string{"用户明确指定某个会话,并要读取消息或追溯引用回复中的原消息上下文时"},
|
||||
AvoidWhen: []string{"跨全部会话按时间查询时使用 chat message list-all"},
|
||||
Examples: []string{
|
||||
"dws chat message list --group <openConversationId> --time \"2026-07-01 00:00:00\" --limit 50",
|
||||
"dws chat message list --group <openConversationId> --limit 50",
|
||||
"dws chat message list --group <openConversationId> --time \"2026-07-01 00:00:00\" --limit 50 --jq '.messages[] | {messageId, text}'",
|
||||
},
|
||||
},
|
||||
@@ -2731,7 +2791,7 @@ func newChatCommand() *cobra.Command {
|
||||
timeVal, _ := cmd.Flags().GetString("time")
|
||||
defaultForward := true
|
||||
if strings.TrimSpace(timeVal) == "" {
|
||||
timeVal = time.Now().Format("2006-01-02 15:04:05")
|
||||
timeVal = defaultChatMessageListTime()
|
||||
defaultForward = false
|
||||
}
|
||||
forward, err := resolveMessageForward(cmd, defaultForward)
|
||||
@@ -3489,7 +3549,7 @@ func newChatCommand() *cobra.Command {
|
||||
chatMessageListAllCmd := &cobra.Command{
|
||||
Use: "list-all",
|
||||
Short: "拉取指定时间范围内当前用户的所有会话消息",
|
||||
Long: `分页拉取当前登录用户在指定时间范围内的所有会话消息。--start 和 --end 限定时间范围,--limit 指定每页数量,--cursor 传分页游标(首页传 0)。服务端按 cursor 分页返回,hasMore=true 时用返回的 nextCursor 值继续翻页。默认只读取单页;只有显式传 --page-all 才会自动翻页并保留、合并 result.conversationMessagesList,同一会话跨页合并 messages。只传 --page-limit、--max-items 或 --page-delay 仍保持单页调用。自动翻页时 --page-limit 控制最多请求页数,--max-items 按消息数精确截断,--page-delay 控制页间等待毫秒数。如果当前账号没有消息搜索权益,CLI 会保留服务端返回的友好提示与开通入口;不要把权限错误解释为时间范围内没有消息。`,
|
||||
Long: `分页拉取当前登录用户在指定时间范围内的所有会话消息。--start 和 --end 可选,不传时默认最近 1 天到当前时间,避免跨全部会话范围过大;--limit 指定每页数量,--cursor 传分页游标(首页传 0)。服务端按 cursor 分页返回,hasMore=true 时用返回的 nextCursor 值继续翻页。默认只读取单页;只有显式传 --page-all 才会自动翻页并保留、合并 result.conversationMessagesList,同一会话跨页合并 messages。只传 --page-limit、--max-items 或 --page-delay 仍保持单页调用。自动翻页时 --page-limit 控制最多请求页数,--max-items 按消息数精确截断,--page-delay 控制页间等待毫秒数。如果当前账号没有消息搜索权益,CLI 会保留服务端返回的友好提示与开通入口;不要把权限错误解释为时间范围内没有消息。`,
|
||||
Example: ` dws chat message list-all --start "2025-03-01 00:00:00" --end "2025-03-31 23:59:59" --limit 50
|
||||
dws chat message list-all --start "2025-03-01 00:00:00" --end "2025-03-31 23:59:59" --limit 50 --cursor "abc123token"
|
||||
dws chat message list-all --start "2025-03-01 00:00:00" --end "2025-03-31 23:59:59" --limit 100 --page-all --page-limit 20 --max-items 500 --page-delay 0`,
|
||||
@@ -3536,7 +3596,7 @@ func newChatCommand() *cobra.Command {
|
||||
chatMessageListBySenderCmd := &cobra.Command{
|
||||
Use: "list-by-sender",
|
||||
Short: "拉取指定发送者的消息(包含单聊和群聊)",
|
||||
Long: `搜索特定人发送给我的消息,返回结果包含单聊和群聊标识。--sender-user-id 指定发送者 userId,--sender-open-dingtalk-id 指定发送者 openDingTalkId,二者互斥。分页参数 --limit(默认 50)和 --cursor(默认 "0")始终传递;hasMore=true 时用返回的 nextCursor 作为下次 --cursor 继续翻页。默认只读取单页;只有显式传 --page-all 才会自动翻页并保留、合并 result.conversationMessagesList,同一会话跨页合并 messages。只传 --page-limit、--max-items 或 --page-delay 仍保持单页调用。自动翻页时 --page-limit 控制最多请求页数,--max-items 按消息数精确截断,--page-delay 控制页间等待毫秒数。`,
|
||||
Long: `搜索特定人发送给我的消息,返回结果包含单聊和群聊标识。--sender-user-id 指定发送者 userId,--sender-open-dingtalk-id 指定发送者 openDingTalkId,二者互斥。--start 和 --end 可选,不传时默认最近 7 天到当前时间。分页参数 --limit(默认 50)和 --cursor(默认 "0")始终传递;hasMore=true 时用返回的 nextCursor 作为下次 --cursor 继续翻页。默认只读取单页;只有显式传 --page-all 才会自动翻页并保留、合并 result.conversationMessagesList,同一会话跨页合并 messages。只传 --page-limit、--max-items 或 --page-delay 仍保持单页调用。自动翻页时 --page-limit 控制最多请求页数,--max-items 按消息数精确截断,--page-delay 控制页间等待毫秒数。`,
|
||||
Example: ` dws chat message list-by-sender --sender-user-id <userId> --start "2026-03-10T00:00:00+08:00" --end "2026-03-11T00:00:00+08:00" --limit 50 --cursor 0
|
||||
dws chat message list-by-sender --sender-open-dingtalk-id <openDingTalkId> --start "2026-03-10T00:00:00+08:00" --end "2026-03-11T00:00:00+08:00" --limit 50 --cursor 0
|
||||
dws chat message list-by-sender --sender-user-id <userId> --start "2026-03-10T00:00:00+08:00" --end "2026-03-10T23:59:59+08:00" --limit 20 --cursor 0
|
||||
@@ -3588,7 +3648,7 @@ func newChatCommand() *cobra.Command {
|
||||
chatMessageListMentionsCmd := &cobra.Command{
|
||||
Use: "list-mentions",
|
||||
Short: "拉取 @我 的消息",
|
||||
Long: `搜索时间范围内 @我 的消息,可选指定群聊。返回结果包含单聊和群聊标识。分页参数 --limit(默认 50)和 --cursor(默认 "0")始终传递;hasMore=true 时用返回的 nextCursor 作为下次 --cursor 继续翻页。默认只读取单页;只有显式传 --page-all 才会自动翻页并保留、合并 result.conversationMessagesList,同一会话跨页合并 messages。只传 --page-limit、--max-items 或 --page-delay 仍保持单页调用。自动翻页时 --page-limit 控制最多请求页数,--max-items 按消息数精确截断,--page-delay 控制页间等待毫秒数。`,
|
||||
Long: `搜索时间范围内 @我 的消息,可选指定群聊。--start 和 --end 可选,不传时默认最近 7 天到当前时间。返回结果包含单聊和群聊标识。分页参数 --limit(默认 50)和 --cursor(默认 "0")始终传递;hasMore=true 时用返回的 nextCursor 作为下次 --cursor 继续翻页。默认只读取单页;只有显式传 --page-all 才会自动翻页并保留、合并 result.conversationMessagesList,同一会话跨页合并 messages。只传 --page-limit、--max-items 或 --page-delay 仍保持单页调用。自动翻页时 --page-limit 控制最多请求页数,--max-items 按消息数精确截断,--page-delay 控制页间等待毫秒数。`,
|
||||
Example: ` dws chat message list-mentions --start "2026-03-10T00:00:00+08:00" --end "2026-03-11T00:00:00+08:00" --limit 50 --cursor 0
|
||||
dws chat message list-mentions --start "2026-04-01T00:00:00+08:00" --end "2026-04-14T00:00:00+08:00" --limit 20 --cursor 0
|
||||
dws chat message list-mentions --group <openconversation_id> --start "2026-03-10T00:00:00+08:00" --end "2026-03-11T00:00:00+08:00" --limit 50 --cursor 0
|
||||
@@ -3776,7 +3836,7 @@ func newChatCommand() *cobra.Command {
|
||||
chatMessageSearchCmd := &cobra.Command{
|
||||
Use: "search",
|
||||
Short: "按关键词搜索消息",
|
||||
Long: `在当前用户的会话中按关键词搜索消息。输出顶层 messages,稳定字段为 messageId 和 text;兼容保留 openMessageId、content 和原始 result。--query 指定搜索关键词(必填)。可选 --group 限定搜索某个会话,不传则搜索所有会话。显式指定会话时,CLI 会先验证 CID,再扫描全局搜索流并在本地精确过滤,避免下层忽略非法 CID 或群聊 CID;默认最多扫描 40 页并返回至 --limit 条范围内消息。时间参数 --start/--end(ISO-8601)限定搜索时间范围。分页参数 --limit(默认 100)和 --cursor(默认 "0")始终传递;hasMore=true 时用返回的 nextCursor 作为下次 --cursor 继续翻页。未指定会话时默认只读取单页;只有显式传 --page-all 才会自动翻页并保留、合并 result.conversationMessagesList,同一会话跨页合并 messages。只传 --page-limit、--max-items 或 --page-delay 仍保持默认行为。自动翻页时 --page-limit 控制最多请求页数,--max-items 按消息数精确截断,--page-delay 控制页间等待毫秒数。`,
|
||||
Long: `在当前用户的会话中按关键词搜索消息。输出顶层 messages,稳定字段为 messageId 和 text;兼容保留 openMessageId、content 和原始 result。--query 指定搜索关键词(必填)。可选 --group 限定搜索某个会话,不传则搜索所有会话。显式指定会话时,CLI 会先验证 CID,再扫描全局搜索流并在本地精确过滤,避免下层忽略非法 CID 或群聊 CID;默认最多扫描 40 页并返回至 --limit 条范围内消息。时间参数 --start/--end(ISO-8601)可选,不传时默认最近 7 天到当前时间。分页参数 --limit(默认 100)和 --cursor(默认 "0")始终传递;hasMore=true 时用返回的 nextCursor 作为下次 --cursor 继续翻页。未指定会话时默认只读取单页;只有显式传 --page-all 才会自动翻页并保留、合并 result.conversationMessagesList,同一会话跨页合并 messages。只传 --page-limit、--max-items 或 --page-delay 仍保持默认行为。自动翻页时 --page-limit 控制最多请求页数,--max-items 按消息数精确截断,--page-delay 控制页间等待毫秒数。`,
|
||||
Example: ` dws chat message search --query "changefree" --start "2026-04-01T00:00:00+08:00" --end "2026-04-15T00:00:00+08:00" --limit 50 --cursor 0
|
||||
dws chat message search --query "codereview" --group <openconversation_id> --start "2026-04-01T00:00:00+08:00" --end "2026-04-15T00:00:00+08:00" --limit 100 --cursor 0
|
||||
dws chat message search --query "链接" --start "2026-04-15T00:00:00+08:00" --end "2026-04-16T00:00:00+08:00" --limit 100 --cursor <nextCursor>
|
||||
@@ -4334,9 +4394,9 @@ chat message edit 或 chat message recall 的 --msg-id 和 --conversation-id。
|
||||
chatMessageListCmd.Flags().String("group", "", "群聊 openconversation_id(群聊时必填)")
|
||||
chatMessageListCmd.Flags().String("user", "", "单聊用户 userId(单聊时与 --open-dingtalk-id 二选一)")
|
||||
chatMessageListCmd.Flags().String("open-dingtalk-id", "", "单聊用户 openDingTalkId(单聊时与 --user 二选一,适用于无法获取 userId 的场景)")
|
||||
chatMessageListCmd.Flags().String("time", "", "开始时间,格式: yyyy-MM-dd HH:mm:ss (必填)")
|
||||
chatMessageListCmd.Flags().String("direction", "", "时间方向: newer=从给定时间往现在拉,older=从给定时间往以前拉(推荐)")
|
||||
chatMessageListCmd.Flags().String("forward", "true", "true 等价 --direction newer,false 等价 --direction older")
|
||||
chatMessageListCmd.Flags().String("time", "", "开始时间,格式: yyyy-MM-dd HH:mm:ss(可选,默认当前时间)")
|
||||
chatMessageListCmd.Flags().String("direction", "", "时间方向: newer=从给定时间往现在拉,older=从给定时间往以前拉(未传 --time 时默认 older)")
|
||||
chatMessageListCmd.Flags().String("forward", "true", "true 等价 --direction newer,false 等价 --direction older(未传 --time 时默认 false)")
|
||||
_ = chatMessageListCmd.Flags().MarkHidden("forward")
|
||||
chatMessageListCmd.Flags().Int("limit", 0, "返回数量,不传则不限制")
|
||||
chatMessageListCmd.Flags().Int("size", 0, "--limit 的旧版别名")
|
||||
@@ -4454,10 +4514,8 @@ chat message edit 或 chat message recall 的 --msg-id 和 --conversation-id。
|
||||
chatMessageListTopicRepliesCmd.Flags().String("forward", "false", "true 等价 --direction newer,false 等价 --direction older(默认 false)")
|
||||
_ = chatMessageListTopicRepliesCmd.Flags().MarkHidden("forward")
|
||||
|
||||
chatMessageListAllCmd.Flags().String("start", "", "起始时间,格式: yyyy-MM-dd HH:mm:ss (必填)")
|
||||
_ = chatMessageListAllCmd.MarkFlagRequired("start")
|
||||
chatMessageListAllCmd.Flags().String("end", "", "结束时间,格式: yyyy-MM-dd HH:mm:ss (必填)")
|
||||
_ = chatMessageListAllCmd.MarkFlagRequired("end")
|
||||
chatMessageListAllCmd.Flags().String("start", "", "起始时间,格式: yyyy-MM-dd HH:mm:ss(可选,默认当前时间前 1 天)")
|
||||
chatMessageListAllCmd.Flags().String("end", "", "结束时间,格式: yyyy-MM-dd HH:mm:ss(可选,默认当前时间)")
|
||||
chatMessageListAllCmd.Flags().Int("limit", 50, "每页返回数量(默认 50)")
|
||||
chatMessageListAllCmd.Flags().Int("size", 0, "--limit 的旧版别名")
|
||||
_ = chatMessageListAllCmd.Flags().MarkHidden("size")
|
||||
@@ -4471,9 +4529,8 @@ chat message edit 或 chat message recall 的 --msg-id 和 --conversation-id。
|
||||
chatMessageListBySenderCmd.Flags().String("sender-open-dingtalk-id", "", "发送者 openDingTalkId(与 --sender-user-id 二选一,适用于无法获取 userId 的场景)")
|
||||
chatMessageListBySenderCmd.Flags().String("user", "", "")
|
||||
_ = chatMessageListBySenderCmd.Flags().MarkHidden("user")
|
||||
chatMessageListBySenderCmd.Flags().String("start", "", "开始时间,ISO-8601 格式 (必填)")
|
||||
_ = chatMessageListBySenderCmd.MarkFlagRequired("start")
|
||||
chatMessageListBySenderCmd.Flags().String("end", "", "结束时间,ISO-8601 格式 (必填)")
|
||||
chatMessageListBySenderCmd.Flags().String("start", "", "开始时间,ISO-8601 格式(可选,默认当前时间前 7 天)")
|
||||
chatMessageListBySenderCmd.Flags().String("end", "", "结束时间,ISO-8601 格式(可选,默认当前时间)")
|
||||
chatMessageListBySenderCmd.Flags().Int("limit", 50, "每页返回数量(默认 50)")
|
||||
chatMessageListBySenderCmd.Flags().Int("size", 0, "--limit 的旧版别名")
|
||||
_ = chatMessageListBySenderCmd.Flags().MarkHidden("size")
|
||||
@@ -4482,10 +4539,8 @@ chat message edit 或 chat message recall 的 --msg-id 和 --conversation-id。
|
||||
|
||||
// list-mentions flags
|
||||
chatMessageListMentionsCmd.Flags().String("group", "", "群聊 openconversation_id(可选,不传则查全部)")
|
||||
chatMessageListMentionsCmd.Flags().String("start", "", "开始时间,ISO-8601 格式 (必填)")
|
||||
_ = chatMessageListMentionsCmd.MarkFlagRequired("start")
|
||||
chatMessageListMentionsCmd.Flags().String("end", "", "结束时间,ISO-8601 格式 (必填)")
|
||||
_ = chatMessageListMentionsCmd.MarkFlagRequired("end")
|
||||
chatMessageListMentionsCmd.Flags().String("start", "", "开始时间,ISO-8601 格式(可选,默认当前时间前 7 天)")
|
||||
chatMessageListMentionsCmd.Flags().String("end", "", "结束时间,ISO-8601 格式(可选,默认当前时间)")
|
||||
chatMessageListMentionsCmd.Flags().Int("limit", 50, "每页返回数量(默认 50)")
|
||||
chatMessageListMentionsCmd.Flags().Int("size", 0, "--limit 的旧版别名")
|
||||
_ = chatMessageListMentionsCmd.Flags().MarkHidden("size")
|
||||
@@ -4510,10 +4565,8 @@ chat message edit 或 chat message recall 的 --msg-id 和 --conversation-id。
|
||||
chatMessageSearchCmd.Flags().String("keyword", "", "--query 的别名")
|
||||
_ = chatMessageSearchCmd.Flags().MarkHidden("keyword")
|
||||
chatMessageSearchCmd.Flags().String("group", "", "群聊 openconversation_id(可选,不传则搜索所有会话)")
|
||||
chatMessageSearchCmd.Flags().String("start", "", "开始时间,ISO-8601 格式 (必填)")
|
||||
_ = chatMessageSearchCmd.MarkFlagRequired("start")
|
||||
chatMessageSearchCmd.Flags().String("end", "", "结束时间,ISO-8601 格式 (必填)")
|
||||
_ = chatMessageSearchCmd.MarkFlagRequired("end")
|
||||
chatMessageSearchCmd.Flags().String("start", "", "开始时间,ISO-8601 格式(可选,默认当前时间前 7 天)")
|
||||
chatMessageSearchCmd.Flags().String("end", "", "结束时间,ISO-8601 格式(可选,默认当前时间)")
|
||||
chatMessageSearchCmd.Flags().Int("limit", 100, "每页返回数量(默认 100)")
|
||||
chatMessageSearchCmd.Flags().Int("size", 0, "--limit 的旧版别名")
|
||||
_ = chatMessageSearchCmd.Flags().MarkHidden("size")
|
||||
@@ -5923,6 +5976,9 @@ flow-status 取值:1=处理中(PROCESSING),2=输入中(INPUTTING),3=完成
|
||||
Short: "下载消息中的资源(图片/视频/语音等)到本地",
|
||||
Long: `下载聊天消息中的图片、视频、语音等资源到本地文件。
|
||||
|
||||
本命令只支持聊天消息 mediaId 下载,不支持钉盘 fileId。
|
||||
如需用 fileId 下载,请使用钉盘/drive 下载命令。
|
||||
|
||||
流程:
|
||||
1. 根据 resourceType + resource-id + message-id + open-conversation-id 向服务端获取下载 URL
|
||||
2. HTTP GET 下载文件到本地
|
||||
@@ -5933,6 +5989,7 @@ flow-status 取值:1=处理中(PROCESSING),2=输入中(INPUTTING),3=完成
|
||||
dws chat message download-media --type mediaId --resource-id <mediaId> --message-id <openMessageId> --open-conversation-id <openConversationId> --output ./downloads/
|
||||
dws chat message download-media --type mediaId --resource-id <mediaId> --message-id <openMessageId> --open-conversation-id <openConversationId> --output ./photo.jpg
|
||||
# resource-id: 从 dws chat message list 返回的消息内容中获取 mediaId
|
||||
# 不支持钉盘 fileId;fileId 下载请使用钉盘/drive 下载命令
|
||||
# message-id: 从 dws chat message list 返回的 openMessageId
|
||||
# open-conversation-id: 从 dws chat search 获取 openConversationId`,
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
@@ -6062,16 +6119,16 @@ flow-status 取值:1=处理中(PROCESSING),2=输入中(INPUTTING),3=完成
|
||||
Selection: contract.SelectionSpec{
|
||||
AgentSummary: "下载消息中的媒体资源到本地",
|
||||
UseWhen: []string{"已知消息、会话和资源 ID,需要保存媒体文件时"},
|
||||
AvoidWhen: []string{"只查看文本消息内容时使用对应消息查询命令"},
|
||||
AvoidWhen: []string{"只查看文本消息内容时使用对应消息查询命令", "资源是钉盘 fileId 时使用钉盘/drive 下载命令"},
|
||||
Examples: []string{"dws chat message download-media --type mediaId --resource-id <mediaId> --message-id <openMessageId> --open-conversation-id <openConversationId> --output ."},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// download-media flags
|
||||
chatMessageDownloadMediaCmd.Flags().String("type", "", "资源类型: mediaId (必填)")
|
||||
chatMessageDownloadMediaCmd.Flags().String("type", "", "资源类型: mediaId(必填;仅支持聊天消息 mediaId,不支持钉盘 fileId)")
|
||||
_ = chatMessageDownloadMediaCmd.MarkFlagRequired("type")
|
||||
chatMessageDownloadMediaCmd.Flags().String("resource-id", "", "资源 ID,mediaId 类型时为消息中的 mediaId 值 (必填)")
|
||||
chatMessageDownloadMediaCmd.Flags().String("resource-id", "", "资源 ID,mediaId 类型时为消息中的 mediaId 值(必填;不是钉盘 fileId)")
|
||||
_ = chatMessageDownloadMediaCmd.MarkFlagRequired("resource-id")
|
||||
chatMessageDownloadMediaCmd.Flags().String("open-conversation-id", "", "会话 openConversationId (必填)")
|
||||
_ = chatMessageDownloadMediaCmd.MarkFlagRequired("open-conversation-id")
|
||||
|
||||
@@ -160,6 +160,9 @@ func TestCrossPlatformCoverageChatCommandValidationAndSuccessEdges(t *testing.T)
|
||||
os.Args = []string{"dws", "chat"}
|
||||
t.Cleanup(func() { deps, os.Args = previousDeps, previousArgs })
|
||||
caller := &productExampleCaller{}
|
||||
if got := formatChatMessageListAllTime(0); got == "" {
|
||||
t.Fatal("formatChatMessageListAllTime returned empty string")
|
||||
}
|
||||
|
||||
commands := [][]string{
|
||||
{"chmod", "chat.read", "--ttl="},
|
||||
@@ -168,9 +171,17 @@ func TestCrossPlatformCoverageChatCommandValidationAndSuccessEdges(t *testing.T)
|
||||
{"message", "list", "--user=D-user", "--time=2026-01-01", "--limit=1"},
|
||||
{"message", "list", "--open-dingtalk-id=D-user", "--time=2026-01-01", "--direction=sideways"},
|
||||
{"message", "list-direct", "--user=D-user", "--limit=1"},
|
||||
{"message", "list-all"},
|
||||
{"message", "list-all", "--end=2026-01-02T00:00:00Z"},
|
||||
{"message", "list-all", "--end=bad"},
|
||||
{"message", "list-all", "--start=not-a-time", "--end=2026-01-02T00:00:00Z"},
|
||||
{"message", "list-all", "--start=2026-01-01 00:00:00", "--end=bad"},
|
||||
{"message", "list-all", "--start=2026-01-02 00:00:00", "--end=2026-01-01 00:00:00"},
|
||||
{"message", "list-all", "--start=2027-01-01 00:00:00"},
|
||||
{"message", "list-by-sender", "--sender-user-id=u1", "--start=bad"},
|
||||
{"message", "list-by-sender", "--sender-user-id=u1", "--start=2026-01-01T00:00:00Z", "--end=bad"},
|
||||
{"message", "list-by-sender", "--sender-user-id=u1", "--start=2026-01-02T00:00:00Z", "--end=2026-01-01T00:00:00Z"},
|
||||
{"message", "list-by-sender", "--sender-user-id=u1", "--end=2026-01-02T00:00:00Z"},
|
||||
{"message", "list-by-sender", "--sender-user-id=u1", "--start=2026-01-01T00:00:00Z", "--end=2026-01-02T00:00:00Z"},
|
||||
{"message", "list-by-sender", "--sender-open-dingtalk-id=D1", "--start=2026-01-01T00:00:00Z"},
|
||||
{"message", "list-mentions", "--start=bad", "--end=2026-01-02T00:00:00Z"},
|
||||
|
||||
@@ -90,3 +90,96 @@ func TestCrossPlatformCoverageChatMessageHelpDocumentsPostSendIDChain(t *testing
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCrossPlatformCoverageChatMessageHelpDocumentsOptionalTimeDefaults(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
contains []string
|
||||
absent []string
|
||||
}{
|
||||
{
|
||||
name: "list",
|
||||
args: []string{"message", "list", "--help"},
|
||||
contains: []string{
|
||||
"--time 可选,不传时默认上海时间当前时间并向旧消息拉取",
|
||||
"默认上海时间当前时间",
|
||||
"未传 --time 时默认 older",
|
||||
},
|
||||
absent: []string{"开始时间,格式: yyyy-MM-dd HH:mm:ss (必填)"},
|
||||
},
|
||||
{
|
||||
name: "search",
|
||||
args: []string{"message", "search", "--help"},
|
||||
contains: []string{
|
||||
"可选,不传时默认最近 7 天到当前时间",
|
||||
"默认当前时间前 7 天",
|
||||
"默认当前时间",
|
||||
},
|
||||
absent: []string{"开始时间,ISO-8601 格式 (必填)", "结束时间,ISO-8601 格式 (必填)"},
|
||||
},
|
||||
{
|
||||
name: "list-by-sender",
|
||||
args: []string{"message", "list-by-sender", "--help"},
|
||||
contains: []string{
|
||||
"--start 和 --end 可选,不传时默认最近 7 天到当前时间",
|
||||
"默认当前时间前 7 天",
|
||||
"默认当前时间",
|
||||
},
|
||||
absent: []string{"开始时间,ISO-8601 格式 (必填)", "结束时间,ISO-8601 格式 (必填)"},
|
||||
},
|
||||
{
|
||||
name: "list-mentions",
|
||||
args: []string{"message", "list-mentions", "--help"},
|
||||
contains: []string{
|
||||
"--start 和 --end 可选,不传时默认最近 7 天到当前时间",
|
||||
"默认当前时间前 7 天",
|
||||
"默认当前时间",
|
||||
},
|
||||
absent: []string{"开始时间,ISO-8601 格式 (必填)", "结束时间,ISO-8601 格式 (必填)"},
|
||||
},
|
||||
{
|
||||
name: "list-all",
|
||||
args: []string{"message", "list-all", "--help"},
|
||||
contains: []string{
|
||||
"--start 和 --end 可选,不传时默认最近 1 天到当前时间",
|
||||
"默认当前时间前 1 天",
|
||||
"默认当前时间",
|
||||
},
|
||||
absent: []string{"起始时间,格式: yyyy-MM-dd HH:mm:ss (必填)", "结束时间,格式: yyyy-MM-dd HH:mm:ss (必填)"},
|
||||
},
|
||||
{
|
||||
name: "download-media",
|
||||
args: []string{"message", "download-media", "--help"},
|
||||
contains: []string{
|
||||
"只支持聊天消息 mediaId 下载,不支持钉盘 fileId",
|
||||
"fileId 下载请使用钉盘/drive 下载命令",
|
||||
"仅支持聊天消息 mediaId,不支持钉盘 fileId",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
cmd := newChatCommand()
|
||||
var output bytes.Buffer
|
||||
cmd.SetOut(&output)
|
||||
cmd.SetErr(&output)
|
||||
cmd.SetArgs(test.args)
|
||||
if err := cmd.Execute(); err != nil {
|
||||
t.Fatalf("dws chat %s: %v\n%s", strings.Join(test.args, " "), err, output.String())
|
||||
}
|
||||
help := output.String()
|
||||
for _, want := range test.contains {
|
||||
if !strings.Contains(help, want) {
|
||||
t.Errorf("chat message %s help missing %q:\n%s", test.name, want, help)
|
||||
}
|
||||
}
|
||||
for _, unwanted := range test.absent {
|
||||
if strings.Contains(help, unwanted) {
|
||||
t.Errorf("chat message %s help still contains %q:\n%s", test.name, unwanted, help)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -148,6 +149,356 @@ func TestChatMessagePaginationDefaultSinglePageUnchanged(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestChatMessagePaginationUsesDefaultTimeWindows(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
wantTool string
|
||||
wantLookback time.Duration
|
||||
}{
|
||||
{
|
||||
name: "list-all defaults to one day",
|
||||
args: []string{"message", "list-all"},
|
||||
wantTool: "search_messages_by_time_range",
|
||||
wantLookback: 24 * time.Hour,
|
||||
},
|
||||
{
|
||||
name: "list-by-sender defaults to seven days",
|
||||
args: []string{"message", "list-by-sender", "--sender-user-id", "u1"},
|
||||
wantTool: "search_messages_by_sender",
|
||||
wantLookback: 7 * 24 * time.Hour,
|
||||
},
|
||||
{
|
||||
name: "list-mentions defaults to seven days",
|
||||
args: []string{"message", "list-mentions"},
|
||||
wantTool: "search_at_me_message",
|
||||
wantLookback: 7 * 24 * time.Hour,
|
||||
},
|
||||
{
|
||||
name: "search defaults to seven days",
|
||||
args: []string{"message", "search", "--query", "发布"},
|
||||
wantTool: "search_messages_by_keyword",
|
||||
wantLookback: 7 * 24 * time.Hour,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
caller := &chatMessagePaginationCaller{}
|
||||
before := time.Now()
|
||||
_, err := executeChatMessagePaginationCommand(t, caller, tt.args...)
|
||||
after := time.Now()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(caller.calls) != 1 {
|
||||
t.Fatalf("calls = %#v, want one call", caller.calls)
|
||||
}
|
||||
got := caller.calls[0]
|
||||
if got.tool != tt.wantTool {
|
||||
t.Fatalf("tool = %q, want %q", got.tool, tt.wantTool)
|
||||
}
|
||||
startMs := chatMessageTimeArgAsMillis(t, got.args["startTime"], tt.wantTool)
|
||||
endMs := chatMessageTimeArgAsMillis(t, got.args["endTime"], tt.wantTool)
|
||||
wantEndMin := before.Truncate(time.Second).UnixMilli()
|
||||
wantEndMax := after.Add(time.Second).UnixMilli()
|
||||
if endMs < wantEndMin || endMs > wantEndMax {
|
||||
t.Fatalf("endTime = %d, want between %d and %d", endMs, wantEndMin, wantEndMax)
|
||||
}
|
||||
wantStartMin := before.Add(-tt.wantLookback).Truncate(time.Second).UnixMilli()
|
||||
wantStartMax := after.Add(-tt.wantLookback).Add(time.Second).UnixMilli()
|
||||
if startMs < wantStartMin || startMs > wantStartMax {
|
||||
t.Fatalf("startTime = %d, want between %d and %d", startMs, wantStartMin, wantStartMax)
|
||||
}
|
||||
if tt.wantTool == "search_messages_by_time_range" {
|
||||
assertChatMessageListAllTimeFormat(t, got.args["startTime"])
|
||||
assertChatMessageListAllTimeFormat(t, got.args["endTime"])
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestChatMessagePaginationDefaultsStartFromExplicitEnd(t *testing.T) {
|
||||
endRaw := "2026-01-01T00:00:00+08:00"
|
||||
endMs, err := parseISOTimeToMillis("end", endRaw)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
wantTool string
|
||||
wantLookback time.Duration
|
||||
}{
|
||||
{
|
||||
name: "list-all defaults start one day before explicit end",
|
||||
args: []string{"message", "list-all", "--end", endRaw},
|
||||
wantTool: "search_messages_by_time_range",
|
||||
wantLookback: 24 * time.Hour,
|
||||
},
|
||||
{
|
||||
name: "list-by-sender defaults start seven days before explicit end",
|
||||
args: []string{"message", "list-by-sender", "--sender-user-id", "u1", "--end", endRaw},
|
||||
wantTool: "search_messages_by_sender",
|
||||
wantLookback: 7 * 24 * time.Hour,
|
||||
},
|
||||
{
|
||||
name: "list-mentions defaults start seven days before explicit end",
|
||||
args: []string{"message", "list-mentions", "--end", endRaw},
|
||||
wantTool: "search_at_me_message",
|
||||
wantLookback: 7 * 24 * time.Hour,
|
||||
},
|
||||
{
|
||||
name: "search defaults start seven days before explicit end",
|
||||
args: []string{"message", "search", "--query", "发布", "--end", endRaw},
|
||||
wantTool: "search_messages_by_keyword",
|
||||
wantLookback: 7 * 24 * time.Hour,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
caller := &chatMessagePaginationCaller{}
|
||||
_, err := executeChatMessagePaginationCommand(t, caller, tt.args...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(caller.calls) != 1 {
|
||||
t.Fatalf("calls = %#v, want one call", caller.calls)
|
||||
}
|
||||
got := caller.calls[0]
|
||||
if got.tool != tt.wantTool {
|
||||
t.Fatalf("tool = %q, want %q", got.tool, tt.wantTool)
|
||||
}
|
||||
startMs := chatMessageTimeArgAsMillis(t, got.args["startTime"], tt.wantTool)
|
||||
gotEndMs := chatMessageTimeArgAsMillis(t, got.args["endTime"], tt.wantTool)
|
||||
if gotEndMs != endMs {
|
||||
t.Fatalf("endTime = %d, want %d", gotEndMs, endMs)
|
||||
}
|
||||
wantStartMs := time.UnixMilli(endMs).Add(-tt.wantLookback).UnixMilli()
|
||||
if startMs != wantStartMs {
|
||||
t.Fatalf("startTime = %d, want %d", startMs, wantStartMs)
|
||||
}
|
||||
if tt.wantTool == "search_messages_by_time_range" {
|
||||
assertStringArg(t, got.args["startTime"], formatChatMessageListAllTime(wantStartMs))
|
||||
assertStringArg(t, got.args["endTime"], endRaw)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestChatMessageListAllDefaultStartFromTimezoneLessEndUsesShanghai(t *testing.T) {
|
||||
previousLocal := time.Local
|
||||
t.Cleanup(func() { time.Local = previousLocal })
|
||||
|
||||
for _, loc := range []*time.Location{time.UTC, time.FixedZone("EST", -5*3600)} {
|
||||
t.Run(loc.String(), func(t *testing.T) {
|
||||
time.Local = loc
|
||||
caller := &chatMessagePaginationCaller{}
|
||||
_, err := executeChatMessagePaginationCommand(t, caller, "message", "list-all", "--end", "2026-03-01 00:00:00")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(caller.calls) != 1 {
|
||||
t.Fatalf("calls = %#v, want one call", caller.calls)
|
||||
}
|
||||
got := caller.calls[0]
|
||||
assertStringArg(t, got.args["endTime"], "2026-03-01 00:00:00")
|
||||
assertStringArg(t, got.args["startTime"], "2026-02-28 00:00:00")
|
||||
|
||||
startMs, err := parseISOTimeToMillis("start", got.args["startTime"].(string))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
endMs, err := parseISOTimeToMillis("end", got.args["endTime"].(string))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if gotWindow := time.Duration(endMs-startMs) * time.Millisecond; gotWindow != 24*time.Hour {
|
||||
t.Fatalf("window = %v, want 24h", gotWindow)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestChatMessagePaginationDefaultsEndFromNowWhenOnlyStartProvided(t *testing.T) {
|
||||
startRaw := "2026-01-01T00:00:00+08:00"
|
||||
startMs, err := parseISOTimeToMillis("start", startRaw)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
wantTool string
|
||||
}{
|
||||
{
|
||||
name: "list-all defaults end to now",
|
||||
args: []string{"message", "list-all", "--start", startRaw},
|
||||
wantTool: "search_messages_by_time_range",
|
||||
},
|
||||
{
|
||||
name: "list-by-sender defaults end to now",
|
||||
args: []string{"message", "list-by-sender", "--sender-user-id", "u1", "--start", startRaw},
|
||||
wantTool: "search_messages_by_sender",
|
||||
},
|
||||
{
|
||||
name: "list-mentions defaults end to now",
|
||||
args: []string{"message", "list-mentions", "--start", startRaw},
|
||||
wantTool: "search_at_me_message",
|
||||
},
|
||||
{
|
||||
name: "search defaults end to now",
|
||||
args: []string{"message", "search", "--query", "发布", "--start", startRaw},
|
||||
wantTool: "search_messages_by_keyword",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
caller := &chatMessagePaginationCaller{}
|
||||
before := time.Now()
|
||||
_, err := executeChatMessagePaginationCommand(t, caller, tt.args...)
|
||||
after := time.Now()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(caller.calls) != 1 {
|
||||
t.Fatalf("calls = %#v, want one call", caller.calls)
|
||||
}
|
||||
got := caller.calls[0]
|
||||
if got.tool != tt.wantTool {
|
||||
t.Fatalf("tool = %q, want %q", got.tool, tt.wantTool)
|
||||
}
|
||||
gotStartMs := chatMessageTimeArgAsMillis(t, got.args["startTime"], tt.wantTool)
|
||||
if gotStartMs != startMs {
|
||||
t.Fatalf("startTime = %d, want %d", gotStartMs, startMs)
|
||||
}
|
||||
endMs := chatMessageTimeArgAsMillis(t, got.args["endTime"], tt.wantTool)
|
||||
wantEndMin := before.Truncate(time.Second).UnixMilli()
|
||||
wantEndMax := after.Add(time.Second).UnixMilli()
|
||||
if endMs < wantEndMin || endMs > wantEndMax {
|
||||
t.Fatalf("endTime = %d, want between %d and %d", endMs, wantEndMin, wantEndMax)
|
||||
}
|
||||
if tt.wantTool == "search_messages_by_time_range" {
|
||||
assertStringArg(t, got.args["startTime"], startRaw)
|
||||
assertChatMessageListAllTimeFormat(t, got.args["endTime"])
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestChatMessageListAllRejectsInvalidTimeBounds(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "invalid start",
|
||||
args: []string{"message", "list-all", "--start", "not-a-time", "--end", "2020-01-01T00:00:00+08:00"},
|
||||
wantErr: "cannot parse time for --start",
|
||||
},
|
||||
{
|
||||
name: "end before start",
|
||||
args: []string{"message", "list-all", "--start", "2021-01-01T00:00:00+08:00", "--end", "2020-01-01T00:00:00+08:00"},
|
||||
wantErr: "--end must be after --start",
|
||||
},
|
||||
{
|
||||
name: "default end before future start",
|
||||
args: []string{"message", "list-all", "--start", "2027-01-01 00:00:00"},
|
||||
wantErr: "--end must be after --start",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
caller := &chatMessagePaginationCaller{}
|
||||
_, err := executeChatMessagePaginationCommand(t, caller, tt.args...)
|
||||
if err == nil {
|
||||
t.Fatal("expected validation error, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), tt.wantErr) {
|
||||
t.Fatalf("error = %q, want contains %q", err.Error(), tt.wantErr)
|
||||
}
|
||||
if len(caller.calls) != 0 {
|
||||
t.Fatalf("calls = %#v, want no MCP call", caller.calls)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func numericArgAsInt64(t *testing.T, value any) int64 {
|
||||
t.Helper()
|
||||
switch v := value.(type) {
|
||||
case int64:
|
||||
return v
|
||||
case int:
|
||||
return int64(v)
|
||||
case float64:
|
||||
return int64(v)
|
||||
case string:
|
||||
parsed, err := parseISOTimeToMillis("time", v)
|
||||
if err != nil {
|
||||
t.Fatalf("parse time arg %q: %v", v, err)
|
||||
}
|
||||
return parsed
|
||||
default:
|
||||
t.Fatalf("unsupported numeric arg type %T (%#v)", value, value)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func chatMessageTimeArgAsMillis(t *testing.T, value any, tool string) int64 {
|
||||
t.Helper()
|
||||
if tool == "search_messages_by_time_range" {
|
||||
return parseChatMessageListAllTimeArg(t, value).UnixMilli()
|
||||
}
|
||||
return numericArgAsInt64(t, value)
|
||||
}
|
||||
|
||||
func parseChatMessageListAllTimeArg(t *testing.T, value any) time.Time {
|
||||
t.Helper()
|
||||
raw, ok := value.(string)
|
||||
if !ok {
|
||||
t.Fatalf("time arg = %#v, want time string", value)
|
||||
}
|
||||
if strings.Contains(raw, "T") {
|
||||
parsedMs, err := parseISOTimeToMillis("time", raw)
|
||||
if err != nil {
|
||||
t.Fatalf("time arg = %q, parse err = %v", raw, err)
|
||||
}
|
||||
return time.UnixMilli(parsedMs)
|
||||
}
|
||||
parsedMs, err := parseISOTimeToMillis("time", raw)
|
||||
if err != nil {
|
||||
t.Fatalf("time arg = %q, parse err = %v", raw, err)
|
||||
}
|
||||
return time.UnixMilli(parsedMs)
|
||||
}
|
||||
|
||||
func assertChatMessageListAllTimeFormat(t *testing.T, value any) {
|
||||
t.Helper()
|
||||
raw, ok := value.(string)
|
||||
if !ok {
|
||||
t.Fatalf("time arg = %#v, want time string", value)
|
||||
}
|
||||
if strings.Contains(raw, "T") {
|
||||
t.Fatalf("time arg = %q, want yyyy-MM-dd HH:mm:ss without RFC3339 separator", raw)
|
||||
}
|
||||
if _, err := parseISOTimeToMillis("time", raw); err != nil {
|
||||
t.Fatalf("time arg = %q, parse err = %v", raw, err)
|
||||
}
|
||||
}
|
||||
|
||||
func assertStringArg(t *testing.T, value any, want string) {
|
||||
t.Helper()
|
||||
got, ok := value.(string)
|
||||
if !ok || got != want {
|
||||
t.Fatalf("arg = %#v, want %q", value, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChatMessagePaginationPageAllAggregatesSevenCommands(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
@@ -724,6 +724,99 @@ func TestCrossPlatformCoverageChatMessageListUsesMCPMetadataGroupKey(t *testing.
|
||||
}
|
||||
}
|
||||
|
||||
func TestCrossPlatformCoverageChatMessageListDefaultTimeUsesShanghaiLocation(t *testing.T) {
|
||||
previousLocal := time.Local
|
||||
t.Cleanup(func() { time.Local = previousLocal })
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
wantTool string
|
||||
wantTarget map[string]any
|
||||
}{
|
||||
{
|
||||
name: "group",
|
||||
args: []string{"message", "list", "--group", "cid-1", "--limit", "50"},
|
||||
wantTool: "list_conversation_message_v2",
|
||||
wantTarget: map[string]any{"openconversation_id": "cid-1"},
|
||||
},
|
||||
{
|
||||
name: "user",
|
||||
args: []string{"message", "list", "--user", "user-1", "--limit", "50"},
|
||||
wantTool: "list_individual_chat_message",
|
||||
wantTarget: map[string]any{"userId": "user-1"},
|
||||
},
|
||||
{
|
||||
name: "user open DingTalk ID fallback",
|
||||
args: []string{"message", "list", "--user", "D-user", "--limit", "50"},
|
||||
wantTool: "list_individual_chat_message",
|
||||
wantTarget: map[string]any{"openDingTalkId": "D-user"},
|
||||
},
|
||||
{
|
||||
name: "open DingTalk ID",
|
||||
args: []string{"message", "list", "--open-dingtalk-id", "D-open", "--limit", "50"},
|
||||
wantTool: "list_individual_chat_message",
|
||||
wantTarget: map[string]any{"openDingTalkId": "D-open"},
|
||||
},
|
||||
{
|
||||
name: "direct user",
|
||||
args: []string{"message", "list-direct", "--user", "user-1", "--limit", "50"},
|
||||
wantTool: "list_individual_chat_message",
|
||||
wantTarget: map[string]any{"userId": "user-1"},
|
||||
},
|
||||
{
|
||||
name: "direct open DingTalk ID",
|
||||
args: []string{"message", "list-direct", "--open-dingtalk-id", "D-direct", "--limit", "50"},
|
||||
wantTool: "list_individual_chat_message",
|
||||
wantTarget: map[string]any{"openDingTalkId": "D-direct"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, loc := range []*time.Location{time.UTC, time.FixedZone("EST", -5*3600)} {
|
||||
t.Run(loc.String(), func(t *testing.T) {
|
||||
time.Local = loc
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
caller := &chatChangedContractCaller{}
|
||||
before := time.Now()
|
||||
err := executeChatChangedContract(t, caller, tt.args...)
|
||||
after := time.Now()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(caller.calls) != 1 {
|
||||
t.Fatalf("calls = %#v, want one MCP call", caller.calls)
|
||||
}
|
||||
if caller.calls[0].toolName != tt.wantTool {
|
||||
t.Fatalf("tool = %q, want %q", caller.calls[0].toolName, tt.wantTool)
|
||||
}
|
||||
for key, want := range tt.wantTarget {
|
||||
if got := caller.calls[0].args[key]; got != want {
|
||||
t.Fatalf("arg %s = %#v, want %#v", key, got, want)
|
||||
}
|
||||
}
|
||||
raw, ok := caller.calls[0].args["time"].(string)
|
||||
if !ok || raw == "" {
|
||||
t.Fatalf("time arg = %#v, want non-empty string", caller.calls[0].args["time"])
|
||||
}
|
||||
gotMs, err := parseISOTimeToMillis("time", raw)
|
||||
if err != nil {
|
||||
t.Fatalf("time arg = %q, parse err = %v", raw, err)
|
||||
}
|
||||
wantMin := before.Add(-time.Second).UnixMilli()
|
||||
wantMax := after.Add(time.Second).UnixMilli()
|
||||
if gotMs < wantMin || gotMs > wantMax {
|
||||
t.Fatalf("time arg = %q (%d), want between %d and %d", raw, gotMs, wantMin, wantMax)
|
||||
}
|
||||
if caller.calls[0].args["forward"] != false {
|
||||
t.Fatalf("forward = %#v, want false when --time is omitted", caller.calls[0].args["forward"])
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCrossPlatformCoverageChatAuditUsesUserIDs(t *testing.T) {
|
||||
caller := &chatChangedContractCaller{}
|
||||
err := executeChatChangedContract(t, caller,
|
||||
|
||||
@@ -21,7 +21,9 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
@@ -39,11 +41,16 @@ type imReadResultCaller struct {
|
||||
results map[string]*edition.ToolResult
|
||||
errors map[string]error
|
||||
calls []imReadResultCall
|
||||
args map[string]any
|
||||
dryRun bool
|
||||
}
|
||||
|
||||
func (c *imReadResultCaller) CallTool(_ context.Context, productID, toolName string, _ map[string]any) (*edition.ToolResult, error) {
|
||||
func (c *imReadResultCaller) CallTool(_ context.Context, productID, toolName string, args map[string]any) (*edition.ToolResult, error) {
|
||||
c.calls = append(c.calls, imReadResultCall{productID: productID, toolName: toolName})
|
||||
c.args = map[string]any{}
|
||||
for k, v := range args {
|
||||
c.args[k] = v
|
||||
}
|
||||
if err := c.errors[toolName]; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -203,6 +210,37 @@ func TestCrossPlatformCoverageChatMessageListProjectsStableFieldsAndPreservesLeg
|
||||
}
|
||||
}
|
||||
|
||||
func TestCrossPlatformCoverageChatMessageListDefaultsTimeAndOlderDirection(t *testing.T) {
|
||||
payload := `{"result":{"messages":[],"hasMore":false}}`
|
||||
caller := &imReadResultCaller{responses: map[string]string{"list_conversation_message_v2": payload}}
|
||||
loc := shanghaiLocation()
|
||||
|
||||
before := time.Now().In(loc)
|
||||
_, err := executeIMReadCommand(t, caller, []string{"dws", "chat"}, newChatCommand,
|
||||
"message", "list", "--group", "cid-1", "--limit", "50")
|
||||
after := time.Now().In(loc)
|
||||
if err != nil {
|
||||
t.Fatalf("chat message list returned error: %v", err)
|
||||
}
|
||||
if len(caller.calls) != 1 || caller.calls[0] != (imReadResultCall{productID: "chat", toolName: "list_conversation_message_v2"}) {
|
||||
t.Fatalf("calls = %#v, want chat/list_conversation_message_v2", caller.calls)
|
||||
}
|
||||
if got := caller.args["forward"]; got != false {
|
||||
t.Fatalf("forward = %#v, want false when --time is omitted", got)
|
||||
}
|
||||
timeRaw, ok := caller.args["time"].(string)
|
||||
if !ok || strings.TrimSpace(timeRaw) == "" {
|
||||
t.Fatalf("time arg = %#v, want generated current time", caller.args["time"])
|
||||
}
|
||||
gotTime, err := time.ParseInLocation("2006-01-02 15:04:05", timeRaw, loc)
|
||||
if err != nil {
|
||||
t.Fatalf("parse generated time %q: %v", timeRaw, err)
|
||||
}
|
||||
if gotTime.Before(before.Add(-time.Second)) || gotTime.After(after.Add(time.Second)) {
|
||||
t.Fatalf("time = %s, want between %s and %s", gotTime, before, after)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCrossPlatformCoverageChatMessageSearchProjectsStableFieldsAndPreservesLegacy(t *testing.T) {
|
||||
payload := `{
|
||||
"result": {
|
||||
|
||||
+4
-4
@@ -2692,7 +2692,7 @@
|
||||
[chat.message.list-all]
|
||||
runnable: true
|
||||
hidden: false
|
||||
flags: --client-id:string|required=false|hidden=false|no-opt=""|scope=inherited, --client-secret:string|required=false|hidden=false|no-opt=""|scope=inherited, --cursor:string|required=false|hidden=false|no-opt=""|scope=local, --debug:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --dry-run:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --end:string|required=true|hidden=false|no-opt=""|scope=local, --fields:string|required=false|hidden=false|no-opt=""|scope=inherited, -f/--format:string|required=false|hidden=false|no-opt=""|scope=inherited, -h/--help:bool|required=false|hidden=false|no-opt="true"|scope=local, --jq:string|required=false|hidden=false|no-opt=""|scope=inherited, --limit:int|required=false|hidden=false|no-opt=""|scope=local, --mock:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -o/--output:string|required=false|hidden=true|no-opt=""|scope=inherited, --profile:string|required=false|hidden=false|no-opt=""|scope=inherited, --size:int|required=false|hidden=true|no-opt=""|scope=local, --start:string|required=true|hidden=false|no-opt=""|scope=local, --timeout:int|required=false|hidden=false|no-opt=""|scope=inherited, --token:string|required=false|hidden=true|no-opt=""|scope=inherited, -v/--verbose:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -y/--yes:bool|required=false|hidden=false|no-opt="true"|scope=inherited
|
||||
flags: --client-id:string|required=false|hidden=false|no-opt=""|scope=inherited, --client-secret:string|required=false|hidden=false|no-opt=""|scope=inherited, --cursor:string|required=false|hidden=false|no-opt=""|scope=local, --debug:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --dry-run:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --end:string|required=false|hidden=false|no-opt=""|scope=local, --fields:string|required=false|hidden=false|no-opt=""|scope=inherited, -f/--format:string|required=false|hidden=false|no-opt=""|scope=inherited, -h/--help:bool|required=false|hidden=false|no-opt="true"|scope=local, --jq:string|required=false|hidden=false|no-opt=""|scope=inherited, --limit:int|required=false|hidden=false|no-opt=""|scope=local, --mock:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -o/--output:string|required=false|hidden=true|no-opt=""|scope=inherited, --profile:string|required=false|hidden=false|no-opt=""|scope=inherited, --size:int|required=false|hidden=true|no-opt=""|scope=local, --start:string|required=false|hidden=false|no-opt=""|scope=local, --timeout:int|required=false|hidden=false|no-opt=""|scope=inherited, --token:string|required=false|hidden=true|no-opt=""|scope=inherited, -v/--verbose:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -y/--yes:bool|required=false|hidden=false|no-opt="true"|scope=inherited
|
||||
|
||||
[chat.message.list-by-ids]
|
||||
runnable: true
|
||||
@@ -2702,7 +2702,7 @@
|
||||
[chat.message.list-by-sender]
|
||||
runnable: true
|
||||
hidden: false
|
||||
flags: --client-id:string|required=false|hidden=false|no-opt=""|scope=inherited, --client-secret:string|required=false|hidden=false|no-opt=""|scope=inherited, --cursor:string|required=false|hidden=false|no-opt=""|scope=local, --debug:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --dry-run:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --end:string|required=false|hidden=false|no-opt=""|scope=local, --fields:string|required=false|hidden=false|no-opt=""|scope=inherited, -f/--format:string|required=false|hidden=false|no-opt=""|scope=inherited, -h/--help:bool|required=false|hidden=false|no-opt="true"|scope=local, --jq:string|required=false|hidden=false|no-opt=""|scope=inherited, --limit:int|required=false|hidden=false|no-opt=""|scope=local, --mock:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -o/--output:string|required=false|hidden=true|no-opt=""|scope=inherited, --profile:string|required=false|hidden=false|no-opt=""|scope=inherited, --sender:string|required=false|hidden=true|no-opt=""|scope=local, --sender-open-dingtalk-id:string|required=false|hidden=false|no-opt=""|scope=local, --sender-user-id:string|required=false|hidden=false|no-opt=""|scope=local, --size:int|required=false|hidden=true|no-opt=""|scope=local, --start:string|required=true|hidden=false|no-opt=""|scope=local, --timeout:int|required=false|hidden=false|no-opt=""|scope=inherited, --token:string|required=false|hidden=true|no-opt=""|scope=inherited, --user:string|required=false|hidden=true|no-opt=""|scope=local, -v/--verbose:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -y/--yes:bool|required=false|hidden=false|no-opt="true"|scope=inherited
|
||||
flags: --client-id:string|required=false|hidden=false|no-opt=""|scope=inherited, --client-secret:string|required=false|hidden=false|no-opt=""|scope=inherited, --cursor:string|required=false|hidden=false|no-opt=""|scope=local, --debug:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --dry-run:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --end:string|required=false|hidden=false|no-opt=""|scope=local, --fields:string|required=false|hidden=false|no-opt=""|scope=inherited, -f/--format:string|required=false|hidden=false|no-opt=""|scope=inherited, -h/--help:bool|required=false|hidden=false|no-opt="true"|scope=local, --jq:string|required=false|hidden=false|no-opt=""|scope=inherited, --limit:int|required=false|hidden=false|no-opt=""|scope=local, --mock:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -o/--output:string|required=false|hidden=true|no-opt=""|scope=inherited, --profile:string|required=false|hidden=false|no-opt=""|scope=inherited, --sender:string|required=false|hidden=true|no-opt=""|scope=local, --sender-open-dingtalk-id:string|required=false|hidden=false|no-opt=""|scope=local, --sender-user-id:string|required=false|hidden=false|no-opt=""|scope=local, --size:int|required=false|hidden=true|no-opt=""|scope=local, --start:string|required=false|hidden=false|no-opt=""|scope=local, --timeout:int|required=false|hidden=false|no-opt=""|scope=inherited, --token:string|required=false|hidden=true|no-opt=""|scope=inherited, --user:string|required=false|hidden=true|no-opt=""|scope=local, -v/--verbose:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -y/--yes:bool|required=false|hidden=false|no-opt="true"|scope=inherited
|
||||
|
||||
[chat.message.list-emotion-replies]
|
||||
runnable: true
|
||||
@@ -2722,7 +2722,7 @@
|
||||
[chat.message.list-mentions]
|
||||
runnable: true
|
||||
hidden: false
|
||||
flags: --chat:string|required=false|hidden=true|no-opt=""|scope=local, --client-id:string|required=false|hidden=false|no-opt=""|scope=inherited, --client-secret:string|required=false|hidden=false|no-opt=""|scope=inherited, --conversation-id:string|required=false|hidden=true|no-opt=""|scope=local, --cursor:string|required=false|hidden=false|no-opt=""|scope=local, --debug:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --dry-run:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --end:string|required=true|hidden=false|no-opt=""|scope=local, --fields:string|required=false|hidden=false|no-opt=""|scope=inherited, -f/--format:string|required=false|hidden=false|no-opt=""|scope=inherited, --group:string|required=false|hidden=false|no-opt=""|scope=local, -h/--help:bool|required=false|hidden=false|no-opt="true"|scope=local, --id:string|required=false|hidden=true|no-opt=""|scope=local, --jq:string|required=false|hidden=false|no-opt=""|scope=inherited, --limit:int|required=false|hidden=false|no-opt=""|scope=local, --mock:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -o/--output:string|required=false|hidden=true|no-opt=""|scope=inherited, --profile:string|required=false|hidden=false|no-opt=""|scope=inherited, --size:int|required=false|hidden=true|no-opt=""|scope=local, --start:string|required=true|hidden=false|no-opt=""|scope=local, --timeout:int|required=false|hidden=false|no-opt=""|scope=inherited, --token:string|required=false|hidden=true|no-opt=""|scope=inherited, -v/--verbose:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -y/--yes:bool|required=false|hidden=false|no-opt="true"|scope=inherited
|
||||
flags: --chat:string|required=false|hidden=true|no-opt=""|scope=local, --client-id:string|required=false|hidden=false|no-opt=""|scope=inherited, --client-secret:string|required=false|hidden=false|no-opt=""|scope=inherited, --conversation-id:string|required=false|hidden=true|no-opt=""|scope=local, --cursor:string|required=false|hidden=false|no-opt=""|scope=local, --debug:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --dry-run:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --end:string|required=false|hidden=false|no-opt=""|scope=local, --fields:string|required=false|hidden=false|no-opt=""|scope=inherited, -f/--format:string|required=false|hidden=false|no-opt=""|scope=inherited, --group:string|required=false|hidden=false|no-opt=""|scope=local, -h/--help:bool|required=false|hidden=false|no-opt="true"|scope=local, --id:string|required=false|hidden=true|no-opt=""|scope=local, --jq:string|required=false|hidden=false|no-opt=""|scope=inherited, --limit:int|required=false|hidden=false|no-opt=""|scope=local, --mock:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -o/--output:string|required=false|hidden=true|no-opt=""|scope=inherited, --profile:string|required=false|hidden=false|no-opt=""|scope=inherited, --size:int|required=false|hidden=true|no-opt=""|scope=local, --start:string|required=false|hidden=false|no-opt=""|scope=local, --timeout:int|required=false|hidden=false|no-opt=""|scope=inherited, --token:string|required=false|hidden=true|no-opt=""|scope=inherited, -v/--verbose:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -y/--yes:bool|required=false|hidden=false|no-opt="true"|scope=inherited
|
||||
|
||||
[chat.message.list-pin-msg]
|
||||
runnable: true
|
||||
@@ -2782,7 +2782,7 @@
|
||||
[chat.message.search]
|
||||
runnable: true
|
||||
hidden: false
|
||||
flags: --chat:string|required=false|hidden=true|no-opt=""|scope=local, --client-id:string|required=false|hidden=false|no-opt=""|scope=inherited, --client-secret:string|required=false|hidden=false|no-opt=""|scope=inherited, --conversation-id:string|required=false|hidden=true|no-opt=""|scope=local, --cursor:string|required=false|hidden=false|no-opt=""|scope=local, --debug:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --dry-run:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --end:string|required=true|hidden=false|no-opt=""|scope=local, --fields:string|required=false|hidden=false|no-opt=""|scope=inherited, -f/--format:string|required=false|hidden=false|no-opt=""|scope=inherited, --group:string|required=false|hidden=false|no-opt=""|scope=local, -h/--help:bool|required=false|hidden=false|no-opt="true"|scope=local, --id:string|required=false|hidden=true|no-opt=""|scope=local, --jq:string|required=false|hidden=false|no-opt=""|scope=inherited, --keyword:string|required=false|hidden=true|no-opt=""|scope=local, --limit:int|required=false|hidden=false|no-opt=""|scope=local, --mock:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -o/--output:string|required=false|hidden=true|no-opt=""|scope=inherited, --profile:string|required=false|hidden=false|no-opt=""|scope=inherited, --query:string|required=false|hidden=false|no-opt=""|scope=local, --size:int|required=false|hidden=true|no-opt=""|scope=local, --start:string|required=true|hidden=false|no-opt=""|scope=local, --timeout:int|required=false|hidden=false|no-opt=""|scope=inherited, --token:string|required=false|hidden=true|no-opt=""|scope=inherited, -v/--verbose:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -y/--yes:bool|required=false|hidden=false|no-opt="true"|scope=inherited
|
||||
flags: --chat:string|required=false|hidden=true|no-opt=""|scope=local, --client-id:string|required=false|hidden=false|no-opt=""|scope=inherited, --client-secret:string|required=false|hidden=false|no-opt=""|scope=inherited, --conversation-id:string|required=false|hidden=true|no-opt=""|scope=local, --cursor:string|required=false|hidden=false|no-opt=""|scope=local, --debug:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --dry-run:bool|required=false|hidden=false|no-opt="true"|scope=inherited, --end:string|required=false|hidden=false|no-opt=""|scope=local, --fields:string|required=false|hidden=false|no-opt=""|scope=inherited, -f/--format:string|required=false|hidden=false|no-opt=""|scope=inherited, --group:string|required=false|hidden=false|no-opt=""|scope=local, -h/--help:bool|required=false|hidden=false|no-opt="true"|scope=local, --id:string|required=false|hidden=true|no-opt=""|scope=local, --jq:string|required=false|hidden=false|no-opt=""|scope=inherited, --keyword:string|required=false|hidden=true|no-opt=""|scope=local, --limit:int|required=false|hidden=false|no-opt=""|scope=local, --mock:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -o/--output:string|required=false|hidden=true|no-opt=""|scope=inherited, --profile:string|required=false|hidden=false|no-opt=""|scope=inherited, --query:string|required=false|hidden=false|no-opt=""|scope=local, --size:int|required=false|hidden=true|no-opt=""|scope=local, --start:string|required=false|hidden=false|no-opt=""|scope=local, --timeout:int|required=false|hidden=false|no-opt=""|scope=inherited, --token:string|required=false|hidden=true|no-opt=""|scope=inherited, -v/--verbose:bool|required=false|hidden=false|no-opt="true"|scope=inherited, -y/--yes:bool|required=false|hidden=false|no-opt="true"|scope=inherited
|
||||
|
||||
[chat.message.search-advanced]
|
||||
runnable: true
|
||||
|
||||
Reference in New Issue
Block a user