Some checks are pending
ci / plugin-ci (push) Waiting to run
- Replace bridgeUser with bridgeClient (*xmppClient.Client) in XMPP bridge - Update createXMPPClient to return XMPP client with TLS configuration - Migrate connection, disconnection, and room operations to use bridgeClient - Update Ping() and RoomExists() methods to use client methods directly - Maintain bridge-agnostic user management system for additional users - Fix formatting and import organization across bridge components 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
300 lines
8.1 KiB
Go
300 lines
8.1 KiB
Go
package mattermost
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/config"
|
|
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/logger"
|
|
"github.com/mattermost/mattermost-plugin-bridge-xmpp/server/model"
|
|
mmModel "github.com/mattermost/mattermost/server/public/model"
|
|
"github.com/mattermost/mattermost/server/public/plugin"
|
|
)
|
|
|
|
// MattermostUser represents a Mattermost user that implements the BridgeUser interface
|
|
type MattermostUser struct {
|
|
// User identity
|
|
id string
|
|
displayName string
|
|
username string
|
|
email string
|
|
|
|
// Mattermost API
|
|
api plugin.API
|
|
|
|
// State management
|
|
state model.UserState
|
|
stateMu sync.RWMutex
|
|
|
|
// Configuration
|
|
config *config.Configuration
|
|
|
|
// Goroutine lifecycle
|
|
ctx context.Context
|
|
cancel context.CancelFunc
|
|
|
|
// Logger
|
|
logger logger.Logger
|
|
}
|
|
|
|
// NewMattermostUser creates a new Mattermost user
|
|
func NewMattermostUser(id, displayName, username, email string, api plugin.API, cfg *config.Configuration, logger logger.Logger) *MattermostUser {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
return &MattermostUser{
|
|
id: id,
|
|
displayName: displayName,
|
|
username: username,
|
|
email: email,
|
|
api: api,
|
|
state: model.UserStateOffline,
|
|
config: cfg,
|
|
ctx: ctx,
|
|
cancel: cancel,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
// Validation
|
|
func (u *MattermostUser) Validate() error {
|
|
if u.id == "" {
|
|
return fmt.Errorf("user ID cannot be empty")
|
|
}
|
|
if u.username == "" {
|
|
return fmt.Errorf("username cannot be empty")
|
|
}
|
|
if u.config == nil {
|
|
return fmt.Errorf("configuration cannot be nil")
|
|
}
|
|
if u.api == nil {
|
|
return fmt.Errorf("Mattermost API cannot be nil")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Identity (bridge-agnostic)
|
|
func (u *MattermostUser) GetID() string {
|
|
return u.id
|
|
}
|
|
|
|
func (u *MattermostUser) GetDisplayName() string {
|
|
return u.displayName
|
|
}
|
|
|
|
// State management
|
|
func (u *MattermostUser) GetState() model.UserState {
|
|
u.stateMu.RLock()
|
|
defer u.stateMu.RUnlock()
|
|
return u.state
|
|
}
|
|
|
|
func (u *MattermostUser) SetState(state model.UserState) error {
|
|
u.stateMu.Lock()
|
|
defer u.stateMu.Unlock()
|
|
|
|
u.logger.LogDebug("Changing Mattermost user state", "user_id", u.id, "old_state", u.state, "new_state", state)
|
|
u.state = state
|
|
|
|
// TODO: Update user status in Mattermost if needed
|
|
// This could involve setting custom status or presence indicators
|
|
|
|
return nil
|
|
}
|
|
|
|
// Channel operations (abstracted from rooms/channels/groups)
|
|
func (u *MattermostUser) JoinChannel(channelID string) error {
|
|
u.logger.LogDebug("Mattermost user joining channel", "user_id", u.id, "channel_id", channelID)
|
|
|
|
// Add user to channel
|
|
_, appErr := u.api.AddUserToChannel(channelID, u.id, "")
|
|
if appErr != nil {
|
|
return fmt.Errorf("failed to add Mattermost user %s to channel %s: %w", u.id, channelID, appErr)
|
|
}
|
|
|
|
u.logger.LogInfo("Mattermost user joined channel", "user_id", u.id, "channel_id", channelID)
|
|
return nil
|
|
}
|
|
|
|
func (u *MattermostUser) LeaveChannel(channelID string) error {
|
|
u.logger.LogDebug("Mattermost user leaving channel", "user_id", u.id, "channel_id", channelID)
|
|
|
|
// Remove user from channel
|
|
appErr := u.api.DeleteChannelMember(channelID, u.id)
|
|
if appErr != nil {
|
|
return fmt.Errorf("failed to remove Mattermost user %s from channel %s: %w", u.id, channelID, appErr)
|
|
}
|
|
|
|
u.logger.LogInfo("Mattermost user left channel", "user_id", u.id, "channel_id", channelID)
|
|
return nil
|
|
}
|
|
|
|
func (u *MattermostUser) SendMessageToChannel(channelID, message string) error {
|
|
u.logger.LogDebug("Mattermost user sending message to channel", "user_id", u.id, "channel_id", channelID)
|
|
|
|
// Create post
|
|
post := &mmModel.Post{
|
|
UserId: u.id,
|
|
ChannelId: channelID,
|
|
Message: message,
|
|
}
|
|
|
|
// Send post
|
|
_, appErr := u.api.CreatePost(post)
|
|
if appErr != nil {
|
|
return fmt.Errorf("failed to send message to Mattermost channel %s: %w", channelID, appErr)
|
|
}
|
|
|
|
u.logger.LogDebug("Mattermost user sent message to channel", "user_id", u.id, "channel_id", channelID)
|
|
return nil
|
|
}
|
|
|
|
// Connection lifecycle
|
|
func (u *MattermostUser) Connect() error {
|
|
u.logger.LogDebug("Connecting Mattermost user", "user_id", u.id, "username", u.username)
|
|
|
|
// For Mattermost users, "connecting" means verifying the user exists and is accessible
|
|
user, appErr := u.api.GetUser(u.id)
|
|
if appErr != nil {
|
|
return fmt.Errorf("failed to verify Mattermost user %s: %w", u.id, appErr)
|
|
}
|
|
|
|
// Update user information if it has changed
|
|
if user.GetDisplayName("") != u.displayName {
|
|
u.displayName = user.GetDisplayName("")
|
|
u.logger.LogDebug("Updated Mattermost user display name", "user_id", u.id, "display_name", u.displayName)
|
|
}
|
|
|
|
u.logger.LogInfo("Mattermost user connected", "user_id", u.id, "username", u.username)
|
|
|
|
// Update state to online
|
|
_ = u.SetState(model.UserStateOnline)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (u *MattermostUser) Disconnect() error {
|
|
u.logger.LogDebug("Disconnecting Mattermost user", "user_id", u.id, "username", u.username)
|
|
|
|
// For Mattermost users, "disconnecting" is mostly a state change
|
|
// The user still exists in Mattermost, but we're not actively managing them
|
|
|
|
_ = u.SetState(model.UserStateOffline)
|
|
|
|
u.logger.LogInfo("Mattermost user disconnected", "user_id", u.id, "username", u.username)
|
|
return nil
|
|
}
|
|
|
|
func (u *MattermostUser) IsConnected() bool {
|
|
return u.GetState() == model.UserStateOnline
|
|
}
|
|
|
|
func (u *MattermostUser) Ping() error {
|
|
if u.api == nil {
|
|
return fmt.Errorf("Mattermost API not initialized for user %s", u.id)
|
|
}
|
|
|
|
// Test API connectivity by getting server version
|
|
version := u.api.GetServerVersion()
|
|
if version == "" {
|
|
return fmt.Errorf("Mattermost API ping returned empty server version for user %s", u.id)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// CheckChannelExists checks if a Mattermost channel exists
|
|
func (u *MattermostUser) CheckChannelExists(channelID string) (bool, error) {
|
|
if u.api == nil {
|
|
return false, fmt.Errorf("Mattermost API not initialized for user %s", u.id)
|
|
}
|
|
|
|
// Try to get the channel by ID
|
|
_, appErr := u.api.GetChannel(channelID)
|
|
if appErr != nil {
|
|
// Check if it's a "not found" error
|
|
if appErr.StatusCode == 404 {
|
|
return false, nil // Channel doesn't exist
|
|
}
|
|
return false, fmt.Errorf("failed to check channel existence: %w", appErr)
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
// Goroutine lifecycle
|
|
func (u *MattermostUser) Start(ctx context.Context) error {
|
|
u.logger.LogDebug("Starting Mattermost user", "user_id", u.id, "username", u.username)
|
|
|
|
// Update context
|
|
u.ctx = ctx
|
|
|
|
// Connect to verify user exists
|
|
if err := u.Connect(); err != nil {
|
|
return fmt.Errorf("failed to start Mattermost user %s: %w", u.id, err)
|
|
}
|
|
|
|
// Start monitoring in a goroutine
|
|
go u.monitor()
|
|
|
|
u.logger.LogInfo("Mattermost user started", "user_id", u.id, "username", u.username)
|
|
return nil
|
|
}
|
|
|
|
func (u *MattermostUser) Stop() error {
|
|
u.logger.LogDebug("Stopping Mattermost user", "user_id", u.id, "username", u.username)
|
|
|
|
// Cancel context to stop goroutines
|
|
if u.cancel != nil {
|
|
u.cancel()
|
|
}
|
|
|
|
// Disconnect
|
|
if err := u.Disconnect(); err != nil {
|
|
u.logger.LogWarn("Error disconnecting Mattermost user during stop", "user_id", u.id, "error", err)
|
|
}
|
|
|
|
u.logger.LogInfo("Mattermost user stopped", "user_id", u.id, "username", u.username)
|
|
return nil
|
|
}
|
|
|
|
// monitor periodically checks the user's status and updates information
|
|
func (u *MattermostUser) monitor() {
|
|
u.logger.LogDebug("Starting monitor for Mattermost user", "user_id", u.id)
|
|
|
|
// Simple monitoring - check user exists periodically
|
|
for {
|
|
select {
|
|
case <-u.ctx.Done():
|
|
u.logger.LogDebug("Monitor stopped for Mattermost user", "user_id", u.id)
|
|
return
|
|
default:
|
|
// Wait before next check
|
|
timeoutCtx, cancel := context.WithTimeout(u.ctx, 60*time.Second)
|
|
select {
|
|
case <-u.ctx.Done():
|
|
cancel()
|
|
return
|
|
case <-timeoutCtx.Done():
|
|
cancel()
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// GetUsername returns the Mattermost username for this user (Mattermost-specific method)
|
|
func (u *MattermostUser) GetUsername() string {
|
|
return u.username
|
|
}
|
|
|
|
// GetEmail returns the Mattermost email for this user (Mattermost-specific method)
|
|
func (u *MattermostUser) GetEmail() string {
|
|
return u.email
|
|
}
|
|
|
|
// GetAPI returns the Mattermost API instance (for advanced operations)
|
|
func (u *MattermostUser) GetAPI() plugin.API {
|
|
return u.api
|
|
}
|