Net-Nostr
view release on metacpan or search on metacpan
my $PK = 'a' x 64;
my $PK2 = 'b' x 64;
my $PK3 = 'c' x 64;
###############################################################################
# Live Streaming Event (kind 30311)
###############################################################################
subtest 'live_event: kind 30311' => sub {
my $event = Net::Nostr::LiveActivity->live_event(
pubkey => $PK,
identifier => 'demo-stream',
);
is($event->kind, 30311, 'kind is 30311');
ok($event->is_addressable, 'addressable');
};
# Spec: d tag with unique identifier
subtest 'live_event: d tag' => sub {
my $event = Net::Nostr::LiveActivity->live_event(
pubkey => $PK,
identifier => 'demo-cf-stream',
);
my @d = grep { $_->[0] eq 'd' } @{$event->tags};
is($d[0][1], 'demo-cf-stream', 'd tag');
};
# Spec: title tag
subtest 'live_event: title tag' => sub {
my $event = Net::Nostr::LiveActivity->live_event(
pubkey => $PK,
identifier => 'x',
title => 'Adult Swim Metalocalypse',
);
my @t = grep { $_->[0] eq 'title' } @{$event->tags};
is($t[0][1], 'Adult Swim Metalocalypse', 'title');
};
# Spec: summary tag
subtest 'live_event: summary tag' => sub {
my $event = Net::Nostr::LiveActivity->live_event(
pubkey => $PK,
identifier => 'x',
summary => 'Live stream from IPTV-ORG collection',
);
my @s = grep { $_->[0] eq 'summary' } @{$event->tags};
is($s[0][1], 'Live stream from IPTV-ORG collection', 'summary');
};
# Spec: image tag
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};
is($r[0][1], 'https://example.com/recording.mp4', 'recording');
};
# Spec: starts and ends timestamps
subtest 'live_event: starts and ends tags' => sub {
my $event = Net::Nostr::LiveActivity->live_event(
pubkey => $PK,
identifier => 'x',
starts => '1687182672',
ends => '1687186272',
);
my @s = grep { $_->[0] eq 'starts' } @{$event->tags};
is($s[0][1], '1687182672', 'starts');
my @e = grep { $_->[0] eq 'ends' } @{$event->tags};
is($e[0][1], '1687186272', 'ends');
};
# Spec: status tag (planned, live, ended)
subtest 'live_event: status tag' => sub {
my $event = Net::Nostr::LiveActivity->live_event(
pubkey => $PK,
identifier => 'x',
status => 'live',
);
my @s = grep { $_->[0] eq 'status' } @{$event->tags};
is($s[0][1], 'live', 'status');
};
# Spec: current_participants and total_participants
subtest 'live_event: participant count tags' => sub {
my $event = Net::Nostr::LiveActivity->live_event(
pubkey => $PK,
identifier => 'x',
current_participants => '100',
total_participants => '200',
);
my @cp = grep { $_->[0] eq 'current_participants' } @{$event->tags};
is($cp[0][1], '100', 'current_participants');
my @tp = grep { $_->[0] eq 'total_participants' } @{$event->tags};
is($tp[0][1], '200', 'total_participants');
};
# Spec: t (hashtag) tags, multiple allowed
subtest 'live_event: hashtag tags' => sub {
my $event = Net::Nostr::LiveActivity->live_event(
pubkey => $PK,
identifier => 'x',
hashtags => ['animation', 'iptv'],
);
my @t = grep { $_->[0] eq 't' } @{$event->tags};
# Spec: relays tag
subtest 'live_event: relays tag' => sub {
my $event = Net::Nostr::LiveActivity->live_event(
pubkey => $PK,
identifier => 'x',
relays => ['wss://one.com', 'wss://two.com'],
);
my @r = grep { $_->[0] eq 'relays' } @{$event->tags};
is(scalar @r, 1, 'one relays tag');
is($r[0][1], 'wss://one.com', 'first relay');
is($r[0][2], 'wss://two.com', 'second relay');
};
# Spec: pinned tag for pinned live chat message
subtest 'live_event: pinned tag' => sub {
my $event_id = 'e' x 64;
my $event = Net::Nostr::LiveActivity->live_event(
pubkey => $PK,
identifier => 'x',
pinned => [$event_id],
);
my @pin = grep { $_->[0] eq 'pinned' } @{$event->tags};
is($pin[0][1], $event_id, 'pinned event id');
};
# Spec line 85: "pin one or more live chat messages"
subtest 'live_event: multiple pinned tags' => sub {
my $id1 = 'e' x 64;
my $id2 = 'f' x 64;
my $event = Net::Nostr::LiveActivity->live_event(
pubkey => $PK,
identifier => 'x',
pinned => [$id1, $id2],
);
my @pin = grep { $_->[0] eq 'pinned' } @{$event->tags};
is(scalar @pin, 2, 'two pinned tags');
is($pin[0][1], $id1, 'first pinned');
is($pin[1][1], $id2, 'second pinned');
};
# Spec: content is empty
subtest 'live_event: content defaults to empty' => sub {
my $event = Net::Nostr::LiveActivity->live_event(
pubkey => $PK,
identifier => 'x',
);
is($event->content, '', 'empty content');
};
# Spec: requires identifier
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)
###############################################################################
subtest 'chat_message: kind 1311' => sub {
my $event = Net::Nostr::LiveActivity->chat_message(
pubkey => $PK,
activity => "30311:${PK2}:demo",
content => 'Hello',
);
is($event->kind, 1311, 'kind is 1311');
ok($event->is_regular, 'regular');
};
# Spec: MUST include a tag of the activity
subtest 'chat_message: a tag' => sub {
my $event = Net::Nostr::LiveActivity->chat_message(
pubkey => $PK,
activity => "30311:${PK2}:demo-cf-stream",
content => 'Zaps to live streams is beautiful.',
);
my @a = grep { $_->[0] eq 'a' } @{$event->tags};
is($a[0][1], "30311:${PK2}:demo-cf-stream", 'a tag');
};
# Spec: a tag with optional relay url and root marker
subtest 'chat_message: a tag with relay hint' => sub {
my $event = Net::Nostr::LiveActivity->chat_message(
pubkey => $PK,
activity => "30311:${PK2}:demo",
relay_hint => 'wss://relay.example.com',
content => 'Hello',
);
my @a = grep { $_->[0] eq 'a' } @{$event->tags};
is($a[0][2], 'wss://relay.example.com', 'relay hint');
is($a[0][3], 'root', 'root marker');
};
# Spec: e tag for reply
subtest 'chat_message: e tag for reply' => sub {
my $parent_id = 'f' x 64;
my $event = Net::Nostr::LiveActivity->chat_message(
pubkey => $PK,
activity => "30311:${PK2}:demo",
reply_to => $parent_id,
content => 'Reply',
);
my @e = grep { $_->[0] eq 'e' } @{$event->tags};
is($e[0][1], $parent_id, 'e tag');
};
# Spec: requires activity
# Spec: hand tag (optional, raised flag)
subtest 'room_presence: hand tag' => sub {
my $event = Net::Nostr::LiveActivity->room_presence(
pubkey => $PK,
activity => "30312:${PK2}:main-room",
hand => '1',
);
my @h = grep { $_->[0] eq 'hand' } @{$event->tags};
is($h[0][1], '1', 'hand raised');
};
# Spec: hand tag omitted when not set
subtest 'room_presence: no hand tag by default' => sub {
my $event = Net::Nostr::LiveActivity->room_presence(
pubkey => $PK,
activity => "30312:${PK2}:main-room",
);
my @h = grep { $_->[0] eq 'hand' } @{$event->tags};
is(scalar @h, 0, 'no hand tag');
};
# Spec: requires activity
subtest 'room_presence: requires activity' => sub {
like(
dies {
Net::Nostr::LiveActivity->room_presence(pubkey => $PK)
},
qr/activity/i,
'requires activity'
);
};
# Spec example
subtest 'room_presence: spec example' => sub {
my $event = Net::Nostr::LiveActivity->room_presence(
pubkey => $PK,
activity => "30312:${PK2}:main-room",
relay_hint => 'wss://relay.com',
hand => '1',
);
is($event->kind, 10312, 'kind');
ok($event->is_replaceable, 'replaceable');
my @a = grep { $_->[0] eq 'a' } @{$event->tags};
is($a[0][3], 'root', 'root marker');
my @h = grep { $_->[0] eq 'hand' } @{$event->tags};
is($h[0][1], '1', 'hand');
};
###############################################################################
# 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');
is($parsed->pinned->[0], 'e' x 64, 'pinned');
};
subtest 'from_event: chat_message round-trip' => sub {
my $event = Net::Nostr::LiveActivity->chat_message(
pubkey => $PK,
activity => "30311:${PK2}:demo",
relay_hint => 'wss://relay.com',
reply_to => 'f' x 64,
content => 'Hello!',
);
my $parsed = Net::Nostr::LiveActivity->from_event($event);
is($parsed->activity, "30311:${PK2}:demo", 'activity');
is($parsed->relay_hint, 'wss://relay.com', 'relay_hint');
is($parsed->reply_to, 'f' x 64, 'reply_to');
};
subtest 'from_event: meeting_space round-trip' => sub {
my $event = Net::Nostr::LiveActivity->meeting_space(
pubkey => $PK,
identifier => 'main-room',
room => 'Main Room',
status => 'open',
service => 'https://meet.example.com',
endpoint => 'https://api.example.com',
participants => [[$PK2, 'wss://relay.com/', 'Host']],
);
my $parsed = Net::Nostr::LiveActivity->from_event($event);
is($parsed->identifier, 'main-room', 'identifier');
is($parsed->room, 'Main Room', 'room');
is($parsed->status, 'open', 'status');
is($parsed->service, 'https://meet.example.com', 'service');
is($parsed->endpoint, 'https://api.example.com', 'endpoint');
is($parsed->participants->[0][0], $PK2, 'participant');
};
subtest 'from_event: meeting_room round-trip' => sub {
my $event = Net::Nostr::LiveActivity->meeting_room(
pubkey => $PK,
identifier => 'mtg-1',
space_ref => ["30312:${PK2}:room", 'wss://relay.com'],
title => 'Meeting',
starts => '1676262123',
ends => '1676269323',
status => 'live',
);
my $parsed = Net::Nostr::LiveActivity->from_event($event);
is($parsed->identifier, 'mtg-1', 'identifier');
is($parsed->space_ref->[0], "30312:${PK2}:room", 'space_ref coord');
is($parsed->space_ref->[1], 'wss://relay.com', 'space_ref relay');
( run in 0.714 second using v1.01-cache-2.11-cpan-140bd7fdf52 )