- Add SessionRefresher interface for token refresh abstraction - Update ProtonMailClient to auto-refresh on 401 responses - Add DoWithContext method for context-aware HTTP requests - Update SessionManager with RefreshTokenWithContext method - Update LoginWithCredentials and LoginInteractive to accept context - Add checkAuthenticatedWithManager helper for commands needing session manager - All API methods now support proper cancellation via context.Context Files changed: - internal/api/client.go - Auto-refresh on 401, context support - internal/auth/session.go - Context-aware refresh and login methods - internal/auth/interface.go - SessionRefresher interface - cmd/mail.go, cmd/draft.go, cmd/folders.go - Updated to use new helpers - cmd/auth.go - Context support for login commands Co-Authored-By: Paperclip <noreply@paperclip.ing>
264 lines
6.9 KiB
Go
264 lines
6.9 KiB
Go
package cmd
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
|
|
"github.com/frenocorp/pop/internal/api"
|
|
"github.com/frenocorp/pop/internal/config"
|
|
internalmail "github.com/frenocorp/pop/internal/mail"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
func mailDraftCmd() *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "draft",
|
|
Short: "Manage draft messages",
|
|
Long: `Save, list, edit, and send draft messages.`,
|
|
}
|
|
|
|
cmd.AddCommand(draftSaveCmd())
|
|
cmd.AddCommand(draftListCmd())
|
|
cmd.AddCommand(draftEditCmd())
|
|
cmd.AddCommand(draftSendCmd())
|
|
|
|
return cmd
|
|
}
|
|
|
|
func draftSaveCmd() *cobra.Command {
|
|
var to, cc, bcc, subject, bodyFile, body string
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "save",
|
|
Short: "Save a draft message",
|
|
Long: `Save a message as a draft in ProtonMail.`,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
if to == "" {
|
|
return fmt.Errorf("recipient is required (--to)")
|
|
}
|
|
|
|
msgBody := body
|
|
if bodyFile != "" {
|
|
data, err := os.ReadFile(bodyFile)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read body file: %w", err)
|
|
}
|
|
msgBody = string(data)
|
|
}
|
|
|
|
recipients := parseRecipients(to)
|
|
var ccRecipients []internalmail.Recipient
|
|
if cc != "" {
|
|
ccRecipients = parseRecipients(cc)
|
|
}
|
|
|
|
var bccRecipients []internalmail.Recipient
|
|
if bcc != "" {
|
|
bccRecipients = parseRecipients(bcc)
|
|
}
|
|
|
|
cfgMgr := config.NewConfigManager()
|
|
cfg, err := cfgMgr.Load()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to load config: %w", err)
|
|
}
|
|
|
|
session, sessionMgr, err := checkAuthenticatedWithManager()
|
|
if err != nil {
|
|
return fmt.Errorf("not authenticated: %w", err)
|
|
}
|
|
|
|
client := api.NewProtonMailClient(cfg, sessionMgr)
|
|
client.SetAuthHeader(session.AccessToken)
|
|
mailClient := internalmail.NewClient(client)
|
|
|
|
draft := internalmail.Draft{
|
|
To: recipients,
|
|
CC: ccRecipients,
|
|
BCC: bccRecipients,
|
|
Subject: subject,
|
|
Body: msgBody,
|
|
}
|
|
|
|
messageID, err := mailClient.SaveDraft(draft, session.MailPassphrase)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to save draft: %w", err)
|
|
}
|
|
|
|
fmt.Printf("Draft saved with ID: %s\n", messageID)
|
|
return nil
|
|
},
|
|
}
|
|
|
|
cmd.Flags().StringVarP(&to, "to", "t", "", "Recipient addresses (comma-separated)")
|
|
cmd.Flags().StringVarP(&cc, "cc", "c", "", "CC addresses (comma-separated)")
|
|
cmd.Flags().StringVarP(&bcc, "bcc", "b", "", "BCC addresses (comma-separated)")
|
|
cmd.Flags().StringVarP(&subject, "subject", "s", "", "Draft subject")
|
|
cmd.Flags().StringVarP(&bodyFile, "body-file", "f", "", "File containing draft body")
|
|
cmd.Flags().StringVar(&body, "body", "", "Inline draft body")
|
|
|
|
return cmd
|
|
}
|
|
|
|
func draftListCmd() *cobra.Command {
|
|
var page, pageSize string
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "list",
|
|
Short: "List draft messages",
|
|
Long: `List all draft messages in ProtonMail.`,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
pageVal, err := strconv.Atoi(page)
|
|
if err != nil || pageVal < 1 {
|
|
pageVal = 1
|
|
}
|
|
|
|
pageSizeVal, err := strconv.Atoi(pageSize)
|
|
if err != nil || pageSizeVal < 1 {
|
|
pageSizeVal = 20
|
|
}
|
|
|
|
cfgMgr := config.NewConfigManager()
|
|
cfg, err := cfgMgr.Load()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to load config: %w", err)
|
|
}
|
|
|
|
session, sessionMgr, err := checkAuthenticatedWithManager()
|
|
if err != nil {
|
|
return fmt.Errorf("not authenticated: %w", err)
|
|
}
|
|
|
|
client := api.NewProtonMailClient(cfg, sessionMgr)
|
|
client.SetAuthHeader(session.AccessToken)
|
|
mailClient := internalmail.NewClient(client)
|
|
|
|
result, err := mailClient.ListDrafts(pageVal, pageSizeVal, session.MailPassphrase)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to list drafts: %w", err)
|
|
}
|
|
|
|
return printMessages(result.Messages)
|
|
},
|
|
}
|
|
|
|
cmd.Flags().StringVar(&page, "page", "1", "Page number")
|
|
cmd.Flags().StringVar(&pageSize, "page-size", "20", "Drafts per page (max 100)")
|
|
|
|
return cmd
|
|
}
|
|
|
|
func draftEditCmd() *cobra.Command {
|
|
var to, cc, bcc, subject, bodyFile, body string
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "edit <draft-id>",
|
|
Short: "Edit a draft message",
|
|
Long: `Edit an existing draft message in ProtonMail.`,
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
messageID := args[0]
|
|
|
|
var recipients []internalmail.Recipient
|
|
if to != "" {
|
|
recipients = parseRecipients(to)
|
|
}
|
|
|
|
var ccRecipients []internalmail.Recipient
|
|
if cc != "" {
|
|
ccRecipients = parseRecipients(cc)
|
|
}
|
|
|
|
var bccRecipients []internalmail.Recipient
|
|
if bcc != "" {
|
|
bccRecipients = parseRecipients(bcc)
|
|
}
|
|
|
|
msgBody := body
|
|
if bodyFile != "" {
|
|
data, err := os.ReadFile(bodyFile)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read body file: %w", err)
|
|
}
|
|
msgBody = string(data)
|
|
}
|
|
|
|
cfgMgr := config.NewConfigManager()
|
|
cfg, err := cfgMgr.Load()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to load config: %w", err)
|
|
}
|
|
|
|
session, sessionMgr, err := checkAuthenticatedWithManager()
|
|
if err != nil {
|
|
return fmt.Errorf("not authenticated: %w", err)
|
|
}
|
|
|
|
client := api.NewProtonMailClient(cfg, sessionMgr)
|
|
client.SetAuthHeader(session.AccessToken)
|
|
mailClient := internalmail.NewClient(client)
|
|
|
|
draft := internalmail.Draft{
|
|
To: recipients,
|
|
CC: ccRecipients,
|
|
BCC: bccRecipients,
|
|
Subject: subject,
|
|
Body: msgBody,
|
|
}
|
|
|
|
if err := mailClient.UpdateDraft(messageID, draft, session.MailPassphrase); err != nil {
|
|
return fmt.Errorf("failed to update draft: %w", err)
|
|
}
|
|
|
|
fmt.Println("Draft updated successfully")
|
|
return nil
|
|
},
|
|
}
|
|
|
|
cmd.Flags().StringVarP(&to, "to", "t", "", "New recipient addresses (comma-separated)")
|
|
cmd.Flags().StringVarP(&cc, "cc", "c", "", "New CC addresses (comma-separated)")
|
|
cmd.Flags().StringVarP(&bcc, "bcc", "b", "", "New BCC addresses (comma-separated)")
|
|
cmd.Flags().StringVarP(&subject, "subject", "s", "", "New draft subject")
|
|
cmd.Flags().StringVarP(&bodyFile, "body-file", "f", "", "File containing new draft body")
|
|
cmd.Flags().StringVar(&body, "body", "", "New inline draft body")
|
|
|
|
return cmd
|
|
}
|
|
|
|
func draftSendCmd() *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "send <draft-id>",
|
|
Short: "Send a draft message",
|
|
Long: `Send an existing draft message from ProtonMail.`,
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
messageID := args[0]
|
|
|
|
cfgMgr := config.NewConfigManager()
|
|
cfg, err := cfgMgr.Load()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to load config: %w", err)
|
|
}
|
|
|
|
session, sessionMgr, err := checkAuthenticatedWithManager()
|
|
if err != nil {
|
|
return fmt.Errorf("not authenticated: %w", err)
|
|
}
|
|
|
|
client := api.NewProtonMailClient(cfg, sessionMgr)
|
|
client.SetAuthHeader(session.AccessToken)
|
|
mailClient := internalmail.NewClient(client)
|
|
|
|
if err := mailClient.SendDraft(messageID, session.MailPassphrase); err != nil {
|
|
return fmt.Errorf("failed to send draft: %w", err)
|
|
}
|
|
|
|
fmt.Println("Draft sent successfully")
|
|
return nil
|
|
},
|
|
}
|
|
|
|
return cmd
|
|
}
|