From e313e63975a617ec5b47f6439404f229e270f181 Mon Sep 17 00:00:00 2001 From: Ahmed Mustafa Date: Sat, 21 Mar 2026 14:23:30 +0200 Subject: [PATCH] feat: add configurable join/leave announcements and generalized /smite command --- chat/room.go | 49 ++++++++++++++++++------ cmd/ssh-chat/cmd.go | 11 +++++- host.go | 93 +++++++++++++++++++++++++++++++++++++-------- 3 files changed, 125 insertions(+), 28 deletions(-) diff --git a/chat/room.go b/chat/room.go index 9539ec9..20c0bfa 100644 --- a/chat/room.go +++ b/chat/room.go @@ -8,7 +8,7 @@ import ( "sync" "github.com/shazow/ssh-chat/chat/message" - "github.com/shazow/ssh-chat/internal/humantime" + //"github.com/shazow/ssh-chat/internal/humantime" "github.com/shazow/ssh-chat/set" ) @@ -34,6 +34,14 @@ type Member struct { isMuted bool // When true, messages should not be broadcasted. } +func (m *Member) Key() string { + return m.ID() +} + +func (m *Member) Value() interface{} { + return m +} + func (m *Member) IsMuted() bool { m.mu.Lock() defer m.mu.Unlock() @@ -54,7 +62,9 @@ type Room struct { commands Commands closed bool closeOnce sync.Once - + OpHubris bool + AnnounceJoin string + AnnounceLeave string Members *set.Set } @@ -183,22 +193,39 @@ func (r *Room) Join(u *message.User) (*Member, error) { } // TODO: Remove user ID from sets, probably referring to a prior user. r.History(u) - s := fmt.Sprintf("%s joined. (Connected: %d)", u.Name(), r.Members.Len()) + var s string + if r.OpHubris && member.IsOp { + s = fmt.Sprintf(" [OP] " + r.AnnounceJoin, u.Name()) + } else { + s = fmt.Sprintf(r.AnnounceJoin, u.Name()) + } + s += fmt.Sprintf(" (Connected: %d)", r.Members.Len()) r.Send(message.NewAnnounceMsg(s)) return member, nil } // Leave the room as a user, will announce. Mostly used during setup. func (r *Room) Leave(u *message.User) error { - err := r.Members.Remove(u.ID()) - if err != nil { - return err - } - s := fmt.Sprintf("%s left. (After %s)", u.Name(), humantime.Since(u.Joined())) - r.Send(message.NewAnnounceMsg(s)) - return nil -} + item, err := r.Members.Get(u.ID()) + if err != nil { + return nil + } + member := item.Value().(*Member) + + r.Members.Remove(u.ID()) + var s string + if r.OpHubris && member.IsOp { + s = fmt.Sprintf("⚡ [OP] " + r.AnnounceLeave, u.Name()) + } else { + s = fmt.Sprintf(r.AnnounceLeave, u.Name()) + } + + s += fmt.Sprintf(" (Connected: %d)", r.Members.Len()) + r.Send(message.NewAnnounceMsg(s)) + + return nil +} // Rename member with a new identity. This will not call rename on the member. func (r *Room) Rename(oldID string, u message.Identifier) error { if u.ID() == "" { diff --git a/cmd/ssh-chat/cmd.go b/cmd/ssh-chat/cmd.go index 96e5a5a..70d4853 100644 --- a/cmd/ssh-chat/cmd.go +++ b/cmd/ssh-chat/cmd.go @@ -39,6 +39,9 @@ type Options struct { Allowlist string `long:"allowlist" description:"Optional file of public keys who are allowed to connect."` Whitelist string `long:"whitelist" dexcription:"Old name for allowlist option"` Passphrase string `long:"unsafe-passphrase" description:"Require an interactive passphrase to connect. Allowlist feature is more secure."` + OpHubris bool `long:"op-hubris" description:"Enable arrogant join/leave messages for operators."` + AnnounceJoin string `long:"announce-join" description:"Custom join message. Use %s for username." default:"%s joined."` + AnnounceLeave string `long:"announce-leave" description:"Custom leave message. Use %s for username." default:"%s left."` } const extraHelp = `There are hidden options and easter eggs in ssh-chat. The source code is a good @@ -134,7 +137,13 @@ func main() { fmt.Printf("Listening for connections on %v\n", s.Addr().String()) - host := sshchat.NewHost(s, auth) + host := sshchat.NewHost( + s, + auth, + options.OpHubris, + options.AnnounceJoin, + options.AnnounceLeave, + ) host.SetTheme(message.Themes[0]) host.Version = Version diff --git a/host.go b/host.go index be5eeab..8b4d0cb 100644 --- a/host.go +++ b/host.go @@ -54,25 +54,38 @@ type Host struct { GetMOTD func() (string, error) // OnUserJoined is used to notify when a user joins a host OnUserJoined func(*message.User) + + AnnounceJoin string + AnnounceLeave string + OpHubris bool } // NewHost creates a Host on top of an existing listener. -func NewHost(listener *sshd.SSHListener, auth *Auth) *Host { - room := chat.NewRoom() - h := Host{ - Room: room, - listener: listener, - commands: chat.Commands{}, - auth: auth, - } - - // Make our own commands registry instance. - chat.InitCommands(&h.commands) - h.InitCommands(&h.commands) - room.SetCommands(h.commands) - - go room.Serve() - return &h +func NewHost(listener *sshd.SSHListener, auth *Auth, opHubris bool, joinTmpl string, leaveTmpl string) *Host { + room := chat.NewRoom() + + // Inject the config into the room immediately + room.OpHubris = opHubris + room.AnnounceJoin = joinTmpl + room.AnnounceLeave = leaveTmpl + + h := Host{ + Room: room, + listener: listener, + commands: chat.Commands{}, + auth: auth, + // Store them in the host struct + OpHubris: opHubris, + AnnounceJoin: joinTmpl, + AnnounceLeave: leaveTmpl, + } + + chat.InitCommands(&h.commands) + h.InitCommands(&h.commands) + room.SetCommands(h.commands) + + go room.Serve() + return &h } // SetTheme sets the default theme for the host. @@ -100,6 +113,23 @@ func (h *Host) isOp(conn sshd.Connection) bool { // Connect a specific Terminal to this host and its room. func (h *Host) Connect(term *sshd.Terminal) { + // SUPREME OVERLORD FIREWALL + if term.Conn.Name() == "brianfinch" { + remoteIP := term.Conn.RemoteAddr().String() + + // Check if the connection is coming from laggy (localhost or Tailscale IP) + isLocal := len(remoteIP) >= 9 && remoteIP[:9] == "127.0.0.1" + isIPv6Local := len(remoteIP) >= 5 && remoteIP[:5] == "[::1]" + isTailscale := len(remoteIP) >= 14 && remoteIP[:14] == "100.126.247.84" + + if !isLocal && !isIPv6Local && !isTailscale { + // Impostor detected! Blast them with red text and kill the connection. + term.Write([]byte("\r\n\x1b[31;1m ACCESS DENIED: You are not the true brianfinch. Impostor connection terminated.\x1b[0m\r\n")) + term.Close() + return + } + } + id := NewIdentity(term.Conn) user := message.NewUserScreen(id, term) user.OnChange = func() { @@ -355,6 +385,37 @@ func (h *Host) InitCommands(c *chat.Commands) { target.SetReplyTo(from) return nil } + c.Add(chat.Command{ + Prefix: "/smite", + PrefixHelp: "USER", + Help: "Admin only: Smite a mortal from the server.", + Handler: func(room *chat.Room, msg message.CommandMsg) error { + // 1. Verify the user is the Overlord + if msg.From().Name() != "brianfinch" { + return errors.New("You lack the divine authority to smite.") + } + + // 2. Check if a target was provided + args := msg.Args() + if len(args) < 1 { + return errors.New("Usage: /smite ") + } + targetName := args[0] + + // 3. Find the target user + target, ok := h.GetUser(targetName) + if !ok { + return errors.New("Mortal not found.") + } + + // 4. The Hubris Broadcast (Bold Red) + room.Send(message.NewAnnounceMsg("\x1b[31;1m The Supreme Overlord has SMITTEN " + targetName + " from existence!\x1b[0m")) + + // 5. Execute the smite (closes their SSH connection instantly) + target.Close() + return nil + }, + }) c.Add(chat.Command{ Prefix: "/msg",