universal-parcel-scraper 0.49.2-main.561 → 0.49.2-main.563

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.
@@ -52,7 +52,7 @@
52
52
  "discarded": [
53
53
  "recipient",
54
54
  "sender",
55
- "addresses",
55
+ "recipient_address",
56
56
  "contact_details",
57
57
  "delivery_preferences",
58
58
  "proof_of_delivery"
@@ -16,12 +16,32 @@ function countryCode(value) {
16
16
  const code = clean(value, 8).toUpperCase();
17
17
  return /^[A-Z]{2}$/.test(code) ? code : '';
18
18
  }
19
- /** The pickup point's name, in the first language bpost provides. Its address is never read. */
20
- function pickupPointName(point) {
21
- if (!isRecord(point) || !isRecord(point.name))
19
+ const LANGUAGES = ['en', 'fr', 'nl', 'de'];
20
+ /** A field bpost gives per language, in the first of these languages it provides. */
21
+ function localized(field, maxLength, languages = LANGUAGES) {
22
+ return isRecord(field) ? languages.map((language) => clean(field[language], maxLength)).find(Boolean) ?? '' : '';
23
+ }
24
+ // The steps for which bpost's tracker shows "Collected" at the delivery point
25
+ // instead of "Delivered to" the recipient's address.
26
+ const COLLECTED_STEPS = new Set(['PICKED_UP_IN_POST_OFFICE', 'PICKED_UP_IN_POST_POINT', 'PICKED_UP_IN_KARIBOO_POINT',
27
+ 'PICKED_UP_IN_PARCEL_LOCKER', 'PICKED_UP_IN_SHOP', 'PICKED_UP_IN_PICKUP_POINT_INTERNATIONAL']);
28
+ /**
29
+ * The pickup point's name, then its street and number, then "postcode town",
30
+ * in one language. The name alone when the street or town is missing. Its code
31
+ * and opening hours are not read.
32
+ */
33
+ function pickupPoint(point) {
34
+ if (!isRecord(point))
22
35
  return '';
23
- const names = point.name;
24
- return ['en', 'fr', 'nl', 'de'].map((language) => clean(names[language], 160)).find(Boolean) ?? '';
36
+ const name = localized(point.name, 160);
37
+ const language = LANGUAGES.find((code) => localized(point.street, 120, [code]) && localized(point.municipality, 80, [code]));
38
+ if (!name || !language)
39
+ return name;
40
+ // A number or postcode reads the same in every language.
41
+ const any = [language, ...LANGUAGES];
42
+ const street = [localized(point.street, 120, [language]), localized(point.streetNumber, 16, any)].filter(Boolean).join(' ');
43
+ const town = [localized(point.postcode, 16, any), localized(point.municipality, 80, [language])].filter(Boolean).join(' ');
44
+ return [name, street, town].join('\n');
25
45
  }
26
46
  export function parseBpost(payload, number) {
27
47
  const requested = normalizeBpostNumber(number);
@@ -84,7 +104,12 @@ export function parseBpost(payload, number) {
84
104
  const mapped = classifyBpostStatus(latest.description);
85
105
  const current = returnDelivery && mapped?.stage === 'delivered' ? { status: 'exception', stage: 'returned' } : mapped;
86
106
  const destination = isRecord(item.receiver) ? countryCode(item.receiver.countryCode) : '';
87
- const pickupPoint = current?.stage === 'ready_for_pickup' ? pickupPointName(item.deliveryPoint) : '';
107
+ // The delivery point is the pickup point itself, apart from the receiver: bpost's
108
+ // tracker shows it with opening hours and directions from its own coordinates.
109
+ // It stays once the parcel is collected there, never after a door delivery.
110
+ const collected = current?.stage === 'delivered' && isRecord(item.activeStep)
111
+ && typeof item.activeStep.knownProcessStep === 'string' && COLLECTED_STEPS.has(item.activeStep.knownProcessStep);
112
+ const point = current?.stage === 'ready_for_pickup' || collected ? pickupPoint(item.deliveryPoint) : '';
88
113
  // The sender's barcode is kept only when it is an S10 number other than the one searched.
89
114
  const senderBarcode = clean(item.senderBarcode, 32).toUpperCase();
90
115
  const international = senderBarcode !== requested && isValidS10TrackingNumber(senderBarcode) ? senderBarcode : '';
@@ -97,7 +122,7 @@ export function parseBpost(payload, number) {
97
122
  ...(typeof grams === 'number' && Number.isFinite(grams) && grams > 0 ? { weight_kg: grams / 1000 } : {}),
98
123
  ...(dimensionMatch && dimensionMatch.slice(1).every((value) => Number(value) > 0)
99
124
  ? { dimensions_text: `${dimensionMatch.slice(1).join(' × ')} cm` } : {}),
100
- ...(pickupPoint ? { pickup_point: pickupPoint } : {}),
125
+ ...(point ? { pickup_point: point } : {}),
101
126
  ...(destination ? { destination_country: destination } : {}),
102
127
  ...(international ? { international_tracking_number: international } : {}),
103
128
  events: events.slice(0, 100) };
@@ -70,6 +70,18 @@ function pickupPoint(raw) {
70
70
  }
71
71
  return [name, city].filter(Boolean).join('\n');
72
72
  }
73
+ /**
74
+ * Whether the parcel waits at its pickup point, or was collected there: its
75
+ * last movement before the delivery made it ready for pickup. Notices prove
76
+ * no movement.
77
+ */
78
+ function atPickupPoint(stage, events) {
79
+ if (stage === 'ready_for_pickup')
80
+ return true;
81
+ if (stage !== 'delivered')
82
+ return false;
83
+ return events.find((event) => !['pending', 'delivered'].includes(event.stage ?? ''))?.stage === 'ready_for_pickup';
84
+ }
73
85
  function measurement(raw, units) {
74
86
  if (!isRecord(raw) || typeof raw.unit !== 'string' || !Object.hasOwn(units, raw.unit.toLowerCase()))
75
87
  return null;
@@ -130,7 +142,7 @@ export function parse(payload, trackingNumber) {
130
142
  weight_kg: measurement(measurements.weight, { kg: 1, g: 0.001 }),
131
143
  dimensions_text: dimensions.every((value) => value !== null) ? `${dimensions.join(' × ')} cm` : null,
132
144
  // Posti's tracker shows the point at any status, a planned one in transit included.
133
- pickup_point: current?.stage === 'ready_for_pickup' ? pickupPoint(hit.pickupPoint) : null,
145
+ pickup_point: atPickupPoint(current?.stage, events) ? pickupPoint(hit.pickupPoint) : null,
134
146
  timezone: 'Europe/Helsinki',
135
147
  events,
136
148
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "universal-parcel-scraper",
3
- "version": "0.49.2-main.561",
3
+ "version": "0.49.2-main.563",
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",