mirror of https://github.com/fiatjaf/nak.git
nak publish
This commit is contained in:
parent
83195d9a00
commit
67e291e80d
6
count.go
6
count.go
|
@ -82,12 +82,6 @@ var count = &cli.Command{
|
||||||
biggerUrlSize = len(relay.URL)
|
biggerUrlSize = len(relay.URL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
|
||||||
for _, relay := range relays {
|
|
||||||
relay.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
filter := nostr.Filter{}
|
filter := nostr.Filter{}
|
||||||
|
|
252
event.go
252
event.go
|
@ -140,10 +140,6 @@ example:
|
||||||
// try to connect to the relays here
|
// try to connect to the relays here
|
||||||
var relays []*nostr.Relay
|
var relays []*nostr.Relay
|
||||||
|
|
||||||
// these are defaults, they will be replaced if we use the magic dynamic thing
|
|
||||||
logthis := func(relayUrl string, s string, args ...any) { log(s, args...) }
|
|
||||||
colorizethis := func(relayUrl string, colorize func(string, ...any) string) {}
|
|
||||||
|
|
||||||
if relayUrls := c.Args().Slice(); len(relayUrls) > 0 {
|
if relayUrls := c.Args().Slice(); len(relayUrls) > 0 {
|
||||||
relays = connectToAllRelays(ctx, c, relayUrls, nil,
|
relays = connectToAllRelays(ctx, c, relayUrls, nil,
|
||||||
nostr.PoolOptions{
|
nostr.PoolOptions{
|
||||||
|
@ -157,19 +153,11 @@ example:
|
||||||
os.Exit(3)
|
os.Exit(3)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
defer func() {
|
|
||||||
for _, relay := range relays {
|
|
||||||
relay.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
kr, sec, err := gatherKeyerFromArguments(ctx, c)
|
kr, sec, err := gatherKeyerFromArguments(ctx, c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
doAuth := c.Bool("auth")
|
|
||||||
|
|
||||||
// then process input and generate events:
|
// then process input and generate events:
|
||||||
|
|
||||||
// will reuse this
|
// will reuse this
|
||||||
|
@ -314,123 +302,7 @@ example:
|
||||||
}
|
}
|
||||||
stdout(result)
|
stdout(result)
|
||||||
|
|
||||||
// publish to relays
|
return publishFlow(ctx, c, kr, evt, relays)
|
||||||
successRelays := make([]string, 0, len(relays))
|
|
||||||
if len(relays) > 0 {
|
|
||||||
os.Stdout.Sync()
|
|
||||||
|
|
||||||
if c.Bool("confirm") {
|
|
||||||
relaysStr := make([]string, len(relays))
|
|
||||||
for i, r := range relays {
|
|
||||||
relaysStr[i] = strings.ToLower(strings.Split(r.URL, "://")[1])
|
|
||||||
}
|
|
||||||
time.Sleep(time.Millisecond * 10)
|
|
||||||
if !askConfirmation("publish to [ " + strings.Join(relaysStr, " ") + " ]? ") {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if supportsDynamicMultilineMagic() {
|
|
||||||
// overcomplicated multiline rendering magic
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
urls := make([]string, len(relays))
|
|
||||||
lines := make([][][]byte, len(urls))
|
|
||||||
flush := func() {
|
|
||||||
for _, line := range lines {
|
|
||||||
for _, part := range line {
|
|
||||||
os.Stderr.Write(part)
|
|
||||||
}
|
|
||||||
os.Stderr.Write([]byte{'\n'})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
render := func() {
|
|
||||||
clearLines(len(lines))
|
|
||||||
flush()
|
|
||||||
}
|
|
||||||
flush()
|
|
||||||
|
|
||||||
logthis = func(relayUrl, s string, args ...any) {
|
|
||||||
idx := slices.Index(urls, relayUrl)
|
|
||||||
lines[idx] = append(lines[idx], []byte(fmt.Sprintf(s, args...)))
|
|
||||||
render()
|
|
||||||
}
|
|
||||||
colorizethis = func(relayUrl string, colorize func(string, ...any) string) {
|
|
||||||
cleanUrl, _ := strings.CutPrefix(relayUrl, "wss://")
|
|
||||||
idx := slices.Index(urls, relayUrl)
|
|
||||||
lines[idx][0] = []byte(fmt.Sprintf("publishing to %s... ", colorize(cleanUrl)))
|
|
||||||
render()
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, relay := range relays {
|
|
||||||
urls[i] = relay.URL
|
|
||||||
lines[i] = make([][]byte, 1, 3)
|
|
||||||
colorizethis(relay.URL, color.CyanString)
|
|
||||||
}
|
|
||||||
render()
|
|
||||||
|
|
||||||
for res := range sys.Pool.PublishMany(ctx, urls, evt) {
|
|
||||||
if res.Error == nil {
|
|
||||||
colorizethis(res.RelayURL, colors.successf)
|
|
||||||
logthis(res.RelayURL, "success.")
|
|
||||||
successRelays = append(successRelays, res.RelayURL)
|
|
||||||
} else {
|
|
||||||
colorizethis(res.RelayURL, colors.errorf)
|
|
||||||
|
|
||||||
// in this case it's likely that the lowest-level error is the one that will be more helpful
|
|
||||||
low := unwrapAll(res.Error)
|
|
||||||
|
|
||||||
// hack for some messages such as from relay.westernbtc.com
|
|
||||||
msg := strings.ReplaceAll(low.Error(), evt.PubKey.Hex(), "author")
|
|
||||||
|
|
||||||
// do not allow the message to overflow the term window
|
|
||||||
msg = clampMessage(msg, 20+len(res.RelayURL))
|
|
||||||
|
|
||||||
logthis(res.RelayURL, msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// normal dumb flow
|
|
||||||
for _, relay := range relays {
|
|
||||||
publish:
|
|
||||||
cleanUrl, _ := strings.CutPrefix(relay.URL, "wss://")
|
|
||||||
log("publishing to %s... ", color.CyanString(cleanUrl))
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
err := relay.Publish(ctx, evt)
|
|
||||||
if err == nil {
|
|
||||||
// published fine
|
|
||||||
log("success.\n")
|
|
||||||
successRelays = append(successRelays, relay.URL)
|
|
||||||
continue // continue to next relay
|
|
||||||
}
|
|
||||||
|
|
||||||
// error publishing
|
|
||||||
if strings.HasPrefix(err.Error(), "msg: auth-required:") && kr != nil && doAuth {
|
|
||||||
// if the relay is requesting auth and we can auth, let's do it
|
|
||||||
pk, _ := kr.GetPublicKey(ctx)
|
|
||||||
npub := nip19.EncodeNpub(pk)
|
|
||||||
log("authenticating as %s... ", color.YellowString("%s…%s", npub[0:7], npub[58:]))
|
|
||||||
if err := relay.Auth(ctx, kr.SignEvent); err == nil {
|
|
||||||
// try to publish again, but this time don't try to auth again
|
|
||||||
doAuth = false
|
|
||||||
goto publish
|
|
||||||
} else {
|
|
||||||
log("auth error: %s. ", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log("failed: %s\n", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(successRelays) > 0 && c.Bool("nevent") {
|
|
||||||
log(nip19.EncodeNevent(evt.ID, successRelays, evt.PubKey) + "\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for stdinEvent := range getJsonsOrBlank() {
|
for stdinEvent := range getJsonsOrBlank() {
|
||||||
|
@ -443,3 +315,125 @@ example:
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func publishFlow(ctx context.Context, c *cli.Command, kr nostr.Signer, evt nostr.Event, relays []*nostr.Relay) error {
|
||||||
|
doAuth := c.Bool("auth")
|
||||||
|
|
||||||
|
// publish to relays
|
||||||
|
successRelays := make([]string, 0, len(relays))
|
||||||
|
if len(relays) > 0 {
|
||||||
|
os.Stdout.Sync()
|
||||||
|
|
||||||
|
if c.Bool("confirm") {
|
||||||
|
relaysStr := make([]string, len(relays))
|
||||||
|
for i, r := range relays {
|
||||||
|
relaysStr[i] = strings.ToLower(strings.Split(r.URL, "://")[1])
|
||||||
|
}
|
||||||
|
time.Sleep(time.Millisecond * 10)
|
||||||
|
if !askConfirmation("publish to [ " + strings.Join(relaysStr, " ") + " ]? ") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if supportsDynamicMultilineMagic() {
|
||||||
|
// overcomplicated multiline rendering magic
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
urls := make([]string, len(relays))
|
||||||
|
lines := make([][][]byte, len(urls))
|
||||||
|
flush := func() {
|
||||||
|
for _, line := range lines {
|
||||||
|
for _, part := range line {
|
||||||
|
os.Stderr.Write(part)
|
||||||
|
}
|
||||||
|
os.Stderr.Write([]byte{'\n'})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
render := func() {
|
||||||
|
clearLines(len(lines))
|
||||||
|
flush()
|
||||||
|
}
|
||||||
|
flush()
|
||||||
|
|
||||||
|
logthis := func(relayUrl, s string, args ...any) {
|
||||||
|
idx := slices.Index(urls, relayUrl)
|
||||||
|
lines[idx] = append(lines[idx], []byte(fmt.Sprintf(s, args...)))
|
||||||
|
render()
|
||||||
|
}
|
||||||
|
colorizethis := func(relayUrl string, colorize func(string, ...any) string) {
|
||||||
|
cleanUrl, _ := strings.CutPrefix(relayUrl, "wss://")
|
||||||
|
idx := slices.Index(urls, relayUrl)
|
||||||
|
lines[idx][0] = []byte(fmt.Sprintf("publishing to %s... ", colorize(cleanUrl)))
|
||||||
|
render()
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, relay := range relays {
|
||||||
|
urls[i] = relay.URL
|
||||||
|
lines[i] = make([][]byte, 1, 3)
|
||||||
|
colorizethis(relay.URL, color.CyanString)
|
||||||
|
}
|
||||||
|
render()
|
||||||
|
|
||||||
|
for res := range sys.Pool.PublishMany(ctx, urls, evt) {
|
||||||
|
if res.Error == nil {
|
||||||
|
colorizethis(res.RelayURL, colors.successf)
|
||||||
|
logthis(res.RelayURL, "success.")
|
||||||
|
successRelays = append(successRelays, res.RelayURL)
|
||||||
|
} else {
|
||||||
|
colorizethis(res.RelayURL, colors.errorf)
|
||||||
|
|
||||||
|
// in this case it's likely that the lowest-level error is the one that will be more helpful
|
||||||
|
low := unwrapAll(res.Error)
|
||||||
|
|
||||||
|
// hack for some messages such as from relay.westernbtc.com
|
||||||
|
msg := strings.ReplaceAll(low.Error(), evt.PubKey.Hex(), "author")
|
||||||
|
|
||||||
|
// do not allow the message to overflow the term window
|
||||||
|
msg = clampMessage(msg, 20+len(res.RelayURL))
|
||||||
|
|
||||||
|
logthis(res.RelayURL, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// normal dumb flow
|
||||||
|
for _, relay := range relays {
|
||||||
|
publish:
|
||||||
|
cleanUrl, _ := strings.CutPrefix(relay.URL, "wss://")
|
||||||
|
log("publishing to %s... ", color.CyanString(cleanUrl))
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
err := relay.Publish(ctx, evt)
|
||||||
|
if err == nil {
|
||||||
|
// published fine
|
||||||
|
log("success.\n")
|
||||||
|
successRelays = append(successRelays, relay.URL)
|
||||||
|
continue // continue to next relay
|
||||||
|
}
|
||||||
|
|
||||||
|
// error publishing
|
||||||
|
if strings.HasPrefix(err.Error(), "msg: auth-required:") && kr != nil && doAuth {
|
||||||
|
// if the relay is requesting auth and we can auth, let's do it
|
||||||
|
pk, _ := kr.GetPublicKey(ctx)
|
||||||
|
npub := nip19.EncodeNpub(pk)
|
||||||
|
log("authenticating as %s... ", color.YellowString("%s…%s", npub[0:7], npub[58:]))
|
||||||
|
if err := relay.Auth(ctx, kr.SignEvent); err == nil {
|
||||||
|
// try to publish again, but this time don't try to auth again
|
||||||
|
doAuth = false
|
||||||
|
goto publish
|
||||||
|
} else {
|
||||||
|
log("auth error: %s. ", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log("failed: %s\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(successRelays) > 0 && c.Bool("nevent") {
|
||||||
|
log(nip19.EncodeNevent(evt.ID, successRelays, evt.PubKey) + "\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
7
fetch.go
7
fetch.go
|
@ -27,13 +27,6 @@ var fetch = &cli.Command{
|
||||||
),
|
),
|
||||||
ArgsUsage: "[nip05_or_nip19_code]",
|
ArgsUsage: "[nip05_or_nip19_code]",
|
||||||
Action: func(ctx context.Context, c *cli.Command) error {
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
defer func() {
|
|
||||||
sys.Pool.Relays.Range(func(_ string, relay *nostr.Relay) bool {
|
|
||||||
relay.Close()
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
}()
|
|
||||||
|
|
||||||
for code := range getStdinLinesOrArguments(c.Args()) {
|
for code := range getStdinLinesOrArguments(c.Args()) {
|
||||||
filter := nostr.Filter{}
|
filter := nostr.Filter{}
|
||||||
var authorHint nostr.PubKey
|
var authorHint nostr.PubKey
|
||||||
|
|
4
go.mod
4
go.mod
|
@ -4,7 +4,6 @@ go 1.24.1
|
||||||
|
|
||||||
require (
|
require (
|
||||||
fiatjaf.com/lib v0.3.1
|
fiatjaf.com/lib v0.3.1
|
||||||
fiatjaf.com/nostr v0.0.1
|
|
||||||
github.com/bep/debounce v1.2.1
|
github.com/bep/debounce v1.2.1
|
||||||
github.com/btcsuite/btcd/btcec/v2 v2.3.4
|
github.com/btcsuite/btcd/btcec/v2 v2.3.4
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e
|
||||||
|
@ -23,6 +22,7 @@ require (
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
fiatjaf.com/nostr v0.0.0-20250506031545-0d99789a54e2 // indirect
|
||||||
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
|
||||||
|
@ -86,5 +86,3 @@ require (
|
||||||
google.golang.org/protobuf v1.36.2 // indirect
|
google.golang.org/protobuf v1.36.2 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
replace fiatjaf.com/nostr => ../nostrlib
|
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -1,6 +1,8 @@
|
||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
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-20250506031545-0d99789a54e2 h1:WDjFQ8hPUAvTDKderZ0NC6vaRBBxODPchKER4wuQdG8=
|
||||||
|
fiatjaf.com/nostr v0.0.0-20250506031545-0d99789a54e2/go.mod h1:VPs38Fc8J1XAErV750CXAmMUqIq3XEX9VZVj/LuQzzM=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
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=
|
||||||
|
|
1
main.go
1
main.go
|
@ -44,6 +44,7 @@ var app = &cli.Command{
|
||||||
mcpServer,
|
mcpServer,
|
||||||
curl,
|
curl,
|
||||||
fsCmd,
|
fsCmd,
|
||||||
|
publish,
|
||||||
},
|
},
|
||||||
Version: version,
|
Version: version,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
|
|
|
@ -0,0 +1,181 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"fiatjaf.com/nostr"
|
||||||
|
"fiatjaf.com/nostr/nip19"
|
||||||
|
"fiatjaf.com/nostr/sdk"
|
||||||
|
"github.com/urfave/cli/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
var publish = &cli.Command{
|
||||||
|
Name: "publish",
|
||||||
|
Usage: "publishes a note with content from stdin",
|
||||||
|
Description: `reads content from stdin and publishes it as a note, optionally as a reply to another note.
|
||||||
|
|
||||||
|
example:
|
||||||
|
echo "hello world" | nak publish
|
||||||
|
echo "I agree!" | nak publish --reply nevent1...
|
||||||
|
echo "tagged post" | nak publish -t t=mytag -t e=someeventid`,
|
||||||
|
DisableSliceFlagSeparator: true,
|
||||||
|
Flags: append(defaultKeyFlags,
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "reply",
|
||||||
|
Usage: "event id, naddr1 or nevent1 code to reply to",
|
||||||
|
},
|
||||||
|
&cli.StringSliceFlag{
|
||||||
|
Name: "tag",
|
||||||
|
Aliases: []string{"t"},
|
||||||
|
Usage: "sets a tag field on the event, takes a value like -t e=<id> or -t sometag=\"value one;value two;value three\"",
|
||||||
|
},
|
||||||
|
&NaturalTimeFlag{
|
||||||
|
Name: "created-at",
|
||||||
|
Aliases: []string{"time", "ts"},
|
||||||
|
Usage: "unix timestamp value for the created_at field",
|
||||||
|
DefaultText: "now",
|
||||||
|
Value: nostr.Now(),
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "auth",
|
||||||
|
Usage: "always perform nip42 \"AUTH\" when facing an \"auth-required: \" rejection and try again",
|
||||||
|
Category: CATEGORY_EXTRAS,
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "nevent",
|
||||||
|
Usage: "print the nevent code (to stderr) after the event is published",
|
||||||
|
Category: CATEGORY_EXTRAS,
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "confirm",
|
||||||
|
Usage: "ask before publishing the event",
|
||||||
|
Category: CATEGORY_EXTRAS,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Action: func(ctx context.Context, c *cli.Command) error {
|
||||||
|
content, err := io.ReadAll(os.Stdin)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to read from stdin: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
evt := nostr.Event{
|
||||||
|
Kind: 1,
|
||||||
|
Content: strings.TrimSpace(string(content)),
|
||||||
|
Tags: make(nostr.Tags, 0, 4),
|
||||||
|
CreatedAt: nostr.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle timestamp flag
|
||||||
|
if c.IsSet("created-at") {
|
||||||
|
evt.CreatedAt = getNaturalDate(c, "created-at")
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle reply flag
|
||||||
|
var replyRelays []string
|
||||||
|
if replyTo := c.String("reply"); replyTo != "" {
|
||||||
|
var replyEvent *nostr.Event
|
||||||
|
|
||||||
|
// try to decode as nevent or naddr first
|
||||||
|
if strings.HasPrefix(replyTo, "nevent1") || strings.HasPrefix(replyTo, "naddr1") {
|
||||||
|
_, value, err := nip19.Decode(replyTo)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid reply target: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch pointer := value.(type) {
|
||||||
|
case nostr.EventPointer:
|
||||||
|
replyEvent, _, err = sys.FetchSpecificEvent(ctx, pointer, sdk.FetchSpecificEventParameters{})
|
||||||
|
case nostr.EntityPointer:
|
||||||
|
replyEvent, _, err = sys.FetchSpecificEvent(ctx, pointer, sdk.FetchSpecificEventParameters{})
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to fetch reply target event: %w", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// try as raw event ID
|
||||||
|
id, err := nostr.IDFromHex(replyTo)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid event id: %w", err)
|
||||||
|
}
|
||||||
|
replyEvent, _, err = sys.FetchSpecificEvent(ctx, nostr.EventPointer{ID: id}, sdk.FetchSpecificEventParameters{})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to fetch reply target event: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if replyEvent.Kind != 1 {
|
||||||
|
evt.Kind = 1111
|
||||||
|
}
|
||||||
|
|
||||||
|
// add reply tags
|
||||||
|
evt.Tags = append(evt.Tags,
|
||||||
|
nostr.Tag{"e", replyEvent.ID.Hex(), "", "reply"},
|
||||||
|
nostr.Tag{"p", replyEvent.PubKey.Hex()},
|
||||||
|
)
|
||||||
|
|
||||||
|
replyRelays = sys.FetchInboxRelays(ctx, replyEvent.PubKey, 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle other tags -- copied from event.go
|
||||||
|
tagFlags := c.StringSlice("tag")
|
||||||
|
for _, tagFlag := range tagFlags {
|
||||||
|
// tags are in the format key=value
|
||||||
|
tagName, tagValue, found := strings.Cut(tagFlag, "=")
|
||||||
|
tag := []string{tagName}
|
||||||
|
if found {
|
||||||
|
// tags may also contain extra elements separated with a ";"
|
||||||
|
tagValues := strings.Split(tagValue, ";")
|
||||||
|
tag = append(tag, tagValues...)
|
||||||
|
}
|
||||||
|
evt.Tags = append(evt.Tags, tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
// process the content
|
||||||
|
targetRelays := sys.PrepareNoteEvent(ctx, &evt)
|
||||||
|
|
||||||
|
// connect to all the relays (like event.go)
|
||||||
|
kr, _, err := gatherKeyerFromArguments(ctx, c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pk, err := kr.GetPublicKey(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get our public key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
relayUrls := sys.FetchWriteRelays(ctx, pk)
|
||||||
|
relayUrls = nostr.AppendUnique(relayUrls, targetRelays...)
|
||||||
|
relayUrls = nostr.AppendUnique(relayUrls, replyRelays...)
|
||||||
|
relayUrls = nostr.AppendUnique(relayUrls, c.Args().Slice()...)
|
||||||
|
relays := connectToAllRelays(ctx, c, relayUrls, nil,
|
||||||
|
nostr.PoolOptions{
|
||||||
|
AuthHandler: func(ctx context.Context, authEvent *nostr.Event) error {
|
||||||
|
return authSigner(ctx, c, func(s string, args ...any) {}, authEvent)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(relays) == 0 {
|
||||||
|
if len(relayUrls) == 0 {
|
||||||
|
return fmt.Errorf("no relays to publish this note to.")
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("failed to connect to any of [ %v ].", relayUrls)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sign the event
|
||||||
|
if err := kr.SignEvent(ctx, &evt); err != nil {
|
||||||
|
return fmt.Errorf("error signing event: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// print
|
||||||
|
stdout(evt.String())
|
||||||
|
|
||||||
|
// publish (like event.go)
|
||||||
|
return publishFlow(ctx, c, kr, evt, relays)
|
||||||
|
},
|
||||||
|
}
|
6
req.go
6
req.go
|
@ -113,12 +113,6 @@ example:
|
||||||
for i, relay := range relays {
|
for i, relay := range relays {
|
||||||
relayUrls[i] = relay.URL
|
relayUrls[i] = relay.URL
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
|
||||||
for _, relay := range relays {
|
|
||||||
relay.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// go line by line from stdin or run once with input from flags
|
// go line by line from stdin or run once with input from flags
|
||||||
|
|
Loading…
Reference in New Issue