package flood import ( "math" "sort" "strings" "time" "unicode/utf8" lru "github.com/hashicorp/golang-lru" ) const ( runes = 6 words = 6 black = "\u24B6\u262D\u272F\u262E\u2721\u5350\u534D\u2719\u0FD5\u0FD6\u16CB\uA5A6\u0FD7\u0FD8" ) type Kicker interface { Kick(nick string, message ...string) Ban(nick string) } type Check struct { k Kicker cache *lru.Cache } func New(k Kicker) *Check { cache, _ := lru.New(100) c := &Check{k: k, cache: cache} go c.decay(time.Hour) return c } func (c Check) get(nick string) (int, bool) { if n, ok := c.cache.Get(nick); ok { return n.(int), true } return 0, false } func (c Check) del(nick string) { c.cache.Remove(nick) } func (c Check) decay(t time.Duration) { for range time.Tick(t) { for _, k := range c.cache.Keys() { nick := k.(string) n, _ := c.get(nick) if n > 1 { c.set(nick, n-1) } else { c.del(nick) } } } } func (c Check) set(nick string, n int) { c.cache.Add(nick, n) } func (c Check) Check(text, nick string) { if isFlood(text) { count, _ := c.get(nick) c.set(nick, count+1) if count >= 3 { c.k.Ban(nick) c.del(nick) } c.k.Kick(nick) } } func entropy(s string) (e float64) { n := make(map[rune]float64) for _, r := range s { n[r] += 1 / float64(len(s)) } for _, v := range n { e -= v * math.Log2(v) } return e } func isFlood(s string) bool { if strings.ContainsAny(s, black) { return true } if utf8.RuneCountInString(s) <= runes { return false } if v := strings.Fields(s); len(v) >= words { return commonWord(v) >= len(v)/2 } return entropy(s) <= 1 } func commonWord(v []string) int { m := make(map[string]int) for _, w := range v { m[w]++ } l := make([]int, len(m)) for _, n := range m { l = append(l, n) } sort.Sort(sort.Reverse(sort.IntSlice(l))) return l[0] }