Full Data Update

This commit is contained in:
2026-07-17 21:17:18 -04:00
parent 3b3df1686c
commit 772415207e
6 changed files with 797 additions and 163 deletions
+243 -58
View File
@@ -1,4 +1,5 @@
const express = require('express');
const WebSocket = require('ws');
const auth = require('express-basic-auth');
const { createHash } = require('crypto');
@@ -40,6 +41,58 @@ const hazeTranslator = {
"Pathfinder",
"Trailblazer",
"Maven"
],
tag: [
"Vicious Dogs",
"Biohazard",
"Radioactive",
"Fence",
"Rats",
"Still Water",
"Barbed Wire",
"Fire",
"Flashlight Recommended",
"Climbing Rope Recommended",
"Boat",
"Electrical Hazard",
"Helmet Recommended",
"Boots Recommended",
"Falling Debris",
"Dust Mask Recommended",
"Asbestos",
"Extreme Temperature",
"Squatters",
"Risk of Violence or Crime",
"Emotional Distress",
"Landmines",
"Respirator Recommended",
"Active Security",
"No Trespassing Sign",
"Private Property Sign",
"Flood Risk",
"Locked",
"Dead Air",
"Unstable Structure",
"Black Mold",
"Ticks",
"Mosquitoes",
"Razor Wire",
"Fiberglass",
"Bears",
"Wolves",
"Wildlife",
"Broken Glass",
"Scorpions",
"Snakes",
"Venomous Spiders",
"Karens Present",
"Gloves Recommended",
"Boarded Up",
"Thorns",
"Dangerous Hights",
"Hazmat Suit Recommended",
"Heavy Overgrowth",
"Bats"
]
};
app.use(auth({
@@ -48,13 +101,15 @@ app.use(auth({
}
}));
app.all('/locations.geojson', (req, res) => {
// Assemble full Haze location data, can take a LONG time
app.all('/locations_full.geojson', (req, res) => {
// Verify Inputs
if ((!req.auth.user || !req.auth.password) && (!req.query.auth)) {
res.status(401).send('Unauthorized');
return;
}
// Query Haze API Websocket
// Setup local variables for request
let hazePwHash = null;
let hazeUser = null;
let hazeSocketCounter = 0;
@@ -71,8 +126,64 @@ app.all('/locations.geojson', (req, res) => {
hazePwHash = createHash('sha256').update(req.auth.password).digest('hex');
}
}
const haze = new WebSocket('wss://do.ecven.com:8120/explr');
const haze = new WebSocket('wss://do.ecven.com:8120/explr-mb');
let locations = [];
let failCounter = 0;
let queryComplete = false;
// Socket Message Handlers, defined here to use the local variables above
const sendComplete = () => {
if (!queryComplete) {
queryComplete = true;
haze.close();
let testTime = (new Date().getTime() - requestStartTime);
const countExported = locations.filter(location => location && location.completed).length;
console.log(`${(new Date().getTime() - requestStartTime)/1000}s | FAILS: ${failCounter} | TIMEOUT: ${testTime} | TOTAL LOCATIONS: ${locations.length} | EXPORTED: ${countExported} | TIMEDOUT: ${locations.length - countExported - failCounter}`);
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify({
...geoJsonTemplate,
features: locations.map(location => ({
type: "Feature",
geometry: {
type: "Point",
coordinates: [location['longitude'], location['latitude']]
},
properties: {
id: location['id'],
title: location['name'],
"marker-symbol": location['isUnconfirmedLocation'] === 1 ? "star-stroked" : "star",
author: location['author'] || '',
explored: location['isExplored'] || false,
confirmed: location['isConfirmed'] || false,
category: location['category'] || '',
level: location['level'] || '',
rating: location['rating'] || '',
difficulty: location['difficulty'] || '',
comments: location['comments'] || [],
querySuccess: location['completed']
}
}))
}));
}
}
const queryLocation = (index) => {
haze.send(locations[index]['requestQuery']);
setTimeout(() => {
if (locations[index]['completed'] === 0) {
console.log(`Timeout on ${locations[index]['id']}}`);
locations[index]['completed'] = -1; // mark as failed
failCounter++;
let nextIndex = locations.findIndex(location => location && location.completed === 0);
if (locations.every(location => location && location.completed !== 0) || nextIndex === -1) {
sendComplete();
} else {
queryLocation(nextIndex);
}
}
}, 10000);
}
// These are the socket event handlers, in order of expected use
haze.addEventListener('message', (event) => {
const data = JSON.parse(event.data);
let haze_authToken = '', haze_username = '';
@@ -103,15 +214,9 @@ app.all('/locations.geojson', (req, res) => {
break;
case "getMyLocationsRequest_response":
if (data['body']['response'] === 1) {
// locations = data['body']['locations'].map(location => {
// return {
// id: location['id'],
// title: location['name'],
// completed: false
// };
// });
data['body']['locations'].forEach(location => {
locations[hazeSocketCounter++] = {
hazeSocketCounter++;
locations[hazeSocketCounter] = {
id: location['id'],
title: location['name'],
latitude: location['coordinates']['latitude'],
@@ -119,21 +224,17 @@ app.all('/locations.geojson', (req, res) => {
isExplored: location['explored'] > 0,
isConfirmed: location['isUnconfirmedLocation'] !== 1,
category: hazeTranslator.category[location['locationCategory']],
completed: false
}
});
// need to make the socketIDs dynamic and handle completed status before returning final payload
// FIX: Currently throwing response 2 "User does not have access to location."
locations.forEach((location, index) => {
if (location) {
haze.send(JSON.stringify({
rawBasicData: JSON.stringify(location),
requestQuery: JSON.stringify({
event: "getLocation_request",
body: locations.map(location => location.id),
socketMessageId: index
}));
body: [ location['id'] ],
socketMessageId: hazeSocketCounter
}),
completed: 0
}
});
queryLocation(locations.findIndex(location => location && location.completed === 0));
} else {
console.dir(data);
res.status(500).send('Failed to retrieve locations');
@@ -143,14 +244,29 @@ app.all('/locations.geojson', (req, res) => {
break;
case "getLocation_response":
if (data['body']['response'] === 1) {
let locationTags = {};
data['body']['location']['locationRating']['ratingItemsList'].map(tag => {
locationTags[hazeTranslator.tag[tag['id']] ? hazeTranslator.tag[tag['id']] : tag['id']] = tag['count'];
});
locations[data['socketMessageId']] = {
...locations[data['socketMessageId']],
completed: true,
completed: 1,
author: `${data['body']['location']['submittorUserDatas']['username']} (${data['body']['location']['submittorUserDatas']['email']})`,
rating: hazeTranslator.rating[data['body']['location']['locationRating']['overallRating']],
difficulty: hazeTranslator.difficulty[data['body']['location']['locationRating']['difficultyRating']],
level: hazeTranslator.level[data['body']['location']['locationRating']['levelRating']],
// TODO: use body.location.locationRating.ratingItemsList for location tags
tags: locationTags,
images: [
... data['body']['location']['gallery'].map(image => ({
id: image['id'],
mimetype: `image/${image['mediaType']}`,
author: `${image['uploaderData']['username']} (${image['uploaderData']['email']})`,
url: image['url'],
thumbnail: image['thumbnailUrl'],
likes: image['likes'],
date: image['uploadDate'],
}))
],
comments: data['body']['location']['locationCommentsAndCommunityVotes'].map(comment => ({
id: comment['id'],
text: comment['comment'],
@@ -158,43 +274,111 @@ app.all('/locations.geojson', (req, res) => {
author: comment['submittor']['username'],
}))
};
let testTime = (new Date().getTime() - requestStartTime) > 30000; // 30s timeout
if (locations.every(location => location && location.completed) || testTime) {
res.setHeader('Content-Type', 'application/json');
haze.close();
res.end(JSON.stringify({
...geoJsonTemplate,
features: locations.map(location => ({
type: "Feature",
geometry: {
type: "Point",
coordinates: [location['longitude'], location['latitude']]
},
properties: {
id: location['id'],
title: location['name'],
"marker-symbol": location['isUnconfirmedLocation'] === 1 ? "star-stroked" : "star",
author: location['author'] || '',
explored: location['isExplored'] || false,
confirmed: location['isConfirmed'] || false,
category: location['category'] || '',
level: location['level'] || '',
rating: location['rating'] || '',
difficulty: location['difficulty'] || '',
comments: location['comments'] || [],
querySuccess: location['completed']
}
}))
}));
}
} else {
console.dir(data);
// res.status(500).send('Failed to retrieve locations');
// haze.close();
// res.end();
console.log(`Failure on ${locations[data['socketMessageId']]['id']}: ${data['body']['responseDetails']}`);
locations[data['socketMessageId']]['completed'] = -1; // mark as failed
failCounter++;
}
// Completion Check and increment
let nextIndex = locations.findIndex(location => location && location.completed === 0);
if (locations.every(location => location && location.completed !== 0) || nextIndex === -1) {
sendComplete();
} else {
queryLocation(nextIndex);
}
break;
case "ping":
console.log('Ping received from Haze API');
break;
default:
console.log(JSON.stringify(data, null, 2));
res.status(500).send('Unexpected response from Haze API');
haze.close();
res.end();
}
});
});
// Original API, for only listing locations with minimal data
app.all('/locations.geojson', (req, res) => {
// Verify Inputs
if ((!req.auth.user || !req.auth.password) && (!req.query.auth)) {
res.status(401).send('Unauthorized');
return;
}
// Query Haze API Websocket
let hazePwHash = null;
let hazeUser = null;
if (req.query.auth) {
let paramAuth = Buffer.from(req.query.auth, 'base64').toString('utf-8');
hawUser = paramAuth.split(":")[0];
hazePwHash = paramAuth.split(":")[1];
} else {
hazeUser = req.auth.user;
if (/\b[A-Fa-f0-9]{64}\b/.test(req.auth.password)) {
hazePwHash = req.auth.password;
} else {
hazePwHash = createHash('sha256').update(req.auth.password).digest('hex');
}
}
const haze = new WebSocket('wss://do.ecven.com:8120/explr');
haze.addEventListener('message', (event) => {
const data = JSON.parse(event.data);
let haze_authToken = '', haze_username = '';
switch (data['event']) {
case "connected":
haze.send(JSON.stringify({
"event": "authenticateAccount_request",
"body": [
{
"password": hazePwHash,
"email": hazeUser
}
],
"socketMessageId": 0
}));
break;
case "authenticateAccount_response":
if (data['body']['response'] === 1) {
haze_authToken = data['body']['authToken'];
haze_username = data['body']['username'];
haze.send('{"event": "getMyLocationsRequest_request","body": [{"higlightImages": true}],"socketMessageId": 0}');
} else {
console.dir(req.query);
res.status(401).json({ ...data, password: req.auth.password });
haze.close();
res.end();
}
break;
case "getMyLocationsRequest_response":
if (data['body']['response'] === 1) {
const hazeLocations = data['body']['locations'].map(location => {
return {
type: "Feature",
geometry: {
type: "Point",
coordinates: [location['coordinates']['longitude'], location['coordinates']['latitude']]
},
properties: {
id: location['id'],
title: location['name'],
"marker-symbol": location['isUnconfirmedLocation'] === 1 ? "star-stroked" : "star",
}
}
});
res.setHeader('Content-Type', 'application/json');
haze.close();
res.end(JSON.stringify({
...geonJsonTemplate,
features: hazeLocations
}));
} else {
console.dir(data);
res.status(500).send('Failed to retrieve locations');
haze.close();
res.end();
}
break;
default:
console.dir(data);
res.status(500).send('Unexpected response from Haze API');
@@ -204,6 +388,7 @@ app.all('/locations.geojson', (req, res) => {
});
});
app.listen(port, () => {
console.log(`Server is running on http://localhost:${port}`);
});