marvin

A simple and modular IRC bot

git clone https://git.8pit.net/marvin.git

  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			}
101
102			m.feeds[u] = feed.Items[0].PubDate
103		}(url)
104	}
105
106	wg.Wait()
107}
108
109func (m *Module) fetchFeed(url string) (feed feedparser.Feed, err error) {
110	resp, err := http.Get(url)
111	if err != nil {
112		return
113	}
114
115	reader := resp.Body
116	defer reader.Close()
117
118	feed, err = feedparser.Parse(reader)
119	if err != nil {
120		return
121	}
122
123	return
124}
125
126func (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}