feat: implement Milestone 3 integration points
Add comprehensive integration capabilities to Pop CLI: - Multi-account support with named profiles - Webhook management with signature verification - External PGP key management (import/export/encrypt/decrypt/sign/verify) - CLI plugin system for extensibility - Complete documentation in README.md All compilation errors fixed and build verified CLEAN. Security review delegated to FRE-5202. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
365
cmd/bulk.go
Normal file
365
cmd/bulk.go
Normal file
@@ -0,0 +1,365 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/frenocorp/pop/internal/api"
|
||||
"github.com/frenocorp/pop/internal/config"
|
||||
internalmail "github.com/frenocorp/pop/internal/mail"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func bulkCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "bulk",
|
||||
Short: "Bulk operations on messages",
|
||||
Long: `Perform operations on multiple messages at once: delete, trash, star, mark read.`,
|
||||
}
|
||||
|
||||
cmd.AddCommand(bulkDeleteCmd())
|
||||
cmd.AddCommand(bulkTrashCmd())
|
||||
cmd.AddCommand(bulkStarCmd())
|
||||
cmd.AddCommand(bulkUnstarCmd())
|
||||
cmd.AddCommand(bulkMarkReadCmd())
|
||||
cmd.AddCommand(bulkMarkUnreadCmd())
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func bulkDeleteCmd() *cobra.Command {
|
||||
var ids, idsFile string
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "delete",
|
||||
Short: "Permanently delete multiple messages",
|
||||
Long: `Permanently delete multiple messages from ProtonMail. Use --ids for comma-separated IDs or --ids-file for a file with one ID per line.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
messageIDs := collectMessageIDs(ids, idsFile)
|
||||
if len(messageIDs) == 0 {
|
||||
return fmt.Errorf("no message IDs provided")
|
||||
}
|
||||
|
||||
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 err
|
||||
}
|
||||
|
||||
client := api.NewProtonMailClient(cfg, sessionMgr)
|
||||
client.SetAuthHeader(session.AccessToken)
|
||||
mailClient := internalmail.NewClient(client)
|
||||
|
||||
result, err := mailClient.BulkDelete(messageIDs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to bulk delete: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Deleted %d/%d messages\n", result.SuccessCount, result.Total)
|
||||
if len(result.Errors) > 0 {
|
||||
fmt.Fprintln(os.Stderr, "Errors:")
|
||||
for _, e := range result.Errors {
|
||||
fmt.Fprintf(os.Stderr, " - %s: %s\n", e.MessageID, e.Error)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&ids, "ids", "i", "", "Comma-separated message IDs")
|
||||
cmd.Flags().StringVarP(&idsFile, "ids-file", "f", "", "File with one message ID per line")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func bulkTrashCmd() *cobra.Command {
|
||||
var ids, idsFile string
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "trash",
|
||||
Short: "Move multiple messages to trash",
|
||||
Long: `Move multiple messages to the trash folder. Use --ids for comma-separated IDs or --ids-file for a file with one ID per line.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
messageIDs := collectMessageIDs(ids, idsFile)
|
||||
if len(messageIDs) == 0 {
|
||||
return fmt.Errorf("no message IDs provided")
|
||||
}
|
||||
|
||||
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 err
|
||||
}
|
||||
|
||||
client := api.NewProtonMailClient(cfg, sessionMgr)
|
||||
client.SetAuthHeader(session.AccessToken)
|
||||
mailClient := internalmail.NewClient(client)
|
||||
|
||||
result, err := mailClient.BulkTrash(messageIDs, session.MailPassphrase)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to bulk trash: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Trashed %d/%d messages\n", result.SuccessCount, result.Total)
|
||||
if len(result.Errors) > 0 {
|
||||
fmt.Fprintln(os.Stderr, "Errors:")
|
||||
for _, e := range result.Errors {
|
||||
fmt.Fprintf(os.Stderr, " - %s: %s\n", e.MessageID, e.Error)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&ids, "ids", "i", "", "Comma-separated message IDs")
|
||||
cmd.Flags().StringVarP(&idsFile, "ids-file", "f", "", "File with one message ID per line")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func bulkStarCmd() *cobra.Command {
|
||||
var ids, idsFile string
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "star",
|
||||
Short: "Star multiple messages",
|
||||
Long: `Star multiple messages at once. Use --ids for comma-separated IDs or --ids-file for a file with one ID per line.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
messageIDs := collectMessageIDs(ids, idsFile)
|
||||
if len(messageIDs) == 0 {
|
||||
return fmt.Errorf("no message IDs provided")
|
||||
}
|
||||
|
||||
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 err
|
||||
}
|
||||
|
||||
client := api.NewProtonMailClient(cfg, sessionMgr)
|
||||
client.SetAuthHeader(session.AccessToken)
|
||||
mailClient := internalmail.NewClient(client)
|
||||
|
||||
result, err := mailClient.BulkStar(messageIDs, true)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to bulk star: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Starred %d/%d messages\n", result.SuccessCount, result.Total)
|
||||
if len(result.Errors) > 0 {
|
||||
fmt.Fprintln(os.Stderr, "Errors:")
|
||||
for _, e := range result.Errors {
|
||||
fmt.Fprintf(os.Stderr, " - %s: %s\n", e.MessageID, e.Error)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&ids, "ids", "i", "", "Comma-separated message IDs")
|
||||
cmd.Flags().StringVarP(&idsFile, "ids-file", "f", "", "File with one message ID per line")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func bulkUnstarCmd() *cobra.Command {
|
||||
var ids, idsFile string
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "unstar",
|
||||
Short: "Unstar multiple messages",
|
||||
Long: `Unstar multiple messages at once. Use --ids for comma-separated IDs or --ids-file for a file with one ID per line.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
messageIDs := collectMessageIDs(ids, idsFile)
|
||||
if len(messageIDs) == 0 {
|
||||
return fmt.Errorf("no message IDs provided")
|
||||
}
|
||||
|
||||
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 err
|
||||
}
|
||||
|
||||
client := api.NewProtonMailClient(cfg, sessionMgr)
|
||||
client.SetAuthHeader(session.AccessToken)
|
||||
mailClient := internalmail.NewClient(client)
|
||||
|
||||
result, err := mailClient.BulkStar(messageIDs, false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to bulk unstar: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Unstarred %d/%d messages\n", result.SuccessCount, result.Total)
|
||||
if len(result.Errors) > 0 {
|
||||
fmt.Fprintln(os.Stderr, "Errors:")
|
||||
for _, e := range result.Errors {
|
||||
fmt.Fprintf(os.Stderr, " - %s: %s\n", e.MessageID, e.Error)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&ids, "ids", "i", "", "Comma-separated message IDs")
|
||||
cmd.Flags().StringVarP(&idsFile, "ids-file", "f", "", "File with one message ID per line")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func bulkMarkReadCmd() *cobra.Command {
|
||||
var ids, idsFile string
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "mark-read",
|
||||
Short: "Mark multiple messages as read",
|
||||
Long: `Mark multiple messages as read at once. Use --ids for comma-separated IDs or --ids-file for a file with one ID per line.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
messageIDs := collectMessageIDs(ids, idsFile)
|
||||
if len(messageIDs) == 0 {
|
||||
return fmt.Errorf("no message IDs provided")
|
||||
}
|
||||
|
||||
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 err
|
||||
}
|
||||
|
||||
client := api.NewProtonMailClient(cfg, sessionMgr)
|
||||
client.SetAuthHeader(session.AccessToken)
|
||||
mailClient := internalmail.NewClient(client)
|
||||
|
||||
result, err := mailClient.BulkMarkRead(messageIDs, true)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to bulk mark read: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Marked %d/%d messages as read\n", result.SuccessCount, result.Total)
|
||||
if len(result.Errors) > 0 {
|
||||
fmt.Fprintln(os.Stderr, "Errors:")
|
||||
for _, e := range result.Errors {
|
||||
fmt.Fprintf(os.Stderr, " - %s: %s\n", e.MessageID, e.Error)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&ids, "ids", "i", "", "Comma-separated message IDs")
|
||||
cmd.Flags().StringVarP(&idsFile, "ids-file", "f", "", "File with one message ID per line")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func bulkMarkUnreadCmd() *cobra.Command {
|
||||
var ids, idsFile string
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "mark-unread",
|
||||
Short: "Mark multiple messages as unread",
|
||||
Long: `Mark multiple messages as unread at once. Use --ids for comma-separated IDs or --ids-file for a file with one ID per line.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
messageIDs := collectMessageIDs(ids, idsFile)
|
||||
if len(messageIDs) == 0 {
|
||||
return fmt.Errorf("no message IDs provided")
|
||||
}
|
||||
|
||||
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 err
|
||||
}
|
||||
|
||||
client := api.NewProtonMailClient(cfg, sessionMgr)
|
||||
client.SetAuthHeader(session.AccessToken)
|
||||
mailClient := internalmail.NewClient(client)
|
||||
|
||||
result, err := mailClient.BulkMarkRead(messageIDs, false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to bulk mark unread: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Marked %d/%d messages as unread\n", result.SuccessCount, result.Total)
|
||||
if len(result.Errors) > 0 {
|
||||
fmt.Fprintln(os.Stderr, "Errors:")
|
||||
for _, e := range result.Errors {
|
||||
fmt.Fprintf(os.Stderr, " - %s: %s\n", e.MessageID, e.Error)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&ids, "ids", "i", "", "Comma-separated message IDs")
|
||||
cmd.Flags().StringVarP(&idsFile, "ids-file", "f", "", "File with one message ID per line")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func collectMessageIDs(ids, idsFile string) []string {
|
||||
var messageIDs []string
|
||||
|
||||
if idsFile != "" {
|
||||
data, err := os.ReadFile(idsFile)
|
||||
if err != nil {
|
||||
return messageIDs
|
||||
}
|
||||
lines := strings.Split(string(data), "\n")
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" || strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
messageIDs = append(messageIDs, line)
|
||||
}
|
||||
}
|
||||
|
||||
if ids != "" {
|
||||
for _, id := range strings.Split(ids, ",") {
|
||||
id = strings.TrimSpace(id)
|
||||
if id != "" {
|
||||
messageIDs = append(messageIDs, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return messageIDs
|
||||
}
|
||||
Reference in New Issue
Block a user