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) { 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/api/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") 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) { body := map[string]string{ "Passphrase": passphrase, } jsonBody, err := json.Marshal(body) if err != nil { return nil, fmt.Errorf("failed to marshal request: %w", err) } reqURL := fmt.Sprintf("%s/api/messages/%s", c.baseURL, url.QueryEscape(messageID)) 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 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) } var result struct { Data Message `json:"Data"` } if err := json.Unmarshal(respBody, &result); err != nil { return nil, fmt.Errorf("failed to parse response: %w", err) } return &result.Data, nil } func (c *Client) Send(req SendRequest) error { body := map[string]interface{}{ "Type": "0", "Passphrase": req.Passphrase, "Subject": req.Subject, "HTML": req.HTML, "To": req.To, "Body": req.Body, } if len(req.CC) > 0 { body["CC"] = req.CC } if len(req.BCC) > 0 { body["BCC"] = req.BCC } jsonBody, err := json.Marshal(body) if err != nil { return fmt.Errorf("failed to marshal request: %w", err) } reqURL := fmt.Sprintf("%s/api/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) 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) { body := map[string]interface{}{ "Type": "2", "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/api/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 { Data struct { MessageID string `json:"MessageID"` } `json:"Data"` } if err := json.Unmarshal(respBody, &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 { body := map[string]interface{}{ "Passphrase": passphrase, "Subject": draft.Subject, "To": draft.To, "Body": draft.Body, } if len(draft.CC) > 0 { body["CC"] = draft.CC } jsonBody, err := json.Marshal(body) if err != nil { return fmt.Errorf("failed to marshal request: %w", err) } reqURL := fmt.Sprintf("%s/api/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 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) { 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/api/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 }