universal-parcel-scraper 0.809.2-main.4367 → 0.810.1-main.4374

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
@@ -161,7 +161,7 @@ the command line's `track --source ParcelsApp` does the same.
161
161
  <!-- GENERATED:stages -->
162
162
  <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">
163
163
 
164
- The carrier folders record 17,645 such statuses, each filed under one stage.
164
+ The carrier folders record 17,648 such statuses, each filed under one stage.
165
165
  <!-- /GENERATED:stages -->
166
166
 
167
167
  Wording nobody recorded yet goes through a classifier that reads seven European languages.
package/data/catalog.json CHANGED
@@ -427,6 +427,8 @@
427
427
  "autoComplete": "postal-code"
428
428
  }
429
429
  ],
430
+ "recognitionRank": 524,
431
+ "recognitionPattern": "^1810\\d{14}$",
430
432
  "refresh": {
431
433
  "minMinutes": 30
432
434
  },
@@ -476,6 +478,10 @@
476
478
  {
477
479
  "pattern": "^(?=.{13}$|.{15}$)3S[A-Z]{1,3}\\d+$",
478
480
  "confidence": "low"
481
+ },
482
+ {
483
+ "pattern": "^1810\\d{14}$",
484
+ "confidence": "low"
479
485
  }
480
486
  ]
481
487
  },
@@ -20397,6 +20397,19 @@
20397
20397
  "heppner"
20398
20398
  ]
20399
20399
  },
20400
+ {
20401
+ "input": "181011000000000017",
20402
+ "carrier": "unknown",
20403
+ "confidence": "low",
20404
+ "candidates": [
20405
+ "spring-gds",
20406
+ "bpost",
20407
+ "ecoscooting"
20408
+ ],
20409
+ "asked": [
20410
+ "spring-gds"
20411
+ ]
20412
+ },
20400
20413
  {
20401
20414
  "input": "18113149",
20402
20415
  "carrier": "unknown",
@@ -685,6 +685,8 @@ var tt = {
685
685
  inputMode: "text",
686
686
  autoComplete: "postal-code"
687
687
  }],
688
+ recognitionRank: 524,
689
+ recognitionPattern: "^1810\\d{14}$",
688
690
  refresh: { minMinutes: 30 },
689
691
  localClocks: !0
690
692
  },
@@ -722,6 +724,10 @@ var tt = {
722
724
  {
723
725
  pattern: "^(?=.{13}$|.{15}$)3S[A-Z]{1,3}\\d+$",
724
726
  confidence: "low"
727
+ },
728
+ {
729
+ pattern: "^1810\\d{14}$",
730
+ confidence: "low"
725
731
  }
726
732
  ]
727
733
  },
@@ -26880,7 +26886,8 @@ var tt = {
26880
26886
  "spring-gds": [
26881
26887
  "spring-gds-1",
26882
26888
  "spring-gds-2",
26883
- "spring-gds-3"
26889
+ "spring-gds-3",
26890
+ "spring-gds-1810"
26884
26891
  ],
26885
26892
  postlogistics: ["postlogistics-dashed-reference", "postlogistics-compact-reference"],
26886
26893
  dachser: [],
@@ -34304,6 +34311,7 @@ var Uu = {
34304
34311
  speedaf: 989,
34305
34312
  speedex: 938,
34306
34313
  speedy: 280,
34314
+ "spring-gds": 524,
34307
34315
  "spx-br": 982,
34308
34316
  "st-courier": 753,
34309
34317
  "stallion-express": 105,
@@ -7,6 +7,8 @@
7
7
  {"wording": "预计…飞", "language": "zh", "english": "Flight booked", "stage": "in_transit", "confirmedBy": "source", "note": "The row names the day the flight is expected to leave, as month-day; the key replaces it with an ellipsis and the English leaves it out, since it is still to come. As a universal provider relays it."},
8
8
  {"wording": "已飞未落地", "language": "zh", "english": "Flight departed, not yet landed", "stage": "in_transit", "confirmedBy": "source", "note": "As a universal provider relays it."},
9
9
  {"wording": "一程已落地 二程预计…飞", "language": "zh", "english": "First flight landed, connecting flight booked", "stage": "in_transit", "confirmedBy": "source", "note": "The first leg has landed; the row names the day the second is expected to leave, which the key replaces with an ellipsis. As a universal provider relays it."},
10
- {"wording": "二程已飞未落地", "language": "zh", "english": "Connecting flight departed, not yet landed", "stage": "in_transit", "confirmedBy": "source", "note": "As a universal provider relays it."}
10
+ {"wording": "二程已飞未落地", "language": "zh", "english": "Connecting flight departed, not yet landed", "stage": "in_transit", "confirmedBy": "source", "note": "As a universal provider relays it."},
11
+ {"wording": "已落地 等待拆板提货", "language": "zh", "english": "Landed, awaiting unloading for collection", "stage": "in_transit", "confirmedBy": "live", "firstSeen": "2026-10-29", "note": "The last flight has landed; its pallets are still to be broken down and the shipment collected from the airport."},
12
+ {"wording": "已提回 等待清关", "language": "zh", "english": "Collected from the airport, awaiting customs clearance", "stage": "customs", "confirmedBy": "live", "firstSeen": "2026-10-29", "note": "Collected from the airport and held for import clearance, as other forwarders' rows of the same step read."}
11
13
  ]
12
14
  }
@@ -22,7 +22,7 @@
22
22
  * delivery postcode. With a Dutch postcode, or another country's postcode
23
23
  * given with that country, that tracker is asked first.
24
24
  */
25
- import type { AdapterFactory, TrackingContext } from '../../core/adapter/index.js';
25
+ import { type AdapterFactory, type TrackingContext } from '../../core/adapter/index.js';
26
26
  import type { CarrierResult } from '../../core/result/index.js';
27
27
  import { type StepRecorder } from '../../core/telemetry/index.js';
28
28
  /** Projects one `tracking-items` payload. Pure: the offline tests target this. */
@@ -1,3 +1,29 @@
1
+ /**
2
+ * PostNL international tracking (carrier id `spring-gds`).
3
+ *
4
+ * The folder keeps the historical `spring-gds` id for parcels and clients that
5
+ * already store it; the carrier is presented as PostNL everywhere else. Spring
6
+ * GDS is PostNL's international subsidiary and its mailingtechnology.com
7
+ * portal shows the same barcode (see this folder's README).
8
+ *
9
+ * `postnl.post` hands out a visitor token that lasts a day to anyone who asks,
10
+ * then accepts a batch tracking request with it. The tracker keeps the token
11
+ * for its later lookups. Both calls replay once after a transport failure or an
12
+ * HTTP 502/503/504, and an HTTP 429 only when it supplies a short, valid
13
+ * `Retry-After` that ends before the lookup does. A 429 left standing names the
14
+ * wait PostNL asked for.
15
+ *
16
+ * The response echoes the requested barcodes, so the item whose `item` equals
17
+ * the requested number is the only one read. PostNL answers an unknown barcode
18
+ * with an ordinary item that has no events and says so in `message`.
19
+ *
20
+ * A 3S barcode sent within the Netherlands is not there: PostNL's domestic
21
+ * tracker on jouw.postnl.nl shows it, given the barcode, the country and the
22
+ * delivery postcode. With a Dutch postcode, or another country's postcode
23
+ * given with that country, that tracker is asked first.
24
+ */
25
+ import { recognizeFromLookup } from '../../core/adapter/index.js';
26
+ import { CARRIER_DEFINITIONS } from '../../core/catalog/definitions.js';
1
27
  import { DateTime } from 'luxon';
2
28
  import { CarrierError, IndeterminateError, InputRequiredError, NoHistoryError, NotFoundError, RateLimitedError, SchemaError, TransportError, UpstreamHttpError, UpstreamNetworkError, } from '../../core/errors/index.js';
3
29
  import { runSteps } from '../../core/runner/index.js';
@@ -8,6 +34,7 @@ import { fetchBounded, parseJsonBytes, userAgentOf } from '../../core/transport/
8
34
  import { isRecord } from '../../core/types.js';
9
35
  import { postNLCode, postNLDomesticStage, postNLStatus } from './status.js';
10
36
  const PROVIDER = 'PostNL';
37
+ const RECOGNIZED = new RegExp(CARRIER_DEFINITIONS['spring-gds']?.tracking.recognitionPattern ?? '(?!)');
11
38
  const TOKEN_URL = 'https://postnl.post/api/v1/auth/token';
12
39
  const TRACK_URL = 'https://postnl.post/api/v1/tracking-items';
13
40
  const DOMESTIC_URL = 'https://jouw.postnl.nl/track-and-trace/api/trackAndTrace';
@@ -449,5 +476,8 @@ export const adapter = (environment) => {
449
476
  // on the same host, and each replays once inside the step.
450
477
  steps: ['domestic', 'direct'],
451
478
  track: (input, context) => tracker.fetch(input.number, context, input.postcode, input.postcodeCountry),
479
+ // Only the series the catalog's recognition pattern names: a 3S barcode PostNL
480
+ // does not find may still be on the domestic tracker, behind a postcode.
481
+ recognize: (number, context) => recognizeFromLookup(() => tracker.fetch(number, context), () => RECOGNIZED.test(comparableIdentifier(number))),
452
482
  };
453
483
  };
@@ -40,6 +40,10 @@
40
40
  "domestic",
41
41
  "direct"
42
42
  ],
43
+ "recognition": {
44
+ "rank": 524,
45
+ "pattern": "^1810\\d{14}$"
46
+ },
43
47
  "refresh": {
44
48
  "minMinutes": 30
45
49
  },
@@ -118,6 +122,11 @@
118
122
  "id": "spring-gds-3",
119
123
  "pattern": "^(?=.{13}$|.{15}$)3S[A-Z]{1,3}\\d+$",
120
124
  "confidence": "low"
125
+ },
126
+ {
127
+ "id": "spring-gds-1810",
128
+ "pattern": "^1810\\d{14}$",
129
+ "confidence": "low"
121
130
  }
122
131
  ]
123
132
  }
@@ -595,6 +595,23 @@
595
595
  "confidence": "high"
596
596
  },
597
597
  "note": "A PostNL tracking link archived by the Wayback Machine. PostNL's tracker returns its history, an item delivered abroad."
598
+ },
599
+ {
600
+ "number": "181011000000000017",
601
+ "role": "shipment",
602
+ "evidence": "synthetic",
603
+ "derivedFrom": "donated_real",
604
+ "shapeConfirmed": "2026-10-29",
605
+ "expect": {
606
+ "carrier": "unknown",
607
+ "confidence": "low",
608
+ "candidates": [
609
+ "spring-gds",
610
+ "bpost",
611
+ "ecoscooting"
612
+ ]
613
+ },
614
+ "note": "Made-up number in the shape of a parcel for Italy that PostNL's international tracker answered: 1810 and fourteen digits."
598
615
  }
599
616
  ]
600
617
  }
@@ -6,6 +6,7 @@
6
6
  {"code": "Warehousing_Packed", "wording": "Parcel has been packed and is ready for outbound shipping", "stage": "in_transit", "language": "en", "confirmedBy": "live", "firstSeen": "2026-10-21", "note": "Packed at the warehouse for the line abroad; its Chinese name is 包裹装箱."},
7
7
  {"code": "Warehousing_Departed", "wording": "Departed from the original facility", "stage": "in_transit", "language": "en", "confirmedBy": "live", "firstSeen": "2026-10-25", "note": "The parcel left Techlink's warehouse in China."},
8
8
  {"code": "Decl_ExportDone", "wording": "Export customs declaration completed", "stage": "in_transit", "language": "en", "confirmedBy": "live", "firstSeen": "2026-10-25", "note": "China's export declaration, written after the parcel left the warehouse; the parcel is not held in customs."},
9
- {"code": "InTransit_Departure", "wording": "Depart from the original airport", "stage": "in_transit", "language": "en", "confirmedBy": "live", "firstSeen": "2026-10-26", "note": "The flight left the airport in China."}
9
+ {"code": "InTransit_Departure", "wording": "Depart from the original airport", "stage": "in_transit", "language": "en", "confirmedBy": "live", "firstSeen": "2026-10-26", "note": "The flight left the airport in China."},
10
+ {"code": "InTransit_Arrival", "wording": "Arrive at the destination airport", "stage": "in_transit", "language": "en", "confirmedBy": "live", "firstSeen": "2026-10-29", "note": "The flight landed in the destination country, before its import clearance."}
10
11
  ]
11
12
  }
@@ -427,6 +427,8 @@
427
427
  "autoComplete": "postal-code"
428
428
  }
429
429
  ],
430
+ "recognitionRank": 524,
431
+ "recognitionPattern": "^1810\\d{14}$",
430
432
  "refresh": {
431
433
  "minMinutes": 30
432
434
  },
@@ -476,6 +478,10 @@
476
478
  {
477
479
  "pattern": "^(?=.{13}$|.{15}$)3S[A-Z]{1,3}\\d+$",
478
480
  "confidence": "low"
481
+ },
482
+ {
483
+ "pattern": "^1810\\d{14}$",
484
+ "confidence": "low"
479
485
  }
480
486
  ]
481
487
  },
@@ -298,6 +298,8 @@ export declare const CARRIER_CATALOG: {
298
298
  readonly inputMode: "text";
299
299
  readonly autoComplete: "postal-code";
300
300
  }];
301
+ readonly recognitionRank: 524;
302
+ readonly recognitionPattern: "^1810\\d{14}$";
301
303
  readonly refresh: {
302
304
  readonly minMinutes: 30;
303
305
  };
@@ -330,6 +332,9 @@ export declare const CARRIER_CATALOG: {
330
332
  }, {
331
333
  readonly pattern: "^(?=.{13}$|.{15}$)3S[A-Z]{1,3}\\d+$";
332
334
  readonly confidence: "low";
335
+ }, {
336
+ readonly pattern: "^1810\\d{14}$";
337
+ readonly confidence: "low";
333
338
  }];
334
339
  };
335
340
  readonly postlogistics: {
@@ -428,6 +428,8 @@ export const CARRIER_CATALOG = {
428
428
  "autoComplete": "postal-code"
429
429
  }
430
430
  ],
431
+ "recognitionRank": 524,
432
+ "recognitionPattern": "^1810\\d{14}$",
431
433
  "refresh": {
432
434
  "minMinutes": 30
433
435
  },
@@ -477,6 +479,10 @@ export const CARRIER_CATALOG = {
477
479
  {
478
480
  "pattern": "^(?=.{13}$|.{15}$)3S[A-Z]{1,3}\\d+$",
479
481
  "confidence": "low"
482
+ },
483
+ {
484
+ "pattern": "^1810\\d{14}$",
485
+ "confidence": "low"
480
486
  }
481
487
  ]
482
488
  },
@@ -36392,7 +36398,7 @@ export const DETECTION_RULE_IDS = {
36392
36398
  "aliexpress": ["aliexpress-1", "aliexpress-cnfr", "aliexpress-2", "aliexpress-maersk", "aliexpress-lp-reference", "aliexpress-ap-reference", "aliexpress-cnbr", "aliexpress-cnmex", "aliexpress-cnusup", "aliexpress-cacainiao", "aliexpress-rl", "aliexpress-jncn", "aliexpress-gfus", "aliexpress-ecsc-aaro", "aliexpress-pa", "aliexpress-f", "aliexpress-9c-i", "aliexpress-9c-j", "aliexpress-rtz", "aliexpress-fmp", "aliexpress-sda-waybill", "aliexpress-ty", "aliexpress-mi", "aliexpress-xx"],
36393
36399
  "sunyou": ["sunyou-1", "sunyou-2", "sunyou-3"],
36394
36400
  "hermes": ["hermes-delivery-note"],
36395
- "spring-gds": ["spring-gds-1", "spring-gds-2", "spring-gds-3"],
36401
+ "spring-gds": ["spring-gds-1", "spring-gds-2", "spring-gds-3", "spring-gds-1810"],
36396
36402
  "postlogistics": ["postlogistics-dashed-reference", "postlogistics-compact-reference"],
36397
36403
  "dachser": [],
36398
36404
  "dhl": ["dhl-1", "dhl-14", "dhl-16", "dhl-15", "dhl-9", "dhl-8", "dhl-10", "dhl-3", "dhl-13", "dhl-12", "dhl-7"],
@@ -404,6 +404,7 @@ export const CARRIER_RECOGNITION_RANKS = {
404
404
  "speedaf": 989,
405
405
  "speedex": 938,
406
406
  "speedy": 280,
407
+ "spring-gds": 524,
407
408
  "spx-br": 982,
408
409
  "st-courier": 753,
409
410
  "stallion-express": 105,
@@ -560,13 +560,18 @@ const LOCAL_WALL_TIME = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?$/;
560
560
  // A flight written before it leaves is still to come: no scan, as the lines' own trackers drop it.
561
561
  // 17FEIA's estimated departure, and the flight DHLink, Intel-Valley and Mayi Yinfu book.
562
562
  const flightToCome = (label) => FLIGHT_ESTIMATE.test(label) || DHLINK_FLIGHT.test(label) || intelValleyFlight(label) || MAYIYINFU_FLIGHT.test(label);
563
+ // A carrier's schedule, as 17TRACK relays it beside the milestones: an ocean line's estimated vessel departure and
564
+ // arrival, each again once updated, and its cargo cut-off, and a flight's ETD or ETA. Each is dated when it is due,
565
+ // often weeks ahead, so it is no scan.
566
+ const SCHEDULE = /^(?:estimated vessel (?:departure|arrival)|cfs\/cy cut-off date)(?: \(last updated\))?$|^flight et[ad]\b/i;
563
567
  // A time or date in brackets with no words, as 17TRACK relays a Juxi Express row between the flight
564
- // being arranged at the airport and the departure: it says nothing happened, so it is no scan.
565
- const BARE_TIME = /^[((]\s*\d{1,2}\s*[::]\s*\d{1,2}\s*[))]$/;
568
+ // being arranged at the airport and the departure, and ParcelsApp one dated by month and day ("(10.20)")
569
+ // between its departure and arrival: it says nothing happened, so it is no scan.
570
+ const BARE_TIME = /^[((]\s*\d{1,2}\s*[::..]\s*\d{1,2}\s*[))]$/;
566
571
  function describe(description, stage) {
567
572
  // A copy of SunYou's departure drops the last-mile number, as SunYou's own result does.
568
573
  const label = sunYouWording(relayedNotice(text(description)).replace(CLEARANCE_STATUS, ''));
569
- if (!label || isNotice(label) || flightToCome(label) || BARE_TIME.test(label))
574
+ if (!label || isNotice(label) || flightToCome(label) || SCHEDULE.test(label) || BARE_TIME.test(label))
570
575
  return null;
571
576
  const declared = typeof stage === 'string' && Object.hasOwn(STAGES, stage) ? STAGES[stage] : undefined;
572
577
  // Established source semantics (e.g. handoff/negation) remain first. A real
@@ -2,7 +2,7 @@
2
2
  "openapi": "3.1.0",
3
3
  "info": {
4
4
  "title": "Universal Parcel Scraper",
5
- "version": "0.809.2-main.4367"
5
+ "version": "0.810.1-main.4374"
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.809.2-main.4367",
3
+ "version": "0.810.1-main.4374",
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",