nostr 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +1 -1
  3. data/.rubocop.yml +23 -0
  4. data/.tool-versions +2 -1
  5. data/CHANGELOG.md +36 -1
  6. data/README.md +92 -228
  7. data/docs/.gitignore +4 -0
  8. data/docs/.vitepress/config.mjs +112 -0
  9. data/docs/README.md +44 -0
  10. data/docs/api-examples.md +49 -0
  11. data/docs/bun.lockb +0 -0
  12. data/docs/common-use-cases/bech32-encoding-and-decoding-(NIP-19).md +190 -0
  13. data/docs/core/client.md +108 -0
  14. data/docs/core/keys.md +136 -0
  15. data/docs/core/user.md +43 -0
  16. data/docs/events/contact-list.md +29 -0
  17. data/docs/events/encrypted-direct-message.md +28 -0
  18. data/docs/events/recommend-server.md +32 -0
  19. data/docs/events/set-metadata.md +20 -0
  20. data/docs/events/text-note.md +15 -0
  21. data/docs/events.md +11 -0
  22. data/docs/getting-started/installation.md +21 -0
  23. data/docs/getting-started/overview.md +170 -0
  24. data/docs/implemented-nips.md +9 -0
  25. data/docs/index.md +44 -0
  26. data/docs/markdown-examples.md +85 -0
  27. data/docs/package.json +12 -0
  28. data/docs/relays/connecting-to-a-relay.md +21 -0
  29. data/docs/relays/publishing-events.md +29 -0
  30. data/docs/relays/receiving-events.md +6 -0
  31. data/docs/subscriptions/creating-a-subscription.md +49 -0
  32. data/docs/subscriptions/deleting-a-subscription.md +10 -0
  33. data/docs/subscriptions/filtering-subscription-events.md +115 -0
  34. data/docs/subscriptions/updating-a-subscription.md +4 -0
  35. data/lib/nostr/bech32.rb +203 -0
  36. data/lib/nostr/client.rb +2 -1
  37. data/lib/nostr/crypto.rb +11 -7
  38. data/lib/nostr/errors/error.rb +7 -0
  39. data/lib/nostr/errors/invalid_hrp_error.rb +21 -0
  40. data/lib/nostr/errors/invalid_key_format_error.rb +20 -0
  41. data/lib/nostr/errors/invalid_key_length_error.rb +20 -0
  42. data/lib/nostr/errors/invalid_key_type_error.rb +18 -0
  43. data/lib/nostr/errors/key_validation_error.rb +6 -0
  44. data/lib/nostr/errors.rb +8 -0
  45. data/lib/nostr/event.rb +3 -4
  46. data/lib/nostr/events/encrypted_direct_message.rb +4 -3
  47. data/lib/nostr/filter.rb +4 -4
  48. data/lib/nostr/key.rb +100 -0
  49. data/lib/nostr/key_pair.rb +30 -6
  50. data/lib/nostr/keygen.rb +43 -4
  51. data/lib/nostr/private_key.rb +36 -0
  52. data/lib/nostr/public_key.rb +36 -0
  53. data/lib/nostr/relay_message_type.rb +18 -0
  54. data/lib/nostr/subscription.rb +2 -2
  55. data/lib/nostr/user.rb +17 -8
  56. data/lib/nostr/version.rb +1 -1
  57. data/lib/nostr.rb +6 -0
  58. data/nostr.gemspec +9 -9
  59. data/sig/nostr/bech32.rbs +14 -0
  60. data/sig/nostr/client.rbs +5 -5
  61. data/sig/nostr/crypto.rbs +5 -5
  62. data/sig/nostr/errors/error.rbs +4 -0
  63. data/sig/nostr/errors/invalid_hrb_error.rbs +6 -0
  64. data/sig/nostr/errors/invalid_key_format_error.rbs +5 -0
  65. data/sig/nostr/errors/invalid_key_length_error.rbs +5 -0
  66. data/sig/nostr/errors/invalid_key_type_error.rbs +5 -0
  67. data/sig/nostr/errors/key_validation_error.rbs +4 -0
  68. data/sig/nostr/event.rbs +4 -4
  69. data/sig/nostr/events/encrypted_direct_message.rbs +2 -2
  70. data/sig/nostr/filter.rbs +3 -12
  71. data/sig/nostr/key.rbs +16 -0
  72. data/sig/nostr/key_pair.rbs +7 -3
  73. data/sig/nostr/keygen.rbs +5 -2
  74. data/sig/nostr/private_key.rbs +4 -0
  75. data/sig/nostr/public_key.rbs +4 -0
  76. data/sig/nostr/relay_message_type.rbs +8 -0
  77. data/sig/nostr/user.rbs +4 -8
  78. data/sig/vendor/bech32/nostr/entity.rbs +41 -0
  79. data/sig/vendor/bech32/nostr/nip19.rbs +20 -0
  80. data/sig/vendor/bech32/segwit_addr.rbs +21 -0
  81. data/sig/vendor/bech32.rbs +25 -0
  82. data/sig/vendor/event_emitter.rbs +10 -3
  83. data/sig/vendor/event_machine/channel.rbs +1 -1
  84. data/sig/vendor/faye/websocket/api.rbs +45 -0
  85. data/sig/vendor/faye/websocket/client.rbs +43 -0
  86. data/sig/vendor/faye/websocket.rbs +30 -0
  87. metadata +79 -21
@@ -0,0 +1,21 @@
1
+ # Installation
2
+
3
+ Install the gem and add to the application's Gemfile by executing:
4
+
5
+ ```shell
6
+ bundle add nostr
7
+ ```
8
+
9
+ If bundler is not being used to manage dependencies, install the gem by executing:
10
+
11
+ ```shell
12
+ gem install nostr
13
+ ```
14
+
15
+ ## Requiring the gem
16
+
17
+ All examples in this guide assume that the gem has been required:
18
+
19
+ ```ruby
20
+ require 'nostr'
21
+ ```
@@ -0,0 +1,170 @@
1
+ ---
2
+ editLink: true
3
+ ---
4
+
5
+ # Getting started
6
+
7
+ This gem abstracts the complexity that you would face when trying to connect to relays web sockets, send and receive
8
+ events, handle events callbacks and much more.
9
+
10
+ ## Visual overview
11
+
12
+ Begin your journey with an overview of the essential functions. A visual representation below maps out the key
13
+ components we'll delve into in this section.
14
+
15
+ ```mermaid
16
+ classDiagram
17
+ class Client {
18
+ connect(relay)
19
+ publish(event)
20
+ subscribe(subscription_id, filter)
21
+ unsubscribe(subscription_id)
22
+ }
23
+ class Relay {
24
+ url
25
+ name
26
+ }
27
+ class Event {
28
+ pubkey
29
+ created_at
30
+ kind
31
+ tags
32
+ content
33
+ add_event_reference(event_id)
34
+ add_pubkey_reference(pubkey)
35
+ serialize()
36
+ to_h()
37
+ sign(private_key)
38
+ }
39
+ class Subscription {
40
+ id
41
+ filter
42
+ }
43
+ class Filter {
44
+ ids
45
+ authors
46
+ kinds
47
+ since
48
+ until
49
+ limit
50
+ to_h()
51
+ }
52
+ class EventKind {
53
+ <<Enumeration>>
54
+ SET_METADATA
55
+ TEXT_NOTE
56
+ RECOMMEND_SERVER
57
+ CONTACT_LIST
58
+ ENCRYPTED_DIRECT_MESSAGE
59
+ }
60
+ class KeyPair {
61
+ private_key
62
+ public_key
63
+ }
64
+ class Keygen {
65
+ generate_key_pair()
66
+ generate_private_key()
67
+ extract_public_key(private_key)
68
+ }
69
+ class User {
70
+ keypair
71
+ create_event(event_attributes)
72
+ }
73
+
74
+ Client --> Relay : connects via <br> WebSockets to
75
+ Client --> Event : uses WebSockets to <br> publish and receive
76
+ Client --> Subscription : receives Events via
77
+ Subscription --> Filter : uses
78
+ Event --> EventKind : is of kind
79
+ User --> KeyPair : has
80
+ User --> Event : creates and signs
81
+ User --> Keygen : uses to generate keys
82
+ Keygen --> KeyPair : generates
83
+ ```
84
+
85
+ ## Code overview
86
+
87
+ Explore the provided code snippet to learn about initializing the Nostr [client](../core/client.md), generating
88
+ a [keypair](../core/keys), [publishing](../relays/publishing-events) an event, and
89
+ efficiently [managing event subscriptions](../subscriptions/creating-a-subscription) (including event reception,
90
+ filtering, and WebSocket event handling).
91
+
92
+ ```ruby
93
+ # Require the gem
94
+ require 'nostr'
95
+
96
+ # Instantiate a client
97
+ client = Nostr::Client.new
98
+
99
+ # a) Use an existing keypair
100
+ keypair = Nostr::KeyPair.new(
101
+ private_key: Nostr::PrivateKey.new('your-hex-private-key'),
102
+ public_key: Nostr::PublicKey.new('your-hex-public-key'),
103
+ )
104
+
105
+ # b) Or build a keypair from a private key
106
+ keygen = Nostr::Keygen.new
107
+ keypair = keygen.get_key_pair_from_private_key(
108
+ Nostr::PrivateKey.new('your-hex-private-key')
109
+ )
110
+
111
+ # c) Or create a new keypair
112
+ keygen = Nostr::Keygen.new
113
+ keypair = keygen.generate_keypair
114
+
115
+ # Create a user with the keypair
116
+ user = Nostr::User.new(keypair: keypair)
117
+
118
+ # Create a signed event
119
+ text_note_event = user.create_event(
120
+ kind: Nostr::EventKind::TEXT_NOTE,
121
+ content: 'Your feedback is appreciated, now pay $8'
122
+ )
123
+
124
+ # Connect asynchronously to a relay
125
+ relay = Nostr::Relay.new(url: 'wss://nostr.wine', name: 'Wine')
126
+ client.connect(relay)
127
+
128
+ # Listen asynchronously for the connect event
129
+ client.on :connect do
130
+ # Send the event to the Relay
131
+ client.publish(text_note_event)
132
+
133
+ # Create a filter to receive the first 20 text notes
134
+ # and encrypted direct messages from the relay that
135
+ # were created in the previous hour
136
+ filter = Nostr::Filter.new(
137
+ kinds: [
138
+ Nostr::EventKind::TEXT_NOTE,
139
+ Nostr::EventKind::ENCRYPTED_DIRECT_MESSAGE
140
+ ],
141
+ since: Time.now.to_i - 3600, # 1 hour ago
142
+ until: Time.now.to_i,
143
+ limit: 20,
144
+ )
145
+
146
+ # Subscribe to events matching conditions of a filter
147
+ subscription = client.subscribe(filter: filter)
148
+
149
+ # Unsubscribe from events matching the filter above
150
+ client.unsubscribe(subscription.id)
151
+ end
152
+
153
+ # Listen for incoming messages and print them
154
+ client.on :message do |message|
155
+ puts message
156
+ end
157
+
158
+ # Listen for error messages
159
+ client.on :error do |error_message|
160
+ # Handle the error
161
+ end
162
+
163
+ # Listen for the close event
164
+ client.on :close do |code, reason|
165
+ # You may attempt to reconnect to the relay here
166
+ end
167
+ ```
168
+
169
+ Beyond what's covered here, the Nostr protocol and this gem boast a wealth of additional functionalities. For an
170
+ in-depth exploration of these capabilities, proceed to the next page.
@@ -0,0 +1,9 @@
1
+ # Implemented NIPs
2
+
3
+ NIPs stand for Nostr Implementation Possibilities. They exist to document what may be implemented by Nostr-compatible
4
+ relay and client software.
5
+
6
+ - [NIP-01: Basic protocol flow description](https://github.com/nostr-protocol/nips/blob/master/01.md)
7
+ - [NIP-02: Contact List and Petnames](https://github.com/nostr-protocol/nips/blob/master/02.md)
8
+ - [NIP-04: Encrypted Direct Message](https://github.com/nostr-protocol/nips/blob/master/04.md)
9
+ - [NIP-19: Bech32-encoded entities](https://github.com/nostr-protocol/nips/blob/master/19.md)
data/docs/index.md ADDED
@@ -0,0 +1,44 @@
1
+ ---
2
+ # https://vitepress.dev/reference/default-theme-home-page
3
+ layout: home
4
+
5
+ hero:
6
+ name: "Nostr"
7
+ text: "Ruby"
8
+ tagline: "The Nostr protocol implemented in a Ruby gem."
9
+ actions:
10
+ - theme: brand
11
+ text: Getting Started
12
+ link: /getting-started/overview
13
+ - theme: alt
14
+ text: View on Github
15
+ link: https://github.com/wilsonsilva/nostr
16
+ - theme: alt
17
+ text: View on RubyDoc
18
+ link: https://www.rubydoc.info/gems/nostr
19
+ - theme: alt
20
+ text: View on RubyGems
21
+ link: https://rubygems.org/gems/nostr
22
+
23
+ features:
24
+ - title: Asynchronous
25
+ details: Non-blocking I/O for maximum performance.
26
+ icon: ⚡
27
+ - title: Lightweight
28
+ details: Minimal runtime dependencies.
29
+ icon: 🪶
30
+ - title: Intuitive
31
+ details: The API mirrors the Nostr protocol specification domain language.
32
+ icon: 💡
33
+ - title: Fully documented
34
+ details: All code is documented from both the maintainer's as well as the consumer's perspective.
35
+ icon: 📚
36
+ - title: Fully tested
37
+ details: All code is tested with 100% coverage.
38
+ icon: 🧪
39
+ - title: Fully typed
40
+ details: All code is typed with <a href="https://rubygems.org/gems/rbs" target="_blank">RBS</a> with the help of <a href="https://rubygems.org/gems/typeprof" target="_blank">TypeProf</a>. Type correctness is enforced by <a href="https://rubygems.org/gems/steep" target="_blank">Steep</a>.
41
+ icon: ✅
42
+
43
+ ---
44
+
@@ -0,0 +1,85 @@
1
+ # Markdown Extension Examples
2
+
3
+ This page demonstrates some of the built-in markdown extensions provided by VitePress.
4
+
5
+ ## Syntax Highlighting
6
+
7
+ VitePress provides Syntax Highlighting powered by [Shiki](https://github.com/shikijs/shiki), with additional features like line-highlighting:
8
+
9
+ **Input**
10
+
11
+ ````
12
+ ```js{4}
13
+ export default {
14
+ data () {
15
+ return {
16
+ msg: 'Highlighted!'
17
+ }
18
+ }
19
+ }
20
+ ```
21
+ ````
22
+
23
+ **Output**
24
+
25
+ ```js{4}
26
+ export default {
27
+ data () {
28
+ return {
29
+ msg: 'Highlighted!'
30
+ }
31
+ }
32
+ }
33
+ ```
34
+
35
+ ## Custom Containers
36
+
37
+ **Input**
38
+
39
+ ```md
40
+ ::: info
41
+ This is an info box.
42
+ :::
43
+
44
+ ::: tip
45
+ This is a tip.
46
+ :::
47
+
48
+ ::: warning
49
+ This is a warning.
50
+ :::
51
+
52
+ ::: danger
53
+ This is a dangerous warning.
54
+ :::
55
+
56
+ ::: details
57
+ This is a details block.
58
+ :::
59
+ ```
60
+
61
+ **Output**
62
+
63
+ ::: info
64
+ This is an info box.
65
+ :::
66
+
67
+ ::: tip
68
+ This is a tip.
69
+ :::
70
+
71
+ ::: warning
72
+ This is a warning.
73
+ :::
74
+
75
+ ::: danger
76
+ This is a dangerous warning.
77
+ :::
78
+
79
+ ::: details
80
+ This is a details block.
81
+ :::
82
+
83
+ ## More
84
+
85
+ Check out the documentation for the [full list of markdown extensions](https://vitepress.dev/guide/markdown).
data/docs/package.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "scripts": {
3
+ "docs:dev": "vitepress dev",
4
+ "docs:build": "vitepress build",
5
+ "docs:preview": "vitepress preview"
6
+ },
7
+ "devDependencies": {
8
+ "mermaid": "^10.6.1",
9
+ "vitepress": "^1.0.0-rc.25",
10
+ "vitepress-plugin-mermaid": "^2.0.15"
11
+ }
12
+ }
@@ -0,0 +1,21 @@
1
+ # Connecting to a Relay
2
+
3
+ You must connect your nostr [Client](../core/client) to a relay in order to send and receive [Events](../events).
4
+ Instantiate a [`Nostr::Client`](https://www.rubydoc.info/gems/nostr/Nostr/Client) and a
5
+ [`Nostr::Relay`](https://www.rubydoc.info/gems/nostr/Nostr/Relay) giving it the `url` of your relay. The `name`
6
+ attribute is just descriptive.
7
+ Calling [`Client#connect`](https://www.rubydoc.info/gems/nostr/Nostr/Client#connect-instance_method) attempts to
8
+ establish a WebSocket connection between the Client and the Relay.
9
+
10
+ ```ruby
11
+ client = Nostr::Client.new
12
+ relay = Nostr::Relay.new(url: 'wss://relay.damus.io', name: 'Damus')
13
+
14
+ # Listen for the connect event
15
+ client.on :connect do
16
+ # When this block executes, you're connected to the relay
17
+ end
18
+
19
+ # Connect to a relay asynchronously
20
+ client.connect(relay)
21
+ ```
@@ -0,0 +1,29 @@
1
+ # Publishing events
2
+
3
+ Create a [signed event](../core/keys) and call the method
4
+ [`Nostr::Client#publish`](https://www.rubydoc.info/gems/nostr/Nostr/Client#publish-instance_method) to send the
5
+ event to the relay.
6
+
7
+ ```ruby{4-8,17}
8
+ # Create a user with the keypair
9
+ user = Nostr::User.new(keypair: keypair)
10
+
11
+ # Create a signed event
12
+ text_note_event = user.create_event(
13
+ kind: Nostr::EventKind::TEXT_NOTE,
14
+ content: 'Your feedback is appreciated, now pay $8'
15
+ )
16
+
17
+ # Connect asynchronously to a relay
18
+ relay = Nostr::Relay.new(url: 'wss://nostr.wine', name: 'Wine')
19
+ client.connect(relay)
20
+
21
+ # Listen asynchronously for the connect event
22
+ client.on :connect do
23
+ # Send the event to the relay
24
+ client.publish(text_note_event)
25
+ end
26
+ ```
27
+
28
+ The relay will verify the signature of the event with the public key. If the signature is valid, the relay should
29
+ broadcast the event to all subscribers.
@@ -0,0 +1,6 @@
1
+ # Receiving events
2
+
3
+ To receive events from Relays, you must create a subscription on the relay. A subscription is a filter that defines the
4
+ events you want to receive.
5
+
6
+ For more information, read the [Subscription](../subscriptions/creating-a-subscription.md) section.
@@ -0,0 +1,49 @@
1
+ # Creating a subscription
2
+
3
+ A client can request events and subscribe to new updates __after__ it has established a connection with the Relay.
4
+
5
+ You may use a [`Nostr::Filter`](https://www.rubydoc.info/gems/nostr/Nostr/Filter) instance with as many attributes as
6
+ you wish:
7
+
8
+ ```ruby
9
+ client.on :connect do
10
+ filter = Nostr::Filter.new(
11
+ ids: ['8535d5e2d7b9dc07567f676fbe70428133c9884857e1915f5b1cc6514c2fdff8'],
12
+ authors: ['ae00f88a885ce76afad5cbb2459ef0dcf0df0907adc6e4dac16e1bfbd7074577'],
13
+ kinds: [Nostr::EventKind::TEXT_NOTE],
14
+ e: ["f111593a72cc52a7f0978de5ecf29b4653d0cf539f1fa50d2168fc1dc8280e52"],
15
+ p: ["f1f9b0996d4ff1bf75e79e4cc8577c89eb633e68415c7faf74cf17a07bf80bd8"],
16
+ since: 1230981305,
17
+ until: 1292190341,
18
+ limit: 420,
19
+ )
20
+
21
+ subscription = client.subscribe(subscription_id: 'an-id', filter: filter)
22
+ end
23
+ ```
24
+
25
+ With just a few:
26
+
27
+ ```ruby
28
+ client.on :connect do
29
+ filter = Nostr::Filter.new(kinds: [Nostr::EventKind::TEXT_NOTE])
30
+ subscription = client.subscribe(subscription_id: 'an-id', filter: filter)
31
+ end
32
+ ```
33
+
34
+ Or omit the filter:
35
+
36
+ ```ruby
37
+ client.on :connect do
38
+ subscription = client.subscribe(subscription_id: 'an-id')
39
+ end
40
+ ```
41
+
42
+ Or even omit the subscription id:
43
+
44
+ ```ruby
45
+ client.on :connect do
46
+ subscription = client.subscribe(filter: filter)
47
+ subscription.id # => "13736f08dee8d7b697222ba605c6fab2" (randomly generated)
48
+ end
49
+ ```
@@ -0,0 +1,10 @@
1
+ # Stop previous subscriptions
2
+
3
+ You can stop receiving messages from a subscription by calling
4
+ [`Nostr::Client#unsubscribe`](https://www.rubydoc.info/gems/nostr/Nostr/Client#unsubscribe-instance_method) with the
5
+ ID of the subscription you want to stop receiving messages from:
6
+
7
+ ```ruby
8
+ client.unsubscribe('your-subscription-id')
9
+ client.unsubscribe(subscription.id)
10
+ ```
@@ -0,0 +1,115 @@
1
+ # Filtering events
2
+
3
+ ## Filtering by id
4
+
5
+ You can filter events by their ids:
6
+
7
+ ```ruby
8
+ filter = Nostr::Filter.new(
9
+ ids: [
10
+ # matches events with these exact IDs
11
+ '8535d5e2d7b9dc07567f676fbe70428133c9884857e1915f5b1cc6514c2fdff8',
12
+ '461544014d87c9eaf3e76e021240007dff2c7afb356319f99c741b45749bf82f',
13
+ ]
14
+ )
15
+ subscription = client.subscribe(filter: filter)
16
+ ```
17
+
18
+ ## Filtering by author
19
+
20
+ You can filter events by their author's pubkey:
21
+
22
+ ```ruby
23
+ filter = Nostr::Filter.new(
24
+ authors: [
25
+ # matches events whose (authors) pubkey match these exact IDs
26
+ 'b698043170d580f8ae5bad4ac80b1fdb508e957f0bbffe97f2a8915fa8b34070',
27
+ '51f853ff4894b062950e46ebed8c1c7015160f8173994414a96dd286f65f0f49',
28
+ ]
29
+ )
30
+ subscription = client.subscribe(filter: filter)
31
+ ```
32
+
33
+ ## Filtering by kind
34
+
35
+ You can filter events by their kind:
36
+
37
+ ```ruby
38
+ filter = Nostr::Filter.new(
39
+ kinds: [
40
+ # matches events whose kind is TEXT_NOTE
41
+ Nostr::EventKind::TEXT_NOTE,
42
+ # and matches events whose kind is CONTACT_LIST
43
+ Nostr::EventKind::CONTACT_LIST,
44
+ ]
45
+ )
46
+ subscription = client.subscribe(filter: filter)
47
+ ```
48
+
49
+ ## Filtering by referenced event
50
+
51
+ You can filter events by the events they reference (in their `e` tag):
52
+
53
+ ```ruby
54
+ filter = Nostr::Filter.new(
55
+ e: [
56
+ # matches events that reference other events whose ids match these exact IDs
57
+ 'f111593a72cc52a7f0978de5ecf29b4653d0cf539f1fa50d2168fc1dc8280e52',
58
+ 'f1f9b0996d4ff1bf75e79e4cc8577c89eb633e68415c7faf74cf17a07bf80bd8',
59
+ ]
60
+ )
61
+ subscription = client.subscribe(filter: filter)
62
+ ```
63
+
64
+ ## Filtering by referenced pubkey
65
+
66
+ You can filter events by the pubkeys they reference (in their `p` tag):
67
+
68
+ ```ruby
69
+ filter = Nostr::Filter.new(
70
+ p: [
71
+ # matches events that reference other pubkeys that match these exact IDs
72
+ 'b698043170d580f8ae5bad4ac80b1fdb508e957f0bbffe97f2a8915fa8b34070',
73
+ '51f853ff4894b062950e46ebed8c1c7015160f8173994414a96dd286f65f0f49',
74
+ ]
75
+ )
76
+ subscription = client.subscribe(filter: filter)
77
+ ```
78
+
79
+ ## Filtering by timestamp
80
+
81
+ You can filter events by their timestamp:
82
+
83
+ ```ruby
84
+ filter = Nostr::Filter.new(
85
+ since: 1230981305, # matches events that are newer than this timestamp
86
+ until: 1292190341, # matches events that are older than this timestamp
87
+ )
88
+ subscription = client.subscribe(filter: filter)
89
+ ```
90
+
91
+ ## Limiting the number of events
92
+
93
+ You can limit the number of events received:
94
+
95
+ ```ruby
96
+ filter = Nostr::Filter.new(
97
+ limit: 420, # matches at most 420 events
98
+ )
99
+ subscription = client.subscribe(filter: filter)
100
+ ```
101
+
102
+ ## Combining filters
103
+
104
+ You can combine filters. For example, to match `5` text note events that are newer than `1230981305` from the author
105
+ `ae00f88a885ce76afad5cbb2459ef0dcf0df0907adc6e4dac16e1bfbd7074577`:
106
+
107
+ ```ruby
108
+ filter = Nostr::Filter.new(
109
+ authors: ['ae00f88a885ce76afad5cbb2459ef0dcf0df0907adc6e4dac16e1bfbd7074577'],
110
+ kinds: [Nostr::EventKind::TEXT_NOTE],
111
+ since: 1230981305,
112
+ limit: 5,
113
+ )
114
+ subscription = client.subscribe(filter: filter)
115
+ ```
@@ -0,0 +1,4 @@
1
+ # Updating a subscription
2
+
3
+ Updating a subscription is done by creating a new subscription with the same id as the previous one. See
4
+ [creating a subscription](./creating-a-subscription.md) for more information.