support multiline stdin on decode, encode and fetch, and improve the helpers.

This commit is contained in:
fiatjaf 2023-11-08 12:50:36 -03:00
parent 5722061bf3
commit 714d65312c
No known key found for this signature in database
GPG Key ID: BAD43C4BE5C1A3A1
4 changed files with 238 additions and 200 deletions

View File

@ -34,11 +34,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 {
args := c.Args() for input := range getStdinLinesOrFirstArgument(c) {
if args.Len() != 1 {
return fmt.Errorf("invalid number of arguments, need just one")
}
input := args.First()
if strings.HasPrefix(input, "nostr:") { if strings.HasPrefix(input, "nostr:") {
input = input[6:] input = input[6:]
} }
@ -54,7 +50,8 @@ 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 {
return fmt.Errorf("hex string with invalid number of bytes: %d", len(b)) lineProcessingError(c, "hex string with invalid number of bytes: %d", len(b))
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}
@ -67,10 +64,15 @@ 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 {
return fmt.Errorf("couldn't decode input") lineProcessingError(c, "couldn't decode input '%s': %s", input, err)
continue
} }
fmt.Println(decodeResult.JSON()) fmt.Println(decodeResult.JSON())
}
exitIfLineProcessingError(c)
return nil return nil
}, },
} }

View File

@ -28,36 +28,44 @@ var encode = &cli.Command{
Subcommands: []*cli.Command{ Subcommands: []*cli.Command{
{ {
Name: "npub", Name: "npub",
Usage: "encode a hex private 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 {
target := getStdinOrFirstArgument(c) for target := range getStdinLinesOrFirstArgument(c) {
if err := validate32BytesHex(target); err != nil { if err := validate32BytesHex(target); err != nil {
return err lineProcessingError(c, "invalid public key: %s", target, err)
continue
} }
if npub, err := nip19.EncodePublicKey(target); err == nil { if npub, err := nip19.EncodePublicKey(target); err == nil {
fmt.Println(npub) fmt.Println(npub)
return nil
} else { } else {
return err return err
} }
}
exitIfLineProcessingError(c)
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(c *cli.Context) error {
target := getStdinOrFirstArgument(c) for target := range getStdinLinesOrFirstArgument(c) {
if err := validate32BytesHex(target); err != nil { if err := validate32BytesHex(target); err != nil {
return err lineProcessingError(c, "invalid private key: %s", target, err)
continue
} }
if npub, err := nip19.EncodePrivateKey(target); err == nil { if npub, err := nip19.EncodePrivateKey(target); err == nil {
fmt.Println(npub) fmt.Println(npub)
return nil
} else { } else {
return err return err
} }
}
exitIfLineProcessingError(c)
return nil
}, },
}, },
{ {
@ -71,9 +79,10 @@ var encode = &cli.Command{
}, },
}, },
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
target := getStdinOrFirstArgument(c) for target := range getStdinLinesOrFirstArgument(c) {
if err := validate32BytesHex(target); err != nil { if err := validate32BytesHex(target); err != nil {
return err lineProcessingError(c, "invalid public key: %s", target, err)
continue
} }
relays := c.StringSlice("relay") relays := c.StringSlice("relay")
@ -83,10 +92,13 @@ 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) fmt.Println(npub)
return nil
} else { } else {
return err return err
} }
}
exitIfLineProcessingError(c)
return nil
}, },
}, },
{ {
@ -104,9 +116,10 @@ var encode = &cli.Command{
}, },
}, },
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
target := getStdinOrFirstArgument(c) for target := range getStdinLinesOrFirstArgument(c) {
if err := validate32BytesHex(target); err != nil { if err := validate32BytesHex(target); err != nil {
return err lineProcessingError(c, "invalid event id: %s", target, err)
continue
} }
author := c.String("author") author := c.String("author")
@ -123,10 +136,13 @@ 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) fmt.Println(npub)
return nil
} else { } else {
return err return err
} }
}
exitIfLineProcessingError(c)
return nil
}, },
}, },
{ {
@ -136,7 +152,7 @@ var encode = &cli.Command{
&cli.StringFlag{ &cli.StringFlag{
Name: "identifier", Name: "identifier",
Aliases: []string{"d"}, Aliases: []string{"d"},
Usage: "the \"d\" tag identifier of this replaceable event", Usage: "the \"d\" tag identifier of this replaceable event -- can also be read from stdin",
Required: true, Required: true,
}, },
&cli.StringFlag{ &cli.StringFlag{
@ -158,6 +174,7 @@ var encode = &cli.Command{
}, },
}, },
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
for d := range getStdinLinesOrBlank() {
pubkey := c.String("pubkey") pubkey := c.String("pubkey")
if err := validate32BytesHex(pubkey); err != nil { if err := validate32BytesHex(pubkey); err != nil {
return err return err
@ -168,9 +185,12 @@ var encode = &cli.Command{
return fmt.Errorf("kind must be between 30000 and 39999, as per NIP-16, got %d", kind) return fmt.Errorf("kind must be between 30000 and 39999, as per NIP-16, got %d", kind)
} }
d := c.String("identifier")
if d == "" { if d == "" {
return fmt.Errorf("\"d\" tag identifier can't be empty") d = c.String("identifier")
if d == "" {
lineProcessingError(c, "\"d\" tag identifier can't be empty")
continue
}
} }
relays := c.StringSlice("relay") relays := c.StringSlice("relay")
@ -180,27 +200,34 @@ 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) fmt.Println(npub)
return nil
} else { } else {
return err return err
} }
}
exitIfLineProcessingError(c)
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(c *cli.Context) error {
target := getStdinOrFirstArgument(c) for target := range getStdinLinesOrFirstArgument(c) {
if err := validate32BytesHex(target); err != nil { if err := validate32BytesHex(target); err != nil {
return err lineProcessingError(c, "invalid event id: %s", target, err)
continue
} }
if npub, err := nip19.EncodeNote(target); err == nil { if note, err := nip19.EncodeNote(target); err == nil {
fmt.Println(npub) fmt.Println(note)
return nil
} else { } else {
return err return err
} }
}
exitIfLineProcessingError(c)
return nil
}, },
}, },
}, },

View File

@ -24,12 +24,13 @@ 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) {
filter := nostr.Filter{} filter := nostr.Filter{}
code := getStdinOrFirstArgument(c)
prefix, value, err := nip19.Decode(code) prefix, value, err := nip19.Decode(code)
if err != nil { if err != nil {
return err lineProcessingError(c, "failed to decode: %s", err)
continue
} }
relays := c.StringSlice("relay") relays := c.StringSlice("relay")
@ -78,13 +79,16 @@ var fetch = &cli.Command{
} }
if len(relays) == 0 { if len(relays) == 0 {
return fmt.Errorf("no relay hints found") lineProcessingError(c, "no relay hints found")
continue
} }
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) fmt.Println(ie.Event)
} }
}
exitIfLineProcessingError(c)
return nil return nil
}, },
} }

View File

@ -2,10 +2,8 @@ package main
import ( import (
"bufio" "bufio"
"bytes"
"context" "context"
"fmt" "fmt"
"io"
"net/url" "net/url"
"os" "os"
"strings" "strings"
@ -18,40 +16,47 @@ const (
) )
func getStdinLinesOrBlank() chan string { func getStdinLinesOrBlank() chan string {
ch := make(chan string) multi := make(chan string)
go func() { if hasStdinLines := writeStdinLinesOrNothing(multi); !hasStdinLines {
if stat, _ := os.Stdin.Stat(); stat.Mode()&os.ModeCharDevice == 0 { single := make(chan string, 1)
// piped single <- ""
scanner := bufio.NewScanner(os.Stdin) close(single)
for scanner.Scan() { return single
ch <- scanner.Text()
}
} else { } else {
// not piped return multi
ch <- ""
} }
close(ch)
}()
return ch
} }
func getStdinOrFirstArgument(c *cli.Context) string { func getStdinLinesOrFirstArgument(c *cli.Context) chan string {
// try the first argument // try the first argument
target := c.Args().First() target := c.Args().First()
if target != "" { if target != "" {
return target single := make(chan string, 1)
single <- target
return single
} }
// try the stdin // try the stdin
stat, _ := os.Stdin.Stat() multi := make(chan string)
if (stat.Mode() & os.ModeCharDevice) == 0 { writeStdinLinesOrNothing(multi)
read := bytes.NewBuffer(make([]byte, 0, 1000)) return multi
_, err := io.Copy(read, os.Stdin)
if err == nil {
return strings.TrimSpace(read.String())
} }
func writeStdinLinesOrNothing(ch chan string) (hasStdinLines bool) {
if stat, _ := os.Stdin.Stat(); stat.Mode()&os.ModeCharDevice == 0 {
// piped
go func() {
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
ch <- strings.TrimSpace(scanner.Text())
}
close(ch)
}()
return true
} else {
// not piped
return false
} }
return ""
} }
func validateRelayURLs(wsurls []string) error { func validateRelayURLs(wsurls []string) error {