From d317e8f6df0e0e16445db606da1d683a6b35f531 Mon Sep 17 00:00:00 2001 From: Alexander Neonxp Kiryukhin Date: Tue, 30 Dec 2025 19:33:39 +0300 Subject: =?UTF-8?q?=D0=BD=D0=B0=D1=87=D0=B0=D0=BB=D1=8C=D0=BD=D1=8B=D0=B9?= =?UTF-8?q?=20=D0=BA=D0=BE=D0=BC=D0=BC=D0=B8=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/chat/chat.go | 178 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 internal/chat/chat.go (limited to 'internal/chat/chat.go') diff --git a/internal/chat/chat.go b/internal/chat/chat.go new file mode 100644 index 0000000..4a639ba --- /dev/null +++ b/internal/chat/chat.go @@ -0,0 +1,178 @@ +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) + } +} -- cgit v1.2.3