universal-parcel-scraper 0.121.0-main.1069 → 0.122.0-main.1072
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 +22 -4
- package/data/coverage-summary.json +1 -1
- package/data/detection-golden.json +96 -0
- package/dist/browser/scraper.js +14 -4
- package/dist/carriers/epost-global/adapter.d.ts +15 -0
- package/dist/carriers/epost-global/adapter.js +38 -0
- package/dist/carriers/epost-global/carrier.json +71 -10
- package/dist/carriers/epost-global/numbers.json +228 -0
- package/dist/carriers/epost-global/parser.d.ts +14 -0
- package/dist/carriers/epost-global/parser.js +212 -0
- package/dist/carriers/epost-global/status.d.ts +13 -0
- package/dist/carriers/epost-global/status.js +55 -0
- package/dist/carriers/epost-global/statuses.json +14 -1
- package/dist/core/catalog/statusMaps.js +2 -1
- package/dist/data/catalog.json +22 -4
- package/dist/generated/catalog.d.ts +13 -4
- package/dist/generated/catalog.js +22 -4
- 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 **181 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 5,
|
|
158
|
+
The carrier folders record 5,331 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
|
@@ -6141,15 +6141,33 @@
|
|
|
6141
6141
|
"timezone": "America/Los_Angeles",
|
|
6142
6142
|
"tracking": {
|
|
6143
6143
|
"mode": "automatic",
|
|
6144
|
-
"adapter": "
|
|
6144
|
+
"adapter": "epost-global",
|
|
6145
|
+
"recognitionRank": 65,
|
|
6146
|
+
"localClocks": true
|
|
6145
6147
|
},
|
|
6146
6148
|
"canaryUrl": "https://epgtrack.com/",
|
|
6147
|
-
"trackingUrlTemplate": "https://epgtrack.com/",
|
|
6149
|
+
"trackingUrlTemplate": "https://epgtrack.com/{trackingNumber}",
|
|
6148
6150
|
"trackingSearchUrl": "https://epgtrack.com/",
|
|
6149
|
-
"linkRules": [
|
|
6151
|
+
"linkRules": [
|
|
6152
|
+
{
|
|
6153
|
+
"domains": [
|
|
6154
|
+
"epgtrack.com"
|
|
6155
|
+
],
|
|
6156
|
+
"path": "^/(EPG\\d{10,15})/?$"
|
|
6157
|
+
},
|
|
6158
|
+
{
|
|
6159
|
+
"domains": [
|
|
6160
|
+
"epgtrack.com"
|
|
6161
|
+
],
|
|
6162
|
+
"pathPattern": "^/$",
|
|
6163
|
+
"params": [
|
|
6164
|
+
"id"
|
|
6165
|
+
]
|
|
6166
|
+
}
|
|
6167
|
+
],
|
|
6150
6168
|
"detectionRules": [
|
|
6151
6169
|
{
|
|
6152
|
-
"pattern": "^EPG\\d{10,
|
|
6170
|
+
"pattern": "^EPG\\d{10,15}$",
|
|
6153
6171
|
"confidence": "high"
|
|
6154
6172
|
}
|
|
6155
6173
|
]
|
|
@@ -28358,6 +28358,46 @@
|
|
|
28358
28358
|
"epost-global"
|
|
28359
28359
|
]
|
|
28360
28360
|
},
|
|
28361
|
+
{
|
|
28362
|
+
"input": "EPG011023513021511",
|
|
28363
|
+
"carrier": "epost-global",
|
|
28364
|
+
"confidence": "high",
|
|
28365
|
+
"candidates": [
|
|
28366
|
+
"epost-global"
|
|
28367
|
+
]
|
|
28368
|
+
},
|
|
28369
|
+
{
|
|
28370
|
+
"input": "EPG011024032660913",
|
|
28371
|
+
"carrier": "epost-global",
|
|
28372
|
+
"confidence": "high",
|
|
28373
|
+
"candidates": [
|
|
28374
|
+
"epost-global"
|
|
28375
|
+
]
|
|
28376
|
+
},
|
|
28377
|
+
{
|
|
28378
|
+
"input": "EPG011024033241487",
|
|
28379
|
+
"carrier": "epost-global",
|
|
28380
|
+
"confidence": "high",
|
|
28381
|
+
"candidates": [
|
|
28382
|
+
"epost-global"
|
|
28383
|
+
]
|
|
28384
|
+
},
|
|
28385
|
+
{
|
|
28386
|
+
"input": "EPG011024034506837",
|
|
28387
|
+
"carrier": "epost-global",
|
|
28388
|
+
"confidence": "high",
|
|
28389
|
+
"candidates": [
|
|
28390
|
+
"epost-global"
|
|
28391
|
+
]
|
|
28392
|
+
},
|
|
28393
|
+
{
|
|
28394
|
+
"input": "EPG011113700050012",
|
|
28395
|
+
"carrier": "epost-global",
|
|
28396
|
+
"confidence": "high",
|
|
28397
|
+
"candidates": [
|
|
28398
|
+
"epost-global"
|
|
28399
|
+
]
|
|
28400
|
+
},
|
|
28361
28401
|
{
|
|
28362
28402
|
"input": "EPG0112300001175",
|
|
28363
28403
|
"carrier": "epost-global",
|
|
@@ -28374,6 +28414,38 @@
|
|
|
28374
28414
|
"epost-global"
|
|
28375
28415
|
]
|
|
28376
28416
|
},
|
|
28417
|
+
{
|
|
28418
|
+
"input": "EPG030878223676213",
|
|
28419
|
+
"carrier": "epost-global",
|
|
28420
|
+
"confidence": "high",
|
|
28421
|
+
"candidates": [
|
|
28422
|
+
"epost-global"
|
|
28423
|
+
]
|
|
28424
|
+
},
|
|
28425
|
+
{
|
|
28426
|
+
"input": "EPG050927120007542",
|
|
28427
|
+
"carrier": "epost-global",
|
|
28428
|
+
"confidence": "high",
|
|
28429
|
+
"candidates": [
|
|
28430
|
+
"epost-global"
|
|
28431
|
+
]
|
|
28432
|
+
},
|
|
28433
|
+
{
|
|
28434
|
+
"input": "EPG071024031554244",
|
|
28435
|
+
"carrier": "epost-global",
|
|
28436
|
+
"confidence": "high",
|
|
28437
|
+
"candidates": [
|
|
28438
|
+
"epost-global"
|
|
28439
|
+
]
|
|
28440
|
+
},
|
|
28441
|
+
{
|
|
28442
|
+
"input": "EPG071101400605458",
|
|
28443
|
+
"carrier": "epost-global",
|
|
28444
|
+
"confidence": "high",
|
|
28445
|
+
"candidates": [
|
|
28446
|
+
"epost-global"
|
|
28447
|
+
]
|
|
28448
|
+
},
|
|
28377
28449
|
{
|
|
28378
28450
|
"input": "EPG10223001274",
|
|
28379
28451
|
"carrier": "epost-global",
|
|
@@ -28382,6 +28454,30 @@
|
|
|
28382
28454
|
"epost-global"
|
|
28383
28455
|
]
|
|
28384
28456
|
},
|
|
28457
|
+
{
|
|
28458
|
+
"input": "EPG141034707026058",
|
|
28459
|
+
"carrier": "epost-global",
|
|
28460
|
+
"confidence": "high",
|
|
28461
|
+
"candidates": [
|
|
28462
|
+
"epost-global"
|
|
28463
|
+
]
|
|
28464
|
+
},
|
|
28465
|
+
{
|
|
28466
|
+
"input": "EPG161034705036758",
|
|
28467
|
+
"carrier": "epost-global",
|
|
28468
|
+
"confidence": "high",
|
|
28469
|
+
"candidates": [
|
|
28470
|
+
"epost-global"
|
|
28471
|
+
]
|
|
28472
|
+
},
|
|
28473
|
+
{
|
|
28474
|
+
"input": "EPG250969101591620",
|
|
28475
|
+
"carrier": "epost-global",
|
|
28476
|
+
"confidence": "high",
|
|
28477
|
+
"candidates": [
|
|
28478
|
+
"epost-global"
|
|
28479
|
+
]
|
|
28480
|
+
},
|
|
28385
28481
|
{
|
|
28386
28482
|
"input": "ERA907538712MY",
|
|
28387
28483
|
"carrier": "pos-malaysia",
|
package/dist/browser/scraper.js
CHANGED
|
@@ -4942,14 +4942,23 @@ var De = {
|
|
|
4942
4942
|
timezone: "America/Los_Angeles",
|
|
4943
4943
|
tracking: {
|
|
4944
4944
|
mode: "automatic",
|
|
4945
|
-
adapter: "
|
|
4945
|
+
adapter: "epost-global",
|
|
4946
|
+
recognitionRank: 65,
|
|
4947
|
+
localClocks: !0
|
|
4946
4948
|
},
|
|
4947
4949
|
canaryUrl: "https://epgtrack.com/",
|
|
4948
|
-
trackingUrlTemplate: "https://epgtrack.com/",
|
|
4950
|
+
trackingUrlTemplate: "https://epgtrack.com/{trackingNumber}",
|
|
4949
4951
|
trackingSearchUrl: "https://epgtrack.com/",
|
|
4950
|
-
linkRules: [
|
|
4952
|
+
linkRules: [{
|
|
4953
|
+
domains: ["epgtrack.com"],
|
|
4954
|
+
path: "^/(EPG\\d{10,15})/?$"
|
|
4955
|
+
}, {
|
|
4956
|
+
domains: ["epgtrack.com"],
|
|
4957
|
+
pathPattern: "^/$",
|
|
4958
|
+
params: ["id"]
|
|
4959
|
+
}],
|
|
4951
4960
|
detectionRules: [{
|
|
4952
|
-
pattern: "^EPG\\d{10,
|
|
4961
|
+
pattern: "^EPG\\d{10,15}$",
|
|
4953
4962
|
confidence: "high"
|
|
4954
4963
|
}]
|
|
4955
4964
|
},
|
|
@@ -12351,6 +12360,7 @@ var Qc = {
|
|
|
12351
12360
|
elta: 150,
|
|
12352
12361
|
emile: 480,
|
|
12353
12362
|
envialia: 260,
|
|
12363
|
+
"epost-global": 65,
|
|
12354
12364
|
"express-one": 190,
|
|
12355
12365
|
"fan-courier": 330,
|
|
12356
12366
|
"four-px": 570,
|
|
@@ -0,0 +1,15 @@
|
|
|
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 action the tracking page posts its numbers to. */
|
|
5
|
+
export declare const EPOST_GLOBAL_ENDPOINT = "https://epgtrack.com/TrackingShipment/ShipmentData";
|
|
6
|
+
export declare class EpostGlobalTracker {
|
|
7
|
+
private readonly options;
|
|
8
|
+
constructor(options?: {
|
|
9
|
+
fetcher?: typeof fetch;
|
|
10
|
+
recorder?: StepRecorder;
|
|
11
|
+
userAgent?: string;
|
|
12
|
+
});
|
|
13
|
+
fetch(raw: string, context?: TrackingContext): Promise<CarrierResult>;
|
|
14
|
+
}
|
|
15
|
+
export declare const adapter: AdapterFactory;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { accepted, recognizeFromLookup } from '../../core/adapter/index.js';
|
|
2
|
+
import { runSteps } from '../../core/runner/index.js';
|
|
3
|
+
import { firstAttemptMs, networkRetryStep } from '../../core/runner/networkRetry.js';
|
|
4
|
+
import { NOOP_RECORDER } from '../../core/telemetry/index.js';
|
|
5
|
+
import { decodeText, fetchBounded, userAgentOf } from '../../core/transport/index.js';
|
|
6
|
+
import { EPOST_GLOBAL_MAX_BYTES, normalizeEpostGlobalNumber, parseEpostGlobal, PROVIDER } from './parser.js';
|
|
7
|
+
/** The action the tracking page posts its numbers to. */
|
|
8
|
+
export const EPOST_GLOBAL_ENDPOINT = 'https://epgtrack.com/TrackingShipment/ShipmentData';
|
|
9
|
+
export class EpostGlobalTracker {
|
|
10
|
+
options;
|
|
11
|
+
constructor(options = {}) {
|
|
12
|
+
this.options = options;
|
|
13
|
+
}
|
|
14
|
+
async fetch(raw, context = {}) {
|
|
15
|
+
const number = normalizeEpostGlobalNumber(raw);
|
|
16
|
+
const read = async (signal, timeoutMs) => {
|
|
17
|
+
const { bytes } = await fetchBounded(EPOST_GLOBAL_ENDPOINT, { method: 'POST', signal, headers: {
|
|
18
|
+
Accept: 'text/html, */*; q=0.01', 'User-Agent': userAgentOf(this.options.userAgent), 'X-Requested-With': 'XMLHttpRequest',
|
|
19
|
+
}, body: new URLSearchParams({ id: number }) }, { provider: PROVIDER, timeoutMs: Math.max(1, Math.floor(timeoutMs)), maxBytes: EPOST_GLOBAL_MAX_BYTES, fetcher: this.options.fetcher });
|
|
20
|
+
return parseEpostGlobal(decodeText(bytes), raw);
|
|
21
|
+
};
|
|
22
|
+
// A request that fails to reach the site, or hangs, gets one more try: the
|
|
23
|
+
// first gets half of the budget, so the retry fits in the rest.
|
|
24
|
+
return runSteps({ carrier: 'epost-global', budgetMs: context.budgetMs ?? 15_000, signal: context.signal,
|
|
25
|
+
recorder: this.options.recorder ?? NOOP_RECORDER }, [
|
|
26
|
+
{ id: 'direct', run: ({ signal, remainingMs }) => read(signal, firstAttemptMs(remainingMs)) },
|
|
27
|
+
networkRetryStep(({ signal, remainingMs }) => read(signal, remainingMs)),
|
|
28
|
+
]);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export const adapter = (environment) => {
|
|
32
|
+
const tracker = new EpostGlobalTracker({ fetcher: environment.fetcher, recorder: environment.recorder, userAgent: environment.userAgent });
|
|
33
|
+
return {
|
|
34
|
+
id: 'epost-global', recordsSteps: true, steps: ['direct', 'retry'],
|
|
35
|
+
track: (input, context) => tracker.fetch(input.number, context),
|
|
36
|
+
recognize: (number, context) => recognizeFromLookup(() => tracker.fetch(number, context), () => accepted(() => normalizeEpostGlobalNumber(number))),
|
|
37
|
+
};
|
|
38
|
+
};
|
|
@@ -17,24 +17,85 @@
|
|
|
17
17
|
"timezone": "America/Los_Angeles",
|
|
18
18
|
"tracking": {
|
|
19
19
|
"mode": "automatic",
|
|
20
|
-
"adapter": "
|
|
21
|
-
"steps": [
|
|
20
|
+
"adapter": "epost-global",
|
|
21
|
+
"steps": [
|
|
22
|
+
"direct",
|
|
23
|
+
"retry"
|
|
24
|
+
],
|
|
25
|
+
"recognition": {
|
|
26
|
+
"rank": 65
|
|
27
|
+
},
|
|
28
|
+
"localClocks": true
|
|
22
29
|
},
|
|
23
|
-
"capabilities": [
|
|
30
|
+
"capabilities": [
|
|
31
|
+
"history",
|
|
32
|
+
"location",
|
|
33
|
+
"provider_code",
|
|
34
|
+
"destination_country",
|
|
35
|
+
"delivery_partner",
|
|
36
|
+
"delivery_tracking_number"
|
|
37
|
+
],
|
|
24
38
|
"portal": {
|
|
25
|
-
"url": "https://epgtrack.com/",
|
|
39
|
+
"url": "https://epgtrack.com/{trackingNumber}",
|
|
26
40
|
"searchUrl": "https://epgtrack.com/",
|
|
27
41
|
"canaryUrl": "https://epgtrack.com/",
|
|
28
|
-
"shows": [
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
42
|
+
"shows": [
|
|
43
|
+
"status",
|
|
44
|
+
"history",
|
|
45
|
+
"location",
|
|
46
|
+
"progress_rail",
|
|
47
|
+
"destination_country",
|
|
48
|
+
"postcodes",
|
|
49
|
+
"related_awb_numbers",
|
|
50
|
+
"delivery_partner",
|
|
51
|
+
"delivery_tracking_number"
|
|
52
|
+
],
|
|
53
|
+
"scraped": [
|
|
54
|
+
"status",
|
|
55
|
+
"history",
|
|
56
|
+
"location",
|
|
57
|
+
"provider_code",
|
|
58
|
+
"offset_timestamps",
|
|
59
|
+
"destination_country",
|
|
60
|
+
"delivery_partner",
|
|
61
|
+
"delivery_tracking_number"
|
|
62
|
+
],
|
|
63
|
+
"discarded": [
|
|
64
|
+
"progress_rail",
|
|
65
|
+
"postcodes",
|
|
66
|
+
"related_awb_numbers"
|
|
67
|
+
],
|
|
68
|
+
"unavailable": [
|
|
69
|
+
"eta",
|
|
70
|
+
"delivered_at",
|
|
71
|
+
"weight",
|
|
72
|
+
"service_name",
|
|
73
|
+
"sender_name",
|
|
74
|
+
"recipient_name",
|
|
75
|
+
"proof_of_delivery"
|
|
76
|
+
]
|
|
32
77
|
},
|
|
33
|
-
"links": [
|
|
78
|
+
"links": [
|
|
79
|
+
{
|
|
80
|
+
"domains": [
|
|
81
|
+
"epgtrack.com"
|
|
82
|
+
],
|
|
83
|
+
"path": "^/(EPG\\d{10,15})/?$"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"domains": [
|
|
87
|
+
"epgtrack.com"
|
|
88
|
+
],
|
|
89
|
+
"pathPattern": "^/$",
|
|
90
|
+
"params": [
|
|
91
|
+
"id"
|
|
92
|
+
]
|
|
93
|
+
}
|
|
94
|
+
],
|
|
34
95
|
"detection": [
|
|
35
96
|
{
|
|
36
97
|
"id": "epost-global-1",
|
|
37
|
-
"pattern": "^EPG\\d{10,
|
|
98
|
+
"pattern": "^EPG\\d{10,15}$",
|
|
38
99
|
"confidence": "high"
|
|
39
100
|
}
|
|
40
101
|
]
|
|
@@ -289,6 +289,234 @@
|
|
|
289
289
|
"confidence": "high"
|
|
290
290
|
},
|
|
291
291
|
"note": "An Amazon seller's forum post lists it among ePost Global numbers rejected by Amazon's carrier-code check."
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
"number": "EPG011113700050012",
|
|
295
|
+
"role": "shipment",
|
|
296
|
+
"evidence": "public_shipment_report",
|
|
297
|
+
"source": {
|
|
298
|
+
"url": "https://web.archive.org/web/20260924140354/https://epgtrack.com/?id=EPG011113700050012",
|
|
299
|
+
"date": "2026-09-24",
|
|
300
|
+
"dateKind": "capture",
|
|
301
|
+
"access": "indexed_excerpt"
|
|
302
|
+
},
|
|
303
|
+
"context": {
|
|
304
|
+
"assessment": "confirmed"
|
|
305
|
+
},
|
|
306
|
+
"expect": {
|
|
307
|
+
"carrier": "epost-global",
|
|
308
|
+
"confidence": "high"
|
|
309
|
+
},
|
|
310
|
+
"note": "Looked up on ePost Global's own tracker, as an archived address shows; the capture was not opened. ePost Global's tracker knows it."
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
"number": "EPG011024034506837",
|
|
314
|
+
"role": "shipment",
|
|
315
|
+
"evidence": "public_shipment_report",
|
|
316
|
+
"source": {
|
|
317
|
+
"url": "https://web.archive.org/web/20260929124903/https://epgtrack.com/EPG011024034506837",
|
|
318
|
+
"date": "2026-09-29",
|
|
319
|
+
"dateKind": "capture",
|
|
320
|
+
"access": "indexed_excerpt"
|
|
321
|
+
},
|
|
322
|
+
"context": {
|
|
323
|
+
"assessment": "confirmed"
|
|
324
|
+
},
|
|
325
|
+
"expect": {
|
|
326
|
+
"carrier": "epost-global",
|
|
327
|
+
"confidence": "high"
|
|
328
|
+
},
|
|
329
|
+
"note": "Looked up on ePost Global's own tracker, as an archived address shows; the capture was not opened. ePost Global's tracker knows it."
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
"number": "EPG071101400605458",
|
|
333
|
+
"role": "shipment",
|
|
334
|
+
"evidence": "public_shipment_report",
|
|
335
|
+
"source": {
|
|
336
|
+
"url": "https://web.archive.org/web/20260901114745/https://epgtrack.com/?id=EPG071101400605458",
|
|
337
|
+
"date": "2026-09-01",
|
|
338
|
+
"dateKind": "capture",
|
|
339
|
+
"access": "indexed_excerpt"
|
|
340
|
+
},
|
|
341
|
+
"context": {
|
|
342
|
+
"assessment": "confirmed"
|
|
343
|
+
},
|
|
344
|
+
"expect": {
|
|
345
|
+
"carrier": "epost-global",
|
|
346
|
+
"confidence": "high"
|
|
347
|
+
},
|
|
348
|
+
"note": "Looked up on ePost Global's own tracker, as an archived address shows; the capture was not opened. ePost Global's tracker knows it."
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
"number": "EPG011023513021511",
|
|
352
|
+
"role": "shipment",
|
|
353
|
+
"evidence": "public_shipment_report",
|
|
354
|
+
"source": {
|
|
355
|
+
"url": "https://web.archive.org/web/20260813121523/https://epgtrack.com/?id=EPG011023513021511",
|
|
356
|
+
"date": "2026-08-13",
|
|
357
|
+
"dateKind": "capture",
|
|
358
|
+
"access": "indexed_excerpt"
|
|
359
|
+
},
|
|
360
|
+
"context": {
|
|
361
|
+
"assessment": "confirmed"
|
|
362
|
+
},
|
|
363
|
+
"expect": {
|
|
364
|
+
"carrier": "epost-global",
|
|
365
|
+
"confidence": "high"
|
|
366
|
+
},
|
|
367
|
+
"note": "Looked up on ePost Global's own tracker, as an archived address shows; the capture was not opened. ePost Global's tracker knows it."
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
"number": "EPG071024031554244",
|
|
371
|
+
"role": "shipment",
|
|
372
|
+
"evidence": "public_shipment_report",
|
|
373
|
+
"source": {
|
|
374
|
+
"url": "https://web.archive.org/web/20260801221733/https://epgtrack.com/EPG071024031554244",
|
|
375
|
+
"date": "2026-08-01",
|
|
376
|
+
"dateKind": "capture",
|
|
377
|
+
"access": "indexed_excerpt"
|
|
378
|
+
},
|
|
379
|
+
"context": {
|
|
380
|
+
"assessment": "confirmed"
|
|
381
|
+
},
|
|
382
|
+
"expect": {
|
|
383
|
+
"carrier": "epost-global",
|
|
384
|
+
"confidence": "high"
|
|
385
|
+
},
|
|
386
|
+
"note": "Looked up on ePost Global's own tracker, as an archived address shows; the capture was not opened. ePost Global's tracker knows it."
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
"number": "EPG011024033241487",
|
|
390
|
+
"role": "shipment",
|
|
391
|
+
"evidence": "public_shipment_report",
|
|
392
|
+
"source": {
|
|
393
|
+
"url": "https://web.archive.org/web/20260905144617/https://epgtrack.com/EPG011024033241487",
|
|
394
|
+
"date": "2026-09-05",
|
|
395
|
+
"dateKind": "capture",
|
|
396
|
+
"access": "indexed_excerpt"
|
|
397
|
+
},
|
|
398
|
+
"context": {
|
|
399
|
+
"assessment": "confirmed"
|
|
400
|
+
},
|
|
401
|
+
"expect": {
|
|
402
|
+
"carrier": "epost-global",
|
|
403
|
+
"confidence": "high"
|
|
404
|
+
},
|
|
405
|
+
"note": "Looked up on ePost Global's own tracker, as an archived address shows; the capture was not opened. ePost Global's tracker knows it."
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
"number": "EPG011024032660913",
|
|
409
|
+
"role": "shipment",
|
|
410
|
+
"evidence": "public_shipment_report",
|
|
411
|
+
"source": {
|
|
412
|
+
"url": "https://web.archive.org/web/20260825230629/https://epgtrack.com/EPG011024032660913",
|
|
413
|
+
"date": "2026-08-25",
|
|
414
|
+
"dateKind": "capture",
|
|
415
|
+
"access": "indexed_excerpt"
|
|
416
|
+
},
|
|
417
|
+
"context": {
|
|
418
|
+
"assessment": "confirmed"
|
|
419
|
+
},
|
|
420
|
+
"expect": {
|
|
421
|
+
"carrier": "epost-global",
|
|
422
|
+
"confidence": "high"
|
|
423
|
+
},
|
|
424
|
+
"note": "Looked up on ePost Global's own tracker, as an archived address shows; the capture was not opened. ePost Global's tracker knows it."
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
"number": "EPG030878223676213",
|
|
428
|
+
"role": "shipment",
|
|
429
|
+
"evidence": "public_shipment_report",
|
|
430
|
+
"source": {
|
|
431
|
+
"url": "https://web.archive.org/web/20240713081303/https://epgtrack.com/EPG030878223676213",
|
|
432
|
+
"date": "2024-07-13",
|
|
433
|
+
"dateKind": "capture",
|
|
434
|
+
"access": "indexed_excerpt"
|
|
435
|
+
},
|
|
436
|
+
"context": {
|
|
437
|
+
"assessment": "candidate"
|
|
438
|
+
},
|
|
439
|
+
"expect": {
|
|
440
|
+
"carrier": "epost-global",
|
|
441
|
+
"confidence": "high"
|
|
442
|
+
},
|
|
443
|
+
"note": "Looked up on ePost Global's own tracker, as an archived address shows; the capture was not opened."
|
|
444
|
+
},
|
|
445
|
+
{
|
|
446
|
+
"number": "EPG050927120007542",
|
|
447
|
+
"role": "shipment",
|
|
448
|
+
"evidence": "public_shipment_report",
|
|
449
|
+
"source": {
|
|
450
|
+
"url": "https://web.archive.org/web/20230127065102/https://epgtrack.com/EPG050927120007542",
|
|
451
|
+
"date": "2023-01-27",
|
|
452
|
+
"dateKind": "capture",
|
|
453
|
+
"access": "indexed_excerpt"
|
|
454
|
+
},
|
|
455
|
+
"context": {
|
|
456
|
+
"assessment": "candidate"
|
|
457
|
+
},
|
|
458
|
+
"expect": {
|
|
459
|
+
"carrier": "epost-global",
|
|
460
|
+
"confidence": "high"
|
|
461
|
+
},
|
|
462
|
+
"note": "Looked up on ePost Global's own tracker, as an archived address shows; the capture was not opened."
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
"number": "EPG141034707026058",
|
|
466
|
+
"role": "shipment",
|
|
467
|
+
"evidence": "public_shipment_report",
|
|
468
|
+
"source": {
|
|
469
|
+
"url": "https://web.archive.org/web/20250126060413/https://epgtrack.com/EPG141034707026058",
|
|
470
|
+
"date": "2025-01-26",
|
|
471
|
+
"dateKind": "capture",
|
|
472
|
+
"access": "indexed_excerpt"
|
|
473
|
+
},
|
|
474
|
+
"context": {
|
|
475
|
+
"assessment": "candidate"
|
|
476
|
+
},
|
|
477
|
+
"expect": {
|
|
478
|
+
"carrier": "epost-global",
|
|
479
|
+
"confidence": "high"
|
|
480
|
+
},
|
|
481
|
+
"note": "Looked up on ePost Global's own tracker, as an archived address shows; the capture was not opened."
|
|
482
|
+
},
|
|
483
|
+
{
|
|
484
|
+
"number": "EPG161034705036758",
|
|
485
|
+
"role": "shipment",
|
|
486
|
+
"evidence": "public_shipment_report",
|
|
487
|
+
"source": {
|
|
488
|
+
"url": "https://web.archive.org/web/20240811023147/https://epgtrack.com/EPG161034705036758",
|
|
489
|
+
"date": "2024-08-11",
|
|
490
|
+
"dateKind": "capture",
|
|
491
|
+
"access": "indexed_excerpt"
|
|
492
|
+
},
|
|
493
|
+
"context": {
|
|
494
|
+
"assessment": "candidate"
|
|
495
|
+
},
|
|
496
|
+
"expect": {
|
|
497
|
+
"carrier": "epost-global",
|
|
498
|
+
"confidence": "high"
|
|
499
|
+
},
|
|
500
|
+
"note": "Looked up on ePost Global's own tracker, as an archived address shows; the capture was not opened."
|
|
501
|
+
},
|
|
502
|
+
{
|
|
503
|
+
"number": "EPG250969101591620",
|
|
504
|
+
"role": "shipment",
|
|
505
|
+
"evidence": "public_shipment_report",
|
|
506
|
+
"source": {
|
|
507
|
+
"url": "https://web.archive.org/web/20231206193211/https://epgtrack.com/?id=EPG250969101591620",
|
|
508
|
+
"date": "2023-12-06",
|
|
509
|
+
"dateKind": "capture",
|
|
510
|
+
"access": "indexed_excerpt"
|
|
511
|
+
},
|
|
512
|
+
"context": {
|
|
513
|
+
"assessment": "candidate"
|
|
514
|
+
},
|
|
515
|
+
"expect": {
|
|
516
|
+
"carrier": "epost-global",
|
|
517
|
+
"confidence": "high"
|
|
518
|
+
},
|
|
519
|
+
"note": "Looked up on ePost Global's own tracker, as an archived address shows; the capture was not opened."
|
|
292
520
|
}
|
|
293
521
|
]
|
|
294
522
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { CarrierResult } from '../../core/result/index.js';
|
|
2
|
+
export declare const PROVIDER = "ePost Global";
|
|
3
|
+
export declare const EPOST_GLOBAL_MAX_BYTES: number;
|
|
4
|
+
/** An ePost Global number; anything else is a number the tracker is not asked for. */
|
|
5
|
+
export declare function normalizeEpostGlobalNumber(raw: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* The HTML fragment `POST /TrackingShipment/ShipmentData` answers: one card
|
|
8
|
+
* per number asked, whose click handler carries the shipment's details as
|
|
9
|
+
* JSON. The tracker also finds a shipment by its partner's number, so the
|
|
10
|
+
* details count only when their own number is the one asked for. The
|
|
11
|
+
* recipient's postcode, the scans' ZIP codes, the master air waybill and the
|
|
12
|
+
* customer reference are never read.
|
|
13
|
+
*/
|
|
14
|
+
export declare function parseEpostGlobal(html: string, raw: string): CarrierResult;
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { load } from 'cheerio';
|
|
2
|
+
import { DateTime } from 'luxon';
|
|
3
|
+
import { carrierIdFromName, carrierIdFromPartner } from '../../core/catalog/hints.js';
|
|
4
|
+
import { normalizeTrackingNumber } from '../../core/detection/index.js';
|
|
5
|
+
import { USPS_ROUTING_BARCODE, uspsPackageIdentifier } from '../../core/detection/usps.js';
|
|
6
|
+
import { ChallengeError, IndeterminateError, InvalidInputError, NotFoundError, SchemaError } from '../../core/errors/index.js';
|
|
7
|
+
import { languageStageStatus } from '../../core/status/index.js';
|
|
8
|
+
import { firstOccurrence, usStateTimeZone } from '../../core/time/index.js';
|
|
9
|
+
import { cleanScalar } from '../../core/transport/index.js';
|
|
10
|
+
import { isRecord } from '../../core/types.js';
|
|
11
|
+
import { epostGlobalFacilityScan, epostGlobalStage } from './status.js';
|
|
12
|
+
export const PROVIDER = 'ePost Global';
|
|
13
|
+
// A delivered parcel's reply is up to a hundred kilobytes: its history twice,
|
|
14
|
+
// as data and as rendered rows.
|
|
15
|
+
export const EPOST_GLOBAL_MAX_BYTES = 1024 * 1024;
|
|
16
|
+
// EPG and ten to fifteen digits; current labels print fifteen.
|
|
17
|
+
const NUMBER = /^EPG\d{10,15}$/;
|
|
18
|
+
// The page's call that shows a card's details: its scans, the card's index,
|
|
19
|
+
// the number asked, the partner's number and the shipment's details.
|
|
20
|
+
const CALL = /^transactionDetails\('(.*?)','(\d+)','([^']*)', '([^']*)', '(.*)'\)$/s;
|
|
21
|
+
// The tracker's notice on the card of a number it has no shipment for.
|
|
22
|
+
const NOT_TRACKED = /We haven.t received any tracking updates/;
|
|
23
|
+
// A browser check, which the site has not been seen to send.
|
|
24
|
+
const CHALLENGE = /captcha|cf-turnstile|_cf_chl_opt|<title>\s*(?:Just a moment|Attention Required)/i;
|
|
25
|
+
const WALL_CLOCK = /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})(?:\.\d{1,7})?$/;
|
|
26
|
+
// A processing center's place: its town and state ("Itasca IL").
|
|
27
|
+
const FACILITY = /^[A-Za-z][A-Za-z .'-]{0,60} ([A-Z]{2})$/;
|
|
28
|
+
// States split between zones, where the state does not tell the clock.
|
|
29
|
+
const SPLIT_STATES = new Set(['AK', 'FL', 'ID', 'IN', 'KS', 'KY', 'MI', 'ND', 'NE', 'NV', 'OR', 'SD', 'TN', 'TX']);
|
|
30
|
+
// Partners whose names the catalog does not know. Spring's PostNL service is
|
|
31
|
+
// tracked by spring-gds under the same number.
|
|
32
|
+
const VENDORS = new Map([['SPRING POST NL', 'spring-gds']]);
|
|
33
|
+
const MAX_ROWS = 500;
|
|
34
|
+
const MAX_EVENTS = 100;
|
|
35
|
+
/** An ePost Global number; anything else is a number the tracker is not asked for. */
|
|
36
|
+
export function normalizeEpostGlobalNumber(raw) {
|
|
37
|
+
const number = raw.length <= 64 ? normalizeTrackingNumber(raw) : '';
|
|
38
|
+
if (!NUMBER.test(number))
|
|
39
|
+
throw new InvalidInputError(PROVIDER, 'ePost Global numbers are EPG and ten to fifteen digits');
|
|
40
|
+
return number;
|
|
41
|
+
}
|
|
42
|
+
/** An optional text field; an object or array is a changed reply. */
|
|
43
|
+
function text(row, key, maxLength = 500) {
|
|
44
|
+
const value = row[key];
|
|
45
|
+
if (value === null || value === undefined)
|
|
46
|
+
return '';
|
|
47
|
+
if (typeof value !== 'string' && typeof value !== 'number')
|
|
48
|
+
throw new SchemaError(PROVIDER, `ePost Global returned an invalid ${key}`);
|
|
49
|
+
return cleanScalar(value, maxLength);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* A scan's place up to its first word holding a digit, where partners put a
|
|
53
|
+
* postcode, a depot number or a street address. A dash is no place, and a
|
|
54
|
+
* country named twice is named once.
|
|
55
|
+
*/
|
|
56
|
+
function placeOf(raw) {
|
|
57
|
+
const words = [];
|
|
58
|
+
for (const word of raw.split(' ')) {
|
|
59
|
+
if (/\d/.test(word))
|
|
60
|
+
break;
|
|
61
|
+
words.push(word);
|
|
62
|
+
}
|
|
63
|
+
const parts = words.join(' ').split(',').map((part) => part.trim()).filter(Boolean);
|
|
64
|
+
const place = parts.filter((part, index) => part !== parts[index - 1]).join(', ');
|
|
65
|
+
return /^[-.\s]*$/.test(place) ? '' : place;
|
|
66
|
+
}
|
|
67
|
+
/** The zone of a processing center's scan: the clock of the center's state. */
|
|
68
|
+
function facilityZone(description, place) {
|
|
69
|
+
if (!epostGlobalFacilityScan(description))
|
|
70
|
+
return null;
|
|
71
|
+
const state = FACILITY.exec(place)?.[1];
|
|
72
|
+
return state && !SPLIT_STATES.has(state) ? usStateTimeZone(state) : null;
|
|
73
|
+
}
|
|
74
|
+
/** A scan's clock: an instant in the center's zone, else the wall clock as stamped. */
|
|
75
|
+
function clockOf(stamp, zone) {
|
|
76
|
+
const wall = WALL_CLOCK.exec(stamp)?.[1];
|
|
77
|
+
if (!wall || !DateTime.fromISO(wall, { zone: 'utc' }).isValid) {
|
|
78
|
+
throw new SchemaError(PROVIDER, 'ePost Global returned an invalid scan time');
|
|
79
|
+
}
|
|
80
|
+
if (!zone)
|
|
81
|
+
return { local_time: wall };
|
|
82
|
+
const time = firstOccurrence(DateTime.fromISO(wall, { zone })).toISO({ suppressMilliseconds: true });
|
|
83
|
+
if (!time)
|
|
84
|
+
throw new SchemaError(PROVIDER, 'ePost Global returned an invalid scan time');
|
|
85
|
+
return { time };
|
|
86
|
+
}
|
|
87
|
+
function projectScan(row) {
|
|
88
|
+
if (!isRecord(row))
|
|
89
|
+
throw new SchemaError(PROVIDER, 'ePost Global returned an invalid scan');
|
|
90
|
+
const description = text(row, 'Event');
|
|
91
|
+
if (!description)
|
|
92
|
+
return null;
|
|
93
|
+
const stamp = text(row, 'EventDT', 64);
|
|
94
|
+
const place = placeOf(text(row, 'Loc', 200));
|
|
95
|
+
const code = text(row, 'ECode', 32);
|
|
96
|
+
const staged = epostGlobalStage(description);
|
|
97
|
+
const event = {
|
|
98
|
+
...clockOf(stamp, facilityZone(description, place)),
|
|
99
|
+
description,
|
|
100
|
+
...(place ? { location: place } : {}),
|
|
101
|
+
...(code ? { provider_code: code } : {}),
|
|
102
|
+
...(staged ? { stage: staged.stage, stage_source: staged.source } : {}),
|
|
103
|
+
};
|
|
104
|
+
return { key: JSON.stringify([stamp, description, place, code]), event };
|
|
105
|
+
}
|
|
106
|
+
/** The delivering partner: one the catalog knows by name, whose link, if any, is its own. */
|
|
107
|
+
function deliveryPartner(vendor, url) {
|
|
108
|
+
const mapped = VENDORS.get(vendor.toUpperCase());
|
|
109
|
+
if (mapped)
|
|
110
|
+
return mapped;
|
|
111
|
+
const named = vendor ? carrierIdFromName(vendor) : undefined;
|
|
112
|
+
return named && named !== 'epost-global' && carrierIdFromPartner(vendor, url) === named ? named : undefined;
|
|
113
|
+
}
|
|
114
|
+
/** The partner's number, without the recipient's ZIP code a USPS routing barcode opens with. */
|
|
115
|
+
function partnerNumber(raw, number) {
|
|
116
|
+
const compact = normalizeTrackingNumber(raw);
|
|
117
|
+
const code = USPS_ROUTING_BARCODE.test(compact) ? uspsPackageIdentifier(compact) ?? '' : compact;
|
|
118
|
+
return /^[A-Z0-9]{4,40}$/.test(code) && code !== number ? code : undefined;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* The HTML fragment `POST /TrackingShipment/ShipmentData` answers: one card
|
|
122
|
+
* per number asked, whose click handler carries the shipment's details as
|
|
123
|
+
* JSON. The tracker also finds a shipment by its partner's number, so the
|
|
124
|
+
* details count only when their own number is the one asked for. The
|
|
125
|
+
* recipient's postcode, the scans' ZIP codes, the master air waybill and the
|
|
126
|
+
* customer reference are never read.
|
|
127
|
+
*/
|
|
128
|
+
export function parseEpostGlobal(html, raw) {
|
|
129
|
+
const number = normalizeEpostGlobalNumber(raw);
|
|
130
|
+
const $ = load(html);
|
|
131
|
+
const cards = $('div[id^="tracking-card"]');
|
|
132
|
+
if (!cards.length) {
|
|
133
|
+
if (CHALLENGE.test(html))
|
|
134
|
+
throw new ChallengeError(PROVIDER, 'ePost Global answered with a browser check');
|
|
135
|
+
throw new SchemaError(PROVIDER, 'ePost Global returned a page without a shipment card');
|
|
136
|
+
}
|
|
137
|
+
if (cards.length > 1)
|
|
138
|
+
throw new SchemaError(PROVIDER, 'ePost Global returned several shipments');
|
|
139
|
+
const call = CALL.exec(cards.attr('onclick') ?? '');
|
|
140
|
+
if (!call)
|
|
141
|
+
throw new SchemaError(PROVIDER, 'ePost Global returned a card without its details');
|
|
142
|
+
if (normalizeTrackingNumber(call[3]) !== number)
|
|
143
|
+
throw new SchemaError(PROVIDER, 'ePost Global answered for another number');
|
|
144
|
+
if (!call[5]) {
|
|
145
|
+
if (NOT_TRACKED.test(cards.text()))
|
|
146
|
+
throw new NotFoundError(PROVIDER);
|
|
147
|
+
throw new IndeterminateError(PROVIDER, 'ePost Global returned a card without details');
|
|
148
|
+
}
|
|
149
|
+
let details;
|
|
150
|
+
try {
|
|
151
|
+
details = JSON.parse(call[5]);
|
|
152
|
+
}
|
|
153
|
+
catch (cause) {
|
|
154
|
+
throw new SchemaError(PROVIDER, 'ePost Global returned details that are not JSON', { cause });
|
|
155
|
+
}
|
|
156
|
+
if (!isRecord(details))
|
|
157
|
+
throw new SchemaError(PROVIDER, 'ePost Global returned invalid details');
|
|
158
|
+
const track = normalizeTrackingNumber(text(details, 'Track', 64));
|
|
159
|
+
if (normalizeTrackingNumber(text(details, 'Ref', 64)) !== number) {
|
|
160
|
+
// Found by another number of the shipment: its partner's, which is not ePost Global's own.
|
|
161
|
+
if (track === number)
|
|
162
|
+
throw new IndeterminateError(PROVIDER, 'ePost Global found another shipment by its partner number');
|
|
163
|
+
throw new SchemaError(PROVIDER, 'ePost Global returned another shipment');
|
|
164
|
+
}
|
|
165
|
+
const rows = details.Events;
|
|
166
|
+
if (!Array.isArray(rows))
|
|
167
|
+
throw new SchemaError(PROVIDER, 'ePost Global returned a shipment without scans');
|
|
168
|
+
if (rows.length > MAX_ROWS)
|
|
169
|
+
throw new SchemaError(PROVIDER, 'ePost Global returned an oversized history');
|
|
170
|
+
const seen = new Set();
|
|
171
|
+
const events = [];
|
|
172
|
+
for (const row of rows) {
|
|
173
|
+
const scan = projectScan(row);
|
|
174
|
+
if (!scan || seen.has(scan.key))
|
|
175
|
+
continue;
|
|
176
|
+
seen.add(scan.key);
|
|
177
|
+
events.push(scan.event);
|
|
178
|
+
}
|
|
179
|
+
if (!events.length)
|
|
180
|
+
throw new IndeterminateError(PROVIDER, 'ePost Global has no scans for the shipment yet');
|
|
181
|
+
// The reply lists scans newest first by their stamps. Partner clocks are not
|
|
182
|
+
// instants, so its order stands. A receipt after the parcel moved on is the
|
|
183
|
+
// partner's, not its acceptance.
|
|
184
|
+
let moved = false;
|
|
185
|
+
for (const event of [...events].reverse()) {
|
|
186
|
+
const stage = event.stage;
|
|
187
|
+
if (stage === 'accepted' && moved)
|
|
188
|
+
Object.assign(event, { stage: 'in_transit', stage_source: 'carrier_map' });
|
|
189
|
+
if (stage && !['registered', 'accepted', 'exception'].includes(stage))
|
|
190
|
+
moved = true;
|
|
191
|
+
}
|
|
192
|
+
const newest = events[0];
|
|
193
|
+
// A partner's announcement after ePost Global shipped the parcel is no step back.
|
|
194
|
+
const current = events.find((event) => event.stage && event.stage !== 'registered') ?? events.find((event) => event.stage);
|
|
195
|
+
const stage = current?.stage;
|
|
196
|
+
const partner = deliveryPartner(text(details, 'Vendor', 80), text(details, 'VendorTrackingUrl', 500) || text(details, 'VendorBaseUrl', 500));
|
|
197
|
+
const delivery = partnerNumber(track, number);
|
|
198
|
+
const country = text(details, 'DC', 8).toUpperCase();
|
|
199
|
+
return {
|
|
200
|
+
status: stage ? languageStageStatus(stage) : 'unknown',
|
|
201
|
+
...(stage ? { current_stage: stage, current_stage_source: current.stage_source } : {}),
|
|
202
|
+
last_status_text: newest.description,
|
|
203
|
+
last_update: newest.time ?? null,
|
|
204
|
+
...(newest.local_time ? { last_update_local: newest.local_time } : {}),
|
|
205
|
+
expected_delivery: null,
|
|
206
|
+
...(partner ? { delivery_carrier: partner } : {}),
|
|
207
|
+
...(delivery ? { delivery_tracking_number: delivery } : {}),
|
|
208
|
+
...(/^[A-Z]{2}$/.test(country) ? { destination_country: country } : {}),
|
|
209
|
+
events: events.slice(0, MAX_EVENTS),
|
|
210
|
+
...(events.length > MAX_EVENTS ? { history_truncated: true } : {}),
|
|
211
|
+
};
|
|
212
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type ClassifiedWording, type Stage } from '../../core/status/index.js';
|
|
2
|
+
import { type CarrierStatusMap } from '../../core/status/statusMap.js';
|
|
3
|
+
/** The stage the map gives the wording, if it knows it. */
|
|
4
|
+
export declare function epostGlobalScanStage(wording: string): Stage | undefined;
|
|
5
|
+
/** A scan at one of ePost Global's processing centers. */
|
|
6
|
+
export declare function epostGlobalFacilityScan(wording: string): boolean;
|
|
7
|
+
/**
|
|
8
|
+
* A scan's stage from the map, else from the shared wording rules. Wording
|
|
9
|
+
* neither knows stays without a stage, so it remains visible for review.
|
|
10
|
+
*/
|
|
11
|
+
export declare function epostGlobalStage(wording: string): ClassifiedWording | null;
|
|
12
|
+
/** Codes are shared with the partners' own, so only the wording is read. */
|
|
13
|
+
export declare const statusMap: CarrierStatusMap;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { classifyWording } from '../../core/status/index.js';
|
|
2
|
+
import { normalizeStatusWording } from '../../core/status/statusMap.js';
|
|
3
|
+
/**
|
|
4
|
+
* ePost Global's English wording. Its rows carry a code, but rows it relays
|
|
5
|
+
* from the partner that delivers the parcel keep the partner's code, so a code
|
|
6
|
+
* alone names no scan: PostNL's 30 is ePost Global's 30. The map reads the
|
|
7
|
+
* wording: ePost Global's own, then the partners' that the shared wording
|
|
8
|
+
* rules leave without a stage. Other partner wording goes to those rules.
|
|
9
|
+
*/
|
|
10
|
+
const STAGES = new Map([
|
|
11
|
+
['data received', 'registered'],
|
|
12
|
+
// Stamped a minute after the data, every time: the label, not a scan.
|
|
13
|
+
['shipment in transit to the epost global processing center', 'registered'],
|
|
14
|
+
['parcel received and processing', 'accepted'],
|
|
15
|
+
['parcel shipped', 'in_transit'],
|
|
16
|
+
// DAI Post, for New Zealand: its manifest, then its origin hub and the airline.
|
|
17
|
+
['customer information manifested', 'registered'],
|
|
18
|
+
['checked in at origin hub', 'in_transit'],
|
|
19
|
+
['airline delay', 'in_transit'],
|
|
20
|
+
['collected from the airline for processing', 'in_transit'],
|
|
21
|
+
// PostNL, through Spring.
|
|
22
|
+
['the item is tied to a mawb and ready for shipment', 'in_transit'],
|
|
23
|
+
// La Poste's wording for parcels PostNL brings to France, after they left the United States.
|
|
24
|
+
['your item has been dropped off by the sender with our postal partner in the country of origin.', 'in_transit'],
|
|
25
|
+
['your item is ready to be forwarded from its foreign territory. it will be given to the carrier for forwarding.', 'in_transit'],
|
|
26
|
+
]);
|
|
27
|
+
// The handover a minute after shipping: "In transit to {service} tracking #: {number}".
|
|
28
|
+
const HANDED_OVER = /^in transit (?:to|via) \S.* tracking ?#:/;
|
|
29
|
+
// The rows ePost Global's processing centers scan, stamped on the center's clock.
|
|
30
|
+
const FACILITY_SCANS = new Set(['parcel received and processing', 'parcel shipped']);
|
|
31
|
+
/** The stage the map gives the wording, if it knows it. */
|
|
32
|
+
export function epostGlobalScanStage(wording) {
|
|
33
|
+
const normalized = normalizeStatusWording(wording);
|
|
34
|
+
return HANDED_OVER.test(normalized) ? 'in_transit' : STAGES.get(normalized);
|
|
35
|
+
}
|
|
36
|
+
/** A scan at one of ePost Global's processing centers. */
|
|
37
|
+
export function epostGlobalFacilityScan(wording) {
|
|
38
|
+
return FACILITY_SCANS.has(normalizeStatusWording(wording));
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* A scan's stage from the map, else from the shared wording rules. Wording
|
|
42
|
+
* neither knows stays without a stage, so it remains visible for review.
|
|
43
|
+
*/
|
|
44
|
+
export function epostGlobalStage(wording) {
|
|
45
|
+
const mapped = epostGlobalScanStage(wording);
|
|
46
|
+
if (mapped)
|
|
47
|
+
return { stage: mapped, source: 'carrier_map' };
|
|
48
|
+
const worded = classifyWording(wording, 'in_transit');
|
|
49
|
+
return worded.source === 'none' ? null : worded;
|
|
50
|
+
}
|
|
51
|
+
/** Codes are shared with the partners' own, so only the wording is read. */
|
|
52
|
+
export const statusMap = {
|
|
53
|
+
stage: (_code, wording) => epostGlobalScanStage(wording),
|
|
54
|
+
gaps: [],
|
|
55
|
+
};
|
|
@@ -1,4 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"carrier": "epost-global",
|
|
3
|
-
"entries": [
|
|
3
|
+
"entries": [
|
|
4
|
+
{"code": "11", "wording": "Data Received", "stage": "registered", "language": "en", "confirmedBy": "live", "note": "The shipper's data, without a place."},
|
|
5
|
+
{"code": "EPGS01", "wording": "Shipment in Transit to the ePost Global Processing Center", "stage": "registered", "language": "en", "confirmedBy": "live", "note": "Stamped a minute after the data on every parcel that has it: the label, not a scan."},
|
|
6
|
+
{"code": "30", "wording": "Parcel Received and Processing", "stage": "accepted", "language": "en", "confirmedBy": "live", "note": "A processing center's receipt, with its town and state; on the center's own clock."},
|
|
7
|
+
{"code": "32", "wording": "Parcel Shipped", "stage": "in_transit", "language": "en", "confirmedBy": "live", "note": "The parcel left the processing center, on the center's clock."},
|
|
8
|
+
{"code": "41", "wording": "In transit to … tracking #: …", "stage": "in_transit", "language": "en", "confirmedBy": "live", "note": "The handover a minute after shipping, naming the partner's service and number; also worded \"In transit via …\" and with \"tracking#:\"."},
|
|
9
|
+
{"code": "CI02", "wording": "Customer Information Manifested", "stage": "registered", "language": "en", "confirmedBy": "live", "note": "Relayed from DAI Post, which delivers in New Zealand."},
|
|
10
|
+
{"code": "OA01", "wording": "Checked in at origin hub", "stage": "in_transit", "language": "en", "confirmedBy": "live", "note": "Relayed from DAI Post, at its US hub after ePost Global shipped the parcel."},
|
|
11
|
+
{"code": "AD01", "wording": "Airline Delay", "stage": "in_transit", "language": "en", "confirmedBy": "live", "note": "Relayed from DAI Post; a delay, not a problem with the parcel."},
|
|
12
|
+
{"code": "DA05", "wording": "Collected from the airline for processing", "stage": "in_transit", "language": "en", "confirmedBy": "live", "note": "Relayed from DAI Post in the destination country."},
|
|
13
|
+
{"code": "1239", "wording": "The item is tied to a MAWB and ready for shipment", "stage": "in_transit", "language": "en", "confirmedBy": "live", "note": "Relayed from PostNL through Spring, after ePost Global shipped the parcel."},
|
|
14
|
+
{"code": "EPG-1272", "wording": "Your item has been dropped off by the sender with our postal partner in the country of origin.", "stage": "in_transit", "language": "en", "confirmedBy": "live", "note": "La Poste's wording for a parcel PostNL brings to France, relayed after PostNL accepted it."},
|
|
15
|
+
{"code": "EPG-1270", "wording": "Your item is ready to be forwarded from its foreign territory. It will be given to the carrier for forwarding.", "stage": "in_transit", "language": "en", "confirmedBy": "live", "note": "La Poste's wording, relayed for a parcel bound for France."}
|
|
16
|
+
]
|
|
4
17
|
}
|
|
@@ -41,6 +41,7 @@ import { statusMap as econt } from '../../carriers/econt/status.js';
|
|
|
41
41
|
import { statusMap as elta } from '../../carriers/elta/status.js';
|
|
42
42
|
import { statusMap as emile } from '../../carriers/emile/status.js';
|
|
43
43
|
import { statusMap as envialia } from '../../carriers/envialia/status.js';
|
|
44
|
+
import { statusMap as epostGlobal } from '../../carriers/epost-global/status.js';
|
|
44
45
|
import { statusMap as expressOne } from '../../carriers/express-one/status.js';
|
|
45
46
|
import { statusMap as fanCourier } from '../../carriers/fan-courier/status.js';
|
|
46
47
|
import { statusMap as foxpost } from '../../carriers/foxpost/status.js';
|
|
@@ -105,7 +106,7 @@ export const STATUS_MAPS = {
|
|
|
105
106
|
'acs-courier': acsCourier, aliexpress, 'allegro-one': allegroOne, 'an-post': anPost, 'apc-overnight': apcOvernight, 'aras-kargo': arasKargo,
|
|
106
107
|
'box-now': boxNow, cacesa, cargus, cdek, cdl, 'ceska-posta': ceskaPosta, 'china-post': chinaPost, 'chit-chats': chitChats, chronopost, 'correos-spain': correosSpain,
|
|
107
108
|
'cyprus-post': cyprusPost, dao, 'dhl-express': dhlExpress, dpd, 'dpd-de': dpdDe, 'dpd-fr': dpdFr, 'dpd-ie': dpdIe, 'dpd-pl': dpdPl, dsv,
|
|
108
|
-
dynalogic, 'early-bird': earlyBird, econt, elta, emile, envialia, 'express-one': expressOne, 'fan-courier': fanCourier, foxpost,
|
|
109
|
+
dynalogic, 'early-bird': earlyBird, econt, elta, emile, envialia, 'epost-global': epostGlobal, 'express-one': expressOne, 'fan-courier': fanCourier, foxpost,
|
|
109
110
|
'gebrueder-weiss': gebruederWeiss, geis, 'geniki-taxydromiki': geniki, 'gls-es': glsEs, 'gls-it': glsIt, 'go-express': goExpress,
|
|
110
111
|
'gofo-fr': gofoFr, 'gofo-it': gofoIt, helthjem, 'hrvatska-posta': hrvatskaPosta, 'j-and-t': jAndT, 'j-and-t-cargo': jntCargo, jitsu,
|
|
111
112
|
'kuehne-nagel': kuehneNagel, 'la-poste': laPoste, 'latvijas-pasts': latvijasPasts, 'lietuvos-pastas': lietuvosPastas,
|
package/dist/data/catalog.json
CHANGED
|
@@ -6141,15 +6141,33 @@
|
|
|
6141
6141
|
"timezone": "America/Los_Angeles",
|
|
6142
6142
|
"tracking": {
|
|
6143
6143
|
"mode": "automatic",
|
|
6144
|
-
"adapter": "
|
|
6144
|
+
"adapter": "epost-global",
|
|
6145
|
+
"recognitionRank": 65,
|
|
6146
|
+
"localClocks": true
|
|
6145
6147
|
},
|
|
6146
6148
|
"canaryUrl": "https://epgtrack.com/",
|
|
6147
|
-
"trackingUrlTemplate": "https://epgtrack.com/",
|
|
6149
|
+
"trackingUrlTemplate": "https://epgtrack.com/{trackingNumber}",
|
|
6148
6150
|
"trackingSearchUrl": "https://epgtrack.com/",
|
|
6149
|
-
"linkRules": [
|
|
6151
|
+
"linkRules": [
|
|
6152
|
+
{
|
|
6153
|
+
"domains": [
|
|
6154
|
+
"epgtrack.com"
|
|
6155
|
+
],
|
|
6156
|
+
"path": "^/(EPG\\d{10,15})/?$"
|
|
6157
|
+
},
|
|
6158
|
+
{
|
|
6159
|
+
"domains": [
|
|
6160
|
+
"epgtrack.com"
|
|
6161
|
+
],
|
|
6162
|
+
"pathPattern": "^/$",
|
|
6163
|
+
"params": [
|
|
6164
|
+
"id"
|
|
6165
|
+
]
|
|
6166
|
+
}
|
|
6167
|
+
],
|
|
6150
6168
|
"detectionRules": [
|
|
6151
6169
|
{
|
|
6152
|
-
"pattern": "^EPG\\d{10,
|
|
6170
|
+
"pattern": "^EPG\\d{10,15}$",
|
|
6153
6171
|
"confidence": "high"
|
|
6154
6172
|
}
|
|
6155
6173
|
]
|
|
@@ -4174,14 +4174,23 @@ export declare const CARRIER_CATALOG: {
|
|
|
4174
4174
|
readonly timezone: "America/Los_Angeles";
|
|
4175
4175
|
readonly tracking: {
|
|
4176
4176
|
readonly mode: "automatic";
|
|
4177
|
-
readonly adapter: "
|
|
4177
|
+
readonly adapter: "epost-global";
|
|
4178
|
+
readonly recognitionRank: 65;
|
|
4179
|
+
readonly localClocks: true;
|
|
4178
4180
|
};
|
|
4179
4181
|
readonly canaryUrl: "https://epgtrack.com/";
|
|
4180
|
-
readonly trackingUrlTemplate: "https://epgtrack.com/";
|
|
4182
|
+
readonly trackingUrlTemplate: "https://epgtrack.com/{trackingNumber}";
|
|
4181
4183
|
readonly trackingSearchUrl: "https://epgtrack.com/";
|
|
4182
|
-
readonly linkRules: readonly [
|
|
4184
|
+
readonly linkRules: readonly [{
|
|
4185
|
+
readonly domains: readonly ["epgtrack.com"];
|
|
4186
|
+
readonly path: "^/(EPG\\d{10,15})/?$";
|
|
4187
|
+
}, {
|
|
4188
|
+
readonly domains: readonly ["epgtrack.com"];
|
|
4189
|
+
readonly pathPattern: "^/$";
|
|
4190
|
+
readonly params: readonly ["id"];
|
|
4191
|
+
}];
|
|
4183
4192
|
readonly detectionRules: readonly [{
|
|
4184
|
-
readonly pattern: "^EPG\\d{10,
|
|
4193
|
+
readonly pattern: "^EPG\\d{10,15}$";
|
|
4185
4194
|
readonly confidence: "high";
|
|
4186
4195
|
}];
|
|
4187
4196
|
};
|
|
@@ -6142,15 +6142,33 @@ export const CARRIER_CATALOG = {
|
|
|
6142
6142
|
"timezone": "America/Los_Angeles",
|
|
6143
6143
|
"tracking": {
|
|
6144
6144
|
"mode": "automatic",
|
|
6145
|
-
"adapter": "
|
|
6145
|
+
"adapter": "epost-global",
|
|
6146
|
+
"recognitionRank": 65,
|
|
6147
|
+
"localClocks": true
|
|
6146
6148
|
},
|
|
6147
6149
|
"canaryUrl": "https://epgtrack.com/",
|
|
6148
|
-
"trackingUrlTemplate": "https://epgtrack.com/",
|
|
6150
|
+
"trackingUrlTemplate": "https://epgtrack.com/{trackingNumber}",
|
|
6149
6151
|
"trackingSearchUrl": "https://epgtrack.com/",
|
|
6150
|
-
"linkRules": [
|
|
6152
|
+
"linkRules": [
|
|
6153
|
+
{
|
|
6154
|
+
"domains": [
|
|
6155
|
+
"epgtrack.com"
|
|
6156
|
+
],
|
|
6157
|
+
"path": "^/(EPG\\d{10,15})/?$"
|
|
6158
|
+
},
|
|
6159
|
+
{
|
|
6160
|
+
"domains": [
|
|
6161
|
+
"epgtrack.com"
|
|
6162
|
+
],
|
|
6163
|
+
"pathPattern": "^/$",
|
|
6164
|
+
"params": [
|
|
6165
|
+
"id"
|
|
6166
|
+
]
|
|
6167
|
+
}
|
|
6168
|
+
],
|
|
6151
6169
|
"detectionRules": [
|
|
6152
6170
|
{
|
|
6153
|
-
"pattern": "^EPG\\d{10,
|
|
6171
|
+
"pattern": "^EPG\\d{10,15}$",
|
|
6154
6172
|
"confidence": "high"
|
|
6155
6173
|
}
|
|
6156
6174
|
]
|
|
@@ -64,6 +64,7 @@ import { adapter as elta } from '../carriers/elta/adapter.js';
|
|
|
64
64
|
import { adapter as emile } from '../carriers/emile/adapter.js';
|
|
65
65
|
import { adapter as ems } from '../carriers/ems/adapter.js';
|
|
66
66
|
import { adapter as envialia } from '../carriers/envialia/adapter.js';
|
|
67
|
+
import { adapter as epostGlobal } from '../carriers/epost-global/adapter.js';
|
|
67
68
|
import { adapter as estafeta } from '../carriers/estafeta/adapter.js';
|
|
68
69
|
import { adapter as evri } from '../carriers/evri/adapter.js';
|
|
69
70
|
import { adapter as evriUk } from '../carriers/evri-uk/adapter.js';
|
|
@@ -246,6 +247,7 @@ export const REGISTRY = {
|
|
|
246
247
|
"emile": emile,
|
|
247
248
|
"ems": ems,
|
|
248
249
|
"envialia": envialia,
|
|
250
|
+
"epost-global": epostGlobal,
|
|
249
251
|
"estafeta": estafeta,
|
|
250
252
|
"evri": evri,
|
|
251
253
|
"evri-uk": evriUk,
|
|
@@ -435,7 +437,7 @@ export const REGISTRY = {
|
|
|
435
437
|
"emile": "emile",
|
|
436
438
|
"ems": "ems",
|
|
437
439
|
"envialia": "envialia",
|
|
438
|
-
"epost-global": "
|
|
440
|
+
"epost-global": "epost-global",
|
|
439
441
|
"estafeta": "estafeta",
|
|
440
442
|
"estes": "universal",
|
|
441
443
|
"evri": "evri",
|
package/dist/server/openapi.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "universal-parcel-scraper",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.122.0-main.1072",
|
|
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",
|