universal-parcel-scraper 0.2.0 → 0.3.0-main.298

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.
Files changed (210) hide show
  1. package/README.md +209 -71
  2. package/data/carrier.schema.json +6 -4
  3. package/data/catalog.json +6 -6
  4. package/dist/app.d.ts +11 -0
  5. package/dist/app.js +11 -0
  6. package/dist/browser/scraper.js +1386 -1568
  7. package/dist/carriers/aliexpress/adapter.d.ts +5 -3
  8. package/dist/carriers/aliexpress/adapter.js +28 -8
  9. package/dist/carriers/amazon-shipping/adapter.d.ts +3 -2
  10. package/dist/carriers/amazon-shipping/adapter.js +15 -10
  11. package/dist/carriers/amazon-shipping/eligibility.d.ts +2 -1
  12. package/dist/carriers/amazon-shipping/eligibility.js +2 -2
  13. package/dist/carriers/aramex/adapter.d.ts +2 -0
  14. package/dist/carriers/aramex/adapter.js +5 -3
  15. package/dist/carriers/aramex/parser.js +2 -2
  16. package/dist/carriers/asendia/adapter.d.ts +8 -0
  17. package/dist/carriers/asendia/adapter.js +66 -11
  18. package/dist/carriers/asendia/probe.d.ts +2 -0
  19. package/dist/carriers/asendia/probe.js +9 -7
  20. package/dist/carriers/australia-post/adapter.js +2 -2
  21. package/dist/carriers/austrian-post/adapter.js +2 -2
  22. package/dist/carriers/blue-dart/parser.js +2 -2
  23. package/dist/carriers/bpost/parser.js +2 -2
  24. package/dist/carriers/bring-posten/parser.js +2 -2
  25. package/dist/carriers/brt/parser.js +2 -2
  26. package/dist/carriers/c-chez-vous/adapter.d.ts +3 -2
  27. package/dist/carriers/c-chez-vous/adapter.js +15 -10
  28. package/dist/carriers/canada-post/adapter.d.ts +1 -0
  29. package/dist/carriers/canada-post/adapter.js +5 -3
  30. package/dist/carriers/canada-post/parser.js +2 -2
  31. package/dist/carriers/canpar/parser.js +2 -2
  32. package/dist/carriers/ciblex/parser.js +2 -2
  33. package/dist/carriers/colis-prive/adapter.d.ts +3 -2
  34. package/dist/carriers/colis-prive/adapter.js +0 -0
  35. package/dist/carriers/colisweb/adapter.d.ts +3 -2
  36. package/dist/carriers/colisweb/adapter.js +13 -8
  37. package/dist/carriers/correios-br/parser.js +2 -2
  38. package/dist/carriers/correos-chile/parser.js +3 -3
  39. package/dist/carriers/correos-express/parser.js +2 -2
  40. package/dist/carriers/correos-spain/adapter.d.ts +4 -2
  41. package/dist/carriers/correos-spain/adapter.js +16 -8
  42. package/dist/carriers/ctt/adapter.d.ts +2 -2
  43. package/dist/carriers/ctt/adapter.js +30 -25
  44. package/dist/carriers/ctt-express/parser.js +2 -2
  45. package/dist/carriers/dachser/adapter.d.ts +3 -2
  46. package/dist/carriers/dachser/adapter.js +34 -16
  47. package/dist/carriers/delhivery/parser.js +2 -2
  48. package/dist/carriers/dhl/adapter.d.ts +2 -2
  49. package/dist/carriers/dhl/adapter.js +50 -22
  50. package/dist/carriers/dhl-ecommerce/adapter.d.ts +3 -3
  51. package/dist/carriers/dhl-ecommerce/adapter.js +8 -8
  52. package/dist/carriers/dpd/adapter.d.ts +3 -3
  53. package/dist/carriers/dpd/adapter.js +113 -57
  54. package/dist/carriers/dpd/carrier.json +1 -1
  55. package/dist/carriers/dpd-fr/adapter.d.ts +2 -2
  56. package/dist/carriers/dpd-fr/adapter.js +29 -20
  57. package/dist/carriers/dtdc/parser.js +2 -2
  58. package/dist/carriers/ecoscooting/parser.js +2 -2
  59. package/dist/carriers/ems/adapter.js +2 -2
  60. package/dist/carriers/estafeta/parser.js +2 -2
  61. package/dist/carriers/evri/adapter.js +2 -2
  62. package/dist/carriers/fedex/adapter.d.ts +2 -2
  63. package/dist/carriers/fedex/adapter.js +14 -10
  64. package/dist/carriers/four-px/adapter.js +2 -2
  65. package/dist/carriers/geodis/adapter.d.ts +3 -2
  66. package/dist/carriers/geodis/adapter.js +13 -8
  67. package/dist/carriers/gls-ch/adapter.d.ts +4 -3
  68. package/dist/carriers/gls-ch/adapter.js +27 -19
  69. package/dist/carriers/gls-ch/carrier.json +1 -1
  70. package/dist/carriers/gls-de/adapter.d.ts +3 -3
  71. package/dist/carriers/gls-de/adapter.js +14 -9
  72. package/dist/carriers/gls-de/carrier.json +1 -1
  73. package/dist/carriers/gls-fr/adapter.d.ts +3 -2
  74. package/dist/carriers/gls-fr/adapter.js +16 -11
  75. package/dist/carriers/gofo/parser.js +2 -2
  76. package/dist/carriers/heppner/adapter.d.ts +4 -2
  77. package/dist/carriers/heppner/adapter.js +19 -14
  78. package/dist/carriers/heppner/carrier.json +1 -1
  79. package/dist/carriers/hermes/adapter.d.ts +3 -2
  80. package/dist/carriers/hermes/adapter.js +13 -8
  81. package/dist/carriers/hermes-de/adapter.d.ts +3 -2
  82. package/dist/carriers/hermes-de/adapter.js +16 -12
  83. package/dist/carriers/india-post/adapter.d.ts +2 -2
  84. package/dist/carriers/india-post/adapter.js +42 -21
  85. package/dist/carriers/inpost/adapter.d.ts +4 -2
  86. package/dist/carriers/inpost/adapter.js +17 -10
  87. package/dist/carriers/japan-post/adapter.js +2 -2
  88. package/dist/carriers/korea-post/adapter.js +2 -2
  89. package/dist/carriers/la-poste/adapter.d.ts +4 -2
  90. package/dist/carriers/la-poste/adapter.js +18 -13
  91. package/dist/carriers/landmark-global/parser.js +2 -2
  92. package/dist/carriers/mondial-relay/adapter.d.ts +2 -2
  93. package/dist/carriers/mondial-relay/adapter.js +26 -17
  94. package/dist/carriers/mondial-relay/carrier.json +1 -1
  95. package/dist/carriers/mrw/adapter.js +4 -2
  96. package/dist/carriers/mrw/parser.js +3 -3
  97. package/dist/carriers/nacex/parser.js +2 -2
  98. package/dist/carriers/ninja-van/parser.js +2 -2
  99. package/dist/carriers/nz-post/parser.js +2 -2
  100. package/dist/carriers/ontrac/parser.js +2 -2
  101. package/dist/carriers/paack/adapter.d.ts +4 -2
  102. package/dist/carriers/paack/adapter.js +24 -14
  103. package/dist/carriers/paack/carrier.json +1 -1
  104. package/dist/carriers/packeta/adapter.d.ts +4 -2
  105. package/dist/carriers/packeta/adapter.js +16 -8
  106. package/dist/carriers/planzer/adapter.d.ts +5 -3
  107. package/dist/carriers/planzer/adapter.js +35 -12
  108. package/dist/carriers/planzer/shared.d.ts +4 -1
  109. package/dist/carriers/planzer/shared.js +20 -6
  110. package/dist/carriers/poczta-polska/parser.js +2 -2
  111. package/dist/carriers/pos-malaysia/adapter.d.ts +2 -0
  112. package/dist/carriers/pos-malaysia/adapter.js +9 -5
  113. package/dist/carriers/poste-italiane/adapter.d.ts +4 -2
  114. package/dist/carriers/poste-italiane/adapter.js +16 -8
  115. package/dist/carriers/posti/adapter.js +2 -2
  116. package/dist/carriers/postlogistics/adapter.d.ts +6 -4
  117. package/dist/carriers/postlogistics/adapter.js +49 -17
  118. package/dist/carriers/postlogistics/status.d.ts +2 -0
  119. package/dist/carriers/postlogistics/status.js +2 -0
  120. package/dist/carriers/postnord/parser.js +2 -2
  121. package/dist/carriers/purolator/parser.js +2 -2
  122. package/dist/carriers/relais-colis/adapter.js +2 -2
  123. package/dist/carriers/royal-mail/adapter.d.ts +2 -2
  124. package/dist/carriers/royal-mail/adapter.js +14 -10
  125. package/dist/carriers/seur/parser.js +2 -2
  126. package/dist/carriers/sf-express/parser.js +2 -2
  127. package/dist/carriers/singapore-post/adapter.js +2 -2
  128. package/dist/carriers/spring-gds/adapter.d.ts +2 -0
  129. package/dist/carriers/spring-gds/adapter.js +16 -15
  130. package/dist/carriers/sunyou/adapter.d.ts +5 -3
  131. package/dist/carriers/sunyou/adapter.js +16 -9
  132. package/dist/carriers/swiss-post/adapter.d.ts +4 -4
  133. package/dist/carriers/swiss-post/adapter.js +33 -15
  134. package/dist/carriers/swiss-post-cargo/adapter.d.ts +3 -2
  135. package/dist/carriers/swiss-post-cargo/adapter.js +14 -9
  136. package/dist/carriers/the-courier-guy/number.js +3 -2
  137. package/dist/carriers/tipsa/parser.js +3 -3
  138. package/dist/carriers/tnt/adapter.js +3 -3
  139. package/dist/carriers/ukrposhta/adapter.js +20 -62
  140. package/dist/carriers/ukrposhta/parser.js +2 -2
  141. package/dist/carriers/uniuni/parser.js +3 -3
  142. package/dist/carriers/ups/adapter.d.ts +2 -2
  143. package/dist/carriers/ups/adapter.js +40 -32
  144. package/dist/carriers/usps/adapter.d.ts +2 -2
  145. package/dist/carriers/usps/adapter.js +14 -10
  146. package/dist/carriers/yamato/adapter.js +2 -2
  147. package/dist/carriers/yanwen/adapter.js +2 -2
  148. package/dist/carriers/yto/parser.js +2 -2
  149. package/dist/carriers/yunda/parser.js +2 -2
  150. package/dist/carriers/yunexpress/adapter.js +21 -39
  151. package/dist/cli/index.d.ts +2 -0
  152. package/dist/cli/index.js +49 -19
  153. package/dist/core/adapter/index.d.ts +18 -1
  154. package/dist/core/adapter/index.js +28 -4
  155. package/dist/core/adapter/track.js +21 -7
  156. package/dist/core/catalog/index.d.ts +1 -15
  157. package/dist/core/catalog/index.js +3 -106
  158. package/dist/core/catalog/inputs.d.ts +2 -2
  159. package/dist/core/catalog/inputs.js +4 -4
  160. package/dist/core/catalog/parcel.d.ts +53 -0
  161. package/dist/core/catalog/parcel.js +105 -0
  162. package/dist/core/catalog/types.d.ts +1 -29
  163. package/dist/core/catalog/urls.js +1 -0
  164. package/dist/core/errors/hint.d.ts +7 -0
  165. package/dist/core/errors/hint.js +11 -1
  166. package/dist/core/errors/index.d.ts +8 -1
  167. package/dist/core/errors/index.js +11 -0
  168. package/dist/core/runner/index.d.ts +23 -1
  169. package/dist/core/runner/index.js +71 -14
  170. package/dist/core/transport/boundedFetch.js +24 -19
  171. package/dist/core/transport/browser.d.ts +1 -0
  172. package/dist/core/transport/browser.js +76 -99
  173. package/dist/core/transport/index.d.ts +4 -1
  174. package/dist/core/transport/index.js +3 -1
  175. package/dist/core/transport/localBrowser.d.ts +24 -0
  176. package/dist/core/transport/localBrowser.js +87 -0
  177. package/dist/core/transport/trawl.d.ts +6 -0
  178. package/dist/core/transport/trawl.js +30 -9
  179. package/dist/core/transport/userAgent.d.ts +8 -0
  180. package/dist/core/transport/userAgent.js +15 -0
  181. package/dist/data/catalog.json +6 -6
  182. package/dist/facade/index.d.ts +3 -0
  183. package/dist/facade/index.js +29 -11
  184. package/dist/generated/catalog.d.ts +6 -6
  185. package/dist/generated/catalog.js +6 -6
  186. package/dist/index.d.ts +0 -4
  187. package/dist/index.js +0 -4
  188. package/dist/providers/parcelsapp/adapter.d.ts +1 -1
  189. package/dist/providers/parcelsapp/adapter.js +13 -10
  190. package/dist/providers/parcelsapp/http.d.ts +1 -1
  191. package/dist/providers/parcelsapp/http.js +2 -2
  192. package/dist/providers/postal-ninja/adapter.d.ts +2 -2
  193. package/dist/providers/postal-ninja/adapter.js +9 -8
  194. package/dist/providers/seventeentrack/adapter.d.ts +1 -1
  195. package/dist/providers/seventeentrack/adapter.js +7 -7
  196. package/dist/providers/shared/capture.d.ts +2 -0
  197. package/dist/providers/shared/capture.js +1 -1
  198. package/dist/providers/shared/result.js +4 -2
  199. package/dist/providers/ship24/adapter.d.ts +2 -2
  200. package/dist/providers/ship24/adapter.js +15 -11
  201. package/dist/providers/ship24/http.d.ts +1 -1
  202. package/dist/providers/ship24/http.js +4 -3
  203. package/dist/providers/universal.d.ts +3 -2
  204. package/dist/providers/universal.js +20 -2
  205. package/dist/providers/upu/adapter.js +2 -2
  206. package/dist/scripts/carrier-canary.js +2 -1
  207. package/dist/server/index.d.ts +15 -0
  208. package/dist/server/index.js +90 -12
  209. package/dist/server/openapi.json +399 -52
  210. package/package.json +13 -7
package/README.md CHANGED
@@ -1,124 +1,262 @@
1
1
  <div align="center">
2
2
 
3
+ <img src="docs/assets/logo.svg" width="88" alt="">
4
+
3
5
  # Universal Parcel Scraper
4
6
 
5
- **A parcel. One timeline. Your infrastructure.**
7
+ **Parcel tracking that asks the carrier directly, from your own machine.**
6
8
 
9
+ [![npm](https://img.shields.io/npm/v/universal-parcel-scraper)](https://www.npmjs.com/package/universal-parcel-scraper)
7
10
  [![CI](https://github.com/plhery/universal-parcel-scraper/actions/workflows/ci.yml/badge.svg)](https://github.com/plhery/universal-parcel-scraper/actions/workflows/ci.yml)
8
11
  [![License: Apache-2.0](https://img.shields.io/badge/core-Apache--2.0-blue.svg)](LICENSE)
9
12
 
10
- Detect a carrier, fetch its tracking history, and get consistent stages and scan times.
11
- A TypeScript library, a CLI, and a small HTTP server. No tracking-service account required.
12
-
13
13
  <!-- GENERATED:summary -->
14
14
  **105 carriers · 85 active dedicated adapters · 58 countries represented**
15
15
  <!-- /GENERATED:summary -->
16
16
 
17
+ [Try it](#try-it) · [Ways to run it](#ways-to-run-it) · [Coverage](#how-much-can-it-track) · [Carriers](carriers/) · [Add a carrier](CONTRIBUTING.md)
18
+
19
+ <img src="docs/assets/demo.svg" width="840" alt="The track command printing a parcel's history as JSON, next to a timeline drawn from it">
20
+
17
21
  </div>
18
22
 
19
- DHL, UPS, FedEx, USPS, Swiss Post, DPD, PostNL, and many more.
20
- [Browse the carriers](carriers/) · [How it works](ARCHITECTURE.md) · [Add a carrier](CONTRIBUTING.md)
23
+ Give it a tracking number. It works out which carrier the number belongs to and fetches the
24
+ history from that carrier's own website. What comes back has one shape, so a UPS parcel and
25
+ a Poczta Polska parcel look the same to your code.
26
+
27
+ There is no account to open and no API key. Every carrier publishes tracking in its own way,
28
+ and hosted tracking APIs smooth that over for a fee while seeing every number you look up.
29
+ Here the same work is open code: one adapter per carrier, each in [its own folder](carriers/)
30
+ with a README on how that site is read.
31
+
32
+ ## Try it
33
+
34
+ You need Node.js 24 or newer.
35
+
36
+ ```sh
37
+ npx universal-parcel-scraper detect 1Z999AA10123456784
38
+ ```
39
+
40
+ Detection is offline. It checks the number against each carrier's formats and checksums and
41
+ answers `ups` without sending anything anywhere.
42
+
43
+ ```sh
44
+ npx universal-parcel-scraper track YOUR_TRACKING_NUMBER
45
+ ```
46
+
47
+ Tracking does go out, to the carrier that owns the number. If a number fits several carriers,
48
+ name one with `--carrier dhl`. A few carriers also want the delivery postcode (`--postcode`)
49
+ or the link from the shipping email (`--tracking-url`).
21
50
 
22
- ## Get started
51
+ ## Ways to run it
23
52
 
24
- Node.js 24 or newer:
53
+ ### Command line
25
54
 
26
55
  ```sh
27
- npm install universal-parcel-scraper
28
- npx parcel-scraper detect 1Z999AA10123456784
29
- npx parcel-scraper track YOUR_TRACKING_NUMBER --carrier ups
56
+ npm install -g universal-parcel-scraper
30
57
  ```
31
58
 
59
+ The command is `parcel-scraper`:
60
+
61
+ ```sh
62
+ parcel-scraper detect <number, link or text> # which carrier is this? no network
63
+ parcel-scraper track <number> # the parcel's history
64
+ parcel-scraper recognize <number> # ask the possible carriers which one knows it
65
+ parcel-scraper carriers # the catalog, with the ids --carrier accepts
66
+ parcel-scraper serve # the HTTP API described below
67
+ ```
68
+
69
+ Everything prints JSON, so it pipes:
70
+
71
+ ```sh
72
+ parcel-scraper track "$NUMBER" | jq -r '.result.current_stage'
73
+ ```
74
+
75
+ `parcel-scraper --help` lists the environment variables.
76
+
77
+ ### Node
78
+
32
79
  ```js
33
80
  import { createTracker } from 'universal-parcel-scraper/node';
34
81
 
35
82
  const tracker = createTracker();
36
- const { carrier, source, result } = await tracker.track({
37
- number: process.env.PARCEL_NUMBER,
38
- carrier: 'ups',
39
- });
40
- console.log(carrier, source, result.current_stage, result.events);
83
+ const { carrier, source, result } = await tracker.track({ number: process.env.PARCEL_NUMBER });
84
+
85
+ console.log(carrier, result.current_stage);
86
+ for (const scan of result.events) {
87
+ console.log(scan.time, scan.location, scan.description, scan.stage);
88
+ }
41
89
  ```
42
90
 
43
- Detection also works in the browser, with no network calls:
91
+ Each scan keeps the carrier's wording and gains a `stage` from [one shared list](data/stages.json).
92
+ `source` names who answered, and `attempts` lists everything that was tried. When nothing
93
+ returns a history, `track()` throws a `TrackingError` carrying those attempts and a hint about
94
+ the cause. [examples/node.mjs](examples/node.mjs) is a runnable version.
95
+
96
+ ### Browser
44
97
 
45
98
  ```js
46
99
  import { parseTrackingInput } from 'universal-parcel-scraper';
47
- const match = parseTrackingInput('1Z999AA10123456784'); // synthetic example
100
+
101
+ parseTrackingInput('1Z999AA10123456784');
102
+ // { carrier: 'ups', confidence: 'high', candidates: ['ups'], … }
48
103
  ```
49
104
 
50
- Direct HTTP works out of the box. Some carriers need Chromium, image processing, or the
51
- optional [TRAWL browser service](trawl/README.md). Install the transports you need:
105
+ The root import has no Node imports and makes no requests, so it can sit behind a form field
106
+ and name the carrier as someone types.
107
+
108
+ ### HTTP
109
+
110
+ For anything that is not JavaScript:
52
111
 
53
112
  ```sh
54
- npm install playwright-core sharp onnxruntime-web
113
+ parcel-scraper serve --port 8080
55
114
  ```
56
115
 
57
- Pass `chromiumPath` or `trawlUrl` to `createTracker()`. Commercial aggregators are opt-in:
58
- `createTracker({ providers: ['ParcelsApp', 'Ship24', '17TRACK', 'Postal Ninja', 'UPU'] })`.
59
- The default is direct adapters plus UPU for eligible postal numbers.
116
+ ```sh
117
+ curl http://127.0.0.1:8080/v1/track \
118
+ -H 'Content-Type: application/json' \
119
+ -d '{"number":"1Z999AA10123456784","carrier":"ups"}'
120
+ ```
60
121
 
61
- ## How much can it track?
122
+ `/v1/detect`, `/v1/recognize` and `/v1/carriers` sit next to it, and the
123
+ [OpenAPI file](server/openapi.json) describes them all. The server caches recent answers in
124
+ memory and rate-limits callers. It has no database and never polls on its own.
62
125
 
63
- Carrier count tells you what the catalog knows. Tracking history tells you what a source
64
- actually returned. Here is the recorded comparison on our **100-carrier reference set**:
126
+ Set `SCRAPER_TOKEN` to require a bearer token. `SCRAPER_DEMO_PAGE=true` serves a small page
127
+ at `/` for trying numbers by hand. Behind a reverse proxy, set `SCRAPER_TRUSTED_PROXIES` so
128
+ the rate limit counts each caller and not the proxy. The other settings are in
129
+ [.env.example](.env.example).
65
130
 
66
- <!-- GENERATED:coverage -->
67
- | Source | Carriers with history |
68
- | --- | ---: |
69
- | **Universal Parcel Scraper, all fallbacks enabled** | **70 / 100** |
70
- | Dedicated adapters alone | 51 / 100 |
71
- | ParcelsApp | 50 / 100 |
72
- | Postal Ninja | 41 / 100 |
73
- | 17TRACK | 40 / 100 |
74
- | Ship24 | 38 / 100 |
75
- | UPU | 11 / 100 |
76
- <!-- /GENERATED:coverage -->
131
+ ### Docker
77
132
 
78
- Generated from [coverage.json](providers/coverage.json). These are recorded outcomes for
79
- the comparison reference of each carrier, including partial histories; alternate samples
80
- are excluded. This curated set is not a global market-share ranking or a live availability
81
- promise. Old parcels expire, formats differ, and browser challenges change.
82
- [Full results and limitations](providers/COVERAGE.md).
133
+ ```sh
134
+ docker run --rm -p 127.0.0.1:8080:8080 ghcr.io/plhery/universal-parcel-scraper:0.3.0
135
+ ```
136
+
137
+ The image runs `serve` and ships Chromium, for the carriers that only answer a real browser.
138
+
139
+ ## What you can build with it
140
+
141
+ - **A tracking screen in your own app.** [Delivery Tracker](https://github.com/plhery/delivery-tracker),
142
+ an open-source iPhone app and PWA, runs on this package.
143
+ - **A Home Assistant sensor** for the parcel you are waiting on.
144
+ [The config is in examples](examples/home-assistant.yaml).
145
+ - **Order status inside a shop or help desk**, so customers are not sent off to the carrier's
146
+ site. Any backend that speaks HTTP can call the server.
147
+ - **Tracking numbers pulled out of shipping emails.** `detect` takes pasted text and carrier
148
+ links as well as bare numbers.
149
+ - **A carrier field that fills itself in**, with the browser import.
150
+ - **A cron job** that pings you when `current_stage` turns `delivered`.
151
+
152
+ The library answers one lookup at a time. Storing parcels and deciding when to check again
153
+ are yours to do.
83
154
 
84
- Hosted APIs advertise much larger carrier catalogs and handle hosting for you. This project
85
- gives you the retrieval code, control over which sources receive a number, and no per-lookup
86
- subscription. Catalog totals from vendors are not comparable to this reference test.
155
+ ## How it works
87
156
 
88
- ## HTTP, Docker, and Home Assistant
157
+ <picture>
158
+ <source media="(prefers-color-scheme: dark)" srcset="docs/assets/how-it-works-dark.svg">
159
+ <img src="docs/assets/how-it-works-light.svg" width="840" alt="An input is detected offline and tracked by the carrier's dedicated adapter. With no history, the lookup moves to the fallbacks you enabled. Either way the result is one timeline.">
160
+ </picture>
161
+
162
+ Each adapter uses plain HTTP wherever the carrier's site allows it. Some sites only answer a
163
+ real browser and others need image processing, so those adapters can drive a local Chromium
164
+ or the optional [TRAWL browser service](trawl/README.md). Install what your carriers need:
89
165
 
90
166
  ```sh
91
- npx parcel-scraper serve --port 8080
92
- curl http://127.0.0.1:8080/v1/track \
93
- -H 'Content-Type: application/json' \
94
- -d '{"number":"1Z999AA10123456784","carrier":"ups"}'
167
+ npm install playwright-core sharp onnxruntime-web
95
168
  ```
96
169
 
97
- The server shares cached answers, spaces universal-provider calls, and limits requests.
98
- It stores no parcel list and runs no background polling. Set `SCRAPER_TOKEN` for bearer
99
- authentication, `SCRAPER_PROVIDERS` to select fallbacks, and `SCRAPER_DEMO_PAGE=true` for a
100
- small one-off tracking page. [HTTP contract](server/openapi.json) · [Configuration](.env.example).
170
+ Then pass `chromiumPath` or `trawlUrl` to `createTracker()`. The CLI and the server read
171
+ `TRACKING_CHROMIUM_PATH` and `FLARESOLVERR_URL`.
101
172
 
102
- The versioned container includes Chromium and the optional transports:
173
+ Where a carrier accepts a plain client, the adapter names itself with the package's default
174
+ User-Agent. Pass `userAgent`, or set `SCRAPER_USER_AGENT`, to send your own.
103
175
 
104
- ```sh
105
- docker run --rm -p 127.0.0.1:8080:8080 ghcr.io/plhery/universal-parcel-scraper:0.2.0
176
+ When the adapter finds no history, the lookup can move on to an aggregator. Only UPU is on
177
+ by default, for the postal numbers it can serve. The commercial ones are opt-in, and each
178
+ one you enable receives the tracking number:
179
+
180
+ ```js
181
+ createTracker({ providers: ['ParcelsApp', 'Ship24', '17TRACK', 'Postal Ninja', 'UPU'] });
106
182
  ```
107
183
 
108
- A [Home Assistant REST sensor](examples/home-assistant.yaml) can call the same API.
109
- [Node example](examples/node.mjs) · [Development](CONTRIBUTING.md).
184
+ [ARCHITECTURE.md](ARCHITECTURE.md) has the rest.
185
+
186
+ ## How much can it track?
187
+
188
+ A carrier count says what the catalog knows about. Whether a real parcel comes back with its
189
+ history is a different question, so each source was run on its own against the same
190
+ **100-carrier reference set**. The top bar counts a carrier when any source below it returned
191
+ a history.
110
192
 
111
- ## Data and limits
193
+ <!-- GENERATED:coverage -->
194
+ <picture>
195
+ <source media="(prefers-color-scheme: dark)" srcset="docs/assets/coverage-dark.svg">
196
+ <img src="docs/assets/coverage-light.svg" alt="Carriers with tracking history: This project, all fallbacks enabled 70, This project, dedicated adapters alone 51, ParcelsApp 50, Postal Ninja 41, 17TRACK 40, Ship24 38, UPU 11." width="760">
197
+ </picture>
112
198
 
113
- Lookups send the tracking number and any required postcode or capability URL to the selected
114
- carrier. Enabled commercial fallbacks receive the number; ParcelsApp also receives a supplied
115
- postcode. A configured TRAWL instance handles the requested pages. Browser pages can load the
116
- carrier's or provider's challenge resources. The library has no telemetry destination;
117
- HTTP logs contain route names and outcomes, never parcel inputs.
199
+ <details>
200
+ <summary>The same numbers as a table</summary>
118
201
 
119
- Consumers own polling and persistence. Respect the catalog's refresh limits and the upstream
120
- retry advice. Carrier sites can change or refuse automated requests; some need a postcode,
121
- a full tracking link, or an account and remain limited. Unresolved local clocks stay unresolved.
202
+ | Source | Carriers with history |
203
+ | --- | ---: |
204
+ | **Universal Parcel Scraper, all fallbacks enabled** | **70 / 100** |
205
+ | Dedicated adapters alone | 51 / 100 |
206
+ | ParcelsApp | 50 / 100 |
207
+ | Postal Ninja | 41 / 100 |
208
+ | 17TRACK | 40 / 100 |
209
+ | Ship24 | 38 / 100 |
210
+ | UPU | 11 / 100 |
211
+
212
+ </details>
213
+ <!-- /GENERATED:coverage -->
122
214
 
123
- The core is [Apache-2.0](LICENSE). The optional TRAWL derivative is [AGPL-3.0](trawl/LICENSE).
124
- Data and model credits are in [NOTICE](NOTICE).
215
+ The numbers come from [coverage.json](providers/coverage.json): the recorded outcome for each
216
+ carrier's comparison reference, partial histories included and alternate samples left out.
217
+ The set is curated. Read it as a comparison between sources, not as a market-share ranking
218
+ or a promise about today, because parcels expire and carrier sites change.
219
+ [Carrier-by-carrier results and limitations](providers/COVERAGE.md).
220
+
221
+ ## Next to a hosted tracking API
222
+
223
+ | | Universal Parcel Scraper | Hosted tracking API |
224
+ | --- | --- | --- |
225
+ | Account | None | Sign-up and an API key |
226
+ | Cost | Your own compute | A plan or a per-shipment price |
227
+ | Who sees the tracking number | The carrier, plus any fallback you enable | The vendor, then the carrier |
228
+ | Carriers | The [catalog](carriers/) in this repository | A much larger catalog |
229
+ | Updates | You poll | Webhooks |
230
+ | When a carrier changes its site | The adapter breaks until it is fixed here | The vendor deals with it |
231
+ | Hosting | Yours | Theirs |
232
+
233
+ If you want the widest catalog and nothing to run, a hosted API is the better choice. This
234
+ project is for when the numbers should stay on your side, or when a price per parcel makes
235
+ no sense for what you are building. Vendor catalog totals are not comparable with the
236
+ reference test above.
237
+
238
+ ## Privacy and limits
239
+
240
+ A lookup sends the tracking number to the carrier, along with the postcode or tracking link
241
+ when that carrier requires one. Fallbacks you enabled receive the number too, and ParcelsApp
242
+ also gets a postcode if you supplied it. Pages opened in a browser, locally or through TRAWL,
243
+ can load the carrier's or provider's challenge scripts. The library has no telemetry, and
244
+ the server's logs hold route names and outcomes, never parcel inputs.
245
+
246
+ This is scraping, so things break. Carriers redesign their sites and turn away automated
247
+ requests. A few need more than the number, such as a postcode or the link from the shipping
248
+ email, and the ones that require an account stay limited. Keep to the catalog's refresh
249
+ limits and to the retry advice that comes with an error. A scan time with no known UTC offset
250
+ is returned as the carrier wrote it, with no zone guessed.
251
+
252
+ ## Contributing
253
+
254
+ Adapters break when carriers change their sites. Each carrier lives in its own folder with
255
+ synthetic fixtures and offline tests, so a fix stays local to that folder. For a new carrier,
256
+ `npm run carrier:new` scaffolds the folder and [CONTRIBUTING.md](CONTRIBUTING.md) covers the
257
+ rest. Report security issues through [SECURITY.md](SECURITY.md).
258
+
259
+ ## License
260
+
261
+ The core is [Apache-2.0](LICENSE). The optional TRAWL image is a derivative under
262
+ [AGPL-3.0](trawl/LICENSE). Data and model credits are in [NOTICE](NOTICE).
@@ -205,7 +205,7 @@
205
205
  "minLength": 1
206
206
  },
207
207
  "canaryUrl": {
208
- "description": "Public, credential-free HTTPS URL the daily canary probes. Required for automatic carriers.",
208
+ "description": "Public, credential-free HTTPS URL the weekly canary probes. Required for automatic carriers.",
209
209
  "type": "string"
210
210
  },
211
211
  "shows": {
@@ -273,9 +273,11 @@
273
273
  ],
274
274
  "properties": {
275
275
  "field": {
276
- "description": "Input name; the generator checks it against the validators it accepts.",
277
- "type": "string",
278
- "minLength": 1
276
+ "description": "Input name. The generator checks that the validator is one this input accepts.",
277
+ "enum": [
278
+ "trackingUrl",
279
+ "postcode"
280
+ ]
279
281
  },
280
282
  "validator": {
281
283
  "type": "string",
package/data/catalog.json CHANGED
@@ -713,7 +713,7 @@
713
713
  "adapter": "gls-ch",
714
714
  "requirements": [
715
715
  {
716
- "field": "dpdPostcode",
716
+ "field": "postcode",
717
717
  "validator": "swissPostcode",
718
718
  "label": "Delivery postcode",
719
719
  "type": "text",
@@ -780,7 +780,7 @@
780
780
  "adapter": "dpd",
781
781
  "requirements": [
782
782
  {
783
- "field": "dpdPostcode",
783
+ "field": "postcode",
784
784
  "validator": "swissPostcode",
785
785
  "optional": true,
786
786
  "label": "Delivery postcode",
@@ -879,7 +879,7 @@
879
879
  "adapter": "mondial-relay",
880
880
  "requirements": [
881
881
  {
882
- "field": "dpdPostcode",
882
+ "field": "postcode",
883
883
  "whenTrackingNumber": "^(?![0-9]{26}$).*$",
884
884
  "validator": "francePostcode",
885
885
  "label": "Delivery postcode",
@@ -1268,7 +1268,7 @@
1268
1268
  "adapter": "heppner",
1269
1269
  "requirements": [
1270
1270
  {
1271
- "field": "dpdPostcode",
1271
+ "field": "postcode",
1272
1272
  "validator": "swissOrFrancePostcode",
1273
1273
  "label": "Delivery postcode",
1274
1274
  "type": "text",
@@ -1350,7 +1350,7 @@
1350
1350
  "adapter": "paack",
1351
1351
  "requirements": [
1352
1352
  {
1353
- "field": "dpdPostcode",
1353
+ "field": "postcode",
1354
1354
  "validator": "paackPostcode",
1355
1355
  "label": "Delivery postcode",
1356
1356
  "type": "text",
@@ -1545,7 +1545,7 @@
1545
1545
  "adapter": "gls-de",
1546
1546
  "requirements": [
1547
1547
  {
1548
- "field": "dpdPostcode",
1548
+ "field": "postcode",
1549
1549
  "validator": "swissOrFrancePostcode",
1550
1550
  "label": "Delivery postcode",
1551
1551
  "type": "text",
package/dist/app.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Helpers shaped for the parcel app this package was extracted from: its
3
+ * parcel view, carrier-name hints for provider results, and the clock helpers
4
+ * its sync uses. They change with that app and carry no stability promise;
5
+ * the tracking contract is the other entry points.
6
+ */
7
+ export * from './core/catalog/parcel.js';
8
+ export * from './core/catalog/hints.js';
9
+ export * from './core/time/result.js';
10
+ export { universalCarrierHints } from './providers/shared/hints.js';
11
+ export { recognitionAskedCarriers } from './core/catalog/recognition.js';
package/dist/app.js ADDED
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Helpers shaped for the parcel app this package was extracted from: its
3
+ * parcel view, carrier-name hints for provider results, and the clock helpers
4
+ * its sync uses. They change with that app and carry no stability promise;
5
+ * the tracking contract is the other entry points.
6
+ */
7
+ export * from './core/catalog/parcel.js';
8
+ export * from './core/catalog/hints.js';
9
+ export * from './core/time/result.js';
10
+ export { universalCarrierHints } from './providers/shared/hints.js';
11
+ export { recognitionAskedCarriers } from './core/catalog/recognition.js';