universal-parcel-scraper 0.63.0-main.816 → 0.64.0-main.817
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 +2 -2
- package/data/catalog.json +8 -2
- package/data/coverage-summary.json +1 -1
- package/data/detection-golden.json +19 -3
- package/dist/browser/scraper.js +9 -3
- package/dist/carriers/j-and-t-cargo/adapter.d.ts +12 -0
- package/dist/carriers/j-and-t-cargo/adapter.js +51 -0
- package/dist/carriers/j-and-t-cargo/carrier.json +17 -11
- package/dist/carriers/j-and-t-cargo/numbers.json +5 -4
- package/dist/carriers/j-and-t-cargo/parser.d.ts +8 -0
- package/dist/carriers/j-and-t-cargo/parser.js +124 -0
- package/dist/carriers/j-and-t-cargo/status.d.ts +4 -0
- package/dist/carriers/j-and-t-cargo/status.js +4 -0
- package/dist/carriers/j-and-t-cargo/statuses.json +8 -1
- package/dist/carriers/ups/adapter.d.ts +1 -1
- package/dist/carriers/ups/adapter.js +54 -26
- package/dist/core/catalog/statusMaps.js +2 -1
- package/dist/core/detection/jntCargo.d.ts +5 -0
- package/dist/core/detection/jntCargo.js +9 -0
- package/dist/core/transport/trawl.d.ts +2 -0
- package/dist/core/transport/trawl.js +4 -3
- package/dist/data/catalog.json +8 -2
- package/dist/generated/catalog.d.ts +7 -2
- package/dist/generated/catalog.js +9 -3
- package/dist/generated/recognition.js +1 -0
- package/dist/generated/registry.js +3 -1
- 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 **120 dedicated adapters** and **5 universal fallbacks**
|
|
17
17
|
|
|
18
18
|
<sub>128 catalog entries · 58 countries represented</sub>
|
|
19
19
|
<!-- /GENERATED:summary -->
|
|
@@ -155,7 +155,7 @@ createTracker({ providers: ['ParcelsApp', 'Ship24', '17TRACK', 'Postal Ninja', '
|
|
|
155
155
|
<!-- GENERATED:stages -->
|
|
156
156
|
<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">
|
|
157
157
|
|
|
158
|
-
The carrier folders record 3,
|
|
158
|
+
The carrier folders record 3,341 such statuses, each filed under one stage.
|
|
159
159
|
<!-- /GENERATED:stages -->
|
|
160
160
|
|
|
161
161
|
Wording nobody recorded yet goes through a classifier that reads seven European languages.
|
package/data/catalog.json
CHANGED
|
@@ -5279,8 +5279,10 @@
|
|
|
5279
5279
|
"selectable": true,
|
|
5280
5280
|
"timezone": "UTC",
|
|
5281
5281
|
"tracking": {
|
|
5282
|
-
"mode": "
|
|
5283
|
-
"adapter":
|
|
5282
|
+
"mode": "automatic",
|
|
5283
|
+
"adapter": "j-and-t-cargo",
|
|
5284
|
+
"recognitionRank": 47,
|
|
5285
|
+
"localClocks": true
|
|
5284
5286
|
},
|
|
5285
5287
|
"canaryUrl": "https://www.jtcargo.id/",
|
|
5286
5288
|
"trackingUrlTemplate": "https://www.jtcargo.id/networkQuery?waybillNo={trackingNumber}&type=0",
|
|
@@ -5299,6 +5301,10 @@
|
|
|
5299
5301
|
{
|
|
5300
5302
|
"pattern": "^20\\d{10}$",
|
|
5301
5303
|
"confidence": "low"
|
|
5304
|
+
},
|
|
5305
|
+
{
|
|
5306
|
+
"pattern": "^20\\d{13}$",
|
|
5307
|
+
"confidence": "low"
|
|
5302
5308
|
}
|
|
5303
5309
|
]
|
|
5304
5310
|
},
|
|
@@ -5556,6 +5556,7 @@
|
|
|
5556
5556
|
"sf-express"
|
|
5557
5557
|
],
|
|
5558
5558
|
"asked": [
|
|
5559
|
+
"j-and-t-cargo",
|
|
5559
5560
|
"brt",
|
|
5560
5561
|
"colis-prive",
|
|
5561
5562
|
"sto",
|
|
@@ -5583,6 +5584,7 @@
|
|
|
5583
5584
|
"fedex"
|
|
5584
5585
|
],
|
|
5585
5586
|
"asked": [
|
|
5587
|
+
"j-and-t-cargo",
|
|
5586
5588
|
"brt",
|
|
5587
5589
|
"colis-prive",
|
|
5588
5590
|
"sto",
|
|
@@ -5606,6 +5608,7 @@
|
|
|
5606
5608
|
"lbc-express"
|
|
5607
5609
|
],
|
|
5608
5610
|
"asked": [
|
|
5611
|
+
"j-and-t-cargo",
|
|
5609
5612
|
"brt",
|
|
5610
5613
|
"colis-prive",
|
|
5611
5614
|
"sto",
|
|
@@ -5630,6 +5633,7 @@
|
|
|
5630
5633
|
"lbc-express"
|
|
5631
5634
|
],
|
|
5632
5635
|
"asked": [
|
|
5636
|
+
"j-and-t-cargo",
|
|
5633
5637
|
"brt",
|
|
5634
5638
|
"colis-prive",
|
|
5635
5639
|
"sto",
|
|
@@ -5653,6 +5657,7 @@
|
|
|
5653
5657
|
"lbc-express"
|
|
5654
5658
|
],
|
|
5655
5659
|
"asked": [
|
|
5660
|
+
"j-and-t-cargo",
|
|
5656
5661
|
"brt",
|
|
5657
5662
|
"colis-prive",
|
|
5658
5663
|
"sto",
|
|
@@ -5678,10 +5683,10 @@
|
|
|
5678
5683
|
],
|
|
5679
5684
|
"asked": [
|
|
5680
5685
|
"purolator",
|
|
5686
|
+
"j-and-t-cargo",
|
|
5681
5687
|
"brt",
|
|
5682
5688
|
"colis-prive",
|
|
5683
|
-
"sto"
|
|
5684
|
-
"j-and-t"
|
|
5689
|
+
"sto"
|
|
5685
5690
|
]
|
|
5686
5691
|
},
|
|
5687
5692
|
{
|
|
@@ -5701,6 +5706,7 @@
|
|
|
5701
5706
|
"lbc-express"
|
|
5702
5707
|
],
|
|
5703
5708
|
"asked": [
|
|
5709
|
+
"j-and-t-cargo",
|
|
5704
5710
|
"brt",
|
|
5705
5711
|
"colis-prive",
|
|
5706
5712
|
"sto",
|
|
@@ -5724,6 +5730,7 @@
|
|
|
5724
5730
|
"lbc-express"
|
|
5725
5731
|
],
|
|
5726
5732
|
"asked": [
|
|
5733
|
+
"j-and-t-cargo",
|
|
5727
5734
|
"brt",
|
|
5728
5735
|
"colis-prive",
|
|
5729
5736
|
"sto",
|
|
@@ -5770,6 +5777,7 @@
|
|
|
5770
5777
|
"lbc-express"
|
|
5771
5778
|
],
|
|
5772
5779
|
"asked": [
|
|
5780
|
+
"j-and-t-cargo",
|
|
5773
5781
|
"brt",
|
|
5774
5782
|
"colis-prive",
|
|
5775
5783
|
"sto",
|
|
@@ -5794,6 +5802,7 @@
|
|
|
5794
5802
|
"lbc-express"
|
|
5795
5803
|
],
|
|
5796
5804
|
"asked": [
|
|
5805
|
+
"j-and-t-cargo",
|
|
5797
5806
|
"brt",
|
|
5798
5807
|
"colis-prive",
|
|
5799
5808
|
"sto",
|
|
@@ -5820,6 +5829,7 @@
|
|
|
5820
5829
|
"sagawa"
|
|
5821
5830
|
],
|
|
5822
5831
|
"asked": [
|
|
5832
|
+
"j-and-t-cargo",
|
|
5823
5833
|
"brt",
|
|
5824
5834
|
"colis-prive",
|
|
5825
5835
|
"sto",
|
|
@@ -5861,6 +5871,7 @@
|
|
|
5861
5871
|
"lbc-express"
|
|
5862
5872
|
],
|
|
5863
5873
|
"asked": [
|
|
5874
|
+
"j-and-t-cargo",
|
|
5864
5875
|
"brt",
|
|
5865
5876
|
"colis-prive",
|
|
5866
5877
|
"sto",
|
|
@@ -5884,6 +5895,7 @@
|
|
|
5884
5895
|
"lbc-express"
|
|
5885
5896
|
],
|
|
5886
5897
|
"asked": [
|
|
5898
|
+
"j-and-t-cargo",
|
|
5887
5899
|
"brt",
|
|
5888
5900
|
"colis-prive",
|
|
5889
5901
|
"sto",
|
|
@@ -5896,9 +5908,11 @@
|
|
|
5896
5908
|
"confidence": "low",
|
|
5897
5909
|
"candidates": [
|
|
5898
5910
|
"brt",
|
|
5899
|
-
"yunda"
|
|
5911
|
+
"yunda",
|
|
5912
|
+
"j-and-t-cargo"
|
|
5900
5913
|
],
|
|
5901
5914
|
"asked": [
|
|
5915
|
+
"j-and-t-cargo",
|
|
5902
5916
|
"brt"
|
|
5903
5917
|
]
|
|
5904
5918
|
},
|
|
@@ -5956,6 +5970,7 @@
|
|
|
5956
5970
|
"lbc-express"
|
|
5957
5971
|
],
|
|
5958
5972
|
"asked": [
|
|
5973
|
+
"j-and-t-cargo",
|
|
5959
5974
|
"brt",
|
|
5960
5975
|
"colis-prive",
|
|
5961
5976
|
"sto",
|
|
@@ -6001,6 +6016,7 @@
|
|
|
6001
6016
|
"lbc-express"
|
|
6002
6017
|
],
|
|
6003
6018
|
"asked": [
|
|
6019
|
+
"j-and-t-cargo",
|
|
6004
6020
|
"brt",
|
|
6005
6021
|
"colis-prive",
|
|
6006
6022
|
"sto",
|
package/dist/browser/scraper.js
CHANGED
|
@@ -4267,8 +4267,10 @@ var be = {
|
|
|
4267
4267
|
selectable: !0,
|
|
4268
4268
|
timezone: "UTC",
|
|
4269
4269
|
tracking: {
|
|
4270
|
-
mode: "
|
|
4271
|
-
adapter:
|
|
4270
|
+
mode: "automatic",
|
|
4271
|
+
adapter: "j-and-t-cargo",
|
|
4272
|
+
recognitionRank: 47,
|
|
4273
|
+
localClocks: !0
|
|
4272
4274
|
},
|
|
4273
4275
|
canaryUrl: "https://www.jtcargo.id/",
|
|
4274
4276
|
trackingUrlTemplate: "https://www.jtcargo.id/networkQuery?waybillNo={trackingNumber}&type=0",
|
|
@@ -4280,6 +4282,9 @@ var be = {
|
|
|
4280
4282
|
detectionRules: [{
|
|
4281
4283
|
pattern: "^20\\d{10}$",
|
|
4282
4284
|
confidence: "low"
|
|
4285
|
+
}, {
|
|
4286
|
+
pattern: "^20\\d{13}$",
|
|
4287
|
+
confidence: "low"
|
|
4283
4288
|
}]
|
|
4284
4289
|
},
|
|
4285
4290
|
"lbc-express": {
|
|
@@ -4846,7 +4851,7 @@ var be = {
|
|
|
4846
4851
|
"intelcom-2",
|
|
4847
4852
|
"intelcom-3"
|
|
4848
4853
|
],
|
|
4849
|
-
"j-and-t-cargo": ["j-and-t-cargo-1"],
|
|
4854
|
+
"j-and-t-cargo": ["j-and-t-cargo-1", "j-and-t-cargo-2"],
|
|
4850
4855
|
"lbc-express": ["lbc-domestic"],
|
|
4851
4856
|
"nova-poshta": ["nova-poshta-domestic"],
|
|
4852
4857
|
omgo: ["omgo-1"],
|
|
@@ -9554,6 +9559,7 @@ var mc = {
|
|
|
9554
9559
|
"hermes-de": 40,
|
|
9555
9560
|
inpost: 38,
|
|
9556
9561
|
"j-and-t": 9,
|
|
9562
|
+
"j-and-t-cargo": 47,
|
|
9557
9563
|
"la-poste": 80,
|
|
9558
9564
|
nacex: 41,
|
|
9559
9565
|
"ninja-van": 28,
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type AdapterFactory, type TrackingContext } from '../../core/adapter/index.js';
|
|
2
|
+
import { type StepRecorder } from '../../core/telemetry/index.js';
|
|
3
|
+
export declare class JntCargoTracker {
|
|
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,51 @@
|
|
|
1
|
+
import { accepted, recognizeFromLookup } from '../../core/adapter/index.js';
|
|
2
|
+
import { BudgetExceededError, CarrierError, UpstreamHttpError, UpstreamNetworkError } from '../../core/errors/index.js';
|
|
3
|
+
import { runSteps } from '../../core/runner/index.js';
|
|
4
|
+
import { NOOP_RECORDER } from '../../core/telemetry/index.js';
|
|
5
|
+
import { decodeText, fetchBounded, userAgentOf } from '../../core/transport/index.js';
|
|
6
|
+
import { JNT_CARGO_ENDPOINT, JNT_CARGO_MAX_BYTES, JNT_CARGO_PROVIDER, normalizeJntCargoNumber, parseJntCargoJson } from './parser.js';
|
|
7
|
+
export class JntCargoTracker {
|
|
8
|
+
options;
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
this.options = options;
|
|
11
|
+
}
|
|
12
|
+
async fetch(raw, context = {}) {
|
|
13
|
+
const number = normalizeJntCargoNumber(raw);
|
|
14
|
+
const budgetMs = context.budgetMs ?? 15_000;
|
|
15
|
+
if (!Number.isFinite(budgetMs))
|
|
16
|
+
throw new TypeError('J&T Cargo budget must be finite');
|
|
17
|
+
return runSteps({ carrier: JNT_CARGO_PROVIDER, budgetMs, signal: context.signal,
|
|
18
|
+
recorder: this.options.recorder ?? NOOP_RECORDER }, [{ id: 'direct', run: async ({ signal, remainingMs }) => {
|
|
19
|
+
try {
|
|
20
|
+
const { bytes } = await fetchBounded(JNT_CARGO_ENDPOINT, {
|
|
21
|
+
method: 'POST', signal, headers: { 'Content-Type': 'application/json', 'User-Agent': userAgentOf(this.options.userAgent) },
|
|
22
|
+
body: JSON.stringify({ waybillNo: number, langType: 'EN', searchWaybillOrCustomerOrderId: '1' }),
|
|
23
|
+
}, { provider: JNT_CARGO_PROVIDER, timeoutMs: Math.max(1, Math.floor(remainingMs)), maxBytes: JNT_CARGO_MAX_BYTES,
|
|
24
|
+
fetcher: this.options.fetcher });
|
|
25
|
+
signal.throwIfAborted();
|
|
26
|
+
return parseJntCargoJson(decodeText(bytes), number);
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
if (signal.aborted) {
|
|
30
|
+
if (signal.reason instanceof DOMException && signal.reason.name === 'TimeoutError')
|
|
31
|
+
throw new BudgetExceededError(JNT_CARGO_PROVIDER, budgetMs);
|
|
32
|
+
throw signal.reason;
|
|
33
|
+
}
|
|
34
|
+
// Bounded transport diagnostics can contain the waybill or recipient
|
|
35
|
+
// details. Keep the taxonomy and retry window without those payloads.
|
|
36
|
+
if (error instanceof UpstreamHttpError || error instanceof UpstreamNetworkError) {
|
|
37
|
+
const kind = error instanceof UpstreamHttpError && [404, 410].includes(error.status) ? 'transport' : error.kind;
|
|
38
|
+
throw new CarrierError(kind, JNT_CARGO_PROVIDER, `J&T Cargo tracking failed (${kind})`, {
|
|
39
|
+
status: error.status, retryAfterMs: error.retryAfterMs, reason: error.reason,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
} }]);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export const adapter = environment => {
|
|
48
|
+
const tracker = new JntCargoTracker({ fetcher: environment.fetcher, recorder: environment.recorder, userAgent: environment.userAgent });
|
|
49
|
+
return { id: JNT_CARGO_PROVIDER, recordsSteps: true, steps: ['direct'], track: (input, context) => tracker.fetch(input.number, context),
|
|
50
|
+
recognize: (number, context) => recognizeFromLookup(() => tracker.fetch(number, context), () => accepted(() => normalizeJntCargoNumber(number))) };
|
|
51
|
+
};
|
|
@@ -16,25 +16,26 @@
|
|
|
16
16
|
"selectable": true,
|
|
17
17
|
"timezone": "UTC",
|
|
18
18
|
"tracking": {
|
|
19
|
-
"mode": "
|
|
20
|
-
"adapter":
|
|
21
|
-
"steps": []
|
|
19
|
+
"mode": "automatic",
|
|
20
|
+
"adapter": "j-and-t-cargo",
|
|
21
|
+
"steps": ["direct"],
|
|
22
|
+
"recognition": { "rank": 47 },
|
|
23
|
+
"localClocks": true
|
|
22
24
|
},
|
|
23
|
-
"capabilities": [],
|
|
25
|
+
"capabilities": ["history", "location", "provider_code", "service_name"],
|
|
24
26
|
"portal": {
|
|
25
27
|
"url": "https://www.jtcargo.id/networkQuery?waybillNo={trackingNumber}&type=0",
|
|
26
28
|
"searchUrl": "https://www.jtcargo.id/networkQuery",
|
|
27
29
|
"canaryUrl": "https://www.jtcargo.id/",
|
|
28
30
|
"shows": [
|
|
29
31
|
"status",
|
|
30
|
-
"history"
|
|
32
|
+
"history",
|
|
33
|
+
"location",
|
|
34
|
+
"service_name"
|
|
31
35
|
],
|
|
32
|
-
"scraped": [],
|
|
33
|
-
"discarded": [],
|
|
34
|
-
"unavailable": [
|
|
35
|
-
"status",
|
|
36
|
-
"history"
|
|
37
|
-
]
|
|
36
|
+
"scraped": ["status", "history", "location", "provider_code", "service_name"],
|
|
37
|
+
"discarded": ["customer_messages", "recipient_details", "staff_details", "proof_images", "consignment_totals", "progress_rail"],
|
|
38
|
+
"unavailable": []
|
|
38
39
|
},
|
|
39
40
|
"links": [
|
|
40
41
|
{
|
|
@@ -51,6 +52,11 @@
|
|
|
51
52
|
"id": "j-and-t-cargo-1",
|
|
52
53
|
"pattern": "^20\\d{10}$",
|
|
53
54
|
"confidence": "low"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"id": "j-and-t-cargo-2",
|
|
58
|
+
"pattern": "^20\\d{13}$",
|
|
59
|
+
"confidence": "low"
|
|
54
60
|
}
|
|
55
61
|
]
|
|
56
62
|
}
|
|
@@ -444,7 +444,7 @@
|
|
|
444
444
|
"access": "full_page"
|
|
445
445
|
},
|
|
446
446
|
"context": {
|
|
447
|
-
"assessment": "
|
|
447
|
+
"assessment": "confirmed",
|
|
448
448
|
"service": "jt cargo multi piece 44",
|
|
449
449
|
"reportedRole": "master_consignment"
|
|
450
450
|
},
|
|
@@ -477,7 +477,7 @@
|
|
|
477
477
|
"access": "full_page"
|
|
478
478
|
},
|
|
479
479
|
"context": {
|
|
480
|
-
"assessment": "
|
|
480
|
+
"assessment": "confirmed",
|
|
481
481
|
"service": "jt cargo multi piece",
|
|
482
482
|
"reportedRole": "child_consignment"
|
|
483
483
|
},
|
|
@@ -486,7 +486,8 @@
|
|
|
486
486
|
"confidence": "low",
|
|
487
487
|
"candidates": [
|
|
488
488
|
"brt",
|
|
489
|
-
"yunda"
|
|
489
|
+
"yunda",
|
|
490
|
+
"j-and-t-cargo"
|
|
490
491
|
]
|
|
491
492
|
},
|
|
492
493
|
"note": "J&T Cargo's public reply identifies this piece of the linked consignment.",
|
|
@@ -514,7 +515,7 @@
|
|
|
514
515
|
"access": "full_page"
|
|
515
516
|
},
|
|
516
517
|
"context": {
|
|
517
|
-
"assessment": "
|
|
518
|
+
"assessment": "confirmed",
|
|
518
519
|
"service": "jt cargo insured fragile",
|
|
519
520
|
"reportedRole": "reported_shipment"
|
|
520
521
|
},
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { CarrierResult } from '../../core/result/index.js';
|
|
2
|
+
export declare const JNT_CARGO_PROVIDER = "j-and-t-cargo";
|
|
3
|
+
export declare const JNT_CARGO_ENDPOINT = "https://office.jtcargo.co.id/official/waybill/trackingCustomerByWaybillNo";
|
|
4
|
+
export declare const JNT_CARGO_MAX_BYTES = 2000000;
|
|
5
|
+
export declare function normalizeJntCargoNumber(raw: string): string;
|
|
6
|
+
/** Parse exactly the requested master or piece, never an aggregate of related waybills. */
|
|
7
|
+
export declare function parseJntCargo(payload: unknown, rawNumber: string): CarrierResult;
|
|
8
|
+
export declare function parseJntCargoJson(text: string, number: string): CarrierResult;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { DateTime } from 'luxon';
|
|
2
|
+
import { isJntCargoTrackingNumber } from '../../core/detection/jntCargo.js';
|
|
3
|
+
import { normalizeTrackingNumber } from '../../core/detection/normalize.js';
|
|
4
|
+
import { ChallengeError, IndeterminateError, InvalidInputError, SchemaError } from '../../core/errors/index.js';
|
|
5
|
+
import { classifyWording, languageStageStatus } from '../../core/status/index.js';
|
|
6
|
+
import { clean } from '../../core/transport/text.js';
|
|
7
|
+
import { isRecord } from '../../core/types.js';
|
|
8
|
+
import { jntCargoStage } from './status.js';
|
|
9
|
+
export const JNT_CARGO_PROVIDER = 'j-and-t-cargo';
|
|
10
|
+
export const JNT_CARGO_ENDPOINT = 'https://office.jtcargo.co.id/official/waybill/trackingCustomerByWaybillNo';
|
|
11
|
+
export const JNT_CARGO_MAX_BYTES = 2_000_000;
|
|
12
|
+
const MAX_SCANS = 500;
|
|
13
|
+
export function normalizeJntCargoNumber(raw) {
|
|
14
|
+
const number = normalizeTrackingNumber(raw);
|
|
15
|
+
if (!isJntCargoTrackingNumber(number))
|
|
16
|
+
throw new InvalidInputError(JNT_CARGO_PROVIDER, 'J&T Cargo requires a supported master or piece waybill');
|
|
17
|
+
return number;
|
|
18
|
+
}
|
|
19
|
+
function invalid(message = 'J&T Cargo returned an invalid tracking reply') {
|
|
20
|
+
throw new SchemaError(JNT_CARGO_PROVIDER, message);
|
|
21
|
+
}
|
|
22
|
+
function optionalText(value, limit) {
|
|
23
|
+
if (value != null && typeof value !== 'string')
|
|
24
|
+
invalid('J&T Cargo returned an invalid scan field');
|
|
25
|
+
return clean(value, limit);
|
|
26
|
+
}
|
|
27
|
+
/** UTC validates the calendar only: Indonesia has three zones, and the feed establishes none. */
|
|
28
|
+
function localClock(value) {
|
|
29
|
+
if (typeof value !== 'string' || !/^\d{4}-\d{2}-\d{2} (?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d$/.test(value)) {
|
|
30
|
+
invalid('J&T Cargo returned an invalid scan clock');
|
|
31
|
+
}
|
|
32
|
+
const local = value.replace(' ', 'T');
|
|
33
|
+
const parsed = DateTime.fromISO(local, { zone: 'UTC' });
|
|
34
|
+
if (!parsed.isValid || parsed.year < 2000)
|
|
35
|
+
invalid('J&T Cargo returned an invalid scan clock');
|
|
36
|
+
return local;
|
|
37
|
+
}
|
|
38
|
+
function project(row, number) {
|
|
39
|
+
if (!isRecord(row) || row.billCode !== number)
|
|
40
|
+
invalid('J&T Cargo returned a scan of a different waybill');
|
|
41
|
+
const code = typeof row.code === 'number' && Number.isInteger(row.code) && row.code >= 0 && row.code <= 999999
|
|
42
|
+
? String(row.code) : typeof row.code === 'string' && /^\d{1,6}$/.test(row.code) ? row.code : invalid('J&T Cargo returned an invalid scan code');
|
|
43
|
+
// These are the feed's fixed labels. The customer sentence, remarks, staff
|
|
44
|
+
// and proof fields contain personal details and are never projected.
|
|
45
|
+
const description = optionalText(row.status, 100) || optionalText(row.scanTypeName, 100);
|
|
46
|
+
if (!description)
|
|
47
|
+
invalid('J&T Cargo returned an empty scan label');
|
|
48
|
+
const location = [optionalText(row.scanNetworkCity, 100), optionalText(row.scanNetworkProvince, 100)].filter(Boolean).join(', ');
|
|
49
|
+
const mapped = jntCargoStage(code);
|
|
50
|
+
const wording = mapped ? undefined : classifyWording(description, 'pending');
|
|
51
|
+
return { description, local_time: localClock(row.scanTime), provider_code: code,
|
|
52
|
+
...(location ? { location } : {}),
|
|
53
|
+
stage: mapped ?? wording.stage, stage_source: mapped ? 'carrier_map' : wording.source };
|
|
54
|
+
}
|
|
55
|
+
/** Parse exactly the requested master or piece, never an aggregate of related waybills. */
|
|
56
|
+
export function parseJntCargo(payload, rawNumber) {
|
|
57
|
+
const number = normalizeJntCargoNumber(rawNumber);
|
|
58
|
+
if (!isRecord(payload) || typeof payload.code !== 'number' || !Number.isInteger(payload.code)
|
|
59
|
+
|| typeof payload.succ !== 'boolean' || typeof payload.fail !== 'boolean')
|
|
60
|
+
invalid();
|
|
61
|
+
// The official client treats these as expired/refused account authentication.
|
|
62
|
+
// A future gate must not be reported as a missing shipment.
|
|
63
|
+
if ([148013016, 148013017].includes(payload.code))
|
|
64
|
+
throw new ChallengeError(JNT_CARGO_PROVIDER, 'J&T Cargo refused tracking authentication');
|
|
65
|
+
if (payload.code !== 1 || !payload.succ || payload.fail)
|
|
66
|
+
throw new IndeterminateError(JNT_CARGO_PROVIDER, 'J&T Cargo could not complete the tracking request');
|
|
67
|
+
if (!Array.isArray(payload.data))
|
|
68
|
+
invalid();
|
|
69
|
+
if (!payload.data.length)
|
|
70
|
+
throw new IndeterminateError(JNT_CARGO_PROVIDER, 'J&T Cargo returned no identity-bound history');
|
|
71
|
+
if (payload.data.length !== 1)
|
|
72
|
+
invalid('J&T Cargo returned several waybills');
|
|
73
|
+
const bill = payload.data[0];
|
|
74
|
+
if (!isRecord(bill) || bill.keyword !== number)
|
|
75
|
+
invalid('J&T Cargo returned a different waybill');
|
|
76
|
+
// Unknown numbers return a success envelope with the query echoed and null
|
|
77
|
+
// shipment fields. That placeholder does not establish absence or ownership.
|
|
78
|
+
if (bill.details === null || Array.isArray(bill.details) && !bill.details.length) {
|
|
79
|
+
throw new IndeterminateError(JNT_CARGO_PROVIDER, 'J&T Cargo returned the waybill without shipment history');
|
|
80
|
+
}
|
|
81
|
+
if (!Array.isArray(bill.details))
|
|
82
|
+
invalid();
|
|
83
|
+
if (bill.details.length > MAX_SCANS)
|
|
84
|
+
invalid('J&T Cargo returned excessive tracking history');
|
|
85
|
+
const events = [];
|
|
86
|
+
const seen = new Set();
|
|
87
|
+
for (const scan of bill.details) {
|
|
88
|
+
const event = project(scan, number);
|
|
89
|
+
const key = JSON.stringify(event);
|
|
90
|
+
if (!seen.has(key)) {
|
|
91
|
+
seen.add(key);
|
|
92
|
+
events.push(event);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// The public client displays this newest-first order. Local clocks cannot
|
|
96
|
+
// safely reorder scans between facilities in different Indonesian zones.
|
|
97
|
+
const latest = events[0];
|
|
98
|
+
const stage = latest.stage_source === 'none' ? undefined : latest.stage;
|
|
99
|
+
const service = optionalText(bill.expressTypeName, 80);
|
|
100
|
+
return { status: stage ? languageStageStatus(stage) : 'unknown',
|
|
101
|
+
...(stage ? { current_stage: stage, current_stage_source: latest.stage_source } : {}),
|
|
102
|
+
last_status_text: latest.description, last_update: null, last_update_local: latest.local_time,
|
|
103
|
+
...(service ? { service_name: service } : {}), ...(events.length > 100 ? { history_truncated: true } : {}),
|
|
104
|
+
events: events.slice(0, 100) };
|
|
105
|
+
}
|
|
106
|
+
export function parseJntCargoJson(text, number) {
|
|
107
|
+
if (text.length > JNT_CARGO_MAX_BYTES || new TextEncoder().encode(text).length > JNT_CARGO_MAX_BYTES)
|
|
108
|
+
invalid('J&T Cargo returned excessive tracking data');
|
|
109
|
+
const body = text.replace(/^\uFEFF/, '').trim();
|
|
110
|
+
if (/^</.test(body) && /<title>\s*Just a moment|cf-chl-|challenge-platform|cf-turnstile|captcha|access denied|captcha-delivery\.com/i.test(body)
|
|
111
|
+
|| /^access denied\b/i.test(body)) {
|
|
112
|
+
throw new ChallengeError(JNT_CARGO_PROVIDER);
|
|
113
|
+
}
|
|
114
|
+
if (!body)
|
|
115
|
+
throw new IndeterminateError(JNT_CARGO_PROVIDER, 'J&T Cargo returned an empty reply');
|
|
116
|
+
let payload;
|
|
117
|
+
try {
|
|
118
|
+
payload = JSON.parse(body);
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
invalid('J&T Cargo returned invalid tracking JSON');
|
|
122
|
+
}
|
|
123
|
+
return parseJntCargo(payload, number);
|
|
124
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import statuses from './statuses.json' with { type: 'json' };
|
|
2
|
+
const STAGES = new Map(statuses.entries.map(entry => [entry.code, entry.stage]));
|
|
3
|
+
export const jntCargoStage = (code) => STAGES.get(code);
|
|
4
|
+
export const statusMap = { stage: code => code ? jntCargoStage(code) : undefined, gaps: [] };
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"carrier": "j-and-t-cargo",
|
|
3
|
-
"entries": [
|
|
3
|
+
"entries": [
|
|
4
|
+
{ "code": "10", "wording": "sudah di ambil", "language": "id", "stage": "accepted", "confirmedBy": "live" },
|
|
5
|
+
{ "code": "50", "wording": "dalam proses delivery", "language": "id", "stage": "in_transit", "confirmedBy": "live", "note": "Departure scan, not final delivery." },
|
|
6
|
+
{ "code": "90", "wording": "dalam proses delivery", "language": "id", "stage": "in_transit", "confirmedBy": "live", "note": "Arrival at an operations centre." },
|
|
7
|
+
{ "code": "92", "wording": "dalam proses delivery", "language": "id", "stage": "in_transit", "confirmedBy": "live", "note": "Arrival at an outlet." },
|
|
8
|
+
{ "code": "94", "wording": "dalam proses distribusi", "language": "id", "stage": "out_for_delivery", "confirmedBy": "live" },
|
|
9
|
+
{ "code": "100", "wording": "Sudah Diterima", "language": "id", "stage": "delivered", "confirmedBy": "live" }
|
|
10
|
+
]
|
|
4
11
|
}
|
|
@@ -5,7 +5,7 @@ import { type StepRecorder } from '../../core/telemetry/index.js';
|
|
|
5
5
|
import { TrawlClient } from '../../core/transport/index.js';
|
|
6
6
|
export declare function upsTrackingUrl(trackingNumber: string): string;
|
|
7
7
|
/**
|
|
8
|
-
* Akamai rejected the session (HTTP 401/403/419
|
|
8
|
+
* Akamai rejected the session (HTTP 401/403/419, or a page that carries no
|
|
9
9
|
* XSRF token). It stays a distinct class because the adapter reacts to it: the
|
|
10
10
|
* cached session is refreshed once, then dropped, before the browser tier runs.
|
|
11
11
|
*/
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { load } from 'cheerio';
|
|
2
2
|
import makeFetchCookie from 'fetch-cookie';
|
|
3
3
|
import { CookieJar } from 'tough-cookie';
|
|
4
|
-
import { ChallengeError, IndeterminateError, InvalidInputError, NotFoundError, SchemaError, TransportError } from '../../core/errors/index.js';
|
|
4
|
+
import { carrierErrorKind, ChallengeError, IndeterminateError, InvalidInputError, NotFoundError, SchemaError, TransportError, UpstreamHttpError } from '../../core/errors/index.js';
|
|
5
5
|
import { isValidUpsTrackingNumber } from '../../core/detection/ups.js';
|
|
6
6
|
import { uspsPackageIdentifier } from '../../core/detection/usps.js';
|
|
7
7
|
import { runSteps, singleFlight, takeTurn } from '../../core/runner/index.js';
|
|
@@ -21,7 +21,7 @@ const DEFAULT_TIMEOUT_MS = 90_000;
|
|
|
21
21
|
const DEFAULT_DIRECT_TIMEOUT_MS = 20_000;
|
|
22
22
|
// How long the browser may wait for the page's own status call after the page settles.
|
|
23
23
|
const SETTLE_TIMEOUT_MS = 15_000;
|
|
24
|
-
//
|
|
24
|
+
// Retain the latest bounded sequence of matching replies.
|
|
25
25
|
const MAX_CAPTURED = 20;
|
|
26
26
|
// Provider strings are short; the rendered page's whole text is not, and the
|
|
27
27
|
// identity check reads all of it, so it passes its own limit to `clean`.
|
|
@@ -34,7 +34,7 @@ export function upsTrackingUrl(trackingNumber) {
|
|
|
34
34
|
return url.toString();
|
|
35
35
|
}
|
|
36
36
|
/**
|
|
37
|
-
* Akamai rejected the session (HTTP 401/403/419
|
|
37
|
+
* Akamai rejected the session (HTTP 401/403/419, or a page that carries no
|
|
38
38
|
* XSRF token). It stays a distinct class because the adapter reacts to it: the
|
|
39
39
|
* cached session is refreshed once, then dropped, before the browser tier runs.
|
|
40
40
|
*/
|
|
@@ -133,15 +133,27 @@ class UPSHttpSession {
|
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
135
|
async request(url, init, description, bounds) {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
136
|
+
let result;
|
|
137
|
+
try {
|
|
138
|
+
result = await fetchBounded(url, { ...init, signal: bounds.signal }, {
|
|
139
|
+
provider: description,
|
|
140
|
+
timeoutMs: Math.max(1, Math.floor(Math.min(this.timeoutMs, bounds.deadline - performance.now()))),
|
|
141
|
+
maxBytes: MAX_BYTES,
|
|
142
|
+
redirect: 'follow',
|
|
143
|
+
fetcher: this.#fetcher,
|
|
144
|
+
allowHttpStatuses: [401, 403, 419],
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
// A throttle keeps the transport's status and Retry-After and never
|
|
149
|
+
// refreshes the session. Other HTTP failures retain UPS's inconclusive
|
|
150
|
+
// classification: even a 404 says nothing about the shipment.
|
|
151
|
+
if (error instanceof UpstreamHttpError && error.status !== 429) {
|
|
152
|
+
throw new IndeterminateError('UPS', `${description} returned HTTP ${error.status}`);
|
|
153
|
+
}
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
156
|
+
if ([401, 403, 419].includes(result.response.status)) {
|
|
145
157
|
throw new UPSSessionRejected(`${description} returned HTTP ${result.response.status}`);
|
|
146
158
|
}
|
|
147
159
|
// Any other rejected status proves nothing about the shipment, so the
|
|
@@ -154,7 +166,20 @@ class UPSHttpSession {
|
|
|
154
166
|
}
|
|
155
167
|
/** An answer no other tier or rendered page may replace. */
|
|
156
168
|
function isFinalAnswer(error) {
|
|
157
|
-
return error instanceof InvalidInputError || error instanceof NotFoundError || error instanceof UPSUnavailableError
|
|
169
|
+
return error instanceof InvalidInputError || error instanceof NotFoundError || error instanceof UPSUnavailableError
|
|
170
|
+
|| carrierErrorKind(error) === 'rate_limited';
|
|
171
|
+
}
|
|
172
|
+
/** Headers from a captured response are case-insensitive, like HTTP headers. */
|
|
173
|
+
function capturedHttpError(status, headers) {
|
|
174
|
+
if (status === 404 || status === 410) {
|
|
175
|
+
return new IndeterminateError('UPS', `UPS status API returned HTTP ${status}`);
|
|
176
|
+
}
|
|
177
|
+
if (status === 419)
|
|
178
|
+
return new ChallengeError('UPS', 'UPS rejected the browser session', { status });
|
|
179
|
+
const retryHeader = Object.entries(headers).find(([key]) => key.toLowerCase() === 'retry-after')?.[1];
|
|
180
|
+
const delay = retryHeader === undefined ? NaN : /^\d+$/.test(retryHeader.trim())
|
|
181
|
+
? Number(retryHeader) * 1_000 : Date.parse(retryHeader) - Date.now();
|
|
182
|
+
return new UpstreamHttpError('UPS', status, Number.isFinite(delay) ? Math.max(0, delay) : undefined);
|
|
158
183
|
}
|
|
159
184
|
function withoutIcons(value) {
|
|
160
185
|
return clean(value.replace(/\b(?:check_circle|content_copy|expand_more|check)\b/g, ' '));
|
|
@@ -530,28 +555,31 @@ export class UPSTracker {
|
|
|
530
555
|
maxBytes: MAX_BYTES,
|
|
531
556
|
fetcher: this.#fetcher,
|
|
532
557
|
signal,
|
|
558
|
+
captureWindow: 'last',
|
|
533
559
|
});
|
|
534
|
-
|
|
535
|
-
//
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
560
|
+
// The last matching response is authoritative. An earlier success cannot
|
|
561
|
+
// answer after the page's retry was refused or returned invalid data.
|
|
562
|
+
const entry = page.capturedResponses.filter((capture) => capture.url === STATUS_API).slice(-MAX_CAPTURED).at(-1);
|
|
563
|
+
// UPS's 428 verification flow can still leave a valid rendered answer.
|
|
564
|
+
if (entry && entry.status !== 428) {
|
|
565
|
+
if (entry.status >= 400)
|
|
566
|
+
throw capturedHttpError(entry.status, entry.headers);
|
|
567
|
+
if (entry.status !== 200 || entry.truncated || entry.body === null || entry.base64Encoded || entry.error) {
|
|
568
|
+
throw new SchemaError('UPS', 'UPS returned an unreadable status response');
|
|
569
|
+
}
|
|
570
|
+
let payload;
|
|
539
571
|
try {
|
|
540
|
-
|
|
572
|
+
payload = JSON.parse(entry.body);
|
|
541
573
|
}
|
|
542
|
-
catch
|
|
543
|
-
|
|
544
|
-
throw error;
|
|
545
|
-
// An unreadable or unrelated reply; the rendered page may still answer.
|
|
546
|
-
captureError = error instanceof Error ? error : new SchemaError('UPS', 'UPS returned invalid tracking data', { cause: error });
|
|
574
|
+
catch {
|
|
575
|
+
throw new SchemaError('UPS', 'UPS returned invalid tracking JSON');
|
|
547
576
|
}
|
|
577
|
+
return this.#structuredResult(number, payload);
|
|
548
578
|
}
|
|
549
579
|
try {
|
|
550
580
|
return this.#renderedResult(page.html, number);
|
|
551
581
|
}
|
|
552
582
|
catch (error) {
|
|
553
|
-
if (captureError)
|
|
554
|
-
throw captureError;
|
|
555
583
|
throw new TransportError('UPS', 'TRAWL did not capture the UPS status response', { cause: error });
|
|
556
584
|
}
|
|
557
585
|
}
|
|
@@ -24,6 +24,7 @@ import { statusMap as emile } from '../../carriers/emile/status.js';
|
|
|
24
24
|
import { statusMap as gofoFr } from '../../carriers/gofo-fr/status.js';
|
|
25
25
|
import { statusMap as gofoIt } from '../../carriers/gofo-it/status.js';
|
|
26
26
|
import { statusMap as jAndT } from '../../carriers/j-and-t/status.js';
|
|
27
|
+
import { statusMap as jntCargo } from '../../carriers/j-and-t-cargo/status.js';
|
|
27
28
|
import { statusMap as laPoste } from '../../carriers/la-poste/status.js';
|
|
28
29
|
import { statusMap as oldDominion } from '../../carriers/old-dominion/status.js';
|
|
29
30
|
import { statusMap as omgo } from '../../carriers/omgo/status.js';
|
|
@@ -43,7 +44,7 @@ import { CARRIER_DEFINITIONS } from './definitions.js';
|
|
|
43
44
|
export const STATUS_MAPS = {
|
|
44
45
|
aliexpress, 'an-post': anPost, 'china-post': chinaPost, chronopost, 'correos-spain': correosSpain, 'dhl-express': dhlExpress,
|
|
45
46
|
dpd, 'dpd-de': dpdDe, 'dpd-fr': dpdFr, 'dpd-pl': dpdPl, emile, 'gofo-fr': gofoFr, 'gofo-it': gofoIt,
|
|
46
|
-
'j-and-t': jAndT, 'la-poste': laPoste, 'old-dominion': oldDominion, omgo,
|
|
47
|
+
'j-and-t': jAndT, 'j-and-t-cargo': jntCargo, 'la-poste': laPoste, 'old-dominion': oldDominion, omgo,
|
|
47
48
|
postlogistics, sagawa, speedx, sto, 'swiss-post': swissPost, 'thailand-post': thailandPost, tnt, ups, yunexpress,
|
|
48
49
|
};
|
|
49
50
|
/** The carrier the app files the universal providers' scans under. */
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** J&T Cargo Indonesia masters and pieces confirmed by its own tracking feed. */
|
|
2
|
+
export declare const JNT_CARGO_MASTER_PATTERN = "^20\\d{10}$";
|
|
3
|
+
export declare const JNT_CARGO_PIECE_PATTERN = "^20\\d{13}$";
|
|
4
|
+
/** Shape eligibility only; neither a master nor a piece is confirmed offline. */
|
|
5
|
+
export declare function isJntCargoTrackingNumber(number: string): boolean;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** J&T Cargo Indonesia masters and pieces confirmed by its own tracking feed. */
|
|
2
|
+
export const JNT_CARGO_MASTER_PATTERN = '^20\\d{10}$';
|
|
3
|
+
export const JNT_CARGO_PIECE_PATTERN = '^20\\d{13}$';
|
|
4
|
+
const MASTER = new RegExp(JNT_CARGO_MASTER_PATTERN);
|
|
5
|
+
const PIECE = new RegExp(JNT_CARGO_PIECE_PATTERN);
|
|
6
|
+
/** Shape eligibility only; neither a master nor a piece is confirmed offline. */
|
|
7
|
+
export function isJntCargoTrackingNumber(number) {
|
|
8
|
+
return MASTER.test(number) || PIECE.test(number);
|
|
9
|
+
}
|
|
@@ -54,6 +54,8 @@ export interface TrawlCallOptions {
|
|
|
54
54
|
signal?: AbortSignal;
|
|
55
55
|
/** Require a solved browser tier (2 or 3) with HTTP 200. Default true. */
|
|
56
56
|
requireSolved?: boolean;
|
|
57
|
+
/** Which bounded capture window to retain. Defaults to the first 50 responses. */
|
|
58
|
+
captureWindow?: 'first' | 'last';
|
|
57
59
|
}
|
|
58
60
|
export declare class TrawlError extends CarrierError {
|
|
59
61
|
constructor(provider: string, message: string, options?: CarrierErrorOptions);
|
|
@@ -64,10 +64,11 @@ export function trawlEndpoint(configured) {
|
|
|
64
64
|
endpoint.hash = '';
|
|
65
65
|
return endpoint;
|
|
66
66
|
}
|
|
67
|
-
function captured(value) {
|
|
67
|
+
function captured(value, window = 'first') {
|
|
68
68
|
if (!Array.isArray(value))
|
|
69
69
|
return [];
|
|
70
|
-
|
|
70
|
+
const records = value.filter(isRecord);
|
|
71
|
+
return (window === 'last' ? records.slice(-50) : records.slice(0, 50)).map((entry) => ({
|
|
71
72
|
url: typeof entry.url === 'string' ? entry.url : '',
|
|
72
73
|
status: Number.isInteger(entry.status) ? Number(entry.status) : 0,
|
|
73
74
|
headers: isRecord(entry.headers)
|
|
@@ -238,7 +239,7 @@ export class TrawlClient {
|
|
|
238
239
|
userAgent: typeof value.userAgent === 'string' ? value.userAgent : null,
|
|
239
240
|
tier,
|
|
240
241
|
statusCode,
|
|
241
|
-
capturedResponses: captured(value.capturedResponses),
|
|
242
|
+
capturedResponses: captured(value.capturedResponses, options.captureWindow),
|
|
242
243
|
raw: value,
|
|
243
244
|
};
|
|
244
245
|
}
|
package/dist/data/catalog.json
CHANGED
|
@@ -5279,8 +5279,10 @@
|
|
|
5279
5279
|
"selectable": true,
|
|
5280
5280
|
"timezone": "UTC",
|
|
5281
5281
|
"tracking": {
|
|
5282
|
-
"mode": "
|
|
5283
|
-
"adapter":
|
|
5282
|
+
"mode": "automatic",
|
|
5283
|
+
"adapter": "j-and-t-cargo",
|
|
5284
|
+
"recognitionRank": 47,
|
|
5285
|
+
"localClocks": true
|
|
5284
5286
|
},
|
|
5285
5287
|
"canaryUrl": "https://www.jtcargo.id/",
|
|
5286
5288
|
"trackingUrlTemplate": "https://www.jtcargo.id/networkQuery?waybillNo={trackingNumber}&type=0",
|
|
@@ -5299,6 +5301,10 @@
|
|
|
5299
5301
|
{
|
|
5300
5302
|
"pattern": "^20\\d{10}$",
|
|
5301
5303
|
"confidence": "low"
|
|
5304
|
+
},
|
|
5305
|
+
{
|
|
5306
|
+
"pattern": "^20\\d{13}$",
|
|
5307
|
+
"confidence": "low"
|
|
5302
5308
|
}
|
|
5303
5309
|
]
|
|
5304
5310
|
},
|
|
@@ -3606,8 +3606,10 @@ export declare const CARRIER_CATALOG: {
|
|
|
3606
3606
|
readonly selectable: true;
|
|
3607
3607
|
readonly timezone: "UTC";
|
|
3608
3608
|
readonly tracking: {
|
|
3609
|
-
readonly mode: "
|
|
3610
|
-
readonly adapter:
|
|
3609
|
+
readonly mode: "automatic";
|
|
3610
|
+
readonly adapter: "j-and-t-cargo";
|
|
3611
|
+
readonly recognitionRank: 47;
|
|
3612
|
+
readonly localClocks: true;
|
|
3611
3613
|
};
|
|
3612
3614
|
readonly canaryUrl: "https://www.jtcargo.id/";
|
|
3613
3615
|
readonly trackingUrlTemplate: "https://www.jtcargo.id/networkQuery?waybillNo={trackingNumber}&type=0";
|
|
@@ -3619,6 +3621,9 @@ export declare const CARRIER_CATALOG: {
|
|
|
3619
3621
|
readonly detectionRules: readonly [{
|
|
3620
3622
|
readonly pattern: "^20\\d{10}$";
|
|
3621
3623
|
readonly confidence: "low";
|
|
3624
|
+
}, {
|
|
3625
|
+
readonly pattern: "^20\\d{13}$";
|
|
3626
|
+
readonly confidence: "low";
|
|
3622
3627
|
}];
|
|
3623
3628
|
};
|
|
3624
3629
|
readonly "lbc-express": {
|
|
@@ -5280,8 +5280,10 @@ export const CARRIER_CATALOG = {
|
|
|
5280
5280
|
"selectable": true,
|
|
5281
5281
|
"timezone": "UTC",
|
|
5282
5282
|
"tracking": {
|
|
5283
|
-
"mode": "
|
|
5284
|
-
"adapter":
|
|
5283
|
+
"mode": "automatic",
|
|
5284
|
+
"adapter": "j-and-t-cargo",
|
|
5285
|
+
"recognitionRank": 47,
|
|
5286
|
+
"localClocks": true
|
|
5285
5287
|
},
|
|
5286
5288
|
"canaryUrl": "https://www.jtcargo.id/",
|
|
5287
5289
|
"trackingUrlTemplate": "https://www.jtcargo.id/networkQuery?waybillNo={trackingNumber}&type=0",
|
|
@@ -5300,6 +5302,10 @@ export const CARRIER_CATALOG = {
|
|
|
5300
5302
|
{
|
|
5301
5303
|
"pattern": "^20\\d{10}$",
|
|
5302
5304
|
"confidence": "low"
|
|
5305
|
+
},
|
|
5306
|
+
{
|
|
5307
|
+
"pattern": "^20\\d{13}$",
|
|
5308
|
+
"confidence": "low"
|
|
5303
5309
|
}
|
|
5304
5310
|
]
|
|
5305
5311
|
},
|
|
@@ -5837,7 +5843,7 @@ export const DETECTION_RULE_IDS = {
|
|
|
5837
5843
|
"gofo-fr": ["gofo-fr-1", "gofo-fr-2"],
|
|
5838
5844
|
"gofo-it": ["gofo-it-1"],
|
|
5839
5845
|
"intelcom": ["intelcom-1", "intelcom-2", "intelcom-3"],
|
|
5840
|
-
"j-and-t-cargo": ["j-and-t-cargo-1"],
|
|
5846
|
+
"j-and-t-cargo": ["j-and-t-cargo-1", "j-and-t-cargo-2"],
|
|
5841
5847
|
"lbc-express": ["lbc-domestic"],
|
|
5842
5848
|
"nova-poshta": ["nova-poshta-domestic"],
|
|
5843
5849
|
"omgo": ["omgo-1"],
|
|
@@ -64,6 +64,7 @@ import { adapter as indiaPost } from '../carriers/india-post/adapter.js';
|
|
|
64
64
|
import { adapter as inpost } from '../carriers/inpost/adapter.js';
|
|
65
65
|
import { adapter as intelcom } from '../carriers/intelcom/adapter.js';
|
|
66
66
|
import { adapter as jAndT } from '../carriers/j-and-t/adapter.js';
|
|
67
|
+
import { adapter as jAndTCargo } from '../carriers/j-and-t-cargo/adapter.js';
|
|
67
68
|
import { adapter as japanPost } from '../carriers/japan-post/adapter.js';
|
|
68
69
|
import { adapter as jdLogistics } from '../carriers/jd-logistics/adapter.js';
|
|
69
70
|
import { adapter as koreaPost } from '../carriers/korea-post/adapter.js';
|
|
@@ -185,6 +186,7 @@ export const REGISTRY = {
|
|
|
185
186
|
"inpost": inpost,
|
|
186
187
|
"intelcom": intelcom,
|
|
187
188
|
"j-and-t": jAndT,
|
|
189
|
+
"j-and-t-cargo": jAndTCargo,
|
|
188
190
|
"japan-post": japanPost,
|
|
189
191
|
"jd-logistics": jdLogistics,
|
|
190
192
|
"korea-post": koreaPost,
|
|
@@ -309,7 +311,7 @@ export const REGISTRY = {
|
|
|
309
311
|
"intelcom": "intelcom",
|
|
310
312
|
"intl-post": "universal",
|
|
311
313
|
"j-and-t": "j-and-t",
|
|
312
|
-
"j-and-t-cargo":
|
|
314
|
+
"j-and-t-cargo": "j-and-t-cargo",
|
|
313
315
|
"japan-post": "japan-post",
|
|
314
316
|
"jd-logistics": "jd-logistics",
|
|
315
317
|
"korea-post": "korea-post",
|
package/dist/server/openapi.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "universal-parcel-scraper",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.64.0-main.817",
|
|
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",
|