~gjabell/mfn

7fabf0a768a9a0a13b7c65804eacccf73920d3d4 — Galen Abell 1 year, 10 days ago 3ada0f2
Convert html content to plaintext
9 files changed, 59 insertions(+), 90 deletions(-)

M README.md
M config.go
M email.go
M go.mod
M go.sum
M gotify.go
M main.go
D template.go
M webhook.go
M README.md => README.md +4 -2
@@ 29,7 29,6 @@ from = "My Server <myserver@example.com>" # email sender
username = "myuser" # email server username
password = "mypass" # email server password
server = "smtp.example.com:587" # email server url with port
html = true # true to allow html content (no escaping), defaults to false

[gotify]
title_template = """


@@ 55,7 54,6 @@ template = """
<br/>
<a href="{{ .URL }}">{{ .URL }}</a>""" # golang template string, which will be sent as the body of the webhook
endpoint = "https://webhooks.example.com" # the url of the webhook endpoint
html = true # true to allow html content (no escaping), defaults to false
```

Note that only one notifier is required.


@@ 69,3 67,7 @@ Note that only one notifier is required.
## Implementation

`mfn` uses the official Miniflux Golang client to query Miniflux for new entries. On the first run, `mfn` will determine the id of the most recent entry and store it in a plaintext file. On subsequent runs, `mfn` will only request entries posted after the latest entry, updating the latest entry id on each run. Each entry requested after the initial sync will trigger notifications based on the notifications in the config file.

## Contributing

Bug-tracker: [todo.sr.ht/~gjabell/mfn](https://todo.sr.ht/~gjabell/mfn)

M config.go => config.go +26 -27
@@ 3,6 3,7 @@ package main
import (
	"fmt"
	"reflect"
	"text/template"

	"github.com/BurntSushi/toml"
)


@@ 12,17 13,16 @@ type validator interface {
}

type EmailConfig struct {
	SubjectTemplate    *Template `toml:"-"`
	SubjectTemplateStr string    `toml:"subject_template"`
	BodyTemplate       *Template `toml:"-"`
	BodyTemplateStr    string    `toml:"body_template"`
	EmailTo            string    `toml:"to"`
	EmailFrom          string    `toml:"from"`
	Username           string    `toml:"username"`
	Password           string    `toml:"password"`
	Server             string    `toml:"server"`
	StartTLS           bool      `toml:"starttls"`
	HTML               bool      `toml:"html"`
	SubjectTemplate    *template.Template `toml:"-"`
	SubjectTemplateStr string             `toml:"subject_template"`
	BodyTemplate       *template.Template `toml:"-"`
	BodyTemplateStr    string             `toml:"body_template"`
	EmailTo            string             `toml:"to"`
	EmailFrom          string             `toml:"from"`
	Username           string             `toml:"username"`
	Password           string             `toml:"password"`
	Server             string             `toml:"server"`
	StartTLS           bool               `toml:"starttls"`
}

func (c *EmailConfig) validate() error {


@@ 41,13 41,13 @@ func (c *EmailConfig) validate() error {
	}

	// parse the templates
	subjTmpl, err := NewTemplate("subject_template", c.HTML).Parse(c.SubjectTemplateStr)
	subjTmpl, err := template.New("subject_template").Parse(c.SubjectTemplateStr)
	if err != nil {
		return err
	}
	c.SubjectTemplate = subjTmpl

	bodyTmpl, err := NewTemplate("body_template", c.HTML).Parse(c.BodyTemplateStr)
	bodyTmpl, err := template.New("body_template").Parse(c.BodyTemplateStr)
	if err != nil {
		return err
	}


@@ 57,13 57,13 @@ func (c *EmailConfig) validate() error {
}

type GotifyConfig struct {
	TitleTemplate      *Template `toml:"-"`
	TitleTemplateStr   string    `toml:"title_template"`
	MessageTemplate    *Template `toml:"-"`
	MessageTemplateStr string    `toml:"message_template"`
	Server             string    `toml:"server"`
	Token              string    `toml:"token"`
	Priority           int       `toml:"priority"`
	TitleTemplate      *template.Template `toml:"-"`
	TitleTemplateStr   string             `toml:"title_template"`
	MessageTemplate    *template.Template `toml:"-"`
	MessageTemplateStr string             `toml:"message_template"`
	Server             string             `toml:"server"`
	Token              string             `toml:"token"`
	Priority           int                `toml:"priority"`
}

func (c *GotifyConfig) validate() error {


@@ 79,13 79,13 @@ func (c *GotifyConfig) validate() error {
	}

	// parse the templates
	titleTmpl, err := NewTemplate("title_template", false).Parse(c.TitleTemplateStr)
	titleTmpl, err := template.New("title_template").Parse(c.TitleTemplateStr)
	if err != nil {
		return err
	}
	c.TitleTemplate = titleTmpl

	messageTmpl, err := NewTemplate("message_template", false).Parse(c.MessageTemplateStr)
	messageTmpl, err := template.New("message_template").Parse(c.MessageTemplateStr)
	if err != nil {
		return err
	}


@@ 95,10 95,9 @@ func (c *GotifyConfig) validate() error {
}

type WebhookConfig struct {
	Template    *Template `toml:"-"`
	TemplateStr string    `toml:"template"`
	Endpoint    string    `toml:"endpoint"`
	HTML        bool      `toml:"html"`
	Template    *template.Template `toml:"-"`
	TemplateStr string             `toml:"template"`
	Endpoint    string             `toml:"endpoint"`
}

func (c *WebhookConfig) validate() error {


@@ 112,7 111,7 @@ func (c *WebhookConfig) validate() error {
	}

	// parse the template
	tmpl, err := NewTemplate("webhook", c.HTML).Parse(c.TemplateStr)
	tmpl, err := template.New("webhook").Parse(c.TemplateStr)
	if err != nil {
		return err
	}

M email.go => email.go +3 -2
@@ 7,6 7,7 @@ import (
	"io"
	gomail "net/mail"
	"strings"
	"text/template"
	"time"

	"github.com/emersion/go-message/mail"


@@ 16,8 17,8 @@ import (
)

type Email struct {
	subjectTemplate *Template
	bodyTemplate    *Template
	subjectTemplate *template.Template
	bodyTemplate    *template.Template
	emailTo         string
	emailFrom       string
	username        string

M go.mod => go.mod +3 -0
@@ 9,5 9,8 @@ require (
	github.com/emersion/go-sasl v0.0.0-20191210011802-430746ea8b9b
	github.com/emersion/go-smtp v0.12.1
	github.com/mattn/go-sqlite3 v2.0.2+incompatible
	github.com/olekukonko/tablewriter v0.0.4 // indirect
	github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
	jaytaylor.com/html2text v0.0.0-20200412013138-3577fbdbcff7
	miniflux.app v0.0.0-20200125042750-15727f716a83
)

M go.sum => go.sum +9 -0
@@ 28,11 28,17 @@ github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
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/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs=
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/mattn/go-sqlite3 v2.0.2+incompatible h1:qzw9c2GNT8UFrgWNDhCTqRqYUSmu/Dav/9Z58LGpk7U=
github.com/mattn/go-sqlite3 v2.0.2+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
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/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
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.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=


@@ 50,6 56,7 @@ golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20191112182307-2180aed22343 h1:00ohfJ4K98s3m6BGUoBd8nyfp4Yl0GoIKvw5abItTjI=
golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=


@@ 65,5 72,7 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.2/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
jaytaylor.com/html2text v0.0.0-20200412013138-3577fbdbcff7 h1:mub0MmFLOn8XLikZOAhgLD1kXJq8jgftSrrv7m00xFo=
jaytaylor.com/html2text v0.0.0-20200412013138-3577fbdbcff7/go.mod h1:OxvTsCwKosqQ1q7B+8FwXqg4rKZ/UG9dUW+g/VL2xH4=
miniflux.app v0.0.0-20200125042750-15727f716a83 h1:dhLBkGl+VAP+R4iQvE30xqh8xjBFs4ErypWhq4I0dXA=
miniflux.app v0.0.0-20200125042750-15727f716a83/go.mod h1:BqPkLb01QU9bhx4/rOlLomYGfhpHhexBsEsAx8uDpCw=

M gotify.go => gotify.go +3 -2
@@ 5,13 5,14 @@ import (
	"net/http"
	"net/url"
	"strconv"
	"text/template"

	mf "miniflux.app/client"
)

type Gotify struct {
	titleTemplate   *Template
	messageTemplate *Template
	titleTemplate   *template.Template
	messageTemplate *template.Template
	server          string
	token           string
	priority        int

M main.go => main.go +9 -0
@@ 9,6 9,7 @@ import (
	"strings"

	"git.sr.ht/~sircmpwn/getopt"
	"jaytaylor.com/html2text"

	mf "miniflux.app/client"
)


@@ 170,6 171,14 @@ func (mfn *Mfn) execNotifications() error {
			continue
		}

		// always convert content to plaintext
		plainContent, err := html2text.FromString(e.Content,
			html2text.Options{PrettyTables: true})
		if err != nil {
			return err
		}
		e.Content = plainContent

		for name, notifier := range mfn.notifiers {
			if err := notifier.Notify(e); err != nil {
				log.Printf("Failed to send %s notification: %s\n", name, err)

D template.go => template.go +0 -56
@@ 1,56 0,0 @@
package main

import (
	html "html/template"
	"io"
	text "text/template"
)

// Templater delegates calls to either text.Template or html.Template
// type Templater interface {
// 	Parse(string) (*Template, error)
// 	Execute(io.Writer, interface{}) error
// }

// Template is a wrapper around text and html templates
type Template struct {
	name         string
	html         bool
	textTemplate *text.Template
	htmlTemplate *html.Template
}

func NewTemplate(name string, html bool) *Template {
	return &Template{
		name: name,
		html: html,
	}
}

func (t *Template) Parse(template string) (*Template, error) {
	if t.html {
		tmpl, err := text.New(t.name).Parse(template)
		if err != nil {
			return nil, err
		}

		t.textTemplate = tmpl
		return t, nil
	}

	tmpl, err := html.New(t.name).Parse(template)
	if err != nil {
		return nil, err
	}

	t.htmlTemplate = tmpl
	return t, nil
}

func (t *Template) Execute(wr io.Writer, data interface{}) error {
	if t.html {
		return t.textTemplate.Execute(wr, data)
	}

	return t.htmlTemplate.Execute(wr, data)
}

M webhook.go => webhook.go +2 -1
@@ 6,12 6,13 @@ import (
	"io/ioutil"
	"log"
	"net/http"
	"text/template"

	mf "miniflux.app/client"
)

type Webhook struct {
	template *Template
	template *template.Template
	endpoint string
}