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')
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)
}

View File

@ -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
}

View File

@ -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)

View File

@ -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(),