WWW::Netflix::API - Interface for Netflix's API


WWW-Netflix-API documentation Contained in the WWW-Netflix-API distribution.

Index


Code Index:

NAME

Top

WWW::Netflix::API - Interface for Netflix's API

VERSION

Top

Version 0.10

OVERVIEW

Top

This module is to provide your perl applications with easy access to the Netflix API (http://developer.netflix.com/). The Netflix API allows access to movie and user information, including queues, rating, rental history, and more.

SYNOPSIS

Top

  use WWW::Netflix::API;
  use XML::Simple;
  use Data::Dumper;

  my %auth = Your::Custom::getAuthFromCache();
  # consumer key/secret values below are fake
  my $netflix = WWW::Netflix::API->new({
        consumer_key    => '4958gj86hj6g99',
        consumer_secret => 'QWEas1zxcv',
	access_token    => $auth{access_token},
	access_secret   => $auth{access_secret},
	user_id         => $auth{user_id},

	content_filter => sub { use XML::Simple; XMLin(@_) },  # optional
  });
  if( ! $auth{user_id} ){
    my ( $user, $pass ) = .... ;
    @auth{qw/access_token access_secret user_id/} = $netflix->RequestAccess( $user, $pass );
    Your::Custom::storeAuthInCache( %auth );
  }

  $netflix->REST->Users->Feeds;
  $netflix->Get() or die $netflix->content_error;
  print Dumper $netflix->content;

  $netflix->REST->Catalog->Titles->Movies('18704531');
  $netflix->Get() or die $netflix->content_error;
  print Dumper $netflix->content;

And for resources that do not require a netflix account:

  use WWW::Netflix::API;
  use XML::Simple;
  my $netflix = WWW::Netflix::API->new({
        consumer_key
        consumer_secret
        content_filter => sub { XMLin(@_) },
  });
  $netflix->REST->Catalog->Titles;
  $netflix->Get( term => 'zzyzx' );
  printf "%d Results.\n", $netflix->content->{number_of_results};
  printf "Title: %s\n", $_->{title}->{regular} for values %{ $netflix->content->{catalog_title} };




  # Retrieve entire catalog:
  $netflix->content_filter('catalog.xml');
  $netflix->REST->Catalog->Titles->Index;
  $netflix->Get();

GETTING STARTED

Top

The first step to using this module is to register at http://developer.netflix.com -- you will need to register your application, for which you'll receive a consumer_key and consumer_secret keypair.

Any applications written with the Netflix API must adhere to the Terms of Use (http://developer.netflix.com/page/Api_terms_of_use) and Branding Requirements (http://developer.netflix.com/docs/Branding).

Usage

This module provides access to the REST API via perl syntactical sugar. For example, to find a user's queue, the REST url is of the form users/userID/feeds :

  http://api.netflix.com/users/T1tareQFowlmc8aiTEXBcQ5aed9h_Z8zdmSX1SnrKoOCA-/queues/disc

Using this module, the syntax would be (note that the Post or Delete methods can be used instead of Get, depending upon the API action being taken):

  $netflix->REST->Users->Queues->Disc;
  $netflix->Get(%$params) or die $netflix->content_error;
  print $netflix->content;

Other examples include:

  $netflix->REST->Users;
  $netflix->REST->Users->At_Home;
  $netflix->REST->Catalog->Titles->Movies('18704531');
  $netflix->REST->Users->Feeds;
  $netflix->REST->Users->Rental_History;

All of the possibilities (and parameter details) are outlined here: http://developer.netflix.com/docs/REST_API_Reference

There is a helper method "rest2sugar" included that will provide the proper syntax given a url. This is handy for translating the example urls in the REST API Reference.

Resources

The following describe the authentication that's happening under the hood and were used heavily in writing this module:

http://developer.netflix.com/docs/Security

http://josephsmarr.com/2008/10/01/using-netflixs-new-api-a-step-by-step-guide/#

Net::OAuth

EXAMPLES

Top

The examples/ directory in the distribution has several examples to use as starting points.

There is a vars.inc file in the directory -- most of these example read that for customer key/secret, etc, so fill that file out first to enter your specific values.

Examples include:

login.pl

Takes a netflix account login/password and (for a customer key) obtains an access token, secret, and user_id.

profile.pl

Gets a user's netflix profile and prints the name.

queue.pl

Displays the Disc and Instant queues for a user.

feeds.pl

Grabs all of the user's feeds and stores the .rss files to disk.

history2ical.pl

Converts the rental history (shipped/returned, watched dates) into an ICal calendar (.ics) file.

search.pl

Takes a search string and returns the results from a catalog search.

  $ perl search.pl firefly
  4 results:
  Grave of the Fireflies (1988)
  Firefly Dreams (2001)
  Firefly (2002)
  Serenity (2005)

catalog.pl

Retrieves the entire Netflix catalog and saves it to catalog.xml in the current directory.

catalog-lwp_handlers.pl

Retrieves the entire Netflix catalog and saves it to catalog.xml in the current directory -- uses LWP handlers as an example of modifying the "ua" attribute.

catalog2db.pl

Converts the xmlfile fetched by catalog.pl to a SQLite database. Also contains an example Class::DBI setup for working with the generated database.

Also see the "TEST SUITE" source code for more examples.

METHODS

Top

new

This is the constructor. Takes a hashref of "ATTRIBUTES". Inherited from Class::Accessor.

Most important options to pass are the "consumer_key" and "consumer_secret".

REST

This is used to change the resource that is being accessed. Some examples:

  # The user-friendly way:
  $netflix->REST->Users->Feeds;

  # Including numeric parts:
  $netflix->REST->Catalog->Titles->Movies('60021896');

  # Load a pre-formed url (e.g. a title_ref from a previous query)
  $netflix->REST('http://api.netflix.com/users/T1tareQFowlmc8aiTEXBcQ5aed9h_Z8zdmSX1SnrKoOCA-/queues/disc?feed_token=T1u.tZSbY9311F5W0C5eVQXaJ49.KBapZdwjuCiUBzhoJ_.lTGnmES6JfOZbrxsFzf&oauth_consumer_key=v9s778n692e9qvd83wfj9t8c&output=atom');

RequestAccess

This is used to login as a netflix user in order to get an access token.

  my ($access_token, $access_secret, $user_id) = $netflix->RequestAccess( $user, $pass );

Get

Post

Delete

rest2sugar

ATTRIBUTES

Top

consumer_key

consumer_secret

access_token

access_secret

user_id

ua

User agent to use under the hood. Defaults to LWP::UserAgent->new(). Can be altered anytime, e.g.

	$netflix->ua->timeout(500);

content_filter

The content returned by the REST calls is POX (plain old XML). Setting this attribute to a code ref will cause the content to be "piped" through it.

  use XML::Simple;
  $netflix->content_filter(  sub { XMLin(@_) }  );  # Parse the XML into a perl data structure

If this is set to a scalar, it will be treated as a filename to store the result to, instead of it going to memory. This is especially useful for retrieving the (large) full catalog -- see "catalog.pl".

  $netflix->content_filter('catalog.xml');
  $netflix->REST->Catalog->Titles->Index;
  $netflix->Get();
  # print `ls -lart catalog.xml`

content

Read-Only. This returns the content from the REST calls. Natively, this is a scalar of POX (plain old XML). If a "content_filter" is set, the "original_content" is filtered through that, and the result is returned as the value of the content attribute (and is cached in the "_filtered_content" attribute.

original_content

Read-Only. This will return a scalar which is simply a dereference of "content_ref".

content_ref

Scalar reference to the original content.

content_error

Read-Only. If an error occurs, this will hold the error message/information.

url

Read-Only.

rest_url

Read-Only.

INTERNAL

Top

_url

_params

_levels

_submit

__OAuth_Request

_set_content

Takes scalar reference.

_filtered_content

Used to cache the return value of filtering the content through the content_filter.

WWW::Netflix::API::_UrlAppender

TEST SUITE

Top

Most of the test suite in the t/ directory requires a customer key/secret and access token/secret. You can supply these via enviromental variables:

	# *nix
	export WWW_NETFLIX_API__CONSUMER_KEY="qweqweqwew"
	export WWW_NETFLIX_API__CONSUMER_SECRET="asdasd"
	export WWW_NETFLIX_API__LOGIN_USER="you@example.com"
	export WWW_NETFLIX_API__LOGIN_PASS="qpoiuy"
	export WWW_NETFLIX_API__ACCESS_TOKEN="trreqweyueretrewere"
	export WWW_NETFLIX_API__ACCESS_SECRET="mnmbmbdsf"

	REM DOS
	SET WWW_NETFLIX_API__CONSUMER_KEY=qweqweqwew
	SET WWW_NETFLIX_API__CONSUMER_SECRET=asdasd
	SET WWW_NETFLIX_API__LOGIN_USER=you@example.com
	SET WWW_NETFLIX_API__LOGIN_PASS=qpoiuy
	SET WWW_NETFLIX_API__ACCESS_TOKEN=trreqweyueretrewere
	SET WWW_NETFLIX_API__ACCESS_SECRET=mnmbmbdsf

And then, from the extracted distribution directory, either run the whole test suite:

	perl Makefile.PL
	make test

or just execute specific tests:

	prove -v -Ilib t/api.t
	prove -v -Ilib t/access_token.t

APPLICATIONS

Top

Are you using WWW::Netflix::API in your application? Please email me with your application name and url or email, and i will be happy to list it here.

AUTHOR

Top

David Westbrook (CPAN: davidrw), <dwestbrook at gmail.com>

BUGS

Top

Please report any bugs or feature requests to bug-www-netflix-api at rt.cpan.org, or through the web interface at http://rt.cpan.org/NoAuth/ReportBug.html?Queue=WWW-Netflix-API. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.

SUPPORT

Top

You can find documentation for this module with the perldoc command.

    perldoc WWW::Netflix::API




You can also look for information at:

* RT: CPAN's request tracker

http://rt.cpan.org/NoAuth/Bugs.html?Dist=WWW-Netflix-API

* AnnoCPAN: Annotated CPAN documentation

http://annocpan.org/dist/WWW-Netflix-API

* CPAN Ratings

http://cpanratings.perl.org/d/WWW-Netflix-API

* Search CPAN

http://search.cpan.org/dist/WWW-Netflix-API

COPYRIGHT & LICENSE

Top


WWW-Netflix-API documentation Contained in the WWW-Netflix-API distribution.

package WWW::Netflix::API;

use warnings;
use strict;

our $VERSION = '0.10';

use base qw(Class::Accessor);

use Net::OAuth;
use HTTP::Request::Common;
use LWP::UserAgent;
use WWW::Mechanize;
use URI::Escape;

__PACKAGE__->mk_accessors(qw/
	consumer_key
	consumer_secret
	content_filter
	ua
	access_token
	access_secret
	user_id
	_levels
	rest_url
	_url
	_params

	content_ref
	_filtered_content
	content_error
/);

sub new {
  my $self = shift;
  my $fields = shift || {};
  $fields->{ua} ||= LWP::UserAgent->new();
  return $self->SUPER::new( $fields, @_ );
}

sub content {
  my $self = shift;
  return $self->_filtered_content if $self->_filtered_content;
  return unless $self->content_ref;
  return ${$self->content_ref} unless $self->content_filter && ref($self->content_filter);
  return $self->_filtered_content(
	&{$self->content_filter}( ${$self->content_ref}, @_ )
  );
}

sub original_content {
  my $self = shift;
  return $self->content_ref ? ${$self->content_ref} : undef;
}

sub _set_content {
  my $self = shift;
  my $content_ref = shift;
  $self->content_error( undef );
  $self->_filtered_content( undef );
  return $self->content_ref( $content_ref );
}

sub REST {
  my $self = shift;
  my $url = shift;
  $self->_levels([]);
  $self->_set_content(undef);
  if( $url ){
    my ($url, $querystring) = split '\?', $url, 2;
    $self->_url($url);
    $self->_params({
	map {
	  my ($k,$v) = split /=/, $_, 2;
	  $k !~ /^oauth_/
	    ? ( $k => uri_unescape($v) )
	    : ()
	}
	split /&/, $querystring||''
    });
    return $self->url;
  }
  $self->_url(undef);
  $self->_params({});
  return WWW::Netflix::API::_UrlAppender->new( stack => $self->_levels, append => {users=>$self->user_id} );
}

sub url {
  my $self = shift;
  return $self->_url if $self->_url;
  return join '/', 'http://api.netflix.com', @{ $self->_levels || [] };
}

sub _submit {
  my $self = shift;
  my $method = shift;
  my %options = ( %{$self->_params || {}}, @_ );
  my $which = $self->access_token ? 'protected resource' : 'consumer'; 
  my $res = $self->__OAuth_Request(
	$which,
	request_url    => $self->url,
	request_method => $method,
	token => $self->access_token,
	token_secret => $self->access_secret,
	extra_params => \%options,
  ) or do {
	warn $self->content_error;
	return;
  };

  return 1;
}
sub Get {
  my $self = shift;
  return $self->_submit('GET', @_);
}
sub Post {
  my $self = shift;
  return $self->_submit('POST', @_);
}
sub Delete {
  my $self = shift;
  return $self->_submit('DELETE', @_);
}

sub rest2sugar {
  my $self = shift;
  my $url = shift;
  my @stack = ( '$netflix', 'REST' );
  my @params;
  $url =~ s#^http://api.netflix.com##;
  $url =~ s#(/users/)(\w|-){30,}/#$1#i;
  $url =~ s#/(\d+)(?=/|$)#('$1')#;
  if( $url =~ s#\?(.+)## ){
    my $querystring = $1;
    @params = map {
	  my ($k,$v) = split /=/, $_, 2;
	  [ $k, uri_unescape($v) ]
	}
	split /&/, $querystring;
  }
  push @stack, map {
		join '_', map { ucfirst } split '_', lc $_
	}
	grep { length($_) }
	split '/', $url
  ;
  return (
	join('->', @stack),
	sprintf('$netflix->Get(%s)',
		join( ', ', map { sprintf "'%s' => '%s'", @$_ } @params ),
	),
  );
	
}

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

sub RequestAccess {
  my $self = shift;
  my ($user, $pass) = @_;

  my ($request, $response);
  ###
  $request = $self->__OAuth_Request(
	'request token',
	request_url  => 'http://api.netflix.com/oauth/request_token',
	request_method => 'POST',
  ) or do {
	warn $self->content_error;
	return;
  };
  $response = Net::OAuth->response('request token')->from_post_body( $self->original_content );
  my $request_token    = $response->token
	or return;
  my $request_secret   = $response->token_secret;
  my $login_url        = $response->extra_params->{login_url};
  my $application_name = $response->extra_params->{application_name};
  $application_name =~ tr/+/ /;
  $application_name = uri_unescape( $application_name );
  ###
    my $mech = WWW::Mechanize->new;
    my $url = sprintf '%s&oauth_callback=%s&oauth_consumer_key=%s&application_name=%s',
	$login_url,
	map { uri_escape($_) }
		'',
		$self->consumer_key,
		$application_name,
    ; 
    $mech->get($url);
    if ( ! $mech->success ) {
	warn sprintf 'Get of "%s" FAILED (%s): "%s"', $url, $mech->res->status_line, $mech->content;
	return;
    }
    my %fields = (
		login => $user,
		password => $pass,
    );
    if( ! $mech->form_with_fields(keys %fields) ){
      warn "Submission to '$url' failed. Content: ".$mech->content;
      return;
    }
    $mech->submit_form( fields => \%fields );
  return unless $mech->content =~ /successfully/i && $mech->content !~ /failed/i;
  ###
  $request = $self->__OAuth_Request(
	'access token',
	request_url  => 'http://api.netflix.com/oauth/access_token',
	request_method => 'POST',
	token => $request_token,
	token_secret => $request_secret,
  ) or do {
	warn $self->content_error;
	return;
  };
  $response = Net::OAuth->response('access token')->from_post_body( $self->original_content );

  $self->access_token(  $response->token );
  $self->access_secret( $response->token_secret );
  $self->user_id(       $response->extra_params->{user_id} );
  return ($self->access_token, $self->access_secret, $self->user_id);
}

sub __OAuth_Request {
  my $self = shift;
  my $request_type = shift;
  my $params = {   # Options to pass-through to Net::OAuth::*Request constructor
	# Static:
        consumer_key      => $self->consumer_key,
        consumer_secret   => $self->consumer_secret,
        signature_method  => 'HMAC-SHA1',
        timestamp         => time,
        nonce             => join('::', $0, $$),
        version           => '1.0',

	# Defaults:
	request_url       => $self->url,
	request_method    => 'POST',

	# User overrides/additions:
	@_

	# Most common user-provided params will be:
	#   request_url
	#   request_method
	#   token
	#   token_secret
	#   extra_params
  };
  $self->_set_content(undef);

  my $request = Net::OAuth->request( $request_type )->new( %$params );
  $request->sign;

  my $url = $request->to_url;
  $self->rest_url( "$url" );

  my $method = $params->{request_method};
  my $req;
  if( $method eq 'GET' ){
        $req = GET $url;
  }elsif(  $method eq 'POST' ){
        $req = POST $url;
  }elsif(  $method eq 'DELETE' ){
        $req = HTTP::Request->new( 'DELETE', $url );
  }else{
        $self->content_error( "Unknown method '$method'" );
        return;
  }
  # if content_filter exists and is a scalar, then use it as the filename to write to instead of content being in memory.
  my $response = $self->ua->request( $req, ($self->content_filter && !ref($self->content_filter) ? $self->content_filter : ()) );
  if ( ! $response->is_success ) {
        $self->content_error( sprintf '%s Request to "%s" failed (%s): "%s"', $method, $url, $response->status_line, $response->content );
        return;
  }elsif( ! length ${$response->content_ref} ){
        $self->content_error( sprintf '%s Request to "%s" failed (%s) (__EMPTY_CONTENT__): "%s"', $method, $url, $response->status_line, $response->content );
        return;
  }
  $self->_set_content( $response->content_ref );

  return $response;
}

########################################

package WWW::Netflix::API::_UrlAppender;

use strict;
use warnings;
our $AUTOLOAD;

sub new {
  my $self = shift;
  my $params = { @_ };
  return bless { stack => $params->{stack}, append => $params->{append}||{} }, $self;
}

sub AUTOLOAD {
  my $self = shift;
  my $dir = lc $AUTOLOAD;
  $dir =~ s/.*:://; 
  if( $dir ne 'destroy' ){
    push @{ $self->{stack} }, $dir;
    push @{ $self->{stack} }, @_ if scalar @_;
    push @{ $self->{stack} }, $self->{append}->{$dir} if exists $self->{append}->{$dir};
  }
  return $self;
}

########################################

1; # End of WWW::Netflix::API

__END__