diff --git a/index.js b/index.js index ab388a2..2f88830 100644 --- a/index.js +++ b/index.js @@ -28,14 +28,59 @@ app.ws('/iem', (ws, req) => { "uuid": curUUID, "host": hostname })); - wsConnections.push(ws); + sock = wsConnections.push({ ws, req, subs: [] }); ws.on('close', () => { console.log(`disconnected from ${req.ip}`); - wsConnections = wsConnections.filter((conn) => conn !== ws); + wsConnections = wsConnections.filter((conn) => conn.ws !== ws); }); ws.on('message', (msg) => { - if (msg === "ping") { - ws.send("pong") + const data = JSON.parse(msg); + if (!data.type) return; + switch (data.type) { + case "subscribe": + if (data.channel) { + if (!wsConnections[sock - 1].subs.includes(data.channel)) { + wsConnections[sock - 1].subs.push(data.channel); + ws.send(JSON.stringify({ + "type": "internal-response", + "code": 200, + "data": { + "message": `Subscribed to ${data.channel}` + } + })); + } else { + ws.send(JSON.stringify({ + "type": "internal-response", + "code": 409, + "data": { + "error": "Already subscribed to this channel." + } + })); + } + } + break; + case "unsubscribe": + if (data.channel) { + if (wsConnections[sock - 1].subs.includes(data.channel)) { + wsConnections[sock - 1].subs = wsConnections[sock - 1].subs.filter((sub) => sub !== data.channel); + ws.send(JSON.stringify({ + "type": "internal-response", + "code": 200, + "data": { + "message": `Unsubscribed from ${data.channel}` + } + })); + } else { + ws.send(JSON.stringify({ + "type": "internal-response", + "code": 404, + "data": { + "error": "Not subscribed to this channel." + } + })); + } + } + break; } }); }); @@ -228,13 +273,6 @@ 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.yellow("[WARN]")} Restarting XMPP connection...`); - xmpp.disconnect().then(() => { - xmpp.start(); - }); - }, 10000) // Stops spam from getting old messages if (startup) return; // Get channel name @@ -254,7 +292,6 @@ xmpp.on("stanza", (stanza) => { if (!evt) { 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: [ @@ -276,193 +313,68 @@ xmpp.on("stanza", (stanza) => { 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}`) - // ntfyBody = { - // "topic": `${config.ntfy.prefix}${fromChannel}`, - // "message": bodyData.string, - // "tags": [`Timestamp: ${product_id.timestamp}`, `Station: ${product_id.station}`, `WMO: ${product_id.wmo}`, `PIL: ${product_id.pil}`, `Channel: ${fromChannel}`], - // "priority": evt.priority, - // "actions": [{ "action": "view", "label": "Product", "url": bodyData.url }, { "action": "view", "label": "Product Text", "url": `https://mesonet.agron.iastate.edu/api/1/nwstext/${product_id_raw}` }] - // } - // if (stanza.getChild("x").attrs.twitter_media) { - // ntfyBody.attach = stanza.getChild("x").attrs.twitter_media; - // } - // fetch(config.ntfy.server, { - // method: 'POST', - // body: JSON.stringify(ntfyBody), - // headers: { - // '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 (res.status !== 200) console.log(`${colors.red("[ERROR]")} NTFY failed for ${config.ntfy.prefix}${fromChannel} with status ${res.status} ${res.statusText}`); - - - // }).catch((err) => { - // console.error(err) - // }) - // } - - // Send discord msg - // let embed = { - // description: ` ${bodyData.string}`, - // color: parseInt(config.priorityColors[evt.priority].replace("#", ""), 16) || 0x000000, - // timestamp: product_id.timestamp, - // footer: { - // text: `Station: ${product_id.station} PID: ${product_id_raw} Channel: ${fromChannel}` - // } - // } - // if (stanza.getChild("x").attrs.twitter_media) { - // embed.image = { - // url: stanza.getChild("x").attrs.twitter_media - // } - // } - - // let discordMsg = { - // embeds: [embed], - // components: [ - // { - // type: 1, - // components: [ - // { - // type: 2, - // label: "Product", - // style: 5, - // url: bodyData.url - // }, - // { - // type: 2, - // style: 1, - // custom_id: `product|${product_id_raw}`, - // label: "Product Text", - // emoji: { - // name: "📄" - // } - // } - // ] - // } - // ] - // } - // // Discord Channel Handling - // db.all(`SELECT * FROM channels WHERE iemchannel = ?`, [fromChannel], (err, rows) => { - // if (err) { - // console.log(`${colors.red("[ERROR]")} ${err.message}`); - // } - // if (!rows) return; // No channels to alert - // rows.forEach(async (row) => { - // // Get Filters as arrays - // if (!row.filterEvt) row.filterEvt = ""; - // if (!row.filter) row.filter = ""; - // let filterEvt = row.filterEvt.toLowerCase().split(","); - // let filters = row.filter.toLowerCase().split(","); - // if (evt.priority < row.minPriority) return; - // // If the event type is not in th filter, ignore it. Make sure filterEvt isnt null - // if (!filterEvt[0]) filterEvt = []; - // if (!filterEvt.includes(evt.code.toLowerCase()) && !filterEvt.length == 0) return; - - // let channel = discord.channels.cache.get(row.channelid); - // if (!channel) return console.log(`${colors.red("[ERROR]")} Channel ${row.channelid} not found`); - - // // fetch the product text - // trySend = () => { - // fetch(`https://mesonet.agron.iastate.edu/api/1/nwstext/${product_id_raw}`).then((res) => { - // // If neither the body nor the product text contains the filter, ignore it - // res.text().then((text) => { - // if (!filters.some((filter) => body.toLowerCase().includes(filter)) && !filters.some((filter) => text.toLowerCase().includes(filter))) return; - // thisMsg = JSON.parse(JSON.stringify(discordMsg)); - // thisMsg.content = row.custommessage || null; - // channel.send(thisMsg).catch((err) => { - // 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) => { - // setTimeout(() => { - // console.log(`${colors.red("[ERROR]")} Failed to fetch product text, retrying... ${err}`) - // trySend(); - // }) - // }); - // } - // trySend(); - // }); - // }); - - // Handle WebSocket if (wsConnections.length > 0) { - wsConnections.forEach((ws) => { - ws.send(JSON.stringify({ - "type": "iem-message", - "data": { - "channel": getWFOByRoom(fromChannel), - "event": evt, - "body": bodyData.string, - "timestamp": product_id.timestamp, - "wmo": product_id.wmo, - "pil": product_id.pil, - "station": product_id.station, - "raw": product_id_raw, - "rawBody": body, - "image": stanza.getChild("x").attrs.twitter_media || null - } - })); + wsConnections.forEach((connection) => { + if (connection.subs.includes(fromChannel) || connection.subs.includes("*")) { + connection.ws.send(JSON.stringify({ + "type": "iem-message", + "data": { + "channel": getWFOByRoom(fromChannel), + "event": evt, + "body": bodyData.string, + "timestamp": product_id.timestamp, + "wmo": product_id.wmo, + "pil": product_id.pil, + "station": product_id.station, + "raw": product_id_raw, + "rawBody": body, + "image": stanza.getChild("x").attrs.twitter_media || null + } + })); + } }); } } }); +xmpp.on("status", (status) => { + console.log(`${colors.cyan("[INFO]")} XMPP Status: ${status}`); + // Broadcast a message to all connected WebSocket clients + wsConnections.forEach((connection) => { + if (connection.ws.readyState === 1) { // Ensure the socket is open + connection.ws.send(JSON.stringify({ + "type": "xmpp-status", + "status": status + })); + } + }); +}); + +xmpp.reconnect.on("reconnecting", () => { + console.log(`${colors.yellow("[WARN]")} XMPP Reconnecting...`); + wsConnections.forEach((connection) => { + if (connection.ws.readyState === 1) { // Ensure the socket is open + connection.ws.send(JSON.stringify({ + "type": "xmpp-reconnect", + "status": "reconnecting" + })); + } + }); +}) + +xmpp.reconnect.on("reconnected", () => { + console.log(`${colors.green("[INFO]")} XMPP Reconnected`); + wsConnections.forEach((connection) => { + if (connection.ws.readyState === 1) { // Ensure the socket is open + connection.ws.send(JSON.stringify({ + "type": "xmpp-reconnect", + "status": "reconnected" + })); + } + }); +}) + const createDiscordEmbed = (data) => { const embed = { description: ` ${data.body}`, @@ -541,7 +453,6 @@ xmpp.on("close", () => { const start = () => { startup = true; xmpp.start().catch((err) => { - console.log("BWUH") console.log(`${colors.red("[ERROR]")} XMPP failed to start: ${err}.`); setTimeout(start, 5000); }); diff --git a/public/index.html b/public/index.html index 8713367..c2de236 100644 --- a/public/index.html +++ b/public/index.html @@ -1,10 +1,12 @@ + Document +
+ \ No newline at end of file