summaryrefslogtreecommitdiffstats
path: root/app
diff options
context:
space:
mode:
authorJoram Wilander <jwawilander@gmail.com>2017-03-13 09:23:16 -0400
committerChristopher Speller <crspeller@gmail.com>2017-03-13 09:23:16 -0400
commite9c6cc269b5c9fe82e5f38d63344a07365bccd6b (patch)
tree711fefd3511dbd5a7f1a20225f00b766eb4808f7 /app
parent8b0eedbbcd47ba09142c72a71969840aa6e121d2 (diff)
downloadchat-e9c6cc269b5c9fe82e5f38d63344a07365bccd6b.tar.gz
chat-e9c6cc269b5c9fe82e5f38d63344a07365bccd6b.tar.bz2
chat-e9c6cc269b5c9fe82e5f38d63344a07365bccd6b.zip
Move command logic into app layer (#5617)
Diffstat (limited to 'app')
-rw-r--r--app/auto_channels.go74
-rw-r--r--app/auto_constants.go36
-rw-r--r--app/auto_environment.go99
-rw-r--r--app/auto_posts.go103
-rw-r--r--app/auto_teams.go81
-rw-r--r--app/auto_users.go109
-rw-r--r--app/command.go304
-rw-r--r--app/command_away.go43
-rw-r--r--app/command_echo.go97
-rw-r--r--app/command_expand_collapse.go87
-rw-r--r--app/command_invite_people.go64
-rw-r--r--app/command_join.go62
-rw-r--r--app/command_loadtest.go437
-rw-r--r--app/command_logout.go48
-rw-r--r--app/command_me.go38
-rw-r--r--app/command_msg.go110
-rw-r--r--app/command_offline.go43
-rw-r--r--app/command_online.go43
-rw-r--r--app/command_shortcuts.go95
-rw-r--r--app/command_shrug.go43
20 files changed, 2016 insertions, 0 deletions
diff --git a/app/auto_channels.go b/app/auto_channels.go
new file mode 100644
index 000000000..3945a5a4f
--- /dev/null
+++ b/app/auto_channels.go
@@ -0,0 +1,74 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package app
+
+import (
+ "github.com/mattermost/platform/model"
+ "github.com/mattermost/platform/utils"
+)
+
+type AutoChannelCreator struct {
+ client *model.Client
+ team *model.Team
+ Fuzzy bool
+ DisplayNameLen utils.Range
+ DisplayNameCharset string
+ NameLen utils.Range
+ NameCharset string
+ ChannelType string
+}
+
+func NewAutoChannelCreator(client *model.Client, team *model.Team) *AutoChannelCreator {
+ return &AutoChannelCreator{
+ client: client,
+ team: team,
+ Fuzzy: false,
+ DisplayNameLen: CHANNEL_DISPLAY_NAME_LEN,
+ DisplayNameCharset: utils.ALPHANUMERIC,
+ NameLen: CHANNEL_NAME_LEN,
+ NameCharset: utils.LOWERCASE,
+ ChannelType: CHANNEL_TYPE,
+ }
+}
+
+func (cfg *AutoChannelCreator) createRandomChannel() (*model.Channel, bool) {
+ var displayName string
+ if cfg.Fuzzy {
+ displayName = utils.FuzzName()
+ } else {
+ displayName = utils.RandomName(cfg.NameLen, cfg.NameCharset)
+ }
+ name := utils.RandomName(cfg.NameLen, cfg.NameCharset)
+
+ channel := &model.Channel{
+ TeamId: cfg.team.Id,
+ DisplayName: displayName,
+ Name: name,
+ Type: cfg.ChannelType}
+
+ println(cfg.client.GetTeamRoute())
+ result, err := cfg.client.CreateChannel(channel)
+ if err != nil {
+ err.Translate(utils.T)
+ println(err.Error())
+ println(err.DetailedError)
+ return nil, false
+ }
+ return result.Data.(*model.Channel), true
+}
+
+func (cfg *AutoChannelCreator) CreateTestChannels(num utils.Range) ([]*model.Channel, bool) {
+ numChannels := utils.RandIntFromRange(num)
+ channels := make([]*model.Channel, numChannels)
+
+ for i := 0; i < numChannels; i++ {
+ var err bool
+ channels[i], err = cfg.createRandomChannel()
+ if err != true {
+ return channels, false
+ }
+ }
+
+ return channels, true
+}
diff --git a/app/auto_constants.go b/app/auto_constants.go
new file mode 100644
index 000000000..c8c903e32
--- /dev/null
+++ b/app/auto_constants.go
@@ -0,0 +1,36 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package app
+
+import (
+ "github.com/mattermost/platform/model"
+ "github.com/mattermost/platform/utils"
+)
+
+const (
+ USER_PASSWORD = "passwd"
+ CHANNEL_TYPE = model.CHANNEL_OPEN
+ FUZZ_USER_EMAIL_PREFIX_LEN = 10
+ BTEST_TEAM_DISPLAY_NAME = "TestTeam"
+ BTEST_TEAM_NAME = "z-z-testdomaina"
+ BTEST_TEAM_EMAIL = "test@nowhere.com"
+ BTEST_TEAM_TYPE = model.TEAM_OPEN
+ BTEST_USER_NAME = "Mr. Testing Tester"
+ BTEST_USER_EMAIL = "success+ttester@simulator.amazonses.com"
+ BTEST_USER_PASSWORD = "passwd"
+)
+
+var (
+ TEAM_NAME_LEN = utils.Range{Begin: 10, End: 20}
+ TEAM_DOMAIN_NAME_LEN = utils.Range{Begin: 10, End: 20}
+ TEAM_EMAIL_LEN = utils.Range{Begin: 15, End: 30}
+ USER_NAME_LEN = utils.Range{Begin: 5, End: 20}
+ USER_EMAIL_LEN = utils.Range{Begin: 15, End: 30}
+ CHANNEL_DISPLAY_NAME_LEN = utils.Range{Begin: 10, End: 20}
+ CHANNEL_NAME_LEN = utils.Range{Begin: 5, End: 20}
+ POST_MESSAGE_LEN = utils.Range{Begin: 100, End: 400}
+ POST_HASHTAGS_NUM = utils.Range{Begin: 5, End: 10}
+ POST_MENTIONS_NUM = utils.Range{Begin: 0, End: 3}
+ TEST_IMAGE_FILENAMES = []string{"test.png", "testjpg.jpg", "testgif.gif"}
+)
diff --git a/app/auto_environment.go b/app/auto_environment.go
new file mode 100644
index 000000000..b0a4f54b8
--- /dev/null
+++ b/app/auto_environment.go
@@ -0,0 +1,99 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package app
+
+import (
+ "github.com/mattermost/platform/model"
+ "github.com/mattermost/platform/utils"
+ "math/rand"
+ "time"
+)
+
+type TestEnvironment struct {
+ Teams []*model.Team
+ Environments []TeamEnvironment
+}
+
+func CreateTestEnvironmentWithTeams(client *model.Client, rangeTeams utils.Range, rangeChannels utils.Range, rangeUsers utils.Range, rangePosts utils.Range, fuzzy bool) (TestEnvironment, bool) {
+ rand.Seed(time.Now().UTC().UnixNano())
+
+ teamCreator := NewAutoTeamCreator(client)
+ teamCreator.Fuzzy = fuzzy
+ teams, err := teamCreator.CreateTestTeams(rangeTeams)
+ if err != true {
+ return TestEnvironment{}, false
+ }
+
+ environment := TestEnvironment{teams, make([]TeamEnvironment, len(teams))}
+
+ for i, team := range teams {
+ userCreator := NewAutoUserCreator(client, team)
+ userCreator.Fuzzy = fuzzy
+ randomUser, err := userCreator.createRandomUser()
+ if err != true {
+ return TestEnvironment{}, false
+ }
+ client.LoginById(randomUser.Id, USER_PASSWORD)
+ client.SetTeamId(team.Id)
+ teamEnvironment, err := CreateTestEnvironmentInTeam(client, team, rangeChannels, rangeUsers, rangePosts, fuzzy)
+ if err != true {
+ return TestEnvironment{}, false
+ }
+ environment.Environments[i] = teamEnvironment
+ }
+
+ return environment, true
+}
+
+func CreateTestEnvironmentInTeam(client *model.Client, team *model.Team, rangeChannels utils.Range, rangeUsers utils.Range, rangePosts utils.Range, fuzzy bool) (TeamEnvironment, bool) {
+ rand.Seed(time.Now().UTC().UnixNano())
+
+ // We need to create at least one user
+ if rangeUsers.Begin <= 0 {
+ rangeUsers.Begin = 1
+ }
+
+ userCreator := NewAutoUserCreator(client, team)
+ userCreator.Fuzzy = fuzzy
+ users, err := userCreator.CreateTestUsers(rangeUsers)
+ if err != true {
+ return TeamEnvironment{}, false
+ }
+ usernames := make([]string, len(users))
+ for i, user := range users {
+ usernames[i] = user.Username
+ }
+
+ channelCreator := NewAutoChannelCreator(client, team)
+ channelCreator.Fuzzy = fuzzy
+ channels, err := channelCreator.CreateTestChannels(rangeChannels)
+
+ // Have every user join every channel
+ for _, user := range users {
+ for _, channel := range channels {
+ client.LoginById(user.Id, USER_PASSWORD)
+ client.JoinChannel(channel.Id)
+ }
+ }
+
+ if err != true {
+ return TeamEnvironment{}, false
+ }
+
+ numPosts := utils.RandIntFromRange(rangePosts)
+ numImages := utils.RandIntFromRange(rangePosts) / 4
+ for j := 0; j < numPosts; j++ {
+ user := users[utils.RandIntFromRange(utils.Range{Begin: 0, End: len(users) - 1})]
+ client.LoginById(user.Id, USER_PASSWORD)
+ for i, channel := range channels {
+ postCreator := NewAutoPostCreator(client, channel.Id)
+ postCreator.HasImage = i < numImages
+ postCreator.Users = usernames
+ postCreator.Fuzzy = fuzzy
+ postCreator.CreateRandomPost()
+ }
+ }
+
+ return TeamEnvironment{users, channels}, true
+}
diff --git a/app/auto_posts.go b/app/auto_posts.go
new file mode 100644
index 000000000..b32407539
--- /dev/null
+++ b/app/auto_posts.go
@@ -0,0 +1,103 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package app
+
+import (
+ "bytes"
+ "github.com/mattermost/platform/model"
+ "github.com/mattermost/platform/utils"
+ "io"
+ "os"
+)
+
+type AutoPostCreator struct {
+ client *model.Client
+ channelid string
+ Fuzzy bool
+ TextLength utils.Range
+ HasImage bool
+ ImageFilenames []string
+ Users []string
+ Mentions utils.Range
+ Tags utils.Range
+}
+
+// Automatic poster used for testing
+func NewAutoPostCreator(client *model.Client, channelid string) *AutoPostCreator {
+ return &AutoPostCreator{
+ client: client,
+ channelid: channelid,
+ Fuzzy: false,
+ TextLength: utils.Range{Begin: 100, End: 200},
+ HasImage: false,
+ ImageFilenames: TEST_IMAGE_FILENAMES,
+ Users: []string{},
+ Mentions: utils.Range{Begin: 0, End: 5},
+ Tags: utils.Range{Begin: 0, End: 7},
+ }
+}
+
+func (cfg *AutoPostCreator) UploadTestFile() ([]string, bool) {
+ filename := cfg.ImageFilenames[utils.RandIntFromRange(utils.Range{Begin: 0, End: len(cfg.ImageFilenames) - 1})]
+
+ path := utils.FindDir("web/static/images")
+ file, err := os.Open(path + "/" + filename)
+ defer file.Close()
+
+ data := &bytes.Buffer{}
+ _, err = io.Copy(data, file)
+ if err != nil {
+ return nil, false
+ }
+
+ resp, appErr := cfg.client.UploadPostAttachment(data.Bytes(), cfg.channelid, filename)
+ if appErr != nil {
+ return nil, false
+ }
+
+ return []string{resp.FileInfos[0].Id}, true
+}
+
+func (cfg *AutoPostCreator) CreateRandomPost() (*model.Post, bool) {
+ var fileIds []string
+ if cfg.HasImage {
+ var err1 bool
+ fileIds, err1 = cfg.UploadTestFile()
+ if err1 == false {
+ return nil, false
+ }
+ }
+
+ var postText string
+ if cfg.Fuzzy {
+ postText = utils.FuzzPost()
+ } else {
+ postText = utils.RandomText(cfg.TextLength, cfg.Tags, cfg.Mentions, cfg.Users)
+ }
+
+ post := &model.Post{
+ ChannelId: cfg.channelid,
+ Message: postText,
+ FileIds: fileIds}
+ result, err2 := cfg.client.CreatePost(post)
+ if err2 != nil {
+ return nil, false
+ }
+ return result.Data.(*model.Post), true
+}
+
+func (cfg *AutoPostCreator) CreateTestPosts(rangePosts utils.Range) ([]*model.Post, bool) {
+ numPosts := utils.RandIntFromRange(rangePosts)
+ posts := make([]*model.Post, numPosts)
+
+ for i := 0; i < numPosts; i++ {
+ var err bool
+ posts[i], err = cfg.CreateRandomPost()
+ if err != true {
+ return posts, false
+ }
+ }
+
+ return posts, true
+}
diff --git a/app/auto_teams.go b/app/auto_teams.go
new file mode 100644
index 000000000..6e66f4446
--- /dev/null
+++ b/app/auto_teams.go
@@ -0,0 +1,81 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package app
+
+import (
+ "github.com/mattermost/platform/model"
+ "github.com/mattermost/platform/utils"
+)
+
+type TeamEnvironment struct {
+ Users []*model.User
+ Channels []*model.Channel
+}
+
+type AutoTeamCreator struct {
+ client *model.Client
+ Fuzzy bool
+ NameLength utils.Range
+ NameCharset string
+ DomainLength utils.Range
+ DomainCharset string
+ EmailLength utils.Range
+ EmailCharset string
+}
+
+func NewAutoTeamCreator(client *model.Client) *AutoTeamCreator {
+ return &AutoTeamCreator{
+ client: client,
+ Fuzzy: false,
+ NameLength: TEAM_NAME_LEN,
+ NameCharset: utils.LOWERCASE,
+ DomainLength: TEAM_DOMAIN_NAME_LEN,
+ DomainCharset: utils.LOWERCASE,
+ EmailLength: TEAM_EMAIL_LEN,
+ EmailCharset: utils.LOWERCASE,
+ }
+}
+
+func (cfg *AutoTeamCreator) createRandomTeam() (*model.Team, bool) {
+ var teamEmail string
+ var teamDisplayName string
+ var teamName string
+ if cfg.Fuzzy {
+ teamEmail = "success+" + model.NewId() + "simulator.amazonses.com"
+ teamDisplayName = utils.FuzzName()
+ teamName = utils.FuzzName()
+ } else {
+ teamEmail = "success+" + model.NewId() + "simulator.amazonses.com"
+ teamDisplayName = utils.RandomName(cfg.NameLength, cfg.NameCharset)
+ teamName = utils.RandomName(cfg.NameLength, cfg.NameCharset) + model.NewId()
+ }
+ team := &model.Team{
+ DisplayName: teamDisplayName,
+ Name: teamName,
+ Email: teamEmail,
+ Type: model.TEAM_OPEN,
+ }
+
+ result, err := cfg.client.CreateTeam(team)
+ if err != nil {
+ return nil, false
+ }
+ createdTeam := result.Data.(*model.Team)
+ return createdTeam, true
+}
+
+func (cfg *AutoTeamCreator) CreateTestTeams(num utils.Range) ([]*model.Team, bool) {
+ numTeams := utils.RandIntFromRange(num)
+ teams := make([]*model.Team, numTeams)
+
+ for i := 0; i < numTeams; i++ {
+ var err bool
+ teams[i], err = cfg.createRandomTeam()
+ if err != true {
+ return teams, false
+ }
+ }
+
+ return teams, true
+}
diff --git a/app/auto_users.go b/app/auto_users.go
new file mode 100644
index 000000000..7a99cc90b
--- /dev/null
+++ b/app/auto_users.go
@@ -0,0 +1,109 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package app
+
+import (
+ "github.com/mattermost/platform/model"
+ "github.com/mattermost/platform/store"
+ "github.com/mattermost/platform/utils"
+
+ l4g "github.com/alecthomas/log4go"
+)
+
+type AutoUserCreator struct {
+ client *model.Client
+ team *model.Team
+ EmailLength utils.Range
+ EmailCharset string
+ NameLength utils.Range
+ NameCharset string
+ Fuzzy bool
+}
+
+func NewAutoUserCreator(client *model.Client, team *model.Team) *AutoUserCreator {
+ return &AutoUserCreator{
+ client: client,
+ team: team,
+ EmailLength: USER_EMAIL_LEN,
+ EmailCharset: utils.LOWERCASE,
+ NameLength: USER_NAME_LEN,
+ NameCharset: utils.LOWERCASE,
+ Fuzzy: false,
+ }
+}
+
+// Basic test team and user so you always know one
+func CreateBasicUser(client *model.Client) *model.AppError {
+ result, _ := client.FindTeamByName(BTEST_TEAM_NAME)
+ if result.Data.(bool) == false {
+ newteam := &model.Team{DisplayName: BTEST_TEAM_DISPLAY_NAME, Name: BTEST_TEAM_NAME, Email: BTEST_TEAM_EMAIL, Type: BTEST_TEAM_TYPE}
+ result, err := client.CreateTeam(newteam)
+ if err != nil {
+ return err
+ }
+ basicteam := result.Data.(*model.Team)
+ newuser := &model.User{Email: BTEST_USER_EMAIL, Nickname: BTEST_USER_NAME, Password: BTEST_USER_PASSWORD}
+ result, err = client.CreateUser(newuser, "")
+ if err != nil {
+ return err
+ }
+ ruser := result.Data.(*model.User)
+ store.Must(Srv.Store.User().VerifyEmail(ruser.Id))
+ store.Must(Srv.Store.Team().SaveMember(&model.TeamMember{TeamId: basicteam.Id, UserId: ruser.Id}))
+ }
+ return nil
+}
+
+func (cfg *AutoUserCreator) createRandomUser() (*model.User, bool) {
+ var userEmail string
+ var userName string
+ if cfg.Fuzzy {
+ userEmail = "success+" + model.NewId() + "simulator.amazonses.com"
+ userName = utils.FuzzName()
+ } else {
+ userEmail = "success+" + model.NewId() + "simulator.amazonses.com"
+ userName = utils.RandomName(cfg.NameLength, cfg.NameCharset)
+ }
+
+ user := &model.User{
+ Email: userEmail,
+ Nickname: userName,
+ Password: USER_PASSWORD}
+
+ result, err := cfg.client.CreateUserWithInvite(user, "", "", cfg.team.InviteId)
+ if err != nil {
+ err.Translate(utils.T)
+ l4g.Error(err.Error())
+ return nil, false
+ }
+
+ ruser := result.Data.(*model.User)
+
+ status := &model.Status{UserId: ruser.Id, Status: model.STATUS_ONLINE, Manual: false, LastActivityAt: model.GetMillis(), ActiveChannel: ""}
+ if result := <-Srv.Store.Status().SaveOrUpdate(status); result.Err != nil {
+ result.Err.Translate(utils.T)
+ l4g.Error(result.Err.Error())
+ return nil, false
+ }
+
+ // We need to cheat to verify the user's email
+ store.Must(Srv.Store.User().VerifyEmail(ruser.Id))
+
+ return result.Data.(*model.User), true
+}
+
+func (cfg *AutoUserCreator) CreateTestUsers(num utils.Range) ([]*model.User, bool) {
+ numUsers := utils.RandIntFromRange(num)
+ users := make([]*model.User, numUsers)
+
+ for i := 0; i < numUsers; i++ {
+ var err bool
+ users[i], err = cfg.createRandomUser()
+ if err != true {
+ return users, false
+ }
+ }
+
+ return users, true
+}
diff --git a/app/command.go b/app/command.go
index 2d5861206..491813efe 100644
--- a/app/command.go
+++ b/app/command.go
@@ -4,9 +4,40 @@
package app
import (
+ "crypto/tls"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "net/url"
+ "strings"
+
+ l4g "github.com/alecthomas/log4go"
"github.com/mattermost/platform/model"
+ "github.com/mattermost/platform/utils"
+ goi18n "github.com/nicksnyder/go-i18n/i18n"
)
+type CommandProvider interface {
+ GetTrigger() string
+ GetCommand(T goi18n.TranslateFunc) *model.Command
+ DoCommand(args *model.CommandArgs, message string) *model.CommandResponse
+}
+
+var commandProviders = make(map[string]CommandProvider)
+
+func RegisterCommandProvider(newProvider CommandProvider) {
+ commandProviders[newProvider.GetTrigger()] = newProvider
+}
+
+func GetCommandProvider(name string) CommandProvider {
+ provider, ok := commandProviders[name]
+ if ok {
+ return provider
+ }
+
+ return nil
+}
+
func CreateCommandPost(post *model.Post, teamId string, response *model.CommandResponse) (*model.Post, *model.AppError) {
post.Message = parseSlackLinksToMarkdown(response.Text)
post.CreateAt = model.GetMillis()
@@ -29,3 +60,276 @@ func CreateCommandPost(post *model.Post, teamId string, response *model.CommandR
return post, nil
}
+
+func ListCommands(teamId string, T goi18n.TranslateFunc) ([]*model.Command, *model.AppError) {
+ commands := make([]*model.Command, 0, 32)
+ seen := make(map[string]bool)
+ for _, value := range commandProviders {
+ cpy := *value.GetCommand(T)
+ if cpy.AutoComplete && !seen[cpy.Id] {
+ cpy.Sanitize()
+ seen[cpy.Trigger] = true
+ commands = append(commands, &cpy)
+ }
+ }
+
+ if *utils.Cfg.ServiceSettings.EnableCommands {
+ if result := <-Srv.Store.Command().GetByTeam(teamId); result.Err != nil {
+ return nil, result.Err
+ } else {
+ teamCmds := result.Data.([]*model.Command)
+ for _, cmd := range teamCmds {
+ if cmd.AutoComplete && !seen[cmd.Id] {
+ cmd.Sanitize()
+ seen[cmd.Trigger] = true
+ commands = append(commands, cmd)
+ }
+ }
+ }
+ }
+
+ return commands, nil
+}
+
+func ListTeamCommands(teamId string) ([]*model.Command, *model.AppError) {
+ if !*utils.Cfg.ServiceSettings.EnableCommands {
+ return nil, model.NewAppError("ListTeamCommands", "api.command.disabled.app_error", nil, "", http.StatusNotImplemented)
+ }
+
+ if result := <-Srv.Store.Command().GetByTeam(teamId); result.Err != nil {
+ return nil, result.Err
+ } else {
+ return result.Data.([]*model.Command), nil
+ }
+}
+
+func ExecuteCommand(args *model.CommandArgs) (*model.CommandResponse, *model.AppError) {
+ parts := strings.Split(args.Command, " ")
+ trigger := parts[0][1:]
+ trigger = strings.ToLower(trigger)
+ message := strings.Join(parts[1:], " ")
+ provider := GetCommandProvider(trigger)
+
+ if provider != nil {
+ response := provider.DoCommand(args, message)
+ return HandleCommandResponse(provider.GetCommand(args.T), args, response, true)
+ } else {
+ if !*utils.Cfg.ServiceSettings.EnableCommands {
+ return nil, model.NewAppError("ExecuteCommand", "api.command.disabled.app_error", nil, "", http.StatusNotImplemented)
+ }
+
+ chanChan := Srv.Store.Channel().Get(args.ChannelId, true)
+ teamChan := Srv.Store.Team().Get(args.TeamId)
+ userChan := Srv.Store.User().Get(args.UserId)
+
+ if result := <-Srv.Store.Command().GetByTeam(args.TeamId); result.Err != nil {
+ return nil, result.Err
+ } else {
+
+ var team *model.Team
+ if tr := <-teamChan; tr.Err != nil {
+ return nil, tr.Err
+ } else {
+ team = tr.Data.(*model.Team)
+ }
+
+ var user *model.User
+ if ur := <-userChan; ur.Err != nil {
+ return nil, ur.Err
+ } else {
+ user = ur.Data.(*model.User)
+ }
+
+ var channel *model.Channel
+ if cr := <-chanChan; cr.Err != nil {
+ return nil, cr.Err
+ } else {
+ channel = cr.Data.(*model.Channel)
+ }
+
+ teamCmds := result.Data.([]*model.Command)
+ for _, cmd := range teamCmds {
+ if trigger == cmd.Trigger {
+ l4g.Debug(fmt.Sprintf(utils.T("api.command.execute_command.debug"), trigger, args.UserId))
+
+ p := url.Values{}
+ p.Set("token", cmd.Token)
+
+ p.Set("team_id", cmd.TeamId)
+ p.Set("team_domain", team.Name)
+
+ p.Set("channel_id", args.ChannelId)
+ p.Set("channel_name", channel.Name)
+
+ p.Set("user_id", args.UserId)
+ p.Set("user_name", user.Username)
+
+ p.Set("command", "/"+trigger)
+ p.Set("text", message)
+ p.Set("response_url", "not supported yet")
+
+ method := "POST"
+ if cmd.Method == model.COMMAND_METHOD_GET {
+ method = "GET"
+ }
+
+ tr := &http.Transport{
+ TLSClientConfig: &tls.Config{InsecureSkipVerify: *utils.Cfg.ServiceSettings.EnableInsecureOutgoingConnections},
+ }
+ client := &http.Client{Transport: tr}
+
+ req, _ := http.NewRequest(method, cmd.URL, strings.NewReader(p.Encode()))
+ req.Header.Set("Accept", "application/json")
+ if cmd.Method == model.COMMAND_METHOD_POST {
+ req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
+ }
+
+ if resp, err := client.Do(req); err != nil {
+ return nil, model.NewAppError("command", "api.command.execute_command.failed.app_error", map[string]interface{}{"Trigger": trigger}, err.Error(), http.StatusInternalServerError)
+ } else {
+ if resp.StatusCode == http.StatusOK {
+ response := model.CommandResponseFromJson(resp.Body)
+ if response == nil {
+ return nil, model.NewAppError("command", "api.command.execute_command.failed_empty.app_error", map[string]interface{}{"Trigger": trigger}, "", http.StatusInternalServerError)
+ } else {
+ return HandleCommandResponse(cmd, args, response, false)
+ }
+ } else {
+ defer resp.Body.Close()
+ body, _ := ioutil.ReadAll(resp.Body)
+ return nil, model.NewAppError("command", "api.command.execute_command.failed_resp.app_error", map[string]interface{}{"Trigger": trigger, "Status": resp.Status}, string(body), http.StatusInternalServerError)
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return nil, model.NewAppError("command", "api.command.execute_command.not_found.app_error", map[string]interface{}{"Trigger": trigger}, "", http.StatusNotFound)
+}
+
+func HandleCommandResponse(command *model.Command, args *model.CommandArgs, response *model.CommandResponse, builtIn bool) (*model.CommandResponse, *model.AppError) {
+ post := &model.Post{}
+ post.ChannelId = args.ChannelId
+ post.RootId = args.RootId
+ post.ParentId = args.ParentId
+ post.UserId = args.UserId
+
+ if !builtIn {
+ post.AddProp("from_webhook", "true")
+ }
+
+ if utils.Cfg.ServiceSettings.EnablePostUsernameOverride {
+ if len(command.Username) != 0 {
+ post.AddProp("override_username", command.Username)
+ } else if len(response.Username) != 0 {
+ post.AddProp("override_username", response.Username)
+ }
+ }
+
+ if utils.Cfg.ServiceSettings.EnablePostIconOverride {
+ if len(command.IconURL) != 0 {
+ post.AddProp("override_icon_url", command.IconURL)
+ } else if len(response.IconURL) != 0 {
+ post.AddProp("override_icon_url", response.IconURL)
+ } else {
+ post.AddProp("override_icon_url", "")
+ }
+ }
+
+ if _, err := CreateCommandPost(post, args.TeamId, response); err != nil {
+ l4g.Error(err.Error())
+ }
+
+ return response, nil
+}
+
+func CreateCommand(cmd *model.Command) (*model.Command, *model.AppError) {
+ if !*utils.Cfg.ServiceSettings.EnableCommands {
+ return nil, model.NewAppError("CreateCommand", "api.command.disabled.app_error", nil, "", http.StatusNotImplemented)
+ }
+
+ cmd.Trigger = strings.ToLower(cmd.Trigger)
+
+ if result := <-Srv.Store.Command().GetByTeam(cmd.TeamId); result.Err != nil {
+ return nil, result.Err
+ } else {
+ teamCmds := result.Data.([]*model.Command)
+ for _, existingCommand := range teamCmds {
+ if cmd.Trigger == existingCommand.Trigger {
+ return nil, model.NewAppError("CreateCommand", "api.command.duplicate_trigger.app_error", nil, "", http.StatusBadRequest)
+ }
+ }
+ for _, builtInProvider := range commandProviders {
+ builtInCommand := *builtInProvider.GetCommand(utils.T)
+ if cmd.Trigger == builtInCommand.Trigger {
+ return nil, model.NewAppError("CreateCommand", "api.command.duplicate_trigger.app_error", nil, "", http.StatusBadRequest)
+ }
+ }
+ }
+
+ if result := <-Srv.Store.Command().Save(cmd); result.Err != nil {
+ return nil, result.Err
+ } else {
+ return result.Data.(*model.Command), nil
+ }
+}
+
+func GetCommand(commandId string) (*model.Command, *model.AppError) {
+ if !*utils.Cfg.ServiceSettings.EnableCommands {
+ return nil, model.NewAppError("GetCommand", "api.command.disabled.app_error", nil, "", http.StatusNotImplemented)
+ }
+
+ if result := <-Srv.Store.Command().Get(commandId); result.Err != nil {
+ return nil, result.Err
+ } else {
+ return result.Data.(*model.Command), nil
+ }
+}
+
+func UpdateCommand(oldCmd, updatedCmd *model.Command) (*model.Command, *model.AppError) {
+ if !*utils.Cfg.ServiceSettings.EnableCommands {
+ return nil, model.NewAppError("UpdateCommand", "api.command.disabled.app_error", nil, "", http.StatusNotImplemented)
+ }
+
+ updatedCmd.Trigger = strings.ToLower(updatedCmd.Trigger)
+ updatedCmd.Id = oldCmd.Id
+ updatedCmd.Token = oldCmd.Token
+ updatedCmd.CreateAt = oldCmd.CreateAt
+ updatedCmd.UpdateAt = model.GetMillis()
+ updatedCmd.DeleteAt = oldCmd.DeleteAt
+ updatedCmd.CreatorId = oldCmd.CreatorId
+ updatedCmd.TeamId = oldCmd.TeamId
+
+ if result := <-Srv.Store.Command().Update(updatedCmd); result.Err != nil {
+ return nil, result.Err
+ } else {
+ return result.Data.(*model.Command), nil
+ }
+}
+
+func RegenCommandToken(cmd *model.Command) (*model.Command, *model.AppError) {
+ if !*utils.Cfg.ServiceSettings.EnableCommands {
+ return nil, model.NewAppError("RegenCommandToken", "api.command.disabled.app_error", nil, "", http.StatusNotImplemented)
+ }
+
+ cmd.Token = model.NewId()
+
+ if result := <-Srv.Store.Command().Update(cmd); result.Err != nil {
+ return nil, result.Err
+ } else {
+ return result.Data.(*model.Command), nil
+ }
+}
+
+func DeleteCommand(commandId string) *model.AppError {
+ if !*utils.Cfg.ServiceSettings.EnableCommands {
+ return model.NewAppError("DeleteCommand", "api.command.disabled.app_error", nil, "", http.StatusNotImplemented)
+ }
+
+ if err := (<-Srv.Store.Command().Delete(commandId, model.GetMillis())).Err; err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/app/command_away.go b/app/command_away.go
new file mode 100644
index 000000000..55553fa3f
--- /dev/null
+++ b/app/command_away.go
@@ -0,0 +1,43 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package app
+
+import (
+ "github.com/mattermost/platform/model"
+ goi18n "github.com/nicksnyder/go-i18n/i18n"
+)
+
+type AwayProvider struct {
+}
+
+const (
+ CMD_AWAY = "away"
+)
+
+func init() {
+ RegisterCommandProvider(&AwayProvider{})
+}
+
+func (me *AwayProvider) GetTrigger() string {
+ return CMD_AWAY
+}
+
+func (me *AwayProvider) GetCommand(T goi18n.TranslateFunc) *model.Command {
+ return &model.Command{
+ Trigger: CMD_AWAY,
+ AutoComplete: true,
+ AutoCompleteDesc: T("api.command_away.desc"),
+ DisplayName: T("api.command_away.name"),
+ }
+}
+
+func (me *AwayProvider) DoCommand(args *model.CommandArgs, message string) *model.CommandResponse {
+ rmsg := args.T("api.command_away.success")
+ if len(message) > 0 {
+ rmsg = message + " " + rmsg
+ }
+ SetStatusAwayIfNeeded(args.UserId, true)
+
+ return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: rmsg}
+}
diff --git a/app/command_echo.go b/app/command_echo.go
new file mode 100644
index 000000000..40d70e54a
--- /dev/null
+++ b/app/command_echo.go
@@ -0,0 +1,97 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package app
+
+import (
+ "strconv"
+ "strings"
+ "time"
+
+ l4g "github.com/alecthomas/log4go"
+ "github.com/mattermost/platform/model"
+ goi18n "github.com/nicksnyder/go-i18n/i18n"
+)
+
+var echoSem chan bool
+
+type EchoProvider struct {
+}
+
+const (
+ CMD_ECHO = "echo"
+)
+
+func init() {
+ RegisterCommandProvider(&EchoProvider{})
+}
+
+func (me *EchoProvider) GetTrigger() string {
+ return CMD_ECHO
+}
+
+func (me *EchoProvider) GetCommand(T goi18n.TranslateFunc) *model.Command {
+ return &model.Command{
+ Trigger: CMD_ECHO,
+ AutoComplete: true,
+ AutoCompleteDesc: T("api.command_echo.desc"),
+ AutoCompleteHint: T("api.command_echo.hint"),
+ DisplayName: T("api.command_echo.name"),
+ }
+}
+
+func (me *EchoProvider) DoCommand(args *model.CommandArgs, message string) *model.CommandResponse {
+ if len(message) == 0 {
+ return &model.CommandResponse{Text: args.T("api.command_echo.message.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
+ }
+
+ maxThreads := 100
+
+ delay := 0
+ if endMsg := strings.LastIndex(message, "\""); string(message[0]) == "\"" && endMsg > 1 {
+ if checkDelay, err := strconv.Atoi(strings.Trim(message[endMsg:], " \"")); err == nil {
+ delay = checkDelay
+ }
+ message = message[1:endMsg]
+ } else if strings.Index(message, " ") > -1 {
+ delayIdx := strings.LastIndex(message, " ")
+ delayStr := strings.Trim(message[delayIdx:], " ")
+
+ if checkDelay, err := strconv.Atoi(delayStr); err == nil {
+ delay = checkDelay
+ message = message[:delayIdx]
+ }
+ }
+
+ if delay > 10000 {
+ return &model.CommandResponse{Text: args.T("api.command_echo.delay.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
+ }
+
+ if echoSem == nil {
+ // We want one additional thread allowed so we never reach channel lockup
+ echoSem = make(chan bool, maxThreads+1)
+ }
+
+ if len(echoSem) >= maxThreads {
+ return &model.CommandResponse{Text: args.T("api.command_echo.high_volume.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
+ }
+
+ echoSem <- true
+ go func() {
+ defer func() { <-echoSem }()
+ post := &model.Post{}
+ post.ChannelId = args.ChannelId
+ post.RootId = args.RootId
+ post.ParentId = args.ParentId
+ post.Message = message
+ post.UserId = args.UserId
+
+ time.Sleep(time.Duration(delay) * time.Second)
+
+ if _, err := CreatePost(post, args.TeamId, true); err != nil {
+ l4g.Error(args.T("api.command_echo.create.app_error"), err)
+ }
+ }()
+
+ return &model.CommandResponse{}
+}
diff --git a/app/command_expand_collapse.go b/app/command_expand_collapse.go
new file mode 100644
index 000000000..a4a152c60
--- /dev/null
+++ b/app/command_expand_collapse.go
@@ -0,0 +1,87 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package app
+
+import (
+ "strconv"
+
+ "github.com/mattermost/platform/model"
+ goi18n "github.com/nicksnyder/go-i18n/i18n"
+)
+
+type ExpandProvider struct {
+}
+
+type CollapseProvider struct {
+}
+
+const (
+ CMD_EXPAND = "expand"
+ CMD_COLLAPSE = "collapse"
+)
+
+func init() {
+ RegisterCommandProvider(&ExpandProvider{})
+ RegisterCommandProvider(&CollapseProvider{})
+}
+
+func (me *ExpandProvider) GetTrigger() string {
+ return CMD_EXPAND
+}
+
+func (me *CollapseProvider) GetTrigger() string {
+ return CMD_COLLAPSE
+}
+
+func (me *ExpandProvider) GetCommand(T goi18n.TranslateFunc) *model.Command {
+ return &model.Command{
+ Trigger: CMD_EXPAND,
+ AutoComplete: true,
+ AutoCompleteDesc: T("api.command_expand.desc"),
+ DisplayName: T("api.command_expand.name"),
+ }
+}
+
+func (me *CollapseProvider) GetCommand(T goi18n.TranslateFunc) *model.Command {
+ return &model.Command{
+ Trigger: CMD_COLLAPSE,
+ AutoComplete: true,
+ AutoCompleteDesc: T("api.command_collapse.desc"),
+ DisplayName: T("api.command_collapse.name"),
+ }
+}
+
+func (me *ExpandProvider) DoCommand(args *model.CommandArgs, message string) *model.CommandResponse {
+ return setCollapsePreference(args, false)
+}
+
+func (me *CollapseProvider) DoCommand(args *model.CommandArgs, message string) *model.CommandResponse {
+ return setCollapsePreference(args, true)
+}
+
+func setCollapsePreference(args *model.CommandArgs, isCollapse bool) *model.CommandResponse {
+ pref := model.Preference{
+ UserId: args.UserId,
+ Category: model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS,
+ Name: model.PREFERENCE_NAME_COLLAPSE_SETTING,
+ Value: strconv.FormatBool(isCollapse),
+ }
+
+ if result := <-Srv.Store.Preference().Save(&model.Preferences{pref}); result.Err != nil {
+ return &model.CommandResponse{Text: args.T("api.command_expand_collapse.fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
+ }
+
+ socketMessage := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_PREFERENCE_CHANGED, "", "", args.UserId, nil)
+ socketMessage.Add("preference", pref.ToJson())
+ go Publish(socketMessage)
+
+ var rmsg string
+
+ if isCollapse {
+ rmsg = args.T("api.command_collapse.success")
+ } else {
+ rmsg = args.T("api.command_expand.success")
+ }
+ return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: rmsg}
+}
diff --git a/app/command_invite_people.go b/app/command_invite_people.go
new file mode 100644
index 000000000..12ef03f45
--- /dev/null
+++ b/app/command_invite_people.go
@@ -0,0 +1,64 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package app
+
+import (
+ "strings"
+
+ l4g "github.com/alecthomas/log4go"
+ "github.com/mattermost/platform/model"
+ "github.com/mattermost/platform/utils"
+ goi18n "github.com/nicksnyder/go-i18n/i18n"
+)
+
+type InvitePeopleProvider struct {
+}
+
+const (
+ CMD_INVITE_PEOPLE = "invite_people"
+)
+
+func init() {
+ RegisterCommandProvider(&InvitePeopleProvider{})
+}
+
+func (me *InvitePeopleProvider) GetTrigger() string {
+ return CMD_INVITE_PEOPLE
+}
+
+func (me *InvitePeopleProvider) GetCommand(T goi18n.TranslateFunc) *model.Command {
+ return &model.Command{
+ Trigger: CMD_INVITE_PEOPLE,
+ AutoComplete: true,
+ AutoCompleteDesc: T("api.command.invite_people.desc"),
+ AutoCompleteHint: T("api.command.invite_people.hint"),
+ DisplayName: T("api.command.invite_people.name"),
+ }
+}
+
+func (me *InvitePeopleProvider) DoCommand(args *model.CommandArgs, message string) *model.CommandResponse {
+ if !utils.Cfg.EmailSettings.SendEmailNotifications {
+ return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: args.T("api.command.invite_people.email_off")}
+ }
+
+ emailList := strings.Fields(message)
+
+ for i := len(emailList) - 1; i >= 0; i-- {
+ emailList[i] = strings.Trim(emailList[i], ",")
+ if !strings.Contains(emailList[i], "@") {
+ emailList = append(emailList[:i], emailList[i+1:]...)
+ }
+ }
+
+ if len(emailList) == 0 {
+ return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: args.T("api.command.invite_people.no_email")}
+ }
+
+ if err := InviteNewUsersToTeam(emailList, args.TeamId, args.UserId, args.SiteURL); err != nil {
+ l4g.Error(err.Error())
+ return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: args.T("api.command.invite_people.fail")}
+ }
+
+ return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: args.T("api.command.invite_people.sent")}
+}
diff --git a/app/command_join.go b/app/command_join.go
new file mode 100644
index 000000000..5b19dd7a0
--- /dev/null
+++ b/app/command_join.go
@@ -0,0 +1,62 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package app
+
+import (
+ "github.com/mattermost/platform/model"
+ goi18n "github.com/nicksnyder/go-i18n/i18n"
+)
+
+type JoinProvider struct {
+}
+
+const (
+ CMD_JOIN = "join"
+)
+
+func init() {
+ RegisterCommandProvider(&JoinProvider{})
+}
+
+func (me *JoinProvider) GetTrigger() string {
+ return CMD_JOIN
+}
+
+func (me *JoinProvider) GetCommand(T goi18n.TranslateFunc) *model.Command {
+ return &model.Command{
+ Trigger: CMD_JOIN,
+ AutoComplete: true,
+ AutoCompleteDesc: T("api.command_join.desc"),
+ AutoCompleteHint: T("api.command_join.hint"),
+ DisplayName: T("api.command_join.name"),
+ }
+}
+
+func (me *JoinProvider) DoCommand(args *model.CommandArgs, message string) *model.CommandResponse {
+ if result := <-Srv.Store.Channel().GetByName(args.TeamId, message, true); result.Err != nil {
+ return &model.CommandResponse{Text: args.T("api.command_join.list.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
+ } else {
+ channel := result.Data.(*model.Channel)
+
+ if channel.Name == message {
+
+ if channel.Type != model.CHANNEL_OPEN {
+ return &model.CommandResponse{Text: args.T("api.command_join.fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
+ }
+
+ if err := JoinChannel(channel, args.UserId); err != nil {
+ return &model.CommandResponse{Text: args.T("api.command_join.fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
+ }
+
+ team, err := GetTeam(channel.TeamId)
+ if err != nil {
+ return &model.CommandResponse{Text: args.T("api.command_join.fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
+ }
+
+ return &model.CommandResponse{GotoLocation: args.SiteURL + "/" + team.Name + "/channels/" + channel.Name, Text: args.T("api.command_join.success"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
+ }
+ }
+
+ return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: args.T("api.command_join.missing.app_error")}
+}
diff --git a/app/command_loadtest.go b/app/command_loadtest.go
new file mode 100644
index 000000000..d3c7474ae
--- /dev/null
+++ b/app/command_loadtest.go
@@ -0,0 +1,437 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package app
+
+import (
+ "io"
+ "net/http"
+ "path"
+ "strconv"
+ "strings"
+
+ l4g "github.com/alecthomas/log4go"
+ "github.com/mattermost/platform/model"
+ "github.com/mattermost/platform/utils"
+ goi18n "github.com/nicksnyder/go-i18n/i18n"
+)
+
+var usage = `Mattermost load testing commands to help configure the system
+
+ COMMANDS:
+
+ Setup - Creates a testing environment in current team.
+ /loadtest setup [teams] [fuzz] <Num Channels> <Num Users> <NumPosts>
+
+ Example:
+ /loadtest setup teams fuzz 10 20 50
+
+ Users - Add a specified number of random users with fuzz text to current team.
+ /loadtest users [fuzz] <Min Users> <Max Users>
+
+ Example:
+ /loadtest users fuzz 5 10
+
+ Channels - Add a specified number of random channels with fuzz text to current team.
+ /loadtest channels [fuzz] <Min Channels> <Max Channels>
+
+ Example:
+ /loadtest channels fuzz 5 10
+
+ Posts - Add some random posts with fuzz text to current channel.
+ /loadtest posts [fuzz] <Min Posts> <Max Posts> <Max Images>
+
+ Example:
+ /loadtest posts fuzz 5 10 3
+
+ Url - Add a post containing the text from a given url to current channel.
+ /loadtest url
+
+ Example:
+ /loadtest http://www.example.com/sample_file.md
+
+ Json - Add a post using the JSON file as payload to the current channel.
+ /loadtest json url
+
+ Example
+ /loadtest json http://www.example.com/sample_body.json
+
+`
+
+const (
+ CMD_LOADTEST = "loadtest"
+)
+
+type LoadTestProvider struct {
+}
+
+func init() {
+ if !utils.Cfg.ServiceSettings.EnableTesting {
+ RegisterCommandProvider(&LoadTestProvider{})
+ }
+}
+
+func (me *LoadTestProvider) GetTrigger() string {
+ return CMD_LOADTEST
+}
+
+func (me *LoadTestProvider) GetCommand(T goi18n.TranslateFunc) *model.Command {
+ return &model.Command{
+ Trigger: CMD_LOADTEST,
+ AutoComplete: false,
+ AutoCompleteDesc: "Debug Load Testing",
+ AutoCompleteHint: "help",
+ DisplayName: "loadtest",
+ }
+}
+
+func (me *LoadTestProvider) DoCommand(args *model.CommandArgs, message string) *model.CommandResponse {
+ //This command is only available when EnableTesting is true
+ if !utils.Cfg.ServiceSettings.EnableTesting {
+ return &model.CommandResponse{}
+ }
+
+ if strings.HasPrefix(message, "setup") {
+ return me.SetupCommand(args, message)
+ }
+
+ if strings.HasPrefix(message, "users") {
+ return me.UsersCommand(args, message)
+ }
+
+ if strings.HasPrefix(message, "channels") {
+ return me.ChannelsCommand(args, message)
+ }
+
+ if strings.HasPrefix(message, "posts") {
+ return me.PostsCommand(args, message)
+ }
+
+ if strings.HasPrefix(message, "url") {
+ return me.UrlCommand(args, message)
+ }
+ if strings.HasPrefix(message, "json") {
+ return me.JsonCommand(args, message)
+ }
+ return me.HelpCommand(args, message)
+}
+
+func (me *LoadTestProvider) HelpCommand(args *model.CommandArgs, message string) *model.CommandResponse {
+ return &model.CommandResponse{Text: usage, ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
+}
+
+func (me *LoadTestProvider) SetupCommand(args *model.CommandArgs, message string) *model.CommandResponse {
+ tokens := strings.Fields(strings.TrimPrefix(message, "setup"))
+ doTeams := contains(tokens, "teams")
+ doFuzz := 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.NewClient(args.SiteURL)
+
+ if doTeams {
+ if err := CreateBasicUser(client); err != nil {
+ return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
+ }
+ client.Login(BTEST_USER_EMAIL, BTEST_USER_PASSWORD)
+ environment, err := CreateTestEnvironmentWithTeams(
+ 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 != true {
+ return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
+ } else {
+ l4g.Info("Testing environment created")
+ for i := 0; i < len(environment.Teams); i++ {
+ l4g.Info("Team Created: " + environment.Teams[i].Name)
+ l4g.Info("\t User to login: " + environment.Environments[i].Users[0].Email + ", " + USER_PASSWORD)
+ }
+ }
+ } else {
+
+ var team *model.Team
+ if tr := <-Srv.Store.Team().Get(args.TeamId); tr.Err != nil {
+ return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
+ } else {
+ team = tr.Data.(*model.Team)
+ }
+
+ client.MockSession(args.Session.Token)
+ client.SetTeamId(args.TeamId)
+ CreateTestEnvironmentInTeam(
+ client,
+ team,
+ utils.Range{Begin: numChannels, End: numChannels},
+ utils.Range{Begin: numUsers, End: numUsers},
+ utils.Range{Begin: numPosts, End: numPosts},
+ doFuzz)
+ }
+
+ return &model.CommandResponse{Text: "Created enviroment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
+}
+
+func (me *LoadTestProvider) UsersCommand(args *model.CommandArgs, message string) *model.CommandResponse {
+ cmd := strings.TrimSpace(strings.TrimPrefix(message, "users"))
+
+ doFuzz := false
+ if strings.Index(cmd, "fuzz") == 0 {
+ doFuzz = true
+ cmd = strings.TrimSpace(strings.TrimPrefix(cmd, "fuzz"))
+ }
+
+ usersr, err := parseRange(cmd, "")
+ if err == false {
+ usersr = utils.Range{Begin: 2, End: 5}
+ }
+
+ var team *model.Team
+ if tr := <-Srv.Store.Team().Get(args.TeamId); tr.Err != nil {
+ return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
+ } else {
+ team = tr.Data.(*model.Team)
+ }
+
+ client := model.NewClient(args.SiteURL)
+ client.SetTeamId(team.Id)
+ userCreator := NewAutoUserCreator(client, team)
+ userCreator.Fuzzy = doFuzz
+ userCreator.CreateTestUsers(usersr)
+
+ return &model.CommandResponse{Text: "Added users", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
+}
+
+func (me *LoadTestProvider) ChannelsCommand(args *model.CommandArgs, message string) *model.CommandResponse {
+ cmd := strings.TrimSpace(strings.TrimPrefix(message, "channels"))
+
+ doFuzz := false
+ if strings.Index(cmd, "fuzz") == 0 {
+ doFuzz = true
+ cmd = strings.TrimSpace(strings.TrimPrefix(cmd, "fuzz"))
+ }
+
+ channelsr, err := parseRange(cmd, "")
+ if err == false {
+ channelsr = utils.Range{Begin: 2, End: 5}
+ }
+
+ var team *model.Team
+ if tr := <-Srv.Store.Team().Get(args.TeamId); tr.Err != nil {
+ return &model.CommandResponse{Text: "Failed to create testing environment", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
+ } else {
+ team = tr.Data.(*model.Team)
+ }
+
+ client := model.NewClient(args.SiteURL)
+ client.SetTeamId(team.Id)
+ client.MockSession(args.Session.Token)
+ channelCreator := NewAutoChannelCreator(client, team)
+ channelCreator.Fuzzy = doFuzz
+ channelCreator.CreateTestChannels(channelsr)
+
+ return &model.CommandResponse{Text: "Added channels", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
+}
+
+func (me *LoadTestProvider) PostsCommand(args *model.CommandArgs, message string) *model.CommandResponse {
+ cmd := strings.TrimSpace(strings.TrimPrefix(message, "posts"))
+
+ doFuzz := false
+ if strings.Index(cmd, "fuzz") == 0 {
+ doFuzz = true
+ cmd = strings.TrimSpace(strings.TrimPrefix(cmd, "fuzz"))
+ }
+
+ postsr, err := parseRange(cmd, "")
+ if err == false {
+ postsr = utils.Range{Begin: 20, End: 30}
+ }
+
+ tokens := strings.Fields(cmd)
+ rimages := utils.Range{Begin: 0, End: 0}
+ if len(tokens) >= 3 {
+ if numImages, err := strconv.Atoi(tokens[2]); err == nil {
+ rimages = utils.Range{Begin: numImages, End: numImages}
+ }
+ }
+
+ var usernames []string
+ if result := <-Srv.Store.User().GetProfiles(args.TeamId, 0, 1000); result.Err == nil {
+ profileUsers := result.Data.([]*model.User)
+ usernames = make([]string, len(profileUsers))
+ i := 0
+ for _, userprof := range profileUsers {
+ usernames[i] = userprof.Username
+ i++
+ }
+ }
+
+ client := model.NewClient(args.SiteURL)
+ client.SetTeamId(args.TeamId)
+ client.MockSession(args.Session.Token)
+ testPoster := NewAutoPostCreator(client, args.ChannelId)
+ testPoster.Fuzzy = doFuzz
+ testPoster.Users = usernames
+
+ numImages := utils.RandIntFromRange(rimages)
+ numPosts := utils.RandIntFromRange(postsr)
+ for i := 0; i < numPosts; i++ {
+ testPoster.HasImage = (i < numImages)
+ testPoster.CreateRandomPost()
+ }
+
+ return &model.CommandResponse{Text: "Added posts", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
+}
+
+func (me *LoadTestProvider) UrlCommand(args *model.CommandArgs, message string) *model.CommandResponse {
+ url := strings.TrimSpace(strings.TrimPrefix(message, "url"))
+ if len(url) == 0 {
+ return &model.CommandResponse{Text: "Command must contain a url", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
+ }
+
+ // provide a shortcut to easily access tests stored in doc/developer/tests
+ if !strings.HasPrefix(url, "http") {
+ url = "https://raw.githubusercontent.com/mattermost/platform/master/tests/" + url
+
+ if path.Ext(url) == "" {
+ url += ".md"
+ }
+ }
+
+ var contents io.ReadCloser
+ if r, err := http.Get(url); err != nil {
+ return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
+ } else if r.StatusCode > 400 {
+ return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
+ } else {
+ contents = r.Body
+ }
+
+ bytes := make([]byte, 4000)
+
+ // break contents into 4000 byte posts
+ for {
+ length, err := contents.Read(bytes)
+ if err != nil && err != io.EOF {
+ return &model.CommandResponse{Text: "Encountered error reading file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
+ }
+
+ if length == 0 {
+ break
+ }
+
+ post := &model.Post{}
+ post.Message = string(bytes[:length])
+ post.ChannelId = args.ChannelId
+ post.UserId = args.UserId
+
+ if _, err := CreatePost(post, args.TeamId, false); err != nil {
+ return &model.CommandResponse{Text: "Unable to create post", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
+ }
+ }
+
+ return &model.CommandResponse{Text: "Loaded data", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
+}
+
+func (me *LoadTestProvider) JsonCommand(args *model.CommandArgs, message string) *model.CommandResponse {
+ url := strings.TrimSpace(strings.TrimPrefix(message, "json"))
+ if len(url) == 0 {
+ return &model.CommandResponse{Text: "Command must contain a url", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
+ }
+
+ // provide a shortcut to easily access tests stored in doc/developer/tests
+ if !strings.HasPrefix(url, "http") {
+ url = "https://raw.githubusercontent.com/mattermost/platform/master/tests/" + url
+
+ if path.Ext(url) == "" {
+ url += ".json"
+ }
+ }
+
+ var contents io.ReadCloser
+ if r, err := http.Get(url); err != nil {
+ return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
+ } else if r.StatusCode > 400 {
+ return &model.CommandResponse{Text: "Unable to get file", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
+ } else {
+ contents = r.Body
+ }
+
+ post := model.PostFromJson(contents)
+ post.ChannelId = args.ChannelId
+ post.UserId = args.UserId
+ if post.Message == "" {
+ post.Message = message
+ }
+
+ if _, err := CreatePost(post, args.TeamId, false); err != nil {
+ return &model.CommandResponse{Text: "Unable to create post", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
+ }
+ return &model.CommandResponse{Text: "Loaded data", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
+}
+
+func parseRange(command string, cmd string) (utils.Range, bool) {
+ tokens := strings.Fields(strings.TrimPrefix(command, cmd))
+ var begin int
+ var end int
+ var err1 error
+ var err2 error
+ switch {
+ case len(tokens) == 1:
+ begin, err1 = strconv.Atoi(tokens[0])
+ end = begin
+ if err1 != nil {
+ return utils.Range{Begin: 0, End: 0}, false
+ }
+ case len(tokens) >= 2:
+ begin, err1 = strconv.Atoi(tokens[0])
+ end, err2 = strconv.Atoi(tokens[1])
+ if err1 != nil || err2 != nil {
+ return utils.Range{Begin: 0, End: 0}, false
+ }
+ default:
+ return utils.Range{Begin: 0, End: 0}, false
+ }
+ return utils.Range{Begin: begin, End: end}, true
+}
+
+func contains(items []string, token string) bool {
+ for _, elem := range items {
+ if elem == token {
+ return true
+ }
+ }
+ return false
+}
diff --git a/app/command_logout.go b/app/command_logout.go
new file mode 100644
index 000000000..1a353056e
--- /dev/null
+++ b/app/command_logout.go
@@ -0,0 +1,48 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package app
+
+import (
+ "github.com/mattermost/platform/model"
+ goi18n "github.com/nicksnyder/go-i18n/i18n"
+)
+
+type LogoutProvider struct {
+}
+
+const (
+ CMD_LOGOUT = "logout"
+)
+
+func init() {
+ RegisterCommandProvider(&LogoutProvider{})
+}
+
+func (me *LogoutProvider) GetTrigger() string {
+ return CMD_LOGOUT
+}
+
+func (me *LogoutProvider) GetCommand(T goi18n.TranslateFunc) *model.Command {
+ return &model.Command{
+ Trigger: CMD_LOGOUT,
+ AutoComplete: true,
+ AutoCompleteDesc: T("api.command_logout.desc"),
+ AutoCompleteHint: "",
+ DisplayName: T("api.command_logout.name"),
+ }
+}
+
+func (me *LogoutProvider) DoCommand(args *model.CommandArgs, message string) *model.CommandResponse {
+ FAIL := &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: args.T("api.command_logout.fail_message")}
+ SUCCESS := &model.CommandResponse{GotoLocation: "/login"}
+
+ // We can't actually remove the user's cookie from here so we just dump their session and let the browser figure it out
+ if args.Session.Id != "" {
+ if err := RevokeSessionById(args.Session.Id); err != nil {
+ return FAIL
+ }
+ return SUCCESS
+ }
+ return FAIL
+}
diff --git a/app/command_me.go b/app/command_me.go
new file mode 100644
index 000000000..bb29ec1e0
--- /dev/null
+++ b/app/command_me.go
@@ -0,0 +1,38 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package app
+
+import (
+ "github.com/mattermost/platform/model"
+ goi18n "github.com/nicksnyder/go-i18n/i18n"
+)
+
+type MeProvider struct {
+}
+
+const (
+ CMD_ME = "me"
+)
+
+func init() {
+ RegisterCommandProvider(&MeProvider{})
+}
+
+func (me *MeProvider) GetTrigger() string {
+ return CMD_ME
+}
+
+func (me *MeProvider) GetCommand(T goi18n.TranslateFunc) *model.Command {
+ return &model.Command{
+ Trigger: CMD_ME,
+ AutoComplete: true,
+ AutoCompleteDesc: T("api.command_me.desc"),
+ AutoCompleteHint: T("api.command_me.hint"),
+ DisplayName: T("api.command_me.name"),
+ }
+}
+
+func (me *MeProvider) DoCommand(args *model.CommandArgs, message string) *model.CommandResponse {
+ return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_IN_CHANNEL, Text: "*" + message + "*"}
+}
diff --git a/app/command_msg.go b/app/command_msg.go
new file mode 100644
index 000000000..fd4ace61a
--- /dev/null
+++ b/app/command_msg.go
@@ -0,0 +1,110 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package app
+
+import (
+ "strings"
+
+ l4g "github.com/alecthomas/log4go"
+ "github.com/mattermost/platform/model"
+ goi18n "github.com/nicksnyder/go-i18n/i18n"
+)
+
+type msgProvider struct {
+}
+
+const (
+ CMD_MSG = "msg"
+)
+
+func init() {
+ RegisterCommandProvider(&msgProvider{})
+}
+
+func (me *msgProvider) GetTrigger() string {
+ return CMD_MSG
+}
+
+func (me *msgProvider) GetCommand(T goi18n.TranslateFunc) *model.Command {
+ return &model.Command{
+ Trigger: CMD_MSG,
+ AutoComplete: true,
+ AutoCompleteDesc: T("api.command_msg.desc"),
+ AutoCompleteHint: T("api.command_msg.hint"),
+ DisplayName: T("api.command_msg.name"),
+ }
+}
+
+func (me *msgProvider) DoCommand(args *model.CommandArgs, message string) *model.CommandResponse {
+
+ splitMessage := strings.SplitN(message, " ", 2)
+
+ parsedMessage := ""
+ targetUsername := ""
+ teamId := ""
+
+ if len(splitMessage) > 1 {
+ parsedMessage = strings.SplitN(message, " ", 2)[1]
+ }
+ targetUsername = strings.SplitN(message, " ", 2)[0]
+ targetUsername = strings.TrimPrefix(targetUsername, "@")
+
+ var userProfile *model.User
+ if result := <-Srv.Store.User().GetByUsername(targetUsername); result.Err != nil {
+ l4g.Error(result.Err.Error())
+ return &model.CommandResponse{Text: args.T("api.command_msg.missing.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
+ } else {
+ userProfile = result.Data.(*model.User)
+ }
+
+ if userProfile.Id == args.UserId {
+ return &model.CommandResponse{Text: args.T("api.command_msg.missing.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
+ }
+
+ // Find the channel based on this user
+ channelName := model.GetDMNameFromIds(args.UserId, userProfile.Id)
+
+ targetChannelId := ""
+ if channel := <-Srv.Store.Channel().GetByName(args.TeamId, channelName, true); channel.Err != nil {
+ if channel.Err.Id == "store.sql_channel.get_by_name.missing.app_error" {
+ if directChannel, err := CreateDirectChannel(args.UserId, userProfile.Id); err != nil {
+ l4g.Error(err.Error())
+ return &model.CommandResponse{Text: args.T("api.command_msg.dm_fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
+ } else {
+ targetChannelId = directChannel.Id
+ }
+ } else {
+ l4g.Error(channel.Err.Error())
+ return &model.CommandResponse{Text: args.T("api.command_msg.dm_fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
+ }
+ } else {
+ channel := channel.Data.(*model.Channel)
+ targetChannelId = channel.Id
+ teamId = channel.TeamId
+ }
+
+ if len(parsedMessage) > 0 {
+ post := &model.Post{}
+ post.Message = parsedMessage
+ post.ChannelId = targetChannelId
+ post.UserId = args.UserId
+ if _, err := CreatePost(post, args.TeamId, true); err != nil {
+ return &model.CommandResponse{Text: args.T("api.command_msg.fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
+ }
+ }
+
+ if teamId == "" {
+ if len(args.Session.TeamMembers) == 0 {
+ return &model.CommandResponse{Text: args.T("api.command_msg.fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
+ }
+ teamId = args.Session.TeamMembers[0].TeamId
+ }
+
+ team, err := GetTeam(teamId)
+ if err != nil {
+ return &model.CommandResponse{Text: args.T("api.command_msg.fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
+ }
+
+ return &model.CommandResponse{GotoLocation: args.SiteURL + "/" + team.Name + "/channels/" + channelName, Text: "", ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
+}
diff --git a/app/command_offline.go b/app/command_offline.go
new file mode 100644
index 000000000..6e2c125f8
--- /dev/null
+++ b/app/command_offline.go
@@ -0,0 +1,43 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package app
+
+import (
+ "github.com/mattermost/platform/model"
+ goi18n "github.com/nicksnyder/go-i18n/i18n"
+)
+
+type OfflineProvider struct {
+}
+
+const (
+ CMD_OFFLINE = "offline"
+)
+
+func init() {
+ RegisterCommandProvider(&OfflineProvider{})
+}
+
+func (me *OfflineProvider) GetTrigger() string {
+ return CMD_OFFLINE
+}
+
+func (me *OfflineProvider) GetCommand(T goi18n.TranslateFunc) *model.Command {
+ return &model.Command{
+ Trigger: CMD_OFFLINE,
+ AutoComplete: true,
+ AutoCompleteDesc: T("api.command_offline.desc"),
+ DisplayName: T("api.command_offline.name"),
+ }
+}
+
+func (me *OfflineProvider) DoCommand(args *model.CommandArgs, message string) *model.CommandResponse {
+ rmsg := args.T("api.command_offline.success")
+ if len(message) > 0 {
+ rmsg = message + " " + rmsg
+ }
+ SetStatusOffline(args.UserId, true)
+
+ return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: rmsg}
+}
diff --git a/app/command_online.go b/app/command_online.go
new file mode 100644
index 000000000..bd6fbab60
--- /dev/null
+++ b/app/command_online.go
@@ -0,0 +1,43 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package app
+
+import (
+ "github.com/mattermost/platform/model"
+ goi18n "github.com/nicksnyder/go-i18n/i18n"
+)
+
+type OnlineProvider struct {
+}
+
+const (
+ CMD_ONLINE = "online"
+)
+
+func init() {
+ RegisterCommandProvider(&OnlineProvider{})
+}
+
+func (me *OnlineProvider) GetTrigger() string {
+ return CMD_ONLINE
+}
+
+func (me *OnlineProvider) GetCommand(T goi18n.TranslateFunc) *model.Command {
+ return &model.Command{
+ Trigger: CMD_ONLINE,
+ AutoComplete: true,
+ AutoCompleteDesc: T("api.command_online.desc"),
+ DisplayName: T("api.command_online.name"),
+ }
+}
+
+func (me *OnlineProvider) DoCommand(args *model.CommandArgs, message string) *model.CommandResponse {
+ rmsg := args.T("api.command_online.success")
+ if len(message) > 0 {
+ rmsg = message + " " + rmsg
+ }
+ SetStatusOnline(args.UserId, args.Session.Id, true)
+
+ return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: rmsg}
+}
diff --git a/app/command_shortcuts.go b/app/command_shortcuts.go
new file mode 100644
index 000000000..93e5f0f51
--- /dev/null
+++ b/app/command_shortcuts.go
@@ -0,0 +1,95 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package app
+
+import (
+ "bytes"
+ "strings"
+
+ "github.com/mattermost/platform/model"
+ goi18n "github.com/nicksnyder/go-i18n/i18n"
+)
+
+type ShortcutsProvider struct {
+}
+
+const (
+ CMD_SHORTCUTS = "shortcuts"
+)
+
+func init() {
+ RegisterCommandProvider(&ShortcutsProvider{})
+}
+
+func (me *ShortcutsProvider) GetTrigger() string {
+ return CMD_SHORTCUTS
+}
+
+func (me *ShortcutsProvider) GetCommand(T goi18n.TranslateFunc) *model.Command {
+ return &model.Command{
+ Trigger: CMD_SHORTCUTS,
+ AutoComplete: true,
+ AutoCompleteDesc: T("api.command_shortcuts.desc"),
+ AutoCompleteHint: "",
+ DisplayName: T("api.command_shortcuts.name"),
+ }
+}
+
+func (me *ShortcutsProvider) DoCommand(args *model.CommandArgs, message string) *model.CommandResponse {
+ shortcutIds := [28]string{
+ "api.command_shortcuts.header",
+ // Nav shortcuts
+ "api.command_shortcuts.nav.header",
+ "api.command_shortcuts.nav.prev",
+ "api.command_shortcuts.nav.next",
+ "api.command_shortcuts.nav.unread_prev",
+ "api.command_shortcuts.nav.unread_next",
+ "api.command_shortcuts.nav.switcher",
+ "api.command_shortcuts.nav.settings",
+ "api.command_shortcuts.nav.recent_mentions",
+ // Files shortcuts
+ "api.command_shortcuts.files.header",
+ "api.command_shortcuts.files.upload",
+ // Msg shortcuts
+ "api.command_shortcuts.msgs.header",
+ "api.command_shortcuts.msgs.mark_as_read",
+ "api.command_shortcuts.msgs.reprint_prev",
+ "api.command_shortcuts.msgs.reprint_next",
+ "api.command_shortcuts.msgs.edit",
+ "api.command_shortcuts.msgs.comp_username",
+ "api.command_shortcuts.msgs.comp_channel",
+ "api.command_shortcuts.msgs.comp_emoji",
+ // Browser shortcuts
+ "api.command_shortcuts.browser.header",
+ "api.command_shortcuts.browser.channel_prev",
+ "api.command_shortcuts.browser.channel_next",
+ "api.command_shortcuts.browser.font_increase",
+ "api.command_shortcuts.browser.font_decrease",
+ "api.command_shortcuts.browser.highlight_prev",
+ "api.command_shortcuts.browser.highlight_next",
+ "api.command_shortcuts.browser.newline",
+ }
+
+ var osDependentWords map[string]interface{}
+ if strings.Contains(message, "mac") {
+ osDependentWords = map[string]interface{}{
+ "CmdOrCtrl": args.T("api.command_shortcuts.cmd"),
+ "ChannelPrevCmd": args.T("api.command_shortcuts.browser.channel_prev.cmd_mac"),
+ "ChannelNextCmd": args.T("api.command_shortcuts.browser.channel_next.cmd_mac"),
+ }
+ } else {
+ osDependentWords = map[string]interface{}{
+ "CmdOrCtrl": args.T("api.command_shortcuts.ctrl"),
+ "ChannelPrevCmd": args.T("api.command_shortcuts.browser.channel_prev.cmd"),
+ "ChannelNextCmd": args.T("api.command_shortcuts.browser.channel_next.cmd"),
+ }
+ }
+
+ var buffer bytes.Buffer
+ for _, element := range shortcutIds {
+ buffer.WriteString(args.T(element, osDependentWords))
+ }
+
+ return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: buffer.String()}
+}
diff --git a/app/command_shrug.go b/app/command_shrug.go
new file mode 100644
index 000000000..12d1039ec
--- /dev/null
+++ b/app/command_shrug.go
@@ -0,0 +1,43 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package app
+
+import (
+ "github.com/mattermost/platform/model"
+ goi18n "github.com/nicksnyder/go-i18n/i18n"
+)
+
+type ShrugProvider struct {
+}
+
+const (
+ CMD_SHRUG = "shrug"
+)
+
+func init() {
+ RegisterCommandProvider(&ShrugProvider{})
+}
+
+func (me *ShrugProvider) GetTrigger() string {
+ return CMD_SHRUG
+}
+
+func (me *ShrugProvider) GetCommand(T goi18n.TranslateFunc) *model.Command {
+ return &model.Command{
+ Trigger: CMD_SHRUG,
+ AutoComplete: true,
+ AutoCompleteDesc: T("api.command_shrug.desc"),
+ AutoCompleteHint: T("api.command_shrug.hint"),
+ DisplayName: T("api.command_shrug.name"),
+ }
+}
+
+func (me *ShrugProvider) DoCommand(args *model.CommandArgs, message string) *model.CommandResponse {
+ rmsg := `¯\\\_(ツ)\_/¯`
+ if len(message) > 0 {
+ rmsg = message + " " + rmsg
+ }
+
+ return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_IN_CHANNEL, Text: rmsg}
+}