From e059fba48704ac93b48875fc848b60118bb1d34d Mon Sep 17 00:00:00 2001 From: fiatjaf Date: Mon, 30 Mar 2026 12:20:52 -0300 Subject: [PATCH] event/req: accept -h tag and addresses in -a tag. --- event.go | 55 +++++++++++++++++++++++++++++++++++++++++++++++++-- flags.go | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ main.go | 6 ++++++ req.go | 50 +++++++++++++++++++++++++++++++++++----------- 4 files changed, 158 insertions(+), 13 deletions(-) diff --git a/event.go b/event.go index 79eff84..cc6250a 100644 --- a/event.go +++ b/event.go @@ -72,6 +72,12 @@ example: Value: false, Category: CATEGORY_SIGNER, }, + &cli.BoolFlag{ + Name: "no-sign", + Usage: "print the event without signing it, using the specified pubkey", + Value: false, + Category: CATEGORY_SIGNER, + }, &cli.UintFlag{ Name: "pow", Usage: "nip13 difficulty to target when doing hash work on the event id", @@ -100,6 +106,12 @@ example: Value: 0, Category: CATEGORY_EVENT_FIELDS, }, + &PubKeyOrAddressFlag{ + Name: "author", + Aliases: []string{"a"}, + Usage: "set the event pubkey or add an 'a' tag if it's an address", + Category: CATEGORY_EVENT_FIELDS, + }, &cli.StringFlag{ Name: "content", Aliases: []string{"c"}, @@ -129,6 +141,11 @@ example: Usage: "shortcut for --tag d=", Category: CATEGORY_EVENT_FIELDS, }, + &cli.StringSliceFlag{ + Name: "h", + Usage: "shortcut for --tag h=", + Category: CATEGORY_EVENT_FIELDS, + }, &NaturalTimeFlag{ Name: "created-at", Aliases: []string{"time", "ts"}, @@ -252,12 +269,40 @@ example: tags = append(tags, nostr.Tag{"d", dtag}) } } + for _, htag := range c.StringSlice("h") { + if tags.FindWithValue("h", htag) == nil { + tags = append(tags, nostr.Tag{"h", htag}) + } + } + + var authorPubKey nostr.PubKey + for _, a := range getPubKeyOrAddressSlice(c, "author") { + // is it an address? + if a.Addr != nil { + aTag := a.Addr.AsTagReference() + if tags.FindWithValue("a", aTag) == nil { + tags = append(tags, nostr.Tag{"a", aTag}) + } + continue + } + + // or is it an "author" pubkey? + if a.PubKey != nostr.ZeroPK { + if authorPubKey != nostr.ZeroPK { + return fmt.Errorf("multiple author pubkeys provided") + } + authorPubKey = a.PubKey + } + } if len(tags) > 0 { for _, tag := range tags { evt.Tags = append(evt.Tags, tag) } mustRehashAndResign = true } + if authorPubKey != nostr.ZeroPK { + evt.PubKey = authorPubKey + } if c.IsSet("created-at") { evt.CreatedAt = getNaturalDate(c, "created-at") @@ -282,7 +327,7 @@ example: if err != nil { return err } - } else { + } else if evt.PubKey == nostr.ZeroPK { evt.PubKey, _ = kr.GetPublicKey(ctx) } @@ -293,7 +338,13 @@ example: mustRehashAndResign = true } - if evt.Sig == [64]byte{} || mustRehashAndResign { + if c.Bool("no-sign") { + if evt.PubKey == nostr.ZeroPK { + return fmt.Errorf("--no-sign requires a pubkey in the event or via --author") + } + evt.ID = nostr.ZeroID + evt.Sig = [64]byte{} + } else if evt.Sig == [64]byte{} || mustRehashAndResign { if numSigners := c.Uint("musig"); numSigners > 1 { // must do musig pubkeys := c.StringSlice("musig-pubkey") diff --git a/flags.go b/flags.go index 89d45a4..8429f70 100644 --- a/flags.go +++ b/flags.go @@ -183,6 +183,66 @@ func getPubKeySlice(cmd *cli.Command, name string) []nostr.PubKey { // // +type PubKeyOrAddress struct { + PubKey nostr.PubKey + Addr *nostr.EntityPointer +} + +type ( + pubKeyOrAddressValue struct { + value PubKeyOrAddress + hasBeenSet bool + } + pubKeyOrAddressSlice = cli.SliceBase[PubKeyOrAddress, struct{}, pubKeyOrAddressValue] + PubKeyOrAddressFlag = cli.FlagBase[[]PubKeyOrAddress, struct{}, pubKeyOrAddressSlice] +) + +var _ cli.ValueCreator[PubKeyOrAddress, struct{}] = pubKeyOrAddressValue{} + +func (t pubKeyOrAddressValue) Create(val PubKeyOrAddress, p *PubKeyOrAddress, c struct{}) cli.Value { + *p = val + return &pubKeyOrAddressValue{ + value: val, + } +} + +func (t pubKeyOrAddressValue) ToString(b PubKeyOrAddress) string { + if b.Addr != nil { + return b.Addr.AsTagReference() + } + return b.PubKey.String() +} + +func (t *pubKeyOrAddressValue) Set(value string) error { + pubkey, err1 := parsePubKey(value) + if err1 == nil { + t.value = PubKeyOrAddress{PubKey: pubkey} + t.hasBeenSet = true + return nil + } + + addr, err2 := nostr.ParseAddrString(value) + if err2 == nil { + t.value = PubKeyOrAddress{Addr: &addr} + t.hasBeenSet = true + return nil + } + + return fmt.Errorf("value is neither a pubkey or an address: %w; %w", err1, err2) +} + +func (t *pubKeyOrAddressValue) String() string { return fmt.Sprintf("%#v", t.value) } +func (t *pubKeyOrAddressValue) Value() PubKeyOrAddress { return t.value } +func (t *pubKeyOrAddressValue) Get() any { return t.value } + +func getPubKeyOrAddressSlice(cmd *cli.Command, name string) []PubKeyOrAddress { + return cmd.Value(name).([]PubKeyOrAddress) +} + +// +// +// + type ( IDFlag = cli.FlagBase[nostr.ID, struct{}, idValue] ) diff --git a/main.go b/main.go index a40e8e2..b6b4a3e 100644 --- a/main.go +++ b/main.go @@ -120,6 +120,12 @@ func init() { Name: "version", Usage: "prints the version", } + cli.HelpFlag = &cli.BoolFlag{ + Name: "help", + Usage: "shows help", + HideDefault: true, + Local: true, + } } func main() { diff --git a/req.go b/req.go index 998a726..bbd450a 100644 --- a/req.go +++ b/req.go @@ -387,7 +387,7 @@ readevents: } var reqFilterFlags = []cli.Flag{ - &PubKeySliceFlag{ + &PubKeyOrAddressFlag{ Name: "author", Aliases: []string{"a"}, Usage: "only accept events from these authors", @@ -426,6 +426,11 @@ var reqFilterFlags = []cli.Flag{ Usage: "shortcut for --tag d=", Category: CATEGORY_FILTER_ATTRIBUTES, }, + &cli.StringSliceFlag{ + Name: "h", + Usage: "shortcut for --tag h=", + Category: CATEGORY_FILTER_ATTRIBUTES, + }, &NaturalTimeFlag{ Name: "since", Aliases: []string{"s"}, @@ -451,10 +456,30 @@ var reqFilterFlags = []cli.Flag{ }, } +type flagTag struct { + key string + value string +} + func applyFlagsToFilter(c *cli.Command, filter *nostr.Filter) error { - if authors := getPubKeySlice(c, "author"); len(authors) > 0 { - filter.Authors = append(filter.Authors, authors...) + tags := make([]flagTag, 0, 5) + + if as := getPubKeyOrAddressSlice(c, "author"); len(as) > 0 { + for _, author := range as { + + // is it an address? + if author.Addr != nil { + tags = append(tags, flagTag{"a", author.Addr.AsTagReference()}) + continue + } + + // or is it an "author" pubkey? + if author.PubKey != nostr.ZeroPK { + filter.Authors = append(filter.Authors, author.PubKey) + } + } } + if ids := getIDSlice(c, "id"); len(ids) > 0 { filter.IDs = append(filter.IDs, ids...) } @@ -464,7 +489,7 @@ func applyFlagsToFilter(c *cli.Command, filter *nostr.Filter) error { if search := c.String("search"); search != "" { filter.Search = search } - tags := make([][]string, 0, 5) + for _, tagFlag := range c.StringSlice("tag") { spl := strings.SplitN(tagFlag, "=", 2) if len(spl) == 2 { @@ -472,19 +497,22 @@ func applyFlagsToFilter(c *cli.Command, filter *nostr.Filter) error { if len(spl) == 1 { val = decodeTagValue(val, []rune(spl[0])[0]) } - tags = append(tags, []string{spl[0], val}) + tags = append(tags, flagTag{spl[0], val}) } else { return fmt.Errorf("invalid --tag '%s'", tagFlag) } } for _, etag := range c.StringSlice("e") { - tags = append(tags, []string{"e", decodeTagValue(etag, 'e')}) + tags = append(tags, flagTag{"e", decodeTagValue(etag, 'e')}) } for _, ptag := range c.StringSlice("p") { - tags = append(tags, []string{"p", decodeTagValue(ptag, 'p')}) + tags = append(tags, flagTag{"p", decodeTagValue(ptag, 'p')}) } for _, dtag := range c.StringSlice("d") { - tags = append(tags, []string{"d", dtag}) + tags = append(tags, flagTag{"d", dtag}) + } + for _, htag := range c.StringSlice("h") { + tags = append(tags, flagTag{"h", htag}) } if len(tags) > 0 && filter.Tags == nil { @@ -492,10 +520,10 @@ func applyFlagsToFilter(c *cli.Command, filter *nostr.Filter) error { } for _, tag := range tags { - if _, ok := filter.Tags[tag[0]]; !ok { - filter.Tags[tag[0]] = make([]string, 0, 3) + if _, ok := filter.Tags[tag.key]; !ok { + filter.Tags[tag.key] = make([]string, 0, 3) } - filter.Tags[tag[0]] = append(filter.Tags[tag[0]], tag[1]) + filter.Tags[tag.key] = append(filter.Tags[tag.key], tag.value) } if c.IsSet("since") {