universal-parcel-scraper 0.69.7-main.899 → 0.70.0-main.901

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/data/catalog.json CHANGED
@@ -1312,6 +1312,21 @@
1312
1312
  "tracking": {
1313
1313
  "mode": "automatic",
1314
1314
  "adapter": "colis-prive",
1315
+ "requirements": [
1316
+ {
1317
+ "field": "postcode",
1318
+ "whenTrackingNumber": "^[A-Z0-9]{12}$",
1319
+ "validator": "colisPrivePostcode",
1320
+ "label": "Delivery postcode",
1321
+ "type": "text",
1322
+ "placeholder": "75001",
1323
+ "help": "Colis Privé needs the recipient postcode with the 12-character number: 5 digits in France, 4 in Belgium, or L and 4 digits in Luxembourg.",
1324
+ "pattern": "^(?:(?:0[1-9]|[1-8][0-9]|9[0-578])[0-9]{3}|(?:(?:[Bb][Ee]?|[Ll][Uu]?)[ \\-]?)?[1-9][0-9]{3})$",
1325
+ "maxLength": 7,
1326
+ "inputMode": "text",
1327
+ "autoComplete": "postal-code"
1328
+ }
1329
+ ],
1315
1330
  "recognitionRank": 30
1316
1331
  },
1317
1332
  "canaryUrl": "https://colisprive.com/moncolis/pages/DetailColis.aspx",
@@ -1194,6 +1194,19 @@ var ye = {
1194
1194
  tracking: {
1195
1195
  mode: "automatic",
1196
1196
  adapter: "colis-prive",
1197
+ requirements: [{
1198
+ field: "postcode",
1199
+ whenTrackingNumber: "^[A-Z0-9]{12}$",
1200
+ validator: "colisPrivePostcode",
1201
+ label: "Delivery postcode",
1202
+ type: "text",
1203
+ placeholder: "75001",
1204
+ help: "Colis Privé needs the recipient postcode with the 12-character number: 5 digits in France, 4 in Belgium, or L and 4 digits in Luxembourg.",
1205
+ pattern: "^(?:(?:0[1-9]|[1-8][0-9]|9[0-578])[0-9]{3}|(?:(?:[Bb][Ee]?|[Ll][Uu]?)[ \\-]?)?[1-9][0-9]{3})$",
1206
+ maxLength: 7,
1207
+ inputMode: "text",
1208
+ autoComplete: "postal-code"
1209
+ }],
1197
1210
  recognitionRank: 30
1198
1211
  },
1199
1212
  canaryUrl: "https://colisprive.com/moncolis/pages/DetailColis.aspx",
@@ -11947,9 +11960,15 @@ var Yc = /^(?=.{3,12}$)(?=.*\d)[A-Z0-9]+(?:[ -][A-Z0-9]+)*$/;
11947
11960
  function Xc(e) {
11948
11961
  return e.trim().toUpperCase().replace(/\s+/g, " ");
11949
11962
  }
11963
+ function Zc(e) {
11964
+ let t = e.toUpperCase().replace(/\s+/g, "");
11965
+ if (/^(?:0[1-9]|[1-8]\d|9[0-5]|97|98)\d{3}$/.test(t)) return t;
11966
+ let n = /^(?:(BE?|LU?)-?)?([1-9]\d{3})$/.exec(t);
11967
+ return n ? `${n[1]?.charAt(0) ?? ""}${n[2]}` : null;
11968
+ }
11950
11969
  //#endregion
11951
11970
  //#region core/catalog/inputs.ts
11952
- function Zc(e, t, n, r) {
11971
+ function Qc(e, t, n, r) {
11953
11972
  let i = {
11954
11973
  trackingUrl: n.trim() || null,
11955
11974
  postcode: r.trim() || null
@@ -11960,7 +11979,7 @@ function Zc(e, t, n, r) {
11960
11979
  field: "postcode",
11961
11980
  validator: "internationalPostcode"
11962
11981
  });
11963
- for (let [e, t] of Object.entries(i)) if (t !== null && !o.has(e)) throw TypeError(e === "trackingUrl" ? "A tracking URL is not used for this carrier or tracking number" : "A delivery postcode is not used for this carrier");
11982
+ for (let [e, t] of Object.entries(i)) if (t !== null && !o.has(e)) throw TypeError(e === "trackingUrl" ? "A tracking URL is not used for this carrier or tracking number" : "A delivery postcode is not used for this carrier or tracking number");
11964
11983
  for (let [n, r] of o) {
11965
11984
  let a = i[n];
11966
11985
  if (!a) {
@@ -11994,17 +12013,23 @@ function Zc(e, t, n, r) {
11994
12013
  i[n] = e.replace(/\s+/g, "");
11995
12014
  break;
11996
12015
  }
12016
+ case "colisPrivePostcode": {
12017
+ let t = Zc(a);
12018
+ if (!t) throw TypeError(`${x(e).displayName} requires a French, Belgian or Luxembourg delivery postcode`);
12019
+ i[n] = t;
12020
+ break;
12021
+ }
11997
12022
  default: r.validator;
11998
12023
  }
11999
12024
  }
12000
12025
  return i;
12001
12026
  }
12002
- function Qc(e) {
12027
+ function $c(e) {
12003
12028
  let t = Xc(e);
12004
12029
  if (!Yc.test(t)) throw TypeError("A valid delivery postcode is required");
12005
12030
  return t;
12006
12031
  }
12007
- var $c = { carriers: [
12032
+ var el = { carriers: [
12008
12033
  {
12009
12034
  carrier: "dhl",
12010
12035
  direct: "Yes",
@@ -15970,80 +15995,80 @@ var $c = { carriers: [
15970
15995
  }
15971
15996
  }]
15972
15997
  }
15973
- ] }, el = [
15998
+ ] }, tl = [
15974
15999
  "Ship24",
15975
16000
  "ParcelsApp",
15976
16001
  "17TRACK",
15977
16002
  "Postal Ninja",
15978
16003
  "UPU"
15979
- ], tl = /* @__PURE__ */ new Set([
16004
+ ], nl = /* @__PURE__ */ new Set([
15980
16005
  "no_history",
15981
16006
  "summary_only",
15982
16007
  "undated",
15983
16008
  "sign_in",
15984
16009
  "postcode_prompt"
15985
- ]), nl = $c.carriers, rl = new Map(nl.map((e) => [e.carrier, e]));
15986
- function il(e) {
16010
+ ]), rl = el.carriers, il = new Map(rl.map((e) => [e.carrier, e]));
16011
+ function al(e) {
15987
16012
  return typeof e == "number" || e === "history" || typeof e == "object" && !!e;
15988
16013
  }
15989
- function al(e, t) {
15990
- let n = t.filter((t) => t.results[e] !== void 0), r = n.some((t) => !t.stale && (tl.has(t.results[e]) || t.results[e] === "wrong_carrier") && Object.entries(t.results).some(([t, n]) => t !== e && t !== "UPU" && il(n)));
15991
- return n.some((t) => il(t.results[e])) ? n.some((t) => typeof t.results[e] == "object") || r ? "partial" : "full" : n.some((t) => ["wrong_carrier", "refused"].includes(t.results[e])) ? "excluded" : n.some((t) => !t.stale && tl.has(t.results[e])) ? "empty" : "unknown";
16014
+ function ol(e, t) {
16015
+ let n = t.filter((t) => t.results[e] !== void 0), r = n.some((t) => !t.stale && (nl.has(t.results[e]) || t.results[e] === "wrong_carrier") && Object.entries(t.results).some(([t, n]) => t !== e && t !== "UPU" && al(n)));
16016
+ return n.some((t) => al(t.results[e])) ? n.some((t) => typeof t.results[e] == "object") || r ? "partial" : "full" : n.some((t) => ["wrong_carrier", "refused"].includes(t.results[e])) ? "excluded" : n.some((t) => !t.stale && nl.has(t.results[e])) ? "empty" : "unknown";
15992
16017
  }
15993
- function ol(e) {
15994
- let t = e ? rl.get(e) : void 0;
15995
- return t ? Object.fromEntries(el.map((e) => [e, al(e, t.references)])) : null;
16018
+ function sl(e) {
16019
+ let t = e ? il.get(e) : void 0;
16020
+ return t ? Object.fromEntries(tl.map((e) => [e, ol(e, t.references)])) : null;
15996
16021
  }
15997
16022
  //#endregion
15998
16023
  //#region providers/plan.ts
15999
- var sl = [
16024
+ var cl = [
16000
16025
  "ParcelsApp",
16001
16026
  "Ship24",
16002
16027
  "17TRACK",
16003
16028
  "UPU"
16004
- ], cl = /* @__PURE__ */ new Set(["17TRACK", "Postal Ninja"]);
16005
- function ll(e) {
16029
+ ], ll = /* @__PURE__ */ new Set(["17TRACK", "Postal Ninja"]);
16030
+ function ul(e) {
16006
16031
  return e === "ParcelsApp" ? 45e3 : e === "UPU" ? 8e3 : 3e4;
16007
16032
  }
16008
- function ul(e) {
16033
+ function dl(e) {
16009
16034
  return e && /^\d{10}$/.test(y(e)) ? "Ship24" : e && /^[CL][A-Z]\d{9}CN$/.test(y(e)) && ft(e) ? "17TRACK" : void 0;
16010
16035
  }
16011
- var dl = {
16036
+ var fl = {
16012
16037
  full: 0,
16013
16038
  partial: 1,
16014
16039
  unknown: 2,
16015
16040
  empty: 3,
16016
16041
  excluded: 4
16017
16042
  };
16018
- function fl(e, t) {
16019
- let n = dl[e?.[t] ?? "unknown"];
16020
- return t === "UPU" ? dl.unknown : t === "Postal Ninja" ? Math.max(n, dl.empty) : n;
16043
+ function pl(e, t) {
16044
+ let n = fl[e?.[t] ?? "unknown"];
16045
+ return t === "UPU" ? fl.unknown : t === "Postal Ninja" ? Math.max(n, fl.empty) : n;
16021
16046
  }
16022
- var pl = (e) => e === "UPU" ? 2 : +(e === "Postal Ninja");
16023
- function ml(e, t) {
16047
+ var ml = (e) => e === "UPU" ? 2 : +(e === "Postal Ninja");
16048
+ function hl(e, t) {
16024
16049
  let n = (e) => e !== "UPU" && t?.[e] === "excluded";
16025
- return (e.some((e) => e !== "UPU" && !n(e)) ? e.filter((e) => !n(e)) : [...e]).sort((n, r) => pl(n) - pl(r) || fl(t, n) - fl(t, r) || Number(cl.has(n)) - Number(cl.has(r)) || e.indexOf(n) - e.indexOf(r));
16050
+ return (e.some((e) => e !== "UPU" && !n(e)) ? e.filter((e) => !n(e)) : [...e]).sort((n, r) => ml(n) - ml(r) || pl(t, n) - pl(t, r) || Number(ll.has(n)) - Number(ll.has(r)) || e.indexOf(n) - e.indexOf(r));
16026
16051
  }
16027
- function hl(e = {}) {
16052
+ function gl(e = {}) {
16028
16053
  let { trackingNumber: t, enablePostalNinja: n = !1 } = e, r = (n ? [
16029
16054
  "ParcelsApp",
16030
16055
  "Ship24",
16031
16056
  "17TRACK",
16032
16057
  "Postal Ninja",
16033
16058
  "UPU"
16034
- ] : [...sl]).filter((e) => e !== "UPU" || t === void 0 || ft(t)), i = (e.carriers ?? []).find((e) => ol(e)) ?? null, a = ol(i), o = ml(r, a), s = ul(t);
16059
+ ] : [...cl]).filter((e) => e !== "UPU" || t === void 0 || ft(t)), i = (e.carriers ?? []).find((e) => sl(e)) ?? null, a = sl(i), o = hl(r, a), s = dl(t);
16035
16060
  return {
16036
16061
  sources: s ? [s, ...o.filter((e) => e !== s)] : o,
16037
16062
  carrier: i,
16038
16063
  tier: (e) => a?.[e] ?? "unknown",
16039
- rank: (e) => fl(a, e)
16064
+ rank: (e) => pl(a, e)
16040
16065
  };
16041
16066
  }
16042
- function gl(e = !1, t) {
16043
- return hl({
16067
+ function _l(e = !1, t) {
16068
+ return gl({
16044
16069
  trackingNumber: t,
16045
16070
  enablePostalNinja: e
16046
16071
  }).sources;
16047
16072
  }
16048
16073
  //#endregion
16049
- export { Ce as AMAZON_NUMBER_PATTERN, Pe as AUTOMATIC_CARRIER_IDS, cl as BROWSER_SOURCES, f as BudgetExceededError, S as CARRIERS, ye as CARRIER_CATALOG, b as CARRIER_DEFINITIONS, be as CARRIER_IDS, Fe as CARRIER_NAMES, t as CarrierError, a as ChallengeError, Lo as EXPLICIT_OFFSET_PATTERN, r as IndeterminateError, l as InputRequiredError, u as InvalidInputError, vc as MAX_RECOGNITIONS, s as MaintenanceError, i as NoHistoryError, n as NotFoundError, o as RateLimitedError, Ue as SELECTABLE_CARRIERS, Se as STAGES, c as SchemaError, Mt as TRACKING_CANDIDATE_PATTERNS, Ie as TRACKING_LINK_RULES, d as TransportError, sl as UNIVERSAL_SOURCES, m as UpstreamHttpError, ee as UpstreamNetworkError, Me as activeRequirements, Ee as amazonMarketplace, De as amazonOrdersUrl, Oe as amazonShippingOrigin, ke as amazonShippingUrl, ds as calendarDay, as as canadaProvinceTimeZone, je as carrierAdapter, x as carrierDefinition, g as carrierErrorKind, He as carrierInfo, Le as carrierLinkRules, We as carrierRequirements, Ae as carrierTimezone, Dt as checksumRejections, lc as classifyStage, ic as classifyWording, $o as countryCode, es as countryTimeZone, Rc as deliveryHandoff, Ot as detectCarrier, C as detectCarrierMatch, ls as epochMillisTime, us as epochSecondsTime, te as errorTypeOf, xs as eventPoint, zo as explicitOffsetTime, mc as failureHint, pc as failureKind, ge as formatTrackingNumber, Lc as hasDirectHandoffAdapter, uc as inferStage, we as isAmazonTrackingNumber, he as isPlanzerSharedTrackingNumber, Xe as isValidCorreosSpainCheckLetter, v as isValidDhlExpressWaybill, $e as isValidDpdParcelNumber, ct as isValidGlsParcelNumber, lt as isValidHermesParcelNumber, me as isValidMondialRelayBarcode, fe as isValidPocztaPolskaBarcode, ft as isValidS10TrackingNumber, pe as isValidSscc, oe as isValidTntConsignmentNumber, bt as isValidUspsPackageBarcode, Vo as isoTime, It as keywordNumberInText, rc as languageStageStatus, qe as localizedCarrierUrl, Re as matchesDomain, Uo as mislabeledLocalTime, Wo as mislabeledWallTime, Zc as normalizeCarrierInputs, Ss as normalizeCarrierResult, Qc as normalizeDeliveryPostcode, y as normalizeTrackingNumber, ml as orderUniversalSources, Ht as parseTrackingInput, ul as priorityUniversalSource, xc as recognitionCandidates, yc as recognitionNumberShape, Cc as recognizeAll, Nt as recognizedNumberInText, cs as regionHasTown, Ne as requiredRequirements, Ge as requirementSatisfied, Te as requiresAmazonAccount, fc as resolveResult, cc as resultHasUpdate, sc as resultStage, _ as retryAfterMsOf, qo as settleGuessedClocks, Dc as settleRecognition, Go as sharedClockZone, dc as stageSource, mt as supportsSwissPostHandoff, ts as timeZoneCountry, os as townKey, nc as trackingLanguageStage, Be as trackingLink, ze as trackingNumberForLink, Ke as tracksAutomatically, hl as universalPlan, ll as universalSourceBudget, gl as universalSources, rs as usStateTimeZone, yt as uspsPackageIdentifier, At as validTrackingNumber, Jc as validateDachserTrackingUrl, qc as validatePlanzerSharedUrl, ac as wordingStage, Bo as zonedTime };
16074
+ export { Ce as AMAZON_NUMBER_PATTERN, Pe as AUTOMATIC_CARRIER_IDS, ll as BROWSER_SOURCES, f as BudgetExceededError, S as CARRIERS, ye as CARRIER_CATALOG, b as CARRIER_DEFINITIONS, be as CARRIER_IDS, Fe as CARRIER_NAMES, t as CarrierError, a as ChallengeError, Lo as EXPLICIT_OFFSET_PATTERN, r as IndeterminateError, l as InputRequiredError, u as InvalidInputError, vc as MAX_RECOGNITIONS, s as MaintenanceError, i as NoHistoryError, n as NotFoundError, o as RateLimitedError, Ue as SELECTABLE_CARRIERS, Se as STAGES, c as SchemaError, Mt as TRACKING_CANDIDATE_PATTERNS, Ie as TRACKING_LINK_RULES, d as TransportError, cl as UNIVERSAL_SOURCES, m as UpstreamHttpError, ee as UpstreamNetworkError, Me as activeRequirements, Ee as amazonMarketplace, De as amazonOrdersUrl, Oe as amazonShippingOrigin, ke as amazonShippingUrl, ds as calendarDay, as as canadaProvinceTimeZone, je as carrierAdapter, x as carrierDefinition, g as carrierErrorKind, He as carrierInfo, Le as carrierLinkRules, We as carrierRequirements, Ae as carrierTimezone, Dt as checksumRejections, lc as classifyStage, ic as classifyWording, $o as countryCode, es as countryTimeZone, Rc as deliveryHandoff, Ot as detectCarrier, C as detectCarrierMatch, ls as epochMillisTime, us as epochSecondsTime, te as errorTypeOf, xs as eventPoint, zo as explicitOffsetTime, mc as failureHint, pc as failureKind, ge as formatTrackingNumber, Lc as hasDirectHandoffAdapter, uc as inferStage, we as isAmazonTrackingNumber, he as isPlanzerSharedTrackingNumber, Xe as isValidCorreosSpainCheckLetter, v as isValidDhlExpressWaybill, $e as isValidDpdParcelNumber, ct as isValidGlsParcelNumber, lt as isValidHermesParcelNumber, me as isValidMondialRelayBarcode, fe as isValidPocztaPolskaBarcode, ft as isValidS10TrackingNumber, pe as isValidSscc, oe as isValidTntConsignmentNumber, bt as isValidUspsPackageBarcode, Vo as isoTime, It as keywordNumberInText, rc as languageStageStatus, qe as localizedCarrierUrl, Re as matchesDomain, Uo as mislabeledLocalTime, Wo as mislabeledWallTime, Qc as normalizeCarrierInputs, Ss as normalizeCarrierResult, $c as normalizeDeliveryPostcode, y as normalizeTrackingNumber, hl as orderUniversalSources, Ht as parseTrackingInput, dl as priorityUniversalSource, xc as recognitionCandidates, yc as recognitionNumberShape, Cc as recognizeAll, Nt as recognizedNumberInText, cs as regionHasTown, Ne as requiredRequirements, Ge as requirementSatisfied, Te as requiresAmazonAccount, fc as resolveResult, cc as resultHasUpdate, sc as resultStage, _ as retryAfterMsOf, qo as settleGuessedClocks, Dc as settleRecognition, Go as sharedClockZone, dc as stageSource, mt as supportsSwissPostHandoff, ts as timeZoneCountry, os as townKey, nc as trackingLanguageStage, Be as trackingLink, ze as trackingNumberForLink, Ke as tracksAutomatically, gl as universalPlan, ul as universalSourceBudget, _l as universalSources, rs as usStateTimeZone, yt as uspsPackageIdentifier, At as validTrackingNumber, Jc as validateDachserTrackingUrl, qc as validatePlanzerSharedUrl, ac as wordingStage, Bo as zonedTime };
@@ -7,10 +7,11 @@
7
7
  * before any text is read, so nothing identifying a person can reach the result.
8
8
  *
9
9
  * The lookup credential is the 12-character shipment number followed by the
10
- * recipient's postcode: five digits in France, or B (Belgium) or L (Luxembourg)
11
- * and four digits. The postcode is what makes the page reachable,
12
- * so it is part of the tracking credential: it is never logged, put in an issue
13
- * or written into a fixture.
10
+ * recipient's postcode: five digits in France, four in Belgium, bare or after
11
+ * B, or L and four digits in Luxembourg. The postcode comes at the end of the
12
+ * number, as in Colis Privé's own links, or as a separate input. It is what
13
+ * makes the page reachable, so it is part of the tracking credential: it is
14
+ * never logged, put in an issue or written into a fixture.
14
15
  */
15
16
  import { type AdapterFactory, type TrackingContext } from '../../core/adapter/index.js';
16
17
  import { NotFoundError } from '../../core/errors/index.js';
@@ -23,8 +24,13 @@ export { classifyStatus } from './status.js';
23
24
  export declare class ColisPriveTrackingError extends NotFoundError {
24
25
  constructor();
25
26
  }
26
- export declare function normalizeColisPriveCredential(raw: string): string;
27
- export declare function colisPriveTrackingUrl(rawCredential: string): string;
27
+ /**
28
+ * The value the page is opened with: the shipment and the postcode, written
29
+ * after it or given apart. A bare shipment is a missing credential, as the
30
+ * page never opens without its postcode.
31
+ */
32
+ export declare function normalizeColisPriveCredential(raw: string, rawPostcode?: string): string;
33
+ export declare function colisPriveTrackingUrl(rawCredential: string, rawPostcode?: string): string;
28
34
  export declare function parseColisPriveTrackingHtml(html: string, rawCredential: string): CarrierResult;
29
35
  export interface ColisPriveTrackerOptions {
30
36
  timeoutMs?: number;
@@ -36,6 +42,6 @@ export declare class ColisPriveTracker {
36
42
  #private;
37
43
  readonly timeoutMs: number;
38
44
  constructor(options?: ColisPriveTrackerOptions);
39
- fetch(rawCredential: string, context?: TrackingContext): Promise<CarrierResult>;
45
+ fetch(rawCredential: string, rawPostcode?: string, context?: TrackingContext): Promise<CarrierResult>;
40
46
  }
41
47
  export declare const adapter: AdapterFactory;
@@ -7,14 +7,16 @@
7
7
  * before any text is read, so nothing identifying a person can reach the result.
8
8
  *
9
9
  * The lookup credential is the 12-character shipment number followed by the
10
- * recipient's postcode: five digits in France, or B (Belgium) or L (Luxembourg)
11
- * and four digits. The postcode is what makes the page reachable,
12
- * so it is part of the tracking credential: it is never logged, put in an issue
13
- * or written into a fixture.
10
+ * recipient's postcode: five digits in France, four in Belgium, bare or after
11
+ * B, or L and four digits in Luxembourg. The postcode comes at the end of the
12
+ * number, as in Colis Privé's own links, or as a separate input. It is what
13
+ * makes the page reachable, so it is part of the tracking credential: it is
14
+ * never logged, put in an issue or written into a fixture.
14
15
  */
15
16
  import { load } from 'cheerio';
16
- import { accepted, lookupBudget, recognizeFromLookup } from '../../core/adapter/index.js';
17
- import { InvalidInputError, NotFoundError, SchemaError } from '../../core/errors/index.js';
17
+ import { lookupBudget, recognizeFromLookup } from '../../core/adapter/index.js';
18
+ import { colisPrivePostcode } from '../../core/catalog/postcode.js';
19
+ import { InputRequiredError, InvalidInputError, NotFoundError, SchemaError } from '../../core/errors/index.js';
18
20
  import { UpstreamHttpError, clean, decodeText, fetchBounded, userAgentOf } from '../../core/transport/index.js';
19
21
  import { calendarDay } from '../../core/time/index.js';
20
22
  import { classifyStatus, comparableText } from './status.js';
@@ -25,6 +27,14 @@ const TIMEZONE = 'Europe/Paris';
25
27
  const MAX_RESPONSE_BYTES = 500_000;
26
28
  const DEFAULT_TIMEOUT_MS = 15_000;
27
29
  const MAX_EVENTS_TO_RETURN = 100;
30
+ const CREDENTIAL_MESSAGE = 'Colis Privé tracking requires the 12-character shipment number and the recipient postcode '
31
+ + '(5 digits in France, 4 in Belgium, or L and 4 digits in Luxembourg)';
32
+ const SHIPMENT = /^[A-Z0-9]{12}$/;
33
+ /** The shipment with its postcode at the end, as Colis Privé's links carry it. */
34
+ function combined(number) {
35
+ const postcode = number.slice(12);
36
+ return SHIPMENT.test(number.slice(0, 12)) && colisPrivePostcode(postcode) === postcode;
37
+ }
28
38
  function shipmentPart(credential) {
29
39
  return credential.slice(0, 12);
30
40
  }
@@ -60,12 +70,24 @@ export class ColisPriveTrackingError extends NotFoundError {
60
70
  this.name = 'ColisPriveTrackingError';
61
71
  }
62
72
  }
63
- export function normalizeColisPriveCredential(raw) {
64
- const credential = raw.trim().toLocaleUpperCase('en-US');
65
- if (!/^[A-Z0-9]{12}(?:(?:0[1-9]|[1-8]\d|9[0-5]|97|98)\d{3}|[BL][1-9]\d{3})$/.test(credential)) {
66
- throw new InvalidInputError(PROVIDER, 'Colis Privé tracking requires the 12-character shipment number followed by the recipient postcode (5 digits, or B or L and 4 digits)');
73
+ /**
74
+ * The value the page is opened with: the shipment and the postcode, written
75
+ * after it or given apart. A bare shipment is a missing credential, as the
76
+ * page never opens without its postcode.
77
+ */
78
+ export function normalizeColisPriveCredential(raw, rawPostcode = '') {
79
+ const number = raw.trim().toLocaleUpperCase('en-US');
80
+ if (rawPostcode.trim()) {
81
+ const postcode = colisPrivePostcode(rawPostcode);
82
+ if (!SHIPMENT.test(number) || !postcode)
83
+ throw new InvalidInputError(PROVIDER, CREDENTIAL_MESSAGE);
84
+ return `${number}${postcode}`;
67
85
  }
68
- return credential;
86
+ if (SHIPMENT.test(number))
87
+ throw new InputRequiredError(PROVIDER, 'the recipient postcode', CREDENTIAL_MESSAGE);
88
+ if (!combined(number))
89
+ throw new InvalidInputError(PROVIDER, CREDENTIAL_MESSAGE);
90
+ return number;
69
91
  }
70
92
  /** The day a row announces for a confirmed appointment or a relay drop-off: "… le DD/MM/YYYY". */
71
93
  function plannedDay(description) {
@@ -75,8 +97,8 @@ function plannedDay(description) {
75
97
  return match ? calendarDay(Number(match[3]), Number(match[2]), Number(match[1])) : null;
76
98
  }
77
99
  const SETTLED = new Set(['delivered', 'returned', 'exception', 'failed_attempt', 'ready_for_pickup']);
78
- export function colisPriveTrackingUrl(rawCredential) {
79
- const credential = normalizeColisPriveCredential(rawCredential);
100
+ export function colisPriveTrackingUrl(rawCredential, rawPostcode = '') {
101
+ const credential = normalizeColisPriveCredential(rawCredential, rawPostcode);
80
102
  const url = new URL(TRACKING_ENDPOINT);
81
103
  url.searchParams.set('numColis', credential);
82
104
  url.searchParams.set('lang', 'fr');
@@ -164,8 +186,8 @@ export class ColisPriveTracker {
164
186
  this.#fetcher = options.fetcher;
165
187
  this.#userAgent = userAgentOf(options.userAgent);
166
188
  }
167
- async fetch(rawCredential, context = {}) {
168
- const credential = normalizeColisPriveCredential(rawCredential);
189
+ async fetch(rawCredential, rawPostcode = '', context = {}) {
190
+ const credential = normalizeColisPriveCredential(rawCredential, rawPostcode);
169
191
  const budget = lookupBudget(context, this.timeoutMs);
170
192
  const { response, bytes } = await fetchBounded(colisPriveTrackingUrl(credential), {
171
193
  signal: budget.signal,
@@ -197,9 +219,9 @@ export const adapter = (environment) => {
197
219
  return {
198
220
  id: 'colis-prive',
199
221
  steps: ['direct'],
200
- // The postcode is already part of the stored number for this carrier.
201
- track: (input, context) => tracker.fetch(input.number, context),
202
- // A bare 12-character number needs the postcode appended: it reads as unknown.
203
- recognize: (number, context) => recognizeFromLookup(() => tracker.fetch(number, context), () => accepted(() => normalizeColisPriveCredential(number))),
222
+ // The postcode ends the number, or a bare number carries it apart.
223
+ track: (input, context) => tracker.fetch(input.number, input.postcode ?? '', context),
224
+ // Recognition gets no postcode, so a bare 12-character number reads as unknown.
225
+ recognize: (number, context) => recognizeFromLookup(() => tracker.fetch(number, '', context), () => combined(number.trim().toLocaleUpperCase('en-US'))),
204
226
  };
205
227
  };
@@ -19,6 +19,21 @@
19
19
  "tracking": {
20
20
  "mode": "automatic",
21
21
  "adapter": "colis-prive",
22
+ "requirements": [
23
+ {
24
+ "field": "postcode",
25
+ "whenTrackingNumber": "^[A-Z0-9]{12}$",
26
+ "validator": "colisPrivePostcode",
27
+ "label": "Delivery postcode",
28
+ "type": "text",
29
+ "placeholder": "75001",
30
+ "help": "Colis Privé needs the recipient postcode with the 12-character number: 5 digits in France, 4 in Belgium, or L and 4 digits in Luxembourg.",
31
+ "pattern": "^(?:(?:0[1-9]|[1-8][0-9]|9[0-578])[0-9]{3}|(?:(?:[Bb][Ee]?|[Ll][Uu]?)[ \\-]?)?[1-9][0-9]{3})$",
32
+ "maxLength": 7,
33
+ "inputMode": "text",
34
+ "autoComplete": "postal-code"
35
+ }
36
+ ],
22
37
  "steps": [
23
38
  "direct"
24
39
  ],
@@ -3,7 +3,7 @@ import { activeRequirements, carrierDefinition } from './index.js';
3
3
  import { isValidMondialRelayBarcode } from '../detection/index.js';
4
4
  import { validateDachserTrackingUrl } from './urls.js';
5
5
  import { validatePlanzerSharedUrl } from './urls.js';
6
- import { DELIVERY_POSTCODE, deliveryPostcodeText } from './postcode.js';
6
+ import { DELIVERY_POSTCODE, colisPrivePostcode, deliveryPostcodeText } from './postcode.js';
7
7
  export { AUTOMATIC_CARRIER_IDS, CARRIER_NAMES, activeRequirements, carrierAdapter, carrierDefinition, carrierTimezone, requiredRequirements, } from './index.js';
8
8
  export { isValidS10TrackingNumber, supportsSwissPostHandoff, } from '../detection/index.js';
9
9
  export function normalizeCarrierInputs(carrierId, trackingNumber, trackingUrl, postcode) {
@@ -29,7 +29,7 @@ export function normalizeCarrierInputs(carrierId, trackingNumber, trackingUrl, p
29
29
  if (field === 'trackingUrl') {
30
30
  throw new TypeError('A tracking URL is not used for this carrier or tracking number');
31
31
  }
32
- throw new TypeError('A delivery postcode is not used for this carrier');
32
+ throw new TypeError('A delivery postcode is not used for this carrier or tracking number');
33
33
  }
34
34
  }
35
35
  for (const [field, requirement] of requirements) {
@@ -93,6 +93,14 @@ export function normalizeCarrierInputs(carrierId, trackingNumber, trackingUrl, p
93
93
  supplied[field] = rawPostcode.replace(/\s+/g, '');
94
94
  break;
95
95
  }
96
+ case 'colisPrivePostcode': {
97
+ const normalized = colisPrivePostcode(value);
98
+ if (!normalized) {
99
+ throw new TypeError(`${carrierDefinition(carrierId).displayName} requires a French, Belgian or Luxembourg delivery postcode`);
100
+ }
101
+ supplied[field] = normalized;
102
+ break;
103
+ }
96
104
  default:
97
105
  requirement.validator;
98
106
  }
@@ -2,3 +2,10 @@
2
2
  export declare const DELIVERY_POSTCODE: RegExp;
3
3
  /** A typed postcode as it is checked and sent: trimmed, in capitals, with single spaces. */
4
4
  export declare function deliveryPostcodeText(value: string): string;
5
+ /**
6
+ * A Colis Privé recipient postcode as its page takes it after the shipment
7
+ * number: France's five digits, or Belgium's or Luxembourg's four, bare or
8
+ * after B or L. A written country prefix (B-1000, LU 1234) becomes that
9
+ * letter. Null for anything else.
10
+ */
11
+ export declare function colisPrivePostcode(value: string): string | null;
@@ -4,3 +4,18 @@ export const DELIVERY_POSTCODE = /^(?=.{3,12}$)(?=.*\d)[A-Z0-9]+(?:[ -][A-Z0-9]+
4
4
  export function deliveryPostcodeText(value) {
5
5
  return value.trim().toUpperCase().replace(/\s+/g, ' ');
6
6
  }
7
+ /**
8
+ * A Colis Privé recipient postcode as its page takes it after the shipment
9
+ * number: France's five digits, or Belgium's or Luxembourg's four, bare or
10
+ * after B or L. A written country prefix (B-1000, LU 1234) becomes that
11
+ * letter. Null for anything else.
12
+ */
13
+ export function colisPrivePostcode(value) {
14
+ const text = value.toUpperCase().replace(/\s+/g, '');
15
+ if (/^(?:0[1-9]|[1-8]\d|9[0-5]|97|98)\d{3}$/.test(text))
16
+ return text;
17
+ const match = /^(?:(BE?|LU?)-?)?([1-9]\d{3})$/.exec(text);
18
+ if (!match)
19
+ return null;
20
+ return `${match[1]?.charAt(0) ?? ''}${match[2]}`;
21
+ }
@@ -11,7 +11,7 @@ import type { CarrierId } from '../../generated/catalog.js';
11
11
  import type { DetectionConfidence } from '../detection/types.js';
12
12
  export type CarrierTrackingMode = 'automatic' | 'link-only';
13
13
  export type CarrierInputField = 'trackingUrl' | 'postcode';
14
- export type CarrierInputValidator = 'planzerSharedUrl' | 'dachserCapabilityUrl' | 'swissPostcode' | 'francePostcode' | 'germanyPostcode' | 'swissOrFrancePostcode' | 'internationalPostcode' | 'paackPostcode';
14
+ export type CarrierInputValidator = 'planzerSharedUrl' | 'dachserCapabilityUrl' | 'swissPostcode' | 'francePostcode' | 'germanyPostcode' | 'swissOrFrancePostcode' | 'internationalPostcode' | 'paackPostcode' | 'colisPrivePostcode';
15
15
  /** What the "add a parcel" forms render for a carrier that needs extra input. */
16
16
  export interface CarrierInputRequirement {
17
17
  field: CarrierInputField;
@@ -1312,6 +1312,21 @@
1312
1312
  "tracking": {
1313
1313
  "mode": "automatic",
1314
1314
  "adapter": "colis-prive",
1315
+ "requirements": [
1316
+ {
1317
+ "field": "postcode",
1318
+ "whenTrackingNumber": "^[A-Z0-9]{12}$",
1319
+ "validator": "colisPrivePostcode",
1320
+ "label": "Delivery postcode",
1321
+ "type": "text",
1322
+ "placeholder": "75001",
1323
+ "help": "Colis Privé needs the recipient postcode with the 12-character number: 5 digits in France, 4 in Belgium, or L and 4 digits in Luxembourg.",
1324
+ "pattern": "^(?:(?:0[1-9]|[1-8][0-9]|9[0-578])[0-9]{3}|(?:(?:[Bb][Ee]?|[Ll][Uu]?)[ \\-]?)?[1-9][0-9]{3})$",
1325
+ "maxLength": 7,
1326
+ "inputMode": "text",
1327
+ "autoComplete": "postal-code"
1328
+ }
1329
+ ],
1315
1330
  "recognitionRank": 30
1316
1331
  },
1317
1332
  "canaryUrl": "https://colisprive.com/moncolis/pages/DetailColis.aspx",
@@ -840,6 +840,19 @@ export declare const CARRIER_CATALOG: {
840
840
  readonly tracking: {
841
841
  readonly mode: "automatic";
842
842
  readonly adapter: "colis-prive";
843
+ readonly requirements: readonly [{
844
+ readonly field: "postcode";
845
+ readonly whenTrackingNumber: "^[A-Z0-9]{12}$";
846
+ readonly validator: "colisPrivePostcode";
847
+ readonly label: "Delivery postcode";
848
+ readonly type: "text";
849
+ readonly placeholder: "75001";
850
+ readonly help: "Colis Privé needs the recipient postcode with the 12-character number: 5 digits in France, 4 in Belgium, or L and 4 digits in Luxembourg.";
851
+ readonly pattern: "^(?:(?:0[1-9]|[1-8][0-9]|9[0-578])[0-9]{3}|(?:(?:[Bb][Ee]?|[Ll][Uu]?)[ \\-]?)?[1-9][0-9]{3})$";
852
+ readonly maxLength: 7;
853
+ readonly inputMode: "text";
854
+ readonly autoComplete: "postal-code";
855
+ }];
843
856
  readonly recognitionRank: 30;
844
857
  };
845
858
  readonly canaryUrl: "https://colisprive.com/moncolis/pages/DetailColis.aspx";
@@ -1313,6 +1313,21 @@ export const CARRIER_CATALOG = {
1313
1313
  "tracking": {
1314
1314
  "mode": "automatic",
1315
1315
  "adapter": "colis-prive",
1316
+ "requirements": [
1317
+ {
1318
+ "field": "postcode",
1319
+ "whenTrackingNumber": "^[A-Z0-9]{12}$",
1320
+ "validator": "colisPrivePostcode",
1321
+ "label": "Delivery postcode",
1322
+ "type": "text",
1323
+ "placeholder": "75001",
1324
+ "help": "Colis Privé needs the recipient postcode with the 12-character number: 5 digits in France, 4 in Belgium, or L and 4 digits in Luxembourg.",
1325
+ "pattern": "^(?:(?:0[1-9]|[1-8][0-9]|9[0-578])[0-9]{3}|(?:(?:[Bb][Ee]?|[Ll][Uu]?)[ \\-]?)?[1-9][0-9]{3})$",
1326
+ "maxLength": 7,
1327
+ "inputMode": "text",
1328
+ "autoComplete": "postal-code"
1329
+ }
1330
+ ],
1316
1331
  "recognitionRank": 30
1317
1332
  },
1318
1333
  "canaryUrl": "https://colisprive.com/moncolis/pages/DetailColis.aspx",
@@ -2,7 +2,7 @@
2
2
  "openapi": "3.1.0",
3
3
  "info": {
4
4
  "title": "Universal Parcel Scraper",
5
- "version": "0.69.7"
5
+ "version": "0.70.0"
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.69.7-main.899",
3
+ "version": "0.70.0-main.901",
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",