mirror of
https://github.com/mattermost/mattermost.git
synced 2026-02-03 20:40:00 -05:00
* Add mutex to model.Post to guard against race conditions on Post.Props * Rename mutex * Add GetProp() method to Post * Fix more tests * Fix flaky test Benchmarks: BenchmarkPostPropsGet_indirect BenchmarkPostPropsGet_indirect-2 85026746 13.0 ns/op 0 B/op 0 allocs/op BenchmarkPostPropsGet_indirect-4 90273747 13.0 ns/op 0 B/op 0 allocs/op BenchmarkPostPropsGet_indirect-8 88324293 13.0 ns/op 0 B/op 0 allocs/op BenchmarkPostPropsGet_indirect-16 91427720 13.1 ns/op 0 B/op 0 allocs/op BenchmarkPostPropsGet_direct BenchmarkPostPropsGet_direct-2 1000000000 0.242 ns/op 0 B/op 0 allocs/op BenchmarkPostPropsGet_direct-4 1000000000 0.241 ns/op 0 B/op 0 allocs/op BenchmarkPostPropsGet_direct-8 1000000000 0.240 ns/op 0 B/op 0 allocs/op BenchmarkPostPropsGet_direct-16 1000000000 0.241 ns/op 0 B/op 0 allocs/op BenchmarkPostPropsAdd_indirect BenchmarkPostPropsAdd_indirect-2 5602224 203 ns/op 336 B/op 2 allocs/op BenchmarkPostPropsAdd_indirect-4 5959496 206 ns/op 336 B/op 2 allocs/op BenchmarkPostPropsAdd_indirect-8 5833999 205 ns/op 336 B/op 2 allocs/op BenchmarkPostPropsAdd_indirect-16 5802493 225 ns/op 336 B/op 2 allocs/op BenchmarkPostPropsAdd_direct BenchmarkPostPropsAdd_direct-2 100000000 11.3 ns/op 0 B/op 0 allocs/op BenchmarkPostPropsAdd_direct-4 100000000 11.3 ns/op 0 B/op 0 allocs/op BenchmarkPostPropsAdd_direct-8 100000000 11.6 ns/op 0 B/op 0 allocs/op BenchmarkPostPropsAdd_direct-16 99840794 11.4 ns/op 0 B/op 0 allocs/op BenchmarkPostPropsDel_indirect BenchmarkPostPropsDel_indirect-2 18824002 61.9 ns/op 48 B/op 1 allocs/op BenchmarkPostPropsDel_indirect-4 19470736 63.8 ns/op 48 B/op 1 allocs/op BenchmarkPostPropsDel_indirect-8 17640460 65.3 ns/op 48 B/op 1 allocs/op BenchmarkPostPropsDel_indirect-16 18692962 65.4 ns/op 48 B/op 1 allocs/op BenchmarkPostPropsDel_direct BenchmarkPostPropsDel_direct-2 516257440 2.34 ns/op 0 B/op 0 allocs/op BenchmarkPostPropsDel_direct-4 514865216 2.43 ns/op 0 B/op 0 allocs/op BenchmarkPostPropsDel_direct-8 511330477 2.37 ns/op 0 B/op 0 allocs/op BenchmarkPostPropsDel_direct-16 499504010 2.38 ns/op 0 B/op 0 allocs/op
287 lines
8.6 KiB
Go
287 lines
8.6 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package plugin
|
|
|
|
import (
|
|
"io/ioutil"
|
|
"path/filepath"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/mattermost/mattermost-server/v5/model"
|
|
"github.com/mattermost/mattermost-server/v5/utils"
|
|
)
|
|
|
|
type ensureBotOptions struct {
|
|
ProfileImagePath string
|
|
IconImagePath string
|
|
}
|
|
|
|
type EnsureBotOption func(*ensureBotOptions)
|
|
|
|
func ProfileImagePath(path string) EnsureBotOption {
|
|
return func(args *ensureBotOptions) {
|
|
args.ProfileImagePath = path
|
|
}
|
|
}
|
|
|
|
func IconImagePath(path string) EnsureBotOption {
|
|
return func(args *ensureBotOptions) {
|
|
args.IconImagePath = path
|
|
}
|
|
}
|
|
|
|
// EnsureBot implements Helpers.EnsureBot
|
|
func (p *HelpersImpl) EnsureBot(bot *model.Bot, options ...EnsureBotOption) (retBotID string, retErr error) {
|
|
err := p.ensureServerVersion("5.10.0")
|
|
if err != nil {
|
|
return "", errors.Wrap(err, "failed to ensure bot")
|
|
}
|
|
|
|
// Default options
|
|
o := &ensureBotOptions{
|
|
ProfileImagePath: "",
|
|
IconImagePath: "",
|
|
}
|
|
|
|
for _, setter := range options {
|
|
setter(o)
|
|
}
|
|
|
|
botID, err := p.ensureBot(bot)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
err = p.setBotImages(botID, o.ProfileImagePath, o.IconImagePath)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return botID, nil
|
|
}
|
|
|
|
type ShouldProcessMessageOption func(*shouldProcessMessageOptions)
|
|
|
|
type shouldProcessMessageOptions struct {
|
|
AllowSystemMessages bool
|
|
AllowBots bool
|
|
AllowWebhook bool
|
|
FilterChannelIDs []string
|
|
FilterUserIDs []string
|
|
OnlyBotDMs bool
|
|
}
|
|
|
|
// AllowSystemMessages configures a call to ShouldProcessMessage to return true for system messages.
|
|
//
|
|
// As it is typically desirable only to consume messages from users of the system, ShouldProcessMessage ignores system messages by default.
|
|
func AllowSystemMessages() ShouldProcessMessageOption {
|
|
return func(options *shouldProcessMessageOptions) {
|
|
options.AllowSystemMessages = true
|
|
}
|
|
}
|
|
|
|
// AllowBots configures a call to ShouldProcessMessage to return true for bot posts.
|
|
//
|
|
// As it is typically desirable only to consume messages from human users of the system, ShouldProcessMessage ignores bot messages by default. When allowing bots, take care to avoid a loop where two plugins respond to each others posts repeatedly.
|
|
func AllowBots() ShouldProcessMessageOption {
|
|
return func(options *shouldProcessMessageOptions) {
|
|
options.AllowBots = true
|
|
}
|
|
}
|
|
|
|
// AllowWebhook configures a call to ShouldProcessMessage to return true for posts from webhook.
|
|
//
|
|
// As it is typically desirable only to consume messages from human users of the system, ShouldProcessMessage ignores webhook messages by default.
|
|
func AllowWebhook() ShouldProcessMessageOption {
|
|
return func(options *shouldProcessMessageOptions) {
|
|
options.AllowWebhook = true
|
|
}
|
|
}
|
|
|
|
// FilterChannelIDs configures a call to ShouldProcessMessage to return true only for the given channels.
|
|
//
|
|
// By default, posts from all channels are allowed to be processed.
|
|
func FilterChannelIDs(filterChannelIDs []string) ShouldProcessMessageOption {
|
|
return func(options *shouldProcessMessageOptions) {
|
|
options.FilterChannelIDs = filterChannelIDs
|
|
}
|
|
}
|
|
|
|
// FilterUserIDs configures a call to ShouldProcessMessage to return true only for the given users.
|
|
//
|
|
// By default, posts from all non-bot users are allowed.
|
|
func FilterUserIDs(filterUserIDs []string) ShouldProcessMessageOption {
|
|
return func(options *shouldProcessMessageOptions) {
|
|
options.FilterUserIDs = filterUserIDs
|
|
}
|
|
}
|
|
|
|
// OnlyBotDMs configures a call to ShouldProcessMessage to return true only for direct messages sent to the bot created by EnsureBot.
|
|
//
|
|
// By default, posts from all channels are allowed.
|
|
func OnlyBotDMs() ShouldProcessMessageOption {
|
|
return func(options *shouldProcessMessageOptions) {
|
|
options.OnlyBotDMs = true
|
|
}
|
|
}
|
|
|
|
// ShouldProcessMessage implements Helpers.ShouldProcessMessage
|
|
func (p *HelpersImpl) ShouldProcessMessage(post *model.Post, options ...ShouldProcessMessageOption) (bool, error) {
|
|
messageProcessOptions := &shouldProcessMessageOptions{}
|
|
for _, option := range options {
|
|
option(messageProcessOptions)
|
|
}
|
|
|
|
botIDBytes, kvGetErr := p.API.KVGet(BOT_USER_KEY)
|
|
if kvGetErr != nil {
|
|
return false, errors.Wrap(kvGetErr, "failed to get bot")
|
|
}
|
|
|
|
if botIDBytes != nil {
|
|
if post.UserId == string(botIDBytes) {
|
|
return false, nil
|
|
}
|
|
}
|
|
|
|
if post.IsSystemMessage() && !messageProcessOptions.AllowSystemMessages {
|
|
return false, nil
|
|
}
|
|
|
|
if !messageProcessOptions.AllowWebhook && post.GetProp("from_webhook") == "true" {
|
|
return false, nil
|
|
}
|
|
|
|
if !messageProcessOptions.AllowBots {
|
|
user, appErr := p.API.GetUser(post.UserId)
|
|
if appErr != nil {
|
|
return false, errors.Wrap(appErr, "unable to get user")
|
|
}
|
|
|
|
if user.IsBot {
|
|
return false, nil
|
|
}
|
|
}
|
|
|
|
if len(messageProcessOptions.FilterChannelIDs) != 0 && !utils.StringInSlice(post.ChannelId, messageProcessOptions.FilterChannelIDs) {
|
|
return false, nil
|
|
}
|
|
|
|
if len(messageProcessOptions.FilterUserIDs) != 0 && !utils.StringInSlice(post.UserId, messageProcessOptions.FilterUserIDs) {
|
|
return false, nil
|
|
}
|
|
|
|
if botIDBytes != nil && messageProcessOptions.OnlyBotDMs {
|
|
channel, appErr := p.API.GetChannel(post.ChannelId)
|
|
if appErr != nil {
|
|
return false, errors.Wrap(appErr, "unable to get channel")
|
|
}
|
|
|
|
if !model.IsBotDMChannel(channel, string(botIDBytes)) {
|
|
return false, nil
|
|
}
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
func (p *HelpersImpl) readFile(path string) ([]byte, error) {
|
|
bundlePath, err := p.API.GetBundlePath()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to get bundle path")
|
|
}
|
|
|
|
imageBytes, err := ioutil.ReadFile(filepath.Join(bundlePath, path))
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to read image")
|
|
}
|
|
return imageBytes, nil
|
|
}
|
|
|
|
func (p *HelpersImpl) ensureBot(bot *model.Bot) (retBotID string, retErr error) {
|
|
// Must provide a bot with a username
|
|
if bot == nil || len(bot.Username) < 1 {
|
|
return "", errors.New("passed a bad bot, nil or no username")
|
|
}
|
|
|
|
// If we fail for any reason, this could be a race between creation of bot and
|
|
// retrieval from another EnsureBot. Just try the basic retrieve existing again.
|
|
defer func() {
|
|
if retBotID == "" || retErr != nil {
|
|
var err error
|
|
var botIDBytes []byte
|
|
|
|
err = utils.ProgressiveRetry(func() error {
|
|
botIDBytes, err = p.API.KVGet(BOT_USER_KEY)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
})
|
|
|
|
if err == nil && botIDBytes != nil {
|
|
retBotID = string(botIDBytes)
|
|
retErr = nil
|
|
}
|
|
}
|
|
}()
|
|
|
|
botIDBytes, kvGetErr := p.API.KVGet(BOT_USER_KEY)
|
|
if kvGetErr != nil {
|
|
return "", errors.Wrap(kvGetErr, "failed to get bot")
|
|
}
|
|
|
|
// If the bot has already been created, there is nothing to do.
|
|
if botIDBytes != nil {
|
|
botID := string(botIDBytes)
|
|
return botID, nil
|
|
}
|
|
|
|
// Check for an existing bot user with that username. If one exists, then use that.
|
|
if user, userGetErr := p.API.GetUserByUsername(bot.Username); userGetErr == nil && user != nil {
|
|
if user.IsBot {
|
|
if kvSetErr := p.API.KVSet(BOT_USER_KEY, []byte(user.Id)); kvSetErr != nil {
|
|
p.API.LogWarn("Failed to set claimed bot user id.", "userid", user.Id, "err", kvSetErr)
|
|
}
|
|
} else {
|
|
p.API.LogError("Plugin attempted to use an account that already exists. Convert user to a bot account in the CLI by running 'mattermost user convert <username> --bot'. If the user is an existing user account you want to preserve, change its username and restart the Mattermost server, after which the plugin will create a bot account with that name. For more information about bot accounts, see https://mattermost.com/pl/default-bot-accounts", "username", bot.Username, "user_id", user.Id)
|
|
}
|
|
return user.Id, nil
|
|
}
|
|
|
|
// Create a new bot user for the plugin
|
|
createdBot, createBotErr := p.API.CreateBot(bot)
|
|
if createBotErr != nil {
|
|
return "", errors.Wrap(createBotErr, "failed to create bot")
|
|
}
|
|
|
|
if kvSetErr := p.API.KVSet(BOT_USER_KEY, []byte(createdBot.UserId)); kvSetErr != nil {
|
|
p.API.LogWarn("Failed to set created bot user id.", "userid", createdBot.UserId, "err", kvSetErr)
|
|
}
|
|
|
|
return createdBot.UserId, nil
|
|
}
|
|
|
|
func (p *HelpersImpl) setBotImages(botID, profileImagePath, iconImagePath string) error {
|
|
if profileImagePath != "" {
|
|
imageBytes, err := p.readFile(profileImagePath)
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to read profile image")
|
|
}
|
|
appErr := p.API.SetProfileImage(botID, imageBytes)
|
|
if appErr != nil {
|
|
return errors.Wrap(appErr, "failed to set profile image")
|
|
}
|
|
}
|
|
if iconImagePath != "" {
|
|
imageBytes, err := p.readFile(iconImagePath)
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to read icon image")
|
|
}
|
|
appErr := p.API.SetBotIconImage(botID, imageBytes)
|
|
if appErr != nil {
|
|
return errors.Wrap(appErr, "failed to set icon image")
|
|
}
|
|
}
|
|
return nil
|
|
}
|