universal-parcel-scraper 0.861.0-main.4621 → 0.862.1-main.4625

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.
Files changed (40) hide show
  1. package/README.md +2 -2
  2. package/data/catalog.json +9 -3
  3. package/data/coverage-summary.json +1 -1
  4. package/data/detection-golden.json +36 -0
  5. package/dist/browser/scraper.js +12 -3
  6. package/dist/carriers/amazon-shipping/adapter.js +5 -2
  7. package/dist/carriers/amazon-shipping/status.d.ts +2 -0
  8. package/dist/carriers/amazon-shipping/status.js +8 -3
  9. package/dist/carriers/consegnia/adapter.d.ts +17 -0
  10. package/dist/carriers/consegnia/adapter.js +107 -0
  11. package/dist/carriers/consegnia/carrier.json +38 -7
  12. package/dist/carriers/consegnia/parser.d.ts +29 -0
  13. package/dist/carriers/consegnia/parser.js +169 -0
  14. package/dist/carriers/consegnia/status.d.ts +7 -0
  15. package/dist/carriers/consegnia/status.js +23 -0
  16. package/dist/carriers/consegnia/statuses.json +8 -1
  17. package/dist/carriers/fmx/adapter.d.ts +18 -0
  18. package/dist/carriers/fmx/adapter.js +50 -0
  19. package/dist/carriers/fmx/carrier.json +43 -7
  20. package/dist/carriers/fmx/parser.d.ts +17 -0
  21. package/dist/carriers/fmx/parser.js +140 -0
  22. package/dist/carriers/fmx/status.d.ts +7 -0
  23. package/dist/carriers/fmx/status.js +30 -0
  24. package/dist/carriers/fmx/statuses.json +5 -1
  25. package/dist/carriers/hitch/adapter.d.ts +18 -0
  26. package/dist/carriers/hitch/adapter.js +49 -0
  27. package/dist/carriers/hitch/carrier.json +43 -7
  28. package/dist/carriers/hitch/parser.d.ts +19 -0
  29. package/dist/carriers/hitch/parser.js +151 -0
  30. package/dist/carriers/hitch/status.d.ts +7 -0
  31. package/dist/carriers/hitch/status.js +22 -0
  32. package/dist/carriers/hitch/statuses.json +9 -1
  33. package/dist/core/catalog/statusMaps.js +6 -3
  34. package/dist/data/catalog.json +9 -3
  35. package/dist/generated/catalog.d.ts +9 -3
  36. package/dist/generated/catalog.js +9 -3
  37. package/dist/generated/recognition.js +3 -0
  38. package/dist/generated/registry.js +9 -3
  39. package/dist/server/openapi.json +1 -1
  40. package/package.json +1 -1
package/README.md CHANGED
@@ -13,7 +13,7 @@ The engine behind [Peek](https://peektracker.com) ([GitHub](https://github.com/p
13
13
  [![License: Apache-2.0](https://img.shields.io/badge/core-Apache--2.0-blue.svg)](LICENSE)
14
14
 
15
15
  <!-- GENERATED:summary -->
16
- **3,500+ carriers** through **658 dedicated adapters** and **5 universal fallbacks**
16
+ **3,500+ carriers** through **661 dedicated adapters** and **5 universal fallbacks**
17
17
 
18
18
  <sub>1045 catalog entries · 145 countries represented</sub>
19
19
  <!-- /GENERATED:summary -->
@@ -163,7 +163,7 @@ the command line's `track --source ParcelsApp` does the same.
163
163
  <!-- GENERATED:stages -->
164
164
  <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">
165
165
 
166
- The carrier folders record 17,879 such statuses, each filed under one stage.
166
+ The carrier folders record 17,895 such statuses, each filed under one stage.
167
167
  <!-- /GENERATED:stages -->
168
168
 
169
169
  Wording nobody recorded yet goes through a classifier that reads seven European languages.
package/data/catalog.json CHANGED
@@ -11611,7 +11611,9 @@
11611
11611
  "timezone": "Asia/Manila",
11612
11612
  "tracking": {
11613
11613
  "mode": "automatic",
11614
- "adapter": "universal"
11614
+ "adapter": "consegnia",
11615
+ "recognitionRank": 57,
11616
+ "localClocks": true
11615
11617
  },
11616
11618
  "canaryUrl": "https://clc-express.com/",
11617
11619
  "trackingUrlTemplate": "https://clc-express.com/track-delivery?awb={trackingNumber}",
@@ -18128,7 +18130,9 @@
18128
18130
  "timezone": "Asia/Kuala_Lumpur",
18129
18131
  "tracking": {
18130
18132
  "mode": "automatic",
18131
- "adapter": "universal"
18133
+ "adapter": "fmx",
18134
+ "recognitionRank": 147,
18135
+ "localClocks": true
18132
18136
  },
18133
18137
  "canaryUrl": "https://www.fmx.asia/",
18134
18138
  "trackingUrlTemplate": "https://www.fmx.asia/result_detail.php?p=1&conno={trackingNumber}",
@@ -20577,7 +20581,9 @@
20577
20581
  "timezone": "Asia/Manila",
20578
20582
  "tracking": {
20579
20583
  "mode": "automatic",
20580
- "adapter": "universal"
20584
+ "adapter": "hitch",
20585
+ "recognitionRank": 117,
20586
+ "localClocks": true
20581
20587
  },
20582
20588
  "canaryUrl": "https://hitch.com.ph/",
20583
20589
  "trackingUrlTemplate": "https://hitch.com.ph/tracking?no={trackingNumber}",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "catalog": 1045,
3
- "activeAdapters": 658,
3
+ "activeAdapters": 661,
4
4
  "countries": 145,
5
5
  "referenceCarriers": 100,
6
6
  "comparison": {
@@ -87063,6 +87063,9 @@
87063
87063
  "confidence": "low",
87064
87064
  "candidates": [
87065
87065
  "fmx"
87066
+ ],
87067
+ "asked": [
87068
+ "fmx"
87066
87069
  ]
87067
87070
  },
87068
87071
  {
@@ -87071,6 +87074,9 @@
87071
87074
  "confidence": "low",
87072
87075
  "candidates": [
87073
87076
  "fmx"
87077
+ ],
87078
+ "asked": [
87079
+ "fmx"
87074
87080
  ]
87075
87081
  },
87076
87082
  {
@@ -87079,6 +87085,9 @@
87079
87085
  "confidence": "low",
87080
87086
  "candidates": [
87081
87087
  "fmx"
87088
+ ],
87089
+ "asked": [
87090
+ "fmx"
87082
87091
  ]
87083
87092
  },
87084
87093
  {
@@ -100584,6 +100593,9 @@
100584
100593
  "confidence": "low",
100585
100594
  "candidates": [
100586
100595
  "hitch"
100596
+ ],
100597
+ "asked": [
100598
+ "hitch"
100587
100599
  ]
100588
100600
  },
100589
100601
  {
@@ -100592,6 +100604,9 @@
100592
100604
  "confidence": "low",
100593
100605
  "candidates": [
100594
100606
  "hitch"
100607
+ ],
100608
+ "asked": [
100609
+ "hitch"
100595
100610
  ]
100596
100611
  },
100597
100612
  {
@@ -100600,6 +100615,9 @@
100600
100615
  "confidence": "low",
100601
100616
  "candidates": [
100602
100617
  "hitch"
100618
+ ],
100619
+ "asked": [
100620
+ "hitch"
100603
100621
  ]
100604
100622
  },
100605
100623
  {
@@ -100608,6 +100626,9 @@
100608
100626
  "confidence": "low",
100609
100627
  "candidates": [
100610
100628
  "hitch"
100629
+ ],
100630
+ "asked": [
100631
+ "hitch"
100611
100632
  ]
100612
100633
  },
100613
100634
  {
@@ -100616,6 +100637,9 @@
100616
100637
  "confidence": "low",
100617
100638
  "candidates": [
100618
100639
  "hitch"
100640
+ ],
100641
+ "asked": [
100642
+ "hitch"
100619
100643
  ]
100620
100644
  },
100621
100645
  {
@@ -100624,6 +100648,9 @@
100624
100648
  "confidence": "low",
100625
100649
  "candidates": [
100626
100650
  "hitch"
100651
+ ],
100652
+ "asked": [
100653
+ "hitch"
100627
100654
  ]
100628
100655
  },
100629
100656
  {
@@ -100632,6 +100659,9 @@
100632
100659
  "confidence": "low",
100633
100660
  "candidates": [
100634
100661
  "hitch"
100662
+ ],
100663
+ "asked": [
100664
+ "hitch"
100635
100665
  ]
100636
100666
  },
100637
100667
  {
@@ -100640,6 +100670,9 @@
100640
100670
  "confidence": "low",
100641
100671
  "candidates": [
100642
100672
  "hitch"
100673
+ ],
100674
+ "asked": [
100675
+ "hitch"
100643
100676
  ]
100644
100677
  },
100645
100678
  {
@@ -100648,6 +100681,9 @@
100648
100681
  "confidence": "low",
100649
100682
  "candidates": [
100650
100683
  "hitch"
100684
+ ],
100685
+ "asked": [
100686
+ "hitch"
100651
100687
  ]
100652
100688
  },
100653
100689
  {
@@ -9243,7 +9243,9 @@ var tt = {
9243
9243
  timezone: "Asia/Manila",
9244
9244
  tracking: {
9245
9245
  mode: "automatic",
9246
- adapter: "universal"
9246
+ adapter: "consegnia",
9247
+ recognitionRank: 57,
9248
+ localClocks: !0
9247
9249
  },
9248
9250
  canaryUrl: "https://clc-express.com/",
9249
9251
  trackingUrlTemplate: "https://clc-express.com/track-delivery?awb={trackingNumber}",
@@ -14105,7 +14107,9 @@ var tt = {
14105
14107
  timezone: "Asia/Kuala_Lumpur",
14106
14108
  tracking: {
14107
14109
  mode: "automatic",
14108
- adapter: "universal"
14110
+ adapter: "fmx",
14111
+ recognitionRank: 147,
14112
+ localClocks: !0
14109
14113
  },
14110
14114
  canaryUrl: "https://www.fmx.asia/",
14111
14115
  trackingUrlTemplate: "https://www.fmx.asia/result_detail.php?p=1&conno={trackingNumber}",
@@ -15930,7 +15934,9 @@ var tt = {
15930
15934
  timezone: "Asia/Manila",
15931
15935
  tracking: {
15932
15936
  mode: "automatic",
15933
- adapter: "universal"
15937
+ adapter: "hitch",
15938
+ recognitionRank: 117,
15939
+ localClocks: !0
15934
15940
  },
15935
15941
  canaryUrl: "https://hitch.com.ph/",
15936
15942
  trackingUrlTemplate: "https://hitch.com.ph/tracking?no={trackingNumber}",
@@ -39169,6 +39175,7 @@ var ed = {
39169
39175
  "closer-logistics": 849,
39170
39176
  "colis-prive": 740,
39171
39177
  "connect-couriers": 831,
39178
+ consegnia: 57,
39172
39179
  coordinadora: 973,
39173
39180
  "copa-courier": 773,
39174
39181
  corre: 587,
@@ -39259,6 +39266,7 @@ var ed = {
39259
39266
  fleetex: 571,
39260
39267
  fleetoptics: 763,
39261
39268
  flippost: 568,
39269
+ fmx: 147,
39262
39270
  "forward-air": 755,
39263
39271
  "forza-delivery": 633,
39264
39272
  "four-px": 570,
@@ -39301,6 +39309,7 @@ var ed = {
39301
39309
  hermes: 602,
39302
39310
  "hermes-at": 876,
39303
39311
  "hermes-de": 840,
39312
+ hitch: 117,
39304
39313
  "houng-ah-loun": 637,
39305
39314
  hovership: 687,
39306
39315
  "hr-parcel": 887,
@@ -6,7 +6,7 @@ import { ChallengeError, IndeterminateError, InvalidInputError, NotFoundError, S
6
6
  import { listsNewestFirst } from '../../core/result/order.js';
7
7
  import { cleanScalar, decodeText, fetchBounded, parseJsonBytes, userAgentOf } from '../../core/transport/index.js';
8
8
  import { isRecord } from '../../core/types.js';
9
- import { classifyStatus, statusKey } from './status.js';
9
+ import { classifyStatus, DELAYED, statusKey } from './status.js';
10
10
  const PROVIDER = 'Amazon Shipping';
11
11
  const DEFAULT_TIMEOUT_MS = 15_000;
12
12
  const MAX_RESPONSE_BYTES = 2_000_000;
@@ -266,7 +266,10 @@ export function parseAmazonShippingTrackingResponse(payload, zone = 'Europe/Pari
266
266
  // that had just left their pickup site and went on being scanned for another day.
267
267
  const unscannedDelivery = current.stage === 'delivered' && latestKnown !== undefined
268
268
  && !parsedEvents.some((item) => item.classified.stage === 'delivered');
269
- const active = current.status !== 'unknown' && !echoesNewest && !unscannedDelivery ? current : latestKnown?.classified;
269
+ // A delay moves nothing: a parcel whose scans show only its label keeps that stage, with the delay as its text.
270
+ const unmovedDelay = current === DELAYED && latestKnown?.classified.stage === 'registered';
271
+ const active = unmovedDelay ? { ...latestKnown.classified, description: DELAYED.description }
272
+ : current.status !== 'unknown' && !echoesNewest && !unscannedDelivery ? current : latestKnown?.classified;
270
273
  if (!active) {
271
274
  throw new SchemaError(PROVIDER, 'Amazon Shipping returned incomplete tracking details');
272
275
  }
@@ -20,6 +20,8 @@ export interface ClassifiedStatus {
20
20
  description: string;
21
21
  }
22
22
  export declare const UNKNOWN_STATUS: ClassifiedStatus;
23
+ /** A late delivery: no exception, and no movement of its own. */
24
+ export declare const DELAYED: ClassifiedStatus;
23
25
  /** Fold any provider signal into the comparison key: lower case, letters and digits only. */
24
26
  export declare function statusKey(value: unknown): string;
25
27
  /** Classify one or more provider signals; the first matching rule wins. */
@@ -3,6 +3,12 @@ export const UNKNOWN_STATUS = {
3
3
  stage: 'in_transit',
4
4
  description: 'Amazon Shipping update',
5
5
  };
6
+ /** A late delivery: no exception, and no movement of its own. */
7
+ export const DELAYED = {
8
+ status: 'in_transit',
9
+ stage: 'in_transit',
10
+ description: 'Delivery delayed',
11
+ };
6
12
  /** Fold any provider signal into the comparison key: lower case, letters and digits only. */
7
13
  export function statusKey(value) {
8
14
  if (typeof value !== 'string' && typeof value !== 'number')
@@ -48,9 +54,8 @@ export function classifyStatus(...values) {
48
54
  ])) {
49
55
  return { status: 'exception', stage: 'failed_attempt', description: 'Delivery issue' };
50
56
  }
51
- if (includesAny(key, ['delayed', 'late'])) {
52
- return { status: 'in_transit', stage: 'in_transit', description: 'Delivery delayed' };
53
- }
57
+ if (includesAny(key, ['delayed', 'late']))
58
+ return DELAYED;
54
59
  if (includesAny(key, ['delivered'])) {
55
60
  return { status: 'delivered', stage: 'delivered', description: 'Delivered' };
56
61
  }
@@ -0,0 +1,17 @@
1
+ import { type AdapterFactory, type TrackingContext } from '../../core/adapter/index.js';
2
+ import type { CarrierResult } from '../../core/result/index.js';
3
+ import { type StepRecorder } from '../../core/telemetry/index.js';
4
+ /** The tracking page, which opens the session its lookup needs and carries the session's token. */
5
+ export declare const CONSEGNIA_PAGE = "https://clc-express.com/track-delivery";
6
+ /** The lookup the tracking page's script posts the number to. */
7
+ export declare const CONSEGNIA_LOOKUP = "https://clc-express.com/track-delivery-result";
8
+ export declare class ConsegniaTracker {
9
+ private readonly options;
10
+ constructor(options?: {
11
+ fetcher?: typeof fetch;
12
+ recorder?: StepRecorder;
13
+ userAgent?: string;
14
+ });
15
+ fetch(raw: string, context?: TrackingContext): Promise<CarrierResult>;
16
+ }
17
+ export declare const adapter: AdapterFactory;
@@ -0,0 +1,107 @@
1
+ import { accepted, recognizeFromLookup } from '../../core/adapter/index.js';
2
+ import { ChallengeError, IndeterminateError, NotFoundError, SchemaError, TransportError } from '../../core/errors/index.js';
3
+ import { runSteps } from '../../core/runner/index.js';
4
+ import { firstAttemptMs, networkRetryStep } from '../../core/runner/networkRetry.js';
5
+ import { NOOP_RECORDER } from '../../core/telemetry/index.js';
6
+ import { decodeText, fetchBounded, userAgentOf } from '../../core/transport/index.js';
7
+ import { CARRIER, CONSEGNIA_MAX_BYTES, consegniaJson, consegniaToken, isBlockPage, isUnknownShipment, normalizeConsegniaNumber, parseConsegnia, PROVIDER, } from './parser.js';
8
+ const ORIGIN = 'https://clc-express.com';
9
+ /** The tracking page, which opens the session its lookup needs and carries the session's token. */
10
+ export const CONSEGNIA_PAGE = `${ORIGIN}/track-delivery`;
11
+ /** The lookup the tracking page's script posts the number to. */
12
+ export const CONSEGNIA_LOOKUP = `${ORIGIN}/track-delivery-result`;
13
+ const REDIRECTS = [301, 302, 303, 307, 308];
14
+ // The lookup answers an unknown number with its own HTTP 404; any other is a missing route.
15
+ const GONE = [404, 410];
16
+ // Laravel's refusal of a token or session it did not issue, or one that expired.
17
+ const REFUSED_SESSION = 419;
18
+ const COOKIE_NAME = /^[A-Za-z0-9!#$%&'*+.^_`|~-]{1,128}$/;
19
+ const COOKIE_VALUE = /^[^\s;,]{1,4096}$/;
20
+ const MAX_COOKIES = 8;
21
+ const DEFAULT_BUDGET_MS = 15_000;
22
+ /**
23
+ * The cookies the tracking page sets, as the lookup sends them back: Laravel's
24
+ * session, its token cookie and the site's own. Null without the session.
25
+ */
26
+ function sessionCookies(response) {
27
+ const pairs = [];
28
+ for (const header of response.headers.getSetCookie()) {
29
+ const pair = header.split(';', 1)[0].trim();
30
+ const separator = pair.indexOf('=');
31
+ const name = pair.slice(0, separator);
32
+ if (separator > 0 && COOKIE_NAME.test(name) && COOKIE_VALUE.test(pair.slice(separator + 1)) && pairs.length < MAX_COOKIES)
33
+ pairs.push(pair);
34
+ }
35
+ return pairs.some((pair) => /^[^=]+_session=/.test(pair)) ? pairs.join('; ') : null;
36
+ }
37
+ export class ConsegniaTracker {
38
+ options;
39
+ constructor(options = {}) {
40
+ this.options = options;
41
+ }
42
+ async fetch(raw, context = {}) {
43
+ const number = normalizeConsegniaNumber(raw);
44
+ const userAgent = userAgentOf(this.options.userAgent);
45
+ // One attempt opens a session on the tracking page and posts the number with its token, as the page's script does.
46
+ const attempt = async (signal, timeoutMs) => {
47
+ const deadline = performance.now() + timeoutMs;
48
+ // A request left without time fails as a timeout, which the retry can recover.
49
+ const left = () => Math.max(1, Math.floor(deadline - performance.now()));
50
+ const page = await fetchBounded(CONSEGNIA_PAGE, { method: 'GET', signal, headers: {
51
+ Accept: 'text/html,application/xhtml+xml', 'User-Agent': userAgent,
52
+ } }, { provider: PROVIDER, timeoutMs: left(), maxBytes: CONSEGNIA_MAX_BYTES, redirect: 'manual',
53
+ allowHttpStatuses: [...REDIRECTS, ...GONE, 403], fetcher: this.options.fetcher });
54
+ if (page.response.status === 403)
55
+ throw new ChallengeError(PROVIDER, 'Consegnia refused its tracking page', { status: 403 });
56
+ if (page.response.status !== 200) {
57
+ throw new TransportError(PROVIDER, 'Consegnia tracking page is unavailable', { status: page.response.status });
58
+ }
59
+ const html = decodeText(page.bytes);
60
+ const token = consegniaToken(html);
61
+ if (!token) {
62
+ if (isBlockPage(html))
63
+ throw new ChallengeError(PROVIDER, 'Consegnia answered the tracking page with a browser check');
64
+ throw new SchemaError(PROVIDER, 'Consegnia tracking page carries no lookup token');
65
+ }
66
+ const cookie = sessionCookies(page.response);
67
+ if (!cookie)
68
+ throw new SchemaError(PROVIDER, 'Consegnia tracking page opened no session');
69
+ const lookup = await fetchBounded(CONSEGNIA_LOOKUP, { method: 'POST', signal, headers: {
70
+ Accept: 'application/json, text/javascript, */*; q=0.01', 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
71
+ 'User-Agent': userAgent, Cookie: cookie, 'X-Requested-With': 'XMLHttpRequest', Origin: ORIGIN, Referer: CONSEGNIA_PAGE,
72
+ }, body: new URLSearchParams({ _token: token, awb_number: number }).toString() }, { provider: PROVIDER, timeoutMs: left(),
73
+ maxBytes: CONSEGNIA_MAX_BYTES, redirect: 'manual', allowHttpStatuses: [...REDIRECTS, ...GONE, 403, REFUSED_SESSION],
74
+ fetcher: this.options.fetcher });
75
+ const { status } = lookup.response;
76
+ const body = decodeText(lookup.bytes).replace(/^\uFEFF/, '').trim();
77
+ if (REDIRECTS.includes(status) || status === 410)
78
+ throw new TransportError(PROVIDER, 'Consegnia tracking lookup is unavailable', { status });
79
+ if (status === 403)
80
+ throw new ChallengeError(PROVIDER, 'Consegnia refused the lookup', { status });
81
+ // The session the page just opened was refused: it says nothing about the shipment.
82
+ if (status === REFUSED_SESSION)
83
+ throw new IndeterminateError(PROVIDER, 'Consegnia refused the tracking page’s session', { status });
84
+ if (status === 404) {
85
+ if (isUnknownShipment(body))
86
+ throw new NotFoundError(PROVIDER);
87
+ throw new TransportError(PROVIDER, 'Consegnia tracking lookup is unavailable', { status });
88
+ }
89
+ return parseConsegnia(consegniaJson(body), number);
90
+ };
91
+ // An attempt that fails to reach Consegnia, or hangs, gets one more in a new
92
+ // session: the first gets half of the budget, so the retry fits in the rest.
93
+ return runSteps({ carrier: CARRIER, budgetMs: context.budgetMs ?? DEFAULT_BUDGET_MS, signal: context.signal,
94
+ recorder: this.options.recorder ?? NOOP_RECORDER }, [
95
+ { id: 'direct', run: ({ signal, remainingMs }) => attempt(signal, firstAttemptMs(remainingMs)) },
96
+ networkRetryStep(({ signal, remainingMs }) => attempt(signal, remainingMs)),
97
+ ]);
98
+ }
99
+ }
100
+ export const adapter = (environment) => {
101
+ const tracker = new ConsegniaTracker({ fetcher: environment.fetcher, recorder: environment.recorder, userAgent: environment.userAgent });
102
+ return {
103
+ id: CARRIER, recordsSteps: true, steps: ['direct', 'retry'],
104
+ track: (input, context) => tracker.fetch(input.number, context),
105
+ recognize: (number, context) => recognizeFromLookup(() => tracker.fetch(number, context), () => accepted(() => normalizeConsegniaNumber(number))),
106
+ };
107
+ };
@@ -18,18 +18,49 @@
18
18
  "timezone": "Asia/Manila",
19
19
  "tracking": {
20
20
  "mode": "automatic",
21
- "adapter": "universal",
22
- "steps": []
21
+ "adapter": "consegnia",
22
+ "steps": [
23
+ "direct",
24
+ "retry"
25
+ ],
26
+ "recognition": {
27
+ "rank": 57
28
+ },
29
+ "localClocks": true
23
30
  },
24
- "capabilities": [],
31
+ "capabilities": [
32
+ "history"
33
+ ],
25
34
  "portal": {
26
35
  "url": "https://clc-express.com/track-delivery?awb={trackingNumber}",
27
36
  "searchUrl": "https://clc-express.com/track-delivery",
28
37
  "canaryUrl": "https://clc-express.com/",
29
- "shows": [],
30
- "scraped": [],
31
- "discarded": [],
32
- "unavailable": []
38
+ "shows": [
39
+ "status",
40
+ "history",
41
+ "failed_delivery_reason",
42
+ "awb_date",
43
+ "delivered_at"
44
+ ],
45
+ "scraped": [
46
+ "status",
47
+ "history",
48
+ "failed_delivery_reason"
49
+ ],
50
+ "discarded": [
51
+ "awb_date",
52
+ "delivered_at"
53
+ ],
54
+ "unavailable": [
55
+ "location",
56
+ "eta",
57
+ "offset_timestamps",
58
+ "provider_code",
59
+ "weight",
60
+ "sender_name",
61
+ "recipient_name",
62
+ "proof_of_delivery"
63
+ ]
33
64
  },
34
65
  "links": [
35
66
  {
@@ -0,0 +1,29 @@
1
+ import type { CarrierResult } from '../../core/result/index.js';
2
+ export declare const PROVIDER = "Consegnia Logistics";
3
+ export declare const CARRIER = "consegnia";
4
+ export declare const CONSEGNIA_MAX_BYTES: number;
5
+ /** A number for Consegnia's tracker: CLC and nine digits. */
6
+ export declare function normalizeConsegniaNumber(raw: string): string;
7
+ /** A firewall or bot-check page in place of the tracking page or the lookup's JSON. */
8
+ export declare function isBlockPage(body: string): boolean;
9
+ /** The token the tracking page's form sends with a number, or null when the page has none. */
10
+ export declare function consegniaToken(html: string): string | null;
11
+ /**
12
+ * The lookup's reply for one number, `{ success, data: { html } }`: a fragment
13
+ * its server renders with the number, the current status, the last update, the
14
+ * booking day, the delivery clock once delivered, and the history, newest
15
+ * first, each row a status, the reason for a failed delivery and a wall clock
16
+ * to the minute with no zone. It names no person, place or phone number.
17
+ *
18
+ * The fragment must carry the whole number asked. The delivery clock is not
19
+ * read: a delivered parcel's newest row gives it, and it has no zone either.
20
+ */
21
+ export declare function parseConsegnia(payload: unknown, raw: string): CarrierResult;
22
+ /**
23
+ * The lookup's answer to a number it does not hold: HTTP 404 with `success`
24
+ * false and its message. The message does not quote the number. Any other 404
25
+ * is a missing route.
26
+ */
27
+ export declare function isUnknownShipment(body: string): boolean;
28
+ /** The lookup's reply as JSON, or the reason it is not one. */
29
+ export declare function consegniaJson(body: string): unknown;