mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-17 15:46:44 +00:00
GODT-35: New pmapi client and manager using resty
This commit is contained in:
@ -18,110 +18,68 @@
|
||||
package pmapi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"errors"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
)
|
||||
|
||||
// Import errors.
|
||||
const (
|
||||
ImportMessageTooLarge = 36022
|
||||
)
|
||||
const MaxImportMessageRequestLength = 10
|
||||
|
||||
// ImportReq is an import request.
|
||||
type ImportReq struct {
|
||||
// A list of messages that will be imported.
|
||||
Messages []*ImportMsgReq
|
||||
}
|
||||
|
||||
// WriteTo writes the import request to a multipart writer.
|
||||
func (req *ImportReq) WriteTo(w *multipart.Writer) (err error) {
|
||||
// Create Metadata field.
|
||||
mw, err := w.CreateFormField("Metadata")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Build metadata.
|
||||
metadata := map[string]*ImportMsgReq{}
|
||||
for i, msg := range req.Messages {
|
||||
name := strconv.Itoa(i)
|
||||
metadata[name] = msg
|
||||
}
|
||||
|
||||
// Write metadata.
|
||||
if err = json.NewEncoder(mw).Encode(metadata); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Write messages.
|
||||
for i, msg := range req.Messages {
|
||||
name := strconv.Itoa(i)
|
||||
|
||||
var fw io.Writer
|
||||
if fw, err = w.CreateFormFile(name, name+".eml"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = fw.Write(msg.Body); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Adding new line to properly fetch the whole body on the API side.
|
||||
// The reason is the bug in PHP: https://bugs.php.net/bug.php?id=75923
|
||||
// Messages generated by PM already have it but importing already
|
||||
// encrypted messages might not have it.
|
||||
if _, err = fw.Write([]byte("\r\n")); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// ImportMsgReq is a request to import a message. All fields are optional except AddressID and Body.
|
||||
type ImportMsgReq struct {
|
||||
// The address where the message will be imported.
|
||||
AddressID string
|
||||
// The full MIME message.
|
||||
Body []byte `json:"-"`
|
||||
|
||||
// 0: read, 1: unread.
|
||||
Unread int
|
||||
// 1 if the message has been replied.
|
||||
IsReplied int
|
||||
// 1 if the message has been replied to all.
|
||||
IsRepliedAll int
|
||||
// 1 if the message has been forwarded.
|
||||
IsForwarded int
|
||||
// The time when the message was received as a Unix time.
|
||||
Time int64
|
||||
// The type of the imported message.
|
||||
Flags int64
|
||||
// The labels to apply to the imported message. Must contain at least one system label.
|
||||
LabelIDs []string
|
||||
Metadata *ImportMetadata // Metadata about the message to import.
|
||||
Message []byte // The raw RFC822 message.
|
||||
}
|
||||
|
||||
func (req ImportMsgReq) String() string {
|
||||
data, _ := json.Marshal(req)
|
||||
return string(data)
|
||||
}
|
||||
type ImportMsgReqs []*ImportMsgReq
|
||||
|
||||
// ImportRes is a response to an import request.
|
||||
type ImportRes struct {
|
||||
Res
|
||||
func (reqs ImportMsgReqs) buildMultipartFormData() ([]*resty.MultipartField, error) {
|
||||
var fields []*resty.MultipartField
|
||||
|
||||
Responses []struct {
|
||||
Name string
|
||||
Response struct {
|
||||
Res
|
||||
MessageID string
|
||||
}
|
||||
metadata := make(map[string]*ImportMetadata)
|
||||
|
||||
for i, req := range reqs {
|
||||
name := strconv.Itoa(i)
|
||||
|
||||
metadata[name] = req.Metadata
|
||||
|
||||
fields = append(fields, &resty.MultipartField{
|
||||
Param: name,
|
||||
FileName: name + ".eml",
|
||||
ContentType: "message/rfc822",
|
||||
Reader: bytes.NewReader(req.Message),
|
||||
})
|
||||
}
|
||||
|
||||
b, err := json.Marshal(metadata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fields = append(fields, &resty.MultipartField{
|
||||
Param: "Metadata",
|
||||
ContentType: "application/json",
|
||||
Reader: bytes.NewReader(b),
|
||||
})
|
||||
|
||||
return fields, nil
|
||||
}
|
||||
|
||||
// TODO: Add other metadata.
|
||||
type ImportMetadata struct {
|
||||
AddressID string
|
||||
Unread Boolean // 0: read, 1: unread.
|
||||
IsReplied Boolean // 1 if the message has been replied.
|
||||
IsRepliedAll Boolean // 1 if the message has been replied to all.
|
||||
IsForwarded Boolean // 1 if the message has been forwarded.
|
||||
Time int64 // The time when the message was received as a Unix time.
|
||||
Flags int64 // The type of the imported message.
|
||||
LabelIDs []string // The labels to apply to the imported message. Must contain at least one system label.
|
||||
}
|
||||
|
||||
// ImportMsgRes is a response to a single message import request.
|
||||
type ImportMsgRes struct {
|
||||
// The error encountered while importing the message, if any.
|
||||
Error error
|
||||
@ -130,41 +88,46 @@ type ImportMsgRes struct {
|
||||
}
|
||||
|
||||
// Import imports messages to the user's account.
|
||||
func (c *client) Import(reqs []*ImportMsgReq) (resps []*ImportMsgRes, err error) {
|
||||
importReq := &ImportReq{Messages: reqs}
|
||||
func (c *client) Import(ctx context.Context, reqs ImportMsgReqs) ([]*ImportMsgRes, error) {
|
||||
if len(reqs) > MaxImportMessageRequestLength {
|
||||
return nil, errors.New("request is too long")
|
||||
}
|
||||
|
||||
req, w, err := c.NewMultipartRequest("POST", "/mail/v4/messages/import")
|
||||
fields, err := reqs.buildMultipartFormData()
|
||||
if err != nil {
|
||||
return
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We will write the request as long as it is sent to the API.
|
||||
var importRes ImportRes
|
||||
done := make(chan error, 1)
|
||||
go (func() {
|
||||
done <- c.DoJSON(req, &importRes)
|
||||
})()
|
||||
|
||||
// Write the request.
|
||||
if err = importReq.WriteTo(w.Writer); err != nil {
|
||||
return
|
||||
}
|
||||
_ = w.Close()
|
||||
|
||||
if err = <-done; err != nil {
|
||||
return
|
||||
}
|
||||
if err = importRes.Err(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
resps = make([]*ImportMsgRes, len(importRes.Responses))
|
||||
for i, r := range importRes.Responses {
|
||||
resps[i] = &ImportMsgRes{
|
||||
Error: r.Response.Err(),
|
||||
MessageID: r.Response.MessageID,
|
||||
var res struct {
|
||||
Responses []struct {
|
||||
Name string
|
||||
Response struct {
|
||||
Error
|
||||
MessageID string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resps, err
|
||||
if _, err := c.do(ctx, func(r *resty.Request) (*resty.Response, error) {
|
||||
return r.SetMultipartFields(fields...).SetResult(&res).Post("/mail/v4/messages/import")
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var resps []*ImportMsgRes
|
||||
|
||||
for _, resp := range res.Responses {
|
||||
var err error
|
||||
|
||||
if resp.Response.Code != 1000 {
|
||||
err = resp.Response.Error
|
||||
}
|
||||
|
||||
resps = append(resps, &ImportMsgRes{
|
||||
Error: err,
|
||||
MessageID: resp.Response.MessageID,
|
||||
})
|
||||
}
|
||||
|
||||
return resps, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user