universal-parcel-scraper 0.861.0-main.4621 → 0.862.1-main.4625
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 +9 -3
- package/data/coverage-summary.json +1 -1
- package/data/detection-golden.json +36 -0
- package/dist/browser/scraper.js +12 -3
- package/dist/carriers/amazon-shipping/adapter.js +5 -2
- package/dist/carriers/amazon-shipping/status.d.ts +2 -0
- package/dist/carriers/amazon-shipping/status.js +8 -3
- package/dist/carriers/consegnia/adapter.d.ts +17 -0
- package/dist/carriers/consegnia/adapter.js +107 -0
- package/dist/carriers/consegnia/carrier.json +38 -7
- package/dist/carriers/consegnia/parser.d.ts +29 -0
- package/dist/carriers/consegnia/parser.js +169 -0
- package/dist/carriers/consegnia/status.d.ts +7 -0
- package/dist/carriers/consegnia/status.js +23 -0
- package/dist/carriers/consegnia/statuses.json +8 -1
- package/dist/carriers/fmx/adapter.d.ts +18 -0
- package/dist/carriers/fmx/adapter.js +50 -0
- package/dist/carriers/fmx/carrier.json +43 -7
- package/dist/carriers/fmx/parser.d.ts +17 -0
- package/dist/carriers/fmx/parser.js +140 -0
- package/dist/carriers/fmx/status.d.ts +7 -0
- package/dist/carriers/fmx/status.js +30 -0
- package/dist/carriers/fmx/statuses.json +5 -1
- package/dist/carriers/hitch/adapter.d.ts +18 -0
- package/dist/carriers/hitch/adapter.js +49 -0
- package/dist/carriers/hitch/carrier.json +43 -7
- package/dist/carriers/hitch/parser.d.ts +19 -0
- package/dist/carriers/hitch/parser.js +151 -0
- package/dist/carriers/hitch/status.d.ts +7 -0
- package/dist/carriers/hitch/status.js +22 -0
- package/dist/carriers/hitch/statuses.json +9 -1
- package/dist/core/catalog/statusMaps.js +6 -3
- package/dist/data/catalog.json +9 -3
- package/dist/generated/catalog.d.ts +9 -3
- package/dist/generated/catalog.js +9 -3
- package/dist/generated/recognition.js +3 -0
- package/dist/generated/registry.js +9 -3
- 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 **661 dedicated adapters** and **5 universal fallbacks**
|
|
17
17
|
|
|
18
18
|
<sub>1045 catalog entries · 145 countries represented</sub>
|
|
19
19
|
<!-- /GENERATED:summary -->
|
|
@@ -163,7 +163,7 @@ the command line's `track --source ParcelsApp` does the same.
|
|
|
163
163
|
<!-- GENERATED:stages -->
|
|
164
164
|
<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">
|
|
165
165
|
|
|
166
|
-
The carrier folders record 17,
|
|
166
|
+
The carrier folders record 17,895 such statuses, each filed under one stage.
|
|
167
167
|
<!-- /GENERATED:stages -->
|
|
168
168
|
|
|
169
169
|
Wording nobody recorded yet goes through a classifier that reads seven European languages.
|
package/data/catalog.json
CHANGED
|
@@ -11611,7 +11611,9 @@
|
|
|
11611
11611
|
"timezone": "Asia/Manila",
|
|
11612
11612
|
"tracking": {
|
|
11613
11613
|
"mode": "automatic",
|
|
11614
|
-
"adapter": "
|
|
11614
|
+
"adapter": "consegnia",
|
|
11615
|
+
"recognitionRank": 57,
|
|
11616
|
+
"localClocks": true
|
|
11615
11617
|
},
|
|
11616
11618
|
"canaryUrl": "https://clc-express.com/",
|
|
11617
11619
|
"trackingUrlTemplate": "https://clc-express.com/track-delivery?awb={trackingNumber}",
|
|
@@ -18128,7 +18130,9 @@
|
|
|
18128
18130
|
"timezone": "Asia/Kuala_Lumpur",
|
|
18129
18131
|
"tracking": {
|
|
18130
18132
|
"mode": "automatic",
|
|
18131
|
-
"adapter": "
|
|
18133
|
+
"adapter": "fmx",
|
|
18134
|
+
"recognitionRank": 147,
|
|
18135
|
+
"localClocks": true
|
|
18132
18136
|
},
|
|
18133
18137
|
"canaryUrl": "https://www.fmx.asia/",
|
|
18134
18138
|
"trackingUrlTemplate": "https://www.fmx.asia/result_detail.php?p=1&conno={trackingNumber}",
|
|
@@ -20577,7 +20581,9 @@
|
|
|
20577
20581
|
"timezone": "Asia/Manila",
|
|
20578
20582
|
"tracking": {
|
|
20579
20583
|
"mode": "automatic",
|
|
20580
|
-
"adapter": "
|
|
20584
|
+
"adapter": "hitch",
|
|
20585
|
+
"recognitionRank": 117,
|
|
20586
|
+
"localClocks": true
|
|
20581
20587
|
},
|
|
20582
20588
|
"canaryUrl": "https://hitch.com.ph/",
|
|
20583
20589
|
"trackingUrlTemplate": "https://hitch.com.ph/tracking?no={trackingNumber}",
|
|
@@ -87063,6 +87063,9 @@
|
|
|
87063
87063
|
"confidence": "low",
|
|
87064
87064
|
"candidates": [
|
|
87065
87065
|
"fmx"
|
|
87066
|
+
],
|
|
87067
|
+
"asked": [
|
|
87068
|
+
"fmx"
|
|
87066
87069
|
]
|
|
87067
87070
|
},
|
|
87068
87071
|
{
|
|
@@ -87071,6 +87074,9 @@
|
|
|
87071
87074
|
"confidence": "low",
|
|
87072
87075
|
"candidates": [
|
|
87073
87076
|
"fmx"
|
|
87077
|
+
],
|
|
87078
|
+
"asked": [
|
|
87079
|
+
"fmx"
|
|
87074
87080
|
]
|
|
87075
87081
|
},
|
|
87076
87082
|
{
|
|
@@ -87079,6 +87085,9 @@
|
|
|
87079
87085
|
"confidence": "low",
|
|
87080
87086
|
"candidates": [
|
|
87081
87087
|
"fmx"
|
|
87088
|
+
],
|
|
87089
|
+
"asked": [
|
|
87090
|
+
"fmx"
|
|
87082
87091
|
]
|
|
87083
87092
|
},
|
|
87084
87093
|
{
|
|
@@ -100584,6 +100593,9 @@
|
|
|
100584
100593
|
"confidence": "low",
|
|
100585
100594
|
"candidates": [
|
|
100586
100595
|
"hitch"
|
|
100596
|
+
],
|
|
100597
|
+
"asked": [
|
|
100598
|
+
"hitch"
|
|
100587
100599
|
]
|
|
100588
100600
|
},
|
|
100589
100601
|
{
|
|
@@ -100592,6 +100604,9 @@
|
|
|
100592
100604
|
"confidence": "low",
|
|
100593
100605
|
"candidates": [
|
|
100594
100606
|
"hitch"
|
|
100607
|
+
],
|
|
100608
|
+
"asked": [
|
|
100609
|
+
"hitch"
|
|
100595
100610
|
]
|
|
100596
100611
|
},
|
|
100597
100612
|
{
|
|
@@ -100600,6 +100615,9 @@
|
|
|
100600
100615
|
"confidence": "low",
|
|
100601
100616
|
"candidates": [
|
|
100602
100617
|
"hitch"
|
|
100618
|
+
],
|
|
100619
|
+
"asked": [
|
|
100620
|
+
"hitch"
|
|
100603
100621
|
]
|
|
100604
100622
|
},
|
|
100605
100623
|
{
|
|
@@ -100608,6 +100626,9 @@
|
|
|
100608
100626
|
"confidence": "low",
|
|
100609
100627
|
"candidates": [
|
|
100610
100628
|
"hitch"
|
|
100629
|
+
],
|
|
100630
|
+
"asked": [
|
|
100631
|
+
"hitch"
|
|
100611
100632
|
]
|
|
100612
100633
|
},
|
|
100613
100634
|
{
|
|
@@ -100616,6 +100637,9 @@
|
|
|
100616
100637
|
"confidence": "low",
|
|
100617
100638
|
"candidates": [
|
|
100618
100639
|
"hitch"
|
|
100640
|
+
],
|
|
100641
|
+
"asked": [
|
|
100642
|
+
"hitch"
|
|
100619
100643
|
]
|
|
100620
100644
|
},
|
|
100621
100645
|
{
|
|
@@ -100624,6 +100648,9 @@
|
|
|
100624
100648
|
"confidence": "low",
|
|
100625
100649
|
"candidates": [
|
|
100626
100650
|
"hitch"
|
|
100651
|
+
],
|
|
100652
|
+
"asked": [
|
|
100653
|
+
"hitch"
|
|
100627
100654
|
]
|
|
100628
100655
|
},
|
|
100629
100656
|
{
|
|
@@ -100632,6 +100659,9 @@
|
|
|
100632
100659
|
"confidence": "low",
|
|
100633
100660
|
"candidates": [
|
|
100634
100661
|
"hitch"
|
|
100662
|
+
],
|
|
100663
|
+
"asked": [
|
|
100664
|
+
"hitch"
|
|
100635
100665
|
]
|
|
100636
100666
|
},
|
|
100637
100667
|
{
|
|
@@ -100640,6 +100670,9 @@
|
|
|
100640
100670
|
"confidence": "low",
|
|
100641
100671
|
"candidates": [
|
|
100642
100672
|
"hitch"
|
|
100673
|
+
],
|
|
100674
|
+
"asked": [
|
|
100675
|
+
"hitch"
|
|
100643
100676
|
]
|
|
100644
100677
|
},
|
|
100645
100678
|
{
|
|
@@ -100648,6 +100681,9 @@
|
|
|
100648
100681
|
"confidence": "low",
|
|
100649
100682
|
"candidates": [
|
|
100650
100683
|
"hitch"
|
|
100684
|
+
],
|
|
100685
|
+
"asked": [
|
|
100686
|
+
"hitch"
|
|
100651
100687
|
]
|
|
100652
100688
|
},
|
|
100653
100689
|
{
|
package/dist/browser/scraper.js
CHANGED
|
@@ -9243,7 +9243,9 @@ var tt = {
|
|
|
9243
9243
|
timezone: "Asia/Manila",
|
|
9244
9244
|
tracking: {
|
|
9245
9245
|
mode: "automatic",
|
|
9246
|
-
adapter: "
|
|
9246
|
+
adapter: "consegnia",
|
|
9247
|
+
recognitionRank: 57,
|
|
9248
|
+
localClocks: !0
|
|
9247
9249
|
},
|
|
9248
9250
|
canaryUrl: "https://clc-express.com/",
|
|
9249
9251
|
trackingUrlTemplate: "https://clc-express.com/track-delivery?awb={trackingNumber}",
|
|
@@ -14105,7 +14107,9 @@ var tt = {
|
|
|
14105
14107
|
timezone: "Asia/Kuala_Lumpur",
|
|
14106
14108
|
tracking: {
|
|
14107
14109
|
mode: "automatic",
|
|
14108
|
-
adapter: "
|
|
14110
|
+
adapter: "fmx",
|
|
14111
|
+
recognitionRank: 147,
|
|
14112
|
+
localClocks: !0
|
|
14109
14113
|
},
|
|
14110
14114
|
canaryUrl: "https://www.fmx.asia/",
|
|
14111
14115
|
trackingUrlTemplate: "https://www.fmx.asia/result_detail.php?p=1&conno={trackingNumber}",
|
|
@@ -15930,7 +15934,9 @@ var tt = {
|
|
|
15930
15934
|
timezone: "Asia/Manila",
|
|
15931
15935
|
tracking: {
|
|
15932
15936
|
mode: "automatic",
|
|
15933
|
-
adapter: "
|
|
15937
|
+
adapter: "hitch",
|
|
15938
|
+
recognitionRank: 117,
|
|
15939
|
+
localClocks: !0
|
|
15934
15940
|
},
|
|
15935
15941
|
canaryUrl: "https://hitch.com.ph/",
|
|
15936
15942
|
trackingUrlTemplate: "https://hitch.com.ph/tracking?no={trackingNumber}",
|
|
@@ -39169,6 +39175,7 @@ var ed = {
|
|
|
39169
39175
|
"closer-logistics": 849,
|
|
39170
39176
|
"colis-prive": 740,
|
|
39171
39177
|
"connect-couriers": 831,
|
|
39178
|
+
consegnia: 57,
|
|
39172
39179
|
coordinadora: 973,
|
|
39173
39180
|
"copa-courier": 773,
|
|
39174
39181
|
corre: 587,
|
|
@@ -39259,6 +39266,7 @@ var ed = {
|
|
|
39259
39266
|
fleetex: 571,
|
|
39260
39267
|
fleetoptics: 763,
|
|
39261
39268
|
flippost: 568,
|
|
39269
|
+
fmx: 147,
|
|
39262
39270
|
"forward-air": 755,
|
|
39263
39271
|
"forza-delivery": 633,
|
|
39264
39272
|
"four-px": 570,
|
|
@@ -39301,6 +39309,7 @@ var ed = {
|
|
|
39301
39309
|
hermes: 602,
|
|
39302
39310
|
"hermes-at": 876,
|
|
39303
39311
|
"hermes-de": 840,
|
|
39312
|
+
hitch: 117,
|
|
39304
39313
|
"houng-ah-loun": 637,
|
|
39305
39314
|
hovership: 687,
|
|
39306
39315
|
"hr-parcel": 887,
|
|
@@ -6,7 +6,7 @@ import { ChallengeError, IndeterminateError, InvalidInputError, NotFoundError, S
|
|
|
6
6
|
import { listsNewestFirst } from '../../core/result/order.js';
|
|
7
7
|
import { cleanScalar, decodeText, fetchBounded, parseJsonBytes, userAgentOf } from '../../core/transport/index.js';
|
|
8
8
|
import { isRecord } from '../../core/types.js';
|
|
9
|
-
import { classifyStatus, statusKey } from './status.js';
|
|
9
|
+
import { classifyStatus, DELAYED, statusKey } from './status.js';
|
|
10
10
|
const PROVIDER = 'Amazon Shipping';
|
|
11
11
|
const DEFAULT_TIMEOUT_MS = 15_000;
|
|
12
12
|
const MAX_RESPONSE_BYTES = 2_000_000;
|
|
@@ -266,7 +266,10 @@ export function parseAmazonShippingTrackingResponse(payload, zone = 'Europe/Pari
|
|
|
266
266
|
// that had just left their pickup site and went on being scanned for another day.
|
|
267
267
|
const unscannedDelivery = current.stage === 'delivered' && latestKnown !== undefined
|
|
268
268
|
&& !parsedEvents.some((item) => item.classified.stage === 'delivered');
|
|
269
|
-
|
|
269
|
+
// A delay moves nothing: a parcel whose scans show only its label keeps that stage, with the delay as its text.
|
|
270
|
+
const unmovedDelay = current === DELAYED && latestKnown?.classified.stage === 'registered';
|
|
271
|
+
const active = unmovedDelay ? { ...latestKnown.classified, description: DELAYED.description }
|
|
272
|
+
: current.status !== 'unknown' && !echoesNewest && !unscannedDelivery ? current : latestKnown?.classified;
|
|
270
273
|
if (!active) {
|
|
271
274
|
throw new SchemaError(PROVIDER, 'Amazon Shipping returned incomplete tracking details');
|
|
272
275
|
}
|
|
@@ -20,6 +20,8 @@ export interface ClassifiedStatus {
|
|
|
20
20
|
description: string;
|
|
21
21
|
}
|
|
22
22
|
export declare const UNKNOWN_STATUS: ClassifiedStatus;
|
|
23
|
+
/** A late delivery: no exception, and no movement of its own. */
|
|
24
|
+
export declare const DELAYED: ClassifiedStatus;
|
|
23
25
|
/** Fold any provider signal into the comparison key: lower case, letters and digits only. */
|
|
24
26
|
export declare function statusKey(value: unknown): string;
|
|
25
27
|
/** Classify one or more provider signals; the first matching rule wins. */
|
|
@@ -3,6 +3,12 @@ export const UNKNOWN_STATUS = {
|
|
|
3
3
|
stage: 'in_transit',
|
|
4
4
|
description: 'Amazon Shipping update',
|
|
5
5
|
};
|
|
6
|
+
/** A late delivery: no exception, and no movement of its own. */
|
|
7
|
+
export const DELAYED = {
|
|
8
|
+
status: 'in_transit',
|
|
9
|
+
stage: 'in_transit',
|
|
10
|
+
description: 'Delivery delayed',
|
|
11
|
+
};
|
|
6
12
|
/** Fold any provider signal into the comparison key: lower case, letters and digits only. */
|
|
7
13
|
export function statusKey(value) {
|
|
8
14
|
if (typeof value !== 'string' && typeof value !== 'number')
|
|
@@ -48,9 +54,8 @@ export function classifyStatus(...values) {
|
|
|
48
54
|
])) {
|
|
49
55
|
return { status: 'exception', stage: 'failed_attempt', description: 'Delivery issue' };
|
|
50
56
|
}
|
|
51
|
-
if (includesAny(key, ['delayed', 'late']))
|
|
52
|
-
return
|
|
53
|
-
}
|
|
57
|
+
if (includesAny(key, ['delayed', 'late']))
|
|
58
|
+
return DELAYED;
|
|
54
59
|
if (includesAny(key, ['delivered'])) {
|
|
55
60
|
return { status: 'delivered', stage: 'delivered', description: 'Delivered' };
|
|
56
61
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type AdapterFactory, type TrackingContext } from '../../core/adapter/index.js';
|
|
2
|
+
import type { CarrierResult } from '../../core/result/index.js';
|
|
3
|
+
import { type StepRecorder } from '../../core/telemetry/index.js';
|
|
4
|
+
/** The tracking page, which opens the session its lookup needs and carries the session's token. */
|
|
5
|
+
export declare const CONSEGNIA_PAGE = "https://clc-express.com/track-delivery";
|
|
6
|
+
/** The lookup the tracking page's script posts the number to. */
|
|
7
|
+
export declare const CONSEGNIA_LOOKUP = "https://clc-express.com/track-delivery-result";
|
|
8
|
+
export declare class ConsegniaTracker {
|
|
9
|
+
private readonly options;
|
|
10
|
+
constructor(options?: {
|
|
11
|
+
fetcher?: typeof fetch;
|
|
12
|
+
recorder?: StepRecorder;
|
|
13
|
+
userAgent?: string;
|
|
14
|
+
});
|
|
15
|
+
fetch(raw: string, context?: TrackingContext): Promise<CarrierResult>;
|
|
16
|
+
}
|
|
17
|
+
export declare const adapter: AdapterFactory;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { accepted, recognizeFromLookup } from '../../core/adapter/index.js';
|
|
2
|
+
import { ChallengeError, IndeterminateError, NotFoundError, SchemaError, TransportError } from '../../core/errors/index.js';
|
|
3
|
+
import { runSteps } from '../../core/runner/index.js';
|
|
4
|
+
import { firstAttemptMs, networkRetryStep } from '../../core/runner/networkRetry.js';
|
|
5
|
+
import { NOOP_RECORDER } from '../../core/telemetry/index.js';
|
|
6
|
+
import { decodeText, fetchBounded, userAgentOf } from '../../core/transport/index.js';
|
|
7
|
+
import { CARRIER, CONSEGNIA_MAX_BYTES, consegniaJson, consegniaToken, isBlockPage, isUnknownShipment, normalizeConsegniaNumber, parseConsegnia, PROVIDER, } from './parser.js';
|
|
8
|
+
const ORIGIN = 'https://clc-express.com';
|
|
9
|
+
/** The tracking page, which opens the session its lookup needs and carries the session's token. */
|
|
10
|
+
export const CONSEGNIA_PAGE = `${ORIGIN}/track-delivery`;
|
|
11
|
+
/** The lookup the tracking page's script posts the number to. */
|
|
12
|
+
export const CONSEGNIA_LOOKUP = `${ORIGIN}/track-delivery-result`;
|
|
13
|
+
const REDIRECTS = [301, 302, 303, 307, 308];
|
|
14
|
+
// The lookup answers an unknown number with its own HTTP 404; any other is a missing route.
|
|
15
|
+
const GONE = [404, 410];
|
|
16
|
+
// Laravel's refusal of a token or session it did not issue, or one that expired.
|
|
17
|
+
const REFUSED_SESSION = 419;
|
|
18
|
+
const COOKIE_NAME = /^[A-Za-z0-9!#$%&'*+.^_`|~-]{1,128}$/;
|
|
19
|
+
const COOKIE_VALUE = /^[^\s;,]{1,4096}$/;
|
|
20
|
+
const MAX_COOKIES = 8;
|
|
21
|
+
const DEFAULT_BUDGET_MS = 15_000;
|
|
22
|
+
/**
|
|
23
|
+
* The cookies the tracking page sets, as the lookup sends them back: Laravel's
|
|
24
|
+
* session, its token cookie and the site's own. Null without the session.
|
|
25
|
+
*/
|
|
26
|
+
function sessionCookies(response) {
|
|
27
|
+
const pairs = [];
|
|
28
|
+
for (const header of response.headers.getSetCookie()) {
|
|
29
|
+
const pair = header.split(';', 1)[0].trim();
|
|
30
|
+
const separator = pair.indexOf('=');
|
|
31
|
+
const name = pair.slice(0, separator);
|
|
32
|
+
if (separator > 0 && COOKIE_NAME.test(name) && COOKIE_VALUE.test(pair.slice(separator + 1)) && pairs.length < MAX_COOKIES)
|
|
33
|
+
pairs.push(pair);
|
|
34
|
+
}
|
|
35
|
+
return pairs.some((pair) => /^[^=]+_session=/.test(pair)) ? pairs.join('; ') : null;
|
|
36
|
+
}
|
|
37
|
+
export class ConsegniaTracker {
|
|
38
|
+
options;
|
|
39
|
+
constructor(options = {}) {
|
|
40
|
+
this.options = options;
|
|
41
|
+
}
|
|
42
|
+
async fetch(raw, context = {}) {
|
|
43
|
+
const number = normalizeConsegniaNumber(raw);
|
|
44
|
+
const userAgent = userAgentOf(this.options.userAgent);
|
|
45
|
+
// One attempt opens a session on the tracking page and posts the number with its token, as the page's script does.
|
|
46
|
+
const attempt = async (signal, timeoutMs) => {
|
|
47
|
+
const deadline = performance.now() + timeoutMs;
|
|
48
|
+
// A request left without time fails as a timeout, which the retry can recover.
|
|
49
|
+
const left = () => Math.max(1, Math.floor(deadline - performance.now()));
|
|
50
|
+
const page = await fetchBounded(CONSEGNIA_PAGE, { method: 'GET', signal, headers: {
|
|
51
|
+
Accept: 'text/html,application/xhtml+xml', 'User-Agent': userAgent,
|
|
52
|
+
} }, { provider: PROVIDER, timeoutMs: left(), maxBytes: CONSEGNIA_MAX_BYTES, redirect: 'manual',
|
|
53
|
+
allowHttpStatuses: [...REDIRECTS, ...GONE, 403], fetcher: this.options.fetcher });
|
|
54
|
+
if (page.response.status === 403)
|
|
55
|
+
throw new ChallengeError(PROVIDER, 'Consegnia refused its tracking page', { status: 403 });
|
|
56
|
+
if (page.response.status !== 200) {
|
|
57
|
+
throw new TransportError(PROVIDER, 'Consegnia tracking page is unavailable', { status: page.response.status });
|
|
58
|
+
}
|
|
59
|
+
const html = decodeText(page.bytes);
|
|
60
|
+
const token = consegniaToken(html);
|
|
61
|
+
if (!token) {
|
|
62
|
+
if (isBlockPage(html))
|
|
63
|
+
throw new ChallengeError(PROVIDER, 'Consegnia answered the tracking page with a browser check');
|
|
64
|
+
throw new SchemaError(PROVIDER, 'Consegnia tracking page carries no lookup token');
|
|
65
|
+
}
|
|
66
|
+
const cookie = sessionCookies(page.response);
|
|
67
|
+
if (!cookie)
|
|
68
|
+
throw new SchemaError(PROVIDER, 'Consegnia tracking page opened no session');
|
|
69
|
+
const lookup = await fetchBounded(CONSEGNIA_LOOKUP, { method: 'POST', signal, headers: {
|
|
70
|
+
Accept: 'application/json, text/javascript, */*; q=0.01', 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
|
71
|
+
'User-Agent': userAgent, Cookie: cookie, 'X-Requested-With': 'XMLHttpRequest', Origin: ORIGIN, Referer: CONSEGNIA_PAGE,
|
|
72
|
+
}, body: new URLSearchParams({ _token: token, awb_number: number }).toString() }, { provider: PROVIDER, timeoutMs: left(),
|
|
73
|
+
maxBytes: CONSEGNIA_MAX_BYTES, redirect: 'manual', allowHttpStatuses: [...REDIRECTS, ...GONE, 403, REFUSED_SESSION],
|
|
74
|
+
fetcher: this.options.fetcher });
|
|
75
|
+
const { status } = lookup.response;
|
|
76
|
+
const body = decodeText(lookup.bytes).replace(/^\uFEFF/, '').trim();
|
|
77
|
+
if (REDIRECTS.includes(status) || status === 410)
|
|
78
|
+
throw new TransportError(PROVIDER, 'Consegnia tracking lookup is unavailable', { status });
|
|
79
|
+
if (status === 403)
|
|
80
|
+
throw new ChallengeError(PROVIDER, 'Consegnia refused the lookup', { status });
|
|
81
|
+
// The session the page just opened was refused: it says nothing about the shipment.
|
|
82
|
+
if (status === REFUSED_SESSION)
|
|
83
|
+
throw new IndeterminateError(PROVIDER, 'Consegnia refused the tracking page’s session', { status });
|
|
84
|
+
if (status === 404) {
|
|
85
|
+
if (isUnknownShipment(body))
|
|
86
|
+
throw new NotFoundError(PROVIDER);
|
|
87
|
+
throw new TransportError(PROVIDER, 'Consegnia tracking lookup is unavailable', { status });
|
|
88
|
+
}
|
|
89
|
+
return parseConsegnia(consegniaJson(body), number);
|
|
90
|
+
};
|
|
91
|
+
// An attempt that fails to reach Consegnia, or hangs, gets one more in a new
|
|
92
|
+
// session: the first gets half of the budget, so the retry fits in the rest.
|
|
93
|
+
return runSteps({ carrier: CARRIER, budgetMs: context.budgetMs ?? DEFAULT_BUDGET_MS, signal: context.signal,
|
|
94
|
+
recorder: this.options.recorder ?? NOOP_RECORDER }, [
|
|
95
|
+
{ id: 'direct', run: ({ signal, remainingMs }) => attempt(signal, firstAttemptMs(remainingMs)) },
|
|
96
|
+
networkRetryStep(({ signal, remainingMs }) => attempt(signal, remainingMs)),
|
|
97
|
+
]);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
export const adapter = (environment) => {
|
|
101
|
+
const tracker = new ConsegniaTracker({ fetcher: environment.fetcher, recorder: environment.recorder, userAgent: environment.userAgent });
|
|
102
|
+
return {
|
|
103
|
+
id: CARRIER, recordsSteps: true, steps: ['direct', 'retry'],
|
|
104
|
+
track: (input, context) => tracker.fetch(input.number, context),
|
|
105
|
+
recognize: (number, context) => recognizeFromLookup(() => tracker.fetch(number, context), () => accepted(() => normalizeConsegniaNumber(number))),
|
|
106
|
+
};
|
|
107
|
+
};
|
|
@@ -18,18 +18,49 @@
|
|
|
18
18
|
"timezone": "Asia/Manila",
|
|
19
19
|
"tracking": {
|
|
20
20
|
"mode": "automatic",
|
|
21
|
-
"adapter": "
|
|
22
|
-
"steps": [
|
|
21
|
+
"adapter": "consegnia",
|
|
22
|
+
"steps": [
|
|
23
|
+
"direct",
|
|
24
|
+
"retry"
|
|
25
|
+
],
|
|
26
|
+
"recognition": {
|
|
27
|
+
"rank": 57
|
|
28
|
+
},
|
|
29
|
+
"localClocks": true
|
|
23
30
|
},
|
|
24
|
-
"capabilities": [
|
|
31
|
+
"capabilities": [
|
|
32
|
+
"history"
|
|
33
|
+
],
|
|
25
34
|
"portal": {
|
|
26
35
|
"url": "https://clc-express.com/track-delivery?awb={trackingNumber}",
|
|
27
36
|
"searchUrl": "https://clc-express.com/track-delivery",
|
|
28
37
|
"canaryUrl": "https://clc-express.com/",
|
|
29
|
-
"shows": [
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
38
|
+
"shows": [
|
|
39
|
+
"status",
|
|
40
|
+
"history",
|
|
41
|
+
"failed_delivery_reason",
|
|
42
|
+
"awb_date",
|
|
43
|
+
"delivered_at"
|
|
44
|
+
],
|
|
45
|
+
"scraped": [
|
|
46
|
+
"status",
|
|
47
|
+
"history",
|
|
48
|
+
"failed_delivery_reason"
|
|
49
|
+
],
|
|
50
|
+
"discarded": [
|
|
51
|
+
"awb_date",
|
|
52
|
+
"delivered_at"
|
|
53
|
+
],
|
|
54
|
+
"unavailable": [
|
|
55
|
+
"location",
|
|
56
|
+
"eta",
|
|
57
|
+
"offset_timestamps",
|
|
58
|
+
"provider_code",
|
|
59
|
+
"weight",
|
|
60
|
+
"sender_name",
|
|
61
|
+
"recipient_name",
|
|
62
|
+
"proof_of_delivery"
|
|
63
|
+
]
|
|
33
64
|
},
|
|
34
65
|
"links": [
|
|
35
66
|
{
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { CarrierResult } from '../../core/result/index.js';
|
|
2
|
+
export declare const PROVIDER = "Consegnia Logistics";
|
|
3
|
+
export declare const CARRIER = "consegnia";
|
|
4
|
+
export declare const CONSEGNIA_MAX_BYTES: number;
|
|
5
|
+
/** A number for Consegnia's tracker: CLC and nine digits. */
|
|
6
|
+
export declare function normalizeConsegniaNumber(raw: string): string;
|
|
7
|
+
/** A firewall or bot-check page in place of the tracking page or the lookup's JSON. */
|
|
8
|
+
export declare function isBlockPage(body: string): boolean;
|
|
9
|
+
/** The token the tracking page's form sends with a number, or null when the page has none. */
|
|
10
|
+
export declare function consegniaToken(html: string): string | null;
|
|
11
|
+
/**
|
|
12
|
+
* The lookup's reply for one number, `{ success, data: { html } }`: a fragment
|
|
13
|
+
* its server renders with the number, the current status, the last update, the
|
|
14
|
+
* booking day, the delivery clock once delivered, and the history, newest
|
|
15
|
+
* first, each row a status, the reason for a failed delivery and a wall clock
|
|
16
|
+
* to the minute with no zone. It names no person, place or phone number.
|
|
17
|
+
*
|
|
18
|
+
* The fragment must carry the whole number asked. The delivery clock is not
|
|
19
|
+
* read: a delivered parcel's newest row gives it, and it has no zone either.
|
|
20
|
+
*/
|
|
21
|
+
export declare function parseConsegnia(payload: unknown, raw: string): CarrierResult;
|
|
22
|
+
/**
|
|
23
|
+
* The lookup's answer to a number it does not hold: HTTP 404 with `success`
|
|
24
|
+
* false and its message. The message does not quote the number. Any other 404
|
|
25
|
+
* is a missing route.
|
|
26
|
+
*/
|
|
27
|
+
export declare function isUnknownShipment(body: string): boolean;
|
|
28
|
+
/** The lookup's reply as JSON, or the reason it is not one. */
|
|
29
|
+
export declare function consegniaJson(body: string): unknown;
|