rockauto_api 0.1.1 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4e4cbe41448443aa7171c3b64a5eabd9cb57238eede7153139787e20a02d703d
4
- data.tar.gz: 26413f356a2c6d7981fa912415e878f2f776a1d2c836cd43b60039e3c9217ec0
3
+ metadata.gz: 2d861f195fe9c02dc5737f5756f0c3e7248ddad192a380913dd9e95a65d327b8
4
+ data.tar.gz: 93ef1dddb194b74a1d091039460b3586466f1e225bba2289b0da43cfe5253e02
5
5
  SHA512:
6
- metadata.gz: 21c380dc61636b085274e65ac4529e33ebd5fe2218f6d0be8d7ff22f5cf00763163098ffc5828ca4161375707f0f5b62df85fa43ef7952e522ea13d81aa76022
7
- data.tar.gz: cd88096e1c0e0473373dee1ac07244007a2e8b21a176539ff733c92178b496c6012a7b663b3a647400ddd1a094d89a4566a8f0ba285fd71c96aeab48fb27497b
6
+ metadata.gz: d4606e5e6fe6ea4b7301722ddaba983dfab6de91c47ea8510054898b1d95f292b44c4adf2e98ce5bd44bca10d8dc2f7c86b539df7f888863b0b86e821f809d91
7
+ data.tar.gz: 0d6ec3fdb8ba7f234e91dbfcbd31b504d44dc53b10ab3c1d3610e28a63dd3c4f983a79b077f3312c3922f1e3c824fb6c1688aeebf7e1c404c8716482e38ba7d0
data/README.md ADDED
@@ -0,0 +1,380 @@
1
+ # RockautoApi
2
+
3
+ An unofficial Ruby API client for [RockAuto.com](https://www.rockauto.com). Browse vehicle catalogs, search parts by number, look up fitments, check order status, and manage your RockAuto account.
4
+
5
+ **Disclaimer:** This gem is for **educational and research purposes only**. It is not affiliated with, endorsed by, or officially connected to RockAuto LLC. Automated scraping of RockAuto.com may violate their Terms of Service. Use responsibly and respect rate limits.
6
+
7
+ ---
8
+
9
+ ## Installation
10
+
11
+ Add to your Gemfile:
12
+
13
+ ```ruby
14
+ gem "rockauto_api"
15
+ ```
16
+
17
+ Then:
18
+
19
+ ```
20
+ bundle install
21
+ ```
22
+
23
+ Or install directly:
24
+
25
+ ```
26
+ gem install rockauto_api
27
+ ```
28
+
29
+ ---
30
+
31
+ ## Quick Start
32
+
33
+ ```ruby
34
+ require "rockauto_api"
35
+
36
+ client = RockautoApi::Client.new
37
+
38
+ # Browse what RockAuto sells
39
+ makes = client.get_makes
40
+ makes.makes #=> ["ACURA", "AUDI", "BMW", ...]
41
+
42
+ years = client.get_years_for_make("AUDI")
43
+ years.years #=> [1980, 1981, ..., 2026]
44
+
45
+ models = client.get_models_for_make_year("AUDI", 2019)
46
+ models.models #=> ["A4", "A5", "A6", "A7", ...]
47
+
48
+ engines = client.get_engines_for_vehicle("AUDI", 2019, "A4")
49
+ engines.engines #=> [Engine(description: "2.0L L4 Turbocharged", carcode: "3443561"), ...]
50
+
51
+ # Get part categories for a vehicle
52
+ categories = client.get_part_categories("AUDI", 2019, "A4", "3443561")
53
+ categories.categories #=> [PartCategory(name: "Belt Drive"), ...]
54
+
55
+ # Get parts in a category
56
+ parts = client.get_parts_by_category("AUDI", 2019, "A4", "3443561", "Belt Drive")
57
+ parts.parts #=> [PartInfo, ...]
58
+ ```
59
+
60
+ ---
61
+
62
+ ## All Endpoints
63
+
64
+ ### Vehicle Catalog
65
+
66
+ Browse the vehicle catalog to find makes, years, models, and engines.
67
+
68
+ ```ruby
69
+ # List all makes
70
+ makes = client.get_makes
71
+ makes.makes #=> ["ACURA", "AUDI", "BMW", ...]
72
+ makes.count #=> Integer
73
+
74
+ # List years for a make
75
+ years = client.get_years_for_make("ACURA")
76
+ years.make #=> "ACURA"
77
+ years.years #=> [1986, 1987, ..., 2026]
78
+ years.count #=> Integer
79
+
80
+ # List models for a make and year
81
+ models = client.get_models_for_make_year("ACURA", 2020)
82
+ models.make #=> "ACURA"
83
+ models.year #=> 2020
84
+ models.models #=> ["MDX", "TLX", ...]
85
+ models.count #=> Integer
86
+
87
+ # List engines for a specific vehicle
88
+ engines = client.get_engines_for_vehicle("ACURA", 2020, "MDX")
89
+ engines.make #=> "ACURA"
90
+ engines.year #=> 2020
91
+ engines.model #=> "MDX"
92
+ engines.engines #=> [Engine(description, carcode, href), ...]
93
+ engines.count #=> Integer
94
+ ```
95
+
96
+ ### Part Categories
97
+
98
+ Get the part categories available for a specific vehicle, and parts within a category.
99
+
100
+ ```ruby
101
+ # Get all part categories for a vehicle
102
+ categories = client.get_part_categories("ACURA", 2020, "MDX", "3444459")
103
+ categories.make #=> "ACURA"
104
+ categories.year #=> 2020
105
+ categories.model #=> "MDX"
106
+ categories.carcode #=> "3444459"
107
+ categories.categories #=> [PartCategory(name, group_name, href), ...]
108
+ categories.count #=> Integer
109
+
110
+ # Get parts in a category
111
+ parts = client.get_parts_by_category("ACURA", 2020, "MDX", "3444459", "Brake & Wheel Hub")
112
+ parts.make #=> "ACURA"
113
+ parts.year #=> 2020
114
+ parts.model #=> "MDX"
115
+ parts.carcode #=> "3444459"
116
+ parts.category #=> "Brake & Wheel Hub"
117
+ parts.parts #=> [PartInfo, ...]
118
+ parts.count #=> Integer
119
+ ```
120
+
121
+ ### Part Search
122
+
123
+ Search for parts by number, name, or browse available manufacturers, groups, and types.
124
+
125
+ ```ruby
126
+ # Browse available manufacturers (cached for 24 hours)
127
+ manufacturers = client.get_manufacturers
128
+ manufacturers.manufacturers #=> [PartSearchOption(value, text), ...]
129
+ manufacturers.lookup("Bosch") #=> PartSearchOption(value: "128", text: "Bosch")
130
+
131
+ # Browse part groups (cached for 24 hours)
132
+ groups = client.get_part_groups
133
+ groups.part_groups #=> [PartSearchOption(value, text), ...]
134
+
135
+ # Browse part types (cached for 24 hours)
136
+ types = client.get_part_types
137
+ types.part_types #=> [PartSearchOption(value, text), ...]
138
+
139
+ # Search parts by number
140
+ results = client.search_parts_by_number("FG0326")
141
+ results.parts #=> [PartInfo, ...]
142
+ results.count #=> Integer
143
+ results.search_term #=> "FG0326"
144
+ results.manufacturer #=> "All"
145
+ results.part_group #=> "All"
146
+
147
+ # Search with filters
148
+ results = client.search_parts_by_number(
149
+ "FG0326",
150
+ manufacturer: "Bosch",
151
+ part_group: "Brakes",
152
+ part_type: "Pads"
153
+ )
154
+
155
+ # Include fitment data (triggers additional API call per part)
156
+ results = client.search_parts_by_number("FG0326", include_fitments: true)
157
+ results.parts.first.buyers_guide #=> BuyersGuideResult
158
+
159
+ # Look up what a part is called
160
+ results = client.what_is_part_called("brake pad")
161
+ results.results #=> [WhatIsPartCalledResult(main_category, subcategory, full_path), ...]
162
+ ```
163
+
164
+ ### Fitment / Buyers Guide
165
+
166
+ Get vehicle fitment information for a specific part.
167
+
168
+ ```ruby
169
+ fitment = client.get_fitment_for_part(listing_data)
170
+ # listing_data comes from PartInfo#listing_data or can be constructed manually
171
+ fitment.part_number #=> "FG0326"
172
+ fitment.brand #=> "Bosch"
173
+ fitment.fitments #=> [FitmentInfo(year, make, model, engine, ...), ...]
174
+ fitment.count #=> Integer
175
+ ```
176
+
177
+ ### Order Status
178
+
179
+ Look up order status and request order lists (no login required).
180
+
181
+ ```ruby
182
+ # Look up an order by email and order number
183
+ result = client.lookup_order_status("you@example.com", "RA-123456")
184
+ result.success #=> true/false
185
+ result.order #=> OrderStatus (if found)
186
+ result.error #=> OrderStatusError (if not found)
187
+
188
+ # Order status details
189
+ order = result.order
190
+ order.order_number #=> "RA-123456"
191
+ order.order_date #=> "2024-01-15"
192
+ order.status #=> "Shipped"
193
+ order.items #=> [OrderItem(part_number, description, quantity, ...), ...]
194
+ order.billing #=> BillingInfo(subtotal, shipping_cost, tax, total, ...)
195
+ order.shipping #=> ShippingInfo(method, carrier, tracking_number, ...)
196
+ order.notes #=> String
197
+ order.return_eligibility #=> String
198
+
199
+ # Request RockAuto to email you a list of your orders
200
+ client.request_order_list(:email, "you@example.com") #=> true
201
+
202
+ # Request via SMS
203
+ client.request_order_list(:sms, "+15551234567") #=> true
204
+ ```
205
+
206
+ ### Account (Authenticated)
207
+
208
+ Requires logging in with valid RockAuto credentials.
209
+
210
+ ```ruby
211
+ # Configure credentials globally
212
+ RockautoApi.configure do |config|
213
+ config.credentials = { email: "you@example.com", password: "your_password" }
214
+ end
215
+
216
+ # Or pass credentials per-session
217
+ client = RockautoApi::Client.new
218
+ client.login("you@example.com", "your_password")
219
+
220
+ # Check login status
221
+ client.authenticated? #=> true/false
222
+
223
+ # Get saved addresses
224
+ addresses = client.get_saved_addresses
225
+ addresses.addresses #=> [SavedAddress(name, full_name, address_line1, city, state, ...), ...]
226
+ addresses.count #=> Integer
227
+ addresses.has_default #=> true/false
228
+
229
+ # Get saved vehicles
230
+ vehicles = client.get_saved_vehicles
231
+ vehicles.vehicles #=> [SavedVehicle(year, make, model, engine, carcode, ...), ...]
232
+ vehicles.count #=> Integer
233
+
234
+ # Get order history
235
+ history = client.get_order_history
236
+ history.orders #=> [OrderHistoryItem(order_number, date, status, total, vehicle), ...]
237
+ history.count #=> Integer
238
+ history.search_time #=> "2026-06-28T..."
239
+
240
+ # Get full account activity (addresses + vehicles)
241
+ activity = client.get_account_activity
242
+ activity.saved_addresses #=> SavedAddressesResult
243
+ activity.saved_vehicles #=> SavedVehiclesResult
244
+
245
+ # Add an external order to your account
246
+ client.add_external_order("you@example.com", "RA-123456") #=> true
247
+
248
+ # Logout
249
+ client.logout #=> true
250
+ client.authenticated? #=> false
251
+ ```
252
+
253
+ ### Tools
254
+
255
+ Browse RockAuto's tool catalog.
256
+
257
+ ```ruby
258
+ # Get tool categories
259
+ categories = client.get_tool_categories
260
+ categories.categories #=> [ToolCategory(name, group_name, href, level), ...]
261
+ categories.count #=> Integer
262
+
263
+ # Get tools in a category
264
+ tools = client.get_tools_by_category("/en/tools/?parttype=260")
265
+ tools.tools #=> [ToolInfo(name, part_number, brand, description, ...), ...]
266
+ tools.count #=> Integer
267
+ tools.category #=> "?parttype=260"
268
+ ```
269
+
270
+ ---
271
+
272
+ ## Configuration
273
+
274
+ ```ruby
275
+ RockautoApi.configure do |config|
276
+ config.default_mobile = true # Use mobile site headers (slimmer HTML)
277
+ config.request_timeout = 30 # HTTP timeout in seconds
278
+ config.credentials = { email: "me@example.com", password: "s3cret" }
279
+ config.cache = Rails.cache # Use Rails cache for 24h/7d TTLs
280
+ end
281
+ ```
282
+
283
+ ---
284
+
285
+ ## Rails Integration
286
+
287
+ The gem includes a Railtie that automatically hooks into `Rails.cache`. No extra setup needed:
288
+
289
+ ```ruby
290
+ # Gemfile
291
+ gem "rockauto_api"
292
+
293
+ # config/initializers/rockauto_api.rb
294
+ RockautoApi.configure do |config|
295
+ config.default_mobile = false
296
+ end
297
+
298
+ # app/models/part_lookup.rb
299
+ class PartLookup
300
+ def search(query)
301
+ client = RockautoApi::Client.new
302
+ client.search_parts_by_number(query)
303
+ end
304
+ end
305
+ ```
306
+
307
+ ---
308
+
309
+ ## Error Handling
310
+
311
+ All errors inherit from `RockautoApi::Error`:
312
+
313
+ | Error Class | When It's Raised |
314
+ |---|---|
315
+ | `RockautoApi::AuthenticationError` | Calling authenticated methods without logging in |
316
+ | `RockautoApi::NetworkError` | HTTP request fails (timeout, connection error) |
317
+ | `RockautoApi::CaptchaError` | RockAuto returns a CAPTCHA challenge |
318
+ | `RockautoApi::ParseError` | Failed to parse HTML response |
319
+ | `RockautoApi::NotFoundError` | Resource not found |
320
+
321
+ Example:
322
+
323
+ ```ruby
324
+ begin
325
+ client.get_saved_addresses
326
+ rescue RockautoApi::AuthenticationError => e
327
+ puts "Please login first: #{e.message}"
328
+ rescue RockautoApi::NetworkError => e
329
+ puts "Network issue: #{e.message}"
330
+ end
331
+ ```
332
+
333
+ ---
334
+
335
+ ## Development
336
+
337
+ ```
338
+ git clone https://github.com/bendangelo/rockauto_api
339
+ cd rockauto_api
340
+ bin/setup
341
+ ```
342
+
343
+ Run the test suite:
344
+
345
+ ```
346
+ bundle exec rspec
347
+ ```
348
+
349
+ Tests use [VCR](https://github.com/vcr/vcr) to record and replay HTTP interactions. To re-record cassettes with live data:
350
+
351
+ ```
352
+ rm -rf spec/cassettes/
353
+ bundle exec rspec
354
+ ```
355
+
356
+ New cassettes will be created recording real HTTP requests. Sensitive data (credentials, cookies) is automatically filtered.
357
+
358
+ ---
359
+
360
+ ## Contributing
361
+
362
+ 1. Fork the repository
363
+ 2. Create a feature branch (`git checkout -b feature/my-feature`)
364
+ 3. Commit your changes (`git commit -am "Add my feature"`)
365
+ 4. Push to the branch (`git push origin feature/my-feature`)
366
+ 5. Open a Pull Request
367
+
368
+ Please include tests for any new functionality and ensure the full suite passes.
369
+
370
+ ---
371
+
372
+ ## License
373
+
374
+ Apache 2.0. See [LICENSE](LICENSE).
375
+
376
+ ---
377
+
378
+ ## Disclaimer
379
+
380
+ This software is provided for **educational and research purposes only**. It is not affiliated with, endorsed by, or officially connected to RockAuto LLC. Automated access to RockAuto.com may violate their Terms of Service. Users are solely responsible for ensuring their use complies with all applicable terms and laws. The authors assume no liability for any misuse.
@@ -68,6 +68,7 @@ module RockautoApi
68
68
 
69
69
  resp = @conn.get("/")
70
70
  @nck_token = Parsers::HtmlHelpers.extract_javascript_variable(resp.body, "_nck")
71
+ @nck_token ||= Parsers::HtmlHelpers.extract_csrf_token(resp.body, "_nck")
71
72
  @jnck_token = @nck_token ? CGI.escape(@nck_token) : nil
72
73
  @session_initialized = true
73
74
  end
@@ -126,23 +127,50 @@ module RockautoApi
126
127
  end
127
128
 
128
129
  if make
129
- payload = {
130
- "jsn" => {
131
- "make" => make,
132
- "nodetype" => "make",
133
- "loaded" => false,
134
- "expand_after_load" => true,
135
- "fetching" => true,
136
- "max_group_index" => 363,
137
- "mkt_US" => true,
138
- "mkt_CA" => false,
139
- "mkt_MX" => false
140
- }
141
- }
130
+ payload = navnode_fetch_payload(
131
+ make: make,
132
+ nodetype: "make",
133
+ label: make,
134
+ href: "#{BASE_URL}/en/catalog/#{make.downcase}"
135
+ )
142
136
  call_catalog_api("navnode_fetch", payload)
143
137
  end
144
138
  end
145
139
 
140
+ def navnode_fetch_payload(make:, nodetype:, label: nil, href: nil, year: nil, model: nil, carcode: nil)
141
+ jsn = {
142
+ "tab" => "catalog",
143
+ "make" => make,
144
+ "nodetype" => nodetype,
145
+ "jsdata" => {
146
+ "markets" => [
147
+ {"c" => "US", "y" => "Y", "i" => "Y"},
148
+ {"c" => "CA", "y" => "Y", "i" => "Y"},
149
+ {"c" => "MX", "y" => "Y", "i" => "Y"}
150
+ ],
151
+ "mktlist" => "US,CA,MX",
152
+ "showForMarkets" => {"US" => true, "CA" => true, "MX" => true},
153
+ "importanceByMarket" => {"US" => "Y", "CA" => "Y", "MX" => "Y"},
154
+ "Show" => 1
155
+ },
156
+ "loaded" => false,
157
+ "expand_after_load" => true,
158
+ "fetching" => true
159
+ }
160
+
161
+ jsn["year"] = year.to_s if year
162
+ jsn["model"] = model if model
163
+ jsn["carcode"] = carcode if carcode
164
+ jsn["label"] = label if label
165
+ jsn["href"] = href if href
166
+ jsn["labelset"] = true if label || href
167
+ jsn["jump_to_after_expand"] = true if nodetype == "make"
168
+ jsn["dont_change_url"] = true if nodetype == "make"
169
+ jsn["has_more_auto_open_steps"] = true if nodetype == "make"
170
+
171
+ { "jsn" => jsn, "max_group_index" => 388 }
172
+ end
173
+
146
174
  def get(path)
147
175
  init_session!
148
176
  resp = @conn.get(path)
@@ -8,23 +8,41 @@ module RockautoApi
8
8
  ORDER_HISTORY_URL = "/en/orderhistory/"
9
9
 
10
10
  def login(email, password)
11
- payload = {
12
- "jsn" => {
13
- "email" => email,
14
- "password" => password,
15
- "keep_me_logged_in" => false
16
- }
11
+ init_session!
12
+
13
+ form_data = {
14
+ "loginaction" => "login",
15
+ "accountemail" => email,
16
+ "captchacode" => "",
17
+ "passworddecoy" => "",
18
+ "password" => password,
19
+ "passwordconfirmdecoy" => "",
20
+ "passwordconfirm" => "",
21
+ "keepsignin" => "false",
22
+ "async" => "1",
23
+ "accountlogin_php" => "1"
17
24
  }
18
25
 
19
- response = call_catalog_api("login", payload)
20
- @authenticated = response["success"] == true || response.dig("response", "success") == true
26
+ resp = account_api_post(form_data)
27
+
28
+ result = JSON.parse(resp.body)
29
+ @authenticated = result["message"]&.include?("Successful") || false
21
30
  @authenticated
22
- rescue NetworkError
31
+ rescue StandardError
23
32
  @authenticated = false
24
33
  end
25
34
 
26
35
  def logout
27
- response = call_catalog_api("logout", {})
36
+ init_session!
37
+
38
+ form_data = {
39
+ "loginaction" => "logout",
40
+ "async" => "1",
41
+ "accountlogin_php" => "1"
42
+ }
43
+
44
+ account_api_post(form_data)
45
+
28
46
  @authenticated = false
29
47
  true
30
48
  rescue StandardError
@@ -154,6 +172,23 @@ module RockautoApi
154
172
  def require_authentication!
155
173
  raise AuthenticationError, "Not authenticated. Call login(email, password) first." unless @authenticated
156
174
  end
175
+
176
+ def account_api_post(form_data)
177
+ Faraday.new(url: Client::BASE_URL) do |f|
178
+ f.request :url_encoded
179
+ f.use :cookie_jar
180
+ f.adapter Faraday.default_adapter
181
+ f.options.timeout = RockautoApi.configuration&.request_timeout || 30
182
+ @conn.headers["Cookie"].to_s.split(";").each do |cookie|
183
+ name, val = cookie.strip.split("=", 2)
184
+ f.headers["Cookie"] = "#{f.headers['Cookie']}; #{name}=#{val}" if name && val
185
+ end
186
+ f.headers["User-Agent"] = Client::MOBILE_HEADERS["User-Agent"]
187
+ f.headers["Referer"] = "#{Client::BASE_URL}/"
188
+ f.headers["Content-Type"] = "application/x-www-form-urlencoded; charset=UTF-8"
189
+ f.headers["X-Requested-With"] = "XMLHttpRequest"
190
+ end.post("/catalog/catalogapi.php", form_data)
191
+ end
157
192
  end
158
193
  end
159
194
  end
@@ -4,22 +4,15 @@ module RockautoApi
4
4
  module Endpoints
5
5
  module PartCategories
6
6
  def get_part_categories(make, year, model, carcode)
7
- payload = {
8
- "jsn" => {
9
- "make" => make,
10
- "year" => year.to_s,
11
- "model" => model,
12
- "carcode" => carcode,
13
- "nodetype" => "model",
14
- "loaded" => false,
15
- "expand_after_load" => true,
16
- "fetching" => true,
17
- "max_group_index" => 0,
18
- "mkt_US" => true,
19
- "mkt_CA" => false,
20
- "mkt_MX" => false
21
- }
22
- }
7
+ payload = navnode_fetch_payload(
8
+ make: make,
9
+ nodetype: "model",
10
+ label: model,
11
+ href: "#{Client::BASE_URL}/en/catalog/#{make.downcase},#{year},#{model.downcase}",
12
+ year: year,
13
+ model: model,
14
+ carcode: carcode
15
+ )
23
16
 
24
17
  response = call_catalog_api("navnode_fetch", payload)
25
18
  html = response.dig("html_fill_sections", "navchildren[]") || ""
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RockautoApi
4
- VERSION = "0.1.1"
4
+ VERSION = "0.2.0"
5
5
  end
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.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben D'Angelo
@@ -141,6 +141,7 @@ executables: []
141
141
  extensions: []
142
142
  extra_rdoc_files: []
143
143
  files:
144
+ - README.md
144
145
  - lib/rockauto_api.rb
145
146
  - lib/rockauto_api/cache.rb
146
147
  - lib/rockauto_api/client.rb