universal-parcel-scraper 0.49.2-main.565 → 0.49.2-main.567

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.
@@ -2,7 +2,7 @@ import { DateTime } from 'luxon';
2
2
  import { isValidS10TrackingNumber, normalizeTrackingNumber } from '../../core/detection/index.js';
3
3
  import { IndeterminateError, InvalidInputError, SchemaError } from '../../core/errors/index.js';
4
4
  import { calendarDay, explicitOffsetTime } from '../../core/time/index.js';
5
- import { clean } from '../../core/transport/index.js';
5
+ import { clean, cleanScalar } from '../../core/transport/index.js';
6
6
  import { isRecord } from '../../core/types.js';
7
7
  import { bringStatus, bringWording } from './status.js';
8
8
  /**
@@ -64,6 +64,18 @@ function estimatedDay(domain) {
64
64
  const day = /^(\d{4})-(\d{2})-(\d{2})(?:$|T)/.exec(eta.dateOfEstimatedDeliveryIso);
65
65
  return day ? calendarDay(Number(day[1]), Number(day[2]), Number(day[3])) : null;
66
66
  }
67
+ /**
68
+ * Whether the parcel was collected at the pickup point the portal names: the
69
+ * delivery, and the movement before it that made the parcel ready for pickup,
70
+ * were scanned at that point's unit. Notices prove no movement.
71
+ */
72
+ function collectedAt(rows, pickupUnit) {
73
+ const unit = cleanScalar(pickupUnit, 32);
74
+ const moves = rows.filter(row => bringStatus(String(row.status), text(row.lmCauseCode, 8)));
75
+ const index = moves.findIndex(row => row.status !== 'DELIVERED');
76
+ return Boolean(unit) && index > 0 && moves[index].status === 'READY_FOR_PICKUP'
77
+ && moves.slice(0, index + 1).every(row => cleanScalar(row.unitId, 32) === unit);
78
+ }
67
79
  export function parseBring(payload, rawNumber) {
68
80
  const number = normalizeBringNumber(rawNumber);
69
81
  if (!isRecord(payload))
@@ -126,8 +138,9 @@ export function parseBring(payload, rawNumber) {
126
138
  const sender = text(consignment.senderName, 200) || text(parcel.senderName, 200);
127
139
  const pickupInfo = isRecord(parcel.domain.deliveryType) && isRecord(parcel.domain.deliveryType.pickupPointInfo)
128
140
  ? parcel.domain.deliveryType.pickupPointInfo : {};
129
- const pickup = current?.stage === 'ready_for_pickup'
130
- ? text(parcel.expectedPickupUnitName, 200) || text(pickupInfo.expectedPickupUnitName, 200) : '';
141
+ const atPickupPoint = current?.stage === 'ready_for_pickup'
142
+ || (current?.stage === 'delivered' && collectedAt(parcel.eventSet.filter(isRecord), parcel.expectedPickupUnitId));
143
+ const pickup = atPickupPoint ? text(parcel.expectedPickupUnitName, 200) || text(pickupInfo.expectedPickupUnitName, 200) : '';
131
144
  const country = isRecord(consignment.recipientAddress) ? consignment.recipientAddress.countryCode : undefined;
132
145
  const active = current && ['pending', 'in_transit', 'out_for_delivery'].includes(current.status) && current.stage !== 'ready_for_pickup';
133
146
  return { status: current?.status ?? 'unknown', ...(current ? { current_stage: current.stage } : {}),
@@ -11,6 +11,36 @@ export function normalizePostnordNumber(raw) {
11
11
  }
12
12
  return number;
13
13
  }
14
+ /**
15
+ * The scan that made the parcel available at its service point, while it waits
16
+ * there or once it is collected there: the delivery, scanned at that same point,
17
+ * came right after it. A parcel taken back out for delivery has a movement between.
18
+ */
19
+ function atPickupPoint(stage, events) {
20
+ if (stage === 'ready_for_pickup')
21
+ return events.find((event) => event.stage === 'ready_for_pickup');
22
+ if (stage !== 'delivered')
23
+ return undefined;
24
+ const index = events.findIndex((event) => event.stage !== 'delivered');
25
+ const ready = events[index];
26
+ return index > 0 && ready?.stage === 'ready_for_pickup' && ready.location
27
+ && events.slice(0, index).every((event) => event.location === ready.location) ? ready : undefined;
28
+ }
29
+ /**
30
+ * The service point's name, then its street and "postcode town", from the point
31
+ * record the tracking widget shows under a parcel ready for pickup. Without that
32
+ * record the scan's name stands alone. The receiver's address is a separate record.
33
+ */
34
+ function pickupPoint(raw, scanned) {
35
+ const point = isRecord(raw) ? raw : {};
36
+ const address = isRecord(point.address) ? point.address : {};
37
+ const name = clean(point.name, 160);
38
+ if (!name)
39
+ return scanned;
40
+ const street = clean(address.street, 120);
41
+ const town = clean(address.city, 80);
42
+ return street && town ? [name, street, [clean(address.postalCode, 16), town].filter(Boolean).join(' ')].join('\n') : name;
43
+ }
14
44
  export function parsePostnord(payload, number) {
15
45
  const requested = normalizePostnordNumber(number);
16
46
  if (!isRecord(payload) || payload.shipmentId !== requested || payload.actualReturnedId !== requested
@@ -69,8 +99,8 @@ export function parsePostnord(payload, number) {
69
99
  // The portal names the sender, usually the shop; of the receiver's address only the country is read.
70
100
  const sender = isRecord(payload.sender) ? clean(payload.sender.name, 200) : '';
71
101
  const country = isRecord(payload.receiver) && isRecord(payload.receiver.address) ? payload.receiver.address.countryCode : undefined;
72
- // The service point holding the parcel, as the scan that made it available names it.
73
- const pickup = mapped?.stage === 'ready_for_pickup' ? events.find((event) => event.stage === 'ready_for_pickup')?.location : undefined;
102
+ const pickupScan = atPickupPoint(mapped?.stage, events);
103
+ const pickup = pickupScan ? pickupPoint(payload.servicePoint, pickupScan.location ?? '') : '';
74
104
  return { status: mapped?.status ?? 'unknown', ...(mapped ? { current_stage: mapped.stage } : {}),
75
105
  last_status_text: header || events[0].description, last_update: events[0].time,
76
106
  expected_delivery: null, ...(delivery?.time ? { delivered_at: delivery.time } : {}),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "universal-parcel-scraper",
3
- "version": "0.49.2-main.565",
3
+ "version": "0.49.2-main.567",
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",