feat(gmail): export filters as WebUI XML (#553)
* feat(gmail): export filters as WebUI XML * fix(gmail): satisfy filter export lint
This commit is contained in:
parent
3ed52354a0
commit
e8e1ac4635
@ -18,6 +18,7 @@
|
||||
- Sheets: add header-safe `sheets table clear` for clearing table data rows without touching headers or footers.
|
||||
- Sheets: add `sheets conditional-format` and `sheets banding` commands for rule-based formatting and alternating color banded ranges. (#378) — thanks @codBang.
|
||||
- Agent docs: add a bundled `gog` skill for safe JSON-first Google Workspace automation from coding agents. (#353, #451) — thanks @TimPietrusky and @sluramod.
|
||||
- Gmail: export filters as Gmail WebUI-importable Atom XML, while keeping API JSON export via `--format json`. (#174) — thanks @gwpl.
|
||||
|
||||
### Fixed
|
||||
- Agent safety: compile baked safety profile policies into generated hash switches so raw allow/deny rule strings are not embedded as patchable YAML. (#540) — thanks @drewburchfield.
|
||||
|
||||
@ -805,7 +805,8 @@ 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
|
||||
gog gmail filters export --out ./mailFilters.xml # Gmail WebUI importable XML
|
||||
gog gmail filters export --format json --out ./filters.json
|
||||
|
||||
# Settings
|
||||
gog gmail autoforward get
|
||||
|
||||
@ -318,7 +318,7 @@ Generated from `gog schema --json`.
|
||||
- [`gog gmail (mail,email) settings filters <command>`](commands/gog-gmail-settings-filters.md) - Filter operations
|
||||
- [`gog gmail (mail,email) settings filters create (add,new) [flags]`](commands/gog-gmail-settings-filters-create.md) - Create a new email filter
|
||||
- [`gog gmail (mail,email) settings filters delete (rm,del,remove) <filterId>`](commands/gog-gmail-settings-filters-delete.md) - Delete a filter
|
||||
- [`gog gmail (mail,email) settings filters export [flags]`](commands/gog-gmail-settings-filters-export.md) - Export filters as JSON
|
||||
- [`gog gmail (mail,email) settings filters export [flags]`](commands/gog-gmail-settings-filters-export.md) - Export filters as Gmail WebUI-compatible XML
|
||||
- [`gog gmail (mail,email) settings filters get (info,show) <filterId>`](commands/gog-gmail-settings-filters-get.md) - Get a specific filter
|
||||
- [`gog gmail (mail,email) settings filters list (ls)`](commands/gog-gmail-settings-filters-list.md) - List all email filters
|
||||
- [`gog gmail (mail,email) settings forwarding <command>`](commands/gog-gmail-settings-forwarding.md) - Forwarding addresses
|
||||
|
||||
@ -361,7 +361,7 @@ Generated pages: 466.
|
||||
- [gog gmail settings filters](gog-gmail-settings-filters.md) - Filter operations
|
||||
- [gog gmail settings filters create](gog-gmail-settings-filters-create.md) - Create a new email filter
|
||||
- [gog gmail settings filters delete](gog-gmail-settings-filters-delete.md) - Delete a filter
|
||||
- [gog gmail settings filters export](gog-gmail-settings-filters-export.md) - Export filters as JSON
|
||||
- [gog gmail settings filters export](gog-gmail-settings-filters-export.md) - Export filters as Gmail WebUI-compatible XML
|
||||
- [gog gmail settings filters get](gog-gmail-settings-filters-get.md) - Get a specific filter
|
||||
- [gog gmail settings filters list](gog-gmail-settings-filters-list.md) - List all email filters
|
||||
- [gog gmail settings forwarding](gog-gmail-settings-forwarding.md) - Forwarding addresses
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
> Generated from `gog schema --json`. Do not edit this page by hand; run `make docs-commands`.
|
||||
|
||||
Export filters as JSON
|
||||
Export filters as Gmail WebUI-compatible XML
|
||||
|
||||
## Usage
|
||||
|
||||
@ -26,11 +26,12 @@ gog gmail (mail,email) settings filters export [flags]
|
||||
| `-n`<br>`--dry-run`<br>`--dryrun`<br>`--noop`<br>`--preview` | `bool` | | Do not make changes; print intended actions and exit successfully |
|
||||
| `--enable-commands` | `string` | | Comma-separated list of enabled commands; dot paths allowed (restricts CLI) |
|
||||
| `-y`<br>`--force`<br>`--assume-yes`<br>`--yes` | `bool` | | Skip confirmations for destructive commands |
|
||||
| `--format` | `string` | | Export format: xml or json (default: xml; --json without --out uses json for compatibility) |
|
||||
| `--gmail-no-send` | `bool` | false | Block Gmail send operations (agent safety) |
|
||||
| `-h`<br>`--help` | `kong.helpFlag` | | Show context-sensitive help. |
|
||||
| `-j`<br>`--json`<br>`--machine` | `bool` | false | Output JSON to stdout (best for scripting) |
|
||||
| `--no-input`<br>`--non-interactive`<br>`--noninteractive` | `bool` | | Never prompt; fail instead (useful for CI) |
|
||||
| `-o`<br>`--out` | `string` | | Write JSON export to this file (defaults to stdout) |
|
||||
| `-o`<br>`--out` | `string` | | Write export to this file (defaults to stdout) |
|
||||
| `-p`<br>`--plain`<br>`--tsv` | `bool` | false | Output stable, parseable text to stdout (TSV; no colors) |
|
||||
| `--results-only` | `bool` | | In JSON mode, emit only the primary result (drops envelope fields like nextPageToken) |
|
||||
| `--select`<br>`--pick`<br>`--project` | `string` | | In JSON mode, select comma-separated fields (best-effort; supports dot paths). Desire path: use --fields for most commands. |
|
||||
|
||||
@ -18,7 +18,7 @@ gog gmail (mail,email) settings filters <command>
|
||||
|
||||
- [gog gmail settings filters create](gog-gmail-settings-filters-create.md) - Create a new email filter
|
||||
- [gog gmail settings filters delete](gog-gmail-settings-filters-delete.md) - Delete a filter
|
||||
- [gog gmail settings filters export](gog-gmail-settings-filters-export.md) - Export filters as JSON
|
||||
- [gog gmail settings filters export](gog-gmail-settings-filters-export.md) - Export filters as Gmail WebUI-compatible XML
|
||||
- [gog gmail settings filters get](gog-gmail-settings-filters-get.md) - Get a specific filter
|
||||
- [gog gmail settings filters list](gog-gmail-settings-filters-list.md) - List all email filters
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@ package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
@ -15,7 +16,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"`
|
||||
Export GmailFiltersExportCmd `cmd:"" name:"export" help:"Export filters as Gmail WebUI-compatible XML"`
|
||||
}
|
||||
|
||||
type GmailFiltersListCmd struct{}
|
||||
@ -139,11 +140,12 @@ func (c *GmailFiltersDeleteCmd) Run(ctx context.Context, flags *RootFlags) error
|
||||
}
|
||||
|
||||
type GmailFiltersExportCmd struct {
|
||||
Out string `name:"out" short:"o" help:"Write JSON export to this file (defaults to stdout)"`
|
||||
Out string `name:"out" short:"o" help:"Write export to this file (defaults to stdout)"`
|
||||
Format string `name:"format" help:"Export format: xml or json (default: xml; --json without --out uses json for compatibility)"`
|
||||
}
|
||||
|
||||
func (c *GmailFiltersExportCmd) Run(ctx context.Context, flags *RootFlags) error {
|
||||
svc, err := loadGmailSettingsService(ctx, flags)
|
||||
account, svc, err := requireGmailService(ctx, flags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -153,10 +155,46 @@ func (c *GmailFiltersExportCmd) Run(ctx context.Context, flags *RootFlags) error
|
||||
return err
|
||||
}
|
||||
|
||||
payload := map[string]any{"filters": resp.Filter}
|
||||
format := strings.ToLower(strings.TrimSpace(c.Format))
|
||||
outPath := strings.TrimSpace(c.Out)
|
||||
if format == "" {
|
||||
format = "xml"
|
||||
if outPath == "" && outfmt.IsJSON(ctx) {
|
||||
format = "json"
|
||||
}
|
||||
}
|
||||
|
||||
payload := map[string]any{"filters": resp.Filter}
|
||||
var data []byte
|
||||
switch format {
|
||||
case "json":
|
||||
if outPath == "" {
|
||||
return outfmt.WriteJSON(ctx, os.Stdout, payload)
|
||||
}
|
||||
data, err = json.MarshalIndent(payload, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data = append(data, '\n')
|
||||
case "xml":
|
||||
labelNames, labelErr := fetchLabelIDToName(svc)
|
||||
if labelErr != nil {
|
||||
return labelErr
|
||||
}
|
||||
data, err = marshalGmailFiltersXML(account, resp.Filter, labelNames)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if outPath == "" {
|
||||
_, err = os.Stdout.Write(data)
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return usage("--format must be xml or json")
|
||||
}
|
||||
|
||||
if outPath == "" {
|
||||
return outfmt.WriteJSON(ctx, os.Stdout, payload)
|
||||
return nil
|
||||
}
|
||||
|
||||
f, outPath, err := createUserOutputFile(outPath)
|
||||
@ -165,7 +203,7 @@ func (c *GmailFiltersExportCmd) Run(ctx context.Context, flags *RootFlags) error
|
||||
}
|
||||
defer func() { _ = f.Close() }()
|
||||
|
||||
if err := outfmt.WriteJSON(ctx, f, payload); err != nil {
|
||||
if _, err := f.Write(data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -174,6 +212,7 @@ func (c *GmailFiltersExportCmd) Run(ctx context.Context, flags *RootFlags) error
|
||||
"exported": true,
|
||||
"path": outPath,
|
||||
"count": len(resp.Filter),
|
||||
"format": format,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@ package cmd
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@ -210,19 +211,52 @@ func TestGmailFiltersList_NoFilters(t *testing.T) {
|
||||
|
||||
func TestGmailFiltersExport(t *testing.T) {
|
||||
origNew := newGmailService
|
||||
t.Cleanup(func() { newGmailService = origNew })
|
||||
origNow := nowGmailFiltersExport
|
||||
t.Cleanup(func() {
|
||||
newGmailService = origNew
|
||||
nowGmailFiltersExport = origNow
|
||||
})
|
||||
nowGmailFiltersExport = func() time.Time { return time.Date(2026, 5, 5, 1, 2, 3, 0, time.UTC) }
|
||||
|
||||
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 {
|
||||
switch {
|
||||
case strings.Contains(r.URL.Path, "/gmail/v1/users/me/labels") && 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"}},
|
||||
"labels": []map[string]any{
|
||||
{"id": "Label_1", "name": "Notifications & Alerts"},
|
||||
},
|
||||
})
|
||||
return
|
||||
case 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",
|
||||
"to": "b@example.com",
|
||||
"subject": "A&B",
|
||||
"query": `from:alerts has:attachment`,
|
||||
"negatedQuery": "category:promotions",
|
||||
"hasAttachment": true,
|
||||
"excludeChats": true,
|
||||
"size": 1024,
|
||||
"sizeComparison": "larger",
|
||||
},
|
||||
"action": map[string]any{
|
||||
"addLabelIds": []string{"Label_1", "STARRED", "IMPORTANT", "CATEGORY_SOCIAL"},
|
||||
"removeLabelIds": []string{"INBOX", "UNREAD", "SPAM"},
|
||||
"forward": "f@example.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
return
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
http.NotFound(w, r)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
@ -243,12 +277,58 @@ func TestGmailFiltersExport(t *testing.T) {
|
||||
}
|
||||
ctx := ui.WithUI(context.Background(), u)
|
||||
|
||||
t.Run("stdout json", func(t *testing.T) {
|
||||
t.Run("stdout xml", func(t *testing.T) {
|
||||
out := captureStdout(t, func() {
|
||||
if err := runKong(t, &GmailFiltersExportCmd{}, []string{}, ctx, flags); err != nil {
|
||||
t.Fatalf("export stdout: %v", err)
|
||||
}
|
||||
})
|
||||
if !strings.HasPrefix(out, xml.Header) {
|
||||
t.Fatalf("missing XML header: %q", out)
|
||||
}
|
||||
if !strings.Contains(out, `xmlns:apps="http://schemas.google.com/apps/2006"`) {
|
||||
t.Fatalf("missing apps namespace: %q", out)
|
||||
}
|
||||
if !strings.Contains(out, `name="label" value="Notifications & Alerts"`) {
|
||||
t.Fatalf("missing escaped label name: %q", out)
|
||||
}
|
||||
for _, want := range []string{
|
||||
`name="from" value="a@example.com"`,
|
||||
`name="subject" value="A&B"`,
|
||||
`name="hasTheWord" value="from:alerts has:attachment"`,
|
||||
`name="doesNotHaveTheWord" value="category:promotions"`,
|
||||
`name="hasAttachment" value="true"`,
|
||||
`name="excludeChats" value="true"`,
|
||||
`name="size" value="1024"`,
|
||||
`name="sizeUnit" value="s_sb"`,
|
||||
`name="sizeOperator" value="s_sl"`,
|
||||
`name="shouldStar" value="true"`,
|
||||
`name="shouldAlwaysMarkAsImportant" value="true"`,
|
||||
`name="smartLabelToApply" value="^smartlabel_social"`,
|
||||
`name="shouldArchive" value="true"`,
|
||||
`name="shouldMarkAsRead" value="true"`,
|
||||
`name="shouldNeverSpam" value="true"`,
|
||||
`name="forwardTo" value="f@example.com"`,
|
||||
} {
|
||||
if !strings.Contains(out, want) {
|
||||
t.Fatalf("missing %s in XML:\n%s", want, out)
|
||||
}
|
||||
}
|
||||
var parsed gmailFiltersXMLFeed
|
||||
if err := xml.Unmarshal([]byte(out), &parsed); err != nil {
|
||||
t.Fatalf("xml parse: %v", err)
|
||||
}
|
||||
if parsed.Author.Email != "a@b.com" || len(parsed.Entries) != 1 {
|
||||
t.Fatalf("unexpected parsed feed: %#v", parsed)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("stdout json compatibility", func(t *testing.T) {
|
||||
out := captureStdout(t, func() {
|
||||
if err := runKong(t, &GmailFiltersExportCmd{}, []string{"--format", "json"}, 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)
|
||||
@ -259,8 +339,27 @@ func TestGmailFiltersExport(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("file export", func(t *testing.T) {
|
||||
path := t.TempDir() + "/filters.json"
|
||||
t.Run("global json keeps old stdout json", func(t *testing.T) {
|
||||
jsonCtx := outfmt.WithMode(ctx, outfmt.Mode{JSON: true})
|
||||
jsonFlags := *flags
|
||||
jsonFlags.JSON = true
|
||||
out := captureStdout(t, func() {
|
||||
if err := runKong(t, &GmailFiltersExportCmd{}, []string{}, jsonCtx, &jsonFlags); 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 xml export", func(t *testing.T) {
|
||||
path := t.TempDir() + "/mailFilters.xml"
|
||||
if err := runKong(t, &GmailFiltersExportCmd{}, []string{"--out", path}, ctx, flags); err != nil {
|
||||
t.Fatalf("export file: %v", err)
|
||||
}
|
||||
@ -268,14 +367,24 @@ func TestGmailFiltersExport(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("read export: %v", err)
|
||||
}
|
||||
if !strings.Contains(string(b), "<feed") || !strings.Contains(string(b), "Mail Filters") {
|
||||
t.Fatalf("unexpected XML export: %s", b)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("file json export", func(t *testing.T) {
|
||||
path := t.TempDir() + "/filters.json"
|
||||
if err := runKong(t, &GmailFiltersExportCmd{}, []string{"--format", "json", "--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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
206
internal/cmd/gmail_filters_export.go
Normal file
206
internal/cmd/gmail_filters_export.go
Normal file
@ -0,0 +1,206 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"google.golang.org/api/gmail/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
gmailFiltersAtomNamespace = "http://www.w3.org/2005/Atom"
|
||||
gmailFiltersAppsNamespace = "http://schemas.google.com/apps/2006"
|
||||
)
|
||||
|
||||
var nowGmailFiltersExport = time.Now
|
||||
|
||||
type gmailFiltersXMLFeed struct {
|
||||
XMLName xml.Name `xml:"feed"`
|
||||
XMLNS string `xml:"xmlns,attr"`
|
||||
XMLNSApps string `xml:"xmlns:apps,attr"`
|
||||
Title string `xml:"title"`
|
||||
ID string `xml:"id"`
|
||||
Updated string `xml:"updated"`
|
||||
Author gmailFiltersXMLAuthor `xml:"author"`
|
||||
Entries []gmailFiltersXMLEntry `xml:"entry"`
|
||||
}
|
||||
|
||||
type gmailFiltersXMLAuthor struct {
|
||||
Name string `xml:"name"`
|
||||
Email string `xml:"email"`
|
||||
}
|
||||
|
||||
type gmailFiltersXMLEntry struct {
|
||||
Category gmailFiltersXMLCategory `xml:"category"`
|
||||
Title string `xml:"title"`
|
||||
ID string `xml:"id"`
|
||||
Updated string `xml:"updated"`
|
||||
Content string `xml:"content"`
|
||||
Properties []gmailFiltersXMLProperty `xml:"apps:property"`
|
||||
}
|
||||
|
||||
type gmailFiltersXMLCategory struct {
|
||||
Term string `xml:"term,attr"`
|
||||
}
|
||||
|
||||
type gmailFiltersXMLProperty struct {
|
||||
Name string `xml:"name,attr"`
|
||||
Value string `xml:"value,attr"`
|
||||
}
|
||||
|
||||
func marshalGmailFiltersXML(account string, filters []*gmail.Filter, labelNames map[string]string) ([]byte, error) {
|
||||
nowTime := nowGmailFiltersExport().UTC()
|
||||
now := nowTime.Format(time.RFC3339)
|
||||
feed := gmailFiltersXMLFeed{
|
||||
XMLNS: gmailFiltersAtomNamespace,
|
||||
XMLNSApps: gmailFiltersAppsNamespace,
|
||||
Title: "Mail Filters",
|
||||
ID: fmt.Sprintf("tag:mail.google.com,2008:filters:%d", nowTime.UnixMilli()),
|
||||
Updated: now,
|
||||
Author: gmailFiltersXMLAuthor{
|
||||
Name: strings.TrimSpace(account),
|
||||
Email: strings.TrimSpace(account),
|
||||
},
|
||||
Entries: make([]gmailFiltersXMLEntry, 0, len(filters)),
|
||||
}
|
||||
|
||||
for _, filter := range filters {
|
||||
if filter == nil {
|
||||
continue
|
||||
}
|
||||
entry := gmailFiltersXMLEntry{
|
||||
Category: gmailFiltersXMLCategory{Term: "filter"},
|
||||
Title: "Mail Filter",
|
||||
ID: "tag:mail.google.com,2008:filter:" + strings.TrimSpace(filter.Id),
|
||||
Updated: now,
|
||||
}
|
||||
entry.Properties = append(entry.Properties, gmailFilterCriteriaXMLProperties(filter.Criteria)...)
|
||||
entry.Properties = append(entry.Properties, gmailFilterActionXMLProperties(filter.Action, labelNames)...)
|
||||
feed.Entries = append(feed.Entries, entry)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString(xml.Header)
|
||||
enc := xml.NewEncoder(&buf)
|
||||
enc.Indent("", " ")
|
||||
if err := enc.Encode(feed); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := enc.Flush(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf.WriteByte('\n')
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func gmailFilterCriteriaXMLProperties(criteria *gmail.FilterCriteria) []gmailFiltersXMLProperty {
|
||||
if criteria == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var props []gmailFiltersXMLProperty
|
||||
props = appendXMLProperty(props, "from", criteria.From)
|
||||
props = appendXMLProperty(props, "to", criteria.To)
|
||||
props = appendXMLProperty(props, "subject", criteria.Subject)
|
||||
props = appendXMLProperty(props, "hasTheWord", criteria.Query)
|
||||
props = appendXMLProperty(props, "doesNotHaveTheWord", criteria.NegatedQuery)
|
||||
if criteria.HasAttachment {
|
||||
props = appendXMLProperty(props, "hasAttachment", "true")
|
||||
}
|
||||
if criteria.ExcludeChats {
|
||||
props = appendXMLProperty(props, "excludeChats", "true")
|
||||
}
|
||||
if criteria.Size > 0 {
|
||||
props = appendXMLProperty(props, "size", strconv.FormatInt(criteria.Size, 10))
|
||||
props = appendXMLProperty(props, "sizeUnit", "s_sb")
|
||||
switch strings.ToLower(strings.TrimSpace(criteria.SizeComparison)) {
|
||||
case "larger":
|
||||
props = appendXMLProperty(props, "sizeOperator", "s_sl")
|
||||
case "smaller":
|
||||
props = appendXMLProperty(props, "sizeOperator", "s_ss")
|
||||
}
|
||||
}
|
||||
return props
|
||||
}
|
||||
|
||||
func gmailFilterActionXMLProperties(action *gmail.FilterAction, labelNames map[string]string) []gmailFiltersXMLProperty {
|
||||
if action == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var props []gmailFiltersXMLProperty
|
||||
for _, id := range action.AddLabelIds {
|
||||
switch strings.ToUpper(strings.TrimSpace(id)) {
|
||||
case "":
|
||||
continue
|
||||
case gmailSystemLabelStarred:
|
||||
props = appendXMLProperty(props, "shouldStar", "true")
|
||||
case gmailSystemLabelImportant:
|
||||
props = appendXMLProperty(props, "shouldAlwaysMarkAsImportant", "true")
|
||||
case gmailSystemLabelTrash:
|
||||
props = appendXMLProperty(props, "shouldTrash", "true")
|
||||
default:
|
||||
if smartLabel := gmailFilterSmartLabelXMLValue(id); smartLabel != "" {
|
||||
props = appendXMLProperty(props, "smartLabelToApply", smartLabel)
|
||||
continue
|
||||
}
|
||||
props = appendXMLProperty(props, "label", gmailFilterXMLLabelName(id, labelNames))
|
||||
}
|
||||
}
|
||||
for _, id := range action.RemoveLabelIds {
|
||||
switch strings.ToUpper(strings.TrimSpace(id)) {
|
||||
case "":
|
||||
continue
|
||||
case gmailSystemLabelInbox:
|
||||
props = appendXMLProperty(props, "shouldArchive", "true")
|
||||
case gmailSystemLabelUnread:
|
||||
props = appendXMLProperty(props, "shouldMarkAsRead", "true")
|
||||
case gmailSystemLabelSpam:
|
||||
props = appendXMLProperty(props, "shouldNeverSpam", "true")
|
||||
case gmailSystemLabelImportant:
|
||||
props = appendXMLProperty(props, "shouldNeverMarkAsImportant", "true")
|
||||
}
|
||||
}
|
||||
props = appendXMLProperty(props, "forwardTo", action.Forward)
|
||||
return props
|
||||
}
|
||||
|
||||
func gmailFilterXMLLabelName(id string, labelNames map[string]string) string {
|
||||
trimmed := strings.TrimSpace(id)
|
||||
if labelNames == nil {
|
||||
return trimmed
|
||||
}
|
||||
if name := strings.TrimSpace(labelNames[trimmed]); name != "" {
|
||||
return name
|
||||
}
|
||||
return trimmed
|
||||
}
|
||||
|
||||
func gmailFilterSmartLabelXMLValue(id string) string {
|
||||
switch strings.ToUpper(strings.TrimSpace(id)) {
|
||||
case "CATEGORY_PERSONAL":
|
||||
return "^smartlabel_personal"
|
||||
case "CATEGORY_SOCIAL":
|
||||
return "^smartlabel_social"
|
||||
case "CATEGORY_PROMOTIONS":
|
||||
return "^smartlabel_promo"
|
||||
case "CATEGORY_UPDATES":
|
||||
return "^smartlabel_notification"
|
||||
case "CATEGORY_FORUMS":
|
||||
return "^smartlabel_group"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func appendXMLProperty(props []gmailFiltersXMLProperty, name, value string) []gmailFiltersXMLProperty {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
return props
|
||||
}
|
||||
return append(props, gmailFiltersXMLProperty{Name: name, Value: value})
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user