NAME

Net::Nostr::Channel - NIP-28 public chat channels

SYNOPSIS

use Net::Nostr::Channel;
use Net::Nostr::Key;

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

# Create a channel (kind 40)
my $event = Net::Nostr::Channel->create(
    pubkey  => $key->pubkey_hex,
    name    => 'Perl Nostr',
    about   => 'Discussion about Perl and Nostr',
    picture => 'https://example.com/perl.png',
    relays  => ['wss://relay.example.com/'],
);
$key->sign_event($event);
$client->publish($event);
my $channel_id = $event->id;

# Update channel metadata (kind 41)
my $update = Net::Nostr::Channel->set_metadata(
    pubkey     => $key->pubkey_hex,
    channel_id => $channel_id,
    name       => 'Perl Nostr Chat',
    relay_url  => 'wss://relay.example.com/',
    categories => ['perl', 'nostr'],
);

# Send a message (kind 42)
my $msg = Net::Nostr::Channel->message(
    pubkey     => $key->pubkey_hex,
    channel_id => $channel_id,
    content    => 'Hello, channel!',
    relay_url  => 'wss://relay.example.com/',
);

# Reply to a message (kind 42)
my $reply = Net::Nostr::Channel->reply(
    pubkey     => $key->pubkey_hex,
    channel_id => $channel_id,
    to         => $msg,
    content    => 'Welcome!',
);

# Hide a message (kind 43, client-side moderation)
my $hide = Net::Nostr::Channel->hide_message(
    pubkey     => $key->pubkey_hex,
    message_id => $msg->id,
    reason     => 'spam',
);

# Mute a user (kind 44, client-side moderation)
my $mute = Net::Nostr::Channel->mute_user(
    pubkey      => $key->pubkey_hex,
    user_pubkey => $spammer_pk,
    reason      => 'spammer',
);

# Parse channel metadata from a received event
my $meta = Net::Nostr::Channel->metadata_from_event($event);
say $meta->{name};     # 'Perl Nostr'
say $meta->{about};    # 'Discussion about Perl and Nostr'
say $meta->{picture};  # 'https://example.com/perl.png'

# Get channel ID from a message or metadata update
my $ch_id = Net::Nostr::Channel->channel_id($msg_event);

# Parse hide/mute info from received events
my $hide_info = Net::Nostr::Channel->hide_from_event($hide_event);
say $hide_info->{message_id};
say $hide_info->{reason};  # or undef

my $mute_info = Net::Nostr::Channel->mute_from_event($mute_event);
say $mute_info->{pubkey};
say $mute_info->{reason};  # or undef

DESCRIPTION

Implements NIP-28 public chat channels. Channels are created with kind 40 events, metadata is updated with kind 41, and messages are sent as kind 42. Client-side moderation is provided via kind 43 (hide message) and kind 44 (mute user).

All moderation is client-centric: clients decide what content to show or hide, with no additional requirements on relays.

CLASS METHODS

create

my $event = Net::Nostr::Channel->create(
    pubkey   => $hex_pubkey,
    name     => 'Channel Name',
    about    => 'Description',       # optional
    picture  => 'https://pic.url',   # optional
    relays   => ['wss://relay.com'], # optional
    metadata => { rules => '...' },  # optional, extra metadata fields
);

Creates a kind 40 channel creation event. The content field is JSON containing the channel metadata. name is required. metadata is an optional hashref of additional metadata fields to include in the JSON content. Extra arguments are passed through to "new" in Net::Nostr::Event.

The event ID of the returned event becomes the channel identifier used by all other methods.

my $event = Net::Nostr::Channel->create(
    pubkey => $key->pubkey_hex,
    name   => 'My Channel',
);
my $channel_id = $event->id;

set_metadata

my $event = Net::Nostr::Channel->set_metadata(
    pubkey     => $hex_pubkey,
    channel_id => $channel_id,
    name       => 'New Name',         # optional
    about      => 'New description',  # optional
    picture    => 'https://new.pic',  # optional
    relays     => ['wss://r.com'],    # optional
    relay_url  => 'wss://relay.com/', # optional, defaults to ''
    categories => ['topic1'],         # optional, added as t tags
    metadata   => { rules => '...' }, # optional, extra metadata fields
);

Creates a kind 41 channel metadata update event. channel_id is required. The e tag uses NIP-10 marked root format. metadata is an optional hashref of additional metadata fields. Only the most recent kind 41 per channel should be used.

Clients should ignore kind 41 events from pubkeys other than the channel creator.

my $update = Net::Nostr::Channel->set_metadata(
    pubkey     => $key->pubkey_hex,
    channel_id => $channel_id,
    name       => 'Updated Name',
    categories => ['nostr', 'perl'],
);
# tags: [['e', $channel_id, '', 'root'], ['t', 'nostr'], ['t', 'perl']]

message

my $event = Net::Nostr::Channel->message(
    pubkey     => $hex_pubkey,
    channel_id => $channel_id,
    content    => 'Hello!',
    relay_url  => 'wss://relay.com/',  # optional, defaults to ''
);

Creates a kind 42 root channel message. The e tag points to the channel creation event with NIP-10 root marker.

my $msg = Net::Nostr::Channel->message(
    pubkey     => $key->pubkey_hex,
    channel_id => $channel_id,
    content    => 'First message!',
);
# tags: [['e', $channel_id, '', 'root']]

reply

my $event = Net::Nostr::Channel->reply(
    pubkey     => $hex_pubkey,
    channel_id => $channel_id,
    to         => $parent_event,
    content    => 'Reply text',
    relay_url  => 'wss://relay.com/',  # optional, defaults to ''
);

Creates a kind 42 reply to a channel message. Has two e tags: one with root marker pointing to the channel, and one with reply marker pointing to the parent message. A p tag for the parent author is appended unless replying to self.

my $reply = Net::Nostr::Channel->reply(
    pubkey     => $key->pubkey_hex,
    channel_id => $channel_id,
    to         => $msg,
    content    => 'I agree!',
);
# tags: [['e', $channel_id, '', 'root'],
#        ['e', $msg->id, '', 'reply'],
#        ['p', $msg->pubkey, '']]

hide_message

my $event = Net::Nostr::Channel->hide_message(
    pubkey     => $hex_pubkey,
    message_id => $msg_event_id,
    reason     => 'spam',  # optional
);

Creates a kind 43 event indicating the user no longer wants to see the specified message. reason is optional; when provided, the content is JSON {"reason":"..."}.

Clients should hide kind 42 events if there is a matching kind 43 from the user. Clients may also hide for other users based on multiple hide events.

my $hide = Net::Nostr::Channel->hide_message(
    pubkey     => $key->pubkey_hex,
    message_id => $msg->id,
    reason     => 'off-topic',
);

mute_user

my $event = Net::Nostr::Channel->mute_user(
    pubkey      => $hex_pubkey,
    user_pubkey => $target_pubkey,
    reason      => 'spammer',  # optional
);

Creates a kind 44 event indicating the user no longer wants to see messages from the specified user. reason is optional; when provided, the content is JSON {"reason":"..."}.

Clients should hide kind 42 events from the muted pubkey if there is a matching kind 44 from the user.

my $mute = Net::Nostr::Channel->mute_user(
    pubkey      => $key->pubkey_hex,
    user_pubkey => $spammer,
    reason      => 'posting spam',
);

metadata_from_event

my $meta = Net::Nostr::Channel->metadata_from_event($event);
# { name => '...', about => '...', picture => '...', relays => [...] }

Parses channel metadata from a kind 40 or kind 41 event. Returns a hashref decoded from the event's JSON content. Fields not present in the content will not be in the hashref. Croaks if the event is not kind 40 or 41.

my $meta = Net::Nostr::Channel->metadata_from_event($channel_event);
say $meta->{name};

channel_id

my $id = Net::Nostr::Channel->channel_id($event);  # or undef

Extracts the channel ID (the kind 40 event ID) from a kind 41 or kind 42 event by finding the e tag with root marker. Returns undef if no root e tag is found.

my $ch_id = Net::Nostr::Channel->channel_id($msg_event);
# the event ID of the kind 40 channel creation event

hide_from_event

my $info = Net::Nostr::Channel->hide_from_event($event);
# { message_id => '...', reason => '...' or undef }

Parses a kind 43 event. Returns a hashref with message_id (from the e tag) and reason (from the JSON content, or undef). Croaks if the event is not kind 43.

my $info = Net::Nostr::Channel->hide_from_event($hide_event);
say "Hidden: $info->{message_id}";
say "Reason: $info->{reason}" if $info->{reason};

mute_from_event

my $info = Net::Nostr::Channel->mute_from_event($event);
# { pubkey => '...', reason => '...' or undef }

Parses a kind 44 event. Returns a hashref with pubkey (from the p tag) and reason (from the JSON content, or undef). Croaks if the event is not kind 44.

my $info = Net::Nostr::Channel->mute_from_event($mute_event);
say "Muted: $info->{pubkey}";
say "Reason: $info->{reason}" if $info->{reason};

SEE ALSO

NIP-28, Net::Nostr, Net::Nostr::Event, Net::Nostr::Thread