Helperbot & the AoC Plugin are working pretty well

This commit is contained in:
Brian Buller 2019-11-15 13:50:22 -06:00
parent 0bbab771ce
commit 8f70393598
4 changed files with 251 additions and 84 deletions

View File

@ -57,7 +57,7 @@ func (a *App) initialize() error {
slackToken, _ = reader.ReadString('\n') slackToken, _ = reader.ReadString('\n')
a.m.setSlackToken(strings.TrimSpace(slackToken)) a.m.setSlackToken(strings.TrimSpace(slackToken))
} }
slackDMid, err = a.m.getSlackAdminDMId() slackDMid, err = a.m.GetSlackAdminDMId()
if err != nil || slackDMid == "" { if err != nil || slackDMid == "" {
fmt.Print("Slack Admin DM ID: ") fmt.Print("Slack Admin DM ID: ")
slackDMid, _ = reader.ReadString('\n') slackDMid, _ = reader.ReadString('\n')
@ -79,6 +79,8 @@ func (a *App) watchMessageChannel() {
if slackMsg.Type == "control" && slackMsg.Name == "quit" { if slackMsg.Type == "control" && slackMsg.Name == "quit" {
a.running = false a.running = false
break break
} else if msg.GetDestination() == "error" {
fmt.Printf("ERROR: %s: %s\n", msg.GetSource(), msg.GetMessage().Text)
} else if msg.GetDestination() == "slack" { } else if msg.GetDestination() == "slack" {
a.m.SendSlackChannelMessage(&slackMsg) a.m.SendSlackChannelMessage(&slackMsg)
} }

View File

@ -17,18 +17,10 @@ func (m *BotModel) setSlackAdminDMId(adminId string) error {
return m.SetString([]string{"slack", "config", "admin_dm_id"}, adminId) 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"}) 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 */ /* End DB Functions */
func (m *BotModel) NewSlack() error { 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 { func (m *BotModel) SendSlackAdminMessage(msg *slack.Message) error {
// Send message to slack admin // Send message to slack admin
var err error var err error
msg.Channel, err = m.getSlackAdminDMId() msg.Channel, err = m.GetSlackAdminDMId()
if err != nil { if err != nil {
return err return err
} }

View File

@ -4,6 +4,7 @@ import slack "git.bullercodeworks.com/brian/go-slack"
type Model interface { type Model interface {
SendMessage(src, dest string, message slack.Message) SendMessage(src, dest string, message slack.Message)
GetSlackAdminDMId() (string, error)
GetBytes(path []string) ([]byte, error) GetBytes(path []string) ([]byte, error)
SetBytes(path []string, val []byte) error SetBytes(path []string, val []byte) error
GetString(path []string) (string, error) GetString(path []string) (string, error)

View File

@ -23,6 +23,7 @@ type AoCState struct {
model helperbot.Model model helperbot.Model
boardId string boardId string
sessionCookie string sessionCookie string
sessionNeedsUpdate bool
aoc *aoc.AoC aoc *aoc.AoC
} }
@ -47,6 +48,8 @@ func (s *AoCState) Initialize(m helperbot.Model) error {
aocSession = strings.TrimSpace(aocSession) aocSession = strings.TrimSpace(aocSession)
if err != nil || aocSession == "" { if err != nil || aocSession == "" {
s.RequestSessionCookie() s.RequestSessionCookie()
} else {
s.sessionCookie = aocSession
} }
aocChannelId, err = s.getChannelId() aocChannelId, err = s.getChannelId()
@ -64,45 +67,28 @@ func (s *AoCState) Initialize(m helperbot.Model) error {
func (s *AoCState) ProcessMessage(m helperbot.Message) { func (s *AoCState) ProcessMessage(m helperbot.Message) {
if m.GetSource() == "slack" { if m.GetSource() == "slack" {
slackMsg := m.GetMessage() slackMsg := m.GetMessage()
msgPts := strings.Fields(slackMsg.Text) if len(slackMsg.Channel) == 0 {
if len(msgPts) < 1 {
return return
} }
if msgPts[0] != "!aoc" || len(msgPts) < 2 { switch slackMsg.Channel[0] {
return case 'C':
} s.ProcessChannelMessage(slackMsg)
msgPts = msgPts[1:] case 'D':
yr, err := strconv.Atoi(msgPts[0]) admin, err := s.model.GetSlackAdminDMId()
if err != nil { if err != nil {
yr = s.GetLatestYear() s.model.SendMessage(s.Name(), "error", slack.Message{
} else { Type: "error",
msgPts = msgPts[1:] Text: "Error getting Admin DM Id",
} Time: time.Now(),
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)
}
s.model.SendMessage(s.Name(), "slack", slack.Message{
Type: "message",
Channel: slackMsg.Channel,
Text: txt,
}) })
return return
} }
if slackMsg.Channel == admin {
s.ProcessAdminDirectMessage(slackMsg)
} else {
s.ProcessDirectMessage(slackMsg)
}
}
} }
} }
@ -130,47 +116,18 @@ func (s *AoCState) NewAoC() error {
func (s *AoCState) runLoop() { func (s *AoCState) runLoop() {
for { for {
channelId, err := s.getChannelId() _, err := s.getChannelId()
if err != nil { if err != nil {
// This plugin fails without a channel id // This plugin fails without a channel id
return return
} }
for _, yr := range s.GetListOfAoCYears() { for _, yr := range s.GetListOfAoCYears() {
l, err := s.aoc.GetLeaderboard(yr) if !s.sessionNeedsUpdate {
if err != nil { s.AoCBoardCheckAndUpdate(yr)
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)
}
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{ s.model.SendMessage(s.Name(), "main", slack.Message{
Type: "status", 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) { func (s *AoCState) DoTopForAll() (string, error) {
mbrMap := make(map[string]aoc.Member) mbrMap := make(map[string]aoc.Member)
for _, yr := range s.GetListOfAoCYears() { for _, yr := range s.GetListOfAoCYears() {
@ -269,6 +372,74 @@ func (s *AoCState) GetAoCBoard(yr int) (*aoc.Leaderboard, error) {
return s.aoc.GetLeaderboard(yr) 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() { func (s *AoCState) RequestBoardId() {
reader := bufio.NewReader(os.Stdin) reader := bufio.NewReader(os.Stdin)
fmt.Print("Advent of Code Board ID: ") fmt.Print("Advent of Code Board ID: ")
@ -281,6 +452,7 @@ func (s *AoCState) RequestSessionCookie() {
fmt.Print("Advent of Code Session Cookie: ") fmt.Print("Advent of Code Session Cookie: ")
aocSession, _ := reader.ReadString('\n') aocSession, _ := reader.ReadString('\n')
s.setAoCSessionCookie(strings.TrimSpace(aocSession)) s.setAoCSessionCookie(strings.TrimSpace(aocSession))
s.sessionCookie = aocSession
} }
// The channel that we post updates to // The channel that we post updates to
@ -327,7 +499,7 @@ func (s *AoCState) saveLeaderboard(l *aoc.Leaderboard) error {
} }
for _, v := range l.Members { for _, v := range l.Members {
if err = s.saveMember(l.Event, &v); err != nil { 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", Type: "error",
Text: fmt.Sprintf("Error Saving Member (%s)", v.Name), Text: fmt.Sprintf("Error Saving Member (%s)", v.Name),
Time: time.Now(), Time: time.Now(),