Compare commits

..

5 commits

4 changed files with 170 additions and 72 deletions

View file

@ -297,5 +297,19 @@
"required": false "required": false
} }
] ]
},
{
"name": "devtest",
"description": "[BOT OWNER ONLY] Test the bot with a sample message",
"default_member_permissions": 0,
"type": 1,
"options": [
{
"name": "randstr",
"description": "override random string in test message for testing",
"type": 3,
"required": false
}
]
} }
] ]

102
index.js
View file

@ -358,6 +358,14 @@ Image and product text are optional, but if they exist, they will be in the data
var sent = {} var sent = {}
const handleDiscord = function (data, randStr) { const handleDiscord = function (data, randStr) {
/*
Example data object:
{
type: 'iem-message',
data: {
channel: { room: 'botstalk', location: 'All_Bots_Talk' },
*/
const rawBody = data.data.body; const rawBody = data.data.body;
const text = data.data.productText; const text = data.data.productText;
const product_id_raw = data.data.raw const product_id_raw = data.data.raw
@ -448,59 +456,34 @@ const handleDiscord = function (data, randStr) {
} }
} }
}, 10 * 60 * 1000); }, 10 * 60 * 1000);
let errCount = 0;
function sendMessage() {
channel.send(thisMsg).catch((err) => { channel.send(thisMsg).catch((err) => {
console.error(err); console.error(err);
}).then((msg) => { }).then((msg) => {
if (msg.channel.type === Discord.ChannelType.GuildAnnouncement) msg.crosspost(); if (msg.channel.type === Discord.ChannelType.GuildAnnouncement) msg.crosspost();
}).catch(() => { }).catch(() => {
console.log(`${colors.yellow("[WARN]")} Failed to send message to ${channel.guild.name}/${channel.name} (${channel.guild.id}/${channel.id})`); console.log(`${colors.yellow("[WARN]")} Failed to send message to ${channel.guild.name}/${channel.name} (${channel.guild.id}/${channel.id})`);
if (errCount < 3) {
errCount++;
setTimeout(sendMessage, 5000); // Retry after 5 seconds
} else {
const logChannel = discord.guilds.cache.get(config.discord.mainGuild).channels.cache.get(config.discord.logChannel); const logChannel = discord.guilds.cache.get(config.discord.mainGuild).channels.cache.get(config.discord.logChannel);
logChannel.send({ logChannel.send({
embeds: [ embeds: [
{ {
title: "Failed to send message", title: "Failed to send message",
description: `There is likely an issue with permissions. Please notify the server owner if possible. description: `Failed to send message after 3 attempts, skipping for now. Channel may be deleted or bot may have lost access.\n
Guild: ${channel.guild.name} (${channel.guild.id}) Channel: ${channel.guild.name}/${channel.name} (${channel.guild.id}/${channel.id})
Channel: ${channel.name} (${channel.id})
Guild Owner: <@${channel.guild.ownerId}> (${channel.guild.ownerId})
Sub Info: \`\`\`json\n${JSON.stringify(row)}\`\`\``, Sub Info: \`\`\`json\n${JSON.stringify(row)}\`\`\``,
color: 0xff0000 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})`
} }
] });
} }
] sendMessage()
}).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`);
});
})
});
});
}); });
}); });
@ -548,8 +531,15 @@ const handleDiscord = function (data, randStr) {
if (!filters.some((filter) => body.string.toLowerCase().includes(filter)) && !filters.some((filter) => text.toLowerCase().includes(filter))) return; if (!filters.some((filter) => body.string.toLowerCase().includes(filter)) && !filters.some((filter) => text.toLowerCase().includes(filter))) return;
thisMsg = JSON.parse(JSON.stringify(discordMsg)); thisMsg = JSON.parse(JSON.stringify(discordMsg));
thisMsg.content = row.custommessage || null; thisMsg.content = row.custommessage || null;
let errCount = 0;
function sendMessage() {
user.send(thisMsg).catch((err) => { user.send(thisMsg).catch((err) => {
console.log(`${colors.yellow("[WARN]")} Failed to send message to ${user.tag} (${user.id})`); console.log(`${colors.yellow("[WARN]")} Failed to send message to ${user.tag} (${user.id})`);
if (errCount < 3) {
errCount++;
setTimeout(sendMessage, 5000); // Retry after 5 seconds
} else {
const logChannel = discord.guilds.cache.get(config.discord.mainGuild).channels.cache.get(config.discord.logChannel); const logChannel = discord.guilds.cache.get(config.discord.mainGuild).channels.cache.get(config.discord.logChannel);
logChannel.send({ logChannel.send({
embeds: [ embeds: [
@ -561,14 +551,11 @@ const handleDiscord = function (data, randStr) {
color: 0xff0000 color: 0xff0000
} }
] ]
})
db.run(`DELETE FROM userAlerts WHERE userid = ?`, [row.userid], (err) => {
if (err) {
console.error(err.message);
}
console.log(`${colors.cyan("[INFO]")} Deleted all subs for user ${user.id} from database`);
}); });
}) }
});
}
sendMessage();
}); });
}); });
}; };
@ -1429,13 +1416,42 @@ discord.on("interactionCreate", async (interaction) => {
}] }]
} }
bugReportChannel.send(report).then(() => { bugReportChannel.send(report).then(() => {
interaction.reply({ content: "Bug report sent! Thank you for your feedback.", embeds: report.embeds}); interaction.reply({ content: "Bug report sent! Thank you for your feedback.", embeds: report.embeds });
}).catch((err) => { }).catch((err) => {
interaction.reply({ content: "Failed to send bug report. Please try again later.", ephemeral: true }); interaction.reply({ content: "Failed to send bug report. Please try again later.", ephemeral: true });
console.error(`${colors.red("[ERROR]")} Failed to send bug report: ${err.message}`); console.error(`${colors.red("[ERROR]")} Failed to send bug report: ${err.message}`);
}); });
break; break;
case "devtest":
if (interaction.user.id !== config.discord.owner) return interaction.reply({ content: "You are not the bot owner.", ephemeral: true });
// Test suite;
let output = "Dev Test Started:\n";
await interaction.reply({ content: output, ephemeral: true });
//1. Add current channel to db with a fake room (discordtest)
db.run(`INSERT INTO channels (channelid, iemchannel) VALUES (?, ?)`, [interaction.channel.id, "discordtest"], async (err) => {
if (err) {
console.error(err.message);
await interaction.editReply({ content: output + "Failed to add channel to database.", ephemeral: true });
return; // Stop the test. grr
} else {
output += "Added channel to database.\n";
interaction.editReply({ content: output, ephemeral: true });
//2. directly trigger handleDiscord with a fake message for the fake room.
let randStr = interaction.options.getString("randstr") || Math.random().toString(36).substring(2, 15)
handleDiscord(require("./testmsg.json"), randStr);
output += "Triggered handleDiscord with test message.\n";
interaction.editReply({ content: output, ephemeral: true });
setTimeout(() => {
// 3. At this point the dev needs to verify that the message sent, or didnt send if testing retry code. Just end at this point.
output += "Dev Test Completed. Please verify the message was sent correctly.";
interaction.editReply({ content: output, ephemeral: true });
}, 2500);
}
});
break;
} }
case Discord.InteractionType.MessageComponent: case Discord.InteractionType.MessageComponent:
if (!interaction.customId) return; if (!interaction.customId) return;

35
testmsg.json Normal file
View file

@ -0,0 +1,35 @@
{
"type": "iem-message",
"data": {
"channel": {
"room": "discordtest",
"location": "Discord Test Room"
},
"fromChannel": "discordtest",
"event": {
"text": "Severe Thunderstorm Warning",
"priority": 4,
"code": "SVR"
},
"body": {
"string": "!!!DISCORD TEST MESSAGE!!! DMX issues Severe Thunderstorm Warning (SVR) for Polk County until Jun 12, 16:15 UTC ",
"url": "https://mesonet.agron.iastate.edu/vtec/f/2026-O-NEW-KDMX-SV-W-0123_2026-06-12T1545Z"
},
"timestamp": "2026-06-12T15:45:00.000Z",
"wmo": "WUUS53",
"pil": "SVRDMX",
"station": "KDMX",
"product_data": {
"timestamp": "2026-06-12T15:45:00.000Z",
"originalTimestamp": "202606121545",
"station": "KDMX",
"wmo": "WUUS53",
"pil": "SVRDMX"
},
"raw": "202606121545-KDMX-WUUS53-SVRDMX",
"rawBody": "!!!DISCORD TEST MESSAGE!!! DMX issues Severe Thunderstorm Warning (SVR) for Polk County until Jun 12, 16:15 UTC https://mesonet.agron.iastate.edu/vtec/f/2026-O-NEW-KDMX-SV-W-0123_2026-06-12T1545Z",
"image": "https://mesonet.agron.iastate.edu/plotting/auto/plot/208/network:WFO::wfo:DMX::year:2026::phenomenav:SV::significancev:W::etn:123::valid:2026-06-12%201545::_r:86.png",
"productText": "!!!DISCORD TEST MESSAGE!!!\n\n796 \nWUUS53 KDMX 121545\nSVRDMX\nIAC153-121615-\n/O.NEW.KDMX.SV.W.0123.260612T1545Z-260612T1615Z/\nBULLETIN - IMMEDIATE BROADCAST REQUESTED\nSevere Thunderstorm Warning\nNational Weather Service Des Moines IA\n1045 AM CDT Fri Jun 12 2026\n\nThe National Weather Service in Des Moines has issued a\n\n* Severe Thunderstorm Warning for...\n Polk County in central Iowa...\n\n* Until 1115 AM CDT.\n\n* At 1045 AM CDT, a severe thunderstorm was located near Ankeny,\n moving east at 35 mph.\n\n* HAZARD...60 mph wind gusts and quarter size hail.\n\n$$\n"
}
}

33
wstest.js Normal file
View file

@ -0,0 +1,33 @@
const WebSocket = require('ws');
const config = require('./config.json');
const ws = new WebSocket(
config.WS_URL || "wss://iembot.dev/iem",
{
headers: {
"user-agent": "IEM-Alerter-DiscordBot/1.0 (+https://git.chrischro.me/iem-alerter/discord-bot; contact: me@ko4wal.radio)"
}
}
);
ws.on('open', () => {
console.log('Connected to WebSocket');
ws.send(JSON.stringify({ type: 'subscribe', channel: '*' }));
});
ws.on('message', (rawData) => {
try {
const data = JSON.parse(rawData);
console.log(JSON.stringify(data, null, 2));
} catch (err) {
console.log('Raw (non-JSON):', rawData.toString());
}
});
ws.on('close', () => {
console.log('WebSocket connection closed.');
});
ws.on('error', (err) => {
console.error('WebSocket error:', err);
});