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;
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};
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)
# 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 0.850 second using v1.01-cache-2.11-cpan-140bd7fdf52 )