|
|
@ -3,17 +3,19 @@ const fs = require("fs");
|
|
|
|
const config = require("./config.json");
|
|
|
|
const config = require("./config.json");
|
|
|
|
const funcs = require("./funcs.js");
|
|
|
|
const funcs = require("./funcs.js");
|
|
|
|
const wfos = require("./data/wfos.json");
|
|
|
|
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 outlookURLs = require("./data/outlook.json");
|
|
|
|
const satellites = require("./data/satellites.json");
|
|
|
|
const satellites = require("./data/satellites.json");
|
|
|
|
const nwrstreams = { callsigns: {} };
|
|
|
|
const nwrstreams = {callsigns:{}};
|
|
|
|
const Jimp = require("jimp");
|
|
|
|
const Jimp = require("jimp");
|
|
|
|
|
|
|
|
const { client, xml } = require("@xmpp/client");
|
|
|
|
const fetch = require("node-fetch");
|
|
|
|
const fetch = require("node-fetch");
|
|
|
|
const html = require("html-entities")
|
|
|
|
const html = require("html-entities")
|
|
|
|
const Discord = require("discord.js");
|
|
|
|
const Discord = require("discord.js");
|
|
|
|
const dVC = require("@discordjs/voice");
|
|
|
|
const dVC = require("@discordjs/voice");
|
|
|
|
const colors = require("colors");
|
|
|
|
const colors = require("colors");
|
|
|
|
const sqlite3 = require("sqlite3").verbose();
|
|
|
|
const sqlite3 = require("sqlite3").verbose();
|
|
|
|
const WebSocket = require('ws');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
satMessages = {};
|
|
|
|
satMessages = {};
|
|
|
|
|
|
|
|
|
|
|
@ -258,7 +260,8 @@ var iem = []
|
|
|
|
var startup = true;
|
|
|
|
var startup = true;
|
|
|
|
var startTimestap = new Date();
|
|
|
|
var startTimestap = new Date();
|
|
|
|
var messages = 0;
|
|
|
|
var messages = 0;
|
|
|
|
|
|
|
|
var errCount = 0;
|
|
|
|
|
|
|
|
const curUUID = generateUUID();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// nwrstreams setup
|
|
|
|
// nwrstreams setup
|
|
|
@ -279,320 +282,340 @@ const fetchNWRstreams = () => {
|
|
|
|
fetchNWRstreams();
|
|
|
|
fetchNWRstreams();
|
|
|
|
setInterval(fetchNWRstreams, 5 * 60 * 1000); // Every 5 minutes
|
|
|
|
setInterval(fetchNWRstreams, 5 * 60 * 1000); // Every 5 minutes
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const xmpp = client({
|
|
|
|
|
|
|
|
service: "xmpp://conference.weather.im",
|
|
|
|
|
|
|
|
domain: "weather.im",
|
|
|
|
|
|
|
|
resource: `discord-weather-bot-${generateRandomString({ upper: true, lower: true, number: true }, 5)}`, // Weird fix to "Username already in use"
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
/* We are replacing the xmpp client with a websocket.
|
|
|
|
//debug(xmpp, true);
|
|
|
|
The new data will look something like this
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
type: 'iem-message',
|
|
|
|
|
|
|
|
data: {
|
|
|
|
|
|
|
|
channel: { room: 'botstalk', location: 'All_Bots_Talk' },
|
|
|
|
|
|
|
|
event: { priority: 1, text: 'Marine Weather Message', code: 'MWW' },
|
|
|
|
|
|
|
|
body: 'LCH updates Small Craft Advisory (expires Coastal waters from Cameron LA to High Island TX out 20 NM, Coastal waters from Intracoastal City to Cameron LA out 20 NM, Coastal waters from Lower Atchafalaya River to Intracoastal City LA out 20 NM [GM], continues Waters from Cameron LA to High Island TX from 20 to 60 NM, Waters from Intracoastal City to
|
|
|
|
|
|
|
|
Cameron LA from 20 to 60 NM, Waters from Lower Atchafalaya River to Intracoastal City LA from 20 to 60 NM [GM]) . ',
|
|
|
|
|
|
|
|
timestamp: '2025-04-20T15:12:22.000Z',
|
|
|
|
|
|
|
|
wmo: 'WHUS74',
|
|
|
|
|
|
|
|
pil: 'MWWLCH',
|
|
|
|
|
|
|
|
station: 'KLCH',
|
|
|
|
|
|
|
|
raw: '202504201512-KLCH-WHUS74-MWWLCH',
|
|
|
|
|
|
|
|
rawBody: 'LCH updates Small Craft Advisory (expires Coastal waters from Cameron LA to High Island TX out 20 NM, Coastal waters from Intracoastal City to Cameron LA out 20 NM, Coastal waters from Lower Atchafalaya River to Intracoastal City LA out 20 NM [GM], continues Waters from Cameron LA to High Island TX from 20 to 60 NM, Waters from Intracoastal City
|
|
|
|
|
|
|
|
to Cameron LA from 20 to 60 NM, Waters from Lower Atchafalaya River to Intracoastal City LA from 20 to 60 NM [GM]) . https://mesonet.agron.iastate.edu/vtec/f/2025-O-EXP-KLCH-SC-Y-0028_2025-04-20T15:12Z',
|
|
|
|
|
|
|
|
image: 'https://mesonet.agron.iastate.edu/plotting/auto/plot/208/network:WFO::wfo:LCH::year:2025::phenomenav:SC::significancev:Y::etn:28::valid:2025-04-20%201512::_r:86.png',
|
|
|
|
|
|
|
|
productText: '570 \n' +
|
|
|
|
|
|
|
|
'WHUS74 KLCH 201512\n' +
|
|
|
|
|
|
|
|
'MWWLCH\n' +
|
|
|
|
|
|
|
|
'\n' +
|
|
|
|
|
|
|
|
'URGENT - MARINE WEATHER MESSAGE\n' +
|
|
|
|
|
|
|
|
'National Weather Service Lake Charles LA\n' +
|
|
|
|
|
|
|
|
'1012 AM CDT Sun Apr 20 2025\n' +
|
|
|
|
|
|
|
|
'\n' +
|
|
|
|
|
|
|
|
'GMZ450-452-455-201615-\n' +
|
|
|
|
|
|
|
|
'/O.EXP.KLCH.SC.Y.0028.000000T0000Z-250420T1500Z/\n' +
|
|
|
|
|
|
|
|
'Coastal waters from Cameron LA to High Island TX out 20 NM-\n' +
|
|
|
|
|
|
|
|
'Coastal waters from Intracoastal City to Cameron LA out 20 NM-\n' +
|
|
|
|
|
|
|
|
'Coastal waters from Lower Atchafalaya River to Intracoastal City\n' +
|
|
|
|
|
|
|
|
'LA out 20 NM-\n' +
|
|
|
|
|
|
|
|
'1012 AM CDT Sun Apr 20 2025\n' +
|
|
|
|
|
|
|
|
'\n' +
|
|
|
|
|
|
|
|
'...SMALL CRAFT ADVISORY HAS EXPIRED...\n' +
|
|
|
|
|
|
|
|
'\n' +
|
|
|
|
|
|
|
|
'$$\n' +
|
|
|
|
|
|
|
|
'\n' +
|
|
|
|
|
|
|
|
'GMZ470-472-475-202100-\n' +
|
|
|
|
|
|
|
|
'/O.CON.KLCH.SC.Y.0028.000000T0000Z-250420T2100Z/\n' +
|
|
|
|
|
|
|
|
'Coastal waters from Cameron LA to High Island TX from 20 to 60 NM-\n' +
|
|
|
|
|
|
|
|
'Coastal waters from Intracoastal City to Cameron LA from 20 to\n' +
|
|
|
|
|
|
|
|
'60 NM-\n' +
|
|
|
|
|
|
|
|
'Coastal waters from Lower Atchafalaya River to Intracoastal City\n' +
|
|
|
|
|
|
|
|
'LA from 20 to 60 NM-\n' +
|
|
|
|
|
|
|
|
'1012 AM CDT Sun Apr 20 2025\n' +
|
|
|
|
|
|
|
|
'\n' +
|
|
|
|
|
|
|
|
'...SMALL CRAFT ADVISORY REMAINS IN EFFECT UNTIL 4 PM CDT THIS\n' +
|
|
|
|
|
|
|
|
'AFTERNOON...\n' +
|
|
|
|
|
|
|
|
'\n' +
|
|
|
|
|
|
|
|
'* WHAT...Seas 4 to 7 ft.\n' +
|
|
|
|
|
|
|
|
'\n' +
|
|
|
|
|
|
|
|
'* WHERE...Coastal waters from Cameron LA to High Island TX from \n' +
|
|
|
|
|
|
|
|
' 20 to 60 NM, Coastal waters from Intracoastal City to Cameron \n' +
|
|
|
|
|
|
|
|
' LA from 20 to 60 NM and Coastal waters from Lower Atchafalaya \n' +
|
|
|
|
|
|
|
|
' River to Intracoastal City LA from 20 to 60 NM.\n' +
|
|
|
|
|
|
|
|
'\n' +
|
|
|
|
|
|
|
|
'* WHEN...Until 4 PM CDT this afternoon.\n' +
|
|
|
|
|
|
|
|
'\n' +
|
|
|
|
|
|
|
|
'* IMPACTS...Conditions will be hazardous to small craft.\n' +
|
|
|
|
|
|
|
|
'\n' +
|
|
|
|
|
|
|
|
'PRECAUTIONARY/PREPAREDNESS ACTIONS...\n' +
|
|
|
|
|
|
|
|
'\n' +
|
|
|
|
|
|
|
|
'Inexperienced mariners, especially those operating smaller\n' +
|
|
|
|
|
|
|
|
'vessels, should avoid navigating in hazardous conditions.\n' +
|
|
|
|
|
|
|
|
'\n' +
|
|
|
|
|
|
|
|
'&&\n' +
|
|
|
|
|
|
|
|
'\n' +
|
|
|
|
|
|
|
|
'$$\n'
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Image and product text are optional, but if they exist, they will be in the data object.
|
|
|
|
xmpp.on("error", (err) => {
|
|
|
|
|
|
|
|
console.log(`${colors.red("[ERROR]")} XMPP Error: ${err}. Trying to reconnect...`);
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
|
|
xmpp.stop().then(() => {
|
|
|
|
|
|
|
|
start();
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}, 5000);
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
*/
|
|
|
|
xmpp.on("offline", () => {
|
|
|
|
|
|
|
|
console.log(`${colors.yellow("[WARN]")} XMPP offline, trying to reconnect...`);
|
|
|
|
|
|
|
|
xmpp.disconnect().then(() => {
|
|
|
|
|
|
|
|
xmpp.stop().then(() => {
|
|
|
|
|
|
|
|
start();
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const handleDiscordGuilds = function (data) {
|
|
|
|
var restartTimer = null;
|
|
|
|
const rawBody = data.data.body;
|
|
|
|
|
|
|
|
const text = data.data.productText;
|
|
|
|
xmpp.on("stanza", (stanza) => {
|
|
|
|
const product_id_raw = data.data.raw
|
|
|
|
// Debug stuff
|
|
|
|
const product_id = data.product_data;
|
|
|
|
if (config.debug >= 2) console.log(`${colors.magenta("[DEBUG]")} Stanza: ${stanza.toString()}`);
|
|
|
|
const fromChannel = data.data.channel.room;
|
|
|
|
|
|
|
|
const bodyData = data.data.body;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let embed = {
|
|
|
|
// Handle Room List
|
|
|
|
description: `<t:${product_id.timestamp / 1000}:T> <t:${product_id.timestamp / 1000}:R> ${bodyData.string}`,
|
|
|
|
if (stanza.is("iq") && stanza.attrs.type === "result" && stanza.getChild("query")) {
|
|
|
|
color: parseInt(config.priorityColors[evt.priority].replace("#", ""), 16) || 0x000000,
|
|
|
|
query = stanza.getChild("query");
|
|
|
|
timestamp: product_id.timestamp,
|
|
|
|
if (query.attrs.xmlns === "http://jabber.org/protocol/disco#items") {
|
|
|
|
footer: {
|
|
|
|
query.getChildren("item").forEach((item) => {
|
|
|
|
text: `Station: ${product_id.station} PID: ${product_id_raw} Channel: ${fromChannel}`
|
|
|
|
// Check if the JID is on the blacklist, if so, ignore it
|
|
|
|
}
|
|
|
|
if (blacklist.includes(item.attrs.jid)) return;
|
|
|
|
}
|
|
|
|
// get proper name from wfos
|
|
|
|
if (data.data.image) {
|
|
|
|
const wfo = getWFOByRoom(item.attrs.jid.split("@")[0]);
|
|
|
|
embed.image = {
|
|
|
|
item.attrs.properName = wfo.location;
|
|
|
|
url: data.data.image
|
|
|
|
iem.push(item.attrs);
|
|
|
|
|
|
|
|
console.log(`${colors.cyan("[INFO]")} Found room: ${item.attrs.jid}`);
|
|
|
|
|
|
|
|
// Join the room
|
|
|
|
|
|
|
|
//xmpp.send(xml("presence", { to: `${channel.jid}/${channel.name}/${curUUID}` }, xml("item", { role: "visitor" })));
|
|
|
|
|
|
|
|
xmpp.send(xml("presence", { to: `${item.attrs.jid}/${curUUID}` }, xml("item", { role: "visitor" })));
|
|
|
|
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
|
|
|
fromChannel = stanza.attrs.from.split("@")[0];
|
|
|
|
|
|
|
|
// Ignores
|
|
|
|
|
|
|
|
if (!stanza.getChild("x")) return; // No PID, ignore it
|
|
|
|
|
|
|
|
if (!stanza.getChild("x").attrs.product_id) return;
|
|
|
|
|
|
|
|
|
|
|
|
let discordMsg = {
|
|
|
|
const product_id = parseProductID(stanza.getChild("x").attrs.product_id);
|
|
|
|
embeds: [embed],
|
|
|
|
const product_id_raw = stanza.getChild("x").attrs.product_id;
|
|
|
|
components: [
|
|
|
|
// Get body of message
|
|
|
|
{
|
|
|
|
const body = html.decode(stanza.getChildText("body"));
|
|
|
|
type: 1,
|
|
|
|
const bodyData = getFirstURL(body);
|
|
|
|
components: [
|
|
|
|
// get product id from "x" tag
|
|
|
|
|
|
|
|
var evt = events[product_id.pil.substring(0, 3)];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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: [
|
|
|
|
{
|
|
|
|
{
|
|
|
|
type: 2,
|
|
|
|
title: "Unknown Event Type",
|
|
|
|
label: "Product",
|
|
|
|
description: `Unknown event type: ${product_id.pil.substring(0, 3)}. Please check the logs for more details.`,
|
|
|
|
style: 5,
|
|
|
|
color: 0xff0000
|
|
|
|
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`);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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(() => {
|
|
|
|
|
|
|
|
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`);
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// User DM alert handling
|
|
|
|
|
|
|
|
db.all(`SELECT * FROM userAlerts WHERE iemchannel = ?`, [fromChannel], (err, rows) => {
|
|
|
|
|
|
|
|
if (err) {
|
|
|
|
|
|
|
|
console.error(err.message);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!rows) return; // No users to alert
|
|
|
|
|
|
|
|
rows.forEach((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 priority is less than the min priority, ignore it
|
|
|
|
evt.code = product_id.pil.substring(0, 3);
|
|
|
|
if (evt.priority < row.minPriority) return;
|
|
|
|
// Check timestamp, if not within 3 minutes, ignore it
|
|
|
|
// If the event type is not in th filter, ignore it. Make sure filterEvt isnt null
|
|
|
|
const now = new Date();
|
|
|
|
if (!filterEvt[0]) filterEvt = [];
|
|
|
|
const diff = (now - product_id.timestamp) / 1000 / 60;
|
|
|
|
if (!filterEvt.includes(evt.code.toLowerCase()) && !filterEvt.length == 0) return;
|
|
|
|
if (diff > 3) return;
|
|
|
|
let user = discord.users.cache.get(row.userid);
|
|
|
|
// if (config.debug >= 1) console.log(`${colors.magenta("[DEBUG]")} New message from ${fromChannel}`);
|
|
|
|
if (!user) return console.log(`${colors.red("[ERROR]")} User ${row.userid} not found`);
|
|
|
|
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}`);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
user.send(thisMsg).catch((err) => {
|
|
|
|
|
|
|
|
console.error(err);
|
|
|
|
|
|
|
|
}).catch((err) => {
|
|
|
|
}).catch((err) => {
|
|
|
|
console.log(`${colors.yellow("[WARN]")} Failed to send message to ${user.tag} (${user.id})`);
|
|
|
|
console.error(err)
|
|
|
|
const logChannel = discord.guilds.cache.get(config.discord.mainGuild).channels.cache.get(config.discord.logChannel);
|
|
|
|
})
|
|
|
|
logChannel.send({
|
|
|
|
}
|
|
|
|
embeds: [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Send discord msg
|
|
|
|
|
|
|
|
let embed = {
|
|
|
|
|
|
|
|
description: `<t:${product_id.timestamp / 1000}:T> <t:${product_id.timestamp / 1000}:R> ${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: [
|
|
|
|
{
|
|
|
|
{
|
|
|
|
title: "Failed to send DM",
|
|
|
|
type: 2,
|
|
|
|
description: `User may have DMs disabled, or bot doesnt' share a server anymore!.
|
|
|
|
label: "Product",
|
|
|
|
User: ${user.tag} (${user.id})
|
|
|
|
style: 5,
|
|
|
|
Sub Info: \`\`\`json\n${JSON.stringify(row)}\`\`\``,
|
|
|
|
url: bodyData.url
|
|
|
|
color: 0xff0000
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
type: 2,
|
|
|
|
|
|
|
|
style: 1,
|
|
|
|
|
|
|
|
custom_id: `product|${product_id_raw}`,
|
|
|
|
|
|
|
|
label: "Product Text",
|
|
|
|
|
|
|
|
emoji: {
|
|
|
|
|
|
|
|
name: "📄"
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
]
|
|
|
|
]
|
|
|
|
}).then(() => {
|
|
|
|
}
|
|
|
|
db.run(`DELETE FROM userAlerts WHERE userid = ? AND iemchannel = ?`, [user.id, fromChannel], (err) => {
|
|
|
|
]
|
|
|
|
if (err) {
|
|
|
|
}
|
|
|
|
console.error(err.message);
|
|
|
|
// Discord Channel Handling
|
|
|
|
}
|
|
|
|
db.all(`SELECT * FROM channels WHERE iemchannel = ?`, [fromChannel], (err, rows) => {
|
|
|
|
console.log(`${colors.cyan("[INFO]")} Deleted user ${user.id} from database`);
|
|
|
|
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();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var retries = 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function connectWebSocket() {
|
|
|
|
|
|
|
|
console.log('Attempting to connect to WebSocket...');
|
|
|
|
|
|
|
|
const ws = new WebSocket(config.WS_URL || "wss://iem-alerter.ko4wal.radio/iem");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ws.on('open', () => {
|
|
|
|
|
|
|
|
console.log('Connected to WebSocket');
|
|
|
|
|
|
|
|
ws.send(JSON.stringify({ type: 'subscribe', channel: '*' })); // Subscribe to all channels
|
|
|
|
|
|
|
|
retries = 0; // Reset retries on successful connection
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ws.on('message', (data) => {
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
const data = JSON.parse(data);
|
|
|
|
|
|
|
|
switch (data.type) {
|
|
|
|
|
|
|
|
case "iem-message":
|
|
|
|
|
|
|
|
handleDiscordGuilds(data);
|
|
|
|
|
|
|
|
handleDiscordDMs(data);
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
case "internal-response":
|
|
|
|
|
|
|
|
if (data.code == 200) {
|
|
|
|
|
|
|
|
console.log(`${colors.cyan("[INFO]")} ${data.data.message}`);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
console.log(`${colors.red(`[ERROR] ${data.code}`)} ${data.data.error}`);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "connection-info":
|
|
|
|
|
|
|
|
console.log(`${colors.cyan("[INFO]")} Connected to ${data.host} (${data.uuid})`);
|
|
|
|
|
|
|
|
iem = data.iem;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// User DM alert handling
|
|
|
|
|
|
|
|
db.all(`SELECT * FROM userAlerts WHERE iemchannel = ?`, [fromChannel], (err, rows) => {
|
|
|
|
|
|
|
|
if (err) {
|
|
|
|
|
|
|
|
console.error(err.message);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (err) {
|
|
|
|
if (!rows) return; // No users to alert
|
|
|
|
console.log(`${colors.red("[ERROR]")} Error parsing WebSocket message: ${err}`);
|
|
|
|
rows.forEach((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(",");
|
|
|
|
|
|
|
|
|
|
|
|
ws.on('close', () => {
|
|
|
|
// If priority is less than the min priority, ignore it
|
|
|
|
console.log('WebSocket connection closed. Reconnecting...');
|
|
|
|
if (evt.priority < row.minPriority) return;
|
|
|
|
retries++;
|
|
|
|
// If the event type is not in th filter, ignore it. Make sure filterEvt isnt null
|
|
|
|
const retryDelay = Math.min(5000, Math.pow(2, retries) * 100);
|
|
|
|
if (!filterEvt[0]) filterEvt = [];
|
|
|
|
console.log(`Retrying connection in ${retryDelay} ms...`);
|
|
|
|
if (!filterEvt.includes(evt.code.toLowerCase()) && !filterEvt.length == 0) return;
|
|
|
|
setTimeout(connectWebSocket, retryDelay);
|
|
|
|
let user = discord.users.cache.get(row.userid);
|
|
|
|
});
|
|
|
|
if (!user) return console.log(`${colors.red("[ERROR]")} User ${row.userid} not found`);
|
|
|
|
|
|
|
|
|
|
|
|
ws.on('error', (err) => {
|
|
|
|
// fetch the product text
|
|
|
|
console.error('WebSocket error:', err);
|
|
|
|
trySend = () => {
|
|
|
|
ws.close(); // Ensure the connection is closed before retrying
|
|
|
|
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;
|
|
|
|
|
|
|
|
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) => {
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
|
|
console.log(`${colors.red("[ERROR]")} Failed to fetch product text, retrying... ${err}`)
|
|
|
|
|
|
|
|
trySend();
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
});;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
trySend();
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
connectWebSocket();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// START DISCORD
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
discord.on('ready', async () => {
|
|
|
|
xmpp.on("online", async (address) => {
|
|
|
|
console.log(`${colors.cyan("[INFO]")} Logged in as ${discord.user.tag}`);
|
|
|
|
|
|
|
|
if (config["uptime-kuma"].enabled) {
|
|
|
|
if (config["uptime-kuma"].enabled) {
|
|
|
|
fetch(config["uptime-kuma"].url).then(() => {
|
|
|
|
fetch(config["uptime-kuma"].url).then(() => {
|
|
|
|
console.log(`${colors.cyan("[INFO]")} Sent heartbeat to Uptime Kuma`)
|
|
|
|
console.log(`${colors.cyan("[INFO]")} Sent heartbeat to Uptime Kuma`)
|
|
|
@ -605,6 +628,73 @@ discord.on('ready', async () => {
|
|
|
|
}, config["uptime-kuma"].interval * 1000) // Every X seconds
|
|
|
|
}, config["uptime-kuma"].interval * 1000) // Every X seconds
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
errCount = 0;
|
|
|
|
|
|
|
|
// Start listening on all channels, (dont ban me funny man)
|
|
|
|
|
|
|
|
// for (const channel in iem) {
|
|
|
|
|
|
|
|
// console.log(`Joining ${channel.name}`)
|
|
|
|
|
|
|
|
// await xmpp.send(xml("presence", { to: `${channel.jud}/${channel.name}` }));
|
|
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
/* sub format
|
|
|
|
|
|
|
|
<presence to="botstalk@conference.weather.im/add9b8f1-038d-47ed-b708-6ed60075a82f" xmlns="jabber:client">
|
|
|
|
|
|
|
|
<x xmlns="http://jabber.org/protocol/muc#user">
|
|
|
|
|
|
|
|
<item>
|
|
|
|
|
|
|
|
<role>visitor</role>
|
|
|
|
|
|
|
|
</item>
|
|
|
|
|
|
|
|
</x>
|
|
|
|
|
|
|
|
</presence>
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Request room list
|
|
|
|
|
|
|
|
// Automatically find room list
|
|
|
|
|
|
|
|
xmpp.send(xml("iq", { type: "get", to: "conference.weather.im", id: "rooms" }, xml("query", { xmlns: "http://jabber.org/protocol/disco#items" })));
|
|
|
|
|
|
|
|
// Join all channels (Old method)
|
|
|
|
|
|
|
|
// iem.forEach((channel => {
|
|
|
|
|
|
|
|
// console.log(`${colors.cyan("[INFO]")} Joining ${channel.jid}/${channel.name}/${curUUID}`)
|
|
|
|
|
|
|
|
// //xmpp.send(xml("presence", { to: `${channel.jid}/${channel.jid.split("@")[0]}` }));
|
|
|
|
|
|
|
|
// xmpp.send(xml("presence", { to: `${channel.jid}/${channel.name}/${curUUID}` }, xml("item", { role: "visitor" })));
|
|
|
|
|
|
|
|
// }))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
console.log(`${colors.cyan("[INFO]")} Connected to XMPP server as ${address.toString()}`);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
|
|
startup = false;
|
|
|
|
|
|
|
|
console.log(`${colors.cyan("[INFO]")} Startup complete, listening for messages...`);
|
|
|
|
|
|
|
|
}, 1000)
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
xmpp.on("close", () => {
|
|
|
|
|
|
|
|
console.log(`${colors.yellow("[WARN]")} XMPP connection closed, trying to reconnect...`);
|
|
|
|
|
|
|
|
xmpp.disconnect().then(() => {
|
|
|
|
|
|
|
|
xmpp.stop().then(() => {
|
|
|
|
|
|
|
|
start();
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const start = () => {
|
|
|
|
|
|
|
|
startup = true;
|
|
|
|
|
|
|
|
xmpp.start().catch((err) => {
|
|
|
|
|
|
|
|
errCount++;
|
|
|
|
|
|
|
|
if (errCount >= 5) {
|
|
|
|
|
|
|
|
console.log(`${colors.red("[ERROR]")} XMPP failed to start after 5 attempts, exiting...`);
|
|
|
|
|
|
|
|
process.exit(1);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
console.log(`${colors.red("[ERROR]")} XMPP failed to start: ${err}.`);
|
|
|
|
|
|
|
|
xmpp.disconnect().then(() => {
|
|
|
|
|
|
|
|
xmpp.stop().then(() => {
|
|
|
|
|
|
|
|
start();
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// END XMPP
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// START DISCORD
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
discord.on('ready', async () => {
|
|
|
|
|
|
|
|
console.log(`${colors.cyan("[INFO]")} Logged in as ${discord.user.tag}`);
|
|
|
|
|
|
|
|
|
|
|
|
// Get all guilds, and log them
|
|
|
|
// Get all guilds, and log them
|
|
|
|
discord.guilds.cache.forEach((guild) => {
|
|
|
|
discord.guilds.cache.forEach((guild) => {
|
|
|
|
console.log(`${colors.cyan("[INFO]")} In guild: ${guild.name} (${guild.id})`);
|
|
|
|
console.log(`${colors.cyan("[INFO]")} In guild: ${guild.name} (${guild.id})`);
|
|
|
|