395 lines
17 KiB
JavaScript
395 lines
17 KiB
JavaScript
const express = require('express');
|
|
const WebSocket = require('ws');
|
|
const auth = require('express-basic-auth');
|
|
const { createHash } = require('crypto');
|
|
|
|
|
|
const app = express();
|
|
const port = 8355;
|
|
const geoJsonTemplate = {
|
|
type: "FeatureCollection",
|
|
features: [],
|
|
"marker-symbol-images": {
|
|
"star": "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 640 640'><path opacity='.4' fill='currentColor' d='M160 263.7L160 512C160 520.8 167.2 528 176 528L261.9 528L231.6 479.9C227.2 473 228.8 463.9 235.2 458.8L320 391.5L261.9 315.4C250.6 300.6 269.3 281.8 284.2 292.9L399.4 379.1C407.8 385.4 408 397.9 399.8 404.4L320 467.8L357.9 528L464 528C472.8 528 480 520.8 480 512L480 263.7L320 120.2L160 263.7z'/><path fill='currentColor' d='M336 70.1C326.9 61.9 313.1 61.9 304 70.1L72 278.1C62.1 286.9 61.3 302.1 70.2 312C79.1 321.9 94.2 322.7 104.1 313.8L112.1 306.6L112.1 511.9C112.1 547.2 140.8 575.9 176.1 575.9L464.1 575.9C499.4 575.9 528.1 547.2 528.1 511.9L528.1 306.6L536.1 313.8C546 322.6 561.1 321.8 570 312C578.9 302.2 578 287 568.2 278.1L528.2 242.2L528.2 152C528.2 138.7 517.5 128 504.2 128C490.9 128 480.2 138.7 480.2 152L480.2 199.2L336.2 70.1zM480 263.7L480 512C480 520.8 472.8 528 464 528L357.9 528L320 467.8L399.7 404.5C407.9 398 407.7 385.4 399.3 379.2L284.2 293C269.3 281.9 250.6 300.8 261.9 315.5L320 391.6L235.2 458.9C228.8 464 227.3 473.1 231.6 480L261.9 528.1L176 528.1C167.2 528.1 160 520.9 160 512.1L160 263.7L320 120.2L480 263.7z'/></svg>",
|
|
"star-stroked": "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512'><path fill='currentColor' d='M64 32C28.7 32 0 60.7 0 96L0 416c0 35.3 28.7 64 64 64l320 0c35.3 0 64-28.7 64-64l0-320c0-35.3-28.7-64-64-64L64 32zm73.8 149.3c7.9-22.3 29.1-37.3 52.8-37.3l58.3 0c34.9 0 63.1 28.3 63.1 63.1 0 22.6-12.1 43.5-31.7 54.8L248 280.4c-.2 13-10.9 23.6-24 23.6-13.3 0-24-10.7-24-24l0-13.5c0-8.6 4.6-16.5 12.1-20.8l44.3-25.4c4.7-2.7 7.6-7.7 7.6-13.1 0-8.4-6.8-15.1-15.1-15.1l-58.3 0c-3.4 0-6.4 2.1-7.5 5.3l-.4 1.2c-4.4 12.5-18.2 19-30.6 14.6s-19-18.2-14.6-30.6l.4-1.2zM192 368a32 32 0 1 1 64 0 32 32 0 1 1 -64 0zM48 104a24 24 0 1 1 48 0 24 24 0 1 1 -48 0zM376 80a24 24 0 1 1 0 48 24 24 0 1 1 0-48zM48 408a24 24 0 1 1 48 0 24 24 0 1 1 -48 0zm328-24a24 24 0 1 1 0 48 24 24 0 1 1 0-48z'/></svg>"
|
|
}
|
|
};
|
|
const hazeTranslator = {
|
|
category: [
|
|
"Urbex",
|
|
"Nature",
|
|
"Spontaneous"
|
|
],
|
|
difficulty: [
|
|
"Not Sure",
|
|
"Baby",
|
|
"Walk in the Park",
|
|
"Challenge",
|
|
"Madman",
|
|
"Deathwish"
|
|
],
|
|
rating: [
|
|
"Not Sure",
|
|
"Check it Out",
|
|
"Worth the Trip",
|
|
"Must See"
|
|
],
|
|
level: [
|
|
"Lost Soul",
|
|
"Roamer",
|
|
"Adventurer",
|
|
"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({
|
|
authorizer: (username, password) => {
|
|
return true;
|
|
}
|
|
}));
|
|
|
|
// 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;
|
|
}
|
|
|
|
// Setup local variables for request
|
|
let hazePwHash = null;
|
|
let hazeUser = null;
|
|
let hazeSocketCounter = 0;
|
|
let requestStartTime = new Date().getTime();
|
|
if (req.query.auth) {
|
|
let paramAuth = Buffer.from(req.query.auth, 'base64').toString('utf-8');
|
|
hazeUser = 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-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 = '';
|
|
switch (data['event']) {
|
|
case "connected":
|
|
haze.send(JSON.stringify({
|
|
"event": "authenticateAccount_request",
|
|
"body": [
|
|
{
|
|
"password": hazePwHash,
|
|
"email": hazeUser
|
|
}
|
|
],
|
|
"socketMessageId": hazeSocketCounter++
|
|
}));
|
|
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": ' + hazeSocketCounter++ + '}');
|
|
} 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) {
|
|
data['body']['locations'].forEach(location => {
|
|
hazeSocketCounter++;
|
|
locations[hazeSocketCounter] = {
|
|
id: location['id'],
|
|
title: location['name'],
|
|
latitude: location['coordinates']['latitude'],
|
|
longitude: location['coordinates']['longitude'],
|
|
isExplored: location['explored'] > 0,
|
|
isConfirmed: location['isUnconfirmedLocation'] !== 1,
|
|
category: hazeTranslator.category[location['locationCategory']],
|
|
rawBasicData: JSON.stringify(location),
|
|
requestQuery: JSON.stringify({
|
|
event: "getLocation_request",
|
|
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');
|
|
haze.close();
|
|
res.end();
|
|
}
|
|
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: 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']],
|
|
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'],
|
|
rating: (comment['upvotes']*1)+(comment['downvotes']* -1),
|
|
author: comment['submittor']['username'],
|
|
}))
|
|
};
|
|
} else {
|
|
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');
|
|
haze.close();
|
|
res.end();
|
|
}
|
|
});
|
|
});
|
|
|
|
|
|
app.listen(port, () => {
|
|
console.log(`Server is running on http://localhost:${port}`);
|
|
});
|