rockauto_api 0.1.0 → 0.1.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: eff8195831ca08f029eb1128c3fbdc9a8bc5c6441a41d3eba61b2279add15d46
4
- data.tar.gz: e961fb95855b0e0d7433ae5c9122ae3a755763e4a5ad99ce22bc0966759ebd9b
3
+ metadata.gz: 4e4cbe41448443aa7171c3b64a5eabd9cb57238eede7153139787e20a02d703d
4
+ data.tar.gz: 26413f356a2c6d7981fa912415e878f2f776a1d2c836cd43b60039e3c9217ec0
5
5
  SHA512:
6
- metadata.gz: e5b8f220ea632303e38d8a34a9ef4bddc5481b2d2c618bc73de1bd286af05cfe8e6ad0fddf621c311cb175b38735a1bb7f04ba0edb3a192a015dbc574622a8d4
7
- data.tar.gz: '078556f85c37110c00410c72602a46757c6e8d0fb344f74f1f3d8c14d3e5f0ad6a6870715d6ced9e3c060fe56d6dae593d41460b921aec8e3e64e8362d36ac2c'
6
+ metadata.gz: 21c380dc61636b085274e65ac4529e33ebd5fe2218f6d0be8d7ff22f5cf00763163098ffc5828ca4161375707f0f5b62df85fa43ef7952e522ea13d81aa76022
7
+ data.tar.gz: cd88096e1c0e0473373dee1ac07244007a2e8b21a176539ff733c92178b496c6012a7b663b3a647400ddd1a094d89a4566a8f0ba285fd71c96aeab48fb27497b
@@ -106,6 +106,7 @@ module RockautoApi
106
106
  end
107
107
 
108
108
  def post_with_csrf(url, form_data)
109
+ init_session!
109
110
  page_resp = @conn.get(url)
110
111
  nck = Parsers::HtmlHelpers.extract_csrf_token(page_resp.body)
111
112
  form_data["_nck"] = nck if nck
@@ -143,6 +144,7 @@ module RockautoApi
143
144
  end
144
145
 
145
146
  def get(path)
147
+ init_session!
146
148
  resp = @conn.get(path)
147
149
  resp.body
148
150
  rescue Faraday::Error => e
@@ -150,6 +152,7 @@ module RockautoApi
150
152
  end
151
153
 
152
154
  def post(path, body = nil)
155
+ init_session!
153
156
  resp = @conn.post(path, body)
154
157
  resp.body
155
158
  rescue Faraday::Error => e
@@ -62,24 +62,51 @@ module RockautoApi
62
62
  type_value = match&.value || ""
63
63
  end
64
64
 
65
+ init_session!
66
+
67
+ page_resp = @conn.get("/en/partsearch/")
68
+ nck = Parsers::HtmlHelpers.extract_csrf_token(page_resp.body)
69
+
65
70
  form_data = {
71
+ "_nck" => nck || "",
72
+ "_jnck" => @jnck_token || "",
66
73
  "dopartsearch" => "1",
67
74
  "partsearch[partnum][partsearch_007]" => part_number,
68
75
  "partsearch[manufacturer][partsearch_007]" => man_value,
69
76
  "partsearch[partgroup][partsearch_007]" => group_value,
70
77
  "partsearch[parttype][partsearch_007]" => type_value,
71
78
  "partsearch[partname][partsearch_007]" => part_name || "",
72
- "partsearch[do][partsearch_007]" => "Search"
79
+ "partsearch[do][partsearch_007]" => "Search",
80
+ "func" => "sendparttabsearch",
81
+ "payload" => "{}",
82
+ "api_json_request" => "1",
83
+ "sctchecked" => "1",
84
+ "scbeenloaded" => "false",
85
+ "curCartGroupID" => ""
73
86
  }
74
87
 
75
- html = post_with_csrf("/en/partsearch/", form_data)
76
- doc = Nokogiri::HTML(html)
88
+ resp = Faraday.new(url: "https://www.rockauto.com") do |f|
89
+ f.request :url_encoded
90
+ f.use :cookie_jar
91
+ f.adapter Faraday.default_adapter
92
+ f.options.timeout = RockautoApi.configuration&.request_timeout || 30
93
+ f.headers["X-Requested-With"] = "XMLHttpRequest"
94
+ f.headers["Content-Type"] = "application/x-www-form-urlencoded; charset=UTF-8"
95
+ f.headers["User-Agent"] = "Mozilla/5.0 (iPhone; CPU iPhone OS 17_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1 Mobile/15E148 Safari/604.1"
96
+ f.headers["Referer"] = "https://www.rockauto.com/en/partsearch/"
97
+ @conn.headers["Cookie"].to_s.split(";").each do |cookie|
98
+ name, val = cookie.strip.split("=", 2)
99
+ f.headers["Cookie"] = "#{f.headers['Cookie']}; #{name}=#{val}" if name && val
100
+ end
101
+ end.post("catalog/catalogapi.php", form_data)
77
102
 
78
- parts = parse_part_search_results(doc)
103
+ response = JSON.parse(resp.body)
104
+
105
+ parts = parse_part_search_json(response)
79
106
  parts = parts.map do |p|
80
107
  attrs = p.to_h
81
108
  if include_fitments && attrs[:listing_data]
82
- fitment_result = get_fitment_for_part(attrs[:listing_data])
109
+ attrs[:buyers_guide] = get_fitment_for_part(attrs[:listing_data])
83
110
  end
84
111
  Models::PartInfo.new(**attrs)
85
112
  end
@@ -91,6 +118,8 @@ module RockautoApi
91
118
  manufacturer: manufacturer || "All",
92
119
  part_group: part_group || "All"
93
120
  )
121
+ rescue Faraday::Error => e
122
+ raise NetworkError, "Part search failed: #{e.message}"
94
123
  end
95
124
 
96
125
  def what_is_part_called(search_query)
@@ -146,6 +175,65 @@ module RockautoApi
146
175
  data.empty? ? nil : data
147
176
  end
148
177
 
178
+ def parse_part_search_json(response)
179
+ html = response["searchnoderesults"]
180
+ return [] unless html && !html.empty?
181
+
182
+ doc = Nokogiri::HTML(html)
183
+ parse_part_search_listings(doc)
184
+ end
185
+
186
+ def parse_part_search_listings(doc)
187
+ doc.css(".listing-container-c").map { |container|
188
+ begin
189
+ next nil if container.at_css(".listing-final-partnumber").nil?
190
+
191
+ part_number = container.at_css(".listing-final-partnumber")&.text&.strip || "Unknown"
192
+ brand = container.at_css(".listing-final-manufacturer")&.text&.strip
193
+ price_el = container.at_css(".listing-price")
194
+ price = price_el&.text&.strip
195
+ image_elem = container.at_css("img.listing-inline-image") || container.at_css("img")
196
+ image_url = Parsers::HtmlHelpers.make_absolute_url(image_elem["src"]) if image_elem&.attr("src")
197
+ info_link = container.at_css("a[href*='moreinfo']")
198
+ info_url = Parsers::HtmlHelpers.make_absolute_url(info_link["href"]) if info_link&.attr("href")
199
+ name_text = container.at_css(".listing-text-row b")&.text&.strip
200
+ name = name_text || "#{brand} #{part_number}"
201
+ category_elem = container.at_css(".listing-footnote-text")
202
+ category = category_elem&.text&.strip
203
+
204
+ supplement_input = container.at_css("input[name='listing_data_supplemental'], input[id^='listing_data_supplemental']")
205
+ essential_input = container.at_css("input[name^='listing_data_essential'], input[id^='listing_data_essential']")
206
+ listing_data = nil
207
+ if supplement_input || essential_input
208
+ listing_data = {}
209
+ if essential_input
210
+ ess = JSON.parse(essential_input["value"] || "{}") rescue {}
211
+ listing_data["groupindex"] = ess["groupindex"].to_s if ess["groupindex"]
212
+ listing_data["car"] = { "carcode" => ess["carcode"], "parttype" => ess["parttype"], "partkey" => ess["partkey"] }
213
+ end
214
+ if supplement_input
215
+ supp = JSON.parse(supplement_input["value"] || "{}") rescue {}
216
+ listing_data["supplemental"] = { "partnumber" => supp["partnumber"], "catalogname" => supp["catalogname"] }
217
+ end
218
+ end
219
+
220
+ Models::PartInfo.new(
221
+ name: name,
222
+ part_number: part_number,
223
+ brand: brand,
224
+ price: price,
225
+ url: nil,
226
+ image_url: image_url,
227
+ info_url: info_url,
228
+ category: category,
229
+ listing_data: listing_data
230
+ )
231
+ rescue StandardError
232
+ nil
233
+ end
234
+ }.compact
235
+ end
236
+
149
237
  def parse_what_is_called_results(doc)
150
238
  doc.css("a").map { |a|
151
239
  text = a.text.strip
@@ -15,6 +15,7 @@ module RockautoApi
15
15
  attribute? :specifications, Types::String.optional
16
16
  attribute? :compatibility_notes, Types::String.optional
17
17
  attribute? :listing_data, Types::Hash
18
+ attribute? :buyers_guide, Types.Instance(RockautoApi::Models::BuyersGuideResult)
18
19
  end
19
20
 
20
21
  class PartSearchResult < Dry::Struct
@@ -14,12 +14,21 @@ module RockautoApi
14
14
  next if cells.first.match?(/\A\s*(?:Year|Make|Model)\s*\z/i)
15
15
 
16
16
  year = cells[0].to_i
17
+ if year.zero?
18
+ year = cells[2].to_i
19
+ make = cells[0] || "Unknown"
20
+ model = cells[1] || "Unknown"
21
+ else
22
+ make = cells[1] || "Unknown"
23
+ model = cells[2] || "Unknown"
24
+ end
25
+
17
26
  next if year.zero?
18
27
 
19
28
  fitments << Models::FitmentInfo.new(
20
29
  year: year,
21
- make: cells[1] || "Unknown",
22
- model: cells[2] || "Unknown",
30
+ make: make,
31
+ model: model,
23
32
  engine: cells[3],
24
33
  transmission: cells[4],
25
34
  drivetrain: cells[5],
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RockautoApi
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.1"
5
5
  end
data/lib/rockauto_api.rb CHANGED
@@ -13,8 +13,8 @@ require_relative "rockauto_api/configuration"
13
13
  require_relative "rockauto_api/errors"
14
14
  require_relative "rockauto_api/cache"
15
15
  require_relative "rockauto_api/models/vehicle"
16
- require_relative "rockauto_api/models/part"
17
16
  require_relative "rockauto_api/models/fitment"
17
+ require_relative "rockauto_api/models/part"
18
18
  require_relative "rockauto_api/models/order"
19
19
  require_relative "rockauto_api/models/account"
20
20
  require_relative "rockauto_api/models/tool"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rockauto_api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben D'Angelo