From fdaf24fd0aff9e1089157fd65d81a5aa3fe550bd Mon Sep 17 00:00:00 2001 From: citrons Date: Mon, 2 Jun 2025 16:32:49 -0500 Subject: user statuses --- client/application.go | 18 +++++++------- client/channel_window.go | 3 +-- client/cmd_window.go | 6 ++++- client/command.go | 16 ++++++++++++ client/ui.go | 20 +++++++++++++++ client/user_messages.go | 60 +++++++++++++++++++++++++++++++++++++++++++++ proto/strfail.go | 2 ++ server/channel/command.go | 4 +++ server/user/command.go | 8 ++++++ server/user/user.go | 8 ++++-- server/validate/validate.go | 2 +- 11 files changed, 132 insertions(+), 15 deletions(-) create mode 100644 client/user_messages.go diff --git a/client/application.go b/client/application.go index 5ac3b9d..fb12717 100644 --- a/client/application.go +++ b/client/application.go @@ -64,7 +64,7 @@ func (a *application) OnEvent(cmd proto.Command) { } case "update": if len(cmd.Args) > 0 { - a.cache.Update(cmd.Target, cmd.Args[0]) + a.onUpdate(cmd.Target, cmd.Args[0]) } case "delete": a.cache.Gone(cmd.Target) @@ -80,6 +80,13 @@ func (a *application) OnResponse(requestId string, cmd proto.Command) { } } +func (a *application) onUpdate(target string, update proto.Object) { + if target == a.uid { + a.logUserUpdate(target, update) + } + a.cache.Update(target, update) +} + func (a *application) Sub(id string) { a.Request(proto.NewCmd("s", id), func(cmd proto.Command) { if cmd.Kind == "i" && len(cmd.Args) > 0 { @@ -126,12 +133,7 @@ func (a *application) auth(name string, authCallback func(success bool)) { } func (a *application) sendUpdate(o proto.Object, cb func(proto.Command)) { - a.Request(proto.NewCmd("update", o.Id, o), func(response proto.Command) { - if response.Kind == "ok" { - a.cache.Update(o.Id, o) - } - cb(response) - }) + a.Request(proto.NewCmd("update", o.Id, o), cb) } func (a *application) lookup( @@ -212,8 +214,6 @@ func (a *application) setNick(newName string) { if len(response.Args) != 0 { a.cmdWindow.err(proto.Strfail(response.Args[0])) } - case "ok": - a.cmdWindow.info("your name is: %s", newName) } } a.sendUpdate( diff --git a/client/channel_window.go b/client/channel_window.go index b516775..d8f4c2c 100644 --- a/client/channel_window.go +++ b/client/channel_window.go @@ -98,8 +98,7 @@ func (cw *channelWindow) Send(text string) { } case "fail": if len(response.Args) > 0 { - f := proto.Fail(response.Args[0]) - globalApp.cmdWindow.err(f.Error()) + globalApp.cmdWindow.fail(response.Args[0]) } } } diff --git a/client/cmd_window.go b/client/cmd_window.go index fe6e60a..c423b87 100644 --- a/client/cmd_window.go +++ b/client/cmd_window.go @@ -1,9 +1,9 @@ package main import ( - "citrons.xyz/talk/client/buffer" "citrons.xyz/talk/client/window" "citrons.xyz/talk/tui" + "citrons.xyz/talk/proto" "fmt" ) @@ -119,6 +119,10 @@ func (w *cmdWindow) err(f string, a ...any) { w.Buf.Add(logMsg {lastIndex, fmt.Sprintf(f, a...), logErr}) } +func (w *cmdWindow) fail(o proto.Object) { + w.err(proto.Strfail(o)) +} + func (w *cmdWindow) cmd(f string, a ...any) { lastIndex++ w.Buf.Add(logMsg {lastIndex, fmt.Sprintf(f, a...), logCmd}) diff --git a/client/command.go b/client/command.go index 2c146e9..3122dc6 100644 --- a/client/command.go +++ b/client/command.go @@ -1,5 +1,9 @@ package main +import ( + "citrons.xyz/talk/proto" +) + func isCommand(text string) (bool, string) { if text[0] == '/' { if len(text) > 1 && text[1] == '/' { @@ -93,6 +97,18 @@ func (a *application) doCommand(command string, args []string, text string) { a.cmdWindow.Buffer().Add(msg) }) } + case "status": + if !a.authenticated { + break + } + cb := func(response proto.Command) { + if response.Kind == "fail" && len(response.Args) > 0 { + a.cmdWindow.fail(response.Args[0]) + } + } + a.sendUpdate(proto.Object { + "u", a.uid, map[string]string {"status": text}, + }, cb) case "create": if a.authenticated { a.createChannel(text) diff --git a/client/ui.go b/client/ui.go index 740cc54..f3a79c6 100644 --- a/client/ui.go +++ b/client/ui.go @@ -3,6 +3,7 @@ package main import ( "citrons.xyz/talk/client/window" "citrons.xyz/talk/tui" + "citrons.xyz/talk/proto" "zgo.at/termfo/keys" "os" ) @@ -68,6 +69,25 @@ func (a *application) onInput(ev tui.Event) { } +func (a *application) logUserUpdate(uid string, update proto.Object) { + u := a.cache.Get(uid) + if u == nil { + return + } + switch { + case update.Fields["status"] != "": + lastIndex++ + a.cmdWindow.Buffer().Add(userStatusMsg { + lastIndex, uid, u.Fields[""], update.Fields["status"], + }) + case update.Fields[""] != "": + lastIndex++ + a.cmdWindow.Buffer().Add(nameChangeMsg { + lastIndex, uid, u.Fields[""], update.Fields[""], + }) + } +} + func (a *application) showNickBox() { tui.Push("username", tui.Box {Width: tui.TextSize, Height: tui.TextSize}) tui.Text("[", nil) diff --git a/client/user_messages.go b/client/user_messages.go new file mode 100644 index 0000000..80b8fcc --- /dev/null +++ b/client/user_messages.go @@ -0,0 +1,60 @@ +package main + +import ( + "citrons.xyz/talk/tui" + "fmt" +) + +type nameChangeMsg struct { + index int + uid string + oldName string + newName string +} + +type userStatusMsg struct { + index int + uid string + username string + status string +} + +func (m nameChangeMsg) Id() string { + return fmt.Sprintf("name change.%d", m.index) +} + +func (m userStatusMsg) Id() string { + return fmt.Sprintf("user status.%d", m.index) +} + +func (m nameChangeMsg) Show(odd bool) { + tui.Push("", tui.Box {Width: tui.Fill, Height: tui.TextSize}) + tui.Text("nick: ", &tui.Style { + Fg: tui.BrightBlack, Bg: colorDefault[odd], + }) + tui.Text(m.oldName, &tui.Style { + Fg: tui.White, Bg: colorDefault[odd], Italic: true, + }) + tui.Text(" -> ", &tui.Style { + Fg: tui.BrightBlack, Bg: colorDefault[odd], + }) + tui.Text(m.newName, &tui.Style { + Fg: tui.White, Bg: colorDefault[odd], Bold: true, + }) + tui.Pop() +} + +func (m userStatusMsg) Show(odd bool) { + tui.Push("", tui.Box {Width: tui.Fill, Height: tui.TextSize}) + tui.Text("status: ", &tui.Style { + Fg: tui.BrightBlack, Bg: colorDefault[odd], + }) + tui.Text(m.username, &tui.Style { + Fg: tui.White, Bg: colorDefault[odd], Bold: true, + }) + tui.Text(" : ", &tui.Style { + Fg: tui.BrightBlack, Bg: colorDefault[odd], + }) + tui.Text(m.status, nil) + tui.Pop() +} diff --git a/proto/strfail.go b/proto/strfail.go index 83f314b..eb3d1c6 100644 --- a/proto/strfail.go +++ b/proto/strfail.go @@ -14,6 +14,8 @@ func Strfail(fail Object) string { return "name is in use: " + fail.Fields[""] case "invalid-name": return "name is too long or contains invalid characters: " + fail.Fields[""] + case "too-long": + return "message or status is too long" case "not-in-channel": return "you are not a member of this channel: " + fail.Fields[""] default: diff --git a/server/channel/command.go b/server/channel/command.go index af5ff16..e645055 100644 --- a/server/channel/command.go +++ b/server/channel/command.go @@ -25,6 +25,10 @@ func (c *Channel) SendRequest(r session.Request) { return } } + if len(m.Fields[""]) > 50000 { + r.Reply(proto.Fail{"too-long", "", nil}.Cmd()) + return + } default: r.ReplyInvalid() return diff --git a/server/user/command.go b/server/user/command.go index b14845c..0c4f6d0 100644 --- a/server/user/command.go +++ b/server/user/command.go @@ -23,15 +23,23 @@ func (u *User) SendRequest(r session.Request) { return } name := u.name + status := u.status for k, v := range upd.Fields { switch k { case "": name = v + case "status": + status = v default: r.ReplyInvalid() return } } + if len(status) > 512 { + r.Reply(proto.Fail{"too-long", "", nil}.Cmd()) + return + } + u.status = status if name != u.name { err := u.Rename(name) if err != nil { diff --git a/server/user/user.go b/server/user/user.go index 0a9ae4e..fa5f37b 100644 --- a/server/user/user.go +++ b/server/user/user.go @@ -17,6 +17,8 @@ type User struct { store *UserStore name string id string + status string + description string Stream session.Stream Channels map[string]bool Anonymous bool @@ -98,9 +100,11 @@ func (u *User) Delete() { } func (u *User) GetInfo() proto.Object { - return proto.Object { - "u", u.id, map[string]string {"": u.name}, + i := map[string]string {"": u.name} + if u.status != "" { + i["status"] = u.status } + return proto.Object {"u", u.id, i} } func (t Tombstone) GetInfo() proto.Object { diff --git a/server/validate/validate.go b/server/validate/validate.go index 4415557..9b3ff0d 100644 --- a/server/validate/validate.go +++ b/server/validate/validate.go @@ -6,7 +6,7 @@ import ( ) func Name(name string) bool { - if len(Fold(name)) == 0 || len(name) >= 256 { + if len(Fold(name)) == 0 || len(name) > 256 { return false } for _, r := range name { -- cgit v1.2.3