File Coverage

blib/lib/WWW/ThisIsMyJam.pm
Criterion Covered Total %
statement 36 72 50.0
branch 0 12 0.0
condition 0 3 0.0
subroutine 12 18 66.6
pod n/a
total 48 105 45.7


line stmt bran cond sub pod time code
1             package WWW::ThisIsMyJam;
2              
3             # ABSTRACT: Synchronous and asynchronous interfaces to This Is My Jam
4 3     3   8652 use version; our $VERSION = version->declare('v0.1.0');
  3         6968  
  3         16  
5 3     3   263 use strict;
  3         12  
  3         83  
6 3     3   17 use warnings;
  3         10  
  3         91  
7 3     3   154 use Carp;
  3         6  
  3         295  
8 3     3   2625 use JSON::Tiny;
  3         57961  
  3         190  
9 3     3   3337 use Try::Tiny;
  3         5033  
  3         182  
10 3     3   4943 use HTTP::Tiny;
  3         171648  
  3         119  
11 3     3   4199 use URI;
  3         13235  
  3         95  
12 3     3   2262 use URI::QueryParam;
  3         2218  
  3         228  
13             my $can_async
14             = try { require AnyEvent; require AnyEvent::HTTP; !!1 } catch { !1 };
15             #
16 3     3   2481 use Moo;
  3         52360  
  3         22  
17 3     3   8099 use Types::Standard qw[Object Int Str];
  3         210207  
  3         42  
18 3     3   5763 use Type::Utils qw[coerce from];
  3         14768  
  3         29  
19             #
20             has apiversion => (is => 'ro', isa => Int, default => '1');
21             coerce Object, from Str, q[URI->new( $_ )];
22             has baseurl => (is => 'ro',
23             isa => Object,
24             default => 'http://api.thisismyjam.com',
25             coerce => Object->coercion
26             );
27             #
28             our %API = (
29             person => {path => ':person',
30             method => 'GET',
31             params => [qw[person cb]],
32             required => [qw[person]],
33             returns => 'HashRef'
34             },
35             likes => {path => ':person/likes',
36             method => 'GET',
37             params => [qw[person show page cb]],
38             required => [qw[person]],
39             returns => 'ArrayRef[HashRef]'
40             },
41             jams => {path => ':person/jams',
42             method => 'GET',
43             params => [qw[person show page cb]],
44             required => [qw[person]],
45             returns => 'ArrayRef[HashRef]'
46             },
47             following => {path => ':person/following',
48             method => 'GET',
49             params => [qw[person order page cb]],
50             required => [qw[person]],
51             returns => 'ArrayRef[HashRef]'
52             },
53             followers => {path => ':person/followers',
54             method => 'GET',
55             params => [qw[person order page cb]],
56             required => [qw[person]],
57             returns => 'ArrayRef[HashRef]'
58             },
59             follow => {path => ':person/follow',
60             method => 'POST',
61             params => [qw[person cb]],
62             required => [qw[person]],
63             returns => 'Bool',
64             authenticate => 1
65             },
66             unfollow => {path => ':person/unfollow',
67             method => 'POST',
68             params => [qw[person cb]],
69             required => [qw[person]],
70             returns => 'Bool',
71             authenticate => 1
72             },
73              
74             # Jam
75             jam => {path => 'jams/:id',
76             method => 'GET',
77             params => [qw[id cb]],
78             required => [qw[id]],
79             returns => 'Jam'
80             },
81             likers => {path => 'jams/:id/likes',
82             method => 'GET',
83             params => [qw[id cb]],
84             required => [qw[id]],
85             returns => 'ArrayRef[User]'
86             },
87             comments => {path => 'jams/:id/comments',
88             method => 'GET',
89             params => [qw[id cb]],
90             required => [qw[id]],
91             returns => 'ArrayRef[Comment]'
92             },
93             related => {path => 'jams/:id/related',
94             method => 'GET',
95             params => [qw[id cb]],
96             required => [qw[id]],
97             returns => 'ArrayRef[Jam]'
98             },
99             like => {path => 'jams/:id/like',
100             method => 'POST',
101             params => [qw[id cb]],
102             required => [qw[id]],
103             returns => 'Bool',
104             authenticate => 1
105             },
106             unlike => {path => 'jams/:id/unlike',
107             method => 'POST',
108             params => [qw[id cb]],
109             required => [qw[id]],
110             returns => 'Bool',
111             authenticate => 1
112             },
113             post_comment => {path => 'jams/:id/comment',
114             method => 'POST',
115             params => [qw[id comment cb]],
116             required => [qw[id comment]],
117             returns => 'Bool',
118             authenticate => 1
119             },
120             rejammers => {path => 'jams/:id/rejammers',
121             method => 'GET',
122             params => [qw[id cb]],
123             required => [qw[id]],
124             returns => 'ArrayRef[Person]',
125             undocumented => 1
126             },
127              
128             # Comments
129             get_comment => {path => 'comments/:id',
130             method => 'GET',
131             params => [qw[id cb]],
132             required => [qw[id]],
133             returns => 'Comment'
134             },
135             delete_comment => {path => 'comments/:id',
136             method => 'DELETE',
137             params => [qw[id cb]],
138             required => [qw[id]],
139             returns => 'Bool',
140             authenticate => 1
141             },
142              
143             # Explore
144             popular_jams => {path => 'explore/popular',
145             method => 'GET',
146             params => [qw[cb]],
147             required => [],
148             returns => 'ArrayRef[Jam]',
149             },
150             trending_jams => {path => 'explore/breaking',
151             method => 'GET',
152             params => [qw[cb]],
153             required => [],
154             returns => 'ArrayRef[Jam]'
155             },
156             rare_jams => {path => 'explore/rare',
157             method => 'GET',
158             params => [qw[cb]],
159             required => [],
160             returns => 'ArrayRef[Jam]'
161             },
162             random_jams => {path => 'explore/chance',
163             method => 'GET',
164             params => [qw[cb]],
165             required => [],
166             returns => 'ArrayRef[Jam]'
167             },
168             newbie_jams => {path => 'explore/newcomers',
169             method => 'GET',
170             params => [qw[cb]],
171             required => [],
172             returns => 'ArrayRef[Jam]'
173             },
174             related_jams => {path => 'explore/related',
175             method => 'GET',
176             params => [qw[username cb]],
177             required => [qw[username]],
178             returns => 'ArrayRef[Jam]'
179             },
180              
181             # Search
182             search_jams => {path => 'search/jam',
183             method => 'GET',
184             params => [qw[by q cb]],
185             required => [qw[by q]],
186             returns => 'ArrayRef[Jam]'
187             },
188             ,
189             search_people => {path => 'search/person',
190             method => 'GET',
191             params => [qw[by q cb]],
192             required => [qw[by q]],
193             returns => 'ArrayRef[Jam]'
194             },
195              
196             # Misc
197             verify => {path => 'verify',
198             method => 'GET',
199             params => [qw[cb]],
200             required => [],
201             returns => 'HashRef',
202             authenticate => 1
203             }
204             );
205             for my $method (keys %API) {
206             eval sprintf
207             q[sub WWW::ThisIsMyJam::%s { my $s = shift; $s->_request($s->_parse_args( '%s', @_ )) }],
208             $method, $method;
209             }
210              
211             # Private methods
212             sub _request {
213 0     0     my ($s, $method, $url, $cb) = @_;
214 0 0         if ($cb) {
215              
216             # this is async
217 0 0         croak 'AnyEvent and AnyEvent::HTTP are required for async mode'
218             unless $can_async;
219             AnyEvent::HTTP::http_request(
220             $method, $url,
221             sub {
222 0     0     my $body = shift;
223 0           my $meta = $s->_decode_json($body);
224 0           return $cb->($meta);
225             }
226 0           );
227 0           return 0;
228             }
229              
230             # this is sync
231 0           my $result = HTTP::Tiny->new->request($method, $url);
232 0 0         $result->{'success'} or croak "Can't fetch $url: " . $result->{'reason'};
233 0           my $meta = $s->_decode_json($result->{'content'});
234 0           return $meta;
235             }
236              
237             sub _decode_json {
238 0     0     my $s = shift;
239 0           my $json = shift;
240 0     0     my $data = try { JSON::Tiny::j($json) }
241 0     0     catch { croak "Can't decode '$json': $_" };
  0            
242 0           return $data;
243             }
244              
245             sub _parse_args {
246 0     0     my $s = shift;
247 0           my $method = shift;
248 0   0       $API{$method} // confess 'Unknown RPC method: ' . $method;
249 0           my %args;
250 0           for my $arg (@{$API{$method}{required}}) {
  0            
251 0 0         last if ref $_[0] eq 'HASH';
252 0           $args{$arg} = shift;
253             }
254 0           my $args_2 = shift;
255 0           @args{keys %$args_2} = values %$args_2;
256              
257             #for my $arg (@{$API{$method}{required}}) {
258             # $args{$arg} // croak qq[required arg '$arg' is is missing!];
259             #}
260             # replace placeholder arguments
261 0           my $local_path = $API{$method}{path};
262 0 0         $local_path =~ s,/:id$,,
263             unless exists $args{id}; # remove optional trailing id
264 0           $local_path
265 0 0         =~ s/:(\w+)/delete $args{$1} or croak "required arg '$1' missing"/eg;
266             #
267 0           my $cb = delete $args{cb};
268 0           my $uri = URI->new(sprintf '%s/%d/%s.json', $s->baseurl,
269             $s->apiversion, $local_path);
270 0           $uri->query_form_hash(%args);
271 0           ($API{$method}{method}, $uri, $cb);
272             }
273             !!'This is my Jam!';
274              
275             =pod
276              
277             =head1 NAME
278              
279             WWW::ThisIsMyJam - Synchronous and asynchronous interfaces to This Is My Jam
280              
281             =head1 SYNOPSIS
282              
283             use WWW::ThisIsMyJam;
284             my $jam = WWW::ThisIsMyJam->new;
285             $jam->person('jamoftheday');
286              
287             =head1 Description
288              
289             This module provides access to Jam data through the new, official API in
290             synchronous or asynchronous mode.
291              
292             The asynchronous mode requires you have L and L
293             available. However, since it's just I and not I, it is
294             not declared as a prerequisite.
295              
296             =head1 Methods
297              
298             This Is My Jam provides an ultra simple, JSON-based API. First, we'll cover
299             public utility methods and then the categorized Jam functions.
300              
301             =head2 Standard
302              
303             =head3 new( )
304              
305             Creates a new L object.
306              
307             # typical usage
308             my $jam = WWW::ThisIsMyJam->new;
309              
310             # it would be pointless to change these, but it's possible
311             my $jam = WWW::ThisIsMyJam->new(
312             apiversion => 1,
313             basename => 'http://api.thisismyjam.com'
314             );
315              
316             =head2 Person
317              
318             Person methods cover a single user.
319              
320             =head3 user( person )
321              
322             A user's overview data includes all information about the requested person
323             along with their current jam (if any). All methods require at least a
324             username.
325              
326             # Fetch info for the Jam of the Day account
327             my $overview = $timj->user( 'jamoftheday' );
328              
329             # Use callbacks for a specific user
330             $timj->user( 'jamoftheday', { cb => sub { my ( $overview ) = @_; ... } });
331              
332             =head3 likes( person )
333              
334             Returns a list of liked jams. Takes the optional parameter 'show', which
335             specifies whether to include only current or past (expired) jams.
336              
337             # Get jams I like
338             $timj->likes( 'jamoftheday' );
339              
340             # Only get active jams
341             $timj->likes({ person => 'jamoftheday', show => 'current' });
342              
343             # Only get expired jams
344             $timj->likes({ person => 'jamoftheday', show => 'past' });
345              
346             =head3 jams( person )
347              
348             Returns a list of the person's jams. Optional parameter 'show' can be set to
349             only show past (expired) jams.
350              
351             # Get all of a user's jams
352             $timj->jams({ person => 'jamoftheday' });
353              
354             # Only get (expired) jams from the past
355             $timj->jams({ person => 'jamoftheday', show => 'past' });
356              
357             =head3 following( person )
358              
359             Returns a list of people that a particular person is following. Optional
360             parameter 'order' can be set to sort the users: C<< order => 'followedDate' >>
361             orders by date followed; C<< order => 'affinity' >> currently orders by number
362             of likes from the requested person; C<< order => 'name' >> orders by name
363             alphabetically.
364              
365             While omitted from the official documentation, observation indicates that
366             'affinity' is the default order.
367              
368             # Get users the person if following
369             $timj->following({ person => 'jamoftheday' });
370              
371             # Get users the person is following sorted by name
372             $timj->following({ person => 'jamoftheday', order => 'name' });
373              
374             =head3 followers( person )
375              
376             Returns a list of people that a particular person is followed by. Optional
377             parameter 'order' can be set to sort the users: C<< order => 'followedDate' >>
378             orders by date followed; C<< order => 'affinity' >> currently orders by number
379             of likes from the requested person; C<< order => 'name' >> orders by name
380             alphabetically.
381              
382             While omitted from the official documentation, observation indicates that
383             'affinity' is the default order.
384              
385             # Get users the person if following
386             $timj->followers({ person => 'jamoftheday' });
387              
388             # Get users the person is following sorted by name
389             $timj->followers({ person => 'jamoftheday', order => 'name' });
390              
391             =head3 follow( person )
392              
393             Follow the specified user. Requires L.
394              
395             # Follow someone
396             $timj->follow({ person => 'jamoftheday' });
397              
398             =head3 unfollow( person )
399              
400             Unfollow the specified user. Requires L.
401              
402             # Unfollow someone
403             $timj->unfollow({ person => 'jamoftheday' });
404              
405             =head2 Jam
406              
407             Jam methods return metadata about a single or list of jams.
408              
409             =head3 jam( id )
410              
411             Retrieves information on a single jam by ID.
412              
413             # Get info about a jam. (Jam of the Day from March 6th, 2013)
414             $timj->jam( { id => '4zugtyg' });
415              
416             =head3 likers( id )
417              
418             Returns a list of the people who liked a particular jam.
419              
420             # Get a list of people who liked the Jam of the Day from March 6th, 2013
421             $timj->likers( { id => '4zugtyg' });
422              
423             Note: "likers" isn't a word. I may change the name of this method before
424             v1.0.0.
425              
426             =head3 comments( id )
427              
428             Returns a list of the comments that have been added to a jam.
429              
430             # What you say?
431             $timj->comments({ id => '4zugtyg' });
432              
433             =head3 related( id )
434              
435             Returns a list of jams that may be related (musically or otherwise) to the
436             specified jam. Only works on active jams and not to be confused with
437             L|/"related_jams( username )">.
438              
439             # All of these things are just like the other
440             $timj->related({ id => '5sd5q1b' });
441              
442             =head3 like( id )
443              
444             Like a jam. You can only like jams that are currently active. Requires
445             L.
446              
447             $timj->like({ id => '4zugtyg' });
448              
449             =head3 unlike( id )
450              
451             Unlike a jam. You can only unlike jams that are currently active. Requires
452             L.
453              
454             $timj->like({ id => '4zugtyg' });
455              
456             =head3 post_comment( id, comment )
457              
458             Post a new comment on a jam. Requires L.
459              
460             # Add nothing to the conversation
461             $timj->post_comment({ id => '4zugtyg', comment => '+1' });
462              
463             =head3 rejammers( id )
464              
465             Returns a list of people who rejammed this jam.
466              
467             # Find *true* fans of this jam
468             $timj->rejammers({ id => '4zugtyg' });
469              
470             =head2 Comments
471              
472             =head3 get_comment( id )
473              
474             Retrieve a single comment by ID.
475              
476             # What's the story, morning glory?
477             $timj->delete_comment({ id => 'q0hdq3' });
478              
479             =head3 delete_comment( id )
480              
481             Delete a single comment. Only the author of the comment and the person who
482             posted the jam can delete it. Requires L.
483              
484             # Quiet, you!
485             $timj->delete_comment({ id => 'q0hdq3' });
486              
487             =head2 Explore
488              
489             =head3 popular_jams( )
490              
491             Returns a list of today's most loved jams.
492              
493             $timj->popular_jams();
494              
495             =head3 trending_jams( )
496              
497             Returns a list of songs getting a lot of recent attention.
498              
499             $timj->trending_jams();
500              
501             =head3 rare_jams( )
502              
503             Returns a list of tracks we don't hear that often.
504              
505             $timj->rare_jams();
506              
507             =head3 random_jams( )
508              
509             Returns a random list of current jams.
510              
511             $timj->random_jams();
512              
513             =head3 newbie_jams( )
514              
515             Returns a list of jams from people who have just joined This Is My Jam.
516              
517             $timj->newbie_jams();
518              
519             =head3 related_jams( username )
520              
521             A list of jams related to username's current jam. Easily but not to be
522             confused with L|/"related( id )">.
523              
524             # Grab jams related to the current Jam of the Day
525             $timj->related_jams({ username => 'jamoftheday' });
526              
527             Yes, I know the person vs. username inconsistency. Blame This Is My Jam's API
528             designers or forget it and just use the
529             L:
530              
531             # Same as above but less confusing
532             $timj->related_jams( 'jamoftheday' );
533              
534             =head2 Search
535              
536             Search methods return lists of related material. With great power...
537              
538             =head3 search_jams( by, q )
539              
540             Searching by artist will return jams by or similar to the requested artist.
541             Genre search is powered by Last.fm tag search. Hashtag support is experimental
542             (no pagination, might be slow so use the
543             L).
544              
545             # Find jams similar to those by The Knife
546             $timj->search_jams({ by => 'artist', q => 'the knife' });
547              
548             # Find electronica jams
549             $timj->search_jams({ by => 'genre', q => 'electro' });
550              
551             # Find jams with descriptions containing #jolly hashtags
552             $timj->search_jams({ by => 'hashtag', q => 'jolly' }); # Note missing #
553              
554             =head3 search_people( by, q )
555              
556             You can either search for people by name, artist and track. Searching by name
557             returns people with the search string in their username, full name or Twitter
558             name. Searching by artist returns people who have posted tracks by artists
559             (fuzzy) matching the search string. Searching by track returns people who have
560             posted a particular track (strict, case-insensitive matching).
561              
562             # Find users with the word 'jam' in their name, username, twitter handle
563             $timj->search_people({ by => 'name' , q => 'jam' });
564              
565             # Find users who jam out to music by The Beach Boys
566             $timj->search_people({ by => 'artist', q => 'beach boys' });
567              
568             # Find users who jam out to 'Video Games' by Lana del Rey
569             $timj->search_people({ by => 'track', q => 'Lana del Rey|Video games' });
570              
571             =head2 Miscellaneous
572              
573             =head3 verify( )
574              
575             Returns information about the currently L user.
576              
577             # Eh?
578             $timj->verify();
579              
580             =head1 API Methods and Arguments
581              
582             Most This Is My Jam API methods take parameters. All WWW::ThisIsMyJam API
583             methods will accept a HASH ref of named parameters as specified in the
584             This Is My Jam API documentation. For convenience, many WWW::ThisIsMyJam
585             methods accept simple positional arguments. The positional parameter passing
586             style is optional; you can always use the named parameters in a HASH reference
587             if you prefer.
588              
589             You may pass any number of required parameters as positional parameters. You
590             I pass them in the order specified in the documentation for each method.
591             Optional parameters must be passed as named parameters in a HASH reference.
592             The HASH reference containing the named parameters must be the final parameter
593             to the method call. Any required parameters not passed as positional
594             parameters, must be included in the named parameter HASH reference.
595              
596             For example, the method C has one required parameter, C.
597             You can call C with a HASH ref argument:
598              
599             $timj->following({ person => 'jamoftheday' });
600              
601             Or, you can use the convenient, positional parameter form:
602              
603             $timj->following('jamoftheday');
604              
605             The C method also has an optional parameter: C. You B
606             use the HASH ref form:
607              
608             $timj->following({ person => 'jamoftheday', order => 'name' });
609              
610             You may use the convenient positional form for the required C
611             parameter with the optional parameters specified in the named parameter HASH
612             reference:
613              
614             $timj->following('jamoftheday', { order => 'name' });
615              
616             Convenience form is provided for the required parameters of all API methods.
617             So, these two calls are equivalent:
618              
619             $timj->search_jams({ by => 'artist', q => 'Stone Roses' });
620             $timj->search_jams('artist', 'Stone Roses');
621              
622             This scheme is ripped directly from L.
623              
624             =head2 Paging
625              
626             Some methods return partial results a page at a time, currently 60 items per
627             page. For these, there is an optional C parameter. The first page is
628             returned by passing C<< page => 1 >>, the second page by passing
629             C<< page => 2 >>, etc. If no C parameter is passed, the first page is
630             returned. Each paged response contains a C HASH ref with a C
631             key. On the last page, C will be C.
632              
633             Here's an example that demonstrates how to obtain all of a user's previous
634             jams in a loop:
635              
636             my @jams;
637             for (my $page = 1;; ++$page) {
638             my $r = $timj->jams({person => 'jamoftheday', page => $page});
639             push @jams, @{$r->{jams}};
640             last unless $r->{list}{hasMore};
641             }
642              
643             =head2 Asynchronus Callbacks
644              
645             The supported asynchronous mode requires an additional parameter C. This
646             must be a CODE ref and works like so:
647              
648             $timj->verify({ cb => sub { ... } });
649              
650             $timj->jams( 'jamoftheday', { cb => sub { ... } });
651              
652             $timj->like({ id => '4zugtyg', cb => sub { ... } });
653              
654             This is ripped directly from L.
655              
656             =head1 Authentication
657              
658             In order to perform actions on behalf of a user such as
659             L or L, a
660             user first needs to give permission to your app. Once that's been done, you
661             can make authenticated calls.
662              
663             This Is My Jam uses OAuth 1.0 for authentication. Before v1.0.0,
664             WWW::ThisIsMyJam will support OAuth.
665              
666             =head1 Dependencies
667              
668             =over 4
669              
670             =item * L
671              
672             =item * L
673              
674             =item * L
675              
676             =item * L
677              
678             =item * L
679              
680             =item * L
681              
682             =item * L
683              
684             =item * L
685              
686             =back
687              
688             =head2 Optional Dependencies
689              
690             =over 4
691              
692             =item * L
693              
694             =item * L
695              
696             =back
697              
698             =head1 See Also
699              
700             =over 4
701              
702             =item * L
703              
704             =item * L
705              
706             =item * L
707              
708             =back
709              
710             =head1 Bug Reports
711              
712             If email is better for you, L but I
713             would rather have bugs sent through the issue tracker found at
714             http://github.com/sanko/www-thisismyjam/issues.
715              
716             =head1 Author
717              
718             Sanko Robinson - http://sankorobinson.com/
719              
720             CPAN ID: SANKO
721              
722             =head1 License and Legal
723              
724             Copyright (C) 2013 by Sanko Robinson
725              
726             This program is free software; you can redistribute it and/or modify it under
727             the terms of
728             L.
729             See the F file included with this distribution or
730             L
731             for clarification.
732              
733             When separated from the distribution, all original POD documentation is
734             covered by the
735             L.
736             See the
737             L.
738              
739             =cut