eq2go/internal/chat/manager.go

308 lines
8.3 KiB
Go

package chat
import (
"context"
"fmt"
"strings"
"sync"
"time"
)
// ChatService provides high-level chat system management
type ChatService struct {
manager *ChatManager
mu sync.RWMutex
}
// NewChatService creates a new chat service instance
func NewChatService(database ChannelDatabase, clientManager ClientManager, playerManager PlayerManager, languageProcessor LanguageProcessor) *ChatService {
return &ChatService{
manager: NewChatManager(database, clientManager, playerManager, languageProcessor),
}
}
// Initialize initializes the chat service and loads world channels
func (cs *ChatService) Initialize(ctx context.Context) error {
cs.mu.Lock()
defer cs.mu.Unlock()
return cs.manager.Initialize(ctx)
}
// ProcessChannelCommand processes chat channel commands (join, leave, create, etc.)
func (cs *ChatService) ProcessChannelCommand(characterID int32, command, channelName string, args ...string) error {
cs.mu.RLock()
defer cs.mu.RUnlock()
switch strings.ToLower(command) {
case "join":
password := ""
if len(args) > 0 {
password = args[0]
}
return cs.manager.JoinChannel(characterID, channelName, password)
case "leave":
return cs.manager.LeaveChannel(characterID, channelName)
case "create":
password := ""
if len(args) > 0 {
password = args[0]
}
return cs.manager.CreateChannel(channelName, password)
case "tell", "say":
if len(args) == 0 {
return fmt.Errorf("no message provided")
}
message := strings.Join(args, " ")
return cs.manager.TellChannel(characterID, channelName, message)
case "who", "list":
return cs.manager.SendChannelUserList(characterID, channelName)
default:
return fmt.Errorf("unknown channel command: %s", command)
}
}
// SendChannelMessage sends a message to a channel
func (cs *ChatService) SendChannelMessage(senderID int32, channelName string, message string, customName ...string) error {
cs.mu.RLock()
defer cs.mu.RUnlock()
return cs.manager.TellChannel(senderID, channelName, message, customName...)
}
// JoinChannel adds a character to a channel
func (cs *ChatService) JoinChannel(characterID int32, channelName string, password ...string) error {
cs.mu.RLock()
defer cs.mu.RUnlock()
return cs.manager.JoinChannel(characterID, channelName, password...)
}
// LeaveChannel removes a character from a channel
func (cs *ChatService) LeaveChannel(characterID int32, channelName string) error {
cs.mu.RLock()
defer cs.mu.RUnlock()
return cs.manager.LeaveChannel(characterID, channelName)
}
// LeaveAllChannels removes a character from all channels
func (cs *ChatService) LeaveAllChannels(characterID int32) error {
cs.mu.RLock()
defer cs.mu.RUnlock()
return cs.manager.LeaveAllChannels(characterID)
}
// CreateChannel creates a new custom channel
func (cs *ChatService) CreateChannel(channelName string, password ...string) error {
cs.mu.RLock()
defer cs.mu.RUnlock()
return cs.manager.CreateChannel(channelName, password...)
}
// GetWorldChannelList returns available world channels for a character
func (cs *ChatService) GetWorldChannelList(characterID int32) ([]ChannelInfo, error) {
cs.mu.RLock()
defer cs.mu.RUnlock()
return cs.manager.GetWorldChannelList(characterID)
}
// ChannelExists checks if a channel exists
func (cs *ChatService) ChannelExists(channelName string) bool {
cs.mu.RLock()
defer cs.mu.RUnlock()
return cs.manager.ChannelExists(channelName)
}
// IsInChannel checks if a character is in a channel
func (cs *ChatService) IsInChannel(characterID int32, channelName string) bool {
cs.mu.RLock()
defer cs.mu.RUnlock()
return cs.manager.IsInChannel(characterID, channelName)
}
// GetChannelInfo returns information about a specific channel
func (cs *ChatService) GetChannelInfo(channelName string) (*ChannelInfo, error) {
cs.mu.RLock()
defer cs.mu.RUnlock()
channel := cs.manager.GetChannel(channelName)
if channel == nil {
return nil, fmt.Errorf("channel %s not found", channelName)
}
info := channel.GetChannelInfo()
return &info, nil
}
// GetStatistics returns chat system statistics
func (cs *ChatService) GetStatistics() ChatStatistics {
cs.mu.RLock()
defer cs.mu.RUnlock()
return cs.manager.GetStatistics()
}
// SendChannelUserList sends the user list for a channel to a character
func (cs *ChatService) SendChannelUserList(requesterID int32, channelName string) error {
cs.mu.RLock()
defer cs.mu.RUnlock()
return cs.manager.SendChannelUserList(requesterID, channelName)
}
// ValidateChannelName checks if a channel name is valid
func (cs *ChatService) ValidateChannelName(channelName string) error {
if len(channelName) == 0 {
return fmt.Errorf("channel name cannot be empty")
}
if len(channelName) > MaxChannelNameLength {
return fmt.Errorf("channel name too long: %d > %d", len(channelName), MaxChannelNameLength)
}
// Check for invalid characters
if strings.ContainsAny(channelName, " \t\n\r") {
return fmt.Errorf("channel name cannot contain whitespace")
}
return nil
}
// ValidateChannelPassword checks if a channel password is valid
func (cs *ChatService) ValidateChannelPassword(password string) error {
if len(password) > MaxChannelPasswordLength {
return fmt.Errorf("channel password too long: %d > %d", len(password), MaxChannelPasswordLength)
}
return nil
}
// GetChannelMembers returns the list of members in a channel
func (cs *ChatService) GetChannelMembers(channelName string) ([]int32, error) {
cs.mu.RLock()
defer cs.mu.RUnlock()
channel := cs.manager.GetChannel(channelName)
if channel == nil {
return nil, fmt.Errorf("channel %s not found", channelName)
}
return channel.GetMembers(), nil
}
// CleanupEmptyChannels removes empty custom channels (called periodically)
func (cs *ChatService) CleanupEmptyChannels() int {
cs.mu.Lock()
defer cs.mu.Unlock()
removed := 0
for name, channel := range cs.manager.channels {
if channel.channelType == ChannelTypeCustom && channel.IsEmpty() {
// Check if channel has been empty for a reasonable time
if time.Since(channel.created) > 5*time.Minute {
delete(cs.manager.channels, name)
removed++
}
}
}
return removed
}
// BroadcastSystemMessage sends a system message to all members of a channel
func (cs *ChatService) BroadcastSystemMessage(channelName string, message string, systemName string) error {
cs.mu.RLock()
defer cs.mu.RUnlock()
return cs.manager.TellChannel(0, channelName, message, systemName)
}
// GetActiveChannels returns a list of channels that have active members
func (cs *ChatService) GetActiveChannels() []string {
cs.mu.RLock()
defer cs.mu.RUnlock()
var activeChannels []string
for name, channel := range cs.manager.channels {
if !channel.IsEmpty() {
activeChannels = append(activeChannels, name)
}
}
return activeChannels
}
// GetChannelsByType returns channels of a specific type
func (cs *ChatService) GetChannelsByType(channelType int) []string {
cs.mu.RLock()
defer cs.mu.RUnlock()
var channels []string
for name, channel := range cs.manager.channels {
if channel.channelType == channelType {
channels = append(channels, name)
}
}
return channels
}
// ProcessChannelFilter applies filtering to channel lists based on player criteria
func (cs *ChatService) ProcessChannelFilter(characterID int32, filter ChannelFilter) ([]ChannelInfo, error) {
cs.mu.RLock()
defer cs.mu.RUnlock()
playerInfo, err := cs.manager.playerManager.GetPlayerInfo(characterID)
if err != nil {
return nil, fmt.Errorf("failed to get player info: %w", err)
}
var filteredChannels []ChannelInfo
for _, channel := range cs.manager.channels {
// Apply type filters
if !filter.IncludeWorld && channel.channelType == ChannelTypeWorld {
continue
}
if !filter.IncludeCustom && channel.channelType == ChannelTypeCustom {
continue
}
// Apply level range filters
if filter.MinLevel > 0 && playerInfo.Level < filter.MinLevel {
continue
}
if filter.MaxLevel > 0 && playerInfo.Level > filter.MaxLevel {
continue
}
// Apply race filter
if filter.Race > 0 && playerInfo.Race != filter.Race {
continue
}
// Apply class filter
if filter.Class > 0 && playerInfo.Class != filter.Class {
continue
}
// Check if player can actually join the channel
if cs.manager.canJoinChannel(playerInfo.Level, playerInfo.Race, playerInfo.Class,
channel.levelRestriction, channel.raceRestriction, channel.classRestriction) {
filteredChannels = append(filteredChannels, channel.GetChannelInfo())
}
}
return filteredChannels, nil
}