mirror of
https://github.com/fiatjaf/nak.git
synced 2025-12-08 16:48:51 +00:00
Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a934cc76b | ||
|
|
e0c967efa9 | ||
|
|
36c32ae308 | ||
|
|
6d23509d8c | ||
|
|
29b6ecbafe | ||
|
|
11f37afa5b | ||
|
|
cf1694704e | ||
|
|
b3ef2c1289 | ||
|
|
cfdea699bc | ||
|
|
014c6bc11d | ||
|
|
0240866fa1 | ||
|
|
a4d9ceecfa | ||
|
|
56657d8aa9 | ||
|
|
ea7b88cfd7 | ||
|
|
2042b14578 | ||
|
|
9d43e66fac | ||
|
|
85e9610265 | ||
|
|
2edfa5cbea | ||
|
|
9690dc70cb | ||
|
|
c90e61dbec | ||
|
|
d226cd6ce4 | ||
|
|
3d78e91f62 | ||
|
|
84965f2253 | ||
|
|
928c73513c | ||
|
|
a36142604d | ||
|
|
220fe84f1b | ||
|
|
48c0e342e3 | ||
|
|
ec2e214c02 | ||
|
|
9f62d4679f | ||
|
|
809865ca0c | ||
|
|
813ab3b6ac | ||
|
|
09ed2a040a | ||
|
|
7846960c4e | ||
|
|
ce6bb0aa22 | ||
|
|
49ce12ffc2 | ||
|
|
a5013c513d | ||
|
|
8f51fe757b | ||
|
|
30ca5776c5 | ||
|
|
e18e8c00e7 | ||
|
|
bca4362ca5 | ||
|
|
54c4be10bd |
6
.github/workflows/release-cli.yml
vendored
6
.github/workflows/release-cli.yml
vendored
@@ -25,10 +25,14 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
goos: [linux, freebsd, darwin, windows]
|
goos: [linux, freebsd, darwin, windows]
|
||||||
goarch: [amd64, arm64]
|
goarch: [amd64, arm64, riscv64]
|
||||||
exclude:
|
exclude:
|
||||||
- goarch: arm64
|
- goarch: arm64
|
||||||
goos: windows
|
goos: windows
|
||||||
|
- goarch: riscv64
|
||||||
|
goos: windows
|
||||||
|
- goarch: riscv64
|
||||||
|
goos: darwin
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: wangyoucao577/go-release-action@v1.40
|
- uses: wangyoucao577/go-release-action@v1.40
|
||||||
|
|||||||
153
README.md
153
README.md
@@ -77,11 +77,158 @@ publishing to wss://relayable.org... success.
|
|||||||
invalid .id, expected 05bd99d54cb835f427e0092c4275ee44c7ff51219eff417c19f70c9e2c53ad5a, got 05bd99d54cb835f327e0092c4275ee44c7ff51219eff417c19f70c9e2c53ad5a
|
invalid .id, expected 05bd99d54cb835f427e0092c4275ee44c7ff51219eff417c19f70c9e2c53ad5a, got 05bd99d54cb835f327e0092c4275ee44c7ff51219eff417c19f70c9e2c53ad5a
|
||||||
```
|
```
|
||||||
|
|
||||||
### fetch all quoted events by a given pubkey in their last 100 notes
|
### fetch all quoted events by a given pubkey in their last 10 notes of 2023
|
||||||
```shell
|
```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 10 -k 1 --until 'December 31 2023' -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":"0000000a5109c9747e3847282fcaef3d221d1be5e864ced7b2099d416a18d15a","pubkey":"7bdef7be22dd8e59f4600e044aa53a1cf975a9dc7d27df5833bc77db784a5805","created_at":1703869609,"tags":[["nonce","12912720851599460299","25"]],"content":"https://image.nostr.build/5eb40d3cae799bc572763b8f8bee95643344fa392d280efcb0fd28a935879e2a.png\n\nNostr is not dying.\nIt is just a physiological and healthy slowdown on the part of all those who have made this possible in such a short time, sharing extraordinary enthusiasm. This is necessary to regain a little energy, it will allow some things to be cleaned up and more focused goals to be set.\n\nIt is like the caterpillar that is about to become a butterfly, it has to stop moving, acting, doing all the time; it has to do one last silent work and rest, letting time go by. And then a new phase of life can begin.\n\nWe have an amazing 2024 ahead.\nThank you all, who have given so much and believe in Nostr.\n\nPS: an interesting cue suggested by this image, you cannot have both silk and butterfly, you have to choose: a precious and sophisticated ornament, or the living, colorful beauty of freedom.","sig":"16fe157fb13dba2474d510db5253edc409b465515371015a91b26b8f39e5aa873453bc366947c37463c49466f5fceb7dea0485432f979a03471c8f76b73e553c"}
|
||||||
|
{"kind":1,"id":"ac0cc72dfee39f41d94568f574e7b613d3979facbd7b477a16b52eb763db4b6e","pubkey":"2250f69694c2a43929e77e5de0f6a61ae5e37a1ee6d6a3baef1706ed9901248b","created_at":1703873865,"tags":[["r","https://zine.wavlake.com/2023-year-in-review/"]],"content":"It's been an incredible year for us here at Wavlake and we wanted to take a moment to look back and see how far we've come since launch. Read more.. https://zine.wavlake.com/2023-year-in-review/","sig":"189e354f67f48f3046fd762c83f9bf3a776d502d514e2839a1b459c30107a02453304ef695cdc7d254724041feec3800806b21eb76259df87144aaef821ace5b"}
|
||||||
|
{"kind":1,"id":"6215766c5aadfaf51488134682f7d28f237218b5405da2fc11d1fefe1ebf8154","pubkey":"4ce6abbd68dab6e9fdf6e8e9912a8e12f9b539e078c634c55a9bff2994a514dd","created_at":1703879775,"tags":[["imeta","url https://video.nostr.build/7b4e7c326fa4fcba58a40914ce9db4f060bd917878f2194f6d139948b085ebb9.mp4","blurhash eHD,QG_4ogMu_3to%O-:MwM_IWRjx^-pIUoe-;t7%Nt7%gV?M{WBxu","dim 480x268"],["t","zaps"],["t","powakili23"],["p","4f82bced42584a6acfced2a657b5acabc4f90d75a95ed3ff888f3b04b8928630"],["p","ce75bae2349804caa5f4de8ae8f775bb558135f412441d9e32f88e4226c5d165"],["p","94bd495b78f8f6e5aff8ebc90e052d3a409d1f9d82e43ab56ca2cafb81b18ddf"],["p","50ff5b7ebeac1cc0d03dc878be8a59f1b63d45a7d5e60ade4b6f6f31eca25954"],["p","f300cf2bdf9808ed229dfa468260753a0b179935bdb87612b6d4f5b9fe3fc7cf"],["r","https://geyser.fund/entry/2636"],["r","https://video.nostr.build/7b4e7c326fa4fcba58a40914ce9db4f060bd917878f2194f6d139948b085ebb9.mp4"]],"content":"POWA - HQ UPDATE - DEC 2023\nTLDR: plan to open January 2024, 1 million Sats to go to reach milestone. #zaps go to fund this project. ⚡️powa@geyser.fund\n\nHello,\n\nFirst and foremost, I’d like to thank you for the incredible support shown for this project. It’s been an absolute honor to oversee this Proof of Work initiative.\n\nI am thrilled to announce that we are right on track for the grand opening in January 2024.\n\nCurrently, we're just over 1 million Sats away from reaching our target for this phase.\n\nPlease take a moment to enjoy the video and stay tuned for further updates about POWA. \n\nMan Like Who?\nMan Like Kweks!\n🇹🇿⚡️💜🏔️\n#powakili23\nnostr:npub1f7ptem2ztp9x4n7w62n90ddv40z0jrt4490d8lug3uasfwyjsccqkknerm nostr:npub1ee6m4c35nqzv4f05m69w3am4hd2czd05zfzpm83jlz8yyfk969js78tfcv nostr:npub1jj75jkmclrmwttlca0ysupfd8fqf68uastjr4dtv5t90hqd33h0s4gcksp nostr:npub12rl4kl474swvp5paeputazje7xmr63d86hnq4hjtdahnrm9zt92qgq500s nostr:npub17vqv727lnqyw6g5alfrgycr48g930xf4hku8vy4k6n6mnl3lcl8sglecc5 \n\nhttps://geyser.fund/entry/2636 https://video.nostr.build/7b4e7c326fa4fcba58a40914ce9db4f060bd917878f2194f6d139948b085ebb9.mp4 ","sig":"97d13c17d91c319f343cc770222d6d4a0a714d0e7e4ef43373adaf215a4c077f0bdf12bac488c74dbd4d55718d46c17a617b93c8660736b70bcd61a8820ece67"}
|
||||||
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
## 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
|
||||||
|
```
|
||||||
|
|
||||||
|
### generate a NIP-70 protected event with a date set to two weeks ago and some multi-value tags
|
||||||
|
```shell
|
||||||
|
~> nak event --ts 'two weeks ago' -t '-' -t 'e=f59911b561c37c90b01e9e5c2557307380835c83399756f4d62d8167227e420a;wss://relay.whatever.com;root;a9e0f110f636f3191644110c19a33448daf09d7cda9708a769e91b7e91340208' -t 'p=a9e0f110f636f3191644110c19a33448daf09d7cda9708a769e91b7e91340208;wss://p-relay.com' -c 'I know the future'
|
||||||
|
{"kind":1,"id":"f030fccd90c783858dfcee204af94826cf0f1c85d6fc85a0087e9e5172419393","pubkey":"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798","created_at":1719677535,"tags":[["-"],["e","f59911b561c37c90b01e9e5c2557307380835c83399756f4d62d8167227e420a","wss://relay.whatever.com","root","a9e0f110f636f3191644110c19a33448daf09d7cda9708a769e91b7e91340208"],["p","a9e0f110f636f3191644110c19a33448daf09d7cda9708a769e91b7e91340208","wss://p-relay.com"]],"content":"I know the future","sig":"8b36a74e29df8bc12bed66896820da6940d4d9409721b3ed2e910c838833a178cb45fd5bb1c6eb6adc66ab2808bfac9f6644a2c55a6570bb2ad90f221c9c7551"}
|
||||||
|
```
|
||||||
|
|
||||||
|
### download the latest 50000 notes from a relay, regardless of their natural query limits, by paginating requests
|
||||||
|
```shell
|
||||||
|
~> nak req -k 1 --limit 50000 --paginate --paginate-interval 2s nos.lol > events.jsonl
|
||||||
|
~> wc -l events.jsonl
|
||||||
|
50000 events.jsonl
|
||||||
|
```
|
||||||
|
|
||||||
|
### run a somewhat verbose local relay for test purposes
|
||||||
|
```shell
|
||||||
|
~> nak serve
|
||||||
|
> relay running at ws://localhost:10547
|
||||||
|
got request {"kinds":[1],"authors":["79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"],"since":1724082362}
|
||||||
|
got event {"kind":1,"id":"e3c6bf630d6deea74c0ee2f7f7ba6da55a627498a32f1e72029229bb1810bce3","pubkey":"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798","created_at":1724082366,"tags":[],"content":"two","sig":"34261cf226c3fee2df24e55a89f43f5349c98a64bce46bdc46807b0329f334cea93e9e8bc285c1259a5684cf23f5e507c8e6dad47a31a6615d706b1130d09e69"}
|
||||||
|
got event {"kind":1,"id":"0bbb397c8f87ae557650b9d6ee847292df8e530c458ffea1b24bdcb7bed0ec5e","pubkey":"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798","created_at":1724082369,"tags":[],"content":"three","sig":"aa1cb7d5f0f03f358fc4c0a4351a4f1c66e3a7627021b618601c56ba598b825b6d95d9c8720a4c60666a7eb21e17018cf326222f9f574a9396f2f2da7f007546"}
|
||||||
|
• events stored: 2, subscriptions opened: 1
|
||||||
|
got event {"kind":1,"id":"029ebff759dd54dbd01b929f879fea5802de297e1c3768ca16d9b97cc8bca38f","pubkey":"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798","created_at":1724082371,"tags":[],"content":"four","sig":"9816de517d87d4c3ede57c1c50e3c237486794241afadcd891e1acbba2c5e672286090e6ad3402b047d69bae8095bc4e20e57ac70d92386dfa26db216379330f"}
|
||||||
|
got event {"kind":1,"id":"fe6489fa6fbb925be839377b9b7049d73be755dc2bdad97ff6dd9eecbf8b3a32","pubkey":"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798","created_at":1724082383,"tags":[],"content":"five","sig":"865ce5e32eead5bdb950ac1fbc55bc92dde26818ee3136634538ec42914de179a51e672c2d4269d4362176e5e8cd5e08e69b35b91c6c2af867e129b93d607635"}
|
||||||
|
got request {"kinds":[30818]}
|
||||||
|
• events stored: 4, subscriptions opened: 1
|
||||||
|
```
|
||||||
|
|
||||||
|
### make an event with a PoW target
|
||||||
|
```shell
|
||||||
|
~> nak event -c 'hello getwired.app and labour.fiatjaf.com' --pow 24
|
||||||
|
{"kind":1,"id":"0000009dcc7c62056eafdb41fac817379ec2becf0ce27c5fbe98d0735d968147","pubkey":"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798","created_at":1724160828,"tags":[["nonce","515504","24"]],"content":"hello getwired.app and labour.fiatjaf.com","sig":"7edb988065ccc12779fe99270945b212f3723838f315d76d5e90e9ffa27198f13fa556614295f518d968d55bab81878167d4162b3a7cf81a6b423c6761bd504c"}
|
||||||
|
```
|
||||||
|
|
||||||
|
### make a nostr event signed with a key given as an environment variable
|
||||||
|
|
||||||
|
```shell
|
||||||
|
~> export NOSTR_SECRET_KEY=ncryptsec1qggyy9vw0nclmw8ly9caz6aa7f85a4ufhsct64uva337pulsdw00n6twa2lzhzk2znzsyu60urx9s08lx00ke6ual3lszyn5an9zarm6s70lw5lj6dv3mj3f9p4tvp0we6qyz4gp420mapfmvqheuttv
|
||||||
|
~> nak event -c 'it supports keys as hex, nsec or ncryptsec'
|
||||||
|
type the password to decrypt your secret key: ********
|
||||||
|
{"kind":1,"id":"5cbf3feb9a7d99c3ee2a88693a591caca1a8348fea427b3652c27f7a8a76af48","pubkey":"b00bcab55375d8c7b731dd9841f6d805ff1cf6fdc945e7326786deb5ddac6ce4","created_at":1724247924,"tags":[],"content":"it supports keys as hex, nsec or ncryptsec","sig":"fb3fd170bc10e5042322c7a05dd4bbd8ac9947b39026b8a7afd1ee02524e8e3aa1d9554e9c7b6181ca1b45cab01cd06643bdffa5ce678b475e6b185e1c14b085"}
|
||||||
|
```
|
||||||
|
|
||||||
|
### download some helpful `jq` functions for dealing with nostr events
|
||||||
|
```shell
|
||||||
|
~> nak req -i 412f2d3e73acc312942c055ac2a695dc60bf58ff97e06689a8a79e97796c4cdb relay.westernbtc.com | jq -r .content > ~/.jq
|
||||||
|
```
|
||||||
|
|
||||||
|
### watch a NIP-53 livestream (zap.stream etc)
|
||||||
|
```shell
|
||||||
|
~> # this requires the jq utils from the step above
|
||||||
|
~> mpv $(nak fetch naddr1qqjxvvm9xscnsdtx95cxvcfk956rsvtx943rje3k95mx2dp389jnwwrp8ymxgqg4waehxw309aex2mrp0yhxgctdw4eju6t09upzpn6956apxcad0mfp8grcuugdysg44eepex68h50t73zcathmfs49qvzqqqrkvu7ed38k | jq -r 'tag_value("streaming")')
|
||||||
|
~>
|
||||||
|
~> # or without the utils
|
||||||
|
~> mpv $(nak fetch naddr1qqjxvvm9xscnsdtx95cxvcfk956rsvtx943rje3k95mx2dp389jnwwrp8ymxgqg4waehxw309aex2mrp0yhxgctdw4eju6t09upzpn6956apxcad0mfp8grcuugdysg44eepex68h50t73zcathmfs49qvzqqqrkvu7ed38k | jq -r '.tags | map(select(.[0] == "streaming") | .[1])[0]')
|
||||||
|
```
|
||||||
|
|
||||||
|
### download a NIP-35 torrent from an `nevent`
|
||||||
|
```shell
|
||||||
|
~> # this requires the jq utils from two steps above
|
||||||
|
~> aria2c $(nak fetch nevent1qqsdsg6x7uujekac4ga7k7qa9q9sx8gqj7xzjf5w9us0dm0ghvf4ugspp4mhxue69uhkummn9ekx7mq6dw9y4 | jq -r '"magnet:?xt=urn:btih:\(tag_value("x"))&dn=\(tag_value("title"))&tr=http%3A%2F%2Ftracker.loadpeers.org%3A8080%2FxvRKfvAlnfuf5EfxTT5T0KIVPtbqAHnX%2Fannounce&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.openbittorrent.com%3A6969%2Fannounce&tr=udp%3A%2F%2Fopen.stealth.si%3A80%2Fannounce&tr=udp%3A%2F%2Ftracker.torrent.eu.org%3A451%2Fannounce&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337&tr=\(tags("tracker") | map(.[1] | @uri) | join("&tr="))"')
|
||||||
|
```
|
||||||
|
|
||||||
|
## contributing to this repository
|
||||||
|
|
||||||
Use NIP-34 to send your patches to `naddr1qqpkucttqy28wumn8ghj7un9d3shjtnwdaehgu3wvfnsz9nhwden5te0wfjkccte9ehx7um5wghxyctwvsq3gamnwvaz7tmjv4kxz7fwv3sk6atn9e5k7q3q80cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkwsxpqqqpmej2wctpn`.
|
Use NIP-34 to send your patches to `naddr1qqpkucttqy28wumn8ghj7un9d3shjtnwdaehgu3wvfnsz9nhwden5te0wfjkccte9ehx7um5wghxyctwvsq3gamnwvaz7tmjv4kxz7fwv3sk6atn9e5k7q3q80cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkwsxpqqqpmej2wctpn`.
|
||||||
|
|||||||
14
bunker.go
14
bunker.go
@@ -11,24 +11,24 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
|
"github.com/fiatjaf/cli/v3"
|
||||||
"github.com/nbd-wtf/go-nostr"
|
"github.com/nbd-wtf/go-nostr"
|
||||||
"github.com/nbd-wtf/go-nostr/nip19"
|
"github.com/nbd-wtf/go-nostr/nip19"
|
||||||
"github.com/nbd-wtf/go-nostr/nip46"
|
"github.com/nbd-wtf/go-nostr/nip46"
|
||||||
"github.com/urfave/cli/v3"
|
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
var bunker = &cli.Command{
|
var bunker = &cli.Command{
|
||||||
Name: "bunker",
|
Name: "bunker",
|
||||||
Usage: "starts a NIP-46 signer daemon with the given --sec key",
|
Usage: "starts a NIP-46 signer daemon with the given --sec key",
|
||||||
ArgsUsage: "[relay...]",
|
ArgsUsage: "[relay...]",
|
||||||
Description: ``,
|
Description: ``,
|
||||||
|
DisableSliceFlagSeparator: true,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "sec",
|
Name: "sec",
|
||||||
Usage: "secret key to sign the event, as hex or nsec",
|
Usage: "secret key to sign the event, as hex or nsec",
|
||||||
DefaultText: "the key '1'",
|
DefaultText: "the key '1'",
|
||||||
Value: "0000000000000000000000000000000000000000000000000000000000000001",
|
|
||||||
},
|
},
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "prompt-sec",
|
Name: "prompt-sec",
|
||||||
@@ -177,7 +177,7 @@ var bunker = &cli.Command{
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
return harmless || slices.Contains(authorizedKeys, from) || slices.Contains(authorizedSecrets, secret)
|
return slices.Contains(authorizedKeys, from) || slices.Contains(authorizedSecrets, secret)
|
||||||
}
|
}
|
||||||
|
|
||||||
for ie := range events {
|
for ie := range events {
|
||||||
|
|||||||
9
count.go
9
count.go
@@ -7,14 +7,15 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/fiatjaf/cli/v3"
|
||||||
"github.com/nbd-wtf/go-nostr"
|
"github.com/nbd-wtf/go-nostr"
|
||||||
"github.com/urfave/cli/v3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var count = &cli.Command{
|
var count = &cli.Command{
|
||||||
Name: "count",
|
Name: "count",
|
||||||
Usage: "generates encoded COUNT messages and optionally use them to talk to relays",
|
Usage: "generates encoded COUNT messages and optionally use them to talk to relays",
|
||||||
Description: `outputs a NIP-45 request (the flags are mostly the same as 'nak req').`,
|
Description: `outputs a NIP-45 request (the flags are mostly the same as 'nak req').`,
|
||||||
|
DisableSliceFlagSeparator: true,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
&cli.StringSliceFlag{
|
&cli.StringSliceFlag{
|
||||||
Name: "author",
|
Name: "author",
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/fiatjaf/cli/v3"
|
||||||
"github.com/nbd-wtf/go-nostr"
|
"github.com/nbd-wtf/go-nostr"
|
||||||
"github.com/nbd-wtf/go-nostr/nip19"
|
"github.com/nbd-wtf/go-nostr/nip19"
|
||||||
sdk "github.com/nbd-wtf/nostr-sdk"
|
sdk "github.com/nbd-wtf/nostr-sdk"
|
||||||
"github.com/urfave/cli/v3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var decode = &cli.Command{
|
var decode = &cli.Command{
|
||||||
@@ -20,6 +20,7 @@ var decode = &cli.Command{
|
|||||||
nak decode nevent1qqs29yet5tp0qq5xu5qgkeehkzqh5qu46739axzezcxpj4tjlkx9j7gpr4mhxue69uhkummnw3ez6ur4vgh8wetvd3hhyer9wghxuet5sh59ud
|
nak decode nevent1qqs29yet5tp0qq5xu5qgkeehkzqh5qu46739axzezcxpj4tjlkx9j7gpr4mhxue69uhkummnw3ez6ur4vgh8wetvd3hhyer9wghxuet5sh59ud
|
||||||
nak decode nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpz4mhxue69uhk2er9dchxummnw3ezumrpdejqz8thwden5te0dehhxarj94c82c3wwajkcmr0wfjx2u3wdejhgqgcwaehxw309aex2mrp0yhxummnw3exzarf9e3k7mgnp0sh5
|
nak decode nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpz4mhxue69uhk2er9dchxummnw3ezumrpdejqz8thwden5te0dehhxarj94c82c3wwajkcmr0wfjx2u3wdejhgqgcwaehxw309aex2mrp0yhxummnw3exzarf9e3k7mgnp0sh5
|
||||||
nak decode nsec1jrmyhtjhgd9yqalps8hf9mayvd58852gtz66m7tqpacjedkp6kxq4dyxsr`,
|
nak decode nsec1jrmyhtjhgd9yqalps8hf9mayvd58852gtz66m7tqpacjedkp6kxq4dyxsr`,
|
||||||
|
DisableSliceFlagSeparator: true,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "id",
|
Name: "id",
|
||||||
|
|||||||
21
encode.go
21
encode.go
@@ -4,9 +4,9 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/fiatjaf/cli/v3"
|
||||||
"github.com/nbd-wtf/go-nostr"
|
"github.com/nbd-wtf/go-nostr"
|
||||||
"github.com/nbd-wtf/go-nostr/nip19"
|
"github.com/nbd-wtf/go-nostr/nip19"
|
||||||
"github.com/urfave/cli/v3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var encode = &cli.Command{
|
var encode = &cli.Command{
|
||||||
@@ -25,10 +25,12 @@ var encode = &cli.Command{
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
DisableSliceFlagSeparator: true,
|
||||||
Commands: []*cli.Command{
|
Commands: []*cli.Command{
|
||||||
{
|
{
|
||||||
Name: "npub",
|
Name: "npub",
|
||||||
Usage: "encode a hex public key into bech32 'npub' format",
|
Usage: "encode a hex public key into bech32 'npub' format",
|
||||||
|
DisableSliceFlagSeparator: true,
|
||||||
Action: func(ctx context.Context, c *cli.Command) error {
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
for target := range getStdinLinesOrArguments(c.Args()) {
|
for target := range getStdinLinesOrArguments(c.Args()) {
|
||||||
if ok := nostr.IsValidPublicKey(target); !ok {
|
if ok := nostr.IsValidPublicKey(target); !ok {
|
||||||
@@ -48,8 +50,9 @@ var encode = &cli.Command{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "nsec",
|
Name: "nsec",
|
||||||
Usage: "encode a hex private key into bech32 'nsec' format",
|
Usage: "encode a hex private key into bech32 'nsec' format",
|
||||||
|
DisableSliceFlagSeparator: true,
|
||||||
Action: func(ctx context.Context, c *cli.Command) error {
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
for target := range getStdinLinesOrArguments(c.Args()) {
|
for target := range getStdinLinesOrArguments(c.Args()) {
|
||||||
if ok := nostr.IsValid32ByteHex(target); !ok {
|
if ok := nostr.IsValid32ByteHex(target); !ok {
|
||||||
@@ -78,6 +81,7 @@ var encode = &cli.Command{
|
|||||||
Usage: "attach relay hints to nprofile code",
|
Usage: "attach relay hints to nprofile code",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
DisableSliceFlagSeparator: true,
|
||||||
Action: func(ctx context.Context, c *cli.Command) error {
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
for target := range getStdinLinesOrArguments(c.Args()) {
|
for target := range getStdinLinesOrArguments(c.Args()) {
|
||||||
if ok := nostr.IsValid32ByteHex(target); !ok {
|
if ok := nostr.IsValid32ByteHex(target); !ok {
|
||||||
@@ -116,6 +120,7 @@ var encode = &cli.Command{
|
|||||||
Usage: "attach an author pubkey as a hint to the nevent code",
|
Usage: "attach an author pubkey as a hint to the nevent code",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
DisableSliceFlagSeparator: true,
|
||||||
Action: func(ctx context.Context, c *cli.Command) error {
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
for target := range getStdinLinesOrArguments(c.Args()) {
|
for target := range getStdinLinesOrArguments(c.Args()) {
|
||||||
if ok := nostr.IsValid32ByteHex(target); !ok {
|
if ok := nostr.IsValid32ByteHex(target); !ok {
|
||||||
@@ -174,6 +179,7 @@ var encode = &cli.Command{
|
|||||||
Usage: "attach relay hints to naddr code",
|
Usage: "attach relay hints to naddr code",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
DisableSliceFlagSeparator: true,
|
||||||
Action: func(ctx context.Context, c *cli.Command) error {
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
for d := range getStdinLinesOrBlank() {
|
for d := range getStdinLinesOrBlank() {
|
||||||
pubkey := c.String("pubkey")
|
pubkey := c.String("pubkey")
|
||||||
@@ -211,8 +217,9 @@ var encode = &cli.Command{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "note",
|
Name: "note",
|
||||||
Usage: "generate note1 event codes (not recommended)",
|
Usage: "generate note1 event codes (not recommended)",
|
||||||
|
DisableSliceFlagSeparator: true,
|
||||||
Action: func(ctx context.Context, c *cli.Command) error {
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
for target := range getStdinLinesOrArguments(c.Args()) {
|
for target := range getStdinLinesOrArguments(c.Args()) {
|
||||||
if ok := nostr.IsValid32ByteHex(target); !ok {
|
if ok := nostr.IsValid32ByteHex(target); !ok {
|
||||||
|
|||||||
111
event.go
111
event.go
@@ -5,19 +5,22 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/fiatjaf/cli/v3"
|
||||||
"github.com/mailru/easyjson"
|
"github.com/mailru/easyjson"
|
||||||
"github.com/nbd-wtf/go-nostr"
|
"github.com/nbd-wtf/go-nostr"
|
||||||
|
"github.com/nbd-wtf/go-nostr/nip13"
|
||||||
"github.com/nbd-wtf/go-nostr/nip19"
|
"github.com/nbd-wtf/go-nostr/nip19"
|
||||||
"github.com/nbd-wtf/go-nostr/nson"
|
|
||||||
"github.com/urfave/cli/v3"
|
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
const CATEGORY_EVENT_FIELDS = "EVENT FIELDS"
|
const (
|
||||||
|
CATEGORY_EVENT_FIELDS = "EVENT FIELDS"
|
||||||
|
CATEGORY_SIGNER = "SIGNER OPTIONS"
|
||||||
|
CATEGORY_EXTRAS = "EXTRAS"
|
||||||
|
)
|
||||||
|
|
||||||
var event = &cli.Command{
|
var event = &cli.Command{
|
||||||
Name: "event",
|
Name: "event",
|
||||||
@@ -33,25 +36,29 @@ if an event -- or a partial event -- is given on stdin, the flags can be used to
|
|||||||
example:
|
example:
|
||||||
echo '{"id":"a889df6a387419ff204305f4c2d296ee328c3cd4f8b62f205648a541b4554dfb","pubkey":"c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5","created_at":1698623783,"kind":1,"tags":[],"content":"hello from the nostr army knife","sig":"84876e1ee3e726da84e5d195eb79358b2b3eaa4d9bd38456fde3e8a2af3f1cd4cda23f23fda454869975b3688797d4c66e12f4c51c1b43c6d2997c5e61865661"}' | nak event wss://offchain.pub
|
echo '{"id":"a889df6a387419ff204305f4c2d296ee328c3cd4f8b62f205648a541b4554dfb","pubkey":"c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5","created_at":1698623783,"kind":1,"tags":[],"content":"hello from the nostr army knife","sig":"84876e1ee3e726da84e5d195eb79358b2b3eaa4d9bd38456fde3e8a2af3f1cd4cda23f23fda454869975b3688797d4c66e12f4c51c1b43c6d2997c5e61865661"}' | nak event wss://offchain.pub
|
||||||
echo '{"tags": [["t", "spam"]]}' | nak event -c 'this is spam'`,
|
echo '{"tags": [["t", "spam"]]}' | nak event -c 'this is spam'`,
|
||||||
|
DisableSliceFlagSeparator: true,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "sec",
|
Name: "sec",
|
||||||
Usage: "secret key to sign the event, as nsec, ncryptsec or hex",
|
Usage: "secret key to sign the event, as nsec, ncryptsec or hex",
|
||||||
DefaultText: "the key '1'",
|
DefaultText: "the key '1'",
|
||||||
Value: "0000000000000000000000000000000000000000000000000000000000000001",
|
Category: CATEGORY_SIGNER,
|
||||||
},
|
},
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "prompt-sec",
|
Name: "prompt-sec",
|
||||||
Usage: "prompt the user to paste a hex or nsec with which to sign the event",
|
Usage: "prompt the user to paste a hex or nsec with which to sign the event",
|
||||||
|
Category: CATEGORY_SIGNER,
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "connect",
|
Name: "connect",
|
||||||
Usage: "sign event using NIP-46, expects a bunker://... URL",
|
Usage: "sign event using NIP-46, expects a bunker://... URL",
|
||||||
|
Category: CATEGORY_SIGNER,
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "connect-as",
|
Name: "connect-as",
|
||||||
Usage: "private key to when communicating with the bunker given on --connect",
|
Usage: "private key to when communicating with the bunker given on --connect",
|
||||||
DefaultText: "a random key",
|
DefaultText: "a random key",
|
||||||
|
Category: CATEGORY_SIGNER,
|
||||||
},
|
},
|
||||||
// ~ these args are only for the convoluted musig2 signing process
|
// ~ these args are only for the convoluted musig2 signing process
|
||||||
// they will be generally copy-shared-pasted across some manual coordination method between participants
|
// they will be generally copy-shared-pasted across some manual coordination method between participants
|
||||||
@@ -60,6 +67,7 @@ example:
|
|||||||
Usage: "number of signers to use for musig2",
|
Usage: "number of signers to use for musig2",
|
||||||
Value: 1,
|
Value: 1,
|
||||||
DefaultText: "1 -- i.e. do not use musig2 at all",
|
DefaultText: "1 -- i.e. do not use musig2 at all",
|
||||||
|
Category: CATEGORY_SIGNER,
|
||||||
},
|
},
|
||||||
&cli.StringSliceFlag{
|
&cli.StringSliceFlag{
|
||||||
Name: "musig-pubkey",
|
Name: "musig-pubkey",
|
||||||
@@ -78,23 +86,27 @@ example:
|
|||||||
Hidden: true,
|
Hidden: true,
|
||||||
},
|
},
|
||||||
// ~~~
|
// ~~~
|
||||||
&cli.BoolFlag{
|
&cli.UintFlag{
|
||||||
Name: "envelope",
|
Name: "pow",
|
||||||
Usage: "print the event enveloped in a [\"EVENT\", ...] message ready to be sent to a relay",
|
Usage: "NIP-13 difficulty to target when doing hash work on the event id",
|
||||||
|
Category: CATEGORY_EXTRAS,
|
||||||
},
|
},
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "auth",
|
Name: "envelope",
|
||||||
Usage: "always perform NIP-42 \"AUTH\" when facing an \"auth-required: \" rejection and try again",
|
Usage: "print the event enveloped in a [\"EVENT\", ...] message ready to be sent to a relay",
|
||||||
|
Category: CATEGORY_EXTRAS,
|
||||||
},
|
},
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "nevent",
|
Name: "auth",
|
||||||
Usage: "print the nevent code (to stderr) after the event is published",
|
Usage: "always perform NIP-42 \"AUTH\" when facing an \"auth-required: \" rejection and try again",
|
||||||
|
Category: CATEGORY_EXTRAS,
|
||||||
},
|
},
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "nson",
|
Name: "nevent",
|
||||||
Usage: "encode the event using NSON",
|
Usage: "print the nevent code (to stderr) after the event is published",
|
||||||
|
Category: CATEGORY_EXTRAS,
|
||||||
},
|
},
|
||||||
&cli.IntFlag{
|
&cli.UintFlag{
|
||||||
Name: "kind",
|
Name: "kind",
|
||||||
Aliases: []string{"k"},
|
Aliases: []string{"k"},
|
||||||
Usage: "event kind",
|
Usage: "event kind",
|
||||||
@@ -131,12 +143,12 @@ example:
|
|||||||
Usage: "shortcut for --tag d=<value>",
|
Usage: "shortcut for --tag d=<value>",
|
||||||
Category: CATEGORY_EVENT_FIELDS,
|
Category: CATEGORY_EVENT_FIELDS,
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&NaturalTimeFlag{
|
||||||
Name: "created-at",
|
Name: "created-at",
|
||||||
Aliases: []string{"time", "ts"},
|
Aliases: []string{"time", "ts"},
|
||||||
Usage: "unix timestamp value for the created_at field",
|
Usage: "unix timestamp value for the created_at field",
|
||||||
DefaultText: "now",
|
DefaultText: "now",
|
||||||
Value: "",
|
Value: nostr.Now(),
|
||||||
Category: CATEGORY_EVENT_FIELDS,
|
Category: CATEGORY_EVENT_FIELDS,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -182,7 +194,7 @@ example:
|
|||||||
kindWasSupplied = strings.Contains(stdinEvent, `"kind"`)
|
kindWasSupplied = strings.Contains(stdinEvent, `"kind"`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if kind := c.Int("kind"); slices.Contains(c.FlagNames(), "kind") {
|
if kind := c.Uint("kind"); slices.Contains(c.FlagNames(), "kind") {
|
||||||
evt.Kind = int(kind)
|
evt.Kind = int(kind)
|
||||||
mustRehashAndResign = true
|
mustRehashAndResign = true
|
||||||
} else if !kindWasSupplied {
|
} else if !kindWasSupplied {
|
||||||
@@ -190,16 +202,17 @@ example:
|
|||||||
mustRehashAndResign = true
|
mustRehashAndResign = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if content := c.String("content"); content != "" {
|
if c.IsSet("content") {
|
||||||
evt.Content = content
|
evt.Content = c.String("content")
|
||||||
mustRehashAndResign = true
|
mustRehashAndResign = true
|
||||||
} else if evt.Content == "" && evt.Kind == 1 {
|
} else if evt.Content == "" && evt.Kind == 1 {
|
||||||
evt.Content = "hello from the nostr army knife"
|
evt.Content = "hello from the nostr army knife"
|
||||||
mustRehashAndResign = true
|
mustRehashAndResign = true
|
||||||
}
|
}
|
||||||
|
|
||||||
tags := make(nostr.Tags, 0, 5)
|
tagFlags := c.StringSlice("tag")
|
||||||
for _, tagFlag := range c.StringSlice("tag") {
|
tags := make(nostr.Tags, 0, len(tagFlags)+2)
|
||||||
|
for _, tagFlag := range tagFlags {
|
||||||
// tags are in the format key=value
|
// tags are in the format key=value
|
||||||
tagName, tagValue, found := strings.Cut(tagFlag, "=")
|
tagName, tagValue, found := strings.Cut(tagFlag, "=")
|
||||||
tag := []string{tagName}
|
tag := []string{tagName}
|
||||||
@@ -208,20 +221,17 @@ example:
|
|||||||
tagValues := strings.Split(tagValue, ";")
|
tagValues := strings.Split(tagValue, ";")
|
||||||
tag = append(tag, tagValues...)
|
tag = append(tag, tagValues...)
|
||||||
}
|
}
|
||||||
tags = tags.AppendUnique(tag)
|
tags = append(tags, tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, etag := range c.StringSlice("e") {
|
for _, etag := range c.StringSlice("e") {
|
||||||
tags = tags.AppendUnique([]string{"e", etag})
|
tags = tags.AppendUnique([]string{"e", etag})
|
||||||
mustRehashAndResign = true
|
|
||||||
}
|
}
|
||||||
for _, ptag := range c.StringSlice("p") {
|
for _, ptag := range c.StringSlice("p") {
|
||||||
tags = tags.AppendUnique([]string{"p", ptag})
|
tags = tags.AppendUnique([]string{"p", ptag})
|
||||||
mustRehashAndResign = true
|
|
||||||
}
|
}
|
||||||
for _, dtag := range c.StringSlice("d") {
|
for _, dtag := range c.StringSlice("d") {
|
||||||
tags = tags.AppendUnique([]string{"d", dtag})
|
tags = tags.AppendUnique([]string{"d", dtag})
|
||||||
mustRehashAndResign = true
|
|
||||||
}
|
}
|
||||||
if len(tags) > 0 {
|
if len(tags) > 0 {
|
||||||
for _, tag := range tags {
|
for _, tag := range tags {
|
||||||
@@ -230,22 +240,41 @@ example:
|
|||||||
mustRehashAndResign = true
|
mustRehashAndResign = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if createdAt := c.String("created-at"); createdAt != "" {
|
if c.IsSet("created-at") {
|
||||||
ts := time.Now()
|
evt.CreatedAt = getNaturalDate(c, "created-at")
|
||||||
if createdAt != "now" {
|
|
||||||
if v, err := strconv.ParseInt(createdAt, 10, 64); err != nil {
|
|
||||||
return fmt.Errorf("failed to parse timestamp '%s': %w", createdAt, err)
|
|
||||||
} else {
|
|
||||||
ts = time.Unix(v, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
evt.CreatedAt = nostr.Timestamp(ts.Unix())
|
|
||||||
mustRehashAndResign = true
|
mustRehashAndResign = true
|
||||||
} else if evt.CreatedAt == 0 {
|
} else if evt.CreatedAt == 0 {
|
||||||
evt.CreatedAt = nostr.Now()
|
evt.CreatedAt = nostr.Now()
|
||||||
mustRehashAndResign = true
|
mustRehashAndResign = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if difficulty := c.Uint("pow"); difficulty > 0 {
|
||||||
|
// before doing pow we need the pubkey
|
||||||
|
if bunker != nil {
|
||||||
|
evt.PubKey, err = bunker.GetPublicKey(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("can't pow: failed to get public key from bunker: %w", err)
|
||||||
|
}
|
||||||
|
} else if numSigners := c.Uint("musig"); numSigners > 1 && sec != "" {
|
||||||
|
pubkeys := c.StringSlice("musig-pubkey")
|
||||||
|
if int(numSigners) != len(pubkeys) {
|
||||||
|
return fmt.Errorf("when doing a pow with musig we must know all signer pubkeys upfront")
|
||||||
|
}
|
||||||
|
evt.PubKey, err = getMusigAggregatedKey(ctx, pubkeys)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
evt.PubKey, _ = nostr.GetPublicKey(sec)
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to generate work with this difficulty -- runs forever
|
||||||
|
nonceTag, _ := nip13.DoWork(ctx, evt, int(difficulty))
|
||||||
|
evt.Tags = append(evt.Tags, nonceTag)
|
||||||
|
|
||||||
|
mustRehashAndResign = true
|
||||||
|
}
|
||||||
|
|
||||||
if evt.Sig == "" || mustRehashAndResign {
|
if evt.Sig == "" || mustRehashAndResign {
|
||||||
if bunker != nil {
|
if bunker != nil {
|
||||||
if err := bunker.SignEvent(ctx, &evt); err != nil {
|
if err := bunker.SignEvent(ctx, &evt); err != nil {
|
||||||
@@ -276,8 +305,6 @@ example:
|
|||||||
if c.Bool("envelope") {
|
if c.Bool("envelope") {
|
||||||
j, _ := json.Marshal(nostr.EventEnvelope{Event: evt})
|
j, _ := json.Marshal(nostr.EventEnvelope{Event: evt})
|
||||||
result = string(j)
|
result = string(j)
|
||||||
} else if c.Bool("nson") {
|
|
||||||
result, _ = nson.Marshal(&evt)
|
|
||||||
} else {
|
} else {
|
||||||
j, _ := easyjson.Marshal(&evt)
|
j, _ := easyjson.Marshal(&evt)
|
||||||
result = string(j)
|
result = string(j)
|
||||||
|
|||||||
110
example_test.go
110
example_test.go
@@ -2,9 +2,12 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"os"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// these tests are tricky because commands and flags are declared as globals and values set in one call may persist
|
||||||
|
// to the next. for example, if in the first test we set --limit 2 then doesn't specify --limit in the second then
|
||||||
|
// it will still return true for cmd.IsSet("limit") and then we will set .LimitZero = true
|
||||||
|
|
||||||
var ctx = context.Background()
|
var ctx = context.Background()
|
||||||
|
|
||||||
func ExampleEventBasic() {
|
func ExampleEventBasic() {
|
||||||
@@ -14,22 +17,22 @@ func ExampleEventBasic() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// (for some reason there can only be one test dealing with stdin in the suite otherwise it halts)
|
// (for some reason there can only be one test dealing with stdin in the suite otherwise it halts)
|
||||||
func ExampleEventParsingFromStdin() {
|
// func ExampleEventParsingFromStdin() {
|
||||||
prevStdin := os.Stdin
|
// prevStdin := os.Stdin
|
||||||
defer func() { os.Stdin = prevStdin }()
|
// defer func() { os.Stdin = prevStdin }()
|
||||||
r, w, _ := os.Pipe()
|
// r, w, _ := os.Pipe()
|
||||||
os.Stdin = r
|
// os.Stdin = r
|
||||||
w.WriteString("{\"content\":\"hello world\"}\n{\"content\":\"hello sun\"}\n")
|
// w.WriteString("{\"content\":\"hello world\"}\n{\"content\":\"hello sun\"}\n")
|
||||||
app.Run(ctx, []string{"nak", "event", "-t", "t=spam", "--ts", "1699485669"})
|
// app.Run(ctx, []string{"nak", "event", "-t", "t=spam", "--ts", "1699485669"})
|
||||||
// Output:
|
// // Output:
|
||||||
// {"id":"bda134f9077c11973afe6aa5a1cc6f5bcea01c40d318b8f91dcb8e50507cfa52","pubkey":"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798","created_at":1699485669,"kind":1,"tags":[["t","spam"]],"content":"hello world","sig":"7552454bb8e7944230142634e3e34ac7468bad9b21ed6909da572c611018dff1d14d0792e98b5806f6330edc51e09efa6d0b66a9694dc34606c70f4e580e7493"}
|
// // {"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"}
|
// // {"id":"879c36ec73acca288825b53585389581d3836e7f0fe4d46e5eba237ca56d6af5","pubkey":"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798","created_at":1699485669,"kind":1,"tags":[["t","spam"]],"content":"hello sun","sig":"6c7e6b13ebdf931d26acfdd00bec2ec1140ddaf8d1ed61453543a14e729a460fe36c40c488ccb194a0e1ab9511cb6c36741485f501bdb93c39ca4c51bc59cbd4"}
|
||||||
}
|
// }
|
||||||
|
|
||||||
func ExampleEventComplex() {
|
func ExampleEventComplex() {
|
||||||
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"})
|
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:
|
// 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"}
|
// {"kind":11,"id":"19aba166dcf354bf5ef64f4afe69ada1eb851495001ee05e07d393ee8c8ea179","pubkey":"2fa2104d6b38d11b0230010559879124e42ab8dfeff5ff29dc9cdadd4ecacc3f","created_at":1699485669,"tags":[["t","spam"],["r","https://abc.def?name=foobar","nothing"],["e","36d88cf5fcc449f2390a424907023eda7a74278120eebab8d02797cd92e7e29c"]],"content":"skjdbaskd","sig":"cf452def4a68341c897c3fc96fa34dc6895a5b8cc266d4c041bcdf758ec992ec5adb8b0179e98552aaaf9450526a26d7e62e413b15b1c57e0cfc8db6b29215d7"}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExampleEncode() {
|
func ExampleEncode() {
|
||||||
@@ -70,17 +73,11 @@ func ExampleReq() {
|
|||||||
// ["REQ","nak",{"kinds":[1],"authors":["2fa2104d6b38d11b0230010559879124e42ab8dfeff5ff29dc9cdadd4ecacc3f"],"limit":18,"#e":["aec4de6d051a7c2b6ca2d087903d42051a31e07fb742f1240970084822de10a6"]}]
|
// ["REQ","nak",{"kinds":[1],"authors":["2fa2104d6b38d11b0230010559879124e42ab8dfeff5ff29dc9cdadd4ecacc3f"],"limit":18,"#e":["aec4de6d051a7c2b6ca2d087903d42051a31e07fb742f1240970084822de10a6"]}]
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExampleReqIdFromRelay() {
|
|
||||||
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() {
|
func ExampleMultipleFetch() {
|
||||||
app.Run(ctx, []string{"nak", "fetch", "naddr1qqyrgcmyxe3kvefhqyxhwumn8ghj7mn0wvhxcmmvqgs9kqvr4dkruv3t7n2pc6e6a7v9v2s5fprmwjv4gde8c4fe5y29v0srqsqqql9ngrt6tu", "nevent1qyd8wumn8ghj7urewfsk66ty9enxjct5dfskvtnrdakj7qgmwaehxw309aex2mrp0yh8wetnw3jhymnzw33jucm0d5hszxthwden5te0wfjkccte9eekummjwsh8xmmrd9skctcpzamhxue69uhkzarvv9ejumn0wd68ytnvv9hxgtcqyqllp5v5j0nxr74fptqxkhvfv0h3uj870qpk3ln8a58agyxl3fka296ewr8"})
|
app.Run(ctx, []string{"nak", "fetch", "naddr1qqyrgcmyxe3kvefhqyxhwumn8ghj7mn0wvhxcmmvqgs9kqvr4dkruv3t7n2pc6e6a7v9v2s5fprmwjv4gde8c4fe5y29v0srqsqqql9ngrt6tu", "nevent1qyd8wumn8ghj7urewfsk66ty9enxjct5dfskvtnrdakj7qgmwaehxw309aex2mrp0yh8wetnw3jhymnzw33jucm0d5hszxthwden5te0wfjkccte9eekummjwsh8xmmrd9skctcpzamhxue69uhkzarvv9ejumn0wd68ytnvv9hxgtcqyqllp5v5j0nxr74fptqxkhvfv0h3uj870qpk3ln8a58agyxl3fka296ewr8"})
|
||||||
// Output:
|
// 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"}
|
// {"kind":31923,"id":"9ae5014573fc75ced00b343868d2cd9343ebcbbae50591c6fa8ae1cd99568f05","pubkey":"5b0183ab6c3e322bf4d41c6b3aef98562a144847b7499543727c5539a114563e","created_at":1707764605,"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"}
|
// {"kind":1,"id":"3ff0d19493e661faa90ac06b5d8963ef1e48fe780368fe67ed0fd410df8a6dd5","pubkey":"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d","created_at":1710759386,"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() {
|
func ExampleKeyPublic() {
|
||||||
@@ -91,50 +88,37 @@ func ExampleKeyPublic() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ExampleKeyDecrypt() {
|
func ExampleKeyDecrypt() {
|
||||||
app.Run(ctx, []string{"nak", "key", "decrypt", "ncryptsec1qggfep0m5ythsegkmwfrhhx2zx5gazyhdygvlngcds4wsgdpzfy6nr0exy0pdk0ydwrqyhndt2trtwcgwwag0ja3aqclzptfxxqvprdyaz3qfrmazpecx2ff6dph5mfdjnh5sw8sgecul32eru6xet34", "banana"})
|
app.Run(ctx, []string{"nak", "key", "decrypt", "ncryptsec1qgg2gx2a7hxpsse2zulrv7m8qwccvl3mh8e9k8vtz3wpyrwuuclaq73gz7ddt5kpa93qyfhfjakguuf8uhw90jn6mszh7kqeh9mxzlyw8hy75fluzx4h75frwmu2yngsq7hx7w32d0vdyxyns5g6rqft", "banana"})
|
||||||
// Output:
|
// Output:
|
||||||
// nsec180cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkwsgyumg0
|
// 718d756f60cf5179ef35b39dc6db3ff58f04c0734f81f6d4410f0b047ddf9029
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExampleRelay() {
|
func ExampleReqIdFromRelay() {
|
||||||
app.Run(ctx, []string{"nak", "relay", "relay.nos.social", "pyramid.fiatjaf.com"})
|
app.Run(ctx, []string{"nak", "req", "-i", "20a6606ed548fe7107533cf3416ce1aa5e957c315c2a40249e12bd9873dca7da", "--limit", "1", "nos.lol"})
|
||||||
// Output:
|
// Output:
|
||||||
// {
|
// {"kind":1,"id":"20a6606ed548fe7107533cf3416ce1aa5e957c315c2a40249e12bd9873dca7da","pubkey":"dd664d5e4016433a8cd69f005ae1480804351789b59de5af06276de65633d319","created_at":1720972243,"tags":[["e","bdb2210fe6d9c4b141f08b5d9d1147cd8e1dc1d82f552a889ab171894249d21d","","root"],["e","c2e45f09e7d62ed12afe2b8b1bcf6be823b560a53ef06905365a78979a1b9ee3","","reply"],["p","036533caa872376946d4e4fdea4c1a0441eda38ca2d9d9417bb36006cbaabf58","","mention"]],"content":"Yeah, so bizarre, but I guess most people are meant to be serfs.","sig":"9ea7488415c250d0ac8fcb2219f211cb369dddf2a75c0f63d2db773c6dc1ef9dd9679b8941c0e7551744ea386afebad2024be8ce3ac418d4f47c95e7491af38e"}
|
||||||
// "name": "nos.social strfry relay",
|
}
|
||||||
// "description": "This is a strfry instance handled by nos.social",
|
|
||||||
// "pubkey": "89ef92b9ebe6dc1e4ea398f6477f227e95429627b0a33dc89b640e137b256be5",
|
func ExampleReqWithFlagsAfter1() {
|
||||||
// "contact": "https://nos.social",
|
app.Run(ctx, []string{"nak", "req", "nos.lol", "-i", "20a6606ed548fe7107533cf3416ce1aa5e957c315c2a40249e12bd9873dca7da", "--limit", "1"})
|
||||||
// "supported_nips": [
|
// Output:
|
||||||
// 1,
|
// {"kind":1,"id":"20a6606ed548fe7107533cf3416ce1aa5e957c315c2a40249e12bd9873dca7da","pubkey":"dd664d5e4016433a8cd69f005ae1480804351789b59de5af06276de65633d319","created_at":1720972243,"tags":[["e","bdb2210fe6d9c4b141f08b5d9d1147cd8e1dc1d82f552a889ab171894249d21d","","root"],["e","c2e45f09e7d62ed12afe2b8b1bcf6be823b560a53ef06905365a78979a1b9ee3","","reply"],["p","036533caa872376946d4e4fdea4c1a0441eda38ca2d9d9417bb36006cbaabf58","","mention"]],"content":"Yeah, so bizarre, but I guess most people are meant to be serfs.","sig":"9ea7488415c250d0ac8fcb2219f211cb369dddf2a75c0f63d2db773c6dc1ef9dd9679b8941c0e7551744ea386afebad2024be8ce3ac418d4f47c95e7491af38e"}
|
||||||
// 2,
|
}
|
||||||
// 4,
|
|
||||||
// 9,
|
func ExampleReqWithFlagsAfter2() {
|
||||||
// 11,
|
app.Run(ctx, []string{"nak", "req", "-e", "893d4c10f1c230240812c6bdf9ad877eed1e29e87029d153820c24680bb183b1", "nostr.mom", "--author", "2a7dcf382bcc96a393ada5c975f500393b3f7be6e466bff220aa161ad6b15eb6", "--limit", "1", "-k", "7"})
|
||||||
// 12,
|
// Output:
|
||||||
// 16,
|
// {"kind":7,"id":"9b4868b068ea34ae51092807586c4541b3569d9efc23862aea48ef13de275857","pubkey":"2a7dcf382bcc96a393ada5c975f500393b3f7be6e466bff220aa161ad6b15eb6","created_at":1720987327,"tags":[["e","893d4c10f1c230240812c6bdf9ad877eed1e29e87029d153820c24680bb183b1"],["p","1e978baae414eee990dba992871549ad4a099b9d6f7e71c8059b254ea024dddc"],["k","1"]],"content":"❤️","sig":"7eddd112c642ecdb031330dadc021790642b3c10ecc64158ba3ae63edd798b26afb9b5a3bba72835ce171719a724de1472f65c9b3339b6bead0ce2846f93dfc9"}
|
||||||
// 20,
|
}
|
||||||
// 22,
|
|
||||||
// 28,
|
func ExampleReqWithFlagsAfter3() {
|
||||||
// 33,
|
app.Run(ctx, []string{"nak", "req", "--limit", "1", "pyramid.fiatjaf.com", "-a", "3f770d65d3a764a9c5cb503ae123e62ec7598ad035d836e2a810f3877a745b24", "-qp", "3f770d65d3a764a9c5cb503ae123e62ec7598ad035d836e2a810f3877a745b24", "-e", "9f3c1121c96edf17d84b9194f74d66d012b28c4e25b3ef190582c76b8546a188"})
|
||||||
// 40
|
// Output:
|
||||||
// ],
|
// {"kind":1,"id":"101572c80ebdc963dab8440f6307387a3023b6d90f7e495d6c5ee1ef77045a67","pubkey":"3f770d65d3a764a9c5cb503ae123e62ec7598ad035d836e2a810f3877a745b24","created_at":1720987305,"tags":[["e","ceacdc29fa7a0b51640b30d2424e188215460617db5ba5bb52d3fbf0094eebb3","","root"],["e","9f3c1121c96edf17d84b9194f74d66d012b28c4e25b3ef190582c76b8546a188","","reply"],["p","3f770d65d3a764a9c5cb503ae123e62ec7598ad035d836e2a810f3877a745b24"],["p","6b96c3eb36c6cd457d906bbaafe7b36cacfb8bcc4ab235be6eab3b71c6669251"]],"content":"Nope. I grew up playing in the woods. Never once saw a bear in the woods. If I did, I'd probably shiy my pants, then scream at it like I was a crazy person with my arms above my head to make me seem huge.","sig":"b098820b4a5635865cada9f9a5813be2bc6dd7180e16e590cf30e07916d8ed6ed98ab38b64f3bfba12d88d37335f229f7ef8c084bc48132e936c664a54d3e650"}
|
||||||
// "software": "git+https://github.com/hoytech/strfry.git",
|
}
|
||||||
// "version": "0.9.4",
|
|
||||||
// "icon": ""
|
func ExampleNaturalTimestamps() {
|
||||||
// }
|
app.Run(ctx, []string{"nak", "event", "-t", "plu=pla", "-e", "3f770d65d3a764a9c5cb503ae123e62ec7598ad035d836e2a810f3877a745b24", "--ts", "May 19 2018 03:37:19", "-c", "nn"})
|
||||||
// {
|
// Output:
|
||||||
// "name": "the fiatjaf pyramid",
|
// {"kind":0,"id":"b10da0095f96aa2accd99fa3d93bf29a76f51d2594cf5a0a52f8e961aecd0b67","pubkey":"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798","created_at":1526711839,"tags":[["plu","pla"],["e","3f770d65d3a764a9c5cb503ae123e62ec7598ad035d836e2a810f3877a745b24"]],"content":"nn","sig":"988442c97064a041ba5e2bfbd64e84d3f819b2169e865511d9d53e74667949ff165325942acaa2ca233c8b529adedf12cf44088cf04081b56d098c5f4d52dd8f"}
|
||||||
// "description": "a relay just for the coolest of the coolest",
|
|
||||||
// "pubkey": "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d",
|
|
||||||
// "contact": "",
|
|
||||||
// "supported_nips": [],
|
|
||||||
// "software": "https://github.com/fiatjaf/khatru",
|
|
||||||
// "version": "n/a",
|
|
||||||
// "limitation": {
|
|
||||||
// "auth_required": false,
|
|
||||||
// "payment_required": false,
|
|
||||||
// "restricted_writes": true
|
|
||||||
// },
|
|
||||||
// "icon": "https://clipart-library.com/images_k/pyramid-transparent/pyramid-transparent-19.png"
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|||||||
122
fetch.go
122
fetch.go
@@ -2,32 +2,35 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/fiatjaf/cli/v3"
|
||||||
"github.com/nbd-wtf/go-nostr"
|
"github.com/nbd-wtf/go-nostr"
|
||||||
|
"github.com/nbd-wtf/go-nostr/nip05"
|
||||||
"github.com/nbd-wtf/go-nostr/nip19"
|
"github.com/nbd-wtf/go-nostr/nip19"
|
||||||
sdk "github.com/nbd-wtf/nostr-sdk"
|
sdk "github.com/nbd-wtf/nostr-sdk"
|
||||||
"github.com/urfave/cli/v3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var fetch = &cli.Command{
|
var fetch = &cli.Command{
|
||||||
Name: "fetch",
|
Name: "fetch",
|
||||||
Usage: "fetches events related to the given nip19 code from the included relay hints or the author's NIP-65 relays.",
|
Usage: "fetches events related to the given nip19 or nip05 code from the included relay hints or the author's NIP-65 relays.",
|
||||||
Description: `example usage:
|
Description: `example usage:
|
||||||
nak fetch nevent1qqsxrwm0hd3s3fddh4jc2574z3xzufq6qwuyz2rvv3n087zvym3dpaqprpmhxue69uhhqatzd35kxtnjv4kxz7tfdenju6t0xpnej4
|
nak fetch nevent1qqsxrwm0hd3s3fddh4jc2574z3xzufq6qwuyz2rvv3n087zvym3dpaqprpmhxue69uhhqatzd35kxtnjv4kxz7tfdenju6t0xpnej4
|
||||||
echo npub1h8spmtw9m2huyv6v2j2qd5zv956z2zdugl6mgx02f2upffwpm3nqv0j4ps | nak fetch --relay wss://relay.nostr.band`,
|
echo npub1h8spmtw9m2huyv6v2j2qd5zv956z2zdugl6mgx02f2upffwpm3nqv0j4ps | nak fetch --relay wss://relay.nostr.band`,
|
||||||
Flags: []cli.Flag{
|
DisableSliceFlagSeparator: true,
|
||||||
|
Flags: append(reqFilterFlags,
|
||||||
&cli.StringSliceFlag{
|
&cli.StringSliceFlag{
|
||||||
Name: "relay",
|
Name: "relay",
|
||||||
Aliases: []string{"r"},
|
Aliases: []string{"r"},
|
||||||
Usage: "also use these relays to fetch from",
|
Usage: "also use these relays to fetch from",
|
||||||
},
|
},
|
||||||
},
|
),
|
||||||
ArgsUsage: "[nip19code]",
|
ArgsUsage: "[nip05_or_nip19_code]",
|
||||||
Action: func(ctx context.Context, c *cli.Command) error {
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
pool := nostr.NewSimplePool(ctx)
|
sys := sdk.NewSystem()
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
pool.Relays.Range(func(_ string, relay *nostr.Relay) bool {
|
sys.Pool.Relays.Range(func(_ string, relay *nostr.Relay) bool {
|
||||||
relay.Close()
|
relay.Close()
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
@@ -35,64 +38,81 @@ var fetch = &cli.Command{
|
|||||||
|
|
||||||
for code := range getStdinLinesOrArguments(c.Args()) {
|
for code := range getStdinLinesOrArguments(c.Args()) {
|
||||||
filter := nostr.Filter{}
|
filter := nostr.Filter{}
|
||||||
|
|
||||||
prefix, value, err := nip19.Decode(code)
|
|
||||||
if err != nil {
|
|
||||||
ctx = lineProcessingError(ctx, "failed to decode: %s", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
relays := c.StringSlice("relay")
|
|
||||||
if err := normalizeAndValidateRelayURLs(relays); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var authorHint string
|
var authorHint string
|
||||||
|
relays := c.StringSlice("relay")
|
||||||
|
|
||||||
switch prefix {
|
if nip05.IsValidIdentifier(code) {
|
||||||
case "nevent":
|
pp, err := nip05.QueryIdentifier(ctx, code)
|
||||||
v := value.(nostr.EventPointer)
|
if err != nil {
|
||||||
filter.IDs = append(filter.IDs, v.ID)
|
ctx = lineProcessingError(ctx, "failed to fetch nip05: %s", err)
|
||||||
if v.Author != "" {
|
continue
|
||||||
authorHint = v.Author
|
}
|
||||||
|
authorHint = pp.PublicKey
|
||||||
|
relays = append(relays, pp.Relays...)
|
||||||
|
filter.Authors = append(filter.Authors, pp.PublicKey)
|
||||||
|
} else {
|
||||||
|
prefix, value, err := nip19.Decode(code)
|
||||||
|
if err != nil {
|
||||||
|
ctx = lineProcessingError(ctx, "failed to decode: %s", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := normalizeAndValidateRelayURLs(relays); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch prefix {
|
||||||
|
case "nevent":
|
||||||
|
v := value.(nostr.EventPointer)
|
||||||
|
filter.IDs = append(filter.IDs, v.ID)
|
||||||
|
if v.Author != "" {
|
||||||
|
authorHint = v.Author
|
||||||
|
}
|
||||||
|
relays = append(relays, v.Relays...)
|
||||||
|
case "note":
|
||||||
|
filter.IDs = append(filter.IDs, value.(string))
|
||||||
|
case "naddr":
|
||||||
|
v := value.(nostr.EntityPointer)
|
||||||
|
filter.Kinds = []int{v.Kind}
|
||||||
|
filter.Tags = nostr.TagMap{"d": []string{v.Identifier}}
|
||||||
|
filter.Authors = append(filter.Authors, v.PublicKey)
|
||||||
|
authorHint = v.PublicKey
|
||||||
|
relays = append(relays, v.Relays...)
|
||||||
|
case "nprofile":
|
||||||
|
v := value.(nostr.ProfilePointer)
|
||||||
|
filter.Authors = append(filter.Authors, v.PublicKey)
|
||||||
|
authorHint = v.PublicKey
|
||||||
|
relays = append(relays, v.Relays...)
|
||||||
|
case "npub":
|
||||||
|
v := value.(string)
|
||||||
|
filter.Authors = append(filter.Authors, v)
|
||||||
|
authorHint = v
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unexpected prefix %s", prefix)
|
||||||
}
|
}
|
||||||
relays = append(relays, v.Relays...)
|
|
||||||
case "naddr":
|
|
||||||
v := value.(nostr.EntityPointer)
|
|
||||||
filter.Tags = nostr.TagMap{"d": []string{v.Identifier}}
|
|
||||||
filter.Kinds = append(filter.Kinds, v.Kind)
|
|
||||||
filter.Authors = append(filter.Authors, v.PublicKey)
|
|
||||||
authorHint = v.PublicKey
|
|
||||||
relays = append(relays, v.Relays...)
|
|
||||||
case "nprofile":
|
|
||||||
v := value.(nostr.ProfilePointer)
|
|
||||||
filter.Authors = append(filter.Authors, v.PublicKey)
|
|
||||||
filter.Kinds = append(filter.Kinds, 0)
|
|
||||||
authorHint = v.PublicKey
|
|
||||||
relays = append(relays, v.Relays...)
|
|
||||||
case "npub":
|
|
||||||
v := value.(string)
|
|
||||||
filter.Authors = append(filter.Authors, v)
|
|
||||||
filter.Kinds = append(filter.Kinds, 0)
|
|
||||||
authorHint = v
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if authorHint != "" {
|
if authorHint != "" {
|
||||||
relayList := sdk.FetchRelaysForPubkey(ctx, pool, authorHint,
|
relays := sys.FetchOutboxRelays(ctx, authorHint, 3)
|
||||||
"wss://purplepag.es", "wss://relay.damus.io", "wss://relay.noswhere.com",
|
for _, url := range relays {
|
||||||
"wss://nos.lol", "wss://public.relaying.io", "wss://relay.nostr.band")
|
relays = append(relays, url)
|
||||||
for _, relayListItem := range relayList {
|
|
||||||
if relayListItem.Outbox {
|
|
||||||
relays = append(relays, relayListItem.URL)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(filter.Authors) > 0 && len(filter.Kinds) == 0 {
|
||||||
|
filter.Kinds = append(filter.Kinds, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := applyFlagsToFilter(c, &filter); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if len(relays) == 0 {
|
if len(relays) == 0 {
|
||||||
ctx = lineProcessingError(ctx, "no relay hints found")
|
ctx = lineProcessingError(ctx, "no relay hints found")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for ie := range pool.SubManyEose(ctx, relays, nostr.Filters{filter}) {
|
for ie := range sys.Pool.SubManyEose(ctx, relays, nostr.Filters{filter}) {
|
||||||
stdout(ie.Event)
|
stdout(ie.Event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
95
flags.go
Normal file
95
flags.go
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fiatjaf/cli/v3"
|
||||||
|
"github.com/markusmobius/go-dateparser"
|
||||||
|
"github.com/nbd-wtf/go-nostr"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NaturalTimeFlag = cli.FlagBase[nostr.Timestamp, struct{}, naturalTimeValue]
|
||||||
|
|
||||||
|
// wrap to satisfy golang's flag interface.
|
||||||
|
type naturalTimeValue struct {
|
||||||
|
timestamp *nostr.Timestamp
|
||||||
|
hasBeenSet bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ cli.ValueCreator[nostr.Timestamp, struct{}] = naturalTimeValue{}
|
||||||
|
|
||||||
|
// Below functions are to satisfy the ValueCreator interface
|
||||||
|
|
||||||
|
func (t naturalTimeValue) Create(val nostr.Timestamp, p *nostr.Timestamp, c struct{}) cli.Value {
|
||||||
|
*p = val
|
||||||
|
return &naturalTimeValue{
|
||||||
|
timestamp: p,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t naturalTimeValue) ToString(b nostr.Timestamp) string {
|
||||||
|
ts := b.Time()
|
||||||
|
|
||||||
|
if ts.IsZero() {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%v", ts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Timestamp constructor(for internal testing only)
|
||||||
|
func newTimestamp(timestamp nostr.Timestamp) *naturalTimeValue {
|
||||||
|
return &naturalTimeValue{timestamp: ×tamp}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Below functions are to satisfy the flag.Value interface
|
||||||
|
|
||||||
|
// Parses the string value to timestamp
|
||||||
|
func (t *naturalTimeValue) Set(value string) error {
|
||||||
|
var ts time.Time
|
||||||
|
if n, err := strconv.ParseInt(value, 10, 64); err == nil {
|
||||||
|
// when the input is a raw number, treat it as an exact timestamp
|
||||||
|
ts = time.Unix(n, 0)
|
||||||
|
} else if errors.Is(err, strconv.ErrRange) {
|
||||||
|
// this means a huge number, so we should fail
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
// otherwise try to parse it as a human date string in natural language
|
||||||
|
date, err := dateparser.Parse(&dateparser.Configuration{
|
||||||
|
DefaultTimezone: time.Local,
|
||||||
|
CurrentTime: time.Now(),
|
||||||
|
}, value)
|
||||||
|
ts = date.Time
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.timestamp != nil {
|
||||||
|
*t.timestamp = nostr.Timestamp(ts.Unix())
|
||||||
|
}
|
||||||
|
|
||||||
|
t.hasBeenSet = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a readable representation of this value (for usage defaults)
|
||||||
|
func (t *naturalTimeValue) String() string {
|
||||||
|
return fmt.Sprintf("%#v", t.timestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value returns the timestamp value stored in the flag
|
||||||
|
func (t *naturalTimeValue) Value() *nostr.Timestamp {
|
||||||
|
return t.timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the flag structure
|
||||||
|
func (t *naturalTimeValue) Get() any {
|
||||||
|
return *t.timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNaturalDate(cmd *cli.Command, name string) nostr.Timestamp {
|
||||||
|
return cmd.Value(name).(nostr.Timestamp)
|
||||||
|
}
|
||||||
46
go.mod
46
go.mod
@@ -1,41 +1,63 @@
|
|||||||
module github.com/fiatjaf/nak
|
module github.com/fiatjaf/nak
|
||||||
|
|
||||||
go 1.21
|
go 1.22
|
||||||
|
|
||||||
toolchain go1.21.0
|
toolchain go1.22.4
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/bep/debounce v1.2.1
|
||||||
github.com/btcsuite/btcd/btcec/v2 v2.3.3
|
github.com/btcsuite/btcd/btcec/v2 v2.3.3
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e
|
||||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0
|
||||||
github.com/fatih/color v1.16.0
|
github.com/fatih/color v1.16.0
|
||||||
|
github.com/fiatjaf/cli/v3 v3.0.0-20240723181502-e7dd498b16ae
|
||||||
|
github.com/fiatjaf/eventstore v0.7.1
|
||||||
|
github.com/fiatjaf/khatru v0.7.5
|
||||||
github.com/mailru/easyjson v0.7.7
|
github.com/mailru/easyjson v0.7.7
|
||||||
github.com/nbd-wtf/go-nostr v0.34.2
|
github.com/markusmobius/go-dateparser v1.2.3
|
||||||
github.com/nbd-wtf/nostr-sdk v0.0.5
|
github.com/nbd-wtf/go-nostr v0.34.10
|
||||||
github.com/urfave/cli/v3 v3.0.0-alpha9
|
github.com/nbd-wtf/nostr-sdk v0.5.0
|
||||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
|
golang.org/x/exp v0.0.0-20240707233637-46b078467d37
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||||
github.com/btcsuite/btcd/btcutil v1.1.3 // indirect
|
github.com/btcsuite/btcd/btcutil v1.1.3 // indirect
|
||||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect
|
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
github.com/chzyer/logex v1.1.10 // indirect
|
github.com/chzyer/logex v1.1.10 // indirect
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect
|
||||||
github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect
|
github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect
|
||||||
github.com/fiatjaf/eventstore v0.2.16 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
|
github.com/elliotchance/pie/v2 v2.7.0 // indirect
|
||||||
|
github.com/fasthttp/websocket v1.5.7 // indirect
|
||||||
|
github.com/fiatjaf/generic-ristretto v0.0.1 // indirect
|
||||||
github.com/gobwas/httphead v0.1.0 // indirect
|
github.com/gobwas/httphead v0.1.0 // indirect
|
||||||
github.com/gobwas/pool v0.2.1 // indirect
|
github.com/gobwas/pool v0.2.1 // indirect
|
||||||
github.com/gobwas/ws v1.4.0 // indirect
|
github.com/gobwas/ws v1.4.0 // indirect
|
||||||
|
github.com/golang/glog v1.1.2 // indirect
|
||||||
|
github.com/graph-gophers/dataloader/v7 v7.1.0 // indirect
|
||||||
|
github.com/hablullah/go-hijri v1.0.2 // indirect
|
||||||
|
github.com/hablullah/go-juliandays v1.0.0 // indirect
|
||||||
|
github.com/jalaali/go-jalaali v0.0.0-20210801064154-80525e88d958 // indirect
|
||||||
github.com/josharian/intern v1.0.0 // indirect
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
|
github.com/klauspost/compress v1.17.8 // indirect
|
||||||
|
github.com/magefile/mage v1.14.0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/puzpuzpuz/xsync/v3 v3.1.0 // indirect
|
github.com/puzpuzpuz/xsync/v3 v3.1.0 // indirect
|
||||||
|
github.com/rs/cors v1.7.0 // indirect
|
||||||
|
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect
|
||||||
|
github.com/tetratelabs/wazero v1.2.1 // indirect
|
||||||
github.com/tidwall/gjson v1.17.1 // indirect
|
github.com/tidwall/gjson v1.17.1 // indirect
|
||||||
github.com/tidwall/match v1.1.1 // indirect
|
github.com/tidwall/match v1.1.1 // indirect
|
||||||
github.com/tidwall/pretty v1.2.1 // indirect
|
github.com/tidwall/pretty v1.2.1 // indirect
|
||||||
golang.org/x/crypto v0.7.0 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
golang.org/x/sys v0.20.0 // indirect
|
github.com/valyala/fasthttp v1.51.0 // indirect
|
||||||
golang.org/x/text v0.8.0 // indirect
|
github.com/wasilibs/go-re2 v1.3.0 // indirect
|
||||||
|
golang.org/x/crypto v0.21.0 // indirect
|
||||||
|
golang.org/x/net v0.22.0 // indirect
|
||||||
|
golang.org/x/sys v0.22.0 // indirect
|
||||||
|
golang.org/x/text v0.16.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
replace github.com/urfave/cli/v3 => github.com/fiatjaf/cli/v3 v3.0.0-20240626022047-0fc2565ea728
|
|
||||||
|
|||||||
90
go.sum
90
go.sum
@@ -1,4 +1,8 @@
|
|||||||
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
|
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
|
||||||
|
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||||
|
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||||
|
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
|
||||||
|
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
|
||||||
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
|
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
|
||||||
github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M=
|
github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M=
|
||||||
github.com/btcsuite/btcd v0.23.0/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY=
|
github.com/btcsuite/btcd v0.23.0/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY=
|
||||||
@@ -23,6 +27,8 @@ github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku
|
|||||||
github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
|
github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
|
||||||
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
|
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
|
||||||
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
|
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
|
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
|
||||||
@@ -40,12 +46,24 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeC
|
|||||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg=
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg=
|
||||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
|
||||||
github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218=
|
github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218=
|
||||||
|
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
|
||||||
|
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||||
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
|
github.com/elliotchance/pie/v2 v2.7.0 h1:FqoIKg4uj0G/CrLGuMS9ejnFKa92lxE1dEgBD3pShXg=
|
||||||
|
github.com/elliotchance/pie/v2 v2.7.0/go.mod h1:18t0dgGFH006g4eVdDtWfgFZPQEgl10IoEO8YWEq3Og=
|
||||||
|
github.com/fasthttp/websocket v1.5.7 h1:0a6o2OfeATvtGgoMKleURhLT6JqWPg7fYfWnH4KHau4=
|
||||||
|
github.com/fasthttp/websocket v1.5.7/go.mod h1:bC4fxSono9czeXHQUVKxsC0sNjbm7lPJR04GDFqClfU=
|
||||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
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/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-20240723181502-e7dd498b16ae h1:0B/1dU3YECIbPoBIRTQ4c0scZCNz9TVHtQpiODGrTTo=
|
||||||
github.com/fiatjaf/cli/v3 v3.0.0-20240626022047-0fc2565ea728/go.mod h1:Z1ItyMma7t6I7zHG9OpbExhHQOSkFf/96n+mAZ9MtVI=
|
github.com/fiatjaf/cli/v3 v3.0.0-20240723181502-e7dd498b16ae/go.mod h1:aAWPO4bixZZxPtOnH6K3q4GbQ0jftUNDW9Oa861IRew=
|
||||||
github.com/fiatjaf/eventstore v0.2.16 h1:NR64mnyUT5nJR8Sj2AwJTd1Hqs5kKJcCFO21ggUkvWg=
|
github.com/fiatjaf/eventstore v0.7.1 h1:5f2yvEtYvsvMBNttysmXhSSum5M1qwvPzjEQ/BFue7Q=
|
||||||
github.com/fiatjaf/eventstore v0.2.16/go.mod h1:rUc1KhVufVmC+HUOiuPweGAcvG6lEOQCkRCn2Xn5VRA=
|
github.com/fiatjaf/eventstore v0.7.1/go.mod h1:ek/yWbanKVG767fK51Q3+6Mvi5oEHYSsdPym40nZexw=
|
||||||
|
github.com/fiatjaf/generic-ristretto v0.0.1 h1:LUJSU87X/QWFsBXTwnH3moFe4N8AjUxT+Rfa0+bo6YM=
|
||||||
|
github.com/fiatjaf/generic-ristretto v0.0.1/go.mod h1:cvV6ANHDA/GrfzVrig7N7i6l8CWnkVZvtQ2/wk9DPVE=
|
||||||
|
github.com/fiatjaf/khatru v0.7.5 h1:UFo+cdbqHDn1W4Q4h03y3vzh1BiU+6fLYK48oWU2K34=
|
||||||
|
github.com/fiatjaf/khatru v0.7.5/go.mod h1:WVqij7X9Vr9UAMIwafQbKVFKxc42Np37vyficwUr/nQ=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
||||||
@@ -54,6 +72,8 @@ github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
|||||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||||
github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
|
github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
|
||||||
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
|
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
|
||||||
|
github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo=
|
||||||
|
github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||||
@@ -65,24 +85,38 @@ github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW
|
|||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/graph-gophers/dataloader/v7 v7.1.0 h1:Wn8HGF/q7MNXcvfaBnLEPEFJttVHR8zuEqP1obys/oc=
|
||||||
|
github.com/graph-gophers/dataloader/v7 v7.1.0/go.mod h1:1bKE0Dm6OUcTB/OAuYVOZctgIz7Q3d0XrYtlIzTgg6Q=
|
||||||
|
github.com/hablullah/go-hijri v1.0.2 h1:drT/MZpSZJQXo7jftf5fthArShcaMtsal0Zf/dnmp6k=
|
||||||
|
github.com/hablullah/go-hijri v1.0.2/go.mod h1:OS5qyYLDjORXzK4O1adFw9Q5WfhOcMdAKglDkcTxgWQ=
|
||||||
|
github.com/hablullah/go-juliandays v1.0.0 h1:A8YM7wIj16SzlKT0SRJc9CD29iiaUzpBLzh5hr0/5p0=
|
||||||
|
github.com/hablullah/go-juliandays v1.0.0/go.mod h1:0JOYq4oFOuDja+oospuc61YoX+uNEn7Z6uHYTbBzdGc=
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
|
github.com/jalaali/go-jalaali v0.0.0-20210801064154-80525e88d958 h1:qxLoi6CAcXVzjfvu+KXIXJOAsQB62LXjsfbOaErsVzE=
|
||||||
|
github.com/jalaali/go-jalaali v0.0.0-20210801064154-80525e88d958/go.mod h1:Wqfu7mjUHj9WDzSSPI5KfBclTTEnLveRUFr/ujWnTgE=
|
||||||
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||||
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
|
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
|
||||||
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
|
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
|
||||||
|
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
|
||||||
|
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||||
|
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
|
||||||
|
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||||
|
github.com/markusmobius/go-dateparser v1.2.3 h1:TvrsIvr5uk+3v6poDjaicnAFJ5IgtFHgLiuMY2Eb7Nw=
|
||||||
|
github.com/markusmobius/go-dateparser v1.2.3/go.mod h1:cMwQRrBUQlK1UI5TIFHEcvpsMbkWrQLXuaPNMFzuYLk=
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
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 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/nbd-wtf/go-nostr v0.34.2 h1:9b4qZ29DhQf9xEWN8/7zfDD868r1jFbpjrR3c+BHc+E=
|
github.com/nbd-wtf/go-nostr v0.34.10 h1:scJH45sFk5LOzHJNLw0EFTknCCKfKlo3tK+vdpTHz3Q=
|
||||||
github.com/nbd-wtf/go-nostr v0.34.2/go.mod h1:NZQkxl96ggbO8rvDpVjcsojJqKTPwqhP4i82O7K5DJs=
|
github.com/nbd-wtf/go-nostr v0.34.10/go.mod h1:NZQkxl96ggbO8rvDpVjcsojJqKTPwqhP4i82O7K5DJs=
|
||||||
github.com/nbd-wtf/nostr-sdk v0.0.5 h1:rec+FcDizDVO0W25PX0lgYMXvP7zNNOgI3Fu9UCm4BY=
|
github.com/nbd-wtf/nostr-sdk v0.5.0 h1:zrMxcvMSxkw29RyfXEdF3XW5rUWLuT5Q9oBAhd5dyew=
|
||||||
github.com/nbd-wtf/nostr-sdk v0.0.5/go.mod h1:iJJsikesCGLNFZ9dLqhLPDzdt924EagUmdQxT3w2Lmk=
|
github.com/nbd-wtf/nostr-sdk v0.5.0/go.mod h1:MJ7gYv3XiZKU6MHSM0N7oHqQAQhbvpgGQk4Q+XUdIUs=
|
||||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
@@ -92,15 +126,23 @@ github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5
|
|||||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/puzpuzpuz/xsync/v3 v3.1.0 h1:EewKT7/LNac5SLiEblJeUu8z5eERHrmRLnMQL2d7qX4=
|
github.com/puzpuzpuz/xsync/v3 v3.1.0 h1:EewKT7/LNac5SLiEblJeUu8z5eERHrmRLnMQL2d7qX4=
|
||||||
github.com/puzpuzpuz/xsync/v3 v3.1.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
github.com/puzpuzpuz/xsync/v3 v3.1.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||||
|
github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
|
||||||
|
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
||||||
|
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk=
|
||||||
|
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
|
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
|
||||||
|
github.com/tetratelabs/wazero v1.2.1 h1:J4X2hrGzJvt+wqltuvcSjHQ7ujQxA9gb6PeMs4qlUWs=
|
||||||
|
github.com/tetratelabs/wazero v1.2.1/go.mod h1:wYx2gNRg8/WihJfSDxA1TIL8H+GkfLYm+bIfbblu9VQ=
|
||||||
github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=
|
github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=
|
||||||
github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||||
@@ -108,20 +150,28 @@ github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JT
|
|||||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
|
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
|
||||||
|
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
|
||||||
|
github.com/wasilibs/go-re2 v1.3.0 h1:LFhBNzoStM3wMie6rN2slD1cuYH2CGiHpvNL3UtcsMw=
|
||||||
|
github.com/wasilibs/go-re2 v1.3.0/go.mod h1:AafrCXVvGRJJOImMajgJ2M7rVmWyisVK7sFshbxnVrg=
|
||||||
|
github.com/wasilibs/nottinygc v0.4.0 h1:h1TJMihMC4neN6Zq+WKpLxgd9xCFMw7O9ETLwY2exJQ=
|
||||||
|
github.com/wasilibs/nottinygc v0.4.0/go.mod h1:oDcIotskuYNMpqMF23l7Z8uzD4TC0WXHK8jetlB3HIo=
|
||||||
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
|
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
|
golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w=
|
||||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||||
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
|
||||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@@ -134,13 +184,13 @@ golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
|
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/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=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
|||||||
22
helpers.go
22
helpers.go
@@ -13,11 +13,11 @@ import (
|
|||||||
|
|
||||||
"github.com/chzyer/readline"
|
"github.com/chzyer/readline"
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
|
"github.com/fiatjaf/cli/v3"
|
||||||
"github.com/nbd-wtf/go-nostr"
|
"github.com/nbd-wtf/go-nostr"
|
||||||
"github.com/nbd-wtf/go-nostr/nip19"
|
"github.com/nbd-wtf/go-nostr/nip19"
|
||||||
"github.com/nbd-wtf/go-nostr/nip46"
|
"github.com/nbd-wtf/go-nostr/nip46"
|
||||||
"github.com/nbd-wtf/go-nostr/nip49"
|
"github.com/nbd-wtf/go-nostr/nip49"
|
||||||
"github.com/urfave/cli/v3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -75,7 +75,7 @@ func writeStdinLinesOrNothing(ch chan string) (hasStdinLines bool) {
|
|||||||
// piped
|
// piped
|
||||||
go func() {
|
go func() {
|
||||||
scanner := bufio.NewScanner(os.Stdin)
|
scanner := bufio.NewScanner(os.Stdin)
|
||||||
scanner.Buffer(make([]byte, 16*1024), 256*1024)
|
scanner.Buffer(make([]byte, 16*1024*1024), 256*1024*1024)
|
||||||
hasEmittedAtLeastOne := false
|
hasEmittedAtLeastOne := false
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
ch <- strings.TrimSpace(scanner.Text())
|
ch <- strings.TrimSpace(scanner.Text())
|
||||||
@@ -195,7 +195,17 @@ func gatherSecretKeyOrBunkerFromArguments(ctx context.Context, c *cli.Command) (
|
|||||||
})
|
})
|
||||||
return "", bunker, err
|
return "", bunker, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// take private from flags, environment variable or default to 1
|
||||||
sec := c.String("sec")
|
sec := c.String("sec")
|
||||||
|
if sec == "" {
|
||||||
|
if key, ok := os.LookupEnv("NOSTR_SECRET_KEY"); ok {
|
||||||
|
sec = key
|
||||||
|
} else {
|
||||||
|
sec = "0000000000000000000000000000000000000000000000000000000000000001"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if c.Bool("prompt-sec") {
|
if c.Bool("prompt-sec") {
|
||||||
if isPiped() {
|
if isPiped() {
|
||||||
return "", nil, fmt.Errorf("can't prompt for a secret key when processing data from a pipe, try again without --prompt-sec")
|
return "", nil, fmt.Errorf("can't prompt for a secret key when processing data from a pipe, try again without --prompt-sec")
|
||||||
@@ -205,6 +215,7 @@ func gatherSecretKeyOrBunkerFromArguments(ctx context.Context, c *cli.Command) (
|
|||||||
return "", nil, fmt.Errorf("failed to get secret key: %w", err)
|
return "", nil, fmt.Errorf("failed to get secret key: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(sec, "ncryptsec1") {
|
if strings.HasPrefix(sec, "ncryptsec1") {
|
||||||
sec, err = promptDecrypt(sec)
|
sec, err = promptDecrypt(sec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -221,6 +232,7 @@ func gatherSecretKeyOrBunkerFromArguments(ctx context.Context, c *cli.Command) (
|
|||||||
if ok := nostr.IsValid32ByteHex(sec); !ok {
|
if ok := nostr.IsValid32ByteHex(sec); !ok {
|
||||||
return "", nil, fmt.Errorf("invalid secret key")
|
return "", nil, fmt.Errorf("invalid secret key")
|
||||||
}
|
}
|
||||||
|
|
||||||
return sec, nil, nil
|
return sec, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,22 +264,18 @@ func askPassword(msg string, shouldAskAgain func(answer string) bool) (string, e
|
|||||||
EnableMask: true,
|
EnableMask: true,
|
||||||
MaskRune: '*',
|
MaskRune: '*',
|
||||||
}
|
}
|
||||||
return _ask(config, msg, "", shouldAskAgain)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _ask(config *readline.Config, msg string, defaultValue string, shouldAskAgain func(answer string) bool) (string, error) {
|
|
||||||
rl, err := readline.NewEx(config)
|
rl, err := readline.NewEx(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
rl.WriteStdin([]byte(defaultValue))
|
|
||||||
for {
|
for {
|
||||||
answer, err := rl.Readline()
|
answer, err := rl.Readline()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
answer = strings.TrimSpace(strings.ToLower(answer))
|
answer = strings.TrimSpace(answer)
|
||||||
if shouldAskAgain != nil && shouldAskAgain(answer) {
|
if shouldAskAgain != nil && shouldAskAgain(answer) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
52
key.go
52
key.go
@@ -11,16 +11,17 @@ import (
|
|||||||
"github.com/btcsuite/btcd/btcec/v2"
|
"github.com/btcsuite/btcd/btcec/v2"
|
||||||
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
|
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
|
||||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
|
"github.com/fiatjaf/cli/v3"
|
||||||
"github.com/nbd-wtf/go-nostr"
|
"github.com/nbd-wtf/go-nostr"
|
||||||
"github.com/nbd-wtf/go-nostr/nip19"
|
"github.com/nbd-wtf/go-nostr/nip19"
|
||||||
"github.com/nbd-wtf/go-nostr/nip49"
|
"github.com/nbd-wtf/go-nostr/nip49"
|
||||||
"github.com/urfave/cli/v3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var key = &cli.Command{
|
var key = &cli.Command{
|
||||||
Name: "key",
|
Name: "key",
|
||||||
Usage: "operations on secret keys: generate, derive, encrypt, decrypt.",
|
Usage: "operations on secret keys: generate, derive, encrypt, decrypt.",
|
||||||
Description: ``,
|
Description: ``,
|
||||||
|
DisableSliceFlagSeparator: true,
|
||||||
Commands: []*cli.Command{
|
Commands: []*cli.Command{
|
||||||
generate,
|
generate,
|
||||||
public,
|
public,
|
||||||
@@ -31,9 +32,10 @@ var key = &cli.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
var generate = &cli.Command{
|
var generate = &cli.Command{
|
||||||
Name: "generate",
|
Name: "generate",
|
||||||
Usage: "generates a secret key",
|
Usage: "generates a secret key",
|
||||||
Description: ``,
|
Description: ``,
|
||||||
|
DisableSliceFlagSeparator: true,
|
||||||
Action: func(ctx context.Context, c *cli.Command) error {
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
sec := nostr.GeneratePrivateKey()
|
sec := nostr.GeneratePrivateKey()
|
||||||
stdout(sec)
|
stdout(sec)
|
||||||
@@ -42,10 +44,11 @@ var generate = &cli.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
var public = &cli.Command{
|
var public = &cli.Command{
|
||||||
Name: "public",
|
Name: "public",
|
||||||
Usage: "computes a public key from a secret key",
|
Usage: "computes a public key from a secret key",
|
||||||
Description: ``,
|
Description: ``,
|
||||||
ArgsUsage: "[secret]",
|
ArgsUsage: "[secret]",
|
||||||
|
DisableSliceFlagSeparator: true,
|
||||||
Action: func(ctx context.Context, c *cli.Command) error {
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
for sec := range getSecretKeysFromStdinLinesOrSlice(ctx, c, c.Args().Slice()) {
|
for sec := range getSecretKeysFromStdinLinesOrSlice(ctx, c, c.Args().Slice()) {
|
||||||
pubkey, err := nostr.GetPublicKey(sec)
|
pubkey, err := nostr.GetPublicKey(sec)
|
||||||
@@ -60,10 +63,11 @@ var public = &cli.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
var encrypt = &cli.Command{
|
var encrypt = &cli.Command{
|
||||||
Name: "encrypt",
|
Name: "encrypt",
|
||||||
Usage: "encrypts a secret key and prints an ncryptsec code",
|
Usage: "encrypts a secret key and prints an ncryptsec code",
|
||||||
Description: `uses the NIP-49 standard.`,
|
Description: `uses the NIP-49 standard.`,
|
||||||
ArgsUsage: "<secret> <password>",
|
ArgsUsage: "<secret> <password>",
|
||||||
|
DisableSliceFlagSeparator: true,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
&cli.IntFlag{
|
&cli.IntFlag{
|
||||||
Name: "logn",
|
Name: "logn",
|
||||||
@@ -98,10 +102,11 @@ var encrypt = &cli.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
var decrypt = &cli.Command{
|
var decrypt = &cli.Command{
|
||||||
Name: "decrypt",
|
Name: "decrypt",
|
||||||
Usage: "takes an ncrypsec and a password and decrypts it into an nsec",
|
Usage: "takes an ncrypsec and a password and decrypts it into an nsec",
|
||||||
Description: `uses the NIP-49 standard.`,
|
Description: `uses the NIP-49 standard.`,
|
||||||
ArgsUsage: "<ncryptsec-code> <password>",
|
ArgsUsage: "<ncryptsec-code> <password>",
|
||||||
|
DisableSliceFlagSeparator: true,
|
||||||
Action: func(ctx context.Context, c *cli.Command) error {
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
var ncryptsec string
|
var ncryptsec string
|
||||||
var password string
|
var password string
|
||||||
@@ -116,8 +121,7 @@ var decrypt = &cli.Command{
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to decrypt: %s", err)
|
return fmt.Errorf("failed to decrypt: %s", err)
|
||||||
}
|
}
|
||||||
nsec, _ := nip19.EncodePrivateKey(sec)
|
stdout(sec)
|
||||||
stdout(nsec)
|
|
||||||
return nil
|
return nil
|
||||||
case 1:
|
case 1:
|
||||||
if arg := c.Args().Get(0); strings.HasPrefix(arg, "ncryptsec1") {
|
if arg := c.Args().Get(0); strings.HasPrefix(arg, "ncryptsec1") {
|
||||||
@@ -136,8 +140,7 @@ var decrypt = &cli.Command{
|
|||||||
ctx = lineProcessingError(ctx, "failed to decrypt: %s", err)
|
ctx = lineProcessingError(ctx, "failed to decrypt: %s", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
nsec, _ := nip19.EncodePrivateKey(sec)
|
stdout(sec)
|
||||||
stdout(nsec)
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -153,7 +156,8 @@ var combine = &cli.Command{
|
|||||||
Description: `The public keys must have 33 bytes (66 characters hex), with the 02 or 03 prefix. It is common in Nostr to drop that first byte, so you'll have to derive the public keys again from the private keys in order to get it back.
|
Description: `The public keys must have 33 bytes (66 characters hex), with the 02 or 03 prefix. It is common in Nostr to drop that first byte, so you'll have to derive the public keys again from the private keys in order to get it back.
|
||||||
|
|
||||||
However, if the intent is to check if two existing Nostr pubkeys match a given combined pubkey, then it might be sufficient to calculate the combined key for all the possible combinations of pubkeys in the input.`,
|
However, if the intent is to check if two existing Nostr pubkeys match a given combined pubkey, then it might be sufficient to calculate the combined key for all the possible combinations of pubkeys in the input.`,
|
||||||
ArgsUsage: "[pubkey...]",
|
ArgsUsage: "[pubkey...]",
|
||||||
|
DisableSliceFlagSeparator: true,
|
||||||
Action: func(ctx context.Context, c *cli.Command) error {
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
type Combination struct {
|
type Combination struct {
|
||||||
Variants []string `json:"input_variants"`
|
Variants []string `json:"input_variants"`
|
||||||
|
|||||||
22
main.go
22
main.go
@@ -4,14 +4,16 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/fiatjaf/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var app = &cli.Command{
|
var app = &cli.Command{
|
||||||
Name: "nak",
|
Name: "nak",
|
||||||
Suggest: true,
|
Suggest: true,
|
||||||
UseShortOptionHandling: true,
|
UseShortOptionHandling: true,
|
||||||
Usage: "the nostr army knife command-line tool",
|
AllowFlagsAfterArguments: true,
|
||||||
|
Usage: "the nostr army knife command-line tool",
|
||||||
|
DisableSliceFlagSeparator: true,
|
||||||
Commands: []*cli.Command{
|
Commands: []*cli.Command{
|
||||||
req,
|
req,
|
||||||
count,
|
count,
|
||||||
@@ -23,18 +25,20 @@ var app = &cli.Command{
|
|||||||
verify,
|
verify,
|
||||||
relay,
|
relay,
|
||||||
bunker,
|
bunker,
|
||||||
|
serve,
|
||||||
},
|
},
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "quiet",
|
Name: "quiet",
|
||||||
Usage: "do not print logs and info messages to stderr, use -qq to also not print anything to stdout",
|
Usage: "do not print logs and info messages to stderr, use -qq to also not print anything to stdout",
|
||||||
Aliases: []string{"q"},
|
Aliases: []string{"q"},
|
||||||
|
Persistent: true,
|
||||||
Action: func(ctx context.Context, c *cli.Command, b bool) error {
|
Action: func(ctx context.Context, c *cli.Command, b bool) error {
|
||||||
q := c.Count("quiet")
|
q := c.Count("quiet")
|
||||||
if q >= 1 {
|
if q >= 1 {
|
||||||
log = func(msg string, args ...any) {}
|
log = func(msg string, args ...any) {}
|
||||||
if q >= 2 {
|
if q >= 2 {
|
||||||
stdout = func(a ...any) (int, error) { return 0, nil }
|
stdout = func(_ ...any) (int, error) { return 0, nil }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
27
musig2.go
27
musig2.go
@@ -15,8 +15,33 @@ import (
|
|||||||
"github.com/nbd-wtf/go-nostr"
|
"github.com/nbd-wtf/go-nostr"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func getMusigAggregatedKey(_ context.Context, keys []string) (string, error) {
|
||||||
|
knownSigners := make([]*btcec.PublicKey, len(keys))
|
||||||
|
for i, spk := range keys {
|
||||||
|
bpk, err := hex.DecodeString(spk)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("'%s' is invalid hex: %w", spk, err)
|
||||||
|
}
|
||||||
|
if len(bpk) == 32 {
|
||||||
|
return "", fmt.Errorf("'%s' is missing the leading parity byte", spk)
|
||||||
|
}
|
||||||
|
pk, err := btcec.ParsePubKey(bpk)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("'%s' is not a valid pubkey: %w", spk, err)
|
||||||
|
}
|
||||||
|
knownSigners[i] = pk
|
||||||
|
}
|
||||||
|
|
||||||
|
aggpk, _, _, err := musig2.AggregateKeys(knownSigners, true)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("aggregation failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return hex.EncodeToString(aggpk.FinalKey.SerializeCompressed()[1:]), nil
|
||||||
|
}
|
||||||
|
|
||||||
func performMusig(
|
func performMusig(
|
||||||
ctx context.Context,
|
_ context.Context,
|
||||||
sec string,
|
sec string,
|
||||||
evt *nostr.Event,
|
evt *nostr.Event,
|
||||||
numSigners int,
|
numSigners int,
|
||||||
|
|||||||
73
paginate.go
Normal file
73
paginate.go
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"math"
|
||||||
|
"slices"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/nbd-wtf/go-nostr"
|
||||||
|
)
|
||||||
|
|
||||||
|
func paginateWithPoolAndParams(pool *nostr.SimplePool, interval time.Duration, globalLimit uint64) func(ctx context.Context, urls []string, filters nostr.Filters) chan nostr.IncomingEvent {
|
||||||
|
return func(ctx context.Context, urls []string, filters nostr.Filters) chan nostr.IncomingEvent {
|
||||||
|
// filters will always be just one
|
||||||
|
filter := filters[0]
|
||||||
|
|
||||||
|
nextUntil := nostr.Now()
|
||||||
|
if filter.Until != nil {
|
||||||
|
nextUntil = *filter.Until
|
||||||
|
}
|
||||||
|
|
||||||
|
if globalLimit == 0 {
|
||||||
|
globalLimit = uint64(filter.Limit)
|
||||||
|
if globalLimit == 0 && !filter.LimitZero {
|
||||||
|
globalLimit = math.MaxUint64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var globalCount uint64 = 0
|
||||||
|
globalCh := make(chan nostr.IncomingEvent)
|
||||||
|
|
||||||
|
repeatedCache := make([]string, 0, 300)
|
||||||
|
nextRepeatedCache := make([]string, 0, 300)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer close(globalCh)
|
||||||
|
|
||||||
|
for {
|
||||||
|
filter.Until = &nextUntil
|
||||||
|
time.Sleep(interval)
|
||||||
|
|
||||||
|
keepGoing := false
|
||||||
|
for evt := range pool.SubManyEose(ctx, urls, nostr.Filters{filter}) {
|
||||||
|
if slices.Contains(repeatedCache, evt.ID) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
keepGoing = true // if we get one that isn't repeated, then keep trying to get more
|
||||||
|
nextRepeatedCache = append(nextRepeatedCache, evt.ID)
|
||||||
|
|
||||||
|
globalCh <- evt
|
||||||
|
|
||||||
|
globalCount++
|
||||||
|
if globalCount >= globalLimit {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if evt.CreatedAt < *filter.Until {
|
||||||
|
nextUntil = evt.CreatedAt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !keepGoing {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
repeatedCache = nextRepeatedCache
|
||||||
|
nextRepeatedCache = nextRepeatedCache[:0]
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return globalCh
|
||||||
|
}
|
||||||
|
}
|
||||||
11
relay.go
11
relay.go
@@ -10,12 +10,11 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
|
|
||||||
|
"github.com/fiatjaf/cli/v3"
|
||||||
"github.com/nbd-wtf/go-nostr"
|
"github.com/nbd-wtf/go-nostr"
|
||||||
"github.com/nbd-wtf/go-nostr/nip11"
|
"github.com/nbd-wtf/go-nostr/nip11"
|
||||||
"github.com/nbd-wtf/go-nostr/nip86"
|
"github.com/nbd-wtf/go-nostr/nip86"
|
||||||
"github.com/urfave/cli/v3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var relay = &cli.Command{
|
var relay = &cli.Command{
|
||||||
@@ -23,7 +22,8 @@ var relay = &cli.Command{
|
|||||||
Usage: "gets the relay information document for the given relay, as JSON",
|
Usage: "gets the relay information document for the given relay, as JSON",
|
||||||
Description: `example:
|
Description: `example:
|
||||||
nak relay nostr.wine`,
|
nak relay nostr.wine`,
|
||||||
ArgsUsage: "<relay-url>",
|
ArgsUsage: "<relay-url>",
|
||||||
|
DisableSliceFlagSeparator: true,
|
||||||
Action: func(ctx context.Context, c *cli.Command) error {
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
for url := range getStdinLinesOrArguments(c.Args()) {
|
for url := range getStdinLinesOrArguments(c.Args()) {
|
||||||
if url == "" {
|
if url == "" {
|
||||||
@@ -79,7 +79,6 @@ var relay = &cli.Command{
|
|||||||
Name: "sec",
|
Name: "sec",
|
||||||
Usage: "secret key to sign the event, as nsec, ncryptsec or hex",
|
Usage: "secret key to sign the event, as nsec, ncryptsec or hex",
|
||||||
DefaultText: "the key '1'",
|
DefaultText: "the key '1'",
|
||||||
Value: "0000000000000000000000000000000000000000000000000000000000000001",
|
|
||||||
},
|
},
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "prompt-sec",
|
Name: "prompt-sec",
|
||||||
@@ -131,13 +130,13 @@ var relay = &cli.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Authorization
|
// Authorization
|
||||||
hostname := strings.Split(strings.Split(httpUrl, "://")[1], "/")[0]
|
|
||||||
payloadHash := sha256.Sum256(reqj)
|
payloadHash := sha256.Sum256(reqj)
|
||||||
authEvent := nostr.Event{
|
authEvent := nostr.Event{
|
||||||
Kind: 27235,
|
Kind: 27235,
|
||||||
CreatedAt: nostr.Now(),
|
CreatedAt: nostr.Now(),
|
||||||
Tags: nostr.Tags{
|
Tags: nostr.Tags{
|
||||||
{"host", hostname},
|
{"u", httpUrl},
|
||||||
|
{"method", "POST"},
|
||||||
{"payload", hex.EncodeToString(payloadHash[:])},
|
{"payload", hex.EncodeToString(payloadHash[:])},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
311
req.go
311
req.go
@@ -5,15 +5,17 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/fiatjaf/cli/v3"
|
||||||
"github.com/mailru/easyjson"
|
"github.com/mailru/easyjson"
|
||||||
"github.com/nbd-wtf/go-nostr"
|
"github.com/nbd-wtf/go-nostr"
|
||||||
"github.com/urfave/cli/v3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const CATEGORY_FILTER_ATTRIBUTES = "FILTER ATTRIBUTES"
|
const (
|
||||||
|
CATEGORY_FILTER_ATTRIBUTES = "FILTER ATTRIBUTES"
|
||||||
|
// CATEGORY_SIGNER = "SIGNER OPTIONS" -- defined at event.go as the same (yes, I know)
|
||||||
|
)
|
||||||
|
|
||||||
var req = &cli.Command{
|
var req = &cli.Command{
|
||||||
Name: "req",
|
Name: "req",
|
||||||
@@ -28,74 +30,27 @@ it can also take a filter from stdin, optionally modify it with flags and send i
|
|||||||
|
|
||||||
example:
|
example:
|
||||||
echo '{"kinds": [1], "#t": ["test"]}' | nak req -l 5 -k 4549 --tag t=spam wss://nostr-pub.wellorder.net`,
|
echo '{"kinds": [1], "#t": ["test"]}' | nak req -l 5 -k 4549 --tag t=spam wss://nostr-pub.wellorder.net`,
|
||||||
Flags: []cli.Flag{
|
DisableSliceFlagSeparator: true,
|
||||||
&cli.StringSliceFlag{
|
Flags: append(reqFilterFlags,
|
||||||
Name: "author",
|
|
||||||
Aliases: []string{"a"},
|
|
||||||
Usage: "only accept events from these authors (pubkey as hex)",
|
|
||||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
|
||||||
},
|
|
||||||
&cli.StringSliceFlag{
|
|
||||||
Name: "id",
|
|
||||||
Aliases: []string{"i"},
|
|
||||||
Usage: "only accept events with these ids (hex)",
|
|
||||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
|
||||||
},
|
|
||||||
&cli.IntSliceFlag{
|
|
||||||
Name: "kind",
|
|
||||||
Aliases: []string{"k"},
|
|
||||||
Usage: "only accept events with these kind numbers",
|
|
||||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
|
||||||
},
|
|
||||||
&cli.StringSliceFlag{
|
|
||||||
Name: "tag",
|
|
||||||
Aliases: []string{"t"},
|
|
||||||
Usage: "takes a tag like -t e=<id>, only accept events with these tags",
|
|
||||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
|
||||||
},
|
|
||||||
&cli.StringSliceFlag{
|
|
||||||
Name: "e",
|
|
||||||
Usage: "shortcut for --tag e=<value>",
|
|
||||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
|
||||||
},
|
|
||||||
&cli.StringSliceFlag{
|
|
||||||
Name: "p",
|
|
||||||
Usage: "shortcut for --tag p=<value>",
|
|
||||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
|
||||||
},
|
|
||||||
&cli.StringSliceFlag{
|
|
||||||
Name: "d",
|
|
||||||
Usage: "shortcut for --tag d=<value>",
|
|
||||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "since",
|
|
||||||
Aliases: []string{"s"},
|
|
||||||
Usage: "only accept events newer than this (unix timestamp)",
|
|
||||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "until",
|
|
||||||
Aliases: []string{"u"},
|
|
||||||
Usage: "only accept events older than this (unix timestamp)",
|
|
||||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
|
||||||
},
|
|
||||||
&cli.IntFlag{
|
|
||||||
Name: "limit",
|
|
||||||
Aliases: []string{"l"},
|
|
||||||
Usage: "only accept up to this number of events",
|
|
||||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "search",
|
|
||||||
Usage: "a NIP-50 search query, use it only with relays that explicitly support it",
|
|
||||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "stream",
|
Name: "stream",
|
||||||
Usage: "keep the subscription open, printing all events as they are returned",
|
Usage: "keep the subscription open, printing all events as they are returned",
|
||||||
DefaultText: "false, will close on EOSE",
|
DefaultText: "false, will close on EOSE",
|
||||||
},
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "paginate",
|
||||||
|
Usage: "make multiple REQs to the relay decreasing the value of 'until' until 'limit' or 'since' conditions are met",
|
||||||
|
DefaultText: "false",
|
||||||
|
},
|
||||||
|
&cli.DurationFlag{
|
||||||
|
Name: "paginate-interval",
|
||||||
|
Usage: "time between queries when using --paginate",
|
||||||
|
},
|
||||||
|
&cli.UintFlag{
|
||||||
|
Name: "paginate-global-limit",
|
||||||
|
Usage: "global limit at which --paginate should stop",
|
||||||
|
DefaultText: "uses the value given by --limit/-l or infinite",
|
||||||
|
},
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "bare",
|
Name: "bare",
|
||||||
Usage: "when printing the filter, print just the filter, not enveloped in a [\"REQ\", ...] array",
|
Usage: "when printing the filter, print just the filter, not enveloped in a [\"REQ\", ...] array",
|
||||||
@@ -105,30 +60,34 @@ example:
|
|||||||
Usage: "always perform NIP-42 \"AUTH\" when facing an \"auth-required: \" rejection and try again",
|
Usage: "always perform NIP-42 \"AUTH\" when facing an \"auth-required: \" rejection and try again",
|
||||||
},
|
},
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "force-pre-auth",
|
Name: "force-pre-auth",
|
||||||
Aliases: []string{"fpa"},
|
Aliases: []string{"fpa"},
|
||||||
Usage: "after connecting, for a NIP-42 \"AUTH\" message to be received, act on it and only then send the \"REQ\"",
|
Usage: "after connecting, for a NIP-42 \"AUTH\" message to be received, act on it and only then send the \"REQ\"",
|
||||||
|
Category: CATEGORY_SIGNER,
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "sec",
|
Name: "sec",
|
||||||
Usage: "secret key to sign the AUTH challenge, as hex or nsec",
|
Usage: "secret key to sign the AUTH challenge, as hex or nsec",
|
||||||
DefaultText: "the key '1'",
|
DefaultText: "the key '1'",
|
||||||
Value: "0000000000000000000000000000000000000000000000000000000000000001",
|
Category: CATEGORY_SIGNER,
|
||||||
},
|
},
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "prompt-sec",
|
Name: "prompt-sec",
|
||||||
Usage: "prompt the user to paste a hex or nsec with which to sign the AUTH challenge",
|
Usage: "prompt the user to paste a hex or nsec with which to sign the AUTH challenge",
|
||||||
|
Category: CATEGORY_SIGNER,
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "connect",
|
Name: "connect",
|
||||||
Usage: "sign AUTH using NIP-46, expects a bunker://... URL",
|
Usage: "sign AUTH using NIP-46, expects a bunker://... URL",
|
||||||
|
Category: CATEGORY_SIGNER,
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "connect-as",
|
Name: "connect-as",
|
||||||
Usage: "private key to when communicating with the bunker given on --connect",
|
Usage: "private key to when communicating with the bunker given on --connect",
|
||||||
DefaultText: "a random key",
|
DefaultText: "a random key",
|
||||||
|
Category: CATEGORY_SIGNER,
|
||||||
},
|
},
|
||||||
},
|
),
|
||||||
ArgsUsage: "[relay...]",
|
ArgsUsage: "[relay...]",
|
||||||
Action: func(ctx context.Context, c *cli.Command) error {
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
var pool *nostr.SimplePool
|
var pool *nostr.SimplePool
|
||||||
@@ -187,81 +146,18 @@ example:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if authors := c.StringSlice("author"); len(authors) > 0 {
|
if err := applyFlagsToFilter(c, &filter); err != nil {
|
||||||
filter.Authors = append(filter.Authors, authors...)
|
return err
|
||||||
}
|
|
||||||
if ids := c.StringSlice("id"); len(ids) > 0 {
|
|
||||||
filter.IDs = append(filter.IDs, ids...)
|
|
||||||
}
|
|
||||||
for _, kind64 := range c.IntSlice("kind") {
|
|
||||||
filter.Kinds = append(filter.Kinds, int(kind64))
|
|
||||||
}
|
|
||||||
if search := c.String("search"); search != "" {
|
|
||||||
filter.Search = search
|
|
||||||
}
|
|
||||||
tags := make([][]string, 0, 5)
|
|
||||||
for _, tagFlag := range c.StringSlice("tag") {
|
|
||||||
spl := strings.Split(tagFlag, "=")
|
|
||||||
if len(spl) == 2 && len(spl[0]) == 1 {
|
|
||||||
tags = append(tags, spl)
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("invalid --tag '%s'", tagFlag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, etag := range c.StringSlice("e") {
|
|
||||||
tags = append(tags, []string{"e", etag})
|
|
||||||
}
|
|
||||||
for _, ptag := range c.StringSlice("p") {
|
|
||||||
tags = append(tags, []string{"p", ptag})
|
|
||||||
}
|
|
||||||
for _, dtag := range c.StringSlice("d") {
|
|
||||||
tags = append(tags, []string{"d", dtag})
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(tags) > 0 && filter.Tags == nil {
|
|
||||||
filter.Tags = make(nostr.TagMap)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tag := range tags {
|
|
||||||
if _, ok := filter.Tags[tag[0]]; !ok {
|
|
||||||
filter.Tags[tag[0]] = make([]string, 0, 3)
|
|
||||||
}
|
|
||||||
filter.Tags[tag[0]] = append(filter.Tags[tag[0]], tag[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
if since := c.String("since"); since != "" {
|
|
||||||
if since == "now" {
|
|
||||||
ts := nostr.Now()
|
|
||||||
filter.Since = &ts
|
|
||||||
} else if i, err := strconv.Atoi(since); err == nil {
|
|
||||||
ts := nostr.Timestamp(i)
|
|
||||||
filter.Since = &ts
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("parse error: Invalid numeric literal %q", since)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if until := c.String("until"); until != "" {
|
|
||||||
if until == "now" {
|
|
||||||
ts := nostr.Now()
|
|
||||||
filter.Until = &ts
|
|
||||||
} else if i, err := strconv.Atoi(until); err == nil {
|
|
||||||
ts := nostr.Timestamp(i)
|
|
||||||
filter.Until = &ts
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("parse error: Invalid numeric literal %q", until)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if limit := c.Int("limit"); limit != 0 {
|
|
||||||
filter.Limit = int(limit)
|
|
||||||
} else if c.IsSet("limit") || c.Bool("stream") {
|
|
||||||
filter.LimitZero = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(relayUrls) > 0 {
|
if len(relayUrls) > 0 {
|
||||||
fn := pool.SubManyEose
|
fn := pool.SubManyEose
|
||||||
if c.Bool("stream") {
|
if c.Bool("paginate") {
|
||||||
|
fn = paginateWithPoolAndParams(pool, c.Duration("paginate-interval"), c.Uint("paginate-global-limit"))
|
||||||
|
} else if c.Bool("stream") {
|
||||||
fn = pool.SubMany
|
fn = pool.SubMany
|
||||||
}
|
}
|
||||||
|
|
||||||
for ie := range fn(ctx, relayUrls, nostr.Filters{filter}) {
|
for ie := range fn(ctx, relayUrls, nostr.Filters{filter}) {
|
||||||
stdout(ie.Event)
|
stdout(ie.Event)
|
||||||
}
|
}
|
||||||
@@ -283,3 +179,130 @@ example:
|
|||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var reqFilterFlags = []cli.Flag{
|
||||||
|
&cli.StringSliceFlag{
|
||||||
|
Name: "author",
|
||||||
|
Aliases: []string{"a"},
|
||||||
|
Usage: "only accept events from these authors (pubkey as hex)",
|
||||||
|
Category: CATEGORY_FILTER_ATTRIBUTES,
|
||||||
|
},
|
||||||
|
&cli.StringSliceFlag{
|
||||||
|
Name: "id",
|
||||||
|
Aliases: []string{"i"},
|
||||||
|
Usage: "only accept events with these ids (hex)",
|
||||||
|
Category: CATEGORY_FILTER_ATTRIBUTES,
|
||||||
|
},
|
||||||
|
&cli.IntSliceFlag{
|
||||||
|
Name: "kind",
|
||||||
|
Aliases: []string{"k"},
|
||||||
|
Usage: "only accept events with these kind numbers",
|
||||||
|
Category: CATEGORY_FILTER_ATTRIBUTES,
|
||||||
|
},
|
||||||
|
&cli.StringSliceFlag{
|
||||||
|
Name: "tag",
|
||||||
|
Aliases: []string{"t"},
|
||||||
|
Usage: "takes a tag like -t e=<id>, only accept events with these tags",
|
||||||
|
Category: CATEGORY_FILTER_ATTRIBUTES,
|
||||||
|
},
|
||||||
|
&cli.StringSliceFlag{
|
||||||
|
Name: "e",
|
||||||
|
Usage: "shortcut for --tag e=<value>",
|
||||||
|
Category: CATEGORY_FILTER_ATTRIBUTES,
|
||||||
|
},
|
||||||
|
&cli.StringSliceFlag{
|
||||||
|
Name: "p",
|
||||||
|
Usage: "shortcut for --tag p=<value>",
|
||||||
|
Category: CATEGORY_FILTER_ATTRIBUTES,
|
||||||
|
},
|
||||||
|
&cli.StringSliceFlag{
|
||||||
|
Name: "d",
|
||||||
|
Usage: "shortcut for --tag d=<value>",
|
||||||
|
Category: CATEGORY_FILTER_ATTRIBUTES,
|
||||||
|
},
|
||||||
|
&NaturalTimeFlag{
|
||||||
|
Name: "since",
|
||||||
|
Aliases: []string{"s"},
|
||||||
|
Usage: "only accept events newer than this (unix timestamp)",
|
||||||
|
Category: CATEGORY_FILTER_ATTRIBUTES,
|
||||||
|
},
|
||||||
|
&NaturalTimeFlag{
|
||||||
|
Name: "until",
|
||||||
|
Aliases: []string{"u"},
|
||||||
|
Usage: "only accept events older than this (unix timestamp)",
|
||||||
|
Category: CATEGORY_FILTER_ATTRIBUTES,
|
||||||
|
},
|
||||||
|
&cli.UintFlag{
|
||||||
|
Name: "limit",
|
||||||
|
Aliases: []string{"l"},
|
||||||
|
Usage: "only accept up to this number of events",
|
||||||
|
Category: CATEGORY_FILTER_ATTRIBUTES,
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "search",
|
||||||
|
Usage: "a NIP-50 search query, use it only with relays that explicitly support it",
|
||||||
|
Category: CATEGORY_FILTER_ATTRIBUTES,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyFlagsToFilter(c *cli.Command, filter *nostr.Filter) error {
|
||||||
|
if authors := c.StringSlice("author"); len(authors) > 0 {
|
||||||
|
filter.Authors = append(filter.Authors, authors...)
|
||||||
|
}
|
||||||
|
if ids := c.StringSlice("id"); len(ids) > 0 {
|
||||||
|
filter.IDs = append(filter.IDs, ids...)
|
||||||
|
}
|
||||||
|
for _, kind64 := range c.IntSlice("kind") {
|
||||||
|
filter.Kinds = append(filter.Kinds, int(kind64))
|
||||||
|
}
|
||||||
|
if search := c.String("search"); search != "" {
|
||||||
|
filter.Search = search
|
||||||
|
}
|
||||||
|
tags := make([][]string, 0, 5)
|
||||||
|
for _, tagFlag := range c.StringSlice("tag") {
|
||||||
|
spl := strings.Split(tagFlag, "=")
|
||||||
|
if len(spl) == 2 && len(spl[0]) == 1 {
|
||||||
|
tags = append(tags, spl)
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("invalid --tag '%s'", tagFlag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, etag := range c.StringSlice("e") {
|
||||||
|
tags = append(tags, []string{"e", etag})
|
||||||
|
}
|
||||||
|
for _, ptag := range c.StringSlice("p") {
|
||||||
|
tags = append(tags, []string{"p", ptag})
|
||||||
|
}
|
||||||
|
for _, dtag := range c.StringSlice("d") {
|
||||||
|
tags = append(tags, []string{"d", dtag})
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(tags) > 0 && filter.Tags == nil {
|
||||||
|
filter.Tags = make(nostr.TagMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tag := range tags {
|
||||||
|
if _, ok := filter.Tags[tag[0]]; !ok {
|
||||||
|
filter.Tags[tag[0]] = make([]string, 0, 3)
|
||||||
|
}
|
||||||
|
filter.Tags[tag[0]] = append(filter.Tags[tag[0]], tag[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.IsSet("since") {
|
||||||
|
nts := getNaturalDate(c, "since")
|
||||||
|
filter.Since = &nts
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.IsSet("until") {
|
||||||
|
nts := getNaturalDate(c, "until")
|
||||||
|
filter.Until = &nts
|
||||||
|
}
|
||||||
|
|
||||||
|
if limit := c.Uint("limit"); limit != 0 {
|
||||||
|
filter.Limit = int(limit)
|
||||||
|
} else if c.IsSet("limit") {
|
||||||
|
filter.LimitZero = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
126
serve.go
Normal file
126
serve.go
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/bep/debounce"
|
||||||
|
"github.com/fatih/color"
|
||||||
|
"github.com/fiatjaf/cli/v3"
|
||||||
|
"github.com/fiatjaf/eventstore/slicestore"
|
||||||
|
"github.com/fiatjaf/khatru"
|
||||||
|
"github.com/nbd-wtf/go-nostr"
|
||||||
|
)
|
||||||
|
|
||||||
|
var serve = &cli.Command{
|
||||||
|
Name: "serve",
|
||||||
|
Usage: "starts an in-memory relay for testing purposes",
|
||||||
|
DisableSliceFlagSeparator: true,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "hostname",
|
||||||
|
Usage: "hostname where to listen for connections",
|
||||||
|
Value: "localhost",
|
||||||
|
},
|
||||||
|
&cli.UintFlag{
|
||||||
|
Name: "port",
|
||||||
|
Usage: "port where to listen for connections",
|
||||||
|
Value: 10547,
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "events",
|
||||||
|
Usage: "file containing the initial batch of events that will be served by the relay as newline-separated JSON (jsonl)",
|
||||||
|
DefaultText: "the relay will start empty",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
|
db := slicestore.SliceStore{MaxLimit: math.MaxInt}
|
||||||
|
|
||||||
|
var scanner *bufio.Scanner
|
||||||
|
if path := c.String("events"); path != "" {
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to file at '%s': %w", path, err)
|
||||||
|
}
|
||||||
|
scanner = bufio.NewScanner(f)
|
||||||
|
} else if isPiped() {
|
||||||
|
scanner = bufio.NewScanner(os.Stdin)
|
||||||
|
}
|
||||||
|
|
||||||
|
if scanner != nil {
|
||||||
|
scanner.Buffer(make([]byte, 16*1024*1024), 256*1024*1024)
|
||||||
|
i := 0
|
||||||
|
for scanner.Scan() {
|
||||||
|
var evt nostr.Event
|
||||||
|
if err := json.Unmarshal(scanner.Bytes(), &evt); err != nil {
|
||||||
|
return fmt.Errorf("invalid event received at line %d: %s (`%s`)", i, err, scanner.Text())
|
||||||
|
}
|
||||||
|
db.SaveEvent(ctx, &evt)
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rl := khatru.NewRelay()
|
||||||
|
rl.QueryEvents = append(rl.QueryEvents, db.QueryEvents)
|
||||||
|
rl.CountEvents = append(rl.CountEvents, db.CountEvents)
|
||||||
|
rl.DeleteEvent = append(rl.DeleteEvent, db.DeleteEvent)
|
||||||
|
rl.StoreEvent = append(rl.StoreEvent, db.SaveEvent)
|
||||||
|
|
||||||
|
started := make(chan bool)
|
||||||
|
exited := make(chan error)
|
||||||
|
|
||||||
|
hostname := c.String("hostname")
|
||||||
|
port := int(c.Uint("port"))
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
err := rl.Start(hostname, port, started)
|
||||||
|
exited <- err
|
||||||
|
}()
|
||||||
|
|
||||||
|
bold := color.New(color.Bold).Sprintf
|
||||||
|
italic := color.New(color.Italic).Sprint
|
||||||
|
|
||||||
|
var printStatus func()
|
||||||
|
|
||||||
|
// relay logging
|
||||||
|
rl.RejectFilter = append(rl.RejectFilter, func(ctx context.Context, filter nostr.Filter) (reject bool, msg string) {
|
||||||
|
log(" got %s %v\n", color.HiYellowString("request"), italic(filter))
|
||||||
|
printStatus()
|
||||||
|
return false, ""
|
||||||
|
})
|
||||||
|
rl.RejectCountFilter = append(rl.RejectCountFilter, func(ctx context.Context, filter nostr.Filter) (reject bool, msg string) {
|
||||||
|
log(" got %s %v\n", color.HiCyanString("count request"), italic(filter))
|
||||||
|
printStatus()
|
||||||
|
return false, ""
|
||||||
|
})
|
||||||
|
rl.RejectEvent = append(rl.RejectEvent, func(ctx context.Context, event *nostr.Event) (reject bool, msg string) {
|
||||||
|
log(" got %s %v\n", color.BlueString("event"), italic(event))
|
||||||
|
printStatus()
|
||||||
|
return false, ""
|
||||||
|
})
|
||||||
|
|
||||||
|
d := debounce.New(time.Second * 2)
|
||||||
|
printStatus = func() {
|
||||||
|
d(func() {
|
||||||
|
totalEvents := 0
|
||||||
|
ch, _ := db.QueryEvents(ctx, nostr.Filter{})
|
||||||
|
for range ch {
|
||||||
|
totalEvents++
|
||||||
|
}
|
||||||
|
subs := rl.GetListeningFilters()
|
||||||
|
|
||||||
|
log(" %s events stored: %s, subscriptions opened: %s\n", color.HiMagentaString("•"), color.HiMagentaString("%d", totalEvents), color.HiMagentaString("%d", len(subs)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
<-started
|
||||||
|
log("%s relay running at %s\n", color.HiRedString(">"), bold("ws://%s:%d", hostname, port))
|
||||||
|
|
||||||
|
return <-exited
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -4,8 +4,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/fiatjaf/cli/v3"
|
||||||
"github.com/nbd-wtf/go-nostr"
|
"github.com/nbd-wtf/go-nostr"
|
||||||
"github.com/urfave/cli/v3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var verify = &cli.Command{
|
var verify = &cli.Command{
|
||||||
@@ -15,6 +15,7 @@ var verify = &cli.Command{
|
|||||||
echo '{"id":"a889df6a387419ff204305f4c2d296ee328c3cd4f8b62f205648a541b4554dfb","pubkey":"c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5","created_at":1698623783,"kind":1,"tags":[],"content":"hello from the nostr army knife","sig":"84876e1ee3e726da84e5d195eb79358b2b3eaa4d9bd38456fde3e8a2af3f1cd4cda23f23fda454869975b3688797d4c66e12f4c51c1b43c6d2997c5e61865661"}' | nak verify
|
echo '{"id":"a889df6a387419ff204305f4c2d296ee328c3cd4f8b62f205648a541b4554dfb","pubkey":"c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5","created_at":1698623783,"kind":1,"tags":[],"content":"hello from the nostr army knife","sig":"84876e1ee3e726da84e5d195eb79358b2b3eaa4d9bd38456fde3e8a2af3f1cd4cda23f23fda454869975b3688797d4c66e12f4c51c1b43c6d2997c5e61865661"}' | nak verify
|
||||||
|
|
||||||
it outputs nothing if the verification is successful.`,
|
it outputs nothing if the verification is successful.`,
|
||||||
|
DisableSliceFlagSeparator: true,
|
||||||
Action: func(ctx context.Context, c *cli.Command) error {
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
for stdinEvent := range getStdinLinesOrArguments(c.Args()) {
|
for stdinEvent := range getStdinLinesOrArguments(c.Args()) {
|
||||||
evt := nostr.Event{}
|
evt := nostr.Event{}
|
||||||
|
|||||||
Reference in New Issue
Block a user