iobroker.zendure-solarflow 1.10.4 → 1.10.5
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 +4 -0
- package/build/services/jobSchedule.js +11 -0
- package/build/services/jobSchedule.js.map +2 -2
- package/io-package.json +14 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -48,6 +48,10 @@ If you find the adapter useful for you and want to support my work, feel free to
|
|
|
48
48
|
[](https://www.paypal.com/paypalme/PeterFrommert)
|
|
49
49
|
|
|
50
50
|
## Changelog
|
|
51
|
+
### 1.10.5 (2025-01-16)
|
|
52
|
+
|
|
53
|
+
- Fix start of calculation if HUB is connected with Ace
|
|
54
|
+
|
|
51
55
|
### 1.10.4 (2025-01-14)
|
|
52
56
|
|
|
53
57
|
- Fix "Grid Input Power" state if connected with Ace
|
|
@@ -48,6 +48,17 @@ const startCalculationJob = async (adapter) => {
|
|
|
48
48
|
adapter.deviceList.forEach((device) => {
|
|
49
49
|
if (device.productKey != "s3Xk4x") {
|
|
50
50
|
(0, import_calculationService.calculateEnergy)(adapter, device.productKey, device.deviceKey);
|
|
51
|
+
if (device.packList && device.packList.length > 0) {
|
|
52
|
+
device.packList.forEach(async (subDevice) => {
|
|
53
|
+
if (subDevice.productName.toLocaleLowerCase() == "ace 1500") {
|
|
54
|
+
(0, import_calculationService.calculateEnergy)(
|
|
55
|
+
adapter,
|
|
56
|
+
subDevice.productKey,
|
|
57
|
+
subDevice.deviceKey
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
51
62
|
}
|
|
52
63
|
});
|
|
53
64
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/services/jobSchedule.ts"],
|
|
4
|
-
"sourcesContent": ["/* eslint-disable @typescript-eslint/indent */\r\nimport { scheduleJob } from \"node-schedule\";\r\nimport { ZendureSolarflow } from \"../main\";\r\n/* import { connectMqttClient } from \"./mqttService\";\r\nimport { login } from \"./webService\"; */\r\nimport { ISolarFlowDeviceDetails } from \"../models/ISolarFlowDeviceDetails\";\r\nimport { calculateEnergy, resetTodaysValues } from \"./calculationService\";\r\n\r\n/* const refreshAccessToken = async (adapter: ZendureSolarflow): Promise<void> => {\r\n // Relogin every 3 hours to get a fresh accessToken!\r\n adapter.log.info(`[startRefreshAccessTokenTimerJob] Stop connections!`);\r\n\r\n // Scheduler beenden\r\n if (adapter.resetValuesJob) {\r\n adapter.resetValuesJob.cancel();\r\n }\r\n\r\n if (adapter.checkStatesJob) {\r\n adapter.checkStatesJob?.cancel();\r\n }\r\n\r\n if (adapter.calculationJob) {\r\n adapter.calculationJob.cancel();\r\n }\r\n\r\n if (adapter.mqttClient) {\r\n adapter.mqttClient.end();\r\n }\r\n\r\n adapter.log.info(\r\n `[startRefreshAccessTokenTimerJob] Refreshing accessToken in 10 seconds!`,\r\n );\r\n await adapter.delay(10 * 1000);\r\n\r\n adapter.resetValuesJob = undefined;\r\n adapter.checkStatesJob = undefined;\r\n adapter.calculationJob = undefined;\r\n adapter.mqttClient = undefined;\r\n\r\n if (adapter.config.userName && adapter.config.password) {\r\n login(adapter)?.then((_accessToken: string) => {\r\n adapter.accessToken = _accessToken;\r\n adapter.lastLogin = new Date();\r\n adapter.setState(\"info.connection\", true, true);\r\n\r\n connectMqttClient(adapter);\r\n });\r\n }\r\n}; */\r\n\r\nexport const startRefreshAccessTokenTimerJob = async (\r\n adapter: ZendureSolarflow\r\n): Promise<void> => {\r\n adapter.refreshAccessTokenInterval = adapter.setInterval(\r\n async () => {\r\n adapter.log.info(\r\n `Refresh Access Token - Adapter will restart in 20 seconds!`\r\n );\r\n\r\n await adapter.delay(20 * 1000);\r\n adapter.restart();\r\n },\r\n 3 * 60 * 60 * 1000\r\n );\r\n};\r\n\r\nexport const startResetValuesJob = async (\r\n adapter: ZendureSolarflow\r\n): Promise<void> => {\r\n adapter.resetValuesJob = scheduleJob(\"5 0 0 * * *\", () => {\r\n // Reset Values\r\n resetTodaysValues(adapter);\r\n });\r\n};\r\n\r\nexport const startCalculationJob = async (\r\n adapter: ZendureSolarflow\r\n): Promise<void> => {\r\n adapter.calculationJob = scheduleJob(\"*/30 * * * * *\", () => {\r\n adapter.deviceList.forEach((device) => {\r\n if (device.productKey != \"s3Xk4x\") {\r\n calculateEnergy(adapter, device.productKey, device.deviceKey);\r\n }\r\n });\r\n });\r\n};\r\n\r\nexport const startCheckStatesAndConnectionJob = async (\r\n adapter: ZendureSolarflow\r\n): Promise<void> => {\r\n // Check for states that has no updates in the last 5 minutes and set them to 0\r\n const statesToReset: string[] = [\r\n \"outputHomePower\",\r\n \"outputPackPower\",\r\n \"gridInputPower\",\r\n \"packInputPower\",\r\n \"solarInputPower\",\r\n ];\r\n\r\n let refreshAccessTokenNeeded = false;\r\n\r\n adapter.log.debug(\r\n `[checkStatesJob] Starting check of states and connection!`\r\n );\r\n\r\n adapter.checkStatesJob = scheduleJob(\"*/5 * * * *\", async () => {\r\n adapter.deviceList.forEach(async (device: ISolarFlowDeviceDetails) => {\r\n if (refreshAccessTokenNeeded) {\r\n return;\r\n }\r\n\r\n const lastUpdate = await adapter?.getStateAsync(\r\n device.productKey + \".\" + device.deviceKey + \".lastUpdate\"\r\n );\r\n\r\n const wifiState = await adapter?.getStateAsync(\r\n device.productKey + \".\" + device.deviceKey + \".wifiState\"\r\n );\r\n\r\n const fiveMinutesAgo = (Date.now() / 1000 - 5 * 60) * 1000; // Five minutes ago\r\n const tenMinutesAgo = (Date.now() / 1000 - 10 * 60) * 1000; // Ten minutes ago\r\n\r\n if (\r\n lastUpdate &&\r\n lastUpdate.val &&\r\n Number(lastUpdate.val) < fiveMinutesAgo &&\r\n wifiState?.val == \"Connected\"\r\n ) {\r\n adapter.log.warn(\r\n `[checkStatesJob] Last update for deviceKey ${\r\n device.deviceKey\r\n } was at ${new Date(\r\n Number(lastUpdate)\r\n )}, device seems to be online - so maybe connection is broken - restart adapter in 20 seconds!`\r\n );\r\n\r\n await adapter.delay(20 * 1000);\r\n adapter.restart();\r\n\r\n // set marker, so we discontinue the forEach Loop because of reconnect!\r\n refreshAccessTokenNeeded = true;\r\n }\r\n\r\n if (\r\n lastUpdate &&\r\n lastUpdate.val &&\r\n Number(lastUpdate.val) < tenMinutesAgo &&\r\n !refreshAccessTokenNeeded\r\n ) {\r\n adapter.log.debug(\r\n `[checkStatesJob] Last update for deviceKey ${\r\n device.deviceKey\r\n } was at ${new Date(\r\n Number(lastUpdate)\r\n )}, checking for pseudo power values!`\r\n );\r\n // State was not updated in the last 10 minutes... set states to 0\r\n await statesToReset.forEach(async (stateName: string) => {\r\n await adapter?.setState(\r\n device.productKey + \".\" + device.deviceKey + \".\" + stateName,\r\n 0,\r\n true\r\n );\r\n });\r\n\r\n // set electricLevel from deviceList\r\n if (device.electricity) {\r\n await adapter?.setState(\r\n device.productKey + \".\" + device.deviceKey + \".electricLevel\",\r\n device.electricity,\r\n true\r\n );\r\n }\r\n }\r\n });\r\n });\r\n};\r\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,2BAA4B;AAK5B,gCAAmD;AA4C5C,MAAM,kCAAkC,OAC7C,YACkB;AAClB,UAAQ,6BAA6B,QAAQ;AAAA,IAC3C,YAAY;AACV,cAAQ,IAAI;AAAA,QACV;AAAA,MACF;AAEA,YAAM,QAAQ,MAAM,KAAK,GAAI;AAC7B,cAAQ,QAAQ;AAAA,IAClB;AAAA,IACA,IAAI,KAAK,KAAK;AAAA,EAChB;AACF;AAEO,MAAM,sBAAsB,OACjC,YACkB;AAClB,UAAQ,qBAAiB,kCAAY,eAAe,MAAM;AAExD,qDAAkB,OAAO;AAAA,EAC3B,CAAC;AACH;AAEO,MAAM,sBAAsB,OACjC,YACkB;AAClB,UAAQ,qBAAiB,kCAAY,kBAAkB,MAAM;AAC3D,YAAQ,WAAW,QAAQ,CAAC,WAAW;AACrC,UAAI,OAAO,cAAc,UAAU;AACjC,uDAAgB,SAAS,OAAO,YAAY,OAAO,SAAS;AAAA,
|
|
4
|
+
"sourcesContent": ["/* eslint-disable @typescript-eslint/indent */\r\nimport { scheduleJob } from \"node-schedule\";\r\nimport { ZendureSolarflow } from \"../main\";\r\n/* import { connectMqttClient } from \"./mqttService\";\r\nimport { login } from \"./webService\"; */\r\nimport { ISolarFlowDeviceDetails } from \"../models/ISolarFlowDeviceDetails\";\r\nimport { calculateEnergy, resetTodaysValues } from \"./calculationService\";\r\n\r\n/* const refreshAccessToken = async (adapter: ZendureSolarflow): Promise<void> => {\r\n // Relogin every 3 hours to get a fresh accessToken!\r\n adapter.log.info(`[startRefreshAccessTokenTimerJob] Stop connections!`);\r\n\r\n // Scheduler beenden\r\n if (adapter.resetValuesJob) {\r\n adapter.resetValuesJob.cancel();\r\n }\r\n\r\n if (adapter.checkStatesJob) {\r\n adapter.checkStatesJob?.cancel();\r\n }\r\n\r\n if (adapter.calculationJob) {\r\n adapter.calculationJob.cancel();\r\n }\r\n\r\n if (adapter.mqttClient) {\r\n adapter.mqttClient.end();\r\n }\r\n\r\n adapter.log.info(\r\n `[startRefreshAccessTokenTimerJob] Refreshing accessToken in 10 seconds!`,\r\n );\r\n await adapter.delay(10 * 1000);\r\n\r\n adapter.resetValuesJob = undefined;\r\n adapter.checkStatesJob = undefined;\r\n adapter.calculationJob = undefined;\r\n adapter.mqttClient = undefined;\r\n\r\n if (adapter.config.userName && adapter.config.password) {\r\n login(adapter)?.then((_accessToken: string) => {\r\n adapter.accessToken = _accessToken;\r\n adapter.lastLogin = new Date();\r\n adapter.setState(\"info.connection\", true, true);\r\n\r\n connectMqttClient(adapter);\r\n });\r\n }\r\n}; */\r\n\r\nexport const startRefreshAccessTokenTimerJob = async (\r\n adapter: ZendureSolarflow\r\n): Promise<void> => {\r\n adapter.refreshAccessTokenInterval = adapter.setInterval(\r\n async () => {\r\n adapter.log.info(\r\n `Refresh Access Token - Adapter will restart in 20 seconds!`\r\n );\r\n\r\n await adapter.delay(20 * 1000);\r\n adapter.restart();\r\n },\r\n 3 * 60 * 60 * 1000\r\n );\r\n};\r\n\r\nexport const startResetValuesJob = async (\r\n adapter: ZendureSolarflow\r\n): Promise<void> => {\r\n adapter.resetValuesJob = scheduleJob(\"5 0 0 * * *\", () => {\r\n // Reset Values\r\n resetTodaysValues(adapter);\r\n });\r\n};\r\n\r\nexport const startCalculationJob = async (\r\n adapter: ZendureSolarflow\r\n): Promise<void> => {\r\n adapter.calculationJob = scheduleJob(\"*/30 * * * * *\", () => {\r\n adapter.deviceList.forEach((device) => {\r\n if (device.productKey != \"s3Xk4x\") {\r\n calculateEnergy(adapter, device.productKey, device.deviceKey);\r\n\r\n // Check if connected with ACE, then calculate also for ACE device\r\n if (device.packList && device.packList.length > 0) {\r\n device.packList.forEach(async (subDevice) => {\r\n if (subDevice.productName.toLocaleLowerCase() == \"ace 1500\") {\r\n calculateEnergy(\r\n adapter,\r\n subDevice.productKey,\r\n subDevice.deviceKey\r\n );\r\n }\r\n });\r\n }\r\n }\r\n });\r\n });\r\n};\r\n\r\nexport const startCheckStatesAndConnectionJob = async (\r\n adapter: ZendureSolarflow\r\n): Promise<void> => {\r\n // Check for states that has no updates in the last 5 minutes and set them to 0\r\n const statesToReset: string[] = [\r\n \"outputHomePower\",\r\n \"outputPackPower\",\r\n \"gridInputPower\",\r\n \"packInputPower\",\r\n \"solarInputPower\",\r\n ];\r\n\r\n let refreshAccessTokenNeeded = false;\r\n\r\n adapter.log.debug(\r\n `[checkStatesJob] Starting check of states and connection!`\r\n );\r\n\r\n adapter.checkStatesJob = scheduleJob(\"*/5 * * * *\", async () => {\r\n adapter.deviceList.forEach(async (device: ISolarFlowDeviceDetails) => {\r\n if (refreshAccessTokenNeeded) {\r\n return;\r\n }\r\n\r\n const lastUpdate = await adapter?.getStateAsync(\r\n device.productKey + \".\" + device.deviceKey + \".lastUpdate\"\r\n );\r\n\r\n const wifiState = await adapter?.getStateAsync(\r\n device.productKey + \".\" + device.deviceKey + \".wifiState\"\r\n );\r\n\r\n const fiveMinutesAgo = (Date.now() / 1000 - 5 * 60) * 1000; // Five minutes ago\r\n const tenMinutesAgo = (Date.now() / 1000 - 10 * 60) * 1000; // Ten minutes ago\r\n\r\n if (\r\n lastUpdate &&\r\n lastUpdate.val &&\r\n Number(lastUpdate.val) < fiveMinutesAgo &&\r\n wifiState?.val == \"Connected\"\r\n ) {\r\n adapter.log.warn(\r\n `[checkStatesJob] Last update for deviceKey ${\r\n device.deviceKey\r\n } was at ${new Date(\r\n Number(lastUpdate)\r\n )}, device seems to be online - so maybe connection is broken - restart adapter in 20 seconds!`\r\n );\r\n\r\n await adapter.delay(20 * 1000);\r\n adapter.restart();\r\n\r\n // set marker, so we discontinue the forEach Loop because of reconnect!\r\n refreshAccessTokenNeeded = true;\r\n }\r\n\r\n if (\r\n lastUpdate &&\r\n lastUpdate.val &&\r\n Number(lastUpdate.val) < tenMinutesAgo &&\r\n !refreshAccessTokenNeeded\r\n ) {\r\n adapter.log.debug(\r\n `[checkStatesJob] Last update for deviceKey ${\r\n device.deviceKey\r\n } was at ${new Date(\r\n Number(lastUpdate)\r\n )}, checking for pseudo power values!`\r\n );\r\n // State was not updated in the last 10 minutes... set states to 0\r\n await statesToReset.forEach(async (stateName: string) => {\r\n await adapter?.setState(\r\n device.productKey + \".\" + device.deviceKey + \".\" + stateName,\r\n 0,\r\n true\r\n );\r\n });\r\n\r\n // set electricLevel from deviceList\r\n if (device.electricity) {\r\n await adapter?.setState(\r\n device.productKey + \".\" + device.deviceKey + \".electricLevel\",\r\n device.electricity,\r\n true\r\n );\r\n }\r\n }\r\n });\r\n });\r\n};\r\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,2BAA4B;AAK5B,gCAAmD;AA4C5C,MAAM,kCAAkC,OAC7C,YACkB;AAClB,UAAQ,6BAA6B,QAAQ;AAAA,IAC3C,YAAY;AACV,cAAQ,IAAI;AAAA,QACV;AAAA,MACF;AAEA,YAAM,QAAQ,MAAM,KAAK,GAAI;AAC7B,cAAQ,QAAQ;AAAA,IAClB;AAAA,IACA,IAAI,KAAK,KAAK;AAAA,EAChB;AACF;AAEO,MAAM,sBAAsB,OACjC,YACkB;AAClB,UAAQ,qBAAiB,kCAAY,eAAe,MAAM;AAExD,qDAAkB,OAAO;AAAA,EAC3B,CAAC;AACH;AAEO,MAAM,sBAAsB,OACjC,YACkB;AAClB,UAAQ,qBAAiB,kCAAY,kBAAkB,MAAM;AAC3D,YAAQ,WAAW,QAAQ,CAAC,WAAW;AACrC,UAAI,OAAO,cAAc,UAAU;AACjC,uDAAgB,SAAS,OAAO,YAAY,OAAO,SAAS;AAG5D,YAAI,OAAO,YAAY,OAAO,SAAS,SAAS,GAAG;AACjD,iBAAO,SAAS,QAAQ,OAAO,cAAc;AAC3C,gBAAI,UAAU,YAAY,kBAAkB,KAAK,YAAY;AAC3D;AAAA,gBACE;AAAA,gBACA,UAAU;AAAA,gBACV,UAAU;AAAA,cACZ;AAAA,YACF;AAAA,UACF,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AACH;AAEO,MAAM,mCAAmC,OAC9C,YACkB;AAElB,QAAM,gBAA0B;AAAA,IAC9B;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,MAAI,2BAA2B;AAE/B,UAAQ,IAAI;AAAA,IACV;AAAA,EACF;AAEA,UAAQ,qBAAiB,kCAAY,eAAe,YAAY;AAC9D,YAAQ,WAAW,QAAQ,OAAO,WAAoC;AACpE,UAAI,0BAA0B;AAC5B;AAAA,MACF;AAEA,YAAM,aAAa,OAAM,mCAAS;AAAA,QAChC,OAAO,aAAa,MAAM,OAAO,YAAY;AAAA;AAG/C,YAAM,YAAY,OAAM,mCAAS;AAAA,QAC/B,OAAO,aAAa,MAAM,OAAO,YAAY;AAAA;AAG/C,YAAM,kBAAkB,KAAK,IAAI,IAAI,MAAO,IAAI,MAAM;AACtD,YAAM,iBAAiB,KAAK,IAAI,IAAI,MAAO,KAAK,MAAM;AAEtD,UACE,cACA,WAAW,OACX,OAAO,WAAW,GAAG,IAAI,mBACzB,uCAAW,QAAO,aAClB;AACA,gBAAQ,IAAI;AAAA,UACV,8CACE,OAAO,SACT,WAAW,IAAI;AAAA,YACb,OAAO,UAAU;AAAA,UACnB,CAAC;AAAA,QACH;AAEA,cAAM,QAAQ,MAAM,KAAK,GAAI;AAC7B,gBAAQ,QAAQ;AAGhB,mCAA2B;AAAA,MAC7B;AAEA,UACE,cACA,WAAW,OACX,OAAO,WAAW,GAAG,IAAI,iBACzB,CAAC,0BACD;AACA,gBAAQ,IAAI;AAAA,UACV,8CACE,OAAO,SACT,WAAW,IAAI;AAAA,YACb,OAAO,UAAU;AAAA,UACnB,CAAC;AAAA,QACH;AAEA,cAAM,cAAc,QAAQ,OAAO,cAAsB;AACvD,iBAAM,mCAAS;AAAA,YACb,OAAO,aAAa,MAAM,OAAO,YAAY,MAAM;AAAA,YACnD;AAAA,YACA;AAAA;AAAA,QAEJ,CAAC;AAGD,YAAI,OAAO,aAAa;AACtB,iBAAM,mCAAS;AAAA,YACb,OAAO,aAAa,MAAM,OAAO,YAAY;AAAA,YAC7C,OAAO;AAAA,YACP;AAAA;AAAA,QAEJ;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AACH;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "zendure-solarflow",
|
|
4
|
-
"version": "1.10.
|
|
4
|
+
"version": "1.10.5",
|
|
5
5
|
"news": {
|
|
6
|
+
"1.10.5": {
|
|
7
|
+
"en": "Fix start of calculation if HUB is connected with Ace",
|
|
8
|
+
"de": "Fix Beginn der Berechnung, wenn HUB mit Ace verbunden ist",
|
|
9
|
+
"ru": "Исправить начало расчета, если HUB подключен к Ace",
|
|
10
|
+
"pt": "Corrigir o início do cálculo se HUB estiver conectado com Ace",
|
|
11
|
+
"nl": "Fix begin van berekening als HUB is verbonden met Ace",
|
|
12
|
+
"fr": "Correction du début du calcul si HUB est connecté à Ace",
|
|
13
|
+
"it": "Fissare l'inizio del calcolo se HUB è collegato con Ace",
|
|
14
|
+
"es": "Arranque el inicio del cálculo si HUB está conectado con Ace",
|
|
15
|
+
"pl": "Popraw początek obliczeń, jeśli HUB jest połączony z Asem",
|
|
16
|
+
"uk": "Виправлення запуску розрахунку, якщо HUB підключено до Ace",
|
|
17
|
+
"zh-cn": "如果 HUB 与 Ace 连接, 则固定开始计算"
|
|
18
|
+
},
|
|
6
19
|
"1.10.4": {
|
|
7
20
|
"en": "Fix \"Grid Input Power\" state if connected with Ace",
|
|
8
21
|
"de": "Fix \"Grid Input Power\" Zustand, wenn mit Ace verbunden",
|
|
@@ -80,19 +93,6 @@
|
|
|
80
93
|
"pl": "Napraw dla niskiego napięcia Blok nie wyłączony.",
|
|
81
94
|
"uk": "Фіксація для низького напруги Блок не деактивований.",
|
|
82
95
|
"zh-cn": "低电压修复 屏蔽未关闭 ."
|
|
83
|
-
},
|
|
84
|
-
"1.9.2": {
|
|
85
|
-
"en": "Fix state some definitions",
|
|
86
|
-
"de": "Einige Definitionen festlegen",
|
|
87
|
-
"ru": "Установить некоторые определения",
|
|
88
|
-
"pt": "Corrigir estado algumas definições",
|
|
89
|
-
"nl": "Fix geeft enkele definities",
|
|
90
|
-
"fr": "Fixer les définitions",
|
|
91
|
-
"it": "Fissare lo stato alcune definizioni",
|
|
92
|
-
"es": "Determinar el estado algunas definiciones",
|
|
93
|
-
"pl": "Popraw niektóre definicje",
|
|
94
|
-
"uk": "Виправлення стану деяких визначення",
|
|
95
|
-
"zh-cn": "修正一些定义"
|
|
96
96
|
}
|
|
97
97
|
},
|
|
98
98
|
"titleLang": {
|