Add /forecast

This commit is contained in:
Christopher Cookman 2024-06-19 21:37:18 -06:00
parent ff0f4da7ad
commit cea680a013
Signed by: ChrisChrome
GPG key ID: A023A26E42C33A42
4 changed files with 170 additions and 0 deletions

View file

@ -231,5 +231,26 @@
"type": 1,
"integration_types": [0,1],
"contexts": [0, 1, 2]
},
{
"name": "forecast",
"description": "Get a forecast for a location",
"type": 1,
"integration_types": [0,1],
"contexts": [0, 1, 2],
"options": [
{
"name": "location",
"description": "Location to get forecast for (In the United States)",
"type": 3,
"required": true
},
{
"name": "periods",
"description": "Number of periods to get forecast for",
"type": 4,
"required": false
}
]
}
]

135
funcs.js Normal file
View file

@ -0,0 +1,135 @@
const geolib = require("geolib");
// Use OSM API to get coordinates https://nominatim.openstreetmap.org/search?q=search+query&format=json&limit=1
const getCoordinates = async (location) => {
return new Promise((resolve, reject) => {
// Make location url friendly
location = encodeURIComponent(location);
const url = `https://nominatim.openstreetmap.org/search?q=${location}&format=json&limit=1`;
// use custom useragent (discord-iem-bot, chris@chrischro.me)
const options = {
headers: {
'User-Agent': '(discord-iem-bot, chris@chrischro.me)',
},
};
// Make request
fetch(url, options)
.then(response => response.json())
.then(data => {
if (data.length > 0) {
resolve({
lat: data[0].lat,
lon: data[0].lon,
});
} else {
reject('Location not found');
}
})
.catch(err => {
reject(err);
});
})
};
const getForecast = async (lat, lon) => {
return new Promise((resolve, reject) => {
const url = `https://api.weather.gov/points/${lat},${lon}`;
// use same custom ua
const options = {
headers: {
'User-Agent': '(discord-iem-bot, chris@chrischro.me)',
},
};
// Make request
fetch(url, options)
.then(response => response.json())
.then(data => {
if (data.properties?.forecast) {
fetch(data.properties.forecast)
.then(response => response.json())
.then(data2 => {
data2.properties.relativeLocation = data.properties.relativeLocation;
resolve(data2);
})
.catch(err => {
reject(err);
});
} else {
reject('Forecast not found');
}
})
.catch(err => {
reject(err);
});
})
};
const getWeatherBySearch = async (search) => {
return new Promise((resolve, reject) => {
getCoordinates(search)
.then(coords => {
getForecast(coords.lat, coords.lon)
.then(data => {
resolve(data);
})
.catch(err => {
reject(err);
});
})
.catch(err => {
reject(err);
});
})
};
const generateDiscordEmbeds = (forecastData, numOfDays) => {
// Take the first 7 periods and make them into embeds
const embeds = [];
if (!numOfDays) numOfDays = 1;
for (let i = 0; i < numOfDays; i++) {
const period = forecastData.properties.periods[i];
const embed = {
title: `${period.name} in ${forecastData.properties.relativeLocation.properties.city} ${forecastData.properties.relativeLocation.properties.state}`,
description: period.detailedForecast,
timestamp: new Date(period.startTime),
thumbnail: {
url: period.icon,
},
fields: [
{
name: 'Temperature',
value: `${period.temperature}°F`,
inline: true
},
{
name: 'Wind',
value: `${period.windDirection} ${period.windSpeed}`,
inline: true
},
{
name: 'Precipitation',
value: period.probabilityOfPrecipitation.value ? period.probabilityOfPrecipitation.value + '%' : '0%',
inline: true
},
{
name: 'Humidity',
value: period.relativeHumidity.value + '%',
}
],
footer: {
text: 'Data provided by the National Weather Service'
}
};
embeds.push(embed);
}
return embeds;
}
module.exports = {
getCoordinates,
getForecast,
getWeatherBySearch,
generateDiscordEmbeds
};

View file

@ -1,6 +1,7 @@
// Requires
const fs = require("fs");
const config = require("./config.json");
const funcs = require("./funcs.js");
const wfos = require("./data/wfos.json");
const blacklist = require("./data/blacklist.json");
const events = require("./data/events.json");
@ -1280,6 +1281,18 @@ discord.on("interactionCreate", async (interaction) => {
});
break;
case "forecast":
await interaction.deferReply();
periods = interaction.options.getInteger("periods") || 1;
funcs.getWeatherBySearch(interaction.options.getString("location")).then((weather) => {
embeds = funcs.generateDiscordEmbeds(weather, periods);
interaction.editReply({ embeds });
}).catch((err) => {
interaction.editReply({ content: "Failed to get forecast", ephemeral: true });
if (config.debug >= 1) console.log(`${colors.red("[ERROR]")} Failed to get forecast: ${err.message}`);
});
break;

View file

@ -15,6 +15,7 @@
"@xmpp/debug": "^0.13.0",
"colors": "^1.4.0",
"discord.js": "^14.15.2",
"geolib": "^3.3.4",
"html-entities": "^2.5.2",
"jimp": "^0.22.12",
"sodium": "^3.0.2",