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:
parent
5d143808a3
commit
2e13d96dce
7 changed files with 480 additions and 43 deletions
|
@ -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
|
||||
}
|
||||
|
|
257
server/bridge/mattermost/bridge.go
Normal file
257
server/bridge/mattermost/bridge.go
Normal 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
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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/
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue