iobroker.google-sharedlocations2 0.0.3 → 0.1.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 +5 -0
- package/admin/jsonConfig.json +131 -47
- package/io-package.json +27 -2
- package/package.json +1 -1
- package/src/lib/Cookie.ts +220 -0
- package/src/lib/Fence.ts +62 -0
- package/src/lib/User.ts +53 -0
- package/src/lib/adapter-config.d.ts +9 -0
- package/src/main.ts +134 -170
package/README.md
CHANGED
|
@@ -27,6 +27,11 @@ 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.1.0 (2026-02-02)
|
|
31
|
+
* (Garfonso) added: support for places
|
|
32
|
+
* (Garfonso) added: support for fences
|
|
33
|
+
* (Garfonso) try to prevent login as much as possible.
|
|
34
|
+
|
|
30
35
|
### 0.0.3 (2026-01-28)
|
|
31
36
|
* (Garfonso) prevent login if no username and password is set
|
|
32
37
|
* (Garfonso) fix tests
|
package/admin/jsonConfig.json
CHANGED
|
@@ -1,51 +1,135 @@
|
|
|
1
1
|
{
|
|
2
2
|
"i18n": true,
|
|
3
|
-
"type": "
|
|
4
|
-
"items":
|
|
5
|
-
|
|
6
|
-
"type": "
|
|
7
|
-
"label": "
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
3
|
+
"type": "tabs",
|
|
4
|
+
"items": [
|
|
5
|
+
{
|
|
6
|
+
"type": "panel",
|
|
7
|
+
"label": "Google Settings",
|
|
8
|
+
"items": {
|
|
9
|
+
"_explanation": {
|
|
10
|
+
"type": "staticText",
|
|
11
|
+
"label": "Please provide the credentials of an ioBroker Google Account which you share your location with using the Google Maps App. It is strongly recommended to create a dedicated Google Account for this purpose.",
|
|
12
|
+
"newLine": true,
|
|
13
|
+
"xs": 12,
|
|
14
|
+
"sm": 12,
|
|
15
|
+
"md": 12,
|
|
16
|
+
"lg": 12,
|
|
17
|
+
"xl": 12
|
|
18
|
+
},
|
|
19
|
+
"googleUsername": {
|
|
20
|
+
"type": "text",
|
|
21
|
+
"label": "Username of ioBroker Google Account",
|
|
22
|
+
"newLine": true,
|
|
23
|
+
"xs": 12,
|
|
24
|
+
"sm": 12,
|
|
25
|
+
"md": 6,
|
|
26
|
+
"lg": 6,
|
|
27
|
+
"xl": 6
|
|
28
|
+
},
|
|
29
|
+
"googlePassword": {
|
|
30
|
+
"type": "password",
|
|
31
|
+
"label": "Password of ioBroker Google Account",
|
|
32
|
+
"visible": true,
|
|
33
|
+
"xs": 12,
|
|
34
|
+
"sm": 12,
|
|
35
|
+
"md": 6,
|
|
36
|
+
"lg": 6,
|
|
37
|
+
"xl": 6
|
|
38
|
+
},
|
|
39
|
+
"pollInterval": {
|
|
40
|
+
"type": "number",
|
|
41
|
+
"label": "Polling Interval (in seconds, at least 60 seconds)",
|
|
42
|
+
"default": 300,
|
|
43
|
+
"unit": "s",
|
|
44
|
+
"min": 60,
|
|
45
|
+
"max": 86400,
|
|
46
|
+
"step": 10,
|
|
47
|
+
"newLine": true,
|
|
48
|
+
"xs": 12,
|
|
49
|
+
"sm": 12,
|
|
50
|
+
"md": 6,
|
|
51
|
+
"lg": 6,
|
|
52
|
+
"xl": 6
|
|
53
|
+
}
|
|
54
|
+
}
|
|
14
55
|
},
|
|
15
|
-
|
|
16
|
-
"type": "
|
|
17
|
-
"label": "
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
56
|
+
{
|
|
57
|
+
"type": "panel",
|
|
58
|
+
"label": "Geofencing Settings",
|
|
59
|
+
"items": {
|
|
60
|
+
"placesInstance": {
|
|
61
|
+
"type": "instance",
|
|
62
|
+
"label": "Instance of iobroker.places to use (set to -1 to auto-detect)",
|
|
63
|
+
"adapter": "places",
|
|
64
|
+
"allowDeactivate": true,
|
|
65
|
+
"newLine": true,
|
|
66
|
+
"xs": 12,
|
|
67
|
+
"sm": 12,
|
|
68
|
+
"md": 6,
|
|
69
|
+
"lg": 6,
|
|
70
|
+
"xl": 6
|
|
71
|
+
},
|
|
72
|
+
"fences": {
|
|
73
|
+
"type": "table",
|
|
74
|
+
"clone": false,
|
|
75
|
+
"newLine": true,
|
|
76
|
+
"xs": 12,
|
|
77
|
+
"sm": 12,
|
|
78
|
+
"md": 12,
|
|
79
|
+
"lg": 12,
|
|
80
|
+
"xl": 12,
|
|
81
|
+
"label": "Fences, will create boolean states for each fence to indicate if user is within the fence",
|
|
82
|
+
"items": [
|
|
83
|
+
{
|
|
84
|
+
"type": "text",
|
|
85
|
+
"title": "Name of fence",
|
|
86
|
+
"width": "20%",
|
|
87
|
+
"attr": "name",
|
|
88
|
+
"sort": true,
|
|
89
|
+
"default": ""
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"type": "number",
|
|
93
|
+
"title": "Latitude",
|
|
94
|
+
"width": "20%",
|
|
95
|
+
"attr": "latitude"
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"type": "number",
|
|
99
|
+
"title": "Longitude",
|
|
100
|
+
"width": "20%",
|
|
101
|
+
"attr": "longitude"
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"type": "number",
|
|
105
|
+
"title": "Radius (m)",
|
|
106
|
+
"width": "5%",
|
|
107
|
+
"attr": "radius",
|
|
108
|
+
"default": 100
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"type": "selectSendTo",
|
|
112
|
+
"command": "getUsers",
|
|
113
|
+
"attr": "user",
|
|
114
|
+
"multiple": false,
|
|
115
|
+
"title": "Users",
|
|
116
|
+
"width": "20%",
|
|
117
|
+
"default": ""
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"type": "text",
|
|
121
|
+
"title": "Fence ID",
|
|
122
|
+
"width": "15%",
|
|
123
|
+
"attr": "fenceId",
|
|
124
|
+
"defaultFunc": "data.name + data.user",
|
|
125
|
+
"alsoDependsOn": [
|
|
126
|
+
"name",
|
|
127
|
+
"user"
|
|
128
|
+
]
|
|
129
|
+
}
|
|
130
|
+
]
|
|
131
|
+
}
|
|
132
|
+
}
|
|
49
133
|
}
|
|
50
|
-
|
|
51
|
-
}
|
|
134
|
+
]
|
|
135
|
+
}
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "google-sharedlocations2",
|
|
4
|
-
"version": "0.0
|
|
4
|
+
"version": "0.1.0",
|
|
5
5
|
"news": {
|
|
6
|
+
"0.1.0": {
|
|
7
|
+
"en": "added: support for places\nadded: support for fences\ntry to prevent login as much as possible.",
|
|
8
|
+
"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.",
|
|
9
|
+
"ru": "добавлено: поддержка мест\nдобавлено: поддержка заборов\nстарайтесь максимально предотвратить вход.",
|
|
10
|
+
"pt": "adicionado: suporte para locais\nadicionado: suporte para cercas\ntentar evitar o login o máximo possível.",
|
|
11
|
+
"nl": "toegevoegd: ondersteuning voor plaatsen\ntoegevoegd: steun voor hekken\nproberen zo veel mogelijk inloggen te voorkomen.",
|
|
12
|
+
"fr": "ajouté: soutien aux places\najouté: soutien aux clôtures\nessayer d'éviter la connexion autant que possible.",
|
|
13
|
+
"it": "aggiunto: supporto per i luoghi\naggiunto: supporto per recinzioni\ncercare di impedire il login il più possibile.",
|
|
14
|
+
"es": "añadido: apoyo a los lugares\nañadido: soporte para vallas\ntratar de prevenir la entrada tanto como sea posible.",
|
|
15
|
+
"pl": "dodane: wsparcie dla miejsc\ndodane: wsparcie dla ogrodzeń\nspróbuj zapobiec logowaniu jak najwięcej.",
|
|
16
|
+
"uk": "додано: підтримка місць\nдоданий: підтримка парканів\nнамагатися попередити логін якомога простіше.",
|
|
17
|
+
"zh-cn": "添加:对位置的支持\n添加:支持围栏\n尽量防止登录."
|
|
18
|
+
},
|
|
6
19
|
"0.0.3": {
|
|
7
20
|
"en": "prevent login if no username and password is set\nfix tests",
|
|
8
21
|
"de": "verhindern login, wenn kein benutzername und passwort eingestellt ist\ntests repariert",
|
|
@@ -95,6 +108,7 @@
|
|
|
95
108
|
"compact": true,
|
|
96
109
|
"connectionType": "cloud",
|
|
97
110
|
"dataSource": "poll",
|
|
111
|
+
"messagebox": true,
|
|
98
112
|
"adminUI": {
|
|
99
113
|
"config": "json"
|
|
100
114
|
},
|
|
@@ -153,7 +167,9 @@
|
|
|
153
167
|
"native": {
|
|
154
168
|
"googleUsername": "",
|
|
155
169
|
"googlePassword": "",
|
|
156
|
-
"pollInterval": 60
|
|
170
|
+
"pollInterval": 60,
|
|
171
|
+
"placesInstance": "",
|
|
172
|
+
"fences": []
|
|
157
173
|
},
|
|
158
174
|
"protectedNative": [
|
|
159
175
|
"googlePassword"
|
|
@@ -197,6 +213,15 @@
|
|
|
197
213
|
"def": ""
|
|
198
214
|
}
|
|
199
215
|
},
|
|
216
|
+
{
|
|
217
|
+
"_id": "fences",
|
|
218
|
+
"type": "folder",
|
|
219
|
+
"common": {
|
|
220
|
+
"name": "Geofences",
|
|
221
|
+
"desc": "States for fences defined in the config."
|
|
222
|
+
},
|
|
223
|
+
"native": {}
|
|
224
|
+
},
|
|
200
225
|
{
|
|
201
226
|
"_id": "users",
|
|
202
227
|
"type": "folder",
|
package/package.json
CHANGED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import type { GoogleSharedlocations2 } from '../main';
|
|
3
|
+
import puppeteer from 'puppeteer';
|
|
4
|
+
import type { Browser } from 'puppeteer';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Helper class to manage Google cookies.
|
|
8
|
+
*/
|
|
9
|
+
export class Cookie {
|
|
10
|
+
currentCookie: string;
|
|
11
|
+
username?: string;
|
|
12
|
+
password?: string;
|
|
13
|
+
adapter: GoogleSharedlocations2;
|
|
14
|
+
log;
|
|
15
|
+
private browser: Browser | null = null;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Construct cookie helper
|
|
19
|
+
*
|
|
20
|
+
* @param adapter - adapter instance
|
|
21
|
+
*/
|
|
22
|
+
constructor(adapter: GoogleSharedlocations2) {
|
|
23
|
+
this.currentCookie = '';
|
|
24
|
+
this.username = '';
|
|
25
|
+
this.password = '';
|
|
26
|
+
this.adapter = adapter;
|
|
27
|
+
this.log = adapter.log;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Initialize the cookie helper by loading the cookie from state.
|
|
32
|
+
*/
|
|
33
|
+
async init(): Promise<void> {
|
|
34
|
+
this.username = this.adapter.config.googleUsername;
|
|
35
|
+
this.password = this.adapter.config.googlePassword;
|
|
36
|
+
try {
|
|
37
|
+
const state = await this.adapter.getStateAsync('info.currentCookies');
|
|
38
|
+
if (state && state.val && typeof state.val === 'string') {
|
|
39
|
+
this.currentCookie = state.val;
|
|
40
|
+
this.log?.debug('Loaded cookie from state.');
|
|
41
|
+
} else {
|
|
42
|
+
this.currentCookie = '';
|
|
43
|
+
this.log?.debug('No cookie found in state, trying to log in to get new one.');
|
|
44
|
+
await this.loginToGetNewCookies();
|
|
45
|
+
}
|
|
46
|
+
} catch (err: any) {
|
|
47
|
+
this.log?.error(`Error loading cookie from state: ${err}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Store the current cookie in an iobroker state.
|
|
53
|
+
*/
|
|
54
|
+
async storeCookie(): Promise<void> {
|
|
55
|
+
try {
|
|
56
|
+
await this.adapter.setStateAsync('info.currentCookies', this.currentCookie, true);
|
|
57
|
+
} catch (err: any) {
|
|
58
|
+
this.log?.error(`Error storing cookie: ${err}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Augment the current cookie with data from the 'set-cookie' header.
|
|
64
|
+
*
|
|
65
|
+
* @param headers - HTTP headers of axios response
|
|
66
|
+
*/
|
|
67
|
+
async augmentCookieFromHeader(headers: Record<string, any>): Promise<void> {
|
|
68
|
+
if (headers['set-cookie'] && headers['set-cookie'].length) {
|
|
69
|
+
this.log?.debug('New header received.');
|
|
70
|
+
const oldLength = this.currentCookie.length;
|
|
71
|
+
const cookies = this.currentCookie.split('; ').map(c => c.split('='));
|
|
72
|
+
|
|
73
|
+
//split old cookie and new cookie. Update single values.
|
|
74
|
+
for (const header of headers['set-cookie']) {
|
|
75
|
+
const incomingCookies = header.split('; ');
|
|
76
|
+
for (const cookie of incomingCookies) {
|
|
77
|
+
const [name, value] = cookie.split('=');
|
|
78
|
+
const cIndex = cookies.findIndex(c => c[0] === name);
|
|
79
|
+
if (cIndex < 0) {
|
|
80
|
+
cookies.push([name, value]); //add
|
|
81
|
+
} else {
|
|
82
|
+
cookies[cIndex][1] = value; //update
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
this.currentCookie = cookies.map(cv => cv.join('=')).join('; ');
|
|
88
|
+
this.log?.debug(`Cookie updated. Length: ${oldLength} -> ${this.currentCookie.length}`);
|
|
89
|
+
return this.storeCookie();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Improve the current cookie by making a request to Google My Account page.
|
|
95
|
+
*/
|
|
96
|
+
async improveCookie(): Promise<void> {
|
|
97
|
+
//see https://github.com/costastf/locationsharinglib/blob/master/locationsharinglib/locationsharinglib.py#L105
|
|
98
|
+
const options = {
|
|
99
|
+
url: 'https://myaccount.google.com/?hl=en',
|
|
100
|
+
headers: {
|
|
101
|
+
Cookie: this.currentCookie,
|
|
102
|
+
},
|
|
103
|
+
method: 'get',
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
const response = await axios(options);
|
|
108
|
+
|
|
109
|
+
if (response.status !== 200) {
|
|
110
|
+
this.log?.error(`Failed improving cookie: ${response.status}`);
|
|
111
|
+
} else {
|
|
112
|
+
await this.augmentCookieFromHeader(response.headers);
|
|
113
|
+
}
|
|
114
|
+
} catch (err: any) {
|
|
115
|
+
this.log?.error(err);
|
|
116
|
+
this.log?.info('Connection to google maps failure.');
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Login to Google using puppeteer to get new cookies.
|
|
122
|
+
*/
|
|
123
|
+
async loginToGetNewCookies(): Promise<boolean> {
|
|
124
|
+
try {
|
|
125
|
+
if (this.browser) {
|
|
126
|
+
this.log.info('Seems we are already trying to log in. Aborting new login attempt.');
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
if (!this.username || !this.password) {
|
|
130
|
+
this.log.warn('Google username or password not set in adapter configuration. Can not login.');
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
this.log.info('Trying to login to Google to get new cookies.');
|
|
135
|
+
//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
|
+
});
|
|
153
|
+
|
|
154
|
+
this.log.debug('going to google login page.');
|
|
155
|
+
await page.goto(
|
|
156
|
+
'https://accounts.google.com/ServiceLogin?hl=de&continue=https://www.google.com/maps&gae=cb-eomtm',
|
|
157
|
+
{
|
|
158
|
+
waitUntil: 'networkidle2',
|
|
159
|
+
timeout: 60000,
|
|
160
|
+
},
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
this.log.debug('filling in username and clicking next.');
|
|
164
|
+
await page.locator('#identifierId').fill(this.username);
|
|
165
|
+
//is this enough, or do we need to search button in this div?
|
|
166
|
+
await page.locator('#identifierNext').click();
|
|
167
|
+
//waiting for #password fails in headles.. :-(
|
|
168
|
+
await page.waitForNetworkIdle({ idleTime: 2000 });
|
|
169
|
+
|
|
170
|
+
this.log.debug('filling in password and clicking next.');
|
|
171
|
+
//do we need to wait until page is loaded / rendered here?
|
|
172
|
+
await page.locator('input[type="password"]').fill(this.password);
|
|
173
|
+
this.log.debug('clicking password next button.');
|
|
174
|
+
await page.locator('#passwordNext').click();
|
|
175
|
+
//await page.waitForNetworkIdle({ idleTime: 2000 }); -> does never happen in headless.. :-/
|
|
176
|
+
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
177
|
+
|
|
178
|
+
await page.goto('https://www.google.com/maps');
|
|
179
|
+
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;
|
|
197
|
+
return true;
|
|
198
|
+
} catch (e) {
|
|
199
|
+
this.log.error(`Error in puppeteer: ${(e as Error).message}`);
|
|
200
|
+
}
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Clean up on unload.
|
|
206
|
+
*/
|
|
207
|
+
async cleanUp(): Promise<void> {
|
|
208
|
+
if (this.browser) {
|
|
209
|
+
await this.browser.close();
|
|
210
|
+
this.browser = null;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Check if the current cookie is valid.
|
|
216
|
+
*/
|
|
217
|
+
isValid(): boolean {
|
|
218
|
+
return this.currentCookie.length > 50;
|
|
219
|
+
}
|
|
220
|
+
}
|
package/src/lib/Fence.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { User } from './User';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Geofence class
|
|
5
|
+
*/
|
|
6
|
+
export class Fence {
|
|
7
|
+
name: string;
|
|
8
|
+
lat: number;
|
|
9
|
+
long: number;
|
|
10
|
+
radius: number;
|
|
11
|
+
user: string;
|
|
12
|
+
fenceId: string;
|
|
13
|
+
valid: boolean = true;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Constructor
|
|
17
|
+
*
|
|
18
|
+
* @param name of the fence
|
|
19
|
+
* @param lat of the fence center
|
|
20
|
+
* @param long of the fence center
|
|
21
|
+
* @param radius in meters
|
|
22
|
+
* @param user user id to check
|
|
23
|
+
* @param fenceId iobroker state id to set
|
|
24
|
+
*/
|
|
25
|
+
constructor(name: string, lat: number, long: number, radius: number, user: string, fenceId: string) {
|
|
26
|
+
this.name = name;
|
|
27
|
+
this.lat = lat;
|
|
28
|
+
this.long = long;
|
|
29
|
+
this.radius = radius;
|
|
30
|
+
this.user = user;
|
|
31
|
+
this.fenceId = fenceId;
|
|
32
|
+
this.valid = !!(lat && long && radius > 0 && user && fenceId);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private toRadians(degrees: number): number {
|
|
36
|
+
return degrees * (Math.PI / 180);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
private haversineDistance(lat1: number, lon1: number, lat2: number, lon2: number): number {
|
|
40
|
+
const R = 6371000; // Earth radius in meters
|
|
41
|
+
const dLat = this.toRadians(lat2 - lat1);
|
|
42
|
+
const dLon = this.toRadians(lon2 - lon1);
|
|
43
|
+
const a =
|
|
44
|
+
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
|
45
|
+
Math.cos(this.toRadians(lat1)) * Math.cos(this.toRadians(lat2)) * Math.sin(dLon / 2) * Math.sin(dLon / 2);
|
|
46
|
+
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
|
47
|
+
return R * c;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Check if a point is inside the fence.
|
|
52
|
+
*
|
|
53
|
+
* @param user to check
|
|
54
|
+
*/
|
|
55
|
+
isInsideFence(user: User): boolean {
|
|
56
|
+
if (this.valid && user.id && user.lat && user.long) {
|
|
57
|
+
const distance = this.haversineDistance(this.lat, this.long, user.lat, user.long);
|
|
58
|
+
return distance <= this.radius;
|
|
59
|
+
}
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
package/src/lib/User.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User class representing a user with location and info data.
|
|
3
|
+
*/
|
|
4
|
+
export class User {
|
|
5
|
+
id: string | null;
|
|
6
|
+
name?: string;
|
|
7
|
+
photoURL?: string;
|
|
8
|
+
lat?: number;
|
|
9
|
+
long?: number;
|
|
10
|
+
address?: string;
|
|
11
|
+
battery?: number;
|
|
12
|
+
timestamp?: number;
|
|
13
|
+
accuracy?: number;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Creates a User instance from location data.
|
|
17
|
+
*
|
|
18
|
+
* @param locationData - Array containing user location and info data
|
|
19
|
+
*/
|
|
20
|
+
constructor(locationData: Array<any>) {
|
|
21
|
+
this.id = null;
|
|
22
|
+
if (locationData && Array.isArray(locationData)) {
|
|
23
|
+
// locationData present
|
|
24
|
+
if (locationData[0] && locationData[0][0]) {
|
|
25
|
+
this.id = locationData[0][0];
|
|
26
|
+
}
|
|
27
|
+
if (locationData[0] && locationData[0][1]) {
|
|
28
|
+
this.photoURL = locationData[0][1];
|
|
29
|
+
}
|
|
30
|
+
if (locationData[0] && locationData[0][3]) {
|
|
31
|
+
this.name = locationData[0][3];
|
|
32
|
+
}
|
|
33
|
+
if (locationData[1] && locationData[1][1] && locationData[1][1][2]) {
|
|
34
|
+
this.lat = locationData[1][1][2];
|
|
35
|
+
}
|
|
36
|
+
if (locationData[1] && locationData[1][1] && locationData[1][1][1]) {
|
|
37
|
+
this.long = locationData[1][1][1];
|
|
38
|
+
}
|
|
39
|
+
if (locationData[1] && locationData[1][4]) {
|
|
40
|
+
this.address = locationData[1][4];
|
|
41
|
+
}
|
|
42
|
+
if (locationData[13] && locationData[13][1]) {
|
|
43
|
+
this.battery = locationData[13][1];
|
|
44
|
+
}
|
|
45
|
+
if (locationData[1] && locationData[1][2]) {
|
|
46
|
+
this.timestamp = locationData[1][2];
|
|
47
|
+
}
|
|
48
|
+
if (locationData[1] && locationData[1][3]) {
|
|
49
|
+
this.accuracy = locationData[1][3];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -7,6 +7,15 @@ declare global {
|
|
|
7
7
|
googleUsername: string;
|
|
8
8
|
googlePassword: string;
|
|
9
9
|
pollInterval: number;
|
|
10
|
+
placesInstance: string;
|
|
11
|
+
fences: Array<{
|
|
12
|
+
name: string;
|
|
13
|
+
latitude: number;
|
|
14
|
+
longitude: number;
|
|
15
|
+
radius: number;
|
|
16
|
+
user: string;
|
|
17
|
+
fenceId: string;
|
|
18
|
+
}>;
|
|
10
19
|
}
|
|
11
20
|
}
|
|
12
21
|
}
|
package/src/main.ts
CHANGED
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
// you need to create an adapter
|
|
7
7
|
import * as utils from '@iobroker/adapter-core';
|
|
8
8
|
|
|
9
|
-
import
|
|
10
|
-
import
|
|
9
|
+
import { User } from './lib/User';
|
|
10
|
+
import { Fence } from './lib/Fence';
|
|
11
|
+
import { Cookie } from './lib/Cookie';
|
|
11
12
|
import axios from 'axios';
|
|
12
13
|
|
|
13
14
|
//used to test timeout against
|
|
@@ -16,13 +17,22 @@ const MAX_INT32 = 2 ** 31 - 1; // 2147483647 (hex 0x7FFFFFFF)
|
|
|
16
17
|
// Load your modules here, e.g.:
|
|
17
18
|
// import * as fs from 'fs';
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
/**
|
|
21
|
+
* The adapter class
|
|
22
|
+
*/
|
|
23
|
+
export class GoogleSharedlocations2 extends utils.Adapter {
|
|
21
24
|
_pollTimeout: ioBroker.Timeout | undefined;
|
|
22
25
|
_pollInterval: number = 300;
|
|
23
26
|
_successFullPolls: number = 1; // let us try a relogin at start, if cookie does not work.
|
|
24
|
-
|
|
27
|
+
_users: Record<string, User> = {};
|
|
28
|
+
fences: Fence[] = [];
|
|
29
|
+
cookie: Cookie;
|
|
25
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Creates an instance of the adapter.
|
|
33
|
+
*
|
|
34
|
+
* @param options - adapter options
|
|
35
|
+
*/
|
|
26
36
|
public constructor(options: Partial<utils.AdapterOptions> = {}) {
|
|
27
37
|
super({
|
|
28
38
|
...options,
|
|
@@ -31,8 +41,9 @@ class GoogleSharedlocations2 extends utils.Adapter {
|
|
|
31
41
|
this.on('ready', this.onReady.bind(this));
|
|
32
42
|
this.on('stateChange', this.onStateChange.bind(this));
|
|
33
43
|
// this.on('objectChange', this.onObjectChange.bind(this));
|
|
34
|
-
|
|
44
|
+
this.on('message', this.onMessage.bind(this));
|
|
35
45
|
this.on('unload', this.onUnload.bind(this));
|
|
46
|
+
this.cookie = new Cookie(this);
|
|
36
47
|
}
|
|
37
48
|
|
|
38
49
|
/**
|
|
@@ -43,15 +54,7 @@ class GoogleSharedlocations2 extends utils.Adapter {
|
|
|
43
54
|
|
|
44
55
|
// Reset the connection indicator during startup
|
|
45
56
|
await this.setState('info.connection', false, true);
|
|
46
|
-
|
|
47
|
-
if (!this._cookies) {
|
|
48
|
-
if (!this.config.googleUsername || !this.config.googlePassword) {
|
|
49
|
-
this.log.error('Google username or password not set in adapter configuration!');
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
await this.loginToGetNewCookies();
|
|
53
|
-
}
|
|
54
|
-
|
|
57
|
+
await this.cookie.init();
|
|
55
58
|
await this.subscribeStatesAsync('info.currentCookies');
|
|
56
59
|
|
|
57
60
|
//sanitize polling interval:
|
|
@@ -66,20 +69,67 @@ class GoogleSharedlocations2 extends utils.Adapter {
|
|
|
66
69
|
this._pollInterval = MAX_INT32;
|
|
67
70
|
}
|
|
68
71
|
|
|
72
|
+
//read fences:
|
|
73
|
+
for (const fenceConfig of this.config.fences || []) {
|
|
74
|
+
const fence = new Fence(
|
|
75
|
+
fenceConfig.name,
|
|
76
|
+
fenceConfig.latitude,
|
|
77
|
+
fenceConfig.longitude,
|
|
78
|
+
fenceConfig.radius,
|
|
79
|
+
fenceConfig.user,
|
|
80
|
+
fenceConfig.fenceId,
|
|
81
|
+
);
|
|
82
|
+
if (fence.valid) {
|
|
83
|
+
this.fences.push(fence);
|
|
84
|
+
await this.setObjectNotExistsAsync(`fences.${fence.fenceId}`, {
|
|
85
|
+
type: 'state',
|
|
86
|
+
common: {
|
|
87
|
+
name: `${fence.name}`,
|
|
88
|
+
type: 'boolean',
|
|
89
|
+
read: true,
|
|
90
|
+
write: false,
|
|
91
|
+
role: 'sensor',
|
|
92
|
+
},
|
|
93
|
+
native: {},
|
|
94
|
+
});
|
|
95
|
+
} else {
|
|
96
|
+
this.log.warn(`Fence ${fenceConfig.name} is not valid and will be ignored.`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
//clear old fences:
|
|
100
|
+
const adapterObjects = await this.getAdapterObjectsAsync();
|
|
101
|
+
for (const objId of Object.keys(adapterObjects)) {
|
|
102
|
+
if (objId.startsWith(`${this.namespace}.fences.`) || objId.startsWith('fences.')) {
|
|
103
|
+
const fenceId = objId.split('.').pop() || '';
|
|
104
|
+
const found = this.fences.find(f => f.fenceId === fenceId);
|
|
105
|
+
if (!found) {
|
|
106
|
+
this.log.info(`Deleting old fence state ${objId} as it is not in configuration anymore.`);
|
|
107
|
+
await this.delObjectAsync(objId);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
69
112
|
//start polling positions
|
|
70
113
|
this.pollPositions();
|
|
71
|
-
if (this.
|
|
114
|
+
if (this.cookie.isValid()) {
|
|
72
115
|
await this.sendRequest();
|
|
73
116
|
}
|
|
74
117
|
}
|
|
75
118
|
|
|
76
119
|
private pollPositions(): void {
|
|
77
120
|
this._pollTimeout = this.setTimeout(async () => {
|
|
78
|
-
if (!this.
|
|
121
|
+
if (!this.cookie.isValid()) {
|
|
79
122
|
this.log.debug('Cannot poll positions, no cookies available!');
|
|
80
123
|
} else {
|
|
81
124
|
this.log.debug('Polling positions with current cookies.');
|
|
125
|
+
const lastSuccessPolls = this._successFullPolls;
|
|
82
126
|
await this.sendRequest();
|
|
127
|
+
if (this._successFullPolls > 0 && lastSuccessPolls !== this._successFullPolls) {
|
|
128
|
+
if (this._successFullPolls % 10 === 0) {
|
|
129
|
+
//try to get some more headers from google:
|
|
130
|
+
await this.cookie.improveCookie();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
83
133
|
}
|
|
84
134
|
//schedule next poll
|
|
85
135
|
return this.pollPositions();
|
|
@@ -87,7 +137,7 @@ class GoogleSharedlocations2 extends utils.Adapter {
|
|
|
87
137
|
}
|
|
88
138
|
|
|
89
139
|
private async sendRequest(): Promise<void> {
|
|
90
|
-
if (!this.
|
|
140
|
+
if (!this.cookie.isValid()) {
|
|
91
141
|
this.log.error('Cannot send request, no cookies available!');
|
|
92
142
|
await this.setState('info.connection', false, true);
|
|
93
143
|
return;
|
|
@@ -99,7 +149,7 @@ class GoogleSharedlocations2 extends utils.Adapter {
|
|
|
99
149
|
method: 'GET',
|
|
100
150
|
url: 'https://www.google.com/maps/rpc/locationsharing/read',
|
|
101
151
|
headers: {
|
|
102
|
-
Cookie: this.
|
|
152
|
+
Cookie: this.cookie.currentCookie,
|
|
103
153
|
},
|
|
104
154
|
params: {
|
|
105
155
|
authuser: 2,
|
|
@@ -120,71 +170,36 @@ class GoogleSharedlocations2 extends utils.Adapter {
|
|
|
120
170
|
this._successFullPolls += 1;
|
|
121
171
|
await this.setState('info.connection', true, true);
|
|
122
172
|
for (const location of locations) {
|
|
123
|
-
|
|
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
|
+
}
|
|
124
180
|
}
|
|
125
181
|
} else {
|
|
126
182
|
this.log.info('No shared locations found in the response, probably not logged in.');
|
|
127
183
|
if (this._successFullPolls > 0) {
|
|
128
184
|
//try to get new cookie:
|
|
129
|
-
|
|
185
|
+
this._successFullPolls = 0;
|
|
186
|
+
await this.cookie.loginToGetNewCookies();
|
|
130
187
|
}
|
|
131
188
|
}
|
|
132
189
|
} catch (e) {
|
|
133
190
|
this.log.error(`Error during request: ${(e as Error).message}`);
|
|
134
191
|
if (this._successFullPolls > 0) {
|
|
135
192
|
//try to get new cookie:
|
|
136
|
-
|
|
193
|
+
this._successFullPolls = 0;
|
|
194
|
+
await this.cookie.loginToGetNewCookies();
|
|
137
195
|
}
|
|
138
196
|
}
|
|
139
197
|
}
|
|
140
198
|
|
|
141
|
-
private async fillIntoObjects(
|
|
199
|
+
private async fillIntoObjects(user: User): Promise<void> {
|
|
142
200
|
try {
|
|
143
|
-
const user = {
|
|
144
|
-
id: undefined,
|
|
145
|
-
photoURL: undefined,
|
|
146
|
-
name: undefined,
|
|
147
|
-
lat: undefined,
|
|
148
|
-
long: undefined,
|
|
149
|
-
address: undefined,
|
|
150
|
-
battery: undefined,
|
|
151
|
-
timestamp: undefined,
|
|
152
|
-
accuracy: undefined,
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
if (locationData && Array.isArray(locationData)) {
|
|
156
|
-
// locationData present
|
|
157
|
-
if (locationData[0] && locationData[0][0]) {
|
|
158
|
-
user.id = locationData[0][0];
|
|
159
|
-
}
|
|
160
|
-
if (locationData[0] && locationData[0][1]) {
|
|
161
|
-
user.photoURL = locationData[0][1];
|
|
162
|
-
}
|
|
163
|
-
if (locationData[0] && locationData[0][3]) {
|
|
164
|
-
user.name = locationData[0][3];
|
|
165
|
-
}
|
|
166
|
-
if (locationData[1] && locationData[1][1] && locationData[1][1][2]) {
|
|
167
|
-
user.lat = locationData[1][1][2];
|
|
168
|
-
}
|
|
169
|
-
if (locationData[1] && locationData[1][1] && locationData[1][1][1]) {
|
|
170
|
-
user.long = locationData[1][1][1];
|
|
171
|
-
}
|
|
172
|
-
if (locationData[1] && locationData[1][4]) {
|
|
173
|
-
user.address = locationData[1][4];
|
|
174
|
-
}
|
|
175
|
-
if (locationData[13] && locationData[13][1]) {
|
|
176
|
-
user.battery = locationData[13][1];
|
|
177
|
-
}
|
|
178
|
-
if (locationData[1] && locationData[1][2]) {
|
|
179
|
-
user.timestamp = locationData[1][2];
|
|
180
|
-
}
|
|
181
|
-
if (locationData[1] && locationData[1][3]) {
|
|
182
|
-
user.accuracy = locationData[1][3];
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
201
|
if (user.id) {
|
|
187
|
-
const basepath = `users.${user.id
|
|
202
|
+
const basepath = `users.${user.id}`;
|
|
188
203
|
const deviceObj = {
|
|
189
204
|
_id: basepath,
|
|
190
205
|
type: 'device',
|
|
@@ -307,84 +322,28 @@ class GoogleSharedlocations2 extends utils.Adapter {
|
|
|
307
322
|
}
|
|
308
323
|
}
|
|
309
324
|
|
|
310
|
-
private async
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
return;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
this.log.info('Trying to login to Google to get new cookies.');
|
|
322
|
-
this._successFullPolls = 0;
|
|
323
|
-
|
|
324
|
-
//testing puppeteer:
|
|
325
|
-
this.log.debug('Starting browser.');
|
|
326
|
-
this._browser = await puppeteer.launch({
|
|
327
|
-
headless: true,
|
|
328
|
-
args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-blink-features=AutomationControlled'],
|
|
329
|
-
ignoreDefaultArgs: ['--enable-automation'], //hide automation flag, did not help.
|
|
330
|
-
});
|
|
331
|
-
this.log.debug('browser started, opening new page.');
|
|
332
|
-
const page = await this._browser.newPage();
|
|
333
|
-
|
|
334
|
-
//hide puppeteer automation flag
|
|
335
|
-
await page.evaluateOnNewDocument(() => {
|
|
336
|
-
Object.defineProperty(navigator, 'webdriver', { get: () => false });
|
|
337
|
-
});
|
|
338
|
-
await page.setUserAgent({
|
|
339
|
-
userAgent:
|
|
340
|
-
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|
325
|
+
private async notifyPlaces(user: User): Promise<void> {
|
|
326
|
+
if (this.config.placesInstance && user.id && user.lat && user.long) {
|
|
327
|
+
await this.sendToAsync(this.config.placesInstance, {
|
|
328
|
+
user: user.name,
|
|
329
|
+
latitude: user.lat,
|
|
330
|
+
longitude: user.long,
|
|
331
|
+
timestamp: user.timestamp || Date.now(),
|
|
332
|
+
address: user.address,
|
|
341
333
|
});
|
|
334
|
+
}
|
|
335
|
+
}
|
|
342
336
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
this.log.debug('filling in username and clicking next.');
|
|
353
|
-
await page.locator('#identifierId').fill(this.config.googleUsername);
|
|
354
|
-
//is this enough, or do we need to search button in this div?
|
|
355
|
-
await page.locator('#identifierNext').click();
|
|
356
|
-
//waiting for #password fails in headles.. :-(
|
|
357
|
-
await page.waitForNetworkIdle({ idleTime: 2000 });
|
|
358
|
-
|
|
359
|
-
this.log.debug('filling in password and clicking next.');
|
|
360
|
-
//do we need to wait until page is loaded / rendered here?
|
|
361
|
-
await page.locator('input[type="password"]').fill(this.config.googlePassword);
|
|
362
|
-
this.log.debug('clicking password next button.');
|
|
363
|
-
await page.locator('#passwordNext').click();
|
|
364
|
-
//await page.waitForNetworkIdle({ idleTime: 2000 }); -> does never happen in headless.. :-/
|
|
365
|
-
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
366
|
-
|
|
367
|
-
await page.goto('https://www.google.com/maps');
|
|
368
|
-
this.log.debug('getting cookies.');
|
|
369
|
-
//using deprecated function, but browser.cookies just does not work...???
|
|
370
|
-
const cookies = await page.cookies();
|
|
371
|
-
|
|
372
|
-
this._cookies = cookies
|
|
373
|
-
.filter(c => c.domain.includes('google'))
|
|
374
|
-
.map(c => `${c.name}=${c.value}`)
|
|
375
|
-
.join('; ');
|
|
376
|
-
//this.log.debug(this._cookies);
|
|
377
|
-
//console.log(this._cookies);
|
|
378
|
-
await this._browser.close();
|
|
379
|
-
if (this._cookies.length < 50) {
|
|
380
|
-
this.log.warn('Cookie string seems too short, login probably failed!');
|
|
381
|
-
} else {
|
|
382
|
-
this.log.info('Obtained new cookies from Google login.');
|
|
383
|
-
await this.setState('info.currentCookies', { val: this._cookies, ack: true });
|
|
337
|
+
private async checkFences(user: User): Promise<void> {
|
|
338
|
+
for (const fence of this.fences) {
|
|
339
|
+
if (fence.valid && fence.user === user.id) {
|
|
340
|
+
const inside = fence.isInsideFence(user);
|
|
341
|
+
await this.setStateChangedAsync(`fences.${fence.fenceId}`, {
|
|
342
|
+
val: inside,
|
|
343
|
+
ts: user.timestamp,
|
|
344
|
+
ack: true,
|
|
345
|
+
});
|
|
384
346
|
}
|
|
385
|
-
this._browser = null;
|
|
386
|
-
} catch (e) {
|
|
387
|
-
this.log.error(`Error in puppeteer: ${(e as Error).message}`);
|
|
388
347
|
}
|
|
389
348
|
}
|
|
390
349
|
|
|
@@ -393,7 +352,7 @@ class GoogleSharedlocations2 extends utils.Adapter {
|
|
|
393
352
|
*
|
|
394
353
|
* @param callback - Callback function
|
|
395
354
|
*/
|
|
396
|
-
private onUnload(callback: () => void): void {
|
|
355
|
+
private async onUnload(callback: () => void): Promise<void> {
|
|
397
356
|
try {
|
|
398
357
|
// Here you must clear all timeouts or intervals that may still be active
|
|
399
358
|
// clearTimeout(timeout1);
|
|
@@ -403,13 +362,7 @@ class GoogleSharedlocations2 extends utils.Adapter {
|
|
|
403
362
|
if (this._pollTimeout) {
|
|
404
363
|
clearTimeout(this._pollTimeout);
|
|
405
364
|
}
|
|
406
|
-
|
|
407
|
-
//ignore results here.
|
|
408
|
-
this._browser
|
|
409
|
-
.close()
|
|
410
|
-
.then(() => {})
|
|
411
|
-
.catch(() => {});
|
|
412
|
-
}
|
|
365
|
+
await this.cookie.cleanUp();
|
|
413
366
|
callback();
|
|
414
367
|
} catch (error) {
|
|
415
368
|
this.log.error(`Error during unloading: ${(error as Error).message}`);
|
|
@@ -442,35 +395,46 @@ class GoogleSharedlocations2 extends utils.Adapter {
|
|
|
442
395
|
if (id.endsWith('info.currentCookies') && state && !state.ack) {
|
|
443
396
|
if (state.val === '') {
|
|
444
397
|
this.log.info('Current cookies state was cleared, trying to obtain new cookies.');
|
|
445
|
-
this.
|
|
446
|
-
await this.loginToGetNewCookies();
|
|
447
|
-
if (this.
|
|
398
|
+
this._successFullPolls = 0;
|
|
399
|
+
await this.cookie.loginToGetNewCookies();
|
|
400
|
+
if (this.cookie.isValid()) {
|
|
448
401
|
await this.sendRequest();
|
|
449
402
|
}
|
|
450
403
|
} else {
|
|
451
404
|
this.log.info(
|
|
452
405
|
'Current cookies state was changed from outside the adapter, updating internal cookie store.',
|
|
453
406
|
);
|
|
454
|
-
this.
|
|
407
|
+
this.cookie.currentCookie = state.val as string;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Some message was sent to this instance over message box. Used by email, pushover, text2speech, ...
|
|
414
|
+
* Using this method requires "common.messagebox" property to be set to true in io-package.json
|
|
415
|
+
*
|
|
416
|
+
* @param obj - message object
|
|
417
|
+
*/
|
|
418
|
+
private onMessage(obj: ioBroker.Message): void {
|
|
419
|
+
this.log.debug(`Received ${obj?.command} message`);
|
|
420
|
+
if (obj?.command === 'getUsers') {
|
|
421
|
+
this.log.debug('Received getUsers message');
|
|
422
|
+
// Send response in callback if required
|
|
423
|
+
if (obj.callback) {
|
|
424
|
+
try {
|
|
425
|
+
const result = Object.values(this._users).map(user => ({
|
|
426
|
+
value: user.id,
|
|
427
|
+
label: user.name || user.id,
|
|
428
|
+
}));
|
|
429
|
+
this.log.debug(`Result: ${JSON.stringify(result)}`);
|
|
430
|
+
this.sendTo(obj.from, obj.command, result, obj.callback);
|
|
431
|
+
} catch (e) {
|
|
432
|
+
this.log.error(`Error processing getUsers message: ${(e as Error).message}`);
|
|
433
|
+
this.sendTo(obj.from, obj.command, [], obj.callback);
|
|
434
|
+
}
|
|
455
435
|
}
|
|
456
436
|
}
|
|
457
437
|
}
|
|
458
|
-
// If you need to accept messages in your adapter, uncomment the following block and the corresponding line in the constructor.
|
|
459
|
-
// /**
|
|
460
|
-
// * Some message was sent to this instance over message box. Used by email, pushover, text2speech, ...
|
|
461
|
-
// * Using this method requires "common.messagebox" property to be set to true in io-package.json
|
|
462
|
-
// */
|
|
463
|
-
//
|
|
464
|
-
// private onMessage(obj: ioBroker.Message): void {
|
|
465
|
-
// if (typeof obj === 'object' && obj.message) {
|
|
466
|
-
// if (obj.command === 'send') {
|
|
467
|
-
// // e.g. send email or pushover or whatever
|
|
468
|
-
// this.log.info('send command');
|
|
469
|
-
// // Send response in callback if required
|
|
470
|
-
// if (obj.callback) this.sendTo(obj.from, obj.command, 'Message received', obj.callback);
|
|
471
|
-
// }
|
|
472
|
-
// }
|
|
473
|
-
// }
|
|
474
438
|
}
|
|
475
439
|
//if (require.main !== module) {
|
|
476
440
|
// Export the constructor in compact mode
|