universal-parcel-scraper 0.654.0-main.3113 → 0.654.2-main.3118

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
@@ -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 16,485 such statuses, each filed under one stage.
158
+ The carrier folders record 16,496 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.
@@ -1,6 +1,23 @@
1
1
  function comparable(value) {
2
- return value.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toUpperCase().replace(/\s+/g, ' ').trim();
2
+ return value.normalize('NFD').replace(/[̀-ͯ]/g, '').toUpperCase().replace(/\s+/g, ' ').trim();
3
3
  }
4
+ const TRANSIT = { status: 'in_transit', stage: 'in_transit' };
5
+ // Scans of a parcel leaving Chile and of the legs before delivery, as providers and an
6
+ // open-source client relay them, without the portal's code. The whole wording must match.
7
+ const WORDINGS = new Map([
8
+ ['RECIBIDO POR CORREOSCHILE', { status: 'in_transit', stage: 'accepted' }],
9
+ ['RECIBIDO EN PLANTA DE ORIGEN', TRANSIT],
10
+ ['RECIBIDO EN PLANTA ORIGEN', TRANSIT],
11
+ ['ENVIO DESPACHADO', TRANSIT],
12
+ ['DESPACHADO A OFICINA DE CORREOSCHILE', TRANSIT],
13
+ ['RECIBIDO EN OFICINA DE CORREOSCHILE', TRANSIT],
14
+ ['DISPONIBLE PARA SALIR AL PAIS DE DESTINO', TRANSIT],
15
+ ['ENVIO DESPACHADO AL PAIS DE DESTINO', TRANSIT],
16
+ ['ENVIO INGRESA A PAIS DE DESTINO', TRANSIT],
17
+ ['RECEPCION INTERNACIONAL POR CORREOSCHILE', TRANSIT],
18
+ // The destination country's import, after the parcel entered it.
19
+ ['ENVIO EN PROCESO DE INTERNACION', { status: 'in_transit', stage: 'customs' }],
20
+ ]);
4
21
  /** Only actual scan wording establishes progress; the portal also sends future rail markers. */
5
22
  export function classifyCorreosChileScan(code, wording) {
6
23
  const label = comparable(wording);
@@ -13,5 +30,5 @@ export function classifyCorreosChileScan(code, wording) {
13
30
  if ((code === '006' || code === '010') && label === 'ENVIO ENTREGADO') {
14
31
  return { status: 'delivered', stage: 'delivered' };
15
32
  }
16
- return null;
33
+ return WORDINGS.get(label) ?? null;
17
34
  }
@@ -7,6 +7,83 @@
7
7
  "language": "es",
8
8
  "stage": "customs",
9
9
  "confirmedBy": "live"
10
+ },
11
+ {
12
+ "wording": "RECIBIDO POR CORREOSCHILE",
13
+ "language": "es",
14
+ "stage": "accepted",
15
+ "confirmedBy": "live",
16
+ "note": "A parcel leaving Chile, as a universal provider relays it without the portal code."
17
+ },
18
+ {
19
+ "wording": "RECIBIDO EN PLANTA DE ORIGEN",
20
+ "language": "es",
21
+ "stage": "in_transit",
22
+ "confirmedBy": "live",
23
+ "note": "A parcel leaving Chile, as a universal provider relays it without the portal code."
24
+ },
25
+ {
26
+ "wording": "ENVIO DESPACHADO",
27
+ "language": "es",
28
+ "stage": "in_transit",
29
+ "confirmedBy": "live",
30
+ "note": "A parcel leaving Chile, as a universal provider relays it without the portal code."
31
+ },
32
+ {
33
+ "wording": "DISPONIBLE PARA SALIR AL PAIS DE DESTINO",
34
+ "language": "es",
35
+ "stage": "in_transit",
36
+ "confirmedBy": "live",
37
+ "note": "A parcel leaving Chile, as a universal provider relays it without the portal code."
38
+ },
39
+ {
40
+ "wording": "ENVIO DESPACHADO AL PAIS DE DESTINO",
41
+ "language": "es",
42
+ "stage": "in_transit",
43
+ "confirmedBy": "live",
44
+ "note": "A parcel leaving Chile, as a universal provider relays it without the portal code."
45
+ },
46
+ {
47
+ "wording": "ENVIO INGRESA A PAIS DE DESTINO",
48
+ "language": "es",
49
+ "stage": "in_transit",
50
+ "confirmedBy": "live",
51
+ "note": "A parcel leaving Chile, as a universal provider relays it without the portal code."
52
+ },
53
+ {
54
+ "wording": "ENVIO EN PROCESO DE INTERNACION",
55
+ "language": "es",
56
+ "stage": "customs",
57
+ "confirmedBy": "live",
58
+ "note": "A parcel leaving Chile, as a universal provider relays it without the portal code."
59
+ },
60
+ {
61
+ "wording": "RECIBIDO EN PLANTA ORIGEN",
62
+ "language": "es",
63
+ "stage": "in_transit",
64
+ "confirmedBy": "prior-art",
65
+ "note": "In the example reply of an open-source Correos de Chile client, without the portal code."
66
+ },
67
+ {
68
+ "wording": "DESPACHADO A OFICINA DE CORREOSCHILE",
69
+ "language": "es",
70
+ "stage": "in_transit",
71
+ "confirmedBy": "prior-art",
72
+ "note": "In the example reply of an open-source Correos de Chile client, without the portal code."
73
+ },
74
+ {
75
+ "wording": "RECIBIDO EN OFICINA DE CORREOSCHILE",
76
+ "language": "es",
77
+ "stage": "in_transit",
78
+ "confirmedBy": "prior-art",
79
+ "note": "In the example reply of an open-source Correos de Chile client, without the portal code."
80
+ },
81
+ {
82
+ "wording": "RECEPCION INTERNACIONAL POR CORREOSCHILE",
83
+ "language": "es",
84
+ "stage": "in_transit",
85
+ "confirmedBy": "prior-art",
86
+ "note": "In the example reply of an open-source Correos de Chile client, without the portal code."
10
87
  }
11
88
  ]
12
89
  }
@@ -61,8 +61,8 @@ export declare class DHLTracker {
61
61
  /**
62
62
  * Whether DHL Paket knows the number, over HTTP alone: the direct session,
63
63
  * renewed as for a lookup, and never the browser. DHL's explicit no-data
64
- * answer is unknown; a rejected session, another DHL service or a postcode
65
- * request stays inconclusive.
64
+ * answer is unknown; a postcode request for a parcel DHL holds is known; a
65
+ * rejected session or another DHL service stays inconclusive.
66
66
  */
67
67
  recognizes(trackingNumber: string, context?: TrackingContext): Promise<Recognition>;
68
68
  private lookup;
@@ -97,10 +97,12 @@ export function parseDHLTrackingResponse(payload, trackingNumber) {
97
97
  if (matches.length !== 1)
98
98
  throw new SchemaError(PROVIDER, 'DHL did not return one matching shipment');
99
99
  const shipment = matches[0];
100
+ const missing = isRecord(shipment.sendungNichtGefunden) ? shipment.sendungNichtGefunden : {};
100
101
  if (shipment.plzBenoetigt === true || shipment.versandDatumBenoetigt === true) {
101
- throw new InputRequiredError(PROVIDER, 'a postcode or shipping date', 'DHL requires additional verification on its tracking website');
102
+ // DHL asks the recipient's postcode to show a parcel it holds; a record it also calls not found names none.
103
+ const held = shipment.plzBenoetigt === true && !Object.values(missing).some((flag) => flag === true);
104
+ throw new InputRequiredError(PROVIDER, 'a postcode or shipping date', 'DHL requires additional verification on its tracking website', held ? { reason: 'parcel_found' } : undefined);
102
105
  }
103
- const missing = isRecord(shipment.sendungNichtGefunden) ? shipment.sendungNichtGefunden : {};
104
106
  // DHL Paket's tracker answered that it holds no Paket shipment under the number. Another DHL
105
107
  // service may still know it, so it stays inconclusive, but it is an answer, not an outage.
106
108
  if (missing.keineDhlPaketSendung === true || missing.sendungsnummerNichtSuchbar === true) {
@@ -274,8 +276,8 @@ export class DHLTracker {
274
276
  /**
275
277
  * Whether DHL Paket knows the number, over HTTP alone: the direct session,
276
278
  * renewed as for a lookup, and never the browser. DHL's explicit no-data
277
- * answer is unknown; a rejected session, another DHL service or a postcode
278
- * request stays inconclusive.
279
+ * answer is unknown; a postcode request for a parcel DHL holds is known; a
280
+ * rejected session or another DHL service stays inconclusive.
279
281
  */
280
282
  async recognizes(trackingNumber, context = {}) {
281
283
  return takeTurn(this.serialize, 'dhl', context, (left) => {
@@ -411,7 +411,8 @@ function candidates(data, key, carriers) {
411
411
  export function locatePlace(location, hints = {}) {
412
412
  return locate(location, hints)?.place ?? null;
413
413
  }
414
- function locate(location, hints) {
414
+ /** `split` reads a country the text names apart from its town; without it the whole text is the town's. */
415
+ function locate(location, hints, split = true) {
415
416
  const text = location?.trim();
416
417
  // A military post office's base can be anywhere in its region (`militaryPostOffice`).
417
418
  if (!text || militaryPostOffice(text))
@@ -420,7 +421,7 @@ function locate(location, hints) {
420
421
  if (facility)
421
422
  return { place: facility, text: true };
422
423
  const data = gazetteer();
423
- const explicit = trackingPlace(text);
424
+ const explicit = split ? trackingPlace(text) : { country: null, place: text };
424
425
  let country = explicit.country && data.countries.has(explicit.country) ? explicit.country : null;
425
426
  let rest = explicit.country ? explicit.place : text;
426
427
  const context = hints.countries ?? [];
@@ -569,6 +570,16 @@ function locate(location, hints) {
569
570
  },
570
571
  };
571
572
  }
573
+ // A hub named after a city that shares a country's name: GIG Logistics' "BENIN HUB"
574
+ // is in Benin City. On a parcel in the city's country, and not in the named one, the
575
+ // city it is, when it is a large one.
576
+ const facilityWords = tokens(rest);
577
+ if (country && !context.includes(country) && text.endsWith(rest) && facilityWords.length
578
+ && facilityWords.every((token) => FACILITY_WORDS.has(token.key))) {
579
+ const town = locate(text, hints, false);
580
+ if (town?.place.precision === 'city' && context.includes(town.place.country) && (town.thousands ?? 0) >= NAMESAKE_THOUSANDS)
581
+ return town;
582
+ }
572
583
  // A two-letter code with no town of its country before it, on a parcel nothing else puts
573
584
  // there, may be no country at all: Royal Mail ends its mail centres with MC and its
574
585
  // delivery offices with DO, India's states are written like countries. A town the text
@@ -2,7 +2,7 @@
2
2
  "openapi": "3.1.0",
3
3
  "info": {
4
4
  "title": "Universal Parcel Scraper",
5
- "version": "0.654.0"
5
+ "version": "0.654.2"
6
6
  },
7
7
  "paths": {
8
8
  "/health": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "universal-parcel-scraper",
3
- "version": "0.654.0-main.3113",
3
+ "version": "0.654.2-main.3118",
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",