From 8f70393598e699b131c8fc755476d00de52ec86b Mon Sep 17 00:00:00 2001 From: Brian Buller Date: Fri, 15 Nov 2019 13:50:22 -0600 Subject: [PATCH] Helperbot & the AoC Plugin are working pretty well --- cmd/app.go | 4 +- cmd/model_slack.go | 12 +- interfaces.go | 1 + plugins_src/plugin_aoc.go | 318 +++++++++++++++++++++++++++++--------- 4 files changed, 251 insertions(+), 84 deletions(-) diff --git a/cmd/app.go b/cmd/app.go index 1e28b09..6f38f50 100644 --- a/cmd/app.go +++ b/cmd/app.go @@ -57,7 +57,7 @@ func (a *App) initialize() error { slackToken, _ = reader.ReadString('\n') a.m.setSlackToken(strings.TrimSpace(slackToken)) } - slackDMid, err = a.m.getSlackAdminDMId() + slackDMid, err = a.m.GetSlackAdminDMId() if err != nil || slackDMid == "" { fmt.Print("Slack Admin DM ID: ") slackDMid, _ = reader.ReadString('\n') @@ -79,6 +79,8 @@ func (a *App) watchMessageChannel() { if slackMsg.Type == "control" && slackMsg.Name == "quit" { a.running = false break + } else if msg.GetDestination() == "error" { + fmt.Printf("ERROR: %s: %s\n", msg.GetSource(), msg.GetMessage().Text) } else if msg.GetDestination() == "slack" { a.m.SendSlackChannelMessage(&slackMsg) } diff --git a/cmd/model_slack.go b/cmd/model_slack.go index a13ad1f..40136f8 100644 --- a/cmd/model_slack.go +++ b/cmd/model_slack.go @@ -17,18 +17,10 @@ func (m *BotModel) setSlackAdminDMId(adminId string) error { return m.SetString([]string{"slack", "config", "admin_dm_id"}, adminId) } -func (m *BotModel) getSlackAdminDMId() (string, error) { +func (m *BotModel) GetSlackAdminDMId() (string, error) { return m.GetString([]string{"slack", "config", "admin_dm_id"}) } -func (m *BotModel) setSlackChannelId(chanId string) error { - return m.SetString([]string{"slack", "config", "channel_id"}, chanId) -} - -func (m *BotModel) getSlackChannelId() (string, error) { - return m.GetString([]string{"slack", "config", "channel_id"}) -} - /* End DB Functions */ func (m *BotModel) NewSlack() error { @@ -66,7 +58,7 @@ func (m *BotModel) SendSlackChannelMessage(msg *slack.Message) error { func (m *BotModel) SendSlackAdminMessage(msg *slack.Message) error { // Send message to slack admin var err error - msg.Channel, err = m.getSlackAdminDMId() + msg.Channel, err = m.GetSlackAdminDMId() if err != nil { return err } diff --git a/interfaces.go b/interfaces.go index d987031..a9dfac2 100644 --- a/interfaces.go +++ b/interfaces.go @@ -4,6 +4,7 @@ import slack "git.bullercodeworks.com/brian/go-slack" type Model interface { SendMessage(src, dest string, message slack.Message) + GetSlackAdminDMId() (string, error) GetBytes(path []string) ([]byte, error) SetBytes(path []string, val []byte) error GetString(path []string) (string, error) diff --git a/plugins_src/plugin_aoc.go b/plugins_src/plugin_aoc.go index c162fec..68fb074 100644 --- a/plugins_src/plugin_aoc.go +++ b/plugins_src/plugin_aoc.go @@ -20,9 +20,10 @@ import ( /* Plugin State */ type AoCState struct { - model helperbot.Model - boardId string - sessionCookie string + model helperbot.Model + boardId string + sessionCookie string + sessionNeedsUpdate bool aoc *aoc.AoC } @@ -47,6 +48,8 @@ func (s *AoCState) Initialize(m helperbot.Model) error { aocSession = strings.TrimSpace(aocSession) if err != nil || aocSession == "" { s.RequestSessionCookie() + } else { + s.sessionCookie = aocSession } aocChannelId, err = s.getChannelId() @@ -64,44 +67,27 @@ func (s *AoCState) Initialize(m helperbot.Model) error { func (s *AoCState) ProcessMessage(m helperbot.Message) { if m.GetSource() == "slack" { slackMsg := m.GetMessage() - msgPts := strings.Fields(slackMsg.Text) - if len(msgPts) < 1 { + if len(slackMsg.Channel) == 0 { return } - if msgPts[0] != "!aoc" || len(msgPts) < 2 { - return - } - msgPts = msgPts[1:] - yr, err := strconv.Atoi(msgPts[0]) - if err != nil { - yr = s.GetLatestYear() - } else { - msgPts = msgPts[1:] - } - switch msgPts[0] { - case "ping": - s.model.SendMessage(s.Name(), "slack", slack.Message{ - Type: "message", - Channel: slackMsg.Channel, - Text: ":christmas_tree: PONG :christmas_tree:", - }) - case "top": - var txt string - if yr == -1 { - var err error - txt, err = s.DoTopForAll() - if err != nil { - txt = "Error calculating all-time tops" - } - } else { - txt = s.DoTopForYear(yr) + switch slackMsg.Channel[0] { + case 'C': + s.ProcessChannelMessage(slackMsg) + case 'D': + admin, err := s.model.GetSlackAdminDMId() + if err != nil { + s.model.SendMessage(s.Name(), "error", slack.Message{ + Type: "error", + Text: "Error getting Admin DM Id", + Time: time.Now(), + }) + return + } + if slackMsg.Channel == admin { + s.ProcessAdminDirectMessage(slackMsg) + } else { + s.ProcessDirectMessage(slackMsg) } - s.model.SendMessage(s.Name(), "slack", slack.Message{ - Type: "message", - Channel: slackMsg.Channel, - Text: txt, - }) - return } } } @@ -130,47 +116,18 @@ func (s *AoCState) NewAoC() error { func (s *AoCState) runLoop() { for { - channelId, err := s.getChannelId() + _, err := s.getChannelId() if err != nil { // This plugin fails without a channel id return } for _, yr := range s.GetListOfAoCYears() { - l, err := s.aoc.GetLeaderboard(yr) - if err != nil { - msg := slack.Message{ - Type: "error", - Text: fmt.Sprintf("Error processing leaderboard (%d)", yr), - Time: time.Now(), - } - s.model.SendMessage(s.Name(), "main", msg) - } else { - msg := slack.Message{ - Type: "success", - Text: fmt.Sprintf("Received leaderboard (%d)", yr), - Time: time.Now(), - } - s.model.SendMessage(s.Name(), "main", msg) - // Compare the new leaderboard to the saved one - for _, v := range l.Members { - mbr, err := s.getMember(l.Event, v.ID) - if err != nil { - continue - } - if mbr.Stars != v.Stars { - s.model.SendMessage(s.Name(), "slack", slack.Message{ - Type: "message", - Channel: channelId, - Text: ":christmas_tree: " + v.Name + " now has " + strconv.Itoa(v.Stars) + " stars! :christmas_tree:", - }) - } - } - // Save the leaderboard to the db - s.saveLeaderboard(l) + if !s.sessionNeedsUpdate { + s.AoCBoardCheckAndUpdate(yr) + time.Sleep(time.Minute) } - time.Sleep(time.Minute) } - time.Sleep(time.Minute * 10) + time.Sleep(time.Minute) } s.model.SendMessage(s.Name(), "main", slack.Message{ Type: "status", @@ -179,6 +136,152 @@ func (s *AoCState) runLoop() { }) } +func (s *AoCState) ProcessDirectMessage(slackMsg slack.Message) { + msgPts := strings.Fields(slackMsg.Text) + if len(msgPts) < 2 || msgPts[0] != "!aoc" { + return + } + switch msgPts[1] { + case "help": + s.DoHelpCmd(slackMsg) + case "ping": + s.DoPingCmd(slackMsg) + case "top": + s.DoTopCmd(slackMsg) + } +} + +func (s *AoCState) ProcessAdminDirectMessage(slackMsg slack.Message) { + msgPts := strings.Fields(slackMsg.Text) + if len(msgPts) < 2 || msgPts[0] != "!aoc" { + return + } + switch msgPts[1] { + case "help": + s.DoHelpAdminCmd(slackMsg) + case "ping": + s.DoPingCmd(slackMsg) + case "top": + s.DoTopCmd(slackMsg) + case "session": + s.DoSessionCmd(slackMsg) + } +} + +func (s *AoCState) ProcessChannelMessage(slackMsg slack.Message) { + msgPts := strings.Fields(slackMsg.Text) + if len(msgPts) < 2 || msgPts[0] != "!aoc" { + return + } + switch msgPts[1] { + case "top": + s.DoTopCmd(slackMsg) + } +} + +func (s *AoCState) DoHelpCmd(slackMsg slack.Message) { + txt := fmt.Sprint(":christmas_tree: AoC Help :christmas_tree:\n", + "-- WiP --", + ) + s.model.SendMessage(s.Name(), "slack", slack.Message{ + Type: "message", + Channel: slackMsg.Channel, + Text: txt, + }) +} + +func (s *AoCState) DoHelpAdminCmd(slackMsg slack.Message) { + txt := fmt.Sprint(":christmas_tree: AoC Help :christmas_tree:\n", + "-- WiP --", + ) + s.model.SendMessage(s.Name(), "slack", slack.Message{ + Type: "message", + Channel: slackMsg.Channel, + Text: txt, + }) +} + +func (s *AoCState) DoPingCmd(slackMsg slack.Message) { + s.model.SendMessage(s.Name(), "slack", slack.Message{ + Type: "message", + Channel: slackMsg.Channel, + Text: ":christmas_tree: PONG :christmas_tree:", + }) +} + +func (s *AoCState) DoSessionCmd(slackMsg slack.Message) { + msgPts := strings.Fields(slackMsg.Text) + if len(msgPts) == 3 && msgPts[1] == "session" { + // Set the session cookie + admin, err := s.model.GetSlackAdminDMId() + if err != nil { + s.model.SendMessage(s.Name(), "error", slack.Message{ + Type: "error", + Text: "Error getting Admin DM Id", + Time: time.Now(), + }) + return + } + aocSession := msgPts[2] + s.setAoCSessionCookie(strings.TrimSpace(aocSession)) + s.sessionCookie = aocSession + s.sessionNeedsUpdate = false + s.model.SendMessage(s.Name(), "slack", slack.Message{ + Type: "message", + Channel: admin, + Text: ":christmas_tree: New Session: " + s.sessionCookie, + }) + } else if len(msgPts) == 2 && msgPts[1] == "session" { + // Print the session cookie + admin, err := s.model.GetSlackAdminDMId() + if err != nil { + s.model.SendMessage(s.Name(), "error", slack.Message{ + Type: "error", + Text: "Error getting Admin DM Id", + Time: time.Now(), + }) + return + } + // We only send the session cookie to the admin + s.model.SendMessage(s.Name(), "slack", slack.Message{ + Type: "message", + Channel: admin, + Text: ":christmas_tree: session: " + s.sessionCookie, + }) + } +} + +func (s *AoCState) DoTopCmd(slackMsg slack.Message) { + msgPts := strings.Fields(slackMsg.Text) + var err error + var yr int + if len(msgPts) > 2 { + yr, err = strconv.Atoi(msgPts[2]) + if err != nil { + if msgPts[2] == "all" { + yr = -1 + } + } + } else { + yr = s.GetLatestYear() + } + var txt string + if yr == -1 { + var err error + txt, err = s.DoTopForAll() + if err != nil { + txt = "Error calculating all-time tops" + } + } else { + txt = s.DoTopForYear(yr) + } + s.model.SendMessage(s.Name(), "slack", slack.Message{ + Type: "message", + Channel: slackMsg.Channel, + Text: txt, + }) +} + func (s *AoCState) DoTopForAll() (string, error) { mbrMap := make(map[string]aoc.Member) for _, yr := range s.GetListOfAoCYears() { @@ -269,6 +372,74 @@ func (s *AoCState) GetAoCBoard(yr int) (*aoc.Leaderboard, error) { return s.aoc.GetLeaderboard(yr) } +func (s *AoCState) AoCBoardCheckAndUpdate(yr int) { + channelId, _ := s.getChannelId() + if s.AoCBoardNeedsUpdate(yr) { + l, err := s.aoc.GetLeaderboard(yr) + if err != nil { + admin, adminErr := s.model.GetSlackAdminDMId() + if adminErr != nil { + s.model.SendMessage(s.Name(), "error", slack.Message{ + Type: "error", + Text: "Error getting Admin DM Id", + Time: time.Now(), + }) + return + } + if err.Error() == "Invalid Session Cookie" { + s.sessionNeedsUpdate = true + } + s.model.SendMessage(s.Name(), "slack", slack.Message{ + Type: "message", + Channel: admin, + Text: fmt.Sprintf(":warning: AoC Error processing leaderboard (%d) - %s", yr, err.Error()), + }) + return + } + + msg := slack.Message{ + Type: "success", + Text: fmt.Sprintf("Received leaderboard (%d)", yr), + Time: time.Now(), + } + s.model.SendMessage(s.Name(), "main", msg) + // Compare the new leaderboard to the saved one + for _, v := range l.Members { + mbr, err := s.getMember(l.Event, v.ID) + if err != nil { + continue + } + if mbr.Stars != v.Stars { + s.model.SendMessage(s.Name(), "slack", slack.Message{ + Type: "message", + Channel: channelId, + Text: ":christmas_tree: " + v.Name + " now has " + strconv.Itoa(v.Stars) + " stars! :christmas_tree:", + }) + } + } + // Save the leaderboard to the db + s.saveLeaderboard(l) + } +} + +func (s *AoCState) AoCBoardNeedsUpdate(yr int) bool { + var freshDt time.Duration + if yr == s.GetLatestYear() { + freshDt, _ = time.ParseDuration("10m") + } else { + freshDt, _ = time.ParseDuration("1h") + } + l, err := s.aoc.GetCachedLeaderboard(yr) + if err != nil { + if err.Error() == "Invalid Year" { + return false + } + // This board is not cached, we need to pull it + return true + } + return time.Since(l.LastFetch) > freshDt +} + func (s *AoCState) RequestBoardId() { reader := bufio.NewReader(os.Stdin) fmt.Print("Advent of Code Board ID: ") @@ -281,6 +452,7 @@ func (s *AoCState) RequestSessionCookie() { fmt.Print("Advent of Code Session Cookie: ") aocSession, _ := reader.ReadString('\n') s.setAoCSessionCookie(strings.TrimSpace(aocSession)) + s.sessionCookie = aocSession } // The channel that we post updates to @@ -327,7 +499,7 @@ func (s *AoCState) saveLeaderboard(l *aoc.Leaderboard) error { } for _, v := range l.Members { if err = s.saveMember(l.Event, &v); err != nil { - s.model.SendMessage(s.Name(), "main", slack.Message{ + s.model.SendMessage(s.Name(), "error", slack.Message{ Type: "error", Text: fmt.Sprintf("Error Saving Member (%s)", v.Name), Time: time.Now(),