helpers_bots.go 8.9 KB

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