iobroker.parcelapp 0.2.13 → 0.2.15
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 +19 -17
- package/admin/i18n/de/translations.json +0 -5
- package/admin/i18n/en/translations.json +0 -5
- package/admin/i18n/es/translations.json +0 -5
- package/admin/i18n/fr/translations.json +0 -5
- package/admin/i18n/it/translations.json +0 -5
- package/admin/i18n/nl/translations.json +0 -5
- package/admin/i18n/pl/translations.json +0 -5
- package/admin/i18n/pt/translations.json +0 -5
- package/admin/i18n/ru/translations.json +0 -5
- package/admin/i18n/uk/translations.json +0 -5
- package/admin/i18n/zh-cn/translations.json +0 -5
- package/admin/jsonConfig.json +0 -17
- package/build/lib/state-manager.js +107 -24
- package/build/lib/state-manager.js.map +2 -2
- package/build/lib/types.js +134 -23
- package/build/lib/types.js.map +2 -2
- package/build/main.js +36 -5
- package/build/main.js.map +2 -2
- package/io-package.json +123 -84
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -24,8 +24,8 @@ ioBroker adapter that connects to the [parcel.app](https://parcelapp.net) API an
|
|
|
24
24
|
- **Automatic polling** with configurable interval (5–60 minutes)
|
|
25
25
|
- **Configurable cleanup** — auto-remove delivered packages or keep them until deleted in parcel.app
|
|
26
26
|
- **Add deliveries** via sendTo message from scripts or other adapters
|
|
27
|
-
- **Admin UI** with connection test
|
|
28
|
-
- **Status labels
|
|
27
|
+
- **Admin UI** with connection test and polling settings
|
|
28
|
+
- **Status labels follow the ioBroker system language** — all 11 supported languages (de, en, ru, pt, nl, fr, it, es, pl, uk, zh-cn), English fallback for unknown codes
|
|
29
29
|
|
|
30
30
|
---
|
|
31
31
|
|
|
@@ -45,7 +45,8 @@ ioBroker adapter that connects to the [parcel.app](https://parcelapp.net) API an
|
|
|
45
45
|
| **API Key** | Your parcel.app API key (get it at [web.parcelapp.net](https://web.parcelapp.net)) | — |
|
|
46
46
|
| **Poll Interval** | How often to fetch updates (minutes) | 10 |
|
|
47
47
|
| **Auto-remove delivered** | Remove delivered packages from states automatically. When disabled, they stay until deleted in parcel.app. | Yes |
|
|
48
|
-
|
|
48
|
+
|
|
49
|
+
Status labels (`Delivered`, `In Transit`, …) and delivery estimates (`today`, `tomorrow`, `in X days`) are rendered in the ioBroker system language.
|
|
49
50
|
|
|
50
51
|
---
|
|
51
52
|
|
|
@@ -114,6 +115,18 @@ The delivery is added to your parcel.app account and immediately appears in ioBr
|
|
|
114
115
|
---
|
|
115
116
|
|
|
116
117
|
## Changelog
|
|
118
|
+
### 0.2.15 (2026-04-26)
|
|
119
|
+
- Process-level `unhandledRejection` / `uncaughtException` handlers added as last-line-of-defence against fire-and-forget rejections.
|
|
120
|
+
- Stop shipping the `manual-review` release-script plugin — adapter-only consequence.
|
|
121
|
+
- Bump min js-controller to `>=7.0.23` (matches latest-repo recommendation).
|
|
122
|
+
- Audit-driven boilerplate sync with the other krobi adapters (`.vscode` json5 schemas, `tsconfig.test` looser test rules).
|
|
123
|
+
- README footer-link to `CHANGELOG_OLD.md` restored, `CHANGELOG_OLD.md` cleaned up to consistent compact style.
|
|
124
|
+
|
|
125
|
+
### 0.2.14 (2026-04-23)
|
|
126
|
+
- Separate test-build output (`build-test/`) from production `build/`, no more duplicated `build/src` + `build/test` tree in published packages.
|
|
127
|
+
- Declare `deliveries` folder and `summary` channel as instance objects so their parent exists before per-package states appear.
|
|
128
|
+
- Localize status labels and delivery estimates to all 11 ioBroker languages via `system.config.language`; the per-instance `Status Language` option is removed.
|
|
129
|
+
- Fix `summary.todayCount` for non-DE/EN languages (filter compared estimate strings against `heute`/`today`, so it always returned zero elsewhere).
|
|
117
130
|
|
|
118
131
|
### 0.2.13 (2026-04-19)
|
|
119
132
|
- Latest-repo review compliance: `common.messagebox=true` added because the admin-UI `Check Connection` and `Add Delivery` buttons route through `onMessage`. Runtime behaviour unchanged.
|
|
@@ -131,25 +144,14 @@ The delivery is added to your parcel.app account and immediately appears in ioBr
|
|
|
131
144
|
- Simplify obsolete state cleanup, use setObjectNotExistsAsync for states
|
|
132
145
|
|
|
133
146
|
### 0.2.10 (2026-04-12)
|
|
134
|
-
- Fix test timezone bug, remove unused devDependencies, add `no-floating-promises` lint rule
|
|
135
|
-
- Remove redundant `actions/checkout` from CI workflow
|
|
147
|
+
- Fix test timezone bug, remove unused devDependencies, add `no-floating-promises` lint rule.
|
|
148
|
+
- Remove redundant `actions/checkout` from CI workflow.
|
|
136
149
|
|
|
137
150
|
### 0.2.9 (2026-04-08)
|
|
138
|
-
-
|
|
139
|
-
|
|
140
|
-
### 0.2.8 (2026-04-05)
|
|
141
|
-
- Clean up empty parent folders after removing obsolete states
|
|
142
|
-
|
|
143
|
-
### 0.2.7 (2026-04-05)
|
|
144
|
-
- Consistent UI labels across all adapters
|
|
145
|
-
|
|
146
|
-
### 0.2.6 (2026-04-05)
|
|
147
|
-
- Remove redundant scripts, compress documentation
|
|
151
|
+
- Standard ioBroker test suite added, test-build config optimised.
|
|
148
152
|
|
|
149
153
|
Older entries have been moved to [CHANGELOG_OLD.md](CHANGELOG_OLD.md).
|
|
150
154
|
|
|
151
|
-
---
|
|
152
|
-
|
|
153
155
|
## Support
|
|
154
156
|
|
|
155
157
|
- [ioBroker Forum](https://forum.iobroker.net/)
|
|
@@ -7,17 +7,12 @@
|
|
|
7
7
|
"donatePaypal": "Spenden via PayPal",
|
|
8
8
|
"header_connection": "parcel.app API",
|
|
9
9
|
"header_delivery": "Lieferungen",
|
|
10
|
-
"header_display": "Anzeige",
|
|
11
10
|
"header_polling": "Abfrage",
|
|
12
11
|
"label_apiKey": "API-Schlüssel",
|
|
13
12
|
"label_autoRemoveDelivered": "Zugestellte Pakete automatisch entfernen",
|
|
14
|
-
"label_language": "Status-Sprache",
|
|
15
13
|
"label_pollInterval": "Abfrageintervall (Minuten)",
|
|
16
|
-
"lang_de": "Deutsch",
|
|
17
|
-
"lang_en": "Englisch",
|
|
18
14
|
"msg_connectionFailed": "Verbindung fehlgeschlagen!",
|
|
19
15
|
"msg_connectionOk": "Verbindung erfolgreich!",
|
|
20
16
|
"tooltip_autoRemoveDelivered": "Wenn aktiviert, werden zugestellte Pakete automatisch aus dem Objektbaum entfernt. Wenn deaktiviert, bleiben sie mit Status 'Zugestellt' bestehen, bis sie in parcel.app gelöscht werden.",
|
|
21
|
-
"tooltip_language": "Sprache für Statusmeldungen (z.B. 'Unterwegs' vs 'In Transit')",
|
|
22
17
|
"tooltip_pollInterval": "Wie oft Lieferupdates abgefragt werden (5-60 Minuten, Standard: 10). Rate-Limit: 20 Anfragen/Stunde."
|
|
23
18
|
}
|
|
@@ -11,11 +11,6 @@
|
|
|
11
11
|
"header_delivery": "Delivery",
|
|
12
12
|
"label_autoRemoveDelivered": "Automatically remove delivered packages",
|
|
13
13
|
"tooltip_autoRemoveDelivered": "When enabled, delivered packages are automatically removed from the state tree. When disabled, they remain with status 'Delivered' until deleted in parcel.app.",
|
|
14
|
-
"header_display": "Display",
|
|
15
|
-
"label_language": "Status Language",
|
|
16
|
-
"tooltip_language": "Language for delivery status labels (e.g. 'In Transit' vs 'Unterwegs')",
|
|
17
|
-
"lang_de": "German",
|
|
18
|
-
"lang_en": "English",
|
|
19
14
|
"supportHeader": "Support",
|
|
20
15
|
"aboutInfo": "This adapter is free and open source. If you find it useful, please consider supporting its development with a small donation.",
|
|
21
16
|
"donateKofi": "Support on Ko-fi",
|
|
@@ -7,17 +7,12 @@
|
|
|
7
7
|
"donatePaypal": "Donar vía PayPal",
|
|
8
8
|
"header_connection": "parcel.app API",
|
|
9
9
|
"header_delivery": "Entregas",
|
|
10
|
-
"header_display": "Visualización",
|
|
11
10
|
"header_polling": "Consulta",
|
|
12
11
|
"label_apiKey": "Clave API",
|
|
13
12
|
"label_autoRemoveDelivered": "Eliminar automáticamente paquetes entregados",
|
|
14
|
-
"label_language": "Idioma de estados",
|
|
15
13
|
"label_pollInterval": "Intervalo de consulta (minutos)",
|
|
16
|
-
"lang_de": "Alemán",
|
|
17
|
-
"lang_en": "Inglés",
|
|
18
14
|
"msg_connectionFailed": "¡Error de conexión!",
|
|
19
15
|
"msg_connectionOk": "¡Conexión exitosa!",
|
|
20
16
|
"tooltip_autoRemoveDelivered": "Cuando está activado, los paquetes entregados se eliminan automáticamente del árbol de estados. Cuando está desactivado, permanecen con el estado 'Entregado' hasta ser eliminados en parcel.app.",
|
|
21
|
-
"tooltip_language": "Idioma para las etiquetas de estado de entrega",
|
|
22
17
|
"tooltip_pollInterval": "Con qué frecuencia obtener actualizaciones de entrega (5-60 minutos, predeterminado: 10). Límite: 20 solicitudes/hora."
|
|
23
18
|
}
|
|
@@ -7,17 +7,12 @@
|
|
|
7
7
|
"donatePaypal": "Faire un don via PayPal",
|
|
8
8
|
"header_connection": "parcel.app API",
|
|
9
9
|
"header_delivery": "Livraisons",
|
|
10
|
-
"header_display": "Affichage",
|
|
11
10
|
"header_polling": "Interrogation",
|
|
12
11
|
"label_apiKey": "Clé API",
|
|
13
12
|
"label_autoRemoveDelivered": "Supprimer automatiquement les colis livrés",
|
|
14
|
-
"label_language": "Langue des statuts",
|
|
15
13
|
"label_pollInterval": "Intervalle d interrogation (minutes)",
|
|
16
|
-
"lang_de": "Allemand",
|
|
17
|
-
"lang_en": "Anglais",
|
|
18
14
|
"msg_connectionFailed": "Échec de la connexion !",
|
|
19
15
|
"msg_connectionOk": "Connexion réussie !",
|
|
20
16
|
"tooltip_autoRemoveDelivered": "Lorsque activé, les colis livrés sont automatiquement supprimés de l'arborescence d'états. Lorsque désactivé, ils restent avec le statut 'Livré' jusqu'à leur suppression dans parcel.app.",
|
|
21
|
-
"tooltip_language": "Langue pour les libellés de statut de livraison",
|
|
22
17
|
"tooltip_pollInterval": "Fréquence de récupération des mises à jour de livraison (5-60 minutes, défaut : 10). Limite : 20 requêtes/heure."
|
|
23
18
|
}
|
|
@@ -7,17 +7,12 @@
|
|
|
7
7
|
"donatePaypal": "Dona tramite PayPal",
|
|
8
8
|
"header_connection": "parcel.app API",
|
|
9
9
|
"header_delivery": "Consegne",
|
|
10
|
-
"header_display": "Visualizzazione",
|
|
11
10
|
"header_polling": "Polling",
|
|
12
11
|
"label_apiKey": "Chiave API",
|
|
13
12
|
"label_autoRemoveDelivered": "Rimuovi automaticamente i pacchi consegnati",
|
|
14
|
-
"label_language": "Lingua degli stati",
|
|
15
13
|
"label_pollInterval": "Intervallo di polling (minuti)",
|
|
16
|
-
"lang_de": "Tedesco",
|
|
17
|
-
"lang_en": "Inglese",
|
|
18
14
|
"msg_connectionFailed": "Connessione fallita!",
|
|
19
15
|
"msg_connectionOk": "Connessione riuscita!",
|
|
20
16
|
"tooltip_autoRemoveDelivered": "Se attivato, i pacchi consegnati vengono automaticamente rimossi dall'albero degli stati. Se disattivato, rimangono con lo stato 'Consegnato' fino alla cancellazione in parcel.app.",
|
|
21
|
-
"tooltip_language": "Lingua per le etichette di stato della consegna",
|
|
22
17
|
"tooltip_pollInterval": "Frequenza di aggiornamento delle consegne (5-60 minuti, predefinito: 10). Limite: 20 richieste/ora."
|
|
23
18
|
}
|
|
@@ -7,17 +7,12 @@
|
|
|
7
7
|
"donatePaypal": "Doneren via PayPal",
|
|
8
8
|
"header_connection": "parcel.app API",
|
|
9
9
|
"header_delivery": "Leveringen",
|
|
10
|
-
"header_display": "Weergave",
|
|
11
10
|
"header_polling": "Polling",
|
|
12
11
|
"label_apiKey": "API-sleutel",
|
|
13
12
|
"label_autoRemoveDelivered": "Afgeleverde pakketten automatisch verwijderen",
|
|
14
|
-
"label_language": "Statustaal",
|
|
15
13
|
"label_pollInterval": "Polling-interval (minuten)",
|
|
16
|
-
"lang_de": "Duits",
|
|
17
|
-
"lang_en": "Engels",
|
|
18
14
|
"msg_connectionFailed": "Verbinding mislukt!",
|
|
19
15
|
"msg_connectionOk": "Verbinding succesvol!",
|
|
20
16
|
"tooltip_autoRemoveDelivered": "Indien ingeschakeld worden afgeleverde pakketten automatisch uit de objectboom verwijderd. Indien uitgeschakeld blijven ze met status 'Afgeleverd' bestaan tot ze in parcel.app worden verwijderd.",
|
|
21
|
-
"tooltip_language": "Taal voor bezorgingsstatussen",
|
|
22
17
|
"tooltip_pollInterval": "Hoe vaak bezorgingsupdates ophalen (5-60 minuten, standaard: 10). Limiet: 20 verzoeken/uur."
|
|
23
18
|
}
|
|
@@ -7,17 +7,12 @@
|
|
|
7
7
|
"donatePaypal": "Przekaż darowiznę przez PayPal",
|
|
8
8
|
"header_connection": "parcel.app API",
|
|
9
9
|
"header_delivery": "Dostawy",
|
|
10
|
-
"header_display": "Wyświetlanie",
|
|
11
10
|
"header_polling": "Odpytywanie",
|
|
12
11
|
"label_apiKey": "Klucz API",
|
|
13
12
|
"label_autoRemoveDelivered": "Automatycznie usuwaj dostarczone przesyłki",
|
|
14
|
-
"label_language": "Język statusów",
|
|
15
13
|
"label_pollInterval": "Interwał odpytywania (minuty)",
|
|
16
|
-
"lang_de": "Niemiecki",
|
|
17
|
-
"lang_en": "Angielski",
|
|
18
14
|
"msg_connectionFailed": "Połączenie nieudane!",
|
|
19
15
|
"msg_connectionOk": "Połączenie udane!",
|
|
20
16
|
"tooltip_autoRemoveDelivered": "Po włączeniu dostarczone przesyłki są automatycznie usuwane z drzewa stanów. Po wyłączeniu pozostają ze statusem 'Dostarczono' do usunięcia w parcel.app.",
|
|
21
|
-
"tooltip_language": "Język dla etykiet statusu dostawy",
|
|
22
17
|
"tooltip_pollInterval": "Jak często pobierać aktualizacje dostaw (5-60 minut, domyślnie: 10). Limit: 20 zapytań/godzinę."
|
|
23
18
|
}
|
|
@@ -7,17 +7,12 @@
|
|
|
7
7
|
"donatePaypal": "Doar via PayPal",
|
|
8
8
|
"header_connection": "parcel.app API",
|
|
9
9
|
"header_delivery": "Entregas",
|
|
10
|
-
"header_display": "Exibição",
|
|
11
10
|
"header_polling": "Consulta",
|
|
12
11
|
"label_apiKey": "Chave API",
|
|
13
12
|
"label_autoRemoveDelivered": "Remover automaticamente pacotes entregues",
|
|
14
|
-
"label_language": "Idioma do status",
|
|
15
13
|
"label_pollInterval": "Intervalo de consulta (minutos)",
|
|
16
|
-
"lang_de": "Alemão",
|
|
17
|
-
"lang_en": "Inglês",
|
|
18
14
|
"msg_connectionFailed": "Falha na conexão!",
|
|
19
15
|
"msg_connectionOk": "Conexão bem-sucedida!",
|
|
20
16
|
"tooltip_autoRemoveDelivered": "Quando ativado, os pacotes entregues são automaticamente removidos da árvore de estados. Quando desativado, permanecem com o estado 'Entregue' até serem eliminados no parcel.app.",
|
|
21
|
-
"tooltip_language": "Idioma para os rótulos de status de entrega",
|
|
22
17
|
"tooltip_pollInterval": "Com que frequência buscar atualizações de entrega (5-60 minutos, padrão: 10). Limite: 20 solicitações/hora."
|
|
23
18
|
}
|
|
@@ -7,17 +7,12 @@
|
|
|
7
7
|
"donatePaypal": "Пожертвовать через PayPal",
|
|
8
8
|
"header_connection": "parcel.app API",
|
|
9
9
|
"header_delivery": "Доставки",
|
|
10
|
-
"header_display": "Отображение",
|
|
11
10
|
"header_polling": "Опрос",
|
|
12
11
|
"label_apiKey": "API-ключ",
|
|
13
12
|
"label_autoRemoveDelivered": "Автоматически удалять доставленные посылки",
|
|
14
|
-
"label_language": "Язык статусов",
|
|
15
13
|
"label_pollInterval": "Интервал опроса (минуты)",
|
|
16
|
-
"lang_de": "Немецкий",
|
|
17
|
-
"lang_en": "Английский",
|
|
18
14
|
"msg_connectionFailed": "Ошибка соединения!",
|
|
19
15
|
"msg_connectionOk": "Соединение успешно!",
|
|
20
16
|
"tooltip_autoRemoveDelivered": "Если включено, доставленные посылки автоматически удаляются из дерева состояний. Если выключено, они остаются со статусом 'Доставлено' до удаления в parcel.app.",
|
|
21
|
-
"tooltip_language": "Язык для статусов доставки",
|
|
22
17
|
"tooltip_pollInterval": "Как часто получать обновления доставки (5-60 минут, по умолчанию: 10). Лимит: 20 запросов/час."
|
|
23
18
|
}
|
|
@@ -7,17 +7,12 @@
|
|
|
7
7
|
"donatePaypal": "Пожертвувати через PayPal",
|
|
8
8
|
"header_connection": "parcel.app API",
|
|
9
9
|
"header_delivery": "Доставки",
|
|
10
|
-
"header_display": "Відображення",
|
|
11
10
|
"header_polling": "Опитування",
|
|
12
11
|
"label_apiKey": "API-ключ",
|
|
13
12
|
"label_autoRemoveDelivered": "Автоматично видаляти доставлені посилки",
|
|
14
|
-
"label_language": "Мова статусів",
|
|
15
13
|
"label_pollInterval": "Інтервал опитування (хвилини)",
|
|
16
|
-
"lang_de": "Німецька",
|
|
17
|
-
"lang_en": "Англійська",
|
|
18
14
|
"msg_connectionFailed": "Помилка з'єднання!",
|
|
19
15
|
"msg_connectionOk": "З'єднання успішне!",
|
|
20
16
|
"tooltip_autoRemoveDelivered": "Якщо увімкнено, доставлені посилки автоматично видаляються з дерева станів. Якщо вимкнено, вони залишаються зі статусом 'Доставлено' до видалення в parcel.app.",
|
|
21
|
-
"tooltip_language": "Мова для міток статусу доставки",
|
|
22
17
|
"tooltip_pollInterval": "Як часто отримувати оновлення доставки (5-60 хвилин, за замовчуванням: 10). Ліміт: 20 запитів/годину."
|
|
23
18
|
}
|
|
@@ -7,17 +7,12 @@
|
|
|
7
7
|
"donatePaypal": "通过 PayPal 捐赠",
|
|
8
8
|
"header_connection": "parcel.app API",
|
|
9
9
|
"header_delivery": "配送",
|
|
10
|
-
"header_display": "显示",
|
|
11
10
|
"header_polling": "轮询",
|
|
12
11
|
"label_apiKey": "API 密钥",
|
|
13
12
|
"label_autoRemoveDelivered": "自动移除已送达的包裹",
|
|
14
|
-
"label_language": "状态语言",
|
|
15
13
|
"label_pollInterval": "轮询间隔(分钟)",
|
|
16
|
-
"lang_de": "德语",
|
|
17
|
-
"lang_en": "英语",
|
|
18
14
|
"msg_connectionFailed": "连接失败!",
|
|
19
15
|
"msg_connectionOk": "连接成功!",
|
|
20
16
|
"tooltip_autoRemoveDelivered": "启用后,已送达的包裹会自动从状态树中移除。禁用后,它们将保持'已送达'状态,直到在 parcel.app 中删除。",
|
|
21
|
-
"tooltip_language": "快递状态标签的语言",
|
|
22
17
|
"tooltip_pollInterval": "多久获取一次快递更新(5-60分钟,默认:10)。速率限制:20次请求/小时。"
|
|
23
18
|
}
|
package/admin/jsonConfig.json
CHANGED
|
@@ -62,23 +62,6 @@
|
|
|
62
62
|
"default": true,
|
|
63
63
|
"xs": 12, "sm": 12, "md": 12, "lg": 12, "xl": 12
|
|
64
64
|
},
|
|
65
|
-
"displayHeader": {
|
|
66
|
-
"newLine": true,
|
|
67
|
-
"type": "header",
|
|
68
|
-
"text": "header_display",
|
|
69
|
-
"size": 5
|
|
70
|
-
},
|
|
71
|
-
"language": {
|
|
72
|
-
"type": "select",
|
|
73
|
-
"label": "label_language",
|
|
74
|
-
"tooltip": "tooltip_language",
|
|
75
|
-
"default": "de",
|
|
76
|
-
"options": [
|
|
77
|
-
{ "label": "lang_de", "value": "de" },
|
|
78
|
-
{ "label": "lang_en", "value": "en" }
|
|
79
|
-
],
|
|
80
|
-
"xs": 12, "sm": 12, "md": 6, "lg": 6, "xl": 6
|
|
81
|
-
},
|
|
82
65
|
"_supportHeader": {
|
|
83
66
|
"newLine": true,
|
|
84
67
|
"type": "header",
|
|
@@ -18,7 +18,8 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
18
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
19
|
var state_manager_exports = {};
|
|
20
20
|
__export(state_manager_exports, {
|
|
21
|
-
StateManager: () => StateManager
|
|
21
|
+
StateManager: () => StateManager,
|
|
22
|
+
resolveLanguage: () => resolveLanguage
|
|
22
23
|
});
|
|
23
24
|
module.exports = __toCommonJS(state_manager_exports);
|
|
24
25
|
var import_types = require("./types");
|
|
@@ -45,13 +46,78 @@ const ESTIMATE_LABELS = {
|
|
|
45
46
|
today: "today",
|
|
46
47
|
tomorrow: "tomorrow",
|
|
47
48
|
days: "in %d days"
|
|
49
|
+
},
|
|
50
|
+
ru: {
|
|
51
|
+
overdue: "\u043F\u0440\u043E\u0441\u0440\u043E\u0447\u0435\u043D\u043E",
|
|
52
|
+
today: "\u0441\u0435\u0433\u043E\u0434\u043D\u044F",
|
|
53
|
+
tomorrow: "\u0437\u0430\u0432\u0442\u0440\u0430",
|
|
54
|
+
days: "\u0447\u0435\u0440\u0435\u0437 %d \u0434\u043D."
|
|
55
|
+
},
|
|
56
|
+
pt: {
|
|
57
|
+
overdue: "atrasado",
|
|
58
|
+
today: "hoje",
|
|
59
|
+
tomorrow: "amanh\xE3",
|
|
60
|
+
days: "em %d dias"
|
|
61
|
+
},
|
|
62
|
+
nl: {
|
|
63
|
+
overdue: "te laat",
|
|
64
|
+
today: "vandaag",
|
|
65
|
+
tomorrow: "morgen",
|
|
66
|
+
days: "over %d dagen"
|
|
67
|
+
},
|
|
68
|
+
fr: {
|
|
69
|
+
overdue: "en retard",
|
|
70
|
+
today: "aujourd'hui",
|
|
71
|
+
tomorrow: "demain",
|
|
72
|
+
days: "dans %d jours"
|
|
73
|
+
},
|
|
74
|
+
it: {
|
|
75
|
+
overdue: "in ritardo",
|
|
76
|
+
today: "oggi",
|
|
77
|
+
tomorrow: "domani",
|
|
78
|
+
days: "tra %d giorni"
|
|
79
|
+
},
|
|
80
|
+
es: {
|
|
81
|
+
overdue: "atrasado",
|
|
82
|
+
today: "hoy",
|
|
83
|
+
tomorrow: "ma\xF1ana",
|
|
84
|
+
days: "en %d d\xEDas"
|
|
85
|
+
},
|
|
86
|
+
pl: {
|
|
87
|
+
overdue: "zaleg\u0142e",
|
|
88
|
+
today: "dzisiaj",
|
|
89
|
+
tomorrow: "jutro",
|
|
90
|
+
days: "za %d dni"
|
|
91
|
+
},
|
|
92
|
+
uk: {
|
|
93
|
+
overdue: "\u043F\u0440\u043E\u0441\u0442\u0440\u043E\u0447\u0435\u043D\u043E",
|
|
94
|
+
today: "\u0441\u044C\u043E\u0433\u043E\u0434\u043D\u0456",
|
|
95
|
+
tomorrow: "\u0437\u0430\u0432\u0442\u0440\u0430",
|
|
96
|
+
days: "\u0447\u0435\u0440\u0435\u0437 %d \u0434\u043D."
|
|
97
|
+
},
|
|
98
|
+
"zh-cn": {
|
|
99
|
+
overdue: "\u5DF2\u903E\u671F",
|
|
100
|
+
today: "\u4ECA\u5929",
|
|
101
|
+
tomorrow: "\u660E\u5929",
|
|
102
|
+
days: "%d \u5929\u540E"
|
|
48
103
|
}
|
|
49
104
|
};
|
|
105
|
+
function resolveLanguage(language) {
|
|
106
|
+
if (typeof language === "string" && import_types.SUPPORTED_LANGUAGES.includes(language)) {
|
|
107
|
+
return language;
|
|
108
|
+
}
|
|
109
|
+
return import_types.FALLBACK_LANGUAGE;
|
|
110
|
+
}
|
|
50
111
|
class StateManager {
|
|
51
112
|
adapter;
|
|
52
|
-
|
|
53
|
-
|
|
113
|
+
language;
|
|
114
|
+
/**
|
|
115
|
+
* @param adapter The ioBroker adapter instance
|
|
116
|
+
* @param language Language code from system.config.language (falls back to English)
|
|
117
|
+
*/
|
|
118
|
+
constructor(adapter, language) {
|
|
54
119
|
this.adapter = adapter;
|
|
120
|
+
this.language = resolveLanguage(language);
|
|
55
121
|
}
|
|
56
122
|
/**
|
|
57
123
|
* Sanitize a string for use as ioBroker object ID (see adapter.FORBIDDEN_CHARS).
|
|
@@ -114,8 +180,8 @@ class StateManager {
|
|
|
114
180
|
native: {}
|
|
115
181
|
});
|
|
116
182
|
const statusCode = this.parseStatus(delivery);
|
|
117
|
-
const
|
|
118
|
-
const
|
|
183
|
+
const labels = import_types.STATUS_LABELS[this.language];
|
|
184
|
+
const statusText = labels[statusCode] || `Unknown (${statusCode})`;
|
|
119
185
|
await Promise.all([
|
|
120
186
|
this.createAndSet(
|
|
121
187
|
`${devicePath}.carrier`,
|
|
@@ -129,7 +195,7 @@ class StateManager {
|
|
|
129
195
|
"Status",
|
|
130
196
|
"string",
|
|
131
197
|
"text",
|
|
132
|
-
|
|
198
|
+
statusText
|
|
133
199
|
),
|
|
134
200
|
this.createAndSet(
|
|
135
201
|
`${devicePath}.statusCode`,
|
|
@@ -198,20 +264,14 @@ class StateManager {
|
|
|
198
264
|
}
|
|
199
265
|
/**
|
|
200
266
|
* Update summary states. Expects already-filtered active deliveries.
|
|
267
|
+
* The `summary` channel itself is declared via io-package.json instanceObjects.
|
|
201
268
|
*
|
|
202
269
|
* @param activeDeliveries Only active (non-delivered) deliveries
|
|
203
270
|
*/
|
|
204
271
|
async updateSummary(activeDeliveries) {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
native: {}
|
|
209
|
-
});
|
|
210
|
-
const todayDeliveries = activeDeliveries.filter((d) => {
|
|
211
|
-
const statusCode = this.parseStatus(d);
|
|
212
|
-
const estimate = this.calculateDeliveryEstimate(d, statusCode);
|
|
213
|
-
return estimate === "heute" || estimate === "today";
|
|
214
|
-
});
|
|
272
|
+
const todayDeliveries = activeDeliveries.filter(
|
|
273
|
+
(d) => this.isToday(d, this.parseStatus(d))
|
|
274
|
+
);
|
|
215
275
|
await Promise.all([
|
|
216
276
|
this.createAndSet(
|
|
217
277
|
"summary.activeCount",
|
|
@@ -284,14 +344,15 @@ class StateManager {
|
|
|
284
344
|
return end ? `${start} - ${end}` : start;
|
|
285
345
|
}
|
|
286
346
|
/**
|
|
287
|
-
*
|
|
347
|
+
* Days from today to the expected delivery date. Returns null when the
|
|
348
|
+
* delivery has no usable expected date or is in a non-trackable status.
|
|
288
349
|
*
|
|
289
350
|
* @param delivery The delivery data
|
|
290
351
|
* @param statusCode Pre-parsed status code
|
|
291
352
|
*/
|
|
292
|
-
|
|
353
|
+
computeDiffDays(delivery, statusCode) {
|
|
293
354
|
if (!TRACKABLE_STATUSES.has(statusCode)) {
|
|
294
|
-
return
|
|
355
|
+
return null;
|
|
295
356
|
}
|
|
296
357
|
let expectedDate = null;
|
|
297
358
|
const ts = coerceNumber(delivery.timestamp_expected);
|
|
@@ -301,7 +362,7 @@ class StateManager {
|
|
|
301
362
|
expectedDate = new Date(delivery.date_expected);
|
|
302
363
|
}
|
|
303
364
|
if (!expectedDate || isNaN(expectedDate.getTime())) {
|
|
304
|
-
return
|
|
365
|
+
return null;
|
|
305
366
|
}
|
|
306
367
|
const now = /* @__PURE__ */ new Date();
|
|
307
368
|
const todayStart = new Date(
|
|
@@ -314,11 +375,22 @@ class StateManager {
|
|
|
314
375
|
expectedDate.getMonth(),
|
|
315
376
|
expectedDate.getDate()
|
|
316
377
|
);
|
|
317
|
-
|
|
378
|
+
return Math.round(
|
|
318
379
|
(expectedStart.getTime() - todayStart.getTime()) / (1e3 * 60 * 60 * 24)
|
|
319
380
|
);
|
|
320
|
-
|
|
321
|
-
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Calculate human-readable delivery estimate.
|
|
384
|
+
*
|
|
385
|
+
* @param delivery The delivery data
|
|
386
|
+
* @param statusCode Pre-parsed status code
|
|
387
|
+
*/
|
|
388
|
+
calculateDeliveryEstimate(delivery, statusCode) {
|
|
389
|
+
const diffDays = this.computeDiffDays(delivery, statusCode);
|
|
390
|
+
if (diffDays === null) {
|
|
391
|
+
return "";
|
|
392
|
+
}
|
|
393
|
+
const l = ESTIMATE_LABELS[this.language];
|
|
322
394
|
if (diffDays < 0) {
|
|
323
395
|
return l.overdue;
|
|
324
396
|
}
|
|
@@ -330,6 +402,16 @@ class StateManager {
|
|
|
330
402
|
}
|
|
331
403
|
return l.days.replace("%d", String(diffDays));
|
|
332
404
|
}
|
|
405
|
+
/**
|
|
406
|
+
* Whether the delivery is expected today. Language-agnostic, used by the
|
|
407
|
+
* summary filter so `todayCount` works across all languages.
|
|
408
|
+
*
|
|
409
|
+
* @param delivery The delivery data
|
|
410
|
+
* @param statusCode Pre-parsed status code
|
|
411
|
+
*/
|
|
412
|
+
isToday(delivery, statusCode) {
|
|
413
|
+
return this.computeDiffDays(delivery, statusCode) === 0;
|
|
414
|
+
}
|
|
333
415
|
/**
|
|
334
416
|
* Format the latest tracking event.
|
|
335
417
|
*
|
|
@@ -413,6 +495,7 @@ class StateManager {
|
|
|
413
495
|
}
|
|
414
496
|
// Annotate the CommonJS export names for ESM import in node:
|
|
415
497
|
0 && (module.exports = {
|
|
416
|
-
StateManager
|
|
498
|
+
StateManager,
|
|
499
|
+
resolveLanguage
|
|
417
500
|
});
|
|
418
501
|
//# sourceMappingURL=state-manager.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/state-manager.ts"],
|
|
4
|
-
"sourcesContent": ["import type { AdapterInstance } from \"@iobroker/adapter-core\";\nimport type { ParcelDelivery } from \"./types\";\nimport { STATUS_LABELS_DE, STATUS_LABELS_EN } from \"./types\";\n\n/** Status codes that have expected delivery date/time */\nconst TRACKABLE_STATUSES = new Set([2, 4, 8]);\n\n/**\n * Coerce a value to a finite number. Accepts numbers and numeric strings.\n * Returns null for anything else \u2014 used to guard against API drift.\n *\n * @param v Value to coerce\n */\nfunction coerceNumber(v: unknown): number | null {\n if (typeof v === \"number\" && Number.isFinite(v)) {\n return v;\n }\n if (typeof v === \"string\" && v.length > 0) {\n const n = parseFloat(v);\n return Number.isFinite(n) ? n : null;\n }\n return null;\n}\n\nconst ESTIMATE_LABELS: Record<string, Record<string, string>> = {\n de: {\n overdue: \"\u00FCberf\u00E4llig\",\n today: \"heute\",\n tomorrow: \"morgen\",\n days: \"in %d Tagen\",\n },\n en: {\n overdue: \"overdue\",\n today: \"today\",\n tomorrow: \"tomorrow\",\n days: \"in %d days\",\n },\n};\n\n/** Manages ioBroker states for parcel deliveries */\nexport class StateManager {\n private adapter: AdapterInstance;\n\n /** @param adapter The ioBroker adapter instance */\n constructor(adapter: AdapterInstance) {\n this.adapter = adapter;\n }\n\n /**\n * Sanitize a string for use as ioBroker object ID (see adapter.FORBIDDEN_CHARS).\n * API-drift guard: returns \"unknown\" for non-string input.\n *\n * @param name Raw value to sanitize (any type)\n */\n sanitize(name: unknown): string {\n if (typeof name !== \"string\") {\n return \"unknown\";\n }\n return (\n name\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"_\")\n .replace(/^_+|_+$/g, \"\")\n .slice(0, 50) || \"unknown\"\n );\n }\n\n /**\n * Parse the status code from a delivery. API documents `status_code` as\n * a numeric string, but we accept numbers too and fall back to 0 for drift.\n *\n * @param delivery The delivery to parse\n */\n parseStatus(delivery: ParcelDelivery): number {\n const raw = delivery.status_code as unknown;\n if (typeof raw === \"number\" && Number.isFinite(raw)) {\n return Math.trunc(raw);\n }\n if (typeof raw === \"string\") {\n const n = parseInt(raw, 10);\n return Number.isFinite(n) ? n : 0;\n }\n return 0;\n }\n\n /**\n * Build a unique package ID from a delivery.\n *\n * @param delivery The delivery to build an ID for\n */\n packageId(delivery: ParcelDelivery): string {\n let id = this.sanitize(delivery.tracking_number);\n // API-drift guard: only string values extend the id\n if (\n typeof delivery.extra_information === \"string\" &&\n delivery.extra_information.length > 0\n ) {\n id += `_${this.sanitize(delivery.extra_information)}`;\n }\n return id;\n }\n\n /**\n * Update or create all states for a delivery.\n *\n * @param delivery The delivery data from API\n * @param carrierName Resolved carrier display name\n */\n async updateDelivery(\n delivery: ParcelDelivery,\n carrierName: string,\n ): Promise<void> {\n const pkgId = this.packageId(delivery);\n const devicePath = `deliveries.${pkgId}`;\n\n const description =\n typeof delivery.description === \"string\" ? delivery.description : \"\";\n const trackingNumber =\n typeof delivery.tracking_number === \"string\"\n ? delivery.tracking_number\n : \"\";\n const extraInfo =\n typeof delivery.extra_information === \"string\"\n ? delivery.extra_information\n : \"\";\n\n await this.adapter.extendObjectAsync(devicePath, {\n type: \"device\",\n common: {\n name: description || `Package ${trackingNumber || pkgId}`,\n },\n native: {},\n });\n\n const statusCode = this.parseStatus(delivery);\n const lang = this.adapter.config.language || \"de\";\n const labels = lang === \"de\" ? STATUS_LABELS_DE : STATUS_LABELS_EN;\n\n await Promise.all([\n this.createAndSet(\n `${devicePath}.carrier`,\n \"Carrier\",\n \"string\",\n \"text\",\n carrierName,\n ),\n this.createAndSet(\n `${devicePath}.status`,\n \"Status\",\n \"string\",\n \"text\",\n labels[statusCode] || `Unknown (${statusCode})`,\n ),\n this.createAndSet(\n `${devicePath}.statusCode`,\n \"Status Code\",\n \"number\",\n \"value\",\n statusCode,\n ),\n this.createAndSet(\n `${devicePath}.description`,\n \"Description\",\n \"string\",\n \"text\",\n description,\n ),\n this.createAndSet(\n `${devicePath}.trackingNumber`,\n \"Tracking Number\",\n \"string\",\n \"text\",\n trackingNumber,\n ),\n this.createAndSet(\n `${devicePath}.extraInfo`,\n \"Extra Information\",\n \"string\",\n \"text\",\n extraInfo,\n ),\n this.createAndSet(\n `${devicePath}.deliveryWindow`,\n \"Delivery Window\",\n \"string\",\n \"text\",\n this.calculateDeliveryWindow(delivery, statusCode),\n ),\n this.createAndSet(\n `${devicePath}.deliveryEstimate`,\n \"Delivery Estimate\",\n \"string\",\n \"text\",\n this.calculateDeliveryEstimate(delivery, statusCode),\n ),\n this.createAndSet(\n `${devicePath}.lastEvent`,\n \"Last Event\",\n \"string\",\n \"text\",\n this.formatLastEvent(delivery),\n ),\n this.createAndSet(\n `${devicePath}.lastLocation`,\n \"Last Location\",\n \"string\",\n \"text\",\n this.extractLastLocation(delivery),\n ),\n this.createAndSet(\n `${devicePath}.lastUpdated`,\n \"Last Updated\",\n \"string\",\n \"date\",\n new Date().toISOString(),\n ),\n ]);\n }\n\n /**\n * Update summary states. Expects already-filtered active deliveries.\n *\n * @param activeDeliveries Only active (non-delivered) deliveries\n */\n async updateSummary(activeDeliveries: ParcelDelivery[]): Promise<void> {\n await this.adapter.extendObjectAsync(\"summary\", {\n type: \"channel\",\n common: { name: \"Summary\" },\n native: {},\n });\n\n const todayDeliveries = activeDeliveries.filter((d) => {\n const statusCode = this.parseStatus(d);\n const estimate = this.calculateDeliveryEstimate(d, statusCode);\n return estimate === \"heute\" || estimate === \"today\";\n });\n\n await Promise.all([\n this.createAndSet(\n \"summary.activeCount\",\n \"Active Deliveries\",\n \"number\",\n \"value\",\n activeDeliveries.length,\n ),\n this.createAndSet(\n \"summary.todayCount\",\n \"Deliveries Today\",\n \"number\",\n \"value\",\n todayDeliveries.length,\n ),\n this.createAndSet(\n \"summary.deliveryWindow\",\n \"Combined Delivery Window\",\n \"string\",\n \"text\",\n this.calculateCombinedWindow(todayDeliveries),\n ),\n ]);\n }\n\n /**\n * Remove deliveries that are no longer active.\n *\n * @param activeIds List of currently active package IDs\n */\n async cleanupDeliveries(activeIds: string[]): Promise<void> {\n const activeSet = new Set(activeIds.map((id) => `deliveries.${id}`));\n\n const objects = await this.adapter.getObjectViewAsync(\"system\", \"device\", {\n startkey: `${this.adapter.namespace}.deliveries.`,\n endkey: `${this.adapter.namespace}.deliveries.\\u9999`,\n });\n\n for (const row of objects.rows) {\n const relativeId = row.id.replace(`${this.adapter.namespace}.`, \"\");\n if (relativeId.startsWith(\"deliveries.\") && !activeSet.has(relativeId)) {\n await this.adapter.delObjectAsync(relativeId, { recursive: true });\n this.adapter.log.debug(`Removed stale delivery: ${relativeId}`);\n }\n }\n }\n\n /**\n * Calculate delivery time window \u2014 only from Unix timestamps.\n *\n * @param delivery The delivery data\n * @param statusCode Pre-parsed status code\n */\n private calculateDeliveryWindow(\n delivery: ParcelDelivery,\n statusCode: number,\n ): string {\n if (!TRACKABLE_STATUSES.has(statusCode)) {\n return \"\";\n }\n\n const formatTime = (timestamp: unknown): string | null => {\n const ts = coerceNumber(timestamp);\n if (ts === null || ts <= 0) {\n return null;\n }\n const d = new Date(ts * 1000);\n if (Number.isNaN(d.getTime())) {\n return null;\n }\n return `${d.getHours().toString().padStart(2, \"0\")}:${d.getMinutes().toString().padStart(2, \"0\")}`;\n };\n\n const start = formatTime(delivery.timestamp_expected);\n const end = formatTime(delivery.timestamp_expected_end);\n\n if (!start) {\n return \"\";\n }\n return end ? `${start} - ${end}` : start;\n }\n\n /**\n * Calculate human-readable delivery estimate.\n *\n * @param delivery The delivery data\n * @param statusCode Pre-parsed status code\n */\n private calculateDeliveryEstimate(\n delivery: ParcelDelivery,\n statusCode: number,\n ): string {\n if (!TRACKABLE_STATUSES.has(statusCode)) {\n return \"\";\n }\n\n let expectedDate: Date | null = null;\n const ts = coerceNumber(delivery.timestamp_expected);\n if (ts !== null && ts > 0) {\n expectedDate = new Date(ts * 1000);\n } else if (\n typeof delivery.date_expected === \"string\" &&\n delivery.date_expected.length > 0\n ) {\n expectedDate = new Date(delivery.date_expected);\n }\n\n if (!expectedDate || isNaN(expectedDate.getTime())) {\n return \"\";\n }\n\n const now = new Date();\n const todayStart = new Date(\n now.getFullYear(),\n now.getMonth(),\n now.getDate(),\n );\n const expectedStart = new Date(\n expectedDate.getFullYear(),\n expectedDate.getMonth(),\n expectedDate.getDate(),\n );\n const diffDays = Math.round(\n (expectedStart.getTime() - todayStart.getTime()) / (1000 * 60 * 60 * 24),\n );\n\n const lang = this.adapter.config.language || \"de\";\n const l = ESTIMATE_LABELS[lang] || ESTIMATE_LABELS.en;\n\n if (diffDays < 0) {\n return l.overdue;\n }\n if (diffDays === 0) {\n return l.today;\n }\n if (diffDays === 1) {\n return l.tomorrow;\n }\n return l.days.replace(\"%d\", String(diffDays));\n }\n\n /**\n * Format the latest tracking event.\n *\n * @param delivery The delivery data\n */\n private formatLastEvent(delivery: ParcelDelivery): string {\n if (!Array.isArray(delivery.events) || delivery.events.length === 0) {\n return \"\";\n }\n const latest = delivery.events[0];\n if (!latest || typeof latest !== \"object\") {\n return \"\";\n }\n const parts: string[] = [];\n if (typeof latest.event === \"string\" && latest.event.length > 0) {\n parts.push(latest.event);\n }\n if (typeof latest.date === \"string\" && latest.date.length > 0) {\n parts.push(latest.date);\n }\n return parts.join(\" - \");\n }\n\n /**\n * Extract location from latest event.\n *\n * @param delivery The delivery data\n */\n private extractLastLocation(delivery: ParcelDelivery): string {\n if (!Array.isArray(delivery.events) || delivery.events.length === 0) {\n return \"\";\n }\n const latest = delivery.events[0];\n if (!latest || typeof latest !== \"object\") {\n return \"\";\n }\n return typeof latest.location === \"string\" ? latest.location : \"\";\n }\n\n /**\n * Calculate combined delivery window for today's packages.\n *\n * @param todayDeliveries Deliveries expected today\n */\n private calculateCombinedWindow(todayDeliveries: ParcelDelivery[]): string {\n const windows = todayDeliveries\n .map((d) => this.calculateDeliveryWindow(d, this.parseStatus(d)))\n .filter((w) => w.length > 0);\n\n if (windows.length === 0) {\n return \"\";\n }\n if (windows.length === 1) {\n return windows[0];\n }\n\n const times: {\n /** Window start */ start: string;\n /** Window end */ end: string;\n }[] = [];\n for (const w of windows) {\n const match = w.match(/(\\d{2}:\\d{2})(?:\\s*-\\s*(\\d{2}:\\d{2}))?/);\n if (match) {\n times.push({ start: match[1], end: match[2] || match[1] });\n }\n }\n\n if (times.length === 0) {\n return \"\";\n }\n\n times.sort((a, b) => a.start.localeCompare(b.start));\n return `${times[0].start} - ${times[times.length - 1].end}`;\n }\n\n /**\n * Create/extend a read-only state and set its value.\n *\n * @param id State ID relative to adapter namespace\n * @param name Display name\n * @param type Value type\n * @param role ioBroker role\n * @param val Value to set\n */\n private async createAndSet(\n id: string,\n name: string,\n type: ioBroker.CommonType,\n role: string,\n val: ioBroker.StateValue,\n ): Promise<void> {\n await this.adapter.setObjectNotExistsAsync(id, {\n type: \"state\",\n common: { name, type, role, read: true, write: false },\n native: {},\n });\n await this.adapter.setStateAsync(id, { val, ack: true });\n }\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA,
|
|
4
|
+
"sourcesContent": ["import type { AdapterInstance } from \"@iobroker/adapter-core\";\nimport type { ParcelDelivery } from \"./types\";\nimport { STATUS_LABELS, SUPPORTED_LANGUAGES, FALLBACK_LANGUAGE } from \"./types\";\n\n/** Status codes that have expected delivery date/time */\nconst TRACKABLE_STATUSES = new Set([2, 4, 8]);\n\n/**\n * Coerce a value to a finite number. Accepts numbers and numeric strings.\n * Returns null for anything else \u2014 used to guard against API drift.\n *\n * @param v Value to coerce\n */\nfunction coerceNumber(v: unknown): number | null {\n if (typeof v === \"number\" && Number.isFinite(v)) {\n return v;\n }\n if (typeof v === \"string\" && v.length > 0) {\n const n = parseFloat(v);\n return Number.isFinite(n) ? n : null;\n }\n return null;\n}\n\n/** Delivery-estimate labels keyed by language code. Keys must match STATUS_LABELS. */\nconst ESTIMATE_LABELS: Record<string, Record<string, string>> = {\n de: {\n overdue: \"\u00FCberf\u00E4llig\",\n today: \"heute\",\n tomorrow: \"morgen\",\n days: \"in %d Tagen\",\n },\n en: {\n overdue: \"overdue\",\n today: \"today\",\n tomorrow: \"tomorrow\",\n days: \"in %d days\",\n },\n ru: {\n overdue: \"\u043F\u0440\u043E\u0441\u0440\u043E\u0447\u0435\u043D\u043E\",\n today: \"\u0441\u0435\u0433\u043E\u0434\u043D\u044F\",\n tomorrow: \"\u0437\u0430\u0432\u0442\u0440\u0430\",\n days: \"\u0447\u0435\u0440\u0435\u0437 %d \u0434\u043D.\",\n },\n pt: {\n overdue: \"atrasado\",\n today: \"hoje\",\n tomorrow: \"amanh\u00E3\",\n days: \"em %d dias\",\n },\n nl: {\n overdue: \"te laat\",\n today: \"vandaag\",\n tomorrow: \"morgen\",\n days: \"over %d dagen\",\n },\n fr: {\n overdue: \"en retard\",\n today: \"aujourd'hui\",\n tomorrow: \"demain\",\n days: \"dans %d jours\",\n },\n it: {\n overdue: \"in ritardo\",\n today: \"oggi\",\n tomorrow: \"domani\",\n days: \"tra %d giorni\",\n },\n es: {\n overdue: \"atrasado\",\n today: \"hoy\",\n tomorrow: \"ma\u00F1ana\",\n days: \"en %d d\u00EDas\",\n },\n pl: {\n overdue: \"zaleg\u0142e\",\n today: \"dzisiaj\",\n tomorrow: \"jutro\",\n days: \"za %d dni\",\n },\n uk: {\n overdue: \"\u043F\u0440\u043E\u0441\u0442\u0440\u043E\u0447\u0435\u043D\u043E\",\n today: \"\u0441\u044C\u043E\u0433\u043E\u0434\u043D\u0456\",\n tomorrow: \"\u0437\u0430\u0432\u0442\u0440\u0430\",\n days: \"\u0447\u0435\u0440\u0435\u0437 %d \u0434\u043D.\",\n },\n \"zh-cn\": {\n overdue: \"\u5DF2\u903E\u671F\",\n today: \"\u4ECA\u5929\",\n tomorrow: \"\u660E\u5929\",\n days: \"%d \u5929\u540E\",\n },\n};\n\n/**\n * Resolve a language code to one that has labels. Falls back to English\n * when the system language is not one of the supported ioBroker languages.\n *\n * @param language Raw language code (e.g. from system.config.language)\n */\nexport function resolveLanguage(language: unknown): string {\n if (typeof language === \"string\" && SUPPORTED_LANGUAGES.includes(language)) {\n return language;\n }\n return FALLBACK_LANGUAGE;\n}\n\n/** Manages ioBroker states for parcel deliveries */\nexport class StateManager {\n private adapter: AdapterInstance;\n private language: string;\n\n /**\n * @param adapter The ioBroker adapter instance\n * @param language Language code from system.config.language (falls back to English)\n */\n constructor(adapter: AdapterInstance, language: string) {\n this.adapter = adapter;\n this.language = resolveLanguage(language);\n }\n\n /**\n * Sanitize a string for use as ioBroker object ID (see adapter.FORBIDDEN_CHARS).\n * API-drift guard: returns \"unknown\" for non-string input.\n *\n * @param name Raw value to sanitize (any type)\n */\n sanitize(name: unknown): string {\n if (typeof name !== \"string\") {\n return \"unknown\";\n }\n return (\n name\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"_\")\n .replace(/^_+|_+$/g, \"\")\n .slice(0, 50) || \"unknown\"\n );\n }\n\n /**\n * Parse the status code from a delivery. API documents `status_code` as\n * a numeric string, but we accept numbers too and fall back to 0 for drift.\n *\n * @param delivery The delivery to parse\n */\n parseStatus(delivery: ParcelDelivery): number {\n const raw = delivery.status_code as unknown;\n if (typeof raw === \"number\" && Number.isFinite(raw)) {\n return Math.trunc(raw);\n }\n if (typeof raw === \"string\") {\n const n = parseInt(raw, 10);\n return Number.isFinite(n) ? n : 0;\n }\n return 0;\n }\n\n /**\n * Build a unique package ID from a delivery.\n *\n * @param delivery The delivery to build an ID for\n */\n packageId(delivery: ParcelDelivery): string {\n let id = this.sanitize(delivery.tracking_number);\n // API-drift guard: only string values extend the id\n if (\n typeof delivery.extra_information === \"string\" &&\n delivery.extra_information.length > 0\n ) {\n id += `_${this.sanitize(delivery.extra_information)}`;\n }\n return id;\n }\n\n /**\n * Update or create all states for a delivery.\n *\n * @param delivery The delivery data from API\n * @param carrierName Resolved carrier display name\n */\n async updateDelivery(\n delivery: ParcelDelivery,\n carrierName: string,\n ): Promise<void> {\n const pkgId = this.packageId(delivery);\n const devicePath = `deliveries.${pkgId}`;\n\n const description =\n typeof delivery.description === \"string\" ? delivery.description : \"\";\n const trackingNumber =\n typeof delivery.tracking_number === \"string\"\n ? delivery.tracking_number\n : \"\";\n const extraInfo =\n typeof delivery.extra_information === \"string\"\n ? delivery.extra_information\n : \"\";\n\n await this.adapter.extendObjectAsync(devicePath, {\n type: \"device\",\n common: {\n name: description || `Package ${trackingNumber || pkgId}`,\n },\n native: {},\n });\n\n const statusCode = this.parseStatus(delivery);\n const labels = STATUS_LABELS[this.language];\n const statusText = labels[statusCode] || `Unknown (${statusCode})`;\n\n await Promise.all([\n this.createAndSet(\n `${devicePath}.carrier`,\n \"Carrier\",\n \"string\",\n \"text\",\n carrierName,\n ),\n this.createAndSet(\n `${devicePath}.status`,\n \"Status\",\n \"string\",\n \"text\",\n statusText,\n ),\n this.createAndSet(\n `${devicePath}.statusCode`,\n \"Status Code\",\n \"number\",\n \"value\",\n statusCode,\n ),\n this.createAndSet(\n `${devicePath}.description`,\n \"Description\",\n \"string\",\n \"text\",\n description,\n ),\n this.createAndSet(\n `${devicePath}.trackingNumber`,\n \"Tracking Number\",\n \"string\",\n \"text\",\n trackingNumber,\n ),\n this.createAndSet(\n `${devicePath}.extraInfo`,\n \"Extra Information\",\n \"string\",\n \"text\",\n extraInfo,\n ),\n this.createAndSet(\n `${devicePath}.deliveryWindow`,\n \"Delivery Window\",\n \"string\",\n \"text\",\n this.calculateDeliveryWindow(delivery, statusCode),\n ),\n this.createAndSet(\n `${devicePath}.deliveryEstimate`,\n \"Delivery Estimate\",\n \"string\",\n \"text\",\n this.calculateDeliveryEstimate(delivery, statusCode),\n ),\n this.createAndSet(\n `${devicePath}.lastEvent`,\n \"Last Event\",\n \"string\",\n \"text\",\n this.formatLastEvent(delivery),\n ),\n this.createAndSet(\n `${devicePath}.lastLocation`,\n \"Last Location\",\n \"string\",\n \"text\",\n this.extractLastLocation(delivery),\n ),\n this.createAndSet(\n `${devicePath}.lastUpdated`,\n \"Last Updated\",\n \"string\",\n \"date\",\n new Date().toISOString(),\n ),\n ]);\n }\n\n /**\n * Update summary states. Expects already-filtered active deliveries.\n * The `summary` channel itself is declared via io-package.json instanceObjects.\n *\n * @param activeDeliveries Only active (non-delivered) deliveries\n */\n async updateSummary(activeDeliveries: ParcelDelivery[]): Promise<void> {\n const todayDeliveries = activeDeliveries.filter((d) =>\n this.isToday(d, this.parseStatus(d)),\n );\n\n await Promise.all([\n this.createAndSet(\n \"summary.activeCount\",\n \"Active Deliveries\",\n \"number\",\n \"value\",\n activeDeliveries.length,\n ),\n this.createAndSet(\n \"summary.todayCount\",\n \"Deliveries Today\",\n \"number\",\n \"value\",\n todayDeliveries.length,\n ),\n this.createAndSet(\n \"summary.deliveryWindow\",\n \"Combined Delivery Window\",\n \"string\",\n \"text\",\n this.calculateCombinedWindow(todayDeliveries),\n ),\n ]);\n }\n\n /**\n * Remove deliveries that are no longer active.\n *\n * @param activeIds List of currently active package IDs\n */\n async cleanupDeliveries(activeIds: string[]): Promise<void> {\n const activeSet = new Set(activeIds.map((id) => `deliveries.${id}`));\n\n const objects = await this.adapter.getObjectViewAsync(\"system\", \"device\", {\n startkey: `${this.adapter.namespace}.deliveries.`,\n endkey: `${this.adapter.namespace}.deliveries.\u9999`,\n });\n\n for (const row of objects.rows) {\n const relativeId = row.id.replace(`${this.adapter.namespace}.`, \"\");\n if (relativeId.startsWith(\"deliveries.\") && !activeSet.has(relativeId)) {\n await this.adapter.delObjectAsync(relativeId, { recursive: true });\n this.adapter.log.debug(`Removed stale delivery: ${relativeId}`);\n }\n }\n }\n\n /**\n * Calculate delivery time window \u2014 only from Unix timestamps.\n *\n * @param delivery The delivery data\n * @param statusCode Pre-parsed status code\n */\n private calculateDeliveryWindow(\n delivery: ParcelDelivery,\n statusCode: number,\n ): string {\n if (!TRACKABLE_STATUSES.has(statusCode)) {\n return \"\";\n }\n\n const formatTime = (timestamp: unknown): string | null => {\n const ts = coerceNumber(timestamp);\n if (ts === null || ts <= 0) {\n return null;\n }\n const d = new Date(ts * 1000);\n if (Number.isNaN(d.getTime())) {\n return null;\n }\n return `${d.getHours().toString().padStart(2, \"0\")}:${d.getMinutes().toString().padStart(2, \"0\")}`;\n };\n\n const start = formatTime(delivery.timestamp_expected);\n const end = formatTime(delivery.timestamp_expected_end);\n\n if (!start) {\n return \"\";\n }\n return end ? `${start} - ${end}` : start;\n }\n\n /**\n * Days from today to the expected delivery date. Returns null when the\n * delivery has no usable expected date or is in a non-trackable status.\n *\n * @param delivery The delivery data\n * @param statusCode Pre-parsed status code\n */\n private computeDiffDays(\n delivery: ParcelDelivery,\n statusCode: number,\n ): number | null {\n if (!TRACKABLE_STATUSES.has(statusCode)) {\n return null;\n }\n\n let expectedDate: Date | null = null;\n const ts = coerceNumber(delivery.timestamp_expected);\n if (ts !== null && ts > 0) {\n expectedDate = new Date(ts * 1000);\n } else if (\n typeof delivery.date_expected === \"string\" &&\n delivery.date_expected.length > 0\n ) {\n expectedDate = new Date(delivery.date_expected);\n }\n\n if (!expectedDate || isNaN(expectedDate.getTime())) {\n return null;\n }\n\n const now = new Date();\n const todayStart = new Date(\n now.getFullYear(),\n now.getMonth(),\n now.getDate(),\n );\n const expectedStart = new Date(\n expectedDate.getFullYear(),\n expectedDate.getMonth(),\n expectedDate.getDate(),\n );\n return Math.round(\n (expectedStart.getTime() - todayStart.getTime()) / (1000 * 60 * 60 * 24),\n );\n }\n\n /**\n * Calculate human-readable delivery estimate.\n *\n * @param delivery The delivery data\n * @param statusCode Pre-parsed status code\n */\n private calculateDeliveryEstimate(\n delivery: ParcelDelivery,\n statusCode: number,\n ): string {\n const diffDays = this.computeDiffDays(delivery, statusCode);\n if (diffDays === null) {\n return \"\";\n }\n const l = ESTIMATE_LABELS[this.language];\n if (diffDays < 0) {\n return l.overdue;\n }\n if (diffDays === 0) {\n return l.today;\n }\n if (diffDays === 1) {\n return l.tomorrow;\n }\n return l.days.replace(\"%d\", String(diffDays));\n }\n\n /**\n * Whether the delivery is expected today. Language-agnostic, used by the\n * summary filter so `todayCount` works across all languages.\n *\n * @param delivery The delivery data\n * @param statusCode Pre-parsed status code\n */\n private isToday(delivery: ParcelDelivery, statusCode: number): boolean {\n return this.computeDiffDays(delivery, statusCode) === 0;\n }\n\n /**\n * Format the latest tracking event.\n *\n * @param delivery The delivery data\n */\n private formatLastEvent(delivery: ParcelDelivery): string {\n if (!Array.isArray(delivery.events) || delivery.events.length === 0) {\n return \"\";\n }\n const latest = delivery.events[0];\n if (!latest || typeof latest !== \"object\") {\n return \"\";\n }\n const parts: string[] = [];\n if (typeof latest.event === \"string\" && latest.event.length > 0) {\n parts.push(latest.event);\n }\n if (typeof latest.date === \"string\" && latest.date.length > 0) {\n parts.push(latest.date);\n }\n return parts.join(\" - \");\n }\n\n /**\n * Extract location from latest event.\n *\n * @param delivery The delivery data\n */\n private extractLastLocation(delivery: ParcelDelivery): string {\n if (!Array.isArray(delivery.events) || delivery.events.length === 0) {\n return \"\";\n }\n const latest = delivery.events[0];\n if (!latest || typeof latest !== \"object\") {\n return \"\";\n }\n return typeof latest.location === \"string\" ? latest.location : \"\";\n }\n\n /**\n * Calculate combined delivery window for today's packages.\n *\n * @param todayDeliveries Deliveries expected today\n */\n private calculateCombinedWindow(todayDeliveries: ParcelDelivery[]): string {\n const windows = todayDeliveries\n .map((d) => this.calculateDeliveryWindow(d, this.parseStatus(d)))\n .filter((w) => w.length > 0);\n\n if (windows.length === 0) {\n return \"\";\n }\n if (windows.length === 1) {\n return windows[0];\n }\n\n const times: {\n /** Window start */ start: string;\n /** Window end */ end: string;\n }[] = [];\n for (const w of windows) {\n const match = w.match(/(\\d{2}:\\d{2})(?:\\s*-\\s*(\\d{2}:\\d{2}))?/);\n if (match) {\n times.push({ start: match[1], end: match[2] || match[1] });\n }\n }\n\n if (times.length === 0) {\n return \"\";\n }\n\n times.sort((a, b) => a.start.localeCompare(b.start));\n return `${times[0].start} - ${times[times.length - 1].end}`;\n }\n\n /**\n * Create/extend a read-only state and set its value.\n *\n * @param id State ID relative to adapter namespace\n * @param name Display name\n * @param type Value type\n * @param role ioBroker role\n * @param val Value to set\n */\n private async createAndSet(\n id: string,\n name: string,\n type: ioBroker.CommonType,\n role: string,\n val: ioBroker.StateValue,\n ): Promise<void> {\n await this.adapter.setObjectNotExistsAsync(id, {\n type: \"state\",\n common: { name, type, role, read: true, write: false },\n native: {},\n });\n await this.adapter.setStateAsync(id, { val, ack: true });\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|