From 9547711e8df33d3f4a4c1998870cb9e2312c1a85 Mon Sep 17 00:00:00 2001
From: fiatjaf <fiatjaf@gmail.com>
Date: Thu, 3 Apr 2025 11:42:33 -0300
Subject: [PATCH] nice dynamic UI when connecting to relays, and go much faster
 concurrently.

---
 .gitignore |   1 +
 bunker.go  |   4 +-
 count.go   |   7 +--
 dvm.go     |   2 +-
 event.go   |  14 +++--
 go.mod     |   1 +
 go.sum     |   2 +
 helpers.go | 182 +++++++++++++++++++++++++++++++++++++++++------------
 req.go     |  65 +++++++++++--------
 9 files changed, 199 insertions(+), 79 deletions(-)

diff --git a/.gitignore b/.gitignore
index 9f82d74..5f39b84 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
 nak
 mnt
+nak.exe
diff --git a/bunker.go b/bunker.go
index a1eedba..0787e18 100644
--- a/bunker.go
+++ b/bunker.go
@@ -11,10 +11,10 @@ import (
 	"time"
 
 	"github.com/fatih/color"
-	"github.com/urfave/cli/v3"
 	"github.com/nbd-wtf/go-nostr"
 	"github.com/nbd-wtf/go-nostr/nip19"
 	"github.com/nbd-wtf/go-nostr/nip46"
+	"github.com/urfave/cli/v3"
 )
 
 var bunker = &cli.Command{
@@ -49,7 +49,7 @@ var bunker = &cli.Command{
 		qs := url.Values{}
 		relayURLs := make([]string, 0, c.Args().Len())
 		if relayUrls := c.Args().Slice(); len(relayUrls) > 0 {
-			relays := connectToAllRelays(ctx, relayUrls, false)
+			relays := connectToAllRelays(ctx, relayUrls, nil)
 			if len(relays) == 0 {
 				log("failed to connect to any of the given relays.\n")
 				os.Exit(3)
diff --git a/count.go b/count.go
index 4599c0b..26157b0 100644
--- a/count.go
+++ b/count.go
@@ -6,10 +6,10 @@ import (
 	"os"
 	"strings"
 
-	"github.com/urfave/cli/v3"
 	"github.com/nbd-wtf/go-nostr"
 	"github.com/nbd-wtf/go-nostr/nip45"
 	"github.com/nbd-wtf/go-nostr/nip45/hyperloglog"
+	"github.com/urfave/cli/v3"
 )
 
 var count = &cli.Command{
@@ -70,10 +70,7 @@ var count = &cli.Command{
 		biggerUrlSize := 0
 		relayUrls := c.Args().Slice()
 		if len(relayUrls) > 0 {
-			relays := connectToAllRelays(ctx,
-				relayUrls,
-				false,
-			)
+			relays := connectToAllRelays(ctx, relayUrls, nil)
 			if len(relays) == 0 {
 				log("failed to connect to any of the given relays.\n")
 				os.Exit(3)
diff --git a/dvm.go b/dvm.go
index f4142fc..88ed9cc 100644
--- a/dvm.go
+++ b/dvm.go
@@ -60,7 +60,7 @@ var dvm = &cli.Command{
 				Flags:                     flags,
 				Action: func(ctx context.Context, c *cli.Command) error {
 					relayUrls := c.StringSlice("relay")
-					relays := connectToAllRelays(ctx, relayUrls, false)
+					relays := connectToAllRelays(ctx, relayUrls, nil)
 					if len(relays) == 0 {
 						log("failed to connect to any of the given relays.\n")
 						os.Exit(3)
diff --git a/event.go b/event.go
index dbc2a7c..5bae947 100644
--- a/event.go
+++ b/event.go
@@ -134,7 +134,7 @@ example:
 		// try to connect to the relays here
 		var relays []*nostr.Relay
 		if relayUrls := c.Args().Slice(); len(relayUrls) > 0 {
-			relays = connectToAllRelays(ctx, relayUrls, false)
+			relays = connectToAllRelays(ctx, relayUrls, nil)
 			if len(relays) == 0 {
 				log("failed to connect to any of the given relays.\n")
 				os.Exit(3)
@@ -209,13 +209,19 @@ example:
 			}
 
 			for _, etag := range c.StringSlice("e") {
-				tags = tags.AppendUnique([]string{"e", etag})
+				if tags.FindWithValue("e", etag) == nil {
+					tags = append(tags, nostr.Tag{"e", etag})
+				}
 			}
 			for _, ptag := range c.StringSlice("p") {
-				tags = tags.AppendUnique([]string{"p", ptag})
+				if tags.FindWithValue("p", ptag) == nil {
+					tags = append(tags, nostr.Tag{"p", ptag})
+				}
 			}
 			for _, dtag := range c.StringSlice("d") {
-				tags = tags.AppendUnique([]string{"d", dtag})
+				if tags.FindWithValue("d", dtag) == nil {
+					tags = append(tags, nostr.Tag{"d", dtag})
+				}
 			}
 			if len(tags) > 0 {
 				for _, tag := range tags {
diff --git a/go.mod b/go.mod
index 81cc8a0..4b7b3a6 100644
--- a/go.mod
+++ b/go.mod
@@ -20,6 +20,7 @@ require (
 	github.com/nbd-wtf/go-nostr v0.51.8
 	github.com/urfave/cli/v3 v3.0.0-beta1
 	golang.org/x/exp v0.0.0-20250305212735-054e65f0b394
+	golang.org/x/term v0.30.0
 )
 
 require (
diff --git a/go.sum b/go.sum
index 3c2ec46..9a3a22d 100644
--- a/go.sum
+++ b/go.sum
@@ -247,6 +247,8 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
 golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
+golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
 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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
diff --git a/helpers.go b/helpers.go
index 70b8789..1e4a0db 100644
--- a/helpers.go
+++ b/helpers.go
@@ -10,8 +10,10 @@ import (
 	"net/textproto"
 	"net/url"
 	"os"
+	"runtime"
 	"slices"
 	"strings"
+	"sync"
 	"time"
 
 	"github.com/fatih/color"
@@ -19,6 +21,7 @@ import (
 	"github.com/nbd-wtf/go-nostr"
 	"github.com/nbd-wtf/go-nostr/sdk"
 	"github.com/urfave/cli/v3"
+	"golang.org/x/term"
 )
 
 var sys *sdk.System
@@ -149,7 +152,7 @@ func normalizeAndValidateRelayURLs(wsurls []string) error {
 func connectToAllRelays(
 	ctx context.Context,
 	relayUrls []string,
-	forcePreAuth bool,
+	preAuthSigner func(ctx context.Context, log func(s string, args ...any), authEvent nostr.RelayEvent) (err error), // if this exists we will force preauth
 	opts ...nostr.PoolOption,
 ) []*nostr.Relay {
 	sys.Pool = nostr.NewSimplePool(context.Background(),
@@ -163,52 +166,149 @@ func connectToAllRelays(
 	)
 
 	relays := make([]*nostr.Relay, 0, len(relayUrls))
-relayLoop:
-	for _, url := range relayUrls {
-		log("connecting to %s... ", url)
-		if relay, err := sys.Pool.EnsureRelay(url); err == nil {
-			if forcePreAuth {
-				log("waiting for auth challenge... ")
-				signer := opts[0].(nostr.WithAuthHandler)
-				time.Sleep(time.Millisecond * 200)
-			challengeWaitLoop:
-				for {
-					// beginhack
-					// here starts the biggest and ugliest hack of this codebase
-					if err := relay.Auth(ctx, func(authEvent *nostr.Event) error {
-						challengeTag := authEvent.Tags.Find("challenge")
-						if challengeTag[1] == "" {
-							return fmt.Errorf("auth not received yet *****")
-						}
-						return signer(ctx, nostr.RelayEvent{Event: authEvent, Relay: relay})
-					}); err == nil {
-						// auth succeeded
-						break challengeWaitLoop
-					} else {
-						// auth failed
-						if strings.HasSuffix(err.Error(), "auth not received yet *****") {
-							// it failed because we didn't receive the challenge yet, so keep waiting
-							time.Sleep(time.Second)
-							continue challengeWaitLoop
-						} else {
-							// it failed for some other reason, so skip this relay
-							log(err.Error() + "\n")
-							continue relayLoop
-						}
-					}
-					// endhack
-				}
-			}
 
-			relays = append(relays, relay)
-			log("ok.\n")
-		} else {
-			log(err.Error() + "\n")
+	if supportsDynamicMultilineMagic() {
+		// overcomplicated multiline rendering magic
+		lines := make([][][]byte, len(relayUrls))
+		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()
+
+		wg := sync.WaitGroup{}
+		wg.Add(len(relayUrls))
+		for i, url := range relayUrls {
+			lines[i] = make([][]byte, 1, 2)
+			logthis := func(s string, args ...any) {
+				lines[i] = append(lines[i], []byte(fmt.Sprintf(s, args...)))
+				render()
+			}
+			colorizepreamble := func(c func(string, ...any) string) {
+				lines[i][0] = []byte(fmt.Sprintf("%s... ", c(url)))
+			}
+			colorizepreamble(color.CyanString)
+
+			go func() {
+				relay := connectToSingleRelay(ctx, url, preAuthSigner, colorizepreamble, logthis)
+				if relay != nil {
+					relays = append(relays, relay)
+				}
+				wg.Done()
+			}()
+		}
+		wg.Wait()
+	} else {
+		// simple flow
+		for _, url := range relayUrls {
+			log("connecting to %s... ", url)
+			relay := connectToSingleRelay(ctx, url, preAuthSigner, nil, log)
+			if relay != nil {
+				relays = append(relays, relay)
+			}
+			log("\n")
 		}
 	}
+
 	return relays
 }
 
+func connectToSingleRelay(
+	ctx context.Context,
+	url string,
+	preAuthSigner func(ctx context.Context, log func(s string, args ...any), authEvent nostr.RelayEvent) (err error),
+	colorizepreamble func(c func(string, ...any) string),
+	logthis func(s string, args ...any),
+) *nostr.Relay {
+	if relay, err := sys.Pool.EnsureRelay(url); err == nil {
+		if preAuthSigner != nil {
+			if colorizepreamble != nil {
+				colorizepreamble(color.YellowString)
+			}
+			logthis("waiting for auth challenge... ")
+			time.Sleep(time.Millisecond * 200)
+
+			for range 5 {
+				if err := relay.Auth(ctx, func(authEvent *nostr.Event) error {
+					challengeTag := authEvent.Tags.Find("challenge")
+					if challengeTag[1] == "" {
+						return fmt.Errorf("auth not received yet *****") // what a giant hack
+					}
+					return preAuthSigner(ctx, logthis, nostr.RelayEvent{Event: authEvent, Relay: relay})
+				}); err == nil {
+					// auth succeeded
+					goto preauthSuccess
+				} else {
+					// auth failed
+					if strings.HasSuffix(err.Error(), "auth not received yet *****") {
+						// it failed because we didn't receive the challenge yet, so keep waiting
+						time.Sleep(time.Second)
+						continue
+					} else {
+						// it failed for some other reason, so skip this relay
+						if colorizepreamble != nil {
+							colorizepreamble(color.RedString)
+						}
+						logthis(err.Error())
+						return nil
+					}
+				}
+			}
+			if colorizepreamble != nil {
+				colorizepreamble(color.RedString)
+			}
+			logthis("failed to get an AUTH challenge in enough time.")
+			return nil
+		}
+
+	preauthSuccess:
+		if colorizepreamble != nil {
+			colorizepreamble(color.GreenString)
+		}
+		logthis("ok.")
+		return relay
+	} else {
+		if colorizepreamble != nil {
+			colorizepreamble(color.RedString)
+		}
+		logthis(err.Error())
+		return nil
+	}
+}
+
+func clearLines(lineCount int) {
+	for i := 0; i < lineCount; i++ {
+		os.Stderr.Write([]byte("\033[0A\033[2K\r"))
+	}
+}
+
+func supportsDynamicMultilineMagic() bool {
+	if runtime.GOOS == "windows" {
+		return false
+	}
+	if !term.IsTerminal(0) {
+		return false
+	}
+
+	width, _, err := term.GetSize(0)
+	if err != nil {
+		return false
+	}
+	if width < 110 {
+		return false
+	}
+
+	return true
+}
+
 func lineProcessingError(ctx context.Context, msg string, args ...any) context.Context {
 	log(msg+"\n", args...)
 	return context.WithValue(ctx, LINE_PROCESSING_ERROR, true)
diff --git a/req.go b/req.go
index 854f1d7..559088e 100644
--- a/req.go
+++ b/req.go
@@ -8,6 +8,7 @@ import (
 
 	"github.com/mailru/easyjson"
 	"github.com/nbd-wtf/go-nostr"
+	"github.com/nbd-wtf/go-nostr/nip19"
 	"github.com/nbd-wtf/go-nostr/nip77"
 	"github.com/urfave/cli/v3"
 )
@@ -76,35 +77,46 @@ example:
 	Action: func(ctx context.Context, c *cli.Command) error {
 		relayUrls := c.Args().Slice()
 		if len(relayUrls) > 0 {
+			// this is used both for the normal AUTH (after "auth-required:" is received) or forced pre-auth
+			authSigner := func(ctx context.Context, log func(s string, args ...any), authEvent nostr.RelayEvent) (err error) {
+				defer func() {
+					if err != nil {
+						log("auth to %s failed: %s",
+							authEvent.Tags.Find("relay")[1],
+							err,
+						)
+					}
+				}()
+
+				if !c.Bool("auth") && !c.Bool("force-pre-auth") {
+					return fmt.Errorf("auth not authorized")
+				}
+				kr, _, err := gatherKeyerFromArguments(ctx, c)
+				if err != nil {
+					return err
+				}
+
+				pk, _ := kr.GetPublicKey(ctx)
+				npub, _ := nip19.EncodePublicKey(pk)
+				log("performing auth as %s…%s... ", npub[0:7], npub[58:])
+
+				return kr.SignEvent(ctx, authEvent.Event)
+			}
+
+			// connect to all relays we expect to use in this call in parallel
+			forcePreAuthSigner := authSigner
+			if !c.Bool("force-pre-auth") {
+				forcePreAuthSigner = nil
+			}
 			relays := connectToAllRelays(ctx,
 				relayUrls,
-				c.Bool("force-pre-auth"),
-				nostr.WithAuthHandler(
-					func(ctx context.Context, authEvent nostr.RelayEvent) (err error) {
-						defer func() {
-							if err != nil {
-								log("auth to %s failed: %s\n",
-									authEvent.Tags.Find("relay")[1],
-									err,
-								)
-							}
-						}()
-
-						if !c.Bool("auth") && !c.Bool("force-pre-auth") {
-							return fmt.Errorf("auth not authorized")
-						}
-						kr, _, err := gatherKeyerFromArguments(ctx, c)
-						if err != nil {
-							return err
-						}
-
-						pk, _ := kr.GetPublicKey(ctx)
-						log("performing auth as %s... ", pk)
-
-						return kr.SignEvent(ctx, authEvent.Event)
-					},
-				),
+				forcePreAuthSigner,
+				nostr.WithAuthHandler(func(ctx context.Context, authEvent nostr.RelayEvent) error {
+					return authSigner(ctx, func(s string, args ...any) { log(s+"\n", args...) }, authEvent)
+				}),
 			)
+
+			// stop here already if all connections failed
 			if len(relays) == 0 {
 				log("failed to connect to any of the given relays.\n")
 				os.Exit(3)
@@ -121,6 +133,7 @@ example:
 			}()
 		}
 
+		// go line by line from stdin or run once with input from flags
 		for stdinFilter := range getJsonsOrBlank() {
 			filter := nostr.Filter{}
 			if stdinFilter != "" {