diff --git a/levelup_model.go b/levelup_model.go index fc0000c..e2f5ace 100644 --- a/levelup_model.go +++ b/levelup_model.go @@ -38,3 +38,31 @@ func getAllLevelUpChannelXp(user string) map[string]int { closeDatabase() return ret } + +func getAllNonLevelUpStats(user string) map[string]int { + openDatabase() + ret := make(map[string]int) + // First, get a list of all levelup stats + db.Update(func(tx *bolt.Tx) error { + var b, uB, uSB *bolt.Bucket + var err error + + b = tx.Bucket([]byte("users")) + if b == nil { + return fmt.Errorf("Unable to open 'users' bucket") + } + if uB = b.Bucket([]byte(user)); uB != nil { + if uSB = uB.Bucket([]byte("stats")); uSB != nil { + return uSB.ForEach(func(k, v []byte) error { + if !strings.HasPrefix(string(k), "levelup-") { + ret[string(k)], _ = strconv.Atoi(string(v)) + } + return nil + }) + } + } + return err + }) + closeDatabase() + return ret +} diff --git a/processor_general.go b/processor_general.go index 473d44c..316d877 100644 --- a/processor_general.go +++ b/processor_general.go @@ -275,41 +275,45 @@ func (wm *generalWebModule) handleStats(w http.ResponseWriter, req *http.Request sc := "var stats = {totalchannelmessages:" sc = fmt.Sprintf("%s%d,", sc, s.TotalChannelMessages) sc = sc + "channels:[" - for _, k := range s.ChannelStats { - sc = fmt.Sprintf("%s{name:\"%s\",member_count:%d,message_count:%d},", - sc, - k.Name, - k.MemberCount, - k.MessageCount, - ) + if len(s.ChannelStats) > 0 { + for _, k := range s.ChannelStats { + sc = fmt.Sprintf("%s{name:\"%s\",member_count:%d,message_count:%d},", + sc, + k.Name, + k.MemberCount, + k.MessageCount, + ) + } + // Trim the last , + sc = sc[:len(sc)-1] } - // Trim the last , - sc = sc[:len(sc)-1] sc = sc + "]," sc = sc + "users:[" - for _, usr := range s.UserStats { - sc = fmt.Sprintf("%s{name:\"%s\",message_count:%d,", - sc, - usr.Name, - usr.Messages, - ) - sc = sc + "messages:{" - sc = sc + "hours:{" - for i, k := range usr.Hours { - sc = sc + fmt.Sprintf("%d:%d,", i, k) + if len(s.UserStats) > 0 { + for _, usr := range s.UserStats { + sc = fmt.Sprintf("%s{name:\"%s\",message_count:%d,", + sc, + usr.Name, + usr.Messages, + ) + sc = sc + "messages:{" + sc = sc + "hours:{" + for i, k := range usr.Hours { + sc = sc + fmt.Sprintf("%d:%d,", i, k) + } + sc = sc[:len(sc)-1] + sc = sc + "}," + sc = sc + "dow:{" + for _, k := range []string{"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"} { + sc = sc + fmt.Sprintf("%s:%d,", k, usr.Dow[k]) + } + sc = sc[:len(sc)-1] + sc = sc + "}}" + sc = sc + "}," } + // Trim the last , sc = sc[:len(sc)-1] - sc = sc + "}," - sc = sc + "dow:{" - for _, k := range []string{"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"} { - sc = sc + fmt.Sprintf("%s:%d,", k, usr.Dow[k]) - } - sc = sc[:len(sc)-1] - sc = sc + "}}" - sc = sc + "}," } - // Trim the last , - sc = sc[:len(sc)-1] sc = sc + "]," // Get all of the message stats sc = sc + "messages:{" diff --git a/processor_levelup.go b/processor_levelup.go index ac31321..1c93d15 100644 --- a/processor_levelup.go +++ b/processor_levelup.go @@ -3,6 +3,8 @@ package main import ( "fmt" "net/http" + + "github.com/gorilla/mux" ) type levelUpStatProcessor struct{} @@ -56,6 +58,7 @@ func (wm *levelUpWebModule) GetName() string { func (wm *levelUpWebModule) GetRoutes() map[string]func(http.ResponseWriter, *http.Request) { ret := make(map[string]func(http.ResponseWriter, *http.Request)) ret["/levelup/"] = wm.handleLevelUpGeneral + ret["/levelup/profile/{username}"] = wm.handleLevelUpProfile return ret } func (wm *levelUpWebModule) Register() { @@ -106,23 +109,49 @@ func (wm *levelUpWebModule) handleLevelUpGeneral(w http.ResponseWriter, req *htt sc := "var levelUpStats = {" sc = sc + "users:[" - for _, k := range userStats { - sc = sc + "{" - sc = sc + "name:\"" + k.Name + "\"," - sc = fmt.Sprintf("%sxp:%d,", sc, k.Xp) - sc = sc + "channels:[" - if len(k.ChannelStats) > 0 { - for chK, chV := range k.ChannelStats { - sc = fmt.Sprintf("%s{name:\"%s\",xp:%d},", sc, chK, chV) + if len(userStats) > 0 { + for _, k := range userStats { + sc = sc + "{" + sc = sc + "name:\"" + k.Name + "\"," + sc = fmt.Sprintf("%sxp:%d,", sc, k.Xp) + sc = sc + "channels:[" + if len(k.ChannelStats) > 0 { + for chK, chV := range k.ChannelStats { + sc = fmt.Sprintf("%s{name:\"%s\",xp:%d},", sc, chK, chV) + } + sc = sc[:len(sc)-1] } - sc = sc[:len(sc)-1] + sc = sc + "]}," } - sc = sc + "]}," + sc = sc[:len(sc)-1] } - sc = sc[:len(sc)-1] sc = sc + "]};" addToInlineScript(sc) site.Scripts = append(site.Scripts, "/assets/js/levelup_main.js") showPage("levelup-main.html", site, w) } + +func (wm *levelUpWebModule) handleLevelUpProfile(w http.ResponseWriter, req *http.Request) { + type UserLevelUpStats struct { + Name string + Xp int + ChannelStats map[string]int + OtherStats map[string]int + } + var u *User + var err error + vars := mux.Vars(req) + if u, err = getUserInfoFromName(vars["username"]); err != nil { + pageNotFound(fmt.Errorf(fmt.Sprintf("Couldn't find a profile for user "+vars["username"])+"\n%s\n", err), w) + return + } + p := UserLevelUpStats{Name: vars["username"]} + p.Xp, _ = getUserStat(u.ID, "levelup-xp") + p.ChannelStats = getAllLevelUpChannelXp(u.ID) + p.OtherStats = getAllNonLevelUpStats(u.ID) + site.TemplateData = p + + initRequest(w, req) + showPage("levelup-profile.html", site, w) +} diff --git a/processor_levelupachieve.go b/processor_levelupachieve.go new file mode 100644 index 0000000..2f971a8 --- /dev/null +++ b/processor_levelupachieve.go @@ -0,0 +1,95 @@ +package main + +import ( + "net/http" + + "github.com/gorilla/mux" +) + +type levelUpAchieveStatProcessor struct { + Achievements []levelUpAchievement +} + +func (p *levelUpAchieveStatProcessor) GetName() string { + return "LevelUp Achievements" +} + +func (p *levelUpAchieveStatProcessor) GetStatKeys() []string { + return []string{ + "levelupachievement-*", + } +} + +type levelUpAchievement interface { + GetName() string + GetText() string + // Returns whether the user already has this achievement + DoesUserHave(uID string) bool + // Processes the message, returns true if the achievement was triggered + ProcessMessage(m *Message) bool +} + +func (p *levelUpAchieveStatProcessor) ProcessMessage(m *Message) { + type UserLevelUpStats struct { + Name string + Xp int + ChannelStats map[string]int + OtherStats map[string]int + } + var u *User + var err error + vars := mux.Vars(req) + if u, err = getUserInfo(m.User); err != nil { + return + } + + p := UserLevelUpStats{Name: u.Name} + p.Xp, _ = getUserStat(u.ID, "levelup-xp") + p.ChannelStats = getAllLevelUpChannelXp(u.ID) + p.OtherStats = getAllNonLevelUpStats(u.ID) + +} +func (p *levelUpAchieveStatProcessor) ProcessAdminMessage(m *Message) {} +func (p *levelUpAchieveStatProcessor) ProcessBotMessage(m *Message) {} + +func (p *levelUpAchieveStatProcessor) ProcessUserMessage(m *Message) {} +func (p *levelUpAchieveStatProcessor) ProcessAdminUserMessage(m *Message) {} +func (p *levelUpAchieveStatProcessor) ProcessBotUserMessage(m *Message) {} + +func (p *levelUpAchieveStatProcessor) ProcessChannelMessage(m *Message) { +} +func (p *levelUpAchieveStatProcessor) ProcessAdminChannelMessage(m *Message) {} +func (p *levelUpAchieveStatProcessor) ProcessBotChannelMessage(m *Message) {} + +/* + * Web Site Module + */ +type levelUpAchieveWebModule struct{} + +func (wm *levelUpAchieveWebModule) GetName() string { + return "LevelUp Achievement Web Module" +} +func (wm *levelUpAchieveWebModule) GetRoutes() map[string]func(http.ResponseWriter, *http.Request) { + ret := make(map[string]func(http.ResponseWriter, *http.Request)) + ret["/levelup/achieve"] = wm.handleLevelUpAchieveGeneral + return ret +} +func (wm *levelUpAchieveWebModule) Register() { + for k, v := range wm.GetRoutes() { + r.HandleFunc(k, v) + } +} +func (wm *levelUpAchieveWebModule) GetMenuEntries() []menuItem { + var ret []menuItem + ret = append(ret, menuItem{Text: "Achieve GET!", Link: "/levelup/achieve"}) + return ret +} +func (wm *levelUpAchieveWebModule) GetBottomMenuEntries() []menuItem { + var ret []menuItem + return ret +} + +func (wm *levelUpAchieveWebModule) handleLevelUpAchieveGeneral(w http.ResponseWriter, req *http.Request) { + initRequest(w, req) + setMenuItemActive("Achieve GET!") +} diff --git a/statbot.go b/statbot.go index 2f2179f..6a19f96 100644 --- a/statbot.go +++ b/statbot.go @@ -65,6 +65,9 @@ func statBotMain(slack *Slack) { registerStatProcessor(new(levelUpStatProcessor)) registerStatProcessor(new(generalStatProcessor)) + levelUpAchievements := new(levelUpAchieveStatProcessor) + // Register Achievements + registerMessageProcessor(new(generalProcessor)) fmt.Println("statbot ready, ^C exits") @@ -74,6 +77,7 @@ func statBotMain(slack *Slack) { // read each incoming message m, err := slack.getMessage() if err == nil { + writeToLog(" " + time.Now().Format(time.RFC3339) + " - Received Message\n") processMessage(slack, &m) } } diff --git a/statbot_model.go b/statbot_model.go index ef2eb05..a857c53 100644 --- a/statbot_model.go +++ b/statbot_model.go @@ -553,54 +553,56 @@ func getUserInfo(usr string) (*User, error) { err := db.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte("users")) if uB := b.Bucket([]byte(usr)); uB != nil { - var err error - if ret.Name, err = bktGetString(uB, "name"); err != nil { - return err - } - if ret.Deleted, err = bktGetBool(uB, "deleted"); err != nil { - return err - } - if ret.Status, err = bktGetString(uB, "status"); err != nil { - return err - } - if ret.Color, err = bktGetString(uB, "color"); err != nil { - return err - } - if ret.RealName, err = bktGetString(uB, "real_name"); err != nil { - return err - } - if ret.TZ, err = bktGetString(uB, "tz"); err != nil { - return err - } - if ret.TZLabel, err = bktGetString(uB, "tz_label"); err != nil { - return err - } - if ret.TZOffset, err = bktGetInt(uB, "tz_offset"); err != nil { - return err - } - if ret.IsAdmin, err = bktGetBool(uB, "is_admin"); err != nil { - return err - } - if ret.IsOwner, err = bktGetBool(uB, "is_owner"); err != nil { - return err - } - if ret.IsPrimaryOwner, err = bktGetBool(uB, "is_primary_owner"); err != nil { - return err - } - if ret.IsRestricted, err = bktGetBool(uB, "is_restricted"); err != nil { - return err - } - if ret.IsUltraRestricted, err = bktGetBool(uB, "is_ultra_restricted"); err != nil { - return err - } - if ret.IsBot, err = bktGetBool(uB, "is_bot"); err != nil { - return err - } - if ret.HasFiles, err = bktGetBool(uB, "has_files"); err != nil { - return err - } - if ret.LastUpdated, err = bktGetTime(uB, "last_updated"); err != nil { - return err + if uIB := uB.Bucket([]byte("info")); uIB != nil { + var err error + if ret.Name, err = bktGetString(uIB, "name"); err != nil { + return err + } + if ret.Deleted, err = bktGetBool(uIB, "deleted"); err != nil { + return err + } + if ret.Status, err = bktGetString(uIB, "status"); err != nil { + return err + } + if ret.Color, err = bktGetString(uIB, "color"); err != nil { + return err + } + if ret.RealName, err = bktGetString(uIB, "real_name"); err != nil { + return err + } + if ret.TZ, err = bktGetString(uIB, "tz"); err != nil { + return err + } + if ret.TZLabel, err = bktGetString(uIB, "tz_label"); err != nil { + return err + } + if ret.TZOffset, err = bktGetInt(uIB, "tz_offset"); err != nil { + return err + } + if ret.IsAdmin, err = bktGetBool(uIB, "is_admin"); err != nil { + return err + } + if ret.IsOwner, err = bktGetBool(uIB, "is_owner"); err != nil { + return err + } + if ret.IsPrimaryOwner, err = bktGetBool(uIB, "is_primary_owner"); err != nil { + return err + } + if ret.IsRestricted, err = bktGetBool(uIB, "is_restricted"); err != nil { + return err + } + if ret.IsUltraRestricted, err = bktGetBool(uIB, "is_ultra_restricted"); err != nil { + return err + } + if ret.IsBot, err = bktGetBool(uIB, "is_bot"); err != nil { + return err + } + if ret.HasFiles, err = bktGetBool(uIB, "has_files"); err != nil { + return err + } + if ret.LastUpdated, err = bktGetTime(uB, "last_updated"); err != nil { + return err + } } return nil } @@ -610,6 +612,84 @@ func getUserInfo(usr string) (*User, error) { return &ret, err } +func getUserInfoFromName(usrName string) (*User, error) { + openDatabase() + ret := User{} + err := db.Update(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte("users")) + c := b.Cursor() + var tstName string + var uB, uIB *bolt.Bucket + var err error + var userID string + for k, v := c.First(); k != nil && strings.ToLower(tstName) != strings.ToLower(usrName); k, v = c.Next() { + if v == nil { + if uB = b.Bucket(k); uB != nil { + if uIB = uB.Bucket([]byte("info")); uIB != nil { + if tstName, err = bktGetString(uIB, "name"); err != nil { + return err + } + } + userID = string(k) + } + } + } + if tstName != usrName { + return fmt.Errorf("Requested user (" + usrName + ") not found") + } + ret.ID = userID + ret.Name = tstName + if ret.Deleted, err = bktGetBool(uIB, "deleted"); err != nil { + return err + } + if ret.Status, err = bktGetString(uIB, "status"); err != nil { + return err + } + if ret.Color, err = bktGetString(uIB, "color"); err != nil { + return err + } + if ret.RealName, err = bktGetString(uIB, "real_name"); err != nil { + return err + } + if ret.TZ, err = bktGetString(uIB, "tz"); err != nil { + return err + } + if ret.TZLabel, err = bktGetString(uIB, "tz_label"); err != nil { + return err + } + if ret.TZOffset, err = bktGetInt(uIB, "tz_offset"); err != nil { + return err + } + if ret.IsAdmin, err = bktGetBool(uIB, "is_admin"); err != nil { + return err + } + if ret.IsOwner, err = bktGetBool(uIB, "is_owner"); err != nil { + return err + } + if ret.IsPrimaryOwner, err = bktGetBool(uIB, "is_primary_owner"); err != nil { + return err + } + if ret.IsRestricted, err = bktGetBool(uIB, "is_restricted"); err != nil { + return err + } + if ret.IsUltraRestricted, err = bktGetBool(uIB, "is_ultra_restricted"); err != nil { + return err + } + if ret.IsBot, err = bktGetBool(uIB, "is_bot"); err != nil { + return err + } + if ret.HasFiles, err = bktGetBool(uIB, "has_files"); err != nil { + return err + } + if ret.LastUpdated, err = bktGetTime(uIB, "last_updated"); err != nil { + return err + } + return err + }) + closeDatabase() + return &ret, err +} + func saveUserInfo(usr *User) error { openDatabase() err := db.Update(func(tx *bolt.Tx) error { @@ -670,7 +750,7 @@ func saveUserInfo(usr *User) error { if err = bktPutBool(uIB, "has_files", usr.HasFiles); err != nil { return err } - err = bktPutTime(uIB, "last_udpated", usr.LastUpdated) + err = bktPutTime(uIB, "last_updated", usr.LastUpdated) return err }) closeDatabase() diff --git a/statbotweb.go b/statbotweb.go index 3f0d1f4..41a5cdc 100644 --- a/statbotweb.go +++ b/statbotweb.go @@ -6,6 +6,7 @@ import ( "net/http" "os" "text/template" + "time" "github.com/gorilla/context" "github.com/gorilla/mux" @@ -72,6 +73,7 @@ func statWebMain(slack *Slack) { registerWebModule(new(generalWebModule)) registerWebModule(new(levelUpWebModule)) + registerWebModule(new(levelUpAchieveWebModule)) http.Handle("/", r) go func() { @@ -80,6 +82,7 @@ func statWebMain(slack *Slack) { } func initRequest(w http.ResponseWriter, req *http.Request) { + writeToLog(" " + time.Now().Format(time.RFC3339) + " >> Web Request Received\n") site.SubTitle = "" site.Stylesheets = make([]string, 0, 0) site.Stylesheets = append(site.Stylesheets, "/assets/css/pure-min.css") @@ -190,6 +193,13 @@ func addToInlineScript(s string) { site.InlineScript = fmt.Sprintf("%s%s", site.InlineScript, s) } +func pageNotFound(err error, w http.ResponseWriter) { + if err == nil { + err = fmt.Errorf("Page Not Found") + } + http.Error(w, err.Error(), 404) +} + func assertError(err error, w http.ResponseWriter) bool { if err != nil { http.Error(w, err.Error(), 500) diff --git a/templates/levelup-profile.html b/templates/levelup-profile.html new file mode 100644 index 0000000..53ba0b1 --- /dev/null +++ b/templates/levelup-profile.html @@ -0,0 +1,12 @@ +

{{ .TemplateData.Name }}

+
{{ .TemplateData.Xp }}
+ +