github.com/haalcala/mattermost-server-change-repo@v0.0.0-20210713015153-16753fbeee5f/app/slashcommands/command_loadtest.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See LICENSE.txt for license information.
     3  
     4  package slashcommands
     5  
     6  import (
     7  	"io"
     8  	"io/ioutil"
     9  	"net/http"
    10  	"path"
    11  	"regexp"
    12  	"strconv"
    13  	"strings"
    14  
    15  	goi18n "github.com/mattermost/go-i18n/i18n"
    16  	"github.com/pkg/errors"
    17  
    18  	"github.com/mattermost/mattermost-server/v5/app"
    19  	"github.com/mattermost/mattermost-server/v5/mlog"
    20  	"github.com/mattermost/mattermost-server/v5/model"
    21  	"github.com/mattermost/mattermost-server/v5/utils"
    22  )
    23  
    24  var usage = `Mattermost testing commands to help configure the system
    25  
    26  	COMMANDS:
    27  
    28  	Setup - Creates a testing environment in current team.
    29  		/test setup [teams] [fuzz] <Num Channels> <Num Users> <NumPosts>
    30  
    31  		Example:
    32  		/test setup teams fuzz 10 20 50
    33  
    34  	Users - Add a specified number of random users with fuzz text to current team.
    35  		/test users [fuzz] <Min Users> <Max Users>
    36  
    37  		Example:
    38  			/test users fuzz 5 10
    39  
    40  	Channels - Add a specified number of random channels with fuzz text to current team.
    41  		/test channels [fuzz] <Min Channels> <Max Channels>
    42  
    43  		Example:
    44  			/test channels fuzz 5 10
    45  
    46  	ThreadedPost - create a large threaded post
    47          /test threaded_post
    48  
    49  	Posts - Add some random posts with fuzz text to current channel.
    50  		/test posts [fuzz] <Min Posts> <Max Posts> <Max Images>
    51  
    52  		Example:
    53  			/test posts fuzz 5 10 3
    54  
    55  	Post - Add post to a channel as another user.
    56  		/test post u=@username p=passwd c=~channelname t=teamname "message"
    57  
    58  		Example:
    59  			/test post u=@user-1 p=user-1 c=~town-square t=ad-1 "message"
    60  
    61  	Url - Add a post containing the text from a given url to current channel.
    62  		/test url
    63  
    64  		Example:
    65  			/test http://www.example.com/sample_file.md
    66  
    67  	Json - Add a post using the JSON file as payload to the current channel.
    68  	        /test json url
    69  
    70  		Example
    71  		/test json http://www.example.com/sample_body.json
    72  
    73  `
    74  
    75  const (
    76  	CmdTest = "test"
    77  )
    78  
    79  var (
    80  	userRE    = regexp.MustCompile(`u=@([^\s]+)`)
    81  	passwdRE  = regexp.MustCompile(`p=([^\s]+)`)
    82  	teamRE    = regexp.MustCompile(`t=([^\s]+)`)
    83  	channelRE = regexp.MustCompile(`c=~([^\s]+)`)
    84  	messageRE = regexp.MustCompile(`"(.*)"`)
    85  )
    86  
    87  type LoadTestProvider struct {
    88  }
    89  
    90  func init() {
    91  	app.RegisterCommandProvider(&LoadTestProvider{})
    92  }
    93  
    94  func (*LoadTestProvider) GetTrigger() string {
    95  	return CmdTest
    96  }
    97  
    98  func (*LoadTestProvider) GetCommand(a *app.App, T goi18n.TranslateFunc) *model.Command {
    99  	if !*a.Config().ServiceSettings.EnableTesting {
   100  		return nil
   101  	}
   102  	return &model.Command{
   103  		Trigger:          CmdTest,
   104  		AutoComplete:     false,
   105  		AutoCompleteDesc: "Debug Load Testing",
   106  		AutoCompleteHint: "help",
   107  		DisplayName:      "test",
   108  	}
   109  }
   110  
   111  func (lt *LoadTestProvider) DoCommand(a *app.App, args *model.CommandArgs, message string) *model.CommandResponse {
   112  	commandResponse, err := lt.doCommand(a, args, message)
   113  	if err != nil {
   114  		mlog.Error("failed command /"+CmdTest, mlog.Err(err))
   115  	}
   116  
   117  	return commandResponse
   118  }
   119  
   120  func (lt *LoadTestProvider) doCommand(a *app.App, args *model.CommandArgs, message string) (*model.CommandResponse, error) {
   121  	//This command is only available when EnableTesting is true
   122  	if !*a.Config().ServiceSettings.EnableTesting {
   123  		return &model.CommandResponse{}, nil
   124  	}
   125  
   126  	if strings.HasPrefix(message, "setup") {
   127  		return lt.SetupCommand(a, args, message)
   128  	}
   129  
   130  	if strings.HasPrefix(message, "users") {
   131  		return lt.UsersCommand(a, args, message)
   132  	}
   133  
   134  	if strings.HasPrefix(message, "activate_user") {
   135  		return lt.ActivateUserCommand(a, args, message)
   136  	}
   137  
   138  	if strings.HasPrefix(message, "deactivate_user") {
   139  		return lt.DeActivateUserCommand(a, args, message)
   140  	}
   141  
   142  	if strings.HasPrefix(message, "channels") {
   143  		return lt.ChannelsCommand(a, args, message)
   144  	}
   145  
   146  	if strings.HasPrefix(message, "posts") {
   147  		return lt.PostsCommand(a, args, message)
   148  	}
   149  
   150  	if strings.HasPrefix(message, "post") {
   151  		return lt.PostCommand(a, args, message)
   152  	}
   153  
   154  	if strings.HasPrefix(message, "threaded_post") {
   155  		return lt.ThreadedPostCommand(a, args, message)
   156  	}
   157  
   158  	if strings.HasPrefix(message, "url") {
   159  		return lt.UrlCommand(a, args, message)
   160  	}
   161  
   162  	if strings.HasPrefix(message, "json") {
   163  		return lt.JsonCommand(a, args, message)
   164  	}
   165  
   166  	return lt.HelpCommand(args, message), nil
   167  }
   168  
   169  func (*LoadTestProvider) HelpCommand(args *model.CommandArgs, message string) *model.CommandResponse {
   170  	return &model.CommandResponse{Text: usage, ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
   171  }
   172  
   173  func (*LoadTestProvider) SetupCommand(a *app.App, args *model.CommandArgs, message string) (*model.CommandResponse, error) {
   174  	tokens := strings.Fields(strings.TrimPrefix(message, "setup"))
   175  	doTeams := contains(tokens, "teams")
   176  	doFuzz := contains(tokens, "fuzz")
   177  
   178  	numArgs := 0
   179  	if doTeams {
   180  		numArgs++
   181  	}
   182  	if doFuzz {
   183  		numArgs++
   184  	}
   185  
   186  	var numTeams int
   187  	var numChannels int
   188  	var numUsers int
   189  	var numPosts int
   190  
   191  	// Defaults
   192  	numTeams = 10
   193  	numChannels = 10
   194  	numUsers = 10
   195  	numPosts = 10
   196  
   197  	if doTeams {
   198  		if (len(tokens) - numArgs) >= 4 {
   199  			numTeams, _ = strconv.Atoi(tokens[numArgs+0])
   200  			numChannels, _ = strconv.Atoi(tokens[numArgs+1])
   201  			numUsers, _ = strconv.Atoi(tokens[numArgs+2])
   202  			numPosts, _ = strconv.Atoi(tokens[numArgs+3])
   203  		}
   204  	} else {
   205  		if (len(tokens) - numArgs) >= 3 {
   206  			numChannels, _ = strconv.Atoi(tokens[numArgs+0])
   207  			numUsers, _ = strconv.Atoi(tokens[numArgs+1])
   208  			numPosts, _ = strconv.Atoi(tokens[numArgs+2])
   209  		}
   210  	}
   211  	client := model.NewAPIv4Client(args.SiteURL)
   212  
   213  	if doTeams {
   214  		if err := CreateBasicUser(a, client); err != nil {
   215  			return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   216  		}
   217  		_, resp := client.Login(BTestUserEmail, BTestUserPassword)
   218  		if resp.Error != nil {
   219  			return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, resp.Error
   220  		}
   221  		environment, err := CreateTestEnvironmentWithTeams(
   222  			a,
   223  			client,
   224  			utils.Range{Begin: numTeams, End: numTeams},
   225  			utils.Range{Begin: numChannels, End: numChannels},
   226  			utils.Range{Begin: numUsers, End: numUsers},
   227  			utils.Range{Begin: numPosts, End: numPosts},
   228  			doFuzz)
   229  		if err != nil {
   230  			return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   231  		}
   232  
   233  		mlog.Info("Testing environment created")
   234  		for i := 0; i < len(environment.Teams); i++ {
   235  			mlog.Info("Team Created: " + environment.Teams[i].Name)
   236  			mlog.Info("\t User to login: " + environment.Environments[i].Users[0].Email + ", " + UserPassword)
   237  		}
   238  	} else {
   239  		team, err := a.Srv().Store.Team().Get(args.TeamId)
   240  		if err != nil {
   241  			return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   242  		}
   243  
   244  		CreateTestEnvironmentInTeam(
   245  			a,
   246  			client,
   247  			team,
   248  			utils.Range{Begin: numChannels, End: numChannels},
   249  			utils.Range{Begin: numUsers, End: numUsers},
   250  			utils.Range{Begin: numPosts, End: numPosts},
   251  			doFuzz)
   252  	}
   253  
   254  	return &model.CommandResponse{Text: "Created environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, nil
   255  }
   256  
   257  func (*LoadTestProvider) ActivateUserCommand(a *app.App, args *model.CommandArgs, message string) (*model.CommandResponse, error) {
   258  	user_id := strings.TrimSpace(strings.TrimPrefix(message, "activate_user"))
   259  	if err := a.UpdateUserActive(user_id, true); err != nil {
   260  		return &model.CommandResponse{Text: "Failed to activate user", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   261  	}
   262  
   263  	return &model.CommandResponse{Text: "Activated user", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, nil
   264  }
   265  
   266  func (*LoadTestProvider) DeActivateUserCommand(a *app.App, args *model.CommandArgs, message string) (*model.CommandResponse, error) {
   267  	user_id := strings.TrimSpace(strings.TrimPrefix(message, "deactivate_user"))
   268  	if err := a.UpdateUserActive(user_id, false); err != nil {
   269  		return &model.CommandResponse{Text: "Failed to deactivate user", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   270  	}
   271  
   272  	return &model.CommandResponse{Text: "DeActivated user", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, nil
   273  }
   274  
   275  func (*LoadTestProvider) UsersCommand(a *app.App, args *model.CommandArgs, message string) (*model.CommandResponse, error) {
   276  	cmd := strings.TrimSpace(strings.TrimPrefix(message, "users"))
   277  
   278  	doFuzz := false
   279  	if strings.Index(cmd, "fuzz") == 0 {
   280  		doFuzz = true
   281  		cmd = strings.TrimSpace(strings.TrimPrefix(cmd, "fuzz"))
   282  	}
   283  
   284  	usersr, ok := parseRange(cmd, "")
   285  	if !ok {
   286  		usersr = utils.Range{Begin: 2, End: 5}
   287  	}
   288  
   289  	team, err := a.Srv().Store.Team().Get(args.TeamId)
   290  	if err != nil {
   291  		return &model.CommandResponse{Text: "Failed to add users", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   292  	}
   293  
   294  	client := model.NewAPIv4Client(args.SiteURL)
   295  	userCreator := NewAutoUserCreator(a, client, team)
   296  	userCreator.Fuzzy = doFuzz
   297  	if _, err := userCreator.CreateTestUsers(usersr); err != nil {
   298  		return &model.CommandResponse{Text: "Failed to add users: " + err.Error(), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   299  	}
   300  
   301  	return &model.CommandResponse{Text: "Added users", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, nil
   302  }
   303  
   304  func (*LoadTestProvider) ChannelsCommand(a *app.App, args *model.CommandArgs, message string) (*model.CommandResponse, error) {
   305  	cmd := strings.TrimSpace(strings.TrimPrefix(message, "channels"))
   306  
   307  	doFuzz := false
   308  	if strings.Index(cmd, "fuzz") == 0 {
   309  		doFuzz = true
   310  		cmd = strings.TrimSpace(strings.TrimPrefix(cmd, "fuzz"))
   311  	}
   312  
   313  	channelsr, ok := parseRange(cmd, "")
   314  	if !ok {
   315  		channelsr = utils.Range{Begin: 2, End: 5}
   316  	}
   317  
   318  	team, err := a.Srv().Store.Team().Get(args.TeamId)
   319  	if err != nil {
   320  		return &model.CommandResponse{Text: "Failed to add channels", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   321  	}
   322  
   323  	channelCreator := NewAutoChannelCreator(a, team, args.UserId)
   324  	channelCreator.Fuzzy = doFuzz
   325  	if _, err := channelCreator.CreateTestChannels(channelsr); err != nil {
   326  		return &model.CommandResponse{Text: "Failed to create test channels: " + err.Error(), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   327  	}
   328  
   329  	return &model.CommandResponse{Text: "Added channels", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, nil
   330  }
   331  
   332  func (*LoadTestProvider) ThreadedPostCommand(a *app.App, args *model.CommandArgs, message string) (*model.CommandResponse, error) {
   333  	var usernames []string
   334  	options := &model.UserGetOptions{InTeamId: args.TeamId, Page: 0, PerPage: 1000}
   335  	if profileUsers, err := a.Srv().Store.User().GetProfiles(options); err == nil {
   336  		usernames = make([]string, len(profileUsers))
   337  		i := 0
   338  		for _, userprof := range profileUsers {
   339  			usernames[i] = userprof.Username
   340  			i++
   341  		}
   342  	}
   343  
   344  	testPoster := NewAutoPostCreator(a, args.ChannelId, args.UserId)
   345  	testPoster.Fuzzy = true
   346  	testPoster.Users = usernames
   347  	rpost, err2 := testPoster.CreateRandomPost()
   348  	if err2 != nil {
   349  		return &model.CommandResponse{Text: "Failed to create a post", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err2
   350  	}
   351  	for i := 0; i < 1000; i++ {
   352  		testPoster.CreateRandomPostNested(rpost.Id, rpost.Id)
   353  	}
   354  
   355  	return &model.CommandResponse{Text: "Added threaded post", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, nil
   356  }
   357  
   358  func (*LoadTestProvider) PostsCommand(a *app.App, args *model.CommandArgs, message string) (*model.CommandResponse, error) {
   359  	cmd := strings.TrimSpace(strings.TrimPrefix(message, "posts"))
   360  
   361  	doFuzz := false
   362  	if strings.Index(cmd, "fuzz") == 0 {
   363  		doFuzz = true
   364  		cmd = strings.TrimSpace(strings.TrimPrefix(cmd, "fuzz"))
   365  	}
   366  
   367  	postsr, ok := parseRange(cmd, "")
   368  	if !ok {
   369  		postsr = utils.Range{Begin: 20, End: 30}
   370  	}
   371  
   372  	tokens := strings.Fields(cmd)
   373  	rimages := utils.Range{Begin: 0, End: 0}
   374  	if len(tokens) >= 3 {
   375  		if numImages, err := strconv.Atoi(tokens[2]); err == nil {
   376  			rimages = utils.Range{Begin: numImages, End: numImages}
   377  		}
   378  	}
   379  
   380  	var usernames []string
   381  	options := &model.UserGetOptions{InTeamId: args.TeamId, Page: 0, PerPage: 1000}
   382  	if profileUsers, err := a.Srv().Store.User().GetProfiles(options); err == nil {
   383  		usernames = make([]string, len(profileUsers))
   384  		i := 0
   385  		for _, userprof := range profileUsers {
   386  			usernames[i] = userprof.Username
   387  			i++
   388  		}
   389  	}
   390  
   391  	testPoster := NewAutoPostCreator(a, args.ChannelId, args.UserId)
   392  	testPoster.Fuzzy = doFuzz
   393  	testPoster.Users = usernames
   394  
   395  	numImages := utils.RandIntFromRange(rimages)
   396  	numPosts := utils.RandIntFromRange(postsr)
   397  	for i := 0; i < numPosts; i++ {
   398  		testPoster.HasImage = (i < numImages)
   399  		_, err := testPoster.CreateRandomPost()
   400  		if err != nil {
   401  			return &model.CommandResponse{Text: "Failed to add posts", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   402  		}
   403  
   404  	}
   405  
   406  	return &model.CommandResponse{Text: "Added posts", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, nil
   407  }
   408  
   409  func getMatch(re *regexp.Regexp, text string) string {
   410  	if match := re.FindStringSubmatch(text); match != nil {
   411  		return match[1]
   412  	}
   413  
   414  	return ""
   415  }
   416  
   417  func (*LoadTestProvider) PostCommand(a *app.App, args *model.CommandArgs, message string) (*model.CommandResponse, error) {
   418  	textMessage := getMatch(messageRE, message)
   419  	if textMessage == "" {
   420  		return &model.CommandResponse{Text: "No message to post", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, nil
   421  	}
   422  
   423  	teamName := getMatch(teamRE, message)
   424  	team, err := a.GetTeamByName(teamName)
   425  	if err != nil {
   426  		return &model.CommandResponse{Text: "Failed to get a team", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   427  	}
   428  
   429  	channelName := getMatch(channelRE, message)
   430  	channel, err := a.GetChannelByName(channelName, team.Id, true)
   431  	if err != nil {
   432  		return &model.CommandResponse{Text: "Failed to get a channel", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   433  	}
   434  
   435  	passwd := getMatch(passwdRE, message)
   436  	username := getMatch(userRE, message)
   437  	user, err := a.GetUserByUsername(username)
   438  	if err != nil {
   439  		return &model.CommandResponse{Text: "Failed to get a user", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   440  	}
   441  
   442  	client := model.NewAPIv4Client(args.SiteURL)
   443  	_, resp := client.LoginById(user.Id, passwd)
   444  	if resp.Error != nil {
   445  		return &model.CommandResponse{Text: "Failed to login a user", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, resp.Error
   446  	}
   447  
   448  	post := &model.Post{
   449  		ChannelId: channel.Id,
   450  		Message:   textMessage,
   451  	}
   452  	_, resp = client.CreatePost(post)
   453  	if resp.Error != nil {
   454  		return &model.CommandResponse{Text: "Failed to create a post", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, resp.Error
   455  	}
   456  
   457  	return &model.CommandResponse{Text: "Added a post to " + channel.DisplayName, ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, nil
   458  }
   459  
   460  func (*LoadTestProvider) UrlCommand(a *app.App, args *model.CommandArgs, message string) (*model.CommandResponse, error) {
   461  	url := strings.TrimSpace(strings.TrimPrefix(message, "url"))
   462  	if url == "" {
   463  		return &model.CommandResponse{Text: "Command must contain a url", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, nil
   464  	}
   465  
   466  	// provide a shortcut to easily access tests stored in doc/developer/tests
   467  	if !strings.HasPrefix(url, "http") {
   468  		url = "https://raw.githubusercontent.com/mattermost/mattermost-server/master/tests/" + url
   469  
   470  		if path.Ext(url) == "" {
   471  			url += ".md"
   472  		}
   473  	}
   474  
   475  	r, err := http.Get(url)
   476  	if err != nil {
   477  		return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   478  	}
   479  	defer func() {
   480  		io.Copy(ioutil.Discard, r.Body)
   481  		r.Body.Close()
   482  	}()
   483  
   484  	if r.StatusCode > 400 {
   485  		return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, errors.Errorf("unexpected status code %d", r.StatusCode)
   486  	}
   487  
   488  	bytes := make([]byte, 4000)
   489  
   490  	// break contents into 4000 byte posts
   491  	for {
   492  		length, err := r.Body.Read(bytes)
   493  		if err != nil && err != io.EOF {
   494  			return &model.CommandResponse{Text: "Encountered error reading file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   495  		}
   496  
   497  		if length == 0 {
   498  			break
   499  		}
   500  
   501  		post := &model.Post{}
   502  		post.Message = string(bytes[:length])
   503  		post.ChannelId = args.ChannelId
   504  		post.UserId = args.UserId
   505  
   506  		if _, err := a.CreatePostMissingChannel(post, false); err != nil {
   507  			return &model.CommandResponse{Text: "Unable to create post", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   508  		}
   509  	}
   510  
   511  	return &model.CommandResponse{Text: "Loaded data", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, nil
   512  }
   513  
   514  func (*LoadTestProvider) JsonCommand(a *app.App, args *model.CommandArgs, message string) (*model.CommandResponse, error) {
   515  	url := strings.TrimSpace(strings.TrimPrefix(message, "json"))
   516  	if url == "" {
   517  		return &model.CommandResponse{Text: "Command must contain a url", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, nil
   518  	}
   519  
   520  	// provide a shortcut to easily access tests stored in doc/developer/tests
   521  	if !strings.HasPrefix(url, "http") {
   522  		url = "https://raw.githubusercontent.com/mattermost/mattermost-server/master/tests/" + url
   523  
   524  		if path.Ext(url) == "" {
   525  			url += ".json"
   526  		}
   527  	}
   528  
   529  	r, err := http.Get(url)
   530  	if err != nil {
   531  		return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   532  	}
   533  
   534  	if r.StatusCode > 400 {
   535  		return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, errors.Errorf("unexpected status code %d", r.StatusCode)
   536  	}
   537  	defer func() {
   538  		io.Copy(ioutil.Discard, r.Body)
   539  		r.Body.Close()
   540  	}()
   541  
   542  	post := model.PostFromJson(r.Body)
   543  	post.ChannelId = args.ChannelId
   544  	post.UserId = args.UserId
   545  	if post.Message == "" {
   546  		post.Message = message
   547  	}
   548  
   549  	if _, err := a.CreatePostMissingChannel(post, false); err != nil {
   550  		return &model.CommandResponse{Text: "Unable to create post", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, err
   551  	}
   552  
   553  	return &model.CommandResponse{Text: "Loaded data", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}, nil
   554  }
   555  
   556  func parseRange(command string, cmd string) (utils.Range, bool) {
   557  	tokens := strings.Fields(strings.TrimPrefix(command, cmd))
   558  	var begin int
   559  	var end int
   560  	var err1 error
   561  	var err2 error
   562  	switch {
   563  	case len(tokens) == 1:
   564  		begin, err1 = strconv.Atoi(tokens[0])
   565  		end = begin
   566  		if err1 != nil {
   567  			return utils.Range{Begin: 0, End: 0}, false
   568  		}
   569  	case len(tokens) >= 2:
   570  		begin, err1 = strconv.Atoi(tokens[0])
   571  		end, err2 = strconv.Atoi(tokens[1])
   572  		if err1 != nil || err2 != nil {
   573  			return utils.Range{Begin: 0, End: 0}, false
   574  		}
   575  	default:
   576  		return utils.Range{Begin: 0, End: 0}, false
   577  	}
   578  	return utils.Range{Begin: begin, End: end}, true
   579  }
   580  
   581  func contains(items []string, token string) bool {
   582  	for _, elem := range items {
   583  		if elem == token {
   584  			return true
   585  		}
   586  	}
   587  	return false
   588  }