nostr 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +2 -0
- data/.rubocop_todo.yml +23 -0
- data/CHANGELOG.md +9 -0
- data/README.md +165 -4
- data/lib/nostr/client.rb +176 -0
- data/lib/nostr/client_message_type.rb +15 -0
- data/lib/nostr/event.rb +93 -0
- data/lib/nostr/event_fragment.rb +111 -0
- data/lib/nostr/event_kind.rb +28 -0
- data/lib/nostr/filter.rb +172 -0
- data/lib/nostr/key_pair.rb +46 -0
- data/lib/nostr/keygen.rb +78 -0
- data/lib/nostr/relay.rb +43 -0
- data/lib/nostr/subscription.rb +65 -0
- data/lib/nostr/user.rb +92 -0
- data/lib/nostr/version.rb +2 -1
- data/lib/nostr.rb +11 -2
- data/nostr.gemspec +12 -2
- metadata +145 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 90770e269f0c966f76013797314b9e3b4b0743fa57f5b84f2460371b413bc5f1
|
4
|
+
data.tar.gz: 7a4a908a6c1b086781d26dc6f45c289a85859e12e1e854451a0e9f8e75f5fa06
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 35a4401f078587bed79d6063f4051cca3f8dc170c074c552a202fee7872b75668eb8204a5f6db09f810ed0c5bb84026438d7e942f48a4825d63ba64552d032c7
|
7
|
+
data.tar.gz: b9400efc3228dea17e23e40803e2e7b94ce9966e6a729fcf5ea0b79ffcea07b7e51f7fd9703d01af1d287ffdaf85b6b8d49d325813b8e32518a0be240537e86e
|
data/.rubocop.yml
CHANGED
data/.rubocop_todo.yml
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# This configuration was generated by
|
2
|
+
# `rubocop --auto-gen-config`
|
3
|
+
# on 2023-01-12 10:00:10 UTC using RuboCop version 1.42.0.
|
4
|
+
# The point is for the user to remove these configuration records
|
5
|
+
# one by one as the offenses are removed from the code base.
|
6
|
+
# Note that changes in the inspected code, or installation of new
|
7
|
+
# versions of RuboCop, may require this file to be generated again.
|
8
|
+
|
9
|
+
# Offense count: 2
|
10
|
+
# Configuration parameters: AllowedMethods, AllowedPatterns, IgnoredMethods, CountRepeatedAttributes.
|
11
|
+
Metrics/AbcSize:
|
12
|
+
Max: 23
|
13
|
+
|
14
|
+
# Offense count: 2
|
15
|
+
# Configuration parameters: CountComments, CountAsOne, ExcludedMethods, AllowedMethods, AllowedPatterns, IgnoredMethods.
|
16
|
+
Metrics/MethodLength:
|
17
|
+
Max: 16
|
18
|
+
|
19
|
+
# Offense count: 4
|
20
|
+
# Configuration parameters: AssignmentOnly.
|
21
|
+
RSpec/InstanceVariable:
|
22
|
+
Exclude:
|
23
|
+
- 'spec/nostr/client_spec.rb'
|
data/CHANGELOG.md
CHANGED
@@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file.
|
|
4
4
|
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
5
5
|
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
6
6
|
|
7
|
+
## [0.2.0] - 2023-01-12
|
8
|
+
|
9
|
+
### Added
|
10
|
+
|
11
|
+
- [NIP-01](https://github.com/nostr-protocol/nips/blob/master/01.md) compliant client
|
12
|
+
|
7
13
|
## [0.1.0] - 2023-01-06
|
8
14
|
|
9
15
|
- Initial release
|
16
|
+
|
17
|
+
[0.2.0]: https://github.com/wilsonsilva/nostr/compare/v0.1.0...v0.2.0
|
18
|
+
[0.1.0]: https://github.com/wilsonsilva/nostr/compare/7fded5...v0.1.0
|
data/README.md
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
# Nostr
|
2
2
|
|
3
3
|
[](https://badge.fury.io/rb/nostr)
|
4
|
-
[](https://codeclimate.com/github/wilsonsilva/nostr/maintainability)
|
5
|
+
[](https://codeclimate.com/github/wilsonsilva/nostr/test_coverage)
|
6
6
|
|
7
|
-
Nostr client. Please note that the API is likely to change as the gem is still in development and
|
8
|
-
stable release. Use with caution.
|
7
|
+
Asynchronous Nostr client. Please note that the API is likely to change as the gem is still in development and
|
8
|
+
has not yet reached a stable release. Use with caution.
|
9
9
|
|
10
10
|
## Installation
|
11
11
|
|
@@ -19,10 +19,171 @@ If bundler is not being used to manage dependencies, install the gem by executin
|
|
19
19
|
|
20
20
|
## Usage
|
21
21
|
|
22
|
+
### Requiring the gem
|
23
|
+
|
24
|
+
All examples below assume that the gem has been required.
|
25
|
+
|
22
26
|
```ruby
|
23
27
|
require 'nostr'
|
24
28
|
```
|
25
29
|
|
30
|
+
|
31
|
+
### Generating a keypair
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
keygen = Nostr::Keygen.new
|
35
|
+
keypair = keygen.generate_keypair
|
36
|
+
|
37
|
+
keypair.private_key
|
38
|
+
keypair.public_key
|
39
|
+
```
|
40
|
+
|
41
|
+
### Generating a private key and a public key
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
keygen = Nostr::Keygen.new
|
45
|
+
|
46
|
+
private_key = keygen.generate_private_key
|
47
|
+
public_key = keygen.extract_public_key(private_key)
|
48
|
+
```
|
49
|
+
|
50
|
+
### Connecting to a Relay
|
51
|
+
|
52
|
+
Clients can connect to multiple Relays. In this version, a Client can only connect to a single Relay at a time.
|
53
|
+
|
54
|
+
You may instantiate multiple Clients and multiple Relays.
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
client = Nostr::Client.new
|
58
|
+
relay = Nostr::Relay.new(url: 'wss://relay.damus.io', name: 'Damus')
|
59
|
+
|
60
|
+
client.connect(relay)
|
61
|
+
```
|
62
|
+
|
63
|
+
### WebSocket events
|
64
|
+
|
65
|
+
All communication between clients and relays happen in WebSockets.
|
66
|
+
|
67
|
+
The `:connect` event is fired when a connection with a WebSocket is opened. You must call `Nostr::Client#connect` first.
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
client.on :connect do
|
71
|
+
# all the code goes here
|
72
|
+
end
|
73
|
+
```
|
74
|
+
|
75
|
+
The `:close` event is fired when a connection with a WebSocket has been closed because of an error.
|
76
|
+
|
77
|
+
```ruby
|
78
|
+
client.on :error do |error_message|
|
79
|
+
puts error_message
|
80
|
+
end
|
81
|
+
|
82
|
+
# > Network error: wss://rsslay.fiatjaf.com: Unable to verify the server certificate for 'rsslay.fiatjaf.com'
|
83
|
+
```
|
84
|
+
|
85
|
+
The `:message` event is fired whenwhen data is received through a WebSocket.
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
client.on :message do |message|
|
89
|
+
puts message
|
90
|
+
end
|
91
|
+
|
92
|
+
# > Network error: wss://rsslay.fiatjaf.com: Unable to verify the server certificate for 'rsslay.fiatjaf.com'
|
93
|
+
```
|
94
|
+
|
95
|
+
The `:close` event is fired when a connection with a WebSocket is closed.
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
client.on :close do |code, reason|
|
99
|
+
# you may attempt to reconnect
|
100
|
+
|
101
|
+
client.connect(relay)
|
102
|
+
end
|
103
|
+
```
|
104
|
+
|
105
|
+
### Requesting for events / creating a subscription
|
106
|
+
|
107
|
+
A client can request events and subscribe to new updates after it has established a connection with the Relay.
|
108
|
+
|
109
|
+
You may use a `Nostr::Filter` instance with as many attributes as you wish:
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
client.on :connect do
|
113
|
+
filter = Nostr::Filter.new(
|
114
|
+
ids: ['8535d5e2d7b9dc07567f676fbe70428133c9884857e1915f5b1cc6514c2fdff8'],
|
115
|
+
authors: ['ae00f88a885ce76afad5cbb2459ef0dcf0df0907adc6e4dac16e1bfbd7074577'],
|
116
|
+
kinds: [Nostr::EventKind::TEXT_NOTE],
|
117
|
+
e: ["f111593a72cc52a7f0978de5ecf29b4653d0cf539f1fa50d2168fc1dc8280e52"],
|
118
|
+
p: ["f1f9b0996d4ff1bf75e79e4cc8577c89eb633e68415c7faf74cf17a07bf80bd8"],
|
119
|
+
since: 1230981305,
|
120
|
+
until: 1292190341,
|
121
|
+
limit: 420,
|
122
|
+
)
|
123
|
+
|
124
|
+
subscription = client.subscribe('a_random_subscription_id', filter)
|
125
|
+
end
|
126
|
+
```
|
127
|
+
|
128
|
+
With just a few:
|
129
|
+
|
130
|
+
```ruby
|
131
|
+
client.on :connect do
|
132
|
+
filter = Nostr::Filter.new(kinds: [Nostr::EventKind::TEXT_NOTE])
|
133
|
+
subscription = client.subscribe('a_random_subscription_id', filter)
|
134
|
+
end
|
135
|
+
```
|
136
|
+
|
137
|
+
Or omit the filter:
|
138
|
+
|
139
|
+
```ruby
|
140
|
+
client.on :connect do
|
141
|
+
subscription = client.subscribe('a_random_subscription_id')
|
142
|
+
end
|
143
|
+
```
|
144
|
+
|
145
|
+
Or even omit the subscription id:
|
146
|
+
|
147
|
+
```ruby
|
148
|
+
client.on :connect do
|
149
|
+
subscription = client.subscribe('a_random_subscription_id')
|
150
|
+
end
|
151
|
+
```
|
152
|
+
|
153
|
+
### Stop previous subscriptions
|
154
|
+
|
155
|
+
You can stop receiving messages from a subscription by calling `#unsubscribe`:
|
156
|
+
|
157
|
+
```ruby
|
158
|
+
client.unsubscribe('your_subscription_id')
|
159
|
+
```
|
160
|
+
|
161
|
+
### Publishing an event
|
162
|
+
|
163
|
+
To publish an event you need a keypair.
|
164
|
+
|
165
|
+
```ruby
|
166
|
+
# Set up the private key
|
167
|
+
private_key = 'a630b06e2f883378d0aa335b9adaf7734603e00433350b684fe53e184f08c58f'
|
168
|
+
user = Nostr::User.new(private_key)
|
169
|
+
|
170
|
+
# Create a signed event
|
171
|
+
event = user.create_event(
|
172
|
+
created_at: 1667422587, # optional, defaults to the current time
|
173
|
+
kind: Nostr::EventKind::TEXT_NOTE,
|
174
|
+
tags: [], # optional, defaults to []
|
175
|
+
content: 'Your feedback is appreciated, now pay $8'
|
176
|
+
)
|
177
|
+
|
178
|
+
# Send it to the Relay
|
179
|
+
client.publish(event)
|
180
|
+
```
|
181
|
+
|
182
|
+
## NIPS
|
183
|
+
|
184
|
+
- [x] [NIP-01 - Client](https://github.com/nostr-protocol/nips/blob/master/01.md)
|
185
|
+
- [ ] [NIP-01 - Relay](https://github.com/nostr-protocol/nips/blob/master/01.md)
|
186
|
+
|
26
187
|
## Development
|
27
188
|
|
28
189
|
After checking out the repo, run `bin/setup` to install dependencies.
|
data/lib/nostr/client.rb
ADDED
@@ -0,0 +1,176 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'event_emitter'
|
4
|
+
require 'faye/websocket'
|
5
|
+
|
6
|
+
module Nostr
|
7
|
+
# Clients can talk with relays and can subscribe to any set of events using a subscription filters.
|
8
|
+
# The filter represents all the set of nostr events that a client is interested in.
|
9
|
+
#
|
10
|
+
# There is no sign-up or account creation for a client. Every time a client connects to a relay, it submits its
|
11
|
+
# subscription filters and the relay streams the "interested events" to the client as long as they are connected.
|
12
|
+
#
|
13
|
+
class Client
|
14
|
+
include EventEmitter
|
15
|
+
|
16
|
+
# Instantiates a new Client
|
17
|
+
#
|
18
|
+
# @api public
|
19
|
+
#
|
20
|
+
# @example Instantiating a client that logs all the events it sends and receives
|
21
|
+
# client = Nostr::Client.new(debug: true)
|
22
|
+
#
|
23
|
+
def initialize
|
24
|
+
@subscriptions = {}
|
25
|
+
|
26
|
+
initialize_channels
|
27
|
+
end
|
28
|
+
|
29
|
+
# Connects to the Relay's websocket endpoint
|
30
|
+
#
|
31
|
+
# @api public
|
32
|
+
#
|
33
|
+
# @example Connecting to a relay
|
34
|
+
# relay = Nostr::Relay.new(url: 'wss://relay.damus.io', name: 'Damus')
|
35
|
+
# client.connect(relay)
|
36
|
+
#
|
37
|
+
# @param [Relay] relay The relay to connect to
|
38
|
+
#
|
39
|
+
# @return [void]
|
40
|
+
#
|
41
|
+
def connect(relay)
|
42
|
+
execute_within_an_em_thread do
|
43
|
+
client = Faye::WebSocket::Client.new(relay.url, [], { tls: { verify_peer: false } })
|
44
|
+
parent_to_child_channel.subscribe { |msg| client.send(msg) }
|
45
|
+
|
46
|
+
client.on :open do
|
47
|
+
child_to_parent_channel.push(type: :open)
|
48
|
+
end
|
49
|
+
|
50
|
+
client.on :message do |event|
|
51
|
+
child_to_parent_channel.push(type: :message, data: event.data)
|
52
|
+
end
|
53
|
+
|
54
|
+
client.on :error do |event|
|
55
|
+
child_to_parent_channel.push(type: :error, message: event.message)
|
56
|
+
end
|
57
|
+
|
58
|
+
client.on :close do |event|
|
59
|
+
child_to_parent_channel.push(type: :close, code: event.code, reason: event.reason)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Subscribes to a set of events using a filter
|
65
|
+
#
|
66
|
+
# @api public
|
67
|
+
#
|
68
|
+
# @example Creating a subscription with no id and no filters
|
69
|
+
# subscription = client.subscribe
|
70
|
+
#
|
71
|
+
# @example Creating a subscription with an ID
|
72
|
+
# subscription = client.subscribe(subscription_id: 'my-subscription')
|
73
|
+
#
|
74
|
+
# @example Subscribing to all events created after a certain time
|
75
|
+
# subscription = client.subscribe(filter: Nostr::Filter.new(since: 1230981305))
|
76
|
+
#
|
77
|
+
# @param subscription_id [String] The subscription id. A random string.
|
78
|
+
# @param filter [Filter] A set of attributes that represent the events that the client is interested in.
|
79
|
+
#
|
80
|
+
# @return [Subscription] The subscription object
|
81
|
+
#
|
82
|
+
def subscribe(subscription_id: SecureRandom.hex, filter: Filter.new)
|
83
|
+
subscriptions[subscription_id] = Subscription.new(id: subscription_id, filter:)
|
84
|
+
parent_to_child_channel.push([ClientMessageType::REQ, subscription_id, filter.to_h].to_json)
|
85
|
+
subscriptions[subscription_id]
|
86
|
+
end
|
87
|
+
|
88
|
+
# Stops a previous subscription
|
89
|
+
#
|
90
|
+
# @api public
|
91
|
+
#
|
92
|
+
# @example Stopping a subscription
|
93
|
+
# client.unsubscribe(subscription.id)
|
94
|
+
#
|
95
|
+
# @example Stopping a subscription
|
96
|
+
# client.unsubscribe('my-subscription')
|
97
|
+
#
|
98
|
+
# @param subscription_id [String] ID of a previously created subscription.
|
99
|
+
#
|
100
|
+
# @return [void]
|
101
|
+
#
|
102
|
+
def unsubscribe(subscription_id)
|
103
|
+
subscriptions.delete(subscription_id)
|
104
|
+
parent_to_child_channel.push([ClientMessageType::CLOSE, subscription_id].to_json)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Sends an event to a Relay
|
108
|
+
#
|
109
|
+
# @api public
|
110
|
+
#
|
111
|
+
# @example Sending an event to a relay
|
112
|
+
# client.publish(event)
|
113
|
+
#
|
114
|
+
# @param event [Event] The event to be sent to a Relay
|
115
|
+
#
|
116
|
+
# @return [void]
|
117
|
+
#
|
118
|
+
def publish(event)
|
119
|
+
parent_to_child_channel.push([ClientMessageType::EVENT, event.to_h].to_json)
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
|
124
|
+
# The subscriptions that the client has created
|
125
|
+
#
|
126
|
+
# @api private
|
127
|
+
#
|
128
|
+
# @return [Hash{String=>Subscription}>]
|
129
|
+
#
|
130
|
+
attr_reader :subscriptions
|
131
|
+
|
132
|
+
# The channel that the parent thread uses to send messages to the child thread
|
133
|
+
#
|
134
|
+
# @api private
|
135
|
+
#
|
136
|
+
# @return [EventMachine::Channel]
|
137
|
+
#
|
138
|
+
attr_reader :parent_to_child_channel
|
139
|
+
|
140
|
+
# The channel that the child thread uses to send messages to the parent thread
|
141
|
+
#
|
142
|
+
# @api private
|
143
|
+
#
|
144
|
+
# @return [EventMachine::Channel]
|
145
|
+
#
|
146
|
+
attr_reader :child_to_parent_channel
|
147
|
+
|
148
|
+
# Executes a block of code within the EventMachine thread
|
149
|
+
#
|
150
|
+
# @api private
|
151
|
+
#
|
152
|
+
# @return [Thread]
|
153
|
+
#
|
154
|
+
def execute_within_an_em_thread(&block)
|
155
|
+
Thread.new { EventMachine.run(block) }
|
156
|
+
end
|
157
|
+
|
158
|
+
# Creates the communication channels between threads
|
159
|
+
#
|
160
|
+
# @api private
|
161
|
+
#
|
162
|
+
# @return [void]
|
163
|
+
#
|
164
|
+
def initialize_channels
|
165
|
+
@parent_to_child_channel = EventMachine::Channel.new
|
166
|
+
@child_to_parent_channel = EventMachine::Channel.new
|
167
|
+
|
168
|
+
child_to_parent_channel.subscribe do |msg|
|
169
|
+
emit :connect if msg[:type] == :open
|
170
|
+
emit :message, msg[:data] if msg[:type] == :message
|
171
|
+
emit :error, msg[:message] if msg[:type] == :error
|
172
|
+
emit :close, msg[:code], msg[:reason] if msg[:type] == :close
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nostr
|
4
|
+
# Clients can send 3 types of messages, which must be JSON arrays
|
5
|
+
module ClientMessageType
|
6
|
+
# @return [String] Used to publish events
|
7
|
+
EVENT = 'EVENT'
|
8
|
+
|
9
|
+
# @return [String] Used to request events and subscribe to new updates
|
10
|
+
REQ = 'REQ'
|
11
|
+
|
12
|
+
# @return [String] Used to stop previous subscriptions
|
13
|
+
CLOSE = 'CLOSE'
|
14
|
+
end
|
15
|
+
end
|
data/lib/nostr/event.rb
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nostr
|
4
|
+
# The only object type that exists in Nostr is an event. Events are immutable.
|
5
|
+
class Event < EventFragment
|
6
|
+
# 32-bytes sha256 of the the serialized event data.
|
7
|
+
# To obtain the event.id, we sha256 the serialized event. The serialization is done over the UTF-8 JSON-serialized
|
8
|
+
# string (with no white space or line breaks)
|
9
|
+
#
|
10
|
+
# @api public
|
11
|
+
#
|
12
|
+
# @example Getting the event id
|
13
|
+
# event.id # => 'ccf9fdf3e1466d7c20969c71ec98defcf5f54aee088513e1b73ccb7bd770d460'
|
14
|
+
#
|
15
|
+
# @return [String]
|
16
|
+
#
|
17
|
+
attr_reader :id
|
18
|
+
|
19
|
+
# 64-bytes signature of the sha256 hash of the serialized event data, which is
|
20
|
+
# the same as the "id" field
|
21
|
+
#
|
22
|
+
# @api public
|
23
|
+
#
|
24
|
+
# @example Getting the event signature
|
25
|
+
# event.sig # => ''
|
26
|
+
#
|
27
|
+
# @return [String]
|
28
|
+
#
|
29
|
+
attr_reader :sig
|
30
|
+
|
31
|
+
# Instantiates a new Event
|
32
|
+
#
|
33
|
+
# @api public
|
34
|
+
#
|
35
|
+
# @example Instantiating a new event
|
36
|
+
# Nostr::Event.new(
|
37
|
+
# id: 'ccf9fdf3e1466d7c20969c71ec98defcf5f54aee088513e1b73ccb7bd770d460',
|
38
|
+
# pubkey: '48df4af6e240ac5f7c5de89bf5941b249880be0e87d03685b178ccb1a315f52e',
|
39
|
+
# created_at: 1230981305,
|
40
|
+
# kind: 1,
|
41
|
+
# tags: [],
|
42
|
+
# content: 'Your feedback is appreciated, now pay $8',
|
43
|
+
# sig: '123ac2923b792ce730b3da34f16155470ab13c8f97f9c53eaeb334f1fb3a5dc9a7f643
|
44
|
+
# 937c6d6e9855477638f5655c5d89c9aa5501ea9b578a66aced4f1cd7b3'
|
45
|
+
# )
|
46
|
+
#
|
47
|
+
#
|
48
|
+
# @param id [String] 32-bytes sha256 of the the serialized event data.
|
49
|
+
# @param sig [String] 64-bytes signature of the sha256 hash of the serialized event data, which is
|
50
|
+
# the same as the "id" field
|
51
|
+
#
|
52
|
+
def initialize(id:, sig:, **kwargs)
|
53
|
+
super(**kwargs)
|
54
|
+
|
55
|
+
@id = id
|
56
|
+
@sig = sig
|
57
|
+
end
|
58
|
+
|
59
|
+
# Converts the event to a hash
|
60
|
+
#
|
61
|
+
# @api public
|
62
|
+
#
|
63
|
+
# @example Converting the event to a hash
|
64
|
+
# event.to_h
|
65
|
+
#
|
66
|
+
# @return [Hash] The event as a hash.
|
67
|
+
#
|
68
|
+
def to_h
|
69
|
+
{
|
70
|
+
id:,
|
71
|
+
pubkey:,
|
72
|
+
created_at:,
|
73
|
+
kind:,
|
74
|
+
tags:,
|
75
|
+
content:,
|
76
|
+
sig:
|
77
|
+
}
|
78
|
+
end
|
79
|
+
|
80
|
+
# Compares two events. Returns true if all attributes are equal and false otherwise
|
81
|
+
#
|
82
|
+
# @api public
|
83
|
+
#
|
84
|
+
# @example
|
85
|
+
# event1 == event2 # => true
|
86
|
+
#
|
87
|
+
# @return [Boolean] True if all attributes are equal and false otherwise
|
88
|
+
#
|
89
|
+
def ==(other)
|
90
|
+
to_h == other.to_h
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nostr
|
4
|
+
# Part of an +Event+. A complete +Event+ must have an +id+ and a +sig+.
|
5
|
+
class EventFragment
|
6
|
+
# 32-bytes hex-encoded public key of the event creator
|
7
|
+
#
|
8
|
+
# @api public
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# event.pubkey # => '48df4af6e240ac5f7c5de89bf5941b249880be0e87d03685b178ccb1a315f52e'
|
12
|
+
#
|
13
|
+
# @return [String]
|
14
|
+
#
|
15
|
+
attr_reader :pubkey
|
16
|
+
|
17
|
+
# Date of the creation of the vent. A UNIX timestamp, in seconds
|
18
|
+
#
|
19
|
+
# @api public
|
20
|
+
#
|
21
|
+
# @example
|
22
|
+
# event.created_at # => 1230981305
|
23
|
+
#
|
24
|
+
# @return [Integer]
|
25
|
+
#
|
26
|
+
attr_reader :created_at
|
27
|
+
|
28
|
+
# The kind of the event. An integer from 0 to 2
|
29
|
+
#
|
30
|
+
# @api public
|
31
|
+
#
|
32
|
+
# @example
|
33
|
+
# event.kind # => 1
|
34
|
+
#
|
35
|
+
# @return [Integer]
|
36
|
+
#
|
37
|
+
attr_reader :kind
|
38
|
+
|
39
|
+
# An array of tags. Each tag is an array of strings
|
40
|
+
#
|
41
|
+
# @api public
|
42
|
+
#
|
43
|
+
# @example Tags referencing an event
|
44
|
+
# event.tags #=> [["e", "event_id", "relay URL"]]
|
45
|
+
#
|
46
|
+
# @example Tags referencing a key
|
47
|
+
# event.tags #=> [["p", "event_id", "relay URL"]]
|
48
|
+
#
|
49
|
+
# @return [Array<Array>]
|
50
|
+
#
|
51
|
+
attr_reader :tags
|
52
|
+
|
53
|
+
# An arbitrary string
|
54
|
+
#
|
55
|
+
# @api public
|
56
|
+
#
|
57
|
+
# @example
|
58
|
+
# event.content # => 'Your feedback is appreciated, now pay $8'
|
59
|
+
#
|
60
|
+
# @return [String]
|
61
|
+
#
|
62
|
+
attr_reader :content
|
63
|
+
|
64
|
+
# Instantiates a new EventFragment
|
65
|
+
#
|
66
|
+
# @api public
|
67
|
+
#
|
68
|
+
# @example
|
69
|
+
# Nostr::EventFragment.new(
|
70
|
+
# pubkey: 'ccf9fdf3e1466d7c20969c71ec98defcf5f54aee088513e1b73ccb7bd770d460',
|
71
|
+
# created_at: 1230981305,
|
72
|
+
# kind: 1,
|
73
|
+
# tags: [['e', '189df012cfff8a075785b884bd702025f4a7a37710f581c4ac9d33e24b585408']],
|
74
|
+
# content: 'Your feedback is appreciated, now pay $8'
|
75
|
+
# )
|
76
|
+
#
|
77
|
+
# @param pubkey [String] 32-bytes hex-encoded public key of the event creator.
|
78
|
+
# @param created_at [Integer] Date of the creation of the vent. A UNIX timestamp, in seconds.
|
79
|
+
# @param kind [Integer] The kind of the event. An integer from 0 to 2.
|
80
|
+
# @param tags [Array<Array>] An array of tags. Each tag is an array of strings.
|
81
|
+
# @param content [String] Arbitrary string.
|
82
|
+
#
|
83
|
+
def initialize(pubkey:, kind:, content:, created_at: Time.now.to_i, tags: [])
|
84
|
+
@pubkey = pubkey
|
85
|
+
@created_at = created_at
|
86
|
+
@kind = kind
|
87
|
+
@tags = tags
|
88
|
+
@content = content
|
89
|
+
end
|
90
|
+
|
91
|
+
# Serializes the event fragment, to obtain a SHA256 hash of it
|
92
|
+
#
|
93
|
+
# @api public
|
94
|
+
#
|
95
|
+
# @example Converting the event to a hash
|
96
|
+
# event_fragment.serialize
|
97
|
+
#
|
98
|
+
# @return [Array] The event fragment as an array.
|
99
|
+
#
|
100
|
+
def serialize
|
101
|
+
[
|
102
|
+
0,
|
103
|
+
pubkey,
|
104
|
+
created_at,
|
105
|
+
kind,
|
106
|
+
tags,
|
107
|
+
content
|
108
|
+
]
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nostr
|
4
|
+
# Defines the event kinds that can be emitted by clients.
|
5
|
+
module EventKind
|
6
|
+
# The content is set to a stringified JSON object +{name: <username>, about: <string>,
|
7
|
+
# picture: <url, string>}+ describing the user who created the event. A relay may delete past set_metadata
|
8
|
+
# events once it gets a new one for the same pubkey.
|
9
|
+
#
|
10
|
+
# @return [Integer]
|
11
|
+
#
|
12
|
+
SET_METADATA = 0
|
13
|
+
|
14
|
+
# The content is set to the text content of a note (anything the user wants to say).
|
15
|
+
# Non-plaintext notes should instead use kind 1000-10000 as described in NIP-16.
|
16
|
+
#
|
17
|
+
# @return [Integer]
|
18
|
+
#
|
19
|
+
TEXT_NOTE = 1
|
20
|
+
|
21
|
+
# The content is set to the URL (e.g., wss://somerelay.com) of a relay the event creator wants to
|
22
|
+
# recommend to its followers.
|
23
|
+
#
|
24
|
+
# @return [Integer]
|
25
|
+
#
|
26
|
+
RECOMMEND_SERVER = 2
|
27
|
+
end
|
28
|
+
end
|