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 sync.RWMutex configuration *configuration stopChannel chan struct{} } // Known game processes var knownGames = map[string]bool{ "FortniteClient-Win64-Shipping.exe": true, "OpenDental.exe": true, "chrome.exe": false, } // ReceiveProcessList handles incoming process list data from the desktop app func (p *Plugin) ReceiveProcessList(w http.ResponseWriter, r *http.Request) { var payload struct { ProcessList string `json:"processList"` UserID string `json:"userID"` } if err := json.NewDecoder(r.Body).Decode(&payload); err != nil { p.API.LogError("Failed to decode process list", "error", err.Error()) http.Error(w, "Invalid request payload", http.StatusBadRequest) return } // Check for known games in the process list for gameName := range knownGames { if strings.Contains(payload.ProcessList, gameName) { if err := p.SetUserGameStatus(payload.UserID, gameName); err != nil { p.API.LogError("Failed to set user game status", "error", err.Error()) } return } } // Clear the user status if no known games are found if err := p.SetUserGameStatus(payload.UserID, ""); err != nil { p.API.LogError("Failed to clear user game status", "error", err.Error()) } } // 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 } // Avoid unnecessary updates if user.Props["custom_status"] == status { return nil } customStatus := model.CustomStatus{ Emoji: "video_game", Text: status, Duration: "0", } customStatusJSON, err := json.Marshal(customStatus) if err != nil { return err } 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()) } } else { if err := p.SetUserGameStatus(userID, ""); err != nil { p.API.LogError("Failed to clear user game status", "error", err.Error()) } } time.Sleep(30 * time.Second) } } } // GetActiveGame scans processes to determine the active game based on known game names func (p *Plugin) GetActiveGame() (string, error) { cmd := exec.Command("tasklist") // Use tasklist for Windows output, err := cmd.Output() if err != nil { return "", err } lines := strings.Split(string(output), "\n") for _, line := range lines { for gameName := range knownGames { if strings.Contains(line, gameName) { return gameName, nil } } } return "", nil } // OnActivate is called when the plugin is activated func (p *Plugin) OnActivate() error { config := p.getConfiguration() if config.EnableGameStatus { p.stopChannel = make(chan struct{}) // Register the command for setting the game 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 } // Register the HTTP handler for receiving process lists http.HandleFunc("/plugins/com.mattermost.gamestatusupdate/processlist", p.ReceiveProcessList) // Retrieve the current user ID session, err := p.API.GetSession("current") // Use "current" to get the session for the logged-in user if err != nil { return fmt.Errorf("failed to get session: %w", err) // Handle the error appropriately } userID := session.UserId // Start monitoring game status for the current user go p.MonitorGameStatus(userID) } return nil } // HandleWebSocketEvent handles incoming WebSocket events. func (p *Plugin) HandleWebSocketEvent(event model.WebSocketEvent) { // Check if the event is of type user_status if event.GetBroadcast().ChannelId == "user_status" { var data struct { UserID string `json:"user_id"` } // Accessing the data as a map eventData := event.GetData() // Convert to JSON dataBytes, err := json.Marshal(eventData) if err != nil { p.API.LogError("Failed to marshal WebSocket event data", "error", err.Error()) return } // Unmarshal the JSON string to extract user ID if err := json.Unmarshal(dataBytes, &data); err != nil { p.API.LogError("Failed to unmarshal WebSocket event data", "error", err.Error()) return } // Update the user's game status based on active game game, err := p.GetActiveGame() if err != nil { p.API.LogError("Failed to get active game", "error", err.Error()) return } if game != "" { if err := p.SetUserGameStatus(data.UserID, game); err != nil { p.API.LogError("Failed to update user game status", "error", err.Error()) } } else { if err := p.SetUserGameStatus(data.UserID, ""); err != nil { p.API.LogError("Failed to clear user game status", "error", err.Error()) } } } }