Fixing issues with channel stats

This commit is contained in:
Brian Buller 2015-10-29 14:16:45 -05:00
parent d5947f745f
commit aef1feb64f
5 changed files with 152 additions and 89 deletions

View File

@ -18,8 +18,9 @@ func (p *generalProcessor) GetHelp() string {
return ""
}
func (p *generalProcessor) ProcessAdminMessage(slack *Slack, m *Message) {}
func (p *generalProcessor) ProcessMessage(slack *Slack, m *Message) {}
func (p *generalProcessor) ProcessBotMessage(slack *Slack, m *Message) {}
func (p *generalProcessor) ProcessAdminMessage(slack *Slack, m *Message) {}
func (p *generalProcessor) ProcessUserMessage(slack *Slack, m *Message) {}
func (p *generalProcessor) ProcessAdminUserMessage(slack *Slack, m *Message) {
@ -68,9 +69,11 @@ func (p *generalProcessor) ProcessAdminUserMessage(slack *Slack, m *Message) {
}
}
}
func (p *generalProcessor) ProcessBotUserMessage(slack *Slack, m *Message) {}
func (p *generalProcessor) ProcessChannelMessage(slack *Slack, m *Message) {}
func (p *generalProcessor) ProcessAdminChannelMessage(slack *Slack, m *Message) {}
func (p *generalProcessor) ProcessBotChannelMessage(slack *Slack, m *Message) {}
/*
*General Statistics Processor
@ -96,15 +99,18 @@ func (p *generalStatProcessor) ProcessMessage(m *Message) {
incrementUserStat(m.User, "message-dow-"+m.Time.Format("Mon"))
incrementUserStat(m.User, "message-dom-"+m.Time.Format("_2"))
}
func (p *generalStatProcessor) ProcessBotMessage(m *Message) {}
func (p *generalStatProcessor) ProcessUserMessage(m *Message) {
incrementUserStat(m.User, "bot-message")
}
func (p *generalStatProcessor) ProcessBotUserMessage(m *Message) {}
func (p *generalStatProcessor) ProcessChannelMessage(m *Message) {
incrementUserStat(m.User, "channel-message")
incrementChannelStat(m.User, "message-hour-"+m.Time.Format("15"))
incrementChannelStat(m.User, "message-dow-"+m.Time.Format("Mon"))
incrementChannelStat(m.User, "message-dom-"+m.Time.Format("_2"))
incrementChannelStat(m.Channel, "message-hour-"+m.Time.Format("15"))
incrementChannelStat(m.Channel, "message-dow-"+m.Time.Format("Mon"))
incrementChannelStat(m.Channel, "message-dom-"+m.Time.Format("_2"))
}
func (p *generalStatProcessor) ProcessBotChannelMessage(m *Message) {}

View File

@ -13,8 +13,12 @@ func (p *levelUpStatProcessor) GetStatKeys() []string {
}
func (p *levelUpStatProcessor) ProcessMessage(m *Message) {}
func (p *levelUpStatProcessor) ProcessAdminMessage(m *Message) {}
func (p *levelUpStatProcessor) ProcessBotMessage(m *Message) {}
func (p *levelUpStatProcessor) ProcessUserMessage(m *Message) {}
func (p *levelUpStatProcessor) ProcessAdminUserMessage(m *Message) {}
func (p *levelUpStatProcessor) ProcessBotUserMessage(m *Message) {}
func (p *levelUpStatProcessor) ProcessChannelMessage(m *Message) {
// levelup XP, for now, is awarded like this:
@ -33,3 +37,5 @@ func (p *levelUpStatProcessor) ProcessChannelMessage(m *Message) {
}
}
}
func (p *levelUpStatProcessor) ProcessAdminChannelMessage(m *Message) {}
func (p *levelUpStatProcessor) ProcessBotChannelMessage(m *Message) {}

View File

@ -5,8 +5,6 @@ import (
"fmt"
"io/ioutil"
"net/http"
"strconv"
"strings"
"sync/atomic"
"time"
@ -86,11 +84,7 @@ func (s *Slack) getMessage() (Message, error) {
var m Message
err := websocket.JSON.Receive(s.socket, &m)
txtArr := strings.Split(m.Ts, ".")
if t, err := strconv.Atoi(txtArr[0]); err == nil {
rawts := int64(t)
m.Time = time.Unix(0, rawts*1000000000)
}
m.Time = convertSlackTimestamp(m.Ts)
return m, err
}
@ -153,6 +147,9 @@ func (s *Slack) getChannelInfo(cid string) (*Channel, error) {
err = fmt.Errorf("Slack error: %s", respObj.Error)
return nil, err
}
respObj.Channel.LastRead = convertSlackTimestamp(respObj.Channel.LastReadRaw)
return respObj.Channel, nil
}

View File

@ -1,7 +1,6 @@
package main
import (
"encoding/json"
"fmt"
"os"
"strings"
@ -15,10 +14,13 @@ type messageProcessor interface {
GetHelp() string
ProcessMessage(s *Slack, m *Message)
ProcessAdminMessage(s *Slack, m *Message)
ProcessBotMessage(s *Slack, m *Message)
ProcessUserMessage(s *Slack, m *Message)
ProcessAdminUserMessage(s *Slack, m *Message)
ProcessBotUserMessage(s *Slack, m *Message)
ProcessChannelMessage(s *Slack, m *Message)
ProcessAdminChannelMessage(s *Slack, m *Message)
ProcessBotChannelMessage(s *Slack, m *Message)
}
var messageProcessors []messageProcessor
@ -27,8 +29,11 @@ type statProcessor interface {
GetName() string
GetStatKeys() []string
ProcessMessage(m *Message)
ProcessBotMessage(m *Message)
ProcessUserMessage(m *Message)
ProcessBotUserMessage(m *Message)
ProcessChannelMessage(m *Message)
ProcessBotChannelMessage(m *Message)
}
var statProcessors []statProcessor
@ -77,43 +82,68 @@ func statBotMain(slack *Slack) {
}
func processMessage(slack *Slack, m *Message) {
/*
if mb, me := json.Marshal(m); me == nil {
// Write the JSON representation to the log
writeToLog(string(mb) + "\n")
}
*/
// TODO: Handle reaction_added messages
// TODO: Handle reaction_removed messages
if m.Type == "message" {
for _, proc := range statProcessors {
proc.ProcessMessage(m)
}
for _, proc := range messageProcessors {
if isAdmin(m.User) {
proc.ProcessAdminMessage(slack, m)
}
proc.ProcessMessage(slack, m)
}
var err error
var usr *User
// Check if we know who the user is
usr, err := getUserInfo(m.User)
usr, err = getUserInfo(m.User)
// If the user information hasn't been updated in the last day, update it.
if err != nil || usr.LastUpdated.IsZero() || time.Since(usr.LastUpdated) > (time.Hour*24) {
if u, ue := slack.getUserInfo(m.User); ue == nil {
saveUserInfo(u)
}
}
for _, stats := range statProcessors {
if err == nil {
if usr.IsBot {
stats.ProcessBotMessage(m)
} else {
stats.ProcessMessage(m)
}
}
}
for _, proc := range messageProcessors {
if isAdmin(m.User) {
proc.ProcessAdminMessage(slack, m)
}
if usr.IsBot {
proc.ProcessBotMessage(slack, m)
} else {
proc.ProcessMessage(slack, m)
}
}
if m.Channel != "" {
// Check if we know what the channel is
chnl, err := getChannelInfo(m.Channel)
var isDirectMessage bool
// If the channel information hasn't been updated in the last day, update it.
if err != nil || chnl.LastUpdated.IsZero() || time.Since(chnl.LastUpdated) > (time.Hour*24) {
// Either we don't have this channel, or it's a direct message
if c, ce := slack.getChannelInfo(m.Channel); ce != nil {
if c, ce := slack.getChannelInfo(m.Channel); ce == nil {
// Save channel info
saveChannelInfo(c)
} else {
isDirectMessage = true
}
}
if isDirectMessage {
// Invalid channel, save as a direct message
saveUserMessage(m.User, m)
for _, proc := range statProcessors {
proc.ProcessUserMessage(m)
for _, stats := range statProcessors {
stats.ProcessUserMessage(m)
}
for _, proc := range messageProcessors {
if isAdmin(m.User) {
@ -122,10 +152,9 @@ func processMessage(slack *Slack, m *Message) {
proc.ProcessUserMessage(slack, m)
}
} else {
// Save channel info
saveChannelInfo(c)
// And save the channel message
saveChannelMessage(m.Channel, m)
err = saveChannelMessage(m.Channel, m)
for _, proc := range statProcessors {
proc.ProcessChannelMessage(m)
}
@ -136,7 +165,6 @@ func processMessage(slack *Slack, m *Message) {
}
}
}
}
func registerMessageProcessor(b messageProcessor) {
// Register a Message Processor

View File

@ -3,6 +3,7 @@ package main
import (
"fmt"
"strconv"
"strings"
"time"
"github.com/boltdb/bolt"
@ -69,6 +70,23 @@ func initDatabase() error {
return err
}
func isBot(uid string) (bool, error) {
openDatabase()
var ret bool
err := db.View(func(tx *bolt.Tx) error {
var b, uB *bolt.Bucket
var err error
b = tx.Bucket([]byte("users"))
if b == nil {
return fmt.Errorf("Unable to open 'users' bucket")
}
uB = b.Bucket([]byte(uid))
ret, err = bktGetBool(uB, "is_bot")
return err
})
return ret, err
}
func isAdmin(uid string) bool {
var foundUser bool
openDatabase()
@ -165,7 +183,7 @@ type Channel struct {
IsArchived bool `json:"is_archived"`
IsGeneral bool `json:"is_general"`
IsMember bool `json:"is_member"`
LastReadRaw int `json:"last_read"`
LastReadRaw string `json:"last_read"`
LastRead time.Time
Latest *Message `json:"latest"`
UnreadCount int `json:"unread_count"`
@ -327,6 +345,7 @@ func saveChannelInfo(chnl *Channel) error {
if err = bktPutString(chMembersB, idxKey, mm); err != nil {
return err
}
mbIdx++
}
if chnl.Topic != nil {
if err = bktPutString(chIB, "topic_value", chnl.Topic.Value); err != nil {
@ -357,56 +376,59 @@ func saveChannelInfo(chnl *Channel) error {
return err
}
func saveChannelStat(channel string, key string, val string) error {
func saveChannelStat(channel string, key string, val int) error {
openDatabase()
err := db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("channels"))
if chB, err := b.CreateBucketIfNotExists([]byte(channel)); err == nil {
chB.Put([]byte(key), []byte(val))
var b, chB, chSB *bolt.Bucket
var err error
b = tx.Bucket([]byte("channels"))
if chB, err = b.CreateBucketIfNotExists([]byte(channel)); err == nil {
if chSB, err = chB.CreateBucketIfNotExists([]byte("stats")); err == nil {
err = bktPutInt(chSB, key, val)
}
return nil
}
return err
})
closeDatabase()
return err
}
func incrementChannelStat(channel string, key string) error {
func addChannelStat(channel string, key string, addVal int) error {
openDatabase()
strRet, err := getChannelStat(channel, key)
if err == nil {
if i, err := strconv.Atoi(strRet); err == nil {
i++
return saveChannelStat(channel, key, fmt.Sprintf("%d", i))
}
return err
}
v, err := getChannelStat(channel, key)
err = saveChannelStat(channel, key, v+addVal)
closeDatabase()
return err
}
func incrementChannelStat(channel string, key string) error {
return addChannelStat(channel, key, 1)
}
func decrementChannelStat(channel string, key string) error {
openDatabase()
strRet, err := getChannelStat(channel, key)
if err == nil {
if i, err := strconv.Atoi(strRet); err == nil {
i--
return saveChannelStat(channel, key, fmt.Sprintf("%d", i))
}
return err
}
closeDatabase()
return err
return addChannelStat(channel, key, -1)
}
func getChannelStat(channel string, key string) (string, error) {
func getChannelStat(channel string, key string) (int, error) {
openDatabase()
var ret string
var ret int
err := db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("channels"))
if chB := b.Bucket([]byte(channel)); chB != nil {
ret = string(chB.Get([]byte(key)))
var b, chB, chSB *bolt.Bucket
var err error
b = tx.Bucket([]byte("channels"))
if b == nil {
return fmt.Errorf("Unable to open 'channels' bucket")
}
return nil
if chB, err = b.CreateBucketIfNotExists([]byte(channel)); err == nil {
if chSB, err = chB.CreateBucketIfNotExists([]byte("stats")); err == nil {
ret, err = bktGetInt(chSB, key)
return err
}
}
return err
})
closeDatabase()
return ret, err
@ -641,7 +663,7 @@ func saveUserMessage(user string, message *Message) error {
func getUserStat(user string, key string) (int, error) {
openDatabase()
var ret int
err := db.Update(func(tx *bolt.Tx) error {
err := db.View(func(tx *bolt.Tx) error {
var b, uB, uSB *bolt.Bucket
var err error
@ -651,10 +673,6 @@ func getUserStat(user string, key string) (int, error) {
}
if uB, err = b.CreateBucketIfNotExists([]byte(user)); err == nil {
if uSB, err = uB.CreateBucketIfNotExists([]byte("stats")); err == nil {
uSB.ForEach(func(k, v []byte) error {
return nil
})
ret, err = bktGetInt(uSB, key)
return err
}
@ -674,12 +692,10 @@ func saveUserStat(user string, key string, val int) error {
b = tx.Bucket([]byte("users"))
if uB, err = b.CreateBucketIfNotExists([]byte(user)); err == nil {
if uSB, err = uB.CreateBucketIfNotExists([]byte("stats")); err == nil {
if err = bktPutInt(uSB, key, val); err != nil {
err = bktPutInt(uSB, key, val)
}
}
return err
}
}
}
return nil
})
closeDatabase()
return err
@ -763,6 +779,16 @@ func bktPutTime(b *bolt.Bucket, key string, val time.Time) error {
return b.Put([]byte(key), []byte(bs))
}
func convertSlackTimestamp(ts string) time.Time {
var ret time.Time
txtArr := strings.Split(ts, ".")
if t, err := strconv.Atoi(txtArr[0]); err == nil {
rawts := int64(t)
ret = time.Unix(0, rawts*1000000000)
}
return ret
}
// All user stats are saved in the db like so:
// users (bucket)
// |- username1 (bucket)