package Flickr::API; use strict; use warnings; use LWP::UserAgent; use XML::Parser::Lite::Tree; use XML::LibXML::Simple; use Flickr::API::Request; use Flickr::API::Response; use Net::OAuth; use Digest::MD5 qw(md5_hex); use Scalar::Util qw(blessed); use Encode qw(encode_utf8); use Carp; use Storable qw(store_fd retrieve_fd); our @ISA = qw(LWP::UserAgent); our $VERSION = '1.28'; sub new { my ($class, $args) = @_; my $self; if ($args->{lwpobj}){ my $lwpobj = $args->{lwpobj}; if (defined($lwpobj)){ my $lwpobjtype = Scalar::Util::blessed($lwpobj); if (defined($lwpobjtype)){ $self = $lwpobj; @ISA = ($lwpobjtype); } } } $self = LWP::UserAgent->new unless $self; # # If the args have consumer_key, handle as oauth # if (defined($args->{consumer_key})) { $self->{api_type} = 'oauth'; $self->{rest_uri} = $args->{rest_uri} || 'https://api.flickr.com/services/rest/'; $self->{auth_uri} = $args->{auth_uri} || 'https://api.flickr.com/services/oauth/authorize'; $self->{upload_uri} = $args->{upload_uri} || 'https://api.flickr.com/services/upload/'; if (defined($args->{consumer_secret})) { # # for the flickr api object # $self->{oauth_request} = 'consumer'; $self->{consumer_key} = $args->{consumer_key}; $self->{consumer_secret} = $args->{consumer_secret}; $self->{unicode} = $args->{unicode} || 0; # # for Net::OAuth Consumer Requests # $self->{oauth}->{request_method} = $args->{request_method} || 'GET'; $self->{oauth}->{request_url} = $self->{rest_uri}; $self->{oauth}->{consumer_secret} = $args->{consumer_secret}; $self->{oauth}->{consumer_key} = $args->{consumer_key}; $self->{oauth}->{nonce} = $args->{nonce} || _make_nonce(); $self->{oauth}->{signature_method} = $args->{signature_method} ||'HMAC-SHA1'; $self->{oauth}->{timestamp} = $args->{timestamp} || time; $self->{oauth}->{version} = '1.0'; $self->{oauth}->{callback} = $args->{callback}; } else { carp "OAuth calls must have at least a consumer_key and a consumer_secret"; $self->_set_status(0,"OAuth call without consumer_secret"); } if (defined($args->{token}) && defined($args->{token_secret})) { # # If we have token/token secret then we are for protected resources # $self->{oauth}->{token_secret} = $args->{token_secret}; $self->{oauth}->{token} = $args->{token}; $self->{oauth_request} = 'protected resource'; } # # Preserve request and access tokens # if (defined($args->{request_token}) and ref($args->{request_token}) eq 'Net::OAuth::V1_0A::RequestTokenResponse') { $self->{oauth}->{request_token} = $args->{request_token}; } if (defined($args->{access_token}) and ref($args->{access_token}) eq 'Net::OAuth::AccessTokenResponse') { $self->{oauth}->{access_token} = $args->{access_token}; } } else { $self->{api_type} = 'flickr'; $self->{api_key} = $args->{api_key} || $args->{key}; $self->{api_secret} = $args->{api_secret} || $args->{secret}; $self->{rest_uri} = $args->{rest_uri} || 'https://api.flickr.com/services/rest/'; $self->{auth_uri} = $args->{auth_uri} || 'https://api.flickr.com/services/auth/'; $self->{upload_uri} = $args->{upload_uri} || 'https://api.flickr.com/services/upload/'; $self->{unicode} = $args->{unicode} || 0; if (defined($args->{key}) or defined ($self->{key})) { delete $args->{key}; delete $self->{key}; # Silenty switch key to api_key until a later release # carp "Argument 'key' is deprecated and has been changed to api_key"; } if (defined ($args->{secret}) or defined ($self->{secret})) { delete $args->{secret}; delete $self->{secret}; # Silenty switch secret to api_secret until a later release # carp "Argument 'secret' is deprecated and has been changed to api_secret"; } $self->{fauth}->{frob} = $args->{frob}; $self->{fauth}->{api_key} = $self->{api_key}; $self->{fauth}->{api_secret} = $self->{api_secret}; $self->{fauth}->{token} = $args->{token}; carp "You must pass an API key or a Consumer key to the constructor" unless defined $self->{api_key}; } eval { require Compress::Zlib; $self->default_header('Accept-Encoding' => 'gzip'); }; bless $self, $class; $self->_clear_status(); $self->_initialize(); return $self; } # # Execution Methods # sub execute_method { my ($self, $method, $args) = @_; my $request; if ($self->is_oauth) { # # Consumer Request Params # my $oauth = {}; $oauth->{nonce} = _make_nonce(); $oauth->{consumer_key} = $self->{oauth}->{consumer_key}; $oauth->{consumer_secret} = $self->{oauth}->{consumer_secret}; $oauth->{timestamp} = time; $oauth->{signature_method} = $self->{oauth}->{signature_method}; $oauth->{version} = $self->{oauth}->{version}; if (defined($args->{'token'}) or defined($args->{'token_secret'})) { carp "\ntoken and token_secret must be specified in Flickr::API->new() and are being discarded\n"; undef $args->{'token'}; undef $args->{'token_secret'}; } if (defined($args->{'consumer_key'}) or defined($args->{'consumer_secret'})) { carp "\nconsumer_key and consumer_secret must be specified in Flickr::API->new() and are being discarded\n"; undef $args->{'consumer_key'}; undef $args->{'consumer_secret'}; } $oauth->{extra_params} = $args; $oauth->{extra_params}->{method} = $method; # # Protected resource params # if (defined($self->{oauth}->{token})) { $oauth->{token} = $self->{oauth}->{token}; $oauth->{token_secret} = $self->{oauth}->{token_secret}; } $request = Flickr::API::Request->new({ 'api_type' => 'oauth', 'method' => $method, 'args' => $oauth, 'rest_uri' => $self->{rest_uri}, 'unicode' => $self->{unicode}, }); } else { $request = Flickr::API::Request->new({ 'api_type' => 'flickr', 'method' => $method, 'args' => $args, 'rest_uri' => $self->{rest_uri}, 'unicode' => $self->{unicode}, }); } return $self->execute_request($request); } sub execute_request { my ($self, $request) = @_; $request->{api_args}->{method} = $request->{api_method}; unless ($self->is_oauth) { $request->{api_args}->{api_key} = $self->{api_key}; } if (defined($self->{api_secret}) && length($self->{api_secret})) { unless ($self->is_oauth) { $request->{api_args}->{api_sig} = $self->_sign_args($request->{api_args}); } } unless ($self->is_oauth) { $request->encode_args(); } my $response = $self->request($request); bless $response, 'Flickr::API::Response'; $response->init_flickr(); if ($response->{_rc} != 200){ $response->set_fail(0, "API returned a non-200 status code ($response->{_rc})"); return $response; } my $content = $response->decoded_content(); $content = $response->content() unless defined $content; my $xls = XML::LibXML::Simple->new(ForceArray => 0); my $tree = XML::Parser::Lite::Tree::instance()->parse($content); my $hashref = $xls->XMLin($content,KeyAttr => []); my $rsp_node = $self->_find_tag($tree->{children}); if ($rsp_node->{name} ne 'rsp'){ $response->set_fail(0, "API returned an invalid response"); return $response; } if ($rsp_node->{attributes}->{stat} eq 'fail'){ my $fail_node = $self->_find_tag($rsp_node->{children}); if ($fail_node->{name} eq 'err'){ $response->set_fail($fail_node->{attributes}->{code}, $fail_node->{attributes}->{msg}); } else { $response->set_fail(0, "Method failed but returned no error code"); } return $response; } if ($rsp_node->{attributes}->{stat} eq 'ok'){ $response->set_ok($rsp_node,$hashref); return $response; } $response->set_fail(0, "API returned an invalid status code"); return $response; } sub upload { my ($self, $args) = @_; my $upload; unless ($self->api_permissions() eq 'write' || $self->api_permissions() eq 'delete') { croak "insufficient permission for upload"; } my %cfg = $self->export_config; $cfg{'request_url'} = $self->{upload_uri}; $upload = Flickr::API::Upload->new({ 'photo' => $args, 'api' => \%cfg, 'api_type' => $self->api_type(), }); my $response = $self->request($upload); bless $response, 'Flickr::API::Response'; $response->init_flickr(); if ($response->{_rc} != 200){ $response->set_fail(0, "Upload returned a non-200 status code ($response->{_rc})"); return $response; } my $content = $response->decoded_content(); $content = $response->content() unless defined $content; my $xls = XML::LibXML::Simple->new(ForceArray => 0); my $tree = XML::Parser::Lite::Tree::instance()->parse($content); my $hashref = $xls->XMLin($content,KeyAttr => []); my $rsp_node = $self->_find_tag($tree->{children}); if ($rsp_node->{name} ne 'rsp'){ $response->set_fail(0, "Upload returned an invalid response"); return $response; } if ($rsp_node->{attributes}->{stat} eq 'fail'){ my $fail_node = $self->_find_tag($rsp_node->{children}); if ($fail_node->{name} eq 'err'){ $response->set_fail($fail_node->{attributes}->{code}, $fail_node->{attributes}->{msg}); } else { $response->set_fail(0, "Upload failed but returned no error code"); } return $response; } if ($rsp_node->{attributes}->{stat} eq 'ok'){ $response->set_ok($rsp_node,$hashref); return $response; } $response->set_fail(0, "API returned an invalid status code"); return $response; } # # Persistent config methods # # # Method to return hash of important Flickr or OAuth parameters. # OAuth can also export meaningful subsets of parameters based # on OAuth message type. # sub export_config { my ($self, $type, $params) = @_; if ($self->is_oauth) { unless($params) { $params='do_it'; } my %oauth; if (defined($type)) { if ($params =~ m/^m.*/i) { %oauth = map { ($_) => undef } @{Net::OAuth->request($type)->all_message_params()}; } elsif ($params =~ m/^a.*/i) { %oauth = map { ($_) => undef } @{Net::OAuth->request($type)->all_api_params()}; } else { %oauth = map { ($_) => undef } @{Net::OAuth->request($type)->all_params()}; } foreach my $param (keys %oauth) { if (defined ($self->{oauth}->{$param})) { $oauth{$param} = $self->{oauth}->{$param}; } } return %oauth; } else { return %{$self->{oauth}}; } } else { return %{$self->{fauth}}; } } # # Use perl core Storable to save important parameters. # sub export_storable_config { my ($self,$file) = @_; open my $EXPORT, '>', $file or croak "\nCannot open $file for write: $!\n"; my %config = $self->export_config(); store_fd(\%config, $EXPORT); close $EXPORT; return; } # # Use perl core Storable for re-vivifying an API object from saved parameters # sub import_storable_config { my ($class,$file) = @_; open my $IMPORT, '<', $file or croak "\nCannot open $file for read: $!\n"; my $config_ref = retrieve_fd($IMPORT); close $IMPORT; my $api = $class->new($config_ref); return $api; } # # Preauthorization Methods # # Handle request token requests (process: REQUEST TOKEN, authorize, access token) # sub oauth_request_token { my ($self, $args) = @_; my %oauth = %{$self->{oauth}}; unless ($self->is_oauth) { carp "\noauth_request_token called for Non-OAuth Flickr::API object\n"; return; } unless ($self->get_oauth_request_type() eq 'consumer') { croak "\noauth_request_token called using protected resource Flickr::API object\n"; } $self->{oauth_request} = 'Request Token'; $oauth{request_url} = $args->{request_token_url} || 'https://api.flickr.com/services/oauth/request_token'; $oauth{callback} = $args->{callback} || 'https://127.0.0.1'; $Net::OAuth::PROTOCOL_VERSION = Net::OAuth::PROTOCOL_VERSION_1_0A; my $orequest = Net::OAuth->request('Request Token')->new(%oauth); $orequest->sign; my $response = $self->get($orequest->to_url); my $content = $response->decoded_content(); $content = $response->content() unless defined $content; if ($content =~ m/^oauth_problem=(.+)$/) { carp "\nRequest token not granted: '",$1,"'\n"; $self->{oauth}->{request_token} = $1; return $1; } $self->{oauth}->{request_token} = Net::OAuth->response('request token')->from_post_body($content); $self->{oauth}->{callback} = $oauth{callback}; return 'ok'; } # # Participate in authorization (process: request token, AUTHORIZE, access token) # sub oauth_authorize_uri { my ($self, $args) = @_; unless ($self->is_oauth) { carp "oauth_authorize_uri called for Non-OAuth Flickr::API object"; return; } my %oauth = %{$self->{oauth}}; $self->{oauth_request} = 'User Authentication'; $oauth{perms} = lc($args->{perms}) || 'read'; carp "\nThe 'perms' parameter must be one of: read, write, delete\n" and return unless defined($oauth{perms}) && $oauth{perms} =~ /^(read|write|delete)$/; $Net::OAuth::PROTOCOL_VERSION = Net::OAuth::PROTOCOL_VERSION_1_0A; return $self->{auth_uri} . '?oauth_token=' . $oauth{'request_token'}{'token'} . '&perms=' . $oauth{perms}; } # # flickr preauthorization # sub request_auth_url { my ($self, $perms, $frob) = @_; if ($self->is_oauth) { carp "request_auth_url called for an OAuth instantiated Flickr::API"; return; } $perms = lc($perms); carp "\nThe 'perms' parameter must be one of: read, write, delete\n" and return unless defined($perms) && $perms =~ /^(read|write|delete)$/; return unless defined $self->{api_secret} && length $self->{api_secret}; my %fauth = ( 'api_key' => $self->{api_key}, 'perms' => $perms ); if ($frob) { $fauth{frob} = $frob; } my $sig = $self->_sign_args(\%fauth); $fauth{api_sig} = $sig; my $uri = URI->new($self->{auth_uri}); $uri->query_form(%fauth); return $uri; } # # Access Token (post authorization) Methods # # Handle access token requests (process: request token, authorize, ACCESS TOKEN) # sub oauth_access_token { my ($self, $args) = @_; unless ($self->is_oauth) { carp "oauth_access_token called for Non-OAuth Flickr::API object"; return; } if ($args->{token} ne $self->{oauth}->{request_token}->{token}) { carp "Request token in API does not match token for access token request"; return; } # # Stuff the values for the Net::OAuth factory # $self->{oauth}->{verifier} = $args->{verifier}; $self->{oauth}->{token} = $args->{token}; $self->{oauth}->{token_secret} = $self->{oauth}->{request_token}->{token_secret}; my %oauth = %{$self->{oauth}}; $oauth{request_url} = $args->{access_token_url} || 'https://api.flickr.com/services/oauth/access_token'; $Net::OAuth::PROTOCOL_VERSION = Net::OAuth::PROTOCOL_VERSION_1_0A; my $request = Net::OAuth->request('Access Token')->new(%oauth); $request->sign; my $response = $self->get($request->to_url); my $content = $response->decoded_content(); $content = $response->content() unless defined $content; if ($content =~ m/^oauth_problem=(.+)$/) { carp "\nAccess token not granted: '",$1,"'\n"; $self->{oauth}->{access_token} = $1; delete $self->{oauth}->{token}; # Not saving problematic request token delete $self->{oauth}->{token_secret}; # token secret delete $self->{oauth}->{verifier}; # and verifier copies return $1; } $self->{oauth}->{access_token} = Net::OAuth->response('access token')->from_post_body($content); $self->{oauth}->{token} = $self->{oauth}->{access_token}->token(); $self->{oauth}->{token_secret} = $self->{oauth}->{access_token}->token_secret(); delete $self->{oauth}->{request_token}; #No longer valid, anyway delete $self->{oauth}->{verifier}; return 'ok'; } sub flickr_access_token { my ($self,$frob) = @_; my $rsp = $self->execute_method('flickr.auth.getToken', {api_key => $self->{api_key}, frob => $frob }); my $response_ref = $rsp->as_hash(); $self->{fauth}->{frob} = $frob; $self->{token} = $response_ref->{auth}->{token}; $self->{fauth}->{token} = $response_ref->{auth}->{token}; $self->{fauth}->{user} = $response_ref->{auth}->{user}; return $response_ref->{stat}; } # # Utility methods # sub is_oauth { my ($self) = @_; if (defined $self->{api_type} and $self->{api_type} eq 'oauth') { return 1; } else { return 0; } } sub get_oauth_request_type { my ($self) = @_; if (defined $self->{api_type} and $self->{api_type} eq 'oauth') { return $self->{oauth_request}; } else { return; } } sub api_type { my ($self) = @_; return $self->{api_type}; } sub api_success { my ($self) = @_; return $self->{flickr}->{status}->{api_success}; } sub api_message { my ($self) = @_; return $self->{flickr}->{status}->{api_message}; } sub api_permissions { my ($self) = @_; my $rsp; my $check; my $retval; if ($self->is_oauth) { if (defined($self->{oauth}->{perms})) { $self->_set_status(1,"Permissions retrieved from config."); } else { $self->{oauth}->{perms} = 'none'; #preload no perms $rsp = $self->execute_method('flickr.auth.oauth.checkToken'); if (!$rsp->success()) { $rsp->_propagate_status($self->{flickr}->{status}); carp "\nUnable to validate OAuth token. Flickr error: ", $self->{flickr}->{status}->{error_code}," - \"", $self->{flickr}->{status}->{error_message},"\" \n"; delete $self->{oauth}->{perms}; $self->_set_status(0,"Unable to validate OAuth token, Flickr API call not successful."); } else { $check = $rsp->as_hash(); $self->{oauth}->{perms} = $check->{oauth}->{perms}; $self->_set_status(1,"Permissions retrieved from Flickr."); } } # else not cached $retval = $self->{oauth}->{perms}; } # is_oauth else { # is_flickr if (defined($self->{fauth}->{perms})) { $self->_set_status(1,"Permissions retrieved from config."); } else { $self->{fauth}->{perms} = 'none'; #preload no perms $rsp = $self->execute_method('flickr.auth.checkToken',{'auth_token' => $self->{fauth}->{token}}); if (!$rsp->success()) { $rsp->_propagate_status($self->{flickr}->{status}); carp "\nUnable to validate Flickr token. Flickr error: ", $self->{flickr}->{status}->{error_code}," - \"", $self->{flickr}->{status}->{error_message},"\" \n"; delete $self->{fauth}->{perms}; $self->_set_status(0,"Unable to validate Flickr token, Flickr API call not successful."); } else { $check = $rsp->as_hash(); $self->{fauth}->{perms} = $check->{auth}->{perms}; $self->_set_status(1,"Permissions retrieved from Flickr."); } } # else not cached $retval = $self->{fauth}->{perms}; } # else is_flickr return $retval; } # # Private methods # sub _sign_args { my ($self, $args) = @_; if ($self->is_oauth) { carp "_sign_args called for an OAuth instantiated Flickr::API"; return; } my $sig = $self->{api_secret}; foreach my $key (sort {$a cmp $b} keys %{$args}) { my $value = (defined($args->{$key})) ? $args->{$key} : ""; $sig .= $key . $value; } return md5_hex(encode_utf8($sig)) if $self->{unicode}; return md5_hex($sig); } sub _find_tag { my ($self, $children) = @_; for my $child(@{$children}){ return $child if $child->{type} eq 'element'; } return {}; } sub _make_nonce { return md5_hex(rand); } sub _export_api { my ($self) = @_; my $api = {}; $api->{oauth} = $self->{oauth}; $api->{fauth} = $self->{fauth}; $api->{flickr} = $self->{flickr}; $api->{api_type} = $self->{api_type}; $api->{api_key} = $self->{api_key}; $api->{api_secret} = $self->{api_secret}; $api->{rest_uri} = $self->{rest_uri}; $api->{unicode} = $self->{unicode}; $api->{auth_uri} = $self->{auth_uri}; $api->{upload_uri} = $self->{upload_uri}; return $api; } sub _initialize { my ($self) = @_; $self->_set_status(1,'Base API initialized'); return; } sub _full_status { my ($self) = @_; return $self->{flickr}->{status}; } sub _clear_status { my ($self) = @_; # the API status $self->_set_status(1,''); # the propagated response status $self->{flickr}->{status}->{_rc} = 0; $self->{flickr}->{status}->{success} = 1; # initialize as successful $self->{flickr}->{status}->{error_code} = 0; $self->{flickr}->{status}->{error_message} = ''; return; } sub _set_status { my ($self, $good, $msg) = @_; if ($good != 0) { $good = 1; } $self->{flickr}->{status}->{api_success} = $good; $self->{flickr}->{status}->{api_message} = $msg; return; } 1; __END__ =head1 NAME Flickr::API - Perl interface to the Flickr API =head1 SYNOPSIS =head2 Using OAuth to call a B not requiring authentication use Flickr::API; my $api = Flickr::API->new({ 'consumer_key' => 'your_api_key', 'consumer_secret' => 'your_app_secret', }); my $response = $api->execute_method('flickr.test.echo', { 'foo' => 'bar', 'baz' => 'quux', }); my $config_file = $HOME/saved-flickr.st; $api->export_storable_config($config_file); =head2 Non-OAuth method calling B not requiring authentication use Flickr::API; # key deprecated in favor of api_key # secret deprecated in favor of api_secret # my $api = Flickr::API->new({ 'api_key' => 'your_api_key', 'api_secret' => 'your_app_secret', }); my $response = $api->execute_method('flickr.test.echo', { 'foo' => 'bar', 'baz' => 'quux', }); =head2 Alternatively, Using OAuth for non-authenticated B use Flickr::API; use Flickr::API::Request; my $api = Flickr::API->new({'consumer_key' => 'your_api_key','consumer_secret' => 'your_app_secret'}); my $request = Flickr::API::Request->new({ 'method' => 'flickr.test.echo', 'args' => {}, }); my $response = $api->execute_request($request); =head2 Authenticate an OAuth API Object starting with saved configuration use Flickr::API; use Term::ReadLine; my $config_file = "$ENV{HOME}/saved-flickr.st"; my $term = Term::ReadLine->new('Testing Flickr::API'); $term->ornaments(0); my $api = Flickr::API->import_storable_config($config_file); my $rt_rc = $api->oauth_request_token( { 'callback' => 'https://127.0.0.1/' } ); my %request_token; if ( $rt_rc eq 'ok' ) { my $uri = $api->oauth_authorize_uri({ 'perms' => 'read' }); my $prompt = "\n\n$uri\n\n" . "Copy the above url to a browser, and authenticate with Flickr\n" . "Press [ENTER] once you get the redirect: "; my $input = $term->readline($prompt); $prompt = "\n\nCopy the redirect URL from your browser and enter it\nHere: "; $input = $term->readline($prompt); chomp($input); my ($callback_returned,$token_received) = split(/\?/,$input); my (@parms) = split(/\&/,$token_received); foreach my $pair (@parms) { my ($key,$val) = split(/=/,$pair); $key =~ s/oauth_//; $request_token{$key}=$val; } } my $ac_rc = $api->oauth_access_token(\%request_token); if ( $ac_rc eq 'ok' ) { $api->export_storable_config($config_file); my $response = $api->execute_method('flickr.auth.oauth.checkToken'); my $hash_ref = $response->as_hash(); $response = $api->execute_method('flickr.prefs.getPrivacy'); my $rsp_node = $response->as_tree(); } =head2 The OAuth authorization uri will look something like: https://api.flickr.com/services/oauth/authorize?oauth_token=12345678901234567-890abcdefedcba98&perms=read =head2 The callback is called with a token and verifier such as: https://127.0.0.1/?oauth_token=12345678901234567-890abcdefedcba98&oauth_verifier=cafe12345678feed =head1 DESCRIPTION An interface for using the Flickr API. C is a subclass of L, so all of the various proxy, request limits, caching, etc are available. C can instantiate using either the Flickr Authentication (deprecated) or the OAuth Authentication. OAuth is handled using L. =head1 SUBROUTINES/METHODS =over =item C 'value', ... })> Returns as new L object. The options are as follows: =over =item either C for the Flickr auth or C for OAuth Your API key (one or the other form is required) =item either C for the Flickr auth or C for OAuth Your API key's secret (the one matching the api_key/consumer_key is required) =item C & C Override the URIs used for contacting the API. =item C Base the C on this object, instead of creating a new instance of L. This is useful for using the features of e.g. L. =item C This flag controls whether Flickr::API expects you to pass UTF-8 bytes (unicode=0, the default) or actual unicode strings (unicode=1) in the request. =item C, C, C, C, C These values are used by L to assemble and sign OAuth I request Flickr API calls. The defaults are usually fine. =item C The callback is used in oauth authentication. When Flickr authorizes you, it returns the access token and access token secret in a callback URL. This defaults to https://127.0.0.1/ =item C and C These values are used by L to assemble and sign OAuth I request Flickr API calls. =back =item C Constructs a L object and executes it, returning a L object. =item C Executes a L object, returning a L object. Calls are signed if a secret was specified when creating the L object. =item C Returns a L object representing the URL that an application must redirect a user to for approving an authentication token. C<$perms> must be B, B, or B. For web-based applications I<$frob> is an optional parameter. Returns undef if a secret was not specified when creating the C object. =item C Returns a hash of all or part of the persistent parts of the Flickr::API object with additional behaviors for Flickr::API objects using OAuth. =over =item oauth message type: one of C, C, C, C or C This is one of the the message type that L handles. Message type is optional. =item oauth parameter set: C or C or undef. L will return message params, api params or all params depending on what is requested. All params is the default. =back If the Flickr::API object identifies as Flickr original authentication, return a hashref $VAR1 = { 'frob' => '12332112332112300-feedabcde123456c-1234567', 'api_key' => 'cafefeedbeef13579246801234567890', 'api_secret' => 'beef321432154321', 'token' => '97531086421234567-cafe123456789abc' }; or the subset thereof depending on what has been used by the API. If the older form of key/secret was used, the constructor will change these to the api_key/api_secret forms. If the API object identifies as OAuth authentication, and C is specified, then export_config will return a hash of the OAuth parameters for the specified L message type. Further, if parameter is specified, then export_config returns either either the set of B parameters or B parameters for the message type. If parameter is not specified then both parameter type are returned. For example: my %config = $api->export_config('protected resource'); or my %config = $api->export_config('protected resource','message'); When export_config is called without arguments, then it returns the OAuth portion of the L object. If present the L I and I objects are also included. VAR1 = { 'access_token' => bless( { 'extra_params' => { 'fullname' => 'Louis', 'user_nsid' => '12345678@N00', 'username' => 'meanameicallmyself' }, 'from_hash' => 1, 'token' => '12345678901234567-cafe123098765432', 'token_secret' => 'eebeef000fedbca1' }, 'Net::OAuth::AccessTokenResponse' ), 'callback' => 'https://127.0.0.1', 'consumer_key' => 'cafefeedbeef13579246801234567890', 'consumer_secret' => 'fedcba9876543210', 'nonce' => '917fa882fa7babd5a1b7702e7d19502a', 'request_method' => 'GET', 'request_url' => 'https://api.flickr.com/services/rest/', 'signature_method' => 'HMAC-SHA1', 'timestamp' => 1436129308, 'token' => '12345678901234567-cafe123098765432', 'token_secret' => 'eebeef000fedbca1', 'version' => '1.0' }; my %config = $api->export_config(); =back This method can be used to extract and save the API parameters for future use. =over =item C This method wraps export_config with a file open and storable store_fd to add some persistence to a Flickr::API object. =item C This method retrieves a storable config of a Flickr::API object and revivifies the object. =item C Returns the oauth request type in the Flickr::API object. Some Flickr methods will require a C request type and others a simple C request type. =item C Assembles, signs, and makes the OAuth B call, and if successful stores the L I in the L object. The required parameters are: =over =item C Your API Key =item C Your API Key's secret =item C The URI Method: GET or POST =item C Defaults to: L =back =item C The required parameters are: =over =item C =back =item C Assembles, signs, and makes the OAuth B call, and if successful stores the L I in the L object. The required parameters are: =over =item C Your API Key =item C Your API Key's secret =item C The URI Method: GET or POST =item C Defaults to: L =item C The request token secret from the L I object returned from the I call. =back =item C Returns a L object representing the URL that an application must redirect a user to for approving a request token. =over =item C Permission the application is requesting, one of B, defaults to B. =back =item C Returns B<1> if the L object is OAuth flavored, B<0> otherwise. =back =head1 AUTHOR Cal Henderson, Ecal@iamcal.comE Auth API patches provided by Aaron Straup Cope Subclassing patch from AHP OAuth patches and additions Louis B. Moore =head1 LICENSE AND COPYRIGHT Copyright (C) 2004-2013, Cal Henderson, Ecal@iamcal.comE OAuth patches and additions Copyright (C) 2014-2016 Louis B. Moore This program is released under the Artistic License 2.0 by The Perl Foundation. =head1 SEE ALSO L, L, L, L, L, L L L =cut