308 lines
8.3 KiB
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
|
|
}
|