File Coverage

blib/lib/Mojo/WebService/LastFM.pm
Criterion Covered Total %
statement 77 77 100.0
branch 20 24 83.3
condition 2 2 100.0
subroutine 20 20 100.0
pod 6 6 100.0
total 125 129 96.9


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