package main import ( "container/ring" "flag" "fmt" "log" "sort" "strings" "time" "dim13.org/duck" "dim13.org/rss" "dim13.org/theo" irc "github.com/fluffle/goirc/client" ) var ( server = flag.String("server", "irc.freenode.org:6667", "IRC Server") room = flag.String("room", "#lor", "IRC Channel") name = flag.String("name", "dim13", "Bots Name") ) type Commander interface { irc.Handler fmt.Stringer Timeout() bool WithArgs(int) bool } type Command struct { Help string Arg string Last time.Time } type ( Last struct{ Command } RSS struct{ Command } Theo struct{ Command } Help struct{ Command } Top struct{ Command } Duck struct{ Command } Blame struct{ Command } ) var ( commands = make(map[string]Commander) buffer = ring.New(10) ) func Register(cmd string, f Commander) { commands[cmd] = f } func (v Command) String() string { return v.Help } func (v *Command) Timeout() bool { log.Println("timeout:", time.Since(v.Last)) if time.Since(v.Last) > time.Minute { v.Last = time.Now() return false } return true } func (_ Command) WithArgs(n int) bool { return n == 1 } func (_ Last) Handle(conn *irc.Conn, line *irc.Line) { buffer.Do(func(v interface{}) { if v != nil { l := v.(*irc.Line) s := fmt.Sprintf("%v <%v> %v", l.Time.Format(time.Kitchen), l.Nick, l.Text()) conn.Privmsg(line.Nick, s) } }) } func (_ Theo) Handle(conn *irc.Conn, line *irc.Line) { conn.Privmsg(line.Target(), theo.Theo()) } func (v RSS) Handle(conn *irc.Conn, line *irc.Line) { news, err := rss.Fetch(v.Arg) if err != nil { conn.Privmsg(line.Target(), err.Error()) return } if line.Public() && len(news.Channel.Items) > 3 { news.Channel.Items = news.Channel.Items[:3] } for n, i := range news.Channel.Items { s := fmt.Sprintf("%2d. %v - %v", n+1, i.Title, i.Link) conn.Privmsg(line.Target(), s) } } func (_ Help) Handle(conn *irc.Conn, line *irc.Line) { var msg []string for k, v := range commands { msg = append(msg, fmt.Sprintf("%-8s%v", k, v)) } sort.Sort(sort.StringSlice(msg)) for _, s := range msg { conn.Privmsg(line.Nick, s) } } func (_ Top) Handle(conn *irc.Conn, line *irc.Line) { n := 100 if line.Public() { n = 10 } s := fmt.Sprint(NewScores(n)) conn.Privmsg(line.Target(), s) } func (_ Duck) Timeout() bool { return false } func (_ Duck) WithArgs(_ int) bool { return true } func (_ Duck) Handle(conn *irc.Conn, line *irc.Line) { if q := strings.SplitN(line.Text(), " ", 2); len(q) == 2 { if a, err := duck.Abstract(q[1]); err != nil { conn.Privmsg(line.Target(), err.Error()) } else { conn.Privmsg(line.Target(), a) } } } func (_ Blame) Handle(conn *irc.Conn, line *irc.Line) { src := []string{ "Source: http://www.dim13.org/cgi-bin/cgit.cgi/bot.git/", "Install: go get dim13.org/bot", } for _, s := range src { conn.Privmsg(line.Target(), s) } } func MedianLength(v []string) int { if len(v) == 0 { return 0 } l := make([]int, len(v)) for i, s := range v { l[i] = len(s) } sort.Sort(sort.IntSlice(l)) return l[len(l)/2] } func privmsg(conn *irc.Conn, line *irc.Line) { f := strings.Fields(line.Text()) if line.Public() && line.Nick != conn.Me().Nick { buffer.Value = line buffer = buffer.Next() Count(line.Nick) } // lookup command if len(f) > 0 { cmd := strings.ToLower(f[0]) if c, ok := commands[cmd]; ok { if c.WithArgs(len(f)) && !(line.Public() && c.Timeout()) { log.Println(line.Nick, f) go c.Handle(conn, line) } } } // extract single link and fetch title for _, v := range f { if strings.HasPrefix(v, "http") { go func(url string) { log.Println(line.Nick, url) t, err := FetchTitle(url) if err != nil { log.Println(err) } if t != "" { conn.Privmsg(line.Target(), "Title: "+t) } }(v) } } if m := MedianLength(f); len(f) > 4 && m < 2 { log.Println("kick", line.Nick) conn.Kick(*room, line.Nick, "flood") } } func init() { flag.Parse() Register("last", &Last{ Command{ Help: "Return last 10 messages", }, }) Register("theo", &Theo{ Command{ Help: "Quote Theo De Raadt", }, }) Register("news", &RSS{ Command{ Help: "LOR news (msg private to see all)", Arg: `https://www.linux.org.ru/section-rss.jsp?section=1`, }, }) Register("forum", &RSS{ Command{ Help: "LOR forum (msg private to see all)", Arg: `https://www.linux.org.ru/section-rss.jsp?section=2`, }, }) Register("gallery", &RSS{ Command{ Help: "LOR gallery (msg private to see all)", Arg: `https://www.linux.org.ru/section-rss.jsp?section=3`, }, }) Register("bsd", &RSS{ Command{ Help: "Undeadly news (msg private to see all)", Arg: `http://undeadly.org/cgi?action=rss`, }, }) Register("help", &Help{ Command{ Help: "This help", }, }) Register("top", &Top{ Command{ Help: "Top 10 flooder (msg private to see top 100)", }, }) Register("define", &Duck{ Command{ Help: "Perform duckduckgo instant answer search", }, }) Register("blame", &Blame{ Command{ Help: "Blame author and return link to source code", }, }) } func main() { c := irc.SimpleClient(*name) c.EnableStateTracking() quit := make(chan bool) c.HandleFunc(irc.DISCONNECTED, func(conn *irc.Conn, line *irc.Line) { quit <- true }) c.HandleFunc(irc.CONNECTED, func(conn *irc.Conn, line *irc.Line) { conn.Join(*room) }) c.HandleFunc(irc.PRIVMSG, privmsg) if err := c.ConnectTo(*server); err != nil { log.Fatal(err) } go autoSave() <-quit }