universal-parcel-scraper 0.11.0 → 0.12.0-main.336
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 +4 -4
- package/data/catalog.json +3 -3
- package/data/coverage-summary.json +3 -3
- package/data/detection-golden.json +140 -0
- package/dist/browser/scraper.js +22 -19
- package/dist/carriers/cne/adapter.d.ts +12 -0
- package/dist/carriers/cne/adapter.js +43 -0
- package/dist/carriers/cne/carrier.json +17 -5
- package/dist/carriers/cne/numbers.json +28 -2
- package/dist/carriers/cne/parser.d.ts +3 -0
- package/dist/carriers/cne/parser.js +74 -0
- package/dist/carriers/cne/statuses.json +63 -1
- package/dist/carriers/evri-uk/numbers.json +19 -2
- package/dist/carriers/intelcom/numbers.json +26 -0
- package/dist/carriers/intelcom/parser.js +9 -2
- package/dist/carriers/intelcom/statuses.json +9 -1
- package/dist/carriers/lbc-express/adapter.d.ts +13 -0
- package/dist/carriers/lbc-express/adapter.js +150 -0
- package/dist/carriers/lbc-express/carrier.json +20 -7
- package/dist/carriers/lbc-express/numbers.json +52 -0
- package/dist/carriers/lbc-express/parser.d.ts +3 -0
- package/dist/carriers/lbc-express/parser.js +68 -0
- package/dist/carriers/lbc-express/statuses.json +20 -1
- package/dist/carriers/nova-poshta/adapter.js +22 -5
- package/dist/carriers/nova-poshta/carrier.json +18 -6
- package/dist/carriers/nova-poshta/numbers.json +49 -0
- package/dist/carriers/nova-poshta/parser.d.ts +2 -0
- package/dist/carriers/nova-poshta/parser.js +98 -1
- package/dist/carriers/nova-poshta/statuses.json +93 -1
- package/dist/carriers/royal-mail/adapter.d.ts +3 -0
- package/dist/carriers/royal-mail/adapter.js +94 -47
- package/dist/carriers/royal-mail/numbers.json +13 -0
- package/dist/carriers/royal-mail/status.d.ts +1 -0
- package/dist/carriers/royal-mail/status.js +14 -0
- package/dist/carriers/royal-mail/statuses.json +151 -11
- package/dist/carriers/spx-ph/numbers.json +16 -0
- package/dist/carriers/spx-ph/parser.js +10 -4
- package/dist/carriers/spx-ph/statuses.json +56 -1
- package/dist/data/catalog.json +3 -3
- package/dist/generated/catalog.d.ts +3 -3
- package/dist/generated/catalog.js +3 -3
- package/dist/generated/registry.js +6 -2
- package/dist/providers/coverage.json +19 -15
- package/dist/server/openapi.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ The engine behind [Peek](https://peektracker.com) ([GitHub](https://github.com/p
|
|
|
13
13
|
[](LICENSE)
|
|
14
14
|
|
|
15
15
|
<!-- GENERATED:summary -->
|
|
16
|
-
**3,500+ carriers** through **
|
|
16
|
+
**3,500+ carriers** through **96 dedicated adapters** and **5 universal fallbacks**
|
|
17
17
|
|
|
18
18
|
<sub>119 catalog entries · 58 countries represented</sub>
|
|
19
19
|
<!-- /GENERATED:summary -->
|
|
@@ -33,9 +33,9 @@ its own as a command, a Node library or an HTTP server.
|
|
|
33
33
|
## Benchmark
|
|
34
34
|
|
|
35
35
|
<!-- GENERATED:coverage -->
|
|
36
|
-
In a curated benchmark of 113 carriers, dedicated adapters return tracking history for **
|
|
36
|
+
In a curated benchmark of 113 carriers, dedicated adapters return tracking history for **69**, rising to **82** with all fallbacks enabled. The best single aggregator returns 53.
|
|
37
37
|
|
|
38
|
-
<img src="docs/assets/coverage.svg" alt="Carriers with tracking history: This project, all fallbacks enabled
|
|
38
|
+
<img src="docs/assets/coverage.svg" alt="Carriers with tracking history: This project, all fallbacks enabled 82, This project, dedicated adapters alone 69, ParcelsApp 53, Postal Ninja 43, 17TRACK 40, Ship24 42, UPU 12." width="760">
|
|
39
39
|
<!-- /GENERATED:coverage -->
|
|
40
40
|
|
|
41
41
|
[Method and results per carrier](providers/COVERAGE.md)
|
|
@@ -149,7 +149,7 @@ createTracker({ providers: ['ParcelsApp', 'Ship24', '17TRACK', 'Postal Ninja', '
|
|
|
149
149
|
<!-- GENERATED:stages -->
|
|
150
150
|
<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">
|
|
151
151
|
|
|
152
|
-
The carrier folders record 1,
|
|
152
|
+
The carrier folders record 1,779 such statuses, each filed under one stage.
|
|
153
153
|
<!-- /GENERATED:stages -->
|
|
154
154
|
|
|
155
155
|
Wording nobody recorded yet goes through a classifier that reads seven European languages.
|
package/data/catalog.json
CHANGED
|
@@ -4054,7 +4054,7 @@
|
|
|
4054
4054
|
"timezone": "UTC",
|
|
4055
4055
|
"tracking": {
|
|
4056
4056
|
"mode": "automatic",
|
|
4057
|
-
"adapter": "
|
|
4057
|
+
"adapter": "cne"
|
|
4058
4058
|
},
|
|
4059
4059
|
"canaryUrl": "https://www.cne.com/track",
|
|
4060
4060
|
"trackingUrlTemplate": "https://www.cne.com/en/track?no={trackingNumber}",
|
|
@@ -4316,10 +4316,10 @@
|
|
|
4316
4316
|
"timezone": "Asia/Manila",
|
|
4317
4317
|
"tracking": {
|
|
4318
4318
|
"mode": "automatic",
|
|
4319
|
-
"adapter": "
|
|
4319
|
+
"adapter": "lbc-express",
|
|
4320
|
+
"localClocks": true
|
|
4320
4321
|
},
|
|
4321
4322
|
"canaryUrl": "https://www.lbcexpress.com/track/",
|
|
4322
|
-
"trackingUrlTemplate": "https://www.lbcexpress.com/track/?tracking_no={trackingNumber}",
|
|
4323
4323
|
"linkRules": [
|
|
4324
4324
|
{
|
|
4325
4325
|
"domains": [
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"catalog": 119,
|
|
3
|
-
"activeAdapters":
|
|
3
|
+
"activeAdapters": 96,
|
|
4
4
|
"countries": 58,
|
|
5
5
|
"referenceCarriers": 113,
|
|
6
6
|
"comparison": {
|
|
7
|
-
"combined":
|
|
8
|
-
"direct":
|
|
7
|
+
"combined": 82,
|
|
8
|
+
"direct": 69,
|
|
9
9
|
"providers": {
|
|
10
10
|
"ParcelsApp": 53,
|
|
11
11
|
"Postal Ninja": 43,
|
|
@@ -837,6 +837,28 @@
|
|
|
837
837
|
"ciblex"
|
|
838
838
|
]
|
|
839
839
|
},
|
|
840
|
+
{
|
|
841
|
+
"input": "100000000001",
|
|
842
|
+
"carrier": "unknown",
|
|
843
|
+
"confidence": "low",
|
|
844
|
+
"candidates": [
|
|
845
|
+
"fedex",
|
|
846
|
+
"dpd-fr",
|
|
847
|
+
"mondial-relay",
|
|
848
|
+
"colis-prive",
|
|
849
|
+
"mrw",
|
|
850
|
+
"purolator",
|
|
851
|
+
"sf-express",
|
|
852
|
+
"sto",
|
|
853
|
+
"zto",
|
|
854
|
+
"yamato",
|
|
855
|
+
"j-and-t",
|
|
856
|
+
"lbc-express"
|
|
857
|
+
],
|
|
858
|
+
"asked": [
|
|
859
|
+
"colis-prive"
|
|
860
|
+
]
|
|
861
|
+
},
|
|
840
862
|
{
|
|
841
863
|
"input": "1000478495775",
|
|
842
864
|
"carrier": "unknown",
|
|
@@ -1336,6 +1358,28 @@
|
|
|
1336
1358
|
"colis-prive"
|
|
1337
1359
|
]
|
|
1338
1360
|
},
|
|
1361
|
+
{
|
|
1362
|
+
"input": "142062960271",
|
|
1363
|
+
"carrier": "unknown",
|
|
1364
|
+
"confidence": "low",
|
|
1365
|
+
"candidates": [
|
|
1366
|
+
"fedex",
|
|
1367
|
+
"dpd-fr",
|
|
1368
|
+
"mondial-relay",
|
|
1369
|
+
"colis-prive",
|
|
1370
|
+
"mrw",
|
|
1371
|
+
"purolator",
|
|
1372
|
+
"sf-express",
|
|
1373
|
+
"sto",
|
|
1374
|
+
"zto",
|
|
1375
|
+
"yamato",
|
|
1376
|
+
"j-and-t",
|
|
1377
|
+
"lbc-express"
|
|
1378
|
+
],
|
|
1379
|
+
"asked": [
|
|
1380
|
+
"colis-prive"
|
|
1381
|
+
]
|
|
1382
|
+
},
|
|
1339
1383
|
{
|
|
1340
1384
|
"input": "1595193512495",
|
|
1341
1385
|
"carrier": "unknown",
|
|
@@ -1743,6 +1787,30 @@
|
|
|
1743
1787
|
"postlogistics"
|
|
1744
1788
|
]
|
|
1745
1789
|
},
|
|
1790
|
+
{
|
|
1791
|
+
"input": "20710517491331",
|
|
1792
|
+
"carrier": "unknown",
|
|
1793
|
+
"confidence": "low",
|
|
1794
|
+
"candidates": [
|
|
1795
|
+
"dpd",
|
|
1796
|
+
"relais-colis",
|
|
1797
|
+
"ciblex",
|
|
1798
|
+
"hermes-de",
|
|
1799
|
+
"seur",
|
|
1800
|
+
"brt",
|
|
1801
|
+
"delhivery",
|
|
1802
|
+
"dpd-de",
|
|
1803
|
+
"dpd-uk",
|
|
1804
|
+
"nova-poshta"
|
|
1805
|
+
],
|
|
1806
|
+
"asked": [
|
|
1807
|
+
"dpd",
|
|
1808
|
+
"seur",
|
|
1809
|
+
"brt",
|
|
1810
|
+
"hermes-de",
|
|
1811
|
+
"relais-colis"
|
|
1812
|
+
]
|
|
1813
|
+
},
|
|
1746
1814
|
{
|
|
1747
1815
|
"input": "2103/11207088",
|
|
1748
1816
|
"carrier": "nacex",
|
|
@@ -2333,6 +2401,18 @@
|
|
|
2333
2401
|
"relais-colis"
|
|
2334
2402
|
]
|
|
2335
2403
|
},
|
|
2404
|
+
{
|
|
2405
|
+
"input": "3A5V609829713",
|
|
2406
|
+
"carrier": "unknown",
|
|
2407
|
+
"confidence": "none",
|
|
2408
|
+
"candidates": []
|
|
2409
|
+
},
|
|
2410
|
+
{
|
|
2411
|
+
"input": "3A5V721250488",
|
|
2412
|
+
"carrier": "unknown",
|
|
2413
|
+
"confidence": "none",
|
|
2414
|
+
"candidates": []
|
|
2415
|
+
},
|
|
2336
2416
|
{
|
|
2337
2417
|
"input": "3SA5210528875",
|
|
2338
2418
|
"carrier": "spring-gds",
|
|
@@ -2941,6 +3021,29 @@
|
|
|
2941
3021
|
"ciblex"
|
|
2942
3022
|
]
|
|
2943
3023
|
},
|
|
3024
|
+
{
|
|
3025
|
+
"input": "59001766134310",
|
|
3026
|
+
"carrier": "unknown",
|
|
3027
|
+
"confidence": "low",
|
|
3028
|
+
"candidates": [
|
|
3029
|
+
"dpd",
|
|
3030
|
+
"relais-colis",
|
|
3031
|
+
"ciblex",
|
|
3032
|
+
"seur",
|
|
3033
|
+
"brt",
|
|
3034
|
+
"delhivery",
|
|
3035
|
+
"dpd-de",
|
|
3036
|
+
"dpd-uk",
|
|
3037
|
+
"nova-poshta"
|
|
3038
|
+
],
|
|
3039
|
+
"asked": [
|
|
3040
|
+
"dpd",
|
|
3041
|
+
"seur",
|
|
3042
|
+
"brt",
|
|
3043
|
+
"relais-colis",
|
|
3044
|
+
"ciblex"
|
|
3045
|
+
]
|
|
3046
|
+
},
|
|
2944
3047
|
{
|
|
2945
3048
|
"input": "5N30053962734",
|
|
2946
3049
|
"carrier": "la-poste",
|
|
@@ -4536,6 +4639,18 @@
|
|
|
4536
4639
|
"dhl"
|
|
4537
4640
|
]
|
|
4538
4641
|
},
|
|
4642
|
+
{
|
|
4643
|
+
"input": "CRIN26062320118472",
|
|
4644
|
+
"carrier": "unknown",
|
|
4645
|
+
"confidence": "none",
|
|
4646
|
+
"candidates": []
|
|
4647
|
+
},
|
|
4648
|
+
{
|
|
4649
|
+
"input": "CRIN26082718020483",
|
|
4650
|
+
"carrier": "unknown",
|
|
4651
|
+
"confidence": "none",
|
|
4652
|
+
"candidates": []
|
|
4653
|
+
},
|
|
4539
4654
|
{
|
|
4540
4655
|
"input": "CV000562646ES",
|
|
4541
4656
|
"carrier": "unknown",
|
|
@@ -5013,6 +5128,15 @@
|
|
|
5013
5128
|
"hermes-de"
|
|
5014
5129
|
]
|
|
5015
5130
|
},
|
|
5131
|
+
{
|
|
5132
|
+
"input": "H00RVA0538924343",
|
|
5133
|
+
"carrier": "unknown",
|
|
5134
|
+
"confidence": "low",
|
|
5135
|
+
"candidates": [
|
|
5136
|
+
"evri",
|
|
5137
|
+
"evri-uk"
|
|
5138
|
+
]
|
|
5139
|
+
},
|
|
5016
5140
|
{
|
|
5017
5141
|
"input": "H0595C0000314450",
|
|
5018
5142
|
"carrier": "unknown",
|
|
@@ -5772,6 +5896,14 @@
|
|
|
5772
5896
|
"spx-ph"
|
|
5773
5897
|
]
|
|
5774
5898
|
},
|
|
5899
|
+
{
|
|
5900
|
+
"input": "PH2542959127193",
|
|
5901
|
+
"carrier": "unknown",
|
|
5902
|
+
"confidence": "low",
|
|
5903
|
+
"candidates": [
|
|
5904
|
+
"spx-ph"
|
|
5905
|
+
]
|
|
5906
|
+
},
|
|
5775
5907
|
{
|
|
5776
5908
|
"input": "PHAS9C0419962810128017D",
|
|
5777
5909
|
"carrier": "correos-spain",
|
|
@@ -6663,6 +6795,14 @@
|
|
|
6663
6795
|
"yunexpress"
|
|
6664
6796
|
]
|
|
6665
6797
|
},
|
|
6798
|
+
{
|
|
6799
|
+
"input": "YT660296803GB",
|
|
6800
|
+
"carrier": "royal-mail",
|
|
6801
|
+
"confidence": "high",
|
|
6802
|
+
"candidates": [
|
|
6803
|
+
"royal-mail"
|
|
6804
|
+
]
|
|
6805
|
+
},
|
|
6666
6806
|
{
|
|
6667
6807
|
"input": "YT8879092508074",
|
|
6668
6808
|
"carrier": "yto",
|
package/dist/browser/scraper.js
CHANGED
|
@@ -3131,7 +3131,7 @@ var ce = {
|
|
|
3131
3131
|
timezone: "UTC",
|
|
3132
3132
|
tracking: {
|
|
3133
3133
|
mode: "automatic",
|
|
3134
|
-
adapter: "
|
|
3134
|
+
adapter: "cne"
|
|
3135
3135
|
},
|
|
3136
3136
|
canaryUrl: "https://www.cne.com/track",
|
|
3137
3137
|
trackingUrlTemplate: "https://www.cne.com/en/track?no={trackingNumber}",
|
|
@@ -3312,10 +3312,10 @@ var ce = {
|
|
|
3312
3312
|
timezone: "Asia/Manila",
|
|
3313
3313
|
tracking: {
|
|
3314
3314
|
mode: "automatic",
|
|
3315
|
-
adapter: "
|
|
3315
|
+
adapter: "lbc-express",
|
|
3316
|
+
localClocks: !0
|
|
3316
3317
|
},
|
|
3317
3318
|
canaryUrl: "https://www.lbcexpress.com/track/",
|
|
3318
|
-
trackingUrlTemplate: "https://www.lbcexpress.com/track/?tracking_no={trackingNumber}",
|
|
3319
3319
|
linkRules: [{
|
|
3320
3320
|
domains: ["lbcexpress.com"],
|
|
3321
3321
|
params: ["tracking_no"]
|
|
@@ -8288,7 +8288,10 @@ var gs = { carriers: [
|
|
|
8288
8288
|
"17TRACK": "no_history",
|
|
8289
8289
|
"Postal Ninja": 4,
|
|
8290
8290
|
UPU: "no_history"
|
|
8291
|
-
} }
|
|
8291
|
+
} }, {
|
|
8292
|
+
note: "Alternate public reference returned eight identity-bound full-history scans in an interactive official session; fresh automated browser retrieval remains denied: https://www.reddit.com/r/royalmail/comments/1wh27pw/cant_track_my_shipment/",
|
|
8293
|
+
results: {}
|
|
8294
|
+
}]
|
|
8292
8295
|
},
|
|
8293
8296
|
{
|
|
8294
8297
|
carrier: "swiss-post",
|
|
@@ -9728,7 +9731,7 @@ var gs = { carriers: [
|
|
|
9728
9731
|
direct: "No adapter",
|
|
9729
9732
|
sample: "Blocked",
|
|
9730
9733
|
references: [{
|
|
9731
|
-
note: "
|
|
9734
|
+
note: "The official interactive navigation flow obtained rotating keys and six dated parcel scans. Fresh automated browser sessions are denied the keys: https://www.trustpilot.com/review/printerval.co.uk",
|
|
9732
9735
|
results: {
|
|
9733
9736
|
Ship24: "unverified",
|
|
9734
9737
|
ParcelsApp: "unverified",
|
|
@@ -9756,9 +9759,9 @@ var gs = { carriers: [
|
|
|
9756
9759
|
{
|
|
9757
9760
|
carrier: "intelcom",
|
|
9758
9761
|
direct: "Yes (Canada)",
|
|
9759
|
-
sample: "Not found",
|
|
9762
|
+
sample: "Not found; alternate ✓ 6",
|
|
9760
9763
|
references: [{
|
|
9761
|
-
note: "
|
|
9764
|
+
note: "Comparison reference returned not_found: https://www.justanswer.com/software/vud02-intelcom-package-not-delivered-address-dispute.html. An alternate public reference returned six scans: https://intelcom.ca/en/track-your-package/?tracking-id=CRIN26082718020483",
|
|
9762
9765
|
results: {
|
|
9763
9766
|
Ship24: "unverified",
|
|
9764
9767
|
ParcelsApp: "unverified",
|
|
@@ -9800,10 +9803,10 @@ var gs = { carriers: [
|
|
|
9800
9803
|
},
|
|
9801
9804
|
{
|
|
9802
9805
|
carrier: "lbc-express",
|
|
9803
|
-
direct: "
|
|
9804
|
-
sample: "
|
|
9806
|
+
direct: "Yes (local Chromium)",
|
|
9807
|
+
sample: "Unverified; alternate ✓ 6",
|
|
9805
9808
|
references: [{
|
|
9806
|
-
note: "
|
|
9809
|
+
note: "Original comparison reference has not been retried through the implemented browser flow: https://www.trustpilot.com/review/www.lbcexpress.com. An alternate public reference returned six unique date-only scans in a fresh automated browser: https://uk.trustpilot.com/review/www.lbcexpress.com",
|
|
9807
9810
|
results: {
|
|
9808
9811
|
Ship24: "unverified",
|
|
9809
9812
|
ParcelsApp: "unverified",
|
|
@@ -9815,10 +9818,10 @@ var gs = { carriers: [
|
|
|
9815
9818
|
},
|
|
9816
9819
|
{
|
|
9817
9820
|
carrier: "nova-poshta",
|
|
9818
|
-
direct: "Yes (Ukraine
|
|
9819
|
-
sample: "
|
|
9821
|
+
direct: "Yes (Ukraine)",
|
|
9822
|
+
sample: "✓ 10",
|
|
9820
9823
|
references: [{
|
|
9821
|
-
note: "
|
|
9824
|
+
note: "Public Ukrainian waybill returned ten identity-bound movement scans: https://www.vidhuk.ua/uk/novaya-pochta-nova-poshta/review-2377100. A redirected import waybill returned twenty-two scans: https://www.vidhuk.ua/uk/novaya-pochta-nova-poshta/review-2376836",
|
|
9822
9825
|
results: {
|
|
9823
9826
|
Ship24: "unverified",
|
|
9824
9827
|
ParcelsApp: "unverified",
|
|
@@ -9831,9 +9834,9 @@ var gs = { carriers: [
|
|
|
9831
9834
|
{
|
|
9832
9835
|
carrier: "spx-ph",
|
|
9833
9836
|
direct: "Yes (Philippines)",
|
|
9834
|
-
sample: "
|
|
9837
|
+
sample: "✓ 10",
|
|
9835
9838
|
references: [{
|
|
9836
|
-
note: "
|
|
9839
|
+
note: "Public marketplace reference returned ten visible identity-bound scans: https://www.reddit.com/r/ShopeePH/comments/1i67ifq/",
|
|
9837
9840
|
results: {
|
|
9838
9841
|
Ship24: "unverified",
|
|
9839
9842
|
ParcelsApp: "unverified",
|
|
@@ -9845,10 +9848,10 @@ var gs = { carriers: [
|
|
|
9845
9848
|
},
|
|
9846
9849
|
{
|
|
9847
9850
|
carrier: "cne",
|
|
9848
|
-
direct: "
|
|
9849
|
-
sample: "
|
|
9851
|
+
direct: "Yes",
|
|
9852
|
+
sample: "✓ 9",
|
|
9850
9853
|
references: [{
|
|
9851
|
-
note: "
|
|
9854
|
+
note: "Public CNE reference returned nine movement scans with unresolved unqualified clocks: https://nz.trustpilot.com/review/oriental-secrets.com. A second public reference returned a registration scan: https://ca.trustpilot.com/review/firalli.com?page=8",
|
|
9852
9855
|
results: {
|
|
9853
9856
|
Ship24: "unverified",
|
|
9854
9857
|
ParcelsApp: "unverified",
|
|
@@ -9863,7 +9866,7 @@ var gs = { carriers: [
|
|
|
9863
9866
|
direct: "No adapter",
|
|
9864
9867
|
sample: "Blocked",
|
|
9865
9868
|
references: [{
|
|
9866
|
-
note: "
|
|
9869
|
+
note: "The current corporate tracking form posts to the public tracking service, which returns access denied. This is not evidence of parcel absence.",
|
|
9867
9870
|
results: {
|
|
9868
9871
|
Ship24: "unverified",
|
|
9869
9872
|
ParcelsApp: "unverified",
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AdapterFactory, TrackingContext } from '../../core/adapter/index.js';
|
|
2
|
+
import { type StepRecorder } from '../../core/telemetry/index.js';
|
|
3
|
+
export declare class CneTracker {
|
|
4
|
+
private readonly options;
|
|
5
|
+
constructor(options?: {
|
|
6
|
+
fetcher?: typeof fetch;
|
|
7
|
+
recorder?: StepRecorder;
|
|
8
|
+
userAgent?: string;
|
|
9
|
+
});
|
|
10
|
+
fetch(raw: string, context?: TrackingContext): Promise<import("../../index.js").CarrierResult>;
|
|
11
|
+
}
|
|
12
|
+
export declare const adapter: AdapterFactory;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { TransportError, UpstreamHttpError } from '../../core/errors/index.js';
|
|
3
|
+
import { runSteps } from '../../core/runner/index.js';
|
|
4
|
+
import { NOOP_RECORDER } from '../../core/telemetry/index.js';
|
|
5
|
+
import { fetchBounded, parseJsonBytes, userAgentOf } from '../../core/transport/index.js';
|
|
6
|
+
import { normalizeCneNumber, parseCne } from './parser.js';
|
|
7
|
+
const ENDPOINT = 'https://wapi.cne.com/tracking/officialWebsite';
|
|
8
|
+
// The anonymous website's public WASM client prefixes inputs before MD5:
|
|
9
|
+
// https://www.cne.com/assets/wasm/wasm_md5_bg-074da2d9.wasm
|
|
10
|
+
// This is a form-protocol constant, not an account credential. Keep its space.
|
|
11
|
+
const PUBLIC_FORM_PREFIX = '01979c0bfa33772dbb3f5c07bd651b21 ';
|
|
12
|
+
const sign = (input) => createHash('md5').update(PUBLIC_FORM_PREFIX + input).digest('hex');
|
|
13
|
+
export class CneTracker {
|
|
14
|
+
options;
|
|
15
|
+
constructor(options = {}) {
|
|
16
|
+
this.options = options;
|
|
17
|
+
}
|
|
18
|
+
async fetch(raw, context = {}) {
|
|
19
|
+
const number = normalizeCneNumber(raw);
|
|
20
|
+
return runSteps({ carrier: 'cne', signal: context.signal, budgetMs: context.budgetMs ?? 15_000,
|
|
21
|
+
recorder: this.options.recorder ?? NOOP_RECORDER }, [{ id: 'direct', run: async ({ signal, remainingMs }) => {
|
|
22
|
+
const timestamp = Date.now().toString();
|
|
23
|
+
try {
|
|
24
|
+
const { bytes } = await fetchBounded(`${ENDPOINT}?t=${timestamp}`, { method: 'POST', signal,
|
|
25
|
+
headers: { 'Content-Type': 'application/json', Accept: 'application/json, text/plain, */*',
|
|
26
|
+
Origin: 'https://www.cne.com', Referer: 'https://www.cne.com/', 'User-Agent': userAgentOf(this.options.userAgent),
|
|
27
|
+
signature: sign(timestamp.substring(0, 12) + number) },
|
|
28
|
+
body: JSON.stringify({ lan: 'en', logisticsNo: number, md5: sign(number + 'Track') }),
|
|
29
|
+
}, { provider: 'CNE Express', timeoutMs: Math.max(1, Math.floor(remainingMs)), maxBytes: 1_000_000, fetcher: this.options.fetcher });
|
|
30
|
+
return parseCne(parseJsonBytes(bytes, 'CNE Express'), number);
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
if (error instanceof UpstreamHttpError && [404, 410].includes(error.status))
|
|
34
|
+
throw new TransportError('CNE Express', 'CNE tracking endpoint is unavailable', { cause: error });
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
} }]);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export const adapter = environment => {
|
|
41
|
+
const tracker = new CneTracker({ fetcher: environment.fetcher, recorder: environment.recorder, userAgent: environment.userAgent });
|
|
42
|
+
return { id: 'cne', recordsSteps: true, steps: ['direct'], track: (input, context) => tracker.fetch(input.number, context) };
|
|
43
|
+
};
|
|
@@ -17,10 +17,15 @@
|
|
|
17
17
|
"timezone": "UTC",
|
|
18
18
|
"tracking": {
|
|
19
19
|
"mode": "automatic",
|
|
20
|
-
"adapter": "
|
|
21
|
-
"steps": [
|
|
20
|
+
"adapter": "cne",
|
|
21
|
+
"steps": [
|
|
22
|
+
"direct"
|
|
23
|
+
]
|
|
22
24
|
},
|
|
23
|
-
"capabilities": [
|
|
25
|
+
"capabilities": [
|
|
26
|
+
"history",
|
|
27
|
+
"location"
|
|
28
|
+
],
|
|
24
29
|
"portal": {
|
|
25
30
|
"url": "https://www.cne.com/en/track?no={trackingNumber}",
|
|
26
31
|
"canaryUrl": "https://www.cne.com/track",
|
|
@@ -30,8 +35,15 @@
|
|
|
30
35
|
"location",
|
|
31
36
|
"delivery_partner"
|
|
32
37
|
],
|
|
33
|
-
"scraped": [
|
|
34
|
-
|
|
38
|
+
"scraped": [
|
|
39
|
+
"status",
|
|
40
|
+
"history",
|
|
41
|
+
"location"
|
|
42
|
+
],
|
|
43
|
+
"discarded": [
|
|
44
|
+
"transfer_reference",
|
|
45
|
+
"merchant_reference"
|
|
46
|
+
],
|
|
35
47
|
"unavailable": [
|
|
36
48
|
"eta",
|
|
37
49
|
"weight"
|
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"carrier": "cne",
|
|
3
|
-
"
|
|
4
|
-
|
|
3
|
+
"records": [
|
|
4
|
+
{
|
|
5
|
+
"number": "3A5V721250488",
|
|
6
|
+
"role": "shipment",
|
|
7
|
+
"evidence": "public_shipment_report",
|
|
8
|
+
"source": {
|
|
9
|
+
"url": "https://ca.trustpilot.com/review/firalli.com?page=8"
|
|
10
|
+
},
|
|
11
|
+
"expect": {
|
|
12
|
+
"carrier": "unknown",
|
|
13
|
+
"confidence": "none"
|
|
14
|
+
},
|
|
15
|
+
"note": "Customer report explicitly attributes the number to CNE. Requires explicit carrier selection."
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"number": "3A5V609829713",
|
|
19
|
+
"role": "shipment",
|
|
20
|
+
"evidence": "public_shipment_report",
|
|
21
|
+
"source": {
|
|
22
|
+
"url": "https://nz.trustpilot.com/review/oriental-secrets.com"
|
|
23
|
+
},
|
|
24
|
+
"expect": {
|
|
25
|
+
"carrier": "unknown",
|
|
26
|
+
"confidence": "none"
|
|
27
|
+
},
|
|
28
|
+
"note": "Customer report explicitly attributes the number to CNE and describes a return."
|
|
29
|
+
}
|
|
30
|
+
]
|
|
5
31
|
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { normalizeTrackingNumber } from '../../core/detection/index.js';
|
|
2
|
+
import { IndeterminateError, InvalidInputError, NotFoundError, SchemaError } from '../../core/errors/index.js';
|
|
3
|
+
import { classifyWording, languageStageStatus } from '../../core/status/index.js';
|
|
4
|
+
import { explicitOffsetTime } from '../../core/time/index.js';
|
|
5
|
+
import { clean, cleanScalar } from '../../core/transport/index.js';
|
|
6
|
+
import { isRecord } from '../../core/types.js';
|
|
7
|
+
const PROVIDER = 'CNE Express';
|
|
8
|
+
const WORDING = {
|
|
9
|
+
'Parcel Infomation Received': 'registered', 'Picked Up by Courier': 'accepted',
|
|
10
|
+
'Shipment Loaded': 'in_transit', 'Departed CNE Facility': 'in_transit',
|
|
11
|
+
'Arrived at CNE Facility': 'in_transit', 'Inbound scan': 'in_transit',
|
|
12
|
+
'Shipment Processed': 'in_transit', 'Sorting failed []': 'exception',
|
|
13
|
+
"It was returned to customer's address by CNE": 'returned',
|
|
14
|
+
};
|
|
15
|
+
export function normalizeCneNumber(raw) {
|
|
16
|
+
const number = normalizeTrackingNumber(raw);
|
|
17
|
+
if (!/^[A-Z0-9]{6,40}$/.test(number))
|
|
18
|
+
throw new InvalidInputError(PROVIDER, 'CNE requires an alphanumeric parcel reference');
|
|
19
|
+
return number;
|
|
20
|
+
}
|
|
21
|
+
function scanTime(value) {
|
|
22
|
+
const match = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?(?:Z|[+-](\d{2}):?(\d{2}))$/i.exec(value);
|
|
23
|
+
if (!match || (match[1] !== undefined && (Number(match[1]) > 14 || Number(match[2]) > 59
|
|
24
|
+
|| (Number(match[1]) === 14 && Number(match[2]) !== 0))))
|
|
25
|
+
return null;
|
|
26
|
+
return explicitOffsetTime(value);
|
|
27
|
+
}
|
|
28
|
+
function scan(raw) {
|
|
29
|
+
if (!isRecord(raw) || !clean(raw.details, 1000))
|
|
30
|
+
throw new SchemaError(PROVIDER, 'CNE returned an invalid movement');
|
|
31
|
+
const description = clean(raw.details, 1000), clock = clean(raw.date, 64);
|
|
32
|
+
const location = clean(raw.place, 200), stage = WORDING[description];
|
|
33
|
+
const mapped = stage ? { stage, source: 'carrier_map' } : classifyWording(description, 'pending');
|
|
34
|
+
const time = scanTime(clock);
|
|
35
|
+
return { description, ...(location ? { location } : {}),
|
|
36
|
+
...(time ? { time: time.iso } : clock ? { provider_time_text: clock } : {}),
|
|
37
|
+
...(mapped.source !== 'none' ? { stage: mapped.stage, stage_source: mapped.source } : {}),
|
|
38
|
+
...(cleanScalar(raw.state) ? { provider_code: cleanScalar(raw.state) } : {}) };
|
|
39
|
+
}
|
|
40
|
+
export function parseCne(payload, raw) {
|
|
41
|
+
const number = normalizeCneNumber(raw);
|
|
42
|
+
if (!isRecord(payload) || typeof payload.ReturnValue !== 'number')
|
|
43
|
+
throw new SchemaError(PROVIDER, 'CNE returned an invalid envelope');
|
|
44
|
+
if (payload.ReturnValue !== 1) {
|
|
45
|
+
if (payload.ReturnValue === 0 && payload.cMess === '订单不存在'
|
|
46
|
+
&& payload.Response_Info === undefined && payload.trackingEventList === undefined)
|
|
47
|
+
throw new NotFoundError(PROVIDER);
|
|
48
|
+
throw new IndeterminateError(PROVIDER, 'CNE did not complete the lookup');
|
|
49
|
+
}
|
|
50
|
+
const identity = payload.Response_Info;
|
|
51
|
+
if (!isRecord(identity) || clean(identity.trackingNbr) !== number)
|
|
52
|
+
throw new SchemaError(PROVIDER, 'CNE returned a different shipment');
|
|
53
|
+
// These are echoed source identifiers, unlike referNbr and transNbr, which
|
|
54
|
+
// may be merchant and downstream references. Do not follow those aliases.
|
|
55
|
+
if (identity.Number_t !== undefined && clean(identity.Number_t) !== number)
|
|
56
|
+
throw new SchemaError(PROVIDER, 'CNE returned conflicting shipment identifiers');
|
|
57
|
+
if (!Array.isArray(payload.trackingEventList) || payload.trackingEventList.length > 1000)
|
|
58
|
+
throw new SchemaError(PROVIDER, 'CNE returned an invalid history');
|
|
59
|
+
if (!payload.trackingEventList.length)
|
|
60
|
+
throw new IndeterminateError(PROVIDER, 'CNE returned no shipment movements');
|
|
61
|
+
// The official client reverses the oldest-first array. Preserve that order
|
|
62
|
+
// even when its unqualified cross-border clocks cannot resolve to instants.
|
|
63
|
+
const events = payload.trackingEventList.map(scan).reverse();
|
|
64
|
+
const latest = events[0];
|
|
65
|
+
const summaryStage = cleanScalar(identity.status) === '8' ? 'returned' : undefined;
|
|
66
|
+
const stage = summaryStage ?? latest.stage;
|
|
67
|
+
const source = summaryStage ? 'carrier_map' : latest.stage_source;
|
|
68
|
+
const country = clean(identity.Destination, 64).toUpperCase();
|
|
69
|
+
return { status: stage ? languageStageStatus(stage) : 'unknown',
|
|
70
|
+
...(stage ? { current_stage: stage, current_stage_source: source } : {}),
|
|
71
|
+
last_status_text: latest.description, last_update: latest.time ?? null,
|
|
72
|
+
...(stage === 'delivered' && latest.time ? { delivered_at: latest.time } : {}),
|
|
73
|
+
...(/^[A-Z]{2}$/.test(country) ? { destination_country: country } : {}), events: events.slice(0, 100) };
|
|
74
|
+
}
|
|
@@ -1,4 +1,66 @@
|
|
|
1
1
|
{
|
|
2
2
|
"carrier": "cne",
|
|
3
|
-
"entries": [
|
|
3
|
+
"entries": [
|
|
4
|
+
{
|
|
5
|
+
"wording": "Parcel Infomation Received",
|
|
6
|
+
"language": "en",
|
|
7
|
+
"stage": "registered",
|
|
8
|
+
"confirmedBy": "live"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"wording": "Picked Up by Courier",
|
|
12
|
+
"language": "en",
|
|
13
|
+
"stage": "accepted",
|
|
14
|
+
"confirmedBy": "live"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"wording": "Shipment Loaded",
|
|
18
|
+
"language": "en",
|
|
19
|
+
"stage": "in_transit",
|
|
20
|
+
"confirmedBy": "live"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"wording": "Departed CNE Facility",
|
|
24
|
+
"language": "en",
|
|
25
|
+
"stage": "in_transit",
|
|
26
|
+
"confirmedBy": "live"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"wording": "Arrived at CNE Facility",
|
|
30
|
+
"language": "en",
|
|
31
|
+
"stage": "in_transit",
|
|
32
|
+
"confirmedBy": "live"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"wording": "Inbound scan",
|
|
36
|
+
"language": "en",
|
|
37
|
+
"stage": "in_transit",
|
|
38
|
+
"confirmedBy": "live"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"wording": "Shipment Processed",
|
|
42
|
+
"language": "en",
|
|
43
|
+
"stage": "in_transit",
|
|
44
|
+
"confirmedBy": "live"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"wording": "Sorting failed []",
|
|
48
|
+
"language": "en",
|
|
49
|
+
"stage": "exception",
|
|
50
|
+
"confirmedBy": "live"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"wording": "It was returned to customer's address by CNE",
|
|
54
|
+
"language": "en",
|
|
55
|
+
"stage": "returned",
|
|
56
|
+
"confirmedBy": "live"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"code": "8",
|
|
60
|
+
"wording": "退件",
|
|
61
|
+
"language": "zh",
|
|
62
|
+
"stage": "returned",
|
|
63
|
+
"confirmedBy": "live"
|
|
64
|
+
}
|
|
65
|
+
]
|
|
4
66
|
}
|