github.com/spline-fu/mattermost-server@v4.10.10+incompatible/app/command.go (about) 1 // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 // See License.txt for license information. 3 4 package app 5 6 import ( 7 "fmt" 8 "io" 9 "io/ioutil" 10 "net/http" 11 "net/url" 12 "strings" 13 14 "github.com/mattermost/mattermost-server/mlog" 15 "github.com/mattermost/mattermost-server/model" 16 "github.com/mattermost/mattermost-server/utils" 17 goi18n "github.com/nicksnyder/go-i18n/i18n" 18 ) 19 20 type CommandProvider interface { 21 GetTrigger() string 22 GetCommand(a *App, T goi18n.TranslateFunc) *model.Command 23 DoCommand(a *App, args *model.CommandArgs, message string) *model.CommandResponse 24 } 25 26 var commandProviders = make(map[string]CommandProvider) 27 28 func RegisterCommandProvider(newProvider CommandProvider) { 29 commandProviders[newProvider.GetTrigger()] = newProvider 30 } 31 32 func GetCommandProvider(name string) CommandProvider { 33 provider, ok := commandProviders[name] 34 if ok { 35 return provider 36 } 37 38 return nil 39 } 40 41 func (a *App) CreateCommandPost(post *model.Post, teamId string, response *model.CommandResponse) (*model.Post, *model.AppError) { 42 post.Message = parseSlackLinksToMarkdown(response.Text) 43 post.CreateAt = model.GetMillis() 44 45 if strings.HasPrefix(post.Type, model.POST_SYSTEM_MESSAGE_PREFIX) { 46 err := model.NewAppError("CreateCommandPost", "api.context.invalid_param.app_error", map[string]interface{}{"Name": "post.type"}, "", http.StatusBadRequest) 47 return nil, err 48 } 49 50 if response.Attachments != nil { 51 parseSlackAttachment(post, response.Attachments) 52 } 53 54 if response.ResponseType == model.COMMAND_RESPONSE_TYPE_IN_CHANNEL { 55 return a.CreatePostMissingChannel(post, true) 56 } else if (response.ResponseType == "" || response.ResponseType == model.COMMAND_RESPONSE_TYPE_EPHEMERAL) && (response.Text != "" || response.Attachments != nil) { 57 post.ParentId = "" 58 a.SendEphemeralPost(post.UserId, post) 59 } 60 61 return post, nil 62 } 63 64 // previous ListCommands now ListAutocompleteCommands 65 func (a *App) ListAutocompleteCommands(teamId string, T goi18n.TranslateFunc) ([]*model.Command, *model.AppError) { 66 commands := make([]*model.Command, 0, 32) 67 seen := make(map[string]bool) 68 for _, value := range commandProviders { 69 if cmd := value.GetCommand(a, T); cmd != nil { 70 cpy := *cmd 71 if cpy.AutoComplete && !seen[cpy.Id] { 72 cpy.Sanitize() 73 seen[cpy.Trigger] = true 74 commands = append(commands, &cpy) 75 } 76 } 77 } 78 79 for _, cmd := range a.PluginCommandsForTeam(teamId) { 80 if cmd.AutoComplete && !seen[cmd.Trigger] { 81 seen[cmd.Trigger] = true 82 commands = append(commands, cmd) 83 } 84 } 85 86 if *a.Config().ServiceSettings.EnableCommands { 87 if result := <-a.Srv.Store.Command().GetByTeam(teamId); result.Err != nil { 88 return nil, result.Err 89 } else { 90 teamCmds := result.Data.([]*model.Command) 91 for _, cmd := range teamCmds { 92 if cmd.AutoComplete && !seen[cmd.Id] { 93 cmd.Sanitize() 94 seen[cmd.Trigger] = true 95 commands = append(commands, cmd) 96 } 97 } 98 } 99 } 100 101 return commands, nil 102 } 103 104 func (a *App) ListTeamCommands(teamId string) ([]*model.Command, *model.AppError) { 105 if !*a.Config().ServiceSettings.EnableCommands { 106 return nil, model.NewAppError("ListTeamCommands", "api.command.disabled.app_error", nil, "", http.StatusNotImplemented) 107 } 108 109 if result := <-a.Srv.Store.Command().GetByTeam(teamId); result.Err != nil { 110 return nil, result.Err 111 } else { 112 return result.Data.([]*model.Command), nil 113 } 114 } 115 116 func (a *App) ListAllCommands(teamId string, T goi18n.TranslateFunc) ([]*model.Command, *model.AppError) { 117 commands := make([]*model.Command, 0, 32) 118 seen := make(map[string]bool) 119 for _, value := range commandProviders { 120 if cmd := value.GetCommand(a, T); cmd != nil { 121 cpy := *cmd 122 if cpy.AutoComplete && !seen[cpy.Trigger] { 123 cpy.Sanitize() 124 seen[cpy.Trigger] = true 125 commands = append(commands, &cpy) 126 } 127 } 128 } 129 130 for _, cmd := range a.PluginCommandsForTeam(teamId) { 131 if !seen[cmd.Trigger] { 132 seen[cmd.Trigger] = true 133 commands = append(commands, cmd) 134 } 135 } 136 137 if *a.Config().ServiceSettings.EnableCommands { 138 if result := <-a.Srv.Store.Command().GetByTeam(teamId); result.Err != nil { 139 return nil, result.Err 140 } else { 141 teamCmds := result.Data.([]*model.Command) 142 for _, cmd := range teamCmds { 143 if !seen[cmd.Trigger] { 144 cmd.Sanitize() 145 seen[cmd.Trigger] = true 146 commands = append(commands, cmd) 147 } 148 } 149 } 150 } 151 152 return commands, nil 153 } 154 155 func (a *App) ExecuteCommand(args *model.CommandArgs) (*model.CommandResponse, *model.AppError) { 156 parts := strings.Split(args.Command, " ") 157 trigger := parts[0][1:] 158 trigger = strings.ToLower(trigger) 159 message := strings.Join(parts[1:], " ") 160 provider := GetCommandProvider(trigger) 161 162 if provider != nil { 163 if cmd := provider.GetCommand(a, args.T); cmd != nil { 164 response := provider.DoCommand(a, args, message) 165 return a.HandleCommandResponse(cmd, args, response, true) 166 } 167 } 168 169 if cmd, response, err := a.ExecutePluginCommand(args); err != nil { 170 return nil, err 171 } else if cmd != nil { 172 return a.HandleCommandResponse(cmd, args, response, true) 173 } 174 175 if !*a.Config().ServiceSettings.EnableCommands { 176 return nil, model.NewAppError("ExecuteCommand", "api.command.disabled.app_error", nil, "", http.StatusNotImplemented) 177 } 178 179 chanChan := a.Srv.Store.Channel().Get(args.ChannelId, true) 180 teamChan := a.Srv.Store.Team().Get(args.TeamId) 181 userChan := a.Srv.Store.User().Get(args.UserId) 182 183 if result := <-a.Srv.Store.Command().GetByTeam(args.TeamId); result.Err != nil { 184 return nil, result.Err 185 } else { 186 187 var team *model.Team 188 if tr := <-teamChan; tr.Err != nil { 189 return nil, tr.Err 190 } else { 191 team = tr.Data.(*model.Team) 192 } 193 194 var user *model.User 195 if ur := <-userChan; ur.Err != nil { 196 return nil, ur.Err 197 } else { 198 user = ur.Data.(*model.User) 199 } 200 201 var channel *model.Channel 202 if cr := <-chanChan; cr.Err != nil { 203 return nil, cr.Err 204 } else { 205 channel = cr.Data.(*model.Channel) 206 } 207 208 teamCmds := result.Data.([]*model.Command) 209 for _, cmd := range teamCmds { 210 if trigger == cmd.Trigger { 211 mlog.Debug(fmt.Sprintf(utils.T("api.command.execute_command.debug"), trigger, args.UserId)) 212 213 p := url.Values{} 214 p.Set("token", cmd.Token) 215 216 p.Set("team_id", cmd.TeamId) 217 p.Set("team_domain", team.Name) 218 219 p.Set("channel_id", args.ChannelId) 220 p.Set("channel_name", channel.Name) 221 222 p.Set("user_id", args.UserId) 223 p.Set("user_name", user.Username) 224 225 p.Set("command", "/"+trigger) 226 p.Set("text", message) 227 228 if hook, err := a.CreateCommandWebhook(cmd.Id, args); err != nil { 229 return nil, model.NewAppError("command", "api.command.execute_command.failed.app_error", map[string]interface{}{"Trigger": trigger}, err.Error(), http.StatusInternalServerError) 230 } else { 231 p.Set("response_url", args.SiteURL+"/hooks/commands/"+hook.Id) 232 } 233 234 method := "POST" 235 if cmd.Method == model.COMMAND_METHOD_GET { 236 method = "GET" 237 } 238 239 req, _ := http.NewRequest(method, cmd.URL, strings.NewReader(p.Encode())) 240 req.Header.Set("Accept", "application/json") 241 req.Header.Set("Authorization", "Token "+cmd.Token) 242 if cmd.Method == model.COMMAND_METHOD_POST { 243 req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 244 } 245 246 if resp, err := a.HTTPClient(false).Do(req); err != nil { 247 return nil, model.NewAppError("command", "api.command.execute_command.failed.app_error", map[string]interface{}{"Trigger": trigger}, err.Error(), http.StatusInternalServerError) 248 } else { 249 defer resp.Body.Close() 250 251 body := io.LimitReader(resp.Body, MaxIntegrationResponseSize) 252 253 if resp.StatusCode == http.StatusOK { 254 if response, err := model.CommandResponseFromHTTPBody(resp.Header.Get("Content-Type"), body); err != nil { 255 return nil, model.NewAppError("command", "api.command.execute_command.failed.app_error", map[string]interface{}{"Trigger": trigger}, err.Error(), http.StatusInternalServerError) 256 } else if response == nil { 257 return nil, model.NewAppError("command", "api.command.execute_command.failed_empty.app_error", map[string]interface{}{"Trigger": trigger}, "", http.StatusInternalServerError) 258 } else { 259 return a.HandleCommandResponse(cmd, args, response, false) 260 } 261 } else { 262 bodyBytes, _ := ioutil.ReadAll(body) 263 return nil, model.NewAppError("command", "api.command.execute_command.failed_resp.app_error", map[string]interface{}{"Trigger": trigger, "Status": resp.Status}, string(bodyBytes), http.StatusInternalServerError) 264 } 265 } 266 } 267 } 268 } 269 270 return nil, model.NewAppError("command", "api.command.execute_command.not_found.app_error", map[string]interface{}{"Trigger": trigger}, "", http.StatusNotFound) 271 } 272 273 func (a *App) HandleCommandResponse(command *model.Command, args *model.CommandArgs, response *model.CommandResponse, builtIn bool) (*model.CommandResponse, *model.AppError) { 274 post := &model.Post{} 275 post.ChannelId = args.ChannelId 276 post.RootId = args.RootId 277 post.ParentId = args.ParentId 278 post.UserId = args.UserId 279 post.Type = response.Type 280 post.Props = response.Props 281 282 isBotPost := !builtIn 283 284 if a.Config().ServiceSettings.EnablePostUsernameOverride { 285 if len(command.Username) != 0 { 286 post.AddProp("override_username", command.Username) 287 isBotPost = true 288 } else if len(response.Username) != 0 { 289 post.AddProp("override_username", response.Username) 290 isBotPost = true 291 } 292 } 293 294 if a.Config().ServiceSettings.EnablePostIconOverride { 295 if len(command.IconURL) != 0 { 296 post.AddProp("override_icon_url", command.IconURL) 297 isBotPost = true 298 } else if len(response.IconURL) != 0 { 299 post.AddProp("override_icon_url", response.IconURL) 300 isBotPost = true 301 } else { 302 post.AddProp("override_icon_url", "") 303 } 304 } 305 306 if isBotPost { 307 post.AddProp("from_webhook", "true") 308 } 309 310 // Process Slack text replacements 311 response.Text = a.ProcessSlackText(response.Text) 312 response.Attachments = a.ProcessSlackAttachments(response.Attachments) 313 314 if _, err := a.CreateCommandPost(post, args.TeamId, response); err != nil { 315 mlog.Error(err.Error()) 316 } 317 318 return response, nil 319 } 320 321 func (a *App) CreateCommand(cmd *model.Command) (*model.Command, *model.AppError) { 322 if !*a.Config().ServiceSettings.EnableCommands { 323 return nil, model.NewAppError("CreateCommand", "api.command.disabled.app_error", nil, "", http.StatusNotImplemented) 324 } 325 326 cmd.Trigger = strings.ToLower(cmd.Trigger) 327 328 if result := <-a.Srv.Store.Command().GetByTeam(cmd.TeamId); result.Err != nil { 329 return nil, result.Err 330 } else { 331 teamCmds := result.Data.([]*model.Command) 332 for _, existingCommand := range teamCmds { 333 if cmd.Trigger == existingCommand.Trigger { 334 return nil, model.NewAppError("CreateCommand", "api.command.duplicate_trigger.app_error", nil, "", http.StatusBadRequest) 335 } 336 } 337 for _, builtInProvider := range commandProviders { 338 builtInCommand := builtInProvider.GetCommand(a, utils.T) 339 if builtInCommand != nil && cmd.Trigger == builtInCommand.Trigger { 340 return nil, model.NewAppError("CreateCommand", "api.command.duplicate_trigger.app_error", nil, "", http.StatusBadRequest) 341 } 342 } 343 } 344 345 if result := <-a.Srv.Store.Command().Save(cmd); result.Err != nil { 346 return nil, result.Err 347 } else { 348 return result.Data.(*model.Command), nil 349 } 350 } 351 352 func (a *App) GetCommand(commandId string) (*model.Command, *model.AppError) { 353 if !*a.Config().ServiceSettings.EnableCommands { 354 return nil, model.NewAppError("GetCommand", "api.command.disabled.app_error", nil, "", http.StatusNotImplemented) 355 } 356 357 if result := <-a.Srv.Store.Command().Get(commandId); result.Err != nil { 358 result.Err.StatusCode = http.StatusNotFound 359 return nil, result.Err 360 } else { 361 return result.Data.(*model.Command), nil 362 } 363 } 364 365 func (a *App) UpdateCommand(oldCmd, updatedCmd *model.Command) (*model.Command, *model.AppError) { 366 if !*a.Config().ServiceSettings.EnableCommands { 367 return nil, model.NewAppError("UpdateCommand", "api.command.disabled.app_error", nil, "", http.StatusNotImplemented) 368 } 369 370 updatedCmd.Trigger = strings.ToLower(updatedCmd.Trigger) 371 updatedCmd.Id = oldCmd.Id 372 updatedCmd.Token = oldCmd.Token 373 updatedCmd.CreateAt = oldCmd.CreateAt 374 updatedCmd.UpdateAt = model.GetMillis() 375 updatedCmd.DeleteAt = oldCmd.DeleteAt 376 updatedCmd.CreatorId = oldCmd.CreatorId 377 updatedCmd.TeamId = oldCmd.TeamId 378 379 if result := <-a.Srv.Store.Command().Update(updatedCmd); result.Err != nil { 380 return nil, result.Err 381 } else { 382 return result.Data.(*model.Command), nil 383 } 384 } 385 386 func (a *App) MoveCommand(team *model.Team, command *model.Command) *model.AppError { 387 command.TeamId = team.Id 388 389 if result := <-a.Srv.Store.Command().Update(command); result.Err != nil { 390 return result.Err 391 } 392 393 return nil 394 } 395 396 func (a *App) RegenCommandToken(cmd *model.Command) (*model.Command, *model.AppError) { 397 if !*a.Config().ServiceSettings.EnableCommands { 398 return nil, model.NewAppError("RegenCommandToken", "api.command.disabled.app_error", nil, "", http.StatusNotImplemented) 399 } 400 401 cmd.Token = model.NewId() 402 403 if result := <-a.Srv.Store.Command().Update(cmd); result.Err != nil { 404 return nil, result.Err 405 } else { 406 return result.Data.(*model.Command), nil 407 } 408 } 409 410 func (a *App) DeleteCommand(commandId string) *model.AppError { 411 if !*a.Config().ServiceSettings.EnableCommands { 412 return model.NewAppError("DeleteCommand", "api.command.disabled.app_error", nil, "", http.StatusNotImplemented) 413 } 414 415 if err := (<-a.Srv.Store.Command().Delete(commandId, model.GetMillis())).Err; err != nil { 416 return err 417 } 418 419 return nil 420 }