2023-05-03 15:33:32 -04:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
2023-11-13 12:57:35 -05:00
|
|
|
"os"
|
2023-12-12 07:30:04 -05:00
|
|
|
"strconv"
|
2023-05-03 15:33:32 -04:00
|
|
|
"strings"
|
|
|
|
|
2023-11-28 13:18:43 -05:00
|
|
|
"github.com/mailru/easyjson"
|
2023-05-03 15:33:32 -04:00
|
|
|
"github.com/nbd-wtf/go-nostr"
|
|
|
|
"github.com/urfave/cli/v2"
|
|
|
|
)
|
|
|
|
|
|
|
|
const CATEGORY_FILTER_ATTRIBUTES = "FILTER ATTRIBUTES"
|
|
|
|
|
|
|
|
var req = &cli.Command{
|
|
|
|
Name: "req",
|
2023-05-23 15:01:09 -04:00
|
|
|
Usage: "generates encoded REQ messages and optionally use them to talk to relays",
|
2023-05-23 14:57:03 -04:00
|
|
|
Description: `outputs a NIP-01 Nostr filter. when a relay is not given, will print the filter, otherwise will connect to the given relay and send the filter.
|
|
|
|
|
2023-10-29 18:11:35 -04:00
|
|
|
example:
|
2023-10-29 20:48:18 -04:00
|
|
|
nak req -k 1 -l 15 wss://nostr.wine wss://nostr-pub.wellorder.net
|
|
|
|
nak req -k 0 -a 3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d wss://nos.lol | jq '.content | fromjson | .name'
|
2023-10-29 18:11:35 -04:00
|
|
|
|
|
|
|
it can also take a filter from stdin, optionally modify it with flags and send it to specific relays (or just print it).
|
|
|
|
|
|
|
|
example:
|
2023-11-20 13:01:51 -05:00
|
|
|
echo '{"kinds": [1], "#t": ["test"]}' | nak req -l 5 -k 4549 --tag t=spam wss://nostr-pub.wellorder.net`,
|
2023-05-03 15:33:32 -04:00
|
|
|
Flags: []cli.Flag{
|
|
|
|
&cli.StringSliceFlag{
|
|
|
|
Name: "author",
|
|
|
|
Aliases: []string{"a"},
|
|
|
|
Usage: "only accept events from these authors (pubkey as hex)",
|
|
|
|
Category: CATEGORY_FILTER_ATTRIBUTES,
|
|
|
|
},
|
|
|
|
&cli.StringSliceFlag{
|
|
|
|
Name: "id",
|
|
|
|
Aliases: []string{"i"},
|
|
|
|
Usage: "only accept events with these ids (hex)",
|
|
|
|
Category: CATEGORY_FILTER_ATTRIBUTES,
|
|
|
|
},
|
|
|
|
&cli.IntSliceFlag{
|
|
|
|
Name: "kind",
|
|
|
|
Aliases: []string{"k"},
|
|
|
|
Usage: "only accept events with these kind numbers",
|
|
|
|
Category: CATEGORY_FILTER_ATTRIBUTES,
|
|
|
|
},
|
|
|
|
&cli.StringSliceFlag{
|
|
|
|
Name: "tag",
|
|
|
|
Aliases: []string{"t"},
|
|
|
|
Usage: "takes a tag like -t e=<id>, only accept events with these tags",
|
|
|
|
Category: CATEGORY_FILTER_ATTRIBUTES,
|
|
|
|
},
|
|
|
|
&cli.StringSliceFlag{
|
2023-05-03 17:14:06 -04:00
|
|
|
Name: "e",
|
2023-05-03 15:33:32 -04:00
|
|
|
Usage: "shortcut for --tag e=<value>",
|
|
|
|
Category: CATEGORY_FILTER_ATTRIBUTES,
|
|
|
|
},
|
|
|
|
&cli.StringSliceFlag{
|
2023-05-03 17:14:06 -04:00
|
|
|
Name: "p",
|
2023-05-03 15:33:32 -04:00
|
|
|
Usage: "shortcut for --tag p=<value>",
|
|
|
|
Category: CATEGORY_FILTER_ATTRIBUTES,
|
|
|
|
},
|
2024-01-17 06:50:59 -05:00
|
|
|
&cli.StringSliceFlag{
|
|
|
|
Name: "d",
|
|
|
|
Usage: "shortcut for --tag d=<value>",
|
|
|
|
Category: CATEGORY_FILTER_ATTRIBUTES,
|
|
|
|
},
|
2023-12-12 07:30:04 -05:00
|
|
|
&cli.StringFlag{
|
2023-05-03 15:33:32 -04:00
|
|
|
Name: "since",
|
|
|
|
Aliases: []string{"s"},
|
|
|
|
Usage: "only accept events newer than this (unix timestamp)",
|
|
|
|
Category: CATEGORY_FILTER_ATTRIBUTES,
|
|
|
|
},
|
2023-12-12 07:34:50 -05:00
|
|
|
&cli.StringFlag{
|
2023-05-03 15:33:32 -04:00
|
|
|
Name: "until",
|
|
|
|
Aliases: []string{"u"},
|
|
|
|
Usage: "only accept events older than this (unix timestamp)",
|
|
|
|
Category: CATEGORY_FILTER_ATTRIBUTES,
|
|
|
|
},
|
|
|
|
&cli.IntFlag{
|
|
|
|
Name: "limit",
|
|
|
|
Aliases: []string{"l"},
|
|
|
|
Usage: "only accept up to this number of events",
|
|
|
|
Category: CATEGORY_FILTER_ATTRIBUTES,
|
|
|
|
},
|
2023-10-08 14:49:11 -04:00
|
|
|
&cli.StringFlag{
|
|
|
|
Name: "search",
|
|
|
|
Usage: "a NIP-50 search query, use it only with relays that explicitly support it",
|
|
|
|
Category: CATEGORY_FILTER_ATTRIBUTES,
|
|
|
|
},
|
2023-12-09 14:32:04 -05:00
|
|
|
&cli.BoolFlag{
|
|
|
|
Name: "stream",
|
|
|
|
Usage: "keep the subscription open, printing all events as they are returned",
|
|
|
|
DefaultText: "false, will close on EOSE",
|
|
|
|
},
|
2023-05-03 15:33:32 -04:00
|
|
|
&cli.BoolFlag{
|
|
|
|
Name: "bare",
|
2023-05-23 14:57:03 -04:00
|
|
|
Usage: "when printing the filter, print just the filter, not enveloped in a [\"REQ\", ...] array",
|
|
|
|
},
|
|
|
|
&cli.BoolFlag{
|
2023-12-09 14:32:04 -05:00
|
|
|
Name: "auth",
|
|
|
|
Usage: "always perform NIP-42 \"AUTH\" when facing an \"auth-required: \" rejection and try again",
|
|
|
|
},
|
|
|
|
&cli.StringFlag{
|
|
|
|
Name: "sec",
|
|
|
|
Usage: "secret key to sign the AUTH challenge, as hex or nsec",
|
|
|
|
DefaultText: "the key '1'",
|
|
|
|
Value: "0000000000000000000000000000000000000000000000000000000000000001",
|
|
|
|
},
|
|
|
|
&cli.BoolFlag{
|
|
|
|
Name: "prompt-sec",
|
|
|
|
Usage: "prompt the user to paste a hex or nsec with which to sign the AUTH challenge",
|
2023-05-03 15:33:32 -04:00
|
|
|
},
|
2024-02-05 22:58:26 -05:00
|
|
|
&cli.StringFlag{
|
|
|
|
Name: "connect",
|
|
|
|
Usage: "sign AUTH using NIP-46, expects a bunker://... URL",
|
|
|
|
},
|
2023-05-03 15:33:32 -04:00
|
|
|
},
|
2023-05-23 14:57:03 -04:00
|
|
|
ArgsUsage: "[relay...]",
|
2023-05-03 15:33:32 -04:00
|
|
|
Action: func(c *cli.Context) error {
|
2023-11-13 12:57:35 -05:00
|
|
|
var pool *nostr.SimplePool
|
|
|
|
relayUrls := c.Args().Slice()
|
|
|
|
if len(relayUrls) > 0 {
|
|
|
|
var relays []*nostr.Relay
|
2023-12-09 14:32:04 -05:00
|
|
|
pool, relays = connectToAllRelays(c.Context, relayUrls, nostr.WithAuthHandler(func(evt *nostr.Event) error {
|
|
|
|
if !c.Bool("auth") {
|
|
|
|
return fmt.Errorf("auth not authorized")
|
|
|
|
}
|
2024-02-05 22:58:26 -05:00
|
|
|
sec, bunker, err := gatherSecretKeyOrBunkerFromArguments(c)
|
2023-12-09 14:32:04 -05:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2024-02-05 22:58:26 -05:00
|
|
|
|
|
|
|
var pk string
|
|
|
|
if bunker != nil {
|
|
|
|
pk, err = bunker.GetPublicKey(c.Context)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to get public key from bunker: %w", err)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
pk, _ = nostr.GetPublicKey(sec)
|
|
|
|
}
|
2023-12-09 14:32:04 -05:00
|
|
|
log("performing auth as %s...\n", pk)
|
2024-02-05 22:58:26 -05:00
|
|
|
|
|
|
|
if bunker != nil {
|
|
|
|
return bunker.SignEvent(c.Context, evt)
|
|
|
|
} else {
|
|
|
|
return evt.Sign(sec)
|
|
|
|
}
|
2023-12-09 14:32:04 -05:00
|
|
|
}))
|
2023-11-13 12:57:35 -05:00
|
|
|
if len(relays) == 0 {
|
2023-11-13 13:03:27 -05:00
|
|
|
log("failed to connect to any of the given relays.\n")
|
2023-11-13 12:57:35 -05:00
|
|
|
os.Exit(3)
|
|
|
|
}
|
|
|
|
relayUrls = make([]string, len(relays))
|
|
|
|
for i, relay := range relays {
|
|
|
|
relayUrls[i] = relay.URL
|
|
|
|
}
|
2024-01-11 19:29:39 -05:00
|
|
|
|
|
|
|
defer func() {
|
|
|
|
for _, relay := range relays {
|
|
|
|
relay.Close()
|
|
|
|
}
|
|
|
|
}()
|
2023-11-13 12:57:35 -05:00
|
|
|
}
|
|
|
|
|
2023-11-07 15:57:43 -05:00
|
|
|
for stdinFilter := range getStdinLinesOrBlank() {
|
|
|
|
filter := nostr.Filter{}
|
|
|
|
if stdinFilter != "" {
|
2023-11-28 13:18:43 -05:00
|
|
|
if err := easyjson.Unmarshal([]byte(stdinFilter), &filter); err != nil {
|
2023-11-08 10:56:38 -05:00
|
|
|
lineProcessingError(c, "invalid filter '%s' received from stdin: %s", stdinFilter, err)
|
2023-11-07 15:57:43 -05:00
|
|
|
continue
|
|
|
|
}
|
2023-10-29 18:11:35 -04:00
|
|
|
}
|
2023-05-03 15:33:32 -04:00
|
|
|
|
2023-11-07 15:57:43 -05:00
|
|
|
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})
|
2023-05-03 15:33:32 -04:00
|
|
|
}
|
2024-01-17 06:50:59 -05:00
|
|
|
for _, dtag := range c.StringSlice("d") {
|
|
|
|
tags = append(tags, []string{"d", dtag})
|
|
|
|
}
|
2023-10-29 18:11:35 -04:00
|
|
|
|
2023-11-07 15:57:43 -05:00
|
|
|
if len(tags) > 0 && filter.Tags == nil {
|
|
|
|
filter.Tags = make(nostr.TagMap)
|
2023-05-03 15:33:32 -04:00
|
|
|
}
|
|
|
|
|
2023-11-07 15:57:43 -05:00
|
|
|
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])
|
|
|
|
}
|
2023-05-03 15:33:32 -04:00
|
|
|
|
2023-12-12 07:30:04 -05:00
|
|
|
if since := c.String("since"); since != "" {
|
|
|
|
if since == "now" {
|
|
|
|
ts := nostr.Now()
|
|
|
|
filter.Since = &ts
|
|
|
|
} else if i, err := strconv.Atoi(since); err == nil {
|
|
|
|
ts := nostr.Timestamp(i)
|
|
|
|
filter.Since = &ts
|
|
|
|
} else {
|
|
|
|
return fmt.Errorf("parse error: Invalid numeric literal %q", since)
|
|
|
|
}
|
2023-05-23 14:57:03 -04:00
|
|
|
}
|
2023-12-12 07:34:50 -05:00
|
|
|
if until := c.String("until"); until != "" {
|
|
|
|
if until == "now" {
|
|
|
|
ts := nostr.Now()
|
|
|
|
filter.Until = &ts
|
|
|
|
} else if i, err := strconv.Atoi(until); err == nil {
|
|
|
|
ts := nostr.Timestamp(i)
|
|
|
|
filter.Until = &ts
|
|
|
|
} else {
|
|
|
|
return fmt.Errorf("parse error: Invalid numeric literal %q", until)
|
|
|
|
}
|
2023-05-23 14:57:03 -04:00
|
|
|
}
|
2023-11-07 15:57:43 -05:00
|
|
|
if limit := c.Int("limit"); limit != 0 {
|
|
|
|
filter.Limit = limit
|
2023-05-23 14:57:03 -04:00
|
|
|
}
|
|
|
|
|
2023-11-13 12:57:35 -05:00
|
|
|
if len(relayUrls) > 0 {
|
2023-11-07 15:57:43 -05:00
|
|
|
fn := pool.SubManyEose
|
|
|
|
if c.Bool("stream") {
|
|
|
|
fn = pool.SubMany
|
|
|
|
}
|
2023-11-13 12:57:35 -05:00
|
|
|
for ie := range fn(c.Context, relayUrls, nostr.Filters{filter}) {
|
2024-01-24 20:38:51 -05:00
|
|
|
stdout(ie.Event)
|
2023-11-07 15:57:43 -05:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// no relays given, will just print the filter
|
|
|
|
var result string
|
|
|
|
if c.Bool("bare") {
|
|
|
|
result = filter.String()
|
|
|
|
} else {
|
2023-11-28 13:18:43 -05:00
|
|
|
j, _ := json.Marshal(nostr.ReqEnvelope{SubscriptionID: "nak", Filters: nostr.Filters{filter}})
|
2023-11-07 15:57:43 -05:00
|
|
|
result = string(j)
|
|
|
|
}
|
|
|
|
|
2024-01-24 20:38:51 -05:00
|
|
|
stdout(result)
|
2023-11-07 15:57:43 -05:00
|
|
|
}
|
2023-05-03 15:33:32 -04:00
|
|
|
}
|
|
|
|
|
2023-11-07 15:57:43 -05:00
|
|
|
exitIfLineProcessingError(c)
|
2023-05-03 15:33:32 -04:00
|
|
|
return nil
|
|
|
|
},
|
|
|
|
}
|