File Coverage

blib/lib/WWW/Spotify.pm
Criterion Covered Total %
statement 93 297 31.3
branch 18 80 22.5
condition 11 37 29.7
subroutine 21 50 42.0
pod 16 31 51.6
total 159 495 32.1


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