1 // Package reqenvelope is a message from a client to a relay containing a
2 // subscription identifier and an array of filters to search for events.
3 package reqenvelope
4 5 import (
6 "io"
7 8 "next.orly.dev/pkg/nostr/encoders/envelopes"
9 "next.orly.dev/pkg/nostr/encoders/filter"
10 "next.orly.dev/pkg/nostr/encoders/text"
11 "next.orly.dev/pkg/nostr/interfaces/codec"
12 "next.orly.dev/pkg/nostr/utils/constraints"
13 "next.orly.dev/pkg/lol/chk"
14 )
15 16 // L is the label associated with this type of codec.Envelope.
17 const L = "REQ"
18 19 // T is a filter/subscription request envelope that can contain multiple
20 // filters. These prompt the relay to search its event store and return all
21 // events and if the limit is unset or large enough, it will continue to return
22 // newly received events after it returns an eoseenvelope.T.
23 type T struct {
24 Subscription []byte
25 Filters *filter.S
26 }
27 28 var _ codec.Envelope = (*T)(nil)
29 30 // New creates a new reqenvelope.T with a standard subscription.Id and empty
31 // filters.T.
32 func New() *T { return new(T) }
33 34 // NewFrom creates a new reqenvelope.T with a provided subscription.Id and
35 // filters.T.
36 func NewFrom(id []byte, ff *filter.S) *T {
37 return &T{
38 Subscription: id,
39 Filters: ff,
40 }
41 }
42 43 func NewWithId[V constraints.Bytes](id V, ff *filter.S) (sub *T) {
44 return &T{
45 Subscription: []byte(id),
46 Filters: ff,
47 }
48 }
49 50 // Label returns the label of a reqenvelope.T.
51 func (en *T) Label() string { return L }
52 53 // Write the REQ T to a provided io.Writer.
54 func (en *T) Write(w io.Writer) (err error) {
55 _, err = w.Write(en.Marshal(nil))
56 return
57 }
58 59 // Marshal a reqenvelope.T envelope into minified JSON, appending to a provided
60 // destination slice. Note that this ensures correct string escaping on the
61 // subscription.Id field.
62 func (en *T) Marshal(dst []byte) (b []byte) {
63 var err error
64 _ = err
65 b = dst
66 b = envelopes.Marshal(
67 b, L,
68 func(bst []byte) (o []byte) {
69 o = bst
70 o = append(o, '"')
71 o = append(o, en.Subscription...)
72 o = append(o, '"')
73 for _, f := range *en.Filters {
74 o = append(o, ',')
75 o = f.Marshal(o)
76 }
77 return
78 },
79 )
80 return
81 }
82 83 // Unmarshal into a reqenvelope.T from minified JSON, returning the remainder
84 // after the end of the envelope. Note that this ensures the subscription.Id
85 // string is correctly unescaped by NIP-01 escaping rules.
86 func (en *T) Unmarshal(b []byte) (r []byte, err error) {
87 r = b
88 if en.Subscription, r, err = text.UnmarshalQuoted(r); chk.E(err) {
89 return
90 }
91 if r, err = text.Comma(r); chk.E(err) {
92 return
93 }
94 en.Filters = new(filter.S)
95 if r, err = en.Filters.Unmarshal(r); chk.E(err) {
96 return
97 }
98 if r, err = envelopes.SkipToTheEnd(r); chk.E(err) {
99 return
100 }
101 return
102 }
103 104 // Parse reads a REQ envelope from minified JSON into a newly allocated
105 // reqenvelope.T.
106 func (en *T) Parse(b []byte) (t *T, rem []byte, err error) {
107 t = New()
108 if rem, err = t.Unmarshal(b); chk.E(err) {
109 return
110 }
111 return
112 }
113