diff --git a/blacklist.json b/blacklist.json new file mode 100644 index 0000000..f0b3e61 --- /dev/null +++ b/blacklist.json @@ -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" +] \ No newline at end of file diff --git a/config.json b/config.json new file mode 100644 index 0000000..1c40649 --- /dev/null +++ b/config.json @@ -0,0 +1,10 @@ +{ + "debug": 0, + "priorityColors": { + "1": "#00AFFF", + "2": "#00FF00", + "3": "#FFFF00", + "4": "#FFA500", + "5": "#FF0000" + } +} \ No newline at end of file diff --git a/events.json b/events.json new file mode 100644 index 0000000..5b4fc35 --- /dev/null +++ b/events.json @@ -0,0 +1,1530 @@ +{ + "ABV": { + "priority": 1, + "text": "Rawinsonde Data Above 100 Millibars" + }, + "ADA": { + "priority": 1, + "text": "Alarm/Alert Administrative Msg" + }, + "ADM": { + "priority": 1, + "text": "Alert Administrative Message" + }, + "ADR": { + "text": "Administrative Message", + "priority": 5 + }, + "ADV": { + "priority": 1, + "text": "Generic Space Environment Advisory" + }, + "AFD": { + "priority": 1, + "text": "Area Forecast Discussion" + }, + "AFM": { + "priority": 1, + "text": "Area Forecast Matrices" + }, + "AFP": { + "priority": 1, + "text": "Area Forecast Product" + }, + "AFW": { + "priority": 1, + "text": "Fire Weather Matrix" + }, + "AGF": { + "priority": 1, + "text": "Agricultural Forecast" + }, + "AGO": { + "priority": 1, + "text": "Agricultural Observations" + }, + "ALT": { + "priority": 1, + "text": "Space Environment Alert" + }, + "AQA": { + "priority": 2, + "text": "Air Quality Alert" + }, + "AQI": { + "priority": 1, + "text": "Air Quality Index Statement" + }, + "ASA": { + "priority": 1, + "text": "Air Stagnation Advisory" + }, + "AVA": { + "text": "Avalanche Watch", + "priority": 3 + }, + "AVG": { + "priority": 1, + "text": "Avalanche Weather Guidance" + }, + "AVW": { + "text": "Avalanche Warning", + "priority": 5 + }, + "AWO": { + "priority": 1, + "text": "Area Weather Outlook" + }, + "AWS": { + "priority": 1, + "text": "Area Weather Summary" + }, + "AWU": { + "priority": 1, + "text": "Area Weather Update" + }, + "AWW": { + "priority": 1, + "text": "Airport Weather Warning" + }, + "BLU": { + "priority": 5, + "text": "Blue Alert" + }, + "BOY": { + "priority": 1, + "text": "Buoy Report" + }, + "BRG": { + "priority": 1, + "text": "Coast Guard Observations" + }, + "BRT": { + "priority": 1, + "text": "Hourly Roundup for Weather Radio" + }, + "CAE": { + "text": "Child Abduction Emergency", + "priority": 5 + }, + "CCF": { + "priority": 1, + "text": "Coded City Forecast" + }, + "CDW": { + "text": "Civil Danger Warning", + "priority": 5 + }, + "CEM": { + "text": "Civil Emergency Message", + "priority": 5 + }, + "CF6": { + "priority": 1, + "text": "WFO Monthly/Daily Climate Data" + }, + "CFP": { + "priority": 1, + "text": "Convective Forecast Product" + }, + "CFW": { + "text": "Coastal Flood Warning", + "priority": 5 + }, + "CGR": { + "priority": 1, + "text": "Coast Guard Surface Report" + }, + "CHG": { + "priority": 1, + "text": "Computer Hurricane Guidance" + }, + "CLA": { + "priority": 1, + "text": "Climatological Report (Annual)" + }, + "CLI": { + "priority": 1, + "text": "Climatological Report (Daily)" + }, + "CLM": { + "priority": 1, + "text": "Climatological Report (Monthly)" + }, + "CLQ": { + "priority": 1, + "text": "Climatological Report (Quarterly)" + }, + "CLS": { + "priority": 1, + "text": "Climatological Report (Seasonal)" + }, + "CLT": { + "priority": 1, + "text": "Climate Report" + }, + "CMM": { + "priority": 1, + "text": "Coded Climatological Monthly Means" + }, + "COD": { + "priority": 1, + "text": "Coded Analysis and Forecasts" + }, + "CPF": { + "priority": 1, + "text": "Great Lakes Port Forecast" + }, + "CUR": { + "priority": 1, + "text": "Routine Space Environment Products" + }, + "CWA": { + "priority": 1, + "text": "Center (CWSU) Weather Advisory" + }, + "CWF": { + "priority": 1, + "text": "Coastal Waters Forecast" + }, + "CWS": { + "priority": 1, + "text": "Center (CWSU) Weather Statement" + }, + "DAY": { + "priority": 1, + "text": "Routine Space Environment Product (Daily)" + }, + "DDO": { + "priority": 1, + "text": "Daily Dispersion Outlook" + }, + "DGT": { + "priority": 1, + "text": "Drought Information Statement" + }, + "DMO": { + "text": "Practice/Demo Warning", + "priority": 1 + }, + "DSA": { + "priority": 1, + "text": "Unnumbered Depression / Suspicious Area Advisory" + }, + "DSM": { + "priority": 1, + "text": "ASOS Daily Summary" + }, + "DSW": { + "text": "Dust Storm Warning", + "priority": 4 + }, + "EFP": { + "priority": 1, + "text": "3 To 5 Day Extended Forecast" + }, + "EOL": { + "priority": 1, + "text": "Average 6 To 10 Day Weather Outlook (Local)" + }, + "EQI": { + "priority": 3, + "text": "Tsunami Bulletin" + }, + "EQR": { + "priority": 1, + "text": "Earthquake Report" + }, + "EQW": { + "text": "Earthquake Warning", + "priority": 5 + }, + "ESF": { + "priority": 1, + "text": "Flood Potential Outlook" + }, + "ESG": { + "priority": 1, + "text": "Extended Streamflow Guidance" + }, + "ESP": { + "priority": 1, + "text": "Extended Streamflow Prediction" + }, + "ESS": { + "priority": 1, + "text": "Water Supply Outlook" + }, + "EVI": { + "text": "Evacuation Immediate", + "priority": 5 + }, + "EWW": { + "priority": 5, + "text": "Extreme Wind Warning" + }, + "FA0": { + "priority": 1, + "text": "Aviation Area Forecasts (Pacific)" + }, + "FA1": { + "priority": 1, + "text": "Aviation Area Forecasts (Northeast)" + }, + "FA2": { + "priority": 1, + "text": "Aviation Area Forecasts (Southeast)" + }, + "FA3": { + "priority": 1, + "text": "Aviation Area Forecasts (North Central)" + }, + "FA4": { + "priority": 1, + "text": "Aviation Area Forecasts (South Central)" + }, + "FA5": { + "priority": 1, + "text": "Aviation Area Forecasts (Rocky Mountains)" + }, + "FA6": { + "priority": 1, + "text": "Aviation Area Forecasts (West Coast)" + }, + "FA7": { + "priority": 1, + "text": "Aviation Area Forecasts (Juneau, AK)" + }, + "FA8": { + "priority": 1, + "text": "Aviation Area Forecasts (Anchorage, AK)" + }, + "FA9": { + "priority": 1, + "text": "Aviation Area Forecasts (Fairbanks, AK)" + }, + "FD0": { + "priority": 1, + "text": "24 Hr Fd Winds Aloft Fcst (45,000 and 53,000 Ft)" + }, + "FD1": { + "priority": 1, + "text": "6 Hour Winds Aloft Forecast" + }, + "FD2": { + "priority": 1, + "text": "12 Hour Winds Aloft Forecast" + }, + "FD3": { + "priority": 1, + "text": "24 Hour Winds Aloft Forecast" + }, + "FD4": { + "priority": 1, + "text": "Winds Aloft Forecast" + }, + "FD5": { + "priority": 1, + "text": "Winds Aloft Forecast" + }, + "FD6": { + "priority": 1, + "text": "Winds Aloft Forecast" + }, + "FD7": { + "priority": 1, + "text": "Winds Aloft Forecast" + }, + "FD8": { + "priority": 1, + "text": "6 Hour Fd Winds Aloft Fcst (45,000 and 53,000 Ft)" + }, + "FD9": { + "priority": 1, + "text": "12 Hr Fd Winds Aloft Fcst (45,000 and 53,000 Ft)" + }, + "FDI": { + "priority": 1, + "text": "Fire Danger Indices" + }, + "FFA": { + "text": "Flash Flood Watch", + "priority": 4 + }, + "FFG": { + "priority": 1, + "text": "Flash Flood Guidance" + }, + "FFH": { + "priority": 1, + "text": "Headwater Guidance" + }, + "FFS": { + "text": "Flash Flood Statement", + "priority": 2 + }, + "FFW": { + "text": "Flash Flood Warning", + "priority": 4 + }, + "FLN": { + "priority": 1, + "text": "National Flood Summary" + }, + "FLS": { + "text": "Flood Statement", + "priority": 2 + }, + "FLW": { + "text": "Flood Warning", + "priority": 3 + }, + "FOF": { + "priority": 1, + "text": "Upper Wind Fallout Forecast" + }, + "FRW": { + "text": "Fire Warning", + "priority": 3 + }, + "FSH": { + "priority": 1, + "text": "Natl Marine Fisheries Administrative Service Message" + }, + "FTM": { + "priority": 3, + "text": "WSR-88D Radar Outage Notification / Free Text Message" + }, + "FTP": { + "priority": 1, + "text": "FOUS Prog Max/Min Temp/Pop Guidance" + }, + "FWA": { + "priority": 1, + "text": "Fire Weather Administrative Message" + }, + "FWD": { + "priority": 1, + "text": "Fire Weather Outlook Discussion" + }, + "FWF": { + "priority": 1, + "text": "Routine Fire Wx Fcst (With/Without 6-10 Day Outlook)" + }, + "FWL": { + "priority": 1, + "text": "Land Management Forecasts" + }, + "FWM": { + "priority": 1, + "text": "Miscellaneous Fire Weather Product" + }, + "FWN": { + "priority": 1, + "text": "Fire Weather Notification" + }, + "FWO": { + "priority": 1, + "text": "Fire Weather Observation" + }, + "FWS": { + "priority": 1, + "text": "Spot Forecast" + }, + "FZL": { + "priority": 1, + "text": "Freezing Level Data (RADAT)" + }, + "GLF": { + "priority": 1, + "text": "Great Lakes Forecast" + }, + "GLS": { + "priority": 1, + "text": "Great Lakes Storm Summary" + }, + "GRE": { + "priority": 1, + "text": "GREEN" + }, + "HD1": { + "priority": 1, + "text": "RFC Derived QPF Data Product" + }, + "HD2": { + "priority": 1, + "text": "RFCDerived QPF Data Product" + }, + "HD3": { + "priority": 1, + "text": "RFC Derived QPF Data Product" + }, + "HD4": { + "priority": 1, + "text": "RFC Derived QPF Data Product" + }, + "HD7": { + "priority": 1, + "text": "RFC Derived QPF Data Product" + }, + "HD8": { + "priority": 1, + "text": "RFC Derived QPF Data Product" + }, + "HD9": { + "priority": 1, + "text": "RFC Derived QPF Data Product" + }, + "HLS": { + "text": "Hurricane Statement", + "priority": 3 + }, + "HMD": { + "priority": 1, + "text": "Hydrometeorological Discussion" + }, + "HML": { + "priority": 1, + "text": "AHPS XML" + }, + "HMW": { + "text": "Hazardous Materials Warning", + "priority": 5 + }, + "HP1": { + "priority": 1, + "text": "RFC QPF Verification Product" + }, + "HP2": { + "priority": 1, + "text": "RFC QPF Verification Product" + }, + "HP3": { + "priority": 1, + "text": "RFC QPF Verification Product" + }, + "HP4": { + "priority": 1, + "text": "RFC QPF Verification Product" + }, + "HP5": { + "priority": 1, + "text": "RFC QPF Verification Product" + }, + "HP6": { + "priority": 1, + "text": "RFC QPF Verification Product" + }, + "HP7": { + "priority": 1, + "text": "RFC QPF Verification Product" + }, + "HP8": { + "priority": 1, + "text": "RFC QPF Verification Product" + }, + "HRR": { + "priority": 1, + "text": "Weather Roundup" + }, + "HSF": { + "priority": 1, + "text": "High Seas Forecast" + }, + "HWO": { + "priority": 2, + "text": "Hazardous Weather Outlook" + }, + "HWR": { + "priority": 1, + "text": "Hourly Weather Roundup" + }, + "HYD": { + "priority": 1, + "text": "Daily Hydrometeorological Products" + }, + "HYM": { + "priority": 1, + "text": "Monthly Hydrometeorological Plain Language Product" + }, + "ICE": { + "priority": 1, + "text": "Ice Forecast" + }, + "IDM": { + "priority": 1, + "text": "Ice Drift Vectors" + }, + "INI": { + "priority": 1, + "text": "ADMINISTR [NOUS51 KWBC]" + }, + "IOB": { + "priority": 1, + "text": "Ice Observation" + }, + "KPA": { + "priority": 1, + "text": "Keep Alive Message" + }, + "LAE": { + "text": "Local Area Emergency", + "priority": 5 + }, + "LCD": { + "priority": 1, + "text": "Preliminary Local Climatological Data" + }, + "LCO": { + "priority": 1, + "text": "Local Cooperative Observation" + }, + "LEW": { + "text": "Law Enforcement Warning", + "priority": 5 + }, + "LFP": { + "priority": 2, + "text": "Local Forecast" + }, + "LKE": { + "priority": 1, + "text": "Lake Stages" + }, + "LLS": { + "priority": 1, + "text": "Low-Level Sounding" + }, + "LOW": { + "priority": 1, + "text": "Low Temperatures" + }, + "LSR": { + "priority": 3, + "text": "Local Storm Report" + }, + "LTG": { + "priority": 1, + "text": "Lightning Data" + }, + "MAN": { + "priority": 1, + "text": "Rawinsonde Observation Mandatory Levels" + }, + "MAP": { + "priority": 1, + "text": "Mean Areal Precipitation" + }, + "MAW": { + "priority": 1, + "text": "Amended Marine Forecast" + }, + "MFM": { + "priority": 1, + "text": "Marine Forecast Matrix" + }, + "MIM": { + "priority": 1, + "text": "Marine Interpretation Message" + }, + "MIS": { + "priority": 1, + "text": "Miscellaneous Local Product" + }, + "MOB": { + "priority": 1, + "text": "MOB Observations" + }, + "MON": { + "priority": 1, + "text": "Routine Space Environment Product Issued Monthly" + }, + "MRP": { + "priority": 1, + "text": "Techniques Development Laboratory Marine Product" + }, + "MSM": { + "priority": 1, + "text": "ASOS Monthly Summary Message" + }, + "MTR": { + "priority": 1, + "text": "METAR Formatted Surface Weather Observation" + }, + "MTT": { + "priority": 1, + "text": "METAR Test Message" + }, + "MVF": { + "priority": 1, + "text": "Marine Verification Coded Message" + }, + "MWS": { + "priority": 1, + "text": "Marine Weather Statement" + }, + "MWW": { + "priority": 1, + "text": "Marine Weather Message" + }, + "NOU": { + "priority": 1, + "text": "Weather Reconnaisance Flights" + }, + "NOW": { + "priority": 1, + "text": "Short Term Forecast" + }, + "NOX": { + "priority": 1, + "text": "Data Mgt Message" + }, + "NPW": { + "priority": 3, + "text": "Non-Precipitation Warnings / Watches / Advisories" + }, + "NSH": { + "priority": 1, + "text": "Nearshore Marine Forecast" + }, + "NUW": { + "text": "Nuclear Power Plant Warning", + "priority": 5 + }, + "NWR": { + "priority": 1, + "text": "NOAA Weather Radio Forecast" + }, + "OAV": { + "priority": 1, + "text": "Other Aviation Products" + }, + "OBS": { + "priority": 1, + "text": "Observations" + }, + "OFA": { + "priority": 1, + "text": "Offshore Aviation Area Forecast" + }, + "OFF": { + "priority": 1, + "text": "Offshore Forecast" + }, + "OMR": { + "priority": 1, + "text": "Other Marine Products" + }, + "OPU": { + "priority": 1, + "text": "Other Public Products" + }, + "OSO": { + "priority": 1, + "text": "Other Surface Observations" + }, + "OSW": { + "priority": 1, + "text": "Ocean Surface Winds" + }, + "OUA": { + "priority": 1, + "text": "Other Upper Air Data" + }, + "OZF": { + "priority": 1, + "text": "Zone Forecast" + }, + "PFM": { + "priority": 1, + "text": "Point Forecast Matrices" + }, + "PFW": { + "priority": 1, + "text": "Fire Weather Point Forecast Matrices" + }, + "PLS": { + "priority": 1, + "text": "Plain Language Ship Report" + }, + "PMD": { + "priority": 1, + "text": "Prognostic Meteorological Discussion" + }, + "PNS": { + "priority": 2, + "text": "Public Information Statement" + }, + "POE": { + "priority": 1, + "text": "Probability of Exceed" + }, + "PRB": { + "priority": 1, + "text": "Heat Index Forecast Tables" + }, + "PRC": { + "priority": 1, + "text": "State Pilot Report Collective" + }, + "PRE": { + "priority": 1, + "text": "Preliminary Forecasts" + }, + "PSH": { + "priority": 2, + "text": "Post Storm Hurricane Report" + }, + "PTS": { + "priority": 3, + "text": "Probabilistic Outlook Points" + }, + "PWO": { + "priority": 1, + "text": "Public Severe Weather Outlook" + }, + "PWS": { + "priority": 1, + "text": "Tropical Cyclone Probabilities" + }, + "QPF": { + "priority": 1, + "text": "Quantitative Precipitation Forecast" + }, + "QPS": { + "priority": 1, + "text": "Quantitative Precipitation Statement" + }, + "RDF": { + "priority": 1, + "text": "Revised Digital Forecast" + }, + "REC": { + "priority": 1, + "text": "Recreational Report" + }, + "RER": { + "priority": 2, + "text": "Record Report" + }, + "RET": { + "priority": 5, + "text": "EAS Activation Request" + }, + "RFD": { + "priority": 1, + "text": "Rangeland Fire Danger Forecast" + }, + "RFI": { + "priority": 1, + "text": "RFI Observation" + }, + "RFR": { + "priority": 1, + "text": "Route Forecast" + }, + "RFW": { + "priority": 3, + "text": "Red Flag Warning" + }, + "RHW": { + "text": "Radiological Hazard Warning", + "priority": 5 + }, + "RMT": { + "text": "Required Monthly Test", + "priority": 1 + }, + "RNS": { + "priority": 1, + "text": "Rain Information Statement" + }, + "RR1": { + "priority": 1, + "text": "Hydro-Met Data Report Part 1" + }, + "RR2": { + "priority": 1, + "text": "Hydro-Met Data Report Part 2" + }, + "RR3": { + "priority": 1, + "text": "Hydro-Met Data Report Part 3" + }, + "RR4": { + "priority": 1, + "text": "Hydro-Met Data Report Part 4" + }, + "RR5": { + "priority": 1, + "text": "Hydro-Met Data Report Part 5" + }, + "RR6": { + "priority": 1, + "text": "Hydro-Met Data Report Part 6" + }, + "RR7": { + "priority": 1, + "text": "Hydro-Met Data Report Part 7" + }, + "RR8": { + "priority": 1, + "text": "Hydro-Met Data Report Part 8" + }, + "RR9": { + "priority": 1, + "text": "Hydro-Met Data Report Part 9" + }, + "RRA": { + "priority": 1, + "text": "Automated Hydrologic Observation Sta Report (AHOS)" + }, + "RRM": { + "priority": 1, + "text": "Miscellaneous Hydrologic Data" + }, + "RRS": { + "priority": 1, + "text": "HADS Data" + }, + "RRY": { + "priority": 1, + "text": "ASOS SHEF Hourly Routine Test Message" + }, + "RSD": { + "priority": 1, + "text": "Daily Snotel Data" + }, + "RSM": { + "priority": 1, + "text": "Monthly Snotel Data" + }, + "RTP": { + "priority": 1, + "text": "Regional Max/Min Temp and Precipitation Table" + }, + "RVA": { + "priority": 1, + "text": "River Summary" + }, + "RVD": { + "priority": 1, + "text": "Daily River Forecasts" + }, + "RVF": { + "priority": 1, + "text": "River Forecast" + }, + "RVI": { + "priority": 1, + "text": "River Ice Statement" + }, + "RVM": { + "priority": 1, + "text": "Miscellaneous River Product" + }, + "RVR": { + "priority": 1, + "text": "River Recreation Statement" + }, + "RVS": { + "priority": 1, + "text": "River Statement" + }, + "RWR": { + "priority": 1, + "text": "Regional Weather Roundup" + }, + "RWS": { + "priority": 1, + "text": "Regional Weather Summary" + }, + "RWT": { + "text": "Required Weekly Test", + "priority": 1 + }, + "SAB": { + "priority": 1, + "text": "Special Avalanche Bulletin" + }, + "SAF": { + "priority": 1, + "text": "Speci Agri Wx Fcst / Advisory / Flying Farmer Fcst Outlook" + }, + "SAG": { + "priority": 1, + "text": "Snow Avalanche Guidance" + }, + "SAT": { + "priority": 1, + "text": "APT Prediction" + }, + "SAW": { + "priority": 1, + "text": "Prelim Notice of Watch & Cancellation Msg (Aviation)" + }, + "SCC": { + "priority": 2, + "text": "Storm Summary" + }, + "SCD": { + "priority": 1, + "text": "Supplementary Climatological Data (ASOS)" + }, + "SCN": { + "priority": 1, + "text": "Soil Climate Analysis Network Data" + }, + "SCP": { + "priority": 1, + "text": "Satellite Cloud Product" + }, + "SCS": { + "priority": 1, + "text": "Selected Cities Summary" + }, + "SDO": { + "priority": 1, + "text": "Supplementary Data Observation (ASOS)" + }, + "SDS": { + "priority": 1, + "text": "Special Dispersion Statement" + }, + "SEL": { + "priority": 4, + "text": "Severe Local Storm Watch and Watch Cancellation Msg" + }, + "SEV": { + "priority": 1, + "text": "SPC Watch Point Information Message" + }, + "SFP": { + "priority": 1, + "text": "State Forecast" + }, + "SFT": { + "priority": 1, + "text": "Tabular State Forecast" + }, + "SGL": { + "priority": 1, + "text": "Rawinsonde Observation Significant Levels" + }, + "SHP": { + "priority": 1, + "text": "Surface Ship Report at Synoptic Time" + }, + "SIG": { + "priority": 1, + "text": "International Sigmet / Convective Sigmet" + }, + "SIM": { + "priority": 1, + "text": "Satellite Interpretation Message" + }, + "SLS": { + "priority": 1, + "text": "Severe Local Storm Watch and Areal Outline" + }, + "SMF": { + "priority": 1, + "text": "Smoke Management Weather Forecast" + }, + "SMW": { + "text": "Special Marine Warning", + "priority": 4 + }, + "SOO": { + "priority": 1, + "text": "SOO Product" + }, + "SPE": { + "priority": 1, + "text": "Satellite Precipitation Estimates (TXUS20 KWBC)" + }, + "SPF": { + "priority": 1, + "text": "Storm Strike Probability Bulletin (TPC)" + }, + "SPS": { + "text": "Special Weather Statement", + "priority": 2 + }, + "SPW": { + "text": "Shelter In Place Warning", + "priority": 5 + }, + "SQW": { + "priority": 5, + "text": "Snow Squall Warning" + }, + "SRD": { + "priority": 1, + "text": "Surf Discussion" + }, + "SRF": { + "priority": 1, + "text": "Surf Forecast" + }, + "SRG": { + "priority": 1, + "text": "Soaring Guidance" + }, + "SSM": { + "priority": 1, + "text": "Main Synoptic Hour Surface Observation" + }, + "STA": { + "priority": 1, + "text": "Network and Severe Weather Statistical Summaries" + }, + "STD": { + "priority": 1, + "text": "Satellite Tropical Disturbance Summary" + }, + "STO": { + "priority": 1, + "text": "Road Condition Reports (State Agencies)" + }, + "STP": { + "priority": 1, + "text": "State Max/Min Temperature and Precipitation Table" + }, + "STQ": { + "priority": 1, + "text": "Spot Forecast Request" + }, + "SUM": { + "priority": 1, + "text": "Space Weather Message" + }, + "SVR": { + "text": "Severe Thunderstorm Warning", + "priority": 5 + }, + "SVS": { + "text": "Severe Weather Statement", + "priority": 3 + }, + "SWO": { + "priority": 4, + "text": "Severe Storm Outlook Narrative (AC)" + }, + "SWS": { + "priority": 1, + "text": "State Weather Summary" + }, + "SYN": { + "priority": 1, + "text": "Regional Weather Synopsis" + }, + "TAF": { + "priority": 1, + "text": "Terminal Aerodrome Forecast" + }, + "TAP": { + "priority": 1, + "text": "Terminal Alerting Products" + }, + "TAV": { + "priority": 1, + "text": "Travelers Forecast Table" + }, + "TCA": { + "priority": 1, + "text": "Aviation Tropical Cyclone Advisory" + }, + "TCD": { + "priority": 1, + "text": "Tropical Cyclone Discussion" + }, + "TCE": { + "priority": 1, + "text": "Tropical Cyclone Position Estimate" + }, + "TCM": { + "priority": 1, + "text": "Marine/Aviation Tropical Cyclone Advisory" + }, + "TCP": { + "priority": 1, + "text": "Public Tropical Cyclone Advisory" + }, + "TCS": { + "priority": 1, + "text": "Satellite Tropical Cyclone Summary" + }, + "TCU": { + "priority": 1, + "text": "Tropical Cyclone Update" + }, + "TCV": { + "priority": 4, + "text": "Tropical Cyclone Watch/Warning Break Points" + }, + "TIB": { + "priority": 4, + "text": "Tsunami Bulletin" + }, + "TID": { + "priority": 1, + "text": "Tide Report" + }, + "TMA": { + "priority": 1, + "text": "Tsunami Tide/Seismic Message Acknowledgement" + }, + "TOE": { + "text": "911 Telephone Outage Emergency", + "priority": 5 + }, + "TOR": { + "text": "Tornado Warning", + "priority": 5 + }, + "TPT": { + "priority": 1, + "text": "Temperature Precipitation Table (Natl and Intnl)" + }, + "TSU": { + "priority": 5, + "text": "Tsunami Watch/Warning" + }, + "TUV": { + "priority": 1, + "text": "Weather Bulletin" + }, + "TVL": { + "priority": 1, + "text": "Travelers Forecast" + }, + "TWB": { + "priority": 1, + "text": "Transcribed Weather Broadcast" + }, + "TWD": { + "priority": 1, + "text": "Tropical Weather Discussion" + }, + "TWO": { + "priority": 1, + "text": "Tropical Weather Outlook and Summary" + }, + "TWS": { + "priority": 1, + "text": "Tropical Weather Summary" + }, + "URN": { + "priority": 1, + "text": "Aircraft Reconnaissance" + }, + "UVI": { + "priority": 1, + "text": "Ultraviolet Index" + }, + "VAA": { + "priority": 3, + "text": "Volcanic Activity Advisory" + }, + "VER": { + "priority": 1, + "text": "Forecast Verification Statistics" + }, + "VFT": { + "priority": 1, + "text": "Terminal Aerodrome Forecast (TAF) Verification" + }, + "VOW": { + "text": "Volcano Warning", + "priority": 5 + }, + "WA0": { + "priority": 1, + "text": "Airmet (Pacific)" + }, + "WA1": { + "priority": 1, + "text": "Airmet (Northeast)" + }, + "WA2": { + "priority": 1, + "text": "Airmet (Southeast)" + }, + "WA3": { + "priority": 1, + "text": "Airmet (North Central)" + }, + "WA4": { + "priority": 1, + "text": "Airmet (South Central)" + }, + "WA5": { + "priority": 1, + "text": "Airmet (Rocky Mountains)" + }, + "WA6": { + "priority": 1, + "text": "Airmet (West Coast)" + }, + "WA7": { + "priority": 1, + "text": "Airmet (Juneau, AK)" + }, + "WA8": { + "priority": 1, + "text": "Airmet (Anchorage, AK)" + }, + "WA9": { + "priority": 1, + "text": "Airmet (Fairbanks, AK)" + }, + "WAR": { + "priority": 1, + "text": "Space Environment Warning" + }, + "WAT": { + "priority": 1, + "text": "Space Environment Watch" + }, + "WCN": { + "priority": 5, + "text": "Weather Watch Clearance Notification" + }, + "WCR": { + "priority": 1, + "text": "Weekly Weather and Crop Report" + }, + "WDA": { + "priority": 1, + "text": "Weekly Data for Agriculture" + }, + "WDU": { + "priority": 2, + "text": "Warning Decision Update" + }, + "WEK": { + "priority": 1, + "text": "Routine Space Environment Product Issued Weekly" + }, + "WOU": { + "priority": 4, + "text": "Tornado/Severe Thunderstorm Watch" + }, + "WS1": { + "priority": 1, + "text": "Sigmet (Northeast)" + }, + "WS2": { + "priority": 1, + "text": "Sigmet (Southeast)" + }, + "WS3": { + "priority": 1, + "text": "Sigmet (North Central)" + }, + "WS4": { + "priority": 1, + "text": "Sigmet (South Central)" + }, + "WS5": { + "priority": 1, + "text": "Sigmet (Rocky Mountains)" + }, + "WS6": { + "priority": 1, + "text": "Sigmet (West Coast)" + }, + "WST": { + "priority": 1, + "text": "Tropical Cyclone Sigmet" + }, + "WSV": { + "priority": 1, + "text": "Volcanic Activity Sigmet" + }, + "WSW": { + "text": "Winter Storm Warning", + "priority": 4 + }, + "WWA": { + "priority": 1, + "text": "Watch Status Report" + }, + "WWP": { + "priority": 3, + "text": "Severe Thunderstorm / Tornado Watch Probabilities" + }, + "ZFP": { + "priority": 2, + "text": "Zone Forecast Product" + }, + "BZW": { + "text": "Blizzard Warning", + "priority": 5 + }, + "CFA": { + "text": "Coastal Flood Watch", + "priority": 4 + }, + "FLA": { + "text": "Flood Watch", + "priority": 2 + }, + "HWA": { + "text": "High Wind Watch", + "priority": 4 + }, + "HWW": { + "text": "High Wind Warning", + "priority": 5 + }, + "HUA": { + "text": "Hurricane Watch", + "priority": 4 + }, + "HUW": { + "text": "Hurricane Warning", + "priority": 5 + }, + "SVA": { + "text": "Severe Thunderstorm Watch", + "priority": 4 + }, + "TOA": { + "text": "Tornado Watch", + "priority": 5 + }, + "TRA": { + "text": "Tropical Storm Watch", + "priority": 4 + }, + "TRW": { + "text": "Tropical Storm Warning", + "priority": 5 + }, + "TSA": { + "text": "Tsunami Watch", + "priority": 4 + }, + "TSW": { + "text": "Tsunami Warning", + "priority": 5 + }, + "WSA": { + "text": "Winter Storm Watch", + "priority": 4 + }, + "EAN": { + "text": "Emergency Action Notification", + "priority": 5 + }, + "EAT": { + "text": "Emergency Action Termination", + "priority": 5 + }, + "NIC": { + "text": "National Information Center", + "priority": 5 + }, + "NPT": { + "text": "National Periodic Test", + "priority": 5 + }, + "BHW": { + "text": "Biological Hazard Warning", + "priority": 5 + }, + "BWW": { + "text": "Boil Water Warning", + "priority": 5 + }, + "CWW": { + "text": "Conataminated Water Warning", + "priority": 5 + }, + "DBA": { + "text": "Dam Break Watch", + "priority": 4 + }, + "DBW": { + "text": "Dam Break Warning", + "priority": 5 + }, + "DEW": { + "text": "Contagious Disease Warning", + "priority": 5 + }, + "EVA": { + "text": "Evacuation Watch", + "priority": 4 + }, + "FCW": { + "text": "Food Contamination Warning", + "priority": 3 + }, + "FSW": { + "text": "Flash Freeze Warning", + "priority": 4 + }, + "FZW": { + "text": "Freeze Warning", + "priority": 5 + }, + "IBW": { + "text": "Iceberg Warning", + "priority": 4 + }, + "IFW": { + "text": "Industrial Fire Warning", + "priority": 4 + }, + "LSW": { + "text": "Land Slide Warning", + "priority": 4 + }, + "NAT": { + "text": "National Audible Test", + "priority": 4 + }, + "NST": { + "text": "National Silent Test", + "priority": 4 + }, + "POS": { + "text": "Power Outage Statement", + "priority": 2 + }, + "WFA": { + "text": "Wild Fire Watch", + "priority": 3 + }, + "WFW": { + "text": "Wild Fire Warning", + "priority": 3 + }, + "NMN": { + "text": "Network Message Notification", + "priority": 5 + }, + "AHD": { + "text": "Area Hydrological Discussion", + "priority": 1 + }, + "GMT": { + "text": "AIRMET", + "priority": 1 + }, + "AHO": { + "text": "High Density Observations (USAF/NOAA)", + "priority": 3 + }, + "REP": { + "text": "RECCO Observations (tropical cyclone)", + "priority": 3 + }, + "PIR": { + "text": "Pilot Reports", + "priority": 1 + } +} diff --git a/index.js b/index.js index e69de29..0fb0d72 100644 --- a/index.js +++ b/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: ` ${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: ` ${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 + + + + visitor + + + + */ + + // 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(); +}); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index be35834..28f81d8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index e1e03b7..a5ec3d6 100644 --- a/package.json +++ b/package.json @@ -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" } } diff --git a/wfos.json b/wfos.json new file mode 100644 index 0000000..1b5b26d --- /dev/null +++ b/wfos.json @@ -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" + } +} \ No newline at end of file