forked from ChrisChrome/weather-bot
Add /forecast
This commit is contained in:
parent
ff0f4da7ad
commit
cea680a013
|
@ -231,5 +231,26 @@
|
||||||
"type": 1,
|
"type": 1,
|
||||||
"integration_types": [0,1],
|
"integration_types": [0,1],
|
||||||
"contexts": [0, 1, 2]
|
"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
135
funcs.js
Normal 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
|
||||||
|
};
|
13
index.js
13
index.js
|
@ -1,6 +1,7 @@
|
||||||
// Requires
|
// Requires
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const config = require("./config.json");
|
const config = require("./config.json");
|
||||||
|
const funcs = require("./funcs.js");
|
||||||
const wfos = require("./data/wfos.json");
|
const wfos = require("./data/wfos.json");
|
||||||
const blacklist = require("./data/blacklist.json");
|
const blacklist = require("./data/blacklist.json");
|
||||||
const events = require("./data/events.json");
|
const events = require("./data/events.json");
|
||||||
|
@ -1280,6 +1281,18 @@ discord.on("interactionCreate", async (interaction) => {
|
||||||
});
|
});
|
||||||
break;
|
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;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
"@xmpp/debug": "^0.13.0",
|
"@xmpp/debug": "^0.13.0",
|
||||||
"colors": "^1.4.0",
|
"colors": "^1.4.0",
|
||||||
"discord.js": "^14.15.2",
|
"discord.js": "^14.15.2",
|
||||||
|
"geolib": "^3.3.4",
|
||||||
"html-entities": "^2.5.2",
|
"html-entities": "^2.5.2",
|
||||||
"jimp": "^0.22.12",
|
"jimp": "^0.22.12",
|
||||||
"sodium": "^3.0.2",
|
"sodium": "^3.0.2",
|
||||||
|
|
Loading…
Reference in a new issue