package main import ( "encoding/json" "fmt" "net/http" "os/exec" "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. 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, "chrome.exe": false, } // GetActiveGame scans processes to determine the active game based on known game names func (p *Plugin) GetActiveGame() (string, error) { // For Linux, use the ps command to get the list of processes cmd := exec.Command("ps", "-e") // -e lists all processes 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 } } } // handleProcessList handles the incoming process list from the desktop app func (p *Plugin) handleProcessList(w http.ResponseWriter, r *http.Request) { var requestData struct { ProcessList string `json:"processList"` UserID string `json:"userID"` } if err := json.NewDecoder(r.Body).Decode(&requestData); err != nil { http.Error(w, "Invalid request payload", http.StatusBadRequest) return } // Check for active game from the process list game := "" for knownGame := range knownGames { if strings.Contains(requestData.ProcessList, knownGame) { game = knownGame break } } if game != "" { err := p.SetUserGameStatus(requestData.UserID, game) if err != nil { http.Error(w, "Failed to update user status", http.StatusInternalServerError) return } } w.WriteHeader(http.StatusOK) } // 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 } // Create a new HTTP ServeMux and register the endpoint mux := http.NewServeMux() mux.HandleFunc("/processlist", p.handleProcessList) // Start the HTTP server go func() { if err := http.ListenAndServe(":8780", mux); err != nil { p.API.LogError("Failed to start HTTP server", "error", err.Error()) } }() return nil } // OnDeactivate is called when the plugin is deactivated func (p *Plugin) OnDeactivate() error { close(p.stopChannel) return nil } func main() { plugin.ClientMain(&Plugin{}) }