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 --- 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 ++++++++++++++++++++++++++++++ 5 files changed, 104 insertions(+), 28 deletions(-) create mode 100644 internal/feed/template/text.tpl.go (limited to 'internal/feed') 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-70-g09d2