aboutsummaryrefslogtreecommitdiff
path: root/internal/server
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--internal/server/client.go72
-rw-r--r--internal/server/conn.go26
-rw-r--r--internal/server/display.go83
-rw-r--r--internal/server/event.go133
4 files changed, 188 insertions, 126 deletions
diff --git a/internal/server/client.go b/internal/server/client.go
index 698f7fa..8a23c8b 100644
--- a/internal/server/client.go
+++ b/internal/server/client.go
@@ -2,18 +2,30 @@ package server
import (
"context"
+ "encoding/binary"
"io"
"log/slog"
+ "sync"
- "go.neonxp.ru/qchat/internal/chat"
+ "golang.org/x/crypto/ssh"
"golang.org/x/term"
)
-func (s *Server) serveClient(ctx context.Context, rw io.ReadWriteCloser, user *chat.User) {
- t := term.NewTerminal(rw, "[] ")
- // TODO resize terminal to user size
+func (s *Server) serveClient(
+ ctx context.Context,
+ conn *ssh.ServerConn,
+ channel ssh.Channel,
+ requests <-chan *ssh.Request,
+) {
+ wg := sync.WaitGroup{}
- go func() {
+ identify := conn.Permissions.ExtraData["identify"].(string)
+ user := s.chat.NewUser(conn.User(), identify)
+
+ t := term.NewTerminal(channel, "[] ")
+
+ // Обработка ввода пользователя
+ wg.Go(func() {
for {
select {
case <-ctx.Done():
@@ -31,6 +43,8 @@ func (s *Server) serveClient(ctx context.Context, rw io.ReadWriteCloser, user *c
slog.Error("failed read line", slog.Any("err", err))
}
+ conn.Close()
+
return
}
if len(line) == 0 {
@@ -39,20 +53,42 @@ func (s *Server) serveClient(ctx context.Context, rw io.ReadWriteCloser, user *c
s.chat.Input(ctx, user, line)
}
- }()
+ })
- for message := range user.Events {
- switch message := message.(type) {
- case chat.Message:
- displayMessage(t, message, user)
- case chat.SelfMessage:
- displaySelfMessage(t, message)
- case chat.SystemMessage:
- displaySystemMessage(t, message)
- case chat.UserJoined:
- displayUserJoined(t, message)
- case chat.UserLeft:
- displayUserLeft(t, message)
+ wg.Go(func() {
+ for req := range requests {
+ switch req.Type {
+ case "pty-req":
+ termLen := req.Payload[3]
+ w, h := parseDims(req.Payload[termLen+4:])
+ t.SetSize(w, h)
+ req.Reply(true, nil)
+ case "window-change":
+ w, h := parseDims(req.Payload)
+ t.SetSize(w, h)
+ req.Reply(true, nil)
+ case "shell":
+ req.Reply(len(req.Payload) == 0, nil)
+ default:
+ req.Reply(false, nil)
+ }
+ slog.Debug(
+ "req",
+ slog.String("type", req.Type),
+ slog.Bool("want-reply", req.WantReply),
+ slog.String("payload", string(req.Payload)),
+ )
}
+ })
+
+ for message := range user.Events {
+ processUserEvent(message, t, user)
}
}
+
+func parseDims(b []byte) (int, int) {
+ w := binary.BigEndian.Uint32(b)
+ h := binary.BigEndian.Uint32(b[4:])
+
+ return int(w), int(h)
+}
diff --git a/internal/server/conn.go b/internal/server/conn.go
index 1069b0b..347b4eb 100644
--- a/internal/server/conn.go
+++ b/internal/server/conn.go
@@ -35,31 +35,7 @@ func (s *Server) serveConn(ctx context.Context, nConn net.Conn, config *ssh.Serv
}
wg.Go(func() {
- for req := range requests {
- switch req.Type {
- case "pty-req":
- req.Reply(true, nil)
- case "shell":
- req.Reply(true, nil)
- default:
- req.Reply(false, nil)
- }
- slog.Debug(
- "req",
- slog.String("type", req.Type),
- slog.Bool("want-reply", req.WantReply),
- slog.String("payload", string(req.Payload)),
- )
- }
- })
-
- wg.Go(func() {
- identify := conn.Permissions.ExtraData["identify"].(string)
- user := s.chat.NewUser(conn.User(), identify)
- slog.Info("joined", slog.String("user", user.NUsername()))
- s.serveClient(ctx, channel, user)
- slog.Info("disconnected", slog.String("user", user.NUsername()))
- conn.Close()
+ s.serveClient(ctx, conn, channel, requests)
})
}
diff --git a/internal/server/display.go b/internal/server/display.go
deleted file mode 100644
index c3450ad..0000000
--- a/internal/server/display.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package server
-
-import (
- "fmt"
- "io"
- "regexp"
- "strings"
-
- "go.neonxp.ru/qchat/internal/chat"
-)
-
-var replacements = []replacement{
- {
- From: *regexp.MustCompile(`\*(.+?)\*`),
- To: fmt.Sprintf("%s$1%s", escapeCodes[Bold], escapeCodes[Reset]),
- },
- {
- From: *regexp.MustCompile(`_(.+?)_`),
- To: fmt.Sprintf("%s$1%s", escapeCodes[Italic], escapeCodes[Reset]),
- },
-}
-
-func displayMessage(t io.Writer, message chat.Message, user *chat.User) {
- msg := message.Message
- mentionFrom := "@" + user.Username
- mentionTo := string(escapeCodes[Blink]) + string(escapeCodes[Underline]) + "@" + user.Username + string(escapeCodes[Reset])
- if strings.Contains(msg, mentionFrom) {
- msg = strings.ReplaceAll(msg, mentionFrom, mentionTo)
- msg = "\x07\a" + msg
- }
-
- for _, r := range replacements {
- msg = r.From.ReplaceAllString(msg, r.To)
- }
-
- fmt.Fprintf(t, "%s%15s%s [%s]%s: %s\n",
- escapeCodes[Blue],
- message.User.NUsername(),
- escapeCodes[Green],
- message.Time.Format("15:04:05"),
- escapeCodes[Reset],
- msg,
- )
-}
-
-func displaySystemMessage(t io.Writer, message chat.SystemMessage) {
- fmt.Fprintf(t, "%s* %s %s\n",
- escapeCodes[Green],
- message.Message,
- escapeCodes[Reset],
- )
-}
-func displaySelfMessage(t io.Writer, message chat.SelfMessage) {
- fmt.Fprintf(t, "%s* %s %s%s\n",
- escapeCodes[Blue],
- message.User.NUsername(),
- message.Message,
- escapeCodes[Reset],
- )
-}
-
-func displayUserJoined(t io.Writer, presence chat.UserJoined) {
- fmt.Fprintf(t, "%s* %s joined to %s chan%s\n",
- escapeCodes[Green],
- presence.User.NUsername(),
- presence.Chan.Name,
- escapeCodes[Reset],
- )
-}
-
-func displayUserLeft(t io.Writer, presence chat.UserLeft) {
- fmt.Fprintf(t, "%s* %s left %s chan%s\n",
- escapeCodes[Red],
- presence.User.NUsername(),
- presence.Chan.Name,
- escapeCodes[Reset],
- )
-}
-
-type replacement struct {
- From regexp.Regexp
- To string
-}
diff --git a/internal/server/event.go b/internal/server/event.go
new file mode 100644
index 0000000..d006d22
--- /dev/null
+++ b/internal/server/event.go
@@ -0,0 +1,133 @@
+package server
+
+import (
+ "fmt"
+ "io"
+ "regexp"
+ "strings"
+
+ "go.neonxp.ru/qchat/internal/chat"
+)
+
+var replacements = []replacement{
+ {
+ From: *regexp.MustCompile(`\*(.+?)\*`),
+ To: fmt.Sprintf("%s$1%s", escapeCodes[Bold], escapeCodes[Reset]),
+ },
+ {
+ From: *regexp.MustCompile(`\+(.+?)\+`),
+ To: fmt.Sprintf("%s$1%s", escapeCodes[Italic], escapeCodes[Reset]),
+ },
+ {
+ From: *regexp.MustCompile(`\-(.+?)\-`),
+ To: fmt.Sprintf("%s$1%s", escapeCodes[Strike], escapeCodes[Reset]),
+ },
+ {
+ From: *regexp.MustCompile(`_(.+?)_`),
+ To: fmt.Sprintf("%s$1%s", escapeCodes[Underline], escapeCodes[Reset]),
+ },
+}
+
+func processUserEvent(message any, w io.Writer, user *chat.User) {
+ switch message := message.(type) {
+ case chat.Message:
+ displayMessage(w, message, user)
+ case chat.SelfMessage:
+ displaySelfMessage(w, message)
+ case chat.SystemMessage:
+ displaySystemMessage(w, message)
+ case chat.UserJoined:
+ displayUserJoined(w, message)
+ case chat.UserLeft:
+ displayUserLeft(w, message)
+ case chat.UserLogs:
+ displayUserLogs(w, message)
+ }
+}
+
+func displayMessage(w io.Writer, message chat.Message, user *chat.User) {
+ msg := message.Message
+ mentionFrom := "@" + user.Username
+ mentionTo := string(escapeCodes[Yellow]) + string(escapeCodes[Underline]) + "@" + user.Username + string(escapeCodes[Reset])
+ if strings.Contains(msg, mentionFrom) {
+ msg = strings.ReplaceAll(msg, mentionFrom, mentionTo)
+ msg = "\x07\a" + msg
+ }
+
+ for _, r := range replacements {
+ msg = r.From.ReplaceAllString(msg, r.To)
+ }
+
+ fmt.Fprintf(w, "%s%18s%s [%s]%s: %s\n",
+ escapeCodes[Blue],
+ message.User.NUsername(),
+ escapeCodes[Green],
+ message.Time.Format("15:04:05"),
+ escapeCodes[Reset],
+ msg,
+ )
+}
+
+func displaySystemMessage(w io.Writer, message chat.SystemMessage) {
+ fmt.Fprintf(w, "%s* %s %s\n",
+ escapeCodes[Green],
+ message.Message,
+ escapeCodes[Reset],
+ )
+}
+func displaySelfMessage(w io.Writer, message chat.SelfMessage) {
+ fmt.Fprintf(w, "%s* %s %s%s\n",
+ escapeCodes[Blue],
+ message.User.NUsername(),
+ message.Message,
+ escapeCodes[Reset],
+ )
+}
+
+func displayUserJoined(w io.Writer, presence chat.UserJoined) {
+ fmt.Fprintf(w, "%s* %s joined to %s chan%s\n",
+ escapeCodes[Green],
+ presence.User.NUsername(),
+ presence.Chan.Name,
+ escapeCodes[Reset],
+ )
+}
+
+func displayUserLeft(w io.Writer, presence chat.UserLeft) {
+ fmt.Fprintf(w, "%s* %s left %s chan%s\n",
+ escapeCodes[Red],
+ presence.User.NUsername(),
+ presence.Chan.Name,
+ escapeCodes[Reset],
+ )
+}
+func displayUserLogs(w io.Writer, message chat.UserLogs) {
+ messages := make([]string, 0, len(message.Logs))
+ for _, m := range message.Logs {
+ switch m.Type {
+ case chat.TypeNone:
+ continue
+ case chat.TypeJoined:
+ messages = append(messages, fmt.Sprintf("* %18s [%s]: joined", m.Username, m.Time.Format("15:04:05")))
+ case chat.TypeLeft:
+ messages = append(messages, fmt.Sprintf("* %18s [%s]: left", m.Username, m.Time.Format("15:04:05")))
+ case chat.TypeMessage:
+ messages = append(messages, fmt.Sprintf("%18s [%s]: %s", m.Username, m.Time.Format("15:04:05"), m.Message))
+ }
+ }
+
+ if len(messages) == 0 {
+ return
+ }
+
+ fmt.Fprintf(w, "%s* Last channel messages:\n%s%s\n",
+ escapeCodes[Blue],
+ strings.Join(messages, "\n"),
+ escapeCodes[Reset],
+ )
+}
+
+type replacement struct {
+ From regexp.Regexp
+ To string
+}