universal-parcel-scraper 0.55.0-main.708 → 0.56.0-main.723
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 +42 -1
- package/data/coverage-summary.json +1 -1
- package/data/detection-golden.json +49 -2
- package/dist/browser/scraper.js +1048 -1005
- package/dist/carriers/chronopost/adapter.js +23 -16
- package/dist/carriers/dpd/adapter.d.ts +2 -2
- package/dist/carriers/dpd/adapter.js +29 -20
- package/dist/carriers/dpd-de/adapter.js +2 -3
- package/dist/carriers/dpd-de/app.d.ts +5 -2
- package/dist/carriers/dpd-de/app.js +10 -3
- package/dist/carriers/dpd-de/carrier.json +1 -1
- package/dist/carriers/dpd-de/numbers.json +26 -0
- package/dist/carriers/dpd-de/service.d.ts +42 -7
- package/dist/carriers/dpd-de/service.js +54 -26
- package/dist/carriers/dpd-fr/adapter.js +18 -16
- package/dist/carriers/dpd-pl/carrier.json +7 -0
- package/dist/carriers/emile/carrier.json +56 -0
- package/dist/carriers/emile/numbers.json +94 -0
- package/dist/carriers/emile/statuses.json +4 -0
- package/dist/carriers/four-px/numbers.json +5 -2
- package/dist/carriers/gls-ch/adapter.d.ts +4 -0
- package/dist/carriers/gls-ch/adapter.js +8 -3
- package/dist/carriers/gls-ch/status.js +2 -2
- package/dist/carriers/gls-ch/statuses.json +1 -1
- package/dist/carriers/gls-de/adapter.d.ts +4 -0
- package/dist/carriers/gls-de/adapter.js +8 -3
- package/dist/carriers/gls-de/statuses.json +1 -1
- package/dist/carriers/gls-fr/adapter.js +8 -4
- package/dist/carriers/la-poste/adapter.js +23 -11
- package/dist/carriers/poczta-polska/parser.js +22 -6
- package/dist/carriers/swiss-post/adapter.d.ts +1 -1
- package/dist/carriers/swiss-post/adapter.js +17 -8
- package/dist/carriers/ukrposhta/parser.js +19 -3
- package/dist/core/catalog/hints.js +8 -5
- package/dist/core/errors/index.d.ts +8 -1
- package/dist/core/errors/index.js +7 -0
- package/dist/core/runner/networkRetry.d.ts +15 -0
- package/dist/core/runner/networkRetry.js +51 -0
- package/dist/core/status/language.js +32 -6
- package/dist/data/catalog.json +42 -1
- package/dist/generated/catalog.d.ts +30 -2
- package/dist/generated/catalog.js +45 -2
- package/dist/generated/registry.js +1 -0
- package/dist/node.d.ts +1 -1
- package/dist/providers/parcelsapp/adapter.js +9 -3
- package/dist/server/openapi.json +1 -1
- package/package.json +1 -1
|
@@ -23,14 +23,17 @@ const CATALOG_NAMES = new Set([
|
|
|
23
23
|
// Postal lookup candidates, not proof of which operator delivers a shipment.
|
|
24
24
|
// Only operators whose dedicated adapter looks up mail from abroad by its S10
|
|
25
25
|
// number are useful here: India Post's, Pos Malaysia's, CTT's and Poste
|
|
26
|
-
// Italiane's take only numbers of their own. Poczta Polska
|
|
27
|
-
//
|
|
28
|
-
// origin
|
|
29
|
-
//
|
|
26
|
+
// Italiane's take only numbers of their own. Poczta Polska dates scans at its
|
|
27
|
+
// delivery office, and other inward scans when they name a Polish office; those
|
|
28
|
+
// relayed from the origin keep its wall clock. Ukrposhta now dates its scans in
|
|
29
|
+
// Ukraine, but its status API can miss mail from abroad, and its portal then
|
|
30
|
+
// needs a browser session for every lookup. The host also checks adapter
|
|
31
|
+
// availability and required inputs before making a request.
|
|
30
32
|
const NATIONAL_POSTS = new Map([
|
|
31
33
|
['AU', 'australia-post'], ['CA', 'canada-post'], ['CH', 'swiss-post'], ['DE', 'dhl'],
|
|
32
34
|
['ES', 'correos-spain'], ['FI', 'posti'], ['FR', 'la-poste'], ['GB', 'royal-mail'],
|
|
33
|
-
['JP', 'japan-post'], ['NL', 'spring-gds'], ['
|
|
35
|
+
['JP', 'japan-post'], ['NL', 'spring-gds'], ['PL', 'poczta-polska'], ['SE', 'postnord'],
|
|
36
|
+
['US', 'usps'],
|
|
34
37
|
]);
|
|
35
38
|
const englishCountries = new Intl.DisplayNames(['en'], { type: 'region' });
|
|
36
39
|
// Deprecated codes carry their successor's name, so fold them into it ("FX" is France).
|
|
@@ -57,7 +57,14 @@ export declare class SchemaError extends CarrierError {
|
|
|
57
57
|
}
|
|
58
58
|
export declare class InputRequiredError extends CarrierError {
|
|
59
59
|
readonly field: string;
|
|
60
|
-
|
|
60
|
+
/**
|
|
61
|
+
* The catalog id of the carrier that asks for the input, when the source names one: a universal
|
|
62
|
+
* provider relaying a carrier's postcode gate. Absent when the source names none, or none that maps.
|
|
63
|
+
*/
|
|
64
|
+
readonly carrier?: string;
|
|
65
|
+
constructor(provider: string, field: string, message?: string, options?: CarrierErrorOptions & {
|
|
66
|
+
carrier?: string;
|
|
67
|
+
});
|
|
61
68
|
}
|
|
62
69
|
/**
|
|
63
70
|
* The adapter rejected the number or credential before any request: the format
|
|
@@ -89,10 +89,17 @@ export class SchemaError extends CarrierError {
|
|
|
89
89
|
}
|
|
90
90
|
export class InputRequiredError extends CarrierError {
|
|
91
91
|
field;
|
|
92
|
+
/**
|
|
93
|
+
* The catalog id of the carrier that asks for the input, when the source names one: a universal
|
|
94
|
+
* provider relaying a carrier's postcode gate. Absent when the source names none, or none that maps.
|
|
95
|
+
*/
|
|
96
|
+
carrier;
|
|
92
97
|
constructor(provider, field, message = `${provider} tracking requires ${field}`, options) {
|
|
93
98
|
super('input_required', provider, message, options);
|
|
94
99
|
this.name = 'InputRequiredError';
|
|
95
100
|
this.field = field;
|
|
101
|
+
if (options?.carrier !== undefined)
|
|
102
|
+
this.carrier = options.carrier;
|
|
96
103
|
}
|
|
97
104
|
}
|
|
98
105
|
/**
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { StepContext, StepSpec } from './index.js';
|
|
2
|
+
/** Whether an error is, or wraps, a request that did not reach the carrier, including one that timed out. */
|
|
3
|
+
export declare function isNetworkFailure(error: unknown): boolean;
|
|
4
|
+
/** The time a first attempt's request gets out of what is left: half, so a retry still fits. */
|
|
5
|
+
export declare function firstAttemptMs(remainingMs: number): number;
|
|
6
|
+
/** A step that runs the read again after the step before it failed to reach the carrier. */
|
|
7
|
+
export declare function networkRetryStep<T>(run: (context: StepContext) => Promise<T>): StepSpec<T>;
|
|
8
|
+
/**
|
|
9
|
+
* `attempt` with the time its request may take, then once more after a network failure or a
|
|
10
|
+
* timeout, while the budget has time left and its signal has not ended.
|
|
11
|
+
*/
|
|
12
|
+
export declare function withNetworkRetry<T>(budget: {
|
|
13
|
+
signal?: AbortSignal;
|
|
14
|
+
remainingMs: () => number;
|
|
15
|
+
}, attempt: (timeoutMs: number) => Promise<T>): Promise<T>;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One more try at a carrier's own read after a network failure or a timeout, inside the lookup's
|
|
3
|
+
* budget. The first attempt's request gets at most half of the time left, so a request that hangs
|
|
4
|
+
* leaves time for the second, which gets the rest. Only a failure to reach the carrier is tried
|
|
5
|
+
* again: an HTTP status, a reply that does not parse and a definite answer stand, and so does any
|
|
6
|
+
* failure once the lookup's own signal has ended. Reads only: a request that changes something, or
|
|
7
|
+
* one bound to a session the failure may have spent, is not replayed.
|
|
8
|
+
*
|
|
9
|
+
* Steps run by `runSteps` add `networkRetryStep` after the first and give it `firstAttemptMs` of
|
|
10
|
+
* what is left, so the retry is recorded as a step of its own. A read outside the runner wraps its
|
|
11
|
+
* request in `withNetworkRetry`.
|
|
12
|
+
*/
|
|
13
|
+
import { UpstreamNetworkError } from '../errors/index.js';
|
|
14
|
+
const MAX_CAUSE_DEPTH = 8;
|
|
15
|
+
/** Whether an error is, or wraps, a request that did not reach the carrier, including one that timed out. */
|
|
16
|
+
export function isNetworkFailure(error) {
|
|
17
|
+
const seen = new Set();
|
|
18
|
+
let current = error;
|
|
19
|
+
for (let depth = 0; depth < MAX_CAUSE_DEPTH && current instanceof Error && !seen.has(current); depth += 1) {
|
|
20
|
+
seen.add(current);
|
|
21
|
+
if (current instanceof UpstreamNetworkError)
|
|
22
|
+
return true;
|
|
23
|
+
current = current.cause;
|
|
24
|
+
}
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
/** The time a first attempt's request gets out of what is left: half, so a retry still fits. */
|
|
28
|
+
export function firstAttemptMs(remainingMs) {
|
|
29
|
+
return Math.max(1, Math.floor(remainingMs / 2));
|
|
30
|
+
}
|
|
31
|
+
/** A step that runs the read again after the step before it failed to reach the carrier. */
|
|
32
|
+
export function networkRetryStep(run) {
|
|
33
|
+
return { id: 'retry', recovers: isNetworkFailure, run };
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* `attempt` with the time its request may take, then once more after a network failure or a
|
|
37
|
+
* timeout, while the budget has time left and its signal has not ended.
|
|
38
|
+
*/
|
|
39
|
+
export async function withNetworkRetry(budget, attempt) {
|
|
40
|
+
try {
|
|
41
|
+
return await attempt(firstAttemptMs(budget.remainingMs()));
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
if (!isNetworkFailure(error) || budget.signal?.aborted)
|
|
45
|
+
throw error;
|
|
46
|
+
const left = budget.remainingMs();
|
|
47
|
+
if (left <= 1)
|
|
48
|
+
throw error;
|
|
49
|
+
return await attempt(left);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -44,12 +44,31 @@ export function deliveryForecastRemainder(description) {
|
|
|
44
44
|
const CARRIER_STEP = String.raw `(?:dispatched|despatched|shipped|sent|received|processed|handed (?:over|in)|posted)\b`;
|
|
45
45
|
/** A handover to a carrier or courier, not to the recipient's side or to customs. */
|
|
46
46
|
const HANDED_TO_CARRIER = String.raw `handed (?:over )?to (?!(?:the |a |an |your )?(?:recipient|addressee|consignee|customer|receiver|neighbou?r|customs|you|person)\b)`;
|
|
47
|
+
/** A carrier by its brand, as handovers to one are worded. */
|
|
48
|
+
const CARRIER_BRAND = String.raw `(?:gls|dpd|dhl|ups|fedex|tnt|hermes|colissimo|chronopost|brt|sda|inpost)\b`;
|
|
49
|
+
/** A place a carrier delivers to, worded after the brand or noun of its network: "DHL Packstation". */
|
|
50
|
+
const CARRIER_PLACE = String.raw `(?:packstation|paketstation|paketshop|parcelshop|shop|filiale|access point|pick ?up|locker|consigne|relais|restante|fermopoint|point|punto|box|station)\b`;
|
|
51
|
+
/** A carrier named on its own, not one of its places. */
|
|
52
|
+
const NOT_A_PLACE = String.raw `(?! ${CARRIER_PLACE})`;
|
|
53
|
+
/**
|
|
54
|
+
* The same handover negated in French ("n'a pas encore été remis à GLS"),
|
|
55
|
+
* German ("wurde noch nicht an GLS übergeben", or a heading such as "Paket
|
|
56
|
+
* noch nicht an DPD übergeben") and Italian ("non è ancora stato affidato a
|
|
57
|
+
* GLS"): one that has not happened, to a carrier or an operator, not one that
|
|
58
|
+
* cannot or will not happen, nor one to the carrier's shop, locker or pickup
|
|
59
|
+
* point.
|
|
60
|
+
*/
|
|
61
|
+
const NOT_HANDED_TO_CARRIER = [
|
|
62
|
+
String.raw `(?:\bn'(?:a|ont)(?: toujours)? (?:pas|jamais)(?: encore)? ete|\bpas encore) (?:remis|confie)e?s? (?:a|au|aux) (?:(?:la|le|les) |l')?(?:(?:transporteur|livreur|coursier|messagerie|operateur|prestataire)s?|poste|${CARRIER_BRAND})\b${NOT_A_PLACE}`,
|
|
63
|
+
String.raw `(?:\b(?:wurde|wurden|ist|sind|war|waren)\b(?: [\w'-]+){0,4}? |(?:^|[.;:] )(?:(?:das|die|der|ihr|ihre) )?(?:[\w-]+ )?(?:noch )?)nicht an (?:(?:den|die|das|dem|der|unseren|unsere|unserem) )?(?:[\w-]+ )?(?:zusteller|paketzusteller|paketdienst|kurier|kurierdienst|versanddienst|spediteur|post|${CARRIER_BRAND}) ubergeben\b(?! werden)`,
|
|
64
|
+
String.raw `\bnon (?:(?:e|sono|ancora|stat[oaie]|mai) )*affidat[oaie] (?:(?:a|ad|al|allo|alla|ai|agli|alle) |all')(?:corrier[ei]|vettor[ei]|trasportator[ei]|spedizionier[ei]|operatore|posta|poste|${CARRIER_BRAND})\b${NOT_A_PLACE}`,
|
|
65
|
+
].join('|');
|
|
47
66
|
/**
|
|
48
67
|
* A step the carrier has not taken yet ("not yet received", "not dispatched
|
|
49
68
|
* yet") or a handover that has not happened ("has not been handed over to
|
|
50
69
|
* GLS"), from the negation to the end of its sentence.
|
|
51
70
|
*/
|
|
52
|
-
const NOT_YET = new RegExp(String.raw `(?:\bnot (?:yet (?:been )?${CARRIER_STEP}|(?:been )?${CARRIER_STEP}(?: \w+){0,3}? yet\b)|(?:\bnot|n't)(?: been)? ${HANDED_TO_CARRIER})${SENTENCE}*`, 'g');
|
|
71
|
+
const NOT_YET = new RegExp(String.raw `(?:\bnot (?:yet (?:been )?${CARRIER_STEP}|(?:been )?${CARRIER_STEP}(?: \w+){0,3}? yet\b)|(?:\bnot|n't)(?: been)? ${HANDED_TO_CARRIER}|(?:\bnot|n't) yet(?: been)? ${HANDED_TO_CARRIER}(?!(?:the |a |an |your )?(?:[\w-]+ )?${CARRIER_PLACE})|${NOT_HANDED_TO_CARRIER})${SENTENCE}*`, 'g');
|
|
53
72
|
/** A handover the carrier made, not a pickup order passed on or the sender's own hand-in. */
|
|
54
73
|
const HANDED_TO = String.raw `(?<!\b(?:(?:pick ?up|collection) (?:order|request)|sender|shipper|seller|merchant|consignor)(?: has| have)?(?: been)? )\bhanded (?:over )?to `;
|
|
55
74
|
/**
|
|
@@ -59,6 +78,8 @@ const HANDED_TO = String.raw `(?<!\b(?:(?:pick ?up|collection) (?:order|request)
|
|
|
59
78
|
const COURIER = String.raw `(?:(?:the|a|an|our|your) )?(?:delivery )?couriers?\b(?! (?:compan|service|partner|network|guy\b))`;
|
|
60
79
|
const HANDED_TO_COURIER = new RegExp(`${HANDED_TO}${COURIER}`);
|
|
61
80
|
const HANDED_TO_NETWORK = new RegExp(String.raw `${HANDED_TO}(?!${COURIER})(?:the |a |an |our |your )?(?:[\w-]+ ){0,2}?(?:courier|carrier)s?\b`);
|
|
81
|
+
/** Customs or clearance named in any language. */
|
|
82
|
+
const CUSTOMS = /customs|clearance|douan|formalites (?:d')?(?:import|export)|zoll|dogan|\b(?:des)?aduan[ae]|alfandeg|\bceln|government agency|autorite gouvernementale|staatliche behorde|autorita governativa/;
|
|
62
83
|
/** The side of customs a scan may name: "import", "export", "import/export". */
|
|
63
84
|
const CUSTOMS_SIDE = String.raw `(?:(?:import|export)(?:\/(?:import|export))? |local |destination |origin )?`;
|
|
64
85
|
/**
|
|
@@ -167,7 +188,7 @@ export function trackingLanguageStage(description) {
|
|
|
167
188
|
if (/^(?:a )?(?:new|next|another|further) (?:delivery )?attempt\b/.test(text)
|
|
168
189
|
&& !/\b(?:not|unable|fail(?:ed|ure)?|unsuccessful|missed|absent|closed|refused|nobody|no one)\b|n't\b/.test(text))
|
|
169
190
|
return 'in_transit';
|
|
170
|
-
if (/not (?:yet )?delivered|\bundelivered\b|could not.*deliver|\bcan(?: ?not|'t) be delivered\b|unable to deliver|delivery (?:attempt|failed)|non livre|n'(?:a|avons) (?:pas )?pu.*(?:remis|remettre)|n'a pas pu etre (?:distribue|livre)|\bne (?:peut|pourra|pourrait|pouvait) (?:pas )?etre (?:distribue|livre)|livraison (?:impossible|echouee)|\bechec (?:de (?:la )?)?livraison|tentative de livraison|nicht zugestellt|nicht zugestellt werden|zustellung.*(?:fehlgeschlagen|nicht moglich)|zustellversuch|non consegnat[oa]|non e stato possibile consegnare|consegna (?:fallita|non riuscita)|tentativo di consegna/.test(text))
|
|
191
|
+
if (/not (?:yet )?delivered|\bundelivered\b|could not.*deliver|\bcan(?: ?not|'t) be delivered\b|unable to deliver|delivery (?:attempt|failed)|non livre|n'(?:a|avons) (?:pas )?pu.*(?:remis|remettre)|n'a pas pu etre (?:distribue|livre)|\bne (?:peut|pourra|pourrait|pouvait) (?:pas )?etre (?:distribue|livre)|livraison (?:impossible|echouee)|\bechec (?:de (?:la )?)?livraison|tentative de livraison|nicht zugestellt|nicht zugestellt werden|zustellung.*(?:fehlgeschlagen|nicht moglich)|zustellversuch|non consegnat[oa]|\bmancata consegna\b|non e stato possibile consegnare|consegna (?:fallita|non riuscita)|tentativo di consegna/.test(text))
|
|
171
192
|
return 'failed_attempt';
|
|
172
193
|
if (/\bno (?:(?:ha|han|hemos|se|le|lo|la|fue|sido|ser|es|era|pudo|puede|podido|posible|todavia|aun) )*(?:entregad[oa]s?|entregar(?:lo|la|le|se)?|repartid[oa]s?|realizar la entrega|efectuar la entrega)\b|\bsin entregar\b|\b(?:entrega|reparto) (?:fallid[oa]|imposible|no (?:(?:ha|han|se|fue|sido|ser|pudo|puede|podido) )*(?:realizad[oa]|efectuad[oa]|completad[oa]|posible|conseguid[oa]))\b|\bimposible (?:entregar|realizar la entrega|efectuar la entrega)\b|\bfallo (?:en la|de) entrega\b|\bintento fallido\b|\bintento de (?:entrega|reparto)\b(?! (?:programad|previst|para (?:hoy|manana|el)|manana|hoy))|\bnao (?:(?:foi|foram|ser|sera|pode|podemos|conseguimos|conseguiu|se|possivel|e|ainda|tem sido|esta|estava) )*(?:entregues?|entregar|efetuar a entrega|efectuar a entrega|realizar a entrega)\b|\bentrega (?:falhada|sem sucesso|impossivel|nao (?:(?:foi|pode|ser|sera|tem sido) )*(?:efetuada|efectuada|realizada|conseguida|concretizada|possivel))\b|\bfalha (?:na|de) entrega\b|\btentativa (?:de entrega|sem sucesso)\b(?! (?:agendada|prevista|programada|para (?:hoje|amanha)|amanha|hoje))|\bcarteiro nao atendido\b/.test(text))
|
|
173
194
|
return 'failed_attempt';
|
|
@@ -263,11 +284,16 @@ export function trackingLanguageStage(description) {
|
|
|
263
284
|
return 'registered';
|
|
264
285
|
// The carrier has not had the parcel yet: "Shipment not yet received or
|
|
265
286
|
// processed". What else the scan reports still counts: "Arrived at hub; not
|
|
266
|
-
// yet processed" is in transit, and a clearance delay stays with customs
|
|
287
|
+
// yet processed" is in transit, and a clearance delay stays with customs,
|
|
288
|
+
// even worded within the step's own sentence ("Not handed over to the
|
|
289
|
+
// carrier: awaiting customs clearance"), as a release there moves it on
|
|
290
|
+
// unless it is still to come ("clearance not completed").
|
|
267
291
|
if (!/\b(?:at|in|by) (?:the )?(?:destination|delivery|recipient|consignee|addressee|customer|customs|office of exchange)\b/.test(text)) {
|
|
268
292
|
const rest = text.replace(NOT_YET, ' ');
|
|
269
|
-
if (rest !== text)
|
|
270
|
-
return trackingLanguageStage(rest.replace(/^[\s,;:.!?]+|[\s,;:]+$/g, ''))
|
|
293
|
+
if (rest !== text) {
|
|
294
|
+
return trackingLanguageStage(rest.replace(/^[\s,;:.!?]+|[\s,;:]+$/g, ''))
|
|
295
|
+
?? (CUSTOMS.test(text) ? (!CUSTOMS_PENDING.test(text) && CUSTOMS_RELEASED.test(text) ? 'in_transit' : 'customs') : 'registered');
|
|
296
|
+
}
|
|
271
297
|
}
|
|
272
298
|
if (/^(?:reported|recorded|announced|item created|enregistre|annonce|erfasst|angekundigt|registrato|annunciato)$/.test(text))
|
|
273
299
|
return 'registered';
|
|
@@ -280,7 +306,7 @@ export function trackingLanguageStage(description) {
|
|
|
280
306
|
return 'customs';
|
|
281
307
|
if (CUSTOMS_RELEASED.test(text))
|
|
282
308
|
return 'in_transit';
|
|
283
|
-
if (
|
|
309
|
+
if (CUSTOMS.test(text))
|
|
284
310
|
return 'customs';
|
|
285
311
|
if (/out for (?:physical )?delivery|\bout with (?:the |a |our |your )?(?:delivery )?(?:courier|driver)s?\b(?! for (?:pick ?up|collection|return))|being delivered|in delivery|(?:loading|loaded).*delivery vehicle|on (?:\w+ )?vehicle for delivery|(?:courier|driver|delivery champion) has (?:the|your) (?:shipment|parcel|package|item)|en cours de livraison|en livraison|de la livraison de (?:son|votre) colis ce jour|charg(?:e|ement).*vehicule de livraison|in zustellung|zustellfahrzeug.*(?:geladen|verladen)|(?:beladen|verladung).*zustellfahrzeug|in consegna|caric(?:at|amento).*veicolo.*consegna/.test(text))
|
|
286
312
|
return 'out_for_delivery';
|
package/dist/data/catalog.json
CHANGED
|
@@ -4846,7 +4846,7 @@
|
|
|
4846
4846
|
"domains": [
|
|
4847
4847
|
"tracking.dpd.de"
|
|
4848
4848
|
],
|
|
4849
|
-
"path": "^/status/[a-z]{2}_[A-Z]{2}/parcel/(\\d{14})/?$"
|
|
4849
|
+
"path": "^/status/[a-z]{2}_[A-Z]{2}/parcel/(\\d{14}[0-9A-Z]?)/?$"
|
|
4850
4850
|
},
|
|
4851
4851
|
{
|
|
4852
4852
|
"domains": [
|
|
@@ -4909,6 +4909,12 @@
|
|
|
4909
4909
|
"pattern": "^13\\d{12}$",
|
|
4910
4910
|
"confidence": "low",
|
|
4911
4911
|
"preferred": true
|
|
4912
|
+
},
|
|
4913
|
+
{
|
|
4914
|
+
"pattern": "^13\\d{12}[0-9A-Z]$",
|
|
4915
|
+
"confidence": "low",
|
|
4916
|
+
"checksum": "dpd",
|
|
4917
|
+
"preferred": true
|
|
4912
4918
|
}
|
|
4913
4919
|
]
|
|
4914
4920
|
},
|
|
@@ -4999,6 +5005,41 @@
|
|
|
4999
5005
|
}
|
|
5000
5006
|
]
|
|
5001
5007
|
},
|
|
5008
|
+
"emile": {
|
|
5009
|
+
"displayName": "Emile",
|
|
5010
|
+
"color": "#e42920",
|
|
5011
|
+
"aliases": [
|
|
5012
|
+
"Emile Express"
|
|
5013
|
+
],
|
|
5014
|
+
"countries": [
|
|
5015
|
+
"CA"
|
|
5016
|
+
],
|
|
5017
|
+
"selectable": true,
|
|
5018
|
+
"timezone": "UTC",
|
|
5019
|
+
"tracking": {
|
|
5020
|
+
"mode": "link-only",
|
|
5021
|
+
"adapter": null
|
|
5022
|
+
},
|
|
5023
|
+
"canaryUrl": "https://www.emileps.com/",
|
|
5024
|
+
"trackingUrlTemplate": "https://www.emileps.com/tracking?tracking={trackingNumber}",
|
|
5025
|
+
"trackingSearchUrl": "https://www.emileps.com/tracking",
|
|
5026
|
+
"linkRules": [
|
|
5027
|
+
{
|
|
5028
|
+
"domains": [
|
|
5029
|
+
"emileps.com"
|
|
5030
|
+
],
|
|
5031
|
+
"params": [
|
|
5032
|
+
"tracking"
|
|
5033
|
+
]
|
|
5034
|
+
}
|
|
5035
|
+
],
|
|
5036
|
+
"detectionRules": [
|
|
5037
|
+
{
|
|
5038
|
+
"pattern": "^EM\\d{12}CA$",
|
|
5039
|
+
"confidence": "low"
|
|
5040
|
+
}
|
|
5041
|
+
]
|
|
5042
|
+
},
|
|
5002
5043
|
"evri-uk": {
|
|
5003
5044
|
"displayName": "Evri UK",
|
|
5004
5045
|
"color": "#00014d",
|
|
@@ -3321,7 +3321,7 @@ export declare const CARRIER_CATALOG: {
|
|
|
3321
3321
|
readonly trackingSearchUrl: "https://www.dpd.com/de/en/empfangen/sendungsverfolgung-und-live-tracking/";
|
|
3322
3322
|
readonly linkRules: readonly [{
|
|
3323
3323
|
readonly domains: readonly ["tracking.dpd.de"];
|
|
3324
|
-
readonly path: "^/status/[a-z]{2}_[A-Z]{2}/parcel/(\\d{14})/?$";
|
|
3324
|
+
readonly path: "^/status/[a-z]{2}_[A-Z]{2}/parcel/(\\d{14}[0-9A-Z]?)/?$";
|
|
3325
3325
|
}, {
|
|
3326
3326
|
readonly domains: readonly ["my.dpd.de"];
|
|
3327
3327
|
readonly params: readonly ["parcelno"];
|
|
@@ -3362,6 +3362,11 @@ export declare const CARRIER_CATALOG: {
|
|
|
3362
3362
|
readonly pattern: "^13\\d{12}$";
|
|
3363
3363
|
readonly confidence: "low";
|
|
3364
3364
|
readonly preferred: true;
|
|
3365
|
+
}, {
|
|
3366
|
+
readonly pattern: "^13\\d{12}[0-9A-Z]$";
|
|
3367
|
+
readonly confidence: "low";
|
|
3368
|
+
readonly checksum: "dpd";
|
|
3369
|
+
readonly preferred: true;
|
|
3365
3370
|
}];
|
|
3366
3371
|
};
|
|
3367
3372
|
readonly "dpd-uk": {
|
|
@@ -3424,6 +3429,29 @@ export declare const CARRIER_CATALOG: {
|
|
|
3424
3429
|
readonly confidence: "high";
|
|
3425
3430
|
}];
|
|
3426
3431
|
};
|
|
3432
|
+
readonly emile: {
|
|
3433
|
+
readonly displayName: "Emile";
|
|
3434
|
+
readonly color: "#e42920";
|
|
3435
|
+
readonly aliases: readonly ["Emile Express"];
|
|
3436
|
+
readonly countries: readonly ["CA"];
|
|
3437
|
+
readonly selectable: true;
|
|
3438
|
+
readonly timezone: "UTC";
|
|
3439
|
+
readonly tracking: {
|
|
3440
|
+
readonly mode: "link-only";
|
|
3441
|
+
readonly adapter: null;
|
|
3442
|
+
};
|
|
3443
|
+
readonly canaryUrl: "https://www.emileps.com/";
|
|
3444
|
+
readonly trackingUrlTemplate: "https://www.emileps.com/tracking?tracking={trackingNumber}";
|
|
3445
|
+
readonly trackingSearchUrl: "https://www.emileps.com/tracking";
|
|
3446
|
+
readonly linkRules: readonly [{
|
|
3447
|
+
readonly domains: readonly ["emileps.com"];
|
|
3448
|
+
readonly params: readonly ["tracking"];
|
|
3449
|
+
}];
|
|
3450
|
+
readonly detectionRules: readonly [{
|
|
3451
|
+
readonly pattern: "^EM\\d{12}CA$";
|
|
3452
|
+
readonly confidence: "low";
|
|
3453
|
+
}];
|
|
3454
|
+
};
|
|
3427
3455
|
readonly "evri-uk": {
|
|
3428
3456
|
readonly displayName: "Evri UK";
|
|
3429
3457
|
readonly color: "#00014d";
|
|
@@ -3684,7 +3712,7 @@ export declare const CARRIER_CATALOG: {
|
|
|
3684
3712
|
}];
|
|
3685
3713
|
};
|
|
3686
3714
|
};
|
|
3687
|
-
export declare const CARRIER_IDS: readonly ["swiss-post", "swiss-post-cargo", "quickpac", "planzer", "aliexpress", "sunyou", "hermes", "spring-gds", "postlogistics", "dachser", "dhl", "dhl-ecommerce", "ups", "amazon-logistics", "amazon-shipping", "fedex", "gls-ch", "dpd", "dpd-fr", "mondial-relay", "relais-colis", "la-poste", "chronopost", "gls-fr", "colis-prive", "geodis", "colisweb", "c-chez-vous", "heppner", "ciblex", "paack", "asendia", "shipup", "india-post", "hermes-de", "gls-de", "delivengo", "intl-post", "unknown", "royal-mail", "parcelforce", "evri", "inpost", "an-post", "bpost", "austrian-post", "postnord", "posti", "correos-express", "seur", "mrw", "nacex", "ctt", "ctt-express", "poste-italiane", "brt", "ecoscooting", "tipsa", "ukrposhta", "usps", "canada-post", "purolator", "canpar", "ontrac", "speedx", "uniuni", "landmark-global", "old-dominion", "spee-dee", "gofo", "estafeta", "correios-br", "correos-chile", "yunexpress", "four-px", "blue-dart", "delhivery", "nz-post", "singapore-post", "japan-post", "sf-express", "sto", "yunda", "yto", "zto", "jd-logistics", "yamato", "korea-post", "thailand-post", "dtdc", "australia-post", "hongkong-post", "pos-malaysia", "ninja-van", "china-post", "packeta", "poczta-polska", "bring-posten", "aramex", "tnt", "correos-spain", "yanwen", "the-courier-guy", "j-and-t", "ems", "cne", "dhl-ecommerce-es", "dhl-ecommerce-nl", "dhl-ecommerce-pl", "dhl-ecommerce-uk", "dhl-express", "dpd-de", "dpd-pl", "dpd-uk", "ekart", "evri-uk", "intelcom", "j-and-t-cargo", "lbc-express", "nova-poshta", "omgo", "sagawa", "speedpak", "spx-ph", "xpressbees"];
|
|
3715
|
+
export declare const CARRIER_IDS: readonly ["swiss-post", "swiss-post-cargo", "quickpac", "planzer", "aliexpress", "sunyou", "hermes", "spring-gds", "postlogistics", "dachser", "dhl", "dhl-ecommerce", "ups", "amazon-logistics", "amazon-shipping", "fedex", "gls-ch", "dpd", "dpd-fr", "mondial-relay", "relais-colis", "la-poste", "chronopost", "gls-fr", "colis-prive", "geodis", "colisweb", "c-chez-vous", "heppner", "ciblex", "paack", "asendia", "shipup", "india-post", "hermes-de", "gls-de", "delivengo", "intl-post", "unknown", "royal-mail", "parcelforce", "evri", "inpost", "an-post", "bpost", "austrian-post", "postnord", "posti", "correos-express", "seur", "mrw", "nacex", "ctt", "ctt-express", "poste-italiane", "brt", "ecoscooting", "tipsa", "ukrposhta", "usps", "canada-post", "purolator", "canpar", "ontrac", "speedx", "uniuni", "landmark-global", "old-dominion", "spee-dee", "gofo", "estafeta", "correios-br", "correos-chile", "yunexpress", "four-px", "blue-dart", "delhivery", "nz-post", "singapore-post", "japan-post", "sf-express", "sto", "yunda", "yto", "zto", "jd-logistics", "yamato", "korea-post", "thailand-post", "dtdc", "australia-post", "hongkong-post", "pos-malaysia", "ninja-van", "china-post", "packeta", "poczta-polska", "bring-posten", "aramex", "tnt", "correos-spain", "yanwen", "the-courier-guy", "j-and-t", "ems", "cne", "dhl-ecommerce-es", "dhl-ecommerce-nl", "dhl-ecommerce-pl", "dhl-ecommerce-uk", "dhl-express", "dpd-de", "dpd-pl", "dpd-uk", "ekart", "emile", "evri-uk", "intelcom", "j-and-t-cargo", "lbc-express", "nova-poshta", "omgo", "sagawa", "speedpak", "spx-ph", "xpressbees"];
|
|
3688
3716
|
export type CarrierId = (typeof CARRIER_IDS)[number];
|
|
3689
3717
|
/** Each carrier's detection rule ids, in the order of its `detectionRules`. */
|
|
3690
3718
|
export declare const DETECTION_RULE_IDS: Readonly<Record<CarrierId, readonly string[]>>;
|
|
@@ -4847,7 +4847,7 @@ export const CARRIER_CATALOG = {
|
|
|
4847
4847
|
"domains": [
|
|
4848
4848
|
"tracking.dpd.de"
|
|
4849
4849
|
],
|
|
4850
|
-
"path": "^/status/[a-z]{2}_[A-Z]{2}/parcel/(\\d{14})/?$"
|
|
4850
|
+
"path": "^/status/[a-z]{2}_[A-Z]{2}/parcel/(\\d{14}[0-9A-Z]?)/?$"
|
|
4851
4851
|
},
|
|
4852
4852
|
{
|
|
4853
4853
|
"domains": [
|
|
@@ -4910,6 +4910,12 @@ export const CARRIER_CATALOG = {
|
|
|
4910
4910
|
"pattern": "^13\\d{12}$",
|
|
4911
4911
|
"confidence": "low",
|
|
4912
4912
|
"preferred": true
|
|
4913
|
+
},
|
|
4914
|
+
{
|
|
4915
|
+
"pattern": "^13\\d{12}[0-9A-Z]$",
|
|
4916
|
+
"confidence": "low",
|
|
4917
|
+
"checksum": "dpd",
|
|
4918
|
+
"preferred": true
|
|
4913
4919
|
}
|
|
4914
4920
|
]
|
|
4915
4921
|
},
|
|
@@ -5000,6 +5006,41 @@ export const CARRIER_CATALOG = {
|
|
|
5000
5006
|
}
|
|
5001
5007
|
]
|
|
5002
5008
|
},
|
|
5009
|
+
"emile": {
|
|
5010
|
+
"displayName": "Emile",
|
|
5011
|
+
"color": "#e42920",
|
|
5012
|
+
"aliases": [
|
|
5013
|
+
"Emile Express"
|
|
5014
|
+
],
|
|
5015
|
+
"countries": [
|
|
5016
|
+
"CA"
|
|
5017
|
+
],
|
|
5018
|
+
"selectable": true,
|
|
5019
|
+
"timezone": "UTC",
|
|
5020
|
+
"tracking": {
|
|
5021
|
+
"mode": "link-only",
|
|
5022
|
+
"adapter": null
|
|
5023
|
+
},
|
|
5024
|
+
"canaryUrl": "https://www.emileps.com/",
|
|
5025
|
+
"trackingUrlTemplate": "https://www.emileps.com/tracking?tracking={trackingNumber}",
|
|
5026
|
+
"trackingSearchUrl": "https://www.emileps.com/tracking",
|
|
5027
|
+
"linkRules": [
|
|
5028
|
+
{
|
|
5029
|
+
"domains": [
|
|
5030
|
+
"emileps.com"
|
|
5031
|
+
],
|
|
5032
|
+
"params": [
|
|
5033
|
+
"tracking"
|
|
5034
|
+
]
|
|
5035
|
+
}
|
|
5036
|
+
],
|
|
5037
|
+
"detectionRules": [
|
|
5038
|
+
{
|
|
5039
|
+
"pattern": "^EM\\d{12}CA$",
|
|
5040
|
+
"confidence": "low"
|
|
5041
|
+
}
|
|
5042
|
+
]
|
|
5043
|
+
},
|
|
5003
5044
|
"evri-uk": {
|
|
5004
5045
|
"displayName": "Evri UK",
|
|
5005
5046
|
"color": "#00014d",
|
|
@@ -5523,6 +5564,7 @@ export const CARRIER_IDS = [
|
|
|
5523
5564
|
"dpd-pl",
|
|
5524
5565
|
"dpd-uk",
|
|
5525
5566
|
"ekart",
|
|
5567
|
+
"emile",
|
|
5526
5568
|
"evri-uk",
|
|
5527
5569
|
"intelcom",
|
|
5528
5570
|
"j-and-t-cargo",
|
|
@@ -5648,9 +5690,10 @@ export const DETECTION_RULE_IDS = {
|
|
|
5648
5690
|
"dhl-ecommerce-uk": ["dhl-ecommerce-uk-2", "dhl-ecommerce-uk-1"],
|
|
5649
5691
|
"dhl-express": ["dhl-express-waybill"],
|
|
5650
5692
|
"dpd-de": ["dpd-de-1", "dpd-de-2"],
|
|
5651
|
-
"dpd-pl": ["dpd-pl-1", "dpd-pl-2"],
|
|
5693
|
+
"dpd-pl": ["dpd-pl-1", "dpd-pl-2", "dpd-pl-3"],
|
|
5652
5694
|
"dpd-uk": ["dpd-uk-3", "dpd-uk-4", "dpd-uk-1", "dpd-uk-2"],
|
|
5653
5695
|
"ekart": ["ekart-ecommerce"],
|
|
5696
|
+
"emile": ["emile-1"],
|
|
5654
5697
|
"evri-uk": ["evri-uk-1", "evri-uk-2", "evri-uk-3"],
|
|
5655
5698
|
"intelcom": ["intelcom-1", "intelcom-2", "intelcom-3"],
|
|
5656
5699
|
"j-and-t-cargo": ["j-and-t-cargo-1"],
|
package/dist/node.d.ts
CHANGED
|
@@ -8,5 +8,5 @@ export { REGISTRY } from './generated/registry.js';
|
|
|
8
8
|
export { UniversalTracker, UniversalTrackingError } from './providers/universal.js';
|
|
9
9
|
export { TrackingCaptureError, SeventeenTrackLookupError, SeventeenTrackNoHistoryError, SeventeenTrackVerificationError } from './providers/universal.js';
|
|
10
10
|
export { amazonShippingEligibility } from './carriers/amazon-shipping/eligibility.js';
|
|
11
|
-
export { warmDpdSession, type DpdSession, type DpdSessionStore } from './carriers/dpd-de/service.js';
|
|
11
|
+
export { warmDpdSession, type DpdSession, type DpdSessionEvent, type DpdSessionStore } from './carriers/dpd-de/service.js';
|
|
12
12
|
export { createTrackingServer, type TrackingServerOptions } from './server/index.js';
|
|
@@ -250,9 +250,15 @@ function parseHistory(payload, trackingNumber, timezone = null) {
|
|
|
250
250
|
markReturnLeg(scans);
|
|
251
251
|
latest.check();
|
|
252
252
|
if (!events.length) {
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
|
|
253
|
+
const gates = payload.states.filter((raw) => isRecord(raw)
|
|
254
|
+
&& Array.isArray(raw.require_fields) && raw.require_fields.some((field) => isRecord(field) && field.name === 'zipcode'));
|
|
255
|
+
if (gates.length) {
|
|
256
|
+
// A gate names the carrier that asks for the postcode, as a scan does. It is
|
|
257
|
+
// given only when every gate names the same one and it maps to the catalog.
|
|
258
|
+
const [name, ...others] = gates.map((gate) => stateCarrierName(payload, gate));
|
|
259
|
+
const carrier = typeof name === 'string' && others.every((other) => other === name)
|
|
260
|
+
? universalCarrierHints([name], number).discovered_carrier : undefined;
|
|
261
|
+
throw new InputRequiredError(SOURCE, 'postcode', 'ParcelsApp requires a valid delivery postcode or further recipient information', carrier ? { carrier } : undefined);
|
|
256
262
|
}
|
|
257
263
|
// An empty history is the NO_DATA answer; states that don't parse are not an answer.
|
|
258
264
|
if (!payload.states.length)
|
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.
|
|
3
|
+
"version": "0.56.0-main.723",
|
|
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",
|