File Coverage

blib/lib/Lyrics/Fetcher/LyricsOVH.pm
Criterion Covered Total %
statement 20 75 26.6
branch 0 14 0.0
condition 0 3 0.0
subroutine 7 10 70.0
pod 2 2 100.0
total 29 104 27.8


line stmt bran cond sub pod time code
1             package Lyrics::Fetcher::LyricsOVH;
2              
3             # $Id$
4              
5 1     1   76594 use 5.008000;
  1         5  
6 1     1   6 use strict;
  1         2  
  1         39  
7 1     1   7 use warnings;
  1         2  
  1         40  
8 1     1   803 use LWP::UserAgent;
  1         52010  
  1         51  
9 1     1   15 use HTTP::Request;
  1         2  
  1         32  
10 1     1   6 use Carp;
  1         3  
  1         984  
11              
12             my $haveLyricsFetcher = 0;
13 1     1   739 eval "use Lyrics::Fetcher (qw(\$AGENT)); \$haveLyricsFetcher = 1; 1";
  1         3265  
  1         31  
14              
15             our $VERSION = 0.01;
16              
17             # the HTTP User-Agent we'll send:
18             our $AGENT = ($haveLyricsFetcher && defined $Lyrics::Fetcher::AGENT)
19             ? $Lyrics::Fetcher::AGENT
20             : "Mozilla/5.0 (X11; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0";
21              
22             $Lyrics::Fetcher::Error = 'OK' unless ($haveLyricsFetcher);
23              
24             sub fetch {
25 0     0 1   my $self = shift;
26 0           my ($artist, $song) = @_;
27              
28             # reset the error var, change it if an error occurs.
29 0           $Lyrics::Fetcher::Error = 'OK';
30              
31 0 0 0       unless ($artist && $song) {
32 0           carp($Lyrics::Fetcher::Error
33             = 'fetch() called without artist and song');
34 0           return;
35             }
36              
37 0           $artist =~ s#\s*\/.*$##; #ONLY USE 1ST ARTIST, IF MORE THAN ONE!
38 0           $artist =~ s/\s+/\%20/g;
39 0           $artist =~ s/[^a-z0-9\%]//gi;
40              
41 0           $song =~ s/\s+/\%20/g;
42 0           $song =~ s/[^a-z0-9\%]//gi;
43              
44             # Their URLs look like e.g.:
45             # https://api.lyrics.ovh/v1/Dire%20straits/heavy%sfuel%s
46 0           my $url = "https://api.lyrics.ovh/v1/${artist}/$song";
47              
48 0           my $ua = LWP::UserAgent->new(
49             ssl_opts => { verify_hostname => 0, },
50             );
51 0           $ua->timeout(10);
52 0           $ua->agent($AGENT);
53 0           $ua->protocols_allowed(['https']);
54 0           $ua->cookie_jar( {} );
55 0           push @{ $ua->requests_redirectable }, 'GET';
  0            
56 0           (my $referer = $url) =~ s{^(\w+)\:\/\/}{};
57 0           my $protocol = $1;
58 0           $referer =~ s{\/.+$}{\/};
59 0           my $host = $referer;
60 0           $host =~ s{\/$}{};
61 0           $referer = $protocol . '://' . $referer;
62 0           my $req = new HTTP::Request 'GET' => $url;
63 0           $req->header(
64             'Accept' =>
65             'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
66             'Accept-Language' => 'en-US,en;q=0.5',
67             'Accept-Encoding' => 'gzip, deflate',
68             'Connection' => 'keep-alive',
69             'Upgrade-insecure-requests' => 1,
70             'Host' => $host,
71             );
72              
73 0           my $res = $ua->request($req);
74              
75 0 0         if ($res->is_success) {
76 0           my $lyrics = _parse($res->decoded_content);
77 0           return $lyrics;
78             } else {
79 0 0         if ($res->status_line =~ /^404/) {
80 0           $Lyrics::Fetcher::Error = 'Lyrics not found';
81 0           return;
82             } else {
83 0           carp($Lyrics::Fetcher::Error
84             = "Failed to retrieve $url (".$res->status_line.')');
85 0           return;
86             }
87             }
88             }
89              
90             # Allow user to specify a different user-agent:
91             sub agent {
92 0 0   0 1   if (defined $_[1]) {
93 0           $AGENT = $_[1];
94             } else {
95 0           return $AGENT;
96             }
97             }
98              
99             # Internal use only functions:
100              
101             sub _parse {
102 0     0     my $html = shift;
103              
104 0 0         if (my ($goodbit) = $html =~
105             m{\{\"lyrics\"\:\"([^\"]+)\"}msi)
106             {
107 0           my $text = '';
108             # convert literal "\" followed by "r" or "n", etc. to "\r" or "\n" characters respectively:
109 0           eval "\$text = \"$goodbit\";";
110              
111             # fix apparent site bug where they use "\n\n" where they appear to mean "\r\n" (excess double-lines):
112 0           $text =~ s/\n\n/\n/gs;
113             # normalize Windowsey \r\n sequences:
114 0           $text =~ s/\r+//gs;
115             # strip off pre & post padding with spaces:
116 0           $text =~ s/^ +//mg;
117 0           $text =~ s/ +$//mg;
118             # clear up repeated blank lines:
119 0           $text =~ s/(\R){2,}/\n\n/gs;
120             # and remove any blank top lines:
121 0           $text =~ s/^\R+//s;
122 0           $text =~ s/\R\R+$/\n/s;
123 0 0         $text .= "\n" unless ($text =~ /\n$/s);
124             # now fix up for either Windows or Linux/Unix:
125 0 0         $text =~ s/\R/\r\n/gs if ($^O =~ /Win/);
126              
127 0           return $text;
128             } else {
129 0           carp "Failed to identify lyrics on result page";
130 0           return;
131             }
132              
133             } # end of sub parse
134              
135             1;
136              
137             __END__