nostrb 0.1.0.1
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 +7 -0
- data/Rakefile +24 -0
- data/VERSION +1 -0
- data/lib/nostrb/event.rb +154 -0
- data/lib/nostrb/filter.rb +144 -0
- data/lib/nostrb/json.rb +19 -0
- data/lib/nostrb/names.rb +71 -0
- data/lib/nostrb/oj.rb +6 -0
- data/lib/nostrb/relay.rb +142 -0
- data/lib/nostrb/sequel.rb +222 -0
- data/lib/nostrb/source.rb +111 -0
- data/lib/nostrb/sqlite.rb +441 -0
- data/lib/nostrb.rb +111 -0
- data/nostrb.gemspec +18 -0
- data/test/common.rb +25 -0
- data/test/event.rb +193 -0
- data/test/nostrb.rb +87 -0
- data/test/relay.rb +360 -0
- data/test/source.rb +136 -0
- metadata +74 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 6f6159cbd2c246ae573103bd68e56e7cd97b0d3d81d36d8e0816abc5bd23c3c1
|
4
|
+
data.tar.gz: 8bcf8fe77b3baa61882ea849df549b2800ebbf5e788d55c2c1c00efddc99e1f8
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 369121a3fa1b9ead9bc245a3bd5f18ce6a6e2cc3571e1c91019afb4752845de7edbef086b755ad3b90d4b1a072997b2cce8047e19c4db293cc14328332febb56
|
7
|
+
data.tar.gz: 8b19fa2c2b738e53711fdb4bbdac6a196ce8499b8a212b742be734318a526bd495bed6ddfd97f771c005323ee2583f6207feea8f79850b95e7c3aced24ac3cec
|
data/Rakefile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'rake/testtask'
|
2
|
+
|
3
|
+
Rake::TestTask.new :test do |t|
|
4
|
+
t.pattern = "test/*.rb"
|
5
|
+
t.warning = true
|
6
|
+
end
|
7
|
+
|
8
|
+
task :relay do |t|
|
9
|
+
sh "ruby -I lib examples/relay.rb"
|
10
|
+
end
|
11
|
+
|
12
|
+
task default: [:test, :relay]
|
13
|
+
|
14
|
+
begin
|
15
|
+
require 'buildar'
|
16
|
+
|
17
|
+
Buildar.new do |b|
|
18
|
+
b.gemspec_file = 'nostrb.gemspec'
|
19
|
+
b.version_file = 'VERSION'
|
20
|
+
b.use_git = true
|
21
|
+
end
|
22
|
+
rescue LoadError
|
23
|
+
warn "buildar tasks unavailable"
|
24
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0.1
|
data/lib/nostrb/event.rb
ADDED
@@ -0,0 +1,154 @@
|
|
1
|
+
require 'nostrb'
|
2
|
+
|
3
|
+
module Nostrb
|
4
|
+
class Event
|
5
|
+
|
6
|
+
# Event
|
7
|
+
# content: any string
|
8
|
+
# kind: 0..65535
|
9
|
+
# tags: Array[Array[string]]
|
10
|
+
# pubkey: 64 hex chars (32B binary)
|
11
|
+
|
12
|
+
def self.digest(ary) = Nostrb.digest(Nostrb.json(Nostrb.ary!(ary)))
|
13
|
+
|
14
|
+
def self.tag_values(tag, tags)
|
15
|
+
tags.select { |a| a[0] == tag }.map { |a| a[1] }
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.d_tag(tags)
|
19
|
+
tag_values('d', tags).first
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_reader :content, :kind, :tags, :pk
|
23
|
+
|
24
|
+
def initialize(content = '', kind: 1, tags: [], pk:)
|
25
|
+
@content = Nostrb.txt!(content)
|
26
|
+
@kind = Nostrb.kind!(kind)
|
27
|
+
@tags = Nostrb.tags!(tags)
|
28
|
+
@pk = Nostrb.key!(pk)
|
29
|
+
end
|
30
|
+
|
31
|
+
alias_method :to_s, :content
|
32
|
+
|
33
|
+
def serialize(created_at)
|
34
|
+
[0, self.pubkey, Nostrb.int!(created_at), @kind, @tags, @content]
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_a = serialize(Time.now.to_i)
|
38
|
+
def pubkey = SchnorrSig.bin2hex(@pk)
|
39
|
+
def digest(created_at) = Event.digest(serialize(created_at))
|
40
|
+
def sign(sk) = SignedEvent.new(self, sk)
|
41
|
+
|
42
|
+
#
|
43
|
+
# Tags
|
44
|
+
#
|
45
|
+
|
46
|
+
# add an array of 2+ strings to @tags
|
47
|
+
def add_tag(tag, value, *rest)
|
48
|
+
@tags.push([Nostrb.txt!(tag), Nostrb.txt!(value)] +
|
49
|
+
rest.each { |s| Nostrb.txt!(s) })
|
50
|
+
end
|
51
|
+
|
52
|
+
# add an event tag based on event id, hex encoded
|
53
|
+
def ref_event(eid_hex, *rest)
|
54
|
+
add_tag('e', Nostrb.id!(eid_hex), *rest)
|
55
|
+
end
|
56
|
+
|
57
|
+
# add a pubkey tag based on pubkey, 64 bytes hex encoded
|
58
|
+
def ref_pubkey(pubkey, *rest)
|
59
|
+
add_tag('p', Nostrb.pubkey!(pubkey), *rest)
|
60
|
+
end
|
61
|
+
|
62
|
+
# kind: and pubkey: required
|
63
|
+
def ref_replace(*rest, kind:, pubkey:, d_tag: '')
|
64
|
+
val = [Nostrb.kind!(kind), Nostrb.pubkey!(pubkey), d_tag].join(':')
|
65
|
+
add_tag('a', val, *rest)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# SignedEvent
|
70
|
+
# id: 64 hex chars (32B binary)
|
71
|
+
# created_at: unix seconds, integer
|
72
|
+
# sig: 128 hex chars (64B binary)
|
73
|
+
|
74
|
+
class SignedEvent
|
75
|
+
class Error < RuntimeError; end
|
76
|
+
class IdCheck < Error; end
|
77
|
+
class SignatureCheck < Error; end
|
78
|
+
|
79
|
+
def self.validate!(parsed)
|
80
|
+
Nostrb.check!(parsed, Hash)
|
81
|
+
Nostrb.txt!(parsed.fetch("content"))
|
82
|
+
Nostrb.pubkey!(parsed.fetch("pubkey"))
|
83
|
+
Nostrb.kind!(parsed.fetch("kind"))
|
84
|
+
Nostrb.tags!(parsed.fetch("tags"))
|
85
|
+
Nostrb.int!(parsed.fetch("created_at"))
|
86
|
+
Nostrb.id!(parsed.fetch("id"))
|
87
|
+
Nostrb.sig!(parsed.fetch("sig"))
|
88
|
+
parsed
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.digest(valid) = Nostrb.digest(Nostrb.json(serialize(valid)))
|
92
|
+
|
93
|
+
def self.serialize(valid)
|
94
|
+
Array[ 0,
|
95
|
+
valid["pubkey"],
|
96
|
+
valid["created_at"],
|
97
|
+
valid["kind"],
|
98
|
+
valid["tags"],
|
99
|
+
valid["content"], ]
|
100
|
+
end
|
101
|
+
|
102
|
+
# Validate the id (optional) and signature
|
103
|
+
# May raise explicitly: IdCheck, SignatureCheck
|
104
|
+
# May raise implicitly: Nostrb::SizeError, EncodingError, TypeError,
|
105
|
+
# SchnorrSig::Error
|
106
|
+
# Return a _completely validated_ hash
|
107
|
+
def self.verify(valid, check_id: true)
|
108
|
+
id, pubkey, sig = valid["id"], valid["pubkey"], valid["sig"]
|
109
|
+
|
110
|
+
# extract binary values for signature verification
|
111
|
+
digest = SchnorrSig.hex2bin id
|
112
|
+
pk = SchnorrSig.hex2bin pubkey
|
113
|
+
signature = SchnorrSig.hex2bin sig
|
114
|
+
|
115
|
+
# verify the signature
|
116
|
+
unless SchnorrSig.verify?(pk, digest, signature)
|
117
|
+
raise(SignatureCheck, sig)
|
118
|
+
end
|
119
|
+
# (optional) verify the id / digest
|
120
|
+
raise(IdCheck, id) if check_id and digest != SignedEvent.digest(valid)
|
121
|
+
valid
|
122
|
+
end
|
123
|
+
|
124
|
+
attr_reader :event, :created_at, :digest, :signature
|
125
|
+
|
126
|
+
# sk is used to generate @signature and then discarded
|
127
|
+
def initialize(event, sk)
|
128
|
+
@event = Nostrb.check!(event, Event)
|
129
|
+
@created_at = Time.now.to_i
|
130
|
+
@digest = @event.digest(@created_at)
|
131
|
+
@signature = SchnorrSig.sign(Nostrb.key!(sk), @digest)
|
132
|
+
end
|
133
|
+
|
134
|
+
def content = @event.content
|
135
|
+
def kind = @event.kind
|
136
|
+
def tags = @event.tags
|
137
|
+
def pubkey = @event.pubkey
|
138
|
+
def to_s = @event.to_s
|
139
|
+
def serialize = @event.serialize(@created_at)
|
140
|
+
|
141
|
+
def id = SchnorrSig.bin2hex(@digest)
|
142
|
+
def sig = SchnorrSig.bin2hex(@signature)
|
143
|
+
|
144
|
+
def to_h
|
145
|
+
Hash[ "content" => @event.content,
|
146
|
+
"kind" => @event.kind,
|
147
|
+
"tags" => @event.tags,
|
148
|
+
"pubkey" => @event.pubkey,
|
149
|
+
"created_at" => @created_at,
|
150
|
+
"id" => self.id,
|
151
|
+
"sig" => self.sig ]
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
module Nostrb
|
2
|
+
module Seconds
|
3
|
+
def milliseconds(i) = i / 1000r
|
4
|
+
def seconds(i) = i
|
5
|
+
def minutes(i) = 60 * i
|
6
|
+
def hours(i) = 60 * minutes(i)
|
7
|
+
def days(i) = 24 * hours(i)
|
8
|
+
def weeks(i) = 7 * days(i)
|
9
|
+
def months(i) = years(i) / 12
|
10
|
+
def years(i) = 365 * days(i)
|
11
|
+
|
12
|
+
def process(hsh)
|
13
|
+
seconds = 0
|
14
|
+
[:seconds, :minutes, :hours, :days, :weeks, :months, :years].each { |p|
|
15
|
+
seconds += send(p, hsh[p]) if hsh.key?(p)
|
16
|
+
}
|
17
|
+
seconds
|
18
|
+
end
|
19
|
+
end
|
20
|
+
Seconds.extend(Seconds)
|
21
|
+
|
22
|
+
class Filter
|
23
|
+
TAG = /\A#([a-zA-Z])\z/
|
24
|
+
|
25
|
+
def self.ago(hsh)
|
26
|
+
Time.now.to_i - Seconds.process(hsh)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.ingest(hash)
|
30
|
+
f = Filter.new
|
31
|
+
|
32
|
+
if ids = hash.delete("ids")
|
33
|
+
f.add_ids(*ids)
|
34
|
+
end
|
35
|
+
if authors = hash.delete("authors")
|
36
|
+
f.add_authors(*authors)
|
37
|
+
end
|
38
|
+
if kinds = hash.delete("kinds")
|
39
|
+
f.add_kinds(*kinds)
|
40
|
+
end
|
41
|
+
if since = hash.delete("since")
|
42
|
+
f.since = since
|
43
|
+
end
|
44
|
+
if _until = hash.delete("until")
|
45
|
+
f.until = _until
|
46
|
+
end
|
47
|
+
if limit = hash.delete("limit")
|
48
|
+
f.limit = limit
|
49
|
+
end
|
50
|
+
|
51
|
+
# anything left in hash should only be single letter tags
|
52
|
+
hash.each { |tag, ary|
|
53
|
+
if matches = tag.match(TAG)
|
54
|
+
f.add_tag(matches[1], ary)
|
55
|
+
else
|
56
|
+
warn "unmatched tag: #{tag}"
|
57
|
+
end
|
58
|
+
}
|
59
|
+
f
|
60
|
+
end
|
61
|
+
|
62
|
+
attr_reader :ids, :authors, :kinds, :tags, :limit
|
63
|
+
|
64
|
+
def initialize
|
65
|
+
@ids = []
|
66
|
+
@authors = []
|
67
|
+
@kinds = []
|
68
|
+
@tags = {}
|
69
|
+
@since = nil
|
70
|
+
@until = nil
|
71
|
+
@limit = nil
|
72
|
+
end
|
73
|
+
|
74
|
+
def add_ids(*event_ids)
|
75
|
+
@ids += event_ids.each { |id| Nostrb.id!(id) }
|
76
|
+
end
|
77
|
+
|
78
|
+
def add_authors(*pubkeys)
|
79
|
+
@authors += pubkeys.each { |pubkey| Nostrb.pubkey!(pubkey) }
|
80
|
+
end
|
81
|
+
|
82
|
+
def add_kinds(*kinds)
|
83
|
+
@kinds += kinds.each { |k| Nostrb.kind!(k) }
|
84
|
+
end
|
85
|
+
|
86
|
+
def add_tag(letter, list)
|
87
|
+
@tags[Nostrb.txt!(letter, length: 1)] =
|
88
|
+
Nostrb.ary!(list, max: 99).each { |s| Nostrb.txt!(s) }
|
89
|
+
end
|
90
|
+
|
91
|
+
def since(hsh = nil) = hsh.nil? ? @since : (@since = Filter.ago(hsh))
|
92
|
+
def since=(int)
|
93
|
+
@since = int.nil? ? nil : Nostrb.int!(int)
|
94
|
+
end
|
95
|
+
|
96
|
+
def until(hsh = nil) = hsh.nil? ? @until : (@until = Filter.ago(hsh))
|
97
|
+
def until=(int)
|
98
|
+
@until = int.nil? ? nil : Nostrb.int!(int)
|
99
|
+
end
|
100
|
+
|
101
|
+
def limit=(int)
|
102
|
+
@limit = int.nil? ? nil : Nostrb.int!(int)
|
103
|
+
end
|
104
|
+
|
105
|
+
# Input
|
106
|
+
# Ruby hash as returned from SignedEvent.validate!
|
107
|
+
def match?(valid)
|
108
|
+
return false if !@ids.empty? and !@ids.include?(valid["id"])
|
109
|
+
return false if !@authors.empty? and !@authors.include?(valid["pubkey"])
|
110
|
+
return false if !@kinds.empty? and !@kinds.include?(valid["kind"])
|
111
|
+
return false if !@since.nil? and @since > valid["created_at"]
|
112
|
+
return false if !@until.nil? and @until < valid["created_at"]
|
113
|
+
if !@tags.empty?
|
114
|
+
tags = valid["tags"]
|
115
|
+
@tags.each { |letter, ary|
|
116
|
+
tag_match = false
|
117
|
+
tags.each { |(tag, val)|
|
118
|
+
next if tag_match
|
119
|
+
if tag == letter
|
120
|
+
return false if !ary.include?(val)
|
121
|
+
tag_match = true
|
122
|
+
end
|
123
|
+
}
|
124
|
+
return false unless tag_match
|
125
|
+
}
|
126
|
+
end
|
127
|
+
true
|
128
|
+
end
|
129
|
+
|
130
|
+
def to_h
|
131
|
+
h = Hash.new
|
132
|
+
h["ids"] = @ids if !@ids.empty?
|
133
|
+
h["authors"] = @authors if !@authors.empty?
|
134
|
+
h["kinds"] = @kinds if !@kinds.empty?
|
135
|
+
@tags.each { |letter, ary|
|
136
|
+
h['#' + letter.to_s] = ary if !ary.empty?
|
137
|
+
}
|
138
|
+
h["since"] = @since unless @since.nil?
|
139
|
+
h["until"] = @until unless @until.nil?
|
140
|
+
h["limit"] = @limit unless @limit.nil?
|
141
|
+
h
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
data/lib/nostrb/json.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Nostrb
|
4
|
+
# per NIP-01
|
5
|
+
JSON_OPTIONS = {
|
6
|
+
allow_nan: false,
|
7
|
+
max_nesting: 4, # event is 3 deep, wire format is 4 deep
|
8
|
+
script_safe: false,
|
9
|
+
ascii_only: false,
|
10
|
+
array_nl: '',
|
11
|
+
object_nl: '',
|
12
|
+
indent: '',
|
13
|
+
space: '',
|
14
|
+
space_before: '',
|
15
|
+
}
|
16
|
+
|
17
|
+
def self.parse(json) = JSON.parse(json, **JSON_OPTIONS)
|
18
|
+
def self.json(object) = JSON.generate(object, **JSON_OPTIONS)
|
19
|
+
end
|
data/lib/nostrb/names.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'nostrb'
|
2
|
+
|
3
|
+
module Nostrb
|
4
|
+
# NIP-05
|
5
|
+
module Names
|
6
|
+
LOCAL = /\A[_a-z0-0\-.]+\z/i
|
7
|
+
DOMAIN = /\A[a-z0-9\-.]+\z/i
|
8
|
+
|
9
|
+
# given bob@example.com, return [bob, example.com]
|
10
|
+
def self.identifier(str)
|
11
|
+
a = str.split('@')
|
12
|
+
raise "unexpected #{a.inspect}" unless a.length == 2
|
13
|
+
raise "bad local: #{a[0]}" unless LOCAL.match a[0]
|
14
|
+
raise "bad domain #{a[1]}" unless DOMAIN.match a[1]
|
15
|
+
a
|
16
|
+
end
|
17
|
+
|
18
|
+
# content: <JSON>
|
19
|
+
# kind: 0 (set_metadata)
|
20
|
+
# JSON:
|
21
|
+
# name:
|
22
|
+
# about:
|
23
|
+
# picture:
|
24
|
+
# nip05: bob@example.com
|
25
|
+
def self.extract_nip05(json)
|
26
|
+
addr = Nostrb.parse(json)['nip05']
|
27
|
+
identifier(addr) if addr
|
28
|
+
end
|
29
|
+
|
30
|
+
# when we get bob's profile back,
|
31
|
+
# if it has a nip05 field
|
32
|
+
# visit: https://example.com/.well-known/nostr.json?name=bob
|
33
|
+
def self.well_known_url(local, domain)
|
34
|
+
format("https://%s/.well-known/nostr.json?name=%s", domain, local)
|
35
|
+
end
|
36
|
+
|
37
|
+
# look for a pubkey:
|
38
|
+
# {"names":{"bob":"b0b0...b0"}}
|
39
|
+
# the pubkey at the URL should match the pubkey from the profile event
|
40
|
+
def self.extract_names(json)
|
41
|
+
hsh = Nostrb.parse(json).fetch("names")
|
42
|
+
hsh.each { |name, pubkey|
|
43
|
+
Nostrb.txt!(name)
|
44
|
+
Nostrb.pubkey!(pubkey)
|
45
|
+
}
|
46
|
+
hsh
|
47
|
+
end
|
48
|
+
|
49
|
+
# or even relays too:
|
50
|
+
# {"names":{"bob":"b0b0...b0"}
|
51
|
+
# "relays":{
|
52
|
+
# "b0b0...b0":["wss://relay.example.com/","wss://relay2.example.com/"]
|
53
|
+
# }
|
54
|
+
# }
|
55
|
+
def self.extract_relays(json)
|
56
|
+
hsh = Nostrb.parse(json).fetch("relays")
|
57
|
+
hsh.each { |pubkey, urls|
|
58
|
+
Nostrb.pubkey!(pubkey)
|
59
|
+
Nostrb.ary!(urls)
|
60
|
+
urls.each { |u| Nostrb.txt!(u) }
|
61
|
+
}
|
62
|
+
hsh
|
63
|
+
end
|
64
|
+
|
65
|
+
# or, if we see bob@example.com
|
66
|
+
# visit: https://example.com/.well-known/nostr.json?name=bob
|
67
|
+
# get the pubkey
|
68
|
+
# subscribe to that user's profile events
|
69
|
+
# check if that user has nip05 and it matches bob@example.com
|
70
|
+
end
|
71
|
+
end
|
data/lib/nostrb/oj.rb
ADDED
data/lib/nostrb/relay.rb
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
require 'nostrb/event'
|
2
|
+
require 'nostrb/filter'
|
3
|
+
require 'nostrb/sqlite'
|
4
|
+
require 'set' # jruby wants this
|
5
|
+
|
6
|
+
# Kind:
|
7
|
+
# 1,4..44,1000..9999: regular -- relay stores all
|
8
|
+
# 0,3: replaceable -- relay stores only the last message from pubkey
|
9
|
+
# 2: deprecated
|
10
|
+
# 10_000..19_999: replaceable -- relay stores latest(pubkey, kind)
|
11
|
+
# 20_000..29_999: ephemeral -- relay doesn't store
|
12
|
+
# 30_000..39_999: parameterized replaceable -- latest(pubkey, kind, dtag)
|
13
|
+
|
14
|
+
# for replaceable events with same timestamp, lowest id wins
|
15
|
+
|
16
|
+
module Nostrb
|
17
|
+
class Server
|
18
|
+
def self.event(sid, event) = ["EVENT", Nostrb.sid!(sid), event.to_h]
|
19
|
+
def self.ok(eid, msg = "", ok: true)
|
20
|
+
["OK", Nostrb.id!(eid), !!ok, ok ? Nostrb.txt!(msg) : Nostrb.help!(msg)]
|
21
|
+
end
|
22
|
+
def self.eose(sid) = ["EOSE", Nostrb.sid!(sid)]
|
23
|
+
def self.closed(sid, msg) = ["CLOSED", Nostrb.sid!(sid), Nostrb.help!(msg)]
|
24
|
+
def self.notice(msg) = ["NOTICE", Nostrb.txt!(msg)]
|
25
|
+
def self.error(e) = notice(message(e))
|
26
|
+
|
27
|
+
def self.message(excp)
|
28
|
+
format("%s: %s", excp.class.name.split('::').last, excp.message)
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize(db_filename = nil, storage: :sqlite)
|
32
|
+
case storage
|
33
|
+
when :sqlite
|
34
|
+
mod = Nostrb::SQLite
|
35
|
+
when :sequel
|
36
|
+
require 'nostrb/sequel'
|
37
|
+
mod = Nostrb::Sequel
|
38
|
+
else
|
39
|
+
raise "unexpected: #{storage.inspect}"
|
40
|
+
end
|
41
|
+
db_filename ||= mod::Storage::FILENAME
|
42
|
+
@reader = mod::Reader.new(db_filename)
|
43
|
+
@writer = mod::Writer.new(db_filename)
|
44
|
+
end
|
45
|
+
|
46
|
+
# accepts a single json array
|
47
|
+
# returns a ruby array of response strings (json array)
|
48
|
+
def ingest(json)
|
49
|
+
begin
|
50
|
+
a = Nostrb.ary!(Nostrb.parse(json))
|
51
|
+
case a[0]
|
52
|
+
when 'EVENT'
|
53
|
+
[handle_event(Nostrb.check!(a[1], Hash))]
|
54
|
+
when 'REQ'
|
55
|
+
sid = Nostrb.sid!(a[1])
|
56
|
+
filters = a[2..-1].map { |f| Filter.ingest(f) }
|
57
|
+
handle_req(sid, *filters)
|
58
|
+
when 'CLOSE'
|
59
|
+
[handle_close(Nostrb.sid!(a[1]))]
|
60
|
+
else
|
61
|
+
[Server.notice("unexpected: #{a[0].inspect}")]
|
62
|
+
end
|
63
|
+
rescue StandardError => e
|
64
|
+
[Server.error(e)]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# return a single response
|
69
|
+
def handle_event(hsh)
|
70
|
+
begin
|
71
|
+
hsh = SignedEvent.validate!(hsh)
|
72
|
+
rescue Nostrb::Error, KeyError, RuntimeError => e
|
73
|
+
return Server.error(e)
|
74
|
+
end
|
75
|
+
|
76
|
+
eid = hsh.fetch('id')
|
77
|
+
|
78
|
+
begin
|
79
|
+
hsh = SignedEvent.verify(hsh)
|
80
|
+
case hsh['kind']
|
81
|
+
when 1, (4..44), (1000..9999)
|
82
|
+
# regular, store all
|
83
|
+
@writer.add_event(hsh)
|
84
|
+
when 0, 3, (10_000..19_999)
|
85
|
+
# replaceable, store latest (pubkey, kind)
|
86
|
+
@writer.add_r_event(hsh)
|
87
|
+
when 20_000..29_999
|
88
|
+
# ephemeral, don't store
|
89
|
+
when 30_000..30_999
|
90
|
+
# parameterized replaceable, store latest (pubkey, kind, dtag)
|
91
|
+
# TODO: implement dtag stuff
|
92
|
+
@writer.add_r_event(hsh)
|
93
|
+
else
|
94
|
+
raise(SignedEvent::Error, "kind: #{hsh['kind']}")
|
95
|
+
end
|
96
|
+
|
97
|
+
Server.ok(eid)
|
98
|
+
rescue SignedEvent::Error => e
|
99
|
+
Server.ok(eid, Server.message(e), ok: false)
|
100
|
+
rescue Nostrb::Error, KeyError, RuntimeError => e
|
101
|
+
Server.error(e)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# return an array of response
|
106
|
+
# filter1:
|
107
|
+
# ids: [id1, id2]
|
108
|
+
# authors: [pubkey1]
|
109
|
+
# filter2:
|
110
|
+
# ids: [id3, id4]
|
111
|
+
# authors: [pubkey2]
|
112
|
+
|
113
|
+
# run filter1
|
114
|
+
# for any fields specified (ids, authors)
|
115
|
+
# if any values match, the event is a match
|
116
|
+
# all fields provided must match for the event to match
|
117
|
+
# ids must have a match and authors must have a match
|
118
|
+
|
119
|
+
# run filter2 just like filter1
|
120
|
+
# the result set is the union of filter1 and filter2
|
121
|
+
|
122
|
+
def handle_req(sid, *filters)
|
123
|
+
responses = Set.new
|
124
|
+
|
125
|
+
filters.each { |f|
|
126
|
+
@reader.process_events(f).each { |h|
|
127
|
+
responses << Server.event(sid, h) if f.match? h
|
128
|
+
}
|
129
|
+
@reader.process_r_events(f).each { |h|
|
130
|
+
responses << Server.event(sid, h) if f.match? h
|
131
|
+
}
|
132
|
+
}
|
133
|
+
responses = responses.to_a
|
134
|
+
responses << Server.eose(sid)
|
135
|
+
end
|
136
|
+
|
137
|
+
# single response
|
138
|
+
def handle_close(sid)
|
139
|
+
Server.closed(sid, "reason: CLOSE requested")
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|