From 6ccca357e20d98a8d7f8ec61bd3e299df08a272b Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Wed, 23 Aug 2023 23:33:49 +0900 Subject: [PATCH] support NIP-45 COUNT --- README.md | 31 +++++++++++ count.go | 162 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ main.go | 1 + 3 files changed, 194 insertions(+) create mode 100644 count.go diff --git a/README.md b/README.md index e31da1f..97b98a4 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,7 @@ USAGE: COMMANDS: req generates encoded REQ messages and optionally use them to talk to relays + count generates encoded COUNT messages and optionally use them to talk to relays event generates an encoded event and either prints it or sends it to a set of relays decode decodes nip19, nip21, nip05 or hex entities encode encodes notes and other stuff to nip19 entities @@ -136,6 +137,36 @@ OPTIONS: -e value [ -e value ] shortcut for --tag e= -p value [ -p value ] shortcut for --tag p= +~> nak count --help +NAME: + nak count - generates encoded COUNT messages and optionally use them to talk to relays + +USAGE: + nak count [command options] [relay...] + +DESCRIPTION: + outputs a NIP-45 request. Mostly same as req. + + example usage (with 'nostcat'): + nak count -k 1 -a 3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d | nostcat wss://nos.lol + standalone: + nak count -k 1 wss://nos.lol + +OPTIONS: + --bare when printing the filter, print just the filter, not enveloped in a ["COUNT", ...] array (default: false) + --stream keep the subscription open, printing all events as they are returned (default: false, will close on EOSE) + + FILTER ATTRIBUTES + + --author value, -a value [ --author value, -a value ] only accept events from these authors (pubkey as hex) + --id value, -i value [ --id value, -i value ] only accept events with these ids (hex) + --kind value, -k value [ --kind value, -k value ] only accept events with these kind numbers + --limit value, -l value only accept up to this number of events (default: 0) + --since value, -s value only accept events newer than this (unix timestamp) (default: 0) + --tag value, -t value [ --tag value, -t value ] takes a tag like -t e=, only accept events with these tags + --until value, -u value only accept events older than this (unix timestamp) (default: 0) + -e value [ -e value ] shortcut for --tag e= + -p value [ -p value ] shortcut for --tag p= ~> nak decode --help NAME: diff --git a/count.go b/count.go new file mode 100644 index 0000000..7176fa5 --- /dev/null +++ b/count.go @@ -0,0 +1,162 @@ +package main + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/nbd-wtf/go-nostr" + "github.com/urfave/cli/v2" +) + +const CATEGORY_COUNT_ATTRIBUTES = "FILTER ATTRIBUTES" + +var count = &cli.Command{ + Name: "count", + Usage: "generates encoded COUNT messages and optionally use them to talk to relays", + Description: `outputs a NIP-45 request. Mostly same as req. + +example usage (with 'nostcat'): + nak count -k 1 -a 3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d | nostcat wss://nos.lol +standalone: + nak count -k 1 wss://nos.lol`, + 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=, only accept events with these tags", + Category: CATEGORY_FILTER_ATTRIBUTES, + }, + &cli.StringSliceFlag{ + Name: "e", + Usage: "shortcut for --tag e=", + Category: CATEGORY_FILTER_ATTRIBUTES, + }, + &cli.StringSliceFlag{ + Name: "p", + Usage: "shortcut for --tag p=", + Category: CATEGORY_FILTER_ATTRIBUTES, + }, + &cli.IntFlag{ + Name: "since", + Aliases: []string{"s"}, + Usage: "only accept events newer than this (unix timestamp)", + Category: CATEGORY_FILTER_ATTRIBUTES, + }, + &cli.IntFlag{ + 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, + }, + &cli.BoolFlag{ + Name: "bare", + Usage: "when printing the filter, print just the filter, not enveloped in a [\"COUNT\", ...] array", + }, + &cli.BoolFlag{ + Name: "stream", + Usage: "keep the subscription open, printing all events as they are returned", + DefaultText: "false, will close on EOSE", + }, + }, + ArgsUsage: "[relay...]", + Action: func(c *cli.Context) error { + filter := nostr.Filter{} + + if authors := c.StringSlice("author"); len(authors) > 0 { + filter.Authors = authors + } + if ids := c.StringSlice("id"); len(ids) > 0 { + filter.IDs = ids + } + if kinds := c.IntSlice("kind"); len(kinds) > 0 { + filter.Kinds = kinds + } + + 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 = 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 evt := range fn(c.Context, relays, nostr.Filters{filter}) { + fmt.Println(evt) + } + } else { + // no relays given, will just print the filter + var result string + if c.Bool("bare") { + result = filter.String() + } else { + j, _ := json.Marshal([]any{"COUNT", "nak", filter}) + result = string(j) + } + + fmt.Println(result) + } + + return nil + }, +} diff --git a/main.go b/main.go index 00f461c..0c88b22 100644 --- a/main.go +++ b/main.go @@ -13,6 +13,7 @@ func main() { Usage: "the nostr army knife command-line tool", Commands: []*cli.Command{ req, + count, event, decode, encode,