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__ |