line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Net::LastFMAPI; |
2
|
1
|
|
|
1
|
|
26850
|
use strict; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
30
|
|
3
|
1
|
|
|
1
|
|
5
|
use warnings; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
25
|
|
4
|
1
|
|
|
1
|
|
10
|
use v5.10; |
|
1
|
|
|
|
|
7
|
|
|
1
|
|
|
|
|
34
|
|
5
|
1
|
|
|
1
|
|
10753
|
use LWP::UserAgent; |
|
1
|
|
|
|
|
147367
|
|
|
1
|
|
|
|
|
41
|
|
6
|
1
|
|
|
1
|
|
92
|
use Digest::MD5 'md5_hex'; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
93
|
|
7
|
1
|
|
|
1
|
|
1294
|
use JSON::XS; |
|
1
|
|
|
|
|
27200
|
|
|
1
|
|
|
|
|
2380
|
|
8
|
1
|
|
|
1
|
|
2179
|
use YAML::Syck; |
|
1
|
|
|
|
|
4319
|
|
|
1
|
|
|
|
|
944
|
|
9
|
1
|
|
|
1
|
|
1337
|
use File::Slurp; |
|
1
|
|
|
|
|
17070
|
|
|
1
|
|
|
|
|
87
|
|
10
|
1
|
|
|
1
|
|
12
|
use File::Path 'make_path'; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
64
|
|
11
|
1
|
|
|
1
|
|
16269
|
use File::HomeDir 'my_home'; |
|
1
|
|
|
|
|
8399
|
|
|
1
|
|
|
|
|
88
|
|
12
|
1
|
|
|
1
|
|
11
|
use URI; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
30
|
|
13
|
1
|
|
|
1
|
|
5
|
use Exporter 'import'; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
47
|
|
14
|
|
|
|
|
|
|
our @EXPORT = ('lastfm', 'lastfm_config', 'lastfm_iter'); |
15
|
1
|
|
|
1
|
|
6
|
use Carp; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
409
|
|
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
our $VERSION = "0.63"; |
18
|
|
|
|
|
|
|
our $url = 'http://ws.audioscrobbler.com/2.0/'; |
19
|
|
|
|
|
|
|
our $api_key = 'dfab9b1c7357c55028c84b9a8fb68880'; |
20
|
|
|
|
|
|
|
our $secret = 'd004c86dcfa8ef4c3977b04f558535f2'; |
21
|
|
|
|
|
|
|
our $session_key; # see load_save_sessionkey() |
22
|
|
|
|
|
|
|
our $ua = new LWP::UserAgent(agent => "Net::LastFMAPI/$VERSION"); |
23
|
|
|
|
|
|
|
our $username; # not important |
24
|
|
|
|
|
|
|
our $xml = 0; |
25
|
|
|
|
|
|
|
our $cache = 0; |
26
|
|
|
|
|
|
|
our $cache_dir = my_home()."/.net-lastfmapi-cache/"; |
27
|
|
|
|
|
|
|
our $sk_savefile = my_home()."/.net-lastfmapi-sessionkey"; |
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
sub load_save_sessionkey { # see get_session_key() |
30
|
0
|
|
|
0
|
0
|
0
|
my $key = shift; |
31
|
0
|
0
|
|
|
|
0
|
if ($key) { |
32
|
0
|
|
|
|
|
0
|
write_file($sk_savefile, $key); |
33
|
|
|
|
|
|
|
} |
34
|
|
|
|
|
|
|
else { |
35
|
0
|
|
|
|
|
0
|
$key = eval{ read_file($sk_savefile) }; |
|
0
|
|
|
|
|
0
|
|
36
|
|
|
|
|
|
|
} |
37
|
0
|
|
|
|
|
0
|
$session_key = $key; |
38
|
|
|
|
|
|
|
} |
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
sub lastfm_config { |
41
|
2
|
|
|
2
|
0
|
519
|
my %configs = @_; |
42
|
2
|
|
|
|
|
5
|
for my $k (qw{api_key secret session_key ua xml cache cache_dir sk_savefile}) { |
43
|
16
|
|
|
|
|
30
|
my $v = delete $configs{$k}; |
44
|
16
|
100
|
|
|
|
34
|
if (defined $v) { |
45
|
1
|
|
|
1
|
|
16
|
no strict 'refs'; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
3916
|
|
46
|
1
|
|
|
|
|
2
|
${$k} = $v; |
|
1
|
|
|
|
|
4
|
|
47
|
|
|
|
|
|
|
} |
48
|
|
|
|
|
|
|
} |
49
|
2
|
100
|
|
|
|
201
|
croak "invalid config items: ".join(", ", keys %configs) if keys %configs; |
50
|
|
|
|
|
|
|
} |
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
sub dumpfile { |
53
|
0
|
|
|
0
|
0
|
0
|
my $file = shift; |
54
|
0
|
|
|
|
|
0
|
my $json = encode_json(shift); |
55
|
0
|
|
|
|
|
0
|
write_file($file, $json); |
56
|
|
|
|
|
|
|
} |
57
|
|
|
|
|
|
|
sub loadfile { |
58
|
0
|
|
|
0
|
0
|
0
|
my $file = shift; |
59
|
0
|
|
|
|
|
0
|
my $json = read_file($file); |
60
|
0
|
|
|
|
|
0
|
decode_json($json); |
61
|
|
|
|
|
|
|
} |
62
|
|
|
|
|
|
|
#{{{ |
63
|
|
|
|
|
|
|
our $methods = { |
64
|
|
|
|
|
|
|
'album.addtags' => {auth => 1, post => 1, signed => 1, id => 302}, |
65
|
|
|
|
|
|
|
'album.getbuylinks' => {id => 429}, |
66
|
|
|
|
|
|
|
'album.getinfo' => {id => 290}, |
67
|
|
|
|
|
|
|
'album.getshouts' => {page => 1, id => 450}, |
68
|
|
|
|
|
|
|
'album.gettags' => {auth => 1, signed => 1, id => 317}, |
69
|
|
|
|
|
|
|
'album.gettoptags' => {id => 438}, |
70
|
|
|
|
|
|
|
'album.removetag' => {auth => 1, post => 1, signed => 1, id => 314}, |
71
|
|
|
|
|
|
|
'album.search' => {page => 1, id => 357}, |
72
|
|
|
|
|
|
|
'album.share' => {auth => 1, post => 1, signed => 1, id => 436}, |
73
|
|
|
|
|
|
|
'artist.addtags' => {auth => 1, post => 1, signed => 1, id => 303}, |
74
|
|
|
|
|
|
|
'artist.getcorrection' => {id => 446}, |
75
|
|
|
|
|
|
|
'artist.getevents' => {page => 1, id => 117}, |
76
|
|
|
|
|
|
|
'artist.getimages' => {page => 1, id => 407}, |
77
|
|
|
|
|
|
|
'artist.getinfo' => {id => 267}, |
78
|
|
|
|
|
|
|
'artist.getpastevents' => {page => 1, id => 428}, |
79
|
|
|
|
|
|
|
'artist.getpodcast' => {id => 118}, |
80
|
|
|
|
|
|
|
'artist.getshouts' => {page => 1, id => 397}, |
81
|
|
|
|
|
|
|
'artist.getsimilar' => {id => 119}, |
82
|
|
|
|
|
|
|
'artist.gettags' => {auth => 1, signed => 1, id => 318}, |
83
|
|
|
|
|
|
|
'artist.gettopalbums' => {page => 1, id => 287}, |
84
|
|
|
|
|
|
|
'artist.gettopfans' => {id => 310}, |
85
|
|
|
|
|
|
|
'artist.gettoptags' => {id => 288}, |
86
|
|
|
|
|
|
|
'artist.gettoptracks' => {page => 1, id => 277}, |
87
|
|
|
|
|
|
|
'artist.removetag' => {auth => 1, post => 1, signed => 1, id => 315}, |
88
|
|
|
|
|
|
|
'artist.search' => {page => 1, id => 272}, |
89
|
|
|
|
|
|
|
'artist.share' => {auth => 1, post => 1, signed => 1, id => 306}, |
90
|
|
|
|
|
|
|
'artist.shout' => {auth => 1, post => 1, signed => 1, id => 408}, |
91
|
|
|
|
|
|
|
'auth.getmobilesession' => {signed => 1, id => 266}, |
92
|
|
|
|
|
|
|
'auth.getsession' => {signed => 1, id => 125}, |
93
|
|
|
|
|
|
|
'auth.gettoken' => {signed => 1, id => 265}, |
94
|
|
|
|
|
|
|
'chart.gethypedartists' => {page => 1, id => 493}, |
95
|
|
|
|
|
|
|
'chart.gethypedtracks' => {page => 1, id => 494}, |
96
|
|
|
|
|
|
|
'chart.getlovedtracks' => {page => 1, id => 495}, |
97
|
|
|
|
|
|
|
'chart.gettopartists' => {page => 1, id => 496}, |
98
|
|
|
|
|
|
|
'chart.gettoptags' => {page => 1, id => 497}, |
99
|
|
|
|
|
|
|
'chart.gettoptracks' => {page => 1, id => 498}, |
100
|
|
|
|
|
|
|
'event.attend' => {auth => 1, post => 1, signed => 1, id => 307}, |
101
|
|
|
|
|
|
|
'event.getattendees' => {page => 1, id => 391}, |
102
|
|
|
|
|
|
|
'event.getinfo' => {id => 292}, |
103
|
|
|
|
|
|
|
'event.getshouts' => {page => 1, id => 399}, |
104
|
|
|
|
|
|
|
'event.share' => {auth => 1, post => 1, signed => 1, id => 350}, |
105
|
|
|
|
|
|
|
'event.shout' => {auth => 1, post => 1, signed => 1, id => 409}, |
106
|
|
|
|
|
|
|
'geo.getevents' => {page => 1, id => 270}, |
107
|
|
|
|
|
|
|
'geo.getmetroartistchart' => {id => 421}, |
108
|
|
|
|
|
|
|
'geo.getmetrohypeartistchart' => {id => 420}, |
109
|
|
|
|
|
|
|
'geo.getmetrohypetrackchart' => {id => 422}, |
110
|
|
|
|
|
|
|
'geo.getmetrotrackchart' => {id => 423}, |
111
|
|
|
|
|
|
|
'geo.getmetrouniqueartistchart' => {id => 424}, |
112
|
|
|
|
|
|
|
'geo.getmetrouniquetrackchart' => {id => 425}, |
113
|
|
|
|
|
|
|
'geo.getmetroweeklychartlist' => {id => 426}, |
114
|
|
|
|
|
|
|
'geo.getmetros' => {id => 435}, |
115
|
|
|
|
|
|
|
'geo.gettopartists' => {page => 1, id => 297}, |
116
|
|
|
|
|
|
|
'geo.gettoptracks' => {page => 1, id => 298}, |
117
|
|
|
|
|
|
|
'group.gethype' => {id => 259}, |
118
|
|
|
|
|
|
|
'group.getmembers' => {page => 1, id => 379}, |
119
|
|
|
|
|
|
|
'group.getweeklyalbumchart' => {id => 293}, |
120
|
|
|
|
|
|
|
'group.getweeklyartistchart' => {id => 294}, |
121
|
|
|
|
|
|
|
'group.getweeklychartlist' => {id => 295}, |
122
|
|
|
|
|
|
|
'group.getweeklytrackchart' => {id => 296}, |
123
|
|
|
|
|
|
|
'library.addalbum' => {auth => 1, post => 1, signed => 1, id => 370}, |
124
|
|
|
|
|
|
|
'library.addartist' => {auth => 1, post => 1, signed => 1, id => 371}, |
125
|
|
|
|
|
|
|
'library.addtrack' => {auth => 1, post => 1, signed => 1, id => 372}, |
126
|
|
|
|
|
|
|
'library.getalbums' => {page => 1, id => 321}, |
127
|
|
|
|
|
|
|
'library.getartists' => {page => 1, id => 322}, |
128
|
|
|
|
|
|
|
'library.gettracks' => {page => 1, id => 323}, |
129
|
|
|
|
|
|
|
'library.removealbum' => {auth => 1, post => 1, signed => 1, id => 523}, |
130
|
|
|
|
|
|
|
'library.removeartist' => {auth => 1, post => 1, signed => 1, id => 524}, |
131
|
|
|
|
|
|
|
'library.removescrobble' => {auth => 1, post => 1, signed => 1, id => 525}, |
132
|
|
|
|
|
|
|
'library.removetrack' => {auth => 1, post => 1, signed => 1, id => 526}, |
133
|
|
|
|
|
|
|
'playlist.addtrack' => {auth => 1, post => 1, signed => 1, id => 337}, |
134
|
|
|
|
|
|
|
'playlist.create' => {auth => 1, post => 1, signed => 1, id => 365}, |
135
|
|
|
|
|
|
|
'radio.getplaylist' => {auth => 1, signed => 1, id => 256}, |
136
|
|
|
|
|
|
|
'radio.search' => {id => 418}, |
137
|
|
|
|
|
|
|
'radio.tune' => {auth => 1, post => 1, signed => 1, id => 160}, |
138
|
|
|
|
|
|
|
'tag.getinfo' => {id => 452}, |
139
|
|
|
|
|
|
|
'tag.getsimilar' => {id => 311}, |
140
|
|
|
|
|
|
|
'tag.gettopalbums' => {page => 1, id => 283}, |
141
|
|
|
|
|
|
|
'tag.gettopartists' => {page => 1, id => 284}, |
142
|
|
|
|
|
|
|
'tag.gettoptags' => {id => 276}, |
143
|
|
|
|
|
|
|
'tag.gettoptracks' => {page => 1, id => 285}, |
144
|
|
|
|
|
|
|
'tag.getweeklyartistchart' => {id => 358}, |
145
|
|
|
|
|
|
|
'tag.getweeklychartlist' => {id => 359}, |
146
|
|
|
|
|
|
|
'tag.search' => {page => 1, id => 273}, |
147
|
|
|
|
|
|
|
'tasteometer.compare' => {id => 258}, |
148
|
|
|
|
|
|
|
'tasteometer.comparegroup' => {id => 500}, |
149
|
|
|
|
|
|
|
'track.addtags' => {auth => 1, post => 1, signed => 1, id => 304}, |
150
|
|
|
|
|
|
|
'track.ban' => {auth => 1, post => 1, signed => 1, id => 261}, |
151
|
|
|
|
|
|
|
'track.getbuylinks' => {id => 431}, |
152
|
|
|
|
|
|
|
'track.getcorrection' => {id => 447}, |
153
|
|
|
|
|
|
|
'track.getfingerprintmetadata' => {id => 441}, |
154
|
|
|
|
|
|
|
'track.getinfo' => {id => 356}, |
155
|
|
|
|
|
|
|
'track.getshouts' => {page => 1, id => 453}, |
156
|
|
|
|
|
|
|
'track.getsimilar' => {id => 319}, |
157
|
|
|
|
|
|
|
'track.gettags' => {auth => 1, signed => 1, id => 320}, |
158
|
|
|
|
|
|
|
'track.gettopfans' => {id => 312}, |
159
|
|
|
|
|
|
|
'track.gettoptags' => {id => 289}, |
160
|
|
|
|
|
|
|
'track.love' => {auth => 1, post => 1, signed => 1, id => 260}, |
161
|
|
|
|
|
|
|
'track.removetag' => {auth => 1, post => 1, signed => 1, id => 316}, |
162
|
|
|
|
|
|
|
'track.scrobble' => {auth => 1, post => 1, signed => 1, id => 443}, |
163
|
|
|
|
|
|
|
'track.search' => {page => 1, id => 286}, |
164
|
|
|
|
|
|
|
'track.share' => {auth => 1, post => 1, signed => 1, id => 305}, |
165
|
|
|
|
|
|
|
'track.unban' => {auth => 1, post => 1, signed => 1, id => 449}, |
166
|
|
|
|
|
|
|
'track.unlove' => {auth => 1, post => 1, signed => 1, id => 440}, |
167
|
|
|
|
|
|
|
'track.updatenowplaying' => {auth => 1, post => 1, signed => 1, id => 454}, |
168
|
|
|
|
|
|
|
'user.getartisttracks' => {page => 1, id => 432}, |
169
|
|
|
|
|
|
|
'user.getbannedtracks' => {page => 1, id => 448}, |
170
|
|
|
|
|
|
|
'user.getevents' => {page => 1, id => 291}, |
171
|
|
|
|
|
|
|
'user.getfriends' => {page => 1, id => 263}, |
172
|
|
|
|
|
|
|
'user.getinfo' => {auth => 1, id => 344}, |
173
|
|
|
|
|
|
|
'user.getlovedtracks' => {page => 1, id => 329}, |
174
|
|
|
|
|
|
|
'user.getneighbours' => {id => 264}, |
175
|
|
|
|
|
|
|
'user.getnewreleases' => {id => 444}, |
176
|
|
|
|
|
|
|
'user.getpastevents' => {page => 1, id => 343}, |
177
|
|
|
|
|
|
|
'user.getpersonaltags' => {page => 1, id => 455}, |
178
|
|
|
|
|
|
|
'user.getplaylists' => {id => 313}, |
179
|
|
|
|
|
|
|
'user.getrecentstations' => {auth => 1, signed => 1, page => 1, id => 414}, |
180
|
|
|
|
|
|
|
'user.getrecenttracks' => {page => 1, id => 278}, |
181
|
|
|
|
|
|
|
'user.getrecommendedartists' => {auth => 1, signed => 1, page => 1, id => 388}, |
182
|
|
|
|
|
|
|
'user.getrecommendedevents' => {auth => 1, signed => 1, page => 1, id => 375}, |
183
|
|
|
|
|
|
|
'user.getshouts' => {page => 1, id => 401}, |
184
|
|
|
|
|
|
|
'user.gettopalbums' => {page => 1, id => 299}, |
185
|
|
|
|
|
|
|
'user.gettopartists' => {page => 1, id => 300}, |
186
|
|
|
|
|
|
|
'user.gettoptags' => {id => 123}, |
187
|
|
|
|
|
|
|
'user.gettoptracks' => {page => 1, id => 301}, |
188
|
|
|
|
|
|
|
'user.getweeklyalbumchart' => {id => 279}, |
189
|
|
|
|
|
|
|
'user.getweeklyartistchart' => {id => 281}, |
190
|
|
|
|
|
|
|
'user.getweeklychartlist' => {id => 280}, |
191
|
|
|
|
|
|
|
'user.getweeklytrackchart' => {id => 282}, |
192
|
|
|
|
|
|
|
'user.shout' => {auth => 1, post => 1, signed => 1, id => 411}, |
193
|
|
|
|
|
|
|
'venue.getevents' => {id => 394}, |
194
|
|
|
|
|
|
|
'venue.getpastevents' => {page => 1, id => 395}, |
195
|
|
|
|
|
|
|
'venue.search' => {page => 1, id => 396}, |
196
|
|
|
|
|
|
|
}; |
197
|
|
|
|
|
|
|
#}}} |
198
|
|
|
|
|
|
|
our %last_params; |
199
|
|
|
|
|
|
|
our $last_response; |
200
|
|
|
|
|
|
|
our %last_response_meta; |
201
|
|
|
|
|
|
|
sub lastfm { |
202
|
4
|
|
|
4
|
0
|
7159
|
my ($method, @params) = @_; |
203
|
4
|
|
|
|
|
10
|
$method = lc($method); |
204
|
|
|
|
|
|
|
|
205
|
4
|
|
|
|
|
8
|
my %params; |
206
|
4
|
|
|
|
|
23
|
my $i = 0; |
207
|
4
|
|
|
|
|
16
|
while (my $p = shift @params) { |
208
|
57
|
100
|
|
|
|
91
|
if (ref $p eq "HASH") { |
209
|
55
|
|
|
|
|
118
|
while (my ($k,$v) = each %$p) { |
210
|
165
|
|
|
|
|
718
|
$params{$k."[".$i."]"} = $v; |
211
|
|
|
|
|
|
|
} |
212
|
55
|
100
|
|
|
|
229
|
croak "too multitudinous (limit 50)" if $i > 49; |
213
|
54
|
|
|
|
|
117
|
$i++ |
214
|
|
|
|
|
|
|
} |
215
|
|
|
|
|
|
|
else { |
216
|
2
|
|
|
|
|
9
|
$params{$p} = shift @params; |
217
|
|
|
|
|
|
|
} |
218
|
|
|
|
|
|
|
} |
219
|
3
|
|
|
|
|
8
|
$params{method} = $method; |
220
|
3
|
|
|
|
|
8
|
$params{api_key} = $api_key; |
221
|
3
|
50
|
66
|
|
|
19
|
$params{format} = "json" unless $params{format} || $xml; |
222
|
3
|
100
|
66
|
|
|
25
|
delete $params{format} if $params{format} && $params{format} eq "xml"; |
223
|
|
|
|
|
|
|
|
224
|
3
|
50
|
33
|
|
|
26
|
unless (exists $methods->{$method}) { |
|
|
50
|
|
|
|
|
|
225
|
0
|
|
|
|
|
0
|
carp "method $method is not known to Net::LastFMAPI" |
226
|
|
|
|
|
|
|
} |
227
|
|
|
|
|
|
|
elsif (defined $params{page} && !$methods->{$method}->{page}) { |
228
|
0
|
|
|
|
|
0
|
carp "method $method is not known to be paginated, but hey" |
229
|
|
|
|
|
|
|
} |
230
|
|
|
|
|
|
|
|
231
|
3
|
|
|
|
|
9
|
sessionise(\%params); |
232
|
|
|
|
|
|
|
|
233
|
3
|
|
|
|
|
13
|
sign(\%params); |
234
|
|
|
|
|
|
|
|
235
|
3
|
|
|
|
|
26
|
%last_params = %params; |
236
|
|
|
|
|
|
|
|
237
|
3
|
|
|
|
|
7
|
my $cache = $cache; |
238
|
3
|
50
|
|
|
|
9
|
if ( $cache ) { |
239
|
0
|
0
|
|
|
|
0
|
unless ( -d $cache ) { |
240
|
0
|
|
|
|
|
0
|
$cache = $cache_dir; |
241
|
0
|
|
|
|
|
0
|
make_path( $cache ); |
242
|
|
|
|
|
|
|
} |
243
|
0
|
|
|
|
|
0
|
my $cache_key_json = encode_json( [ map { $_, $params{$_} } sort keys %params ] ); |
|
0
|
|
|
|
|
0
|
|
244
|
0
|
|
|
|
|
0
|
my $file = "$cache/" . md5_hex( $cache_key_json ); |
245
|
0
|
0
|
|
|
|
0
|
if ( -f $file ) { |
246
|
0
|
|
|
|
|
0
|
my $data = loadfile( $file ); |
247
|
0
|
|
|
|
|
0
|
return _rowify_content( $data->{content} ); |
248
|
|
|
|
|
|
|
} |
249
|
|
|
|
|
|
|
|
250
|
0
|
|
|
|
|
0
|
$cache = $file; |
251
|
|
|
|
|
|
|
} |
252
|
|
|
|
|
|
|
|
253
|
3
|
|
|
|
|
6
|
my $res; |
254
|
3
|
100
|
|
|
|
10
|
if ($methods->{$method}->{post}) { |
255
|
1
|
|
|
|
|
6
|
$res = $ua->post($url, Content => \%params); |
256
|
|
|
|
|
|
|
} |
257
|
|
|
|
|
|
|
else { |
258
|
2
|
|
|
|
|
18
|
my $uri = URI->new($url); |
259
|
2
|
|
|
|
|
10334
|
$uri->query_form(%params); |
260
|
2
|
|
|
|
|
383
|
$res = $ua->get($uri); |
261
|
|
|
|
|
|
|
} |
262
|
|
|
|
|
|
|
|
263
|
3
|
|
100
|
|
|
678
|
$params{format} ||= "xml"; |
264
|
3
|
|
|
|
|
11
|
my $content = $res->decoded_content; |
265
|
3
|
50
|
|
|
|
23
|
croak "Last.fm contains faulty data for a piece of data you requested and " |
266
|
|
|
|
|
|
|
. "is unable to return a useful reply. Will be treated as an empty reply." |
267
|
|
|
|
|
|
|
if $content eq qq|""\n|; |
268
|
|
|
|
|
|
|
|
269
|
3
|
|
|
2
|
|
15
|
my $decoded_json = sub { $content = decode_json($content); }; |
|
2
|
|
|
|
|
82
|
|
270
|
3
|
50
|
66
|
|
|
12
|
unless ($res->is_success && |
|
|
|
33
|
|
|
|
|
271
|
|
|
|
|
|
|
($params{format} eq "json" && !exists($decoded_json->()->{error}) |
272
|
|
|
|
|
|
|
|| $params{format} eq "xml" && $content =~ //)) { |
273
|
|
|
|
|
|
|
|
274
|
0
|
|
|
|
|
0
|
my @clues; |
275
|
0
|
0
|
|
|
|
0
|
if ($res->is_success) { |
276
|
0
|
0
|
|
|
|
0
|
if ($res->decoded_content =~ /Invalid session key - Please re-authenticate/) { |
|
|
0
|
|
|
|
|
|
277
|
0
|
|
|
|
|
0
|
push @clues, "Set NET_LASTFMAPI_REAUTH=1 to re-authenticate"; |
278
|
|
|
|
|
|
|
} |
279
|
|
|
|
|
|
|
elsif ($methods->{$method}) { |
280
|
0
|
|
|
|
|
0
|
push @clues, "Documentation for the '$method' method:\n" |
281
|
|
|
|
|
|
|
." http://www.last.fm/api/show/?service=$methods->{$method}->{id}" |
282
|
|
|
|
|
|
|
} |
283
|
|
|
|
|
|
|
} |
284
|
|
|
|
|
|
|
|
285
|
0
|
0
|
|
|
|
0
|
if (ref $content eq "HASH") { |
286
|
0
|
|
|
|
|
0
|
$content = "Content translated JSON->YAML:\n".Dump($content); |
287
|
|
|
|
|
|
|
} |
288
|
|
|
|
|
|
|
else { |
289
|
0
|
|
|
|
|
0
|
$content = "Content:\n$content"; |
290
|
|
|
|
|
|
|
} |
291
|
|
|
|
|
|
|
|
292
|
0
|
|
|
|
|
0
|
croak join("\n", |
293
|
|
|
|
|
|
|
"Something went wrong.", |
294
|
|
|
|
|
|
|
"HTTP Status: ".$res->status_line, |
295
|
|
|
|
|
|
|
@clues, |
296
|
|
|
|
|
|
|
"", |
297
|
|
|
|
|
|
|
$content, |
298
|
|
|
|
|
|
|
"" |
299
|
|
|
|
|
|
|
); |
300
|
|
|
|
|
|
|
} |
301
|
|
|
|
|
|
|
|
302
|
3
|
50
|
|
|
|
24
|
if ($cache) { |
303
|
0
|
|
|
|
|
0
|
dumpfile($cache, {content => $content}); |
304
|
|
|
|
|
|
|
} |
305
|
3
|
|
|
|
|
4
|
$last_response = $content; |
306
|
3
|
|
|
|
|
11
|
return _rowify_content( $content ); |
307
|
|
|
|
|
|
|
} |
308
|
|
|
|
|
|
|
|
309
|
|
|
|
|
|
|
sub _rowify_content { |
310
|
3
|
|
|
3
|
|
5
|
my ( $content ) = @_; |
311
|
3
|
50
|
|
|
|
8
|
return extract_rows( $content ) if wantarray; |
312
|
3
|
|
|
|
|
28
|
return $content; |
313
|
|
|
|
|
|
|
} |
314
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
sub extract_rows { |
316
|
0
|
|
|
0
|
0
|
0
|
my ( $content ) = @_; |
317
|
0
|
0
|
|
|
|
0
|
if (!$last_params{format}) { |
318
|
0
|
|
|
|
|
0
|
croak "returning rows from xml is not supported"; |
319
|
|
|
|
|
|
|
} |
320
|
0
|
|
|
|
|
0
|
my @main_keys = keys %{$content}; |
|
0
|
|
|
|
|
0
|
|
321
|
0
|
|
|
|
|
0
|
my $main_data = $content->{$main_keys[0]}; |
322
|
0
|
|
|
|
|
0
|
my @data_keys = sort keys %{$main_data}; |
|
0
|
|
|
|
|
0
|
|
323
|
0
|
0
|
0
|
|
|
0
|
unless (@main_keys == 1 && @data_keys == 2 && $data_keys[0] eq '@attr') { |
|
|
|
0
|
|
|
|
|
324
|
0
|
|
|
|
|
0
|
my ( $text, $total ) = ( $main_data->{'#text'}, $main_data->{total} ); |
325
|
0
|
0
|
0
|
|
|
0
|
return if defined $text && $text =~ /^\s+$/ && defined $total && $total == 0; # no rows |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
326
|
0
|
|
|
|
|
0
|
carp "extracting rows may be broken"; |
327
|
|
|
|
|
|
|
} |
328
|
0
|
|
|
|
|
0
|
%last_response_meta = %{ $main_data->{$data_keys[0]} }; |
|
0
|
|
|
|
|
0
|
|
329
|
0
|
|
|
|
|
0
|
my $rows = $main_data->{$data_keys[1]}; |
330
|
0
|
0
|
|
|
|
0
|
if (ref $rows ne "ARRAY") { |
331
|
|
|
|
|
|
|
# schemaless translation of xml to data creates these cases |
332
|
0
|
0
|
|
|
|
0
|
if (ref $rows eq "HASH") { # 1 row |
|
|
0
|
|
|
|
|
|
333
|
0
|
|
|
|
|
0
|
$rows = [ $rows ]; |
334
|
|
|
|
|
|
|
} |
335
|
|
|
|
|
|
|
elsif ($rows =~ /^\s+$/) { # no rows |
336
|
0
|
|
|
|
|
0
|
$rows = []; |
337
|
0
|
|
|
|
|
0
|
carp "got whitespacey string instead of empty row array, this happens" |
338
|
|
|
|
|
|
|
} |
339
|
|
|
|
|
|
|
else { |
340
|
0
|
|
|
|
|
0
|
carp "not an array of rows... '$rows' returning ()"; |
341
|
|
|
|
|
|
|
} |
342
|
|
|
|
|
|
|
} |
343
|
0
|
|
|
|
|
0
|
return @$rows; |
344
|
|
|
|
|
|
|
} |
345
|
|
|
|
|
|
|
|
346
|
|
|
|
|
|
|
sub lastfm_iter { |
347
|
0
|
|
|
0
|
0
|
0
|
my @rows = lastfm(@_, page => 1); |
348
|
0
|
|
|
|
|
0
|
my $params = { %last_params }; |
349
|
0
|
0
|
|
|
|
0
|
if (!$params->{format}) { |
350
|
0
|
|
|
|
|
0
|
croak "paginating xml is not supported"; |
351
|
|
|
|
|
|
|
} |
352
|
0
|
0
|
|
|
|
0
|
if (@rows == 0) { |
353
|
0
|
|
|
0
|
|
0
|
return sub { }; |
|
0
|
|
|
|
|
0
|
|
354
|
|
|
|
|
|
|
} |
355
|
0
|
|
|
|
|
0
|
my $page = $last_response_meta{page}; |
356
|
0
|
|
|
|
|
0
|
my $totalpages = $last_response_meta{totalPages}; |
357
|
|
|
|
|
|
|
my $next_page = sub { |
358
|
0
|
0
|
|
0
|
|
0
|
return () if $page++ >= $totalpages; |
359
|
0
|
|
|
|
|
0
|
my %params = %$params; |
360
|
0
|
|
|
|
|
0
|
$params{page} = $page; |
361
|
0
|
|
|
|
|
0
|
my $method = delete $params{method}; |
362
|
0
|
|
|
|
|
0
|
my @rows = lastfm($method, %params); |
363
|
0
|
|
|
|
|
0
|
return @rows; |
364
|
0
|
|
|
|
|
0
|
}; |
365
|
|
|
|
|
|
|
return sub { |
366
|
0
|
0
|
|
0
|
|
0
|
unless (@rows) { |
367
|
0
|
|
|
|
|
0
|
push @rows, $next_page->(); |
368
|
|
|
|
|
|
|
} |
369
|
0
|
|
|
|
|
0
|
return shift @rows; |
370
|
|
|
|
|
|
|
} |
371
|
0
|
|
|
|
|
0
|
} |
372
|
|
|
|
|
|
|
|
373
|
|
|
|
|
|
|
sub sessionise { |
374
|
3
|
|
|
3
|
0
|
5
|
my $params = shift; |
375
|
3
|
|
|
|
|
10
|
my $m = $methods->{$params->{method}}; |
376
|
3
|
50
|
33
|
|
|
29
|
unless (delete $params->{auth} || $m && $m->{auth}) { |
|
|
|
33
|
|
|
|
|
377
|
|
|
|
|
|
|
return |
378
|
0
|
|
|
|
|
0
|
} |
379
|
3
|
|
|
|
|
7
|
$params->{sk} = get_session_key(); |
380
|
|
|
|
|
|
|
} |
381
|
|
|
|
|
|
|
|
382
|
|
|
|
|
|
|
sub get_session_key { |
383
|
3
|
50
|
|
3
|
0
|
11
|
unless (defined $session_key) { |
384
|
0
|
|
|
|
|
0
|
load_save_sessionkey() |
385
|
|
|
|
|
|
|
} |
386
|
3
|
50
|
|
|
|
8
|
unless (defined $session_key) { |
387
|
0
|
|
|
|
|
0
|
my $key; |
388
|
0
|
|
|
|
|
0
|
eval { $key = request_session(); }; |
|
0
|
|
|
|
|
0
|
|
389
|
0
|
0
|
|
|
|
0
|
if ($@) { |
390
|
0
|
|
|
|
|
0
|
die "--- Died while making requests to get a session:\n$@"; |
391
|
|
|
|
|
|
|
} |
392
|
0
|
|
|
|
|
0
|
load_save_sessionkey($key); |
393
|
|
|
|
|
|
|
} |
394
|
3
|
|
50
|
|
|
12
|
return $session_key || die "unable to acquire session key..."; |
395
|
|
|
|
|
|
|
} |
396
|
|
|
|
|
|
|
|
397
|
|
|
|
|
|
|
sub request_session { |
398
|
0
|
|
|
0
|
0
|
0
|
my $res = lastfm("auth.gettoken", format => "xml"); |
399
|
|
|
|
|
|
|
|
400
|
0
|
0
|
|
|
|
0
|
my ($token) = $res =~ m{(.+)} |
401
|
|
|
|
|
|
|
or die "no foundo token: $res"; |
402
|
|
|
|
|
|
|
|
403
|
0
|
|
|
|
|
0
|
talk_authorisation($token); |
404
|
|
|
|
|
|
|
|
405
|
0
|
|
|
|
|
0
|
my $sess = lastfm("auth.getSession", token => $token, format => "xml"); |
406
|
|
|
|
|
|
|
|
407
|
0
|
0
|
|
|
|
0
|
($username) = $sess =~ m{(.+)} |
408
|
|
|
|
|
|
|
or die "no name!? $sess"; |
409
|
0
|
0
|
|
|
|
0
|
my ($key) = $sess =~ m{(.+)} |
410
|
|
|
|
|
|
|
or die "no key!? $sess"; |
411
|
0
|
|
|
|
|
0
|
return $key; |
412
|
|
|
|
|
|
|
} |
413
|
|
|
|
|
|
|
|
414
|
|
|
|
|
|
|
|
415
|
|
|
|
|
|
|
sub talk_authorisation { |
416
|
0
|
|
|
0
|
0
|
0
|
my $token = shift; |
417
|
0
|
|
|
|
|
0
|
say "Sorry about this but could you go over here: " |
418
|
|
|
|
|
|
|
."http://www.last.fm/api/auth/?api_key=" |
419
|
|
|
|
|
|
|
.$api_key."&token=".$token; |
420
|
0
|
|
|
|
|
0
|
say "Hit enter to continue..."; |
421
|
0
|
|
|
|
|
0
|
; |
422
|
|
|
|
|
|
|
} |
423
|
|
|
|
|
|
|
|
424
|
|
|
|
|
|
|
sub sign { |
425
|
3
|
|
|
3
|
0
|
6
|
my $params = shift; |
426
|
3
|
100
|
|
|
|
12
|
return unless $methods->{$params->{method}}->{signed}; |
427
|
15
|
|
66
|
|
|
32
|
my $jumble = join "", map { $_ => $params->{$_} } |
|
16
|
|
|
|
|
57
|
|
428
|
1
|
|
|
|
|
17
|
grep { !($_ eq "format" || $_ eq "callback") } sort keys %$params; |
429
|
1
|
|
|
|
|
15
|
my $hash = md5_hex($jumble.$secret); |
430
|
1
|
|
|
|
|
4
|
$params->{api_sig} = $hash; |
431
|
|
|
|
|
|
|
} |
432
|
|
|
|
|
|
|
|
433
|
|
|
|
|
|
|
if ($ENV{NET_LASTFMAPI_REAUTH}) { |
434
|
|
|
|
|
|
|
say "Re-authenticatinging..."; |
435
|
|
|
|
|
|
|
if (-e $sk_savefile) { |
436
|
|
|
|
|
|
|
unlink($sk_savefile); |
437
|
|
|
|
|
|
|
} |
438
|
|
|
|
|
|
|
undef $session_key; |
439
|
|
|
|
|
|
|
get_session_key(); |
440
|
|
|
|
|
|
|
say "Got session key: $session_key"; |
441
|
|
|
|
|
|
|
say "Unsetting NET_LASTFMAPI_REAUTH..."; |
442
|
|
|
|
|
|
|
delete $ENV{NET_LASTFMAPI_REAUTH}; |
443
|
|
|
|
|
|
|
say "Done"; |
444
|
|
|
|
|
|
|
exit; |
445
|
|
|
|
|
|
|
} |
446
|
|
|
|
|
|
|
|
447
|
|
|
|
|
|
|
1; |
448
|
|
|
|
|
|
|
|
449
|
|
|
|
|
|
|
__END__ |