diff --git a/data/commands.json b/data/commands.json index eba77c6..14f9c71 100644 --- a/data/commands.json +++ b/data/commands.json @@ -297,5 +297,19 @@ "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 + } + ] } ] \ No newline at end of file diff --git a/index.js b/index.js index c2a6323..08888db 100644 --- a/index.js +++ b/index.js @@ -358,6 +358,14 @@ Image and product text are optional, but if they exist, they will be in the data var sent = {} 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 text = data.data.productText; const product_id_raw = data.data.raw @@ -448,59 +456,34 @@ const handleDiscord = function (data, randStr) { } } }, 10 * 60 * 1000); - channel.send(thisMsg).catch((err) => { - console.error(err); - }).then((msg) => { - if (msg.channel.type === Discord.ChannelType.GuildAnnouncement) msg.crosspost(); - }).catch(() => { - 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`); + let errCount = 0; + function sendMessage() { + channel.send(thisMsg).catch((err) => { + console.error(err); + }).then((msg) => { + if (msg.channel.type === Discord.ChannelType.GuildAnnouncement) msg.crosspost(); + }).catch(() => { + 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); + logChannel.send({ + embeds: [ + { + title: "Failed to send message", + description: `Failed to send message after 3 attempts, skipping for now. Channel may be deleted or bot may have lost access.\n + Channel: ${channel.guild.name}/${channel.name} (${channel.guild.id}/${channel.id}) + Sub Info: \`\`\`json\n${JSON.stringify(row)}\`\`\``, + color: 0xff0000 + } + ] }); - }) + } }); - }); + } + sendMessage() }); }); @@ -543,32 +526,36 @@ const handleDiscord = function (data, randStr) { } } }, 10 * 60 * 1000); - + if (!filters.some((filter) => body.string.toLowerCase().includes(filter)) && !filters.some((filter) => text.toLowerCase().includes(filter))) return; thisMsg = JSON.parse(JSON.stringify(discordMsg)); thisMsg.content = row.custommessage || null; - user.send(thisMsg).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 - } - ] - }) - db.run(`DELETE FROM userAlerts WHERE userid = ?`, [row.userid], (err) => { - if (err) { - console.error(err.message); + let errCount = 0; + + function sendMessage() { + user.send(thisMsg).catch((err) => { + 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); + 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 + } + ] + }); } - 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(() => { - 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) => { 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}`); }); 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: if (!interaction.customId) return; diff --git a/testmsg.json b/testmsg.json new file mode 100644 index 0000000..a9bb6cb --- /dev/null +++ b/testmsg.json @@ -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" + } +} + diff --git a/wstest.js b/wstest.js new file mode 100644 index 0000000..f3e0035 --- /dev/null +++ b/wstest.js @@ -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); +});