Skip to content

Commit 9ae5798

Browse files
committed
print event in 'validate' and 'filter', apply jq on 'filter' and 'req' with gojq.
1 parent 5930437 commit 9ae5798

7 files changed

Lines changed: 183 additions & 16 deletions

File tree

filter.go

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,13 @@ example:
1919
nak filter '{"kind": 1, "content": "hello"}' '{"kinds": [1]}' -k 0
2020
`,
2121
DisableSliceFlagSeparator: true,
22-
Flags: reqFilterFlags,
23-
ArgsUsage: "[event_json] [base_filter_json]",
22+
Flags: append(append([]cli.Flag{}, reqFilterFlags...),
23+
&cli.StringFlag{
24+
Name: "jq",
25+
Usage: "filter matching events with jq expression",
26+
},
27+
),
28+
ArgsUsage: "[event_json] [base_filter_json]",
2429
Action: func(ctx context.Context, c *cli.Command) error {
2530
args := c.Args().Slice()
2631

@@ -54,6 +59,11 @@ example:
5459
return err
5560
}
5661

62+
jq, err := jqPrepare(c.String("jq"))
63+
if err != nil {
64+
return err
65+
}
66+
5767
// if there is no stdin we'll still get an empty object here
5868
for evtj := range getJsonsOrBlank() {
5969
var evt nostr.Event
@@ -83,7 +93,20 @@ example:
8393
}
8494

8595
if baseFilter.Matches(evt) {
86-
stdout(evt)
96+
var out string
97+
if jq == nil {
98+
out = evt.String()
99+
} else {
100+
v, matches, err := jq(evt)
101+
if err != nil {
102+
return fmt.Errorf("jq filter failed: %w", err)
103+
}
104+
if !matches {
105+
continue
106+
}
107+
out, _ = json.MarshalToString(v)
108+
}
109+
stdout(out)
87110
} else {
88111
logverbose("event %s didn't match %s", evt, baseFilter)
89112
}

go.mod

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ require (
3131
require (
3232
fiatjaf.com/lib v0.3.6
3333
github.com/hanwen/go-fuse/v2 v2.9.0
34+
github.com/itchyny/gojq v0.12.19
3435
)
3536

3637
require (
@@ -54,6 +55,8 @@ require (
5455
github.com/charmbracelet/x/term v0.2.1 // indirect
5556
github.com/chzyer/logex v1.1.10 // indirect
5657
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect
58+
github.com/clipperhouse/stringish v0.1.1 // indirect
59+
github.com/clipperhouse/uax29/v2 v2.3.0 // indirect
5760
github.com/coder/websocket v1.8.14 // indirect
5861
github.com/davecgh/go-spew v1.1.1 // indirect
5962
github.com/decred/dcrd/crypto/blake256 v1.1.0 // indirect
@@ -69,14 +72,15 @@ require (
6972
github.com/gorilla/css v1.0.1 // indirect
7073
github.com/hablullah/go-hijri v1.0.2 // indirect
7174
github.com/hablullah/go-juliandays v1.0.0 // indirect
75+
github.com/itchyny/timefmt-go v0.1.8 // indirect
7276
github.com/jalaali/go-jalaali v0.0.0-20210801064154-80525e88d958 // indirect
7377
github.com/josharian/intern v1.0.0 // indirect
7478
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
7579
github.com/klauspost/compress v1.18.0 // indirect
7680
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
7781
github.com/magefile/mage v1.14.0 // indirect
7882
github.com/mattn/go-colorable v0.1.14 // indirect
79-
github.com/mattn/go-runewidth v0.0.16 // indirect
83+
github.com/mattn/go-runewidth v0.0.19 // indirect
8084
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
8185
github.com/microcosm-cc/bluemonday v1.0.27 // indirect
8286
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
@@ -102,7 +106,7 @@ require (
102106
github.com/yuin/goldmark-emoji v1.0.5 // indirect
103107
golang.org/x/crypto v0.39.0 // indirect
104108
golang.org/x/net v0.41.0 // indirect
105-
golang.org/x/sys v0.35.0 // indirect
109+
golang.org/x/sys v0.38.0 // indirect
106110
golang.org/x/text v0.26.0 // indirect
107111
gopkg.in/yaml.v3 v3.0.1 // indirect
108112
rsc.io/qr v0.2.0 // indirect

go.sum

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
fiatjaf.com/lib v0.3.6 h1:GRZNSxHI2EWdjSKVuzaT+c0aifLDtS16SzkeJaHyJfY=
22
fiatjaf.com/lib v0.3.6/go.mod h1:UlHaZvPHj25PtKLh9GjZkUHRmQ2xZ8Jkoa4VRaLeeQ8=
3-
fiatjaf.com/nostr v0.0.0-20260402062956-72a5be58d755 h1:Tt9XwQMaGaZw2cwujK8IAD/g6FkJC9WWRJuz+7qM1zM=
4-
fiatjaf.com/nostr v0.0.0-20260402062956-72a5be58d755/go.mod h1:iRKV8eYKzePA30MdbaYBpAv8pYQ6to8rDr3W+R2hJzM=
53
fiatjaf.com/nostr v0.0.0-20260416191442-f50b7b0f8dcb h1:zOni3zgiu+hnzZFjt8SAMzmntlAxm+c8T9kEr0qwGRw=
64
fiatjaf.com/nostr v0.0.0-20260416191442-f50b7b0f8dcb/go.mod h1:1cmygNC87Pw06/WjkZqDV+Xo6rV10kpTjzuayosIX4Y=
75
github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ=
@@ -85,6 +83,10 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5O
8583
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
8684
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
8785
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
86+
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
87+
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
88+
github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4=
89+
github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
8890
github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
8991
github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
9092
github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI=
@@ -153,6 +155,10 @@ github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSo
153155
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
154156
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
155157
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
158+
github.com/itchyny/gojq v0.12.19 h1:ttXA0XCLEMoaLOz5lSeFOZ6u6Q3QxmG46vfgI4O0DEs=
159+
github.com/itchyny/gojq v0.12.19/go.mod h1:5galtVPDywX8SPSOrqjGxkBeDhSxEW1gSxoy7tn1iZY=
160+
github.com/itchyny/timefmt-go v0.1.8 h1:1YEo1JvfXeAHKdjelbYr/uCuhkybaHCeTkH8Bo791OI=
161+
github.com/itchyny/timefmt-go v0.1.8/go.mod h1:5E46Q+zj7vbTgWY8o5YkMeYb4I6GeWLFnetPy5oBrAI=
156162
github.com/jalaali/go-jalaali v0.0.0-20210801064154-80525e88d958 h1:qxLoi6CAcXVzjfvu+KXIXJOAsQB62LXjsfbOaErsVzE=
157163
github.com/jalaali/go-jalaali v0.0.0-20210801064154-80525e88d958/go.mod h1:Wqfu7mjUHj9WDzSSPI5KfBclTTEnLveRUFr/ujWnTgE=
158164
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
@@ -192,8 +198,8 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
192198
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
193199
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
194200
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
195-
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
196-
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
201+
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
202+
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
197203
github.com/mattn/go-tty v0.0.7 h1:KJ486B6qI8+wBO7kQxYgmmEFDaFEE96JMBQ7h400N8Q=
198204
github.com/mattn/go-tty v0.0.7/go.mod h1:f2i5ZOvXBU/tCABmLmOfzLz9azMo5wdAaElRNnJKr+k=
199205
github.com/mdp/qrterminal/v3 v3.2.1 h1:6+yQjiiOsSuXT5n9/m60E54vdgFsw0zhADHhHLrFet4=
@@ -324,8 +330,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
324330
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
325331
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
326332
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
327-
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
328-
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
333+
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
334+
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
329335
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
330336
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
331337
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=

jq.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
6+
"fiatjaf.com/nostr"
7+
"github.com/itchyny/gojq"
8+
)
9+
10+
const eventJQPrelude = `
11+
def tags(tagName): .tags | map(select(.[0] == tagName));
12+
def tag(tagName): tags(tagName) | .[0];
13+
def value(tagName): tag(tagName)[1];
14+
def has(tagName): (tags(tagName) | length) > 0;
15+
def hasnt(tagName): (tags(tagName) | length) == 0;
16+
def has_value(tagName; tagValue): tags(tagName) | map(select(.[1] == tagValue)) | length > 0;
17+
`
18+
19+
type jqProcessor func(nostr.Event) (any, bool, error)
20+
21+
func jqPrepare(expr string) (jqProcessor, error) {
22+
if expr == "" {
23+
return nil, nil
24+
}
25+
26+
query, err := gojq.Parse(eventJQPrelude + expr)
27+
if err != nil {
28+
return nil, fmt.Errorf("invalid jq expression: %w", err)
29+
}
30+
31+
code, err := gojq.Compile(query)
32+
if err != nil {
33+
return nil, fmt.Errorf("failed to compile jq expression: %w", err)
34+
}
35+
36+
return func(evt nostr.Event) (any, bool, error) {
37+
input, err := toJQInput(evt)
38+
if err != nil {
39+
return nil, false, err
40+
}
41+
42+
iter := code.Run(input)
43+
for {
44+
v, ok := iter.Next()
45+
if !ok {
46+
return v, false, nil
47+
}
48+
49+
if err, ok := v.(error); ok {
50+
return v, false, err
51+
}
52+
53+
if jqTruthy(v) {
54+
return v, true, nil
55+
}
56+
}
57+
}, nil
58+
}
59+
60+
func toJQInput(v any) (any, error) {
61+
data, err := json.Marshal(v)
62+
if err != nil {
63+
return nil, fmt.Errorf("failed to marshal jq input: %w", err)
64+
}
65+
66+
var input any
67+
if err := json.Unmarshal(data, &input); err != nil {
68+
return nil, fmt.Errorf("failed to unmarshal jq input: %w", err)
69+
}
70+
71+
return input, nil
72+
}
73+
74+
func jqTruthy(v any) bool {
75+
switch v := v.(type) {
76+
case nil:
77+
return false
78+
case bool:
79+
return v
80+
default:
81+
return true
82+
}
83+
}

req.go

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ example:
4444
DisableSliceFlagSeparator: true,
4545
Flags: append(defaultKeyFlags,
4646
append(reqFilterFlags,
47+
&cli.StringFlag{
48+
Name: "jq",
49+
Usage: "filter returned events with jq expression",
50+
},
4751
&cli.StringFlag{
4852
Name: "only-missing",
4953
Usage: "use nip77 negentropy to only fetch events that aren't present in the given jsonl file",
@@ -125,6 +129,14 @@ example:
125129
return fmt.Errorf("relay URLs are incompatible with --bare or --spell")
126130
}
127131

132+
jq, err := jqPrepare(c.String("jq"))
133+
if err != nil {
134+
return err
135+
}
136+
if jq != nil && len(relayUrls) == 0 && !c.Bool("outbox") {
137+
return fmt.Errorf("--jq requires relay URLs or --outbox")
138+
}
139+
128140
if len(relayUrls) > 0 && !negentropy {
129141
// this is used both for the normal AUTH (after "auth-required:" is received) or forced pre-auth
130142
// connect to all relays we expect to use in this call in parallel
@@ -206,6 +218,7 @@ example:
206218

207219
target := PrintingQuerierPublisher{
208220
QuerierPublisher: wrappers.StorePublisher{Store: store, MaxLimit: math.MaxInt},
221+
jq: jq,
209222
}
210223

211224
var source nostr.Querier = nil
@@ -235,7 +248,9 @@ example:
235248
}
236249
}
237250
} else {
238-
performReq(ctx, filter, relayUrls, c.Bool("stream"), c.Bool("outbox"), c.Uint("outbox-relays-per-pubkey"), c.Bool("paginate"), c.Duration("paginate-interval"), "nak-req")
251+
if err := performReq(ctx, filter, relayUrls, c.Bool("stream"), c.Bool("outbox"), c.Uint("outbox-relays-per-pubkey"), c.Bool("paginate"), c.Duration("paginate-interval"), "nak-req", jq); err != nil {
252+
return err
253+
}
239254
}
240255
} else {
241256
// no relays given, will just print the filter or spell
@@ -277,7 +292,8 @@ func performReq(
277292
paginate bool,
278293
paginateInterval time.Duration,
279294
label string,
280-
) {
295+
jq jqProcessor,
296+
) error {
281297
var results chan nostr.RelayEvent
282298
var closeds chan nostr.RelayClosed
283299

@@ -431,7 +447,22 @@ readevents:
431447
if !stillOpen {
432448
break readevents
433449
}
434-
stdout(ie.Event)
450+
451+
var out string
452+
if jq == nil {
453+
out = ie.Event.String()
454+
} else {
455+
v, matches, err := jq(ie.Event)
456+
if err != nil {
457+
return fmt.Errorf("jq filter failed: %w", err)
458+
}
459+
if !matches {
460+
continue
461+
}
462+
out, _ = json.MarshalToString(v)
463+
}
464+
stdout(out)
465+
435466
case closed, stillOpen := <-closeds:
436467
if stillOpen {
437468
if closed.HandledAuth {
@@ -444,6 +475,8 @@ readevents:
444475
break readevents
445476
}
446477
}
478+
479+
return nil
447480
}
448481

449482
var reqFilterFlags = []cli.Flag{
@@ -604,11 +637,25 @@ func applyFlagsToFilter(c *cli.Command, filter *nostr.Filter) error {
604637

605638
type PrintingQuerierPublisher struct {
606639
nostr.QuerierPublisher
640+
jq jqProcessor
607641
}
608642

609643
func (p PrintingQuerierPublisher) Publish(ctx context.Context, evt nostr.Event) error {
610644
if err := p.QuerierPublisher.Publish(ctx, evt); err == nil {
611-
stdout(evt)
645+
var out string
646+
if p.jq == nil {
647+
out = evt.String()
648+
} else {
649+
v, matches, err := p.jq(evt)
650+
if err != nil {
651+
return fmt.Errorf("jq filter failed: %w", err)
652+
}
653+
if !matches {
654+
return nil
655+
}
656+
out, _ = json.MarshalToString(v)
657+
}
658+
stdout(out)
612659
return nil
613660
} else if err == eventstore.ErrDupEvent {
614661
return nil

spell.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,9 @@ func runSpell(
248248

249249
// execute
250250
logSpellDetails(spell)
251-
performReq(ctx, spellFilter, spellRelays, stream, outbox, c.Uint("outbox-relays-per-pubkey"), false, 0, "nak-spell")
251+
if err := performReq(ctx, spellFilter, spellRelays, stream, outbox, c.Uint("outbox-relays-per-pubkey"), false, 0, "nak-spell", nil); err != nil {
252+
return err
253+
}
252254

253255
return nil
254256
}

validate.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ nak event -k 1 -p not_a_pubkey | nak validate
5555
return fmt.Errorf("schema validation failed: %w", err)
5656
}
5757

58+
stdout(evt)
59+
5860
return nil
5961
}
6062

0 commit comments

Comments
 (0)