iobroker.bmw 2.8.2 → 2.8.3

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.
package/README.md CHANGED
@@ -25,6 +25,10 @@ bmw.0.VIN.properties
25
25
  bmw.0.VIN.remotev2
26
26
 
27
27
  ## Changelog
28
+ ### 2.8.3 (2024-11-18)
29
+
30
+ - login fixed
31
+
28
32
  ### 2.8.2 (2024-10-05)
29
33
 
30
34
  - fix error getvehicles v2 failed
@@ -81,11 +81,47 @@
81
81
  <label for="password" class="translate">App Password</label>
82
82
  </div>
83
83
  </div>
84
+ <div class="row">
85
+ <div class="col s6 input-field" id="captchaResponse">
86
+ Check Captcha Box and press Submit. Save the form.
87
+ <div>
88
+ <form id="captcha_form" action="#" method="post">
89
+ <!-- hCaptcha widget -->
90
+ <div class="h-captcha" data-sitekey="7244955f-8f30-4445-adff-4fefe059f815"></div>
91
+ <br />
92
+ <button type="submit" class="btn">Submit</button>
93
+ </form>
94
+
95
+ <!-- hCaptcha script -->
96
+ <script src="https://hcaptcha.com/1/api.js" async defer></script>
97
+ </div>
98
+ </div>
99
+ <p></p>
100
+ <script>
101
+ document.getElementById('captcha_form').addEventListener('submit', function (event) {
102
+ event.preventDefault(); // Prevent the default form submission
103
+
104
+ const hCaptchaResponse = document.querySelector('[name="h-captcha-response"]').value;
105
+ const responseElement = document.getElementById('captchaResponse');
106
+
107
+ if (hCaptchaResponse) {
108
+ document.getElementById('captcha').value = hCaptchaResponse;
109
+ //trigger change event
110
+ var event = new Event('change');
111
+ document.getElementById('captcha').dispatchEvent(event);
112
+ }
113
+ });
114
+ </script>
115
+
116
+ <input type="text" class="value" id="captcha" />
117
+ <label for="captcha" class="translate">Captcha</label>
118
+ </div>
84
119
  <div class="row">
85
120
  <div class="col s2 input-field">
86
121
  <select id="brand" class="value">
87
122
  <option value="bmw">BMW</option>
88
123
  <option value="mini">Mini</option>
124
+ <option value="toyota">Toyota Supra</option>
89
125
  </select>
90
126
  <label for="brand" class="translate">Brand</label>
91
127
  </div>
package/admin/words.js CHANGED
@@ -1,65 +1,66 @@
1
+ // eslint-disable-next-line no-unused-vars
1
2
  /*global systemDictionary:true */
2
- "use strict";
3
+ 'use strict';
3
4
 
4
5
  systemDictionary = {
5
- "bmw adapter settings": {
6
- en: "Adapter settings for bmw",
7
- de: "Adaptereinstellungen für bmw",
8
- ru: "Настройки адаптера для bmw",
9
- pt: "Configurações do adaptador para bmw",
10
- nl: "Adapterinstellingen voor bmw",
6
+ 'bmw adapter settings': {
7
+ en: 'Adapter settings for bmw',
8
+ de: 'Adaptereinstellungen für bmw',
9
+ ru: 'Настройки адаптера для bmw',
10
+ pt: 'Configurações do adaptador para bmw',
11
+ nl: 'Adapterinstellingen voor bmw',
11
12
  fr: "Paramètres d'adaptateur pour bmw",
12
13
  it: "Impostazioni dell'adattatore per bmw",
13
- es: "Ajustes del adaptador para bmw",
14
- pl: "Ustawienia adaptera dla bmw",
15
- "zh-cn": "bmw2的适配器设置",
14
+ es: 'Ajustes del adaptador para bmw',
15
+ pl: 'Ustawienia adaptera dla bmw',
16
+ 'zh-cn': 'bmw2的适配器设置',
16
17
  },
17
- "App Email": {
18
- en: "App Email",
19
- de: "App-E-Mail",
20
- ru: "Электронная почта приложения",
21
- pt: "Email do aplicativo",
22
- nl: "App-e-mail",
18
+ 'App Email': {
19
+ en: 'App Email',
20
+ de: 'App-E-Mail',
21
+ ru: 'Электронная почта приложения',
22
+ pt: 'Email do aplicativo',
23
+ nl: 'App-e-mail',
23
24
  fr: "Courriel de l'application",
24
25
  it: "E-mail dell'app",
25
- es: "Correo electrónico de la aplicación",
26
- pl: "E-mail aplikacji",
27
- "zh-cn": "应用电子邮件",
26
+ es: 'Correo electrónico de la aplicación',
27
+ pl: 'E-mail aplikacji',
28
+ 'zh-cn': '应用电子邮件',
28
29
  },
29
- "App Password": {
30
- en: "App Password",
31
- de: "App-Passwort",
32
- ru: "Пароль приложения",
33
- pt: "Senha de app",
34
- nl: "App-wachtwoord",
30
+ 'App Password': {
31
+ en: 'App Password',
32
+ de: 'App-Passwort',
33
+ ru: 'Пароль приложения',
34
+ pt: 'Senha de app',
35
+ nl: 'App-wachtwoord',
35
36
  fr: "Mot de passe de l'application",
36
37
  it: "Password dell'app",
37
- es: "Contraseña de la aplicación",
38
- pl: "Hasło do aplikacji",
39
- "zh-cn": "应用密码",
38
+ es: 'Contraseña de la aplicación',
39
+ pl: 'Hasło do aplikacji',
40
+ 'zh-cn': '应用密码',
40
41
  },
41
- "Update interval (in minutes)": {
42
- en: "Update interval (in minutes)",
43
- de: "Aktualisierungsintervall (in Minuten)",
44
- ru: "Интервал обновления (в минутах)",
45
- pt: "Intervalo de atualização (em minutos)",
46
- nl: "Update-interval (in minuten)",
47
- fr: "Intervalle de mise à jour (en minutes)",
48
- it: "Intervallo di aggiornamento (in minuti)",
49
- es: "Intervalo de actualización (en minutos)",
50
- pl: "Interwał aktualizacji (w minutach)",
51
- "zh-cn": "更新间隔(分钟)",
42
+ 'Update interval (in minutes)': {
43
+ en: 'Update interval (in minutes)',
44
+ de: 'Aktualisierungsintervall (in Minuten)',
45
+ ru: 'Интервал обновления (в минутах)',
46
+ pt: 'Intervalo de atualização (em minutos)',
47
+ nl: 'Update-interval (in minuten)',
48
+ fr: 'Intervalle de mise à jour (en minutes)',
49
+ it: 'Intervallo di aggiornamento (in minuti)',
50
+ es: 'Intervalo de actualización (en minutos)',
51
+ pl: 'Interwał aktualizacji (w minutach)',
52
+ 'zh-cn': '更新间隔(分钟)',
52
53
  },
53
54
  Brand: {
54
- en: "Brand",
55
- de: "Marke",
56
- ru: "Марка",
57
- pt: "Marca",
58
- nl: "Brand",
59
- fr: "Marque",
60
- it: "Marca",
61
- es: "Marca",
62
- pl: "Brandy",
63
- "zh-cn": "陪审团",
55
+ en: 'Brand',
56
+ de: 'Marke',
57
+ ru: 'Марка',
58
+ pt: 'Marca',
59
+ nl: 'Brand',
60
+ fr: 'Marque',
61
+ it: 'Marca',
62
+ es: 'Marca',
63
+ pl: 'Brandy',
64
+ 'zh-cn': '陪审团',
64
65
  },
65
66
  };
@@ -0,0 +1,50 @@
1
+ const globals = require('globals');
2
+ const js = require('@eslint/js');
3
+
4
+ const {
5
+ FlatCompat,
6
+ } = require('@eslint/eslintrc');
7
+
8
+ const compat = new FlatCompat({
9
+ baseDirectory: __dirname,
10
+ recommendedConfig: js.configs.recommended,
11
+ allConfig: js.configs.all
12
+ });
13
+
14
+ module.exports = [...compat.extends('eslint:recommended'), {
15
+ plugins: {},
16
+
17
+ languageOptions: {
18
+ globals: {
19
+ ...globals.node,
20
+ ...globals.mocha,
21
+ },
22
+
23
+ ecmaVersion: 2020,
24
+ sourceType: 'commonjs',
25
+ },
26
+
27
+ rules: {
28
+ indent: ['error', 2, {
29
+ SwitchCase: 1,
30
+ }],
31
+
32
+ 'no-console': 'off',
33
+
34
+ 'no-unused-vars': ['error', {
35
+ ignoreRestSiblings: true,
36
+ argsIgnorePattern: '^_',
37
+ }],
38
+
39
+ 'no-var': 'error',
40
+ 'no-trailing-spaces': 'error',
41
+ 'prefer-const': 'error',
42
+
43
+ quotes: ['error', 'single', {
44
+ avoidEscape: true,
45
+ allowTemplateLiterals: true,
46
+ }],
47
+
48
+ semi: ['error', 'always'],
49
+ },
50
+ }];
package/io-package.json CHANGED
@@ -1,8 +1,21 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "bmw",
4
- "version": "2.8.2",
4
+ "version": "2.8.3",
5
5
  "news": {
6
+ "2.8.3": {
7
+ "en": "login fixed",
8
+ "de": "Anmeldung gefixt",
9
+ "ru": "фиксированный",
10
+ "pt": "login fixo",
11
+ "nl": "login vast",
12
+ "fr": "login corrigé",
13
+ "it": "login fisso",
14
+ "es": "login fijo",
15
+ "pl": "login stały",
16
+ "uk": "логін",
17
+ "zh-cn": "登录已固定"
18
+ },
6
19
  "2.8.2": {
7
20
  "en": "fix error getvehicles v2 failed",
8
21
  "de": "fehlerbehebung v2 versagt",
@@ -44,10 +57,6 @@
44
57
  "2.6.3": {
45
58
  "en": "Add start and stop charging remotes",
46
59
  "de": "Start und Stop Charging Remotes hinzugefügt"
47
- },
48
- "2.6.2": {
49
- "en": "Fix Charging response parsing",
50
- "de": "Fix Charging Antwort Verarbeitung"
51
60
  }
52
61
  },
53
62
  "titleLang": {
@@ -76,12 +85,8 @@
76
85
  "uk": "Adapter for BMW",
77
86
  "zh-cn": "宝马适配器"
78
87
  },
79
- "authors": [
80
- "TA2k <tombox2020@gmail.com>"
81
- ],
82
- "keywords": [
83
- "BMW"
84
- ],
88
+ "authors": ["TA2k <tombox2020@gmail.com>"],
89
+ "keywords": ["BMW"],
85
90
  "license": "MIT",
86
91
  "platform": "Javascript/Node.js",
87
92
  "main": "main.js",
@@ -112,18 +117,15 @@
112
117
  }
113
118
  ]
114
119
  },
115
- "encryptedNative": [
116
- "password"
117
- ],
118
- "protectedNative": [
119
- "password"
120
- ],
120
+ "encryptedNative": ["password"],
121
+ "protectedNative": ["password"],
121
122
  "native": {
122
123
  "username": "",
123
124
  "password": "",
124
125
  "interval": 5,
125
126
  "brand": "bmw",
126
- "ignorelist": ""
127
+ "ignorelist": "",
128
+ "captcha": ""
127
129
  },
128
130
  "objects": [],
129
131
  "instanceObjects": [
@@ -215,7 +215,9 @@ function extractArray(adapter, element, key, path, write, preferedArrayName, for
215
215
  function isJsonString(str) {
216
216
  try {
217
217
  JSON.parse(str);
218
+ // eslint-disable-next-line no-unused-vars
218
219
  } catch (e) {
220
+
219
221
  return false;
220
222
  }
221
223
  return true;
package/main.js CHANGED
@@ -186,7 +186,20 @@ class Bmw extends utils.Adapter {
186
186
  this.log.error('Please set username and password');
187
187
  return;
188
188
  }
189
- await this.login();
189
+ const sessionState = await this.getStateAsync('auth.session');
190
+ if (sessionState && sessionState.val) {
191
+ this.session = JSON.parse(sessionState.val);
192
+ this.log.info('Session found. If the login fails please delete bmw.0.auth.session and restart the adapter');
193
+ this.log.debug(JSON.stringify(this.session));
194
+ await this.refreshToken();
195
+ } else {
196
+ if (!this.config.captcha) {
197
+ this.log.error('Please generate a captcha in the instance settings');
198
+ return;
199
+ }
200
+
201
+ await this.login();
202
+ }
190
203
 
191
204
  if (this.session.access_token) {
192
205
  this.log.info(`Start getting ${this.config.brand} vehicles`);
@@ -196,29 +209,20 @@ class Bmw extends utils.Adapter {
196
209
  await this.updateDemands();
197
210
  await this.sleep(5000);
198
211
  await this.updateTrips();
199
- this.updateInterval = setInterval(
200
- async () => {
201
- await this.sleep(2000);
202
- await this.updateDevices();
203
- },
204
- this.config.interval * 60 * 1000,
205
- );
206
- this.demandInterval = setInterval(
207
- async () => {
208
- await this.sleep(2000);
209
- await this.updateDemands();
210
- await this.sleep(5000);
211
- await this.updateTrips();
212
- },
213
- 24 * 60 * 60 * 1000,
214
- );
215
- this.refreshTokenInterval = setInterval(
216
- async () => {
217
- await this.refreshToken();
218
- await this.sleep(5000);
219
- },
220
- (this.session.expires_in - 123) * 1000,
221
- );
212
+ this.updateInterval = setInterval(async () => {
213
+ await this.sleep(2000);
214
+ await this.updateDevices();
215
+ }, this.config.interval * 60 * 1000);
216
+ this.demandInterval = setInterval(async () => {
217
+ await this.sleep(2000);
218
+ await this.updateDemands();
219
+ await this.sleep(5000);
220
+ await this.updateTrips();
221
+ }, 24 * 60 * 60 * 1000);
222
+ this.refreshTokenInterval = setInterval(async () => {
223
+ await this.refreshToken();
224
+ await this.sleep(5000);
225
+ }, (this.session.expires_in - 123) * 1000);
222
226
  }
223
227
  }
224
228
  async login() {
@@ -228,6 +232,7 @@ class Bmw extends utils.Adapter {
228
232
  'Mozilla/5.0 (iPhone; CPU iPhone OS 12_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1.2 Mobile/15E148 Safari/604.1',
229
233
  'Accept-Language': 'de-de',
230
234
  'Content-Type': 'application/x-www-form-urlencoded',
235
+ hcaptchatoken: this.config.captcha,
231
236
  };
232
237
  const [code_verifier, codeChallenge] = this.getCodeChallenge();
233
238
  const data = {
@@ -251,27 +256,28 @@ class Bmw extends utils.Adapter {
251
256
  data: qs.stringify(data),
252
257
  withCredentials: true,
253
258
  })
254
- .then((res) => {
259
+ .then(async (res) => {
255
260
  this.log.debug(JSON.stringify(res.data));
256
261
  return res.data;
257
262
  })
258
- .catch((error) => {
263
+ .catch(async (error) => {
259
264
  this.log.error('Login failed');
260
265
  this.log.error(error);
261
266
  if (error.response) {
262
267
  this.log.error(JSON.stringify(error.response.data));
263
268
  }
264
269
  if (error.response && error.response.status === 401) {
265
- this.log.error('Please check username and password or too many logins in 5 minutes');
266
-
270
+ this.log.error('Please check username and password or generate a new captcha in the instance settings');
271
+ this.log.error('Please wait 5 minutes before trying again');
267
272
  this.log.error('Start relogin in 5min');
273
+
268
274
  this.reLoginTimeout && clearTimeout(this.reLoginTimeout);
269
- this.reLoginTimeout = setTimeout(
270
- () => {
271
- this.login();
272
- },
273
- 5000 * 60 * 1,
274
- );
275
+ this.reLoginTimeout = setTimeout(async () => {
276
+ //get adapter settings and set captcha to null
277
+ const adapterSettings = await this.getForeignObjectAsync('system.adapter.' + this.namespace);
278
+ adapterSettings.native.captcha = null;
279
+ await this.setForeignObjectAsync('system.adapter.' + this.namespace, adapterSettings);
280
+ }, 5000 * 60 * 1);
275
281
  }
276
282
  if (error.response && error.response.status === 400) {
277
283
  this.log.error('Please check username and password');
@@ -326,9 +332,29 @@ class Bmw extends utils.Adapter {
326
332
  },
327
333
  data: 'code=' + code + '&redirect_uri=com.bmw.connected://oauth&grant_type=authorization_code&code_verifier=' + code_verifier,
328
334
  })
329
- .then((res) => {
335
+ .then(async (res) => {
330
336
  this.log.debug(JSON.stringify(res.data));
331
337
  this.session = res.data;
338
+ await this.extendObject('auth', {
339
+ type: 'channel',
340
+ common: {
341
+ name: 'Authentification Information',
342
+ },
343
+ native: {},
344
+ });
345
+ await this.extendObject('auth.session', {
346
+ type: 'state',
347
+ common: {
348
+ name: 'Session Token',
349
+ type: 'string',
350
+ role: 'value',
351
+ read: true,
352
+ write: false,
353
+ },
354
+ native: {},
355
+ });
356
+
357
+ this.setState('auth.session', JSON.stringify(this.session), true);
332
358
  this.setState('info.connection', true, true);
333
359
  return res.data;
334
360
  })
@@ -451,12 +477,9 @@ class Bmw extends utils.Adapter {
451
477
  }
452
478
  this.log.info('Adapter will retry in 3 minutes to get vehicles');
453
479
  this.reLoginTimeout && clearTimeout(this.reLoginTimeout);
454
- this.reLoginTimeout = setTimeout(
455
- () => {
456
- this.getVehiclesv2();
457
- },
458
- 1000 * 60 * 3,
459
- );
480
+ this.reLoginTimeout = setTimeout(() => {
481
+ this.getVehiclesv2();
482
+ }, 1000 * 60 * 3);
460
483
  });
461
484
  await this.sleep(5000);
462
485
  }
@@ -475,8 +498,7 @@ class Bmw extends utils.Adapter {
475
498
  headers['bmw-vin'] = vin;
476
499
  await this.requestClient({
477
500
  method: 'get',
478
- url:
479
- 'https://cocoapi.bmwgroup.com/eadrax-vcs/v4/vehicles/state?apptimezone=120&appDateTime=' + Date.now() + '&tireGuardMode=ENABLED',
501
+ url: 'https://cocoapi.bmwgroup.com/eadrax-vcs/v4/vehicles/state?apptimezone=120&appDateTime=' + Date.now() + '&tireGuardMode=ENABLED',
480
502
  headers: headers,
481
503
  })
482
504
  .then(async (res) => {
@@ -820,6 +842,8 @@ class Bmw extends utils.Adapter {
820
842
  .then((res) => {
821
843
  this.log.debug(JSON.stringify(res.data));
822
844
  this.session = res.data;
845
+
846
+ this.setState('auth.session', JSON.stringify(this.session), true);
823
847
  this.setState('info.connection', true, true);
824
848
  return res.data;
825
849
  })
@@ -829,12 +853,9 @@ class Bmw extends utils.Adapter {
829
853
  error.response && this.log.error(JSON.stringify(error.response.data));
830
854
  this.log.error('Start relogin in 1min');
831
855
  this.reLoginTimeout && clearTimeout(this.reLoginTimeout);
832
- this.reLoginTimeout = setTimeout(
833
- () => {
834
- this.login();
835
- },
836
- 1000 * 60 * 1,
837
- );
856
+ this.reLoginTimeout = setTimeout(() => {
857
+ this.login();
858
+ }, 1000 * 60 * 1);
838
859
  });
839
860
  }
840
861
 
@@ -842,15 +863,22 @@ class Bmw extends utils.Adapter {
842
863
  * Is called when adapter shuts down - callback has to be called under any circumstances!
843
864
  * @param {() => void} callback
844
865
  */
845
- onUnload(callback) {
866
+ async onUnload(callback) {
846
867
  try {
847
868
  clearTimeout(this.refreshTimeout);
848
869
  clearTimeout(this.reLoginTimeout);
849
870
  clearInterval(this.updateInterval);
850
871
  clearInterval(this.refreshTokenInterval);
851
872
  this.demandInterval && clearInterval(this.demandInterval);
873
+ //get adapter settings and set captcha to null
874
+ if (this.config.captcha) {
875
+ const adapterSettings = await this.getForeignObjectAsync('system.adapter.' + this.namespace);
876
+ adapterSettings.native.captcha = null;
877
+ await this.setForeignObjectAsync('system.adapter.' + this.namespace, adapterSettings);
878
+ }
852
879
  callback();
853
880
  } catch (e) {
881
+ this.log.error(e);
854
882
  callback();
855
883
  }
856
884
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.bmw",
3
- "version": "2.8.2",
3
+ "version": "2.8.3",
4
4
  "description": "Adapter for BMW",
5
5
  "author": {
6
6
  "name": "TA2k",
@@ -16,27 +16,30 @@
16
16
  "url": "https://github.com/TA2k/ioBroker.bmw"
17
17
  },
18
18
  "dependencies": {
19
- "@iobroker/adapter-core": "^3.2.1",
19
+ "@iobroker/adapter-core": "^3.2.2",
20
20
  "axios": "^1.7.7",
21
- "http-cookie-agent": "^5.0.4",
21
+ "http-cookie-agent": "^6.0.6",
22
22
  "json-bigint": "^1.0.0",
23
23
  "json2iob": "^2.6.12",
24
- "qs": "^6.13.0",
25
- "tough-cookie": "^4.1.4"
24
+ "qs": "^6.13.1",
25
+ "tough-cookie": "^5.0.0"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@alcalzone/release-script": "^3.8.0",
29
29
  "@alcalzone/release-script-plugin-iobroker": "^3.7.2",
30
30
  "@alcalzone/release-script-plugin-license": "^3.7.0",
31
31
  "@alcalzone/release-script-plugin-manual-review": "^3.7.0",
32
- "@iobroker/testing": "^4.1.3",
32
+ "@eslint/eslintrc": "^3.2.0",
33
+ "@eslint/js": "^9.15.0",
34
+ "@iobroker/testing": "^5.0.0",
33
35
  "@tsconfig/node16": "^16.1.3",
34
- "@types/node": "^20.16.10",
35
- "eslint": "^8.57.1",
36
+ "@types/node": "^22.9.0",
37
+ "eslint": "^9.15.0",
36
38
  "eslint-config-prettier": "^9.1.0",
37
39
  "eslint-plugin-prettier": "^5.2.1",
40
+ "globals": "^15.12.0",
38
41
  "prettier": "^3.3.3",
39
- "typescript": "~5.6.2"
42
+ "typescript": "~5.6.3"
40
43
  },
41
44
  "main": "main.js",
42
45
  "scripts": {