File Coverage

blib/lib/WebService/Eventful.pm
Criterion Covered Total %
statement 19 21 90.4
branch n/a
condition n/a
subroutine 7 7 100.0
pod n/a
total 26 28 92.8


line stmt bran cond sub pod time code
1             package WebService::Eventful;
2              
3             =head1 NAME
4              
5             WebService::Eventful - Perl interface to Eventful public API
6              
7             =head1 SYNOPSIS
8              
9             use WebService::Eventful;
10             use Data::Dumper;
11            
12             my $evdb = WebService::Eventful->new(app_key => $app_key);
13            
14             # call() accepts either an array ref or a hash ref.
15             my $event = $evdb->call('events/get', {id => 'E0-001-000218163-6'})
16             or die "Can't retrieve event: $WebService::Eventful::errstr";
17            
18             print "Title: $event->{title}\n";
19              
20             my $venue = $evdb->call('venues/get', [id => $event->{venue_id}])
21             or die "Can't retrieve venue: $WebService::Eventful::errstr";
22            
23             print "Venue: $venue->{name}\n";
24              
25             $evdb->setup_Oauth (
26             consumer_key => "Your_Consumer_Key",
27             consumer_secret => "Your_Consumer_Secret",
28             oauth_token => "Your_Oauth_Token",
29             oauth_secret => "Your_Oauth_Token_Secret");
30              
31              
32             my $locs = $evdb->call('users/locales/list' )
33             or die "Can't retrieve user locales : $WebService::Eventful::errstr";
34              
35             print "Your locations are => " . Dumper ($locs) . "\n";
36              
37              
38             =head1 DESCRIPTION
39              
40             The Eventful API allows you to build tools and applications that interact with Eventful. This module provides a Perl interface to that API, including oauth authentication .
41              
42             See http://api.eventful.com/ for details.
43              
44             =head1 AUTHORS
45              
46             Copyright 2013 Eventful, Inc. All rights reserved.
47              
48             You may distribute under the terms of either the GNU General Public License or the Artistic License, as specified in the Perl README file.
49              
50             =head1 ACKNOWLEDGEMENTS
51              
52             Special thanks to Daniel Westermann-Clark for adding support for "flavors" of
53             plug-in parsers. Visit Podbop.org to see other cool things made by Daniel.
54              
55             =cut
56              
57             require 5.6.0;
58              
59 4     4   2970 use strict;
  4         9  
  4         125  
60 4     4   20 use warnings;
  4         6  
  4         111  
61 4     4   27 no warnings qw(uninitialized);
  4         8  
  4         133  
62              
63 4     4   20 use Carp;
  4         6  
  4         312  
64 4     4   13715 use LWP::UserAgent;
  4         302179  
  4         165  
65 4     4   46 use Digest::MD5 qw(md5_hex);
  4         8  
  4         318  
66 4     4   5999 use OAuth::Lite::Consumer;
  0            
  0            
67             use Module::Pluggable::Object;
68             use Data::Dumper;
69              
70             =head1 VERSION
71              
72             1.01 - September 2006
73             1.03 - August 2013
74             1.05 - Sept 2013
75              
76             =cut
77              
78             our $VERSION = 1.05;
79              
80             our $VERBOSE = 0;
81             our $DEBUG = 0;
82              
83             our $default_api_server = 'http://api.eventful.com';
84             our $default_flavor = 'rest';
85              
86             our $errcode;
87             our $errstr;
88              
89             =head1 CLASS METHODS
90              
91             =head2 new
92            
93             $evdb = WebService::Eventful->new(app_key => $app_key);
94              
95             Creates a new API object. Requires a valid app_key as provided by Eventful.
96              
97             You can also specify an API "flavor", such as C, to use a different format.
98              
99             $evdb = WebService::Eventful->new(app_key => $app_key, flavor => 'yaml');
100              
101             Valid flavors are C, C, and C.
102              
103             =cut
104              
105             sub new
106             {
107             my $thing = shift;
108             my $class = ref($thing) || $thing;
109            
110             my %params = @_;
111             my $self =
112             {
113             'app_key' => $params{app_key} || $params{app_token},
114             'debug' => $params{debug},
115             'verbose' => $params{verbose},
116             'api_root' => $params{api_root} || $default_api_server,
117             };
118            
119             $DEBUG ||= $params{debug};
120             $VERBOSE ||= $params{verbose};
121            
122             print "Creating object in class ($class)...\n" if $VERBOSE;
123            
124             bless $self, $class;
125            
126             my $flavor = $params{flavor} || $default_flavor;
127             $self->{parser} = $self->_find_parser($flavor);
128             croak "No parser found for flavor [$flavor]"
129             unless $self->{parser};
130              
131              
132             # Create an LWP user agent for later use.
133             $self->{user_agent} = LWP::UserAgent->new(
134             agent => "Eventful_API_Perl_Wrapper/$VERSION-$flavor",
135             );
136            
137             return $self;
138             }
139              
140             # Attempt to find a parser for the specified API flavor.
141             # Returns the package name if one is found.
142             sub _find_parser
143             {
144             my ($self, $requested_flavor) = @_;
145              
146             # Based on Catalyst::Plugin::ConfigLoader
147             my $finder = Module::Pluggable::Object->new(
148             search_path => [ __PACKAGE__ ],
149             require => 1,
150             );
151              
152             my $parser;
153             foreach my $plugin ($finder->plugins) {
154             my $flavor = $plugin->flavor;
155             if ($flavor eq $requested_flavor) {
156             $parser = $plugin;
157             }
158             }
159              
160             return $parser;
161             }
162              
163              
164             =head1 OBJECT METHODS
165              
166             =head2 setup_Oauth
167              
168             $evdb->setup_Oauth(consumer_key => 'CoNsUmErKey', consumer_secret => 'CoNsUmErSeCrEt', oauth_token => 'AcCeSsToKeN', oauth_secret => 'SeCrEtToKeN');
169              
170             Sets up the OAuth parameters that will be used to construct the Authorization header with an oauth signature computed on the parameters of the call.
171              
172             =cut
173              
174             sub setup_Oauth
175             {
176             my $self = shift;
177            
178             my %args = @_;
179              
180             # Generate Consumer
181             my $oauth_consumer = OAuth::Lite::Consumer->new(
182             consumer_key => $args{consumer_key},
183             consumer_secret => $args{consumer_secret},
184             signature_method => ($args{signature_method} || 'HMAC-SHA1') );
185              
186             # Generate Token
187             my $oauth_token = OAuth::Lite::Token->new (
188             token => $args{oauth_token},
189             secret => $args{oauth_secret});
190              
191             # Save them for when we need to compute the signature when the url is requested in the call.
192             $self->{oauth_consumer} = $oauth_consumer;
193             $self->{oauth_token} = $oauth_token;
194              
195             return 1;
196             }
197              
198             =head2 call
199              
200             $data = $evdb->call($method, \%arguments, [$force_array]);
201              
202             Calls the specified method with the given arguments and any previous authentication information (including C). Returns a hash reference containing the results.
203              
204             =cut
205              
206             sub call
207             {
208             my $self = shift;
209            
210             my $method = shift;
211             my $args = shift || [];
212             my $force_array = shift;
213              
214             # Remove any leading slash from the method name.
215             $method =~ s%^/%%;
216             # If we have no force_array, see if we have one for this method.
217             if ($self->{parser}->flavor eq 'rest' and !$force_array) {
218              
219             # The following code is automatically generated.
220             #
221             # BEGIN REPLACE
222             if($method eq 'calendars/latest/stickers') {
223             $force_array = ['site'];
224             }
225              
226             elsif($method eq 'calendars/tags/cloud') {
227             $force_array = ['tag'];
228             }
229              
230             elsif($method eq 'demands/get') {
231             $force_array = ['link', 'comment', 'image', 'tag', 'event', 'member'];
232             }
233              
234             elsif($method eq 'demands/latest/hottest') {
235             $force_array = ['demand', 'event'];
236             }
237              
238             elsif($method eq 'demands/search') {
239             $force_array = ['demand', 'event'];
240             }
241              
242             elsif($method eq 'events/get') {
243             $force_array = ['link', 'comment', 'trackback', 'image', 'parent', 'child', 'tag', 'feed', 'calendar', 'group', 'user', 'relationship', 'performer', 'rrule', 'exrule', 'rdate', 'exdate', 'date', 'category'];
244             }
245              
246             elsif($method eq 'events/recurrence/list') {
247             $force_array = ['recurrence'];
248             }
249              
250             elsif($method eq 'events/tags/cloud') {
251             $force_array = ['tag'];
252             }
253              
254             elsif($method eq 'events/validate/hcal') {
255             $force_array = ['tag', 'event_url', 'venue_url', 'event'];
256             }
257              
258             elsif($method eq 'groups/get') {
259             $force_array = ['user', 'calendar', 'link', 'comment', 'trackback', 'image', 'tag'];
260             }
261              
262             elsif($method eq 'groups/search') {
263             $force_array = ['group'];
264             }
265              
266             elsif($method eq 'groups/users/list') {
267             $force_array = ['user'];
268             }
269              
270             elsif($method eq 'internal/events/submissions/pending') {
271             $force_array = ['submission'];
272             }
273              
274             elsif($method eq 'internal/events/submissions/set_status') {
275             $force_array = ['submission'];
276             }
277              
278             elsif($method eq 'internal/events/submissions/status') {
279             $force_array = ['target'];
280             }
281              
282             elsif($method eq 'internal/submissions/targets') {
283             $force_array = ['target'];
284             }
285              
286             elsif($method eq 'locales/search') {
287             $force_array = ['suggestion'];
288             }
289              
290             elsif($method eq 'performers/demands/list') {
291             $force_array = ['demand'];
292             }
293              
294             elsif($method eq 'performers/get') {
295             $force_array = ['link', 'comment', 'image', 'tag', 'event', 'demand', 'trackback'];
296             }
297              
298             elsif($method eq 'performers/search') {
299             $force_array = ['performer'];
300             }
301              
302             elsif($method eq 'users/calendars/get') {
303             $force_array = ['rule', 'feed'];
304             }
305              
306             elsif($method eq 'users/calendars/list') {
307             $force_array = ['calendar'];
308             }
309              
310             elsif($method eq 'users/comments/get') {
311             $force_array = ['comment'];
312             }
313              
314             elsif($method eq 'users/demands/list') {
315             $force_array = ['demand', 'event'];
316             }
317              
318             elsif($method eq 'users/details/get') {
319             $force_array = ['demand', 'event', 'group', 'link', 'performer', 'venue', 'friend'];
320             }
321              
322             elsif($method eq 'users/events/recent') {
323             $force_array = ['event'];
324             }
325              
326             elsif($method eq 'users/favorites/tags/list') {
327             $force_array = ['tag'];
328             }
329              
330             elsif($method eq 'users/friends/demands/list') {
331             $force_array = ['demand', 'event', 'user'];
332             }
333              
334             elsif($method eq 'users/get') {
335             $force_array = ['site', 'im_account', 'event', 'venue', 'performer', 'comment', 'trackback', 'calendar', 'locale', 'link', 'event', 'image'];
336             }
337              
338             elsif($method eq 'users/groups/list') {
339             $force_array = ['group'];
340             }
341              
342             elsif($method eq 'users/performers/demands/list') {
343             $force_array = ['demand'];
344             }
345              
346             elsif($method eq 'users/search') {
347             $force_array = ['user'];
348             }
349              
350             elsif($method eq 'users/venues/get') {
351             $force_array = ['user_venue'];
352             }
353              
354             elsif($method eq 'venues/get') {
355             $force_array = ['link', 'comment', 'trackback', 'image', 'parent', 'child', 'event', 'tag', 'feed', 'calendar', 'group'];
356             }
357              
358             elsif($method eq 'venues/tags/cloud') {
359             $force_array = ['tag'];
360             }
361              
362             else {
363             $force_array = ['event', 'venue', 'comment', 'trackback', 'calendar', 'group', 'user', 'performer', 'member'];
364             }
365              
366             # END REPLACE
367              
368             }
369              
370             # Construct the method URL.
371             my $url = join '/', $self->{api_root}, $self->{parser}->flavor, $method;
372             print "Calling ($url)...\n" if $VERBOSE;
373            
374             # Pre-process the arguments into a hash (for searching) and an array ref
375             my $arg_present = {};
376             if (ref($args) eq 'ARRAY')
377             {
378             # Create a hash of the array values (assumes [foo => 'bar', baz => 1]).
379             my %arg_present = @{$args};
380             $arg_present = \%arg_present;
381             }
382             elsif (ref($args) eq 'HASH')
383             {
384             # Migrate the provided hash to an array ref.
385             $arg_present = $args;
386             my @args = %{$args};
387             $args = \@args;
388             }
389             else
390             {
391             $errcode = 'Missing parameter';
392             $errstr = 'Missing parameters: The second argument to call() should be an array or hash reference.';
393             return undef;
394             }
395            
396             # Add the standard arguments to the list.
397             if ($self->{app_key} and !$arg_present->{app_key}) {
398             push @{$args}, 'app_key' , $self->{app_key};
399             }
400            
401             # If one of the arguments is a file, set up the Common-friendly
402             # file indicator field and set the content-type.
403             my $content_type = '';
404             foreach my $this_field (keys %{$arg_present})
405             {
406             # Any argument with a name that ends in "_file" is a file.
407             if ($this_field =~ /_file$/)
408             {
409             $content_type = 'form-data';
410             next if ref($arg_present->{$this_field}) eq 'ARRAY';
411             my $file =
412             [
413             $arg_present->{$this_field},
414             ];
415            
416             # Replace the original argument with the file indicator.
417             $arg_present->{$this_field} = $file;
418             my $last_arg = scalar(@{$args}) - 1;
419             ARG: for my $i (0..$last_arg)
420             {
421             if ($args->[$i] eq $this_field)
422             {
423             # If this is the right arg, replace the item after it.
424             splice(@{$args}, $i + 1, 1, $file);
425             last ARG;
426             }
427             }
428             }
429             }
430            
431             # Fetch the data using the POST method.
432             my $ua = $self->{user_agent};
433              
434             # If we are doing Oauth authentication then we need to compute the signature/nonce/etc and add them into the query string
435             if (exists $self->{oauth_consumer} ) {
436              
437             my %oauth_data;
438             my $jx = 0;
439             # $content_type = 'form-data';
440             if ($content_type eq 'form-data') {
441             $oauth_data{app_key} = $self->{app_key};
442             } else {
443             %oauth_data = %$arg_present;
444             $oauth_data{app_key} = $self->{app_key};
445             }
446             print "Your oauth params for signature are => " . Dumper (\%oauth_data) . "\n" if ($DEBUG);
447             my $oauth_query = $self->{oauth_consumer}->gen_auth_query('POST', $url, $self->{oauth_token}, \%oauth_data );
448             my $oauth_header = 'OAuth';
449             my $comma = '';
450             foreach my $pair (split ('&',$oauth_query)) {
451             my ($var,$val) = (split ('=',$pair) );
452             $oauth_header .= ($comma . " $var=" . '"' . $val . '"') ;
453             $comma = ',';
454             }
455             print "Oauth added to your url => $oauth_header\n" if ($DEBUG);
456             $ua->default_header('Authorization' => $oauth_header );
457             warn "Your oauth header is => Oauth : $oauth_query\n" if ($DEBUG);
458             }
459              
460             my $response = $ua->request(POST $url,
461             'Content-type' => $content_type,
462             'Content' => $args,
463             );
464             unless ($response->is_success)
465             {
466             $errcode = $response->code;
467             $errstr = $response->code . ': ' . $response->message;
468             return undef;
469             }
470            
471             $self->{response_content} = $response->content();
472             my $data;
473            
474             my $ctype = $self->{parser}->ctype;
475             if ($response->header('Content-Type') =~ m/$ctype/i)
476             {
477             # Parse the response into a Perl data structure.
478             if ($self->{parser}->flavor eq 'rest')
479             {
480             # Maintain backwards compatibility.
481             $self->{response_xml} = $self->{response_content};
482             }
483             $data = $self->{response_data} = $self->{parser}->parse($self->{response_content}, $force_array);
484            
485             # Check for errors.
486             if ($data->{string})
487             {
488             $errcode = $data->{string};
489             $errstr = $data->{string} . ": " .$data->{description};
490             print "\n", $self->{response_content}, "\n" if $DEBUG;
491             return undef;
492             }
493             }
494             else
495             {
496             print "Content-type is: ", $response->header('Content-Type'), "\n";
497             $data = $self->{response_content};
498             }
499              
500             return $data;
501             }
502              
503             # Copied shamelessly from CGI::Minimal.
504             sub url_encode
505             {
506             my $s = shift;
507             return '' unless defined($s);
508            
509             # Filter out any URL-unfriendly characters.
510             $s =~ s/([^-_.a-zA-Z0-9])/"\%".unpack("H",$1).unpack("h",$1)/egs;
511            
512             return $s;
513             }
514              
515             1;
516              
517             __END__