mirror of
https://github.com/fiatjaf/nak.git
synced 2025-12-08 16:48:51 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d95b6f50ff | ||
|
|
200e4e61f7 | ||
|
|
714d65312c | ||
|
|
5722061bf3 | ||
|
|
6f72d3c133 | ||
|
|
78932833df | ||
|
|
31b42c3499 |
24
LICENSE
Normal file
24
LICENSE
Normal file
@@ -0,0 +1,24 @@
|
||||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <https://unlicense.org>
|
||||
@@ -76,3 +76,8 @@ publishing to wss://relayable.org... success.
|
||||
~> echo '{"content":"hello world","created_at":1698923350,"id":"05bd99d54cb835f327e0092c4275ee44c7ff51219eff417c19f70c9e2c53ad5a","kind":1,"pubkey":"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798","sig":"0a04a296321ed933858577f36fb2fb9a0933e966f9ee32b539493f5a4d00120891b1ca9152ebfbc04fb403bdaa7c73f415e7c4954e55726b4b4fa8cebf008cd6","tags":[]}' | nak verify
|
||||
invalid .id, expected 05bd99d54cb835f427e0092c4275ee44c7ff51219eff417c19f70c9e2c53ad5a, got 05bd99d54cb835f327e0092c4275ee44c7ff51219eff417c19f70c9e2c53ad5a
|
||||
```
|
||||
|
||||
### fetch all quoted events by a given pubkey in their last 100 notes
|
||||
```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
|
||||
```
|
||||
|
||||
72
decode.go
72
decode.go
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip19"
|
||||
"github.com/nbd-wtf/go-nostr/sdk"
|
||||
sdk "github.com/nbd-wtf/nostr-sdk"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
@@ -34,43 +34,45 @@ var decode = &cli.Command{
|
||||
},
|
||||
ArgsUsage: "<npub | nprofile | nip05 | nevent | naddr | nsec>",
|
||||
Action: func(c *cli.Context) error {
|
||||
args := c.Args()
|
||||
if args.Len() != 1 {
|
||||
return fmt.Errorf("invalid number of arguments, need just one")
|
||||
}
|
||||
input := args.First()
|
||||
if strings.HasPrefix(input, "nostr:") {
|
||||
input = input[6:]
|
||||
}
|
||||
|
||||
var decodeResult DecodeResult
|
||||
if b, err := hex.DecodeString(input); err == nil {
|
||||
if len(b) == 64 {
|
||||
decodeResult.HexResult.PossibleTypes = []string{"sig"}
|
||||
decodeResult.HexResult.Signature = hex.EncodeToString(b)
|
||||
} else if len(b) == 32 {
|
||||
decodeResult.HexResult.PossibleTypes = []string{"pubkey", "private_key", "event_id"}
|
||||
decodeResult.HexResult.ID = hex.EncodeToString(b)
|
||||
decodeResult.HexResult.PrivateKey = hex.EncodeToString(b)
|
||||
decodeResult.HexResult.PublicKey = hex.EncodeToString(b)
|
||||
} else {
|
||||
return fmt.Errorf("hex string with invalid number of bytes: %d", len(b))
|
||||
for input := range getStdinLinesOrFirstArgument(c) {
|
||||
if strings.HasPrefix(input, "nostr:") {
|
||||
input = input[6:]
|
||||
}
|
||||
} else if evp := sdk.InputToEventPointer(input); evp != nil {
|
||||
decodeResult = DecodeResult{EventPointer: evp}
|
||||
} else if pp := sdk.InputToProfile(c.Context, input); pp != nil {
|
||||
decodeResult = DecodeResult{ProfilePointer: pp}
|
||||
} else if prefix, value, err := nip19.Decode(input); err == nil && prefix == "naddr" {
|
||||
ep := value.(nostr.EntityPointer)
|
||||
decodeResult = DecodeResult{EntityPointer: &ep}
|
||||
} else if prefix, value, err := nip19.Decode(input); err == nil && prefix == "nsec" {
|
||||
decodeResult.PrivateKey.PrivateKey = value.(string)
|
||||
decodeResult.PrivateKey.PublicKey, _ = nostr.GetPublicKey(value.(string))
|
||||
} else {
|
||||
return fmt.Errorf("couldn't decode input")
|
||||
|
||||
var decodeResult DecodeResult
|
||||
if b, err := hex.DecodeString(input); err == nil {
|
||||
if len(b) == 64 {
|
||||
decodeResult.HexResult.PossibleTypes = []string{"sig"}
|
||||
decodeResult.HexResult.Signature = hex.EncodeToString(b)
|
||||
} else if len(b) == 32 {
|
||||
decodeResult.HexResult.PossibleTypes = []string{"pubkey", "private_key", "event_id"}
|
||||
decodeResult.HexResult.ID = hex.EncodeToString(b)
|
||||
decodeResult.HexResult.PrivateKey = hex.EncodeToString(b)
|
||||
decodeResult.HexResult.PublicKey = hex.EncodeToString(b)
|
||||
} else {
|
||||
lineProcessingError(c, "hex string with invalid number of bytes: %d", len(b))
|
||||
continue
|
||||
}
|
||||
} else if evp := sdk.InputToEventPointer(input); evp != nil {
|
||||
decodeResult = DecodeResult{EventPointer: evp}
|
||||
} else if pp := sdk.InputToProfile(c.Context, input); pp != nil {
|
||||
decodeResult = DecodeResult{ProfilePointer: pp}
|
||||
} else if prefix, value, err := nip19.Decode(input); err == nil && prefix == "naddr" {
|
||||
ep := value.(nostr.EntityPointer)
|
||||
decodeResult = DecodeResult{EntityPointer: &ep}
|
||||
} else if prefix, value, err := nip19.Decode(input); err == nil && prefix == "nsec" {
|
||||
decodeResult.PrivateKey.PrivateKey = value.(string)
|
||||
decodeResult.PrivateKey.PublicKey, _ = nostr.GetPublicKey(value.(string))
|
||||
} else {
|
||||
lineProcessingError(c, "couldn't decode input '%s': %s", input, err)
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Println(decodeResult.JSON())
|
||||
|
||||
}
|
||||
|
||||
fmt.Println(decodeResult.JSON())
|
||||
exitIfLineProcessingError(c)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
213
encode.go
213
encode.go
@@ -1,9 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr/nip19"
|
||||
"github.com/urfave/cli/v2"
|
||||
@@ -28,36 +26,44 @@ var encode = &cli.Command{
|
||||
Subcommands: []*cli.Command{
|
||||
{
|
||||
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 {
|
||||
target := getStdinOrFirstArgument(c)
|
||||
if err := validate32BytesHex(target); err != nil {
|
||||
return err
|
||||
for target := range getStdinLinesOrFirstArgument(c) {
|
||||
if err := validate32BytesHex(target); err != nil {
|
||||
lineProcessingError(c, "invalid public key: %s", target, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if npub, err := nip19.EncodePublicKey(target); err == nil {
|
||||
fmt.Println(npub)
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if npub, err := nip19.EncodePublicKey(target); err == nil {
|
||||
fmt.Println(npub)
|
||||
return nil
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
exitIfLineProcessingError(c)
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "nsec",
|
||||
Usage: "encode a hex private key into bech32 'nsec' format",
|
||||
Action: func(c *cli.Context) error {
|
||||
target := getStdinOrFirstArgument(c)
|
||||
if err := validate32BytesHex(target); err != nil {
|
||||
return err
|
||||
for target := range getStdinLinesOrFirstArgument(c) {
|
||||
if err := validate32BytesHex(target); err != nil {
|
||||
lineProcessingError(c, "invalid private key: %s", target, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if npub, err := nip19.EncodePrivateKey(target); err == nil {
|
||||
fmt.Println(npub)
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if npub, err := nip19.EncodePrivateKey(target); err == nil {
|
||||
fmt.Println(npub)
|
||||
return nil
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
exitIfLineProcessingError(c)
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -71,22 +77,26 @@ var encode = &cli.Command{
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
target := getStdinOrFirstArgument(c)
|
||||
if err := validate32BytesHex(target); err != nil {
|
||||
return err
|
||||
for target := range getStdinLinesOrFirstArgument(c) {
|
||||
if err := validate32BytesHex(target); err != nil {
|
||||
lineProcessingError(c, "invalid public key: %s", target, err)
|
||||
continue
|
||||
}
|
||||
|
||||
relays := c.StringSlice("relay")
|
||||
if err := validateRelayURLs(relays); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if npub, err := nip19.EncodeProfile(target, relays); err == nil {
|
||||
fmt.Println(npub)
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
relays := c.StringSlice("relay")
|
||||
if err := validateRelayURLs(relays); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if npub, err := nip19.EncodeProfile(target, relays); err == nil {
|
||||
fmt.Println(npub)
|
||||
return nil
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
exitIfLineProcessingError(c)
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -104,29 +114,33 @@ var encode = &cli.Command{
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
target := getStdinOrFirstArgument(c)
|
||||
if err := validate32BytesHex(target); err != nil {
|
||||
return err
|
||||
}
|
||||
for target := range getStdinLinesOrFirstArgument(c) {
|
||||
if err := validate32BytesHex(target); err != nil {
|
||||
lineProcessingError(c, "invalid event id: %s", target, err)
|
||||
continue
|
||||
}
|
||||
|
||||
author := c.String("author")
|
||||
if author != "" {
|
||||
if err := validate32BytesHex(author); err != nil {
|
||||
author := c.String("author")
|
||||
if author != "" {
|
||||
if err := validate32BytesHex(author); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
relays := c.StringSlice("relay")
|
||||
if err := validateRelayURLs(relays); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if npub, err := nip19.EncodeEvent(target, relays, author); err == nil {
|
||||
fmt.Println(npub)
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
relays := c.StringSlice("relay")
|
||||
if err := validateRelayURLs(relays); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if npub, err := nip19.EncodeEvent(target, relays, author); err == nil {
|
||||
fmt.Println(npub)
|
||||
return nil
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
exitIfLineProcessingError(c)
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -136,7 +150,7 @@ var encode = &cli.Command{
|
||||
&cli.StringFlag{
|
||||
Name: "identifier",
|
||||
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,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
@@ -158,64 +172,61 @@ var encode = &cli.Command{
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
pubkey := c.String("pubkey")
|
||||
if err := validate32BytesHex(pubkey); err != nil {
|
||||
return err
|
||||
for d := range getStdinLinesOrBlank() {
|
||||
pubkey := c.String("pubkey")
|
||||
if err := validate32BytesHex(pubkey); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
kind := c.Int("kind")
|
||||
if kind < 30000 || kind >= 40000 {
|
||||
return fmt.Errorf("kind must be between 30000 and 39999, as per NIP-16, got %d", kind)
|
||||
}
|
||||
|
||||
if d == "" {
|
||||
d = c.String("identifier")
|
||||
if d == "" {
|
||||
lineProcessingError(c, "\"d\" tag identifier can't be empty")
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
relays := c.StringSlice("relay")
|
||||
if err := validateRelayURLs(relays); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if npub, err := nip19.EncodeEntity(pubkey, kind, d, relays); err == nil {
|
||||
fmt.Println(npub)
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
kind := c.Int("kind")
|
||||
if kind < 30000 || kind >= 40000 {
|
||||
return fmt.Errorf("kind must be between 30000 and 39999, as per NIP-16, got %d", kind)
|
||||
}
|
||||
|
||||
d := c.String("identifier")
|
||||
if d == "" {
|
||||
return fmt.Errorf("\"d\" tag identifier can't be empty")
|
||||
}
|
||||
|
||||
relays := c.StringSlice("relay")
|
||||
if err := validateRelayURLs(relays); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if npub, err := nip19.EncodeEntity(pubkey, kind, d, relays); err == nil {
|
||||
fmt.Println(npub)
|
||||
return nil
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
exitIfLineProcessingError(c)
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "note",
|
||||
Usage: "generate note1 event codes (not recommended)",
|
||||
Action: func(c *cli.Context) error {
|
||||
target := getStdinOrFirstArgument(c)
|
||||
if err := validate32BytesHex(target); err != nil {
|
||||
return err
|
||||
for target := range getStdinLinesOrFirstArgument(c) {
|
||||
if err := validate32BytesHex(target); err != nil {
|
||||
lineProcessingError(c, "invalid event id: %s", target, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if note, err := nip19.EncodeNote(target); err == nil {
|
||||
fmt.Println(note)
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if npub, err := nip19.EncodeNote(target); err == nil {
|
||||
fmt.Println(npub)
|
||||
return nil
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
exitIfLineProcessingError(c)
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func validate32BytesHex(target string) error {
|
||||
if _, err := hex.DecodeString(target); err != nil {
|
||||
return fmt.Errorf("target '%s' is not valid hex: %s", target, err)
|
||||
}
|
||||
if len(target) != 64 {
|
||||
return fmt.Errorf("expected '%s' to be 64 characters (32 bytes), got %d", target, len(target))
|
||||
}
|
||||
if strings.ToLower(target) != target {
|
||||
return fmt.Errorf("expected target to be all lowercase hex. try again with '%s'", strings.ToLower(target))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
217
event.go
217
event.go
@@ -9,8 +9,10 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bgentry/speakeasy"
|
||||
"github.com/mailru/easyjson"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip19"
|
||||
"github.com/nbd-wtf/go-nostr/nson"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
@@ -35,10 +37,14 @@ example:
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "sec",
|
||||
Usage: "secret key to sign the event",
|
||||
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: "envelope",
|
||||
Usage: "print the event enveloped in a [\"EVENT\", ...] message ready to be sent to a relay",
|
||||
@@ -90,115 +96,146 @@ example:
|
||||
},
|
||||
ArgsUsage: "[relay...]",
|
||||
Action: func(c *cli.Context) error {
|
||||
evt := nostr.Event{
|
||||
Tags: make(nostr.Tags, 0, 3),
|
||||
}
|
||||
|
||||
mustRehashAndResign := false
|
||||
|
||||
if stdinEvent := getStdin(); stdinEvent != "" {
|
||||
if err := json.Unmarshal([]byte(stdinEvent), &evt); err != nil {
|
||||
return fmt.Errorf("invalid event received from stdin: %w", err)
|
||||
// gather the secret key first
|
||||
sec := c.String("sec")
|
||||
if c.Bool("prompt-sec") {
|
||||
if isPiped() {
|
||||
return fmt.Errorf("can't prompt for a secret key when processing data from a pipe, try again without --prompt-sec")
|
||||
}
|
||||
var err error
|
||||
sec, err = speakeasy.FAsk(os.Stderr, "type your secret key as nsec or hex: ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get secret key: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if kind := c.Int("kind"); kind != 0 {
|
||||
evt.Kind = kind
|
||||
mustRehashAndResign = true
|
||||
} else if evt.Kind == 0 {
|
||||
evt.Kind = 1
|
||||
mustRehashAndResign = true
|
||||
}
|
||||
|
||||
if content := c.String("content"); content != "" {
|
||||
evt.Content = content
|
||||
mustRehashAndResign = true
|
||||
} else if evt.Content == "" && evt.Kind == 1 {
|
||||
evt.Content = "hello from the nostr army knife"
|
||||
mustRehashAndResign = true
|
||||
}
|
||||
|
||||
tags := make(nostr.Tags, 0, 5)
|
||||
for _, tagFlag := range c.StringSlice("tag") {
|
||||
// tags are in the format key=value
|
||||
spl := strings.Split(tagFlag, "=")
|
||||
if len(spl) == 2 && len(spl[0]) > 0 {
|
||||
tag := nostr.Tag{spl[0]}
|
||||
// tags may also contain extra elements separated with a ";"
|
||||
spl2 := strings.Split(spl[1], ";")
|
||||
tag = append(tag, spl2...)
|
||||
// ~
|
||||
tags = append(tags, tag)
|
||||
if strings.HasPrefix(sec, "nsec1") {
|
||||
_, hex, err := nip19.Decode(sec)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid nsec: %w", err)
|
||||
}
|
||||
sec = hex.(string)
|
||||
}
|
||||
for _, etag := range c.StringSlice("e") {
|
||||
tags = append(tags, []string{"e", etag})
|
||||
mustRehashAndResign = true
|
||||
if len(sec) > 64 {
|
||||
return fmt.Errorf("invalid secret key: too large")
|
||||
}
|
||||
for _, ptag := range c.StringSlice("p") {
|
||||
tags = append(tags, []string{"p", ptag})
|
||||
mustRehashAndResign = true
|
||||
}
|
||||
if len(tags) > 0 {
|
||||
for _, tag := range tags {
|
||||
evt.Tags = append(evt.Tags, tag)
|
||||
}
|
||||
mustRehashAndResign = true
|
||||
sec = strings.Repeat("0", 64-len(sec)) + sec // left-pad
|
||||
if err := validate32BytesHex(sec); err != nil {
|
||||
return fmt.Errorf("invalid secret key")
|
||||
}
|
||||
|
||||
if createdAt := c.String("created-at"); createdAt != "" {
|
||||
ts := time.Now()
|
||||
if createdAt != "now" {
|
||||
if v, err := strconv.ParseInt(createdAt, 10, 64); err != nil {
|
||||
return fmt.Errorf("failed to parse timestamp '%s': %w", createdAt, err)
|
||||
} else {
|
||||
ts = time.Unix(v, 0)
|
||||
// then process input and generate events
|
||||
for stdinEvent := range getStdinLinesOrBlank() {
|
||||
evt := nostr.Event{
|
||||
Tags: make(nostr.Tags, 0, 3),
|
||||
}
|
||||
|
||||
mustRehashAndResign := false
|
||||
if stdinEvent != "" {
|
||||
if err := json.Unmarshal([]byte(stdinEvent), &evt); err != nil {
|
||||
lineProcessingError(c, "invalid event received from stdin: %s", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
evt.CreatedAt = nostr.Timestamp(ts.Unix())
|
||||
mustRehashAndResign = true
|
||||
} else if evt.CreatedAt == 0 {
|
||||
evt.CreatedAt = nostr.Now()
|
||||
mustRehashAndResign = true
|
||||
}
|
||||
|
||||
if evt.Sig == "" || mustRehashAndResign {
|
||||
if err := evt.Sign(c.String("sec")); err != nil {
|
||||
return fmt.Errorf("error signing with provided key: %w", err)
|
||||
if kind := c.Int("kind"); kind != 0 {
|
||||
evt.Kind = kind
|
||||
mustRehashAndResign = true
|
||||
} else if evt.Kind == 0 {
|
||||
evt.Kind = 1
|
||||
mustRehashAndResign = true
|
||||
}
|
||||
}
|
||||
|
||||
relays := c.Args().Slice()
|
||||
if len(relays) > 0 {
|
||||
fmt.Println(evt.String())
|
||||
for _, url := range relays {
|
||||
fmt.Fprintf(os.Stderr, "publishing to %s... ", url)
|
||||
if relay, err := nostr.RelayConnect(c.Context, url); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to connect: %s\n", err)
|
||||
} else {
|
||||
ctx, cancel := context.WithTimeout(c.Context, 10*time.Second)
|
||||
defer cancel()
|
||||
if status, err := relay.Publish(ctx, evt); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed: %s\n", err)
|
||||
if content := c.String("content"); content != "" {
|
||||
evt.Content = content
|
||||
mustRehashAndResign = true
|
||||
} else if evt.Content == "" && evt.Kind == 1 {
|
||||
evt.Content = "hello from the nostr army knife"
|
||||
mustRehashAndResign = true
|
||||
}
|
||||
|
||||
tags := make(nostr.Tags, 0, 5)
|
||||
for _, tagFlag := range c.StringSlice("tag") {
|
||||
// tags are in the format key=value
|
||||
spl := strings.Split(tagFlag, "=")
|
||||
if len(spl) == 2 && len(spl[0]) > 0 {
|
||||
tag := nostr.Tag{spl[0]}
|
||||
// tags may also contain extra elements separated with a ";"
|
||||
spl2 := strings.Split(spl[1], ";")
|
||||
tag = append(tag, spl2...)
|
||||
// ~
|
||||
tags = append(tags, tag)
|
||||
}
|
||||
}
|
||||
for _, etag := range c.StringSlice("e") {
|
||||
tags = append(tags, []string{"e", etag})
|
||||
mustRehashAndResign = true
|
||||
}
|
||||
for _, ptag := range c.StringSlice("p") {
|
||||
tags = append(tags, []string{"p", ptag})
|
||||
mustRehashAndResign = true
|
||||
}
|
||||
if len(tags) > 0 {
|
||||
for _, tag := range tags {
|
||||
evt.Tags = append(evt.Tags, tag)
|
||||
}
|
||||
mustRehashAndResign = true
|
||||
}
|
||||
|
||||
if createdAt := c.String("created-at"); createdAt != "" {
|
||||
ts := time.Now()
|
||||
if createdAt != "now" {
|
||||
if v, err := strconv.ParseInt(createdAt, 10, 64); err != nil {
|
||||
return fmt.Errorf("failed to parse timestamp '%s': %w", createdAt, err)
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "%s.\n", status)
|
||||
ts = time.Unix(v, 0)
|
||||
}
|
||||
}
|
||||
evt.CreatedAt = nostr.Timestamp(ts.Unix())
|
||||
mustRehashAndResign = true
|
||||
} else if evt.CreatedAt == 0 {
|
||||
evt.CreatedAt = nostr.Now()
|
||||
mustRehashAndResign = true
|
||||
}
|
||||
} else {
|
||||
var result string
|
||||
if c.Bool("envelope") {
|
||||
j, _ := json.Marshal([]any{"EVENT", evt})
|
||||
result = string(j)
|
||||
} else if c.Bool("nson") {
|
||||
result, _ = nson.Marshal(&evt)
|
||||
|
||||
if evt.Sig == "" || mustRehashAndResign {
|
||||
if err := evt.Sign(sec); err != nil {
|
||||
return fmt.Errorf("error signing with provided key: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
relays := c.Args().Slice()
|
||||
if len(relays) > 0 {
|
||||
fmt.Println(evt.String())
|
||||
for _, url := range relays {
|
||||
fmt.Fprintf(os.Stderr, "publishing to %s... ", url)
|
||||
if relay, err := nostr.RelayConnect(c.Context, url); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to connect: %s\n", err)
|
||||
} else {
|
||||
ctx, cancel := context.WithTimeout(c.Context, 10*time.Second)
|
||||
defer cancel()
|
||||
if status, err := relay.Publish(ctx, evt); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed: %s\n", err)
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "%s.\n", status)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
j, _ := easyjson.Marshal(&evt)
|
||||
result = string(j)
|
||||
var result string
|
||||
if c.Bool("envelope") {
|
||||
j, _ := json.Marshal([]any{"EVENT", evt})
|
||||
result = string(j)
|
||||
} else if c.Bool("nson") {
|
||||
result, _ = nson.Marshal(&evt)
|
||||
} else {
|
||||
j, _ := easyjson.Marshal(&evt)
|
||||
result = string(j)
|
||||
}
|
||||
fmt.Println(result)
|
||||
}
|
||||
fmt.Println(result)
|
||||
}
|
||||
|
||||
exitIfLineProcessingError(c)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
114
fetch.go
114
fetch.go
@@ -5,7 +5,7 @@ import (
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip19"
|
||||
"github.com/nbd-wtf/go-nostr/sdk"
|
||||
sdk "github.com/nbd-wtf/nostr-sdk"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
@@ -24,67 +24,71 @@ var fetch = &cli.Command{
|
||||
},
|
||||
ArgsUsage: "[nip19code]",
|
||||
Action: func(c *cli.Context) error {
|
||||
filter := nostr.Filter{}
|
||||
code := getStdinOrFirstArgument(c)
|
||||
for code := range getStdinLinesOrFirstArgument(c) {
|
||||
filter := nostr.Filter{}
|
||||
|
||||
prefix, value, err := nip19.Decode(code)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
relays := c.StringSlice("relay")
|
||||
if err := validateRelayURLs(relays); err != nil {
|
||||
return err
|
||||
}
|
||||
var authorHint string
|
||||
|
||||
switch prefix {
|
||||
case "nevent":
|
||||
v := value.(nostr.EventPointer)
|
||||
filter.IDs = append(filter.IDs, v.ID)
|
||||
if v.Author != "" {
|
||||
authorHint = v.Author
|
||||
prefix, value, err := nip19.Decode(code)
|
||||
if err != nil {
|
||||
lineProcessingError(c, "failed to decode: %s", err)
|
||||
continue
|
||||
}
|
||||
relays = v.Relays
|
||||
case "naddr":
|
||||
v := value.(nostr.EntityPointer)
|
||||
filter.Tags = nostr.TagMap{"d": []string{v.Identifier}}
|
||||
filter.Kinds = append(filter.Kinds, v.Kind)
|
||||
filter.Authors = append(filter.Authors, v.PublicKey)
|
||||
authorHint = v.PublicKey
|
||||
relays = v.Relays
|
||||
case "nprofile":
|
||||
v := value.(nostr.ProfilePointer)
|
||||
filter.Authors = append(filter.Authors, v.PublicKey)
|
||||
filter.Kinds = append(filter.Kinds, 0)
|
||||
authorHint = v.PublicKey
|
||||
relays = v.Relays
|
||||
case "npub":
|
||||
v := value.(string)
|
||||
filter.Authors = append(filter.Authors, v)
|
||||
filter.Kinds = append(filter.Kinds, 0)
|
||||
authorHint = v
|
||||
}
|
||||
|
||||
pool := nostr.NewSimplePool(c.Context)
|
||||
if authorHint != "" {
|
||||
relayList := sdk.FetchRelaysForPubkey(c.Context, pool, authorHint,
|
||||
"wss://purplepag.es", "wss://offchain.pub", "wss://public.relaying.io")
|
||||
for _, relayListItem := range relayList {
|
||||
if relayListItem.Outbox {
|
||||
relays = append(relays, relayListItem.URL)
|
||||
relays := c.StringSlice("relay")
|
||||
if err := validateRelayURLs(relays); err != nil {
|
||||
return err
|
||||
}
|
||||
var authorHint string
|
||||
|
||||
switch prefix {
|
||||
case "nevent":
|
||||
v := value.(nostr.EventPointer)
|
||||
filter.IDs = append(filter.IDs, v.ID)
|
||||
if v.Author != "" {
|
||||
authorHint = v.Author
|
||||
}
|
||||
relays = v.Relays
|
||||
case "naddr":
|
||||
v := value.(nostr.EntityPointer)
|
||||
filter.Tags = nostr.TagMap{"d": []string{v.Identifier}}
|
||||
filter.Kinds = append(filter.Kinds, v.Kind)
|
||||
filter.Authors = append(filter.Authors, v.PublicKey)
|
||||
authorHint = v.PublicKey
|
||||
relays = v.Relays
|
||||
case "nprofile":
|
||||
v := value.(nostr.ProfilePointer)
|
||||
filter.Authors = append(filter.Authors, v.PublicKey)
|
||||
filter.Kinds = append(filter.Kinds, 0)
|
||||
authorHint = v.PublicKey
|
||||
relays = v.Relays
|
||||
case "npub":
|
||||
v := value.(string)
|
||||
filter.Authors = append(filter.Authors, v)
|
||||
filter.Kinds = append(filter.Kinds, 0)
|
||||
authorHint = v
|
||||
}
|
||||
|
||||
pool := nostr.NewSimplePool(c.Context)
|
||||
if authorHint != "" {
|
||||
relayList := sdk.FetchRelaysForPubkey(c.Context, pool, authorHint,
|
||||
"wss://purplepag.es", "wss://offchain.pub", "wss://public.relaying.io")
|
||||
for _, relayListItem := range relayList {
|
||||
if relayListItem.Outbox {
|
||||
relays = append(relays, relayListItem.URL)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(relays) == 0 {
|
||||
lineProcessingError(c, "no relay hints found")
|
||||
continue
|
||||
}
|
||||
|
||||
for ie := range pool.SubManyEose(c.Context, relays, nostr.Filters{filter}) {
|
||||
fmt.Println(ie.Event)
|
||||
}
|
||||
}
|
||||
|
||||
if len(relays) == 0 {
|
||||
return fmt.Errorf("no relay hints found")
|
||||
}
|
||||
|
||||
for ie := range pool.SubManyEose(c.Context, relays, nostr.Filters{filter}) {
|
||||
fmt.Println(ie.Event)
|
||||
}
|
||||
|
||||
exitIfLineProcessingError(c)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
27
go.mod
27
go.mod
@@ -1,35 +1,40 @@
|
||||
module github.com/fiatjaf/nak
|
||||
|
||||
go 1.20
|
||||
go 1.21
|
||||
|
||||
toolchain go1.21.0
|
||||
|
||||
require (
|
||||
github.com/bgentry/speakeasy v0.1.0
|
||||
github.com/mailru/easyjson v0.7.7
|
||||
github.com/nbd-wtf/go-nostr v0.24.2
|
||||
github.com/nbd-wtf/go-nostr v0.25.3
|
||||
github.com/nbd-wtf/nostr-sdk v0.0.2
|
||||
github.com/urfave/cli/v2 v2.25.3
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect
|
||||
github.com/btcsuite/btcd/btcutil v1.1.3 // indirect
|
||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.1 // 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/decred/dcrd/crypto/blake256 v1.0.0 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.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/dgraph-io/ristretto v0.1.1 // indirect
|
||||
github.com/dustin/go-humanize v1.0.0 // indirect
|
||||
github.com/fiatjaf/eventstore v0.1.0 // indirect
|
||||
github.com/gobwas/httphead v0.1.0 // indirect
|
||||
github.com/gobwas/pool v0.2.1 // indirect
|
||||
github.com/gobwas/ws v1.2.0 // indirect
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect
|
||||
github.com/golang/glog v1.0.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/puzpuzpuz/xsync/v2 v2.5.0 // indirect
|
||||
github.com/puzpuzpuz/xsync/v2 v2.5.1 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/tidwall/gjson v1.14.4 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.0 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||
golang.org/x/exp v0.0.0-20221106115401-f9659909a136 // indirect
|
||||
golang.org/x/sys v0.6.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 // indirect
|
||||
golang.org/x/sys v0.8.0 // indirect
|
||||
)
|
||||
|
||||
47
go.sum
47
go.sum
@@ -1,18 +1,21 @@
|
||||
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.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/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA=
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE=
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k=
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU=
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U=
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04=
|
||||
github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A=
|
||||
github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE=
|
||||
github.com/btcsuite/btcd/btcutil v1.1.3 h1:xfbtw8lwpp0G6NwSHb+UE67ryTFHJAiNuipusjXSohQ=
|
||||
github.com/btcsuite/btcd/btcutil v1.1.3/go.mod h1:UR7dsSJzJUfMmFiiLlIrMq1lS9jh9EdCV7FStZSnpi0=
|
||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
|
||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U=
|
||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
|
||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 h1:KdUfX2zKommPRa+PD0sWZUyXe9w277ABlgELO7H04IM=
|
||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
|
||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
|
||||
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
|
||||
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
|
||||
@@ -22,18 +25,21 @@ github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku
|
||||
github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
|
||||
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
|
||||
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
|
||||
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/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 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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
|
||||
github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218=
|
||||
github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8=
|
||||
github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA=
|
||||
@@ -41,6 +47,8 @@ github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczC
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/fiatjaf/eventstore v0.1.0 h1:/g7VTw6dsXmjICD3rBuHNIvAammHJ5unrKJ71Dz+VTs=
|
||||
github.com/fiatjaf/eventstore v0.1.0/go.mod h1:juMei5HL3HJi6t7vZjj7VdEItDPu31+GLROepdUK4tw=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
||||
@@ -49,8 +57,9 @@ github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.2.0 h1:u0p9s3xLYpZCA1z5JgCkMeB34CKCMMQbM+G8Ii7YD0I=
|
||||
github.com/gobwas/ws v1.2.0/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
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.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=
|
||||
@@ -71,8 +80,10 @@ github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlT
|
||||
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/nbd-wtf/go-nostr v0.24.2 h1:1PdFED7uHh3BlXfDVD96npBc0YAgj9hPT+l6NWog4kc=
|
||||
github.com/nbd-wtf/go-nostr v0.24.2/go.mod h1:eE8Qf8QszZbCd9arBQyotXqATNUElWsTEEx+LLORhyQ=
|
||||
github.com/nbd-wtf/go-nostr v0.25.3 h1:RPPh4cOosw0OZi5KG627pZ3GlKxiKsjARluzen/mB9g=
|
||||
github.com/nbd-wtf/go-nostr v0.25.3/go.mod h1:bkffJI+x914sPQWum9ZRUn66D7NpDnAoWo1yICvj3/0=
|
||||
github.com/nbd-wtf/nostr-sdk v0.0.2 h1:mZIeti+DOF0D1179q+NLL/h0LVMMOPRQAYpOuUrn5Zk=
|
||||
github.com/nbd-wtf/nostr-sdk v0.0.2/go.mod h1:KQZOtzcrXBlVhpZYG1tw83ADIONNMMPjUU3ZAH5U2RY=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
@@ -86,14 +97,15 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/puzpuzpuz/xsync/v2 v2.5.0 h1:2k4qrO/orvmEXZ3hmtHqIy9XaQtPTwzMZk1+iErpE8c=
|
||||
github.com/puzpuzpuz/xsync/v2 v2.5.0/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2znR9qmR35kU=
|
||||
github.com/puzpuzpuz/xsync/v2 v2.5.1 h1:mVGYAvzDSu52+zaGyNjC+24Xw2bQi3kTr4QJ6N9pIIU=
|
||||
github.com/puzpuzpuz/xsync/v2 v2.5.1/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2znR9qmR35kU=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
|
||||
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
|
||||
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
@@ -108,14 +120,15 @@ github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsr
|
||||
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20221106115401-f9659909a136 h1:Fq7F/w7MAa1KJ5bt2aJ62ihqp9HDcRuyILskkpIAurw=
|
||||
golang.org/x/exp v0.0.0-20221106115401-f9659909a136/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 h1:5llv2sWeaMSnA3w2kS57ouQQ4pudlXrR0dCgw51QK9o=
|
||||
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
|
||||
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc h1:zK/HqS5bZxDptfPJNq8v7vJfXtkU7r9TLIoSr1bXaP4=
|
||||
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -127,8 +140,9 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
@@ -150,3 +164,4 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
87
helpers.go
87
helpers.go
@@ -1,9 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
@@ -11,24 +12,57 @@ import (
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func getStdin() string {
|
||||
const (
|
||||
LINE_PROCESSING_ERROR = iota
|
||||
)
|
||||
|
||||
func isPiped() bool {
|
||||
stat, _ := os.Stdin.Stat()
|
||||
if (stat.Mode() & os.ModeCharDevice) == 0 {
|
||||
read := bytes.NewBuffer(make([]byte, 0, 1000))
|
||||
_, err := io.Copy(read, os.Stdin)
|
||||
if err == nil {
|
||||
return strings.TrimSpace(read.String())
|
||||
}
|
||||
}
|
||||
return ""
|
||||
return stat.Mode()&os.ModeCharDevice == 0
|
||||
}
|
||||
|
||||
func getStdinOrFirstArgument(c *cli.Context) string {
|
||||
func getStdinLinesOrBlank() chan string {
|
||||
multi := make(chan string)
|
||||
if hasStdinLines := writeStdinLinesOrNothing(multi); !hasStdinLines {
|
||||
single := make(chan string, 1)
|
||||
single <- ""
|
||||
close(single)
|
||||
return single
|
||||
} else {
|
||||
return multi
|
||||
}
|
||||
}
|
||||
|
||||
func getStdinLinesOrFirstArgument(c *cli.Context) chan string {
|
||||
// try the first argument
|
||||
target := c.Args().First()
|
||||
if target != "" {
|
||||
return target
|
||||
single := make(chan string, 1)
|
||||
single <- target
|
||||
return single
|
||||
}
|
||||
|
||||
// try the stdin
|
||||
multi := make(chan string)
|
||||
writeStdinLinesOrNothing(multi)
|
||||
return multi
|
||||
}
|
||||
|
||||
func writeStdinLinesOrNothing(ch chan string) (hasStdinLines bool) {
|
||||
if isPiped() {
|
||||
// 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 getStdin()
|
||||
}
|
||||
|
||||
func validateRelayURLs(wsurls []string) error {
|
||||
@@ -49,3 +83,28 @@ func validateRelayURLs(wsurls []string) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validate32BytesHex(target string) error {
|
||||
if _, err := hex.DecodeString(target); err != nil {
|
||||
return fmt.Errorf("target '%s' is not valid hex: %s", target, err)
|
||||
}
|
||||
if len(target) != 64 {
|
||||
return fmt.Errorf("expected '%s' to be 64 characters (32 bytes), got %d", target, len(target))
|
||||
}
|
||||
if strings.ToLower(target) != target {
|
||||
return fmt.Errorf("expected target to be all lowercase hex. try again with '%s'", strings.ToLower(target))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func lineProcessingError(c *cli.Context, msg string, args ...any) {
|
||||
c.Context = context.WithValue(c.Context, LINE_PROCESSING_ERROR, true)
|
||||
fmt.Fprintf(os.Stderr, msg+"\n", args...)
|
||||
}
|
||||
|
||||
func exitIfLineProcessingError(c *cli.Context) {
|
||||
if val := c.Context.Value(LINE_PROCESSING_ERROR); val != nil && val.(bool) {
|
||||
os.Exit(123)
|
||||
}
|
||||
}
|
||||
|
||||
154
req.go
154
req.go
@@ -95,87 +95,91 @@ example:
|
||||
},
|
||||
ArgsUsage: "[relay...]",
|
||||
Action: func(c *cli.Context) error {
|
||||
filter := nostr.Filter{}
|
||||
if stdinFilter := getStdin(); stdinFilter != "" {
|
||||
if err := json.Unmarshal([]byte(stdinFilter), &filter); err != nil {
|
||||
return fmt.Errorf("invalid filter received from stdin: %w", err)
|
||||
for stdinFilter := range getStdinLinesOrBlank() {
|
||||
filter := nostr.Filter{}
|
||||
if stdinFilter != "" {
|
||||
if err := json.Unmarshal([]byte(stdinFilter), &filter); err != nil {
|
||||
lineProcessingError(c, "invalid filter '%s' received from stdin: %s", stdinFilter, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if authors := c.StringSlice("author"); len(authors) > 0 {
|
||||
filter.Authors = append(filter.Authors, authors...)
|
||||
}
|
||||
if ids := c.StringSlice("id"); len(ids) > 0 {
|
||||
filter.IDs = append(filter.IDs, ids...)
|
||||
}
|
||||
if kinds := c.IntSlice("kind"); len(kinds) > 0 {
|
||||
filter.Kinds = append(filter.Kinds, kinds...)
|
||||
}
|
||||
if search := c.String("search"); search != "" {
|
||||
filter.Search = search
|
||||
}
|
||||
tags := make([][]string, 0, 5)
|
||||
for _, tagFlag := range c.StringSlice("tag") {
|
||||
spl := strings.Split(tagFlag, "=")
|
||||
if len(spl) == 2 && len(spl[0]) == 1 {
|
||||
tags = append(tags, spl)
|
||||
if authors := c.StringSlice("author"); len(authors) > 0 {
|
||||
filter.Authors = append(filter.Authors, authors...)
|
||||
}
|
||||
if ids := c.StringSlice("id"); len(ids) > 0 {
|
||||
filter.IDs = append(filter.IDs, ids...)
|
||||
}
|
||||
if kinds := c.IntSlice("kind"); len(kinds) > 0 {
|
||||
filter.Kinds = append(filter.Kinds, kinds...)
|
||||
}
|
||||
if search := c.String("search"); search != "" {
|
||||
filter.Search = search
|
||||
}
|
||||
tags := make([][]string, 0, 5)
|
||||
for _, tagFlag := range c.StringSlice("tag") {
|
||||
spl := strings.Split(tagFlag, "=")
|
||||
if len(spl) == 2 && len(spl[0]) == 1 {
|
||||
tags = append(tags, spl)
|
||||
} else {
|
||||
return fmt.Errorf("invalid --tag '%s'", tagFlag)
|
||||
}
|
||||
}
|
||||
for _, etag := range c.StringSlice("e") {
|
||||
tags = append(tags, []string{"e", etag})
|
||||
}
|
||||
for _, ptag := range c.StringSlice("p") {
|
||||
tags = append(tags, []string{"p", ptag})
|
||||
}
|
||||
|
||||
if len(tags) > 0 && filter.Tags == nil {
|
||||
filter.Tags = make(nostr.TagMap)
|
||||
}
|
||||
|
||||
for _, tag := range tags {
|
||||
if _, ok := filter.Tags[tag[0]]; !ok {
|
||||
filter.Tags[tag[0]] = make([]string, 0, 3)
|
||||
}
|
||||
filter.Tags[tag[0]] = append(filter.Tags[tag[0]], tag[1])
|
||||
}
|
||||
|
||||
if since := c.Int("since"); since != 0 {
|
||||
ts := nostr.Timestamp(since)
|
||||
filter.Since = &ts
|
||||
}
|
||||
if until := c.Int("until"); until != 0 {
|
||||
ts := nostr.Timestamp(until)
|
||||
filter.Until = &ts
|
||||
}
|
||||
if limit := c.Int("limit"); limit != 0 {
|
||||
filter.Limit = limit
|
||||
}
|
||||
|
||||
relays := c.Args().Slice()
|
||||
if len(relays) > 0 {
|
||||
pool := nostr.NewSimplePool(c.Context)
|
||||
fn := pool.SubManyEose
|
||||
if c.Bool("stream") {
|
||||
fn = pool.SubMany
|
||||
}
|
||||
for ie := range fn(c.Context, relays, nostr.Filters{filter}) {
|
||||
fmt.Println(ie.Event)
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("invalid --tag '%s'", tagFlag)
|
||||
// no relays given, will just print the filter
|
||||
var result string
|
||||
if c.Bool("bare") {
|
||||
result = filter.String()
|
||||
} else {
|
||||
j, _ := json.Marshal([]any{"REQ", "nak", filter})
|
||||
result = string(j)
|
||||
}
|
||||
|
||||
fmt.Println(result)
|
||||
}
|
||||
}
|
||||
for _, etag := range c.StringSlice("e") {
|
||||
tags = append(tags, []string{"e", etag})
|
||||
}
|
||||
for _, ptag := range c.StringSlice("p") {
|
||||
tags = append(tags, []string{"p", ptag})
|
||||
}
|
||||
|
||||
if len(tags) > 0 && filter.Tags == nil {
|
||||
filter.Tags = make(nostr.TagMap)
|
||||
}
|
||||
|
||||
for _, tag := range tags {
|
||||
if _, ok := filter.Tags[tag[0]]; !ok {
|
||||
filter.Tags[tag[0]] = make([]string, 0, 3)
|
||||
}
|
||||
filter.Tags[tag[0]] = append(filter.Tags[tag[0]], tag[1])
|
||||
}
|
||||
|
||||
if since := c.Int("since"); since != 0 {
|
||||
ts := nostr.Timestamp(since)
|
||||
filter.Since = &ts
|
||||
}
|
||||
if until := c.Int("until"); until != 0 {
|
||||
ts := nostr.Timestamp(until)
|
||||
filter.Until = &ts
|
||||
}
|
||||
if limit := c.Int("limit"); limit != 0 {
|
||||
filter.Limit = limit
|
||||
}
|
||||
|
||||
relays := c.Args().Slice()
|
||||
if len(relays) > 0 {
|
||||
pool := nostr.NewSimplePool(c.Context)
|
||||
fn := pool.SubManyEose
|
||||
if c.Bool("stream") {
|
||||
fn = pool.SubMany
|
||||
}
|
||||
for ie := range fn(c.Context, relays, nostr.Filters{filter}) {
|
||||
fmt.Println(ie.Event)
|
||||
}
|
||||
} else {
|
||||
// no relays given, will just print the filter
|
||||
var result string
|
||||
if c.Bool("bare") {
|
||||
result = filter.String()
|
||||
} else {
|
||||
j, _ := json.Marshal([]any{"REQ", "nak", filter})
|
||||
result = string(j)
|
||||
}
|
||||
|
||||
fmt.Println(result)
|
||||
}
|
||||
|
||||
exitIfLineProcessingError(c)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
31
verify.go
31
verify.go
@@ -2,7 +2,6 @@ package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/urfave/cli/v2"
|
||||
@@ -17,21 +16,27 @@ var verify = &cli.Command{
|
||||
it outputs nothing if the verification is successful.
|
||||
`,
|
||||
Action: func(c *cli.Context) error {
|
||||
evt := nostr.Event{}
|
||||
if stdinEvent := getStdin(); stdinEvent != "" {
|
||||
if err := json.Unmarshal([]byte(stdinEvent), &evt); err != nil {
|
||||
return fmt.Errorf("invalid JSON: %w", err)
|
||||
for stdinEvent := range getStdinLinesOrBlank() {
|
||||
evt := nostr.Event{}
|
||||
if stdinEvent != "" {
|
||||
if err := json.Unmarshal([]byte(stdinEvent), &evt); err != nil {
|
||||
lineProcessingError(c, "invalid event: %s", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if evt.GetID() != evt.ID {
|
||||
lineProcessingError(c, "invalid .id, expected %s, got %s", evt.GetID(), evt.ID)
|
||||
continue
|
||||
}
|
||||
|
||||
if ok, err := evt.CheckSignature(); !ok {
|
||||
lineProcessingError(c, "invalid signature: %s", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if evt.GetID() != evt.ID {
|
||||
return fmt.Errorf("invalid .id, expected %s, got %s", evt.GetID(), evt.ID)
|
||||
}
|
||||
|
||||
if ok, err := evt.CheckSignature(); !ok {
|
||||
return fmt.Errorf("invalid signature: %w", err)
|
||||
}
|
||||
|
||||
exitIfLineProcessingError(c)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user