package main import ( "database/sql" "fmt" "strings" "sync" sq "github.com/Masterminds/squirrel" "github.com/mattermost/mattermost/server/public/model" "github.com/mattermost/mattermost/server/public/plugin" pluginapi "github.com/mattermost/mattermost/server/public/pluginapi" ) // 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 apiClient *pluginapi.Client db *sql.DB } // ServeHTTP demonstrates a plugin that handles HTTP requests by greeting the world. // func (p *Plugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Request) { // fmt.Fprint(w, "Hello, world!") // } func (p *Plugin) Q() sq.StatementBuilderType { return sq.StatementBuilder.PlaceholderFormat(sq.Dollar).RunWith(p.db) } func (p *Plugin) OnActivate() error { p.apiClient = pluginapi.NewClient(p.API, p.Driver) db, dbErr := p.apiClient.Store.GetMasterDB() if dbErr != nil { return dbErr } p.db = db if err := p.API.UnregisterCommand("", "fawkes"); err != nil { return err } autocompleteData := model.NewAutocompleteData("fawkes", "[command]", "Gets information about the msteams current state") whitelistedCmd := model.NewAutocompleteData("count-whitelisted", "", "Counts the amount of whitelisted users in msteamssync_whitelisted_users") // adds the from arg? autocompleteData.AddCommand(whitelistedCmd) lastConnectAtCmd := model.NewAutocompleteData("count-lastconnectat", "", "Counts the amount of users with a lastconnectat value != 0 in msteamssync_whitelisted_users") autocompleteData.AddCommand(lastConnectAtCmd) lastDisconnectAtCmd := model.NewAutocompleteData("count-lastdisconnectat", "", "Counts the amount of users with a lastdisconnectat value != 0 in msteamssync_whitelisted_users") autocompleteData.AddCommand(lastDisconnectAtCmd) userInfoCmd := model.NewAutocompleteData("user-info", "[username|email]", "Returns information about the user state regarding msteams") autocompleteData.AddCommand(userInfoCmd) channelInfoCmd := model.NewAutocompleteData("channel-info", "[channelID]", "Returns information about the channel state regarding msteams") autocompleteData.AddCommand(channelInfoCmd) listInvitesCmd := model.NewAutocompleteData("list-invites", "", "List the current invites") autocompleteData.AddCommand(listInvitesCmd) removeInviteCmd := model.NewAutocompleteData("remove-invite", "[mmUserID]", "Deletes the invite for the user ID") autocompleteData.AddCommand(removeInviteCmd) cmd := &model.Command{ Trigger: "fawkes", AutoComplete: true, AutoCompleteDesc: "Get insights of the user state", AutocompleteData: autocompleteData, } if err := p.API.RegisterCommand(cmd); err != nil { return err } p.API.LogDebug("plugin started") return nil } func (p *Plugin) ExecuteCommand(_ *plugin.Context, args *model.CommandArgs) (*model.CommandResponse, *model.AppError) { split := strings.Fields(args.Command) command := split[0] var parameters []string action := "" if len(split) > 1 { action = split[1] } if len(split) > 2 { parameters = split[2:] } if command != "/fawkes" { return &model.CommandResponse{}, nil } switch (action) { case "count-whitelisted": rows, err := p.Q().Select("count(*)").From("msteamssync_whitelisted_users").Query() if err != nil { return p.sendEphemeralAndExit(fmt.Sprintf("ERROR: %s", err), args) } defer rows.Close() var result int rows.Next() if err := rows.Scan(&result); err != nil { return p.sendEphemeralAndExit(fmt.Sprintf("ERROR: %s", err), args) } return p.sendEphemeralAndExit(fmt.Sprintf("There are %d users in the msteamssync_whitelisted_users table", result), args) case "count-lastconnectat": rows, err := p.Q().Select("count(*)").From("msteamssync_users").Where(sq.NotEq{"lastconnectat": 0}).Query() if err != nil { return p.sendEphemeralAndExit(fmt.Sprintf("ERROR: %s", err), args) } defer rows.Close() var result int rows.Next() if err := rows.Scan(&result); err != nil { return p.sendEphemeralAndExit(fmt.Sprintf("ERROR: %s", err), args) } return p.sendEphemeralAndExit(fmt.Sprintf("There are %d users in the msteamssync_users table with `lastconnectat != 0`", result), args) case "count-lastdisconnectat": rows, err := p.Q().Select("count(*)").From("msteamssync_users").Where(sq.NotEq{"lastdisconnectat": 0}).Query() if err != nil { return p.sendEphemeralAndExit(fmt.Sprintf("ERROR: %s", err), args) } defer rows.Close() var result int rows.Next() if err := rows.Scan(&result); err != nil { return p.sendEphemeralAndExit(fmt.Sprintf("ERROR: %s", err), args) } return p.sendEphemeralAndExit(fmt.Sprintf("There are %d users in the msteamssync_users table with `lastdisconnectat != 0`", result), args) case "user-info": if len(parameters) != 1 { return p.sendEphemeralAndExit("user-info must receive one \"username\" or \"email\" parameter", args) } email := parameters[0] var user *model.User var appErr *model.AppError user, appErr = p.API.GetUserByEmail(email) if appErr != nil { user, appErr = p.API.GetUserByUsername(email) if appErr != nil { return p.sendEphemeralAndExit(fmt.Sprintf("ERROR getting the user by email and username: %s", appErr.Error()), args) } } pref, appErr := p.API.GetPreferenceForUser(user.Id, "pp_com.mattermost.msteams-sync", "platform") if appErr != nil { return p.sendEphemeralAndExit(fmt.Sprintf("ERROR getting user preferences: %s", appErr.Error()), args) } rows, _ := p.Q(). Select("msteamsuserid, token, lastconnectat, lastdisconnectat"). From("msteamssync_users"). Where(sq.Eq{"mmuserid": user.Id}). Query() // if err != nil { // return p.sendEphemeralAndExit(fmt.Sprintf("ERROR querying for msteamssync user: %s", err), args) // } defer rows.Close() var mmTeamsUserID string var token string var lastConnectAt int var lastDisconnectAt int // if !rows.Next() { // return p.sendEphemeralAndExit(fmt.Sprintf("User %q not found in the msteamssync_users table", email), args) // } if rows.Next() { if err := rows.Scan(&mmTeamsUserID, &token, &lastConnectAt, &lastDisconnectAt); err != nil { return p.sendEphemeralAndExit(fmt.Sprintf("ERROR scanning rows: %s", err), args) } } var remoteID string if user.RemoteId != nil { remoteID = *user.RemoteId } hasToken := false if token != "" { hasToken = true } return p.sendEphemeralAndExit(fmt.Sprintf("User %q has\n - `mmTeamsUserID`: %s\n - `hasToken`: %v\n - `lastConnectAt`: %d\n - `lastDisconnectAt`: %d\n - `remoteID`: %s\n - `primaryPlatform preference`: %s", email, mmTeamsUserID, hasToken, lastConnectAt, lastDisconnectAt, remoteID, pref.Value), args) case "channel-info": if len(parameters) != 1 { return p.sendEphemeralAndExit("channel-info must receive the channel ID", args) } channelID := parameters[0] channel, appErr := p.API.GetChannel(channelID) if appErr != nil { return p.sendEphemeralAndExit(fmt.Sprintf("ERROR getting the channel: %s", appErr.Error()), args) } return p.sendEphemeralAndExit(fmt.Sprintf(" - `Id`: %s\n - `name`: %s\n - `IsShared`: %v", channel.Id, channel.Name, channel.IsShared()), args) case "list-invites": rows, err := p.Q(). Select("mmuserid, invitependingsince, invitelastsentat"). From("msteamssync_invited_users"). Query() if err != nil { return p.sendEphemeralAndExit(fmt.Sprintf("ERROR querying for invited users: %s", err), args) } defer rows.Close() invites := []map[string]any{} for rows.Next() { var mmUserID string var invitePendingSince int var inviteLastSentAt int if err := rows.Scan(&mmUserID, &invitePendingSince, &inviteLastSentAt); err != nil { return p.sendEphemeralAndExit(fmt.Sprintf("ERROR scanning row: %s", err), args) } invites = append(invites, map[string]any{ "mmUserID": mmUserID, "invitePendingSince": invitePendingSince, "inviteLastSentAt": inviteLastSentAt, }) } message := "Invite list:\n" for _, invite := range invites { message += fmt.Sprintf( " - `mmUserID`: %s :: `invitePendingSince`: %d :: `inviteLastSentAt`: %d\n", invite["mmUserID"], invite["invitePendingSince"], invite["inviteLastSentAt"], ) } return p.sendEphemeralAndExit(message, args) case "delete-invite": return p.sendEphemeralAndExit("Not implemented yet", args) default: p.sendEphemeral(fmt.Sprintf("Invalid command %q", action), args) return &model.CommandResponse{}, nil } } func (p *Plugin) sendEphemeral(message string, args *model.CommandArgs) { _ = p.API.SendEphemeralPost(args.UserId, &model.Post{ Message: message, UserId: args.UserId, ChannelId: args.ChannelId, }) } func (p *Plugin) sendEphemeralAndExit(message string, args *model.CommandArgs) (*model.CommandResponse, *model.AppError) { p.sendEphemeral(message, args) return &model.CommandResponse{}, nil } // See https://developers.mattermost.com/extend/plugins/server/reference/