feat(gmail): add filters export command (#119) (thanks @Jeswang)
This commit is contained in:
parent
004b68cc70
commit
fbb230b60b
@ -18,6 +18,7 @@
|
||||
- Auth: add `auth add|manage --listen-addr` plus `--redirect-host` for browser OAuth behind proxies or remote loopback forwarding. (#227) — thanks @cyberfox.
|
||||
- Auth: add `--extra-scopes` to `auth add` for appending custom OAuth scope URIs beyond the built-in service scopes. (#421) — thanks @peteradams2026.
|
||||
- Gmail: add `gmail labels rename` to rename user labels by ID or exact name, with system-label guards and wrong-case ID safety. (#391) — thanks @adam-zethraeus.
|
||||
- Gmail: add `gmail filters export` to dump filter definitions as JSON to stdout or a file for backup/script workflows. (#119) — thanks @Jeswang.
|
||||
- Gmail: add `gmail messages modify` for single-message label changes, complementing thread- and batch-level modify flows. (#281) — thanks @zerone0x.
|
||||
- Calendar: add `calendar subscribe` (aliases `sub`, `add-calendar`) to add a shared calendar to the current account’s calendar list. (#327) — thanks @cdthompson.
|
||||
- Calendar: add `calendar alias list|set|unset`, and let calendar commands resolve configured aliases before API/name lookup. (#393) — thanks @salmonumbrella.
|
||||
|
||||
@ -656,6 +656,7 @@ gog gmail batch modify <messageId> <messageId> --add STARRED --remove INBOX
|
||||
gog gmail filters list
|
||||
gog gmail filters create --from 'noreply@example.com' --add-label 'Notifications'
|
||||
gog gmail filters delete <filterId>
|
||||
gog gmail filters export --out ./filters.json
|
||||
|
||||
# Settings
|
||||
gog gmail autoforward get
|
||||
|
||||
@ -19,6 +19,7 @@ type GmailFiltersCmd struct {
|
||||
Get GmailFiltersGetCmd `cmd:"" name:"get" aliases:"info,show" help:"Get a specific filter"`
|
||||
Create GmailFiltersCreateCmd `cmd:"" name:"create" aliases:"add,new" help:"Create a new email filter"`
|
||||
Delete GmailFiltersDeleteCmd `cmd:"" name:"delete" aliases:"rm,del,remove" help:"Delete a filter"`
|
||||
Export GmailFiltersExportCmd `cmd:"" name:"export" help:"Export filters as JSON"`
|
||||
}
|
||||
|
||||
type GmailFiltersListCmd struct{}
|
||||
@ -386,3 +387,52 @@ func (c *GmailFiltersDeleteCmd) Run(ctx context.Context, flags *RootFlags) error
|
||||
u.Out().Printf("Filter %s deleted successfully", filterID)
|
||||
return nil
|
||||
}
|
||||
|
||||
type GmailFiltersExportCmd struct {
|
||||
Out string `name:"out" short:"o" help:"Write JSON export to this file (defaults to stdout)"`
|
||||
}
|
||||
|
||||
func (c *GmailFiltersExportCmd) Run(ctx context.Context, flags *RootFlags) error {
|
||||
u := ui.FromContext(ctx)
|
||||
account, err := requireAccount(flags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
svc, err := newGmailService(ctx, account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := svc.Users.Settings.Filters.List("me").Do()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
payload := map[string]any{"filters": resp.Filter}
|
||||
outPath := strings.TrimSpace(c.Out)
|
||||
if outPath == "" {
|
||||
return outfmt.WriteJSON(ctx, os.Stdout, payload)
|
||||
}
|
||||
|
||||
f, err := os.Create(outPath) //nolint:gosec // explicit user-selected output path
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = f.Close() }()
|
||||
|
||||
if err := outfmt.WriteJSON(ctx, f, payload); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if outfmt.IsJSON(ctx) {
|
||||
return outfmt.WriteJSON(ctx, os.Stdout, map[string]any{
|
||||
"exported": true,
|
||||
"path": outPath,
|
||||
"count": len(resp.Filter),
|
||||
})
|
||||
}
|
||||
|
||||
u.Out().Printf("Exported %d filters to %s", len(resp.Filter), outPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@ -203,3 +204,74 @@ func TestGmailFiltersList_NoFilters(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestGmailFiltersExport(t *testing.T) {
|
||||
origNew := newGmailService
|
||||
t.Cleanup(func() { newGmailService = origNew })
|
||||
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if strings.Contains(r.URL.Path, "/gmail/v1/users/me/settings/filters") && r.Method == http.MethodGet {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||
"filter": []map[string]any{
|
||||
{"id": "f1", "criteria": map[string]any{"from": "a@example.com"}},
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
http.NotFound(w, r)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
svc, err := gmail.NewService(context.Background(),
|
||||
option.WithoutAuthentication(),
|
||||
option.WithHTTPClient(srv.Client()),
|
||||
option.WithEndpoint(srv.URL+"/"),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("NewService: %v", err)
|
||||
}
|
||||
newGmailService = func(context.Context, string) (*gmail.Service, error) { return svc, nil }
|
||||
|
||||
flags := &RootFlags{Account: "a@b.com"}
|
||||
u, uiErr := ui.New(ui.Options{Stdout: io.Discard, Stderr: io.Discard, Color: "never"})
|
||||
if uiErr != nil {
|
||||
t.Fatalf("ui.New: %v", uiErr)
|
||||
}
|
||||
ctx := ui.WithUI(context.Background(), u)
|
||||
|
||||
t.Run("stdout json", func(t *testing.T) {
|
||||
out := captureStdout(t, func() {
|
||||
if err := runKong(t, &GmailFiltersExportCmd{}, []string{}, ctx, flags); err != nil {
|
||||
t.Fatalf("export stdout: %v", err)
|
||||
}
|
||||
})
|
||||
var payload map[string]any
|
||||
if err := json.Unmarshal([]byte(out), &payload); err != nil {
|
||||
t.Fatalf("json parse: %v", err)
|
||||
}
|
||||
filters, ok := payload["filters"].([]any)
|
||||
if !ok || len(filters) != 1 {
|
||||
t.Fatalf("unexpected payload: %#v", payload)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("file export", func(t *testing.T) {
|
||||
path := t.TempDir() + "/filters.json"
|
||||
if err := runKong(t, &GmailFiltersExportCmd{}, []string{"--out", path}, ctx, flags); err != nil {
|
||||
t.Fatalf("export file: %v", err)
|
||||
}
|
||||
b, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatalf("read export: %v", err)
|
||||
}
|
||||
var payload map[string]any
|
||||
if err := json.Unmarshal(b, &payload); err != nil {
|
||||
t.Fatalf("json parse: %v", err)
|
||||
}
|
||||
filters, ok := payload["filters"].([]any)
|
||||
if !ok || len(filters) != 1 {
|
||||
t.Fatalf("unexpected payload: %#v", payload)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user