gruik

Fork of GCU-Squad's RSS-to-IRC bridge
git clone https://git.instinctive.eu/gruik.git
Log | Files | Refs | README | LICENSE

commit 6b93082010a752a9849edebfdb27c5a934cb700a
parent 21ec63fd82dd169d7fe94628367fe8d3f99a3b43
Author: Emile 'iMil' Heitor <imil@NetBSD.org>
Date:   Wed, 12 Jul 2023 19:25:37 +0200

feat: many features added, list, add, remove feeds; colors; session disk save...

Diffstat:
MREADME.md | 18+++++++++++++-----
Mmain.go | 146+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
2 files changed, 141 insertions(+), 23 deletions(-)

diff --git a/README.md b/README.md @@ -11,10 +11,18 @@ Here's a sample `config.yaml` file: irc: server: irc.libera.chat nick: Gruik - password: piggypiggy # optional channel: myfeedz - debug: false # optional - port: 6667 # optional + password: piggypiggy # optional + debug: false # ditto + port: 6667 # ditto + delay: 2s # ditto, delay between 2 lines to avoid flood + colors: # ditto + title: pink # ditto + news: bold # ditto + link: lightblue # ditto + ops: # ditto + - MrFoo # ditto + - MrsBar # ditto feeds: urls: @@ -25,8 +33,8 @@ feeds: - https://lwn.net/headlines/rss - https://www.phoronix.com/rss.php - https://lobste.rs/rss - maxnews: 10 # optional - maxage: 1h # optional + maxnews: 10 # optional + maxage: 1h # optional frequency: 5m # optional ``` diff --git a/main.go b/main.go @@ -1,10 +1,12 @@ package main import ( + "encoding/json" "fmt" "io" "log" "os" + "strconv" "strings" "time" @@ -13,10 +15,27 @@ import ( "github.com/spf13/viper" ) +type News struct { + Origin string `json:"origin"` + Title string `json:"title"` + Link string `json:"link"` +} + +const feedJson = "feed.json" + +func newsExists(newslist []News, news News) bool { + for _, n := range newslist { + if n.Title == news.Title && n.Link == news.Link { + return true + } + } + return false +} + // Fetch and post news from RSS feeds func newsFetch(client *girc.Client, channel string) { - postedItems := make(map[string]bool) + newslist := []News{} start := time.Now() @@ -29,11 +48,25 @@ func newsFetch(client *girc.Client, channel string) { time.Sleep(viper.GetDuration("feeds.frequency")) } + // load saved news + f, err := os.OpenFile(feedJson, os.O_CREATE|os.O_RDWR, 0o644) + if err != nil { + log.Fatalf("can't open %s: %v", feedJson, err) + } + defer f.Close() + // read news from disk + decoder := json.NewDecoder(f) + if err := decoder.Decode(&newslist); err != nil { + client.Cmd.Message(channel, "could load news list, empty?") + } + encoder := json.NewEncoder(f) // for later writing + oneDay := 24 * time.Hour for { + // reset news list after one day if time.Since(start) > oneDay { - postedItems = make(map[string]bool) + newslist = []News{} } for _, feedURL := range viper.GetStringSlice("feeds.urls") { log.Printf("fetching %s...\n", feedURL) @@ -46,10 +79,14 @@ func newsFetch(client *girc.Client, channel string) { i := 0 // number of posted news for _, item := range feed.Items { - itemID := fmt.Sprintf("%s:%s", feed.Title, item.Title) + news := News{ + Origin: feed.Title, + Title: item.Title, + Link: item.Link, + } // Check if item was already posted - if postedItems[itemID] { - log.Printf("already posted %s\n", itemID) + if newsExists(newslist, news) { + log.Printf("already posted %s\n", item.Title) continue } // don't paste news older than feeds.maxage @@ -63,30 +100,44 @@ func newsFetch(client *girc.Client, channel string) { break } - post := fmt.Sprintf("[%s] %s: %s", feed.Title, item.Title, item.Link) - client.Cmd.Message(channel, post) + post := fmt.Sprintf("[{%s}%s{c}] {%s}%s{c}: {%s}%s{c}", + viper.GetString("irc.colors.title"), + feed.Title, + viper.GetString("irc.colors.news"), + item.Title, + viper.GetString("irc.colors.link"), + item.Link) + + client.Cmd.Message(channel, girc.Fmt(post)) - time.Sleep(2 * time.Second) + time.Sleep(viper.GetDuration("irc.delay")) // Mark item as posted - postedItems[itemID] = true + newslist = append(newslist, news) } } + if err = encoder.Encode(newslist); err != nil { + client.Cmd.Message(channel, "could write newslist") + } time.Sleep(viper.GetDuration("feeds.frequency")) } } func confDefault() { kv := map[string]interface{}{ - "irc.server": "irc.libera.chat", - "irc.nick": "gruik", - "irc.channel": "goaste", - "irc.debug": false, - "irc.port": 6667, - "feeds.urls": []string{}, - "feeds.maxnews": 10, - "feeds.maxage": "1h", - "feeds.frequency": "10m", + "irc.server": "irc.libera.chat", + "irc.nick": "gruik", + "irc.channel": "goaste", + "irc.debug": false, + "irc.port": 6667, + "irc.delay": "2s", + "irc.colors.title": "pink", + "irc.colors.news": "bold", + "irc.colors.link": "lightblue", + "feeds.urls": []string{}, + "feeds.maxnews": 10, + "feeds.maxage": "1h", + "feeds.frequency": "10m", } for k, v := range kv { @@ -96,6 +147,15 @@ func confDefault() { } } +func isOp(nick string) bool { + for _, op := range viper.GetStringSlice("irc.ops") { + if nick == op { + return true + } + } + return false +} + func main() { config := "config" if len(os.Args) > 1 { @@ -142,6 +202,56 @@ func main() { client.Handlers.Add(girc.CONNECTED, func(c *girc.Client, e girc.Event) { c.Cmd.Join(channel) }) + client.Handlers.Add(girc.PRIVMSG, func(c *girc.Client, e girc.Event) { + if strings.HasPrefix(e.Last(), "!die") { + if isOp(e.Source.Name) { + c.Close() + } + } + if strings.HasPrefix(e.Last(), "!lsfeeds") { + for i, f := range viper.GetStringSlice("feeds.urls") { + n := strconv.Itoa(i + 1) + c.Cmd.Message(channel, n+". "+f) + time.Sleep(viper.GetDuration("irc.delay")) + } + } + if isOp(e.Source.Name) && strings.HasPrefix(e.Last(), "!addfeed") { + s := e.Last() + if !strings.Contains(s, " ") { + return + } + s = s[strings.LastIndex(s, " ")+1:] + ss := append(viper.GetStringSlice("feeds.urls"), s) + viper.Set("feeds.urls", ss) + c.Cmd.ReplyTo(e, girc.Fmt("feed {b}{green}added{c}{b}")) + if err := viper.WriteConfig(); err != nil { + c.Cmd.ReplyTo(e, girc.Fmt("adding feed {b}{red}failed{c}{b}")) + } + } + if isOp(e.Source.Name) && strings.HasPrefix(e.Last(), "!rmfeed") { + s := e.Last() + if !strings.Contains(s, " ") { + return + } + s = s[strings.LastIndex(s, " ")+1:] + ss := viper.GetStringSlice("feeds.urls") + i, err := strconv.Atoi(s) + if err != nil { + c.Cmd.ReplyTo(e, "index conversion failed") + return + } + if i < 1 || i > len(ss) { + c.Cmd.ReplyTo(e, "bad index number") + return + } + ss = append(ss[:i-1], ss[i:]...) + viper.Set("feeds.urls", ss) + c.Cmd.ReplyTo(e, girc.Fmt("feed {b}{green}removed{c}{b}")) + if err := viper.WriteConfig(); err != nil { + c.Cmd.ReplyTo(e, girc.Fmt("removing feed {b}{red}failed{c}{b}")) + } + } + }) go newsFetch(client, channel)