Gwuh
This commit is contained in:
parent
e90ce71f23
commit
ff0a1215c2
94
blacklist.json
Normal file
94
blacklist.json
Normal file
|
@ -0,0 +1,94 @@
|
|||
[
|
||||
"rerchat@conference.weather.im",
|
||||
"ncrfcchat@conference.weather.im",
|
||||
"mkx_madison_spotters@conference.weather.im",
|
||||
"zanchat@conference.weather.im",
|
||||
"michiganwxalerts@conference.weather.im",
|
||||
"skywarnstatewidechase@conference.weather.im",
|
||||
"wws44fl@conference.weather.im",
|
||||
"hatchat@conference.weather.im",
|
||||
"crisfield-chat@conference.weather.im",
|
||||
"gstweather@conference.weather.im",
|
||||
"tws_chat@conference.weather.im",
|
||||
"franklin_chat@conference.weather.im",
|
||||
"cnrfcchat@conference.weather.im",
|
||||
"mke-skywarn@conference.weather.im",
|
||||
"tnwxdiscuss@conference.weather.im",
|
||||
"khws@conference.weather.im",
|
||||
"kcichat@conference.weather.im",
|
||||
"kodster@conference.weather.im",
|
||||
"wxst@conference.weather.im",
|
||||
"reddit_weatherlab@conference.weather.im",
|
||||
"nalsw@conference.weather.im",
|
||||
"barnburnerwi@conference.weather.im",
|
||||
"ohiostormspottersteamoss@conference.weather.im",
|
||||
"siawx_chat@conference.weather.im",
|
||||
"n90@conference.weather.im",
|
||||
"nerfcchat@conference.weather.im",
|
||||
"lmrfcchat@conference.weather.im",
|
||||
"mbrfcchat@conference.weather.im",
|
||||
"pitchat@conference.weather.im",
|
||||
"chachat@conference.weather.im",
|
||||
"spcmobile2006@conference.weather.im",
|
||||
"phl@conference.weather.im",
|
||||
"wbkoweatherwatchers@conference.weather.im",
|
||||
"gcwxchat@conference.weather.im",
|
||||
"zzmkxchat@conference.weather.im",
|
||||
"dentcoeas@conference.weather.im",
|
||||
"marfcchat@conference.weather.im",
|
||||
"easwtalk@conference.weather.im",
|
||||
"gccc-nc-skywarn@conference.weather.im",
|
||||
"mseas-weather-discussion@conference.weather.im",
|
||||
"abc3340@conference.weather.im",
|
||||
"awpwxchat@conference.weather.im",
|
||||
"wilchat@conference.weather.im",
|
||||
"okc_chatrooms@conference.weather.im",
|
||||
"kdtxchat@conference.weather.im",
|
||||
"wnpchat@conference.weather.im",
|
||||
"fox6chat@conference.weather.im",
|
||||
"cbrfcchat@conference.weather.im",
|
||||
"wisconsin_storm_spotters@conference.weather.im",
|
||||
"nwrfcchat@conference.weather.im",
|
||||
"wgrfcchat@conference.weather.im",
|
||||
"uswat@conference.weather.im",
|
||||
"iowawx@conference.weather.im",
|
||||
"ohrfcchat@conference.weather.im",
|
||||
"serfcchat@conference.weather.im",
|
||||
"stichat@conference.weather.im",
|
||||
"whntweather@conference.weather.im",
|
||||
"knsw@conference.weather.im",
|
||||
"test@conference.weather.im",
|
||||
"abc3340skywatcher@conference.weather.im",
|
||||
"abrfcchat@conference.weather.im",
|
||||
"bmxspotterchat@conference.weather.im",
|
||||
"aprfcchat@conference.weather.im",
|
||||
"wwsreport@conference.weather.im",
|
||||
"bmxalertchat@conference.weather.im",
|
||||
"twitter@conference.weather.im",
|
||||
"potomac_tracon@conference.weather.im",
|
||||
"detroiteaschat@conference.weather.im",
|
||||
"cwest@conference.weather.im",
|
||||
"sweaseops1@conference.weather.im",
|
||||
"scwx@conference.weather.im",
|
||||
"nwsc@conference.weather.im",
|
||||
"zabchat@conference.weather.im",
|
||||
"ztlchat@conference.weather.im",
|
||||
"zbwchat@conference.weather.im",
|
||||
"zauchat@conference.weather.im",
|
||||
"zobchat@conference.weather.im",
|
||||
"zdvchat@conference.weather.im",
|
||||
"zfwchat@conference.weather.im",
|
||||
"zhuchat@conference.weather.im",
|
||||
"zidchat@conference.weather.im",
|
||||
"zjxchat@conference.weather.im",
|
||||
"zkcchat@conference.weather.im",
|
||||
"zlachat@conference.weather.im",
|
||||
"zmechat@conference.weather.im",
|
||||
"zmachat@conference.weather.im",
|
||||
"zmpchat@conference.weather.im",
|
||||
"zoachat@conference.weather.im",
|
||||
"zlcchat@conference.weather.im",
|
||||
"zsechat@conference.weather.im",
|
||||
"zdcchat@conference.weather.im",
|
||||
"znychat@conference.weather.im"
|
||||
]
|
10
config.json
Normal file
10
config.json
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"debug": 0,
|
||||
"priorityColors": {
|
||||
"1": "#00AFFF",
|
||||
"2": "#00FF00",
|
||||
"3": "#FFFF00",
|
||||
"4": "#FFA500",
|
||||
"5": "#FF0000"
|
||||
}
|
||||
}
|
1530
events.json
Normal file
1530
events.json
Normal file
File diff suppressed because it is too large
Load diff
542
index.js
542
index.js
|
@ -0,0 +1,542 @@
|
|||
require('dotenv').config();
|
||||
const config = require("./config.json");
|
||||
const express = require('express');
|
||||
const expressWs = require('express-ws');
|
||||
const { client, xml } = require("@xmpp/client");
|
||||
const colors = require('colors');
|
||||
const html = require('html-entities');
|
||||
const blacklist = require("./blacklist.json")
|
||||
const wfos = require("./wfos.json")
|
||||
const events = require("./events.json")
|
||||
|
||||
const app = express();
|
||||
expressWs(app);
|
||||
|
||||
// Serve static files from the "public" directory
|
||||
app.use(express.static('public'));
|
||||
global.wsConnections = [];
|
||||
|
||||
// IEM WebSocket
|
||||
app.ws('/iem', (ws, req) => {
|
||||
console.log(`connection from ${req.ip}`);
|
||||
wsConnections.push(ws);
|
||||
ws.on('close', () => {
|
||||
console.log(`disconnected from ${req.ip}`);
|
||||
wsConnections = wsConnections.filter((conn) => conn !== ws);
|
||||
});
|
||||
ws.on('message', (msg) => {
|
||||
if (msg === "ping") {
|
||||
ws.send("pong")
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Random funcs
|
||||
function toTitleCase(str) {
|
||||
return str.replace(
|
||||
/\w\S*/g,
|
||||
function (txt) {
|
||||
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const parseProductID = function (product_id) {
|
||||
const [timestamp, station, wmo, pil] = product_id.split("-");
|
||||
return {
|
||||
timestamp: convertDate(timestamp),
|
||||
originalTimestamp: timestamp,
|
||||
station,
|
||||
wmo,
|
||||
pil
|
||||
};
|
||||
}
|
||||
|
||||
// Convert date format 202405080131 (YYYYMMddHHmm) to iso format, hours and mins is UTC
|
||||
const convertDate = function (date) {
|
||||
const year = date.substring(0, 4);
|
||||
const month = date.substring(4, 6);
|
||||
const day = date.substring(6, 8);
|
||||
const hours = date.substring(8, 10);
|
||||
const mins = date.substring(10, 12);
|
||||
// Because they don't have seconds, assume current seconds
|
||||
const secs = new Date().getSeconds();
|
||||
return new Date(Date.UTC(year, month - 1, day, hours, mins, secs));
|
||||
}
|
||||
|
||||
// Get number of unique channels in the database
|
||||
const getUniqueChannels = function () {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.all(`SELECT DISTINCT channelid FROM channels`, (err, rows) => {
|
||||
if (err) {
|
||||
console.error(err.message);
|
||||
}
|
||||
// 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 });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Get first url in a string, return object {string, url} remove the url from the string
|
||||
const getFirstURL = function (string) {
|
||||
url = string.match(/(https?:\/\/[^\s]+)/g);
|
||||
if (!url) return { string, url: null };
|
||||
const newString = string.replace(url[0], "");
|
||||
return { string: newString, url: url[0] };
|
||||
}
|
||||
|
||||
// Function to get the room name from the WFO code
|
||||
const getWFOroom = function (code) {
|
||||
code = code.toLowerCase();
|
||||
if (wfos[code]) {
|
||||
return wfos[code].room;
|
||||
} else {
|
||||
return code;
|
||||
}
|
||||
}
|
||||
|
||||
// Function to get WFO data
|
||||
const getWFO = function (code) {
|
||||
code = code.toLowerCase();
|
||||
if (wfos[code]) {
|
||||
return wfos[code];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Get WFO data from room name
|
||||
|
||||
function getWFOByRoom(room) {
|
||||
room = room.toLowerCase();
|
||||
for (const key in wfos) {
|
||||
if (wfos.hasOwnProperty(key) && wfos[key].room === room) {
|
||||
return wfos[key];
|
||||
}
|
||||
}
|
||||
return {
|
||||
location: room,
|
||||
room: room
|
||||
};
|
||||
}
|
||||
|
||||
// func to Generate random string, ({upper, lower, number, special}, length)
|
||||
|
||||
const generateRandomString = function (options, length) {
|
||||
let result = '';
|
||||
const characters = {
|
||||
upper: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
|
||||
lower: 'abcdefghijklmnopqrstuvwxyz',
|
||||
number: '0123456789',
|
||||
special: '!@#$%^&*()_+'
|
||||
};
|
||||
let chars = '';
|
||||
for (const key in options) {
|
||||
if (options[key]) {
|
||||
chars += characters[key];
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Func to generate UUID
|
||||
const generateUUID = function () {
|
||||
return generateRandomString({ lower: true, upper: true, number: true }, 8) + "-" + generateRandomString({ lower: true, upper: true, number: true }, 4) + "-" + generateRandomString({ lower: true, upper: true, number: true }, 4) + "-" + generateRandomString({ lower: true, upper: true, number: true }, 4) + "-" + generateRandomString({ lower: true, upper: true, number: true }, 12);
|
||||
}
|
||||
|
||||
// Variable setup
|
||||
var iem = []
|
||||
var startup = true;
|
||||
var startTimestap = new Date();
|
||||
var messages = 0;
|
||||
var errCount = 0;
|
||||
const curUUID = generateUUID();
|
||||
|
||||
|
||||
// Start IEM XMPP Connection
|
||||
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"
|
||||
});
|
||||
|
||||
//debug(xmpp, true);
|
||||
|
||||
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();
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
var restartTimer = null;
|
||||
|
||||
xmpp.on("stanza", (stanza) => {
|
||||
// Debug stuff
|
||||
//if (config.debug >= 2) console.log(`${colors.magenta("[DEBUG]")} Stanza: ${stanza.toString()}`);
|
||||
|
||||
|
||||
// Handle Room List
|
||||
if (stanza.is("iq") && stanza.attrs.type === "result" && stanza.getChild("query")) {
|
||||
query = stanza.getChild("query");
|
||||
if (query.attrs.xmlns === "http://jabber.org/protocol/disco#items") {
|
||||
query.getChildren("item").forEach((item) => {
|
||||
// Check if the JID is on the blacklist, if so, ignore it
|
||||
if (blacklist.includes(item.attrs.jid)) return;
|
||||
// get proper name from wfos
|
||||
const wfo = getWFOByRoom(item.attrs.jid.split("@")[0]);
|
||||
item.attrs.properName = wfo.location;
|
||||
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;
|
||||
|
||||
const product_id = parseProductID(stanza.getChild("x").attrs.product_id);
|
||||
const product_id_raw = stanza.getChild("x").attrs.product_id;
|
||||
// Get body of message
|
||||
const body = html.decode(stanza.getChildText("body"));
|
||||
const bodyData = getFirstURL(body);
|
||||
// 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: [
|
||||
{
|
||||
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);
|
||||
// Check timestamp, if not within 3 minutes, ignore it
|
||||
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}`);
|
||||
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: `<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: [
|
||||
// {
|
||||
// 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({
|
||||
"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
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const createDiscordEmbed = (data) => {
|
||||
const embed = {
|
||||
description: `<t:${Math.floor(data.timestamp / 1000)}:T> <t:${Math.floor(data.timestamp / 1000)}:R> ${data.body}`,
|
||||
color: parseInt(config.priorityColors[data.event.priority].replace("#", ""), 16) || 0x000000,
|
||||
timestamp: new Date(data.timestamp).toISOString(),
|
||||
footer: {
|
||||
text: `Station: ${data.station} PID: ${data.raw} Channel: ${data.channel}`
|
||||
}
|
||||
};
|
||||
|
||||
if (data.image) {
|
||||
embed.image = {
|
||||
url: data.image
|
||||
};
|
||||
}
|
||||
|
||||
return embed;
|
||||
};
|
||||
|
||||
|
||||
|
||||
xmpp.on("online", async (address) => {
|
||||
// if (config["uptime-kuma"].enabled) {
|
||||
// fetch(config["uptime-kuma"].url).then(() => {
|
||||
// console.log(`${colors.cyan("[INFO]")} Sent heartbeat to Uptime Kuma`)
|
||||
// })
|
||||
// setInterval(() => {
|
||||
// // Send POST request to config["uptime-kuma"].url
|
||||
// fetch(config["uptime-kuma"].url).then(() => {
|
||||
// console.log(`${colors.cyan("[INFO]")} Sent heartbeat to Uptime Kuma`)
|
||||
// })
|
||||
// }, config["uptime-kuma"].interval * 1000) // Every X seconds
|
||||
// }
|
||||
// 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) => {
|
||||
console.log("BWUH")
|
||||
console.log(`${colors.red("[ERROR]")} XMPP failed to start: ${err}.`);
|
||||
setTimeout(start, 5000);
|
||||
});
|
||||
}
|
||||
|
||||
// Start Express Server
|
||||
const PORT = process.env.SERVER_PORT || 3000;
|
||||
// Start the server
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Server is listening on ${PORT}`);
|
||||
start();
|
||||
});
|
29
package-lock.json
generated
29
package-lock.json
generated
|
@ -10,9 +10,11 @@
|
|||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@xmpp/client": "^0.13.4",
|
||||
"colors": "^1.4.0",
|
||||
"dotenv": "^16.5.0",
|
||||
"express": "^5.1.0",
|
||||
"express-ws": "^5.0.2"
|
||||
"express-ws": "^5.0.2",
|
||||
"html-entities": "^2.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@ampproject/remapping": {
|
||||
|
@ -1141,6 +1143,15 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/colors": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
|
||||
"integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.1.90"
|
||||
}
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz",
|
||||
|
@ -2082,6 +2093,22 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/html-entities": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz",
|
||||
"integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/mdevils"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://patreon.com/mdevils"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/http-errors": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
|
||||
|
|
|
@ -11,8 +11,10 @@
|
|||
"description": "",
|
||||
"dependencies": {
|
||||
"@xmpp/client": "^0.13.4",
|
||||
"colors": "^1.4.0",
|
||||
"dotenv": "^16.5.0",
|
||||
"express": "^5.1.0",
|
||||
"express-ws": "^5.0.2"
|
||||
"express-ws": "^5.0.2",
|
||||
"html-entities": "^2.6.0"
|
||||
}
|
||||
}
|
||||
|
|
594
wfos.json
Normal file
594
wfos.json
Normal file
|
@ -0,0 +1,594 @@
|
|||
{
|
||||
"iln": {
|
||||
"room": "ilnchat",
|
||||
"location": "Wilmington"
|
||||
},
|
||||
"zhu": {
|
||||
"room": "zhuchat",
|
||||
"location": "Houston"
|
||||
},
|
||||
"zme": {
|
||||
"room": "zmechat",
|
||||
"location": "Memphis"
|
||||
},
|
||||
"ggw": {
|
||||
"room": "ggwchat",
|
||||
"location": "Glasgow"
|
||||
},
|
||||
"sew": {
|
||||
"room": "sewchat",
|
||||
"location": "Seattle"
|
||||
},
|
||||
"ohx": {
|
||||
"room": "ohxchat",
|
||||
"location": "Nashville"
|
||||
},
|
||||
"meg": {
|
||||
"room": "megchat",
|
||||
"location": "Memphis"
|
||||
},
|
||||
"mob": {
|
||||
"room": "mobchat",
|
||||
"location": "Mobile"
|
||||
},
|
||||
"bro": {
|
||||
"room": "brochat",
|
||||
"location": "Brownsville"
|
||||
},
|
||||
"ddc": {
|
||||
"room": "ddcchat",
|
||||
"location": "Dodge_City"
|
||||
},
|
||||
"mfl": {
|
||||
"room": "mflchat",
|
||||
"location": "Miami"
|
||||
},
|
||||
"dvn": {
|
||||
"room": "dvnchat",
|
||||
"location": "Quad_Cities_Ia"
|
||||
},
|
||||
"gjt": {
|
||||
"room": "gjtchat",
|
||||
"location": "Grand_Junction"
|
||||
},
|
||||
"ztl": {
|
||||
"room": "ztlchat",
|
||||
"location": "Atlanta"
|
||||
},
|
||||
"lmk": {
|
||||
"room": "lmkchat",
|
||||
"location": "Louisville"
|
||||
},
|
||||
"fwd": {
|
||||
"room": "fwdchat",
|
||||
"location": "Dallas-Fort_Worth"
|
||||
},
|
||||
"boi": {
|
||||
"room": "boichat",
|
||||
"location": "Boise"
|
||||
},
|
||||
"slc": {
|
||||
"room": "slcchat",
|
||||
"location": "Salt_Lake_City"
|
||||
},
|
||||
"zob": {
|
||||
"room": "zobchat",
|
||||
"location": "Cleveland"
|
||||
},
|
||||
"ama": {
|
||||
"room": "amachat",
|
||||
"location": "Amarillo"
|
||||
},
|
||||
"pqr": {
|
||||
"room": "pqrchat",
|
||||
"location": "Portland"
|
||||
},
|
||||
"zid": {
|
||||
"room": "zidchat",
|
||||
"location": "Indianapolis"
|
||||
},
|
||||
"nhc": {
|
||||
"room": "nhcchat",
|
||||
"location": "National_Hurricane_Center"
|
||||
},
|
||||
"sto": {
|
||||
"room": "stochat",
|
||||
"location": "Sacramento"
|
||||
},
|
||||
"jkl": {
|
||||
"room": "jklchat",
|
||||
"location": "Jackson"
|
||||
},
|
||||
"grr": {
|
||||
"room": "grrchat",
|
||||
"location": "Grand_Rapids"
|
||||
},
|
||||
"hnx": {
|
||||
"room": "hnxchat",
|
||||
"location": "San_Joaquin_Valley-Hanford"
|
||||
},
|
||||
"spc": {
|
||||
"room": "spcchat",
|
||||
"location": "Storm_Prediction_Center"
|
||||
},
|
||||
"lch": {
|
||||
"room": "lchchat",
|
||||
"location": "Lake_Charles"
|
||||
},
|
||||
"zbw": {
|
||||
"room": "zbwchat",
|
||||
"location": "Boston"
|
||||
},
|
||||
"otx": {
|
||||
"room": "otxchat",
|
||||
"location": "Spokane"
|
||||
},
|
||||
"vef": {
|
||||
"room": "vefchat",
|
||||
"location": "Las_Vegas"
|
||||
},
|
||||
"pub": {
|
||||
"room": "pubchat",
|
||||
"location": "Pueblo"
|
||||
},
|
||||
"psr": {
|
||||
"room": "psrchat",
|
||||
"location": "Phoenix"
|
||||
},
|
||||
"sgx": {
|
||||
"room": "sgxchat",
|
||||
"location": "San_Diego"
|
||||
},
|
||||
"pbz": {
|
||||
"room": "pbzchat",
|
||||
"location": "Pittsburgh"
|
||||
},
|
||||
"eka": {
|
||||
"room": "ekachat",
|
||||
"location": "Eureka"
|
||||
},
|
||||
"ilx": {
|
||||
"room": "ilxchat",
|
||||
"location": "Lincoln"
|
||||
},
|
||||
"jsj": {
|
||||
"room": "jsjchat",
|
||||
"location": "San_Juan"
|
||||
},
|
||||
"mlb": {
|
||||
"room": "mlbchat",
|
||||
"location": "Melbourne"
|
||||
},
|
||||
"arx": {
|
||||
"room": "arxchat",
|
||||
"location": "La_Crosse"
|
||||
},
|
||||
"ilm": {
|
||||
"room": "ilmchat",
|
||||
"location": "Wilmington"
|
||||
},
|
||||
"okx": {
|
||||
"room": "okxchat",
|
||||
"location": "New_York"
|
||||
},
|
||||
"bot": {
|
||||
"room": "botstalk",
|
||||
"location": "All_Bots_Talk"
|
||||
},
|
||||
"lkn": {
|
||||
"room": "lknchat",
|
||||
"location": "Elko"
|
||||
},
|
||||
"rah": {
|
||||
"room": "rahchat",
|
||||
"location": "Raleigh"
|
||||
},
|
||||
"afc": {
|
||||
"room": "afcchat",
|
||||
"location": "Anchorage"
|
||||
},
|
||||
"lot": {
|
||||
"room": "lotchat",
|
||||
"location": "Chicago"
|
||||
},
|
||||
"box": {
|
||||
"room": "boxchat",
|
||||
"location": "Boston-Taunton"
|
||||
},
|
||||
"zny": {
|
||||
"room": "znychat",
|
||||
"location": "New_York"
|
||||
},
|
||||
"akq": {
|
||||
"room": "akqchat",
|
||||
"location": "Wakefield"
|
||||
},
|
||||
"zkc": {
|
||||
"room": "zkcchat",
|
||||
"location": "Kansas_City"
|
||||
},
|
||||
"bis": {
|
||||
"room": "bischat",
|
||||
"location": "Bismarck"
|
||||
},
|
||||
"twc": {
|
||||
"room": "twcchat",
|
||||
"location": "Tucson"
|
||||
},
|
||||
"abr": {
|
||||
"room": "abrchat",
|
||||
"location": "Aberdeen"
|
||||
},
|
||||
"pih": {
|
||||
"room": "pihchat",
|
||||
"location": "Pocatello-Idaho_Falls"
|
||||
},
|
||||
"zoa": {
|
||||
"room": "zoachat",
|
||||
"location": "Oakland"
|
||||
},
|
||||
"mtr": {
|
||||
"room": "mtrchat",
|
||||
"location": "San_Francisco"
|
||||
},
|
||||
"sju": {
|
||||
"room": "sjuchat",
|
||||
"location": "San_Juan"
|
||||
},
|
||||
"gid": {
|
||||
"room": "gidchat",
|
||||
"location": "Hastings"
|
||||
},
|
||||
"zmp": {
|
||||
"room": "zmpchat",
|
||||
"location": "Minneapolis"
|
||||
},
|
||||
"phi": {
|
||||
"room": "phichat",
|
||||
"location": "Mount_Holly"
|
||||
},
|
||||
"chs": {
|
||||
"room": "chschat",
|
||||
"location": "Charleston"
|
||||
},
|
||||
"ajk": {
|
||||
"room": "ajkchat",
|
||||
"location": "Juneau"
|
||||
},
|
||||
"bmx": {
|
||||
"room": "bmxchat",
|
||||
"location": "Birmingham"
|
||||
},
|
||||
"lix": {
|
||||
"room": "lixchat",
|
||||
"location": "New_Orleans"
|
||||
},
|
||||
"apx": {
|
||||
"room": "apxchat",
|
||||
"location": "Gaylord"
|
||||
},
|
||||
"hun": {
|
||||
"room": "hunchat",
|
||||
"location": "Huntsville"
|
||||
},
|
||||
"zma": {
|
||||
"room": "zmachat",
|
||||
"location": "Miami"
|
||||
},
|
||||
"dtx": {
|
||||
"room": "dtxchat",
|
||||
"location": "Detroit"
|
||||
},
|
||||
"gum": {
|
||||
"room": "gumchat",
|
||||
"location": "Guam"
|
||||
},
|
||||
"crp": {
|
||||
"room": "crpchat",
|
||||
"location": "Corpus_Christi"
|
||||
},
|
||||
"zjx": {
|
||||
"room": "zjxchat",
|
||||
"location": "Jacksonville"
|
||||
},
|
||||
"shv": {
|
||||
"room": "shvchat",
|
||||
"location": "Shreveport"
|
||||
},
|
||||
"tbw": {
|
||||
"room": "tbwchat",
|
||||
"location": "Tampa_Bay_Area-Ruskin"
|
||||
},
|
||||
"cys": {
|
||||
"room": "cyschat",
|
||||
"location": "Cheyenne"
|
||||
},
|
||||
"hfo": {
|
||||
"room": "hfochat",
|
||||
"location": "Honolulu"
|
||||
},
|
||||
"dmx": {
|
||||
"room": "dmxchat",
|
||||
"location": "Des_Moines"
|
||||
},
|
||||
"zse": {
|
||||
"room": "zsechat",
|
||||
"location": "Seattle"
|
||||
},
|
||||
"rev": {
|
||||
"room": "revchat",
|
||||
"location": "Reno"
|
||||
},
|
||||
"car": {
|
||||
"room": "carchat",
|
||||
"location": "Caribou"
|
||||
},
|
||||
"mso": {
|
||||
"room": "msochat",
|
||||
"location": "Missoula"
|
||||
},
|
||||
"key": {
|
||||
"room": "keychat",
|
||||
"location": "Key_West"
|
||||
},
|
||||
"riw": {
|
||||
"room": "riwchat",
|
||||
"location": "Riverton"
|
||||
},
|
||||
"mhx": {
|
||||
"room": "mhxchat",
|
||||
"location": "Newport-Morehead_City"
|
||||
},
|
||||
"cae": {
|
||||
"room": "caechat",
|
||||
"location": "Columbia"
|
||||
},
|
||||
"ind": {
|
||||
"room": "indchat",
|
||||
"location": "Indianapolis"
|
||||
},
|
||||
"dlh": {
|
||||
"room": "dlhchat",
|
||||
"location": "Duluth"
|
||||
},
|
||||
"unr": {
|
||||
"room": "unrchat",
|
||||
"location": "Rapid_City"
|
||||
},
|
||||
"zab": {
|
||||
"room": "zabchat",
|
||||
"location": "Albuquerque"
|
||||
},
|
||||
"zlc": {
|
||||
"room": "zlcchat",
|
||||
"location": "Salt_Lake_City"
|
||||
},
|
||||
"ffc": {
|
||||
"room": "ffcchat",
|
||||
"location": "Peachtree_City"
|
||||
},
|
||||
"epz": {
|
||||
"room": "epzchat",
|
||||
"location": "El_Paso_Tx-Santa_Teresa"
|
||||
},
|
||||
"tae": {
|
||||
"room": "taechat",
|
||||
"location": "Tallahassee"
|
||||
},
|
||||
"tfx": {
|
||||
"room": "tfxchat",
|
||||
"location": "Great_Falls"
|
||||
},
|
||||
"abq": {
|
||||
"room": "abqchat",
|
||||
"location": "Albuquerque"
|
||||
},
|
||||
"rlx": {
|
||||
"room": "rlxchat",
|
||||
"location": "Charleston"
|
||||
},
|
||||
"oun": {
|
||||
"room": "ounchat",
|
||||
"location": "Norman"
|
||||
},
|
||||
"cle": {
|
||||
"room": "clechat",
|
||||
"location": "Cleveland"
|
||||
},
|
||||
"lox": {
|
||||
"room": "loxchat",
|
||||
"location": "Los_Angeles-Oxnard"
|
||||
},
|
||||
"fsd": {
|
||||
"room": "fsdchat",
|
||||
"location": "Sioux_Falls"
|
||||
},
|
||||
"bgm": {
|
||||
"room": "bgmchat",
|
||||
"location": "Binghamton"
|
||||
},
|
||||
"ewx": {
|
||||
"room": "ewxchat",
|
||||
"location": "Austin-San_Antonio"
|
||||
},
|
||||
"zdv": {
|
||||
"room": "zdvchat",
|
||||
"location": "Denver"
|
||||
},
|
||||
"eax": {
|
||||
"room": "eaxchat",
|
||||
"location": "Kansas_City-Pleasant_Hill"
|
||||
},
|
||||
"iwx": {
|
||||
"room": "iwxchat",
|
||||
"location": "Northern_Indiana"
|
||||
},
|
||||
"lub": {
|
||||
"room": "lubchat",
|
||||
"location": "Lubbock"
|
||||
},
|
||||
"buf": {
|
||||
"room": "bufchat",
|
||||
"location": "Buffalo"
|
||||
},
|
||||
"pdt": {
|
||||
"room": "pdtchat",
|
||||
"location": "Pendleton"
|
||||
},
|
||||
"mkx": {
|
||||
"room": "mkxchat",
|
||||
"location": "Milwaukee-Sullivan"
|
||||
},
|
||||
"grb": {
|
||||
"room": "grbchat",
|
||||
"location": "Green_Bay"
|
||||
},
|
||||
"top": {
|
||||
"room": "topchat",
|
||||
"location": "Topeka"
|
||||
},
|
||||
"rnk": {
|
||||
"room": "rnkchat",
|
||||
"location": "Blacksburg"
|
||||
},
|
||||
"lzk": {
|
||||
"room": "lzkchat",
|
||||
"location": "Little_Rock"
|
||||
},
|
||||
"ctp": {
|
||||
"room": "ctpchat",
|
||||
"location": "State_College"
|
||||
},
|
||||
"gld": {
|
||||
"room": "gldchat",
|
||||
"location": "Goodland"
|
||||
},
|
||||
"byz": {
|
||||
"room": "byzchat",
|
||||
"location": "Billings"
|
||||
},
|
||||
"mpx": {
|
||||
"room": "mpxchat",
|
||||
"location": "Twin_Cities-Chanhassen"
|
||||
},
|
||||
"zdc": {
|
||||
"room": "zdcchat",
|
||||
"location": "Washington_DC"
|
||||
},
|
||||
"lsx": {
|
||||
"room": "lsxchat",
|
||||
"location": "St_Louis"
|
||||
},
|
||||
"pah": {
|
||||
"room": "pahchat",
|
||||
"location": "Paducah"
|
||||
},
|
||||
"bou": {
|
||||
"room": "bouchat",
|
||||
"location": "Denver"
|
||||
},
|
||||
"tsa": {
|
||||
"room": "tsachat",
|
||||
"location": "Tulsa"
|
||||
},
|
||||
"zfw": {
|
||||
"room": "zfwchat",
|
||||
"location": "Fort_Worth"
|
||||
},
|
||||
"zau": {
|
||||
"room": "zauchat",
|
||||
"location": "Chicago"
|
||||
},
|
||||
"mqt": {
|
||||
"room": "mqtchat",
|
||||
"location": "Marquette"
|
||||
},
|
||||
"maf": {
|
||||
"room": "mafchat",
|
||||
"location": "Midland-Odessa"
|
||||
},
|
||||
"mrx": {
|
||||
"room": "mrxchat",
|
||||
"location": "Morristown"
|
||||
},
|
||||
"fgz": {
|
||||
"room": "fgzchat",
|
||||
"location": "Flagstaff"
|
||||
},
|
||||
"oax": {
|
||||
"room": "oaxchat",
|
||||
"location": "Omaha-Valley"
|
||||
},
|
||||
"btv": {
|
||||
"room": "btvchat",
|
||||
"location": "Burlington"
|
||||
},
|
||||
"ict": {
|
||||
"room": "ictchat",
|
||||
"location": "Wichita"
|
||||
},
|
||||
"mfr": {
|
||||
"room": "mfrchat",
|
||||
"location": "Medford"
|
||||
},
|
||||
"hgx": {
|
||||
"room": "hgxchat",
|
||||
"location": "Houston-Galveston"
|
||||
},
|
||||
"afg": {
|
||||
"room": "afgchat",
|
||||
"location": "Fairbanks"
|
||||
},
|
||||
"sgf": {
|
||||
"room": "sgfchat",
|
||||
"location": "Springfield"
|
||||
},
|
||||
"haw": {
|
||||
"room": "hawaii",
|
||||
"location": "Hawaii"
|
||||
},
|
||||
"zla": {
|
||||
"room": "zlachat",
|
||||
"location": "Los_Angeles"
|
||||
},
|
||||
"aly": {
|
||||
"room": "alychat",
|
||||
"location": "Albany"
|
||||
},
|
||||
"sjt": {
|
||||
"room": "sjtchat",
|
||||
"location": "San_Angelo"
|
||||
},
|
||||
"jan": {
|
||||
"room": "janchat",
|
||||
"location": "Jackson"
|
||||
},
|
||||
"gsp": {
|
||||
"room": "gspchat",
|
||||
"location": "Greenville-Spartanburg"
|
||||
},
|
||||
"lwx": {
|
||||
"room": "lwxchat",
|
||||
"location": "Baltimore_Md-_Washington_Dc"
|
||||
},
|
||||
"gyx": {
|
||||
"room": "gyxchat",
|
||||
"location": "Gray"
|
||||
},
|
||||
"wpc": {
|
||||
"room": "wpcchat",
|
||||
"location": "Weather_Prediction_Center"
|
||||
},
|
||||
"lbf": {
|
||||
"room": "lbfchat",
|
||||
"location": "North_Platte"
|
||||
},
|
||||
"jax": {
|
||||
"room": "jaxchat",
|
||||
"location": "Jacksonville"
|
||||
},
|
||||
"fgf": {
|
||||
"room": "fgfchat",
|
||||
"location": "Grand_Forks"
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue