Net-Nostr

 view release on metacpan or  search on metacpan

lib/Net/Nostr/LiveActivity.pm  view on Meta::CPAN

use strictures 2;

use Carp qw(croak);
use Net::Nostr::Event;

use Class::Tiny qw(
    identifier
    title
    summary
    image
    streaming
    recording
    starts
    ends
    status
    current_participants
    total_participants
    hashtags
    participants
    relays
    pinned

lib/Net/Nostr/LiveActivity.pm  view on Meta::CPAN

}

sub live_event {
    my ($class, %args) = @_;

    my $identifier = delete $args{identifier}
        // croak "live_event requires 'identifier'";
    my $title                = delete $args{title};
    my $summary              = delete $args{summary};
    my $image                = delete $args{image};
    my $streaming            = delete $args{streaming};
    my $recording            = delete $args{recording};
    my $starts               = delete $args{starts};
    my $ends                 = delete $args{ends};
    my $status               = delete $args{status};
    my $current_participants = delete $args{current_participants};
    my $total_participants   = delete $args{total_participants};
    my $hashtags             = delete $args{hashtags} // [];
    my $participants         = delete $args{participants} // [];
    my $relays               = delete $args{relays};
    my $pinned               = delete $args{pinned} // [];

    my @tags;
    push @tags, ['d', $identifier];
    push @tags, ['title', $title]         if defined $title;
    push @tags, ['summary', $summary]     if defined $summary;
    push @tags, ['image', $image]         if defined $image;
    push @tags, ['streaming', $streaming] if defined $streaming;
    push @tags, ['recording', $recording] if defined $recording;
    push @tags, ['starts', $starts]       if defined $starts;
    push @tags, ['ends', $ends]           if defined $ends;
    push @tags, ['status', $status]       if defined $status;
    push @tags, ['current_participants', $current_participants]
        if defined $current_participants;
    push @tags, ['total_participants', $total_participants]
        if defined $total_participants;
    push @tags, ['t', $_] for @$hashtags;
    push @tags, ['p', @$_] for @$participants;

lib/Net/Nostr/LiveActivity.pm  view on Meta::CPAN

    my %attrs;
    my (@hashtags, @participants, @relays, @pinned);

    for my $tag (@{$event->tags}) {
        next unless @$tag >= 2;
        my $t = $tag->[0];
        if    ($t eq 'd')                    { $attrs{identifier} = $tag->[1] }
        elsif ($t eq 'title')                { $attrs{title} = $tag->[1] }
        elsif ($t eq 'summary')              { $attrs{summary} = $tag->[1] }
        elsif ($t eq 'image')                { $attrs{image} = $tag->[1] }
        elsif ($t eq 'streaming')            { $attrs{streaming} = $tag->[1] }
        elsif ($t eq 'recording')            { $attrs{recording} = $tag->[1] }
        elsif ($t eq 'starts')               { $attrs{starts} = $tag->[1] }
        elsif ($t eq 'ends')                 { $attrs{ends} = $tag->[1] }
        elsif ($t eq 'status')               { $attrs{status} = $tag->[1] }
        elsif ($t eq 'current_participants') { $attrs{current_participants} = $tag->[1] }
        elsif ($t eq 'total_participants')   { $attrs{total_participants} = $tag->[1] }
        elsif ($t eq 't')                    { push @hashtags, $tag->[1] }
        elsif ($t eq 'p')                    { push @participants, [@{$tag}[1 .. $#$tag]] }
        elsif ($t eq 'relays')               { @relays = @{$tag}[1 .. $#$tag] }
        elsif ($t eq 'pinned')               { push @pinned, $tag->[1] }

lib/Net/Nostr/LiveActivity.pm  view on Meta::CPAN



=head1 NAME

Net::Nostr::LiveActivity - NIP-53 Live Activities

=head1 SYNOPSIS

    use Net::Nostr::LiveActivity;

    # Live streaming event (kind 30311)
    my $event = Net::Nostr::LiveActivity->live_event(
        pubkey     => $hex_pubkey,
        identifier => 'my-stream',
        title      => 'My Stream',
        status     => 'live',
    );

    # Live chat message (kind 1311)
    my $event = Net::Nostr::LiveActivity->chat_message(
        pubkey   => $hex_pubkey,

lib/Net/Nostr/LiveActivity.pm  view on Meta::CPAN

    Net::Nostr::LiveActivity->validate($event);

=head1 DESCRIPTION

Implements NIP-53 (Live Activities). Five event kinds are used:

=over 4

=item * B<Live Streaming Event> (kind 30311) - An addressable event
advertising a live stream. Contains tags for title, summary, image,
streaming URL, recording URL, start/end times, status, participant
counts, hashtags, participant roles, relay lists, and pinned chat
messages. Updated continuously as participants join and leave.

=item * B<Live Chat Message> (kind 1311) - A regular event for live
chat. MUST include an C<a> tag referencing the parent live activity.
MAY include an C<e> tag for replies.

=item * B<Meeting Space> (kind 30312) - An addressable event defining
a virtual interactive space. MUST have C<room>, C<status>, C<service>,
and at least one C<p> tag with a Host role.

lib/Net/Nostr/LiveActivity.pm  view on Meta::CPAN

=head1 CLASS METHODS

=head2 live_event

    my $event = Net::Nostr::LiveActivity->live_event(
        pubkey               => $hex_pubkey,       # required
        identifier           => $id,               # required (d tag)
        title                => $title,            # optional
        summary              => $summary,          # optional
        image                => $url,              # optional
        streaming            => $url,              # optional
        recording            => $url,              # optional
        starts               => $timestamp,        # optional
        ends                 => $timestamp,        # optional
        status               => $status,           # optional (planned/live/ended)
        current_participants => $count,            # optional
        total_participants   => $count,            # optional
        hashtags             => [$tag, ...],       # optional (t tags)
        participants         => [[$pk, $relay, $role, $proof], ...], # optional (p tags)
        relays               => [$url, ...],       # optional (relays tag)
        pinned               => [$event_id, ...],  # optional (pinned tags)
    );

Creates a kind 30311 live streaming L<Net::Nostr::Event>. Each C<p>
tag SHOULD have a displayable role name (e.g. C<Host>, C<Speaker>,
C<Participant>). The relay and proof fields in participant entries are
optional. Content defaults to C<''>.

=head2 chat_message

    my $event = Net::Nostr::LiveActivity->chat_message(
        pubkey     => $hex_pubkey,                 # required
        activity   => "30311:$pk:$d_id",           # required (a tag)
        relay_hint => $relay_url,                  # optional

lib/Net/Nostr/LiveActivity.pm  view on Meta::CPAN

Event title (kinds 30311, 30313).

=head2 summary

Event description.

=head2 image

Preview image URL.

=head2 streaming

Live stream URL (kind 30311).

=head2 recording

Recording URL (kind 30311).

=head2 starts

Start timestamp in seconds.

t/54-LiveActivity.t  view on Meta::CPAN

};

###############################################################################
# Public methods available
###############################################################################

subtest 'public methods available' => sub {
    can_ok('Net::Nostr::LiveActivity',
        qw(new live_event chat_message meeting_space meeting_room
           room_presence from_event validate
           identifier title summary image streaming recording
           starts ends status current_participants total_participants
           hashtags participants relays pinned
           activity relay_hint reply_to
           room service endpoint space_ref hand));
};

done_testing;

t/nip/53.t  view on Meta::CPAN

subtest 'live_event: image tag' => sub {
    my $event = Net::Nostr::LiveActivity->live_event(
        pubkey     => $PK,
        identifier => 'x',
        image      => 'https://i.imgur.com/CaKq6Mt.png',
    );
    my @i = grep { $_->[0] eq 'image' } @{$event->tags};
    is($i[0][1], 'https://i.imgur.com/CaKq6Mt.png', 'image');
};

# Spec: streaming tag
subtest 'live_event: streaming tag' => sub {
    my $event = Net::Nostr::LiveActivity->live_event(
        pubkey     => $PK,
        identifier => 'x',
        streaming  => 'https://example.com/stream.m3u8',
    );
    my @s = grep { $_->[0] eq 'streaming' } @{$event->tags};
    is($s[0][1], 'https://example.com/stream.m3u8', 'streaming');
};

# Spec: recording tag
subtest 'live_event: recording tag' => sub {
    my $event = Net::Nostr::LiveActivity->live_event(
        pubkey     => $PK,
        identifier => 'x',
        recording  => 'https://example.com/recording.mp4',
    );
    my @r = grep { $_->[0] eq 'recording' } @{$event->tags};

t/nip/53.t  view on Meta::CPAN

subtest 'live_event: requires identifier' => sub {
    like(
        dies {
            Net::Nostr::LiveActivity->live_event(pubkey => $PK)
        },
        qr/identifier/i,
        'requires identifier'
    );
};

# Spec example: live streaming
subtest 'live_event: spec example' => sub {
    my $event = Net::Nostr::LiveActivity->live_event(
        pubkey     => '1597246ac22f7d1375041054f2a4986bd971d8d196d7997e48973263ac9879ec',
        identifier => 'demo-cf-stream',
        title      => 'Adult Swim Metalocalypse',
        summary    => 'Live stream from IPTV-ORG collection',
        streaming  => 'https://adultswim-vodlive.cdn.turner.com/live/metalocalypse/stream.m3u8',
        starts     => '1687182672',
        status     => 'live',
        hashtags   => ['animation', 'iptv'],
        image      => 'https://i.imgur.com/CaKq6Mt.png',
    );
    is($event->kind, 30311, 'kind');
    my @d = grep { $_->[0] eq 'd' } @{$event->tags};
    is($d[0][1], 'demo-cf-stream', 'd');
    my @title = grep { $_->[0] eq 'title' } @{$event->tags};
    is($title[0][1], 'Adult Swim Metalocalypse', 'title');
    my @streaming = grep { $_->[0] eq 'streaming' } @{$event->tags};
    is($streaming[0][1], 'https://adultswim-vodlive.cdn.turner.com/live/metalocalypse/stream.m3u8', 'streaming');
    my @status = grep { $_->[0] eq 'status' } @{$event->tags};
    is($status[0][1], 'live', 'status');
    my @t = grep { $_->[0] eq 't' } @{$event->tags};
    is(scalar @t, 2, 'hashtag count');
    is($t[0][1], 'animation', 'first hashtag');
    is($t[1][1], 'iptv', 'second hashtag');
};

###############################################################################
# Live Chat Message (kind 1311)

t/nip/53.t  view on Meta::CPAN

# from_event: round-trip parsing
###############################################################################

subtest 'from_event: live_event round-trip' => sub {
    my $event = Net::Nostr::LiveActivity->live_event(
        pubkey               => $PK,
        identifier           => 'demo-stream',
        title                => 'Test Stream',
        summary              => 'A test',
        image                => 'https://example.com/img.png',
        streaming            => 'https://example.com/stream.m3u8',
        recording            => 'https://example.com/rec.mp4',
        starts               => '1687182672',
        ends                 => '1687186272',
        status               => 'live',
        current_participants => '50',
        total_participants   => '100',
        hashtags             => ['test', 'live'],
        participants         => [[$PK2, 'wss://relay.com/', 'Host']],
        relays               => ['wss://one.com'],
        pinned               => ['e' x 64],
    );
    my $parsed = Net::Nostr::LiveActivity->from_event($event);
    is($parsed->identifier, 'demo-stream', 'identifier');
    is($parsed->title, 'Test Stream', 'title');
    is($parsed->summary, 'A test', 'summary');
    is($parsed->image, 'https://example.com/img.png', 'image');
    is($parsed->streaming, 'https://example.com/stream.m3u8', 'streaming');
    is($parsed->recording, 'https://example.com/rec.mp4', 'recording');
    is($parsed->starts, '1687182672', 'starts');
    is($parsed->ends, '1687186272', 'ends');
    is($parsed->status, 'live', 'status');
    is($parsed->current_participants, '50', 'current_participants');
    is($parsed->total_participants, '100', 'total_participants');
    is($parsed->hashtags, ['test', 'live'], 'hashtags');
    is($parsed->participants->[0][0], $PK2, 'participant pubkey');
    is($parsed->participants->[0][2], 'Host', 'participant role');
    is($parsed->relays, ['wss://one.com'], 'relays');



( run in 1.027 second using v1.01-cache-2.11-cpan-140bd7fdf52 )