NAME
Net::Nostr::Blossom - NIP-B7 Blossom media server lists
SYNOPSIS
use Net::Nostr::Blossom;
# Build a Blossom server list
my $bl = Net::Nostr::Blossom->new;
$bl->add('https://blossom.self.hosted');
$bl->add('https://cdn.blossom.cloud');
# Convert to a kind 10063 event for publishing
my $event = $bl->to_event(pubkey => $key->pubkey_hex);
$key->sign_event($event);
$client->publish($event);
# Parse from a received kind 10063 event
my $bl = Net::Nostr::Blossom->from_event($event);
for my $url ($bl->servers) {
say $url;
}
# Extract SHA-256 hash from a Blossom URL
my $url = "https://old-server.com/${\('a' x 64)}.png";
my ($hash, $ext) = Net::Nostr::Blossom->extract_hash($url);
# $hash = 'aaa...aaa' (64 hex chars), $ext = 'png'
# Generate alternative URLs from other Blossom servers
my @urls = Net::Nostr::Blossom->resolve_urls(
"https://dead-server.com/${\('a' x 64)}.png",
['https://blossom.self.hosted', 'https://cdn.blossom.cloud'],
);
# Verify downloaded content matches expected SHA-256
use Digest::SHA qw(sha256_hex);
my $data = 'file contents';
my $expected_hash = sha256_hex($data);
Net::Nostr::Blossom->verify_sha256($data, $expected_hash); # true
DESCRIPTION
Implements NIP-B7 Blossom media integration. Blossom is a set of standards for dealing with servers that store files addressable by their SHA-256 hashes.
Nostr clients SHOULD fetch kind:10063 lists of Blossom servers for each user. When a URL in an event ends with a 64-character hex string (with or without a file extension) and is no longer available, clients SHOULD look up the user's kind:10063 server list and try the same hash on alternative servers.
When downloading files, clients SHOULD verify that the SHA-256 hash of the content matches the 64-character hex string in the URL.
CONSTRUCTOR
new
my $bl = Net::Nostr::Blossom->new;
Creates an empty Blossom server list. Croaks on unknown arguments.
from_event
my $bl = Net::Nostr::Blossom->from_event($event);
Parses a kind 10063 event into a Blossom server list. Extracts all server tags and ignores other tag types. Croaks if the event is not kind 10063.
my $event = Net::Nostr::Event->new(
pubkey => 'a' x 64, kind => 10063, content => '',
tags => [['server', 'https://blossom.example.com']],
);
my $bl = Net::Nostr::Blossom->from_event($event);
say $bl->count; # 1
METHODS
add
$bl->add($url);
Adds a Blossom server URL. If the URL already exists, it is not duplicated. Returns $self for chaining.
$bl->add('https://server1.com')
->add('https://server2.com');
remove
$bl->remove($url);
Removes the server with the given URL. No-op if not present. Returns $self for chaining.
contains
my $bool = $bl->contains($url);
Returns true if the given server URL is in the list.
$bl->add('https://blossom.example.com');
say $bl->contains('https://blossom.example.com'); # 1
say $bl->contains('https://other.com'); # 0
count
my $n = $bl->count;
Returns the number of servers in the list.
servers
my @urls = $bl->servers;
Returns the list of server URLs in the order they were added.
to_tags
my $tags = $bl->to_tags;
# [['server', 'https://blossom.self.hosted'], ['server', 'https://cdn.blossom.cloud']]
Returns the server list as an arrayref of tag arrays.
to_event
my $event = $bl->to_event(pubkey => $pubkey_hex);
my $event = $bl->to_event(pubkey => $pubkey_hex, created_at => time());
Creates a kind 10063 Net::Nostr::Event from the server list. All extra arguments are passed through to Net::Nostr::Event->new. The kind, content, and tags fields are set automatically.
extract_hash
my ($hash, $ext) = Net::Nostr::Blossom->extract_hash($url);
Extracts a 64-character hex string (SHA-256 hash) from a URL. Returns the hash and optional file extension, or (undef, undef) if the URL does not contain a recognizable Blossom hash.
my ($h, $ext) = Net::Nostr::Blossom->extract_hash(
'https://server.com/abc123...def.png'
);
# $h = 'abc123...def' (64 hex chars)
# $ext = 'png'
resolve_urls
my @urls = Net::Nostr::Blossom->resolve_urls($url, \@servers);
Given a URL containing a SHA-256 hash and a list of Blossom server URLs, generates alternative URLs by combining each server with the hash (and optional file extension). Returns an empty list if the URL does not contain a recognizable hash.
my @urls = Net::Nostr::Blossom->resolve_urls(
"https://unavailable.com/${\ ('b' x 64)}.jpg",
['https://blossom.self.hosted', 'https://cdn.blossom.cloud'],
);
# ('https://blossom.self.hosted/bbb...bbb.jpg',
# 'https://cdn.blossom.cloud/bbb...bbb.jpg')
verify_sha256
my $ok = Net::Nostr::Blossom->verify_sha256($data, $expected_hash);
Verifies that the SHA-256 hash of $data matches $expected_hash. Returns true on match, false otherwise. Clients SHOULD call this after downloading files from Blossom servers.
my $data = 'file contents';
my $hash = Digest::SHA::sha256_hex($data);
Net::Nostr::Blossom->verify_sha256($data, $hash); # true
Net::Nostr::Blossom->verify_sha256('tampered', $hash); # false