universal-parcel-scraper 0.181.0 → 0.183.0-main.1321

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 (48) hide show
  1. package/README.md +2 -2
  2. package/data/catalog.json +23 -5
  3. package/data/coverage-summary.json +1 -1
  4. package/data/detection-golden.json +50 -0
  5. package/dist/browser/scraper.js +21 -5
  6. package/dist/carriers/couriersplease/adapter.d.ts +17 -0
  7. package/dist/carriers/couriersplease/adapter.js +111 -0
  8. package/dist/carriers/couriersplease/carrier.json +51 -7
  9. package/dist/carriers/couriersplease/parser.d.ts +22 -0
  10. package/dist/carriers/couriersplease/parser.js +222 -0
  11. package/dist/carriers/couriersplease/status.d.ts +7 -0
  12. package/dist/carriers/couriersplease/status.js +96 -0
  13. package/dist/carriers/couriersplease/statuses.json +21 -18
  14. package/dist/carriers/gdex/adapter.d.ts +17 -0
  15. package/dist/carriers/gdex/adapter.js +58 -0
  16. package/dist/carriers/gdex/carrier.json +47 -7
  17. package/dist/carriers/gdex/numbers.json +38 -0
  18. package/dist/carriers/gdex/parser.d.ts +15 -0
  19. package/dist/carriers/gdex/parser.js +149 -0
  20. package/dist/carriers/gdex/status.d.ts +7 -0
  21. package/dist/carriers/gdex/status.js +41 -0
  22. package/dist/carriers/gdex/statuses.json +12 -3
  23. package/dist/carriers/leopards-courier/adapter.d.ts +13 -0
  24. package/dist/carriers/leopards-courier/adapter.js +73 -0
  25. package/dist/carriers/leopards-courier/carrier.json +52 -7
  26. package/dist/carriers/leopards-courier/numbers.json +6 -8
  27. package/dist/carriers/leopards-courier/parser.d.ts +26 -0
  28. package/dist/carriers/leopards-courier/parser.js +190 -0
  29. package/dist/carriers/leopards-courier/status.d.ts +7 -0
  30. package/dist/carriers/leopards-courier/status.js +34 -0
  31. package/dist/carriers/leopards-courier/statuses.json +4 -1
  32. package/dist/carriers/pos-indonesia/adapter.d.ts +14 -0
  33. package/dist/carriers/pos-indonesia/adapter.js +74 -0
  34. package/dist/carriers/pos-indonesia/carrier.json +53 -7
  35. package/dist/carriers/pos-indonesia/numbers.json +52 -14
  36. package/dist/carriers/pos-indonesia/parser.d.ts +25 -0
  37. package/dist/carriers/pos-indonesia/parser.js +201 -0
  38. package/dist/carriers/pos-indonesia/status.d.ts +12 -0
  39. package/dist/carriers/pos-indonesia/status.js +41 -0
  40. package/dist/carriers/pos-indonesia/statuses.json +7 -0
  41. package/dist/core/catalog/statusMaps.js +12 -5
  42. package/dist/data/catalog.json +23 -5
  43. package/dist/generated/catalog.d.ts +17 -5
  44. package/dist/generated/catalog.js +23 -5
  45. package/dist/generated/recognition.js +4 -0
  46. package/dist/generated/registry.js +12 -4
  47. package/dist/server/openapi.json +1 -1
  48. 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 **256 dedicated adapters** and **5 universal fallbacks**
16
+ **3,500+ carriers** through **260 dedicated adapters** and **5 universal fallbacks**
17
17
 
18
18
  <sub>494 catalog entries · 113 countries represented</sub>
19
19
  <!-- /GENERATED:summary -->
@@ -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 9,582 such statuses, each filed under one stage.
158
+ The carrier folders record 9,604 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.
package/data/catalog.json CHANGED
@@ -7692,7 +7692,9 @@
7692
7692
  "timezone": "Australia/Sydney",
7693
7693
  "tracking": {
7694
7694
  "mode": "automatic",
7695
- "adapter": "universal"
7695
+ "adapter": "couriersplease",
7696
+ "recognitionRank": 961,
7697
+ "localClocks": true
7696
7698
  },
7697
7699
  "canaryUrl": "https://www.couriersplease.com.au/tools-track",
7698
7700
  "trackingUrlTemplate": "https://www.couriersplease.com.au/tools-track?no={trackingNumber}",
@@ -10264,10 +10266,23 @@
10264
10266
  "timezone": "Asia/Kuala_Lumpur",
10265
10267
  "tracking": {
10266
10268
  "mode": "automatic",
10267
- "adapter": "universal"
10269
+ "adapter": "gdex",
10270
+ "recognitionRank": 957
10268
10271
  },
10269
- "canaryUrl": "https://my.gdexpress.com/",
10272
+ "canaryUrl": "https://app.gdexpress.com/",
10273
+ "trackingUrlTemplate": "https://gdexpress.com/tracking/?consignmentno={trackingNumber}",
10274
+ "trackingSearchUrl": "https://gdexpress.com/our-business/delivery/tools-references/tracking/",
10270
10275
  "linkRules": [
10276
+ {
10277
+ "domains": [
10278
+ "gdexpress.com",
10279
+ "www.gdexpress.com"
10280
+ ],
10281
+ "pathPattern": "^/tracking/?$",
10282
+ "params": [
10283
+ "consignmentno"
10284
+ ]
10285
+ },
10271
10286
  {
10272
10287
  "domains": [
10273
10288
  "web2.gdexpress.com"
@@ -12685,7 +12700,8 @@
12685
12700
  "timezone": "Asia/Karachi",
12686
12701
  "tracking": {
12687
12702
  "mode": "automatic",
12688
- "adapter": "universal"
12703
+ "adapter": "leopards-courier",
12704
+ "recognitionRank": 965
12689
12705
  },
12690
12706
  "canaryUrl": "https://pk.leopardscourier.com/",
12691
12707
  "trackingSearchUrl": "https://pk.leopardscourier.com/",
@@ -15151,7 +15167,9 @@
15151
15167
  "timezone": "Asia/Jakarta",
15152
15168
  "tracking": {
15153
15169
  "mode": "automatic",
15154
- "adapter": "universal"
15170
+ "adapter": "pos-indonesia",
15171
+ "recognitionRank": 964,
15172
+ "localClocks": true
15155
15173
  },
15156
15174
  "canaryUrl": "https://www.posindonesia.co.id/id/tracking",
15157
15175
  "trackingUrlTemplate": "https://www.posindonesia.co.id/id/tracking/{trackingNumber}",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "catalog": 494,
3
- "activeAdapters": 256,
3
+ "activeAdapters": 260,
4
4
  "countries": 113,
5
5
  "referenceCarriers": 100,
6
6
  "comparison": {
@@ -51340,6 +51340,17 @@
51340
51340
  "chronopost"
51341
51341
  ]
51342
51342
  },
51343
+ {
51344
+ "input": "EE205187475BN",
51345
+ "carrier": "intl-post",
51346
+ "confidence": "high",
51347
+ "candidates": [
51348
+ "intl-post"
51349
+ ],
51350
+ "asked": [
51351
+ "chronopost"
51352
+ ]
51353
+ },
51343
51354
  {
51344
51355
  "input": "EE212938475ID",
51345
51356
  "carrier": "intl-post",
@@ -57016,6 +57027,14 @@
57016
57027
  "four-px"
57017
57028
  ]
57018
57029
  },
57030
+ {
57031
+ "input": "LP107221675JP",
57032
+ "carrier": "japan-post",
57033
+ "confidence": "high",
57034
+ "candidates": [
57035
+ "japan-post"
57036
+ ]
57037
+ },
57019
57038
  {
57020
57039
  "input": "LP157079155HK",
57021
57040
  "carrier": "hongkong-post",
@@ -58651,6 +58670,22 @@
58651
58670
  "tnt-it"
58652
58671
  ]
58653
58672
  },
58673
+ {
58674
+ "input": "MY37279142960",
58675
+ "carrier": "gdex",
58676
+ "confidence": "high",
58677
+ "candidates": [
58678
+ "gdex"
58679
+ ]
58680
+ },
58681
+ {
58682
+ "input": "MY37324153968",
58683
+ "carrier": "gdex",
58684
+ "confidence": "high",
58685
+ "candidates": [
58686
+ "gdex"
58687
+ ]
58688
+ },
58654
58689
  {
58655
58690
  "input": "MY40000807898",
58656
58691
  "carrier": "gdex",
@@ -59769,6 +59804,9 @@
59769
59804
  "confidence": "low",
59770
59805
  "candidates": [
59771
59806
  "pos-indonesia"
59807
+ ],
59808
+ "asked": [
59809
+ "pos-indonesia"
59772
59810
  ]
59773
59811
  },
59774
59812
  {
@@ -59794,6 +59832,9 @@
59794
59832
  "confidence": "low",
59795
59833
  "candidates": [
59796
59834
  "pos-indonesia"
59835
+ ],
59836
+ "asked": [
59837
+ "pos-indonesia"
59797
59838
  ]
59798
59839
  },
59799
59840
  {
@@ -59802,6 +59843,9 @@
59802
59843
  "confidence": "low",
59803
59844
  "candidates": [
59804
59845
  "pos-indonesia"
59846
+ ],
59847
+ "asked": [
59848
+ "pos-indonesia"
59805
59849
  ]
59806
59850
  },
59807
59851
  {
@@ -59810,6 +59854,9 @@
59810
59854
  "confidence": "low",
59811
59855
  "candidates": [
59812
59856
  "pos-indonesia"
59857
+ ],
59858
+ "asked": [
59859
+ "pos-indonesia"
59813
59860
  ]
59814
59861
  },
59815
59862
  {
@@ -67982,6 +68029,9 @@
67982
68029
  "confidence": "low",
67983
68030
  "candidates": [
67984
68031
  "pos-indonesia"
68032
+ ],
68033
+ "asked": [
68034
+ "pos-indonesia"
67985
68035
  ]
67986
68036
  },
67987
68037
  {
@@ -6050,7 +6050,9 @@ var Ae = {
6050
6050
  timezone: "Australia/Sydney",
6051
6051
  tracking: {
6052
6052
  mode: "automatic",
6053
- adapter: "universal"
6053
+ adapter: "couriersplease",
6054
+ recognitionRank: 961,
6055
+ localClocks: !0
6054
6056
  },
6055
6057
  canaryUrl: "https://www.couriersplease.com.au/tools-track",
6056
6058
  trackingUrlTemplate: "https://www.couriersplease.com.au/tools-track?no={trackingNumber}",
@@ -7951,10 +7953,17 @@ var Ae = {
7951
7953
  timezone: "Asia/Kuala_Lumpur",
7952
7954
  tracking: {
7953
7955
  mode: "automatic",
7954
- adapter: "universal"
7956
+ adapter: "gdex",
7957
+ recognitionRank: 957
7955
7958
  },
7956
- canaryUrl: "https://my.gdexpress.com/",
7959
+ canaryUrl: "https://app.gdexpress.com/",
7960
+ trackingUrlTemplate: "https://gdexpress.com/tracking/?consignmentno={trackingNumber}",
7961
+ trackingSearchUrl: "https://gdexpress.com/our-business/delivery/tools-references/tracking/",
7957
7962
  linkRules: [{
7963
+ domains: ["gdexpress.com", "www.gdexpress.com"],
7964
+ pathPattern: "^/tracking/?$",
7965
+ params: ["consignmentno"]
7966
+ }, {
7958
7967
  domains: ["web2.gdexpress.com"],
7959
7968
  pathPattern: "^/official/iframe/etracking(?:2|_v2)\\.php$",
7960
7969
  params: ["capture"]
@@ -9749,7 +9758,8 @@ var Ae = {
9749
9758
  timezone: "Asia/Karachi",
9750
9759
  tracking: {
9751
9760
  mode: "automatic",
9752
- adapter: "universal"
9761
+ adapter: "leopards-courier",
9762
+ recognitionRank: 965
9753
9763
  },
9754
9764
  canaryUrl: "https://pk.leopardscourier.com/",
9755
9765
  trackingSearchUrl: "https://pk.leopardscourier.com/",
@@ -11567,7 +11577,9 @@ var Ae = {
11567
11577
  timezone: "Asia/Jakarta",
11568
11578
  tracking: {
11569
11579
  mode: "automatic",
11570
- adapter: "universal"
11580
+ adapter: "pos-indonesia",
11581
+ recognitionRank: 964,
11582
+ localClocks: !0
11571
11583
  },
11572
11584
  canaryUrl: "https://www.posindonesia.co.id/id/tracking",
11573
11585
  trackingUrlTemplate: "https://www.posindonesia.co.id/id/tracking/{trackingNumber}",
@@ -21041,6 +21053,7 @@ var nl = {
21041
21053
  "correo-argentino": 978,
21042
21054
  "correos-de-mexico": 30,
21043
21055
  "correos-express": 800,
21056
+ couriersplease: 961,
21044
21057
  "ctt-express": 790,
21045
21058
  "cyprus-post": 60,
21046
21059
  "dai-post": 62,
@@ -21074,6 +21087,7 @@ var nl = {
21074
21087
  "four-px": 570,
21075
21088
  foxpost: 200,
21076
21089
  gati: 43,
21090
+ gdex: 957,
21077
21091
  geis: 90,
21078
21092
  "geniki-taxydromiki": 140,
21079
21093
  "geodis-pl": 994,
@@ -21100,6 +21114,7 @@ var nl = {
21100
21114
  "kuehne-nagel": 435,
21101
21115
  "la-poste": 1e3,
21102
21116
  "latvijas-pasts": 100,
21117
+ "leopards-courier": 965,
21103
21118
  "lietuvos-pastas": 895,
21104
21119
  logen: 48,
21105
21120
  "loomis-express": 285,
@@ -21124,6 +21139,7 @@ var nl = {
21124
21139
  paquetexpress: 35,
21125
21140
  passport: 55,
21126
21141
  "poczta-polska": 750,
21142
+ "pos-indonesia": 964,
21127
21143
  "post-luxembourg": 80,
21128
21144
  "posta-slovenije": 130,
21129
21145
  "posta-srbije": 110,
@@ -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. */
5
+ export declare const COURIERSPLEASE_PAGE = "https://www.couriersplease.com.au/tools-track";
6
+ /** The lookup the tracking page makes for the numbers typed in. */
7
+ export declare const COURIERSPLEASE_API = "https://www.couriersplease.com.au/api/track/locate";
8
+ export declare class CouriersPleaseTracker {
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,111 @@
1
+ import { accepted, recognizeFromLookup } from '../../core/adapter/index.js';
2
+ import { ChallengeError, IndeterminateError, RateLimitedError, 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 { COURIERSPLEASE_MAX_BYTES, couriersPleaseToken, normalizeCouriersPleaseNumber, parseCouriersPlease, PROVIDER } from './parser.js';
8
+ /** The tracking page, which opens the session its lookup needs. */
9
+ export const COURIERSPLEASE_PAGE = 'https://www.couriersplease.com.au/tools-track';
10
+ /** The lookup the tracking page makes for the numbers typed in. */
11
+ export const COURIERSPLEASE_API = 'https://www.couriersplease.com.au/api/track/locate';
12
+ const ORIGIN = 'https://www.couriersplease.com.au';
13
+ const PAGE_MAX_BYTES = 512 * 1024;
14
+ const REDIRECTS = [301, 302, 303, 307, 308];
15
+ const GONE = [404, 410];
16
+ // Azure Front Door's own page when its firewall refuses a request: a 429 for
17
+ // too many requests at once, a 403 for a blocked client.
18
+ const BLOCKED = /<title>\s*Service unavailable\s*<\/title>|The request is blocked\./i;
19
+ // Bot checks a CDN could put in place of the page or the reply.
20
+ const CHALLENGE = /_cf_chl_opt|cf-turnstile|<title>\s*Just a moment|challenge-platform/i;
21
+ // The session cookie the page sets beside its token.
22
+ const SESSION = /^__Host-csrf=[A-Za-z0-9._~+/=-]{8,512}$/;
23
+ function retryAfterMs(response) {
24
+ const value = response.headers.get('retry-after')?.trim() ?? '';
25
+ return /^\d{1,5}$/.test(value) ? Number(value) * 1000 : undefined;
26
+ }
27
+ /** A refusal before any reply: a rate limit, a firewall block or a moved route. */
28
+ function refusal(response, body, stage) {
29
+ if (response.status === 429) {
30
+ throw new RateLimitedError(PROVIDER, retryAfterMs(response), `CouriersPlease rate limited its ${stage}`, { status: 429 });
31
+ }
32
+ if (REDIRECTS.includes(response.status) || GONE.includes(response.status)) {
33
+ throw new TransportError(PROVIDER, `CouriersPlease ${stage} is unavailable`, { status: response.status });
34
+ }
35
+ if (BLOCKED.test(body) || CHALLENGE.test(body))
36
+ throw new ChallengeError(PROVIDER, `CouriersPlease blocked its ${stage}`, { status: response.status });
37
+ throw new ChallengeError(PROVIDER, `CouriersPlease refused its ${stage}`, { status: response.status });
38
+ }
39
+ function json(body) {
40
+ try {
41
+ return JSON.parse(body);
42
+ }
43
+ catch (cause) {
44
+ if (BLOCKED.test(body.slice(0, 64_000)) || CHALLENGE.test(body.slice(0, 64_000))) {
45
+ throw new ChallengeError(PROVIDER, 'CouriersPlease answered with a block page', { cause });
46
+ }
47
+ throw new SchemaError(PROVIDER, 'CouriersPlease returned a reply that is not JSON', { cause });
48
+ }
49
+ }
50
+ export class CouriersPleaseTracker {
51
+ options;
52
+ constructor(options = {}) {
53
+ this.options = options;
54
+ }
55
+ async fetch(raw, context = {}) {
56
+ const number = normalizeCouriersPleaseNumber(raw);
57
+ const userAgent = userAgentOf(this.options.userAgent);
58
+ const read = async (signal, timeoutMs) => {
59
+ // The page and the lookup share the attempt's time.
60
+ const deadline = performance.now() + timeoutMs;
61
+ const left = () => Math.max(1, Math.floor(deadline - performance.now()));
62
+ const page = await fetchBounded(COURIERSPLEASE_PAGE, { method: 'GET', signal, headers: {
63
+ Accept: 'text/html,application/xhtml+xml', 'User-Agent': userAgent,
64
+ } }, { provider: PROVIDER, timeoutMs: left(), maxBytes: PAGE_MAX_BYTES, redirect: 'manual', fetcher: this.options.fetcher,
65
+ allowHttpStatuses: [...REDIRECTS, ...GONE, 403, 429] });
66
+ const html = decodeText(page.bytes);
67
+ if (page.response.status !== 200)
68
+ refusal(page.response, html.slice(0, 64_000), 'tracking page');
69
+ if (BLOCKED.test(html.slice(0, 64_000)) || CHALLENGE.test(html.slice(0, 64_000))) {
70
+ throw new ChallengeError(PROVIDER, 'CouriersPlease answered the tracking page with a block page');
71
+ }
72
+ // The lookup takes the page's token with the cookie it was minted for.
73
+ const token = couriersPleaseToken(html);
74
+ const cookie = page.response.headers.getSetCookie().map((header) => header.split(';', 1)[0].trim()).find((part) => SESSION.test(part));
75
+ if (!token || !cookie)
76
+ throw new SchemaError(PROVIDER, 'CouriersPlease tracking page opened no session');
77
+ const { response, bytes } = await fetchBounded(COURIERSPLEASE_API, { method: 'POST', signal, headers: {
78
+ Accept: 'application/json', 'Content-Type': 'application/json', 'User-Agent': userAgent, Origin: ORIGIN,
79
+ // The service answers only requests its own pages make, as browsers mark them.
80
+ 'Sec-Fetch-Site': 'same-origin', 'csrf-token': token, Cookie: cookie,
81
+ }, body: JSON.stringify({ trackingCode: number }) }, { provider: PROVIDER, timeoutMs: left(), maxBytes: COURIERSPLEASE_MAX_BYTES,
82
+ redirect: 'manual', fetcher: this.options.fetcher, allowHttpStatuses: [...REDIRECTS, ...GONE, 400, 403, 429] });
83
+ const body = decodeText(bytes);
84
+ if (response.status === 400 || response.status === 403) {
85
+ // The session the page just opened was refused: a fresh one is the only remedy.
86
+ if (/CSRF/i.test(body.slice(0, 4_000)))
87
+ throw new IndeterminateError(PROVIDER, 'CouriersPlease refused the tracking page’s session');
88
+ if (response.status === 400 && !BLOCKED.test(body.slice(0, 4_000)))
89
+ throw new IndeterminateError(PROVIDER, 'CouriersPlease did not take the lookup');
90
+ }
91
+ if (response.status !== 200)
92
+ refusal(response, body.slice(0, 64_000), 'tracking lookup');
93
+ return parseCouriersPlease(json(body), number);
94
+ };
95
+ // A page or lookup that fails to reach CouriersPlease, or hangs, gets one
96
+ // more try with a new session: the first gets half of the budget.
97
+ return runSteps({ carrier: 'couriersplease', budgetMs: context.budgetMs ?? 20_000, signal: context.signal,
98
+ recorder: this.options.recorder ?? NOOP_RECORDER }, [
99
+ { id: 'direct', run: ({ signal, remainingMs }) => read(signal, firstAttemptMs(remainingMs)) },
100
+ networkRetryStep(({ signal, remainingMs }) => read(signal, remainingMs)),
101
+ ]);
102
+ }
103
+ }
104
+ export const adapter = (environment) => {
105
+ const tracker = new CouriersPleaseTracker({ fetcher: environment.fetcher, recorder: environment.recorder, userAgent: environment.userAgent });
106
+ return {
107
+ id: 'couriersplease', recordsSteps: true, steps: ['direct', 'retry'],
108
+ track: (input, context) => tracker.fetch(input.number, context),
109
+ recognize: (number, context) => recognizeFromLookup(() => tracker.fetch(number, context), () => accepted(() => normalizeCouriersPleaseNumber(number))),
110
+ };
111
+ };
@@ -17,18 +17,62 @@
17
17
  "timezone": "Australia/Sydney",
18
18
  "tracking": {
19
19
  "mode": "automatic",
20
- "adapter": "universal",
21
- "steps": []
20
+ "adapter": "couriersplease",
21
+ "steps": [
22
+ "direct",
23
+ "retry"
24
+ ],
25
+ "recognition": {
26
+ "rank": 961
27
+ },
28
+ "localClocks": true
22
29
  },
23
- "capabilities": [],
30
+ "capabilities": [
31
+ "history",
32
+ "location",
33
+ "provider_code",
34
+ "eta"
35
+ ],
24
36
  "portal": {
25
37
  "url": "https://www.couriersplease.com.au/tools-track?no={trackingNumber}",
26
38
  "searchUrl": "https://www.couriersplease.com.au/tools-track",
27
39
  "canaryUrl": "https://www.couriersplease.com.au/tools-track",
28
- "shows": [],
29
- "scraped": [],
30
- "discarded": [],
31
- "unavailable": []
40
+ "shows": [
41
+ "status",
42
+ "history",
43
+ "location",
44
+ "provider_code",
45
+ "eta",
46
+ "delivered_at",
47
+ "parcel_count",
48
+ "weight",
49
+ "volume",
50
+ "customer_reference",
51
+ "recipient_name",
52
+ "driver_name",
53
+ "depot_code"
54
+ ],
55
+ "scraped": [
56
+ "status",
57
+ "history",
58
+ "location",
59
+ "provider_code",
60
+ "eta"
61
+ ],
62
+ "discarded": [
63
+ "delivered_at",
64
+ "parcel_count",
65
+ "weight",
66
+ "volume",
67
+ "customer_reference",
68
+ "recipient_name",
69
+ "driver_name",
70
+ "depot_code"
71
+ ],
72
+ "unavailable": [
73
+ "service_name",
74
+ "offset_timestamps"
75
+ ]
32
76
  },
33
77
  "links": [
34
78
  {
@@ -0,0 +1,22 @@
1
+ import type { CarrierResult } from '../../core/result/index.js';
2
+ export declare const PROVIDER = "CouriersPlease";
3
+ export declare const COURIERSPLEASE_MAX_BYTES: number;
4
+ /** A number for CouriersPlease's tracker, in upper case; anything else is not one it issues. */
5
+ export declare function normalizeCouriersPleaseNumber(raw: string): string;
6
+ /** The session token the tracking page puts in its `csrf-token` meta tag, or null. */
7
+ export declare function couriersPleaseToken(html: string): string | null;
8
+ /**
9
+ * A scan's wording without its markup. A line break becomes the end of a
10
+ * sentence, as on the page, and a sentence that points to a link, such as
11
+ * "Please contact our Customer Support Team here", is left out.
12
+ */
13
+ export declare function couriersPleaseWording(raw: string): string;
14
+ /**
15
+ * The reply of the tracking page's `/api/track/locate` for one number. Its one
16
+ * entry holds the consignment's details, when the number is a consignment, and
17
+ * each item's rows, the first naming the item. A consignment must be the
18
+ * number asked, or an item must: a label or coupon has no consignment details.
19
+ * An unknown number gets no rows ("No scan events found", or an empty list),
20
+ * which the page reports as not a valid tracking number.
21
+ */
22
+ export declare function parseCouriersPlease(payload: unknown, raw: string): CarrierResult;