package main import ( "fmt" "os" "strings" "time" ) const programName = "statbot" type messageProcessor interface { GetName() string GetHelp() string ProcessMessage(s *Slack, m *Message) ProcessAdminMessage(s *Slack, m *Message) ProcessBotMessage(s *Slack, m *Message) ProcessUserMessage(s *Slack, m *Message) ProcessAdminUserMessage(s *Slack, m *Message) ProcessBotUserMessage(s *Slack, m *Message) ProcessChannelMessage(s *Slack, m *Message) ProcessAdminChannelMessage(s *Slack, m *Message) ProcessBotChannelMessage(s *Slack, m *Message) } var messageProcessors []messageProcessor type statProcessor interface { GetName() string GetStatKeys() []string ProcessMessage(m *Message) ProcessBotMessage(m *Message) ProcessUserMessage(m *Message) ProcessBotUserMessage(m *Message) ProcessChannelMessage(m *Message) ProcessBotChannelMessage(m *Message) } var statProcessors []statProcessor func main() { if len(os.Args) != 2 { fmt.Fprintf(os.Stderr, "usage: statbot slack-bot-token\n") os.Exit(1) } var err error var slack *Slack if err = initDatabase(); err != nil { panic(err) } if slack, err = CreateSlack(os.Args[1]); err != nil { panic(err) } // For now, we're not running the web server statWebMain(slack) statBotMain(slack) } // This is the main function for the statbot func statBotMain(slack *Slack) { // start a websocket-based Real Time API session registerStatProcessor(new(levelUpStatProcessor)) registerStatProcessor(new(generalStatProcessor)) registerMessageProcessor(new(generalProcessor)) fmt.Println("statbot ready, ^C exits") writeToLog("== " + time.Now().Format(time.RFC3339) + " - Bot Started ==\n") for { // read each incoming message m, err := slack.getMessage() if err == nil { processMessage(slack, &m) } } writeToLog("== " + time.Now().Format(time.RFC3339) + " - Bot Stopped ==\n\n") } func processMessage(slack *Slack, m *Message) { /* if mb, me := json.Marshal(m); me == nil { // Write the JSON representation to the log writeToLog(string(mb) + "\n") } */ // TODO: Handle reaction_added messages // TODO: Handle reaction_removed messages if m.Type == "message" { var err error var usr *User // Check if we know who the user is usr, err = getUserInfo(m.User) // If the user information hasn't been updated in the last day, update it. if err != nil || usr.LastUpdated.IsZero() || time.Since(usr.LastUpdated) > (time.Hour*24) { if u, ue := slack.getUserInfo(m.User); ue == nil { saveUserInfo(u) } } for _, stats := range statProcessors { if err == nil { if usr.IsBot { stats.ProcessBotMessage(m) } else { stats.ProcessMessage(m) } } } for _, proc := range messageProcessors { if isAdmin(m.User) { proc.ProcessAdminMessage(slack, m) } if usr.IsBot { proc.ProcessBotMessage(slack, m) } else { proc.ProcessMessage(slack, m) } } if m.Channel != "" { // Check if we know what the channel is chnl, err := getChannelInfo(m.Channel) var isDirectMessage bool // If the channel information hasn't been updated in the last day, update it. if err != nil || chnl.LastUpdated.IsZero() || time.Since(chnl.LastUpdated) > (time.Hour*24) { // Either we don't have this channel, or it's a direct message if c, ce := slack.getChannelInfo(m.Channel); ce == nil { // Save channel info saveChannelInfo(c) } else { isDirectMessage = true } } if isDirectMessage { // Invalid channel, save as a direct message saveUserMessage(m.User, m) for _, stats := range statProcessors { stats.ProcessUserMessage(m) } for _, proc := range messageProcessors { if isAdmin(m.User) { proc.ProcessAdminUserMessage(slack, m) } proc.ProcessUserMessage(slack, m) } } else { // And save the channel message err = saveChannelMessage(m.Channel, m) for _, proc := range statProcessors { proc.ProcessChannelMessage(m) } for _, proc := range messageProcessors { proc.ProcessChannelMessage(slack, m) } } } } } func registerMessageProcessor(b messageProcessor) { // Register a Message Processor // Make sure that we haven't already registered it for _, proc := range messageProcessors { if proc.GetName() == b.GetName() { panic(fmt.Errorf("Attempted to Re-register Message Processor %s", b.GetName())) } } messageProcessors = append(messageProcessors, b) } func registerStatProcessor(b statProcessor) { // Register a Statistic Processor // First make sure that we don't have any 'key' collisions for _, proc := range statProcessors { for _, testKey := range proc.GetStatKeys() { for _, k := range b.GetStatKeys() { if strings.Replace(testKey, "*", "", -1) == strings.Replace(k, "*", "", -1) { panic(fmt.Errorf("Stat Key Collision (%s=>%s and %s=>%s)", b.GetName(), k, proc.GetName(), testKey, )) } } } } statProcessors = append(statProcessors, b) } func writeToLog(d string) { f, err := os.OpenFile("statbot.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0664) if err != nil { panic(err) } f.WriteString(d) f.Close() }