2024-04-17 17:36:28 +01:00
package main
import (
2024-04-18 15:05:55 +01:00
"database/sql"
2024-04-17 17:36:28 +01:00
"fmt"
2024-04-18 15:05:55 +01:00
"strings"
2024-04-17 17:36:28 +01:00
"sync"
2024-04-18 15:05:55 +01:00
sq "github.com/Masterminds/squirrel"
"github.com/mattermost/mattermost/server/public/model"
2024-04-17 17:36:28 +01:00
"github.com/mattermost/mattermost/server/public/plugin"
2024-04-18 15:05:55 +01:00
pluginapi "github.com/mattermost/mattermost/server/public/pluginapi"
2024-04-17 17:36:28 +01:00
)
// 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
2024-04-18 15:05:55 +01:00
apiClient * pluginapi . Client
db * sql . DB
2024-04-17 17:36:28 +01:00
}
// ServeHTTP demonstrates a plugin that handles HTTP requests by greeting the world.
2024-04-18 15:05:55 +01:00
// 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 )
}
}
2024-04-18 16:23:07 +01:00
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 ( ) .
2024-04-18 15:05:55 +01:00
Select ( "msteamsuserid, token, lastconnectat, lastdisconnectat" ) .
From ( "msteamssync_users" ) .
Where ( sq . Eq { "mmuserid" : user . Id } ) .
Query ( )
2024-04-18 16:23:07 +01:00
// if err != nil {
// return p.sendEphemeralAndExit(fmt.Sprintf("ERROR querying for msteamssync user: %s", err), args)
// }
2024-04-18 15:05:55 +01:00
defer rows . Close ( )
var mmTeamsUserID string
var token string
var lastConnectAt int
var lastDisconnectAt int
2024-04-18 16:23:07 +01:00
// 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 )
}
2024-04-18 15:05:55 +01:00
}
var remoteID string
if user . RemoteId != nil {
remoteID = * user . RemoteId
}
hasToken := false
if token != "" {
hasToken = true
}
2024-04-18 16:23:07 +01:00
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 )
2024-04-18 15:05:55 +01:00
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
2024-04-17 17:36:28 +01:00
}
// See https://developers.mattermost.com/extend/plugins/server/reference/