universal-parcel-scraper 0.89.0-main.975 → 0.91.0-main.981

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.
Files changed (48) hide show
  1. package/README.md +2 -2
  2. package/data/catalog.json +47 -7
  3. package/data/coverage-summary.json +1 -1
  4. package/data/detection-golden.json +32 -0
  5. package/dist/browser/scraper.js +39 -9
  6. package/dist/carriers/allegro-one/adapter.d.ts +15 -0
  7. package/dist/carriers/allegro-one/adapter.js +63 -0
  8. package/dist/carriers/allegro-one/carrier.json +56 -8
  9. package/dist/carriers/allegro-one/numbers.json +7 -7
  10. package/dist/carriers/allegro-one/parser.d.ts +15 -0
  11. package/dist/carriers/allegro-one/parser.js +91 -0
  12. package/dist/carriers/allegro-one/status.d.ts +12 -0
  13. package/dist/carriers/allegro-one/status.js +49 -0
  14. package/dist/carriers/allegro-one/statuses.json +26 -1
  15. package/dist/carriers/ceska-posta/adapter.d.ts +15 -0
  16. package/dist/carriers/ceska-posta/adapter.js +65 -0
  17. package/dist/carriers/ceska-posta/carrier.json +54 -7
  18. package/dist/carriers/ceska-posta/numbers.json +40 -0
  19. package/dist/carriers/ceska-posta/parser.d.ts +15 -0
  20. package/dist/carriers/ceska-posta/parser.js +137 -0
  21. package/dist/carriers/ceska-posta/status.d.ts +13 -0
  22. package/dist/carriers/ceska-posta/status.js +76 -0
  23. package/dist/carriers/ceska-posta/statuses.json +34 -1
  24. package/dist/carriers/express-one/adapter.d.ts +14 -0
  25. package/dist/carriers/express-one/adapter.js +49 -0
  26. package/dist/carriers/express-one/carrier.json +40 -7
  27. package/dist/carriers/express-one/parser.d.ts +12 -0
  28. package/dist/carriers/express-one/parser.js +178 -0
  29. package/dist/carriers/express-one/status.d.ts +13 -0
  30. package/dist/carriers/express-one/status.js +48 -0
  31. package/dist/carriers/express-one/statuses.json +123 -1
  32. package/dist/carriers/posta-slovenije/adapter.d.ts +14 -0
  33. package/dist/carriers/posta-slovenije/adapter.js +70 -0
  34. package/dist/carriers/posta-slovenije/carrier.json +51 -8
  35. package/dist/carriers/posta-slovenije/numbers.json +8 -8
  36. package/dist/carriers/posta-slovenije/parser.d.ts +17 -0
  37. package/dist/carriers/posta-slovenije/parser.js +131 -0
  38. package/dist/carriers/posta-slovenije/status.d.ts +13 -0
  39. package/dist/carriers/posta-slovenije/status.js +56 -0
  40. package/dist/carriers/posta-slovenije/statuses.json +13 -1
  41. package/dist/core/catalog/statusMaps.js +11 -6
  42. package/dist/data/catalog.json +47 -7
  43. package/dist/generated/catalog.d.ts +33 -7
  44. package/dist/generated/catalog.js +49 -9
  45. package/dist/generated/recognition.js +4 -0
  46. package/dist/generated/registry.js +12 -4
  47. package/dist/server/openapi.json +1 -1
  48. 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: 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 **145 dedicated adapters** and **5 universal fallbacks**
16
+ **3,500+ carriers** through **149 dedicated adapters** and **5 universal fallbacks**
17
17
 
18
18
  <sub>208 catalog entries · 66 countries represented</sub>
19
19
  <!-- /GENERATED:summary -->
@@ -155,7 +155,7 @@ createTracker({ providers: ['ParcelsApp', 'Ship24', '17TRACK', 'Postal Ninja', '
155
155
  <!-- GENERATED:stages -->
156
156
  <img src="docs/assets/stages.svg" alt="DHL: Die Sendung wurde in das Zustellfahrzeug geladen.; Mondial Relay: En cours de livraison; Correios Brazil: Objeto saiu para entrega ao destinatário; Correos Express: EN REPARTO; Yamato Transport: 配達中; La Poste / Colissimo: DISTOU. All are filed under out_for_delivery." width="760">
157
157
 
158
- The carrier folders record 4,002 such statuses, each filed under one stage.
158
+ The carrier folders record 4,083 such statuses, each filed under one stage.
159
159
  <!-- /GENERATED:stages -->
160
160
 
161
161
  Wording nobody recorded yet goes through a classifier that reads seven European languages.
package/data/catalog.json CHANGED
@@ -4805,10 +4805,32 @@
4805
4805
  "timezone": "Europe/Warsaw",
4806
4806
  "tracking": {
4807
4807
  "mode": "automatic",
4808
- "adapter": "universal"
4808
+ "adapter": "allegro-one",
4809
+ "recognitionRank": 770
4809
4810
  },
4810
- "canaryUrl": "https://allegro.pl/",
4811
- "linkRules": [],
4811
+ "canaryUrl": "https://allegro.pl/allegrodelivery/sledzenie-paczki",
4812
+ "trackingUrlTemplate": "https://allegro.pl/allegrodelivery/sledzenie-paczki?numer={trackingNumber}",
4813
+ "trackingSearchUrl": "https://allegro.pl/allegrodelivery/sledzenie-paczki",
4814
+ "linkRules": [
4815
+ {
4816
+ "domains": [
4817
+ "allegro.pl"
4818
+ ],
4819
+ "pathPattern": "^/allegrodelivery/sledzenie-paczki/?$",
4820
+ "params": [
4821
+ "numer"
4822
+ ]
4823
+ },
4824
+ {
4825
+ "domains": [
4826
+ "allegro.pl"
4827
+ ],
4828
+ "pathPattern": "^/kampania/one/kurier/sledzenie-paczki/?$",
4829
+ "params": [
4830
+ "numer"
4831
+ ]
4832
+ }
4833
+ ],
4812
4834
  "detectionRules": [
4813
4835
  {
4814
4836
  "pattern": "^A[0D][0-9A-Z]{8}$",
@@ -5069,7 +5091,9 @@
5069
5091
  "timezone": "Europe/Prague",
5070
5092
  "tracking": {
5071
5093
  "mode": "automatic",
5072
- "adapter": "universal"
5094
+ "adapter": "ceska-posta",
5095
+ "recognitionRank": 760,
5096
+ "localClocks": true
5073
5097
  },
5074
5098
  "canaryUrl": "https://www.postaonline.cz/trackandtrace",
5075
5099
  "trackingUrlTemplate": "https://www.postaonline.cz/trackandtrace/-/zasilka/{trackingNumber}",
@@ -5086,6 +5110,12 @@
5086
5110
  {
5087
5111
  "pattern": "^[BD][A-Z]\\d{10}[A-Z]$",
5088
5112
  "confidence": "high"
5113
+ },
5114
+ {
5115
+ "pattern": "^[A-Z]{2}\\d{9}CZ$",
5116
+ "confidence": "low",
5117
+ "checksum": "s10",
5118
+ "preferred": true
5089
5119
  }
5090
5120
  ]
5091
5121
  },
@@ -6155,7 +6185,9 @@
6155
6185
  "timezone": "Europe/Budapest",
6156
6186
  "tracking": {
6157
6187
  "mode": "automatic",
6158
- "adapter": "universal"
6188
+ "adapter": "express-one",
6189
+ "recognitionRank": 780,
6190
+ "localClocks": true
6159
6191
  },
6160
6192
  "canaryUrl": "https://tracking.expressone.hu/",
6161
6193
  "trackingUrlTemplate": "https://tracking.expressone.hu/?plc_number={trackingNumber}",
@@ -7558,7 +7590,8 @@
7558
7590
  "timezone": "Europe/Ljubljana",
7559
7591
  "tracking": {
7560
7592
  "mode": "automatic",
7561
- "adapter": "universal"
7593
+ "adapter": "posta-slovenije",
7594
+ "recognitionRank": 790
7562
7595
  },
7563
7596
  "canaryUrl": "https://moja.posta.si/tracking",
7564
7597
  "trackingUrlTemplate": "https://moja.posta.si/tracking?trackingNumber={trackingNumber}",
@@ -7574,7 +7607,14 @@
7574
7607
  ]
7575
7608
  }
7576
7609
  ],
7577
- "detectionRules": []
7610
+ "detectionRules": [
7611
+ {
7612
+ "pattern": "^[A-Z]{2}\\d{9}SI$",
7613
+ "confidence": "low",
7614
+ "checksum": "s10",
7615
+ "preferred": true
7616
+ }
7617
+ ]
7578
7618
  },
7579
7619
  "posta-srbije": {
7580
7620
  "displayName": "Pošta Srbije",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "catalog": 208,
3
- "activeAdapters": 145,
3
+ "activeAdapters": 149,
4
4
  "countries": 66,
5
5
  "referenceCarriers": 100,
6
6
  "comparison": {
@@ -25888,6 +25888,7 @@
25888
25888
  "intl-post"
25889
25889
  ],
25890
25890
  "asked": [
25891
+ "posta-slovenije",
25891
25892
  "chronopost"
25892
25893
  ]
25893
25894
  },
@@ -25899,6 +25900,7 @@
25899
25900
  "intl-post"
25900
25901
  ],
25901
25902
  "asked": [
25903
+ "posta-slovenije",
25902
25904
  "chronopost"
25903
25905
  ]
25904
25906
  },
@@ -25910,6 +25912,7 @@
25910
25912
  "intl-post"
25911
25913
  ],
25912
25914
  "asked": [
25915
+ "posta-slovenije",
25913
25916
  "chronopost"
25914
25917
  ]
25915
25918
  },
@@ -26031,6 +26034,7 @@
26031
26034
  "intl-post"
26032
26035
  ],
26033
26036
  "asked": [
26037
+ "posta-slovenije",
26034
26038
  "chronopost"
26035
26039
  ]
26036
26040
  },
@@ -26851,6 +26855,18 @@
26851
26855
  "intelcom"
26852
26856
  ]
26853
26857
  },
26858
+ {
26859
+ "input": "CS011354097CZ",
26860
+ "carrier": "intl-post",
26861
+ "confidence": "high",
26862
+ "candidates": [
26863
+ "intl-post"
26864
+ ],
26865
+ "asked": [
26866
+ "ceska-posta",
26867
+ "chronopost"
26868
+ ]
26869
+ },
26854
26870
  {
26855
26871
  "input": "CT001362607SK",
26856
26872
  "carrier": "intl-post",
@@ -27068,6 +27084,7 @@
27068
27084
  "intl-post"
27069
27085
  ],
27070
27086
  "asked": [
27087
+ "posta-slovenije",
27071
27088
  "chronopost"
27072
27089
  ]
27073
27090
  },
@@ -30477,6 +30494,7 @@
30477
30494
  "intl-post"
30478
30495
  ],
30479
30496
  "asked": [
30497
+ "posta-slovenije",
30480
30498
  "chronopost"
30481
30499
  ]
30482
30500
  },
@@ -30668,6 +30686,7 @@
30668
30686
  "intl-post"
30669
30687
  ],
30670
30688
  "asked": [
30689
+ "posta-slovenije",
30671
30690
  "chronopost"
30672
30691
  ]
30673
30692
  },
@@ -34133,6 +34152,7 @@
34133
34152
  "intl-post"
34134
34153
  ],
34135
34154
  "asked": [
34155
+ "posta-slovenije",
34136
34156
  "chronopost"
34137
34157
  ]
34138
34158
  },
@@ -34167,6 +34187,18 @@
34167
34187
  "correos-chile"
34168
34188
  ]
34169
34189
  },
34190
+ {
34191
+ "input": "RR562174447CZ",
34192
+ "carrier": "intl-post",
34193
+ "confidence": "high",
34194
+ "candidates": [
34195
+ "intl-post"
34196
+ ],
34197
+ "asked": [
34198
+ "ceska-posta",
34199
+ "chronopost"
34200
+ ]
34201
+ },
34170
34202
  {
34171
34203
  "input": "RR568370453CL",
34172
34204
  "carrier": "correos-chile",
@@ -3947,10 +3947,21 @@ var be = {
3947
3947
  timezone: "Europe/Warsaw",
3948
3948
  tracking: {
3949
3949
  mode: "automatic",
3950
- adapter: "universal"
3950
+ adapter: "allegro-one",
3951
+ recognitionRank: 770
3951
3952
  },
3952
- canaryUrl: "https://allegro.pl/",
3953
- linkRules: [],
3953
+ canaryUrl: "https://allegro.pl/allegrodelivery/sledzenie-paczki",
3954
+ trackingUrlTemplate: "https://allegro.pl/allegrodelivery/sledzenie-paczki?numer={trackingNumber}",
3955
+ trackingSearchUrl: "https://allegro.pl/allegrodelivery/sledzenie-paczki",
3956
+ linkRules: [{
3957
+ domains: ["allegro.pl"],
3958
+ pathPattern: "^/allegrodelivery/sledzenie-paczki/?$",
3959
+ params: ["numer"]
3960
+ }, {
3961
+ domains: ["allegro.pl"],
3962
+ pathPattern: "^/kampania/one/kurier/sledzenie-paczki/?$",
3963
+ params: ["numer"]
3964
+ }],
3954
3965
  detectionRules: [{
3955
3966
  pattern: "^A[0D][0-9A-Z]{8}$",
3956
3967
  confidence: "high"
@@ -4145,7 +4156,9 @@ var be = {
4145
4156
  timezone: "Europe/Prague",
4146
4157
  tracking: {
4147
4158
  mode: "automatic",
4148
- adapter: "universal"
4159
+ adapter: "ceska-posta",
4160
+ recognitionRank: 760,
4161
+ localClocks: !0
4149
4162
  },
4150
4163
  canaryUrl: "https://www.postaonline.cz/trackandtrace",
4151
4164
  trackingUrlTemplate: "https://www.postaonline.cz/trackandtrace/-/zasilka/{trackingNumber}",
@@ -4157,6 +4170,11 @@ var be = {
4157
4170
  detectionRules: [{
4158
4171
  pattern: "^[BD][A-Z]\\d{10}[A-Z]$",
4159
4172
  confidence: "high"
4173
+ }, {
4174
+ pattern: "^[A-Z]{2}\\d{9}CZ$",
4175
+ confidence: "low",
4176
+ checksum: "s10",
4177
+ preferred: !0
4160
4178
  }]
4161
4179
  },
4162
4180
  "chit-chats": {
@@ -4950,7 +4968,9 @@ var be = {
4950
4968
  timezone: "Europe/Budapest",
4951
4969
  tracking: {
4952
4970
  mode: "automatic",
4953
- adapter: "universal"
4971
+ adapter: "express-one",
4972
+ recognitionRank: 780,
4973
+ localClocks: !0
4954
4974
  },
4955
4975
  canaryUrl: "https://tracking.expressone.hu/",
4956
4976
  trackingUrlTemplate: "https://tracking.expressone.hu/?plc_number={trackingNumber}",
@@ -6005,7 +6025,8 @@ var be = {
6005
6025
  timezone: "Europe/Ljubljana",
6006
6026
  tracking: {
6007
6027
  mode: "automatic",
6008
- adapter: "universal"
6028
+ adapter: "posta-slovenije",
6029
+ recognitionRank: 790
6009
6030
  },
6010
6031
  canaryUrl: "https://moja.posta.si/tracking",
6011
6032
  trackingUrlTemplate: "https://moja.posta.si/tracking?trackingNumber={trackingNumber}",
@@ -6015,7 +6036,12 @@ var be = {
6015
6036
  pathPattern: "^/tracking/?$",
6016
6037
  params: ["trackingNumber"]
6017
6038
  }],
6018
- detectionRules: []
6039
+ detectionRules: [{
6040
+ pattern: "^[A-Z]{2}\\d{9}SI$",
6041
+ confidence: "low",
6042
+ checksum: "s10",
6043
+ preferred: !0
6044
+ }]
6019
6045
  },
6020
6046
  "posta-srbije": {
6021
6047
  displayName: "Pošta Srbije",
@@ -7039,7 +7065,7 @@ var be = {
7039
7065
  cargus: [],
7040
7066
  cdek: [],
7041
7067
  cdl: ["cdl-1", "cdl-2"],
7042
- "ceska-posta": ["ceska-posta-1"],
7068
+ "ceska-posta": ["ceska-posta-1", "ceska-posta-s10"],
7043
7069
  "chit-chats": ["chit-chats-1"],
7044
7070
  cne: ["cne-1"],
7045
7071
  "correos-de-mexico": [],
@@ -7133,7 +7159,7 @@ var be = {
7133
7159
  paquetexpress: [],
7134
7160
  passport: ["passport-1"],
7135
7161
  "post-luxembourg": [],
7136
- "posta-slovenije": [],
7162
+ "posta-slovenije": ["posta-slovenije-s10"],
7137
7163
  "posta-srbije": [],
7138
7164
  ppl: [],
7139
7165
  ptt: ["ptt-1", "ptt-s10"],
@@ -11856,6 +11882,7 @@ function yc(e) {
11856
11882
  var bc = {
11857
11883
  "acs-courier": 570,
11858
11884
  aliexpress: 140,
11885
+ "allegro-one": 770,
11859
11886
  "aras-kargo": 670,
11860
11887
  "australia-post": 60,
11861
11888
  "austrian-post": 390,
@@ -11865,6 +11892,7 @@ var bc = {
11865
11892
  "canada-post": 150,
11866
11893
  canpar: 340,
11867
11894
  cargus: 730,
11895
+ "ceska-posta": 760,
11868
11896
  chronopost: 450,
11869
11897
  ciblex: 200,
11870
11898
  "colis-prive": 300,
@@ -11884,6 +11912,7 @@ var bc = {
11884
11912
  econt: 560,
11885
11913
  elta: 550,
11886
11914
  emile: 40,
11915
+ "express-one": 780,
11887
11916
  "fan-courier": 510,
11888
11917
  "four-px": 130,
11889
11918
  foxpost: 520,
@@ -11910,6 +11939,7 @@ var bc = {
11910
11939
  ontrac: 210,
11911
11940
  "orlen-paczka": 490,
11912
11941
  "poczta-polska": 310,
11942
+ "posta-slovenije": 790,
11913
11943
  "poste-italiane": 460,
11914
11944
  posti: 270,
11915
11945
  postlogistics: 260,
@@ -0,0 +1,15 @@
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
+ export declare const ALLEGRO_ONE_ENDPOINT = "https://edge.allegro.pl/one/tracking";
5
+ export declare function allegroOneTrackingUrl(raw: string): string;
6
+ export declare class AllegroOneTracker {
7
+ private readonly options;
8
+ constructor(options?: {
9
+ fetcher?: typeof fetch;
10
+ recorder?: StepRecorder;
11
+ userAgent?: string;
12
+ });
13
+ fetch(raw: string, context?: TrackingContext): Promise<CarrierResult>;
14
+ }
15
+ export declare const adapter: AdapterFactory;
@@ -0,0 +1,63 @@
1
+ import { accepted, recognizeFromLookup } from '../../core/adapter/index.js';
2
+ import { ChallengeError, IndeterminateError, SchemaError } 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 { ALLEGRO_ONE_MAX_BYTES, normalizeAllegroOneNumber, parseAllegroOne, PROVIDER } from './parser.js';
8
+ // The service Allegro's tracking page asks through its edge gateway.
9
+ export const ALLEGRO_ONE_ENDPOINT = 'https://edge.allegro.pl/one/tracking';
10
+ // The gateway answers only the media type Allegro's own pages ask for.
11
+ const MEDIA_TYPE = 'application/vnd.allegro.internal.v1+json';
12
+ const REDIRECTS = [301, 302, 303, 307, 308];
13
+ // DataDome's interstitial, which guards Allegro's pages and could come instead of the reply.
14
+ const CHALLENGE = /captcha-delivery\.com|datadome/i;
15
+ export function allegroOneTrackingUrl(raw) {
16
+ return `${ALLEGRO_ONE_ENDPOINT}?packageNo=${encodeURIComponent(normalizeAllegroOneNumber(raw))}`;
17
+ }
18
+ export class AllegroOneTracker {
19
+ options;
20
+ constructor(options = {}) {
21
+ this.options = options;
22
+ }
23
+ async fetch(raw, context = {}) {
24
+ const url = allegroOneTrackingUrl(raw);
25
+ const read = async (signal, timeoutMs) => {
26
+ // The scans are worded in the language asked for: Polish for `pl-PL` or
27
+ // `*`, English for `en-US`. The status map reads English.
28
+ const { response, bytes } = await fetchBounded(url, { signal, headers: {
29
+ Accept: MEDIA_TYPE, 'Accept-Language': 'en-US', 'User-Agent': userAgentOf(this.options.userAgent),
30
+ } }, { provider: PROVIDER, timeoutMs: Math.max(1, Math.floor(timeoutMs)), maxBytes: ALLEGRO_ONE_MAX_BYTES, redirect: 'manual',
31
+ allowHttpStatuses: [...REDIRECTS, 400, 404, 406, 410], fetcher: this.options.fetcher });
32
+ // A refused request, a retired route ("Feature unavailable") or a move says nothing about the parcel.
33
+ if (response.status !== 200)
34
+ throw new IndeterminateError(PROVIDER, 'Allegro One tracking service is unavailable');
35
+ const text = decodeText(bytes);
36
+ let reply;
37
+ try {
38
+ reply = JSON.parse(text);
39
+ }
40
+ catch (error) {
41
+ if (CHALLENGE.test(text))
42
+ throw new ChallengeError(PROVIDER, 'Allegro One answered with a bot check', { cause: error });
43
+ throw new SchemaError(PROVIDER, 'Allegro One returned a reply that is not JSON', { cause: error });
44
+ }
45
+ return parseAllegroOne(reply, raw);
46
+ };
47
+ // A request that fails to reach Allegro, or hangs, gets one more try: the
48
+ // first gets half of the budget, so the retry fits in the rest.
49
+ return runSteps({ carrier: 'allegro-one', budgetMs: context.budgetMs ?? 15_000, signal: context.signal,
50
+ recorder: this.options.recorder ?? NOOP_RECORDER }, [
51
+ { id: 'direct', run: ({ signal, remainingMs }) => read(signal, firstAttemptMs(remainingMs)) },
52
+ networkRetryStep(({ signal, remainingMs }) => read(signal, remainingMs)),
53
+ ]);
54
+ }
55
+ }
56
+ export const adapter = (environment) => {
57
+ const tracker = new AllegroOneTracker({ fetcher: environment.fetcher, recorder: environment.recorder, userAgent: environment.userAgent });
58
+ return {
59
+ id: 'allegro-one', recordsSteps: true, steps: ['direct', 'retry'],
60
+ track: (input, context) => tracker.fetch(input.number, context),
61
+ recognize: (number, context) => recognizeFromLookup(() => tracker.fetch(number, context), () => accepted(() => normalizeAllegroOneNumber(number))),
62
+ };
63
+ };
@@ -19,18 +19,66 @@
19
19
  "timezone": "Europe/Warsaw",
20
20
  "tracking": {
21
21
  "mode": "automatic",
22
- "adapter": "universal",
23
- "steps": []
22
+ "adapter": "allegro-one",
23
+ "steps": [
24
+ "direct",
25
+ "retry"
26
+ ],
27
+ "recognition": {
28
+ "rank": 770
29
+ }
24
30
  },
25
- "capabilities": [],
31
+ "capabilities": [
32
+ "history",
33
+ "delivered_at"
34
+ ],
26
35
  "portal": {
27
- "canaryUrl": "https://allegro.pl/",
28
- "shows": [],
29
- "scraped": [],
36
+ "url": "https://allegro.pl/allegrodelivery/sledzenie-paczki?numer={trackingNumber}",
37
+ "searchUrl": "https://allegro.pl/allegrodelivery/sledzenie-paczki",
38
+ "canaryUrl": "https://allegro.pl/allegrodelivery/sledzenie-paczki",
39
+ "shows": [
40
+ "status",
41
+ "history",
42
+ "delivered_at"
43
+ ],
44
+ "scraped": [
45
+ "status",
46
+ "history",
47
+ "delivered_at",
48
+ "offset_timestamps"
49
+ ],
30
50
  "discarded": [],
31
- "unavailable": []
51
+ "unavailable": [
52
+ "location",
53
+ "eta",
54
+ "pickup_point",
55
+ "weight",
56
+ "service_name",
57
+ "sender_name",
58
+ "recipient_name",
59
+ "delivery_partner"
60
+ ]
32
61
  },
33
- "links": [],
62
+ "links": [
63
+ {
64
+ "domains": [
65
+ "allegro.pl"
66
+ ],
67
+ "pathPattern": "^/allegrodelivery/sledzenie-paczki/?$",
68
+ "params": [
69
+ "numer"
70
+ ]
71
+ },
72
+ {
73
+ "domains": [
74
+ "allegro.pl"
75
+ ],
76
+ "pathPattern": "^/kampania/one/kurier/sledzenie-paczki/?$",
77
+ "params": [
78
+ "numer"
79
+ ]
80
+ }
81
+ ],
34
82
  "detection": [
35
83
  {
36
84
  "id": "allegro-one-1",
@@ -12,7 +12,7 @@
12
12
  "access": "full_page"
13
13
  },
14
14
  "context": {
15
- "assessment": "candidate"
15
+ "assessment": "confirmed"
16
16
  },
17
17
  "expect": {
18
18
  "carrier": "allegro-one",
@@ -31,7 +31,7 @@
31
31
  "access": "full_page"
32
32
  },
33
33
  "context": {
34
- "assessment": "candidate"
34
+ "assessment": "confirmed"
35
35
  },
36
36
  "expect": {
37
37
  "carrier": "allegro-one",
@@ -50,7 +50,7 @@
50
50
  "access": "full_page"
51
51
  },
52
52
  "context": {
53
- "assessment": "candidate"
53
+ "assessment": "confirmed"
54
54
  },
55
55
  "expect": {
56
56
  "carrier": "allegro-one",
@@ -69,7 +69,7 @@
69
69
  "access": "full_page"
70
70
  },
71
71
  "context": {
72
- "assessment": "candidate"
72
+ "assessment": "confirmed"
73
73
  },
74
74
  "expect": {
75
75
  "carrier": "allegro-one",
@@ -88,7 +88,7 @@
88
88
  "access": "full_page"
89
89
  },
90
90
  "context": {
91
- "assessment": "candidate"
91
+ "assessment": "confirmed"
92
92
  },
93
93
  "expect": {
94
94
  "carrier": "allegro-one",
@@ -107,7 +107,7 @@
107
107
  "access": "full_page"
108
108
  },
109
109
  "context": {
110
- "assessment": "candidate"
110
+ "assessment": "confirmed"
111
111
  },
112
112
  "expect": {
113
113
  "carrier": "allegro-one",
@@ -126,7 +126,7 @@
126
126
  "access": "full_page"
127
127
  },
128
128
  "context": {
129
- "assessment": "candidate"
129
+ "assessment": "confirmed"
130
130
  },
131
131
  "expect": {
132
132
  "carrier": "allegro-one",
@@ -0,0 +1,15 @@
1
+ import type { CarrierResult } from '../../core/result/index.js';
2
+ export declare const PROVIDER = "Allegro One";
3
+ export declare const ALLEGRO_ONE_MAX_BYTES: number;
4
+ /** An Allegro One number; anything else is a number Allegro One does not issue. */
5
+ export declare function normalizeAllegroOneNumber(raw: string): string;
6
+ /**
7
+ * The reply of `GET /one/tracking?packageNo={number}` on Allegro's edge
8
+ * gateway: `{ "status": [...] }`, each scan a UTC instant and a wording. It
9
+ * does not echo the number: the scans are those of the number in the request.
10
+ * An empty or null list is what the tracker shows as "not found". Every A0
11
+ * number seen has scans from its label on, so an empty list is not found for
12
+ * them; every real AD number seen had an empty list, so for AD and the older
13
+ * ALE form it stays inconclusive.
14
+ */
15
+ export declare function parseAllegroOne(payload: unknown, raw: string): CarrierResult;
@@ -0,0 +1,91 @@
1
+ import { normalizeTrackingNumber } from '../../core/detection/index.js';
2
+ import { InvalidInputError, NoHistoryError, NotFoundError, SchemaError } from '../../core/errors/index.js';
3
+ import { classifyWording, languageStageStatus } from '../../core/status/index.js';
4
+ import { explicitOffsetTime } from '../../core/time/index.js';
5
+ import { clean } from '../../core/transport/index.js';
6
+ import { isRecord } from '../../core/types.js';
7
+ import { allegroOneStage } from './status.js';
8
+ export const PROVIDER = 'Allegro One';
9
+ // A parcel's reply is a list of short scans; a few kilobytes.
10
+ export const ALLEGRO_ONE_MAX_BYTES = 256 * 1024;
11
+ // Allegro One's ten-character numbers start A0, Allegro Delivery's start AD,
12
+ // and the older form the tracker still answers is ALE and twelve digits.
13
+ const NUMBER = /^(?:A[0D][0-9A-Z]{8}|ALE\d{12})$/;
14
+ const MAX_ROWS = 300;
15
+ const MAX_EVENTS = 100;
16
+ /** An Allegro One number; anything else is a number Allegro One does not issue. */
17
+ export function normalizeAllegroOneNumber(raw) {
18
+ const number = raw.length <= 64 ? normalizeTrackingNumber(raw) : '';
19
+ if (!NUMBER.test(number)) {
20
+ throw new InvalidInputError(PROVIDER, 'Allegro One numbers are A0 or AD and eight letters and digits, or ALE and twelve digits');
21
+ }
22
+ return number;
23
+ }
24
+ function projectScan(row, index) {
25
+ if (!isRecord(row) || typeof row.eventTimestamp !== 'string' || typeof row.description !== 'string') {
26
+ throw new SchemaError(PROVIDER, 'Allegro One returned an invalid scan');
27
+ }
28
+ const time = explicitOffsetTime(row.eventTimestamp);
29
+ if (!time)
30
+ throw new SchemaError(PROVIDER, 'Allegro One returned a scan without an offset time');
31
+ const description = clean(row.description, 300);
32
+ if (!description)
33
+ throw new SchemaError(PROVIDER, 'Allegro One returned a scan without wording');
34
+ const stage = allegroOneStage(description);
35
+ return {
36
+ event: { time: time.iso, description, ...(stage ? { stage, stage_source: 'carrier_map' } : {}) },
37
+ at: time.timestamp,
38
+ index,
39
+ };
40
+ }
41
+ /**
42
+ * The reply of `GET /one/tracking?packageNo={number}` on Allegro's edge
43
+ * gateway: `{ "status": [...] }`, each scan a UTC instant and a wording. It
44
+ * does not echo the number: the scans are those of the number in the request.
45
+ * An empty or null list is what the tracker shows as "not found". Every A0
46
+ * number seen has scans from its label on, so an empty list is not found for
47
+ * them; every real AD number seen had an empty list, so for AD and the older
48
+ * ALE form it stays inconclusive.
49
+ */
50
+ export function parseAllegroOne(payload, raw) {
51
+ const number = normalizeAllegroOneNumber(raw);
52
+ if (!isRecord(payload) || !('status' in payload) || (payload.status !== null && !Array.isArray(payload.status))) {
53
+ throw new SchemaError(PROVIDER, 'Allegro One returned an unexpected reply');
54
+ }
55
+ const rows = (payload.status ?? []);
56
+ if (rows.length > MAX_ROWS)
57
+ throw new SchemaError(PROVIDER, 'Allegro One returned an oversized history');
58
+ if (!rows.length) {
59
+ if (number.startsWith('A0'))
60
+ throw new NotFoundError(PROVIDER);
61
+ throw new NoHistoryError(PROVIDER, 'Allegro One has no scans for this number');
62
+ }
63
+ const seen = new Set();
64
+ const scans = [];
65
+ rows.forEach((row, index) => {
66
+ const scan = projectScan(row, index);
67
+ const key = `${scan.event.time}\n${scan.event.description}`;
68
+ if (seen.has(key))
69
+ return;
70
+ seen.add(key);
71
+ scans.push(scan);
72
+ });
73
+ // The reply lists scans newest first and the tracker sorts them so too.
74
+ // Instants order them; equal ones keep the reply's order.
75
+ scans.sort((left, right) => right.at - left.at || left.index - right.index);
76
+ const newest = scans[0].event;
77
+ const current = newest.stage
78
+ ? { stage: newest.stage, source: 'carrier_map' }
79
+ : classifyWording(newest.description, 'in_transit');
80
+ const stage = current.source !== 'none' ? current.stage : undefined;
81
+ return {
82
+ status: stage ? languageStageStatus(stage) : 'unknown',
83
+ ...(stage ? { current_stage: stage, current_stage_source: current.source } : {}),
84
+ last_status_text: newest.description,
85
+ last_update: newest.time,
86
+ expected_delivery: null,
87
+ ...(stage === 'delivered' ? { delivered_at: newest.time } : {}),
88
+ events: scans.slice(0, MAX_EVENTS).map((scan) => scan.event),
89
+ ...(scans.length > MAX_EVENTS ? { history_truncated: true } : {}),
90
+ };
91
+ }