From 2a5ce3b249913b7a59decf890b38e532c33c13e1 Mon Sep 17 00:00:00 2001 From: fiatjaf Date: Sun, 18 Jan 2026 14:47:28 -0300 Subject: [PATCH] blossom mirror to only take a URL and do its thing, not try to list blobs. --- blossom.go | 153 ++++++++++++++--------------------------------------- event.go | 2 +- go.mod | 2 +- go.sum | 2 + publish.go | 2 +- req.go | 2 +- 6 files changed, 45 insertions(+), 118 deletions(-) diff --git a/blossom.go b/blossom.go index 7df0015..264fddb 100644 --- a/blossom.go +++ b/blossom.go @@ -3,15 +3,10 @@ package main import ( "bytes" "context" - "crypto/sha256" - "encoding/base64" - "encoding/hex" "fmt" "io" - "net/http" "os" - "fiatjaf.com/nostr" "fiatjaf.com/nostr/keyer" "fiatjaf.com/nostr/nipb0/blossom" "github.com/urfave/cli/v3" @@ -234,47 +229,56 @@ if any of the files are not found the command will fail, otherwise it will succe }, }, { - Name: "mirror", - Usage: "mirrors blobs from source server to target server", - Description: `lists all blobs from the source server and mirrors them to the target server using BUD-04. requires --sec to sign the authorization event.`, + Name: "mirror", + Usage: "mirrors a from a server to another", + Description: `examples: + mirroring a single blob: + nak blossom mirror https://nostr.download/5672be22e6da91c12b929a0f46b9e74de8b5366b9b19a645ff949c24052f9ad4 -s blossom.band + + mirroring all blobs from a certain pubkey from one server to the other: + nak blossom list 78ce6faa72264387284e647ba6938995735ec8c7d5c5a65737e55130f026307d -s nostr.download | nak blossom mirror -s blossom.band`, DisableSliceFlagSeparator: true, Action: func(ctx context.Context, c *cli.Command) error { - targetClient, err := getBlossomClient(ctx, c) + client, err := getBlossomClient(ctx, c) if err != nil { return err } - // Create client for source server - sourceServer := c.Args().First() - keyer, _, err := gatherKeyerFromArguments(ctx, c) - if err != nil { - return err - } - sourceClient := blossom.NewClient(sourceServer, keyer) - - // Get list of blobs from source server - bds, err := sourceClient.List(ctx) - if err != nil { - return fmt.Errorf("failed to list blobs from source server: %w", err) - } - - // Mirror each blob to target server - hasError := false - for _, bd := range bds { - mirrored, err := mirrorBlob(ctx, targetClient, bd.URL) + var bd blossom.BlobDescriptor + if input := c.Args().First(); input != "" { + blobURL := input + if err := json.Unmarshal([]byte(input), &bd); err == nil { + blobURL = bd.URL + } + bd, err := client.MirrorBlob(ctx, blobURL) if err != nil { - fmt.Fprintf(os.Stderr, "failed to mirror %s: %s\n", bd.SHA256, err) - hasError = true - continue + return err + } + out, _ := json.Marshal(bd) + stdout(out) + return nil + } else { + for input := range getJsonsOrBlank() { + if input == "{}" { + continue + } + + blobURL := input + if err := json.Unmarshal([]byte(input), &bd); err == nil { + blobURL = bd.URL + } + bd, err := client.MirrorBlob(ctx, blobURL) + if err != nil { + ctx = lineProcessingError(ctx, "failed to mirror '%s': %w", blobURL, err) + continue + } + out, _ := json.Marshal(bd) + stdout(out) } - j, _ := json.Marshal(mirrored) - stdout(string(j)) + exitIfLineProcessingError(ctx) } - if hasError { - os.Exit(3) - } return nil }, }, @@ -288,82 +292,3 @@ func getBlossomClient(ctx context.Context, c *cli.Command) (*blossom.Client, err } return blossom.NewClient(c.String("server"), keyer), nil } - -// mirrorBlob mirrors a blob from a URL to the mediaserver using BUD-04 -func mirrorBlob(ctx context.Context, client *blossom.Client, url string) (*blossom.BlobDescriptor, error) { - resp, err := http.Get(url) - if err != nil { - return nil, fmt.Errorf("failed to download blob from URL: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("failed to download blob: HTTP %d", resp.StatusCode) - } - - data, err := io.ReadAll(resp.Body) - if err != nil { - return nil, fmt.Errorf("failed to read blob content: %w", err) - } - - hash := sha256.Sum256(data) - hashHex := hex.EncodeToString(hash[:]) - - signer := client.GetSigner() - pubkey, _ := signer.GetPublicKey(ctx) - - evt := nostr.Event{ - Kind: 24242, - CreatedAt: nostr.Now(), - Tags: nostr.Tags{ - {"t", "upload"}, - {"x", hashHex}, - {"expiration", fmt.Sprintf("%d", nostr.Now()+60)}, - }, - Content: "blossom stuff", - PubKey: pubkey, - } - - if err := signer.SignEvent(ctx, &evt); err != nil { - return nil, fmt.Errorf("failed to sign authorization event: %w", err) - } - - evtj, err := json.Marshal(evt) - if err != nil { - return nil, fmt.Errorf("failed to marshal authorization event: %w", err) - } - auth := base64.StdEncoding.EncodeToString(evtj) - - mediaserver := client.GetMediaServer() - mirrorURL := mediaserver + "mirror" - - requestBody := map[string]string{"url": url} - requestJSON, _ := json.Marshal(requestBody) - - req, err := http.NewRequestWithContext(ctx, "PUT", mirrorURL, bytes.NewReader(requestJSON)) - if err != nil { - return nil, fmt.Errorf("failed to create mirror request: %w", err) - } - - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", "Nostr "+auth) - - httpClient := &http.Client{} - mirrorResp, err := httpClient.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to send mirror request: %w", err) - } - defer mirrorResp.Body.Close() - - if mirrorResp.StatusCode < 200 || mirrorResp.StatusCode >= 300 { - body, _ := io.ReadAll(mirrorResp.Body) - return nil, fmt.Errorf("mirror request failed with HTTP %d: %s", mirrorResp.StatusCode, string(body)) - } - - var bd blossom.BlobDescriptor - if err := json.NewDecoder(mirrorResp.Body).Decode(&bd); err != nil { - return nil, fmt.Errorf("failed to decode blob descriptor: %w", err) - } - - return &bd, nil -} diff --git a/event.go b/event.go index 2a3a2b7..fc4e037 100644 --- a/event.go +++ b/event.go @@ -145,7 +145,7 @@ example: if relayUrls := c.Args().Slice(); len(relayUrls) > 0 { relays = connectToAllRelays(ctx, c, relayUrls, nil, nostr.PoolOptions{ - AuthHandler: func(ctx context.Context, authEvent *nostr.Event) error { + AuthRequiredHandler: func(ctx context.Context, authEvent *nostr.Event) error { return authSigner(ctx, c, func(s string, args ...any) {}, authEvent) }, }, diff --git a/go.mod b/go.mod index a85d675..fa4c31a 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/fiatjaf/nak go 1.25 require ( - fiatjaf.com/nostr v0.0.0-20251230181913-e52ffa631bd6 + fiatjaf.com/nostr v0.0.0-20260118173002-57d595a5b4c7 github.com/AlecAivazis/survey/v2 v2.3.7 github.com/bep/debounce v1.2.1 github.com/btcsuite/btcd/btcec/v2 v2.3.6 diff --git a/go.sum b/go.sum index 0f00285..f699988 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ fiatjaf.com/lib v0.3.2 h1:RBS41z70d8Rp8e2nemQsbPY1NLLnEGShiY2c+Bom3+Q= fiatjaf.com/lib v0.3.2/go.mod h1:UlHaZvPHj25PtKLh9GjZkUHRmQ2xZ8Jkoa4VRaLeeQ8= fiatjaf.com/nostr v0.0.0-20251230181913-e52ffa631bd6 h1:yH+cU9ZNgUdMCRa5eS3pmqTPP/QdZtSmQAIrN/U5nEc= fiatjaf.com/nostr v0.0.0-20251230181913-e52ffa631bd6/go.mod h1:ue7yw0zHfZj23Ml2kVSdBx0ENEaZiuvGxs/8VEN93FU= +fiatjaf.com/nostr v0.0.0-20260118173002-57d595a5b4c7 h1:CkMr8zFLfoOO59+oNlBXXrga00lTKyl2A4fUXAJQ7fY= +fiatjaf.com/nostr v0.0.0-20260118173002-57d595a5b4c7/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= diff --git a/publish.go b/publish.go index e297030..50f36ec 100644 --- a/publish.go +++ b/publish.go @@ -153,7 +153,7 @@ example: relayUrls = nostr.AppendUnique(relayUrls, c.Args().Slice()...) relays := connectToAllRelays(ctx, c, relayUrls, nil, nostr.PoolOptions{ - AuthHandler: func(ctx context.Context, authEvent *nostr.Event) error { + AuthRequiredHandler: func(ctx context.Context, authEvent *nostr.Event) error { return authSigner(ctx, c, func(s string, args ...any) {}, authEvent) }, }, diff --git a/req.go b/req.go index b43efb3..3945e8b 100644 --- a/req.go +++ b/req.go @@ -138,7 +138,7 @@ example: relayUrls, forcePreAuthSigner, nostr.PoolOptions{ - AuthHandler: func(ctx context.Context, authEvent *nostr.Event) error { + AuthRequiredHandler: func(ctx context.Context, authEvent *nostr.Event) error { return authSigner(ctx, c, func(s string, args ...any) { if strings.HasPrefix(s, "authenticating as") { cleanUrl, _ := strings.CutPrefix(