package chat import ( "context" "fmt" "strings" "sync" "time" "go.neonxp.ru/qchat/internal/config" ) var ( helpMessage = "Available commands:\n" + "/join [chan] - change current channel to [chan]\n" + "/chans - list all chans\n" + "/users - list all online users\n" + "ctrl+c - leave chat" ) type Chat struct { cfg *config.Config users map[*User]struct{} channels map[*Channel]struct{} wg sync.WaitGroup mu sync.RWMutex } func New(cfg *config.Config) *Chat { return &Chat{ cfg: cfg, users: make(map[*User]struct{}, 32), channels: make(map[*Channel]struct{}, 32), wg: sync.WaitGroup{}, mu: sync.RWMutex{}, } } func (c *Chat) Run(ctx context.Context) error { for _, channel := range c.cfg.Channels { c.NewChannel(ctx, channel.Name) } c.wg.Wait() return nil } func (c *Chat) NewUser(username, identify string) *User { u := &User{ Username: username, Identify: identify, Chans: map[string]*Channel{}, CurrentChan: nil, Events: make(chan any, 32), mu: sync.RWMutex{}, } ch := c.GetChannel("main") if ch != nil { u.JoinChan(ch) } c.users[u] = struct{}{} u.Events <- SystemMessage{ Message: fmt.Sprintf("Connected to %s chat server...", c.cfg.Server.Name), } return u } func (c *Chat) RemoveUser(u *User) { c.mu.Lock() defer c.mu.Unlock() for ch := range c.channels { ch.Leave(u) } close(u.Events) delete(c.users, u) } func (c *Chat) NewChannel(ctx context.Context, name string) *Channel { ch := &Channel{ Name: name, Users: make(map[*User]struct{}, 32), Events: make(chan any), mu: sync.RWMutex{}, } c.mu.Lock() defer c.mu.Unlock() c.channels[ch] = struct{}{} c.wg.Go(func() { ch.Listen(ctx) }) return ch } func (c *Chat) GetChannel(name string) *Channel { for ch := range c.channels { if ch.Name == name { return ch } } return nil } func (c *Chat) Message(user *User, message string) { if user.CurrentChan == nil { return } user.CurrentChan.Events <- Message{ User: user, Message: message, Time: time.Now(), } } func (c *Chat) SelfMessage(user *User, message string) { if user.CurrentChan == nil { return } user.CurrentChan.Events <- SelfMessage{ User: user, Message: message, } } func (c *Chat) SystemMessage(user *User, message string) { user.Events <- SystemMessage{ Message: message, } } func (c *Chat) Input(ctx context.Context, user *User, input string) { cmd := strings.ToLower(input) switch { case strings.HasPrefix(cmd, "/me "): c.SelfMessage(user, strings.TrimPrefix(input, "/me ")) case cmd == "/help": c.SystemMessage(user, helpMessage) case cmd == "/list": list := make([]string, 0, len(c.channels)) for ch := range c.channels { list = append(list, ch.Name) } c.SystemMessage(user, "Chans:\n"+strings.Join(list, "\n")) case cmd == "/users": list := make([]string, 0, len(c.users)) for u := range c.users { list = append(list, u.NUsername()) } c.SystemMessage(user, "Users:\n"+strings.Join(list, "\n")) case strings.HasPrefix(cmd, "/join "): newChanName := strings.TrimPrefix(input, "/join ") var newChan *Channel for ch := range c.channels { if ch.Name == newChanName { newChan = ch break } } if newChan == nil { newChan = c.NewChannel(ctx, newChanName) } user.CurrentChan.Leave(user) user.CurrentChan = newChan newChan.Join(user) default: c.Message(user, input) } }