File Coverage

blib/lib/WWW/Netflix/API.pm
Criterion Covered Total %
statement 95 175 54.2
branch 19 54 35.1
condition 6 18 33.3
subroutine 19 25 76.0
pod 10 10 100.0
total 149 282 52.8


line stmt bran cond sub pod time code
1             package WWW::Netflix::API;
2              
3 9     9   215978 use warnings;
  9         24  
  9         277  
4 9     9   49 use strict;
  9         17  
  9         473  
5              
6             our $VERSION = '0.12';
7              
8 9     9   44 use base qw(Class::Accessor);
  9         24  
  9         9739  
9              
10 9     9   34575 use Net::OAuth;
  9         10139  
  9         363  
11 9     9   11449 use HTTP::Request::Common;
  9         294643  
  9         940  
12 9     9   10556 use LWP::UserAgent;
  9         262578  
  9         354  
13 9     9   19015 use WWW::Mechanize;
  9         1260089  
  9         402  
14 9     9   106 use URI::Escape;
  9         18  
  9         21973  
15              
16             __PACKAGE__->mk_accessors(qw/
17             consumer_key
18             consumer_secret
19             content_filter
20             ua
21             access_token
22             access_secret
23             user_id
24             _levels
25             base_url
26             rest_url
27             _url
28             _params
29              
30             content_ref
31             _filtered_content
32             content_error
33             /);
34              
35             sub new {
36 2     2 1 42 my $self = shift;
37 2   50     10 my $fields = shift || {};
38 2   33     39 $fields->{ua} ||= LWP::UserAgent->new();
39 2         21420 $fields->{base_url} = "api-public.netflix.com";
40 2         39 return $self->SUPER::new( $fields, @_ );
41             }
42              
43             sub content {
44 4     4 1 8515 my $self = shift;
45 4 50       17 return $self->_filtered_content if $self->_filtered_content;
46 4 100       65 return unless $self->content_ref;
47 2 100 66     28 return ${$self->content_ref} unless $self->content_filter && ref($self->content_filter);
  1         15  
48 1         38 return $self->_filtered_content(
49 1         26 &{$self->content_filter}( ${$self->content_ref}, @_ )
  1         4  
50             );
51             }
52              
53             sub original_content {
54 4     4 1 6960 my $self = shift;
55 4 100       19 return $self->content_ref ? ${$self->content_ref} : undef;
  2         32  
56             }
57              
58             sub _set_content {
59 7     7   58818 my $self = shift;
60 7         12 my $content_ref = shift;
61 7         56 $self->content_error( undef );
62 7         88 $self->_filtered_content( undef );
63 7         80 return $self->content_ref( $content_ref );
64             }
65              
66             sub REST {
67 3     3 1 3767 my $self = shift;
68 3         6 my $url = shift;
69 3         14 $self->_levels([]);
70 3         53 $self->_set_content(undef);
71 3 50       31 if( $url ){
72 0         0 my ($url, $querystring) = split '\?', $url, 2;
73 0         0 $self->_url($url);
74 0         0 $self->_params({
75             map {
76 0   0     0 my ($k,$v) = split /=/, $_, 2;
77 0 0       0 $k !~ /^oauth_/
78             ? ( $k => uri_unescape($v) )
79             : ()
80             }
81             split /&/, $querystring||''
82             });
83 0         0 return $self->url;
84             }
85 3         10 $self->_url(undef);
86 3         37 $self->_params({});
87 3         27 return WWW::Netflix::API::_UrlAppender->new( stack => $self->_levels, append => {users=>$self->user_id} );
88             }
89              
90             sub url {
91 7     7 1 10408 my $self = shift;
92 7 100       26 return $self->_url if $self->_url;
93 5 100       65 return join '/', "http://" . $self->{base_url}, @{ $self->_levels || [] };
  5         18  
94             }
95              
96             sub _submit {
97 0     0   0 my $self = shift;
98 0         0 my $method = shift;
99 0 0       0 my %options = ( %{$self->_params || {}}, @_ );
  0         0  
100 0 0       0 my $which = $self->access_token ? 'protected resource' : 'consumer';
101             my $res = $self->__OAuth_Request(
102             $which,
103             request_url => $self->url,
104             request_method => $method,
105             token => $self->access_token,
106             token_secret => $self->access_secret,
107             extra_params => \%options,
108 0 0       0 ) or do {
109 0         0 warn $self->content_error;
110 0         0 return;
111             };
112              
113 0         0 return 1;
114             }
115             sub Get {
116 0     0 1 0 my $self = shift;
117 0         0 return $self->_submit('GET', @_);
118             }
119             sub Post {
120 0     0 1 0 my $self = shift;
121 0         0 return $self->_submit('POST', @_);
122             }
123             sub Delete {
124 0     0 1 0 my $self = shift;
125 0         0 return $self->_submit('DELETE', @_);
126             }
127              
128             sub rest2sugar {
129 3     3 1 1134 my $self = shift;
130 3         7 my $url = shift;
131 3         6 my @stack = ( '$netflix', 'REST' );
132 3         5 my @params;
133 3         34 $url =~ s#^http://$self->{base_url}##;
134 3         26 $url =~ s#(/users/)(\w|-){30,}/#$1#i;
135 3         20 $url =~ s#/(\d+)(?=/|\?|$)#('$1')#;
136 3 100       18 if( $url =~ s#\?(.+)## ){
137 1         3 my $querystring = $1;
138 4         33 @params = map {
139 1         7 my ($k,$v) = split /=/, $_, 2;
140 4         15 [ $k, uri_unescape($v) ]
141             }
142             split /&/, $querystring;
143             }
144 9         35 push @stack, map {
145 9         21 join '_', map { ucfirst } split '_', lc $_
  12         21  
146             }
147 3         24 grep { length($_) }
148             split '/', $url
149             ;
150             return (
151 4         20 join('->', @stack),
152             sprintf('$netflix->Get(%s)',
153 3         24 join( ', ', map { sprintf "'%s' => '%s'", @$_ } @params ),
154             ),
155             );
156            
157             }
158              
159             # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
160              
161             sub RequestAccess {
162 0     0 1 0 my $self = shift;
163 0         0 my ($user, $pass) = @_;
164              
165 0         0 my ($request, $response);
166             ###
167             $request = $self->__OAuth_Request(
168             'request token',
169             request_url => "http://" . $self->{base_url} . "/oauth/request_token",
170             request_method => 'POST',
171 0 0       0 ) or do {
172 0         0 warn $self->content_error;
173 0         0 return;
174             };
175 0         0 $response = Net::OAuth->response('request token')->from_post_body( $self->original_content );
176 0 0       0 my $request_token = $response->token
177             or return;
178 0         0 my $request_secret = $response->token_secret;
179 0         0 my $login_url = $response->extra_params->{login_url};
180 0         0 my $application_name = $response->extra_params->{application_name};
181 0         0 $application_name =~ tr/+/ /;
182 0         0 $application_name = uri_unescape( $application_name );
183             ###
184 0         0 my $mech = WWW::Mechanize->new;
185 0         0 my $url = sprintf '%s&oauth_callback=%s&oauth_consumer_key=%s&application_name=%s',
186             $login_url,
187 0         0 map { uri_escape($_) }
188             '',
189             $self->consumer_key,
190             $application_name,
191             ;
192 0         0 $mech->get($url);
193 0 0       0 if ( ! $mech->success ) {
194 0         0 warn sprintf 'Get of "%s" FAILED (%s): "%s"', $url, $mech->res->status_line, $mech->content;
195 0         0 return;
196             }
197 0         0 my %fields = (
198             login => $user,
199             password => $pass,
200             );
201 0 0       0 if( ! $mech->form_with_fields(keys %fields) ){
202 0         0 warn "Submission to '$url' failed. Content: ".$mech->content;
203 0         0 return;
204             }
205 0         0 $mech->submit_form( fields => \%fields );
206 0 0 0     0 return unless $mech->content =~ /successfully/i && $mech->content !~ /failed/i;
207             ###
208             $request = $self->__OAuth_Request(
209             'access token',
210             request_url => "http://" . $self->{base_url} . "/oauth/access_token",
211             request_method => 'POST',
212             token => $request_token,
213             token_secret => $request_secret,
214 0 0       0 ) or do {
215 0         0 warn $self->content_error;
216 0         0 return;
217             };
218 0         0 $response = Net::OAuth->response('access token')->from_post_body( $self->original_content );
219              
220 0         0 $self->access_token( $response->token );
221 0         0 $self->access_secret( $response->token_secret );
222 0         0 $self->user_id( $response->extra_params->{user_id} );
223 0         0 return ($self->access_token, $self->access_secret, $self->user_id);
224             }
225              
226             sub __OAuth_Request {
227 0     0   0 my $self = shift;
228 0         0 my $request_type = shift;
229 0         0 my $params = { # Options to pass-through to Net::OAuth::*Request constructor
230             # Static:
231             consumer_key => $self->consumer_key,
232             consumer_secret => $self->consumer_secret,
233             signature_method => 'HMAC-SHA1',
234             timestamp => time,
235             nonce => join('::', $0, $$),
236             version => '1.0',
237              
238             # Defaults:
239             request_url => $self->url,
240             request_method => 'POST',
241              
242             # User overrides/additions:
243             @_
244              
245             # Most common user-provided params will be:
246             # request_url
247             # request_method
248             # token
249             # token_secret
250             # extra_params
251             };
252 0         0 $self->_set_content(undef);
253              
254 0         0 my $request = Net::OAuth->request( $request_type )->new( %$params );
255 0         0 $request->sign;
256              
257 0         0 my $url = $request->to_url;
258 0         0 $self->rest_url( "$url" );
259              
260 0         0 my $method = $params->{request_method};
261 0         0 my $req;
262 0 0       0 if( $method eq 'GET' ){
    0          
    0          
263 0         0 $req = GET $url;
264             }elsif( $method eq 'POST' ){
265 0         0 $req = POST $url;
266             }elsif( $method eq 'DELETE' ){
267 0         0 $req = HTTP::Request->new( 'DELETE', $url );
268             }else{
269 0         0 $self->content_error( "Unknown method '$method'" );
270 0         0 return;
271             }
272             # if content_filter exists and is a scalar, then use it as the filename to write to instead of content being in memory.
273 0 0 0     0 my $response = $self->ua->request( $req, ($self->content_filter && !ref($self->content_filter) ? $self->content_filter : ()) );
274 0 0       0 if ( ! $response->is_success ) {
  0 0       0  
275 0         0 $self->content_error( sprintf '%s Request to "%s" failed (%s): "%s"', $method, $url, $response->status_line, $response->content );
276 0         0 return;
277             }elsif( ! length ${$response->content_ref} ){
278 0         0 $self->content_error( sprintf '%s Request to "%s" failed (%s) (__EMPTY_CONTENT__): "%s"', $method, $url, $response->status_line, $response->content );
279 0         0 return;
280             }
281 0         0 $self->_set_content( $response->content_ref );
282              
283 0         0 return $response;
284             }
285              
286             ########################################
287              
288             package WWW::Netflix::API::_UrlAppender;
289              
290 9     9   87 use strict;
  9         22  
  9         356  
291 9     9   164 use warnings;
  9         31  
  9         1933  
292             our $AUTOLOAD;
293              
294             sub new {
295 5     5   79 my $self = shift;
296 5         16 my $params = { @_ };
297 5   100     67 return bless { stack => $params->{stack}, append => $params->{append}||{} }, $self;
298             }
299              
300             sub AUTOLOAD {
301 15     15   5088 my $self = shift;
302 15         31 my $dir = lc $AUTOLOAD;
303 15         60 $dir =~ s/.*:://;
304 15 50       40 if( $dir ne 'destroy' ){
305 15         18 push @{ $self->{stack} }, $dir;
  15         35  
306 15 100       38 push @{ $self->{stack} }, @_ if scalar @_;
  2         4  
307 15 100       43 push @{ $self->{stack} }, $self->{append}->{$dir} if exists $self->{append}->{$dir};
  3         9  
308             }
309 15         89 return $self;
310             }
311              
312             ########################################
313              
314             1; # End of WWW::Netflix::API
315              
316             __END__