main #21

Open
ChrisChrome wants to merge 44 commits from main into shard-test
10 changed files with 592 additions and 160 deletions

1
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1 @@
{}

7
PRIVACY.md Normal file
View file

@ -0,0 +1,7 @@
# Privacy Policy
Just to make this simple, heres a list of whats stored and how it's used
- Discord channel/user IDs of subscribed channels/DMs - Should be obvious, in case it's not, we need these to know where to send alerts.
- IEM rooms and filters - Should also be obvious, but again, we need to know which rooms you subscribed to
Other than that, occasional debug logging may be enabled to fix bugs, and any logs gathered from debugging will be erased immidiately.

5
TERMS.md Normal file
View file

@ -0,0 +1,5 @@
# Terms of Service
## This is mostly here to comply with Discord verified bot stuff
Other than complying with the GPL-3.0 License when contributing/using the code of this project, this bot is NOT to be used as an emergency alerting system, and should NOT be trusted with life/property under any circumstances.
Chris Chrome and any other contributors are not to be held liable should this not work in an emergency, as you shouldn't be using it for emergency alerts.

View file

@ -91,6 +91,4 @@
"zsechat@conference.weather.im",
"zdcchat@conference.weather.im",
"znychat@conference.weather.im"
]

View file

@ -217,7 +217,7 @@
},
"DSW": {
"text": "Dust Storm Warning",
"priority": 5
"priority": 4
},
"EFP": {
"priority": 1,
@ -365,7 +365,7 @@
},
"FFW": {
"text": "Flash Flood Warning",
"priority": 4
"priority": 5
},
"FLN": {
"priority": 1,
@ -385,7 +385,7 @@
},
"FRW": {
"text": "Fire Warning",
"priority": 4
"priority": 3
},
"FSH": {
"priority": 1,
@ -680,7 +680,7 @@
"text": "Data Mgt Message"
},
"NPW": {
"priority": 1,
"priority": 3,
"text": "Non-Precipitation Warnings / Watches / Advisories"
},
"NSH": {
@ -1160,7 +1160,7 @@
"text": "Tropical Cyclone Update"
},
"TCV": {
"priority": 1,
"priority": 4,
"text": "Tropical Cyclone Watch/Warning Break Points"
},
"TIB": {
@ -1308,7 +1308,7 @@
"text": "Routine Space Environment Product Issued Weekly"
},
"WOU": {
"priority": 4,
"priority": 5,
"text": "Tornado/Severe Thunderstorm Watch"
},
"WS1": {
@ -1345,7 +1345,7 @@
},
"WSW": {
"text": "Winter Storm Warning",
"priority": 5
"priority": 4
},
"WWA": {
"priority": 1,
@ -1365,11 +1365,11 @@
},
"CFA": {
"text": "Coastal Flood Watch",
"priority": 4
"priority": 3
},
"FLA": {
"text": "Flood Watch",
"priority": 2
"priority": 3
},
"HWA": {
"text": "High Wind Watch",
@ -1389,7 +1389,7 @@
},
"SVA": {
"text": "Severe Thunderstorm Watch",
"priority": 4
"priority": 5
},
"TOA": {
"text": "Tornado Watch",
@ -1405,7 +1405,7 @@
},
"TSA": {
"text": "Tsunami Watch",
"priority": 4
"priority": 5
},
"TSW": {
"text": "Tsunami Warning",
@ -1522,6 +1522,9 @@
"REP": {
"text": "RECCO Observations (tropical cyclone)",
"priority": 3
},
"PIR": {
"text": "Pilot Reports",
"priority": 1
}
}

View file

@ -1,13 +1,13 @@
{
"convective": [
"https://www.spc.noaa.gov/products/outlook/day1otlk.gif",
"https://www.spc.noaa.gov/products/outlook/day2otlk.gif",
"https://www.spc.noaa.gov/products/outlook/day3otlk.gif",
"https://www.spc.noaa.gov/products/exper/day4-8/day4prob.gif",
"https://www.spc.noaa.gov/products/exper/day4-8/day5prob.gif",
"https://www.spc.noaa.gov/products/exper/day4-8/day6prob.gif",
"https://www.spc.noaa.gov/products/exper/day4-8/day7prob.gif",
"https://www.spc.noaa.gov/products/exper/day4-8/day8prob.gif"
"https://weather.cod.edu/cdata/text/images/spc/co/day1/categorical/spccoday1.categorical.latest.png",
"https://climate.cod.edu/data/text/images/spc/co/day2/categorical/spccoday2.categorical.latest.png",
"https://climate.cod.edu/data/text/images/spc/co/day3/categorical/spccoday3.categorical.latest.png",
"https://climate.cod.edu/data/text/images/spc/co/day4/severe/spccoday4.severe.latest.png",
"https://climate.cod.edu/data/text/images/spc/co/day5/severe/spccoday5.severe.latest.png",
"https://climate.cod.edu/data/text/images/spc/co/day6/severe/spccoday6.severe.latest.png",
"https://climate.cod.edu/data/text/images/spc/co/day7/severe/spccoday7.severe.latest.png",
"https://climate.cod.edu/data/text/images/spc/co/day8/severe/spccoday8.severe.latest.png"
],
"fire": [
"https://www.spc.noaa.gov/products/exper/fire_wx/imgs/day1otlk_fire.gif",

187
data/satellites.json Normal file
View file

@ -0,0 +1,187 @@
{
"GOES-16": {
"products": {
"Full Disk": {
"Visible": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/FD/GEOCOLOR/1808x1808.jpg",
"Airmass": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/FD/AirMass/1808x1808.jpg",
"Infrared": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/FD/13/1808x1808.jpg",
"Water Vapor": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/FD/10/1808x1808.jpg"
},
"Floater 1": {
"Visible": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/MESO/M1/GEOCOLOR/1000x1000.jpg",
"Infrared": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/MESO/M1/13/1000x1000.jpg",
"Water Vapor": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/MESO/M1/10/1000x1000.jpg"
},
"Floater 2": {
"Visible": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/MESO/M2/GEOCOLOR/1000x1000.jpg",
"Infrared": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/MESO/M2/13/1000x1000.jpg",
"Water Vapor": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/MESO/M2/10/1000x1000.jpg"
},
"United States": {
"Visible": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/CONUS/GEOCOLOR/2500x1500.jpg",
"Airmass": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/CONUS/AirMass/2500x1500.jpg",
"Infrared": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/CONUS/13/2500x1500.jpg",
"Water Vapor": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/CONUS/10/2500x1500.jpg"
},
"Canada": {
"Visible": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/can/GEOCOLOR/2250x1125.jpg",
"Airmass": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/can/AirMass/2250x1125.jpg",
"Infrared": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/can/13/2250x1125.jpg",
"Water Vapor": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/can/10/2250x1125.jpg"
},
"Mexico": {
"Visible": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/mex/GEOCOLOR/1000x1000.jpg",
"Airmass": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/mex/AirMass/1000x1000.jpg",
"Infrared": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/mex/13/1000x1000.jpg",
"Water Vapor": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/mex/10/1000x1000.jpg"
},
"US East Coast": {
"Visible": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/eus/GEOCOLOR/1000x1000.jpg",
"Airmass": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/eus/AirMass/1000x1000.jpg",
"Infrared": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/eus/13/1000x1000.jpg",
"Water Vapor": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/eus/10/1000x1000.jpg"
},
"Gulf of Mexico": {
"Visible": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/gm/GEOCOLOR/1000x1000.jpg",
"Airmass": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/gm/AirMass/1000x1000.jpg",
"Infrared": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/gm/13/1000x1000.jpg",
"Water Vapor": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/gm/10/1000x1000.jpg"
},
"Puerto Rico": {
"Visible": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/pr/GEOCOLOR/1200x1200.jpg",
"Airmass": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/pr/AirMass/1200x1200.jpg",
"Infrared": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/pr/13/1200x1200.jpg",
"Water Vapor": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/pr/10/1200x1200.jpg"
}
}
},
"GOES-18": {
"products": {
"Full Disk": {
"Visible": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/FD/GEOCOLOR/1808x1808.jpg",
"Airmass": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/FD/AirMass/1808x1808.jpg",
"Infrared": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/FD/13/1808x1808.jpg",
"Water Vapor": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/FD/10/1808x1808.jpg"
},
"Floater 1": {
"Visible": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/MESO/M1/GEOCOLOR/1000x1000.jpg",
"Infrared": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/MESO/M1/13/1000x1000.jpg",
"Water Vapor": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/MESO/M1/10/1000x1000.jpg"
},
"Floater 2": {
"Visible": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/MESO/M2/GEOCOLOR/1000x1000.jpg",
"Infrared": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/MESO/M2/13/1000x1000.jpg",
"Water Vapor": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/MESO/M2/10/1000x1000.jpg"
},
"US West Coast": {
"Visible": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/wus/GEOCOLOR/1000x1000.jpg",
"Airmass": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/wus/AirMass/1000x1000.jpg",
"Infrared": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/wus/13/1000x1000.jpg",
"Water Vapor": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/wus/10/1000x1000.jpg"
},
"Hawaii": {
"Visible": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/SECTOR/hi/GEOCOLOR/1200x1200.jpg",
"Airmass": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/SECTOR/hi/AirMass/1200x1200.jpg",
"Infrared": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/SECTOR/hi/13/1200x1200.jpg",
"Water Vapor": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/SECTOR/hi/10/1200x1200.jpg"
},
"Alaska": {
"Visible": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/SECTOR/ak/GEOCOLOR/1000x1000.jpg",
"Airmass": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/SECTOR/ak/AirMass/1000x1000.jpg",
"Infrared": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/SECTOR/ak/13/1000x1000.jpg",
"Water Vapor": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/SECTOR/ak/10/1000x1000.jpg"
}
}
},
"Himawari": {
"products": {
"Full Disk": {
"Visible": "https://rammb.cira.colostate.edu/ramsdis/online/images/latest_hi_res/himawari-8/full_disk_ahi_true_color.jpg",
"Airmass": "https://rammb.cira.colostate.edu/ramsdis/online/images/latest_hi_res/himawari-8/full_disk_ahi_rgb_airmass.jpg",
"Infrared": "https://www.ssec.wisc.edu/data/geo/images/himawari09/latest-himawari09_11_fd.gif",
"Water Vapor": "https://www.ssec.wisc.edu/data/geo/images/himawari09/latest-himawari09_10_fd.gif"
},
"Floater 1": {
"Visible": "https://rammb.cira.colostate.edu/ramsdis/online/images/latest/himawari-8/floater_02_geocolor.pngv",
"Airmass": "https://rammb.cira.colostate.edu/ramsdis/online/images/latest/himawari-8/floater_02_rgb_airmass.png"
},
"American Samoa": {
"Visible": "https://rammb.cira.colostate.edu/ramsdis/online/images/latest_hi_res/himawari-8/american_samoa_ahi_natural_color.png",
"Airmass": "https://rammb.cira.colostate.edu/ramsdis/online/images/latest_hi_res/himawari-8/american_samoa_ahi_rgb_airmass.png"
},
"Australia": {
"Visible": "https://rammb.cira.colostate.edu/ramsdis/online/images/latest/himawari-8/australia_true_color.jpg",
"Airmass": "https://rammb.cira.colostate.edu/ramsdis/online/images/latest_hi_res/himawari-8/australia_ahi_rgb_airmass.png"
},
"New Zealand": {
"Visible": "https://rammb.cira.colostate.edu/ramsdis/online/images/latest_hi_res/himawari-8/new_zealand_ahi_natural_color.png",
"Airmass": "https://rammb.cira.colostate.edu/ramsdis/online/images/latest_hi_res/himawari-8/new_zealand_ahi_rgb_airmass.png"
},
"Guam": {
"Visible": "https://rammb.cira.colostate.edu/ramsdis/online/images/latest_hi_res/himawari-8/guam_ahi_natural_color.png",
"Airmass": "https://rammb.cira.colostate.edu/ramsdis/online/images/latest_hi_res/himawari-8/guam_ahi_rgb_airmass.png"
},
"Hawaii": {
"Visible": "https://rammb.cira.colostate.edu/ramsdis/online/images/latest_hi_res/himawari-8/hawaii_ahi_natural_color.png"
},
"Japan": {
"Visible": "https://rammb.cira.colostate.edu/ramsdis/online/images/latest_hi_res/himawari-8/japan_ahi_natural_color.png",
"Airmass": "https://rammb.cira.colostate.edu/ramsdis/online/images/latest_hi_res/himawari-8/japan_ahi_rgb_airmass.png"
},
"Russia": {
"Visible": "https://rammb.cira.colostate.edu/ramsdis/online/images/latest/himawari-8/eastern_russia_true_color.jpg"
},
"China": {
"Visible": "https://rammb.cira.colostate.edu/ramsdis/online/images/latest_hi_res/himawari-8/eastern_china_ahi_natural_color.png",
"Airmass": "https://rammb.cira.colostate.edu/ramsdis/online/images/latest_hi_res/himawari-8/eastern_china_ahi_rgb_airmass.png"
}
}
},
"EWS-G2": {
"products": {
"Full Disk": {
"Visible": "https://www.ssec.wisc.edu/data/geo/images/ews-g1/latest_ews-g1_01_fd.gif",
"Water Vapor": "https://www.ssec.wisc.edu/data/geo/images/ews-g1/latest_ews-g1_03_fd.gif"
}
}
},
"FY-2G": {
"products": {
"Full Disk": {
"Visible": "https://www.ssec.wisc.edu/data/geo/images/fy2g/latest_fy2g_01_fd.gif",
"Infrared": "https://www.ssec.wisc.edu/data/geo/images/fy2g/latest_fy2g_02_fd.gif",
"Water Vapor": "https://www.ssec.wisc.edu/data/geo/images/fy2g/latest_fy2g_04_fd.gif"
}
}
},
"GK-2A": {
"products": {
"Full Disk": {
"Infrared": "https://kiwiweather.com/gk-2a/FD_sanchez.jpg"
}
}
},
"Meteosat 9": {
"products": {
"Full Disk": {
"Visible": "https://www.ssec.wisc.edu/data/geo/images/met-iodc/latest_met-iodc_01_fd.jpg",
"Infrared": "https://www.ssec.wisc.edu/data/geo/images/met-iodc/latest_met-iodc_04_fd.jpg",
"Water Vapor": "https://www.ssec.wisc.edu/data/geo/images/met-iodc/latest_met-iodc_06_fd.jpg"
}
}
},
"Meteosat 10": {
"products": {
"Full Disk": {
"Visible": "https://www.ssec.wisc.edu/data/geo/images/met-prime/latest_met-prime_01_fd.gif",
"Infrared": "https://www.ssec.wisc.edu/data/geo/images/met-prime/latest_met-prime_04_fd.gif",
"Water Vapor": "https://www.ssec.wisc.edu/data/geo/images/met-prime/latest_met-prime_06_fd.gif"
},
"Europe": {
"Visible": "https://www.ssec.wisc.edu/data/geo/images/met-prime/latest_met-prime_01_euro.gif",
"Infrared": "https://www.ssec.wisc.edu/data/geo/images/met-prime/latest_met-prime_04_euro.gif",
"Water Vapor": "https://www.ssec.wisc.edu/data/geo/images/met-prime/latest_met-prime_06_euro.gif"
}
}
}
}

View file

@ -1,30 +0,0 @@
{
"GOES-16": [
{
"name": "GeoColor",
"url": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/CONUS/GEOCOLOR/latest.jpg"
},
{
"name": "Infrared",
"url": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/CONUS/13/latest.jpg"
},
{
"name": "FullDisk",
"url": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/FD/GEOCOLOR/678x678.jpg"
}
],
"GOES-18": [
{
"name": "GeoColor",
"url": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/CONUS/GEOCOLOR/latest.jpg"
},
{
"name": "Infrared",
"url": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/CONUS/13/latest.jpg"
},
{
"name": "FullDisk",
"url": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/FD/GEOCOLOR/678x678.jpg"
}
]
}

475
index.js
View file

@ -6,8 +6,8 @@ const wfos = require("./data/wfos.json");
const blacklist = require("./data/blacklist.json");
const events = require("./data/events.json");
const outlookURLs = require("./data/outlook.json");
const sattelites = require("./data/sattelites.json");
const nwrstreams = require("./data/nwrstreams.json")
const satellites = require("./data/satellites.json");
const nwrstreams = {callsigns:{}};
const Jimp = require("jimp");
const { client, xml } = require("@xmpp/client");
const fetch = require("node-fetch");
@ -16,8 +16,12 @@ const Discord = require("discord.js");
const dVC = require("@discordjs/voice");
const colors = require("colors");
const sqlite3 = require("sqlite3").verbose();
satMessages = {};
// Setup Discord
const discord = new Discord.Client({
intents: [
"Guilds",
"GuildVoiceStates",
@ -32,7 +36,6 @@ const rest = new REST({
version: '10'
}).setToken(config.discord.token);
// Setup SQlite DB
const db = new sqlite3.Database("channels.db", (err) => {
if (err) {
@ -40,8 +43,9 @@ const db = new sqlite3.Database("channels.db", (err) => {
}
console.log(`${colors.cyan("[INFO]")} Connected to the database`);
// Create tables if they dont exist
db.run(`CREATE TABLE IF NOT EXISTS channels (channelid TEXT, iemchannel TEXT, custommessage TEXT, minPriority INTEGER, "filter" TEXT, filterevt TEXT);`);
db.run(`CREATE TABLE IF NOT EXISTS channels (channelid TEXT, iemchannel TEXT, custommessage TEXT, minPriority INTEGER, "filter" TEXT, filterEvt TEXT);`);
db.run(`CREATE TABLE IF NOT EXISTS userAlerts (userid TEXT, iemchannel TEXT, filter TEXT, filterEvt TEXT, minPriority INT, custommessage TEXT);`);
db.run(`ALTER TABLE channels RENAME COLUMN filterevt TO filterEvt;`)
});
@ -85,7 +89,17 @@ const getUniqueChannels = function () {
if (err) {
console.error(err.message);
}
resolve(rows.length);
// Go through channels. and get number of unique guilds
const guilds = [];
rows.forEach((row) => {
const channel = discord.channels.cache.get(row.channelid);
if (!channel) return;
if (!guilds.includes(channel.guild.id)) {
guilds.push(channel.guild.id);
}
});
resolve({ channels: rows.length, guilds: guilds.length });
});
});
}
@ -250,6 +264,24 @@ var errCount = 0;
const curUUID = generateUUID();
// nwrstreams setup
// get icecast json data
const fetchNWRstreams = () => {
fetch("https://icestats.weatherradio.org/").then((res) => {
res.json().then((json) => {
json.icestats.source.forEach((source) => {
nwrstreams.callsigns[source.server_name] = source.listenurl;
});
});
console.log(`${colors.cyan("[INFO]")} Fetched NWR streams`);
}).catch((err) => {
console.error(err);
});
}
fetchNWRstreams();
setInterval(fetchNWRstreams, 5 * 60 * 1000); // Every 5 minutes
const xmpp = client({
service: "xmpp://conference.weather.im",
domain: "weather.im",
@ -276,6 +308,8 @@ xmpp.on("offline", () => {
})
});
var restartTimer = null;
xmpp.on("stanza", (stanza) => {
// Debug stuff
if (config.debug >= 2) console.log(`${colors.magenta("[DEBUG]")} Stanza: ${stanza.toString()}`);
@ -301,6 +335,11 @@ xmpp.on("stanza", (stanza) => {
}
// Get new messages and log them, ignore old messages
if (stanza.is("message") && stanza.attrs.type === "groupchat") {
clearTimeout(restartTimer)
restartTimer = setTimeout(() => {
console.log(`${colors.red("[FATAL]")} No messages from weather.im in 10 minutes, restarting!!!!!!!!!!!`)
process.exit(1)
}, 600000)
// Stops spam from getting old messages
if (startup) return;
// Get channel name
@ -321,6 +360,16 @@ xmpp.on("stanza", (stanza) => {
evt = { name: "Unknown", priority: 3 }
console.log(`${colors.red("[ERROR]")} Unknown event type: ${product_id.pil.substring(0, 3)}. Fix me`);
console.log(`${colors.magenta("[DEBUG]")} ${bodyData.string}`)
const logChannel = discord.guilds.cache.get(config.discord.mainGuild).channels.cache.get(config.discord.logChannel);
logChannel.send({
embeds: [
{
title: "Unknown Event Type",
description: `Unknown event type: ${product_id.pil.substring(0, 3)}. Please check the logs for more details.`,
color: 0xff0000
}
]
});
}
evt.code = product_id.pil.substring(0, 3);
@ -328,13 +377,14 @@ xmpp.on("stanza", (stanza) => {
const now = new Date();
const diff = (now - product_id.timestamp) / 1000 / 60;
if (diff > 3) return;
if (config.debug >= 1) console.log(`${colors.magenta("[DEBUG]")} New message from ${fromChannel}`);
// if (config.debug >= 1) console.log(`${colors.magenta("[DEBUG]")} New message from ${fromChannel}`);
console.log(`${colors.cyan("[INFO]")} ${getWFOByRoom(fromChannel).location} - ${evt.text} - ${product_id.timestamp}`);
messages++;
// Handle NTFY
if (config.ntfy.enabled) {
if (config.debug >= 1) console.log(`${colors.magenta("[DEBUG]")} Sending NTFY for ${config.ntfy.prefix}${fromChannel}`)
//if (config.debug >= 1) console.log(`${colors.magenta("[DEBUG]")} Sending NTFY for ${config.ntfy.prefix}${fromChannel}`)
ntfyBody = {
"topic": `${config.ntfy.prefix}${fromChannel}`,
"message": bodyData.string,
@ -352,7 +402,7 @@ xmpp.on("stanza", (stanza) => {
'Authorization': `Bearer ${config.ntfy.token}`
}
}).then((res) => {
if (config.debug >= 1) console.log(`${colors.magenta("[DEBUG]")} NTFY sent for ${config.ntfy.prefix}${fromChannel} with status ${res.status} ${res.statusText}`);
//if (config.debug >= 1) console.log(`${colors.magenta("[DEBUG]")} NTFY sent for ${config.ntfy.prefix}${fromChannel} with status ${res.status} ${res.statusText}`);
if (res.status !== 200) console.log(`${colors.red("[ERROR]")} NTFY failed for ${config.ntfy.prefix}${fromChannel} with status ${res.status} ${res.statusText}`);
@ -392,7 +442,7 @@ xmpp.on("stanza", (stanza) => {
{
type: 2,
style: 1,
custom_id: product_id_raw,
custom_id: `product|${product_id_raw}`,
label: "Product Text",
emoji: {
name: "📄"
@ -408,7 +458,7 @@ xmpp.on("stanza", (stanza) => {
console.log(`${colors.red("[ERROR]")} ${err.message}`);
}
if (!rows) return; // No channels to alert
rows.forEach((row) => {
rows.forEach(async (row) => {
// Get Filters as arrays
if (!row.filterEvt) row.filterEvt = "";
if (!row.filter) row.filter = "";
@ -434,6 +484,54 @@ xmpp.on("stanza", (stanza) => {
console.error(err);
}).then((msg) => {
if (msg.channel.type === Discord.ChannelType.GuildAnnouncement) msg.crosspost();
}).catch((err) => {
console.log(`${colors.yellow("[WARN]")} Failed to send message to ${channel.guild.name}/${channel.name} (${channel.guild.id}/${channel.id})`);
const logChannel = discord.guilds.cache.get(config.discord.mainGuild).channels.cache.get(config.discord.logChannel);
logChannel.send({
embeds: [
{
title: "Failed to send message",
description: `There is likely an issue with permissions. Please notify the server owner if possible.
Guild: ${channel.guild.name} (${channel.guild.id})
Channel: ${channel.name} (${channel.id})
Guild Owner: <@${channel.guild.ownerId}> (${channel.guild.ownerId})
Sub Info: \`\`\`json\n${JSON.stringify(row)}\`\`\``,
color: 0xff0000
}
]
});
discord.users.fetch(channel.guild.ownerId).then((user) => {
user.send({
embeds: [
{
title: "Issue with your subscribed channel.",
description: `There is likely an issue with permissions. Please check that I can send messages in <#${channel.id}>\nYour subscription has been removed, and you will need to resubscribe to get alerts.`,
color: 0xff0000,
fields: [
{
name: "Guild",
value: `${channel.guild.name} (${channel.guild.id})`
},
{
name: "Channel",
value: `${channel.name} (${channel.id})`
}
]
}
]
}).catch((err) => {
console.log(`${colors.red("[ERROR]")} Failed to send message to ${channel.guild.ownerId}`);
}).then(() => {
if (channel.guildId == config.discord.mainGuild) return;
db.run(`DELETE FROM channels WHERE channelid = ? AND iemchannel = ?`, [channel.id, fromChannel], (err) => {
if (err) {
console.error(err.message);
}
console.log(`${colors.cyan("[INFO]")} Deleted channel ${channel.id} from database`);
});
})
});
});
});
}).catch((err) => {
@ -479,6 +577,27 @@ xmpp.on("stanza", (stanza) => {
thisMsg.content = row.custommessage || null;
user.send(thisMsg).catch((err) => {
console.error(err);
}).catch((err) => {
console.log(`${colors.yellow("[WARN]")} Failed to send message to ${user.tag} (${user.id})`);
const logChannel = discord.guilds.cache.get(config.discord.mainGuild).channels.cache.get(config.discord.logChannel);
logChannel.send({
embeds: [
{
title: "Failed to send DM",
description: `User may have DMs disabled, or bot doesnt' share a server anymore!.
User: ${user.tag} (${user.id})
Sub Info: \`\`\`json\n${JSON.stringify(row)}\`\`\``,
color: 0xff0000
}
]
}).then(() => {
db.run(`DELETE FROM userAlerts WHERE userid = ? AND iemchannel = ?`, [user.id, fromChannel], (err) => {
if (err) {
console.error(err.message);
}
console.log(`${colors.cyan("[INFO]")} Deleted user ${user.id} from database`);
});
})
});
});
}).catch((err) => {
@ -585,20 +704,19 @@ discord.on('ready', async () => {
commands = require("./data/commands.json");
// Add dynamic commands (based on datas files)
satCommand = {
"name": "sattelite",
"description": "Get the latest sattelite images from a given sattelite",
"name": "satellite",
"description": "Get the latest satellite images from a given satellite",
"options": [
{
"name": "sattelite",
"description": "The sattelite to get images from",
"name": "satellite",
"description": "The satellite to get images from",
"type": 3,
"required": true,
"choices": []
}
]
}
for (const key in sattelites) {
// Push the key to the choices array
for (const key in satellites) {
satCommand.options[0].choices.push({
"name": key,
"value": key
@ -673,22 +791,16 @@ discord.on('ready', async () => {
"description": "The URL of the stream to play",
"type": 3,
"required": true,
"choices": []
"autocomplete": true
}
]
}
for (const key in nwrstreams.callsigns) {
nwrplayCommand.options[0].choices.push({
"name": key,
"value": key
});
}
commands.push(nwrplayCommand);
}
await (async () => {
try {
//Global
if (config.debug >= 1) console.log(`${colors.magenta("[DEBUG]")} Registering global commands: ${JSON.stringify(commands, null, 2)}`);
if (config.debug >= 1) console.log(`${colors.magenta("[DEBUG]")} Registering global commands`);
await rest.put(Routes.applicationCommands(discord.user.id), { body: commands })
} catch (error) {
console.error(error);
@ -753,21 +865,26 @@ discord.on("interactionCreate", async (interaction) => {
filterEvt = interaction.options.getString("filterevt") || null;
message = interaction.options.getString("message") || null;
if (interaction.inGuild()) {
db.get(`SELECT * FROM channels WHERE channelid = ? AND iemchannel = ?`, [interaction.channel.id, room], (err, row) => {
if (err) {
console.error(err.message);
interaction.reply({ content: "Failed to subscribe to room", ephemeral: true });
} else if (row) {
return interaction.reply({ content: `Already subscribed to \`${getWFOByRoom(room).location}\`\nIf you want to update a subscribtion, please unsubscribe and resubscribe. This will be made a command eventually.`, ephemeral: true });
}
db.run(`INSERT INTO channels (channelid, iemchannel, custommessage, filter, filterevt, minPriority) VALUES (?, ?, ?, ? ,? ,?)`, [interaction.channel.id, room, message, filter, filterEvt, minPriority], (err) => {
interaction.channel.send("Permission check").then((msg) => {
msg.delete();
db.get(`SELECT * FROM channels WHERE channelid = ? AND iemchannel = ?`, [interaction.channel.id, room], (err, row) => {
if (err) {
console.error(err.message);
interaction.reply({ content: "Failed to subscribe to room", ephemeral: true });
} else {
interaction.reply({ content: `Subscribed to \`${getWFOByRoom(room).location}\``, ephemeral: true });
} else if (row) {
return interaction.reply({ content: `Already subscribed to \`${getWFOByRoom(room).location}\`\nIf you want to update a subscribtion, please unsubscribe and resubscribe. This will be made a command eventually.`, ephemeral: true });
}
db.run(`INSERT INTO channels (channelid, iemchannel, custommessage, filter, filterEvt, minPriority) VALUES (?, ?, ?, ? ,? ,?)`, [interaction.channel.id, room, message, filter, filterEvt, minPriority], (err) => {
if (err) {
console.error(err.message);
interaction.reply({ content: "Failed to subscribe to room", ephemeral: true });
} else {
interaction.reply({ content: `Subscribed to \`${getWFOByRoom(room).location}\``, ephemeral: true });
}
});
});
}).catch((err) => {
interaction.reply({ content: "Failed to subscribe to room. Bot does not have send message permissions here!", ephemeral: true });
});
} else { // We're in a DM
db.get(`SELECT * FROM userAlerts WHERE userid = ? AND iemchannel = ?`, [interaction.user.id, room], (err, row) => {
@ -891,7 +1008,8 @@ discord.on("interactionCreate", async (interaction) => {
channels = row.count
await getUniqueChannels().then((unique) => {
uniques = unique;
uniques = unique.channels;
guilds = unique.guilds;
});
discord.users.fetch("289884287765839882").then((chrisUser) => {
const embed = {
@ -916,12 +1034,12 @@ discord.on("interactionCreate", async (interaction) => {
},
{
name: "Subscribed Rooms",
value: channels.toLocaleString(),
value: `${channels.toLocaleString()}`,
inline: true
},
{
name: "Unique Channels",
value: uniques.toLocaleString(),
value: `${uniques.toLocaleString()} in ${guilds} guilds.`,
inline: true
}
],
@ -1237,48 +1355,42 @@ discord.on("interactionCreate", async (interaction) => {
console.error(err);
});
break;
case "sattelite": // Get satellite images
sat = interaction.options.getString("sattelite");
if (!sattelites[sat]) return interaction.reply({ content: "Invalid satellite", ephemeral: true });
case "satellite": // Get satellite images
sat = interaction.options.getString("satellite");
if (!satellites[sat]) return interaction.reply({ content: "Invalid satellite", ephemeral: true });
// Fetch all the images
await interaction.deferReply();
imageBuffers = {};
embeds = [];
files = [];
sattelites[sat].forEach(async (imgData) => {
// Get a buffer for the data, and put that in imageBuffers with the "name" as the key
fetch(imgData.url).then((res) => {
if (res.status !== 200) {
interaction.editReply({ content: "Failed to get satellite images", ephemeral: true });
return;
}
res.buffer().then((buffer) => {
imageBuffers[imgData.name] = buffer;
files.push({
attachment: buffer,
name: `${imgData.name}.jpg`
});
embeds.push({
title: `${sat} ${imgData.name}`,
image: {
url: `attachment://${imgData.name}.jpg`
productOptions = []
await (() => {
for (const key in satellites[sat].products) {
// make a discord customid safe id for the product name, add it to the satellites object
satellites[sat].products[key].customid = key.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
productOptions.push({
label: key,
value: satellites[sat].products[key].customid
})
}
console.log(JSON.stringify(productOptions, null, 2))
})();
satMessages[interaction.id] = {
sat
}
await interaction.reply({
content: "Choose a product",
components: [
{
type: 1,
components: [
{
type: 3,
custom_id: `satproduct|${interaction.id}`,
label: "Product",
// map options to product names
options: productOptions
}
});
// Check if we have all the images
if (Object.keys(imageBuffers).length === sattelites[sat].length) {
// Send the images
interaction.editReply({
embeds,
files
});
}
});
}).catch((err) => {
interaction.editReply({ content: "Failed to get satellite images", ephemeral: true });
console.log(`${colors.red("[ERROR]")} Failed to get satellite images: ${err.message}`);
console.error(err);
});
});
]
}
]
})
break;
case "forecast":
@ -1299,51 +1411,198 @@ discord.on("interactionCreate", async (interaction) => {
}
case Discord.InteractionType.MessageComponent:
if (interaction.customId) {
const product_id = interaction.customId;
url = `https://mesonet.agron.iastate.edu/api/1/nwstext/${product_id}`;
await interaction.deferReply({ ephemeral: true });
fetch(url).then((res) => {
if (res.status !== 200) {
interaction.reply({ content: "Failed to get product text", ephemeral: true });
return;
if (!interaction.customId) return;
switch (interaction.customId.split("|")[0]) {
case "product":
if (interaction.customId) {
const product_id = interaction.customId.split("|")[1];
url = `https://mesonet.agron.iastate.edu/api/1/nwstext/${product_id}`;
await interaction.deferReply({ ephemeral: true });
fetch(url).then((res) => {
if (res.status !== 200) {
interaction.reply({ content: "Failed to get product text", ephemeral: true });
return;
}
// Retruns raw text, paginate it into multiple embeds if needed
res.text().then(async (text) => {
const pages = text.match(/[\s\S]{1,1900}(?=\n|$)/g);
// const embeds = pages.map((page, ind) => ({
// title: `Product Text for ${product_id} Pg ${ind + 1}/${pages.length}`,
// description: `\`\`\`${page}\`\`\``,
// color: 0x00ff00
// }));
const messages = pages.map((page, ind) => {
return `\`\`\`${page}\`\`\``
})
messages.forEach(async (message) => {
interaction.followUp({ content: message, ephemeral: true });
})
});
}).catch((err) => {
interaction.reply({ content: "Failed to get product text", ephemeral: true });
console.log(`${colors.red("[ERROR]")} Failed to get product text: ${err.message}`);
});
}
// Retruns raw text, paginate it into multiple embeds if needed
res.text().then(async (text) => {
const pages = text.match(/[\s\S]{1,1900}(?=\n|$)/g);
// const embeds = pages.map((page, ind) => ({
// title: `Product Text for ${product_id} Pg ${ind + 1}/${pages.length}`,
// description: `\`\`\`${page}\`\`\``,
// color: 0x00ff00
// }));
const messages = pages.map((page, ind) => {
return `\`\`\`${page}\`\`\``
})
messages.forEach(async (message) => {
interaction.followUp({ content: message, ephemeral: true });
})
break;
case "satproduct":
satData = satMessages[interaction.customId.split("|")[1]];
sat = satData.sat
product = interaction.values[0];
// find the original product name
product_name = Object.keys(satellites[sat].products).find(key => satellites[sat].products[key].customid === product);
imageOptions = []
satMessages[interaction.customId.split("|")[1]] = {
sat,
product,
product_name,
images: {}
}
await (() => {
// for key, value in satellites[sat].products[product_name]
console.log(product_name)
for (const key in satellites[sat].products[product_name]) {
// make a discord customid safe id for the product name, add it to the satellites object
//console.log(satellites[sat].products[product_name])
if (key === "customid") continue;
satMessages[interaction.customId.split("|")[1]].images[key.replace(/[^a-zA-Z0-9]/g, "").toLowerCase()] = satellites[sat].products[product_name][key];
imageOptions.push({
label: key,
value: key.replace(/[^a-zA-Z0-9]/g, "").toLowerCase()
})
}
})();
interaction.deferReply();
await interaction.message.edit({
content: "Choose an image to view",
components: [
{
type: 1,
components: [
{
type: 3,
custom_id: `satproduct2|${interaction.customId.split("|")[1]}`,
label: "Image",
// map options to product names
options: imageOptions
}
]
}
]
}).then(() => {
interaction.deleteReply();
});
}).catch((err) => {
interaction.reply({ content: "Failed to get product text", ephemeral: true });
console.log(`${colors.red("[ERROR]")} Failed to get product text: ${err.message}`);
});
break;
case "satproduct2":
satData = satMessages[interaction.customId.split("|")[1]];
sat = satData.sat;
product = satData.product;
product_name = satData.product_name;
image = interaction.values[0];
url = satData.images[image];
// get filename from url
filename = url.split("/").pop();
interaction.deferReply();
// Get the image
fetch(url).then((res) => {
if (res.status !== 200) {
interaction.message.edit({ content: "Failed to get image", ephemeral: true }).then(() => {
interaction.deleteReply();
});
return;
}
embeds = [];
files = [];
res.buffer().then(async (buffer) => {
files.push({
attachment: buffer,
name: filename
})
embeds.push({
title: `${sat}/${product_name}/${image}`,
image: {
url: `attachment://${filename}`
},
color: 0x00ff00
});
interaction.message.edit({
embeds,
files,
components: [],
content: null
}).then(() => {
interaction.deleteReply();
});
}
);
}).catch((err) => {
interaction.message.edit({ content: "Failed to get image", ephemeral: true }).then(() => {
interaction.deleteReply();
});
console.log(`${colors.red("[ERROR]")} Failed to get image: ${err.stack}`);
});
break;
}
break;
case Discord.InteractionType.ApplicationCommandAutocomplete:
//map nwrstreams
if (interaction.commandName === "nwrplay") {
let callsignSearch = interaction.options.getString("callsign");
let callsigns = Object.keys(nwrstreams.callsigns);
let results = callsigns.filter((callsign) => callsign.toLowerCase().includes(callsignSearch.toLowerCase()));
if (results.length > 25) {
results = results.slice(0, 25);
}
interaction.respond(results.map((callsign) => ({ name: callsign, value: callsign })));
}
break;
}
});
discord.on("guildCreate", (guild) => {
discord.on("guildCreate", async (guild) => {
let logs = await guild.fetchAuditLogs()
logs = logs.entries.filter(e => e.action === Discord.AuditLogEvent.BotAdd)
let user = logs.find(l => l.target?.id === discord.user.id)?.executor
// Get the main guild
const myGuild = discord.guilds.cache.get(config.discord.mainGuild);
// Get the log channel
const channel = myGuild.channels.cache.get(config.discord.logChannel);
// Send a message to the log channel
let invite = await discord.guilds.cache.get(config.discord.mainGuild).channels.cache.get(config.discord.inviteChannel).createInvite();
user.send({
embeds: [{
description: `Thanks for adding ${discord.user.username}!\nIf you have **ANY** questions, comments, suggestions, bug reports, etc, please feel free to throw it by us in our support server!\n\nTo get started use \`/subscribe\` to get alerts!`,
color: 0x00ff00
}],
components: [
{
type: Discord.ComponentType.ActionRow,
components: [
{
type: Discord.ComponentType.Button,
url: `https://discord.gg/${invite.code}`,
style: Discord.ButtonStyle.Link,
emoji: "",
label: "IEM Alerter Support Server"
}
]
}
]
}).catch((err) => {
console.log(`${colors.red("[ERROR]")} Failed to send message to user ${user.id}: ${err.message}`);
})
channel.send({
embeds: [
{
description: `I joined \`${guild.name}\``,
fields: [
{
"name": "User",
"value": `<@${user.id}> (@${user.username}) ${user.displayName}`
}
],
color: 0x00ff00
}
]
@ -1368,7 +1627,7 @@ discord.on("guildDelete", (guild) => {
})
process.on("unhandledRejection", (error, promise) => {
console.log(`${colors.red("[ERROR]")} Unhandled Rejection @ ${promise}: ${error}`);
console.log(`${colors.red("[ERROR]")} Unhandled Rejection @ ${promise}: ${error.stack}`);
// create errors folder if it doesnt exist
if (!fs.existsSync("./error")) {
fs.mkdirSync("./error");
@ -1411,5 +1670,7 @@ process.on("uncaughtException", (error) => {
return;
});
// Login to discord
discord.login(config.discord.token);

View file

@ -14,7 +14,7 @@
"@xmpp/client": "^0.13.1",
"@xmpp/debug": "^0.13.0",
"colors": "^1.4.0",
"discord.js": "^14.15.2",
"discord.js": "14.14.1",
"geolib": "^3.3.4",
"html-entities": "^2.5.2",
"jimp": "^0.22.12",