File Coverage

blib/lib/WWW/Spotify.pm
Criterion Covered Total %
statement 147 292 50.3
branch 29 80 36.2
condition 14 34 41.1
subroutine 32 50 64.0
pod 16 31 51.6
total 238 487 48.8


line stmt bran cond sub pod time code
1 3     3   425168 use strict;
  3         5  
  3         73  
2 3     3   11 use warnings;
  3         4  
  3         130  
3             package WWW::Spotify;
4             $WWW::Spotify::VERSION = '0.009';
5 3     3   1382 use Moo 2.002004;
  3         27074  
  3         14  
6              
7 3     3   3601 use Data::Dumper;
  3         4172  
  3         132  
8 3     3   13 use File::Basename;
  3         2  
  3         166  
9 3     3   845 use HTTP::Headers;
  3         10457  
  3         80  
10 3     3   1197 use IO::CaptureOutput qw( capture qxx qxy );
  3         39883  
  3         161  
11 3     3   1210 use JSON::Path;
  3         48544  
  3         19  
12 3     3   1036 use JSON::MaybeXS qw( decode_json );
  3         1539  
  3         139  
13 3     3   1353 use LWP::Protocol::https ();
  3         205982  
  3         87  
14 3     3   1278 use MIME::Base64;
  3         1500  
  3         160  
15 3     3   14 use Scalar::Util;
  3         4  
  3         98  
16 3     3   807 use Try::Tiny qw( catch try );
  3         1521  
  3         145  
17 3     3   1377 use Types::Standard qw( Bool InstanceOf Int Str );
  3         127045  
  3         27  
18 3     3   2456 use URI;
  3         6  
  3         58  
19 3     3   12 use URI::Escape;
  3         3  
  3         147  
20 3     3   1235 use WWW::Mechanize;
  3         150464  
  3         7093  
21              
22             has 'oauth_authorize_url' => (
23             is => 'rw',
24             isa => Str,
25             default => 'https://accounts.spotify.com/authorize'
26             );
27              
28             has 'oauth_token_url' => (
29             is => 'rw',
30             isa => Str,
31             default => 'https://accounts.spotify.com/api/token'
32             );
33              
34             has 'oauth_redirect_uri' => (
35             is => 'rw',
36             isa => Str,
37             default => 'http://www.spotify.com'
38             );
39              
40             has 'oauth_client_id' => (
41             is => 'rw',
42             isa => Str,
43             default => $ENV{SPOTIFY_CLIENT_ID} || ''
44             );
45              
46             has 'oauth_client_secret' => (
47             is => 'rw',
48             isa => Str,
49             default => $ENV{SPOTIFY_CLIENT_SECRET} || ''
50             );
51              
52             has 'current_oath_code' => (
53             is => 'rw',
54             isa => Str,
55             default => ''
56             );
57              
58             has 'current_access_token' => (
59             is => 'rw',
60             isa => Str,
61             default => ''
62             );
63              
64             has 'result_format' => (
65             is => 'rw',
66             isa => Str,
67             default => 'json'
68             );
69              
70             has 'grab_response_header' => (
71             is => 'rw',
72             isa => Int,
73             default => 0
74             );
75              
76             has 'results' => (
77             is => 'rw',
78             isa => Int,
79             default => '15'
80             );
81              
82             has 'debug' => (
83             is => 'rw',
84             isa => Bool,
85             default => 0
86             );
87              
88             has 'uri_scheme' => (
89             is => 'rw',
90             isa => Str,
91             default => 'https'
92             );
93              
94             has 'current_client_credentials' => (
95             is => 'rw',
96             isa => Str,
97             default => ''
98             );
99              
100             has uri_hostname => (
101             is => 'rw',
102             isa => Str,
103             default => 'api.spotify.com'
104             );
105              
106             has uri_domain_path => (
107             is => 'rw',
108             isa => Str,
109             default => 'api'
110             );
111              
112             has call_type => (
113             is => 'rw',
114             isa => Str
115             );
116              
117             has auto_json_decode => (
118             is => 'rw',
119             isa => Int,
120             default => 0
121             );
122              
123             has auto_xml_decode => (
124             is => 'rw',
125             isa => Int,
126             default => 0
127             );
128              
129             has last_result => (
130             is => 'rw',
131             isa => Str,
132             default => q{}
133             );
134              
135             has last_error => (
136             is => 'rw',
137             isa => Str,
138             default => q{}
139             );
140              
141             has response_headers => (
142             is => 'rw',
143             isa => Str,
144             default => q{}
145             );
146              
147             has problem => (
148             is => 'rw',
149             isa => Str,
150             default => q{}
151             );
152              
153             has ua => (
154             is => 'ro',
155             isa => InstanceOf ['WWW::Mechanize'],
156             handles => { _mech => 'clone' },
157             lazy => 1,
158             default => sub { WWW::Mechanize->new( autocheck => 0 ) },
159             );
160              
161             my %api_call_options = (
162              
163             '/v1/albums/{id}' => {
164             info => 'Get an album',
165             type => 'GET',
166             method => 'album'
167             },
168              
169             '/v1/albums?ids={ids}' => {
170             info => 'Get several albums',
171             type => 'GET',
172             method => 'albums',
173             params => [ 'limit', 'offset' ]
174             },
175              
176             '/v1/albums/{id}/tracks' => {
177             info => "Get an album's tracks",
178             type => 'GET',
179             method => 'albums_tracks'
180             },
181              
182             '/v1/artists/{id}' => {
183             info => "Get an artist",
184             type => 'GET',
185             method => 'artist'
186             },
187              
188             '/v1/artists?ids={ids}' => {
189             info => "Get several artists",
190             type => 'GET',
191             method => 'artists'
192             },
193              
194             '/v1/artists/{id}/albums' => {
195             info => "Get an artist's albums",
196             type => 'GET',
197             method => 'artist_albums',
198             params => [ 'limit', 'offset', 'country', 'album_type' ]
199             },
200              
201             '/v1/artists/{id}/top-tracks?country={country}' => {
202             info => "Get an artist's top tracks",
203             type => 'GET',
204             method => 'artist_top_tracks',
205             params => ['country']
206             },
207              
208             '/v1/artists/{id}/related-artists' => {
209             info => "Get an artist's top tracks",
210             type => 'GET',
211             method => 'artist_related_artists',
212              
213             # params => [ 'country' ]
214             },
215              
216             # adding q and type to url unlike example since they are both required
217             '/v1/search?q={q}&type={type}' => {
218             info => "Search for an item",
219             type => 'GET',
220             method => 'search',
221             params => [ 'limit', 'offset', 'q', 'type' ]
222             },
223              
224             '/v1/tracks/{id}' => {
225             info => "Get a track",
226             type => 'GET',
227             method => 'track'
228             },
229              
230             '/v1/tracks?ids={ids}' => {
231             info => "Get several tracks",
232             type => 'GET',
233             method => 'tracks'
234             },
235              
236             '/v1/users/{user_id}' => {
237             info => "Get a user's profile",
238             type => 'GET',
239             method => 'user'
240             },
241              
242             '/v1/me' => {
243              
244             info => "Get current user's profile",
245             type => 'GET',
246             method => 'me'
247             },
248              
249             '/v1/users/{user_id}/playlists' => {
250             info => "Get a list of a user's playlists",
251             type => 'GET',
252             method => 'user_playlist'
253             },
254              
255             '/v1/users/{user_id}/playlists/{playlist_id}' => {
256             info => "Get a playlist",
257             type => 'GET',
258             method => ''
259             },
260              
261             '/v1/browse/featured-playlists' => {
262             info => "Get a list of featured playlists",
263             type => 'GET',
264             method => 'browse_featured_playlists'
265             },
266              
267             '/v1/browse/new-releases' => {
268             info => "Get a list of new releases",
269             type => 'GET',
270             method => 'browse_new_releases'
271             },
272              
273             '/v1/users/{user_id}/playlists/{playlist_id}/tracks' => {
274             info => "Get a playlist's tracks",
275             type => 'POST',
276             method => ''
277             },
278              
279             '/v1/users/{user_id}/playlists' => {
280             info => 'Create a playlist',
281             type => 'POST',
282             method => ''
283             },
284              
285             '/v1/users/{user_id}/playlists/{playlist_id}/tracks' => {
286             info => 'Add tracks to a playlist',
287             type => 'POST',
288             method => ''
289             }
290             );
291              
292             my %method_to_uri = ();
293              
294             foreach my $key ( keys %api_call_options ) {
295             next if $api_call_options{$key}->{method} eq '';
296             $method_to_uri{ $api_call_options{$key}->{method} } = $key;
297             }
298              
299             sub send_post_request {
300 0     0 0 0 my $self = shift;
301 0         0 my $attributes = shift;
302              
303             # we will need do some auth nere
304              
305             }
306              
307             sub send_get_request {
308              
309             # need to build the URL here
310 13     13 0 22 my $self = shift;
311              
312 13         21 my $attributes = shift;
313              
314 13         23 my $uri_params = '';
315              
316 13 100 66     82 if ( defined $attributes->{extras}
317             and ref $attributes->{extras} eq 'HASH' ) {
318 3         6 my @tmp = ();
319              
320 3         4 foreach my $key ( keys %{ $attributes->{extras} } ) {
  3         15  
321 7         19 push @tmp, "$key=$attributes->{extras}{$key}";
322             }
323 3         11 $uri_params = join( '&', @tmp );
324             }
325              
326 13 50 33     63 if ( exists $attributes->{format}
327             && $attributes->{format} =~ /json|xml|xspf|jsonp/ ) {
328 0         0 $self->result_format( $attributes->{format} );
329 0         0 delete $attributes->{format};
330             }
331              
332             # my $url = $self->build_url_base($call_type);
333 13         20 my $url;
334 13 50       58 if ( $attributes->{method} eq 'query_full_url' ) {
335 0         0 $url = $attributes->{url};
336             }
337             else {
338              
339 13         268 $url = $self->uri_scheme();
340              
341             # the ://
342 13         86 $url .= "://";
343              
344             # the domain
345 13         201 $url .= $self->uri_hostname();
346              
347 13         98 my $path = $method_to_uri{ $attributes->{method} };
348 13 50       41 if ($path) {
349              
350 13 50       174 warn "raw: $path" if $self->debug();
351              
352 13 100 66     217 if ( $path =~ /search/ && $attributes->{method} eq 'search' ) {
    100 66        
    100 66        
353 1         6 $path =~ s/\{q\}/$attributes->{q}/;
354 1         3 $path =~ s/\{type\}/$attributes->{type}/;
355             }
356             elsif ( $path =~ m/\{id\}/ && exists $attributes->{params}{id} ) {
357 7         38 $path =~ s/\{id\}/$attributes->{params}{id}/;
358             }
359             elsif ( $path =~ m/\{ids\}/ && exists $attributes->{params}{ids} )
360             {
361 4         24 $path =~ s/\{ids\}/$attributes->{params}{ids}/;
362             }
363              
364 13 100       58 if ( $path =~ m/\{country\}/ ) {
365 1         5 $path =~ s/\{country\}/$attributes->{params}{country}/;
366             }
367              
368 13 50 66     56 if ( $path =~ m/\{user_id\}/
369             && exists $attributes->{params}{user_id} ) {
370 1         6 $path =~ s/\{user_id\}/$attributes->{params}{user_id}/;
371             }
372              
373 13 0 33     46 if ( $path =~ m/\{playlist_id\}/
374             && exists $attributes->{params}{playlist_id} ) {
375 0         0 $path
376             =~ s/\{playlist_id\}/$attributes->{params}{playlist_id}/;
377             }
378              
379 13 50       187 warn "modified: $path\n" if $self->debug();
380             }
381              
382 13         77 $url .= $path;
383             }
384              
385             # now we need to address the "extra" attributes if any
386 13 100       37 if ($uri_params) {
387 3         4 my $start_with = '?';
388 3 100       9 if ( $url =~ /\?/ ) {
389 1         2 $start_with = '&';
390             }
391 3         5 $url .= $start_with . $uri_params;
392             }
393              
394 13 50       161 warn "$url\n" if $self->debug;
395 13         114 local $ENV{PERL_LWP_SSL_VERIFY_HOSTNAME} = 0;
396 13         200 my $mech = $self->_mech;
397              
398 13 50       14904 if ( $attributes->{client_auth_required} ) {
399              
400 0 0       0 if ( $self->current_access_token() eq '' ) {
401 0 0       0 warn "Needed to get access token\n" if $self->debug();
402 0         0 $self->get_client_credentials();
403             }
404             $mech->add_header(
405 0         0 'Authorization' => 'Bearer ' . $self->current_access_token() );
406             }
407              
408 13         52 $mech->get($url);
409              
410 13 50       785436 if ( $self->grab_response_header() == 1 ) {
411 0         0 $self->_set_response_headers($mech);
412             }
413 13         150 return $self->format_results( $mech->content );
414              
415             }
416              
417             sub _set_response_headers {
418 0     0   0 my $self = shift;
419 0         0 my $mech = shift;
420              
421 0         0 my $hd;
422 0     0   0 capture { $mech->dump_headers(); } \$hd;
  0         0  
423              
424 0         0 $self->response_headers($hd);
425 0         0 return;
426             }
427              
428             sub format_results {
429 13     13 0 222 my $self = shift;
430 13         19 my $content = shift;
431              
432             # want to store the result in case
433             # we want to interact with it via a helper method
434 13         227 $self->last_result($content);
435              
436             # FIX ME / TEST ME
437             # vefify both of these work and return the *same* perl hash
438              
439             # when / how should we check the status? Do we need to?
440             # if so then we need to create another method that will
441             # manage a Sucess vs. Fail request
442              
443 13 50 33     514 if ( $self->auto_json_decode && $self->result_format eq 'json' ) {
444 0         0 return decode_json $content;
445             }
446              
447 13 50 33     260 if ( $self->auto_xml_decode && $self->result_format eq 'xml' ) {
448              
449             # FIX ME
450 0         0 require XML::Simple;
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         741 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 500 my $self = shift;
703 1         2 my $id = shift;
704              
705 1         7 return $self->send_get_request(
706             {
707             method => 'album',
708             params => { 'id' => $id }
709             }
710             );
711             }
712              
713             sub albums {
714 2     2 1 2886 my $self = shift;
715 2         4 my $ids = shift;
716              
717 2 100       7 if ( ref($ids) eq 'ARRAY' ) {
718 1         4 $ids = join_ids($ids);
719             }
720              
721 2         14 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 2054 my $self = shift;
737 1         2 my $album_id = shift;
738 1         2 my $extras = shift;
739              
740 1         7 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 833 my $self = shift;
752 1         2 my $id = shift;
753              
754 1         7 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 669 my $self = shift;
765 1         2 my $artists = shift;
766              
767 1 50       4 if ( ref($artists) eq 'ARRAY' ) {
768 0         0 $artists = join_ids($artists);
769             }
770              
771 1         6 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 693 my $self = shift;
782 1         2 my $artist_id = shift;
783 1         1 my $extras = shift;
784              
785 1         9 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 708 my $self = shift;
797 1         2 my $artist_id = shift;
798 1         2 my $country = shift;
799              
800 1         18 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 1154 my $self = shift;
814 1         3 my $artist_id = shift;
815 1         1 my $country = shift;
816              
817 1         8 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 7364 my $self = shift;
848 1         2 my $q = shift;
849 1         2 my $type = shift;
850 1         1 my $extras = shift;
851              
852 1         6 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 729 my $self = shift;
866 1         3 my $id = shift;
867 1         6 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 665 my $self = shift;
914 1         2 my $tracks = shift;
915              
916 1 50       4 if ( ref($tracks) eq 'ARRAY' ) {
917 0         0 $tracks = join_ids($tracks);
918             }
919              
920 1         7 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 892 my $self = shift;
931 1         1 my $user_id = shift;
932 1         8 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.009
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-2017 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__