package main import ( "encoding/json" "fmt" "os/exec" "runtime" "strings" "sync" "time" "github.com/mattermost/mattermost/server/public/model" "github.com/mattermost/mattermost/server/public/plugin" ) // Plugin implements the interface expected by the Mattermost server to communicate between the server and plugin processes. type Plugin struct { plugin.MattermostPlugin // configurationLock synchronizes access to the configuration. configurationLock sync.RWMutex // configuration is the active plugin configuration. Consult getConfiguration and // setConfiguration for usage. configuration *configuration // stopChannel is used to stop the background process monitor when the plugin is deactivated. stopChannel chan struct{} } // Known game processes var knownGames = map[string]bool{ "FortniteClient-Win64-Shipping.exe": true, "OpenDental.exe": true, "gameprocess": true, } // GetActiveGame scans processes to determine the active game based on known game names func (p *Plugin) GetActiveGame() (string, error) { var cmd *exec.Cmd // Check the operating system and execute the appropriate command if runtime.GOOS == "windows" { // For Windows, use tasklist command to get the list of processes cmd = exec.Command("tasklist") } else { // For Linux/macOS, use ps -e command to get the list of processes cmd = exec.Command("ps", "-e") } output, err := cmd.Output() if err != nil { return "", err } // Convert output to a string and split into lines lines := strings.Split(string(output), "\n") for _, line := range lines { for gameName := range knownGames { // Check if the line contains a known game process if strings.Contains(line, gameName) { return gameName, nil } } } return "", nil } // SetUserGameStatus updates the user's custom status to reflect the game they are playing func (p *Plugin) SetUserGameStatus(userID, game string) error { status := fmt.Sprintf("playing: %s", game) user, appErr := p.API.GetUser(userID) if appErr != nil { return appErr } // Serialize the custom status to JSON customStatus := model.CustomStatus{ Emoji: "video_game", Text: status, Duration: "0", // Status remains until changed } customStatusJSON, err := json.Marshal(customStatus) if err != nil { return err } // Set the custom status in user.Props user.Props = map[string]string{ "custom_status": string(customStatusJSON), } if _, appErr := p.API.UpdateUser(user); appErr != nil { return appErr } return nil } // MonitorGameStatus continuously monitors running processes and updates the user's status func (p *Plugin) MonitorGameStatus(userID string) { for { select { case <-p.stopChannel: return default: game, err := p.GetActiveGame() if err != nil { p.API.LogError("Failed to scan processes", "error", err.Error()) continue } if game != "" { if err := p.SetUserGameStatus(userID, game); err != nil { p.API.LogError("Failed to set user game status", "error", err.Error()) } } // Sleep for a defined interval before scanning again time.Sleep(30 * time.Second) // Scan every 30 seconds (can be configurable) } } } // ExecuteCommand handles the `/setgame` command to manually set a game func (p *Plugin) ExecuteCommand(c *plugin.Context, command *model.CommandArgs) (*model.CommandResponse, *model.AppError) { split := strings.Fields(command.Command) if len(split) == 0 { return &model.CommandResponse{}, nil } switch split[0] { case "/setgame": var game string var err error if len(split) < 2 { // Scan for active game if no specific game is mentioned game, err = p.GetActiveGame() if err != nil { return nil, &model.AppError{Message: "Failed to scan processes", StatusCode: 500} } } else { game = strings.Join(split[1:], " ") } err = p.SetUserGameStatus(command.UserId, game) if err != nil { return nil, &model.AppError{Message: "Failed to set game status", StatusCode: 500} } return &model.CommandResponse{ Text: fmt.Sprintf("your status has been updated to: %s", game), }, nil default: return &model.CommandResponse{}, nil } } // OnActivate is called when the plugin is activated func (p *Plugin) OnActivate() error { p.stopChannel = make(chan struct{}) command := &model.Command{ Trigger: "setgame", AutoComplete: true, AutoCompleteDesc: "set the game you are currently playing", AutoCompleteHint: "[game]", } if err := p.API.RegisterCommand(command); err != nil { return err } // Start background process monitoring for all users go p.MonitorGameStatus("user_id_here") // Replace with actual user ID(s) return nil } // OnDeactivate is called when the plugin is deactivated func (p *Plugin) OnDeactivate() error { close(p.stopChannel) return nil }