line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Mojo::WebService::LastFM; |
2
|
|
|
|
|
|
|
|
3
|
4
|
|
|
4
|
|
9582
|
use Moo; |
|
4
|
|
|
|
|
36641
|
|
|
4
|
|
|
|
|
16
|
|
4
|
4
|
|
|
4
|
|
6708
|
use strictures 2; |
|
4
|
|
|
|
|
5619
|
|
|
4
|
|
|
|
|
135
|
|
5
|
4
|
|
|
4
|
|
1120
|
use Mojo::UserAgent; |
|
4
|
|
|
|
|
215240
|
|
|
4
|
|
|
|
|
43
|
|
6
|
4
|
|
|
4
|
|
110
|
use Mojo::Promise; |
|
4
|
|
|
|
|
7
|
|
|
4
|
|
|
|
|
33
|
|
7
|
4
|
|
|
4
|
|
84
|
use Mojo::Exception; |
|
4
|
|
|
|
|
6
|
|
|
4
|
|
|
|
|
117
|
|
8
|
4
|
|
|
4
|
|
19
|
use Carp; |
|
4
|
|
|
|
|
7
|
|
|
4
|
|
|
|
|
187
|
|
9
|
4
|
|
|
4
|
|
1709
|
use namespace::clean; |
|
4
|
|
|
|
|
33819
|
|
|
4
|
|
|
|
|
29
|
|
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
our $VERSION = '0.01'; # TRIAL |
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
has 'api_key' => ( is => 'ro' ); |
15
|
|
|
|
|
|
|
has 'ua' => ( is => 'lazy', builder => sub |
16
|
|
|
|
|
|
|
{ |
17
|
3
|
|
|
3
|
|
4791
|
my $self = shift; |
18
|
3
|
|
|
|
|
43
|
my $ua = Mojo::UserAgent->new; |
19
|
3
|
|
|
|
|
28
|
$ua->transactor->name("Mojo-WebService-LastFM"); |
20
|
3
|
|
|
|
|
114
|
$ua->connect_timeout(5); |
21
|
3
|
|
|
|
|
26
|
return $ua; |
22
|
|
|
|
|
|
|
}); |
23
|
|
|
|
|
|
|
has 'base_url' => ( is => 'lazy', default => 'http://ws.audioscrobbler.com/2.0' ); |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
sub recenttracks |
26
|
|
|
|
|
|
|
{ |
27
|
8
|
|
|
8
|
1
|
398
|
my ($self, $params, $callback) = @_; |
28
|
8
|
100
|
|
|
|
507
|
croak '$username is undefined' unless defined $params->{'username'}; |
29
|
5
|
100
|
|
|
|
102
|
croak '$callback is undefined' unless defined $callback; |
30
|
|
|
|
|
|
|
|
31
|
4
|
|
100
|
|
|
33
|
my $limit = $params->{'limit'} // 1; |
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
my $url = $self->base_url . |
34
|
|
|
|
|
|
|
'/?method=user.getrecenttracks' . |
35
|
4
|
|
|
|
|
86
|
'&user=' . $params->{'username'} . |
36
|
|
|
|
|
|
|
'&api_key=' . $self->api_key . |
37
|
|
|
|
|
|
|
'&format=json' . |
38
|
|
|
|
|
|
|
'&limit=' . $limit; |
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
$self->ua->get($url => sub |
41
|
|
|
|
|
|
|
{ |
42
|
4
|
|
|
4
|
|
33453
|
my ($ua, $tx) = @_; |
43
|
4
|
50
|
|
|
|
18
|
$callback->($tx->error) unless defined $tx->result; |
44
|
|
|
|
|
|
|
|
45
|
4
|
|
|
|
|
81
|
my $json = $tx->res->json; |
46
|
4
|
50
|
|
|
|
997
|
$callback->(Mojo::Exception->new('json response is undefined')) unless defined $json; |
47
|
|
|
|
|
|
|
|
48
|
4
|
|
|
|
|
10
|
$callback->($json); |
49
|
4
|
|
|
|
|
99
|
}); |
50
|
|
|
|
|
|
|
} |
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
sub recenttracks_p |
53
|
|
|
|
|
|
|
{ |
54
|
7
|
|
|
7
|
1
|
1421
|
my ($self, $params) = @_; |
55
|
|
|
|
|
|
|
|
56
|
7
|
|
|
|
|
25
|
my $promise = Mojo::Promise->new; |
57
|
7
|
|
|
4
|
|
196
|
$self->recenttracks($params, sub { $promise->resolve(shift) }); |
|
4
|
|
|
|
|
16
|
|
58
|
4
|
|
|
|
|
11182
|
return $promise; |
59
|
|
|
|
|
|
|
} |
60
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
sub nowplaying |
62
|
|
|
|
|
|
|
{ |
63
|
7
|
|
|
7
|
1
|
374
|
my ($self, $params, $callback) = @_; |
64
|
7
|
|
|
|
|
9
|
my $username; |
65
|
7
|
100
|
|
|
|
19
|
if ( ref $params eq 'HASH' ) |
|
|
100
|
|
|
|
|
|
66
|
|
|
|
|
|
|
{ |
67
|
4
|
50
|
|
|
|
16
|
croak 'username is undefined' unless exists $params->{'username'}; |
68
|
4
|
|
|
|
|
7
|
$username = $params->{'username'}; |
69
|
|
|
|
|
|
|
} |
70
|
|
|
|
|
|
|
elsif ( ref \$params eq 'SCALAR' ) |
71
|
|
|
|
|
|
|
{ |
72
|
2
|
|
|
|
|
3
|
$username = $params; |
73
|
|
|
|
|
|
|
} |
74
|
|
|
|
|
|
|
else |
75
|
|
|
|
|
|
|
{ |
76
|
1
|
|
|
|
|
97
|
croak 'Invalid params format. Accept Hashref or Scalar.'; |
77
|
|
|
|
|
|
|
} |
78
|
|
|
|
|
|
|
|
79
|
6
|
100
|
|
|
|
103
|
croak 'callback is undefined' unless defined $callback; |
80
|
|
|
|
|
|
|
|
81
|
5
|
|
|
|
|
6
|
my $np; |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
$self->recenttracks_p({ 'username' => $username, 'limit' => 1 })->then(sub |
84
|
|
|
|
|
|
|
{ |
85
|
3
|
|
|
3
|
|
1043
|
my $json = shift; |
86
|
3
|
50
|
|
|
|
8
|
$callback->(Mojo::Exception->new('$json is undefined')) unless defined $json; |
87
|
|
|
|
|
|
|
|
88
|
3
|
100
|
|
|
|
10
|
if ( exists $json->{'recenttracks'}{'track'}[0] ) |
89
|
|
|
|
|
|
|
{ |
90
|
2
|
|
|
|
|
4
|
my $track = $json->{'recenttracks'}{'track'}[0]; |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
my $np = { |
93
|
|
|
|
|
|
|
'artist' => $track->{'artist'}{'#text'}, |
94
|
|
|
|
|
|
|
'album' => $track->{'album'}{'#text'}, |
95
|
|
|
|
|
|
|
'title' => $track->{'name'}, |
96
|
|
|
|
|
|
|
'date' => $track->{'date'}, |
97
|
2
|
|
|
|
|
10
|
'image' => $track->{'image'}, |
98
|
|
|
|
|
|
|
}; |
99
|
|
|
|
|
|
|
|
100
|
2
|
|
|
|
|
6
|
$callback->($np); |
101
|
|
|
|
|
|
|
} |
102
|
|
|
|
|
|
|
else |
103
|
|
|
|
|
|
|
{ |
104
|
1
|
|
|
|
|
8
|
$callback->(Mojo::Exception->new('Error: Response missing now-playing information.')); |
105
|
|
|
|
|
|
|
} |
106
|
5
|
|
|
|
|
14
|
}); |
107
|
|
|
|
|
|
|
} |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
sub nowplaying_p |
110
|
|
|
|
|
|
|
{ |
111
|
6
|
|
|
6
|
1
|
3549
|
my ($self, $params) = @_; |
112
|
6
|
|
|
|
|
21
|
my $promise = Mojo::Promise->new; |
113
|
|
|
|
|
|
|
|
114
|
6
|
|
|
3
|
|
176
|
$self->nowplaying($params, sub{ $promise->resolve(shift) }); |
|
3
|
|
|
|
|
18
|
|
115
|
3
|
|
|
|
|
171
|
return $promise; |
116
|
|
|
|
|
|
|
} |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
sub info |
119
|
|
|
|
|
|
|
{ |
120
|
3
|
|
|
3
|
1
|
375
|
my ($self, $user, $callback) = @_; |
121
|
3
|
100
|
|
|
|
226
|
croak 'user is undefined' unless defined $user; |
122
|
2
|
100
|
|
|
|
114
|
croak 'callback is undefined' unless defined $callback; |
123
|
|
|
|
|
|
|
|
124
|
1
|
|
|
|
|
42
|
my $url = $self->base_url . |
125
|
|
|
|
|
|
|
'/?method=user.getinfo' . |
126
|
|
|
|
|
|
|
'&user=' . $user . |
127
|
|
|
|
|
|
|
'&api_key=' . $self->api_key . |
128
|
|
|
|
|
|
|
'&format=json'; |
129
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
$self->ua->get($url => sub |
131
|
|
|
|
|
|
|
{ |
132
|
1
|
|
|
1
|
|
12334
|
my ($ua, $tx) = @_; |
133
|
1
|
|
|
|
|
5
|
my $json = $tx->res->json; |
134
|
1
|
|
|
|
|
166
|
$callback->($json); |
135
|
1
|
|
|
|
|
33
|
}); |
136
|
|
|
|
|
|
|
} |
137
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
sub info_p |
139
|
|
|
|
|
|
|
{ |
140
|
2
|
|
|
2
|
1
|
1550
|
my ($self, $user) = @_; |
141
|
2
|
|
|
|
|
15
|
my $promise = Mojo::Promise->new; |
142
|
|
|
|
|
|
|
|
143
|
2
|
|
|
1
|
|
100
|
$self->info($user, sub { $promise->resolve(shift) }); |
|
1
|
|
|
|
|
6
|
|
144
|
|
|
|
|
|
|
|
145
|
1
|
|
|
|
|
4454
|
return $promise; |
146
|
|
|
|
|
|
|
} |
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
1; |
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
=encoding utf8 |
151
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
=head1 NAME |
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
Mojo::WebService::LastFM - Non-blocking recent tracks information from Last.FM |
155
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
=head1 SYNOPSIS |
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
use Mojo::WebService::LastFM; |
159
|
|
|
|
|
|
|
use Data::Dumper; |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
my $user = 'vsTerminus'; |
162
|
|
|
|
|
|
|
my $last = Mojo::WebService::LastFM->new('api_key' => 'abc123'); |
163
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
# Get currently playing or last played track using a callback, passing username as a scalar, |
165
|
|
|
|
|
|
|
# and dump the resulting hash to screen using Data::Dumper |
166
|
|
|
|
|
|
|
$last->nowplaying($user, sub { say Dumper(shift) }); |
167
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
# Get currently playing or last played track using a promise, passing username in a hashref |
169
|
|
|
|
|
|
|
$last->nowplaying_p({ |
170
|
|
|
|
|
|
|
'username' => $user |
171
|
|
|
|
|
|
|
})->then(sub |
172
|
|
|
|
|
|
|
{ |
173
|
|
|
|
|
|
|
my $np = shift; |
174
|
|
|
|
|
|
|
if ( exists $np->{'date'} ) |
175
|
|
|
|
|
|
|
{ |
176
|
|
|
|
|
|
|
say $user . ' last listened to ' . $np->{'title'} . ' by ' . $np->{'artist'} . ' from ' . $np->{'album'} . ' at ' . $np->{'date'}; |
177
|
|
|
|
|
|
|
} |
178
|
|
|
|
|
|
|
else |
179
|
|
|
|
|
|
|
{ |
180
|
|
|
|
|
|
|
say $user . ' is currently listening to ' . $np->{'title'} . ' by ' . $np->{'artist'} . ' from ' . $np->{'album'}; |
181
|
|
|
|
|
|
|
} |
182
|
|
|
|
|
|
|
})->catch(sub |
183
|
|
|
|
|
|
|
{ |
184
|
|
|
|
|
|
|
my $err = shift; |
185
|
|
|
|
|
|
|
die $err->message; |
186
|
|
|
|
|
|
|
}); |
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
# Get a complete recent tracks payload using a callback |
189
|
|
|
|
|
|
|
# Print a formatted string of values |
190
|
|
|
|
|
|
|
$last->recenttracks({ 'username' => $user }, sub |
191
|
|
|
|
|
|
|
{ |
192
|
|
|
|
|
|
|
my $json = shift; |
193
|
|
|
|
|
|
|
my $track = $json->{'recenttracks'}{'track'}[0]; |
194
|
|
|
|
|
|
|
my $artist = $track->{'artist'}{'#text'}; |
195
|
|
|
|
|
|
|
my $album = $track->{'album'}{'#text'}; |
196
|
|
|
|
|
|
|
my $title = $track->{'name'}; |
197
|
|
|
|
|
|
|
my $date = $track->{'date'}; |
198
|
|
|
|
|
|
|
my $img = $track->{'image'}; |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
my $when = ( defined $date ? 'Last played' : 'Now playing' ); |
201
|
|
|
|
|
|
|
say "$when $artist - $album - $title"; |
202
|
|
|
|
|
|
|
}); |
203
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
# Get a complete recent tracks payload using a promise |
205
|
|
|
|
|
|
|
# Dump the hash to screen with Data::Dumper |
206
|
|
|
|
|
|
|
$last->recenttracks_p({ 'username' => $user })->then(sub{ say Dumper(shift) }); |
207
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
=head1 DESCRIPTION |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
L is an asynchronous way to request currently playing or recently played song information from Last.FM. It works with callbacks or with promises, whichever you prefer. |
211
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
It also provides the option to either fetch the entire JSON return object as a hashref, or to fetch a simplified hash which contains only the currently playing or last played song info. The latter is easier to work with if you just want to display the currently playing song. |
213
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
=head1 ATTRIBUTES |
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
=head2 api_key |
217
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
You will need an API Key for Last.FM, which you can get from L. |
219
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
You will receive an API Key and an API Secret. Each will be a string of base-16 numbers (0-9a-f). |
221
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
You don't need the API Secret for anything in this module (currently), but make sure when you get it you record it somewhere (eg your password vault) because LastFM won't show it to you again and you may need it in the future. |
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
=head2 ua |
225
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
A Mojo::UserAgent object is used to make all of the HTTP calls asynchronously. |
227
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
=head2 base_url |
229
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
This is the base URL for the Last.FM API site. It defaults to 'http://ws.audioscrobbler.com/2.0'. |
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
API call URLs are made by appending endpoints to this base string. |
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
=head1 METHODS |
235
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
L implements the following methods |
237
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
=head2 recenttracks |
239
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
Request the complete 'recenttracks' JSON structure from Last.FM |
241
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
Takes a hashref and a callback, returns nothing. |
243
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
The hashref must contain at least a 'username' value, but may also specify a 'limit' value for the number of recent tracks to retrieve. |
245
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
The callback should be a sub. recenttracks will call this sub and pass it the json object it got from the API. |
247
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
$lastfm->recenttracks({'username' => $some_user, 'limit' => 1}, sub { my $json = shift; ... }); |
249
|
|
|
|
|
|
|
|
250
|
|
|
|
|
|
|
=head2 recenttracks_p |
251
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
Version of recenttracks which accepts a params hashref and returns a L |
253
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
$lastfm->recenttracks_p({'username' => $another_user})->then(sub{ say Dumper(shift) })->catch(sub{ say Dumper(shift) }); |
255
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
=head2 nowplaying |
257
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
Return only the currently playing track or the last played track in a simplified object structure. |
259
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
Takes a username either as a scalar or as a hashref with the 'username' key and a callback sub. |
261
|
|
|
|
|
|
|
Sends the resulting JSON payload as a hashref to the callback. |
262
|
|
|
|
|
|
|
|
263
|
|
|
|
|
|
|
The response includes the Artist, Album, Title, and Album Art URL. If it is not the currently playing track it will also include the date/time of when the last track was played. |
264
|
|
|
|
|
|
|
Checking for the existence of the date key is the simplest way to determine if the song is currently playing or not. |
265
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
# As scalar |
267
|
|
|
|
|
|
|
$lastfm->nowplaying('SomeUser1234', sub { my $json = shift; say "Now Playing: " . $json->{'artist'} . " - " . $json->{'title'} ; }); |
268
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
# As hashref |
270
|
|
|
|
|
|
|
$lastfm->nowplaying({'username' => 'SomeUser1234'}, sub { exists shift->{'date'} ? say "Last Played" : say "Currently Playing" }); |
271
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
=head2 nowplaying_p |
273
|
|
|
|
|
|
|
|
274
|
|
|
|
|
|
|
Promise version of nowplaying. |
275
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
Takes a username as a scalar or as a hashref, returns a L |
277
|
|
|
|
|
|
|
|
278
|
|
|
|
|
|
|
$lastfm->nowplaying_p('SomeUser5678')->then(sub{ say Dumper(shift) }); |
279
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
=head2 info |
281
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
Returns user profile info for the specified user. |
283
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
Accepts a username as a string and a callback sub. |
285
|
|
|
|
|
|
|
Sends the resulting JSON payload as a hashref to the callback. |
286
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
$lastfm->info($username, sub { say Dumper(shift) }); |
288
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
=head2 info_p |
290
|
|
|
|
|
|
|
|
291
|
|
|
|
|
|
|
Promise version of info. Takes a username as a string, returns a L |
292
|
|
|
|
|
|
|
|
293
|
|
|
|
|
|
|
$lastfm->info_p($user)->then(sub{ say Dumper(shift) }); |
294
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
=head1 BUGS |
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
Report any issues on L |
298
|
|
|
|
|
|
|
|
299
|
|
|
|
|
|
|
=head1 AUTHOR |
300
|
|
|
|
|
|
|
|
301
|
|
|
|
|
|
|
Travis Smith |
302
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE |
304
|
|
|
|
|
|
|
|
305
|
|
|
|
|
|
|
This software is Copyright (c) 2020 by Travis Smith. |
306
|
|
|
|
|
|
|
|
307
|
|
|
|
|
|
|
This is free software, licensed under: |
308
|
|
|
|
|
|
|
|
309
|
|
|
|
|
|
|
The MIT (X11) License |
310
|
|
|
|
|
|
|
|
311
|
|
|
|
|
|
|
=head1 SEE ALSO |
312
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
L, L, L. |
314
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
=cut |