File Coverage

blib/lib/Net/LastFM/Submission.pm
Criterion Covered Total %
statement 90 103 87.3
branch 30 46 65.2
condition 21 40 52.5
subroutine 17 19 89.4
pod 2 2 100.0
total 160 210 76.1


line stmt bran cond sub pod time code
1             package Net::LastFM::Submission;
2 3     3   92783 use strict;
  3         9  
  3         113  
3 3     3   16 use warnings;
  3         7  
  3         88  
4              
5 3     3   6254 use LWP::UserAgent;
  3         185587  
  3         108  
6 3     3   7668 use HTTP::Request::Common 'GET', 'POST';
  3         6337  
  3         226  
7 3     3   16 use Digest::MD5 'md5_hex';
  3         6  
  3         138  
8 3     3   13 use Carp 'croak';
  3         4  
  3         116  
9              
10 3     3   14 use base 'Exporter'; our @EXPORT = 'encode_data';
  3         3  
  3         349  
11              
12 3   50 3   16 use constant DEBUG => $ENV{'SUBMISSION_DEBUG'} || 0;
  3         7  
  3         1280  
13              
14             our $VERSION = '0.64';
15             our $URL = 'http://post.audioscrobbler.com/';
16              
17             sub new {
18 6     6 1 2985 my $class = shift;
19 6 50       34 my $param = ref $_[0] eq 'HASH' ? shift : {@_};
20            
21 6   100     178 my $self = {
      100        
      66        
      66        
      100        
22             'proto' => '1.2.1',
23             'limit' => 50, # last.fm limit
24            
25             'client' => {
26             'id' => $param->{'client_id' } || 'tst', # test client id
27             'ver' => $param->{'client_ver'} || '1.0', # test client version
28             },
29             'user' => {
30             'name' => $param->{'user' } || croak('Need user name'),
31             'password' => $param->{'password'},
32             },
33             'api' => {
34             'key' => $param->{'api_key' },
35             'secret' => $param->{'api_secret' },
36             },
37             'auth' => {
38             'session' => $param->{'session_key'},
39             },
40            
41             'ua' => $param->{'ua' } || LWP::UserAgent->new('timeout' => 10, 'agent' => join '/', __PACKAGE__, $VERSION),
42            
43             'enc' => $param->{'enc'} || 'cp1251',
44             };
45            
46 5 100       13242 if (defined $self->{'user'}->{'password'}) {
47 3         12 $self->{'auth'}->{'type'} = 'standard';
48             } else {
49 2 100       5 croak 'Need shared data (api_key/api_secret/session_key) for Web Services authentication' if grep { !$_ } @{$self->{'api'}}{'key', 'secret'}, $self->{'auth'}->{'session'};
  6         38  
  2         10  
50 1         3 $self->{'auth'}->{'type'} = 'web';
51             }
52            
53 4         8 if (DEBUG) {
54             warn "Last.fm Submissions Protocol v$self->{'proto'}";
55             warn "Client Identifier: $self->{'client'}->{'id'}/$self->{'client'}->{'ver'}";
56             warn $self->{'auth'}->{'type'} eq 'web' ? 'Web Services Authentication' : 'Standard Authentication';
57             }
58            
59 4   33     40 bless $self, ref $class || $class;
60             }
61              
62             {
63 3     3   15 no strict 'refs';
  3         5  
  3         4182  
64             for my $m ('handshake', 'now_playing', 'submit') {
65             *{$m} = sub {
66 0     0   0 my $self = shift;
67 0         0 my $r = $self->${\"_request_$m"}(@_);
  0         0  
68            
69 0 0       0 return $r unless ref $r eq 'HTTP::Request';
70            
71 0         0 my $data = $self->_response($self->{'ua'}->request($r));
72 0 0       0 $self->_save_handshake($data) if $m eq 'handshake'; # spesial action for handshake
73            
74 0         0 return $data;
75             };
76             }
77             }
78              
79             # save handshake data
80              
81             sub _save_handshake {
82 0     0   0 my $self = shift;
83 0         0 my $data = shift;
84            
85 0 0 0     0 if ($data->{'status'} && $data->{'url'} && $data->{'sid'}) {
      0        
86 0         0 DEBUG && warn "Save handshake data: $data->{'url'}->{'np'}, $data->{'sid'}";
87 0         0 $self->{'hs'} = $data;
88             }
89            
90 0         0 return $data;
91             }
92              
93             # generate requests
94              
95             sub _request_handshake {
96 1     1   976 my $self = shift;
97 1         10 my $time = time;
98            
99 1 50       25 $self->{'auth'}->{'token'} = md5_hex(($self->{'auth'}->{'type'} eq 'web' ? $self->{'api'}->{'secret'} : md5_hex $self->{'user'}->{'password'}).$time);
100            
101 1 50       20 my $r = GET(join '?', $URL, join '&',
102             'hs=true',
103             "p=$self->{'proto' }",
104             "c=$self->{'client'}->{'id' }",
105             "v=$self->{'client'}->{'ver' }",
106             "u=$self->{'user' }->{'name'}",
107             "t=$time",
108             "a=$self->{'auth'}->{'token'}",
109             $self->{'auth'}->{'type'} eq 'web' ? ("api_key=$self->{'api'}->{'key'}", "sk=$self->{'auth'}->{'session'}") : (),
110             );
111            
112 1         18318 DEBUG && warn $r->as_string;
113            
114 1         8 return $r;
115             }
116              
117             sub _request_now_playing {
118 4     4   1865 my $self = shift;
119 4 50       15 my $param = ref $_[0] eq 'HASH' ? shift : {@_};
120            
121 4 100       19 return $self->_error('Need a now-playing URL returned by the handshake request') unless $self->{'hs'}->{'url'}->{'np'};
122 3 100       9 return $self->_error('Need session ID string returned by the handshake request') unless $self->{'hs'}->{'sid'};
123 2 100       5 return $self->_error('Need artist/title name') if grep { !$param->{$_} } 'artist', 'title';
  4         17  
124            
125 1   33     10 my $enc = $param->{'enc'} || $self->{'enc'};
126 1         4 $_ = encode_data($_, $enc) for grep { $_ } @$param{'artist', 'title', 'album'};
  3         8  
127            
128 1         13 my $r = POST($self->{'hs'}->{'url'}->{'np'}, [
129             's' => $self->{'hs'}->{'sid'},
130             'a' => $param->{'artist'},
131             't' => $param->{'title' },
132             'b' => $param->{'album' },
133             'l' => $param->{'length'},
134             'n' => $param->{'id' },
135             'm' => $param->{'mb_id' },
136             ]);
137            
138 1         671 DEBUG && warn $r->as_string;
139            
140 1         5 return $r;
141             }
142              
143             sub _request_submit {
144 3     3   724 my $self = shift;
145 3 50       16 my $list = ref $_[0] eq 'HASH' ? [@_] : [{@_}];
146            
147 3 100       18 return $self->_error('Need a submit URL returned by the handshake request' ) unless $self->{'hs'}->{'url'}->{'sm'};
148 2 50       7 return $self->_error('Need session ID string returned by the handshake request') unless $self->{'hs'}->{'sid'};
149 2         3 DEBUG && warn "Use first $self->{'limit'} tracks for submissions";
150            
151 1   33     8 $list = [
152             grep {
153 2 100       15 my $enc = $_->{'enc'} || $self->{'enc'};
154 1         4 $_ = encode_data($_, $enc) for grep { $_ } @$_{'artist', 'title', 'album'};
  3         9  
155 1         4 1;
156             }
157 2         6 grep { $_->{'title'} && $_->{'artist'} }
158             splice @$list, 0, $self->{'limit'}
159             ];
160 2 100       9 return $self->_error('Need artist/title name') unless @$list;
161            
162 1         2 my $i;
163 1 50       6 my $r = POST($self->{'hs'}->{'url'}->{'sm'}, [
164             's' => $self->{'hs'}->{'sid'},
165             map {
166 1         5 $i = defined $i ? $i+1 : 0;
167             (
168 1   33     35 "a[$i]" => $_->{'artist'},
      50        
169             "t[$i]" => $_->{'title' },
170             "i[$i]" => $_->{'time' } || time,
171             "o[$i]" => $_->{'source'} || 'R',
172             "r[$i]" => $_->{'rating'},
173             "l[$i]" => $_->{'length'},
174             "b[$i]" => $_->{'album' },
175             "n[$i]" => $_->{'id' },
176             "m[$i]" => $_->{'mb_id' },
177             );
178             }
179             @$list
180             ]);
181            
182 1         603 DEBUG && warn $r->as_string;
183            
184 1         5 return $r;
185             }
186              
187             # parse response
188              
189             sub _response {
190 4     4   1795 my $self = shift;
191 4         5 my $r = shift;
192            
193 4 100 100     29 return $self->_error('No response object') unless $r && ref $r eq 'HTTP::Response';
194            
195 2         3 DEBUG && warn join "\n", $r->status_line, $r->content;
196            
197 1 50       57 return $r->is_success && $r->content =~ /^ (OK) ( \n (\w+) \n (\S+) \n (\S+) )? /sx
198             ? {'status' => $1, $2 ? ('sid' => $3, 'url' => {'np' => $4, 'sm' => $5} ) : ()}
199 2 50 66     58 : {'code' => $r->code, map { ('error' => $_->[0], $_->[1] ? ('reason' => $_->[1]) : ()) } [$r->content =~ /^(\S+)(?:\s+(.*))?/]}
    100          
200             ;
201             }
202              
203             # generate error
204              
205             sub _error {
206 8     8   24 shift;
207 8         77 return {'error' => 'ERROR', 'reason' => shift};
208             }
209              
210             # encode data
211              
212             sub encode_data($$) {
213 5     5 1 1088 my $data = shift;
214 5         6 my $enc = shift;
215            
216 3     3   3922 use Encode ();
  3         35744  
  3         309  
217 5 50       55 DEBUG && warn("Encode data $enc to utf8"), $data = Encode::encode_utf8 Encode::decode($enc, $data) unless Encode::is_utf8($data);
218 5         202 Encode::_utf8_off($data);
219            
220 5         18 return $data;
221             }
222              
223             1;
224              
225             __END__