feat: implement centralized channel mapping management

Adds OnChannelMappingDeleted method to BridgeManager for centralized
cleanup of channel mappings across all bridge types. Updates slash
commands to use centralized management and fixes method naming
inconsistencies.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Felipe M 2025-08-01 18:18:10 +02:00
parent 5d143808a3
commit 2e13d96dce
No known key found for this signature in database
GPG key ID: 52E5D65FCF99808A
7 changed files with 480 additions and 43 deletions

View file

@ -6,7 +6,7 @@
"support_url": "https://github.com/mattermost/mattermost-plugin-bridge-xmpp/issues",
"icon_path": "assets/logo.png",
"version": "",
"min_server_version": "6.2.1",
"min_server_version": "9.5.0",
"server": {
"executables": {
"darwin-amd64": "server/dist/plugin-darwin-amd64",
@ -82,4 +82,4 @@
"version": "v0.1.1"
}
}
}
}

View file

@ -213,4 +213,100 @@ func (m *Manager) OnPluginConfigurationChange(config any) error {
m.logger.LogInfo("Configuration changes propagated to all bridges")
return nil
}
}
// OnChannelMappingCreated handles the creation of a channel mapping by calling the appropriate bridge
func (m *Manager) OnChannelMappingCreated(channelID, bridgeName, bridgeRoomID string) error {
// Input validation
if channelID == "" {
return fmt.Errorf("channelID cannot be empty")
}
if bridgeName == "" {
return fmt.Errorf("bridgeName cannot be empty")
}
if bridgeRoomID == "" {
return fmt.Errorf("bridgeRoomID cannot be empty")
}
m.logger.LogDebug("Creating channel mapping", "channel_id", channelID, "bridge_name", bridgeName, "bridge_room_id", bridgeRoomID)
// Get the specific bridge
bridge, err := m.GetBridge(bridgeName)
if err != nil {
m.logger.LogError("Failed to get bridge", "bridge_name", bridgeName, "error", err)
return fmt.Errorf("failed to get bridge '%s': %w", bridgeName, err)
}
// Check if bridge is connected
if !bridge.IsConnected() {
return fmt.Errorf("bridge '%s' is not connected", bridgeName)
}
// Create the channel mapping on the receiving bridge
if err = bridge.CreateChannelMapping(channelID, bridgeRoomID); err != nil {
m.logger.LogError("Failed to create channel mapping", "channel_id", channelID, "bridge_name", bridgeName, "bridge_room_id", bridgeRoomID, "error", err)
return fmt.Errorf("failed to create channel mapping for bridge '%s': %w", bridgeName, err)
}
mattermostBridge, err := m.GetBridge("mattermost")
if err != nil {
m.logger.LogError("Failed to get Mattermost bridge", "error", err)
return fmt.Errorf("failed to get Mattermost bridge: %w", err)
}
// Create the channel mapping in the Mattermost bridge
if err = mattermostBridge.CreateChannelMapping(channelID, bridgeRoomID); err != nil {
m.logger.LogError("Failed to create channel mapping in Mattermost bridge", "channel_id", channelID, "bridge_name", bridgeName, "bridge_room_id", bridgeRoomID, "error", err)
return fmt.Errorf("failed to create channel mapping in Mattermost bridge: %w", err)
}
m.logger.LogInfo("Successfully created channel mapping", "channel_id", channelID, "bridge_name", bridgeName, "bridge_room_id", bridgeRoomID)
return nil
}
// OnChannelMappingDeleted handles the deletion of a channel mapping by calling the appropriate bridges
func (m *Manager) OnChannelMappingDeleted(channelID, bridgeName string) error {
// Input validation
if channelID == "" {
return fmt.Errorf("channelID cannot be empty")
}
if bridgeName == "" {
return fmt.Errorf("bridgeName cannot be empty")
}
m.logger.LogDebug("Deleting channel mapping", "channel_id", channelID, "bridge_name", bridgeName)
// Get the specific bridge
bridge, err := m.GetBridge(bridgeName)
if err != nil {
m.logger.LogError("Failed to get bridge", "bridge_name", bridgeName, "error", err)
return fmt.Errorf("failed to get bridge '%s': %w", bridgeName, err)
}
// Check if bridge is connected
if !bridge.IsConnected() {
return fmt.Errorf("bridge '%s' is not connected", bridgeName)
}
// Delete the channel mapping from the specific bridge
if err = bridge.DeleteChannelMapping(channelID); err != nil {
m.logger.LogError("Failed to delete channel mapping", "channel_id", channelID, "bridge_name", bridgeName, "error", err)
return fmt.Errorf("failed to delete channel mapping for bridge '%s': %w", bridgeName, err)
}
// Also delete from Mattermost bridge to clean up reverse mappings
mattermostBridge, err := m.GetBridge("mattermost")
if err != nil {
m.logger.LogError("Failed to get Mattermost bridge", "error", err)
return fmt.Errorf("failed to get Mattermost bridge: %w", err)
}
// Delete the channel mapping from the Mattermost bridge
if err = mattermostBridge.DeleteChannelMapping(channelID); err != nil {
m.logger.LogError("Failed to delete channel mapping from Mattermost bridge", "channel_id", channelID, "bridge_name", bridgeName, "error", err)
return fmt.Errorf("failed to delete channel mapping from Mattermost bridge: %w", err)
}
m.logger.LogInfo("Successfully deleted channel mapping", "channel_id", channelID, "bridge_name", bridgeName)
return nil
}

View file

@ -0,0 +1,257 @@
package mattermost
import (
"context"
"fmt"
"sync"
"sync/atomic"
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/config"
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/logger"
pluginModel "github.com/mattermost/mattermost-plugin-bridge-xmpp/server/model"
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/store/kvstore"
"github.com/mattermost/mattermost/server/public/plugin"
)
// mattermostBridge handles syncing messages between Mattermost instances
type mattermostBridge struct {
logger logger.Logger
api plugin.API
kvstore kvstore.KVStore
// Connection management
connected atomic.Bool
ctx context.Context
cancel context.CancelFunc
// Current configuration
config *config.Configuration
configMu sync.RWMutex
// Channel mappings cache
channelMappings map[string]string
mappingsMu sync.RWMutex
}
// NewBridge creates a new Mattermost bridge
func NewBridge(log logger.Logger, api plugin.API, kvstore kvstore.KVStore, cfg *config.Configuration) pluginModel.Bridge {
ctx, cancel := context.WithCancel(context.Background())
bridge := &mattermostBridge{
logger: log,
api: api,
kvstore: kvstore,
ctx: ctx,
cancel: cancel,
channelMappings: make(map[string]string),
config: cfg,
}
return bridge
}
// UpdateConfiguration updates the bridge configuration
func (b *mattermostBridge) UpdateConfiguration(newConfig any) error {
cfg, ok := newConfig.(*config.Configuration)
if !ok {
return fmt.Errorf("invalid configuration type")
}
b.configMu.Lock()
oldConfig := b.config
b.config = cfg
b.configMu.Unlock()
// Log the configuration change
b.logger.LogInfo("Mattermost bridge configuration updated", "old_config", oldConfig, "new_config", cfg)
return nil
}
// Start initializes the bridge
func (b *mattermostBridge) Start() error {
b.logger.LogDebug("Starting Mattermost bridge")
b.configMu.RLock()
config := b.config
b.configMu.RUnlock()
if config == nil {
return fmt.Errorf("bridge configuration not set")
}
// For Mattermost bridge, we're always "connected" since we're running within Mattermost
b.connected.Store(true)
// Load existing channel mappings
if err := b.loadChannelMappings(); err != nil {
b.logger.LogWarn("Failed to load some channel mappings", "error", err)
}
b.logger.LogInfo("Mattermost bridge started successfully")
return nil
}
// Stop shuts down the bridge
func (b *mattermostBridge) Stop() error {
b.logger.LogInfo("Stopping Mattermost bridge")
if b.cancel != nil {
b.cancel()
}
b.connected.Store(false)
b.logger.LogInfo("Mattermost bridge stopped")
return nil
}
// loadChannelMappings loads existing channel mappings from KV store
func (b *mattermostBridge) loadChannelMappings() error {
b.logger.LogDebug("Loading channel mappings for Mattermost bridge")
// Get all channel mappings from KV store for Mattermost bridge
mappings, err := b.getAllChannelMappings()
if err != nil {
return fmt.Errorf("failed to load channel mappings: %w", err)
}
if len(mappings) == 0 {
b.logger.LogInfo("No channel mappings found for Mattermost bridge")
return nil
}
b.logger.LogInfo("Found channel mappings for Mattermost bridge", "count", len(mappings))
// Update local cache
b.mappingsMu.Lock()
for channelID, roomID := range mappings {
b.channelMappings[channelID] = roomID
}
b.mappingsMu.Unlock()
return nil
}
// getAllChannelMappings retrieves all channel mappings from KV store for Mattermost bridge
func (b *mattermostBridge) getAllChannelMappings() (map[string]string, error) {
if b.kvstore == nil {
return nil, fmt.Errorf("KV store not initialized")
}
mappings := make(map[string]string)
// Get all keys with the Mattermost bridge mapping prefix
mattermostPrefix := kvstore.KeyPrefixChannelMap + "mattermost_"
keys, err := b.kvstore.ListKeysWithPrefix(0, 1000, mattermostPrefix)
if err != nil {
return nil, fmt.Errorf("failed to list Mattermost bridge mapping keys: %w", err)
}
// Load each mapping
for _, key := range keys {
channelIDBytes, err := b.kvstore.Get(key)
if err != nil {
b.logger.LogWarn("Failed to load mapping for key", "key", key, "error", err)
continue
}
// Extract room ID from the key
roomID := kvstore.ExtractIdentifierFromChannelMapKey(key, "mattermost")
if roomID == "" {
b.logger.LogWarn("Failed to extract room ID from key", "key", key)
continue
}
channelID := string(channelIDBytes)
mappings[channelID] = roomID
}
return mappings, nil
}
// IsConnected returns whether the bridge is connected
func (b *mattermostBridge) IsConnected() bool {
// Mattermost bridge is always "connected" since it runs within Mattermost
return b.connected.Load()
}
// CreateChannelMapping creates a mapping between a Mattermost channel and another Mattermost room/channel
func (b *mattermostBridge) CreateChannelMapping(channelID, roomID string) error {
if b.kvstore == nil {
return fmt.Errorf("KV store not initialized")
}
// Store forward and reverse mappings using bridge-agnostic keys
err := b.kvstore.Set(kvstore.BuildChannelMapKey("mattermost", channelID), []byte(roomID))
if err != nil {
return fmt.Errorf("failed to store channel room mapping: %w", err)
}
// Update local cache
b.mappingsMu.Lock()
b.channelMappings[channelID] = roomID
b.mappingsMu.Unlock()
b.logger.LogInfo("Created Mattermost channel room mapping", "channel_id", channelID, "room_id", roomID)
return nil
}
// GetChannelMapping gets the room ID for a Mattermost channel
func (b *mattermostBridge) GetChannelMapping(channelID string) (string, error) {
// Check cache first
b.mappingsMu.RLock()
roomID, exists := b.channelMappings[channelID]
b.mappingsMu.RUnlock()
if exists {
return roomID, nil
}
if b.kvstore == nil {
return "", fmt.Errorf("KV store not initialized")
}
// Check if we have a mapping in the KV store for this channel ID
roomIDBytes, err := b.kvstore.Get(kvstore.BuildChannelMapKey("mattermost", channelID))
if err != nil {
return "", nil // Unmapped channels are expected
}
roomID = string(roomIDBytes)
// Update cache
b.mappingsMu.Lock()
b.channelMappings[channelID] = roomID
b.mappingsMu.Unlock()
return roomID, nil
}
// DeleteChannelMapping removes a mapping between a Mattermost channel and room
func (b *mattermostBridge) DeleteChannelMapping(channelID string) error {
if b.kvstore == nil {
return fmt.Errorf("KV store not initialized")
}
// Get the room ID from the mapping before deleting
roomID, err := b.GetChannelMapping(channelID)
if err != nil {
return fmt.Errorf("failed to get channel mapping: %w", err)
}
if roomID == "" {
return fmt.Errorf("channel is not mapped to any room")
}
// Delete forward and reverse mappings from KV store
err = b.kvstore.Delete(kvstore.BuildChannelMapKey("mattermost", channelID))
if err != nil {
return fmt.Errorf("failed to delete channel room mapping: %w", err)
}
// Remove from local cache
b.mappingsMu.Lock()
delete(b.channelMappings, channelID)
b.mappingsMu.Unlock()
b.logger.LogInfo("Deleted Mattermost channel room mapping", "channel_id", channelID, "room_id", roomID)
return nil
}

View file

@ -142,9 +142,6 @@ func (b *xmppBridge) Start() error {
return fmt.Errorf("bridge configuration not set")
}
// Print the configuration for debugging
b.logger.LogDebug("Bridge configuration", "config", config)
if !config.EnableSync {
b.logger.LogInfo("XMPP sync is disabled, bridge will not start")
return nil
@ -378,19 +375,13 @@ func (b *xmppBridge) IsConnected() bool {
return b.connected.Load()
}
// CreateChannelRoomMapping creates a mapping between a Mattermost channel and XMPP room
func (b *xmppBridge) CreateChannelRoomMapping(channelID, roomJID string) error {
// CreateChannelMapping creates a mapping between a Mattermost channel and XMPP room
func (b *xmppBridge) CreateChannelMapping(channelID, roomJID string) error {
if b.kvstore == nil {
return fmt.Errorf("KV store not initialized")
}
// Store forward and reverse mappings using bridge-agnostic keys
err := b.kvstore.Set(kvstore.BuildChannelMapKey("mattermost", channelID), []byte(roomJID))
if err != nil {
return fmt.Errorf("failed to store channel room mapping: %w", err)
}
err = b.kvstore.Set(kvstore.BuildChannelMapKey("xmpp", roomJID), []byte(channelID))
err := b.kvstore.Set(kvstore.BuildChannelMapKey("xmpp", roomJID), []byte(channelID))
if err != nil {
return fmt.Errorf("failed to store reverse room mapping: %w", err)
}
@ -411,8 +402,8 @@ func (b *xmppBridge) CreateChannelRoomMapping(channelID, roomJID string) error {
return nil
}
// GetChannelRoomMapping gets the XMPP room JID for a Mattermost channel
func (b *xmppBridge) GetChannelRoomMapping(channelID string) (string, error) {
// GetChannelMapping gets the XMPP room JID for a Mattermost channel
func (b *xmppBridge) GetChannelMapping(channelID string) (string, error) {
// Check cache first
b.mappingsMu.RLock()
roomJID, exists := b.channelMappings[channelID]
@ -427,7 +418,7 @@ func (b *xmppBridge) GetChannelRoomMapping(channelID string) (string, error) {
}
// Check if we have a mapping in the KV store for this channel ID
roomJIDBytes, err := b.kvstore.Get(kvstore.BuildChannelMapKey("mattermost", channelID))
roomJIDBytes, err := b.kvstore.Get(kvstore.BuildChannelMapKey("xmpp", channelID))
if err != nil {
return "", nil // Unmapped channels are expected
}
@ -442,14 +433,14 @@ func (b *xmppBridge) GetChannelRoomMapping(channelID string) (string, error) {
return roomJID, nil
}
// DeleteChannelRoomMapping removes a mapping between a Mattermost channel and XMPP room
func (b *xmppBridge) DeleteChannelRoomMapping(channelID string) error {
// DeleteChannelMapping removes a mapping between a Mattermost channel and XMPP room
func (b *xmppBridge) DeleteChannelMapping(channelID string) error {
if b.kvstore == nil {
return fmt.Errorf("KV store not initialized")
}
// Get the room JID from the mapping before deleting
roomJID, err := b.GetChannelRoomMapping(channelID)
roomJID, err := b.GetChannelMapping(channelID)
if err != nil {
return fmt.Errorf("failed to get channel mapping: %w", err)
}
@ -457,12 +448,6 @@ func (b *xmppBridge) DeleteChannelRoomMapping(channelID string) error {
return fmt.Errorf("channel is not mapped to any room")
}
// Delete forward and reverse mappings from KV store
err = b.kvstore.Delete(kvstore.BuildChannelMapKey("mattermost", channelID))
if err != nil {
return fmt.Errorf("failed to delete channel room mapping: %w", err)
}
err = b.kvstore.Delete(kvstore.BuildChannelMapKey("xmpp", roomJID))
if err != nil {
return fmt.Errorf("failed to delete reverse room mapping: %w", err)

View file

@ -118,7 +118,7 @@ func (c *Handler) executeMapCommand(args *model.CommandArgs, fields []string) *m
}
}
// Get the XMPP bridge
// Get the XMPP bridge to check existing mappings
bridge, err := c.bridgeManager.GetBridge("xmpp")
if err != nil {
return &model.CommandResponse{
@ -136,7 +136,7 @@ func (c *Handler) executeMapCommand(args *model.CommandArgs, fields []string) *m
}
// Check if channel is already mapped
existingMapping, err := bridge.GetChannelRoomMapping(channelID)
existingMapping, err := bridge.GetChannelMapping(channelID)
if err != nil {
return &model.CommandResponse{
ResponseType: model.CommandResponseTypeEphemeral,
@ -151,8 +151,8 @@ func (c *Handler) executeMapCommand(args *model.CommandArgs, fields []string) *m
}
}
// Create the mapping
err = bridge.CreateChannelRoomMapping(channelID, roomJID)
// Create the mapping using BridgeManager
err = c.bridgeManager.OnChannelMappingCreated(channelID, "xmpp", roomJID)
if err != nil {
return &model.CommandResponse{
ResponseType: model.CommandResponseTypeEphemeral,
@ -169,7 +169,7 @@ func (c *Handler) executeMapCommand(args *model.CommandArgs, fields []string) *m
func (c *Handler) executeUnmapCommand(args *model.CommandArgs) *model.CommandResponse {
channelID := args.ChannelId
// Get the XMPP bridge
// Get the XMPP bridge to check existing mappings
bridge, err := c.bridgeManager.GetBridge("xmpp")
if err != nil {
return &model.CommandResponse{
@ -179,7 +179,7 @@ func (c *Handler) executeUnmapCommand(args *model.CommandArgs) *model.CommandRes
}
// Check if channel is mapped
roomJID, err := bridge.GetChannelRoomMapping(channelID)
roomJID, err := bridge.GetChannelMapping(channelID)
if err != nil {
return &model.CommandResponse{
ResponseType: model.CommandResponseTypeEphemeral,
@ -195,7 +195,7 @@ func (c *Handler) executeUnmapCommand(args *model.CommandArgs) *model.CommandRes
}
// Delete the mapping
err = bridge.DeleteChannelRoomMapping(channelID)
err = c.bridgeManager.OnChannelMappingDeleted(channelID, "xmpp")
if err != nil {
return &model.CommandResponse{
ResponseType: model.CommandResponseTypeEphemeral,
@ -230,7 +230,7 @@ func (c *Handler) executeStatusCommand(args *model.CommandArgs) *model.CommandRe
// Check if current channel is mapped
channelID := args.ChannelId
roomJID, err := bridge.GetChannelRoomMapping(channelID)
roomJID, err := bridge.GetChannelMapping(channelID)
var mappingText string
if err != nil {

View file

@ -1,5 +1,16 @@
package model
type BridgeID string
type UserState int
const (
UserStateOnline UserState = iota
UserStateAway
UserStateBusy
UserStateOffline
)
type BridgeManager interface {
// RegisterBridge registers a bridge with the given name. Returns an error if the name is empty,
// the bridge is nil, or a bridge with the same name is already registered.
@ -39,6 +50,12 @@ type BridgeManager interface {
// Returns an error if any bridge fails to update its configuration, but continues to
// attempt updating all bridges.
OnPluginConfigurationChange(config any) error
// OnChannelMappingCreated is called when a channel mapping is created.
OnChannelMappingCreated(channelID, bridgeName, bridgeRoomID string) error
// OnChannelMappingDeleted is called when a channel mapping is deleted.
OnChannelMappingDeleted(channelID, bridgeName string) error
}
type Bridge interface {
@ -51,15 +68,38 @@ type Bridge interface {
// Stop stops the bridge
Stop() error
// CreateChannelRoomMapping creates a mapping between a Mattermost channel ID and an bridge room ID.
CreateChannelRoomMapping(channelID, roomJID string) error
// CreateChannelMapping creates a mapping between a Mattermost channel ID and an bridge room ID.
CreateChannelMapping(channelID, roomJID string) error
// GetChannelRoomMapping retrieves the bridge room ID for a given Mattermost channel ID.
GetChannelRoomMapping(channelID string) (string, error)
// GetChannelMapping retrieves the bridge room ID for a given Mattermost channel ID.
GetChannelMapping(channelID string) (string, error)
// DeleteChannelRoomMapping removes a mapping between a Mattermost channel ID and a bridge room ID.
DeleteChannelRoomMapping(channelID string) error
// DeleteChannelMapping removes a mapping between a Mattermost channel ID and a bridge room ID.
DeleteChannelMapping(channelID string) error
// IsConnected checks if the bridge is connected to the remote service.
IsConnected() bool
}
type BridgeUserManager interface {
// CreateUser creates a new user in the bridge system.
CreateUser(userID string, userData any) error
// GetUser retrieves user data for a given user ID.
GetUser(userID string) (any, error)
// UpdateUser updates user data for a given user ID.
UpdateUser(userID string, userData any) error
// DeleteUser removes a user from the bridge system.
DeleteUser(userID string) error
// ListUsers returns a list of all users in the bridge system.
ListUsers() ([]string, error)
// HasUser checks if a user exists in the bridge system.
HasUser(userID string) bool
// OnUserStateChange is called when a user's state changes (e.g., online, away, offline).
OnUserStateChange(userID string, state UserState) error
}

View file

@ -7,6 +7,7 @@ import (
"time"
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/bridge"
mattermostbridge "github.com/mattermost/mattermost-plugin-bridge-xmpp/server/bridge/mattermost"
xmppbridge "github.com/mattermost/mattermost-plugin-bridge-xmpp/server/bridge/xmpp"
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/command"
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/config"
@ -43,6 +44,10 @@ type Plugin struct {
// remoteID is the identifier returned by RegisterPluginForSharedChannels
remoteID string
// botUserID is the ID of the bot user created for this plugin
botUserID string
// backgroundJob is the scheduled job that runs periodically to perform background tasks.
backgroundJob *cluster.Job
// configurationLock synchronizes access to the configuration.
@ -71,6 +76,12 @@ func (p *Plugin) OnActivate() error {
cfg := p.getConfiguration()
p.logger.LogDebug("Loaded configuration in OnActivate", "config", cfg)
// Register the plugin for shared channels
if err := p.registerForSharedChannels(); err != nil {
p.logger.LogError("Failed to register for shared channels", "error", err)
return fmt.Errorf("failed to register for shared channels: %w", err)
}
// Initialize bridge manager
p.bridgeManager = bridge.NewManager(p.logger)
@ -118,6 +129,10 @@ func (p *Plugin) OnDeactivate() error {
}
}
if err := p.API.UnregisterPluginForSharedChannels(manifest.Id); err != nil {
p.API.LogError("Failed to unregister plugin for shared channels", "err", err)
}
return nil
}
@ -143,19 +158,63 @@ func (p *Plugin) initXMPPClient() {
func (p *Plugin) initBridges(cfg config.Configuration) error {
// Create and register XMPP bridge
bridge := xmppbridge.NewBridge(
xmppBridge := xmppbridge.NewBridge(
p.logger,
p.API,
p.kvstore,
&cfg,
)
if err := p.bridgeManager.RegisterBridge("xmpp", bridge); err != nil {
if err := p.bridgeManager.RegisterBridge("xmpp", xmppBridge); err != nil {
return fmt.Errorf("failed to register XMPP bridge: %w", err)
}
// Create and register Mattermost bridge
mattermostBridge := mattermostbridge.NewBridge(
p.logger,
p.API,
p.kvstore,
&cfg,
)
if err := p.bridgeManager.RegisterBridge("mattermost", mattermostBridge); err != nil {
return fmt.Errorf("failed to register Mattermost bridge: %w", err)
}
p.logger.LogInfo("Bridge instances created and registered successfully")
return nil
}
func (p *Plugin) registerForSharedChannels() error {
botUserID, err := p.API.EnsureBotUser(&model.Bot{
Username: "mattermost-bridge",
DisplayName: "Mattermost Bridge",
Description: "Mattermost Bridge Bot",
})
if err != nil {
return fmt.Errorf("failed to ensure bot user: %w", err)
}
p.botUserID = botUserID
opts := model.RegisterPluginOpts{
Displayname: "XMPP-Bridge",
PluginID: manifest.Id,
CreatorID: botUserID,
AutoShareDMs: false,
AutoInvited: false,
}
remoteID, appErr := p.API.RegisterPluginForSharedChannels(opts)
if appErr != nil {
return fmt.Errorf("failed to register plugin for shared channels: %w", appErr)
}
// Store the remote ID for use in sync operations
p.remoteID = remoteID
p.logger.LogInfo("Successfully registered plugin for shared channels", "remote_id", remoteID)
return nil
}
// See https://developers.mattermost.com/extend/plugins/server/reference/