universal-parcel-scraper 0.893.0-main.4789 → 0.894.0-main.4792

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,7 +13,7 @@ The engine behind [Peek](https://peektracker.com) ([GitHub](https://github.com/p
13
13
  [![License: Apache-2.0](https://img.shields.io/badge/core-Apache--2.0-blue.svg)](LICENSE)
14
14
 
15
15
  <!-- GENERATED:summary -->
16
- **3,500+ carriers** through **670 dedicated adapters** and **5 universal fallbacks**
16
+ **3,500+ carriers** through **671 dedicated adapters** and **5 universal fallbacks**
17
17
 
18
18
  <sub>1201 catalog entries · 154 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 18,030 such statuses, each filed under one stage.
166
+ The carrier folders record 18,036 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
@@ -16854,7 +16854,8 @@
16854
16854
  "timezone": "Asia/Kolkata",
16855
16855
  "tracking": {
16856
16856
  "mode": "automatic",
16857
- "adapter": "ekart"
16857
+ "adapter": "ekart",
16858
+ "recognitionRank": 598
16858
16859
  },
16859
16860
  "canaryUrl": "https://www.ekartlogistics.in/track-order",
16860
16861
  "trackingUrlTemplate": "https://ekartlogistics.com/ekartlogistics-web/shipmenttrack/{trackingNumber}",
@@ -18564,7 +18565,7 @@
18564
18565
  "domains": [
18565
18566
  "www.far800.com"
18566
18567
  ],
18567
- "pathPattern": "^/logistics/result/?$",
18568
+ "pathPattern": "^/(?:en/)?logistics/result/?$",
18568
18569
  "params": [
18569
18570
  "key"
18570
18571
  ],
@@ -38094,7 +38095,8 @@
38094
38095
  "timezone": "America/New_York",
38095
38096
  "tracking": {
38096
38097
  "mode": "automatic",
38097
- "adapter": "universal"
38098
+ "adapter": "skybox",
38099
+ "localClocks": true
38098
38100
  },
38099
38101
  "canaryUrl": "https://www.skybox.net/",
38100
38102
  "trackingUrlTemplate": "https://www.skybox.net/TrackingInformation/IndexPublic?tracking={trackingNumber}",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "catalog": 1201,
3
- "activeAdapters": 670,
3
+ "activeAdapters": 671,
4
4
  "countries": 154,
5
5
  "referenceCarriers": 100,
6
6
  "comparison": {
@@ -13218,7 +13218,8 @@ var at = {
13218
13218
  timezone: "Asia/Kolkata",
13219
13219
  tracking: {
13220
13220
  mode: "automatic",
13221
- adapter: "ekart"
13221
+ adapter: "ekart",
13222
+ recognitionRank: 598
13222
13223
  },
13223
13224
  canaryUrl: "https://www.ekartlogistics.in/track-order",
13224
13225
  trackingUrlTemplate: "https://ekartlogistics.com/ekartlogistics-web/shipmenttrack/{trackingNumber}",
@@ -14485,7 +14486,7 @@ var at = {
14485
14486
  trackingSearchUrl: "https://www.far800.com/",
14486
14487
  linkRules: [{
14487
14488
  domains: ["www.far800.com"],
14488
- pathPattern: "^/logistics/result/?$",
14489
+ pathPattern: "^/(?:en/)?logistics/result/?$",
14489
14490
  params: ["key"],
14490
14491
  ownNumbersOnly: !0
14491
14492
  }],
@@ -29173,7 +29174,8 @@ var at = {
29173
29174
  timezone: "America/New_York",
29174
29175
  tracking: {
29175
29176
  mode: "automatic",
29176
- adapter: "universal"
29177
+ adapter: "skybox",
29178
+ localClocks: !0
29177
29179
  },
29178
29180
  canaryUrl: "https://www.skybox.net/",
29179
29181
  trackingUrlTemplate: "https://www.skybox.net/TrackingInformation/IndexPublic?tracking={trackingNumber}",
@@ -42999,6 +43001,7 @@ var sd = {
42999
43001
  ecoscooting: 655,
43000
43002
  ecourier: 743,
43001
43003
  "egypt-post": 944,
43004
+ ekart: 598,
43002
43005
  elta: 150,
43003
43006
  emile: 480,
43004
43007
  "ems-vietnam": 962,
@@ -1,4 +1,4 @@
1
- import type { AdapterFactory, TrackingContext } from '../../core/adapter/index.js';
1
+ import { type AdapterFactory, type TrackingContext } from '../../core/adapter/index.js';
2
2
  import { type StepRecorder } from '../../core/telemetry/index.js';
3
3
  export declare class EkartTracker {
4
4
  private readonly options;
@@ -1,6 +1,7 @@
1
1
  import { load } from 'cheerio';
2
2
  import makeFetchCookie from 'fetch-cookie';
3
3
  import { CookieJar } from 'tough-cookie';
4
+ import { accepted, recognizeFromLookup } from '../../core/adapter/index.js';
4
5
  import { BudgetExceededError, ChallengeError, SchemaError, TransportError, UpstreamHttpError } from '../../core/errors/index.js';
5
6
  import { runSteps } from '../../core/runner/index.js';
6
7
  import { NOOP_RECORDER } from '../../core/telemetry/index.js';
@@ -52,5 +53,9 @@ export class EkartTracker {
52
53
  }
53
54
  export const adapter = environment => {
54
55
  const tracker = new EkartTracker({ fetcher: environment.fetcher, recorder: environment.recorder, userAgent: environment.userAgent });
55
- return { id: 'ekart', recordsSteps: true, steps: ['direct'], track: (input, context) => tracker.fetch(input.number, context) };
56
+ return {
57
+ id: 'ekart', recordsSteps: true, steps: ['direct'], track: (input, context) => tracker.fetch(input.number, context),
58
+ // A parcel's history says Ekart knows the number; its empty answer for one it does not know stays inconclusive.
59
+ recognize: (number, context) => recognizeFromLookup(() => tracker.fetch(number, context), () => accepted(() => normalizeEkartNumber(number))),
60
+ };
56
61
  };
@@ -19,7 +19,10 @@
19
19
  "adapter": "ekart",
20
20
  "steps": [
21
21
  "direct"
22
- ]
22
+ ],
23
+ "recognition": {
24
+ "rank": 598
25
+ }
23
26
  },
24
27
  "capabilities": [
25
28
  "history",
@@ -70,7 +70,7 @@
70
70
  "domains": [
71
71
  "www.far800.com"
72
72
  ],
73
- "pathPattern": "^/logistics/result/?$",
73
+ "pathPattern": "^/(?:en/)?logistics/result/?$",
74
74
  "params": [
75
75
  "key"
76
76
  ],
@@ -0,0 +1,18 @@
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 fragment SkyBox's public tracking page loads for a number. */
5
+ export declare const SKYBOX_TRACKING = "https://www.skybox.net/TrackingInformation/GetContentPublic";
6
+ /** The fragment's address for one number. */
7
+ export declare function skyboxTrackingUrl(number: string): string;
8
+ export declare class SkyboxTracker {
9
+ private readonly options;
10
+ constructor(options?: {
11
+ fetcher?: typeof fetch;
12
+ recorder?: StepRecorder;
13
+ userAgent?: string;
14
+ });
15
+ private read;
16
+ fetch(raw: string, context?: TrackingContext): Promise<CarrierResult>;
17
+ }
18
+ export declare const adapter: AdapterFactory;
@@ -0,0 +1,49 @@
1
+ import { accepted, recognizeFromLookup } from '../../core/adapter/index.js';
2
+ import { 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, normalizeSkyboxNumber, parseSkybox, PROVIDER, SKYBOX_MAX_BYTES } from './parser.js';
8
+ /** The fragment SkyBox's public tracking page loads for a number. */
9
+ export const SKYBOX_TRACKING = 'https://www.skybox.net/TrackingInformation/GetContentPublic';
10
+ const REDIRECTS = [301, 302, 303, 307, 308];
11
+ const DEFAULT_BUDGET_MS = 20_000;
12
+ /** The fragment's address for one number. */
13
+ export function skyboxTrackingUrl(number) {
14
+ return `${SKYBOX_TRACKING}?${new URLSearchParams({ tracking: number })}`;
15
+ }
16
+ export class SkyboxTracker {
17
+ options;
18
+ constructor(options = {}) {
19
+ this.options = options;
20
+ }
21
+ async read(number, signal, timeoutMs) {
22
+ const { response, bytes } = await fetchBounded(skyboxTrackingUrl(number), { method: 'GET', signal, headers: {
23
+ Accept: 'text/html', 'Accept-Language': 'en', 'User-Agent': userAgentOf(this.options.userAgent),
24
+ } }, { provider: PROVIDER, timeoutMs: Math.max(1, Math.floor(timeoutMs)), maxBytes: SKYBOX_MAX_BYTES, redirect: 'manual',
25
+ fetcher: this.options.fetcher, allowHttpStatuses: [...REDIRECTS, 404, 410] });
26
+ // The fragment answers HTTP 200 for a number it does not know; a redirect, such as to the site's error page, or a 404 is a moved route.
27
+ if (response.status !== 200)
28
+ throw new TransportError(PROVIDER, 'SkyBox tracking fragment moved', { status: response.status });
29
+ return parseSkybox(decodeText(bytes), number);
30
+ }
31
+ async fetch(raw, context = {}) {
32
+ const number = normalizeSkyboxNumber(raw);
33
+ // A read that fails to reach the tracker, or hangs, is sent once more: the
34
+ // first try gets half of the budget.
35
+ return runSteps({ carrier: CARRIER, budgetMs: context.budgetMs ?? DEFAULT_BUDGET_MS, signal: context.signal,
36
+ recorder: this.options.recorder ?? NOOP_RECORDER }, [
37
+ { id: 'direct', run: ({ signal, remainingMs }) => this.read(number, signal, firstAttemptMs(remainingMs)) },
38
+ networkRetryStep(({ signal, remainingMs }) => this.read(number, signal, remainingMs)),
39
+ ]);
40
+ }
41
+ }
42
+ export const adapter = (environment) => {
43
+ const tracker = new SkyboxTracker({ fetcher: environment.fetcher, recorder: environment.recorder, userAgent: environment.userAgent });
44
+ return {
45
+ id: CARRIER, recordsSteps: true, steps: ['direct', 'retry'],
46
+ track: (input, context) => tracker.fetch(input.number, context),
47
+ recognize: (number, context) => recognizeFromLookup(() => tracker.fetch(number, context), () => accepted(() => normalizeSkyboxNumber(number))),
48
+ };
49
+ };
@@ -21,18 +21,56 @@
21
21
  "timezone": "America/New_York",
22
22
  "tracking": {
23
23
  "mode": "automatic",
24
- "adapter": "universal",
25
- "steps": []
24
+ "adapter": "skybox",
25
+ "steps": [
26
+ "direct",
27
+ "retry"
28
+ ],
29
+ "localClocks": true
26
30
  },
27
- "capabilities": [],
31
+ "capabilities": [
32
+ "history",
33
+ "location"
34
+ ],
28
35
  "portal": {
29
36
  "url": "https://www.skybox.net/TrackingInformation/IndexPublic?tracking={trackingNumber}",
30
37
  "searchUrl": "https://www.skybox.net/TrackingInformation/IndexPublic",
31
38
  "canaryUrl": "https://www.skybox.net/",
32
- "shows": [],
33
- "scraped": [],
34
- "discarded": [],
35
- "unavailable": []
39
+ "shows": [
40
+ "progress_rail",
41
+ "history",
42
+ "location",
43
+ "origin",
44
+ "destination",
45
+ "awb_date",
46
+ "recipient_id",
47
+ "recipient_name",
48
+ "weight",
49
+ "order_contents",
50
+ "tracking_alias"
51
+ ],
52
+ "scraped": [
53
+ "history",
54
+ "location"
55
+ ],
56
+ "discarded": [
57
+ "progress_rail",
58
+ "origin",
59
+ "destination",
60
+ "awb_date",
61
+ "recipient_id",
62
+ "recipient_name",
63
+ "weight",
64
+ "order_contents",
65
+ "tracking_alias"
66
+ ],
67
+ "unavailable": [
68
+ "provider_code",
69
+ "eta",
70
+ "offset_timestamps",
71
+ "service_name",
72
+ "proof_of_delivery"
73
+ ]
36
74
  },
37
75
  "links": [
38
76
  {
@@ -0,0 +1,17 @@
1
+ import type { CarrierResult } from '../../core/result/index.js';
2
+ export declare const PROVIDER = "SkyBox";
3
+ export declare const CARRIER = "skybox";
4
+ export declare const SKYBOX_MAX_BYTES: number;
5
+ /** A number for SkyBox's tracker: twelve digits, with or without the dash it prints after the fourth. */
6
+ export declare function normalizeSkyboxNumber(raw: string): string;
7
+ /**
8
+ * The fragment SkyBox's public tracking page loads for one number,
9
+ * `/TrackingInformation/GetContentPublic?tracking=`, which its server renders.
10
+ * Its search box carries the number asked. A known parcel has a skybill id,
11
+ * a card with SkyBox's number, the number it arrived under, the reception
12
+ * date, the suite, the origin, the destination, the consignee, the weight and
13
+ * the items, a status bar and the tracking table. SkyBox's number must be the
14
+ * one asked. An unknown number has the skybill id 0, one message and no card.
15
+ * Only the tracking table is kept.
16
+ */
17
+ export declare function parseSkybox(html: string, raw: string): CarrierResult;
@@ -0,0 +1,125 @@
1
+ import { load } from 'cheerio';
2
+ import { normalizeTrackingNumber } from '../../core/detection/index.js';
3
+ import { ChallengeError, IndeterminateError, InvalidInputError, NoHistoryError, NotFoundError, SchemaError } from '../../core/errors/index.js';
4
+ import { languageStageStatus } from '../../core/status/index.js';
5
+ import { calendarDay } from '../../core/time/index.js';
6
+ import { clean } from '../../core/transport/index.js';
7
+ import { skyboxStage } from './status.js';
8
+ export const PROVIDER = 'SkyBox';
9
+ export const CARRIER = 'skybox';
10
+ // A fragment is about 21 kB.
11
+ export const SKYBOX_MAX_BYTES = 512 * 1024;
12
+ // Twelve digits, which the tracker prints as four digits, a dash and eight digits.
13
+ const NUMBER = /^\d{12}$/;
14
+ const SHOWN_NUMBER = /^\d{4}-?\d{8}$/;
15
+ // "04/24/2025 18:00": month, day, year and a wall clock to the minute with no zone.
16
+ const CLOCK = /^(\d{2})\/(\d{2})\/(\d{4}) (\d{2}):(\d{2})$/;
17
+ // Bot checks and firewall refusals that could stand in for the fragment.
18
+ const CHALLENGE = /_cf_chl_opt|cf-turnstile|<title>\s*(?:Just a moment|Attention Required!|Access Denied)|g-recaptcha|h-captcha|The requested URL was rejected|Request Rejected/i;
19
+ // The whole message the fragment shows for a number the tracker does not know.
20
+ const NOT_FOUND = 'The tracking number does not exists';
21
+ const MAX_EVENTS = 200;
22
+ /** A number for SkyBox's tracker: twelve digits, with or without the dash it prints after the fourth. */
23
+ export function normalizeSkyboxNumber(raw) {
24
+ const number = typeof raw === 'string' && raw.length <= 64 ? normalizeTrackingNumber(raw) : '';
25
+ if (!NUMBER.test(number))
26
+ throw new InvalidInputError(PROVIDER, 'SkyBox numbers are twelve digits');
27
+ return number;
28
+ }
29
+ /** A row's wall clock, to the minute. */
30
+ function wallClock(text) {
31
+ const parts = CLOCK.exec(text);
32
+ if (parts) {
33
+ const [month, day, year, hour, minute] = parts.slice(1).map(Number);
34
+ const date = calendarDay(year, month, day);
35
+ if (date && year >= 2000 && hour <= 23 && minute <= 59)
36
+ return `${date}T${parts[4]}:${parts[5]}:00`;
37
+ }
38
+ throw new SchemaError(PROVIDER, 'SkyBox returned an invalid scan time');
39
+ }
40
+ /** A hidden or disabled field's value, or null when the fragment has no such field. */
41
+ function field($, id) {
42
+ const input = $(`input#${id}`);
43
+ if (input.length > 1)
44
+ throw new SchemaError(PROVIDER, 'SkyBox returned several parcels');
45
+ return input.length ? clean(input.attr('value') ?? '', 80) : null;
46
+ }
47
+ /** The tracking table, newest first: each row gives a scan's wording, its place and its wall clock. */
48
+ function eventsOf($) {
49
+ const card = $('#collapse-tracking');
50
+ if (card.length > 1)
51
+ throw new SchemaError(PROVIDER, 'SkyBox returned several histories');
52
+ const rows = card.find('table tbody tr').toArray();
53
+ if (rows.length > MAX_EVENTS)
54
+ throw new SchemaError(PROVIDER, 'SkyBox returned an invalid history');
55
+ const events = rows.map((row) => {
56
+ const cells = $(row).children('td').toArray().map((cell) => clean($(cell).text(), 300));
57
+ const [description, location, clock] = cells;
58
+ if (cells.length !== 3 || !description || clock === undefined)
59
+ throw new SchemaError(PROVIDER, 'SkyBox returned an invalid scan');
60
+ const staged = skyboxStage(description);
61
+ return {
62
+ local_time: wallClock(clock),
63
+ ...(location ? { location } : {}),
64
+ description,
65
+ ...(staged ? { stage: staged.stage, stage_source: staged.source } : {}),
66
+ };
67
+ });
68
+ // Newest first; the table lists them so, and a stable sort keeps scans of one minute in its order.
69
+ return events.sort((left, right) => right.local_time.localeCompare(left.local_time));
70
+ }
71
+ /**
72
+ * The fragment SkyBox's public tracking page loads for one number,
73
+ * `/TrackingInformation/GetContentPublic?tracking=`, which its server renders.
74
+ * Its search box carries the number asked. A known parcel has a skybill id,
75
+ * a card with SkyBox's number, the number it arrived under, the reception
76
+ * date, the suite, the origin, the destination, the consignee, the weight and
77
+ * the items, a status bar and the tracking table. SkyBox's number must be the
78
+ * one asked. An unknown number has the skybill id 0, one message and no card.
79
+ * Only the tracking table is kept.
80
+ */
81
+ export function parseSkybox(html, raw) {
82
+ const number = normalizeSkyboxNumber(raw);
83
+ if (CHALLENGE.test(html.slice(0, 64_000)))
84
+ throw new ChallengeError(PROVIDER, 'SkyBox answered with a browser check');
85
+ const $ = load(html);
86
+ $('script, style, noscript').remove();
87
+ if ($('#trackinginfo-search-content').length !== 1)
88
+ throw new SchemaError(PROVIDER, 'SkyBox returned an unexpected page');
89
+ const asked = field($, 'trackinginfosearch-txtTracking');
90
+ if (!asked)
91
+ throw new SchemaError(PROVIDER, 'SkyBox returned a fragment without the number asked');
92
+ if (normalizeTrackingNumber(asked) !== number)
93
+ throw new IndeterminateError(PROVIDER, 'SkyBox answered for another number');
94
+ const skybill = field($, 'hdn_trackinginfosearch_skybillid');
95
+ if (skybill === null || !/^\d+$/.test(skybill))
96
+ throw new SchemaError(PROVIDER, 'SkyBox returned a fragment without its skybill');
97
+ const sections = $('.trackinginfosearch-section');
98
+ if (Number(skybill) === 0) {
99
+ const message = clean($('#search-msg').text(), 120);
100
+ if (sections.length || message !== NOT_FOUND)
101
+ throw new SchemaError(PROVIDER, 'SkyBox returned an unexpected fragment');
102
+ throw new NotFoundError(PROVIDER, 'SkyBox does not know this number');
103
+ }
104
+ const shown = field($, 'trackinginfosearch-lblTrackingNumber');
105
+ if (!sections.length || !shown || !SHOWN_NUMBER.test(shown))
106
+ throw new SchemaError(PROVIDER, 'SkyBox returned a parcel without its number');
107
+ // The tracker also finds a parcel by the number it arrived under; only SkyBox's own number is taken.
108
+ if (normalizeTrackingNumber(shown) !== number)
109
+ throw new IndeterminateError(PROVIDER, 'SkyBox answered for another parcel');
110
+ const events = eventsOf($);
111
+ const newest = events[0];
112
+ if (!newest)
113
+ throw new NoHistoryError(PROVIDER);
114
+ const current = events.find((event) => event.stage);
115
+ const stage = current?.stage;
116
+ return {
117
+ status: stage ? languageStageStatus(stage) : 'unknown',
118
+ ...(stage ? { current_stage: stage, current_stage_source: current.stage_source } : {}),
119
+ last_status_text: newest.description,
120
+ last_update: null,
121
+ last_update_local: newest.local_time,
122
+ expected_delivery: null,
123
+ events,
124
+ };
125
+ }
@@ -0,0 +1,7 @@
1
+ import { type ClassifiedWording, type Stage } from '../../core/status/index.js';
2
+ import { type CarrierStatusMap } from '../../core/status/statusMap.js';
3
+ /** The stage SkyBox's map gives a wording in `normalizeStatusWording` form; one it does not list has none. */
4
+ export declare function skyboxMappedStage(wording: string): Stage | undefined;
5
+ /** A scan's stage: the map, else the shared wording rules, else nothing. */
6
+ export declare function skyboxStage(wording: string): ClassifiedWording | null;
7
+ export declare const statusMap: CarrierStatusMap;
@@ -0,0 +1,22 @@
1
+ import { classifyWording } from '../../core/status/index.js';
2
+ import { normalizeStatusWording } from '../../core/status/statusMap.js';
3
+ import statuses from './statuses.json' with { type: 'json' };
4
+ /** The scan wordings of SkyBox's tracker, which carry no code. Another wording falls to the shared wording rules. */
5
+ const WORDINGS = new Map(statuses.entries
6
+ .map((entry) => [normalizeStatusWording(entry.wording), entry.stage]));
7
+ /** The stage SkyBox's map gives a wording in `normalizeStatusWording` form; one it does not list has none. */
8
+ export function skyboxMappedStage(wording) {
9
+ return WORDINGS.get(wording);
10
+ }
11
+ /** A scan's stage: the map, else the shared wording rules, else nothing. */
12
+ export function skyboxStage(wording) {
13
+ const mapped = skyboxMappedStage(normalizeStatusWording(wording));
14
+ if (mapped)
15
+ return { stage: mapped, source: 'carrier_map' };
16
+ const worded = classifyWording(wording, 'in_transit');
17
+ return worded.source === 'none' ? null : worded;
18
+ }
19
+ export const statusMap = {
20
+ stage: (_code, wording) => skyboxMappedStage(wording),
21
+ gaps: [],
22
+ };
@@ -1,4 +1,11 @@
1
1
  {
2
2
  "carrier": "skybox",
3
- "entries": []
3
+ "entries": [
4
+ {"wording": "SKYBILL CREATED", "stage": "accepted", "language": "en", "confirmedBy": "live", "note": "SkyBox's air waybill for a parcel its Miami warehouse has received; the parcel's reception date precedes it."},
5
+ {"wording": "IN TRANSIT", "stage": "in_transit", "language": "en", "confirmedBy": "live", "note": "Shown both at the Miami warehouse and in the destination country."},
6
+ {"wording": "TRANSFERRED TO AIRPORT", "stage": "in_transit", "language": "en", "confirmedBy": "live", "note": "Handed from the Miami warehouse to the airport for the flight."},
7
+ {"wording": "ARRIVED TO YOUR COUNTRY", "stage": "in_transit", "language": "en", "confirmedBy": "live", "note": "Landed in the destination country, before customs; the row can still name the Miami warehouse as its place."},
8
+ {"wording": "RELEASED FROM CUSTOMS", "stage": "in_transit", "language": "en", "confirmedBy": "live", "note": "Cleared by the destination country's customs, before the last mile."},
9
+ {"wording": "PROOF OF DELIVERY", "stage": "delivered", "language": "en", "confirmedBy": "live", "note": "The last row of a delivered parcel, at its destination town."}
10
+ ]
4
11
  }
@@ -549,6 +549,7 @@ import { statusMap as shreeAnjani } from '../../carriers/shree-anjani/status.js'
549
549
  import { statusMap as shreeMaruti } from '../../carriers/shree-maruti/status.js';
550
550
  import { statusMap as singaporePost } from '../../carriers/singapore-post/status.js';
551
551
  import { statusMap as skroutzLastMile } from '../../carriers/skroutz-last-mile/status.js';
552
+ import { statusMap as skybox } from '../../carriers/skybox/status.js';
552
553
  import { statusMap as skyking } from '../../carriers/skyking/status.js';
553
554
  import { statusMap as skynetSa } from '../../carriers/skynet-sa/status.js';
554
555
  import { statusMap as skynetWorldwide } from '../../carriers/skynet-worldwide/status.js';
@@ -753,7 +754,7 @@ export const STATUS_MAPS = {
753
754
  'rpx-indonesia': rpxIndonesia, 'safeway-express-cargo': safewayExpressCargo, sagawa, 'sagawa-global': sagawaGlobal, saia, sailpost, sameday, 'sapx-express': sapxExpress,
754
755
  'sat-ukraine': satUkraine, 'saudi-post': saudiPost, seino, seko, sendex, sendle, sending, serpost, servientrega, seur, 'sf-express': sfExpress, sfc, shadowfax, shipa, shipblu, shipentegra, shiprocket, shouwu,
755
756
  'shree-anjani': shreeAnjani, 'shree-maruti': shreeMaruti, 'singapore-post': singaporePost, 'skroutz-last-mile': skroutzLastMile,
756
- skyking, 'skynet-sa': skynetSa, 'skynet-worldwide': skynetWorldwide, skypostal, slgtrax,
757
+ skybox, skyking, 'skynet-sa': skynetSa, 'skynet-worldwide': skynetWorldwide, skypostal, slgtrax,
757
758
  'slovak-parcel-service': slovakParcelService,
758
759
  'slovenska-posta': slovenskaPosta, 'smsa-express': smsaExpress, 'south-african-post-office': southAfricanPost,
759
760
  'southeastern-freight-lines': southeasternFreightLines, 'sp-express': spExpress, 'spee-dee': speeDee, 'speed-and-safe': speedAndSafe, speedaf, speedex, speedpak, speedx, speedy, 'spring-gds': springGds, 'spx-br': spxBr, 'spx-id': spxId, 'spx-my': spxMy, 'spx-sg': spxSg,
@@ -16854,7 +16854,8 @@
16854
16854
  "timezone": "Asia/Kolkata",
16855
16855
  "tracking": {
16856
16856
  "mode": "automatic",
16857
- "adapter": "ekart"
16857
+ "adapter": "ekart",
16858
+ "recognitionRank": 598
16858
16859
  },
16859
16860
  "canaryUrl": "https://www.ekartlogistics.in/track-order",
16860
16861
  "trackingUrlTemplate": "https://ekartlogistics.com/ekartlogistics-web/shipmenttrack/{trackingNumber}",
@@ -18564,7 +18565,7 @@
18564
18565
  "domains": [
18565
18566
  "www.far800.com"
18566
18567
  ],
18567
- "pathPattern": "^/logistics/result/?$",
18568
+ "pathPattern": "^/(?:en/)?logistics/result/?$",
18568
18569
  "params": [
18569
18570
  "key"
18570
18571
  ],
@@ -38094,7 +38095,8 @@
38094
38095
  "timezone": "America/New_York",
38095
38096
  "tracking": {
38096
38097
  "mode": "automatic",
38097
- "adapter": "universal"
38098
+ "adapter": "skybox",
38099
+ "localClocks": true
38098
38100
  },
38099
38101
  "canaryUrl": "https://www.skybox.net/",
38100
38102
  "trackingUrlTemplate": "https://www.skybox.net/TrackingInformation/IndexPublic?tracking={trackingNumber}",
@@ -10845,6 +10845,7 @@ export declare const CARRIER_CATALOG: {
10845
10845
  readonly tracking: {
10846
10846
  readonly mode: "automatic";
10847
10847
  readonly adapter: "ekart";
10848
+ readonly recognitionRank: 598;
10848
10849
  };
10849
10850
  readonly canaryUrl: "https://www.ekartlogistics.in/track-order";
10850
10851
  readonly trackingUrlTemplate: "https://ekartlogistics.com/ekartlogistics-web/shipmenttrack/{trackingNumber}";
@@ -11923,7 +11924,7 @@ export declare const CARRIER_CATALOG: {
11923
11924
  readonly trackingSearchUrl: "https://www.far800.com/";
11924
11925
  readonly linkRules: readonly [{
11925
11926
  readonly domains: readonly ["www.far800.com"];
11926
- readonly pathPattern: "^/logistics/result/?$";
11927
+ readonly pathPattern: "^/(?:en/)?logistics/result/?$";
11927
11928
  readonly params: readonly ["key"];
11928
11929
  readonly ownNumbersOnly: true;
11929
11930
  }];
@@ -24269,7 +24270,8 @@ export declare const CARRIER_CATALOG: {
24269
24270
  readonly timezone: "America/New_York";
24270
24271
  readonly tracking: {
24271
24272
  readonly mode: "automatic";
24272
- readonly adapter: "universal";
24273
+ readonly adapter: "skybox";
24274
+ readonly localClocks: true;
24273
24275
  };
24274
24276
  readonly canaryUrl: "https://www.skybox.net/";
24275
24277
  readonly trackingUrlTemplate: "https://www.skybox.net/TrackingInformation/IndexPublic?tracking={trackingNumber}";
@@ -16855,7 +16855,8 @@ export const CARRIER_CATALOG = {
16855
16855
  "timezone": "Asia/Kolkata",
16856
16856
  "tracking": {
16857
16857
  "mode": "automatic",
16858
- "adapter": "ekart"
16858
+ "adapter": "ekart",
16859
+ "recognitionRank": 598
16859
16860
  },
16860
16861
  "canaryUrl": "https://www.ekartlogistics.in/track-order",
16861
16862
  "trackingUrlTemplate": "https://ekartlogistics.com/ekartlogistics-web/shipmenttrack/{trackingNumber}",
@@ -18565,7 +18566,7 @@ export const CARRIER_CATALOG = {
18565
18566
  "domains": [
18566
18567
  "www.far800.com"
18567
18568
  ],
18568
- "pathPattern": "^/logistics/result/?$",
18569
+ "pathPattern": "^/(?:en/)?logistics/result/?$",
18569
18570
  "params": [
18570
18571
  "key"
18571
18572
  ],
@@ -38095,7 +38096,8 @@ export const CARRIER_CATALOG = {
38095
38096
  "timezone": "America/New_York",
38096
38097
  "tracking": {
38097
38098
  "mode": "automatic",
38098
- "adapter": "universal"
38099
+ "adapter": "skybox",
38100
+ "localClocks": true
38099
38101
  },
38100
38102
  "canaryUrl": "https://www.skybox.net/",
38101
38103
  "trackingUrlTemplate": "https://www.skybox.net/TrackingInformation/IndexPublic?tracking={trackingNumber}",
@@ -148,6 +148,7 @@ export const CARRIER_RECOGNITION_RANKS = {
148
148
  "ecoscooting": 655,
149
149
  "ecourier": 743,
150
150
  "egypt-post": 944,
151
+ "ekart": 598,
151
152
  "elta": 150,
152
153
  "emile": 480,
153
154
  "ems-vietnam": 962,
@@ -538,6 +538,7 @@ import { adapter as shreeAnjani } from '../carriers/shree-anjani/adapter.js';
538
538
  import { adapter as shreeMaruti } from '../carriers/shree-maruti/adapter.js';
539
539
  import { adapter as singaporePost } from '../carriers/singapore-post/adapter.js';
540
540
  import { adapter as skroutzLastMile } from '../carriers/skroutz-last-mile/adapter.js';
541
+ import { adapter as skybox } from '../carriers/skybox/adapter.js';
541
542
  import { adapter as skyking } from '../carriers/skyking/adapter.js';
542
543
  import { adapter as skynetSa } from '../carriers/skynet-sa/adapter.js';
543
544
  import { adapter as skynetWorldwide } from '../carriers/skynet-worldwide/adapter.js';
@@ -1210,6 +1211,7 @@ export const REGISTRY = {
1210
1211
  "shree-maruti": shreeMaruti,
1211
1212
  "singapore-post": singaporePost,
1212
1213
  "skroutz-last-mile": skroutzLastMile,
1214
+ "skybox": skybox,
1213
1215
  "skyking": skyking,
1214
1216
  "skynet-sa": skynetSa,
1215
1217
  "skynet-worldwide": skynetWorldwide,
@@ -2296,7 +2298,7 @@ export const REGISTRY = {
2296
2298
  "singapore-post": "singapore-post",
2297
2299
  "sjl-express": "universal",
2298
2300
  "skroutz-last-mile": "skroutz-last-mile",
2299
- "skybox": "universal",
2301
+ "skybox": "skybox",
2300
2302
  "skyex-ae": "universal",
2301
2303
  "skyking": "skyking",
2302
2304
  "skynet-sa": "skynet-sa",
@@ -2,7 +2,7 @@
2
2
  "openapi": "3.1.0",
3
3
  "info": {
4
4
  "title": "Universal Parcel Scraper",
5
- "version": "0.893.0-main.4789"
5
+ "version": "0.894.0-main.4792"
6
6
  },
7
7
  "paths": {
8
8
  "/health": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "universal-parcel-scraper",
3
- "version": "0.893.0-main.4789",
3
+ "version": "0.894.0-main.4792",
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",