Full Mattermost server source with integrated Community Enterprise features. Includes vendor directory for offline/air-gapped builds. Structure: - enterprise-impl/: Enterprise feature implementations - enterprise-community/: Init files that register implementations - enterprise/: Bridge imports (community_imports.go) - vendor/: All dependencies for offline builds Build (online): go build ./cmd/mattermost Build (offline/air-gapped): go build -mod=vendor ./cmd/mattermost 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
338 lines
10 KiB
Go
338 lines
10 KiB
Go
package pluginapi
|
|
|
|
import (
|
|
"slices"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
)
|
|
|
|
// PostService exposes methods to manipulate posts.
|
|
type PostService struct {
|
|
api plugin.API
|
|
}
|
|
|
|
// CreatePost creates a post.
|
|
//
|
|
// Minimum server version: 5.2
|
|
func (p *PostService) CreatePost(post *model.Post) error {
|
|
createdPost, appErr := p.api.CreatePost(post)
|
|
if appErr != nil {
|
|
return normalizeAppErr(appErr)
|
|
}
|
|
|
|
err := createdPost.ShallowCopy(post)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// DM sends a post as a direct message
|
|
//
|
|
// Minimum server version: 5.2
|
|
func (p *PostService) DM(senderUserID, receiverUserID string, post *model.Post) error {
|
|
channel, appErr := p.api.GetDirectChannel(senderUserID, receiverUserID)
|
|
if appErr != nil {
|
|
return normalizeAppErr(appErr)
|
|
}
|
|
post.ChannelId = channel.Id
|
|
post.UserId = senderUserID
|
|
return p.CreatePost(post)
|
|
}
|
|
|
|
// GetPost gets a post.
|
|
//
|
|
// Minimum server version: 5.2
|
|
func (p *PostService) GetPost(postID string) (*model.Post, error) {
|
|
post, appErr := p.api.GetPost(postID)
|
|
|
|
return post, normalizeAppErr(appErr)
|
|
}
|
|
|
|
// UpdatePost updates a post.
|
|
//
|
|
// Minimum server version: 5.2
|
|
func (p *PostService) UpdatePost(post *model.Post) error {
|
|
updatedPost, appErr := p.api.UpdatePost(post)
|
|
if appErr != nil {
|
|
return normalizeAppErr(appErr)
|
|
}
|
|
|
|
err := updatedPost.ShallowCopy(post)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// DeletePost deletes a post.
|
|
//
|
|
// Minimum server version: 5.2
|
|
func (p *PostService) DeletePost(postID string) error {
|
|
return normalizeAppErr(p.api.DeletePost(postID))
|
|
}
|
|
|
|
// SendEphemeralPost creates an ephemeral post.
|
|
//
|
|
// Minimum server version: 5.2
|
|
func (p *PostService) SendEphemeralPost(userID string, post *model.Post) {
|
|
*post = *p.api.SendEphemeralPost(userID, post)
|
|
}
|
|
|
|
// UpdateEphemeralPost updates an ephemeral message previously sent to the user.
|
|
// EXPERIMENTAL: This API is experimental and can be changed without advance notice.
|
|
//
|
|
// Minimum server version: 5.2
|
|
func (p *PostService) UpdateEphemeralPost(userID string, post *model.Post) {
|
|
*post = *p.api.UpdateEphemeralPost(userID, post)
|
|
}
|
|
|
|
// DeleteEphemeralPost deletes an ephemeral message previously sent to the user.
|
|
// EXPERIMENTAL: This API is experimental and can be changed without advance notice.
|
|
//
|
|
// Minimum server version: 5.2
|
|
func (p *PostService) DeleteEphemeralPost(userID, postID string) {
|
|
p.api.DeleteEphemeralPost(userID, postID)
|
|
}
|
|
|
|
// GetPostThread gets a post with all the other posts in the same thread.
|
|
//
|
|
// Minimum server version: 5.6
|
|
func (p *PostService) GetPostThread(postID string) (*model.PostList, error) {
|
|
postList, appErr := p.api.GetPostThread(postID)
|
|
|
|
return postList, normalizeAppErr(appErr)
|
|
}
|
|
|
|
// GetPostsSince gets posts created after a specified time as Unix time in milliseconds.
|
|
//
|
|
// Minimum server version: 5.6
|
|
func (p *PostService) GetPostsSince(channelID string, time int64) (*model.PostList, error) {
|
|
postList, appErr := p.api.GetPostsSince(channelID, time)
|
|
|
|
return postList, normalizeAppErr(appErr)
|
|
}
|
|
|
|
// GetPostsAfter gets a page of posts that were posted after the post provided.
|
|
//
|
|
// Minimum server version: 5.6
|
|
func (p *PostService) GetPostsAfter(channelID, postID string, page, perPage int) (*model.PostList, error) {
|
|
postList, appErr := p.api.GetPostsAfter(channelID, postID, page, perPage)
|
|
|
|
return postList, normalizeAppErr(appErr)
|
|
}
|
|
|
|
// GetPostsBefore gets a page of posts that were posted before the post provided.
|
|
//
|
|
// Minimum server version: 5.6
|
|
func (p *PostService) GetPostsBefore(channelID, postID string, page, perPage int) (*model.PostList, error) {
|
|
postList, appErr := p.api.GetPostsBefore(channelID, postID, page, perPage)
|
|
|
|
return postList, normalizeAppErr(appErr)
|
|
}
|
|
|
|
// GetPostsForChannel gets a list of posts for a channel.
|
|
//
|
|
// Minimum server version: 5.6
|
|
func (p *PostService) GetPostsForChannel(channelID string, page, perPage int) (*model.PostList, error) {
|
|
postList, appErr := p.api.GetPostsForChannel(channelID, page, perPage)
|
|
|
|
return postList, normalizeAppErr(appErr)
|
|
}
|
|
|
|
// SearchPostsInTeam returns a list of posts in a specific team that match the given params.
|
|
//
|
|
// Minimum server version: 5.10
|
|
func (p *PostService) SearchPostsInTeam(teamID string, paramsList []*model.SearchParams) ([]*model.Post, error) {
|
|
postList, appErr := p.api.SearchPostsInTeam(teamID, paramsList)
|
|
|
|
return postList, normalizeAppErr(appErr)
|
|
}
|
|
|
|
// AddReaction add a reaction to a post.
|
|
//
|
|
// Minimum server version: 5.3
|
|
func (p *PostService) AddReaction(reaction *model.Reaction) error {
|
|
addedReaction, appErr := p.api.AddReaction(reaction)
|
|
if appErr != nil {
|
|
return normalizeAppErr(appErr)
|
|
}
|
|
|
|
*reaction = *addedReaction
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetReactions get the reactions of a post.
|
|
//
|
|
// Minimum server version: 5.3
|
|
func (p *PostService) GetReactions(postID string) ([]*model.Reaction, error) {
|
|
reactions, appErr := p.api.GetReactions(postID)
|
|
|
|
return reactions, normalizeAppErr(appErr)
|
|
}
|
|
|
|
// RemoveReaction remove a reaction from a post.
|
|
//
|
|
// Minimum server version: 5.3
|
|
func (p *PostService) RemoveReaction(reaction *model.Reaction) error {
|
|
return normalizeAppErr(p.api.RemoveReaction(reaction))
|
|
}
|
|
|
|
type ShouldProcessMessageOption func(*shouldProcessMessageOptions)
|
|
|
|
type shouldProcessMessageOptions struct {
|
|
AllowSystemMessages bool
|
|
AllowBots bool
|
|
AllowWebhook bool
|
|
FilterChannelIDs []string
|
|
FilterUserIDs []string
|
|
OnlyBotDMs bool
|
|
BotID string
|
|
}
|
|
|
|
// 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
|
|
}
|
|
}
|
|
|
|
// If provided, BotID configures ShouldProcessMessage to skip its retrieval from the store.
|
|
//
|
|
// By default, posts from all non-bot users are allowed.
|
|
func BotID(botID string) ShouldProcessMessageOption {
|
|
return func(options *shouldProcessMessageOptions) {
|
|
options.BotID = botID
|
|
}
|
|
}
|
|
|
|
// ShouldProcessMessage returns if the message should be processed by a message hook.
|
|
//
|
|
// Use this method to avoid processing unnecessary messages in a MessageHasBeenPosted
|
|
// or MessageWillBePosted hook, and indeed in some cases avoid an infinite loop between
|
|
// two automated bots or plugins.
|
|
//
|
|
// The behavior is customizable using the given options, since plugin needs may vary.
|
|
// By default, system messages and messages from bots will be skipped.
|
|
//
|
|
// Minimum server version: 5.2
|
|
func (p *PostService) ShouldProcessMessage(post *model.Post, options ...ShouldProcessMessageOption) (bool, error) {
|
|
messageProcessOptions := &shouldProcessMessageOptions{}
|
|
for _, option := range options {
|
|
option(messageProcessOptions)
|
|
}
|
|
|
|
var botIDBytes []byte
|
|
var kvGetErr *model.AppError
|
|
|
|
if messageProcessOptions.BotID != "" {
|
|
botIDBytes = []byte(messageProcessOptions.BotID)
|
|
} else {
|
|
botIDBytes, kvGetErr = p.api.KVGet(botUserKey)
|
|
|
|
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(model.PostPropsFromWebhook) == "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 && !slices.Contains(messageProcessOptions.FilterChannelIDs, post.ChannelId) {
|
|
return false, nil
|
|
}
|
|
|
|
if len(messageProcessOptions.FilterUserIDs) != 0 && !slices.Contains(messageProcessOptions.FilterUserIDs, post.UserId) {
|
|
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
|
|
}
|