sucker 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ (The MIT License)
2
+
3
+ Copyright (c) 2009 Paper Cavalier
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ 'Software'), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.textile ADDED
@@ -0,0 +1,19 @@
1
+ h1. Sucker
2
+
3
+ Sucker is a thin Ruby wrapper to the Amazon Product Advertising API. It's built on Curb, which is great for querying Amazon on multiple network ports.
4
+
5
+ @sucker = Sucker.new(
6
+ :locale => "us",
7
+ :key => "key",
8
+ :secret => "secret")
9
+
10
+ @sucker.curl { |curl| curl.interface = "eth2" }
11
+
12
+ @sucker.parameters.merge!({
13
+ "Operation" => "ItemLookup",
14
+ "IdType" => "ASIN",
15
+ "ItemId" => "0816614024" })
16
+ @sucker.fetch
17
+ p @sucker.to_h # Now, do something with that hash!
18
+
19
+ !http://upload.wikimedia.org/wikipedia/commons/a/af/Vacuum_cleaner.jpg!
@@ -0,0 +1,112 @@
1
+ # = Sucker
2
+ # Sucker is a thin Ruby wrapper to the {Amazon Product Advertising API}[http://docs.amazonwebservices.com/AWSECommerceService/latest/DG/].
3
+ #
4
+ module Sucker
5
+ class Request
6
+ HOSTS = {
7
+ :us => 'ecs.amazonaws.com',
8
+ :uk => 'ecs.amazonaws.co.uk',
9
+ :de => 'ecs.amazonaws.de',
10
+ :ca => 'ecs.amazonaws.ca',
11
+ :fr => 'ecs.amazonaws.fr',
12
+ :jp => 'ecs.amazonaws.jp' }
13
+
14
+ # The Amazon locale to query
15
+ attr_accessor :locale
16
+
17
+ # The Amazon secret access key
18
+ attr_accessor :secret
19
+
20
+ # The hash of parameters to query Amazon with
21
+ attr_accessor :parameters
22
+
23
+ def initialize(args)
24
+ self.parameters = {
25
+ "Service" => "AWSECommerceService",
26
+ "Version" => Sucker::AMAZON_API_VERSION
27
+ }
28
+
29
+ args.each { |k, v| send("#{k}=", v) }
30
+ end
31
+
32
+ # A reusable, configurable cURL object
33
+ def curl
34
+ @curl ||= Curl::Easy.new
35
+
36
+ yield @curl if block_given?
37
+
38
+ @curl
39
+ end
40
+
41
+ # Hits Amazon with an API request
42
+ def fetch
43
+ return nil if !valid?
44
+
45
+ curl.url = uri.to_s
46
+ curl.perform
47
+ end
48
+
49
+ # A helper method that sets the AWS Access Key ID
50
+ def key=(key)
51
+ parameters["AWSAccessKeyId"] = key
52
+ end
53
+
54
+ # Returns a hash of the response
55
+ def to_h
56
+ Crack::XML.parse(curl.body_str)
57
+ end
58
+
59
+ private
60
+
61
+ # Escapes parameters and concatenates them into a query string
62
+ def build_query
63
+ parameters.
64
+ sort.
65
+ collect do |k, v|
66
+ "#{CGI.escape(k)}=" + CGI.escape(v.is_a?(Array) ? v.join(",") : v)
67
+ end.
68
+ join("&")
69
+ end
70
+
71
+ def digest
72
+ OpenSSL::Digest::Digest.new("sha256")
73
+ end
74
+
75
+ def host
76
+ HOSTS[locale.to_sym]
77
+ end
78
+
79
+ def path
80
+ "/onca/xml"
81
+ end
82
+
83
+ # Returns a signed and timestamped query string
84
+ def sign_query
85
+ timestamp_parameters
86
+
87
+ query = build_query
88
+
89
+ string = ["GET", host, path, query].join("\n")
90
+ hmac = OpenSSL::HMAC.digest(digest, secret, string)
91
+
92
+ query + "&Signature=" + CGI.escape([hmac].pack("m").chomp)
93
+ end
94
+
95
+ def uri
96
+ URI::HTTP.build(
97
+ :host => host,
98
+ :path => path,
99
+ :query => sign_query)
100
+ end
101
+
102
+ # Timestamps the parameters
103
+ def timestamp_parameters
104
+ self.parameters["Timestamp"] = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
105
+ end
106
+
107
+ # Returns true if request has key, secret, and a valid locale set
108
+ def valid?
109
+ !!locale && !!HOSTS[locale.to_sym] && !!secret && !!parameters["AWSAccessKeyId"]
110
+ end
111
+ end
112
+ end
data/lib/sucker.rb ADDED
@@ -0,0 +1,14 @@
1
+ module Sucker
2
+ VERSION = '0.1.0'.freeze
3
+ AMAZON_API_VERSION = '2009-11-01'.freeze
4
+
5
+ class SuckerError < StandardError; end
6
+
7
+ def self.new(args={})
8
+ Sucker::Request.new(args)
9
+ end
10
+ end
11
+
12
+ require 'cgi'
13
+ require 'sucker/request'
14
+ require 'crack/xml'
@@ -0,0 +1,53 @@
1
+ require "spec_helper"
2
+
3
+ module Sucker
4
+ describe "Item Lookup" do
5
+ before do
6
+ @sucker = Sucker.new(
7
+ :locale => "us",
8
+ :key => amazon["key"],
9
+ :secret => amazon["secret"])
10
+
11
+ # @sucker.curl { |curl| curl.verbose = true }
12
+
13
+ @sucker.parameters.merge!({
14
+ "Operation" => "ItemLookup",
15
+ "IdType" => "ASIN"})
16
+ end
17
+
18
+ context "single item" do
19
+ before do
20
+ @sucker.parameters.merge!({
21
+ "ItemId" => "0816614024" })
22
+ @sucker.fetch
23
+ @item = @sucker.to_h["ItemLookupResponse"]["Items"]["Item"]
24
+ end
25
+
26
+ it "returns an item" do
27
+ @item.should be_an_instance_of Hash
28
+ end
29
+
30
+ it "includes an ASIN string" do
31
+ @item["ASIN"].should eql "0816614024"
32
+ end
33
+
34
+ it "includes the item attributes" do
35
+ @item["ItemAttributes"].should be_an_instance_of Hash
36
+ end
37
+ end
38
+
39
+ context "multiple items" do
40
+ before do
41
+ @sucker.parameters.merge!({
42
+ "ItemId" => ["0816614024", "0143105825"] })
43
+ @sucker.fetch
44
+ @items = @sucker.to_h["ItemLookupResponse"]["Items"]["Item"]
45
+ end
46
+
47
+ it "returns two items" do
48
+ @items.should be_an_instance_of Array
49
+ @items.size.should eql 2
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,29 @@
1
+ require "spec_helper"
2
+
3
+ module Sucker
4
+ describe "Seller Listing Search" do
5
+ before do
6
+ @sucker = Sucker.new(
7
+ :locale => "us",
8
+ :key => amazon["key"],
9
+ :secret => amazon["secret"])
10
+
11
+ # @sucker.curl { |curl| curl.verbose = true }
12
+
13
+ @sucker.parameters.merge!({
14
+ "Operation" => "SellerListingSearch",
15
+ "SellerId" => "A31N271NVIORU3"})
16
+ @sucker.fetch
17
+ @listings = @sucker.to_h["SellerListingSearchResponse"]["SellerListings"]
18
+ end
19
+
20
+ it "returns page count" do
21
+ @listings["TotalPages"].to_i.should be > 0
22
+ end
23
+
24
+ it "returns listings" do
25
+ @listings["SellerListing"].size.should be > 0
26
+ @listings["SellerListing"].first.has_key?("Price").should be_true
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,7 @@
1
+ require "rubygems"
2
+ require "bundler"
3
+ Bundler.require(:default)
4
+
5
+ require File.expand_path("../../lib/sucker", __FILE__)
6
+
7
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each{ |f| require f }
@@ -0,0 +1,3 @@
1
+ def amazon
2
+ @amazon ||= YAML::load_file(File.dirname(__FILE__) + "/amazon.yml")
3
+ end
@@ -0,0 +1,7 @@
1
+ def fixture(filename)
2
+ File.new(File.dirname(__FILE__) + "/../fixtures/#{filename}.xml", "r").read
3
+ end
4
+
5
+ def record(filename, output)
6
+ File.open(File.dirname(__FILE__) + "/../fixtures/#{filename}.xml", "w") { |f| f.write output }
7
+ end
@@ -0,0 +1,159 @@
1
+ require "spec_helper"
2
+
3
+ module Sucker
4
+ describe "Request" do
5
+ before do
6
+ @sucker = Sucker.new
7
+ end
8
+
9
+ context "public" do
10
+ context ".new" do
11
+ it "sets default parameters" do
12
+ default_parameters = {
13
+ "Service" => "AWSECommerceService",
14
+ "Version" => Sucker::AMAZON_API_VERSION }
15
+ @sucker.parameters.should eql default_parameters
16
+ end
17
+ end
18
+
19
+ context "#curl" do
20
+ it "returns a cURL object" do
21
+ @sucker.curl.should be_an_instance_of Curl::Easy
22
+ end
23
+
24
+ it "configures the cURL object" do
25
+ @sucker.curl.interface.should be_nil
26
+
27
+ @sucker.curl do |curl|
28
+ curl.interface = "eth1"
29
+ end
30
+
31
+ @sucker.curl.interface.should eql "eth1"
32
+ end
33
+ end
34
+
35
+ context "#fetch" do
36
+ it "returns nil if valid? returns false" do
37
+ @sucker.stub!(:valid?).and_return(false)
38
+ @sucker.fetch.should be_nil
39
+ end
40
+ end
41
+
42
+ context "#to_h" do
43
+ before do
44
+ @sucker.curl.stub!(:body_str).and_return(fixture("single_item_lookup.us"))
45
+ end
46
+
47
+ it "should return a hash" do
48
+ @sucker.to_h.should be_an_instance_of Hash
49
+ end
50
+ end
51
+ end
52
+
53
+ context "private" do
54
+ context "#build_query" do
55
+ it "canonicalizes parameters" do
56
+ query = @sucker.send(:build_query)
57
+ query.should eql "Service=AWSECommerceService&Version=#{Sucker::AMAZON_API_VERSION}"
58
+ end
59
+
60
+ it "sorts parameters" do
61
+ @sucker.parameters["Foo"] = "bar"
62
+ query = @sucker.send(:build_query)
63
+ query.should match /^Foo=bar/
64
+ end
65
+
66
+ it "converts a parameter whose value is an array to a string" do
67
+ @sucker.parameters["Foo"] = ["bar", "baz"]
68
+ query = @sucker.send(:build_query)
69
+ query.should match /^Foo=bar%2Cbaz/
70
+ end
71
+ end
72
+
73
+ context "#digest" do
74
+ it "returns a digest object" do
75
+ @sucker.send(:digest).should be_an_instance_of OpenSSL::Digest::Digest
76
+ end
77
+ end
78
+
79
+ context "#key=" do
80
+ it "sets the Amazon AWS access key in the parameters" do
81
+ @sucker.key = "key"
82
+ @sucker.parameters["AWSAccessKeyId"].should eql "key"
83
+ end
84
+ end
85
+
86
+ context "#host" do
87
+ it "returns a host" do
88
+ @sucker.locale = "us"
89
+ @sucker.send(:host).should eql "ecs.amazonaws.com"
90
+ end
91
+ end
92
+
93
+ context "#path" do
94
+ it "returns a path" do
95
+ @sucker.send(:path).should eql "/onca/xml"
96
+ end
97
+ end
98
+
99
+ context "#sign_query" do
100
+ it "returns a signed query string" do
101
+ @sucker.secret = "secret"
102
+ @sucker.locale = "us"
103
+ query = @sucker.send :sign_query
104
+ query.should match /&Signature=.*/
105
+ end
106
+ end
107
+
108
+ context "#timestamp_parameters" do
109
+ it "upserts a timestamp to the parameters" do
110
+ @sucker.send :timestamp_parameters
111
+ @sucker.parameters["Timestamp"].should match /^\d+-\d+-\d+T\d+:\d+:\d+Z$/
112
+ end
113
+ end
114
+
115
+ context "#uri" do
116
+ it "returns the URI with which to query Amazon" do
117
+ @sucker.key = "key"
118
+ @sucker.locale = "us"
119
+ @sucker.secret = "secret"
120
+ @sucker.send(:uri).should be_an_instance_of URI::HTTP
121
+ end
122
+ end
123
+
124
+ context "valid?" do
125
+ it "returns true if key, secret, and a valid locale are set" do
126
+ @sucker.key = "key"
127
+ @sucker.locale = "us"
128
+ @sucker.secret = "secret"
129
+ @sucker.send(:valid?).should be_true
130
+ end
131
+
132
+ it "returns false if key is not set" do
133
+ @sucker.locale = "us"
134
+ @sucker.secret = "secret"
135
+ @sucker.send(:valid?).should be_false
136
+ end
137
+
138
+ it "returns false if secret is not set" do
139
+ @sucker.locale = "us"
140
+ @sucker.key = "key"
141
+ @sucker.send(:valid?).should be_false
142
+ end
143
+
144
+ it "returns false if locale is not set" do
145
+ @sucker.key = "key"
146
+ @sucker.secret = "secret"
147
+ @sucker.send(:valid?).should be_false
148
+ end
149
+
150
+ it "returns false if locale is not valid" do
151
+ @sucker.key = "key"
152
+ @sucker.locale = "US"
153
+ @sucker.secret = "secret"
154
+ @sucker.send(:valid?).should be_false
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,9 @@
1
+ require "spec_helper"
2
+
3
+ describe Sucker do
4
+ context ".new" do
5
+ it "returns a Request object" do
6
+ Sucker.new.should be_an_instance_of Sucker::Request
7
+ end
8
+ end
9
+ end
metadata ADDED
@@ -0,0 +1,151 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sucker
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Hakan Ensari
14
+ - Piotr Laszewski
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2010-07-21 00:00:00 +01:00
20
+ default_executable:
21
+ dependencies:
22
+ - !ruby/object:Gem::Dependency
23
+ prerelease: false
24
+ type: :runtime
25
+ name: crack
26
+ version_requirements: &id001 !ruby/object:Gem::Requirement
27
+ none: false
28
+ requirements:
29
+ - - "="
30
+ - !ruby/object:Gem::Version
31
+ hash: 11
32
+ segments:
33
+ - 0
34
+ - 1
35
+ - 8
36
+ version: 0.1.8
37
+ requirement: *id001
38
+ - !ruby/object:Gem::Dependency
39
+ prerelease: false
40
+ type: :runtime
41
+ name: curb
42
+ version_requirements: &id002 !ruby/object:Gem::Requirement
43
+ none: false
44
+ requirements:
45
+ - - "="
46
+ - !ruby/object:Gem::Version
47
+ hash: 105
48
+ segments:
49
+ - 0
50
+ - 7
51
+ - 7
52
+ - 1
53
+ version: 0.7.7.1
54
+ requirement: *id002
55
+ - !ruby/object:Gem::Dependency
56
+ prerelease: false
57
+ type: :development
58
+ name: rspec
59
+ version_requirements: &id003 !ruby/object:Gem::Requirement
60
+ none: false
61
+ requirements:
62
+ - - "="
63
+ - !ruby/object:Gem::Version
64
+ hash: 62196417
65
+ segments:
66
+ - 2
67
+ - 0
68
+ - 0
69
+ - beta
70
+ - 17
71
+ version: 2.0.0.beta.17
72
+ requirement: *id003
73
+ - !ruby/object:Gem::Dependency
74
+ prerelease: false
75
+ type: :development
76
+ name: ruby-debug
77
+ version_requirements: &id004 !ruby/object:Gem::Requirement
78
+ none: false
79
+ requirements:
80
+ - - "="
81
+ - !ruby/object:Gem::Version
82
+ hash: 49
83
+ segments:
84
+ - 0
85
+ - 10
86
+ - 3
87
+ version: 0.10.3
88
+ requirement: *id004
89
+ description: Sucker is a thin Ruby wrapper to the Amazon Product Advertising API.
90
+ email: code@papercavalier.com
91
+ executables: []
92
+
93
+ extensions: []
94
+
95
+ extra_rdoc_files:
96
+ - LICENSE
97
+ - README.textile
98
+ files:
99
+ - LICENSE
100
+ - lib/sucker.rb
101
+ - lib/sucker/request.rb
102
+ - README.textile
103
+ - spec/integration/item_lookup_spec.rb
104
+ - spec/integration/seller_listing_search_spec.rb
105
+ - spec/spec_helper.rb
106
+ - spec/support/amazon_credentials.rb
107
+ - spec/support/curb_stubber.rb
108
+ - spec/unit/sucker/request_spec.rb
109
+ - spec/unit/sucker_spec.rb
110
+ has_rdoc: true
111
+ homepage: http://github.com/papercavalier/sucker
112
+ licenses: []
113
+
114
+ post_install_message:
115
+ rdoc_options:
116
+ - --charset=UTF-8
117
+ require_paths:
118
+ - lib
119
+ required_ruby_version: !ruby/object:Gem::Requirement
120
+ none: false
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ hash: 3
125
+ segments:
126
+ - 0
127
+ version: "0"
128
+ required_rubygems_version: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ hash: 3
134
+ segments:
135
+ - 0
136
+ version: "0"
137
+ requirements: []
138
+
139
+ rubyforge_project:
140
+ rubygems_version: 1.3.7
141
+ signing_key:
142
+ specification_version: 3
143
+ summary: A thin Ruby wrapper to the Amazon Product Advertising API
144
+ test_files:
145
+ - spec/integration/item_lookup_spec.rb
146
+ - spec/integration/seller_listing_search_spec.rb
147
+ - spec/spec_helper.rb
148
+ - spec/support/amazon_credentials.rb
149
+ - spec/support/curb_stubber.rb
150
+ - spec/unit/sucker/request_spec.rb
151
+ - spec/unit/sucker_spec.rb