iobroker.google-sharedlocations2 0.3.2 → 0.3.4
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 +6 -0
- package/admin/google-sharedlocations2.png +0 -0
- package/io-package.json +28 -28
- package/package.json +8 -10
- package/src/lib/Cookie.ts +69 -33
- package/src/main.ts +4 -1
package/README.md
CHANGED
|
@@ -27,6 +27,12 @@ Copyright and trademark of Google are property of Google.
|
|
|
27
27
|
Placeholder for the next version (at the beginning of the line):
|
|
28
28
|
### **WORK IN PROGRESS**
|
|
29
29
|
-->
|
|
30
|
+
### 0.3.4 (2026-04-22)
|
|
31
|
+
* (Garfonso) replaced axios dependency. Tried to make login more robust.
|
|
32
|
+
|
|
33
|
+
### 0.3.3 (2026-02-17)
|
|
34
|
+
* (Garfonso) if deleting cookies, also delete cookies in Browser to force login with username & password.
|
|
35
|
+
|
|
30
36
|
### 0.3.2 (2026-02-09)
|
|
31
37
|
* (Garfonso) refresh with browser ignores cookies from adapter
|
|
32
38
|
|
|
Binary file
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "google-sharedlocations2",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.4",
|
|
5
5
|
"news": {
|
|
6
|
+
"0.3.4": {
|
|
7
|
+
"en": "replaced axios dependency. Tried to make login more robust.",
|
|
8
|
+
"de": "ersetzt axios Abhängigkeit. Versuchen Sie, die Anmeldung robuster zu machen.",
|
|
9
|
+
"ru": "замена аксиозависимости. Пытался сделать логин более надежным.",
|
|
10
|
+
"pt": "substituiu a dependência axios. Tentou tornar o login mais robusto.",
|
|
11
|
+
"nl": "axios afhankelijkheid vervangen. Probeerde om login robuuster te maken.",
|
|
12
|
+
"fr": "a remplacé la dépendance aux axios. J'ai essayé de rendre la connexion plus robuste.",
|
|
13
|
+
"it": "ha sostituito la dipendenza dagli assi. Ho cercato di rendere il login più robusto.",
|
|
14
|
+
"es": "sustituyó la dependencia de axios. Traté de hacer que el login sea más robusto.",
|
|
15
|
+
"pl": "zastąpił zależność aksjońską. Próbował uczynić logowanie bardziej solidnym.",
|
|
16
|
+
"uk": "замінена аксіос залежність. Виконується, щоб зробити логін більш надійним.",
|
|
17
|
+
"zh-cn": "取代对轴的依赖。 试图使登录更坚固."
|
|
18
|
+
},
|
|
19
|
+
"0.3.3": {
|
|
20
|
+
"en": "if deleting cookies, also delete cookies in Browser to force login with username & password.",
|
|
21
|
+
"de": "wenn Sie Cookies löschen, löschen Sie auch Cookies im Browser, um die Anmeldung mit Benutzername & Passwort zu zwingen.",
|
|
22
|
+
"ru": "при удалении файлов cookie также удаляйте файлы cookie в браузере, чтобы заставить войти в систему с именем пользователя и паролем.",
|
|
23
|
+
"pt": "se excluir cookies, também excluir cookies no Navegador para forçar login com nome de usuário e senha.",
|
|
24
|
+
"nl": "als u cookies verwijdert, verwijdert u ook cookies in de browser om inloggen met gebruikersnaam en wachtwoord te forceren.",
|
|
25
|
+
"fr": "si vous supprimez les cookies, supprimez également les cookies dans le navigateur pour forcer la connexion avec le nom d'utilisateur et le mot de passe.",
|
|
26
|
+
"it": "se la cancellazione dei cookie, anche eliminare i cookie in Browser per forzare il login con nome utente e password.",
|
|
27
|
+
"es": "si elimina las cookies, también eliminar las cookies en Browser para forzar el login con el nombre de usuario & contraseña.",
|
|
28
|
+
"pl": "jeśli usuniesz pliki cookie, usuń je również w przeglądarce, aby wymusić logowanie z nazwą użytkownika i hasłem.",
|
|
29
|
+
"uk": "якщо видаліть файли cookie, також видаліть файли cookie у браузері, щоб захистити логін з ім'ям користувача та паролем.",
|
|
30
|
+
"zh-cn": "如果删除 cookie, 也可以在浏览器中删除 cookie, 以强制用户名和密码登录 ."
|
|
31
|
+
},
|
|
6
32
|
"0.3.2": {
|
|
7
33
|
"en": "refresh with browser ignores cookies from adapter",
|
|
8
34
|
"de": "mit browser aktualisieren ignoriert cookies vom adapter",
|
|
@@ -67,32 +93,6 @@
|
|
|
67
93
|
"pl": "poprawa odzyskiwania z błędów logowania",
|
|
68
94
|
"uk": "поліпшення відновлення від помилок логіна",
|
|
69
95
|
"zh-cn": "改进登录错误的恢复"
|
|
70
|
-
},
|
|
71
|
-
"0.1.0": {
|
|
72
|
-
"en": "added: support for places\nadded: support for fences\ntry to prevent login as much as possible.",
|
|
73
|
-
"de": "hinzugefügt: unterstützung für places\nhinzugefügt: unterstützung für fences\nversuche, die anmeldung so weit wie möglich zu verhindern.",
|
|
74
|
-
"ru": "добавлено: поддержка мест\nдобавлено: поддержка заборов\nстарайтесь максимально предотвратить вход.",
|
|
75
|
-
"pt": "adicionado: suporte para locais\nadicionado: suporte para cercas\ntentar evitar o login o máximo possível.",
|
|
76
|
-
"nl": "toegevoegd: ondersteuning voor plaatsen\ntoegevoegd: steun voor hekken\nproberen zo veel mogelijk inloggen te voorkomen.",
|
|
77
|
-
"fr": "ajouté: soutien aux places\najouté: soutien aux clôtures\nessayer d'éviter la connexion autant que possible.",
|
|
78
|
-
"it": "aggiunto: supporto per i luoghi\naggiunto: supporto per recinzioni\ncercare di impedire il login il più possibile.",
|
|
79
|
-
"es": "añadido: apoyo a los lugares\nañadido: soporte para vallas\ntratar de prevenir la entrada tanto como sea posible.",
|
|
80
|
-
"pl": "dodane: wsparcie dla miejsc\ndodane: wsparcie dla ogrodzeń\nspróbuj zapobiec logowaniu jak najwięcej.",
|
|
81
|
-
"uk": "додано: підтримка місць\nдоданий: підтримка парканів\nнамагатися попередити логін якомога простіше.",
|
|
82
|
-
"zh-cn": "添加:对位置的支持\n添加:支持围栏\n尽量防止登录."
|
|
83
|
-
},
|
|
84
|
-
"0.0.3": {
|
|
85
|
-
"en": "prevent login if no username and password is set\nfix tests",
|
|
86
|
-
"de": "verhindern login, wenn kein benutzername und passwort eingestellt ist\ntests repariert",
|
|
87
|
-
"ru": "предотвратить вход в систему, если имя пользователя и пароль не установлены\nисправление",
|
|
88
|
-
"pt": "impedir o login se nenhum nome de usuário e senha estiver definido\ncorrigir os testes",
|
|
89
|
-
"nl": "login voorkomen als er geen gebruikersnaam en wachtwoord is ingesteld\nvastleggen van tests",
|
|
90
|
-
"fr": "empêcher le login si aucun nom d'utilisateur et mot de passe n'est défini\nessais de correction",
|
|
91
|
-
"it": "impedire il login se non viene impostato nessun nome utente e password\ntest di correzione",
|
|
92
|
-
"es": "previene el inicio de sesión si no se establece el nombre de usuario y la contraseña\npruebas de reparación",
|
|
93
|
-
"pl": "zapobiec logowaniu, jeśli nie jest ustawiona nazwa użytkownika i hasło\nbadania naprawcze",
|
|
94
|
-
"uk": "заборонити логін, якщо не встановлено ім’я користувача та пароль\nфіксувати тести",
|
|
95
|
-
"zh-cn": "如果没有设置用户名和密码, 请防止登录\n固定测试"
|
|
96
96
|
}
|
|
97
97
|
},
|
|
98
98
|
"titleLang": {
|
|
@@ -158,7 +158,7 @@
|
|
|
158
158
|
],
|
|
159
159
|
"globalDependencies": [
|
|
160
160
|
{
|
|
161
|
-
"admin": ">=7.
|
|
161
|
+
"admin": ">=7.6.17"
|
|
162
162
|
}
|
|
163
163
|
],
|
|
164
164
|
"osDependencies": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iobroker.google-sharedlocations2",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"description": "Share your location with iobroker via google maps.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Garfonso",
|
|
@@ -26,21 +26,19 @@
|
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@iobroker/adapter-core": "^3.3.2",
|
|
29
|
-
"
|
|
30
|
-
"puppeteer": "^24.36.1"
|
|
29
|
+
"puppeteer": "^24.42.0"
|
|
31
30
|
},
|
|
32
31
|
"devDependencies": {
|
|
33
|
-
"@alcalzone/release-script": "^5.
|
|
34
|
-
"@alcalzone/release-script-plugin-iobroker": "^
|
|
35
|
-
"@alcalzone/release-script-plugin-license": "^
|
|
36
|
-
"@alcalzone/release-script-plugin-manual-review": "^
|
|
32
|
+
"@alcalzone/release-script": "^5.1.1",
|
|
33
|
+
"@alcalzone/release-script-plugin-iobroker": "^5.1.2",
|
|
34
|
+
"@alcalzone/release-script-plugin-license": "^5.1.1",
|
|
35
|
+
"@alcalzone/release-script-plugin-manual-review": "^5.1.1",
|
|
37
36
|
"@iobroker/adapter-dev": "^1.5.0",
|
|
38
|
-
"@iobroker/dev-server": "^0.8.0",
|
|
39
37
|
"@iobroker/eslint-config": "^2.2.0",
|
|
40
38
|
"@iobroker/testing": "^5.2.2",
|
|
41
|
-
"@iobroker/types": "^7.1.
|
|
39
|
+
"@iobroker/types": "^7.1.1",
|
|
42
40
|
"@tsconfig/node22": "^22.0.5",
|
|
43
|
-
"@types/node": "^22.19.
|
|
41
|
+
"@types/node": "^22.19.17 < 23",
|
|
44
42
|
"source-map-support": "^0.5.21",
|
|
45
43
|
"ts-node": "^10.9.2",
|
|
46
44
|
"typescript": "~5.9.3"
|
package/src/lib/Cookie.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import axios from 'axios';
|
|
2
1
|
import type { GoogleSharedlocations2 } from '../main';
|
|
3
2
|
import puppeteer from 'puppeteer';
|
|
4
|
-
import type { Browser, Page, CookieData } from 'puppeteer';
|
|
3
|
+
import type { Browser, Page, CookieData, CookiePriority, CookieSameSite } from 'puppeteer';
|
|
5
4
|
import { mkdir } from 'fs/promises';
|
|
5
|
+
import type { RequestCredentials } from 'undici-types/fetch';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Helper class to manage Google cookies.
|
|
@@ -114,18 +114,24 @@ export class Cookie {
|
|
|
114
114
|
/**
|
|
115
115
|
* Augment the current cookie with data from the 'set-cookie' header.
|
|
116
116
|
*
|
|
117
|
-
* @param
|
|
117
|
+
* @param headersObj - HTTP headers of response
|
|
118
118
|
*/
|
|
119
|
-
async augmentCookieFromHeader(
|
|
120
|
-
|
|
119
|
+
async augmentCookieFromHeader(headersObj: Headers): Promise<void> {
|
|
120
|
+
const headers = headersObj.getSetCookie();
|
|
121
|
+
if (headers.length > 0) {
|
|
121
122
|
this.log?.debug('New header received.');
|
|
122
123
|
const oldLength = this.cookies.length;
|
|
123
124
|
|
|
124
125
|
//split old cookie and new cookie. Update single values.
|
|
125
|
-
for (const header of headers
|
|
126
|
+
for (const header of headers) {
|
|
126
127
|
//console.log('Processing header cookie:', header);
|
|
127
128
|
const keyValues = header.split('; ');
|
|
128
|
-
const
|
|
129
|
+
const firstCookie = keyValues.shift();
|
|
130
|
+
if (firstCookie === undefined) {
|
|
131
|
+
this.log.debug(`Invalid cookie header: ${header}`);
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
const [name, value] = firstCookie.split('='); // first part is cookie, rest are attributes like path, secure etc.
|
|
129
135
|
const cookie = {
|
|
130
136
|
name: name.trim(),
|
|
131
137
|
value: value.trim(),
|
|
@@ -147,13 +153,13 @@ export class Cookie {
|
|
|
147
153
|
cookie.httpOnly = true;
|
|
148
154
|
break;
|
|
149
155
|
case 'samesite':
|
|
150
|
-
cookie.sameSite = v ? v.trim() : 'Lax';
|
|
156
|
+
cookie.sameSite = v ? (v.trim() as CookieSameSite) : 'Lax';
|
|
151
157
|
break;
|
|
152
158
|
case 'expires':
|
|
153
159
|
cookie.expires = new Date(v).getTime() / 1000; //puppeteer expects expires in seconds, not milliseconds
|
|
154
160
|
break;
|
|
155
161
|
case 'priority':
|
|
156
|
-
cookie.priority = v ? v.trim() : 'Medium';
|
|
162
|
+
cookie.priority = v ? (v.trim() as CookiePriority) : 'Medium';
|
|
157
163
|
break;
|
|
158
164
|
default:
|
|
159
165
|
this.log.debug(`Unknown cookie attribute: ${k}=${v}`);
|
|
@@ -189,8 +195,9 @@ export class Cookie {
|
|
|
189
195
|
*/
|
|
190
196
|
async improveCookie(): Promise<boolean> {
|
|
191
197
|
//see https://github.com/costastf/locationsharinglib/blob/master/locationsharinglib/locationsharinglib.py#L105
|
|
198
|
+
const url = 'https://myaccount.google.com/?hl=en';
|
|
192
199
|
const options = {
|
|
193
|
-
|
|
200
|
+
credentials: 'same-origin' as RequestCredentials, //or do we need 'include' here?
|
|
194
201
|
headers: {
|
|
195
202
|
Cookie: this.cookies.map(c => `${c.name}=${c.value}`).join('; '),
|
|
196
203
|
},
|
|
@@ -198,7 +205,7 @@ export class Cookie {
|
|
|
198
205
|
};
|
|
199
206
|
|
|
200
207
|
try {
|
|
201
|
-
const response = await
|
|
208
|
+
const response = await fetch(url, options);
|
|
202
209
|
|
|
203
210
|
if (response.status !== 200) {
|
|
204
211
|
this.log?.error(`Failed improving cookie: ${response.status}`);
|
|
@@ -257,30 +264,34 @@ export class Cookie {
|
|
|
257
264
|
|
|
258
265
|
//send request with current cookies
|
|
259
266
|
this.log.debug('Sending request with current cookies');
|
|
267
|
+
const url =
|
|
268
|
+
'https://www.google.com/maps/rpc/locationsharing/read?authuser=2&hl=en&gl=us&pb=!1m7!8m6!1m3!1i14!2i8413!3i5385!2i6!3x4095!2m3!1e0!2sm!3i407105169!3m7!2sen!5e1105!12m4!1e68!2m2!1sset!2sRoadmap!4e1!5m4!1e4!8m2!1e0!1e1!6m9!1e12!2i2!26m1!4b1!30m1!1f1.3953487873077393!39b1!44e1!50e0!23i4111425';
|
|
260
269
|
const options = {
|
|
261
270
|
method: 'GET',
|
|
262
|
-
|
|
271
|
+
credentials: 'same-origin' as RequestCredentials, //or do we need 'include' here?
|
|
263
272
|
headers: {
|
|
264
273
|
Cookie: this.cookies.map(c => `${c.name}=${c.value}`).join('; '),
|
|
265
274
|
},
|
|
266
|
-
params: {
|
|
275
|
+
/*params: {
|
|
267
276
|
authuser: 2,
|
|
268
277
|
hl: 'en',
|
|
269
278
|
gl: 'us',
|
|
270
279
|
//pb is place on map. Is irrelevant, set to google head quarters here.
|
|
271
280
|
pb: '!1m7!8m6!1m3!1i14!2i8413!3i5385!2i6!3x4095!2m3!1e0!2sm!3i407105169!3m7!2sen!5e1105!12m4!1e68!2m2!1sset!2sRoadmap!4e1!5m4!1e4!8m2!1e0!1e1!6m9!1e12!2i2!26m1!4b1!30m1!1f1.3953487873077393!39b1!44e1!50e0!23i4111425',
|
|
272
|
-
}
|
|
281
|
+
},*/
|
|
273
282
|
};
|
|
274
|
-
|
|
275
283
|
try {
|
|
276
|
-
const response = await
|
|
284
|
+
const response = await fetch(url, options);
|
|
277
285
|
this.log.debug(`Request successful, response code: ${response.status}`);
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
286
|
+
if (response.ok) {
|
|
287
|
+
const dataBuffer = await response.text();
|
|
288
|
+
const data = dataBuffer.split('\n').slice(1).join('\n');
|
|
289
|
+
const locationData = JSON.parse(data);
|
|
290
|
+
const locations = locationData[0];
|
|
291
|
+
if (locations && locations.length > 0) {
|
|
292
|
+
await this.augmentCookieFromHeader(response.headers);
|
|
293
|
+
return locations;
|
|
294
|
+
}
|
|
284
295
|
}
|
|
285
296
|
this.log.info('No shared locations found in the response, probably not logged in.');
|
|
286
297
|
} catch (e) {
|
|
@@ -314,6 +325,7 @@ export class Cookie {
|
|
|
314
325
|
/**
|
|
315
326
|
* Refresh the current cookie by using puppeteer to load Google Maps with existing cookie.
|
|
316
327
|
*
|
|
328
|
+
* @param withCookies - if true, will try to set existing cookies in browser before loading page, default is false. This can help if cookies are still valid but not complete enough to work without browser session.
|
|
317
329
|
* @returns true if refresh was successful
|
|
318
330
|
*/
|
|
319
331
|
async refreshCookieWithBrowser(withCookies: boolean = false): Promise<boolean> {
|
|
@@ -376,21 +388,25 @@ export class Cookie {
|
|
|
376
388
|
|
|
377
389
|
/**
|
|
378
390
|
* Login to Google using puppeteer to get new cookies.
|
|
391
|
+
*
|
|
392
|
+
* @param forceLogin - if true, will try to login even if current cookie seems valid, default is false
|
|
379
393
|
*/
|
|
380
|
-
async loginToGetNewCookies(): Promise<boolean> {
|
|
394
|
+
async loginToGetNewCookies(forceLogin: boolean = false): Promise<boolean> {
|
|
381
395
|
let currentStep;
|
|
382
396
|
try {
|
|
383
397
|
// try to refresh cookie from browser session first:
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
this.log.info('Cookie refresh successful, no need to login again.');
|
|
387
|
-
return true;
|
|
388
|
-
} else if (this.isValid()) {
|
|
389
|
-
this.log.info('Current cookie seems valid, trying refresh.');
|
|
390
|
-
result = await this.refreshCookieWithBrowser(true);
|
|
398
|
+
if (!forceLogin) {
|
|
399
|
+
let result = await this.refreshCookieWithBrowser();
|
|
391
400
|
if (result) {
|
|
392
|
-
this.log.info('Cookie refresh
|
|
401
|
+
this.log.info('Cookie refresh successful, no need to login again.');
|
|
393
402
|
return true;
|
|
403
|
+
} else if (this.isValid()) {
|
|
404
|
+
this.log.info('Current cookie seems valid, trying refresh.');
|
|
405
|
+
result = await this.refreshCookieWithBrowser(true);
|
|
406
|
+
if (result) {
|
|
407
|
+
this.log.info('Cookie refresh with existing cookies successful, no need to login again.');
|
|
408
|
+
return true;
|
|
409
|
+
}
|
|
394
410
|
}
|
|
395
411
|
}
|
|
396
412
|
|
|
@@ -416,6 +432,13 @@ export class Cookie {
|
|
|
416
432
|
currentStep = msg;
|
|
417
433
|
this.log.debug(msg);
|
|
418
434
|
};
|
|
435
|
+
|
|
436
|
+
if (forceLogin) {
|
|
437
|
+
logDebug('Force login enabled, clearing cookies and local storage.');
|
|
438
|
+
const cookies = await this.browser!.cookies();
|
|
439
|
+
await this.browser!.deleteCookie(...cookies);
|
|
440
|
+
}
|
|
441
|
+
|
|
419
442
|
logDebug('going to google login page.');
|
|
420
443
|
await page.goto(
|
|
421
444
|
'https://accounts.google.com/ServiceLogin?hl=de&continue=https://www.google.com/maps&gae=cb-eomtm',
|
|
@@ -439,8 +462,21 @@ export class Cookie {
|
|
|
439
462
|
return false;
|
|
440
463
|
}
|
|
441
464
|
|
|
442
|
-
|
|
443
|
-
|
|
465
|
+
try {
|
|
466
|
+
logDebug('Trying to click on username, if user was logged in before.');
|
|
467
|
+
const userElement = await page.$(`[data-email="${this.username}"]`);
|
|
468
|
+
if (userElement) {
|
|
469
|
+
await userElement.click();
|
|
470
|
+
} else {
|
|
471
|
+
logDebug('No user element found, filling in username.');
|
|
472
|
+
await page.locator('#identifierId').fill(this.username);
|
|
473
|
+
}
|
|
474
|
+
} catch (e: any) {
|
|
475
|
+
logDebug(`Ok, no user it seems (${e}). Let's fill in useranme`);
|
|
476
|
+
logDebug('filling in username.');
|
|
477
|
+
await page.locator('#identifierId').fill(this.username);
|
|
478
|
+
}
|
|
479
|
+
|
|
444
480
|
//is this enough, or do we need to search button in this div?
|
|
445
481
|
logDebug('clicking user next button.');
|
|
446
482
|
await page.locator('#identifierNext').click();
|
package/src/main.ts
CHANGED
|
@@ -141,6 +141,9 @@ export class GoogleSharedlocations2 extends utils.Adapter {
|
|
|
141
141
|
await this.setState('info.connection', false, true);
|
|
142
142
|
if (this._successFullPolls > 0) {
|
|
143
143
|
//try to get new cookie:
|
|
144
|
+
this.log.debug(
|
|
145
|
+
`Polling failed, trying to obtain new cookies, because got ${this._successFullPolls} valid results before.`,
|
|
146
|
+
);
|
|
144
147
|
this._successFullPolls = 0;
|
|
145
148
|
await this.cookie.loginToGetNewCookies();
|
|
146
149
|
}
|
|
@@ -365,7 +368,7 @@ export class GoogleSharedlocations2 extends utils.Adapter {
|
|
|
365
368
|
this.log.info('Current cookies state was cleared, trying to obtain new cookies.');
|
|
366
369
|
this._successFullPolls = 0;
|
|
367
370
|
this.cookie.readCookieFromString(''); //clear old cookie
|
|
368
|
-
await this.cookie.loginToGetNewCookies();
|
|
371
|
+
await this.cookie.loginToGetNewCookies(true);
|
|
369
372
|
} else {
|
|
370
373
|
this.log.info(
|
|
371
374
|
'Current cookies state was changed from outside the adapter, updating internal cookie store.',
|