feat(gmail): add single-message modify command (#281) (thanks @zerone0x)

This commit is contained in:
Peter Steinberger 2026-03-08 01:07:05 +00:00
parent 643a255679
commit d448264a27
6 changed files with 24 additions and 17 deletions

View File

@ -11,6 +11,7 @@
- Sheets: add `sheets create --parent` to place new spreadsheets in a Drive folder. (#424) — thanks @ManManavadaria.
- Calendar: add `calendar subscribe` (aliases `sub`, `add-calendar`) to add a shared calendar to the current accounts calendar list. (#327) — thanks @cdthompson.
- Gmail: add `watch serve --history-types` filtering (`messageAdded|messageDeleted|labelAdded|labelRemoved`) and include `deletedMessageIds` in webhook payloads. (#168) — thanks @salmonumbrella.
- Gmail: add `gmail messages modify` for single-message label changes, complementing thread- and batch-level modify flows. (#281) — thanks @zerone0x.
- Contacts: support `--org`, `--title`, `--url`, `--note`, and `--custom` on create/update; include custom fields in get output with deterministic ordering. (#199) — thanks @phuctm97.
- Drive: add `drive ls --all` (alias `--global`) to list across all accessible files; make `--all` and `--parent` mutually exclusive. (#107) — thanks @struong.
- Docs: update install docs to use the official Homebrew core formula (`brew install gogcli`). (#361) — thanks @zeldrisho.

View File

@ -109,14 +109,11 @@ func (c *GmailBatchModifyCmd) Run(ctx context.Context, flags *RootFlags) error {
return err
}
idMap, err := fetchLabelNameToID(svc)
addIDs, removeIDs, err := resolveModifyLabelIDs(svc, addLabels, removeLabels)
if err != nil {
return err
}
addIDs := resolveLabelIDs(addLabels, idMap)
removeIDs := resolveLabelIDs(removeLabels, idMap)
err = svc.Users.Messages.BatchModify("me", &gmail.BatchModifyMessagesRequest{
Ids: ids,
AddLabelIds: addIDs,

View File

@ -30,6 +30,15 @@ func resolveLabelIDs(labels []string, nameToID map[string]string) []string {
return out
}
func resolveModifyLabelIDs(svc *gmail.Service, addLabels, removeLabels []string) ([]string, []string, error) {
idMap, err := fetchLabelNameToID(svc)
if err != nil {
return nil, nil, err
}
return resolveLabelIDs(addLabels, idMap), resolveLabelIDs(removeLabels, idMap), nil
}
func ensureLabelNameAvailable(svc *gmail.Service, name string) error {
idMap, err := fetchLabelNameToID(svc)
if err != nil {

View File

@ -186,14 +186,11 @@ func (c *GmailMessagesModifyCmd) Run(ctx context.Context, flags *RootFlags) erro
return err
}
idMap, err := fetchLabelNameToID(svc)
addIDs, removeIDs, err := resolveModifyLabelIDs(svc, addLabels, removeLabels)
if err != nil {
return err
}
addIDs := resolveLabelIDs(addLabels, idMap)
removeIDs := resolveLabelIDs(removeLabels, idMap)
_, err = svc.Users.Messages.Modify("me", messageID, &gmail.ModifyMessageRequest{
AddLabelIds: addIDs,
RemoveLabelIds: removeIDs,

View File

@ -39,7 +39,7 @@ func TestGmailMessagesModifyCmd_JSON(t *testing.T) {
RemoveLabelIds []string `json:"removeLabelIds"`
}
_ = json.NewDecoder(r.Body).Decode(&body)
if len(body.AddLabelIds) != 1 || body.AddLabelIds[0] != "TRASH" {
if len(body.AddLabelIds) != 1 || body.AddLabelIds[0] != "Label_1" {
http.Error(w, "bad addLabelIds", http.StatusBadRequest)
return
}
@ -79,7 +79,7 @@ func TestGmailMessagesModifyCmd_JSON(t *testing.T) {
if err := runKong(t, &GmailMessagesModifyCmd{}, []string{
"msg1",
"--add", "TRASH",
"--add", "Custom",
"--remove", "INBOX",
}, ctx, flags); err != nil {
t.Fatalf("execute: %v", err)
@ -97,7 +97,7 @@ func TestGmailMessagesModifyCmd_JSON(t *testing.T) {
if parsed.Modified != "msg1" {
t.Fatalf("unexpected modified: %q", parsed.Modified)
}
if len(parsed.AddedLabels) != 1 || parsed.AddedLabels[0] != "TRASH" {
if len(parsed.AddedLabels) != 1 || parsed.AddedLabels[0] != "Label_1" {
t.Fatalf("unexpected added labels: %#v", parsed.AddedLabels)
}
if len(parsed.RemovedLabels) != 1 || parsed.RemovedLabels[0] != "INBOX" {
@ -113,7 +113,7 @@ func TestGmailMessagesModifyCmd_JSON(t *testing.T) {
if err := runKong(t, &GmailMessagesModifyCmd{}, []string{
"msg1",
"--add", "TRASH",
"--add", "Custom",
"--remove", "INBOX",
}, ctx, flags); err != nil {
t.Fatalf("execute plain: %v", err)
@ -148,4 +148,11 @@ func TestGmailMessagesModifyCmd_ValidationErrors(t *testing.T) {
t.Fatalf("expected validation error, got %v", err)
}
})
t.Run("empty message id", func(t *testing.T) {
err := runKong(t, &GmailMessagesModifyCmd{}, []string{"", "--add", "INBOX"}, ctx, flags)
if err == nil || !strings.Contains(err.Error(), "empty messageId") {
t.Fatalf("expected empty messageId error, got %v", err)
}
})
}

View File

@ -212,15 +212,11 @@ func (c *GmailThreadModifyCmd) Run(ctx context.Context, flags *RootFlags) error
return err
}
// Resolve label names to IDs
idMap, err := fetchLabelNameToID(svc)
addIDs, removeIDs, err := resolveModifyLabelIDs(svc, addLabels, removeLabels)
if err != nil {
return err
}
addIDs := resolveLabelIDs(addLabels, idMap)
removeIDs := resolveLabelIDs(removeLabels, idMap)
// Use Gmail's Threads.Modify API
_, err = svc.Users.Threads.Modify("me", threadID, &gmail.ModifyThreadRequest{
AddLabelIds: addIDs,