universal-parcel-scraper 0.97.0-main.997 → 0.99.0-main.1001
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 +2 -2
- package/data/catalog.json +14 -5
- package/data/coverage-summary.json +1 -1
- package/dist/browser/scraper.js +15 -5
- package/dist/carriers/royal-mail/carrier.json +5 -1
- package/dist/carriers/royal-mail/identity.d.ts +2 -0
- package/dist/carriers/royal-mail/identity.js +5 -0
- package/dist/carriers/royal-mail/parser.js +31 -7
- package/dist/carriers/royal-mail/status.js +2 -0
- package/dist/carriers/royal-mail/statuses.json +18 -0
- package/dist/carriers/whistl/adapter.d.ts +14 -0
- package/dist/carriers/whistl/adapter.js +54 -0
- package/dist/carriers/whistl/carrier.json +53 -10
- package/dist/carriers/whistl/numbers.json +2 -2
- package/dist/carriers/whistl/parser.d.ts +14 -0
- package/dist/carriers/whistl/parser.js +194 -0
- package/dist/carriers/whistl/status.d.ts +12 -0
- package/dist/carriers/whistl/status.js +115 -0
- package/dist/carriers/whistl/statuses.json +474 -1
- package/dist/core/catalog/eventIdentity.js +2 -1
- package/dist/core/catalog/statusMaps.js +2 -1
- package/dist/data/catalog.json +14 -5
- package/dist/generated/catalog.d.ts +10 -4
- package/dist/generated/catalog.js +14 -5
- package/dist/generated/recognition.js +1 -0
- package/dist/generated/registry.js +3 -1
- package/dist/server/openapi.json +1 -1
- 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)
|
|
14
14
|
|
|
15
15
|
<!-- GENERATED:summary -->
|
|
16
|
-
**3,500+ carriers** through **
|
|
16
|
+
**3,500+ carriers** through **157 dedicated adapters** and **5 universal fallbacks**
|
|
17
17
|
|
|
18
18
|
<sub>208 catalog entries · 66 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 4,
|
|
158
|
+
The carrier folders record 4,426 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
|
@@ -1983,7 +1983,11 @@
|
|
|
1983
1983
|
"tracking": {
|
|
1984
1984
|
"mode": "automatic",
|
|
1985
1985
|
"adapter": "royal-mail",
|
|
1986
|
-
"browserRecognitionRank": 900
|
|
1986
|
+
"browserRecognitionRank": 900,
|
|
1987
|
+
"refresh": {
|
|
1988
|
+
"minMinutes": 60
|
|
1989
|
+
},
|
|
1990
|
+
"localClocks": true
|
|
1987
1991
|
},
|
|
1988
1992
|
"canaryUrl": "https://www.royalmail.com/track-your-item",
|
|
1989
1993
|
"trackingUrlTemplate": "https://www.royalmail.com/portal/rm/track?trackNumber={trackingNumber}",
|
|
@@ -8360,7 +8364,9 @@
|
|
|
8360
8364
|
"color": "#fd4f00",
|
|
8361
8365
|
"aliases": [
|
|
8362
8366
|
"Whistl UK",
|
|
8363
|
-
"TNT Post UK"
|
|
8367
|
+
"TNT Post UK",
|
|
8368
|
+
"Parcelhub",
|
|
8369
|
+
"Whistl Despatch"
|
|
8364
8370
|
],
|
|
8365
8371
|
"countries": [
|
|
8366
8372
|
"GB"
|
|
@@ -8369,7 +8375,9 @@
|
|
|
8369
8375
|
"timezone": "Europe/London",
|
|
8370
8376
|
"tracking": {
|
|
8371
8377
|
"mode": "automatic",
|
|
8372
|
-
"adapter": "
|
|
8378
|
+
"adapter": "whistl",
|
|
8379
|
+
"recognitionRank": 880,
|
|
8380
|
+
"localClocks": true
|
|
8373
8381
|
},
|
|
8374
8382
|
"canaryUrl": "https://web.parcelhub.net/Tracking",
|
|
8375
8383
|
"trackingUrlTemplate": "https://web.parcelhub.net/Tracking/reference/{trackingNumber}",
|
|
@@ -8377,9 +8385,10 @@
|
|
|
8377
8385
|
"linkRules": [
|
|
8378
8386
|
{
|
|
8379
8387
|
"domains": [
|
|
8380
|
-
"web.parcelhub.net"
|
|
8388
|
+
"web.parcelhub.net",
|
|
8389
|
+
"despatch.whistl.co.uk"
|
|
8381
8390
|
],
|
|
8382
|
-
"path": "^/Tracking/reference/([^/?#]+)
|
|
8391
|
+
"path": "^/Tracking/reference/([^/?#]+)(?:/postcode/[^/?#]*)?/?$"
|
|
8383
8392
|
},
|
|
8384
8393
|
{
|
|
8385
8394
|
"domains": [
|
package/dist/browser/scraper.js
CHANGED
|
@@ -1724,7 +1724,9 @@ var be = {
|
|
|
1724
1724
|
tracking: {
|
|
1725
1725
|
mode: "automatic",
|
|
1726
1726
|
adapter: "royal-mail",
|
|
1727
|
-
browserRecognitionRank: 900
|
|
1727
|
+
browserRecognitionRank: 900,
|
|
1728
|
+
refresh: { minMinutes: 60 },
|
|
1729
|
+
localClocks: !0
|
|
1728
1730
|
},
|
|
1729
1731
|
canaryUrl: "https://www.royalmail.com/track-your-item",
|
|
1730
1732
|
trackingUrlTemplate: "https://www.royalmail.com/portal/rm/track?trackNumber={trackingNumber}",
|
|
@@ -6599,20 +6601,27 @@ var be = {
|
|
|
6599
6601
|
whistl: {
|
|
6600
6602
|
displayName: "Whistl",
|
|
6601
6603
|
color: "#fd4f00",
|
|
6602
|
-
aliases: [
|
|
6604
|
+
aliases: [
|
|
6605
|
+
"Whistl UK",
|
|
6606
|
+
"TNT Post UK",
|
|
6607
|
+
"Parcelhub",
|
|
6608
|
+
"Whistl Despatch"
|
|
6609
|
+
],
|
|
6603
6610
|
countries: ["GB"],
|
|
6604
6611
|
selectable: !0,
|
|
6605
6612
|
timezone: "Europe/London",
|
|
6606
6613
|
tracking: {
|
|
6607
6614
|
mode: "automatic",
|
|
6608
|
-
adapter: "
|
|
6615
|
+
adapter: "whistl",
|
|
6616
|
+
recognitionRank: 880,
|
|
6617
|
+
localClocks: !0
|
|
6609
6618
|
},
|
|
6610
6619
|
canaryUrl: "https://web.parcelhub.net/Tracking",
|
|
6611
6620
|
trackingUrlTemplate: "https://web.parcelhub.net/Tracking/reference/{trackingNumber}",
|
|
6612
6621
|
trackingSearchUrl: "https://web.parcelhub.net/Tracking",
|
|
6613
6622
|
linkRules: [{
|
|
6614
|
-
domains: ["web.parcelhub.net"],
|
|
6615
|
-
path: "^/Tracking/reference/([^/?#]+)
|
|
6623
|
+
domains: ["web.parcelhub.net", "despatch.whistl.co.uk"],
|
|
6624
|
+
path: "^/Tracking/reference/([^/?#]+)(?:/postcode/[^/?#]*)?/?$"
|
|
6616
6625
|
}, {
|
|
6617
6626
|
domains: ["trackmyitem.whistl.co.uk"],
|
|
6618
6627
|
path: "^/tracking/([^/?#]+)/?$"
|
|
@@ -12010,6 +12019,7 @@ var bc = {
|
|
|
12010
12019
|
uniuni: 330,
|
|
12011
12020
|
venipak: 630,
|
|
12012
12021
|
"vinted-go": 720,
|
|
12022
|
+
whistl: 880,
|
|
12013
12023
|
"yurtici-kargo": 660
|
|
12014
12024
|
}, xc = 5;
|
|
12015
12025
|
function Sc(e) {
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Scans abroad kept the UK offset Royal Mail labels them with until October
|
|
2
|
+
// 2026 and now show their country's, unless the country keeps several clocks.
|
|
3
|
+
export const sameInstantIdentityPolicy = {
|
|
4
|
+
sourceCarrierId: 'royal-mail', storedSources: ['royal-mail'], requireProviderCode: true, relabelledFrom: 'Europe/London',
|
|
5
|
+
};
|
|
@@ -2,7 +2,7 @@ import { DateTime } from 'luxon';
|
|
|
2
2
|
import { load } from 'cheerio';
|
|
3
3
|
import { ChallengeError, IndeterminateError, InvalidInputError, RateLimitedError, SchemaError } from '../../core/errors/index.js';
|
|
4
4
|
import { clean, cleanScalar } from '../../core/transport/index.js';
|
|
5
|
-
import { explicitOffsetTime } from '../../core/time/index.js';
|
|
5
|
+
import { countryCode, countryTimeZone, explicitOffsetTime, isoTime } from '../../core/time/index.js';
|
|
6
6
|
import { isRecord } from '../../core/types.js';
|
|
7
7
|
import { isRoyalMailDomesticReference } from '../../core/detection/royalMail.js';
|
|
8
8
|
import { isValidS10TrackingNumber } from '../../core/detection/s10.js';
|
|
@@ -41,6 +41,22 @@ function eventTime(value) {
|
|
|
41
41
|
return null;
|
|
42
42
|
return explicitOffsetTime(raw)?.iso ?? null;
|
|
43
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* A scan abroad names its country after the place, and Royal Mail labels that
|
|
46
|
+
* office's wall clock with the UK's offset. The clock is read in the country's
|
|
47
|
+
* zone, or kept as a wall clock when the country has several; null at home.
|
|
48
|
+
*/
|
|
49
|
+
function abroadClock(raw, location) {
|
|
50
|
+
const country = countryCode(/,\s*([^,]+)$/.exec(location)?.[1]);
|
|
51
|
+
if (!country || country === 'GB' || !eventTime(raw))
|
|
52
|
+
return null;
|
|
53
|
+
const wall = DateTime.fromISO(raw, { setZone: true }).toISO({ includeOffset: false, suppressMilliseconds: true });
|
|
54
|
+
if (!wall)
|
|
55
|
+
return null;
|
|
56
|
+
const zone = countryTimeZone(country);
|
|
57
|
+
const placed = zone ? isoTime(wall, zone) : null;
|
|
58
|
+
return placed ? { time: placed.iso } : { local_time: wall };
|
|
59
|
+
}
|
|
44
60
|
// Royal Mail appends a Post Office branch's postcode in brackets, and an
|
|
45
61
|
// overseas partner can report a delivery office by postcode alone.
|
|
46
62
|
const POSTCODE = /^(?:[A-Z]{1,2}\d[A-Z\d]? ?\d[A-Z]{2}|\d{3,10}(?:-\d{3,4})?)$/i;
|
|
@@ -78,13 +94,14 @@ export function parseRoyalMailTrackingResponse(payload, trackingNumber) {
|
|
|
78
94
|
throw new SchemaError('Royal Mail', 'Royal Mail did not return the requested parcel');
|
|
79
95
|
}
|
|
80
96
|
const summary = mailpiece.summary;
|
|
81
|
-
const summaryText = cleanScalar(summary.statusDescription);
|
|
97
|
+
const summaryText = cleanScalar(summary.statusDescription).replace(/\*\*/g, '').trim();
|
|
82
98
|
if (mailpiece.events !== undefined && !Array.isArray(mailpiece.events)) {
|
|
83
99
|
throw new SchemaError('Royal Mail', 'Royal Mail returned invalid tracking events');
|
|
84
100
|
}
|
|
85
101
|
const scans = Array.isArray(mailpiece.events) ? mailpiece.events : [];
|
|
86
102
|
const events = [];
|
|
87
103
|
const seen = new Set();
|
|
104
|
+
const abroad = new Map();
|
|
88
105
|
for (const raw of scans.slice(0, MAX_EVENTS_TO_INSPECT)) {
|
|
89
106
|
if (!isRecord(raw))
|
|
90
107
|
throw new SchemaError('Royal Mail', 'Royal Mail returned invalid tracking events');
|
|
@@ -96,12 +113,15 @@ export function parseRoyalMailTrackingResponse(payload, trackingNumber) {
|
|
|
96
113
|
const code = cleanScalar(raw.eventCode, 64);
|
|
97
114
|
const mapped = royalMailEventStage(code);
|
|
98
115
|
const stage = mapped ?? royalMailStage(description) ?? undefined;
|
|
99
|
-
const time = eventTime(raw.eventDateTime);
|
|
100
116
|
const timeText = cleanScalar(raw.eventDateTime, 64);
|
|
101
117
|
const location = placeName(raw.locationName);
|
|
118
|
+
const clock = abroadClock(timeText, location);
|
|
119
|
+
if (clock && !abroad.has(timeText))
|
|
120
|
+
abroad.set(timeText, clock);
|
|
121
|
+
const time = clock ? ('time' in clock ? clock.time : null) : eventTime(raw.eventDateTime);
|
|
102
122
|
const event = {
|
|
103
|
-
...(time ? { time } : {}),
|
|
104
|
-
...(!time && timeText ? { provider_time_text: timeText } : {}),
|
|
123
|
+
...(clock ?? (time ? { time } : {})),
|
|
124
|
+
...(!time && !clock && timeText ? { provider_time_text: timeText } : {}),
|
|
105
125
|
...(location ? { location } : {}),
|
|
106
126
|
description: stage === 'delivered' ? 'Delivered' : description,
|
|
107
127
|
...(stage ? { stage } : {}),
|
|
@@ -127,15 +147,19 @@ export function parseRoyalMailTrackingResponse(payload, trackingNumber) {
|
|
|
127
147
|
throw new SchemaError('Royal Mail', 'Royal Mail returned no usable tracking status');
|
|
128
148
|
const stage = royalMailSummaryStage(summaryCategory, statusText) ?? undefined;
|
|
129
149
|
const delivered = stage === 'delivered';
|
|
150
|
+
// The summary repeats the newest scan's label, which a scan abroad corrects.
|
|
151
|
+
const lastText = cleanScalar(summary.lastEventDateTime, 64);
|
|
152
|
+
const lastClock = abroad.get(lastText);
|
|
153
|
+
const lastTime = lastClock ? ('time' in lastClock ? lastClock.time : null) : eventTime(lastText);
|
|
130
154
|
const deliveredAt = !delivered ? null : trimmed.find(event => event.stage === 'delivered' && event.time)?.time
|
|
131
|
-
?? (royalMailEventStage(cleanScalar(summary.lastEventCode, 64)) === 'delivered' ?
|
|
155
|
+
?? (royalMailEventStage(cleanScalar(summary.lastEventCode, 64)) === 'delivered' ? lastTime : null);
|
|
132
156
|
// Only the full-history reply names the destination.
|
|
133
157
|
const destination = cleanScalar(summary.destinationCountryCode, 8);
|
|
134
158
|
return {
|
|
135
159
|
status: stage ? statusForStage(stage) : 'unknown',
|
|
136
160
|
...(stage ? { current_stage: stage } : {}),
|
|
137
161
|
last_status_text: delivered ? 'Delivered' : statusText,
|
|
138
|
-
last_update:
|
|
162
|
+
last_update: lastClock ? lastTime : lastTime || trimmed[0]?.time || null,
|
|
139
163
|
expected_delivery: delivered || !isRecord(mailpiece.estimatedDelivery)
|
|
140
164
|
? null : expectedDelivery(mailpiece.estimatedDelivery.date),
|
|
141
165
|
...(deliveredAt ? { delivered_at: deliveredAt } : {}),
|
|
@@ -257,6 +257,24 @@
|
|
|
257
257
|
"confirmedBy": "live",
|
|
258
258
|
"firstSeen": "2026-10-08",
|
|
259
259
|
"note": "Same notice as EVAIP."
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
"code": "EVJOE",
|
|
263
|
+
"wording": "Item Received",
|
|
264
|
+
"language": "en",
|
|
265
|
+
"stage": "in_transit",
|
|
266
|
+
"confirmedBy": "live",
|
|
267
|
+
"firstSeen": "2026-10-12",
|
|
268
|
+
"note": "Arrival at the destination country's office of exchange; the shared wording alone would read as acceptance."
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
"code": "EVJDU",
|
|
272
|
+
"wording": "Item has left the overseas International Processing Centre",
|
|
273
|
+
"language": "en",
|
|
274
|
+
"stage": "in_transit",
|
|
275
|
+
"confirmedBy": "live",
|
|
276
|
+
"firstSeen": "2026-10-12",
|
|
277
|
+
"note": "Departure from the destination country's office of exchange."
|
|
260
278
|
}
|
|
261
279
|
]
|
|
262
280
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
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
|
+
export declare const WHISTL_ENDPOINT = "https://web.parcelhub.net/Tracking/reference/";
|
|
5
|
+
export declare class WhistlTracker {
|
|
6
|
+
private readonly options;
|
|
7
|
+
constructor(options?: {
|
|
8
|
+
fetcher?: typeof fetch;
|
|
9
|
+
recorder?: StepRecorder;
|
|
10
|
+
userAgent?: string;
|
|
11
|
+
});
|
|
12
|
+
fetch(raw: string, context?: TrackingContext): Promise<CarrierResult>;
|
|
13
|
+
}
|
|
14
|
+
export declare const adapter: AdapterFactory;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { accepted, recognizeFromLookup } from '../../core/adapter/index.js';
|
|
2
|
+
import { IndeterminateError, 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 { normalizeWhistlNumber, parseWhistl, PROVIDER, WHISTL_MAX_BYTES } from './parser.js';
|
|
8
|
+
// Whistl's parcel tracker on its Parcelhub platform, which Whistl's site links
|
|
9
|
+
// and whose form opens a reference at this path. No postcode is sent: the
|
|
10
|
+
// tracker takes it only to narrow the search.
|
|
11
|
+
export const WHISTL_ENDPOINT = 'https://web.parcelhub.net/Tracking/reference/';
|
|
12
|
+
const REDIRECTS = [301, 302, 303, 307, 308];
|
|
13
|
+
// A missing route is not a missing parcel: the tracker answers unknown references with a heading.
|
|
14
|
+
const GONE = [404, 410];
|
|
15
|
+
export class WhistlTracker {
|
|
16
|
+
options;
|
|
17
|
+
constructor(options = {}) {
|
|
18
|
+
this.options = options;
|
|
19
|
+
}
|
|
20
|
+
async fetch(raw, context = {}) {
|
|
21
|
+
const number = normalizeWhistlNumber(raw);
|
|
22
|
+
const url = `${WHISTL_ENDPOINT}${encodeURIComponent(number)}`;
|
|
23
|
+
const read = async (signal, timeoutMs) => {
|
|
24
|
+
const { response, bytes } = await fetchBounded(url, { method: 'GET', signal, headers: {
|
|
25
|
+
Accept: 'text/html,application/xhtml+xml',
|
|
26
|
+
'User-Agent': userAgentOf(this.options.userAgent),
|
|
27
|
+
} }, { provider: PROVIDER, timeoutMs: Math.max(1, Math.floor(timeoutMs)), maxBytes: WHISTL_MAX_BYTES,
|
|
28
|
+
redirect: 'manual', allowHttpStatuses: [...REDIRECTS, ...GONE], fetcher: this.options.fetcher });
|
|
29
|
+
if (REDIRECTS.includes(response.status))
|
|
30
|
+
throw new IndeterminateError(PROVIDER, 'Whistl sent the lookup to another page');
|
|
31
|
+
if (GONE.includes(response.status))
|
|
32
|
+
throw new TransportError(PROVIDER, 'Whistl tracking page is unavailable');
|
|
33
|
+
const html = decodeText(bytes);
|
|
34
|
+
if (!html.trim())
|
|
35
|
+
throw new IndeterminateError(PROVIDER, 'Whistl returned an empty page');
|
|
36
|
+
return parseWhistl(html, number);
|
|
37
|
+
};
|
|
38
|
+
// A request that fails to reach Whistl, or hangs, gets one more try: the
|
|
39
|
+
// first gets half of the budget, so the retry fits in the rest.
|
|
40
|
+
return runSteps({ carrier: 'whistl', budgetMs: context.budgetMs ?? 15_000, signal: context.signal,
|
|
41
|
+
recorder: this.options.recorder ?? NOOP_RECORDER }, [
|
|
42
|
+
{ id: 'direct', run: ({ signal, remainingMs }) => read(signal, firstAttemptMs(remainingMs)) },
|
|
43
|
+
networkRetryStep(({ signal, remainingMs }) => read(signal, remainingMs)),
|
|
44
|
+
]);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export const adapter = (environment) => {
|
|
48
|
+
const tracker = new WhistlTracker({ fetcher: environment.fetcher, recorder: environment.recorder, userAgent: environment.userAgent });
|
|
49
|
+
return {
|
|
50
|
+
id: 'whistl', recordsSteps: true, steps: ['direct', 'retry'],
|
|
51
|
+
track: (input, context) => tracker.fetch(input.number, context),
|
|
52
|
+
recognize: (number, context) => recognizeFromLookup(() => tracker.fetch(number, context), () => accepted(() => normalizeWhistlNumber(number))),
|
|
53
|
+
};
|
|
54
|
+
};
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
"displayName": "Whistl",
|
|
4
4
|
"aliases": [
|
|
5
5
|
"Whistl UK",
|
|
6
|
-
"TNT Post UK"
|
|
6
|
+
"TNT Post UK",
|
|
7
|
+
"Parcelhub",
|
|
8
|
+
"Whistl Despatch"
|
|
7
9
|
],
|
|
8
10
|
"region": {
|
|
9
11
|
"countries": [
|
|
@@ -17,25 +19,66 @@
|
|
|
17
19
|
"timezone": "Europe/London",
|
|
18
20
|
"tracking": {
|
|
19
21
|
"mode": "automatic",
|
|
20
|
-
"adapter": "
|
|
21
|
-
"steps": [
|
|
22
|
+
"adapter": "whistl",
|
|
23
|
+
"steps": [
|
|
24
|
+
"direct",
|
|
25
|
+
"retry"
|
|
26
|
+
],
|
|
27
|
+
"recognition": {
|
|
28
|
+
"rank": 880
|
|
29
|
+
},
|
|
30
|
+
"localClocks": true
|
|
22
31
|
},
|
|
23
|
-
"capabilities": [
|
|
32
|
+
"capabilities": [
|
|
33
|
+
"history",
|
|
34
|
+
"location",
|
|
35
|
+
"service_name",
|
|
36
|
+
"delivery_partner",
|
|
37
|
+
"delivery_tracking_number",
|
|
38
|
+
"canonical_tracking_number"
|
|
39
|
+
],
|
|
24
40
|
"portal": {
|
|
25
41
|
"url": "https://web.parcelhub.net/Tracking/reference/{trackingNumber}",
|
|
26
42
|
"searchUrl": "https://web.parcelhub.net/Tracking",
|
|
27
43
|
"canaryUrl": "https://web.parcelhub.net/Tracking",
|
|
28
|
-
"shows": [
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
44
|
+
"shows": [
|
|
45
|
+
"status",
|
|
46
|
+
"history",
|
|
47
|
+
"location",
|
|
48
|
+
"service_name",
|
|
49
|
+
"delivery_partner",
|
|
50
|
+
"delivery_tracking_number",
|
|
51
|
+
"sender_reference",
|
|
52
|
+
"recipient_reference"
|
|
53
|
+
],
|
|
54
|
+
"scraped": [
|
|
55
|
+
"status",
|
|
56
|
+
"history",
|
|
57
|
+
"location",
|
|
58
|
+
"service_name",
|
|
59
|
+
"delivery_partner",
|
|
60
|
+
"delivery_tracking_number"
|
|
61
|
+
],
|
|
62
|
+
"discarded": [
|
|
63
|
+
"sender_reference",
|
|
64
|
+
"recipient_reference",
|
|
65
|
+
"signatory"
|
|
66
|
+
],
|
|
67
|
+
"unavailable": [
|
|
68
|
+
"eta",
|
|
69
|
+
"weight",
|
|
70
|
+
"sender_name",
|
|
71
|
+
"recipient_name",
|
|
72
|
+
"offset_timestamps"
|
|
73
|
+
]
|
|
32
74
|
},
|
|
33
75
|
"links": [
|
|
34
76
|
{
|
|
35
77
|
"domains": [
|
|
36
|
-
"web.parcelhub.net"
|
|
78
|
+
"web.parcelhub.net",
|
|
79
|
+
"despatch.whistl.co.uk"
|
|
37
80
|
],
|
|
38
|
-
"path": "^/Tracking/reference/([^/?#]+)
|
|
81
|
+
"path": "^/Tracking/reference/([^/?#]+)(?:/postcode/[^/?#]*)?/?$"
|
|
39
82
|
},
|
|
40
83
|
{
|
|
41
84
|
"domains": [
|
|
@@ -12,13 +12,13 @@
|
|
|
12
12
|
"access": "full_page"
|
|
13
13
|
},
|
|
14
14
|
"context": {
|
|
15
|
-
"assessment": "
|
|
15
|
+
"assessment": "confirmed"
|
|
16
16
|
},
|
|
17
17
|
"expect": {
|
|
18
18
|
"carrier": "unknown",
|
|
19
19
|
"confidence": "none"
|
|
20
20
|
},
|
|
21
|
-
"note": "A review on Whistl's Trustpilot page gives it as the tracking number."
|
|
21
|
+
"note": "A review on Whistl's Trustpilot page gives it as the tracking number. Whistl's Parcelhub tracker knows it as a shipment reference whose one parcel another carrier delivers under its own number."
|
|
22
22
|
},
|
|
23
23
|
{
|
|
24
24
|
"number": "L1213411000055365001",
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { CarrierResult } from '../../core/result/index.js';
|
|
2
|
+
export declare const PROVIDER = "Whistl";
|
|
3
|
+
export declare const WHISTL_MAX_BYTES = 1000000;
|
|
4
|
+
/** A reference as Whistl's tracker takes it: letters and digits, at least one digit. */
|
|
5
|
+
export declare function normalizeWhistlNumber(raw: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* Whistl's tracker page for one shipment reference or tracking number: the
|
|
8
|
+
* shipment's reference as its heading, the carrier and service, and for each
|
|
9
|
+
* parcel its tracking number and scans, newest first. The tracker finds a
|
|
10
|
+
* shipment by its reference or by a parcel's tracking number, so the page
|
|
11
|
+
* must name the requested number as one of them. Sender's and recipient's
|
|
12
|
+
* references, signatories and comments are never read.
|
|
13
|
+
*/
|
|
14
|
+
export declare function parseWhistl(html: string, rawNumber: string): CarrierResult;
|