rockauto_api 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.
- checksums.yaml +7 -0
- data/lib/rockauto_api/cache.rb +34 -0
- data/lib/rockauto_api/client.rb +167 -0
- data/lib/rockauto_api/configuration.rb +23 -0
- data/lib/rockauto_api/endpoints/account.rb +159 -0
- data/lib/rockauto_api/endpoints/fitment.rb +61 -0
- data/lib/rockauto_api/endpoints/orders.rb +51 -0
- data/lib/rockauto_api/endpoints/part_categories.rb +83 -0
- data/lib/rockauto_api/endpoints/part_search.rb +165 -0
- data/lib/rockauto_api/endpoints/tools.rb +74 -0
- data/lib/rockauto_api/endpoints/vehicles.rb +86 -0
- data/lib/rockauto_api/errors.rb +10 -0
- data/lib/rockauto_api/models/account.rb +66 -0
- data/lib/rockauto_api/models/fitment.rb +22 -0
- data/lib/rockauto_api/models/order.rb +61 -0
- data/lib/rockauto_api/models/part.rb +75 -0
- data/lib/rockauto_api/models/tool.rb +41 -0
- data/lib/rockauto_api/models/vehicle.rb +76 -0
- data/lib/rockauto_api/parsers/fitment_parser.rb +40 -0
- data/lib/rockauto_api/parsers/html_helpers.rb +56 -0
- data/lib/rockauto_api/parsers/order_parser.rb +66 -0
- data/lib/rockauto_api/parsers/part_extractor.rb +79 -0
- data/lib/rockauto_api/railtie.rb +17 -0
- data/lib/rockauto_api/version.rb +5 -0
- data/lib/rockauto_api.rb +44 -0
- metadata +192 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RockautoApi
|
|
4
|
+
module Endpoints
|
|
5
|
+
module PartSearch
|
|
6
|
+
def get_manufacturers
|
|
7
|
+
cache.fetch("rockauto:manufacturers", ttl: 86400) do
|
|
8
|
+
html = get("/en/partsearch/")
|
|
9
|
+
opts = Parsers::HtmlHelpers.select_options(html, "manufacturer_partsearch_007")
|
|
10
|
+
manufacturers = opts.map { |o| Models::PartSearchOption.new(value: o[:value], text: o[:text]) }
|
|
11
|
+
Models::ManufacturerOptions.new(
|
|
12
|
+
manufacturers: manufacturers,
|
|
13
|
+
count: manufacturers.size
|
|
14
|
+
)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def get_part_groups
|
|
19
|
+
cache.fetch("rockauto:part_groups", ttl: 86400) do
|
|
20
|
+
html = get("/en/partsearch/")
|
|
21
|
+
opts = Parsers::HtmlHelpers.select_options(html, "partgroup_partsearch_007")
|
|
22
|
+
groups = opts.map { |o| Models::PartSearchOption.new(value: o[:value], text: o[:text]) }
|
|
23
|
+
Models::PartGroupOptions.new(
|
|
24
|
+
part_groups: groups,
|
|
25
|
+
count: groups.size
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def get_part_types
|
|
31
|
+
cache.fetch("rockauto:part_types", ttl: 86400) do
|
|
32
|
+
html = get("/en/partsearch/")
|
|
33
|
+
opts = Parsers::HtmlHelpers.select_options(html, "parttype_partsearch_007")
|
|
34
|
+
types = opts.map { |o| Models::PartSearchOption.new(value: o[:value], text: o[:text]) }
|
|
35
|
+
Models::PartTypeOptions.new(
|
|
36
|
+
part_types: types,
|
|
37
|
+
count: types.size
|
|
38
|
+
)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def search_parts_by_number(part_number, manufacturer: nil, part_group: nil, part_type: nil, part_name: nil, include_fitments: false)
|
|
43
|
+
man_value = ""
|
|
44
|
+
group_value = ""
|
|
45
|
+
type_value = ""
|
|
46
|
+
|
|
47
|
+
if manufacturer
|
|
48
|
+
mans = get_manufacturers
|
|
49
|
+
match = mans.lookup(manufacturer)
|
|
50
|
+
man_value = match&.value || ""
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
if part_group
|
|
54
|
+
groups = get_part_groups
|
|
55
|
+
match = groups.lookup(part_group)
|
|
56
|
+
group_value = match&.value || ""
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
if part_type
|
|
60
|
+
types = get_part_types
|
|
61
|
+
match = types.lookup(part_type)
|
|
62
|
+
type_value = match&.value || ""
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
form_data = {
|
|
66
|
+
"dopartsearch" => "1",
|
|
67
|
+
"partsearch[partnum][partsearch_007]" => part_number,
|
|
68
|
+
"partsearch[manufacturer][partsearch_007]" => man_value,
|
|
69
|
+
"partsearch[partgroup][partsearch_007]" => group_value,
|
|
70
|
+
"partsearch[parttype][partsearch_007]" => type_value,
|
|
71
|
+
"partsearch[partname][partsearch_007]" => part_name || "",
|
|
72
|
+
"partsearch[do][partsearch_007]" => "Search"
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
html = post_with_csrf("/en/partsearch/", form_data)
|
|
76
|
+
doc = Nokogiri::HTML(html)
|
|
77
|
+
|
|
78
|
+
parts = parse_part_search_results(doc)
|
|
79
|
+
parts = parts.map do |p|
|
|
80
|
+
attrs = p.to_h
|
|
81
|
+
if include_fitments && attrs[:listing_data]
|
|
82
|
+
fitment_result = get_fitment_for_part(attrs[:listing_data])
|
|
83
|
+
end
|
|
84
|
+
Models::PartInfo.new(**attrs)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
Models::PartSearchResult.new(
|
|
88
|
+
parts: parts,
|
|
89
|
+
count: parts.size,
|
|
90
|
+
search_term: part_number,
|
|
91
|
+
manufacturer: manufacturer || "All",
|
|
92
|
+
part_group: part_group || "All"
|
|
93
|
+
)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def what_is_part_called(search_query)
|
|
97
|
+
html = post("/en/partsearch/", {
|
|
98
|
+
"topsearchinput[input]" => search_query,
|
|
99
|
+
"topsearchinput[submit]" => "Search"
|
|
100
|
+
})
|
|
101
|
+
doc = Nokogiri::HTML(html)
|
|
102
|
+
results = parse_what_is_called_results(doc)
|
|
103
|
+
|
|
104
|
+
Models::WhatIsPartCalledResults.new(
|
|
105
|
+
results: results,
|
|
106
|
+
count: results.size,
|
|
107
|
+
search_term: search_query
|
|
108
|
+
)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
private
|
|
112
|
+
|
|
113
|
+
def parse_part_search_results(doc)
|
|
114
|
+
doc.css("table tr").map { |row|
|
|
115
|
+
cells = row.css("td")
|
|
116
|
+
next nil if cells.size < 3
|
|
117
|
+
|
|
118
|
+
part = Parsers::PartExtractor.extract_from_row(row)
|
|
119
|
+
next nil unless part
|
|
120
|
+
|
|
121
|
+
listing_data = extract_listing_data_from_row(row)
|
|
122
|
+
|
|
123
|
+
attrs = part.to_h
|
|
124
|
+
attrs[:listing_data] = listing_data if listing_data
|
|
125
|
+
Models::PartInfo.new(**attrs)
|
|
126
|
+
}.compact
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def extract_listing_data_from_row(row)
|
|
130
|
+
data = {}
|
|
131
|
+
|
|
132
|
+
row.css("[data-groupindex], [data-carcode], [data-parttype], [data-partkey], [data-partnumber], [data-catalogname], [data-optkey]").each do |el|
|
|
133
|
+
data["groupindex"] = el["data-groupindex"] if el["data-groupindex"]
|
|
134
|
+
data["car"] = { "carcode" => el["data-carcode"], "parttype" => el["data-parttype"], "partkey" => el["data-partkey"] }
|
|
135
|
+
data["supplemental"] = { "partnumber" => el["data-partnumber"], "catalogname" => el["data-catalogname"] }
|
|
136
|
+
data["optkey"] = el["data-optkey"] if el["data-optkey"]
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
row.css("a").each do |a|
|
|
140
|
+
onclick = a["onclick"]
|
|
141
|
+
next unless onclick
|
|
142
|
+
match = onclick.match(/getbuyersguide\('([^']+)'/)
|
|
143
|
+
data["buyersguide_key"] = match[1] if match
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
data.empty? ? nil : data
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def parse_what_is_called_results(doc)
|
|
150
|
+
doc.css("a").map { |a|
|
|
151
|
+
text = a.text.strip
|
|
152
|
+
next nil if text.empty?
|
|
153
|
+
path_parts = text.split(">").map(&:strip)
|
|
154
|
+
main_category = path_parts[0]
|
|
155
|
+
subcategory = path_parts[1]
|
|
156
|
+
Models::WhatIsPartCalledResult.new(
|
|
157
|
+
main_category: main_category || "",
|
|
158
|
+
subcategory: subcategory || "",
|
|
159
|
+
full_path: text
|
|
160
|
+
)
|
|
161
|
+
}.compact
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RockautoApi
|
|
4
|
+
module Endpoints
|
|
5
|
+
module Tools
|
|
6
|
+
def get_tool_categories(path = "/en/tools/")
|
|
7
|
+
html = get(path)
|
|
8
|
+
doc = Nokogiri::HTML(html)
|
|
9
|
+
|
|
10
|
+
categories = doc.css("a").map { |a|
|
|
11
|
+
text = a.text.strip
|
|
12
|
+
href = a["href"]
|
|
13
|
+
next nil if text.empty? || href.nil? || !href.include?("tools")
|
|
14
|
+
Models::ToolCategory.new(
|
|
15
|
+
name: text,
|
|
16
|
+
group_name: text,
|
|
17
|
+
href: Parsers::HtmlHelpers.make_absolute_url(href),
|
|
18
|
+
level: path.scan("/").size - 1
|
|
19
|
+
)
|
|
20
|
+
}.compact
|
|
21
|
+
|
|
22
|
+
Models::ToolCategories.new(
|
|
23
|
+
categories: categories,
|
|
24
|
+
count: categories.size,
|
|
25
|
+
level: path.scan("/").size - 1,
|
|
26
|
+
parent_path: path
|
|
27
|
+
)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def get_tools_by_category(category_path)
|
|
31
|
+
html = get(category_path)
|
|
32
|
+
doc = Nokogiri::HTML(html)
|
|
33
|
+
tools = parse_tools_from_table(doc)
|
|
34
|
+
category_name = category_path.split("/").last
|
|
35
|
+
|
|
36
|
+
Models::ToolsResult.new(
|
|
37
|
+
tools: tools,
|
|
38
|
+
count: tools.size,
|
|
39
|
+
category: category_name,
|
|
40
|
+
category_path: category_path
|
|
41
|
+
)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def parse_tools_from_table(doc)
|
|
47
|
+
doc.css("table tr").map { |row|
|
|
48
|
+
cells = row.css("td")
|
|
49
|
+
next nil if cells.size < 3
|
|
50
|
+
|
|
51
|
+
texts = cells.map { |c| c.text.strip }
|
|
52
|
+
|
|
53
|
+
link = row.at_css("a")
|
|
54
|
+
img = row.at_css("img")
|
|
55
|
+
|
|
56
|
+
Models::ToolInfo.new(
|
|
57
|
+
name: texts[1] || texts.first || "",
|
|
58
|
+
part_number: texts[0] || "",
|
|
59
|
+
brand: texts[2],
|
|
60
|
+
description: texts[3],
|
|
61
|
+
url: link ? Parsers::HtmlHelpers.make_absolute_url(link["href"]) : nil,
|
|
62
|
+
image_url: img ? Parsers::HtmlHelpers.make_absolute_url(img["src"]) : nil,
|
|
63
|
+
info_url: nil,
|
|
64
|
+
video_url: nil,
|
|
65
|
+
specifications: nil,
|
|
66
|
+
category: nil,
|
|
67
|
+
features: nil,
|
|
68
|
+
warranty_info: nil
|
|
69
|
+
)
|
|
70
|
+
}.compact
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RockautoApi
|
|
4
|
+
module Endpoints
|
|
5
|
+
module Vehicles
|
|
6
|
+
BASE_CATALOG_URL = "https://www.rockauto.com/en/catalog/"
|
|
7
|
+
|
|
8
|
+
def get_makes
|
|
9
|
+
html = get("/en/catalog/")
|
|
10
|
+
results = Models::VehicleMakes.from_html(html)
|
|
11
|
+
results
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def get_years_for_make(make)
|
|
15
|
+
simulate_navigation_context(make: make)
|
|
16
|
+
|
|
17
|
+
path = "/en/catalog/#{make}"
|
|
18
|
+
html = get(path)
|
|
19
|
+
doc = Nokogiri::HTML(html)
|
|
20
|
+
|
|
21
|
+
years = doc.css('a[href*="catalog"]').map { |a|
|
|
22
|
+
href = a["href"]
|
|
23
|
+
next nil if href.nil?
|
|
24
|
+
match = href.match(/#{Regexp.escape(make)},(\d{4})/i)
|
|
25
|
+
match ? match[1].to_i : nil
|
|
26
|
+
}.compact.uniq.sort
|
|
27
|
+
|
|
28
|
+
Models::VehicleYears.new(
|
|
29
|
+
make: make,
|
|
30
|
+
years: years,
|
|
31
|
+
count: years.size
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def get_models_for_make_year(make, year)
|
|
36
|
+
simulate_navigation_context(make: make, year: year.to_s)
|
|
37
|
+
|
|
38
|
+
path = "/en/catalog/#{make},#{year}"
|
|
39
|
+
html = get(path)
|
|
40
|
+
doc = Nokogiri::HTML(html)
|
|
41
|
+
|
|
42
|
+
models = doc.css('a[href*="catalog"]').map { |a|
|
|
43
|
+
href = a["href"]
|
|
44
|
+
next nil if href.nil?
|
|
45
|
+
match = href.match(/#{Regexp.escape(make)},#{year},(.+)/i)
|
|
46
|
+
next nil unless match
|
|
47
|
+
model = match[1].split(",").first
|
|
48
|
+
model&.strip
|
|
49
|
+
}.compact.uniq
|
|
50
|
+
|
|
51
|
+
Models::VehicleModels.new(
|
|
52
|
+
make: make,
|
|
53
|
+
year: year,
|
|
54
|
+
models: models,
|
|
55
|
+
count: models.size
|
|
56
|
+
)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def get_engines_for_vehicle(make, year, model)
|
|
60
|
+
path = "/en/catalog/#{make},#{year},#{model}"
|
|
61
|
+
html = get(path)
|
|
62
|
+
doc = Nokogiri::HTML(html)
|
|
63
|
+
|
|
64
|
+
engines = doc.css('a[href*="carcode"]').map { |a|
|
|
65
|
+
href = a["href"]
|
|
66
|
+
next nil unless href
|
|
67
|
+
match = href.match(/carcode=(\d+)/)
|
|
68
|
+
next nil unless match
|
|
69
|
+
Models::Engine.new(
|
|
70
|
+
description: a.text.strip,
|
|
71
|
+
carcode: match[1],
|
|
72
|
+
href: Parsers::HtmlHelpers.make_absolute_url(href)
|
|
73
|
+
)
|
|
74
|
+
}.compact
|
|
75
|
+
|
|
76
|
+
Models::VehicleEngines.new(
|
|
77
|
+
make: make,
|
|
78
|
+
year: year,
|
|
79
|
+
model: model,
|
|
80
|
+
engines: engines,
|
|
81
|
+
count: engines.size
|
|
82
|
+
)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RockautoApi
|
|
4
|
+
class Error < StandardError; end
|
|
5
|
+
class AuthenticationError < Error; end
|
|
6
|
+
class CaptchaError < Error; end
|
|
7
|
+
class NetworkError < Error; end
|
|
8
|
+
class ParseError < Error; end
|
|
9
|
+
class NotFoundError < Error; end
|
|
10
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RockautoApi
|
|
4
|
+
module Models
|
|
5
|
+
class SavedAddress < Dry::Struct
|
|
6
|
+
attribute? :name, Types::String.optional
|
|
7
|
+
attribute? :full_name, Types::String.optional
|
|
8
|
+
attribute? :address_line1, Types::String.optional
|
|
9
|
+
attribute? :address_line2, Types::String.optional
|
|
10
|
+
attribute? :city, Types::String.optional
|
|
11
|
+
attribute? :state, Types::String.optional
|
|
12
|
+
attribute? :postal_code, Types::String.optional
|
|
13
|
+
attribute? :country, Types::String.optional
|
|
14
|
+
attribute? :phone, Types::String.optional
|
|
15
|
+
attribute :is_default, Types::Bool.default(false)
|
|
16
|
+
attribute? :address_id, Types::String.optional
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class SavedAddressesResult < Dry::Struct
|
|
20
|
+
attribute :addresses, Types::Array.of(SavedAddress)
|
|
21
|
+
attribute :count, Types::Integer
|
|
22
|
+
attribute :has_default, Types::Bool.default(false)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class SavedVehicle < Dry::Struct
|
|
26
|
+
attribute :year, Types::Coercible::Integer
|
|
27
|
+
attribute :make, Types::String
|
|
28
|
+
attribute :model, Types::String
|
|
29
|
+
attribute? :engine, Types::String.optional
|
|
30
|
+
attribute? :carcode, Types::String.optional
|
|
31
|
+
attribute? :display_name, Types::String.optional
|
|
32
|
+
attribute? :catalog_url, Types::String.optional
|
|
33
|
+
attribute? :vehicle_id, Types::String.optional
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class SavedVehiclesResult < Dry::Struct
|
|
37
|
+
attribute :vehicles, Types::Array.of(SavedVehicle)
|
|
38
|
+
attribute :count, Types::Integer
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
class OrderHistoryItem < Dry::Struct
|
|
42
|
+
attribute :order_number, Types::String
|
|
43
|
+
attribute? :date, Types::String.optional
|
|
44
|
+
attribute? :status, Types::String.optional
|
|
45
|
+
attribute? :total, Types::String.optional
|
|
46
|
+
attribute? :vehicle, Types::String.optional
|
|
47
|
+
attribute? :order_url, Types::String.optional
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
class OrderHistoryResult < Dry::Struct
|
|
51
|
+
attribute :orders, Types::Array.of(OrderHistoryItem)
|
|
52
|
+
attribute :count, Types::Integer
|
|
53
|
+
attribute? :filter_applied, Types::String.optional
|
|
54
|
+
attribute? :search_time, Types::String.optional
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
class AccountActivityResult < Dry::Struct
|
|
58
|
+
attribute :order_history, OrderHistoryResult.optional
|
|
59
|
+
attribute :saved_addresses, SavedAddressesResult.optional
|
|
60
|
+
attribute :saved_vehicles, SavedVehiclesResult.optional
|
|
61
|
+
attribute :has_discount_codes, Types::Bool.default(false)
|
|
62
|
+
attribute :has_store_credit, Types::Bool.default(false)
|
|
63
|
+
attribute :has_alerts, Types::Bool.default(false)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RockautoApi
|
|
4
|
+
module Models
|
|
5
|
+
class FitmentInfo < Dry::Struct
|
|
6
|
+
attribute :year, Types::Integer
|
|
7
|
+
attribute :make, Types::String
|
|
8
|
+
attribute :model, Types::String
|
|
9
|
+
attribute? :engine, Types::String.optional
|
|
10
|
+
attribute? :transmission, Types::String.optional
|
|
11
|
+
attribute? :drivetrain, Types::String.optional
|
|
12
|
+
attribute? :notes, Types::String.optional
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class BuyersGuideResult < Dry::Struct
|
|
16
|
+
attribute :part_number, Types::String
|
|
17
|
+
attribute :brand, Types::String
|
|
18
|
+
attribute :fitments, Types::Array.of(FitmentInfo)
|
|
19
|
+
attribute :count, Types::Integer
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RockautoApi
|
|
4
|
+
module Models
|
|
5
|
+
class OrderItem < Dry::Struct
|
|
6
|
+
attribute :part_number, Types::String
|
|
7
|
+
attribute :description, Types::String
|
|
8
|
+
attribute? :brand, Types::String.optional
|
|
9
|
+
attribute? :quantity, Types::String.optional
|
|
10
|
+
attribute? :unit_price, Types::String.optional
|
|
11
|
+
attribute? :total_price, Types::String.optional
|
|
12
|
+
attribute? :status, Types::String.optional
|
|
13
|
+
attribute? :tracking_number, Types::String.optional
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class BillingInfo < Dry::Struct
|
|
17
|
+
attribute? :subtotal, Types::String.optional
|
|
18
|
+
attribute? :shipping_cost, Types::String.optional
|
|
19
|
+
attribute? :tax, Types::String.optional
|
|
20
|
+
attribute? :total, Types::String.optional
|
|
21
|
+
attribute? :payment_method, Types::String.optional
|
|
22
|
+
attribute? :payment_status, Types::String.optional
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class ShippingInfo < Dry::Struct
|
|
26
|
+
attribute? :method, Types::String.optional
|
|
27
|
+
attribute? :cost, Types::String.optional
|
|
28
|
+
attribute? :carrier, Types::String.optional
|
|
29
|
+
attribute? :tracking_number, Types::String.optional
|
|
30
|
+
attribute? :estimated_delivery, Types::String.optional
|
|
31
|
+
attribute? :actual_delivery, Types::String.optional
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class OrderStatus < Dry::Struct
|
|
35
|
+
attribute :order_number, Types::String
|
|
36
|
+
attribute? :order_date, Types::String.optional
|
|
37
|
+
attribute? :status, Types::String.optional
|
|
38
|
+
attribute? :customer_email, Types::String.optional
|
|
39
|
+
attribute? :customer_phone, Types::String.optional
|
|
40
|
+
attribute :items, Types::Array.of(OrderItem).default([].freeze)
|
|
41
|
+
attribute :billing, BillingInfo.optional
|
|
42
|
+
attribute :shipping, ShippingInfo.optional
|
|
43
|
+
attribute? :notes, Types::String.optional
|
|
44
|
+
attribute? :return_eligibility, Types::String.optional
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
class OrderStatusError < Dry::Struct
|
|
48
|
+
attribute :error_type, Types::String
|
|
49
|
+
attribute :message, Types::String
|
|
50
|
+
attribute :order_number, Types::String
|
|
51
|
+
attribute :suggestions, Types::Array.of(Types::String).default([].freeze)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
class OrderStatusResult < Dry::Struct
|
|
55
|
+
attribute :success, Types::Bool
|
|
56
|
+
attribute :order, OrderStatus.optional
|
|
57
|
+
attribute :error, OrderStatusError.optional
|
|
58
|
+
attribute? :lookup_time, Types::String.optional
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RockautoApi
|
|
4
|
+
module Models
|
|
5
|
+
class PartInfo < Dry::Struct
|
|
6
|
+
attribute :name, Types::String
|
|
7
|
+
attribute :part_number, Types::String
|
|
8
|
+
attribute? :brand, Types::String.optional
|
|
9
|
+
attribute? :price, Types::String.optional
|
|
10
|
+
attribute? :url, Types::String.optional
|
|
11
|
+
attribute? :image_url, Types::String.optional
|
|
12
|
+
attribute? :info_url, Types::String.optional
|
|
13
|
+
attribute? :video_url, Types::String.optional
|
|
14
|
+
attribute? :category, Types::String.optional
|
|
15
|
+
attribute? :specifications, Types::String.optional
|
|
16
|
+
attribute? :compatibility_notes, Types::String.optional
|
|
17
|
+
attribute? :listing_data, Types::Hash
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class PartSearchResult < Dry::Struct
|
|
21
|
+
attribute :parts, Types::Array.of(PartInfo)
|
|
22
|
+
attribute :count, Types::Integer
|
|
23
|
+
attribute :search_term, Types::String
|
|
24
|
+
attribute :manufacturer, Types::String.default("All")
|
|
25
|
+
attribute :part_group, Types::String.default("All")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
class PartSearchOption < Dry::Struct
|
|
29
|
+
attribute :value, Types::String
|
|
30
|
+
attribute :text, Types::String
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
class ManufacturerOptions < Dry::Struct
|
|
34
|
+
attribute :manufacturers, Types::Array.of(PartSearchOption)
|
|
35
|
+
attribute :count, Types::Integer
|
|
36
|
+
attribute? :last_updated, Types::String.optional
|
|
37
|
+
|
|
38
|
+
def lookup(name)
|
|
39
|
+
manufacturers.find { |m| m.text.casecmp(name).zero? }
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
class PartGroupOptions < Dry::Struct
|
|
44
|
+
attribute :part_groups, Types::Array.of(PartSearchOption)
|
|
45
|
+
attribute :count, Types::Integer
|
|
46
|
+
attribute? :last_updated, Types::String.optional
|
|
47
|
+
|
|
48
|
+
def lookup(name)
|
|
49
|
+
part_groups.find { |g| g.text.casecmp(name).zero? }
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
class PartTypeOptions < Dry::Struct
|
|
54
|
+
attribute :part_types, Types::Array.of(PartSearchOption)
|
|
55
|
+
attribute :count, Types::Integer
|
|
56
|
+
attribute? :last_updated, Types::String.optional
|
|
57
|
+
|
|
58
|
+
def lookup(name)
|
|
59
|
+
part_types.find { |t| t.text.casecmp(name).zero? }
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
class WhatIsPartCalledResult < Dry::Struct
|
|
64
|
+
attribute :main_category, Types::String
|
|
65
|
+
attribute :subcategory, Types::String
|
|
66
|
+
attribute :full_path, Types::String
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
class WhatIsPartCalledResults < Dry::Struct
|
|
70
|
+
attribute :results, Types::Array.of(WhatIsPartCalledResult)
|
|
71
|
+
attribute :count, Types::Integer
|
|
72
|
+
attribute :search_term, Types::String
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RockautoApi
|
|
4
|
+
module Models
|
|
5
|
+
class ToolCategory < Dry::Struct
|
|
6
|
+
attribute :name, Types::String
|
|
7
|
+
attribute :group_name, Types::String
|
|
8
|
+
attribute? :href, Types::String.optional
|
|
9
|
+
attribute :level, Types::Integer.default(0)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class ToolCategories < Dry::Struct
|
|
13
|
+
attribute :categories, Types::Array.of(ToolCategory)
|
|
14
|
+
attribute :count, Types::Integer
|
|
15
|
+
attribute :level, Types::Integer.default(0)
|
|
16
|
+
attribute? :parent_path, Types::String.optional
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class ToolInfo < Dry::Struct
|
|
20
|
+
attribute :name, Types::String
|
|
21
|
+
attribute :part_number, Types::String
|
|
22
|
+
attribute? :brand, Types::String.optional
|
|
23
|
+
attribute? :description, Types::String.optional
|
|
24
|
+
attribute? :url, Types::String.optional
|
|
25
|
+
attribute? :image_url, Types::String.optional
|
|
26
|
+
attribute? :info_url, Types::String.optional
|
|
27
|
+
attribute? :video_url, Types::String.optional
|
|
28
|
+
attribute? :specifications, Types::String.optional
|
|
29
|
+
attribute? :category, Types::String.optional
|
|
30
|
+
attribute? :features, Types::String.optional
|
|
31
|
+
attribute? :warranty_info, Types::String.optional
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class ToolsResult < Dry::Struct
|
|
35
|
+
attribute :tools, Types::Array.of(ToolInfo)
|
|
36
|
+
attribute :count, Types::Integer
|
|
37
|
+
attribute :category, Types::String
|
|
38
|
+
attribute? :category_path, Types::String.optional
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RockautoApi
|
|
4
|
+
module Types
|
|
5
|
+
include Dry.Types()
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
module Models
|
|
9
|
+
class VehicleMakes < Dry::Struct
|
|
10
|
+
attribute :makes, Types::Array.of(Types::String)
|
|
11
|
+
attribute :count, Types::Integer
|
|
12
|
+
|
|
13
|
+
def self.from_html(html)
|
|
14
|
+
doc = Nokogiri::HTML(html)
|
|
15
|
+
makes = doc.css('a[href*="/en/catalog/"]').map { |a|
|
|
16
|
+
href = a["href"]
|
|
17
|
+
next nil if href.nil? || href == "/en/catalog/" || href.include?(",")
|
|
18
|
+
a.text.strip
|
|
19
|
+
}.compact.uniq
|
|
20
|
+
new(makes: makes, count: makes.size)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class VehicleYears < Dry::Struct
|
|
25
|
+
attribute :make, Types::String
|
|
26
|
+
attribute :years, Types::Array.of(Types::Integer)
|
|
27
|
+
attribute :count, Types::Integer
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
class VehicleModels < Dry::Struct
|
|
31
|
+
attribute :make, Types::String
|
|
32
|
+
attribute :year, Types::Integer
|
|
33
|
+
attribute :models, Types::Array.of(Types::String)
|
|
34
|
+
attribute :count, Types::Integer
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
class Engine < Dry::Struct
|
|
38
|
+
attribute :description, Types::String
|
|
39
|
+
attribute :carcode, Types::String
|
|
40
|
+
attribute? :href, Types::String.optional
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
class VehicleEngines < Dry::Struct
|
|
44
|
+
attribute :make, Types::String
|
|
45
|
+
attribute :year, Types::Integer
|
|
46
|
+
attribute :model, Types::String
|
|
47
|
+
attribute :engines, Types::Array.of(Engine)
|
|
48
|
+
attribute :count, Types::Integer
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
class PartCategory < Dry::Struct
|
|
52
|
+
attribute :name, Types::String
|
|
53
|
+
attribute :group_name, Types::String
|
|
54
|
+
attribute? :href, Types::String.optional
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
class VehiclePartCategories < Dry::Struct
|
|
58
|
+
attribute :make, Types::String
|
|
59
|
+
attribute :year, Types::Integer
|
|
60
|
+
attribute :model, Types::String
|
|
61
|
+
attribute :carcode, Types::String
|
|
62
|
+
attribute :categories, Types::Array.of(PartCategory)
|
|
63
|
+
attribute :count, Types::Integer
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
class VehiclePartsResult < Dry::Struct
|
|
67
|
+
attribute :make, Types::String
|
|
68
|
+
attribute :year, Types::Integer
|
|
69
|
+
attribute :model, Types::String
|
|
70
|
+
attribute :carcode, Types::String
|
|
71
|
+
attribute :category, Types::String
|
|
72
|
+
attribute :parts, Types::Array
|
|
73
|
+
attribute :count, Types::Integer
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|