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:
M | README.md | | | 18 | +++++++++++++----- |
M | main.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)