mirror of
https://github.com/fiatjaf/nak.git
synced 2025-12-08 16:48:51 +00:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f9cf01b48b | ||
|
|
fb7c49bb5c | ||
|
|
4ad0769a62 | ||
|
|
3ace11d7b2 | ||
|
|
194e94ec9a | ||
|
|
2b2018b742 | ||
|
|
30c8eb83b2 | ||
|
|
015cfd857c | ||
|
|
4e5f7e6d21 | ||
|
|
fb9faf24ae | ||
|
|
76ca99a73b | ||
|
|
7890466783 | ||
|
|
ba2d86ca33 | ||
|
|
dff57c207e | ||
|
|
c3777abd81 | ||
|
|
746a13861d | ||
|
|
bd7b22c4ff | ||
|
|
01b30b49de | ||
|
|
9d4f1ec852 | ||
|
|
88acf8ccda | ||
|
|
bbe4bfdaa0 |
46
README.md
46
README.md
@@ -19,6 +19,13 @@ It pairs nicely with https://github.com/blakejakopovic/nostcat using unix pipes.
|
||||
|
||||
~> nak event -c hello --sec e8314053e76f5fff73481f6a1a1affce3f34645f0c10df0a8d33fd80f0f03255
|
||||
{"id":"ed840ef37a40cce4f4b8c361e5df13457ad664209cf4a297fd7df7e84fdd32e0","pubkey":"5b36b874b2b983197ba4be80553b2e4b6db2895a04567cea0aa47585b2e0c620","created_at":1683201092,"kind":1,"tags":[],"content":"hello","sig":"304a87dbbdf986a187eb9417316cfe3d6f8f31793ba20c9c6d7e4ebeeefe950d6ecba6098c201b7170c04e27c2f920d607a90f5c8763c35ac806dce37df1d05d"}
|
||||
~> nak event -c hello --sec e8314053e76f5fff73481f6a1a1affce3f34645f0c10df0a8d33fd80f0f03255 wss://relay.stoner.com wss://nos.lol wss://nostr.wine wss://atlas.nostr.land wss://relay.damus.io
|
||||
{"id":"54a534647bdcd2751d743fea4fc9eee5dba613887d69425f0891d9c2f82772a5","pubkey":"5b36b874b2b983197ba4be80553b2e4b6db2895a04567cea0aa47585b2e0c620","created_at":1684895417,"kind":1,"tags":[],"content":"hello","sig":"81a14cfe628fab6cd6135bb66f6e8b3bb4bfce4f666462a1303fdfbc9038fd141e73db3fe7e774a8f023fc70622c50a67d4fa41d3d09806c78f051985c11e0bd"}
|
||||
publishing to wss://relay.stoner.com... failed: msg: blocked: pubkey is not allowed to publish to this relay
|
||||
publishing to wss://nos.lol... success.
|
||||
publishing to wss://nostr.wine... failed: msg: blocked: not an active paid member
|
||||
publishing to wss://atlas.nostr.land... failed: msg: blocked: pubkey not admitted
|
||||
publishing to wss://relay.damus.io... success.
|
||||
|
||||
~> nak decode nevent1qqs29yet5tp0qq5xu5qgkeehkzqh5qu46739axzezcxpj4tjlkx9j7gpr4mhxue69uhkummnw3ez6ur4vgh8wetvd3hhyer9wghxuet5sh59ud
|
||||
{
|
||||
@@ -30,6 +37,9 @@ It pairs nicely with https://github.com/blakejakopovic/nostcat using unix pipes.
|
||||
|
||||
~> nak req -a a2932ba2c2f00286e5008b6737b0817a0395d7a25e9859160c195572fd8c5979 -k 1 -a e8b487c079b0f67c695ae6c4c2552a47f38adfa2533cc5926bd2c102942fdcb7
|
||||
["REQ","nak",{"kinds":[1],"authors":["a2932ba2c2f00286e5008b6737b0817a0395d7a25e9859160c195572fd8c5979","e8b487c079b0f67c695ae6c4c2552a47f38adfa2533cc5926bd2c102942fdcb7"]}]
|
||||
|
||||
~> nak req -k 1 -l 1 --stream wss://relay.stoner.com
|
||||
{"id":"1d73832917bf5a72276c53e9246c28b97225b51cd5735843434f7756fc0ddead","pubkey":"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d","created_at":1684894689,"kind":1,"tags":[["p","bcbeb5a2e6b547f6d0c3d8c16145f7bb94f3639ec7ecbcfe50045dbb2eede70b","wss://nos.lol","artk42"],["e","b5af6815c8d89a7d5b6201b9e624fbd5389fca3337ba2dc05c6187234a7c1bd5","wss://nos.lol","root"],["e","5795e27aff0a459a30c64a61a32c43d968cd19c8f1926cf01fc02e9da7c56f2b","wss://nos.lol","reply"],["client","coracle"]],"content":"Because that makes no sense.","sig":"3ee5b2b26ec6b116ef1a6b1c10bc7e56674a3c36841814f68b57f63259f3d78e23629d4599afe67e72c220e27b4b0966cc51adc1da808c8c6111dedb531ac0c3"}
|
||||
```
|
||||
|
||||
### documentation
|
||||
@@ -44,8 +54,9 @@ USAGE:
|
||||
|
||||
COMMANDS:
|
||||
req generates encoded REQ messages and optionally use them to talk to relays
|
||||
event generates an encoded event
|
||||
event generates an encoded event and either prints it or sends it to a set of relays
|
||||
decode decodes nip19, nip21, nip05 or hex entities
|
||||
encode encodes notes and other stuff to nip19 entities
|
||||
help, h Shows a list of commands or help for one command
|
||||
|
||||
GLOBAL OPTIONS:
|
||||
@@ -53,7 +64,7 @@ GLOBAL OPTIONS:
|
||||
|
||||
~> nak event --help
|
||||
NAME:
|
||||
nak event - generates an encoded event
|
||||
nak event - generates an encoded event and either prints it or sends it to a set of relays
|
||||
|
||||
USAGE:
|
||||
nak event [command options] [arguments...]
|
||||
@@ -61,6 +72,8 @@ USAGE:
|
||||
DESCRIPTION:
|
||||
example usage (for sending directly to a relay with 'nostcat'):
|
||||
nak event -k 1 -c hello --envelope | nostcat wss://nos.lol
|
||||
standalone:
|
||||
nak event -k 1 -c hello wss://nos.lol`,
|
||||
|
||||
OPTIONS:
|
||||
--envelope print the event enveloped in a ["EVENT", ...] message ready to be sent to a relay (default: false)
|
||||
@@ -87,7 +100,6 @@ DESCRIPTION:
|
||||
|
||||
example usage (with 'nostcat'):
|
||||
nak req -k 1 -a 3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d | nostcat wss://nos.lol
|
||||
|
||||
standalone:
|
||||
nak req -k 1 wss://nos.lol
|
||||
|
||||
@@ -143,6 +155,34 @@ OPTIONS:
|
||||
--id, -e return just the event id, if applicable (default: false)
|
||||
--pubkey, -p return just the pubkey, if applicable (default: false)
|
||||
--help, -h show help
|
||||
|
||||
|
||||
~> nak encode --help
|
||||
NAME:
|
||||
nak encode - encodes notes and other stuff to nip19 entities
|
||||
|
||||
USAGE:
|
||||
nak encode command [command options] [arguments...]
|
||||
|
||||
DESCRIPTION:
|
||||
example usage:
|
||||
nak encode npub <pubkey-hex>
|
||||
nak encode nprofile <pubkey-hex>
|
||||
nak encode nprofile --relay <relay-url> <pubkey-hex>
|
||||
nak encode nevent <event-id>
|
||||
nak encode nevent --author <pubkey-hex> --relay <relay-url> --relay <other-relay> <event-id>
|
||||
nak encode nsec <privkey-hex>
|
||||
|
||||
COMMANDS:
|
||||
npub encode a hex private key into bech32 'npub' format
|
||||
nsec encode a hex private key into bech32 'nsec' format
|
||||
nprofile generate profile codes with attached relay information
|
||||
nevent generate event codes with optionally attached relay information
|
||||
naddr generate codes for NIP-33 parameterized replaceable events
|
||||
help, h Shows a list of commands or help for one command
|
||||
|
||||
OPTIONS:
|
||||
--help, -h show help
|
||||
```
|
||||
|
||||
written in go using [go-nostr](https://github.com/nbd-wtf/go-nostr), heavily inspired by [nostril](http://git.jb55.com/nostril/).
|
||||
|
||||
7
edit.svg
Normal file
7
edit.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" ?>
|
||||
<svg width="800px" height="800px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<g>
|
||||
<path d="M20,16v4a2,2,0,0,1-2,2H4a2,2,0,0,1-2-2V6A2,2,0,0,1,4,4H8" fill="none" stroke="#f9cc9d" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
|
||||
<polygon fill="none" points="12.5 15.8 22 6.2 17.8 2 8.3 11.5 8 16 12.5 15.8" stroke="#f9cc9d" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 463 B |
224
encode.go
Normal file
224
encode.go
Normal file
@@ -0,0 +1,224 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr/nip19"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var encode = &cli.Command{
|
||||
Name: "encode",
|
||||
Usage: "encodes notes and other stuff to nip19 entities",
|
||||
Description: `example usage:
|
||||
nak encode npub <pubkey-hex>
|
||||
nak encode nprofile <pubkey-hex>
|
||||
nak encode nprofile --relay <relay-url> <pubkey-hex>
|
||||
nak encode nevent <event-id>
|
||||
nak encode nevent --author <pubkey-hex> --relay <relay-url> --relay <other-relay> <event-id>
|
||||
nak encode nsec <privkey-hex>`,
|
||||
Before: func(c *cli.Context) error {
|
||||
if c.Args().Len() < 2 {
|
||||
return fmt.Errorf("expected more than 2 arguments.")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Subcommands: []*cli.Command{
|
||||
{
|
||||
Name: "npub",
|
||||
Usage: "encode a hex private key into bech32 'npub' format",
|
||||
Action: func(c *cli.Context) error {
|
||||
target := c.Args().First()
|
||||
if err := validate32BytesHex(target); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if npub, err := nip19.EncodePublicKey(target); err == nil {
|
||||
fmt.Println(npub)
|
||||
return nil
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "nsec",
|
||||
Usage: "encode a hex private key into bech32 'nsec' format",
|
||||
Action: func(c *cli.Context) error {
|
||||
target := c.Args().First()
|
||||
if err := validate32BytesHex(target); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if npub, err := nip19.EncodePrivateKey(target); err == nil {
|
||||
fmt.Println(npub)
|
||||
return nil
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "nprofile",
|
||||
Usage: "generate profile codes with attached relay information",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringSliceFlag{
|
||||
Name: "relay",
|
||||
Aliases: []string{"r"},
|
||||
Usage: "attach relay hints to nprofile code",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
target := c.Args().First()
|
||||
if err := validate32BytesHex(target); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
relays := c.StringSlice("relay")
|
||||
if err := validateRelayURLs(relays); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if npub, err := nip19.EncodeProfile(target, relays); err == nil {
|
||||
fmt.Println(npub)
|
||||
return nil
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "nevent",
|
||||
Usage: "generate event codes with optionally attached relay information",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringSliceFlag{
|
||||
Name: "relay",
|
||||
Aliases: []string{"r"},
|
||||
Usage: "attach relay hints to nevent code",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "author",
|
||||
Usage: "attach an author pubkey as a hint to the nevent code",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
target := c.Args().First()
|
||||
if err := validate32BytesHex(target); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
author := c.String("author")
|
||||
if author != "" {
|
||||
if err := validate32BytesHex(author); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
relays := c.StringSlice("relay")
|
||||
if err := validateRelayURLs(relays); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if npub, err := nip19.EncodeEvent(target, relays, author); err == nil {
|
||||
fmt.Println(npub)
|
||||
return nil
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "naddr",
|
||||
Usage: "generate codes for NIP-33 parameterized replaceable events",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "identifier",
|
||||
Aliases: []string{"d"},
|
||||
Usage: "the \"d\" tag identifier of this replaceable event",
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "pubkey",
|
||||
Usage: "pubkey of the naddr author",
|
||||
Aliases: []string{"p"},
|
||||
Required: true,
|
||||
},
|
||||
&cli.Int64Flag{
|
||||
Name: "kind",
|
||||
Aliases: []string{"k"},
|
||||
Usage: "kind of referred replaceable event",
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "relay",
|
||||
Aliases: []string{"r"},
|
||||
Usage: "attach relay hints to naddr code",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
pubkey := c.String("pubkey")
|
||||
if err := validate32BytesHex(pubkey); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
kind := c.Int("kind")
|
||||
if kind < 30000 || kind >= 40000 {
|
||||
return fmt.Errorf("kind must be between 30000 and 39999, as per NIP-16, got %d", kind)
|
||||
}
|
||||
|
||||
d := c.String("identifier")
|
||||
if d == "" {
|
||||
return fmt.Errorf("\"d\" tag identifier can't be empty")
|
||||
}
|
||||
|
||||
relays := c.StringSlice("relay")
|
||||
if err := validateRelayURLs(relays); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if npub, err := nip19.EncodeEntity(pubkey, kind, d, relays); err == nil {
|
||||
fmt.Println(npub)
|
||||
return nil
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func validate32BytesHex(target string) error {
|
||||
if _, err := hex.DecodeString(target); err != nil {
|
||||
return fmt.Errorf("target '%s' is not valid hex: %s", target, err)
|
||||
}
|
||||
if len(target) != 64 {
|
||||
return fmt.Errorf("expected '%s' to be 64 characters (32 bytes), got %d", target, len(target))
|
||||
}
|
||||
if strings.ToLower(target) != target {
|
||||
return fmt.Errorf("expected target to be all lowercase hex. try again with '%s'", strings.ToLower(target))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateRelayURLs(wsurls []string) error {
|
||||
for _, wsurl := range wsurls {
|
||||
u, err := url.Parse(wsurl)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid relay url '%s': %s", wsurl, err)
|
||||
}
|
||||
|
||||
if u.Scheme != "ws" && u.Scheme != "wss" {
|
||||
return fmt.Errorf("relay url must use wss:// or ws:// schemes, got '%s'", wsurl)
|
||||
}
|
||||
|
||||
if u.Host == "" {
|
||||
return fmt.Errorf("relay url '%s' is missing the hostname", wsurl)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
53
event.go
53
event.go
@@ -1,13 +1,17 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mailru/easyjson"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nson"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
@@ -15,9 +19,11 @@ const CATEGORY_EVENT_FIELDS = "EVENT FIELDS"
|
||||
|
||||
var event = &cli.Command{
|
||||
Name: "event",
|
||||
Usage: "generates an encoded event",
|
||||
Usage: "generates an encoded event and either prints it or sends it to a set of relays",
|
||||
Description: `example usage (for sending directly to a relay with 'nostcat'):
|
||||
nak event -k 1 -c hello --envelope | nostcat wss://nos.lol`,
|
||||
nak event -k 1 -c hello --envelope | nostcat wss://nos.lol
|
||||
standalone:
|
||||
nak event -k 1 -c hello wss://nos.lol`,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "sec",
|
||||
@@ -29,6 +35,10 @@ var event = &cli.Command{
|
||||
Name: "envelope",
|
||||
Usage: "print the event enveloped in a [\"EVENT\", ...] message ready to be sent to a relay",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "nson",
|
||||
Usage: "encode the event using NSON",
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "kind",
|
||||
Aliases: []string{"k"},
|
||||
@@ -70,6 +80,7 @@ var event = &cli.Command{
|
||||
Category: CATEGORY_EVENT_FIELDS,
|
||||
},
|
||||
},
|
||||
ArgsUsage: "[relay...]",
|
||||
Action: func(c *cli.Context) error {
|
||||
evt := nostr.Event{
|
||||
Kind: c.Int("kind"),
|
||||
@@ -77,11 +88,17 @@ var event = &cli.Command{
|
||||
Tags: make(nostr.Tags, 0, 3),
|
||||
}
|
||||
|
||||
tags := make([][]string, 0, 5)
|
||||
tags := make(nostr.Tags, 0, 5)
|
||||
for _, tagFlag := range c.StringSlice("tag") {
|
||||
// tags are in the format key=value
|
||||
spl := strings.Split(tagFlag, "=")
|
||||
if len(spl) == 2 && len(spl[0]) == 1 {
|
||||
tags = append(tags, spl)
|
||||
if len(spl) == 2 && len(spl[0]) > 0 {
|
||||
tag := nostr.Tag{spl[0]}
|
||||
// tags may also contain extra elements separated with a ";"
|
||||
spl2 := strings.Split(spl[1], ";")
|
||||
tag = append(tag, spl2...)
|
||||
// ~
|
||||
tags = append(tags, tag)
|
||||
}
|
||||
}
|
||||
for _, etag := range c.StringSlice("e") {
|
||||
@@ -111,15 +128,37 @@ var event = &cli.Command{
|
||||
return fmt.Errorf("error signing with provided key: %w", err)
|
||||
}
|
||||
|
||||
relays := c.Args().Slice()
|
||||
if len(relays) > 0 {
|
||||
fmt.Println(evt.String())
|
||||
for _, url := range relays {
|
||||
fmt.Fprintf(os.Stderr, "publishing to %s... ", url)
|
||||
if relay, err := nostr.RelayConnect(c.Context, url); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to connect: %s\n", err)
|
||||
} else {
|
||||
ctx, cancel := context.WithTimeout(c.Context, 10*time.Second)
|
||||
defer cancel()
|
||||
if status, err := relay.Publish(ctx, evt); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed: %s\n", err)
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "%s.\n", status)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var result string
|
||||
if c.Bool("envelope") {
|
||||
j, _ := json.Marshal([]any{"EVENT", evt})
|
||||
result = string(j)
|
||||
} else if c.Bool("nson") {
|
||||
result, _ = nson.Marshal(&evt)
|
||||
} else {
|
||||
result = evt.String()
|
||||
j, _ := easyjson.Marshal(&evt)
|
||||
result = string(j)
|
||||
}
|
||||
fmt.Println(result)
|
||||
}
|
||||
|
||||
fmt.Println(result)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
2
ext.svg
2
ext.svg
@@ -1 +1 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M17 13.5v6H5v-12h6m3-3h6v6m0-6-9 9" class="icon_svg-stroke" stroke="#666" stroke-width="1.5" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"></path></svg>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M17 13.5v6H5v-12h6m3-3h6v6m0-6-9 9" class="icon_svg-stroke" stroke="#3b82f6" stroke-width="1.5" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"></path></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 278 B After Width: | Height: | Size: 281 B |
6
go.mod
6
go.mod
@@ -3,12 +3,12 @@ module github.com/fiatjaf/nak
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/nbd-wtf/go-nostr v0.18.3
|
||||
github.com/mailru/easyjson v0.7.7
|
||||
github.com/nbd-wtf/go-nostr v0.19.2
|
||||
github.com/urfave/cli/v2 v2.25.3
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/SaveTheRbtz/generic-sync-map-go v0.0.0-20220414055132-a37292614db8 // indirect
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect
|
||||
github.com/btcsuite/btcd/btcutil v1.1.3 // indirect
|
||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 // indirect
|
||||
@@ -19,7 +19,7 @@ require (
|
||||
github.com/gobwas/pool v0.2.1 // indirect
|
||||
github.com/gobwas/ws v1.2.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/puzpuzpuz/xsync v1.5.2 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/tidwall/gjson v1.14.4 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
|
||||
8
go.sum
8
go.sum
@@ -1,5 +1,3 @@
|
||||
github.com/SaveTheRbtz/generic-sync-map-go v0.0.0-20220414055132-a37292614db8 h1:Xa6tp8DPDhdV+k23uiTC/GrAYOe4IdyJVKtob4KW3GA=
|
||||
github.com/SaveTheRbtz/generic-sync-map-go v0.0.0-20220414055132-a37292614db8/go.mod h1:ihkm1viTbO/LOsgdGoFPBSvzqvx7ibvkMzYp3CgtHik=
|
||||
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
|
||||
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
|
||||
github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M=
|
||||
@@ -63,8 +61,8 @@ github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlT
|
||||
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/nbd-wtf/go-nostr v0.18.3 h1:ofMYxlFAptyoErlOGOCUk7zGHQNJ8/ZkIXXOsveFZ+c=
|
||||
github.com/nbd-wtf/go-nostr v0.18.3/go.mod h1:GPJOOK8US38kz+bfb9nWe873Xu0e6bXlThejOs1LTkc=
|
||||
github.com/nbd-wtf/go-nostr v0.19.2 h1:Oofhe5+EKvf74fZQmYyX5G4RS74/na1aNabsB/cW9b4=
|
||||
github.com/nbd-wtf/go-nostr v0.19.2/go.mod h1:F9y6+M8askJCjilLgMC3rD0moA6UtG1MCnyClNYXeys=
|
||||
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=
|
||||
@@ -75,6 +73,8 @@ github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/puzpuzpuz/xsync v1.5.2 h1:yRAP4wqSOZG+/4pxJ08fPTwrfL0IzE/LKQ/cw509qGY=
|
||||
github.com/puzpuzpuz/xsync v1.5.2/go.mod h1:K98BYhX3k1dQ2M63t1YNVDanbwUPmBCAhNmVrrxfiGg=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
|
||||
1
req.go
1
req.go
@@ -18,7 +18,6 @@ var req = &cli.Command{
|
||||
|
||||
example usage (with 'nostcat'):
|
||||
nak req -k 1 -a 3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d | nostcat wss://nos.lol
|
||||
|
||||
standalone:
|
||||
nak req -k 1 wss://nos.lol`,
|
||||
Flags: []cli.Flag{
|
||||
|
||||
@@ -16,7 +16,10 @@ import snow.*
|
||||
import Utils.*
|
||||
|
||||
object Components {
|
||||
def render32Bytes(bytes32: ByteVector32): Resource[IO, HtmlDivElement[IO]] =
|
||||
def render32Bytes(
|
||||
store: Store,
|
||||
bytes32: ByteVector32
|
||||
): Resource[IO, HtmlDivElement[IO]] =
|
||||
div(
|
||||
cls := "text-md",
|
||||
entry("canonical hex", bytes32.toHex),
|
||||
@@ -25,9 +28,16 @@ object Components {
|
||||
cls := "mt-2 pl-2 mb-2",
|
||||
entry(
|
||||
"npub",
|
||||
NIP19.encode(XOnlyPublicKey(bytes32)),
|
||||
Some(
|
||||
selectable(
|
||||
store,
|
||||
NIP19.encode(XOnlyPublicKey(bytes32))
|
||||
)
|
||||
)
|
||||
),
|
||||
nip19_21(
|
||||
store,
|
||||
"nprofile",
|
||||
NIP19.encode(ProfilePointer(XOnlyPublicKey(bytes32)))
|
||||
)
|
||||
@@ -37,21 +47,35 @@ object Components {
|
||||
cls := "pl-2 mb-2",
|
||||
entry(
|
||||
"nsec",
|
||||
NIP19.encode(PrivateKey(bytes32)),
|
||||
Some(
|
||||
selectable(
|
||||
store,
|
||||
NIP19.encode(PrivateKey(bytes32))
|
||||
)
|
||||
)
|
||||
),
|
||||
entry(
|
||||
"npub",
|
||||
NIP19.encode(XOnlyPublicKey(bytes32))
|
||||
NIP19.encode(PrivateKey(bytes32).publicKey.xonly),
|
||||
Some(
|
||||
selectable(
|
||||
store,
|
||||
NIP19.encode(PrivateKey(bytes32).publicKey.xonly)
|
||||
)
|
||||
)
|
||||
),
|
||||
nip19_21(
|
||||
store,
|
||||
"nprofile",
|
||||
NIP19.encode(ProfilePointer(XOnlyPublicKey(bytes32)))
|
||||
NIP19.encode(ProfilePointer(PrivateKey(bytes32).publicKey.xonly))
|
||||
)
|
||||
),
|
||||
"if this is an event id:",
|
||||
div(
|
||||
cls := "pl-2 mb-2",
|
||||
nip19_21(
|
||||
store,
|
||||
"nevent",
|
||||
NIP19.encode(EventPointer(bytes32.toHex))
|
||||
)
|
||||
@@ -60,10 +84,16 @@ object Components {
|
||||
cls := "pl-2 mb-2",
|
||||
entry(
|
||||
"note",
|
||||
NIP19.encode(bytes32),
|
||||
Some(
|
||||
selectable(
|
||||
store,
|
||||
NIP19.encode(bytes32)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
def renderEventPointer(
|
||||
store: Store,
|
||||
@@ -71,13 +101,21 @@ object Components {
|
||||
): Resource[IO, HtmlDivElement[IO]] =
|
||||
div(
|
||||
cls := "text-md",
|
||||
entry("event id (hex)", evp.id),
|
||||
entry(
|
||||
"event id (hex)",
|
||||
evp.id,
|
||||
Some(selectable(store, evp.id))
|
||||
),
|
||||
relayHints(store, evp.relays),
|
||||
evp.author.map { pk =>
|
||||
entry("author hint (pubkey hex)", pk.value.toHex)
|
||||
},
|
||||
nip19_21("nevent", NIP19.encode(evp)),
|
||||
entry("note", NIP19.encode(ByteVector32.fromValidHex(evp.id)))
|
||||
nip19_21(store, "nevent", NIP19.encode(evp)),
|
||||
entry(
|
||||
"note",
|
||||
NIP19.encode(ByteVector32.fromValidHex(evp.id)),
|
||||
Some(selectable(store, NIP19.encode(ByteVector32.fromValidHex(evp.id))))
|
||||
)
|
||||
)
|
||||
|
||||
def renderProfilePointer(
|
||||
@@ -87,30 +125,55 @@ object Components {
|
||||
): Resource[IO, HtmlDivElement[IO]] =
|
||||
div(
|
||||
cls := "text-md",
|
||||
sk.map { k => entry("private key (hex)", k.value.toHex) },
|
||||
sk.map { k => entry("nsec", NIP19.encode(k)) },
|
||||
entry("public key (hex)", pp.pubkey.value.toHex),
|
||||
sk.map { k =>
|
||||
entry(
|
||||
"private key (hex)",
|
||||
k.value.toHex,
|
||||
Some(selectable(store, k.value.toHex))
|
||||
)
|
||||
},
|
||||
sk.map { k =>
|
||||
entry(
|
||||
"nsec",
|
||||
NIP19.encode(k),
|
||||
Some(selectable(store, NIP19.encode(k)))
|
||||
)
|
||||
},
|
||||
entry(
|
||||
"public key (hex)",
|
||||
pp.pubkey.value.toHex,
|
||||
Some(selectable(store, pp.pubkey.value.toHex))
|
||||
),
|
||||
relayHints(
|
||||
store,
|
||||
pp.relays,
|
||||
dynamic = if sk.isDefined then false else true
|
||||
),
|
||||
entry("npub", NIP19.encode(pp.pubkey)),
|
||||
nip19_21("nprofile", NIP19.encode(pp))
|
||||
entry(
|
||||
"npub",
|
||||
NIP19.encode(pp.pubkey),
|
||||
Some(selectable(store, NIP19.encode(pp.pubkey)))
|
||||
),
|
||||
nip19_21(store, "nprofile", NIP19.encode(pp))
|
||||
)
|
||||
|
||||
def renderAddressPointer(
|
||||
store: Store,
|
||||
addr: snow.AddressPointer
|
||||
): Resource[IO, HtmlDivElement[IO]] =
|
||||
): Resource[IO, HtmlDivElement[IO]] = {
|
||||
val nip33atag =
|
||||
s"${addr.kind}:${addr.author.value.toHex}:${addr.d}"
|
||||
|
||||
div(
|
||||
cls := "text-md",
|
||||
entry("author (pubkey hex)", addr.author.value.toHex),
|
||||
entry("identifier", addr.d),
|
||||
entry("identifier (d tag)", addr.d),
|
||||
entry("kind", addr.kind.toString),
|
||||
relayHints(store, addr.relays),
|
||||
nip19_21("naddr", NIP19.encode(addr))
|
||||
nip19_21(store, "naddr", NIP19.encode(addr)),
|
||||
entry("nip33 'a' tag", nip33atag, Some(selectable(store, nip33atag)))
|
||||
)
|
||||
}
|
||||
|
||||
def renderEvent(
|
||||
store: Store,
|
||||
@@ -205,35 +268,60 @@ object Components {
|
||||
),
|
||||
event.id.map(id =>
|
||||
nip19_21(
|
||||
store,
|
||||
"nevent",
|
||||
NIP19.encode(EventPointer(id, author = event.pubkey))
|
||||
)
|
||||
),
|
||||
if event.kind >= 30000 && event.kind < 40000 then
|
||||
event.pubkey
|
||||
.map(author =>
|
||||
nip19_21(
|
||||
store,
|
||||
"naddr",
|
||||
NIP19.encode(
|
||||
AddressPointer(
|
||||
d = event.tags
|
||||
.collectFirst { case "d" :: v :: _ => v }
|
||||
.getOrElse(""),
|
||||
kind = event.kind,
|
||||
author = author,
|
||||
relays = List.empty
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
else
|
||||
event.id.map(id =>
|
||||
entry(
|
||||
"note",
|
||||
NIP19.encode(ByteVector32.fromValidHex(id))
|
||||
NIP19.encode(ByteVector32.fromValidHex(id)),
|
||||
Some(selectable(store, NIP19.encode(ByteVector32.fromValidHex(id))))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
private def entry(
|
||||
key: String,
|
||||
value: String
|
||||
value: String,
|
||||
selectLink: Option[Resource[IO, HtmlSpanElement[IO]]] = None
|
||||
): Resource[IO, HtmlDivElement[IO]] =
|
||||
div(
|
||||
cls := "flex items-center space-x-3",
|
||||
span(cls := "font-bold", key + " "),
|
||||
span(Styles.mono, cls := "max-w-xl", value)
|
||||
span(Styles.mono, cls := "max-w-xl break-all", value),
|
||||
selectLink
|
||||
)
|
||||
|
||||
private def nip19_21(
|
||||
store: Store,
|
||||
key: String,
|
||||
code: String
|
||||
): Resource[IO, HtmlDivElement[IO]] =
|
||||
div(
|
||||
span(cls := "font-bold", key + " "),
|
||||
span(Styles.mono, cls := "break-all", code),
|
||||
selectable(store, code),
|
||||
a(
|
||||
href := "nostr:" + code,
|
||||
external
|
||||
@@ -254,13 +342,65 @@ object Components {
|
||||
div(
|
||||
cls := "flex items-center space-x-3",
|
||||
span(cls := "font-bold", "relay hints "),
|
||||
span(Styles.mono, cls := "max-w-xl", value),
|
||||
if relays.size == 0 then div("")
|
||||
else
|
||||
// displaying each relay hint
|
||||
div(
|
||||
cls := "flex flex-wrap max-w-xl",
|
||||
relays
|
||||
.map(url =>
|
||||
div(
|
||||
Styles.mono,
|
||||
cls := "flex items-center rounded py-0.5 px-1 mr-1 mb-1 bg-orange-100",
|
||||
url,
|
||||
// removing a relay hint by clicking on the x
|
||||
div(
|
||||
cls := "cursor-pointer ml-1 text-rose-600 hover:text-rose-300",
|
||||
onClick --> (_.foreach(_ => {
|
||||
store.result.get.flatMap(result =>
|
||||
store.input.set(
|
||||
result
|
||||
.map {
|
||||
case a: AddressPointer =>
|
||||
NIP19
|
||||
.encode(
|
||||
a.copy(relays =
|
||||
relays.filterNot(_ == url)
|
||||
)
|
||||
)
|
||||
case p: ProfilePointer =>
|
||||
NIP19
|
||||
.encode(
|
||||
p.copy(relays =
|
||||
relays.filterNot(_ == url)
|
||||
)
|
||||
)
|
||||
case e: EventPointer =>
|
||||
NIP19
|
||||
.encode(
|
||||
e.copy(relays =
|
||||
relays.filterNot(_ == url)
|
||||
)
|
||||
)
|
||||
case r => ""
|
||||
}
|
||||
.getOrElse("")
|
||||
)
|
||||
)
|
||||
})),
|
||||
"×"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
,
|
||||
active.map {
|
||||
case true =>
|
||||
div(
|
||||
input.withSelf { self =>
|
||||
(
|
||||
onKeyPress --> (_.foreach(evt =>
|
||||
// confirm adding a relay hint
|
||||
evt.key match {
|
||||
case "Enter" =>
|
||||
self.value.get.flatMap(url =>
|
||||
@@ -274,17 +414,17 @@ object Components {
|
||||
case a: AddressPointer =>
|
||||
NIP19
|
||||
.encode(
|
||||
a.copy(relays = url :: a.relays)
|
||||
a.copy(relays = a.relays :+ url)
|
||||
)
|
||||
case p: ProfilePointer =>
|
||||
NIP19
|
||||
.encode(
|
||||
p.copy(relays = url :: p.relays)
|
||||
p.copy(relays = p.relays :+ url)
|
||||
)
|
||||
case e: EventPointer =>
|
||||
NIP19
|
||||
.encode(
|
||||
e.copy(relays = url :: e.relays)
|
||||
e.copy(relays = e.relays :+ url)
|
||||
)
|
||||
case r => ""
|
||||
}
|
||||
@@ -301,6 +441,7 @@ object Components {
|
||||
}
|
||||
)
|
||||
case false if dynamic =>
|
||||
// button to add a new relay hint
|
||||
button(
|
||||
Styles.buttonSmall,
|
||||
"add relay hint",
|
||||
@@ -311,5 +452,25 @@ object Components {
|
||||
)
|
||||
}
|
||||
|
||||
private def selectable(
|
||||
store: Store,
|
||||
code: String
|
||||
): Resource[IO, HtmlSpanElement[IO]] =
|
||||
span(
|
||||
store.input.map(current =>
|
||||
if current == code then a("")
|
||||
else
|
||||
a(
|
||||
href := "#/" + code,
|
||||
onClick --> (_.foreach(evt =>
|
||||
evt.preventDefault >>
|
||||
store.input.set(code)
|
||||
)),
|
||||
edit
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
private val edit = img(cls := "inline w-4 ml-2", src := "edit.svg")
|
||||
private val external = img(cls := "inline w-4 ml-2", src := "ext.svg")
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ object Main extends IOWebApp {
|
||||
def render: Resource[IO, HtmlDivElement[IO]] = Store(window).flatMap {
|
||||
store =>
|
||||
div(
|
||||
cls := "flex w-full h-full flex-col items-center justify-center",
|
||||
cls := "flex w-full flex-col items-center justify-center",
|
||||
div(
|
||||
cls := "w-4/5",
|
||||
h1(
|
||||
@@ -131,7 +131,7 @@ object Main extends IOWebApp {
|
||||
cls := "w-full flex my-5",
|
||||
store.result.map {
|
||||
case Left(msg) => div(msg)
|
||||
case Right(bytes: ByteVector32) => render32Bytes(bytes)
|
||||
case Right(bytes: ByteVector32) => render32Bytes(store, bytes)
|
||||
case Right(event: Event) => renderEvent(store, event)
|
||||
case Right(pp: ProfilePointer) => renderProfilePointer(store, pp)
|
||||
case Right(evp: EventPointer) => renderEventPointer(store, evp)
|
||||
|
||||
@@ -26,7 +26,23 @@ object Parser {
|
||||
case Right(evp: EventPointer) => Right(evp)
|
||||
case Right(sk: PrivateKey) => Right(sk)
|
||||
case Right(addr: AddressPointer) => Right(addr)
|
||||
case Left(_) if input.split(":").size == 3 =>
|
||||
// parse "a" tag format, nip 33
|
||||
val spl = input.split(":")
|
||||
(
|
||||
spl(0).toIntOption,
|
||||
ByteVector.fromHex(spl(1)),
|
||||
Some(spl(2))
|
||||
).mapN((kind, author, identifier) =>
|
||||
AddressPointer(
|
||||
identifier,
|
||||
kind,
|
||||
scoin.XOnlyPublicKey(ByteVector32(author)),
|
||||
relays = List.empty
|
||||
)
|
||||
).toRight("couldn't parse as a nip33 'a' tag")
|
||||
case Left(_) =>
|
||||
// parse event json
|
||||
parse(input) match {
|
||||
case Left(err: io.circe.ParsingFailure) =>
|
||||
Left("not valid JSON or NIP-19 code")
|
||||
|
||||
Reference in New Issue
Block a user