universal-parcel-scraper 0.69.6-main.895 → 0.69.7-main.899
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 +1 -1
- package/data/catalog.json +1 -1
- package/dist/browser/scraper.js +1 -1
- package/dist/carriers/dpd/carrier.json +1 -1
- package/dist/carriers/evri-uk/browser.js +1 -1
- package/dist/carriers/evri-uk/parser.js +8 -2
- package/dist/carriers/evri-uk/statuses.json +3 -0
- package/dist/carriers/hermes-de/adapter.js +19 -4
- package/dist/carriers/hermes-de/status.d.ts +2 -0
- package/dist/carriers/hermes-de/status.js +5 -1
- package/dist/carriers/hermes-de/statuses.json +2 -2
- package/dist/data/catalog.json +1 -1
- package/dist/generated/catalog.d.ts +1 -1
- package/dist/generated/catalog.js +1 -1
- package/dist/server/openapi.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -155,7 +155,7 @@ createTracker({ providers: ['ParcelsApp', 'Ship24', '17TRACK', 'Postal Ninja', '
|
|
|
155
155
|
<!-- GENERATED:stages -->
|
|
156
156
|
<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">
|
|
157
157
|
|
|
158
|
-
The carrier folders record 3,
|
|
158
|
+
The carrier folders record 3,350 such statuses, each filed under one stage.
|
|
159
159
|
<!-- /GENERATED:stages -->
|
|
160
160
|
|
|
161
161
|
Wording nobody recorded yet goes through a classifier that reads seven European languages.
|
package/data/catalog.json
CHANGED
package/dist/browser/scraper.js
CHANGED
|
@@ -113,7 +113,7 @@ export async function requestEvriUkInPage({ number, budgetMs }) {
|
|
|
113
113
|
}
|
|
114
114
|
const stageCode = event.trackingStage.trackingStageCode.trim();
|
|
115
115
|
const description = event.trackingPoint.description;
|
|
116
|
-
const safeDescription = stageCode === '5_COURIER' ? 'Delivered'
|
|
116
|
+
const safeDescription = stageCode === '5_COURIER' || stageCode === '5_SHOP' ? 'Delivered'
|
|
117
117
|
: /\b(?:delivered (?:to|by)|signed (?:for )?by)\b/i.test(description) ? 'Delivery update' : description;
|
|
118
118
|
const eta = record(event.eta) && scalar(event.eta.start, 64) && scalar(event.eta.end, 64)
|
|
119
119
|
? { start: event.eta.start, end: event.eta.end } : undefined;
|
|
@@ -5,14 +5,18 @@ import { explicitOffsetTime } from '../../core/time/index.js';
|
|
|
5
5
|
import { clean, cleanScalar } from '../../core/transport/text.js';
|
|
6
6
|
import { isRecord } from '../../core/types.js';
|
|
7
7
|
// Evri's rail codes. A parcel's `trackingListV2` lists the rail it is on;
|
|
8
|
-
// a customer return runs 0, 2, 4.
|
|
8
|
+
// a customer return runs 0, 2, 4. A parcel sent to a ParcelShop or locker
|
|
9
|
+
// waits on 4_SHOP or 4_LOCKER, which Evri's page calls awaiting collection,
|
|
10
|
+
// and 5_SHOP is its collection.
|
|
9
11
|
const STAGES = new Map([
|
|
10
12
|
['0', 'registered'], ['1', 'registered'], ['2', 'in_transit'], ['3', 'in_transit'], ['4', 'returned'],
|
|
11
13
|
['4_COURIER', 'out_for_delivery'], ['5_COURIER', 'delivered'],
|
|
14
|
+
['4_SHOP', 'ready_for_pickup'], ['4_LOCKER', 'ready_for_pickup'], ['5_SHOP', 'delivered'],
|
|
12
15
|
]);
|
|
13
16
|
// Tracking points that their rail code files too broadly: a drop-off or
|
|
14
17
|
// collection is the acceptance, and a courier's failed visit stays on the
|
|
15
|
-
// out-for-delivery rail.
|
|
18
|
+
// out-for-delivery rail. The collection rails are never refined: a
|
|
19
|
+
// ParcelShop arrival there is the parcel waiting for its recipient.
|
|
16
20
|
const POINTS = new Map([
|
|
17
21
|
['ACCEPTED_BY_HERMES', 'accepted'], ['ARRIVED_PARCELSHOP', 'accepted'], ['COLLECTED_BY_EVRI', 'accepted'],
|
|
18
22
|
['QUADIENT_LOCKER_DROPOFF', 'accepted'], ['PROCESSING_RETURN', 'returned'],
|
|
@@ -22,6 +26,8 @@ function stageOf(stageCode, pointCode) {
|
|
|
22
26
|
const rail = STAGES.get(stageCode);
|
|
23
27
|
if (!rail)
|
|
24
28
|
return undefined;
|
|
29
|
+
if (rail === 'ready_for_pickup' || stageCode === '5_SHOP')
|
|
30
|
+
return rail;
|
|
25
31
|
if (rail === 'out_for_delivery' && pointCode.startsWith('NOT_DELIVERED_'))
|
|
26
32
|
return 'failed_attempt';
|
|
27
33
|
return POINTS.get(pointCode) ?? rail;
|
|
@@ -27,6 +27,9 @@
|
|
|
27
27
|
{ "code": "COURIER_REATTEMPT_CUSTOMER_NOT_AVAILABLE", "language": "en", "stage": "failed_attempt", "confirmedBy": "live", "firstSeen": "2026-10-08", "note": "On the courier rail: \"unable to deliver your parcel today\"." },
|
|
28
28
|
{ "code": "REDELIVER_WORKDAY_COURIER", "language": "en", "stage": "failed_attempt", "confirmedBy": "live", "firstSeen": "2026-10-08", "note": "Same wording as the missed visit." },
|
|
29
29
|
{ "code": "NOT_DELIVERED_BUSINESS_CLOSED", "language": "en", "stage": "failed_attempt", "confirmedBy": "live", "firstSeen": "2026-10-08", "note": "Every NOT_DELIVERED_ point on the courier rail is a failed attempt." },
|
|
30
|
+
{ "code": "4_SHOP", "language": "en", "stage": "ready_for_pickup", "confirmedBy": "bundle", "firstSeen": "2026-10-11", "note": "Rail code: evri.com's tracking page reads 4_SHOP as awaiting collection at a ParcelShop. Every point on this rail is ready for pickup." },
|
|
31
|
+
{ "code": "4_LOCKER", "language": "en", "stage": "ready_for_pickup", "confirmedBy": "bundle", "firstSeen": "2026-10-11", "note": "Rail code: awaiting collection from an Evri locker, opened with the recipient's code." },
|
|
32
|
+
{ "code": "5_SHOP", "language": "en", "stage": "delivered", "confirmedBy": "bundle", "firstSeen": "2026-10-11", "note": "Rail code: evri.com's page reads it as collected from the ParcelShop or locker; the wording becomes \"Delivered\"." },
|
|
30
33
|
{ "code": "DELIVERED", "language": "en", "stage": "delivered", "confirmedBy": "live", "firstSeen": "2026-10-08" },
|
|
31
34
|
{ "code": "DELIVERED_LETTERBOX", "language": "en", "stage": "delivered", "confirmedBy": "live", "firstSeen": "2026-10-08" },
|
|
32
35
|
{ "code": "DELIVERED_OUTBUILDING", "language": "en", "stage": "delivered", "confirmedBy": "live", "firstSeen": "2026-10-08" },
|
|
@@ -3,7 +3,7 @@ import { lookupBudget, recognizeFromLookup } from '../../core/adapter/index.js';
|
|
|
3
3
|
import { InvalidInputError, NotFoundError, SchemaError, UpstreamHttpError } from '../../core/errors/index.js';
|
|
4
4
|
import { clean, fetchBounded, parseJsonBytes, userAgentOf } from '../../core/transport/index.js';
|
|
5
5
|
import { isRecord } from '../../core/types.js';
|
|
6
|
-
import { COLLECTED_AT_PARCELSHOP, hermesGermanyMilestone, IGNORED_BOOKING_STATUS } from './status.js';
|
|
6
|
+
import { COLLECTED_AT_PARCELSHOP, hermesGermanyMilestone, IGNORED_BOOKING_STATUS, IN_PARCELBOX } from './status.js';
|
|
7
7
|
// Public recipient protocol inspected 2026-09-08:
|
|
8
8
|
// https://gcp-prd.my-deliveries.de/tnt/bundle/tnt-bundle-v2.js
|
|
9
9
|
// Separate from Hermes Einrichtungs-Service (myhes.de). The postcode-protected
|
|
@@ -113,9 +113,24 @@ export function parseHermesGermanyResponse(payload, trackingNumber) {
|
|
|
113
113
|
const atg = isRecord(parcel.atg) ? parcel.atg : {};
|
|
114
114
|
const sender = clean(atg.companyName) || null;
|
|
115
115
|
const eta = etaDate(parcel.eta ?? parcel.deliveryForecast ?? attributes.deliveredTimestamp);
|
|
116
|
-
|
|
116
|
+
// The tracking page offers the Hermes Box pickup code while
|
|
117
|
+
// `readyForParcelBoxCollection` is true: the parcel waits in the locker,
|
|
118
|
+
// even when the newest row or the delivered flag reads as a delivery.
|
|
119
|
+
// Once Hermes clears it, a box delivery that is still the newest row has
|
|
120
|
+
// been taken out.
|
|
121
|
+
const view = isRecord(parcel.viewParameters) ? parcel.viewParameters : {};
|
|
122
|
+
const boxReady = view.readyForParcelBoxCollection;
|
|
123
|
+
const latestCode = latest.event.provider_code;
|
|
124
|
+
const readsDelivered = !latest.metadata || latest.metadata.stage === 'delivered';
|
|
125
|
+
const inBox = latestCode === IN_PARCELBOX ? boxReady !== false : boxReady === true && readsDelivered;
|
|
126
|
+
const takenOut = latestCode === IN_PARCELBOX && boxReady === false;
|
|
127
|
+
if (inBox) {
|
|
128
|
+
latest.metadata = hermesGermanyMilestone(IN_PARCELBOX);
|
|
129
|
+
latest.event.stage = 'ready_for_pickup';
|
|
130
|
+
}
|
|
131
|
+
const deliveredByCode = latest.metadata?.status === 'delivered' || takenOut;
|
|
117
132
|
const deliveredByFlag = attributes.delivered === true;
|
|
118
|
-
const delivered = deliveredByCode || deliveredByFlag;
|
|
133
|
+
const delivered = !inBox && (deliveredByCode || deliveredByFlag);
|
|
119
134
|
if (!latest.metadata && !delivered) {
|
|
120
135
|
return {
|
|
121
136
|
status: 'unknown',
|
|
@@ -128,7 +143,7 @@ export function parseHermesGermanyResponse(payload, trackingNumber) {
|
|
|
128
143
|
};
|
|
129
144
|
}
|
|
130
145
|
const status = delivered ? 'delivered' : latest.metadata.status;
|
|
131
|
-
const stage = latest.metadata?.stage ?? (delivered ? 'delivered' : 'in_transit');
|
|
146
|
+
const stage = takenOut ? 'delivered' : latest.metadata?.stage ?? (delivered ? 'delivered' : 'in_transit');
|
|
132
147
|
const deliveredAt = delivered ? latest.event.time : null;
|
|
133
148
|
const pickupPoint = parcelShop(parcel, latest);
|
|
134
149
|
return {
|
|
@@ -21,6 +21,8 @@ export interface Milestone {
|
|
|
21
21
|
/** `parcelStatus` → milestone, read from the public recipient bundle on 2026-09-08. */
|
|
22
22
|
export declare const STATUSES: Record<string, Milestone>;
|
|
23
23
|
export declare const IGNORED_BOOKING_STATUS: Set<string>;
|
|
24
|
+
/** The code for a parcel put into a Hermes Box, which waits there for its recipient. */
|
|
25
|
+
export declare const IN_PARCELBOX = "DELIVERED_PARCELBOX";
|
|
24
26
|
/** Codes for a parcel the recipient collected at a ParcelShop. */
|
|
25
27
|
export declare const COLLECTED_AT_PARCELSHOP: Set<string>;
|
|
26
28
|
/** The milestone for a `parcelStatus`, or undefined when the code is not mapped. */
|
|
@@ -30,11 +30,13 @@ export const STATUSES = {
|
|
|
30
30
|
DELIVERED_DROPOFF: milestone('delivered', 'delivered', 'Delivered to the agreed safe place'),
|
|
31
31
|
DELIVERED_MAILBOX: milestone('delivered', 'delivered', 'Delivered to the mailbox'),
|
|
32
32
|
DELIVERED_PARCELSHOP: milestone('delivered', 'delivered', 'Collected at the ParcelShop'),
|
|
33
|
-
DELIVERED_PARCELBOX: milestone('delivered', 'delivered', 'Delivered to the parcel box'),
|
|
34
33
|
PICKED_UP_BY_RECIPIENT: milestone('delivered', 'delivered', 'Collected by the recipient'),
|
|
35
34
|
COLLECTED: milestone('delivered', 'delivered', 'Collected by the recipient'),
|
|
36
35
|
PARCELSHOP_COLLECTED_BY_CUSTOMER: milestone('delivered', 'delivered', 'Collected at the ParcelShop'),
|
|
37
36
|
INTERNATIONAL_DELIVERED: milestone('delivered', 'delivered', 'Delivered'),
|
|
37
|
+
// A Hermes Box is a pickup locker: the recipient takes the parcel out with
|
|
38
|
+
// a pickup code within 72 hours, or it goes back to the sender.
|
|
39
|
+
DELIVERED_PARCELBOX: milestone('ready_for_pickup', 'out_for_delivery', 'Ready for collection from the Hermes Box'),
|
|
38
40
|
READY_FOR_PICKUP: milestone('ready_for_pickup', 'out_for_delivery', 'Ready for collection'),
|
|
39
41
|
PARCELSHOP_ITEMS_FOR_COLLECTION: milestone('ready_for_pickup', 'out_for_delivery', 'Ready for collection at the ParcelShop'),
|
|
40
42
|
READY_FOR_COLLECTION: milestone('ready_for_pickup', 'out_for_delivery', 'Ready for collection'),
|
|
@@ -51,6 +53,8 @@ export const STATUSES = {
|
|
|
51
53
|
// Delivery preference bookings (a safe place, a ParcelShop) record the
|
|
52
54
|
// recipient's wish, not a movement, and must never move the parcel backwards.
|
|
53
55
|
export const IGNORED_BOOKING_STATUS = new Set(['EDL_BOOKED_DROPOFF', 'EDL_BOOKED_PARCELSHOP']);
|
|
56
|
+
/** The code for a parcel put into a Hermes Box, which waits there for its recipient. */
|
|
57
|
+
export const IN_PARCELBOX = 'DELIVERED_PARCELBOX';
|
|
54
58
|
/** Codes for a parcel the recipient collected at a ParcelShop. */
|
|
55
59
|
export const COLLECTED_AT_PARCELSHOP = new Set(['PARCELSHOP_COLLECTED_BY_CUSTOMER', 'DELIVERED_PARCELSHOP']);
|
|
56
60
|
/** The milestone for a `parcelStatus`, or undefined when the code is not mapped. */
|
|
@@ -184,10 +184,10 @@
|
|
|
184
184
|
},
|
|
185
185
|
{
|
|
186
186
|
"code": "DELIVERED_PARCELBOX",
|
|
187
|
-
"stage": "
|
|
187
|
+
"stage": "ready_for_pickup",
|
|
188
188
|
"confirmedBy": "official-doc",
|
|
189
189
|
"firstSeen": "2026-09-08",
|
|
190
|
-
"note": "
|
|
190
|
+
"note": "Put into a Hermes Box, a pickup locker. Hermes's Hermes Box terms (myhermes.de/content/pdf/hermesbox_nutzungsbedingungen.pdf) have the recipient generate a pickup code on the tracking page and take the parcel out within 72 hours, or it goes back to the sender."
|
|
191
191
|
},
|
|
192
192
|
{
|
|
193
193
|
"code": "PICKED_UP_BY_RECIPIENT",
|
package/dist/data/catalog.json
CHANGED
|
@@ -591,7 +591,7 @@ export declare const CARRIER_CATALOG: {
|
|
|
591
591
|
readonly params: readonly ["parcelNumber", "parcelnumber"];
|
|
592
592
|
}];
|
|
593
593
|
readonly detectionRules: readonly [{
|
|
594
|
-
readonly pattern: "^06
|
|
594
|
+
readonly pattern: "^06[01]\\d{11}$";
|
|
595
595
|
readonly confidence: "low";
|
|
596
596
|
readonly preferred: true;
|
|
597
597
|
}, {
|
package/dist/server/openapi.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "universal-parcel-scraper",
|
|
3
|
-
"version": "0.69.
|
|
3
|
+
"version": "0.69.7-main.899",
|
|
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",
|