Compare commits

..

31 Commits

Author SHA1 Message Date
fiatjaf
3dfcec69b7 --nevent flag on nak event to print an nevent at the end. 2024-01-24 22:43:23 -03:00
fiatjaf
14b69f36cf -q to silence stderr, -qq to silence everything. 2024-01-24 22:38:51 -03:00
fiatjaf
3f7089e27e signal that we accept patches over NIP-34. 2024-01-23 10:20:54 -03:00
fiatjaf
6a75c8aec3 UseShortOptionHandling and Suggest 2024-01-23 10:20:54 -03:00
fiatjaf
b17887fe21 replace validate32BytesHex() with native calls from go-nostr. 2024-01-21 07:45:22 -03:00
fiatjaf
77103cae0c req: -d flag too. 2024-01-17 08:50:59 -03:00
fiatjaf
59a2c16b42 event: -d shortcut flag and use .AppendUnique() 2024-01-17 08:49:18 -03:00
fiatjaf
48d19196bb fix publishing to multiple relays. 2024-01-16 09:16:56 -03:00
fiatjaf
ad7010e506 use scanner.Buffer() to make stdin able to parse hellish giant events up to 256kb (the default was 64kb). 2024-01-11 21:48:54 -03:00
fiatjaf
584881266e bunker: update to go-nostr nip46 api breaking change. 2024-01-11 21:41:50 -03:00
fiatjaf
a30f422d7d close relay websockets cleanly. 2024-01-11 21:29:46 -03:00
fiatjaf
637b9440ef upgrade go-nostr and xsync. 2024-01-10 21:19:19 -03:00
fiatjaf
16c1e795bd fetch: more places to fetch relay lists from. 2024-01-02 11:05:43 -03:00
OHASHI Hideya
8373da647e Fix tags with values containing = 2023-12-24 09:29:29 -03:00
fiatjaf
f295f130f2 remove .scalafmt.conf 2023-12-23 21:30:58 -03:00
fiatjaf
5415fd369c update go-nostr to fix pool infinite loop. 2023-12-15 11:15:12 -03:00
Daniel Cadenas
f35cb4bd1d Fix tags with values containing = 2023-12-13 20:48:01 -03:00
Yasuhiro Matsumoto
242b028656 -until now 2023-12-12 13:34:30 -03:00
Yasuhiro Matsumoto
f0d90b567c -since now 2023-12-12 13:34:30 -03:00
fiatjaf
2d1e27f766 fix for nil error case on publish. 2023-12-10 20:49:08 -03:00
fiatjaf
bfa72640cd bunker: a better prompt. 2023-12-09 17:42:01 -03:00
fiatjaf
e5b0b15908 bunker: improve error message. 2023-12-09 17:11:55 -03:00
fiatjaf
0860cfcf6d rename nsecbunker->bunker. 2023-12-09 16:37:59 -03:00
fiatjaf
b7b61c0723 support --auth/--sec/--prompt-sec on req. 2023-12-09 16:32:04 -03:00
fiatjaf
ed3156ae10 fix event publishing flow: no need to reconnect and AUTH messages make sense. 2023-12-09 09:14:45 -03:00
fiatjaf
30dbe2c1c0 fix handling multiple lines in event (broken in previous commit). 2023-12-07 18:14:26 -03:00
fiatjaf
4d75605c20 print event more consistently and auth when required and allowed. 2023-12-07 18:12:18 -03:00
fiatjaf
26b1aa359a nsecbunker/nip46 is working now. 2023-12-02 15:33:37 -03:00
fiatjaf
bc7cd0939c nsecbunker work-in-progress. 2023-12-02 12:20:15 -03:00
fiatjaf
5657fdc6a7 update go-nostr. 2023-12-01 13:22:04 -03:00
fiatjaf
d9d36e7619 use easyjson and envelopes. 2023-11-28 15:18:43 -03:00
16 changed files with 439 additions and 198 deletions

10
.gitignore vendored
View File

@@ -1,9 +1 @@
target
.bsp
globals.bundle.js
yarn.lock
node_modules
project/project
.metals
.bloop
project/metals.sbt
nak

View File

@@ -1,2 +0,0 @@
version = 3.5.8
runner.dialect = scala3

View File

@@ -81,3 +81,8 @@ invalid .id, expected 05bd99d54cb835f427e0092c4275ee44c7ff51219eff417c19f70c9e2c
```shell
nak req -l 100 -k 1 -a 2edbcea694d164629854a52583458fd6d965b161e3c48b57d3aff01940558884 wss://relay.damus.io | jq -r '.content | match("nostr:((note1|nevent1)[a-z0-9]+)";"g") | .captures[0].string' | nak decode | jq -cr '{ids: [.id]}' | nak req wss://relay.damus.io
```
## Contributing to this repository
Use NIP-34 to send your patches to `naddr1qqpkucttqy28wumn8ghj7un9d3shjtnyv9kh2uewd9hsz9nhwden5te0wfjkccte9ehx7um5wghxyctwvsq3vamnwvaz7tmjv4kxz7fwwpexjmtpdshxuet5qgsg04q5ypr6f4n65mv7e5hs05z50hy7vvgua8uc8szwtp262cfwn6srqsqqqauedy5x7y`.

140
bunker.go Normal file
View File

@@ -0,0 +1,140 @@
package main
import (
"encoding/json"
"fmt"
"net/url"
"os"
"github.com/manifoldco/promptui"
"github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/nip19"
"github.com/nbd-wtf/go-nostr/nip46"
"github.com/urfave/cli/v2"
"golang.org/x/exp/slices"
)
var bunker = &cli.Command{
Name: "bunker",
Usage: "starts a NIP-46 signer daemon with the given --sec key",
ArgsUsage: "[relay...]",
Description: ``,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "sec",
Usage: "secret key to sign the event, as hex or nsec",
DefaultText: "the key '1'",
Value: "0000000000000000000000000000000000000000000000000000000000000001",
},
&cli.BoolFlag{
Name: "prompt-sec",
Usage: "prompt the user to paste a hex or nsec with which to sign the event",
},
&cli.BoolFlag{
Name: "yes",
Aliases: []string{"y"},
Usage: "always respond to any NIP-46 requests from anyone",
},
},
Action: func(c *cli.Context) error {
// try to connect to the relays here
qs := url.Values{}
relayURLs := make([]string, 0, c.Args().Len())
if relayUrls := c.Args().Slice(); len(relayUrls) > 0 {
_, relays := connectToAllRelays(c.Context, relayUrls)
if len(relays) == 0 {
log("failed to connect to any of the given relays.\n")
os.Exit(3)
}
for _, relay := range relays {
relayURLs = append(relayURLs, relay.URL)
qs.Add("relay", relay.URL)
}
}
if len(relayURLs) == 0 {
return fmt.Errorf("not connected to any relays: please specify at least one")
}
// gather the secret key
sec, err := gatherSecretKeyFromArguments(c)
if err != nil {
return err
}
pubkey, err := nostr.GetPublicKey(sec)
if err != nil {
return err
}
npub, _ := nip19.EncodePublicKey(pubkey)
log("listening at %s%v%s:\n %spubkey:%s %s\n %snpub:%s %s\n %sconnection code:%s %s\n %sbunker:%s %s\n\n",
BOLD_ON, relayURLs, BOLD_OFF,
BOLD_ON, BOLD_OFF, pubkey,
BOLD_ON, BOLD_OFF, npub,
BOLD_ON, BOLD_OFF, fmt.Sprintf("%s#secret?%s", npub, qs.Encode()),
BOLD_ON, BOLD_OFF, fmt.Sprintf("bunker://%s?%s", pubkey, qs.Encode()),
)
alwaysYes := c.Bool("yes")
// subscribe to relays
pool := nostr.NewSimplePool(c.Context)
events := pool.SubMany(c.Context, relayURLs, nostr.Filters{
{
Kinds: []int{24133},
Tags: nostr.TagMap{"p": []string{pubkey}},
},
})
signer := nip46.NewStaticKeySigner(sec)
for ie := range events {
req, resp, eventResponse, harmless, err := signer.HandleRequest(ie.Event)
if err != nil {
log("< failed to handle request from %s: %s", ie.Event.PubKey, err.Error())
continue
}
jreq, _ := json.MarshalIndent(req, " ", " ")
log("- got request from '%s': %s\n", ie.Event.PubKey, string(jreq))
jresp, _ := json.MarshalIndent(resp, " ", " ")
log("~ responding with %s\n", string(jresp))
if alwaysYes || harmless || askProceed(ie.Event.PubKey) {
if err := ie.Relay.Publish(c.Context, eventResponse); err == nil {
log("* sent response!\n")
} else {
log("* failed to send response: %s\n", err)
}
}
}
return nil
},
}
var allowedSources = make([]string, 0, 2)
func askProceed(source string) bool {
if slices.Contains(allowedSources, source) {
return true
}
prompt := promptui.Select{
Label: "proceed?",
Items: []string{
"no",
"yes",
"always from this source",
},
}
n, _, _ := prompt.Run()
switch n {
case 0:
return false
case 1:
return true
case 2:
allowedSources = append(allowedSources, source)
return true
}
return false
}

View File

@@ -78,7 +78,7 @@ var count = &cli.Command{
tags := make([][]string, 0, 5)
for _, tagFlag := range c.StringSlice("tag") {
spl := strings.Split(tagFlag, "=")
spl := strings.SplitN(tagFlag, "=", 2)
if len(spl) == 2 && len(spl[0]) == 1 {
tags = append(tags, spl)
} else {
@@ -139,7 +139,7 @@ var count = &cli.Command{
var result string
j, _ := json.Marshal([]any{"COUNT", "nak", filter})
result = string(j)
fmt.Println(result)
stdout(result)
}
return nil

View File

@@ -3,7 +3,6 @@ package main
import (
"encoding/hex"
"encoding/json"
"fmt"
"strings"
"github.com/nbd-wtf/go-nostr"
@@ -68,7 +67,7 @@ var decode = &cli.Command{
continue
}
fmt.Println(decodeResult.JSON())
stdout(decodeResult.JSON())
}

View File

@@ -3,6 +3,7 @@ package main
import (
"fmt"
"github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/nip19"
"github.com/urfave/cli/v2"
)
@@ -29,13 +30,13 @@ var encode = &cli.Command{
Usage: "encode a hex public key into bech32 'npub' format",
Action: func(c *cli.Context) error {
for target := range getStdinLinesOrFirstArgument(c) {
if err := validate32BytesHex(target); err != nil {
lineProcessingError(c, "invalid public key: %s", target, err)
if ok := nostr.IsValidPublicKey(target); !ok {
lineProcessingError(c, "invalid public key: %s", target)
continue
}
if npub, err := nip19.EncodePublicKey(target); err == nil {
fmt.Println(npub)
stdout(npub)
} else {
return err
}
@@ -50,13 +51,13 @@ var encode = &cli.Command{
Usage: "encode a hex private key into bech32 'nsec' format",
Action: func(c *cli.Context) error {
for target := range getStdinLinesOrFirstArgument(c) {
if err := validate32BytesHex(target); err != nil {
lineProcessingError(c, "invalid private key: %s", target, err)
if ok := nostr.IsValid32ByteHex(target); !ok {
lineProcessingError(c, "invalid private key: %s", target)
continue
}
if npub, err := nip19.EncodePrivateKey(target); err == nil {
fmt.Println(npub)
stdout(npub)
} else {
return err
}
@@ -78,8 +79,8 @@ var encode = &cli.Command{
},
Action: func(c *cli.Context) error {
for target := range getStdinLinesOrFirstArgument(c) {
if err := validate32BytesHex(target); err != nil {
lineProcessingError(c, "invalid public key: %s", target, err)
if ok := nostr.IsValid32ByteHex(target); !ok {
lineProcessingError(c, "invalid public key: %s", target)
continue
}
@@ -89,7 +90,7 @@ var encode = &cli.Command{
}
if npub, err := nip19.EncodeProfile(target, relays); err == nil {
fmt.Println(npub)
stdout(npub)
} else {
return err
}
@@ -115,15 +116,15 @@ var encode = &cli.Command{
},
Action: func(c *cli.Context) error {
for target := range getStdinLinesOrFirstArgument(c) {
if err := validate32BytesHex(target); err != nil {
lineProcessingError(c, "invalid event id: %s", target, err)
if ok := nostr.IsValid32ByteHex(target); !ok {
lineProcessingError(c, "invalid event id: %s", target)
continue
}
author := c.String("author")
if author != "" {
if err := validate32BytesHex(author); err != nil {
return err
if ok := nostr.IsValidPublicKey(author); !ok {
return fmt.Errorf("invalid 'author' public key")
}
}
@@ -133,7 +134,7 @@ var encode = &cli.Command{
}
if npub, err := nip19.EncodeEvent(target, relays, author); err == nil {
fmt.Println(npub)
stdout(npub)
} else {
return err
}
@@ -174,8 +175,8 @@ var encode = &cli.Command{
Action: func(c *cli.Context) error {
for d := range getStdinLinesOrBlank() {
pubkey := c.String("pubkey")
if err := validate32BytesHex(pubkey); err != nil {
return err
if ok := nostr.IsValidPublicKey(pubkey); !ok {
return fmt.Errorf("invalid 'pubkey'")
}
kind := c.Int("kind")
@@ -197,7 +198,7 @@ var encode = &cli.Command{
}
if npub, err := nip19.EncodeEntity(pubkey, kind, d, relays); err == nil {
fmt.Println(npub)
stdout(npub)
} else {
return err
}
@@ -212,13 +213,13 @@ var encode = &cli.Command{
Usage: "generate note1 event codes (not recommended)",
Action: func(c *cli.Context) error {
for target := range getStdinLinesOrFirstArgument(c) {
if err := validate32BytesHex(target); err != nil {
lineProcessingError(c, "invalid event id: %s", target, err)
if ok := nostr.IsValid32ByteHex(target); !ok {
lineProcessingError(c, "invalid event id: %s", target)
continue
}
if note, err := nip19.EncodeNote(target); err == nil {
fmt.Println(note)
stdout(note)
} else {
return err
}

134
event.go
View File

@@ -9,7 +9,6 @@ import (
"strings"
"time"
"github.com/bgentry/speakeasy"
"github.com/mailru/easyjson"
"github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/nip19"
@@ -49,6 +48,14 @@ example:
Name: "envelope",
Usage: "print the event enveloped in a [\"EVENT\", ...] message ready to be sent to a relay",
},
&cli.BoolFlag{
Name: "auth",
Usage: "always perform NIP-42 \"AUTH\" when facing an \"auth-required: \" rejection and try again",
},
&cli.BoolFlag{
Name: "nevent",
Usage: "print the nevent code (to stderr) after the event is published",
},
&cli.BoolFlag{
Name: "nson",
Usage: "encode the event using NSON",
@@ -85,6 +92,11 @@ example:
Usage: "shortcut for --tag p=<value>",
Category: CATEGORY_EVENT_FIELDS,
},
&cli.StringSliceFlag{
Name: "d",
Usage: "shortcut for --tag d=<value>",
Category: CATEGORY_EVENT_FIELDS,
},
&cli.StringFlag{
Name: "created-at",
Aliases: []string{"time", "ts"},
@@ -106,32 +118,19 @@ example:
}
}
// gather the secret key first
sec := c.String("sec")
if c.Bool("prompt-sec") {
if isPiped() {
return fmt.Errorf("can't prompt for a secret key when processing data from a pipe, try again without --prompt-sec")
}
var err error
sec, err = speakeasy.FAsk(os.Stderr, "type your secret key as nsec or hex: ")
if err != nil {
return fmt.Errorf("failed to get secret key: %w", err)
defer func() {
for _, relay := range relays {
relay.Close()
}
}()
// gather the secret key
sec, err := gatherSecretKeyFromArguments(c)
if err != nil {
return err
}
if strings.HasPrefix(sec, "nsec1") {
_, hex, err := nip19.Decode(sec)
if err != nil {
return fmt.Errorf("invalid nsec: %w", err)
}
sec = hex.(string)
}
if len(sec) > 64 {
return fmt.Errorf("invalid secret key: too large")
}
sec = strings.Repeat("0", 64-len(sec)) + sec // left-pad
if err := validate32BytesHex(sec); err != nil {
return fmt.Errorf("invalid secret key")
}
doAuth := c.Bool("auth")
// then process input and generate events
for stdinEvent := range getStdinLinesOrBlank() {
@@ -141,8 +140,9 @@ example:
kindWasSupplied := false
mustRehashAndResign := false
if stdinEvent != "" {
if err := json.Unmarshal([]byte(stdinEvent), &evt); err != nil {
if err := easyjson.Unmarshal([]byte(stdinEvent), &evt); err != nil {
lineProcessingError(c, "invalid event received from stdin: %s", err)
continue
}
@@ -168,22 +168,27 @@ example:
tags := make(nostr.Tags, 0, 5)
for _, tagFlag := range c.StringSlice("tag") {
// tags are in the format key=value
spl := strings.Split(tagFlag, "=")
if len(spl) == 2 && len(spl[0]) > 0 {
tag := nostr.Tag{spl[0]}
tagName, tagValue, found := strings.Cut(tagFlag, "=")
tag := []string{tagName}
if found {
// tags may also contain extra elements separated with a ";"
spl2 := strings.Split(spl[1], ";")
tag = append(tag, spl2...)
tagValues := strings.Split(tagValue, ";")
tag = append(tag, tagValues...)
// ~
tags = append(tags, tag)
tags = tags.AppendUnique(tag)
}
}
for _, etag := range c.StringSlice("e") {
tags = append(tags, []string{"e", etag})
tags = tags.AppendUnique([]string{"e", etag})
mustRehashAndResign = true
}
for _, ptag := range c.StringSlice("p") {
tags = append(tags, []string{"p", ptag})
tags = tags.AppendUnique([]string{"p", ptag})
mustRehashAndResign = true
}
for _, dtag := range c.StringSlice("d") {
tags = tags.AppendUnique([]string{"d", dtag})
mustRehashAndResign = true
}
if len(tags) > 0 {
@@ -215,35 +220,56 @@ example:
}
}
// print event as json
var result string
if c.Bool("envelope") {
j, _ := json.Marshal(nostr.EventEnvelope{Event: evt})
result = string(j)
} else if c.Bool("nson") {
result, _ = nson.Marshal(&evt)
} else {
j, _ := easyjson.Marshal(&evt)
result = string(j)
}
stdout(result)
// publish to relays
successRelays := make([]string, 0, len(relays))
if len(relays) > 0 {
fmt.Println(evt.String())
os.Stdout.Sync()
for _, relay := range relays {
publish:
log("publishing to %s... ", relay.URL)
if relay, err := nostr.RelayConnect(c.Context, relay.URL); err != nil {
log("failed to connect: %s\n", err)
} else {
ctx, cancel := context.WithTimeout(c.Context, 10*time.Second)
defer cancel()
if status, err := relay.Publish(ctx, evt); err != nil {
log("failed: %s\n", err)
ctx, cancel := context.WithTimeout(c.Context, 10*time.Second)
defer cancel()
err := relay.Publish(ctx, evt)
if err == nil {
// published fine
log("success.\n")
successRelays = append(successRelays, relay.URL)
continue // continue to next relay
}
// error publishing
if strings.HasPrefix(err.Error(), "msg: auth-required:") && sec != "" && doAuth {
// if the relay is requesting auth and we can auth, let's do it
pk, _ := nostr.GetPublicKey(sec)
log("performing auth as %s... ", pk)
if err := relay.Auth(c.Context, func(evt *nostr.Event) error { return evt.Sign(sec) }); err == nil {
// try to publish again, but this time don't try to auth again
doAuth = false
goto publish
} else {
log("%s.\n", status)
log("auth error: %s. ", err)
}
}
log("failed: %s\n", err)
}
} else {
var result string
if c.Bool("envelope") {
j, _ := json.Marshal([]any{"EVENT", evt})
result = string(j)
} else if c.Bool("nson") {
result, _ = nson.Marshal(&evt)
} else {
j, _ := easyjson.Marshal(&evt)
result = string(j)
if len(successRelays) > 0 && c.Bool("nevent") {
nevent, _ := nip19.EncodeEvent(evt.ID, successRelays, evt.PubKey)
log(nevent + "\n")
}
fmt.Println(result)
}
}

View File

@@ -7,9 +7,9 @@ func ExampleEventBasic() {
}
func ExampleEventComplex() {
app.Run([]string{"nak", "event", "--ts", "1699485669", "-k", "11", "-c", "skjdbaskd", "--sec", "17", "-t", "t=spam", "-e", "36d88cf5fcc449f2390a424907023eda7a74278120eebab8d02797cd92e7e29c", "-t", "r=https://abc.def;nothing"})
app.Run([]string{"nak", "event", "--ts", "1699485669", "-k", "11", "-c", "skjdbaskd", "--sec", "17", "-t", "t=spam", "-e", "36d88cf5fcc449f2390a424907023eda7a74278120eebab8d02797cd92e7e29c", "-t", "r=https://abc.def?name=foobar;nothing"})
// Output:
// {"id":"aec4de6d051a7c2b6ca2d087903d42051a31e07fb742f1240970084822de10a6","pubkey":"2fa2104d6b38d11b0230010559879124e42ab8dfeff5ff29dc9cdadd4ecacc3f","created_at":1699485669,"kind":11,"tags":[["t","spam"],["r","https://abc.def","nothing"],["e","36d88cf5fcc449f2390a424907023eda7a74278120eebab8d02797cd92e7e29c"]],"content":"skjdbaskd","sig":"1165ac7a27d774d351ef19c8e918fb22f4005fcba193976c3d7edba6ef87ead7f14467f376a9e199f8371835368d86a8506f591e382528d00287fb168a7b8f38"}
// {"id":"19aba166dcf354bf5ef64f4afe69ada1eb851495001ee05e07d393ee8c8ea179","pubkey":"2fa2104d6b38d11b0230010559879124e42ab8dfeff5ff29dc9cdadd4ecacc3f","created_at":1699485669,"kind":11,"tags":[["t","spam"],["r","https://abc.def?name=foobar","nothing"],["e","36d88cf5fcc449f2390a424907023eda7a74278120eebab8d02797cd92e7e29c"]],"content":"skjdbaskd","sig":"cf452def4a68341c897c3fc96fa34dc6895a5b8cc266d4c041bcdf758ec992ec5adb8b0179e98552aaaf9450526a26d7e62e413b15b1c57e0cfc8db6b29215d7"}
}
func ExampleReq() {

View File

@@ -1,8 +1,6 @@
package main
import (
"fmt"
"github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/nip19"
sdk "github.com/nbd-wtf/nostr-sdk"
@@ -24,6 +22,15 @@ var fetch = &cli.Command{
},
ArgsUsage: "[nip19code]",
Action: func(c *cli.Context) error {
pool := nostr.NewSimplePool(c.Context)
defer func() {
pool.Relays.Range(func(_ string, relay *nostr.Relay) bool {
relay.Close()
return true
})
}()
for code := range getStdinLinesOrFirstArgument(c) {
filter := nostr.Filter{}
@@ -67,10 +74,10 @@ var fetch = &cli.Command{
authorHint = v
}
pool := nostr.NewSimplePool(c.Context)
if authorHint != "" {
relayList := sdk.FetchRelaysForPubkey(c.Context, pool, authorHint,
"wss://purplepag.es", "wss://offchain.pub", "wss://public.relaying.io")
"wss://purplepag.es", "wss://relay.damus.io", "wss://relay.noswhere.com",
"wss://nos.lol", "wss://public.relaying.io", "wss://relay.nostr.band")
for _, relayListItem := range relayList {
if relayListItem.Outbox {
relays = append(relays, relayListItem.URL)
@@ -84,7 +91,7 @@ var fetch = &cli.Command{
}
for ie := range pool.SubManyEose(c.Context, relays, nostr.Filters{filter}) {
fmt.Println(ie.Event)
stdout(ie.Event)
}
}

27
go.mod
View File

@@ -7,34 +7,31 @@ toolchain go1.21.0
require (
github.com/bgentry/speakeasy v0.1.0
github.com/mailru/easyjson v0.7.7
github.com/nbd-wtf/go-nostr v0.25.7
github.com/nbd-wtf/nostr-sdk v0.0.2
github.com/urfave/cli/v2 v2.25.3
github.com/manifoldco/promptui v0.9.0
github.com/nbd-wtf/go-nostr v0.28.1
github.com/nbd-wtf/nostr-sdk v0.0.5
github.com/urfave/cli/v2 v2.25.7
golang.org/x/exp v0.0.0-20231006140011-7918f672742d
)
require (
github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect
github.com/btcsuite/btcd/btcutil v1.1.3 // indirect
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
github.com/dgraph-io/ristretto v0.1.1 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/fiatjaf/eventstore v0.1.0 // indirect
github.com/fiatjaf/eventstore v0.2.16 // indirect
github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect
github.com/gobwas/ws v1.2.0 // indirect
github.com/golang/glog v1.0.0 // indirect
github.com/gobwas/ws v1.3.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/puzpuzpuz/xsync/v2 v2.5.1 // indirect
github.com/puzpuzpuz/xsync/v3 v3.0.2 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/tidwall/gjson v1.14.4 // indirect
github.com/tidwall/gjson v1.17.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 // indirect
golang.org/x/sys v0.8.0 // indirect
golang.org/x/sys v0.14.0 // indirect
)

76
go.sum
View File

@@ -25,9 +25,12 @@ 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/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/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/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/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -41,25 +44,16 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeC
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218=
github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8=
github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA=
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.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/fiatjaf/eventstore v0.1.0 h1:/g7VTw6dsXmjICD3rBuHNIvAammHJ5unrKJ71Dz+VTs=
github.com/fiatjaf/eventstore v0.1.0/go.mod h1:juMei5HL3HJi6t7vZjj7VdEItDPu31+GLROepdUK4tw=
github.com/fiatjaf/eventstore v0.2.16 h1:NR64mnyUT5nJR8Sj2AwJTd1Hqs5kKJcCFO21ggUkvWg=
github.com/fiatjaf/eventstore v0.2.16/go.mod h1:rUc1KhVufVmC+HUOiuPweGAcvG6lEOQCkRCn2Xn5VRA=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
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/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
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/ws v1.2.0 h1:u0p9s3xLYpZCA1z5JgCkMeB34CKCMMQbM+G8Ii7YD0I=
github.com/gobwas/ws v1.2.0/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ=
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
github.com/gobwas/ws v1.3.1 h1:Qi34dfLMWJbiKaNbDVzM9x27nZBjmkaW6i4+Ku+pGVU=
github.com/gobwas/ws v1.3.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
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.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
@@ -80,12 +74,12 @@ github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlT
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/nbd-wtf/go-nostr v0.25.3 h1:RPPh4cOosw0OZi5KG627pZ3GlKxiKsjARluzen/mB9g=
github.com/nbd-wtf/go-nostr v0.25.3/go.mod h1:bkffJI+x914sPQWum9ZRUn66D7NpDnAoWo1yICvj3/0=
github.com/nbd-wtf/go-nostr v0.25.7 h1:DcGOSgKVr/L6w62tRtKeV2t46sRyFcq9pWcyIFkh0eM=
github.com/nbd-wtf/go-nostr v0.25.7/go.mod h1:bkffJI+x914sPQWum9ZRUn66D7NpDnAoWo1yICvj3/0=
github.com/nbd-wtf/nostr-sdk v0.0.2 h1:mZIeti+DOF0D1179q+NLL/h0LVMMOPRQAYpOuUrn5Zk=
github.com/nbd-wtf/nostr-sdk v0.0.2/go.mod h1:KQZOtzcrXBlVhpZYG1tw83ADIONNMMPjUU3ZAH5U2RY=
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
github.com/nbd-wtf/go-nostr v0.28.1 h1:XQi/lBsigBXHRm7IDBJE7SR9citCh9srgf8sA5iVW3A=
github.com/nbd-wtf/go-nostr v0.28.1/go.mod h1:OQ8sNLFJnsj17BdqZiLSmjJBIFTfDqckEYC3utS4qoY=
github.com/nbd-wtf/nostr-sdk v0.0.5 h1:rec+FcDizDVO0W25PX0lgYMXvP7zNNOgI3Fu9UCm4BY=
github.com/nbd-wtf/nostr-sdk v0.0.5/go.mod h1:iJJsikesCGLNFZ9dLqhLPDzdt924EagUmdQxT3w2Lmk=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
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=
@@ -95,44 +89,40 @@ 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.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/puzpuzpuz/xsync/v2 v2.5.1 h1:mVGYAvzDSu52+zaGyNjC+24Xw2bQi3kTr4QJ6N9pIIU=
github.com/puzpuzpuz/xsync/v2 v2.5.1/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2znR9qmR35kU=
github.com/puzpuzpuz/xsync/v3 v3.0.2 h1:3yESHrRFYr6xzkz61LLkvNiPFXxJEAABanTQpKbAaew=
github.com/puzpuzpuz/xsync/v3 v3.0.2/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM=
github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/urfave/cli/v2 v2.25.3 h1:VJkt6wvEBOoSjPFQvOkv6iWIrsJyCrKGtCtxXWwmGeY=
github.com/urfave/cli/v2 v2.25.3/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs=
github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 h1:5llv2sWeaMSnA3w2kS57ouQQ4pudlXrR0dCgw51QK9o=
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -141,10 +131,9 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -161,9 +150,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -3,24 +3,30 @@ package main
import (
"bufio"
"context"
"encoding/hex"
"fmt"
"net/url"
"os"
"strings"
"github.com/bgentry/speakeasy"
"github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/nip19"
"github.com/urfave/cli/v2"
)
const (
LINE_PROCESSING_ERROR = iota
BOLD_ON = "\033[1m"
BOLD_OFF = "\033[21m"
)
var log = func(msg string, args ...any) {
fmt.Fprintf(os.Stderr, msg, args...)
}
var stdout = fmt.Println
func isPiped() bool {
stat, _ := os.Stdin.Stat()
return stat.Mode()&os.ModeCharDevice == 0
@@ -59,6 +65,7 @@ func writeStdinLinesOrNothing(ch chan string) (hasStdinLines bool) {
// piped
go func() {
scanner := bufio.NewScanner(os.Stdin)
scanner.Buffer(make([]byte, 16*1024), 256*1024)
for scanner.Scan() {
ch <- strings.TrimSpace(scanner.Text())
}
@@ -90,23 +97,13 @@ func validateRelayURLs(wsurls []string) error {
return nil
}
func validate32BytesHex(target string) error {
if _, err := hex.DecodeString(target); err != nil {
return fmt.Errorf("target '%s' is not valid hex: %s", target, err)
}
if len(target) != 64 {
return fmt.Errorf("expected '%s' to be 64 characters (32 bytes), got %d", target, len(target))
}
if strings.ToLower(target) != target {
return fmt.Errorf("expected target to be all lowercase hex. try again with '%s'", strings.ToLower(target))
}
return nil
}
func connectToAllRelays(ctx context.Context, relayUrls []string) (*nostr.SimplePool, []*nostr.Relay) {
func connectToAllRelays(
ctx context.Context,
relayUrls []string,
opts ...nostr.PoolOption,
) (*nostr.SimplePool, []*nostr.Relay) {
relays := make([]*nostr.Relay, 0, len(relayUrls))
pool := nostr.NewSimplePool(ctx)
pool := nostr.NewSimplePool(ctx, opts...)
for _, url := range relayUrls {
log("connecting to %s... ", url)
if relay, err := pool.EnsureRelay(url); err == nil {
@@ -129,3 +126,33 @@ func exitIfLineProcessingError(c *cli.Context) {
os.Exit(123)
}
}
func gatherSecretKeyFromArguments(c *cli.Context) (string, error) {
sec := c.String("sec")
if c.Bool("prompt-sec") {
if isPiped() {
return "", fmt.Errorf("can't prompt for a secret key when processing data from a pipe, try again without --prompt-sec")
}
var err error
sec, err = speakeasy.FAsk(os.Stderr, "type your secret key as nsec or hex: ")
if err != nil {
return "", fmt.Errorf("failed to get secret key: %w", err)
}
}
if strings.HasPrefix(sec, "nsec1") {
_, hex, err := nip19.Decode(sec)
if err != nil {
return "", fmt.Errorf("invalid nsec: %w", err)
}
sec = hex.(string)
}
if len(sec) > 64 {
return "", fmt.Errorf("invalid secret key: too large")
}
sec = strings.Repeat("0", 64-len(sec)) + sec // left-pad
if ok := nostr.IsValid32ByteHex(sec); !ok {
return "", fmt.Errorf("invalid secret key")
}
return sec, nil
}

24
main.go
View File

@@ -1,15 +1,18 @@
package main
import (
"fmt"
"os"
"github.com/urfave/cli/v2"
)
var q int
var app = &cli.App{
Name: "nak",
Usage: "the nostr army knife command-line tool",
Name: "nak",
Suggest: true,
UseShortOptionHandling: true,
Usage: "the nostr army knife command-line tool",
Commands: []*cli.Command{
req,
count,
@@ -19,15 +22,20 @@ var app = &cli.App{
encode,
verify,
relay,
bunker,
},
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "silent",
Usage: "do not print logs and info messages to stderr",
Aliases: []string{"s"},
Name: "quiet",
Usage: "do not print logs and info messages to stderr, use -qq to also not print anything to stdout",
Count: &q,
Aliases: []string{"q"},
Action: func(ctx *cli.Context, b bool) error {
if b {
if q >= 1 {
log = func(msg string, args ...any) {}
if q >= 2 {
stdout = func(a ...any) (int, error) { return 0, nil }
}
}
return nil
},
@@ -37,7 +45,7 @@ var app = &cli.App{
func main() {
if err := app.Run(os.Args); err != nil {
fmt.Println(err)
stdout(err)
os.Exit(1)
}
}

View File

@@ -31,7 +31,7 @@ var relay = &cli.Command{
}
pretty, _ := json.MarshalIndent(info, "", " ")
fmt.Println(string(pretty))
stdout(string(pretty))
return nil
},
}

87
req.go
View File

@@ -4,8 +4,10 @@ import (
"encoding/json"
"fmt"
"os"
"strconv"
"strings"
"github.com/mailru/easyjson"
"github.com/nbd-wtf/go-nostr"
"github.com/urfave/cli/v2"
)
@@ -60,13 +62,18 @@ example:
Usage: "shortcut for --tag p=<value>",
Category: CATEGORY_FILTER_ATTRIBUTES,
},
&cli.IntFlag{
&cli.StringSliceFlag{
Name: "d",
Usage: "shortcut for --tag d=<value>",
Category: CATEGORY_FILTER_ATTRIBUTES,
},
&cli.StringFlag{
Name: "since",
Aliases: []string{"s"},
Usage: "only accept events newer than this (unix timestamp)",
Category: CATEGORY_FILTER_ATTRIBUTES,
},
&cli.IntFlag{
&cli.StringFlag{
Name: "until",
Aliases: []string{"u"},
Usage: "only accept events older than this (unix timestamp)",
@@ -83,14 +90,28 @@ example:
Usage: "a NIP-50 search query, use it only with relays that explicitly support it",
Category: CATEGORY_FILTER_ATTRIBUTES,
},
&cli.BoolFlag{
Name: "stream",
Usage: "keep the subscription open, printing all events as they are returned",
DefaultText: "false, will close on EOSE",
},
&cli.BoolFlag{
Name: "bare",
Usage: "when printing the filter, print just the filter, not enveloped in a [\"REQ\", ...] array",
},
&cli.BoolFlag{
Name: "stream",
Usage: "keep the subscription open, printing all events as they are returned",
DefaultText: "false, will close on EOSE",
Name: "auth",
Usage: "always perform NIP-42 \"AUTH\" when facing an \"auth-required: \" rejection and try again",
},
&cli.StringFlag{
Name: "sec",
Usage: "secret key to sign the AUTH challenge, as hex or nsec",
DefaultText: "the key '1'",
Value: "0000000000000000000000000000000000000000000000000000000000000001",
},
&cli.BoolFlag{
Name: "prompt-sec",
Usage: "prompt the user to paste a hex or nsec with which to sign the AUTH challenge",
},
},
ArgsUsage: "[relay...]",
@@ -99,7 +120,18 @@ example:
relayUrls := c.Args().Slice()
if len(relayUrls) > 0 {
var relays []*nostr.Relay
pool, relays = connectToAllRelays(c.Context, relayUrls)
pool, relays = connectToAllRelays(c.Context, relayUrls, nostr.WithAuthHandler(func(evt *nostr.Event) error {
if !c.Bool("auth") {
return fmt.Errorf("auth not authorized")
}
sec, err := gatherSecretKeyFromArguments(c)
if err != nil {
return err
}
pk, _ := nostr.GetPublicKey(sec)
log("performing auth as %s...\n", pk)
return evt.Sign(sec)
}))
if len(relays) == 0 {
log("failed to connect to any of the given relays.\n")
os.Exit(3)
@@ -108,12 +140,18 @@ example:
for i, relay := range relays {
relayUrls[i] = relay.URL
}
defer func() {
for _, relay := range relays {
relay.Close()
}
}()
}
for stdinFilter := range getStdinLinesOrBlank() {
filter := nostr.Filter{}
if stdinFilter != "" {
if err := json.Unmarshal([]byte(stdinFilter), &filter); err != nil {
if err := easyjson.Unmarshal([]byte(stdinFilter), &filter); err != nil {
lineProcessingError(c, "invalid filter '%s' received from stdin: %s", stdinFilter, err)
continue
}
@@ -146,6 +184,9 @@ example:
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)
@@ -158,13 +199,27 @@ example:
filter.Tags[tag[0]] = append(filter.Tags[tag[0]], tag[1])
}
if since := c.Int("since"); since != 0 {
ts := nostr.Timestamp(since)
filter.Since = &ts
if since := c.String("since"); since != "" {
if since == "now" {
ts := nostr.Now()
filter.Since = &ts
} else if i, err := strconv.Atoi(since); err == nil {
ts := nostr.Timestamp(i)
filter.Since = &ts
} else {
return fmt.Errorf("parse error: Invalid numeric literal %q", since)
}
}
if until := c.Int("until"); until != 0 {
ts := nostr.Timestamp(until)
filter.Until = &ts
if until := c.String("until"); until != "" {
if until == "now" {
ts := nostr.Now()
filter.Until = &ts
} else if i, err := strconv.Atoi(until); err == nil {
ts := nostr.Timestamp(i)
filter.Until = &ts
} else {
return fmt.Errorf("parse error: Invalid numeric literal %q", until)
}
}
if limit := c.Int("limit"); limit != 0 {
filter.Limit = limit
@@ -176,7 +231,7 @@ example:
fn = pool.SubMany
}
for ie := range fn(c.Context, relayUrls, nostr.Filters{filter}) {
fmt.Println(ie.Event)
stdout(ie.Event)
}
} else {
// no relays given, will just print the filter
@@ -184,11 +239,11 @@ example:
if c.Bool("bare") {
result = filter.String()
} else {
j, _ := json.Marshal([]any{"REQ", "nak", filter})
j, _ := json.Marshal(nostr.ReqEnvelope{SubscriptionID: "nak", Filters: nostr.Filters{filter}})
result = string(j)
}
fmt.Println(result)
stdout(result)
}
}