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

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See LICENSE.txt for license information.
     3  
     4  package plugin
     5  
     6  import (
     7  	"io/ioutil"
     8  	"path/filepath"
     9  
    10  	"github.com/pkg/errors"
    11  
    12  	"github.com/mattermost/mattermost-server/v5/model"
    13  	"github.com/mattermost/mattermost-server/v5/utils"
    14  )
    15  
    16  type ensureBotOptions struct {
    17  	ProfileImagePath string
    18  	IconImagePath    string
    19  }
    20  
    21  type EnsureBotOption func(*ensureBotOptions)
    22  
    23  func ProfileImagePath(path string) EnsureBotOption {
    24  	return func(args *ensureBotOptions) {
    25  		args.ProfileImagePath = path
    26  	}
    27  }
    28  
    29  func IconImagePath(path string) EnsureBotOption {
    30  	return func(args *ensureBotOptions) {
    31  		args.IconImagePath = path
    32  	}
    33  }
    34  
    35  // EnsureBot implements Helpers.EnsureBot
    36  func (p *HelpersImpl) EnsureBot(bot *model.Bot, options ...EnsureBotOption) (retBotID string, retErr error) {
    37  	err := p.ensureServerVersion("5.10.0")
    38  	if err != nil {
    39  		return "", errors.Wrap(err, "failed to ensure bot")
    40  	}
    41  
    42  	// Default options
    43  	o := &ensureBotOptions{
    44  		ProfileImagePath: "",
    45  		IconImagePath:    "",
    46  	}
    47  
    48  	for _, setter := range options {
    49  		setter(o)
    50  	}
    51  
    52  	botID, err := p.ensureBot(bot)
    53  	if err != nil {
    54  		return "", err
    55  	}
    56  
    57  	err = p.setBotImages(botID, o.ProfileImagePath, o.IconImagePath)
    58  	if err != nil {
    59  		return "", err
    60  	}
    61  	return botID, nil
    62  }
    63  
    64  type ShouldProcessMessageOption func(*shouldProcessMessageOptions)
    65  
    66  type shouldProcessMessageOptions struct {
    67  	AllowSystemMessages bool
    68  	AllowBots           bool
    69  	AllowWebhook        bool
    70  	FilterChannelIDs    []string
    71  	FilterUserIDs       []string
    72  	OnlyBotDMs          bool
    73  }
    74  
    75  // AllowSystemMessages configures a call to ShouldProcessMessage to return true for system messages.
    76  //
    77  // As it is typically desirable only to consume messages from users of the system, ShouldProcessMessage ignores system messages by default.
    78  func AllowSystemMessages() ShouldProcessMessageOption {
    79  	return func(options *shouldProcessMessageOptions) {
    80  		options.AllowSystemMessages = true
    81  	}
    82  }
    83  
    84  // AllowBots configures a call to ShouldProcessMessage to return true for bot posts.
    85  //
    86  // 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.
    87  func AllowBots() ShouldProcessMessageOption {
    88  	return func(options *shouldProcessMessageOptions) {
    89  		options.AllowBots = true
    90  	}
    91  }
    92  
    93  // AllowWebhook configures a call to ShouldProcessMessage to return true for posts from webhook.
    94  //
    95  // As it is typically desirable only to consume messages from human users of the system, ShouldProcessMessage ignores webhook messages by default.
    96  func AllowWebhook() ShouldProcessMessageOption {
    97  	return func(options *shouldProcessMessageOptions) {
    98  		options.AllowWebhook = true
    99  	}
   100  }
   101  
   102  // FilterChannelIDs configures a call to ShouldProcessMessage to return true only for the given channels.
   103  //
   104  // By default, posts from all channels are allowed to be processed.
   105  func FilterChannelIDs(filterChannelIDs []string) ShouldProcessMessageOption {
   106  	return func(options *shouldProcessMessageOptions) {
   107  		options.FilterChannelIDs = filterChannelIDs
   108  	}
   109  }
   110  
   111  // FilterUserIDs configures a call to ShouldProcessMessage to return true only for the given users.
   112  //
   113  // By default, posts from all non-bot users are allowed.
   114  func FilterUserIDs(filterUserIDs []string) ShouldProcessMessageOption {
   115  	return func(options *shouldProcessMessageOptions) {
   116  		options.FilterUserIDs = filterUserIDs
   117  	}
   118  }
   119  
   120  // OnlyBotDMs configures a call to ShouldProcessMessage to return true only for direct messages sent to the bot created by EnsureBot.
   121  //
   122  // By default, posts from all channels are allowed.
   123  func OnlyBotDMs() ShouldProcessMessageOption {
   124  	return func(options *shouldProcessMessageOptions) {
   125  		options.OnlyBotDMs = true
   126  	}
   127  }
   128  
   129  // ShouldProcessMessage implements Helpers.ShouldProcessMessage
   130  func (p *HelpersImpl) ShouldProcessMessage(post *model.Post, options ...ShouldProcessMessageOption) (bool, error) {
   131  	messageProcessOptions := &shouldProcessMessageOptions{}
   132  	for _, option := range options {
   133  		option(messageProcessOptions)
   134  	}
   135  
   136  	botIDBytes, kvGetErr := p.API.KVGet(BotUserKey)
   137  	if kvGetErr != nil {
   138  		return false, errors.Wrap(kvGetErr, "failed to get bot")
   139  	}
   140  
   141  	if botIDBytes != nil {
   142  		if post.UserId == string(botIDBytes) {
   143  			return false, nil
   144  		}
   145  	}
   146  
   147  	if post.IsSystemMessage() && !messageProcessOptions.AllowSystemMessages {
   148  		return false, nil
   149  	}
   150  
   151  	if !messageProcessOptions.AllowWebhook && post.GetProp("from_webhook") == "true" {
   152  		return false, nil
   153  	}
   154  
   155  	if !messageProcessOptions.AllowBots {
   156  		user, appErr := p.API.GetUser(post.UserId)
   157  		if appErr != nil {
   158  			return false, errors.Wrap(appErr, "unable to get user")
   159  		}
   160  
   161  		if user.IsBot {
   162  			return false, nil
   163  		}
   164  	}
   165  
   166  	if len(messageProcessOptions.FilterChannelIDs) != 0 && !utils.StringInSlice(post.ChannelId, messageProcessOptions.FilterChannelIDs) {
   167  		return false, nil
   168  	}
   169  
   170  	if len(messageProcessOptions.FilterUserIDs) != 0 && !utils.StringInSlice(post.UserId, messageProcessOptions.FilterUserIDs) {
   171  		return false, nil
   172  	}
   173  
   174  	if botIDBytes != nil && messageProcessOptions.OnlyBotDMs {
   175  		channel, appErr := p.API.GetChannel(post.ChannelId)
   176  		if appErr != nil {
   177  			return false, errors.Wrap(appErr, "unable to get channel")
   178  		}
   179  
   180  		if !model.IsBotDMChannel(channel, string(botIDBytes)) {
   181  			return false, nil
   182  		}
   183  	}
   184  
   185  	return true, nil
   186  }
   187  
   188  func (p *HelpersImpl) readFile(path string) ([]byte, error) {
   189  	bundlePath, err := p.API.GetBundlePath()
   190  	if err != nil {
   191  		return nil, errors.Wrap(err, "failed to get bundle path")
   192  	}
   193  
   194  	imageBytes, err := ioutil.ReadFile(filepath.Join(bundlePath, path))
   195  	if err != nil {
   196  		return nil, errors.Wrap(err, "failed to read image")
   197  	}
   198  	return imageBytes, nil
   199  }
   200  
   201  func (p *HelpersImpl) ensureBot(bot *model.Bot) (retBotID string, retErr error) {
   202  	// Must provide a bot with a username
   203  	if bot == nil || len(bot.Username) < 1 {
   204  		return "", errors.New("passed a bad bot, nil or no username")
   205  	}
   206  
   207  	// If we fail for any reason, this could be a race between creation of bot and
   208  	// retrieval from another EnsureBot. Just try the basic retrieve existing again.
   209  	defer func() {
   210  		if retBotID == "" || retErr != nil {
   211  			var err error
   212  			var botIDBytes []byte
   213  
   214  			err = utils.ProgressiveRetry(func() error {
   215  				botIDBytes, err = p.API.KVGet(BotUserKey)
   216  				if err != nil {
   217  					return err
   218  				}
   219  				return nil
   220  			})
   221  
   222  			if err == nil && botIDBytes != nil {
   223  				retBotID = string(botIDBytes)
   224  				retErr = nil
   225  			}
   226  		}
   227  	}()
   228  
   229  	botIDBytes, kvGetErr := p.API.KVGet(BotUserKey)
   230  	if kvGetErr != nil {
   231  		return "", errors.Wrap(kvGetErr, "failed to get bot")
   232  	}
   233  
   234  	// If the bot has already been created, use it
   235  	if botIDBytes != nil {
   236  		botID := string(botIDBytes)
   237  
   238  		// ensure existing bot is synced with what is being created
   239  		botPatch := &model.BotPatch{
   240  			Username:    &bot.Username,
   241  			DisplayName: &bot.DisplayName,
   242  			Description: &bot.Description,
   243  		}
   244  
   245  		if _, err := p.API.PatchBot(botID, botPatch); err != nil {
   246  			return "", errors.Wrap(err, "failed to patch bot")
   247  		}
   248  
   249  		return botID, nil
   250  	}
   251  
   252  	// Check for an existing bot user with that username. If one exists, then use that.
   253  	if user, userGetErr := p.API.GetUserByUsername(bot.Username); userGetErr == nil && user != nil {
   254  		if user.IsBot {
   255  			if kvSetErr := p.API.KVSet(BotUserKey, []byte(user.Id)); kvSetErr != nil {
   256  				p.API.LogWarn("Failed to set claimed bot user id.", "userid", user.Id, "err", kvSetErr)
   257  			}
   258  		} else {
   259  			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)
   260  		}
   261  		return user.Id, nil
   262  	}
   263  
   264  	// Create a new bot user for the plugin
   265  	createdBot, createBotErr := p.API.CreateBot(bot)
   266  	if createBotErr != nil {
   267  		return "", errors.Wrap(createBotErr, "failed to create bot")
   268  	}
   269  
   270  	if kvSetErr := p.API.KVSet(BotUserKey, []byte(createdBot.UserId)); kvSetErr != nil {
   271  		p.API.LogWarn("Failed to set created bot user id.", "userid", createdBot.UserId, "err", kvSetErr)
   272  	}
   273  
   274  	return createdBot.UserId, nil
   275  }
   276  
   277  func (p *HelpersImpl) setBotImages(botID, profileImagePath, iconImagePath string) error {
   278  	if profileImagePath != "" {
   279  		imageBytes, err := p.readFile(profileImagePath)
   280  		if err != nil {
   281  			return errors.Wrap(err, "failed to read profile image")
   282  		}
   283  		appErr := p.API.SetProfileImage(botID, imageBytes)
   284  		if appErr != nil {
   285  			return errors.Wrap(appErr, "failed to set profile image")
   286  		}
   287  	}
   288  	if iconImagePath != "" {
   289  		imageBytes, err := p.readFile(iconImagePath)
   290  		if err != nil {
   291  			return errors.Wrap(err, "failed to read icon image")
   292  		}
   293  		appErr := p.API.SetBotIconImage(botID, imageBytes)
   294  		if appErr != nil {
   295  			return errors.Wrap(appErr, "failed to set icon image")
   296  		}
   297  	}
   298  	return nil
   299  }