File Coverage

lib/Video/NRK/Cache/ProgramId.pm
Criterion Covered Total %
statement 20 20 100.0
branch n/a
condition n/a
subroutine 7 7 100.0
pod n/a
total 27 27 100.0


line stmt bran cond sub pod time code
1 2     2   124019 use v5.37.9;
  2         9  
2 2     2   14 use feature 'class';
  2         5  
  2         364  
3 2     2   12 no warnings 'experimental::class';
  2         4  
  2         1470  
4 2     2   2312 use utf8;
  2         705  
  2         16  
5              
6             package Video::NRK::Cache::ProgramId 3.02; # Dist::Zilla doesn't know about class yet
7              
8             class Video::NRK::Cache::ProgramId;
9             # ABSTRACT: NRK on-demand "PRF" program ID
10              
11              
12 2     2   176 use Carp qw( carp croak );
  2         4  
  2         140  
13 2     2   1262 use HTTP::Tiny ();
  2         77333  
  2         74  
14 2     2   15 use warnings::register;
  2         4  
  2         2945  
15              
16              
17             our $QUICK_ID = 1; # skip HTML parsing if possible, rely on hard-coded API base
18             our $NRK_BASE = 'https://tv.nrk.no/program';
19             our $PSAPI_BASE = 'https://psapi.nrk.no';
20              
21              
22             field $program_id;
23             field $url;
24             field $id;
25             field $psapi_base :param //= $PSAPI_BASE;
26             field $parse :param = undef;
27             field $ua :param = HTTP::Tiny->new;
28              
29             my $nrk_re = qr{//[^/]*nrk\.no(?:/|$)};
30             my $prfid_re = qr/[A-ZØÆÅ]{4}[0-9]{8}/;
31              
32              
33             method id { $id }
34             method url { $url }
35             method psapi_base { $psapi_base }
36              
37              
38             ADJUST {
39             $self->parse($parse) if defined $parse;
40             }
41              
42              
43             method parse ($parse_) {
44             $parse = $parse_;
45            
46             # Strategies to obtain the NRK on-demand "PRF" program ID:
47             # 1. parse from URL
48             # 2. get from HTTP header
49             # 3. parse from web page meta data
50             # 4. first string on web page that looks like an ID
51            
52             $self->_parse_as_string and return;
53             $url = $parse;
54             $self->_parse_from_header and return;
55             $self->_parse_from_body and return;
56            
57             croak "Failed to discover NRK 'PRF' program ID; giving up on '$url'";
58             }
59              
60              
61             method _parse_as_string () {
62             return unless $parse =~ m/^$prfid_re$/;
63            
64             # the user supplied the program ID instead of the URL
65             $id = $parse;
66             $url = "$NRK_BASE/$parse";
67             }
68              
69              
70             method _parse_from_header () {
71             return unless $QUICK_ID;
72            
73             if ($url =~ m<^http.+/($prfid_re)(?:/|$)>) {
74             return $id = $1;
75             }
76            
77             $id = eval { $ua->head($url)->{headers}{'x-nrk-program-id'} } // '';
78             return $id if $id =~ m/^$prfid_re$/;
79             }
80              
81              
82             method _parse_from_body () {
83             my $res = $ua->get($url, {headers => { Accept => 'text/html' }});
84             $url = $res->{url};
85             carp "Warning: This doesn't look like NRK. Check the URL '$url'" if warnings::enabled && $url !~ m/^https?:$nrk_re/i;
86             my $error = $res->{status} eq "599" ? ": $res->{content}" : "";
87             croak "HTTP error $res->{status} $res->{reason} on $url$error" unless $res->{success};
88            
89             my $html = $res->{content};
90             my ($base_url) = $html =~ m/\bdata-psapi-base-url="([^"]+)"/i;
91             $psapi_base = $base_url if $base_url && $base_url =~ m/https:$nrk_re/i;
92             $id = $res->{headers}{'x-nrk-program-id'} // ''; # this header might not have been present in the HEAD response
93             return $id if $id =~ m/^$prfid_re$/;
94            
95             return $id if ($id) = $html =~ m/\bprogram-id(?:"\s+content)?="($prfid_re)"/i;
96             return $id if ($id) = $html =~ m/"prf(?:Id"\s*:\s*"|:)($prfid_re)"/;
97            
98             carp "Warning: Failed to discover NRK 'PRF' program ID; trying harder" if warnings::enabled;
99             return $id if ($id) = $html =~ m/\b($prfid_re)\b/;
100             return $id if ($id) = $html =~ m/(?:\\u002[Ff]|\%2[Ff])($prfid_re)\b/;
101             return $id if ($id) = $html =~ m/(?:[0-9a-z_]|\\u[0-9A-F]{4}|\%[0-9A-F]{2})($prfid_re)\b/; # last-ditch effort
102             }
103              
104              
105             1;
106              
107             __END__