universal-parcel-scraper 0.828.2-main.4439 → 0.828.3-main.4441
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/carriers/gls-it/status.d.ts +4 -3
- package/dist/carriers/gls-it/status.js +12 -3
- package/dist/carriers/gls-it/statuses.json +64 -0
- package/dist/carriers/gzyl/hualei.js +21 -1
- package/dist/carriers/jd-logistics/domestic.js +8 -4
- package/dist/carriers/jd-logistics/status.d.ts +3 -3
- package/dist/carriers/jd-logistics/status.js +10 -0
- package/dist/carriers/jd-logistics/statuses.json +56 -0
- package/dist/carriers/jrt/status.js +4 -1
- package/dist/carriers/jrt/statuses.json +360 -36
- package/dist/carriers/yunexpress/status.js +2 -0
- package/dist/carriers/yunexpress/statuses.json +16 -0
- package/dist/providers/shared/result.js +3 -0
- package/dist/server/openapi.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -163,7 +163,7 @@ the command line's `track --source ParcelsApp` does the same.
|
|
|
163
163
|
<!-- GENERATED:stages -->
|
|
164
164
|
<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">
|
|
165
165
|
|
|
166
|
-
The carrier folders record 17,
|
|
166
|
+
The carrier folders record 17,707 such statuses, each filed under one stage.
|
|
167
167
|
<!-- /GENERATED:stages -->
|
|
168
168
|
|
|
169
169
|
Wording nobody recorded yet goes through a classifier that reads seven European languages.
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { type ClassifiedWording, type Stage } from '../../core/status/index.js';
|
|
2
2
|
import { type CarrierStatusMap } from '../../core/status/statusMap.js';
|
|
3
3
|
/**
|
|
4
|
-
* The tracker's scan sentences in its English locale, mapped whole
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* The tracker's scan sentences in its English locale, mapped whole, and their
|
|
5
|
+
* Italian versions, which the English locale sometimes answers with instead.
|
|
6
|
+
* The scans carry no code, only a sentence, and every network depot writes the
|
|
7
|
+
* same ones.
|
|
7
8
|
*/
|
|
8
9
|
export declare const GLS_ITALY_SCANS: Readonly<Record<string, Stage>>;
|
|
9
10
|
/**
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { classifyWording } from '../../core/status/index.js';
|
|
2
2
|
import { normalizeStatusWording } from '../../core/status/statusMap.js';
|
|
3
3
|
/**
|
|
4
|
-
* The tracker's scan sentences in its English locale, mapped whole
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* The tracker's scan sentences in its English locale, mapped whole, and their
|
|
5
|
+
* Italian versions, which the English locale sometimes answers with instead.
|
|
6
|
+
* The scans carry no code, only a sentence, and every network depot writes the
|
|
7
|
+
* same ones.
|
|
7
8
|
*/
|
|
8
9
|
export const GLS_ITALY_SCANS = {
|
|
9
10
|
// The sender booked the shipment and has not handed it over yet.
|
|
@@ -21,6 +22,14 @@ export const GLS_ITALY_SCANS = {
|
|
|
21
22
|
'Parcel not delivered as the consignee was absent, please book redelivery within 3 working days as per instructions provided by email or sms.': 'failed_attempt',
|
|
22
23
|
// Held at the depot until the recipient answers the redelivery link.
|
|
23
24
|
'Not in delivery. Awaiting instructions by link included in received email or sms': 'exception',
|
|
25
|
+
"La spedizione e' stata creata dal mittente, attendiamo che ci venga affidata per l'invio a destinazione.": 'registered',
|
|
26
|
+
'Partita dalla sede mittente. In transito.': 'in_transit',
|
|
27
|
+
'In transito.': 'in_transit',
|
|
28
|
+
'Arrivata nella Sede GLS locale.': 'in_transit',
|
|
29
|
+
'Consegna prevista nel corso della giornata odierna.': 'out_for_delivery',
|
|
30
|
+
'Consegnata.': 'delivered',
|
|
31
|
+
'Destinatario assente al primo passaggio. Prego prenotare la consegna entro 3 giorni lavorativi, seguendo le istruzioni ricevute via mail o sms.': 'failed_attempt',
|
|
32
|
+
"Spedizione non in consegna. Il destinatario puo' prenotare la riconsegna tramite link presente in email / sms ricevuto": 'exception',
|
|
24
33
|
};
|
|
25
34
|
/**
|
|
26
35
|
* The progress rail's current step, a GLS group code. It has five steps and no
|
|
@@ -89,6 +89,70 @@
|
|
|
89
89
|
"firstSeen": "2026-10-11",
|
|
90
90
|
"note": "Held at the depot after a failed attempt until the recipient books a redelivery through the link."
|
|
91
91
|
},
|
|
92
|
+
{
|
|
93
|
+
"wording": "La spedizione e' stata creata dal mittente, attendiamo che ci venga affidata per l'invio a destinazione.",
|
|
94
|
+
"language": "it",
|
|
95
|
+
"stage": "registered",
|
|
96
|
+
"confirmedBy": "live",
|
|
97
|
+
"firstSeen": "2026-10-29",
|
|
98
|
+
"note": "The Italian version of the sender's booking, before the hand-over."
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"wording": "Partita dalla sede mittente. In transito.",
|
|
102
|
+
"language": "it",
|
|
103
|
+
"stage": "in_transit",
|
|
104
|
+
"confirmedBy": "live",
|
|
105
|
+
"firstSeen": "2026-10-29",
|
|
106
|
+
"note": "The Italian version of the sending depot's dispatch."
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"wording": "In transito.",
|
|
110
|
+
"language": "it",
|
|
111
|
+
"stage": "in_transit",
|
|
112
|
+
"confirmedBy": "live",
|
|
113
|
+
"firstSeen": "2026-10-29",
|
|
114
|
+
"note": "The Italian version of a hub scan between the two depots."
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"wording": "Arrivata nella Sede GLS locale.",
|
|
118
|
+
"language": "it",
|
|
119
|
+
"stage": "in_transit",
|
|
120
|
+
"confirmedBy": "live",
|
|
121
|
+
"firstSeen": "2026-10-29",
|
|
122
|
+
"note": "The Italian version of the arrival at the receiving depot."
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"wording": "Consegna prevista nel corso della giornata odierna.",
|
|
126
|
+
"language": "it",
|
|
127
|
+
"stage": "out_for_delivery",
|
|
128
|
+
"confirmedBy": "live",
|
|
129
|
+
"firstSeen": "2026-10-29",
|
|
130
|
+
"note": "The Italian version of the morning scan of the delivery round."
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"wording": "Consegnata.",
|
|
134
|
+
"language": "it",
|
|
135
|
+
"stage": "delivered",
|
|
136
|
+
"confirmedBy": "live",
|
|
137
|
+
"firstSeen": "2026-10-29",
|
|
138
|
+
"note": "The Italian version of the delivery."
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"wording": "Destinatario assente al primo passaggio. Prego prenotare la consegna entro 3 giorni lavorativi, seguendo le istruzioni ricevute via mail o sms.",
|
|
142
|
+
"language": "it",
|
|
143
|
+
"stage": "failed_attempt",
|
|
144
|
+
"confirmedBy": "live",
|
|
145
|
+
"firstSeen": "2026-10-29",
|
|
146
|
+
"note": "The Italian version of a failed attempt because nobody was in."
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
"wording": "Spedizione non in consegna. Il destinatario puo' prenotare la riconsegna tramite link presente in email / sms ricevuto",
|
|
150
|
+
"language": "it",
|
|
151
|
+
"stage": "exception",
|
|
152
|
+
"confirmedBy": "live",
|
|
153
|
+
"firstSeen": "2026-10-29",
|
|
154
|
+
"note": "The Italian version of the hold until the recipient books a redelivery."
|
|
155
|
+
},
|
|
92
156
|
{
|
|
93
157
|
"code": "PREADVICE",
|
|
94
158
|
"stage": "registered",
|
|
@@ -200,9 +200,29 @@ function scans(rows, tenant, readAt) {
|
|
|
200
200
|
&& repeats(entry.event.description, other.event.description))) : kept;
|
|
201
201
|
// Rows come newest first; rows on one clock keep the reply's order.
|
|
202
202
|
unique.sort((a, b) => (a.clock === b.clock ? a.index - b.index : a.clock < b.clock ? 1 : -1));
|
|
203
|
-
const
|
|
203
|
+
const leg = returnLeg(unique.map(({ event }) => event));
|
|
204
|
+
const events = unique.map(({ event, english }, index) => ({ ...event, ...(english ? { description: english } : {}),
|
|
205
|
+
...(leg[index] ? { stage: 'returned' } : {}) }));
|
|
204
206
|
return { events: events.slice(0, MAX_EVENTS), truncated: events.length > MAX_EVENTS, ahead: [...ahead] };
|
|
205
207
|
}
|
|
208
|
+
const RETURN_LEG = new Set(['in_transit', 'out_for_delivery', 'delivered']);
|
|
209
|
+
/**
|
|
210
|
+
* Which scans, newest first, are on the way back: after a row the tenant's map
|
|
211
|
+
* reads as returned, a move or a delivery is the parcel going, or delivered,
|
|
212
|
+
* back to the sender. A return only the shared wording rules read starts none.
|
|
213
|
+
*/
|
|
214
|
+
function returnLeg(events) {
|
|
215
|
+
const leg = events.map(() => false);
|
|
216
|
+
let back = false;
|
|
217
|
+
for (let index = events.length - 1; index >= 0; index--) {
|
|
218
|
+
const { stage, stage_source: source } = events[index];
|
|
219
|
+
if (back && stage && RETURN_LEG.has(stage))
|
|
220
|
+
leg[index] = true;
|
|
221
|
+
else if (stage === 'returned' && source === 'carrier_map')
|
|
222
|
+
back = true;
|
|
223
|
+
}
|
|
224
|
+
return leg;
|
|
225
|
+
}
|
|
206
226
|
/**
|
|
207
227
|
* The result. The record's own summary status is not read: it can be ahead
|
|
208
228
|
* of the rows held back.
|
|
@@ -6,6 +6,7 @@ import { explicitOffsetTime } from '../../core/time/index.js';
|
|
|
6
6
|
import { clean } from '../../core/transport/index.js';
|
|
7
7
|
import { isRecord } from '../../core/types.js';
|
|
8
8
|
import { normalizeJdNumber } from './parser.js';
|
|
9
|
+
import { jdLogisticsStatus } from './status.js';
|
|
9
10
|
import { withoutCourierContact } from './wording.js';
|
|
10
11
|
const PROVIDER = 'JD Logistics';
|
|
11
12
|
export const JD_DOMESTIC_TRACK = 'https://www.jdl.com/waybill/trackNoPin';
|
|
@@ -20,7 +21,7 @@ export function jdDomesticWording(raw) {
|
|
|
20
21
|
const text = withoutCourierContact(clean(raw, 2000))
|
|
21
22
|
.replace(/由[^,。;;]{1,100}签收/gu, '签收')
|
|
22
23
|
.replace(/((?:已|成功)签收)[,,。;;].*$/u, '$1');
|
|
23
|
-
const contact =
|
|
24
|
+
const contact = /配送员|快递员|揽收员|京东小哥|快递小哥|签收人|收件人|联系电话|联系方式|手机号码?|电话(?:号码?)?|signed(?: for)? by|recipient(?: name)?\s*:|courier(?: name| phone)?\s*:/iu.exec(text);
|
|
24
25
|
const head = (contact ? text.slice(0, contact.index) : text).replace(/[\s,,;;。::]+$/u, '').trim();
|
|
25
26
|
if (head)
|
|
26
27
|
return head;
|
|
@@ -89,10 +90,12 @@ export function parseJdDomestic(payload, raw) {
|
|
|
89
90
|
wordless++;
|
|
90
91
|
continue;
|
|
91
92
|
}
|
|
92
|
-
const classification = classifyWording(description);
|
|
93
93
|
const code = typeof row.state === 'string' || typeof row.state === 'number' ? clean(String(row.state), 40) : '';
|
|
94
|
+
const mapped = code ? jdLogisticsStatus(code, description) : undefined;
|
|
95
|
+
const classification = classifyWording(description);
|
|
94
96
|
events.push({ description, ...time, ...(code ? { provider_code: code } : {}),
|
|
95
|
-
...(
|
|
97
|
+
...(mapped ? { stage: mapped.stage, stage_source: 'carrier_map' }
|
|
98
|
+
: classification.source !== 'none' ? { stage: classification.stage, stage_source: classification.source } : {}) });
|
|
96
99
|
}
|
|
97
100
|
}
|
|
98
101
|
if (!events.length)
|
|
@@ -105,8 +108,9 @@ export function parseJdDomestic(payload, raw) {
|
|
|
105
108
|
const unique = events.filter(event => { const key = JSON.stringify(event); if (seen.has(key))
|
|
106
109
|
return false; seen.add(key); return true; });
|
|
107
110
|
const kept = unique.slice(0, 100), latest = kept[0];
|
|
111
|
+
const coded = latest.provider_code ? jdLogisticsStatus(latest.provider_code, latest.description) : undefined;
|
|
108
112
|
const classification = classifyWording(latest.description);
|
|
109
|
-
return { status: classification.source !== 'none' ? languageStageStatus(classification.stage) : 'unknown',
|
|
113
|
+
return { status: coded?.status ?? (classification.source !== 'none' ? languageStageStatus(classification.stage) : 'unknown'),
|
|
110
114
|
...(latest.stage ? { current_stage: latest.stage, current_stage_source: latest.stage_source } : {}),
|
|
111
115
|
last_status_text: latest.description, last_update: latest.time ?? null,
|
|
112
116
|
...(latest.local_time ? { last_update_local: latest.local_time } : {}),
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* JD Logistics international operation codes (`operationCode`), as the
|
|
3
|
-
* international feed sends them on each scan
|
|
4
|
-
*
|
|
5
|
-
* wording rules.
|
|
3
|
+
* international feed sends them on each scan, and the domestic tracker's scan
|
|
4
|
+
* states (`state`). Codes not listed here leave the scan to the steps JD relays
|
|
5
|
+
* from a last-mile partner, then to the shared wording rules.
|
|
6
6
|
*/
|
|
7
7
|
import type { ClassifiedStatus } from '../../core/status/index.js';
|
|
8
8
|
import type { CarrierStatusMap } from '../../core/status/statusMap.js';
|
|
@@ -17,6 +17,16 @@ const CODES = new Map([
|
|
|
17
17
|
['VUVU', MOVING],
|
|
18
18
|
['DMLMLS', { status: 'out_for_delivery', stage: 'out_for_delivery' }],
|
|
19
19
|
['DLV', { status: 'delivered', stage: 'delivered' }],
|
|
20
|
+
// The domestic tracker's states, whose Chinese wording the shared rules do not read.
|
|
21
|
+
// A courier is assigned to collect the parcel; the collection follows under 200001.
|
|
22
|
+
['200034', { status: 'pending', stage: 'registered' }],
|
|
23
|
+
['200001', { status: 'in_transit', stage: 'accepted' }],
|
|
24
|
+
['200077', MOVING],
|
|
25
|
+
['200019', MOVING],
|
|
26
|
+
['200000', MOVING],
|
|
27
|
+
['10054', MOVING],
|
|
28
|
+
['11000', { status: 'out_for_delivery', stage: 'out_for_delivery' }],
|
|
29
|
+
['10034', { status: 'delivered', stage: 'delivered' }],
|
|
20
30
|
]);
|
|
21
31
|
// A last-mile partner's step, read by its wording ahead of the code JD relays it under: GOFO's
|
|
22
32
|
// label comes under SRRF3P, a code JD has shown with that step alone, and GOFO's loading at a
|
|
@@ -108,6 +108,62 @@
|
|
|
108
108
|
"confirmedBy": "live",
|
|
109
109
|
"firstSeen": "2026-10-10",
|
|
110
110
|
"note": "Signed for."
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"code": "200034",
|
|
114
|
+
"stage": "registered",
|
|
115
|
+
"confirmedBy": "live",
|
|
116
|
+
"firstSeen": "2026-10-29",
|
|
117
|
+
"note": "A courier is assigned to collect the parcel (您的快件将由…). The courier's name and phone follow and are dropped."
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"code": "200001",
|
|
121
|
+
"stage": "accepted",
|
|
122
|
+
"confirmedBy": "live",
|
|
123
|
+
"firstSeen": "2026-10-29",
|
|
124
|
+
"note": "Collected from the sender (京东快件已取件)."
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"code": "200077",
|
|
128
|
+
"stage": "in_transit",
|
|
129
|
+
"confirmedBy": "live",
|
|
130
|
+
"firstSeen": "2026-10-29",
|
|
131
|
+
"note": "At the collecting service station, waiting to be loaded (已到达【…】,准备装车)."
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
"code": "200019",
|
|
135
|
+
"stage": "in_transit",
|
|
136
|
+
"confirmedBy": "live",
|
|
137
|
+
"firstSeen": "2026-10-29",
|
|
138
|
+
"note": "Left the collecting service station for the next site (已离开【…】,正在发往下一站)."
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"code": "200000",
|
|
142
|
+
"stage": "in_transit",
|
|
143
|
+
"confirmedBy": "live",
|
|
144
|
+
"firstSeen": "2026-10-29",
|
|
145
|
+
"note": "Arrived at a sorting center or the delivery station (已到达【…】)."
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
"code": "10054",
|
|
149
|
+
"stage": "in_transit",
|
|
150
|
+
"confirmedBy": "live",
|
|
151
|
+
"firstSeen": "2026-10-29",
|
|
152
|
+
"note": "Left a sorting center for the next site (已离开【…】,正在发往下一站)."
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
"code": "11000",
|
|
156
|
+
"stage": "out_for_delivery",
|
|
157
|
+
"confirmedBy": "live",
|
|
158
|
+
"firstSeen": "2026-10-29",
|
|
159
|
+
"note": "With the courier for delivery (正在派送中)."
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
"code": "10034",
|
|
163
|
+
"stage": "delivered",
|
|
164
|
+
"confirmedBy": "live",
|
|
165
|
+
"firstSeen": "2026-10-29",
|
|
166
|
+
"note": "Delivered (已送达)."
|
|
111
167
|
}
|
|
112
168
|
]
|
|
113
169
|
}
|
|
@@ -3,13 +3,16 @@ import statuses from './statuses.json' with { type: 'json' };
|
|
|
3
3
|
const WORDINGS = statusWordings(statuses.entries);
|
|
4
4
|
// The airport row names the day of the booked flight in both halves.
|
|
5
5
|
const FLIGHT_DAY = /^(货物已交机场,预计)\d{1,2}(号的航班 goods delivered to airport, scheduled on flight of the )\d{1,2}(?:st|nd|rd|th)$/;
|
|
6
|
+
// So does the export clearance row, in both halves: "等待27号航班起飞 … on flight of the 27th".
|
|
7
|
+
const CLEARED_FLIGHT_DAY = /^(报关已放行等待)\d{1,2}(号航班起飞 customs released,awaiting departure on flight of the )\d{1,2}(?:st|nd|rd|th)$/;
|
|
6
8
|
// The English row of a booked flight names its day.
|
|
7
9
|
const TAKE_OFF_DAY = /^(flight is expected to take off on )[a-z]+ \d{1,2}$/;
|
|
8
10
|
// The newer English rows name the flight's days as month.day: "expected to depart on 10.27 and land on 10.27".
|
|
9
11
|
const FLIGHT_DATES = /^(the flight is expected to depart on )\d{1,2}\.\d{1,2}( and land on )\d{1,2}\.\d{1,2}$/;
|
|
10
12
|
const LANDING_DATE = /^(the flight has departed and is expected to )\d{1,2}\.\d{1,2}( land)$/;
|
|
11
13
|
/** The key a row's `normalizeStatusWording` wording is mapped under: a booked flight's day becomes an ellipsis. */
|
|
12
|
-
export const jrtScanKey = (wording) => wording.replace(FLIGHT_DAY, '$1…$2…')
|
|
14
|
+
export const jrtScanKey = (wording) => wording.replace(FLIGHT_DAY, '$1…$2…')
|
|
15
|
+
.replace(CLEARED_FLIGHT_DAY, '$1…$2…').replace(TAKE_OFF_DAY, '$1…')
|
|
13
16
|
.replace(FLIGHT_DATES, '$1…$2…').replace(LANDING_DATE, '$1…$2');
|
|
14
17
|
/**
|
|
15
18
|
* The stage JRT's map gives a row, by its whole `normalizeStatusWording` wording:
|
|
@@ -1,41 +1,365 @@
|
|
|
1
1
|
{
|
|
2
2
|
"carrier": "jrt",
|
|
3
3
|
"entries": [
|
|
4
|
-
{
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
{
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
{
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
{
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
{
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
4
|
+
{
|
|
5
|
+
"wording": "货物电子信息已经收到",
|
|
6
|
+
"language": "zh",
|
|
7
|
+
"english": "The electronic cargo information has been received",
|
|
8
|
+
"stage": "registered",
|
|
9
|
+
"confirmedBy": "live",
|
|
10
|
+
"note": "The platform writes this row itself for an order without scans, on its creation time. The English is the row's on BYHD's tracker."
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"wording": "Shipment information received",
|
|
14
|
+
"language": "en",
|
|
15
|
+
"stage": "registered",
|
|
16
|
+
"confirmedBy": "live",
|
|
17
|
+
"firstSeen": "2026-10-16"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"wording": "Shipment information sent to FedEx",
|
|
21
|
+
"language": "en",
|
|
22
|
+
"stage": "registered",
|
|
23
|
+
"confirmedBy": "live",
|
|
24
|
+
"firstSeen": "2026-10-26",
|
|
25
|
+
"note": "The label's data passed to FedEx, which delivers the last leg, before JRT's first scan."
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"wording": "到达收货点",
|
|
29
|
+
"language": "zh",
|
|
30
|
+
"english": "Arrived at the receiving point",
|
|
31
|
+
"stage": "accepted",
|
|
32
|
+
"confirmedBy": "live",
|
|
33
|
+
"firstSeen": "2026-10-16",
|
|
34
|
+
"note": "The first scan in JRT's hands; GZYL's tenant of the same platform words it \"Arrive at the cross border receiving point\". The English is Tianhui Hengyuan's and KUN Express's for their arrival on the platform."
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"wording": "货物离开操作中心",
|
|
38
|
+
"language": "zh",
|
|
39
|
+
"english": "Goods leave the operation center",
|
|
40
|
+
"stage": "in_transit",
|
|
41
|
+
"confirmedBy": "live",
|
|
42
|
+
"firstSeen": "2026-10-16",
|
|
43
|
+
"note": "GZYL's tenant of the same platform words it \"Goods leave the operation center\"."
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"wording": "Shipment arrived at facility",
|
|
47
|
+
"language": "en",
|
|
48
|
+
"stage": "in_transit",
|
|
49
|
+
"confirmedBy": "live",
|
|
50
|
+
"firstSeen": "2026-10-16"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"wording": "The goods have been packed and sent to the airline",
|
|
54
|
+
"language": "en",
|
|
55
|
+
"stage": "in_transit",
|
|
56
|
+
"confirmedBy": "live",
|
|
57
|
+
"firstSeen": "2026-10-26"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"wording": "Arriving at the airline, in the process of booking a flight",
|
|
61
|
+
"language": "en",
|
|
62
|
+
"stage": "in_transit",
|
|
63
|
+
"confirmedBy": "live",
|
|
64
|
+
"firstSeen": "2026-10-26",
|
|
65
|
+
"note": "At the airline while a flight is booked."
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"wording": "货物已交机场,等待航班安排中 Cargo delivered to airport, pending flight arrangement",
|
|
69
|
+
"language": "zh",
|
|
70
|
+
"stage": "in_transit",
|
|
71
|
+
"confirmedBy": "live",
|
|
72
|
+
"firstSeen": "2026-10-17",
|
|
73
|
+
"note": "Handed to the airport to wait for a flight. It is not a delivery to the recipient, though the translation reads \"delivered\"."
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"wording": "货物已交机场,预计…号的航班 Goods delivered to airport, scheduled on flight of the …",
|
|
77
|
+
"english": "Goods delivered to airport",
|
|
78
|
+
"language": "zh",
|
|
79
|
+
"stage": "in_transit",
|
|
80
|
+
"confirmedBy": "live",
|
|
81
|
+
"firstSeen": "2026-10-17",
|
|
82
|
+
"note": "Handed to the airport, with the day of the flight booked in both halves (\"预计11号的航班\", \"of the 11rd\"); the map reads each day as an ellipsis. The flight is still to come, so the row shows its English without it."
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"wording": "报关已放行等待…号航班起飞 Customs released,Awaiting departure on flight of the …",
|
|
86
|
+
"language": "zh",
|
|
87
|
+
"stage": "in_transit",
|
|
88
|
+
"confirmedBy": "live",
|
|
89
|
+
"firstSeen": "2026-10-29",
|
|
90
|
+
"note": "Cleared for export, waiting for the flight whose day both halves name (\"等待27号航班\", \"of the 27th\"); the map reads each day as an ellipsis."
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"wording": "航班起飞,以实际抵达为准 Flight departure subject to actual arrival time",
|
|
94
|
+
"language": "zh",
|
|
95
|
+
"stage": "in_transit",
|
|
96
|
+
"confirmedBy": "live",
|
|
97
|
+
"firstSeen": "2026-10-17"
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"wording": "航班落地,拆板中 Flight landing, dismantling board in progress",
|
|
101
|
+
"language": "zh",
|
|
102
|
+
"stage": "in_transit",
|
|
103
|
+
"confirmedBy": "live",
|
|
104
|
+
"firstSeen": "2026-10-17",
|
|
105
|
+
"note": "Landed; the pallets are broken down before clearance."
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"wording": "机场内等待提货,等待清关 Waiting for cargo pickup and customs clearance at the airport.",
|
|
109
|
+
"language": "zh",
|
|
110
|
+
"stage": "customs",
|
|
111
|
+
"confirmedBy": "live",
|
|
112
|
+
"firstSeen": "2026-10-17"
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"wording": "已提货,清关中 The goods have been picked up and are currently undergoing customs clearance",
|
|
116
|
+
"language": "zh",
|
|
117
|
+
"stage": "customs",
|
|
118
|
+
"confirmedBy": "live",
|
|
119
|
+
"firstSeen": "2026-10-17",
|
|
120
|
+
"note": "Collected from the airport; import clearance is under way."
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"wording": "清关完成,卡车发运 Customs clearance completed; truck dispatched.",
|
|
124
|
+
"language": "zh",
|
|
125
|
+
"stage": "in_transit",
|
|
126
|
+
"confirmedBy": "live",
|
|
127
|
+
"firstSeen": "2026-10-17"
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"wording": "已交服务商 Delivered to service provider",
|
|
131
|
+
"language": "zh",
|
|
132
|
+
"stage": "in_transit",
|
|
133
|
+
"confirmedBy": "live",
|
|
134
|
+
"firstSeen": "2026-10-17",
|
|
135
|
+
"note": "Handed to the delivering carrier. It is not a delivery to the recipient, though the translation reads \"delivered\"."
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"wording": "the instruction data for this shipment have been provided by the sender to dhl electronically",
|
|
139
|
+
"language": "en",
|
|
140
|
+
"stage": "registered",
|
|
141
|
+
"confirmedBy": "live",
|
|
142
|
+
"firstSeen": "2026-10-17",
|
|
143
|
+
"note": "Copied from DHL, which delivers the last leg: the label's data has reached it. JRT copies DHL's rows in lower case."
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"wording": "the shipment has been processed in the parcel center of origin",
|
|
147
|
+
"language": "en",
|
|
148
|
+
"stage": "in_transit",
|
|
149
|
+
"confirmedBy": "live",
|
|
150
|
+
"firstSeen": "2026-10-17",
|
|
151
|
+
"note": "Copied from DHL's hub after the hand-off."
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"wording": "unloaded from movement / tour vehicle",
|
|
155
|
+
"language": "en",
|
|
156
|
+
"stage": "in_transit",
|
|
157
|
+
"confirmedBy": "live",
|
|
158
|
+
"firstSeen": "2026-10-17",
|
|
159
|
+
"note": "Copied from DHL."
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
"wording": "loaded to movement / tour vehicle",
|
|
163
|
+
"language": "en",
|
|
164
|
+
"stage": "in_transit",
|
|
165
|
+
"confirmedBy": "live",
|
|
166
|
+
"firstSeen": "2026-10-17",
|
|
167
|
+
"note": "Copied from DHL."
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
"wording": "pick-up was successful.",
|
|
171
|
+
"language": "en",
|
|
172
|
+
"stage": "in_transit",
|
|
173
|
+
"confirmedBy": "live",
|
|
174
|
+
"firstSeen": "2026-10-29",
|
|
175
|
+
"note": "Copied from DHL: DHL collected the parcel after JRT's hand-over, so it is already on its way."
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
"wording": "the shipment has been processed in the destination parcel center",
|
|
179
|
+
"language": "en",
|
|
180
|
+
"stage": "in_transit",
|
|
181
|
+
"confirmedBy": "live",
|
|
182
|
+
"firstSeen": "2026-10-29",
|
|
183
|
+
"note": "Copied from DHL's hub near the recipient."
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
"wording": "movement / tour vehicle arrived",
|
|
187
|
+
"language": "en",
|
|
188
|
+
"stage": "in_transit",
|
|
189
|
+
"confirmedBy": "live",
|
|
190
|
+
"firstSeen": "2026-10-29",
|
|
191
|
+
"note": "Copied from DHL."
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
"wording": "not delivered-consignee could not be reached",
|
|
195
|
+
"language": "en",
|
|
196
|
+
"stage": "failed_attempt",
|
|
197
|
+
"confirmedBy": "live",
|
|
198
|
+
"firstSeen": "2026-10-29",
|
|
199
|
+
"note": "Copied from DHL: nobody was in."
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
"wording": "held for consignee collection",
|
|
203
|
+
"language": "en",
|
|
204
|
+
"stage": "ready_for_pickup",
|
|
205
|
+
"confirmedBy": "live",
|
|
206
|
+
"firstSeen": "2026-10-29",
|
|
207
|
+
"note": "Copied from DHL: left at a pickup point for the recipient."
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
"wording": "shipment will be returned to origin-not ordered",
|
|
211
|
+
"language": "en",
|
|
212
|
+
"stage": "returned",
|
|
213
|
+
"confirmedBy": "live",
|
|
214
|
+
"firstSeen": "2026-10-29",
|
|
215
|
+
"note": "Copied from DHL: the recipient refused it as not ordered, and it goes back."
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
"wording": "loaded to movement / tour vehicle(return to sender)",
|
|
219
|
+
"language": "en",
|
|
220
|
+
"stage": "returned",
|
|
221
|
+
"confirmedBy": "live",
|
|
222
|
+
"firstSeen": "2026-10-29",
|
|
223
|
+
"note": "Copied from DHL, on the way back to the sender."
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
"wording": "unloaded from movement / tour vehicle(return to sender)",
|
|
227
|
+
"language": "en",
|
|
228
|
+
"stage": "returned",
|
|
229
|
+
"confirmedBy": "live",
|
|
230
|
+
"firstSeen": "2026-10-29",
|
|
231
|
+
"note": "Copied from DHL, on the way back to the sender."
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
"wording": "movement / tour vehicle arrived(return to sender)",
|
|
235
|
+
"language": "en",
|
|
236
|
+
"stage": "returned",
|
|
237
|
+
"confirmedBy": "live",
|
|
238
|
+
"firstSeen": "2026-10-29",
|
|
239
|
+
"note": "Copied from DHL, on the way back to the sender."
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
"wording": "the shipment has been successfully delivered",
|
|
243
|
+
"language": "en",
|
|
244
|
+
"stage": "delivered",
|
|
245
|
+
"confirmedBy": "live",
|
|
246
|
+
"firstSeen": "2026-10-29",
|
|
247
|
+
"note": "Copied from DHL. After a return it is the delivery back to the sender, and the parser reads it as returned."
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
"wording": "returned shipment has been received by processing center",
|
|
251
|
+
"language": "en",
|
|
252
|
+
"stage": "returned",
|
|
253
|
+
"confirmedBy": "live",
|
|
254
|
+
"firstSeen": "2026-10-29",
|
|
255
|
+
"note": "The returned parcel is back at JRT's center."
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
"wording": "The instruction data for this shipment have been provided by the sender to DHL electronically.",
|
|
259
|
+
"language": "en",
|
|
260
|
+
"stage": "registered",
|
|
261
|
+
"confirmedBy": "live",
|
|
262
|
+
"note": "DHL's label data, copied with its case and full stop, before JRT's first scan."
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
"wording": "快件操作完成",
|
|
266
|
+
"language": "zh",
|
|
267
|
+
"english": "Shipment departed from facility",
|
|
268
|
+
"stage": "in_transit",
|
|
269
|
+
"confirmedBy": "live",
|
|
270
|
+
"note": "The English is HuaHan's, whose API documentation gives it for this summary of the departure scan."
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
"wording": "Sending to the airport",
|
|
274
|
+
"language": "en",
|
|
275
|
+
"stage": "in_transit",
|
|
276
|
+
"confirmedBy": "live"
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
"wording": "Arriving at the airport - arranging flights",
|
|
280
|
+
"language": "en",
|
|
281
|
+
"stage": "in_transit",
|
|
282
|
+
"confirmedBy": "live",
|
|
283
|
+
"note": "At the airport while a flight is booked."
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
"wording": "Flight is expected to take off on …",
|
|
287
|
+
"language": "en",
|
|
288
|
+
"stage": "in_transit",
|
|
289
|
+
"confirmedBy": "live",
|
|
290
|
+
"note": "Waiting at the airport for the booked flight, whose day is read as an ellipsis."
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
"wording": "Flight has flown",
|
|
294
|
+
"language": "en",
|
|
295
|
+
"stage": "in_transit",
|
|
296
|
+
"confirmedBy": "live"
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
"wording": "Flight has arrived",
|
|
300
|
+
"language": "en",
|
|
301
|
+
"stage": "in_transit",
|
|
302
|
+
"confirmedBy": "live"
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
"wording": "Dismantling and picking up goods in progress",
|
|
306
|
+
"language": "en",
|
|
307
|
+
"stage": "in_transit",
|
|
308
|
+
"confirmedBy": "live",
|
|
309
|
+
"note": "Landed; the pallets are broken down and the goods collected for clearance."
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
"wording": "Customs clearance of the cargo is in progress",
|
|
313
|
+
"language": "en",
|
|
314
|
+
"stage": "customs",
|
|
315
|
+
"confirmedBy": "live"
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
"wording": "Customs clearance completed",
|
|
319
|
+
"language": "en",
|
|
320
|
+
"stage": "in_transit",
|
|
321
|
+
"confirmedBy": "live"
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
"wording": "The goods have been handed over to the carrier",
|
|
325
|
+
"language": "en",
|
|
326
|
+
"stage": "in_transit",
|
|
327
|
+
"confirmedBy": "live",
|
|
328
|
+
"note": "Handed to the carrier that delivers the last leg."
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
"wording": "The shipment has been posted by the sender at the retail outlet",
|
|
332
|
+
"language": "en",
|
|
333
|
+
"stage": "in_transit",
|
|
334
|
+
"confirmedBy": "live",
|
|
335
|
+
"note": "Copied from DHL: JRT hands the parcel in at a Deutsche Post outlet for the last leg, so the parcel is already on its way."
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
"wording": "The flight is expected to depart on … and land on …",
|
|
339
|
+
"language": "en",
|
|
340
|
+
"stage": "in_transit",
|
|
341
|
+
"confirmedBy": "live",
|
|
342
|
+
"note": "Waiting at the airport for the booked flight, whose days, month.day, are read as an ellipsis."
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
"wording": "The flight has departed and is expected to … land",
|
|
346
|
+
"language": "en",
|
|
347
|
+
"stage": "in_transit",
|
|
348
|
+
"confirmedBy": "live",
|
|
349
|
+
"note": "In the air; the landing day, month.day, is read as an ellipsis."
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
"wording": "The flight has arrived and is awaiting dismantling",
|
|
353
|
+
"language": "en",
|
|
354
|
+
"stage": "in_transit",
|
|
355
|
+
"confirmedBy": "live"
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
"wording": "The NOA has been received",
|
|
359
|
+
"language": "en",
|
|
360
|
+
"stage": "in_transit",
|
|
361
|
+
"confirmedBy": "live",
|
|
362
|
+
"note": "The airline's notice of arrival, after the landing and before the pickup and customs clearance."
|
|
363
|
+
}
|
|
40
364
|
]
|
|
41
365
|
}
|
|
@@ -39,6 +39,8 @@ const ENTRIES = [
|
|
|
39
39
|
['The driver is out for delivery', { status: 'out_for_delivery', stage: 'out_for_delivery' }],
|
|
40
40
|
// GOFO's own scans in the US, read as its codes are: the vehicle is the line haul between its facilities.
|
|
41
41
|
['Arrived at GOFO Regional Hub', { status: 'in_transit', stage: 'in_transit' }],
|
|
42
|
+
['Departed GOFO Regional Hub', { status: 'in_transit', stage: 'in_transit' }],
|
|
43
|
+
['In Transit to Next Facility', { status: 'in_transit', stage: 'in_transit' }],
|
|
42
44
|
['Loaded on Vehicle', { status: 'in_transit', stage: 'in_transit' }],
|
|
43
45
|
['Arrived at GOFO Delivery Station', { status: 'in_transit', stage: 'in_transit' }],
|
|
44
46
|
['Out for Delivery', { status: 'out_for_delivery', stage: 'out_for_delivery' }],
|
|
@@ -152,6 +152,22 @@
|
|
|
152
152
|
"firstSeen": "2026-10-23",
|
|
153
153
|
"note": "Relayed from GOFO in the US, as GOFO reads its own scan."
|
|
154
154
|
},
|
|
155
|
+
{
|
|
156
|
+
"wording": "Departed GOFO Regional Hub",
|
|
157
|
+
"stage": "in_transit",
|
|
158
|
+
"language": "en",
|
|
159
|
+
"confirmedBy": "live",
|
|
160
|
+
"firstSeen": "2026-10-29",
|
|
161
|
+
"note": "Relayed from GOFO in the US, as GOFO reads its own scan."
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"wording": "In Transit to Next Facility",
|
|
165
|
+
"stage": "in_transit",
|
|
166
|
+
"language": "en",
|
|
167
|
+
"confirmedBy": "live",
|
|
168
|
+
"firstSeen": "2026-10-29",
|
|
169
|
+
"note": "Relayed from GOFO: its line haul to the next facility."
|
|
170
|
+
},
|
|
155
171
|
{
|
|
156
172
|
"wording": "Loaded on Vehicle",
|
|
157
173
|
"stage": "in_transit",
|
|
@@ -428,6 +428,9 @@ function providerWordingStage(description) {
|
|
|
428
428
|
// An Post's item waiting at the post office it names (code 52), and GLS Italy's delivery round, as their own maps read them.
|
|
429
429
|
if (/^your delivery is in [^,]{1,60}, post office[.!]?$/i.test(value))
|
|
430
430
|
return 'ready_for_pickup';
|
|
431
|
+
// An item waiting for its recipient at a post office, as ParcelsApp relayed it for one bound for Slovenia.
|
|
432
|
+
if (/^item prepared for pick-up at the post office[.!]?$/i.test(value))
|
|
433
|
+
return 'ready_for_pickup';
|
|
431
434
|
if (/^delivery scheduled during the day[.!]?$/i.test(value))
|
|
432
435
|
return 'out_for_delivery';
|
|
433
436
|
// Latvijas Pasts' acceptance at a post office, which ParcelsApp follows with the office's name.
|
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.828.
|
|
3
|
+
"version": "0.828.3-main.4441",
|
|
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",
|