mirror of
https://github.com/fiatjaf/nak.git
synced 2025-12-09 00:58:50 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e18e8c00e7 | ||
|
|
bca4362ca5 | ||
|
|
54c4be10bd | ||
|
|
27f925c05e | ||
|
|
79cb63a1b4 | ||
|
|
5ee0036128 | ||
|
|
316d94166e | ||
|
|
2ca6bb0940 | ||
|
|
ac00c5065f |
90
README.md
90
README.md
@@ -79,9 +79,95 @@ invalid .id, expected 05bd99d54cb835f427e0092c4275ee44c7ff51219eff417c19f70c9e2c
|
||||
|
||||
### fetch all quoted events by a given pubkey in their last 100 notes
|
||||
```shell
|
||||
nak req -l 100 -k 1 -a 2edbcea694d164629854a52583458fd6d965b161e3c48b57d3aff01940558884 relay.damus.io | jq -r '.content | match("nostr:((note1|nevent1)[a-z0-9]+)";"g") | .captures[0].string' | nak decode | jq -cr '{ids: [.id]}' | nak req relay.damus.io
|
||||
~> nak req -l 100 -k 1 -a 2edbcea694d164629854a52583458fd6d965b161e3c48b57d3aff01940558884 relay.damus.io | jq -r '.content | match("nostr:((note1|nevent1)[a-z0-9]+)";"g") | .captures[0].string' | nak decode | jq -cr '{ids: [.id]}' | nak req relay.damus.io
|
||||
connecting to relay.damus.io...
|
||||
ok.
|
||||
{"kind":1,"id":"dad32411fea62fda6ae057e97c73402f2031913388a721e059728a0efee5f0dd","pubkey":"5b0183ab6c3e322bf4d41c6b3aef98562a144847b7499543727c5539a114563e","created_at":1709057416,"tags":[["p","ad9d42203fd2480ea2e5c4c64593a027708aebe2b02aa60bd7b1d666daa5b08d"],["p","5ea721dd7828229a39a372477090208db30a6c2d357951b8ae504d2ecf86c06c"]],"content":"Fridays Edition of nostr:npub14kw5ygpl6fyqagh9cnrytyaqyacg46lzkq42vz7hk8txdk49kzxs04j7y0 will feature nostr:npub1t6njrhtc9q3f5wdrwfrhpypq3kes5mpdx4u4rw9w2pxjanuxcpkqveagv3 \n\nWe will be diving into Bitcoin, Content Creating, Music and the future of V4V. \n\nNostr Nest 2.0 🤘🏻\nSet your BlockClocks for 4:30EST","sig":"69cc403982e6c5fe996d545d6057c581a46be97ab79d818c1bc01e84e9f11a64a275d8834a4063a59fa135f7f116e38c51125173d5ce88671a4ddc2f656e01e4"}
|
||||
{"kind":1,"id":"a681e9ca594dc455018be0a1c895576a8264956aee3e4fc01b872aa6df580632","pubkey":"5b0183ab6c3e322bf4d41c6b3aef98562a144847b7499543727c5539a114563e","created_at":1708456729,"tags":[["p","ad9d42203fd2480ea2e5c4c64593a027708aebe2b02aa60bd7b1d666daa5b08d"],["t","plebchain"],["p","4ce6abbd68dab6e9fdf6e8e9912a8e12f9b539e078c634c55a9bff2994a514dd"],["p","b83a28b7e4e5d20bd960c5faeb6625f95529166b8bdb045d42634a2f35919450"],["p","5a9c48c8f4782351135dd89c5d8930feb59cb70652ffd37d9167bf922f2d1069"],["p","f8e6c64342f1e052480630e27e1016dce35fc3a614e60434fef4aa2503328ca9"],["p","2edbcea694d164629854a52583458fd6d965b161e3c48b57d3aff01940558884"],["r","https://nostrnests.com/plebchainradio"]],"content":"This weeks edition of nostr:npub14kw5ygpl6fyqagh9cnrytyaqyacg46lzkq42vz7hk8txdk49kzxs04j7y0 will feature the wildly talented #Plebchain Legend nostr:npub1fnn2h0tgm2mwnl0kar5ez25wztum2w0q0rrrf326n0ljn999znwsqf4xnx \n\nWe will be discussing building his local community in Tanzania, his worldly success in V4V music and all things Nostr and Bitcoin. \n\nFilling in for nostr:npub1hqaz3dlyuhfqhktqchawke39l92jj9nt30dsgh2zvd9z7dv3j3gqpkt56s this week will be nostr:npub1t2wy3j850q34zy6amzw9mzfsl66eedcx2tlaxlv3v7leytedzp5szs8c2u \u0026 nostr:npub1lrnvvs6z78s9yjqxxr38uyqkmn34lsaxznnqgd877j4z2qej3j5s09qnw5 from nostr:npub19mduaf5569jx9xz555jcx3v06mvktvtpu0zgk47n4lcpjsz43zzqhj6vzk \n\nSet your Blockclocks for this Friday 4PM EST! \nhttps://nostrnests.com/plebchainradio","sig":"b4528c7a248bf04ab9fcd0ce8033fdc9656b0e92dccf5f3a6b8cd7ad66cf074619100c7d192ae9a87745bc5445f6fe36221c1fd5820d5038bbcae2aedb5090d8"}
|
||||
{"kind":1,"id":"cc396bbc9e01910e56ef169916c39197d468b65e80c42aaa0a874a32500039c4","pubkey":"b9d02cb8fddeb191701ec0648e37ed1f6afba263e0060fc06099a62851d25e04","created_at":1708210291,"tags":[["imeta","url https://image.nostr.build/185e4c10dc2e46dacf6ad38fbc4319f52860fc3f96efb433be55bdb91fc225b8.jpg","blurhash eUGR9vR*4pt6OqK0WVspWWoL0fso%eWBwM+~aySxs:S2tQW;VtWVW;","dim 4032x3024"],["p","f133b246f07633fde1a894133ac270ab8750502b64a9779c0bac3c9228198dda"],["p","5ea721dd7828229a39a372477090208db30a6c2d357951b8ae504d2ecf86c06c"],["t","cultureshock"],["r","https://image.nostr.build/185e4c10dc2e46dacf6ad38fbc4319f52860fc3f96efb433be55bdb91fc225b8.jpg"]],"content":"10 minutes.\n\nFireside chat between nostr:npub17yemy3hswcelmcdgjsfn4sns4wr4q5ptvj5h08qt4s7fy2qe3hdqsczs99 and nostr:npub1t6njrhtc9q3f5wdrwfrhpypq3kes5mpdx4u4rw9w2pxjanuxcpkqveagv3 followed by live set to close down #CultureShock.\n\nStreaming V4V at tunestr.io https://image.nostr.build/185e4c10dc2e46dacf6ad38fbc4319f52860fc3f96efb433be55bdb91fc225b8.jpg ","sig":"b0464529b2d2de2b4df911d47dbfe4aa31ac8f2db285b1a5930941cf1877425c14d6901d1be1bfaa13cd3d981d5e9a722debfc47c4f087d95da628a7035437ec"}
|
||||
{"kind":1,"id":"f24aed86b493f266952ed35d4724582946cdf73985581f9986641f81fad6b73d","pubkey":"fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52","created_at":1707912005,"tags":[["p","fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52","wss://140.f7z.io/","mention"]],"content":"Releasing: Whynotstr 0.0.0 😂\n\nCollaborative editing, the left-side-of-the-curve approach\n\nTry it out: https://collab-lemon.vercel.app/\n\nObviously open source: https://github.com/pablof7z/collab\n\nnostr:nevent1qvzqqqqqqypzp75cf0tahv5z7plpdeaws7ex52nmnwgtwfr2g3m37r844evqrr6jqy88wumn8ghj7mn0wvhxcmmv9uq35amnwvaz7tms09exzmtfvshxv6tpw34xze3wvdhk6tcqyznqm6guz9k38dpqtc84s7jflec7m7wpzvtx2xjkjkm2xm89ucy2jhdcnur","sig":"6f562d733e50f5934dcf359a4f16dece1734302c0cc3a793ee2f08007ccb4ade3591373a633538617611f327feb7534ad4d11a8475163c7f734a01c63e52b79f"}
|
||||
...
|
||||
```
|
||||
|
||||
## Contributing to this repository
|
||||
### sign an event collaboratively with multiple parties using musig2
|
||||
```shell
|
||||
~> nak event --sec 1234 -k 1 -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==
|
||||
|
||||
the next signer and they should call this on their side:
|
||||
nak event --sec <insert-secret-key> --musig 2 -k 1 -ts 1720821287 -c 'hello from a combined key' --musig-pubkey 0337a4aef1f8423ca076e4b7d99a8cabff40ddb8231f2a9f01081f15d7fa65c1ba --musig-nonce 0285af37c6c43638cda2c773098e867c749ddf1e9d096b78686c5d000603935ad3025c4a1e042eb6b0dcfd864d1e072d2ce8da06f2c0dcf13fd7d1fcef0dd26dbc92
|
||||
```
|
||||
|
||||
demo videos with [2](https://njump.me/nevent1qqs8pmmae89agph80928l6gjm0wymechqazv80jwqrqy4cgk08epjaczyqalp33lewf5vdq847t6te0wvnags0gs0mu72kz8938tn24wlfze674zkzz), [3](https://njump.me/nevent1qqsrp320drqcnmnam6jvmdd4lgdvh2ay0xrdesrvy6q9qqdfsk7r55qzyqalp33lewf5vdq847t6te0wvnags0gs0mu72kz8938tn24wlfze6c32d4m) and [4](https://njump.me/nevent1qqsre84xe6qpagf2w2xjtjwc95j4dd5ccue68gxl8grkd6t6hjhaj5qzyqalp33lewf5vdq847t6te0wvnags0gs0mu72kz8938tn24wlfze6t8t7ak) parties.
|
||||
|
||||
### generate a private key
|
||||
```shell
|
||||
~> nak key generate 18:59
|
||||
7b94e287b1fafa694ded1619b27de7effd3646104a158e187ff4edc56bc6148d
|
||||
```
|
||||
|
||||
### encrypt key with NIP-49
|
||||
```shell
|
||||
~> nak key encrypt 7b94e287b1fafa694ded1619b27de7effd3646104a158e187ff4edc56bc6148d mypassword
|
||||
ncryptsec1qggx54cg270zy9y8krwmfz29jyypsuxken2fkk99gr52qhje968n6mwkrfstqaqhq9eq94pnzl4nff437l4lp4ur2cs4f9um8738s35l2esx2tas48thtfhrk5kq94pf9j2tpk54yuermra0xu6hl5ls
|
||||
```
|
||||
|
||||
### decrypt key with NIP-49
|
||||
```shell
|
||||
~> nak key decrypt ncryptsec1qggx54cg270zy9y8krwmfz29jyypsuxken2fkk99gr52qhje968n6mwkrfstqaqhq9eq94pnzl4nff437l4lp4ur2cs4f9um8738s35l2esx2tas48thtfhrk5kq94pf9j2tpk54yuermra0xu6hl5ls mypassword
|
||||
7b94e287b1fafa694ded1619b27de7effd3646104a158e187ff4edc56bc6148d
|
||||
~>
|
||||
~> nak key decrypt ncryptsec1qggx54cg270zy9y8krwmfz29jyypsuxken2fkk99gr52qhje968n6mwkrfstqaqhq9eq94pnzl4nff437l4lp4ur2cs4f9um8738s35l2esx2tas48thtfhrk5kq94pf9j2tpk54yuermra0xu6hl5ls
|
||||
type the password to decrypt your secret key: **********
|
||||
7b94e287b1fafa694ded1619b27de7effd3646104a158e187ff4edc56bc6148d
|
||||
```
|
||||
|
||||
### get a public key from a private key
|
||||
```shell
|
||||
~> nak key public 7b94e287b1fafa694ded1619b27de7effd3646104a158e187ff4edc56bc6148d
|
||||
985d66d2644dfa7676e26046914470d66ebc7fa783a3f57f139fde32d0d631d7
|
||||
```
|
||||
|
||||
### sign an event using a remote NIP-46 bunker
|
||||
```shell
|
||||
~> nak event --connect 'bunker://a9e0f110f636f3191644110c19a33448daf09d7cda9708a769e91b7e91340208?relay=wss%3A%2F%2Frelay.damus.io&relay=wss%3A%2F%2Frelay.nsecbunker.com&relay=wss%3A%2F%2Fnos.lol&secret=TWfGbjQCLxUf' -c 'hello from bunker'
|
||||
```
|
||||
|
||||
### sign an event using a NIP-49 encrypted key
|
||||
```shell
|
||||
~> nak event --sec ncryptsec1qggx54cg270zy9y8krwmfz29jyypsuxken2fkk99gr52qhje968n6mwkrfstqaqhq9eq94pnzl4nff437l4lp4ur2cs4f9um8738s35l2esx2tas48thtfhrk5kq94pf9j2tpk54yuermra0xu6hl5ls -c 'hello from encrypted key'
|
||||
type the password to decrypt your secret key: **********
|
||||
{"kind":1,"id":"8aa5c931fb1da507f14801de6a1814b7f0baae984dc502b9889f347f5aa3cc4e","pubkey":"985d66d2644dfa7676e26046914470d66ebc7fa783a3f57f139fde32d0d631d7","created_at":1720822280,"tags":[],"content":"hello from encrypted key","sig":"9d1c9e56e87f787cc5b6191ec47690ce59fa4bef105b56297484253953e18fb930f6683f007e84a9ce9dc9a25b20c191c510629156dcd24bd16e15d302d20944"}
|
||||
```
|
||||
|
||||
### talk to a relay's NIP-86 management API
|
||||
```shell
|
||||
nak relay allowpubkey --sec ncryptsec1qggx54cg270zy9y8krwmfz29jyypsuxken2fkk99gr52qhje968n6mwkrfstqaqhq9eq94pnzl4nff437l4lp4ur2cs4f9um8738s35l2esx2tas48thtfhrk5kq94pf9j2tpk54yuermra0xu6hl5ls --pubkey a9e0f110f636f3191644110c19a33448daf09d7cda9708a769e91b7e91340208 pyramid.fiatjaf.com
|
||||
type the password to decrypt your secret key: **********
|
||||
calling 'allowpubkey' on https://pyramid.fiatjaf.com...
|
||||
{
|
||||
"result": null,
|
||||
"error": "failed to add to whitelist: pubkey 985d66d2644dfa7676e26046914470d66ebc7fa783a3f57f139fde32d0d631d7 doesn't have permission to invite"
|
||||
}
|
||||
```
|
||||
|
||||
### start a bunker locally
|
||||
```shell
|
||||
~> nak bunker --sec ncryptsec1qggrp80ptf0s7kyl0r38ktzg60fem85m89uz7um6rjn4pnep2nnvcgqm8h7q36c76z9sypatdh4fmw6etfxu99mv5cxkw4ymcsryw0zz7evyuplsgvnj5yysf449lq94klzvnahsw2lzxflvcq4qpf5q -k 3fbf7fbb2a2111e205f74aca0166e29e421729c9a07bc45aa85d39535b47c9ed relay.damus.io nos.lol relay.nsecbunker.com
|
||||
connecting to relay.damus.io... ok.
|
||||
connecting to nos.lol... ok.
|
||||
connecting to relay.nsecbunker.com... ok.
|
||||
type the password to decrypt your secret key: ***
|
||||
listening at [wss://relay.damus.io wss://nos.lol wss://relay.nsecbunker.com]:
|
||||
pubkey: f59911b561c37c90b01e9e5c2557307380835c83399756f4d62d8167227e420a
|
||||
npub: npub17kv3rdtpcd7fpvq7newz24eswwqgxhyr8xt4daxk9kqkwgn7gg9q4gy8vf
|
||||
authorized keys:
|
||||
- 3fbf7fbb2a2111e205f74aca0166e29e421729c9a07bc45aa85d39535b47c9ed
|
||||
to restart: nak bunker --sec ncryptsec1qggrp80ptf0s7kyl0r38ktzg60fem85m89uz7um6rjn4pnep2nnvcgqm8h7q36c76z9sypatdh4fmw6etfxu99mv5cxkw4ymcsryw0zz7evyuplsgvnj5yysf449lq94klzvnahsw2lzxflvcq4qpf5q -k 3fbf7fbb2a2111e205f74aca0166e29e421729c9a07bc45aa85d39535b47c9ed relay.damus.io nos.lol relay.nsecbunker.com
|
||||
bunker: bunker://f59911b561c37c90b01e9e5c2557307380835c83399756f4d62d8167227e420a?relay=wss%3A%2F%2Frelay.damus.io&relay=wss%3A%2F%2Fnos.lol&relay=wss%3A%2F%2Frelay.nsecbunker.com&secret=XuuiMbcLwuwL
|
||||
```
|
||||
|
||||
## contributing to this repository
|
||||
|
||||
Use NIP-34 to send your patches to `naddr1qqpkucttqy28wumn8ghj7un9d3shjtnwdaehgu3wvfnsz9nhwden5te0wfjkccte9ehx7um5wghxyctwvsq3gamnwvaz7tmjv4kxz7fwv3sk6atn9e5k7q3q80cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkwsxpqqqpmej2wctpn`.
|
||||
|
||||
@@ -50,7 +50,7 @@ var bunker = &cli.Command{
|
||||
qs := url.Values{}
|
||||
relayURLs := make([]string, 0, c.Args().Len())
|
||||
if relayUrls := c.Args().Slice(); len(relayUrls) > 0 {
|
||||
_, relays := connectToAllRelays(ctx, relayUrls)
|
||||
_, relays := connectToAllRelays(ctx, relayUrls, false)
|
||||
if len(relays) == 0 {
|
||||
log("failed to connect to any of the given relays.\n")
|
||||
os.Exit(3)
|
||||
|
||||
@@ -50,7 +50,7 @@ var decode = &cli.Command{
|
||||
decodeResult.HexResult.PrivateKey = hex.EncodeToString(b)
|
||||
decodeResult.HexResult.PublicKey = hex.EncodeToString(b)
|
||||
} else {
|
||||
lineProcessingError(ctx, "hex string with invalid number of bytes: %d", len(b))
|
||||
ctx = lineProcessingError(ctx, "hex string with invalid number of bytes: %d", len(b))
|
||||
continue
|
||||
}
|
||||
} else if evp := sdk.InputToEventPointer(input); evp != nil {
|
||||
@@ -64,7 +64,7 @@ var decode = &cli.Command{
|
||||
decodeResult.PrivateKey.PrivateKey = value.(string)
|
||||
decodeResult.PrivateKey.PublicKey, _ = nostr.GetPublicKey(value.(string))
|
||||
} else {
|
||||
lineProcessingError(ctx, "couldn't decode input '%s': %s", input, err)
|
||||
ctx = lineProcessingError(ctx, "couldn't decode input '%s': %s", input, err)
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
12
encode.go
12
encode.go
@@ -32,7 +32,7 @@ var encode = &cli.Command{
|
||||
Action: func(ctx context.Context, c *cli.Command) error {
|
||||
for target := range getStdinLinesOrArguments(c.Args()) {
|
||||
if ok := nostr.IsValidPublicKey(target); !ok {
|
||||
lineProcessingError(ctx, "invalid public key: %s", target)
|
||||
ctx = lineProcessingError(ctx, "invalid public key: %s", target)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ var encode = &cli.Command{
|
||||
Action: func(ctx context.Context, c *cli.Command) error {
|
||||
for target := range getStdinLinesOrArguments(c.Args()) {
|
||||
if ok := nostr.IsValid32ByteHex(target); !ok {
|
||||
lineProcessingError(ctx, "invalid private key: %s", target)
|
||||
ctx = lineProcessingError(ctx, "invalid private key: %s", target)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ var encode = &cli.Command{
|
||||
Action: func(ctx context.Context, c *cli.Command) error {
|
||||
for target := range getStdinLinesOrArguments(c.Args()) {
|
||||
if ok := nostr.IsValid32ByteHex(target); !ok {
|
||||
lineProcessingError(ctx, "invalid public key: %s", target)
|
||||
ctx = lineProcessingError(ctx, "invalid public key: %s", target)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ var encode = &cli.Command{
|
||||
Action: func(ctx context.Context, c *cli.Command) error {
|
||||
for target := range getStdinLinesOrArguments(c.Args()) {
|
||||
if ok := nostr.IsValid32ByteHex(target); !ok {
|
||||
lineProcessingError(ctx, "invalid event id: %s", target)
|
||||
ctx = lineProcessingError(ctx, "invalid event id: %s", target)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@ var encode = &cli.Command{
|
||||
if d == "" {
|
||||
d = c.String("identifier")
|
||||
if d == "" {
|
||||
lineProcessingError(ctx, "\"d\" tag identifier can't be empty")
|
||||
ctx = lineProcessingError(ctx, "\"d\" tag identifier can't be empty")
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -216,7 +216,7 @@ var encode = &cli.Command{
|
||||
Action: func(ctx context.Context, c *cli.Command) error {
|
||||
for target := range getStdinLinesOrArguments(c.Args()) {
|
||||
if ok := nostr.IsValid32ByteHex(target); !ok {
|
||||
lineProcessingError(ctx, "invalid event id: %s", target)
|
||||
ctx = lineProcessingError(ctx, "invalid event id: %s", target)
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
4
event.go
4
event.go
@@ -145,7 +145,7 @@ example:
|
||||
// try to connect to the relays here
|
||||
var relays []*nostr.Relay
|
||||
if relayUrls := c.Args().Slice(); len(relayUrls) > 0 {
|
||||
_, relays = connectToAllRelays(ctx, relayUrls)
|
||||
_, relays = connectToAllRelays(ctx, relayUrls, false)
|
||||
if len(relays) == 0 {
|
||||
log("failed to connect to any of the given relays.\n")
|
||||
os.Exit(3)
|
||||
@@ -176,7 +176,7 @@ example:
|
||||
|
||||
if stdinEvent != "" {
|
||||
if err := easyjson.Unmarshal([]byte(stdinEvent), &evt); err != nil {
|
||||
lineProcessingError(ctx, "invalid event received from stdin: %s", err)
|
||||
ctx = lineProcessingError(ctx, "invalid event received from stdin: %s", err)
|
||||
continue
|
||||
}
|
||||
kindWasSupplied = strings.Contains(stdinEvent, `"kind"`)
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
package main
|
||||
|
||||
import "os"
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
)
|
||||
|
||||
var ctx = context.Background()
|
||||
|
||||
func ExampleEventBasic() {
|
||||
app.Run([]string{"nak", "event", "--ts", "1699485669"})
|
||||
app.Run(ctx, []string{"nak", "event", "--ts", "1699485669"})
|
||||
// Output:
|
||||
// {"kind":1,"id":"36d88cf5fcc449f2390a424907023eda7a74278120eebab8d02797cd92e7e29c","pubkey":"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798","created_at":1699485669,"tags":[],"content":"hello from the nostr army knife","sig":"68e71a192e8abcf8582a222434ac823ecc50607450ebe8cc4c145eb047794cc382dc3f888ce879d2f404f5ba6085a47601360a0fa2dd4b50d317bd0c6197c2c2"}
|
||||
}
|
||||
@@ -15,22 +20,22 @@ func ExampleEventParsingFromStdin() {
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdin = r
|
||||
w.WriteString("{\"content\":\"hello world\"}\n{\"content\":\"hello sun\"}\n")
|
||||
app.Run([]string{"nak", "event", "-t", "t=spam", "--ts", "1699485669"})
|
||||
app.Run(ctx, []string{"nak", "event", "-t", "t=spam", "--ts", "1699485669"})
|
||||
// Output:
|
||||
// {"id":"bda134f9077c11973afe6aa5a1cc6f5bcea01c40d318b8f91dcb8e50507cfa52","pubkey":"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798","created_at":1699485669,"kind":1,"tags":[["t","spam"]],"content":"hello world","sig":"7552454bb8e7944230142634e3e34ac7468bad9b21ed6909da572c611018dff1d14d0792e98b5806f6330edc51e09efa6d0b66a9694dc34606c70f4e580e7493"}
|
||||
// {"id":"879c36ec73acca288825b53585389581d3836e7f0fe4d46e5eba237ca56d6af5","pubkey":"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798","created_at":1699485669,"kind":1,"tags":[["t","spam"]],"content":"hello sun","sig":"6c7e6b13ebdf931d26acfdd00bec2ec1140ddaf8d1ed61453543a14e729a460fe36c40c488ccb194a0e1ab9511cb6c36741485f501bdb93c39ca4c51bc59cbd4"}
|
||||
}
|
||||
|
||||
func ExampleEventComplex() {
|
||||
app.Run([]string{"nak", "event", "--ts", "1699485669", "-k", "11", "-c", "skjdbaskd", "--sec", "17", "-t", "t=spam", "-e", "36d88cf5fcc449f2390a424907023eda7a74278120eebab8d02797cd92e7e29c", "-t", "r=https://abc.def?name=foobar;nothing"})
|
||||
app.Run(ctx, []string{"nak", "event", "--ts", "1699485669", "-k", "11", "-c", "skjdbaskd", "--sec", "17", "-t", "t=spam", "-e", "36d88cf5fcc449f2390a424907023eda7a74278120eebab8d02797cd92e7e29c", "-t", "r=https://abc.def?name=foobar;nothing"})
|
||||
// Output:
|
||||
// {"id":"19aba166dcf354bf5ef64f4afe69ada1eb851495001ee05e07d393ee8c8ea179","pubkey":"2fa2104d6b38d11b0230010559879124e42ab8dfeff5ff29dc9cdadd4ecacc3f","created_at":1699485669,"kind":11,"tags":[["t","spam"],["r","https://abc.def?name=foobar","nothing"],["e","36d88cf5fcc449f2390a424907023eda7a74278120eebab8d02797cd92e7e29c"]],"content":"skjdbaskd","sig":"cf452def4a68341c897c3fc96fa34dc6895a5b8cc266d4c041bcdf758ec992ec5adb8b0179e98552aaaf9450526a26d7e62e413b15b1c57e0cfc8db6b29215d7"}
|
||||
}
|
||||
|
||||
func ExampleEncode() {
|
||||
app.Run([]string{"nak", "encode", "npub", "a6a67ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179822"})
|
||||
app.Run([]string{"nak", "encode", "nprofile", "-r", "wss://example.com", "a6a67ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179822"})
|
||||
app.Run([]string{"nak", "encode", "nprofile", "-r", "wss://example.com", "a6a67ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179822", "a5592173975ded9f836a9572ea8b11a7e16ceb66464d66d50b27163f7f039d2c"})
|
||||
app.Run(ctx, []string{"nak", "encode", "npub", "a6a67ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179822"})
|
||||
app.Run(ctx, []string{"nak", "encode", "nprofile", "-r", "wss://example.com", "a6a67ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179822"})
|
||||
app.Run(ctx, []string{"nak", "encode", "nprofile", "-r", "wss://example.com", "a6a67ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179822", "a5592173975ded9f836a9572ea8b11a7e16ceb66464d66d50b27163f7f039d2c"})
|
||||
// npub156n8a7wuhwk9tgrzjh8gwzc8q2dlekedec5djk0js9d3d7qhnq3qjpdq28
|
||||
// nprofile1qqs2dfn7l8wthtz45p3ftn58pvrs9xlumvkuu2xet8egzkcklqtesgspz9mhxue69uhk27rpd4cxcefwvdhk6fl5jug
|
||||
// nprofile1qqs2dfn7l8wthtz45p3ftn58pvrs9xlumvkuu2xet8egzkcklqtesgspz9mhxue69uhk27rpd4cxcefwvdhk6fl5jug
|
||||
@@ -38,7 +43,7 @@ func ExampleEncode() {
|
||||
}
|
||||
|
||||
func ExampleDecode() {
|
||||
app.Run([]string{"nak", "decode", "naddr1qqyrgcmyxe3kvefhqyxhwumn8ghj7mn0wvhxcmmvqgs9kqvr4dkruv3t7n2pc6e6a7v9v2s5fprmwjv4gde8c4fe5y29v0srqsqqql9ngrt6tu", "nevent1qyd8wumn8ghj7urewfsk66ty9enxjct5dfskvtnrdakj7qgmwaehxw309aex2mrp0yh8wetnw3jhymnzw33jucm0d5hszxthwden5te0wfjkccte9eekummjwsh8xmmrd9skctcpzamhxue69uhkzarvv9ejumn0wd68ytnvv9hxgtcqyqllp5v5j0nxr74fptqxkhvfv0h3uj870qpk3ln8a58agyxl3fka296ewr8"})
|
||||
app.Run(ctx, []string{"nak", "decode", "naddr1qqyrgcmyxe3kvefhqyxhwumn8ghj7mn0wvhxcmmvqgs9kqvr4dkruv3t7n2pc6e6a7v9v2s5fprmwjv4gde8c4fe5y29v0srqsqqql9ngrt6tu", "nevent1qyd8wumn8ghj7urewfsk66ty9enxjct5dfskvtnrdakj7qgmwaehxw309aex2mrp0yh8wetnw3jhymnzw33jucm0d5hszxthwden5te0wfjkccte9eekummjwsh8xmmrd9skctcpzamhxue69uhkzarvv9ejumn0wd68ytnvv9hxgtcqyqllp5v5j0nxr74fptqxkhvfv0h3uj870qpk3ln8a58agyxl3fka296ewr8"})
|
||||
// Output:
|
||||
// {
|
||||
// "pubkey": "5b0183ab6c3e322bf4d41c6b3aef98562a144847b7499543727c5539a114563e",
|
||||
@@ -60,39 +65,39 @@ func ExampleDecode() {
|
||||
}
|
||||
|
||||
func ExampleReq() {
|
||||
app.Run([]string{"nak", "req", "-k", "1", "-l", "18", "-a", "2fa2104d6b38d11b0230010559879124e42ab8dfeff5ff29dc9cdadd4ecacc3f", "-e", "aec4de6d051a7c2b6ca2d087903d42051a31e07fb742f1240970084822de10a6"})
|
||||
app.Run(ctx, []string{"nak", "req", "-k", "1", "-l", "18", "-a", "2fa2104d6b38d11b0230010559879124e42ab8dfeff5ff29dc9cdadd4ecacc3f", "-e", "aec4de6d051a7c2b6ca2d087903d42051a31e07fb742f1240970084822de10a6"})
|
||||
// Output:
|
||||
// ["REQ","nak",{"kinds":[1],"authors":["2fa2104d6b38d11b0230010559879124e42ab8dfeff5ff29dc9cdadd4ecacc3f"],"limit":18,"#e":["aec4de6d051a7c2b6ca2d087903d42051a31e07fb742f1240970084822de10a6"]}]
|
||||
}
|
||||
|
||||
func ExampleReqIdFromRelay() {
|
||||
app.Run([]string{"nak", "req", "-i", "3ff0d19493e661faa90ac06b5d8963ef1e48fe780368fe67ed0fd410df8a6dd5", "wss://nostr.wine"})
|
||||
app.Run(ctx, []string{"nak", "req", "-i", "3ff0d19493e661faa90ac06b5d8963ef1e48fe780368fe67ed0fd410df8a6dd5", "wss://nostr.wine"})
|
||||
// Output:
|
||||
// {"id":"3ff0d19493e661faa90ac06b5d8963ef1e48fe780368fe67ed0fd410df8a6dd5","pubkey":"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d","created_at":1710759386,"kind":1,"tags":[],"content":"Nostr was coopted by our the corporate overlords. It is now featured in https://www.iana.org/assignments/well-known-uris/well-known-uris.xhtml.","sig":"faaec167cca4de50b562b7702e8854e2023f0ccd5f36d1b95b6eac20d352206342d6987e9516d283068c768e94dbe8858e2990c3e05405e707fb6fb771ef92f9"}
|
||||
}
|
||||
|
||||
func ExampleMultipleFetch() {
|
||||
app.Run([]string{"nak", "fetch", "naddr1qqyrgcmyxe3kvefhqyxhwumn8ghj7mn0wvhxcmmvqgs9kqvr4dkruv3t7n2pc6e6a7v9v2s5fprmwjv4gde8c4fe5y29v0srqsqqql9ngrt6tu", "nevent1qyd8wumn8ghj7urewfsk66ty9enxjct5dfskvtnrdakj7qgmwaehxw309aex2mrp0yh8wetnw3jhymnzw33jucm0d5hszxthwden5te0wfjkccte9eekummjwsh8xmmrd9skctcpzamhxue69uhkzarvv9ejumn0wd68ytnvv9hxgtcqyqllp5v5j0nxr74fptqxkhvfv0h3uj870qpk3ln8a58agyxl3fka296ewr8"})
|
||||
app.Run(ctx, []string{"nak", "fetch", "naddr1qqyrgcmyxe3kvefhqyxhwumn8ghj7mn0wvhxcmmvqgs9kqvr4dkruv3t7n2pc6e6a7v9v2s5fprmwjv4gde8c4fe5y29v0srqsqqql9ngrt6tu", "nevent1qyd8wumn8ghj7urewfsk66ty9enxjct5dfskvtnrdakj7qgmwaehxw309aex2mrp0yh8wetnw3jhymnzw33jucm0d5hszxthwden5te0wfjkccte9eekummjwsh8xmmrd9skctcpzamhxue69uhkzarvv9ejumn0wd68ytnvv9hxgtcqyqllp5v5j0nxr74fptqxkhvfv0h3uj870qpk3ln8a58agyxl3fka296ewr8"})
|
||||
// Output:
|
||||
// {"id":"9ae5014573fc75ced00b343868d2cd9343ebcbbae50591c6fa8ae1cd99568f05","pubkey":"5b0183ab6c3e322bf4d41c6b3aef98562a144847b7499543727c5539a114563e","created_at":1707764605,"kind":31923,"tags":[["d","4cd6cfe7"],["name","Nostr PHX Presents Culture Shock"],["description","Nostr PHX presents Culture Shock the first Value 4 Value Cultural Event in Downtown Phoenix. We will showcase the power of Nostr + Bitcoin / Lightning with a full day of education, food, drinks, conversation, vendors and best of all, a live convert which will stream globally for the world to zap. "],["start","1708185600"],["end","1708228800"],["start_tzid","America/Phoenix"],["p","5b0183ab6c3e322bf4d41c6b3aef98562a144847b7499543727c5539a114563e","","host"],["location","Hello Merch, 850 W Lincoln St, Phoenix, AZ 85007, USA","Hello Merch","850 W Lincoln St, Phoenix, AZ 85007, USA"],["address","Hello Merch, 850 W Lincoln St, Phoenix, AZ 85007, USA","Hello Merch","850 W Lincoln St, Phoenix, AZ 85007, USA"],["g","9tbq1rzn"],["image","https://flockstr.s3.amazonaws.com/event/15vSaiscDhVH1KBXhA0i8"],["about","Nostr PHX presents Culture Shock : the first Value 4 Value Cultural Event in Downtown Phoenix. We will showcase the power of Nostr + Bitcoin / Lightning with a full day of education, conversation, food and goods which will be capped off with a live concert streamed globally for the world to boost \u0026 zap. \n\nWe strive to source local vendors, local artists, local partnerships. Please reach out to us if you are interested in participating in this historic event. "],["calendar","31924:5b0183ab6c3e322bf4d41c6b3aef98562a144847b7499543727c5539a114563e:1f238c94"]],"content":"Nostr PHX presents Culture Shock : the first Value 4 Value Cultural Event in Downtown Phoenix. We will showcase the power of Nostr + Bitcoin / Lightning with a full day of education, conversation, food and goods which will be capped off with a live concert streamed globally for the world to boost \u0026 zap. \n\nWe strive to source local vendors, local artists, local partnerships. Please reach out to us if you are interested in participating in this historic event. ","sig":"f676629d1414d96b464644de6babde0c96bd21ef9b41ba69ad886a1d13a942b855b715b22ccf38bc07fead18d3bdeee82d9e3825cf6f003fb5ff1766d95c70a0"}
|
||||
// {"id":"3ff0d19493e661faa90ac06b5d8963ef1e48fe780368fe67ed0fd410df8a6dd5","pubkey":"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d","created_at":1710759386,"kind":1,"tags":[],"content":"Nostr was coopted by our the corporate overlords. It is now featured in https://www.iana.org/assignments/well-known-uris/well-known-uris.xhtml.","sig":"faaec167cca4de50b562b7702e8854e2023f0ccd5f36d1b95b6eac20d352206342d6987e9516d283068c768e94dbe8858e2990c3e05405e707fb6fb771ef92f9"}
|
||||
}
|
||||
|
||||
func ExampleKeyPublic() {
|
||||
app.Run([]string{"nak", "key", "public", "3ff0d19493e661faa90ac06b5d8963ef1e48fe780368fe67ed0fd410df8a6dd5", "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d"})
|
||||
app.Run(ctx, []string{"nak", "key", "public", "3ff0d19493e661faa90ac06b5d8963ef1e48fe780368fe67ed0fd410df8a6dd5", "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d"})
|
||||
// Output:
|
||||
// 70f7120d065870513a6bddb61c8d400ad1e43449b1900ffdb5551e4c421375c8
|
||||
// 718d756f60cf5179ef35b39dc6db3ff58f04c0734f81f6d4410f0b047ddf9029
|
||||
}
|
||||
|
||||
func ExampleKeyDecrypt() {
|
||||
app.Run([]string{"nak", "key", "decrypt", "ncryptsec1qggfep0m5ythsegkmwfrhhx2zx5gazyhdygvlngcds4wsgdpzfy6nr0exy0pdk0ydwrqyhndt2trtwcgwwag0ja3aqclzptfxxqvprdyaz3qfrmazpecx2ff6dph5mfdjnh5sw8sgecul32eru6xet34", "banana"})
|
||||
app.Run(ctx, []string{"nak", "key", "decrypt", "ncryptsec1qggfep0m5ythsegkmwfrhhx2zx5gazyhdygvlngcds4wsgdpzfy6nr0exy0pdk0ydwrqyhndt2trtwcgwwag0ja3aqclzptfxxqvprdyaz3qfrmazpecx2ff6dph5mfdjnh5sw8sgecul32eru6xet34", "banana"})
|
||||
// Output:
|
||||
// nsec180cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkwsgyumg0
|
||||
}
|
||||
|
||||
func ExampleRelay() {
|
||||
app.Run([]string{"nak", "relay", "relay.nos.social", "pyramid.fiatjaf.com"})
|
||||
app.Run(ctx, []string{"nak", "relay", "relay.nos.social", "pyramid.fiatjaf.com"})
|
||||
// Output:
|
||||
// {
|
||||
// "name": "nos.social strfry relay",
|
||||
|
||||
4
fetch.go
4
fetch.go
@@ -38,7 +38,7 @@ var fetch = &cli.Command{
|
||||
|
||||
prefix, value, err := nip19.Decode(code)
|
||||
if err != nil {
|
||||
lineProcessingError(ctx, "failed to decode: %s", err)
|
||||
ctx = lineProcessingError(ctx, "failed to decode: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ var fetch = &cli.Command{
|
||||
}
|
||||
|
||||
if len(relays) == 0 {
|
||||
lineProcessingError(ctx, "no relay hints found")
|
||||
ctx = lineProcessingError(ctx, "no relay hints found")
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
4
go.mod
4
go.mod
@@ -10,7 +10,7 @@ require (
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0
|
||||
github.com/fatih/color v1.16.0
|
||||
github.com/mailru/easyjson v0.7.7
|
||||
github.com/nbd-wtf/go-nostr v0.34.0
|
||||
github.com/nbd-wtf/go-nostr v0.34.2
|
||||
github.com/nbd-wtf/nostr-sdk v0.0.5
|
||||
github.com/urfave/cli/v3 v3.0.0-alpha9
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
|
||||
@@ -38,4 +38,4 @@ require (
|
||||
golang.org/x/text v0.8.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/urfave/cli/v3 => github.com/fiatjaf/cli/v3 v3.0.0-20240626022047-0fc2565ea728
|
||||
replace github.com/urfave/cli/v3 => github.com/fiatjaf/cli/v3 v3.0.0-20240712212113-3a8b0280e2c5
|
||||
|
||||
8
go.sum
8
go.sum
@@ -42,8 +42,8 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3
|
||||
github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218=
|
||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
github.com/fiatjaf/cli/v3 v3.0.0-20240626022047-0fc2565ea728 h1:MgEQQSCPDkpILGlJKCj/Gj0l8XwYFpBKYATPJC14ros=
|
||||
github.com/fiatjaf/cli/v3 v3.0.0-20240626022047-0fc2565ea728/go.mod h1:Z1ItyMma7t6I7zHG9OpbExhHQOSkFf/96n+mAZ9MtVI=
|
||||
github.com/fiatjaf/cli/v3 v3.0.0-20240712212113-3a8b0280e2c5 h1:yhTRU02Hn1jwq50uUKRxbPZQg0PODe37s73IJNsCJb0=
|
||||
github.com/fiatjaf/cli/v3 v3.0.0-20240712212113-3a8b0280e2c5/go.mod h1:Z1ItyMma7t6I7zHG9OpbExhHQOSkFf/96n+mAZ9MtVI=
|
||||
github.com/fiatjaf/eventstore v0.2.16 h1:NR64mnyUT5nJR8Sj2AwJTd1Hqs5kKJcCFO21ggUkvWg=
|
||||
github.com/fiatjaf/eventstore v0.2.16/go.mod h1:rUc1KhVufVmC+HUOiuPweGAcvG6lEOQCkRCn2Xn5VRA=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
@@ -79,8 +79,8 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/nbd-wtf/go-nostr v0.34.0 h1:E7tDHFx42gvWwFv1Eysn+NxJqGLmo21x/VEwj2+F21E=
|
||||
github.com/nbd-wtf/go-nostr v0.34.0/go.mod h1:NZQkxl96ggbO8rvDpVjcsojJqKTPwqhP4i82O7K5DJs=
|
||||
github.com/nbd-wtf/go-nostr v0.34.2 h1:9b4qZ29DhQf9xEWN8/7zfDD868r1jFbpjrR3c+BHc+E=
|
||||
github.com/nbd-wtf/go-nostr v0.34.2/go.mod h1:NZQkxl96ggbO8rvDpVjcsojJqKTPwqhP4i82O7K5DJs=
|
||||
github.com/nbd-wtf/nostr-sdk v0.0.5 h1:rec+FcDizDVO0W25PX0lgYMXvP7zNNOgI3Fu9UCm4BY=
|
||||
github.com/nbd-wtf/nostr-sdk v0.0.5/go.mod h1:iJJsikesCGLNFZ9dLqhLPDzdt924EagUmdQxT3w2Lmk=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
|
||||
46
helpers.go
46
helpers.go
@@ -9,6 +9,7 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/chzyer/readline"
|
||||
"github.com/fatih/color"
|
||||
@@ -117,13 +118,48 @@ func normalizeAndValidateRelayURLs(wsurls []string) error {
|
||||
func connectToAllRelays(
|
||||
ctx context.Context,
|
||||
relayUrls []string,
|
||||
forcePreAuth bool,
|
||||
opts ...nostr.PoolOption,
|
||||
) (*nostr.SimplePool, []*nostr.Relay) {
|
||||
relays := make([]*nostr.Relay, 0, len(relayUrls))
|
||||
pool := nostr.NewSimplePool(ctx, opts...)
|
||||
relayLoop:
|
||||
for _, url := range relayUrls {
|
||||
log("connecting to %s... ", url)
|
||||
if relay, err := pool.EnsureRelay(url); err == nil {
|
||||
if forcePreAuth {
|
||||
log("waiting for auth challenge... ")
|
||||
signer := opts[0].(nostr.WithAuthHandler)
|
||||
time.Sleep(time.Millisecond * 200)
|
||||
challengeWaitLoop:
|
||||
for {
|
||||
// beginhack
|
||||
// here starts the biggest and ugliest hack of this codebase
|
||||
if err := relay.Auth(ctx, func(authEvent *nostr.Event) error {
|
||||
challengeTag := authEvent.Tags.GetFirst([]string{"challenge", ""})
|
||||
if (*challengeTag)[1] == "" {
|
||||
return fmt.Errorf("auth not received yet *****")
|
||||
}
|
||||
return signer(authEvent)
|
||||
}); err == nil {
|
||||
// auth succeeded
|
||||
break challengeWaitLoop
|
||||
} else {
|
||||
// auth failed
|
||||
if strings.HasSuffix(err.Error(), "auth not received yet *****") {
|
||||
// it failed because we didn't receive the challenge yet, so keep waiting
|
||||
time.Sleep(time.Second)
|
||||
continue challengeWaitLoop
|
||||
} else {
|
||||
// it failed for some other reason, so skip this relay
|
||||
log(err.Error() + "\n")
|
||||
continue relayLoop
|
||||
}
|
||||
}
|
||||
// endhack
|
||||
}
|
||||
}
|
||||
|
||||
relays = append(relays, relay)
|
||||
log("ok.\n")
|
||||
} else {
|
||||
@@ -133,9 +169,9 @@ func connectToAllRelays(
|
||||
return pool, relays
|
||||
}
|
||||
|
||||
func lineProcessingError(ctx context.Context, msg string, args ...any) {
|
||||
ctx = context.WithValue(ctx, LINE_PROCESSING_ERROR, true)
|
||||
func lineProcessingError(ctx context.Context, msg string, args ...any) context.Context {
|
||||
log(msg+"\n", args...)
|
||||
return context.WithValue(ctx, LINE_PROCESSING_ERROR, true)
|
||||
}
|
||||
|
||||
func exitIfLineProcessingError(ctx context.Context) {
|
||||
@@ -174,7 +210,7 @@ func gatherSecretKeyOrBunkerFromArguments(ctx context.Context, c *cli.Command) (
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("failed to decrypt: %w", err)
|
||||
}
|
||||
} else if bsec, err := hex.DecodeString(strings.Repeat("0", 64-len(sec)) + sec); err == nil {
|
||||
} else if bsec, err := hex.DecodeString(leftPadKey(sec)); err == nil {
|
||||
sec = hex.EncodeToString(bsec)
|
||||
} else if prefix, hexvalue, err := nip19.Decode(sec); err != nil {
|
||||
return "", nil, fmt.Errorf("invalid nsec: %w", err)
|
||||
@@ -248,3 +284,7 @@ func randString(n int) string {
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func leftPadKey(k string) string {
|
||||
return strings.Repeat("0", 64-len(k)) + k
|
||||
}
|
||||
|
||||
17
key.go
17
key.go
@@ -50,7 +50,7 @@ var public = &cli.Command{
|
||||
for sec := range getSecretKeysFromStdinLinesOrSlice(ctx, c, c.Args().Slice()) {
|
||||
pubkey, err := nostr.GetPublicKey(sec)
|
||||
if err != nil {
|
||||
lineProcessingError(ctx, "failed to derive public key: %s", err)
|
||||
ctx = lineProcessingError(ctx, "failed to derive public key: %s", err)
|
||||
continue
|
||||
}
|
||||
stdout(pubkey)
|
||||
@@ -88,7 +88,7 @@ var encrypt = &cli.Command{
|
||||
for sec := range getSecretKeysFromStdinLinesOrSlice(ctx, c, keys) {
|
||||
ncryptsec, err := nip49.Encrypt(sec, password, uint8(c.Int("logn")), 0x02)
|
||||
if err != nil {
|
||||
lineProcessingError(ctx, "failed to encrypt: %s", err)
|
||||
ctx = lineProcessingError(ctx, "failed to encrypt: %s", err)
|
||||
continue
|
||||
}
|
||||
stdout(ncryptsec)
|
||||
@@ -116,8 +116,7 @@ var decrypt = &cli.Command{
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decrypt: %s", err)
|
||||
}
|
||||
nsec, _ := nip19.EncodePrivateKey(sec)
|
||||
stdout(nsec)
|
||||
stdout(sec)
|
||||
return nil
|
||||
case 1:
|
||||
if arg := c.Args().Get(0); strings.HasPrefix(arg, "ncryptsec1") {
|
||||
@@ -133,11 +132,10 @@ var decrypt = &cli.Command{
|
||||
for ncryptsec := range getStdinLinesOrArgumentsFromSlice([]string{ncryptsec}) {
|
||||
sec, err := nip49.Decrypt(ncryptsec, password)
|
||||
if err != nil {
|
||||
lineProcessingError(ctx, "failed to decrypt: %s", err)
|
||||
ctx = lineProcessingError(ctx, "failed to decrypt: %s", err)
|
||||
continue
|
||||
}
|
||||
nsec, _ := nip19.EncodePrivateKey(sec)
|
||||
stdout(nsec)
|
||||
stdout(sec)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -264,13 +262,14 @@ func getSecretKeysFromStdinLinesOrSlice(ctx context.Context, c *cli.Command, key
|
||||
if strings.HasPrefix(sec, "nsec1") {
|
||||
_, data, err := nip19.Decode(sec)
|
||||
if err != nil {
|
||||
lineProcessingError(ctx, "invalid nsec code: %s", err)
|
||||
ctx = lineProcessingError(ctx, "invalid nsec code: %s", err)
|
||||
continue
|
||||
}
|
||||
sec = data.(string)
|
||||
}
|
||||
sec = leftPadKey(sec)
|
||||
if !nostr.IsValid32ByteHex(sec) {
|
||||
lineProcessingError(ctx, "invalid hex key")
|
||||
ctx = lineProcessingError(ctx, "invalid hex key")
|
||||
continue
|
||||
}
|
||||
ch <- sec
|
||||
|
||||
9
main.go
9
main.go
@@ -8,10 +8,11 @@ import (
|
||||
)
|
||||
|
||||
var app = &cli.Command{
|
||||
Name: "nak",
|
||||
Suggest: true,
|
||||
UseShortOptionHandling: true,
|
||||
Usage: "the nostr army knife command-line tool",
|
||||
Name: "nak",
|
||||
Suggest: true,
|
||||
UseShortOptionHandling: true,
|
||||
AllowFlagsAfterArguments: true,
|
||||
Usage: "the nostr army knife command-line tool",
|
||||
Commands: []*cli.Command{
|
||||
req,
|
||||
count,
|
||||
|
||||
193
relay.go
193
relay.go
@@ -1,11 +1,20 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip11"
|
||||
"github.com/nbd-wtf/go-nostr/nip86"
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
@@ -23,7 +32,7 @@ var relay = &cli.Command{
|
||||
|
||||
info, err := nip11.Fetch(ctx, url)
|
||||
if err != nil {
|
||||
lineProcessingError(ctx, "failed to fetch '%s' information document: %w", url, err)
|
||||
ctx = lineProcessingError(ctx, "failed to fetch '%s' information document: %w", url, err)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -32,4 +41,186 @@ var relay = &cli.Command{
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Commands: (func() []*cli.Command {
|
||||
commands := make([]*cli.Command, 0, 12)
|
||||
|
||||
for _, def := range []struct {
|
||||
method string
|
||||
args []string
|
||||
}{
|
||||
{"allowpubkey", []string{"pubkey", "reason"}},
|
||||
{"banpubkey", []string{"pubkey", "reason"}},
|
||||
{"listallowedpubkeys", nil},
|
||||
{"allowpubkey", []string{"pubkey", "reason"}},
|
||||
{"listallowedpubkeys", nil},
|
||||
{"listeventsneedingmoderation", nil},
|
||||
{"allowevent", []string{"id", "reason"}},
|
||||
{"banevent", []string{"id", "reason"}},
|
||||
{"listbannedevents", nil},
|
||||
{"changerelayname", []string{"name"}},
|
||||
{"changerelaydescription", []string{"description"}},
|
||||
{"changerelayicon", []string{"icon"}},
|
||||
{"allowkind", []string{"kind"}},
|
||||
{"disallowkind", []string{"kind"}},
|
||||
{"listallowedkinds", nil},
|
||||
{"blockip", []string{"ip", "reason"}},
|
||||
{"unblockip", []string{"ip", "reason"}},
|
||||
{"listblockedips", nil},
|
||||
} {
|
||||
def := def
|
||||
|
||||
flags := make([]cli.Flag, len(def.args), len(def.args)+4)
|
||||
for i, argName := range def.args {
|
||||
flags[i] = declareFlag(argName)
|
||||
}
|
||||
|
||||
flags = append(flags,
|
||||
&cli.StringFlag{
|
||||
Name: "sec",
|
||||
Usage: "secret key to sign the event, as nsec, ncryptsec or hex",
|
||||
DefaultText: "the key '1'",
|
||||
Value: "0000000000000000000000000000000000000000000000000000000000000001",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "prompt-sec",
|
||||
Usage: "prompt the user to paste a hex or nsec with which to sign the event",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "connect",
|
||||
Usage: "sign event using NIP-46, expects a bunker://... URL",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "connect-as",
|
||||
Usage: "private key to when communicating with the bunker given on --connect",
|
||||
DefaultText: "a random key",
|
||||
},
|
||||
)
|
||||
|
||||
cmd := &cli.Command{
|
||||
Name: def.method,
|
||||
Usage: fmt.Sprintf(`the "%s" relay management RPC call`, def.method),
|
||||
Description: fmt.Sprintf(
|
||||
`the "%s" management RPC call, see https://nips.nostr.com/86 for more information`, def.method),
|
||||
Action: func(ctx context.Context, c *cli.Command) error {
|
||||
params := make([]any, len(def.args))
|
||||
for i, argName := range def.args {
|
||||
params[i] = getArgument(c, argName)
|
||||
}
|
||||
req := nip86.Request{Method: def.method, Params: params}
|
||||
reqj, _ := json.Marshal(req)
|
||||
|
||||
relayUrls := c.Args().Slice()
|
||||
if len(relayUrls) == 0 {
|
||||
stdout(string(reqj))
|
||||
return nil
|
||||
}
|
||||
|
||||
sec, bunker, err := gatherSecretKeyOrBunkerFromArguments(ctx, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, relayUrl := range relayUrls {
|
||||
httpUrl := "http" + nostr.NormalizeURL(relayUrl)[2:]
|
||||
log("calling '%s' on %s... ", def.method, httpUrl)
|
||||
body := bytes.NewBuffer(nil)
|
||||
body.Write(reqj)
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", httpUrl, body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
// Authorization
|
||||
hostname := strings.Split(strings.Split(httpUrl, "://")[1], "/")[0]
|
||||
payloadHash := sha256.Sum256(reqj)
|
||||
authEvent := nostr.Event{
|
||||
Kind: 27235,
|
||||
CreatedAt: nostr.Now(),
|
||||
Tags: nostr.Tags{
|
||||
{"host", hostname},
|
||||
{"payload", hex.EncodeToString(payloadHash[:])},
|
||||
},
|
||||
}
|
||||
if bunker != nil {
|
||||
if err := bunker.SignEvent(ctx, &authEvent); err != nil {
|
||||
return fmt.Errorf("failed to sign with bunker: %w", err)
|
||||
}
|
||||
} else if err := authEvent.Sign(sec); err != nil {
|
||||
return fmt.Errorf("error signing with provided key: %w", err)
|
||||
}
|
||||
evtj, _ := json.Marshal(authEvent)
|
||||
req.Header.Set("Authorization", "Nostr "+base64.StdEncoding.EncodeToString(evtj))
|
||||
|
||||
// Content-Type
|
||||
req.Header.Set("Content-Type", "application/nostr+json+rpc")
|
||||
|
||||
// make request to relay
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
log("failed: %s\n", err)
|
||||
continue
|
||||
}
|
||||
b, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log("failed to read response: %s\n", err)
|
||||
continue
|
||||
}
|
||||
if resp.StatusCode >= 300 {
|
||||
log("failed with status %d\n", resp.StatusCode)
|
||||
bodyPrintable := string(b)
|
||||
if len(bodyPrintable) > 300 {
|
||||
bodyPrintable = bodyPrintable[0:297] + "..."
|
||||
}
|
||||
log(bodyPrintable)
|
||||
continue
|
||||
}
|
||||
var response nip86.Response
|
||||
if err := json.Unmarshal(b, &response); err != nil {
|
||||
log("bad json response: %s\n", err)
|
||||
bodyPrintable := string(b)
|
||||
if len(bodyPrintable) > 300 {
|
||||
bodyPrintable = bodyPrintable[0:297] + "..."
|
||||
}
|
||||
log(bodyPrintable)
|
||||
continue
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
// print the result
|
||||
log("\n")
|
||||
pretty, _ := json.MarshalIndent(response, "", " ")
|
||||
stdout(string(pretty))
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
Flags: flags,
|
||||
}
|
||||
|
||||
commands = append(commands, cmd)
|
||||
}
|
||||
|
||||
return commands
|
||||
})(),
|
||||
}
|
||||
|
||||
func declareFlag(argName string) cli.Flag {
|
||||
usage := "parameter for this management RPC call, see https://nips.nostr.com/86 for more information."
|
||||
switch argName {
|
||||
case "kind":
|
||||
return &cli.IntFlag{Name: argName, Required: true, Usage: usage}
|
||||
case "reason":
|
||||
return &cli.StringFlag{Name: argName, Usage: usage}
|
||||
default:
|
||||
return &cli.StringFlag{Name: argName, Required: true, Usage: usage}
|
||||
}
|
||||
}
|
||||
|
||||
func getArgument(c *cli.Command, argName string) any {
|
||||
switch argName {
|
||||
case "kind":
|
||||
return c.Int(argName)
|
||||
default:
|
||||
return c.String(argName)
|
||||
}
|
||||
}
|
||||
|
||||
14
req.go
14
req.go
@@ -104,6 +104,11 @@ example:
|
||||
Name: "auth",
|
||||
Usage: "always perform NIP-42 \"AUTH\" when facing an \"auth-required: \" rejection and try again",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "force-pre-auth",
|
||||
Aliases: []string{"fpa"},
|
||||
Usage: "after connecting, for a NIP-42 \"AUTH\" message to be received, act on it and only then send the \"REQ\"",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "sec",
|
||||
Usage: "secret key to sign the AUTH challenge, as hex or nsec",
|
||||
@@ -127,11 +132,12 @@ example:
|
||||
ArgsUsage: "[relay...]",
|
||||
Action: func(ctx context.Context, c *cli.Command) error {
|
||||
var pool *nostr.SimplePool
|
||||
|
||||
relayUrls := c.Args().Slice()
|
||||
if len(relayUrls) > 0 {
|
||||
var relays []*nostr.Relay
|
||||
pool, relays = connectToAllRelays(ctx, relayUrls, nostr.WithAuthHandler(func(evt *nostr.Event) error {
|
||||
if !c.Bool("auth") {
|
||||
pool, relays = connectToAllRelays(ctx, relayUrls, c.Bool("force-pre-auth"), nostr.WithAuthHandler(func(evt *nostr.Event) error {
|
||||
if !c.Bool("auth") && !c.Bool("force-pre-auth") {
|
||||
return fmt.Errorf("auth not authorized")
|
||||
}
|
||||
sec, bunker, err := gatherSecretKeyOrBunkerFromArguments(ctx, c)
|
||||
@@ -148,7 +154,7 @@ example:
|
||||
} else {
|
||||
pk, _ = nostr.GetPublicKey(sec)
|
||||
}
|
||||
log("performing auth as %s...\n", pk)
|
||||
log("performing auth as %s... ", pk)
|
||||
|
||||
if bunker != nil {
|
||||
return bunker.SignEvent(ctx, evt)
|
||||
@@ -176,7 +182,7 @@ example:
|
||||
filter := nostr.Filter{}
|
||||
if stdinFilter != "" {
|
||||
if err := easyjson.Unmarshal([]byte(stdinFilter), &filter); err != nil {
|
||||
lineProcessingError(ctx, "invalid filter '%s' received from stdin: %s", stdinFilter, err)
|
||||
ctx = lineProcessingError(ctx, "invalid filter '%s' received from stdin: %s", stdinFilter, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,18 +20,18 @@ it outputs nothing if the verification is successful.`,
|
||||
evt := nostr.Event{}
|
||||
if stdinEvent != "" {
|
||||
if err := json.Unmarshal([]byte(stdinEvent), &evt); err != nil {
|
||||
lineProcessingError(ctx, "invalid event: %s", err)
|
||||
ctx = lineProcessingError(ctx, "invalid event: %s", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if evt.GetID() != evt.ID {
|
||||
lineProcessingError(ctx, "invalid .id, expected %s, got %s", evt.GetID(), evt.ID)
|
||||
ctx = lineProcessingError(ctx, "invalid .id, expected %s, got %s", evt.GetID(), evt.ID)
|
||||
continue
|
||||
}
|
||||
|
||||
if ok, err := evt.CheckSignature(); !ok {
|
||||
lineProcessingError(ctx, "invalid signature: %s", err)
|
||||
ctx = lineProcessingError(ctx, "invalid signature: %s", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user