diff --git a/chat/message/user.go b/chat/message/user.go index d1d862f..d2ffb80 100644 --- a/chat/message/user.go +++ b/chat/message/user.go @@ -22,6 +22,7 @@ var ErrUserClosed = errors.New("user closed") // User definition, implemented set Item interface and io.Writer type User struct { Identifier + OnChange func() Ignored set.Interface Focused set.Interface colorIdx int @@ -80,12 +81,20 @@ func (u *User) SetConfig(cfg UserConfig) { u.mu.Lock() u.config = cfg u.mu.Unlock() + + if u.OnChange != nil { + u.OnChange() + } } // Rename the user with a new Identifier. func (u *User) SetID(id string) { u.Identifier.SetID(id) u.setColorIdx(rand.Int()) + + if u.OnChange != nil { + u.OnChange() + } } // ReplyTo returns the last user that messaged this user. @@ -211,7 +220,7 @@ func (u *User) writeMsg(m Message) error { r := u.render(m) _, err := u.screen.Write([]byte(r)) if err != nil { - logger.Printf("Write failed to %s, closing: %s", u.Name(), err) + logger.Printf("Write failed to %s, closing: %s", u.ID(), err) u.Close() } return err @@ -229,7 +238,7 @@ func (u *User) Send(m Message) error { return ErrUserClosed case u.msg <- m: case <-time.After(messageTimeout): - logger.Printf("Message buffer full, closing: %s", u.Name()) + logger.Printf("Message buffer full, closing: %s", u.ID()) u.Close() return ErrUserClosed } diff --git a/chat/room.go b/chat/room.go index 5b34508..b126845 100644 --- a/chat/room.go +++ b/chat/room.go @@ -239,7 +239,7 @@ func (r *Room) NamesPrefix(prefix string) []string { // Pull out names names := make([]string, 0, len(items)) for _, user := range users { - names = append(names, user.Name()) + names = append(names, user.ID()) } return names } diff --git a/host.go b/host.go index 7be6aed..2a67e39 100644 --- a/host.go +++ b/host.go @@ -13,6 +13,7 @@ import ( "github.com/shazow/ssh-chat/chat" "github.com/shazow/ssh-chat/chat/message" "github.com/shazow/ssh-chat/internal/humantime" + "github.com/shazow/ssh-chat/internal/sanitize" "github.com/shazow/ssh-chat/sshd" ) @@ -92,6 +93,10 @@ func (h *Host) isOp(conn sshd.Connection) bool { func (h *Host) Connect(term *sshd.Terminal) { id := NewIdentity(term.Conn) user := message.NewUserScreen(id, term) + user.OnChange = func() { + term.SetPrompt(GetPrompt(user)) + user.SetHighlight(user.ID()) + } cfg := user.Config() apiMode := strings.ToLower(term.Term()) == "bot" @@ -216,17 +221,6 @@ func (h *Host) Connect(term *sshd.Terminal) { // Skip the remaining rendering workarounds continue } - - cmd := m.Command() - if cmd == "/nick" || cmd == "/theme" { - // Hijack /nick command to update terminal synchronously. Wouldn't - // work if we use h.room.Send(m) above. - // - // FIXME: This is hacky, how do we improve the API to allow for - // this? Chat module shouldn't know about terminals. - term.SetPrompt(GetPrompt(user)) - user.SetHighlight(user.Name()) - } } err = h.Leave(user) @@ -294,7 +288,7 @@ func (h *Host) AutoCompleteFunction(u *message.User) func(line string, pos int, if completed == "/reply" { replyTo := u.ReplyTo() if replyTo != nil { - name := replyTo.Name() + name := replyTo.ID() _, found := h.GetUser(name) if found { completed = "/msg " + name @@ -623,4 +617,63 @@ func (h *Host) InitCommands(c *chat.Commands) { return nil }, }) + + c.Add(chat.Command{ + Op: true, + Prefix: "/rename", + PrefixHelp: "USER NEW_NAME [SYMBOL]", + Help: "Rename USER to NEW_NAME, add optional SYMBOL prefix", + Handler: func(room *chat.Room, msg message.CommandMsg) error { + if !room.IsOp(msg.From()) { + return errors.New("must be op") + } + + args := msg.Args() + if len(args) < 2 { + return errors.New("must specify user and new name") + } + + member, ok := room.MemberByID(args[0]) + if !ok { + return errors.New("user not found") + } + + symbolSet := false + if len(args) == 3 { + s := args[2] + if id, ok := member.Identifier.(*Identity); ok { + id.SetSymbol(s) + } else { + return errors.New("user does not support setting symbol") + } + + body := fmt.Sprintf("Assigned symbol %q by %s.", s, msg.From().Name()) + room.Send(message.NewSystemMsg(body, member.User)) + symbolSet = true + } + + oldID := member.ID() + newID := sanitize.Name(args[1]) + if newID == oldID { + return errors.New("new name is the same as the original") + } else if newID == "" && symbolSet { + if member.User.OnChange != nil { + member.User.OnChange() + } + return nil + } + + member.SetID(newID) + err := room.Rename(oldID, member) + if err != nil { + member.SetID(oldID) + return err + } + + body := fmt.Sprintf("%s was renamed by %s.", oldID, msg.From().Name()) + room.Send(message.NewAnnounceMsg(body)) + + return nil + }, + }) } diff --git a/identity.go b/identity.go index 5eb074b..7b3277f 100644 --- a/identity.go +++ b/identity.go @@ -16,6 +16,7 @@ import ( type Identity struct { sshd.Connection id string + symbol string // symbol is displayed as a prefix to the name created time.Time } @@ -43,8 +44,15 @@ func (i *Identity) SetName(name string) { i.SetID(name) } +func (i *Identity) SetSymbol(symbol string) { + i.symbol = symbol +} + // Name returns the name for the Identity func (i Identity) Name() string { + if i.symbol != "" { + return i.symbol + " " + i.id + } return i.id }