mirror of
https://github.com/fiatjaf/nak.git
synced 2025-12-08 16:48:51 +00:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d23509d8c | ||
|
|
29b6ecbafe | ||
|
|
11f37afa5b | ||
|
|
cf1694704e | ||
|
|
b3ef2c1289 | ||
|
|
cfdea699bc | ||
|
|
014c6bc11d | ||
|
|
0240866fa1 | ||
|
|
a4d9ceecfa | ||
|
|
56657d8aa9 | ||
|
|
ea7b88cfd7 | ||
|
|
2042b14578 | ||
|
|
9d43e66fac | ||
|
|
85e9610265 | ||
|
|
2edfa5cbea | ||
|
|
9690dc70cb | ||
|
|
c90e61dbec | ||
|
|
d226cd6ce4 | ||
|
|
3d78e91f62 | ||
|
|
84965f2253 | ||
|
|
928c73513c | ||
|
|
a36142604d |
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
|
||||||
|
|||||||
56
README.md
56
README.md
@@ -173,6 +173,62 @@ listening at [wss://relay.damus.io wss://nos.lol wss://relay.nsecbunker.com]:
|
|||||||
{"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"}
|
{"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
|
## 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/fiatjaf/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/nbd-wtf/go-nostr"
|
|
||||||
"github.com/fiatjaf/cli/v3"
|
"github.com/fiatjaf/cli/v3"
|
||||||
|
"github.com/nbd-wtf/go-nostr"
|
||||||
)
|
)
|
||||||
|
|
||||||
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/fiatjaf/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/fiatjaf/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 {
|
||||||
|
|||||||
83
event.go
83
event.go
@@ -8,14 +8,19 @@ import (
|
|||||||
"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/fiatjaf/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",
|
||||||
@@ -31,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
|
||||||
@@ -58,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",
|
||||||
@@ -76,17 +86,25 @@ 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{
|
||||||
|
Name: "nevent",
|
||||||
|
Usage: "print the nevent code (to stderr) after the event is published",
|
||||||
|
Category: CATEGORY_EXTRAS,
|
||||||
},
|
},
|
||||||
&cli.UintFlag{
|
&cli.UintFlag{
|
||||||
Name: "kind",
|
Name: "kind",
|
||||||
@@ -192,8 +210,9 @@ example:
|
|||||||
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}
|
||||||
@@ -202,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 {
|
||||||
@@ -232,6 +248,33 @@ example:
|
|||||||
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 {
|
||||||
|
|||||||
@@ -116,3 +116,9 @@ func ExampleReqWithFlagsAfter3() {
|
|||||||
// Output:
|
// 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"}
|
// {"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"}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ExampleNaturalTimestamps() {
|
||||||
|
app.Run(ctx, []string{"nak", "event", "-t", "plu=pla", "-e", "3f770d65d3a764a9c5cb503ae123e62ec7598ad035d836e2a810f3877a745b24", "--ts", "2018-05-19 03:37:19", "-c", "nn"})
|
||||||
|
// Output:
|
||||||
|
// {"kind":1,"id":"0000d199127d5e15046b0a3f2885d464ee18f70968303665ef76326a7d828312","pubkey":"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798","created_at":1724160467,"tags":[["plu","pla"],["e","3f770d65d3a764a9c5cb503ae123e62ec7598ad035d836e2a810f3877a745b24"],["nonce","24783","16"]],"content":"nn","sig":"99471b43ce82ca01fb9b61f36b45ca542870854b2466a9d3884891598f7d7baef36d07f4b02bb194f2f6f781973f24c3d946f702c82321c6cb0c564e76cf43db"}
|
||||||
|
}
|
||||||
|
|||||||
121
fetch.go
121
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/fiatjaf/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,80 @@ 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.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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
2
flags.go
2
flags.go
@@ -6,9 +6,9 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/fiatjaf/cli/v3"
|
||||||
"github.com/markusmobius/go-dateparser"
|
"github.com/markusmobius/go-dateparser"
|
||||||
"github.com/nbd-wtf/go-nostr"
|
"github.com/nbd-wtf/go-nostr"
|
||||||
"github.com/fiatjaf/cli/v3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type NaturalTimeFlag = cli.FlagBase[nostr.Timestamp, struct{}, naturalTimeValue]
|
type NaturalTimeFlag = cli.FlagBase[nostr.Timestamp, struct{}, naturalTimeValue]
|
||||||
|
|||||||
24
go.mod
24
go.mod
@@ -5,43 +5,59 @@ go 1.22
|
|||||||
toolchain go1.22.4
|
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/markusmobius/go-dateparser v1.2.3
|
github.com/markusmobius/go-dateparser v1.2.3
|
||||||
github.com/nbd-wtf/go-nostr v0.34.2
|
github.com/nbd-wtf/go-nostr v0.34.10
|
||||||
github.com/nbd-wtf/nostr-sdk v0.0.5
|
github.com/nbd-wtf/nostr-sdk v0.5.0
|
||||||
golang.org/x/exp v0.0.0-20240707233637-46b078467d37
|
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/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/elliotchance/pie/v2 v2.7.0 // indirect
|
github.com/elliotchance/pie/v2 v2.7.0 // indirect
|
||||||
github.com/fiatjaf/cli/v3 v3.0.0-20240723181502-e7dd498b16ae // indirect
|
github.com/fasthttp/websocket v1.5.7 // indirect
|
||||||
github.com/fiatjaf/eventstore v0.2.16 // 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-hijri v1.0.2 // indirect
|
||||||
github.com/hablullah/go-juliandays v1.0.0 // indirect
|
github.com/hablullah/go-juliandays v1.0.0 // indirect
|
||||||
github.com/jalaali/go-jalaali v0.0.0-20210801064154-80525e88d958 // 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/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/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
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
|
github.com/valyala/fasthttp v1.51.0 // indirect
|
||||||
github.com/wasilibs/go-re2 v1.3.0 // indirect
|
github.com/wasilibs/go-re2 v1.3.0 // indirect
|
||||||
golang.org/x/crypto v0.21.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/sys v0.22.0 // indirect
|
||||||
golang.org/x/text v0.16.0 // indirect
|
golang.org/x/text v0.16.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
46
go.sum
46
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,16 +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 h1:FqoIKg4uj0G/CrLGuMS9ejnFKa92lxE1dEgBD3pShXg=
|
||||||
github.com/elliotchance/pie/v2 v2.7.0/go.mod h1:18t0dgGFH006g4eVdDtWfgFZPQEgl10IoEO8YWEq3Og=
|
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-20240714232133-bb036558919f h1:SQ5W4q4HfpAPA8e8uPADqs4dhI6VvVwUq00DtlpHIt0=
|
|
||||||
github.com/fiatjaf/cli/v3 v3.0.0-20240714232133-bb036558919f/go.mod h1:Z1ItyMma7t6I7zHG9OpbExhHQOSkFf/96n+mAZ9MtVI=
|
|
||||||
github.com/fiatjaf/cli/v3 v3.0.0-20240723181502-e7dd498b16ae h1:0B/1dU3YECIbPoBIRTQ4c0scZCNz9TVHtQpiODGrTTo=
|
github.com/fiatjaf/cli/v3 v3.0.0-20240723181502-e7dd498b16ae h1:0B/1dU3YECIbPoBIRTQ4c0scZCNz9TVHtQpiODGrTTo=
|
||||||
github.com/fiatjaf/cli/v3 v3.0.0-20240723181502-e7dd498b16ae/go.mod h1:aAWPO4bixZZxPtOnH6K3q4GbQ0jftUNDW9Oa861IRew=
|
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=
|
||||||
@@ -58,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=
|
||||||
@@ -69,6 +85,8 @@ 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 h1:drT/MZpSZJQXo7jftf5fthArShcaMtsal0Zf/dnmp6k=
|
||||||
github.com/hablullah/go-hijri v1.0.2/go.mod h1:OS5qyYLDjORXzK4O1adFw9Q5WfhOcMdAKglDkcTxgWQ=
|
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 h1:A8YM7wIj16SzlKT0SRJc9CD29iiaUzpBLzh5hr0/5p0=
|
||||||
@@ -82,6 +100,8 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm
|
|||||||
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 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
|
||||||
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
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=
|
||||||
@@ -93,10 +113,10 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
|
|||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.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=
|
||||||
@@ -106,10 +126,16 @@ 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.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
@@ -124,6 +150,10 @@ 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 h1:LFhBNzoStM3wMie6rN2slD1cuYH2CGiHpvNL3UtcsMw=
|
||||||
github.com/wasilibs/go-re2 v1.3.0/go.mod h1:AafrCXVvGRJJOImMajgJ2M7rVmWyisVK7sFshbxnVrg=
|
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 h1:h1TJMihMC4neN6Zq+WKpLxgd9xCFMw7O9ETLwY2exJQ=
|
||||||
|
|||||||
17
helpers.go
17
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/fiatjaf/cli/v3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -196,12 +196,13 @@ 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")
|
||||||
|
|
||||||
// check in the environment for the secret key
|
|
||||||
if sec == "" {
|
if sec == "" {
|
||||||
if key, ok := os.LookupEnv("NOSTR_PRIVATE_KEY"); ok {
|
if key, ok := os.LookupEnv("NOSTR_SECRET_KEY"); ok {
|
||||||
sec = key
|
sec = key
|
||||||
|
} else {
|
||||||
|
sec = "0000000000000000000000000000000000000000000000000000000000000001"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,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 {
|
||||||
@@ -230,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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,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
|
||||||
}
|
}
|
||||||
|
|||||||
46
key.go
46
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/fiatjaf/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
|
||||||
@@ -151,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"`
|
||||||
|
|||||||
14
main.go
14
main.go
@@ -8,11 +8,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var app = &cli.Command{
|
var app = &cli.Command{
|
||||||
Name: "nak",
|
Name: "nak",
|
||||||
Suggest: true,
|
Suggest: true,
|
||||||
UseShortOptionHandling: true,
|
UseShortOptionHandling: true,
|
||||||
AllowFlagsAfterArguments: true,
|
AllowFlagsAfterArguments: true,
|
||||||
Usage: "the nostr army knife command-line tool",
|
Usage: "the nostr army knife command-line tool",
|
||||||
|
DisableSliceFlagSeparator: true,
|
||||||
Commands: []*cli.Command{
|
Commands: []*cli.Command{
|
||||||
req,
|
req,
|
||||||
count,
|
count,
|
||||||
@@ -24,6 +25,7 @@ var app = &cli.Command{
|
|||||||
verify,
|
verify,
|
||||||
relay,
|
relay,
|
||||||
bunker,
|
bunker,
|
||||||
|
serve,
|
||||||
},
|
},
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
@@ -36,7 +38,7 @@ var app = &cli.Command{
|
|||||||
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
|
||||||
|
}
|
||||||
|
}
|
||||||
6
relay.go
6
relay.go
@@ -11,10 +11,10 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"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/fiatjaf/cli/v3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var relay = &cli.Command{
|
var relay = &cli.Command{
|
||||||
@@ -22,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 == "" {
|
||||||
@@ -78,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",
|
||||||
|
|||||||
297
req.go
297
req.go
@@ -7,12 +7,15 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"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/fiatjaf/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",
|
||||||
@@ -27,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,
|
|
||||||
},
|
|
||||||
&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,
|
|
||||||
},
|
|
||||||
&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",
|
||||||
@@ -104,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
|
||||||
@@ -186,67 +146,15 @@ 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 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") || 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,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/nbd-wtf/go-nostr"
|
|
||||||
"github.com/fiatjaf/cli/v3"
|
"github.com/fiatjaf/cli/v3"
|
||||||
|
"github.com/nbd-wtf/go-nostr"
|
||||||
)
|
)
|
||||||
|
|
||||||
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