1// This program is free software: you can redistribute it and/or modify 2// it under the terms of the GNU Affero General Public License as 3// published by the Free Software Foundation, either version 3 of the 4// License, or (at your option) any later version. 5// 6// This program is distributed in the hope that it will be useful, but 7// WITHOUT ANY WARRANTY; without even the implied warranty of 8// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 9// Affero General Public License for more details. 10// 11// You should have received a copy of the GNU Affero General Public 12// License along with this program. If not, see <http://www.gnu.org/licenses/>. 13 14package feed 15 16import ( 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) 26 27type post struct { 28 Feed feedparser.Feed 29 Item feedparser.Item 30} 31 32type Module struct { 33 feeds map[string]time.Time 34 URLs []string `json:"urls"` 35 Interval string `json:"interval"` 36} 37 38func Init(moduleSet *modules.ModuleSet) { 39 moduleSet.Register(new(Module)) 40} 41 42func (m *Module) Name() string { 43 return "feed" 44} 45 46func (m *Module) Help() string { 47 return "Display new entries for RSS/ATOM feeds." 48} 49 50func (m *Module) Defaults() { 51 m.Interval = "0h15m" 52} 53 54func (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 } 59 60 duration, err := time.ParseDuration(m.Interval) 61 if err != nil { 62 return err 63 } 64 65 newPosts := make(chan post) 66 go func() { 67 for post := range newPosts { 68 m.notify(client, post) 69 } 70 }() 71 72 go func() { 73 for { 74 time.Sleep(duration) 75 m.pollFeeds(newPosts) 76 } 77 }() 78 79 return nil 80} 81 82func (m *Module) pollFeeds(out chan post) { 83 var wg sync.WaitGroup 84 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 return 91 } 92 93 latest := m.feeds[u] 94 for _, i := range feed.Items { 95 if !i.PubDate.After(latest) { 96 break 97 } 98 99 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}