package mail import ( "bytes" "encoding/json" "fmt" "io" "net/http" "net/url" "github.com/frenocorp/pop/internal/api" ) type Client struct { apiClient *api.ProtonMailClient baseURL string } func NewClient(apiClient *api.ProtonMailClient) *Client { return &Client{ apiClient: apiClient, baseURL: apiClient.GetBaseURL(), } } func (c *Client) ListMessages(req ListMessagesRequest) (*ListMessagesResponse, error) { params := url.Values{} params.Set("Page", fmt.Sprintf("%d", req.Page)) params.Set("PageSize", fmt.Sprintf("%d", req.PageSize)) params.Set("Passphrase", req.Passphrase) if req.Folder != FolderInbox { params.Set("Type", fmt.Sprintf("%d", req.Folder)) } if req.Starred != nil { params.Set("Starred", fmt.Sprintf("%t", *req.Starred)) } if req.Read != nil { params.Set("Read", fmt.Sprintf("%t", *req.Read)) } if req.Since > 0 { params.Set("Since", fmt.Sprintf("%d", req.Since)) } reqURL := fmt.Sprintf("%s/api/messages?%s", c.baseURL, params.Encode()) 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 list messages: %w", err) } defer resp.Body.Close() body, 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(body, &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) { params := url.Values{} params.Set("Passphrase", passphrase) reqURL := fmt.Sprintf("%s/api/messages/%s?%s", c.baseURL, url.QueryEscape(messageID), params.Encode()) 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() body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("failed to read response: %w", err) } var result struct { Data Message `json:"Data"` } if err := json.Unmarshal(body, &result); err != nil { return nil, fmt.Errorf("failed to parse response: %w", err) } return &result.Data, nil } func (c *Client) Send(req SendRequest) error { formData := url.Values{} formData.Set("Type", "0") formData.Set("Passphrase", req.Passphrase) formData.Set("Subject", req.Subject) formData.Set("HTML", fmt.Sprintf("%t", req.HTML)) toJSON, _ := json.Marshal(req.To) formData.Set("To", string(toJSON)) if len(req.CC) > 0 { ccJSON, _ := json.Marshal(req.CC) formData.Set("CC", string(ccJSON)) } if len(req.BCC) > 0 { bccJSON, _ := json.Marshal(req.BCC) formData.Set("BCC", string(bccJSON)) } bodyData := req.Body formData.Set("Body", bodyData) reqURL := fmt.Sprintf("%s/api/messages", c.baseURL) httpReq, err := http.NewRequest("POST", reqURL, bytes.NewBufferString(formData.Encode())) if err != nil { return fmt.Errorf("failed to create request: %w", err) } httpReq.Header.Set("Content-Type", "application/x-www-form-urlencoded") 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) error { formData := url.Values{} reqURL := fmt.Sprintf("%s/api/messages/%s/movetotrash", c.baseURL, url.QueryEscape(messageID)) httpReq, err := http.NewRequest("POST", reqURL, bytes.NewBufferString(formData.Encode())) if err != nil { return fmt.Errorf("failed to create request: %w", err) } httpReq.Header.Set("Content-Type", "application/x-www-form-urlencoded") 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/api/messages/%s/delete", c.baseURL, url.QueryEscape(messageID)) httpReq, err := http.NewRequest("POST", 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) { formData := url.Values{} formData.Set("Type", "2") formData.Set("Passphrase", passphrase) formData.Set("Subject", draft.Subject) toJSON, _ := json.Marshal(draft.To) formData.Set("To", string(toJSON)) if len(draft.CC) > 0 { ccJSON, _ := json.Marshal(draft.CC) formData.Set("CC", string(ccJSON)) } if len(draft.BCC) > 0 { bccJSON, _ := json.Marshal(draft.BCC) formData.Set("BCC", string(bccJSON)) } formData.Set("Body", draft.Body) reqURL := fmt.Sprintf("%s/api/messages", c.baseURL) httpReq, err := http.NewRequest("POST", reqURL, bytes.NewBufferString(formData.Encode())) if err != nil { return "", fmt.Errorf("failed to create request: %w", err) } httpReq.Header.Set("Content-Type", "application/x-www-form-urlencoded") resp, err := c.apiClient.Do(httpReq) if err != nil { return "", fmt.Errorf("failed to save draft: %w", err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return "", fmt.Errorf("failed to read response: %w", err) } var result struct { Data struct { MessageID string `json:"MessageID"` } `json:"Data"` } if err := json.Unmarshal(body, &result); err != nil { return "", fmt.Errorf("failed to parse response: %w", err) } return result.Data.MessageID, nil } func (c *Client) UpdateDraft(messageID string, draft Draft, passphrase string) error { formData := url.Values{} formData.Set("Passphrase", passphrase) formData.Set("Subject", draft.Subject) toJSON, _ := json.Marshal(draft.To) formData.Set("To", string(toJSON)) if len(draft.CC) > 0 { ccJSON, _ := json.Marshal(draft.CC) formData.Set("CC", string(ccJSON)) } formData.Set("Body", draft.Body) reqURL := fmt.Sprintf("%s/api/messages/%s", c.baseURL, url.QueryEscape(messageID)) httpReq, err := http.NewRequest("POST", reqURL, bytes.NewBufferString(formData.Encode())) if err != nil { return fmt.Errorf("failed to create request: %w", err) } httpReq.Header.Set("Content-Type", "application/x-www-form-urlencoded") 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) error { formData := url.Values{} reqURL := fmt.Sprintf("%s/api/messages/%s/send", c.baseURL, url.QueryEscape(messageID)) httpReq, err := http.NewRequest("POST", reqURL, bytes.NewBufferString(formData.Encode())) if err != nil { return fmt.Errorf("failed to create request: %w", err) } httpReq.Header.Set("Content-Type", "application/x-www-form-urlencoded") 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) { params := url.Values{} params.Set("Query", req.Query) params.Set("Page", fmt.Sprintf("%d", req.Page)) params.Set("PageSize", fmt.Sprintf("%d", req.PageSize)) params.Set("Passphrase", req.Passphrase) reqURL := fmt.Sprintf("%s/api/messages/search?%s", c.baseURL, params.Encode()) 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 search messages: %w", err) } defer resp.Body.Close() body, 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(body, &result); err != nil { return nil, fmt.Errorf("failed to parse response: %w", err) } return &result, nil }