homebridge-melcloud-control 4.0.0-beta.5 → 4.0.0-beta.51
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/CHANGELOG.md +9 -0
- package/README.md +4 -3
- package/config.schema.json +29 -1
- package/homebridge-ui/public/index.html +18 -8
- package/homebridge-ui/server.js +4 -3
- package/index.js +10 -13
- package/package.json +1 -1
- package/src/melcloud.js +123 -88
package/CHANGELOG.md
CHANGED
|
@@ -16,6 +16,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
16
16
|
- do not configure it manually, always using Config UI X
|
|
17
17
|
- required Homebridge v2.0.0 and above
|
|
18
18
|
|
|
19
|
+
## [4.0.0] - (xx.10.2025)
|
|
20
|
+
|
|
21
|
+
## Changes
|
|
22
|
+
|
|
23
|
+
- added support for MELCloud Home [#215](https://github.com/grzegorz914/homebridge-melcloud-control/issues/215)
|
|
24
|
+
- redme updated
|
|
25
|
+
- config schema updated
|
|
26
|
+
- cleanup
|
|
27
|
+
|
|
19
28
|
## [3.9.5] - (02.09.2025)
|
|
20
29
|
|
|
21
30
|
## Changes
|
package/README.md
CHANGED
|
@@ -204,9 +204,10 @@ Homebridge plugin for Air Conditioner, Heat Pump and Energy Recovery Ventilation
|
|
|
204
204
|
| Key | Description |
|
|
205
205
|
| --- | --- |
|
|
206
206
|
| `name` | Here set the own account name. |
|
|
207
|
-
| `user` | Here set the
|
|
208
|
-
| `passwd` | Here set the
|
|
209
|
-
| `language` | Here select the
|
|
207
|
+
| `user` | Here set the account username. |
|
|
208
|
+
| `passwd` | Here set the account password. |
|
|
209
|
+
| `language` | Here select the account language. |
|
|
210
|
+
| `displayMode` | Here select the account type `None/Disabled`, `MELCloud`, `MELCloud Home`. |
|
|
210
211
|
| `ataDevices[]` | Array of ATA devices created automatically after login to MELCloud from plugin config UI. |
|
|
211
212
|
| `ataDevices[].id` | Read only data, do not change it. |
|
|
212
213
|
| `ataDevices[].type` | Read only data, do not change it. |
|
package/config.schema.json
CHANGED
|
@@ -201,6 +201,32 @@
|
|
|
201
201
|
}
|
|
202
202
|
]
|
|
203
203
|
},
|
|
204
|
+
"displayType": {
|
|
205
|
+
"title": "Account Type",
|
|
206
|
+
"type": "string",
|
|
207
|
+
"default": "disabled",
|
|
208
|
+
"description": "Here select the language used in MELCloud account.",
|
|
209
|
+
"oneOf": [
|
|
210
|
+
{
|
|
211
|
+
"title": "None/Disabled",
|
|
212
|
+
"enum": [
|
|
213
|
+
"disabled"
|
|
214
|
+
]
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
"title": "MELCLoud",
|
|
218
|
+
"enum": [
|
|
219
|
+
"melcloud"
|
|
220
|
+
]
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
"title": "MELCLoud Home",
|
|
224
|
+
"enum": [
|
|
225
|
+
"melcloudhome"
|
|
226
|
+
]
|
|
227
|
+
}
|
|
228
|
+
]
|
|
229
|
+
},
|
|
204
230
|
"ataDevices": {
|
|
205
231
|
"title": "Devices ATA",
|
|
206
232
|
"type": "array",
|
|
@@ -1798,7 +1824,8 @@
|
|
|
1798
1824
|
"name",
|
|
1799
1825
|
"user",
|
|
1800
1826
|
"passwd",
|
|
1801
|
-
"language"
|
|
1827
|
+
"language",
|
|
1828
|
+
"displayType"
|
|
1802
1829
|
]
|
|
1803
1830
|
}
|
|
1804
1831
|
}
|
|
@@ -1817,6 +1844,7 @@
|
|
|
1817
1844
|
"type": "password"
|
|
1818
1845
|
},
|
|
1819
1846
|
"accounts[].language",
|
|
1847
|
+
"accounts[].displayType",
|
|
1820
1848
|
{
|
|
1821
1849
|
"key": "accounts[]",
|
|
1822
1850
|
"type": "tabarray",
|
|
@@ -76,14 +76,22 @@
|
|
|
76
76
|
</select>
|
|
77
77
|
</div>
|
|
78
78
|
|
|
79
|
+
<div class="mb-2">
|
|
80
|
+
<label for="displayType" class="form-label">Account Type</label>
|
|
81
|
+
<select id="displayType" name="displayType" class="form-control">
|
|
82
|
+
<option value="disabled">None/Disabled</option>
|
|
83
|
+
<option value="melcloud">MELCloud</option>
|
|
84
|
+
<option value="melcloudhome">MELCloud Home</option>
|
|
85
|
+
</select>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
79
88
|
<div class="text-center">
|
|
80
|
-
<button id="logIn" type="button" class="btn btn-secondary">
|
|
89
|
+
<button id="logIn" type="button" class="btn btn-secondary">Log In</button>
|
|
81
90
|
<button id="configButton" type="button" class="btn btn-secondary"><i class="fas fa-gear"></i></button>
|
|
82
91
|
</div>
|
|
83
92
|
</form>
|
|
93
|
+
<div id="accountButton" class="text-center mt-2"></div>
|
|
84
94
|
</div>
|
|
85
|
-
|
|
86
|
-
<div id="accountButton" class="mt-2"></div>
|
|
87
95
|
</div>
|
|
88
96
|
|
|
89
97
|
<script>
|
|
@@ -110,7 +118,7 @@
|
|
|
110
118
|
|
|
111
119
|
button.addEventListener('click', async () => {
|
|
112
120
|
for (let j = 0; j < accountsCount; j++) {
|
|
113
|
-
document.getElementById(`button${j}`).className = (j === i ? 'btn btn-
|
|
121
|
+
document.getElementById(`button${j}`).className = (j === i ? 'btn btn-primary' : 'btn btn-secondary');
|
|
114
122
|
}
|
|
115
123
|
|
|
116
124
|
const acc = pluginConfig[0].accounts[i];
|
|
@@ -118,8 +126,9 @@
|
|
|
118
126
|
document.getElementById('name').value = acc.name || '';
|
|
119
127
|
document.getElementById('user').value = acc.user || '';
|
|
120
128
|
document.getElementById('passwd').value = acc.passwd || '';
|
|
121
|
-
document.getElementById('language').value = acc.language || '';
|
|
122
|
-
document.getElementById('
|
|
129
|
+
document.getElementById('language').value = acc.language || '0';
|
|
130
|
+
document.getElementById('displayType').value = acc.displayType || 'disabled';
|
|
131
|
+
document.getElementById('logIn').disabled = !(acc.name && acc.user && acc.passwd && acc.language && acc.displayType);
|
|
123
132
|
this.deviceIndex = i;
|
|
124
133
|
});
|
|
125
134
|
|
|
@@ -134,8 +143,9 @@
|
|
|
134
143
|
acc.user = document.querySelector('#user').value;
|
|
135
144
|
acc.passwd = document.querySelector('#passwd').value;
|
|
136
145
|
acc.language = document.querySelector('#language').value;
|
|
146
|
+
acc.displayType = document.querySelector('#displayType').value;
|
|
137
147
|
|
|
138
|
-
document.getElementById('logIn').disabled = !(acc.name && acc.user && acc.passwd && acc.
|
|
148
|
+
document.getElementById('logIn').disabled = !(acc.name && acc.user && acc.passwd && acc.languaged && acc.displayType);
|
|
139
149
|
|
|
140
150
|
await homebridge.updatePluginConfig(pluginConfig);
|
|
141
151
|
await homebridge.savePluginConfig(pluginConfig);
|
|
@@ -192,7 +202,7 @@
|
|
|
192
202
|
|
|
193
203
|
try {
|
|
194
204
|
const acc = pluginConfig[0].accounts[this.deviceIndex];
|
|
195
|
-
const payload = { accountName: acc.name, user: acc.user, passwd: acc.passwd, language: acc.language };
|
|
205
|
+
const payload = { accountName: acc.name, user: acc.user, passwd: acc.passwd, language: acc.language, displayType: acc.displayType };
|
|
196
206
|
const devicesInMelCloud = await homebridge.request('/connect', payload);
|
|
197
207
|
|
|
198
208
|
// Initialize devices arrays
|
package/homebridge-ui/server.js
CHANGED
|
@@ -17,14 +17,15 @@ class PluginUiServer extends HomebridgePluginUiServer {
|
|
|
17
17
|
const user = payload.user;
|
|
18
18
|
const passwd = payload.passwd;
|
|
19
19
|
const language = payload.language;
|
|
20
|
+
const displayType = payload.displayType;
|
|
20
21
|
const accountFile = `${this.homebridgeStoragePath}/melcloud/${accountName}_Account`;
|
|
21
22
|
const buildingsFile = `${this.homebridgeStoragePath}/melcloud/${accountName}_Buildings`;
|
|
22
23
|
const devicesFile = `${this.homebridgeStoragePath}/melcloud/${accountName}_Devices`;
|
|
23
|
-
const melCloud = new MelCloud(user, passwd, language, accountFile, buildingsFile, devicesFile, false, true);
|
|
24
|
+
const melCloud = new MelCloud(displayType, user, passwd, language, accountFile, buildingsFile, devicesFile, false, true);
|
|
24
25
|
|
|
25
26
|
try {
|
|
26
|
-
const
|
|
27
|
-
const devices = await melCloud.checkDevicesList(
|
|
27
|
+
const accountInfo = await melCloud.connect();
|
|
28
|
+
const devices = await melCloud.checkDevicesList(accountInfo.ContextKey);
|
|
28
29
|
return devices;
|
|
29
30
|
} catch (error) {
|
|
30
31
|
throw new Error(`MELCloud error: ${error.message ?? error}.`);
|
package/index.js
CHANGED
|
@@ -30,6 +30,9 @@ class MelCloudPlatform {
|
|
|
30
30
|
api.on('didFinishLaunching', async () => {
|
|
31
31
|
//loop through accounts
|
|
32
32
|
for (const account of config.accounts) {
|
|
33
|
+
const displayType = account.displayType || 'disabled';
|
|
34
|
+
if (displayType === 'disabled') continue;
|
|
35
|
+
|
|
33
36
|
const accountName = account.name;
|
|
34
37
|
const user = account.user;
|
|
35
38
|
const passwd = account.passwd;
|
|
@@ -64,7 +67,7 @@ class MelCloudPlatform {
|
|
|
64
67
|
passwd: 'removed',
|
|
65
68
|
mqtt: {
|
|
66
69
|
auth: {
|
|
67
|
-
...
|
|
70
|
+
...account.mqtt?.auth,
|
|
68
71
|
passwd: 'removed',
|
|
69
72
|
}
|
|
70
73
|
},
|
|
@@ -76,7 +79,6 @@ class MelCloudPlatform {
|
|
|
76
79
|
const accountFile = `${prefDir}/${accountName}_Account`;
|
|
77
80
|
const buildingsFile = `${prefDir}/${accountName}_Buildings`;
|
|
78
81
|
const devicesFile = `${prefDir}/${accountName}_Devices`;
|
|
79
|
-
const cookiesFile = `${prefDir}/${accountName}cookies`;
|
|
80
82
|
|
|
81
83
|
|
|
82
84
|
//set account refresh interval
|
|
@@ -88,31 +90,26 @@ class MelCloudPlatform {
|
|
|
88
90
|
.on('start', async () => {
|
|
89
91
|
try {
|
|
90
92
|
//melcloud account
|
|
91
|
-
const melCloud = new MelCloud(user, passwd, language, accountFile, buildingsFile, devicesFile,
|
|
93
|
+
const melCloud = new MelCloud(displayType, user, passwd, language, accountFile, buildingsFile, devicesFile, logLevel.warn, logLevel.debug, false)
|
|
92
94
|
.on('success', (msg) => logLevel.success && log.success(`${accountName}, ${msg}`))
|
|
93
95
|
.on('info', (msg) => logLevel.info && log.info(`${accountName}, ${msg}`))
|
|
94
96
|
.on('debug', (msg) => logLevel.debug && log.info(`${accountName}, debug: ${msg}`))
|
|
95
97
|
.on('warn', (msg) => logLevel.warn && log.warn(`${accountName}, ${msg}`))
|
|
96
98
|
.on('error', (msg) => logLevel.error && log.error(`${accountName}, ${msg}`));
|
|
97
99
|
|
|
98
|
-
await melCloud.connectHomeCoocies()
|
|
99
|
-
return;
|
|
100
|
-
|
|
101
|
-
|
|
102
100
|
//connect
|
|
103
|
-
let
|
|
101
|
+
let accountInfo;
|
|
104
102
|
try {
|
|
105
|
-
|
|
103
|
+
accountInfo = await melCloud.connect();
|
|
106
104
|
} catch (error) {
|
|
107
105
|
if (logLevel.error) log.error(`${accountName}, Connect error: ${error.message ?? error}`);
|
|
108
106
|
return;
|
|
109
107
|
}
|
|
110
108
|
|
|
111
|
-
const
|
|
112
|
-
const
|
|
113
|
-
const useFahrenheit = response.useFahrenheit ?? false;
|
|
109
|
+
const contextKey = accountInfo.ContextKey;
|
|
110
|
+
const useFahrenheit = accountInfo.UseFahrenheit;
|
|
114
111
|
|
|
115
|
-
if (contextKey
|
|
112
|
+
if (!contextKey) {
|
|
116
113
|
return;
|
|
117
114
|
}
|
|
118
115
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"displayName": "MELCloud Control",
|
|
3
3
|
"name": "homebridge-melcloud-control",
|
|
4
|
-
"version": "4.0.0-beta.
|
|
4
|
+
"version": "4.0.0-beta.51",
|
|
5
5
|
"description": "Homebridge plugin to control Mitsubishi Air Conditioner, Heat Pump and Energy Recovery Ventilation.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "grzegorz914",
|
package/src/melcloud.js
CHANGED
|
@@ -7,15 +7,15 @@ import Functions from './functions.js';
|
|
|
7
7
|
import { ApiUrls, ApiUrlsHome } from './constants.js';
|
|
8
8
|
|
|
9
9
|
class MelCloud extends EventEmitter {
|
|
10
|
-
constructor(user, passwd, language, accountFile, buildingsFile, devicesFile,
|
|
10
|
+
constructor(displayType, user, passwd, language, accountFile, buildingsFile, devicesFile, logWarn, logDebug, requestConfig) {
|
|
11
11
|
super();
|
|
12
|
+
this.displayType = displayType;
|
|
12
13
|
this.user = user;
|
|
13
14
|
this.passwd = passwd;
|
|
14
15
|
this.language = language;
|
|
15
16
|
this.accountFile = accountFile;
|
|
16
17
|
this.buildingsFile = buildingsFile;
|
|
17
18
|
this.devicesFile = devicesFile;
|
|
18
|
-
this.cookiesFile = cookiesFile;
|
|
19
19
|
this.logWarn = logWarn;
|
|
20
20
|
this.logDebug = logDebug;
|
|
21
21
|
this.requestConfig = requestConfig;
|
|
@@ -58,7 +58,7 @@ class MelCloud extends EventEmitter {
|
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
async
|
|
61
|
+
async checkMelcloudDevicesList(contextKey) {
|
|
62
62
|
try {
|
|
63
63
|
const axiosInstanceGet = axios.create({
|
|
64
64
|
method: 'GET',
|
|
@@ -109,7 +109,7 @@ class MelCloud extends EventEmitter {
|
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
async
|
|
112
|
+
async connectToMelCloud() {
|
|
113
113
|
if (this.logDebug) this.emit('debug', `Connecting to MELCloud`);
|
|
114
114
|
|
|
115
115
|
try {
|
|
@@ -123,7 +123,6 @@ class MelCloud extends EventEmitter {
|
|
|
123
123
|
const account = accountData.data;
|
|
124
124
|
const accountInfo = account.LoginData;
|
|
125
125
|
const contextKey = accountInfo?.ContextKey;
|
|
126
|
-
const useFahrenheit = accountInfo?.UseFahrenheit ?? false;
|
|
127
126
|
this.contextKey = contextKey;
|
|
128
127
|
|
|
129
128
|
const debugData = {
|
|
@@ -153,44 +152,24 @@ class MelCloud extends EventEmitter {
|
|
|
153
152
|
});
|
|
154
153
|
|
|
155
154
|
await this.functions.saveData(this.accountFile, accountInfo);
|
|
156
|
-
|
|
157
155
|
this.emit('success', `Connect to MELCloud Success`);
|
|
158
156
|
|
|
159
|
-
return
|
|
160
|
-
accountInfo,
|
|
161
|
-
contextKey,
|
|
162
|
-
useFahrenheit
|
|
163
|
-
};
|
|
157
|
+
return accountInfo
|
|
164
158
|
} catch (error) {
|
|
165
159
|
throw new Error(`Connect to MELCloud error: ${error.message}`);
|
|
166
160
|
}
|
|
167
161
|
}
|
|
168
162
|
|
|
169
|
-
async
|
|
170
|
-
if (this.logDebug) this.emit('debug', `Connecting to MELCloud Home`);
|
|
171
|
-
|
|
163
|
+
async checkMelcloudHomeDevicesList(contextKey) {
|
|
172
164
|
try {
|
|
173
|
-
const c1 = cookieC1.trim();
|
|
174
|
-
const c2 = cookieC2.trim();
|
|
175
|
-
|
|
176
|
-
const cookieString = [
|
|
177
|
-
'__Secure-monitorandcontrol=chunks-2',
|
|
178
|
-
`__Secure-monitorandcontrolC1=${c1}`,
|
|
179
|
-
`__Secure-monitorandcontrolC2=${c2}`,
|
|
180
|
-
].join('; ');
|
|
181
|
-
|
|
182
165
|
const axiosInstance = axios.create({
|
|
166
|
+
method: 'GET',
|
|
183
167
|
baseURL: ApiUrlsHome.BaseURL,
|
|
184
|
-
timeout: 10000,
|
|
185
|
-
httpsAgent: new Agent({
|
|
186
|
-
keepAlive: false,
|
|
187
|
-
rejectUnauthorized: false
|
|
188
|
-
}),
|
|
189
168
|
headers: {
|
|
190
169
|
'Accept': '*/*',
|
|
191
170
|
'Accept-Language': 'en-US,en;q=0.9',
|
|
192
|
-
'Cookie':
|
|
193
|
-
'User-Agent': 'homebridge-melcloud-
|
|
171
|
+
'Cookie': contextKey,
|
|
172
|
+
'User-Agent': 'homebridge-melcloud-control/4.0.0',
|
|
194
173
|
'DNT': '1',
|
|
195
174
|
'Origin': 'https://melcloudhome.com',
|
|
196
175
|
'Referer': 'https://melcloudhome.com/dashboard',
|
|
@@ -198,93 +177,149 @@ class MelCloud extends EventEmitter {
|
|
|
198
177
|
'Sec-Fetch-Mode': 'cors',
|
|
199
178
|
'Sec-Fetch-Site': 'same-origin',
|
|
200
179
|
'X-CSRF': '1'
|
|
201
|
-
}
|
|
180
|
+
},
|
|
181
|
+
...this.axiosDefaults
|
|
202
182
|
});
|
|
203
183
|
|
|
204
|
-
|
|
205
|
-
const
|
|
206
|
-
|
|
184
|
+
if (this.logDebug) this.emit('debug', `Scanning for devices`);
|
|
185
|
+
const listDevicesData = await axiosInstance(ApiUrlsHome.GetUserContext);
|
|
186
|
+
const buildingsList = listDevicesData.data.buildings;
|
|
187
|
+
if (this.logDebug) this.emit('debug', `Buildings: ${JSON.stringify(buildingsList, null, 2)}`);
|
|
207
188
|
|
|
208
|
-
|
|
189
|
+
if (!buildingsList) {
|
|
190
|
+
if (this.logWarn) this.emit('warn', `No building found`);
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
await this.functions.saveData(this.buildingsFile, buildingsList);
|
|
195
|
+
if (this.logDebug) this.emit('debug', `Buildings list saved`);
|
|
196
|
+
|
|
197
|
+
const allDevices = buildingsList.flatMap(building => {
|
|
198
|
+
const capitalizeKeys = obj =>
|
|
199
|
+
Object.fromEntries(Object.entries(obj).map(([key, value]) => [
|
|
200
|
+
key.charAt(0).toUpperCase() + key.slice(1),
|
|
201
|
+
value
|
|
202
|
+
]));
|
|
203
|
+
|
|
204
|
+
return [
|
|
205
|
+
...(building.airToAirUnits || []).map(device => ({ ...capitalizeKeys(device), Type: 0 })),
|
|
206
|
+
...(building.airToWaterUnits || []).map(device => ({ ...capitalizeKeys(device), Type: 1 })),
|
|
207
|
+
...(building.airToVentilationUnits || []).map(device => ({ ...capitalizeKeys(device), Type: 3 }))
|
|
208
|
+
];
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
const devicesCount = allDevices.length;
|
|
212
|
+
if (devicesCount === 0) {
|
|
213
|
+
if (this.logWarn) this.emit('warn', `No devices found`);
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const devices = allDevices.map(device => ({
|
|
218
|
+
...device, // zachowuje wszystkie pola z allDevices
|
|
219
|
+
DeviceID: device.Id,
|
|
220
|
+
DeviceName: device.GivenDisplayName,
|
|
221
|
+
Settings: Object.fromEntries((device.Settings || []).map(({ Name, Value }) => [Name, Value])),
|
|
222
|
+
Device: device.Capabilities
|
|
223
|
+
}));
|
|
224
|
+
|
|
225
|
+
await this.functions.saveData(this.devicesFile, devices);
|
|
226
|
+
if (this.logDebug) this.emit('debug', `${devicesCount} devices saved`);
|
|
209
227
|
|
|
210
|
-
return
|
|
228
|
+
return devices;
|
|
211
229
|
} catch (error) {
|
|
212
230
|
throw new Error(`Connect to MELCloud Home error: ${error.message}`);
|
|
213
231
|
}
|
|
214
232
|
}
|
|
215
233
|
|
|
216
|
-
async
|
|
217
|
-
|
|
218
|
-
loginUrl.searchParams.set('client_id', '3g4d5l5kivuqi7oia68gib7uso');
|
|
219
|
-
loginUrl.searchParams.set('redirect_uri', 'https://auth.melcloudhome.com/signin-oidc-meu');
|
|
220
|
-
loginUrl.searchParams.set('response_type', 'code');
|
|
221
|
-
loginUrl.searchParams.set('scope', 'openid profile');
|
|
222
|
-
loginUrl.searchParams.set('response_mode', 'form_post');
|
|
223
|
-
|
|
224
|
-
const browser = await puppeteer.launch({
|
|
225
|
-
headless: true,
|
|
226
|
-
args: ['--no-sandbox', '--disable-setuid-sandbox']
|
|
227
|
-
});
|
|
234
|
+
async connectToMelCloudHome() {
|
|
235
|
+
if (this.logDebug) this.emit('debug', `Connecting to MELCloud Home`);
|
|
228
236
|
|
|
237
|
+
const browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox', '--disable-setuid-sandbox'] });
|
|
229
238
|
const page = await browser.newPage();
|
|
230
239
|
|
|
231
240
|
try {
|
|
232
|
-
|
|
233
|
-
await page.goto(
|
|
241
|
+
// Open MELCloud Home
|
|
242
|
+
await page.goto(ApiUrlsHome.BaseURL, { waitUntil: 'networkidle2' });
|
|
243
|
+
const buttons = await page.$$('button.btn--blue');
|
|
244
|
+
let loginBtn = null;
|
|
245
|
+
for (const btn of buttons) {
|
|
246
|
+
const text = await page.evaluate(el => el.textContent, btn);
|
|
247
|
+
if (text.trim() === 'Zaloguj' || text.trim() === 'Log In') {
|
|
248
|
+
loginBtn = btn;
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (!loginBtn && this.logWarn) this.emit('warn', `Login button not found`);
|
|
234
254
|
|
|
235
|
-
|
|
255
|
+
// Set credentials and login
|
|
256
|
+
await Promise.all([loginBtn.click(), page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 20000 })]);
|
|
257
|
+
await page.waitForSelector('input[name="username"]', { timeout: 15000 });
|
|
236
258
|
await page.type('input[name="username"]', this.user, { delay: 50 });
|
|
237
259
|
await page.type('input[name="password"]', this.passwd, { delay: 50 });
|
|
238
260
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
'button[type="submit"]',
|
|
242
|
-
'input[type="submit"]',
|
|
243
|
-
'button[name="signIn"]',
|
|
244
|
-
'button.btn-primary'
|
|
245
|
-
];
|
|
246
|
-
|
|
247
|
-
let buttonFound = false;
|
|
248
|
-
for (const selector of buttonSelectors) {
|
|
249
|
-
const button = await page.$(selector);
|
|
250
|
-
if (button) {
|
|
251
|
-
this.emit('warn', `Found submit button: ${selector}`);
|
|
252
|
-
await Promise.all([
|
|
253
|
-
button.click(),
|
|
254
|
-
page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 20000 }) // czekamy na redirect
|
|
255
|
-
]);
|
|
256
|
-
buttonFound = true;
|
|
257
|
-
break;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
261
|
+
const button1 = await page.$('input[type="submit"]');
|
|
262
|
+
await Promise.all([button1.click(), page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 20000 })]);
|
|
260
263
|
|
|
261
|
-
|
|
262
|
-
|
|
264
|
+
// Get cookies C1 and C2
|
|
265
|
+
let c1 = null, c2 = null;
|
|
266
|
+
const start = Date.now();
|
|
267
|
+
|
|
268
|
+
// Loop max 20s
|
|
269
|
+
while ((!c1 || !c2) && Date.now() - start < 20000) {
|
|
270
|
+
const cookies = await page.cookies();
|
|
271
|
+
c1 = cookies.find(c => c.name === '__Secure-monitorandcontrolC1')?.value || c1;
|
|
272
|
+
c2 = cookies.find(c => c.name === '__Secure-monitorandcontrolC2')?.value || c2;
|
|
273
|
+
if (!c1 || !c2) await new Promise(r => setTimeout(r, 500));
|
|
263
274
|
}
|
|
264
275
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
276
|
+
if (!c1 || !c2) {
|
|
277
|
+
if (this.logWarn) this.emit('warn', `Cookies C1/C2 missing`);
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
268
280
|
|
|
269
|
-
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
const c2 = cookies.find(c => c.name === '__Secure-monitorandcontrolC2')?.value || null;
|
|
281
|
+
const contextKey = ['__Secure-monitorandcontrol=chunks-2', `__Secure-monitorandcontrolC1=${c1}`, `__Secure-monitorandcontrolC2=${c2}`,].join('; ');
|
|
282
|
+
const accountInfo = { ContextKey: contextKey, UseFahrenheit: false };
|
|
283
|
+
this.contextKey = contextKey;
|
|
273
284
|
|
|
274
|
-
|
|
275
|
-
|
|
285
|
+
await this.functions.saveData(this.accountFile, accountInfo);
|
|
286
|
+
this.emit('success', `Connect to MELCloud Home Success`);
|
|
276
287
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
this.emit('error', `Login failed: ${err.message}`);
|
|
281
|
-
return null;
|
|
288
|
+
return accountInfo;
|
|
289
|
+
} catch (error) {
|
|
290
|
+
throw new Error(`Connect to MELCloud Home error: ${error.message}`);
|
|
282
291
|
} finally {
|
|
283
292
|
await browser.close();
|
|
284
293
|
}
|
|
285
294
|
}
|
|
286
295
|
|
|
296
|
+
async connect() {
|
|
297
|
+
let response = {};
|
|
298
|
+
switch (this.displayType) {
|
|
299
|
+
case "melcloud":
|
|
300
|
+
response = await this.connectToMelCloud();
|
|
301
|
+
return response
|
|
302
|
+
case "melcloudhome":
|
|
303
|
+
response = await this.connectToMelCloudHome();
|
|
304
|
+
return response
|
|
305
|
+
default:
|
|
306
|
+
return response
|
|
307
|
+
}
|
|
308
|
+
}
|
|
287
309
|
|
|
310
|
+
async checkDevicesList(contextKey) {
|
|
311
|
+
let devices = [];
|
|
312
|
+
switch (this.displayType) {
|
|
313
|
+
case "melcloud":
|
|
314
|
+
devices = await this.checkMelcloudDevicesList(contextKey);
|
|
315
|
+
return devices
|
|
316
|
+
case "melcloudhome":
|
|
317
|
+
devices = await this.checkMelcloudHomeDevicesList(contextKey);
|
|
318
|
+
return devices
|
|
319
|
+
default:
|
|
320
|
+
return devices;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
288
323
|
|
|
289
324
|
async send(accountInfo) {
|
|
290
325
|
try {
|