ruze 0.1.2 → 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 +4 -4
- data/.env.test +3 -0
- data/.github/workflows/main.yml +1 -1
- data/.rubocop.yml +4 -1
- data/Gemfile +3 -0
- data/LICENSE.txt +1 -1
- data/README.md +52 -14
- data/bin/console +16 -6
- data/lib/ruze/car.rb +2 -2
- data/lib/ruze/device.rb +30 -0
- data/lib/ruze/errors.rb +8 -0
- data/lib/ruze/gigya.rb +192 -33
- data/lib/ruze/kamereon.rb +1 -1
- data/lib/ruze/version.rb +1 -1
- data/lib/ruze.rb +1 -4
- data/ruze.gemspec +1 -1
- metadata +6 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1fa802b8dceb6e72c4e9c04b3a0ee3fd0a1c70d2063b0ffae6d46398057b317d
|
|
4
|
+
data.tar.gz: 9976f664f420a5b351023f3d4bb8ef44947849c752aebbc0c0d8de402dd0a507
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5b5e04039d1562360d0f237179555b5660e8f0d5369269723f343d689dd9f747c2ff184a6bd68331fa493bb9ae5f75e7801fdf55b8924928efe7fea1525f5691
|
|
7
|
+
data.tar.gz: 708613986c1f2f35bd215cc2a4099bc2487d7f456200643c8fba86ffef83b132e30a2cf2878337a15d0eb3236d8172a03bb97489f34abb152d2bbb55c7ecf08d
|
data/.env.test
CHANGED
data/.github/workflows/main.yml
CHANGED
data/.rubocop.yml
CHANGED
data/Gemfile
CHANGED
data/LICENSE.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
The MIT License (MIT)
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2021-
|
|
3
|
+
Copyright (c) 2021-2026 Georg Ledermann
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
data/README.md
CHANGED
|
@@ -38,27 +38,25 @@ export KAMEREON_API_KEY=...
|
|
|
38
38
|
## Usage
|
|
39
39
|
|
|
40
40
|
```ruby
|
|
41
|
-
car = Ruze::Car.new('
|
|
41
|
+
car = Ruze::Car.new('john@example.com', 'my-password')
|
|
42
42
|
|
|
43
43
|
car.battery
|
|
44
44
|
# {
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
#
|
|
52
|
-
# "chargingStatus" => 0.0,
|
|
53
|
-
# "chargingRemainingTime" => 55,
|
|
54
|
-
# "chargingInstantaneousPower" => 0.0
|
|
45
|
+
# "timestamp" => "2026-03-13T09:59:12Z",
|
|
46
|
+
# "batteryLevel" => 66,
|
|
47
|
+
# "batteryAutonomy" => 194,
|
|
48
|
+
# "plugStatus" => 0,
|
|
49
|
+
# "chargingStatus" => 0.0,
|
|
50
|
+
# "chargingRemainingTime" => 55,
|
|
51
|
+
# "chargingRemainingTimeLastUpdateDateTime" => "2026-03-13T09:30:00Z"
|
|
55
52
|
# }
|
|
56
53
|
|
|
57
54
|
car.cockpit
|
|
58
55
|
# {
|
|
59
56
|
# "fuelAutonomy" => 0.0,
|
|
60
57
|
# "fuelQuantity" => 0.0,
|
|
61
|
-
# "totalMileage" => 12345.67
|
|
58
|
+
# "totalMileage" => 12345.67,
|
|
59
|
+
# "timestamp" => "2026-03-13T09:59:12Z"
|
|
62
60
|
# }
|
|
63
61
|
|
|
64
62
|
car.location
|
|
@@ -66,11 +64,51 @@ car.location
|
|
|
66
64
|
# "gpsDirection" => nil,
|
|
67
65
|
# "gpsLatitude" => 50.12345678,
|
|
68
66
|
# "gpsLongitude" => 6.12345678,
|
|
69
|
-
# "lastUpdateTime" => "
|
|
67
|
+
# "lastUpdateTime" => "2026-03-12T11:43:18Z"
|
|
70
68
|
# }
|
|
71
69
|
```
|
|
72
70
|
|
|
73
71
|
|
|
72
|
+
## Two-factor authentication
|
|
73
|
+
|
|
74
|
+
Renault enforces two-factor authentication (2FA) on Gigya accounts, so a
|
|
75
|
+
password alone is no longer enough to log in. RuZE handles this by establishing
|
|
76
|
+
a *trusted device* once: after a single email verification, Renault remembers
|
|
77
|
+
the device for about 30 days, during which logins succeed without a prompt.
|
|
78
|
+
|
|
79
|
+
### One-time setup
|
|
80
|
+
|
|
81
|
+
Trigger a verification code, confirm it with the 6-digit code Renault emails to
|
|
82
|
+
your account, then read the resulting trusted-device pair:
|
|
83
|
+
|
|
84
|
+
```ruby
|
|
85
|
+
gigya = Ruze::Gigya.new('john@example.com', 'my-password')
|
|
86
|
+
|
|
87
|
+
gigya.request_verification_code # emails a 6-digit code (returns nil if none is needed)
|
|
88
|
+
|
|
89
|
+
gigya.verify_code('123456')
|
|
90
|
+
|
|
91
|
+
gmid = gigya.device.gmid
|
|
92
|
+
ucid = gigya.device.ucid
|
|
93
|
+
# Store this gmid/ucid pair somewhere durable (env vars, a file, a database).
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`Ruze::Device` keeps the pair in memory only, so persisting it is up to you. The
|
|
97
|
+
pair stays valid across renewals, so you store it once.
|
|
98
|
+
|
|
99
|
+
### Normal usage
|
|
100
|
+
|
|
101
|
+
Seed a device with your stored pair and `Ruze::Car` logs in without prompting:
|
|
102
|
+
|
|
103
|
+
```ruby
|
|
104
|
+
device = Ruze::Device.new(gmid: gmid, ucid: ucid)
|
|
105
|
+
car = Ruze::Car.new('john@example.com', 'my-password', device: device)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
When the device is missing or has expired, login raises
|
|
109
|
+
`Ruze::TwoFactorRequired` — repeat the one-time setup to renew it.
|
|
110
|
+
|
|
111
|
+
|
|
74
112
|
## Background
|
|
75
113
|
|
|
76
114
|
Thanks to James Muscat (@jamesremuscat) for making PyZE, the Python client for Renault ZE API:
|
|
@@ -106,7 +144,7 @@ This project is not affiliated with, endorsed by, or connected to Renault. I acc
|
|
|
106
144
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
107
145
|
|
|
108
146
|
|
|
109
|
-
Copyright (c) 2021-
|
|
147
|
+
Copyright (c) 2021-2026 Georg Ledermann, released under the AGPL-3.0 License
|
|
110
148
|
|
|
111
149
|
## Code of Conduct
|
|
112
150
|
|
data/bin/console
CHANGED
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
|
|
3
3
|
require 'bundler/setup'
|
|
4
|
+
require 'dotenv/load'
|
|
4
5
|
require 'ruze'
|
|
5
6
|
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
7
|
+
# Convenience for experimenting against the real API, using the credentials and
|
|
8
|
+
# trusted device from your .env (RENAULT_EMAIL, RENAULT_PASSWORD, GIGYA_API_KEY,
|
|
9
|
+
# KAMEREON_API_KEY, RUZE_GMID, RUZE_UCID):
|
|
10
|
+
#
|
|
11
|
+
# car.battery
|
|
12
|
+
# car.cockpit
|
|
13
|
+
# car.location
|
|
14
|
+
#
|
|
15
|
+
def car
|
|
16
|
+
Ruze::Car.new(
|
|
17
|
+
ENV.fetch('RENAULT_EMAIL'),
|
|
18
|
+
ENV.fetch('RENAULT_PASSWORD'),
|
|
19
|
+
device: Ruze::Device.new(gmid: ENV.fetch('RUZE_GMID', nil), ucid: ENV.fetch('RUZE_UCID', nil))
|
|
20
|
+
)
|
|
21
|
+
end
|
|
12
22
|
|
|
13
23
|
require 'irb'
|
|
14
24
|
IRB.start(__FILE__)
|
data/lib/ruze/car.rb
CHANGED
|
@@ -3,8 +3,8 @@ require_relative 'kamereon'
|
|
|
3
3
|
|
|
4
4
|
module Ruze
|
|
5
5
|
class Car
|
|
6
|
-
def initialize(email, password, vin = nil)
|
|
7
|
-
gigya = Ruze::Gigya.new(email, password)
|
|
6
|
+
def initialize(email, password, vin = nil, device: Ruze::Device.new)
|
|
7
|
+
gigya = Ruze::Gigya.new(email, password, device: device)
|
|
8
8
|
@kamereon = Ruze::Kamereon.new(gigya.person_id, gigya.jwt, vin)
|
|
9
9
|
end
|
|
10
10
|
|
data/lib/ruze/device.rb
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module Ruze
|
|
2
|
+
# Holds the Gigya trusted-device identity (gmid/ucid) that lets us skip the
|
|
3
|
+
# two-factor prompt on subsequent logins.
|
|
4
|
+
#
|
|
5
|
+
# This default implementation keeps the pair in memory only. For headless
|
|
6
|
+
# operation across process restarts, seed it from durable storage and persist
|
|
7
|
+
# the pair (see #gmid/#ucid) after a successful bootstrap:
|
|
8
|
+
#
|
|
9
|
+
# device = Ruze::Device.new(gmid: stored_gmid, ucid: stored_ucid)
|
|
10
|
+
# Ruze::Car.new(email, password, device: device)
|
|
11
|
+
class Device
|
|
12
|
+
def initialize(gmid: nil, ucid: nil)
|
|
13
|
+
@gmid = gmid
|
|
14
|
+
@ucid = ucid
|
|
15
|
+
end
|
|
16
|
+
attr_reader :gmid, :ucid
|
|
17
|
+
|
|
18
|
+
# Returns { gmid:, ucid: } or nil when no (complete) device is available.
|
|
19
|
+
def load
|
|
20
|
+
return nil if gmid.to_s.empty? || ucid.to_s.empty?
|
|
21
|
+
|
|
22
|
+
{ gmid: gmid, ucid: ucid }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def save(gmid:, ucid:)
|
|
26
|
+
@gmid = gmid
|
|
27
|
+
@ucid = ucid
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
data/lib/ruze/errors.rb
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
module Ruze
|
|
2
|
+
class Error < StandardError; end
|
|
3
|
+
|
|
4
|
+
# Raised when Renault/Gigya requires two-factor authentication and no trusted
|
|
5
|
+
# device is available. Run the bootstrap flow (request_verification_code +
|
|
6
|
+
# verify_code) to establish a trusted device.
|
|
7
|
+
class TwoFactorRequired < Error; end
|
|
8
|
+
end
|
data/lib/ruze/gigya.rb
CHANGED
|
@@ -1,68 +1,227 @@
|
|
|
1
1
|
require 'net/http'
|
|
2
2
|
require 'json'
|
|
3
|
+
require_relative 'errors'
|
|
4
|
+
require_relative 'device'
|
|
3
5
|
|
|
4
6
|
module Ruze
|
|
5
7
|
class Gigya
|
|
6
|
-
BASE_URL
|
|
8
|
+
BASE_URL = 'https://accounts.eu1.gigya.com'.freeze
|
|
9
|
+
SOCIALIZE_URL = 'https://socialize.eu1.gigya.com'.freeze
|
|
7
10
|
|
|
8
|
-
def initialize(email, password)
|
|
11
|
+
def initialize(email, password, device: Device.new)
|
|
9
12
|
raise ArgumentError unless email.is_a?(String) && password.is_a?(String)
|
|
10
13
|
|
|
11
14
|
@email = email
|
|
12
15
|
@password = password
|
|
16
|
+
@device = device
|
|
13
17
|
end
|
|
14
|
-
attr_reader :email, :password
|
|
18
|
+
attr_reader :email, :password, :device
|
|
15
19
|
|
|
16
20
|
def person_id
|
|
17
|
-
@person_id ||=
|
|
18
|
-
|
|
19
|
-
'
|
|
20
|
-
|
|
21
|
-
), keys: %w[data personId]
|
|
21
|
+
@person_id ||= dig_from post(
|
|
22
|
+
"#{BASE_URL}/accounts.getAccountInfo",
|
|
23
|
+
{ 'apiKey' => api_key, 'login_token' => session_cookie_value }
|
|
24
|
+
), label: 'person_id', keys: %w[data personId]
|
|
22
25
|
end
|
|
23
26
|
|
|
24
27
|
def jwt
|
|
25
|
-
@jwt ||=
|
|
26
|
-
|
|
27
|
-
'
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
), keys: %w[id_token]
|
|
28
|
+
@jwt ||= dig_from post(
|
|
29
|
+
"#{BASE_URL}/accounts.getJWT",
|
|
30
|
+
{ 'apiKey' => api_key,
|
|
31
|
+
'login_token' => session_cookie_value,
|
|
32
|
+
'fields' => 'data.personId,data.gigyaDataCenter',
|
|
33
|
+
'expiration' => 900 }
|
|
34
|
+
), label: 'jwt', keys: %w[id_token]
|
|
32
35
|
end
|
|
33
36
|
|
|
37
|
+
# Logs in using the trusted device and returns the session cookie value.
|
|
38
|
+
# Raises TwoFactorRequired when Renault demands a fresh 2FA verification.
|
|
34
39
|
def session_cookie_value
|
|
35
|
-
@session_cookie_value ||=
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
@session_cookie_value ||= login
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# --- Two-factor bootstrap ------------------------------------------------
|
|
44
|
+
#
|
|
45
|
+
# The Gigya 2FA trusted-device flow below (initTFA -> getEmails ->
|
|
46
|
+
# sendVerificationCode -> completeVerification -> finalizeTFA -> re-login)
|
|
47
|
+
# was reverse-engineered by the renault-api community:
|
|
48
|
+
# https://github.com/hacf-fr/renault-api/issues/2132
|
|
49
|
+
#
|
|
50
|
+
# Run once interactively to establish a trusted device:
|
|
51
|
+
#
|
|
52
|
+
# gigya = Ruze::Gigya.new(email, password)
|
|
53
|
+
# puts "Code sent to #{gigya.request_verification_code}"
|
|
54
|
+
# gigya.verify_code(gets.strip)
|
|
55
|
+
#
|
|
56
|
+
# After that, session_cookie_value works headlessly for ~30 days.
|
|
57
|
+
|
|
58
|
+
# Triggers the email verification code and returns the obfuscated address it
|
|
59
|
+
# was sent to. Returns nil when no verification is needed -- either the
|
|
60
|
+
# account has no 2FA or this device is already trusted.
|
|
61
|
+
def request_verification_code
|
|
62
|
+
ids = device_ids
|
|
63
|
+
reg_token = login_reg_token(ids)
|
|
64
|
+
return nil unless reg_token
|
|
65
|
+
|
|
66
|
+
assertion = init_tfa(reg_token, ids)
|
|
67
|
+
mail = first_email(assertion, ids)
|
|
68
|
+
phv_token = send_verification_code(mail['id'], assertion, ids)
|
|
69
|
+
|
|
70
|
+
@bootstrap = { ids: ids, reg_token: reg_token, assertion: assertion, phv_token: phv_token }
|
|
71
|
+
mail['obfuscated']
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Completes the 2FA flow with the emailed code, finalizes the device as
|
|
75
|
+
# trusted (remembered for ~30 days) and persists it.
|
|
76
|
+
def verify_code(code)
|
|
77
|
+
raise Error, 'Call request_verification_code before verify_code' unless @bootstrap
|
|
78
|
+
|
|
79
|
+
ids = @bootstrap[:ids]
|
|
80
|
+
provider_assertion = complete_verification(code, ids)
|
|
81
|
+
finalize_tfa(provider_assertion, ids)
|
|
82
|
+
|
|
83
|
+
@session_cookie_value = login(ids)
|
|
84
|
+
device.save(gmid: ids[:gmid], ucid: ids[:ucid])
|
|
85
|
+
@session_cookie_value
|
|
41
86
|
end
|
|
42
87
|
|
|
43
88
|
private
|
|
44
89
|
|
|
90
|
+
def login(ids = device_ids)
|
|
91
|
+
json = parse post(
|
|
92
|
+
"#{BASE_URL}/accounts.login",
|
|
93
|
+
login_params(ids), cookie: cookie(ids)
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
case json['errorCode']
|
|
97
|
+
when 0
|
|
98
|
+
json.dig('sessionInfo', 'cookieValue')
|
|
99
|
+
when 403_101
|
|
100
|
+
raise TwoFactorRequired,
|
|
101
|
+
'Two-factor authentication required. Run the bootstrap flow to trust this device.'
|
|
102
|
+
else
|
|
103
|
+
raise Error, "Error in session_cookie_value: #{error_detail(json)}"
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Like #login but returns the regToken from a pending-2FA response instead of
|
|
108
|
+
# raising. Returns nil when the account is already logged in without 2FA.
|
|
109
|
+
def login_reg_token(ids)
|
|
110
|
+
json = parse post(
|
|
111
|
+
"#{BASE_URL}/accounts.login",
|
|
112
|
+
login_params(ids), cookie: cookie(ids)
|
|
113
|
+
)
|
|
114
|
+
return json['regToken'] if json['errorCode'] == 403_101
|
|
115
|
+
return nil if json['errorCode']&.zero?
|
|
116
|
+
|
|
117
|
+
raise Error, "Error in session_cookie_value: #{error_detail(json)}"
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def login_params(ids)
|
|
121
|
+
{
|
|
122
|
+
'apiKey' => api_key,
|
|
123
|
+
'loginID' => email,
|
|
124
|
+
'password' => password,
|
|
125
|
+
'gmid' => ids[:gmid],
|
|
126
|
+
'ucid' => ids[:ucid]
|
|
127
|
+
}
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def init_tfa(reg_token, ids)
|
|
131
|
+
dig_from post(
|
|
132
|
+
"#{BASE_URL}/accounts.tfa.initTFA",
|
|
133
|
+
{ 'apiKey' => api_key, 'regToken' => reg_token, 'provider' => 'gigyaEmail',
|
|
134
|
+
'mode' => 'verify', 'gmid' => ids[:gmid], 'ucid' => ids[:ucid] },
|
|
135
|
+
cookie: cookie(ids)
|
|
136
|
+
), label: 'init_tfa', keys: %w[gigyaAssertion]
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def first_email(assertion, ids)
|
|
140
|
+
emails = dig_from post(
|
|
141
|
+
"#{BASE_URL}/accounts.tfa.email.getEmails",
|
|
142
|
+
{ 'apiKey' => api_key, 'gigyaAssertion' => assertion, 'gmid' => ids[:gmid], 'ucid' => ids[:ucid] },
|
|
143
|
+
cookie: cookie(ids)
|
|
144
|
+
), label: 'get_emails', keys: %w[emails]
|
|
145
|
+
raise Error, 'No verified email address available for 2FA' if emails.nil? || emails.empty?
|
|
146
|
+
|
|
147
|
+
emails.first
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def send_verification_code(email_id, assertion, ids)
|
|
151
|
+
dig_from post(
|
|
152
|
+
"#{BASE_URL}/accounts.tfa.email.sendVerificationCode",
|
|
153
|
+
{ 'apiKey' => api_key, 'emailID' => email_id, 'gigyaAssertion' => assertion,
|
|
154
|
+
'lang' => 'en', 'gmid' => ids[:gmid], 'ucid' => ids[:ucid] },
|
|
155
|
+
cookie: cookie(ids)
|
|
156
|
+
), label: 'send_verification_code', keys: %w[phvToken]
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def complete_verification(code, ids)
|
|
160
|
+
dig_from post(
|
|
161
|
+
"#{BASE_URL}/accounts.tfa.email.completeVerification",
|
|
162
|
+
{ 'apiKey' => api_key, 'gigyaAssertion' => @bootstrap[:assertion],
|
|
163
|
+
'phvToken' => @bootstrap[:phv_token], 'code' => code,
|
|
164
|
+
'gmid' => ids[:gmid], 'ucid' => ids[:ucid] },
|
|
165
|
+
cookie: cookie(ids)
|
|
166
|
+
), label: 'complete_verification', keys: %w[providerAssertion]
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# tempDevice=false marks the device as remembered (no 2FA for ~30 days).
|
|
170
|
+
def finalize_tfa(provider_assertion, ids)
|
|
171
|
+
json = parse post(
|
|
172
|
+
"#{BASE_URL}/accounts.tfa.finalizeTFA",
|
|
173
|
+
{ 'apiKey' => api_key, 'gigyaAssertion' => @bootstrap[:assertion],
|
|
174
|
+
'providerAssertion' => provider_assertion, 'tempDevice' => 'false',
|
|
175
|
+
'regToken' => @bootstrap[:reg_token], 'gmid' => ids[:gmid], 'ucid' => ids[:ucid] },
|
|
176
|
+
cookie: cookie(ids)
|
|
177
|
+
)
|
|
178
|
+
raise Error, "Error in finalize_tfa: #{error_detail(json)}" unless json['errorCode']&.zero?
|
|
179
|
+
|
|
180
|
+
json
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def device_ids
|
|
184
|
+
@device_ids ||= device.load || fetch_ids
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def fetch_ids
|
|
188
|
+
json = parse post("#{SOCIALIZE_URL}/socialize.getIDs", { 'apiKey' => api_key })
|
|
189
|
+
raise Error, "Error in fetch_ids: #{error_detail(json)}" unless json['errorCode']&.zero?
|
|
190
|
+
|
|
191
|
+
{ gmid: json['gmid'], ucid: json['ucid'] }
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def cookie(ids)
|
|
195
|
+
"gmid=#{ids[:gmid]}; ucid=#{ids[:ucid]}"
|
|
196
|
+
end
|
|
197
|
+
|
|
45
198
|
def api_key
|
|
46
199
|
ENV.fetch('GIGYA_API_KEY')
|
|
47
200
|
end
|
|
48
201
|
|
|
49
|
-
def
|
|
50
|
-
URI(
|
|
202
|
+
def post(url, params, cookie: nil)
|
|
203
|
+
uri = URI(url)
|
|
204
|
+
request = Net::HTTP::Post.new(uri)
|
|
205
|
+
request.set_form_data(params.merge('format' => 'json'))
|
|
206
|
+
request['Cookie'] = cookie if cookie
|
|
207
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
|
|
208
|
+
http.request(request)
|
|
209
|
+
end
|
|
51
210
|
end
|
|
52
211
|
|
|
53
|
-
def
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
raise Error, "Error in #{caller}: #{response.message} (#{response.code})"
|
|
57
|
-
end
|
|
212
|
+
def parse(response)
|
|
213
|
+
JSON.parse(response.body)
|
|
214
|
+
end
|
|
58
215
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
raise Error, "Error in #{caller}: #{json['errorDetails']}"
|
|
63
|
-
end
|
|
216
|
+
def dig_from(response, label:, keys:)
|
|
217
|
+
json = parse(response)
|
|
218
|
+
raise Error, "Error in #{label}: #{error_detail(json)}" unless json['errorCode']&.zero?
|
|
64
219
|
|
|
65
220
|
json.dig(*keys)
|
|
66
221
|
end
|
|
222
|
+
|
|
223
|
+
def error_detail(json)
|
|
224
|
+
json['errorDetails'] || json['errorMessage'] || "errorCode #{json['errorCode']}"
|
|
225
|
+
end
|
|
67
226
|
end
|
|
68
227
|
end
|
data/lib/ruze/kamereon.rb
CHANGED
|
@@ -89,7 +89,7 @@ module Ruze
|
|
|
89
89
|
|
|
90
90
|
def return_from(response, keys:)
|
|
91
91
|
unless response.is_a?(Net::HTTPOK)
|
|
92
|
-
caller = caller_locations(1, 1)[0].
|
|
92
|
+
caller = caller_locations(1, 1)[0].base_label
|
|
93
93
|
raise Error, "Error in #{caller}: #{response.message} (#{response.code})"
|
|
94
94
|
end
|
|
95
95
|
|
data/lib/ruze/version.rb
CHANGED
data/lib/ruze.rb
CHANGED
data/ruze.gemspec
CHANGED
|
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
|
|
|
10
10
|
spec.description = 'Queries vehicle data like mileage, charging state and GPS location'
|
|
11
11
|
spec.homepage = 'https://github.com/solectrus/ruze'
|
|
12
12
|
spec.license = 'MIT'
|
|
13
|
-
spec.required_ruby_version = Gem::Requirement.new('>=
|
|
13
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 4.0.0')
|
|
14
14
|
|
|
15
15
|
spec.metadata['homepage_uri'] = spec.homepage
|
|
16
16
|
spec.metadata['source_code_uri'] = 'https://github.com/solectrus/ruze'
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ruze
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Georg Ledermann
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies: []
|
|
13
12
|
description: Queries vehicle data like mileage, charging state and GPS location
|
|
14
13
|
email:
|
|
@@ -31,6 +30,8 @@ files:
|
|
|
31
30
|
- bin/setup
|
|
32
31
|
- lib/ruze.rb
|
|
33
32
|
- lib/ruze/car.rb
|
|
33
|
+
- lib/ruze/device.rb
|
|
34
|
+
- lib/ruze/errors.rb
|
|
34
35
|
- lib/ruze/gigya.rb
|
|
35
36
|
- lib/ruze/kamereon.rb
|
|
36
37
|
- lib/ruze/version.rb
|
|
@@ -43,7 +44,6 @@ metadata:
|
|
|
43
44
|
source_code_uri: https://github.com/solectrus/ruze
|
|
44
45
|
changelog_uri: https://github.com/solectrus/ruze/releases
|
|
45
46
|
rubygems_mfa_required: 'true'
|
|
46
|
-
post_install_message:
|
|
47
47
|
rdoc_options: []
|
|
48
48
|
require_paths:
|
|
49
49
|
- lib
|
|
@@ -51,15 +51,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
51
51
|
requirements:
|
|
52
52
|
- - ">="
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
|
-
version:
|
|
54
|
+
version: 4.0.0
|
|
55
55
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
56
56
|
requirements:
|
|
57
57
|
- - ">="
|
|
58
58
|
- !ruby/object:Gem::Version
|
|
59
59
|
version: '0'
|
|
60
60
|
requirements: []
|
|
61
|
-
rubygems_version:
|
|
62
|
-
signing_key:
|
|
61
|
+
rubygems_version: 4.0.12
|
|
63
62
|
specification_version: 4
|
|
64
63
|
summary: Unofficial Ruby client for the Renault ZE API
|
|
65
64
|
test_files: []
|