func watchUntappd(irc *goirc.Connection) { var ( userEvents []string err error ) lastCheckins := make(map[string]float64) for { for _, user := range cfg.Untappd.Users { // store the last checkin ID, to avoid printing the same checkin twice var lastcheckin float64 if _, ok := lastCheckins[user]; ok { lastcheckin = lastCheckins[user] } if cfg.Untappd.Debug { fmt.Println("query untappd activity for", user, "with last checkin set to", lastcheckin) } userEvents, lastCheckins[user], err = getUntappdActivityFor(user, lastcheckin) if err != nil { log.Println("Failed to get", user, "'s Untappd activity:", err) } else { for _, ev := range userEvents { irc.Privmsgf(cfg.Irc.Channel, "%s", ev) } } time.Sleep(60 * time.Second) } } }
func watchGithub(irc *goirc.Connection) { var err error // start the github watcher evchan := make(chan string) githubCli := makeGithubClient(cfg.Github.Token) for _, repo := range cfg.Github.Repos { splitted := strings.Split(repo, "/") if len(splitted) != 2 { irc.Privmsgf(cfg.Irc.Channel, "Invalid repository syntax '%s'. Must be <owner>/<reponame>", repo) continue } // don't run everything at once, we've got time... time.Sleep(time.Second) go func() { for { err = followRepoEvents(githubCli, splitted[0], splitted[1], evchan) if err != nil { log.Println("github follower crashed with error", err) } time.Sleep(60 * time.Second) } }() } go func() { for ev := range evchan { // no more than one post per second time.Sleep(time.Second) irc.Privmsgf(cfg.Irc.Channel, "%s", ev) } }() }
// send strings to IRC func sendIRC(s []string, i *irc.Connection, e *irc.Event, ready chan bool) { i.SendRaw("PRIVMSG " + e.Arguments[0] + " :\x031,9d-(^_^)z \x039,1 check your PMs for /r/" + e.Message[8:]) for _, str := range s { i.SendRaw("PRIVMSG " + e.Nick + " :" + str) time.Sleep(time.Second * 1) } ready <- true }
// JoinCallback triggers on JOIN event which happens whenever *anyone* joins func JoinCallback(con *irc.Connection) { con.AddCallback("JOIN", func(e *irc.Event) { if e.Nick != config.IRCNick { // Exclude bot joins log.Printf("%s joined %s", e.Nick, e.Arguments[0]) } else { log.Printf(e.Message()) } }) }
func handleAuth(irc *goirc.Connection) { // place a callback on nickserv identification and wait until it is done if cfg.Irc.Nickpass != "" { identwaiter := make(chan bool) irc.AddCallback("NOTICE", func(e *goirc.Event) { re := regexp.MustCompile("NickServ IDENTIFY") if e.Nick == "NickServ" && re.MatchString(e.Message()) { irc.Privmsgf("NickServ", "IDENTIFY %s", cfg.Irc.Nickpass) } reaccepted := regexp.MustCompile("(?i)Password accepted") if e.Nick == "NickServ" && reaccepted.MatchString(e.Message()) { identwaiter <- true } }) for { select { case <-identwaiter: goto identified case <-time.After(5 * time.Second): irc.Privmsgf("NickServ", "IDENTIFY %s", cfg.Irc.Nickpass) } } identified: irc.ClearCallback("NOTICE") close(identwaiter) } return }
func githubPrintReposList(irc *goirc.Connection) { list := "list of followed github repositories: " for _, repo := range cfg.Github.Repos { list += repo + ", " if len(list) > 300 { irc.Privmsgf(cfg.Irc.Channel, "%s", list) list = "" } } if len(list) > 0 { irc.Privmsgf(cfg.Irc.Channel, "%s", list) } return }
// PrivMsgCallback (PRIVMSG) is really any message in IRC that the bot sees func PrivMsgCallback(con *irc.Connection) { con.AddCallback("PRIVMSG", func(e *irc.Event) { message := e.Arguments[1] channel := e.Arguments[0] log.Printf("%s said: %s\n", e.Nick, message) // Look for urls in messages urls := urlRegex.FindAllString(message, -1) if len(urls) > 0 { for _, url := range urls { messageCh := make(chan string, 1) go scrapePage(url, messageCh) con.Privmsg(channel, <-messageCh) } } }) }
func fetchPageTitles(irc *goirc.Connection) { irc.AddCallback("PRIVMSG", func(e *goirc.Event) { rehttp := regexp.MustCompile("(https?://.+)") if rehttp.MatchString(e.Message()) { url := rehttp.FindStringSubmatch(e.Message()) if len(url) < 2 { return } title := fetchTitle(url[1]) log.Printf("Retrieved tile '%s' from url %s\n", title, url[1]) if title != "" { irc.Privmsgf(cfg.Irc.Channel, "Title: %s", title) } } }) return }
// WelcomeCallback is a PrivMsgCallback for the welcome event 001, join channels when we're logged in func WelcomeCallback(con *irc.Connection) { con.AddCallback("001", func(e *irc.Event) { con.Privmsgf("NickServ", "identify %s", config.IRCNickPass) time.Sleep(time.Second * 15) // Need to wait a little for the registration to take effect for _, room := range config.IRCChannels { con.Join(room) log.Printf("Connected to channel %s\n", room) } }) }
// ErrorCallback should log anytime an error happens func ErrorCallback(con *irc.Connection) { con.AddCallback("ERROR", func(e *irc.Event) { log.Printf("Error: %#v\n", e) }) }
func main() { var ( irc *goirc.Connection err error ) var configFile = flag.String("c", "r2d2.cfg", "Load configuration from file") flag.Parse() _, err = os.Stat(*configFile) if err != nil { log.Fatal("%v", err) os.Exit(1) } err = gcfg.ReadFileInto(&cfg, *configFile) if err != nil { log.Fatal("Error in configuration file: %v", err) os.Exit(1) } irc = goirc.IRC(cfg.Irc.Nick, cfg.Irc.Nick) irc.UseTLS = cfg.Irc.TLS irc.VerboseCallbackHandler = cfg.Irc.Debug irc.Debug = cfg.Irc.Debug err = irc.Connect(cfg.Irc.Server) if err != nil { log.Fatal("Connection to IRC server failed: %v", err) os.Exit(1) } // block while performing authentication handleAuth(irc) // we are identified, let's continue if cfg.Irc.ChannelPass != "" { // if a channel pass is used, craft a join command // of the form "&<channel>; <key>" irc.Join(cfg.Irc.Channel + " " + cfg.Irc.ChannelPass) } else { irc.Join(cfg.Irc.Channel) } if cfg.Irc.Debug { irc.Privmsg(cfg.Irc.Channel, "beep beedibeep dibeep") } go watchGithub(irc) go watchUntappd(irc) go fetchPageTitles(irc) initMaxmind() // add callback that captures messages sent to bot terminate := make(chan bool) irc.AddCallback("PRIVMSG", func(e *goirc.Event) { re := regexp.MustCompile("^" + cfg.Irc.Nick + ":(.+)$") if re.MatchString(e.Message()) { parsed := re.FindStringSubmatch(e.Message()) if len(parsed) != 2 { return } req := strings.Trim(parsed[1], " ") resp := handleRequest(e.Nick, req, irc) if resp != "" { irc.Privmsgf(cfg.Irc.Channel, "%s: %s", e.Nick, resp) } } }) <-terminate irc.Loop() irc.Disconnect() }