diff --git a/shortcuts/im/builders_test.go b/shortcuts/im/builders_test.go index c32bd6a329..20963b86ed 100644 --- a/shortcuts/im/builders_test.go +++ b/shortcuts/im/builders_test.go @@ -317,6 +317,17 @@ func TestShortcutValidateBranches(t *testing.T) { } }) + t.Run("ImChatSearch invalid chat-modes value", func(t *testing.T) { + runtime := newTestRuntimeContext(t, map[string]string{ + "query": "ok", + "chat-modes": "group,bogus", + }, nil) + err := ImChatSearch.Validate(context.Background(), runtime) + if err == nil || !strings.Contains(err.Error(), "invalid --chat-modes value") { + t.Fatalf("ImChatSearch.Validate() error = %v", err) + } + }) + t.Run("ImChatUpdate requires fields", func(t *testing.T) { runtime := newTestRuntimeContext(t, map[string]string{ "chat-id": "oc_123", @@ -693,6 +704,39 @@ func TestShortcutDryRunShapes(t *testing.T) { } }) + t.Run("ImChatSearch dry run maps chat-modes to wire values", func(t *testing.T) { + runtime := newTestRuntimeContext(t, map[string]string{ + "query": "team-alpha", + "chat-modes": "group,topic", + }, nil) + got := mustMarshalDryRun(t, ImChatSearch.DryRun(context.Background(), runtime)) + if !strings.Contains(got, `"chat_modes":["default","thread"]`) { + t.Fatalf("ImChatSearch.DryRun() chat_modes mapping = %s", got) + } + }) + + t.Run("ImChatSearch dry run maps single chat-mode topic", func(t *testing.T) { + runtime := newTestRuntimeContext(t, map[string]string{ + "query": "team-alpha", + "chat-modes": "topic", + }, nil) + got := mustMarshalDryRun(t, ImChatSearch.DryRun(context.Background(), runtime)) + if !strings.Contains(got, `"chat_modes":["thread"]`) { + t.Fatalf("ImChatSearch.DryRun() chat_modes mapping = %s", got) + } + }) + + t.Run("ImChatSearch dry run dedupes chat-modes", func(t *testing.T) { + runtime := newTestRuntimeContext(t, map[string]string{ + "query": "team-alpha", + "chat-modes": "group, group", + }, nil) + got := mustMarshalDryRun(t, ImChatSearch.DryRun(context.Background(), runtime)) + if !strings.Contains(got, `"chat_modes":["default"]`) { + t.Fatalf("ImChatSearch.DryRun() chat_modes dedupe = %s", got) + } + }) + t.Run("ImMessagesSearch dry run uses messages search endpoint", func(t *testing.T) { runtime := newMessagesSearchTestRuntimeContext(t, map[string]string{ "query": "incident", diff --git a/shortcuts/im/im_chat_search.go b/shortcuts/im/im_chat_search.go index bcb56afd17..8ee8032bfb 100644 --- a/shortcuts/im/im_chat_search.go +++ b/shortcuts/im/im_chat_search.go @@ -10,6 +10,7 @@ import ( "strconv" "strings" + "github.com/larksuite/cli/errs" "github.com/larksuite/cli/internal/output" "github.com/larksuite/cli/internal/util" "github.com/larksuite/cli/shortcuts/common" @@ -30,6 +31,7 @@ var ImChatSearch = common.Shortcut{ Flags: []common.Flag{ {Name: "query", Desc: "search keyword (max 64 chars)"}, {Name: "search-types", Desc: "chat types, comma-separated (private, external, public_joined, public_not_joined)"}, + {Name: "chat-modes", Desc: "filter by chat mode, comma-separated (group, topic)"}, {Name: "member-ids", Desc: "filter by member open_ids, comma-separated"}, {Name: "is-manager", Type: "bool", Desc: "only show chats you created or manage"}, {Name: "disable-search-by-user", Type: "bool", Desc: "disable search-by-member-name (default: search by member name first, then group name)"}, @@ -71,6 +73,13 @@ var ImChatSearch = common.Shortcut{ } } } + if cm := runtime.Str("chat-modes"); cm != "" { + for _, mode := range common.SplitCSV(cm) { + if mode != "group" && mode != "topic" { + return errs.NewValidationError(errs.SubtypeInvalidArgument, "invalid --chat-modes value %q: expected one of group, topic", mode).WithParam("--chat-modes") + } + } + } if mi := runtime.Str("member-ids"); mi != "" { ids := common.SplitCSV(mi) if len(ids) > 50 { @@ -216,6 +225,24 @@ func buildSearchChatBody(runtime *common.RuntimeContext) map[string]interface{} if st := runtime.Str("search-types"); st != "" { filter["search_types"] = common.SplitCSV(st) } + // chat_modes is a server-side filter. The CLI exposes group/topic; the wire + // expects default/thread. Map and dedupe (the API caps the list at 2, and + // there are only 2 distinct modes) while preserving the user's order. + if cm := runtime.Str("chat-modes"); cm != "" { + seen := map[string]bool{} + var modes []string + for _, mode := range common.SplitCSV(cm) { + wire := map[string]string{"group": "default", "topic": "thread"}[mode] + if wire == "" || seen[wire] { + continue + } + seen[wire] = true + modes = append(modes, wire) + } + if len(modes) > 0 { + filter["chat_modes"] = modes + } + } if mi := runtime.Str("member-ids"); mi != "" { filter["member_ids"] = common.SplitCSV(mi) } diff --git a/skills/lark-im/references/lark-im-chat-search.md b/skills/lark-im/references/lark-im-chat-search.md index 186bb70084..d5a7041996 100644 --- a/skills/lark-im/references/lark-im-chat-search.md +++ b/skills/lark-im/references/lark-im-chat-search.md @@ -15,6 +15,9 @@ lark-cli im +chat-search --query "project" # Restrict by search types lark-cli im +chat-search --query "project" --search-types "private,public_joined" +# Filter by chat mode (group = regular group, topic = topic/thread group) +lark-cli im +chat-search --query "project" --chat-modes "topic" + # Filter by member open_ids (with keyword) lark-cli im +chat-search --query "project" --member-ids "ou_xxx,ou_yyy" @@ -43,6 +46,7 @@ lark-cli im +chat-search --query "project" --dry-run |------|------|------|------| | `--query ` | No (at least one of `--query` / `--member-ids` required) | Max 64 characters | Search keyword. Supports matching localized chat names, member names, multilingual search, pinyin, and prefix fuzzy search. If the query contains `-`, it is automatically wrapped in quotes | | `--search-types ` | No | Comma-separated: `private`, `external`, `public_joined`, `public_not_joined` | Restrict the visible chat types returned by search | +| `--chat-modes ` | No | Comma-separated: `group`, `topic` | Filter by chat mode (server-side): `group` = regular group, `topic` = topic/thread group | | `--member-ids ` | No (at least one of `--query` / `--member-ids` required) | Up to 50, format `ou_xxx` | Filter by member open_ids; can be used alone or combined with `--query` | | `--is-manager` | No | - | Only show chats you created or manage | | `--disable-search-by-user` | No | - | Disable member-name-based matching and search by group name only |