universal-parcel-scraper 0.734.2-main.3759 → 0.736.0-main.3771
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/data/catalog.json +21 -1
- package/data/detection-golden.json +51 -0
- package/dist/browser/scraper.js +41 -15
- package/dist/carriers/fedex/adapter.d.ts +2 -2
- package/dist/carriers/fedex/adapter.js +55 -5
- package/dist/carriers/fedex/identity.js +5 -1
- package/dist/carriers/foxpost/carrier.json +6 -0
- package/dist/carriers/foxpost/numbers.json +20 -0
- package/dist/carriers/foxpost/parser.d.ts +1 -1
- package/dist/carriers/foxpost/parser.js +6 -5
- package/dist/carriers/hermes-de/adapter.js +10 -7
- package/dist/carriers/magyar-posta/carrier.json +12 -0
- package/dist/carriers/magyar-posta/numbers.json +44 -0
- package/dist/carriers/z-leading/carrier.json +7 -1
- package/dist/carriers/z-leading/numbers.json +17 -1
- package/dist/core/adapter/track.js +2 -1
- package/dist/core/catalog/hints.js +3 -2
- package/dist/data/catalog.json +21 -1
- package/dist/generated/catalog.d.ts +16 -1
- package/dist/generated/catalog.js +24 -4
- package/dist/places/index.d.ts +6 -0
- package/dist/places/index.js +65 -1
- package/dist/server/openapi.json +1 -1
- package/package.json +1 -1
package/data/catalog.json
CHANGED
|
@@ -14902,6 +14902,11 @@
|
|
|
14902
14902
|
{
|
|
14903
14903
|
"pattern": "^CLFOX\\d{15}$",
|
|
14904
14904
|
"confidence": "high"
|
|
14905
|
+
},
|
|
14906
|
+
{
|
|
14907
|
+
"pattern": "^66639\\d{5,15}$",
|
|
14908
|
+
"confidence": "low",
|
|
14909
|
+
"preferred": true
|
|
14905
14910
|
}
|
|
14906
14911
|
]
|
|
14907
14912
|
},
|
|
@@ -20625,6 +20630,16 @@
|
|
|
20625
20630
|
"confidence": "low",
|
|
20626
20631
|
"checksum": "magyar-posta",
|
|
20627
20632
|
"preferred": true
|
|
20633
|
+
},
|
|
20634
|
+
{
|
|
20635
|
+
"pattern": "^RL\\d{14}$",
|
|
20636
|
+
"confidence": "low",
|
|
20637
|
+
"preferred": true
|
|
20638
|
+
},
|
|
20639
|
+
{
|
|
20640
|
+
"pattern": "^JJH[A-Z0-9]{17}$",
|
|
20641
|
+
"confidence": "low",
|
|
20642
|
+
"preferred": true
|
|
20628
20643
|
}
|
|
20629
20644
|
]
|
|
20630
20645
|
},
|
|
@@ -32531,7 +32546,12 @@
|
|
|
32531
32546
|
},
|
|
32532
32547
|
"canaryUrl": "https://t.17track.net/",
|
|
32533
32548
|
"linkRules": [],
|
|
32534
|
-
"detectionRules": [
|
|
32549
|
+
"detectionRules": [
|
|
32550
|
+
{
|
|
32551
|
+
"pattern": "^ZL[A-Z]{2}\\d{9}$",
|
|
32552
|
+
"confidence": "low"
|
|
32553
|
+
}
|
|
32554
|
+
]
|
|
32535
32555
|
},
|
|
32536
32556
|
"zajil": {
|
|
32537
32557
|
"displayName": "Zajil Express",
|
|
@@ -50473,6 +50473,27 @@
|
|
|
50473
50473
|
"heppner"
|
|
50474
50474
|
]
|
|
50475
50475
|
},
|
|
50476
|
+
{
|
|
50477
|
+
"input": "6663900000001",
|
|
50478
|
+
"carrier": "unknown",
|
|
50479
|
+
"confidence": "low",
|
|
50480
|
+
"candidates": [
|
|
50481
|
+
"foxpost",
|
|
50482
|
+
"la-poste",
|
|
50483
|
+
"correos-chile",
|
|
50484
|
+
"delhivery",
|
|
50485
|
+
"yunda",
|
|
50486
|
+
"korea-post"
|
|
50487
|
+
],
|
|
50488
|
+
"preferred": [
|
|
50489
|
+
"foxpost"
|
|
50490
|
+
],
|
|
50491
|
+
"asked": [
|
|
50492
|
+
"foxpost",
|
|
50493
|
+
"la-poste",
|
|
50494
|
+
"delhivery"
|
|
50495
|
+
]
|
|
50496
|
+
},
|
|
50476
50497
|
{
|
|
50477
50498
|
"input": "6679804342",
|
|
50478
50499
|
"carrier": "unknown",
|
|
@@ -78103,6 +78124,17 @@
|
|
|
78103
78124
|
"posti"
|
|
78104
78125
|
]
|
|
78105
78126
|
},
|
|
78127
|
+
{
|
|
78128
|
+
"input": "JJH30AAAAAPL00000001",
|
|
78129
|
+
"carrier": "unknown",
|
|
78130
|
+
"confidence": "low",
|
|
78131
|
+
"candidates": [
|
|
78132
|
+
"magyar-posta"
|
|
78133
|
+
],
|
|
78134
|
+
"preferred": [
|
|
78135
|
+
"magyar-posta"
|
|
78136
|
+
]
|
|
78137
|
+
},
|
|
78106
78138
|
{
|
|
78107
78139
|
"input": "JJITA8UW1A2B345678",
|
|
78108
78140
|
"carrier": "poste-italiane",
|
|
@@ -87140,6 +87172,17 @@
|
|
|
87140
87172
|
"chronopost"
|
|
87141
87173
|
]
|
|
87142
87174
|
},
|
|
87175
|
+
{
|
|
87176
|
+
"input": "RL15050014358643",
|
|
87177
|
+
"carrier": "unknown",
|
|
87178
|
+
"confidence": "low",
|
|
87179
|
+
"candidates": [
|
|
87180
|
+
"magyar-posta"
|
|
87181
|
+
],
|
|
87182
|
+
"preferred": [
|
|
87183
|
+
"magyar-posta"
|
|
87184
|
+
]
|
|
87185
|
+
},
|
|
87143
87186
|
{
|
|
87144
87187
|
"input": "RL330279383MD",
|
|
87145
87188
|
"carrier": "intl-post",
|
|
@@ -94754,6 +94797,14 @@
|
|
|
94754
94797
|
"tnt-it"
|
|
94755
94798
|
]
|
|
94756
94799
|
},
|
|
94800
|
+
{
|
|
94801
|
+
"input": "ZLXX000000017",
|
|
94802
|
+
"carrier": "unknown",
|
|
94803
|
+
"confidence": "low",
|
|
94804
|
+
"candidates": [
|
|
94805
|
+
"z-leading"
|
|
94806
|
+
]
|
|
94807
|
+
},
|
|
94757
94808
|
{
|
|
94758
94809
|
"input": "ZND170922989053",
|
|
94759
94810
|
"carrier": "unknown",
|
package/dist/browser/scraper.js
CHANGED
|
@@ -11556,6 +11556,10 @@ var Ze = {
|
|
|
11556
11556
|
detectionRules: [{
|
|
11557
11557
|
pattern: "^CLFOX\\d{15}$",
|
|
11558
11558
|
confidence: "high"
|
|
11559
|
+
}, {
|
|
11560
|
+
pattern: "^66639\\d{5,15}$",
|
|
11561
|
+
confidence: "low",
|
|
11562
|
+
preferred: !0
|
|
11559
11563
|
}]
|
|
11560
11564
|
},
|
|
11561
11565
|
"franch-express": {
|
|
@@ -15788,17 +15792,30 @@ var Ze = {
|
|
|
15788
15792
|
pathPattern: "^/nyomkovetes/nyitooldal/?$",
|
|
15789
15793
|
params: ["searchvalue"]
|
|
15790
15794
|
}],
|
|
15791
|
-
detectionRules: [
|
|
15792
|
-
|
|
15793
|
-
|
|
15794
|
-
|
|
15795
|
-
|
|
15796
|
-
|
|
15797
|
-
|
|
15798
|
-
|
|
15799
|
-
|
|
15800
|
-
|
|
15801
|
-
|
|
15795
|
+
detectionRules: [
|
|
15796
|
+
{
|
|
15797
|
+
pattern: "^[A-Z]{2}\\d{9}HU$",
|
|
15798
|
+
confidence: "low",
|
|
15799
|
+
checksum: "s10",
|
|
15800
|
+
preferred: !0
|
|
15801
|
+
},
|
|
15802
|
+
{
|
|
15803
|
+
pattern: "^P[A-Z][0-9A-Z]{3}\\d{8}$",
|
|
15804
|
+
confidence: "low",
|
|
15805
|
+
checksum: "magyar-posta",
|
|
15806
|
+
preferred: !0
|
|
15807
|
+
},
|
|
15808
|
+
{
|
|
15809
|
+
pattern: "^RL\\d{14}$",
|
|
15810
|
+
confidence: "low",
|
|
15811
|
+
preferred: !0
|
|
15812
|
+
},
|
|
15813
|
+
{
|
|
15814
|
+
pattern: "^JJH[A-Z0-9]{17}$",
|
|
15815
|
+
confidence: "low",
|
|
15816
|
+
preferred: !0
|
|
15817
|
+
}
|
|
15818
|
+
]
|
|
15802
15819
|
},
|
|
15803
15820
|
mailamericas: {
|
|
15804
15821
|
displayName: "MailAmericas",
|
|
@@ -24568,7 +24585,10 @@ var Ze = {
|
|
|
24568
24585
|
},
|
|
24569
24586
|
canaryUrl: "https://t.17track.net/",
|
|
24570
24587
|
linkRules: [],
|
|
24571
|
-
detectionRules: [
|
|
24588
|
+
detectionRules: [{
|
|
24589
|
+
pattern: "^ZL[A-Z]{2}\\d{9}$",
|
|
24590
|
+
confidence: "low"
|
|
24591
|
+
}]
|
|
24572
24592
|
},
|
|
24573
24593
|
zajil: {
|
|
24574
24594
|
displayName: "Zajil Express",
|
|
@@ -25446,7 +25466,7 @@ var Ze = {
|
|
|
25446
25466
|
followmont: [],
|
|
25447
25467
|
"forward-air": [],
|
|
25448
25468
|
"forza-delivery": ["forza-delivery-1"],
|
|
25449
|
-
foxpost: ["foxpost-1"],
|
|
25469
|
+
foxpost: ["foxpost-1", "foxpost-home-delivery"],
|
|
25450
25470
|
"franch-express": [],
|
|
25451
25471
|
fukuyama: [],
|
|
25452
25472
|
gaash: [],
|
|
@@ -25608,7 +25628,12 @@ var Ze = {
|
|
|
25608
25628
|
"m-one": ["m-one-s10"],
|
|
25609
25629
|
"madhur-courier": [],
|
|
25610
25630
|
"maersk-ecommerce": ["maersk-ecommerce-1"],
|
|
25611
|
-
"magyar-posta": [
|
|
25631
|
+
"magyar-posta": [
|
|
25632
|
+
"magyar-posta-s10",
|
|
25633
|
+
"magyar-posta-domestic",
|
|
25634
|
+
"magyar-posta-letter",
|
|
25635
|
+
"magyar-posta-europe-standard"
|
|
25636
|
+
],
|
|
25612
25637
|
mailamericas: [
|
|
25613
25638
|
"mailamericas-ml",
|
|
25614
25639
|
"mailamericas-mmxqp",
|
|
@@ -25939,7 +25964,7 @@ var Ze = {
|
|
|
25939
25964
|
"yurtici-kargo": [],
|
|
25940
25965
|
ywe: ["ywe-1"],
|
|
25941
25966
|
yypost: ["yypost-1"],
|
|
25942
|
-
"z-leading": [],
|
|
25967
|
+
"z-leading": ["z-leading-1"],
|
|
25943
25968
|
zajil: [],
|
|
25944
25969
|
zeleris: ["zeleris-1"],
|
|
25945
25970
|
zheldor: [],
|
|
@@ -31286,6 +31311,7 @@ var Rl = /* @__PURE__ */ new Map([
|
|
|
31286
31311
|
["DE", "dhl"],
|
|
31287
31312
|
["DK", "postnord"],
|
|
31288
31313
|
["DZ", "algerie-poste"],
|
|
31314
|
+
["EE", "omniva"],
|
|
31289
31315
|
["EG", "egypt-post"],
|
|
31290
31316
|
["ES", "correos-spain"],
|
|
31291
31317
|
["FI", "posti"],
|
|
@@ -6,13 +6,13 @@ import { type FedExApiCredentials } from './api.js';
|
|
|
6
6
|
export declare function normalizeFedExTrackingNumber(raw: string): string;
|
|
7
7
|
export declare function fedexTrackingUrl(trackingNumber: string): string;
|
|
8
8
|
/** The structured answer of the tracking page's own API call. */
|
|
9
|
-
export declare function parseFedExTrackingResponse(payload: unknown, trackingNumber: string): CarrierResult;
|
|
9
|
+
export declare function parseFedExTrackingResponse(payload: unknown, trackingNumber: string, readAt?: number): CarrierResult;
|
|
10
10
|
/**
|
|
11
11
|
* The official Track API's answer (`POST /track/v1/trackingnumbers`). It reads
|
|
12
12
|
* the scans, the latest status, the delivery dates and the hold location; the
|
|
13
13
|
* recipient, shipper, signatory, package and service blocks are never read.
|
|
14
14
|
*/
|
|
15
|
-
export declare function parseFedExApiResponse(payload: unknown, trackingNumber: string): CarrierResult;
|
|
15
|
+
export declare function parseFedExApiResponse(payload: unknown, trackingNumber: string, readAt?: number): CarrierResult;
|
|
16
16
|
/**
|
|
17
17
|
* The page the browser rendered, read only to tell a bot challenge from an
|
|
18
18
|
* inconclusive load. The page renders its "can't find that tracking number"
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { load } from 'cheerio';
|
|
2
|
+
import { DateTime } from 'luxon';
|
|
2
3
|
import { recognizeFromBrowserLookup } from '../../core/adapter/index.js';
|
|
3
4
|
import { fillPartialAnswer } from '../../core/result/partial.js';
|
|
4
5
|
import { fedexBarcodeTrackingNumber, fedexGroundEconomyUspsNumber } from '../../core/detection/fedex.js';
|
|
@@ -6,7 +7,7 @@ import { uspsPackageIdentifier } from '../../core/detection/usps.js';
|
|
|
6
7
|
import { ChallengeError, InputRequiredError, InvalidInputError, RateLimitedError, SchemaError, TransportError, UpstreamHttpError, } from '../../core/errors/index.js';
|
|
7
8
|
import { runSteps } from '../../core/runner/index.js';
|
|
8
9
|
import { NOOP_RECORDER } from '../../core/telemetry/index.js';
|
|
9
|
-
import { calendarDay, explicitOffsetTime } from '../../core/time/index.js';
|
|
10
|
+
import { calendarDay, canadaProvinceTimeZone, countryTimeZone, explicitOffsetTime, firstOccurrence, usStateTimeZone, } from '../../core/time/index.js';
|
|
10
11
|
import { clean, cleanScalar, TRAWL_TRANSPORT_ALLOWANCE_MS } from '../../core/transport/index.js';
|
|
11
12
|
import { isRecord } from '../../core/types.js';
|
|
12
13
|
import { FedExApiClient, fedexApiCredentials } from './api.js';
|
|
@@ -85,6 +86,55 @@ function scanClock(scan) {
|
|
|
85
86
|
? calendarDay(Number(day[1]), Number(day[2]), Number(day[3])) : null;
|
|
86
87
|
return local ? { local_time: local } : {};
|
|
87
88
|
}
|
|
89
|
+
// US states and Canadian provinces with places on more than one clock, and the
|
|
90
|
+
// countries whose islands keep another clock than the mainland zone they are given.
|
|
91
|
+
const SEVERAL_CLOCKS = new Set(['AK', 'FL', 'ID', 'IN', 'KS', 'KY', 'MI', 'ND', 'NE', 'NV', 'OR', 'SD', 'TN', 'TX',
|
|
92
|
+
'BC', 'NL', 'NU', 'ON', 'QC', 'ES', 'PT']);
|
|
93
|
+
/** The zone of a place that keeps one clock: its country's, or its US state's or Canadian province's. */
|
|
94
|
+
function placeZone(country, region) {
|
|
95
|
+
if (country === 'US')
|
|
96
|
+
return SEVERAL_CLOCKS.has(region) ? null : usStateTimeZone(region);
|
|
97
|
+
if (country === 'CA')
|
|
98
|
+
return SEVERAL_CLOCKS.has(region) ? null : canadaProvinceTimeZone(region);
|
|
99
|
+
return SEVERAL_CLOCKS.has(country) ? null : countryTimeZone(country);
|
|
100
|
+
}
|
|
101
|
+
/** The page names a US or Canadian scan's state ("MEMPHIS, TN") and any other scan's country ("BOLOGNA IT"). */
|
|
102
|
+
function pageZone(location) {
|
|
103
|
+
const [, comma, code] = /(,?) ([A-Z]{2})$/.exec(location) ?? [];
|
|
104
|
+
if (!code)
|
|
105
|
+
return null;
|
|
106
|
+
if (comma && usStateTimeZone(code))
|
|
107
|
+
return placeZone('US', code);
|
|
108
|
+
if (comma && canadaProvinceTimeZone(code))
|
|
109
|
+
return placeZone('CA', code);
|
|
110
|
+
return placeZone(code, '');
|
|
111
|
+
}
|
|
112
|
+
function apiZone(value) {
|
|
113
|
+
if (!isRecord(value))
|
|
114
|
+
return null;
|
|
115
|
+
return placeZone(cleanScalar(value.countryCode).toUpperCase(), cleanScalar(value.stateOrProvinceCode).toUpperCase());
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* FedEx labels a scan it records after a clock change with the offset its
|
|
119
|
+
* place has kept since, even when the scan came before: a hub's departure the
|
|
120
|
+
* evening before October's change came as +01:00 among scans it had recorded
|
|
121
|
+
* in time as +02:00. A label that is the place's offset at `readAt` but not at
|
|
122
|
+
* the scan's wall clock is put back on the place's clock. A wall clock the
|
|
123
|
+
* place repeats or skips keeps its label.
|
|
124
|
+
*/
|
|
125
|
+
function placeClock(clock, zone, readAt) {
|
|
126
|
+
const shown = clock.time && zone ? DateTime.fromISO(clock.time, { setZone: true }) : null;
|
|
127
|
+
if (!zone || !shown?.isValid || DateTime.fromMillis(readAt, { zone }).offset !== shown.offset)
|
|
128
|
+
return clock;
|
|
129
|
+
const { year, month, day, hour, minute, second } = shown;
|
|
130
|
+
const own = firstOccurrence(DateTime.fromObject({ year, month, day, hour, minute, second }, { zone }));
|
|
131
|
+
if (!own.isValid || own.hour !== hour || own.minute !== minute)
|
|
132
|
+
return clock;
|
|
133
|
+
if (own.getPossibleOffsets().some((possible) => possible.offset === shown.offset))
|
|
134
|
+
return clock;
|
|
135
|
+
const time = own.toISO({ suppressMilliseconds: true });
|
|
136
|
+
return time ? { ...clock, time } : clock;
|
|
137
|
+
}
|
|
88
138
|
/**
|
|
89
139
|
* Newest first. A scan with a time sorts by its instant, since each carries
|
|
90
140
|
* its own offset, and one without a clock comes last. A scan dated by its day
|
|
@@ -259,7 +309,7 @@ function shipmentResult(shipment, number) {
|
|
|
259
309
|
};
|
|
260
310
|
}
|
|
261
311
|
/** The structured answer of the tracking page's own API call. */
|
|
262
|
-
export function parseFedExTrackingResponse(payload, trackingNumber) {
|
|
312
|
+
export function parseFedExTrackingResponse(payload, trackingNumber, readAt = Date.now()) {
|
|
263
313
|
const number = normalizeFedExTrackingNumber(trackingNumber);
|
|
264
314
|
if (!isRecord(payload) || !isRecord(payload.output)) {
|
|
265
315
|
throw new SchemaError('FedEx', 'FedEx returned an invalid tracking response');
|
|
@@ -303,7 +353,7 @@ export function parseFedExTrackingResponse(payload, trackingNumber) {
|
|
|
303
353
|
description: scanDescription(clean(raw.status), clean(raw.scanDetails)),
|
|
304
354
|
wording: `${clean(raw.status)} ${clean(raw.scanDetails)}`,
|
|
305
355
|
code: cleanScalar(raw.statusCD).toLocaleUpperCase('en-US'),
|
|
306
|
-
clock: scanClock(raw),
|
|
356
|
+
clock: placeClock(scanClock(raw), pageZone(clean(raw.scanLocation)), readAt),
|
|
307
357
|
location: clean(raw.scanLocation),
|
|
308
358
|
})),
|
|
309
359
|
statusText: keyStatus,
|
|
@@ -329,7 +379,7 @@ function apiDate(result, ...types) {
|
|
|
329
379
|
* the scans, the latest status, the delivery dates and the hold location; the
|
|
330
380
|
* recipient, shipper, signatory, package and service blocks are never read.
|
|
331
381
|
*/
|
|
332
|
-
export function parseFedExApiResponse(payload, trackingNumber) {
|
|
382
|
+
export function parseFedExApiResponse(payload, trackingNumber, readAt = Date.now()) {
|
|
333
383
|
const number = normalizeFedExTrackingNumber(trackingNumber);
|
|
334
384
|
const complete = isRecord(payload) && isRecord(payload.output) && Array.isArray(payload.output.completeTrackResults)
|
|
335
385
|
? payload.output.completeTrackResults.filter(isRecord) : null;
|
|
@@ -369,7 +419,7 @@ export function parseFedExApiResponse(payload, trackingNumber) {
|
|
|
369
419
|
description: scanDescription(clean(raw.eventDescription), clean(raw.exceptionDescription)),
|
|
370
420
|
wording: `${clean(raw.eventDescription)} ${clean(raw.exceptionDescription)}`,
|
|
371
421
|
code: cleanScalar(raw.eventType).toLocaleUpperCase('en-US'),
|
|
372
|
-
clock: apiClock(raw.date),
|
|
422
|
+
clock: placeClock(apiClock(raw.date), apiZone(raw.scanLocation), readAt),
|
|
373
423
|
location: apiPlace(raw.scanLocation),
|
|
374
424
|
})),
|
|
375
425
|
statusText,
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
// The official API and the tracking page's own reply can word or place one
|
|
2
2
|
// scan differently. A scan read from one takes over the row the other stored
|
|
3
|
-
// at its instant when both read it as the same step.
|
|
3
|
+
// at its instant when both read it as the same step. FedEx labels a scan it
|
|
4
|
+
// records after a clock change with the offset its place took then, which an
|
|
5
|
+
// earlier release stored, so a scan read on its place's own clock takes over
|
|
6
|
+
// the row that shows its wall clock under the other offset.
|
|
4
7
|
export const sameInstantIdentityPolicy = {
|
|
5
8
|
sourceCarrierId: 'fedex', storedSources: ['fedex'], requireProviderCode: false,
|
|
6
9
|
matches: (incoming, stored) => incoming.stage === stored.stage,
|
|
10
|
+
matchWallClock: true,
|
|
7
11
|
};
|
|
@@ -228,6 +228,26 @@
|
|
|
228
228
|
"confidence": "high"
|
|
229
229
|
},
|
|
230
230
|
"note": "Looked up on Foxpost's own tracker, as an archived address shows; the capture was not opened."
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
"number": "6663900000001",
|
|
234
|
+
"role": "shipment",
|
|
235
|
+
"evidence": "synthetic",
|
|
236
|
+
"derivedFrom": "official_shape",
|
|
237
|
+
"shapeConfirmed": "2026-10-25",
|
|
238
|
+
"expect": {
|
|
239
|
+
"carrier": "unknown",
|
|
240
|
+
"confidence": "low",
|
|
241
|
+
"candidates": [
|
|
242
|
+
"foxpost",
|
|
243
|
+
"la-poste",
|
|
244
|
+
"correos-chile",
|
|
245
|
+
"delhivery",
|
|
246
|
+
"yunda",
|
|
247
|
+
"korea-post"
|
|
248
|
+
]
|
|
249
|
+
},
|
|
250
|
+
"note": "Made-up number with the prefix Foxpost's tracking page and help give its home deliveries. No source gives their length; Foxpost's API caps a parcel's barcode at twenty characters."
|
|
231
251
|
}
|
|
232
252
|
]
|
|
233
253
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { CarrierResult } from '../../core/result/index.js';
|
|
2
2
|
export declare const PROVIDER = "Foxpost";
|
|
3
|
-
/** A CLFOX parcel ID, a Z number
|
|
3
|
+
/** A CLFOX parcel ID, a Z number, a ten-digit number starting 66 or a home delivery's 66639 number, as Foxpost's tracker takes them. */
|
|
4
4
|
export declare function normalizeFoxpostNumber(raw: string): string;
|
|
5
5
|
/**
|
|
6
6
|
* The tracker's reply for one parcel: its scans and the locker it waits at.
|
|
@@ -9,9 +9,10 @@ import { firstOccurrence } from '../../core/time/index.js';
|
|
|
9
9
|
import { clean } from '../../core/transport/index.js';
|
|
10
10
|
import { foxpostScanStage } from './status.js';
|
|
11
11
|
export const PROVIDER = 'Foxpost';
|
|
12
|
-
// A Foxpost parcel ID, a Packeta number the tracker also answers for,
|
|
13
|
-
// ten-digit IDs starting 66 it issues as well
|
|
14
|
-
|
|
12
|
+
// A Foxpost parcel ID, a Packeta number the tracker also answers for, the
|
|
13
|
+
// ten-digit IDs starting 66 it issues as well, and its home deliveries' numbers
|
|
14
|
+
// starting 66639, of up to the twenty characters its API allows a barcode.
|
|
15
|
+
const NUMBER = /^(?:CLFOX\d{15}|Z\d{10}|66\d{8}|66639\d{5,15})$/;
|
|
15
16
|
// The page's answer for a number it does not know.
|
|
16
17
|
const ABSENT = /^A megadott csomag azonosító nem található a rendszerünkben\b/;
|
|
17
18
|
const CLOCK = /^(\d{4})\.(\d{2})\.(\d{2}) (\d{2}):(\d{2})$/;
|
|
@@ -20,11 +21,11 @@ const POINT_PAGE = /^\/~\/[A-Za-z0-9_-]{1,40}$/;
|
|
|
20
21
|
const ZONE = 'Europe/Budapest';
|
|
21
22
|
const MAX_ROWS = 200;
|
|
22
23
|
const MAX_EVENTS = 100;
|
|
23
|
-
/** A CLFOX parcel ID, a Z number
|
|
24
|
+
/** A CLFOX parcel ID, a Z number, a ten-digit number starting 66 or a home delivery's 66639 number, as Foxpost's tracker takes them. */
|
|
24
25
|
export function normalizeFoxpostNumber(raw) {
|
|
25
26
|
const number = normalizeTrackingNumber(raw);
|
|
26
27
|
if (!NUMBER.test(number)) {
|
|
27
|
-
throw new InvalidInputError(PROVIDER, 'Foxpost tracks CLFOX parcel IDs, Z numbers
|
|
28
|
+
throw new InvalidInputError(PROVIDER, 'Foxpost tracks CLFOX parcel IDs, Z numbers, ten-digit numbers starting 66 and home deliveries\' numbers starting 66639');
|
|
28
29
|
}
|
|
29
30
|
return number;
|
|
30
31
|
}
|
|
@@ -72,27 +72,30 @@ const DEPOT_PLACES = [
|
|
|
72
72
|
];
|
|
73
73
|
const COUNTRY_PLACES = [
|
|
74
74
|
/\barrived in the destination country (.+?)(?: and is prepared\b|\.?$)/i,
|
|
75
|
-
/\
|
|
75
|
+
/\bfor further shipping \(([^()]+)\)/i,
|
|
76
76
|
];
|
|
77
77
|
// The town after the postcode closing a ParcelShop's address, before the
|
|
78
78
|
// collection deadline the ready-for-pickup wording appends.
|
|
79
79
|
const SHOP_TOWN = /,\s*\d{5}\s+([^\d,]+?)(?:\s+max\.\s.*|\s+abgegeben)?\.?\s*$/;
|
|
80
80
|
/**
|
|
81
81
|
* Where a row's wording puts the parcel. Hermes's own depots and ParcelShops
|
|
82
|
-
* are in Germany
|
|
82
|
+
* are in Germany. A partner's scan (`INTERNATIONAL_…`) names the country after
|
|
83
|
+
* its depot, "prepared in EXEMPLEVILLE for further shipping (France)",
|
|
84
|
+
* or the country alone.
|
|
83
85
|
*/
|
|
84
86
|
function wordedPlace(text, code) {
|
|
85
87
|
const place = (pattern) => clean(pattern.exec(text)?.[1], 120);
|
|
88
|
+
const country = COUNTRY_PLACES.map(place).find(Boolean) ?? '';
|
|
89
|
+
const partner = code.startsWith('INTERNATIONAL_');
|
|
86
90
|
for (const pattern of DEPOT_PLACES) {
|
|
87
91
|
const depot = place(pattern);
|
|
92
|
+
if (depot && partner)
|
|
93
|
+
return country ? `${depot}, ${country}` : depot;
|
|
88
94
|
if (depot)
|
|
89
95
|
return `${depot}, Germany`;
|
|
90
96
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if (country)
|
|
94
|
-
return country;
|
|
95
|
-
}
|
|
97
|
+
if (country)
|
|
98
|
+
return country;
|
|
96
99
|
const town = code.startsWith('PARCELSHOP_') ? place(SHOP_TOWN) : '';
|
|
97
100
|
return town ? `${town}, Germany` : null;
|
|
98
101
|
}
|
|
@@ -106,6 +106,18 @@
|
|
|
106
106
|
"confidence": "low",
|
|
107
107
|
"preferred": true,
|
|
108
108
|
"checksum": "magyar-posta"
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"id": "magyar-posta-letter",
|
|
112
|
+
"pattern": "^RL\\d{14}$",
|
|
113
|
+
"confidence": "low",
|
|
114
|
+
"preferred": true
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"id": "magyar-posta-europe-standard",
|
|
118
|
+
"pattern": "^JJH[A-Z0-9]{17}$",
|
|
119
|
+
"confidence": "low",
|
|
120
|
+
"preferred": true
|
|
109
121
|
}
|
|
110
122
|
]
|
|
111
123
|
}
|
|
@@ -269,6 +269,50 @@
|
|
|
269
269
|
]
|
|
270
270
|
},
|
|
271
271
|
"note": "Magyar Posta's tracking API guide lists it in an example reply; it is not described as a live shipment."
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
"number": "RL15050014358643",
|
|
275
|
+
"role": "shipment",
|
|
276
|
+
"evidence": "public_shipment_report",
|
|
277
|
+
"source": {
|
|
278
|
+
"url": "https://web.archive.org/web/20220727091109/https://www.posta.hu/nyomkovetes/nyitooldal?searchvalue=RL15050014358643",
|
|
279
|
+
"date": "2022-07-27",
|
|
280
|
+
"dateKind": "capture",
|
|
281
|
+
"access": "full_page"
|
|
282
|
+
},
|
|
283
|
+
"context": {
|
|
284
|
+
"service": "domestic registered letter",
|
|
285
|
+
"assessment": "candidate"
|
|
286
|
+
},
|
|
287
|
+
"expect": {
|
|
288
|
+
"carrier": "unknown",
|
|
289
|
+
"confidence": "low",
|
|
290
|
+
"candidates": [
|
|
291
|
+
"magyar-posta"
|
|
292
|
+
]
|
|
293
|
+
},
|
|
294
|
+
"note": "Looked up on Magyar Posta's tracker, as an archived address shows; the capture holds only the search form. The tracker names the RL series as its domestic letters, sixteen characters long."
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
"number": "JJH30AAAAAPL00000001",
|
|
298
|
+
"role": "shipment",
|
|
299
|
+
"evidence": "official_documentation_example",
|
|
300
|
+
"source": {
|
|
301
|
+
"url": "https://www.posta.hu/ikrnyomkovproxy/",
|
|
302
|
+
"access": "full_page"
|
|
303
|
+
},
|
|
304
|
+
"context": {
|
|
305
|
+
"service": "MPL Europe Standard",
|
|
306
|
+
"assessment": "candidate"
|
|
307
|
+
},
|
|
308
|
+
"expect": {
|
|
309
|
+
"carrier": "unknown",
|
|
310
|
+
"confidence": "low",
|
|
311
|
+
"candidates": [
|
|
312
|
+
"magyar-posta"
|
|
313
|
+
]
|
|
314
|
+
},
|
|
315
|
+
"note": "Magyar Posta's tracker gives it as the example of an MPL Europe Standard (DHL) parcel number; it is not described as a live shipment."
|
|
272
316
|
}
|
|
273
317
|
]
|
|
274
318
|
}
|
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"carrier": "z-leading",
|
|
3
3
|
"gap": "No public page shows a whole Z-Leading tracking number.",
|
|
4
|
-
"records": [
|
|
4
|
+
"records": [
|
|
5
|
+
{
|
|
6
|
+
"number": "ZLXX000000017",
|
|
7
|
+
"role": "shipment",
|
|
8
|
+
"evidence": "synthetic",
|
|
9
|
+
"derivedFrom": "donated_real",
|
|
10
|
+
"shapeConfirmed": "2026-10-25",
|
|
11
|
+
"expect": {
|
|
12
|
+
"carrier": "unknown",
|
|
13
|
+
"confidence": "low",
|
|
14
|
+
"candidates": [
|
|
15
|
+
"z-leading"
|
|
16
|
+
]
|
|
17
|
+
},
|
|
18
|
+
"note": "Made-up number in the shape of a parcel the app tracked, which two universal providers filed under Zhongling."
|
|
19
|
+
}
|
|
20
|
+
]
|
|
5
21
|
}
|
|
@@ -4,6 +4,7 @@ import { normalizeCarrierResult } from '../result/index.js';
|
|
|
4
4
|
import { runSteps } from '../runner/index.js';
|
|
5
5
|
import { NOOP_RECORDER } from '../telemetry/index.js';
|
|
6
6
|
import { dateLocalClocks } from '../../places/clocks.js';
|
|
7
|
+
import { restoreLostLetters } from '../../places/index.js';
|
|
7
8
|
/**
|
|
8
9
|
* One carrier lookup, leaving provider selection and scheduling to the consumer.
|
|
9
10
|
* The adapter gets the normalized number, as from `createTracker()`: a number
|
|
@@ -25,7 +26,7 @@ export async function trackCarrier(carrier, typed, options) {
|
|
|
25
26
|
: await runSteps({ carrier, budgetMs: options.budgetMs ?? 120_000, signal: options.signal,
|
|
26
27
|
recorder: options.recorder ?? NOOP_RECORDER }, [{ id: registered.steps[0] ?? 'direct',
|
|
27
28
|
run: ({ signal, remainingMs }) => registered.track(input, ...(options.signal || options.budgetMs !== undefined ? [{ signal, budgetMs: context.budgetMs === undefined ? undefined : Math.min(context.budgetMs, remainingMs) }] : [])) }]);
|
|
28
|
-
return dateLocalClocks(carrier, normalizeCarrierResult(result));
|
|
29
|
+
return dateLocalClocks(carrier, restoreLostLetters(normalizeCarrierResult(result)));
|
|
29
30
|
}
|
|
30
31
|
catch (error) {
|
|
31
32
|
// A caller's cancellation is the caller's reason, not a carrier failure.
|
|
@@ -59,13 +59,14 @@ const CATALOG_NAMES = new Set([
|
|
|
59
59
|
// from France, and dates its own scans on Taiwan time. Pošta Srbije, Pošta
|
|
60
60
|
// Slovenije, Cyprus Post, MaltaPost, POST Luxembourg and Magyar Posta follow mail
|
|
61
61
|
// from abroad under the sending post's number; Magyar Posta's lookup opens a
|
|
62
|
-
// browser.
|
|
62
|
+
// browser. Omniva's tracker carries the scans in Estonia of mail from abroad,
|
|
63
|
+
// on UTC clocks. The host also checks adapter availability and required inputs before
|
|
63
64
|
// making a request.
|
|
64
65
|
const NATIONAL_POSTS = new Map([
|
|
65
66
|
['AM', 'haypost'], ['AR', 'correo-argentino'], ['AT', 'austrian-post'], ['AU', 'australia-post'], ['BD', 'bangladesh-post'],
|
|
66
67
|
['BE', 'bpost'], ['BG', 'bulgarian-posts'], ['BR', 'correios-br'], ['BY', 'belpost'], ['CA', 'canada-post'],
|
|
67
68
|
['CH', 'swiss-post'], ['CU', 'correos-cuba'], ['CY', 'cyprus-post'], ['CZ', 'ceska-posta'], ['DE', 'dhl'], ['DK', 'postnord'], ['DZ', 'algerie-poste'],
|
|
68
|
-
['EG', 'egypt-post'], ['ES', 'correos-spain'], ['FI', 'posti'], ['FO', 'posta-faroe'], ['FR', 'la-poste'],
|
|
69
|
+
['EE', 'omniva'], ['EG', 'egypt-post'], ['ES', 'correos-spain'], ['FI', 'posti'], ['FO', 'posta-faroe'], ['FR', 'la-poste'],
|
|
69
70
|
['GB', 'royal-mail'], ['GR', 'elta'], ['HR', 'hrvatska-posta'], ['HU', 'magyar-posta'], ['IE', 'an-post'], ['IS', 'posturinn'],
|
|
70
71
|
['IT', 'poste-italiane'], ['JO', 'jordan-post'], ['JP', 'japan-post'], ['KE', 'posta-kenya'], ['LT', 'lietuvos-pastas'],
|
|
71
72
|
['LU', 'post-luxembourg'], ['LV', 'latvijas-pasts'], ['MA', 'barid-al-maghrib'], ['MK', 'posta-north-macedonia'], ['MT', 'maltapost'],
|
package/dist/data/catalog.json
CHANGED
|
@@ -14902,6 +14902,11 @@
|
|
|
14902
14902
|
{
|
|
14903
14903
|
"pattern": "^CLFOX\\d{15}$",
|
|
14904
14904
|
"confidence": "high"
|
|
14905
|
+
},
|
|
14906
|
+
{
|
|
14907
|
+
"pattern": "^66639\\d{5,15}$",
|
|
14908
|
+
"confidence": "low",
|
|
14909
|
+
"preferred": true
|
|
14905
14910
|
}
|
|
14906
14911
|
]
|
|
14907
14912
|
},
|
|
@@ -20625,6 +20630,16 @@
|
|
|
20625
20630
|
"confidence": "low",
|
|
20626
20631
|
"checksum": "magyar-posta",
|
|
20627
20632
|
"preferred": true
|
|
20633
|
+
},
|
|
20634
|
+
{
|
|
20635
|
+
"pattern": "^RL\\d{14}$",
|
|
20636
|
+
"confidence": "low",
|
|
20637
|
+
"preferred": true
|
|
20638
|
+
},
|
|
20639
|
+
{
|
|
20640
|
+
"pattern": "^JJH[A-Z0-9]{17}$",
|
|
20641
|
+
"confidence": "low",
|
|
20642
|
+
"preferred": true
|
|
20628
20643
|
}
|
|
20629
20644
|
]
|
|
20630
20645
|
},
|
|
@@ -32531,7 +32546,12 @@
|
|
|
32531
32546
|
},
|
|
32532
32547
|
"canaryUrl": "https://t.17track.net/",
|
|
32533
32548
|
"linkRules": [],
|
|
32534
|
-
"detectionRules": [
|
|
32549
|
+
"detectionRules": [
|
|
32550
|
+
{
|
|
32551
|
+
"pattern": "^ZL[A-Z]{2}\\d{9}$",
|
|
32552
|
+
"confidence": "low"
|
|
32553
|
+
}
|
|
32554
|
+
]
|
|
32535
32555
|
},
|
|
32536
32556
|
"zajil": {
|
|
32537
32557
|
"displayName": "Zajil Express",
|
|
@@ -9664,6 +9664,10 @@ export declare const CARRIER_CATALOG: {
|
|
|
9664
9664
|
readonly detectionRules: readonly [{
|
|
9665
9665
|
readonly pattern: "^CLFOX\\d{15}$";
|
|
9666
9666
|
readonly confidence: "high";
|
|
9667
|
+
}, {
|
|
9668
|
+
readonly pattern: "^66639\\d{5,15}$";
|
|
9669
|
+
readonly confidence: "low";
|
|
9670
|
+
readonly preferred: true;
|
|
9667
9671
|
}];
|
|
9668
9672
|
};
|
|
9669
9673
|
readonly "franch-express": {
|
|
@@ -13288,6 +13292,14 @@ export declare const CARRIER_CATALOG: {
|
|
|
13288
13292
|
readonly confidence: "low";
|
|
13289
13293
|
readonly checksum: "magyar-posta";
|
|
13290
13294
|
readonly preferred: true;
|
|
13295
|
+
}, {
|
|
13296
|
+
readonly pattern: "^RL\\d{14}$";
|
|
13297
|
+
readonly confidence: "low";
|
|
13298
|
+
readonly preferred: true;
|
|
13299
|
+
}, {
|
|
13300
|
+
readonly pattern: "^JJH[A-Z0-9]{17}$";
|
|
13301
|
+
readonly confidence: "low";
|
|
13302
|
+
readonly preferred: true;
|
|
13291
13303
|
}];
|
|
13292
13304
|
};
|
|
13293
13305
|
readonly mailamericas: {
|
|
@@ -20790,7 +20802,10 @@ export declare const CARRIER_CATALOG: {
|
|
|
20790
20802
|
};
|
|
20791
20803
|
readonly canaryUrl: "https://t.17track.net/";
|
|
20792
20804
|
readonly linkRules: readonly [];
|
|
20793
|
-
readonly detectionRules: readonly [
|
|
20805
|
+
readonly detectionRules: readonly [{
|
|
20806
|
+
readonly pattern: "^ZL[A-Z]{2}\\d{9}$";
|
|
20807
|
+
readonly confidence: "low";
|
|
20808
|
+
}];
|
|
20794
20809
|
};
|
|
20795
20810
|
readonly zajil: {
|
|
20796
20811
|
readonly displayName: "Zajil Express";
|
|
@@ -14903,6 +14903,11 @@ export const CARRIER_CATALOG = {
|
|
|
14903
14903
|
{
|
|
14904
14904
|
"pattern": "^CLFOX\\d{15}$",
|
|
14905
14905
|
"confidence": "high"
|
|
14906
|
+
},
|
|
14907
|
+
{
|
|
14908
|
+
"pattern": "^66639\\d{5,15}$",
|
|
14909
|
+
"confidence": "low",
|
|
14910
|
+
"preferred": true
|
|
14906
14911
|
}
|
|
14907
14912
|
]
|
|
14908
14913
|
},
|
|
@@ -20626,6 +20631,16 @@ export const CARRIER_CATALOG = {
|
|
|
20626
20631
|
"confidence": "low",
|
|
20627
20632
|
"checksum": "magyar-posta",
|
|
20628
20633
|
"preferred": true
|
|
20634
|
+
},
|
|
20635
|
+
{
|
|
20636
|
+
"pattern": "^RL\\d{14}$",
|
|
20637
|
+
"confidence": "low",
|
|
20638
|
+
"preferred": true
|
|
20639
|
+
},
|
|
20640
|
+
{
|
|
20641
|
+
"pattern": "^JJH[A-Z0-9]{17}$",
|
|
20642
|
+
"confidence": "low",
|
|
20643
|
+
"preferred": true
|
|
20629
20644
|
}
|
|
20630
20645
|
]
|
|
20631
20646
|
},
|
|
@@ -32532,7 +32547,12 @@ export const CARRIER_CATALOG = {
|
|
|
32532
32547
|
},
|
|
32533
32548
|
"canaryUrl": "https://t.17track.net/",
|
|
32534
32549
|
"linkRules": [],
|
|
32535
|
-
"detectionRules": [
|
|
32550
|
+
"detectionRules": [
|
|
32551
|
+
{
|
|
32552
|
+
"pattern": "^ZL[A-Z]{2}\\d{9}$",
|
|
32553
|
+
"confidence": "low"
|
|
32554
|
+
}
|
|
32555
|
+
]
|
|
32536
32556
|
},
|
|
32537
32557
|
"zajil": {
|
|
32538
32558
|
"displayName": "Zajil Express",
|
|
@@ -33919,7 +33939,7 @@ export const DETECTION_RULE_IDS = {
|
|
|
33919
33939
|
"followmont": [],
|
|
33920
33940
|
"forward-air": [],
|
|
33921
33941
|
"forza-delivery": ["forza-delivery-1"],
|
|
33922
|
-
"foxpost": ["foxpost-1"],
|
|
33942
|
+
"foxpost": ["foxpost-1", "foxpost-home-delivery"],
|
|
33923
33943
|
"franch-express": [],
|
|
33924
33944
|
"fukuyama": [],
|
|
33925
33945
|
"gaash": [],
|
|
@@ -34068,7 +34088,7 @@ export const DETECTION_RULE_IDS = {
|
|
|
34068
34088
|
"m-one": ["m-one-s10"],
|
|
34069
34089
|
"madhur-courier": [],
|
|
34070
34090
|
"maersk-ecommerce": ["maersk-ecommerce-1"],
|
|
34071
|
-
"magyar-posta": ["magyar-posta-s10", "magyar-posta-domestic"],
|
|
34091
|
+
"magyar-posta": ["magyar-posta-s10", "magyar-posta-domestic", "magyar-posta-letter", "magyar-posta-europe-standard"],
|
|
34072
34092
|
"mailamericas": ["mailamericas-ml", "mailamericas-mmxqp", "mailamericas-domc", "mailamericas-s10"],
|
|
34073
34093
|
"mailplus-au": ["mailplus-au-1"],
|
|
34074
34094
|
"mainfreight": ["mainfreight-1"],
|
|
@@ -34369,7 +34389,7 @@ export const DETECTION_RULE_IDS = {
|
|
|
34369
34389
|
"yurtici-kargo": [],
|
|
34370
34390
|
"ywe": ["ywe-1"],
|
|
34371
34391
|
"yypost": ["yypost-1"],
|
|
34372
|
-
"z-leading": [],
|
|
34392
|
+
"z-leading": ["z-leading-1"],
|
|
34373
34393
|
"zajil": [],
|
|
34374
34394
|
"zeleris": ["zeleris-1"],
|
|
34375
34395
|
"zheldor": [],
|
package/dist/places/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { CarrierResult } from '../core/result/index.js';
|
|
1
2
|
export { carrierHomeZone, dateLocalClocks, type ZoneBasis } from './clocks.js';
|
|
2
3
|
/** Where a scan happened, as precisely as its free-text location allows. */
|
|
3
4
|
export interface EventPlace {
|
|
@@ -47,6 +48,11 @@ export declare function placeTimeZone(place: EventPlace): string | null;
|
|
|
47
48
|
* place we can be confident about. A wrong dot is worse than no dot.
|
|
48
49
|
*/
|
|
49
50
|
export declare function locatePlace(location: string | null | undefined, hints?: PlaceHints): EventPlace | null;
|
|
51
|
+
/**
|
|
52
|
+
* The result with the letters its scans' "Town, Country" places lost restored
|
|
53
|
+
* (`restoredTownName`), in the place and wherever the wording names it.
|
|
54
|
+
*/
|
|
55
|
+
export declare function restoreLostLetters(result: CarrierResult): CarrierResult;
|
|
50
56
|
/**
|
|
51
57
|
* Places for a parcel's scans, oldest first. A scan that names no country
|
|
52
58
|
* borrows one from the scans around it, then the destination and the
|
package/dist/places/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { brotliDecompressSync } from 'node:zlib';
|
|
3
3
|
import { CARRIER_DEFINITIONS } from '../core/catalog/definitions.js';
|
|
4
|
-
import { timeZoneCountry } from '../core/time/index.js';
|
|
4
|
+
import { countryCode, timeZoneCountry } from '../core/time/index.js';
|
|
5
5
|
import { ambiguousAddressCodes, militaryPostOffice, trackingPlace } from './trackingLocation.js';
|
|
6
6
|
import facilityList from './facilities.json' with { type: 'json' };
|
|
7
7
|
import hubList from './hubs.json' with { type: 'json' };
|
|
@@ -511,6 +511,70 @@ export function locatePlace(location, hints = {}) {
|
|
|
511
511
|
// Alone, a name two towns of a country share alike says neither.
|
|
512
512
|
return found && !found.twins ? found.place : null;
|
|
513
513
|
}
|
|
514
|
+
const LOST = '\uFFFD';
|
|
515
|
+
/**
|
|
516
|
+
* A town's name with the letters a carrier lost on the way, each a replacement
|
|
517
|
+
* character, filled from the one spelling of a town of `country` that fits:
|
|
518
|
+
* "SAINT-\uFFFDTIENNE" is Saint-Étienne's. A letter inside a word takes the
|
|
519
|
+
* case of the letters beside it, one starting a word the town's own. Null when
|
|
520
|
+
* no letter is lost or no single spelling fits.
|
|
521
|
+
*/
|
|
522
|
+
function restoredTownName(town, country) {
|
|
523
|
+
if (!town.includes(LOST))
|
|
524
|
+
return null;
|
|
525
|
+
const chars = [...town];
|
|
526
|
+
const source = chars.map((char) => char === LOST ? '(\\p{L})' : /[\s-]/u.test(char) ? '[\\s-]'
|
|
527
|
+
: char.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('');
|
|
528
|
+
const pattern = new RegExp(`^${source}$`, 'iu');
|
|
529
|
+
const data = gazetteer();
|
|
530
|
+
const fills = new Set();
|
|
531
|
+
for (const index of countryPlaces(country)) {
|
|
532
|
+
const match = pattern.exec(data.names[index]);
|
|
533
|
+
if (match)
|
|
534
|
+
fills.add(match.slice(1).join(' '));
|
|
535
|
+
}
|
|
536
|
+
if (new Set([...fills].map((fill) => fill.toLowerCase())).size !== 1)
|
|
537
|
+
return null;
|
|
538
|
+
const letters = [...fills][0].split(' ');
|
|
539
|
+
const letter = (char) => char !== undefined && char !== LOST && /\p{L}/u.test(char);
|
|
540
|
+
return chars.map((char, at) => {
|
|
541
|
+
if (char !== LOST)
|
|
542
|
+
return char;
|
|
543
|
+
const filled = letters.shift();
|
|
544
|
+
if (!letter(chars[at - 1]))
|
|
545
|
+
return filled;
|
|
546
|
+
const beside = [chars[at - 1], chars[at + 1]].filter(letter);
|
|
547
|
+
return beside.every((other) => other === other.toUpperCase()) ? filled.toUpperCase() : filled.toLowerCase();
|
|
548
|
+
}).join('');
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* The result with the letters its scans' "Town, Country" places lost restored
|
|
552
|
+
* (`restoredTownName`), in the place and wherever the wording names it.
|
|
553
|
+
*/
|
|
554
|
+
export function restoreLostLetters(result) {
|
|
555
|
+
if (!result.events?.some((event) => event.location?.includes(LOST)))
|
|
556
|
+
return result;
|
|
557
|
+
const restored = new Map();
|
|
558
|
+
for (const event of result.events) {
|
|
559
|
+
const [, town, country] = /^(.+), ([^,]+)$/.exec(event.location ?? '') ?? [];
|
|
560
|
+
const code = country ? countryCode(country) : null;
|
|
561
|
+
const name = town && code && !restored.has(town) ? restoredTownName(town, code) : null;
|
|
562
|
+
if (name)
|
|
563
|
+
restored.set(town, name);
|
|
564
|
+
}
|
|
565
|
+
if (!restored.size)
|
|
566
|
+
return result;
|
|
567
|
+
const spell = (text) => [...restored].reduce((spelled, [lost, name]) => spelled.replaceAll(lost, name), text);
|
|
568
|
+
return {
|
|
569
|
+
...result,
|
|
570
|
+
...(result.last_status_text ? { last_status_text: spell(result.last_status_text) } : {}),
|
|
571
|
+
events: result.events.map((event) => ({
|
|
572
|
+
...event,
|
|
573
|
+
...(event.location ? { location: spell(event.location) } : {}),
|
|
574
|
+
...(event.description ? { description: spell(event.description) } : {}),
|
|
575
|
+
})),
|
|
576
|
+
};
|
|
577
|
+
}
|
|
514
578
|
/** `split` reads a country the text names apart from its town; without it the whole text is the town's. */
|
|
515
579
|
function locate(location, hints, split = true) {
|
|
516
580
|
const text = location?.trim();
|
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.736.0-main.3771",
|
|
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",
|