summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDimitri Sokolyuk <demon@dim13.org>2019-07-06 18:34:01 +0200
committerDimitri Sokolyuk <demon@dim13.org>2019-07-06 18:34:01 +0200
commit090d2fae173d241ee93966e6c6aa95e7d0ec9e86 (patch)
tree4addac001957319763d9c01e53442c0f8be60c3a
parent3b8ee25e5d5cfdb7920e5bfb7ff99a35c5b26345 (diff)
simplify
-rw-r--r--internal/feeds/rss.go19
-rw-r--r--internal/flood/flood.go20
-rw-r--r--internal/flood/flood_test.go2
-rw-r--r--internal/href/href.go34
-rw-r--r--internal/href/href_test.go2
-rw-r--r--internal/re/re.go28
-rw-r--r--main.go25
-rw-r--r--notify.go23
-rw-r--r--room.go78
9 files changed, 132 insertions, 99 deletions
diff --git a/internal/feeds/rss.go b/internal/feeds/rss.go
index f3dd39c..4386c57 100644
--- a/internal/feeds/rss.go
+++ b/internal/feeds/rss.go
@@ -3,7 +3,6 @@ package feeds
import (
"fmt"
"io"
- "log"
"time"
"dim13.org/rss"
@@ -31,19 +30,17 @@ func (n news) String() string {
return s
}
-func (f Feed) Watch(w io.Writer) {
+func (f Feed) watch(w io.Writer) {
ticker := time.NewTicker(f.Every)
defer ticker.Stop()
for t := range ticker.C {
r, err := rss.Fetch(f.URL)
- if err != nil {
- log.Println(f.Name, err)
- return
- }
- last := t.Add(-f.Every)
- for _, i := range r.Channel.Items {
- if i.PubDate.After(last) {
- fmt.Fprint(w, news{f, i})
+ if err == nil {
+ last := t.Add(-f.Every)
+ for _, i := range r.Channel.Items {
+ if i.PubDate.After(last) {
+ fmt.Fprint(w, news{f, i})
+ }
}
}
}
@@ -51,6 +48,6 @@ func (f Feed) Watch(w io.Writer) {
func Watch(w io.Writer, feeds []Feed) {
for _, feed := range feeds {
- go feed.Watch(w)
+ go feed.watch(w)
}
}
diff --git a/internal/flood/flood.go b/internal/flood/flood.go
index b8a71d1..b16b948 100644
--- a/internal/flood/flood.go
+++ b/internal/flood/flood.go
@@ -14,6 +14,24 @@ const (
black = "\u24B6\u262D\u272F\u262E\u2721\u5350\u534D\u2719\u0FD5\u0FD6\u16CB\uA5A6\u0FD7\u0FD8"
)
+type Kicker interface {
+ Kick(nick string, message ...string)
+}
+
+type Checker struct {
+ k Kicker
+}
+
+func New(k Kicker) *Checker {
+ return &Checker{k: k}
+}
+
+func (c Checker) Check(text, nick string) {
+ if isFlood(text) {
+ c.k.Kick(nick)
+ }
+}
+
func entropy(s string) (e float64) {
n := make(map[rune]float64)
for _, r := range s {
@@ -25,7 +43,7 @@ func entropy(s string) (e float64) {
return e
}
-func Is(s string) bool {
+func isFlood(s string) bool {
if strings.ContainsAny(s, black) {
return true
}
diff --git a/internal/flood/flood_test.go b/internal/flood/flood_test.go
index c16da7c..a09a3c4 100644
--- a/internal/flood/flood_test.go
+++ b/internal/flood/flood_test.go
@@ -20,7 +20,7 @@ func TestFlood(t *testing.T) {
}
for _, tc := range testCases {
t.Run(tc.line, func(t *testing.T) {
- if Is(tc.line) != tc.ok {
+ if isFlood(tc.line) != tc.ok {
t.Errorf("want %v", tc.ok)
}
})
diff --git a/internal/href/href.go b/internal/href/href.go
index d134f67..f224d27 100644
--- a/internal/href/href.go
+++ b/internal/href/href.go
@@ -3,12 +3,14 @@ package href
import (
"context"
"errors"
+ "fmt"
"io"
"net/http"
"strings"
"time"
"unicode/utf8"
+ lru "github.com/hashicorp/golang-lru"
"golang.org/x/net/html"
"golang.org/x/net/html/charset"
)
@@ -23,6 +25,34 @@ var (
const maxLength = 10 * 1024 * 1024 // 10MB
+type Titles struct {
+ cache *lru.Cache
+ w io.Writer
+}
+
+func NewTitles(w io.Writer) *Titles {
+ cache, _ := lru.New(100)
+ return &Titles{cache: cache, w: w}
+}
+
+func (t *Titles) Resolve(text string) {
+ for _, v := range parseLinks(text) {
+ if v == "" {
+ continue
+ }
+ title, ok := t.cache.Get(v)
+ if ok {
+ fmt.Fprintf(t.w, "Title: %v (cached)", title)
+ continue
+ }
+ title, err := fetch(v)
+ if err == nil {
+ t.cache.Add(v, title)
+ fmt.Fprintf(t.w, "Title: %v", title)
+ }
+ }
+}
+
func title(r io.Reader) (string, error) {
var inTitle bool
z := html.NewTokenizer(r)
@@ -51,7 +81,7 @@ func title(r io.Reader) (string, error) {
return "", errNoTitle
}
-func Title(uri string) (string, error) {
+func fetch(uri string) (string, error) {
req, err := http.NewRequest("GET", uri, nil)
if err != nil {
return "", err
@@ -86,7 +116,7 @@ func Title(uri string) (string, error) {
return title(r)
}
-func Links(s string) (ret []string) {
+func parseLinks(s string) (ret []string) {
for _, v := range strings.Fields(s) {
switch {
case strings.HasPrefix(v, "www."):
diff --git a/internal/href/href_test.go b/internal/href/href_test.go
index ed2bc40..7f5de3f 100644
--- a/internal/href/href_test.go
+++ b/internal/href/href_test.go
@@ -51,7 +51,7 @@ func TestTitle(t *testing.T) {
io.Copy(w, fd)
}))
defer ts.Close()
- title, err := Title(ts.URL)
+ title, err := fetch(ts.URL)
if err != nil {
t.Error(tc.fixture, err)
}
diff --git a/internal/re/re.go b/internal/re/re.go
index b235282..f7e9543 100644
--- a/internal/re/re.go
+++ b/internal/re/re.go
@@ -2,12 +2,40 @@ package re
import (
"errors"
+ "fmt"
+ "io"
"regexp"
"strings"
+
+ lru "github.com/hashicorp/golang-lru"
)
var errNotRE = errors.New("not re")
+type RE struct {
+ last *lru.Cache
+ w io.Writer
+}
+
+func NewRE(w io.Writer) *RE {
+ last, _ := lru.New(100)
+ return &RE{last: last, w: w}
+}
+
+func (r *RE) Replace(text, nick string) {
+ defer r.last.Add(nick, text)
+ if !strings.HasPrefix(text, "s") {
+ return
+ }
+ if tofix, ok := r.last.Get(nick); ok {
+ global := strings.HasSuffix(text, "g")
+ fixed, err := Replace(tofix.(string), text[1:], global)
+ if err == nil && fixed != tofix {
+ fmt.Fprintf(r.w, "%v meant to say: %s", nick, fixed)
+ }
+ }
+}
+
func Replace(s, r string, global bool) (string, error) {
// min: at least two separators
if len(r) < 2 {
diff --git a/main.go b/main.go
index a3e9f20..f4f320e 100644
--- a/main.go
+++ b/main.go
@@ -9,6 +9,24 @@ import (
irc "github.com/fluffle/goirc/client"
)
+type notify struct {
+ conn *irc.Conn
+ room string
+}
+
+func newNotify(conn *irc.Conn, room string) *notify {
+ return &notify{conn: conn, room: room}
+}
+
+func (n *notify) Write(p []byte) (int, error) {
+ n.conn.Notice(n.room, limitString(p).String())
+ return len(p), nil
+}
+
+func (n *notify) Kick(nick string, message ...string) {
+ n.conn.Kick(n.room, nick, message...)
+}
+
func main() {
node := flag.String("node", "irc.freenode.org", "IRC server")
notls := flag.Bool("notls", false, "disable TLS")
@@ -30,14 +48,13 @@ func main() {
close(done)
})
+ n := newNotify(conn, *room)
conn.HandleFunc(irc.CONNECTED, func(conn *irc.Conn, _ *irc.Line) {
conn.Join(*room)
- feeds.Watch(newNotify(conn, *room), feeds.Feeds)
+ feeds.Watch(n, feeds.Feeds)
})
- r := newRoom(*room, 100)
-
- conn.HandleBG(irc.PRIVMSG, r)
+ conn.HandleBG(irc.PRIVMSG, newRoom(n))
conn.HandleFunc(irc.KICK, func(conn *irc.Conn, _ *irc.Line) {
conn.Join(*room)
diff --git a/notify.go b/notify.go
deleted file mode 100644
index 9234e20..0000000
--- a/notify.go
+++ /dev/null
@@ -1,23 +0,0 @@
-package main
-
-import (
- "log"
-
- irc "github.com/fluffle/goirc/client"
-)
-
-type notify struct {
- conn *irc.Conn
- target string
-}
-
-func newNotify(conn *irc.Conn, target string) *notify {
- return &notify{conn: conn, target: target}
-}
-
-func (n *notify) Write(p []byte) (int, error) {
- s := limitString(p)
- log.Println("send", s)
- n.conn.Notice(n.target, s.String())
- return len(p), nil
-}
diff --git a/room.go b/room.go
index a7c2ad0..6e95413 100644
--- a/room.go
+++ b/room.go
@@ -1,74 +1,40 @@
package main
import (
- "fmt"
- "log"
- "strings"
+ "io"
"dim13.org/bot/internal/flood"
"dim13.org/bot/internal/href"
"dim13.org/bot/internal/re"
irc "github.com/fluffle/goirc/client"
- lru "github.com/hashicorp/golang-lru"
)
type Room struct {
- room string
- last *lru.Cache
- titles *lru.Cache
+ titles *href.Titles
+ flood *flood.Checker
+ re *re.RE
}
-func newRoom(room string, sz int) *Room {
- last, _ := lru.New(sz)
- titles, _ := lru.New(sz)
- return &Room{room: room, last: last, titles: titles}
+type KickWriter interface {
+ io.Writer
+ flood.Kicker
}
-func (r *Room) Handle(conn *irc.Conn, line *irc.Line) {
- defer func() {
- if r := recover(); r != nil {
- log.Println("panic", r)
- }
- }()
- t := line.Text()
- switch {
- case line.Nick == conn.Me().Nick:
- // ignore self
- case flood.Is(t):
- log.Println("flood", line.Nick)
- conn.Kick(r.room, line.Nick)
- case strings.HasPrefix(t, "s"):
- global := strings.HasSuffix(t, "g")
- if tofix, ok := r.last.Get(line.Nick); ok {
- fixed, err := re.Replace(tofix.(string), t[1:], global)
- if err == nil && fixed != tofix {
- log.Println("regexp", t)
- fmt.Fprintf(newNotify(conn, line.Target()), "%v meant to say: %s", line.Nick, fixed)
- return
- }
+func newRoom(w KickWriter) *Room {
+ return &Room{
+ titles: href.NewTitles(w),
+ flood: flood.New(w),
+ re: re.NewRE(w),
+ }
+}
- }
- fallthrough
- default:
- for _, v := range href.Links(t) {
- title, ok := r.titles.Get(v)
- if !ok {
- var err error
- title, err = href.Title(v)
- if err != nil {
- log.Println(v, err)
- }
- r.titles.Add(v, title)
- }
- if title != "" {
- w := newNotify(conn, line.Target())
- if ok {
- fmt.Fprintf(w, "Title: %v (cached)", title)
- } else {
- fmt.Fprintf(w, "Title: %v", title)
- }
- }
- }
- r.last.Add(line.Nick, t)
+func (r *Room) Handle(conn *irc.Conn, line *irc.Line) {
+ text, nick := line.Text(), line.Nick
+ // ignore self
+ if nick == conn.Me().Nick {
+ return
}
+ r.flood.Check(text, nick)
+ r.titles.Resolve(text)
+ r.re.Replace(text, nick)
}