eBay-API-Simple

 view release on metacpan or  search on metacpan

Changes  view on Meta::CPAN

# Changes for eBay::API::Simple

TODO: add ClientAlerts and BestMatch backend

0.29 Wed Aug  1 17:34:01 PDT 2012
- add IAF Token support to Trading module

0.28 Mon May  7 16:37:35 PDT 2012
- add allow_blessed and convert_blessed to json encode call 

0.27
- add support for basic authorization

0.25
- add support for parallel requests using LWP::Parallel

0.24
- fix HTML and RSS test scripts (stale URLs)
- in build_urls method, use sort for consistent URL output
- add better error handling for JSON backend

0.23 Fri May 27 17:31:16 PDT 2011
- add JSON backend

0.22 Fri Apr  1 10:16:30 PDT 2011
- rework test scripts

0.21 Thu Jan 27 22:08:31 PST 2011
- build url function now allows for multiple keys in the query string
  i.e. /uri/?q=foo1&q=foo2&q=foo3
- fix prereq - dependent on URI::Encode >= 3.30

0.20 Wed Jan 26 21:23:35 PST 2011
- fix test

0.19 Wed Jan 26 13:35:59 PST 2011
- check for $ENV{HOME} being defined before using
- add more utf8 fixes
- add special fix for unicode in 5.8.1

0.18 Wed Jul 28 13:11:30 GMT+7 2010
- encode "keywords" field for UTF-8 in Finding module
- test for UTF-8 compatibility for Finding module

0.17 Thu Jul 15 14:09:57 PDT 2010
- add YAML support for API configurations 
- remove worldofgood test
- add SellerReport.pl script for pulling out the sellers details from 
  Shopping and Trading api and the usage of ebay.yaml 

0.16 Thu Jul  1 15:00:46 PDT 2010
- add support for attributes in the request via enable_attributes flag

0.15 Tue Jun 15 15:00:55 PDT 2010
- add sandbox docs
- modify Finding backend to support the "X-EBAY-SOA-SERVICE-NAME" 
  defaults to: FindingService
  * provides support custom services on the Finding platform
   
0.14
- modify debugging
- modify Trading backend: calls can now be made without a password 
  since it's not required for a fetch token call.

0.13 Fri Feb  5 13:28:31 PST 2010
- add query string arg support for RSS and HTML backends
- fixed XML namespace stripping (now done in base class with optional override)

0.12 Wed Jan 27 11:32:30 PST 2010
- add support to the Merchandising services

0.11 Tue Jan 26 18:15:38 PST 2010 
- add to documentation

0.10 Sat Jan 23 19:44:31 PST 2010
- fix tests

0.09 Fri Jan 22 11:54:36 PST 2010
- fix version issue

0.07 Fri Jan 22 11:34:36 PST 2010
- add support for eBay Finding2.0 services
  * eBay::API::Simple::Finding
- add proxy support to base class

0.06 Mon Aug  3 09:06:58 PDT 2009
- fix fish config routine
- add Simple.pm subclass

0.05 Mon Jul 20 17:30:43 PDT 2009
- add default timeout of 20 seconds
- added support for username/password on Trading API

0.04 Mon Feb 16 15:40:05 PST 2009
- release to CPAN
- code cleanup
- add ebay.ini support
- fix tests
- fix xmlout bug in Shopping backend

0.02 Thu Jan  8 13:34:36 PST 2009
- several doc udpates
- made some cosmetic changes

0.01 Thu Jan  8 10:03:09 PST 2009
- alpha release for this eBay::API::Simple library

MANIFEST  view on Meta::CPAN

Changes
lib/eBay/API/Simple.pm
lib/eBay/API/Simple/Finding.pm
lib/eBay/API/Simple/HTML.pm
lib/eBay/API/Simple/JSON.pm
lib/eBay/API/Simple/Merchandising.pm
lib/eBay/API/Simple/Parallel.pm
lib/eBay/API/Simple/RSS.pm
lib/eBay/API/Simple/Shopping.pm
lib/eBay/API/Simple/Trading.pm
lib/eBay/API/SimpleBase.pm
docs/ebay.yaml
Makefile.PL
MANIFEST			This list of files
README.rst
t/01use.t
ex/01Shopping.t
ex/02HTML.t
ex/03Trading.t
ex/04RSS.t
ex/05Finding.t
ex/05Finding_utf8.t
ex/06Merchandising.t
ex/09HalfShopping.t
META.yml                                 Module YAML meta-data (added by MakeMaker)
META.json                                Module JSON meta-data (added by MakeMaker)

META.json  view on Meta::CPAN

{
   "abstract" : "Flexible SDK supporting all eBay web services",
   "author" : [
      "Tim Keefer <tkeefer@gmail.com>"
   ],
   "dynamic_config" : 1,
   "generated_by" : "ExtUtils::MakeMaker version 6.62, CPAN::Meta::Converter version 2.112150",
   "license" : [
      "unknown"
   ],
   "meta-spec" : {
      "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec",
      "version" : "2"
   },
   "name" : "eBay-API-Simple",
   "no_index" : {
      "directory" : [
         "t",
         "inc"
      ]
   },
   "prereqs" : {
      "build" : {
         "requires" : {
            "ExtUtils::MakeMaker" : 0
         }
      },
      "configure" : {
         "requires" : {
            "ExtUtils::MakeMaker" : 0
         }
      },
      "runtime" : {
         "requires" : {
            "HTTP::Headers" : 0,
            "HTTP::Request" : 0,
            "JSON" : 0,
            "LWP::UserAgent" : 0,
            "URI" : "1.57",
            "URI::Escape" : 0,
            "XML::LibXML" : 0,
            "XML::Parser" : 0,
            "XML::Simple" : 0,
            "YAML" : 0
         }
      }
   },
   "release_status" : "stable",
   "version" : "0.29"
}

META.yml  view on Meta::CPAN

---
abstract: 'Flexible SDK supporting all eBay web services'
author:
  - 'Tim Keefer <tkeefer@gmail.com>'
build_requires:
  ExtUtils::MakeMaker: 0
configure_requires:
  ExtUtils::MakeMaker: 0
dynamic_config: 1
generated_by: 'ExtUtils::MakeMaker version 6.62, CPAN::Meta::Converter version 2.112150'
license: unknown
meta-spec:
  url: http://module-build.sourceforge.net/META-spec-v1.4.html
  version: 1.4
name: eBay-API-Simple
no_index:
  directory:
    - t
    - inc
requires:
  HTTP::Headers: 0
  HTTP::Request: 0
  JSON: 0
  LWP::UserAgent: 0
  URI: 1.57
  URI::Escape: 0
  XML::LibXML: 0
  XML::Parser: 0
  XML::Simple: 0
  YAML: 0
version: 0.29

Makefile.PL  view on Meta::CPAN

use ExtUtils::MakeMaker;
# See lib/ExtUtils/MakeMaker.pm for details of how to influence
# the contents of the Makefile that is written.
WriteMakefile(
    NAME              => 'eBay::API::Simple',
    VERSION_FROM      => 'lib/eBay/API/Simple.pm',
    PREREQ_PM         => {
        'XML::LibXML'    => 0,
        'HTTP::Headers'  => 0,
        'HTTP::Request'  => 0,
        'LWP::UserAgent' => 0,
        'XML::Parser'    => 0,
        'XML::Simple'    => 0,
        'URI'            => 1.57,
        'URI::Escape'    => 0,
        'YAML'           => 0,
        'JSON'           => 0,
    }, # e.g., Module::Name => 1.1
    ($] >= 5.005 ?     ## Add these new keywords supported since 5.005
      (ABSTRACT_FROM  => 'lib/eBay/API/Simple.pm', #abstract from module
       AUTHOR         => 'Tim Keefer <tkeefer@gmail.com>') : ()),
);


README.rst  view on Meta::CPAN

eBay::API::Simple
===========================

This module supports eBay's Shopping and Trading API services. In addition, eBay::API::Simple comes with a standard RSS and HTML back-end.

In order to use eBay aspects of this utility you must first register with eBay to get your `eBay Develoepr Site`_ API developer credentials] (see the ebay.yaml option for a way to tie these credentials into the Shopping and Trading back-ends)

Parallel Requests::

    my $pua = eBay::API::Simple::Parallel->new();

    my $call1 = eBay::API::Simple::RSS->new( {
        parallel => $pua,
    } );

    $call1->execute(
        ’http://worldofgood.ebay.com/Clothes-Shoes-Men/43/list?format=rss’,
    );

    my $call2 = eBay::API::Simple::RSS->new( {
        parallel => $pua,
    } );

    $call2->execute(
        ’http://worldofgood.ebay.com/Home-Garden/46/list?format=rss’
    );

    $pua->wait();

    if ( $pua->has_error() ) {
        print "ONE OR MORE FAILURES!\n";
    }

    print $call1->response_content() . "\n";
    print $call2->response_content() "\n";


Merchandising Services::

    use eBay::API::Simple::Merchandising;

    my $api = eBay::API::Simple::Merchandising->new( {
       appid   => '<your app id here>',
    } );

    $api->execute( 'getMostWatchedItems', { 
       maxResults => 3, categoryId => 267 
    });

    if ( $api->has_error() ) {
       die "Call Failed:" . $api->errors_as_string();
    }

    # getters for the response DOM or Hash
    my $dom  = $api->response_dom();
    my $hash = $api->response_hash();

  	 
[eBayAPISimpleMerchandising#Sandbox_Usage Sandbox Usage] |
[eBayAPISimpleMerchandising#Module_Documentation Module Documentation] 

Finding Services::

    use eBay::API::Simple::Finding;

    my $api = eBay::API::Simple::Finding->new( {
       appid   => 'myappid',
    } );

    $api->execute( 'findItemsByKeywords', { keywords => 'shoe' } );

    if ( $api->has_error() ) {
       die "Call Failed:" . $api->errors_as_string();
    }

    # getters for the response DOM or Hash
    my $dom  = $api->response_dom();
    my $hash = $api->response_hash();

[eBayAPISimpleFinding#Sandbox_Usage Sandbox Usage] |
[eBayAPISimpleFinding#Module_Documentation Module Documentation] 


Shopping Services::

    use eBay::API::Simple::Shopping;

    my $api = eBay::API::Simple::Shopping->new( {
       appid   => 'myappid',
    } );

    $api->execute( 'FindItemsAdvanced', { QueryKeywords => 'shoe' } );

    if ( $api->has_error() ) {
       die "Call Failed:" . $api->errors_as_string();
    }

    # getters for the response DOM or Hash
    my $dom  = $api->response_dom();
    my $hash = $api->response_hash();

[eBayAPISimpleShopping#Sandbox_Usage Sandbox Usage] |
[eBayAPISimpleShopping#Module_Documentation Module Documentation] 

Trading Services::

    use eBay::API::Simple::Trading;
  
    my $api = eBay::API::Simple::Trading->new( {
        appid   => 'myappid',
        devid   => 'mydevid',
        certid  => 'mycertid',
        token   => $mytoken,
    } );

    $api->execute( 'GetSearchResults', { Query => 'shoe' } );

    if ( $api->has_error() ) {
       die "Call Failed:" . $api->errors_as_string();
    }

    # getters for the response DOM or Hash
    my $dom  = $api->response_dom();
    my $hash = $api->response_hash();

[eBayAPISimpleTrading#Sandbox_Usage Sandbox Usage] |
[eBayAPISimpleTrading#Module_Documentation Module Documentation]

Generic JSON Backend::

    use eBay::API::Simple::JSON;

    my $api = eBay::API::Simple::JSON->new();

    # 'GET' call
    $api->get( 
       'http://localhost-django-vm.ebay.com/green/api/v1/greenerAlternative/32/'
    );

    if ( $api->has_error() ) {
        die "Call Failed:" . $api->errors_as_string();
    }

    # convenience methods
    my $hash = $api->response_hash();
    my $response_content = $api->response_content();
    my $request_content = $api->request_content();

    # HTTP::Request
    print $api->request->as_string();

    # HTTP::Response
    print $api->response->as_string();
    print $api->response->content();
    print $api->response->is_error();

    # HTTP::Headers
    print $api->response->headers->as_string();
    print $api->response->headers->content_type();

    # 'POST', 'PUT', 'DELETE' calls

    my $data = {     
        "user_eais_token" => "tim", 
        "body_text" => "mytext"
    };

    $api->post( 'http://myendpoint', $data );
    $api->put( 'http://myendpoint', $data );
    $api->delete( 'http://myendpoint' );

Generic HTML Backend::

    use eBay::API::Simple::HTML;

    my $api = eBay::API::Simple::HTML->new();

    $api->execute( 'http://www.example.com' );

    if ( $api->has_error() ) {
        die "Call Failed:" . $api->errors_as_string();
    }

    # getters for the response DOM or Hash
    my $dom  = $api->response_dom();
    my $hash = $api->response_hash();

Generic RSS Backend::

    use eBay::API::Simple::RSS;

    my $api = eBay::API::Simple::RSS->new();

    $api->execute( 
       'http://sfbay.craigslist.org/search/sss?query=shirt&format=rss'
    );

    if ( $api->has_error() ) {
        die "Call Failed:" . $api->errors_as_string();
    }

    # getters for the response DOM or Hash
    my $dom  = $api->response_dom();
    my $hash = $api->response_hash();

More Docs::

Visit CPAN to view the full documentation for [http://search.cpan.org/search?query=eBay%3A%3AAPI%3A%3ASimple eBay::API::Simple].


.. _eBay Developer Site: http://developer.ebay.com/

docs/ebay.yaml  view on Meta::CPAN

# eBay::API::Simple API Configurations
name: ebay_api_config
    
# Trading - External
api.ebay.com:
    password: pass
    username: user
    appid: appid 
    certid: certid
    devid: devid
    token: token

# Shopping
open.api.ebay.com:
    appid: appid
    certid: certid
    devid: devid
    version: 671

# Finding/Merchandising
svcs.ebay.com:
    appid: appid
    version: 1.0.0

ex/01Shopping.t  view on Meta::CPAN

use Test::More;
use strict; no warnings;
#use LWP::Debug qw(+);
use Data::Dumper;
use lib qw/lib/;

my @skip_msg;

BEGIN {


    eval {
        use eBay::API::Simple::Shopping;
    };
    
    if ( $@ ) {
        push @skip_msg, 'missing module eBay::API::Simple::Shopping, skipping test';
    }
    if ( scalar( @skip_msg ) ) {
        plan skip_all => join( ' ', @skip_msg );
    }
    else {
        plan qw(no_plan);
    }    
}

my $call;
eval {
    $call = eBay::API::Simple::Shopping->new(
        { appid => undef } # <----- your appid here
    );
};
if ( $@ ) {
    push( @skip_msg, $@ );
}

#$call->api_init( { 
#    site_id => 0,
#    uri     => $arg_uri,
#    domain  => $arg_domain,
#    app_id  => $arg_appid,
#    version => $arg_version,
#} );

eval {
        
};

SKIP: {
    skip join( ' ', @skip_msg), 1 if scalar( @skip_msg );

    $call->execute( 'FindItemsAdvanced', { 
        QueryKeywords => 'black shoes', 
        MaxEntries => 5,
    } );

    #diag $call->request_content;
    #diag $call->response_content;

    if ( $call->has_error() ) {
        fail( 'api call failed: ' . $call->errors_as_string() );
    }
    else {
        is( ref $call->response_dom(), 'XML::LibXML::Document', 'response dom' );
        is( ref $call->response_hash(), 'HASH', 'response hash' );

        like( $call->nodeContent('Timestamp'), 
            qr/^\d{4}-\d{2}-\d{2}/, 
            'response timestamp' 
        );
    
        ok( $call->nodeContent('TotalItems') > 10, 'response total items' );
        #diag( 'total items: ' . $call->nodeContent('TotalItems') );
        #diag( Dumper( $call->response_hash() ) );
    }

    $call->execute( 'BadCall', { QueryKeywords => 'shoe' } );

    is( $call->has_error(), 1, 'look for error flag' );
    ok( $call->errors_as_string() ne '', 'check for error message' );
    ok( $call->response_content() ne '', 'check for response content' );

    $call->execute( 'FindItemsAdvanced', { QueryKeywords => 'shoe' } );

    is( $call->has_error(), 0, 'error check' );
    is( $call->errors_as_string(), '', 'error string check' );
    ok( $call->nodeContent('TotalItems') > 10, 'response total items' );

    #diag( Dumper( $call->response_content() ) );

    my @nodes = $call->response_dom->findnodes(
        '/FindItemsAdvancedResponse/SearchResult/ItemArray/Item'
    );

    foreach my $n ( @nodes ) {
        # diag( $n->findvalue('Title/text()') );
        ok( $n->findvalue('Title/text()') ne '', 'title check' );
    }


    my $call2 = eBay::API::Simple::Shopping->new( { response_encoding => 'XML' } );
    $call2->execute( 'FindPopularSearches', { QueryKeywords => 'shoe' } );

    #diag( $call2->response_content() );
}

ex/02HTML.t  view on Meta::CPAN

use Test::More;
use strict; no warnings;
#use LWP::Debug qw(+);
use Data::Dumper;
use lib qw/lib/;

BEGIN {
    my @skip_msg;

    eval {
        use eBay::API::Simple::HTML;
    };
    
    if ( $@ ) {
        push @skip_msg, 'missing module eBay::API::Simple::HTML, skipping test';
    }
    if ( scalar( @skip_msg ) ) {
        plan skip_all => join( ' ', @skip_msg );
    }
    else {
        plan qw(no_plan);
    }    
}

my $call = eBay::API::Simple::HTML->new();

$call->execute( 'http://www.example.com/', { utm_campaign =>'simple_test' } );

#diag $call->request_content();
#diag $call->response_content();

if ( $call->has_error() ) {
    fail( 'api call failed: ' . $call->errors_as_string() );
}
else {
    is( ref $call->response_dom(), 'XML::LibXML::Document', 'response dom' );
    is( ref $call->response_hash(), 'HASH', 'response hash' );

    like( $call->response_hash->{head}{title}, 
        qr/Example/i, 
        'hash test' 
    );
    
    ok( $call->nodeContent('title') =~ /example/i, 
        'nodeContent test' );
    #diag Dumper( $call->response_hash );
}

$call->execute( 'http://bogusurlexample.com' );

is( $call->has_error(), 1, 'look for error flag' );
ok( $call->errors_as_string() ne '', 'check for error message' );
ok( $call->response_content() ne '', 'check for response content' );


ex/03Trading.t  view on Meta::CPAN

use Test::More;
use strict; no warnings;
#use LWP::Debug qw(+);
use Data::Dumper;
use lib qw/lib/;

BEGIN {
    my @skip_msg;

    eval {
        use eBay::API::Simple::Trading;
    };
    
    if ( $@ ) {
        push @skip_msg, 'missing module eBay::API::Simple::Trading, skipping test';
    }
    if ( scalar( @skip_msg ) ) {
        plan skip_all => join( ' ', @skip_msg );
    }
    else {
        plan qw(no_plan);
    }    
} 

my $call = eBay::API::Simple::Trading->new( {
    # domain => 'internal-api.vip.ebay.com',
} );

#$call->api_init( { 
#    site_id => 0,
#    uri     => $arg_uri,
#    domain  => $arg_domain,
#    app_id  => $arg_appid,
#    version => $arg_version,
#} );

eval{
    $call->execute( 'GetCategories', 
                    { DetailLevel => 'ReturnAll',
                      LevelLimit => 2,
                      CategoryParent => 11116,
                  } 
                );
};

SKIP: {
    skip $@, 1 if $@;

    if ( $call->has_error() ) {
        fail( 'api call failed: ' . $call->errors_as_string() );
    }
    else {
        is( ref $call->response_dom(), 'XML::LibXML::Document', 'response dom' );
        is( ref $call->response_hash(), 'HASH', 'response hash' );

        like( $call->nodeContent('Timestamp'), 
            qr/^\d{4}-\d{2}-\d{2}/, 
            'response timestamp' 
        );

        ok( $call->nodeContent('ReduceReserveAllowed') =~ /(true|false)/, 
            'reduce reserve allowed node' );
    }
        
}

$call->execute( 'BadCallSSS', { Query => 'shoe' } );

is( $call->has_error(), 1, 'look for error flag' );
ok( $call->errors_as_string() eq 'Call Failure-The API call "BadCallSSS" is invalid or not supported in this release.', 'check for error message' );
ok( $call->response_content() ne '', 'check for response content' );

$call->execute( 'GetSearchResults', { Query => 'shoe', Pagination => { EntriesPerPage => 2, PageNumber => 1 }  } );

is( $call->has_error(), 0, 'error check' );
is( $call->errors_as_string(), '', 'error string check' );
ok( $call->nodeContent('TotalNumberOfEntries') > 10, 'response total items' );
#diag $call->request_object->as_string();

my @nodes = $call->response_dom->findnodes(
    '//Item'
);

foreach my $n ( @nodes ) {
    #diag( $n->findvalue('Title/text()') );
    ok( $n->findvalue('Title/text()') ne '', 'title check' );
}
 
#diag Dumper( $call->response_hash );

ex/04RSS.t  view on Meta::CPAN

use Test::More;
use strict; no warnings;
#use LWP::Debug qw(+);
use Data::Dumper;
use lib qw/lib/;

BEGIN {
    my @skip_msg;

    eval {
        use eBay::API::Simple::RSS;
    };
    
    if ( $@ ) {
        push @skip_msg, 'missing module eBay::API::Simple::RSS, skipping test';
    }
    if ( scalar( @skip_msg ) ) {
        plan skip_all => join( ' ', @skip_msg );
    }
    else {
        plan qw(no_plan);
    }    
}

my $call = eBay::API::Simple::RSS->new();

$call->execute(
    'http://worldofgood.ebay.com/Eco-Organic-Clothes-Shoes-Men-Women-Children/43/list',
    { format => 'rss' },
);

if ( $call->has_error() ) {
    fail( 'api call failed: ' . $call->errors_as_string() );
}
else {
    is( ref $call->response_dom(), 'XML::LibXML::Document', 'response dom' );
    is( ref $call->response_hash(), 'HASH', 'response hash' );

    ok( $call->nodeContent('title') ne '', 'nodeContent test' );
    #diag Dumper( $call->response_hash );
}

$call->execute( 'http://bogusurlexample.com' );

is( $call->has_error(), 1, 'look for error flag' );
ok( $call->errors_as_string() ne '', 'check for error message' );
ok( $call->response_content() ne '', 'check for response content' );

my $call2 = eBay::API::Simple::RSS->new(
    { request_method => 'POST' }
);

$call2->execute(
    'http://en.wikipedia.org/w/index.php?title=Special:RecentChanges&feed=rss',
    { page => 1 },
);

is( ref $call2->response_dom(), 'XML::LibXML::Document', 'post response dom' );
is( ref $call2->response_hash(), 'HASH', 'post response hash' );
ok( $call2->nodeContent('title') ne '', 'post nodeContent test' );

ex/05Finding.t  view on Meta::CPAN

use Test::More;
use strict; no warnings;
#use LWP::Debug qw(+);
use Data::Dumper;
use lib qw/lib/;
use utf8;

my @skip_msg;

BEGIN {

    eval {
        use eBay::API::Simple::Finding;
    };
    
    if ( $@ ) {
        push @skip_msg, 'missing module eBay::API::Simple::Finding, skipping test';
    }
    
       
    if ( scalar( @skip_msg ) ) {
        plan skip_all => join( ' ', @skip_msg );
    }
    else {
        plan qw(no_plan);
    }    

}

my $call;

eval {

    $call = eBay::API::Simple::Finding->new(
        { appid => undef } # <----- your appid here
    );

};

if ( $@ ) {
    push( @skip_msg, $@ );
}

SKIP: {
    skip join("\n", @skip_msg), 1 if scalar(@skip_msg);
   
    $call->execute( 'findItemsByKeywords', { 
        keywords => 'black shoes', 
        paginationInput => { entriesPerPage => 15 } 
    } );

    #diag $call->request_content;
    #diag $call->response_content;
    
    if ( $call->has_error() ) {
        fail( 'api call failed: ' . $call->errors_as_string() );
    }
    else {
        is( ref $call->response_dom(), 'XML::LibXML::Document', 'response dom' );
        is( ref $call->response_hash(), 'HASH', 'response hash' );
        
        like( $call->nodeContent('timestamp'), 
            qr/^\d{4}-\d{2}-\d{2}/, 
            'response timestamp' 
        );
    
        ok( $call->nodeContent('totalEntries') > 10, 'response total entries' );
        #diag( 'total entries: ' . $call->nodeContent('totalEntries') );
        #diag( Dumper( $call->response_hash() ) );
    }

    $call->execute( 'BadCall', { keywords => 'shoe' } );

    is( $call->has_error(), 1, 'look for error flag' );
    ok( $call->errors_as_string() ne '', 'check for error message' );
    ok( $call->response_content() ne '', 'check for response content' );

    $call->execute( 'findItemsByKeywords', 
                    { keywords => 'shoe',
                      paginationInput => { entriesPerPage => 15 }
                  } );

    is( $call->has_error(), 0, 'error check' );
    is( $call->errors_as_string(), '', 'error string check' );
    ok( $call->nodeContent('totalEntries') > 10, 'response total entries' );

# now make sure it works with unicode
    $call->execute( 'findItemsByKeywords', 
                    { keywords => '( shoe, bota, sko, schuh, zapato, chaussure, παπούτσι, scarpa, туфля',
                      paginationInput => { entriesPerPage => 15 }
                  } );
    is( $call->has_error(), 0, 'error check with unicode characters' );
    is( $call->errors_as_string(), '', 'error string check' );
    ok( $call->nodeContent('totalEntries') > 10, 'response total entries' );


    #diag( Dumper( $call->response_hash() ) );

    my @nodes = $call->response_dom->findnodes(
        '//item'
    );

    my $count = 0;
    foreach my $n ( @nodes ) {
        ++$count;
        #diag( $n->findvalue('title/text()') );
        ok( $n->findvalue('title/text()') ne '', "title check $count" );
    }

}

ex/05Finding_utf8.t  view on Meta::CPAN

use Test::More;
use strict; no warnings;
#use LWP::Debug qw(+);
use Data::Dumper;
use lib qw/lib/;
use utf8;

my @skip_msg;

BEGIN {

    eval {
        use eBay::API::Simple::Finding;
    };
    
    if ( $@ ) {
        push @skip_msg, 'missing module eBay::API::Simple::Finding, skipping test';
    }
    
       
    if ( scalar( @skip_msg ) ) {
        plan skip_all => join( ' ', @skip_msg );
    }
    else {
        plan qw(no_plan);
    }    

}

my $call;

eval {

    $call = eBay::API::Simple::Finding->new(
        { appid => undef } # <----- your appid here
    );

};

if ( $@ ) {
    push( @skip_msg, $@ );
}

SKIP: {
    skip join("\n", @skip_msg), 1 if scalar(@skip_msg);
   
    $call->execute( 'findItemsByKeywords', { 
        #keywords => '(shoe, bota, туфля, Alfredo Falcón)', 
        keywords => 'Falcón', 
        paginationInput => { entriesPerPage => 15 } 
    } );

    #diag $call->request_content;
    #diag $call->response_content;
    
    if ( $call->has_error() ) {
        fail( 'api call failed: ' . $call->errors_as_string() );
    }
    else {
        is( ref $call->response_dom(), 'XML::LibXML::Document', 'response dom' );
        is( ref $call->response_hash(), 'HASH', 'response hash' );
        
        like( $call->nodeContent('timestamp'), 
            qr/^\d{4}-\d{2}-\d{2}/, 
            'response timestamp' 
        );
    
        ok( $call->nodeContent('totalEntries') > 2, 'response total entries' );
        #diag( 'total entries: ' . $call->nodeContent('totalEntries') );
        #diag( Dumper( $call->response_hash() ) );
    }

    $call->execute( 'BadCall', { keywords => 'shoe' } );

    is( $call->has_error(), 1, 'look for error flag' );
    ok( $call->errors_as_string() ne '', 'check for error message' );
    ok( $call->response_content() ne '', 'check for response content' );

    $call->execute( 'findItemsByKeywords', { keywords => 'shoe' } );

    is( $call->has_error(), 0, 'error check' );
    is( $call->errors_as_string(), '', 'error string check' );
    ok( $call->nodeContent('totalEntries') > 10, 'response total entries' );

    #diag( Dumper( $call->response_hash() ) );

    my @nodes = $call->response_dom->findnodes(
        '//item'
    );

    my $count = 0;
    foreach my $n ( @nodes ) {
        ++$count;
        #diag( $n->findvalue('title/text()') );
        ok( $n->findvalue('title/text()') ne '', "title check $count" );
    }

}

ex/06Merchandising.t  view on Meta::CPAN

use Test::More;
use strict; no warnings;
#use LWP::Debug qw(+);
use Data::Dumper;
use lib qw/lib/;

my @skip_msg;

BEGIN {

    eval {
        use eBay::API::Simple::Merchandising;
    };
    
    if ( $@ ) {
        push @skip_msg, 'missing module eBay::API::Simple::Merchandising, skipping test';
    }
    
       
    if ( scalar( @skip_msg ) ) {
        plan skip_all => join( ' ', @skip_msg );
    }
    else {
        plan qw(no_plan);
    }    

}

my $call;

eval {

    $call = eBay::API::Simple::Merchandising->new(
        { appid => undef, } # <----- your appid here
    );

};

if ( $@ ) {
    push( @skip_msg, $@ );
}

SKIP: {
    skip join("\n", @skip_msg), 1 if scalar(@skip_msg);
   
    $call->execute(
        'getMostWatchedItems', { maxResults => 1, categoryId => 267 } 
    );

    #diag $call->request_content;
    #diag $call->response_content;
    
    if ( $call->has_error() ) {
        fail( 'api call failed: ' . $call->errors_as_string() );
    }
    else {
        is( ref $call->response_dom(), 'XML::LibXML::Document', 'response dom' );
        is( ref $call->response_hash(), 'HASH', 'response hash' );

        like( $call->nodeContent('timestamp'), 
            qr/^\d{4}-\d{2}-\d{2}/, 
            'response timestamp' 
        );
    
        #diag( Dumper( $call->response_hash() ) );
    }

    $call->execute( 'BadCall', { keywords => 'shoe' } );

    is( $call->has_error(), 1, 'look for error flag' );
    ok( $call->errors_as_string() ne '', 'check for error message' );
    ok( $call->response_content() ne '', 'check for response content' );

    $call->execute( 'getSimilarItems', { itemId => 270358046257 } );

    #diag $call->request_content;
    #diag $call->response_content;

    is( $call->has_error(), 0, 'error check' );
    is( $call->errors_as_string(), '', 'error string check' );
    
    #diag( Dumper( $call->response_hash() ) );

    my @nodes = $call->response_dom->findnodes(
        '//item'
    );

    my $count = 0;
    foreach my $n ( @nodes ) {
        ++$count;
        #diag( $n->findvalue('title/text()') );
        ok( $n->findvalue('title/text()') ne '', "title check $count" );
    }

}

ex/09HalfShopping.t  view on Meta::CPAN

use Test::More;
use strict; no warnings;
#use LWP::Debug qw(+);
use Data::Dumper;
use lib qw/lib/;

my @skip_msg;

plan skip_all => join( ' ', 'Skipping Half test - not implemented' );

BEGIN {


    eval {
        use eBay::API::Simple::Shopping;
    };
    
    if ( $@ ) {
        push @skip_msg, 'missing module eBay::API::Simple::Shopping, skipping test';
    }
    if ( scalar( @skip_msg ) ) {
        #plan skip_all => join( ' ', @skip_msg );
    }
    else {
        #plan qw(no_plan);
    }    
}

my $call;
eval {
    $call = eBay::API::Simple::Shopping->new(
        { appid => undef, enable_attributes => 1 } # <----- your appid here
    );
};
if ( $@ ) {
    push( @skip_msg, $@ );
}


SKIP: {
    skip join( ' ', @skip_msg), 1 if scalar( @skip_msg );

    $call->execute ('FindHalfProducts', { 
        ProductID => { 
            type =>'ISBN', content => '0596006306' 
        }, 
        PageNumber => { content => 1 }, 
    } );

    is( ref $call->response_hash(), 'HASH', 'response hash' );
    
    #print $call->request_content() . "\n\n";
    #print $call->response_content();

    if ( $call->has_error() ) {
        die "Call Failed:" . $call->errors_as_string();
    }

    # getters for the response DOM or Hash
    my $dom   = $call->response_dom();
    my $title = $call->response_hash->{Products}{Product}{Title};
    
    like( $title, qr/Head First by Lynn Beighley/, "title check" );
    
    is( $call->nodeContent( 'Ack' ), 'Success', 'call was successfull' );
}

lib/eBay/API/Simple.pm  view on Meta::CPAN

package eBay::API::Simple;

our $VERSION = '0.29';

=head1 NAME 

eBay::API::Simple - Flexible SDK supporting all eBay web services

=head1 DESCRIPTION

This is the base class for the eBay::API::Simple::* libraries that provide
support for all of eBay's web services. This base class does nothing by itself
and must be subclassed to provide the complete web service support. 

See base class for complete docs, L<eBay::API::SimpleBase>

=item L<eBay::API::SimpleBase>

=item L<eBay::API::Simple::Parallel>

=item L<eBay::API::Simple::Merchandising>

=item L<eBay::API::Simple::Finding>

=item L<eBay::API::Simple::Shopping>

=item L<eBay::API::Simple::Trading>

=item L<eBay::API::Simple::HTML>

=item L<eBay::API::Simple::JSON>

=item L<eBay::API::Simple::RSS>

=head1 GET THE SOURCE

http://code.google.com/p/ebay-api-simple

=head1 PUBLIC METHODS

=head2 eBay::API::Simple::{subclass}->new()

see subclass for more docs.

=item L<eBay::API::SimpleBase>

=item L<eBay::API::Simple::Parallel>

=item L<eBay::API::Simple::Merchandising>

=item L<eBay::API::Simple::Finding>

=item L<eBay::API::Simple::Shopping>

=item L<eBay::API::Simple::Trading>

=item L<eBay::API::Simple::HTML>

=item L<eBay::API::Simple::JSON>

=item L<eBay::API::Simple::RSS>

=head1 AUTHOR

Tim Keefer <tim@timkeefer.com>

=cut
1;

lib/eBay/API/Simple/Finding.pm  view on Meta::CPAN

package eBay::API::Simple::Finding;

use strict;
use warnings;

use base 'eBay::API::SimpleBase';

use HTTP::Request;
use HTTP::Headers;
use XML::Simple;
use Encode;
use utf8;

our $DEBUG = 0;

=head1 NAME

eBay::API::Simple::Finding - Support for eBay's Finding 2.0 web service

=head1 DESCRIPTION

This class provides support for eBay's Finding 2.0 web services.

See http://developer.ebay.com/products/finding/

=head1 USAGE

  my $call = eBay::API::Simple::Finding->new( 
    { appid => '<your app id here>' } 
  );
  
  $call->execute( 'findItemsByKeywords', { keywords => 'shoe' } );

  if ( $call->has_error() ) {
     die "Call Failed:" . $call->errors_as_string();
  }

  # getters for the response DOM or Hash
  my $dom  = $call->response_dom();
  my $hash = $call->response_hash();

  print $call->nodeContent( 'timestamp' );
  print $call->nodeContent( 'totalEntries' );

  my @nodes = $dom->findnodes(
    '//item'
  );

  foreach my $n ( @nodes ) {
    print $n->findvalue('title/text()') . "\n";
  }

=head1 SANDBOX USAGE

  my $call = eBay::API::Simple::Finding->new( { 
    appid => '<your app id here>',
    domain => 'svcs.sandbox.ebay.com',
  } );
  
  $call->execute( 'findItemsByKeywords', { keywords => 'shoe' } );

  if ( $call->has_error() ) {
     die "Call Failed:" . $call->errors_as_string();
  }

  # getters for the response DOM or Hash
  my $dom  = $call->response_dom();
  my $hash = $call->response_hash();

=head1 PUBLIC METHODS

=head2 new( { %options } } 

Constructor for the Finding API call

  my $call = eBay::API::Simple::Finding->new( { 
    appid => '<your app id here>' 
    ... 
  } );

=head3 Options

=over 4

=item appid (required)

This appid is required by the web service. App ids can be obtained at 
http://developer.ebay.com

=item siteid

eBay site id to be supplied to the web service endpoint

defaults to EBAY-US

=item domain

domain for the web service endpoint

defaults to svcs.ebay.com

=item service

SOA Service name 

defaults to FindingService

=item uri

endpoint URI

defaults to /services/search/FindingService/v1

=item version

Version to be supplied to the web service endpoint

defaults to 1.0.0

=item https

Specifies is the API calls should be made over https.

defaults to 0

=item enable_attributes

This flag adds support for attributes in the request. If enabled request
data notes much be defined like so,

myElement => { content => 'element content', myattr => 'attr value' }

defaults to 0

=back

=head3 ALTERNATE CONFIG VIA ebay.yaml

  An ebay.yaml file can be use for configuring each 
  service endpoint.

  YAML files can be placed at the below locations. The first 
  file found will be loaded.

      ./ebay.yaml, ~/ebay.yaml, /etc/ebay.yaml 

  Sample YAML:

      # Trading - External
      api.ebay.com:
        appid: <your appid>
        certid: <your certid>
        devid: <your devid>
        token: <token>

      # Shopping
      open.api.ebay.com:
        appid: <your appid>
        certid: <your certid>
        devid: <your devid>
        version: 671

      # Finding/Merchandising
      svcs.ebay.com:
        appid: <your appid>
        version: 1.0.0

=cut

sub new {
    my $class = shift;
    my $self  = $class->SUPER::new(@_);

    $self->api_config->{service}  ||= 'FindingService';
    $self->api_config->{domain}  ||= 'svcs.ebay.com';
    $self->api_config->{uri}     ||= '/services/search/FindingService/v1';
    $self->api_config->{version} ||= '1.0.0';
    $self->api_config->{https}   ||= 0;
    $self->api_config->{siteid}  ||= 'EBAY-US';
    $self->api_config->{response_encoding} ||= 'XML'; # JSON, NV, SOAP
    $self->api_config->{request_encoding}  ||= 'XML';

    $self->_load_yaml_defaults();
    
    if ( $DEBUG ) {
        print STDERR sprintf( "API CONFIG:\n%s\n",
            $self->api_config_dump()
        );        
    }

    
    $self->_load_credentials();
    
    return $self;    
}

=head2 prepare( $verb, $call_data )

  $self->prepare( 'findItemsByKeywords', { keywords => 'shoe' } );
 
This method will construct the API request based on the $verb and
the $call_data.

=item $verb (required)

call verb, i.e. findItemsItemsByKeywords

=item $call_data (required)

hashref of call_data that will be turned into xml.

=cut

sub prepare {
    my $self = shift;
    
    $self->{verb}      = shift;
    $self->{call_data} = shift;

    if ( ! defined $self->{verb} || ! defined $self->{call_data} ) {
        die "missing verb and call_data";
    }
    
    # make sure we have appid
    $self->_load_credentials();
}

=head1 BASECLASS METHODS

=head2 request_agent

Accessor for the LWP::UserAgent request agent

=head2 request_object

Accessor for the HTTP::Request request object

=head2 request_content

Accessor for the complete request body from the HTTP::Request object

=head2 response_content

Accessor for the HTTP response body content

=head2 response_object

Accessor for the HTTP::Request response object

=head2 response_dom

Accessor for the LibXML response DOM

=head2 response_hash

Accessor for the hashified response content

=head2 nodeContent( $tag, [ $dom ] ) 

Helper for LibXML that retrieves node content

=head2 errors 

Accessor to the hashref of errors

=head2 has_error

Returns true if the call contains errors

=head2 errors_as_string

Returns a string of API errors if there are any.

=head1 PRIVATE METHODS

=head2 _get_request_body

This method supplies the XML body for the web service request

=cut

sub _get_request_body {
    my $self = shift;

    # handle a special unicode issue with perl 5.8.1
    if ( $self->{call_data}->{keywords} && $] eq '5.008001' ) {   
        Encode::_utf8_off($self->{call_data}->{keywords});
    }
    elsif ($self->{call_data}->{keywords}) {
        $self->{call_data}->{keywords} 
            = Encode::encode('utf8', $self->{call_data}->{keywords});
    }

    my $xml = "<?xml version='1.0' encoding='utf-8'?>"
        . "<" . $self->{verb} . "Request xmlns=\"http://www.ebay.com/marketplace/search/v1/services\">"
        . XMLout( 
            $self->{call_data}, 
            NoAttr => !$self->api_config->{enable_attributes},    
            KeepRoot => 1, 
            RootName => undef 
        )
        . "</" . $self->{verb} . "Request>";

    return $xml; 
}

=head2 _get_request_headers 

This method supplies the HTTP::Headers obj for the web service request

=cut

sub _get_request_headers {
    my $self = shift;
   
    my $obj = HTTP::Headers->new();
    
    $obj->push_header("X-EBAY-SOA-SERVICE-NAME" => $self->api_config->{service});
    $obj->push_header("X-EBAY-SOA-SERVICE-VERSION" => $self->api_config->{version});
    $obj->push_header("X-EBAY-SOA-SECURITY-APPNAME"  => $self->api_config->{appid});
    $obj->push_header("X-EBAY-SOA-GLOBAL-ID"  => $self->api_config->{siteid});
    $obj->push_header("X-EBAY-SOA-OPERATION-NAME" => $self->{verb});
    $obj->push_header("X-EBAY-SOA-REQUEST-DATA-FORMAT"  => $self->api_config->{request_encoding});
    $obj->push_header("X-EBAY-SOA-RESPONSE-DATA-FORMAT" => $self->api_config->{response_encoding});
    $obj->push_header("Content-Type" => "text/xml");
    
    return $obj;
}

=head2 _get_request_object 

This method creates and returns the HTTP::Request object for the
web service call.

=cut

sub _get_request_object {
    my $self = shift;

    my $url = sprintf( 'http%s://%s%s',
        ( $self->api_config->{https} ? 's' : '' ),
        $self->api_config->{domain},
        $self->api_config->{uri}
    );
  
    my $request_obj = HTTP::Request->new(
        "POST",
        $url,
        $self->_get_request_headers,
        $self->_get_request_body
    );

    return $request_obj;
}

sub _load_credentials {
    my $self = shift;
    
    # we only need to load credentials once
    return if $self->{_credentials_loaded};
    
    my @missing;
    
    # required by the API
    for my $p ( qw/appid/ ) {
        next if defined $self->api_config->{$p};
        
        if ( my $val = $self->_fish_ebay_ini( $p ) ) {
            $self->api_config->{$p} = $val;
        }
        else {
            push( @missing, $p );
        }
    }

    # die if we didn't get everything
    if ( scalar @missing > 0 ) {
        die "missing API credential: " . join( ", ", @missing );
    }
    
    $self->{_credentials_loaded} = 1;
    return;
}

sub _fish_ebay_ini {
    my $self = shift;
    my $arg  = shift;

    # initialize our hashref
    $self->{_ebay_ini} ||= {};
    
    # revert eBay::API::Simple keys to standard keys
    $arg = 'ApplicationKey' if $arg eq 'appid';

    # return it if we've already found it
    return $self->{_ebay_ini}{$arg} if defined $self->{_ebay_ini}{$arg};
    
    # ini files in order of importance
    my @files = (
        './ebay.ini',           
        "$ENV{HOME}/ebay.ini",
        '/etc/ebay.ini',
    );
    
    foreach my $file ( reverse @files ) {        
        if ( open( FILE, "<", $file ) ) {
        
            while ( my $line = <FILE> ) {
                chomp( $line );
            
                next if $line =~ m!^\s*\#!;
            
                my( $k, $v ) = split( /=/, $line );
            
                if ( defined $k && defined $v) {
                    $v =~ s/^\s+//;
                    $v =~ s/\s+$//;
                    
                    $self->{_ebay_ini}{$k} = $v;
                }
            }

            close FILE;
        }
    }
    
    return $self->{_ebay_ini}{$arg} if defined $self->{_ebay_ini}{$arg};
    return undef;
}

=head1 AUTHOR

Tim Keefer <tim@timkeefer.com>

=cut

1;

lib/eBay/API/Simple/HTML.pm  view on Meta::CPAN

package eBay::API::Simple::HTML;

use strict;
use warnings;

use base 'eBay::API::SimpleBase';

use HTTP::Request;
use HTTP::Headers;
use XML::Simple;
use URI::Escape;
use utf8;

our $DEBUG = 0;

=head1 NAME 

eBay::API::Simple::HTML - Support for grabbing an HTML page via API call

=head1 USAGE

  my $call = eBay::API::Simple::HTML->new();
  $call->execute( 'http://en.wikipedia.org/wiki/Main_Page', { a => 'b' } );

  if ( $call->has_error() ) {
     die "Call Failed:" . $call->errors_as_string();
  }

  # getters for the response DOM or Hash
  my $dom  = $call->response_dom();
  my $hash = $call->response_hash();

  # collect all h2 nodes
  my @h2 = $dom->getElementsByTagName('h2');

  foreach my $n ( @h2 ) {
    print $n->findvalue('text()') . "\n";
  }

=head1 PUBLIC METHODS

=head2 new( { %options } } 

  my $call = ebay::API::Simple::HTML->new();

=cut 

sub new {
    my $class = shift;
    my $self  = $class->SUPER::new(@_);

    $self->api_config->{request_method}  ||= 'GET';
    
    return $self;    
}

=head2 prepare( $url, $%args )

  $call->prepare( 'http://en.wikipedia.org/wiki/Main_Page', { a => 'b' } );
  
This method will construct the API request based on the $verb and
the $call_data.

=head3 Options

=over 4

=item $url (required)

URL for page to fetch

=item %$args (optional)

The supplied args will be encoded and appended to the URL

=back

=cut 

sub prepare {
    my $self = shift;
    
    $self->{url}  = shift;
    
    if ( ! defined $self->{url} ) {
        die "missing url";
    }

    # collect the optional args
    $self->{args} = shift;
}

=head2 response_hash

Custom response_hash method, uses the output from LibXML to generate the 
hash instead of the raw response body.

=cut

sub response_hash {
    my $self = shift;

    if ( ! defined $self->{response_hash} ) {
        $self->{response_hash} = XMLin( $self->response_dom->toString(),
            forcearray => [],
            keyattr    => []
        );
    }

    return $self->{response_hash};
}

=head2 response_dom 

Custom response_dom method, provides a more relaxed parsing to better handle HTML.

=cut

sub response_dom {
    my $self = shift;

    if ( ! defined $self->{response_dom} ) {
        require XML::LibXML;
        my $parser = XML::LibXML->new();
        $parser->recover(1);
        $parser->recover_silently(1);

        eval {
            $self->{response_dom} =
                $parser->parse_html_string( $self->response_content );
        };
        if ( $@ ) {
            $self->errors_append( { 'parsing_error' => $@ } );
        }
    }

    return $self->{response_dom};
}

=head1 BASECLASS METHODS

=head2 request_agent

Accessor for the LWP::UserAgent request agent

=head2 request_object

Accessor for the HTTP::Request request object

=head2 request_content

Accessor for the complete request body from the HTTP::Request object

=head2 response_content

Accessor for the HTTP response body content

=head2 response_object

Accessor for the HTTP::Request response object

=head2 nodeContent( $tag, [ $dom ] ) 

Helper for LibXML that retrieves node content

=head2 errors 

Accessor to the hashref of errors

=head2 has_error

Returns true if the call contains errors

=head2 errors_as_string

Returns a string of API errors if there are any.

=head1 PRIVATE METHODS

=head2 _get_request_body

This method supplies the XML body for the web service request

=cut

sub _get_request_body {
    my $self = shift;
    my @p;
    
    if ( $self->api_config->{request_method} ne 'GET' ) {
        for my $k ( keys %{ $self->{args} } ) {
            push( @p, ( $k . '=' . uri_escape( $self->{args}{$k} ) ) );
        }
    }
    
    return join( '&', @p ) or "";
}

=head2 _get_request_headers 

This methods supplies the headers for the HTML API call

=cut

sub _get_request_headers {
    my $self = shift;
    
    my $obj = HTTP::Headers->new();
    return $obj;
}

=head2 _get_request_object 

This method creates the request object and returns to the parent class

=cut

sub _get_request_object {
    my $self     = shift;
    
    my $req_url  = undef;
    
    # put the args in the url for a GET request only
    if ( $self->api_config->{request_method} eq 'GET'
        && defined $self->{args} ) {
        
        $req_url = $self->_build_url( $self->{url}, $self->{args} );
    }
    else {
        $req_url = $self->{url}; 
    }
    
    my $request_obj = HTTP::Request->new(
        ( $self->api_config->{request_method} || 'GET' ),
        $req_url,
        $self->_get_request_headers,
        $self->_get_request_body,
    );

    if( $self->api_config->{authorization_basic}{enabled} ) {
        $request_obj->authorization_basic(
            $self->api_config->{authorization_basic}{username},
            $self->api_config->{authorization_basic}{password}
        );
    }

    return $request_obj;
}

1;

=head1 AUTHOR

Tim Keefer <tim@timkeefer.com>

=head1 COPYRIGHT

Tim Keefer 2009

=cut

lib/eBay/API/Simple/JSON.pm  view on Meta::CPAN

package eBay::API::Simple::JSON;

use strict;
use warnings;

use base 'eBay::API::SimpleBase';
use JSON;
use HTTP::Request;
use HTTP::Headers;
use XML::Simple;
use URI::Escape;
use utf8;

our $DEBUG = 0;

=head1 NAME 

eBay::API::Simple::JSON - Support for grabbing an RSS feed via API call

=head1 USAGE

  my $api = eBay::API::Simple::JSON->new();

  my $data = {     
    "user_eais_token" => "tim", 
    "body_text" => "mytext",    
    "foo" => "bar",
    "greener_alt_topic" => "/green/api/v1/greenerAlternativeTopic/2/",     
    "items"=> [ {         
        "ebay_item_id"=> "250814913221",         
        "photo"=> "http=>//thumbs2.ebaystatic.com/m/m9X7sXOK303v4e_fgxm_-7w/140.jpg",        
        "price"=> 2.96,         
        "title"=> "TEST PUT - VAPUR 16 OZ FLEXIBLE FOLDABLE WATER BOTTLE BPA FREE"     
    } ],     
    "meta_title"=> "Foldable bottles can be stashed away when the water is gone",     
    "title"=> "TEST PUT - Foldable bottles can be stashed away when the water is gone" 
  };

  $api->execute(
    'http://localhost-django-vm.ebay.com/green/api/v1/greenerAlternative/',
    $data
  );

  print $api->request_content() ."\n";

  if ( $api->has_error() ) {
    print "FAILED: " . $api->response_content();
    #print "FAILED: " . $api->response_hash->{error_message} . "\n";
  }
  else {
    print "SUCCESS!\n";
    print $api->response_object->header('Location') . "\n";
  }
 
  my $hash = $call->response_hash();

  # execution methods for "GET", "POST", "PUT", and "DELETE" requests
  $api->get( $endpoint );
  $api->post( $endpoint, data );
  $api->put( $endpoint, $data );
  $api->delete( $endpoint );
  
=head1 PUBLIC METHODS

=head2 new( { %options } } 

my $call = ebay::API::Simple::JSON->new();

=cut

sub new {
    my $class = shift;
    my $self  = $class->SUPER::new(@_);

    $self->api_config->{request_method}  ||= 'POST';

    return $self;    
}

=head2 prepare( $url, $%args )

  $call->prepare( 
    'http://sfbay.craigslist.org/search/sss',
    { query  => 'shirt', format => 'rss', } 
  );
  
This method will construct the API request using the supplied URL. 

=head3 Options

=over 4

=item $url (required)

Feed URL to fetch

=item %$args (optional)

The supplied args will be encoded and appended to the URL

=back

=cut

sub prepare {
    my $self = shift;

    $self->{url} = shift;

    if ( ! defined $self->{url} ) {
        die "missing url";
    }

    # collect the optional args
    $self->{request_data} = shift;
}

=head2 process()

This method will process the API response.

=cut

sub process {
    my $self = shift;

    $self->SUPER::process();

    $self->response_hash(); # build the hash now to detect errors
}

=head2 get( $url )

execute a "GET" request to the specified endpoint

=cut

sub get {
    my $self = shift;
    $self->{custom_method} = 'GET';
    $self->execute(@_);
    $self->{custom_method} = undef;
}

=head2 post( $url )

execute a "POST" request to the specified endpoint

=cut

sub post {
    my $self = shift;
    $self->{custom_method} = 'POST';
    $self->execute(@_);    
    $self->{custom_method} = undef;
}

=head2 put( $url )

execute a "PUT" request to the specified endpoint

=cut

sub put {
    my $self = shift;
    $self->{custom_method} = 'PUT';
    $self->execute(@_);    
    $self->{custom_method} = undef;
}

=head2 delete( $url )

execute a "DELETE" request to the specified endpoint

=cut

sub delete {
    my $self = shift;
    $self->{custom_method} = 'DELETE';
    $self->execute(@_);    
    $self->{custom_method} = undef;
}

=head1 BASECLASS METHODS

=head2 request_agent

Accessor for the LWP::UserAgent request agent

=head2 request_object

Accessor for the HTTP::Request request object

=head2 request

Accessor for the HTTP::Request request object

=cut

sub request {
    my $self = shift;
    return $self->request_object();
}

=head2 request_content

Accessor for the complete request body from the HTTP::Request object

=head2 response_content

Accessor for the HTTP response body content

=head2 response_object

Accessor for the HTTP::Response response object

=head2 response

Accessor for the HTTP::Response response object

=cut

sub response {
    my $self = shift;
    return $self->response_object;
}

=head2 response_dom

Accessor for the LibXML response DOM

=cut

sub response_dom {
    die "can't use with the JSON backend";
}

=head2 response_hash

Accessor for the hashified response content

=cut

sub response_hash {
    my $self = shift;

    if ( ! defined $self->{response_hash} && $self->response_content ) {
        eval {
            $self->{response_hash} = decode_json( $self->response_content );
        };
        if ( $@ ) {
            $self->errors_append( { 'decode_error' => $@ } );
        }
    }

    return $self->{response_hash};
}

=head2 response_json

Accessor for the json response content

=cut

sub response_json {
    my $self = shift;
    return $self->response_content;
}

=head2 nodeContent( $tag, [ $dom ] ) 

Helper for LibXML that retrieves node content

=cut

sub nodeContent {
    die "not implemented for the JSON backend";
}

=head2 errors 

Accessor to the hashref of errors

=head2 has_error

Returns true if the call contains errors

=cut

sub has_error {
    my $self = shift;
    
    my $has_error =  (keys( %{ $self->errors } ) > 0) ? 1 : 0; 
    return 1 if $has_error;
    return $self->response_object->is_error;
}

=head2 errors_as_string

Returns a string of API errors if there are any.

=cut

sub errors_as_string {
    my $self = shift;
    
    return "" unless $self->has_error;

    my @e;
    for my $k ( keys %{ $self->errors } ) {
        push( @e, $k . '-' . $self->errors->{$k} );
    }

    if ( $self->response_object->is_error ) {
        push( @e, $self->response_content );
    }
    
    return join( "\n", @e );
 
}

=head1 PRIVATE METHODS

=head2 _get_request_body

This method supplies the JSON body for the web service request

=cut

sub _get_request_body {
    my $self = shift;
    return undef if ! defined $self->{request_data};
    my $body;
    eval {
        my $json = JSON->new->utf8;
        $body = $json->allow_blessed->convert_blessed->encode( $self->{request_data} );
    };
    if ( $@ ) {
        $self->errors_append( { 'decode_error' => $@ } );
    }
    return $body;
}

=head2 _get_request_headers 

This methods supplies the headers for the RSS API call

=cut

sub _get_request_headers {
    my $self = shift;
   
    my $obj = HTTP::Headers->new();
    $obj->push_header( 'Content-Type' => 'application/json' );
    return $obj;
}

=head2 _get_request_object 

This method creates the request object and returns to the parent class

=cut

sub _get_request_object {
    my $self     = shift;
    
    my $body = $self->_get_request_body;
    
    my $request_method = $self->{custom_method};
    if ( ! $request_method ) {
        $request_method = defined $body ? 'POST' : 'GET';
    }
    
    my $request_obj = HTTP::Request->new(
        $request_method,
        $self->{url},
        $self->_get_request_headers,
        $self->_get_request_body,
    );

    if( $self->api_config->{authorization_basic}{enabled} ) {
        $request_obj->authorization_basic(
            $self->api_config->{authorization_basic}{username},
            $self->api_config->{authorization_basic}{password}
        );
    }

    return $request_obj;
}

1;

=head1 AUTHOR

Tim Keefer <tim@timkeefer.com>

=head1 COPYRIGHT

Tim Keefer 2009

=cut

lib/eBay/API/Simple/Merchandising.pm  view on Meta::CPAN

package eBay::API::Simple::Merchandising;

use strict;
use warnings;

use base 'eBay::API::SimpleBase';

use HTTP::Request;
use HTTP::Headers;
use XML::Simple;
use utf8;

our $DEBUG = 0;

=head1 NAME

eBay::API::Simple::Merchandising - Support for eBay's Merchandising web service

=head1 DESCRIPTION

This class provides support for eBay's Merchandising web services.

See http://developer.ebay.com/products/merchandising/

=head1 USAGE

  my $call = eBay::API::Simple::Merchandising->new( 
    { appid => '<your app id here>' } 
  );
  
  $call->execute( 'getMostWatchedItems', { maxResults => 3, categoryId => 267 }  );

  if ( $call->has_error() ) {
     die "Call Failed:" . $call->errors_as_string();
  }

  # getters for the response DOM or Hash
  my $dom  = $call->response_dom();
  my $hash = $call->response_hash();

  print $call->nodeContent( 'timestamp' );
  print $call->nodeContent( 'totalEntries' );

  my @nodes = $dom->findnodes(
    '//item'
  );

  foreach my $n ( @nodes ) {
    print $n->findvalue('title/text()') . "\n";
  }

=head1 SANDBOX USAGE

  my $call = eBay::API::Simple::Merchandising->new( { 
     appid => '<your app id here>' 
     domain => '',
  } );
  
  $call->execute( 'getMostWatchedItems', { maxResults => 3, categoryId => 267 }  );

  if ( $call->has_error() ) {
     die "Call Failed:" . $call->errors_as_string();
  }

  # getters for the response DOM or Hash
  my $dom  = $call->response_dom();
  my $hash = $call->response_hash();
  
=head1 PUBLIC METHODS

=head2 new( { %options } } 

Constructor for the Finding API call

  my $call = eBay::API::Simple::Merchandising->new( { 
    appid => '<your app id here>' 
    ... 
  } );

=head3 Options

=over 4

=item appid (required)

This appid is required by the web service. App ids can be obtained at 
http://developer.ebay.com

=item siteid

eBay site id to be supplied to the web service endpoint

defaults to EBAY-US

=item domain

domain for the web service endpoint

defaults to svcs.ebay.com

=item uri

endpoint URI

defaults to /MerchandisingService

=item version

Version to be supplied to the web service endpoint

defaults to 1.0.0

=item https

Specifies is the API calls should be made over https.

defaults to 0

=item enable_attributes

This flag adds support for attributes in the request. If enabled request
data notes much be defined like so,

myElement => { content => 'element content', myattr => 'attr value' }

defaults to 0

=back

=head3 ALTERNATE CONFIG VIA ebay.yaml

An ebay.yaml file can be used for configuring each 
service endpoint.

YAML files can be placed at the below locations. The first 
file found will be loaded.

    ./ebay.yaml, ~/ebay.yaml, /etc/ebay.yaml 

Sample YAML:

    # Trading - External
    api.ebay.com:
      appid: <your appid>
      certid: <your certid>
      devid: <your devid>
      token: <token>

    # Shopping
    open.api.ebay.com:
      appid: <your appid>
      certid: <your certid>
      devid: <your devid>
      version: 671

    # Finding/Merchandising
    svcs.ebay.com:
      appid: <your appid>
      version: 1.0.0


=cut

sub new {
    my $class = shift;
    my $self  = $class->SUPER::new(@_);

    $self->api_config->{domain}  ||= 'svcs.ebay.com';
    $self->api_config->{uri}     ||= '/MerchandisingService';
    #$self->api_config->{version} ||= '1.1.0';
    $self->api_config->{https}   ||= 0;
    $self->api_config->{siteid}  ||= 'EBAY-US';
    $self->api_config->{response_encoding} ||= 'XML'; # JSON, NV, SOAP
    $self->api_config->{request_encoding}  ||= 'XML';

    $self->_load_yaml_defaults();
    
    if ( $DEBUG ) {
        print STDERR sprintf( "API CONFIG:\n%s\n",
            $self->api_config_dump()
        );        
    }
    
    $self->_load_credentials();
    
    return $self;    
}

=head2 prepare( $verb, $call_data )

  $self->prepare( 'getMostWatchedItems', { maxResults => 3, categoryId => 267 } );
 
This method will construct the API request based on the $verb and
the $call_data.

=item $verb (required)

call verb, i.e. getMostWatchedItems

=item $call_data (required)

hashref of call_data that will be turned into xml.

=cut

sub prepare {
    my $self = shift;
    
    $self->{verb}      = shift;
    $self->{call_data} = shift;

    if ( ! defined $self->{verb} || ! defined $self->{call_data} ) {
        die "missing verb and call_data";
    }
    
    # make sure we have appid
    $self->_load_credentials();
}

=head1 BASECLASS METHODS

=head2 request_agent

Accessor for the LWP::UserAgent request agent

=head2 request_object

Accessor for the HTTP::Request request object

=head2 request_content

Accessor for the complete request body from the HTTP::Request object

=head2 response_content

Accessor for the HTTP response body content

=head2 response_object

Accessor for the HTTP::Request response object

=head2 response_dom

Accessor for the LibXML response DOM

=head2 response_hash

Accessor for the hashified response content

=head2 nodeContent( $tag, [ $dom ] ) 

Helper for LibXML that retrieves node content

=head2 errors 

Accessor to the hashref of errors

=head2 has_error

Returns true if the call contains errors

=head2 errors_as_string

Returns a string of API errors if there are any.

=head1 PRIVATE METHODS

=head2 _get_request_body

This method supplies the XML body for the web service request

=cut

sub _get_request_body {
    my $self = shift;

    my $xml = "<?xml version='1.0' encoding='utf-8'?>"
        . "<" . $self->{verb} . "Request xmlns=\"http://www.ebay.com/marketplace/services\">"
        . XMLout( 
            $self->{call_data}, 
            NoAttr => !$self->api_config->{enable_attributes},
            KeepRoot => 1, 
            RootName => undef 
        )
        . "</" . $self->{verb} . "Request>";

    return $xml; 
}

=head2 _get_request_headers 

This method supplies the HTTP::Headers obj for the web service request

=cut

sub _get_request_headers {
    my $self = shift;
   
    my $obj = HTTP::Headers->new();

    if ( defined $self->api_config->{version} ) {
        $obj->push_header("X-EBAY-SOA-SERVICE-VERSION" 
            => $self->api_config->{version});
    }
    $obj->push_header("X-EBAY-SOA-SERVICE-NAME" => "MerchandisingService" );
    $obj->push_header("EBAY-SOA-CONSUMER-ID"  => $self->api_config->{appid});
    $obj->push_header("X-EBAY-SOA-GLOBAL-ID"  => $self->api_config->{siteid});
    $obj->push_header("X-EBAY-SOA-OPERATION-NAME" => $self->{verb});
    $obj->push_header("X-EBAY-SOA-REQUEST-DATA-FORMAT"  
        => $self->api_config->{request_encoding});
    $obj->push_header("X-EBAY-SOA-RESPONSE-DATA-FORMAT" 
        => $self->api_config->{response_encoding});
    $obj->push_header("Content-Type" => "text/xml");
    
    return $obj;
}

=head2 _get_request_object 

This method creates and returns the HTTP::Request object for the
web service call.

=cut

sub _get_request_object {
    my $self = shift;

    my $url = sprintf( 'http%s://%s%s',
        ( $self->api_config->{https} ? 's' : '' ),
        $self->api_config->{domain},
        $self->api_config->{uri}
    );
  
    my $request_obj = HTTP::Request->new(
        "POST",
        $url,
        $self->_get_request_headers,
        $self->_get_request_body
    );

    return $request_obj;
}

sub _load_credentials {
    my $self = shift;
    
    # we only need to load credentials once
    return if $self->{_credentials_loaded};
    
    my @missing;
    
    # required by the API
    for my $p ( qw/appid/ ) {
        next if defined $self->api_config->{$p};
        
        if ( my $val = $self->_fish_ebay_ini( $p ) ) {
            $self->api_config->{$p} = $val;
        }
        else {
            push( @missing, $p );
        }
    }

    # die if we didn't get everything
    if ( scalar @missing > 0 ) {
        die "missing API credential: " . join( ", ", @missing );
    }
    
    $self->{_credentials_loaded} = 1;
    return;
}

sub _fish_ebay_ini {
    my $self = shift;
    my $arg  = shift;

    # initialize our hashref
    $self->{_ebay_ini} ||= {};
    
    # revert eBay::API::Simple keys to standard keys
    $arg = 'ApplicationKey' if $arg eq 'appid';

    # return it if we've already found it
    return $self->{_ebay_ini}{$arg} if defined $self->{_ebay_ini}{$arg};
    
    # ini files in order of importance
    my @files = (
        './ebay.ini',           
        "$ENV{HOME}/ebay.ini",
        '/etc/ebay.ini',
    );
    
    foreach my $file ( reverse @files ) {        
        if ( open( FILE, "<", $file ) ) {
        
            while ( my $line = <FILE> ) {
                chomp( $line );
            
                next if $line =~ m!^\s*\#!;
            
                my( $k, $v ) = split( /=/, $line );
            
                if ( defined $k && defined $v) {
                    $v =~ s/^\s+//;
                    $v =~ s/\s+$//;
                    
                    $self->{_ebay_ini}{$k} = $v;
                }
            }

            close FILE;
        }
    }
    
    return $self->{_ebay_ini}{$arg} if defined $self->{_ebay_ini}{$arg};
    return undef;
}

=head1 AUTHOR

Tim Keefer <tim@timkeefer.com>

=cut

1;

lib/eBay/API/Simple/Parallel.pm  view on Meta::CPAN

package eBay::API::Simple::Parallel;

use strict;
use warnings;

use base 'LWP::Parallel::UserAgent';
use utf8;

our $DEBUG = 0;

=head1 NAME

eBay::API::Simple::Parallel - Support for parallel requests

=head1 USAGE

  my $pua = eBay::API::Simple::Parallel->new();
 
  my $call1 = eBay::API::Simple::RSS->new( {
    parallel => $pua,
  } );

  $call1->execute(
    'http://worldofgood.ebay.com/Clothes-Shoes-Men/43/list?format=rss',
  );

  my $call2 = eBay::API::Simple::RSS->new( {
    parallel => $pua,
  } );

  $call2->execute(
    'http://worldofgood.ebay.com/Home-Garden/46/list?format=rss'
  );

  $pua->wait();

  if ( $pua->has_error() ) {
    print "ONE OR MORE FAILURES!\n";
  }

  print $call1->request_content() . "\n";
  print $call2->response_content() "\n";
  

=head1 PUBLIC METHODS

=head2 new()

  my $pua = ebay::API::Simple::Parallel->new();

=cut

sub new {
    my $class = shift;
    my $self  = $class->SUPER::new( @_ );

    $self->{has_error} = 0;

    return $self;
}

=head2 wait( $timeout )

  $pua->wait();

This method will wait for all requests to complete with an optional timeout.

An array of object instances will be returned.

=cut

sub wait {
    my $self = shift;
    my $timeout = shift;

    my @objects;
    my $entries = $self->SUPER::wait( $timeout );

    for my $key ( keys %$entries ) {
        push( @objects, $entries->{$key}->request->{_ebay_api_simple_instance} );
        delete( $entries->{$key}->request->{_ebay_api_simple_instance}->{parallel} );
        delete( $entries->{$key}->request->{_ebay_api_simple_instance} );
    }

    return \@objects;
}

=head2 has_error

Returns true if any of the calls contain an error.

=cut

sub has_error {
    my $self = shift;

    return $self->{has_error};
}

sub on_connect {
    my $self = shift;
    my $request = shift;
    my $response = shift;
    my $entry = shift;

    if( $DEBUG ) {
        print STDERR "Parallel Connect: " . $request->url . "\n";
    }
}

sub on_return {
    my $self = shift;
    my $request = shift;
    my $response = shift;
    my $entry = shift;

    if( $DEBUG ) {
        print STDERR "Parallel Return: " . $request->url . "\n";
    }

    $request->{_ebay_api_simple_instance}->_process_http_request( $response );
    $request->{_ebay_api_simple_instance}->process();

    if( $request->{_ebay_api_simple_instance}->has_error ) {
        $self->{has_error} = 1;
    }
}

sub on_failure {
    my $self = shift;
    my $request = shift;
    my $response = shift;
    my $entry = shift;

    if( $DEBUG ) {
        print STDERR "Parallel Failure: " . $request->url . "\n";
    }

    $request->{_ebay_api_simple_instance}->_process_http_request( $response );
    $request->{_ebay_api_simple_instance}->process();

    $self->{has_error} = 1;
}

1;

=head1 AUTHOR

Brian Gontowski <bgontowski@gmail.com>

=cut

lib/eBay/API/Simple/RSS.pm  view on Meta::CPAN

package eBay::API::Simple::RSS;

use strict;
use warnings;

use base 'eBay::API::SimpleBase';

use HTTP::Request;
use HTTP::Headers;
use XML::Simple;
use URI::Escape;
use utf8;

our $DEBUG = 0;

=head1 NAME 

eBay::API::Simple::RSS - Support for grabbing an RSS feed via API call

=head1 USAGE

  my $call = eBay::API::Simple::RSS->new();
  $call->execute(
    'http://sfbay.craigslist.org/search/sss',
    {
        query  => 'shirt',
        format => 'rss',
    }
  );

  if ( $call->has_error() ) {
     die "Call Failed:" . $call->errors_as_string();
  }

  # getters for the response DOM or Hash
  my $dom  = $call->response_dom();
  my $hash = $call->response_hash();

  # collect all item nodes
  my @items = $dom->getElementsByTagName('item');

  foreach my $n ( @items ) {
    print $n->findvalue('title/text()') . "\n";
  }
  
=head1 PUBLIC METHODS

=head2 new( { %options } } 

my $call = ebay::API::Simple::RSS->new();

=cut

sub new {
    my $class = shift;
    my $self  = $class->SUPER::new(@_);

    $self->api_config->{request_method}  ||= 'GET';

    return $self;    
}

=head2 prepare( $url, $%args )

  $call->prepare( 
    'http://sfbay.craigslist.org/search/sss',
    { query  => 'shirt', format => 'rss', } 
  );
  
This method will construct the API request using the supplied URL. 

=head3 Options

=over 4

=item $url (required)

Feed URL to fetch

=item %$args (optional)

The supplied args will be encoded and appended to the URL

=back

=cut
sub prepare {
    my $self = shift;
    
    $self->{url} = shift;

    if ( ! defined $self->{url} ) {
        die "missing url";
    }
    
    # collect the optional args
    $self->{args} = shift;
}

=head1 BASECLASS METHODS

=head2 request_agent

Accessor for the LWP::UserAgent request agent

=head2 request_object

Accessor for the HTTP::Request request object

=head2 request_content

Accessor for the complete request body from the HTTP::Request object

=head2 response_content

Accessor for the HTTP response body content

=head2 response_object

Accessor for the HTTP::Request response object

=head2 response_dom

Accessor for the LibXML response DOM

=head2 response_hash

Accessor for the hashified response content

=head2 nodeContent( $tag, [ $dom ] ) 

Helper for LibXML that retrieves node content

=head2 errors 

Accessor to the hashref of errors

=head2 has_error

Returns true if the call contains errors

=head2 errors_as_string

Returns a string of API errors if there are any.

=head1 PRIVATE METHODS

=head2 _get_request_body

This method supplies the XML body for the web service request

=cut

sub _get_request_body {
    my $self = shift;
    my @p;
    
    if ( $self->api_config->{request_method} ne 'GET' ) {
        for my $k ( keys %{ $self->{args} } ) {
            if ( ref( $self->{args}{$k} ) eq 'ARRAY' ) {
                for my $ap ( @{ $self->{args}{$k} } ) {
                    push( @p, 
                        ( $k . '=' . uri_escape_utf8( $ap ) ) 
                    );                    
                }
            }
            else {
                push( @p, ( $k . '=' . uri_escape_utf8( $self->{args}{$k} ) ) );
            }
        }
    }
    
    return join( '&', @p ) or "";
}

=head2 _get_request_headers 

This methods supplies the headers for the RSS API call

=cut

sub _get_request_headers {
    my $self = shift;
   
    my $obj = HTTP::Headers->new();
    return $obj;
}

=head2 _get_request_object 

This method creates the request object and returns to the parent class

=cut

sub _get_request_object {
    my $self     = shift;
    
    my $req_url  = undef;
    
    # put the args in the url for a GET request only
    if ( $self->api_config->{request_method} eq 'GET'
        && defined $self->{args} ) {
        
        $req_url = $self->_build_url( $self->{url}, $self->{args} );
    }
    else {
        $req_url = $self->{url}; 
    }
    
    my $request_obj = HTTP::Request->new(
        ( $self->api_config->{request_method} || 'GET' ),
        $req_url,
        $self->_get_request_headers,
        $self->_get_request_body,
    );

    if( $self->api_config->{authorization_basic}{enabled} ) {
        $request_obj->authorization_basic(
            $self->api_config->{authorization_basic}{username},
            $self->api_config->{authorization_basic}{password}
        );
    }

    return $request_obj;
}


1;

=head1 AUTHOR

Tim Keefer <tim@timkeefer.com>

=head1 COPYRIGHT

Tim Keefer 2009

=cut

lib/eBay/API/Simple/Shopping.pm  view on Meta::CPAN

package eBay::API::Simple::Shopping;

use strict;
use warnings;

use base 'eBay::API::SimpleBase';

use HTTP::Request;
use HTTP::Headers;
use XML::Simple;
use utf8;

our $DEBUG = 0;

=head1 NAME

eBay::API::Simple::Shopping - Support for eBay's Shopping web service

=head1 DESCRIPTION

This class provides support for eBay's Shopping web services.

See http://developer.ebay.com/products/shopping/

=head1 USAGE

  my $call = eBay::API::Simple::Shopping->new( 
    { appid => '<your app id here>' } 
  );
  $call->execute( 'FindItemsAdvanced', { QueryKeywords => 'shoe' } );

  if ( $call->has_error() ) {
     die "Call Failed:" . $call->errors_as_string();
  }

  # getters for the response DOM or Hash
  my $dom  = $call->response_dom();
  my $hash = $call->response_hash();

  print $call->nodeContent( 'Timestamp' );
  print $call->nodeContent( 'TotalItems' );

  my @nodes = $dom->findnodes(
    '/FindItemsAdvancedResponse/SearchResult/ItemArray/Item'
  );

  foreach my $n ( @nodes ) {
    print $n->findvalue('Title/text()') . "\n";
  }

=head1 SANDBOX USAGE

  my $call = eBay::API::Simple::Shopping->new( { 
     appid => '<your app id here>',
     domain => 'open.api.sandbox.ebay.com',   
  } );
  
  $call->execute( 'FindItemsAdvanced', { QueryKeywords => 'shoe' } );

  if ( $call->has_error() ) {
     die "Call Failed:" . $call->errors_as_string();
  }

  # getters for the response DOM or Hash
  my $dom  = $call->response_dom();
  my $hash = $call->response_hash();
  
=head1 PUBLIC METHODS

=head2 new( { %options } } 

Constructor for the Finding API call

    my $call = eBay::API::Simple::Shopping->new( { 
      appid => '<your app id here>' 
      ... 
    } );

=head3 Options

=over 4

=item appid (required)

This appid is required by the web service. App ids can be obtained at 
http://developer.ebay.com

=item siteid

eBay site id to be supplied to the web service endpoint

defaults to 0

=item domain

domain for the web service endpoint

defaults to open.api.ebay.com

=item uri

endpoint URI

defaults to /shopping

=item version

Version to be supplied to the web service endpoint

defaults to 527

=item https

Specifies is the API calls should be made over https.

defaults to 0

=item enable_attributes

This flag adds support for attributes in the request. If enabled request
data notes much be defined like so,

myElement => { content => 'element content', myattr => 'attr value' }

defaults to 0

=back

=head3 ALTERNATE CONFIG VIA ebay.yaml

An ebay.yaml file can be used for configuring each 
service endpoint.

YAML files can be placed at the below locations. The first 
file found will be loaded.

    ./ebay.yaml, ~/ebay.yaml, /etc/ebay.yaml 

Sample YAML:

    # Trading - External
    api.ebay.com:
      appid: <your appid>
      certid: <your certid>
      devid: <your devid>
      token: <token>

    # Shopping
    open.api.ebay.com:
      appid: <your appid>
      certid: <your certid>
      devid: <your devid>
      version: 671

    # Finding/Merchandising
    svcs.ebay.com:
      appid: <your appid>
      version: 1.0.0

=cut

sub new {
    my $class = shift;
    my $self  = $class->SUPER::new(@_);

    $self->api_config->{domain}  ||= 'open.api.ebay.com';
    $self->api_config->{uri}     ||= '/shopping';

    $self->api_config->{version} ||= '527';
    $self->api_config->{https}   ||= 0;
    $self->api_config->{siteid}  ||= 0;
    $self->api_config->{response_encoding} ||= 'XML'; # JSON, NV, SOAP
    $self->api_config->{request_encoding}  ||= 'XML';
    
    $self->_load_yaml_defaults();
    
    if ( $DEBUG ) {
        print STDERR sprintf( "API CONFIG:\n%s\n",
            $self->api_config_dump()
        );        
    }

        
    $self->_load_credentials();
    
    return $self;    
}

=head2 prepare( $verb, $call_data )

  $self->prepare( 'FindItemsAdvanced', { QueryKeywords => 'shoe' } );
 
This method will construct the API request based on the $verb and
the $call_data.

=item $verb (required)

call verb, i.e. FindItemsAdvanced

=item $call_data (required)

hashref of call_data that will be turned into xml.

=cut

sub prepare {
    my $self = shift;
    
    $self->{verb}      = shift;
    $self->{call_data} = shift;

    if ( ! defined $self->{verb} || ! defined $self->{call_data} ) {
        die "missing verb and call_data";
    }
    
    # make sure we have appid
    $self->_load_credentials();
}

=head1 BASECLASS METHODS

=head2 request_agent

Accessor for the LWP::UserAgent request agent

=head2 request_object

Accessor for the HTTP::Request request object

=head2 request_content

Accessor for the complete request body from the HTTP::Request object

=head2 response_content

Accessor for the HTTP response body content

=head2 response_object

Accessor for the HTTP::Request response object

=head2 response_dom

Accessor for the LibXML response DOM

=head2 response_hash

Accessor for the hashified response content

=head2 nodeContent( $tag, [ $dom ] ) 

Helper for LibXML that retrieves node content

=head2 errors 

Accessor to the hashref of errors

=head2 has_error

Returns true if the call contains errors

=head2 errors_as_string

Returns a string of API errors if there are any.

=head1 PRIVATE METHODS

=head2 _get_request_body

This method supplies the XML body for the web service request

=cut

sub _get_request_body {
    my $self = shift;

    my $xml = "<?xml version='1.0' encoding='utf-8'?>"
        . "<" . $self->{verb} . "Request xmlns=\"urn:ebay:apis:eBLBaseComponents\">"
        . XMLout( 
            $self->{call_data}, 
            NoAttr => !$self->api_config->{enable_attributes}, 
            KeepRoot => 1, 
            RootName => undef 
        )
        . "</" . $self->{verb} . "Request>";

    return $xml; 
}

=head2 _get_request_headers 

This method supplies the HTTP::Headers obj for the web service request

=cut

sub _get_request_headers {
    my $self = shift;
   
    my $obj = HTTP::Headers->new();

    $obj->push_header("X-EBAY-API-VERSION" => $self->api_config->{version});
    $obj->push_header("X-EBAY-API-APP-ID"  => $self->api_config->{appid});
    $obj->push_header("X-EBAY-API-SITEID"  => $self->api_config->{siteid});
    $obj->push_header("X-EBAY-API-CALL-NAME" => $self->{verb});
    $obj->push_header("X-EBAY-API-REQUEST-ENCODING"  => $self->api_config->{request_encoding});
    $obj->push_header("X-EBAY-API-RESPONSE-ENCODING" => $self->api_config->{response_encoding});
    $obj->push_header("Content-Type" => "text/xml");
    
    return $obj;
}

=head2 _get_request_object 

This method creates and returns the HTTP::Request object for the
web service call.

=cut

sub _get_request_object {
    my $self = shift;

    my $url = sprintf( 'http%s://%s%s',
        ( $self->api_config->{https} ? 's' : '' ),
        $self->api_config->{domain},
        $self->api_config->{uri}
    );
  
    my $request_obj = HTTP::Request->new(
        "POST",
        $url,
        $self->_get_request_headers,
        $self->_get_request_body
    );

    return $request_obj;
}

sub _load_credentials {
    my $self = shift;
    
    # we only need to load credentials once
    return if $self->{_credentials_loaded};
    
    my @missing;
    
    # required by the API
    for my $p ( qw/appid/ ) {
        next if defined $self->api_config->{$p};
        
        if ( my $val = $self->_fish_ebay_ini( $p ) ) {
            $self->api_config->{$p} = $val;
        }
        else {
            push( @missing, $p );
        }
    }

    # die if we didn't get everything
    if ( scalar @missing > 0 ) {
        die "missing API credential: " . join( ", ", @missing );
    }
    
    $self->{_credentials_loaded} = 1;
    return;
}

sub _fish_ebay_ini {
    my $self = shift;
    my $arg  = shift;

    # initialize our hashref
    $self->{_ebay_ini} ||= {};
    
    # revert eBay::API::Simple keys to standard keys
    $arg = 'ApplicationKey' if $arg eq 'appid';

    # return it if we've already found it
    return $self->{_ebay_ini}{$arg} if defined $self->{_ebay_ini}{$arg};
    
    # ini files in order of importance
    my @files = (
        './ebay.ini',           
        "$ENV{HOME}/ebay.ini",
        '/etc/ebay.ini',
    );
    
    foreach my $file ( reverse @files ) {        
        if ( open( FILE, "<", $file ) ) {
        
            while ( my $line = <FILE> ) {
                chomp( $line );
            
                next if $line =~ m!^\s*\#!;
            
                my( $k, $v ) = split( /=/, $line );
            
                if ( defined $k && defined $v) {
                    $v =~ s/^\s+//;
                    $v =~ s/\s+$//;
                    
                    $self->{_ebay_ini}{$k} = $v;
                }
            }

            close FILE;
        }
    }
    
    return $self->{_ebay_ini}{$arg} if defined $self->{_ebay_ini}{$arg};
    return undef;
}

1;

=head1 AUTHOR

Tim Keefer <tim@timkeefer.com>

=cut

lib/eBay/API/Simple/Trading.pm  view on Meta::CPAN

package eBay::API::Simple::Trading;

use strict;
use warnings;

use base 'eBay::API::SimpleBase';

use HTTP::Request;
use HTTP::Headers;
use XML::Simple;
use utf8;

our $DEBUG = 0;

=head1 NAME

eBay::API::Simple::Trading - Support for eBay's Trading web service

=head1 DESCRIPTION

This class provides support for eBay's Trading web services.

See http://developer.ebay.com/products/trading/

=head1 USAGE

  my $call = eBay::API::Simple::Trading->new( { 
    appid   => '<your appid>',
    devid   => '<your devid>',
    certid  => '<your certid>',
    token   => '<auth token>',
  } );
  
  $call->execute( 'GetSearchResults', { Query => 'shoe' } );

  if ( $call->has_error() ) {
     die "Call Failed:" . $call->errors_as_string();
  }

  # getters for the response DOM or Hash
  my $dom  = $call->response_dom();
  my $hash = $call->response_hash();

  print $call->nodeContent( 'Timestamp' );

  my @nodes = $dom->findnodes(
    '//Item'
  );

  foreach my $n ( @nodes ) {
    print $n->findvalue('Title/text()') . "\n";
  }

=head1 SANDBOX USAGE

  my $call = eBay::API::Simple::Trading->new( { 
    appid   => '<your appid>',
    devid   => '<your devid>',
    certid  => '<your certid>',
    token   => '<auth token>',
    domain  => 'api.sandbox.ebay.com',
  } );
  
  $call->execute( 'GetSearchResults', { Query => 'shoe' } );

  if ( $call->has_error() ) {
     die "Call Failed:" . $call->errors_as_string();
  }

  # getters for the response DOM or Hash
  my $dom  = $call->response_dom();
  my $hash = $call->response_hash();

=head1 PUBLIC METHODS

=head2 new( { %options } } 

Constructor for the Trading API call

    my $call = eBay::API::Simple::Trading->new( { 
      appid   => '<your appid>',
      devid   => '<your devid>',
      certid  => '<your certid>',
      token   => '<auth token>',
      ... 
    } );

=head3 Options

=over 4

=item appid (required)

This is required by the web service and can be obtained at 
http://developer.ebay.com

=item devid (required)

This is required by the web service and can be obtained at 
http://developer.ebay.com

=item certid (required)

This is required by the web service and can be obtained at 
http://developer.ebay.com

=item token (required)

This is required by the web service and can be obtained at 
http://developer.ebay.com

=item siteid

eBay site id to be supplied to the web service endpoint

defaults to 0

=item domain

domain for the web service endpoint

defaults to open.api.ebay.com

=item uri

endpoint URI

defaults to /ws/api.dll

=item version

Version to be supplied to the web service endpoint

defaults to 543

=item https

Specifies is the API calls should be made over https.

defaults to 1

=item enable_attributes

This flag adds support for attributes in the request. If enabled request
data notes much be defined like so,

myElement => { content => 'element content', myattr => 'attr value' }

defaults to 0

=back

=head3 ALTERNATE CONFIG VIA ebay.yaml

An ebay.yaml file can be used for configuring each 
service endpoint.

YAML files can be placed at the below locations. The first 
file found will be loaded.

    ./ebay.yaml, ~/ebay.yaml, /etc/ebay.yaml 

Sample YAML:

    # Trading - External
    api.ebay.com:
      appid: <your appid>
      certid: <your certid>
      devid: <your devid>
      token: <token>

    # Shopping
    open.api.ebay.com:
      appid: <your appid>
      certid: <your certid>
      devid: <your devid>
      version: 671

    # Finding/Merchandising
    svcs.ebay.com:
      appid: <your appid>
      version: 1.0.0

=cut

sub new {
    my $class = shift;
    my $self  = $class->SUPER::new(@_);
    
    $self->api_config->{domain}  ||= 'api.ebay.com';    
    $self->api_config->{uri}     ||= '/ws/api.dll';
    $self->api_config->{version} ||= '543';
        
    unless ( defined $self->api_config->{https} ) {
        $self->api_config->{https} = 1;
    }

    unless ( defined $self->api_config->{siteid} ) {
        $self->api_config->{siteid} = 0;
    }

    $self->_load_yaml_defaults();
    
    if ( $DEBUG ) {
        print STDERR sprintf( "API CONFIG:\n%s\n",
            $self->api_config_dump()
        );        
    }

        
    return $self;
}

=head2 prepare( $verb, $call_data )

  $call->prepare( 'GetSearchResults', { Query => 'shoe' } );
 
This method will construct the API request based on the $verb and
the $call_data.

=item $verb (required)

call verb, i.e. GetSearchResults

=item $call_data (required)

hashref of call_data that will be turned into xml.

=cut

sub prepare {
    my $self = shift;

    $self->{verb}      = shift;
    $self->{call_data} = shift;

    if ( ! defined $self->{verb} || ! defined $self->{call_data} ) {
        die "missing verb and call_data";
    }

    # make sure we have appid, devid, certid, token
    $self->_load_credentials();
}

=head1 BASECLASS METHODS

=head2 request_agent

Accessor for the LWP::UserAgent request agent

=head2 request_object

Accessor for the HTTP::Request request object

=head2 request_content

Accessor for the complete request body from the HTTP::Request object

=head2 response_content

Accessor for the HTTP response body content

=head2 response_object

Accessor for the HTTP::Request response object

=head2 response_dom

Accessor for the LibXML response DOM

=head2 response_hash

Accessor for the hashified response content

=head2 nodeContent( $tag, [ $dom ] ) 

Helper for LibXML that retrieves node content

=head2 errors 

Accessor to the hashref of errors

=head2 has_error

Returns true if the call contains errors

=head2 errors_as_string

Returns a string of API errors if there are any.

=head1 PRIVATE METHODS

=head2 _validate_response

This is called from the base class. The method is suppose to provide the
custom validation code and push to the error stack if the response isn't
valid

=cut

sub _validate_response {
    my $self = shift;

    if ( $self->nodeContent('Ack') eq 'Failure' ) {
        $self->errors_append( {
            'Call Failure' => $self->nodeContent('LongMessage')
        } );
    }
}

=head2 _get_request_body

This method supplies the request body for the Shopping API call

=cut

sub _get_request_body {
    my $self = shift;

    # if auth_method is set to 'token' use token
    # if auth_method is set to 'iaftoken' use iaftoken
    # if auth_method is set to 'user' use username/password
    if ( $self->{auth_method} eq 'token' ) {
         my $xml = "<?xml version='1.0' encoding='utf-8'?>"
             . "<" . $self->{verb} . "Request xmlns=\"urn:ebay:apis:eBLBaseComponents\">"
             . "<RequesterCredentials><eBayAuthToken>"
             . ( $self->api_config->{token} || '' )
             . "</eBayAuthToken></RequesterCredentials>"
             . XMLout( 
                 $self->{call_data}, 
                 NoAttr => !$self->api_config->{enable_attributes},
                 KeepRoot => 1, 
                 RootName => undef 
             )
             . "</" . $self->{verb} . "Request>";

          return $xml;
    }
    elsif ( $self->{auth_method} eq 'iaftoken' ) {
         my $xml = "<?xml version='1.0' encoding='utf-8'?>"
             . "<" . $self->{verb} . "Request xmlns=\"urn:ebay:apis:eBLBaseComponents\">"
             . XMLout( 
                 $self->{call_data}, 
                 NoAttr => !$self->api_config->{enable_attributes},
                 KeepRoot => 1, 
                 RootName => undef 
             )
             . "</" . $self->{verb} . "Request>";

          return $xml;
    }
    elsif ( $self->{auth_method} eq 'user' ) {
        my $xml = "<?xml version='1.0' encoding='utf-8'?>"
             . "<" . $self->{verb} . "Request xmlns=\"urn:ebay:apis:eBLBaseComponents\">"
             . "<RequesterCredentials><Username>"
             . $self->api_config->{username} . "</Username>";
        
        if ( $self->api_config->{password} ) {
            $xml .= "<Password>"
             . $self->api_config->{password} . "</Password>";
        }
        
        $xml .= "</RequesterCredentials>"
             . XMLout( $self->{call_data}, NoAttr => 1, KeepRoot => 1, RootName => undef )
             . "</" . $self->{verb} . "Request>";

        return $xml;
    }
}

=head2 _get_request_headers

This method supplies the headers for the Shopping API call

=cut

sub _get_request_headers {
    my $self = shift;

    my $obj = HTTP::Headers->new();

    $obj->push_header("X-EBAY-API-COMPATIBILITY-LEVEL" =>
        $self->api_config->{version});
    $obj->push_header("X-EBAY-API-DEV-NAME"  => $self->api_config->{devid});
    $obj->push_header("X-EBAY-API-APP-NAME"  => $self->api_config->{appid});
    $obj->push_header("X-EBAY-API-CERT-NAME" => $self->api_config->{certid});
    $obj->push_header("X-EBAY-API-SITEID"    => $self->api_config->{siteid});
    $obj->push_header("X-EBAY-API-CALL-NAME" => $self->{verb});
    if ( $self->{auth_method} eq 'iaftoken' ) {
        $obj->push_header("X-EBAY-API-IAF-TOKEN" => $self->api_config->{iaftoken});
    }
    $obj->push_header("Content-Type" => "text/xml");

    return $obj;
}

=head2 _get_request_object

This method creates the request object and returns to the parent class

=cut

sub _get_request_object {
    my $self = shift;

    my $url = sprintf( 'http%s://%s%s',
        ( $self->api_config->{https} ? 's' : '' ),
        $self->api_config->{domain},
        $self->api_config->{uri}
    );
    my $request_obj = HTTP::Request->new(
        "POST",
        $url,
        $self->_get_request_headers,
        $self->_get_request_body
    );

    return $request_obj;
}

sub _load_credentials {
    my $self = shift;

    # we only need to load credentials once
    return if $self->{_credentials_loaded};

    my @missing;

    # required by the API
    for my $reqd ( qw/devid appid certid/ ) {
        next if defined $self->api_config->{$reqd};

        if ( defined (my $val = $self->_fish_ebay_ini( $reqd )) ) {
            $self->api_config->{$reqd} = $val;
        }
        else {
            push( @missing, $reqd );
        }
    }

    if ( scalar @missing > 0 ) {
        die "missing Authentication: " . join( ", ", @missing );
    }

    # Collect token, iaftoken, username, password, domain, https, uri and version from
    # the ebay.ini file
    # if token found, set auth_method to 'token'
    # if iaftoken found, set auth_method to 'iaftoken'
    # if username/password found set auth_method to 'user'

    for my $optional ( qw/token iaftoken username password domain https uri version/ ) {

       next if defined $self->api_config->{$optional};

       if ( defined ( my $val = $self->_fish_ebay_ini( $optional )) ) {
           $self->api_config->{$optional} = $val;
       }
       else {
           print STDERR "Not defined : " . $optional . "\n" if $DEBUG;
       }
    }

    if ( exists ( $self->api_config->{token} ) ) {
        $self->{auth_method} = 'token';

        delete($self->api_config->{iaftoken});
        delete($self->api_config->{username});
        delete($self->api_config->{password});
    }
    elsif ( exists ( $self->api_config->{iaftoken} ) ) {
        $self->{auth_method} = 'iaftoken';

        delete($self->api_config->{username});
        delete($self->api_config->{password});
    }
    elsif ((exists( $self->api_config->{username} ))
        && (exists( $self->api_config->{password} ))) {
        $self->{auth_method} = 'user';
    }
    elsif ( exists( $self->api_config->{username} ) ) {
        $self->{auth_method} = 'user';        
    }
    else {
        die "missing Authentication : token or username/password \n";
    }


    $self->{_credentials_loaded} = 1;
    return;
}

sub _fish_ebay_ini {
    my $self = shift;
    my $arg  = shift;
    my @files;

    # initialize our hashref
    $self->{_ebay_ini} ||= {};

    # revert eBay::API::Simple keys to standard keys
    $arg = 'DeveloperKey'    if $arg eq 'devid';
    $arg = 'ApplicationKey' if $arg eq 'appid';
    $arg = 'CertificateKey' if $arg eq 'certid';
    $arg = 'Token'          if $arg eq 'token';
    $arg = 'IAFToken'       if $arg eq 'iaftoken';
    $arg = 'UserName'       if $arg eq 'username';
    $arg = 'Password'       if $arg eq 'password';
    $arg = 'Domain'         if $arg eq 'domain';
    $arg = 'Https'          if $arg eq 'https';
    $arg = 'Uri'            if $arg eq 'uri';
    $arg = 'Version'        if $arg eq 'version';

    # return it if we've already found it
    return $self->{_ebay_ini}{$arg} if defined $self->{_ebay_ini}{$arg};

    # ini files in order of importance

    # Make exception for windows
    if ( $^O eq 'MSWin32' ) {
        @files = ( './ebay.ini', './ebay.yaml' );
    }
    else {
          @files = (
              './ebay.yaml',
              "$ENV{HOME}/ebay.yaml",
              '/etc/ebay.yaml',
             './ebay.ini',
             "$ENV{HOME}/ebay.ini",
             '/etc/ebay.ini',
         );
    }

    foreach my $file ( reverse @files ) {
        if ( open( FILE, "<", $file ) ) {
            
            while ( my $line = <FILE> ) {
                chomp( $line );

                next if $line =~ m!^\s*\#!;

                my( $k, $v ) = split( /=/, $line );

                if ( defined $k && defined $v) {
                    $v =~ s/^\s+//;
                    $v =~ s/\s+$//;

                    $self->{_ebay_ini}{$k} = $v;
       
                }
            }

            close FILE;
        }
    }
    return $self->{_ebay_ini}{$arg} if defined $self->{_ebay_ini}{$arg};
    return undef;

}

1;

=head1 AUTHOR

Tim Keefer <tim@timkeefer.com>

=head1 CONTRIBUTORS

Jyothi Krishnan

=cut

lib/eBay/API/SimpleBase.pm  view on Meta::CPAN

package eBay::API::SimpleBase;

use strict;
use warnings;

use XML::LibXML;
use XML::Simple;
use HTTP::Request;
use HTTP::Headers;
use LWP::UserAgent;
use XML::Parser;
use URI::Escape;
use YAML;
use utf8;

use base 'eBay::API::Simple';

# set the preferred xml simple parser
$XML::Simple::PREFERRED_PARSER = 'XML::Parser';

our $DEBUG = 0;

=head1 NAME 

eBay::API::SimpleBase - Flexible SDK supporting all eBay web services

=head1 DESCRIPTION

This is the base class for the eBay::API::Simple::* libraries that provide
support for all of eBay's web services. This base class does nothing by itself
and must be subclassed to provide the complete web service support. 

=item L<eBay::API::Simple::Merchandising>

=item L<eBay::API::Simple::Finding>

=item L<eBay::API::Simple::Shopping>

=item L<eBay::API::Simple::Trading>

=item L<eBay::API::Simple::HTML>

=item L<eBay::API::Simple::JSON>

=item L<eBay::API::Simple::RSS>

=head1 GET THE SOURCE

http://code.google.com/p/ebay-api-simple

=head1 PUBLIC METHODS

=head2 eBay::API::Simple::{subclass}->new()

see subclass for more docs.

=item L<eBay::API::Simple::Merchandising>

=item L<eBay::API::Simple::Finding>

=item L<eBay::API::Simple::Shopping>

=item L<eBay::API::Simple::Trading>

=item L<eBay::API::Simple::HTML>

=item L<eBay::API::Simple::JSON>

=item L<eBay::API::Simple::RSS>

=cut

sub new {
    my $class    = shift;
    my $api_args = shift;

    my $self = {};  
    bless( $self, $class );
    
    # set some defaults 
    $self->api_config->{siteid}   = 0;
    $self->api_config->{enable_attributes} = 0;
    $self->api_config->{timeout}  = 20 unless defined $api_args->{timeout};
    $self->api_config->{parallel} = $api_args->{parallel};
    
    unless (defined $api_args->{preserve_namespace}) {
        $self->api_config->{preserve_namespace} = 0;
    }
    
    # set the config args 
    $self->api_config_append( $api_args );
    
    return $self;   
}

=head2 execute( $verb, $call_data )

Calling this method will prepare the request, execute, and process the response.

It is recommended that prepare and process be subclassed rather than this method.

=item $verb (required)

call verb, i.e. FindItems 

=item $call_data (required)

hashref of call_data that will be turned into xml.

=cut

sub execute {
    my $self = shift;

    $self->prepare( @_ );

    $self->_execute_http_request();

    if ( defined $self->{response_content} ) {
        $self->process();
    }
}

=head2 prepare( $verb, $call_data )

This is called by execute to prepare the request
and may be supplied by the subclass.

=cut

sub prepare {
    my $self = shift;

    $self->{verb}      = shift;
    $self->{call_data} = shift;
    
    if ( ! defined $self->{verb} || ! defined $self->{call_data} ) {
        die "missing verb and call_data";
    }
}

=head2 process()

This is called by execute to process the response
and may be supplied by the subclass.

=cut

sub process {
    my $self = shift;

    if ( $DEBUG ) {
        print STDERR $self->request_object->as_string();
        print STDERR $self->response_object->as_string();
    }
}

=head2 request_agent

Accessor for the LWP::UserAgent request agent

=cut

sub request_agent {
    my $self = shift;
    return $self->{request_agent};
}

=head2 request_object

Accessor for the HTTP::Request request object

=cut

sub request_object {
    my $self = shift;
    return $self->{request_object};
}

=head2 request_content

Accessor for the complete request body from the HTTP::Request object

=cut

sub request_content {
    my $self = shift;
    return $self->{request_object}->as_string();
} 

=head2 response_content

Accessor for the HTTP response body content

=cut

sub response_content {
    my $self = shift;
    return $self->{response_content};
}

=head2 response_object

Accessor for the HTTP::Request response object

=cut

sub response_object {
    my $self = shift;
    return $self->{response_object};
}

=head2 response_dom

Accessor for the LibXML response DOM

=cut

sub response_dom {
    my $self = shift;

    if ( ! defined $self->{response_dom} ) {
        my $parser = XML::LibXML->new();    
        eval {
            $self->{response_dom} = 
                $parser->parse_string( $self->response_content );
        };
        if ( $@ ) {
            $self->errors_append( { 'parsing_error' => $@ } );
        }
    }

    return $self->{response_dom};
}

=head2 response_hash

Accessor for the hashified response content

=cut

sub response_hash {
    my $self = shift;

    if ( ! defined $self->{response_hash} ) {
        $self->{response_hash} = XMLin( $self->response_content,
            forcearray => [],
            keyattr    => []
        );
    }

    return $self->{response_hash};
}

=head2 response_json

Not implemented yet.

=cut

sub response_json {
    my $self = shift;

    if ( ! defined $self->{response_json} ) {
        $self->{response_json} = ''; # xml2json( $self->{response_content} );
    }

    return $self->{response_json};
}

=head2 nodeContent( $tag, [ $dom ] ) 

Helper for LibXML that retrieves node content

=item $tag (required)

This is the name of the xml element

=item $dom (optional)

optionally a DOM object can be passed in. If no DOM object 
is passed then the main response DOM object is used.

=cut
 
sub nodeContent {
    my $self = shift;
    my $tag  = shift;
    my $node = shift;

    $node ||= $self->response_dom();

    return if ! $tag || ! $node;

    my $e = $node->getElementsByTagName($tag);
    if ( defined $e->[0] ) {
        return $e->[0]->textContent();

    }
    else { 
        #print STDERR "no info for $tag\n";
        return;
    }
}

=head2 errors 

Accessor to the hashref of errors

=cut

sub errors {
    my $self = shift;
    $self->{errors} = {} unless defined $self->{errors};
    return $self->{errors};
}

=head2 has_error

Returns true if the call contains errors

=cut

sub has_error {
    my $self = shift;
    my $has_error =  (keys( %{ $self->errors } ) > 0) ? 1 : 0;
    return $has_error;
}

=head2 errors_as_string

Returns a string of API errors if there are any.

=cut

sub errors_as_string {
    my $self = shift;

    my @e;
    for my $k ( keys %{ $self->errors } ) {
        push( @e, $k . '-' . $self->errors->{$k} );
    }
    
    return join( "\n", @e );
}

=head1 INTERNAL METHODS

=head2 api_config

Accessor to a hashref of api config data that will be used to execute
the api call.

  siteid,domain,uri,etc.

=cut

sub api_config {
    my $self = shift;
    $self->{api_config} = {} unless defined $self->{api_config};
    return $self->{api_config};
}

=head2 api_config_append( $hashref )

This method is used to merge config into the config_api hash

=cut

sub api_config_append {
    my $self = shift;
    my $config_hash = shift;

    for my $k ( keys %{ $config_hash } ) {
        $self->api_config->{$k} = $config_hash->{$k};
    }
}

=head2 api_config_dump()

This method is used for debugging

=cut

sub api_config_dump {
    my $self = shift;

    my $str;
    
    while ( my( $key, $value ) = each( %{ $self->api_config } ) ) {
         $str .= sprintf( "%s=%s\n", $key, $value );
    }
    
    return $str;
}

=head2 errors_append

This method lets you append errors to the errors stack

=cut

sub errors_append {
    my $self = shift;
    my $hashref = shift;

    for my $k ( keys %{ $hashref } ) {
        $self->errors->{$k} = $hashref->{$k};
    }

}

=head1 PRIVATE METHODS

=head2 _execute_http_request

This method performs the http request and should be used by 
each subclass.

=cut

sub _execute_http_request {
    my $self = shift;

    # clear previous call data
    $self->_reset();
 
    unless ( defined $self->{request_agent} ) {
        $self->{request_agent} = $self->_get_request_agent();
    }

    unless ( defined $self->{request_object} ) {
        $self->{request_object} = $self->_get_request_object();
    }

    if ( defined $self->api_config->{parallel} ) {
        $self->{request_object}->{_ebay_api_simple_instance} = $self;
        $self->api_config->{parallel}->register( $self->{request_object} );
        return undef;
    }

    my $max_tries = 1;
    
    if ( defined $self->api_config->{retry} ) {
        $max_tries =  $self->api_config->{retry} + 1;
    }

    my $content = '';
    my $error   = '';
    my $response;

    for ( my $i=0; $i < $max_tries; ++$i ) {
        $response = $self->{request_agent}->request( $self->{request_object} );

        if ( $response->is_success ) {
            last; # exit the loop
        }
    }

    $self->_process_http_request( $response );
  
    return $self->{response_content};
}

=head2 _process_http_request

This method processes the http request after it has completed.

=cut

sub _process_http_request {
    my $self = shift;
    my $response = shift;

    $self->{response_object} = $response;

    if ( $response->is_success ) {
        my $content = $response->content();

        unless ($self->api_config->{preserve_namespace}) {
            # strip out the namespace param, with single or double quotes
            $content =~ s/xmlns=("[^"]+"|'[^']+') *//;
        }

        $self->{response_content} = $content;

        # call the classes validate response method if it exists
        $self->_validate_response() if $self->can('_validate_response');
    }
    else {
        # store the error 
        my $error   = $response->status_line;
        $self->errors_append( { http_response => $error } ) if defined $error;     

        my $content = $response->content();
        $self->{response_content} = $content;
    }
}

=head2 _reset

Upon execute() we need to undef any data from a previous call. This
method will clear all call data and is usually done before each execute

=cut

sub _reset {
    my $self = shift;

    # clear previous call
    $self->{errors}            = undef;
    $self->{response_object}   = undef;
    $self->{response_content}  = undef;
    $self->{request_agent}     = undef;
    $self->{request_object}    = undef;
    $self->{response_dom}      = undef;
    $self->{response_json}     = undef;
    $self->{response_hash}     = undef;

}

=head2 _build_url( $base_url, $%params )

Constructs a URL based on the supplied args

=cut

sub _build_url {
    my $self = shift;
    my $base = shift;
    my $args = shift;

    my @p;
    for my $k ( sort keys %{ $args } ) {
        if ( ref( $args->{$k} ) eq 'ARRAY' ) {
            for my $ap ( @{ $args->{$k} } ) {
                push( @p, 
                    ( $k . '=' . uri_escape_utf8( $ap ) ) 
                );                    
            }
        }
        else {
            push( @p, ( $k . '=' . uri_escape_utf8( $args->{$k} ) ) );
        }        
    }

    return( scalar( @p ) > 0 ? $base . '?' . join('&', @p) : $base );
}

=head2 _get_request_body

The request body should be provided by the subclass

=cut

sub _get_request_body {
    my $self = shift;
    
    my $xml = "<sample>some content</sample>";

    return $xml; 
}

=head2 _get_request_headers 

The request headers should be provided by the subclass

=cut

sub _get_request_headers {
    my $self = shift;
   
    my $obj = HTTP::Headers->new();

    $obj->push_header("SAMPLE-HEADER" => 'foo');
    
    return $obj;
}

=head2 _get_request_agent

The request request agent should be used by all subclasses

=cut

sub _get_request_agent {
    my $self = shift;

    my $ua= LWP::UserAgent->new();

    $ua->agent( sprintf( '%s / eBay API Simple (Version: %s)',
        $ua->agent,
        $eBay::API::Simple::VERSION,
    ) );

    # timeout in seconds
    if ( defined $self->api_config->{timeout} ) {
        $ua->timeout( $self->api_config->{timeout} );
    }
    
    # add proxy
    if ( $self->api_config->{http_proxy} ) {
        $ua->proxy( ['http'], $self->api_config->{http_proxy} );
    }

    if ( $self->api_config->{https_proxy} ) {
        $ua->proxy( ['https'], $self->api_config->{https_proxy} );
    }
    
    return $ua;
}

=head2 _get_request_object

The request object should be provided by the subclass

=cut

sub _get_request_object {
    my $self = shift;

    my $url = sprintf( 'http%s://%s%s',
        ( $self->api_config->{https} ? 's' : '' ),
        $self->api_config->{domain},
        $self->api_config->{uri}
    );
  
    my $objRequest = HTTP::Request->new(
        "POST",
        $url,
        $self->_get_request_headers,
        $self->_get_request_body
    );

    if( $self->api_config->{authorization_basic}{enabled} ) {
        $objRequest->authorization_basic(
            $self->api_config->{authorization_basic}{username},
            $self->api_config->{authorization_basic}{password}
        );
    }
        
    return $objRequest;
}

sub authorization_basic {
     my $self = shift;
     my $username = shift;
     my $password = shift;
     $self->api_config->{authorization_basic}{username} = $username;
     $self->api_config->{authorization_basic}{password} = $password;
     $self->api_config->{authorization_basic}{enabled} = 1;
}

sub disable_authorization_basic {
     my $self = shift;
     $self->api_config->{authorization_basic}{enabled} = 0;
}

=head2 _load_yaml_defaults

This method will search for the ebay.yaml file and load configuration defaults
for each service endpoint

YAML files can be placed at the below locations. The first file found will
be loaded.

  ./ebay.yaml, ~/ebay.yaml, /etc/ebay.yaml 

Sample YAML:

  # Trading - External
  api.ebay.com:
    appid: <your appid>
    certid: <your certid>
    devid: <your devid>
    token: <token>
    
  # Shopping
  open.api.ebay.com:
    appid: <your appid>
    certid: <your certid>
    devid: <your devid>
    version: 671

  # Finding/Merchandising
  svcs.ebay.com:
    appid: <your appid>
    version: 1.0.0


=cut

sub _load_yaml_defaults {
    my $self = shift;

    return 1 if $self->{_yaml_loaded};
    
    my @files = (
        "./ebay.yaml",
        "/etc/ebay.yaml",
    );

    push(@files, "$ENV{HOME}/ebay.yaml") if defined ($ENV{HOME});

    foreach my $file ( reverse @files ) {

        if ( open( FILE, "<", $file ) ) {

            my $yaml;
            { 
                local $/ = undef; 
                $yaml = <FILE>; 
            }                

            my $hashref = YAML::Load($yaml);
            my $domain  = $self->api_config->{domain};

            if ( defined $hashref->{ $domain } ) {
                $self->api_config_append( $hashref->{ $domain } );
            }
            
            $self->{_yaml_loaded} = 1;
            close FILE;
            last;
        }
    }


}

1;

=head1 AUTHOR

Tim Keefer <tim@timkeefer.com>

=head1 CONTRIBUTOR

Andrew Dittes <adittes@gmail.com>
Brian Gontowski <bgontowski@gmail.com>

=cut

t/01use.t  view on Meta::CPAN

use strict;
use warnings;
use lib qw(lib);

use Test::More tests => 8;

use_ok( 'eBay::API::Simple' );
use_ok( 'eBay::API::SimpleBase' );
use_ok( 'eBay::API::Simple::Trading' );
use_ok( 'eBay::API::Simple::Finding' );
use_ok( 'eBay::API::Simple::Shopping' );
use_ok( 'eBay::API::Simple::HTML' );
use_ok( 'eBay::API::Simple::RSS' );
use_ok( 'eBay::API::Simple::JSON' );



( run in 0.537 second using v1.01-cache-2.11-cpan-3989ada0592 )