@curless/hotel-storefront 0.30.3 → 0.30.5

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,6 +1,6 @@
1
1
  import { randomBytes, randomUUID } from 'node:crypto';
2
- import { DEFAULT_PAGE, MAX_PAGE, REQUEST_KINDS, REQUEST_NOTE_MAX, guestTooLong, partyNeeds, } from '../backend.js';
3
- import { addDays, arrivalInstant, atHotelTime, nights as nightsBetween, stayProblem, todayAt, } from '../dates.js';
2
+ import { DEFAULT_PAGE, MAX_PAGE, REQUEST_KINDS, REQUEST_NOTE_MAX, guestTooLong, partyNeedsOf, } from '../backend.js';
3
+ import { addDays, arrivalInstant, atHotelTime, nights as nightsBetween, stayProblem, stayProblemOf, todayAt, } from '../dates.js';
4
4
  import { byWords, splitByStay } from '../demo-hotels.js';
5
5
  import { FACILITIES, hasFacility } from '../facilities.js';
6
6
  import { endOf, startProblem } from '../hourly.js';
@@ -583,6 +583,7 @@ export const createHuazhuHotels = (opts) => {
583
583
  ok: false,
584
584
  kind: 'too-far',
585
585
  why: `rooms here are on sale at most ${sellDays} days ahead`,
586
+ args: { days: sellDays },
586
587
  },
587
588
  }))
588
589
  .filter((x) => !(x.roomType && x.roomType.status === 0));
@@ -799,6 +800,12 @@ export const createHuazhuHotels = (opts) => {
799
800
  ...(maxChildren > 0 && maxChildren < 99 ? { maxChildren } : {}),
800
801
  };
801
802
  };
803
+ /** A hotel's "nothing to book", as HotelDetail carries it. */
804
+ const noRoomsOf = (w) => ({
805
+ noRooms: w.why,
806
+ noRoomsCode: w.code,
807
+ ...(w.args ? { noRoomsArgs: w.args } : {}),
808
+ });
802
809
  /** The most a room here sleeps, from rates refused for the party ("sleeps 2"). */
803
810
  const largestOf = (fails) => Math.max(...fails.map((f) => Number(/\d+/.exec(f.why)?.[0] ?? 0)));
804
811
  /**
@@ -811,49 +818,84 @@ export const createHuazhuHotels = (opts) => {
811
818
  */
812
819
  const hourlyNoRoomsWhy = (priced, guests) => {
813
820
  if (priced.length === 0)
814
- return 'This hotel sells no rooms by the hour today.';
821
+ return { why: 'This hotel sells no rooms by the hour today.', code: 'hourly-none' };
815
822
  const fails = priced.map((p) => p.res).filter((r) => !r.ok);
816
823
  if (fails.length > 0 && fails.every((f) => f.kind === 'party'))
817
- return partyNeeds(guests, largestOf(fails));
824
+ return partyNeedsOf(guests, largestOf(fails));
818
825
  if (fails.some((f) => f.kind === 'prepaid'))
819
- return "This hotel's rooms by the hour are paid in advance, and this connector books rates paid at the hotel.";
826
+ return {
827
+ why: "This hotel's rooms by the hour are paid in advance, and this connector books rates paid at the hotel.",
828
+ code: 'prepaid-only',
829
+ };
820
830
  const said = (re) => fails
821
831
  .map((f) => (f.kind === 'closed' ? re.exec(f.why)?.[1] : undefined))
822
832
  .filter((t) => !!t)
823
833
  .sort();
824
834
  const opens = said(/on sale today from (\d\d:\d\d)/)[0];
825
835
  if (opens)
826
- return `Rooms by the hour here go on sale today from ${opens}.`;
836
+ return {
837
+ why: `Rooms by the hour here go on sale today from ${opens}.`,
838
+ code: 'hourly-not-yet',
839
+ args: { opensAt: opens },
840
+ };
827
841
  const last = said(/the last start today was (\d\d:\d\d)/).pop();
828
842
  if (last)
829
- return `No room by the hour is left here today: the last start today was ${last}.`;
830
- return 'No room by the hour is left here today.';
843
+ return {
844
+ why: `No room by the hour is left here today: the last start today was ${last}.`,
845
+ code: 'hourly-over',
846
+ args: { lastStart: last },
847
+ };
848
+ return { why: 'No room by the hour is left here today.', code: 'sold-out' };
831
849
  };
832
850
  /** Why nothing here can be sold for these nights - the most useful reason first. */
833
851
  const noRoomsWhy = (priced, stay, rooms) => {
834
852
  if (priced.length === 0)
835
- return 'This hotel has no rooms on sale through this connector.';
853
+ return {
854
+ why: 'This hotel has no rooms on sale through this connector.',
855
+ code: 'none-on-sale',
856
+ };
836
857
  const fails = priced.map((p) => p.res).filter((r) => !r.ok);
837
858
  const kinds = new Set(fails.map((f) => f.kind));
838
859
  const only = (k) => kinds.size === 1 && kinds.has(k);
839
860
  if (only('party'))
840
- return partyNeeds(stay.guests, largestOf(fails));
861
+ return partyNeedsOf(stay.guests, largestOf(fails));
841
862
  if (only('prepaid'))
842
- return 'Every rate here for those nights is paid in advance, and this connector books rates paid at the hotel.';
863
+ return {
864
+ why: 'Every rate here for those nights is paid in advance, and this connector books rates paid at the hotel.',
865
+ code: 'prepaid-only',
866
+ };
843
867
  if (only('sold-out'))
844
868
  return rooms > 1
845
- ? `No room type here has ${rooms} left for those nights.`
846
- : 'Every room type here is taken for those nights.';
869
+ ? {
870
+ why: `No room type here has ${rooms} left for those nights.`,
871
+ code: 'sold-out',
872
+ args: { rooms },
873
+ }
874
+ : { why: 'Every room type here is taken for those nights.', code: 'sold-out' };
847
875
  if (only('too-many-rooms'))
848
- return `No rate here books ${rooms} rooms at once; ask for fewer.`;
876
+ return {
877
+ why: `No rate here books ${rooms} rooms at once; ask for fewer.`,
878
+ code: 'too-many-rooms',
879
+ args: { rooms },
880
+ };
849
881
  if (only('too-far'))
850
- return `Not on sale yet: ${fails[0]?.why ?? 'rooms here are not sold that far ahead'}.`;
882
+ return {
883
+ why: `Not on sale yet: ${fails[0]?.why ?? 'rooms here are not sold that far ahead'}.`,
884
+ code: 'too-far',
885
+ ...(fails[0]?.args?.days ? { args: { days: fails[0].args.days } } : {}),
886
+ };
851
887
  const rule = fails.find((f) => f.kind === 'stay-rule');
852
888
  if (rule)
853
- return `Not for those nights: ${rule.why}.`;
889
+ return {
890
+ why: `Not for those nights: ${rule.why}.`,
891
+ // Booked too far ahead for this rate is "too far", said with its days.
892
+ ...(rule.args?.days !== undefined && !rule.args.rule
893
+ ? { code: 'too-far', args: { days: rule.args.days } }
894
+ : { code: 'stay-rule', ...(rule.args ? { args: rule.args } : {}) }),
895
+ };
854
896
  if (kinds.has('closed'))
855
- return 'Not on sale for those nights.';
856
- return 'No room here can be booked for those nights.';
897
+ return { why: 'Not on sale for those nights.', code: 'closed' };
898
+ return { why: 'No room here can be booked for those nights.', code: 'unavailable' };
857
899
  };
858
900
  // --------------------------------------------------------------- quote ----
859
901
  const quoteLines = async (trip, fresh = false) => {
@@ -2037,11 +2079,14 @@ export const createHuazhuHotels = (opts) => {
2037
2079
  }
2038
2080
  catch {
2039
2081
  pricedRooms.set(raw.hotelId, []);
2040
- why.set(raw.hotelId, 'Rooms by the hour could not be read from Huazhu just now; open the hotel for them.');
2082
+ why.set(raw.hotelId, {
2083
+ why: 'Rooms by the hour could not be read from Huazhu just now; open the hotel for them.',
2084
+ code: 'unreachable',
2085
+ });
2041
2086
  }
2042
2087
  return;
2043
2088
  }
2044
- const problem = stayProblem(stay, today());
2089
+ const problem = stayProblemOf(stay, today());
2045
2090
  if (problem) {
2046
2091
  pricedRooms.set(raw.hotelId, []);
2047
2092
  why.set(raw.hotelId, problem);
@@ -2055,9 +2100,12 @@ export const createHuazhuHotels = (opts) => {
2055
2100
  }
2056
2101
  catch (err) {
2057
2102
  pricedRooms.set(raw.hotelId, []);
2058
- why.set(raw.hotelId, err instanceof HuazhuRefusal && err.code === 2009
2059
- ? 'Huazhu is answering slowly just now; open the hotel for its rooms.'
2060
- : 'Prices could not be read from Huazhu just now; open the hotel for its rooms.');
2103
+ why.set(raw.hotelId, {
2104
+ why: err instanceof HuazhuRefusal && err.code === 2009
2105
+ ? 'Huazhu is answering slowly just now; open the hotel for its rooms.'
2106
+ : 'Prices could not be read from Huazhu just now; open the hotel for its rooms.',
2107
+ code: 'unreachable',
2108
+ });
2061
2109
  }
2062
2110
  };
2063
2111
  const bestFor = (raw, over) => {
@@ -2157,11 +2205,18 @@ export const createHuazhuHotels = (opts) => {
2157
2205
  if (!stay)
2158
2206
  return m.hotel;
2159
2207
  const b = bestFor(m.raw, f);
2160
- if (!b)
2208
+ if (!b) {
2209
+ const w = why.get(m.raw.hotelId) ?? {
2210
+ why: 'No room here can be booked for those nights.',
2211
+ code: 'unavailable',
2212
+ };
2161
2213
  return {
2162
2214
  ...m.hotel,
2163
- noRoomsForStay: why.get(m.raw.hotelId) ?? 'No room here can be booked for those nights.',
2215
+ noRoomsForStay: w.why,
2216
+ noRoomsForStayCode: w.code,
2217
+ ...(w.args ? { noRoomsForStayArgs: w.args } : {}),
2164
2218
  };
2219
+ }
2165
2220
  return {
2166
2221
  ...m.hotel,
2167
2222
  forStay: {
@@ -2250,9 +2305,9 @@ export const createHuazhuHotels = (opts) => {
2250
2305
  const detail = { hotel: toHotel(raw, view, now()) };
2251
2306
  if (!stay)
2252
2307
  return detail;
2253
- const problem = stayProblem(stay, today());
2308
+ const problem = stayProblemOf(stay, today());
2254
2309
  if (problem)
2255
- return { ...detail, noRooms: problem };
2310
+ return { ...detail, ...noRoomsOf(problem) };
2256
2311
  let priced;
2257
2312
  try {
2258
2313
  priced = await priceStay(raw, stay, 1);
@@ -2261,7 +2316,10 @@ export const createHuazhuHotels = (opts) => {
2261
2316
  return {
2262
2317
  ...detail,
2263
2318
  stay,
2264
- noRooms: `Huazhu could not be asked for this hotel's rooms just now (${err instanceof HuazhuRefusal ? err.huazhuMessage : 'no answer'}). Try again in a moment.`,
2319
+ ...noRoomsOf({
2320
+ why: `Huazhu could not be asked for this hotel's rooms just now (${err instanceof HuazhuRefusal ? err.huazhuMessage : 'no answer'}). Try again in a moment.`,
2321
+ code: 'unreachable',
2322
+ }),
2265
2323
  };
2266
2324
  }
2267
2325
  const rooms = priced
@@ -2272,7 +2330,7 @@ export const createHuazhuHotels = (opts) => {
2272
2330
  ...detail,
2273
2331
  stay,
2274
2332
  rooms,
2275
- ...(rooms.length === 0 ? { noRooms: noRoomsWhy(priced, stay, 1) } : {}),
2333
+ ...(rooms.length === 0 ? noRoomsOf(noRoomsWhy(priced, stay, 1)) : {}),
2276
2334
  };
2277
2335
  },
2278
2336
  async hourlyRooms(id, guests) {
@@ -2290,7 +2348,10 @@ export const createHuazhuHotels = (opts) => {
2290
2348
  return {
2291
2349
  hotel,
2292
2350
  stay,
2293
- noRooms: `Huazhu could not be asked for this hotel's rooms by the hour just now (${err instanceof HuazhuRefusal ? err.huazhuMessage : 'no answer'}). Try again in a moment.`,
2351
+ ...noRoomsOf({
2352
+ why: `Huazhu could not be asked for this hotel's rooms by the hour just now (${err instanceof HuazhuRefusal ? err.huazhuMessage : 'no answer'}). Try again in a moment.`,
2353
+ code: 'unreachable',
2354
+ }),
2294
2355
  };
2295
2356
  }
2296
2357
  const rooms = priced
@@ -2301,7 +2362,7 @@ export const createHuazhuHotels = (opts) => {
2301
2362
  hotel,
2302
2363
  stay,
2303
2364
  rooms,
2304
- ...(rooms.length === 0 ? { noRooms: hourlyNoRoomsWhy(priced, guests) } : {}),
2365
+ ...(rooms.length === 0 ? noRoomsOf(hourlyNoRoomsWhy(priced, guests)) : {}),
2305
2366
  };
2306
2367
  },
2307
2368
  async datesAround(id, stay, o) {