From 5e5d848b1324cc5ea3991276f2a0750883e5aab0 Mon Sep 17 00:00:00 2001 From: René 'Necoro' Neumann Date: Sun, 10 May 2020 22:07:17 +0200 Subject: Text part in emails --- go.mod | 3 +++ go.sum | 13 ++++++---- internal/feed/item.go | 1 + internal/feed/mail.go | 53 +++++++++++++++++++++++++------------- internal/feed/template/html.tpl.go | 4 +-- internal/feed/template/template.go | 32 +++++++++++++++++------ internal/feed/template/text.tpl.go | 42 ++++++++++++++++++++++++++++++ 7 files changed, 115 insertions(+), 33 deletions(-) create mode 100644 internal/feed/template/text.tpl.go diff --git a/go.mod b/go.mod index 6d95cb9..05b3ca0 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,10 @@ require ( github.com/gabriel-vasile/mimetype v1.1.0 github.com/google/go-cmp v0.4.0 github.com/google/uuid v1.1.1 + github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7 github.com/mmcdole/gofeed v1.0.0-beta2.0.20200331235650-4298e4366be3 + github.com/olekukonko/tablewriter v0.0.4 // indirect + github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect golang.org/x/net v0.0.0-20200506145744-7e3656a0809f gopkg.in/yaml.v3 v3.0.0-20200506231410-2ff61e1afc86 ) diff --git a/go.sum b/go.sum index c2ed546..d28b399 100644 --- a/go.sum +++ b/go.sum @@ -15,7 +15,6 @@ github.com/emersion/go-imap v1.0.4/go.mod h1:yKASt+C3ZiDAiCSssxg9caIckWF/JG7ZQTO github.com/emersion/go-imap-uidplus v0.0.0-20200503180755-e75854c361e9 h1:2Kbw3iu7fFeSso6RWIArVNUj1VGG2PvjetnPUW7bnis= github.com/emersion/go-imap-uidplus v0.0.0-20200503180755-e75854c361e9/go.mod h1:GfiSiw/du0221I3Cf4F0DqX3Bv5Xe580gIIATrQtnJg= github.com/emersion/go-message v0.11.1/go.mod h1:C4jnca5HOTo4bGN9YdqNQM9sITuT3Y0K6bSUw9RklvY= -github.com/emersion/go-message v0.11.2/go.mod h1:C4jnca5HOTo4bGN9YdqNQM9sITuT3Y0K6bSUw9RklvY= github.com/emersion/go-message v0.11.3-0.20200422153910-8c6ac6b57e3d h1:GJ4ATGxKc/zmU4LIgw4LiNc1W+MQii/hMM+gWRDaHLU= github.com/emersion/go-message v0.11.3-0.20200422153910-8c6ac6b57e3d/go.mod h1:C4jnca5HOTo4bGN9YdqNQM9sITuT3Y0K6bSUw9RklvY= github.com/emersion/go-sasl v0.0.0-20191210011802-430746ea8b9b h1:uhWtEWBHgop1rqEk2klKaxPAkVDCXexai6hSuRQ7Nvs= @@ -28,14 +27,22 @@ github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7 h1:g0fAGBisHaEQ0TRq1iBvemFRf+8AEWEmBESSiWB3Vsc= +github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk= github.com/martinlindhe/base36 v1.0.0 h1:eYsumTah144C0A8P1T/AVSUk5ZoLnhfYFM3OGQxB52A= github.com/martinlindhe/base36 v1.0.0/go.mod h1:+AtEs8xrBpCeYgSLoY/aJ6Wf37jtBuR0s35750M27+8= +github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54= +github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mmcdole/gofeed v1.0.0-beta2.0.20200331235650-4298e4366be3 h1:Wy+ed15cpwtLcJYNiO4Z0wmjZHpNj4q0RsGbsoxWSMA= github.com/mmcdole/gofeed v1.0.0-beta2.0.20200331235650-4298e4366be3/go.mod h1:tkVcyzS3qVMlQrQxJoEH1hkTiuo9a8emDzkMi7TZBu0= github.com/mmcdole/goxpp v0.0.0-20181012175147-0068e33feabf h1:sWGE2v+hO0Nd4yFU/S/mDBM5plIU8v/Qhfz41hkDIAI= github.com/mmcdole/goxpp v0.0.0-20181012175147-0068e33feabf/go.mod h1:pasqhqstspkosTneA62Nc+2p9SOBBYAPbnmRRWPQ0V8= +github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8= +github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo= +github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -47,8 +54,6 @@ golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20181220203305-927f97764cc3 h1:eH6Eip3UpmR+yM/qI9Ijluzb1bNv/cAU/n+6l8tRSis= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5 h1:WQ8q63x+f/zpC8Ac1s9wLElVoHhm32p6tudrU72n1QA= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f h1:QBjCr1Fz5kw158VqdE9JfI9cJnl/ymnJWAdMuinqL7Y= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -61,7 +66,5 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IV golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200506231410-2ff61e1afc86 h1:OfFoIUYv/me30yv7XlMy4F9RJw8DEm8WQ6QG1Ph4bH0= gopkg.in/yaml.v3 v3.0.0-20200506231410-2ff61e1afc86/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/feed/item.go b/internal/feed/item.go index 52915bf..3cb9089 100644 --- a/internal/feed/item.go +++ b/internal/feed/item.go @@ -22,6 +22,7 @@ type item struct { Feed *gofeed.Feed feed *Feed Body string + TextBody string updateOnly bool reasons []string images []feedImage diff --git a/internal/feed/mail.go b/internal/feed/mail.go index b70ce7d..4cdc57a 100644 --- a/internal/feed/mail.go +++ b/internal/feed/mail.go @@ -4,7 +4,6 @@ import ( "bytes" "encoding/base64" "fmt" - "io" "io/ioutil" "mime" "net/url" @@ -16,6 +15,8 @@ import ( "github.com/emersion/go-message" "github.com/emersion/go-message/mail" "github.com/gabriel-vasile/mimetype" + "github.com/jaytaylor/html2text" + "golang.org/x/net/html" "github.com/Necoro/feed2imap-go/internal/feed/template" "github.com/Necoro/feed2imap-go/internal/http" @@ -48,10 +49,6 @@ func (item *item) toAddress() []*mail.Address { return address(item.feed.Name, item.defaultEmail()) } -func (item *item) writeHtml(writer io.Writer) error { - return template.Html.Execute(writer, item) -} - func (item *item) buildHeader() message.Header { var h mail.Header h.SetContentType("multipart/alternative", nil) @@ -87,9 +84,9 @@ func (item *item) buildHeader() message.Header { return h.Header } -func (item *item) writeHtmlPart(w *message.Writer) error { +func (item *item) writeContentPart(w *message.Writer, typ string, tpl template.Template) error { var ih message.Header - ih.SetContentType("text/html", map[string]string{"charset": "utf-8"}) + ih.SetContentType("text/"+typ, map[string]string{"charset": "utf-8"}) ih.SetContentDisposition("inline", nil) ih.Set("Content-Transfer-Encoding", "8bit") @@ -99,13 +96,21 @@ func (item *item) writeHtmlPart(w *message.Writer) error { } defer partW.Close() - if err = item.writeHtml(w); err != nil { - return fmt.Errorf("writing html part: %w", err) + if err = tpl.Execute(w, item); err != nil { + return fmt.Errorf("writing %s part: %w", typ, err) } return nil } +func (item *item) writeTextPart(w *message.Writer) error { + return item.writeContentPart(w, "plain", template.Text) +} + +func (item *item) writeHtmlPart(w *message.Writer) error { + return item.writeContentPart(w, "html", template.Html) +} + func (img *feedImage) writeImagePart(w *message.Writer, cid string) error { var ih message.Header ih.SetContentType(img.mime, nil) @@ -128,6 +133,7 @@ func (img *feedImage) writeImagePart(w *message.Writer, cid string) error { func (item *item) writeToBuffer(b *bytes.Buffer) error { h := item.buildHeader() + item.buildBody() writer, err := message.CreateWriter(b, h) if err != nil { @@ -135,9 +141,13 @@ func (item *item) writeToBuffer(b *bytes.Buffer) error { } defer writer.Close() - if item.feed.Global.WithPartHtml() { - item.buildBody() + if item.feed.Global.WithPartText() { + if err = item.writeTextPart(writer); err != nil { + return err + } + } + if item.feed.Global.WithPartHtml() { var relWriter *message.Writer if len(item.images) > 0 { var rh message.Header @@ -160,10 +170,9 @@ func (item *item) writeToBuffer(b *bytes.Buffer) error { return err } } - - item.clearImages() // safe memory } + item.clearImages() // safe memory return nil } @@ -248,19 +257,27 @@ func (item *item) buildBody() { } body := getBody(item.Content, item.Description, feed.Body) - - if !feed.InclImages { + bodyNode, err := html.Parse(strings.NewReader(body)) + if err != nil { + log.Errorf("Feed %s: Item %s: Error while parsing html: %s", feed.Name, item.Link, err) item.Body = body + item.TextBody = body return } - doc, err := goquery.NewDocumentFromReader(strings.NewReader(body)) - if err != nil { - log.Errorf("Feed %s: Item %s: Error while parsing html content: %s", feed.Name, item.Link, err) + if feed.Global.WithPartText() { + if item.TextBody, err = html2text.FromHTMLNode(bodyNode); err != nil { + log.Errorf("Feed %s: Item %s: Error while converting html to text: %s", feed.Name, item.Link, err) + } + } + + if !feed.InclImages || !feed.Global.WithPartHtml() || err != nil { item.Body = body return } + doc := goquery.NewDocumentFromNode(bodyNode) + doneAnything := true nodes := doc.Find("img") nodes.Each(func(i int, selection *goquery.Selection) { diff --git a/internal/feed/template/html.tpl.go b/internal/feed/template/html.tpl.go index 4626188..be84030 100644 --- a/internal/feed/template/html.tpl.go +++ b/internal/feed/template/html.tpl.go @@ -1,9 +1,9 @@ package template -var Html = fromString("Feed", feedTpl) +var Html = fromString("Feed", htmlTpl, true) //noinspection HtmlDeprecatedAttribute,HtmlUnknownTarget -const feedTpl = `{{- /*gotype:github.com/Necoro/feed2imap-go/internal/feed.feeditem*/ -}} +const htmlTpl = `{{- /*gotype:github.com/Necoro/feed2imap-go/internal/feed.feeditem*/ -}} {{define "bottomLine"}} {{if .content}} diff --git a/internal/feed/template/template.go b/internal/feed/template/template.go index 5a30c56..d8eb850 100644 --- a/internal/feed/template/template.go +++ b/internal/feed/template/template.go @@ -2,13 +2,26 @@ package template import ( "fmt" - "html/template" + html "html/template" + "io" "strconv" "strings" + text "text/template" "github.com/Necoro/feed2imap-go/pkg/log" ) +type Template interface { + Execute(wr io.Writer, data interface{}) error +} + +func must(t Template, err error) Template { + if err != nil { + panic(err) + } + return t +} + func dict(v ...interface{}) map[string]interface{} { dict := make(map[string]interface{}) lenv := len(v) @@ -53,19 +66,22 @@ func byteCount(str string) string { return fmt.Sprintf("%.1f %cB", float64(b)/float64(div), "KMGTPE"[exp]) } -func html(s string) template.HTML { - return template.HTML(s) +func _html(s string) html.HTML { + return html.HTML(s) } -var funcMap = template.FuncMap{ +var funcMap = html.FuncMap{ "dict": dict, "join": join, "lastUrlPart": lastUrlPart, "byteCount": byteCount, - "html": html, + "html": _html, } -func fromString(name, templateStr string) *template.Template { - tpl := template.New(name).Funcs(funcMap) - return template.Must(tpl.Parse(templateStr)) +func fromString(name, templateStr string, useHtml bool) Template { + if useHtml { + return must(html.New(name).Funcs(funcMap).Parse(templateStr)) + } else { + return must(text.New(name).Funcs(text.FuncMap(funcMap)).Parse(templateStr)) + } } diff --git a/internal/feed/template/text.tpl.go b/internal/feed/template/text.tpl.go new file mode 100644 index 0000000..0c10334 --- /dev/null +++ b/internal/feed/template/text.tpl.go @@ -0,0 +1,42 @@ +package template + +var Text = fromString("Feed", textTpl, false) + +//noinspection HtmlDeprecatedAttribute,HtmlUnknownTarget +const textTpl = `{{- /*gotype:github.com/Necoro/feed2imap-go/internal/feed.feeditem*/ -}} +{{- with .Item.Link -}} +<{{.}}> + +{{ end -}} +{{- with .TextBody -}} +{{.}} +{{ end -}} +{{- with .Item.Enclosures -}} +Files: + {{- range . -}} + {{- .URL}} ({{with .Length}}{{. | byteCount}}, {{end}}{{.Type}}) + {{- end -}} +{{- end}} +-- +Feed: {{ with .Feed.Title -}}{{.}}{{- end }} +{{ with .Feed.Link -}} + <{{.}}> +{{end -}} +Item: {{ with .Item.Title -}} + {{.}} +{{- end }} +{{ with .Item.Link -}} + <{{.}}> +{{end -}} +{{ with .Date -}} + Date: {{.}} +{{ end -}} +{{ with .Creator -}} + Author: {{.}} +{{ end -}} +{{ with (join ", " .Categories) -}} + Filed under: {{.}} +{{ end -}} +{{ with .FeedLink -}} + Feed-Link: {{.}} +{{ end -}}` -- cgit v1.2.3-54-g00ecf