mirror of
https://github.com/fiatjaf/nak.git
synced 2025-12-09 17:18:50 +00:00
Compare commits
33 Commits
v0.16.2
...
789c6a3884
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
789c6a3884 | ||
|
|
eb6fdfdd39 | ||
|
|
2b189756d1 | ||
|
|
530b484662 | ||
|
|
3ff4dbe196 | ||
|
|
2de3ff78ee | ||
|
|
03c1bf832e | ||
|
|
8df130a822 | ||
|
|
e04861fcee | ||
|
|
73d80203a0 | ||
|
|
c3cb59a94a | ||
|
|
59edaba5b8 | ||
|
|
11a690b1c6 | ||
|
|
9f8679591e | ||
|
|
75c1a88333 | ||
|
|
26fc7c338a | ||
|
|
ddc009a391 | ||
|
|
68e49fa6e5 | ||
|
|
79c1a70683 | ||
|
|
77afab780b | ||
|
|
a4f53021f0 | ||
|
|
afa31a58fc | ||
|
|
26f9b33d53 | ||
|
|
51876f89c4 | ||
|
|
ae3cb7c108 | ||
|
|
bec821d3c0 | ||
|
|
5d7240b112 | ||
|
|
bbe1661096 | ||
|
|
ea4ad84aa0 | ||
|
|
85a04aa7ce | ||
|
|
e0ca768695 | ||
|
|
bef3739a67 | ||
|
|
210c0aa282 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
nak
|
nak
|
||||||
mnt
|
mnt
|
||||||
nak.exe
|
nak.exe
|
||||||
|
qtbox
|
||||||
|
|||||||
26
README.md
26
README.md
@@ -47,6 +47,11 @@ nevent1qqs94ee3h0rhz8mc2y76zjf8cjxvw9p6j8nv45zktlwy6uacjea86kgpzfmhxue69uhkummnw
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### fetch all events except those that are present in a given line-delimited json file (negentropy sync)
|
||||||
|
```shell
|
||||||
|
~> nak req --only-missing ./events.jsonl -k 30617 pyramid.fiatjaf.com
|
||||||
|
```
|
||||||
|
|
||||||
### fetch an event using relay and author hints automatically from a nevent1 code, pretty-print it
|
### fetch an event using relay and author hints automatically from a nevent1 code, pretty-print it
|
||||||
```shell
|
```shell
|
||||||
nak fetch nevent1qqs2e3k48vtrkzjm8vvyzcmsmkf58unrxtq2k4h5yspay6vhcqm4wqcpz9mhxue69uhkummnw3ezuamfdejj7q3ql2vyh47mk2p0qlsku7hg0vn29faehy9hy34ygaclpn66ukqp3afqxpqqqqqqz7ttjyq | jq
|
nak fetch nevent1qqs2e3k48vtrkzjm8vvyzcmsmkf58unrxtq2k4h5yspay6vhcqm4wqcpz9mhxue69uhkummnw3ezuamfdejj7q3ql2vyh47mk2p0qlsku7hg0vn29faehy9hy34ygaclpn66ukqp3afqxpqqqqqqz7ttjyq | jq
|
||||||
@@ -183,7 +188,6 @@ you can also display a QR code for the bunker URI by adding the `--qrcode` flag:
|
|||||||
~> nak bunker --persist --sec ncryptsec1... relay.nsec.app nos.lol
|
~> nak bunker --persist --sec ncryptsec1... relay.nsec.app nos.lol
|
||||||
```
|
```
|
||||||
|
|
||||||
```shell
|
|
||||||
then later just
|
then later just
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
@@ -223,6 +227,21 @@ or give it a named profile:
|
|||||||
• events stored: 4, subscriptions opened: 1
|
• events stored: 4, subscriptions opened: 1
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### enable negentropy (nip77) support in your development relay
|
||||||
|
```shell
|
||||||
|
~> nak serve --negentropy
|
||||||
|
```
|
||||||
|
|
||||||
|
### run a grasp server (with a relay)
|
||||||
|
```shell
|
||||||
|
~> nak serve --grasp
|
||||||
|
```
|
||||||
|
|
||||||
|
### run a blossom server (with a relay)
|
||||||
|
```shell
|
||||||
|
~> nak serve --blossom
|
||||||
|
```
|
||||||
|
|
||||||
### make an event with a PoW target
|
### make an event with a PoW target
|
||||||
```shell
|
```shell
|
||||||
~> nak event -c 'hello getwired.app and labour.fiatjaf.com' --pow 24
|
~> nak event -c 'hello getwired.app and labour.fiatjaf.com' --pow 24
|
||||||
@@ -309,3 +328,8 @@ ffmpeg -f alsa -i default -f webm -t 00:00:03 pipe:1 | nak blossom --server blos
|
|||||||
```shell
|
```shell
|
||||||
~> cat all.jsonl | nak filter -k 1111 -a 117673e191b10fe1aedf1736ee74de4cffd4c132ca701960b70a5abad5870faa > filtered.jsonl
|
~> cat all.jsonl | nak filter -k 1111 -a 117673e191b10fe1aedf1736ee74de4cffd4c132ca701960b70a5abad5870faa > filtered.jsonl
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### use negentropy (nip77) to only fetch the ids for a given query
|
||||||
|
```shell
|
||||||
|
~> nak req --ids-only -k 1111 -a npub1vyrx2prp0mne8pczrcvv38ahn5wahsl8hlceeu3f3aqyvmu8zh5s7kfy55 relay.damus.io
|
||||||
|
```
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"fiatjaf.com/nostr"
|
|
||||||
"fiatjaf.com/nostr/keyer"
|
"fiatjaf.com/nostr/keyer"
|
||||||
"fiatjaf.com/nostr/nipb0/blossom"
|
"fiatjaf.com/nostr/nipb0/blossom"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli/v3"
|
||||||
@@ -38,11 +37,11 @@ var blossomCmd = &cli.Command{
|
|||||||
var client *blossom.Client
|
var client *blossom.Client
|
||||||
pubkey := c.Args().First()
|
pubkey := c.Args().First()
|
||||||
if pubkey != "" {
|
if pubkey != "" {
|
||||||
if pk, err := nostr.PubKeyFromHex(pubkey); err != nil {
|
pk, err := parsePubKey(pubkey)
|
||||||
|
if err != nil {
|
||||||
return fmt.Errorf("invalid public key '%s': %w", pubkey, err)
|
return fmt.Errorf("invalid public key '%s': %w", pubkey, err)
|
||||||
} else {
|
|
||||||
client = blossom.NewClient(c.String("server"), keyer.NewReadOnlySigner(pk))
|
|
||||||
}
|
}
|
||||||
|
client = blossom.NewClient(c.String("server"), keyer.NewReadOnlySigner(pk))
|
||||||
} else {
|
} else {
|
||||||
var err error
|
var err error
|
||||||
client, err = getBlossomClient(ctx, c)
|
client, err = getBlossomClient(ctx, c)
|
||||||
|
|||||||
8
count.go
8
count.go
@@ -21,7 +21,7 @@ var count = &cli.Command{
|
|||||||
&PubKeySliceFlag{
|
&PubKeySliceFlag{
|
||||||
Name: "author",
|
Name: "author",
|
||||||
Aliases: []string{"a"},
|
Aliases: []string{"a"},
|
||||||
Usage: "only accept events from these authors (pubkey as hex)",
|
Usage: "only accept events from these authors",
|
||||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
Category: CATEGORY_FILTER_ATTRIBUTES,
|
||||||
},
|
},
|
||||||
&cli.IntSliceFlag{
|
&cli.IntSliceFlag{
|
||||||
@@ -101,16 +101,16 @@ var count = &cli.Command{
|
|||||||
for _, tagFlag := range c.StringSlice("tag") {
|
for _, tagFlag := range c.StringSlice("tag") {
|
||||||
spl := strings.SplitN(tagFlag, "=", 2)
|
spl := strings.SplitN(tagFlag, "=", 2)
|
||||||
if len(spl) == 2 {
|
if len(spl) == 2 {
|
||||||
tags = append(tags, spl)
|
tags = append(tags, []string{spl[0], decodeTagValue(spl[1])})
|
||||||
} else {
|
} else {
|
||||||
return fmt.Errorf("invalid --tag '%s'", tagFlag)
|
return fmt.Errorf("invalid --tag '%s'", tagFlag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, etag := range c.StringSlice("e") {
|
for _, etag := range c.StringSlice("e") {
|
||||||
tags = append(tags, []string{"e", etag})
|
tags = append(tags, []string{"e", decodeTagValue(etag)})
|
||||||
}
|
}
|
||||||
for _, ptag := range c.StringSlice("p") {
|
for _, ptag := range c.StringSlice("p") {
|
||||||
tags = append(tags, []string{"p", ptag})
|
tags = append(tags, []string{"p", decodeTagValue(ptag)})
|
||||||
}
|
}
|
||||||
if len(tags) > 0 {
|
if len(tags) > 0 {
|
||||||
filter.Tags = make(nostr.TagMap)
|
filter.Tags = make(nostr.TagMap)
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ var encode = &cli.Command{
|
|||||||
DisableSliceFlagSeparator: true,
|
DisableSliceFlagSeparator: true,
|
||||||
Action: func(ctx context.Context, c *cli.Command) error {
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
for target := range getStdinLinesOrArguments(c.Args()) {
|
for target := range getStdinLinesOrArguments(c.Args()) {
|
||||||
id, err := nostr.IDFromHex(target)
|
id, err := parseEventID(target)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx = lineProcessingError(ctx, "invalid event id: %s", target)
|
ctx = lineProcessingError(ctx, "invalid event id: %s", target)
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ var decrypt = &cli.Command{
|
|||||||
}
|
}
|
||||||
plaintext, err := nip04.Decrypt(ciphertext, ss)
|
plaintext, err := nip04.Decrypt(ciphertext, ss)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to encrypt as nip04: %w", err)
|
return fmt.Errorf("failed to decrypt as nip04: %w", err)
|
||||||
}
|
}
|
||||||
stdout(plaintext)
|
stdout(plaintext)
|
||||||
}
|
}
|
||||||
|
|||||||
18
event.go
18
event.go
@@ -211,24 +211,30 @@ example:
|
|||||||
if found {
|
if found {
|
||||||
// tags may also contain extra elements separated with a ";"
|
// tags may also contain extra elements separated with a ";"
|
||||||
tagValues := strings.Split(tagValue, ";")
|
tagValues := strings.Split(tagValue, ";")
|
||||||
|
if len(tagValues) >= 1 {
|
||||||
|
tagValues[0] = decodeTagValue(tagValues[0])
|
||||||
|
}
|
||||||
tag = append(tag, tagValues...)
|
tag = append(tag, tagValues...)
|
||||||
}
|
}
|
||||||
tags = append(tags, tag)
|
tags = append(tags, tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, etag := range c.StringSlice("e") {
|
for _, etag := range c.StringSlice("e") {
|
||||||
if tags.FindWithValue("e", etag) == nil {
|
decodedEtag := decodeTagValue(etag)
|
||||||
tags = append(tags, nostr.Tag{"e", etag})
|
if tags.FindWithValue("e", decodedEtag) == nil {
|
||||||
|
tags = append(tags, nostr.Tag{"e", decodedEtag})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, ptag := range c.StringSlice("p") {
|
for _, ptag := range c.StringSlice("p") {
|
||||||
if tags.FindWithValue("p", ptag) == nil {
|
decodedPtag := decodeTagValue(ptag)
|
||||||
tags = append(tags, nostr.Tag{"p", ptag})
|
if tags.FindWithValue("p", decodedPtag) == nil {
|
||||||
|
tags = append(tags, nostr.Tag{"p", decodedPtag})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, dtag := range c.StringSlice("d") {
|
for _, dtag := range c.StringSlice("d") {
|
||||||
if tags.FindWithValue("d", dtag) == nil {
|
decodedDtag := decodeTagValue(dtag)
|
||||||
tags = append(tags, nostr.Tag{"d", dtag})
|
if tags.FindWithValue("d", decodedDtag) == nil {
|
||||||
|
tags = append(tags, nostr.Tag{"d", decodedDtag})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(tags) > 0 {
|
if len(tags) > 0 {
|
||||||
|
|||||||
8
flags.go
8
flags.go
@@ -96,8 +96,8 @@ func (t pubkeyValue) Create(val nostr.PubKey, p *nostr.PubKey, c struct{}) cli.V
|
|||||||
func (t pubkeyValue) ToString(b nostr.PubKey) string { return t.pubkey.String() }
|
func (t pubkeyValue) ToString(b nostr.PubKey) string { return t.pubkey.String() }
|
||||||
|
|
||||||
func (t *pubkeyValue) Set(value string) error {
|
func (t *pubkeyValue) Set(value string) error {
|
||||||
pk, err := nostr.PubKeyFromHex(value)
|
pubkey, err := parsePubKey(value)
|
||||||
t.pubkey = pk
|
t.pubkey = pubkey
|
||||||
t.hasBeenSet = true
|
t.hasBeenSet = true
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -147,8 +147,8 @@ func (t idValue) Create(val nostr.ID, p *nostr.ID, c struct{}) cli.Value {
|
|||||||
func (t idValue) ToString(b nostr.ID) string { return t.id.String() }
|
func (t idValue) ToString(b nostr.ID) string { return t.id.String() }
|
||||||
|
|
||||||
func (t *idValue) Set(value string) error {
|
func (t *idValue) Set(value string) error {
|
||||||
pk, err := nostr.IDFromHex(value)
|
id, err := parseEventID(value)
|
||||||
t.id = pk
|
t.id = id
|
||||||
t.hasBeenSet = true
|
t.hasBeenSet = true
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
23
go.mod
23
go.mod
@@ -1,28 +1,31 @@
|
|||||||
module github.com/fiatjaf/nak
|
module github.com/fiatjaf/nak
|
||||||
|
|
||||||
go 1.24.1
|
go 1.25
|
||||||
|
|
||||||
require (
|
require (
|
||||||
fiatjaf.com/lib v0.3.1
|
fiatjaf.com/lib v0.3.1
|
||||||
fiatjaf.com/nostr v0.0.0-20250907220143-b67e3092b02e
|
fiatjaf.com/nostr v0.0.0-20251126101225-44130595c606
|
||||||
|
github.com/AlecAivazis/survey/v2 v2.3.7
|
||||||
github.com/bep/debounce v1.2.1
|
github.com/bep/debounce v1.2.1
|
||||||
github.com/btcsuite/btcd/btcec/v2 v2.3.5
|
github.com/btcsuite/btcd/btcec/v2 v2.3.6
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e
|
||||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0
|
||||||
github.com/fatih/color v1.16.0
|
github.com/fatih/color v1.16.0
|
||||||
github.com/hanwen/go-fuse/v2 v2.7.2
|
github.com/hanwen/go-fuse/v2 v2.7.2
|
||||||
github.com/json-iterator/go v1.1.12
|
github.com/json-iterator/go v1.1.12
|
||||||
github.com/liamg/magic v0.0.1
|
github.com/liamg/magic v0.0.1
|
||||||
github.com/mailru/easyjson v0.9.0
|
github.com/mailru/easyjson v0.9.1
|
||||||
github.com/mark3labs/mcp-go v0.8.3
|
github.com/mark3labs/mcp-go v0.8.3
|
||||||
github.com/markusmobius/go-dateparser v1.2.3
|
github.com/markusmobius/go-dateparser v1.2.3
|
||||||
github.com/mattn/go-isatty v0.0.20
|
github.com/mattn/go-isatty v0.0.20
|
||||||
github.com/mattn/go-tty v0.0.7
|
github.com/mattn/go-tty v0.0.7
|
||||||
github.com/mdp/qrterminal/v3 v3.2.1
|
github.com/mdp/qrterminal/v3 v3.2.1
|
||||||
|
github.com/puzpuzpuz/xsync/v3 v3.5.1
|
||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.10.0
|
||||||
|
github.com/therecipe/qt v0.0.0-20200904063919-c0c124a5770d
|
||||||
github.com/urfave/cli/v3 v3.0.0-beta1
|
github.com/urfave/cli/v3 v3.0.0-beta1
|
||||||
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b
|
golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6
|
||||||
golang.org/x/sync v0.16.0
|
golang.org/x/sync v0.18.0
|
||||||
golang.org/x/term v0.32.0
|
golang.org/x/term v0.32.0
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -30,6 +33,7 @@ require (
|
|||||||
github.com/FastFilter/xorfilter v0.2.1 // indirect
|
github.com/FastFilter/xorfilter v0.2.1 // indirect
|
||||||
github.com/ImVexed/fasturl v0.0.0-20230304231329-4e41488060f3 // indirect
|
github.com/ImVexed/fasturl v0.0.0-20230304231329-4e41488060f3 // indirect
|
||||||
github.com/andybalholm/brotli v1.1.1 // indirect
|
github.com/andybalholm/brotli v1.1.1 // indirect
|
||||||
|
github.com/bluekeyes/go-gitdiff v0.7.1 // indirect
|
||||||
github.com/btcsuite/btcd v0.24.2 // indirect
|
github.com/btcsuite/btcd v0.24.2 // indirect
|
||||||
github.com/btcsuite/btcd/btcutil v1.1.5 // indirect
|
github.com/btcsuite/btcd/btcutil v1.1.5 // indirect
|
||||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect
|
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect
|
||||||
@@ -45,21 +49,26 @@ require (
|
|||||||
github.com/elnosh/gonuts v0.4.2 // indirect
|
github.com/elnosh/gonuts v0.4.2 // indirect
|
||||||
github.com/fasthttp/websocket v1.5.12 // indirect
|
github.com/fasthttp/websocket v1.5.12 // indirect
|
||||||
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||||
|
github.com/go-git/go-git/v5 v5.16.3 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/gopherjs/gopherjs v0.0.0-20190411002643-bd77b112433e // indirect
|
||||||
github.com/hablullah/go-hijri v1.0.2 // indirect
|
github.com/hablullah/go-hijri v1.0.2 // indirect
|
||||||
github.com/hablullah/go-juliandays v1.0.0 // indirect
|
github.com/hablullah/go-juliandays v1.0.0 // indirect
|
||||||
github.com/jalaali/go-jalaali v0.0.0-20210801064154-80525e88d958 // indirect
|
github.com/jalaali/go-jalaali v0.0.0-20210801064154-80525e88d958 // indirect
|
||||||
github.com/josharian/intern v1.0.0 // indirect
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||||
github.com/klauspost/compress v1.18.0 // indirect
|
github.com/klauspost/compress v1.18.0 // indirect
|
||||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||||
github.com/magefile/mage v1.14.0 // indirect
|
github.com/magefile/mage v1.14.0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
|
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // 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/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/rs/cors v1.11.1 // indirect
|
github.com/rs/cors v1.11.1 // indirect
|
||||||
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect
|
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect
|
||||||
|
github.com/templexxx/cpu v0.0.1 // indirect
|
||||||
|
github.com/templexxx/xhex v0.0.0-20200614015412-aed53437177b // indirect
|
||||||
github.com/tetratelabs/wazero v1.8.0 // indirect
|
github.com/tetratelabs/wazero v1.8.0 // indirect
|
||||||
github.com/tidwall/gjson v1.18.0 // indirect
|
github.com/tidwall/gjson v1.18.0 // indirect
|
||||||
github.com/tidwall/match v1.2.0 // indirect
|
github.com/tidwall/match v1.2.0 // indirect
|
||||||
|
|||||||
88
go.sum
88
go.sum
@@ -1,11 +1,15 @@
|
|||||||
fiatjaf.com/lib v0.3.1 h1:/oFQwNtFRfV+ukmOCxfBEAuayoLwXp4wu2/fz5iHpwA=
|
fiatjaf.com/lib v0.3.1 h1:/oFQwNtFRfV+ukmOCxfBEAuayoLwXp4wu2/fz5iHpwA=
|
||||||
fiatjaf.com/lib v0.3.1/go.mod h1:Ycqq3+mJ9jAWu7XjbQI1cVr+OFgnHn79dQR5oTII47g=
|
fiatjaf.com/lib v0.3.1/go.mod h1:Ycqq3+mJ9jAWu7XjbQI1cVr+OFgnHn79dQR5oTII47g=
|
||||||
fiatjaf.com/nostr v0.0.0-20250907220143-b67e3092b02e h1:SnM2u7nAlK5rUFJsQYOdTUOAbz0B9Z9ihq95akFHdOE=
|
fiatjaf.com/nostr v0.0.0-20251126101225-44130595c606 h1:wQHJ0TFA0Fuq92p/6u6AbsBFq6ZVToSdxV6puXVIruI=
|
||||||
fiatjaf.com/nostr v0.0.0-20250907220143-b67e3092b02e/go.mod h1:RHPNZ7jtRi7dZf590g1urHYhWXnbetvd1oByNbAlgKE=
|
fiatjaf.com/nostr v0.0.0-20251126101225-44130595c606/go.mod h1:ue7yw0zHfZj23Ml2kVSdBx0ENEaZiuvGxs/8VEN93FU=
|
||||||
|
github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ=
|
||||||
|
github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo=
|
||||||
github.com/FastFilter/xorfilter v0.2.1 h1:lbdeLG9BdpquK64ZsleBS8B4xO/QW1IM0gMzF7KaBKc=
|
github.com/FastFilter/xorfilter v0.2.1 h1:lbdeLG9BdpquK64ZsleBS8B4xO/QW1IM0gMzF7KaBKc=
|
||||||
github.com/FastFilter/xorfilter v0.2.1/go.mod h1:aumvdkhscz6YBZF9ZA/6O4fIoNod4YR50kIVGGZ7l9I=
|
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 h1:ClzzXMDDuUbWfNNZqGeYq4PnYOlwlOVIvSyNaIy0ykg=
|
||||||
github.com/ImVexed/fasturl v0.0.0-20230304231329-4e41488060f3/go.mod h1:we0YA5CsBbH5+/NUzC/AlMmxaDtWlXeNsqrwXjTzmzA=
|
github.com/ImVexed/fasturl v0.0.0-20230304231329-4e41488060f3/go.mod h1:we0YA5CsBbH5+/NUzC/AlMmxaDtWlXeNsqrwXjTzmzA=
|
||||||
|
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
|
||||||
|
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
|
||||||
github.com/PowerDNS/lmdb-go v1.9.3 h1:AUMY2pZT8WRpkEv39I9Id3MuoHd+NZbTVpNhruVkPTg=
|
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/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/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
|
||||||
@@ -13,6 +17,8 @@ github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7X
|
|||||||
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
||||||
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
|
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
|
||||||
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
|
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
|
||||||
|
github.com/bluekeyes/go-gitdiff v0.7.1 h1:graP4ElLRshr8ecu0UtqfNTCHrtSyZd3DABQm/DWesQ=
|
||||||
|
github.com/bluekeyes/go-gitdiff v0.7.1/go.mod h1:QpfYYO1E0fTVHVZAZKiRjtSGY9823iCdvGXBcEzHGbM=
|
||||||
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
|
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=
|
github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M=
|
||||||
github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A=
|
github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A=
|
||||||
@@ -20,8 +26,8 @@ github.com/btcsuite/btcd v0.24.2 h1:aLmxPguqxza+4ag8R1I2nnJjSu2iFn/kqtHTIImswcY=
|
|||||||
github.com/btcsuite/btcd v0.24.2/go.mod h1:5C8ChTkl5ejr3WHj8tkQSCmydiMEPB0ZhQhehpq7Dgg=
|
github.com/btcsuite/btcd v0.24.2/go.mod h1:5C8ChTkl5ejr3WHj8tkQSCmydiMEPB0ZhQhehpq7Dgg=
|
||||||
github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA=
|
github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA=
|
||||||
github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE=
|
github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE=
|
||||||
github.com/btcsuite/btcd/btcec/v2 v2.3.5 h1:dpAlnAwmT1yIBm3exhT1/8iUSD98RDJM5vqJVQDQLiU=
|
github.com/btcsuite/btcd/btcec/v2 v2.3.6 h1:IzlsEr9olcSRKB/n7c4351F3xHKxS2lma+1UFGCYd4E=
|
||||||
github.com/btcsuite/btcd/btcec/v2 v2.3.5/go.mod h1:m22FrOAiuxl/tht9wIqAoGHcbnCCaPWyauO8y2LGGtQ=
|
github.com/btcsuite/btcd/btcec/v2 v2.3.6/go.mod h1:m22FrOAiuxl/tht9wIqAoGHcbnCCaPWyauO8y2LGGtQ=
|
||||||
github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A=
|
github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A=
|
||||||
github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE=
|
github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE=
|
||||||
github.com/btcsuite/btcd/btcutil v1.1.5 h1:+wER79R5670vs/ZusMTF1yTcRYE5GUsFbdjdisflzM8=
|
github.com/btcsuite/btcd/btcutil v1.1.5 h1:+wER79R5670vs/ZusMTF1yTcRYE5GUsFbdjdisflzM8=
|
||||||
@@ -51,6 +57,8 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWs
|
|||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
|
github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
|
||||||
github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
|
github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
|
||||||
|
github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI=
|
||||||
|
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||||
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
@@ -81,6 +89,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
|
|||||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
||||||
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
||||||
|
github.com/go-git/go-git/v5 v5.16.3 h1:Z8BtvxZ09bYm/yYNgPKCzgWtaRqDTgIKRgIRHBfU6Z8=
|
||||||
|
github.com/go-git/go-git/v5 v5.16.3/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||||
@@ -97,6 +107,8 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX
|
|||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/gopherjs/gopherjs v0.0.0-20190411002643-bd77b112433e h1:XWcjeEtTFTOVA9Fs1w7n2XBftk5ib4oZrhzWk0B+3eA=
|
||||||
|
github.com/gopherjs/gopherjs v0.0.0-20190411002643-bd77b112433e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/hablullah/go-hijri v1.0.2 h1:drT/MZpSZJQXo7jftf5fthArShcaMtsal0Zf/dnmp6k=
|
github.com/hablullah/go-hijri v1.0.2 h1:drT/MZpSZJQXo7jftf5fthArShcaMtsal0Zf/dnmp6k=
|
||||||
github.com/hablullah/go-hijri v1.0.2/go.mod h1:OS5qyYLDjORXzK4O1adFw9Q5WfhOcMdAKglDkcTxgWQ=
|
github.com/hablullah/go-hijri v1.0.2/go.mod h1:OS5qyYLDjORXzK4O1adFw9Q5WfhOcMdAKglDkcTxgWQ=
|
||||||
@@ -104,6 +116,8 @@ github.com/hablullah/go-juliandays v1.0.0 h1:A8YM7wIj16SzlKT0SRJc9CD29iiaUzpBLzh
|
|||||||
github.com/hablullah/go-juliandays v1.0.0/go.mod h1:0JOYq4oFOuDja+oospuc61YoX+uNEn7Z6uHYTbBzdGc=
|
github.com/hablullah/go-juliandays v1.0.0/go.mod h1:0JOYq4oFOuDja+oospuc61YoX+uNEn7Z6uHYTbBzdGc=
|
||||||
github.com/hanwen/go-fuse/v2 v2.7.2 h1:SbJP1sUP+n1UF8NXBA14BuojmTez+mDgOk0bC057HQw=
|
github.com/hanwen/go-fuse/v2 v2.7.2 h1:SbJP1sUP+n1UF8NXBA14BuojmTez+mDgOk0bC057HQw=
|
||||||
github.com/hanwen/go-fuse/v2 v2.7.2/go.mod h1:ugNaD/iv5JYyS1Rcvi57Wz7/vrLQJo10mmketmoef48=
|
github.com/hanwen/go-fuse/v2 v2.7.2/go.mod h1:ugNaD/iv5JYyS1Rcvi57Wz7/vrLQJo10mmketmoef48=
|
||||||
|
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
|
||||||
|
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
github.com/jalaali/go-jalaali v0.0.0-20210801064154-80525e88d958 h1:qxLoi6CAcXVzjfvu+KXIXJOAsQB62LXjsfbOaErsVzE=
|
github.com/jalaali/go-jalaali v0.0.0-20210801064154-80525e88d958 h1:qxLoi6CAcXVzjfvu+KXIXJOAsQB62LXjsfbOaErsVzE=
|
||||||
github.com/jalaali/go-jalaali v0.0.0-20210801064154-80525e88d958/go.mod h1:Wqfu7mjUHj9WDzSSPI5KfBclTTEnLveRUFr/ujWnTgE=
|
github.com/jalaali/go-jalaali v0.0.0-20210801064154-80525e88d958/go.mod h1:Wqfu7mjUHj9WDzSSPI5KfBclTTEnLveRUFr/ujWnTgE=
|
||||||
@@ -114,29 +128,41 @@ github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFF
|
|||||||
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
|
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||||
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||||
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
|
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
|
||||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||||
github.com/liamg/magic v0.0.1 h1:Ru22ElY+sCh6RvRTWjQzKKCxsEco8hE0co8n1qe7TBM=
|
github.com/liamg/magic v0.0.1 h1:Ru22ElY+sCh6RvRTWjQzKKCxsEco8hE0co8n1qe7TBM=
|
||||||
github.com/liamg/magic v0.0.1/go.mod h1:yQkOmZZI52EA+SQ2xyHpVw8fNvTBruF873Y+Vt6S+fk=
|
github.com/liamg/magic v0.0.1/go.mod h1:yQkOmZZI52EA+SQ2xyHpVw8fNvTBruF873Y+Vt6S+fk=
|
||||||
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
|
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
|
||||||
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||||
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
|
github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8=
|
||||||
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||||
github.com/mark3labs/mcp-go v0.8.3 h1:IzlyN8BaP4YwUMUDqxOGJhGdZXEDQiAPX43dNPgnzrg=
|
github.com/mark3labs/mcp-go v0.8.3 h1:IzlyN8BaP4YwUMUDqxOGJhGdZXEDQiAPX43dNPgnzrg=
|
||||||
github.com/mark3labs/mcp-go v0.8.3/go.mod h1:cjMlBU0cv/cj9kjlgmRhoJ5JREdS7YX83xeIG9Ko/jE=
|
github.com/mark3labs/mcp-go v0.8.3/go.mod h1:cjMlBU0cv/cj9kjlgmRhoJ5JREdS7YX83xeIG9Ko/jE=
|
||||||
github.com/markusmobius/go-dateparser v1.2.3 h1:TvrsIvr5uk+3v6poDjaicnAFJ5IgtFHgLiuMY2Eb7Nw=
|
github.com/markusmobius/go-dateparser v1.2.3 h1:TvrsIvr5uk+3v6poDjaicnAFJ5IgtFHgLiuMY2Eb7Nw=
|
||||||
github.com/markusmobius/go-dateparser v1.2.3/go.mod h1:cMwQRrBUQlK1UI5TIFHEcvpsMbkWrQLXuaPNMFzuYLk=
|
github.com/markusmobius/go-dateparser v1.2.3/go.mod h1:cMwQRrBUQlK1UI5TIFHEcvpsMbkWrQLXuaPNMFzuYLk=
|
||||||
|
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||||
|
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-tty v0.0.7 h1:KJ486B6qI8+wBO7kQxYgmmEFDaFEE96JMBQ7h400N8Q=
|
github.com/mattn/go-tty v0.0.7 h1:KJ486B6qI8+wBO7kQxYgmmEFDaFEE96JMBQ7h400N8Q=
|
||||||
github.com/mattn/go-tty v0.0.7/go.mod h1:f2i5ZOvXBU/tCABmLmOfzLz9azMo5wdAaElRNnJKr+k=
|
github.com/mattn/go-tty v0.0.7/go.mod h1:f2i5ZOvXBU/tCABmLmOfzLz9azMo5wdAaElRNnJKr+k=
|
||||||
github.com/mdp/qrterminal/v3 v3.2.1 h1:6+yQjiiOsSuXT5n9/m60E54vdgFsw0zhADHhHLrFet4=
|
github.com/mdp/qrterminal/v3 v3.2.1 h1:6+yQjiiOsSuXT5n9/m60E54vdgFsw0zhADHhHLrFet4=
|
||||||
github.com/mdp/qrterminal/v3 v3.2.1/go.mod h1:jOTmXvnBsMy5xqLniO0R++Jmjs2sTm9dFSuQ5kpz/SU=
|
github.com/mdp/qrterminal/v3 v3.2.1/go.mod h1:jOTmXvnBsMy5xqLniO0R++Jmjs2sTm9dFSuQ5kpz/SU=
|
||||||
|
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
|
||||||
|
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||||
github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78=
|
github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78=
|
||||||
github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI=
|
github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
@@ -159,19 +185,31 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
|||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg=
|
github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg=
|
||||||
github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||||
|
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
|
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||||
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
|
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
|
||||||
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
|
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
|
||||||
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 h1:D0vL7YNisV2yqE55+q0lFuGse6U8lxlg7fYTctlT5Gc=
|
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 h1:D0vL7YNisV2yqE55+q0lFuGse6U8lxlg7fYTctlT5Gc=
|
||||||
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg=
|
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg=
|
||||||
|
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||||
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
|
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
|
||||||
|
github.com/templexxx/cpu v0.0.1 h1:hY4WdLOgKdc8y13EYklu9OUTXik80BkxHoWvTO6MQQY=
|
||||||
|
github.com/templexxx/cpu v0.0.1/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk=
|
||||||
|
github.com/templexxx/xhex v0.0.0-20200614015412-aed53437177b h1:XeDLE6c9mzHpdv3Wb1+pWBaWv/BlHK0ZYIu/KaL6eHg=
|
||||||
|
github.com/templexxx/xhex v0.0.0-20200614015412-aed53437177b/go.mod h1:7rwmCH0wC2fQvNEvPZ3sKXukhyCTyiaZ5VTZMQYpZKQ=
|
||||||
github.com/tetratelabs/wazero v1.8.0 h1:iEKu0d4c2Pd+QSRieYbnQC9yiFlMS9D+Jr0LsRmcF4g=
|
github.com/tetratelabs/wazero v1.8.0 h1:iEKu0d4c2Pd+QSRieYbnQC9yiFlMS9D+Jr0LsRmcF4g=
|
||||||
github.com/tetratelabs/wazero v1.8.0/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs=
|
github.com/tetratelabs/wazero v1.8.0/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs=
|
||||||
|
github.com/therecipe/qt v0.0.0-20200904063919-c0c124a5770d h1:T+d8FnaLSvM/1BdlDXhW4d5dr2F07bAbB+LpgzMxx+o=
|
||||||
|
github.com/therecipe/qt v0.0.0-20200904063919-c0c124a5770d/go.mod h1:SUUR2j3aE1z6/g76SdD6NwACEpvCxb3fvG82eKbD6us=
|
||||||
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
||||||
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||||
@@ -196,45 +234,72 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
|||||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||||
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
go.etcd.io/bbolt v1.4.2 h1:IrUHp260R8c+zYx/Tm8QZr04CX+qWS5PGfPdevhdm1I=
|
go.etcd.io/bbolt v1.4.2 h1:IrUHp260R8c+zYx/Tm8QZr04CX+qWS5PGfPdevhdm1I=
|
||||||
go.etcd.io/bbolt v1.4.2/go.mod h1:Is8rSHO/b4f3XigBC0lL0+4FwAQv3HXEEIgFMuKHceM=
|
go.etcd.io/bbolt v1.4.2/go.mod h1:Is8rSHO/b4f3XigBC0lL0+4FwAQv3HXEEIgFMuKHceM=
|
||||||
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||||
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||||
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b h1:DXr+pvt3nC887026GRP39Ej11UATqWDmWuS99x26cD0=
|
golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 h1:zfMcR1Cs4KNuomFFgGefv5N0czO2XZpUbxGUy8i8ug0=
|
||||||
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4=
|
golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0=
|
||||||
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190420063019-afa5a82059c6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
||||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||||
|
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
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.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
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.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
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=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
@@ -243,8 +308,9 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ
|
|||||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
|||||||
67
helpers.go
67
helpers.go
@@ -18,6 +18,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"fiatjaf.com/nostr"
|
"fiatjaf.com/nostr"
|
||||||
|
"fiatjaf.com/nostr/nip05"
|
||||||
"fiatjaf.com/nostr/nip19"
|
"fiatjaf.com/nostr/nip19"
|
||||||
"fiatjaf.com/nostr/nip42"
|
"fiatjaf.com/nostr/nip42"
|
||||||
"fiatjaf.com/nostr/sdk"
|
"fiatjaf.com/nostr/sdk"
|
||||||
@@ -75,7 +76,7 @@ func getJsonsOrBlank() iter.Seq[string] {
|
|||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
if !hasStdin {
|
if !hasStdin && !isPiped() {
|
||||||
yield("{}")
|
yield("{}")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -464,6 +465,70 @@ func askConfirmation(msg string) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parsePubKey(value string) (nostr.PubKey, error) {
|
||||||
|
// try nip05 first
|
||||||
|
if nip05.IsValidIdentifier(value) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
|
||||||
|
pp, err := nip05.QueryIdentifier(ctx, value)
|
||||||
|
cancel()
|
||||||
|
if err == nil {
|
||||||
|
return pp.PublicKey, nil
|
||||||
|
}
|
||||||
|
// if nip05 fails, fall through to try as pubkey
|
||||||
|
}
|
||||||
|
|
||||||
|
pk, err := nostr.PubKeyFromHex(value)
|
||||||
|
if err == nil {
|
||||||
|
return pk, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if prefix, decoded, err := nip19.Decode(value); err == nil {
|
||||||
|
switch prefix {
|
||||||
|
case "npub":
|
||||||
|
if pk, ok := decoded.(nostr.PubKey); ok {
|
||||||
|
return pk, nil
|
||||||
|
}
|
||||||
|
case "nprofile":
|
||||||
|
if profile, ok := decoded.(nostr.ProfilePointer); ok {
|
||||||
|
return profile.PublicKey, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nostr.PubKey{}, fmt.Errorf("invalid pubkey (\"%s\"): expected hex, npub, or nprofile", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseEventID(value string) (nostr.ID, error) {
|
||||||
|
id, err := nostr.IDFromHex(value)
|
||||||
|
if err == nil {
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if prefix, decoded, err := nip19.Decode(value); err == nil {
|
||||||
|
switch prefix {
|
||||||
|
case "note":
|
||||||
|
if id, ok := decoded.(nostr.ID); ok {
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
case "nevent":
|
||||||
|
if event, ok := decoded.(nostr.EventPointer); ok {
|
||||||
|
return event.ID, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nostr.ID{}, fmt.Errorf("invalid event id (\"%s\"): expected hex, note, or nevent", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeTagValue(value string) string {
|
||||||
|
if strings.HasPrefix(value, "npub1") || strings.HasPrefix(value, "nevent1") || strings.HasPrefix(value, "note1") || strings.HasPrefix(value, "nprofile1") || strings.HasPrefix(value, "naddr1") {
|
||||||
|
if ptr, err := nip19.ToPointer(value); err == nil {
|
||||||
|
return ptr.AsTagReference()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
var colors = struct {
|
var colors = struct {
|
||||||
reset func(...any) (int, error)
|
reset func(...any) (int, error)
|
||||||
italic func(...any) string
|
italic func(...any) string
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ func gatherSecretKeyOrBunkerFromArguments(ctx context.Context, c *cli.Command) (
|
|||||||
clientKey = nostr.Generate()
|
clientKey = nostr.Generate()
|
||||||
}
|
}
|
||||||
|
|
||||||
logverbose("[nip46]: connecting to %s with client key %s", bunkerURL, clientKey.Hex())
|
logverbose("[nip46]: connecting to %s with client key %s\n", bunkerURL, clientKey.Hex())
|
||||||
|
|
||||||
bunker, err := nip46.ConnectBunker(ctx, clientKey, bunkerURL, nil, func(s string) {
|
bunker, err := nip46.ConnectBunker(ctx, clientKey, bunkerURL, nil, func(s string) {
|
||||||
log(color.CyanString("[nip46]: open the following URL: %s"), s)
|
log(color.CyanString("[nip46]: open the following URL: %s"), s)
|
||||||
|
|||||||
2
main.go
2
main.go
@@ -48,6 +48,8 @@ var app = &cli.Command{
|
|||||||
curl,
|
curl,
|
||||||
fsCmd,
|
fsCmd,
|
||||||
publish,
|
publish,
|
||||||
|
git,
|
||||||
|
nip,
|
||||||
},
|
},
|
||||||
Version: version,
|
Version: version,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
|
|||||||
186
nip.go
Normal file
186
nip.go
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/urfave/cli/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
var nip = &cli.Command{
|
||||||
|
Name: "nip",
|
||||||
|
Usage: "get the description of a NIP from its number",
|
||||||
|
Description: `fetches the NIPs README from GitHub and parses it to find the description of the given NIP number.
|
||||||
|
|
||||||
|
example:
|
||||||
|
nak nip 1
|
||||||
|
nak nip list
|
||||||
|
nak nip open 1`,
|
||||||
|
ArgsUsage: "<NIP number>",
|
||||||
|
Commands: []*cli.Command{
|
||||||
|
{
|
||||||
|
Name: "list",
|
||||||
|
Usage: "list all NIPs",
|
||||||
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
|
return iterateNips(func(nip, desc, link string) bool {
|
||||||
|
stdout(nip + ": " + desc)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "open",
|
||||||
|
Usage: "open the NIP page in the browser",
|
||||||
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
|
reqNum := c.Args().First()
|
||||||
|
if reqNum == "" {
|
||||||
|
return fmt.Errorf("missing NIP number")
|
||||||
|
}
|
||||||
|
|
||||||
|
normalize := func(s string) string {
|
||||||
|
s = strings.ToLower(s)
|
||||||
|
s = strings.TrimPrefix(s, "nip-")
|
||||||
|
s = strings.TrimLeft(s, "0")
|
||||||
|
if s == "" {
|
||||||
|
s = "0"
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
reqNum = normalize(reqNum)
|
||||||
|
|
||||||
|
foundLink := ""
|
||||||
|
err := iterateNips(func(nip, desc, link string) bool {
|
||||||
|
nipNum := normalize(nip)
|
||||||
|
if nipNum == reqNum {
|
||||||
|
foundLink = link
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if foundLink == "" {
|
||||||
|
return fmt.Errorf("NIP-%s not found", strings.ToUpper(reqNum))
|
||||||
|
}
|
||||||
|
|
||||||
|
url := "https://github.com/nostr-protocol/nips/blob/master/" + foundLink
|
||||||
|
fmt.Println("Opening " + url)
|
||||||
|
|
||||||
|
var cmd *exec.Cmd
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "darwin":
|
||||||
|
cmd = exec.Command("open", url)
|
||||||
|
case "windows":
|
||||||
|
cmd = exec.Command("cmd", "/c", "start", url)
|
||||||
|
default:
|
||||||
|
cmd = exec.Command("xdg-open", url)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmd.Start()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
|
reqNum := c.Args().First()
|
||||||
|
if reqNum == "" {
|
||||||
|
return fmt.Errorf("missing NIP number")
|
||||||
|
}
|
||||||
|
|
||||||
|
normalize := func(s string) string {
|
||||||
|
s = strings.ToLower(s)
|
||||||
|
s = strings.TrimPrefix(s, "nip-")
|
||||||
|
s = strings.TrimLeft(s, "0")
|
||||||
|
if s == "" {
|
||||||
|
s = "0"
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
reqNum = normalize(reqNum)
|
||||||
|
|
||||||
|
found := false
|
||||||
|
err := iterateNips(func(nip, desc, link string) bool {
|
||||||
|
nipNum := normalize(nip)
|
||||||
|
|
||||||
|
if nipNum == reqNum {
|
||||||
|
stdout(strings.TrimSpace(desc))
|
||||||
|
found = true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
return fmt.Errorf("NIP-%s not found", strings.ToUpper(reqNum))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func iterateNips(yield func(nip, desc, link string) bool) error {
|
||||||
|
resp, err := http.Get("https://raw.githubusercontent.com/nostr-protocol/nips/master/README.md")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to fetch NIPs README: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to read NIPs README: %w", err)
|
||||||
|
}
|
||||||
|
bodyStr := string(body)
|
||||||
|
epoch := strings.Index(bodyStr, "## List")
|
||||||
|
|
||||||
|
lines := strings.SplitSeq(bodyStr[epoch+8:], "\n")
|
||||||
|
for line := range lines {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
if strings.HasPrefix(line, "##") {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(line, "- [NIP-") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
start := strings.Index(line, "[")
|
||||||
|
end := strings.Index(line, "]")
|
||||||
|
if start == -1 || end == -1 || end < start {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
content := line[start+1 : end]
|
||||||
|
|
||||||
|
parts := strings.SplitN(content, ":", 2)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
nipPart := parts[0]
|
||||||
|
descPart := parts[1]
|
||||||
|
|
||||||
|
rest := line[end+1:]
|
||||||
|
linkStart := strings.Index(rest, "(")
|
||||||
|
linkEnd := strings.Index(rest, ")")
|
||||||
|
link := ""
|
||||||
|
if linkStart != -1 && linkEnd != -1 && linkEnd > linkStart {
|
||||||
|
link = rest[linkStart+1 : linkEnd]
|
||||||
|
}
|
||||||
|
|
||||||
|
if !yield(nipPart, strings.TrimSpace(descPart), link) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"fiatjaf.com/nostr"
|
|
||||||
"fiatjaf.com/nostr/sdk"
|
"fiatjaf.com/nostr/sdk"
|
||||||
"fiatjaf.com/nostr/sdk/hints/bbolth"
|
"fiatjaf.com/nostr/sdk/hints/bbolth"
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
@@ -82,7 +81,7 @@ var outbox = &cli.Command{
|
|||||||
return fmt.Errorf("expected exactly one argument (pubkey)")
|
return fmt.Errorf("expected exactly one argument (pubkey)")
|
||||||
}
|
}
|
||||||
|
|
||||||
pk, err := nostr.PubKeyFromHex(c.Args().First())
|
pk, err := parsePubKey(c.Args().First())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("invalid public key '%s': %w", c.Args().First(), err)
|
return fmt.Errorf("invalid public key '%s': %w", c.Args().First(), err)
|
||||||
}
|
}
|
||||||
|
|||||||
110
req.go
110
req.go
@@ -1,14 +1,19 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"fiatjaf.com/nostr"
|
"fiatjaf.com/nostr"
|
||||||
|
"fiatjaf.com/nostr/eventstore"
|
||||||
|
"fiatjaf.com/nostr/eventstore/slicestore"
|
||||||
|
"fiatjaf.com/nostr/eventstore/wrappers"
|
||||||
"fiatjaf.com/nostr/nip42"
|
"fiatjaf.com/nostr/nip42"
|
||||||
"fiatjaf.com/nostr/nip77"
|
"fiatjaf.com/nostr/nip77"
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
@@ -38,6 +43,11 @@ example:
|
|||||||
DisableSliceFlagSeparator: true,
|
DisableSliceFlagSeparator: true,
|
||||||
Flags: append(defaultKeyFlags,
|
Flags: append(defaultKeyFlags,
|
||||||
append(reqFilterFlags,
|
append(reqFilterFlags,
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "only-missing",
|
||||||
|
Usage: "use nip77 negentropy to only fetch events that aren't present in the given jsonl file",
|
||||||
|
TakesFile: true,
|
||||||
|
},
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "ids-only",
|
Name: "ids-only",
|
||||||
Usage: "use nip77 to fetch just a list of ids",
|
Usage: "use nip77 to fetch just a list of ids",
|
||||||
@@ -53,7 +63,7 @@ example:
|
|||||||
DefaultText: "false, will only use manually-specified relays",
|
DefaultText: "false, will only use manually-specified relays",
|
||||||
},
|
},
|
||||||
&cli.UintFlag{
|
&cli.UintFlag{
|
||||||
Name: "outbox-relays-number",
|
Name: "outbox-relays-per-pubkey",
|
||||||
Aliases: []string{"n"},
|
Aliases: []string{"n"},
|
||||||
Usage: "number of outbox relays to use for each pubkey",
|
Usage: "number of outbox relays to use for each pubkey",
|
||||||
Value: 3,
|
Value: 3,
|
||||||
@@ -90,6 +100,13 @@ example:
|
|||||||
),
|
),
|
||||||
ArgsUsage: "[relay...]",
|
ArgsUsage: "[relay...]",
|
||||||
Action: func(ctx context.Context, c *cli.Command) error {
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
|
negentropy := c.Bool("ids-only") || c.IsSet("only-missing")
|
||||||
|
if negentropy {
|
||||||
|
if c.Bool("paginate") || c.Bool("stream") || c.Bool("outbox") {
|
||||||
|
return fmt.Errorf("negentropy is incompatible with --stream, --outbox or --paginate")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if c.Bool("paginate") && c.Bool("stream") {
|
if c.Bool("paginate") && c.Bool("stream") {
|
||||||
return fmt.Errorf("incompatible flags --paginate and --stream")
|
return fmt.Errorf("incompatible flags --paginate and --stream")
|
||||||
}
|
}
|
||||||
@@ -99,7 +116,7 @@ example:
|
|||||||
}
|
}
|
||||||
|
|
||||||
relayUrls := c.Args().Slice()
|
relayUrls := c.Args().Slice()
|
||||||
if len(relayUrls) > 0 {
|
if len(relayUrls) > 0 && !negentropy {
|
||||||
// this is used both for the normal AUTH (after "auth-required:" is received) or forced pre-auth
|
// this is used both for the normal AUTH (after "auth-required:" is received) or forced pre-auth
|
||||||
// connect to all relays we expect to use in this call in parallel
|
// connect to all relays we expect to use in this call in parallel
|
||||||
forcePreAuthSigner := authSigner
|
forcePreAuthSigner := authSigner
|
||||||
@@ -152,20 +169,60 @@ example:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(relayUrls) > 0 || c.Bool("outbox") {
|
if len(relayUrls) > 0 || c.Bool("outbox") {
|
||||||
if c.Bool("ids-only") {
|
if negentropy {
|
||||||
seen := make(map[nostr.ID]struct{}, max(500, filter.Limit))
|
store := &slicestore.SliceStore{}
|
||||||
for _, url := range relayUrls {
|
store.Init()
|
||||||
ch, err := nip77.FetchIDsOnly(ctx, url, filter)
|
|
||||||
|
if syncFile := c.String("only-missing"); syncFile != "" {
|
||||||
|
file, err := os.Open(syncFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log("negentropy call to %s failed: %s", url, err)
|
return fmt.Errorf("failed to open sync file: %w", err)
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
for id := range ch {
|
defer file.Close()
|
||||||
if _, ok := seen[id]; ok {
|
scanner := bufio.NewScanner(file)
|
||||||
|
scanner.Buffer(make([]byte, 16*1024*1024), 256*1024*1024)
|
||||||
|
for scanner.Scan() {
|
||||||
|
var evt nostr.Event
|
||||||
|
if err := easyjson.Unmarshal([]byte(scanner.Text()), &evt); err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
seen[id] = struct{}{}
|
if err := store.SaveEvent(evt); err != nil || err == eventstore.ErrDupEvent {
|
||||||
stdout(id)
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return fmt.Errorf("failed to read sync file: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
target := PrintingQuerierPublisher{
|
||||||
|
QuerierPublisher: wrappers.StorePublisher{Store: store, MaxLimit: math.MaxInt},
|
||||||
|
}
|
||||||
|
|
||||||
|
var source nostr.Querier = nil
|
||||||
|
if c.IsSet("only-missing") {
|
||||||
|
source = target
|
||||||
|
}
|
||||||
|
|
||||||
|
handle := nip77.SyncEventsFromIDs
|
||||||
|
|
||||||
|
if c.Bool("ids-only") {
|
||||||
|
seen := make(map[nostr.ID]struct{}, max(500, filter.Limit))
|
||||||
|
handle = func(ctx context.Context, dir nip77.Direction) {
|
||||||
|
for id := range dir.Items {
|
||||||
|
if _, ok := seen[id]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[id] = struct{}{}
|
||||||
|
stdout(id.Hex())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, url := range relayUrls {
|
||||||
|
err := nip77.NegentropySync(ctx, url, filter, source, target, handle)
|
||||||
|
if err != nil {
|
||||||
|
log("negentropy sync from %s failed: %s", url, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -194,7 +251,7 @@ example:
|
|||||||
mu := sync.Mutex{}
|
mu := sync.Mutex{}
|
||||||
for _, pubkey := range filter.Authors {
|
for _, pubkey := range filter.Authors {
|
||||||
errg.Go(func() error {
|
errg.Go(func() error {
|
||||||
n := int(c.Uint("outbox-relays-number"))
|
n := int(c.Uint("outbox-relays-per-pubkey"))
|
||||||
for _, url := range sys.FetchOutboxRelays(ctx, pubkey, n) {
|
for _, url := range sys.FetchOutboxRelays(ctx, pubkey, n) {
|
||||||
if slices.Contains(relayUrls, url) {
|
if slices.Contains(relayUrls, url) {
|
||||||
// already hardcoded, ignore
|
// already hardcoded, ignore
|
||||||
@@ -276,13 +333,13 @@ var reqFilterFlags = []cli.Flag{
|
|||||||
&PubKeySliceFlag{
|
&PubKeySliceFlag{
|
||||||
Name: "author",
|
Name: "author",
|
||||||
Aliases: []string{"a"},
|
Aliases: []string{"a"},
|
||||||
Usage: "only accept events from these authors (pubkey as hex)",
|
Usage: "only accept events from these authors",
|
||||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
Category: CATEGORY_FILTER_ATTRIBUTES,
|
||||||
},
|
},
|
||||||
&IDSliceFlag{
|
&IDSliceFlag{
|
||||||
Name: "id",
|
Name: "id",
|
||||||
Aliases: []string{"i"},
|
Aliases: []string{"i"},
|
||||||
Usage: "only accept events with these ids (hex)",
|
Usage: "only accept events with these ids",
|
||||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
Category: CATEGORY_FILTER_ATTRIBUTES,
|
||||||
},
|
},
|
||||||
&cli.IntSliceFlag{
|
&cli.IntSliceFlag{
|
||||||
@@ -354,19 +411,19 @@ func applyFlagsToFilter(c *cli.Command, filter *nostr.Filter) error {
|
|||||||
for _, tagFlag := range c.StringSlice("tag") {
|
for _, tagFlag := range c.StringSlice("tag") {
|
||||||
spl := strings.SplitN(tagFlag, "=", 2)
|
spl := strings.SplitN(tagFlag, "=", 2)
|
||||||
if len(spl) == 2 {
|
if len(spl) == 2 {
|
||||||
tags = append(tags, spl)
|
tags = append(tags, []string{spl[0], decodeTagValue(spl[1])})
|
||||||
} else {
|
} else {
|
||||||
return fmt.Errorf("invalid --tag '%s'", tagFlag)
|
return fmt.Errorf("invalid --tag '%s'", tagFlag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, etag := range c.StringSlice("e") {
|
for _, etag := range c.StringSlice("e") {
|
||||||
tags = append(tags, []string{"e", etag})
|
tags = append(tags, []string{"e", decodeTagValue(etag)})
|
||||||
}
|
}
|
||||||
for _, ptag := range c.StringSlice("p") {
|
for _, ptag := range c.StringSlice("p") {
|
||||||
tags = append(tags, []string{"p", ptag})
|
tags = append(tags, []string{"p", decodeTagValue(ptag)})
|
||||||
}
|
}
|
||||||
for _, dtag := range c.StringSlice("d") {
|
for _, dtag := range c.StringSlice("d") {
|
||||||
tags = append(tags, []string{"d", dtag})
|
tags = append(tags, []string{"d", decodeTagValue(dtag)})
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(tags) > 0 && filter.Tags == nil {
|
if len(tags) > 0 && filter.Tags == nil {
|
||||||
@@ -395,3 +452,18 @@ func applyFlagsToFilter(c *cli.Command, filter *nostr.Filter) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PrintingQuerierPublisher struct {
|
||||||
|
nostr.QuerierPublisher
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p PrintingQuerierPublisher) Publish(ctx context.Context, evt nostr.Event) error {
|
||||||
|
if err := p.QuerierPublisher.Publish(ctx, evt); err == nil {
|
||||||
|
stdout(evt)
|
||||||
|
return nil
|
||||||
|
} else if err == eventstore.ErrDupEvent {
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
120
serve.go
120
serve.go
@@ -2,17 +2,24 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"fiatjaf.com/nostr"
|
"fiatjaf.com/nostr"
|
||||||
"fiatjaf.com/nostr/eventstore/slicestore"
|
"fiatjaf.com/nostr/eventstore/slicestore"
|
||||||
"fiatjaf.com/nostr/khatru"
|
"fiatjaf.com/nostr/khatru"
|
||||||
|
"fiatjaf.com/nostr/khatru/blossom"
|
||||||
|
"fiatjaf.com/nostr/khatru/grasp"
|
||||||
"github.com/bep/debounce"
|
"github.com/bep/debounce"
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
|
"github.com/puzpuzpuz/xsync/v3"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -36,10 +43,25 @@ var serve = &cli.Command{
|
|||||||
Usage: "file containing the initial batch of events that will be served by the relay as newline-separated JSON (jsonl)",
|
Usage: "file containing the initial batch of events that will be served by the relay as newline-separated JSON (jsonl)",
|
||||||
DefaultText: "the relay will start empty",
|
DefaultText: "the relay will start empty",
|
||||||
},
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "negentropy",
|
||||||
|
Usage: "enable negentropy syncing",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "grasp",
|
||||||
|
Usage: "enable grasp server",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "blossom",
|
||||||
|
Usage: "enable blossom server",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Action: func(ctx context.Context, c *cli.Command) error {
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
db := &slicestore.SliceStore{}
|
db := &slicestore.SliceStore{}
|
||||||
|
|
||||||
|
var blobStore *xsync.MapOf[string, []byte]
|
||||||
|
var repoDir string
|
||||||
|
|
||||||
var scanner *bufio.Scanner
|
var scanner *bufio.Scanner
|
||||||
if path := c.String("events"); path != "" {
|
if path := c.String("events"); path != "" {
|
||||||
f, err := os.Open(path)
|
f, err := os.Open(path)
|
||||||
@@ -71,7 +93,11 @@ var serve = &cli.Command{
|
|||||||
rl.Info.Software = "https://github.com/fiatjaf/nak"
|
rl.Info.Software = "https://github.com/fiatjaf/nak"
|
||||||
rl.Info.Version = version
|
rl.Info.Version = version
|
||||||
|
|
||||||
rl.UseEventstore(db, 1_000_000)
|
rl.UseEventstore(db, 500)
|
||||||
|
|
||||||
|
if c.Bool("negentropy") {
|
||||||
|
rl.Negentropy = true
|
||||||
|
}
|
||||||
|
|
||||||
started := make(chan bool)
|
started := make(chan bool)
|
||||||
exited := make(chan error)
|
exited := make(chan error)
|
||||||
@@ -79,16 +105,67 @@ var serve = &cli.Command{
|
|||||||
hostname := c.String("hostname")
|
hostname := c.String("hostname")
|
||||||
port := int(c.Uint("port"))
|
port := int(c.Uint("port"))
|
||||||
|
|
||||||
|
var printStatus func()
|
||||||
|
|
||||||
|
if c.Bool("blossom") {
|
||||||
|
bs := blossom.New(rl, fmt.Sprintf("http://%s:%d", hostname, port))
|
||||||
|
bs.Store = blossom.NewMemoryBlobIndex()
|
||||||
|
|
||||||
|
blobStore = xsync.NewMapOf[string, []byte]()
|
||||||
|
bs.StoreBlob = func(ctx context.Context, sha256 string, ext string, body []byte) error {
|
||||||
|
blobStore.Store(sha256+ext, body)
|
||||||
|
log(" got %s %s\n", color.GreenString("blob stored"), sha256+ext)
|
||||||
|
printStatus()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
bs.LoadBlob = func(ctx context.Context, sha256 string, ext string) (io.ReadSeeker, *url.URL, error) {
|
||||||
|
if body, ok := blobStore.Load(sha256 + ext); ok {
|
||||||
|
log(" got %s %s\n", color.BlueString("blob downloaded"), sha256+ext)
|
||||||
|
printStatus()
|
||||||
|
return bytes.NewReader(body), nil, nil
|
||||||
|
}
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
bs.DeleteBlob = func(ctx context.Context, sha256 string, ext string) error {
|
||||||
|
blobStore.Delete(sha256 + ext)
|
||||||
|
log(" got %s %s\n", color.RedString("blob deleted"), sha256+ext)
|
||||||
|
printStatus()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Bool("grasp") {
|
||||||
|
var err error
|
||||||
|
repoDir, err = os.MkdirTemp("", "nak-serve-grasp-repos-")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create grasp repos directory: %w", err)
|
||||||
|
}
|
||||||
|
g := grasp.New(rl, repoDir)
|
||||||
|
g.OnRead = func(ctx context.Context, pubkey nostr.PubKey, repo string) (reject bool, reason string) {
|
||||||
|
log(" got %s %s %s\n", color.CyanString("git read"), pubkey.Hex(), repo)
|
||||||
|
printStatus()
|
||||||
|
return false, ""
|
||||||
|
}
|
||||||
|
g.OnWrite = func(ctx context.Context, pubkey nostr.PubKey, repo string) (reject bool, reason string) {
|
||||||
|
log(" got %s %s %s\n", color.YellowString("git write"), pubkey.Hex(), repo)
|
||||||
|
printStatus()
|
||||||
|
return false, ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
err := rl.Start(hostname, port, started)
|
err := rl.Start(hostname, port, started)
|
||||||
exited <- err
|
exited <- err
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var printStatus func()
|
|
||||||
|
|
||||||
// relay logging
|
// relay logging
|
||||||
rl.OnRequest = 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))
|
negentropy := ""
|
||||||
|
if khatru.IsNegentropySession(ctx) {
|
||||||
|
negentropy = color.HiBlueString("negentropy ")
|
||||||
|
}
|
||||||
|
|
||||||
|
log(" got %s%s %v\n", negentropy, color.HiYellowString("request"), colors.italic(filter))
|
||||||
printStatus()
|
printStatus()
|
||||||
return false, ""
|
return false, ""
|
||||||
}
|
}
|
||||||
@@ -123,9 +200,36 @@ var serve = &cli.Command{
|
|||||||
}
|
}
|
||||||
subs := rl.GetListeningFilters()
|
subs := rl.GetListeningFilters()
|
||||||
|
|
||||||
log(" %s events: %s, connections: %s, subscriptions: %s\n",
|
blossomMsg := ""
|
||||||
|
if c.Bool("blossom") {
|
||||||
|
blobsStored := blobStore.Size()
|
||||||
|
blossomMsg = fmt.Sprintf("blobs: %s, ",
|
||||||
|
color.HiMagentaString("%d", blobsStored),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
graspMsg := ""
|
||||||
|
if c.Bool("grasp") {
|
||||||
|
gitAnnounced := 0
|
||||||
|
gitStored := 0
|
||||||
|
for evt := range db.QueryEvents(nostr.Filter{Kinds: []nostr.Kind{nostr.Kind(30617)}}, 500) {
|
||||||
|
gitAnnounced++
|
||||||
|
identifier := evt.Tags.GetD()
|
||||||
|
if info, err := os.Stat(filepath.Join(repoDir, identifier)); err == nil && info.IsDir() {
|
||||||
|
gitStored++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
graspMsg = fmt.Sprintf("git announced: %s, git stored: %s, ",
|
||||||
|
color.HiMagentaString("%d", gitAnnounced),
|
||||||
|
color.HiMagentaString("%d", gitStored),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
log(" %s events: %s, %s%sconnections: %s, subscriptions: %s\n",
|
||||||
color.HiMagentaString("•"),
|
color.HiMagentaString("•"),
|
||||||
color.HiMagentaString("%d", totalEvents),
|
color.HiMagentaString("%d", totalEvents),
|
||||||
|
blossomMsg,
|
||||||
|
graspMsg,
|
||||||
color.HiMagentaString("%d", totalConnections.Load()),
|
color.HiMagentaString("%d", totalConnections.Load()),
|
||||||
color.HiMagentaString("%d", len(subs)),
|
color.HiMagentaString("%d", len(subs)),
|
||||||
)
|
)
|
||||||
@@ -133,7 +237,11 @@ var serve = &cli.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
<-started
|
<-started
|
||||||
log("%s relay running at %s\n", color.HiRedString(">"), colors.boldf("ws://%s:%d", hostname, port))
|
log("%s relay running at %s", color.HiRedString(">"), colors.boldf("ws://%s:%d", hostname, port))
|
||||||
|
if c.Bool("grasp") {
|
||||||
|
log(" (grasp repos at %s)", repoDir)
|
||||||
|
}
|
||||||
|
log("\n")
|
||||||
|
|
||||||
return <-exited
|
return <-exited
|
||||||
},
|
},
|
||||||
|
|||||||
5
view/.gitignore
vendored
Normal file
5
view/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
dist
|
||||||
|
view
|
||||||
|
*.json
|
||||||
|
deploy
|
||||||
|
qtbox
|
||||||
13
view/Makefile
Normal file
13
view/Makefile
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
dist: deploy/linux/nakv deploy/windows/nakv.exe
|
||||||
|
mkdir -p dist
|
||||||
|
cd deploy/linux && tar -czvf nakv_linux.tar.gz nakv
|
||||||
|
mv deploy/linux/nakv_linux.tar.gz dist/
|
||||||
|
rm -f deploy/windows/nakv_windows.zip
|
||||||
|
cd deploy/windows && zip nakv_windows *.exe
|
||||||
|
mv deploy/windows/nakv_windows.zip dist/
|
||||||
|
|
||||||
|
deploy/linux/nakv: $(shell find . -name "*.go")
|
||||||
|
qtdeploy -ldflags="-s -w" -fast build desktop github.com/fiatjaf/nakv
|
||||||
|
|
||||||
|
deploy/windows/nakv.exe: $(shell find . -name "*.go")
|
||||||
|
qtdeploy -ldflags="-s -w" -docker build windows_64_static
|
||||||
201
view/event.go
Normal file
201
view/event.go
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"fiatjaf.com/nostr"
|
||||||
|
"github.com/therecipe/qt/core"
|
||||||
|
"github.com/therecipe/qt/widgets"
|
||||||
|
)
|
||||||
|
|
||||||
|
var event struct {
|
||||||
|
kindSpin *widgets.QSpinBox
|
||||||
|
kindNameLabel *widgets.QLabel
|
||||||
|
tagRows [][]*widgets.QLineEdit
|
||||||
|
contentEdit *widgets.QTextEdit
|
||||||
|
createdAtEdit *widgets.QDateTimeEdit
|
||||||
|
outputEdit *widgets.QTextEdit
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateEvent() {
|
||||||
|
kind := nostr.Kind(event.kindSpin.Value())
|
||||||
|
kindName := kind.Name()
|
||||||
|
if kindName != "unknown" {
|
||||||
|
event.kindNameLabel.SetText(kindName)
|
||||||
|
} else {
|
||||||
|
event.kindNameLabel.SetText("")
|
||||||
|
}
|
||||||
|
tags := make(nostr.Tags, 0, len(event.tagRows))
|
||||||
|
for y, tagItems := range event.tagRows {
|
||||||
|
if y == len(event.tagRows)-1 && strings.TrimSpace(tagItems[0].Text()) == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
tag := make(nostr.Tag, 0, len(tagItems))
|
||||||
|
for x, edit := range tagItems {
|
||||||
|
text := strings.TrimSpace(edit.Text())
|
||||||
|
if x == len(tagItems)-1 && text == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
text = decodeTagValue(text)
|
||||||
|
tag = append(tag, text)
|
||||||
|
}
|
||||||
|
if len(tag) > 0 {
|
||||||
|
tags = append(tags, tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result := nostr.Event{
|
||||||
|
Kind: kind,
|
||||||
|
Content: event.contentEdit.ToPlainText(),
|
||||||
|
CreatedAt: nostr.Timestamp(event.createdAtEdit.DateTime().ToMSecsSinceEpoch() / 1000),
|
||||||
|
Tags: tags,
|
||||||
|
}
|
||||||
|
|
||||||
|
finalize := func() {
|
||||||
|
jsonBytes, _ := json.MarshalIndent(result, "", " ")
|
||||||
|
event.outputEdit.SetPlainText(string(jsonBytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
if currentKeyer != nil {
|
||||||
|
signAndFinalize := func() {
|
||||||
|
if currentKeyer != nil {
|
||||||
|
if err := currentKeyer.SignEvent(ctx, &result); err == nil {
|
||||||
|
finalize()
|
||||||
|
} else {
|
||||||
|
statusLabel.SetText("failed to sign: " + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if currentSec == [32]byte{} {
|
||||||
|
// empty key, we must have a bunker
|
||||||
|
debounced.Call(signAndFinalize)
|
||||||
|
} else {
|
||||||
|
// we have a key, can sign immediately
|
||||||
|
signAndFinalize()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
finalize()
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupEventTab() *widgets.QWidget {
|
||||||
|
tab := widgets.NewQWidget(nil, 0)
|
||||||
|
|
||||||
|
// set up event tab
|
||||||
|
layout := widgets.NewQVBoxLayout()
|
||||||
|
tab.SetLayout(layout)
|
||||||
|
|
||||||
|
// kind input
|
||||||
|
kindHBox := widgets.NewQHBoxLayout()
|
||||||
|
layout.AddLayout(kindHBox, 0)
|
||||||
|
kindLabel := widgets.NewQLabel2("kind:", nil, 0)
|
||||||
|
kindHBox.AddWidget(kindLabel, 0, 0)
|
||||||
|
event.kindSpin = widgets.NewQSpinBox(nil)
|
||||||
|
event.kindSpin.SetValue(1)
|
||||||
|
event.kindSpin.SetMinimum(0)
|
||||||
|
event.kindSpin.SetMaximum(1<<16 - 1)
|
||||||
|
kindHBox.AddWidget(event.kindSpin, 0, 0)
|
||||||
|
event.kindSpin.ConnectValueChanged(func(int) {
|
||||||
|
updateEvent()
|
||||||
|
})
|
||||||
|
event.kindNameLabel = widgets.NewQLabel2("", nil, 0)
|
||||||
|
kindHBox.AddWidget(event.kindNameLabel, 0, 0)
|
||||||
|
|
||||||
|
// content input
|
||||||
|
contentLabel := widgets.NewQLabel2("content:", nil, 0)
|
||||||
|
layout.AddWidget(contentLabel, 0, 0)
|
||||||
|
event.contentEdit = widgets.NewQTextEdit(nil)
|
||||||
|
layout.AddWidget(event.contentEdit, 0, 0)
|
||||||
|
event.contentEdit.ConnectTextChanged(updateEvent)
|
||||||
|
|
||||||
|
// created_at input
|
||||||
|
createdAtLabel := widgets.NewQLabel2("created at:", nil, 0)
|
||||||
|
layout.AddWidget(createdAtLabel, 0, 0)
|
||||||
|
event.createdAtEdit = widgets.NewQDateTimeEdit(nil)
|
||||||
|
event.createdAtEdit.SetDateTime(core.QDateTime_CurrentDateTime())
|
||||||
|
layout.AddWidget(event.createdAtEdit, 0, 0)
|
||||||
|
event.createdAtEdit.ConnectDateTimeChanged(func(*core.QDateTime) {
|
||||||
|
updateEvent()
|
||||||
|
})
|
||||||
|
|
||||||
|
// tags input
|
||||||
|
tagsLabel := widgets.NewQLabel2("tags:", nil, 0)
|
||||||
|
layout.AddWidget(tagsLabel, 0, 0)
|
||||||
|
tagsLayout := widgets.NewQVBoxLayout()
|
||||||
|
tagRowHBoxes := make([]widgets.QLayout_ITF, 0, 2)
|
||||||
|
event.tagRows = make([][]*widgets.QLineEdit, 0, 2)
|
||||||
|
layout.AddLayout(tagsLayout, 0)
|
||||||
|
|
||||||
|
var addTagRow func()
|
||||||
|
addTagRow = func() {
|
||||||
|
hbox := widgets.NewQHBoxLayout()
|
||||||
|
tagRowHBoxes = append(tagRowHBoxes, hbox)
|
||||||
|
tagsLayout.AddLayout(hbox, 0)
|
||||||
|
tagItems := []*widgets.QLineEdit{}
|
||||||
|
y := len(event.tagRows)
|
||||||
|
event.tagRows = append(event.tagRows, tagItems)
|
||||||
|
|
||||||
|
var addItem func()
|
||||||
|
addItem = func() {
|
||||||
|
edit := widgets.NewQLineEdit(nil)
|
||||||
|
hbox.AddWidget(edit, 0, 0)
|
||||||
|
x := len(tagItems)
|
||||||
|
tagItems = append(tagItems, edit)
|
||||||
|
event.tagRows[y] = tagItems
|
||||||
|
edit.ConnectTextChanged(func(text string) {
|
||||||
|
if strings.TrimSpace(text) != "" {
|
||||||
|
// when an item input has been filled check if we have to show more
|
||||||
|
if y == len(event.tagRows)-1 {
|
||||||
|
addTagRow()
|
||||||
|
}
|
||||||
|
if x == len(tagItems)-1 {
|
||||||
|
addItem()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// do this when an item input has been emptied: check if we need to remove an item from this row
|
||||||
|
nItems := len(tagItems)
|
||||||
|
if nItems >= 2 && strings.TrimSpace(tagItems[nItems-1].Text()) == "" && strings.TrimSpace(tagItems[nItems-2].Text()) == "" {
|
||||||
|
// remove last item if the last 2 are empty
|
||||||
|
hbox.Layout().RemoveWidget(tagItems[nItems-1])
|
||||||
|
tagItems[nItems-1].DeleteLater()
|
||||||
|
tagItems = tagItems[0 : nItems-1]
|
||||||
|
event.tagRows[y] = tagItems
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if we need to remove rows
|
||||||
|
nRows := len(event.tagRows)
|
||||||
|
itemIsFilled := func(edit *widgets.QLineEdit) bool { return strings.TrimSpace(edit.Text()) != "" }
|
||||||
|
if nRows >= 2 && !slices.ContainsFunc(event.tagRows[nRows-1], itemIsFilled) && !slices.ContainsFunc(event.tagRows[nRows-2], itemIsFilled) {
|
||||||
|
// remove the last row if the last 2 are empty
|
||||||
|
tagsLayout.RemoveItem(tagRowHBoxes[nRows-1])
|
||||||
|
for _, tagItem := range event.tagRows[nRows-1] {
|
||||||
|
tagItem.DeleteLater()
|
||||||
|
}
|
||||||
|
tagRowHBoxes[nRows-1].QLayout_PTR().DeleteLater()
|
||||||
|
event.tagRows = event.tagRows[0 : nRows-1]
|
||||||
|
tagRowHBoxes = tagRowHBoxes[0 : nRows-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateEvent()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
addItem()
|
||||||
|
}
|
||||||
|
|
||||||
|
// first
|
||||||
|
addTagRow()
|
||||||
|
|
||||||
|
// output JSON
|
||||||
|
outputLabel := widgets.NewQLabel2("event:", nil, 0)
|
||||||
|
layout.AddWidget(outputLabel, 0, 0)
|
||||||
|
event.outputEdit = widgets.NewQTextEdit(nil)
|
||||||
|
event.outputEdit.SetReadOnly(true)
|
||||||
|
layout.AddWidget(event.outputEdit, 0, 0)
|
||||||
|
|
||||||
|
return tab
|
||||||
|
}
|
||||||
49
view/helpers.go
Normal file
49
view/helpers.go
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"fiatjaf.com/nostr"
|
||||||
|
"fiatjaf.com/nostr/keyer"
|
||||||
|
"fiatjaf.com/nostr/nip19"
|
||||||
|
"fiatjaf.com/nostr/nip46"
|
||||||
|
)
|
||||||
|
|
||||||
|
func handleSecretKeyOrBunker(sec string) (nostr.SecretKey, nostr.Keyer, error) {
|
||||||
|
if strings.HasPrefix(sec, "bunker://") {
|
||||||
|
// it's a bunker
|
||||||
|
bunkerURL := sec
|
||||||
|
clientKey := nostr.Generate()
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
bunker, err := nip46.ConnectBunker(ctx, clientKey, bunkerURL, nil, func(s string) {})
|
||||||
|
if err != nil {
|
||||||
|
return nostr.SecretKey{}, nil, fmt.Errorf("failed to connect to %s: %w", bunkerURL, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nostr.SecretKey{}, keyer.NewBunkerSignerFromBunkerClient(bunker), err
|
||||||
|
}
|
||||||
|
|
||||||
|
if prefix, ski, err := nip19.Decode(sec); err == nil && prefix == "nsec" {
|
||||||
|
sk := ski.(nostr.SecretKey)
|
||||||
|
return sk, keyer.NewPlainKeySigner(sk), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
sk, err := nostr.SecretKeyFromHex(sec)
|
||||||
|
if err != nil {
|
||||||
|
return nostr.SecretKey{}, nil, fmt.Errorf("invalid secret key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sk, keyer.NewPlainKeySigner(sk), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeTagValue(value string) string {
|
||||||
|
if strings.HasPrefix(value, "npub1") || strings.HasPrefix(value, "nevent1") || strings.HasPrefix(value, "note1") || strings.HasPrefix(value, "nprofile1") || strings.HasPrefix(value, "naddr1") {
|
||||||
|
if ptr, err := nip19.ToPointer(value); err == nil {
|
||||||
|
return ptr.AsTagReference()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
139
view/main.go
Normal file
139
view/main.go
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"fiatjaf.com/lib/debouncer"
|
||||||
|
"fiatjaf.com/nostr"
|
||||||
|
"fiatjaf.com/nostr/nip19"
|
||||||
|
"fiatjaf.com/nostr/nip49"
|
||||||
|
"fiatjaf.com/nostr/sdk"
|
||||||
|
"github.com/therecipe/qt/widgets"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
currentSec nostr.SecretKey
|
||||||
|
currentKeyer nostr.Keyer
|
||||||
|
statusLabel *widgets.QLabel
|
||||||
|
debounced = debouncer.New(800 * time.Millisecond)
|
||||||
|
sys = sdk.NewSystem()
|
||||||
|
ctx = context.Background()
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app := widgets.NewQApplication(len(os.Args), os.Args)
|
||||||
|
|
||||||
|
window := widgets.NewQMainWindow(nil, 0)
|
||||||
|
window.SetMinimumSize2(800, 600)
|
||||||
|
window.SetWindowTitle("nakv")
|
||||||
|
|
||||||
|
centralWidget := widgets.NewQWidget(nil, 0)
|
||||||
|
window.SetCentralWidget(centralWidget)
|
||||||
|
|
||||||
|
mainLayout := widgets.NewQVBoxLayout()
|
||||||
|
centralWidget.SetLayout(mainLayout)
|
||||||
|
|
||||||
|
// private key input
|
||||||
|
secLabel := widgets.NewQLabel2("private key (hex or nsec):", nil, 0)
|
||||||
|
mainLayout.AddWidget(secLabel, 0, 0)
|
||||||
|
|
||||||
|
secHBox := widgets.NewQHBoxLayout()
|
||||||
|
mainLayout.AddLayout(secHBox, 0)
|
||||||
|
secEdit := widgets.NewQLineEdit(nil)
|
||||||
|
secHBox.AddWidget(secEdit, 0, 0)
|
||||||
|
generateButton := widgets.NewQPushButton2("generate", nil)
|
||||||
|
secHBox.AddWidget(generateButton, 0, 0)
|
||||||
|
|
||||||
|
// password input
|
||||||
|
passwordHBox := widgets.NewQHBoxLayout()
|
||||||
|
passwordWidget := widgets.NewQWidget(nil, 0)
|
||||||
|
passwordWidget.SetLayout(passwordHBox)
|
||||||
|
passwordWidget.SetVisible(false)
|
||||||
|
mainLayout.AddWidget(passwordWidget, 0, 0)
|
||||||
|
passwordLabel := widgets.NewQLabel2("password:", nil, 0)
|
||||||
|
passwordHBox.AddWidget(passwordLabel, 0, 0)
|
||||||
|
secPasswordEdit := widgets.NewQLineEdit(nil)
|
||||||
|
secPasswordEdit.SetEchoMode(widgets.QLineEdit__Password)
|
||||||
|
passwordHBox.AddWidget(secPasswordEdit, 0, 0)
|
||||||
|
keyChanged := func(text string) {
|
||||||
|
text = strings.TrimSpace(text)
|
||||||
|
|
||||||
|
var sk nostr.SecretKey
|
||||||
|
var keyer nostr.Keyer
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if text == "" {
|
||||||
|
passwordWidget.SetVisible(false)
|
||||||
|
goto empty
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(text, "ncryptsec1") {
|
||||||
|
passwordWidget.SetVisible(true)
|
||||||
|
password := secPasswordEdit.Text()
|
||||||
|
if password != "" {
|
||||||
|
sk, err = nip49.Decrypt(text, password)
|
||||||
|
if err != nil {
|
||||||
|
statusLabel.SetText("decryption failed: " + err.Error())
|
||||||
|
goto empty
|
||||||
|
}
|
||||||
|
text = hex.EncodeToString(sk[:])
|
||||||
|
} else {
|
||||||
|
goto empty
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
passwordWidget.SetVisible(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
sk, keyer, err = handleSecretKeyOrBunker(text)
|
||||||
|
if err != nil {
|
||||||
|
statusLabel.SetText(err.Error())
|
||||||
|
currentSec = nostr.SecretKey{}
|
||||||
|
currentKeyer = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
currentSec = sk
|
||||||
|
currentKeyer = keyer
|
||||||
|
statusLabel.SetText("")
|
||||||
|
updateEvent()
|
||||||
|
return
|
||||||
|
|
||||||
|
empty:
|
||||||
|
currentSec = nostr.SecretKey{}
|
||||||
|
currentKeyer = nil
|
||||||
|
statusLabel.SetText("")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
secEdit.ConnectTextChanged(keyChanged)
|
||||||
|
secPasswordEdit.ConnectTextChanged(keyChanged)
|
||||||
|
generateButton.ConnectClicked(func(bool) {
|
||||||
|
sk := nostr.Generate()
|
||||||
|
nsec := nip19.EncodeNsec(sk)
|
||||||
|
secEdit.SetText(nsec)
|
||||||
|
keyChanged(nsec)
|
||||||
|
})
|
||||||
|
|
||||||
|
tabWidget := widgets.NewQTabWidget(nil)
|
||||||
|
|
||||||
|
eventTab := setupEventTab()
|
||||||
|
reqTab := setupReqTab()
|
||||||
|
|
||||||
|
tabWidget.AddTab(eventTab, "event")
|
||||||
|
tabWidget.AddTab(reqTab, "req")
|
||||||
|
|
||||||
|
mainLayout.AddWidget(tabWidget, 0, 0)
|
||||||
|
|
||||||
|
statusLabel = widgets.NewQLabel2("", nil, 0)
|
||||||
|
mainLayout.AddWidget(statusLabel, 0, 0)
|
||||||
|
|
||||||
|
// initial render
|
||||||
|
updateEvent()
|
||||||
|
updateReq()
|
||||||
|
|
||||||
|
window.Show()
|
||||||
|
app.Exec()
|
||||||
|
}
|
||||||
353
view/req.go
Normal file
353
view/req.go
Normal file
@@ -0,0 +1,353 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"fiatjaf.com/nostr"
|
||||||
|
"github.com/therecipe/qt/core"
|
||||||
|
"github.com/therecipe/qt/widgets"
|
||||||
|
)
|
||||||
|
|
||||||
|
type reqVars struct {
|
||||||
|
authorsEdits []*widgets.QLineEdit
|
||||||
|
idsEdits []*widgets.QLineEdit
|
||||||
|
kindsEdits []*widgets.QLineEdit
|
||||||
|
kindsLabels []*widgets.QLabel
|
||||||
|
relaysEdits []*widgets.QLineEdit
|
||||||
|
sinceEdit *widgets.QDateTimeEdit
|
||||||
|
untilEdit *widgets.QDateTimeEdit
|
||||||
|
limitSpin *widgets.QSpinBox
|
||||||
|
|
||||||
|
filter nostr.Filter
|
||||||
|
|
||||||
|
outputEdit *widgets.QTextEdit
|
||||||
|
resultsEdit *widgets.QTextEdit
|
||||||
|
}
|
||||||
|
|
||||||
|
var req = reqVars{}
|
||||||
|
|
||||||
|
func setupReqTab() *widgets.QWidget {
|
||||||
|
tab := widgets.NewQWidget(nil, 0)
|
||||||
|
layout := widgets.NewQVBoxLayout()
|
||||||
|
tab.SetLayout(layout)
|
||||||
|
|
||||||
|
// authors
|
||||||
|
authorsLabel := widgets.NewQLabel2("authors:", nil, 0)
|
||||||
|
layout.AddWidget(authorsLabel, 0, 0)
|
||||||
|
authorsVBox := widgets.NewQVBoxLayout()
|
||||||
|
layout.AddLayout(authorsVBox, 0)
|
||||||
|
req.authorsEdits = []*widgets.QLineEdit{}
|
||||||
|
var addAuthorEdit func()
|
||||||
|
addAuthorEdit = func() {
|
||||||
|
edit := widgets.NewQLineEdit(nil)
|
||||||
|
req.authorsEdits = append(req.authorsEdits, edit)
|
||||||
|
authorsVBox.AddWidget(edit, 0, 0)
|
||||||
|
edit.ConnectTextChanged(func(text string) {
|
||||||
|
if strings.TrimSpace(text) != "" {
|
||||||
|
if edit == req.authorsEdits[len(req.authorsEdits)-1] {
|
||||||
|
addAuthorEdit()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
n := len(req.authorsEdits)
|
||||||
|
if n >= 2 && strings.TrimSpace(req.authorsEdits[n-1].Text()) == "" && strings.TrimSpace(req.authorsEdits[n-2].Text()) == "" {
|
||||||
|
authorsVBox.Layout().RemoveWidget(req.authorsEdits[n-1])
|
||||||
|
req.authorsEdits[n-1].DeleteLater()
|
||||||
|
req.authorsEdits = req.authorsEdits[0 : n-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateReq()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
addAuthorEdit()
|
||||||
|
|
||||||
|
// ids
|
||||||
|
idsLabel := widgets.NewQLabel2("ids:", nil, 0)
|
||||||
|
layout.AddWidget(idsLabel, 0, 0)
|
||||||
|
idsVBox := widgets.NewQVBoxLayout()
|
||||||
|
layout.AddLayout(idsVBox, 0)
|
||||||
|
req.idsEdits = []*widgets.QLineEdit{}
|
||||||
|
var addIdEdit func()
|
||||||
|
addIdEdit = func() {
|
||||||
|
edit := widgets.NewQLineEdit(nil)
|
||||||
|
req.idsEdits = append(req.idsEdits, edit)
|
||||||
|
idsVBox.AddWidget(edit, 0, 0)
|
||||||
|
edit.ConnectTextChanged(func(text string) {
|
||||||
|
if strings.TrimSpace(text) != "" {
|
||||||
|
if edit == req.idsEdits[len(req.idsEdits)-1] {
|
||||||
|
addIdEdit()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
n := len(req.idsEdits)
|
||||||
|
if n >= 2 && strings.TrimSpace(req.idsEdits[n-1].Text()) == "" && strings.TrimSpace(req.idsEdits[n-2].Text()) == "" {
|
||||||
|
idsVBox.Layout().RemoveWidget(req.idsEdits[n-1])
|
||||||
|
req.idsEdits[n-1].DeleteLater()
|
||||||
|
req.idsEdits = req.idsEdits[0 : n-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateReq()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
addIdEdit()
|
||||||
|
|
||||||
|
// kinds
|
||||||
|
kindsLabel := widgets.NewQLabel2("kinds:", nil, 0)
|
||||||
|
layout.AddWidget(kindsLabel, 0, 0)
|
||||||
|
kindsVBox := widgets.NewQVBoxLayout()
|
||||||
|
layout.AddLayout(kindsVBox, 0)
|
||||||
|
req.kindsEdits = []*widgets.QLineEdit{}
|
||||||
|
req.kindsLabels = []*widgets.QLabel{}
|
||||||
|
var addKindEdit func()
|
||||||
|
addKindEdit = func() {
|
||||||
|
hbox := widgets.NewQHBoxLayout()
|
||||||
|
kindsVBox.AddLayout(hbox, 0)
|
||||||
|
edit := widgets.NewQLineEdit(nil)
|
||||||
|
req.kindsEdits = append(req.kindsEdits, edit)
|
||||||
|
hbox.AddWidget(edit, 0, 0)
|
||||||
|
label := widgets.NewQLabel2("", nil, 0)
|
||||||
|
req.kindsLabels = append(req.kindsLabels, label)
|
||||||
|
hbox.AddWidget(label, 0, 0)
|
||||||
|
edit.ConnectTextChanged(func(text string) {
|
||||||
|
if strings.TrimSpace(text) != "" {
|
||||||
|
if edit == req.kindsEdits[len(req.kindsEdits)-1] {
|
||||||
|
addKindEdit()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
n := len(req.kindsEdits)
|
||||||
|
if n >= 2 && strings.TrimSpace(req.kindsEdits[n-1].Text()) == "" && strings.TrimSpace(req.kindsEdits[n-2].Text()) == "" {
|
||||||
|
lastItem := kindsVBox.ItemAt(kindsVBox.Count() - 1)
|
||||||
|
kindsVBox.RemoveItem(lastItem)
|
||||||
|
lastHBox := lastItem.Layout()
|
||||||
|
lastHBox.RemoveWidget(req.kindsEdits[n-1])
|
||||||
|
lastHBox.RemoveWidget(req.kindsLabels[n-1])
|
||||||
|
req.kindsEdits[n-1].DeleteLater()
|
||||||
|
req.kindsLabels[n-1].DeleteLater()
|
||||||
|
lastHBox.DeleteLater()
|
||||||
|
req.kindsEdits = req.kindsEdits[0 : n-1]
|
||||||
|
req.kindsLabels = req.kindsLabels[0 : n-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateReq()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
addKindEdit()
|
||||||
|
|
||||||
|
// since
|
||||||
|
sinceHBox := widgets.NewQHBoxLayout()
|
||||||
|
layout.AddLayout(sinceHBox, 0)
|
||||||
|
sinceLabel := widgets.NewQLabel2("since:", nil, 0)
|
||||||
|
sinceHBox.AddWidget(sinceLabel, 0, 0)
|
||||||
|
req.sinceEdit = widgets.NewQDateTimeEdit(nil)
|
||||||
|
{
|
||||||
|
time := core.NewQDateTime3(core.NewQDate3(0, 0, 0), core.NewQTime3(0, 0, 0, 0), 0)
|
||||||
|
time.SetMSecsSinceEpoch(0)
|
||||||
|
req.sinceEdit.SetDateTime(time)
|
||||||
|
}
|
||||||
|
sinceHBox.AddWidget(req.sinceEdit, 0, 0)
|
||||||
|
req.sinceEdit.ConnectDateTimeChanged(func(*core.QDateTime) {
|
||||||
|
updateReq()
|
||||||
|
})
|
||||||
|
|
||||||
|
// until
|
||||||
|
untilHBox := widgets.NewQHBoxLayout()
|
||||||
|
layout.AddLayout(untilHBox, 0)
|
||||||
|
untilLabel := widgets.NewQLabel2("until:", nil, 0)
|
||||||
|
untilHBox.AddWidget(untilLabel, 0, 0)
|
||||||
|
req.untilEdit = widgets.NewQDateTimeEdit(nil)
|
||||||
|
{
|
||||||
|
time := core.NewQDateTime3(core.NewQDate3(0, 0, 0), core.NewQTime3(0, 0, 0, 0), 0)
|
||||||
|
time.SetMSecsSinceEpoch(0)
|
||||||
|
req.untilEdit.SetDateTime(time)
|
||||||
|
}
|
||||||
|
untilHBox.AddWidget(req.untilEdit, 0, 0)
|
||||||
|
req.untilEdit.ConnectDateTimeChanged(func(*core.QDateTime) {
|
||||||
|
updateReq()
|
||||||
|
})
|
||||||
|
|
||||||
|
// limit
|
||||||
|
limitHBox := widgets.NewQHBoxLayout()
|
||||||
|
layout.AddLayout(limitHBox, 0)
|
||||||
|
limitLabel := widgets.NewQLabel2("limit:", nil, 0)
|
||||||
|
limitHBox.AddWidget(limitLabel, 0, 0)
|
||||||
|
req.limitSpin = widgets.NewQSpinBox(nil)
|
||||||
|
req.limitSpin.SetMinimum(0)
|
||||||
|
req.limitSpin.SetMaximum(1000)
|
||||||
|
limitHBox.AddWidget(req.limitSpin, 0, 0)
|
||||||
|
req.limitSpin.ConnectValueChanged(func(int) {
|
||||||
|
updateReq()
|
||||||
|
})
|
||||||
|
|
||||||
|
// output
|
||||||
|
outputLabel := widgets.NewQLabel2("filter:", nil, 0)
|
||||||
|
layout.AddWidget(outputLabel, 0, 0)
|
||||||
|
req.outputEdit = widgets.NewQTextEdit(nil)
|
||||||
|
req.outputEdit.SetReadOnly(true)
|
||||||
|
layout.AddWidget(req.outputEdit, 0, 0)
|
||||||
|
|
||||||
|
// relays
|
||||||
|
relaysLabel := widgets.NewQLabel2("relays:", nil, 0)
|
||||||
|
layout.AddWidget(relaysLabel, 0, 0)
|
||||||
|
relaysVBox := widgets.NewQVBoxLayout()
|
||||||
|
layout.AddLayout(relaysVBox, 0)
|
||||||
|
req.relaysEdits = []*widgets.QLineEdit{}
|
||||||
|
var addRelayEdit func()
|
||||||
|
addRelayEdit = func() {
|
||||||
|
edit := widgets.NewQLineEdit(nil)
|
||||||
|
req.relaysEdits = append(req.relaysEdits, edit)
|
||||||
|
relaysVBox.AddWidget(edit, 0, 0)
|
||||||
|
edit.ConnectTextChanged(func(text string) {
|
||||||
|
if strings.TrimSpace(text) != "" {
|
||||||
|
if edit == req.relaysEdits[len(req.relaysEdits)-1] {
|
||||||
|
addRelayEdit()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
n := len(req.relaysEdits)
|
||||||
|
if n >= 2 && strings.TrimSpace(req.relaysEdits[n-1].Text()) == "" && strings.TrimSpace(req.relaysEdits[n-2].Text()) == "" {
|
||||||
|
relaysVBox.Layout().RemoveWidget(req.relaysEdits[n-1])
|
||||||
|
req.relaysEdits[n-1].DeleteLater()
|
||||||
|
req.relaysEdits = req.relaysEdits[0 : n-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
addRelayEdit()
|
||||||
|
|
||||||
|
// send button
|
||||||
|
buttonHBox := widgets.NewQHBoxLayout()
|
||||||
|
layout.AddLayout(buttonHBox, 0)
|
||||||
|
sendButton := widgets.NewQPushButton2("send request", nil)
|
||||||
|
buttonHBox.AddWidget(sendButton, 0, 0)
|
||||||
|
buttonHBox.AddStretch(1)
|
||||||
|
|
||||||
|
// results
|
||||||
|
resultsLabel := widgets.NewQLabel2("results:", nil, 0)
|
||||||
|
layout.AddWidget(resultsLabel, 0, 0)
|
||||||
|
req.resultsEdit = widgets.NewQTextEdit(nil)
|
||||||
|
req.resultsEdit.SetReadOnly(true)
|
||||||
|
layout.AddWidget(req.resultsEdit, 0, 0)
|
||||||
|
|
||||||
|
sendButton.ConnectClicked(func(checked bool) {
|
||||||
|
req.subscribe()
|
||||||
|
})
|
||||||
|
|
||||||
|
return tab
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateReq() {
|
||||||
|
req.filter = nostr.Filter{}
|
||||||
|
|
||||||
|
// collect authors
|
||||||
|
authors := []nostr.PubKey{}
|
||||||
|
for _, edit := range req.authorsEdits {
|
||||||
|
if pk, err := nostr.PubKeyFromHex(strings.TrimSpace(edit.Text())); err == nil {
|
||||||
|
authors = append(authors, pk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(authors) > 0 {
|
||||||
|
req.filter.Authors = authors
|
||||||
|
}
|
||||||
|
|
||||||
|
// collect ids
|
||||||
|
ids := []nostr.ID{}
|
||||||
|
for _, edit := range req.idsEdits {
|
||||||
|
if id, err := nostr.IDFromHex(strings.TrimSpace(edit.Text())); err == nil {
|
||||||
|
ids = append(ids, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(ids) > 0 {
|
||||||
|
req.filter.IDs = ids
|
||||||
|
}
|
||||||
|
|
||||||
|
// collect kinds
|
||||||
|
kinds := []nostr.Kind{}
|
||||||
|
for _, edit := range req.kindsEdits {
|
||||||
|
text := strings.TrimSpace(edit.Text())
|
||||||
|
if k, err := strconv.Atoi(text); err == nil {
|
||||||
|
kinds = append(kinds, nostr.Kind(k))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(kinds) > 0 {
|
||||||
|
req.filter.Kinds = kinds
|
||||||
|
}
|
||||||
|
|
||||||
|
// update kind labels
|
||||||
|
for i, kind := range kinds {
|
||||||
|
if i < len(req.kindsLabels) {
|
||||||
|
name := kind.Name()
|
||||||
|
if name != "unknown" {
|
||||||
|
req.kindsLabels[i].SetText(name)
|
||||||
|
} else {
|
||||||
|
req.kindsLabels[i].SetText("")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := len(kinds); i < len(req.kindsLabels); i++ {
|
||||||
|
req.kindsLabels[i].SetText("")
|
||||||
|
}
|
||||||
|
|
||||||
|
// since
|
||||||
|
if req.sinceEdit.DateTime().IsValid() {
|
||||||
|
ts := nostr.Timestamp(req.sinceEdit.DateTime().ToMSecsSinceEpoch() / 1000)
|
||||||
|
req.filter.Since = ts
|
||||||
|
}
|
||||||
|
|
||||||
|
// until
|
||||||
|
if req.untilEdit.DateTime().IsValid() {
|
||||||
|
ts := nostr.Timestamp(req.untilEdit.DateTime().ToMSecsSinceEpoch() / 1000)
|
||||||
|
req.filter.Until = ts
|
||||||
|
}
|
||||||
|
|
||||||
|
// limit
|
||||||
|
if req.limitSpin.Value() > 0 {
|
||||||
|
req.filter.Limit = req.limitSpin.Value()
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonBytes, _ := json.Marshal(req.filter)
|
||||||
|
req.outputEdit.SetPlainText(string(jsonBytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req reqVars) subscribe() {
|
||||||
|
// collect relays
|
||||||
|
relays := []string{}
|
||||||
|
for _, edit := range req.relaysEdits {
|
||||||
|
url := strings.TrimSpace(edit.Text())
|
||||||
|
if url != "" {
|
||||||
|
relays = append(relays, url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(relays) == 0 {
|
||||||
|
statusLabel.SetText("no relays specified")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// subscribe
|
||||||
|
statusLabel.SetText("subscribed to " + strings.Join(relays, " "))
|
||||||
|
eoseChan := make(chan struct{})
|
||||||
|
eventsChan := sys.Pool.SubscribeManyNotifyEOSE(ctx, relays, req.filter, eoseChan, nostr.SubscriptionOptions{
|
||||||
|
Label: "nakv-req",
|
||||||
|
})
|
||||||
|
|
||||||
|
// collect events
|
||||||
|
go func() {
|
||||||
|
eosed := false
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case ie, ok := <-eventsChan:
|
||||||
|
if !ok {
|
||||||
|
statusLabel.SetText("subscription ended")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonBytes, _ := json.Marshal(ie.Event)
|
||||||
|
if eosed {
|
||||||
|
req.resultsEdit.SetPlainText(string(jsonBytes) + "\n" + req.resultsEdit.ToPlainText())
|
||||||
|
} else {
|
||||||
|
req.resultsEdit.InsertPlainText("\n" + string(jsonBytes))
|
||||||
|
}
|
||||||
|
case <-eoseChan:
|
||||||
|
eosed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user