diff --git a/exerr/data.go b/exerr/data.go index a5c2002..5d6b0b7 100644 --- a/exerr/data.go +++ b/exerr/data.go @@ -59,6 +59,9 @@ var ( TypeMarshalEntityID = NewType("MARSHAL_ENTITY_ID", langext.Ptr(400)) TypeInvalidCSID = NewType("INVALID_CSID", langext.Ptr(400)) + TypeGoogleStatuscode = NewType("GOOGLE_STATUSCODE", langext.Ptr(400)) + TypeGoogleResponse = NewType("GOOGLE_RESPONSE", langext.Ptr(400)) + TypeUnauthorized = NewType("UNAUTHORIZED", langext.Ptr(401)) TypeAuthFailed = NewType("AUTH_FAILED", langext.Ptr(401)) diff --git a/googleapi/attachment.go b/googleapi/attachment.go index 344a4bb..0c4ca45 100644 --- a/googleapi/attachment.go +++ b/googleapi/attachment.go @@ -7,35 +7,40 @@ import ( type MailAttachment struct { IsInline bool - ContentType *string - Filename *string + ContentType string + Filename string Data []byte } func (a MailAttachment) dump() []string { res := make([]string, 0, 4) - if a.ContentType != nil { - res = append(res, "Content-Type: "+*a.ContentType+"; charset=UTF-8") + if a.ContentType != "" { + res = append(res, "Content-Type: "+a.ContentType+"; charset=UTF-8") } res = append(res, "Content-Transfer-Encoding: base64") if a.IsInline { - if a.Filename != nil { - res = append(res, fmt.Sprintf("Content-Disposition: inline;filename=\"%s\"", *a.Filename)) + if a.Filename != "" { + res = append(res, fmt.Sprintf("Content-Disposition: inline;filename=\"%s\"", a.Filename)) } else { res = append(res, "Content-Disposition: inline") } } else { - if a.Filename != nil { - res = append(res, fmt.Sprintf("Content-Disposition: attachment;filename=\"%s\"", *a.Filename)) + if a.Filename != "" { + res = append(res, fmt.Sprintf("Content-Disposition: attachment;filename=\"%s\"", a.Filename)) } else { res = append(res, "Content-Disposition: attachment") } } - res = append(res, base64.URLEncoding.EncodeToString(a.Data)) + b64 := base64.StdEncoding.EncodeToString(a.Data) + for i := 0; i < len(b64); i += 80 { + res = append(res, b64[i:min(i+80, len(b64))]) + } + + res = append(res) return res } diff --git a/googleapi/body.go b/googleapi/body.go index c6b48a2..c927965 100644 --- a/googleapi/body.go +++ b/googleapi/body.go @@ -1,6 +1,6 @@ package googleapi type MailBody struct { - Plain *string - HTML *string + Plain string + HTML string } diff --git a/googleapi/mimeMessage.go b/googleapi/mimeMessage.go new file mode 100644 index 0000000..a5a4d7b --- /dev/null +++ b/googleapi/mimeMessage.go @@ -0,0 +1,224 @@ +package googleapi + +import ( + "gogs.mikescher.com/BlackForestBytes/goext/langext" + "mime" + "strings" + "time" +) + +// https://datatracker.ietf.org/doc/html/rfc2822 +func encodeMimeMail(from string, recipients []string, cc []string, bcc []string, subject string, body MailBody, attachments []MailAttachment) string { + + data := make([]string, 0, 32) + + data = append(data, "Date: "+time.Now().Format(time.RFC1123Z)) + data = append(data, "MIME-Version: 1.0") + 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)) + + hasInlineAttachments := langext.ArrAny(attachments, func(v MailAttachment) bool { return v.IsInline }) + hasNormalAttachments := langext.ArrAny(attachments, func(v MailAttachment) bool { return !v.IsInline }) + hasPlain := body.Plain != "" + hasHTML := body.HTML != "" + + 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 && hasNormalAttachments { + // "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, dumpMailBody(body, hasInlineAttachments, hasNormalAttachments, 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(normalAttachments)-1 { + data = append(data, "") + } + } + + data = append(data, "--"+mixedBoundary+"--") + + } else if hasNormalAttachments { + // "mixed" + + data = append(data, "Content-Type: multipart/mixed; boundary="+mixedBoundary) + data = append(data, "") + + data = append(data, dumpMailBody(body, hasInlineAttachments, hasNormalAttachments, mixedBoundary, altBoundary)...) + if hasPlain && hasHTML { + data = append(data, "") + } + + for i, attachment := range normalAttachments { + data = append(data, "--"+mixedBoundary) + data = append(data, attachment.dump()...) + + if i < len(normalAttachments)-1 { + data = append(data, "") + } + } + + data = append(data, "--"+mixedBoundary+"--") + + } else if hasInlineAttachments { + // "related" + + data = append(data, "Content-Type: multipart/related; boundary="+relatedBoundary) + data = append(data, "") + + data = append(data, dumpMailBody(body, hasInlineAttachments, hasNormalAttachments, 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+"--") + + } else if hasPlain && hasHTML { + // "alternative" + + data = append(data, "Content-Type: multipart/alternative; boundary="+altBoundary) + data = append(data, "") + + data = append(data, dumpMailBody(body, hasInlineAttachments, hasNormalAttachments, altBoundary, altBoundary)...) + data = append(data, "") + + data = append(data, "--"+altBoundary+"--") + + } else if hasPlain { + // "plain" + + data = append(data, "Content-Type: text/plain; charset=UTF-8") + data = append(data, "Content-Transfer-Encoding: 7bit") + data = append(data, "") + data = append(data, body.Plain) + + } else if hasHTML { + // "plain" + + data = append(data, "Content-Type: text/html; charset=UTF-8") + data = append(data, "Content-Transfer-Encoding: 7bit") + data = append(data, "") + data = append(data, body.HTML) + + } else { + // "empty??" + + } + + return strings.Join(data, "\r\n") +} + +func dumpMailBody(body MailBody, hasInlineAttachments bool, hasNormalAttachments bool, boundary string, boundaryAlt string) []string { + + if body.HTML != "" && body.Plain != "" && !hasInlineAttachments && hasNormalAttachments { + data := make([]string, 0, 16) + data = append(data, "--"+boundary) + data = append(data, "Content-Type: multipart/alternative; boundary="+boundaryAlt) + data = append(data, "") + data = append(data, "--"+boundaryAlt) + data = append(data, "Content-Type: text/plain; charset=UTF-8") + data = append(data, "Content-Transfer-Encoding: 7bit") + data = append(data, "") + data = append(data, body.Plain) + data = append(data, "") + data = append(data, "--"+boundaryAlt) + data = append(data, "Content-Type: text/html; charset=UTF-8") + data = append(data, "Content-Transfer-Encoding: 7bit") + data = append(data, "") + data = append(data, body.HTML) + data = append(data, "") + data = append(data, "--"+boundaryAlt+"--") + return data + } + + if body.HTML != "" && body.Plain != "" && hasInlineAttachments { + data := make([]string, 0, 2) + data = append(data, "--"+boundary) + data = append(data, body.HTML) + return data + } + + if body.HTML != "" && body.Plain != "" { + data := make([]string, 0, 8) + data = append(data, "--"+boundary) + data = append(data, "Content-Type: text/plain; charset=UTF-8") + data = append(data, "Content-Transfer-Encoding: 7bit") + data = append(data, "") + data = append(data, body.Plain) + data = append(data, "") + data = append(data, "--"+boundary) + data = append(data, "Content-Type: text/html; charset=UTF-8") + data = append(data, "Content-Transfer-Encoding: 7bit") + data = append(data, "") + data = append(data, body.HTML) + return data + } + + if body.HTML != "" { + data := make([]string, 0, 2) + data = append(data, "--"+boundary) + data = append(data, "Content-Type: text/html; charset=UTF-8") + data = append(data, "Content-Transfer-Encoding: 7bit") + data = append(data, "") + data = append(data, body.HTML) + return data + } + + if body.Plain != "" { + data := make([]string, 0, 2) + data = append(data, "--"+boundary) + data = append(data, "Content-Type: text/plain; charset=UTF-8") + data = append(data, "Content-Transfer-Encoding: 7bit") + data = append(data, "") + data = append(data, body.Plain) + return data + } + + data := make([]string, 0, 16) + data = append(data, "--"+boundary) + data = append(data, "Content-Type: text/plain; charset=UTF-8") + data = append(data, "Content-Transfer-Encoding: 7bit") + data = append(data, "") + data = append(data, "") // no content ?!? + return data +} diff --git a/googleapi/mimeMessage_test.go b/googleapi/mimeMessage_test.go new file mode 100644 index 0000000..9ed1ca7 --- /dev/null +++ b/googleapi/mimeMessage_test.go @@ -0,0 +1,77 @@ +package googleapi + +import ( + "fmt" + "gogs.mikescher.com/BlackForestBytes/goext/tst" + "os" + "testing" +) + +func TestEncodeMimeMail(t *testing.T) { + + mail := encodeMimeMail( + "noreply@heydyno.de", + []string{"trash@mikescher.de"}, + nil, + nil, + "Hello Test Mail", + MailBody{Plain: "Plain Text"}, + nil) + + fmt.Printf("%s\n\n", mail) +} + +func TestEncodeMimeMail2(t *testing.T) { + + mail := encodeMimeMail( + "noreply@heydyno.de", + []string{"trash@mikescher.de"}, + nil, + nil, + "Hello Test Mail (alternative)", + MailBody{ + Plain: "Plain Text", + HTML: "Non Plain Text", + }, + nil) + + fmt.Printf("%s\n\n", mail) +} + +func TestEncodeMimeMail3(t *testing.T) { + + mail := encodeMimeMail( + "noreply@heydyno.de", + []string{"trash@mikescher.de"}, + nil, + nil, + "Hello Test Mail (alternative)", + MailBody{ + HTML: "Non Plain Text", + }, + []MailAttachment{ + {Data: []byte("HelloWorld"), Filename: "test.txt", IsInline: false, ContentType: "text/plain"}, + }) + + fmt.Printf("%s\n\n", mail) +} + +func TestEncodeMimeMail4(t *testing.T) { + + b := tst.Must(os.ReadFile("/home/mike/Pictures/Screenshot_20220706_190205.png"))(t) + + mail := encodeMimeMail( + "noreply@heydyno.de", + []string{"trash@mikescher.de"}, + nil, + nil, + "Hello Test Mail (inline)", + MailBody{ + HTML: "Non Plain Text", + }, + []MailAttachment{ + {Data: b, Filename: "img.png", IsInline: true, ContentType: "image/png"}, + }) + + fmt.Printf("%s\n\n", mail) +} diff --git a/googleapi/oAuth.go b/googleapi/oAuth.go index 96dc2d6..fc3c051 100644 --- a/googleapi/oAuth.go +++ b/googleapi/oAuth.go @@ -2,6 +2,8 @@ package googleapi import ( "encoding/json" + "fmt" + "gogs.mikescher.com/BlackForestBytes/goext/exerr" "gogs.mikescher.com/BlackForestBytes/goext/langext" "gogs.mikescher.com/BlackForestBytes/goext/timeext" "io" @@ -11,6 +13,7 @@ import ( ) type GoogleOAuth interface { + AccessToken() (string, error) } type oauth struct { @@ -41,16 +44,17 @@ func (c *oauth) AccessToken() (string, error) { httpclient := http.Client{} - req, err := http.NewRequest(http.MethodPost, "https://oauth2.googleapis.com/token", nil) + url := fmt.Sprintf("https://oauth2.googleapis.com/token?client_id=%s&client_secret=%s&grant_type=%s&refresh_token=%s", + c.clientID, + c.clientSecret, + "refresh_token", + c.refreshToken) + + req, err := http.NewRequest(http.MethodPost, url, nil) if err != nil { return "", err } - req.URL.Query().Add("client_id", c.clientID) - req.URL.Query().Add("client_secret", c.clientSecret) - req.URL.Query().Add("grant_type", "refresh_token") - req.URL.Query().Add("refresh_token", c.refreshToken) - reqStartTime := time.Now() res, err := httpclient.Do(req) @@ -74,6 +78,10 @@ func (c *oauth) AccessToken() (string, error) { return "", err } + if r.ExpiresIn == 0 || r.AccessToken == "" { + return "", exerr.New(exerr.TypeGoogleResponse, "google oauth returned no response").Str("body", string(data)).Build() + } + c.lock.Lock() c.expiryDate = langext.Ptr(reqStartTime.Add(timeext.FromSeconds(r.ExpiresIn - 10))) c.accessToken = langext.Ptr(r.AccessToken) diff --git a/googleapi/sendMail.go b/googleapi/sendMail.go index 73de315..db03601 100644 --- a/googleapi/sendMail.go +++ b/googleapi/sendMail.go @@ -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 } diff --git a/googleapi/sendMail_test.go b/googleapi/sendMail_test.go new file mode 100644 index 0000000..43c6ee8 --- /dev/null +++ b/googleapi/sendMail_test.go @@ -0,0 +1,139 @@ +package googleapi + +import ( + "context" + "fmt" + "gogs.mikescher.com/BlackForestBytes/goext/exerr" + "gogs.mikescher.com/BlackForestBytes/goext/langext" + "gogs.mikescher.com/BlackForestBytes/goext/tst" + "os" + "testing" +) + +func TestMain(m *testing.M) { + if !exerr.Initialized() { + exerr.Init(exerr.ErrorPackageConfigInit{ZeroLogErrTraces: langext.PFalse, ZeroLogAllTraces: langext.PFalse}) + } + os.Exit(m.Run()) +} + +func TestSendMail1(t *testing.T) { + auth := NewGoogleOAuth( + "554617284247-8di0j6s5dcmlk4lmk4hdf9kdn8scss54.apps.googleusercontent.com", + "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", + "1//09rEjBe2Ua7SuCgYIARAAGAkSNwF-L9IrIRPdlM4Zb8w8gN4ccZX4srDZmiWtxxDH1kC5c_ApdaM3jK0fJ4eZFRhRt2vvHqiNs4g") + + ctx := context.Background() + + gclient := NewGoogleClient(auth) + + mail, err := gclient.SendMail( + ctx, + "noreply@heydyno.de", + []string{"trash@mikescher.de"}, + nil, + nil, + "Hello Test Mail", + MailBody{Plain: "Plain Text"}, + nil) + + tst.AssertNoErr(t, err) + + fmt.Printf("mail.ID := %s\n", mail.ID) + fmt.Printf("mail.ThreadID := %s\n", mail.ThreadID) + fmt.Printf("mail.LabelIDs := %v\n", mail.LabelIDs) +} + +func TestSendMail2(t *testing.T) { + auth := NewGoogleOAuth( + "554617284247-8di0j6s5dcmlk4lmk4hdf9kdn8scss54.apps.googleusercontent.com", + "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", + "1//09rEjBe2Ua7SuCgYIARAAGAkSNwF-L9IrIRPdlM4Zb8w8gN4ccZX4srDZmiWtxxDH1kC5c_ApdaM3jK0fJ4eZFRhRt2vvHqiNs4g") + + ctx := context.Background() + + gclient := NewGoogleClient(auth) + + mail, err := gclient.SendMail( + ctx, + "noreply@heydyno.de", + []string{"trash@mikescher.de"}, + nil, + nil, + "Hello Test Mail (alternative)", + MailBody{ + Plain: "Plain Text", + HTML: "Non Plain Text", + }, + nil) + + tst.AssertNoErr(t, err) + + fmt.Printf("mail.ID := %s\n", mail.ID) + fmt.Printf("mail.ThreadID := %s\n", mail.ThreadID) + fmt.Printf("mail.LabelIDs := %v\n", mail.LabelIDs) +} + +func TestSendMail3(t *testing.T) { + auth := NewGoogleOAuth( + "554617284247-8di0j6s5dcmlk4lmk4hdf9kdn8scss54.apps.googleusercontent.com", + "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", + "1//09rEjBe2Ua7SuCgYIARAAGAkSNwF-L9IrIRPdlM4Zb8w8gN4ccZX4srDZmiWtxxDH1kC5c_ApdaM3jK0fJ4eZFRhRt2vvHqiNs4g") + + ctx := context.Background() + + gclient := NewGoogleClient(auth) + + mail, err := gclient.SendMail( + ctx, + "noreply@heydyno.de", + []string{"trash@mikescher.de"}, + nil, + nil, + "Hello Test Mail (attach)", + MailBody{ + HTML: "Non Plain Text", + }, + []MailAttachment{ + {Data: []byte("HelloWorld"), Filename: "test.txt", IsInline: false, ContentType: "text/plain"}, + }) + + tst.AssertNoErr(t, err) + + fmt.Printf("mail.ID := %s\n", mail.ID) + fmt.Printf("mail.ThreadID := %s\n", mail.ThreadID) + fmt.Printf("mail.LabelIDs := %v\n", mail.LabelIDs) +} + +func TestSendMail4(t *testing.T) { + auth := NewGoogleOAuth( + "554617284247-8di0j6s5dcmlk4lmk4hdf9kdn8scss54.apps.googleusercontent.com", + "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", + "1//09rEjBe2Ua7SuCgYIARAAGAkSNwF-L9IrIRPdlM4Zb8w8gN4ccZX4srDZmiWtxxDH1kC5c_ApdaM3jK0fJ4eZFRhRt2vvHqiNs4g") + + ctx := context.Background() + + gclient := NewGoogleClient(auth) + + b := tst.Must(os.ReadFile("/home/mike/Pictures/Screenshot_20220706_190205.png"))(t) + + mail, err := gclient.SendMail( + ctx, + "noreply@heydyno.de", + []string{"trash@mikescher.de"}, + nil, + nil, + "Hello Test Mail (inline)", + MailBody{ + HTML: "Non Plain Text", + }, + []MailAttachment{ + {Data: b, Filename: "img.png", IsInline: true, ContentType: "image/png"}, + }) + + tst.AssertNoErr(t, err) + + fmt.Printf("mail.ID := %s\n", mail.ID) + fmt.Printf("mail.ThreadID := %s\n", mail.ThreadID) + fmt.Printf("mail.LabelIDs := %v\n", mail.LabelIDs) +} diff --git a/googleapi/service.go b/googleapi/service.go index b3a90bb..c4501a7 100644 --- a/googleapi/service.go +++ b/googleapi/service.go @@ -1,14 +1,22 @@ package googleapi +import ( + "context" + "net/http" +) + type GoogleClient interface { + SendMail(ctx context.Context, from string, recipients []string, cc []string, bcc []string, subject string, body MailBody, attachments []MailAttachment) (MailRef, error) } type client struct { oauth GoogleOAuth + http http.Client } func NewGoogleClient(oauth GoogleOAuth) GoogleClient { return &client{ oauth: oauth, + http: http.Client{}, } }