diff --git a/.gitignore b/.gitignore index d16fb86..baf4c98 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ cmd/helperbot cmd/*.db cmd/cmd helperbot.tgz + +build/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e4a88b4 --- /dev/null +++ b/Makefile @@ -0,0 +1,15 @@ + +helperbot: + go build -o build/helperbot cmd/helperbot/*.go + +plugins: + ./buildplugins.sh + +package: + go build -o build/helperbot cmd/helperbot/*.go + ./buildplugins.sh + cp cmd/helperbot/helperbot.service build/ + cd build && tar -zcvf helperbot.tgz * + +aoc-util: + go build -o build/aoc-util cmd/aoc-util/*.go diff --git a/buildplugins.sh b/buildplugins.sh index 513e54d..b7576e0 100755 --- a/buildplugins.sh +++ b/buildplugins.sh @@ -1,11 +1,12 @@ #!/bin/bash +mkdir -p build/plugins cd plugins_src for i in `ls`; do echo "Building plugin: $i" go build -buildmode=plugin $i if [ $? -eq 0 ]; then - mv *.so ../cmd/plugins + mv *.so ../build/plugins fi done cd .. diff --git a/cmd/aoc-util/main.go b/cmd/aoc-util/main.go new file mode 100644 index 0000000..aa44d17 --- /dev/null +++ b/cmd/aoc-util/main.go @@ -0,0 +1,211 @@ +package main + +import ( + "fmt" + "os" + "sort" + "strconv" + "time" + + "git.bullercodeworks.com/brian/boltease" +) + +var db *boltease.DB + +func main() { + _, err := os.Stat("helperbot.db") + if err != nil { + fmt.Println("Couldn't find helperbot.db (is it in the CWD?)") + os.Exit(1) + } + if len(os.Args) == 1 { + printUsage([]string{}) + os.Exit(1) + } + run(os.Args[1:]) +} + +func run(args []string) int { + var cmd string + if len(args) == 0 { + printUsage(args) + return 1 + } + var err error + db, err = boltease.Create("helperbot.db", 0600, nil) + if err != nil { + fmt.Println("Error opening database") + return 1 + } + err = db.OpenDB() + if err != nil { + fmt.Println("Error opening database") + return 1 + } + defer db.CloseDB() + + cmd, args = args[0], args[1:] + switch cmd { + case "clear-stars": + return clearYearStars(args) + case "print-member-list": + return printMemberList(args) + case "view-member": + return viewMember(args) + } + return 0 +} + +func clearYearStars(args []string) int { + if len(args) < 1 { + printUsage([]string{"clear-stars"}) + return 1 + } + yr := args[0] + if !isValidYear(yr) { + fmt.Println("Invalid year provided") + return 1 + } + var err error + var members []string + membersPath := []string{"aoc", "leaderboards", yr, "members"} + members, err = db.GetBucketList(membersPath) + if err != nil { + fmt.Println("Error getting member list for year", yr) + return 1 + } + for _, v := range members { + memberPath := append(membersPath, v) + err = db.SetInt(memberPath, "stars", 0) + if err != nil { + fmt.Printf("Error resetting stars for [%s].\n", v) + fmt.Println(err) + fmt.Println("") + } + } + + return 0 +} + +func printMemberList(args []string) int { + memberMap := make(map[string]string) + memberIdMap := make(map[string]string) + var memberIds []string + var memberNames []string + years := getYearsInDB() + for _, v := range years { + var err error + var members []string + yearMembersPath := []string{"aoc", "leaderboards", v, "members"} + members, err = db.GetBucketList(yearMembersPath) + if err != nil { + fmt.Println("Error getting member list for year:", v) + continue + } + for _, mid := range members { + memberPath := append(yearMembersPath, mid) + _, ok := memberIdMap[mid] + if !ok { + memberIds = append(memberIds, mid) + name, err := db.GetValue(memberPath, "name") + if err != nil { + continue + } + memberNames = append(memberNames, name) + memberMap[name] = mid + memberIdMap[mid] = name + } + } + } + + sort.Strings(memberNames) + for _, v := range memberNames { + fmt.Printf("%s %s\n", v, memberMap[v]) + } + return 0 +} + +func viewMember(args []string) int { + if len(args) == 0 { + printUsage([]string{"view-member"}) + return 1 + } + years := getYearsInDB() + mId := args[0] + pInfo := false + for _, v := range years { + memberPath := []string{"aoc", "leaderboards", v, "members", mId} + if !pInfo { + name, err := db.GetValue(memberPath, "name") + if err != nil { + continue + } + fmt.Println(name, mId) + pInfo = true + } + stars, err := db.GetValue(memberPath, "stars") + if err != nil { + continue + } + fmt.Println(v, "stars:", stars) + } + + return 0 +} + +func printUsage(args []string) { + if len(args) == 0 { + fmt.Println("Usage: aoc-util [command]") + fmt.Println("Commands:") + fmt.Println(" clear-stars [year] Reset all member's star count for the given year") + return + } + switch args[0] { + case "clear-stars": + fmt.Println("Usage: aoc-util clear-stars [year]") + case "print-member-list": + fmt.Println("Usage: aoc-util print-member-list") + case "print-member": + fmt.Println("Usage: aoc-util view-member [id]") + } +} + +func getLatestYear() int { + latestYear := time.Now().Year() + if time.Now().Month() < 12 { + latestYear-- + } + return latestYear +} + +func getListOfAoCYears() []int { + var ret []int + for k := getLatestYear(); k > 2014; k-- { + ret = append(ret, k) + } + return ret +} + +func getYearsInDB() []string { + leaderboardsPath := []string{"aoc", "leaderboards"} + years, err := db.GetBucketList(leaderboardsPath) + if err != nil { + fmt.Println("Error getting year list") + return []string{} + } + return years +} + +func isValidYear(yr string) bool { + i, err := strconv.Atoi(yr) + if err != nil { + return false + } + yrs := getListOfAoCYears() + for k := range yrs { + if yrs[k] == i { + return true + } + } + return false +} diff --git a/cmd/app.go b/cmd/app.go deleted file mode 100644 index ef8dec0..0000000 --- a/cmd/app.go +++ /dev/null @@ -1,105 +0,0 @@ -package main - -import ( - "bufio" - "fmt" - "os" - "strings" - - "github.com/nlopes/slack" -) - -type App struct { - DebugMode bool - running bool - - m *BotModel - - plugins []HelperPlugin -} - -func NewApp() (*App, error) { - a := new(App) - if DebugMode { - fmt.Println("Running in Debug Mode. All messages will be sent to Admin DM") - } - a.DebugMode = DebugMode - - err := a.initialize() - if err != nil { - return nil, err - } - a.running = true - - go a.MonitorSlackMessages() - return a, nil -} - -func (a *App) initialize() error { - var err error - if a.m, err = NewBotModel(); err != nil { - return err - } - bt, bterr := a.m.GetString([]string{"config", "plugin_dir"}) - if bterr == nil { - fmt.Println(bt) - } - - reader := bufio.NewReader(os.Stdin) - // Load up the plugins - pluginDir := strings.TrimSpace(a.m.getPluginDir()) - a.LoadPluginsFromDirectory(pluginDir) - if err != nil { - fmt.Println("Error loading plugins") - fmt.Println(err.Error()) - os.Exit(1) - } - - // Now initialize the Slack stuff - var slackToken string - // var slackDmid string - slackToken, err = a.m.getSlackToken() - if err != nil || slackToken == "" { - fmt.Print("Slack API Token: ") - slackToken, _ = reader.ReadString('\n') - a.m.setSlackToken(strings.TrimSpace(slackToken)) - } - - go a.watchMessageChannel() - go a.watchRTMEventChannel() - return nil -} - -func (a *App) MonitorSlackMessages() { - for msg := range a.m.IncomingSlackMessages { - a.m.SendMessage("slack", "main", slack.Message(*msg)) - } -} - -func (a *App) watchMessageChannel() { - for a.running { - msg := <-a.m.messages - slackMsg := msg.GetMessage() - if slackMsg.Type == "control" && slackMsg.Text == "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) - } - - for _, v := range a.plugins { - v.State.ProcessMessage(msg) - } - } -} - -func (a *App) watchRTMEventChannel() { - for a.running { - msg := <-a.m.OtherRTMEvents - for _, v := range a.plugins { - v.State.ProcessRTMEvent(*msg) - } - } -} diff --git a/cmd/helper_plugin.go b/cmd/helper_plugin.go deleted file mode 100644 index 8ccfa8d..0000000 --- a/cmd/helper_plugin.go +++ /dev/null @@ -1,64 +0,0 @@ -package main - -import ( - "fmt" - "io/ioutil" - "os" - "plugin" - "strings" - - "git.bullercodeworks.com/brian/helperbot" -) - -type HelperPlugin struct { - p *plugin.Plugin - - State helperbot.PluginState -} - -func (a *App) LoadPluginsFromDirectory(dir string) error { - fmt.Println("Loading Plugins (", dir, ")") - files, err := ioutil.ReadDir(dir) - if err != nil { - fmt.Println("Error loading plugins") - fmt.Println(err.Error()) - os.Exit(1) - } - for _, f := range files { - if !strings.HasSuffix(f.Name(), ".so") { - fmt.Printf("Skipping file (%s)\n", f.Name()) - continue - } - p, err := plugin.Open(dir + f.Name()) - if err != nil { - fmt.Println(fmt.Sprintf("Error loading plugin (%s)\n", f.Name())) - fmt.Println(err.Error()) - os.Exit(1) - } - hp, err := NewHelperPlugin(p) - if err != nil { - fmt.Println(fmt.Sprintf("Error loading plugin (%s)\n", f.Name())) - fmt.Println(err.Error()) - os.Exit(1) - } - hp.State.Initialize(a.m) - hp.State.Run() - fmt.Printf("Plugin Loaded (%s)\n", f.Name()) - a.plugins = append(a.plugins, *hp) - } - return nil -} - -func NewHelperPlugin(p *plugin.Plugin) (*HelperPlugin, error) { - h := &HelperPlugin{ - p: p, - } - - // Parse the plugin's state - pluginStateSymbol, err := p.Lookup("State") - if err != nil { - return nil, err - } - h.State = pluginStateSymbol.(helperbot.PluginState) - return h, nil -} diff --git a/cmd/helperbot.service b/cmd/helperbot.service deleted file mode 100755 index e0d9865..0000000 --- a/cmd/helperbot.service +++ /dev/null @@ -1,16 +0,0 @@ -[Unit] -Description= -After=syslog.target -After=network.target - -[Service] -Type=simple -User=brbuller -Group=brbuller -WorkingDirectory=/home/brbuller/helperbot/ -ExecStart=/home/brbuller/helperbot/helperbot -Restart=always -Environment="HOME=/home/brbuller/helperbot","USER=brbuller" - -[Install] -WantedBy=multi-user.target diff --git a/cmd/helpers.go b/cmd/helpers.go deleted file mode 100644 index 8584f3c..0000000 --- a/cmd/helpers.go +++ /dev/null @@ -1,14 +0,0 @@ -package main - -import ( - "encoding/json" - - "github.com/nlopes/slack" -) - -func GetMessageJson(msg *slack.Message) string { - if mb, me := json.Marshal(msg); me == nil { - return string(mb) - } - return "" -} diff --git a/cmd/main.go b/cmd/main.go deleted file mode 100644 index c0880fd..0000000 --- a/cmd/main.go +++ /dev/null @@ -1,47 +0,0 @@ -package main - -import ( - "fmt" - "os" - "os/signal" - "syscall" - "time" - - "github.com/nlopes/slack" -) - -var DebugMode = false - -var a *App - -func main() { - if len(os.Args) > 1 { - if os.Args[1] == "-debug" || os.Args[1] == "--debug" { - DebugMode = true - } - } - a, err := NewApp() - if err != nil { - fmt.Println(err) - os.Exit(1) - } - - // Set up a channel to intercept Ctrl+C for graceful shutdowns - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt, syscall.SIGTERM) - go func() { - <-c - // Save the changes when the app quits - fmt.Println("\nFinishing up...") - msg := slack.Message{} - msg.Type = "control" - msg.Text = "quit" - a.m.messages <- NewBotMessage("main", "main", msg) - }() - for a.running { - time.Sleep(time.Second * 2) - } - fmt.Println("Model has stopped running") - fmt.Println("Done") - os.Exit(0) -} diff --git a/cmd/message.go b/cmd/message.go deleted file mode 100644 index 761b166..0000000 --- a/cmd/message.go +++ /dev/null @@ -1,21 +0,0 @@ -package main - -import "github.com/nlopes/slack" - -// This message type is for communications over the messages channel -type BotMessage struct { - source string - dest string - message slack.Message -} - -func NewBotMessage(src, dst string, msg slack.Message) BotMessage { - return BotMessage{ - source: src, - dest: dst, - message: msg, - } -} -func (m BotMessage) GetSource() string { return m.source } -func (m BotMessage) GetDestination() string { return m.dest } -func (m BotMessage) GetMessage() slack.Message { return m.message } diff --git a/cmd/model.go b/cmd/model.go deleted file mode 100644 index 0e82cf3..0000000 --- a/cmd/model.go +++ /dev/null @@ -1,111 +0,0 @@ -package main - -import ( - "errors" - "fmt" - "os" - "strings" - "time" - - "git.bullercodeworks.com/brian/helperbot" - "github.com/br0xen/boltease" - "github.com/nlopes/slack" -) - -type BotModel struct { - db *boltease.DB - - messages chan helperbot.Message - - slackApiToken string - slackApi *slack.Client - slackRTM *slack.RTM - slackRTMLatency time.Duration - slackIdToFriendly map[string]string - IncomingSlackMessages chan *slack.MessageEvent - OtherRTMEvents chan *slack.RTMEvent - - dataCache map[string][]byte -} - -func NewBotModel() (*BotModel, error) { - var err error - m := new(BotModel) - m.dataCache = make(map[string][]byte) - m.messages = make(chan helperbot.Message, 100) - m.db, err = boltease.Create("helperbot.db", 0600, nil) - if err != nil { - return nil, err - } - if err = m.NewSlack(); err != nil { - return nil, err - } - return m, nil -} - -func (m *BotModel) SendMessage(src, dst string, msg slack.Message) { - m.messages <- NewBotMessage(src, dst, msg) -} - -func (m *BotModel) getPluginDir() string { - ret, err := m.GetString([]string{"config", "plugin_dir"}) - if err != nil || strings.TrimSpace(ret) == "" { - ret = "./plugins/" - if err = m.SetString([]string{"config", "plugin_dir"}, ret); err != nil { - fmt.Println("Error setting plugin directory") - fmt.Println(err.Error()) - os.Exit(1) - } - } - fmt.Println("Plugin Dir: ", ret) - return ret -} - -func (m *BotModel) GetBytes(path []string) ([]byte, error) { - var err error - var v []byte - var ok bool - joinedPath := strings.Join(path, "/") - if v, ok = m.dataCache[joinedPath]; !ok { - // Value is not cached, try to pull it from the DB - if len(path) > 2 { - path, key := path[:len(path)-1], path[len(path)-1] - v, err = m.db.GetBytes(path, key) - if err != nil { - return nil, err - } - m.dataCache[joinedPath] = v - } - } - return v, nil -} - -func (m *BotModel) SetBytes(path []string, val []byte) error { - if len(path) > 1 { - joinedPath := strings.Join(path, "/") - path, key := path[:len(path)-1], path[len(path)-1] - err := m.db.SetBytes(path, key, val) - if err != nil { - return err - } - // Update the cache - m.dataCache[joinedPath] = val - return nil - } - return errors.New("Invalid path") -} - -func (m *BotModel) GetString(path []string) (string, error) { - bts, err := m.GetBytes(path) - if err != nil { - return "", err - } - if len(bts) == 0 { - return "", nil - } - return string(bts), nil -} - -func (m *BotModel) SetString(path []string, val string) error { - return m.SetBytes(path, []byte(val)) -} diff --git a/cmd/model_slack.go b/cmd/model_slack.go deleted file mode 100644 index dd54e79..0000000 --- a/cmd/model_slack.go +++ /dev/null @@ -1,228 +0,0 @@ -package main - -import ( - "bufio" - "errors" - "fmt" - "os" - "strings" - "time" - - "github.com/nlopes/slack" -) - -/* DB Functions */ -func (m *BotModel) setSlackToken(token string) error { - return m.SetBytes([]string{"slack", "config", "token"}, []byte(token)) -} - -func (m *BotModel) getSlackToken() (string, error) { - return m.GetString([]string{"slack", "config", "token"}) -} - -func (m *BotModel) setSlackAdminDMId(adminId string) error { - return m.SetString([]string{"slack", "config", "admin_dm_id"}, adminId) -} - -func (m *BotModel) GetSlackAdminDMId() (string, error) { - return m.GetString([]string{"slack", "config", "admin_dm_id"}) -} - -/* End DB Functions */ - -func (m *BotModel) NewSlack() error { - var err error - m.slackApiToken, err = m.getSlackToken() - if err != nil { - if strings.HasPrefix(err.Error(), "Couldn't find") { - m.RequestSlackToken() - } else { - return err - } - } - var slackDMid string - slackDMid, err = m.GetSlackAdminDMId() - if err != nil || slackDMid == "" { - m.RequestAdminDMId() - } - m.IncomingSlackMessages = make(chan *slack.MessageEvent, 50) - m.OtherRTMEvents = make(chan *slack.RTMEvent, 50) - m.slackApi = slack.New(m.slackApiToken) - m.slackIdToFriendly = make(map[string]string) - m.slackRTM = m.slackApi.NewRTM() - m.slackRTMLatency = time.Duration(0) - go m.slackRTM.ManageConnection() - go m.HandleRTMEvents() - return nil -} - -func (m *BotModel) RequestAdminDMId() { - reader := bufio.NewReader(os.Stdin) - fmt.Print("Slack Admin DM ID: ") - dmId, _ := reader.ReadString('\n') - m.setSlackAdminDMId(strings.TrimSpace(dmId)) -} - -func (m *BotModel) RequestSlackToken() { - reader := bufio.NewReader(os.Stdin) - fmt.Print("Slack Token: ") - token, _ := reader.ReadString('\n') - m.slackApiToken = strings.TrimSpace(token) - m.setSlackToken(m.slackApiToken) -} - -func (m *BotModel) HandleRTMEvents() { - for msg := range m.slackRTM.IncomingEvents { - switch ev := msg.Data.(type) { - case *slack.MessageEvent: - m.processMessageEvent(ev) - - case *slack.LatencyReport: - m.OtherRTMEvents <- &msg - - case *slack.RTMError: - fmt.Printf("RTM ERROR: (%d) %s", ev.Code, ev.Msg) - m.OtherRTMEvents <- &msg - - default: // Ignore other events - } - } -} - -func (m *BotModel) SendSlackChannelMessage(msg *slack.Message) error { - if DebugMode { - return m.SendSlackAdminMessage(msg) - } - // Send message to slack channel - m.PostSlackMessage(msg) - return nil -} - -func (m *BotModel) SendSlackAdminMessage(msg *slack.Message) error { - // Send message to slack admin - var err error - msg.Channel, err = m.GetSlackAdminDMId() - if err != nil { - return err - } - m.PostSlackMessage(msg) - return nil -} - -func (m *BotModel) LoadDirectMessages() { - cs, err := m.slackApi.GetIMChannels() - if err != nil { - return - } - for _, v := range cs { - uname, err := m.GetSlackUserName(v.User) - if err != nil { - uname = v.User - } - m.slackIdToFriendly[v.ID] = uname - } -} - -func (m *BotModel) processMessageEvent(ev *slack.MessageEvent) { - m.GetSlackUserName(ev.User) - m.GetSlackIdName(ev.Channel) - m.IncomingSlackMessages <- ev -} - -func (m *BotModel) GetSlackIdName(id string) (string, error) { - switch id[0] { - case 'U': - return m.GetSlackUserName(id) - case 'G': - return m.GetSlackGroupName(id) - case 'C': - return m.GetSlackChannelName(id) - case 'D': - return m.GetSlackIMName(id) - } - return "", errors.New("Unknown ID Type") -} - -func (m *BotModel) GetSlackUserName(id string) (string, error) { - if v, ok := m.slackIdToFriendly[id]; ok { - return v, nil - } - user, err := m.slackApi.GetUserInfo(id) - if err != nil { - return "", err - } - m.slackIdToFriendly[id] = user.Profile.DisplayName - return user.Profile.DisplayName, nil -} - -func (m *BotModel) GetSlackIMName(id string) (string, error) { - if v, ok := m.slackIdToFriendly[id]; ok { - return v, nil - } - c, err := m.slackApi.GetChannelInfo(id) - if err != nil { - return "", err - } - m.slackIdToFriendly[id] = c.Name - return c.Name, nil -} - -func (m *BotModel) GetSlackChannelName(id string) (string, error) { - if v, ok := m.slackIdToFriendly[id]; ok { - return v, nil - } - c, err := m.slackApi.GetChannelInfo(id) - if err != nil { - return "", err - } - m.slackIdToFriendly[id] = c.Name - return c.Name, nil -} - -func (m *BotModel) GetSlackUserIM(id string) (string, error) { - for k, v := range m.slackIdToFriendly { - if v == id { - return k, nil - } - } - _, _, newId, err := m.slackApi.OpenIMChannel(id) - if err != nil { - return "", err - } - m.slackIdToFriendly[id] = newId - return newId, nil -} - -func (m *BotModel) GetSlackGroupName(id string) (string, error) { - if v, ok := m.slackIdToFriendly[id]; ok { - return v, nil - } - g, err := m.slackApi.GetGroupInfo(id) - if err != nil { - return "", err - } - m.slackIdToFriendly[id] = g.Name - return g.Name, nil -} - -func (m *BotModel) PostSlackMessage(msg *slack.Message) { - m.slackRTM.SendMessage(m.slackRTM.NewOutgoingMessage(msg.Text, msg.Channel)) -} - -func (m *BotModel) SendMessageToUser(msg *slack.Message, uid string) error { - dmId, err := m.GetSlackUserIM(uid) - if err != nil { - return err - } - msg.Channel = dmId - m.PostSlackMessage(msg) - return nil -} - -func (m *BotModel) GetSlackUserInfo(uid string) (*slack.User, error) { - return m.slackApi.GetUserInfo(uid) -} - -func (m *BotModel) GetSlackLatency() time.Duration { - return m.slackRTMLatency -} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..25e574d --- /dev/null +++ b/go.mod @@ -0,0 +1,18 @@ +module git.bullercodeworks.com/brian/helperbot + +go 1.17 + +replace git.bullercodeworks.com/brian/go-adventofcode => /home/brbuller/Development/go/src/git.bullercodeworks.com/brian/go-adventofcode + +require ( + git.bullercodeworks.com/brian/boltease v1.0.0 + git.bullercodeworks.com/brian/go-adventofcode v1.0.0 + github.com/nlopes/slack v0.6.0 +) + +require ( + github.com/boltdb/bolt v1.3.1 // indirect + github.com/gorilla/websocket v1.2.0 // indirect + github.com/pkg/errors v0.8.0 // indirect + golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..ec0a34e --- /dev/null +++ b/go.sum @@ -0,0 +1,20 @@ +git.bullercodeworks.com/brian/boltease v1.0.0 h1:kSCdJi+qfnPwdCP2oNUfQ6P4JKWaSWoh10Mx51uuD+Y= +git.bullercodeworks.com/brian/boltease v1.0.0/go.mod h1:3EuVzLDHy1zrjBEYc7RsaBohUzYrH1bx2aKM2Fk6t98= +git.bullercodeworks.com/brian/go-adventofcode v1.0.0 h1:y5FH3gPLaaBtJeJxkDNOfDQSA9i4+BChYf+UNd8pCsA= +git.bullercodeworks.com/brian/go-adventofcode v1.0.0/go.mod h1:h/W8Nm8zsrVSjnoA6BRlf38LbaXHR/m6dIq804LOO3w= +github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4= +github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gorilla/websocket v1.2.0 h1:VJtLvh6VQym50czpZzx07z/kw9EgAxI3x1ZB8taTMQQ= +github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/nlopes/slack v0.6.0 h1:jt0jxVQGhssx1Ib7naAOZEZcGdtIhTzkP0nopK0AsRA= +github.com/nlopes/slack v0.6.0/go.mod h1:JzQ9m3PMAqcpeCam7UaHSuBuupz7CmpjehYMayT6YOk= +github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf h1:2ucpDCmfkl8Bd/FsLtiD653Wf96cW37s+iGx93zsu4k= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/package.sh b/package.sh index 405e138..a02737d 100755 --- a/package.sh +++ b/package.sh @@ -4,5 +4,6 @@ rm helperbot.tgz ./buildplugins.sh cd cmd go build -o helperbot -tar -zcvf ../helperbot.tgz helperbot helperbot.service plugins -cd .. +mv plugins build/ +#tar -zcvf ../helperbot.tgz helperbot helperbot.service plugins +#cd .. diff --git a/plugins_src/plugin_aoc.go b/plugins_src/plugin_aoc.go index 0a34f47..3b29077 100644 --- a/plugins_src/plugin_aoc.go +++ b/plugins_src/plugin_aoc.go @@ -114,6 +114,21 @@ func (s *AoCState) NewAoC() error { } func (s *AoCState) runLoop() { + _, err := s.getChannelId() + // This plugin fails without a channel id + if err != nil { + return + } + // Don't do anything until we've done an initial update of all earlier years + for _, yr := range s.GetListOfAoCYears() { + if yr == s.GetLatestYear() { + continue + } + if !s.sessionNeedsUpdate { + fmt.Printf("Startup: Checking if board needs update (%d)\n", yr) + s.AoCSilentBoardCheckAndUpdate(yr) + } + } for { _, err := s.getChannelId() // This plugin fails without a channel id @@ -342,6 +357,29 @@ func (s *AoCState) GetAoCBoard(yr int) (*aoc.Leaderboard, error) { return s.aoc.GetLeaderboard(yr) } +func (s *AoCState) AoCSilentBoardCheckAndUpdate(yr int) { + if s.AoCBoardNeedsUpdate(yr) { + l, err := s.aoc.GetLeaderboard(yr) + if err != nil { + admin, adminErr := s.model.GetSlackAdminDMId() + if adminErr != nil { + s.SendAdminIdError() + return + } + if err.Error() == "Invalid Session Cookie" { + s.sessionNeedsUpdate = true + } + s.SendSlackMessage(fmt.Sprintf(":warning: AoC Error processing leaderboard (%d) - %s", yr, err.Error()), admin) + return + } + + s.model.SendMessage(s.Name(), "main", + s.BuildMessage("success", fmt.Sprintf("Received leaderboard (%d)", yr), "")) + // Save the leaderboard to the db + s.saveLeaderboard(l) + } +} + func (s *AoCState) AoCBoardCheckAndUpdate(yr int) { channelId, _ := s.getChannelId() if s.AoCBoardNeedsUpdate(yr) { @@ -372,10 +410,19 @@ func (s *AoCState) AoCBoardCheckAndUpdate(yr int) { if v.Stars > 1 { plural = "stars" } + mbrAll := s.getMemberAllYears(v.ID) + var totalStars int + for _, v := range mbrAll { + totalStars += v.Stars + } + allYearsText := "" + if totalStars > 0 { + allYearsText = fmt.Sprintf(" (%d for all years)", totalStars) + } if yr == s.GetLatestYear() { - s.SendSlackMessage(fmt.Sprintf(":christmas_tree: %s now has %d %s! :christmas_tree:", v.Name, v.Stars, plural), channelId) + s.SendSlackMessage(fmt.Sprintf(":christmas_tree: %s now has %d %s! :christmas_tree:%s", v.Name, v.Stars, plural, allYearsText), channelId) } else { - s.SendSlackMessage(fmt.Sprintf(":christmas_tree: %s now has %d %s! (%d) :christmas_tree:", v.Name, v.Stars, plural, yr), channelId) + s.SendSlackMessage(fmt.Sprintf(":christmas_tree: %s now has %d %s! (%d) :christmas_tree:%s", v.Name, v.Stars, plural, yr, allYearsText), channelId) } } } @@ -404,13 +451,10 @@ func (s *AoCState) AoCBoardNeedsUpdate(yr int) bool { func (s *AoCState) AoCBoardIsNew(yr int) bool { l, err := s.aoc.GetCachedLeaderboard(yr) - fmt.Println("Checking if board is new") if err != nil || l == nil { if err.Error() == "Invalid Year" { return false } - fmt.Println("YUP!") - return true } return false @@ -439,6 +483,17 @@ func (s *AoCState) RequestChannelId() { s.setChannelId(strings.TrimSpace(chn)) } +// Returns true if we have all AoC years saved to the DB +func (s *AoCState) haveAllYearsCached() bool { + for _, yr := range s.GetListOfAoCYears() { + _, err := s.aoc.GetCachedLeaderboard(yr) + if err != nil { + return false + } + } + return true +} + /* DB Functions */ func (s *AoCState) setAoCBoardId(brdId string) error { return s.model.SetString([]string{"aoc", "config", "board_id"}, brdId) @@ -510,6 +565,18 @@ func (s *AoCState) saveMember(event string, m *aoc.Member) error { return nil } +func (s *AoCState) getMemberAllYears(memberId string) map[string]*aoc.Member { + ret := make(map[string]*aoc.Member) + for _, yr := range s.GetListOfAoCYears() { + stYr := strconv.Itoa(yr) + m, err := s.getMember(stYr, memberId) + if err == nil { + ret[stYr] = m + } + } + return ret +} + func (s *AoCState) getMember(event string, memberId string) (*aoc.Member, error) { var err error var wrk string