universal-parcel-scraper 0.802.0-main.4307 → 0.802.2-main.4316

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
@@ -161,7 +161,7 @@ the command line's `track --source ParcelsApp` does the same.
161
161
  <!-- GENERATED:stages -->
162
162
  <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">
163
163
 
164
- The carrier folders record 17,596 such statuses, each filed under one stage.
164
+ The carrier folders record 17,602 such statuses, each filed under one stage.
165
165
  <!-- /GENERATED:stages -->
166
166
 
167
167
  Wording nobody recorded yet goes through a classifier that reads seven European languages.
@@ -32348,7 +32348,7 @@ function Ml(e) {
32348
32348
  if (/will be transported|\barrange (?:a )?(?:truck|train|rail|road) transfer\b|sera (?:transporte|achemine)|wird.*transportiert|sara trasportat/.test(t) || /will be available|sera disponible|(?:sera|va etre|doit etre) (?:distribue|remis|mis en (?:livraison|distribution))|wird.*verfugbar|sara disponibile|to be delivered|a livrer|noch zuzustellen|da consegnare|(?:once|when|as soon as) (?:the |your )?(?:shipment|parcel|package|item) is out for delivery/.test(t) || /\bestara disponi(?:ble|vel)\b|\bpor entregar\b|\bpendiente de (?:entrega|entregar|reparto)\b|\bpendente de entrega\b|\b(?:aguarda|aguardando|a aguardar) (?:a )?entrega\b|\bsera (?:enviad|transportad|expedid|encaminhad)[oa]s?\b/.test(t)) return "in_transit";
32349
32349
  if (/\border (?:details|information|info|data) (?:(?:has|have) been |was |were )?received\b(?:[^.;]*)\bready (?:for|to be) (?:pick ?up|picked up|collect)/.test(t)) return "registered";
32350
32350
  if (/ready for (?:pick ?up|collection)|ready to collect|ready to be (?:picked up|collected)\b(?! (?:by|from) (?:the |a |an |our |your )?(?:courier|carrier|driver|sender|shipper|seller|merchant|warehouse)\b)|available for (?:pick ?up|collection)|(?<!\bnot (?:yet )?)\bavailable (?:at|in|from) (?:the |a |an |your |our )?(?:[\w-]+ )?(?:retrieval point|pick ?up (?:point|location|station)|parcel ?shop|service ?point|access point|collection point|relay point|parcel locker)\b|\bwaiting for you (?:at|in) (?:the |a |an |your )?(?:[\w-]+ )?(?:collection point|pick ?up (?:point|location|station)|parcel ?shop|service ?point|access point|relay point|parcel locker|locker|post office)\b|awaiting (?:customer |recipient |your )?(?:pick ?up|collection)\b(?! (?:by|from) (?:the |a |an |our |your )?(?:local |destination |last[- ]mile )?(?:courier|carrier|driver|sender|shipper|seller|merchant|warehouse)\b)|pret.*(?:retrait|retire)|disponible.*(?:retrait|collecte)|vous attend (?:au|en|dans|chez|a)\b|mise? a disposition (?:au|en|dans|a)\b.*(?:point (?:de )?retrait|relais|consigne|locker|bureau de poste|agence)|jours? restants? pour (?:le )?retirer|abholbereit|zur abholung bereit|pront[oa] per il ritiro|disponibile per il ritiro|(?:deposited|depose|hinterlegt|depositat[oa]).*mypost24/.test(t) || /(?<!\b(?:no|nao|ya no|ja nao) (?:(?:esta|estara|se encuentra|se encontra|e|era|foi) )?)\bdisponi(?:ble|vel) (?:para (?:su |ser )?(?:recoger|recogida|recogid[oa]|retirar|retiro|retirada|retirad[oa]|levantamento|levantar|levantad[oa]|recolha|recolher)|(?:en|no|na|num|numa|em) (?:el |la |un |una |su |tu |nuestr[oa] )?(?:punto|oficina|tienda|locker|taquilla|consigna|citypaq|parcel ?shop|buzon inteligente|agencia|delegacion|sucursal|ponto|loja|cacifo|estacao|balcao|payshop|posto|unidade))\b|\blist[oa] para (?:su |ser )?(?:recoger|recogida|recogid[oa]|retirar|retiro)\b|\bpront[oa] (?:para|a) (?:ser )?(?:levantamento|levantar|levantad[oa]|recolha|recolher|retirada|retirar)\b|\b(?:ya )?(?:puede|puedes|podra|podras) (?:pasar a )?recoger(?:lo|la)?\b|\b(?:aguarda|aguardando|a aguardar) (?:o |a )?(?:levantamento|retirada)\b|\besperando (?:su|tu) recogida\b|\b(?:pendiente de|esperando|a la espera de) (?:ser )?recogid[oa] por (?:el |la )?destinatari[oa]\b/.test(t)) return "ready_for_pickup";
32351
- if (/label (?:created|printed)|(?:created|generated) a (?:new )?(?:shipment |shipping )?label|etiquette.*(?:cree|imprime)|cree une etiquette|versandetikett.*(?:erstellt|gedruckt)|etikett erstellt|etichetta.*(?:creata|stampata)|creato un'etichetta|elektronisch|electroni(?:c|que|sch)|elettronic|pre.?advi[cs]|preannunciat|vorangemeldet|preannonce|data.*entered|donnees.*saisies|daten.*erfasst|dati.*inseriti|\bpre ?(?:registered|registration|registr?ad[oa]s?|registrat[oa]|enregistree?s?)\b|\bprerregistr(?:ad[oa]s?|o)\b|\bvorregistriert\b|\bwstepnie zarejestrowan[aey]\b/.test(t) || /\border (?:details|information|info|data) (?:(?:has|have) been |was |were )?(?:received|submitted|transmitted)\b|\breceived (?:your |the )?order (?:details|information|data)\b|\b(?:details|informations|donnees) de (?:la |votre )?commande (?:ont ete )?(?:recu|transmis)e?s?\b|\b(?:auftrags|bestell)daten (?:wurden |sind )?(?:erhalten|ubermittelt|eingegangen)\b|\b(?:dati|dettagli) (?:dell'ordine|ordine) (?:sono stati )?ricevuti\b|\b(?:datos|detalles|informacion) del pedido (?:han sido |fueron )?recibid[oa]s?\b|\b(?:dados|detalhes|informacoes?) d[oa] (?:pedido|encomenda) (?:foram )?recebid[oa]s?\b|\bdane zamowienia (?:zostaly )?(?:otrzymane|przyjete)\b|\botrzymano dane zamowienia\b/.test(t) || /shipment information (?:received|sent to)|\binformation about the (?:shipment|parcel|package) (?:has been |was )?received\b|\bdata received\b|^transferred data$|(?:package|parcel|shipment) data (?:was |has been )?sent to|\bpre ?notification\b|informations d'expedition recues|sendungsinformationen erhalten|informazioni.*spedizione ricevute|(?:consignment|shipment|parcel|package) recorded by|envoi enregistre par|sendung.*absender.*erfasst|spedizione registrata dal/.test(t) || /\b(?:we're|we are) expecting (?:your|the) (?:parcel|package|item|shipment) to arrive with us\b/.test(t) || /\bcreated by the sender\b.*\bwaiting for it to be (?:entrusted|handed(?: over)?) to us\b/.test(t) || /^pre[ -]?alerted (?:shipment|parcel|package|item|consignment)\b|^(?:the |your )?(?:shipment|parcel|package|item|consignment) (?:(?:has been|was|is) )?pre[ -]?alerted$/.test(t) || /^expected from (?!\d{1,2}(?:[:.h]\d{2})?\s*(?:am|pm)?\b|(?:today|tomorrow|(?:mon|tues|wednes|thurs|fri|satur|sun)day)\b)\S/.test(t) || /\bletrejott a rendszerunkben\b.*\bfelado meg nem adta at\b/.test(t) || /preparation chez.*expediteur|preparation.*expediteur|preparazione.*mittente|being prepared.*sender|(?:shipper|sender)(?: that)? (?:they are|is|are) preparing|beim absender.*vorbereitet|warehouse of the sender|shippers warehouse|entrepot de l'expediteur|lager des absenders|magazzino del mittente|demande d'envoi.*prise en compte|collection request.*(?:received|recorded)|^(?:the |your )?collection (?:has been |was )?registered$|abholauftrag.*erfasst|richiesta.*ritiro.*registrata/.test(t) || /\border submitted(?:,| and)? pending (?:to )?rece(?:iv|ipt)|\bshipment data uploaded\b|^sender preparing item\b|^sender (?:has despatched|despatching) item$|^shipping info(?:rmation)? sent to (?!(?:the |your? )?(?:recipient|customer|consignee|addressee|buyer)\b)|\bwe have received your shipment information\b|\bhemos recibido la informacion de su envio\b|\bwe will pick up (?:the|your) (?:package|parcel) from the sender\b|^booked for collection$|\bpending shipping by (?:the )?(?:seller|sender|shipper|merchant)\b|\bshipment order created\b/.test(t) || /^forecast has been received$/.test(t)) return "registered";
32351
+ if (/label (?:created|printed)|(?:created|generated) a (?:new )?(?:shipment |shipping )?label|etiquette.*(?:cree|imprime)|cree une etiquette|versandetikett.*(?:erstellt|gedruckt)|etikett erstellt|etichetta.*(?:creata|stampata)|creato un'etichetta|elektronisch|electroni(?:c|que|sch)|elettronic|pre.?advi[cs]|preannunciat|vorangemeldet|preannonce|data.*entered|donnees.*saisies|daten.*erfasst|dati.*inseriti|\bpre ?(?:registered|registration|registr?ad[oa]s?|registrat[oa]|enregistree?s?)\b|\bprerregistr(?:ad[oa]s?|o)\b|\bvorregistriert\b|\bwstepnie zarejestrowan[aey]\b/.test(t) || /\border (?:details|information|info|data) (?:(?:has|have) been |was |were )?(?:received|submitted|transmitted)\b|\breceived (?:your |the )?order (?:details|information|data)\b|\b(?:details|informations|donnees) de (?:la |votre )?commande (?:ont ete )?(?:recu|transmis)e?s?\b|\b(?:auftrags|bestell)daten (?:wurden |sind )?(?:erhalten|ubermittelt|eingegangen)\b|\b(?:dati|dettagli) (?:dell'ordine|ordine) (?:sono stati )?ricevuti\b|\b(?:datos|detalles|informacion) del pedido (?:han sido |fueron )?recibid[oa]s?\b|\b(?:dados|detalhes|informacoes?) d[oa] (?:pedido|encomenda) (?:foram )?recebid[oa]s?\b|\bdane zamowienia (?:zostaly )?(?:otrzymane|przyjete)\b|\botrzymano dane zamowienia\b/.test(t) || /shipment information (?:received|sent to)|\binformation about the (?:shipment|parcel|package) (?:has been |was )?received\b|\bdata received\b|^transferred data$|(?:package|parcel|shipment) data (?:was |has been )?sent to|\bpre ?notification\b|informations d'expedition recues|sendungsinformationen erhalten|informazioni.*spedizione ricevute|(?:consignment|shipment|parcel|package) recorded by|envoi enregistre par|sendung.*absender.*erfasst|spedizione registrata dal/.test(t) || /\b(?:we're|we are) expecting (?:your|the) (?:parcel|package|item|shipment) to arrive with us\b/.test(t) || /\bcreated by the sender\b.*\bwaiting for it to be (?:entrusted|handed(?: over)?) to us\b/.test(t) || /^pre[ -]?alerted (?:shipment|parcel|package|item|consignment)\b|^(?:the |your )?(?:shipment|parcel|package|item|consignment) (?:(?:has been|was|is) )?pre[ -]?alerted$/.test(t) || /^expected from (?!\d{1,2}(?:[:.h]\d{2})?\s*(?:am|pm)?\b|(?:today|tomorrow|(?:mon|tues|wednes|thurs|fri|satur|sun)day)\b)\S/.test(t) || /\bletrejott a rendszerunkben\b.*\bfelado meg nem adta at\b/.test(t) || /preparation chez.*expediteur|preparation.*expediteur|preparazione.*mittente|being prepared.*sender|(?:shipper|sender)(?: that)? (?:they are|is|are) preparing|beim absender.*vorbereitet|warehouse of the sender|shippers warehouse|entrepot de l'expediteur|lager des absenders|magazzino del mittente|demande d'envoi.*prise en compte|collection request.*(?:received|recorded)|^(?:the |your )?collection (?:has been |was )?registered$|abholauftrag.*erfasst|richiesta.*ritiro.*registrata/.test(t) || /\border submitted(?:,| and)? pending (?:to )?rece(?:iv|ipt)|\bshipment data uploaded\b|^sender preparing item\b|^sender (?:has despatched|despatching) item$|^shipping info(?:rmation)? sent to (?!(?:the |your? )?(?:recipient|customer|consignee|addressee|buyer)\b)|\bwe have received your shipment information\b|\bhemos recibido la informacion de su envio\b|\bwe will pick up (?:the|your) (?:package|parcel) from the sender\b|^booked for collection$|\bpending shipping by (?:the )?(?:seller|sender|shipper|merchant)\b|\bship(?:ment|ping) order created\b/.test(t) || /^forecast has been received$/.test(t)) return "registered";
32352
32352
  if (!/\b(?:at|in|by) (?:the )?(?:destination|delivery|recipient|consignee|addressee|customer|customs|office of exchange)\b/.test(t)) {
32353
32353
  let e = t.replace(sl, " ");
32354
32354
  if (e !== t) return Ml(e.replace(/^[\s,;:.!?]+|[\s,;:]+$/g, "")) ?? (fl.test(t) ? !pl.test(t) && ml.test(t) ? "in_transit" : "customs" : "registered");
@@ -32368,9 +32368,9 @@ function Ml(e) {
32368
32368
  if (/^parcel stored by (?:the )?customer$/.test(t)) return "accepted";
32369
32369
  if (/^scan out hub$/.test(t)) return "in_transit";
32370
32370
  if (/^attempted but unfinished drop[- ]?off at (?:the |a )?locker$/.test(t)) return "registered";
32371
- if (/^(?:(?:the |your )?(?:item|parcel|package|shipment) )?(?:(?:has been|was) )?received by (?:the |a |our )?partner carrier$/.test(t)) return "in_transit";
32371
+ if (/^(?:(?:the |your )?(?:item|parcel|package|shipment) )?(?:(?:has been|was) )?received by (?:the |a |our )?partner carrier$|^(?:(?:the |your )?(?:item|parcel|package|shipment) )?received by in [a-z]{3}\d{2}\.?$/.test(t)) return "in_transit";
32372
32372
  if (/^operation cent(?:er|re) is processing (?:the )?(?:package|parcel|shipment) information$/.test(t)) return "registered";
32373
- if (/^preparing to leave (?:the )?(?:[a-z]+ ){0,3}operation cent(?:er|re)$/.test(t)) return "in_transit";
32373
+ if (/^(?:preparing to leave|(?:the )?goods leav(?:e|ing)) (?:the )?(?:[a-z]+ ){0,3}operation cent(?:er|re)$/.test(t)) return "in_transit";
32374
32374
  if (/^(?:the )?item is ready for shipment$/.test(t)) return "registered";
32375
32375
  if (/^consignment received at (?:the )?postnl acceptance cent(?:re|er)$/.test(t)) return "accepted";
32376
32376
  if (/^shipment expected,? but not yet arrived or processed at postnl$/.test(t)) return "registered";
@@ -32380,7 +32380,7 @@ function Ml(e) {
32380
32380
  if (/^(?:received in office|export to foreign provider)$/.test(t) || /^(?:(?:the )?(?:shipment|parcel|package|item) )?(?:(?:has been|was) )?(?:sent|dispatched|forwarded) abroad$/.test(t) || /^sending (?:the )?(?:package|parcel|item|shipment) from (?!.*\b(?:point|locker|shop|sender|branch)\b)[a-z][a-z .'-]{1,40}$/.test(t)) return "in_transit";
32381
32381
  if (/^(?:posting\/collection|posted|origin scan)$|received at (?:our |the )?(?:[a-z]+ )?origin (?:office|facility|hub)|dropped off (?:after|before) (?:the )?latest drop ?off time/.test(t)) return "accepted";
32382
32382
  if (/(?:\b(?:not|never)|n't)(?: (?:be|been|being))? accepted\b/.test(t)) return "exception";
32383
- if (/accepted|picked up|pick up was successful|^drop off$|pick ?up scan|prepared the package for return to ups or pickup by ups|handed (?:over )?to (?:dpd|gls)|package received at dhl|item booked|consignment was mailed|posted at a postal point|pris en charge|prise en charge|collecte.*(?:reussi|effectue)|recupere.*boite aux lettres|depose.*point postal|remis a (?:dpd|gls)|envoi.*depose|abholung.*erfolgreich|^(?:die sendung wurde )?erfolgreich abgeholt$|eingeliefert|an (?:dpd|gls) ubergeben|paket.*(?:angenommen|ubernommen)|ritiro.*(?:riuscito|effettuato)|pres[oa] in carico|affidat[oa] a (?:dpd|gls)|depositat.*punto postale|spedizione.*impostata/.test(t) || /^item received by royal mail$|\btaken over by one of our operators\b|\bwe've received your (?:parcel|package|item) at the parcel ?shop\b|^(?:(?:the |your )?(?:item|parcel|package|shipment) )?(?:was|has been) dropped off at (?:the |a )?parcel ?shop$|^(?![^,]*\b(?:recipient|consignee|receiver|customer|addressee|buyer|pick ?up point|parcel ?shop|locker|destination)\b)[a-z][\w&' -]{0,40} has received the goods(?:, [^,]{1,40}){0,2}\.?$|^the shipment has been deposited at a seur pickup point$|^(?:the |your )?collection (?:has been |was )?(?:carried out|completed)$|^已入库|^(?:(?:the |your )?(?:item|parcel|package|shipment) )?(?:has been |was )?put into storage$/.test(t) || /^sender advised item dropped off at parcel locker$|^dropped off at postbox$/.test(t) || /^(?:the )?shipment has been taken over at the post (?:office|locker) point$/.test(t) || /^spedizione affidata a una fedex location\b/.test(t) || /^die sendung wurde vom absender (?:im|in der|in einem|in einer) (?:[\w-]+ )?(?:paketshop|parcelshop|filiale)\b(?!.*\bnicht\b).*\babgegeben$/.test(t) || /^(?:the |your )?(?:package|parcel|shipment) (?:was|has been) dropped off by the sender at (?:the |a )?(?:hermes )?(?:paketshop|parcel ?shop)\b/.test(t) || /^sending the (?:package|parcel) at the pick ?up point$|^handing over the (?:package|parcel) to the courier$/.test(t)) return "accepted";
32383
+ if (/accepted|picked up|pick up was successful|^drop off$|pick ?up scan|prepared the package for return to ups or pickup by ups|handed (?:over )?to (?:dpd|gls)|package received at dhl|item booked|consignment was mailed|posted at a postal point|pris en charge|prise en charge|collecte.*(?:reussi|effectue)|recupere.*boite aux lettres|depose.*point postal|remis a (?:dpd|gls)|envoi.*depose|abholung.*erfolgreich|^(?:die sendung wurde )?erfolgreich abgeholt$|eingeliefert|an (?:dpd|gls) ubergeben|paket.*(?:angenommen|ubernommen)|ritiro.*(?:riuscito|effettuato)|pres[oa] in carico|affidat[oa] a (?:dpd|gls)|depositat.*punto postale|spedizione.*impostata/.test(t) || /^到达收货点|^item received by royal mail$|\btaken over by one of our operators\b|\bwe've received your (?:parcel|package|item) at the parcel ?shop\b|^(?:(?:the |your )?(?:item|parcel|package|shipment) )?(?:was|has been) dropped off at (?:the |a )?parcel ?shop$|^(?![^,]*\b(?:recipient|consignee|receiver|customer|addressee|buyer|pick ?up point|parcel ?shop|locker|destination)\b)[a-z][\w&' -]{0,40} has received the goods(?:, [^,]{1,40}){0,2}\.?$|^the shipment has been deposited at a seur pickup point$|^(?:the |your )?collection (?:has been |was )?(?:carried out|completed)$|^已入库|^(?:(?:the |your )?(?:item|parcel|package|shipment) )?(?:has been |was )?put into storage$/.test(t) || /^sender advised item dropped off at parcel locker$|^dropped off at postbox$/.test(t) || /^(?:the )?shipment has been taken over at the post (?:office|locker) point$/.test(t) || /^spedizione affidata a una fedex location\b/.test(t) || /^die sendung wurde vom absender (?:im|in der|in einem|in einer) (?:[\w-]+ )?(?:paketshop|parcelshop|filiale)\b(?!.*\bnicht\b).*\babgegeben$/.test(t) || /^(?:the |your )?(?:package|parcel|shipment) (?:was|has been) dropped off by the sender at (?:the |a )?(?:hermes )?(?:paketshop|parcel ?shop)\b/.test(t) || /^sending the (?:package|parcel) at the pick ?up point$|^handing over the (?:package|parcel) to the courier$/.test(t)) return "accepted";
32384
32384
  if (/^acceptance of the (?:package|parcel) at the delivery branch abroad$/.test(t)) return "in_transit";
32385
32385
  if (/^acceptance of the (?:package|parcel) at the pick ?up point$/.test(t)) return "ready_for_pickup";
32386
32386
  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";
@@ -65,6 +65,8 @@ const STATES = {
65
65
  '待取件': { status: 'in_transit', stage: 'ready_for_pickup' },
66
66
  '已签收': DELIVERED,
67
67
  '派件异常': EXCEPTION,
68
+ // The web preview's label for a failed delivery, which the relayed feeds give as 投递失败.
69
+ '派送异常': { status: 'exception', stage: 'failed_attempt' },
68
70
  '邮件异常': EXCEPTION,
69
71
  '退回寄件人': RETURNED,
70
72
  '待退回': RETURNED,
@@ -356,6 +356,15 @@
356
356
  "firstSeen": "2026-10-08",
357
357
  "note": "State label from the app's own state table; consulted only for scan codes without a mapping. The English is a plain rendering."
358
358
  },
359
+ {
360
+ "wording": "派送异常",
361
+ "language": "zh",
362
+ "english": "Delivery failed",
363
+ "stage": "failed_attempt",
364
+ "confirmedBy": "live",
365
+ "firstSeen": "2026-10-28",
366
+ "note": "State label of the web preview's newest scan on an item whose relayed feeds give its latest scans as failed deliveries (投递失败); consulted only for scan codes without a mapping. The English is a plain rendering."
367
+ },
359
368
  {
360
369
  "wording": "邮件异常",
361
370
  "language": "zh",
@@ -71,6 +71,13 @@ const VERIFIED_WORDING = {
71
71
  "we've received your order details, and we're expecting your parcel shortly": 'registered',
72
72
  'your parcel has been dropped off at a dpd pickup shop': 'accepted',
73
73
  };
74
+ // A parcel its sender hands in at a Pickup shop: the order record of a label that waits for it, and
75
+ // the drop-off. The account that booked such a label did not send it; on a return or donation label
76
+ // it is the recipient.
77
+ const HANDED_IN = new Set([
78
+ "we've received your order details, and we're expecting your parcel shortly",
79
+ 'your parcel has been dropped off at a dpd pickup shop',
80
+ ]);
74
81
  // A round that missed the recipient, with the day of the next one.
75
82
  const MISSED = /^we missed you\. your driver won't be back today but we'll be back [a-z]{3} \d{1,2} [a-z]+ with your parcel$/;
76
83
  /**
@@ -159,6 +166,8 @@ export function parseDpdUkHistory(detailPayload, historyPayload, raw, code) {
159
166
  return true;
160
167
  }).slice(0, 100);
161
168
  const latest = retained[0];
169
+ // A parcel handed in by its sender names none: null drops the account an earlier check named.
170
+ const handedIn = events.some(event => HANDED_IN.has(dpdUkWording(event.description)));
162
171
  const summary = publicDescription(detail.trackingStatusCurrent);
163
172
  const summaryClassification = classifyDpdUkWording(summary);
164
173
  const current = summary ? summaryClassification : classifyDpdUkWording(latest.description);
@@ -166,6 +175,6 @@ export function parseDpdUkHistory(detailPayload, historyPayload, raw, code) {
166
175
  ...(current.source !== 'none' ? { current_stage: current.stage, current_stage_source: current.source } : {}),
167
176
  last_status_text: summary || latest.description, last_update: latest.time ?? null,
168
177
  ...(latest.local_time ? { last_update_local: latest.local_time } : {}),
169
- ...(sender ? { sender_name: sender } : {}),
178
+ ...(handedIn ? { sender_name: null } : sender ? { sender_name: sender } : {}),
170
179
  ...(current.stage === 'delivered' && latest.stage === 'delivered' && latest.time ? { delivered_at: latest.time } : {}), events: retained };
171
180
  }
@@ -6,7 +6,12 @@ import { normalizeStatusWording } from '../../core/status/statusMap.js';
6
6
  * out with a courier or waiting at a post office.
7
7
  */
8
8
  export const ELTA_SCANS = {
9
+ // The sender's label, then the item handed in at a post office, which the shared rules read as delivered.
10
+ 'Label printed by customer': 'registered',
11
+ 'Item deposited at ELTA': 'accepted',
9
12
  'Shipment in transit': 'in_transit',
13
+ // At Athens airport's hub, before the item leaves Greece.
14
+ 'Arrival at the airport': 'in_transit',
10
15
  'With Courier for delivery': 'out_for_delivery',
11
16
  // The courier's attempt: the notice left and its reason, at the same minute.
12
17
  'Notification of attempted delivery': 'failed_attempt',
@@ -1,6 +1,9 @@
1
1
  {
2
2
  "carrier": "elta",
3
3
  "entries": [
4
+ {"wording": "Label printed by customer", "stage": "registered", "language": "en", "confirmedBy": "live", "note": "The sender's own label, before ELTA has the item."},
5
+ {"wording": "Item deposited at ELTA", "stage": "accepted", "language": "en", "confirmedBy": "live", "note": "The sender handed the item in at the post office the scan names, minutes after the label; then it goes on in transit."},
6
+ {"wording": "Arrival at the airport", "stage": "in_transit", "language": "en", "confirmedBy": "live", "note": "At Athens airport's hub, after the domestic legs of an item leaving Greece."},
4
7
  {"wording": "Shipment in transit", "stage": "in_transit", "language": "en", "confirmedBy": "live", "note": "Sent on from the station the scan names, from the sender's agency onwards."},
5
8
  {"wording": "Arrival in", "stage": "in_transit", "language": "en", "confirmedBy": "live", "note": "The tracker follows it with the name of the station the scan names; the map reads the opening words."},
6
9
  {"wording": "With Courier for delivery", "stage": "out_for_delivery", "language": "en", "confirmedBy": "live"},
@@ -102,12 +102,16 @@ export function parseIntelcom(payload, rawNumber) {
102
102
  const events = history.map(scan);
103
103
  if (!events.length && !latest.time)
104
104
  throw new IndeterminateError(PROVIDER, 'Intelcom returned no dated shipment activity');
105
+ // A return collected from the customer goes to the client, which is then its recipient, not its sender:
106
+ // null drops the client an earlier check named.
107
+ const collected = [latestRaw, ...history].some(row => isRecord(row) && row.task_type === 'last_mile_pickup');
108
+ const client = typeof result.client_code === 'string' && Object.hasOwn(SHOWN_CLIENTS, result.client_code) ? SHOWN_CLIENTS[result.client_code] : undefined;
105
109
  const output = { status: latest.stage ? languageStageStatus(latest.stage) : 'unknown',
106
110
  last_status_text: latest.description, last_update: latest.time ?? null,
107
111
  ...(latest.stage ? { current_stage: latest.stage, current_stage_source: latest.stage_source } : {}),
108
112
  ...(latest.stage === 'delivered' && latest.time ? { delivered_at: latest.time } : {}),
109
113
  ...estimate(result, latestRaw, latest.time ? Date.parse(latest.time) : null),
110
- ...(typeof result.client_code === 'string' && Object.hasOwn(SHOWN_CLIENTS, result.client_code) ? { sender_name: SHOWN_CLIENTS[result.client_code] } : {}),
114
+ ...(collected ? { sender_name: null } : client ? { sender_name: client } : {}),
111
115
  ...(events.length ? { events: events.slice(0, 100) } : { summary_only: true, events: [] }) };
112
116
  return output;
113
117
  }
@@ -49,11 +49,13 @@ export function postNLStatus(category, description) {
49
49
  }
50
50
  /**
51
51
  * Labels of PostNL's domestic tracker seen on parcels, lowercase without final
52
- * punctuation: those the shared wording rules read wrong, and its sorting scan.
52
+ * punctuation: those the shared wording rules read wrong, and its pre-advice and
53
+ * sorting scans.
53
54
  * Its observations carry no category.
54
55
  */
55
56
  const DOMESTIC_STAGE = new Map([
56
57
  ['shipment expected, but not yet arrived or processed at postnl', 'registered'],
58
+ ['not yet processed by postnl', 'registered'],
57
59
  ['shipment received by postnl', 'accepted'],
58
60
  ['driver is en route', 'out_for_delivery'],
59
61
  ['shipment sorted in sorting centre', 'in_transit'],
@@ -232,6 +232,14 @@
232
232
  "firstSeen": "2026-10-24",
233
233
  "note": "A label of PostNL's domestic tracker; the shared wording rules read it as in transit."
234
234
  },
235
+ {
236
+ "wording": "Not yet processed by PostNL",
237
+ "language": "en",
238
+ "stage": "registered",
239
+ "confirmedBy": "live",
240
+ "firstSeen": "2026-10-27",
241
+ "note": "A label of PostNL's domestic tracker, with observation code M02, after the shipment was expected and before PostNL has it."
242
+ },
235
243
  {
236
244
  "wording": "Shipment received by PostNL",
237
245
  "language": "en",
@@ -3,7 +3,7 @@ const STAGES = {
3
3
  OriginalWarehouseProcess: 'in_transit', OriginalWarehouseDeparture: 'in_transit',
4
4
  LinehaulAgencyReceive: 'in_transit', LinehaulAgencyDeparture: 'in_transit', LinehaulDeparture: 'in_transit',
5
5
  LinehaulArrival: 'in_transit', CustomsCleared: 'in_transit', SupplierAndLastMileReceive: 'in_transit',
6
- LastMileOutForDelivery: 'out_for_delivery', LastMileDelivered: 'delivered',
6
+ LastMileInTransit: 'in_transit', LastMileOutForDelivery: 'out_for_delivery', LastMileDelivered: 'delivered',
7
7
  };
8
8
  export const statusMap = {
9
9
  stage: code => code && Object.hasOwn(STAGES, code) ? STAGES[code] : undefined, gaps: [],
@@ -81,6 +81,13 @@
81
81
  "stage": "in_transit",
82
82
  "confirmedBy": "live"
83
83
  },
84
+ {
85
+ "code": "LastMileInTransit",
86
+ "wording": "In transit along Poste Italiane’s logistic network",
87
+ "language": "en",
88
+ "stage": "in_transit",
89
+ "confirmedBy": "live"
90
+ },
84
91
  {
85
92
  "code": "LastMileOutForDelivery",
86
93
  "wording": "Parcel is out for delivery.",
@@ -67,6 +67,10 @@ export interface CarrierResult extends JsonObject {
67
67
  last_update?: string | null;
68
68
  expected_delivery?: string | null;
69
69
  expected_delivery_from?: string | null;
70
+ /**
71
+ * Who sent the parcel. Null when the name the carrier gives is not its sender, as the account
72
+ * a return goes back to: a name an earlier answer gave no longer stands. Absent says nothing.
73
+ */
70
74
  sender_name?: string | null;
71
75
  receiver_name?: string | null;
72
76
  /**
@@ -420,8 +420,9 @@ export function trackingLanguageStage(description) {
420
420
  if (/preparation chez.*expediteur|preparation.*expediteur|preparazione.*mittente|being prepared.*sender|(?:shipper|sender)(?: that)? (?:they are|is|are) preparing|beim absender.*vorbereitet|warehouse of the sender|shippers warehouse|entrepot de l'expediteur|lager des absenders|magazzino del mittente|demande d'envoi.*prise en compte|collection request.*(?:received|recorded)|^(?:the |your )?collection (?:has been |was )?registered$|abholauftrag.*erfasst|richiesta.*ritiro.*registrata/.test(text))
421
421
  return 'registered';
422
422
  // The sender's order or data reached the carrier, not the parcel: "Order submitted, pending to
423
- // receive", Royal Mail's "Sender preparing item", "Sender despatching item" and "Sender has despatched item", "Shipping info sent to BRT".
424
- if (/\border submitted(?:,| and)? pending (?:to )?rece(?:iv|ipt)|\bshipment data uploaded\b|^sender preparing item\b|^sender (?:has despatched|despatching) item$|^shipping info(?:rmation)? sent to (?!(?:the |your? )?(?:recipient|customer|consignee|addressee|buyer)\b)|\bwe have received your shipment information\b|\bhemos recibido la informacion de su envio\b|\bwe will pick up (?:the|your) (?:package|parcel) from the sender\b|^booked for collection$|\bpending shipping by (?:the )?(?:seller|sender|shipper|merchant)\b|\bshipment order created\b/.test(text))
423
+ // receive", Royal Mail's "Sender preparing item", "Sender despatching item" and "Sender has despatched item", "Shipping info sent to BRT",
424
+ // and a line's "Shipping Order Created".
425
+ if (/\border submitted(?:,| and)? pending (?:to )?rece(?:iv|ipt)|\bshipment data uploaded\b|^sender preparing item\b|^sender (?:has despatched|despatching) item$|^shipping info(?:rmation)? sent to (?!(?:the |your? )?(?:recipient|customer|consignee|addressee|buyer)\b)|\bwe have received your shipment information\b|\bhemos recibido la informacion de su envio\b|\bwe will pick up (?:the|your) (?:package|parcel) from the sender\b|^booked for collection$|\bpending shipping by (?:the )?(?:seller|sender|shipper|merchant)\b|\bship(?:ment|ping) order created\b/.test(text))
425
426
  return 'registered';
426
427
  // A Shenzhen forwarder's pre-advice from the shipper, before its warehouse has the parcel.
427
428
  if (/^forecast has been received$/.test(text))
@@ -534,14 +535,16 @@ export function trackingLanguageStage(description) {
534
535
  if (/^attempted but unfinished drop[- ]?off at (?:the |a )?locker$/.test(text))
535
536
  return 'registered';
536
537
  // A partner network taking the parcel over mid-journey, as ParcelsApp relays it between a post's export
537
- // scans and the destination's freight scans.
538
- if (/^(?:(?:the |your )?(?:item|parcel|package|shipment) )?(?:(?:has been|was) )?received by (?:the |a |our )?partner carrier$/.test(text))
538
+ // scans and the destination's freight scans, and a line's US hub after the flight ("Parcel received by in
539
+ // JFK01.").
540
+ if (/^(?:(?:the |your )?(?:item|parcel|package|shipment) )?(?:(?:has been|was) )?received by (?:the |a |our )?partner carrier$|^(?:(?:the |your )?(?:item|parcel|package|shipment) )?received by in [a-z]{3}\d{2}\.?$/.test(text))
539
541
  return 'in_transit';
540
542
  // MeiHoo Pegasus's scans at its operation centre, as 17TRACK relays them after the order: the centre handling
541
- // the package's data, which does not say it has the parcel, then the parcel about to leave the centre.
543
+ // the package's data, which does not say it has the parcel, then the parcel about to leave the centre. The
544
+ // Hualei platform's lines word the departure "Goods leave the operation center".
542
545
  if (/^operation cent(?:er|re) is processing (?:the )?(?:package|parcel|shipment) information$/.test(text))
543
546
  return 'registered';
544
- if (/^preparing to leave (?:the )?(?:[a-z]+ ){0,3}operation cent(?:er|re)$/.test(text))
547
+ if (/^(?:preparing to leave|(?:the )?goods leav(?:e|ing)) (?:the )?(?:[a-z]+ ){0,3}operation cent(?:er|re)$/.test(text))
545
548
  return 'in_transit';
546
549
  // PostNL's international scans as providers relay them without its categories, read as Spring GDS's map reads
547
550
  // them: the item prepared before its bulk handover, then PostNL's acceptance centre taking it in.
@@ -584,8 +587,9 @@ export function trackingLanguageStage(description) {
584
587
  // Taken in by the carrier: Royal Mail's "Item received by Royal Mail", a parcel shop's or a warehouse's
585
588
  // receipt, a forwarder's ("<company> has received the goods, <town>, <country>."), the sender's drop-off at a
586
589
  // SEUR pickup point, a collection carried out, and its English "Has been put into storage". A bare "Item
587
- // received" names no receiver and stays unread.
588
- if (/^item received by royal mail$|\btaken over by one of our operators\b|\bwe've received your (?:parcel|package|item) at the parcel ?shop\b|^(?:(?:the |your )?(?:item|parcel|package|shipment) )?(?:was|has been) dropped off at (?:the |a )?parcel ?shop$|^(?![^,]*\b(?:recipient|consignee|receiver|customer|addressee|buyer|pick ?up point|parcel ?shop|locker|destination)\b)[a-z][\w&' -]{0,40} has received the goods(?:, [^,]{1,40}){0,2}\.?$|^the shipment has been deposited at a seur pickup point$|^(?:the |your )?collection (?:has been |was )?(?:carried out|completed)$|^已入库|^(?:(?:the |your )?(?:item|parcel|package|shipment) )?(?:has been |was )?put into storage$/.test(text))
590
+ // received" names no receiver and stays unread. The Hualei platform's arrival at a line's receiving point
591
+ // (到达收货点) is a receipt too.
592
+ if (/^到达收货点|^item received by royal mail$|\btaken over by one of our operators\b|\bwe've received your (?:parcel|package|item) at the parcel ?shop\b|^(?:(?:the |your )?(?:item|parcel|package|shipment) )?(?:was|has been) dropped off at (?:the |a )?parcel ?shop$|^(?![^,]*\b(?:recipient|consignee|receiver|customer|addressee|buyer|pick ?up point|parcel ?shop|locker|destination)\b)[a-z][\w&' -]{0,40} has received the goods(?:, [^,]{1,40}){0,2}\.?$|^the shipment has been deposited at a seur pickup point$|^(?:the |your )?collection (?:has been |was )?(?:carried out|completed)$|^已入库|^(?:(?:the |your )?(?:item|parcel|package|shipment) )?(?:has been |was )?put into storage$/.test(text))
589
593
  return 'accepted';
590
594
  // Royal Mail's sender drop-offs at a parcel locker and a postbox, as providers relay them.
591
595
  if (/^sender advised item dropped off at parcel locker$|^dropped off at postbox$/.test(text))
@@ -30,6 +30,7 @@ import { jsExpressChinaClock } from '../../carriers/js-express/clock.js';
30
30
  import { pitneyBowesPlatformClock, pitneyBowesPostalRelay } from '../../carriers/pitney-bowes/clock.js';
31
31
  import { pocztaPolskaPartnerClock } from '../../carriers/poczta-polska/clock.js';
32
32
  import { facilityZone, LatestScan, relaysFacilityClock } from '../shared/facilityClock.js';
33
+ import { CHINESE_CITIES, CHINESE_PROVINCES } from '../shared/china.js';
33
34
  import { brandCarrierForNumber, universalCarrierHints } from '../shared/hints.js';
34
35
  import { event, isNotice, localEvent, numberOf, place as reportedPlace, result } from '../shared/result.js';
35
36
  import { carrierScan, markLaterReceipts, markReturnLeg } from '../shared/scans.js';
@@ -644,19 +645,6 @@ const POSTAL_ORIGIN_LEG = wordings([
644
645
  ]);
645
646
  // The posting itself, UPU's "Posting/Collection" and EMS's "Posted" (EMA).
646
647
  const POSTAL_POSTING = wordings(['posting/collection', 'posted']);
647
- // Mainland China's provinces, regions and municipalities, as China Post's scans name them.
648
- const CHINESE_PROVINCES = new Set([
649
- 'anhui', 'beijing', 'chongqing', 'fujian', 'gansu', 'guangdong', 'guangxi', 'guizhou', 'hainan', 'hebei', 'heilongjiang', 'henan',
650
- 'hubei', 'hunan', 'innermongolia', 'jiangsu', 'jiangxi', 'jilin', 'liaoning', 'ningxia', 'qinghai', 'shaanxi', 'shandong',
651
- 'shanghai', 'shanxi', 'sichuan', 'tianjin', 'tibet', 'xinjiang', 'xizang', 'yunnan', 'zhejiang',
652
- ]);
653
- // Mainland cities of China Post's provincial and export processing centres, which its scans can name alone.
654
- const CHINESE_CITIES = new Set([
655
- 'changchun', 'changsha', 'chengdu', 'dalian', 'dongguan', 'foshan', 'fuzhou', 'guangzhou', 'guiyang', 'haikou', 'hangzhou', 'harbin',
656
- 'hefei', 'hohhot', 'huizhou', 'jinan', 'jinhua', 'kunming', 'lanzhou', 'lhasa', 'nanchang', 'nanjing', 'nanning', 'ningbo', 'putian',
657
- 'qingdao', 'quanzhou', 'shantou', 'shenyang', 'shenzhen', 'shijiazhuang', 'suzhou', 'taiyuan', 'urumqi', 'wenzhou', 'wuhan', 'wuxi',
658
- 'xiamen', 'xian', 'xining', 'yinchuan', 'yiwu', 'zhengzhou', 'zhongshan',
659
- ]);
660
648
  /**
661
649
  * Whether a China Post scan is on Beijing's clock: its place is written in
662
650
  * Chinese ("示例市 12345678"), ends with a mainland province ("Example City,
@@ -0,0 +1,2 @@
1
+ export declare const CHINESE_PROVINCES: ReadonlySet<string>;
2
+ export declare const CHINESE_CITIES: ReadonlySet<string>;
@@ -0,0 +1,13 @@
1
+ // Mainland China's provinces, regions and municipalities, as China Post's scans name them.
2
+ export const CHINESE_PROVINCES = new Set([
3
+ 'anhui', 'beijing', 'chongqing', 'fujian', 'gansu', 'guangdong', 'guangxi', 'guizhou', 'hainan', 'hebei', 'heilongjiang', 'henan',
4
+ 'hubei', 'hunan', 'innermongolia', 'jiangsu', 'jiangxi', 'jilin', 'liaoning', 'ningxia', 'qinghai', 'shaanxi', 'shandong',
5
+ 'shanghai', 'shanxi', 'sichuan', 'tianjin', 'tibet', 'xinjiang', 'xizang', 'yunnan', 'zhejiang',
6
+ ]);
7
+ // Mainland cities of China Post's provincial and export processing centres, which its scans can name alone.
8
+ export const CHINESE_CITIES = new Set([
9
+ 'changchun', 'changsha', 'chengdu', 'dalian', 'dongguan', 'foshan', 'fuzhou', 'guangzhou', 'guiyang', 'haikou', 'hangzhou', 'harbin',
10
+ 'hefei', 'hohhot', 'huizhou', 'jinan', 'jinhua', 'kunming', 'lanzhou', 'lhasa', 'nanchang', 'nanjing', 'nanning', 'ningbo', 'putian',
11
+ 'qingdao', 'quanzhou', 'shantou', 'shenyang', 'shenzhen', 'shijiazhuang', 'suzhou', 'taiyuan', 'urumqi', 'wenzhou', 'wuhan', 'wuxi',
12
+ 'xiamen', 'xian', 'xining', 'yinchuan', 'yiwu', 'zhengzhou', 'zhongshan',
13
+ ]);
@@ -11,7 +11,7 @@ export declare function hasPrivateDeliveryDetails(description: string): boolean;
11
11
  * area, a "Country" label or a status given before the place, unless it is a
12
12
  * label or placeholder ("--") that names no place, holds private delivery
13
13
  * details or is Posti's "abroad" label. A postcode given alone is the country
14
- * its code names, if any.
14
+ * its code names, if any. A country a feed added goes (`withoutAddedCountry`).
15
15
  */
16
16
  export declare function place(value: unknown): string;
17
17
  /** A place written in brackets after the wording, "Airline received【Hong Kong】": the wording before it and the place. */
@@ -11,6 +11,7 @@
11
11
  import { DateTime } from 'luxon';
12
12
  import { trackingLanguageStage } from '../../core/status/index.js';
13
13
  import { countryCode } from '../../core/time/index.js';
14
+ import { CHINESE_CITIES, CHINESE_PROVINCES } from './china.js';
14
15
  import { deliveryForecastRemainder, nonterminalEnglishReturn } from '../../core/status/language.js';
15
16
  import { IndeterminateError, InvalidInputError, NoHistoryError } from '../../core/errors/index.js';
16
17
  import { PROGRESS } from '../../core/result/partial.js';
@@ -82,6 +83,25 @@ function postcodeCountry(location) {
82
83
  const code = postcode[1] && countryCode(postcode[1]) === postcode[1] && !NOT_COUNTRIES.test(postcode[1]) ? postcode[1] : undefined;
83
84
  return code ? ENGLISH_REGIONS.of(code) ?? '' : '';
84
85
  }
86
+ // Countries whose places can be written in Chinese characters.
87
+ const HAN_COUNTRIES = new Set(['CN', 'HK', 'MO', 'TW', 'JP', 'KR', 'SG']);
88
+ /**
89
+ * A place without a country a feed added: the destination's, which lines
90
+ * append to their warehouses in China ("广州,US") and ParcelsApp spells out
91
+ * ("Shanghai, United States"), or a country given twice ("Example State,
92
+ * United States, United States").
93
+ */
94
+ function withoutAddedCountry(location) {
95
+ const parts = location.split(',').map((part) => part.trim());
96
+ const country = parts.length > 1 ? countryCode(parts.at(-1)) : null;
97
+ if (!country)
98
+ return location;
99
+ const doubled = parts.length > 2 && countryCode(parts.at(-2)) === country;
100
+ const key = (part) => part.toLowerCase().replace(/[^a-z0-9]/g, '');
101
+ const chinese = !HAN_COUNTRIES.has(country) && parts.slice(0, -1).some((part) => /\p{Script=Han}/u.test(part)
102
+ || CHINESE_PROVINCES.has(key(part)) || CHINESE_CITIES.has(key(part)));
103
+ return doubled || chinese ? parts.slice(0, -1).join(', ') : location;
104
+ }
85
105
  /** The place a status given as a location names in 【】 or before its airport, '' when it names none, or undefined for any other location. */
86
106
  function statusPlace(location) {
87
107
  const status = STATUS_PLACE.exec(location);
@@ -96,11 +116,11 @@ function statusPlace(location) {
96
116
  * area, a "Country" label or a status given before the place, unless it is a
97
117
  * label or placeholder ("--") that names no place, holds private delivery
98
118
  * details or is Posti's "abroad" label. A postcode given alone is the country
99
- * its code names, if any.
119
+ * its code names, if any. A country a feed added goes (`withoutAddedCountry`).
100
120
  */
101
121
  export function place(value) {
102
122
  const read = text(value).slice(0, 200).replace(DEPOT_PREFIX, '').replace(BRACKETED_POSTCODE, '').replace(OFFICE_CODE, '').replace(COUNTRY_LABEL, '');
103
- const location = statusPlace(read) ?? postcodeCountry(read) ?? read;
123
+ const location = statusPlace(read) ?? postcodeCountry(read) ?? withoutAddedCountry(read);
104
124
  return /[\p{L}\p{N}]/u.test(location) && !NOT_PLACES.test(location) && !hasPrivateDeliveryDetails(location) && !isPostiAbroadLabel(location)
105
125
  ? location : '';
106
126
  }
@@ -2,7 +2,7 @@
2
2
  "openapi": "3.1.0",
3
3
  "info": {
4
4
  "title": "Universal Parcel Scraper",
5
- "version": "0.802.0-main.4307"
5
+ "version": "0.802.2-main.4316"
6
6
  },
7
7
  "paths": {
8
8
  "/health": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "universal-parcel-scraper",
3
- "version": "0.802.0-main.4307",
3
+ "version": "0.802.2-main.4316",
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",