mirror of
https://github.com/fiatjaf/nak.git
synced 2025-12-11 01:48:50 +00:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
441ee9a5ed | ||
|
|
9a41450209 | ||
|
|
dba2ed0b5f | ||
|
|
2079ddf818 | ||
|
|
2135b68106 | ||
|
|
9f98a0aea3 | ||
|
|
1ba39ca7d7 | ||
|
|
262c0c892a | ||
|
|
363bd66a8a | ||
|
|
eccce6dc4a |
29
bunker.go
29
bunker.go
@@ -14,7 +14,7 @@ import (
|
|||||||
"github.com/nbd-wtf/go-nostr"
|
"github.com/nbd-wtf/go-nostr"
|
||||||
"github.com/nbd-wtf/go-nostr/nip19"
|
"github.com/nbd-wtf/go-nostr/nip19"
|
||||||
"github.com/nbd-wtf/go-nostr/nip46"
|
"github.com/nbd-wtf/go-nostr/nip46"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -45,12 +45,12 @@ var bunker = &cli.Command{
|
|||||||
Usage: "pubkeys for which we will always respond",
|
Usage: "pubkeys for which we will always respond",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: func(c *cli.Context) error {
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
// try to connect to the relays here
|
// try to connect to the relays here
|
||||||
qs := url.Values{}
|
qs := url.Values{}
|
||||||
relayURLs := make([]string, 0, c.Args().Len())
|
relayURLs := make([]string, 0, c.Args().Len())
|
||||||
if relayUrls := c.Args().Slice(); len(relayUrls) > 0 {
|
if relayUrls := c.Args().Slice(); len(relayUrls) > 0 {
|
||||||
_, relays := connectToAllRelays(c.Context, relayUrls)
|
_, relays := connectToAllRelays(ctx, relayUrls)
|
||||||
if len(relays) == 0 {
|
if len(relays) == 0 {
|
||||||
log("failed to connect to any of the given relays.\n")
|
log("failed to connect to any of the given relays.\n")
|
||||||
os.Exit(3)
|
os.Exit(3)
|
||||||
@@ -65,7 +65,7 @@ var bunker = &cli.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
// gather the secret key
|
// gather the secret key
|
||||||
sec, _, err := gatherSecretKeyOrBunkerFromArguments(c)
|
sec, _, err := gatherSecretKeyOrBunkerFromArguments(ctx, c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -115,10 +115,19 @@ var bunker = &cli.Command{
|
|||||||
secretKeyFlag = "--sec " + sec
|
secretKeyFlag = "--sec " + sec
|
||||||
}
|
}
|
||||||
|
|
||||||
|
relayURLsPossiblyWithoutSchema := make([]string, len(relayURLs))
|
||||||
|
for i, url := range relayURLs {
|
||||||
|
if strings.HasPrefix(url, "wss://") {
|
||||||
|
relayURLsPossiblyWithoutSchema[i] = url[6:]
|
||||||
|
} else {
|
||||||
|
relayURLsPossiblyWithoutSchema[i] = url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
restartCommand := fmt.Sprintf("nak bunker %s%s %s",
|
restartCommand := fmt.Sprintf("nak bunker %s%s %s",
|
||||||
secretKeyFlag,
|
secretKeyFlag,
|
||||||
preauthorizedFlags,
|
preauthorizedFlags,
|
||||||
strings.Join(relayURLs, " "),
|
strings.Join(relayURLsPossiblyWithoutSchema, " "),
|
||||||
)
|
)
|
||||||
|
|
||||||
log("listening at %v:\n pubkey: %s \n npub: %s%s%s\n to restart: %s\n bunker: %s\n\n",
|
log("listening at %v:\n pubkey: %s \n npub: %s%s%s\n to restart: %s\n bunker: %s\n\n",
|
||||||
@@ -134,9 +143,9 @@ var bunker = &cli.Command{
|
|||||||
printBunkerInfo()
|
printBunkerInfo()
|
||||||
|
|
||||||
// subscribe to relays
|
// subscribe to relays
|
||||||
pool := nostr.NewSimplePool(c.Context)
|
pool := nostr.NewSimplePool(ctx)
|
||||||
now := nostr.Now()
|
now := nostr.Now()
|
||||||
events := pool.SubMany(c.Context, relayURLs, nostr.Filters{
|
events := pool.SubMany(ctx, relayURLs, nostr.Filters{
|
||||||
{
|
{
|
||||||
Kinds: []int{nostr.KindNostrConnect},
|
Kinds: []int{nostr.KindNostrConnect},
|
||||||
Tags: nostr.TagMap{"p": []string{pubkey}},
|
Tags: nostr.TagMap{"p": []string{pubkey}},
|
||||||
@@ -151,7 +160,7 @@ var bunker = &cli.Command{
|
|||||||
|
|
||||||
// just a gimmick
|
// just a gimmick
|
||||||
var cancelPreviousBunkerInfoPrint context.CancelFunc
|
var cancelPreviousBunkerInfoPrint context.CancelFunc
|
||||||
_, cancel := context.WithCancel(c.Context)
|
_, cancel := context.WithCancel(ctx)
|
||||||
cancelPreviousBunkerInfoPrint = cancel
|
cancelPreviousBunkerInfoPrint = cancel
|
||||||
|
|
||||||
// asking user for authorization
|
// asking user for authorization
|
||||||
@@ -190,7 +199,7 @@ var bunker = &cli.Command{
|
|||||||
for _, relayURL := range relayURLs {
|
for _, relayURL := range relayURLs {
|
||||||
go func(relayURL string) {
|
go func(relayURL string) {
|
||||||
if relay, _ := pool.EnsureRelay(relayURL); relay != nil {
|
if relay, _ := pool.EnsureRelay(relayURL); relay != nil {
|
||||||
err := relay.Publish(c.Context, eventResponse)
|
err := relay.Publish(ctx, eventResponse)
|
||||||
printLock.Lock()
|
printLock.Lock()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
log("* sent response through %s\n", relay.URL)
|
log("* sent response through %s\n", relay.URL)
|
||||||
@@ -206,7 +215,7 @@ var bunker = &cli.Command{
|
|||||||
|
|
||||||
// just after handling one request we trigger this
|
// just after handling one request we trigger this
|
||||||
go func() {
|
go func() {
|
||||||
ctx, cancel := context.WithCancel(c.Context)
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
cancelPreviousBunkerInfoPrint = cancel
|
cancelPreviousBunkerInfoPrint = cancel
|
||||||
// the idea is that we will print the bunker URL again so it is easier to copy-paste by users
|
// the idea is that we will print the bunker URL again so it is easier to copy-paste by users
|
||||||
|
|||||||
17
count.go
17
count.go
@@ -1,13 +1,14 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/nbd-wtf/go-nostr"
|
"github.com/nbd-wtf/go-nostr"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var count = &cli.Command{
|
var count = &cli.Command{
|
||||||
@@ -63,7 +64,7 @@ var count = &cli.Command{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
ArgsUsage: "[relay...]",
|
ArgsUsage: "[relay...]",
|
||||||
Action: func(c *cli.Context) error {
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
filter := nostr.Filter{}
|
filter := nostr.Filter{}
|
||||||
|
|
||||||
if authors := c.StringSlice("author"); len(authors) > 0 {
|
if authors := c.StringSlice("author"); len(authors) > 0 {
|
||||||
@@ -72,7 +73,11 @@ var count = &cli.Command{
|
|||||||
if ids := c.StringSlice("id"); len(ids) > 0 {
|
if ids := c.StringSlice("id"); len(ids) > 0 {
|
||||||
filter.IDs = ids
|
filter.IDs = ids
|
||||||
}
|
}
|
||||||
if kinds := c.IntSlice("kind"); len(kinds) > 0 {
|
if kinds64 := c.IntSlice("kind"); len(kinds64) > 0 {
|
||||||
|
kinds := make([]int, len(kinds64))
|
||||||
|
for i, v := range kinds64 {
|
||||||
|
kinds[i] = int(v)
|
||||||
|
}
|
||||||
filter.Kinds = kinds
|
filter.Kinds = kinds
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,7 +115,7 @@ var count = &cli.Command{
|
|||||||
filter.Until = &ts
|
filter.Until = &ts
|
||||||
}
|
}
|
||||||
if limit := c.Int("limit"); limit != 0 {
|
if limit := c.Int("limit"); limit != 0 {
|
||||||
filter.Limit = limit
|
filter.Limit = int(limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
relays := c.Args().Slice()
|
relays := c.Args().Slice()
|
||||||
@@ -118,12 +123,12 @@ var count = &cli.Command{
|
|||||||
failures := make([]error, 0, len(relays))
|
failures := make([]error, 0, len(relays))
|
||||||
if len(relays) > 0 {
|
if len(relays) > 0 {
|
||||||
for _, relayUrl := range relays {
|
for _, relayUrl := range relays {
|
||||||
relay, err := nostr.RelayConnect(c.Context, relayUrl)
|
relay, err := nostr.RelayConnect(ctx, relayUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
failures = append(failures, err)
|
failures = append(failures, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
count, err := relay.Count(c.Context, nostr.Filters{filter})
|
count, err := relay.Count(ctx, nostr.Filters{filter})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
failures = append(failures, err)
|
failures = append(failures, err)
|
||||||
continue
|
continue
|
||||||
|
|||||||
13
decode.go
13
decode.go
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -8,7 +9,7 @@ import (
|
|||||||
"github.com/nbd-wtf/go-nostr"
|
"github.com/nbd-wtf/go-nostr"
|
||||||
"github.com/nbd-wtf/go-nostr/nip19"
|
"github.com/nbd-wtf/go-nostr/nip19"
|
||||||
sdk "github.com/nbd-wtf/nostr-sdk"
|
sdk "github.com/nbd-wtf/nostr-sdk"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var decode = &cli.Command{
|
var decode = &cli.Command{
|
||||||
@@ -32,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(ctx context.Context, c *cli.Command) error {
|
||||||
for input := range getStdinLinesOrArguments(c.Args()) {
|
for input := range getStdinLinesOrArguments(c.Args()) {
|
||||||
if strings.HasPrefix(input, "nostr:") {
|
if strings.HasPrefix(input, "nostr:") {
|
||||||
input = input[6:]
|
input = input[6:]
|
||||||
@@ -49,12 +50,12 @@ var decode = &cli.Command{
|
|||||||
decodeResult.HexResult.PrivateKey = hex.EncodeToString(b)
|
decodeResult.HexResult.PrivateKey = hex.EncodeToString(b)
|
||||||
decodeResult.HexResult.PublicKey = hex.EncodeToString(b)
|
decodeResult.HexResult.PublicKey = hex.EncodeToString(b)
|
||||||
} else {
|
} else {
|
||||||
lineProcessingError(c, "hex string with invalid number of bytes: %d", len(b))
|
lineProcessingError(ctx, "hex string with invalid number of bytes: %d", len(b))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
} else if evp := sdk.InputToEventPointer(input); evp != nil {
|
} else if evp := sdk.InputToEventPointer(input); evp != nil {
|
||||||
decodeResult = DecodeResult{EventPointer: evp}
|
decodeResult = DecodeResult{EventPointer: evp}
|
||||||
} else if pp := sdk.InputToProfile(c.Context, input); pp != nil {
|
} else if pp := sdk.InputToProfile(ctx, input); pp != nil {
|
||||||
decodeResult = DecodeResult{ProfilePointer: pp}
|
decodeResult = DecodeResult{ProfilePointer: pp}
|
||||||
} else if prefix, value, err := nip19.Decode(input); err == nil && prefix == "naddr" {
|
} else if prefix, value, err := nip19.Decode(input); err == nil && prefix == "naddr" {
|
||||||
ep := value.(nostr.EntityPointer)
|
ep := value.(nostr.EntityPointer)
|
||||||
@@ -63,7 +64,7 @@ var decode = &cli.Command{
|
|||||||
decodeResult.PrivateKey.PrivateKey = value.(string)
|
decodeResult.PrivateKey.PrivateKey = value.(string)
|
||||||
decodeResult.PrivateKey.PublicKey, _ = nostr.GetPublicKey(value.(string))
|
decodeResult.PrivateKey.PublicKey, _ = nostr.GetPublicKey(value.(string))
|
||||||
} else {
|
} else {
|
||||||
lineProcessingError(c, "couldn't decode input '%s': %s", input, err)
|
lineProcessingError(ctx, "couldn't decode input '%s': %s", input, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,7 +72,7 @@ var decode = &cli.Command{
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
exitIfLineProcessingError(c)
|
exitIfLineProcessingError(ctx)
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
60
encode.go
60
encode.go
@@ -1,11 +1,12 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"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"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var encode = &cli.Command{
|
var encode = &cli.Command{
|
||||||
@@ -18,20 +19,20 @@ var encode = &cli.Command{
|
|||||||
nak encode nevent <event-id>
|
nak encode nevent <event-id>
|
||||||
nak encode nevent --author <pubkey-hex> --relay <relay-url> --relay <other-relay> <event-id>
|
nak encode nevent --author <pubkey-hex> --relay <relay-url> --relay <other-relay> <event-id>
|
||||||
nak encode nsec <privkey-hex>`,
|
nak encode nsec <privkey-hex>`,
|
||||||
Before: func(c *cli.Context) error {
|
Before: func(ctx context.Context, c *cli.Command) error {
|
||||||
if c.Args().Len() < 1 {
|
if c.Args().Len() < 1 {
|
||||||
return fmt.Errorf("expected more than 1 argument.")
|
return fmt.Errorf("expected more than 1 argument.")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
Subcommands: []*cli.Command{
|
Commands: []*cli.Command{
|
||||||
{
|
{
|
||||||
Name: "npub",
|
Name: "npub",
|
||||||
Usage: "encode a hex public key into bech32 'npub' format",
|
Usage: "encode a hex public key into bech32 'npub' format",
|
||||||
Action: func(c *cli.Context) error {
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
for target := range getStdinLinesOrArguments(c.Args()) {
|
for target := range getStdinLinesOrArguments(c.Args()) {
|
||||||
if ok := nostr.IsValidPublicKey(target); !ok {
|
if ok := nostr.IsValidPublicKey(target); !ok {
|
||||||
lineProcessingError(c, "invalid public key: %s", target)
|
lineProcessingError(ctx, "invalid public key: %s", target)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,17 +43,17 @@ var encode = &cli.Command{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exitIfLineProcessingError(c)
|
exitIfLineProcessingError(ctx)
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
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(ctx context.Context, c *cli.Command) error {
|
||||||
for target := range getStdinLinesOrArguments(c.Args()) {
|
for target := range getStdinLinesOrArguments(c.Args()) {
|
||||||
if ok := nostr.IsValid32ByteHex(target); !ok {
|
if ok := nostr.IsValid32ByteHex(target); !ok {
|
||||||
lineProcessingError(c, "invalid private key: %s", target)
|
lineProcessingError(ctx, "invalid private key: %s", target)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +64,7 @@ var encode = &cli.Command{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exitIfLineProcessingError(c)
|
exitIfLineProcessingError(ctx)
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -77,15 +78,15 @@ var encode = &cli.Command{
|
|||||||
Usage: "attach relay hints to nprofile code",
|
Usage: "attach relay hints to nprofile code",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: func(c *cli.Context) error {
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
for target := range getStdinLinesOrArguments(c.Args()) {
|
for target := range getStdinLinesOrArguments(c.Args()) {
|
||||||
if ok := nostr.IsValid32ByteHex(target); !ok {
|
if ok := nostr.IsValid32ByteHex(target); !ok {
|
||||||
lineProcessingError(c, "invalid public key: %s", target)
|
lineProcessingError(ctx, "invalid public key: %s", target)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
relays := c.StringSlice("relay")
|
relays := c.StringSlice("relay")
|
||||||
if err := validateRelayURLs(relays); err != nil {
|
if err := normalizeAndValidateRelayURLs(relays); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,7 +97,7 @@ var encode = &cli.Command{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exitIfLineProcessingError(c)
|
exitIfLineProcessingError(ctx)
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -110,14 +111,15 @@ var encode = &cli.Command{
|
|||||||
Usage: "attach relay hints to nevent code",
|
Usage: "attach relay hints to nevent code",
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "author",
|
Name: "author",
|
||||||
Usage: "attach an author pubkey as a hint to the nevent code",
|
Aliases: []string{"a"},
|
||||||
|
Usage: "attach an author pubkey as a hint to the nevent code",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: func(c *cli.Context) error {
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
for target := range getStdinLinesOrArguments(c.Args()) {
|
for target := range getStdinLinesOrArguments(c.Args()) {
|
||||||
if ok := nostr.IsValid32ByteHex(target); !ok {
|
if ok := nostr.IsValid32ByteHex(target); !ok {
|
||||||
lineProcessingError(c, "invalid event id: %s", target)
|
lineProcessingError(ctx, "invalid event id: %s", target)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,7 +131,7 @@ var encode = &cli.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
relays := c.StringSlice("relay")
|
relays := c.StringSlice("relay")
|
||||||
if err := validateRelayURLs(relays); err != nil {
|
if err := normalizeAndValidateRelayURLs(relays); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,7 +142,7 @@ var encode = &cli.Command{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exitIfLineProcessingError(c)
|
exitIfLineProcessingError(ctx)
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -157,10 +159,10 @@ var encode = &cli.Command{
|
|||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "pubkey",
|
Name: "pubkey",
|
||||||
Usage: "pubkey of the naddr author",
|
Usage: "pubkey of the naddr author",
|
||||||
Aliases: []string{"p"},
|
Aliases: []string{"author", "a", "p"},
|
||||||
Required: true,
|
Required: true,
|
||||||
},
|
},
|
||||||
&cli.Int64Flag{
|
&cli.IntFlag{
|
||||||
Name: "kind",
|
Name: "kind",
|
||||||
Aliases: []string{"k"},
|
Aliases: []string{"k"},
|
||||||
Usage: "kind of referred replaceable event",
|
Usage: "kind of referred replaceable event",
|
||||||
@@ -172,7 +174,7 @@ var encode = &cli.Command{
|
|||||||
Usage: "attach relay hints to naddr code",
|
Usage: "attach relay hints to naddr code",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: func(c *cli.Context) error {
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
for d := range getStdinLinesOrBlank() {
|
for d := range getStdinLinesOrBlank() {
|
||||||
pubkey := c.String("pubkey")
|
pubkey := c.String("pubkey")
|
||||||
if ok := nostr.IsValidPublicKey(pubkey); !ok {
|
if ok := nostr.IsValidPublicKey(pubkey); !ok {
|
||||||
@@ -187,34 +189,34 @@ var encode = &cli.Command{
|
|||||||
if d == "" {
|
if d == "" {
|
||||||
d = c.String("identifier")
|
d = c.String("identifier")
|
||||||
if d == "" {
|
if d == "" {
|
||||||
lineProcessingError(c, "\"d\" tag identifier can't be empty")
|
lineProcessingError(ctx, "\"d\" tag identifier can't be empty")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
relays := c.StringSlice("relay")
|
relays := c.StringSlice("relay")
|
||||||
if err := validateRelayURLs(relays); err != nil {
|
if err := normalizeAndValidateRelayURLs(relays); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if npub, err := nip19.EncodeEntity(pubkey, kind, d, relays); err == nil {
|
if npub, err := nip19.EncodeEntity(pubkey, int(kind), d, relays); err == nil {
|
||||||
stdout(npub)
|
stdout(npub)
|
||||||
} else {
|
} else {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exitIfLineProcessingError(c)
|
exitIfLineProcessingError(ctx)
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
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(ctx context.Context, c *cli.Command) error {
|
||||||
for target := range getStdinLinesOrArguments(c.Args()) {
|
for target := range getStdinLinesOrArguments(c.Args()) {
|
||||||
if ok := nostr.IsValid32ByteHex(target); !ok {
|
if ok := nostr.IsValid32ByteHex(target); !ok {
|
||||||
lineProcessingError(c, "invalid event id: %s", target)
|
lineProcessingError(ctx, "invalid event id: %s", target)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,7 +227,7 @@ var encode = &cli.Command{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exitIfLineProcessingError(c)
|
exitIfLineProcessingError(ctx)
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
28
event.go
28
event.go
@@ -13,7 +13,7 @@ import (
|
|||||||
"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/v3"
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ example:
|
|||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "sec",
|
Name: "sec",
|
||||||
Usage: "secret key to sign the event, as hex or nsec",
|
Usage: "secret key to sign the event, as nsec, ncryptsec or hex",
|
||||||
DefaultText: "the key '1'",
|
DefaultText: "the key '1'",
|
||||||
Value: "0000000000000000000000000000000000000000000000000000000000000001",
|
Value: "0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
},
|
},
|
||||||
@@ -141,11 +141,11 @@ example:
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
ArgsUsage: "[relay...]",
|
ArgsUsage: "[relay...]",
|
||||||
Action: func(c *cli.Context) error {
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
// try to connect to the relays here
|
// try to connect to the relays here
|
||||||
var relays []*nostr.Relay
|
var relays []*nostr.Relay
|
||||||
if relayUrls := c.Args().Slice(); len(relayUrls) > 0 {
|
if relayUrls := c.Args().Slice(); len(relayUrls) > 0 {
|
||||||
_, relays = connectToAllRelays(c.Context, relayUrls)
|
_, relays = connectToAllRelays(ctx, relayUrls)
|
||||||
if len(relays) == 0 {
|
if len(relays) == 0 {
|
||||||
log("failed to connect to any of the given relays.\n")
|
log("failed to connect to any of the given relays.\n")
|
||||||
os.Exit(3)
|
os.Exit(3)
|
||||||
@@ -158,7 +158,7 @@ example:
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
sec, bunker, err := gatherSecretKeyOrBunkerFromArguments(c)
|
sec, bunker, err := gatherSecretKeyOrBunkerFromArguments(ctx, c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -176,14 +176,14 @@ example:
|
|||||||
|
|
||||||
if stdinEvent != "" {
|
if stdinEvent != "" {
|
||||||
if err := easyjson.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(ctx, "invalid event received from stdin: %s", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
kindWasSupplied = strings.Contains(stdinEvent, `"kind"`)
|
kindWasSupplied = strings.Contains(stdinEvent, `"kind"`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if kind := c.Int("kind"); slices.Contains(c.FlagNames(), "kind") {
|
if kind := c.Int("kind"); slices.Contains(c.FlagNames(), "kind") {
|
||||||
evt.Kind = kind
|
evt.Kind = int(kind)
|
||||||
mustRehashAndResign = true
|
mustRehashAndResign = true
|
||||||
} else if !kindWasSupplied {
|
} else if !kindWasSupplied {
|
||||||
evt.Kind = 1
|
evt.Kind = 1
|
||||||
@@ -248,7 +248,7 @@ example:
|
|||||||
|
|
||||||
if evt.Sig == "" || mustRehashAndResign {
|
if evt.Sig == "" || mustRehashAndResign {
|
||||||
if bunker != nil {
|
if bunker != nil {
|
||||||
if err := bunker.SignEvent(c.Context, &evt); err != nil {
|
if err := bunker.SignEvent(ctx, &evt); err != nil {
|
||||||
return fmt.Errorf("failed to sign with bunker: %w", err)
|
return fmt.Errorf("failed to sign with bunker: %w", err)
|
||||||
}
|
}
|
||||||
} else if numSigners := c.Uint("musig"); numSigners > 1 && sec != "" {
|
} else if numSigners := c.Uint("musig"); numSigners > 1 && sec != "" {
|
||||||
@@ -256,7 +256,7 @@ example:
|
|||||||
secNonce := c.String("musig-nonce-secret")
|
secNonce := c.String("musig-nonce-secret")
|
||||||
pubNonces := c.StringSlice("musig-nonce")
|
pubNonces := c.StringSlice("musig-nonce")
|
||||||
partialSigs := c.StringSlice("musig-partial")
|
partialSigs := c.StringSlice("musig-partial")
|
||||||
signed, err := performMusig(c.Context,
|
signed, err := performMusig(ctx,
|
||||||
sec, &evt, int(numSigners), pubkeys, pubNonces, secNonce, partialSigs)
|
sec, &evt, int(numSigners), pubkeys, pubNonces, secNonce, partialSigs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("musig error: %w", err)
|
return fmt.Errorf("musig error: %w", err)
|
||||||
@@ -291,7 +291,7 @@ example:
|
|||||||
for _, relay := range relays {
|
for _, relay := range relays {
|
||||||
publish:
|
publish:
|
||||||
log("publishing to %s... ", relay.URL)
|
log("publishing to %s... ", relay.URL)
|
||||||
ctx, cancel := context.WithTimeout(c.Context, 10*time.Second)
|
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
err := relay.Publish(ctx, evt)
|
err := relay.Publish(ctx, evt)
|
||||||
@@ -307,7 +307,7 @@ example:
|
|||||||
// if the relay is requesting auth and we can auth, let's do it
|
// if the relay is requesting auth and we can auth, let's do it
|
||||||
var pk string
|
var pk string
|
||||||
if bunker != nil {
|
if bunker != nil {
|
||||||
pk, err = bunker.GetPublicKey(c.Context)
|
pk, err = bunker.GetPublicKey(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get public key from bunker: %w", err)
|
return fmt.Errorf("failed to get public key from bunker: %w", err)
|
||||||
}
|
}
|
||||||
@@ -315,9 +315,9 @@ example:
|
|||||||
pk, _ = nostr.GetPublicKey(sec)
|
pk, _ = nostr.GetPublicKey(sec)
|
||||||
}
|
}
|
||||||
log("performing auth as %s... ", pk)
|
log("performing auth as %s... ", pk)
|
||||||
if err := relay.Auth(c.Context, func(evt *nostr.Event) error {
|
if err := relay.Auth(ctx, func(evt *nostr.Event) error {
|
||||||
if bunker != nil {
|
if bunker != nil {
|
||||||
return bunker.SignEvent(c.Context, evt)
|
return bunker.SignEvent(ctx, evt)
|
||||||
}
|
}
|
||||||
return evt.Sign(sec)
|
return evt.Sign(sec)
|
||||||
}); err == nil {
|
}); err == nil {
|
||||||
@@ -337,7 +337,7 @@ example:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exitIfLineProcessingError(c)
|
exitIfLineProcessingError(ctx)
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import "os"
|
|||||||
func ExampleEventBasic() {
|
func ExampleEventBasic() {
|
||||||
app.Run([]string{"nak", "event", "--ts", "1699485669"})
|
app.Run([]string{"nak", "event", "--ts", "1699485669"})
|
||||||
// Output:
|
// Output:
|
||||||
// {"id":"36d88cf5fcc449f2390a424907023eda7a74278120eebab8d02797cd92e7e29c","pubkey":"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798","created_at":1699485669,"kind":1,"tags":[],"content":"hello from the nostr army knife","sig":"68e71a192e8abcf8582a222434ac823ecc50607450ebe8cc4c145eb047794cc382dc3f888ce879d2f404f5ba6085a47601360a0fa2dd4b50d317bd0c6197c2c2"}
|
// {"kind":1,"id":"36d88cf5fcc449f2390a424907023eda7a74278120eebab8d02797cd92e7e29c","pubkey":"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798","created_at":1699485669,"tags":[],"content":"hello from the nostr army knife","sig":"68e71a192e8abcf8582a222434ac823ecc50607450ebe8cc4c145eb047794cc382dc3f888ce879d2f404f5ba6085a47601360a0fa2dd4b50d317bd0c6197c2c2"}
|
||||||
}
|
}
|
||||||
|
|
||||||
// (for some reason there can only be one test dealing with stdin in the suite otherwise it halts)
|
// (for some reason there can only be one test dealing with stdin in the suite otherwise it halts)
|
||||||
|
|||||||
22
fetch.go
22
fetch.go
@@ -1,15 +1,17 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
"github.com/nbd-wtf/go-nostr"
|
"github.com/nbd-wtf/go-nostr"
|
||||||
"github.com/nbd-wtf/go-nostr/nip19"
|
"github.com/nbd-wtf/go-nostr/nip19"
|
||||||
sdk "github.com/nbd-wtf/nostr-sdk"
|
sdk "github.com/nbd-wtf/nostr-sdk"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var fetch = &cli.Command{
|
var fetch = &cli.Command{
|
||||||
Name: "fetch",
|
Name: "fetch",
|
||||||
Usage: "fetches events related to the given nip19 code from the included relay hints",
|
Usage: "fetches events related to the given nip19 code from the included relay hints or the author's NIP-65 relays.",
|
||||||
Description: `example usage:
|
Description: `example usage:
|
||||||
nak fetch nevent1qqsxrwm0hd3s3fddh4jc2574z3xzufq6qwuyz2rvv3n087zvym3dpaqprpmhxue69uhhqatzd35kxtnjv4kxz7tfdenju6t0xpnej4
|
nak fetch nevent1qqsxrwm0hd3s3fddh4jc2574z3xzufq6qwuyz2rvv3n087zvym3dpaqprpmhxue69uhhqatzd35kxtnjv4kxz7tfdenju6t0xpnej4
|
||||||
echo npub1h8spmtw9m2huyv6v2j2qd5zv956z2zdugl6mgx02f2upffwpm3nqv0j4ps | nak fetch --relay wss://relay.nostr.band`,
|
echo npub1h8spmtw9m2huyv6v2j2qd5zv956z2zdugl6mgx02f2upffwpm3nqv0j4ps | nak fetch --relay wss://relay.nostr.band`,
|
||||||
@@ -21,8 +23,8 @@ var fetch = &cli.Command{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
ArgsUsage: "[nip19code]",
|
ArgsUsage: "[nip19code]",
|
||||||
Action: func(c *cli.Context) error {
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
pool := nostr.NewSimplePool(c.Context)
|
pool := nostr.NewSimplePool(ctx)
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
pool.Relays.Range(func(_ string, relay *nostr.Relay) bool {
|
pool.Relays.Range(func(_ string, relay *nostr.Relay) bool {
|
||||||
@@ -36,12 +38,12 @@ var fetch = &cli.Command{
|
|||||||
|
|
||||||
prefix, value, err := nip19.Decode(code)
|
prefix, value, err := nip19.Decode(code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lineProcessingError(c, "failed to decode: %s", err)
|
lineProcessingError(ctx, "failed to decode: %s", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
relays := c.StringSlice("relay")
|
relays := c.StringSlice("relay")
|
||||||
if err := validateRelayURLs(relays); err != nil {
|
if err := normalizeAndValidateRelayURLs(relays); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var authorHint string
|
var authorHint string
|
||||||
@@ -75,7 +77,7 @@ var fetch = &cli.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if authorHint != "" {
|
if authorHint != "" {
|
||||||
relayList := sdk.FetchRelaysForPubkey(c.Context, pool, authorHint,
|
relayList := sdk.FetchRelaysForPubkey(ctx, pool, authorHint,
|
||||||
"wss://purplepag.es", "wss://relay.damus.io", "wss://relay.noswhere.com",
|
"wss://purplepag.es", "wss://relay.damus.io", "wss://relay.noswhere.com",
|
||||||
"wss://nos.lol", "wss://public.relaying.io", "wss://relay.nostr.band")
|
"wss://nos.lol", "wss://public.relaying.io", "wss://relay.nostr.band")
|
||||||
for _, relayListItem := range relayList {
|
for _, relayListItem := range relayList {
|
||||||
@@ -86,16 +88,16 @@ var fetch = &cli.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(relays) == 0 {
|
if len(relays) == 0 {
|
||||||
lineProcessingError(c, "no relay hints found")
|
lineProcessingError(ctx, "no relay hints found")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for ie := range pool.SubManyEose(c.Context, relays, nostr.Filters{filter}) {
|
for ie := range pool.SubManyEose(ctx, relays, nostr.Filters{filter}) {
|
||||||
stdout(ie.Event)
|
stdout(ie.Event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exitIfLineProcessingError(c)
|
exitIfLineProcessingError(ctx)
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
11
go.mod
11
go.mod
@@ -7,11 +7,12 @@ toolchain go1.21.0
|
|||||||
require (
|
require (
|
||||||
github.com/btcsuite/btcd/btcec/v2 v2.3.3
|
github.com/btcsuite/btcd/btcec/v2 v2.3.3
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e
|
||||||
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0
|
||||||
github.com/fatih/color v1.16.0
|
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.31.2
|
github.com/nbd-wtf/go-nostr v0.34.0
|
||||||
github.com/nbd-wtf/nostr-sdk v0.0.5
|
github.com/nbd-wtf/nostr-sdk v0.0.5
|
||||||
github.com/urfave/cli/v2 v2.25.7
|
github.com/urfave/cli/v3 v3.0.0-alpha9
|
||||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
|
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -20,9 +21,7 @@ require (
|
|||||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect
|
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect
|
||||||
github.com/chzyer/logex v1.1.10 // indirect
|
github.com/chzyer/logex v1.1.10 // indirect
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect
|
||||||
github.com/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.3.0 // indirect
|
|
||||||
github.com/fiatjaf/eventstore v0.2.16 // indirect
|
github.com/fiatjaf/eventstore v0.2.16 // 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
|
||||||
@@ -31,12 +30,12 @@ require (
|
|||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/puzpuzpuz/xsync/v3 v3.1.0 // indirect
|
github.com/puzpuzpuz/xsync/v3 v3.1.0 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
|
||||||
github.com/tidwall/gjson v1.17.1 // indirect
|
github.com/tidwall/gjson v1.17.1 // indirect
|
||||||
github.com/tidwall/match v1.1.1 // indirect
|
github.com/tidwall/match v1.1.1 // indirect
|
||||||
github.com/tidwall/pretty v1.2.1 // indirect
|
github.com/tidwall/pretty v1.2.1 // indirect
|
||||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
|
||||||
golang.org/x/crypto v0.7.0 // indirect
|
golang.org/x/crypto v0.7.0 // indirect
|
||||||
golang.org/x/sys v0.20.0 // indirect
|
golang.org/x/sys v0.20.0 // indirect
|
||||||
golang.org/x/text v0.8.0 // indirect
|
golang.org/x/text v0.8.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
replace github.com/urfave/cli/v3 => github.com/fiatjaf/cli/v3 v3.0.0-20240626022047-0fc2565ea728
|
||||||
|
|||||||
14
go.sum
14
go.sum
@@ -29,8 +29,6 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5O
|
|||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
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 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
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=
|
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
@@ -44,6 +42,8 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3
|
|||||||
github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218=
|
github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218=
|
||||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||||
|
github.com/fiatjaf/cli/v3 v3.0.0-20240626022047-0fc2565ea728 h1:MgEQQSCPDkpILGlJKCj/Gj0l8XwYFpBKYATPJC14ros=
|
||||||
|
github.com/fiatjaf/cli/v3 v3.0.0-20240626022047-0fc2565ea728/go.mod h1:Z1ItyMma7t6I7zHG9OpbExhHQOSkFf/96n+mAZ9MtVI=
|
||||||
github.com/fiatjaf/eventstore v0.2.16 h1:NR64mnyUT5nJR8Sj2AwJTd1Hqs5kKJcCFO21ggUkvWg=
|
github.com/fiatjaf/eventstore v0.2.16 h1:NR64mnyUT5nJR8Sj2AwJTd1Hqs5kKJcCFO21ggUkvWg=
|
||||||
github.com/fiatjaf/eventstore v0.2.16/go.mod h1:rUc1KhVufVmC+HUOiuPweGAcvG6lEOQCkRCn2Xn5VRA=
|
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.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
@@ -79,8 +79,8 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
|
|||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/nbd-wtf/go-nostr v0.31.2 h1:PkHCAsSzG0Ce8tfF7LKyvZOjYtCdC+hPh5KfO/Rl1b4=
|
github.com/nbd-wtf/go-nostr v0.34.0 h1:E7tDHFx42gvWwFv1Eysn+NxJqGLmo21x/VEwj2+F21E=
|
||||||
github.com/nbd-wtf/go-nostr v0.31.2/go.mod h1:vHKtHyLXDXzYBN0fi/9Y/Q5AD0p+hk8TQVKlldAi0gI=
|
github.com/nbd-wtf/go-nostr v0.34.0/go.mod h1:NZQkxl96ggbO8rvDpVjcsojJqKTPwqhP4i82O7K5DJs=
|
||||||
github.com/nbd-wtf/nostr-sdk v0.0.5 h1:rec+FcDizDVO0W25PX0lgYMXvP7zNNOgI3Fu9UCm4BY=
|
github.com/nbd-wtf/nostr-sdk v0.0.5 h1:rec+FcDizDVO0W25PX0lgYMXvP7zNNOgI3Fu9UCm4BY=
|
||||||
github.com/nbd-wtf/nostr-sdk v0.0.5/go.mod h1:iJJsikesCGLNFZ9dLqhLPDzdt924EagUmdQxT3w2Lmk=
|
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=
|
||||||
@@ -96,8 +96,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
|||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/puzpuzpuz/xsync/v3 v3.1.0 h1:EewKT7/LNac5SLiEblJeUu8z5eERHrmRLnMQL2d7qX4=
|
github.com/puzpuzpuz/xsync/v3 v3.1.0 h1:EewKT7/LNac5SLiEblJeUu8z5eERHrmRLnMQL2d7qX4=
|
||||||
github.com/puzpuzpuz/xsync/v3 v3.1.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
github.com/puzpuzpuz/xsync/v3 v3.1.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||||
github.com/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/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
@@ -110,10 +108,6 @@ github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JT
|
|||||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
github.com/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-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=
|
||||||
|
|||||||
25
helpers.go
25
helpers.go
@@ -16,7 +16,7 @@ import (
|
|||||||
"github.com/nbd-wtf/go-nostr/nip19"
|
"github.com/nbd-wtf/go-nostr/nip19"
|
||||||
"github.com/nbd-wtf/go-nostr/nip46"
|
"github.com/nbd-wtf/go-nostr/nip46"
|
||||||
"github.com/nbd-wtf/go-nostr/nip49"
|
"github.com/nbd-wtf/go-nostr/nip49"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -92,8 +92,11 @@ func writeStdinLinesOrNothing(ch chan string) (hasStdinLines bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateRelayURLs(wsurls []string) error {
|
func normalizeAndValidateRelayURLs(wsurls []string) error {
|
||||||
for _, wsurl := range wsurls {
|
for i, wsurl := range wsurls {
|
||||||
|
wsurl = nostr.NormalizeURL(wsurl)
|
||||||
|
wsurls[i] = wsurl
|
||||||
|
|
||||||
u, err := url.Parse(wsurl)
|
u, err := url.Parse(wsurl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("invalid relay url '%s': %s", wsurl, err)
|
return fmt.Errorf("invalid relay url '%s': %s", wsurl, err)
|
||||||
@@ -130,18 +133,18 @@ func connectToAllRelays(
|
|||||||
return pool, relays
|
return pool, relays
|
||||||
}
|
}
|
||||||
|
|
||||||
func lineProcessingError(c *cli.Context, msg string, args ...any) {
|
func lineProcessingError(ctx context.Context, msg string, args ...any) {
|
||||||
c.Context = context.WithValue(c.Context, LINE_PROCESSING_ERROR, true)
|
ctx = context.WithValue(ctx, LINE_PROCESSING_ERROR, true)
|
||||||
log(msg+"\n", args...)
|
log(msg+"\n", args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func exitIfLineProcessingError(c *cli.Context) {
|
func exitIfLineProcessingError(ctx context.Context) {
|
||||||
if val := c.Context.Value(LINE_PROCESSING_ERROR); val != nil && val.(bool) {
|
if val := ctx.Value(LINE_PROCESSING_ERROR); val != nil && val.(bool) {
|
||||||
os.Exit(123)
|
os.Exit(123)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func gatherSecretKeyOrBunkerFromArguments(c *cli.Context) (string, *nip46.BunkerClient, error) {
|
func gatherSecretKeyOrBunkerFromArguments(ctx context.Context, c *cli.Command) (string, *nip46.BunkerClient, error) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if bunkerURL := c.String("connect"); bunkerURL != "" {
|
if bunkerURL := c.String("connect"); bunkerURL != "" {
|
||||||
@@ -151,7 +154,7 @@ func gatherSecretKeyOrBunkerFromArguments(c *cli.Context) (string, *nip46.Bunker
|
|||||||
} else {
|
} else {
|
||||||
clientKey = nostr.GeneratePrivateKey()
|
clientKey = nostr.GeneratePrivateKey()
|
||||||
}
|
}
|
||||||
bunker, err := nip46.ConnectBunker(c.Context, clientKey, bunkerURL, nil, func(s string) {
|
bunker, err := nip46.ConnectBunker(ctx, clientKey, bunkerURL, nil, func(s string) {
|
||||||
fmt.Fprintf(color.Error, color.CyanString("[nip46]: open the following URL: %s"), s)
|
fmt.Fprintf(color.Error, color.CyanString("[nip46]: open the following URL: %s"), s)
|
||||||
})
|
})
|
||||||
return "", bunker, err
|
return "", bunker, err
|
||||||
@@ -185,7 +188,7 @@ func gatherSecretKeyOrBunkerFromArguments(c *cli.Context) (string, *nip46.Bunker
|
|||||||
return sec, nil, nil
|
return sec, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func promptDecrypt(ncryptsec1 string) (string, error) {
|
func promptDecrypt(ncryptsec string) (string, error) {
|
||||||
for i := 1; i < 4; i++ {
|
for i := 1; i < 4; i++ {
|
||||||
var attemptStr string
|
var attemptStr string
|
||||||
if i > 1 {
|
if i > 1 {
|
||||||
@@ -195,7 +198,7 @@ func promptDecrypt(ncryptsec1 string) (string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
sec, err := nip49.Decrypt(ncryptsec1, password)
|
sec, err := nip49.Decrypt(ncryptsec, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
197
key.go
197
key.go
@@ -1,23 +1,27 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/btcec/v2"
|
"github.com/btcsuite/btcd/btcec/v2"
|
||||||
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
|
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
|
||||||
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
"github.com/nbd-wtf/go-nostr"
|
"github.com/nbd-wtf/go-nostr"
|
||||||
"github.com/nbd-wtf/go-nostr/nip19"
|
"github.com/nbd-wtf/go-nostr/nip19"
|
||||||
"github.com/nbd-wtf/go-nostr/nip49"
|
"github.com/nbd-wtf/go-nostr/nip49"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var key = &cli.Command{
|
var key = &cli.Command{
|
||||||
Name: "key",
|
Name: "key",
|
||||||
Usage: "operations on secret keys: generate, derive, encrypt, decrypt.",
|
Usage: "operations on secret keys: generate, derive, encrypt, decrypt.",
|
||||||
Description: ``,
|
Description: ``,
|
||||||
Subcommands: []*cli.Command{
|
Commands: []*cli.Command{
|
||||||
generate,
|
generate,
|
||||||
public,
|
public,
|
||||||
encrypt,
|
encrypt,
|
||||||
@@ -30,7 +34,7 @@ var generate = &cli.Command{
|
|||||||
Name: "generate",
|
Name: "generate",
|
||||||
Usage: "generates a secret key",
|
Usage: "generates a secret key",
|
||||||
Description: ``,
|
Description: ``,
|
||||||
Action: func(c *cli.Context) error {
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
sec := nostr.GeneratePrivateKey()
|
sec := nostr.GeneratePrivateKey()
|
||||||
stdout(sec)
|
stdout(sec)
|
||||||
return nil
|
return nil
|
||||||
@@ -42,11 +46,11 @@ var public = &cli.Command{
|
|||||||
Usage: "computes a public key from a secret key",
|
Usage: "computes a public key from a secret key",
|
||||||
Description: ``,
|
Description: ``,
|
||||||
ArgsUsage: "[secret]",
|
ArgsUsage: "[secret]",
|
||||||
Action: func(c *cli.Context) error {
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
for sec := range getSecretKeysFromStdinLinesOrSlice(c, c.Args().Slice()) {
|
for sec := range getSecretKeysFromStdinLinesOrSlice(ctx, c, c.Args().Slice()) {
|
||||||
pubkey, err := nostr.GetPublicKey(sec)
|
pubkey, err := nostr.GetPublicKey(sec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lineProcessingError(c, "failed to derive public key: %s", err)
|
lineProcessingError(ctx, "failed to derive public key: %s", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
stdout(pubkey)
|
stdout(pubkey)
|
||||||
@@ -68,24 +72,23 @@ var encrypt = &cli.Command{
|
|||||||
DefaultText: "16",
|
DefaultText: "16",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: func(c *cli.Context) error {
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
var content string
|
keys := make([]string, 0, 1)
|
||||||
var password string
|
var password string
|
||||||
switch c.Args().Len() {
|
switch c.Args().Len() {
|
||||||
case 1:
|
case 1:
|
||||||
content = ""
|
|
||||||
password = c.Args().Get(0)
|
password = c.Args().Get(0)
|
||||||
case 2:
|
case 2:
|
||||||
content = c.Args().Get(0)
|
keys = append(keys, c.Args().Get(0))
|
||||||
password = c.Args().Get(1)
|
password = c.Args().Get(1)
|
||||||
}
|
}
|
||||||
if password == "" {
|
if password == "" {
|
||||||
return fmt.Errorf("no password given")
|
return fmt.Errorf("no password given")
|
||||||
}
|
}
|
||||||
for sec := range getSecretKeysFromStdinLinesOrSlice(c, []string{content}) {
|
for sec := range getSecretKeysFromStdinLinesOrSlice(ctx, c, keys) {
|
||||||
ncryptsec, err := nip49.Encrypt(sec, password, uint8(c.Int("logn")), 0x02)
|
ncryptsec, err := nip49.Encrypt(sec, password, uint8(c.Int("logn")), 0x02)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lineProcessingError(c, "failed to encrypt: %s", err)
|
lineProcessingError(ctx, "failed to encrypt: %s", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
stdout(ncryptsec)
|
stdout(ncryptsec)
|
||||||
@@ -99,65 +102,159 @@ var decrypt = &cli.Command{
|
|||||||
Usage: "takes an ncrypsec and a password and decrypts it into an nsec",
|
Usage: "takes an ncrypsec and a password and decrypts it into an nsec",
|
||||||
Description: `uses the NIP-49 standard.`,
|
Description: `uses the NIP-49 standard.`,
|
||||||
ArgsUsage: "<ncryptsec-code> <password>",
|
ArgsUsage: "<ncryptsec-code> <password>",
|
||||||
Action: func(c *cli.Context) error {
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
var content string
|
var ncryptsec string
|
||||||
var password string
|
var password string
|
||||||
switch c.Args().Len() {
|
switch c.Args().Len() {
|
||||||
case 1:
|
|
||||||
content = ""
|
|
||||||
password = c.Args().Get(0)
|
|
||||||
case 2:
|
case 2:
|
||||||
content = c.Args().Get(0)
|
ncryptsec = c.Args().Get(0)
|
||||||
password = c.Args().Get(1)
|
password = c.Args().Get(1)
|
||||||
}
|
if password == "" {
|
||||||
if password == "" {
|
return fmt.Errorf("no password given")
|
||||||
return fmt.Errorf("no password given")
|
}
|
||||||
}
|
|
||||||
for ncryptsec := range getStdinLinesOrArgumentsFromSlice([]string{content}) {
|
|
||||||
sec, err := nip49.Decrypt(ncryptsec, password)
|
sec, err := nip49.Decrypt(ncryptsec, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lineProcessingError(c, "failed to decrypt: %s", err)
|
return fmt.Errorf("failed to decrypt: %s", err)
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
nsec, _ := nip19.EncodePrivateKey(sec)
|
nsec, _ := nip19.EncodePrivateKey(sec)
|
||||||
stdout(nsec)
|
stdout(nsec)
|
||||||
|
return nil
|
||||||
|
case 1:
|
||||||
|
if arg := c.Args().Get(0); strings.HasPrefix(arg, "ncryptsec1") {
|
||||||
|
ncryptsec = arg
|
||||||
|
if res, err := promptDecrypt(ncryptsec); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
stdout(res)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
password = c.Args().Get(0)
|
||||||
|
for ncryptsec := range getStdinLinesOrArgumentsFromSlice([]string{ncryptsec}) {
|
||||||
|
sec, err := nip49.Decrypt(ncryptsec, password)
|
||||||
|
if err != nil {
|
||||||
|
lineProcessingError(ctx, "failed to decrypt: %s", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
nsec, _ := nip19.EncodePrivateKey(sec)
|
||||||
|
stdout(nsec)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("invalid number of arguments")
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var combine = &cli.Command{
|
var combine = &cli.Command{
|
||||||
Name: "combine",
|
Name: "combine",
|
||||||
Usage: "combines two or more pubkeys using musig2",
|
Usage: "combines two or more pubkeys using musig2",
|
||||||
Description: `The public keys must have 33 bytes (66 characters hex), with the 02 or 03 prefix. It is common in Nostr to drop that first byte, so you'll have to derive the public keys again from the private keys in order to get it back.`,
|
Description: `The public keys must have 33 bytes (66 characters hex), with the 02 or 03 prefix. It is common in Nostr to drop that first byte, so you'll have to derive the public keys again from the private keys in order to get it back.
|
||||||
ArgsUsage: "[pubkey...]",
|
|
||||||
Action: func(c *cli.Context) error {
|
|
||||||
keys := make([]*btcec.PublicKey, 0, 5)
|
|
||||||
for _, pub := range c.Args().Slice() {
|
|
||||||
keyb, err := hex.DecodeString(pub)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error parsing key %s: %w", pub, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
pubk, err := btcec.ParsePubKey(keyb)
|
However, if the intent is to check if two existing Nostr pubkeys match a given combined pubkey, then it might be sufficient to calculate the combined key for all the possible combinations of pubkeys in the input.`,
|
||||||
if err != nil {
|
ArgsUsage: "[pubkey...]",
|
||||||
return fmt.Errorf("error parsing key %s: %w", pub, err)
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
}
|
type Combination struct {
|
||||||
|
Variants []string `json:"input_variants"`
|
||||||
keys = append(keys, pubk)
|
Output struct {
|
||||||
|
XOnly string `json:"x_only"`
|
||||||
|
Variant string `json:"variant"`
|
||||||
|
} `json:"combined_key"`
|
||||||
}
|
}
|
||||||
|
|
||||||
agg, _, _, err := musig2.AggregateKeys(keys, true)
|
type Result struct {
|
||||||
if err != nil {
|
Keys []string `json:"keys"`
|
||||||
return err
|
Combinations []Combination `json:"combinations"`
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(hex.EncodeToString(agg.FinalKey.SerializeCompressed()))
|
result := Result{}
|
||||||
|
|
||||||
|
result.Keys = c.Args().Slice()
|
||||||
|
keyGroups := make([][]*btcec.PublicKey, 0, len(result.Keys))
|
||||||
|
|
||||||
|
for i, keyhex := range result.Keys {
|
||||||
|
keyb, err := hex.DecodeString(keyhex)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error parsing key %s: %w", keyhex, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(keyb) == 32 /* we'll use both the 02 and the 03 prefix versions */ {
|
||||||
|
group := make([]*btcec.PublicKey, 2)
|
||||||
|
for i, prefix := range []byte{0x02, 0x03} {
|
||||||
|
pubk, err := btcec.ParsePubKey(append([]byte{prefix}, keyb...))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "error parsing key %s: %s", keyhex, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
group[i] = pubk
|
||||||
|
}
|
||||||
|
keyGroups = append(keyGroups, group)
|
||||||
|
} else /* assume it's 33 */ {
|
||||||
|
pubk, err := btcec.ParsePubKey(keyb)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error parsing key %s: %w", keyhex, err)
|
||||||
|
}
|
||||||
|
keyGroups = append(keyGroups, []*btcec.PublicKey{pubk})
|
||||||
|
|
||||||
|
// remove the leading byte from the output just so it is all uniform
|
||||||
|
result.Keys[i] = result.Keys[i][2:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Combinations = make([]Combination, 0, 16)
|
||||||
|
|
||||||
|
var fn func(prepend int, curr []int)
|
||||||
|
fn = func(prepend int, curr []int) {
|
||||||
|
curr = append([]int{prepend}, curr...)
|
||||||
|
if len(curr) == len(keyGroups) {
|
||||||
|
combi := Combination{
|
||||||
|
Variants: make([]string, len(keyGroups)),
|
||||||
|
}
|
||||||
|
|
||||||
|
combining := make([]*btcec.PublicKey, len(keyGroups))
|
||||||
|
for g, altKeys := range keyGroups {
|
||||||
|
altKey := altKeys[curr[g]]
|
||||||
|
variant := secp256k1.PubKeyFormatCompressedEven
|
||||||
|
if altKey.Y().Bit(0) == 1 {
|
||||||
|
variant = secp256k1.PubKeyFormatCompressedOdd
|
||||||
|
}
|
||||||
|
combi.Variants[g] = hex.EncodeToString([]byte{variant})
|
||||||
|
combining[g] = altKey
|
||||||
|
}
|
||||||
|
|
||||||
|
agg, _, _, err := musig2.AggregateKeys(combining, true)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "error aggregating: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
serialized := agg.FinalKey.SerializeCompressed()
|
||||||
|
combi.Output.XOnly = hex.EncodeToString(serialized[1:])
|
||||||
|
combi.Output.Variant = hex.EncodeToString(serialized[0:1])
|
||||||
|
result.Combinations = append(result.Combinations, combi)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fn(0, curr)
|
||||||
|
if len(keyGroups[len(keyGroups)-len(curr)-1]) > 1 {
|
||||||
|
fn(1, curr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn(0, nil)
|
||||||
|
if len(keyGroups[len(keyGroups)-1]) > 1 {
|
||||||
|
fn(1, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, _ := json.MarshalIndent(result, "", " ")
|
||||||
|
fmt.Println(string(res))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSecretKeysFromStdinLinesOrSlice(c *cli.Context, keys []string) chan string {
|
func getSecretKeysFromStdinLinesOrSlice(ctx context.Context, c *cli.Command, keys []string) chan string {
|
||||||
ch := make(chan string)
|
ch := make(chan string)
|
||||||
go func() {
|
go func() {
|
||||||
for sec := range getStdinLinesOrArgumentsFromSlice(keys) {
|
for sec := range getStdinLinesOrArgumentsFromSlice(keys) {
|
||||||
@@ -167,13 +264,13 @@ func getSecretKeysFromStdinLinesOrSlice(c *cli.Context, keys []string) chan stri
|
|||||||
if strings.HasPrefix(sec, "nsec1") {
|
if strings.HasPrefix(sec, "nsec1") {
|
||||||
_, data, err := nip19.Decode(sec)
|
_, data, err := nip19.Decode(sec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lineProcessingError(c, "invalid nsec code: %s", err)
|
lineProcessingError(ctx, "invalid nsec code: %s", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
sec = data.(string)
|
sec = data.(string)
|
||||||
}
|
}
|
||||||
if !nostr.IsValid32ByteHex(sec) {
|
if !nostr.IsValid32ByteHex(sec) {
|
||||||
lineProcessingError(c, "invalid hex key")
|
lineProcessingError(ctx, "invalid hex key")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ch <- sec
|
ch <- sec
|
||||||
|
|||||||
13
main.go
13
main.go
@@ -1,14 +1,13 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var q int
|
var app = &cli.Command{
|
||||||
|
|
||||||
var app = &cli.App{
|
|
||||||
Name: "nak",
|
Name: "nak",
|
||||||
Suggest: true,
|
Suggest: true,
|
||||||
UseShortOptionHandling: true,
|
UseShortOptionHandling: true,
|
||||||
@@ -29,9 +28,9 @@ var app = &cli.App{
|
|||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "quiet",
|
Name: "quiet",
|
||||||
Usage: "do not print logs and info messages to stderr, use -qq to also not print anything to stdout",
|
Usage: "do not print logs and info messages to stderr, use -qq to also not print anything to stdout",
|
||||||
Count: &q,
|
|
||||||
Aliases: []string{"q"},
|
Aliases: []string{"q"},
|
||||||
Action: func(ctx *cli.Context, b bool) error {
|
Action: func(ctx context.Context, c *cli.Command, b bool) error {
|
||||||
|
q := c.Count("quiet")
|
||||||
if q >= 1 {
|
if q >= 1 {
|
||||||
log = func(msg string, args ...any) {}
|
log = func(msg string, args ...any) {}
|
||||||
if q >= 2 {
|
if q >= 2 {
|
||||||
@@ -45,7 +44,7 @@ var app = &cli.App{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if err := app.Run(os.Args); err != nil {
|
if err := app.Run(context.Background(), os.Args); err != nil {
|
||||||
stdout(err)
|
stdout(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|||||||
14
relay.go
14
relay.go
@@ -1,12 +1,12 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/nbd-wtf/go-nostr/nip11"
|
"github.com/nbd-wtf/go-nostr/nip11"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var relay = &cli.Command{
|
var relay = &cli.Command{
|
||||||
@@ -15,19 +15,15 @@ var relay = &cli.Command{
|
|||||||
Description: `example:
|
Description: `example:
|
||||||
nak relay nostr.wine`,
|
nak relay nostr.wine`,
|
||||||
ArgsUsage: "<relay-url>",
|
ArgsUsage: "<relay-url>",
|
||||||
Action: func(c *cli.Context) error {
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
for url := range getStdinLinesOrArguments(c.Args()) {
|
for url := range getStdinLinesOrArguments(c.Args()) {
|
||||||
if url == "" {
|
if url == "" {
|
||||||
return fmt.Errorf("specify the <relay-url>")
|
return fmt.Errorf("specify the <relay-url>")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.HasPrefix(url, "wss://") && !strings.HasPrefix(url, "ws://") {
|
info, err := nip11.Fetch(ctx, url)
|
||||||
url = "wss://" + url
|
|
||||||
}
|
|
||||||
|
|
||||||
info, err := nip11.Fetch(c.Context, url)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lineProcessingError(c, "failed to fetch '%s' information document: %w", url, err)
|
lineProcessingError(ctx, "failed to fetch '%s' information document: %w", url, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
25
req.go
25
req.go
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
@@ -9,7 +10,7 @@ import (
|
|||||||
|
|
||||||
"github.com/mailru/easyjson"
|
"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/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
const CATEGORY_FILTER_ATTRIBUTES = "FILTER ATTRIBUTES"
|
const CATEGORY_FILTER_ATTRIBUTES = "FILTER ATTRIBUTES"
|
||||||
@@ -124,23 +125,23 @@ example:
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
ArgsUsage: "[relay...]",
|
ArgsUsage: "[relay...]",
|
||||||
Action: func(c *cli.Context) error {
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
var pool *nostr.SimplePool
|
var pool *nostr.SimplePool
|
||||||
relayUrls := c.Args().Slice()
|
relayUrls := c.Args().Slice()
|
||||||
if len(relayUrls) > 0 {
|
if len(relayUrls) > 0 {
|
||||||
var relays []*nostr.Relay
|
var relays []*nostr.Relay
|
||||||
pool, relays = connectToAllRelays(c.Context, relayUrls, nostr.WithAuthHandler(func(evt *nostr.Event) error {
|
pool, relays = connectToAllRelays(ctx, relayUrls, nostr.WithAuthHandler(func(evt *nostr.Event) error {
|
||||||
if !c.Bool("auth") {
|
if !c.Bool("auth") {
|
||||||
return fmt.Errorf("auth not authorized")
|
return fmt.Errorf("auth not authorized")
|
||||||
}
|
}
|
||||||
sec, bunker, err := gatherSecretKeyOrBunkerFromArguments(c)
|
sec, bunker, err := gatherSecretKeyOrBunkerFromArguments(ctx, c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var pk string
|
var pk string
|
||||||
if bunker != nil {
|
if bunker != nil {
|
||||||
pk, err = bunker.GetPublicKey(c.Context)
|
pk, err = bunker.GetPublicKey(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get public key from bunker: %w", err)
|
return fmt.Errorf("failed to get public key from bunker: %w", err)
|
||||||
}
|
}
|
||||||
@@ -150,7 +151,7 @@ example:
|
|||||||
log("performing auth as %s...\n", pk)
|
log("performing auth as %s...\n", pk)
|
||||||
|
|
||||||
if bunker != nil {
|
if bunker != nil {
|
||||||
return bunker.SignEvent(c.Context, evt)
|
return bunker.SignEvent(ctx, evt)
|
||||||
} else {
|
} else {
|
||||||
return evt.Sign(sec)
|
return evt.Sign(sec)
|
||||||
}
|
}
|
||||||
@@ -175,7 +176,7 @@ example:
|
|||||||
filter := nostr.Filter{}
|
filter := nostr.Filter{}
|
||||||
if stdinFilter != "" {
|
if stdinFilter != "" {
|
||||||
if err := easyjson.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(ctx, "invalid filter '%s' received from stdin: %s", stdinFilter, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -186,8 +187,8 @@ example:
|
|||||||
if ids := c.StringSlice("id"); len(ids) > 0 {
|
if ids := c.StringSlice("id"); len(ids) > 0 {
|
||||||
filter.IDs = append(filter.IDs, ids...)
|
filter.IDs = append(filter.IDs, ids...)
|
||||||
}
|
}
|
||||||
if kinds := c.IntSlice("kind"); len(kinds) > 0 {
|
for _, kind64 := range c.IntSlice("kind") {
|
||||||
filter.Kinds = append(filter.Kinds, kinds...)
|
filter.Kinds = append(filter.Kinds, int(kind64))
|
||||||
}
|
}
|
||||||
if search := c.String("search"); search != "" {
|
if search := c.String("search"); search != "" {
|
||||||
filter.Search = search
|
filter.Search = search
|
||||||
@@ -245,7 +246,7 @@ example:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if limit := c.Int("limit"); limit != 0 {
|
if limit := c.Int("limit"); limit != 0 {
|
||||||
filter.Limit = limit
|
filter.Limit = int(limit)
|
||||||
} else if c.IsSet("limit") || c.Bool("stream") {
|
} else if c.IsSet("limit") || c.Bool("stream") {
|
||||||
filter.LimitZero = true
|
filter.LimitZero = true
|
||||||
}
|
}
|
||||||
@@ -255,7 +256,7 @@ example:
|
|||||||
if c.Bool("stream") {
|
if c.Bool("stream") {
|
||||||
fn = pool.SubMany
|
fn = pool.SubMany
|
||||||
}
|
}
|
||||||
for ie := range fn(c.Context, relayUrls, nostr.Filters{filter}) {
|
for ie := range fn(ctx, relayUrls, nostr.Filters{filter}) {
|
||||||
stdout(ie.Event)
|
stdout(ie.Event)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -272,7 +273,7 @@ example:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exitIfLineProcessingError(c)
|
exitIfLineProcessingError(ctx)
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
13
verify.go
13
verify.go
@@ -1,10 +1,11 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/nbd-wtf/go-nostr"
|
"github.com/nbd-wtf/go-nostr"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var verify = &cli.Command{
|
var verify = &cli.Command{
|
||||||
@@ -14,28 +15,28 @@ var verify = &cli.Command{
|
|||||||
echo '{"id":"a889df6a387419ff204305f4c2d296ee328c3cd4f8b62f205648a541b4554dfb","pubkey":"c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5","created_at":1698623783,"kind":1,"tags":[],"content":"hello from the nostr army knife","sig":"84876e1ee3e726da84e5d195eb79358b2b3eaa4d9bd38456fde3e8a2af3f1cd4cda23f23fda454869975b3688797d4c66e12f4c51c1b43c6d2997c5e61865661"}' | nak verify
|
echo '{"id":"a889df6a387419ff204305f4c2d296ee328c3cd4f8b62f205648a541b4554dfb","pubkey":"c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5","created_at":1698623783,"kind":1,"tags":[],"content":"hello from the nostr army knife","sig":"84876e1ee3e726da84e5d195eb79358b2b3eaa4d9bd38456fde3e8a2af3f1cd4cda23f23fda454869975b3688797d4c66e12f4c51c1b43c6d2997c5e61865661"}' | nak verify
|
||||||
|
|
||||||
it outputs nothing if the verification is successful.`,
|
it outputs nothing if the verification is successful.`,
|
||||||
Action: func(c *cli.Context) error {
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
for stdinEvent := range getStdinLinesOrArguments(c.Args()) {
|
for stdinEvent := range getStdinLinesOrArguments(c.Args()) {
|
||||||
evt := nostr.Event{}
|
evt := nostr.Event{}
|
||||||
if stdinEvent != "" {
|
if stdinEvent != "" {
|
||||||
if err := json.Unmarshal([]byte(stdinEvent), &evt); err != nil {
|
if err := json.Unmarshal([]byte(stdinEvent), &evt); err != nil {
|
||||||
lineProcessingError(c, "invalid event: %s", err)
|
lineProcessingError(ctx, "invalid event: %s", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if evt.GetID() != evt.ID {
|
if evt.GetID() != evt.ID {
|
||||||
lineProcessingError(c, "invalid .id, expected %s, got %s", evt.GetID(), evt.ID)
|
lineProcessingError(ctx, "invalid .id, expected %s, got %s", evt.GetID(), evt.ID)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if ok, err := evt.CheckSignature(); !ok {
|
if ok, err := evt.CheckSignature(); !ok {
|
||||||
lineProcessingError(c, "invalid signature: %s", err)
|
lineProcessingError(ctx, "invalid signature: %s", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exitIfLineProcessingError(c)
|
exitIfLineProcessingError(ctx)
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user