google mail API [[FIN]]
Some checks failed
Build Docker and Deploy / Run goext test-suite (push) Failing after 1m28s

This commit is contained in:
2023-12-04 13:48:11 +01:00
parent 8f13eb2f16
commit 358c238f3d
9 changed files with 549 additions and 131 deletions

View File

@@ -1,127 +1,69 @@
package googleapi
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"fmt"
"gogs.mikescher.com/BlackForestBytes/goext"
"gogs.mikescher.com/BlackForestBytes/goext/exerr"
"gogs.mikescher.com/BlackForestBytes/goext/langext"
"mime"
"strings"
"time"
"io"
"net/http"
)
func (c *client) SendMail(from string, recipients []string, cc []string, bcc []string, subject string, body MailBody, attachments []MailAttachment) error {
data := make([]string, 0, 32)
data = append(data, "Date: "+time.Now().Format(time.RFC1123Z))
data = append(data, "From: "+mime.QEncoding.Encode("UTF-8", from))
data = append(data, "To: "+strings.Join(langext.ArrMap(recipients, func(v string) string { return mime.QEncoding.Encode("UTF-8", v) }), ", "))
if len(cc) > 0 {
data = append(data, "To: "+strings.Join(langext.ArrMap(cc, func(v string) string { return mime.QEncoding.Encode("UTF-8", v) }), ", "))
}
if len(bcc) > 0 {
data = append(data, "Bcc: "+strings.Join(langext.ArrMap(bcc, func(v string) string { return mime.QEncoding.Encode("UTF-8", v) }), ", "))
}
data = append(data, "Subject: "+mime.QEncoding.Encode("UTF-8", subject))
hasAttachments := len(attachments) > 0
hasInlineAttachments := langext.ArrAny(attachments, func(v MailAttachment) bool { return v.IsInline })
hasPlain := body.Plain != nil
hasHTML := body.HTML != nil
mixedBoundary := "--------------" + langext.MustRawHexUUID()
relatedBoundary := "--------------" + langext.MustRawHexUUID()
altBoundary := "--------------" + langext.MustRawHexUUID()
inlineAttachments := langext.ArrFilter(attachments, func(v MailAttachment) bool { return v.IsInline })
normalAttachments := langext.ArrFilter(attachments, func(v MailAttachment) bool { return !v.IsInline })
if hasInlineAttachments {
// "mixed+related"
data = append(data, "Content-Type: multipart/mixed; boundary="+mixedBoundary)
data = append(data, "")
data = append(data, mixedBoundary)
data = append(data, "Content-Type: multipart/related; boundary="+relatedBoundary)
data = append(data, "")
data = append(data, c.dumpHTMLBody(body, hasInlineAttachments, hasAttachments, relatedBoundary, altBoundary)...)
data = append(data, "")
for i, attachment := range inlineAttachments {
data = append(data, relatedBoundary)
data = append(data, attachment.dump()...)
if i < len(inlineAttachments)-1 {
data = append(data, "")
}
}
data = append(data, relatedBoundary)
for i, attachment := range normalAttachments {
data = append(data, mixedBoundary)
data = append(data, attachment.dump()...)
if i < len(inlineAttachments)-1 {
data = append(data, "")
}
}
data = append(data, mixedBoundary)
} else if hasAttachments {
// "mixed"
//TODO https://github.dev/muratgozel/MIMEText/blob/master/src/MIMEMessage.ts
} else if hasPlain && hasHTML {
// "alternative"
//TODO https://github.dev/muratgozel/MIMEText/blob/master/src/MIMEMessage.ts
} else {
// "plain"
//TODO https://github.dev/muratgozel/MIMEText/blob/master/src/MIMEMessage.ts
}
type MailRef struct {
ID string `json:"id"`
ThreadID string `json:"threadId"`
LabelIDs []string `json:"labelIds"`
}
func (c *client) dumpHTMLBody(body MailBody, hasInlineAttachments bool, hasAttachments bool, boundary string, boundaryAlt string) []string {
func (c *client) SendMail(ctx context.Context, from string, recipients []string, cc []string, bcc []string, subject string, body MailBody, attachments []MailAttachment) (MailRef, error) {
data := make([]string, 0, 16)
mm := encodeMimeMail(from, recipients, cc, bcc, subject, body, attachments)
if body.HTML != nil && body.Plain != nil && hasInlineAttachments {
data = append(data, boundary)
data = append(data, *body.HTML)
} else if body.HTML != nil && body.Plain != nil && hasAttachments {
data = append(data, boundary)
data = append(data, "Content-Type: multipart/alternative; boundary="+boundaryAlt)
data = append(data, "")
data = append(data, boundaryAlt)
data = append(data, *body.Plain)
data = append(data, "")
data = append(data, boundaryAlt)
data = append(data, *body.HTML)
data = append(data, "")
data = append(data, boundaryAlt)
} else if body.HTML != nil && body.Plain != nil {
data = append(data, boundary)
data = append(data, *body.Plain)
data = append(data, "")
data = append(data, boundary)
data = append(data, *body.HTML)
} else if body.HTML != nil {
data = append(data, boundary)
data = append(data, *body.HTML)
} else if body.Plain != nil {
data = append(data, boundary)
data = append(data, *body.Plain)
} else {
data = append(data, boundary)
data = append(data, "") // no content ?!?
tok, err := c.oauth.AccessToken()
if err != nil {
return MailRef{}, exerr.Wrap(err, "").Build()
}
return data
url := fmt.Sprintf("https://gmail.googleapis.com/gmail/v1/users/%s/messages/send?alt=json&prettyPrint=false", "me")
msgbody, err := json.Marshal(langext.H{"raw": base64.URLEncoding.EncodeToString([]byte(mm))})
if err != nil {
return MailRef{}, exerr.Wrap(err, "").Build()
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(msgbody))
if err != nil {
return MailRef{}, exerr.Wrap(err, "").Build()
}
req.Header.Add("Authorization", "Bearer "+tok)
req.Header.Add("X-Goog-Api-Client", "blackforestbytes-goext/"+goext.GoextVersion)
req.Header.Add("User-Agent", "blackforestbytes-goext/"+goext.GoextVersion)
req.Header.Add("Content-Type", "application/json")
resp, err := c.http.Do(req)
if err != nil {
return MailRef{}, exerr.Wrap(err, "").Build()
}
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return MailRef{}, exerr.Wrap(err, "").Build()
}
if resp.StatusCode != 200 {
return MailRef{}, exerr.New(exerr.TypeGoogleStatuscode, "gmail returned non-200 statuscode").Int("sc", resp.StatusCode).Str("body", string(respBody)).Build()
}
var respObj MailRef
err = json.Unmarshal(respBody, &respObj)
if err != nil {
return MailRef{}, exerr.Wrap(err, "").Str("body", string(respBody)).Build()
}
return respObj, nil
}