NAME

Net::Nostr::List - NIP-51 lists and sets

SYNOPSIS

use Net::Nostr::List;
use Net::Nostr::Key;

my $key = Net::Nostr::Key->new;

# Create a mute list (kind 10000) with public and private items
my $mute = Net::Nostr::List->new(kind => 10000);
$mute->add('p', $spammer_pubkey);
$mute->add('t', 'bitcoin');
$mute->add('word', 'gm');
$mute->add_private('p', $secret_mute_pubkey);

my $event = $mute->to_event(pubkey => $key->pubkey_hex, key => $key);
$key->sign_event($event);
$client->publish($event);

# Create a pinned notes list (kind 10001)
my $pins = Net::Nostr::List->new(kind => 10001);
$pins->add('e', $note_id_1);
$pins->add('e', $note_id_2);
my $event = $pins->to_event(pubkey => $key->pubkey_hex);

# Create a bookmark set (kind 30003) with metadata
my $bookmarks = Net::Nostr::List->new(kind => 30003, identifier => 'articles');
$bookmarks->title('Saved Articles');
$bookmarks->image('https://example.com/books.png');
$bookmarks->description('Articles I want to read later');
$bookmarks->add('a', "30023:$author:my-article");
$bookmarks->add('e', $note_id);

my $event = $bookmarks->to_event(pubkey => $key->pubkey_hex);

# Create a relay set (kind 30002)
my $relays = Net::Nostr::List->new(kind => 30002, identifier => 'fast');
$relays->title('Fast Relays');
$relays->add('relay', 'wss://relay1.com');
$relays->add('relay', 'wss://relay2.com');

# Parse a received list event (public items only)
my $list = Net::Nostr::List->from_event($event);
for my $item (@{$list->items}) {
    say join(', ', @$item);
}

# Parse with decryption of private items
my $list = Net::Nostr::List->from_event($event, key => $key);
for my $item (@{$list->private_items}) {
    say "private: " . join(', ', @$item);
}

DESCRIPTION

Implements NIP-51 lists. Lists are events whose tags represent references to things (pubkeys, events, relays, hashtags, etc.). Items can be public (in the event tags) or private (encrypted in the event content using NIP-44).

There are two categories of lists:

Standard lists (kinds 10000-19999)

Replaceable events. A user may only have one list of each kind. Examples: mute list (10000), pinned notes (10001), bookmarks (10003).

Sets (kinds 30000-39999)

Addressable events identified by a d tag. Users can have multiple sets of each kind. Sets can have optional title, image, and description tags. Examples: relay sets (30002), curation sets (30004).

This module is deliberately generic: it accepts any kind and any tag types without enforcing kind-specific rules. NIP-51 defines the list structure, but individual NIPs (e.g. NIP-34 for git-related lists) assign meaning to specific kind numbers and tag types. Validation of which tags are appropriate for a given kind is left to the caller.

CONSTRUCTOR

new

my $list = Net::Nostr::List->new(kind => 10000);
my $set  = Net::Nostr::List->new(kind => 30002, identifier => 'my-relays');

Creates a new empty list. kind is required. identifier sets the d tag value for sets (defaults to empty string). Croaks on unknown arguments.

from_event

my $list = Net::Nostr::List->from_event($event);
my $list = Net::Nostr::List->from_event($event, key => $key);

Parses a list from an existing event. Public items are extracted from the event tags. If a Net::Nostr::Key is provided, private items are decrypted from the event content using NIP-44. Croaks if the content uses deprecated NIP-04 encryption.

my $list = Net::Nostr::List->from_event($mute_event, key => $my_key);
say scalar @{$list->private_items};  # number of privately muted items

METHODS

add

$list->add('p', $pubkey);
$list->add('relay', 'wss://relay.com');
$list->add('p', $pubkey, 'wss://hint.com', 'petname');

Appends a public item to the list. Arguments become a tag array. Returns $self for chaining. New items are appended to the end of the list to preserve chronological order.

$list->add('p', $pk1)->add('p', $pk2)->add('t', 'nostr');

add_private

$list->add_private('p', $pubkey);
$list->add_private('word', 'spam');

Appends a private item to the list. Private items are encrypted in the event content when to_event is called. Returns $self for chaining.

$list->add_private('p', $secret1)->add_private('p', $secret2);

items

my $items = $list->items;  # arrayref of arrayrefs

Returns a copy of the public items. Each item is an arrayref matching the tag structure (e.g. ['p', $pubkey]).

for my $item (@{$list->items}) {
    say "tag: $item->[0], value: $item->[1]";
}

private_items

my $items = $list->private_items;  # arrayref of arrayrefs

Returns a copy of the private items. Empty if no private items exist or if the list was parsed without a decryption key.

my $list = Net::Nostr::List->from_event($event, key => $key);
for my $item (@{$list->private_items}) {
    say "private: $item->[0] = $item->[1]";
}

kind

my $kind = $list->kind;

Returns the event kind for this list.

identifier

my $id = $list->identifier;
$list->identifier('my-set');

Gets or sets the d tag value for sets. Only meaningful for addressable event kinds (30000-39999).

title

my $title = $list->title;
$list->title('My Collection');

Gets or sets the optional title for sets.

my $set = Net::Nostr::List->new(kind => 30004, identifier => 'yaks');
$set->title('Yaks');

image

my $url = $list->image;
$list->image('https://example.com/pic.png');

Gets or sets the optional image URL for sets.

description

my $desc = $list->description;
$list->description('A curated collection of yak-related content');

Gets or sets the optional description for sets.

to_event

my $event = $list->to_event(pubkey => $hex_pubkey);
my $event = $list->to_event(pubkey => $hex_pubkey, key => $key);

Creates a Net::Nostr::Event from the list. Public items become event tags. If private items exist, a Net::Nostr::Key must be provided to encrypt them into the event content using NIP-44. Extra arguments are passed through to the Event constructor.

my $event = $list->to_event(
    pubkey     => $key->pubkey_hex,
    key        => $key,
    created_at => time(),
);
$key->sign_event($event);

SEE ALSO

NIP-51, Net::Nostr, Net::Nostr::Event, Net::Nostr::Encryption