mirror of https://github.com/fiatjaf/nak.git
nak admin: for nip86 management (the previous command was broken).
This commit is contained in:
parent
bf1690a041
commit
3b4d6046cf
|
@ -147,7 +147,7 @@ type the password to decrypt your secret key: **********
|
||||||
|
|
||||||
### talk to a relay's NIP-86 management API
|
### talk to a relay's NIP-86 management API
|
||||||
```shell
|
```shell
|
||||||
nak relay allowpubkey --sec ncryptsec1qggx54cg270zy9y8krwmfz29jyypsuxken2fkk99gr52qhje968n6mwkrfstqaqhq9eq94pnzl4nff437l4lp4ur2cs4f9um8738s35l2esx2tas48thtfhrk5kq94pf9j2tpk54yuermra0xu6hl5ls --pubkey a9e0f110f636f3191644110c19a33448daf09d7cda9708a769e91b7e91340208 pyramid.fiatjaf.com
|
nak admin allowpubkey --sec ncryptsec1qggx54cg270zy9y8krwmfz29jyypsuxken2fkk99gr52qhje968n6mwkrfstqaqhq9eq94pnzl4nff437l4lp4ur2cs4f9um8738s35l2esx2tas48thtfhrk5kq94pf9j2tpk54yuermra0xu6hl5ls --pubkey a9e0f110f636f3191644110c19a33448daf09d7cda9708a769e91b7e91340208 pyramid.fiatjaf.com
|
||||||
type the password to decrypt your secret key: **********
|
type the password to decrypt your secret key: **********
|
||||||
calling 'allowpubkey' on https://pyramid.fiatjaf.com...
|
calling 'allowpubkey' on https://pyramid.fiatjaf.com...
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,186 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"fiatjaf.com/nostr"
|
||||||
|
"fiatjaf.com/nostr/nip86"
|
||||||
|
"github.com/urfave/cli/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
var admin = &cli.Command{
|
||||||
|
Name: "admin",
|
||||||
|
Usage: "manage relays using the relay management API",
|
||||||
|
Description: `examples:
|
||||||
|
nak admin allowpubkey myrelay.com --pubkey 1234... --reason "good user"
|
||||||
|
nak admin banpubkey myrelay.com --pubkey 1234... --reason "spam"
|
||||||
|
nak admin listallowedpubkeys myrelay.com
|
||||||
|
nak admin changerelayname myrelay.com --name "My Relay"`,
|
||||||
|
ArgsUsage: "<relay-url>",
|
||||||
|
DisableSliceFlagSeparator: true,
|
||||||
|
Flags: defaultKeyFlags,
|
||||||
|
Commands: (func() []*cli.Command {
|
||||||
|
methods := []struct {
|
||||||
|
method string
|
||||||
|
args []string
|
||||||
|
}{
|
||||||
|
{"allowpubkey", []string{"pubkey", "reason"}},
|
||||||
|
{"banpubkey", []string{"pubkey", "reason"}},
|
||||||
|
{"listallowedpubkeys", nil},
|
||||||
|
{"listbannedpubkeys", nil},
|
||||||
|
{"listeventsneedingmoderation", nil},
|
||||||
|
{"allowevent", []string{"id", "reason"}},
|
||||||
|
{"banevent", []string{"id", "reason"}},
|
||||||
|
{"listbannedevents", nil},
|
||||||
|
{"changerelayname", []string{"name"}},
|
||||||
|
{"changerelaydescription", []string{"description"}},
|
||||||
|
{"changerelayicon", []string{"icon"}},
|
||||||
|
{"allowkind", []string{"kind"}},
|
||||||
|
{"disallowkind", []string{"kind"}},
|
||||||
|
{"listallowedkinds", nil},
|
||||||
|
{"blockip", []string{"ip", "reason"}},
|
||||||
|
{"unblockip", []string{"ip", "reason"}},
|
||||||
|
{"listblockedips", nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
commands := make([]*cli.Command, 0, len(methods))
|
||||||
|
for _, def := range methods {
|
||||||
|
def := def
|
||||||
|
|
||||||
|
flags := make([]cli.Flag, len(def.args), len(def.args)+4)
|
||||||
|
for i, argName := range def.args {
|
||||||
|
flags[i] = declareFlag(argName)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := &cli.Command{
|
||||||
|
Name: def.method,
|
||||||
|
Usage: fmt.Sprintf(`the "%s" relay management RPC call`, def.method),
|
||||||
|
Description: fmt.Sprintf(
|
||||||
|
`the "%s" management RPC call, see https://nips.nostr.com/86 for more information`, def.method),
|
||||||
|
Flags: flags,
|
||||||
|
DisableSliceFlagSeparator: true,
|
||||||
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
|
params := make([]any, len(def.args))
|
||||||
|
for i, argName := range def.args {
|
||||||
|
params[i] = getArgument(c, argName)
|
||||||
|
}
|
||||||
|
req := nip86.Request{Method: def.method, Params: params}
|
||||||
|
reqj, _ := json.Marshal(req)
|
||||||
|
|
||||||
|
relayUrls := c.Args().Slice()
|
||||||
|
if len(relayUrls) == 0 {
|
||||||
|
stdout(string(reqj))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
kr, _, err := gatherKeyerFromArguments(ctx, c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, relayUrl := range relayUrls {
|
||||||
|
httpUrl := "http" + nostr.NormalizeURL(relayUrl)[2:]
|
||||||
|
log("calling '%s' on %s... ", def.method, httpUrl)
|
||||||
|
body := bytes.NewBuffer(nil)
|
||||||
|
body.Write(reqj)
|
||||||
|
req, err := http.NewRequestWithContext(ctx, "POST", httpUrl, body)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authorization
|
||||||
|
payloadHash := sha256.Sum256(reqj)
|
||||||
|
tokenEvent := nostr.Event{
|
||||||
|
Kind: 27235,
|
||||||
|
CreatedAt: nostr.Now(),
|
||||||
|
Tags: nostr.Tags{
|
||||||
|
{"u", httpUrl},
|
||||||
|
{"method", "POST"},
|
||||||
|
{"payload", hex.EncodeToString(payloadHash[:])},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := kr.SignEvent(ctx, &tokenEvent); err != nil {
|
||||||
|
return fmt.Errorf("failed to sign token event: %w", err)
|
||||||
|
}
|
||||||
|
evtj, _ := json.Marshal(tokenEvent)
|
||||||
|
req.Header.Set("Authorization", "Nostr "+base64.StdEncoding.EncodeToString(evtj))
|
||||||
|
|
||||||
|
// Content-Type
|
||||||
|
req.Header.Set("Content-Type", "application/nostr+json+rpc")
|
||||||
|
|
||||||
|
// make request to relay
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
log("failed: %s\n", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
b, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
log("failed to read response: %s\n", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if resp.StatusCode >= 300 {
|
||||||
|
log("failed with status %d\n", resp.StatusCode)
|
||||||
|
bodyPrintable := string(b)
|
||||||
|
if len(bodyPrintable) > 300 {
|
||||||
|
bodyPrintable = bodyPrintable[0:297] + "..."
|
||||||
|
}
|
||||||
|
log(bodyPrintable)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var response nip86.Response
|
||||||
|
if err := json.Unmarshal(b, &response); err != nil {
|
||||||
|
log("bad json response: %s\n", err)
|
||||||
|
bodyPrintable := string(b)
|
||||||
|
if len(bodyPrintable) > 300 {
|
||||||
|
bodyPrintable = bodyPrintable[0:297] + "..."
|
||||||
|
}
|
||||||
|
log(bodyPrintable)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
resp.Body.Close()
|
||||||
|
|
||||||
|
// print the result
|
||||||
|
log("\n")
|
||||||
|
pretty, _ := json.MarshalIndent(response, "", " ")
|
||||||
|
stdout(string(pretty))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
commands = append(commands, cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
return commands
|
||||||
|
})(),
|
||||||
|
}
|
||||||
|
|
||||||
|
func declareFlag(argName string) cli.Flag {
|
||||||
|
usage := "parameter for this management RPC call, see https://nips.nostr.com/86 for more information."
|
||||||
|
switch argName {
|
||||||
|
case "kind":
|
||||||
|
return &cli.IntFlag{Name: argName, Required: true, Usage: usage}
|
||||||
|
case "reason":
|
||||||
|
return &cli.StringFlag{Name: argName, Usage: usage}
|
||||||
|
default:
|
||||||
|
return &cli.StringFlag{Name: argName, Required: true, Usage: usage}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getArgument(c *cli.Command, argName string) any {
|
||||||
|
switch argName {
|
||||||
|
case "kind":
|
||||||
|
return c.Int(argName)
|
||||||
|
default:
|
||||||
|
return c.String(argName)
|
||||||
|
}
|
||||||
|
}
|
5
go.mod
5
go.mod
|
@ -38,7 +38,7 @@ require (
|
||||||
github.com/coder/websocket v1.8.13 // indirect
|
github.com/coder/websocket v1.8.13 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/decred/dcrd/crypto/blake256 v1.1.0 // indirect
|
github.com/decred/dcrd/crypto/blake256 v1.1.0 // indirect
|
||||||
github.com/dgraph-io/ristretto v1.0.0 // indirect
|
github.com/dgraph-io/ristretto/v2 v2.3.0 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/elliotchance/pie/v2 v2.7.0 // indirect
|
github.com/elliotchance/pie/v2 v2.7.0 // indirect
|
||||||
github.com/elnosh/gonuts v0.4.2 // indirect
|
github.com/elnosh/gonuts v0.4.2 // indirect
|
||||||
|
@ -55,7 +55,6 @@ require (
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect
|
github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect
|
||||||
github.com/rs/cors v1.11.1 // indirect
|
github.com/rs/cors v1.11.1 // indirect
|
||||||
|
@ -72,7 +71,7 @@ require (
|
||||||
golang.org/x/crypto v0.39.0 // indirect
|
golang.org/x/crypto v0.39.0 // indirect
|
||||||
golang.org/x/net v0.41.0 // indirect
|
golang.org/x/net v0.41.0 // indirect
|
||||||
golang.org/x/sync v0.16.0 // indirect
|
golang.org/x/sync v0.16.0 // indirect
|
||||||
golang.org/x/sys v0.34.0 // indirect
|
golang.org/x/sys v0.35.0 // indirect
|
||||||
golang.org/x/text v0.26.0 // indirect
|
golang.org/x/text v0.26.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
rsc.io/qr v0.2.0 // indirect
|
rsc.io/qr v0.2.0 // indirect
|
||||||
|
|
8
go.sum
8
go.sum
|
@ -60,8 +60,8 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeC
|
||||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc=
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc=
|
||||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=
|
||||||
github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218=
|
github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218=
|
||||||
github.com/dgraph-io/ristretto v1.0.0 h1:SYG07bONKMlFDUYu5pEu3DGAh8c2OFNzKm6G9J4Si84=
|
github.com/dgraph-io/ristretto/v2 v2.3.0 h1:qTQ38m7oIyd4GAed/QkUZyPFNMnvVWyazGXRwvOt5zk=
|
||||||
github.com/dgraph-io/ristretto v1.0.0/go.mod h1:jTi2FiYEhQ1NsMmA7DeBykizjOuY88NhKBkepyu1jPc=
|
github.com/dgraph-io/ristretto/v2 v2.3.0/go.mod h1:gpoRV3VzrEY1a9dWAYV6T1U7YzfgttXdd/ZzL1s9OZM=
|
||||||
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38=
|
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38=
|
||||||
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
|
@ -222,8 +222,8 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
|
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
|
||||||
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
|
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
|
1
main.go
1
main.go
|
@ -36,6 +36,7 @@ var app = &cli.Command{
|
||||||
key,
|
key,
|
||||||
verify,
|
verify,
|
||||||
relay,
|
relay,
|
||||||
|
admin,
|
||||||
bunker,
|
bunker,
|
||||||
serve,
|
serve,
|
||||||
blossomCmd,
|
blossomCmd,
|
||||||
|
|
177
relay.go
177
relay.go
|
@ -1,30 +1,19 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"fiatjaf.com/nostr"
|
|
||||||
"fiatjaf.com/nostr/nip11"
|
"fiatjaf.com/nostr/nip11"
|
||||||
"fiatjaf.com/nostr/nip86"
|
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var relay = &cli.Command{
|
var relay = &cli.Command{
|
||||||
Name: "relay",
|
Name: "relay",
|
||||||
Usage: "gets the relay information document for the given relay, as JSON -- or allows usage of the relay management API.",
|
Usage: "gets the relay information document for the given relay, as JSON",
|
||||||
Description: `examples:
|
Description: `
|
||||||
fetching relay information:
|
|
||||||
nak relay nostr.wine
|
nak relay nostr.wine
|
||||||
|
`,
|
||||||
managing a relay
|
|
||||||
nak relay nostr.wine banevent --sec 1234 --id 037eb3751073770ff17483b1b1ff125866cd5147668271975ef0a8a8e7ee184a --reason "I don't like it"`,
|
|
||||||
ArgsUsage: "<relay-url>",
|
ArgsUsage: "<relay-url>",
|
||||||
DisableSliceFlagSeparator: true,
|
DisableSliceFlagSeparator: true,
|
||||||
Action: func(ctx context.Context, c *cli.Command) error {
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
|
@ -44,164 +33,4 @@ var relay = &cli.Command{
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
Commands: (func() []*cli.Command {
|
|
||||||
methods := []struct {
|
|
||||||
method string
|
|
||||||
args []string
|
|
||||||
}{
|
|
||||||
{"allowpubkey", []string{"pubkey", "reason"}},
|
|
||||||
{"banpubkey", []string{"pubkey", "reason"}},
|
|
||||||
{"listallowedpubkeys", nil},
|
|
||||||
{"allowpubkey", []string{"pubkey", "reason"}},
|
|
||||||
{"listallowedpubkeys", nil},
|
|
||||||
{"listeventsneedingmoderation", nil},
|
|
||||||
{"allowevent", []string{"id", "reason"}},
|
|
||||||
{"banevent", []string{"id", "reason"}},
|
|
||||||
{"listbannedevents", nil},
|
|
||||||
{"changerelayname", []string{"name"}},
|
|
||||||
{"changerelaydescription", []string{"description"}},
|
|
||||||
{"changerelayicon", []string{"icon"}},
|
|
||||||
{"allowkind", []string{"kind"}},
|
|
||||||
{"disallowkind", []string{"kind"}},
|
|
||||||
{"listallowedkinds", nil},
|
|
||||||
{"blockip", []string{"ip", "reason"}},
|
|
||||||
{"unblockip", []string{"ip", "reason"}},
|
|
||||||
{"listblockedips", nil},
|
|
||||||
}
|
|
||||||
|
|
||||||
commands := make([]*cli.Command, 0, len(methods))
|
|
||||||
for _, def := range methods {
|
|
||||||
def := def
|
|
||||||
|
|
||||||
flags := make([]cli.Flag, len(def.args), len(def.args)+4)
|
|
||||||
for i, argName := range def.args {
|
|
||||||
flags[i] = declareFlag(argName)
|
|
||||||
}
|
|
||||||
|
|
||||||
flags = append(flags, defaultKeyFlags...)
|
|
||||||
|
|
||||||
cmd := &cli.Command{
|
|
||||||
Name: def.method,
|
|
||||||
Usage: fmt.Sprintf(`the "%s" relay management RPC call`, def.method),
|
|
||||||
Description: fmt.Sprintf(
|
|
||||||
`the "%s" management RPC call, see https://nips.nostr.com/86 for more information`, def.method),
|
|
||||||
Flags: flags,
|
|
||||||
DisableSliceFlagSeparator: true,
|
|
||||||
Action: func(ctx context.Context, c *cli.Command) error {
|
|
||||||
params := make([]any, len(def.args))
|
|
||||||
for i, argName := range def.args {
|
|
||||||
params[i] = getArgument(c, argName)
|
|
||||||
}
|
|
||||||
req := nip86.Request{Method: def.method, Params: params}
|
|
||||||
reqj, _ := json.Marshal(req)
|
|
||||||
|
|
||||||
relayUrls := c.Args().Slice()
|
|
||||||
if len(relayUrls) == 0 {
|
|
||||||
stdout(string(reqj))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
kr, _, err := gatherKeyerFromArguments(ctx, c)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, relayUrl := range relayUrls {
|
|
||||||
httpUrl := "http" + nostr.NormalizeURL(relayUrl)[2:]
|
|
||||||
log("calling '%s' on %s... ", def.method, httpUrl)
|
|
||||||
body := bytes.NewBuffer(nil)
|
|
||||||
body.Write(reqj)
|
|
||||||
req, err := http.NewRequestWithContext(ctx, "POST", httpUrl, body)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create request: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Authorization
|
|
||||||
payloadHash := sha256.Sum256(reqj)
|
|
||||||
tokenEvent := nostr.Event{
|
|
||||||
Kind: 27235,
|
|
||||||
CreatedAt: nostr.Now(),
|
|
||||||
Tags: nostr.Tags{
|
|
||||||
{"u", httpUrl},
|
|
||||||
{"method", "POST"},
|
|
||||||
{"payload", hex.EncodeToString(payloadHash[:])},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
if err := kr.SignEvent(ctx, &tokenEvent); err != nil {
|
|
||||||
return fmt.Errorf("failed to sign token event: %w", err)
|
|
||||||
}
|
|
||||||
evtj, _ := json.Marshal(tokenEvent)
|
|
||||||
req.Header.Set("Authorization", "Nostr "+base64.StdEncoding.EncodeToString(evtj))
|
|
||||||
|
|
||||||
// Content-Type
|
|
||||||
req.Header.Set("Content-Type", "application/nostr+json+rpc")
|
|
||||||
|
|
||||||
// make request to relay
|
|
||||||
resp, err := http.DefaultClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
log("failed: %s\n", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
b, err := io.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
log("failed to read response: %s\n", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if resp.StatusCode >= 300 {
|
|
||||||
log("failed with status %d\n", resp.StatusCode)
|
|
||||||
bodyPrintable := string(b)
|
|
||||||
if len(bodyPrintable) > 300 {
|
|
||||||
bodyPrintable = bodyPrintable[0:297] + "..."
|
|
||||||
}
|
|
||||||
log(bodyPrintable)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var response nip86.Response
|
|
||||||
if err := json.Unmarshal(b, &response); err != nil {
|
|
||||||
log("bad json response: %s\n", err)
|
|
||||||
bodyPrintable := string(b)
|
|
||||||
if len(bodyPrintable) > 300 {
|
|
||||||
bodyPrintable = bodyPrintable[0:297] + "..."
|
|
||||||
}
|
|
||||||
log(bodyPrintable)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
resp.Body.Close()
|
|
||||||
|
|
||||||
// print the result
|
|
||||||
log("\n")
|
|
||||||
pretty, _ := json.MarshalIndent(response, "", " ")
|
|
||||||
stdout(string(pretty))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
commands = append(commands, cmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
return commands
|
|
||||||
})(),
|
|
||||||
}
|
|
||||||
|
|
||||||
func declareFlag(argName string) cli.Flag {
|
|
||||||
usage := "parameter for this management RPC call, see https://nips.nostr.com/86 for more information."
|
|
||||||
switch argName {
|
|
||||||
case "kind":
|
|
||||||
return &cli.IntFlag{Name: argName, Required: true, Usage: usage}
|
|
||||||
case "reason":
|
|
||||||
return &cli.StringFlag{Name: argName, Usage: usage}
|
|
||||||
default:
|
|
||||||
return &cli.StringFlag{Name: argName, Required: true, Usage: usage}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getArgument(c *cli.Command, argName string) any {
|
|
||||||
switch argName {
|
|
||||||
case "kind":
|
|
||||||
return c.Int(argName)
|
|
||||||
default:
|
|
||||||
return c.String(argName)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue