NAME
Net::Nostr::Group - NIP-29 relay-based groups
SYNOPSIS
use Net::Nostr::Group;
use Net::Nostr::Key;
my $key = Net::Nostr::Key->new;
# Parse a group identifier
my $parsed = Net::Nostr::Group->parse_id("groups.nostr.com'pizza");
# { host => 'groups.nostr.com', group_id => 'pizza' }
# Format a group identifier
my $id = Net::Nostr::Group->format_id(
host => 'groups.nostr.com',
group_id => 'pizza',
);
# "groups.nostr.com'pizza"
# Validate a group_id
Net::Nostr::Group->validate_group_id('my-group_1'); # 1
Net::Nostr::Group->validate_group_id('INVALID'); # 0
# Join a group (kind 9021)
my $join = Net::Nostr::Group->join_request(
pubkey => $key->pubkey_hex,
group_id => 'pizza',
reason => 'I love pizza',
code => 'invite-abc', # optional invite code
);
$key->sign_event($join);
$client->publish($join);
# Leave a group (kind 9022)
my $leave = Net::Nostr::Group->leave_request(
pubkey => $key->pubkey_hex,
group_id => 'pizza',
);
# Add a user to a group (kind 9000, admin)
my $put = Net::Nostr::Group->put_user(
pubkey => $key->pubkey_hex,
group_id => 'pizza',
target => $user_pubkey,
roles => ['moderator'],
reason => 'promoted',
);
# Remove a user (kind 9001, admin)
my $rm = Net::Nostr::Group->remove_user(
pubkey => $key->pubkey_hex,
group_id => 'pizza',
target => $user_pubkey,
reason => 'spamming',
);
# Edit group metadata (kind 9002, admin)
my $edit = Net::Nostr::Group->edit_metadata(
pubkey => $key->pubkey_hex,
group_id => 'pizza',
name => 'Pizza Lovers',
about => 'We love pizza',
private => 1,
closed => 1,
);
# Delete an event from the group (kind 9005, admin)
my $del = Net::Nostr::Group->delete_event(
pubkey => $key->pubkey_hex,
group_id => 'pizza',
event_id => $spam_event_id,
);
# Create a group (kind 9007)
my $create = Net::Nostr::Group->create_group(
pubkey => $key->pubkey_hex,
group_id => 'new-group',
);
# Delete a group (kind 9008)
my $delete = Net::Nostr::Group->delete_group(
pubkey => $key->pubkey_hex,
group_id => 'old-group',
);
# Create an invite code (kind 9009)
my $invite = Net::Nostr::Group->create_invite(
pubkey => $key->pubkey_hex,
group_id => 'pizza',
code => 'secret-code-123',
);
# Generate group metadata (kind 39000, relay-generated)
my $meta = Net::Nostr::Group->metadata(
pubkey => $relay_pubkey,
group_id => 'pizza',
name => 'Pizza Lovers',
picture => 'https://pizza.com/pizza.png',
about => 'a group for pizza fans',
private => 1,
closed => 1,
);
# Generate admin list (kind 39001, relay-generated)
my $admin_event = Net::Nostr::Group->admins(
pubkey => $relay_pubkey,
group_id => 'pizza',
members => [
{ pubkey => $admin_pk, roles => ['admin'] },
{ pubkey => $mod_pk, roles => ['moderator'] },
],
);
# Generate member list (kind 39002, relay-generated)
my $member_event = Net::Nostr::Group->members(
pubkey => $relay_pubkey,
group_id => 'pizza',
members => [$pk1, $pk2, $pk3],
);
# Generate roles list (kind 39003, relay-generated)
my $role_event = Net::Nostr::Group->roles(
pubkey => $relay_pubkey,
group_id => 'pizza',
roles => [
{ name => 'admin', description => 'full control' },
{ name => 'moderator', description => 'can delete messages' },
],
);
# Parse received events
my $meta_info = Net::Nostr::Group->metadata_from_event($event);
my $admin_info = Net::Nostr::Group->admins_from_event($event);
my $member_info = Net::Nostr::Group->members_from_event($event);
my $role_info = Net::Nostr::Group->roles_from_event($event);
my $gid = Net::Nostr::Group->group_id_from_event($event);
DESCRIPTION
Implements NIP-29 relay-based groups. Groups are identified by a string restricted to a-z0-9-_ and belong to a specific relay. Group state is managed through moderation events (kinds 9000-9009) and user events (kinds 9021-9022). Group metadata is published as addressable events (kinds 39000-39003) signed by the relay.
All user and moderation events MUST include an h tag with the group id. Group metadata events use a d tag instead.
To store a user's list of groups, use a kind 10009 Net::Nostr::List with group and r tags per NIP-51:
use Net::Nostr::List;
my $groups = Net::Nostr::List->new(kind => 10009);
$groups->add('group', "groups.nostr.com'pizza", 'wss://groups.nostr.com', 'Pizza Lovers');
$groups->add('r', 'wss://groups.nostr.com');
my $event = $groups->to_event(pubkey => $key->pubkey_hex);
CLASS METHODS
parse_id
my $parsed = Net::Nostr::Group->parse_id("groups.nostr.com'pizza");
# { host => 'groups.nostr.com', group_id => 'pizza' }
my $parsed = Net::Nostr::Group->parse_id("groups.nostr.com");
# { host => 'groups.nostr.com', group_id => '_' }
Parses a group identifier string in <host>'<group-id> format. If only a host is given (no '), infers _ as the group id per NIP-29. Croaks if the group id contains invalid characters.
format_id
my $id = Net::Nostr::Group->format_id(
host => 'groups.nostr.com',
group_id => 'pizza',
);
# "groups.nostr.com'pizza"
Formats a group identifier string from host and group_id components.
validate_group_id
Net::Nostr::Group->validate_group_id('my-group'); # 1
Net::Nostr::Group->validate_group_id('NOPE'); # 0
Returns true if the group id matches the allowed character set a-z0-9-_.
put_user
my $event = Net::Nostr::Group->put_user(
pubkey => $hex_pubkey,
group_id => 'pizza',
target => $user_pubkey,
roles => ['admin', 'moderator'], # optional
reason => 'promoted', # optional
previous => ['abcd1234'], # optional timeline refs
);
Creates a kind 9000 moderation event to add a user to the group or update their roles. The p tag contains the target pubkey followed by any role strings.
remove_user
my $event = Net::Nostr::Group->remove_user(
pubkey => $hex_pubkey,
group_id => 'pizza',
target => $user_pubkey,
reason => 'spamming', # optional
);
Creates a kind 9001 moderation event to remove a user from the group.
All user and moderation event builders (put_user, remove_user, edit_metadata, delete_event, create_group, delete_group, create_invite, join_request, leave_request) accept an optional previous parameter for timeline references. See put_user for an example.
edit_metadata
my $event = Net::Nostr::Group->edit_metadata(
pubkey => $hex_pubkey,
group_id => 'pizza',
name => 'Pizza Lovers', # optional
picture => 'https://pic.url', # optional
about => 'description', # optional
private => 1, # optional flag
restricted => 1, # optional flag
hidden => 1, # optional flag
closed => 1, # optional flag
unrestricted => 1, # optional flag
open => 1, # optional flag
visible => 1, # optional flag
public => 1, # optional flag
);
Creates a kind 9002 moderation event to update group metadata. Metadata fields become tags. Boolean flags become single-element tags when true.
delete_event
my $event = Net::Nostr::Group->delete_event(
pubkey => $hex_pubkey,
group_id => 'pizza',
event_id => $event_id_hex,
reason => 'spam content', # optional
);
Creates a kind 9005 moderation event to delete an event from the group.
create_group
my $event = Net::Nostr::Group->create_group(
pubkey => $hex_pubkey,
group_id => 'new-group',
);
Creates a kind 9007 event requesting the relay to create a new group.
delete_group
my $event = Net::Nostr::Group->delete_group(
pubkey => $hex_pubkey,
group_id => 'old-group',
reason => 'inactive', # optional
);
Creates a kind 9008 event requesting the relay to delete a group.
create_invite
my $event = Net::Nostr::Group->create_invite(
pubkey => $hex_pubkey,
group_id => 'pizza',
code => 'secret-code-123',
);
Creates a kind 9009 event with an invite code for the group.
join_request
my $event = Net::Nostr::Group->join_request(
pubkey => $hex_pubkey,
group_id => 'pizza',
reason => 'I love pizza', # optional
code => 'invite-abc', # optional invite code
);
Creates a kind 9021 join request event. The optional code tag can be used with invite codes created by create_invite.
leave_request
my $event = Net::Nostr::Group->leave_request(
pubkey => $hex_pubkey,
group_id => 'pizza',
reason => 'moving on', # optional
);
Creates a kind 9022 leave request event.
metadata
my $event = Net::Nostr::Group->metadata(
pubkey => $relay_pubkey,
group_id => 'pizza',
name => 'Pizza Lovers',
picture => 'https://pizza.com/pizza.png',
about => 'a group for pizza fans',
private => 1, # only members can read
restricted => 1, # only members can write
hidden => 1, # hide metadata from non-members
closed => 1, # ignore join requests
);
Creates a kind 39000 addressable event describing group metadata. This event should be signed by the relay's master key. Uses a d tag (not h) with the group id.
admins
my $event = Net::Nostr::Group->admins(
pubkey => $relay_pubkey,
group_id => 'pizza',
content => 'admin list', # optional
members => [
{ pubkey => $pk, roles => ['admin'] },
],
);
Creates a kind 39001 addressable event listing group admins with roles.
members
my $event = Net::Nostr::Group->members(
pubkey => $relay_pubkey,
group_id => 'pizza',
content => 'member list', # optional
members => [$pk1, $pk2],
);
Creates a kind 39002 addressable event listing group members.
roles
my $event = Net::Nostr::Group->roles(
pubkey => $relay_pubkey,
group_id => 'pizza',
content => 'role definitions', # optional
roles => [
{ name => 'admin', description => 'full control' },
{ name => 'moderator' },
],
);
Creates a kind 39003 addressable event listing supported roles.
metadata_from_event
my $meta = Net::Nostr::Group->metadata_from_event($event);
# { group_id => '...', name => '...', picture => '...',
# about => '...', private => 1, closed => 1 }
Parses a kind 39000 event. Returns a hashref with group metadata. Boolean flags (private, restricted, hidden, closed) are set to 1 when present. Croaks if the event is not kind 39000.
admins_from_event
my $result = Net::Nostr::Group->admins_from_event($event);
# { group_id => '...', admins => [{ pubkey => '...', roles => [...] }] }
Parses a kind 39001 event. Returns a hashref with group_id and an arrayref of admin entries. Croaks if the event is not kind 39001.
for my $admin (@{$result->{admins}}) {
say "$admin->{pubkey}: " . join(', ', @{$admin->{roles}});
}
members_from_event
my $result = Net::Nostr::Group->members_from_event($event);
# { group_id => '...', members => [$pk1, $pk2] }
Parses a kind 39002 event. Returns a hashref with group_id and an arrayref of member pubkeys. Croaks if the event is not kind 39002.
roles_from_event
my $result = Net::Nostr::Group->roles_from_event($event);
# { group_id => '...', roles => [{ name => '...', description => '...' }] }
Parses a kind 39003 event. Returns a hashref with group_id and an arrayref of role definitions. Croaks if the event is not kind 39003.
for my $role (@{$result->{roles}}) {
say "$role->{name}: $role->{description}";
}
group_id_from_event
my $gid = Net::Nostr::Group->group_id_from_event($event);
Extracts the group id from an event's h tag (user/moderation events) or d tag (metadata events). If both tags are present, the h tag takes priority. Returns undef if neither is found.
my $gid = Net::Nostr::Group->group_id_from_event($join_event);
say "Group: $gid";
SEE ALSO
NIP-29, Net::Nostr, Net::Nostr::Event, Net::Nostr::List (kind 10009 group storage)