mirror of https://github.com/fiatjaf/nak.git
convert to using nostrlib.
This commit is contained in:
parent
1b43dbda02
commit
d733a31898
11
blossom.go
11
blossom.go
|
@ -5,8 +5,9 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr/keyer"
|
||||
"github.com/nbd-wtf/go-nostr/nipb0/blossom"
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/keyer"
|
||||
"fiatjaf.com/nostr/nipb0/blossom"
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
|
@ -35,7 +36,11 @@ var blossomCmd = &cli.Command{
|
|||
var client *blossom.Client
|
||||
pubkey := c.Args().First()
|
||||
if pubkey != "" {
|
||||
client = blossom.NewClient(client.GetMediaServer(), keyer.NewReadOnlySigner(pubkey))
|
||||
if pk, err := nostr.PubKeyFromHex(pubkey); err != nil {
|
||||
return fmt.Errorf("invalid public key '%s': %w", pubkey, err)
|
||||
} else {
|
||||
client = blossom.NewClient(client.GetMediaServer(), keyer.NewReadOnlySigner(pk))
|
||||
}
|
||||
} else {
|
||||
var err error
|
||||
client, err = getBlossomClient(ctx, c)
|
||||
|
|
40
bunker.go
40
bunker.go
|
@ -10,10 +10,10 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/nip19"
|
||||
"fiatjaf.com/nostr/nip46"
|
||||
"github.com/fatih/color"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip19"
|
||||
"github.com/nbd-wtf/go-nostr/nip46"
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
|
@ -38,7 +38,7 @@ var bunker = &cli.Command{
|
|||
Aliases: []string{"s"},
|
||||
Usage: "secrets for which we will always respond",
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
&PubKeySliceFlag{
|
||||
Name: "authorized-keys",
|
||||
Aliases: []string{"k"},
|
||||
Usage: "pubkeys for which we will always respond",
|
||||
|
@ -49,7 +49,7 @@ var bunker = &cli.Command{
|
|||
qs := url.Values{}
|
||||
relayURLs := make([]string, 0, c.Args().Len())
|
||||
if relayUrls := c.Args().Slice(); len(relayUrls) > 0 {
|
||||
relays := connectToAllRelays(ctx, c, relayUrls, nil)
|
||||
relays := connectToAllRelays(ctx, c, relayUrls, nil, nostr.PoolOptions{})
|
||||
if len(relays) == 0 {
|
||||
log("failed to connect to any of the given relays.\n")
|
||||
os.Exit(3)
|
||||
|
@ -70,7 +70,7 @@ var bunker = &cli.Command{
|
|||
}
|
||||
|
||||
// other arguments
|
||||
authorizedKeys := c.StringSlice("authorized-keys")
|
||||
authorizedKeys := getPubKeySlice(c, "authorized-keys")
|
||||
authorizedSecrets := c.StringSlice("authorized-secrets")
|
||||
|
||||
// this will be used to auto-authorize the next person who connects who isn't pre-authorized
|
||||
|
@ -78,11 +78,8 @@ var bunker = &cli.Command{
|
|||
newSecret := randString(12)
|
||||
|
||||
// static information
|
||||
pubkey, err := nostr.GetPublicKey(sec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
npub, _ := nip19.EncodePublicKey(pubkey)
|
||||
pubkey := sec.Public()
|
||||
npub := nip19.EncodeNpub(pubkey)
|
||||
|
||||
// this function will be called every now and then
|
||||
printBunkerInfo := func() {
|
||||
|
@ -91,7 +88,10 @@ var bunker = &cli.Command{
|
|||
|
||||
authorizedKeysStr := ""
|
||||
if len(authorizedKeys) != 0 {
|
||||
authorizedKeysStr = "\n authorized keys:\n - " + colors.italic(strings.Join(authorizedKeys, "\n - "))
|
||||
authorizedKeysStr = "\n authorized keys:"
|
||||
for _, pubkey := range authorizedKeys {
|
||||
authorizedKeysStr += "\n - " + colors.italic(pubkey.Hex())
|
||||
}
|
||||
}
|
||||
|
||||
authorizedSecretsStr := ""
|
||||
|
@ -101,7 +101,7 @@ var bunker = &cli.Command{
|
|||
|
||||
preauthorizedFlags := ""
|
||||
for _, k := range authorizedKeys {
|
||||
preauthorizedFlags += " -k " + k
|
||||
preauthorizedFlags += " -k " + k.Hex()
|
||||
}
|
||||
for _, s := range authorizedSecrets {
|
||||
preauthorizedFlags += " -s " + s
|
||||
|
@ -142,10 +142,12 @@ var bunker = &cli.Command{
|
|||
// subscribe to relays
|
||||
now := nostr.Now()
|
||||
events := sys.Pool.SubscribeMany(ctx, relayURLs, nostr.Filter{
|
||||
Kinds: []int{nostr.KindNostrConnect},
|
||||
Tags: nostr.TagMap{"p": []string{pubkey}},
|
||||
Kinds: []nostr.Kind{nostr.KindNostrConnect},
|
||||
Tags: nostr.TagMap{"p": []string{pubkey.Hex()}},
|
||||
Since: &now,
|
||||
LimitZero: true,
|
||||
}, nostr.SubscriptionOptions{
|
||||
Label: "nak-bunker",
|
||||
})
|
||||
|
||||
signer := nip46.NewStaticKeySigner(sec)
|
||||
|
@ -158,7 +160,7 @@ var bunker = &cli.Command{
|
|||
cancelPreviousBunkerInfoPrint = cancel
|
||||
|
||||
// asking user for authorization
|
||||
signer.AuthorizeRequest = func(harmless bool, from string, secret string) bool {
|
||||
signer.AuthorizeRequest = func(harmless bool, from nostr.PubKey, secret string) bool {
|
||||
if secret == newSecret {
|
||||
// store this key
|
||||
authorizedKeys = append(authorizedKeys, from)
|
||||
|
@ -236,11 +238,13 @@ var bunker = &cli.Command{
|
|||
}
|
||||
|
||||
uri, err := url.Parse(c.Args().First())
|
||||
if err != nil || uri.Scheme != "nostrconnect" || !nostr.IsValidPublicKey(uri.Host) {
|
||||
if err != nil || uri.Scheme != "nostrconnect" {
|
||||
return fmt.Errorf("invalid uri")
|
||||
}
|
||||
|
||||
return nil
|
||||
// TODO
|
||||
|
||||
return fmt.Errorf("this is not implemented yet")
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
21
count.go
21
count.go
|
@ -6,9 +6,9 @@ import (
|
|||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip45"
|
||||
"github.com/nbd-wtf/go-nostr/nip45/hyperloglog"
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/nip45"
|
||||
"fiatjaf.com/nostr/nip45/hyperloglog"
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
|
@ -18,7 +18,7 @@ var count = &cli.Command{
|
|||
Description: `outputs a nip45 request (the flags are mostly the same as 'nak req').`,
|
||||
DisableSliceFlagSeparator: true,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringSliceFlag{
|
||||
&PubKeySliceFlag{
|
||||
Name: "author",
|
||||
Aliases: []string{"a"},
|
||||
Usage: "only accept events from these authors (pubkey as hex)",
|
||||
|
@ -70,7 +70,7 @@ var count = &cli.Command{
|
|||
biggerUrlSize := 0
|
||||
relayUrls := c.Args().Slice()
|
||||
if len(relayUrls) > 0 {
|
||||
relays := connectToAllRelays(ctx, c, relayUrls, nil)
|
||||
relays := connectToAllRelays(ctx, c, relayUrls, nil, nostr.PoolOptions{})
|
||||
if len(relays) == 0 {
|
||||
log("failed to connect to any of the given relays.\n")
|
||||
os.Exit(3)
|
||||
|
@ -92,16 +92,13 @@ var count = &cli.Command{
|
|||
|
||||
filter := nostr.Filter{}
|
||||
|
||||
if authors := c.StringSlice("author"); len(authors) > 0 {
|
||||
if authors := getPubKeySlice(c, "author"); len(authors) > 0 {
|
||||
filter.Authors = authors
|
||||
}
|
||||
if ids := c.StringSlice("id"); len(ids) > 0 {
|
||||
filter.IDs = ids
|
||||
}
|
||||
if kinds64 := c.IntSlice("kind"); len(kinds64) > 0 {
|
||||
kinds := make([]int, len(kinds64))
|
||||
kinds := make([]nostr.Kind, len(kinds64))
|
||||
for i, v := range kinds64 {
|
||||
kinds[i] = int(v)
|
||||
kinds[i] = nostr.Kind(v)
|
||||
}
|
||||
filter.Kinds = kinds
|
||||
}
|
||||
|
@ -151,7 +148,7 @@ var count = &cli.Command{
|
|||
}
|
||||
for _, relayUrl := range relayUrls {
|
||||
relay, _ := sys.Pool.EnsureRelay(relayUrl)
|
||||
count, hllRegisters, err := relay.Count(ctx, nostr.Filters{filter})
|
||||
count, hllRegisters, err := relay.Count(ctx, filter, nostr.SubscriptionOptions{})
|
||||
fmt.Fprintf(os.Stderr, "%s%s: ", strings.Repeat(" ", biggerUrlSize-len(relayUrl)), relayUrl)
|
||||
|
||||
if err != nil {
|
||||
|
|
2
curl.go
2
curl.go
|
@ -8,7 +8,7 @@ import (
|
|||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"fiatjaf.com/nostr"
|
||||
"github.com/urfave/cli/v3"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
|
|
@ -5,10 +5,10 @@ import (
|
|||
"encoding/hex"
|
||||
"strings"
|
||||
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/nip19"
|
||||
"fiatjaf.com/nostr/sdk"
|
||||
"github.com/urfave/cli/v3"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip19"
|
||||
"github.com/nbd-wtf/go-nostr/sdk"
|
||||
)
|
||||
|
||||
var decode = &cli.Command{
|
||||
|
@ -73,7 +73,7 @@ var decode = &cli.Command{
|
|||
}
|
||||
} else if prefix, value, err := nip19.Decode(input); err == nil && prefix == "nsec" {
|
||||
decodeResult.PrivateKey.PrivateKey = value.(string)
|
||||
decodeResult.PrivateKey.PublicKey, _ = nostr.GetPublicKey(value.(string))
|
||||
decodeResult.PrivateKey.PublicKey = nostr.GetPublicKey(value.(nostr.SecretKey))
|
||||
} else {
|
||||
ctx = lineProcessingError(ctx, "couldn't decode input '%s': %s", input, err)
|
||||
continue
|
||||
|
|
69
encode.go
69
encode.go
|
@ -4,8 +4,8 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip19"
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/nip19"
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
|
@ -33,16 +33,13 @@ var encode = &cli.Command{
|
|||
DisableSliceFlagSeparator: true,
|
||||
Action: func(ctx context.Context, c *cli.Command) error {
|
||||
for target := range getStdinLinesOrArguments(c.Args()) {
|
||||
if ok := nostr.IsValidPublicKey(target); !ok {
|
||||
ctx = lineProcessingError(ctx, "invalid public key: %s", target)
|
||||
pk, err := nostr.PubKeyFromHexCheap(target)
|
||||
if err != nil {
|
||||
ctx = lineProcessingError(ctx, "invalid public key '%s': %w", target, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if npub, err := nip19.EncodePublicKey(target); err == nil {
|
||||
stdout(npub)
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
stdout(nip19.EncodeNpub(pk))
|
||||
}
|
||||
|
||||
exitIfLineProcessingError(ctx)
|
||||
|
@ -55,16 +52,13 @@ var encode = &cli.Command{
|
|||
DisableSliceFlagSeparator: true,
|
||||
Action: func(ctx context.Context, c *cli.Command) error {
|
||||
for target := range getStdinLinesOrArguments(c.Args()) {
|
||||
if ok := nostr.IsValid32ByteHex(target); !ok {
|
||||
ctx = lineProcessingError(ctx, "invalid private key: %s", target)
|
||||
sk, err := nostr.SecretKeyFromHex(target)
|
||||
if err != nil {
|
||||
ctx = lineProcessingError(ctx, "invalid private key '%s': %w", target, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if npub, err := nip19.EncodePrivateKey(target); err == nil {
|
||||
stdout(npub)
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
stdout(nip19.EncodeNsec(sk))
|
||||
}
|
||||
|
||||
exitIfLineProcessingError(ctx)
|
||||
|
@ -84,8 +78,9 @@ var encode = &cli.Command{
|
|||
DisableSliceFlagSeparator: true,
|
||||
Action: func(ctx context.Context, c *cli.Command) error {
|
||||
for target := range getStdinLinesOrArguments(c.Args()) {
|
||||
if ok := nostr.IsValid32ByteHex(target); !ok {
|
||||
ctx = lineProcessingError(ctx, "invalid public key: %s", target)
|
||||
pk, err := nostr.PubKeyFromHexCheap(target)
|
||||
if err != nil {
|
||||
ctx = lineProcessingError(ctx, "invalid public key '%s': %w", target, err)
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -94,11 +89,7 @@ var encode = &cli.Command{
|
|||
return err
|
||||
}
|
||||
|
||||
if npub, err := nip19.EncodeProfile(target, relays); err == nil {
|
||||
stdout(npub)
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
stdout(nip19.EncodeNprofile(pk, relays))
|
||||
}
|
||||
|
||||
exitIfLineProcessingError(ctx)
|
||||
|
@ -114,7 +105,7 @@ var encode = &cli.Command{
|
|||
Aliases: []string{"r"},
|
||||
Usage: "attach relay hints to nevent code",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
&PubKeyFlag{
|
||||
Name: "author",
|
||||
Aliases: []string{"a"},
|
||||
Usage: "attach an author pubkey as a hint to the nevent code",
|
||||
|
@ -123,28 +114,19 @@ var encode = &cli.Command{
|
|||
DisableSliceFlagSeparator: true,
|
||||
Action: func(ctx context.Context, c *cli.Command) error {
|
||||
for target := range getStdinLinesOrArguments(c.Args()) {
|
||||
if ok := nostr.IsValid32ByteHex(target); !ok {
|
||||
id, err := nostr.IDFromHex(target)
|
||||
if err != nil {
|
||||
ctx = lineProcessingError(ctx, "invalid event id: %s", target)
|
||||
continue
|
||||
}
|
||||
|
||||
author := c.String("author")
|
||||
if author != "" {
|
||||
if ok := nostr.IsValidPublicKey(author); !ok {
|
||||
return fmt.Errorf("invalid 'author' public key")
|
||||
}
|
||||
}
|
||||
|
||||
author := getPubKey(c, "author")
|
||||
relays := c.StringSlice("relay")
|
||||
if err := normalizeAndValidateRelayURLs(relays); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if npub, err := nip19.EncodeEvent(target, relays, author); err == nil {
|
||||
stdout(npub)
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
stdout(nip19.EncodeNevent(id, relays, author))
|
||||
}
|
||||
|
||||
exitIfLineProcessingError(ctx)
|
||||
|
@ -161,7 +143,7 @@ var encode = &cli.Command{
|
|||
Usage: "the \"d\" tag identifier of this replaceable event -- can also be read from stdin",
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
&PubKeyFlag{
|
||||
Name: "pubkey",
|
||||
Usage: "pubkey of the naddr author",
|
||||
Aliases: []string{"author", "a", "p"},
|
||||
|
@ -182,10 +164,7 @@ var encode = &cli.Command{
|
|||
DisableSliceFlagSeparator: true,
|
||||
Action: func(ctx context.Context, c *cli.Command) error {
|
||||
for d := range getStdinLinesOrBlank() {
|
||||
pubkey := c.String("pubkey")
|
||||
if ok := nostr.IsValidPublicKey(pubkey); !ok {
|
||||
return fmt.Errorf("invalid 'pubkey'")
|
||||
}
|
||||
pubkey := getPubKey(c, "pubkey")
|
||||
|
||||
kind := c.Int("kind")
|
||||
if kind < 30000 || kind >= 40000 {
|
||||
|
@ -205,11 +184,7 @@ var encode = &cli.Command{
|
|||
return err
|
||||
}
|
||||
|
||||
if npub, err := nip19.EncodeEntity(pubkey, int(kind), d, relays); err == nil {
|
||||
stdout(npub)
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
stdout(nip19.EncodeNaddr(pubkey, nostr.Kind(kind), d, relays))
|
||||
}
|
||||
|
||||
exitIfLineProcessingError(ctx)
|
||||
|
|
|
@ -4,9 +4,8 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
|
||||
"fiatjaf.com/nostr/nip04"
|
||||
"github.com/urfave/cli/v3"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip04"
|
||||
)
|
||||
|
||||
var encrypt = &cli.Command{
|
||||
|
@ -16,7 +15,7 @@ var encrypt = &cli.Command{
|
|||
DisableSliceFlagSeparator: true,
|
||||
Flags: append(
|
||||
defaultKeyFlags,
|
||||
&cli.StringFlag{
|
||||
&PubKeyFlag{
|
||||
Name: "recipient-pubkey",
|
||||
Aliases: []string{"p", "tgt", "target", "pubkey"},
|
||||
Required: true,
|
||||
|
@ -27,10 +26,7 @@ var encrypt = &cli.Command{
|
|||
},
|
||||
),
|
||||
Action: func(ctx context.Context, c *cli.Command) error {
|
||||
target := c.String("recipient-pubkey")
|
||||
if !nostr.IsValidPublicKey(target) {
|
||||
return fmt.Errorf("target %s is not a valid public key", target)
|
||||
}
|
||||
target := getPubKey(c, "recipient-pubkey")
|
||||
|
||||
plaintext := c.Args().First()
|
||||
|
||||
|
@ -81,7 +77,7 @@ var decrypt = &cli.Command{
|
|||
DisableSliceFlagSeparator: true,
|
||||
Flags: append(
|
||||
defaultKeyFlags,
|
||||
&cli.StringFlag{
|
||||
&PubKeyFlag{
|
||||
Name: "sender-pubkey",
|
||||
Aliases: []string{"p", "src", "source", "pubkey"},
|
||||
Required: true,
|
||||
|
@ -92,10 +88,7 @@ var decrypt = &cli.Command{
|
|||
},
|
||||
),
|
||||
Action: func(ctx context.Context, c *cli.Command) error {
|
||||
source := c.String("sender-pubkey")
|
||||
if !nostr.IsValidPublicKey(source) {
|
||||
return fmt.Errorf("source %s is not a valid public key", source)
|
||||
}
|
||||
source := getPubKey(c, "sender-pubkey")
|
||||
|
||||
ciphertext := c.Args().First()
|
||||
|
||||
|
|
27
event.go
27
event.go
|
@ -8,11 +8,11 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/nip13"
|
||||
"fiatjaf.com/nostr/nip19"
|
||||
"github.com/fatih/color"
|
||||
"github.com/mailru/easyjson"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip13"
|
||||
"github.com/nbd-wtf/go-nostr/nip19"
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
|
@ -141,9 +141,11 @@ example:
|
|||
|
||||
if relayUrls := c.Args().Slice(); len(relayUrls) > 0 {
|
||||
relays = connectToAllRelays(ctx, c, relayUrls, nil,
|
||||
nostr.WithAuthHandler(func(ctx context.Context, authEvent nostr.RelayEvent) error {
|
||||
nostr.PoolOptions{
|
||||
AuthHandler: func(ctx context.Context, authEvent *nostr.Event) error {
|
||||
return authSigner(ctx, c, func(s string, args ...any) {}, authEvent)
|
||||
}),
|
||||
},
|
||||
},
|
||||
)
|
||||
if len(relays) == 0 {
|
||||
log("failed to connect to any of the given relays.\n")
|
||||
|
@ -180,7 +182,7 @@ example:
|
|||
}
|
||||
|
||||
if kind := c.Uint("kind"); slices.Contains(c.FlagNames(), "kind") {
|
||||
evt.Kind = int(kind)
|
||||
evt.Kind = nostr.Kind(kind)
|
||||
mustRehashAndResign = true
|
||||
} else if !kindWasSupplied {
|
||||
evt.Kind = 1
|
||||
|
@ -274,7 +276,7 @@ example:
|
|||
mustRehashAndResign = true
|
||||
}
|
||||
|
||||
if evt.Sig == "" || mustRehashAndResign {
|
||||
if evt.Sig == [64]byte{} || mustRehashAndResign {
|
||||
if numSigners := c.Uint("musig"); numSigners > 1 {
|
||||
// must do musig
|
||||
pubkeys := c.StringSlice("musig-pubkey")
|
||||
|
@ -364,7 +366,7 @@ example:
|
|||
low := unwrapAll(res.Error)
|
||||
|
||||
// hack for some messages such as from relay.westernbtc.com
|
||||
msg := strings.ReplaceAll(low.Error(), evt.PubKey, "author")
|
||||
msg := strings.ReplaceAll(low.Error(), evt.PubKey.Hex(), "author")
|
||||
|
||||
// do not allow the message to overflow the term window
|
||||
msg = clampMessage(msg, 20+len(res.RelayURL))
|
||||
|
@ -393,11 +395,9 @@ example:
|
|||
if strings.HasPrefix(err.Error(), "msg: auth-required:") && kr != nil && doAuth {
|
||||
// if the relay is requesting auth and we can auth, let's do it
|
||||
pk, _ := kr.GetPublicKey(ctx)
|
||||
npub, _ := nip19.EncodePublicKey(pk)
|
||||
npub := nip19.EncodeNpub(pk)
|
||||
log("authenticating as %s... ", color.YellowString("%s…%s", npub[0:7], npub[58:]))
|
||||
if err := relay.Auth(ctx, func(authEvent *nostr.Event) error {
|
||||
return kr.SignEvent(ctx, authEvent)
|
||||
}); err == nil {
|
||||
if err := relay.Auth(ctx, kr.SignEvent); err == nil {
|
||||
// try to publish again, but this time don't try to auth again
|
||||
doAuth = false
|
||||
goto publish
|
||||
|
@ -410,8 +410,7 @@ example:
|
|||
}
|
||||
|
||||
if len(successRelays) > 0 && c.Bool("nevent") {
|
||||
nevent, _ := nip19.EncodeEvent(evt.ID, successRelays, evt.PubKey)
|
||||
log(nevent + "\n")
|
||||
log(nip19.EncodeNevent(evt.ID, successRelays, evt.PubKey) + "\n")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
22
fetch.go
22
fetch.go
|
@ -4,11 +4,11 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/nip05"
|
||||
"fiatjaf.com/nostr/nip19"
|
||||
"fiatjaf.com/nostr/sdk/hints"
|
||||
"github.com/urfave/cli/v3"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip05"
|
||||
"github.com/nbd-wtf/go-nostr/nip19"
|
||||
"github.com/nbd-wtf/go-nostr/sdk/hints"
|
||||
)
|
||||
|
||||
var fetch = &cli.Command{
|
||||
|
@ -36,7 +36,7 @@ var fetch = &cli.Command{
|
|||
|
||||
for code := range getStdinLinesOrArguments(c.Args()) {
|
||||
filter := nostr.Filter{}
|
||||
var authorHint string
|
||||
var authorHint nostr.PubKey
|
||||
relays := c.StringSlice("relay")
|
||||
|
||||
if nip05.IsValidIdentifier(code) {
|
||||
|
@ -63,15 +63,15 @@ var fetch = &cli.Command{
|
|||
case "nevent":
|
||||
v := value.(nostr.EventPointer)
|
||||
filter.IDs = append(filter.IDs, v.ID)
|
||||
if v.Author != "" {
|
||||
if v.Author != nostr.ZeroPK {
|
||||
authorHint = v.Author
|
||||
}
|
||||
relays = append(relays, v.Relays...)
|
||||
case "note":
|
||||
filter.IDs = append(filter.IDs, value.(string))
|
||||
filter.IDs = append(filter.IDs, value.([32]byte))
|
||||
case "naddr":
|
||||
v := value.(nostr.EntityPointer)
|
||||
filter.Kinds = []int{v.Kind}
|
||||
filter.Kinds = []nostr.Kind{v.Kind}
|
||||
filter.Tags = nostr.TagMap{"d": []string{v.Identifier}}
|
||||
filter.Authors = append(filter.Authors, v.PublicKey)
|
||||
authorHint = v.PublicKey
|
||||
|
@ -82,7 +82,7 @@ var fetch = &cli.Command{
|
|||
authorHint = v.PublicKey
|
||||
relays = append(relays, v.Relays...)
|
||||
case "npub":
|
||||
v := value.(string)
|
||||
v := value.(nostr.PubKey)
|
||||
filter.Authors = append(filter.Authors, v)
|
||||
authorHint = v
|
||||
default:
|
||||
|
@ -90,7 +90,7 @@ var fetch = &cli.Command{
|
|||
}
|
||||
}
|
||||
|
||||
if authorHint != "" {
|
||||
if authorHint != nostr.ZeroPK {
|
||||
for _, url := range relays {
|
||||
sys.Hints.Save(authorHint, nostr.NormalizeURL(url), hints.LastInHint, nostr.Now())
|
||||
}
|
||||
|
@ -113,7 +113,7 @@ var fetch = &cli.Command{
|
|||
continue
|
||||
}
|
||||
|
||||
for ie := range sys.Pool.FetchMany(ctx, relays, filter) {
|
||||
for ie := range sys.Pool.FetchMany(ctx, relays, filter, nostr.SubscriptionOptions{}) {
|
||||
stdout(ie.Event)
|
||||
}
|
||||
}
|
||||
|
|
157
flags.go
157
flags.go
|
@ -6,14 +6,18 @@ import (
|
|||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/urfave/cli/v3"
|
||||
"fiatjaf.com/nostr"
|
||||
"github.com/markusmobius/go-dateparser"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
type NaturalTimeFlag = cli.FlagBase[nostr.Timestamp, struct{}, naturalTimeValue]
|
||||
|
||||
// wrap to satisfy golang's flag interface.
|
||||
// wrap to satisfy flag interface.
|
||||
type naturalTimeValue struct {
|
||||
timestamp *nostr.Timestamp
|
||||
hasBeenSet bool
|
||||
|
@ -39,11 +43,6 @@ func (t naturalTimeValue) ToString(b nostr.Timestamp) string {
|
|||
return fmt.Sprintf("%v", ts)
|
||||
}
|
||||
|
||||
// Timestamp constructor(for internal testing only)
|
||||
func newTimestamp(timestamp nostr.Timestamp) *naturalTimeValue {
|
||||
return &naturalTimeValue{timestamp: ×tamp}
|
||||
}
|
||||
|
||||
// Below functions are to satisfy the flag.Value interface
|
||||
|
||||
// Parses the string value to timestamp
|
||||
|
@ -93,3 +92,145 @@ func (t *naturalTimeValue) Get() any {
|
|||
func getNaturalDate(cmd *cli.Command, name string) nostr.Timestamp {
|
||||
return cmd.Value(name).(nostr.Timestamp)
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
type (
|
||||
PubKeyFlag = cli.FlagBase[nostr.PubKey, struct{}, pubkeyValue]
|
||||
)
|
||||
|
||||
// wrap to satisfy flag interface.
|
||||
type pubkeyValue struct {
|
||||
pubkey nostr.PubKey
|
||||
hasBeenSet bool
|
||||
}
|
||||
|
||||
var _ cli.ValueCreator[nostr.PubKey, struct{}] = pubkeyValue{}
|
||||
|
||||
// Below functions are to satisfy the ValueCreator interface
|
||||
|
||||
func (t pubkeyValue) Create(val nostr.PubKey, p *nostr.PubKey, c struct{}) cli.Value {
|
||||
*p = val
|
||||
return &pubkeyValue{
|
||||
pubkey: val,
|
||||
}
|
||||
}
|
||||
|
||||
func (t pubkeyValue) ToString(b nostr.PubKey) string {
|
||||
return t.pubkey.String()
|
||||
}
|
||||
|
||||
// Below functions are to satisfy the flag.Value interface
|
||||
|
||||
// Parses the string value to timestamp
|
||||
func (t *pubkeyValue) Set(value string) error {
|
||||
pk, err := nostr.PubKeyFromHex(value)
|
||||
t.pubkey = pk
|
||||
t.hasBeenSet = true
|
||||
return err
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value (for usage defaults)
|
||||
func (t *pubkeyValue) String() string {
|
||||
return fmt.Sprintf("%#v", t.pubkey)
|
||||
}
|
||||
|
||||
// Value returns the pubkey value stored in the flag
|
||||
func (t *pubkeyValue) Value() nostr.PubKey {
|
||||
return t.pubkey
|
||||
}
|
||||
|
||||
// Get returns the flag structure
|
||||
func (t *pubkeyValue) Get() any {
|
||||
return t.pubkey
|
||||
}
|
||||
|
||||
func getPubKey(cmd *cli.Command, name string) nostr.PubKey {
|
||||
return cmd.Value(name).(nostr.PubKey)
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
type (
|
||||
pubkeySlice = cli.SliceBase[nostr.PubKey, struct{}, pubkeyValue]
|
||||
PubKeySliceFlag = cli.FlagBase[[]nostr.PubKey, struct{}, pubkeySlice]
|
||||
)
|
||||
|
||||
func getPubKeySlice(cmd *cli.Command, name string) []nostr.PubKey {
|
||||
return cmd.Value(name).([]nostr.PubKey)
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
type (
|
||||
IDFlag = cli.FlagBase[nostr.ID, struct{}, idValue]
|
||||
)
|
||||
|
||||
// wrap to satisfy flag interface.
|
||||
type idValue struct {
|
||||
id nostr.ID
|
||||
hasBeenSet bool
|
||||
}
|
||||
|
||||
var _ cli.ValueCreator[nostr.ID, struct{}] = idValue{}
|
||||
|
||||
// Below functions are to satisfy the ValueCreator interface
|
||||
|
||||
func (t idValue) Create(val nostr.ID, p *nostr.ID, c struct{}) cli.Value {
|
||||
*p = val
|
||||
return &idValue{
|
||||
id: val,
|
||||
}
|
||||
}
|
||||
|
||||
func (t idValue) ToString(b nostr.ID) string {
|
||||
return t.id.String()
|
||||
}
|
||||
|
||||
// Below functions are to satisfy the flag.Value interface
|
||||
|
||||
// Parses the string value to timestamp
|
||||
func (t *idValue) Set(value string) error {
|
||||
pk, err := nostr.IDFromHex(value)
|
||||
t.id = pk
|
||||
t.hasBeenSet = true
|
||||
return err
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value (for usage defaults)
|
||||
func (t *idValue) String() string {
|
||||
return fmt.Sprintf("%#v", t.id)
|
||||
}
|
||||
|
||||
// Value returns the id value stored in the flag
|
||||
func (t *idValue) Value() nostr.ID {
|
||||
return t.id
|
||||
}
|
||||
|
||||
// Get returns the flag structure
|
||||
func (t *idValue) Get() any {
|
||||
return t.id
|
||||
}
|
||||
|
||||
func getID(cmd *cli.Command, name string) nostr.ID {
|
||||
return cmd.Value(name).(nostr.ID)
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
type (
|
||||
idSlice = cli.SliceBase[nostr.ID, struct{}, idValue]
|
||||
IDSliceFlag = cli.FlagBase[[]nostr.ID, struct{}, idSlice]
|
||||
)
|
||||
|
||||
func getIDSlice(cmd *cli.Command, name string) []nostr.ID {
|
||||
return cmd.Value(name).([]nostr.ID)
|
||||
}
|
||||
|
|
14
fs.go
14
fs.go
|
@ -10,12 +10,12 @@ import (
|
|||
"syscall"
|
||||
"time"
|
||||
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/keyer"
|
||||
"github.com/fatih/color"
|
||||
"github.com/fiatjaf/nak/nostrfs"
|
||||
"github.com/hanwen/go-fuse/v2/fs"
|
||||
"github.com/hanwen/go-fuse/v2/fuse"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/keyer"
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
|
@ -25,15 +25,9 @@ var fsCmd = &cli.Command{
|
|||
Description: `(experimental)`,
|
||||
ArgsUsage: "<mountpoint>",
|
||||
Flags: append(defaultKeyFlags,
|
||||
&cli.StringFlag{
|
||||
&PubKeyFlag{
|
||||
Name: "pubkey",
|
||||
Usage: "public key from where to to prepopulate directories",
|
||||
Validator: func(pk string) error {
|
||||
if nostr.IsValidPublicKey(pk) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("invalid public key '%s'", pk)
|
||||
},
|
||||
},
|
||||
&cli.DurationFlag{
|
||||
Name: "auto-publish-notes",
|
||||
|
@ -58,7 +52,7 @@ var fsCmd = &cli.Command{
|
|||
if signer, _, err := gatherKeyerFromArguments(ctx, c); err == nil {
|
||||
kr = signer
|
||||
} else {
|
||||
kr = keyer.NewReadOnlyUser(c.String("pubkey"))
|
||||
kr = keyer.NewReadOnlyUser(getPubKey(c, "pubkey"))
|
||||
}
|
||||
|
||||
apnt := c.Duration("auto-publish-notes")
|
||||
|
|
8
go.mod
8
go.mod
|
@ -4,26 +4,25 @@ go 1.24.1
|
|||
|
||||
require (
|
||||
fiatjaf.com/lib v0.3.1
|
||||
fiatjaf.com/nostr v0.0.1
|
||||
github.com/bep/debounce v1.2.1
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.3.4
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0
|
||||
github.com/fatih/color v1.16.0
|
||||
github.com/fiatjaf/eventstore v0.16.2
|
||||
github.com/fiatjaf/khatru v0.17.4
|
||||
github.com/hanwen/go-fuse/v2 v2.7.2
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/liamg/magic v0.0.1
|
||||
github.com/mailru/easyjson v0.9.0
|
||||
github.com/mark3labs/mcp-go v0.8.3
|
||||
github.com/markusmobius/go-dateparser v1.2.3
|
||||
github.com/nbd-wtf/go-nostr v0.51.8
|
||||
github.com/urfave/cli/v3 v3.0.0-beta1
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394
|
||||
golang.org/x/term v0.30.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/FastFilter/xorfilter v0.2.1 // indirect
|
||||
github.com/ImVexed/fasturl v0.0.0-20230304231329-4e41488060f3 // indirect
|
||||
github.com/andybalholm/brotli v1.1.1 // indirect
|
||||
github.com/btcsuite/btcd v0.24.2 // indirect
|
||||
|
@ -72,6 +71,9 @@ require (
|
|||
golang.org/x/arch v0.15.0 // indirect
|
||||
golang.org/x/crypto v0.36.0 // indirect
|
||||
golang.org/x/net v0.37.0 // indirect
|
||||
golang.org/x/sync v0.12.0 // indirect
|
||||
golang.org/x/sys v0.31.0 // indirect
|
||||
golang.org/x/text v0.23.0 // indirect
|
||||
)
|
||||
|
||||
replace fiatjaf.com/nostr => ../nostrlib
|
||||
|
|
14
go.sum
14
go.sum
|
@ -1,7 +1,11 @@
|
|||
fiatjaf.com/lib v0.3.1 h1:/oFQwNtFRfV+ukmOCxfBEAuayoLwXp4wu2/fz5iHpwA=
|
||||
fiatjaf.com/lib v0.3.1/go.mod h1:Ycqq3+mJ9jAWu7XjbQI1cVr+OFgnHn79dQR5oTII47g=
|
||||
github.com/FastFilter/xorfilter v0.2.1 h1:lbdeLG9BdpquK64ZsleBS8B4xO/QW1IM0gMzF7KaBKc=
|
||||
github.com/FastFilter/xorfilter v0.2.1/go.mod h1:aumvdkhscz6YBZF9ZA/6O4fIoNod4YR50kIVGGZ7l9I=
|
||||
github.com/ImVexed/fasturl v0.0.0-20230304231329-4e41488060f3 h1:ClzzXMDDuUbWfNNZqGeYq4PnYOlwlOVIvSyNaIy0ykg=
|
||||
github.com/ImVexed/fasturl v0.0.0-20230304231329-4e41488060f3/go.mod h1:we0YA5CsBbH5+/NUzC/AlMmxaDtWlXeNsqrwXjTzmzA=
|
||||
github.com/PowerDNS/lmdb-go v1.9.3 h1:AUMY2pZT8WRpkEv39I9Id3MuoHd+NZbTVpNhruVkPTg=
|
||||
github.com/PowerDNS/lmdb-go v1.9.3/go.mod h1:TE0l+EZK8Z1B4dx070ZxkWTlp8RG1mjN0/+FkFRQMtU=
|
||||
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
|
||||
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
||||
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
||||
|
@ -38,6 +42,8 @@ github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1
|
|||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
|
||||
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
|
||||
|
@ -77,10 +83,6 @@ github.com/fasthttp/websocket v1.5.12 h1:e4RGPpWW2HTbL3zV0Y/t7g0ub294LkiuXXUuTOU
|
|||
github.com/fasthttp/websocket v1.5.12/go.mod h1:I+liyL7/4moHojiOgUOIKEWm9EIxHqxZChS+aMFltyg=
|
||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
github.com/fiatjaf/eventstore v0.16.2 h1:h4rHwSwPcqAKqWUsAbYWUhDeSgm2Kp+PBkJc3FgBYu4=
|
||||
github.com/fiatjaf/eventstore v0.16.2/go.mod h1:0gU8fzYO/bG+NQAVlHtJWOlt3JKKFefh5Xjj2d1dLIs=
|
||||
github.com/fiatjaf/khatru v0.17.4 h1:VzcLUyBKMlP/CAG4iHJbDJmnZgzhbGLKLxJAUuLRogg=
|
||||
github.com/fiatjaf/khatru v0.17.4/go.mod h1:VYQ7ZNhs3C1+E4gBnx+DtEgU0BrPdrl3XYF3H+mq6fg=
|
||||
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/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
||||
|
@ -149,8 +151,6 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
|
|||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/nbd-wtf/go-nostr v0.51.8 h1:CIoS+YqChcm4e1L1rfMZ3/mIwTz4CwApM2qx7MHNzmE=
|
||||
github.com/nbd-wtf/go-nostr v0.51.8/go.mod h1:d6+DfvMWYG5pA3dmNMBJd6WCHVDDhkXbHqvfljf0Gzg=
|
||||
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=
|
||||
|
@ -224,6 +224,8 @@ golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81R
|
|||
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
|
||||
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
||||
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
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=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
|
44
helpers.go
44
helpers.go
|
@ -17,11 +17,12 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/nip19"
|
||||
"fiatjaf.com/nostr/nip42"
|
||||
"fiatjaf.com/nostr/sdk"
|
||||
"github.com/fatih/color"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip19"
|
||||
"github.com/nbd-wtf/go-nostr/sdk"
|
||||
"github.com/urfave/cli/v3"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
@ -155,8 +156,8 @@ func connectToAllRelays(
|
|||
ctx context.Context,
|
||||
c *cli.Command,
|
||||
relayUrls []string,
|
||||
preAuthSigner func(ctx context.Context, c *cli.Command, log func(s string, args ...any), authEvent nostr.RelayEvent) (err error), // if this exists we will force preauth
|
||||
opts ...nostr.PoolOption,
|
||||
preAuthSigner func(ctx context.Context, c *cli.Command, log func(s string, args ...any), authEvent *nostr.Event) (err error), // if this exists we will force preauth
|
||||
opts nostr.PoolOptions,
|
||||
) []*nostr.Relay {
|
||||
// first pass to check if these are valid relay URLs
|
||||
for _, url := range relayUrls {
|
||||
|
@ -166,15 +167,12 @@ func connectToAllRelays(
|
|||
}
|
||||
}
|
||||
|
||||
sys.Pool = nostr.NewSimplePool(context.Background(),
|
||||
append(opts,
|
||||
nostr.WithEventMiddleware(sys.TrackEventHints),
|
||||
nostr.WithPenaltyBox(),
|
||||
nostr.WithRelayOptions(
|
||||
nostr.WithRequestHeader(http.Header{textproto.CanonicalMIMEHeaderKey("user-agent"): {"nak/s"}}),
|
||||
),
|
||||
)...,
|
||||
)
|
||||
opts.EventMiddleware = sys.TrackEventHints
|
||||
opts.PenaltyBox = true
|
||||
opts.RelayOptions = nostr.RelayOptions{
|
||||
RequestHeader: http.Header{textproto.CanonicalMIMEHeaderKey("user-agent"): {"nak/s"}},
|
||||
}
|
||||
sys.Pool = nostr.NewPool(opts)
|
||||
|
||||
relays := make([]*nostr.Relay, 0, len(relayUrls))
|
||||
|
||||
|
@ -236,7 +234,7 @@ func connectToSingleRelay(
|
|||
ctx context.Context,
|
||||
c *cli.Command,
|
||||
url string,
|
||||
preAuthSigner func(ctx context.Context, c *cli.Command, log func(s string, args ...any), authEvent nostr.RelayEvent) (err error),
|
||||
preAuthSigner func(ctx context.Context, c *cli.Command, log func(s string, args ...any), authEvent *nostr.Event) (err error),
|
||||
colorizepreamble func(c func(string, ...any) string),
|
||||
logthis func(s string, args ...any),
|
||||
) *nostr.Relay {
|
||||
|
@ -249,12 +247,12 @@ func connectToSingleRelay(
|
|||
time.Sleep(time.Millisecond * 200)
|
||||
|
||||
for range 5 {
|
||||
if err := relay.Auth(ctx, func(authEvent *nostr.Event) error {
|
||||
if err := relay.Auth(ctx, func(ctx context.Context, authEvent *nostr.Event) error {
|
||||
challengeTag := authEvent.Tags.Find("challenge")
|
||||
if challengeTag[1] == "" {
|
||||
return fmt.Errorf("auth not received yet *****") // what a giant hack
|
||||
}
|
||||
return preAuthSigner(ctx, c, logthis, nostr.RelayEvent{Event: authEvent, Relay: relay})
|
||||
return preAuthSigner(ctx, c, logthis, authEvent)
|
||||
}); err == nil {
|
||||
// auth succeeded
|
||||
goto preauthSuccess
|
||||
|
@ -324,10 +322,10 @@ func supportsDynamicMultilineMagic() bool {
|
|||
return true
|
||||
}
|
||||
|
||||
func authSigner(ctx context.Context, c *cli.Command, log func(s string, args ...any), authEvent nostr.RelayEvent) (err error) {
|
||||
func authSigner(ctx context.Context, c *cli.Command, log func(s string, args ...any), authEvent *nostr.Event) (err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
cleanUrl, _ := strings.CutPrefix(authEvent.Relay.URL, "wss://")
|
||||
cleanUrl, _ := strings.CutPrefix(nip42.GetRelayURLFromAuthEvent(*authEvent), "wss://")
|
||||
log("%s auth failed: %s", colors.errorf(cleanUrl), err)
|
||||
}
|
||||
}()
|
||||
|
@ -341,10 +339,10 @@ func authSigner(ctx context.Context, c *cli.Command, log func(s string, args ...
|
|||
}
|
||||
|
||||
pk, _ := kr.GetPublicKey(ctx)
|
||||
npub, _ := nip19.EncodePublicKey(pk)
|
||||
npub := nip19.EncodeNpub(pk)
|
||||
log("authenticating as %s... ", color.YellowString("%s…%s", npub[0:7], npub[58:]))
|
||||
|
||||
return kr.SignEvent(ctx, authEvent.Event)
|
||||
return kr.SignEvent(ctx, authEvent)
|
||||
}
|
||||
|
||||
func lineProcessingError(ctx context.Context, msg string, args ...any) context.Context {
|
||||
|
@ -368,10 +366,6 @@ func randString(n int) string {
|
|||
return string(b)
|
||||
}
|
||||
|
||||
func leftPadKey(k string) string {
|
||||
return strings.Repeat("0", 64-len(k)) + k
|
||||
}
|
||||
|
||||
func unwrapAll(err error) error {
|
||||
low := err
|
||||
for n := low; n != nil; n = errors.Unwrap(low) {
|
||||
|
|
|
@ -2,19 +2,18 @@ package main
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/keyer"
|
||||
"fiatjaf.com/nostr/nip19"
|
||||
"fiatjaf.com/nostr/nip46"
|
||||
"fiatjaf.com/nostr/nip49"
|
||||
"github.com/chzyer/readline"
|
||||
"github.com/fatih/color"
|
||||
"github.com/urfave/cli/v3"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/keyer"
|
||||
"github.com/nbd-wtf/go-nostr/nip19"
|
||||
"github.com/nbd-wtf/go-nostr/nip46"
|
||||
"github.com/nbd-wtf/go-nostr/nip49"
|
||||
)
|
||||
|
||||
var defaultKeyFlags = []cli.Flag{
|
||||
|
@ -39,10 +38,10 @@ var defaultKeyFlags = []cli.Flag{
|
|||
},
|
||||
}
|
||||
|
||||
func gatherKeyerFromArguments(ctx context.Context, c *cli.Command) (nostr.Keyer, string, error) {
|
||||
func gatherKeyerFromArguments(ctx context.Context, c *cli.Command) (nostr.Keyer, nostr.SecretKey, error) {
|
||||
key, bunker, err := gatherSecretKeyOrBunkerFromArguments(ctx, c)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
return nil, nostr.SecretKey{}, err
|
||||
}
|
||||
|
||||
var kr nostr.Keyer
|
||||
|
@ -55,23 +54,27 @@ func gatherKeyerFromArguments(ctx context.Context, c *cli.Command) (nostr.Keyer,
|
|||
return kr, key, err
|
||||
}
|
||||
|
||||
func gatherSecretKeyOrBunkerFromArguments(ctx context.Context, c *cli.Command) (string, *nip46.BunkerClient, error) {
|
||||
func gatherSecretKeyOrBunkerFromArguments(ctx context.Context, c *cli.Command) (nostr.SecretKey, *nip46.BunkerClient, error) {
|
||||
var err error
|
||||
|
||||
sec := c.String("sec")
|
||||
if strings.HasPrefix(sec, "bunker://") {
|
||||
// it's a bunker
|
||||
bunkerURL := sec
|
||||
clientKey := c.String("connect-as")
|
||||
if clientKey != "" {
|
||||
clientKey = strings.Repeat("0", 64-len(clientKey)) + clientKey
|
||||
clientKeyHex := c.String("connect-as")
|
||||
var clientKey nostr.SecretKey
|
||||
|
||||
if clientKeyHex != "" {
|
||||
clientKey, err = nostr.SecretKeyFromHex(sec)
|
||||
} else {
|
||||
clientKey = nostr.GeneratePrivateKey()
|
||||
clientKey = nostr.Generate()
|
||||
}
|
||||
|
||||
bunker, err := nip46.ConnectBunker(ctx, clientKey, bunkerURL, nil, func(s string) {
|
||||
log(color.CyanString("[nip46]: open the following URL: %s"), s)
|
||||
})
|
||||
return "", bunker, err
|
||||
|
||||
return nostr.SecretKey{}, bunker, err
|
||||
}
|
||||
|
||||
// take private from flags, environment variable or default to 1
|
||||
|
@ -85,35 +88,35 @@ func gatherSecretKeyOrBunkerFromArguments(ctx context.Context, c *cli.Command) (
|
|||
|
||||
if c.Bool("prompt-sec") {
|
||||
if isPiped() {
|
||||
return "", nil, fmt.Errorf("can't prompt for a secret key when processing data from a pipe, try again without --prompt-sec")
|
||||
return nostr.SecretKey{}, nil, fmt.Errorf("can't prompt for a secret key when processing data from a pipe, try again without --prompt-sec")
|
||||
}
|
||||
sec, err = askPassword("type your secret key as ncryptsec, nsec or hex: ", nil)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("failed to get secret key: %w", err)
|
||||
return nostr.SecretKey{}, nil, fmt.Errorf("failed to get secret key: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if strings.HasPrefix(sec, "ncryptsec1") {
|
||||
sec, err = promptDecrypt(sec)
|
||||
sk, err := promptDecrypt(sec)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("failed to decrypt: %w", err)
|
||||
return nostr.SecretKey{}, nil, fmt.Errorf("failed to decrypt: %w", err)
|
||||
}
|
||||
} else if bsec, err := hex.DecodeString(leftPadKey(sec)); err == nil {
|
||||
sec = hex.EncodeToString(bsec)
|
||||
} else if prefix, hexvalue, err := nip19.Decode(sec); err != nil {
|
||||
return "", nil, fmt.Errorf("invalid nsec: %w", err)
|
||||
} else if prefix == "nsec" {
|
||||
sec = hexvalue.(string)
|
||||
return sk, nil, nil
|
||||
}
|
||||
|
||||
if ok := nostr.IsValid32ByteHex(sec); !ok {
|
||||
return "", nil, fmt.Errorf("invalid secret key")
|
||||
if prefix, ski, err := nip19.Decode(sec); err == nil && prefix == "nsec" {
|
||||
return ski.(nostr.SecretKey), nil, nil
|
||||
}
|
||||
|
||||
return sec, nil, nil
|
||||
sk, err := nostr.SecretKeyFromHex(sec)
|
||||
if err != nil {
|
||||
return nostr.SecretKey{}, nil, fmt.Errorf("invalid secret key")
|
||||
}
|
||||
|
||||
return sk, nil, nil
|
||||
}
|
||||
|
||||
func promptDecrypt(ncryptsec string) (string, error) {
|
||||
func promptDecrypt(ncryptsec string) (nostr.SecretKey, error) {
|
||||
for i := 1; i < 4; i++ {
|
||||
var attemptStr string
|
||||
if i > 1 {
|
||||
|
@ -121,7 +124,7 @@ func promptDecrypt(ncryptsec string) (string, error) {
|
|||
}
|
||||
password, err := askPassword("type the password to decrypt your secret key"+attemptStr+": ", nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nostr.SecretKey{}, err
|
||||
}
|
||||
sec, err := nip49.Decrypt(ncryptsec, password)
|
||||
if err != nil {
|
||||
|
@ -129,7 +132,7 @@ func promptDecrypt(ncryptsec string) (string, error) {
|
|||
}
|
||||
return sec, nil
|
||||
}
|
||||
return "", fmt.Errorf("couldn't decrypt private key")
|
||||
return nostr.SecretKey{}, fmt.Errorf("couldn't decrypt private key")
|
||||
}
|
||||
|
||||
func askPassword(msg string, shouldAskAgain func(answer string) bool) (string, error) {
|
||||
|
|
33
key.go
33
key.go
|
@ -6,13 +6,13 @@ import (
|
|||
"fmt"
|
||||
"strings"
|
||||
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/nip19"
|
||||
"fiatjaf.com/nostr/nip49"
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
|
||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||
"github.com/urfave/cli/v3"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip19"
|
||||
"github.com/nbd-wtf/go-nostr/nip49"
|
||||
)
|
||||
|
||||
var key = &cli.Command{
|
||||
|
@ -35,8 +35,8 @@ var generate = &cli.Command{
|
|||
Description: ``,
|
||||
DisableSliceFlagSeparator: true,
|
||||
Action: func(ctx context.Context, c *cli.Command) error {
|
||||
sec := nostr.GeneratePrivateKey()
|
||||
stdout(sec)
|
||||
sec := nostr.Generate()
|
||||
stdout(sec.Hex())
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
@ -54,9 +54,8 @@ var public = &cli.Command{
|
|||
},
|
||||
},
|
||||
Action: func(ctx context.Context, c *cli.Command) error {
|
||||
for sec := range getSecretKeysFromStdinLinesOrSlice(ctx, c, c.Args().Slice()) {
|
||||
b, _ := hex.DecodeString(sec)
|
||||
_, pk := btcec.PrivKeyFromBytes(b)
|
||||
for sk := range getSecretKeysFromStdinLinesOrSlice(ctx, c, c.Args().Slice()) {
|
||||
_, pk := btcec.PrivKeyFromBytes(sk[:])
|
||||
|
||||
if c.Bool("with-parity") {
|
||||
stdout(hex.EncodeToString(pk.SerializeCompressed()))
|
||||
|
@ -264,27 +263,31 @@ However, if the intent is to check if two existing Nostr pubkeys match a given c
|
|||
},
|
||||
}
|
||||
|
||||
func getSecretKeysFromStdinLinesOrSlice(ctx context.Context, _ *cli.Command, keys []string) chan string {
|
||||
ch := make(chan string)
|
||||
func getSecretKeysFromStdinLinesOrSlice(ctx context.Context, _ *cli.Command, keys []string) chan nostr.SecretKey {
|
||||
ch := make(chan nostr.SecretKey)
|
||||
go func() {
|
||||
for sec := range getStdinLinesOrArgumentsFromSlice(keys) {
|
||||
if sec == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
var sk nostr.SecretKey
|
||||
if strings.HasPrefix(sec, "nsec1") {
|
||||
_, data, err := nip19.Decode(sec)
|
||||
if err != nil {
|
||||
ctx = lineProcessingError(ctx, "invalid nsec code: %s", err)
|
||||
continue
|
||||
}
|
||||
sec = data.(string)
|
||||
sk = data.(nostr.SecretKey)
|
||||
}
|
||||
sec = leftPadKey(sec)
|
||||
if !nostr.IsValid32ByteHex(sec) {
|
||||
ctx = lineProcessingError(ctx, "invalid hex key")
|
||||
|
||||
sk, err := nostr.SecretKeyFromHex(sec)
|
||||
if err != nil {
|
||||
ctx = lineProcessingError(ctx, "invalid hex key: %s", err)
|
||||
continue
|
||||
}
|
||||
ch <- sec
|
||||
|
||||
ch <- sk
|
||||
}
|
||||
close(ch)
|
||||
}()
|
||||
|
|
20
main.go
20
main.go
|
@ -7,9 +7,9 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/sdk"
|
||||
"github.com/nbd-wtf/go-nostr/sdk/hints/memoryh"
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/sdk"
|
||||
"fiatjaf.com/nostr/sdk/hints/memoryh"
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
|
@ -111,13 +111,13 @@ var app = &cli.Command{
|
|||
sys = sdk.NewSystem()
|
||||
|
||||
systemOperational:
|
||||
sys.Pool = nostr.NewSimplePool(context.Background(),
|
||||
nostr.WithAuthorKindQueryMiddleware(sys.TrackQueryAttempts),
|
||||
nostr.WithEventMiddleware(sys.TrackEventHints),
|
||||
nostr.WithRelayOptions(
|
||||
nostr.WithRequestHeader(http.Header{textproto.CanonicalMIMEHeaderKey("user-agent"): {"nak/b"}}),
|
||||
),
|
||||
)
|
||||
sys.Pool = nostr.NewPool(nostr.PoolOptions{
|
||||
AuthorKindQueryMiddleware: sys.TrackQueryAttempts,
|
||||
EventMiddleware: sys.TrackEventHints,
|
||||
RelayOptions: nostr.RelayOptions{
|
||||
RequestHeader: http.Header{textproto.CanonicalMIMEHeaderKey("user-agent"): {"nak/b"}},
|
||||
},
|
||||
})
|
||||
|
||||
return ctx, nil
|
||||
},
|
||||
|
|
74
mcp.go
74
mcp.go
|
@ -6,11 +6,11 @@ import (
|
|||
"os"
|
||||
"strings"
|
||||
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/nip19"
|
||||
"fiatjaf.com/nostr/sdk"
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"github.com/mark3labs/mcp-go/server"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip19"
|
||||
"github.com/nbd-wtf/go-nostr/sdk"
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
|
@ -19,13 +19,23 @@ var mcpServer = &cli.Command{
|
|||
Usage: "pander to the AI gods",
|
||||
Description: ``,
|
||||
DisableSliceFlagSeparator: true,
|
||||
Flags: []cli.Flag{},
|
||||
Flags: append(
|
||||
defaultKeyFlags,
|
||||
),
|
||||
Action: func(ctx context.Context, c *cli.Command) error {
|
||||
s := server.NewMCPServer(
|
||||
"nak",
|
||||
version,
|
||||
)
|
||||
|
||||
keyer, sk, err := gatherKeyerFromArguments(ctx, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if sk == nostr.KeyOne && !c.IsSet("sec") {
|
||||
keyer = nil
|
||||
}
|
||||
|
||||
s.AddTool(mcp.NewTool("publish_note",
|
||||
mcp.WithDescription("Publish a short note event to Nostr with the given text content"),
|
||||
mcp.WithString("content", mcp.Description("Arbitrary string to be published"), mcp.Required()),
|
||||
|
@ -36,10 +46,6 @@ var mcpServer = &cli.Command{
|
|||
mention, _ := optional[string](r, "mention")
|
||||
relay, _ := optional[string](r, "relay")
|
||||
|
||||
if mention != "" && !nostr.IsValidPublicKey(mention) {
|
||||
return mcp.NewToolResultError("the given mention isn't a valid public key, it must be 32 bytes hex, like the ones returned by search_profile"), nil
|
||||
}
|
||||
|
||||
sk := os.Getenv("NOSTR_SECRET_KEY")
|
||||
if sk == "" {
|
||||
sk = "0000000000000000000000000000000000000000000000000000000000000001"
|
||||
|
@ -54,12 +60,19 @@ var mcpServer = &cli.Command{
|
|||
}
|
||||
|
||||
if mention != "" {
|
||||
evt.Tags = append(evt.Tags, nostr.Tag{"p", mention})
|
||||
// their inbox relays
|
||||
relays = sys.FetchInboxRelays(ctx, mention, 3)
|
||||
pk, err := nostr.PubKeyFromHex(mention)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError("the given mention isn't a valid public key, it must be 32 bytes hex, like the ones returned by search_profile. Got error: " + err.Error()), nil
|
||||
}
|
||||
|
||||
evt.Sign(sk)
|
||||
evt.Tags = append(evt.Tags, nostr.Tag{"p", pk.Hex()})
|
||||
// their inbox relays
|
||||
relays = sys.FetchInboxRelays(ctx, pk, 3)
|
||||
}
|
||||
|
||||
if err := keyer.SignEvent(ctx, &evt); err != nil {
|
||||
return mcp.NewToolResultError("it was impossible to sign the event, so we can't proceed to publishwith publishing it."), nil
|
||||
}
|
||||
|
||||
// our write relays
|
||||
relays = append(relays, sys.FetchOutboxRelays(ctx, evt.PubKey, 3)...)
|
||||
|
@ -115,7 +128,7 @@ var mcpServer = &cli.Command{
|
|||
|
||||
switch prefix {
|
||||
case "npub":
|
||||
pm := sys.FetchProfileMetadata(ctx, data.(string))
|
||||
pm := sys.FetchProfileMetadata(ctx, data.(nostr.PubKey))
|
||||
return mcp.NewToolResultText(
|
||||
fmt.Sprintf("this is a Nostr profile named '%s', their public key is '%s'",
|
||||
pm.ShortName(), pm.PubKey),
|
||||
|
@ -149,19 +162,23 @@ var mcpServer = &cli.Command{
|
|||
mcp.WithString("name", mcp.Description("Name to be searched"), mcp.Required()),
|
||||
), func(ctx context.Context, r mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
name := required[string](r, "name")
|
||||
re := sys.Pool.QuerySingle(ctx, []string{"relay.nostr.band", "nostr.wine"}, nostr.Filter{Search: name, Kinds: []int{0}})
|
||||
re := sys.Pool.QuerySingle(ctx, []string{"relay.nostr.band", "nostr.wine"}, nostr.Filter{Search: name, Kinds: []nostr.Kind{0}}, nostr.SubscriptionOptions{})
|
||||
if re == nil {
|
||||
return mcp.NewToolResultError("couldn't find anyone with that name"), nil
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText(re.PubKey), nil
|
||||
return mcp.NewToolResultText(re.PubKey.Hex()), nil
|
||||
})
|
||||
|
||||
s.AddTool(mcp.NewTool("get_outbox_relay_for_pubkey",
|
||||
mcp.WithDescription("Get the best relay from where to read notes from a specific Nostr user"),
|
||||
mcp.WithString("pubkey", mcp.Description("Public key of Nostr user we want to know the relay from where to read"), mcp.Required()),
|
||||
), func(ctx context.Context, r mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
pubkey := required[string](r, "pubkey")
|
||||
pubkey, err := nostr.PubKeyFromHex(required[string](r, "pubkey"))
|
||||
if err != nil {
|
||||
return mcp.NewToolResultError("the pubkey given isn't a valid public key, it must be 32 bytes hex, like the ones returned by search_profile. Got error: " + err.Error()), nil
|
||||
}
|
||||
|
||||
res := sys.FetchOutboxRelays(ctx, pubkey, 1)
|
||||
return mcp.NewToolResultText(res[0]), nil
|
||||
})
|
||||
|
@ -171,31 +188,32 @@ var mcpServer = &cli.Command{
|
|||
mcp.WithString("relay", mcp.Description("relay URL to send the query to"), mcp.Required()),
|
||||
mcp.WithNumber("kind", mcp.Description("event kind number to include in the 'kinds' field"), mcp.Required()),
|
||||
mcp.WithNumber("limit", mcp.Description("maximum number of events to query"), mcp.Required()),
|
||||
mcp.WithString("pubkey", mcp.Description("pubkey to include in the 'authors' field")),
|
||||
mcp.WithString("pubkey", mcp.Description("pubkey to include in the 'authors' field, if this is not given we will read any events from this relay")),
|
||||
), func(ctx context.Context, r mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
relay := required[string](r, "relay")
|
||||
kind := int(required[float64](r, "kind"))
|
||||
limit := int(required[float64](r, "limit"))
|
||||
pubkey, _ := optional[string](r, "pubkey")
|
||||
|
||||
if pubkey != "" && !nostr.IsValidPublicKey(pubkey) {
|
||||
return mcp.NewToolResultError("the given pubkey isn't a valid public key, it must be 32 bytes hex, like the ones returned by search_profile"), nil
|
||||
}
|
||||
pubkey, hasPubKey := optional[string](r, "pubkey")
|
||||
|
||||
filter := nostr.Filter{
|
||||
Limit: limit,
|
||||
Kinds: []int{kind},
|
||||
}
|
||||
if pubkey != "" {
|
||||
filter.Authors = []string{pubkey}
|
||||
Kinds: []nostr.Kind{nostr.Kind(kind)},
|
||||
}
|
||||
|
||||
events := sys.Pool.FetchMany(ctx, []string{relay}, filter)
|
||||
if hasPubKey {
|
||||
if pk, err := nostr.PubKeyFromHex(pubkey); err != nil {
|
||||
return mcp.NewToolResultError("the pubkey given isn't a valid public key, it must be 32 bytes hex, like the ones returned by search_profile. Got error: " + err.Error()), nil
|
||||
} else {
|
||||
filter.Authors = append(filter.Authors, pk)
|
||||
}
|
||||
}
|
||||
|
||||
events := sys.Pool.FetchMany(ctx, []string{relay}, filter, nostr.SubscriptionOptions{})
|
||||
|
||||
result := strings.Builder{}
|
||||
for ie := range events {
|
||||
result.WriteString("author public key: ")
|
||||
result.WriteString(ie.PubKey)
|
||||
result.WriteString(ie.PubKey.Hex())
|
||||
result.WriteString("content: '")
|
||||
result.WriteString(ie.Content)
|
||||
result.WriteString("'")
|
||||
|
|
36
musig2.go
36
musig2.go
|
@ -9,39 +9,39 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"fiatjaf.com/nostr"
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
)
|
||||
|
||||
func getMusigAggregatedKey(_ context.Context, keys []string) (string, error) {
|
||||
func getMusigAggregatedKey(_ context.Context, keys []string) (nostr.PubKey, error) {
|
||||
knownSigners := make([]*btcec.PublicKey, len(keys))
|
||||
for i, spk := range keys {
|
||||
bpk, err := hex.DecodeString(spk)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("'%s' is invalid hex: %w", spk, err)
|
||||
return nostr.ZeroPK, fmt.Errorf("'%s' is invalid hex: %w", spk, err)
|
||||
}
|
||||
if len(bpk) == 32 {
|
||||
return "", fmt.Errorf("'%s' is missing the leading parity byte", spk)
|
||||
return nostr.ZeroPK, fmt.Errorf("'%s' is missing the leading parity byte", spk)
|
||||
}
|
||||
pk, err := btcec.ParsePubKey(bpk)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("'%s' is not a valid pubkey: %w", spk, err)
|
||||
return nostr.ZeroPK, fmt.Errorf("'%s' is not a valid pubkey: %w", spk, err)
|
||||
}
|
||||
knownSigners[i] = pk
|
||||
}
|
||||
|
||||
aggpk, _, _, err := musig2.AggregateKeys(knownSigners, true)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("aggregation failed: %w", err)
|
||||
return nostr.ZeroPK, fmt.Errorf("aggregation failed: %w", err)
|
||||
}
|
||||
|
||||
return hex.EncodeToString(aggpk.FinalKey.SerializeCompressed()[1:]), nil
|
||||
return nostr.PubKey(aggpk.FinalKey.SerializeCompressed()[1:]), nil
|
||||
}
|
||||
|
||||
func performMusig(
|
||||
_ context.Context,
|
||||
sec string,
|
||||
sec nostr.SecretKey,
|
||||
evt *nostr.Event,
|
||||
numSigners int,
|
||||
keys []string,
|
||||
|
@ -50,11 +50,7 @@ func performMusig(
|
|||
partialSigs []string,
|
||||
) (signed bool, err error) {
|
||||
// preprocess data received
|
||||
secb, err := hex.DecodeString(sec)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
seck, pubk := btcec.PrivKeyFromBytes(secb)
|
||||
seck, pubk := btcec.PrivKeyFromBytes(sec[:])
|
||||
|
||||
knownSigners := make([]*btcec.PublicKey, 0, numSigners)
|
||||
includesUs := false
|
||||
|
@ -146,7 +142,7 @@ func performMusig(
|
|||
if comb, err := mctx.CombinedKey(); err != nil {
|
||||
return false, fmt.Errorf("failed to combine keys (after %d signers): %w", len(knownSigners), err)
|
||||
} else {
|
||||
evt.PubKey = hex.EncodeToString(comb.SerializeCompressed()[1:])
|
||||
evt.PubKey = nostr.PubKey(comb.SerializeCompressed()[1:])
|
||||
evt.ID = evt.GetID()
|
||||
log("combined key: %x\n\n", comb.SerializeCompressed())
|
||||
}
|
||||
|
@ -200,11 +196,7 @@ func performMusig(
|
|||
|
||||
// signing phase
|
||||
// we always have to sign, so let's do this
|
||||
id := evt.GetID()
|
||||
hash, _ := hex.DecodeString(id)
|
||||
var msg32 [32]byte
|
||||
copy(msg32[:], hash)
|
||||
partialSig, err := session.Sign(msg32) // this will already include our sig in the bundle
|
||||
partialSig, err := session.Sign(evt.GetID()) // this will already include our sig in the bundle
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to produce partial signature: %w", err)
|
||||
}
|
||||
|
@ -225,7 +217,7 @@ func performMusig(
|
|||
}
|
||||
|
||||
// we have the signature
|
||||
evt.Sig = hex.EncodeToString(session.FinalSig().Serialize())
|
||||
evt.Sig = [64]byte(session.FinalSig().Serialize())
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
@ -258,7 +250,7 @@ func eventToCliArgs(evt *nostr.Event) string {
|
|||
b.Grow(100)
|
||||
|
||||
b.WriteString("-k ")
|
||||
b.WriteString(strconv.Itoa(evt.Kind))
|
||||
b.WriteString(strconv.Itoa(int(evt.Kind)))
|
||||
|
||||
b.WriteString(" -ts ")
|
||||
b.WriteString(strconv.FormatInt(int64(evt.CreatedAt), 10))
|
||||
|
@ -269,7 +261,7 @@ func eventToCliArgs(evt *nostr.Event) string {
|
|||
|
||||
for _, tag := range evt.Tags {
|
||||
b.WriteString(" -t '")
|
||||
b.WriteString(tag.Key())
|
||||
b.WriteString(tag[0])
|
||||
if len(tag) > 1 {
|
||||
b.WriteString("=")
|
||||
b.WriteString(tag[1])
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
|
||||
"github.com/hanwen/go-fuse/v2/fs"
|
||||
"github.com/hanwen/go-fuse/v2/fuse"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"fiatjaf.com/nostr"
|
||||
)
|
||||
|
||||
type AsyncFile struct {
|
||||
|
|
|
@ -15,14 +15,15 @@ import (
|
|||
"unsafe"
|
||||
|
||||
"fiatjaf.com/lib/debouncer"
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/nip19"
|
||||
"fiatjaf.com/nostr/nip27"
|
||||
"fiatjaf.com/nostr/nip73"
|
||||
"fiatjaf.com/nostr/nip92"
|
||||
sdk "fiatjaf.com/nostr/sdk"
|
||||
"github.com/fatih/color"
|
||||
"github.com/hanwen/go-fuse/v2/fs"
|
||||
"github.com/hanwen/go-fuse/v2/fuse"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip19"
|
||||
"github.com/nbd-wtf/go-nostr/nip27"
|
||||
"github.com/nbd-wtf/go-nostr/nip92"
|
||||
sdk "github.com/nbd-wtf/go-nostr/sdk"
|
||||
)
|
||||
|
||||
type EntityDir struct {
|
||||
|
@ -95,11 +96,10 @@ func (e *EntityDir) Setattr(_ context.Context, _ fs.FileHandle, in *fuse.SetAttr
|
|||
func (e *EntityDir) OnAdd(_ context.Context) {
|
||||
log := e.root.ctx.Value("log").(func(msg string, args ...any))
|
||||
|
||||
npub, _ := nip19.EncodePublicKey(e.event.PubKey)
|
||||
e.AddChild("@author", e.NewPersistentInode(
|
||||
e.root.ctx,
|
||||
&fs.MemSymlink{
|
||||
Data: []byte(e.root.wd + "/" + npub),
|
||||
Data: []byte(e.root.wd + "/" + nip19.EncodeNpub(e.event.PubKey)),
|
||||
},
|
||||
fs.StableAttr{Mode: syscall.S_IFLNK},
|
||||
), true)
|
||||
|
@ -180,8 +180,12 @@ func (e *EntityDir) OnAdd(_ context.Context) {
|
|||
|
||||
var refsdir *fs.Inode
|
||||
i := 0
|
||||
for ref := range nip27.ParseReferences(*e.event) {
|
||||
for ref := range nip27.Parse(e.event.Content) {
|
||||
if _, isExternal := ref.Pointer.(nip73.ExternalPointer); isExternal {
|
||||
continue
|
||||
}
|
||||
i++
|
||||
|
||||
if refsdir == nil {
|
||||
refsdir = e.NewPersistentInode(e.root.ctx, &fs.Inode{}, fs.StableAttr{Mode: syscall.S_IFDIR})
|
||||
e.root.AddChild("references", refsdir, true)
|
||||
|
@ -320,7 +324,11 @@ func (e *EntityDir) handleWrite() {
|
|||
}
|
||||
|
||||
// add "p" tags from people mentioned and "q" tags from events mentioned
|
||||
for ref := range nip27.ParseReferences(evt) {
|
||||
for ref := range nip27.Parse(evt.Content) {
|
||||
if _, isExternal := ref.Pointer.(nip73.ExternalPointer); isExternal {
|
||||
continue
|
||||
}
|
||||
|
||||
tag := ref.Pointer.AsTag()
|
||||
key := tag[0]
|
||||
val := tag[1]
|
||||
|
@ -339,7 +347,7 @@ func (e *EntityDir) handleWrite() {
|
|||
}
|
||||
logverbose("%s\n", evt)
|
||||
|
||||
relays := e.root.sys.FetchWriteRelays(e.root.ctx, e.root.rootPubKey, 8)
|
||||
relays := e.root.sys.FetchWriteRelays(e.root.ctx, e.root.rootPubKey)
|
||||
if len(relays) == 0 {
|
||||
relays = e.root.sys.FetchOutboxRelays(e.root.ctx, e.root.rootPubKey, 6)
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package nostrfs
|
|||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
|
@ -11,16 +12,16 @@ import (
|
|||
"syscall"
|
||||
"time"
|
||||
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/nip10"
|
||||
"fiatjaf.com/nostr/nip19"
|
||||
"fiatjaf.com/nostr/nip22"
|
||||
"fiatjaf.com/nostr/nip27"
|
||||
"fiatjaf.com/nostr/nip73"
|
||||
"fiatjaf.com/nostr/nip92"
|
||||
sdk "fiatjaf.com/nostr/sdk"
|
||||
"github.com/hanwen/go-fuse/v2/fs"
|
||||
"github.com/hanwen/go-fuse/v2/fuse"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip10"
|
||||
"github.com/nbd-wtf/go-nostr/nip19"
|
||||
"github.com/nbd-wtf/go-nostr/nip22"
|
||||
"github.com/nbd-wtf/go-nostr/nip27"
|
||||
"github.com/nbd-wtf/go-nostr/nip73"
|
||||
"github.com/nbd-wtf/go-nostr/nip92"
|
||||
sdk "github.com/nbd-wtf/go-nostr/sdk"
|
||||
)
|
||||
|
||||
type EventDir struct {
|
||||
|
@ -58,14 +59,13 @@ func (r *NostrRoot) CreateEventDir(
|
|||
h := parent.EmbeddedInode().NewPersistentInode(
|
||||
r.ctx,
|
||||
&EventDir{ctx: r.ctx, wd: r.wd, evt: event},
|
||||
fs.StableAttr{Mode: syscall.S_IFDIR, Ino: hexToUint64(event.ID)},
|
||||
fs.StableAttr{Mode: syscall.S_IFDIR, Ino: binary.BigEndian.Uint64(event.ID[8:16])},
|
||||
)
|
||||
|
||||
npub, _ := nip19.EncodePublicKey(event.PubKey)
|
||||
h.AddChild("@author", h.NewPersistentInode(
|
||||
r.ctx,
|
||||
&fs.MemSymlink{
|
||||
Data: []byte(r.wd + "/" + npub),
|
||||
Data: []byte(r.wd + "/" + nip19.EncodeNpub(event.PubKey)),
|
||||
},
|
||||
fs.StableAttr{Mode: syscall.S_IFLNK},
|
||||
), true)
|
||||
|
@ -88,7 +88,7 @@ func (r *NostrRoot) CreateEventDir(
|
|||
h.AddChild("id", h.NewPersistentInode(
|
||||
r.ctx,
|
||||
&fs.MemRegularFile{
|
||||
Data: []byte(event.ID),
|
||||
Data: []byte(event.ID.Hex()),
|
||||
Attr: fuse.Attr{
|
||||
Mode: 0444,
|
||||
Ctime: uint64(event.CreatedAt),
|
||||
|
@ -115,8 +115,12 @@ func (r *NostrRoot) CreateEventDir(
|
|||
|
||||
var refsdir *fs.Inode
|
||||
i := 0
|
||||
for ref := range nip27.ParseReferences(*event) {
|
||||
for ref := range nip27.Parse(event.Content) {
|
||||
if _, isExternal := ref.Pointer.(nip73.ExternalPointer); isExternal {
|
||||
continue
|
||||
}
|
||||
i++
|
||||
|
||||
if refsdir == nil {
|
||||
refsdir = h.NewPersistentInode(r.ctx, &fs.Inode{}, fs.StableAttr{Mode: syscall.S_IFDIR})
|
||||
h.AddChild("references", refsdir, true)
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
package nostrfs
|
||||
|
||||
import "strconv"
|
||||
import (
|
||||
"fiatjaf.com/nostr"
|
||||
)
|
||||
|
||||
func kindToExtension(kind int) string {
|
||||
func kindToExtension(kind nostr.Kind) string {
|
||||
switch kind {
|
||||
case 30023:
|
||||
return "md"
|
||||
|
@ -12,8 +14,3 @@ func kindToExtension(kind int) string {
|
|||
return "txt"
|
||||
}
|
||||
}
|
||||
|
||||
func hexToUint64(hexStr string) uint64 {
|
||||
v, _ := strconv.ParseUint(hexStr[16:32], 16, 64)
|
||||
return v
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package nostrfs
|
|||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
|
@ -10,12 +11,12 @@ import (
|
|||
"syscall"
|
||||
"time"
|
||||
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/nip19"
|
||||
"github.com/fatih/color"
|
||||
"github.com/hanwen/go-fuse/v2/fs"
|
||||
"github.com/hanwen/go-fuse/v2/fuse"
|
||||
"github.com/liamg/magic"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip19"
|
||||
)
|
||||
|
||||
type NpubDir struct {
|
||||
|
@ -36,7 +37,7 @@ func (r *NostrRoot) CreateNpubDir(
|
|||
return parent.EmbeddedInode().NewPersistentInode(
|
||||
r.ctx,
|
||||
npubdir,
|
||||
fs.StableAttr{Mode: syscall.S_IFDIR, Ino: hexToUint64(pointer.PublicKey)},
|
||||
fs.StableAttr{Mode: syscall.S_IFDIR, Ino: binary.BigEndian.Uint64(pointer.PublicKey[8:16])},
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -49,7 +50,7 @@ func (h *NpubDir) OnAdd(_ context.Context) {
|
|||
|
||||
h.AddChild("pubkey", h.NewPersistentInode(
|
||||
h.root.ctx,
|
||||
&fs.MemRegularFile{Data: []byte(h.pointer.PublicKey + "\n"), Attr: fuse.Attr{Mode: 0444}},
|
||||
&fs.MemRegularFile{Data: []byte(h.pointer.PublicKey.Hex() + "\n"), Attr: fuse.Attr{Mode: 0444}},
|
||||
fs.StableAttr{},
|
||||
), true)
|
||||
|
||||
|
@ -116,8 +117,8 @@ func (h *NpubDir) OnAdd(_ context.Context) {
|
|||
&ViewDir{
|
||||
root: h.root,
|
||||
filter: nostr.Filter{
|
||||
Kinds: []int{1},
|
||||
Authors: []string{h.pointer.PublicKey},
|
||||
Kinds: []nostr.Kind{1},
|
||||
Authors: []nostr.PubKey{h.pointer.PublicKey},
|
||||
},
|
||||
paginate: true,
|
||||
relays: relays,
|
||||
|
@ -138,8 +139,8 @@ func (h *NpubDir) OnAdd(_ context.Context) {
|
|||
&ViewDir{
|
||||
root: h.root,
|
||||
filter: nostr.Filter{
|
||||
Kinds: []int{1111},
|
||||
Authors: []string{h.pointer.PublicKey},
|
||||
Kinds: []nostr.Kind{1111},
|
||||
Authors: []nostr.PubKey{h.pointer.PublicKey},
|
||||
},
|
||||
paginate: true,
|
||||
relays: relays,
|
||||
|
@ -159,8 +160,8 @@ func (h *NpubDir) OnAdd(_ context.Context) {
|
|||
&ViewDir{
|
||||
root: h.root,
|
||||
filter: nostr.Filter{
|
||||
Kinds: []int{20},
|
||||
Authors: []string{h.pointer.PublicKey},
|
||||
Kinds: []nostr.Kind{20},
|
||||
Authors: []nostr.PubKey{h.pointer.PublicKey},
|
||||
},
|
||||
paginate: true,
|
||||
relays: relays,
|
||||
|
@ -180,8 +181,8 @@ func (h *NpubDir) OnAdd(_ context.Context) {
|
|||
&ViewDir{
|
||||
root: h.root,
|
||||
filter: nostr.Filter{
|
||||
Kinds: []int{21, 22},
|
||||
Authors: []string{h.pointer.PublicKey},
|
||||
Kinds: []nostr.Kind{21, 22},
|
||||
Authors: []nostr.PubKey{h.pointer.PublicKey},
|
||||
},
|
||||
paginate: false,
|
||||
relays: relays,
|
||||
|
@ -201,8 +202,8 @@ func (h *NpubDir) OnAdd(_ context.Context) {
|
|||
&ViewDir{
|
||||
root: h.root,
|
||||
filter: nostr.Filter{
|
||||
Kinds: []int{9802},
|
||||
Authors: []string{h.pointer.PublicKey},
|
||||
Kinds: []nostr.Kind{9802},
|
||||
Authors: []nostr.PubKey{h.pointer.PublicKey},
|
||||
},
|
||||
paginate: false,
|
||||
relays: relays,
|
||||
|
@ -222,8 +223,8 @@ func (h *NpubDir) OnAdd(_ context.Context) {
|
|||
&ViewDir{
|
||||
root: h.root,
|
||||
filter: nostr.Filter{
|
||||
Kinds: []int{30023},
|
||||
Authors: []string{h.pointer.PublicKey},
|
||||
Kinds: []nostr.Kind{30023},
|
||||
Authors: []nostr.PubKey{h.pointer.PublicKey},
|
||||
},
|
||||
paginate: false,
|
||||
relays: relays,
|
||||
|
@ -244,8 +245,8 @@ func (h *NpubDir) OnAdd(_ context.Context) {
|
|||
&ViewDir{
|
||||
root: h.root,
|
||||
filter: nostr.Filter{
|
||||
Kinds: []int{30818},
|
||||
Authors: []string{h.pointer.PublicKey},
|
||||
Kinds: []nostr.Kind{30818},
|
||||
Authors: []nostr.PubKey{h.pointer.PublicKey},
|
||||
},
|
||||
paginate: false,
|
||||
relays: relays,
|
||||
|
|
|
@ -6,12 +6,12 @@ import (
|
|||
"syscall"
|
||||
"time"
|
||||
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/nip05"
|
||||
"fiatjaf.com/nostr/nip19"
|
||||
"fiatjaf.com/nostr/sdk"
|
||||
"github.com/hanwen/go-fuse/v2/fs"
|
||||
"github.com/hanwen/go-fuse/v2/fuse"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip05"
|
||||
"github.com/nbd-wtf/go-nostr/nip19"
|
||||
"github.com/nbd-wtf/go-nostr/sdk"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
|
@ -25,7 +25,7 @@ type NostrRoot struct {
|
|||
ctx context.Context
|
||||
wd string
|
||||
sys *sdk.System
|
||||
rootPubKey string
|
||||
rootPubKey nostr.PubKey
|
||||
signer nostr.Signer
|
||||
|
||||
opts Options
|
||||
|
@ -54,7 +54,7 @@ func NewNostrRoot(ctx context.Context, sys *sdk.System, user nostr.User, mountpo
|
|||
}
|
||||
|
||||
func (r *NostrRoot) OnAdd(_ context.Context) {
|
||||
if r.rootPubKey == "" {
|
||||
if r.rootPubKey == nostr.ZeroPK {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -65,16 +65,15 @@ func (r *NostrRoot) OnAdd(_ context.Context) {
|
|||
fl := r.sys.FetchFollowList(r.ctx, r.rootPubKey)
|
||||
for _, f := range fl.Items {
|
||||
pointer := nostr.ProfilePointer{PublicKey: f.Pubkey, Relays: []string{f.Relay}}
|
||||
npub, _ := nip19.EncodePublicKey(f.Pubkey)
|
||||
r.AddChild(
|
||||
npub,
|
||||
nip19.EncodeNpub(f.Pubkey),
|
||||
r.CreateNpubDir(r, pointer, nil),
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
// add ourselves
|
||||
npub, _ := nip19.EncodePublicKey(r.rootPubKey)
|
||||
npub := nip19.EncodeNpub(r.rootPubKey)
|
||||
if r.GetChild(npub) == nil {
|
||||
pointer := nostr.ProfilePointer{PublicKey: r.rootPubKey}
|
||||
|
||||
|
|
|
@ -8,11 +8,12 @@ import (
|
|||
"syscall"
|
||||
|
||||
"fiatjaf.com/lib/debouncer"
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/nip27"
|
||||
"fiatjaf.com/nostr/nip73"
|
||||
"github.com/fatih/color"
|
||||
"github.com/hanwen/go-fuse/v2/fs"
|
||||
"github.com/hanwen/go-fuse/v2/fuse"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip27"
|
||||
)
|
||||
|
||||
type ViewDir struct {
|
||||
|
@ -141,13 +142,17 @@ func (n *ViewDir) publishNote() {
|
|||
}
|
||||
|
||||
// our write relays
|
||||
relays := n.root.sys.FetchWriteRelays(n.root.ctx, n.root.rootPubKey, 8)
|
||||
relays := n.root.sys.FetchWriteRelays(n.root.ctx, n.root.rootPubKey)
|
||||
if len(relays) == 0 {
|
||||
relays = n.root.sys.FetchOutboxRelays(n.root.ctx, n.root.rootPubKey, 6)
|
||||
}
|
||||
|
||||
// add "p" tags from people mentioned and "q" tags from events mentioned
|
||||
for ref := range nip27.ParseReferences(evt) {
|
||||
for ref := range nip27.Parse(evt.Content) {
|
||||
if _, isExternal := ref.Pointer.(nip73.ExternalPointer); isExternal {
|
||||
continue
|
||||
}
|
||||
|
||||
tag := ref.Pointer.AsTag()
|
||||
key := tag[0]
|
||||
val := tag[1]
|
||||
|
@ -159,7 +164,12 @@ func (n *ViewDir) publishNote() {
|
|||
|
||||
// add their "read" relays
|
||||
if key == "p" {
|
||||
for _, r := range n.root.sys.FetchInboxRelays(n.root.ctx, val, 4) {
|
||||
pk, err := nostr.PubKeyFromHex(val)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, r := range n.root.sys.FetchInboxRelays(n.root.ctx, pk, 4) {
|
||||
if !slices.Contains(relays, r) {
|
||||
relays = append(relays, r)
|
||||
}
|
||||
|
@ -196,8 +206,8 @@ func (n *ViewDir) publishNote() {
|
|||
|
||||
if success {
|
||||
n.RmChild("new")
|
||||
n.AddChild(evt.ID, n.root.CreateEventDir(n, &evt), true)
|
||||
log("event published as %s and updated locally.\n", color.BlueString(evt.ID))
|
||||
n.AddChild(evt.ID.Hex(), n.root.CreateEventDir(n, &evt), true)
|
||||
log("event published as %s and updated locally.\n", color.BlueString(evt.ID.Hex()))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -241,23 +251,24 @@ func (n *ViewDir) Opendir(ctx context.Context) syscall.Errno {
|
|||
}
|
||||
|
||||
if n.replaceable {
|
||||
for rkey, evt := range n.root.sys.Pool.FetchManyReplaceable(n.root.ctx, n.relays, n.filter,
|
||||
nostr.WithLabel("nakfs"),
|
||||
).Range {
|
||||
for rkey, evt := range n.root.sys.Pool.FetchManyReplaceable(n.root.ctx, n.relays, n.filter, nostr.SubscriptionOptions{
|
||||
Label: "nakfs",
|
||||
}).Range {
|
||||
name := rkey.D
|
||||
if name == "" {
|
||||
name = "_"
|
||||
}
|
||||
if n.GetChild(name) == nil {
|
||||
n.AddChild(name, n.root.CreateEntityDir(n, evt), true)
|
||||
n.AddChild(name, n.root.CreateEntityDir(n, &evt), true)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for ie := range n.root.sys.Pool.FetchMany(n.root.ctx, n.relays, n.filter,
|
||||
nostr.WithLabel("nakfs"),
|
||||
) {
|
||||
if n.GetChild(ie.Event.ID) == nil {
|
||||
n.AddChild(ie.Event.ID, n.root.CreateEventDir(n, ie.Event), true)
|
||||
nostr.SubscriptionOptions{
|
||||
Label: "nakfs",
|
||||
}) {
|
||||
if n.GetChild(ie.Event.ID.Hex()) == nil {
|
||||
n.AddChild(ie.Event.ID.Hex(), n.root.CreateEventDir(n, &ie.Event), true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
10
outbox.go
10
outbox.go
|
@ -6,8 +6,8 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"fiatjaf.com/nostr"
|
||||
"github.com/urfave/cli/v3"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
)
|
||||
|
||||
var outbox = &cli.Command{
|
||||
|
@ -52,12 +52,12 @@ var outbox = &cli.Command{
|
|||
return fmt.Errorf("expected exactly one argument (pubkey)")
|
||||
}
|
||||
|
||||
pubkey := c.Args().First()
|
||||
if !nostr.IsValidPublicKey(pubkey) {
|
||||
return fmt.Errorf("invalid public key: %s", pubkey)
|
||||
pk, err := nostr.PubKeyFromHex(c.Args().First())
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid public key '%s': %w", c.Args().First(), err)
|
||||
}
|
||||
|
||||
for _, relay := range sys.FetchOutboxRelays(ctx, pubkey, 6) {
|
||||
for _, relay := range sys.FetchOutboxRelays(ctx, pk, 6) {
|
||||
stdout(relay)
|
||||
}
|
||||
|
||||
|
|
6
relay.go
6
relay.go
|
@ -10,9 +10,9 @@ import (
|
|||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip11"
|
||||
"github.com/nbd-wtf/go-nostr/nip86"
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/nip11"
|
||||
"fiatjaf.com/nostr/nip86"
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
|
|
33
req.go
33
req.go
|
@ -6,10 +6,11 @@ import (
|
|||
"os"
|
||||
"strings"
|
||||
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/nip42"
|
||||
"fiatjaf.com/nostr/nip77"
|
||||
"github.com/fatih/color"
|
||||
"github.com/mailru/easyjson"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip77"
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
|
@ -88,16 +89,20 @@ example:
|
|||
c,
|
||||
relayUrls,
|
||||
forcePreAuthSigner,
|
||||
nostr.WithAuthHandler(func(ctx context.Context, authEvent nostr.RelayEvent) error {
|
||||
nostr.PoolOptions{
|
||||
AuthHandler: func(ctx context.Context, authEvent *nostr.Event) error {
|
||||
return authSigner(ctx, c, func(s string, args ...any) {
|
||||
if strings.HasPrefix(s, "authenticating as") {
|
||||
cleanUrl, _ := strings.CutPrefix(authEvent.Relay.URL, "wss://")
|
||||
cleanUrl, _ := strings.CutPrefix(
|
||||
nip42.GetRelayURLFromAuthEvent(*authEvent),
|
||||
"wss://",
|
||||
)
|
||||
s = "authenticating to " + color.CyanString(cleanUrl) + " as" + s[len("authenticating as"):]
|
||||
}
|
||||
log(s+"\n", args...)
|
||||
}, authEvent)
|
||||
}),
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
// stop here already if all connections failed
|
||||
if len(relays) == 0 {
|
||||
|
@ -132,7 +137,7 @@ example:
|
|||
|
||||
if len(relayUrls) > 0 {
|
||||
if c.Bool("ids-only") {
|
||||
seen := make(map[string]struct{}, max(500, filter.Limit))
|
||||
seen := make(map[nostr.ID]struct{}, max(500, filter.Limit))
|
||||
for _, url := range relayUrls {
|
||||
ch, err := nip77.FetchIDsOnly(ctx, url, filter)
|
||||
if err != nil {
|
||||
|
@ -155,7 +160,7 @@ example:
|
|||
fn = sys.Pool.SubscribeMany
|
||||
}
|
||||
|
||||
for ie := range fn(ctx, relayUrls, filter) {
|
||||
for ie := range fn(ctx, relayUrls, filter, nostr.SubscriptionOptions{}) {
|
||||
stdout(ie.Event)
|
||||
}
|
||||
}
|
||||
|
@ -165,7 +170,7 @@ example:
|
|||
if c.Bool("bare") {
|
||||
result = filter.String()
|
||||
} else {
|
||||
j, _ := json.Marshal(nostr.ReqEnvelope{SubscriptionID: "nak", Filters: nostr.Filters{filter}})
|
||||
j, _ := json.Marshal(nostr.ReqEnvelope{SubscriptionID: "nak", Filter: filter})
|
||||
result = string(j)
|
||||
}
|
||||
|
||||
|
@ -179,13 +184,13 @@ example:
|
|||
}
|
||||
|
||||
var reqFilterFlags = []cli.Flag{
|
||||
&cli.StringSliceFlag{
|
||||
&PubKeySliceFlag{
|
||||
Name: "author",
|
||||
Aliases: []string{"a"},
|
||||
Usage: "only accept events from these authors (pubkey as hex)",
|
||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
&IDSliceFlag{
|
||||
Name: "id",
|
||||
Aliases: []string{"i"},
|
||||
Usage: "only accept events with these ids (hex)",
|
||||
|
@ -244,14 +249,14 @@ var reqFilterFlags = []cli.Flag{
|
|||
}
|
||||
|
||||
func applyFlagsToFilter(c *cli.Command, filter *nostr.Filter) error {
|
||||
if authors := c.StringSlice("author"); len(authors) > 0 {
|
||||
if authors := getPubKeySlice(c, "author"); len(authors) > 0 {
|
||||
filter.Authors = append(filter.Authors, authors...)
|
||||
}
|
||||
if ids := c.StringSlice("id"); len(ids) > 0 {
|
||||
if ids := getIDSlice(c, "id"); len(ids) > 0 {
|
||||
filter.IDs = append(filter.IDs, ids...)
|
||||
}
|
||||
for _, kind64 := range c.IntSlice("kind") {
|
||||
filter.Kinds = append(filter.Kinds, int(kind64))
|
||||
filter.Kinds = append(filter.Kinds, nostr.Kind(kind64))
|
||||
}
|
||||
if search := c.String("search"); search != "" {
|
||||
filter.Search = search
|
||||
|
|
32
serve.go
32
serve.go
|
@ -8,11 +8,11 @@ import (
|
|||
"os"
|
||||
"time"
|
||||
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/eventstore/slicestore"
|
||||
"fiatjaf.com/nostr/khatru"
|
||||
"github.com/bep/debounce"
|
||||
"github.com/fatih/color"
|
||||
"github.com/fiatjaf/eventstore/slicestore"
|
||||
"github.com/fiatjaf/khatru"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
|
@ -38,7 +38,7 @@ var serve = &cli.Command{
|
|||
},
|
||||
},
|
||||
Action: func(ctx context.Context, c *cli.Command) error {
|
||||
db := slicestore.SliceStore{MaxLimit: math.MaxInt}
|
||||
db := &slicestore.SliceStore{MaxLimit: math.MaxInt}
|
||||
|
||||
var scanner *bufio.Scanner
|
||||
if path := c.String("events"); path != "" {
|
||||
|
@ -59,7 +59,7 @@ var serve = &cli.Command{
|
|||
if err := json.Unmarshal(scanner.Bytes(), &evt); err != nil {
|
||||
return fmt.Errorf("invalid event received at line %d: %s (`%s`)", i, err, scanner.Text())
|
||||
}
|
||||
db.SaveEvent(ctx, &evt)
|
||||
db.SaveEvent(evt)
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
@ -71,10 +71,7 @@ var serve = &cli.Command{
|
|||
rl.Info.Software = "https://github.com/fiatjaf/nak"
|
||||
rl.Info.Version = version
|
||||
|
||||
rl.QueryEvents = append(rl.QueryEvents, db.QueryEvents)
|
||||
rl.CountEvents = append(rl.CountEvents, db.CountEvents)
|
||||
rl.DeleteEvent = append(rl.DeleteEvent, db.DeleteEvent)
|
||||
rl.StoreEvent = append(rl.StoreEvent, db.SaveEvent)
|
||||
rl.UseEventstore(db)
|
||||
|
||||
started := make(chan bool)
|
||||
exited := make(chan error)
|
||||
|
@ -90,28 +87,29 @@ var serve = &cli.Command{
|
|||
var printStatus func()
|
||||
|
||||
// relay logging
|
||||
rl.RejectFilter = append(rl.RejectFilter, func(ctx context.Context, filter nostr.Filter) (reject bool, msg string) {
|
||||
rl.OnRequest = func(ctx context.Context, filter nostr.Filter) (reject bool, msg string) {
|
||||
log(" got %s %v\n", color.HiYellowString("request"), colors.italic(filter))
|
||||
printStatus()
|
||||
return false, ""
|
||||
})
|
||||
rl.RejectCountFilter = append(rl.RejectCountFilter, func(ctx context.Context, filter nostr.Filter) (reject bool, msg string) {
|
||||
}
|
||||
|
||||
rl.OnCount = func(ctx context.Context, filter nostr.Filter) (reject bool, msg string) {
|
||||
log(" got %s %v\n", color.HiCyanString("count request"), colors.italic(filter))
|
||||
printStatus()
|
||||
return false, ""
|
||||
})
|
||||
rl.RejectEvent = append(rl.RejectEvent, func(ctx context.Context, event *nostr.Event) (reject bool, msg string) {
|
||||
}
|
||||
|
||||
rl.OnEvent = func(ctx context.Context, event nostr.Event) (reject bool, msg string) {
|
||||
log(" got %s %v\n", color.BlueString("event"), colors.italic(event))
|
||||
printStatus()
|
||||
return false, ""
|
||||
})
|
||||
}
|
||||
|
||||
d := debounce.New(time.Second * 2)
|
||||
printStatus = func() {
|
||||
d(func() {
|
||||
totalEvents := 0
|
||||
ch, _ := db.QueryEvents(ctx, nostr.Filter{})
|
||||
for range ch {
|
||||
for range db.QueryEvents(nostr.Filter{}) {
|
||||
totalEvents++
|
||||
}
|
||||
subs := rl.GetListeningFilters()
|
||||
|
|
|
@ -3,8 +3,8 @@ package main
|
|||
import (
|
||||
"context"
|
||||
|
||||
"fiatjaf.com/nostr"
|
||||
"github.com/urfave/cli/v3"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
)
|
||||
|
||||
var verify = &cli.Command{
|
||||
|
@ -30,8 +30,8 @@ it outputs nothing if the verification is successful.`,
|
|||
continue
|
||||
}
|
||||
|
||||
if ok, err := evt.CheckSignature(); !ok {
|
||||
ctx = lineProcessingError(ctx, "invalid signature: %v", err)
|
||||
if !evt.VerifySignature() {
|
||||
ctx = lineProcessingError(ctx, "invalid signature")
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
|
37
wallet.go
37
wallet.go
|
@ -6,10 +6,10 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip60"
|
||||
"github.com/nbd-wtf/go-nostr/nip61"
|
||||
"github.com/nbd-wtf/go-nostr/sdk"
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/nip60"
|
||||
"fiatjaf.com/nostr/nip61"
|
||||
"fiatjaf.com/nostr/sdk"
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
|
@ -30,7 +30,7 @@ func prepareWallet(ctx context.Context, c *cli.Command) (*nip60.Wallet, func(),
|
|||
return nil, nil, fmt.Errorf("error loading walle")
|
||||
}
|
||||
|
||||
w.Processed = func(evt *nostr.Event, err error) {
|
||||
w.Processed = func(evt nostr.Event, err error) {
|
||||
if err == nil {
|
||||
logverbose("processed event %s\n", evt)
|
||||
} else {
|
||||
|
@ -321,11 +321,16 @@ var wallet = &cli.Command{
|
|||
return err
|
||||
}
|
||||
|
||||
amount := c.Uint("amount")
|
||||
amount, err := strconv.ParseInt(c.Args().First(), 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid amount '%s': %w", c.Args().First(), err)
|
||||
}
|
||||
|
||||
target := c.String("target")
|
||||
var pm sdk.ProfileMetadata
|
||||
|
||||
var evt *nostr.Event
|
||||
var eventId string
|
||||
var eventId nostr.ID
|
||||
|
||||
if strings.HasPrefix(target, "nevent1") {
|
||||
evt, _, err = sys.FetchSpecificEventFromInput(ctx, target, sdk.FetchSpecificEventParameters{
|
||||
|
@ -335,13 +340,13 @@ var wallet = &cli.Command{
|
|||
return err
|
||||
}
|
||||
eventId = evt.ID
|
||||
target = evt.PubKey
|
||||
}
|
||||
|
||||
pm, err := sys.FetchProfileFromInput(ctx, target)
|
||||
pm = sys.FetchProfileMetadata(ctx, evt.PubKey)
|
||||
} else {
|
||||
pm, err = sys.FetchProfileFromInput(ctx, target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
log("sending %d sat to '%s' (%s)", amount, pm.ShortName(), pm.Npub())
|
||||
|
||||
|
@ -361,7 +366,7 @@ var wallet = &cli.Command{
|
|||
sys.FetchInboxRelays,
|
||||
sys.FetchOutboxRelays(ctx, pm.PubKey, 3),
|
||||
eventId,
|
||||
amount,
|
||||
uint64(amount),
|
||||
c.String("message"),
|
||||
)
|
||||
if err != nil {
|
||||
|
@ -426,14 +431,14 @@ var wallet = &cli.Command{
|
|||
|
||||
kr, _, _ := gatherKeyerFromArguments(ctx, c)
|
||||
pk, _ := kr.GetPublicKey(ctx)
|
||||
relays := sys.FetchWriteRelays(ctx, pk, 6)
|
||||
relays := sys.FetchWriteRelays(ctx, pk)
|
||||
|
||||
info := nip61.Info{}
|
||||
ie := sys.Pool.QuerySingle(ctx, relays, nostr.Filter{
|
||||
Kinds: []int{10019},
|
||||
Authors: []string{pk},
|
||||
Kinds: []nostr.Kind{10019},
|
||||
Authors: []nostr.PubKey{pk},
|
||||
Limit: 1,
|
||||
})
|
||||
}, nostr.SubscriptionOptions{})
|
||||
if ie != nil {
|
||||
info.ParseEvent(ie.Event)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue