// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. package slashcommands import ( "context" "encoding/json" "io" "net/http" "path" "regexp" "slices" "strconv" "strings" "github.com/pkg/errors" "github.com/mattermost/mattermost/server/public/model" "github.com/mattermost/mattermost/server/public/shared/i18n" "github.com/mattermost/mattermost/server/public/shared/mlog" "github.com/mattermost/mattermost/server/public/shared/request" "github.com/mattermost/mattermost/server/v8/channels/app" "github.com/mattermost/mattermost/server/v8/channels/utils" ) var usage = `Mattermost testing commands to help configure the system COMMANDS: Setup - Creates a testing environment in current team. /test setup [teams] [fuzz] Example: /test setup teams fuzz 10 20 50 Users - Add a specified number of random users with fuzz text to current team, at the specified Unix timestamp in milliseconds. /test users [fuzz] [range=min[,max]] [time=user_join_timestamp] Default: range=2,5 time= Examples: /test users fuzz range=3,8 time=1565076128000 /test users range=1 Channels - Add a specified number of random public (o) or private (p) channels with fuzz text to current team, at the specified Unix timestamp in milliseconds. /test channels [fuzz] [range=min[,max]] [type=(o|p)] [time=channel_create_timestamp] Default: range=2,5 type=o time= Examples: /test channels fuzz range=5,10 type=p time=1565076128000 /test channels range=1 DMs - Add a specified number of random DM messages between the current user and a specified user, at the specified Unix timestamp in milliseconds. If a timestamp is provided, posts are created one millisecond apart. Note: You may need to clear your browser cache in order to see these posts in the UI. /test dms u=@username [range=min[,max]] [time=dm_create_timestamp] Default: range=2,5 time= Examples: /test dms u=@user range=5,10 time=1565076128000 /test dms u=@user range=2 ThreadedPost - Create a threaded post with a specified number of replies at the specified Unix timestamp in milliseconds. If a timestamp is provided, posts are created one millisecond apart. Note: You may need to clear your browser cache in order to see these posts in the UI. /test threaded_post [range=min[,max]] [time=post_timestamp] Default: range=1000 time= Examples: /test threaded_post /test threaded_post range=100,200 time=1565076128000 Posts - Add some random posts with fuzz text to current channel, at the specified Unix timestamp in milliseconds. If a timestamp is provided, posts are created one millisecond apart. Note: You may need to clear your browser cache in order to see these posts in the UI. /test posts [fuzz] [range=min[,max]] [images=max_images] [time=post_timestamp] Default: range=2,5 images=0 time= Example: /test posts fuzz range=5,10 images=3 time=1565076128000 /test posts range=2 Post - Add post to a channel as another user. /test post u=@username p=passwd c=~channelname t=teamname "message" Example: /test post u=@user-1 p=user-1 c=~town-square t=ad-1 "message" Url - Add a post containing the text from a given url to current channel. /test url Example: /test http://www.example.com/sample_file.md Json - Add a post using the JSON file as payload to the current channel. /test json url Example: /test json http://www.example.com/sample_body.json ` const ( CmdTest = "test" ) var ( userRE = regexp.MustCompile(`u=@?([^\s]+)`) passwdRE = regexp.MustCompile(`p=([^\s]+)`) teamRE = regexp.MustCompile(`t=([^\s]+)`) channelRE = regexp.MustCompile(`c=~([^\s]+)`) messageRE = regexp.MustCompile(`"(.*)"`) fuzzRE = regexp.MustCompile(`fuzz`) rangeRE = regexp.MustCompile(`range=([^\s]+)`) timeRE = regexp.MustCompile(`time=([^\s]+)`) imagesRE = regexp.MustCompile(`images=([^\s]+)`) typeRE = regexp.MustCompile(`type=([^\s])+`) ) type LoadTestProvider struct { } func init() { app.RegisterCommandProvider(&LoadTestProvider{}) } func (*LoadTestProvider) GetTrigger() string { return CmdTest } func (*LoadTestProvider) GetCommand(a *app.App, T i18n.TranslateFunc) *model.Command { if !*a.Config().ServiceSettings.EnableTesting { return nil } return &model.Command{ Trigger: CmdTest, AutoComplete: false, AutoCompleteDesc: "Debug Load Testing", AutoCompleteHint: "help", DisplayName: "test", } } func (lt *LoadTestProvider) DoCommand(a *app.App, rctx request.CTX, args *model.CommandArgs, message string) *model.CommandResponse { commandResponse, err := lt.doCommand(a, rctx, args, message) if err != nil { rctx.Logger().Error("failed command /"+CmdTest, mlog.Err(err)) } return commandResponse } func (lt *LoadTestProvider) doCommand(a *app.App, rctx request.CTX, args *model.CommandArgs, message string) (*model.CommandResponse, error) { //This command is only available when EnableTesting is true if !*a.Config().ServiceSettings.EnableTesting { return &model.CommandResponse{}, nil } if strings.HasPrefix(message, "setup") { return lt.SetupCommand(a, rctx, args, message) } if strings.HasPrefix(message, "users") { return lt.UsersCommand(a, rctx, args, message) } if strings.HasPrefix(message, "activate_user") { return lt.ActivateUserCommand(a, rctx, args, message) } if strings.HasPrefix(message, "deactivate_user") { return lt.DeActivateUserCommand(a, rctx, args, message) } if strings.HasPrefix(message, "channels") { return lt.ChannelsCommand(a, rctx, args, message) } if strings.HasPrefix(message, "dms") { return lt.DMsCommand(a, rctx, args, message) } if strings.HasPrefix(message, "posts") { return lt.PostsCommand(a, rctx, args, message) } if strings.HasPrefix(message, "post") { return lt.PostCommand(a, rctx, args, message) } if strings.HasPrefix(message, "threaded_post") { return lt.ThreadedPostCommand(a, rctx, args, message) } if strings.HasPrefix(message, "url") { return lt.URLCommand(a, rctx, args, message) } if strings.HasPrefix(message, "json") { return lt.JSONCommand(a, rctx, args, message) } return lt.HelpCommand(args, message), nil } func (*LoadTestProvider) HelpCommand(args *model.CommandArgs, message string) *model.CommandResponse { return &model.CommandResponse{Text: usage, ResponseType: model.CommandResponseTypeEphemeral} } func (*LoadTestProvider) SetupCommand(a *app.App, rctx request.CTX, args *model.CommandArgs, message string) (*model.CommandResponse, error) { tokens := strings.Fields(strings.TrimPrefix(message, "setup")) doTeams := slices.Contains(tokens, "teams") doFuzz := slices.Contains(tokens, "fuzz") numArgs := 0 if doTeams { numArgs++ } if doFuzz { numArgs++ } var numTeams int var numChannels int var numUsers int var numPosts int // Defaults numTeams = 10 numChannels = 10 numUsers = 10 numPosts = 10 if doTeams { if (len(tokens) - numArgs) >= 4 { numTeams, _ = strconv.Atoi(tokens[numArgs+0]) numChannels, _ = strconv.Atoi(tokens[numArgs+1]) numUsers, _ = strconv.Atoi(tokens[numArgs+2]) numPosts, _ = strconv.Atoi(tokens[numArgs+3]) } } else { if (len(tokens) - numArgs) >= 3 { numChannels, _ = strconv.Atoi(tokens[numArgs+0]) numUsers, _ = strconv.Atoi(tokens[numArgs+1]) numPosts, _ = strconv.Atoi(tokens[numArgs+2]) } } client := model.NewAPIv4Client(args.SiteURL) if doTeams { if err := CreateBasicUser(rctx, a, client); err != nil { return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.CommandResponseTypeEphemeral}, err } _, _, err := client.Login(context.Background(), BTestUserEmail, BTestUserPassword) if err != nil { return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.CommandResponseTypeEphemeral}, err } environment, err := CreateTestEnvironmentWithTeams( a, rctx, client, utils.Range{Begin: numTeams, End: numTeams}, utils.Range{Begin: numChannels, End: numChannels}, utils.Range{Begin: numUsers, End: numUsers}, utils.Range{Begin: numPosts, End: numPosts}, doFuzz) if err != nil { return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.CommandResponseTypeEphemeral}, err } rctx.Logger().Info("Testing environment created") for i := 0; i < len(environment.Teams); i++ { rctx.Logger().Info("Team Created: " + environment.Teams[i].Name) rctx.Logger().Info("\t User to login: " + environment.Environments[i].Users[0].Email + ", " + UserPassword) } } else { team, err := a.Srv().Store().Team().Get(args.TeamId) if err != nil { return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.CommandResponseTypeEphemeral}, err } _, err = CreateTestEnvironmentInTeam( a, rctx, client, team, utils.Range{Begin: numChannels, End: numChannels}, utils.Range{Begin: numUsers, End: numUsers}, utils.Range{Begin: numPosts, End: numPosts}, doFuzz) if err != nil { return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.CommandResponseTypeEphemeral}, err } } return &model.CommandResponse{Text: "Created environment", ResponseType: model.CommandResponseTypeEphemeral}, nil } func (*LoadTestProvider) ActivateUserCommand(a *app.App, rctx request.CTX, args *model.CommandArgs, message string) (*model.CommandResponse, error) { userID := strings.TrimSpace(strings.TrimPrefix(message, "activate_user")) if err := a.UpdateUserActive(rctx, userID, true); err != nil { return &model.CommandResponse{Text: "Failed to activate user", ResponseType: model.CommandResponseTypeEphemeral}, err } return &model.CommandResponse{Text: "Activated user", ResponseType: model.CommandResponseTypeEphemeral}, nil } func (*LoadTestProvider) DeActivateUserCommand(a *app.App, rctx request.CTX, args *model.CommandArgs, message string) (*model.CommandResponse, error) { userID := strings.TrimSpace(strings.TrimPrefix(message, "deactivate_user")) if err := a.UpdateUserActive(rctx, userID, false); err != nil { return &model.CommandResponse{Text: "Failed to deactivate user", ResponseType: model.CommandResponseTypeEphemeral}, err } return &model.CommandResponse{Text: "DeActivated user", ResponseType: model.CommandResponseTypeEphemeral}, nil } func (*LoadTestProvider) UsersCommand(a *app.App, rctx request.CTX, args *model.CommandArgs, message string) (*model.CommandResponse, error) { cmd := strings.TrimSpace(strings.TrimPrefix(message, "users")) doFuzz := false if fuzzRE.MatchString(cmd) { doFuzz = true } var err error rng := utils.Range{Begin: 2, End: 5} rangeParam := getMatch(rangeRE, cmd) if rangeParam != "" { rng, err = parseRange(rangeParam) if err != nil { return &model.CommandResponse{Text: "Failed to add users: " + err.Error(), ResponseType: model.CommandResponseTypeEphemeral}, err } } team, err := a.Srv().Store().Team().Get(args.TeamId) if err != nil { return &model.CommandResponse{Text: "Failed to add users", ResponseType: model.CommandResponseTypeEphemeral}, err } time := int64(0) timeParam := getMatch(timeRE, cmd) if timeParam != "" { time, err = strconv.ParseInt(timeParam, 10, 64) if err != nil || time < 0 { return &model.CommandResponse{Text: "Failed to add users: Invalid time parameter", ResponseType: model.CommandResponseTypeEphemeral}, errors.New("Invalid time parameter") } } client := model.NewAPIv4Client(args.SiteURL) userCreator := NewAutoUserCreator(a, client, team) userCreator.Fuzzy = doFuzz userCreator.JoinTime = time if _, err := userCreator.CreateTestUsers(rctx, rng); err != nil { return &model.CommandResponse{Text: "Failed to add users: " + err.Error(), ResponseType: model.CommandResponseTypeEphemeral}, err } return &model.CommandResponse{Text: "Added users", ResponseType: model.CommandResponseTypeEphemeral}, nil } func (*LoadTestProvider) ChannelsCommand(a *app.App, rctx request.CTX, args *model.CommandArgs, message string) (*model.CommandResponse, error) { cmd := strings.TrimSpace(strings.TrimPrefix(message, "channels")) doFuzz := false if fuzzRE.MatchString(cmd) { doFuzz = true } var err error rng := utils.Range{Begin: 2, End: 5} rangeParam := getMatch(rangeRE, cmd) if rangeParam != "" { rng, err = parseRange(rangeParam) if err != nil { return &model.CommandResponse{Text: "Failed to add channels: " + err.Error(), ResponseType: model.CommandResponseTypeEphemeral}, err } } team, err := a.Srv().Store().Team().Get(args.TeamId) if err != nil { return &model.CommandResponse{Text: "Failed to add channels", ResponseType: model.CommandResponseTypeEphemeral}, err } typ := model.ChannelTypeOpen typeParam := getMatch(typeRE, cmd) if typeParam != "" { switch strings.ToUpper(typeParam) { case "O": case "P": typ = model.ChannelTypePrivate default: return &model.CommandResponse{Text: "Failed to add channels: Invalid type parameter", ResponseType: model.CommandResponseTypeEphemeral}, errors.New("Invalid type parameter") } } time := int64(0) timeParam := getMatch(timeRE, cmd) if timeParam != "" { time, err = strconv.ParseInt(timeParam, 10, 64) if err != nil || time < 0 { return &model.CommandResponse{Text: "Failed to add channels: Invalid time parameter", ResponseType: model.CommandResponseTypeEphemeral}, errors.New("Invalid time parameter") } } channelCreator := NewAutoChannelCreator(a, team, args.UserId) channelCreator.Fuzzy = doFuzz channelCreator.CreateTime = time channelCreator.ChannelType = typ if _, err := channelCreator.CreateTestChannels(rctx, rng); err != nil { return &model.CommandResponse{Text: "Failed to create test channels: " + err.Error(), ResponseType: model.CommandResponseTypeEphemeral}, err } return &model.CommandResponse{Text: "Added channels", ResponseType: model.CommandResponseTypeEphemeral}, nil } func (*LoadTestProvider) DMsCommand(a *app.App, rctx request.CTX, args *model.CommandArgs, message string) (*model.CommandResponse, error) { cmd := strings.TrimSpace(strings.TrimPrefix(message, "dms")) var err error username := getMatch(userRE, message) user, appErr := a.GetUserByUsername(username) if appErr != nil { return &model.CommandResponse{Text: "Failed to add DMS: Invalid username", ResponseType: model.CommandResponseTypeEphemeral}, appErr } rng := utils.Range{Begin: 2, End: 5} rangeParam := getMatch(rangeRE, cmd) if rangeParam != "" { rng, err = parseRange(rangeParam) if err != nil { return &model.CommandResponse{Text: "Failed to add DMs: " + err.Error(), ResponseType: model.CommandResponseTypeEphemeral}, err } } time := int64(0) timeParam := getMatch(timeRE, cmd) if timeParam != "" { time, err = strconv.ParseInt(timeParam, 10, 64) if err != nil || time < 0 { return &model.CommandResponse{Text: "Failed to add DMs: Invalid time parameter", ResponseType: model.CommandResponseTypeEphemeral}, errors.New("Invalid time parameter") } } channel, err := a.GetOrCreateDirectChannel(rctx, args.UserId, user.Id) postCreator := NewAutoPostCreator(a, channel.Id, args.UserId) postCreator.CreateTime = time postCreator.UsersToPostFrom = []string{user.Id} numPosts := utils.RandIntFromRange(rng) for range numPosts { if _, err := postCreator.CreateRandomPost(rctx); err != nil { return &model.CommandResponse{Text: "Failed to create test DMs: " + err.Error(), ResponseType: model.CommandResponseTypeEphemeral}, err } } return &model.CommandResponse{Text: "Added DMs", ResponseType: model.CommandResponseTypeEphemeral}, nil } func (*LoadTestProvider) ThreadedPostCommand(a *app.App, rctx request.CTX, args *model.CommandArgs, message string) (*model.CommandResponse, error) { cmd := strings.TrimSpace(strings.TrimPrefix(message, "threaded_post")) var err error rng := utils.Range{Begin: 1000, End: 1000} rangeParam := getMatch(rangeRE, cmd) if rangeParam != "" { rng, err = parseRange(rangeParam) if err != nil { return &model.CommandResponse{Text: "Failed to create post: " + err.Error(), ResponseType: model.CommandResponseTypeEphemeral}, err } } time := int64(0) timeParam := getMatch(timeRE, cmd) if timeParam != "" { time, err = strconv.ParseInt(timeParam, 10, 64) if err != nil || time < 0 { return &model.CommandResponse{Text: "Failed to create post: Invalid time parameter", ResponseType: model.CommandResponseTypeEphemeral}, errors.New("Invalid time parameter") } } var usernames []string var profileUsers []*model.User options := &model.UserGetOptions{InTeamId: args.TeamId, Page: 0, PerPage: 1000} if profileUsers, err = a.Srv().Store().User().GetProfiles(options); err == nil { usernames = make([]string, len(profileUsers)) i := 0 for _, userprof := range profileUsers { usernames[i] = userprof.Username i++ } } testPoster := NewAutoPostCreator(a, args.ChannelId, args.UserId) testPoster.Fuzzy = true testPoster.Users = usernames testPoster.CreateTime = time rpost, err2 := testPoster.CreateRandomPost(rctx) if err2 != nil { return &model.CommandResponse{Text: "Failed to create a post", ResponseType: model.CommandResponseTypeEphemeral}, err2 } numPosts := utils.RandIntFromRange(rng) for range numPosts { _, err = testPoster.CreateRandomPostNested(rctx, rpost.Id) if err != nil { return &model.CommandResponse{Text: "Failed to create nested post", ResponseType: model.CommandResponseTypeEphemeral}, err } } return &model.CommandResponse{Text: "Added threaded post", ResponseType: model.CommandResponseTypeEphemeral}, nil } func (*LoadTestProvider) PostsCommand(a *app.App, rctx request.CTX, args *model.CommandArgs, message string) (*model.CommandResponse, error) { cmd := strings.TrimSpace(strings.TrimPrefix(message, "posts")) doFuzz := false if fuzzRE.MatchString(cmd) { doFuzz = true } var err error rng := utils.Range{Begin: 2, End: 5} rangeParam := getMatch(rangeRE, cmd) if rangeParam != "" { rng, err = parseRange(rangeParam) if err != nil { return &model.CommandResponse{Text: "Failed to add posts: " + err.Error(), ResponseType: model.CommandResponseTypeEphemeral}, err } } maxImages := 0 imagesParam := getMatch(imagesRE, cmd) if imagesParam != "" { maxImages, err = strconv.Atoi(imagesParam) if err != nil { return &model.CommandResponse{Text: "Failed to add posts: Invalid images parameter", ResponseType: model.CommandResponseTypeEphemeral}, errors.New("Invalid images parameter") } } time := int64(0) timeParam := getMatch(timeRE, cmd) if timeParam != "" { time, err = strconv.ParseInt(timeParam, 10, 64) if err != nil || time < 0 { return &model.CommandResponse{Text: "Failed to add posts: Invalid time parameter", ResponseType: model.CommandResponseTypeEphemeral}, errors.New("Invalid time parameter") } } var usernames []string options := &model.UserGetOptions{InTeamId: args.TeamId, Page: 0, PerPage: 1000} if profileUsers, err := a.Srv().Store().User().GetProfiles(options); err == nil { usernames = make([]string, len(profileUsers)) i := 0 for _, userprof := range profileUsers { usernames[i] = userprof.Username i++ } } testPoster := NewAutoPostCreator(a, args.ChannelId, args.UserId) testPoster.Fuzzy = doFuzz testPoster.Users = usernames testPoster.CreateTime = time numImages := utils.RandIntFromRange(utils.Range{Begin: 0, End: maxImages}) numPosts := utils.RandIntFromRange(rng) for i := range numPosts { testPoster.HasImage = (i < numImages) _, err := testPoster.CreateRandomPost(rctx) if err != nil { return &model.CommandResponse{Text: "Failed to add posts", ResponseType: model.CommandResponseTypeEphemeral}, err } } return &model.CommandResponse{Text: "Added posts", ResponseType: model.CommandResponseTypeEphemeral}, nil } func getMatch(re *regexp.Regexp, text string) string { if match := re.FindStringSubmatch(text); match != nil { return match[1] } return "" } func (*LoadTestProvider) PostCommand(a *app.App, rctx request.CTX, args *model.CommandArgs, message string) (*model.CommandResponse, error) { textMessage := getMatch(messageRE, message) if textMessage == "" { return &model.CommandResponse{Text: "No message to post", ResponseType: model.CommandResponseTypeEphemeral}, nil } teamName := getMatch(teamRE, message) team, err := a.GetTeamByName(teamName) if err != nil { return &model.CommandResponse{Text: "Failed to get a team", ResponseType: model.CommandResponseTypeEphemeral}, err } channelName := getMatch(channelRE, message) channel, err := a.GetChannelByName(rctx, channelName, team.Id, true) if err != nil { return &model.CommandResponse{Text: "Failed to get a channel", ResponseType: model.CommandResponseTypeEphemeral}, err } passwd := getMatch(passwdRE, message) username := getMatch(userRE, message) user, err := a.GetUserByUsername(username) if err != nil { return &model.CommandResponse{Text: "Failed to get a user", ResponseType: model.CommandResponseTypeEphemeral}, err } client := model.NewAPIv4Client(args.SiteURL) _, _, nErr := client.LoginById(context.Background(), user.Id, passwd) if nErr != nil { return &model.CommandResponse{Text: "Failed to login a user", ResponseType: model.CommandResponseTypeEphemeral}, nErr } post := &model.Post{ ChannelId: channel.Id, Message: textMessage, } _, _, nErr = client.CreatePost(context.Background(), post) if nErr != nil { return &model.CommandResponse{Text: "Failed to create a post", ResponseType: model.CommandResponseTypeEphemeral}, nErr } return &model.CommandResponse{Text: "Added a post to " + channel.DisplayName, ResponseType: model.CommandResponseTypeEphemeral}, nil } func (*LoadTestProvider) URLCommand(a *app.App, rctx request.CTX, args *model.CommandArgs, message string) (*model.CommandResponse, error) { url := strings.TrimSpace(strings.TrimPrefix(message, "url")) if url == "" { return &model.CommandResponse{Text: "Command must contain a url", ResponseType: model.CommandResponseTypeEphemeral}, nil } // provide a shortcut to easily access tests stored in doc/developer/tests if !strings.HasPrefix(url, "http") { url = "https://raw.githubusercontent.com/mattermost/mattermost-server/master/server/tests/" + url if path.Ext(url) == "" { url += ".md" } } r, err := http.Get(url) if err != nil { return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.CommandResponseTypeEphemeral}, err } defer func() { _, err := io.Copy(io.Discard, r.Body) if err != nil { rctx.Logger().Warn("Error discarding request body", mlog.Err(err)) } r.Body.Close() }() if r.StatusCode > 400 { return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.CommandResponseTypeEphemeral}, errors.Errorf("unexpected status code %d", r.StatusCode) } bytes := make([]byte, 4000) // break contents into 4000 byte posts for { length, err := r.Body.Read(bytes) if err != nil && err != io.EOF { return &model.CommandResponse{Text: "Encountered error reading file", ResponseType: model.CommandResponseTypeEphemeral}, err } if length == 0 { break } post := &model.Post{} post.Message = string(bytes[:length]) post.ChannelId = args.ChannelId post.UserId = args.UserId if _, err := a.CreatePostMissingChannel(rctx, post, false, true); err != nil { return &model.CommandResponse{Text: "Unable to create post", ResponseType: model.CommandResponseTypeEphemeral}, err } } return &model.CommandResponse{Text: "Loaded data", ResponseType: model.CommandResponseTypeEphemeral}, nil } func (*LoadTestProvider) JSONCommand(a *app.App, rctx request.CTX, args *model.CommandArgs, message string) (*model.CommandResponse, error) { url := strings.TrimSpace(strings.TrimPrefix(message, "json")) if url == "" { return &model.CommandResponse{Text: "Command must contain a url", ResponseType: model.CommandResponseTypeEphemeral}, nil } // provide a shortcut to easily access tests stored in doc/developer/tests if !strings.HasPrefix(url, "http") { url = "https://raw.githubusercontent.com/mattermost/mattermost-server/master/server/tests/" + url if path.Ext(url) == "" { url += ".json" } } r, err := http.Get(url) if err != nil { return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.CommandResponseTypeEphemeral}, err } if r.StatusCode > 400 { return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.CommandResponseTypeEphemeral}, errors.Errorf("unexpected status code %d", r.StatusCode) } defer func() { _, err := io.Copy(io.Discard, r.Body) if err != nil { rctx.Logger().Warn("Error discarding request body", mlog.Err(err)) } r.Body.Close() }() var post model.Post if jsonErr := json.NewDecoder(r.Body).Decode(&post); jsonErr != nil { return &model.CommandResponse{Text: "Unable to decode post", ResponseType: model.CommandResponseTypeEphemeral}, errors.Wrapf(jsonErr, "could not decode post from json") } post.ChannelId = args.ChannelId post.UserId = args.UserId if post.Message == "" { post.Message = message } if _, err := a.CreatePostMissingChannel(rctx, &post, false, true); err != nil { return &model.CommandResponse{Text: "Unable to create post", ResponseType: model.CommandResponseTypeEphemeral}, err } return &model.CommandResponse{Text: "Loaded data", ResponseType: model.CommandResponseTypeEphemeral}, nil } func parseRange(rng string) (utils.Range, error) { tokens := strings.Split(rng, ",") var begin int var end int var err1 error var err2 error switch { case len(tokens) == 1: begin, err1 = strconv.Atoi(tokens[0]) if err1 != nil || begin < 0 { return utils.Range{Begin: 0, End: 0}, errors.New("Invalid range parameter") } end = begin case len(tokens) == 2: begin, err1 = strconv.Atoi(tokens[0]) end, err2 = strconv.Atoi(tokens[1]) if err1 != nil || err2 != nil || begin < 0 || end < begin { return utils.Range{Begin: 0, End: 0}, errors.New("Invalid range parameter") } default: return utils.Range{Begin: 0, End: 0}, errors.New("Invalid range parameter") } return utils.Range{Begin: begin, End: end}, nil }