package main import ( "fmt" "net/http" "sync" "time" "github.com/mattermost/mattermost/server/public/model" "github.com/mattermost/mattermost/server/public/plugin" "github.com/mattermost/mattermost/server/public/pluginapi" "github.com/mattermost/mattermost/server/public/pluginapi/cluster" "github.com/pkg/errors" "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" "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" ) // Plugin implements the interface expected by the Mattermost server to communicate between the server and plugin processes. type Plugin struct { plugin.MattermostPlugin // kvstore is the client used to read/write KV records for this plugin. kvstore kvstore.KVStore // client is the Mattermost server API client. client *pluginapi.Client // commandClient is the client used to register and execute slash commands. commandClient command.Command // logger is the main plugin logger logger logger.Logger // 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. configurationLock sync.RWMutex // configuration is the active plugin configuration. Consult getConfiguration and // setConfiguration for usage. configuration *config.Configuration // Bridge manager for managing all bridge instances bridgeManager pluginModel.BridgeManager } // OnActivate is invoked when the plugin is activated. If an error is returned, the plugin will be deactivated. func (p *Plugin) OnActivate() error { p.client = pluginapi.NewClient(p.API, p.Driver) // Initialize the logger using Mattermost Plugin API p.logger = logger.NewPluginAPILogger(p.API) p.kvstore = kvstore.NewKVStore(p.client) // Load configuration directly cfg := p.getConfiguration() // Register the plugin for shared channels if err := p.registerForSharedChannels(); err != nil { return fmt.Errorf("failed to register for shared channels: %w", err) } // Initialize bridge manager p.bridgeManager = bridge.NewBridgeManager(p.logger, p.API, p.remoteID) // Initialize and register bridges with current configuration if err := p.initBridges(cfg); err != nil { return fmt.Errorf("failed to initialize bridges: %w", err) } p.commandClient = command.NewCommandHandler(p.client, p.API, p.bridgeManager) // Start the bridge manager (this starts message routing) if err := p.bridgeManager.Start(); err != nil { return fmt.Errorf("failed to start bridge manager: %w", err) } // Start all bridges for _, bridgeName := range p.bridgeManager.ListBridges() { if err := p.bridgeManager.StartBridge(bridgeName); err != nil { p.logger.LogWarn("Failed to start bridge during activation", "bridge", bridgeName, "error", err) } } job, err := cluster.Schedule( p.API, "BackgroundJob", cluster.MakeWaitForRoundedInterval(1*time.Hour), p.runJob, ) if err != nil { return errors.Wrap(err, "failed to schedule background job") } p.backgroundJob = job return nil } // OnDeactivate is invoked when the plugin is deactivated. func (p *Plugin) OnDeactivate() error { if p.backgroundJob != nil { if err := p.backgroundJob.Close(); err != nil { p.API.LogError("Failed to close background job", "err", err) } } if p.bridgeManager != nil { if err := p.bridgeManager.Shutdown(); err != nil { p.API.LogError("Failed to shutdown bridge manager", "err", err) } } if err := p.API.UnregisterPluginForSharedChannels(manifest.Id); err != nil { p.API.LogError("Failed to unregister plugin for shared channels", "err", err) } return nil } // This will execute the commands that were registered in the NewCommandHandler function. func (p *Plugin) ExecuteCommand(c *plugin.Context, args *model.CommandArgs) (*model.CommandResponse, *model.AppError) { response, err := p.commandClient.Handle(args) if err != nil { return nil, model.NewAppError("ExecuteCommand", "plugin.command.execute_command.app_error", nil, err.Error(), http.StatusInternalServerError) } return response, nil } func (p *Plugin) initBridges(cfg *config.Configuration) error { // Create and register XMPP bridge xmppBridge := xmppbridge.NewBridge( p.logger, p.API, p.kvstore, cfg, "xmpp", p.remoteID, ) 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, p.botUserID, "mattermost", "mattermost", ) 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/