tinyrelayfile
tinyrelay/internal/daemon/room_threads.go/

internal/daemon/room_threads.go raw
1package daemon
2
3import (
4 "context"
5 "errors"
6
7 "github.com/FelineStateMachine/tinyrelay/internal/event"
8 "github.com/FelineStateMachine/tinyrelay/internal/storage"
9)
10
11var (
12 errRoomThreadSizeLimit = errors.New("restricted: thread exceeds the browser limit")
13 errRoomThreadDepthLimit = errors.New("restricted: thread exceeds the nesting limit")
14)
15
16func (t *Tenant) roomThreadRoot(ctx context.Context, actor, room, id string) (event.Event, error) {
17 seen := map[string]bool{}
18 for len(seen) < 64 && !seen[id] {
19 seen[id] = true
20 rows, err := t.Query(ctx, []event.Filter{{IDs: []string{id}, Limit: intPtr(1)}}, browseSession(t, actor))
21 if err != nil {
22 return event.Event{}, err
23 }
24 if len(rows) != 1 || event.Tag(rows[0], "h") != room {
25 break
26 }
27 root := rows[0]
28 if root.Kind != 9 && root.Kind != 11 && root.Kind != 12 && root.Kind != 40002 {
29 break
30 }
31 parent := event.RoomReplyRoot(root)
32 if parent == "" {
33 return root, nil
34 }
35 id = parent
36 }
37 return event.Event{}, errors.New("not found: thread")
38}
39
40// Follow indexed references so parent-only Buzz replies join the same thread
41// as marked root/reply pairs. The traversal is bounded against hostile trees.
42func (t *Tenant) roomThreadReplies(ctx context.Context, actor, room, root string) ([]event.Event, error) {
43 known := map[string]bool{root: true}
44 frontier := []string{root}
45 var replies []event.Event
46 for depth := 0; len(frontier) > 0 && depth < 64; depth++ {
47 var nextLevel []string
48 for start := 0; start < len(frontier); start += 100 {
49 end := min(start+100, len(frontier))
50 filter := event.Filter{Kinds: []int{9, 12}, Tags: map[string][]string{"h": {room}, "e": frontier[start:end]}}
51 var cursor *storage.EventCursor
52 for {
53 rows, next, err := t.roomMessages(ctx, actor, filter, cursor, 100)
54 if err != nil {
55 return nil, err
56 }
57 for _, row := range rows {
58 if known[row.ID] || !known[event.RoomReplyRoot(row)] {
59 continue
60 }
61 known[row.ID] = true
62 replies = append(replies, row)
63 nextLevel = append(nextLevel, row.ID)
64 if len(replies) > 5000 {
65 return nil, errRoomThreadSizeLimit
66 }
67 }
68 if next == "" {
69 break
70 }
71 cursor, err = parseCollaborationCursor(next)
72 if err != nil {
73 return nil, err
74 }
75 }
76 }
77 frontier = nextLevel
78 }
79 if len(frontier) > 0 {
80 return nil, errRoomThreadDepthLimit
81 }
82 sortEventsNewestFirst(replies)
83 return replies, nil
84}
85
86func roomThreadPage(rows []event.Event, cursor *storage.EventCursor, limit int) ([]event.Event, string) {
87 page := make([]event.Event, 0, limit)
88 for _, row := range rows {
89 if cursor != nil && (row.CreatedAt > cursor.CreatedAt || row.CreatedAt == cursor.CreatedAt && row.ID <= cursor.ID) {
90 continue
91 }
92 if len(page) == limit {
93 return page, collaborationCursor(collaborationItemFrom(page[len(page)-1]))
94 }
95 page = append(page, row)
96 }
97 return page, ""
98}

About

ownerc01503e8035f
visibilitypublic
headd730679639bf

Maintainers

  • c01503e8035f owner
  • ba1b5beed5b5 agent hermes

Clone

  • https://012.run/npub1cq2s86qrtadf36eqq3epm38wmhdynrrcwxf84eldk3f94dkv7ttsrulrz5/tinyrelay.git

Refs