ebay-trader 0.9.5
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.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rspec +3 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +325 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/ebay_trader.gemspec +34 -0
- data/examples/get_categories.rb +39 -0
- data/examples/more_examples.rb +5 -0
- data/lib/ebay_trader.rb +45 -0
- data/lib/ebay_trader/configuration.rb +242 -0
- data/lib/ebay_trader/fetch_token.rb +45 -0
- data/lib/ebay_trader/request.rb +354 -0
- data/lib/ebay_trader/sax_handler.rb +151 -0
- data/lib/ebay_trader/session_id.rb +71 -0
- data/lib/ebay_trader/version.rb +3 -0
- data/lib/ebay_trader/xml_builder.rb +113 -0
- data/lib/hash_with_indifferent_access.rb +23 -0
- metadata +138 -0
@@ -0,0 +1,151 @@
|
|
1
|
+
#require 'active_support/core_ext/hash/indifferent_access'
|
2
|
+
require 'active_support/core_ext/string'
|
3
|
+
require 'hash_with_indifferent_access'
|
4
|
+
|
5
|
+
module EbayTrader
|
6
|
+
class SaxHandler
|
7
|
+
|
8
|
+
attr_accessor :stack
|
9
|
+
attr_accessor :path
|
10
|
+
attr_reader :skip_type_casting
|
11
|
+
attr_reader :known_arrays
|
12
|
+
|
13
|
+
def initialize(args = {})
|
14
|
+
@stack = []
|
15
|
+
@stack.push(HashWithIndifferentAccess.new)
|
16
|
+
@path = []
|
17
|
+
@hash = nil
|
18
|
+
@attributes = {}
|
19
|
+
|
20
|
+
@skip_type_casting = args[:skip_type_casting] || []
|
21
|
+
@skip_type_casting = [@skip_type_casting] unless @skip_type_casting.is_a?(Array)
|
22
|
+
@skip_type_casting.map! { |key| format(key.to_s) }
|
23
|
+
|
24
|
+
@known_arrays = args[:known_arrays] || []
|
25
|
+
@known_arrays = [@known_arrays] unless @known_arrays.is_a?(Array)
|
26
|
+
@known_arrays.map! { |key| format(key.to_s) }
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_hash
|
30
|
+
stack[0]
|
31
|
+
end
|
32
|
+
|
33
|
+
def start_element(name)
|
34
|
+
@attributes.clear
|
35
|
+
name = name.to_s
|
36
|
+
path.push(name)
|
37
|
+
|
38
|
+
hash = HashWithIndifferentAccess.new
|
39
|
+
append(format_key(name), hash)
|
40
|
+
stack.push(hash)
|
41
|
+
end
|
42
|
+
|
43
|
+
def end_element(_)
|
44
|
+
@stack.pop
|
45
|
+
@path.pop
|
46
|
+
end
|
47
|
+
|
48
|
+
def text(value)
|
49
|
+
key = format_key(path.last)
|
50
|
+
auto_cast = !(skip_type_casting.include?(path.last) || skip_type_casting.include?(key.to_s))
|
51
|
+
parent = @stack[-2]
|
52
|
+
|
53
|
+
# If 'CurrencyID' is a defined attribute we are dealing with money type
|
54
|
+
if @attributes.key?('CurrencyID')
|
55
|
+
currency = @attributes.delete('CurrencyID')
|
56
|
+
value = BigDecimal.new(value)
|
57
|
+
if EbayTrader.configuration.price_type == :money && EbayTrader.is_money_gem_installed?
|
58
|
+
value = Money.new((value * 100).round.to_i, currency)
|
59
|
+
else
|
60
|
+
@attributes['Currency'] = currency
|
61
|
+
value = value.to_f if EbayTrader.configuration.price_type == :float
|
62
|
+
value = (value * 100).round.to_i if EbayTrader.configuration.price_type == :fixnum
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
if auto_cast && value.is_a?(String)
|
67
|
+
case
|
68
|
+
when value.downcase == 'false' then value = false
|
69
|
+
when value.downcase == 'true' then value = true
|
70
|
+
when value.match(/^[0-9]+$/) then value = value.to_i
|
71
|
+
when value.match(/^[0-9]+[.][0-9]+$/) then value = value.to_f
|
72
|
+
when value.match(/^[0-9]{4}-[0-9]{2}-[0-9]{2}[T ][0-9]{2}:[0-9]{2}:[0-9]{2}(.[0-9a-z]+)?$/i)
|
73
|
+
value = Time.parse(value)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
if parent[key].is_a?(Array)
|
78
|
+
parent[key].pop if parent[key].last.is_a?(Hash) && parent[key].last.empty?
|
79
|
+
parent[key] << value
|
80
|
+
else
|
81
|
+
parent[key] = value
|
82
|
+
end
|
83
|
+
|
84
|
+
unless @attributes.empty?
|
85
|
+
@attributes.each_pair do |attr_key, attr_value|
|
86
|
+
attr_key_element_name = format_key("#{path.last}#{attr_key}")
|
87
|
+
parent[attr_key_element_name] = attr_value
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def cdata(value)
|
93
|
+
key = format_key(path.last)
|
94
|
+
parent = @stack[-2]
|
95
|
+
parent[key] = value
|
96
|
+
end
|
97
|
+
|
98
|
+
def attr(name, value)
|
99
|
+
return if name.to_s.downcase == 'xmlns'
|
100
|
+
last = path.last
|
101
|
+
return if last.nil?
|
102
|
+
|
103
|
+
name = name[0].upcase + name[1...name.length]
|
104
|
+
@attributes[name] = value
|
105
|
+
end
|
106
|
+
|
107
|
+
def error(message, line, column)
|
108
|
+
raise Exception.new("#{message} at #{line}:#{column}")
|
109
|
+
end
|
110
|
+
|
111
|
+
def append(key, value)
|
112
|
+
key = key.to_s
|
113
|
+
h = @stack.last
|
114
|
+
if h.key?(key)
|
115
|
+
v = h[key]
|
116
|
+
if v.is_a?(Array)
|
117
|
+
v << value
|
118
|
+
else
|
119
|
+
h[key] = [v, value]
|
120
|
+
end
|
121
|
+
else
|
122
|
+
if known_arrays.include?(key)
|
123
|
+
h[key] = [value]
|
124
|
+
else
|
125
|
+
h[key] = value
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
#-------------------------------------------------------------------------
|
131
|
+
private
|
132
|
+
|
133
|
+
# Ensure the key is an underscored Symbol.
|
134
|
+
#
|
135
|
+
# Examples:
|
136
|
+
#
|
137
|
+
# 'ApplyBuyerProtection' -> :apply_buyer_protection
|
138
|
+
# 'PayPalEmailAddress' -> :paypal_email_address
|
139
|
+
# 'SoldOffeBay' -> :sold_off_ebay
|
140
|
+
#
|
141
|
+
# @return [Symbol] the reformatted key.
|
142
|
+
#
|
143
|
+
def format_key(key)
|
144
|
+
key = key.to_s
|
145
|
+
key = key.gsub('PayPal', 'Paypal')
|
146
|
+
key = key.gsub('eBay', 'Ebay')
|
147
|
+
key = key.gsub('EBay', 'Ebay')
|
148
|
+
key.underscore.to_sym
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
require 'ebay_trader/request'
|
3
|
+
|
4
|
+
module EbayTrader
|
5
|
+
|
6
|
+
# Request a session ID from the eBay API.
|
7
|
+
#
|
8
|
+
# @see http://developer.ebay.com/DevZone/XML/docs/Reference/eBay/GetSessionID.html
|
9
|
+
# @see http://developer.ebay.com/DevZone/XML/docs/HowTo/Tokens/GettingTokens.html
|
10
|
+
# @see http://developer.ebay.com/DevZone/guides/ebayfeatures/Basics/Tokens-MultipleUsers.html
|
11
|
+
#
|
12
|
+
class SessionID < Request
|
13
|
+
|
14
|
+
CALL_NAME = 'GetSessionID'
|
15
|
+
|
16
|
+
# The application RuName defined in {Configuration#ru_name}, unless over-ridden in {#initialize} args.
|
17
|
+
# @return [String] the RuName for this call.
|
18
|
+
# @see https://developer.ebay.com/DevZone/account/appsettings/Consent/
|
19
|
+
#
|
20
|
+
attr_reader :ru_name
|
21
|
+
|
22
|
+
# Construct a GetSessionID eBay API call.
|
23
|
+
# @param [Hash] args a hash of optional arguments.
|
24
|
+
# @option args [String] :ru_name Override the default RuName,
|
25
|
+
# which should be defined in {Configuration#ru_name}.
|
26
|
+
#
|
27
|
+
def initialize(args = {})
|
28
|
+
@ru_name = (args[:ru_name] || EbayTrader.configuration.ru_name).freeze
|
29
|
+
|
30
|
+
super(CALL_NAME, args) do
|
31
|
+
RuName ru_name
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Get the session ID returned by the API call.
|
36
|
+
# @return [String] the session ID.
|
37
|
+
#
|
38
|
+
def id
|
39
|
+
response_hash[:session_id]
|
40
|
+
end
|
41
|
+
|
42
|
+
# Get the URL through which a user must sign in using this session ID.
|
43
|
+
# @param [Hash] ruparams eBay appends this data to the AcceptURL and RejectURL.
|
44
|
+
# In a typical rails app this might include the user's model primary key.
|
45
|
+
# @return [String] the sign-in URL.
|
46
|
+
#
|
47
|
+
def sign_in_url(ruparams = {})
|
48
|
+
url = EbayTrader.configuration.production? ? 'https://signin.ebay.com' : 'https://signin.sandbox.ebay.com'
|
49
|
+
url << '/ws/eBayISAPI.dll?SignIn'
|
50
|
+
url << "&runame=#{url_encode ru_name}"
|
51
|
+
url << "&SessID=#{url_encode id}"
|
52
|
+
if ruparams && ruparams.is_a?(Hash) && !ruparams.empty?
|
53
|
+
params = []
|
54
|
+
ruparams.each_pair { |key, value| params << "#{key}=#{value}" }
|
55
|
+
url << "&ruparams=#{url_encode(params.join('&'))}"
|
56
|
+
end
|
57
|
+
url
|
58
|
+
end
|
59
|
+
|
60
|
+
#---------------------------------------------------------------
|
61
|
+
private
|
62
|
+
|
63
|
+
def url_encode(string)
|
64
|
+
CGI.escape string
|
65
|
+
end
|
66
|
+
|
67
|
+
def url_decode(string)
|
68
|
+
CGI.unescape string
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
module EbayTrader
|
2
|
+
class XMLBuilder
|
3
|
+
|
4
|
+
attr_reader :context, :xml, :depth, :tab_width
|
5
|
+
|
6
|
+
def initialize(args = {})
|
7
|
+
@xml = ''
|
8
|
+
@depth = 0
|
9
|
+
@tab_width = (args[:tab_width] || 0).to_i
|
10
|
+
end
|
11
|
+
|
12
|
+
# Catch-all method to avoid having to create individual methods for each XML tag name.
|
13
|
+
def method_missing(method_name, *args, &block)
|
14
|
+
if @context && @context.respond_to?(method_name)
|
15
|
+
@context.send(method_name, *args, &block)
|
16
|
+
else
|
17
|
+
node(method_name, args, &block)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Only respond to method names with only numbers and letters.
|
22
|
+
# Do not respond to names with underscores.
|
23
|
+
def respond_to_missing?(method_name, include_private = false)
|
24
|
+
super || method_name.to_s =~ /^[a-z0-9]+$/i
|
25
|
+
end
|
26
|
+
|
27
|
+
# Begin creating an XML string by specifying the root node.
|
28
|
+
# This also set the context scope, allowing methods and variable
|
29
|
+
# outside the block to be accessed.
|
30
|
+
# @param [String] name the name of the root node element.
|
31
|
+
# @param [Array] args the data for this element.
|
32
|
+
# @param [Block] block an optional block of sub-elements to be nested
|
33
|
+
# within the root node.
|
34
|
+
def root(name, *args, &block)
|
35
|
+
set_context(&block)
|
36
|
+
node(name, args, &block)
|
37
|
+
end
|
38
|
+
|
39
|
+
#---------------------------------------------------------------------------
|
40
|
+
private
|
41
|
+
|
42
|
+
# @see https://github.com/sparklemotion/nokogiri/blob/master/lib/nokogiri/xml/builder.rb
|
43
|
+
def set_context(&block)
|
44
|
+
@context = block_given? ? eval('self', block.binding) : nil
|
45
|
+
@context = nil if @context.is_a?(XMLBuilder)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Create an XML node
|
49
|
+
# @param [String|Symbol] name the name of the XML element (ul, li, strong, etc...)
|
50
|
+
# @param [Array] args Can contain a String of text or a Hash of attributes
|
51
|
+
# @param [Block] block An optional block which will further nest XML
|
52
|
+
def node(name, args, &block)
|
53
|
+
content = get_node_content(args)
|
54
|
+
options = format_node_attributes(get_node_attributes(args))
|
55
|
+
|
56
|
+
@xml << "#{indent_new_line}<#{name}#{options}>#{content}"
|
57
|
+
if block_given?
|
58
|
+
@depth += 1
|
59
|
+
instance_eval(&block)
|
60
|
+
@depth -= 1
|
61
|
+
@xml << indent_new_line
|
62
|
+
end
|
63
|
+
@xml << "</#{name}>"
|
64
|
+
@xml.strip
|
65
|
+
end
|
66
|
+
|
67
|
+
# Return the first Hash in the list of arguments to #node
|
68
|
+
# as this defines the attributes for the XML node.
|
69
|
+
# @return [Hash] the hash of attributes for this node.
|
70
|
+
#
|
71
|
+
def get_node_attributes(args)
|
72
|
+
args.detect { |arg| arg.is_a? Hash } || {}
|
73
|
+
end
|
74
|
+
|
75
|
+
# Return the node content as a String, unless a block is given.
|
76
|
+
# @return [String] the node data.
|
77
|
+
#
|
78
|
+
def get_node_content(args)
|
79
|
+
return nil if block_given?
|
80
|
+
content = nil
|
81
|
+
args.each do |arg|
|
82
|
+
case arg
|
83
|
+
when Hash
|
84
|
+
next
|
85
|
+
when Time
|
86
|
+
# eBay official TimeStamp format YYYY-MM-DDTHH:MM:SS.SSSZ
|
87
|
+
content = arg.strftime('%Y-%m-%dT%H:%M:%S.%Z')
|
88
|
+
when DateTime
|
89
|
+
content = arg.strftime('%Y-%m-%dT%H:%M:%S.%Z')
|
90
|
+
break
|
91
|
+
else
|
92
|
+
content = arg.to_s
|
93
|
+
break
|
94
|
+
end
|
95
|
+
end
|
96
|
+
content
|
97
|
+
end
|
98
|
+
|
99
|
+
# Convert the given Hash of options into a string of XML element attributes.
|
100
|
+
#
|
101
|
+
def format_node_attributes(options)
|
102
|
+
options.collect { |key, value|
|
103
|
+
value = value.to_s.gsub('"', '\"')
|
104
|
+
" #{key}=\"#{value}\""
|
105
|
+
}.join('')
|
106
|
+
end
|
107
|
+
|
108
|
+
# Add a new line to the XML and indent with the appropriate number of spaces.
|
109
|
+
def indent_new_line
|
110
|
+
tab_width > 0 ? ("\n" + (' ' * tab_width * depth)) : ''
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
2
|
+
|
3
|
+
class HashWithIndifferentAccess
|
4
|
+
|
5
|
+
# Perform a depth first search of this hash and return the first element
|
6
|
+
# matching +path+, or +default+ if nothing found.
|
7
|
+
# @param [Array[String | Symbol] | String | Symbol] path to the element of interest.
|
8
|
+
# @param [Object] default the object to be returned if there is no result for +path+.
|
9
|
+
# @return [Object] the first object found on +path+ or +default+.
|
10
|
+
#
|
11
|
+
def deep_find(path, default = nil)
|
12
|
+
return default unless path
|
13
|
+
path = [path] if path.is_a?(String) || path.is_a?(Symbol)
|
14
|
+
return default unless path.is_a?(Array) && !path.empty?
|
15
|
+
|
16
|
+
location = self
|
17
|
+
path.each do |key|
|
18
|
+
return default if location.nil? || !location.key?(key)
|
19
|
+
location = location[key]
|
20
|
+
end
|
21
|
+
return location
|
22
|
+
end
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ebay-trader
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.9.5
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Rob Graham
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-09-08 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.10'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.10'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: activesupport
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '4.0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '4.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: ox
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '2.2'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '2.2'
|
83
|
+
description: |2
|
84
|
+
EbayTrader is a lightweight easy to use Ruby gem for interacting with eBay's Trading API.
|
85
|
+
Using its simple DSL you can quickly and intuitively post XML requests to eBay and rapidly interpret the responses.
|
86
|
+
email:
|
87
|
+
- rob@altabyte.com
|
88
|
+
executables: []
|
89
|
+
extensions: []
|
90
|
+
extra_rdoc_files: []
|
91
|
+
files:
|
92
|
+
- ".gitignore"
|
93
|
+
- ".rspec"
|
94
|
+
- ".travis.yml"
|
95
|
+
- Gemfile
|
96
|
+
- LICENSE.txt
|
97
|
+
- README.md
|
98
|
+
- Rakefile
|
99
|
+
- bin/console
|
100
|
+
- bin/setup
|
101
|
+
- ebay_trader.gemspec
|
102
|
+
- examples/get_categories.rb
|
103
|
+
- examples/more_examples.rb
|
104
|
+
- lib/ebay_trader.rb
|
105
|
+
- lib/ebay_trader/configuration.rb
|
106
|
+
- lib/ebay_trader/fetch_token.rb
|
107
|
+
- lib/ebay_trader/request.rb
|
108
|
+
- lib/ebay_trader/sax_handler.rb
|
109
|
+
- lib/ebay_trader/session_id.rb
|
110
|
+
- lib/ebay_trader/version.rb
|
111
|
+
- lib/ebay_trader/xml_builder.rb
|
112
|
+
- lib/hash_with_indifferent_access.rb
|
113
|
+
homepage: https://github.com/altabyte/ebay_trader
|
114
|
+
licenses:
|
115
|
+
- MIT
|
116
|
+
metadata: {}
|
117
|
+
post_install_message:
|
118
|
+
rdoc_options: []
|
119
|
+
require_paths:
|
120
|
+
- lib
|
121
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - ">="
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - ">="
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '0'
|
131
|
+
requirements: []
|
132
|
+
rubyforge_project:
|
133
|
+
rubygems_version: 2.4.6
|
134
|
+
signing_key:
|
135
|
+
specification_version: 4
|
136
|
+
summary: A DSL to interact with eBay's Trading API using Ruby
|
137
|
+
test_files: []
|
138
|
+
has_rdoc:
|