iobroker.google-sharedlocations2 0.1.0 → 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.
package/README.md CHANGED
@@ -27,6 +27,13 @@ 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.2.0 (2026-02-03)
31
+ * (Garfonso) now using data director to store chrome data
32
+ * (Garfonso) try to use existint cookie in browser to refresh cookie without login.
33
+
34
+ ### 0.1.1 (2026-02-02)
35
+ * (Garfonso) improved recovery from login errors
36
+
30
37
  ### 0.1.0 (2026-02-02)
31
38
  * (Garfonso) added: support for places
32
39
  * (Garfonso) added: support for fences
package/io-package.json CHANGED
@@ -1,8 +1,34 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "google-sharedlocations2",
4
- "version": "0.1.0",
4
+ "version": "0.2.0",
5
5
  "news": {
6
+ "0.2.0": {
7
+ "en": "now using data directory to store chrome data\ntry to use existing cookie in browser to refresh cookie without login.",
8
+ "de": "jetzt mit data directory, um chromdaten zu speichern\nversuchen, existierenden cookie im browser zu verwenden, um cookie ohne anmeldung zu aktualisieren.",
9
+ "ru": "теперь используя директор данных для хранения хромированных данных\nпопробуйте использовать существующий cookie в браузере, чтобы обновить cookie без входа в систему.",
10
+ "pt": "agora usando o diretor de dados para armazenar dados de cromo\ntentar usar cookie existente no navegador para atualizar cookie sem login.",
11
+ "nl": "nu met behulp van data director om chroom gegevens op te slaan\nprobeer bestaande cookie in de browser te gebruiken om cookie te vernieuwen zonder in te loggen.",
12
+ "fr": "maintenant en utilisant le directeur de données pour stocker des données chrome\nessayez d'utiliser le cookie existentint dans le navigateur pour rafraîchir le cookie sans vous connecter.",
13
+ "it": "ora utilizzando il data director per memorizzare i dati cromati\nprovare a utilizzare cookie esisteint nel browser per aggiornare i cookie senza effettuare il login.",
14
+ "es": "ahora utilizando el director de datos para almacenar datos de cromo\ntratar de utilizar la cookie existente en el navegador para refrescar la cookie sin login.",
15
+ "pl": "teraz za pomocą dyrektora danych do przechowywania danych chromowych\nspróbuj użyć plików cookie existint w przeglądarce, aby odświeżyć pliki cookie bez logowania.",
16
+ "uk": "тепер використовуючи каталог даних для зберігання хромованих даних\nспробуйте використовувати наявний cookie-файли в браузері, щоб оновити cookie без реєстрації.",
17
+ "zh-cn": "现在使用数据总监存储铬数据\n尝试在浏览器中使用存在 cookie来刷新 cookie而不登录."
18
+ },
19
+ "0.1.1": {
20
+ "en": "improved recovery from login errors",
21
+ "de": "verbesserte wiederherstellung von login-fehlern",
22
+ "ru": "улучшенное восстановление после ошибок входа",
23
+ "pt": "recuperação melhorada de erros de login",
24
+ "nl": "verbeterd herstel van login fouten",
25
+ "fr": "amélioration de la récupération des erreurs de connexion",
26
+ "it": "miglioramento del recupero da errori di login",
27
+ "es": "mejor recuperación de errores de inicio de sesión",
28
+ "pl": "poprawa odzyskiwania z błędów logowania",
29
+ "uk": "поліпшення відновлення від помилок логіна",
30
+ "zh-cn": "改进登录错误的恢复"
31
+ },
6
32
  "0.1.0": {
7
33
  "en": "added: support for places\nadded: support for fences\ntry to prevent login as much as possible.",
8
34
  "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.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.google-sharedlocations2",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Share your location with iobroker via google maps.",
5
5
  "author": {
6
6
  "name": "Garfonso",
package/src/lib/Cookie.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import axios from 'axios';
2
2
  import type { GoogleSharedlocations2 } from '../main';
3
3
  import puppeteer from 'puppeteer';
4
- import type { Browser } from 'puppeteer';
4
+ import type { Browser, Page } from 'puppeteer';
5
+ import { mkdir } from 'fs/promises';
5
6
 
6
7
  /**
7
8
  * Helper class to manage Google cookies.
@@ -13,18 +14,21 @@ export class Cookie {
13
14
  adapter: GoogleSharedlocations2;
14
15
  log;
15
16
  private browser: Browser | null = null;
17
+ dataDir: string;
16
18
 
17
19
  /**
18
20
  * Construct cookie helper
19
21
  *
20
22
  * @param adapter - adapter instance
23
+ * @param dataDir - data directory of the instance to store browser data to.
21
24
  */
22
- constructor(adapter: GoogleSharedlocations2) {
25
+ constructor(adapter: GoogleSharedlocations2, dataDir: string) {
23
26
  this.currentCookie = '';
24
27
  this.username = '';
25
28
  this.password = '';
26
29
  this.adapter = adapter;
27
30
  this.log = adapter.log;
31
+ this.dataDir = dataDir;
28
32
  }
29
33
 
30
34
  /**
@@ -33,7 +37,10 @@ export class Cookie {
33
37
  async init(): Promise<void> {
34
38
  this.username = this.adapter.config.googleUsername;
35
39
  this.password = this.adapter.config.googlePassword;
40
+ this.log = this.adapter.log; // does not exist during construction...
36
41
  try {
42
+ //ensure data dir exists
43
+ await mkdir(this.dataDir, { recursive: true }); //recursive true should prevent error if already exists.
37
44
  const state = await this.adapter.getStateAsync('info.currentCookies');
38
45
  if (state && state.val && typeof state.val === 'string') {
39
46
  this.currentCookie = state.val;
@@ -93,7 +100,7 @@ export class Cookie {
93
100
  /**
94
101
  * Improve the current cookie by making a request to Google My Account page.
95
102
  */
96
- async improveCookie(): Promise<void> {
103
+ async improveCookie(): Promise<boolean> {
97
104
  //see https://github.com/costastf/locationsharinglib/blob/master/locationsharinglib/locationsharinglib.py#L105
98
105
  const options = {
99
106
  url: 'https://myaccount.google.com/?hl=en',
@@ -108,12 +115,158 @@ export class Cookie {
108
115
 
109
116
  if (response.status !== 200) {
110
117
  this.log?.error(`Failed improving cookie: ${response.status}`);
111
- } else {
112
- await this.augmentCookieFromHeader(response.headers);
118
+ return false;
113
119
  }
120
+ await this.augmentCookieFromHeader(response.headers);
121
+ return true;
114
122
  } catch (err: any) {
115
123
  this.log?.error(err);
116
124
  this.log?.info('Connection to google maps failure.');
125
+ return false;
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Start a puppeteer browser instance and return a new page. Sets up user agent and hides automation flag.
131
+ *
132
+ * @returns puppeteer page or undefined if browser could not be started
133
+ */
134
+ private async startBrowser(): Promise<Page | undefined> {
135
+ if (this.browser) {
136
+ this.log.info('Seems we are already trying to log in. Aborting new login attempt.');
137
+ return;
138
+ }
139
+ this.log.debug('Starting browser.');
140
+ this.browser = await puppeteer.launch({
141
+ headless: true,
142
+ args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-blink-features=AutomationControlled'],
143
+ ignoreDefaultArgs: ['--enable-automation'], //h// ide automation flag, did not help.
144
+ userDataDir: this.dataDir,
145
+ });
146
+ this.log.debug('browser started, opening new page.');
147
+ const page = await this.browser.newPage();
148
+ //hide puppeteer automation flag
149
+ await page.evaluateOnNewDocument(() => {
150
+ Object.defineProperty(navigator, 'webdriver', { get: () => false });
151
+ });
152
+ await page.setUserAgent({
153
+ userAgent:
154
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
155
+ });
156
+
157
+ return page;
158
+ }
159
+
160
+ /**
161
+ * Request location Data from Google Maps
162
+ *
163
+ * @returns Array of location data or undefined if request failed
164
+ */
165
+ async sendRequest(): Promise<Array<any> | undefined> {
166
+ if (!this.isValid()) {
167
+ this.log.error('Cannot send request, no cookies available!');
168
+ return;
169
+ }
170
+
171
+ //send request with current cookies
172
+ this.log.debug('Sending request with current cookies');
173
+ const options = {
174
+ method: 'GET',
175
+ url: 'https://www.google.com/maps/rpc/locationsharing/read',
176
+ headers: {
177
+ Cookie: this.currentCookie,
178
+ },
179
+ params: {
180
+ authuser: 2,
181
+ hl: 'en',
182
+ gl: 'us',
183
+ //pb is place on map. Is irrelevant, set to google head quarters here.
184
+ 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',
185
+ },
186
+ };
187
+
188
+ try {
189
+ const response = await axios.request(options);
190
+ this.log.debug(`Request successful, response code: ${response.status}`);
191
+ const data = response.data.split('\n').slice(1).join('\n');
192
+ const locationData = JSON.parse(data);
193
+ const locations = locationData[0];
194
+ if (locations && locations.length > 0) {
195
+ return locations;
196
+ }
197
+ this.log.info('No shared locations found in the response, probably not logged in.');
198
+ } catch (e) {
199
+ this.log.error(`Error during request: ${(e as Error).message}`);
200
+ }
201
+ }
202
+
203
+ /**
204
+ * Get cookies from the given page and store them. Also closes Browser.
205
+ *
206
+ * @param page - puppeteer page
207
+ */
208
+ private async getCookiesFromPage(page: Page): Promise<void> {
209
+ //using deprecated function, but browser.cookies just does not work...???
210
+ const cookies = await page.cookies();
211
+
212
+ this.currentCookie = cookies
213
+ .filter(c => c.domain.includes('google'))
214
+ .map(c => `${c.name}=${c.value}`)
215
+ .join('; ');
216
+ await this.browser!.close();
217
+ if (this.currentCookie.length < 50) {
218
+ this.log.warn('Cookie string seems too short, login probably failed!');
219
+ } else {
220
+ this.log.info('Obtained new cookies from Google login.');
221
+ await this.storeCookie();
222
+ }
223
+ this.browser = null;
224
+ }
225
+
226
+ /**
227
+ * Refresh the current cookie by using puppeteer to load Google Maps with existing cookie.
228
+ *
229
+ * @returns true if refresh was successful
230
+ */
231
+ private async refreshCookie(): Promise<boolean> {
232
+ if (this.browser) {
233
+ this.log.info('Seems we are already trying to log in. Aborting new login attempt.');
234
+ return false;
235
+ }
236
+
237
+ const page = await this.startBrowser();
238
+ if (!page) {
239
+ this.log.error('Could not start browser for cookie refresh.');
240
+ return false;
241
+ }
242
+
243
+ const cookieArray = this.currentCookie
244
+ .split(';')
245
+ .map(pair => {
246
+ const parts = pair.trim().split('=');
247
+ return parts.length >= 2
248
+ ? {
249
+ name: parts[0].trim(),
250
+ value: parts.slice(1).join('=').trim(),
251
+ domain: '.google.com',
252
+ path: '/',
253
+ secure: true,
254
+ }
255
+ : null;
256
+ })
257
+ .filter(c => c !== null);
258
+ await page.setCookie(...cookieArray);
259
+
260
+ await page.goto('https://www.google.com/maps', { waitUntil: 'networkidle2', timeout: 60000 });
261
+ await new Promise(r => setTimeout(r, 5000));
262
+
263
+ try {
264
+ await this.sendRequest();
265
+ await this.getCookiesFromPage(page);
266
+ return true;
267
+ } catch (e) {
268
+ this.log.error(`Error during cookie refresh: ${(e as Error).message}`);
269
+ return false;
117
270
  }
118
271
  }
119
272
 
@@ -122,6 +275,15 @@ export class Cookie {
122
275
  */
123
276
  async loginToGetNewCookies(): Promise<boolean> {
124
277
  try {
278
+ if (this.currentCookie && this.currentCookie.length >= 50) {
279
+ this.log.info('Current cookie seems valid, trying refresh.');
280
+ await this.refreshCookie();
281
+ if (this.currentCookie && this.currentCookie.length >= 50) {
282
+ this.log.info('Cookie refresh successful, no need to login again.');
283
+ return true;
284
+ }
285
+ }
286
+
125
287
  if (this.browser) {
126
288
  this.log.info('Seems we are already trying to log in. Aborting new login attempt.');
127
289
  return false;
@@ -133,23 +295,11 @@ export class Cookie {
133
295
 
134
296
  this.log.info('Trying to login to Google to get new cookies.');
135
297
  //testing puppeteer:
136
- this.log.debug('Starting browser.');
137
- this.browser = await puppeteer.launch({
138
- headless: true,
139
- args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-blink-features=AutomationControlled'],
140
- ignoreDefaultArgs: ['--enable-automation'], //hide automation flag, did not help.
141
- });
142
- this.log.debug('browser started, opening new page.');
143
- const page = await this.browser.newPage();
144
-
145
- //hide puppeteer automation flag
146
- await page.evaluateOnNewDocument(() => {
147
- Object.defineProperty(navigator, 'webdriver', { get: () => false });
148
- });
149
- await page.setUserAgent({
150
- userAgent:
151
- 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
152
- });
298
+ const page = await this.startBrowser();
299
+ if (!page) {
300
+ this.log.error('Could not start browser for login.');
301
+ return false;
302
+ }
153
303
 
154
304
  this.log.debug('going to google login page.');
155
305
  await page.goto(
@@ -177,26 +327,19 @@ export class Cookie {
177
327
 
178
328
  await page.goto('https://www.google.com/maps');
179
329
  this.log.debug('getting cookies.');
180
- //using deprecated function, but browser.cookies just does not work...???
181
- const cookies = await page.cookies();
182
-
183
- this.currentCookie = cookies
184
- .filter(c => c.domain.includes('google'))
185
- .map(c => `${c.name}=${c.value}`)
186
- .join('; ');
187
- //this.log.debug(this._cookies);
188
- //console.log(this._cookies);
189
- await this.browser.close();
190
- if (this.currentCookie.length < 50) {
191
- this.log.warn('Cookie string seems too short, login probably failed!');
192
- } else {
193
- this.log.info('Obtained new cookies from Google login.');
194
- await this.storeCookie();
195
- }
196
- this.browser = null;
330
+ await this.getCookiesFromPage(page);
197
331
  return true;
198
332
  } catch (e) {
199
333
  this.log.error(`Error in puppeteer: ${(e as Error).message}`);
334
+ // try to close browser if open
335
+ if (this.browser) {
336
+ try {
337
+ await this.browser.close();
338
+ } catch {
339
+ /* ignore */
340
+ }
341
+ }
342
+ this.browser = null;
200
343
  }
201
344
  return false;
202
345
  }
package/src/main.ts CHANGED
@@ -9,7 +9,6 @@ import * as utils from '@iobroker/adapter-core';
9
9
  import { User } from './lib/User';
10
10
  import { Fence } from './lib/Fence';
11
11
  import { Cookie } from './lib/Cookie';
12
- import axios from 'axios';
13
12
 
14
13
  //used to test timeout against
15
14
  const MAX_INT32 = 2 ** 31 - 1; // 2147483647 (hex 0x7FFFFFFF)
@@ -43,7 +42,7 @@ export class GoogleSharedlocations2 extends utils.Adapter {
43
42
  // this.on('objectChange', this.onObjectChange.bind(this));
44
43
  this.on('message', this.onMessage.bind(this));
45
44
  this.on('unload', this.onUnload.bind(this));
46
- this.cookie = new Cookie(this);
45
+ this.cookie = new Cookie(this, utils.getAbsoluteInstanceDataDir(this));
47
46
  }
48
47
 
49
48
  /**
@@ -137,62 +136,26 @@ export class GoogleSharedlocations2 extends utils.Adapter {
137
136
  }
138
137
 
139
138
  private async sendRequest(): Promise<void> {
140
- if (!this.cookie.isValid()) {
141
- this.log.error('Cannot send request, no cookies available!');
139
+ const results = await this.cookie.sendRequest();
140
+ if (!results) {
142
141
  await this.setState('info.connection', false, true);
143
- return;
144
- }
145
-
146
- //send request with current cookies
147
- this.log.debug('Sending request with current cookies');
148
- const options = {
149
- method: 'GET',
150
- url: 'https://www.google.com/maps/rpc/locationsharing/read',
151
- headers: {
152
- Cookie: this.cookie.currentCookie,
153
- },
154
- params: {
155
- authuser: 2,
156
- hl: 'en',
157
- gl: 'us',
158
- //pb is place on map. Is irrelevant, set to google head quarters here.
159
- 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',
160
- },
161
- };
162
-
163
- try {
164
- const response = await axios.request(options);
165
- this.log.debug(`Request successful, response code: ${response.status}`);
166
- const data = response.data.split('\n').slice(1).join('\n');
167
- const locationData = JSON.parse(data);
168
- const locations = locationData[0];
169
- if (locations && locations.length > 0) {
170
- this._successFullPolls += 1;
171
- await this.setState('info.connection', true, true);
172
- for (const location of locations) {
173
- const user = new User(location);
174
- if (user.id) {
175
- this._users[user.id] = user;
176
- await this.fillIntoObjects(user);
177
- await this.notifyPlaces(user);
178
- await this.checkFences(user);
179
- }
180
- }
181
- } else {
182
- this.log.info('No shared locations found in the response, probably not logged in.');
183
- if (this._successFullPolls > 0) {
184
- //try to get new cookie:
185
- this._successFullPolls = 0;
186
- await this.cookie.loginToGetNewCookies();
187
- }
188
- }
189
- } catch (e) {
190
- this.log.error(`Error during request: ${(e as Error).message}`);
191
142
  if (this._successFullPolls > 0) {
192
143
  //try to get new cookie:
193
144
  this._successFullPolls = 0;
194
145
  await this.cookie.loginToGetNewCookies();
195
146
  }
147
+ } else {
148
+ this._successFullPolls += 1;
149
+ await this.setState('info.connection', true, true);
150
+ for (const location of results) {
151
+ const user = new User(location);
152
+ if (user.id) {
153
+ this._users[user.id] = user;
154
+ await this.fillIntoObjects(user);
155
+ await this.notifyPlaces(user);
156
+ await this.checkFences(user);
157
+ }
158
+ }
196
159
  }
197
160
  }
198
161
 
@@ -396,16 +359,17 @@ export class GoogleSharedlocations2 extends utils.Adapter {
396
359
  if (state.val === '') {
397
360
  this.log.info('Current cookies state was cleared, trying to obtain new cookies.');
398
361
  this._successFullPolls = 0;
362
+ this.cookie.currentCookie = '';
399
363
  await this.cookie.loginToGetNewCookies();
400
- if (this.cookie.isValid()) {
401
- await this.sendRequest();
402
- }
403
364
  } else {
404
365
  this.log.info(
405
366
  'Current cookies state was changed from outside the adapter, updating internal cookie store.',
406
367
  );
407
368
  this.cookie.currentCookie = state.val as string;
408
369
  }
370
+ if (this.cookie.isValid()) {
371
+ await this.sendRequest();
372
+ }
409
373
  }
410
374
  }
411
375