iobroker.parcelapp 0.8.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -45,7 +45,7 @@ For details and how to disable it, see the [Sentry plugin documentation](https:/
45
45
  | Option | Description | Default |
46
46
  | ------------------------- | ---------------------------------------------------------------------------------------------------------- | ------- |
47
47
  | **API Key** | Your parcel.app API key (get it at [web.parcelapp.net](https://web.parcelapp.net)) | — |
48
- | **Poll Interval** | How often to fetch updates (minutes) | 10 |
48
+ | **Poll Interval** | How often to fetch updates (minutes). parcel.app serves the list from a ~45–90 min server cache, so shorter intervals mostly reduce the delay until a refresh is noticed. | 10 |
49
49
  | **Auto-remove delivered** | Remove delivered packages from states automatically. When disabled, they stay until deleted in parcel.app. | Yes |
50
50
 
51
51
  Status labels (`Delivered`, `In Transit`, …) and delivery estimates (`today`, `tomorrow`, `in X days`) are rendered in the ioBroker system language.
@@ -65,7 +65,7 @@ parcelapp.0.
65
65
  └── {packageId}. — One device per package
66
66
  ├── carrier — Carrier name (e.g. DHL Express)
67
67
  ├── status — Status text (e.g. In Transit)
68
- ├── statusCode — Status code (0-8)
68
+ ├── statusCode — Status code (0-8, -1 = unknown)
69
69
  ├── description — Package description
70
70
  ├── trackingNumber — Tracking number
71
71
  ├── extraInfo — Extra information (postal code, email)
@@ -73,9 +73,19 @@ parcelapp.0.
73
73
  ├── deliveryEstimate — Human-readable estimate (today, tomorrow)
74
74
  ├── lastEvent — Latest tracking event
75
75
  ├── lastLocation — Last known location
76
- └── lastUpdated — Last update timestamp
76
+ └── lastUpdated — Timestamp of the last tracking-data change
77
77
  ```
78
78
 
79
+ **Status codes** (`statusCode` — the primary datapoint for automations):
80
+
81
+ | Code | Meaning | Code | Meaning |
82
+ | ---- | --------------- | ---- | ----------------------- |
83
+ | 0 | Delivered | 5 | Not Found |
84
+ | 1 | Frozen | 6 | Delivery Attempt Failed |
85
+ | 2 | In Transit | 7 | Exception |
86
+ | 3 | Awaiting Pickup | 8 | Info Received |
87
+ | 4 | Out for Delivery | -1 | Unknown (unexpected API value — package stays visible) |
88
+
79
89
  ---
80
90
 
81
91
  ## Add Deliveries via Script
@@ -93,11 +103,12 @@ sendTo("parcelapp.0", "addDelivery", {
93
103
  });
94
104
  ```
95
105
 
96
- `tracking_number`, `carrier_code` and `description` are required; `language` and `send_push_confirmation` are optional. The delivery is added to your parcel.app account and a poll is triggered right away — but freshly added deliveries usually have no tracking data yet (see the note below).
106
+ `tracking_number`, `carrier_code` and `description` are required; `language` and `send_push_confirmation` are optional. The delivery is added to your parcel.app account and a poll follows right away (at most one poll per minute) — but freshly added deliveries usually have no tracking data yet (see the note below).
97
107
 
98
108
  **Notes:**
99
109
 
100
110
  - **POST rate limit: 20 deliveries per day** — failed attempts (e.g. wrong `carrier_code`) also count against this limit.
111
+ - **Each field may be at most 512 characters**, and the adapter accepts at most **20 addDelivery calls per minute** — beyond either limit the call returns `success: false` with an explanatory `error_message` instead of reaching parcel.app.
101
112
  - Fresh deliveries usually have no tracking events for **45–90 minutes** after they are added. That's a parcel.app-side delay, not an adapter issue.
102
113
  - **Deleting packages is only possible in the parcel.app app/web UI** — the API has no delete endpoint. With `autoRemoveDelivered` enabled, the adapter still drops delivered packages from ioBroker states automatically.
103
114
 
@@ -113,7 +124,7 @@ sendTo("parcelapp.0", "addDelivery", {
113
124
 
114
125
  ### No deliveries shown
115
126
 
116
- - The API returns cached data — new deliveries may take a few minutes to appear
127
+ - The API returns cached data — new deliveries and fresh tracking events can take **45–90 minutes** to appear (parcel.app-side cache)
117
128
  - Check if you have active deliveries in the parcel.app
118
129
 
119
130
  ### Rate limit
@@ -129,12 +140,28 @@ sendTo("parcelapp.0", "addDelivery", {
129
140
  Placeholder for the next version (at the beginning of the line):
130
141
  ### **WORK IN PROGRESS**
131
142
  -->
143
+ ### 0.10.0 (2026-07-08)
144
+
145
+ - Fixed: the admin "Test Connection" button now reports real failures — before, it always showed "Ok" even with a wrong API key.
146
+ - Fixed: a package's last-updated timestamp no longer jumps to the restart time after an adapter restart — it only changes when tracking data actually changed.
147
+ - Fixed: a stalled API response can no longer freeze polling until a manual restart — every request now has a hard 60-second deadline.
148
+ - Fixed: a failed adapter start now triggers an automatic restart instead of leaving the adapter idle until restarted by hand.
149
+ - Changed: recurring errors such as a wrong API key are logged once instead of every poll cycle, and stopping the adapter no longer leaves a red error line in the log.
150
+ - Changed: short ioBroker database hiccups no longer flip the connection indicator — it now reflects only the parcel.app connection.
151
+ - Changed: the fallback package name ("Package …") is localized like all other texts, and the adapter is listed under a fitting admin category (misc-data).
152
+ - Changed: the automatic poll after adding a delivery now respects the one-minute pacing, so bulk-adds can no longer exhaust the hourly API budget.
153
+
154
+ ### 0.9.0 (2026-06-23) — stable
155
+
156
+ - Fixed: tracked packages could disappear from the object tree after a temporary update error or an unexpected API response — a package is now kept until parcel.app actually stops returning it.
157
+ - Changed: multi-day delivery windows now show the date on each side (e.g. `12-06 14:30 - 12-08 18:30`) instead of looking same-day; out-of-range or reversed dates no longer produce a misleading window.
158
+
132
159
  ### 0.8.0 (2026-06-19)
133
160
 
134
161
  - The delivery window is now also shown for carriers that report it only as a date/time range, not just when the API provides a Unix timestamp.
135
162
  - When adding a delivery via script, you can now set an optional tracking language and request a push confirmation.
136
163
 
137
- ### 0.7.2 (2026-06-12)
164
+ ### 0.7.2 (2026-06-12) — stable
138
165
 
139
166
  - Much quieter state updates: a package's last-updated timestamp now only changes when its tracking data actually changed, and device entries are no longer rewritten on every poll
140
167
  - Adding a delivery with a malformed request now returns a clear error message instead of failing cryptically
@@ -143,16 +170,6 @@ sendTo("parcelapp.0", "addDelivery", {
143
170
 
144
171
  - Fixed a timezone edge case in delivery estimates: when the API reports only a calendar date, the estimate could be off by a day in time zones west of UTC — now stable everywhere.
145
172
 
146
- ### 0.7.0 (2026-06-07)
147
-
148
- - Added optional Sentry error reporting: crashes are sent to the developer so issues get fixed faster. Active only with ioBroker diagnostics enabled; anonymous.
149
-
150
- ### 0.6.0 (2026-05-31)
151
-
152
- - The summary delivery window now covers the full time range when several packages are expected the same day — previously an overlapping window could be cut short.
153
- - Packages reported with an unrecognized status are no longer mistaken for delivered and removed; they stay visible as "Unknown".
154
- - A delivery added via the admin button now appears immediately instead of only after the next polling cycle.
155
-
156
173
  [Older changelogs can be found there](CHANGELOG_OLD.md)
157
174
 
158
175
  ## Support
@@ -3,8 +3,6 @@
3
3
  "apiKeyInfo": "Gib deinen parcel.app API-Schlüssel ein. Ein Premium-Abo wird benötigt. Den Schlüssel findest du unter web.parcelapp.net → API-Bereich.",
4
4
  "label_apiKey": "API-Schlüssel",
5
5
  "btn_checkConnection": "Verbindung testen",
6
- "msg_connectionOk": "Verbindung erfolgreich!",
7
- "msg_connectionFailed": "Verbindung fehlgeschlagen!",
8
6
  "header_polling": "Abfrage",
9
7
  "label_pollInterval": "Abfrageintervall (Minuten)",
10
8
  "tooltip_pollInterval": "Wie oft Lieferupdates abgefragt werden (5-60 Minuten, Standard: 10). Rate-Limit: 20 Anfragen/Stunde.",
@@ -36,5 +34,15 @@
36
34
  "estimateOverdue": "überfällig",
37
35
  "estimateToday": "heute",
38
36
  "estimateTomorrow": "morgen",
39
- "estimateDays": "in %d Tagen"
37
+ "estimateDays": "in %s Tagen",
38
+ "status_0": "Zugestellt",
39
+ "status_1": "Eingefroren",
40
+ "status_2": "Unterwegs",
41
+ "status_3": "Abholung erwartet",
42
+ "status_4": "In Zustellung",
43
+ "status_5": "Nicht gefunden",
44
+ "status_6": "Zustellversuch gescheitert",
45
+ "status_7": "Ausnahme",
46
+ "status_8": "Registriert",
47
+ "packageName": "Paket %s"
40
48
  }
@@ -3,8 +3,6 @@
3
3
  "apiKeyInfo": "Enter your parcel.app API key. A Premium subscription is required. Get your key at web.parcelapp.net → API panel.",
4
4
  "label_apiKey": "API Key",
5
5
  "btn_checkConnection": "Test Connection",
6
- "msg_connectionOk": "Connection successful!",
7
- "msg_connectionFailed": "Connection failed!",
8
6
  "header_polling": "Polling",
9
7
  "label_pollInterval": "Poll Interval (minutes)",
10
8
  "tooltip_pollInterval": "How often to fetch delivery updates (5-60 minutes, default: 10). Rate limit: 20 requests/hour.",
@@ -36,5 +34,15 @@
36
34
  "estimateOverdue": "overdue",
37
35
  "estimateToday": "today",
38
36
  "estimateTomorrow": "tomorrow",
39
- "estimateDays": "in %d days"
37
+ "estimateDays": "in %s days",
38
+ "status_0": "Delivered",
39
+ "status_1": "Frozen",
40
+ "status_2": "In Transit",
41
+ "status_3": "Awaiting Pickup",
42
+ "status_4": "Out for Delivery",
43
+ "status_5": "Not Found",
44
+ "status_6": "Delivery Attempt Failed",
45
+ "status_7": "Exception",
46
+ "status_8": "Info Received",
47
+ "packageName": "Package %s"
40
48
  }
@@ -3,8 +3,6 @@
3
3
  "apiKeyInfo": "Introduce tu clave API de parcel.app. Se requiere una suscripción Premium. Obtén tu clave en web.parcelapp.net → panel API.",
4
4
  "label_apiKey": "Clave API",
5
5
  "btn_checkConnection": "Probar conexión",
6
- "msg_connectionOk": "¡Conexión exitosa!",
7
- "msg_connectionFailed": "¡Error de conexión!",
8
6
  "header_polling": "Consulta",
9
7
  "label_pollInterval": "Intervalo de consulta (minutos)",
10
8
  "tooltip_pollInterval": "Con qué frecuencia obtener actualizaciones de entrega (5-60 minutos, predeterminado: 10). Límite: 20 solicitudes/hora.",
@@ -36,5 +34,15 @@
36
34
  "estimateOverdue": "atrasado",
37
35
  "estimateToday": "hoy",
38
36
  "estimateTomorrow": "mañana",
39
- "estimateDays": "en %d días"
37
+ "estimateDays": "en %s días",
38
+ "status_0": "Entregado",
39
+ "status_1": "Congelado",
40
+ "status_2": "En tránsito",
41
+ "status_3": "Esperando recogida",
42
+ "status_4": "En reparto",
43
+ "status_5": "No encontrado",
44
+ "status_6": "Intento de entrega fallido",
45
+ "status_7": "Excepción",
46
+ "status_8": "Registrado",
47
+ "packageName": "Paquete %s"
40
48
  }
@@ -3,8 +3,6 @@
3
3
  "apiKeyInfo": "Entrez votre clé API parcel.app. Un abonnement Premium est requis. Obtenez votre clé sur web.parcelapp.net → panneau API.",
4
4
  "label_apiKey": "Clé API",
5
5
  "btn_checkConnection": "Tester la connexion",
6
- "msg_connectionOk": "Connexion réussie !",
7
- "msg_connectionFailed": "Échec de la connexion !",
8
6
  "header_polling": "Interrogation",
9
7
  "label_pollInterval": "Intervalle d interrogation (minutes)",
10
8
  "tooltip_pollInterval": "Fréquence de récupération des mises à jour de livraison (5-60 minutes, défaut : 10). Limite : 20 requêtes/heure.",
@@ -36,5 +34,15 @@
36
34
  "estimateOverdue": "en retard",
37
35
  "estimateToday": "aujourd'hui",
38
36
  "estimateTomorrow": "demain",
39
- "estimateDays": "dans %d jours"
37
+ "estimateDays": "dans %s jours",
38
+ "status_0": "Livré",
39
+ "status_1": "Gelé",
40
+ "status_2": "En transit",
41
+ "status_3": "En attente de retrait",
42
+ "status_4": "En cours de livraison",
43
+ "status_5": "Introuvable",
44
+ "status_6": "Échec de la livraison",
45
+ "status_7": "Exception",
46
+ "status_8": "Enregistré",
47
+ "packageName": "Colis %s"
40
48
  }
@@ -3,8 +3,6 @@
3
3
  "apiKeyInfo": "Inserisci la tua chiave API parcel.app. È necessario un abbonamento Premium. Ottieni la tua chiave su web.parcelapp.net → pannello API.",
4
4
  "label_apiKey": "Chiave API",
5
5
  "btn_checkConnection": "Testa connessione",
6
- "msg_connectionOk": "Connessione riuscita!",
7
- "msg_connectionFailed": "Connessione fallita!",
8
6
  "header_polling": "Polling",
9
7
  "label_pollInterval": "Intervallo di polling (minuti)",
10
8
  "tooltip_pollInterval": "Frequenza di aggiornamento delle consegne (5-60 minuti, predefinito: 10). Limite: 20 richieste/ora.",
@@ -36,5 +34,15 @@
36
34
  "estimateOverdue": "in ritardo",
37
35
  "estimateToday": "oggi",
38
36
  "estimateTomorrow": "domani",
39
- "estimateDays": "tra %d giorni"
37
+ "estimateDays": "tra %s giorni",
38
+ "status_0": "Consegnato",
39
+ "status_1": "Congelato",
40
+ "status_2": "In transito",
41
+ "status_3": "In attesa di ritiro",
42
+ "status_4": "In consegna",
43
+ "status_5": "Non trovato",
44
+ "status_6": "Consegna fallita",
45
+ "status_7": "Eccezione",
46
+ "status_8": "Registrato",
47
+ "packageName": "Pacco %s"
40
48
  }
@@ -3,8 +3,6 @@
3
3
  "apiKeyInfo": "Voer je parcel.app API-sleutel in. Een Premium-abonnement is vereist. Haal je sleutel op via web.parcelapp.net → API-paneel.",
4
4
  "label_apiKey": "API-sleutel",
5
5
  "btn_checkConnection": "Verbinding testen",
6
- "msg_connectionOk": "Verbinding succesvol!",
7
- "msg_connectionFailed": "Verbinding mislukt!",
8
6
  "header_polling": "Polling",
9
7
  "label_pollInterval": "Polling-interval (minuten)",
10
8
  "tooltip_pollInterval": "Hoe vaak bezorgingsupdates ophalen (5-60 minuten, standaard: 10). Limiet: 20 verzoeken/uur.",
@@ -36,5 +34,15 @@
36
34
  "estimateOverdue": "te laat",
37
35
  "estimateToday": "vandaag",
38
36
  "estimateTomorrow": "morgen",
39
- "estimateDays": "over %d dagen"
37
+ "estimateDays": "over %s dagen",
38
+ "status_0": "Bezorgd",
39
+ "status_1": "Bevroren",
40
+ "status_2": "Onderweg",
41
+ "status_3": "Wacht op ophaling",
42
+ "status_4": "Wordt bezorgd",
43
+ "status_5": "Niet gevonden",
44
+ "status_6": "Bezorgpoging mislukt",
45
+ "status_7": "Uitzondering",
46
+ "status_8": "Geregistreerd",
47
+ "packageName": "Pakket %s"
40
48
  }
@@ -3,8 +3,6 @@
3
3
  "apiKeyInfo": "Wprowadź klucz API parcel.app. Wymagana subskrypcja Premium. Uzyskaj klucz na web.parcelapp.net → panel API.",
4
4
  "label_apiKey": "Klucz API",
5
5
  "btn_checkConnection": "Testuj połączenie",
6
- "msg_connectionOk": "Połączenie udane!",
7
- "msg_connectionFailed": "Połączenie nieudane!",
8
6
  "header_polling": "Odpytywanie",
9
7
  "label_pollInterval": "Interwał odpytywania (minuty)",
10
8
  "tooltip_pollInterval": "Jak często pobierać aktualizacje dostaw (5-60 minut, domyślnie: 10). Limit: 20 zapytań/godzinę.",
@@ -36,5 +34,15 @@
36
34
  "estimateOverdue": "zaległe",
37
35
  "estimateToday": "dzisiaj",
38
36
  "estimateTomorrow": "jutro",
39
- "estimateDays": "za %d dni"
37
+ "estimateDays": "za %s dni",
38
+ "status_0": "Dostarczone",
39
+ "status_1": "Zamrożone",
40
+ "status_2": "W drodze",
41
+ "status_3": "Oczekuje na odbiór",
42
+ "status_4": "W doręczeniu",
43
+ "status_5": "Nie znaleziono",
44
+ "status_6": "Nieudana próba doręczenia",
45
+ "status_7": "Wyjątek",
46
+ "status_8": "Zarejestrowane",
47
+ "packageName": "Paczka %s"
40
48
  }
@@ -3,8 +3,6 @@
3
3
  "apiKeyInfo": "Insira sua chave API do parcel.app. É necessária uma assinatura Premium. Obtenha sua chave em web.parcelapp.net → painel API.",
4
4
  "label_apiKey": "Chave API",
5
5
  "btn_checkConnection": "Testar Conexão",
6
- "msg_connectionOk": "Conexão bem-sucedida!",
7
- "msg_connectionFailed": "Falha na conexão!",
8
6
  "header_polling": "Consulta",
9
7
  "label_pollInterval": "Intervalo de consulta (minutos)",
10
8
  "tooltip_pollInterval": "Com que frequência buscar atualizações de entrega (5-60 minutos, padrão: 10). Limite: 20 solicitações/hora.",
@@ -36,5 +34,15 @@
36
34
  "estimateOverdue": "atrasado",
37
35
  "estimateToday": "hoje",
38
36
  "estimateTomorrow": "amanhã",
39
- "estimateDays": "em %d dias"
37
+ "estimateDays": "em %s dias",
38
+ "status_0": "Entregue",
39
+ "status_1": "Congelado",
40
+ "status_2": "Em trânsito",
41
+ "status_3": "Aguardando recolha",
42
+ "status_4": "Em entrega",
43
+ "status_5": "Não encontrado",
44
+ "status_6": "Tentativa de entrega falhou",
45
+ "status_7": "Exceção",
46
+ "status_8": "Registado",
47
+ "packageName": "Encomenda %s"
40
48
  }
@@ -3,8 +3,6 @@
3
3
  "apiKeyInfo": "Введите API-ключ parcel.app. Требуется Premium-подписка. Получите ключ на web.parcelapp.net → раздел API.",
4
4
  "label_apiKey": "API-ключ",
5
5
  "btn_checkConnection": "Проверить соединение",
6
- "msg_connectionOk": "Соединение успешно!",
7
- "msg_connectionFailed": "Ошибка соединения!",
8
6
  "header_polling": "Опрос",
9
7
  "label_pollInterval": "Интервал опроса (минуты)",
10
8
  "tooltip_pollInterval": "Как часто получать обновления доставки (5-60 минут, по умолчанию: 10). Лимит: 20 запросов/час.",
@@ -36,5 +34,15 @@
36
34
  "estimateOverdue": "просрочено",
37
35
  "estimateToday": "сегодня",
38
36
  "estimateTomorrow": "завтра",
39
- "estimateDays": "через %d дн."
37
+ "estimateDays": "через %s дн.",
38
+ "status_0": "Доставлено",
39
+ "status_1": "Заморожено",
40
+ "status_2": "В пути",
41
+ "status_3": "Ожидает получения",
42
+ "status_4": "Доставляется",
43
+ "status_5": "Не найдено",
44
+ "status_6": "Неудачная доставка",
45
+ "status_7": "Исключение",
46
+ "status_8": "Зарегистрировано",
47
+ "packageName": "Посылка %s"
40
48
  }
@@ -3,8 +3,6 @@
3
3
  "apiKeyInfo": "Введіть ваш API-ключ parcel.app. Потрібна Premium-підписка. Отримайте ключ на web.parcelapp.net → панель API.",
4
4
  "label_apiKey": "API-ключ",
5
5
  "btn_checkConnection": "Перевірити з'єднання",
6
- "msg_connectionOk": "З'єднання успішне!",
7
- "msg_connectionFailed": "Помилка з'єднання!",
8
6
  "header_polling": "Опитування",
9
7
  "label_pollInterval": "Інтервал опитування (хвилини)",
10
8
  "tooltip_pollInterval": "Як часто отримувати оновлення доставки (5-60 хвилин, за замовчуванням: 10). Ліміт: 20 запитів/годину.",
@@ -36,5 +34,15 @@
36
34
  "estimateOverdue": "прострочено",
37
35
  "estimateToday": "сьогодні",
38
36
  "estimateTomorrow": "завтра",
39
- "estimateDays": "через %d дн."
37
+ "estimateDays": "через %s дн.",
38
+ "status_0": "Доставлено",
39
+ "status_1": "Заморожено",
40
+ "status_2": "В дорозі",
41
+ "status_3": "Очікує отримання",
42
+ "status_4": "Доставляється",
43
+ "status_5": "Не знайдено",
44
+ "status_6": "Невдала спроба доставки",
45
+ "status_7": "Виняток",
46
+ "status_8": "Зареєстровано",
47
+ "packageName": "Посилка %s"
40
48
  }
@@ -3,8 +3,6 @@
3
3
  "apiKeyInfo": "输入你的 parcel.app API 密钥。需要 Premium 订阅。在 web.parcelapp.net → API 面板获取密钥。",
4
4
  "label_apiKey": "API 密钥",
5
5
  "btn_checkConnection": "测试连接",
6
- "msg_connectionOk": "连接成功!",
7
- "msg_connectionFailed": "连接失败!",
8
6
  "header_polling": "轮询",
9
7
  "label_pollInterval": "轮询间隔(分钟)",
10
8
  "tooltip_pollInterval": "多久获取一次快递更新(5-60分钟,默认:10)。速率限制:20次请求/小时。",
@@ -36,5 +34,15 @@
36
34
  "estimateOverdue": "已逾期",
37
35
  "estimateToday": "今天",
38
36
  "estimateTomorrow": "明天",
39
- "estimateDays": "%d 天后"
37
+ "estimateDays": "%s 天后",
38
+ "status_0": "已送达",
39
+ "status_1": "已冻结",
40
+ "status_2": "运输中",
41
+ "status_3": "等待取件",
42
+ "status_4": "派送中",
43
+ "status_5": "未找到",
44
+ "status_6": "派送失败",
45
+ "status_7": "异常",
46
+ "status_8": "已登记",
47
+ "packageName": "包裹 %s"
40
48
  }
@@ -35,10 +35,6 @@
35
35
  "label": "btn_checkConnection",
36
36
  "command": "checkConnection",
37
37
  "jsonData": "{\"apiKey\": \"${data.apiKey}\"}",
38
- "result": {
39
- "okText": "msg_connectionOk",
40
- "errorText": "msg_connectionFailed"
41
- },
42
38
  "xs": 12,
43
39
  "sm": 12,
44
40
  "md": 4,
@@ -21,7 +21,8 @@ __export(coerce_exports, {
21
21
  coerceClampedInt: () => coerceClampedInt,
22
22
  coerceFiniteNumber: () => coerceFiniteNumber,
23
23
  errText: () => errText,
24
- isTrueish: () => isTrueish
24
+ isTrueish: () => isTrueish,
25
+ oneLine: () => oneLine
25
26
  });
26
27
  module.exports = __toCommonJS(coerce_exports);
27
28
  const DECIMAL_NUMBER_RE = /^-?\d+(\.\d+)?$/;
@@ -77,11 +78,15 @@ function coerceClampedInt(raw, min, max, defaultValue) {
77
78
  }
78
79
  return Math.max(min, Math.min(max, Math.floor(n)));
79
80
  }
81
+ function oneLine(value) {
82
+ return value.replace(/[\r\n\t\0\v\f\u2028\u2029]+/g, " ");
83
+ }
80
84
  // Annotate the CommonJS export names for ESM import in node:
81
85
  0 && (module.exports = {
82
86
  coerceClampedInt,
83
87
  coerceFiniteNumber,
84
88
  errText,
85
- isTrueish
89
+ isTrueish,
90
+ oneLine
86
91
  });
87
92
  //# sourceMappingURL=coerce.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/coerce.ts"],
4
- "sourcesContent": ["/**\n * Boundary coercion helpers for external API data.\n *\n * The parcel.app API is documented but field types still drift in practice\n * (rare success-flag returned as `\"true\"` string, occasional null where a\n * number is expected). These helpers guard against NaN/Infinity/non-string\n * values reaching ioBroker states.\n */\n\n// Strict decimal regex \u2014 only optional minus sign + digits + optional fractional part.\n// Rejects HEX (`0x...`), exponential (`1e3`), Infinity, NaN, leading/trailing whitespace.\n// Hassemu (E8 in v1.9.0) hardened the same coerce-helper this way; homewizard\n// adopted it in v0.7.2 (D8). Consistent with both adapters.\nconst DECIMAL_NUMBER_RE = /^-?\\d+(\\.\\d+)?$/;\n\n/**\n * Coerce to a finite number or null.\n * Accepts numbers directly; parses strict decimal strings; rejects NaN, Infinity,\n * HEX (`0x...`) and exponential notation (`1e3`).\n *\n * @param value Unknown external value\n */\nexport function coerceFiniteNumber(value: unknown): number | null {\n if (typeof value === \"number\") {\n return Number.isFinite(value) ? value : null;\n }\n if (typeof value === \"string\" && DECIMAL_NUMBER_RE.test(value)) {\n const n = Number(value);\n return Number.isFinite(n) ? n : null;\n }\n return null;\n}\n\n/**\n * Coerce a parcel.app `success` flag. The API returns a real boolean in normal\n * operation, but the guard accepts common string/number encodings (`1`, `\"true\"`,\n * `\"1\"`) so a one-off drift doesn't break the entire poll cycle.\n *\n * @param v Value to interpret as a success flag\n */\nexport function isTrueish(v: unknown): boolean {\n if (typeof v === \"boolean\") {\n return v;\n }\n if (typeof v === \"number\") {\n return v === 1;\n }\n if (typeof v === \"string\") {\n const s = v.toLowerCase();\n return s === \"true\" || s === \"1\";\n }\n return false;\n}\n\n/**\n * Extract a log-friendly message from a thrown / rejected value. Centralizes the\n * `err instanceof Error ? err.message : String(err)` pattern that otherwise\n * gets repeated at every catch-site. Plain objects are JSON-stringified so a\n * `[object Object]` log is avoided when callers throw bag-of-fields.\n *\n * @param err Caught value of unknown shape (Error, string, undefined, ...).\n */\nexport function errText(err: unknown): string {\n if (err instanceof Error) {\n return err.message;\n }\n if (err === null) {\n return \"null\";\n }\n if (err === undefined) {\n return \"undefined\";\n }\n if (typeof err === \"string\") {\n return err;\n }\n if (typeof err === \"number\" || typeof err === \"boolean\" || typeof err === \"bigint\") {\n return String(err);\n }\n // Plain objects + symbols would otherwise stringify to \"[object Object]\" / fail.\n // Prefer JSON for the common case so the log is at least diagnosable.\n try {\n return JSON.stringify(err);\n } catch {\n return Object.prototype.toString.call(err);\n }\n}\n\n/**\n * v0.4.2 (X5): coerce an admin-config integer setting (number-or-string)\n * to a finite, clamped integer. Returns `defaultValue` for non-finite\n * input \u2014 guards against `setInterval(fn, NaN)` tight-loops when the\n * config field happens to come back as a string from the admin UI.\n *\n * @param raw Raw value from `this.config.<field>`.\n * @param min Inclusive lower bound.\n * @param max Inclusive upper bound.\n * @param defaultValue Fallback when raw is missing or unparseable.\n */\nexport function coerceClampedInt(raw: unknown, min: number, max: number, defaultValue: number): number {\n const n = typeof raw === \"number\" ? raw : typeof raw === \"string\" ? parseFloat(raw) : NaN;\n if (!Number.isFinite(n)) {\n return defaultValue;\n }\n return Math.max(min, Math.min(max, Math.floor(n)));\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAaA,MAAM,oBAAoB;AASnB,SAAS,mBAAmB,OAA+B;AAChE,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO,OAAO,SAAS,KAAK,IAAI,QAAQ;AAAA,EAC1C;AACA,MAAI,OAAO,UAAU,YAAY,kBAAkB,KAAK,KAAK,GAAG;AAC9D,UAAM,IAAI,OAAO,KAAK;AACtB,WAAO,OAAO,SAAS,CAAC,IAAI,IAAI;AAAA,EAClC;AACA,SAAO;AACT;AASO,SAAS,UAAU,GAAqB;AAC7C,MAAI,OAAO,MAAM,WAAW;AAC1B,WAAO;AAAA,EACT;AACA,MAAI,OAAO,MAAM,UAAU;AACzB,WAAO,MAAM;AAAA,EACf;AACA,MAAI,OAAO,MAAM,UAAU;AACzB,UAAM,IAAI,EAAE,YAAY;AACxB,WAAO,MAAM,UAAU,MAAM;AAAA,EAC/B;AACA,SAAO;AACT;AAUO,SAAS,QAAQ,KAAsB;AAC5C,MAAI,eAAe,OAAO;AACxB,WAAO,IAAI;AAAA,EACb;AACA,MAAI,QAAQ,MAAM;AAChB,WAAO;AAAA,EACT;AACA,MAAI,QAAQ,QAAW;AACrB,WAAO;AAAA,EACT;AACA,MAAI,OAAO,QAAQ,UAAU;AAC3B,WAAO;AAAA,EACT;AACA,MAAI,OAAO,QAAQ,YAAY,OAAO,QAAQ,aAAa,OAAO,QAAQ,UAAU;AAClF,WAAO,OAAO,GAAG;AAAA,EACnB;AAGA,MAAI;AACF,WAAO,KAAK,UAAU,GAAG;AAAA,EAC3B,QAAQ;AACN,WAAO,OAAO,UAAU,SAAS,KAAK,GAAG;AAAA,EAC3C;AACF;AAaO,SAAS,iBAAiB,KAAc,KAAa,KAAa,cAA8B;AACrG,QAAM,IAAI,OAAO,QAAQ,WAAW,MAAM,OAAO,QAAQ,WAAW,WAAW,GAAG,IAAI;AACtF,MAAI,CAAC,OAAO,SAAS,CAAC,GAAG;AACvB,WAAO;AAAA,EACT;AACA,SAAO,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC;AACnD;",
4
+ "sourcesContent": ["/**\n * Boundary coercion helpers for external API data.\n *\n * The parcel.app API is documented but field types still drift in practice\n * (rare success-flag returned as `\"true\"` string, occasional null where a\n * number is expected). These helpers guard against NaN/Infinity/non-string\n * values reaching ioBroker states.\n */\n\n// Strict decimal regex \u2014 only optional minus sign + digits + optional fractional part.\n// Rejects HEX (`0x...`), exponential (`1e3`), Infinity, NaN, leading/trailing whitespace.\n// Hassemu (E8 in v1.9.0) hardened the same coerce-helper this way; homewizard\n// adopted it in v0.7.2 (D8). Consistent with both adapters.\nconst DECIMAL_NUMBER_RE = /^-?\\d+(\\.\\d+)?$/;\n\n/**\n * Coerce to a finite number or null.\n * Accepts numbers directly; parses strict decimal strings; rejects NaN, Infinity,\n * HEX (`0x...`) and exponential notation (`1e3`).\n *\n * @param value Unknown external value\n */\nexport function coerceFiniteNumber(value: unknown): number | null {\n if (typeof value === \"number\") {\n return Number.isFinite(value) ? value : null;\n }\n if (typeof value === \"string\" && DECIMAL_NUMBER_RE.test(value)) {\n const n = Number(value);\n return Number.isFinite(n) ? n : null;\n }\n return null;\n}\n\n/**\n * Coerce a parcel.app `success` flag. The API returns a real boolean in normal\n * operation, but the guard accepts common string/number encodings (`1`, `\"true\"`,\n * `\"1\"`) so a one-off drift doesn't break the entire poll cycle.\n *\n * @param v Value to interpret as a success flag\n */\nexport function isTrueish(v: unknown): boolean {\n if (typeof v === \"boolean\") {\n return v;\n }\n if (typeof v === \"number\") {\n return v === 1;\n }\n if (typeof v === \"string\") {\n const s = v.toLowerCase();\n return s === \"true\" || s === \"1\";\n }\n return false;\n}\n\n/**\n * Extract a log-friendly message from a thrown / rejected value. Centralizes the\n * `err instanceof Error ? err.message : String(err)` pattern that otherwise\n * gets repeated at every catch-site. Plain objects are JSON-stringified so a\n * `[object Object]` log is avoided when callers throw bag-of-fields.\n *\n * @param err Caught value of unknown shape (Error, string, undefined, ...).\n */\nexport function errText(err: unknown): string {\n if (err instanceof Error) {\n return err.message;\n }\n if (err === null) {\n return \"null\";\n }\n if (err === undefined) {\n return \"undefined\";\n }\n if (typeof err === \"string\") {\n return err;\n }\n if (typeof err === \"number\" || typeof err === \"boolean\" || typeof err === \"bigint\") {\n return String(err);\n }\n // Plain objects + symbols would otherwise stringify to \"[object Object]\" / fail.\n // Prefer JSON for the common case so the log is at least diagnosable.\n try {\n return JSON.stringify(err);\n } catch {\n return Object.prototype.toString.call(err);\n }\n}\n\n/**\n * v0.4.2 (X5): coerce an admin-config integer setting (number-or-string)\n * to a finite, clamped integer. Returns `defaultValue` for non-finite\n * input \u2014 guards against `setInterval(fn, NaN)` tight-loops when the\n * config field happens to come back as a string from the admin UI.\n *\n * @param raw Raw value from `this.config.<field>`.\n * @param min Inclusive lower bound.\n * @param max Inclusive upper bound.\n * @param defaultValue Fallback when raw is missing or unparseable.\n */\nexport function coerceClampedInt(raw: unknown, min: number, max: number, defaultValue: number): number {\n const n = typeof raw === \"number\" ? raw : typeof raw === \"string\" ? parseFloat(raw) : NaN;\n if (!Number.isFinite(n)) {\n return defaultValue;\n }\n return Math.max(min, Math.min(max, Math.floor(n)));\n}\n\n/**\n * Collapse control-character runs (CR / LF / TAB / NUL / VT / FF and the\n * Unicode line separators U+2028/U+2029) in an untrusted string to a single\n * space before it is interpolated into a log line \u2014 prevents log-injection\n * (a forged second log line) from external values (tracking number, carrier\n * code, raw API body, collision raw-key with its NUL separator, \u2026). Fleet\n * convention (hassemu/hueemu v1.36.0 S4); widened in v0.10.0 (I10).\n *\n * @param value Untrusted string to flatten for single-line logging.\n */\nexport function oneLine(value: string): string {\n return value.replace(/[\\r\\n\\t\\0\\v\\f\\u2028\\u2029]+/g, \" \");\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAaA,MAAM,oBAAoB;AASnB,SAAS,mBAAmB,OAA+B;AAChE,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO,OAAO,SAAS,KAAK,IAAI,QAAQ;AAAA,EAC1C;AACA,MAAI,OAAO,UAAU,YAAY,kBAAkB,KAAK,KAAK,GAAG;AAC9D,UAAM,IAAI,OAAO,KAAK;AACtB,WAAO,OAAO,SAAS,CAAC,IAAI,IAAI;AAAA,EAClC;AACA,SAAO;AACT;AASO,SAAS,UAAU,GAAqB;AAC7C,MAAI,OAAO,MAAM,WAAW;AAC1B,WAAO;AAAA,EACT;AACA,MAAI,OAAO,MAAM,UAAU;AACzB,WAAO,MAAM;AAAA,EACf;AACA,MAAI,OAAO,MAAM,UAAU;AACzB,UAAM,IAAI,EAAE,YAAY;AACxB,WAAO,MAAM,UAAU,MAAM;AAAA,EAC/B;AACA,SAAO;AACT;AAUO,SAAS,QAAQ,KAAsB;AAC5C,MAAI,eAAe,OAAO;AACxB,WAAO,IAAI;AAAA,EACb;AACA,MAAI,QAAQ,MAAM;AAChB,WAAO;AAAA,EACT;AACA,MAAI,QAAQ,QAAW;AACrB,WAAO;AAAA,EACT;AACA,MAAI,OAAO,QAAQ,UAAU;AAC3B,WAAO;AAAA,EACT;AACA,MAAI,OAAO,QAAQ,YAAY,OAAO,QAAQ,aAAa,OAAO,QAAQ,UAAU;AAClF,WAAO,OAAO,GAAG;AAAA,EACnB;AAGA,MAAI;AACF,WAAO,KAAK,UAAU,GAAG;AAAA,EAC3B,QAAQ;AACN,WAAO,OAAO,UAAU,SAAS,KAAK,GAAG;AAAA,EAC3C;AACF;AAaO,SAAS,iBAAiB,KAAc,KAAa,KAAa,cAA8B;AACrG,QAAM,IAAI,OAAO,QAAQ,WAAW,MAAM,OAAO,QAAQ,WAAW,WAAW,GAAG,IAAI;AACtF,MAAI,CAAC,OAAO,SAAS,CAAC,GAAG;AACvB,WAAO;AAAA,EACT;AACA,SAAO,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC;AACnD;AAYO,SAAS,QAAQ,OAAuB;AAC7C,SAAO,MAAM,QAAQ,gCAAgC,GAAG;AAC1D;",
6
6
  "names": []
7
7
  }
package/build/lib/i18n.js CHANGED
@@ -18,15 +18,42 @@ var __copyProps = (to, from, except, desc) => {
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
  var i18n_exports = {};
20
20
  __export(i18n_exports, {
21
- tName: () => tName
21
+ packageName: () => packageName,
22
+ statusLabel: () => statusLabel,
23
+ tName: () => tName,
24
+ tText: () => tText
22
25
  });
23
26
  module.exports = __toCommonJS(i18n_exports);
24
27
  var import_adapter_core = require("@iobroker/adapter-core");
25
28
  function tName(key) {
26
29
  return import_adapter_core.I18n.getTranslatedObject(key);
27
30
  }
31
+ function tText(key, ...args) {
32
+ return import_adapter_core.I18n.translate(key, ...args);
33
+ }
34
+ const STATUS_KEYS = {
35
+ 0: "status_0",
36
+ 1: "status_1",
37
+ 2: "status_2",
38
+ 3: "status_3",
39
+ 4: "status_4",
40
+ 5: "status_5",
41
+ 6: "status_6",
42
+ 7: "status_7",
43
+ 8: "status_8"
44
+ };
45
+ function statusLabel(code) {
46
+ const key = STATUS_KEYS[code];
47
+ return key === void 0 ? void 0 : import_adapter_core.I18n.translate(key);
48
+ }
49
+ function packageName(trackingNumber) {
50
+ return import_adapter_core.I18n.getTranslatedObject("packageName", trackingNumber);
51
+ }
28
52
  // Annotate the CommonJS export names for ESM import in node:
29
53
  0 && (module.exports = {
30
- tName
54
+ packageName,
55
+ statusLabel,
56
+ tName,
57
+ tText
31
58
  });
32
59
  //# sourceMappingURL=i18n.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/i18n.ts"],
4
- "sourcesContent": ["import { I18n } from \"@iobroker/adapter-core\";\nimport type translations from \"../../admin/i18n/en.json\";\n\ntype I18nKey = keyof typeof translations;\n\n/**\n * @param key Translation key from admin/i18n/en.json\n */\nexport function tName(key: I18nKey): ioBroker.StringOrTranslated {\n return I18n.getTranslatedObject(key);\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,0BAAqB;AAQd,SAAS,MAAM,KAA2C;AAC/D,SAAO,yBAAK,oBAAoB,GAAG;AACrC;",
4
+ "sourcesContent": ["import { I18n } from \"@iobroker/adapter-core\";\nimport type translations from \"../../admin/i18n/en.json\";\n\ntype I18nKey = keyof typeof translations;\n\n/**\n * @param key Translation key from admin/i18n/en.json\n */\nexport function tName(key: I18nKey): ioBroker.StringOrTranslated {\n return I18n.getTranslatedObject(key);\n}\n\n/**\n * Type-safe wrapper around I18n.translate \u2014 a typo'd key is a compile error,\n * matching the guarantee tName already gives object names (v0.10.0, L13).\n * `%s` placeholders are filled from args, rendered in the system language.\n *\n * @param key Translation key from admin/i18n/en.json\n * @param args Values substituted for `%s` placeholders\n */\nexport function tText(key: I18nKey, ...args: (string | number | boolean | null)[]): string {\n return I18n.translate(key, ...args);\n}\n\n/** status_code \u2192 i18n key. `satisfies` pins every entry to a real key in en.json. */\nconst STATUS_KEYS = {\n 0: \"status_0\",\n 1: \"status_1\",\n 2: \"status_2\",\n 3: \"status_3\",\n 4: \"status_4\",\n 5: \"status_5\",\n 6: \"status_6\",\n 7: \"status_7\",\n 8: \"status_8\",\n} as const satisfies Record<number, I18nKey>;\n\n/**\n * Localized status label for a parsed status code, or undefined for codes the\n * table does not know (API drift) \u2014 the caller renders its own fallback.\n * Replaces the former STATUS_LABELS table in types.ts (v0.10.0, L20): one\n * translation system (admin/i18n via adapter-core I18n) instead of two.\n *\n * @param code Parsed numeric status code\n */\nexport function statusLabel(code: number): string | undefined {\n const key = (STATUS_KEYS as Partial<Record<number, I18nKey>>)[code];\n return key === undefined ? undefined : I18n.translate(key);\n}\n\n/**\n * Localized fallback device name (\"Package <tracking>\") as a translation\n * object \u2014 getTranslatedObject substitutes `%s` in every language, so the\n * one previously hard-English user-facing label is localized too (v0.10.0, L18).\n *\n * @param trackingNumber Tracking number (or package id) interpolated into the name\n */\nexport function packageName(trackingNumber: string): ioBroker.StringOrTranslated {\n return I18n.getTranslatedObject(\"packageName\", trackingNumber);\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,0BAAqB;AAQd,SAAS,MAAM,KAA2C;AAC/D,SAAO,yBAAK,oBAAoB,GAAG;AACrC;AAUO,SAAS,MAAM,QAAiB,MAAoD;AACzF,SAAO,yBAAK,UAAU,KAAK,GAAG,IAAI;AACpC;AAGA,MAAM,cAAc;AAAA,EAClB,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AACL;AAUO,SAAS,YAAY,MAAkC;AAC5D,QAAM,MAAO,YAAiD,IAAI;AAClE,SAAO,QAAQ,SAAY,SAAY,yBAAK,UAAU,GAAG;AAC3D;AASO,SAAS,YAAY,gBAAqD;AAC/E,SAAO,yBAAK,oBAAoB,eAAe,cAAc;AAC/D;",
6
6
  "names": []
7
7
  }