universal-parcel-scraper 0.1024.0-main.5559 → 0.1024.2-main.5565
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/browser/scraper.js +1 -1
- package/dist/carriers/imile/clock.d.ts +6 -0
- package/dist/carriers/imile/clock.js +22 -0
- package/dist/carriers/imile/parser.js +3 -13
- package/dist/carriers/yanwen/adapter.js +2 -1
- package/dist/carriers/yanwen/app.d.ts +4 -1
- package/dist/carriers/yanwen/app.js +18 -6
- package/dist/carriers/yanwen/status.js +6 -2
- package/dist/carriers/yanwen/statuses.json +22 -0
- package/dist/core/status/language.js +2 -2
- package/dist/providers/parcelsapp/adapter.js +5 -2
- package/dist/providers/ship24/adapter.js +4 -1
- package/dist/server/openapi.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -164,7 +164,7 @@ the command line's `track --source ParcelsApp` does the same.
|
|
|
164
164
|
<!-- GENERATED:stages -->
|
|
165
165
|
<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">
|
|
166
166
|
|
|
167
|
-
The carrier folders record 19,
|
|
167
|
+
The carrier folders record 19,308 such statuses, each filed under one stage.
|
|
168
168
|
<!-- /GENERATED:stages -->
|
|
169
169
|
|
|
170
170
|
Wording nobody recorded yet goes through a classifier that reads seven European languages.
|
package/dist/browser/scraper.js
CHANGED
|
@@ -47000,7 +47000,7 @@ function Au(e) {
|
|
|
47000
47000
|
if (/^acceptance of the (?:package|parcel) at the delivery branch abroad$/.test(t)) return "in_transit";
|
|
47001
47001
|
if (/^acceptance of the (?:package|parcel) at the pick ?up point$/.test(t) || /^(?:(?:the )?item )?(?:arrival|arrived|receive item) at (?:the )?collection point for pick ?up\b/.test(t)) return "ready_for_pickup";
|
|
47002
47002
|
if (/^(?:package|parcel) received (?:&|and) scanned at (?:the )?carrier's domestic warehouse$/.test(t) || /^(?:the )?goods (?:arrive|arrived|have arrived) at (?:the )?receiving point$/.test(t) || /(?<!\b(?:no|nao) )\badmitid[oa]s?\b|\badmision (?:en origen|pais de origen)\b/.test(t) || /^aceite(?:\.|$)|\b(?:envio|objeto|objecto|encomenda|remessa) foi aceite\b/.test(t)) return "accepted";
|
|
47003
|
-
if (/\bcross ?dock(?:ed|ing)?\b|\bready for outbound\b|^(?:(?:item|parcel|package|shipment) )?inbound scan(?:ned|ning)?\b|\b(?:un)?bagg(?:ed|ing)\b|\bmaster (?:air ?way ?bill|awb)(?: (?:number|no))? (?:received|assigned|allocated)\b|\bhand ?over to (?:the |an )?(?:airline|air carrier)\b|^(?:(?:the |your )?(?:item|parcel|package|shipment) )?(?:has )?left (?:the|our) warehouse$|\b(?:flight|leg)\b[^.;]*\b(?:is taking off|took off|has taken off|landed)\b|\bflight (?:take ?off|landing)\b|^(?:initial|final) landing$|\bcargo tally in the supervised warehouse\b|\bprepared for (?:the )?last[- ]mile hand ?over\b/.test(t) || /^(?:the )?noa (?:has been |was )?received$|^noa-notification of arrival\b/.test(t) || /^(?:the |your )?(?:parcel|package|shipment|item) is (?:now )?at (?:the |a )?distribution cent(?:er|re)$/.test(t) || /^l'envoi est dans le reseau de livraison local$/.test(t) || /^there (?:has|have) been (?:a |an )?(?:slight |short |small |unexpected )?delay (?:with|to|in) (?:your |the )?(?:delivery|parcel|package|shipment|order)\.?$/.test(t) || /transit|\bline ?haul\b|en route|on (?:its|the) way|\bfacility\b|airport|\bleaving\b|\bdispatched\b|^delay(?:ed)?$|received by (?:the )?(?:local )?(?:delivery|logistics) company|\btransfer to (?:the |a |an )?(?:local |destination )?(?:acting |delivery |logistics |courier )?(?:company|agent|carrier|partner)\b|received by (?:postnl|uniuni)\b|notification.*sent to the recipient|delivery option requested|instruction de livraison recue|cambio (?:delle )?disposizioni di consegna|expedie (?:depuis|du|de)\b|acheminement|unterwegs|\bin viaggio\b|\bw drodze\b|\bonderweg\b|transport|trasport|arriv|depart|processed|processing completed|processing at (?:the )?(?:origin |destination )?branch\b|\breceipt of (?:the )?(?:package|parcel|shipment) at (?:the )?dpd branch\b|sorted|sorting|\btri\b|\btriee?s?\b|traitement|traite|sortier|bearbeitet|bearbeitung|elaborat|elaborazion|smistat|parcel cent(?:er|re)|delivery cent(?:er|re)|centre.*(?:colis|distribution)|paketzentrum|verteilzentrum|centro.*(?:pacchi|distribuzione)|depot|import scan|scan.*import|importscan|scansione.*import|delivery.*delayed|livraison.*(?:retard|report)|zustellung.*verzogert|consegna.*ritard|transferred|transfere|weitergeleitet|inoltrato|left.*(?:point|center)|quitte|verlassen|raggiunto|close bag|fermeture du sac|sack.*geschlossen|chiusura.*sacco|scanned into|scanned at (?:a |the )?checkpoint|dismantling the board|scanne dans|eingescannt|scansionat|\b(?:un)?loaded\b|^(?:item|parcel|shipment) distributed$|charg.*vehicule de transport|transportfahrzeug.*geladen|caric.*veicolo.*trasporto/.test(t) || /^enviad[oa]$|\bclasificad[oa]s?\b|\bclasificacion\b|\ben ruta\b|\ben camino\b|\bcargad[oa]s? en (?:el )?vehiculo\b|\bllegad[oa]s? (?:a|al|en)\b|\bllegada (?:a|al|en|de)\b|\bha llegado\b|\bllego (?:a|al)\b|\bsalida (?:de|del|desde)\b|\b(?:ha )?salido (?:de|del)\b|\bsalio (?:de|del)\b|\ben destino\b|\b(?:agencia|delegacion|plataforma|oficina|franquicia) (?:de )?destino\b|\b(?:unidad|centro|oficina|delegacion|base|agencia|plataforma) de reparto\b|\bcentro (?:de )?(?:clasificacion|distribucion|tratamiento|logistico|operaciones|operativo)\b|\blectura\b|\bleid[oa]s?\b|\brecepcionad[oa]s?\b|\bprocesad[oa]s?\b|\bexpedid[oa]s?\b|\btriagem\b|\btriad[oa]s?\b|\bem tratamento\b|\btratad[oa]s?\b|\bcentro (?:de )?(?:distribuicao|tratamento|triagem|operacional)\b|\bchegad[ao] (?:a|ao|no|na|em)\b|\bchegou (?:a|ao|no|na|em)\b|\bsaida (?:do|da|de|dos|das)\b|\bsaiu (?:do|da|de|dos|das)\b|\bem rota\b|\ba caminho\b|\bencaminhad[oa]s?\b|\b(?:em )?transferencia\b|\btransferid[oa]s?\b|\bprocessad[oa]s?\b|\brecebid[oa] (?:no|na|em) (?:centro|armazem|unidade|plataforma)\b|\brecibid[oa] en (?:el |la |nuestr[oa] )?(?:almacen|centro|delegacion|plataforma|hub|agencia)\b/.test(t)) return "in_transit";
|
|
47003
|
+
if (/\bcross ?dock(?:ed|ing)?\b|\bready for outbound\b|^(?:(?:item|parcel|package|shipment) )?inbound scan(?:ned|ning)?\b|\b(?:un)?bagg(?:ed|ing)\b|\bmaster (?:air ?way ?bill|awb)(?: (?:number|no))? (?:received|assigned|allocated)\b|\bhand ?over to (?:the |an )?(?:airline|air carrier)\b|^(?:(?:the |your )?(?:item|parcel|package|shipment) )?(?:has )?left (?:the|our) warehouse$|\b(?:flight|leg)\b[^.;]*\b(?:is taking off|took off|has taken off|has left|landed)\b|\bflight (?:take ?off|landing)\b|^(?:initial|final) landing$|\bcargo tally in the supervised warehouse\b|\bprepared for (?:the )?last[- ]mile hand ?over\b/.test(t) || /^(?:the )?noa (?:has been |was )?received$|^noa-notification of arrival\b/.test(t) || /^(?:the |your )?(?:parcel|package|shipment|item) is (?:now )?at (?:the |a )?distribution cent(?:er|re)$/.test(t) || /^l'envoi est dans le reseau de livraison local$/.test(t) || /^there (?:has|have) been (?:a |an )?(?:slight |short |small |unexpected )?delay (?:with|to|in) (?:your |the )?(?:delivery|parcel|package|shipment|order)\.?$/.test(t) || /transit|\bline ?haul\b|en route|on (?:its|the) way|\bfacility\b|airport|\bleaving\b|\bdispatched\b|^delay(?:ed)?$|received by (?:the )?(?:local )?(?:delivery|logistics) company|\btransfer to (?:the |a |an )?(?:local |destination )?(?:acting |delivery |logistics |courier )?(?:company|agent|carrier|partner)\b|received by (?:postnl|uniuni)\b|notification.*sent to the recipient|delivery option requested|instruction de livraison recue|cambio (?:delle )?disposizioni di consegna|expedie (?:depuis|du|de)\b|acheminement|unterwegs|\bin viaggio\b|\bw drodze\b|\bonderweg\b|transport|trasport|arriv|depart|processed|processing completed|processing at (?:the )?(?:origin |destination )?branch\b|\breceipt of (?:the )?(?:package|parcel|shipment) at (?:the )?dpd branch\b|sorted|sorting|\btri\b|\btriee?s?\b|traitement|traite|sortier|bearbeitet|bearbeitung|elaborat|elaborazion|smistat|parcel cent(?:er|re)|delivery cent(?:er|re)|centre.*(?:colis|distribution)|paketzentrum|verteilzentrum|centro.*(?:pacchi|distribuzione)|depot|import scan|scan.*import|importscan|scansione.*import|delivery.*delayed|livraison.*(?:retard|report)|zustellung.*verzogert|consegna.*ritard|transferred|transfere|weitergeleitet|inoltrato|left.*(?:point|center)|quitte|verlassen|raggiunto|close bag|fermeture du sac|sack.*geschlossen|chiusura.*sacco|scanned into|scanned at (?:a |the )?checkpoint|dismantling the board|scanne dans|eingescannt|scansionat|\b(?:un)?loaded\b|^(?:item|parcel|shipment) distributed$|charg.*vehicule de transport|transportfahrzeug.*geladen|caric.*veicolo.*trasporto/.test(t) || /^enviad[oa]$|\bclasificad[oa]s?\b|\bclasificacion\b|\ben ruta\b|\ben camino\b|\bcargad[oa]s? en (?:el )?vehiculo\b|\bllegad[oa]s? (?:a|al|en)\b|\bllegada (?:a|al|en|de)\b|\bha llegado\b|\bllego (?:a|al)\b|\bsalida (?:de|del|desde)\b|\b(?:ha )?salido (?:de|del)\b|\bsalio (?:de|del)\b|\ben destino\b|\b(?:agencia|delegacion|plataforma|oficina|franquicia) (?:de )?destino\b|\b(?:unidad|centro|oficina|delegacion|base|agencia|plataforma) de reparto\b|\bcentro (?:de )?(?:clasificacion|distribucion|tratamiento|logistico|operaciones|operativo)\b|\blectura\b|\bleid[oa]s?\b|\brecepcionad[oa]s?\b|\bprocesad[oa]s?\b|\bexpedid[oa]s?\b|\btriagem\b|\btriad[oa]s?\b|\bem tratamento\b|\btratad[oa]s?\b|\bcentro (?:de )?(?:distribuicao|tratamento|triagem|operacional)\b|\bchegad[ao] (?:a|ao|no|na|em)\b|\bchegou (?:a|ao|no|na|em)\b|\bsaida (?:do|da|de|dos|das)\b|\bsaiu (?:do|da|de|dos|das)\b|\bem rota\b|\ba caminho\b|\bencaminhad[oa]s?\b|\b(?:em )?transferencia\b|\btransferid[oa]s?\b|\bprocessad[oa]s?\b|\brecebid[oa] (?:no|na|em) (?:centro|armazem|unidade|plataforma)\b|\brecibid[oa] en (?:el |la |nuestr[oa] )?(?:almacen|centro|delegacion|plataforma|hub|agencia)\b/.test(t)) return "in_transit";
|
|
47004
47004
|
if (/^(?:приинято|принято)$/.test(t)) return "accepted";
|
|
47005
47005
|
if (au.test(t)) return "in_transit";
|
|
47006
47006
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The zone of the recipient country's clock, which every scan shows, where iMile
|
|
3
|
+
* delivers on one. iMile names a country, never a state or province, so Peru's
|
|
4
|
+
* code is Peru's. Null for a country whose clock the scans do not say.
|
|
5
|
+
*/
|
|
6
|
+
export declare function imileCountryClock(country: string): string | null;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The clock iMile's tracker shows a parcel's scans on, which other feeds relaying
|
|
3
|
+
* them keep (providers/ship24).
|
|
4
|
+
*/
|
|
5
|
+
import { countryTimeZone } from '../../core/time/index.js';
|
|
6
|
+
import { countryZones } from '../../places/zones.js';
|
|
7
|
+
/**
|
|
8
|
+
* The clock of a country iMile delivers in on several. New Zealand's other zone is
|
|
9
|
+
* the Chatham Islands': its delivery stations keep Auckland's clock. iMile Australia
|
|
10
|
+
* dates every scan on Sydney's, Queensland's included: on Brisbane's clock, its
|
|
11
|
+
* hubs' arrivals there would come after a consumer first saw them.
|
|
12
|
+
*/
|
|
13
|
+
const COUNTRY_CLOCKS = { NZ: 'Pacific/Auckland', AU: 'Australia/Sydney' };
|
|
14
|
+
/**
|
|
15
|
+
* The zone of the recipient country's clock, which every scan shows, where iMile
|
|
16
|
+
* delivers on one. iMile names a country, never a state or province, so Peru's
|
|
17
|
+
* code is Peru's. Null for a country whose clock the scans do not say.
|
|
18
|
+
*/
|
|
19
|
+
export function imileCountryClock(country) {
|
|
20
|
+
const zones = countryZones(country);
|
|
21
|
+
return countryTimeZone(country) ?? (zones.length === 1 ? zones[0] : COUNTRY_CLOCKS[country] ?? null);
|
|
22
|
+
}
|
|
@@ -2,10 +2,10 @@ import { DateTime } from 'luxon';
|
|
|
2
2
|
import { normalizeTrackingNumber } from '../../core/detection/index.js';
|
|
3
3
|
import { IndeterminateError, InvalidInputError, NoHistoryError, NotFoundError, RateLimitedError, SchemaError } from '../../core/errors/index.js';
|
|
4
4
|
import { languageStageStatus } from '../../core/status/index.js';
|
|
5
|
-
import { calendarDay,
|
|
5
|
+
import { calendarDay, isoTime } from '../../core/time/index.js';
|
|
6
6
|
import { clean } from '../../core/transport/index.js';
|
|
7
7
|
import { isRecord } from '../../core/types.js';
|
|
8
|
-
import {
|
|
8
|
+
import { imileCountryClock } from './clock.js';
|
|
9
9
|
import { imileStage } from './status.js';
|
|
10
10
|
import { imileDisplayWording } from './wording.js';
|
|
11
11
|
export const PROVIDER = 'iMile';
|
|
@@ -29,16 +29,6 @@ const COUNTRIES = {
|
|
|
29
29
|
HND: 'HN', HUN: 'HU', ITA: 'IT', KWT: 'KW', LBN: 'LB', MEX: 'MX', NZL: 'NZ', MKD: 'MK', OMN: 'OM', PER: 'PE',
|
|
30
30
|
POL: 'PL', QAT: 'QA', KSA: 'SA', SRB: 'RS', SVK: 'SK', RSA: 'ZA', ESP: 'ES', TUR: 'TR', UAE: 'AE', GBR: 'GB', USA: 'US',
|
|
31
31
|
};
|
|
32
|
-
// New Zealand's other zone is the Chatham Islands': its delivery stations keep Auckland's clock.
|
|
33
|
-
const MAINLAND_ZONES = { NZ: 'Pacific/Auckland' };
|
|
34
|
-
/**
|
|
35
|
-
* The zone of the recipient country's clock, where iMile delivers on one. iMile
|
|
36
|
-
* names a country, never a state or province, so Peru's code is Peru's.
|
|
37
|
-
*/
|
|
38
|
-
function countryClock(country) {
|
|
39
|
-
const zones = countryZones(country);
|
|
40
|
-
return countryTimeZone(country) ?? (zones.length === 1 ? zones[0] : MAINLAND_ZONES[country] ?? null);
|
|
41
|
-
}
|
|
42
32
|
/** A number for iMile's tracker, in upper case; anything else is not one it answers. */
|
|
43
33
|
export function normalizeImileNumber(raw) {
|
|
44
34
|
const number = raw.length <= 64 ? normalizeTrackingNumber(raw).toUpperCase() : '';
|
|
@@ -175,7 +165,7 @@ export function parseImile(payload, raw) {
|
|
|
175
165
|
const country = Object.hasOwn(COUNTRIES, code) ? COUNTRIES[code] : undefined;
|
|
176
166
|
// Every scan shows the recipient country's clock: a country of one zone
|
|
177
167
|
// places them all, and one of several leaves them local.
|
|
178
|
-
const zone = country ?
|
|
168
|
+
const zone = country ? imileCountryClock(country) : null;
|
|
179
169
|
const placed = zone ? newestFirst.map(({ local_time, ...event }) => {
|
|
180
170
|
const time = isoTime(local_time, zone)?.iso;
|
|
181
171
|
return time ? { time, ...event } : { local_time, ...event };
|
|
@@ -24,7 +24,8 @@ export function yanwenTrackingUrl(raw) {
|
|
|
24
24
|
}
|
|
25
25
|
// Partner scans can carry the delivery postcode: a Canadian one before the
|
|
26
26
|
// town, a US ZIP after the state. A location keeps its town and region only,
|
|
27
|
-
// and a partner's "Not Available" or bare "AA", a code no country holds, is
|
|
27
|
+
// and a partner's "Not Available" or bare "AA", a code no country holds, is
|
|
28
|
+
// none, as are a code of one or two digits and the partner's own name.
|
|
28
29
|
const POSTCODE = /^(?:[A-Z]\d[A-Z] ?\d[A-Z]\d|\d{5}(?:-\d{4})?)$/i;
|
|
29
30
|
// GOFO's European delivery scans say whether a PIN was used and give a door
|
|
30
31
|
// number; neither belongs in the wording.
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type { SameInstantIdentityPolicy } from '../../core/catalog/eventIdentity.js';
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* Whether a relayed scan's place names none: a partner's placeholder ("Not Available"), a code of
|
|
4
|
+
* one or two digits ("03"), or the partner's own name ("INPOST").
|
|
5
|
+
*/
|
|
3
6
|
export declare function isYanwenPlaceholder(location: string): boolean;
|
|
4
7
|
export declare const sameInstantIdentityPolicy: SameInstantIdentityPolicy;
|
|
@@ -1,10 +1,21 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isKnownCarrierName } from '../../core/catalog/hints.js';
|
|
2
|
+
import { matchesLocationEnrichment } from '../../core/catalog/locationIdentity.js';
|
|
2
3
|
import { yanwenWording } from './status.js';
|
|
3
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* Whether a relayed scan's place names none: a partner's placeholder ("Not Available"), a code of
|
|
6
|
+
* one or two digits ("03"), or the partner's own name ("INPOST").
|
|
7
|
+
*/
|
|
4
8
|
export function isYanwenPlaceholder(location) {
|
|
5
|
-
|
|
9
|
+
const place = location.replace(/\s+/g, ' ').trim();
|
|
10
|
+
return /^(?:not available|AA|\d{1,2})$/i.test(place) || isKnownCarrierName(place);
|
|
6
11
|
}
|
|
7
12
|
const same = (value) => value.replace(/\s+/g, ' ').trim().toLowerCase();
|
|
13
|
+
/** A scan stored with a placeholder as its place, which the adapter now drops, at the same instant in the same words, whatever stage it was stored on. */
|
|
14
|
+
function droppedPlaceholder(incoming, stored) {
|
|
15
|
+
return !same(incoming.location) && Boolean(same(stored.location)) && isYanwenPlaceholder(stored.location)
|
|
16
|
+
&& Boolean(same(incoming.description)) && same(incoming.description) === same(stored.description)
|
|
17
|
+
&& !['', 'unknown'].includes(incoming.stage);
|
|
18
|
+
}
|
|
8
19
|
/** A J&T Australia scan stored with the customer service line the adapter now leaves out, on the same stage in the same place. */
|
|
9
20
|
function lostJAndTContact(incoming, stored) {
|
|
10
21
|
const wording = yanwenWording(stored.description);
|
|
@@ -12,11 +23,12 @@ function lostJAndTContact(incoming, stored) {
|
|
|
12
23
|
&& incoming.stage === stored.stage && same(incoming.location) === same(stored.location);
|
|
13
24
|
}
|
|
14
25
|
// A relayed scan stored without a place can take it from the UTC copy the
|
|
15
|
-
// adapter now drops, one stored with a placeholder as its place loses it
|
|
16
|
-
//
|
|
26
|
+
// adapter now drops, one stored with a placeholder as its place loses it and
|
|
27
|
+
// takes the stage the map now gives notices and handovers that came with one,
|
|
28
|
+
// and one stored with J&T Australia's customer service line loses that.
|
|
17
29
|
export const sameInstantIdentityPolicy = {
|
|
18
30
|
sourceCarrierId: 'yanwen', storedSources: ['yanwen'], requireProviderCode: false,
|
|
19
31
|
matchEachScan: true,
|
|
20
|
-
matches: (incoming, stored) => matchesLocationEnrichment(incoming, stored) ||
|
|
32
|
+
matches: (incoming, stored) => matchesLocationEnrichment(incoming, stored) || droppedPlaceholder(incoming, stored)
|
|
21
33
|
|| lostJAndTContact(incoming, stored),
|
|
22
34
|
};
|
|
@@ -45,6 +45,7 @@ const WORDING = new Map([
|
|
|
45
45
|
['Arrived at USPS Regional Origin Facility', { status: 'in_transit', stage: 'in_transit' }],
|
|
46
46
|
['In Transit to Next Facility, Arriving On Time', { status: 'in_transit', stage: 'in_transit' }],
|
|
47
47
|
['Your shipment has been accepted by delivery carrier', { status: 'in_transit', stage: 'in_transit' }],
|
|
48
|
+
['Parcel Picked Up', { status: 'in_transit', stage: 'in_transit' }],
|
|
48
49
|
['Your shipment has arrived at sorting center', { status: 'in_transit', stage: 'in_transit' }],
|
|
49
50
|
// A partner's manifest as the parcel leaves Yanwen's facility, then the destination leg's
|
|
50
51
|
// release from customs, intake and arrival at the delivery office.
|
|
@@ -189,11 +190,14 @@ export function yanwenStatus(description, code) {
|
|
|
189
190
|
}
|
|
190
191
|
const STAGE_BY_WORDING = new Map([...WORDING].map(([wording, { stage }]) => [normalizeStatusWording(wording), stage]));
|
|
191
192
|
// USPS's record of the label Yanwen made, relayed while Yanwen still has the parcel in China, at the
|
|
192
|
-
// label's US origin,
|
|
193
|
-
//
|
|
193
|
+
// label's US origin, DHL's record of the recipient's choice of a Packstation, and InPost's records of
|
|
194
|
+
// the sender's data and of the recipient's delivery choice: notices that move nothing and name no
|
|
195
|
+
// place the parcel has been.
|
|
194
196
|
const NOTICES = new Map([
|
|
195
197
|
['Shipping Label Created, USPS Awaiting Item', "USPS's record of the label Yanwen made for it, relayed while Yanwen still has the parcel: not a scan, so it keeps the stage the parcel already had."],
|
|
196
198
|
['The recipient has selected a Packstation as the new delivery address.', "DHL's record of the recipient's choice of a Packstation, relayed while the parcel can still be in China: not a scan, so it keeps the stage the parcel already had."],
|
|
199
|
+
['Pre-shipment Info sent to carrier', "InPost's record of the sender's data reaching it, relayed while Yanwen still has the parcel: not a scan, so it keeps the stage the parcel already had."],
|
|
200
|
+
['Preference applied', "InPost's record of the recipient's delivery choice, relayed while the parcel can still be in China: not a scan, so it keeps the stage the parcel already had."],
|
|
197
201
|
].map(([wording, note]) => [normalizeStatusWording(wording), note]));
|
|
198
202
|
/** Whether a scan is a notice: it keeps the stage of the scan before it and names no place. */
|
|
199
203
|
export const yanwenNotice = (description) => NOTICES.has(normalizeStatusWording(withoutPrefix(description)));
|
|
@@ -203,6 +203,20 @@
|
|
|
203
203
|
"firstSeen": "2026-11-07",
|
|
204
204
|
"note": "Relayed from DHL a minute after DHL's record of the sender's data, while the parcel is still in China: the recipient's instruction, a notice that keeps the stage before it."
|
|
205
205
|
},
|
|
206
|
+
{
|
|
207
|
+
"wording": "Pre-shipment Info sent to carrier",
|
|
208
|
+
"language": "en",
|
|
209
|
+
"confirmedBy": "live",
|
|
210
|
+
"firstSeen": "2026-11-08",
|
|
211
|
+
"note": "Relayed from InPost hours after Yanwen's pickup scan, while the parcel is still in China, with the code 03 where a place goes: the sender's data reaching the last-mile carrier, a notice that keeps the stage before it."
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
"wording": "Preference applied",
|
|
215
|
+
"language": "en",
|
|
216
|
+
"confirmedBy": "live",
|
|
217
|
+
"firstSeen": "2026-11-08",
|
|
218
|
+
"note": "Relayed from InPost, with its name where a place goes, before the parcel leaves Yanwen's facility: the recipient's delivery choice, a notice that keeps the stage before it."
|
|
219
|
+
},
|
|
206
220
|
{
|
|
207
221
|
"wording": "Shipment information received",
|
|
208
222
|
"language": "en",
|
|
@@ -288,6 +302,14 @@
|
|
|
288
302
|
"firstSeen": "2026-10-08",
|
|
289
303
|
"note": "The last-mile carrier's handover scan; Yanwen's pickup scan is the acceptance."
|
|
290
304
|
},
|
|
305
|
+
{
|
|
306
|
+
"wording": "Parcel Picked Up",
|
|
307
|
+
"language": "en",
|
|
308
|
+
"stage": "in_transit",
|
|
309
|
+
"confirmedBy": "live",
|
|
310
|
+
"firstSeen": "2026-11-08",
|
|
311
|
+
"note": "Relayed from InPost, with its name where a place goes, before Yanwen's outbound scan: the last-mile carrier collecting the parcel from Yanwen, a handover; Yanwen's pickup scan is the acceptance."
|
|
312
|
+
},
|
|
291
313
|
{
|
|
292
314
|
"wording": "Your shipment has arrived at sorting center",
|
|
293
315
|
"language": "en",
|
|
@@ -748,13 +748,13 @@ export function trackingLanguageStage(description) {
|
|
|
748
748
|
// Accepted by CTT: its "Aceite" state, and "O envio foi aceite" in its scans. A return request accepted is no parcel.
|
|
749
749
|
if (/^aceite(?:\.|$)|\b(?:envio|objeto|objecto|encomenda|remessa) foi aceite\b/.test(text))
|
|
750
750
|
return 'accepted';
|
|
751
|
-
// A cross-dock transfer, a flight taking off or landing, a tally in a bonded
|
|
751
|
+
// A cross-dock transfer, a flight taking off, gone or landing, a tally in a bonded
|
|
752
752
|
// warehouse, a hand-over to the last mile being prepared or to an airline, a
|
|
753
753
|
// parcel bagged, unbagged or consolidated onto a master air waybill, a parcel
|
|
754
754
|
// ready for a facility's outbound dispatch or leaving a warehouse, and a
|
|
755
755
|
// forwarder's inbound scan at a facility, which may follow in brackets, move
|
|
756
756
|
// the parcel on.
|
|
757
|
-
if (/\bcross ?dock(?:ed|ing)?\b|\bready for outbound\b|^(?:(?:item|parcel|package|shipment) )?inbound scan(?:ned|ning)?\b|\b(?:un)?bagg(?:ed|ing)\b|\bmaster (?:air ?way ?bill|awb)(?: (?:number|no))? (?:received|assigned|allocated)\b|\bhand ?over to (?:the |an )?(?:airline|air carrier)\b|^(?:(?:the |your )?(?:item|parcel|package|shipment) )?(?:has )?left (?:the|our) warehouse$|\b(?:flight|leg)\b[^.;]*\b(?:is taking off|took off|has taken off|landed)\b|\bflight (?:take ?off|landing)\b|^(?:initial|final) landing$|\bcargo tally in the supervised warehouse\b|\bprepared for (?:the )?last[- ]mile hand ?over\b/.test(text))
|
|
757
|
+
if (/\bcross ?dock(?:ed|ing)?\b|\bready for outbound\b|^(?:(?:item|parcel|package|shipment) )?inbound scan(?:ned|ning)?\b|\b(?:un)?bagg(?:ed|ing)\b|\bmaster (?:air ?way ?bill|awb)(?: (?:number|no))? (?:received|assigned|allocated)\b|\bhand ?over to (?:the |an )?(?:airline|air carrier)\b|^(?:(?:the |your )?(?:item|parcel|package|shipment) )?(?:has )?left (?:the|our) warehouse$|\b(?:flight|leg)\b[^.;]*\b(?:is taking off|took off|has taken off|has left|landed)\b|\bflight (?:take ?off|landing)\b|^(?:initial|final) landing$|\bcargo tally in the supervised warehouse\b|\bprepared for (?:the )?last[- ]mile hand ?over\b/.test(text))
|
|
758
758
|
return 'in_transit';
|
|
759
759
|
// The airline's notice of arrival (NOA) reaching the destination agent, after the landing and
|
|
760
760
|
// before clearance, as Shouwu, JRT and the ground handlers word it.
|
|
@@ -29,6 +29,7 @@ import { DHLINK_RECORD_ZONE, usClockCopy } from '../../carriers/dhlink/clock.js'
|
|
|
29
29
|
import { epostGlobalCopyZone } from '../../carriers/epost-global/status.js';
|
|
30
30
|
import { FIRST_LINE_CHINA_ZONE, firstLineInChina } from '../../carriers/first-line/clock.js';
|
|
31
31
|
import { hrvatskaPostaForeignZone } from '../../carriers/hrvatska-posta/parser.js';
|
|
32
|
+
import { imileCountryClock } from '../../carriers/imile/clock.js';
|
|
32
33
|
import { jsExpressChinaClock } from '../../carriers/js-express/clock.js';
|
|
33
34
|
import { pitneyBowesPlatformClock, pitneyBowesPostalRelay } from '../../carriers/pitney-bowes/clock.js';
|
|
34
35
|
import { pocztaPolskaPartnerClock } from '../../carriers/poczta-polska/clock.js';
|
|
@@ -1033,8 +1034,10 @@ function carrierClock(payload, state, number, status = '', destination = null) {
|
|
|
1033
1034
|
return null;
|
|
1034
1035
|
if (key === 'sunyousytrack' && SUNYOU_DESTINATION_LEG.test(status.trim()))
|
|
1035
1036
|
return countryTimeZone(destination) ?? undefined;
|
|
1036
|
-
if (carrier === 'imile')
|
|
1037
|
-
|
|
1037
|
+
if (carrier === 'imile') {
|
|
1038
|
+
const country = countryCode(destination);
|
|
1039
|
+
return IMILE_FORWARDER_COPY.test(status.trim()) ? 'Asia/Shanghai' : country ? imileCountryClock(country) : null;
|
|
1040
|
+
}
|
|
1038
1041
|
if (carrier === 'mailamericas' && MAILAMERICAS_DESTINATION_CUSTOMS.test(status.trim())) {
|
|
1039
1042
|
return MAILAMERICAS_DESTINATION_ZONES[countryCode(destination) ?? ''] ?? undefined;
|
|
1040
1043
|
}
|
|
@@ -23,6 +23,7 @@ import { correosOriginZone } from '../../carriers/correos-spain/clock.js';
|
|
|
23
23
|
import { seventeenFeiaCopyZone } from '../../carriers/17feia/clock.js';
|
|
24
24
|
import { cttSiteZone, portugalZone } from '../../carriers/ctt/clock.js';
|
|
25
25
|
import { gofoScanZone } from '../../carriers/gofo/clock.js';
|
|
26
|
+
import { imileCountryClock } from '../../carriers/imile/clock.js';
|
|
26
27
|
import { swissPostDestinationZone } from '../../carriers/swiss-post/clock.js';
|
|
27
28
|
import { trackingPlace } from '../../places/trackingLocation.js';
|
|
28
29
|
import { courierZone, universalCarrierHints } from '../shared/hints.js';
|
|
@@ -141,6 +142,8 @@ const RELABELLED_FEEDS = {
|
|
|
141
142
|
'4px-tracking': { shift: 'Asia/Shanghai' },
|
|
142
143
|
// A status is 17FEIA's words, then its label or "OTHER".
|
|
143
144
|
'feia-tracking': { place: (location, _journey, status) => seventeenFeiaCopyZone(status.replace(/,\s*[^,]*$/, ''), location), walls: true },
|
|
145
|
+
// A country of several clocks keeps its wall clocks, as iMile's adapter does, not iMile's Dubai clock.
|
|
146
|
+
'imile-tracking': { clock: (journey) => journey.destination ? imileCountryClock(journey.destination) : null, walls: true },
|
|
144
147
|
};
|
|
145
148
|
function feedOf(scan) {
|
|
146
149
|
const code = isRecord(scan.courier) ? scan.courier.crawler_code : null;
|
|
@@ -378,7 +381,7 @@ export function parseShip24Response(payload, trackingNumber, timezone = null, no
|
|
|
378
381
|
const unnamed = feed || (forwarding && isRecord(raw.courier) && raw.courier.crawler_code === 'mainfreight-tracking');
|
|
379
382
|
// Correos's copy of an origin post's scan takes that post's clock, or keeps its wall clock.
|
|
380
383
|
const sent = wall ? correosOriginCopy(raw, journey) : undefined;
|
|
381
|
-
const placed = !wall ? null : carrierZone(relayed)
|
|
384
|
+
const placed = !wall ? null : carrierZone(relayed) ?? carrierZone(feed?.clock?.(journey))
|
|
382
385
|
?? (sent !== undefined ? carrierZone(sent) : placedZone(journey, raw, wall, feed) ?? (unnamed ? null : carrierZone(namedZone(journey, wall))));
|
|
383
386
|
readings.push({ event: parsed, wall, feed, country: feed?.country ?? locationCountry(raw.location), zone: placed?.zone ?? null, basis: placed?.basis });
|
|
384
387
|
}
|
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.1024.
|
|
3
|
+
"version": "0.1024.2-main.5565",
|
|
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",
|