universal-parcel-scraper 0.43.1-main.403 → 0.44.0-main.411
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 +4 -4
- package/data/detection-golden.json +43 -0
- package/dist/browser/scraper.js +18 -13
- package/dist/carriers/chronopost/carrier.json +4 -4
- package/dist/carriers/chronopost/numbers.json +56 -0
- package/dist/carriers/chronopost/parser.js +29 -3
- package/dist/carriers/dpd/adapter.js +39 -2
- package/dist/carriers/intl-post/carrier.json +1 -1
- package/dist/carriers/intl-post/numbers.json +18 -0
- package/dist/carriers/la-poste/carrier.json +1 -1
- package/dist/carriers/paack/adapter.d.ts +2 -2
- package/dist/carriers/paack/adapter.js +88 -44
- package/dist/carriers/paack/status.d.ts +10 -0
- package/dist/carriers/paack/status.js +27 -14
- package/dist/carriers/paack/statuses.json +44 -4
- package/dist/carriers/spring-gds/carrier.json +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/core/adapter/index.d.ts +1 -1
- package/dist/data/catalog.json +4 -4
- package/dist/facade/index.d.ts +1 -1
- package/dist/generated/catalog.d.ts +4 -4
- package/dist/generated/catalog.js +4 -4
- package/dist/providers/parcelsapp/adapter.d.ts +1 -1
- package/dist/providers/parcelsapp/adapter.js +11 -16
- package/dist/providers/parcelsapp/http.d.ts +2 -4
- package/dist/providers/parcelsapp/http.js +3 -13
- package/dist/providers/plan.d.ts +2 -2
- package/dist/providers/plan.js +17 -5
- package/dist/providers/shared/hints.d.ts +9 -1
- package/dist/providers/shared/hints.js +18 -3
- package/dist/providers/shared/scans.js +2 -0
- package/dist/providers/universal.d.ts +9 -9
- package/dist/providers/universal.js +11 -5
- package/dist/server/openapi.json +3 -2
- package/package.json +1 -1
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,216 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.
|
package/data/catalog.json
CHANGED
|
@@ -331,7 +331,7 @@
|
|
|
331
331
|
],
|
|
332
332
|
"detectionRules": [
|
|
333
333
|
{
|
|
334
|
-
"pattern": "^(?!(?:PZ|XU|XW|XY))[A-Z]{2}\\d{9}NL$",
|
|
334
|
+
"pattern": "^(?!(?:PZ|XR|XT|XU|XW|XY))[A-Z]{2}\\d{9}NL$",
|
|
335
335
|
"confidence": "high",
|
|
336
336
|
"checksum": "s10"
|
|
337
337
|
},
|
|
@@ -1090,7 +1090,7 @@
|
|
|
1090
1090
|
"confidence": "low"
|
|
1091
1091
|
},
|
|
1092
1092
|
{
|
|
1093
|
-
"pattern": "^(?!(?:PZ|XU|XW|XY))[A-Z]{2}\\d{9}FR$",
|
|
1093
|
+
"pattern": "^(?!(?:PZ|XR|XT|XU|XW|XY))[A-Z]{2}\\d{9}FR$",
|
|
1094
1094
|
"confidence": "high",
|
|
1095
1095
|
"checksum": "s10"
|
|
1096
1096
|
}
|
|
@@ -1131,7 +1131,7 @@
|
|
|
1131
1131
|
],
|
|
1132
1132
|
"detectionRules": [
|
|
1133
1133
|
{
|
|
1134
|
-
"pattern": "^(?:PZ|XU|XW|XY)\\d{9}[A-Z]{2}$",
|
|
1134
|
+
"pattern": "^(?:PZ|XR|XT|XU|XW|XY)\\d{9}[A-Z]{2}$",
|
|
1135
1135
|
"confidence": "high",
|
|
1136
1136
|
"checksum": "s10"
|
|
1137
1137
|
},
|
|
@@ -1746,7 +1746,7 @@
|
|
|
1746
1746
|
"linkRules": [],
|
|
1747
1747
|
"detectionRules": [
|
|
1748
1748
|
{
|
|
1749
|
-
"pattern": "^(?![CL][A-Z]\\d{9}DE$)(?!AS\\d{9}US$)(?!(?:PZ|XU|XW|XY))[A-Z]{2}\\d{9}(?!CA$|CH$|FR$|IN$|NL$|GB$|IE$|BE$|PT$|NZ$|SG$|JP$|KR$|TH$|HK$|MY$|CN$|NO$|BR$|ES$|SE$|DK$|AT$|YP$|CL$)[A-Z]{2}$",
|
|
1749
|
+
"pattern": "^(?![CL][A-Z]\\d{9}DE$)(?!AS\\d{9}US$)(?!(?:PZ|XR|XT|XU|XW|XY))[A-Z]{2}\\d{9}(?!CA$|CH$|FR$|IN$|NL$|GB$|IE$|BE$|PT$|NZ$|SG$|JP$|KR$|TH$|HK$|MY$|CN$|NO$|BR$|ES$|SE$|DK$|AT$|YP$|TS$|JF$|CL$)[A-Z]{2}$",
|
|
1750
1750
|
"confidence": "high",
|
|
1751
1751
|
"checksum": "s10"
|
|
1752
1752
|
}
|
|
@@ -10876,6 +10876,17 @@
|
|
|
10876
10876
|
"thailand-post"
|
|
10877
10877
|
]
|
|
10878
10878
|
},
|
|
10879
|
+
{
|
|
10880
|
+
"input": "RR123456785TS",
|
|
10881
|
+
"carrier": "unknown",
|
|
10882
|
+
"confidence": "low",
|
|
10883
|
+
"candidates": [
|
|
10884
|
+
"chronopost"
|
|
10885
|
+
],
|
|
10886
|
+
"asked": [
|
|
10887
|
+
"chronopost"
|
|
10888
|
+
]
|
|
10889
|
+
},
|
|
10879
10890
|
{
|
|
10880
10891
|
"input": "RR123456789PL",
|
|
10881
10892
|
"carrier": "unknown",
|
|
@@ -11585,6 +11596,38 @@
|
|
|
11585
11596
|
"confidence": "none",
|
|
11586
11597
|
"candidates": []
|
|
11587
11598
|
},
|
|
11599
|
+
{
|
|
11600
|
+
"input": "XR123456785TS",
|
|
11601
|
+
"carrier": "chronopost",
|
|
11602
|
+
"confidence": "high",
|
|
11603
|
+
"candidates": [
|
|
11604
|
+
"chronopost"
|
|
11605
|
+
]
|
|
11606
|
+
},
|
|
11607
|
+
{
|
|
11608
|
+
"input": "XT010847192TS",
|
|
11609
|
+
"carrier": "chronopost",
|
|
11610
|
+
"confidence": "high",
|
|
11611
|
+
"candidates": [
|
|
11612
|
+
"chronopost"
|
|
11613
|
+
]
|
|
11614
|
+
},
|
|
11615
|
+
{
|
|
11616
|
+
"input": "XT123456785TS",
|
|
11617
|
+
"carrier": "chronopost",
|
|
11618
|
+
"confidence": "high",
|
|
11619
|
+
"candidates": [
|
|
11620
|
+
"chronopost"
|
|
11621
|
+
]
|
|
11622
|
+
},
|
|
11623
|
+
{
|
|
11624
|
+
"input": "XT335285235FR",
|
|
11625
|
+
"carrier": "chronopost",
|
|
11626
|
+
"confidence": "high",
|
|
11627
|
+
"candidates": [
|
|
11628
|
+
"chronopost"
|
|
11629
|
+
]
|
|
11630
|
+
},
|
|
11588
11631
|
{
|
|
11589
11632
|
"input": "XU123456785FR",
|
|
11590
11633
|
"carrier": "chronopost",
|
package/dist/browser/scraper.js
CHANGED
|
@@ -423,7 +423,7 @@ var ve = {
|
|
|
423
423
|
}
|
|
424
424
|
],
|
|
425
425
|
detectionRules: [{
|
|
426
|
-
pattern: "^(?!(?:PZ|XU|XW|XY))[A-Z]{2}\\d{9}NL$",
|
|
426
|
+
pattern: "^(?!(?:PZ|XR|XT|XU|XW|XY))[A-Z]{2}\\d{9}NL$",
|
|
427
427
|
confidence: "high",
|
|
428
428
|
checksum: "s10"
|
|
429
429
|
}, {
|
|
@@ -966,7 +966,7 @@ var ve = {
|
|
|
966
966
|
confidence: "low"
|
|
967
967
|
},
|
|
968
968
|
{
|
|
969
|
-
pattern: "^(?!(?:PZ|XU|XW|XY))[A-Z]{2}\\d{9}FR$",
|
|
969
|
+
pattern: "^(?!(?:PZ|XR|XT|XU|XW|XY))[A-Z]{2}\\d{9}FR$",
|
|
970
970
|
confidence: "high",
|
|
971
971
|
checksum: "s10"
|
|
972
972
|
}
|
|
@@ -1001,7 +1001,7 @@ var ve = {
|
|
|
1001
1001
|
}],
|
|
1002
1002
|
detectionRules: [
|
|
1003
1003
|
{
|
|
1004
|
-
pattern: "^(?:PZ|XU|XW|XY)\\d{9}[A-Z]{2}$",
|
|
1004
|
+
pattern: "^(?:PZ|XR|XT|XU|XW|XY)\\d{9}[A-Z]{2}$",
|
|
1005
1005
|
confidence: "high",
|
|
1006
1006
|
checksum: "s10"
|
|
1007
1007
|
},
|
|
@@ -1472,7 +1472,7 @@ var ve = {
|
|
|
1472
1472
|
trackingSiteName: "17TRACK",
|
|
1473
1473
|
linkRules: [],
|
|
1474
1474
|
detectionRules: [{
|
|
1475
|
-
pattern: "^(?![CL][A-Z]\\d{9}DE$)(?!AS\\d{9}US$)(?!(?:PZ|XU|XW|XY))[A-Z]{2}\\d{9}(?!CA$|CH$|FR$|IN$|NL$|GB$|IE$|BE$|PT$|NZ$|SG$|JP$|KR$|TH$|HK$|MY$|CN$|NO$|BR$|ES$|SE$|DK$|AT$|YP$|CL$)[A-Z]{2}$",
|
|
1475
|
+
pattern: "^(?![CL][A-Z]\\d{9}DE$)(?!AS\\d{9}US$)(?!(?:PZ|XR|XT|XU|XW|XY))[A-Z]{2}\\d{9}(?!CA$|CH$|FR$|IN$|NL$|GB$|IE$|BE$|PT$|NZ$|SG$|JP$|KR$|TH$|HK$|MY$|CN$|NO$|BR$|ES$|SE$|DK$|AT$|YP$|TS$|JF$|CL$)[A-Z]{2}$",
|
|
1476
1476
|
confidence: "high",
|
|
1477
1477
|
checksum: "s10"
|
|
1478
1478
|
}]
|
|
@@ -10758,19 +10758,24 @@ var lc = {
|
|
|
10758
10758
|
unknown: 2,
|
|
10759
10759
|
empty: 3,
|
|
10760
10760
|
excluded: 4
|
|
10761
|
-
}
|
|
10762
|
-
function
|
|
10761
|
+
};
|
|
10762
|
+
function uc(e, t) {
|
|
10763
|
+
let n = lc[e?.[t] ?? "unknown"];
|
|
10764
|
+
return t === "UPU" ? lc.unknown : t === "Postal Ninja" ? Math.max(n, lc.empty) : n;
|
|
10765
|
+
}
|
|
10766
|
+
var dc = (e) => e === "UPU" ? 2 : +(e === "Postal Ninja");
|
|
10767
|
+
function fc(e, t) {
|
|
10763
10768
|
let n = (e) => e !== "UPU" && t?.[e] === "excluded";
|
|
10764
|
-
return (e.some((e) => e !== "UPU" && !n(e)) ? e.filter((e) => !n(e)) : [...e]).sort((n, r) =>
|
|
10769
|
+
return (e.some((e) => e !== "UPU" && !n(e)) ? e.filter((e) => !n(e)) : [...e]).sort((n, r) => dc(n) - dc(r) || uc(t, n) - uc(t, r) || Number(oc.has(n)) - Number(oc.has(r)) || e.indexOf(n) - e.indexOf(r));
|
|
10765
10770
|
}
|
|
10766
|
-
function
|
|
10771
|
+
function pc(e = {}) {
|
|
10767
10772
|
let { trackingNumber: t, enablePostalNinja: n = !1 } = e, r = (n ? [
|
|
10768
10773
|
"ParcelsApp",
|
|
10769
10774
|
"Ship24",
|
|
10770
|
-
"Postal Ninja",
|
|
10771
10775
|
"17TRACK",
|
|
10776
|
+
"Postal Ninja",
|
|
10772
10777
|
"UPU"
|
|
10773
|
-
] : [...ac]).filter((e) => e !== "UPU" || t === void 0 || b(t)), i = (e.carriers ?? []).find((e) => ic(e)) ?? null, a = ic(i), o =
|
|
10778
|
+
] : [...ac]).filter((e) => e !== "UPU" || t === void 0 || b(t)), i = (e.carriers ?? []).find((e) => ic(e)) ?? null, a = ic(i), o = fc(r, a), s = cc(t);
|
|
10774
10779
|
return {
|
|
10775
10780
|
sources: s ? [s, ...o.filter((e) => e !== s)] : o,
|
|
10776
10781
|
carrier: i,
|
|
@@ -10778,11 +10783,11 @@ function fc(e = {}) {
|
|
|
10778
10783
|
rank: (e) => uc(a, e)
|
|
10779
10784
|
};
|
|
10780
10785
|
}
|
|
10781
|
-
function
|
|
10782
|
-
return
|
|
10786
|
+
function mc(e = !1, t) {
|
|
10787
|
+
return pc({
|
|
10783
10788
|
trackingNumber: t,
|
|
10784
10789
|
enablePostalNinja: e
|
|
10785
10790
|
}).sources;
|
|
10786
10791
|
}
|
|
10787
10792
|
//#endregion
|
|
10788
|
-
export { Se as AMAZON_NUMBER_PATTERN, Ne as AUTOMATIC_CARRIER_IDS, oc as BROWSER_SOURCES, f as BudgetExceededError, Ve as CARRIERS, ve as CARRIER_CATALOG, v as CARRIER_DEFINITIONS, ye as CARRIER_IDS, Pe as CARRIER_NAMES, t as CarrierError, a as ChallengeError, No as EXPLICIT_OFFSET_PATTERN, r as IndeterminateError, l as InputRequiredError, u as InvalidInputError, ys as MAX_RECOGNITIONS, s as MaintenanceError, i as NoHistoryError, n as NotFoundError, o as RateLimitedError, Ue as SELECTABLE_CARRIERS, xe as STAGES, c as SchemaError, St as TRACKING_CANDIDATE_PATTERNS, Fe as TRACKING_LINK_RULES, d as TransportError, ac as UNIVERSAL_SOURCES, m as UpstreamHttpError, ee as UpstreamNetworkError, je as activeRequirements, Te as amazonMarketplace, Ee as amazonOrdersUrl, De as amazonShippingOrigin, Oe as amazonShippingUrl, os as calendarDay, es as canadaProvinceTimeZone, Ae as carrierAdapter, y as carrierDefinition, h as carrierErrorKind, He as carrierInfo, Ie as carrierLinkRules, We as carrierRequirements, ke as carrierTimezone, _t as checksumRejections, us as classifyStage, Ut as classifyWording, Jo as countryCode, Yo as countryTimeZone, Is as deliveryHandoff, vt as detectCarrier, x as detectCarrierMatch, is as epochMillisTime, as as epochSecondsTime, ne as errorTypeOf, Rt as eventPoint, Fo as explicitOffsetTime, hs as failureHint, ms as failureKind, _e as formatTrackingNumber, Fs as hasDirectHandoffAdapter, ds as inferStage, Ce as isAmazonTrackingNumber, ge as isPlanzerSharedTrackingNumber, Xe as isValidCorreosSpainCheckLetter, ae as isValidDhlExpressWaybill, $e as isValidDpdParcelNumber, at as isValidGlsParcelNumber, ot as isValidHermesParcelNumber, he as isValidMondialRelayBarcode, pe as isValidPocztaPolskaBarcode, b as isValidS10TrackingNumber, me as isValidSscc, se as isValidTntConsignmentNumber, dt as isValidUspsPackageBarcode, Lo as isoTime, Et as keywordNumberInText, Ht as languageStageStatus, qe as localizedCarrierUrl, Le as matchesDomain, zo as mislabeledLocalTime, Bo as mislabeledWallTime, Ys as normalizeCarrierInputs, zt as normalizeCarrierResult, Xs as normalizeDeliveryPostcode, _ as normalizeTrackingNumber,
|
|
10793
|
+
export { Se as AMAZON_NUMBER_PATTERN, Ne as AUTOMATIC_CARRIER_IDS, oc as BROWSER_SOURCES, f as BudgetExceededError, Ve as CARRIERS, ve as CARRIER_CATALOG, v as CARRIER_DEFINITIONS, ye as CARRIER_IDS, Pe as CARRIER_NAMES, t as CarrierError, a as ChallengeError, No as EXPLICIT_OFFSET_PATTERN, r as IndeterminateError, l as InputRequiredError, u as InvalidInputError, ys as MAX_RECOGNITIONS, s as MaintenanceError, i as NoHistoryError, n as NotFoundError, o as RateLimitedError, Ue as SELECTABLE_CARRIERS, xe as STAGES, c as SchemaError, St as TRACKING_CANDIDATE_PATTERNS, Fe as TRACKING_LINK_RULES, d as TransportError, ac as UNIVERSAL_SOURCES, m as UpstreamHttpError, ee as UpstreamNetworkError, je as activeRequirements, Te as amazonMarketplace, Ee as amazonOrdersUrl, De as amazonShippingOrigin, Oe as amazonShippingUrl, os as calendarDay, es as canadaProvinceTimeZone, Ae as carrierAdapter, y as carrierDefinition, h as carrierErrorKind, He as carrierInfo, Ie as carrierLinkRules, We as carrierRequirements, ke as carrierTimezone, _t as checksumRejections, us as classifyStage, Ut as classifyWording, Jo as countryCode, Yo as countryTimeZone, Is as deliveryHandoff, vt as detectCarrier, x as detectCarrierMatch, is as epochMillisTime, as as epochSecondsTime, ne as errorTypeOf, Rt as eventPoint, Fo as explicitOffsetTime, hs as failureHint, ms as failureKind, _e as formatTrackingNumber, Fs as hasDirectHandoffAdapter, ds as inferStage, Ce as isAmazonTrackingNumber, ge as isPlanzerSharedTrackingNumber, Xe as isValidCorreosSpainCheckLetter, ae as isValidDhlExpressWaybill, $e as isValidDpdParcelNumber, at as isValidGlsParcelNumber, ot as isValidHermesParcelNumber, he as isValidMondialRelayBarcode, pe as isValidPocztaPolskaBarcode, b as isValidS10TrackingNumber, me as isValidSscc, se as isValidTntConsignmentNumber, dt as isValidUspsPackageBarcode, Lo as isoTime, Et as keywordNumberInText, Ht as languageStageStatus, qe as localizedCarrierUrl, Le as matchesDomain, zo as mislabeledLocalTime, Bo as mislabeledWallTime, Ys as normalizeCarrierInputs, zt as normalizeCarrierResult, Xs as normalizeDeliveryPostcode, _ as normalizeTrackingNumber, fc as orderUniversalSources, Mt as parseTrackingInput, cc as priorityUniversalSource, xs as recognitionCandidates, bs as recognitionNumberShape, Cs as recognizeAll, Ct as recognizedNumberInText, rs as regionHasTown, Me as requiredRequirements, Ge as requirementSatisfied, we as requiresAmazonAccount, ps as resolveResult, ls as resultHasUpdate, cs as resultStage, g as retryAfterMsOf, Uo as settleGuessedClocks, Ts as settleRecognition, Vo as sharedClockZone, fs as stageSource, st as supportsSwissPostHandoff, Xo as timeZoneCountry, ts as townKey, Vt as trackingLanguageStage, ze as trackingLink, Re as trackingNumberForLink, Ke as tracksAutomatically, pc as universalPlan, sc as universalSourceBudget, mc as universalSources, Qo as usStateTimeZone, bt as validTrackingNumber, Ks as validateDachserTrackingUrl, Gs as validatePlanzerSharedUrl, Wt as wordingStage, Io as zonedTime };
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"capabilities": [
|
|
29
29
|
"history",
|
|
30
30
|
"location",
|
|
31
|
+
"eta",
|
|
31
32
|
"provider_code",
|
|
32
33
|
"delivery_tracking_number"
|
|
33
34
|
],
|
|
@@ -43,11 +44,10 @@
|
|
|
43
44
|
"scraped": [
|
|
44
45
|
"status",
|
|
45
46
|
"history",
|
|
46
|
-
"location"
|
|
47
|
-
],
|
|
48
|
-
"discarded": [
|
|
47
|
+
"location",
|
|
49
48
|
"eta"
|
|
50
49
|
],
|
|
50
|
+
"discarded": [],
|
|
51
51
|
"unavailable": []
|
|
52
52
|
},
|
|
53
53
|
"links": [
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"detection": [
|
|
68
68
|
{
|
|
69
69
|
"id": "chronopost-1",
|
|
70
|
-
"pattern": "^(?:PZ|XU|XW|XY)\\d{9}[A-Z]{2}$",
|
|
70
|
+
"pattern": "^(?:PZ|XR|XT|XU|XW|XY)\\d{9}[A-Z]{2}$",
|
|
71
71
|
"confidence": "high",
|
|
72
72
|
"checksum": "s10"
|
|
73
73
|
},
|
|
@@ -109,6 +109,62 @@
|
|
|
109
109
|
},
|
|
110
110
|
"note": "Made-up number that follows the published format."
|
|
111
111
|
},
|
|
112
|
+
{
|
|
113
|
+
"number": "XR123456785TS",
|
|
114
|
+
"role": "shipment",
|
|
115
|
+
"evidence": "synthetic",
|
|
116
|
+
"derivedFrom": "invented",
|
|
117
|
+
"shapeConfirmed": "2026-10-08",
|
|
118
|
+
"expect": {
|
|
119
|
+
"carrier": "chronopost",
|
|
120
|
+
"confidence": "high"
|
|
121
|
+
},
|
|
122
|
+
"note": "Made-up number in the shape of Chronopost parcels whose S10 check digit passes and whose TS suffix is no country."
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"number": "XT123456785TS",
|
|
126
|
+
"role": "shipment",
|
|
127
|
+
"evidence": "synthetic",
|
|
128
|
+
"derivedFrom": "invented",
|
|
129
|
+
"shapeConfirmed": "2026-10-08",
|
|
130
|
+
"expect": {
|
|
131
|
+
"carrier": "chronopost",
|
|
132
|
+
"confidence": "high"
|
|
133
|
+
},
|
|
134
|
+
"note": "Made-up number in the shape of Chronopost parcels whose S10 check digit passes and whose TS suffix is no country."
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
"number": "XT010847192TS",
|
|
138
|
+
"role": "shipment",
|
|
139
|
+
"evidence": "public_shipment_report",
|
|
140
|
+
"source": {
|
|
141
|
+
"url": "https://suivi-de-colis.fr/chronopost/",
|
|
142
|
+
"access": "full_page",
|
|
143
|
+
"date": "2020-06-04",
|
|
144
|
+
"dateKind": "comment publication"
|
|
145
|
+
},
|
|
146
|
+
"expect": {
|
|
147
|
+
"carrier": "chronopost",
|
|
148
|
+
"confidence": "high"
|
|
149
|
+
},
|
|
150
|
+
"note": "Parcel number a reader posted under a third-party Chronopost tracking page; the attribution was not independently verified."
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
"number": "XT335285235FR",
|
|
154
|
+
"role": "shipment",
|
|
155
|
+
"evidence": "public_shipment_report",
|
|
156
|
+
"source": {
|
|
157
|
+
"url": "https://suivi-de-colis.fr/chronopost/",
|
|
158
|
+
"access": "full_page",
|
|
159
|
+
"date": "2020-06-18",
|
|
160
|
+
"dateKind": "comment publication"
|
|
161
|
+
},
|
|
162
|
+
"expect": {
|
|
163
|
+
"carrier": "chronopost",
|
|
164
|
+
"confidence": "high"
|
|
165
|
+
},
|
|
166
|
+
"note": "Parcel number a reader posted under a third-party Chronopost tracking page, describing a Chronopost agency; the attribution was not independently verified."
|
|
167
|
+
},
|
|
112
168
|
{
|
|
113
169
|
"number": "XW295445484JF",
|
|
114
170
|
"role": "shipment",
|
|
@@ -2,7 +2,7 @@ import { DateTime } from 'luxon';
|
|
|
2
2
|
import { isValidDpdParcelNumber } from '../../core/detection/dpd.js';
|
|
3
3
|
import { ChallengeError, IndeterminateError, InvalidInputError, NotFoundError, SchemaError } from '../../core/errors/index.js';
|
|
4
4
|
import { languageStageStatus } from '../../core/status/index.js';
|
|
5
|
-
import { countryCode, explicitOffsetTime } from '../../core/time/index.js';
|
|
5
|
+
import { calendarDay, countryCode, explicitOffsetTime } from '../../core/time/index.js';
|
|
6
6
|
import { clean } from '../../core/transport/index.js';
|
|
7
7
|
import { xmlDocument } from '../../core/transport/xml.js';
|
|
8
8
|
import { chronopostStage, isChronopostNotification } from './status.js';
|
|
@@ -45,6 +45,18 @@ function clock(raw) {
|
|
|
45
45
|
}
|
|
46
46
|
return raw ? { provider_time_text: clean(raw, 64) } : {};
|
|
47
47
|
}
|
|
48
|
+
/** The recipient's chosen redelivery day, printed as dd/MM/yyyy. */
|
|
49
|
+
function redeliveryDay(value) {
|
|
50
|
+
const match = /^(\d{2})\/(\d{2})\/(\d{4})$/.exec(value);
|
|
51
|
+
return match ? calendarDay(Number(match[3]), Number(match[2]), Number(match[1])) : null;
|
|
52
|
+
}
|
|
53
|
+
/** The calendar day of a scan's own clock, or '' without one. */
|
|
54
|
+
const clockDay = (event) => {
|
|
55
|
+
const clock = event.time ?? event.local_time;
|
|
56
|
+
return typeof clock === 'string' ? /^\d{4}-\d{2}-\d{2}/.exec(clock)?.[0] ?? '' : '';
|
|
57
|
+
};
|
|
58
|
+
/** Stages on the way to a planned delivery. */
|
|
59
|
+
const ONGOING = new Set(['pending', 'registered', 'accepted', 'in_transit', 'customs', 'out_for_delivery']);
|
|
48
60
|
/** Keep operational places, never the delivery-point address or shop contact block. */
|
|
49
61
|
function location(office, place) {
|
|
50
62
|
if (/^[\p{L}\p{M} .,'’()-]{1,100} - [A-Z]{2}(?: \(depot \d{1,6}\))?$/u.test(place))
|
|
@@ -95,11 +107,14 @@ export function parseChronopostTrackingXml(xml, rawNumber) {
|
|
|
95
107
|
throw new SchemaError('Chronopost', 'Chronopost returned an empty tracking event');
|
|
96
108
|
const extras = children(scan, 'infoCompList');
|
|
97
109
|
let place = '';
|
|
110
|
+
let redelivery;
|
|
98
111
|
for (const extra of extras) {
|
|
99
112
|
const name = scalar(extra, 'name');
|
|
100
113
|
const value = scalar(extra, 'value');
|
|
101
114
|
if (name === 'Lieu')
|
|
102
115
|
place = value;
|
|
116
|
+
if (name === 'Date de relivraison')
|
|
117
|
+
redelivery = redeliveryDay(value);
|
|
103
118
|
if (name === 'Numéro partenaire') {
|
|
104
119
|
const match = /^(?:GEO\/)?([A-Z0-9]{4,40})$/.exec(value.toUpperCase());
|
|
105
120
|
if (match) {
|
|
@@ -122,7 +137,7 @@ export function parseChronopostTrackingXml(xml, rawNumber) {
|
|
|
122
137
|
...(code ? { provider_code: code } : {}),
|
|
123
138
|
};
|
|
124
139
|
const mapped = chronopostStage(code, description);
|
|
125
|
-
return { event, mapped, notification: isChronopostNotification(description), index };
|
|
140
|
+
return { event, mapped, notification: isChronopostNotification(description), index, redelivery };
|
|
126
141
|
});
|
|
127
142
|
// The operation returns oldest first. Reorder only when all scan clocks
|
|
128
143
|
// establish instants, so mixed local clocks cannot create a guessed order.
|
|
@@ -141,6 +156,17 @@ export function parseChronopostTrackingXml(xml, rawNumber) {
|
|
|
141
156
|
scan.event.stage = scan.notification ? stage : scan.mapped.stage;
|
|
142
157
|
scan.event.stage_source = scan.notification ? 'none' : scan.mapped.source;
|
|
143
158
|
}
|
|
159
|
+
// The newest instruction that names a redelivery day decides, even when its
|
|
160
|
+
// day is unreadable. A later scan that is not progress towards that delivery,
|
|
161
|
+
// or that falls on a later day, ends it.
|
|
162
|
+
let promised = null;
|
|
163
|
+
for (const scan of projected) {
|
|
164
|
+
if (scan.redelivery !== undefined)
|
|
165
|
+
promised = scan.redelivery;
|
|
166
|
+
else if (promised && (clockDay(scan.event) > promised || (!scan.notification && !ONGOING.has(scan.mapped.stage)))) {
|
|
167
|
+
promised = null;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
144
170
|
const events = projected.reverse().slice(0, 100).map(scan => scan.event);
|
|
145
171
|
const latest = events[0];
|
|
146
172
|
const reference = references.size === 1 ? [...references][0] : undefined;
|
|
@@ -154,7 +180,7 @@ export function parseChronopostTrackingXml(xml, rawNumber) {
|
|
|
154
180
|
current_stage_source: stageSource,
|
|
155
181
|
last_status_text: latest.description,
|
|
156
182
|
last_update: latest.time ?? null,
|
|
157
|
-
expected_delivery: null,
|
|
183
|
+
expected_delivery: ['delivered', 'returned', 'ready_for_pickup'].includes(stage) ? null : promised,
|
|
158
184
|
...(reference ? { delivery_tracking_number: reference } : {}),
|
|
159
185
|
...(partner ? { delivery_carrier: partner } : {}),
|
|
160
186
|
...(destination ? { destination_country: destination } : {}),
|
|
@@ -6,7 +6,7 @@ import { DELIVERY_POSTCODE, deliveryPostcodeText } from '../../core/catalog/post
|
|
|
6
6
|
import { dpdParcelNumber } from '../../core/detection/dpd.js';
|
|
7
7
|
import { BudgetExceededError, carrierErrorKind, ChallengeError, IndeterminateError, InvalidInputError, NotFoundError, SchemaError, UpstreamHttpError } from '../../core/errors/index.js';
|
|
8
8
|
import { runSteps } from '../../core/runner/index.js';
|
|
9
|
-
import { isoTime, explicitOffsetTime, zonedTime } from '../../core/time/index.js';
|
|
9
|
+
import { calendarDay, isoTime, explicitOffsetTime, zonedTime } from '../../core/time/index.js';
|
|
10
10
|
import { TrawlClient, decodeText, fetchBounded, parseJsonBytes, userAgentOf } from '../../core/transport/index.js';
|
|
11
11
|
import { isRecord } from '../../core/types.js';
|
|
12
12
|
import { API_LABELS, PROOF_OF_DELIVERY_SCAN, apiStage, apiStatus, scanStage, wordingStatus, } from './status.js';
|
|
@@ -254,6 +254,32 @@ function expectedDelivery(payload) {
|
|
|
254
254
|
return `${date} ${from}–${to}`;
|
|
255
255
|
return from || to ? `${date} ${from || to}` : date;
|
|
256
256
|
}
|
|
257
|
+
/** Scans in which DPD states a delivery day: an estimate, a changed date or the email notice. */
|
|
258
|
+
const DELIVERY_NOTICE = /\b(?:estimated to be|will be) delivered on\b/i;
|
|
259
|
+
const MONTHS = ['january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september',
|
|
260
|
+
'october', 'november', 'december'];
|
|
261
|
+
/** A notice clock as 24-hour `HH:mm`; it stays the recipient's wall clock. */
|
|
262
|
+
function noticeClock(text) {
|
|
263
|
+
const match = /^(\d{1,2}):([0-5]\d)(?:\s*([AP])M)?$/i.exec(text ?? '');
|
|
264
|
+
if (!match)
|
|
265
|
+
return null;
|
|
266
|
+
const hour = Number(match[1]);
|
|
267
|
+
const half = match[3]?.toUpperCase();
|
|
268
|
+
if (half ? hour < 1 || hour > 12 : hour > 23)
|
|
269
|
+
return null;
|
|
270
|
+
return `${String(half ? (hour % 12) + (half === 'P' ? 12 : 0) : hour).padStart(2, '0')}:${match[2]}`;
|
|
271
|
+
}
|
|
272
|
+
/** "…delivered on: Thursday, July 16, 2026", optionally followed by "between 9:00 AM and 12:00 PM". */
|
|
273
|
+
function noticeEstimate(text) {
|
|
274
|
+
const match = /\bdelivered on:?\s+(?:[a-z]+,\s*)?([a-z]+)\s+(\d{1,2}),\s*(\d{4})(?:\s+between\s+(\d{1,2}:\d{2}(?:\s*[AP]M)?)\s+and\s+(\d{1,2}:\d{2}(?:\s*[AP]M)?))?/i.exec(text);
|
|
275
|
+
const month = match ? MONTHS.indexOf(match[1].toLowerCase()) + 1 : 0;
|
|
276
|
+
const day = match && month ? calendarDay(Number(match[3]), month, Number(match[2])) : null;
|
|
277
|
+
if (!match || !day)
|
|
278
|
+
return null;
|
|
279
|
+
const from = noticeClock(match[4]);
|
|
280
|
+
const to = noticeClock(match[5]);
|
|
281
|
+
return from && to && from <= to ? `${day} ${from}–${to}` : day;
|
|
282
|
+
}
|
|
257
283
|
/**
|
|
258
284
|
* Newest scan first. The guest API has listed a parcel's scans oldest first,
|
|
259
285
|
* and the summary and freshness watermark read the first event. Equal or
|
|
@@ -358,6 +384,16 @@ export function parseDPDTrackingApi(payload, trackingNumber, postcodeVerified) {
|
|
|
358
384
|
const deliveredAt = status === 'delivered'
|
|
359
385
|
? events.find((event) => event.stage === 'delivered')?.time
|
|
360
386
|
: undefined;
|
|
387
|
+
// Only the newest notice counts, so an undated or unreadable one leaves no
|
|
388
|
+
// older day behind. A day that a later scan has passed, or a parcel waiting
|
|
389
|
+
// at a pickup point, ends the notice. The payload's own estimate wins; a
|
|
390
|
+
// notice for that same day can only add the email's window.
|
|
391
|
+
const notice = events.find((event) => DELIVERY_NOTICE.test(event.description ?? ''));
|
|
392
|
+
const announced = notice ? noticeEstimate(notice.description ?? '') : null;
|
|
393
|
+
const newestDay = /^\d{4}-\d{2}-\d{2}/.exec(events[0]?.time ?? '')?.[0];
|
|
394
|
+
const forecast = announced && stage !== 'ready_for_pickup' && !(newestDay && announced.slice(0, 10) < newestDay)
|
|
395
|
+
? announced : null;
|
|
396
|
+
const listed = expectedDelivery(payload);
|
|
361
397
|
// Never projected: `receiver` (name, contact, address, geoPosition), the
|
|
362
398
|
// sender's id and address, `customerReference1/2`, `gttsZipCode`, `podUrl`
|
|
363
399
|
// and `product`, which holds the recipient's delivery preference.
|
|
@@ -366,7 +402,8 @@ export function parseDPDTrackingApi(payload, trackingNumber, postcodeVerified) {
|
|
|
366
402
|
...(stage ? { current_stage: stage } : {}),
|
|
367
403
|
last_status_text: statusText,
|
|
368
404
|
last_update: events[0]?.time || apiEventTime(current.eventDateAndTime, '', current.eventDateAndTimeZoneId) || null,
|
|
369
|
-
expected_delivery: status === 'delivered' || status === 'exception' ? null
|
|
405
|
+
expected_delivery: status === 'delivered' || status === 'exception' ? null
|
|
406
|
+
: listed && listed !== forecast?.slice(0, 10) ? listed : forecast ?? listed,
|
|
370
407
|
events,
|
|
371
408
|
source: 'mydpd_guest_api',
|
|
372
409
|
delivery_date: optionalText(payload.deliveryDate),
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"detection": [
|
|
39
39
|
{
|
|
40
40
|
"id": "intl-post-1",
|
|
41
|
-
"pattern": "^(?![CL][A-Z]\\d{9}DE$)(?!AS\\d{9}US$)(?!(?:PZ|XU|XW|XY))[A-Z]{2}\\d{9}(?!CA$|CH$|FR$|IN$|NL$|GB$|IE$|BE$|PT$|NZ$|SG$|JP$|KR$|TH$|HK$|MY$|CN$|NO$|BR$|ES$|SE$|DK$|AT$|YP$|CL$)[A-Z]{2}$",
|
|
41
|
+
"pattern": "^(?![CL][A-Z]\\d{9}DE$)(?!AS\\d{9}US$)(?!(?:PZ|XR|XT|XU|XW|XY))[A-Z]{2}\\d{9}(?!CA$|CH$|FR$|IN$|NL$|GB$|IE$|BE$|PT$|NZ$|SG$|JP$|KR$|TH$|HK$|MY$|CN$|NO$|BR$|ES$|SE$|DK$|AT$|YP$|TS$|JF$|CL$)[A-Z]{2}$",
|
|
42
42
|
"confidence": "high",
|
|
43
43
|
"checksum": "s10"
|
|
44
44
|
}
|
|
@@ -24,6 +24,24 @@
|
|
|
24
24
|
"confidence": "high"
|
|
25
25
|
},
|
|
26
26
|
"note": "Made-up number that follows the published format."
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"number": "RR123456785TS",
|
|
30
|
+
"role": "negative",
|
|
31
|
+
"evidence": "synthetic",
|
|
32
|
+
"derivedFrom": "invented",
|
|
33
|
+
"shapeConfirmed": "2026-10-08",
|
|
34
|
+
"expect": {
|
|
35
|
+
"carrier": "unknown",
|
|
36
|
+
"confidence": "low",
|
|
37
|
+
"candidates": [
|
|
38
|
+
"chronopost"
|
|
39
|
+
],
|
|
40
|
+
"not": [
|
|
41
|
+
"intl-post"
|
|
42
|
+
]
|
|
43
|
+
},
|
|
44
|
+
"note": "Invented number whose S10 check digit passes but whose TS suffix is a Chronopost code rather than an issuing country, so it is not international mail."
|
|
27
45
|
}
|
|
28
46
|
]
|
|
29
47
|
}
|
|
@@ -3,8 +3,8 @@ import type { CarrierResult } from '../../core/result/index.js';
|
|
|
3
3
|
export declare function normalizePaackTrackingNumber(raw: string): string;
|
|
4
4
|
export declare function normalizePaackPostcode(raw: string): string;
|
|
5
5
|
export declare function paackTrackingUrl(rawTrackingNumber: string, rawPostcode: string): string;
|
|
6
|
-
export declare function parsePaackTrackingResponse(payload: unknown,
|
|
7
|
-
export declare function parsePaackTrackingHtml(html: string,
|
|
6
|
+
export declare function parsePaackTrackingResponse(payload: unknown, rawPostcode: string): CarrierResult;
|
|
7
|
+
export declare function parsePaackTrackingHtml(html: string, rawPostcode: string): CarrierResult;
|
|
8
8
|
export interface PaackTrackerOptions {
|
|
9
9
|
timeoutMs?: number;
|
|
10
10
|
/** Test seam; production uses the global fetch. */
|