universal-parcel-scraper 0.969.3-main.5259 → 0.969.4-main.5261
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
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 18,
|
|
166
|
+
The carrier folders record 18,932 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.
|
|
@@ -14,11 +14,29 @@ export const sameInstantIdentityPolicy = {
|
|
|
14
14
|
...translated,
|
|
15
15
|
// A row stored with a label that names no place, without a letter as the parser reads it, is
|
|
16
16
|
// taken over by the row without it. So is one stored at the tracker's "Array", whatever stage
|
|
17
|
-
// the map has given its wording since,
|
|
17
|
+
// the map has given its wording since, an iMile delivery stored with its receiver type, and
|
|
18
|
+
// China Post's scan from the feed that keeps minutes, less than a minute from the other's.
|
|
18
19
|
matches: (incoming, stored) => translated.matches(incoming, stored)
|
|
19
20
|
|| matchesDroppedLocation(incoming, stored, (location) => !/\p{L}/u.test(location))
|
|
20
|
-
|| listPlace(incoming, stored) || receiverCut(incoming, stored),
|
|
21
|
+
|| listPlace(incoming, stored) || receiverCut(incoming, stored) || officeCoded(incoming, stored),
|
|
22
|
+
matchWithinMinute: true,
|
|
21
23
|
};
|
|
24
|
+
// China Post's domestic scans, as the tracker relays them, come from two feeds: one dates them to the
|
|
25
|
+
// minute and places them in the town ("郑州市"), the other to the second, with the office's code after
|
|
26
|
+
// the town ("郑州市 45004400"). The first can word an arrival for the customer, the parcel a 快件 and the
|
|
27
|
+
// centre's slogan after it, where the other has 邮件 and the centre alone.
|
|
28
|
+
const OFFICE_CODE = /^(\S.*?)\s+\d{6,10}$/;
|
|
29
|
+
const POST_SCAN = /^(?:快件|邮件)(到达|离开)(【[^】]{1,80}】)/;
|
|
30
|
+
const postScan = (wording) => POST_SCAN.exec(normalized(wording))?.slice(1).join('');
|
|
31
|
+
function officeCoded(incoming, stored) {
|
|
32
|
+
const [place, saved] = [incoming.location.trim(), stored.location.trim()];
|
|
33
|
+
const [coded, savedCoded] = [OFFICE_CODE.exec(place), OFFICE_CODE.exec(saved)];
|
|
34
|
+
if (!coded === !savedCoded || (coded?.[1] ?? place) !== (savedCoded?.[1] ?? saved))
|
|
35
|
+
return false;
|
|
36
|
+
const scan = postScan(incoming.description);
|
|
37
|
+
return (sameWording(incoming.description, stored.description) || (scan !== undefined && scan === postScan(stored.description)))
|
|
38
|
+
&& (!incoming.providerCode || !stored.providerCode || incoming.providerCode === stored.providerCode);
|
|
39
|
+
}
|
|
22
40
|
function listPlace(incoming, stored) {
|
|
23
41
|
return stored.location.trim() === LIST_PLACE && !incoming.location.trim() && sameWording(incoming.description, stored.description);
|
|
24
42
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { languageStageStatus } from '../../core/status/index.js';
|
|
2
2
|
import { normalizeStatusWording } from '../../core/status/statusMap.js';
|
|
3
3
|
import { chinaPostRelayedStage } from '../china-post/status.js';
|
|
4
|
+
import { jAndTAuMappedStage } from '../j-and-t-au/status.js';
|
|
4
5
|
const WORDING = new Map([
|
|
5
6
|
['Delivered.', { status: 'delivered', stage: 'delivered' }],
|
|
6
7
|
['Shipment out for delivery.', { status: 'out_for_delivery', stage: 'out_for_delivery' }],
|
|
@@ -136,10 +137,18 @@ const DELIVERY_ICON = 'LM40';
|
|
|
136
137
|
const withoutPrefix = (description) => description.replace(/^【[^】]*】\s*/, '').replace(/^[a-z]{2}\.(?=\S)/i, '').trim();
|
|
137
138
|
// A partner's postal event can end with its country's code: "Arrival at Destination Post (Country: TR)".
|
|
138
139
|
const bare = (description) => withoutPrefix(description).replace(/\s*\(Country(?: code)?: [a-z]{2}\)$/i, '');
|
|
140
|
+
// J&T Express Australia's scans after Yanwen's batch handover at the destination port, relayed with its customer
|
|
141
|
+
// service line: read as J&T's own map reads them, but its pickup is Yanwen's handover, not the parcel's acceptance.
|
|
142
|
+
const HOTLINE = /[\s.]*customer service\s*[::]\s*\d[\d\s]*$/i;
|
|
143
|
+
function jAndTAuRelayedStage(wording) {
|
|
144
|
+
const stage = jAndTAuMappedStage(wording.replace(HOTLINE, '.'));
|
|
145
|
+
return stage === 'accepted' ? 'in_transit' : stage;
|
|
146
|
+
}
|
|
139
147
|
// China Post's domestic scans, which Yanwen relays in Chinese with the centre in 【】, read as China Post's own
|
|
140
|
-
// map reads them: "邮件离开【…】,正在发往下一站" in transit, the security check's "【…】退回" an exception.
|
|
148
|
+
// map reads them: "邮件离开【…】,正在发往下一站" in transit, the security check's "【…】退回" an exception. Then J&T Australia's.
|
|
149
|
+
const relayedStage = (normalized) => chinaPostRelayedStage(normalized) ?? jAndTAuRelayedStage(normalized);
|
|
141
150
|
function relayedStatus(description) {
|
|
142
|
-
const stage =
|
|
151
|
+
const stage = relayedStage(normalizeStatusWording(description));
|
|
143
152
|
return stage ? { status: languageStageStatus(stage), stage } : undefined;
|
|
144
153
|
}
|
|
145
154
|
export function yanwenStatus(description, code) {
|
|
@@ -157,7 +166,7 @@ const NOTICES = new Map([
|
|
|
157
166
|
export const yanwenNotice = (description) => NOTICES.has(normalizeStatusWording(withoutPrefix(description)));
|
|
158
167
|
/** What the map says about one scan by its milestone icon, the only code it keeps, else its wording. */
|
|
159
168
|
export const statusMap = {
|
|
160
|
-
stage: (code, wording) => (code === DELIVERY_ICON ? 'delivered' : STAGE_BY_WORDING.get(bare(wording)) ??
|
|
169
|
+
stage: (code, wording) => (code === DELIVERY_ICON ? 'delivered' : STAGE_BY_WORDING.get(bare(wording)) ?? relayedStage(wording)),
|
|
161
170
|
gaps: [
|
|
162
171
|
{ wording: 'ua.', note: "A partner's scan in Ukraine that Yanwen relays as its country's code alone, without words: it names no stage." },
|
|
163
172
|
...[...NOTICES].map(([wording, note]) => ({ wording, note })),
|
|
@@ -675,6 +675,38 @@
|
|
|
675
675
|
"firstSeen": "2026-11-03",
|
|
676
676
|
"note": "China Post's domestic scan, which Yanwen relays in Chinese with the centre in 【】; read as China Post's map reads it."
|
|
677
677
|
},
|
|
678
|
+
{
|
|
679
|
+
"wording": "The parcel has been successfully picked up and is being prepared for transportation.",
|
|
680
|
+
"language": "en",
|
|
681
|
+
"stage": "in_transit",
|
|
682
|
+
"confirmedBy": "live",
|
|
683
|
+
"firstSeen": "2026-11-05",
|
|
684
|
+
"note": "J&T Express Australia's pickup from Yanwen's batch handover at the destination port, relayed with J&T's customer service line after it: a handover, not the parcel's acceptance, which J&T's own history reads it as."
|
|
685
|
+
},
|
|
686
|
+
{
|
|
687
|
+
"wording": "The parcel is dispatched from the transit center to the next destination",
|
|
688
|
+
"language": "en",
|
|
689
|
+
"stage": "in_transit",
|
|
690
|
+
"confirmedBy": "live",
|
|
691
|
+
"firstSeen": "2026-11-05",
|
|
692
|
+
"note": "J&T Express Australia's scan, which Yanwen relays after its batch handover at the destination port; read as J&T's map reads it."
|
|
693
|
+
},
|
|
694
|
+
{
|
|
695
|
+
"wording": "The parcel arrives at the distribution center and is prepared for transport to the next destination.",
|
|
696
|
+
"language": "en",
|
|
697
|
+
"stage": "in_transit",
|
|
698
|
+
"confirmedBy": "live",
|
|
699
|
+
"firstSeen": "2026-11-05",
|
|
700
|
+
"note": "J&T Express Australia's scan, which Yanwen relays after its batch handover at the destination port; read as J&T's map reads it."
|
|
701
|
+
},
|
|
702
|
+
{
|
|
703
|
+
"wording": "Your parcel is out for delivery.",
|
|
704
|
+
"language": "en",
|
|
705
|
+
"stage": "out_for_delivery",
|
|
706
|
+
"confirmedBy": "live",
|
|
707
|
+
"firstSeen": "2026-11-05",
|
|
708
|
+
"note": "J&T Express Australia's scan, which Yanwen relays with J&T's courtesy and customer service line after it; read as J&T's map reads it."
|
|
709
|
+
},
|
|
678
710
|
{
|
|
679
711
|
"wording": "送交出口海关",
|
|
680
712
|
"language": "zh",
|
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.969.
|
|
3
|
+
"version": "0.969.4-main.5261",
|
|
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",
|