sucker 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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