package main import ( "log" "os" "strconv" "time" "github.com/BurntSushi/toml" "github.com/mmcdole/gofeed" ) type Config struct { Urls []string } type Item struct { Title, Description, Url, Filename string Length int } type Podcast struct { Title, Description, Url string Items []Item } type pcList struct { Podcasts []Podcast } func newpcList(confs ...string) (ret *pcList) { ret = &pcList{} ret.Podcasts = make([]Podcast,0) if len(confs) > 0 { if _, err := toml.DecodeFile("output.conf", &ret); err != nil { log.Print("Error reading podcast list:",err) } } return } func (p *pcList) Find(x *Podcast) (int, bool) { for i,y := range p.Podcasts { if y.Title == x.Title { return i, true } } return 0, false } func (p *pcList) Add(x *Podcast) { if x == nil { return } if i,ok := p.Find(x); ok == true { log.Print(" Existing podcast") p.Podcasts[i].Merge(x) } else { log.Print(" New podcast") p.Podcasts = append((*p).Podcasts,*x) } } func (p *Podcast) Merge(x *Podcast) { for _,item := range x.Items { if !p.Has(item) { log.Print(" Appending '",item.Title,"'") p.Items = append(p.Items,item) } } } func (p *Podcast) Has(i Item) bool { for _,x := range p.Items { if x.Title == i.Title { return true } } return false } type Selector func(*gofeed.Item) bool func AllSelectors(ss ...Selector) Selector { return func(i *gofeed.Item) bool { for _, s := range ss { if !s(i) { return false } } return true } } func AnySelector(ss ...Selector) Selector { return func(i *gofeed.Item) bool { for _, s := range ss { if s(i) { return true } } return false } } func newerThan(t time.Time) Selector { return func(i *gofeed.Item) bool { if i.PublishedParsed.After(t) { return true } return false } } func daysAgo(x int) Selector { d := time.Now() return newerThan(time.Date(d.Year(),d.Month(),d.Day()-x,0,0,0,0,time.Local)) } func toPodcast(sel Selector, url string, feed *gofeed.Feed) (ret *Podcast) { ret = &Podcast{ Title: feed.Title, Description: feed.Description, Url: url, Items: []Item{}, } for _, i := range feed.Items { if sel(i) { it := Item{ Title: i.Title, Description: i.Description, } for _, n := range i.Enclosures { if n.Type == "audio/mpeg" { it.Url = n.URL if l, err := strconv.Atoi(n.Length); err == nil { it.Length = l } } } ret.Items = append(ret.Items,it) } } return } func readFeed(url string,sel Selector) *Podcast { fp := gofeed.NewParser() feed, err := fp.ParseURL(url) if err != nil { log.Print(err) return nil } return toPodcast(sel,url,feed) } func main() { log.Print("rssd") log.Print("reading configuration") var conf Config if _, err := toml.DecodeFile("rssd.conf", &conf); err != nil { log.Fatal("Error reading config file:",err) } pl := newpcList("output.conf") sel := daysAgo(60) for _,url := range conf.Urls { log.Print(" -> ",url) pl.Add(readFeed(url,sel)) } of,err := os.Create("output.conf") if err != nil { log.Fatal("Cannot open output file") } enc := toml.NewEncoder(of) log.Print("writing output") enc.Encode(pl) of.Close() log.Print("done") }