File Coverage

blib/lib/Mojo/WebService/LastFM.pm
Criterion Covered Total %
statement 88 89 98.8
branch 23 30 76.6
condition 2 2 100.0
subroutine 21 21 100.0
pod 6 6 100.0
total 140 148 94.5


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