-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapi.js
More file actions
100 lines (89 loc) · 3.41 KB
/
api.js
File metadata and controls
100 lines (89 loc) · 3.41 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
const https = require('https');
const API_URL = 'https://www.holidayextras.com/dock-yard/hapi/hotels/?productType=hotels&agent=WY992&out_flight=FR4076&campaign_id=65642&launch_id=rddef100&lang=en&location=MAN&airport=MAN&check_in=2026-05-09&returning=2026-05-10&terminal=3&token=7f5ad91e-0c07-48da-9133-518caaf24a41&sid=b0664570458711f1b75aad14ee535e33&room_types%5B0%5D=D20';
function fetchJson(url) {
return new Promise((resolve, reject) => {
https.get(url, (res) => {
let data = '';
res.on('data', chunk => { data += chunk; });
res.on('end', () => {
try { resolve(JSON.parse(data)); }
catch (e) { reject(new Error(`JSON parse failed: ${e.message}`)); }
});
}).on('error', reject);
});
}
function deriveHighlight(product) {
if (product.time_to_terminal) {
const t = product.time_to_terminal.toLowerCase();
if (t.includes('walk')) return t.replace('minutes', 'min');
if (t.includes('shuttle')) return 'Free shuttle included';
}
if (product.transfers?.included_in_price && product.transfers?.price === 'free') {
const mins = product.transfers.travel_time;
if (mins && parseInt(mins) <= 3) return `${mins}-min walk to terminal`;
return 'Free hotel shuttle';
}
if (product.on_airport) return 'On the airport';
if (product.distance_miles_string && product.distance_miles_string !== 'null') {
return `${product.distance_miles_string} miles to airport`;
}
return null;
}
function deriveLocation(product) {
if (product.on_airport) return 'On the airport grounds';
if (product.distance_miles_string && product.distance_miles_string !== 'null') {
return `${product.distance_miles_string} miles to the airport`;
}
return product.location?.name || '';
}
function deriveBadges(product) {
const badges = [];
if (product.cancellation?.is_refundable || product.is_refundable) {
badges.push('Flextras: Free Cancellation');
}
badges.push('Never Beaten on Price');
badges.push('15% Off Travel Insurance');
if (product.recommended) badges.push('Tried, Tested, Recommended');
badges.push('The Extracare Guarantee');
return badges;
}
function imageUrl(raw) {
if (!raw) return null;
return raw.startsWith('//') ? `https:${raw}` : raw;
}
function groupByHotel(products) {
const groups = new Map();
for (const p of products) {
const key = p.brand_name;
if (!groups.has(key)) {
groups.set(key, { cheapest: p, packages: [] });
}
const g = groups.get(key);
if (p.price < g.cheapest.price) g.cheapest = p;
g.packages.push(p);
}
return Array.from(groups.values()).map(({ cheapest: p, packages }) => ({
id: p.unprefixedCode.toLowerCase(),
name: p.brand_name,
stars: p.star_rating || null,
imageUrl: imageUrl(p.brand_image || p.logo),
highlight: deriveHighlight(p),
location: deriveLocation(p),
price: (p.price / 100).toFixed(2),
rating: parseFloat(p.score?.score) || null,
badges: deriveBadges(p),
description: p.sales_introduction || '',
packageCount: packages.length,
links: {
map: `/hotel/${p.unprefixedCode.toLowerCase()}/map`,
photos: `/hotel/${p.unprefixedCode.toLowerCase()}/photos`,
packages: `/hotel/${p.unprefixedCode.toLowerCase()}/packages`,
reviews: `/hotel/${p.unprefixedCode.toLowerCase()}/reviews`,
},
}));
}
async function getHotels() {
const raw = await fetchJson(API_URL);
return groupByHotel(raw);
}
module.exports = { getHotels };