universal-parcel-scraper 0.650.0-main.3085 → 0.650.4-main.3091

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,431 such statuses, each filed under one stage.
158
+ The carrier folders record 16,434 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.
@@ -31,6 +31,9 @@ const WORDINGS = {
31
31
  'Arrival at facilities': { status: 'in_transit', stage: 'in_transit' },
32
32
  'Item has passed a checkpoint': { status: 'in_transit', stage: 'in_transit' },
33
33
  'Landmark Global (bpost) has received the item': { status: 'in_transit', stage: 'accepted' },
34
+ // The sender's drop-off, then bpost's acceptance of it a day later.
35
+ 'Drop off item by the customer': { status: 'in_transit', stage: 'accepted' },
36
+ 'Item accepted after deposit by sender': { status: 'in_transit', stage: 'accepted' },
34
37
  'Confirmation of preparation of the shipment received': { status: 'pending', stage: 'registered' },
35
38
  'Item is announced / Landmark Global (bpost) received the information': { status: 'pending', stage: 'registered' },
36
39
  };
@@ -142,6 +142,21 @@
142
142
  "confirmedBy": "live",
143
143
  "firstSeen": "2026-09-28"
144
144
  },
145
+ {
146
+ "wording": "Drop off item by the customer",
147
+ "language": "en",
148
+ "stage": "accepted",
149
+ "confirmedBy": "live",
150
+ "firstSeen": "2026-10-21",
151
+ "note": "The sender's drop-off, before bpost's acceptance of it."
152
+ },
153
+ {
154
+ "wording": "Item accepted after deposit by sender",
155
+ "language": "en",
156
+ "stage": "accepted",
157
+ "confirmedBy": "live",
158
+ "firstSeen": "2026-10-22"
159
+ },
145
160
  {
146
161
  "wording": "Confirmation of preparation of the shipment received",
147
162
  "language": "en",
@@ -36,4 +36,11 @@ export declare function brtOriginPartner(result: CarrierResult): BrtOriginPartne
36
36
  * data, the partner's newer scan gives the current status.
37
37
  */
38
38
  export declare function withOriginLeg(result: CarrierResult, partner: CarrierResult): CarrierResult;
39
+ /**
40
+ * DPD's scans that its own map leaves without a stage (carriers/dpd): there an origin depot's
41
+ * intake can follow an import's customs. In the leg that brought a parcel to BRT, all of it
42
+ * before BRT's hand-over, the hand-over to DPD and its depot's intake are the acceptance, unless
43
+ * DPD had already staged the parcel past it. The scans worded as moving it on are transit once
44
+ * DPD has it; SPL, worded so, also comes before the origin depot, and stays unstaged there.
45
+ */
39
46
  export declare function stagedDpdLeg(result: CarrierResult): CarrierResult;
@@ -6,7 +6,7 @@ import { brandCarrierIds } from '../../core/catalog/hints.js';
6
6
  import { isBrtCustomerParcelId, isBrtTrackingNumber } from '../../core/detection/brt.js';
7
7
  import { calendarDay, countryCode, countryTimeZone, isoTime, sharedClockZone } from '../../core/time/index.js';
8
8
  import { clean } from '../../core/transport/index.js';
9
- import { classifyBrtStatus } from './status.js';
9
+ import { classifyBrtStatus, DPD_INTAKE, DPD_TRANSIT } from './status.js';
10
10
  export function normalizeBrtNumber(raw) {
11
11
  const number = raw.replace(/\s/g, '').toUpperCase();
12
12
  if (!isBrtTrackingNumber(number)) {
@@ -336,8 +336,6 @@ export function withOriginLeg(result, partner) {
336
336
  * DPD had already staged the parcel past it. The scans worded as moving it on are transit once
337
337
  * DPD has it; SPL, worded so, also comes before the origin depot, and stays unstaged there.
338
338
  */
339
- const DPD_INTAKE = new Set(['PARCEL_HANDED', 'ORI']);
340
- const DPD_TRANSIT = new Set(['IN_TRANSIT', 'SPL', 'AT_DELIVERY_CENTER']);
341
339
  export function stagedDpdLeg(result) {
342
340
  if (!result.events?.length)
343
341
  return result;
@@ -1,2 +1,12 @@
1
1
  import type { ClassifiedStatus } from '../../core/status/index.js';
2
+ import type { CarrierStatusMap } from '../../core/status/statusMap.js';
2
3
  export declare function classifyBrtStatus(label: string): ClassifiedStatus | undefined;
4
+ /** In the DPD leg that brought a parcel to BRT, the scans whose stage the scans before them decide (`stagedDpdLeg`). */
5
+ export declare const DPD_INTAKE: ReadonlySet<string>;
6
+ export declare const DPD_TRANSIT: ReadonlySet<string>;
7
+ /**
8
+ * What the map says about one scan. BRT's own scans carry no code. A scan with a code comes from
9
+ * the leg of the partner that brought the parcel to BRT and reads that partner's map. In DPD's leg
10
+ * the hand-over and the depot's intake always get a stage, which the scans before them decide.
11
+ */
12
+ export declare const statusMap: CarrierStatusMap;
@@ -1,3 +1,5 @@
1
+ import { statusMap as chronopost } from '../chronopost/status.js';
2
+ import { statusMap as dpd } from '../dpd/status.js';
1
3
  const labels = {
2
4
  'SHIPPING INFO SENT TO BRT': { status: 'pending', stage: 'registered' },
3
5
  COLLECTED: { status: 'in_transit', stage: 'accepted' },
@@ -18,3 +20,20 @@ export function classifyBrtStatus(label) {
18
20
  const key = label.toUpperCase().trim();
19
21
  return Object.hasOwn(labels, key) ? labels[key] : undefined;
20
22
  }
23
+ /** In the DPD leg that brought a parcel to BRT, the scans whose stage the scans before them decide (`stagedDpdLeg`). */
24
+ export const DPD_INTAKE = new Set(['PARCEL_HANDED', 'ORI']);
25
+ export const DPD_TRANSIT = new Set(['IN_TRANSIT', 'SPL', 'AT_DELIVERY_CENTER']);
26
+ const DPD_LEG_TRANSIT = 'In the DPD leg before BRT it moves the parcel only once DPD has taken it in; before that it names no movement of its own.';
27
+ /**
28
+ * What the map says about one scan. BRT's own scans carry no code. A scan with a code comes from
29
+ * the leg of the partner that brought the parcel to BRT and reads that partner's map. In DPD's leg
30
+ * the hand-over and the depot's intake always get a stage, which the scans before them decide.
31
+ */
32
+ export const statusMap = {
33
+ stage: (code, wording) => (code ? chronopost.stage(code, wording) ?? dpd.stage(code, wording) : classifyBrtStatus(wording)?.stage),
34
+ gaps: [
35
+ ...chronopost.gaps,
36
+ ...dpd.gaps.flatMap((gap) => (gap.code === undefined || DPD_INTAKE.has(gap.code) ? []
37
+ : [{ ...gap, note: DPD_TRANSIT.has(gap.code) ? DPD_LEG_TRANSIT : gap.note }])),
38
+ ],
39
+ };
@@ -22,6 +22,7 @@
22
22
  */
23
23
  import type { CarrierStatus } from '../../core/result/index.js';
24
24
  import type { Stage } from '../../core/status/index.js';
25
+ import type { CarrierStatusMap } from '../../core/status/statusMap.js';
25
26
  /** Package and scan codes, uppercased, to the product stage. */
26
27
  export declare const FEDEX_CODE_STAGE: Readonly<Record<string, Stage>>;
27
28
  /**
@@ -38,3 +39,8 @@ export declare function statusForStage(stage: Stage): CarrierStatus;
38
39
  * wording on a shipment that already has scans reads as moving.
39
40
  */
40
41
  export declare function fedexStatus(code: string, text: string, hasEvents?: boolean): CarrierStatus;
42
+ /**
43
+ * What the map says about one scan. FedEx's scans keep no code, so their wording stages them, as
44
+ * it stages a scan whose code the map does not know.
45
+ */
46
+ export declare const statusMap: CarrierStatusMap;
@@ -123,3 +123,8 @@ export function fedexStatus(code, text, hasEvents = false) {
123
123
  // Unrecognized wording only means "moving" once the shipment has scans.
124
124
  return hasEvents ? 'in_transit' : 'unknown';
125
125
  }
126
+ /**
127
+ * What the map says about one scan. FedEx's scans keep no code, so their wording stages them, as
128
+ * it stages a scan whose code the map does not know.
129
+ */
130
+ export const statusMap = { stage: (_code, wording) => fedexStage(wording) ?? undefined, gaps: [] };
@@ -3,11 +3,10 @@ import { DateTime } from 'luxon';
3
3
  import { IndeterminateError, InvalidInputError, SchemaError } from '../../core/errors/index.js';
4
4
  import { calendarDay, firstOccurrence, regionHasTown, townKey } from '../../core/time/index.js';
5
5
  import { clean } from '../../core/transport/index.js';
6
- import { classifyMrwStatus } from './status.js';
6
+ import { classifyMrwStatus, MRW_NEUTRAL_STATUS } from './status.js';
7
7
  const ORIGIN = 'https://www.mrw.es';
8
8
  const SUMMARY_HEADERS = 'Número de albarán|Fecha|Hora|Estado envío|Histórico de envío';
9
9
  const HISTORY_HEADERS = 'Fecha|Hora|Estado envío|Ubicación';
10
- const NEUTRAL = 'Actualización de seguimiento';
11
10
  /** A clock with its hour zero-padded: the summary writes "1:59" where the history writes "01:59". */
12
11
  function paddedHour(value) {
13
12
  return value.replace(/^(\d):/, '0$1:');
@@ -198,7 +197,7 @@ export function parseMrwHistory(html, raw, summary) {
198
197
  continue;
199
198
  seen.add(key);
200
199
  const mapped = classifyMrwStatus(rawStatus);
201
- events.push({ description: mapped ? rawStatus : NEUTRAL,
200
+ events.push({ description: mapped ? rawStatus : MRW_NEUTRAL_STATUS,
202
201
  ...(mapped ? { provider_status: rawStatus, stage: mapped.stage } : {}),
203
202
  ...clock, ...(location ? { location } : {}) });
204
203
  }
@@ -1,2 +1,7 @@
1
1
  import type { ClassifiedStatus } from '../../core/status/index.js';
2
+ import { type CarrierStatusMap } from '../../core/status/statusMap.js';
2
3
  export declare function classifyMrwStatus(value: string): ClassifiedStatus | undefined;
4
+ /** The label a history row takes when the map does not know its status, which can name the recipient. */
5
+ export declare const MRW_NEUTRAL_STATUS = "Actualizaci\u00F3n de seguimiento";
6
+ /** What the map says about one scan: MRW's statuses carry no code. */
7
+ export declare const statusMap: CarrierStatusMap;
@@ -1,3 +1,4 @@
1
+ import { normalizeStatusWording } from '../../core/status/statusMap.js';
1
2
  const LABELS = {
2
3
  'PENDIENTE DE RECOGER': { status: 'pending', stage: 'registered' },
3
4
  'ENVIO RECOGIDO EN ORIGEN': { status: 'in_transit', stage: 'accepted' },
@@ -36,3 +37,13 @@ export function classifyMrwStatus(value) {
36
37
  const key = value.normalize('NFD').replace(/\p{Diacritic}/gu, '').toUpperCase().replace(/\s+/g, ' ').trim();
37
38
  return Object.hasOwn(LABELS, key) ? LABELS[key] : undefined;
38
39
  }
40
+ /** The label a history row takes when the map does not know its status, which can name the recipient. */
41
+ export const MRW_NEUTRAL_STATUS = 'Actualización de seguimiento';
42
+ /** What the map says about one scan: MRW's statuses carry no code. */
43
+ export const statusMap = {
44
+ stage: (_code, wording) => classifyMrwStatus(wording)?.stage,
45
+ gaps: [{
46
+ wording: normalizeStatusWording(MRW_NEUTRAL_STATUS),
47
+ note: "The adapter's label for a status its map does not know, kept out of the result because it can name the recipient: it names no milestone.",
48
+ }],
49
+ };
@@ -7,12 +7,14 @@ export function classifyPosteItalianeStatus(rawWording) {
7
7
  const value = normalizePosteItalianeWording(rawWording);
8
8
  if (!value)
9
9
  return undefined;
10
- // Dropped off at a Punto Poste: the parcel itself is in Poste's hands.
11
- if (value.startsWith('la spedizione è stata presa in carico presso il punto poste'))
10
+ // Dropped off at a Punto Poste, or taken in by one of Poste's operators at a post office counter or
11
+ // from the sender's home, also on its own line: the parcel itself is in Poste's hands.
12
+ if (value.startsWith('la spedizione è stata presa in carico presso il punto poste')
13
+ || value.startsWith('la spedizione è stata presa in carico da un nostro operatore')
14
+ || value.startsWith('da un nostro operatore presso'))
12
15
  return { status: 'in_transit', stage: 'accepted' };
16
+ // A purchase on poste.it, and the bare line, which names no intake.
13
17
  if (value.startsWith('la spedizione è stata presa in carico')
14
- || value.startsWith('da un nostro operatore presso l’ufficio postale')
15
- || value.startsWith('da un nostro operatore presso l\'ufficio postale')
16
18
  || value === 'a seguito di acquisto da poste.it')
17
19
  return { status: 'pending', stage: 'registered' };
18
20
  if (value.startsWith('la spedizione è in transito')
@@ -2,8 +2,9 @@
2
2
  "carrier": "poste-italiane",
3
3
  "entries": [
4
4
  { "wording": "la spedizione è stata presa in carico presso il Punto Poste", "language": "it", "stage": "accepted", "confirmedBy": "live", "firstSeen": "2026-10-09", "note": "Prefix match, checked before the shorter prefix: a drop-off at a Punto Poste." },
5
- { "wording": "la spedizione è stata presa in carico…", "language": "it", "stage": "registered", "confirmedBy": "live", "firstSeen": "2026-09-10", "note": "Prefix match." },
6
- { "wording": "da un nostro operatore presso l’ufficio postale…", "language": "it", "stage": "registered", "confirmedBy": "prior-art", "firstSeen": "2026-09-10", "note": "Prefix match; both the typographic and the ASCII apostrophe are accepted." },
5
+ { "wording": "la spedizione è stata presa in carico da un nostro operatore…", "language": "it", "stage": "accepted", "confirmedBy": "live", "firstSeen": "2026-10-02", "note": "Prefix match, checked before the shorter prefix: taken in at a post office counter or from the sender's home." },
6
+ { "wording": "la spedizione è stata presa in carico…", "language": "it", "stage": "registered", "confirmedBy": "live", "firstSeen": "2026-09-10", "note": "Prefix match, after the Punto Poste and operator prefixes: the bare line names no intake." },
7
+ { "wording": "da un nostro operatore presso…", "language": "it", "stage": "accepted", "confirmedBy": "prior-art", "firstSeen": "2026-09-10", "note": "Prefix match: the operator's intake on its own line." },
7
8
  { "wording": "a seguito di acquisto da poste.it", "language": "it", "stage": "registered", "confirmedBy": "prior-art", "firstSeen": "2026-09-10", "note": "Exact match." },
8
9
  { "wording": "la spedizione è in transito…", "language": "it", "stage": "in_transit", "confirmedBy": "live", "firstSeen": "2026-09-10", "note": "Prefix match." },
9
10
  { "wording": "completata la fase di verifica per lo svincolo…", "language": "it", "stage": "in_transit", "confirmedBy": "prior-art", "firstSeen": "2026-09-10", "note": "Customs release; prefix match." },
@@ -3,7 +3,7 @@ import { load } from 'cheerio';
3
3
  import { recognizeFromBrowserLookup } from '../../core/adapter/index.js';
4
4
  import { isValidS10TrackingNumber } from '../../core/detection/s10.js';
5
5
  import { uspsPackageIdentifier } from '../../core/detection/usps.js';
6
- import { ChallengeError, InvalidInputError, SchemaError } from '../../core/errors/index.js';
6
+ import { ChallengeError, IndeterminateError, InvalidInputError, SchemaError } from '../../core/errors/index.js';
7
7
  import { runSteps } from '../../core/runner/index.js';
8
8
  import { NOOP_RECORDER } from '../../core/telemetry/index.js';
9
9
  import { firstOccurrence } from '../../core/time/index.js';
@@ -19,6 +19,7 @@ import { uspsStage, uspsStatus } from './status.js';
19
19
  * nothing is replayed over plain HTTP.
20
20
  */
21
21
  const TRACKING_BASE = 'https://tools.usps.com/tracking/';
22
+ const OUTAGE = 'https://www.usps.com/root/global/server_responses/anyapp_outage_apology.htm';
22
23
  const MAX_BYTES = 10_000_000;
23
24
  // The browser's time for every read; with the transport allowance a lookup ends within a
24
25
  // minute. Answers the page gives come well inside it.
@@ -275,6 +276,7 @@ export class USPSTracker {
275
276
  maxTimeout: timeoutMs,
276
277
  // The verification page can become idle before it reloads into tracking.
277
278
  contentWaitForSelector: '#trackingNum',
279
+ captureResponses: [uspsTrackingUrl(number)],
278
280
  }, {
279
281
  provider: 'TRAWL while fetching USPS',
280
282
  timeoutMs,
@@ -282,7 +284,21 @@ export class USPSTracker {
282
284
  fetcher: this.#fetcher,
283
285
  signal,
284
286
  });
285
- const result = parseUSPSTrackingHtml(page.html, number);
287
+ let result;
288
+ try {
289
+ result = parseUSPSTrackingHtml(page.html, number);
290
+ }
291
+ catch (error) {
292
+ // Prefer a complete matching page if it arrived. Only the exact tracking
293
+ // request's observed redirect proves an outage behind a blank wrapper.
294
+ if (error instanceof ChallengeError && page.capturedResponses.some(response => response.url === uspsTrackingUrl(number) && response.trackingNumberSubmitted === number
295
+ && response.status === 302 && response.headers.location === OUTAGE && !response.error
296
+ && !response.truncated && !response.base64Encoded)) {
297
+ // This lookup was redirected; it does not prove every USPS lookup is down.
298
+ throw new IndeterminateError('USPS', 'USPS sent the lookup to its service outage page', { reason: 'maintenance_page' });
299
+ }
300
+ throw error;
301
+ }
286
302
  result.tracking_url = uspsTrackingUrl(number);
287
303
  result.tracking_source = 'rendered-page';
288
304
  return result;
@@ -67,6 +67,7 @@ import { statusMap as bosta } from '../../carriers/bosta/status.js';
67
67
  import { statusMap as boxNow } from '../../carriers/box-now/status.js';
68
68
  import { statusMap as bpost } from '../../carriers/bpost/status.js';
69
69
  import { statusMap as bringerParcel } from '../../carriers/bringer-parcel/status.js';
70
+ import { statusMap as brt } from '../../carriers/brt/status.js';
70
71
  import { statusMap as bulgarianPosts } from '../../carriers/bulgarian-posts/status.js';
71
72
  import { statusMap as burdDelivery } from '../../carriers/burd-delivery/status.js';
72
73
  import { statusMap as bussgods } from '../../carriers/bussgods/status.js';
@@ -184,6 +185,7 @@ import { statusMap as fargoCourier } from '../../carriers/fargo-courier/status.j
184
185
  import { statusMap as fargoUz } from '../../carriers/fargo-uz/status.js';
185
186
  import { statusMap as fastdrop } from '../../carriers/fastdrop/status.js';
186
187
  import { statusMap as fastfrate } from '../../carriers/fastfrate/status.js';
188
+ import { statusMap as fedex } from '../../carriers/fedex/status.js';
187
189
  import { statusMap as fedexFreight } from '../../carriers/fedex-freight/status.js';
188
190
  import { statusMap as fedexPl } from '../../carriers/fedex-pl/status.js';
189
191
  import { statusMap as fermopoint } from '../../carriers/fermopoint/status.js';
@@ -329,6 +331,7 @@ import { statusMap as mngKargo } from '../../carriers/mng-kargo/status.js';
329
331
  import { statusMap as mongolInfinityPost } from '../../carriers/mongol-infinity-post/status.js';
330
332
  import { statusMap as morningGlobal } from '../../carriers/morning-global/status.js';
331
333
  import { statusMap as mrlGlobal } from '../../carriers/mrl-global/status.js';
334
+ import { statusMap as mrw } from '../../carriers/mrw/status.js';
332
335
  import { statusMap as mtscn } from '../../carriers/mtscn/status.js';
333
336
  import { statusMap as mylerz } from '../../carriers/mylerz/status.js';
334
337
  import { statusMap as nacex } from '../../carriers/nacex/status.js';
@@ -558,7 +561,7 @@ export const STATUS_MAPS = {
558
561
  'apple-express': appleExpress, 'aramex-au': aramexAu,
559
562
  'aramex-nz': aramexNz, 'aras-kargo': arasKargo, 'arrow-xl': arrowXl, 'atlantic-courier': atlanticCourier, 'austrian-post': austrianPost, averitt, aymakan, 'bangladesh-post': bangladeshPost,
560
563
  'barid-al-maghrib': baridAlMaghrib, belpost, 'best-overnite-express': bestOverniteExpress, 'better-trucks': betterTrucks, 'bex-express': bexExpress, 'bjs-home-delivery': bjsHomeDelivery, 'blue-express': blueExpress, blueex, bombino,
561
- bookurier, 'border-express': borderExpress, bosta, 'box-now': boxNow, bpost, 'bringer-parcel': bringerParcel, 'bulgarian-posts': bulgarianPosts, 'burd-delivery': burdDelivery, bussgods,
564
+ bookurier, 'border-express': borderExpress, bosta, 'box-now': boxNow, bpost, 'bringer-parcel': bringerParcel, brt, 'bulgarian-posts': bulgarianPosts, 'burd-delivery': burdDelivery, bussgods,
562
565
  byhd, c3x, cacesa, 'canada-post': canadaPost, cargus, castores, 'cbl-logistica': cblLogistica, ccfs, cdek, cdl, celeritas,
563
566
  'central-transport': centralTransport, 'ceska-posta': ceskaPosta, 'ceva-logistics': cevaLogistics, chazki, chilexpress, 'china-post': chinaPost,
564
567
  'chit-chats': chitChats, chronodiali, chronopost, chukou1, 'chunghwa-post': chunghwaPost, chunil, 'city-express-rs': cityExpressRs, 'citylink-express': citylinkExpress, citymail,
@@ -576,7 +579,7 @@ export const STATUS_MAPS = {
576
579
  envialia, 'eparcel-kr': eparcelKr, 'epost-global': epostGlobal, equick, estes, 'estes-forwarding': estesForwarding, eurodis, 'euroexpress-ba': euroexpressBa,
577
580
  'evdemo-lojistik': evdemoLojistik, evri, evropat, ewe, exelot,
578
581
  'exline-kz': exlineKz, exmate, 'express-one': expressOne, 'fan-courier': fanCourier, 'fargo-courier': fargoCourier, 'fargo-uz': fargoUz, fastdrop,
579
- fastfrate, 'fedex-freight': fedexFreight, 'fedex-pl': fedexPl, fermopoint, 'fg-delivery': fgDelivery, 'first-line': firstLine,
582
+ fastfrate, fedex, 'fedex-freight': fedexFreight, 'fedex-pl': fedexPl, fermopoint, 'fg-delivery': fgDelivery, 'first-line': firstLine,
580
583
  'flash-express': flashExpress,
581
584
  'flash-express-ph': flashExpressPh,
582
585
  fleetex, fleetoptics, flippost, followmont, 'forward-air': forwardAir, 'forza-delivery': forzaDelivery, 'four-px': fourPx, foxpost, 'franch-express': franchExpress, fukuyama, gaash, gati, gdex,
@@ -598,7 +601,7 @@ export const STATUS_MAPS = {
598
601
  'lotte-global': lotteGlobal, 'm-and-p': mAndP, 'm-one': mOne, 'madhur-courier': madhurCourier, 'magyar-posta': magyarPosta, mailamericas, 'mailplus-au': mailplusAu, mainfreight,
599
602
  'makati-express': makatiExpress, maltapost, 'maritime-ontario': maritimeOntario, 'maruti-air': marutiAir, matkahuolto, 'mauritius-post': mauritiusPost, mayiyinfu,
600
603
  'mds-collivery': mdsCollivery, meest, 'messenger-cz': messengerCz, 'metropolitan-warehouse': metropolitanWarehouse, 'micro-express': microExpress,
601
- 'midland-transport': midlandTransport, 'mng-kargo': mngKargo, 'mongol-infinity-post': mongolInfinityPost, 'morning-global': morningGlobal, 'mrl-global': mrlGlobal, mtscn, mylerz, nacex, naqel, nationex,
604
+ 'midland-transport': midlandTransport, 'mng-kargo': mngKargo, 'mongol-infinity-post': mongolInfinityPost, 'morning-global': morningGlobal, 'mrl-global': mrlGlobal, mrw, mtscn, mylerz, nacex, naqel, nationex,
602
605
  'next-level-delivery': nextLevelDelivery, 'nim-express': nimExpress, nipost, 'nippon-express': nipponExpress, noest, 'norsk-global': norskGlobal,
603
606
  northline, 'nova-global': novaGlobal, nox, 'nz-couriers': nzCouriers, 'oak-harbor': oakHarbor, oca, ocasa, 'ocs-ana': ocsAna,
604
607
  'old-dominion': oldDominion, 'olva-courier': olvaCourier, omgo, omniva,
@@ -2,7 +2,7 @@
2
2
  "openapi": "3.1.0",
3
3
  "info": {
4
4
  "title": "Universal Parcel Scraper",
5
- "version": "0.650.0"
5
+ "version": "0.650.4"
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.650.0-main.3085",
3
+ "version": "0.650.4-main.3091",
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",