Fixing issues with channel stats
This commit is contained in:
parent
d5947f745f
commit
aef1feb64f
@ -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) {}
|
||||
|
@ -12,9 +12,13 @@ func (p *levelUpStatProcessor) GetStatKeys() []string {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *levelUpStatProcessor) ProcessMessage(m *Message) {}
|
||||
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) 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) {}
|
||||
|
11
slack.go
11
slack.go
@ -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
|
||||
}
|
||||
|
||||
|
98
statbot.go
98
statbot.go
@ -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,61 +82,84 @@ 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")
|
||||
}
|
||||
/*
|
||||
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 {
|
||||
// Invalid channel, save as a direct message
|
||||
saveUserMessage(m.User, m)
|
||||
for _, proc := range statProcessors {
|
||||
proc.ProcessUserMessage(m)
|
||||
}
|
||||
for _, proc := range messageProcessors {
|
||||
if isAdmin(m.User) {
|
||||
proc.ProcessAdminUserMessage(slack, m)
|
||||
}
|
||||
proc.ProcessUserMessage(slack, m)
|
||||
}
|
||||
} else {
|
||||
if c, ce := slack.getChannelInfo(m.Channel); ce == nil {
|
||||
// Save channel info
|
||||
saveChannelInfo(c)
|
||||
// And save the channel message
|
||||
saveChannelMessage(m.Channel, m)
|
||||
for _, proc := range statProcessors {
|
||||
proc.ProcessChannelMessage(m)
|
||||
}
|
||||
for _, proc := range messageProcessors {
|
||||
proc.ProcessChannelMessage(slack, m)
|
||||
} else {
|
||||
isDirectMessage = true
|
||||
}
|
||||
}
|
||||
if isDirectMessage {
|
||||
// Invalid channel, save as a direct message
|
||||
saveUserMessage(m.User, m)
|
||||
for _, stats := range statProcessors {
|
||||
stats.ProcessUserMessage(m)
|
||||
}
|
||||
for _, proc := range messageProcessors {
|
||||
if isAdmin(m.User) {
|
||||
proc.ProcessAdminUserMessage(slack, m)
|
||||
}
|
||||
proc.ProcessUserMessage(slack, m)
|
||||
}
|
||||
} else {
|
||||
// And save the channel message
|
||||
err = saveChannelMessage(m.Channel, m)
|
||||
|
||||
for _, proc := range statProcessors {
|
||||
proc.ProcessChannelMessage(m)
|
||||
}
|
||||
for _, proc := range messageProcessors {
|
||||
proc.ProcessChannelMessage(slack, m)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
108
statbot_model.go
108
statbot_model.go
@ -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 {
|
||||
return err
|
||||
}
|
||||
err = bktPutInt(uSB, key, val)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return err
|
||||
})
|
||||
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)
|
||||
|
Loading…
Reference in New Issue
Block a user