From c2902bfb5d23e06be6bc7560b74e0e95ccf62f32 Mon Sep 17 00:00:00 2001 From: fiatjaf Date: Wed, 3 Jun 2026 14:21:04 -0300 Subject: [PATCH] nak kind, and accept names on --kind flags. --- README.md | 43 ++++++++++++++++++++++++++++++++--- encode.go | 15 +++++++++++-- event.go | 19 ++++++++++++---- flags.go | 52 +++++++++++++++++++++++++++++++++++++++++++ go.mod | 3 ++- go.sum | 14 ++++++++++-- helpers.go | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++ kind.go | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++++ main.go | 1 + req.go | 19 +++++++++++----- validate.go | 30 +++++++++---------------- 11 files changed, 285 insertions(+), 37 deletions(-) create mode 100644 kind.go diff --git a/README.md b/README.md index 5e22f10..39337b2 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,7 @@ ok. ### sign an event collaboratively with multiple parties using musig2 ```shell -~> nak event --sec 1234 -k 1 -c 'hello from a combined key' --musig 2 +~> nak event --sec 1234 -k 'text note' -c 'hello from a combined key' --musig 2 the following code should be saved secretly until the next step an included with --musig-nonce-secret: QebOT03ERmV7km22CqEqBPFmzAkgxQzGGbR7Si8yIZCBrd1N9A3LKwGLO71kbgXZ9EYFKpjiwun4u0mj5Tq6vwM3pK7x+EI8oHbkt9majKv/QN24Ix8qnwEIHxXX+mXBug== @@ -343,7 +343,7 @@ echo "#surely you're joking, mr npub1l2vyh47mk2p0qlsku7hg0vn29faehy9hy34ygaclpn6 ### record and publish an audio note (yakbak, nostur etc) signed from a bunker ```shell -ffmpeg -f alsa -i default -f webm -t 00:00:03 pipe:1 | nak blossom --server blossom.primal.net upload | jq -rc '{content: .url}' | nak event -k 1222 --sec 'bunker://urlgoeshere' pyramid.fiatjaf.com nostr.wine +ffmpeg -f alsa -i default -f webm -t 00:00:03 pipe:1 | nak blossom --server blossom.primal.net upload | jq -rc '{content: .url}' | nak event -k 'voice message' --sec 'bunker://urlgoeshere' pyramid.fiatjaf.com nostr.wine ``` ### gift-wrap an event to a recipient and publish it somewhere @@ -353,7 +353,7 @@ ffmpeg -f alsa -i default -f webm -t 00:00:03 pipe:1 | nak blossom --server blos ### download a gift-wrap event and unwrap it ```shell -~> nak req -p -k 1059 relay.com | nak gift unwrap --sec --from +~> nak req -p -k 'giftwrap' relay.com | nak gift unwrap --sec --from ``` ### sync events between two relays using negentropy @@ -472,3 +472,40 @@ gitnostr.com... ok. ~> nak group chat "'" ~> nak group chat send "'" "" ``` + +### figure out what is a given kind +```shell +~> nak kind 10050 | jq .description +"Relay list to receive DMs" +~> nak kind 'fav relays' | jq +{ + "kind": 10012, + "description": "Favorite relays list", + "in_use": true, + "content": { + "type": "free" + }, + "multiple": [ + "relay" + ], + "tags": [ + { + "name": "relay", + "next": { + "type": "relay", + "required": true + } + }, + { + "name": "a", + "next": { + "type": "addr", + "required": true, + "next": { + "type": "relay" + } + } + } + ] +} +``` diff --git a/encode.go b/encode.go index a70a682..ca543d6 100644 --- a/encode.go +++ b/encode.go @@ -8,6 +8,7 @@ import ( "fiatjaf.com/nostr" "fiatjaf.com/nostr/nip19" + "fiatjaf.com/nostr/schema" "github.com/urfave/cli/v3" ) @@ -290,7 +291,7 @@ var encode = &cli.Command{ Usage: "pubkey of the naddr author", Aliases: []string{"author", "a", "p"}, }, - &cli.IntFlag{ + &KindFlag{ Name: "kind", Aliases: []string{"k"}, Usage: "kind of referred replaceable event", @@ -305,12 +306,22 @@ var encode = &cli.Command{ Usage: "automatically appends outbox relays to the code", Value: 3, }, + + // hidden + &cli.StringFlag{ + Name: "schema", + Usage: "url to download the YAML schema from, or path to the file", + Value: schema.DefaultSchemaURL, + TakesFile: true, + Destination: &schemaURI, + Hidden: true, + }, }, DisableSliceFlagSeparator: true, Action: func(ctx context.Context, c *cli.Command) error { for target := range getEncodeSubcommandInput(c.Args(), true) { pubkey := getPubKey(c, "pubkey") - kind := nostr.Kind(c.Int("kind")) + kind := getKind(c, "kind") d := c.String("identifier") relays := c.StringSlice("relay") diff --git a/event.go b/event.go index 9c6778a..6dfdc08 100644 --- a/event.go +++ b/event.go @@ -13,6 +13,7 @@ import ( "fiatjaf.com/nostr/keyer" "fiatjaf.com/nostr/nip13" "fiatjaf.com/nostr/nip19" + "fiatjaf.com/nostr/schema" "github.com/fatih/color" "github.com/mailru/easyjson" "github.com/urfave/cli/v3" @@ -104,10 +105,10 @@ example: DefaultText: "false, will only use manually-specified relays", Category: CATEGORY_EXTRAS, }, - &cli.UintFlag{ + &KindFlag{ Name: "kind", Aliases: []string{"k"}, - Usage: "event kind", + Usage: "event kind number or name", DefaultText: "1", Value: 0, Category: CATEGORY_EVENT_FIELDS, @@ -165,6 +166,16 @@ example: Usage: "ask before publishing the event", Category: CATEGORY_EXTRAS, }, + + // hidden + &cli.StringFlag{ + Name: "schema", + Usage: "url to download the YAML schema from, or path to the file", + Value: schema.DefaultSchemaURL, + TakesFile: true, + Destination: &schemaURI, + Hidden: true, + }, ), ArgsUsage: "[relay...]", Action: func(ctx context.Context, c *cli.Command) error { @@ -197,8 +208,8 @@ example: return fmt.Errorf("invalid event received from stdin: %s", err) } - if kind := c.Uint("kind"); slices.Contains(c.FlagNames(), "kind") { - evt.Kind = nostr.Kind(kind) + if c.IsSet("kind") { + evt.Kind = getKind(c, "kind") mustRehashAndResign = true } else if !kindWasSupplied { evt.Kind = 1 diff --git a/flags.go b/flags.go index 8429f70..2c9d022 100644 --- a/flags.go +++ b/flags.go @@ -289,3 +289,55 @@ type ( func getIDSlice(cmd *cli.Command, name string) []nostr.ID { return cmd.Value(name).([]nostr.ID) } + +// +// +// + +type ( + KindFlag = cli.FlagBase[nostr.Kind, struct{}, kindValue] +) + +type kindValue struct { + kind nostr.Kind + hasBeenSet bool +} + +var _ cli.ValueCreator[nostr.Kind, struct{}] = kindValue{} + +func (t kindValue) Create(val nostr.Kind, p *nostr.Kind, c struct{}) cli.Value { + *p = val + return &kindValue{ + kind: val, + } +} + +func (t kindValue) ToString(b nostr.Kind) string { return fmt.Sprintf("%d", b) } + +func (t *kindValue) Set(value string) error { + k, err := stringToKind(value) + t.kind = k + t.hasBeenSet = true + return err +} + +func (t *kindValue) String() string { return fmt.Sprintf("%#v", t.kind) } +func (t *kindValue) Value() nostr.Kind { return t.kind } +func (t *kindValue) Get() any { return t.kind } + +func getKind(cmd *cli.Command, name string) nostr.Kind { + return cmd.Value(name).(nostr.Kind) +} + +// +// +// + +type ( + kindSlice = cli.SliceBase[nostr.Kind, struct{}, kindValue] + KindSliceFlag = cli.FlagBase[[]nostr.Kind, struct{}, kindSlice] +) + +func getKindSlice(cmd *cli.Command, name string) []nostr.Kind { + return cmd.Value(name).([]nostr.Kind) +} diff --git a/go.mod b/go.mod index decdbc1..da31794 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/fiatjaf/nak go 1.25 require ( - fiatjaf.com/nostr v0.0.0-20260602223326-015842e96d86 + fiatjaf.com/nostr v0.0.0-20260603164911-395c9609550b github.com/AlecAivazis/survey/v2 v2.3.7 github.com/bep/debounce v1.2.1 github.com/btcsuite/btcd/btcec/v2 v2.3.6 @@ -77,6 +77,7 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/klauspost/compress v1.18.0 // indirect + github.com/lithammer/fuzzysearch v1.1.8 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/magefile/mage v1.14.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect diff --git a/go.sum b/go.sum index ca6a0e0..9db5b11 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ fiatjaf.com/lib v0.3.7 h1:mXZOn7NrUcjSdy4oNvwQyAmes7Ueb+Zr5hjqMIe2dxI= fiatjaf.com/lib v0.3.7/go.mod h1:UlHaZvPHj25PtKLh9GjZkUHRmQ2xZ8Jkoa4VRaLeeQ8= -fiatjaf.com/nostr v0.0.0-20260602223326-015842e96d86 h1:p3HnX1UDT/CfiMvTc4yTcxHQm08ri7DM32P1uKkFNKg= -fiatjaf.com/nostr v0.0.0-20260602223326-015842e96d86/go.mod h1:b1EIUDnd133Ie8Pg8O/biaKdFyCMz28aD4n64g1GqvM= +fiatjaf.com/nostr v0.0.0-20260603164911-395c9609550b h1:uFCYH+AyyhyqL9a04BEJQ/vEZ9QP793mJWXUkIHKxWc= +fiatjaf.com/nostr v0.0.0-20260603164911-395c9609550b/go.mod h1:b1EIUDnd133Ie8Pg8O/biaKdFyCMz28aD4n64g1GqvM= github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ= github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo= github.com/FastFilter/xorfilter v0.2.1 h1:lbdeLG9BdpquK64ZsleBS8B4xO/QW1IM0gMzF7KaBKc= @@ -181,6 +181,8 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/liamg/magic v0.0.1 h1:Ru22ElY+sCh6RvRTWjQzKKCxsEco8hE0co8n1qe7TBM= github.com/liamg/magic v0.0.1/go.mod h1:yQkOmZZI52EA+SQ2xyHpVw8fNvTBruF873Y+Vt6S+fk= +github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4= +github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo= @@ -300,6 +302,7 @@ golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632 golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 h1:zfMcR1Cs4KNuomFFgGefv5N0czO2XZpUbxGUy8i8ug0= golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -308,11 +311,13 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -329,11 +334,13 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -341,11 +348,14 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/helpers.go b/helpers.go index 4570a67..10f62da 100644 --- a/helpers.go +++ b/helpers.go @@ -13,6 +13,7 @@ import ( "path/filepath" "runtime" "slices" + "strconv" "strings" "sync" "time" @@ -22,10 +23,12 @@ import ( "fiatjaf.com/nostr/nip05" "fiatjaf.com/nostr/nip19" "fiatjaf.com/nostr/nip42" + "fiatjaf.com/nostr/schema" "fiatjaf.com/nostr/sdk" "github.com/chzyer/readline" "github.com/fatih/color" jsoniter "github.com/json-iterator/go" + "github.com/lithammer/fuzzysearch/fuzzy" "github.com/mattn/go-isatty" "github.com/mattn/go-tty/v2" "github.com/urfave/cli/v3" @@ -610,6 +613,65 @@ func clampWithEllipsis(s string, size int) string { return s[0:size-1] + "…" } +var ( + schemaURI string + fetchSchemaOnce sync.Once + schemaCache schema.Schema + schemaErrCache error +) + +func getSchema() (schema.Schema, error) { + fetchSchemaOnce.Do(func() { + if strings.HasPrefix(schemaURI, "http") { + schemaCache, schemaErrCache = schema.FetchSchemaFromURL(schemaURI) + } else { + schemaCache, schemaErrCache = schema.NewSchemaFromFile(schemaURI) + } + }) + return schemaCache, schemaErrCache +} + +func stringToKind(value string) (nostr.Kind, error) { + if n, err := strconv.Atoi(value); err == nil && n >= 0 { + return nostr.Kind(n), nil + } + + // find kind from name + sch, err := getSchema() + if err != nil { + return 0, err + } + + fuzzyWords := make([]string, 0, len(sch.Kinds)) + fuzzyIndexes := make([]string, 0, len(sch.Kinds)) + + // exact match + for k, ks := range sch.Kinds { + fuzzyWords = append(fuzzyWords, ks.Description) + fuzzyIndexes = append(fuzzyIndexes, k) + + if strings.EqualFold(ks.Description, value) { + return ks.Kind, nil + } + } + + // fuzzy match + result := fuzzy.RankFindNormalizedFold(value, fuzzyWords) + bestDesc := "" + bestDist := "-" + + if len(result) > 0 { + if bd := result[0].Distance; bd < 26 { + return sch.Kinds[fuzzyIndexes[result[0].OriginalIndex]].Kind, nil + } else { + bestDesc = sch.Kinds[fuzzyIndexes[result[0].OriginalIndex]].Description + bestDist = strconv.Itoa(bd) + } + } + + return 0, fmt.Errorf("unknown kind: %q (closest: %q, distance: %s)", value, bestDesc, bestDist) +} + var colors = struct { reset func(...any) (int, error) italic func(...any) string diff --git a/kind.go b/kind.go new file mode 100644 index 0000000..7811a60 --- /dev/null +++ b/kind.go @@ -0,0 +1,64 @@ +package main + +import ( + "context" + "fmt" + "strconv" + + "fiatjaf.com/nostr" + "fiatjaf.com/nostr/schema" + "github.com/urfave/cli/v3" +) + +var kindCmd = &cli.Command{ + Name: "kind", + Usage: "look up information about a Nostr event kind", + Description: `takes a kind number or a kind name and prints information about that kind from the registry of kinds schema. + +example: + nak kind 1 + nak kind "text note" + nak kind "git meta" + nak kind "fav relays" + nak kind 30023`, + DisableSliceFlagSeparator: true, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "schema", + Usage: "url to download the YAML schema from, or path to the file", + Value: schema.DefaultSchemaURL, + TakesFile: true, + Destination: &schemaURI, + }, + }, + Action: func(ctx context.Context, c *cli.Command) error { + if c.Args().Len() != 1 { + return fmt.Errorf("requires exactly one argument: kind number or name") + } + + input := c.Args().First() + + // resolve input to a kind number + var k nostr.Kind + if n, err := strconv.ParseUint(input, 10, 16); err == nil && n >= 0 { + k = nostr.Kind(n) + } else { + resolved, err := stringToKind(input) + if err != nil { + return fmt.Errorf("failed to resolve kind: %w", err) + } + k = resolved + } + + sch, err := getSchema() + if err != nil { + return err + } + + ks := sch.Kinds[strconv.Itoa(int(k.Num()))] + + j, _ := json.MarshalIndent(ks, "", " ") + stdout(string(j)) + return nil + }, +} diff --git a/main.go b/main.go index 10457fc..d09f4eb 100644 --- a/main.go +++ b/main.go @@ -33,6 +33,7 @@ var app = &cli.Command{ decode, encode, key, + kindCmd, verify, relay, admin, diff --git a/req.go b/req.go index 2f0386c..19f813e 100644 --- a/req.go +++ b/req.go @@ -17,6 +17,7 @@ import ( "fiatjaf.com/nostr/eventstore/wrappers" "fiatjaf.com/nostr/nip42" "fiatjaf.com/nostr/nip77" + "fiatjaf.com/nostr/schema" "github.com/fatih/color" "github.com/mailru/easyjson" "github.com/urfave/cli/v3" @@ -503,10 +504,10 @@ var reqFilterFlags = []cli.Flag{ Usage: "only accept events with these ids", Category: CATEGORY_FILTER_ATTRIBUTES, }, - &cli.IntSliceFlag{ + &KindSliceFlag{ Name: "kind", Aliases: []string{"k"}, - Usage: "only accept events with these kind numbers", + Usage: "only accept events with these kind numbers or kind names", Category: CATEGORY_FILTER_ATTRIBUTES, }, &cli.StringSliceFlag{ @@ -558,6 +559,16 @@ var reqFilterFlags = []cli.Flag{ Usage: "a nip50 search query, use it only with relays that explicitly support it", Category: CATEGORY_FILTER_ATTRIBUTES, }, + + // hidden + &cli.StringFlag{ + Name: "schema", + Usage: "url to download the YAML schema from, or path to the file", + Value: schema.DefaultSchemaURL, + TakesFile: true, + Destination: &schemaURI, + Hidden: true, + }, } type flagTag struct { @@ -587,9 +598,7 @@ func applyFlagsToFilter(c *cli.Command, filter *nostr.Filter) error { if ids := getIDSlice(c, "id"); len(ids) > 0 { filter.IDs = append(filter.IDs, ids...) } - for _, kind64 := range c.IntSlice("kind") { - filter.Kinds = append(filter.Kinds, nostr.Kind(kind64)) - } + filter.Kinds = getKindSlice(c, "kind") if search := c.String("search"); search != "" { filter.Search = search } diff --git a/validate.go b/validate.go index defadc5..57f6aef 100644 --- a/validate.go +++ b/validate.go @@ -3,7 +3,6 @@ package main import ( "context" "fmt" - "strings" "fiatjaf.com/nostr" "fiatjaf.com/nostr/schema" @@ -22,29 +21,21 @@ nak event -k 1 -p not_a_pubkey | nak validate DisableSliceFlagSeparator: true, Flags: []cli.Flag{ &cli.StringFlag{ - Name: "schema", - Usage: "url to download the YAML schema from, or path to the file", - Value: "https://raw.githubusercontent.com/nostr-protocol/registry-of-kinds/refs/heads/master/schema.yaml", - TakesFile: true, + Name: "schema", + Usage: "url to download the YAML schema from, or path to the file", + Value: schema.DefaultSchemaURL, + TakesFile: true, + Destination: &schemaURI, }, }, Action: func(ctx context.Context, c *cli.Command) error { - var validator schema.Validator - - if schemaURL := c.String("schema"); strings.HasPrefix(schemaURL, "http") { - var err error - validator, err = schema.NewValidatorFromURL(schemaURL) - if err != nil { - return fmt.Errorf("failed to instantiate validator from '%s': %w", schemaURL, err) - } - } else { - var err error - validator, err = schema.NewValidatorFromFile(schemaURL) - if err != nil { - return fmt.Errorf("failed to instantiate validator from %s: %w", schemaURL, err) - } + sch, err := getSchema() + if err != nil { + return err } + validator := schema.NewValidatorFromSchema(sch) + handleEvent := func(stdinEvent string) error { evt := nostr.Event{} if err := json.Unmarshal([]byte(stdinEvent), &evt); err != nil { @@ -56,7 +47,6 @@ nak event -k 1 -p not_a_pubkey | nak validate } stdout(evt) - return nil }