FRE-683: Add contacts & attachments management
- Implemented contact CRUD operations (list, add, edit, delete) - Implemented attachment management (list, upload, download) - Created internal/contact/manager.go for contact persistence - Created internal/attachment/manager.go for attachment storage - Added CLI commands in cmd/contacts.go and cmd/attachments.go - Integrated contact and attachment commands into root CLI Files: - internal/contact/types.go - Contact data models - internal/contact/manager.go - Contact CRUD operations - internal/attachment/manager.go - Attachment file operations - cmd/contacts.go - Contact CLI commands - cmd/attachments.go - Attachment CLI commands Contacts stored in ~/.config/pop/contacts.json Attachments stored in ~/.config/pop/attachments/
This commit is contained in:
342
internal/mail/client.go
Normal file
342
internal/mail/client.go
Normal file
@@ -0,0 +1,342 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user