package main import ( "bufio" "crypto/md5" "errors" "flag" "fmt" "io" "log" "os" "sort" "strings" "time" irc "github.com/fluffle/goirc/client" "github.com/mmcdole/gofeed") var ( server = flag.String("server", "", "IRC server") channel = flag.String("channel", "", "IRC channel") nick = flag.String("nick", "", "IRC nickname") period = flag.String("period", "10m", "Update period") seenPath = flag.String("seen", "", "\"Seen\" entries store") postOld = flag.Bool("post-old", false, "Post all entries on the first fetch ever")) func main() { flag.Parse() if !flag.Parsed() || *server == "" || *channel == "" || *nick == "" || *seenPath == "" || len(flag.Args()) == 0 { flag.Usage() if flag.Parsed() { fmt.Fprintln(os.Stderr, "Flags -server, -channel, -nick and -seen are not optional.") fmt.Fprintln(os.Stderr, "At least one url has to be provided.") } os.Exit(1) } updatePeriod, err := time.ParseDuration(*period) if err == nil && updatePeriod < 5*time.Minute { err = fmt.Errorf("too short: %s", updatePeriod) } if err != nil { fmt.Fprintf(os.Stderr, "update period: %s", err) os.Exit(1) } seenFile, err := os.OpenFile(*seenPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644) if err != nil { fmt.Fprintf(os.Stderr, "seen file: %s", err) os.Exit(1) } defer seenFile.Close() seenReader := bufio.NewReader(seenFile) seen := make(map[[md5.Size]byte]struct{}) for { if url, err := seenReader.ReadString('\n'); err != nil { if errors.Is(err, io.EOF) { break } fmt.Fprintf(os.Stderr, "seen file: %s", err) os.Exit(1) } else { url := []byte(strings.TrimSpace(url)) seen[md5.Sum(url)] = struct{}{} } } urls := flag.Args() skipFirst := make(map[string]struct{}) if len(seen) == 0 && !*postOld { for _, url := range urls { skipFirst[url] = struct{}{} } } cfg := irc.NewConfig(*nick, "feed2ircbot", "feed2irc bot") c := irc.Client(cfg) c.EnableStateTracking() c.HandleFunc(irc.CONNECTED, func(*irc.Conn, *irc.Line) { log.Printf("irc: connected") c.Join(*channel) }) c.HandleFunc(irc.DISCONNECTED, func(*irc.Conn, *irc.Line) { log.Printf("irc: disconnected") os.Exit(1) }) joinedChan := make(chan bool, 1) c.HandleFunc(irc.JOIN, func(c *irc.Conn, line *irc.Line) { if c.Me().Nick == line.Nick { log.Printf("irc: joined") close(joinedChan) } }) if err := c.ConnectTo(*server); err != nil { fmt.Fprintf(os.Stderr, "irc: %s", err) os.Exit(1) } <-joinedChan for { for _, url := range urls { p := gofeed.NewParser() if feed, err := p.ParseURL(url); err != nil { log.Printf("%s: %s", url, err) } else { sort.Sort(feed) for _, item := range feed.Items { sum := md5.Sum([]byte(item.Link)) if _, have := seen[sum]; !have { if _, err := fmt.Fprintln(seenFile, item.Link); err != nil { log.Printf("seen file: %s", err) os.Exit(1) } seenFile.Sync() seen[sum] = struct{}{} if _, skip := skipFirst[url]; !skip { s := fmt.Sprintf("%s | %s\n", item.Title, item.Link) c.Privmsgln(*channel, s) } } } delete(skipFirst, url) } } time.Sleep(updatePeriod) }}
package main
import (
"bufio"
"crypto/md5"
"errors"
"flag"
"fmt"
"io"
"log"
"os"
"sort"
"strings"
"time"
irc "github.com/fluffle/goirc/client"
"github.com/mmcdole/gofeed"
)
var (
server = flag.String("server", "", "IRC server")
channel = flag.String("channel", "", "IRC channel")
nick = flag.String("nick", "", "IRC nickname")
period = flag.String("period", "10m", "Update period")
seenPath = flag.String("seen", "", "\"Seen\" entries store")
postOld = flag.Bool("post-old", false, "Post all entries on the first fetch ever")
)
func main() {
flag.Parse()
if !flag.Parsed() || *server == "" || *channel == "" || *nick == "" || *seenPath == "" || len(flag.Args()) == 0 {
flag.Usage()
if flag.Parsed() {
fmt.Fprintln(os.Stderr, "Flags -server, -channel, -nick and -seen are not optional.")
fmt.Fprintln(os.Stderr, "At least one url has to be provided.")
}
os.Exit(1)
}
updatePeriod, err := time.ParseDuration(*period)
if err == nil && updatePeriod < 5*time.Minute {
err = fmt.Errorf("too short: %s", updatePeriod)
}
if err != nil {
fmt.Fprintf(os.Stderr, "update period: %s", err)
os.Exit(1)
}
seenFile, err := os.OpenFile(*seenPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
fmt.Fprintf(os.Stderr, "seen file: %s", err)
os.Exit(1)
}
defer seenFile.Close()
seenReader := bufio.NewReader(seenFile)
seen := make(map[[md5.Size]byte]struct{})
for {
if url, err := seenReader.ReadString('\n'); err != nil {
if errors.Is(err, io.EOF) {
break
}
fmt.Fprintf(os.Stderr, "seen file: %s", err)
os.Exit(1)
} else {
url := []byte(strings.TrimSpace(url))
seen[md5.Sum(url)] = struct{}{}
}
}
urls := flag.Args()
skipFirst := make(map[string]struct{})
if len(seen) == 0 && !*postOld {
for _, url := range urls {
skipFirst[url] = struct{}{}
}
}
cfg := irc.NewConfig(*nick, "feed2ircbot", "feed2irc bot")
c := irc.Client(cfg)
c.EnableStateTracking()
c.HandleFunc(irc.CONNECTED, func(*irc.Conn, *irc.Line) {
log.Printf("irc: connected")
c.Join(*channel)
})
c.HandleFunc(irc.DISCONNECTED, func(*irc.Conn, *irc.Line) {
log.Printf("irc: disconnected")
os.Exit(1)
})
joinedChan := make(chan bool, 1)
c.HandleFunc(irc.JOIN, func(c *irc.Conn, line *irc.Line) {
if c.Me().Nick == line.Nick {
log.Printf("irc: joined")
close(joinedChan)
}
})
if err := c.ConnectTo(*server); err != nil {
fmt.Fprintf(os.Stderr, "irc: %s", err)
os.Exit(1)
}
<-joinedChan
for {
for _, url := range urls {
p := gofeed.NewParser()
if feed, err := p.ParseURL(url); err != nil {
log.Printf("%s: %s", url, err)
} else {
sort.Sort(feed)
for _, item := range feed.Items {
sum := md5.Sum([]byte(item.Link))
if _, have := seen[sum]; !have {
if _, err := fmt.Fprintln(seenFile, item.Link); err != nil {
log.Printf("seen file: %s", err)
os.Exit(1)
}
seenFile.Sync()
seen[sum] = struct{}{}
if _, skip := skipFirst[url]; !skip {
s := fmt.Sprintf("%s | %s\n", item.Title, item.Link)
c.Privmsgln(*channel, s)
}
}
}
delete(skipFirst, url)
}
}
time.Sleep(updatePeriod)
}
}