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:
@@ -7,6 +7,8 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/frenocorp/pop/internal/api"
|
||||
)
|
||||
@@ -390,3 +392,335 @@ func (c *Client) SearchMessages(req SearchRequest) (*SearchResponse, error) {
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user