File Coverage

blib/lib/WWW/Bebo/API.pm
Criterion Covered Total %
statement 442 566 78.0
branch 122 238 51.2
condition 13 58 22.4
subroutine 100 121 82.6
pod 85 85 100.0
total 762 1068 71.3


line stmt bran cond sub pod time code
1             #########################################################################
2             # $Date$
3             # $Revision$
4             # $Author$
5             # ex: set ts=8 sw=4 et
6             #########################################################################
7             package WWW::Bebo::API;
8              
9 21     21   581568 use warnings;
  21         51  
  21         740  
10 21     21   124 use strict;
  21         46  
  21         668  
11 21     21   109 use Carp;
  21         39  
  21         14754  
12              
13 21     21   19892 use version; our $VERSION = qv('0.0.03');
  21         54111  
  21         138  
14              
15 21     21   45108 use LWP::UserAgent;
  21         1479761  
  21         891  
16 21     21   25448 use Time::HiRes qw(time);
  21         55170  
  21         114  
17 21     21   5693 use Digest::MD5 qw(md5_hex);
  21         48  
  21         1794  
18 21     21   48705 use CGI;
  21         400256  
  21         196  
19 21     21   1640 use CGI::Util qw(escape);
  21         38  
  21         85042  
20              
21             our @namespaces = qw(
22             Auth Canvas Events
23             FBML Feed FQL
24             Friends Groups Notifications
25             Photos Profile Users
26             Marketplace Pages
27             );
28              
29             for (@namespaces) {
30             my $package = __PACKAGE__ . "::$_";
31             my $name = "\L$_";
32             ## no critic
33 21 50   21 1 16857 eval qq(
  21 100   21 1 108  
  21 50   21 1 7748  
  21 100   21 1 13521  
  21 100   21 1 55  
  21 50   21 1 4112  
  21 100   21 1 20921  
  21 50   21 1 53  
  21 100   21 1 5523  
  21 100   21 1 18897  
  21 100   21 1 50  
  21 100   21 1 4676  
  21 50   21 1 14163  
  21 100   21 1 54  
  21     14 1 4866  
  21     14 1 12841  
  21     14 1 53  
  21     25 1 5299  
  21     14 1 12751  
  21     19 1 54  
  21     17 1 4166  
  21     20 1 18002  
  21     14 1 55  
  21     14 1 4274  
  21     17 1 12680  
  21     17 1 58  
  21     117 1 4532  
  21     14 1 19922  
  21     14 1 56  
  21     30 1 4460  
  21     14 1 21981  
  21     17 1 777  
  21     17 1 5749  
  21     14 1 12668  
  21     17 1 49  
  21     14 1 4250  
  21     20 1 12358  
  21     14 1 53  
  21     14 1 4162  
  21     14 1 13119  
  21     14 1 51  
  21     14 1 9339  
  14     14   42  
  14     14   83  
  14     14   176  
  14     14   449  
  14     14   49  
  14     14   51  
  25     14   101  
  14     14   50  
  19     14   77  
  17     15   62  
  20     18   77  
  14     20   52  
  14     14   48  
  17     19   66  
  17         74  
  117         2991  
  14         57  
  14         49  
  30         10623  
  30         225  
  14         151  
  30         541  
  14         35  
  14         79  
  14         182  
  14         456  
  17         2019  
  17         87  
  14         117  
  17         437  
  17         2035  
  17         89  
  14         119  
  17         501  
  14         49  
  14         75  
  14         116  
  14         431  
  17         1875  
  17         83  
  14         117  
  17         504  
  14         34  
  14         64  
  14         115  
  14         524  
  20         4035  
  20         86  
  14         109  
  20         606  
  14         61  
  14         52  
  14         33  
  14         82  
  14         78  
  14         33  
  14         156  
  14         58  
  14         216  
  14         49  
  14         36  
  14         79  
  14         76  
  14         164  
  14         88  
  14         52  
  14         464  
  14         48  
  14         31  
  14         76  
  14         80  
  14         34  
  14         73  
  14         62  
  14         66  
  14         51  
  14         119  
  14         82  
  14         286  
  14         38  
  14         86  
  14         60  
  14         52  
  14         44  
  14         45  
  14         96  
  14         82  
  14         34  
  14         241  
  14         57  
  14         50  
  14         58  
  14         112  
  14         82  
  14         81  
  14         35  
  14         169  
  14         62  
  14         197  
  14         410  
  14         111  
  14         85  
  14         86  
  14         36  
  14         162  
  14         64  
  14         52  
  14         50  
  14         37  
  14         83  
  14         73  
  14         37  
  14         88  
  14         56  
  14         50  
  14         51  
  14         31  
  14         83  
  14         95  
  14         48  
  14         172  
  14         58  
  14         51  
  14         53  
  14         33  
  14         80  
  14         78  
  14         32  
  14         161  
  14         60  
  14         54  
  14         51  
  14         35  
  14         77  
  14         81  
  14         31  
  14         87  
  14         60  
  14         61  
  14         50  
  14         32  
  14         83  
  14         98  
  14         75  
  14         74  
  14         58  
  14         85  
  14         63  
  14         259  
  14         85  
  14         84  
  14         34  
  14         154  
  14         67  
  14         51  
  14         51  
  14         45  
  14         88  
  14         89  
  14         33  
  14         172  
  14         64  
  15         75  
  15         69  
  14         220  
  15         419  
  18         2443  
  18         82  
  14         106  
  18         81  
  20         3808  
  20         89  
  14         120  
  20         557  
  14         32  
  14         123  
  14         117  
  14         416  
  19         3139  
  19         107  
  14         124  
  19         467  
34             use $package;
35              
36             sub $name {
37             my \$self = shift;
38             unless ( \$self->{'_$name'} ) {
39             \$self->{'_$name'} = $package->new( base => \$self );
40             }
41             return \$self->{'_$name'};
42             }
43              
44             package $package;
45             sub base { return shift->{'base'}; };
46             sub new {
47             my ( \$class, \%args ) = \@_;
48             my \$self = bless \\\%args, \$class;
49              
50             delete \$self->{\$_} for grep { !/base/xms } keys %{\$self};
51             \$self->\$_ for keys %{\$self};
52              
53             return \$self;
54             };
55              
56             );
57             croak "Cannot create namespace $name: $@\n" if $@;
58             }
59              
60             our %attributes = (
61             parse => 1,
62             format => 'JSON',
63             debug => 0,
64             throw_errors => 1,
65             api_version => '1.0',
66             apps_uri => 'http://apps.bebo.com/',
67             server_uri => 'http://apps.bebo.com/restserver.php',
68             ( map { $_ => q{} }
69             qw(
70             api_key secret desktop
71             last_call_success last_error skipcookie
72             popup next session_key
73             session_expires session_uid callback
74             app_path ua query
75             config app_id
76             )
77             ),
78             );
79              
80             for ( keys %attributes ) {
81             ## no critic
82 25 100   25 1 242 eval qq(
  25 100   18 1 96  
  24 50   14 1 307  
  11 100   19 1 532  
  18 50   14 1 35  
  18 50   19 1 69  
  18 50   14 1 132  
  14 100   15 1 406  
  14 50   23 1 37  
  14 50   18 1 52  
  14 100   14 1 52  
  14 100   14 1 419  
  19 50   14 1 50  
  19 100   14 1 72  
  19 50   14 1 447  
  5 100   115 1 38  
  14 100   22 1 32  
  14 100   14 1 54  
  14 50   14 1 63  
  14 100   18 1 390  
  19 50   14 1 45  
  19 50   14 1 69  
  18 50   14 1 90  
  14 50   14 1 461  
  14 50       68  
  14 50       81  
  14 50       93  
  13 50       359  
  15 50       33  
  15 50       57  
  15 100       63  
  14 100       361  
  23 100       1632  
  23 100       109  
  21 50       141  
  13 50       390  
  18 50       38  
  18 50       60  
  18 50       87  
  14 100       612  
  14 50       30  
  14 50       53  
  14 50       72  
  14 50       563  
  14 50       32  
  14 50       53  
  14 50       115  
  14 50       541  
  14         31  
  14         67  
  14         69  
  14         667  
  14         32  
  14         61  
  14         73  
  14         391  
  14         31  
  14         89  
  14         59  
  14         375  
  115         185  
  115         281  
  108         515  
  14         468  
  22         1619  
  22         86  
  20         188  
  11         347  
  14         31  
  14         88  
  14         75  
  14         379  
  14         32  
  14         70  
  14         61  
  14         500  
  18         1074  
  18         77  
  18         109  
  13         401  
  14         29  
  14         71  
  14         81  
  14         402  
  14         31  
  14         68  
  14         63  
  14         353  
  14         30  
  14         69  
  14         51  
  14         438  
  14         30  
  14         60  
  14         435  
  0         0  
83             sub $_ {
84             my \$self = shift;
85             return \$self->{$_} = shift if defined \$_[0];
86             return \$self->{$_} if defined \$self->{$_};
87             return \$self->{$_} = '$attributes{$_}';
88             }
89             );
90             croak "Cannot create attribute $_: $@\n" if $@;
91             }
92              
93             sub _set_from_outside {
94 14     14   100 my $self = shift;
95              
96 14   66     334 my $app_path = '_' . ( $self->{'app_path'} || $self->app_path );
97 14         49 $app_path =~ tr/a-z/A-Z/;
98 14         46 $app_path =~ tr/A-Za-z0-9_/_/c;
99              
100 14         134 my %ENV_VARS = qw(
101             WBA_API_KEY api_key
102             WBA_SECRET secret
103             WBA_DESKTOP desktop
104             WBA_SESSION_KEY session_key
105             );
106              
107 14 100       74 $self->_set_from_file( $app_path, %ENV_VARS ) if $self->{'config'};
108 14         133 $self->_set_from_env( $app_path, %ENV_VARS );
109              
110 14         46 return;
111             }
112              
113             sub _set_from_file {
114 1     1   2 my $self = shift;
115 1         3 my $app_path = shift;
116 1         4 my %ENV_VARS = @_;
117 1 50       37 open my $config, '<', $self->{'config'} ## no critic
118             or croak "Cannot open $self->{'config'}";
119              
120 1         15 while (<$config>) {
121 4 50       9 carp "Config line: $_" if $self->{'debug'};
122 4         9 chomp;
123 4         11 my ( $key, $val ) = split m/=/xms, $_, 2;
124 4 50       9 next if !$key;
125 4 50       10 carp "Key/Val pair: $key -> $val" if $self->{'debug'};
126 4         8 for ( $key, $val ) {
127 8         14 s/\A\s+//xms;
128 8         20 s/\s+\Z//xms;
129             }
130 4   33     42 $ENV{$key} ||= $val;
131             }
132              
133 1 50       12 close $config or croak "Cannot close $self->{'config'}";
134              
135 1         5 return;
136             }
137              
138             sub _set_from_env {
139 14     14   36 my $self = shift;
140 14         35 my $app_path = shift;
141 14         60 my %ENV_VARS = @_;
142              
143 14         65 for ( keys %ENV_VARS ) {
144 56 50       277 if ( exists $ENV{ $_ . $app_path } ) {
    100          
145 0   0     0 $self->{ $ENV_VARS{$_} } ||= $ENV{ $_ . $app_path };
146             }
147             elsif ( exists $ENV{$_} ) {
148 4   33     26 $self->{ $ENV_VARS{$_} } ||= $ENV{$_};
149             }
150             }
151              
152 14         63 return;
153             }
154              
155             sub new {
156 14     14 1 1392 my ( $self, %args ) = @_;
157 14   33     153 my $class = ref $self || $self;
158 14         58 $self = bless \%args, $class;
159 14         84 $self->_set_from_outside(); # set api_key etc. if needed
160              
161 14   33     371 $self->{'ua'} ||=
162             LWP::UserAgent->new( agent => "Perl-WWW-Bebo-API/$VERSION" );
163 14         61877 my $is_attribute = join q{|}, keys %attributes;
164 14         57 delete $self->{$_} for grep { !/^ $is_attribute $/xms } keys %{$self};
  37         2443  
  14         60  
165              
166             # set up default namespaces
167 14         70 $self->$_($self) for map {"\L$_"} @namespaces;
  196         943  
168              
169             # set up default attributes
170 14         539 $self->$_ for keys %attributes;
171              
172 14         112 return $self;
173             }
174              
175             sub log_string {
176 0     0 1 0 my ( $self, $params, $response ) = @_;
177 0         0 my $string = "\nparams = \n";
178              
179 0         0 $string .= "\t$_:$params->{$_}\n" for sort keys %{$params};
  0         0  
180 0         0 $string .= "response =\n$response\n";
181              
182 0         0 return $string;
183             }
184              
185             sub call_success {
186 0     0 1 0 my $self = shift;
187 0 0       0 $self->last_call_success(shift) if @_;
188 0 0       0 $self->last_error(shift) if @_;
189 0         0 return [ $self->last_call_success, $self->last_error ];
190             }
191              
192             sub call {
193 0     0 1 0 my ( $self, $method, %args ) = @_;
194 0         0 my ( $response, $params, $sig, $img_data );
195 0         0 $self->call_success(1);
196              
197 0         0 ( $params, $img_data ) =
198             $self->_format_and_check_params( $method, %args );
199 0         0 $sig = $self->generate_sig(
200             params => $params,
201             secret => delete $params->{'secret'},
202             );
203              
204 0         0 $response = $self->_post_request( $params, $sig, $img_data );
205              
206 0 0       0 carp $self->log_string( $params, $response ) if $self->debug;
207 0 0       0 if ( $self->_has_error_response($response) ) {
208 0 0       0 if ( $self->throw_errors ) {
209 0         0 confess "Error during REST $method call:",
210             $self->log_string( $params, $response );
211             }
212             }
213              
214 0         0 return $self->_reformat_response( $params, $response );
215             }
216              
217             sub generate_sig {
218 5     5 1 22 my ( $self, %args ) = @_;
219 5         8 my %params = %{ $args{'params'} };
  5         28  
220 5         30 return md5_hex( ( map {"$_=$params{$_}"} sort keys %params ),
  26         146  
221             $args{'secret'} );
222             }
223              
224             sub verify_sig {
225 3     3 1 22 my ( $self, %args ) = @_;
226 3   33     110 return $args{'sig'} eq $self->generate_sig(
227             params => $args{'params'},
228             secret => $args{'secret'} || $self->secret,
229             );
230             }
231              
232             sub session {
233 0     0 1 0 my ( $self, %args ) = @_;
234 0         0 $self->{"session_$_"} = $args{$_} for keys %args;
235 0         0 return;
236             }
237              
238             sub redirect {
239 0     0 1 0 my $self = shift;
240 0         0 my $url = shift;
241 0         0 $self->query(shift);
242              
243 0 0 0     0 if ( $self->canvas->in_fb_canvas ) {
    0          
244 0         0 return qq{};
245             }
246             elsif ($url =~ m{^https?://([^/]*\.)?bebo\.com(:\d+)?}ixms
247             && $self->session_uid )
248             {
249 0         0 return join q{},
250 0         0 map {"$_\n"}
251             '';
254             }
255              
256 0         0 print CGI->new->redirect(
257             $self->get_app_url( next => $self->get_login_url ) );
258 0         0 return 1;
259             }
260              
261 0     0 1 0 sub require_add { return shift->require( 'add', @_ ); }
262 0     0 1 0 sub require_frame { return shift->require( 'frame', @_ ); }
263 0     0 1 0 sub require_login { return shift->require( 'login', @_ ); }
264              
265             sub require { ## no critic
266 0     0 1 0 my $self = shift;
267 0         0 my $what = shift;
268 0         0 $self->query(shift);
269              
270 0 0       0 if ( $what eq 'login' ) {
271 0 0       0 unshift @_, qw( canvas ) if $self->canvas->in_frame;
272             }
273 0 0       0 if ( $what eq 'frame' ) {
274 0 0       0 return if $self->canvas->in_frame;
275 0         0 unshift @_, qw( canvas );
276 0         0 $what = 'login';
277             }
278              
279 0         0 my $user = $self->canvas->get_fb_params->{'user'};
280 0 0       0 if ( $what eq 'add' ) {
281 0 0       0 if ( !$self->canvas->get_fb_params->{'added'} ) {
282 0         0 $user = undef;
283             }
284             }
285 0 0       0 return if $user;
286              
287 0         0 return $self->redirect( $self->get_url( $what, @_ ) );
288             }
289              
290 0     0 1 0 sub get_bebo_url { return shift->get_url( 'bebo', @_ ); }
291 0     0 1 0 sub get_add_url { return shift->get_url( 'add', @_ ); }
292 0     0 1 0 sub get_infinite_session_url { return shift->get_url('infinite_session'); }
293 0     0 1 0 sub get_login_url { return shift->get_url( 'login', @_ ); }
294 0     0 1 0 sub get_app_url { return shift->get_url( 'app', @_ ); }
295              
296             sub get_url {
297 0     0 1 0 my $self = shift;
298 0         0 my $type = shift;
299              
300 0 0       0 if ( $type eq 'bebo' ) {
301 0   0     0 my $site = shift || 'www';
302 0         0 return "http://$site.bebo.com";
303             }
304              
305 0 0       0 if ( $type eq 'app' ) {
306 0         0 return $self->apps_uri . $self->app_path . q{/};
307             }
308              
309 0 0       0 if ( $type eq 'custom' ) {
310 0         0 return (shift) . $self->_add_url_params(@_);
311             }
312              
313 0 0       0 return $self->get_url('bebo')
    0          
    0          
314             . (
315             $type eq 'add' ? '/add.php'
316             : $type eq 'infinite_session' ? '/code_gen.php'
317             : $type eq 'login' ? '/login.php'
318             : q{}
319             ) . $self->_add_url_params(@_);
320             }
321              
322             sub unescape_string {
323 0     0 1 0 my $self = shift;
324 0         0 my $string = shift;
325 0         0 $string =~ s/(?
  0         0  
326 0         0 return $string;
327             }
328              
329             sub _add_url_params {
330 4     4   12 my $self = shift;
331 4         117 my $params = q{?api_key=} . $self->api_key . q{&v=1.0};
332 4 50       13 if (@_) {
333 4 100       13 if ( @_ % 2 ) {
334              
335             # Odd number of elelemts, so didn't pass in canvas => 1
336 1 50       4 $params .= q{&canvas} if grep { $_ eq 'canvas' } @_; ## no critic
  5         13  
337 1         3 @_ = grep { $_ ne 'canvas' } @_;
  5         11  
338             }
339 4         14 my %params = @_;
340 4 100       13 $params .= q{&canvas} if delete $params{'canvas'};
341              
342 4         18 for ( sort keys %params ) {
343 8 100       22 next if not defined $params{$_};
344 4 100       50 $params{$_} = escape( $params{$_} ) if $_ eq 'next';
345 4         55 $params .= "&$_=$params{$_}";
346             }
347             }
348 4         22 return $params;
349             }
350              
351             sub _parser {
352 2     2   28 my $parser = JSON::Any->new;
353              
354             # JSON::Any needs to get fixed
355 1 50       90 $parser->handler->allow_nonref() if $parser->handlerType eq 'JSON::XS';
356 1         8 return $parser;
357             }
358              
359             sub _parse {
360 3     3   2101 my ( $self, $response ) = @_;
361              
362             # Some shortcuts
363 3 50       20 return q{} if $response =~ /\A"?"?\Z/xms;
364 3 100       26 return 1 if $response =~ /\A"?true"?\Z/xms;
365 1 50       5 return 0 if $response =~ /\A"?false"?\Z/xms;
366              
367 1         2 my $parser;
368 1         2 eval { $parser = _parser() };
  1         5  
369              
370             # Only load JSON::Any if we haven't already. Lets the developers
371             # pick their choice of JSON modules (JSON::DWIW, for example)
372 1 50       5 if ($@) { ## no critic
373             ## no critic
374 1     1   73 eval q{use JSON::Any};
  1         1127  
  1         27835  
  1         8  
375 1 50       6696 croak "Unable to load JSON module for parsing:$@\n" if $@;
376 1         5 $parser = _parser();
377             }
378 1 50       36 carp 'JSON::Any is parsing with ' . $parser->handlerType if $self->debug;
379              
380 1         7 return $parser->decode($response);
381             }
382              
383             sub _check_values_of {
384 4     4   6 my ( $self, $params ) = @_;
385              
386 4 100       108 if ( $self->desktop ) {
387 1 50       31 $params->{'call_id'} = time if $self->desktop;
388             }
389              
390 4 100       17 if ( $params->{'method'} !~ m/^auth/xms ) {
391 3   66     63 $params->{'session_key'} ||= $self->session_key;
392 3 100 66     81 if ( !$params->{'callback'} && $self->callback ) {
393 1         25 $params->{'callback'} = $self->callback;
394             }
395             }
396              
397 4         11 $params->{'method'} = "bebo.$params->{'method'}";
398 4   33     112 $params->{'v'} ||= $self->api_version;
399              
400 4   33     119 $params->{$_} ||= $self->$_ for qw(api_key format);
401 4         10 return;
402             }
403              
404             sub _format_and_check_params {
405 0     0   0 my ( $self, $method, %args ) = @_;
406              
407 0   0     0 my $params = delete $args{'params'} || {};
408 0         0 $params->{$_} = $args{$_} for keys %args;
409 0   0     0 $params->{'secret'} ||= $self->secret;
410 0   0     0 $params->{'method'} ||= $method;
411              
412 0         0 $self->_check_values_of($params);
413              
414             # reformat arrays and add each param to digest
415 0         0 for ( keys %{$params} ) {
  0         0  
416 0 0       0 next unless ref $params->{$_} eq 'ARRAY';
417 0         0 $params->{$_} = join q{,}, @{ $params->{$_} };
  0         0  
418             }
419              
420 0 0       0 croak '_format_and_check_params must be called in list context!'
421             if !wantarray;
422              
423 0         0 return ( $params, delete $params->{'data'} );
424             }
425              
426             sub _has_error_response {
427 0     0   0 my $self = shift;
428 0         0 my $response = shift;
429              
430             # The different type of error responses
431 0         0 my $json = q{ \{ "error_code" \D+ (\d+) .* "error_msg" [^"]+ "([^"]+)" };
432 0         0 my $xml = q{ (\d+) .* ([^<]+)};
433              
434 0         0 for ( $json, $xml ) {
435 0 0       0 if ( $response =~ m/$_/xms ) {
436 0         0 $self->call_success( 0, "$1: $2" );
437 0         0 return 1;
438             }
439             }
440              
441 0         0 return;
442             }
443              
444             sub _reformat_response {
445 0     0   0 my ( $self, $params, $response ) = @_;
446              
447             # get actual response when web app
448 0 0       0 if ( $params->{'callback'} ) {
449 0         0 $response =~ s/^$params->{'callback'} [^(]* [(](.+) [)];$/$1/xms;
450             }
451 0         0 undef $params;
452              
453             # ... and unescape it if it's not going to be parsed
454 0 0 0     0 if ( !$self->desktop && !$self->parse ) {
455 0         0 $response = $self->unescape_string($response);
456             }
457              
458 0 0 0     0 if ( $self->parse && $self->format eq 'XML' ) {
459 0         0 $self->parse(0);
460 0 0       0 carp q{format is XML: setting parse to 0} if $self->debug;
461             }
462 0 0       0 return $response if !$self->parse;
463              
464 0         0 $response = $self->_parse($response);
465 0 0       0 return if $self->_is_empty_response($response);
466 0         0 return $response;
467             }
468              
469             sub _is_empty_response {
470 0     0   0 my ( $self, $response ) = @_;
471              
472 0 0 0     0 return 1 if ref $response eq 'HASH' && !keys %{$response};
  0         0  
473 0 0 0     0 return 1 if ref $response eq 'ARRAY' && @{$response} == 0;
  0         0  
474 0         0 return;
475             }
476              
477             sub _post_request {
478 0     0   0 my ( $self, $params, $sig, $img_data ) = @_;
479              
480 0         0 my $post_params = [ map { $_ => $params->{$_} } sort keys %{$params} ];
  0         0  
  0         0  
481 0         0 push @{$post_params}, 'sig' => $sig;
  0         0  
482              
483 0 0       0 if ($img_data) {
484 0         0 push @{$post_params}, data => [
  0         0  
485             undef, 'filename',
486             'Content-Type' => 'image/jpeg',
487             'Content' => $img_data,
488             ];
489             }
490 0         0 return $self->ua->post(
491             $self->server_uri,
492             'Content_type' => 'form-data',
493             'Content' => $post_params,
494             )->content;
495             }
496              
497             1; # Magic true value required at end of module
498             __END__