From d65437d43225d83b832e58224379449f1b832e3e Mon Sep 17 00:00:00 2001 From: Pawel Rozlach Date: Mon, 24 Feb 2020 20:47:09 +0100 Subject: [PATCH 1/6] Implement channel welcome messages --- README.md | 5 ++- server/command.go | 95 ++++++++++++++++++++++++++++++++++++++++++-- server/hooks.go | 68 +++++++++++++++++++++++++++++++ server/plugin.go | 2 + server/team_hooks.go | 21 ---------- 5 files changed, 166 insertions(+), 25 deletions(-) create mode 100644 server/hooks.go delete mode 100644 server/team_hooks.go diff --git a/README.md b/README.md index f239ad3d7..56d38ffd4 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ where - **TeamName**: The team for which the Welcome Bot sends a message for. Must be the team handle used in the URL, in lowercase. For example, in the following URL the **TeamName** value is `my-team`: https://example.com/my-team/channels/my-channel - **DelayInSeconds**: The number of seconds after joining a team that the user receives a welcome message. - **Message**: The message posted to the user. -- (Optional) **AttachmentMessage**: Message text in attachment containing user action buttons. +- (Optional) **AttachmentMessage**: Message text in attachment containing user action buttons. - (Optional) **Actions**: Use this to add new team members to channels automatically or based on which action button they pressed. - **ActionType**: One of `button` or `automatic`. When `button`: enables uses to select which types of channels they want to join. When `automatic`: the user is automatically added to the specified channels. - **ActionDisplayName**: Sets the display name for the user action buttons. @@ -73,6 +73,9 @@ The preview of the configured messages can be done via bot commands: * `/welcomebot help` - show a short usage information * `/welcomebot list` - lists the teams for which greetings were defined * `/welcomebot preview [team-name]` - sends ephemeral messages to the user calling the command, with the preview of the welcome message[s] for the given team name and the user that requested the preview +* `/welcomebot set_channel_welcome` - sets the given text as current's channel welcome message +* `/welcomebot get_channel_welcome` - gets the current's channel welcome message +* `/welcomebot delete_channel_welcome` - deletes the current's channel welcome message ## Example diff --git a/server/command.go b/server/command.go index e72773f0e..d9554417d 100644 --- a/server/command.go +++ b/server/command.go @@ -10,7 +10,11 @@ import ( ) const COMMAND_HELP = `* |/welcomebot preview [team-name] [user-name]| - preview the welcome message for the given team name. The current user's username will be used to render the template. -* |/welcomebot list| - list the teams for which welcome messages were defined` +* |/welcomebot list| - list the teams for which welcome messages were defined +* |/welcomebot set_channel_welcome [welcome-message]| - set the welcome message for the given channel. Direct channels are not supported. +* |/welcomebot get_channel_welcome| - print the welcome message set for the given channel (if any) +* |/welcomebot delete_channel_welcome| - delete the welcome message for the given channel (if any) +` func getCommand() *model.Command { return &model.Command{ @@ -18,7 +22,7 @@ func getCommand() *model.Command { DisplayName: "welcomebot", Description: "Welcome Bot helps add new team members to channels.", AutoComplete: true, - AutoCompleteDesc: "Available commands: preview, help", + AutoCompleteDesc: "Available commands: preview, help, list, set_channel_welcome, get_channel_welcome, delete_channel_welcome", AutoCompleteHint: "[command]", } } @@ -81,7 +85,7 @@ func (p *Plugin) ExecuteCommand(c *plugin.Context, args *model.CommandArgs) (*mo found := false for _, message := range p.getWelcomeMessages() { if message.TeamName == teamName { - if err := p.previewWelcomeMessage(teamName, args, *message); err != nil { + if err = p.previewWelcomeMessage(teamName, args, *message); err != nil { errMsg := fmt.Sprintf("error occured while processing greeting for team `%s`: `%s`", teamName, err) p.postCommandResponse(args, errMsg) return &model.CommandResponse{}, nil @@ -121,6 +125,91 @@ func (p *Plugin) ExecuteCommand(c *plugin.Context, args *model.CommandArgs) (*mo } p.postCommandResponse(args, str.String()) return &model.CommandResponse{}, nil + case "set_channel_welcome": + if len(parameters) == 0 { + p.postCommandResponse(args, "`set_channel_welcome` command requires the message to be provided") + return &model.CommandResponse{}, nil + } + + channelInfo, appErr := p.API.GetChannel(args.ChannelId) + if appErr != nil { + errMsg := fmt.Sprintf( + "error occured while checking the type of the chanelId `%s`: `%s`", + args.ChannelId, err) + p.postCommandResponse(args, errMsg) + return &model.CommandResponse{}, nil + } + + if channelInfo.Type == model.CHANNEL_PRIVATE { + p.postCommandResponse( + args, "welcome messages are not supported for direct channels") + return &model.CommandResponse{}, nil + } + + // strings.Fields will consume ALL whitespace, so plain re-joining of the + // parameters slice will not produce the same message + message := strings.SplitN(args.Command, "set_channel_welcome", 2)[1] + message = strings.TrimSpace(message) + + key := fmt.Sprintf("%s%s", args.ChannelId, WELCOMEBOT_CHANNEL_WELCOME_KEY) + if appErr := p.API.KVSet(key, []byte(message)); appErr != nil { + errMsg := fmt.Sprintf( + "error occured while storing the welcome message for the chanel: `%s`", + appErr) + p.postCommandResponse(args, errMsg) + return &model.CommandResponse{}, nil + } + + p.postCommandResponse(args, fmt.Sprintf("stored the welcome message:\n%s", message)) + return &model.CommandResponse{}, nil + + case "get_channel_welcome": + if len(parameters) > 0 { + p.postCommandResponse(args, "`get_channel_welcome` command does not accept any extra parameters") + return &model.CommandResponse{}, nil + } + + key := fmt.Sprintf("%s%s", args.ChannelId, WELCOMEBOT_CHANNEL_WELCOME_KEY) + if data, appErr := p.API.KVGet(key); appErr != nil { + errMsg := fmt.Sprintf( + "error occured while retrieving the welcome message for the chanel: `%s`", + appErr) + p.postCommandResponse(args, errMsg) + } else if data == nil { + p.postCommandResponse(args, "welcome message has not been set yet") + } else { + p.postCommandResponse(args, fmt.Sprintf("Welcome message is:\n%s", string(data))) + } + + return &model.CommandResponse{}, nil + + case "delete_channel_welcome": + if len(parameters) > 0 { + p.postCommandResponse(args, "`delete_channel_welcome` command does not accept any extra parameters") + return &model.CommandResponse{}, nil + } + + key := fmt.Sprintf("%s%s", args.ChannelId, WELCOMEBOT_CHANNEL_WELCOME_KEY) + if data, appErr := p.API.KVGet(key); appErr != nil { + errMsg := fmt.Sprintf( + "error occured while retrieving the welcome message for the chanel: `%s`", + appErr) + p.postCommandResponse(args, errMsg) + return &model.CommandResponse{}, nil + } else if data == nil { + p.postCommandResponse(args, "welcome message has not been set yet") + return &model.CommandResponse{}, nil + } + + if appErr := p.API.KVDelete(key); appErr != nil { + errMsg := fmt.Sprintf( + "error occured while deleting the welcome message for the chanel: `%s`", + appErr) + p.postCommandResponse(args, errMsg) + } else { + p.postCommandResponse(args, "welcome message has been deleted") + } + return &model.CommandResponse{}, nil case "help": fallthrough case "": diff --git a/server/hooks.go b/server/hooks.go new file mode 100644 index 000000000..59321fb94 --- /dev/null +++ b/server/hooks.go @@ -0,0 +1,68 @@ +package main + +import ( + "fmt" + + "github.com/mattermost/mattermost-server/v5/mlog" + "github.com/mattermost/mattermost-server/v5/model" + "github.com/mattermost/mattermost-server/v5/plugin" +) + +// UserHasJoinedTeam is invoked after the membership has been committed to the database. If +// actor is not nil, the user was added to the team by the actor. +func (p *Plugin) UserHasJoinedTeam(c *plugin.Context, teamMember *model.TeamMember, actor *model.User) { + data := p.constructMessageTemplate(teamMember.UserId, teamMember.TeamId) + if data == nil { + return + } + + for _, message := range p.getWelcomeMessages() { + if message.TeamName == data.Team.Name { + go p.processWelcomeMessage(*data, *message) + } + } +} + +// UserHasJoinedChannel is invoked after the membership has been committed to +// the database. If actor is not nil, the user was invited to the channel by +// the actor. +func (p *Plugin) UserHasJoinedChannel(c *plugin.Context, channelMember *model.ChannelMember, _ *model.User) { + if channelInfo, appErr := p.API.GetChannel(channelMember.ChannelId); appErr != nil { + mlog.Error( + "error occured while checking the type of the chanel", + mlog.String("channelId", channelMember.ChannelId), + mlog.Err(appErr), + ) + return + } else if channelInfo.Type == model.CHANNEL_PRIVATE { + return + } + + key := fmt.Sprintf("%s%s", channelMember.ChannelId, WELCOMEBOT_CHANNEL_WELCOME_KEY) + data, appErr := p.API.KVGet(key) + if appErr != nil { + mlog.Error( + "error occured while retrieving the welcome message", + mlog.String("channelId", channelMember.ChannelId), + mlog.Err(appErr), + ) + return + } + + if data == nil { + // No welcome message for the given channel + return + } + + post := &model.Post{ + UserId: p.botUserID, + ChannelId: channelMember.ChannelId, + Message: string(data), + } + if _, appErr := p.API.CreatePost(post); appErr != nil { + mlog.Error("failed to post welcome message to the channel", + mlog.String("channelId", channelMember.ChannelId), + mlog.Err(appErr), + ) + } +} diff --git a/server/plugin.go b/server/plugin.go index 3186b19e4..43478c3af 100644 --- a/server/plugin.go +++ b/server/plugin.go @@ -12,6 +12,8 @@ const ( botUsername = "welcomebot" botDisplayName = "Welcomebot" botDescription = "A bot account created by the Welcomebot plugin." + + WELCOMEBOT_CHANNEL_WELCOME_KEY = "_chanmsg" ) // Plugin represents the welcome bot plugin diff --git a/server/team_hooks.go b/server/team_hooks.go deleted file mode 100644 index 743e92e28..000000000 --- a/server/team_hooks.go +++ /dev/null @@ -1,21 +0,0 @@ -package main - -import ( - "github.com/mattermost/mattermost-server/v5/model" - "github.com/mattermost/mattermost-server/v5/plugin" -) - -// UserHasJoinedTeam is invoked after the membership has been committed to the database. If -// actor is not nil, the user was added to the team by the actor. -func (p *Plugin) UserHasJoinedTeam(c *plugin.Context, teamMember *model.TeamMember, actor *model.User) { - data := p.constructMessageTemplate(teamMember.UserId, teamMember.TeamId) - if data == nil { - return - } - - for _, message := range p.getWelcomeMessages() { - if message.TeamName == data.Team.Name { - go p.processWelcomeMessage(*data, *message) - } - } -} From a33b3c0b94e11ccb356c10fd59c6d47c47dddfb9 Mon Sep 17 00:00:00 2001 From: Pawel Rozlach Date: Fri, 6 Mar 2020 22:33:24 +0100 Subject: [PATCH 2/6] DRY up the command.go code by moving commands to separate functions --- server/command.go | 288 +++++++++++++++++++++++----------------------- 1 file changed, 142 insertions(+), 146 deletions(-) diff --git a/server/command.go b/server/command.go index d9554417d..3e08aa9f7 100644 --- a/server/command.go +++ b/server/command.go @@ -27,13 +27,12 @@ func getCommand() *model.Command { } } -func (p *Plugin) postCommandResponse(args *model.CommandArgs, text string) { - post := &model.Post{ - UserId: p.botUserID, - ChannelId: args.ChannelId, - Message: text, +func responsef(format string, args ...interface{}) *model.CommandResponse { + return &model.CommandResponse{ + ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, + Text: fmt.Sprintf(format, args...), + Type: model.POST_DEFAULT, } - _ = p.API.SendEphemeralPost(args.UserId, post) } func (p *Plugin) hasSysadminRole(userId string) (bool, error) { @@ -47,7 +46,130 @@ func (p *Plugin) hasSysadminRole(userId string) (bool, error) { return true, nil } -func (p *Plugin) ExecuteCommand(c *plugin.Context, args *model.CommandArgs) (*model.CommandResponse, *model.AppError) { +func validateCommand(action string, parameters []string) *model.CommandResponse { + switch action { + case "preview": + if len(parameters) != 1 { + return responsef("Please specify a team, for which preview should be made.") + } + case "list": + if len(parameters) > 0 { + return responsef("List command does not accept any extra parameters") + } + case "set_channel_welcome": + if len(parameters) == 0 { + return responsef("`set_channel_welcome` command requires the message to be provided") + } + case "get_channel_welcome": + if len(parameters) > 0 { + return responsef("`get_channel_welcome` command does not accept any extra parameters") + } + case "delete_channel_welcome": + if len(parameters) > 0 { + return responsef("`delete_channel_welcome` command does not accept any extra parameters") + } + } + + return nil +} + +func (p *Plugin) executeCommandPreview(teamName string, args *model.CommandArgs) *model.CommandResponse { + found := false + for _, message := range p.getWelcomeMessages() { + if message.TeamName == teamName { + if err := p.previewWelcomeMessage(teamName, args, *message); err != nil { + return responsef("error occured while processing greeting for team `%s`: `%s`", teamName, err) + } + + found = true + } + } + + if !found { + return responsef("team `%s` has not been found", teamName) + } + + return &model.CommandResponse{} +} + +func (p *Plugin) executeCommandList(args *model.CommandArgs) *model.CommandResponse { + wecomeMessages := p.getWelcomeMessages() + + if len(wecomeMessages) == 0 { + return responsef("There are no welcome messages defined") + } + + // Deduplicate entries + teams := make(map[string]struct{}) + for _, message := range wecomeMessages { + teams[message.TeamName] = struct{}{} + } + + var str strings.Builder + str.WriteString("Teams for which welcome messages are defined:") + for team := range teams { + str.WriteString(fmt.Sprintf("\n * %s", team)) + } + return responsef(str.String()) +} + +func (p *Plugin) executeCommandSetWelcome(args *model.CommandArgs) *model.CommandResponse { + channelInfo, appErr := p.API.GetChannel(args.ChannelId) + if appErr != nil { + return responsef("error occured while checking the type of the chanelId `%s`: `%s`", args.ChannelId, appErr) + } + + if channelInfo.Type == model.CHANNEL_PRIVATE { + return responsef("welcome messages are not supported for direct channels") + } + + // strings.Fields will consume ALL whitespace, so plain re-joining of the + // parameters slice will not produce the same message + message := strings.SplitN(args.Command, "set_channel_welcome", 2)[1] + message = strings.TrimSpace(message) + + key := fmt.Sprintf("%s%s", args.ChannelId, WELCOMEBOT_CHANNEL_WELCOME_KEY) + if appErr := p.API.KVSet(key, []byte(message)); appErr != nil { + return responsef("error occured while storing the welcome message for the chanel: `%s`", appErr) + } + + return responsef("stored the welcome message:\n%s", message) +} + +func (p *Plugin) executeCommandGetWelcome(args *model.CommandArgs) *model.CommandResponse { + key := fmt.Sprintf("%s%s", args.ChannelId, WELCOMEBOT_CHANNEL_WELCOME_KEY) + data, appErr := p.API.KVGet(key) + if appErr != nil { + return responsef("error occured while retrieving the welcome message for the chanel: `%s`", appErr) + } + + if data == nil { + return responsef("welcome message has not been set yet") + } + + return responsef("Welcome message is:\n%s", string(data)) +} + +func (p *Plugin) executeCommandDeleteWelcome(args *model.CommandArgs) *model.CommandResponse { + key := fmt.Sprintf("%s%s", args.ChannelId, WELCOMEBOT_CHANNEL_WELCOME_KEY) + data, appErr := p.API.KVGet(key) + + if appErr != nil { + return responsef("error occured while retrieving the welcome message for the chanel: `%s`", appErr) + } + + if data == nil { + return responsef("welcome message has not been set yet") + } + + if appErr := p.API.KVDelete(key); appErr != nil { + return responsef("error occured while deleting the welcome message for the chanel: `%s`", appErr) + } + + return responsef("welcome message has been deleted") +} + +func (p *Plugin) ExecuteCommand(_ *plugin.Context, args *model.CommandArgs) (*model.CommandResponse, *model.AppError) { split := strings.Fields(args.Command) command := split[0] parameters := []string{} @@ -63,162 +185,36 @@ func (p *Plugin) ExecuteCommand(c *plugin.Context, args *model.CommandArgs) (*mo return &model.CommandResponse{}, nil } + if response := validateCommand(action, parameters); response != nil { + return response, nil + } + isSysadmin, err := p.hasSysadminRole(args.UserId) if err != nil { - p.postCommandResponse(args, fmt.Sprintf("authorization failed: %v", err)) - return &model.CommandResponse{}, nil + return responsef("authorization failed: %s", err), nil } if !isSysadmin { - p.postCommandResponse(args, "/welcomebot commands can only be executed by the user with system admin role") - return &model.CommandResponse{}, nil + return responsef("/welcomebot commands can only be executed by the user with system admin role"), nil } switch action { case "preview": - if len(parameters) != 1 { - p.postCommandResponse(args, "Please specify a team, for which preview should be made.") - return &model.CommandResponse{}, nil - } - teamName := parameters[0] - - found := false - for _, message := range p.getWelcomeMessages() { - if message.TeamName == teamName { - if err = p.previewWelcomeMessage(teamName, args, *message); err != nil { - errMsg := fmt.Sprintf("error occured while processing greeting for team `%s`: `%s`", teamName, err) - p.postCommandResponse(args, errMsg) - return &model.CommandResponse{}, nil - } - - found = true - } - } - - if !found { - p.postCommandResponse(args, fmt.Sprintf("team `%s` has not been found", teamName)) - } - return &model.CommandResponse{}, nil + return p.executeCommandPreview(teamName, args), nil case "list": - if len(parameters) > 0 { - p.postCommandResponse(args, "List command does not accept any extra parameters") - return &model.CommandResponse{}, nil - } - - wecomeMessages := p.getWelcomeMessages() - - if len(wecomeMessages) == 0 { - p.postCommandResponse(args, "There are no welcome messages defined") - return &model.CommandResponse{}, nil - } - - // Deduplicate entries - teams := make(map[string]struct{}) - for _, message := range wecomeMessages { - teams[message.TeamName] = struct{}{} - } - - var str strings.Builder - str.WriteString("Teams for which welcome messages are defined:") - for team := range teams { - str.WriteString(fmt.Sprintf("\n * %s", team)) - } - p.postCommandResponse(args, str.String()) - return &model.CommandResponse{}, nil + return p.executeCommandList(args), nil case "set_channel_welcome": - if len(parameters) == 0 { - p.postCommandResponse(args, "`set_channel_welcome` command requires the message to be provided") - return &model.CommandResponse{}, nil - } - - channelInfo, appErr := p.API.GetChannel(args.ChannelId) - if appErr != nil { - errMsg := fmt.Sprintf( - "error occured while checking the type of the chanelId `%s`: `%s`", - args.ChannelId, err) - p.postCommandResponse(args, errMsg) - return &model.CommandResponse{}, nil - } - - if channelInfo.Type == model.CHANNEL_PRIVATE { - p.postCommandResponse( - args, "welcome messages are not supported for direct channels") - return &model.CommandResponse{}, nil - } - - // strings.Fields will consume ALL whitespace, so plain re-joining of the - // parameters slice will not produce the same message - message := strings.SplitN(args.Command, "set_channel_welcome", 2)[1] - message = strings.TrimSpace(message) - - key := fmt.Sprintf("%s%s", args.ChannelId, WELCOMEBOT_CHANNEL_WELCOME_KEY) - if appErr := p.API.KVSet(key, []byte(message)); appErr != nil { - errMsg := fmt.Sprintf( - "error occured while storing the welcome message for the chanel: `%s`", - appErr) - p.postCommandResponse(args, errMsg) - return &model.CommandResponse{}, nil - } - - p.postCommandResponse(args, fmt.Sprintf("stored the welcome message:\n%s", message)) - return &model.CommandResponse{}, nil - + return p.executeCommandSetWelcome(args), nil case "get_channel_welcome": - if len(parameters) > 0 { - p.postCommandResponse(args, "`get_channel_welcome` command does not accept any extra parameters") - return &model.CommandResponse{}, nil - } - - key := fmt.Sprintf("%s%s", args.ChannelId, WELCOMEBOT_CHANNEL_WELCOME_KEY) - if data, appErr := p.API.KVGet(key); appErr != nil { - errMsg := fmt.Sprintf( - "error occured while retrieving the welcome message for the chanel: `%s`", - appErr) - p.postCommandResponse(args, errMsg) - } else if data == nil { - p.postCommandResponse(args, "welcome message has not been set yet") - } else { - p.postCommandResponse(args, fmt.Sprintf("Welcome message is:\n%s", string(data))) - } - - return &model.CommandResponse{}, nil - + return p.executeCommandGetWelcome(args), nil case "delete_channel_welcome": - if len(parameters) > 0 { - p.postCommandResponse(args, "`delete_channel_welcome` command does not accept any extra parameters") - return &model.CommandResponse{}, nil - } - - key := fmt.Sprintf("%s%s", args.ChannelId, WELCOMEBOT_CHANNEL_WELCOME_KEY) - if data, appErr := p.API.KVGet(key); appErr != nil { - errMsg := fmt.Sprintf( - "error occured while retrieving the welcome message for the chanel: `%s`", - appErr) - p.postCommandResponse(args, errMsg) - return &model.CommandResponse{}, nil - } else if data == nil { - p.postCommandResponse(args, "welcome message has not been set yet") - return &model.CommandResponse{}, nil - } - - if appErr := p.API.KVDelete(key); appErr != nil { - errMsg := fmt.Sprintf( - "error occured while deleting the welcome message for the chanel: `%s`", - appErr) - p.postCommandResponse(args, errMsg) - } else { - p.postCommandResponse(args, "welcome message has been deleted") - } - return &model.CommandResponse{}, nil + return p.executeCommandDeleteWelcome(args), nil case "help": fallthrough case "": text := "###### Mattermost welcomebot Plugin - Slash Command Help\n" + strings.Replace(COMMAND_HELP, "|", "`", -1) - p.postCommandResponse(args, text) - return &model.CommandResponse{}, nil + return responsef(text), nil } - p.postCommandResponse(args, fmt.Sprintf("Unknown action %v", action)) - - return &model.CommandResponse{}, nil + return responsef("Unknown action %v", action), nil } From c6e6f60d3213143678de68ea2270cf61ce3e3d12 Mon Sep 17 00:00:00 2001 From: Pawel Rozlach Date: Sun, 22 Mar 2020 16:33:12 +0100 Subject: [PATCH 3/6] Review fixes #1 --- server/command.go | 55 ++++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/server/command.go b/server/command.go index 3e08aa9f7..8802af53a 100644 --- a/server/command.go +++ b/server/command.go @@ -27,9 +27,10 @@ func getCommand() *model.Command { } } -func responsef(format string, args ...interface{}) *model.CommandResponse { +func (p *Plugin) responsef(format string, args ...interface{}) *model.CommandResponse { return &model.CommandResponse{ ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, + Username: p.botUserID, Text: fmt.Sprintf(format, args...), Type: model.POST_DEFAULT, } @@ -46,27 +47,27 @@ func (p *Plugin) hasSysadminRole(userId string) (bool, error) { return true, nil } -func validateCommand(action string, parameters []string) *model.CommandResponse { +func (p *Plugin) validateCommand(action string, parameters []string) *model.CommandResponse { switch action { case "preview": if len(parameters) != 1 { - return responsef("Please specify a team, for which preview should be made.") + return p.responsef("Please specify a team, for which preview should be made.") } case "list": if len(parameters) > 0 { - return responsef("List command does not accept any extra parameters") + return p.responsef("List command does not accept any extra parameters") } case "set_channel_welcome": if len(parameters) == 0 { - return responsef("`set_channel_welcome` command requires the message to be provided") + return p.responsef("`set_channel_welcome` command requires the message to be provided") } case "get_channel_welcome": if len(parameters) > 0 { - return responsef("`get_channel_welcome` command does not accept any extra parameters") + return p.responsef("`get_channel_welcome` command does not accept any extra parameters") } case "delete_channel_welcome": if len(parameters) > 0 { - return responsef("`delete_channel_welcome` command does not accept any extra parameters") + return p.responsef("`delete_channel_welcome` command does not accept any extra parameters") } } @@ -78,7 +79,7 @@ func (p *Plugin) executeCommandPreview(teamName string, args *model.CommandArgs) for _, message := range p.getWelcomeMessages() { if message.TeamName == teamName { if err := p.previewWelcomeMessage(teamName, args, *message); err != nil { - return responsef("error occured while processing greeting for team `%s`: `%s`", teamName, err) + return p.responsef("error occured while processing greeting for team `%s`: `%s`", teamName, err) } found = true @@ -86,7 +87,7 @@ func (p *Plugin) executeCommandPreview(teamName string, args *model.CommandArgs) } if !found { - return responsef("team `%s` has not been found", teamName) + return p.responsef("team `%s` has not been found", teamName) } return &model.CommandResponse{} @@ -96,7 +97,7 @@ func (p *Plugin) executeCommandList(args *model.CommandArgs) *model.CommandRespo wecomeMessages := p.getWelcomeMessages() if len(wecomeMessages) == 0 { - return responsef("There are no welcome messages defined") + return p.responsef("There are no welcome messages defined") } // Deduplicate entries @@ -110,17 +111,17 @@ func (p *Plugin) executeCommandList(args *model.CommandArgs) *model.CommandRespo for team := range teams { str.WriteString(fmt.Sprintf("\n * %s", team)) } - return responsef(str.String()) + return p.responsef(str.String()) } func (p *Plugin) executeCommandSetWelcome(args *model.CommandArgs) *model.CommandResponse { channelInfo, appErr := p.API.GetChannel(args.ChannelId) if appErr != nil { - return responsef("error occured while checking the type of the chanelId `%s`: `%s`", args.ChannelId, appErr) + return p.responsef("error occured while checking the type of the chanelId `%s`: `%s`", args.ChannelId, appErr) } if channelInfo.Type == model.CHANNEL_PRIVATE { - return responsef("welcome messages are not supported for direct channels") + return p.responsef("welcome messages are not supported for direct channels") } // strings.Fields will consume ALL whitespace, so plain re-joining of the @@ -130,24 +131,24 @@ func (p *Plugin) executeCommandSetWelcome(args *model.CommandArgs) *model.Comman key := fmt.Sprintf("%s%s", args.ChannelId, WELCOMEBOT_CHANNEL_WELCOME_KEY) if appErr := p.API.KVSet(key, []byte(message)); appErr != nil { - return responsef("error occured while storing the welcome message for the chanel: `%s`", appErr) + return p.responsef("error occured while storing the welcome message for the chanel: `%s`", appErr) } - return responsef("stored the welcome message:\n%s", message) + return p.responsef("stored the welcome message:\n%s", message) } func (p *Plugin) executeCommandGetWelcome(args *model.CommandArgs) *model.CommandResponse { key := fmt.Sprintf("%s%s", args.ChannelId, WELCOMEBOT_CHANNEL_WELCOME_KEY) data, appErr := p.API.KVGet(key) if appErr != nil { - return responsef("error occured while retrieving the welcome message for the chanel: `%s`", appErr) + return p.responsef("error occured while retrieving the welcome message for the chanel: `%s`", appErr) } if data == nil { - return responsef("welcome message has not been set yet") + return p.responsef("welcome message has not been set yet") } - return responsef("Welcome message is:\n%s", string(data)) + return p.responsef("Welcome message is:\n%s", string(data)) } func (p *Plugin) executeCommandDeleteWelcome(args *model.CommandArgs) *model.CommandResponse { @@ -155,18 +156,18 @@ func (p *Plugin) executeCommandDeleteWelcome(args *model.CommandArgs) *model.Com data, appErr := p.API.KVGet(key) if appErr != nil { - return responsef("error occured while retrieving the welcome message for the chanel: `%s`", appErr) + return p.responsef("error occured while retrieving the welcome message for the chanel: `%s`", appErr) } if data == nil { - return responsef("welcome message has not been set yet") + return p.responsef("welcome message has not been set yet") } if appErr := p.API.KVDelete(key); appErr != nil { - return responsef("error occured while deleting the welcome message for the chanel: `%s`", appErr) + return p.responsef("error occured while deleting the welcome message for the chanel: `%s`", appErr) } - return responsef("welcome message has been deleted") + return p.responsef("welcome message has been deleted") } func (p *Plugin) ExecuteCommand(_ *plugin.Context, args *model.CommandArgs) (*model.CommandResponse, *model.AppError) { @@ -185,16 +186,16 @@ func (p *Plugin) ExecuteCommand(_ *plugin.Context, args *model.CommandArgs) (*mo return &model.CommandResponse{}, nil } - if response := validateCommand(action, parameters); response != nil { + if response := p.validateCommand(action, parameters); response != nil { return response, nil } isSysadmin, err := p.hasSysadminRole(args.UserId) if err != nil { - return responsef("authorization failed: %s", err), nil + return p.responsef("authorization failed: %s", err), nil } if !isSysadmin { - return responsef("/welcomebot commands can only be executed by the user with system admin role"), nil + return p.responsef("/welcomebot commands can only be executed by the user with system admin role"), nil } switch action { @@ -213,8 +214,8 @@ func (p *Plugin) ExecuteCommand(_ *plugin.Context, args *model.CommandArgs) (*mo fallthrough case "": text := "###### Mattermost welcomebot Plugin - Slash Command Help\n" + strings.Replace(COMMAND_HELP, "|", "`", -1) - return responsef(text), nil + return p.responsef(text), nil } - return responsef("Unknown action %v", action), nil + return p.responsef("Unknown action %v", action), nil } From 0ae88807b26898d6319fde649e72ab74386e562b Mon Sep 17 00:00:00 2001 From: Pawel Rozlach Date: Sun, 12 Apr 2020 10:31:39 +0200 Subject: [PATCH 4/6] Change keys prefix ordering --- server/command.go | 6 +++--- server/hooks.go | 2 +- server/plugin.go | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/server/command.go b/server/command.go index 8802af53a..a5ea8296b 100644 --- a/server/command.go +++ b/server/command.go @@ -129,7 +129,7 @@ func (p *Plugin) executeCommandSetWelcome(args *model.CommandArgs) *model.Comman message := strings.SplitN(args.Command, "set_channel_welcome", 2)[1] message = strings.TrimSpace(message) - key := fmt.Sprintf("%s%s", args.ChannelId, WELCOMEBOT_CHANNEL_WELCOME_KEY) + key := fmt.Sprintf("%s%s", WELCOMEBOT_CHANNEL_WELCOME_KEY, args.ChannelId) if appErr := p.API.KVSet(key, []byte(message)); appErr != nil { return p.responsef("error occured while storing the welcome message for the chanel: `%s`", appErr) } @@ -138,7 +138,7 @@ func (p *Plugin) executeCommandSetWelcome(args *model.CommandArgs) *model.Comman } func (p *Plugin) executeCommandGetWelcome(args *model.CommandArgs) *model.CommandResponse { - key := fmt.Sprintf("%s%s", args.ChannelId, WELCOMEBOT_CHANNEL_WELCOME_KEY) + key := fmt.Sprintf("%s%s", WELCOMEBOT_CHANNEL_WELCOME_KEY, args.ChannelId) data, appErr := p.API.KVGet(key) if appErr != nil { return p.responsef("error occured while retrieving the welcome message for the chanel: `%s`", appErr) @@ -152,7 +152,7 @@ func (p *Plugin) executeCommandGetWelcome(args *model.CommandArgs) *model.Comman } func (p *Plugin) executeCommandDeleteWelcome(args *model.CommandArgs) *model.CommandResponse { - key := fmt.Sprintf("%s%s", args.ChannelId, WELCOMEBOT_CHANNEL_WELCOME_KEY) + key := fmt.Sprintf("%s%s", WELCOMEBOT_CHANNEL_WELCOME_KEY, args.ChannelId) data, appErr := p.API.KVGet(key) if appErr != nil { diff --git a/server/hooks.go b/server/hooks.go index 59321fb94..66f62ac33 100644 --- a/server/hooks.go +++ b/server/hooks.go @@ -38,7 +38,7 @@ func (p *Plugin) UserHasJoinedChannel(c *plugin.Context, channelMember *model.Ch return } - key := fmt.Sprintf("%s%s", channelMember.ChannelId, WELCOMEBOT_CHANNEL_WELCOME_KEY) + key := fmt.Sprintf("%s%s", WELCOMEBOT_CHANNEL_WELCOME_KEY, channelMember.ChannelId) data, appErr := p.API.KVGet(key) if appErr != nil { mlog.Error( diff --git a/server/plugin.go b/server/plugin.go index 43478c3af..1463e555a 100644 --- a/server/plugin.go +++ b/server/plugin.go @@ -13,7 +13,7 @@ const ( botDisplayName = "Welcomebot" botDescription = "A bot account created by the Welcomebot plugin." - WELCOMEBOT_CHANNEL_WELCOME_KEY = "_chanmsg" + WELCOMEBOT_CHANNEL_WELCOME_KEY = "chanmsg_" ) // Plugin represents the welcome bot plugin From e29d8fbe1f1ae6e279832eda34819bdb4e605c48 Mon Sep 17 00:00:00 2001 From: Pawel Rozlach Date: Sun, 12 Apr 2020 11:25:47 +0200 Subject: [PATCH 5/6] Switch back to sending ephemeral posts --- server/command.go | 114 ++++++++++++++++++++++++++++------------------ 1 file changed, 69 insertions(+), 45 deletions(-) diff --git a/server/command.go b/server/command.go index a5ea8296b..1d073055c 100644 --- a/server/command.go +++ b/server/command.go @@ -27,13 +27,13 @@ func getCommand() *model.Command { } } -func (p *Plugin) responsef(format string, args ...interface{}) *model.CommandResponse { - return &model.CommandResponse{ - ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, - Username: p.botUserID, - Text: fmt.Sprintf(format, args...), - Type: model.POST_DEFAULT, +func (p *Plugin) postCommandResponse(args *model.CommandArgs, text string, textArgs ...interface{}) { + post := &model.Post{ + UserId: p.botUserID, + ChannelId: args.ChannelId, + Message: fmt.Sprintf(text, textArgs...), } + _ = p.API.SendEphemeralPost(args.UserId, post) } func (p *Plugin) hasSysadminRole(userId string) (bool, error) { @@ -47,39 +47,40 @@ func (p *Plugin) hasSysadminRole(userId string) (bool, error) { return true, nil } -func (p *Plugin) validateCommand(action string, parameters []string) *model.CommandResponse { +func (p *Plugin) validateCommand(action string, parameters []string) string { switch action { case "preview": if len(parameters) != 1 { - return p.responsef("Please specify a team, for which preview should be made.") + return "Please specify a team, for which preview should be made." } case "list": if len(parameters) > 0 { - return p.responsef("List command does not accept any extra parameters") + return "List command does not accept any extra parameters" } case "set_channel_welcome": if len(parameters) == 0 { - return p.responsef("`set_channel_welcome` command requires the message to be provided") + return "`set_channel_welcome` command requires the message to be provided" } case "get_channel_welcome": if len(parameters) > 0 { - return p.responsef("`get_channel_welcome` command does not accept any extra parameters") + return "`get_channel_welcome` command does not accept any extra parameters" } case "delete_channel_welcome": if len(parameters) > 0 { - return p.responsef("`delete_channel_welcome` command does not accept any extra parameters") + return "`delete_channel_welcome` command does not accept any extra parameters" } } - return nil + return "" } -func (p *Plugin) executeCommandPreview(teamName string, args *model.CommandArgs) *model.CommandResponse { +func (p *Plugin) executeCommandPreview(teamName string, args *model.CommandArgs) { found := false for _, message := range p.getWelcomeMessages() { if message.TeamName == teamName { if err := p.previewWelcomeMessage(teamName, args, *message); err != nil { - return p.responsef("error occured while processing greeting for team `%s`: `%s`", teamName, err) + p.postCommandResponse(args, "error occured while processing greeting for team `%s`: `%s`", teamName, err) + return } found = true @@ -87,17 +88,18 @@ func (p *Plugin) executeCommandPreview(teamName string, args *model.CommandArgs) } if !found { - return p.responsef("team `%s` has not been found", teamName) + p.postCommandResponse(args, "team `%s` has not been found", teamName) } - return &model.CommandResponse{} + return } -func (p *Plugin) executeCommandList(args *model.CommandArgs) *model.CommandResponse { +func (p *Plugin) executeCommandList(args *model.CommandArgs) { wecomeMessages := p.getWelcomeMessages() if len(wecomeMessages) == 0 { - return p.responsef("There are no welcome messages defined") + p.postCommandResponse(args, "There are no welcome messages defined") + return } // Deduplicate entries @@ -111,17 +113,20 @@ func (p *Plugin) executeCommandList(args *model.CommandArgs) *model.CommandRespo for team := range teams { str.WriteString(fmt.Sprintf("\n * %s", team)) } - return p.responsef(str.String()) + p.postCommandResponse(args, str.String()) + return } -func (p *Plugin) executeCommandSetWelcome(args *model.CommandArgs) *model.CommandResponse { +func (p *Plugin) executeCommandSetWelcome(args *model.CommandArgs) { channelInfo, appErr := p.API.GetChannel(args.ChannelId) if appErr != nil { - return p.responsef("error occured while checking the type of the chanelId `%s`: `%s`", args.ChannelId, appErr) + p.postCommandResponse(args, "error occured while checking the type of the chanelId `%s`: `%s`", args.ChannelId, appErr) + return } if channelInfo.Type == model.CHANNEL_PRIVATE { - return p.responsef("welcome messages are not supported for direct channels") + p.postCommandResponse(args, "welcome messages are not supported for direct channels") + return } // strings.Fields will consume ALL whitespace, so plain re-joining of the @@ -131,43 +136,52 @@ func (p *Plugin) executeCommandSetWelcome(args *model.CommandArgs) *model.Comman key := fmt.Sprintf("%s%s", WELCOMEBOT_CHANNEL_WELCOME_KEY, args.ChannelId) if appErr := p.API.KVSet(key, []byte(message)); appErr != nil { - return p.responsef("error occured while storing the welcome message for the chanel: `%s`", appErr) + p.postCommandResponse(args, "error occured while storing the welcome message for the chanel: `%s`", appErr) + return } - return p.responsef("stored the welcome message:\n%s", message) + p.postCommandResponse(args, "stored the welcome message:\n%s", message) + return } -func (p *Plugin) executeCommandGetWelcome(args *model.CommandArgs) *model.CommandResponse { +func (p *Plugin) executeCommandGetWelcome(args *model.CommandArgs) { key := fmt.Sprintf("%s%s", WELCOMEBOT_CHANNEL_WELCOME_KEY, args.ChannelId) data, appErr := p.API.KVGet(key) if appErr != nil { - return p.responsef("error occured while retrieving the welcome message for the chanel: `%s`", appErr) + p.postCommandResponse(args, "error occured while retrieving the welcome message for the chanel: `%s`", appErr) + return } if data == nil { - return p.responsef("welcome message has not been set yet") + p.postCommandResponse(args, "welcome message has not been set yet") + return } - return p.responsef("Welcome message is:\n%s", string(data)) + p.postCommandResponse(args, "Welcome message is:\n%s", string(data)) + return } -func (p *Plugin) executeCommandDeleteWelcome(args *model.CommandArgs) *model.CommandResponse { +func (p *Plugin) executeCommandDeleteWelcome(args *model.CommandArgs) { key := fmt.Sprintf("%s%s", WELCOMEBOT_CHANNEL_WELCOME_KEY, args.ChannelId) data, appErr := p.API.KVGet(key) if appErr != nil { - return p.responsef("error occured while retrieving the welcome message for the chanel: `%s`", appErr) + p.postCommandResponse(args, "error occured while retrieving the welcome message for the chanel: `%s`", appErr) + return } if data == nil { - return p.responsef("welcome message has not been set yet") + p.postCommandResponse(args, "welcome message has not been set yet") + return } if appErr := p.API.KVDelete(key); appErr != nil { - return p.responsef("error occured while deleting the welcome message for the chanel: `%s`", appErr) + p.postCommandResponse(args, "error occured while deleting the welcome message for the chanel: `%s`", appErr) + return } - return p.responsef("welcome message has been deleted") + p.postCommandResponse(args, "welcome message has been deleted") + return } func (p *Plugin) ExecuteCommand(_ *plugin.Context, args *model.CommandArgs) (*model.CommandResponse, *model.AppError) { @@ -186,36 +200,46 @@ func (p *Plugin) ExecuteCommand(_ *plugin.Context, args *model.CommandArgs) (*mo return &model.CommandResponse{}, nil } - if response := p.validateCommand(action, parameters); response != nil { - return response, nil + if response := p.validateCommand(action, parameters); response != "" { + p.postCommandResponse(args, response) + return &model.CommandResponse{}, nil } isSysadmin, err := p.hasSysadminRole(args.UserId) if err != nil { - return p.responsef("authorization failed: %s", err), nil + p.postCommandResponse(args, "authorization failed: %s", err) + return &model.CommandResponse{}, nil } if !isSysadmin { - return p.responsef("/welcomebot commands can only be executed by the user with system admin role"), nil + p.postCommandResponse(args, "/welcomebot commands can only be executed by the user with system admin role") + return &model.CommandResponse{}, nil } switch action { case "preview": teamName := parameters[0] - return p.executeCommandPreview(teamName, args), nil + p.executeCommandPreview(teamName, args) + return &model.CommandResponse{}, nil case "list": - return p.executeCommandList(args), nil + p.executeCommandList(args) + return &model.CommandResponse{}, nil case "set_channel_welcome": - return p.executeCommandSetWelcome(args), nil + p.executeCommandSetWelcome(args) + return &model.CommandResponse{}, nil case "get_channel_welcome": - return p.executeCommandGetWelcome(args), nil + p.executeCommandGetWelcome(args) + return &model.CommandResponse{}, nil case "delete_channel_welcome": - return p.executeCommandDeleteWelcome(args), nil + p.executeCommandDeleteWelcome(args) + return &model.CommandResponse{}, nil case "help": fallthrough case "": text := "###### Mattermost welcomebot Plugin - Slash Command Help\n" + strings.Replace(COMMAND_HELP, "|", "`", -1) - return p.responsef(text), nil + p.postCommandResponse(args, text) + return &model.CommandResponse{}, nil } - return p.responsef("Unknown action %v", action), nil + p.postCommandResponse(args, "Unknown action %v", action) + return &model.CommandResponse{}, nil } From d8914a927081236965117729f50e0a5da5778d9e Mon Sep 17 00:00:00 2001 From: Pawel Rozlach Date: Sun, 12 Apr 2020 12:17:09 +0200 Subject: [PATCH 6/6] Send an opportunistic ephemeral post and a DM --- server/hooks.go | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/server/hooks.go b/server/hooks.go index 66f62ac33..48809a7e2 100644 --- a/server/hooks.go +++ b/server/hooks.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "time" "github.com/mattermost/mattermost-server/v5/mlog" "github.com/mattermost/mattermost-server/v5/model" @@ -54,15 +55,36 @@ func (p *Plugin) UserHasJoinedChannel(c *plugin.Context, channelMember *model.Ch return } - post := &model.Post{ + dmChannel, err := p.API.GetDirectChannel(channelMember.UserId, p.botUserID) + if err != nil { + mlog.Error( + "error occured while creating direct channel to the user", + mlog.String("UserId", channelMember.UserId), + mlog.Err(err), + ) + return + } + + // We send a DM and an opportunistic ephemeral message to the channel. See + // the discussion at the link below for more details: + // https://github.com/mattermost/mattermost-plugin-welcomebot/pull/31#issuecomment-611691023 + postDM := &model.Post{ UserId: p.botUserID, - ChannelId: channelMember.ChannelId, + ChannelId: dmChannel.Id, Message: string(data), } - if _, appErr := p.API.CreatePost(post); appErr != nil { + if _, appErr := p.API.CreatePost(postDM); appErr != nil { mlog.Error("failed to post welcome message to the channel", - mlog.String("channelId", channelMember.ChannelId), + mlog.String("channelId", dmChannel.Id), mlog.Err(appErr), ) } + + postChannel := &model.Post{ + UserId: p.botUserID, + ChannelId: channelMember.ChannelId, + Message: string(data), + } + time.Sleep(1 * time.Second) + _ = p.API.SendEphemeralPost(channelMember.UserId, postChannel) }