universal-parcel-scraper 0.3.0-main.306 → 0.3.0
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 +1 -1
- package/dist/app.d.ts +4 -3
- package/dist/app.js +4 -3
- package/dist/carriers/dpd/app.d.ts +2 -0
- package/dist/carriers/dpd/app.js +5 -0
- package/dist/carriers/india-post/adapter.js +24 -3
- package/dist/carriers/india-post/app.d.ts +2 -0
- package/dist/carriers/india-post/app.js +4 -0
- package/dist/carriers/india-post/status.js +2 -0
- package/dist/carriers/india-post/statuses.json +1 -0
- package/dist/core/catalog/eventIdentity.d.ts +8 -0
- package/dist/core/catalog/eventIdentity.js +7 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -149,7 +149,7 @@ createTracker({ providers: ['ParcelsApp', 'Ship24', '17TRACK', 'Postal Ninja', '
|
|
|
149
149
|
<!-- GENERATED:stages -->
|
|
150
150
|
<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">
|
|
151
151
|
|
|
152
|
-
The carrier folders record 1,
|
|
152
|
+
The carrier folders record 1,672 such statuses, each filed under one stage.
|
|
153
153
|
<!-- /GENERATED:stages -->
|
|
154
154
|
|
|
155
155
|
Wording nobody recorded yet goes through a classifier that reads seven European languages.
|
package/dist/app.d.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Helpers shaped for the parcel app this package was extracted from: its
|
|
3
3
|
* parcel view, carrier-name hints for provider results, the clock helpers
|
|
4
|
-
* its sync uses, and the country a scan's
|
|
5
|
-
* that app and carry no stability promise;
|
|
6
|
-
* other entry points.
|
|
4
|
+
* its sync uses, carrier scan-identity policies, and the country a scan's
|
|
5
|
+
* location names. They change with that app and carry no stability promise;
|
|
6
|
+
* the tracking contract is the other entry points.
|
|
7
7
|
*/
|
|
8
8
|
export * from './core/catalog/parcel.js';
|
|
9
9
|
export * from './core/catalog/hints.js';
|
|
10
10
|
export * from './core/time/result.js';
|
|
11
|
+
export { sameInstantIdentityPolicy, type SameInstantIdentityPolicy } from './core/catalog/eventIdentity.js';
|
|
11
12
|
export { universalCarrierHints } from './providers/shared/hints.js';
|
|
12
13
|
export { recognitionAskedCarriers } from './core/catalog/recognition.js';
|
|
13
14
|
export { countryFlag, countryName, trackingLocationCountry, trackingPlace, type TrackingPlace } from './places/trackingLocation.js';
|
package/dist/app.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Helpers shaped for the parcel app this package was extracted from: its
|
|
3
3
|
* parcel view, carrier-name hints for provider results, the clock helpers
|
|
4
|
-
* its sync uses, and the country a scan's
|
|
5
|
-
* that app and carry no stability promise;
|
|
6
|
-
* other entry points.
|
|
4
|
+
* its sync uses, carrier scan-identity policies, and the country a scan's
|
|
5
|
+
* location names. They change with that app and carry no stability promise;
|
|
6
|
+
* the tracking contract is the other entry points.
|
|
7
7
|
*/
|
|
8
8
|
export * from './core/catalog/parcel.js';
|
|
9
9
|
export * from './core/catalog/hints.js';
|
|
10
10
|
export * from './core/time/result.js';
|
|
11
|
+
export { sameInstantIdentityPolicy } from './core/catalog/eventIdentity.js';
|
|
11
12
|
export { universalCarrierHints } from './providers/shared/hints.js';
|
|
12
13
|
export { recognitionAskedCarriers } from './core/catalog/recognition.js';
|
|
13
14
|
export { countryFlag, countryName, trackingLocationCountry, trackingPlace } from './places/trackingLocation.js';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Verified and postcode-free replies reword the same scans at the same instants.
|
|
2
|
+
// A universal provider can also have stored those scans while DPD was unavailable.
|
|
3
|
+
export const sameInstantIdentityPolicy = {
|
|
4
|
+
sourceCarrierId: 'dpd', storedSources: ['dpd', 'unknown'], requireProviderCode: false,
|
|
5
|
+
};
|
|
@@ -109,6 +109,24 @@ const AIRPORT_ZONES = {
|
|
|
109
109
|
LAX: 'America/Los_Angeles', SFO: 'America/Los_Angeles', YYZ: 'America/Toronto',
|
|
110
110
|
SYD: 'Australia/Sydney', MEL: 'Australia/Melbourne',
|
|
111
111
|
};
|
|
112
|
+
// Only verified names are expanded; other airport codes stay as the carrier wrote them.
|
|
113
|
+
const AIRPORT_PLACES = {
|
|
114
|
+
BOM: { name: 'Mumbai Airport', country: 'India' },
|
|
115
|
+
DEL: { name: 'Delhi Airport', country: 'India' },
|
|
116
|
+
FRA: { name: 'Frankfurt Airport', country: 'Germany' },
|
|
117
|
+
CDG: { name: 'Paris Charles de Gaulle Airport', country: 'France' },
|
|
118
|
+
};
|
|
119
|
+
function departureAirport(office) {
|
|
120
|
+
return /^Office - ([A-Z]{3})\b/.exec(office)?.[1];
|
|
121
|
+
}
|
|
122
|
+
/** Flight remarks have a narrow format; unrelated remarks can contain recipient details. */
|
|
123
|
+
function flightDescription(remarks, office) {
|
|
124
|
+
const match = /^Flight No:\s*([A-Z0-9]{2}\d{1,4}[A-Z]?)\s*\(From ([A-Z]{3}) To ([A-Z]{3})\)$/.exec(clean(remarks, 500));
|
|
125
|
+
if (!match || match[2] !== departureAirport(office))
|
|
126
|
+
return 'Aircraft Departure';
|
|
127
|
+
const airport = (code) => AIRPORT_PLACES[code] ? `${AIRPORT_PLACES[code].name} (${code})` : code;
|
|
128
|
+
return `Flight ${match[1]} departed: ${airport(match[2])} → ${airport(match[3])}`;
|
|
129
|
+
}
|
|
112
130
|
/**
|
|
113
131
|
* The zone a take-off row's clock is kept in. Its `tracked_at` is the
|
|
114
132
|
* departure airport's wall clock under a UTC label: a take-off was seen
|
|
@@ -120,7 +138,7 @@ const AIRPORT_ZONES = {
|
|
|
120
138
|
function takeOffZone(providerCode, office, trackedAt) {
|
|
121
139
|
if (providerCode !== TAKE_OFF_CODE || !UTC_LABEL.test(trackedAt))
|
|
122
140
|
return null;
|
|
123
|
-
const airport =
|
|
141
|
+
const airport = departureAirport(office);
|
|
124
142
|
return airport ? AIRPORT_ZONES[airport] ?? null : null;
|
|
125
143
|
}
|
|
126
144
|
/**
|
|
@@ -172,13 +190,16 @@ export function parseIndiaPostTrackingHtml(html, trackingNumber) {
|
|
|
172
190
|
const time = zone
|
|
173
191
|
? mislabeledLocalTime(rawEvent.tracked_at, zone, 100)
|
|
174
192
|
: isoTime(rawEvent.tracked_at, 'Asia/Kolkata', 100);
|
|
175
|
-
const
|
|
193
|
+
const takeOff = providerCode === TAKE_OFF_CODE;
|
|
194
|
+
const description = takeOff ? flightDescription(rawEvent.remarks, office) : eventText(clean(rawEvent.event));
|
|
176
195
|
if (!time || !description)
|
|
177
196
|
return;
|
|
178
197
|
const pincode = /^\d{6}$/.test(clean(rawEvent.pincode, 6))
|
|
179
198
|
? clean(rawEvent.pincode, 6)
|
|
180
199
|
: '';
|
|
181
200
|
const point = officePoint(rawEvent, office);
|
|
201
|
+
const airportCode = takeOff ? departureAirport(office) : undefined;
|
|
202
|
+
const airport = airportCode ? AIRPORT_PLACES[airportCode] : undefined;
|
|
182
203
|
const identity = JSON.stringify([time.iso, description, office, pincode, providerCode]);
|
|
183
204
|
if (seen.has(identity))
|
|
184
205
|
return;
|
|
@@ -190,7 +211,7 @@ export function parseIndiaPostTrackingHtml(html, trackingNumber) {
|
|
|
190
211
|
// deliberately never retained.
|
|
191
212
|
event: {
|
|
192
213
|
time: time.iso,
|
|
193
|
-
location: [office, pincode].filter(Boolean).join(' '),
|
|
214
|
+
location: airport ? `${airport.name} (${airportCode}), ${airport.country}` : [office, pincode].filter(Boolean).join(' '),
|
|
194
215
|
description,
|
|
195
216
|
stage: classified.stage,
|
|
196
217
|
...(providerCode ? { provider_code: providerCode } : {}),
|
|
@@ -11,6 +11,8 @@ function includesAny(value, candidates) {
|
|
|
11
11
|
* `in_transit` stage.
|
|
12
12
|
*/
|
|
13
13
|
export function classifyIndiaPostEvent(...values) {
|
|
14
|
+
if (statusKey(values[0]) === 'aircrafttakeoff')
|
|
15
|
+
return { status: 'in_transit', stage: 'in_transit' };
|
|
14
16
|
const key = values.map(statusKey).filter(Boolean).join(' ');
|
|
15
17
|
if (includesAny(key, [
|
|
16
18
|
'returntosender',
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"carrier": "india-post",
|
|
3
3
|
"entries": [
|
|
4
|
+
{ "code": "AircraftTakeOff", "wording": "Aircraft Departure / UPLIFT", "language": "en", "stage": "in_transit", "confirmedBy": "live", "firstSeen": "2026-10-04", "note": "Departure from the airport named by the office; flight remarks name the route, not a destination arrival." },
|
|
4
5
|
{ "code": "ItemBooked", "wording": "Item Booked", "language": "en", "stage": "accepted", "confirmedBy": "fixture", "firstSeen": "2026-09-01", "note": "Substring key `itembooked`; `articlebooked` and `bookingconfirmed` map the same way." },
|
|
5
6
|
{ "code": "ItemDispatched", "wording": "Item Dispatched", "language": "en", "stage": "in_transit", "confirmedBy": "fixture", "firstSeen": "2026-09-01", "note": "Substring key `itemdispatched`." },
|
|
6
7
|
{ "wording": "Item Bagged", "language": "en", "stage": "in_transit", "confirmedBy": "prior-art", "firstSeen": "2026-09-01", "note": "Substring key `itembagged`." },
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Carrier evidence the parcel app uses when updating a stored scan in place. */
|
|
2
|
+
export interface SameInstantIdentityPolicy {
|
|
3
|
+
readonly sourceCarrierId: string;
|
|
4
|
+
readonly storedSources: readonly string[];
|
|
5
|
+
readonly requireProviderCode: boolean;
|
|
6
|
+
}
|
|
7
|
+
/** Unlisted sources cannot identify a reworded scan by its instant alone. */
|
|
8
|
+
export declare function sameInstantIdentityPolicy(sourceCarrierId: string): SameInstantIdentityPolicy | undefined;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { sameInstantIdentityPolicy as dpd } from '../../carriers/dpd/app.js';
|
|
2
|
+
import { sameInstantIdentityPolicy as indiaPost } from '../../carriers/india-post/app.js';
|
|
3
|
+
const policies = new Map([dpd, indiaPost].map((policy) => [policy.sourceCarrierId, policy]));
|
|
4
|
+
/** Unlisted sources cannot identify a reworded scan by its instant alone. */
|
|
5
|
+
export function sameInstantIdentityPolicy(sourceCarrierId) {
|
|
6
|
+
return policies.get(sourceCarrierId);
|
|
7
|
+
}
|
package/package.json
CHANGED