package main import ( "fmt" "net/http" "github.com/mattermost/mattermost-server/plugin" "github.com/mattermost/mattermost-server/model" "github.com/pkg/errors" "sync" "reflect" "github.com/google/go-cmp/cmp" "os" "github.com/fatih/structs" "github.com/hashicorp/go-multierror" "github.com/kr/pretty" "strings" "regexp" "strconv" ) var userSplit *regexp.Regexp = regexp.MustCompile(`\s+(^|[^,])|\s*,\s*`) type Config struct { NumDoors string WatchPath string AdminUsers string numDoors uint8 } func (c *Config) configMap() map[string]interface{} { var configMap map[string]interface{} = structs.Map(c) var lowerConfigMap map[string]interface{} = make(map[string]interface{}) for k, v := range(configMap) { lowerConfigMap[strings.ToLower(k)] = fmt.Sprintf("%v", v) } return lowerConfigMap } type BathroomMonitorPlugin struct { plugin.MattermostPlugin config *Config adminUsers []string configLock sync.RWMutex configChanged chan struct{} doors map[uint8]bool configUpdates int lastReport *string } func (p *BathroomMonitorPlugin) initDoors() { p.doors = make(map[uint8]bool) for i := uint8(0); i < p.config.numDoors; i++ { p.doors[i] = false } } func (p *BathroomMonitorPlugin) init() *BathroomMonitorPlugin { p.config = &Config{NumDoors: "1", WatchPath: "./", numDoors: 1, AdminUsers:""} p.configChanged = make(chan struct{}) p.initDoors() return p; } func (p *BathroomMonitorPlugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "Hello, world!") } func (p *BathroomMonitorPlugin) confChangedEvent() { select { case p.configChanged <- struct{}{}: default: } } func (p *BathroomMonitorPlugin) checkCorrectConfig() { var currentConfig map[string]interface{} = p.getLowerCasePluginConfig() var goodConfig map[string]interface{} = p.config.configMap() if !cmp.Equal(currentConfig, goodConfig) { p.API.LogInfo(fmt.Sprintf("%d: Saving un-matching config\n%# v\n%# v", p.configUpdates, pretty.Formatter(currentConfig), pretty.Formatter(goodConfig))) p.API.SavePluginConfig(goodConfig) } } func (p *BathroomMonitorPlugin) getLowerCasePluginConfig() map[string]interface{} { var currentConfig map[string]interface{} = p.API.GetPluginConfig() var lowerConfig map[string]interface{} = make(map[string]interface{}) for k, v := range(currentConfig) { lowerConfig[strings.ToLower(k)] = fmt.Sprintf("%v", v) } return lowerConfig } func (p *BathroomMonitorPlugin) postAdminChannel(text string) { p.API.LogError(text) if p.lastReport != nil && *p.lastReport == text { return } p.lastReport = &text var users []*model.User = make([]*model.User, 0, len(p.adminUsers)) for _, un := range(p.adminUsers) { u, _ := p.API.GetUserByUsername(un) if u != nil { users = append(users, u) } } if len(users) > 0 { admin := users[0] bots, _ := p.API.GetBots(&model.BotGetOptions{Page:0, PerPage:1000}) var bathroom_bot *model.Bot = nil if bots != nil { for _, b := range(bots) { if b.Username == "bathroom-bot" { bathroom_bot = b break } } } if bathroom_bot == nil { created_bathroom_bot, err := p.API.CreateBot(&model.Bot{Username:"bathroom-bot", OwnerId:admin.Id, DisplayName:"Bathroom Bot", Description:"Tracks Bathroom Status"}) if err != nil { p.API.LogError(errors.Wrap(err, "Couldn't create bathroom-bot bot").Error()) return } bathroom_bot = created_bathroom_bot } if bathroom_bot == nil { p.API.LogError("Really couldn't create bathroom-bot bot") return } for _, u := range(users) { channel, err := p.API.GetDirectChannel(bathroom_bot.UserId, u.Id); if err != nil { p.API.LogError(errors.Wrap(err, fmt.Sprintf("Couldn't get direct channel to user %s", u.Username)).Error()) continue } p.API.CreatePost(&model.Post{UserId: bathroom_bot.UserId, ChannelId:channel.Id, Message:text, MessageSource:text}) } } else { p.API.LogError("No admin users?") } } func (p *BathroomMonitorPlugin) OnConfigurationChange() error { p.configLock.Lock() defer p.configLock.Unlock() //var currentConfig map[string]interface{} = p.getLowerCasePluginConfig() //if cmp.Equal(currentConfig, p.config.configMap()) { // p.API.LogInfo(fmt.Sprintf("%d: Skipping matching config", p.configUpdates)) // return nil //} //defer p.checkCorrectConfig() var newConfig *Config = new(Config); if err := p.API.LoadPluginConfiguration(newConfig); err != nil { newErr := errors.Wrap(err, fmt.Sprintf("%d: Failed to load configuration", p.configUpdates)) p.postAdminChannel(newErr.Error()) return newErr } //p.API.LogInfo(fmt.Sprintf("Loaded %t %t", newConfig != nil, p.config != nil)) if newConfig == nil || newConfig == p.config || reflect.ValueOf(*newConfig).NumField() == 0 { p.API.LogInfo("Passed same config, or empty?") return nil; } //p.API.LogInfo("Checked dupe") var configErr error = nil if _, err := os.Stat(newConfig.WatchPath); err != nil { newConfig.WatchPath = "./" configErr = multierror.Append(configErr, errors.Wrap(err, "Invalid watch path")) } //p.API.LogInfo("Checked path") numDoors, err := strconv.ParseUint(newConfig.NumDoors, 10, 8) if err != nil { //p.API.LogError("Bad doors! " + err.Error()) newConfig.NumDoors = "1" newConfig.numDoors = 1 configErr = multierror.Append(configErr, errors.Wrap(err, "Invalid number of doors")) } else { //p.API.LogError(fmt.Sprintf("Good doors! %d", numDoors)) newConfig.numDoors = uint8(numDoors) } p.config = newConfig p.adminUsers = make([]string, 0, 4) split := userSplit.Split(newConfig.AdminUsers, -1) for _, un := range split { trimmed := strings.Trim(un, ", \t\n") if trimmed != "" { p.adminUsers = append(p.adminUsers, trimmed) } } p.configUpdates++ p.initDoors() p.API.LogInfo(fmt.Sprintf("%d: Config: %d %s", p.configUpdates, p.config.numDoors, p.config.WatchPath)) if configErr != nil { p.postAdminChannel(configErr.Error()) } return configErr } func watchLoop(p *BathroomMonitorPlugin) { for { p.configLock.Lock() p.configLock.Unlock() select { case <- p.configChanged: p.API.LogInfo(fmt.Sprintf("CONFIG CHANGED, RESTARTING %d", p.configUpdates)) } } } func (p *BathroomMonitorPlugin) OnActivate() error { go watchLoop(p) return nil } func main() { plugin.ClientMain((&BathroomMonitorPlugin{}).init()) }