1// This program is free software: you can redistribute it and/or modify2// it under the terms of the GNU Affero General Public License as3// published by the Free Software Foundation, either version 3 of the4// License, or (at your option) any later version.5//6// This program is distributed in the hope that it will be useful, but7// WITHOUT ANY WARRANTY; without even the implied warranty of8// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU9// Affero General Public License for more details.10//11// You should have received a copy of the GNU Affero General Public12// License along with this program. If not, see <http://www.gnu.org/licenses/>.1314package feed1516import (17 "github.com/nmeum/go-feedparser"18 "github.com/nmeum/marvin/irc"19 "github.com/nmeum/marvin/modules"20 "html"21 "net/http"22 "strings"23 "sync"24 "time"25)2627type post struct {28 Feed feedparser.Feed29 Item feedparser.Item30}3132type Module struct {33 feeds map[string]time.Time34 URLs []string `json:"urls"`35 Interval string `json:"interval"`36}3738func Init(moduleSet *modules.ModuleSet) {39 moduleSet.Register(new(Module))40}4142func (m *Module) Name() string {43 return "feed"44}4546func (m *Module) Help() string {47 return "Display new entries for RSS/ATOM feeds."48}4950func (m *Module) Defaults() {51 m.Interval = "0h15m"52}5354func (m *Module) Load(client *irc.Client) error {55 m.feeds = make(map[string]time.Time)56 for _, url := range m.URLs {57 m.feeds[url] = time.Now()58 }5960 duration, err := time.ParseDuration(m.Interval)61 if err != nil {62 return err63 }6465 newPosts := make(chan post)66 go func() {67 for post := range newPosts {68 m.notify(client, post)69 }70 }()7172 go func() {73 for {74 time.Sleep(duration)75 m.pollFeeds(newPosts)76 }77 }()7879 return nil80}8182func (m *Module) pollFeeds(out chan post) {83 var wg sync.WaitGroup84 for _, url := range m.URLs {85 wg.Add(1)86 go func(u string) {87 defer wg.Done()88 feed, err := m.fetchFeed(u)89 if err != nil {90 return91 }9293 latest := m.feeds[u]94 for _, i := range feed.Items {95 if !i.PubDate.After(latest) {96 break97 }9899 out <- post{feed, i}100 }101102 m.feeds[u] = feed.Items[0].PubDate103 }(url)104 }105106 wg.Wait()107}108109func (m *Module) fetchFeed(url string) (feed feedparser.Feed, err error) {110 resp, err := http.Get(url)111 if err != nil {112 return113 }114115 reader := resp.Body116 defer reader.Close()117118 feed, err = feedparser.Parse(reader)119 if err != nil {120 return121 }122123 return124}125126func (m *Module) notify(client *irc.Client, post post) {127 ftitle := html.UnescapeString(post.Feed.Title)128 for _, ch := range client.Channels {129 ititle := html.UnescapeString(post.Item.Title)130 client.Write("NOTICE %s :%s -- %s new entry %s: %s",131 ch, strings.ToUpper(m.Name()), ftitle, ititle, post.Item.Link)132 }133}