universal-parcel-scraper 0.577.3-main.2713 → 0.578.0-main.2716
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.
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
import { type AdapterFactory, type TrackingContext } from '../../core/adapter/index.js';
|
|
2
2
|
import type { CarrierResult } from '../../core/result/index.js';
|
|
3
3
|
import { type StepRecorder } from '../../core/telemetry/index.js';
|
|
4
|
+
/** A lookup of a DPD parcel number in DPD's group-wide guest service. */
|
|
5
|
+
export type DpdGroupLookup = (number: string, context: TrackingContext) => Promise<CarrierResult>;
|
|
4
6
|
export declare class DpdIeTracker {
|
|
5
7
|
private readonly options;
|
|
6
8
|
constructor(options?: {
|
|
7
9
|
fetcher?: typeof fetch;
|
|
8
10
|
recorder?: StepRecorder;
|
|
9
11
|
userAgent?: string;
|
|
12
|
+
group?: DpdGroupLookup;
|
|
10
13
|
});
|
|
14
|
+
/**
|
|
15
|
+
* A parcel number's answer from DPD Ireland's tracker and DPD's group-wide
|
|
16
|
+
* guest service, asked together: the tracker's history when it holds more
|
|
17
|
+
* scans and is not behind the guest service's milestones, else those. Either
|
|
18
|
+
* answers when the other fails; not found needs both.
|
|
19
|
+
*/
|
|
20
|
+
track(raw: string, context?: TrackingContext): Promise<CarrierResult>;
|
|
11
21
|
fetch(raw: string, context?: TrackingContext): Promise<CarrierResult>;
|
|
12
22
|
private lookup;
|
|
13
23
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { accepted, recognizeFromLookup } from '../../core/adapter/index.js';
|
|
2
|
-
import { BudgetExceededError, ChallengeError, IndeterminateError, SchemaError } from '../../core/errors/index.js';
|
|
2
|
+
import { BudgetExceededError, ChallengeError, IndeterminateError, NotFoundError, SchemaError } from '../../core/errors/index.js';
|
|
3
|
+
import { fillPartialAnswer } from '../../core/result/partial.js';
|
|
3
4
|
import { runSteps } from '../../core/runner/index.js';
|
|
4
5
|
import { firstAttemptMs, networkRetryStep } from '../../core/runner/networkRetry.js';
|
|
5
6
|
import { NOOP_RECORDER } from '../../core/telemetry/index.js';
|
|
6
7
|
import { decodeText, fetchBounded, userAgentOf } from '../../core/transport/index.js';
|
|
7
|
-
import {
|
|
8
|
+
import { DPDTracker } from '../dpd/adapter.js';
|
|
9
|
+
import { isDpdIeChallenge, isDpdIeParcelNumber, normalizeDpdIeNumber, parseDpdIe, parseDpdIeAbsence, PROVIDER } from './parser.js';
|
|
8
10
|
// The tracker www.dpd.ie/tracking/ shows in a frame, with the frame's device type.
|
|
9
11
|
const ORIGIN = 'https://tracking.dpd.ie';
|
|
10
12
|
const RESULT = '/Home/SearchResult?deviceType=5';
|
|
@@ -42,6 +44,27 @@ export class DpdIeTracker {
|
|
|
42
44
|
constructor(options = {}) {
|
|
43
45
|
this.options = options;
|
|
44
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* A parcel number's answer from DPD Ireland's tracker and DPD's group-wide
|
|
49
|
+
* guest service, asked together: the tracker's history when it holds more
|
|
50
|
+
* scans and is not behind the guest service's milestones, else those. Either
|
|
51
|
+
* answers when the other fails; not found needs both.
|
|
52
|
+
*/
|
|
53
|
+
async track(raw, context = {}) {
|
|
54
|
+
const number = normalizeDpdIeNumber(raw);
|
|
55
|
+
if (!isDpdIeParcelNumber(number) || !this.options.group)
|
|
56
|
+
return this.fetch(number, context);
|
|
57
|
+
const shared = { ...context, budgetMs: context.budgetMs ?? 15_000 };
|
|
58
|
+
const [ireland, group] = await Promise.allSettled([this.fetch(number, shared), this.options.group(number, shared)]);
|
|
59
|
+
if (ireland.status === 'fulfilled' && group.status === 'fulfilled') {
|
|
60
|
+
return fillPartialAnswer('dpd-ie', number, group.value, ireland.value) ?? group.value;
|
|
61
|
+
}
|
|
62
|
+
if (ireland.status === 'fulfilled')
|
|
63
|
+
return ireland.value;
|
|
64
|
+
if (group.status === 'fulfilled')
|
|
65
|
+
return group.value;
|
|
66
|
+
throw [group.reason, ireland.reason].find((error) => !(error instanceof NotFoundError)) ?? ireland.reason;
|
|
67
|
+
}
|
|
45
68
|
async fetch(raw, context = {}) {
|
|
46
69
|
const number = normalizeDpdIeNumber(raw);
|
|
47
70
|
const budgetMs = context.budgetMs ?? 15_000;
|
|
@@ -91,10 +114,14 @@ export class DpdIeTracker {
|
|
|
91
114
|
}
|
|
92
115
|
}
|
|
93
116
|
export const adapter = environment => {
|
|
94
|
-
|
|
117
|
+
// DPD's guest service answers for the whole group; Belgium's unit asks it alone.
|
|
118
|
+
const group = new DPDTracker({ country: 'BE', fetcher: environment.fetcher, userAgent: environment.userAgent });
|
|
119
|
+
const tracker = new DpdIeTracker({ fetcher: environment.fetcher, recorder: environment.recorder, userAgent: environment.userAgent,
|
|
120
|
+
group: (number, context) => group.fetch(number, '', context) });
|
|
95
121
|
return {
|
|
96
122
|
id: 'dpd-ie', recordsSteps: true, steps: ['direct', 'retry'],
|
|
97
|
-
track: (input, context) => tracker.
|
|
123
|
+
track: (input, context) => tracker.track(input.number, context),
|
|
124
|
+
// DPD's group-wide match does not make a parcel Irish: recognition reads DPD Ireland's tracker alone.
|
|
98
125
|
recognize: (number, context) => recognizeFromLookup(() => tracker.fetch(number, context), () => accepted(() => normalizeDpdIeNumber(number))),
|
|
99
126
|
};
|
|
100
127
|
};
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import type { CarrierResult } from '../../core/result/index.js';
|
|
2
2
|
export declare const PROVIDER = "DPD Ireland";
|
|
3
3
|
export declare const DPD_IE_ZONE = "Europe/Dublin";
|
|
4
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* A DPD Ireland consignment number, nine or ten digits, or a DPD parcel
|
|
6
|
+
* number, fourteen digits without the check character labels print after them.
|
|
7
|
+
*/
|
|
5
8
|
export declare function normalizeDpdIeNumber(raw: string): string;
|
|
9
|
+
/** Whether a normalized number is a DPD parcel number rather than a consignment number. */
|
|
10
|
+
export declare function isDpdIeParcelNumber(number: string): boolean;
|
|
6
11
|
/** Cloudflare's challenge page in place of the tracker. Its passive script on ordinary pages is not one. */
|
|
7
12
|
export declare function isDpdIeChallenge(html: string): boolean;
|
|
8
13
|
/**
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { load } from 'cheerio';
|
|
2
2
|
import { DateTime } from 'luxon';
|
|
3
3
|
import { normalizeTrackingNumber } from '../../core/detection/index.js';
|
|
4
|
+
import { dpdParcelNumber } from '../../core/detection/dpd.js';
|
|
4
5
|
import { ChallengeError, IndeterminateError, InvalidInputError, NoHistoryError, NotFoundError, SchemaError } from '../../core/errors/index.js';
|
|
5
6
|
import { languageStageStatus } from '../../core/status/index.js';
|
|
6
7
|
import { normalizeStatusWording } from '../../core/status/statusMap.js';
|
|
@@ -12,13 +13,15 @@ export const DPD_IE_ZONE = 'Europe/Dublin';
|
|
|
12
13
|
// The consignment numbers DPD Ireland's tracker shows have nine digits;
|
|
13
14
|
// lookups archived from it also use ten.
|
|
14
15
|
const NUMBER = /^\d{9,10}$/;
|
|
16
|
+
// The fourteen-digit parcel number DPD networks print, which the tracker also finds.
|
|
17
|
+
const PARCEL = /^\d{14}$/;
|
|
15
18
|
const HEADERS = ['Status', 'Location', 'Date & Time'];
|
|
16
19
|
// "07/9/2026 at 1:41": day, month without its zero, year, then the hour without its zero.
|
|
17
20
|
const CLOCK = /^(\d{1,2})\/(\d{1,2})\/(\d{4}) at (\d{1,2}):(\d{2})$/;
|
|
18
21
|
// DPD Ireland's own depots, in the Republic and in Northern Ireland, and its
|
|
19
22
|
// head office and website. Their clocks are Irish wall time. Another
|
|
20
23
|
// network's scan names its town and country, as "<town> - <country>".
|
|
21
|
-
const IRISH_PLACE = /^(?:Dpd [A-Za-z][A-Za-z
|
|
24
|
+
const IRISH_PLACE = /^(?:Dpd [A-Za-z][A-Za-z .&'-]{0,60}|Head Office|Web)$/i;
|
|
22
25
|
// Labels of DPD Ireland's systems rather than places.
|
|
23
26
|
const NOT_PLACES = new Set(['head office', 'web']);
|
|
24
27
|
const VAN = 'Your parcel is on a van and due for delivery';
|
|
@@ -29,12 +32,22 @@ const VAN_HEADLINE = new RegExp(`^${VAN}\\. Expected delivery time (\\d{2}):(\\d
|
|
|
29
32
|
const MAX_ROWS = 500;
|
|
30
33
|
const same = (left, right) => normalizeStatusWording(left) === normalizeStatusWording(right);
|
|
31
34
|
const MAX_EVENTS = 100;
|
|
32
|
-
/**
|
|
35
|
+
/**
|
|
36
|
+
* A DPD Ireland consignment number, nine or ten digits, or a DPD parcel
|
|
37
|
+
* number, fourteen digits without the check character labels print after them.
|
|
38
|
+
*/
|
|
33
39
|
export function normalizeDpdIeNumber(raw) {
|
|
34
40
|
const number = normalizeTrackingNumber(raw);
|
|
35
|
-
if (
|
|
36
|
-
|
|
37
|
-
|
|
41
|
+
if (NUMBER.test(number))
|
|
42
|
+
return number;
|
|
43
|
+
const parcel = dpdParcelNumber(number);
|
|
44
|
+
if (!parcel)
|
|
45
|
+
throw new InvalidInputError(PROVIDER, 'DPD Ireland tracks nine- or ten-digit consignment numbers and fourteen-digit parcel numbers');
|
|
46
|
+
return parcel;
|
|
47
|
+
}
|
|
48
|
+
/** Whether a normalized number is a DPD parcel number rather than a consignment number. */
|
|
49
|
+
export function isDpdIeParcelNumber(number) {
|
|
50
|
+
return PARCEL.test(number);
|
|
38
51
|
}
|
|
39
52
|
/** Cloudflare's challenge page in place of the tracker. Its passive script on ordinary pages is not one. */
|
|
40
53
|
export function isDpdIeChallenge(html) {
|
|
@@ -100,7 +113,9 @@ export function parseDpdIe(html, raw) {
|
|
|
100
113
|
throw new IndeterminateError(PROVIDER, 'DPD Ireland did not return a consignment');
|
|
101
114
|
}
|
|
102
115
|
const info = root.find('.tracking-status-info p').filter((_, node) => /^Consignment\b/.test(clean($(node).text(), 80)));
|
|
103
|
-
|
|
116
|
+
// A parcel number finds its consignment, which the page names instead.
|
|
117
|
+
const consignment = clean(info.find('strong').text(), 40);
|
|
118
|
+
if (info.length !== 1 || info.find('strong').length !== 1 || (isDpdIeParcelNumber(number) ? !NUMBER.test(consignment) : consignment !== number)) {
|
|
104
119
|
throw new SchemaError(PROVIDER, 'DPD Ireland returned a different consignment');
|
|
105
120
|
}
|
|
106
121
|
const lists = root.find('ul.history-list');
|
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.578.0-main.2716",
|
|
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",
|