Compare commits

...

47 Commits

Author SHA1 Message Date
fiatjaf
b7a7e0504f --connect to use nip46 as a client to sign event and auth messages. 2024-02-06 00:58:26 -03:00
mattn
01e1f52a70 fix panic (#12)
* c.Args().Len() - 1 might be negative value

* fix getStdinLinesOrFirstArgument

* fix getStdinLinesOrFirstArgument
2024-02-03 10:03:32 -03:00
fiatjaf
0b9e861f90 fix: print prompt to stderr. 2024-02-02 14:13:11 -03:00
fiatjaf
bda18e035a fix reading hex secret key from input. 2024-02-02 14:11:58 -03:00
fiatjaf
0d46d48881 fix naddr. 2024-01-29 15:37:21 -03:00
fiatjaf
6f24112b5e support ncryptsec in all operations that require a private key and have a nice password prompt. 2024-01-29 10:56:58 -03:00
fiatjaf
f4921f1fe9 nak key: generate, public, encrypt, decrypt. 2024-01-25 08:21:09 -03:00
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
fiatjaf
f2f9dda33a actually this is the fix. 2023-11-24 21:19:10 -03:00
fiatjaf
53cb2c0490 event: fix handling of -k and kind in stdin event, and default to 1. 2023-11-24 21:08:13 -03:00
fiatjaf
4a3c7dc825 remove extra whitespace on usage strings. 2023-11-20 15:01:53 -03:00
fiatjaf
05f2275c9e nak relay 2023-11-20 15:00:50 -03:00
fiatjaf
082be94614 update go-nostr. 2023-11-19 07:20:52 -03:00
fiatjaf
15217f2466 fetch: fix handling of --relay tags. 2023-11-15 09:48:57 -03:00
fiatjaf
8fbfdc65c8 add --silent global option to remove the stderr logs. 2023-11-13 15:03:27 -03:00
fiatjaf
11fe6b5809 connect to relays once per call instead of in each iteration and fail early if no connection works. 2023-11-13 14:57:35 -03:00
fiatjaf
6a7a5eb26e fix bug with kind being set to zero and replaced silently. 2023-11-13 10:34:09 -03:00
18 changed files with 823 additions and 217 deletions

10
.gitignore vendored
View File

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

View File

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

View File

@@ -81,3 +81,7 @@ invalid .id, expected 05bd99d54cb835f427e0092c4275ee44c7ff51219eff417c19f70c9e2c
```shell ```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 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 `naddr1qqpkucttqy28wumn8ghj7un9d3shjtnwdaehgu3wvfnsz9nhwden5te0wfjkccte9ehx7um5wghxyctwvsq3gamnwvaz7tmjv4kxz7fwv3sk6atn9e5k7q3q80cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkwsxpqqqpmej2wctpn`.

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

View File

@@ -3,7 +3,6 @@ package main
import ( import (
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"fmt"
"strings" "strings"
"github.com/nbd-wtf/go-nostr" "github.com/nbd-wtf/go-nostr"
@@ -34,7 +33,7 @@ var decode = &cli.Command{
}, },
ArgsUsage: "<npub | nprofile | nip05 | nevent | naddr | nsec>", ArgsUsage: "<npub | nprofile | nip05 | nevent | naddr | nsec>",
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
for input := range getStdinLinesOrFirstArgument(c) { for input := range getStdinLinesOrFirstArgument(c.Args().First()) {
if strings.HasPrefix(input, "nostr:") { if strings.HasPrefix(input, "nostr:") {
input = input[6:] input = input[6:]
} }
@@ -68,7 +67,7 @@ var decode = &cli.Command{
continue continue
} }
fmt.Println(decodeResult.JSON()) stdout(decodeResult.JSON())
} }

View File

@@ -3,6 +3,7 @@ package main
import ( import (
"fmt" "fmt"
"github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/nip19" "github.com/nbd-wtf/go-nostr/nip19"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
@@ -28,14 +29,14 @@ var encode = &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",
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
for target := range getStdinLinesOrFirstArgument(c) { for target := range getStdinLinesOrFirstArgument(c.Args().First()) {
if err := validate32BytesHex(target); err != nil { if ok := nostr.IsValidPublicKey(target); !ok {
lineProcessingError(c, "invalid public key: %s", target, err) lineProcessingError(c, "invalid public key: %s", target)
continue continue
} }
if npub, err := nip19.EncodePublicKey(target); err == nil { if npub, err := nip19.EncodePublicKey(target); err == nil {
fmt.Println(npub) stdout(npub)
} else { } else {
return err return err
} }
@@ -49,14 +50,14 @@ 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",
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
for target := range getStdinLinesOrFirstArgument(c) { for target := range getStdinLinesOrFirstArgument(c.Args().First()) {
if err := validate32BytesHex(target); err != nil { if ok := nostr.IsValid32ByteHex(target); !ok {
lineProcessingError(c, "invalid private key: %s", target, err) lineProcessingError(c, "invalid private key: %s", target)
continue continue
} }
if npub, err := nip19.EncodePrivateKey(target); err == nil { if npub, err := nip19.EncodePrivateKey(target); err == nil {
fmt.Println(npub) stdout(npub)
} else { } else {
return err return err
} }
@@ -77,9 +78,9 @@ var encode = &cli.Command{
}, },
}, },
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
for target := range getStdinLinesOrFirstArgument(c) { for target := range getStdinLinesOrFirstArgument(c.Args().First()) {
if err := validate32BytesHex(target); err != nil { if ok := nostr.IsValid32ByteHex(target); !ok {
lineProcessingError(c, "invalid public key: %s", target, err) lineProcessingError(c, "invalid public key: %s", target)
continue continue
} }
@@ -89,7 +90,7 @@ var encode = &cli.Command{
} }
if npub, err := nip19.EncodeProfile(target, relays); err == nil { if npub, err := nip19.EncodeProfile(target, relays); err == nil {
fmt.Println(npub) stdout(npub)
} else { } else {
return err return err
} }
@@ -114,16 +115,16 @@ var encode = &cli.Command{
}, },
}, },
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
for target := range getStdinLinesOrFirstArgument(c) { for target := range getStdinLinesOrFirstArgument(c.Args().First()) {
if err := validate32BytesHex(target); err != nil { if ok := nostr.IsValid32ByteHex(target); !ok {
lineProcessingError(c, "invalid event id: %s", target, err) lineProcessingError(c, "invalid event id: %s", target)
continue continue
} }
author := c.String("author") author := c.String("author")
if author != "" { if author != "" {
if err := validate32BytesHex(author); err != nil { if ok := nostr.IsValidPublicKey(author); !ok {
return err 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 { if npub, err := nip19.EncodeEvent(target, relays, author); err == nil {
fmt.Println(npub) stdout(npub)
} else { } else {
return err return err
} }
@@ -174,8 +175,8 @@ var encode = &cli.Command{
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
for d := range getStdinLinesOrBlank() { for d := range getStdinLinesOrBlank() {
pubkey := c.String("pubkey") pubkey := c.String("pubkey")
if err := validate32BytesHex(pubkey); err != nil { if ok := nostr.IsValidPublicKey(pubkey); !ok {
return err return fmt.Errorf("invalid 'pubkey'")
} }
kind := c.Int("kind") kind := c.Int("kind")
@@ -197,7 +198,7 @@ var encode = &cli.Command{
} }
if npub, err := nip19.EncodeEntity(pubkey, kind, d, relays); err == nil { if npub, err := nip19.EncodeEntity(pubkey, kind, d, relays); err == nil {
fmt.Println(npub) stdout(npub)
} else { } else {
return err return err
} }
@@ -211,14 +212,14 @@ var encode = &cli.Command{
Name: "note", Name: "note",
Usage: "generate note1 event codes (not recommended)", Usage: "generate note1 event codes (not recommended)",
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
for target := range getStdinLinesOrFirstArgument(c) { for target := range getStdinLinesOrFirstArgument(c.Args().First()) {
if err := validate32BytesHex(target); err != nil { if ok := nostr.IsValid32ByteHex(target); !ok {
lineProcessingError(c, "invalid event id: %s", target, err) lineProcessingError(c, "invalid event id: %s", target)
continue continue
} }
if note, err := nip19.EncodeNote(target); err == nil { if note, err := nip19.EncodeNote(target); err == nil {
fmt.Println(note) stdout(note)
} else { } else {
return err return err
} }

178
event.go
View File

@@ -9,12 +9,12 @@ import (
"strings" "strings"
"time" "time"
"github.com/bgentry/speakeasy"
"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/nip19" "github.com/nbd-wtf/go-nostr/nip19"
"github.com/nbd-wtf/go-nostr/nson" "github.com/nbd-wtf/go-nostr/nson"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"golang.org/x/exp/slices"
) )
const CATEGORY_EVENT_FIELDS = "EVENT FIELDS" const CATEGORY_EVENT_FIELDS = "EVENT FIELDS"
@@ -32,8 +32,7 @@ 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'`,
`,
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "sec", Name: "sec",
@@ -45,10 +44,22 @@ example:
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",
}, },
&cli.StringFlag{
Name: "connect",
Usage: "sign event using NIP-46, expects a bunker://... URL",
},
&cli.BoolFlag{ &cli.BoolFlag{
Name: "envelope", Name: "envelope",
Usage: "print the event enveloped in a [\"EVENT\", ...] message ready to be sent to a relay", 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{ &cli.BoolFlag{
Name: "nson", Name: "nson",
Usage: "encode the event using NSON", Usage: "encode the event using NSON",
@@ -85,6 +96,11 @@ example:
Usage: "shortcut for --tag p=<value>", Usage: "shortcut for --tag p=<value>",
Category: CATEGORY_EVENT_FIELDS, Category: CATEGORY_EVENT_FIELDS,
}, },
&cli.StringSliceFlag{
Name: "d",
Usage: "shortcut for --tag d=<value>",
Category: CATEGORY_EVENT_FIELDS,
},
&cli.StringFlag{ &cli.StringFlag{
Name: "created-at", Name: "created-at",
Aliases: []string{"time", "ts"}, Aliases: []string{"time", "ts"},
@@ -96,51 +112,50 @@ example:
}, },
ArgsUsage: "[relay...]", ArgsUsage: "[relay...]",
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
// gather the secret key first // try to connect to the relays here
sec := c.String("sec") var relays []*nostr.Relay
if c.Bool("prompt-sec") { if relayUrls := c.Args().Slice(); len(relayUrls) > 0 {
if isPiped() { _, relays = connectToAllRelays(c.Context, relayUrls)
return fmt.Errorf("can't prompt for a secret key when processing data from a pipe, try again without --prompt-sec") if len(relays) == 0 {
} log("failed to connect to any of the given relays.\n")
var err error os.Exit(3)
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) defer func() {
if err != nil { for _, relay := range relays {
return fmt.Errorf("invalid nsec: %w", err) relay.Close()
} }
sec = hex.(string) }()
}
if len(sec) > 64 { sec, bunker, err := gatherSecretKeyOrBunkerFromArguments(c)
return fmt.Errorf("invalid secret key: too large") if err != nil {
} return err
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 // then process input and generate events
for stdinEvent := range getStdinLinesOrBlank() { for stdinEvent := range getStdinLinesOrBlank() {
evt := nostr.Event{ evt := nostr.Event{
Tags: make(nostr.Tags, 0, 3), Tags: make(nostr.Tags, 0, 3),
} }
kindWasSupplied := false
mustRehashAndResign := false mustRehashAndResign := false
if stdinEvent != "" { 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) lineProcessingError(c, "invalid event received from stdin: %s", err)
continue continue
} }
kindWasSupplied = strings.Contains(stdinEvent, `"kind"`)
} }
if kind := c.Int("kind"); kind != 0 { if kind := c.Int("kind"); slices.Contains(c.FlagNames(), "kind") {
evt.Kind = kind evt.Kind = kind
mustRehashAndResign = true mustRehashAndResign = true
} else if evt.Kind == 0 { } else if !kindWasSupplied {
evt.Kind = 1 evt.Kind = 1
mustRehashAndResign = true mustRehashAndResign = true
} }
@@ -156,22 +171,27 @@ example:
tags := make(nostr.Tags, 0, 5) tags := make(nostr.Tags, 0, 5)
for _, tagFlag := range c.StringSlice("tag") { for _, tagFlag := range c.StringSlice("tag") {
// tags are in the format key=value // tags are in the format key=value
spl := strings.Split(tagFlag, "=") tagName, tagValue, found := strings.Cut(tagFlag, "=")
if len(spl) == 2 && len(spl[0]) > 0 { tag := []string{tagName}
tag := nostr.Tag{spl[0]} if found {
// tags may also contain extra elements separated with a ";" // tags may also contain extra elements separated with a ";"
spl2 := strings.Split(spl[1], ";") tagValues := strings.Split(tagValue, ";")
tag = append(tag, spl2...) tag = append(tag, tagValues...)
// ~ // ~
tags = append(tags, tag) tags = tags.AppendUnique(tag)
} }
} }
for _, etag := range c.StringSlice("e") { for _, etag := range c.StringSlice("e") {
tags = append(tags, []string{"e", etag}) tags = tags.AppendUnique([]string{"e", etag})
mustRehashAndResign = true mustRehashAndResign = true
} }
for _, ptag := range c.StringSlice("p") { 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 mustRehashAndResign = true
} }
if len(tags) > 0 { if len(tags) > 0 {
@@ -198,40 +218,78 @@ example:
} }
if evt.Sig == "" || mustRehashAndResign { if evt.Sig == "" || mustRehashAndResign {
if err := evt.Sign(sec); err != nil { if bunker != nil {
if err := bunker.SignEvent(c.Context, &evt); err != nil {
return fmt.Errorf("failed to sign with bunker: %w", err)
}
} else if err := evt.Sign(sec); err != nil {
return fmt.Errorf("error signing with provided key: %w", err) return fmt.Errorf("error signing with provided key: %w", err)
} }
} }
relays := c.Args().Slice() // 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 { if len(relays) > 0 {
fmt.Println(evt.String()) os.Stdout.Sync()
for _, url := range relays { for _, relay := range relays {
fmt.Fprintf(os.Stderr, "publishing to %s... ", url) publish:
if relay, err := nostr.RelayConnect(c.Context, url); err != nil { log("publishing to %s... ", relay.URL)
fmt.Fprintf(os.Stderr, "failed to connect: %s\n", err) ctx, cancel := context.WithTimeout(c.Context, 10*time.Second)
} else { defer cancel()
ctx, cancel := context.WithTimeout(c.Context, 10*time.Second)
defer cancel() err := relay.Publish(ctx, evt)
if status, err := relay.Publish(ctx, evt); err != nil { if err == nil {
fmt.Fprintf(os.Stderr, "failed: %s\n", err) // 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 != "" || bunker != nil) && doAuth {
// if the relay is requesting auth and we can auth, let's do it
var pk string
if bunker != nil {
pk, err = bunker.GetPublicKey(c.Context)
if err != nil {
return fmt.Errorf("failed to get public key from bunker: %w", err)
}
} else { } else {
fmt.Fprintf(os.Stderr, "%s.\n", status) pk, _ = nostr.GetPublicKey(sec)
}
log("performing auth as %s... ", pk)
if err := relay.Auth(c.Context, func(evt *nostr.Event) error {
if bunker != nil {
return bunker.SignEvent(c.Context, evt)
}
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("auth error: %s. ", err)
} }
} }
log("failed: %s\n", err)
} }
} else { if len(successRelays) > 0 && c.Bool("nevent") {
var result string nevent, _ := nip19.EncodeEvent(evt.ID, successRelays, evt.PubKey)
if c.Bool("envelope") { log(nevent + "\n")
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)
} }
fmt.Println(result)
} }
} }

View File

@@ -7,9 +7,9 @@ func ExampleEventBasic() {
} }
func ExampleEventComplex() { 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: // 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() { func ExampleReq() {

View File

@@ -1,8 +1,6 @@
package main package main
import ( import (
"fmt"
"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"
@@ -24,7 +22,16 @@ var fetch = &cli.Command{
}, },
ArgsUsage: "[nip19code]", ArgsUsage: "[nip19code]",
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
for code := range getStdinLinesOrFirstArgument(c) { 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.Args().First()) {
filter := nostr.Filter{} filter := nostr.Filter{}
prefix, value, err := nip19.Decode(code) prefix, value, err := nip19.Decode(code)
@@ -46,20 +53,20 @@ var fetch = &cli.Command{
if v.Author != "" { if v.Author != "" {
authorHint = v.Author authorHint = v.Author
} }
relays = v.Relays relays = append(relays, v.Relays...)
case "naddr": case "naddr":
v := value.(nostr.EntityPointer) v := value.(nostr.EntityPointer)
filter.Tags = nostr.TagMap{"d": []string{v.Identifier}} filter.Tags = nostr.TagMap{"d": []string{v.Identifier}}
filter.Kinds = append(filter.Kinds, v.Kind) filter.Kinds = append(filter.Kinds, v.Kind)
filter.Authors = append(filter.Authors, v.PublicKey) filter.Authors = append(filter.Authors, v.PublicKey)
authorHint = v.PublicKey authorHint = v.PublicKey
relays = v.Relays relays = append(relays, v.Relays...)
case "nprofile": case "nprofile":
v := value.(nostr.ProfilePointer) v := value.(nostr.ProfilePointer)
filter.Authors = append(filter.Authors, v.PublicKey) filter.Authors = append(filter.Authors, v.PublicKey)
filter.Kinds = append(filter.Kinds, 0) filter.Kinds = append(filter.Kinds, 0)
authorHint = v.PublicKey authorHint = v.PublicKey
relays = v.Relays relays = append(relays, v.Relays...)
case "npub": case "npub":
v := value.(string) v := value.(string)
filter.Authors = append(filter.Authors, v) filter.Authors = append(filter.Authors, v)
@@ -67,10 +74,10 @@ var fetch = &cli.Command{
authorHint = v authorHint = v
} }
pool := nostr.NewSimplePool(c.Context)
if authorHint != "" { if authorHint != "" {
relayList := sdk.FetchRelaysForPubkey(c.Context, pool, 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 { for _, relayListItem := range relayList {
if relayListItem.Outbox { if relayListItem.Outbox {
relays = append(relays, relayListItem.URL) relays = append(relays, relayListItem.URL)
@@ -84,7 +91,7 @@ var fetch = &cli.Command{
} }
for ie := range pool.SubManyEose(c.Context, relays, nostr.Filters{filter}) { for ie := range pool.SubManyEose(c.Context, relays, nostr.Filters{filter}) {
fmt.Println(ie.Event) stdout(ie.Event)
} }
} }

32
go.mod
View File

@@ -5,36 +5,36 @@ go 1.21
toolchain go1.21.0 toolchain go1.21.0
require ( require (
github.com/bgentry/speakeasy v0.1.0 github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e
github.com/fatih/color v1.16.0
github.com/mailru/easyjson v0.7.7 github.com/mailru/easyjson v0.7.7
github.com/nbd-wtf/go-nostr v0.25.3 github.com/manifoldco/promptui v0.9.0
github.com/nbd-wtf/nostr-sdk v0.0.2 github.com/nbd-wtf/go-nostr v0.28.4
github.com/urfave/cli/v2 v2.25.3 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 ( require (
github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.2 // 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.0.2 // indirect github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // 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/crypto/blake256 v1.0.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
github.com/dgraph-io/ristretto v0.1.1 // indirect github.com/fiatjaf/eventstore v0.2.16 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/fiatjaf/eventstore v0.1.0 // 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.2.0 // indirect github.com/gobwas/ws v1.3.1 // indirect
github.com/golang/glog v1.0.0 // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/puzpuzpuz/xsync/v2 v2.5.1 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/puzpuzpuz/xsync/v3 v3.0.2 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // 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/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 github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 // indirect golang.org/x/crypto v0.7.0 // indirect
golang.org/x/sys v0.8.0 // indirect golang.org/x/sys v0.14.0 // indirect
) )

86
go.sum
View File

@@ -1,6 +1,4 @@
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
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=
@@ -25,9 +23,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/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.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 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 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 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= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -41,25 +42,18 @@ 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 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= 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/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218=
github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= github.com/fiatjaf/eventstore v0.2.16 h1:NR64mnyUT5nJR8Sj2AwJTd1Hqs5kKJcCFO21ggUkvWg=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/fiatjaf/eventstore v0.2.16/go.mod h1:rUc1KhVufVmC+HUOiuPweGAcvG6lEOQCkRCn2Xn5VRA=
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/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=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= 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 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.2.0 h1:u0p9s3xLYpZCA1z5JgCkMeB34CKCMMQbM+G8Ii7YD0I= github.com/gobwas/ws v1.3.1 h1:Qi34dfLMWJbiKaNbDVzM9x27nZBjmkaW6i4+Ku+pGVU=
github.com/gobwas/ws v1.2.0/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY= github.com/gobwas/ws v1.3.1/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/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=
@@ -80,10 +74,17 @@ 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/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 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/nbd-wtf/go-nostr v0.25.3 h1:RPPh4cOosw0OZi5KG627pZ3GlKxiKsjARluzen/mB9g= github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
github.com/nbd-wtf/go-nostr v0.25.3/go.mod h1:bkffJI+x914sPQWum9ZRUn66D7NpDnAoWo1yICvj3/0= github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
github.com/nbd-wtf/nostr-sdk v0.0.2 h1:mZIeti+DOF0D1179q+NLL/h0LVMMOPRQAYpOuUrn5Zk= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/nbd-wtf/nostr-sdk v0.0.2/go.mod h1:KQZOtzcrXBlVhpZYG1tw83ADIONNMMPjUU3ZAH5U2RY= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/nbd-wtf/go-nostr v0.28.4 h1:chGBpdCQvM9aInf/vVDishY8GHapgqFc/RLl2WDnHQM=
github.com/nbd-wtf/go-nostr v0.28.4/go.mod h1:l9NRRaHPN+QwkqrjNKhnfYjQ0+nKP1xZrVxePPGUs+A=
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/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=
@@ -93,44 +94,42 @@ 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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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/v3 v3.0.2 h1:3yESHrRFYr6xzkz61LLkvNiPFXxJEAABanTQpKbAaew=
github.com/puzpuzpuz/xsync/v2 v2.5.1/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2znR9qmR35kU= 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 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 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/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.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/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.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM=
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= 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 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= 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/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/urfave/cli/v2 v2.25.3 h1:VJkt6wvEBOoSjPFQvOkv6iWIrsJyCrKGtCtxXWwmGeY= github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/urfave/cli/v2 v2.25.3/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc= 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 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= 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-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 h1:5llv2sWeaMSnA3w2kS57ouQQ4pudlXrR0dCgw51QK9o= golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
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-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= 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/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-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-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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -139,10 +138,10 @@ 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-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-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-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.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -159,9 +158,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/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/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.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.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/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.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

@@ -9,13 +9,28 @@ import (
"os" "os"
"strings" "strings"
"github.com/chzyer/readline"
"github.com/fatih/color"
"github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/nip19"
"github.com/nbd-wtf/go-nostr/nip46"
"github.com/nbd-wtf/go-nostr/nip49"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
const ( const (
LINE_PROCESSING_ERROR = iota 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 { func isPiped() bool {
stat, _ := os.Stdin.Stat() stat, _ := os.Stdin.Stat()
return stat.Mode()&os.ModeCharDevice == 0 return stat.Mode()&os.ModeCharDevice == 0
@@ -33,12 +48,11 @@ func getStdinLinesOrBlank() chan string {
} }
} }
func getStdinLinesOrFirstArgument(c *cli.Context) chan string { func getStdinLinesOrFirstArgument(arg string) chan string {
// try the first argument // try the first argument
target := c.Args().First() if arg != "" {
if target != "" {
single := make(chan string, 1) single := make(chan string, 1)
single <- target single <- arg
close(single) close(single)
return single return single
} }
@@ -54,6 +68,7 @@ func writeStdinLinesOrNothing(ch chan string) (hasStdinLines bool) {
// piped // piped
go func() { go func() {
scanner := bufio.NewScanner(os.Stdin) scanner := bufio.NewScanner(os.Stdin)
scanner.Buffer(make([]byte, 16*1024), 256*1024)
for scanner.Scan() { for scanner.Scan() {
ch <- strings.TrimSpace(scanner.Text()) ch <- strings.TrimSpace(scanner.Text())
} }
@@ -85,23 +100,28 @@ func validateRelayURLs(wsurls []string) error {
return nil return nil
} }
func validate32BytesHex(target string) error { func connectToAllRelays(
if _, err := hex.DecodeString(target); err != nil { ctx context.Context,
return fmt.Errorf("target '%s' is not valid hex: %s", target, err) relayUrls []string,
opts ...nostr.PoolOption,
) (*nostr.SimplePool, []*nostr.Relay) {
relays := make([]*nostr.Relay, 0, len(relayUrls))
pool := nostr.NewSimplePool(ctx, opts...)
for _, url := range relayUrls {
log("connecting to %s... ", url)
if relay, err := pool.EnsureRelay(url); err == nil {
relays = append(relays, relay)
log("ok.\n")
} else {
log(err.Error() + "\n")
}
} }
if len(target) != 64 { return pool, relays
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 lineProcessingError(c *cli.Context, msg string, args ...any) { func lineProcessingError(c *cli.Context, msg string, args ...any) {
c.Context = context.WithValue(c.Context, LINE_PROCESSING_ERROR, true) c.Context = context.WithValue(c.Context, LINE_PROCESSING_ERROR, true)
fmt.Fprintf(os.Stderr, msg+"\n", args...) log(msg+"\n", args...)
} }
func exitIfLineProcessingError(c *cli.Context) { func exitIfLineProcessingError(c *cli.Context) {
@@ -109,3 +129,100 @@ func exitIfLineProcessingError(c *cli.Context) {
os.Exit(123) os.Exit(123)
} }
} }
func gatherSecretKeyOrBunkerFromArguments(c *cli.Context) (string, *nip46.BunkerClient, error) {
var err error
if bunkerURL := c.String("connect"); bunkerURL != "" {
clientKey := nostr.GeneratePrivateKey()
bunker, err := nip46.ConnectBunker(c.Context, clientKey, bunkerURL, nil)
return "", bunker, err
}
sec := c.String("sec")
if c.Bool("prompt-sec") {
if isPiped() {
return "", nil, fmt.Errorf("can't prompt for a secret key when processing data from a pipe, try again without --prompt-sec")
}
sec, err = askPassword("type your secret key as ncryptsec, nsec or hex: ", nil)
if err != nil {
return "", nil, fmt.Errorf("failed to get secret key: %w", err)
}
}
if strings.HasPrefix(sec, "ncryptsec1") {
sec, err = promptDecrypt(sec)
if err != nil {
return "", nil, fmt.Errorf("failed to decrypt: %w", err)
}
} else if bsec, err := hex.DecodeString(strings.Repeat("0", 64-len(sec)) + sec); err == nil {
sec = hex.EncodeToString(bsec)
} else if prefix, hexvalue, err := nip19.Decode(sec); err != nil {
return "", nil, fmt.Errorf("invalid nsec: %w", err)
} else if prefix == "nsec" {
sec = hexvalue.(string)
}
if ok := nostr.IsValid32ByteHex(sec); !ok {
return "", nil, fmt.Errorf("invalid secret key")
}
return sec, nil, nil
}
func promptDecrypt(ncryptsec1 string) (string, error) {
for i := 1; i < 4; i++ {
var attemptStr string
if i > 1 {
attemptStr = fmt.Sprintf(" [%d/3]", i)
}
password, err := askPassword("type the password to decrypt your secret key"+attemptStr+": ", nil)
if err != nil {
return "", err
}
sec, err := nip49.Decrypt(ncryptsec1, password)
if err != nil {
continue
}
return sec, nil
}
return "", fmt.Errorf("couldn't decrypt private key")
}
func ask(msg string, defaultValue string, shouldAskAgain func(answer string) bool) (string, error) {
return _ask(&readline.Config{
Stdout: os.Stderr,
Prompt: color.YellowString(msg),
InterruptPrompt: "^C",
DisableAutoSaveHistory: true,
}, msg, defaultValue, shouldAskAgain)
}
func askPassword(msg string, shouldAskAgain func(answer string) bool) (string, error) {
config := &readline.Config{
Stdout: os.Stderr,
Prompt: color.YellowString(msg),
InterruptPrompt: "^C",
DisableAutoSaveHistory: true,
EnableMask: true,
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)
if err != nil {
return "", err
}
rl.WriteStdin([]byte(defaultValue))
for {
answer, err := rl.Readline()
if err != nil {
return "", err
}
answer = strings.TrimSpace(strings.ToLower(answer))
if shouldAskAgain != nil && shouldAskAgain(answer) {
continue
}
return answer, err
}
}

149
key.go Normal file
View File

@@ -0,0 +1,149 @@
package main
import (
"fmt"
"strings"
"github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/nip19"
"github.com/nbd-wtf/go-nostr/nip49"
"github.com/urfave/cli/v2"
)
var key = &cli.Command{
Name: "key",
Usage: "operations on secret keys: generate, derive, encrypt, decrypt.",
Description: ``,
Subcommands: []*cli.Command{
generate,
public,
encrypt,
decrypt,
},
}
var generate = &cli.Command{
Name: "generate",
Usage: "generates a secret key",
Description: ``,
Action: func(c *cli.Context) error {
sec := nostr.GeneratePrivateKey()
stdout(sec)
return nil
},
}
var public = &cli.Command{
Name: "public",
Usage: "computes a public key from a secret key",
Description: ``,
ArgsUsage: "[secret]",
Action: func(c *cli.Context) error {
for sec := range getSecretKeyFromStdinLinesOrFirstArgument(c, c.Args().First()) {
pubkey, err := nostr.GetPublicKey(sec)
if err != nil {
lineProcessingError(c, "failed to derive public key: %s", err)
continue
}
stdout(pubkey)
}
return nil
},
}
var encrypt = &cli.Command{
Name: "encrypt",
Usage: "encrypts a secret key and prints an ncryptsec code",
Description: `uses the NIP-49 standard.`,
ArgsUsage: "<secret> <password>",
Flags: []cli.Flag{
&cli.IntFlag{
Name: "logn",
Usage: "the bigger the number the harder it will be to bruteforce the password",
Value: 16,
DefaultText: "16",
},
},
Action: func(c *cli.Context) error {
var content string
var password string
switch c.Args().Len() {
case 1:
content = ""
password = c.Args().Get(0)
case 2:
content = c.Args().Get(0)
password = c.Args().Get(1)
}
if password == "" {
return fmt.Errorf("no password given")
}
for sec := range getSecretKeyFromStdinLinesOrFirstArgument(c, content) {
ncryptsec, err := nip49.Encrypt(sec, password, uint8(c.Int("logn")), 0x02)
if err != nil {
lineProcessingError(c, "failed to encrypt: %s", err)
continue
}
stdout(ncryptsec)
}
return nil
},
}
var decrypt = &cli.Command{
Name: "decrypt",
Usage: "takes an ncrypsec and a password and decrypts it into an nsec",
Description: `uses the NIP-49 standard.`,
ArgsUsage: "<ncryptsec-code> <password>",
Action: func(c *cli.Context) error {
var content string
var password string
switch c.Args().Len() {
case 1:
content = ""
password = c.Args().Get(0)
case 2:
content = c.Args().Get(0)
password = c.Args().Get(1)
}
if password == "" {
return fmt.Errorf("no password given")
}
for ncryptsec := range getStdinLinesOrFirstArgument(content) {
sec, err := nip49.Decrypt(ncryptsec, password)
if err != nil {
lineProcessingError(c, "failed to decrypt: %s", err)
continue
}
nsec, _ := nip19.EncodePrivateKey(sec)
stdout(nsec)
}
return nil
},
}
func getSecretKeyFromStdinLinesOrFirstArgument(c *cli.Context, content string) chan string {
ch := make(chan string)
go func() {
for sec := range getStdinLinesOrFirstArgument(content) {
if sec == "" {
continue
}
if strings.HasPrefix(sec, "nsec1") {
_, data, err := nip19.Decode(sec)
if err != nil {
lineProcessingError(c, "invalid nsec code: %s", err)
continue
}
sec = data.(string)
}
if !nostr.IsValid32ByteHex(sec) {
lineProcessingError(c, "invalid hex secret key")
continue
}
ch <- sec
}
close(ch)
}()
return ch
}

31
main.go
View File

@@ -1,15 +1,18 @@
package main package main
import ( import (
"fmt"
"os" "os"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
var q int
var app = &cli.App{ var app = &cli.App{
Name: "nak", Name: "nak",
Usage: "the nostr army knife command-line tool", Suggest: true,
UseShortOptionHandling: true,
Usage: "the nostr army knife command-line tool",
Commands: []*cli.Command{ Commands: []*cli.Command{
req, req,
count, count,
@@ -17,13 +20,33 @@ var app = &cli.App{
event, event,
decode, decode,
encode, encode,
key,
verify, verify,
relay,
bunker,
},
Flags: []cli.Flag{
&cli.BoolFlag{
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 q >= 1 {
log = func(msg string, args ...any) {}
if q >= 2 {
stdout = func(a ...any) (int, error) { return 0, nil }
}
}
return nil
},
},
}, },
} }
func main() { func main() {
if err := app.Run(os.Args); err != nil { if err := app.Run(os.Args); err != nil {
fmt.Println(err) stdout(err)
os.Exit(1) os.Exit(1)
} }
} }

37
relay.go Normal file
View File

@@ -0,0 +1,37 @@
package main
import (
"encoding/json"
"fmt"
"strings"
"github.com/nbd-wtf/go-nostr/nip11"
"github.com/urfave/cli/v2"
)
var relay = &cli.Command{
Name: "relay",
Usage: "gets the relay information document for the given relay, as JSON",
Description: `example:
nak relay nostr.wine`,
ArgsUsage: "<relay-url>",
Action: func(c *cli.Context) error {
url := c.Args().First()
if url == "" {
return fmt.Errorf("specify the <relay-url>")
}
if !strings.HasPrefix(url, "wss://") && !strings.HasPrefix(url, "ws://") {
url = "wss://" + url
}
info, err := nip11.Fetch(c.Context, url)
if err != nil {
return fmt.Errorf("failed to fetch '%s' information document: %w", url, err)
}
pretty, _ := json.MarshalIndent(info, "", " ")
stdout(string(pretty))
return nil
},
}

130
req.go
View File

@@ -3,8 +3,11 @@ package main
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"os"
"strconv"
"strings" "strings"
"github.com/mailru/easyjson"
"github.com/nbd-wtf/go-nostr" "github.com/nbd-wtf/go-nostr"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
@@ -23,8 +26,7 @@ example:
it can also take a filter from stdin, optionally modify it with flags and send it to specific relays (or just print it). it can also take a filter from stdin, optionally modify it with flags and send it to specific relays (or just print it).
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{ Flags: []cli.Flag{
&cli.StringSliceFlag{ &cli.StringSliceFlag{
Name: "author", Name: "author",
@@ -60,13 +62,18 @@ example:
Usage: "shortcut for --tag p=<value>", Usage: "shortcut for --tag p=<value>",
Category: CATEGORY_FILTER_ATTRIBUTES, Category: CATEGORY_FILTER_ATTRIBUTES,
}, },
&cli.IntFlag{ &cli.StringSliceFlag{
Name: "d",
Usage: "shortcut for --tag d=<value>",
Category: CATEGORY_FILTER_ATTRIBUTES,
},
&cli.StringFlag{
Name: "since", Name: "since",
Aliases: []string{"s"}, Aliases: []string{"s"},
Usage: "only accept events newer than this (unix timestamp)", Usage: "only accept events newer than this (unix timestamp)",
Category: CATEGORY_FILTER_ATTRIBUTES, Category: CATEGORY_FILTER_ATTRIBUTES,
}, },
&cli.IntFlag{ &cli.StringFlag{
Name: "until", Name: "until",
Aliases: []string{"u"}, Aliases: []string{"u"},
Usage: "only accept events older than this (unix timestamp)", Usage: "only accept events older than this (unix timestamp)",
@@ -83,22 +90,86 @@ example:
Usage: "a NIP-50 search query, use it only with relays that explicitly support it", Usage: "a NIP-50 search query, use it only with relays that explicitly support it",
Category: CATEGORY_FILTER_ATTRIBUTES, Category: CATEGORY_FILTER_ATTRIBUTES,
}, },
&cli.BoolFlag{
Name: "bare",
Usage: "when printing the filter, print just the filter, not enveloped in a [\"REQ\", ...] array",
},
&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: "bare",
Usage: "when printing the filter, print just the filter, not enveloped in a [\"REQ\", ...] array",
},
&cli.BoolFlag{
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",
},
&cli.StringFlag{
Name: "connect",
Usage: "sign AUTH using NIP-46, expects a bunker://... URL",
},
}, },
ArgsUsage: "[relay...]", ArgsUsage: "[relay...]",
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
var pool *nostr.SimplePool
relayUrls := c.Args().Slice()
if len(relayUrls) > 0 {
var relays []*nostr.Relay
pool, relays = connectToAllRelays(c.Context, relayUrls, nostr.WithAuthHandler(func(evt *nostr.Event) error {
if !c.Bool("auth") {
return fmt.Errorf("auth not authorized")
}
sec, bunker, err := gatherSecretKeyOrBunkerFromArguments(c)
if err != nil {
return err
}
var pk string
if bunker != nil {
pk, err = bunker.GetPublicKey(c.Context)
if err != nil {
return fmt.Errorf("failed to get public key from bunker: %w", err)
}
} else {
pk, _ = nostr.GetPublicKey(sec)
}
log("performing auth as %s...\n", pk)
if bunker != nil {
return bunker.SignEvent(c.Context, evt)
} else {
return evt.Sign(sec)
}
}))
if len(relays) == 0 {
log("failed to connect to any of the given relays.\n")
os.Exit(3)
}
relayUrls = make([]string, len(relays))
for i, relay := range relays {
relayUrls[i] = relay.URL
}
defer func() {
for _, relay := range relays {
relay.Close()
}
}()
}
for stdinFilter := range getStdinLinesOrBlank() { for stdinFilter := range getStdinLinesOrBlank() {
filter := nostr.Filter{} filter := nostr.Filter{}
if stdinFilter != "" { 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) lineProcessingError(c, "invalid filter '%s' received from stdin: %s", stdinFilter, err)
continue continue
} }
@@ -131,6 +202,9 @@ example:
for _, ptag := range c.StringSlice("p") { for _, ptag := range c.StringSlice("p") {
tags = append(tags, []string{"p", ptag}) 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 { if len(tags) > 0 && filter.Tags == nil {
filter.Tags = make(nostr.TagMap) filter.Tags = make(nostr.TagMap)
@@ -143,27 +217,39 @@ example:
filter.Tags[tag[0]] = append(filter.Tags[tag[0]], tag[1]) filter.Tags[tag[0]] = append(filter.Tags[tag[0]], tag[1])
} }
if since := c.Int("since"); since != 0 { if since := c.String("since"); since != "" {
ts := nostr.Timestamp(since) if since == "now" {
filter.Since = &ts 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 { if until := c.String("until"); until != "" {
ts := nostr.Timestamp(until) if until == "now" {
filter.Until = &ts 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 { if limit := c.Int("limit"); limit != 0 {
filter.Limit = limit filter.Limit = limit
} }
relays := c.Args().Slice() if len(relayUrls) > 0 {
if len(relays) > 0 {
pool := nostr.NewSimplePool(c.Context)
fn := pool.SubManyEose fn := pool.SubManyEose
if c.Bool("stream") { if c.Bool("stream") {
fn = pool.SubMany fn = pool.SubMany
} }
for ie := range fn(c.Context, relays, nostr.Filters{filter}) { for ie := range fn(c.Context, relayUrls, nostr.Filters{filter}) {
fmt.Println(ie.Event) stdout(ie.Event)
} }
} else { } else {
// no relays given, will just print the filter // no relays given, will just print the filter
@@ -171,11 +257,11 @@ example:
if c.Bool("bare") { if c.Bool("bare") {
result = filter.String() result = filter.String()
} else { } else {
j, _ := json.Marshal([]any{"REQ", "nak", filter}) j, _ := json.Marshal(nostr.ReqEnvelope{SubscriptionID: "nak", Filters: nostr.Filters{filter}})
result = string(j) result = string(j)
} }
fmt.Println(result) stdout(result)
} }
} }

View File

@@ -13,8 +13,7 @@ var verify = &cli.Command{
Description: `example: Description: `example:
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.`,
`,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
for stdinEvent := range getStdinLinesOrBlank() { for stdinEvent := range getStdinLinesOrBlank() {
evt := nostr.Event{} evt := nostr.Event{}