github.com/mattermosttest/mattermost-server/v5@v5.0.0-20200917143240-9dfa12e121f9/app/command.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See LICENSE.txt for license information.
     3  
     4  package app
     5  
     6  import (
     7  	"errors"
     8  	"io"
     9  	"io/ioutil"
    10  	"net/http"
    11  	"net/url"
    12  	"strings"
    13  	"sync"
    14  	"unicode"
    15  
    16  	goi18n "github.com/mattermost/go-i18n/i18n"
    17  	"github.com/mattermost/mattermost-server/v5/mlog"
    18  	"github.com/mattermost/mattermost-server/v5/model"
    19  	"github.com/mattermost/mattermost-server/v5/store"
    20  	"github.com/mattermost/mattermost-server/v5/utils"
    21  )
    22  
    23  type CommandProvider interface {
    24  	GetTrigger() string
    25  	GetCommand(a *App, T goi18n.TranslateFunc) *model.Command
    26  	DoCommand(a *App, args *model.CommandArgs, message string) *model.CommandResponse
    27  }
    28  
    29  var commandProviders = make(map[string]CommandProvider)
    30  
    31  func RegisterCommandProvider(newProvider CommandProvider) {
    32  	commandProviders[newProvider.GetTrigger()] = newProvider
    33  }
    34  
    35  func GetCommandProvider(name string) CommandProvider {
    36  	provider, ok := commandProviders[name]
    37  	if ok {
    38  		return provider
    39  	}
    40  
    41  	return nil
    42  }
    43  
    44  // @openTracingParams teamId, skipSlackParsing
    45  func (a *App) CreateCommandPost(post *model.Post, teamId string, response *model.CommandResponse, skipSlackParsing bool) (*model.Post, *model.AppError) {
    46  	if skipSlackParsing {
    47  		post.Message = response.Text
    48  	} else {
    49  		post.Message = model.ParseSlackLinksToMarkdown(response.Text)
    50  	}
    51  
    52  	post.CreateAt = model.GetMillis()
    53  
    54  	if strings.HasPrefix(post.Type, model.POST_SYSTEM_MESSAGE_PREFIX) {
    55  		err := model.NewAppError("CreateCommandPost", "api.context.invalid_param.app_error", map[string]interface{}{"Name": "post.type"}, "", http.StatusBadRequest)
    56  		return nil, err
    57  	}
    58  
    59  	if response.Attachments != nil {
    60  		model.ParseSlackAttachment(post, response.Attachments)
    61  	}
    62  
    63  	if response.ResponseType == model.COMMAND_RESPONSE_TYPE_IN_CHANNEL {
    64  		return a.CreatePostMissingChannel(post, true)
    65  	}
    66  
    67  	if (response.ResponseType == "" || response.ResponseType == model.COMMAND_RESPONSE_TYPE_EPHEMERAL) && (response.Text != "" || response.Attachments != nil) {
    68  		post.ParentId = ""
    69  		a.SendEphemeralPost(post.UserId, post)
    70  	}
    71  
    72  	return post, nil
    73  }
    74  
    75  // @openTracingParams teamId
    76  // previous ListCommands now ListAutocompleteCommands
    77  func (a *App) ListAutocompleteCommands(teamId string, T goi18n.TranslateFunc) ([]*model.Command, *model.AppError) {
    78  	commands := make([]*model.Command, 0, 32)
    79  	seen := make(map[string]bool)
    80  
    81  	for _, cmd := range a.PluginCommandsForTeam(teamId) {
    82  		if cmd.AutoComplete && !seen[cmd.Trigger] {
    83  			seen[cmd.Trigger] = true
    84  			commands = append(commands, cmd)
    85  		}
    86  	}
    87  
    88  	if *a.Config().ServiceSettings.EnableCommands {
    89  		teamCmds, err := a.Srv().Store.Command().GetByTeam(teamId)
    90  		if err != nil {
    91  			return nil, model.NewAppError("ListAutocompleteCommands", "app.command.listautocompletecommands.internal_error", nil, err.Error(), http.StatusInternalServerError)
    92  		}
    93  
    94  		for _, cmd := range teamCmds {
    95  			if cmd.AutoComplete && !seen[cmd.Trigger] {
    96  				cmd.Sanitize()
    97  				seen[cmd.Trigger] = true
    98  				commands = append(commands, cmd)
    99  			}
   100  		}
   101  	}
   102  
   103  	for _, value := range commandProviders {
   104  		if cmd := value.GetCommand(a, T); cmd != nil {
   105  			cpy := *cmd
   106  			if cpy.AutoComplete && !seen[cpy.Trigger] {
   107  				cpy.Sanitize()
   108  				seen[cpy.Trigger] = true
   109  				commands = append(commands, &cpy)
   110  			}
   111  		}
   112  	}
   113  
   114  	return commands, nil
   115  }
   116  
   117  func (a *App) ListTeamCommands(teamId string) ([]*model.Command, *model.AppError) {
   118  	if !*a.Config().ServiceSettings.EnableCommands {
   119  		return nil, model.NewAppError("ListTeamCommands", "api.command.disabled.app_error", nil, "", http.StatusNotImplemented)
   120  	}
   121  
   122  	teamCmds, err := a.Srv().Store.Command().GetByTeam(teamId)
   123  	if err != nil {
   124  		return nil, model.NewAppError("ListTeamCommands", "app.command.listteamcommands.internal_error", nil, err.Error(), http.StatusInternalServerError)
   125  	}
   126  
   127  	return teamCmds, nil
   128  }
   129  
   130  func (a *App) ListAllCommands(teamId string, T goi18n.TranslateFunc) ([]*model.Command, *model.AppError) {
   131  	commands := make([]*model.Command, 0, 32)
   132  	seen := make(map[string]bool)
   133  	for _, value := range commandProviders {
   134  		if cmd := value.GetCommand(a, T); cmd != nil {
   135  			cpy := *cmd
   136  			if cpy.AutoComplete && !seen[cpy.Trigger] {
   137  				cpy.Sanitize()
   138  				seen[cpy.Trigger] = true
   139  				commands = append(commands, &cpy)
   140  			}
   141  		}
   142  	}
   143  
   144  	for _, cmd := range a.PluginCommandsForTeam(teamId) {
   145  		if !seen[cmd.Trigger] {
   146  			seen[cmd.Trigger] = true
   147  			commands = append(commands, cmd)
   148  		}
   149  	}
   150  
   151  	if *a.Config().ServiceSettings.EnableCommands {
   152  		teamCmds, err := a.Srv().Store.Command().GetByTeam(teamId)
   153  		if err != nil {
   154  			return nil, model.NewAppError("ListAllCommands", "app.command.listallcommands.internal_error", nil, err.Error(), http.StatusInternalServerError)
   155  		}
   156  		for _, cmd := range teamCmds {
   157  			if !seen[cmd.Trigger] {
   158  				cmd.Sanitize()
   159  				seen[cmd.Trigger] = true
   160  				commands = append(commands, cmd)
   161  			}
   162  		}
   163  	}
   164  
   165  	return commands, nil
   166  }
   167  
   168  // @openTracingParams args
   169  func (a *App) ExecuteCommand(args *model.CommandArgs) (*model.CommandResponse, *model.AppError) {
   170  	trigger := ""
   171  	message := ""
   172  	index := strings.IndexFunc(args.Command, unicode.IsSpace)
   173  	if index != -1 {
   174  		trigger = args.Command[:index]
   175  		message = args.Command[index+1:]
   176  	} else {
   177  		trigger = args.Command
   178  	}
   179  	trigger = strings.ToLower(trigger)
   180  	if !strings.HasPrefix(trigger, "/") {
   181  		return nil, model.NewAppError("command", "api.command.execute_command.format.app_error", map[string]interface{}{"Trigger": trigger}, "", http.StatusBadRequest)
   182  	}
   183  	trigger = strings.TrimPrefix(trigger, "/")
   184  
   185  	clientTriggerId, triggerId, appErr := model.GenerateTriggerId(args.UserId, a.AsymmetricSigningKey())
   186  	if appErr != nil {
   187  		mlog.Error("error occurred in generating trigger Id for a user ", mlog.Err(appErr))
   188  	}
   189  
   190  	args.TriggerId = triggerId
   191  
   192  	// Plugins can override built in and custom commands
   193  	cmd, response, appErr := a.tryExecutePluginCommand(args)
   194  	if appErr != nil {
   195  		return nil, appErr
   196  	} else if cmd != nil && response != nil {
   197  		response.TriggerId = clientTriggerId
   198  		return a.HandleCommandResponse(cmd, args, response, true)
   199  	}
   200  
   201  	// Custom commands can override built ins
   202  	cmd, response, appErr = a.tryExecuteCustomCommand(args, trigger, message)
   203  	if appErr != nil {
   204  		return nil, appErr
   205  	} else if cmd != nil && response != nil {
   206  		response.TriggerId = clientTriggerId
   207  		return a.HandleCommandResponse(cmd, args, response, false)
   208  	}
   209  
   210  	cmd, response = a.tryExecuteBuiltInCommand(args, trigger, message)
   211  	if cmd != nil && response != nil {
   212  		return a.HandleCommandResponse(cmd, args, response, true)
   213  	}
   214  
   215  	return nil, model.NewAppError("command", "api.command.execute_command.not_found.app_error", map[string]interface{}{"Trigger": trigger}, "", http.StatusNotFound)
   216  }
   217  
   218  // mentionsToTeamMembers returns all the @ mentions found in message that
   219  // belong to users in the specified team, linking them to their users
   220  func (a *App) mentionsToTeamMembers(message, teamId string) model.UserMentionMap {
   221  	type mentionMapItem struct {
   222  		Name string
   223  		Id   string
   224  	}
   225  
   226  	possibleMentions := model.PossibleAtMentions(message)
   227  	mentionChan := make(chan *mentionMapItem, len(possibleMentions))
   228  
   229  	var wg sync.WaitGroup
   230  	for _, mention := range possibleMentions {
   231  		wg.Add(1)
   232  		go func(mention string) {
   233  			defer wg.Done()
   234  			user, err := a.Srv().Store.User().GetByUsername(mention)
   235  
   236  			if err != nil && err.StatusCode != http.StatusNotFound {
   237  				mlog.Warn("Failed to retrieve user @"+mention, mlog.Err(err))
   238  				return
   239  			}
   240  
   241  			// If it's a http.StatusNotFound error, check for usernames in substrings
   242  			// without trailing punctuation
   243  			if err != nil {
   244  				trimmed, ok := model.TrimUsernameSpecialChar(mention)
   245  				for ; ok; trimmed, ok = model.TrimUsernameSpecialChar(trimmed) {
   246  					userFromTrimmed, userErr := a.Srv().Store.User().GetByUsername(trimmed)
   247  					if userErr != nil && err.StatusCode != http.StatusNotFound {
   248  						return
   249  					}
   250  
   251  					if userErr != nil {
   252  						continue
   253  					}
   254  
   255  					_, err = a.GetTeamMember(teamId, userFromTrimmed.Id)
   256  					if err != nil {
   257  						// The user is not in the team, so we should ignore it
   258  						return
   259  					}
   260  
   261  					mentionChan <- &mentionMapItem{trimmed, userFromTrimmed.Id}
   262  					return
   263  				}
   264  
   265  				return
   266  			}
   267  
   268  			_, err = a.GetTeamMember(teamId, user.Id)
   269  			if err != nil {
   270  				// The user is not in the team, so we should ignore it
   271  				return
   272  			}
   273  
   274  			mentionChan <- &mentionMapItem{mention, user.Id}
   275  		}(mention)
   276  	}
   277  
   278  	wg.Wait()
   279  	close(mentionChan)
   280  
   281  	atMentionMap := make(model.UserMentionMap)
   282  	for mention := range mentionChan {
   283  		atMentionMap[mention.Name] = mention.Id
   284  	}
   285  
   286  	return atMentionMap
   287  }
   288  
   289  // mentionsToPublicChannels returns all the mentions to public channels,
   290  // linking them to their channels
   291  func (a *App) mentionsToPublicChannels(message, teamId string) model.ChannelMentionMap {
   292  	type mentionMapItem struct {
   293  		Name string
   294  		Id   string
   295  	}
   296  
   297  	channelMentions := model.ChannelMentions(message)
   298  	mentionChan := make(chan *mentionMapItem, len(channelMentions))
   299  
   300  	var wg sync.WaitGroup
   301  	for _, channelName := range channelMentions {
   302  		wg.Add(1)
   303  		go func(channelName string) {
   304  			defer wg.Done()
   305  			channel, err := a.GetChannelByName(channelName, teamId, false)
   306  			if err != nil {
   307  				return
   308  			}
   309  
   310  			if !channel.IsOpen() {
   311  				return
   312  			}
   313  
   314  			mentionChan <- &mentionMapItem{channelName, channel.Id}
   315  		}(channelName)
   316  	}
   317  
   318  	wg.Wait()
   319  	close(mentionChan)
   320  
   321  	channelMentionMap := make(model.ChannelMentionMap)
   322  	for mention := range mentionChan {
   323  		channelMentionMap[mention.Name] = mention.Id
   324  	}
   325  
   326  	return channelMentionMap
   327  }
   328  
   329  // tryExecuteBuiltInCommand attempts to run a built in command based on the given arguments. If no such command can be
   330  // found, returns nil for all arguments.
   331  func (a *App) tryExecuteBuiltInCommand(args *model.CommandArgs, trigger string, message string) (*model.Command, *model.CommandResponse) {
   332  	provider := GetCommandProvider(trigger)
   333  	if provider == nil {
   334  		return nil, nil
   335  	}
   336  
   337  	cmd := provider.GetCommand(a, args.T)
   338  	if cmd == nil {
   339  		return nil, nil
   340  	}
   341  
   342  	return cmd, provider.DoCommand(a, args, message)
   343  }
   344  
   345  // tryExecuteCustomCommand attempts to run a custom command based on the given arguments. If no such command can be
   346  // found, returns nil for all arguments.
   347  func (a *App) tryExecuteCustomCommand(args *model.CommandArgs, trigger string, message string) (*model.Command, *model.CommandResponse, *model.AppError) {
   348  	// Handle custom commands
   349  	if !*a.Config().ServiceSettings.EnableCommands {
   350  		return nil, nil, model.NewAppError("ExecuteCommand", "api.command.disabled.app_error", nil, "", http.StatusNotImplemented)
   351  	}
   352  
   353  	chanChan := make(chan store.StoreResult, 1)
   354  	go func() {
   355  		channel, err := a.Srv().Store.Channel().Get(args.ChannelId, true)
   356  		chanChan <- store.StoreResult{Data: channel, NErr: err}
   357  		close(chanChan)
   358  	}()
   359  
   360  	teamChan := make(chan store.StoreResult, 1)
   361  	go func() {
   362  		team, err := a.Srv().Store.Team().Get(args.TeamId)
   363  		teamChan <- store.StoreResult{Data: team, Err: err}
   364  		close(teamChan)
   365  	}()
   366  
   367  	userChan := make(chan store.StoreResult, 1)
   368  	go func() {
   369  		user, err := a.Srv().Store.User().Get(args.UserId)
   370  		userChan <- store.StoreResult{Data: user, Err: err}
   371  		close(userChan)
   372  	}()
   373  
   374  	teamCmds, err := a.Srv().Store.Command().GetByTeam(args.TeamId)
   375  	if err != nil {
   376  		return nil, nil, model.NewAppError("tryExecuteCustomCommand", "app.command.tryexecutecustomcommand.internal_error", nil, err.Error(), http.StatusInternalServerError)
   377  	}
   378  
   379  	tr := <-teamChan
   380  	if tr.Err != nil {
   381  		return nil, nil, tr.Err
   382  	}
   383  	team := tr.Data.(*model.Team)
   384  
   385  	ur := <-userChan
   386  	if ur.Err != nil {
   387  		return nil, nil, ur.Err
   388  	}
   389  	user := ur.Data.(*model.User)
   390  
   391  	cr := <-chanChan
   392  	if cr.NErr != nil {
   393  		var nfErr *store.ErrNotFound
   394  		switch {
   395  		case errors.As(cr.NErr, &nfErr):
   396  			return nil, nil, model.NewAppError("tryExecuteCustomCommand", "app.channel.get.existing.app_error", nil, nfErr.Error(), http.StatusNotFound)
   397  		default:
   398  			return nil, nil, model.NewAppError("tryExecuteCustomCommand", "app.channel.get.find.app_error", nil, cr.NErr.Error(), http.StatusInternalServerError)
   399  		}
   400  	}
   401  	channel := cr.Data.(*model.Channel)
   402  
   403  	var cmd *model.Command
   404  
   405  	for _, teamCmd := range teamCmds {
   406  		if trigger == teamCmd.Trigger {
   407  			cmd = teamCmd
   408  		}
   409  	}
   410  
   411  	if cmd == nil {
   412  		return nil, nil, nil
   413  	}
   414  
   415  	mlog.Debug("Executing command", mlog.String("command", trigger), mlog.String("user_id", args.UserId))
   416  
   417  	p := url.Values{}
   418  	p.Set("token", cmd.Token)
   419  
   420  	p.Set("team_id", cmd.TeamId)
   421  	p.Set("team_domain", team.Name)
   422  
   423  	p.Set("channel_id", args.ChannelId)
   424  	p.Set("channel_name", channel.Name)
   425  
   426  	p.Set("user_id", args.UserId)
   427  	p.Set("user_name", user.Username)
   428  
   429  	p.Set("command", "/"+trigger)
   430  	p.Set("text", message)
   431  
   432  	p.Set("trigger_id", args.TriggerId)
   433  
   434  	userMentionMap := a.mentionsToTeamMembers(message, team.Id)
   435  	for key, values := range userMentionMap.ToURLValues() {
   436  		p[key] = values
   437  	}
   438  
   439  	channelMentionMap := a.mentionsToPublicChannels(message, team.Id)
   440  	for key, values := range channelMentionMap.ToURLValues() {
   441  		p[key] = values
   442  	}
   443  
   444  	hook, appErr := a.CreateCommandWebhook(cmd.Id, args)
   445  	if appErr != nil {
   446  		return cmd, nil, model.NewAppError("command", "api.command.execute_command.failed.app_error", map[string]interface{}{"Trigger": trigger}, appErr.Error(), http.StatusInternalServerError)
   447  	}
   448  	p.Set("response_url", args.SiteURL+"/hooks/commands/"+hook.Id)
   449  
   450  	return a.doCommandRequest(cmd, p)
   451  }
   452  
   453  func (a *App) doCommandRequest(cmd *model.Command, p url.Values) (*model.Command, *model.CommandResponse, *model.AppError) {
   454  	// Prepare the request
   455  	var req *http.Request
   456  	var err error
   457  	if cmd.Method == model.COMMAND_METHOD_GET {
   458  		req, err = http.NewRequest(http.MethodGet, cmd.URL, nil)
   459  	} else {
   460  		req, err = http.NewRequest(http.MethodPost, cmd.URL, strings.NewReader(p.Encode()))
   461  	}
   462  
   463  	if err != nil {
   464  		return cmd, nil, model.NewAppError("command", "api.command.execute_command.failed.app_error", map[string]interface{}{"Trigger": cmd.Trigger}, err.Error(), http.StatusInternalServerError)
   465  	}
   466  
   467  	if cmd.Method == model.COMMAND_METHOD_GET {
   468  		if req.URL.RawQuery != "" {
   469  			req.URL.RawQuery += "&"
   470  		}
   471  		req.URL.RawQuery += p.Encode()
   472  	}
   473  
   474  	req.Header.Set("Accept", "application/json")
   475  	req.Header.Set("Authorization", "Token "+cmd.Token)
   476  	if cmd.Method == model.COMMAND_METHOD_POST {
   477  		req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
   478  	}
   479  
   480  	// Send the request
   481  	resp, err := a.HTTPService().MakeClient(false).Do(req)
   482  	if err != nil {
   483  		return cmd, nil, model.NewAppError("command", "api.command.execute_command.failed.app_error", map[string]interface{}{"Trigger": cmd.Trigger}, err.Error(), http.StatusInternalServerError)
   484  	}
   485  
   486  	defer resp.Body.Close()
   487  
   488  	// Handle the response
   489  	body := io.LimitReader(resp.Body, MaxIntegrationResponseSize)
   490  
   491  	if resp.StatusCode != http.StatusOK {
   492  		// Ignore the error below because the resulting string will just be the empty string if bodyBytes is nil
   493  		bodyBytes, _ := ioutil.ReadAll(body)
   494  
   495  		return cmd, nil, model.NewAppError("command", "api.command.execute_command.failed_resp.app_error", map[string]interface{}{"Trigger": cmd.Trigger, "Status": resp.Status}, string(bodyBytes), http.StatusInternalServerError)
   496  	}
   497  
   498  	response, err := model.CommandResponseFromHTTPBody(resp.Header.Get("Content-Type"), body)
   499  	if err != nil {
   500  		return cmd, nil, model.NewAppError("command", "api.command.execute_command.failed.app_error", map[string]interface{}{"Trigger": cmd.Trigger}, err.Error(), http.StatusInternalServerError)
   501  	} else if response == nil {
   502  		return cmd, nil, model.NewAppError("command", "api.command.execute_command.failed_empty.app_error", map[string]interface{}{"Trigger": cmd.Trigger}, "", http.StatusInternalServerError)
   503  	}
   504  
   505  	return cmd, response, nil
   506  }
   507  
   508  func (a *App) HandleCommandResponse(command *model.Command, args *model.CommandArgs, response *model.CommandResponse, builtIn bool) (*model.CommandResponse, *model.AppError) {
   509  	trigger := ""
   510  	if len(args.Command) != 0 {
   511  		parts := strings.Split(args.Command, " ")
   512  		trigger = parts[0][1:]
   513  		trigger = strings.ToLower(trigger)
   514  	}
   515  
   516  	var lastError *model.AppError
   517  	_, err := a.HandleCommandResponsePost(command, args, response, builtIn)
   518  
   519  	if err != nil {
   520  		mlog.Error("error occurred in handling command response post", mlog.Err(err))
   521  		lastError = err
   522  	}
   523  
   524  	if response.ExtraResponses != nil {
   525  		for _, resp := range response.ExtraResponses {
   526  			_, err := a.HandleCommandResponsePost(command, args, resp, builtIn)
   527  
   528  			if err != nil {
   529  				mlog.Error("error occurred in handling command response post", mlog.Err(err))
   530  				lastError = err
   531  			}
   532  		}
   533  	}
   534  
   535  	if lastError != nil {
   536  		return response, model.NewAppError("command", "api.command.execute_command.create_post_failed.app_error", map[string]interface{}{"Trigger": trigger}, "", http.StatusInternalServerError)
   537  	}
   538  
   539  	return response, nil
   540  }
   541  
   542  func (a *App) HandleCommandResponsePost(command *model.Command, args *model.CommandArgs, response *model.CommandResponse, builtIn bool) (*model.Post, *model.AppError) {
   543  	post := &model.Post{}
   544  	post.ChannelId = args.ChannelId
   545  	post.RootId = args.RootId
   546  	post.ParentId = args.ParentId
   547  	post.UserId = args.UserId
   548  	post.Type = response.Type
   549  	post.SetProps(response.Props)
   550  
   551  	if len(response.ChannelId) != 0 {
   552  		_, err := a.GetChannelMember(response.ChannelId, args.UserId)
   553  		if err != nil {
   554  			err = model.NewAppError("HandleCommandResponsePost", "api.command.command_post.forbidden.app_error", nil, err.Error(), http.StatusForbidden)
   555  			return nil, err
   556  		}
   557  		post.ChannelId = response.ChannelId
   558  	}
   559  
   560  	isBotPost := !builtIn
   561  
   562  	if *a.Config().ServiceSettings.EnablePostUsernameOverride {
   563  		if len(command.Username) != 0 {
   564  			post.AddProp("override_username", command.Username)
   565  			isBotPost = true
   566  		} else if len(response.Username) != 0 {
   567  			post.AddProp("override_username", response.Username)
   568  			isBotPost = true
   569  		}
   570  	}
   571  
   572  	if *a.Config().ServiceSettings.EnablePostIconOverride {
   573  		if len(command.IconURL) != 0 {
   574  			post.AddProp("override_icon_url", command.IconURL)
   575  			isBotPost = true
   576  		} else if len(response.IconURL) != 0 {
   577  			post.AddProp("override_icon_url", response.IconURL)
   578  			isBotPost = true
   579  		} else {
   580  			post.AddProp("override_icon_url", "")
   581  		}
   582  	}
   583  
   584  	if isBotPost {
   585  		post.AddProp("from_webhook", "true")
   586  	}
   587  
   588  	// Process Slack text replacements if the response does not contain "skip_slack_parsing": true.
   589  	if !response.SkipSlackParsing {
   590  		response.Text = a.ProcessSlackText(response.Text)
   591  		response.Attachments = a.ProcessSlackAttachments(response.Attachments)
   592  	}
   593  
   594  	if _, err := a.CreateCommandPost(post, args.TeamId, response, response.SkipSlackParsing); err != nil {
   595  		return post, err
   596  	}
   597  
   598  	return post, nil
   599  }
   600  
   601  func (a *App) CreateCommand(cmd *model.Command) (*model.Command, *model.AppError) {
   602  	if !*a.Config().ServiceSettings.EnableCommands {
   603  		return nil, model.NewAppError("CreateCommand", "api.command.disabled.app_error", nil, "", http.StatusNotImplemented)
   604  	}
   605  
   606  	return a.createCommand(cmd)
   607  }
   608  
   609  func (a *App) createCommand(cmd *model.Command) (*model.Command, *model.AppError) {
   610  	cmd.Trigger = strings.ToLower(cmd.Trigger)
   611  
   612  	teamCmds, err := a.Srv().Store.Command().GetByTeam(cmd.TeamId)
   613  	if err != nil {
   614  		return nil, model.NewAppError("CreateCommand", "app.command.createcommand.internal_error", nil, err.Error(), http.StatusInternalServerError)
   615  	}
   616  
   617  	for _, existingCommand := range teamCmds {
   618  		if cmd.Trigger == existingCommand.Trigger {
   619  			return nil, model.NewAppError("CreateCommand", "api.command.duplicate_trigger.app_error", nil, "", http.StatusBadRequest)
   620  		}
   621  	}
   622  
   623  	for _, builtInProvider := range commandProviders {
   624  		builtInCommand := builtInProvider.GetCommand(a, utils.T)
   625  		if builtInCommand != nil && cmd.Trigger == builtInCommand.Trigger {
   626  			return nil, model.NewAppError("CreateCommand", "api.command.duplicate_trigger.app_error", nil, "", http.StatusBadRequest)
   627  		}
   628  	}
   629  
   630  	command, nErr := a.Srv().Store.Command().Save(cmd)
   631  	if nErr != nil {
   632  		var appErr *model.AppError
   633  		switch {
   634  		case errors.As(nErr, &appErr):
   635  			return nil, appErr
   636  		default:
   637  			return nil, model.NewAppError("CreateCommand", "app.command.createcommand.internal_error", nil, nErr.Error(), http.StatusInternalServerError)
   638  		}
   639  	}
   640  
   641  	return command, nil
   642  }
   643  
   644  func (a *App) GetCommand(commandId string) (*model.Command, *model.AppError) {
   645  	if !*a.Config().ServiceSettings.EnableCommands {
   646  		return nil, model.NewAppError("GetCommand", "api.command.disabled.app_error", nil, "", http.StatusNotImplemented)
   647  	}
   648  
   649  	command, err := a.Srv().Store.Command().Get(commandId)
   650  	if err != nil {
   651  		var nfErr *store.ErrNotFound
   652  		switch {
   653  		case errors.As(err, &nfErr):
   654  			return nil, model.NewAppError("SqlCommandStore.Get", "store.sql_command.get.missing.app_error", map[string]interface{}{"command_id": commandId}, "", http.StatusNotFound)
   655  		default:
   656  			return nil, model.NewAppError("GetCommand", "app.command.getcommand.internal_error", nil, err.Error(), http.StatusInternalServerError)
   657  		}
   658  	}
   659  	return command, nil
   660  }
   661  
   662  func (a *App) UpdateCommand(oldCmd, updatedCmd *model.Command) (*model.Command, *model.AppError) {
   663  	if !*a.Config().ServiceSettings.EnableCommands {
   664  		return nil, model.NewAppError("UpdateCommand", "api.command.disabled.app_error", nil, "", http.StatusNotImplemented)
   665  	}
   666  
   667  	updatedCmd.Trigger = strings.ToLower(updatedCmd.Trigger)
   668  	updatedCmd.Id = oldCmd.Id
   669  	updatedCmd.Token = oldCmd.Token
   670  	updatedCmd.CreateAt = oldCmd.CreateAt
   671  	updatedCmd.UpdateAt = model.GetMillis()
   672  	updatedCmd.DeleteAt = oldCmd.DeleteAt
   673  	updatedCmd.CreatorId = oldCmd.CreatorId
   674  	updatedCmd.PluginId = oldCmd.PluginId
   675  	updatedCmd.TeamId = oldCmd.TeamId
   676  
   677  	command, err := a.Srv().Store.Command().Update(updatedCmd)
   678  	if err != nil {
   679  		var nfErr *store.ErrNotFound
   680  		var appErr *model.AppError
   681  		switch {
   682  		case errors.As(err, &nfErr):
   683  			return nil, model.NewAppError("SqlCommandStore.Update", "store.sql_command.update.missing.app_error", map[string]interface{}{"command_id": updatedCmd.Id}, "", http.StatusNotFound)
   684  		case errors.As(err, &appErr):
   685  			return nil, appErr
   686  		default:
   687  			return nil, model.NewAppError("UpdateCommand", "app.command.updatecommand.internal_error", nil, err.Error(), http.StatusInternalServerError)
   688  		}
   689  	}
   690  
   691  	return command, nil
   692  }
   693  
   694  func (a *App) MoveCommand(team *model.Team, command *model.Command) *model.AppError {
   695  	command.TeamId = team.Id
   696  
   697  	_, err := a.Srv().Store.Command().Update(command)
   698  	if err != nil {
   699  		var nfErr *store.ErrNotFound
   700  		var appErr *model.AppError
   701  		switch {
   702  		case errors.As(err, &nfErr):
   703  			return model.NewAppError("SqlCommandStore.Update", "store.sql_command.update.missing.app_error", map[string]interface{}{"command_id": command.Id}, "", http.StatusNotFound)
   704  		case errors.As(err, &appErr):
   705  			return appErr
   706  		default:
   707  			return model.NewAppError("MoveCommand", "app.command.movecommand.internal_error", nil, err.Error(), http.StatusInternalServerError)
   708  		}
   709  	}
   710  
   711  	return nil
   712  }
   713  
   714  func (a *App) RegenCommandToken(cmd *model.Command) (*model.Command, *model.AppError) {
   715  	if !*a.Config().ServiceSettings.EnableCommands {
   716  		return nil, model.NewAppError("RegenCommandToken", "api.command.disabled.app_error", nil, "", http.StatusNotImplemented)
   717  	}
   718  
   719  	cmd.Token = model.NewId()
   720  
   721  	command, err := a.Srv().Store.Command().Update(cmd)
   722  	if err != nil {
   723  		var nfErr *store.ErrNotFound
   724  		var appErr *model.AppError
   725  		switch {
   726  		case errors.As(err, &nfErr):
   727  			return nil, model.NewAppError("SqlCommandStore.Update", "store.sql_command.update.missing.app_error", map[string]interface{}{"command_id": cmd.Id}, "", http.StatusNotFound)
   728  		case errors.As(err, &appErr):
   729  			return nil, appErr
   730  		default:
   731  			return nil, model.NewAppError("RegenCommandToken", "app.command.regencommandtoken.internal_error", nil, err.Error(), http.StatusInternalServerError)
   732  		}
   733  	}
   734  
   735  	return command, nil
   736  }
   737  
   738  func (a *App) DeleteCommand(commandId string) *model.AppError {
   739  	if !*a.Config().ServiceSettings.EnableCommands {
   740  		return model.NewAppError("DeleteCommand", "api.command.disabled.app_error", nil, "", http.StatusNotImplemented)
   741  	}
   742  
   743  	err := a.Srv().Store.Command().Delete(commandId, model.GetMillis())
   744  	if err != nil {
   745  		return model.NewAppError("DeleteCommand", "app.command.deletecommand.internal_error", nil, err.Error(), http.StatusInternalServerError)
   746  	}
   747  
   748  	return nil
   749  }