universal-parcel-scraper 0.49.2-main.551 → 0.49.2-main.552
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
|
@@ -149,7 +149,7 @@ createTracker({ providers: ['ParcelsApp', 'Ship24', '17TRACK', 'Postal Ninja', '
|
|
|
149
149
|
<!-- GENERATED:stages -->
|
|
150
150
|
<img src="docs/assets/stages.svg" alt="DHL: Die Sendung wurde in das Zustellfahrzeug geladen.; Mondial Relay: En cours de livraison; Correios Brazil: Objeto saiu para entrega ao destinatário; Correos Express: EN REPARTO; Yamato Transport: 配達中; La Poste / Colissimo: DISTOU. All are filed under out_for_delivery." width="760">
|
|
151
151
|
|
|
152
|
-
The carrier folders record 2,
|
|
152
|
+
The carrier folders record 2,766 such statuses, each filed under one stage.
|
|
153
153
|
<!-- /GENERATED:stages -->
|
|
154
154
|
|
|
155
155
|
Wording nobody recorded yet goes through a classifier that reads seven European languages.
|
|
@@ -15,18 +15,20 @@
|
|
|
15
15
|
* does echo the delivery postcode, which must agree with the requested one.
|
|
16
16
|
*
|
|
17
17
|
* Privacy: the loader payload carries the retailer, the recipient's name,
|
|
18
|
-
* e-mail, phone and address,
|
|
19
|
-
* `parse()` copies nothing from those
|
|
20
|
-
* timestamp, our own description and the mapped
|
|
21
|
-
* fields read are the delivery postcode,
|
|
22
|
-
* delivery country, which picks the window's
|
|
18
|
+
* e-mail, phone and address, the PaackGo Point pickup code and QR link, and
|
|
19
|
+
* per-event `variables` that repeat them. `parse()` copies nothing from those:
|
|
20
|
+
* each event is rebuilt from its timestamp, our own description and the mapped
|
|
21
|
+
* stage, and the only order-level fields read are the delivery postcode,
|
|
22
|
+
* compared and never copied, the delivery country, which picks the window's
|
|
23
|
+
* clock, the delivery window, and the PaackGo Point's name and address while
|
|
24
|
+
* the parcel waits there.
|
|
23
25
|
*/
|
|
24
26
|
import { load } from 'cheerio';
|
|
25
27
|
import { DateTime } from 'luxon';
|
|
26
28
|
import { lookupBudget } from '../../core/adapter/index.js';
|
|
27
29
|
import { IndeterminateError, InputRequiredError, InvalidInputError, NotFoundError, SchemaError } from '../../core/errors/index.js';
|
|
28
30
|
import { countryCode, countryTimeZone, explicitOffsetTime } from '../../core/time/index.js';
|
|
29
|
-
import { decodeText, fetchBounded, UpstreamHttpError, userAgentOf } from '../../core/transport/index.js';
|
|
31
|
+
import { clean, decodeText, fetchBounded, UpstreamHttpError, userAgentOf } from '../../core/transport/index.js';
|
|
30
32
|
import { isRecord } from '../../core/types.js';
|
|
31
33
|
import { classifyPaackEvent } from './status.js';
|
|
32
34
|
const TRACKING_ENDPOINT = 'https://mydeliveries.paack.app/tracking/order';
|
|
@@ -196,6 +198,22 @@ function redirectTarget(location, from, trackingNumber, postcode) {
|
|
|
196
198
|
return null;
|
|
197
199
|
return { url: trackingOrderUrl(`${target.origin}${target.pathname}`, trackingNumber, postcode) };
|
|
198
200
|
}
|
|
201
|
+
/**
|
|
202
|
+
* The PaackGo Point the page names: `pudo_name`, then `pudo_address` on the
|
|
203
|
+
* following lines. The address is one string Paack formats itself; the page
|
|
204
|
+
* prints it as it comes and breaks it only where it holds a line break. The
|
|
205
|
+
* pickup code (`pudo_passcode`), QR link and collection deadline beside them
|
|
206
|
+
* are never read.
|
|
207
|
+
*/
|
|
208
|
+
function pickupPoint(order) {
|
|
209
|
+
const name = clean(order.pudo_name, 120);
|
|
210
|
+
if (!name)
|
|
211
|
+
return '';
|
|
212
|
+
const address = typeof order.pudo_address === 'string'
|
|
213
|
+
? order.pudo_address.split('\n').map((line) => clean(line, 200)).filter(Boolean)
|
|
214
|
+
: [];
|
|
215
|
+
return [name, ...address].join('\n');
|
|
216
|
+
}
|
|
199
217
|
/** Letters and digits only, so "SW1A 1AA" and "4445-027" compare by content. */
|
|
200
218
|
function postcodeKey(value) {
|
|
201
219
|
return value.toLocaleUpperCase('en-US').replace(/[^A-Z0-9]/g, '');
|
|
@@ -258,12 +276,14 @@ export function parsePaackTrackingResponse(payload, rawPostcode) {
|
|
|
258
276
|
const expected = isRecord(order.expected_delivery_ts) && !['delivered', 'exception'].includes(current.status)
|
|
259
277
|
? expectedDelivery(order.expected_delivery_ts, zone, newest)
|
|
260
278
|
: null;
|
|
279
|
+
const pickup = current.stage === 'ready_for_pickup' ? pickupPoint(order) : '';
|
|
261
280
|
return {
|
|
262
281
|
status: current.status,
|
|
263
282
|
current_stage: current.stage,
|
|
264
283
|
last_status_text: current.description,
|
|
265
284
|
last_update: activeTime?.iso ?? events[0]?.time ?? null,
|
|
266
285
|
expected_delivery: expected,
|
|
286
|
+
...(pickup ? { pickup_point: pickup } : {}),
|
|
267
287
|
timezone: zone,
|
|
268
288
|
events,
|
|
269
289
|
};
|
|
@@ -40,7 +40,8 @@
|
|
|
40
40
|
},
|
|
41
41
|
"capabilities": [
|
|
42
42
|
"history",
|
|
43
|
-
"eta"
|
|
43
|
+
"eta",
|
|
44
|
+
"pickup_point"
|
|
44
45
|
],
|
|
45
46
|
"portal": {
|
|
46
47
|
"url": "https://mydeliveries.paack.app/tracking?tracking_number={trackingNumber}",
|
|
@@ -50,6 +51,8 @@
|
|
|
50
51
|
"history",
|
|
51
52
|
"eta",
|
|
52
53
|
"eta_window",
|
|
54
|
+
"pickup_point",
|
|
55
|
+
"pickup_code",
|
|
53
56
|
"sender_name",
|
|
54
57
|
"recipient_name",
|
|
55
58
|
"recipient_address"
|
|
@@ -58,9 +61,11 @@
|
|
|
58
61
|
"status",
|
|
59
62
|
"history",
|
|
60
63
|
"eta",
|
|
61
|
-
"eta_window"
|
|
64
|
+
"eta_window",
|
|
65
|
+
"pickup_point"
|
|
62
66
|
],
|
|
63
67
|
"discarded": [
|
|
68
|
+
"pickup_code",
|
|
64
69
|
"sender_name",
|
|
65
70
|
"recipient_name",
|
|
66
71
|
"recipient_address"
|
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
*
|
|
11
11
|
* Order matters: return and failure identifiers are tested before the broader
|
|
12
12
|
* delivery ones, so "notDelivered" can never match the "delivered" substring.
|
|
13
|
+
* PaackGo Point identifiers come from the page's own vocabulary and are tested
|
|
14
|
+
* before the generic words they contain: "rejectedByPudo" is a retried
|
|
15
|
+
* delivery, not a rejection, and "collectedByCustomer" is the recipient
|
|
16
|
+
* collecting the parcel, not a transit collection.
|
|
13
17
|
* The provider's own wording is never returned; each mapped entry supplies the
|
|
14
18
|
* English description we display.
|
|
15
19
|
*
|
|
@@ -15,6 +15,7 @@ const OUT_FOR_DELIVERY = {
|
|
|
15
15
|
description: 'Out for delivery',
|
|
16
16
|
};
|
|
17
17
|
const DELIVERED = { status: 'delivered', stage: 'delivered', description: 'Delivered' };
|
|
18
|
+
const DELIVERY_ISSUE = { status: 'exception', stage: 'failed_attempt', description: 'Delivery issue' };
|
|
18
19
|
/** Paack's English timeline labels, keyed like `statusKey`, with the identifier each one renders. */
|
|
19
20
|
const RELAYED = new Map([
|
|
20
21
|
['orderdetailsreceived', REGISTERED], // manifested
|
|
@@ -36,6 +37,18 @@ export function classifyPaackEvent(value) {
|
|
|
36
37
|
])) {
|
|
37
38
|
return { status: 'exception', stage: 'returned', description: 'Shipment returned' };
|
|
38
39
|
}
|
|
40
|
+
// Expired, refused or unpaid at the PaackGo Point: on its way back to the retailer.
|
|
41
|
+
if (includesAny(key, [
|
|
42
|
+
'inpudotoreturnexpired',
|
|
43
|
+
'inpudotoreturnrejected',
|
|
44
|
+
'inpudotoreturncodnotaccepted',
|
|
45
|
+
])) {
|
|
46
|
+
return { status: 'exception', stage: 'returned', description: 'Returning to sender' };
|
|
47
|
+
}
|
|
48
|
+
// The point was closed, full or refused the parcel; Paack tries again.
|
|
49
|
+
if (includesAny(key, ['pudoclosed', 'pudofull', 'rejectedbypudo'])) {
|
|
50
|
+
return { ...DELIVERY_ISSUE };
|
|
51
|
+
}
|
|
39
52
|
if (includesAny(key, [
|
|
40
53
|
'incorrectaddress',
|
|
41
54
|
'notaccepted',
|
|
@@ -59,10 +72,10 @@ export function classifyPaackEvent(value) {
|
|
|
59
72
|
'notdelivered',
|
|
60
73
|
'undelivered',
|
|
61
74
|
]))
|
|
62
|
-
return {
|
|
63
|
-
if (includesAny(key, ['delivered', 'deliverycompleted']))
|
|
75
|
+
return { ...DELIVERY_ISSUE };
|
|
76
|
+
if (includesAny(key, ['delivered', 'deliverycompleted', 'collectedbycustomer']))
|
|
64
77
|
return { ...DELIVERED };
|
|
65
|
-
if (includesAny(key, ['
|
|
78
|
+
if (includesAny(key, ['droppedinpudo'])) {
|
|
66
79
|
return { status: 'out_for_delivery', stage: 'ready_for_pickup', description: 'Ready for pickup' };
|
|
67
80
|
}
|
|
68
81
|
if (includesAny(key, ['outfordelivery', 'indelivery', 'driverassigned', 'inprogress']))
|
|
@@ -25,6 +25,54 @@
|
|
|
25
25
|
"firstSeen": "2026-09-12",
|
|
26
26
|
"note": "Identifier substring inherited from the pre-package adapter; source not recorded."
|
|
27
27
|
},
|
|
28
|
+
{
|
|
29
|
+
"code": "inpudotoreturnexpired",
|
|
30
|
+
"language": "en",
|
|
31
|
+
"stage": "returned",
|
|
32
|
+
"confirmedBy": "bundle",
|
|
33
|
+
"firstSeen": "2026-10-08",
|
|
34
|
+
"note": "Page label inPudoToReturnExpired, \"Expired at PaackGo Point\": not collected in time, returned to the retailer."
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"code": "inpudotoreturnrejected",
|
|
38
|
+
"language": "en",
|
|
39
|
+
"stage": "returned",
|
|
40
|
+
"confirmedBy": "bundle",
|
|
41
|
+
"firstSeen": "2026-10-08",
|
|
42
|
+
"note": "Page label inPudoToReturnRejected: refused at the PaackGo Point, returned to the retailer. Tested before the rejected exception rule."
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"code": "inpudotoreturncodnotaccepted",
|
|
46
|
+
"language": "en",
|
|
47
|
+
"stage": "returned",
|
|
48
|
+
"confirmedBy": "bundle",
|
|
49
|
+
"firstSeen": "2026-10-08",
|
|
50
|
+
"note": "Page label inPudoToReturnCODNotAccepted: cash on delivery not paid at the PaackGo Point, returned to the retailer."
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"code": "pudoclosed",
|
|
54
|
+
"language": "en",
|
|
55
|
+
"stage": "failed_attempt",
|
|
56
|
+
"confirmedBy": "bundle",
|
|
57
|
+
"firstSeen": "2026-10-08",
|
|
58
|
+
"note": "Page label pudoClosed, \"Has been rescheduled\": the PaackGo Point was closed; Paack tries again."
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"code": "pudofull",
|
|
62
|
+
"language": "en",
|
|
63
|
+
"stage": "failed_attempt",
|
|
64
|
+
"confirmedBy": "bundle",
|
|
65
|
+
"firstSeen": "2026-10-08",
|
|
66
|
+
"note": "Page label pudoFull, \"Has been rescheduled\": the PaackGo Point was full; Paack tries again."
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"code": "rejectedbypudo",
|
|
70
|
+
"language": "en",
|
|
71
|
+
"stage": "failed_attempt",
|
|
72
|
+
"confirmedBy": "bundle",
|
|
73
|
+
"firstSeen": "2026-10-08",
|
|
74
|
+
"note": "Page label rejectedByPudo, \"Has been rescheduled\": the PaackGo Point refused the parcel; Paack tries again. Tested before the rejected exception rule."
|
|
75
|
+
},
|
|
28
76
|
{
|
|
29
77
|
"code": "returntosenderscheduled",
|
|
30
78
|
"language": "en",
|
|
@@ -145,6 +193,22 @@
|
|
|
145
193
|
"firstSeen": "2026-09-12",
|
|
146
194
|
"note": "Identifier substring inherited from the pre-package adapter; source not recorded. Reclassified to exception 2026-09-12."
|
|
147
195
|
},
|
|
196
|
+
{
|
|
197
|
+
"code": "inpudotoreturndamaged",
|
|
198
|
+
"language": "en",
|
|
199
|
+
"stage": "exception",
|
|
200
|
+
"confirmedBy": "bundle",
|
|
201
|
+
"firstSeen": "2026-10-08",
|
|
202
|
+
"note": "Page label inPudoToReturnDamaged, \"Logistic issue\": no longer available at the PaackGo Point. Reached through damaged."
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
"code": "lostbypudo",
|
|
206
|
+
"language": "en",
|
|
207
|
+
"stage": "exception",
|
|
208
|
+
"confirmedBy": "bundle",
|
|
209
|
+
"firstSeen": "2026-10-08",
|
|
210
|
+
"note": "Page label lostByPudo, \"Logistic issue\". Reached through lost."
|
|
211
|
+
},
|
|
148
212
|
{
|
|
149
213
|
"code": "integrationerror",
|
|
150
214
|
"language": "en",
|
|
@@ -186,20 +250,20 @@
|
|
|
186
250
|
"note": "Identifier substring inherited from the pre-package adapter; source not recorded."
|
|
187
251
|
},
|
|
188
252
|
{
|
|
189
|
-
"code": "
|
|
253
|
+
"code": "collectedbycustomer",
|
|
190
254
|
"language": "en",
|
|
191
|
-
"stage": "
|
|
192
|
-
"confirmedBy": "
|
|
193
|
-
"firstSeen": "2026-
|
|
194
|
-
"note": "
|
|
255
|
+
"stage": "delivered",
|
|
256
|
+
"confirmedBy": "bundle",
|
|
257
|
+
"firstSeen": "2026-10-08",
|
|
258
|
+
"note": "Page label collectedByCustomer, \"Collected from PaackGo Point\". Tested before the collected transit rule."
|
|
195
259
|
},
|
|
196
260
|
{
|
|
197
|
-
"code": "
|
|
261
|
+
"code": "droppedinpudo",
|
|
198
262
|
"language": "en",
|
|
199
263
|
"stage": "ready_for_pickup",
|
|
200
|
-
"confirmedBy": "
|
|
201
|
-
"firstSeen": "2026-
|
|
202
|
-
"note": "
|
|
264
|
+
"confirmedBy": "bundle",
|
|
265
|
+
"firstSeen": "2026-10-08",
|
|
266
|
+
"note": "Page label droppedInPudo, \"At PaackGo Point\": the parcel waits at the point named by pudo_name and pudo_address."
|
|
203
267
|
},
|
|
204
268
|
{
|
|
205
269
|
"code": "outfordelivery",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "universal-parcel-scraper",
|
|
3
|
-
"version": "0.49.2-main.
|
|
3
|
+
"version": "0.49.2-main.552",
|
|
4
4
|
"description": "Self-hosted parcel tracking: carrier detection, dedicated scrapers and optional universal providers.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|