forked from ChrisChrome/weather-bot
Add /forecast
This commit is contained in:
parent
ff0f4da7ad
commit
cea680a013
|
@ -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
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
|
||||
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;
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Reference in a new issue