summaryrefslogtreecommitdiff
path: root/server
diff options
context:
space:
mode:
authorcitrons <citrons@mondecitronne.com>2025-06-07 22:09:09 -0500
committercitrons <citrons@mondecitronne.com>2025-06-07 22:09:09 -0500
commit8a8f1f98859123e162090538cbdbe7f5c9d34ea4 (patch)
tree548d9ef52bfa1579c17ce1ee600ba3356fa1ffbe /server
parentf38d6eb807e2b921123dd5efd0b2d632a632e579 (diff)
direct messages
Diffstat (limited to 'server')
-rw-r--r--server/channel/channel.go57
-rw-r--r--server/server/command.go94
2 files changed, 115 insertions, 36 deletions
diff --git a/server/channel/channel.go b/server/channel/channel.go
index 449375b..056b942 100644
--- a/server/channel/channel.go
+++ b/server/channel/channel.go
@@ -6,11 +6,14 @@ import (
"citrons.xyz/talk/server/session"
"citrons.xyz/talk/server/validate"
"citrons.xyz/talk/server/user"
+ "strings"
+ "sort"
)
type ChannelStore struct {
world *object.World
byName map[string]*Channel
+ directChannels map[string]*Channel
deleted map[string]Tombstone
}
@@ -18,6 +21,7 @@ type Channel struct {
store *ChannelStore
id string
name string
+ isDirect bool
members map[string]Membership
messages []proto.Object
byId map[string]int
@@ -31,7 +35,8 @@ type Tombstone struct {
func NewStore(world *object.World) *ChannelStore {
return &ChannelStore {
- world, make(map[string]*Channel), make(map[string]Tombstone),
+ world, make(map[string]*Channel), make(map[string]*Channel),
+ make(map[string]Tombstone),
}
}
@@ -58,12 +63,44 @@ func (cs *ChannelStore) CreateChannel(name string) (*Channel, *proto.Fail) {
return &c, nil
}
+func (cs *ChannelStore) GetDirect(among []string) *Channel {
+ sort.Strings(among)
+ key := strings.Join(among, "\x00")
+ if cs.directChannels[key] == nil {
+ var c Channel
+ c.isDirect = true
+ c.store = cs
+ c.byId = make(map[string]int)
+ c.defaultMembership = DefaultMembership
+ c.members = make(map[string]Membership)
+ for _, member := range among {
+ c.members[member] = c.defaultMembership
+ }
+
+ cs.directChannels[key] = &c
+ c.id = cs.world.NewObject(&c)
+ }
+ return cs.directChannels[key]
+}
+
func (cs *ChannelStore) ByName(name string) *Channel {
return cs.byName[validate.Fold(name)]
}
func (c *Channel) Name() string {
- return c.name
+ if !c.isDirect {
+ return c.name
+ } else {
+ var members []string
+ for member := range c.members {
+ u := c.store.world.GetObject(member)
+ if u != nil {
+ members = append(members, u.GetInfo().Fields[""])
+ }
+ }
+ sort.Strings(members)
+ return strings.Join(members, ", ")
+ }
}
func (c *Channel) Id() string {
@@ -108,6 +145,9 @@ func (c *Channel) Put(m proto.Object) proto.Object {
}
func (c *Channel) prune() {
+ if c.isDirect {
+ return
+ }
for m, _ := range c.members {
switch c.store.world.GetObject(m).(type) {
case *user.User:
@@ -167,12 +207,19 @@ func (c *Channel) Delete() {
c.store.world.PutObject(c.id, deleted)
}
-func (c *Channel) GetInfo() proto.Object {
- return proto.Object {
- "channel", c.id, map[string]string {"": c.name},
+func (c *Channel) Kind() string {
+ switch {
+ case c.isDirect:
+ return "direct-channel"
+ default:
+ return "channel"
}
}
+func (c *Channel) GetInfo() proto.Object {
+ return proto.Object {c.Kind(), c.id, map[string]string {"": c.Name()}}
+}
+
func (t Tombstone) GetInfo() proto.Object {
return proto.Object {
"gone", "", map[string]string {"": t.name, "kind": "channel"},
diff --git a/server/server/command.go b/server/server/command.go
index 306a445..14f8168 100644
--- a/server/server/command.go
+++ b/server/server/command.go
@@ -39,42 +39,41 @@ func (s *server) SendRequest(r session.Request) {
}
case "lookup":
- if len(r.Cmd.Args) != 1 {
- r.ReplyInvalid()
- return
- }
- o := r.Cmd.Args[0]
- var name string
- for k, v := range o.Fields {
- switch k {
- case "":
- name = v
+ var response []proto.Object
+ for _, o := range r.Cmd.Args {
+ var name string
+ for k, v := range o.Fields {
+ switch k {
+ case "":
+ name = v
+ default:
+ r.ReplyInvalid()
+ return
+ }
+ }
+ var info proto.Object
+ switch o.Kind {
+ case "u":
+ u := s.userStore.ByName(name)
+ if u == nil {
+ r.Reply(proto.Fail{"unknown-name", "", nil}.Cmd())
+ return
+ }
+ info = u.GetInfo()
+ case "channel":
+ c := s.channelStore.ByName(name)
+ if c == nil {
+ r.Reply(proto.Fail{"unknown-name", "", nil}.Cmd())
+ return
+ }
+ info = c.GetInfo()
default:
r.ReplyInvalid()
return
}
+ response = append(response, info)
}
- var info proto.Object
- switch o.Kind {
- case "u":
- u := s.userStore.ByName(name)
- if u == nil {
- r.Reply(proto.Fail{"unknown-name", "", nil}.Cmd())
- return
- }
- info = u.GetInfo()
- case "channel":
- c := s.channelStore.ByName(name)
- if c == nil {
- r.Reply(proto.Fail{"unknown-name", "", nil}.Cmd())
- return
- }
- info = c.GetInfo()
- default:
- r.ReplyInvalid()
- return
- }
- r.Reply(proto.NewCmd("i", "", info))
+ r.Reply(proto.NewCmd("i", "", response...))
case "create":
if r.From.UserId == "" {
@@ -111,6 +110,39 @@ func (s *server) SendRequest(r session.Request) {
r.ReplyInvalid()
}
+ case "direct":
+ if r.From.UserId == "" {
+ r.ReplyInvalid()
+ return
+ }
+ if len(r.Cmd.Args) < 1 {
+ r.ReplyInvalid()
+ return
+ }
+ among := []string {r.From.UserId}
+ duplicate := make(map[string]bool)
+ for _, member := range r.Cmd.Args {
+ if member.Kind != "u" {
+ r.ReplyInvalid()
+ return
+ }
+ if duplicate[member.Fields[""]] {
+ r.ReplyInvalid()
+ return
+ }
+ duplicate[member.Fields[""]] = true
+ u := s.world.GetObject(member.Id)
+ switch u.(type) {
+ case *user.User:
+ default:
+ r.Reply(proto.Fail{"bad-target", "", nil}.Cmd())
+ return
+ }
+ among = append(among, member.Id)
+ }
+ c := s.channelStore.GetDirect(among)
+ r.Reply(proto.NewCmd("direct", "", c.GetInfo()))
+
case "channels":
if r.From.UserId == "" {
r.Reply(proto.NewCmd("channels", "", ))