gogcli/internal/cmd/gmail_labels.go
2026-01-08 09:34:05 +01:00

247 lines
5.8 KiB
Go

package cmd
import (
"context"
"fmt"
"os"
"strings"
"google.golang.org/api/gmail/v1"
"github.com/steipete/gogcli/internal/outfmt"
"github.com/steipete/gogcli/internal/ui"
)
type GmailLabelsCmd struct {
List GmailLabelsListCmd `cmd:"" name:"list" help:"List labels"`
Get GmailLabelsGetCmd `cmd:"" name:"get" help:"Get label details (including counts)"`
Create GmailLabelsCreateCmd `cmd:"" name:"create" help:"Create a new label"`
Modify GmailLabelsModifyCmd `cmd:"" name:"modify" help:"Modify labels on threads"`
}
type GmailLabelsGetCmd struct {
Label string `arg:"" name:"labelIdOrName" help:"Label ID or name"`
}
func (c *GmailLabelsGetCmd) Run(ctx context.Context, flags *RootFlags) error {
account, err := requireAccount(flags)
if err != nil {
return err
}
svc, err := newGmailService(ctx, account)
if err != nil {
return err
}
idMap, err := fetchLabelNameToID(svc)
if err != nil {
return err
}
raw := strings.TrimSpace(c.Label)
if raw == "" {
return usage("empty label")
}
id := raw
if v, ok := idMap[strings.ToLower(raw)]; ok {
id = v
}
l, err := svc.Users.Labels.Get("me", id).Context(ctx).Do()
if err != nil {
return err
}
if outfmt.IsJSON(ctx) {
return outfmt.WriteJSON(os.Stdout, map[string]any{"label": l})
}
u := ui.FromContext(ctx)
u.Out().Printf("id\t%s", l.Id)
u.Out().Printf("name\t%s", l.Name)
u.Out().Printf("type\t%s", l.Type)
u.Out().Printf("messages_total\t%d", l.MessagesTotal)
u.Out().Printf("messages_unread\t%d", l.MessagesUnread)
u.Out().Printf("threads_total\t%d", l.ThreadsTotal)
u.Out().Printf("threads_unread\t%d", l.ThreadsUnread)
return nil
}
type GmailLabelsCreateCmd struct {
Name string `arg:"" help:"Label name"`
}
func (c *GmailLabelsCreateCmd) Run(ctx context.Context, flags *RootFlags) error {
u := ui.FromContext(ctx)
account, err := requireAccount(flags)
if err != nil {
return err
}
name := strings.TrimSpace(c.Name)
if name == "" {
return usage("label name is required")
}
svc, err := newGmailService(ctx, account)
if err != nil {
return err
}
err = ensureLabelNameAvailable(svc, name)
if err != nil {
return err
}
label, err := createLabel(ctx, svc, name)
if err != nil {
return mapLabelCreateError(err, name)
}
if outfmt.IsJSON(ctx) {
return outfmt.WriteJSON(os.Stdout, map[string]any{"label": label})
}
u.Out().Printf("Created label: %s (id: %s)", label.Name, label.Id)
return nil
}
func createLabel(ctx context.Context, svc *gmail.Service, name string) (*gmail.Label, error) {
return svc.Users.Labels.Create("me", &gmail.Label{
Name: name,
LabelListVisibility: "labelShow",
MessageListVisibility: "show",
}).Context(ctx).Do()
}
type GmailLabelsListCmd struct{}
func (c *GmailLabelsListCmd) 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.Labels.List("me").Context(ctx).Do()
if err != nil {
return err
}
if outfmt.IsJSON(ctx) {
return outfmt.WriteJSON(os.Stdout, map[string]any{"labels": resp.Labels})
}
if len(resp.Labels) == 0 {
u.Err().Println("No labels")
return nil
}
w, flush := tableWriter(ctx)
defer flush()
fmt.Fprintln(w, "ID\tNAME\tTYPE")
for _, l := range resp.Labels {
fmt.Fprintf(w, "%s\t%s\t%s\n", l.Id, l.Name, l.Type)
}
return nil
}
type GmailLabelsModifyCmd struct {
ThreadIDs []string `arg:"" name:"threadId" help:"Thread IDs"`
Add string `name:"add" help:"Labels to add (comma-separated, name or ID)"`
Remove string `name:"remove" help:"Labels to remove (comma-separated, name or ID)"`
}
func (c *GmailLabelsModifyCmd) Run(ctx context.Context, flags *RootFlags) error {
u := ui.FromContext(ctx)
account, err := requireAccount(flags)
if err != nil {
return err
}
threadIDs := c.ThreadIDs
addLabels := splitCSV(c.Add)
removeLabels := splitCSV(c.Remove)
if len(addLabels) == 0 && len(removeLabels) == 0 {
return usage("must specify --add and/or --remove")
}
svc, err := newGmailService(ctx, account)
if err != nil {
return err
}
idMap, err := fetchLabelNameToID(svc)
if err != nil {
return err
}
addIDs := resolveLabelIDs(addLabels, idMap)
removeIDs := resolveLabelIDs(removeLabels, idMap)
type result struct {
ThreadID string `json:"threadId"`
Success bool `json:"success"`
Error string `json:"error,omitempty"`
}
results := make([]result, 0, len(threadIDs))
for _, tid := range threadIDs {
_, err := svc.Users.Threads.Modify("me", tid, &gmail.ModifyThreadRequest{
AddLabelIds: addIDs,
RemoveLabelIds: removeIDs,
}).Context(ctx).Do()
if err != nil {
results = append(results, result{ThreadID: tid, Success: false, Error: err.Error()})
if !outfmt.IsJSON(ctx) {
u.Err().Errorf("%s: %s", tid, err.Error())
}
continue
}
results = append(results, result{ThreadID: tid, Success: true})
if !outfmt.IsJSON(ctx) {
u.Out().Printf("%s\tok", tid)
}
}
if outfmt.IsJSON(ctx) {
return outfmt.WriteJSON(os.Stdout, map[string]any{"results": results})
}
return nil
}
func fetchLabelNameToID(svc *gmail.Service) (map[string]string, error) {
resp, err := svc.Users.Labels.List("me").Do()
if err != nil {
return nil, err
}
m := make(map[string]string, len(resp.Labels))
for _, l := range resp.Labels {
if l.Id == "" {
continue
}
m[strings.ToLower(l.Id)] = l.Id
if l.Name != "" {
m[strings.ToLower(l.Name)] = l.Id
}
}
return m, nil
}
func fetchLabelIDToName(svc *gmail.Service) (map[string]string, error) {
resp, err := svc.Users.Labels.List("me").Do()
if err != nil {
return nil, err
}
m := make(map[string]string, len(resp.Labels))
for _, l := range resp.Labels {
if l.Id == "" {
continue
}
if l.Name != "" {
m[l.Id] = l.Name
} else {
m[l.Id] = l.Id
}
}
return m, nil
}