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>
727 lines
19 KiB
Go
727 lines
19 KiB
Go
package mail
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/frenocorp/pop/internal/api"
|
|
)
|
|
|
|
type Client struct {
|
|
apiClient *api.ProtonMailClient
|
|
baseURL string
|
|
pgpService *PGPService
|
|
}
|
|
|
|
func NewClient(apiClient *api.ProtonMailClient) *Client {
|
|
return &Client{
|
|
apiClient: apiClient,
|
|
baseURL: apiClient.GetBaseURL(),
|
|
}
|
|
}
|
|
|
|
func (c *Client) SetPGPService(svc *PGPService) {
|
|
c.pgpService = svc
|
|
}
|
|
|
|
func (c *Client) ListMessages(req ListMessagesRequest) (*ListMessagesResponse, error) {
|
|
body := map[string]interface{}{
|
|
"Page": req.Page,
|
|
"PageSize": req.PageSize,
|
|
"Passphrase": req.Passphrase,
|
|
}
|
|
|
|
if req.Folder != FolderInbox {
|
|
body["Type"] = int(req.Folder)
|
|
}
|
|
|
|
if req.Starred != nil {
|
|
body["Starred"] = *req.Starred
|
|
}
|
|
|
|
if req.Read != nil {
|
|
body["Read"] = *req.Read
|
|
}
|
|
|
|
if req.Since > 0 {
|
|
body["Since"] = req.Since
|
|
}
|
|
|
|
jsonBody, err := json.Marshal(body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal request: %w", err)
|
|
}
|
|
|
|
reqURL := fmt.Sprintf("%s/mail/v4/messages", c.baseURL)
|
|
httpReq, err := http.NewRequest("POST", reqURL, bytes.NewBuffer(jsonBody))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
httpReq.Header.Set("Content-Type", "application/json")
|
|
httpReq.Header.Set("X-HTTP-Method-Override", "GET")
|
|
|
|
resp, err := c.apiClient.Do(httpReq)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to list messages: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
respBody, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read response: %w", err)
|
|
}
|
|
|
|
var result ListMessagesResponse
|
|
if err := json.Unmarshal(respBody, &result); err != nil {
|
|
return nil, fmt.Errorf("failed to parse response: %w", err)
|
|
}
|
|
|
|
return &result, nil
|
|
}
|
|
|
|
func (c *Client) GetMessage(messageID string, passphrase string) (*Message, error) {
|
|
var result struct {
|
|
Message Message `json:"Message"`
|
|
}
|
|
|
|
reqURL := fmt.Sprintf("%s/mail/v4/messages/%s", c.baseURL, url.QueryEscape(messageID))
|
|
httpReq, err := http.NewRequest("GET", reqURL, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
|
|
resp, err := c.apiClient.Do(httpReq)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get message: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
respBody, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read response: %w", err)
|
|
}
|
|
|
|
if err := json.Unmarshal(respBody, &result); err != nil {
|
|
return nil, fmt.Errorf("failed to parse response: %w", err)
|
|
}
|
|
|
|
return &result.Message, nil
|
|
}
|
|
|
|
func (c *Client) Send(req SendRequest) error {
|
|
payload := map[string]interface{}{
|
|
"Type": MessageTypeRegular,
|
|
"Passphrase": req.Passphrase,
|
|
"Subject": req.Subject,
|
|
"HTML": req.HTML,
|
|
"To": req.To,
|
|
}
|
|
|
|
if req.Body != "" {
|
|
if c.pgpService != nil {
|
|
encrypted, err := c.pgpService.EncryptBody(req.Body, req.Passphrase)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to encrypt message body: %w", err)
|
|
}
|
|
payload["BodyEnc"] = encrypted
|
|
} else {
|
|
payload["Body"] = req.Body
|
|
}
|
|
}
|
|
|
|
if len(req.CC) > 0 {
|
|
payload["CC"] = req.CC
|
|
}
|
|
|
|
if len(req.BCC) > 0 {
|
|
payload["BCC"] = req.BCC
|
|
}
|
|
|
|
jsonBody, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal request: %w", err)
|
|
}
|
|
|
|
reqURL := fmt.Sprintf("%s/mail/v4/messages", c.baseURL)
|
|
httpReq, err := http.NewRequest("POST", reqURL, bytes.NewBuffer(jsonBody))
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
httpReq.Header.Set("Content-Type", "application/json")
|
|
|
|
resp, err := c.apiClient.Do(httpReq)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to send message: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
|
|
body, _ := io.ReadAll(resp.Body)
|
|
return fmt.Errorf("send failed (status %d): %s", resp.StatusCode, string(body))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Client) MoveToTrash(messageID string, passphrase string) error {
|
|
body := map[string]string{
|
|
"Passphrase": passphrase,
|
|
}
|
|
|
|
jsonBody, err := json.Marshal(body)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal request: %w", err)
|
|
}
|
|
|
|
reqURL := fmt.Sprintf("%s/mail/v4/messages/%s/trash", c.baseURL, url.QueryEscape(messageID))
|
|
httpReq, err := http.NewRequest("PUT", reqURL, bytes.NewBuffer(jsonBody))
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
httpReq.Header.Set("Content-Type", "application/json")
|
|
|
|
resp, err := c.apiClient.Do(httpReq)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to move to trash: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
body, _ := io.ReadAll(resp.Body)
|
|
return fmt.Errorf("trash failed (status %d): %s", resp.StatusCode, string(body))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Client) PermanentlyDelete(messageID string) error {
|
|
reqURL := fmt.Sprintf("%s/mail/v4/messages/%s", c.baseURL, url.QueryEscape(messageID))
|
|
httpReq, err := http.NewRequest("DELETE", reqURL, nil)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
|
|
resp, err := c.apiClient.Do(httpReq)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to delete message: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
body, _ := io.ReadAll(resp.Body)
|
|
return fmt.Errorf("delete failed (status %d): %s", resp.StatusCode, string(body))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Client) SaveDraft(draft Draft, passphrase string) (string, error) {
|
|
body := map[string]interface{}{
|
|
"Type": MessageTypeDraft,
|
|
"Passphrase": passphrase,
|
|
"Subject": draft.Subject,
|
|
"To": draft.To,
|
|
"Body": draft.Body,
|
|
}
|
|
|
|
if len(draft.CC) > 0 {
|
|
body["CC"] = draft.CC
|
|
}
|
|
|
|
if len(draft.BCC) > 0 {
|
|
body["BCC"] = draft.BCC
|
|
}
|
|
|
|
jsonBody, err := json.Marshal(body)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to marshal request: %w", err)
|
|
}
|
|
|
|
reqURL := fmt.Sprintf("%s/mail/v4/messages", c.baseURL)
|
|
httpReq, err := http.NewRequest("POST", reqURL, bytes.NewBuffer(jsonBody))
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
httpReq.Header.Set("Content-Type", "application/json")
|
|
|
|
resp, err := c.apiClient.Do(httpReq)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to save draft: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
respBody, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to read response: %w", err)
|
|
}
|
|
|
|
var result struct {
|
|
Message struct {
|
|
MessageID string `json:"MessageID"`
|
|
} `json:"Message"`
|
|
}
|
|
if err := json.Unmarshal(respBody, &result); err != nil {
|
|
return "", fmt.Errorf("failed to parse response: %w", err)
|
|
}
|
|
|
|
return result.Message.MessageID, nil
|
|
}
|
|
|
|
func (c *Client) UpdateDraft(messageID string, draft Draft, passphrase string) error {
|
|
body := map[string]interface{}{
|
|
"Message": map[string]interface{}{
|
|
"Passphrase": passphrase,
|
|
"Subject": draft.Subject,
|
|
"To": draft.To,
|
|
"Body": draft.Body,
|
|
},
|
|
}
|
|
|
|
if len(draft.CC) > 0 {
|
|
body["Message"].(map[string]interface{})["CC"] = draft.CC
|
|
}
|
|
|
|
jsonBody, err := json.Marshal(body)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal request: %w", err)
|
|
}
|
|
|
|
reqURL := fmt.Sprintf("%s/mail/v4/messages/%s", c.baseURL, url.QueryEscape(messageID))
|
|
httpReq, err := http.NewRequest("PUT", reqURL, bytes.NewBuffer(jsonBody))
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
httpReq.Header.Set("Content-Type", "application/json")
|
|
|
|
resp, err := c.apiClient.Do(httpReq)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to update draft: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
body, _ := io.ReadAll(resp.Body)
|
|
return fmt.Errorf("update failed (status %d): %s", resp.StatusCode, string(body))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Client) SendDraft(messageID string, passphrase string) error {
|
|
body := map[string]string{
|
|
"Passphrase": passphrase,
|
|
}
|
|
|
|
jsonBody, err := json.Marshal(body)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal request: %w", err)
|
|
}
|
|
|
|
reqURL := fmt.Sprintf("%s/mail/v4/messages/%s", c.baseURL, url.QueryEscape(messageID))
|
|
httpReq, err := http.NewRequest("POST", reqURL, bytes.NewBuffer(jsonBody))
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
httpReq.Header.Set("Content-Type", "application/json")
|
|
|
|
resp, err := c.apiClient.Do(httpReq)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to send draft: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
body, _ := io.ReadAll(resp.Body)
|
|
return fmt.Errorf("send draft failed (status %d): %s", resp.StatusCode, string(body))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Client) ListDrafts(page int, pageSize int, passphrase string) (*ListMessagesResponse, error) {
|
|
req := ListMessagesRequest{
|
|
Folder: FolderDraft,
|
|
Page: page,
|
|
PageSize: pageSize,
|
|
Passphrase: passphrase,
|
|
}
|
|
return c.ListMessages(req)
|
|
}
|
|
|
|
func (c *Client) SearchMessages(req SearchRequest) (*SearchResponse, error) {
|
|
body := map[string]interface{}{
|
|
"Query": req.Query,
|
|
"Page": req.Page,
|
|
"PageSize": req.PageSize,
|
|
"Passphrase": req.Passphrase,
|
|
}
|
|
|
|
jsonBody, err := json.Marshal(body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal request: %w", err)
|
|
}
|
|
|
|
reqURL := fmt.Sprintf("%s/mail/v4/messages/search", c.baseURL)
|
|
httpReq, err := http.NewRequest("POST", reqURL, bytes.NewBuffer(jsonBody))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
httpReq.Header.Set("Content-Type", "application/json")
|
|
|
|
resp, err := c.apiClient.Do(httpReq)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to search messages: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
respBody, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read response: %w", err)
|
|
}
|
|
|
|
var result SearchResponse
|
|
if err := json.Unmarshal(respBody, &result); err != nil {
|
|
return nil, fmt.Errorf("failed to parse response: %w", err)
|
|
}
|
|
|
|
return &result, nil
|
|
}
|
|
|
|
func (c *Client) ListConversations(page int, pageSize int, passphrase string) (*ConversationResponse, error) {
|
|
body := map[string]interface{}{
|
|
"Page": page,
|
|
"PageSize": pageSize,
|
|
"Passphrase": passphrase,
|
|
}
|
|
|
|
jsonBody, err := json.Marshal(body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal request: %w", err)
|
|
}
|
|
|
|
reqURL := fmt.Sprintf("%s/mail/v4/conversations", c.baseURL)
|
|
httpReq, err := http.NewRequest("POST", reqURL, bytes.NewBuffer(jsonBody))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
httpReq.Header.Set("Content-Type", "application/json")
|
|
httpReq.Header.Set("X-HTTP-Method-Override", "GET")
|
|
|
|
resp, err := c.apiClient.Do(httpReq)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to list conversations: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
respBody, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read response: %w", err)
|
|
}
|
|
|
|
var result ConversationResponse
|
|
if err := json.Unmarshal(respBody, &result); err != nil {
|
|
return nil, fmt.Errorf("failed to parse response: %w", err)
|
|
}
|
|
|
|
return &result, nil
|
|
}
|
|
|
|
func (c *Client) GetConversation(req GetConversationRequest) (*GetConversationResponse, error) {
|
|
body := map[string]interface{}{
|
|
"Passphrase": req.Passphrase,
|
|
}
|
|
|
|
jsonBody, err := json.Marshal(body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal request: %w", err)
|
|
}
|
|
|
|
reqURL := fmt.Sprintf("%s/mail/v4/conversations/%s", c.baseURL, url.QueryEscape(req.ConversationID))
|
|
httpReq, err := http.NewRequest("POST", reqURL, bytes.NewBuffer(jsonBody))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
httpReq.Header.Set("Content-Type", "application/json")
|
|
httpReq.Header.Set("X-HTTP-Method-Override", "GET")
|
|
|
|
resp, err := c.apiClient.Do(httpReq)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get conversation: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
respBody, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read response: %w", err)
|
|
}
|
|
|
|
var result GetConversationResponse
|
|
if err := json.Unmarshal(respBody, &result); err != nil {
|
|
return nil, fmt.Errorf("failed to parse response: %w", err)
|
|
}
|
|
|
|
return &result, nil
|
|
}
|
|
|
|
func (c *Client) BulkDelete(messageIDs []string) (*BulkResponse, error) {
|
|
body := map[string]interface{}{
|
|
"MessageIDs": messageIDs,
|
|
}
|
|
|
|
jsonBody, err := json.Marshal(body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal request: %w", err)
|
|
}
|
|
|
|
reqURL := fmt.Sprintf("%s/mail/v4/messages/bulk", c.baseURL)
|
|
httpReq, err := http.NewRequest("POST", reqURL, bytes.NewBuffer(jsonBody))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
httpReq.Header.Set("Content-Type", "application/json")
|
|
httpReq.Header.Set("X-HTTP-Method-Override", "DELETE")
|
|
|
|
resp, err := c.apiClient.Do(httpReq)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to bulk delete: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
respBody, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read response: %w", err)
|
|
}
|
|
|
|
var result BulkResponse
|
|
if err := json.Unmarshal(respBody, &result); err != nil {
|
|
return nil, fmt.Errorf("failed to parse response: %w", err)
|
|
}
|
|
|
|
return &result, nil
|
|
}
|
|
|
|
func (c *Client) BulkTrash(messageIDs []string, passphrase string) (*BulkResponse, error) {
|
|
body := map[string]interface{}{
|
|
"MessageIDs": messageIDs,
|
|
"Passphrase": passphrase,
|
|
}
|
|
|
|
jsonBody, err := json.Marshal(body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal request: %w", err)
|
|
}
|
|
|
|
reqURL := fmt.Sprintf("%s/mail/v4/messages/bulk/trash", c.baseURL)
|
|
httpReq, err := http.NewRequest("POST", reqURL, bytes.NewBuffer(jsonBody))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
httpReq.Header.Set("Content-Type", "application/json")
|
|
|
|
resp, err := c.apiClient.Do(httpReq)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to bulk trash: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
respBody, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read response: %w", err)
|
|
}
|
|
|
|
var result BulkResponse
|
|
if err := json.Unmarshal(respBody, &result); err != nil {
|
|
return nil, fmt.Errorf("failed to parse response: %w", err)
|
|
}
|
|
|
|
return &result, nil
|
|
}
|
|
|
|
func (c *Client) BulkStar(messageIDs []string, starred bool) (*BulkResponse, error) {
|
|
body := map[string]interface{}{
|
|
"MessageIDs": messageIDs,
|
|
"Starred": starred,
|
|
}
|
|
|
|
jsonBody, err := json.Marshal(body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal request: %w", err)
|
|
}
|
|
|
|
reqURL := fmt.Sprintf("%s/mail/v4/messages/bulk/star", c.baseURL)
|
|
httpReq, err := http.NewRequest("POST", reqURL, bytes.NewBuffer(jsonBody))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
httpReq.Header.Set("Content-Type", "application/json")
|
|
|
|
resp, err := c.apiClient.Do(httpReq)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to bulk star: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
respBody, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read response: %w", err)
|
|
}
|
|
|
|
var result BulkResponse
|
|
if err := json.Unmarshal(respBody, &result); err != nil {
|
|
return nil, fmt.Errorf("failed to parse response: %w", err)
|
|
}
|
|
|
|
return &result, nil
|
|
}
|
|
|
|
func (c *Client) BulkMarkRead(messageIDs []string, read bool) (*BulkResponse, error) {
|
|
body := map[string]interface{}{
|
|
"MessageIDs": messageIDs,
|
|
"Read": read,
|
|
}
|
|
|
|
jsonBody, err := json.Marshal(body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal request: %w", err)
|
|
}
|
|
|
|
reqURL := fmt.Sprintf("%s/mail/v4/messages/bulk/read", c.baseURL)
|
|
httpReq, err := http.NewRequest("POST", reqURL, bytes.NewBuffer(jsonBody))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
httpReq.Header.Set("Content-Type", "application/json")
|
|
|
|
resp, err := c.apiClient.Do(httpReq)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to bulk mark read: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
respBody, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read response: %w", err)
|
|
}
|
|
|
|
var result BulkResponse
|
|
if err := json.Unmarshal(respBody, &result); err != nil {
|
|
return nil, fmt.Errorf("failed to parse response: %w", err)
|
|
}
|
|
|
|
return &result, nil
|
|
}
|
|
|
|
func (c *Client) ExportMessages(req ExportRequest) ([]ExportedMessage, error) {
|
|
var messages []Message
|
|
|
|
if len(req.MessageIDs) > 0 {
|
|
for _, id := range req.MessageIDs {
|
|
msg, err := c.GetMessage(id, req.Passphrase)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get message %s: %w", id, err)
|
|
}
|
|
messages = append(messages, *msg)
|
|
}
|
|
} else if req.Search != "" {
|
|
searchReq := SearchRequest{
|
|
Query: req.Search,
|
|
Page: 1,
|
|
PageSize: 100,
|
|
Passphrase: req.Passphrase,
|
|
}
|
|
searchResult, err := c.SearchMessages(searchReq)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to search messages: %w", err)
|
|
}
|
|
messages = searchResult.Messages
|
|
} else {
|
|
listReq := ListMessagesRequest{
|
|
Folder: req.Folder,
|
|
Page: 1,
|
|
PageSize: 100,
|
|
Passphrase: req.Passphrase,
|
|
}
|
|
if req.Since > 0 {
|
|
listReq.Since = req.Since
|
|
}
|
|
listResult, err := c.ListMessages(listReq)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to list messages: %w", err)
|
|
}
|
|
messages = listResult.Messages
|
|
}
|
|
|
|
exported := make([]ExportedMessage, 0, len(messages))
|
|
for _, msg := range messages {
|
|
exp := ExportedMessage{
|
|
MessageID: msg.MessageID,
|
|
ConversationID: msg.ConversationID,
|
|
From: msg.Sender,
|
|
To: msg.Recipients,
|
|
Subject: msg.Subject,
|
|
Body: msg.Body,
|
|
Date: msg.CreatedAt.Format(time.RFC3339),
|
|
Starred: msg.Starred,
|
|
Read: msg.Read,
|
|
Attachments: msg.Attachments,
|
|
}
|
|
exported = append(exported, exp)
|
|
}
|
|
|
|
return exported, nil
|
|
}
|
|
|
|
func (c *Client) ImportMessages(req ImportRequest) (*ImportResponse, error) {
|
|
fileData, err := os.ReadFile(req.FilePath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read import file: %w", err)
|
|
}
|
|
|
|
var messages []ExportedMessage
|
|
if req.Format == ExportFormatJSON {
|
|
if err := json.Unmarshal(fileData, &messages); err != nil {
|
|
return nil, fmt.Errorf("failed to parse import file: %w", err)
|
|
}
|
|
} else {
|
|
return nil, fmt.Errorf("unsupported import format: %s (use json)", req.Format.String())
|
|
}
|
|
|
|
if len(messages) == 0 {
|
|
return &ImportResponse{Total: 0, ImportedCount: 0}, nil
|
|
}
|
|
|
|
imported := 0
|
|
var errors []BulkError
|
|
|
|
for _, msg := range messages {
|
|
sendReq := SendRequest{
|
|
To: []Recipient{msg.From.ToRecipient()},
|
|
Subject: msg.Subject,
|
|
Body: msg.Body,
|
|
HTML: msg.HTML,
|
|
Passphrase: req.Passphrase,
|
|
}
|
|
|
|
if err := c.Send(sendReq); err != nil {
|
|
errors = append(errors, BulkError{
|
|
MessageID: msg.MessageID,
|
|
Error: err.Error(),
|
|
})
|
|
continue
|
|
}
|
|
imported++
|
|
}
|
|
|
|
return &ImportResponse{
|
|
ImportedCount: imported,
|
|
Total: len(messages),
|
|
Errors: errors,
|
|
}, nil
|
|
}
|