From 090d2fae173d241ee93966e6c6aa95e7d0ec9e86 Mon Sep 17 00:00:00 2001 From: Dimitri Sokolyuk Date: Sat, 6 Jul 2019 18:34:01 +0200 Subject: simplify --- internal/feeds/rss.go | 19 +++++------ internal/flood/flood.go | 20 +++++++++++- internal/flood/flood_test.go | 2 +- internal/href/href.go | 34 +++++++++++++++++-- internal/href/href_test.go | 2 +- internal/re/re.go | 28 ++++++++++++++++ main.go | 25 +++++++++++--- notify.go | 23 ------------- room.go | 78 +++++++++++++------------------------------- 9 files changed, 132 insertions(+), 99 deletions(-) delete mode 100644 notify.go 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 ¬ify{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 ¬ify{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) } -- cgit v1.2.3