File Coverage

blib/lib/WWW/Spotify.pm
Criterion Covered Total %
statement 150 294 51.0
branch 29 80 36.2
condition 14 34 41.1
subroutine 33 51 64.7
pod 16 31 51.6
total 242 490 49.3


line stmt bran cond sub pod time code
1 3     3   476382 use strict;
  3         4  
  3         77  
2 3     3   10 use warnings;
  3         4  
  3         126  
3             package WWW::Spotify;
4             $WWW::Spotify::VERSION = '0.008';
5 3     3   1327 use Moo 2.002004;
  3         27676  
  3         14  
6              
7 3     3   3508 use Data::Dumper;
  3         4204  
  3         121  
8 3     3   12 use File::Basename;
  3         2  
  3         164  
9 3     3   885 use HTTP::Headers;
  3         10617  
  3         79  
10 3     3   1227 use IO::CaptureOutput qw( capture qxx qxy );
  3         39736  
  3         151  
11 3     3   1225 use JSON::Path;
  3         47415  
  3         16  
12 3     3   989 use JSON::MaybeXS qw( decode_json );
  3         1643  
  3         119  
13 3     3   1219 use LWP::Protocol::https ();
  3         205224  
  3         94  
14 3     3   1252 use MIME::Base64;
  3         1496  
  3         158  
15 3     3   15 use Scalar::Util;
  3         3  
  3         94  
16 3     3   781 use Try::Tiny qw( catch try );
  3         1542  
  3         133  
17 3     3   1414 use Types::Standard qw( Bool InstanceOf Int Str );
  3         127854  
  3         25  
18 3     3   2185 use URI;
  3         5  
  3         60  
19 3     3   10 use URI::Escape;
  3         5  
  3         148  
20 3     3   1328 use WWW::Mechanize;
  3         150295  
  3         97  
21 3     3   2153 use XML::Simple;
  3         18499  
  3         18  
22              
23             has 'oauth_authorize_url' => (
24             is => 'rw',
25             isa => Str,
26             default => 'https://accounts.spotify.com/authorize'
27             );
28              
29             has 'oauth_token_url' => (
30             is => 'rw',
31             isa => Str,
32             default => 'https://accounts.spotify.com/api/token'
33             );
34              
35             has 'oauth_redirect_uri' => (
36             is => 'rw',
37             isa => Str,
38             default => 'http://www.spotify.com'
39             );
40              
41             has 'oauth_client_id' => (
42             is => 'rw',
43             isa => Str,
44             default => $ENV{SPOTIFY_CLIENT_ID} || ''
45             );
46              
47             has 'oauth_client_secret' => (
48             is => 'rw',
49             isa => Str,
50             default => $ENV{SPOTIFY_CLIENT_SECRET} || ''
51             );
52              
53             has 'current_oath_code' => (
54             is => 'rw',
55             isa => Str,
56             default => ''
57             );
58              
59             has 'current_access_token' => (
60             is => 'rw',
61             isa => Str,
62             default => ''
63             );
64              
65             has 'result_format' => (
66             is => 'rw',
67             isa => Str,
68             default => 'json'
69             );
70              
71             has 'grab_response_header' => (
72             is => 'rw',
73             isa => Int,
74             default => 0
75             );
76              
77             has 'results' => (
78             is => 'rw',
79             isa => Int,
80             default => '15'
81             );
82              
83             has 'debug' => (
84             is => 'rw',
85             isa => Bool,
86             default => 0
87             );
88              
89             has 'uri_scheme' => (
90             is => 'rw',
91             isa => Str,
92             default => 'https'
93             );
94              
95             has 'current_client_credentials' => (
96             is => 'rw',
97             isa => Str,
98             default => ''
99             );
100              
101             has uri_hostname => (
102             is => 'rw',
103             isa => Str,
104             default => 'api.spotify.com'
105             );
106              
107             has uri_domain_path => (
108             is => 'rw',
109             isa => Str,
110             default => 'api'
111             );
112              
113             has call_type => (
114             is => 'rw',
115             isa => Str
116             );
117              
118             has auto_json_decode => (
119             is => 'rw',
120             isa => Int,
121             default => 0
122             );
123              
124             has auto_xml_decode => (
125             is => 'rw',
126             isa => Int,
127             default => 0
128             );
129              
130             has last_result => (
131             is => 'rw',
132             isa => Str,
133             default => q{}
134             );
135              
136             has last_error => (
137             is => 'rw',
138             isa => Str,
139             default => q{}
140             );
141              
142             has response_headers => (
143             is => 'rw',
144             isa => Str,
145             default => q{}
146             );
147              
148             has problem => (
149             is => 'rw',
150             isa => Str,
151             default => q{}
152             );
153              
154             has ua => (
155             is => 'ro',
156             isa => InstanceOf ['WWW::Mechanize'],
157             handles => { _mech => 'clone' },
158             lazy => 1,
159             default => sub { WWW::Mechanize->new( autocheck => 0 ) },
160             );
161              
162             my %api_call_options = (
163              
164             '/v1/albums/{id}' => {
165             info => 'Get an album',
166             type => 'GET',
167             method => 'album'
168             },
169              
170             '/v1/albums?ids={ids}' => {
171             info => 'Get several albums',
172             type => 'GET',
173             method => 'albums',
174             params => [ 'limit', 'offset' ]
175             },
176              
177             '/v1/albums/{id}/tracks' => {
178             info => "Get an album's tracks",
179             type => 'GET',
180             method => 'albums_tracks'
181             },
182              
183             '/v1/artists/{id}' => {
184             info => "Get an artist",
185             type => 'GET',
186             method => 'artist'
187             },
188              
189             '/v1/artists?ids={ids}' => {
190             info => "Get several artists",
191             type => 'GET',
192             method => 'artists'
193             },
194              
195             '/v1/artists/{id}/albums' => {
196             info => "Get an artist's albums",
197             type => 'GET',
198             method => 'artist_albums',
199             params => [ 'limit', 'offset', 'country', 'album_type' ]
200             },
201              
202             '/v1/artists/{id}/top-tracks?country={country}' => {
203             info => "Get an artist's top tracks",
204             type => 'GET',
205             method => 'artist_top_tracks',
206             params => ['country']
207             },
208              
209             '/v1/artists/{id}/related-artists' => {
210             info => "Get an artist's top tracks",
211             type => 'GET',
212             method => 'artist_related_artists',
213              
214             # params => [ 'country' ]
215             },
216              
217             # adding q and type to url unlike example since they are both required
218             '/v1/search?q={q}&type={type}' => {
219             info => "Search for an item",
220             type => 'GET',
221             method => 'search',
222             params => [ 'limit', 'offset', 'q', 'type' ]
223             },
224              
225             '/v1/tracks/{id}' => {
226             info => "Get a track",
227             type => 'GET',
228             method => 'track'
229             },
230              
231             '/v1/tracks?ids={ids}' => {
232             info => "Get several tracks",
233             type => 'GET',
234             method => 'tracks'
235             },
236              
237             '/v1/users/{user_id}' => {
238             info => "Get a user's profile",
239             type => 'GET',
240             method => 'user'
241             },
242              
243             '/v1/me' => {
244              
245             info => "Get current user's profile",
246             type => 'GET',
247             method => 'me'
248             },
249              
250             '/v1/users/{user_id}/playlists' => {
251             info => "Get a list of a user's playlists",
252             type => 'GET',
253             method => 'user_playlist'
254             },
255              
256             '/v1/users/{user_id}/playlists/{playlist_id}' => {
257             info => "Get a playlist",
258             type => 'GET',
259             method => ''
260             },
261              
262             '/v1/browse/featured-playlists' => {
263             info => "Get a list of featured playlists",
264             type => 'GET',
265             method => 'browse_featured_playlists'
266             },
267              
268             '/v1/browse/new-releases' => {
269             info => "Get a list of new releases",
270             type => 'GET',
271             method => 'browse_new_releases'
272             },
273              
274             '/v1/users/{user_id}/playlists/{playlist_id}/tracks' => {
275             info => "Get a playlist's tracks",
276             type => 'POST',
277             method => ''
278             },
279              
280             '/v1/users/{user_id}/playlists' => {
281             info => 'Create a playlist',
282             type => 'POST',
283             method => ''
284             },
285              
286             '/v1/users/{user_id}/playlists/{playlist_id}/tracks' => {
287             info => 'Add tracks to a playlist',
288             type => 'POST',
289             method => ''
290             }
291             );
292              
293             my %method_to_uri = ();
294              
295             foreach my $key ( keys %api_call_options ) {
296             next if $api_call_options{$key}->{method} eq '';
297             $method_to_uri{ $api_call_options{$key}->{method} } = $key;
298             }
299              
300             sub send_post_request {
301 0     0 0 0 my $self = shift;
302 0         0 my $attributes = shift;
303              
304             # we will need do some auth nere
305              
306             }
307              
308             sub send_get_request {
309              
310             # need to build the URL here
311 13     13 0 27 my $self = shift;
312              
313 13         23 my $attributes = shift;
314              
315 13         20 my $uri_params = '';
316              
317 13 100 66     71 if ( defined $attributes->{extras}
318             and ref $attributes->{extras} eq 'HASH' ) {
319 3         7 my @tmp = ();
320              
321 3         5 foreach my $key ( keys %{ $attributes->{extras} } ) {
  3         14  
322 7         21 push @tmp, "$key=$attributes->{extras}{$key}";
323             }
324 3         14 $uri_params = join( '&', @tmp );
325             }
326              
327 13 50 33     57 if ( exists $attributes->{format}
328             && $attributes->{format} =~ /json|xml|xspf|jsonp/ ) {
329 0         0 $self->result_format( $attributes->{format} );
330 0         0 delete $attributes->{format};
331             }
332              
333             # my $url = $self->build_url_base($call_type);
334 13         17 my $url;
335 13 50       42 if ( $attributes->{method} eq 'query_full_url' ) {
336 0         0 $url = $attributes->{url};
337             }
338             else {
339              
340 13         272 $url = $self->uri_scheme();
341              
342             # the ://
343 13         93 $url .= "://";
344              
345             # the domain
346 13         240 $url .= $self->uri_hostname();
347              
348 13         94 my $path = $method_to_uri{ $attributes->{method} };
349 13 50       40 if ($path) {
350              
351 13 50       202 warn "raw: $path" if $self->debug();
352              
353 13 100 66     219 if ( $path =~ /search/ && $attributes->{method} eq 'search' ) {
    100 66        
    100 66        
354 1         6 $path =~ s/\{q\}/$attributes->{q}/;
355 1         4 $path =~ s/\{type\}/$attributes->{type}/;
356             }
357             elsif ( $path =~ m/\{id\}/ && exists $attributes->{params}{id} ) {
358 7         47 $path =~ s/\{id\}/$attributes->{params}{id}/;
359             }
360             elsif ( $path =~ m/\{ids\}/ && exists $attributes->{params}{ids} )
361             {
362 4         22 $path =~ s/\{ids\}/$attributes->{params}{ids}/;
363             }
364              
365 13 100       52 if ( $path =~ m/\{country\}/ ) {
366 1         3 $path =~ s/\{country\}/$attributes->{params}{country}/;
367             }
368              
369 13 50 66     49 if ( $path =~ m/\{user_id\}/
370             && exists $attributes->{params}{user_id} ) {
371 1         6 $path =~ s/\{user_id\}/$attributes->{params}{user_id}/;
372             }
373              
374 13 0 33     43 if ( $path =~ m/\{playlist_id\}/
375             && exists $attributes->{params}{playlist_id} ) {
376 0         0 $path
377             =~ s/\{playlist_id\}/$attributes->{params}{playlist_id}/;
378             }
379              
380 13 50       212 warn "modified: $path\n" if $self->debug();
381             }
382              
383 13         81 $url .= $path;
384             }
385              
386             # now we need to address the "extra" attributes if any
387 13 100       40 if ($uri_params) {
388 3         4 my $start_with = '?';
389 3 100       12 if ( $url =~ /\?/ ) {
390 1         2 $start_with = '&';
391             }
392 3         7 $url .= $start_with . $uri_params;
393             }
394              
395 13 50       197 warn "$url\n" if $self->debug;
396 13         129 local $ENV{PERL_LWP_SSL_VERIFY_HOSTNAME} = 0;
397 13         228 my $mech = $self->_mech;
398              
399 13 50       15027 if ( $attributes->{client_auth_required} ) {
400              
401 0 0       0 if ( $self->current_access_token() eq '' ) {
402 0 0       0 warn "Needed to get access token\n" if $self->debug();
403 0         0 $self->get_client_credentials();
404             }
405             $mech->add_header(
406 0         0 'Authorization' => 'Bearer ' . $self->current_access_token() );
407             }
408              
409 13         45 $mech->get($url);
410              
411 13 50       1067009 if ( $self->grab_response_header() == 1 ) {
412 0         0 $self->_set_response_headers($mech);
413             }
414 13         150 return $self->format_results( $mech->content );
415              
416             }
417              
418             sub _set_response_headers {
419 0     0   0 my $self = shift;
420 0         0 my $mech = shift;
421              
422 0         0 my $hd;
423 0     0   0 capture { $mech->dump_headers(); } \$hd;
  0         0  
424              
425 0         0 $self->response_headers($hd);
426 0         0 return;
427             }
428              
429             sub format_results {
430 13     13 0 209 my $self = shift;
431 13         25 my $content = shift;
432              
433             # want to store the result in case
434             # we want to interact with it via a helper method
435 13         244 $self->last_result($content);
436              
437             # FIX ME / TEST ME
438             # vefify both of these work and return the *same* perl hash
439              
440             # when / how should we check the status? Do we need to?
441             # if so then we need to create another method that will
442             # manage a Sucess vs. Fail request
443              
444 13 50 33     509 if ( $self->auto_json_decode && $self->result_format eq 'json' ) {
445 0         0 return decode_json $content;
446             }
447              
448 13 50 33     278 if ( $self->auto_xml_decode && $self->result_format eq 'xml' ) {
449              
450             # FIX ME
451 0         0 my $xs = XML::Simple->new();
452 0         0 return $xs->XMLin($content);
453             }
454              
455             # results are not altered in this cass and would be either
456             # json or xml instead of a perl data structure
457              
458 13         752 return $content;
459             }
460              
461             sub get_oauth_authorize {
462              
463 0     0 0 0 my $self = shift;
464              
465 0 0       0 if ( $self->current_oath_code() ) {
466 0         0 return $self->current_oauth_code();
467             }
468              
469 0         0 my $grant_type = 'authorization_code';
470 0         0 local $ENV{PERL_LWP_SSL_VERIFY_HOSTNAME} = 0;
471 0         0 my $client_and_secret
472             = $self->oauth_client_id() . ':' . $self->oauth_client_secret();
473 0         0 my $encoded = encode_base64($client_and_secret);
474 0         0 chomp($encoded);
475 0         0 $encoded =~ s/\n//g;
476 0         0 my $url = $self->oauth_authorize_url();
477              
478 0         0 my @parts;
479              
480 0         0 $parts[0] = 'response_type=code';
481 0         0 $parts[1] = 'redirect_uri=' . $self->oauth_redirect_uri;
482              
483 0         0 my $params = join( '&', @parts );
484 0         0 $url = $url . "?client_id=" . $self->oauth_client_id() . "&$params";
485              
486 0         0 $self->ua->get($url);
487              
488 0         0 return $self->ua->content;
489             }
490              
491             sub get_client_credentials {
492 0     0 0 0 my $self = shift;
493 0         0 my $scope = shift;
494              
495 0 0       0 if ( $self->current_access_token() ne '' ) {
496 0         0 return $self->current_access_token();
497             }
498 0 0       0 if ( $self->oauth_client_id() eq '' ) {
499 0         0 die 'need to set the client oauth parameters\n';
500             }
501              
502 0         0 my $grant_type = 'client_credentials';
503 0         0 local $ENV{PERL_LWP_SSL_VERIFY_HOSTNAME} = 0;
504 0         0 my $mech = $self->_mech;
505 0         0 my $client_and_secret
506             = $self->oauth_client_id() . ':' . $self->oauth_client_secret();
507 0         0 my $encoded = encode_base64($client_and_secret);
508 0         0 my $url = $self->oauth_token_url();
509              
510             # $url .= "?grant_type=client_credentials";
511             # my $url = $self->oauth_authorize_url();
512             # grant_type=client_credentials
513 0         0 my $extra = {
514             grant_type => $grant_type
515              
516             #code => 'code',
517             #redirect_uri => $self->oauth_redirect_uri
518             };
519 0 0       0 if ($scope) {
520 0         0 $extra->{scope} = $scope;
521             }
522              
523 0         0 chomp($encoded);
524 0         0 $encoded =~ s/\n//g;
525 0         0 $mech->add_header( 'Authorization' => 'Basic ' . $encoded );
526              
527 0         0 $mech->post( $url, [$extra] );
528 0         0 my $content = $mech->content();
529              
530 0 0       0 if ( $content =~ /access_token/ ) {
531 0 0       0 warn "setting access token\n" if $self->debug();
532              
533 0         0 my $result = decode_json $content;
534              
535 0 0       0 if ( $result->{'access_token'} ) {
536 0         0 $self->current_access_token( $result->{'access_token'} );
537             }
538             }
539             }
540              
541             sub get_access_token {
542              
543             # cheap oauth code for now
544              
545 0     0 0 0 my $self = shift;
546 0         0 my $grant_type = 'authorization_code';
547 0         0 my $scope = shift;
548              
549 0         0 my @scopes = (
550             'playlist-modify',
551             'playlist-modify-private',
552             'playlist-read-private',
553             'streaming',
554             'user-read-private',
555             'user-read-email'
556             );
557              
558 0 0       0 if ($scope) {
559              
560             # make sure it is valid
561 0         0 my $good_scope = 0;
562 0         0 foreach my $s (@scopes) {
563 0 0       0 if ( $scope eq $s ) {
564 0         0 $good_scope = 1;
565 0         0 last;
566             }
567             }
568 0 0       0 if ( $good_scope == 0 ) {
569              
570             # clear the scope, it doesn't
571             # look valid
572 0         0 $scope = '';
573             }
574              
575             }
576              
577 0   0     0 $grant_type ||= 'authorization_code';
578              
579             # need to authorize first??
580              
581 0         0 local $ENV{PERL_LWP_SSL_VERIFY_HOSTNAME} = 0;
582 0         0 my $client_and_secret
583             = $self->oauth_client_id() . ':' . $self->oauth_client_secret();
584              
585 0         0 print $client_and_secret , "\n";
586 0         0 print $grant_type , "\n";
587 0         0 my $encoded = encode_base64($client_and_secret);
588 0         0 print $encoded , "\n";
589              
590 0         0 my $url = $self->oauth_token_url;
591 0         0 print $url , "\n";
592 0         0 my $extra = {
593             grant_type => $grant_type,
594             code => 'code',
595             redirect_uri => $self->oauth_redirect_uri
596             };
597 0 0       0 if ($scope) {
598 0         0 $extra->{scope} = $scope;
599             }
600              
601 0         0 my $mech = $self->_mech;
602 0         0 $mech->add_header( 'Authorization' => 'Basic ' . $encoded );
603              
604 0         0 $mech->post( $url, [$extra] );
605              
606 0         0 print $mech->content(), "\n";
607             }
608              
609             sub get {
610              
611             # This seemed like a simple enough method
612             # but everything I tried resulted in unacceptable
613             # trade offs and explict defining of the structures
614             # The new method, which I hope I remember when I
615             # revisit it, was to use JSON::Path
616             # It is an awesome module, but a little heavy
617             # on dependencies. However I would not have been
618             # able to do this in so few lines without it
619              
620             # Making a generalization here
621             # if you use a * you are looking for an array
622             # if you don't have an * you want the first 1 (or should I say you get the first 1)
623              
624 0     0 1 0 my ( $self, @return ) = @_;
625              
626             # my @return = @_;
627              
628 0         0 my @out;
629              
630 0         0 my $result = decode_json $self->last_result();
631              
632 0         0 my $search_ref = $result;
633              
634 0 0       0 warn Dumper($result) if $self->debug();
635              
636 0         0 foreach my $key (@return) {
637 0         0 my $type = 'value';
638 0 0       0 if ( $key =~ /\*\]/ ) {
639 0         0 $type = 'values';
640             }
641              
642 0         0 my $jpath = JSON::Path->new("\$.$key");
643              
644 0         0 my @t_arr = $jpath->$type($result);
645              
646 0 0       0 if ( $type eq 'value' ) {
647 0         0 push @out, $t_arr[0];
648             }
649             else {
650 0         0 push @out, \@t_arr;
651             }
652             }
653 0 0       0 if (wantarray) {
654 0         0 return @out;
655             }
656             else {
657 0         0 return $out[0];
658             }
659              
660             }
661              
662             sub build_url_base {
663              
664             # first the uri type
665 0     0 0 0 my $self = shift;
666 0   0     0 my $call_type = shift || $self->call_type();
667              
668 0         0 my $url = $self->uri_scheme();
669              
670             # the ://
671 0         0 $url .= "://";
672              
673             # the domain
674 0         0 $url .= $self->uri_hostname();
675              
676             # the path
677 0 0       0 if ( $self->uri_domain_path() ) {
678 0         0 $url .= "/" . $self->uri_domain_path();
679             }
680              
681 0         0 return $url;
682             }
683              
684             #- may want to move this at some point
685              
686             sub query_full_url {
687 0     0 1 0 my $self = shift;
688 0         0 my $url = shift;
689 0   0     0 my $client_auth_required = shift || 0;
690 0         0 return $self->send_get_request(
691             {
692             method => 'query_full_url',
693             url => $url,
694             client_auth_required => $client_auth_required
695             }
696             );
697             }
698              
699             #-- spotify specific methods
700              
701             sub album {
702 1     1 1 892 my $self = shift;
703 1         3 my $id = shift;
704              
705 1         6 return $self->send_get_request(
706             {
707             method => 'album',
708             params => { 'id' => $id }
709             }
710             );
711             }
712              
713             sub albums {
714 2     2 1 3104 my $self = shift;
715 2         14 my $ids = shift;
716              
717 2 100       11 if ( ref($ids) eq 'ARRAY' ) {
718 1         4 $ids = join_ids($ids);
719             }
720              
721 2         18 return $self->send_get_request(
722             {
723             method => 'albums',
724             params => { 'ids' => $ids }
725             }
726             );
727              
728             }
729              
730             sub join_ids {
731 1     1 0 2 my $array = shift;
732 1         4 return join( ',', @$array );
733             }
734              
735             sub albums_tracks {
736 1     1 1 2217 my $self = shift;
737 1         3 my $album_id = shift;
738 1         2 my $extras = shift;
739              
740 1         9 return $self->send_get_request(
741             {
742             method => 'albums_tracks',
743             params => { 'id' => $album_id },
744             extras => $extras
745             }
746             );
747              
748             }
749              
750             sub artist {
751 1     1 1 1227 my $self = shift;
752 1         3 my $id = shift;
753              
754 1         8 return $self->send_get_request(
755             {
756             method => 'artist',
757             params => { 'id' => $id }
758             }
759             );
760              
761             }
762              
763             sub artists {
764 1     1 1 738 my $self = shift;
765 1         2 my $artists = shift;
766              
767 1 50       5 if ( ref($artists) eq 'ARRAY' ) {
768 0         0 $artists = join_ids($artists);
769             }
770              
771 1         7 return $self->send_get_request(
772             {
773             method => 'artists',
774             params => { 'ids' => $artists }
775             }
776             );
777              
778             }
779              
780             sub artist_albums {
781 1     1 1 634 my $self = shift;
782 1         2 my $artist_id = shift;
783 1         1 my $extras = shift;
784              
785 1         6 return $self->send_get_request(
786             {
787             method => 'artist_albums',
788             params => { 'id' => $artist_id },
789             extras => $extras
790             }
791             );
792              
793             }
794              
795             sub artist_top_tracks {
796 1     1 1 646 my $self = shift;
797 1         3 my $artist_id = shift;
798 1         2 my $country = shift;
799              
800 1         6 return $self->send_get_request(
801             {
802             method => 'artist_top_tracks',
803             params => {
804             'id' => $artist_id,
805             'country' => $country
806             }
807             }
808             );
809              
810             }
811              
812             sub artist_related_artists {
813 1     1 1 967 my $self = shift;
814 1         1 my $artist_id = shift;
815 1         2 my $country = shift;
816              
817 1         5 return $self->send_get_request(
818             {
819             method => 'artist_related_artists',
820             params => { 'id' => $artist_id }
821              
822             # 'country' => $country
823             # }
824             }
825             );
826              
827             }
828              
829             sub me {
830 0     0 0 0 my $self = shift;
831 0         0 return;
832             }
833              
834             sub next_result_set {
835 0     0 0 0 my $self = shift;
836 0         0 my $result = shift;
837 0         0 return;
838             }
839              
840             sub previous_result_set {
841 0     0 0 0 my $self = shift;
842 0         0 my $result = shift;
843 0         0 return;
844             }
845              
846             sub search {
847 1     1 1 7493 my $self = shift;
848 1         2 my $q = shift;
849 1         2 my $type = shift;
850 1         2 my $extras = shift;
851              
852 1         7 return $self->send_get_request(
853             {
854             method => 'search',
855             q => $q,
856             type => $type,
857             extras => $extras
858              
859             }
860             );
861              
862             }
863              
864             sub track {
865 1     1 1 541 my $self = shift;
866 1         2 my $id = shift;
867 1         5 return $self->send_get_request(
868             {
869             method => 'track',
870             params => { 'id' => $id }
871             }
872             );
873              
874             }
875              
876             sub browse_featured_playlists {
877 0     0 1 0 my $self = shift;
878 0         0 my $extras = shift;
879              
880             # locale
881             # country
882             # limit
883             # offset
884              
885 0         0 return $self->send_get_request(
886             {
887             method => 'browse_featured_playlists',
888             extras => $extras,
889             client_auth_required => 1
890             }
891             );
892             }
893              
894             sub browse_new_releases {
895 0     0 1 0 my $self = shift;
896 0         0 my $extras = shift;
897              
898             # locale
899             # country
900             # limit
901             # offset
902              
903 0         0 return $self->send_get_request(
904             {
905             method => 'browse_new_releases',
906             extras => $extras,
907             client_auth_required => 1
908             }
909             );
910             }
911              
912             sub tracks {
913 1     1 1 453 my $self = shift;
914 1         1 my $tracks = shift;
915              
916 1 50       4 if ( ref($tracks) eq 'ARRAY' ) {
917 0         0 $tracks = join_ids($tracks);
918             }
919              
920 1         5 return $self->send_get_request(
921             {
922             method => 'tracks',
923             params => { 'ids' => $tracks }
924             }
925             );
926              
927             }
928              
929             sub user {
930 1     1 1 765 my $self = shift;
931 1         3 my $user_id = shift;
932 1         6 return $self->send_get_request(
933             {
934             method => 'user',
935             params => { 'user_id' => $user_id }
936             }
937             );
938              
939             }
940              
941             sub user_playlist {
942 0     0 0   my $self = shift;
943 0           return;
944             }
945              
946             sub user_playlist_add_tracks {
947 0     0 0   my $self = shift;
948 0           return;
949             }
950              
951             sub user_playlist_create {
952 0     0 0   my $self = shift;
953 0           return;
954             }
955              
956             sub user_playlists {
957 0     0 0   my $self = shift;
958 0           return;
959             }
960              
961             1;
962              
963             =pod
964              
965             =encoding UTF-8
966              
967             =head1 NAME
968              
969             WWW::Spotify - Spotify Web API Wrapper
970              
971             =head1 VERSION
972              
973             version 0.008
974              
975             =head1 SYNOPSIS
976              
977             use WWW::Spotify;
978              
979             my $spotify = WWW::Spotify->new();
980              
981             my $result;
982              
983             $result = $spotify->album('0sNOF9WDwhWunNAHPD3Baj');
984              
985             # $result is a json structure, you can operate on it directly
986             # or you can use the "get" method see below
987              
988             $result = $spotify->albums( '41MnTivkwTO3UUJ8DrqEJJ,6JWc4iAiJ9FjyK0B59ABb4,6UXCm6bOO4gFlDQZV5yL37' );
989              
990             $result = $spotify->albums_tracks( '6akEvsycLGftJxYudPjmqK',
991             {
992             limit => 1,
993             offset => 1
994              
995             }
996             );
997              
998             $result = $spotify->artist( '0LcJLqbBmaGUft1e9Mm8HV' );
999              
1000             my $artists_multiple = '0oSGxfWSnnOXhD2fKuz2Gy,3dBVyJ7JuOMt4GE9607Qin';
1001              
1002             $result = $spotify->artists( $artists_multiple );
1003              
1004             $result = $spotify->artist_albums( '1vCWHaC5f2uS3yhpwWbIA6' ,
1005             { album_type => 'single',
1006             # country => 'US',
1007             limit => 2,
1008             offset => 0
1009             } );
1010              
1011             $result = $spotify->track( '0eGsygTp906u18L0Oimnem' );
1012              
1013             $result = $spotify->tracks( '0eGsygTp906u18L0Oimnem,1lDWb6b6ieDQ2xT7ewTC3G' );
1014              
1015             $result = $spotify->artist_top_tracks( '43ZHCT0cAZBISjO8DG9PnE', # artist id
1016             'SE' # country
1017             );
1018              
1019             $result = $spotify->search(
1020             'tania bowra' ,
1021             'artist' ,
1022             { limit => 15 , offset => 0 }
1023             );
1024              
1025             $result = $spotify->user( 'glennpmcdonald' );
1026              
1027             # public play interaction example
1028             # NEED TO SET YOUR o_auth client_id and secret for these to work
1029              
1030             $spotify->browse_featured_playlists( country => 'US' );
1031              
1032             my $link = $spotify->get('playlists.items[*].href');
1033              
1034             # $link is an arrayfef of the all the playlist urls
1035              
1036             foreach my $for_tracks (@{$link}) {
1037             # make sure the links look valid
1038             next if $for_tracks !~ /spotify\/play/;
1039             $spotify->query_full_url($for_tracks,1);
1040             my $pl_name = $spotify->get('name');
1041             my $tracks = $spotify->get('tracks.items[*].track.id');
1042             foreach my $track (@{$tracks}) {
1043             print "$track\n";
1044             }
1045             }
1046              
1047             =head1 DESCRIPTION
1048              
1049             Wrapper for the Spotify Web API.
1050              
1051             https://developer.spotify.com/web-api/
1052              
1053             Have access to a JSON viewer to help develop and debug. The Chrome JSON viewer is
1054             very good and provides the exact path of the item within the JSON in the lower left
1055             of the screen as you mouse over an element.
1056              
1057             =head1 CONSTRUCTOR ARGS
1058              
1059             =head2 ua
1060              
1061             You may provide your own L object to the constructor. You may
1062             want to set autocheck off. To get extra debugging information, you can do
1063             something like this:
1064              
1065             use LWP::ConsoleLogger::Easy qw( debug_ua );
1066             use WWW::Mechanize;
1067             use WWW::Spotify;
1068              
1069             my $mech = WWW::Mechanize->new( autocheck => 0 );
1070             debug_ua( $mech );
1071              
1072             my $ua = WWW::Mechanize->new( ua => $ua );
1073              
1074             =head1 METHODS
1075              
1076             =head2 get
1077              
1078             Returns a specific item or array of items from the JSON result of the
1079             last action.
1080              
1081             $result = $spotify->search(
1082             'tania bowra' ,
1083             'artist' ,
1084             { limit => 15 , offset => 0 }
1085             );
1086              
1087             my $image_url = $spotify->get( 'artists.items[0].images[0].url' );
1088              
1089             JSON::Path is the underlying library that actually parses the JSON.
1090              
1091             =head2 query_full_url( $url , [needs o_auth] )
1092              
1093             Results from some calls (playlist for example) return full urls that can be in their entirety. This method allows you
1094             make a call to that url and use all of the o_auth and other features provided.
1095              
1096             $spotify->query_full_url( "https://api.spotify.com/v1/users/spotify/playlists/06U6mm6KPtPIg9D4YGNEnu" , 1 );
1097              
1098             =head2 album
1099              
1100             equivalent to /v1/albums/{id}
1101              
1102             $spotify->album('0sNOF9WDwhWunNAHPD3Baj');
1103              
1104             used album vs albums since it is a singular request
1105              
1106             =head2 albums
1107              
1108             equivalent to /v1/albums?ids={ids}
1109              
1110             $spotify->albums( '41MnTivkwTO3UUJ8DrqEJJ,6JWc4iAiJ9FjyK0B59ABb4,6UXCm6bOO4gFlDQZV5yL37' );
1111              
1112             or
1113              
1114             $spotify->albums( [ '41MnTivkwTO3UUJ8DrqEJJ',
1115             '6JWc4iAiJ9FjyK0B59ABb4',
1116             '6UXCm6bOO4gFlDQZV5yL37' ] );
1117              
1118             =head2 albums_tracks
1119              
1120             equivalent to /v1/albums/{id}/tracks
1121              
1122             $spotify->albums_tracks('6akEvsycLGftJxYudPjmqK',
1123             {
1124             limit => 1,
1125             offset => 1
1126              
1127             }
1128             );
1129              
1130             =head2 artist
1131              
1132             equivalent to /v1/artists/{id}
1133              
1134             $spotify->artist( '0LcJLqbBmaGUft1e9Mm8HV' );
1135              
1136             used artist vs artists since it is a singular request and avoids collision with "artists" method
1137              
1138             =head2 artists
1139              
1140             equivalent to /v1/artists?ids={ids}
1141              
1142             my $artists_multiple = '0oSGxfWSnnOXhD2fKuz2Gy,3dBVyJ7JuOMt4GE9607Qin';
1143              
1144             $spotify->artists( $artists_multiple );
1145              
1146             =head2 artist_albums
1147              
1148             equivalent to /v1/artists/{id}/albums
1149              
1150             $spotify->artist_albums( '1vCWHaC5f2uS3yhpwWbIA6' ,
1151             { album_type => 'single',
1152             # country => 'US',
1153             limit => 2,
1154             offset => 0
1155             } );
1156              
1157             =head2 artist_top_tracks
1158              
1159             equivalent to /v1/artists/{id}/top-tracks
1160              
1161             $spotify->artist_top_tracks( '43ZHCT0cAZBISjO8DG9PnE', # artist id
1162             'SE' # country
1163             );
1164              
1165             =head2 artist_related_artists
1166              
1167             equivalent to /v1/artists/{id}/related-artists
1168              
1169             $spotify->artist_related_artists( '43ZHCT0cAZBISjO8DG9PnE' );
1170              
1171             =head2 search
1172              
1173             equivalent to /v1/search?type=album (etc)
1174              
1175             $spotify->search(
1176             'tania bowra' ,
1177             'artist' ,
1178             { limit => 15 , offset => 0 }
1179             );
1180              
1181             =head2 track
1182              
1183             equivalent to /v1/tracks/{id}
1184              
1185             $spotify->track( '0eGsygTp906u18L0Oimnem' );
1186              
1187             =head2 tracks
1188              
1189             equivalent to /v1/tracks?ids={ids}
1190              
1191             $spotify->tracks( '0eGsygTp906u18L0Oimnem,1lDWb6b6ieDQ2xT7ewTC3G' );
1192              
1193             =head2 browse_featured_playlists
1194              
1195             equivalent to /v1/browse/featured-playlists
1196              
1197             $spotify->browse_featured_playlists();
1198              
1199             requires OAuth
1200              
1201             =head2 browse_new_releases
1202              
1203             equivalent to /v1/browse/new-releases
1204              
1205             requires OAuth
1206              
1207             $spotify->browse_new_releases
1208              
1209             =head2 user
1210              
1211             equivalent to /user
1212              
1213             =head2 oauth_client_id
1214              
1215             needed for requests that require OAuth, see Spotify API documentation for more information
1216              
1217             $spotify->oauth_client_id('2xfjijkcjidjkfdi');
1218              
1219             Can also be set via environment variable, SPOTIFY_CLIENT_ID
1220              
1221             =head2 oauth_client_secret
1222              
1223             needed for requests that require OAuth, see Spotify API documentation for more information
1224              
1225             $spotify->oauth_client_secret('2xfjijkcjidjkfdi');
1226              
1227             Can also be set via environment variable, SPOTIFY_CLIENT_SECRET
1228              
1229             =head1 THANKS
1230              
1231             Paul Lamere at The Echo Nest / Spotify
1232              
1233             All the great Perl community members that keep Perl fun
1234              
1235             =head1 AUTHOR
1236              
1237             Aaron Johnson
1238              
1239             =head1 COPYRIGHT AND LICENSE
1240              
1241             This software is copyright (c) 2014 by Aaron Johnson.
1242              
1243             This is free software; you can redistribute it and/or modify it under
1244             the same terms as the Perl 5 programming language system itself.
1245              
1246             =cut
1247              
1248             __END__