Last active
January 6, 2023 13:29
-
-
Save toshiharu/362eb3b679a3747097f322ba11f2b2b3 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package main | |
| import ( | |
| "fmt" | |
| "log" | |
| "flag" | |
| "os" | |
| yaml "gopkg.in/yaml.v2" | |
| "path/filepath" | |
| "os/user" | |
| "database/sql" | |
| _ "github.com/mattn/go-sqlite3" | |
| "github.com/mmcdole/gofeed" | |
| "time" | |
| "reflect" // https://stackoverflow.com/questions/22367337/last-item-in-a-template-range | |
| "text/template" | |
| "bytes" | |
| "net/smtp" | |
| "github.com/domodwyer/mailyak" | |
| ) | |
| var Logger *log.Logger | |
| type Config struct { | |
| MailTo string `yaml:"mailto"` | |
| MailFrom string `yaml:"mailfrom"` | |
| UserName string `yaml:"username"` | |
| Password string `yaml:"password"` | |
| Feed []string `yaml:"feed"` | |
| } | |
| const tmplStrFmt = ` | |
| <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> | |
| <html> | |
| <head> | |
| <meta http-equiv="Content-Type" content="text/html;charset=utf-8" /> | |
| </head> | |
| <body> | |
| {{ range $i, $e := . -}} | |
| <div> | |
| <div> | |
| <strong style="font-weight:bold;font-size:1.2em">{{ $e.Title }}</strong><br /> | |
| Link: <a href="{{ $e.Link }}">{{ $e.Link }}</a><br /> | |
| </div> | |
| {{ if $e.Content }}<div style="padding: 1em 0">{{ $e.Content }}</div>{{ else }}{{ if $e.Description }}<div style="padding: 1em 0">{{ $e.Description }}</div>{{ else }}<br />{{ end }}{{ end }} | |
| </div> | |
| <div style="font-size:0.8em">Posted on {{ if $e.PublishedParsed }}{{ toRFC1123Z $e.PublishedParsed }}{{ else }}{{ if $e.UpdatedParsed }}{{ toRFC1123Z $e.UpdatedParsed }}{{ else }}{{ epochRFC1123Z }}{{ end }}{{ end }} | <a href="{{ $e.Link }}">permalink</a> | <a href="%s">%s</a><br clear="all" /></div> | |
| </div> | |
| {{ if last $i $ }}{{ else }}<hr />{{ end }} | |
| {{- end }} | |
| </body> | |
| </html> | |
| ` | |
| var funcMap = template.FuncMap{ | |
| "toRFC1123Z": func(t time.Time) string { return t.Format(time.RFC1123Z) }, | |
| "epochRFC1123Z": func() string { return time.Unix(0, 0).Format(time.RFC1123Z) }, | |
| "last": func(x int, a interface{}) bool { return x == reflect.ValueOf(a).Len() - 1 }, | |
| } | |
| func getDateTime(item *gofeed.Item) (dc int64) { | |
| if item.PublishedParsed != nil { | |
| dc = item.PublishedParsed.Unix() | |
| } else if item.UpdatedParsed != nil { | |
| dc = item.UpdatedParsed.Unix() | |
| } else { | |
| dc = 0 | |
| } | |
| return | |
| } | |
| func main() { | |
| Logger := log.New(os.Stdout, "ykr ", log.LstdFlags) | |
| configPath := flag.String("c", "", "a config file") | |
| flag.Parse() | |
| f, err := os.Open(*configPath) | |
| if err != nil { | |
| panic(err) | |
| } | |
| defer f.Close() | |
| decoder := yaml.NewDecoder(f) | |
| var config Config | |
| if err := decoder.Decode(&config); err != nil { | |
| panic(err) | |
| } | |
| basename := filepath.Base(*configPath) | |
| extname := filepath.Ext(*configPath) | |
| name := basename[:len(basename)-len(extname)] | |
| usr, _ := user.Current() | |
| homeDir := usr.HomeDir | |
| appDir := filepath.Join(homeDir, ".ykr") | |
| if _, err := os.Stat(appDir); os.IsNotExist(err) { | |
| err := os.MkdirAll(appDir, 0755) | |
| if err != nil { | |
| panic(err) | |
| } | |
| } | |
| dbPath := filepath.Join(appDir, name + ".sqlite3") | |
| db, err := sql.Open("sqlite3", dbPath) | |
| if err != nil { | |
| panic(err) | |
| } | |
| defer db.Close() | |
| createQuery := `CREATE TABLE IF NOT EXISTS item( | |
| id INTEGER PRIMARY KEY, | |
| feedurl TEXT NOT NULL, | |
| link TEXT NOT NULL, | |
| datetime INTEGER)` | |
| db.Exec(createQuery) | |
| fp := gofeed.NewParser() | |
| for _, url := range config.Feed { | |
| feed, err := fp.ParseURL(url) | |
| if err != nil { | |
| Logger.Println(url) | |
| Logger.Print(err) | |
| continue | |
| } | |
| newItems := []*gofeed.Item{} | |
| for _, item := range feed.Items { | |
| dc := getDateTime(item) | |
| row := db.QueryRow(`SELECT feedurl, link, datetime FROM item WHERE feedurl = ? AND link = ? AND datetime = ?`, url, item.Link, dc) | |
| var ( | |
| feedUrl string | |
| link string | |
| dateTime int64 | |
| ) | |
| err := row.Scan(&feedUrl, &link, &dateTime) | |
| if err != nil { | |
| newItems = append(newItems, item) | |
| } | |
| } | |
| if len(newItems) == 0 { | |
| continue | |
| } | |
| tmplStr := fmt.Sprintf(tmplStrFmt, feed.Link, feed.Title) | |
| tmpl, _ := template.New("").Funcs(funcMap).Parse(tmplStr) | |
| var buf bytes.Buffer | |
| tmpl.Execute(&buf, newItems) | |
| auth := smtp.PlainAuth("", config.UserName, config.Password, "smtp.gmail.com") | |
| mail := mailyak.New("smtp.gmail.com:587", auth) | |
| mail.To(config.MailTo) | |
| mail.From(config.MailFrom) | |
| mail.FromName(feed.Title) | |
| mail.Subject(feed.Title) | |
| mail.HTML().Set(buf.String()) | |
| if err := mail.Send(); err != nil { | |
| panic(err) | |
| } | |
| for _, item := range newItems { | |
| dc := getDateTime(item) | |
| _, err = db.Exec("INSERT INTO item (feedurl, link, datetime) VALUES (?, ?, ?)", url, item.Link, dc) | |
| if err != nil { | |
| panic(err) | |
| } | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment