forked from ChrisChrome/weather-bot
Compare commits
81 commits
Author | SHA1 | Date | |
---|---|---|---|
|
664e00b7f4 | ||
|
cce9b12645 | ||
|
c82ab68c1e | ||
|
a543386394 | ||
|
1ae72ac979 | ||
|
1a7c1735f4 | ||
|
5b487125aa | ||
|
770813b4a7 | ||
|
ded7a2373c | ||
|
79ae2f4c25 | ||
|
e02faaab18 | ||
|
bd275403e9 | ||
|
9b80f79436 | ||
|
4bb679cef8 | ||
|
e952ec60e7 | ||
|
0227979176 | ||
|
9f31d18a6f | ||
|
d491fda5c8 | ||
|
96912d325a | ||
|
00f97fd6e6 | ||
|
09f3fc957c | ||
|
cbb1842100 | ||
|
f763a95472 | ||
|
d88188182c | ||
|
8b94e7e3f8 | ||
|
d653bc3e90 | ||
|
b5bf06a38d | ||
|
ae63a0d52d | ||
|
73e42e2288 | ||
|
9afdd5626e | ||
|
f6acab4d20 | ||
|
b10308ef36 | ||
|
aa8fe9498f | ||
|
d48fe3085a | ||
|
662e0e775c | ||
|
845fead37a | ||
|
70c30420fc | ||
|
98673be5c7 | ||
|
aac1420964 | ||
|
7a12ed05d3 | ||
|
c223eabc62 | ||
|
6b9438ed10 | ||
|
928cb4b958 | ||
|
42487f22b6 | ||
|
7b96229d01 | ||
|
df411d192d | ||
|
b76a77807b | ||
|
5168c5c582 | ||
|
2ff7a2522a | ||
|
d8b6d04edf | ||
|
70cce9e2ba | ||
|
1001d80313 | ||
|
fbcc902bba | ||
|
6bdc4240be | ||
|
be0d17cf6d | ||
|
cea680a013 | ||
|
ff0f4da7ad | ||
|
539eccf7e2 | ||
|
417777edb9 | ||
|
b382b95f6e | ||
|
ea8c2be4a1 | ||
|
177f2c8dc3 | ||
|
c77577a382 | ||
|
46000e92ee | ||
|
04d3cb80e0 | ||
|
dcf5518288 | ||
|
3e322de6cd | ||
|
476f2fd946 | ||
|
ca785ea546 | ||
|
199cd70da5 | ||
|
4b8d0515f9 | ||
|
64f4bc676e | ||
|
a8aebe683c | ||
|
249788ae8c | ||
|
0f495feb55 | ||
|
ec176a87c2 | ||
|
081b4ef261 | ||
|
e5d9a687d4 | ||
|
0a9376d4d7 | ||
|
2e92abfb21 | ||
|
e9b206afff |
15
.forgejo/workflows/ptero-push.yml
Normal file
15
.forgejo/workflows/ptero-push.yml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
# Workflow that runs on `chris-runner`, tells a pterodactyl server at https://panel.chrischro.me/ to restart server 8f44f1fd-8d29-4553-96f1-05dacfca454f with token stored in PTERO_KEY
|
||||||
|
name: ptero-push
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: docker
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Run script
|
||||||
|
run: |
|
||||||
|
curl -X POST -H "Authorization: Bearer ${{ secrets.PTERO_KEY }}" -H "Content-Type: application/json" https://panel.chrischro.me/api/client/servers/8f44f1fd-8d29-4553-96f1-05dacfca454f/power -d '{"signal":"restart"}'
|
1
.vscode/settings.json
vendored
Normal file
1
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{}
|
7
PRIVACY.md
Normal file
7
PRIVACY.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# Privacy Policy
|
||||||
|
|
||||||
|
Just to make this simple, heres a list of whats stored and how it's used
|
||||||
|
- Discord channel/user IDs of subscribed channels/DMs - Should be obvious, in case it's not, we need these to know where to send alerts.
|
||||||
|
- IEM rooms and filters - Should also be obvious, but again, we need to know which rooms you subscribed to
|
||||||
|
|
||||||
|
Other than that, occasional debug logging may be enabled to fix bugs, and any logs gathered from debugging will be erased immidiately.
|
5
TERMS.md
Normal file
5
TERMS.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# Terms of Service
|
||||||
|
## This is mostly here to comply with Discord verified bot stuff
|
||||||
|
|
||||||
|
Other than complying with the GPL-3.0 License when contributing/using the code of this project, this bot is NOT to be used as an emergency alerting system, and should NOT be trusted with life/property under any circumstances.
|
||||||
|
Chris Chrome and any other contributors are not to be held liable should this not work in an emergency, as you shouldn't be using it for emergency alerts.
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"debug": 0,
|
"debug": 0,
|
||||||
|
"voice_enabled": true,
|
||||||
"priorityColors": {
|
"priorityColors": {
|
||||||
"1": "#00AFFF",
|
"1": "#00AFFF",
|
||||||
"2": "#00FF00",
|
"2": "#00FF00",
|
||||||
|
@ -26,7 +27,6 @@
|
||||||
"username": "YOUR_USERNAME",
|
"username": "YOUR_USERNAME",
|
||||||
"password": "YOUR_PASSWORD"
|
"password": "YOUR_PASSWORD"
|
||||||
},
|
},
|
||||||
"voice_enabled": true,
|
|
||||||
|
|
||||||
"uptime-kuma": {
|
"uptime-kuma": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
|
|
|
@ -91,6 +91,4 @@
|
||||||
"zsechat@conference.weather.im",
|
"zsechat@conference.weather.im",
|
||||||
"zdcchat@conference.weather.im",
|
"zdcchat@conference.weather.im",
|
||||||
"znychat@conference.weather.im"
|
"znychat@conference.weather.im"
|
||||||
|
|
||||||
|
|
||||||
]
|
]
|
|
@ -2,7 +2,7 @@
|
||||||
{
|
{
|
||||||
"name": "subscribe",
|
"name": "subscribe",
|
||||||
"description": "Subscribe to a weather.im room",
|
"description": "Subscribe to a weather.im room",
|
||||||
"default_member_permissions": 0,
|
"default_member_permissions": 16,
|
||||||
"options": [
|
"options": [
|
||||||
{
|
{
|
||||||
"name": "room",
|
"name": "room",
|
||||||
|
@ -66,7 +66,7 @@
|
||||||
{
|
{
|
||||||
"name": "unsubscribe",
|
"name": "unsubscribe",
|
||||||
"description": "Unsubscribe from a weather.im room",
|
"description": "Unsubscribe from a weather.im room",
|
||||||
"default_member_permissions": 0,
|
"default_member_permissions": 16,
|
||||||
"options": [
|
"options": [
|
||||||
{
|
{
|
||||||
"name": "room",
|
"name": "room",
|
||||||
|
@ -81,11 +81,13 @@
|
||||||
{
|
{
|
||||||
"name": "list",
|
"name": "list",
|
||||||
"description": "List all subscribed rooms for this channel",
|
"description": "List all subscribed rooms for this channel",
|
||||||
"default_member_permissions": 0
|
"default_member_permissions": 16
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "about",
|
"name": "about",
|
||||||
"description": "About this bot"
|
"description": "About this bot",
|
||||||
|
"integration_types": [0,1],
|
||||||
|
"contexts": [0, 1, 2]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "rooms",
|
"name": "rooms",
|
||||||
|
@ -93,19 +95,23 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "setupall",
|
"name": "setupall",
|
||||||
"description": "[OWNER ONLY] Setup channels in a category for all rooms",
|
"description": "[BOT OWNER ONLY] Setup channels in a category for all rooms",
|
||||||
"default_member_permissions": 0,
|
"default_member_permissions": 0,
|
||||||
"type": 1
|
"type": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "support",
|
"name": "support",
|
||||||
"description": "Get support for the bot",
|
"description": "Get support for the bot",
|
||||||
"type": 1
|
"type": 1,
|
||||||
|
"integration_types": [0,1],
|
||||||
|
"contexts": [0, 1, 2]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "outlook",
|
"name": "outlook",
|
||||||
"description": "Get day 1-8 storm or fire outlook from the SPC",
|
"description": "Get day 1-8 storm or fire outlook from the SPC",
|
||||||
"type": 1,
|
"type": 1,
|
||||||
|
"integration_types": [0,1],
|
||||||
|
"contexts": [0, 1, 2],
|
||||||
"options": [
|
"options": [
|
||||||
{
|
{
|
||||||
"name": "day",
|
"name": "day",
|
||||||
|
@ -222,6 +228,29 @@
|
||||||
{
|
{
|
||||||
"name": "alertmap",
|
"name": "alertmap",
|
||||||
"description": "Get a map of active alerts",
|
"description": "Get a map of active alerts",
|
||||||
"type": 1
|
"type": 1,
|
||||||
|
"integration_types": [0,1],
|
||||||
|
"contexts": [0, 1, 2]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "forecast",
|
||||||
|
"description": "Get a forecast for a location",
|
||||||
|
"type": 1,
|
||||||
|
"integration_types": [0,1],
|
||||||
|
"contexts": [0, 1, 2],
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"name": "location",
|
||||||
|
"description": "Location to get forecast for (In the United States)",
|
||||||
|
"type": 3,
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "periods",
|
||||||
|
"description": "Number of periods to get forecast for",
|
||||||
|
"type": 4,
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
|
@ -217,7 +217,7 @@
|
||||||
},
|
},
|
||||||
"DSW": {
|
"DSW": {
|
||||||
"text": "Dust Storm Warning",
|
"text": "Dust Storm Warning",
|
||||||
"priority": 5
|
"priority": 4
|
||||||
},
|
},
|
||||||
"EFP": {
|
"EFP": {
|
||||||
"priority": 1,
|
"priority": 1,
|
||||||
|
@ -365,7 +365,7 @@
|
||||||
},
|
},
|
||||||
"FFW": {
|
"FFW": {
|
||||||
"text": "Flash Flood Warning",
|
"text": "Flash Flood Warning",
|
||||||
"priority": 4
|
"priority": 5
|
||||||
},
|
},
|
||||||
"FLN": {
|
"FLN": {
|
||||||
"priority": 1,
|
"priority": 1,
|
||||||
|
@ -385,7 +385,7 @@
|
||||||
},
|
},
|
||||||
"FRW": {
|
"FRW": {
|
||||||
"text": "Fire Warning",
|
"text": "Fire Warning",
|
||||||
"priority": 4
|
"priority": 3
|
||||||
},
|
},
|
||||||
"FSH": {
|
"FSH": {
|
||||||
"priority": 1,
|
"priority": 1,
|
||||||
|
@ -680,7 +680,7 @@
|
||||||
"text": "Data Mgt Message"
|
"text": "Data Mgt Message"
|
||||||
},
|
},
|
||||||
"NPW": {
|
"NPW": {
|
||||||
"priority": 1,
|
"priority": 3,
|
||||||
"text": "Non-Precipitation Warnings / Watches / Advisories"
|
"text": "Non-Precipitation Warnings / Watches / Advisories"
|
||||||
},
|
},
|
||||||
"NSH": {
|
"NSH": {
|
||||||
|
@ -1160,7 +1160,7 @@
|
||||||
"text": "Tropical Cyclone Update"
|
"text": "Tropical Cyclone Update"
|
||||||
},
|
},
|
||||||
"TCV": {
|
"TCV": {
|
||||||
"priority": 1,
|
"priority": 4,
|
||||||
"text": "Tropical Cyclone Watch/Warning Break Points"
|
"text": "Tropical Cyclone Watch/Warning Break Points"
|
||||||
},
|
},
|
||||||
"TIB": {
|
"TIB": {
|
||||||
|
@ -1308,7 +1308,7 @@
|
||||||
"text": "Routine Space Environment Product Issued Weekly"
|
"text": "Routine Space Environment Product Issued Weekly"
|
||||||
},
|
},
|
||||||
"WOU": {
|
"WOU": {
|
||||||
"priority": 4,
|
"priority": 5,
|
||||||
"text": "Tornado/Severe Thunderstorm Watch"
|
"text": "Tornado/Severe Thunderstorm Watch"
|
||||||
},
|
},
|
||||||
"WS1": {
|
"WS1": {
|
||||||
|
@ -1345,7 +1345,7 @@
|
||||||
},
|
},
|
||||||
"WSW": {
|
"WSW": {
|
||||||
"text": "Winter Storm Warning",
|
"text": "Winter Storm Warning",
|
||||||
"priority": 5
|
"priority": 4
|
||||||
},
|
},
|
||||||
"WWA": {
|
"WWA": {
|
||||||
"priority": 1,
|
"priority": 1,
|
||||||
|
@ -1365,11 +1365,11 @@
|
||||||
},
|
},
|
||||||
"CFA": {
|
"CFA": {
|
||||||
"text": "Coastal Flood Watch",
|
"text": "Coastal Flood Watch",
|
||||||
"priority": 4
|
"priority": 3
|
||||||
},
|
},
|
||||||
"FLA": {
|
"FLA": {
|
||||||
"text": "Flood Watch",
|
"text": "Flood Watch",
|
||||||
"priority": 2
|
"priority": 3
|
||||||
},
|
},
|
||||||
"HWA": {
|
"HWA": {
|
||||||
"text": "High Wind Watch",
|
"text": "High Wind Watch",
|
||||||
|
@ -1389,7 +1389,7 @@
|
||||||
},
|
},
|
||||||
"SVA": {
|
"SVA": {
|
||||||
"text": "Severe Thunderstorm Watch",
|
"text": "Severe Thunderstorm Watch",
|
||||||
"priority": 4
|
"priority": 5
|
||||||
},
|
},
|
||||||
"TOA": {
|
"TOA": {
|
||||||
"text": "Tornado Watch",
|
"text": "Tornado Watch",
|
||||||
|
@ -1405,7 +1405,7 @@
|
||||||
},
|
},
|
||||||
"TSA": {
|
"TSA": {
|
||||||
"text": "Tsunami Watch",
|
"text": "Tsunami Watch",
|
||||||
"priority": 4
|
"priority": 5
|
||||||
},
|
},
|
||||||
"TSW": {
|
"TSW": {
|
||||||
"text": "Tsunami Warning",
|
"text": "Tsunami Warning",
|
||||||
|
@ -1514,5 +1514,17 @@
|
||||||
"GMT": {
|
"GMT": {
|
||||||
"text": "AIRMET",
|
"text": "AIRMET",
|
||||||
"priority": 1
|
"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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
11
data/nwrstreams.json
Normal file
11
data/nwrstreams.json
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"callsigns": {
|
||||||
|
"WXL46": "https://icecast.sirenarchive.xyz/NWR/WXL46",
|
||||||
|
"WXJ61": "https://icecast.sirenarchive.xyz/NWR/WXJ61",
|
||||||
|
"KZZ57": "https://wxradio.org/IL-Rockford-KZZ57",
|
||||||
|
"KXI41": "https://wxradio.org/IL-CrystalLake-KXI41",
|
||||||
|
"KHB34": "https://wxradio.org/FL-Miami-KHB34",
|
||||||
|
"WNG663": "https://wxradio.org/FL-Princeton-WNG663",
|
||||||
|
"KEC80": "https://wxradio.org/GA-Atlanta-KEC80"
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,13 +1,13 @@
|
||||||
{
|
{
|
||||||
"convective": [
|
"convective": [
|
||||||
"https://www.spc.noaa.gov/products/outlook/day1otlk.gif",
|
"https://weather.cod.edu/cdata/text/images/spc/co/day1/categorical/spccoday1.categorical.latest.png",
|
||||||
"https://www.spc.noaa.gov/products/outlook/day2otlk.gif",
|
"https://climate.cod.edu/data/text/images/spc/co/day2/categorical/spccoday2.categorical.latest.png",
|
||||||
"https://www.spc.noaa.gov/products/outlook/day3otlk.gif",
|
"https://climate.cod.edu/data/text/images/spc/co/day3/categorical/spccoday3.categorical.latest.png",
|
||||||
"https://www.spc.noaa.gov/products/exper/day4-8/day4prob.gif",
|
"https://climate.cod.edu/data/text/images/spc/co/day4/severe/spccoday4.severe.latest.png",
|
||||||
"https://www.spc.noaa.gov/products/exper/day4-8/day5prob.gif",
|
"https://climate.cod.edu/data/text/images/spc/co/day5/severe/spccoday5.severe.latest.png",
|
||||||
"https://www.spc.noaa.gov/products/exper/day4-8/day6prob.gif",
|
"https://climate.cod.edu/data/text/images/spc/co/day6/severe/spccoday6.severe.latest.png",
|
||||||
"https://www.spc.noaa.gov/products/exper/day4-8/day7prob.gif",
|
"https://climate.cod.edu/data/text/images/spc/co/day7/severe/spccoday7.severe.latest.png",
|
||||||
"https://www.spc.noaa.gov/products/exper/day4-8/day8prob.gif"
|
"https://climate.cod.edu/data/text/images/spc/co/day8/severe/spccoday8.severe.latest.png"
|
||||||
],
|
],
|
||||||
"fire": [
|
"fire": [
|
||||||
"https://www.spc.noaa.gov/products/exper/fire_wx/imgs/day1otlk_fire.gif",
|
"https://www.spc.noaa.gov/products/exper/fire_wx/imgs/day1otlk_fire.gif",
|
||||||
|
|
187
data/satellites.json
Normal file
187
data/satellites.json
Normal file
|
@ -0,0 +1,187 @@
|
||||||
|
{
|
||||||
|
"GOES-16": {
|
||||||
|
"products": {
|
||||||
|
"Full Disk": {
|
||||||
|
"Visible": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/FD/GEOCOLOR/1808x1808.jpg",
|
||||||
|
"Airmass": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/FD/AirMass/1808x1808.jpg",
|
||||||
|
"Infrared": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/FD/13/1808x1808.jpg",
|
||||||
|
"Water Vapor": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/FD/10/1808x1808.jpg"
|
||||||
|
},
|
||||||
|
"Floater 1": {
|
||||||
|
"Visible": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/MESO/M1/GEOCOLOR/1000x1000.jpg",
|
||||||
|
"Infrared": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/MESO/M1/13/1000x1000.jpg",
|
||||||
|
"Water Vapor": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/MESO/M1/10/1000x1000.jpg"
|
||||||
|
},
|
||||||
|
"Floater 2": {
|
||||||
|
"Visible": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/MESO/M2/GEOCOLOR/1000x1000.jpg",
|
||||||
|
"Infrared": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/MESO/M2/13/1000x1000.jpg",
|
||||||
|
"Water Vapor": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/MESO/M2/10/1000x1000.jpg"
|
||||||
|
},
|
||||||
|
"United States": {
|
||||||
|
"Visible": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/CONUS/GEOCOLOR/2500x1500.jpg",
|
||||||
|
"Airmass": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/CONUS/AirMass/2500x1500.jpg",
|
||||||
|
"Infrared": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/CONUS/13/2500x1500.jpg",
|
||||||
|
"Water Vapor": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/CONUS/10/2500x1500.jpg"
|
||||||
|
},
|
||||||
|
"Canada": {
|
||||||
|
"Visible": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/can/GEOCOLOR/2250x1125.jpg",
|
||||||
|
"Airmass": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/can/AirMass/2250x1125.jpg",
|
||||||
|
"Infrared": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/can/13/2250x1125.jpg",
|
||||||
|
"Water Vapor": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/can/10/2250x1125.jpg"
|
||||||
|
},
|
||||||
|
"Mexico": {
|
||||||
|
"Visible": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/mex/GEOCOLOR/1000x1000.jpg",
|
||||||
|
"Airmass": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/mex/AirMass/1000x1000.jpg",
|
||||||
|
"Infrared": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/mex/13/1000x1000.jpg",
|
||||||
|
"Water Vapor": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/mex/10/1000x1000.jpg"
|
||||||
|
},
|
||||||
|
"US East Coast": {
|
||||||
|
"Visible": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/eus/GEOCOLOR/1000x1000.jpg",
|
||||||
|
"Airmass": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/eus/AirMass/1000x1000.jpg",
|
||||||
|
"Infrared": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/eus/13/1000x1000.jpg",
|
||||||
|
"Water Vapor": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/eus/10/1000x1000.jpg"
|
||||||
|
},
|
||||||
|
"Gulf of Mexico": {
|
||||||
|
"Visible": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/gm/GEOCOLOR/1000x1000.jpg",
|
||||||
|
"Airmass": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/gm/AirMass/1000x1000.jpg",
|
||||||
|
"Infrared": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/gm/13/1000x1000.jpg",
|
||||||
|
"Water Vapor": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/gm/10/1000x1000.jpg"
|
||||||
|
},
|
||||||
|
"Puerto Rico": {
|
||||||
|
"Visible": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/pr/GEOCOLOR/1200x1200.jpg",
|
||||||
|
"Airmass": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/pr/AirMass/1200x1200.jpg",
|
||||||
|
"Infrared": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/pr/13/1200x1200.jpg",
|
||||||
|
"Water Vapor": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/pr/10/1200x1200.jpg"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"GOES-18": {
|
||||||
|
"products": {
|
||||||
|
"Full Disk": {
|
||||||
|
"Visible": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/FD/GEOCOLOR/1808x1808.jpg",
|
||||||
|
"Airmass": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/FD/AirMass/1808x1808.jpg",
|
||||||
|
"Infrared": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/FD/13/1808x1808.jpg",
|
||||||
|
"Water Vapor": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/FD/10/1808x1808.jpg"
|
||||||
|
},
|
||||||
|
"Floater 1": {
|
||||||
|
"Visible": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/MESO/M1/GEOCOLOR/1000x1000.jpg",
|
||||||
|
"Infrared": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/MESO/M1/13/1000x1000.jpg",
|
||||||
|
"Water Vapor": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/MESO/M1/10/1000x1000.jpg"
|
||||||
|
},
|
||||||
|
"Floater 2": {
|
||||||
|
"Visible": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/MESO/M2/GEOCOLOR/1000x1000.jpg",
|
||||||
|
"Infrared": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/MESO/M2/13/1000x1000.jpg",
|
||||||
|
"Water Vapor": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/MESO/M2/10/1000x1000.jpg"
|
||||||
|
},
|
||||||
|
"US West Coast": {
|
||||||
|
"Visible": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/wus/GEOCOLOR/1000x1000.jpg",
|
||||||
|
"Airmass": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/wus/AirMass/1000x1000.jpg",
|
||||||
|
"Infrared": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/wus/13/1000x1000.jpg",
|
||||||
|
"Water Vapor": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/wus/10/1000x1000.jpg"
|
||||||
|
},
|
||||||
|
"Hawaii": {
|
||||||
|
"Visible": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/SECTOR/hi/GEOCOLOR/1200x1200.jpg",
|
||||||
|
"Airmass": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/SECTOR/hi/AirMass/1200x1200.jpg",
|
||||||
|
"Infrared": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/SECTOR/hi/13/1200x1200.jpg",
|
||||||
|
"Water Vapor": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/SECTOR/hi/10/1200x1200.jpg"
|
||||||
|
},
|
||||||
|
"Alaska": {
|
||||||
|
"Visible": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/SECTOR/ak/GEOCOLOR/1000x1000.jpg",
|
||||||
|
"Airmass": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/SECTOR/ak/AirMass/1000x1000.jpg",
|
||||||
|
"Infrared": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/SECTOR/ak/13/1000x1000.jpg",
|
||||||
|
"Water Vapor": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/SECTOR/ak/10/1000x1000.jpg"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Himawari": {
|
||||||
|
"products": {
|
||||||
|
"Full Disk": {
|
||||||
|
"Visible": "https://rammb.cira.colostate.edu/ramsdis/online/images/latest_hi_res/himawari-8/full_disk_ahi_true_color.jpg",
|
||||||
|
"Airmass": "https://rammb.cira.colostate.edu/ramsdis/online/images/latest_hi_res/himawari-8/full_disk_ahi_rgb_airmass.jpg",
|
||||||
|
"Infrared": "https://www.ssec.wisc.edu/data/geo/images/himawari09/latest-himawari09_11_fd.gif",
|
||||||
|
"Water Vapor": "https://www.ssec.wisc.edu/data/geo/images/himawari09/latest-himawari09_10_fd.gif"
|
||||||
|
},
|
||||||
|
"Floater 1": {
|
||||||
|
"Visible": "https://rammb.cira.colostate.edu/ramsdis/online/images/latest/himawari-8/floater_02_geocolor.pngv",
|
||||||
|
"Airmass": "https://rammb.cira.colostate.edu/ramsdis/online/images/latest/himawari-8/floater_02_rgb_airmass.png"
|
||||||
|
},
|
||||||
|
"American Samoa": {
|
||||||
|
"Visible": "https://rammb.cira.colostate.edu/ramsdis/online/images/latest_hi_res/himawari-8/american_samoa_ahi_natural_color.png",
|
||||||
|
"Airmass": "https://rammb.cira.colostate.edu/ramsdis/online/images/latest_hi_res/himawari-8/american_samoa_ahi_rgb_airmass.png"
|
||||||
|
},
|
||||||
|
"Australia": {
|
||||||
|
"Visible": "https://rammb.cira.colostate.edu/ramsdis/online/images/latest/himawari-8/australia_true_color.jpg",
|
||||||
|
"Airmass": "https://rammb.cira.colostate.edu/ramsdis/online/images/latest_hi_res/himawari-8/australia_ahi_rgb_airmass.png"
|
||||||
|
},
|
||||||
|
"New Zealand": {
|
||||||
|
"Visible": "https://rammb.cira.colostate.edu/ramsdis/online/images/latest_hi_res/himawari-8/new_zealand_ahi_natural_color.png",
|
||||||
|
"Airmass": "https://rammb.cira.colostate.edu/ramsdis/online/images/latest_hi_res/himawari-8/new_zealand_ahi_rgb_airmass.png"
|
||||||
|
},
|
||||||
|
"Guam": {
|
||||||
|
"Visible": "https://rammb.cira.colostate.edu/ramsdis/online/images/latest_hi_res/himawari-8/guam_ahi_natural_color.png",
|
||||||
|
"Airmass": "https://rammb.cira.colostate.edu/ramsdis/online/images/latest_hi_res/himawari-8/guam_ahi_rgb_airmass.png"
|
||||||
|
},
|
||||||
|
"Hawaii": {
|
||||||
|
"Visible": "https://rammb.cira.colostate.edu/ramsdis/online/images/latest_hi_res/himawari-8/hawaii_ahi_natural_color.png"
|
||||||
|
},
|
||||||
|
"Japan": {
|
||||||
|
"Visible": "https://rammb.cira.colostate.edu/ramsdis/online/images/latest_hi_res/himawari-8/japan_ahi_natural_color.png",
|
||||||
|
"Airmass": "https://rammb.cira.colostate.edu/ramsdis/online/images/latest_hi_res/himawari-8/japan_ahi_rgb_airmass.png"
|
||||||
|
},
|
||||||
|
"Russia": {
|
||||||
|
"Visible": "https://rammb.cira.colostate.edu/ramsdis/online/images/latest/himawari-8/eastern_russia_true_color.jpg"
|
||||||
|
},
|
||||||
|
"China": {
|
||||||
|
"Visible": "https://rammb.cira.colostate.edu/ramsdis/online/images/latest_hi_res/himawari-8/eastern_china_ahi_natural_color.png",
|
||||||
|
"Airmass": "https://rammb.cira.colostate.edu/ramsdis/online/images/latest_hi_res/himawari-8/eastern_china_ahi_rgb_airmass.png"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"EWS-G2": {
|
||||||
|
"products": {
|
||||||
|
"Full Disk": {
|
||||||
|
"Visible": "https://www.ssec.wisc.edu/data/geo/images/ews-g1/latest_ews-g1_01_fd.gif",
|
||||||
|
"Water Vapor": "https://www.ssec.wisc.edu/data/geo/images/ews-g1/latest_ews-g1_03_fd.gif"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"FY-2G": {
|
||||||
|
"products": {
|
||||||
|
"Full Disk": {
|
||||||
|
"Visible": "https://www.ssec.wisc.edu/data/geo/images/fy2g/latest_fy2g_01_fd.gif",
|
||||||
|
"Infrared": "https://www.ssec.wisc.edu/data/geo/images/fy2g/latest_fy2g_02_fd.gif",
|
||||||
|
"Water Vapor": "https://www.ssec.wisc.edu/data/geo/images/fy2g/latest_fy2g_04_fd.gif"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"GK-2A": {
|
||||||
|
"products": {
|
||||||
|
"Full Disk": {
|
||||||
|
"Infrared": "https://kiwiweather.com/gk-2a/FD_sanchez.jpg"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Meteosat 9": {
|
||||||
|
"products": {
|
||||||
|
"Full Disk": {
|
||||||
|
"Visible": "https://www.ssec.wisc.edu/data/geo/images/met-iodc/latest_met-iodc_01_fd.jpg",
|
||||||
|
"Infrared": "https://www.ssec.wisc.edu/data/geo/images/met-iodc/latest_met-iodc_04_fd.jpg",
|
||||||
|
"Water Vapor": "https://www.ssec.wisc.edu/data/geo/images/met-iodc/latest_met-iodc_06_fd.jpg"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Meteosat 10": {
|
||||||
|
"products": {
|
||||||
|
"Full Disk": {
|
||||||
|
"Visible": "https://www.ssec.wisc.edu/data/geo/images/met-prime/latest_met-prime_01_fd.gif",
|
||||||
|
"Infrared": "https://www.ssec.wisc.edu/data/geo/images/met-prime/latest_met-prime_04_fd.gif",
|
||||||
|
"Water Vapor": "https://www.ssec.wisc.edu/data/geo/images/met-prime/latest_met-prime_06_fd.gif"
|
||||||
|
},
|
||||||
|
"Europe": {
|
||||||
|
"Visible": "https://www.ssec.wisc.edu/data/geo/images/met-prime/latest_met-prime_01_euro.gif",
|
||||||
|
"Infrared": "https://www.ssec.wisc.edu/data/geo/images/met-prime/latest_met-prime_04_euro.gif",
|
||||||
|
"Water Vapor": "https://www.ssec.wisc.edu/data/geo/images/met-prime/latest_met-prime_06_euro.gif"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,30 +0,0 @@
|
||||||
{
|
|
||||||
"GOES-16": [
|
|
||||||
{
|
|
||||||
"name": "GeoColor",
|
|
||||||
"url": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/CONUS/GEOCOLOR/latest.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Infrared",
|
|
||||||
"url": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/CONUS/13/latest.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "FullDisk",
|
|
||||||
"url": "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/FD/GEOCOLOR/678x678.jpg"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"GOES-18": [
|
|
||||||
{
|
|
||||||
"name": "GeoColor",
|
|
||||||
"url": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/CONUS/GEOCOLOR/latest.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Infrared",
|
|
||||||
"url": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/CONUS/13/latest.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "FullDisk",
|
|
||||||
"url": "https://cdn.star.nesdis.noaa.gov/GOES18/ABI/FD/GEOCOLOR/678x678.jpg"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
138
funcs.js
Normal file
138
funcs.js
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
const geolib = require("geolib");
|
||||||
|
// Use OSM API to get coordinates https://nominatim.openstreetmap.org/search?q=search+query&format=json&limit=1
|
||||||
|
const getCoordinates = async (location) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// Make location url friendly
|
||||||
|
location = encodeURIComponent(location);
|
||||||
|
const url = `https://nominatim.openstreetmap.org/search?q=${location}&format=json&limit=1`;
|
||||||
|
// use custom useragent (discord-iem-bot, chris@chrischro.me)
|
||||||
|
const options = {
|
||||||
|
headers: {
|
||||||
|
'User-Agent': 'Discord-IEM-Bot/1.0 (chris@chrischro.me)',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
// Make request
|
||||||
|
fetch(url, options)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.length > 0) {
|
||||||
|
resolve({
|
||||||
|
lat: data[0].lat,
|
||||||
|
lon: data[0].lon,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
reject('Location not found');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
const getForecast = async (lat, lon) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const url = `https://api.weather.gov/points/${lat},${lon}`;
|
||||||
|
// use same custom ua
|
||||||
|
const options = {
|
||||||
|
headers: {
|
||||||
|
'User-Agent': 'Discord-IEM-Bot/1.0 (chris@chrischro.me)',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
// Make request
|
||||||
|
fetch(url, options)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.properties?.forecast) {
|
||||||
|
fetch(data.properties.forecast)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data2 => {
|
||||||
|
data2.properties.relativeLocation = data.properties.relativeLocation;
|
||||||
|
resolve(data2);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
reject('Forecast not found');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
const getWeatherBySearch = async (search) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
getCoordinates(search)
|
||||||
|
.then(coords => {
|
||||||
|
getForecast(coords.lat, coords.lon)
|
||||||
|
.then(data => {
|
||||||
|
resolve(data);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateDiscordEmbeds = (forecastData, numOfDays) => {
|
||||||
|
// Take the first 7 periods and make them into embeds
|
||||||
|
const embeds = [];
|
||||||
|
if (!numOfDays) numOfDays = 1;
|
||||||
|
for (let i = 0; i < numOfDays; i++) {
|
||||||
|
const period = forecastData.properties.periods[i];
|
||||||
|
if (period.icon?.startsWith("/")) {
|
||||||
|
period.icon = `https://api.weather.gov${period.icon}`
|
||||||
|
}
|
||||||
|
const embed = {
|
||||||
|
title: `${period.name} in ${forecastData.properties.relativeLocation.properties.city} ${forecastData.properties.relativeLocation.properties.state}`,
|
||||||
|
description: `Valid <t:${new Date(period.startTime)/1000}:f> - <t:${new Date(period.endTime)/1000}:f>\n${period.detailedForecast}`,
|
||||||
|
timestamp: new Date(period.startTime),
|
||||||
|
thumbnail: {
|
||||||
|
url: period.icon,
|
||||||
|
},
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'Temperature',
|
||||||
|
value: `${period.temperature}°F`,
|
||||||
|
inline: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Wind',
|
||||||
|
value: `${period.windDirection}@${period.windSpeed}`,
|
||||||
|
inline: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Precipitation',
|
||||||
|
value: period.probabilityOfPrecipitation?.value ? period.probabilityOfPrecipitation?.value + '%' : '0%',
|
||||||
|
inline: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Humidity',
|
||||||
|
value: period.relativeHumidity?.value ? period.relativeHumidity.value + '%' : "0%",
|
||||||
|
}
|
||||||
|
|
||||||
|
],
|
||||||
|
footer: {
|
||||||
|
text: `Data provided by the National Weather Service · Elevation ${forecastData.properties.elevation.value}m`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
embeds.push(embed);
|
||||||
|
}
|
||||||
|
return embeds;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getCoordinates,
|
||||||
|
getForecast,
|
||||||
|
getWeatherBySearch,
|
||||||
|
generateDiscordEmbeds
|
||||||
|
};
|
613
index.js
613
index.js
|
@ -1,11 +1,13 @@
|
||||||
// Requires
|
// Requires
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const config = require("./config.json");
|
const config = require("./config.json");
|
||||||
|
const funcs = require("./funcs.js");
|
||||||
const wfos = require("./data/wfos.json");
|
const wfos = require("./data/wfos.json");
|
||||||
const blacklist = require("./data/blacklist.json");
|
const blacklist = require("./data/blacklist.json");
|
||||||
const events = require("./data/events.json");
|
const events = require("./data/events.json");
|
||||||
const outlookURLs = require("./data/outlook.json");
|
const outlookURLs = require("./data/outlook.json");
|
||||||
const sattelites = require("./data/sattelites.json");
|
const satellites = require("./data/satellites.json");
|
||||||
|
const nwrstreams = {callsigns:{}};
|
||||||
const Jimp = require("jimp");
|
const Jimp = require("jimp");
|
||||||
const { client, xml } = require("@xmpp/client");
|
const { client, xml } = require("@xmpp/client");
|
||||||
const fetch = require("node-fetch");
|
const fetch = require("node-fetch");
|
||||||
|
@ -14,8 +16,12 @@ const Discord = require("discord.js");
|
||||||
const dVC = require("@discordjs/voice");
|
const dVC = require("@discordjs/voice");
|
||||||
const colors = require("colors");
|
const colors = require("colors");
|
||||||
const sqlite3 = require("sqlite3").verbose();
|
const sqlite3 = require("sqlite3").verbose();
|
||||||
|
|
||||||
|
satMessages = {};
|
||||||
|
|
||||||
// Setup Discord
|
// Setup Discord
|
||||||
const discord = new Discord.Client({
|
const discord = new Discord.Client({
|
||||||
|
|
||||||
intents: [
|
intents: [
|
||||||
"Guilds",
|
"Guilds",
|
||||||
"GuildVoiceStates",
|
"GuildVoiceStates",
|
||||||
|
@ -37,8 +43,9 @@ const db = new sqlite3.Database("channels.db", (err) => {
|
||||||
}
|
}
|
||||||
console.log(`${colors.cyan("[INFO]")} Connected to the database`);
|
console.log(`${colors.cyan("[INFO]")} Connected to the database`);
|
||||||
// Create tables if they dont exist
|
// Create tables if they dont exist
|
||||||
db.run(`CREATE TABLE IF NOT EXISTS channels (channelid TEXT, iemchannel TEXT, custommessage TEXT, minPriority INTEGER, "filter" TEXT, filterevt TEXT);`);
|
db.run(`CREATE TABLE IF NOT EXISTS channels (channelid TEXT, iemchannel TEXT, custommessage TEXT, minPriority INTEGER, "filter" TEXT, filterEvt TEXT);`);
|
||||||
db.run(`CREATE TABLE IF NOT EXISTS userAlerts (userid TEXT, iemchannel TEXT, filter TEXT, filterEvt TEXT, minPriority INT, custommessage TEXT);`);
|
db.run(`CREATE TABLE IF NOT EXISTS userAlerts (userid TEXT, iemchannel TEXT, filter TEXT, filterEvt TEXT, minPriority INT, custommessage TEXT);`);
|
||||||
|
db.run(`ALTER TABLE channels RENAME COLUMN filterevt TO filterEvt;`)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@ -82,14 +89,24 @@ const getUniqueChannels = function () {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error(err.message);
|
console.error(err.message);
|
||||||
}
|
}
|
||||||
resolve(rows.length);
|
// 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
|
// Get first url in a string, return object {string, url} remove the url from the string
|
||||||
const getFirstURL = function (string) {
|
const getFirstURL = function (string) {
|
||||||
const url = string.match(/(https?:\/\/[^\s]+)/g);
|
url = string.match(/(https?:\/\/[^\s]+)/g);
|
||||||
if (!url) return { string, url: null };
|
if (!url) return { string, url: null };
|
||||||
const newString = string.replace(url[0], "");
|
const newString = string.replace(url[0], "");
|
||||||
return { string: newString, url: url[0] };
|
return { string: newString, url: url[0] };
|
||||||
|
@ -247,6 +264,24 @@ var errCount = 0;
|
||||||
const curUUID = generateUUID();
|
const curUUID = generateUUID();
|
||||||
|
|
||||||
|
|
||||||
|
// nwrstreams setup
|
||||||
|
// get icecast json data
|
||||||
|
const fetchNWRstreams = () => {
|
||||||
|
fetch("https://icestats.weatherradio.org/").then((res) => {
|
||||||
|
res.json().then((json) => {
|
||||||
|
json.icestats.source.forEach((source) => {
|
||||||
|
nwrstreams.callsigns[source.server_name] = source.listenurl;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
console.log(`${colors.cyan("[INFO]")} Fetched NWR streams`);
|
||||||
|
}).catch((err) => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchNWRstreams();
|
||||||
|
setInterval(fetchNWRstreams, 5 * 60 * 1000); // Every 5 minutes
|
||||||
|
|
||||||
const xmpp = client({
|
const xmpp = client({
|
||||||
service: "xmpp://conference.weather.im",
|
service: "xmpp://conference.weather.im",
|
||||||
domain: "weather.im",
|
domain: "weather.im",
|
||||||
|
@ -273,6 +308,8 @@ xmpp.on("offline", () => {
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var restartTimer = null;
|
||||||
|
|
||||||
xmpp.on("stanza", (stanza) => {
|
xmpp.on("stanza", (stanza) => {
|
||||||
// Debug stuff
|
// Debug stuff
|
||||||
if (config.debug >= 2) console.log(`${colors.magenta("[DEBUG]")} Stanza: ${stanza.toString()}`);
|
if (config.debug >= 2) console.log(`${colors.magenta("[DEBUG]")} Stanza: ${stanza.toString()}`);
|
||||||
|
@ -298,6 +335,11 @@ xmpp.on("stanza", (stanza) => {
|
||||||
}
|
}
|
||||||
// Get new messages and log them, ignore old messages
|
// Get new messages and log them, ignore old messages
|
||||||
if (stanza.is("message") && stanza.attrs.type === "groupchat") {
|
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
|
// Stops spam from getting old messages
|
||||||
if (startup) return;
|
if (startup) return;
|
||||||
// Get channel name
|
// Get channel name
|
||||||
|
@ -318,6 +360,16 @@ xmpp.on("stanza", (stanza) => {
|
||||||
evt = { name: "Unknown", priority: 3 }
|
evt = { name: "Unknown", priority: 3 }
|
||||||
console.log(`${colors.red("[ERROR]")} Unknown event type: ${product_id.pil.substring(0, 3)}. Fix me`);
|
console.log(`${colors.red("[ERROR]")} Unknown event type: ${product_id.pil.substring(0, 3)}. Fix me`);
|
||||||
console.log(`${colors.magenta("[DEBUG]")} ${bodyData.string}`)
|
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);
|
evt.code = product_id.pil.substring(0, 3);
|
||||||
|
@ -325,13 +377,14 @@ xmpp.on("stanza", (stanza) => {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const diff = (now - product_id.timestamp) / 1000 / 60;
|
const diff = (now - product_id.timestamp) / 1000 / 60;
|
||||||
if (diff > 3) return;
|
if (diff > 3) return;
|
||||||
if (config.debug >= 1) console.log(`${colors.magenta("[DEBUG]")} New message from ${fromChannel}`);
|
// 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++;
|
messages++;
|
||||||
|
|
||||||
|
|
||||||
// Handle NTFY
|
// Handle NTFY
|
||||||
if (config.ntfy.enabled) {
|
if (config.ntfy.enabled) {
|
||||||
if (config.debug >= 1) console.log(`${colors.magenta("[DEBUG]")} Sending NTFY for ${config.ntfy.prefix}${fromChannel}`)
|
//if (config.debug >= 1) console.log(`${colors.magenta("[DEBUG]")} Sending NTFY for ${config.ntfy.prefix}${fromChannel}`)
|
||||||
ntfyBody = {
|
ntfyBody = {
|
||||||
"topic": `${config.ntfy.prefix}${fromChannel}`,
|
"topic": `${config.ntfy.prefix}${fromChannel}`,
|
||||||
"message": bodyData.string,
|
"message": bodyData.string,
|
||||||
|
@ -349,7 +402,7 @@ xmpp.on("stanza", (stanza) => {
|
||||||
'Authorization': `Bearer ${config.ntfy.token}`
|
'Authorization': `Bearer ${config.ntfy.token}`
|
||||||
}
|
}
|
||||||
}).then((res) => {
|
}).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 (config.debug >= 1) console.log(`${colors.magenta("[DEBUG]")} NTFY sent for ${config.ntfy.prefix}${fromChannel} with status ${res.status} ${res.statusText}`);
|
||||||
if (res.status !== 200) console.log(`${colors.red("[ERROR]")} NTFY failed for ${config.ntfy.prefix}${fromChannel} with status ${res.status} ${res.statusText}`);
|
if (res.status !== 200) console.log(`${colors.red("[ERROR]")} NTFY failed for ${config.ntfy.prefix}${fromChannel} with status ${res.status} ${res.statusText}`);
|
||||||
|
|
||||||
|
|
||||||
|
@ -389,7 +442,7 @@ xmpp.on("stanza", (stanza) => {
|
||||||
{
|
{
|
||||||
type: 2,
|
type: 2,
|
||||||
style: 1,
|
style: 1,
|
||||||
custom_id: product_id_raw,
|
custom_id: `product|${product_id_raw}`,
|
||||||
label: "Product Text",
|
label: "Product Text",
|
||||||
emoji: {
|
emoji: {
|
||||||
name: "📄"
|
name: "📄"
|
||||||
|
@ -405,7 +458,7 @@ xmpp.on("stanza", (stanza) => {
|
||||||
console.log(`${colors.red("[ERROR]")} ${err.message}`);
|
console.log(`${colors.red("[ERROR]")} ${err.message}`);
|
||||||
}
|
}
|
||||||
if (!rows) return; // No channels to alert
|
if (!rows) return; // No channels to alert
|
||||||
rows.forEach((row) => {
|
rows.forEach(async (row) => {
|
||||||
// Get Filters as arrays
|
// Get Filters as arrays
|
||||||
if (!row.filterEvt) row.filterEvt = "";
|
if (!row.filterEvt) row.filterEvt = "";
|
||||||
if (!row.filter) row.filter = "";
|
if (!row.filter) row.filter = "";
|
||||||
|
@ -431,6 +484,54 @@ xmpp.on("stanza", (stanza) => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
}).then((msg) => {
|
}).then((msg) => {
|
||||||
if (msg.channel.type === Discord.ChannelType.GuildAnnouncement) msg.crosspost();
|
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) => {
|
}).catch((err) => {
|
||||||
|
@ -476,6 +577,27 @@ xmpp.on("stanza", (stanza) => {
|
||||||
thisMsg.content = row.custommessage || null;
|
thisMsg.content = row.custommessage || null;
|
||||||
user.send(thisMsg).catch((err) => {
|
user.send(thisMsg).catch((err) => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
}).catch((err) => {
|
||||||
|
console.log(`${colors.yellow("[WARN]")} Failed to send message to ${user.tag} (${user.id})`);
|
||||||
|
const logChannel = discord.guilds.cache.get(config.discord.mainGuild).channels.cache.get(config.discord.logChannel);
|
||||||
|
logChannel.send({
|
||||||
|
embeds: [
|
||||||
|
{
|
||||||
|
title: "Failed to send DM",
|
||||||
|
description: `User may have DMs disabled, or bot doesnt' share a server anymore!.
|
||||||
|
User: ${user.tag} (${user.id})
|
||||||
|
Sub Info: \`\`\`json\n${JSON.stringify(row)}\`\`\``,
|
||||||
|
color: 0xff0000
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}).then(() => {
|
||||||
|
db.run(`DELETE FROM userAlerts WHERE userid = ? AND iemchannel = ?`, [user.id, fromChannel], (err) => {
|
||||||
|
if (err) {
|
||||||
|
console.error(err.message);
|
||||||
|
}
|
||||||
|
console.log(`${colors.cyan("[INFO]")} Deleted user ${user.id} from database`);
|
||||||
|
});
|
||||||
|
})
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
|
@ -582,20 +704,19 @@ discord.on('ready', async () => {
|
||||||
commands = require("./data/commands.json");
|
commands = require("./data/commands.json");
|
||||||
// Add dynamic commands (based on datas files)
|
// Add dynamic commands (based on datas files)
|
||||||
satCommand = {
|
satCommand = {
|
||||||
"name": "sattelite",
|
"name": "satellite",
|
||||||
"description": "Get the latest sattelite images from a given sattelite",
|
"description": "Get the latest satellite images from a given satellite",
|
||||||
"options": [
|
"options": [
|
||||||
{
|
{
|
||||||
"name": "sattelite",
|
"name": "satellite",
|
||||||
"description": "The sattelite to get images from",
|
"description": "The satellite to get images from",
|
||||||
"type": 3,
|
"type": 3,
|
||||||
"required": true,
|
"required": true,
|
||||||
"choices": []
|
"choices": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
for (const key in sattelites) {
|
for (const key in satellites) {
|
||||||
// Push the key to the choices array
|
|
||||||
satCommand.options[0].choices.push({
|
satCommand.options[0].choices.push({
|
||||||
"name": key,
|
"name": key,
|
||||||
"value": key
|
"value": key
|
||||||
|
@ -630,6 +751,7 @@ discord.on('ready', async () => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "play",
|
"name": "play",
|
||||||
|
"type": 1,
|
||||||
"description": "Play a stream",
|
"description": "Play a stream",
|
||||||
"options": [
|
"options": [
|
||||||
{
|
{
|
||||||
|
@ -658,10 +780,27 @@ discord.on('ready', async () => {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
nwrplayCommand = {
|
||||||
|
"name": "nwrplay",
|
||||||
|
"description": "Nwr stream",
|
||||||
|
"type": 1,
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"name": "callsign",
|
||||||
|
"description": "The URL of the stream to play",
|
||||||
|
"type": 3,
|
||||||
|
"required": true,
|
||||||
|
"autocomplete": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
commands.push(nwrplayCommand);
|
||||||
}
|
}
|
||||||
await (async () => {
|
await (async () => {
|
||||||
try {
|
try {
|
||||||
//Global
|
//Global
|
||||||
|
if (config.debug >= 1) console.log(`${colors.magenta("[DEBUG]")} Registering global commands`);
|
||||||
await rest.put(Routes.applicationCommands(discord.user.id), { body: commands })
|
await rest.put(Routes.applicationCommands(discord.user.id), { body: commands })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
@ -726,21 +865,26 @@ discord.on("interactionCreate", async (interaction) => {
|
||||||
filterEvt = interaction.options.getString("filterevt") || null;
|
filterEvt = interaction.options.getString("filterevt") || null;
|
||||||
message = interaction.options.getString("message") || null;
|
message = interaction.options.getString("message") || null;
|
||||||
if (interaction.inGuild()) {
|
if (interaction.inGuild()) {
|
||||||
db.get(`SELECT * FROM channels WHERE channelid = ? AND iemchannel = ?`, [interaction.channel.id, room], (err, row) => {
|
interaction.channel.send("Permission check").then((msg) => {
|
||||||
if (err) {
|
msg.delete();
|
||||||
console.error(err.message);
|
db.get(`SELECT * FROM channels WHERE channelid = ? AND iemchannel = ?`, [interaction.channel.id, room], (err, row) => {
|
||||||
interaction.reply({ content: "Failed to subscribe to room", ephemeral: true });
|
|
||||||
} else if (row) {
|
|
||||||
return interaction.reply({ content: `Already subscribed to \`${getWFOByRoom(room).location}\`\nIf you want to update a subscribtion, please unsubscribe and resubscribe. This will be made a command eventually.`, ephemeral: true });
|
|
||||||
}
|
|
||||||
db.run(`INSERT INTO channels (channelid, iemchannel, custommessage, filter, filterevt, minPriority) VALUES (?, ?, ?, ? ,? ,?)`, [interaction.channel.id, room, message, filter, filterEvt, minPriority], (err) => {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error(err.message);
|
console.error(err.message);
|
||||||
interaction.reply({ content: "Failed to subscribe to room", ephemeral: true });
|
interaction.reply({ content: "Failed to subscribe to room", ephemeral: true });
|
||||||
} else {
|
} else if (row) {
|
||||||
interaction.reply({ content: `Subscribed to \`${getWFOByRoom(room).location}\``, ephemeral: true });
|
return interaction.reply({ content: `Already subscribed to \`${getWFOByRoom(room).location}\`\nIf you want to update a subscribtion, please unsubscribe and resubscribe. This will be made a command eventually.`, ephemeral: true });
|
||||||
}
|
}
|
||||||
|
db.run(`INSERT INTO channels (channelid, iemchannel, custommessage, filter, filterEvt, minPriority) VALUES (?, ?, ?, ? ,? ,?)`, [interaction.channel.id, room, message, filter, filterEvt, minPriority], (err) => {
|
||||||
|
if (err) {
|
||||||
|
console.error(err.message);
|
||||||
|
interaction.reply({ content: "Failed to subscribe to room", ephemeral: true });
|
||||||
|
} else {
|
||||||
|
interaction.reply({ content: `Subscribed to \`${getWFOByRoom(room).location}\``, ephemeral: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
}).catch((err) => {
|
||||||
|
interaction.reply({ content: "Failed to subscribe to room. Bot does not have send message permissions here!", ephemeral: true });
|
||||||
});
|
});
|
||||||
} else { // We're in a DM
|
} else { // We're in a DM
|
||||||
db.get(`SELECT * FROM userAlerts WHERE userid = ? AND iemchannel = ?`, [interaction.user.id, room], (err, row) => {
|
db.get(`SELECT * FROM userAlerts WHERE userid = ? AND iemchannel = ?`, [interaction.user.id, room], (err, row) => {
|
||||||
|
@ -864,46 +1008,49 @@ discord.on("interactionCreate", async (interaction) => {
|
||||||
channels = row.count
|
channels = row.count
|
||||||
|
|
||||||
await getUniqueChannels().then((unique) => {
|
await getUniqueChannels().then((unique) => {
|
||||||
uniques = unique;
|
uniques = unique.channels;
|
||||||
|
guilds = unique.guilds;
|
||||||
});
|
});
|
||||||
const embed = {
|
discord.users.fetch("289884287765839882").then((chrisUser) => {
|
||||||
title: "About Me!",
|
const embed = {
|
||||||
thumbnail: {
|
title: "About Me!",
|
||||||
url: discord.user.avatarURL()
|
thumbnail: {
|
||||||
},
|
url: discord.user?.avatarURL()
|
||||||
description: `I listen to all the weather.im rooms and send them to discord channels.\nI am open source, you can find my code [here!](https://github.com/ChrisChrome/iembot-2.0)\n\nThis bot is not affiliated with NOAA, the National Weather Service, or the IEM project.`,
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: "Uptime",
|
|
||||||
value: `Since <t:${Math.floor(startTimestap / 1000)}>, Started <t:${Math.floor(startTimestap / 1000)}:R>`,
|
|
||||||
},
|
},
|
||||||
{
|
description: `I listen to all the weather.im rooms and send them to discord channels.\nI am open source, you can find my code [here!](https://github.com/ChrisChrome/iembot-2.0)\n\nThis bot is not affiliated with NOAA, the National Weather Service, or the IEM project.`,
|
||||||
name: "Caught Messages",
|
fields: [
|
||||||
value: `Got ${messages.toLocaleString()} messages since startup`,
|
{
|
||||||
},
|
name: "Uptime",
|
||||||
{
|
value: `Since <t:${Math.floor(startTimestap / 1000)}>, Started <t:${Math.floor(startTimestap / 1000)}:R>`,
|
||||||
name: "Guilds",
|
},
|
||||||
value: guilds.toLocaleString(),
|
{
|
||||||
inline: true
|
name: "Caught Messages",
|
||||||
},
|
value: `Got ${messages.toLocaleString()} messages since startup`,
|
||||||
{
|
},
|
||||||
name: "Subscribed Rooms",
|
{
|
||||||
value: channels.toLocaleString(),
|
name: "Guilds",
|
||||||
inline: true
|
value: guilds.toLocaleString(),
|
||||||
},
|
inline: true
|
||||||
{
|
},
|
||||||
name: "Unique Channels",
|
{
|
||||||
value: uniques.toLocaleString(),
|
name: "Subscribed Rooms",
|
||||||
inline: true
|
value: `${channels.toLocaleString()}`,
|
||||||
|
inline: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Unique Channels",
|
||||||
|
value: `${uniques.toLocaleString()} in ${guilds} guilds.`,
|
||||||
|
inline: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
color: 0x00ff00,
|
||||||
|
footer: {
|
||||||
|
text: "Made by @chrischrome with <3",
|
||||||
|
icon_url: chrisUser.avatarURL()
|
||||||
}
|
}
|
||||||
],
|
|
||||||
color: 0x00ff00,
|
|
||||||
footer: {
|
|
||||||
text: "Made by @chrischrome with <3",
|
|
||||||
icon_url: discord.users.cache.get("289884287765839882").avatarURL()
|
|
||||||
}
|
}
|
||||||
}
|
interaction.reply({ embeds: [embed] });
|
||||||
interaction.reply({ embeds: [embed] });
|
})
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "rooms":
|
case "rooms":
|
||||||
|
@ -1000,7 +1147,7 @@ discord.on("interactionCreate", async (interaction) => {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "playbcfy": // Play broadcastify stream
|
case "playbcfy": // Play broadcastify stream
|
||||||
if (!interaction.inGuild()) return interaction.reply({ content: "This command can only be used in a guild", ephemeral: true });
|
if (!interaction.inGuild()) return interaction.reply({ content: "This command can only be used in a guild", ephemeral: true });
|
||||||
if (!config.broadcastify.enabled) return interaction.reply({ content: "Broadcastify is not enabled", ephemeral: true });
|
if (!config.broadcastify.enabled) return interaction.reply({ content: "Broadcastify is not enabled", ephemeral: true });
|
||||||
streamID = interaction.options.getString("id");
|
streamID = interaction.options.getString("id");
|
||||||
// Check if the stream ID is valid (up to 10 digit alphanumeric)
|
// Check if the stream ID is valid (up to 10 digit alphanumeric)
|
||||||
|
@ -1008,7 +1155,7 @@ discord.on("interactionCreate", async (interaction) => {
|
||||||
// Get the stream URL
|
// Get the stream URL
|
||||||
url = `https://${config.broadcastify.username}:${config.broadcastify.password}@audio.broadcastify.com/${streamID}.mp3`;
|
url = `https://${config.broadcastify.username}:${config.broadcastify.password}@audio.broadcastify.com/${streamID}.mp3`;
|
||||||
// Get the channel
|
// Get the channel
|
||||||
channel = interaction.member.voice.channel;
|
channel = interaction.member.voice?.channel;
|
||||||
if (!channel) return interaction.reply({ content: "You need to be in a voice channel", ephemeral: true });
|
if (!channel) return interaction.reply({ content: "You need to be in a voice channel", ephemeral: true });
|
||||||
// Join the channel and play the stream
|
// Join the channel and play the stream
|
||||||
res = JoinChannel(channel, url, .1, interaction)
|
res = JoinChannel(channel, url, .1, interaction)
|
||||||
|
@ -1020,16 +1167,16 @@ discord.on("interactionCreate", async (interaction) => {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "play": // Play generic stream
|
case "play": // Play generic stream
|
||||||
if (!interaction.inGuild()) return interaction.reply({ content: "This command can only be used in a guild", ephemeral: true });
|
if (!interaction.inGuild()) return interaction.reply({ content: "This command can only be used in a guild", ephemeral: true });
|
||||||
// Get the URL
|
// Use local variables & Get the URL
|
||||||
url = interaction.options.getString("url");
|
interactionUrl = interaction.options.getString("url");
|
||||||
// Sanity check URL for funny stuff
|
|
||||||
if (!url.match(/https?:\/\/[^\s]+/)) return interaction.reply({ content: "Invalid URL", ephemeral: true });
|
|
||||||
// Get the channel
|
// Get the channel
|
||||||
channel = interaction.member.voice.channel;
|
channel = interaction.member.voice?.channel;
|
||||||
|
// Check if in channel
|
||||||
if (!channel) return interaction.reply({ content: "You need to be in a voice channel", ephemeral: true });
|
if (!channel) return interaction.reply({ content: "You need to be in a voice channel", ephemeral: true });
|
||||||
// Join the channel and play the stream
|
// Join the channel and play the stream
|
||||||
st = JoinChannel(channel, url, .1, interaction)
|
st = JoinChannel(channel, interactionUrl, .1, interaction);
|
||||||
|
|
||||||
if (st) {
|
if (st) {
|
||||||
interaction.reply({ content: "Joined, trying to start playing.", ephemeral: true });
|
interaction.reply({ content: "Joined, trying to start playing.", ephemeral: true });
|
||||||
} else {
|
} else {
|
||||||
|
@ -1037,7 +1184,29 @@ discord.on("interactionCreate", async (interaction) => {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "leave": // Leaves Channel
|
case "nwrplay": // Play NWR stream
|
||||||
|
if (!interaction.inGuild()) return interaction.reply({ content: "This command can only be used in a guild", ephemeral: true });
|
||||||
|
|
||||||
|
// Get the callsign
|
||||||
|
const callsign = interaction.options.getString("callsign");
|
||||||
|
// Get the URL associated with the callsign
|
||||||
|
url = nwrstreams.callsigns[callsign];
|
||||||
|
|
||||||
|
// Get the voice channel
|
||||||
|
channel = interaction.member.voice?.channel; // Use a unique variable name
|
||||||
|
if (!channel) return interaction.reply({ content: "You need to be in a voice channel", ephemeral: true });
|
||||||
|
|
||||||
|
// Join the channel and play the stream
|
||||||
|
const streamStatus = JoinChannel(channel, url, .1, interaction); // Use a unique variable name
|
||||||
|
if (streamStatus) {
|
||||||
|
interaction.reply({ content: "Joined, trying to start playing.", ephemeral: true });
|
||||||
|
} else {
|
||||||
|
interaction.reply({ content: `Failed to play stream`, ephemeral: true });
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case "leave": // Leave Channel
|
||||||
if (!interaction.inGuild()) return interaction.reply({ content: "This command can only be used in a guild", ephemeral: true });
|
if (!interaction.inGuild()) return interaction.reply({ content: "This command can only be used in a guild", ephemeral: true });
|
||||||
channel = interaction.member.voice.channel;
|
channel = interaction.member.voice.channel;
|
||||||
if (!channel) return interaction.reply({ content: "You need to be in a voice channel", ephemeral: true });
|
if (!channel) return interaction.reply({ content: "You need to be in a voice channel", ephemeral: true });
|
||||||
|
@ -1050,7 +1219,7 @@ discord.on("interactionCreate", async (interaction) => {
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case "pause": // Pause/unpause stream
|
case "pause": // Pause/unpause stream
|
||||||
if (!interaction.inGuild()) return interaction.reply({ content: "This command can only be used in a guild", ephemeral: true });
|
if (!interaction.inGuild()) return interaction.reply({ content: "This command can only be used in a guild", ephemeral: true });
|
||||||
channel = interaction.member.voice.channel;
|
channel = interaction.member.voice.channel;
|
||||||
if (!channel) return interaction.reply({ content: "You need to be in a voice channel", ephemeral: true });
|
if (!channel) return interaction.reply({ content: "You need to be in a voice channel", ephemeral: true });
|
||||||
res = toggleVoicePause(channel)
|
res = toggleVoicePause(channel)
|
||||||
|
@ -1061,7 +1230,7 @@ discord.on("interactionCreate", async (interaction) => {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "volume": // Set volume
|
case "volume": // Set volume
|
||||||
if (!interaction.inGuild()) return interaction.reply({ content: "This command can only be used in a guild", ephemeral: true });
|
if (!interaction.inGuild()) return interaction.reply({ content: "This command can only be used in a guild", ephemeral: true });
|
||||||
channel = interaction.member.voice.channel;
|
channel = interaction.member.voice.channel;
|
||||||
if (!channel) return interaction.reply({ content: "You need to be in a voice channel", ephemeral: true });
|
if (!channel) return interaction.reply({ content: "You need to be in a voice channel", ephemeral: true });
|
||||||
volume = interaction.options.getInteger("volume") / 100;
|
volume = interaction.options.getInteger("volume") / 100;
|
||||||
|
@ -1158,9 +1327,9 @@ discord.on("interactionCreate", async (interaction) => {
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "alertmap":
|
case "alertmap":
|
||||||
url = "https://forecast.weather.gov/wwamap/png/US.png"
|
const alertmapurl = "https://forecast.weather.gov/wwamap/png/US.png"
|
||||||
await interaction.deferReply();
|
await interaction.deferReply();
|
||||||
fetch(url).then((res) => {
|
fetch(alertmapurl).then((res) => {
|
||||||
if (res.status !== 200) {
|
if (res.status !== 200) {
|
||||||
interaction.editReply({ content: "Failed to get alert map", ephemeral: true });
|
interaction.editReply({ content: "Failed to get alert map", ephemeral: true });
|
||||||
return;
|
return;
|
||||||
|
@ -1186,47 +1355,54 @@ discord.on("interactionCreate", async (interaction) => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "sattelite": // Get satellite images
|
case "satellite": // Get satellite images
|
||||||
sat = interaction.options.getString("sattelite");
|
sat = interaction.options.getString("satellite");
|
||||||
if (!sattelites[sat]) return interaction.reply({ content: "Invalid satellite", ephemeral: true });
|
if (!satellites[sat]) return interaction.reply({ content: "Invalid satellite", ephemeral: true });
|
||||||
// Fetch all the images
|
// Fetch all the images
|
||||||
await interaction.deferReply();
|
productOptions = []
|
||||||
imageBuffers = {};
|
await (() => {
|
||||||
embeds = [];
|
for (const key in satellites[sat].products) {
|
||||||
files = [];
|
// make a discord customid safe id for the product name, add it to the satellites object
|
||||||
sattelites[sat].forEach(async (imgData) => {
|
satellites[sat].products[key].customid = key.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
|
||||||
// Get a buffer for the data, and put that in imageBuffers with the "name" as the key
|
productOptions.push({
|
||||||
fetch(imgData.url).then((res) => {
|
label: key,
|
||||||
if (res.status !== 200) {
|
value: satellites[sat].products[key].customid
|
||||||
interaction.editReply({ content: "Failed to get satellite images", ephemeral: true });
|
})
|
||||||
return;
|
}
|
||||||
}
|
console.log(JSON.stringify(productOptions, null, 2))
|
||||||
res.buffer().then((buffer) => {
|
})();
|
||||||
imageBuffers[imgData.name] = buffer;
|
satMessages[interaction.id] = {
|
||||||
files.push({
|
sat
|
||||||
attachment: buffer,
|
}
|
||||||
name: `${imgData.name}.jpg`
|
await interaction.reply({
|
||||||
});
|
content: "Choose a product",
|
||||||
embeds.push({
|
components: [
|
||||||
title: `${sat} ${imgData.name}`,
|
{
|
||||||
image: {
|
type: 1,
|
||||||
url: `attachment://${imgData.name}.jpg`
|
components: [
|
||||||
|
{
|
||||||
|
type: 3,
|
||||||
|
custom_id: `satproduct|${interaction.id}`,
|
||||||
|
label: "Product",
|
||||||
|
// map options to product names
|
||||||
|
options: productOptions
|
||||||
}
|
}
|
||||||
});
|
]
|
||||||
// Check if we have all the images
|
}
|
||||||
if (Object.keys(imageBuffers).length === sattelites[sat].length) {
|
]
|
||||||
// Send the images
|
})
|
||||||
interaction.editReply({
|
break;
|
||||||
embeds,
|
|
||||||
files
|
case "forecast":
|
||||||
});
|
await interaction.deferReply();
|
||||||
}
|
periods = interaction.options.getInteger("periods") || 1;
|
||||||
});
|
funcs.getWeatherBySearch(interaction.options.getString("location")).then((weather) => {
|
||||||
}).catch((err) => {
|
if (config.debug >= 1) console.log(JSON.stringify(weather, null, 2))
|
||||||
interaction.editReply({ content: "Failed to get satellite images", ephemeral: true });
|
embeds = funcs.generateDiscordEmbeds(weather, periods);
|
||||||
console.log(`${colors.red("[ERROR]")} Failed to get satellite images: ${err.message}`);
|
interaction.editReply({ embeds });
|
||||||
console.error(err);
|
}).catch((err) => {
|
||||||
});
|
interaction.editReply({ content: "Failed to get forecast", ephemeral: true });
|
||||||
|
if (config.debug >= 1) console.log(`${colors.red("[ERROR]")} Failed to get forecast: ${err}`);
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -1235,51 +1411,198 @@ discord.on("interactionCreate", async (interaction) => {
|
||||||
|
|
||||||
}
|
}
|
||||||
case Discord.InteractionType.MessageComponent:
|
case Discord.InteractionType.MessageComponent:
|
||||||
if (interaction.customId) {
|
if (!interaction.customId) return;
|
||||||
const product_id = interaction.customId;
|
switch (interaction.customId.split("|")[0]) {
|
||||||
const url = `https://mesonet.agron.iastate.edu/api/1/nwstext/${product_id}`;
|
case "product":
|
||||||
await interaction.deferReply({ ephemeral: true });
|
if (interaction.customId) {
|
||||||
fetch(url).then((res) => {
|
const product_id = interaction.customId.split("|")[1];
|
||||||
if (res.status !== 200) {
|
url = `https://mesonet.agron.iastate.edu/api/1/nwstext/${product_id}`;
|
||||||
interaction.reply({ content: "Failed to get product text", ephemeral: true });
|
await interaction.deferReply({ ephemeral: true });
|
||||||
return;
|
fetch(url).then((res) => {
|
||||||
|
if (res.status !== 200) {
|
||||||
|
interaction.reply({ content: "Failed to get product text", ephemeral: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Retruns raw text, paginate it into multiple embeds if needed
|
||||||
|
res.text().then(async (text) => {
|
||||||
|
const pages = text.match(/[\s\S]{1,1900}(?=\n|$)/g);
|
||||||
|
// const embeds = pages.map((page, ind) => ({
|
||||||
|
// title: `Product Text for ${product_id} Pg ${ind + 1}/${pages.length}`,
|
||||||
|
// description: `\`\`\`${page}\`\`\``,
|
||||||
|
// color: 0x00ff00
|
||||||
|
// }));
|
||||||
|
const messages = pages.map((page, ind) => {
|
||||||
|
return `\`\`\`${page}\`\`\``
|
||||||
|
})
|
||||||
|
messages.forEach(async (message) => {
|
||||||
|
interaction.followUp({ content: message, ephemeral: true });
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}).catch((err) => {
|
||||||
|
interaction.reply({ content: "Failed to get product text", ephemeral: true });
|
||||||
|
console.log(`${colors.red("[ERROR]")} Failed to get product text: ${err.message}`);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
// Retruns raw text, paginate it into multiple embeds if needed
|
break;
|
||||||
res.text().then(async (text) => {
|
case "satproduct":
|
||||||
const pages = text.match(/[\s\S]{1,2000}(?=\n|$)/g);
|
satData = satMessages[interaction.customId.split("|")[1]];
|
||||||
// const embeds = pages.map((page, ind) => ({
|
sat = satData.sat
|
||||||
// title: `Product Text for ${product_id} Pg ${ind + 1}/${pages.length}`,
|
product = interaction.values[0];
|
||||||
// description: `\`\`\`${page}\`\`\``,
|
// find the original product name
|
||||||
// color: 0x00ff00
|
product_name = Object.keys(satellites[sat].products).find(key => satellites[sat].products[key].customid === product);
|
||||||
// }));
|
imageOptions = []
|
||||||
const messages = pages.map((page, ind) => {
|
satMessages[interaction.customId.split("|")[1]] = {
|
||||||
return `\`\`\`${page}\`\`\``
|
sat,
|
||||||
})
|
product,
|
||||||
messages.forEach(async (message) => {
|
product_name,
|
||||||
interaction.followUp({ content: message, ephemeral: true });
|
images: {}
|
||||||
})
|
}
|
||||||
|
await (() => {
|
||||||
|
// for key, value in satellites[sat].products[product_name]
|
||||||
|
console.log(product_name)
|
||||||
|
for (const key in satellites[sat].products[product_name]) {
|
||||||
|
// make a discord customid safe id for the product name, add it to the satellites object
|
||||||
|
//console.log(satellites[sat].products[product_name])
|
||||||
|
if (key === "customid") continue;
|
||||||
|
satMessages[interaction.customId.split("|")[1]].images[key.replace(/[^a-zA-Z0-9]/g, "").toLowerCase()] = satellites[sat].products[product_name][key];
|
||||||
|
imageOptions.push({
|
||||||
|
label: key,
|
||||||
|
value: key.replace(/[^a-zA-Z0-9]/g, "").toLowerCase()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
interaction.deferReply();
|
||||||
|
await interaction.message.edit({
|
||||||
|
content: "Choose an image to view",
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
type: 1,
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
type: 3,
|
||||||
|
custom_id: `satproduct2|${interaction.customId.split("|")[1]}`,
|
||||||
|
label: "Image",
|
||||||
|
// map options to product names
|
||||||
|
options: imageOptions
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}).then(() => {
|
||||||
|
interaction.deleteReply();
|
||||||
});
|
});
|
||||||
}).catch((err) => {
|
break;
|
||||||
interaction.reply({ content: "Failed to get product text", ephemeral: true });
|
case "satproduct2":
|
||||||
console.log(`${colors.red("[ERROR]")} Failed to get product text: ${err.message}`);
|
satData = satMessages[interaction.customId.split("|")[1]];
|
||||||
});
|
sat = satData.sat;
|
||||||
|
product = satData.product;
|
||||||
|
product_name = satData.product_name;
|
||||||
|
image = interaction.values[0];
|
||||||
|
url = satData.images[image];
|
||||||
|
// get filename from url
|
||||||
|
filename = url.split("/").pop();
|
||||||
|
interaction.deferReply();
|
||||||
|
// Get the image
|
||||||
|
fetch(url).then((res) => {
|
||||||
|
if (res.status !== 200) {
|
||||||
|
interaction.message.edit({ content: "Failed to get image", ephemeral: true }).then(() => {
|
||||||
|
interaction.deleteReply();
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
embeds = [];
|
||||||
|
files = [];
|
||||||
|
|
||||||
|
res.buffer().then(async (buffer) => {
|
||||||
|
files.push({
|
||||||
|
attachment: buffer,
|
||||||
|
name: filename
|
||||||
|
})
|
||||||
|
embeds.push({
|
||||||
|
title: `${sat}/${product_name}/${image}`,
|
||||||
|
image: {
|
||||||
|
url: `attachment://${filename}`
|
||||||
|
},
|
||||||
|
color: 0x00ff00
|
||||||
|
});
|
||||||
|
interaction.message.edit({
|
||||||
|
embeds,
|
||||||
|
files,
|
||||||
|
components: [],
|
||||||
|
content: null
|
||||||
|
}).then(() => {
|
||||||
|
interaction.deleteReply();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}).catch((err) => {
|
||||||
|
interaction.message.edit({ content: "Failed to get image", ephemeral: true }).then(() => {
|
||||||
|
interaction.deleteReply();
|
||||||
|
});
|
||||||
|
console.log(`${colors.red("[ERROR]")} Failed to get image: ${err.stack}`);
|
||||||
|
});
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case Discord.InteractionType.ApplicationCommandAutocomplete:
|
||||||
|
//map nwrstreams
|
||||||
|
if (interaction.commandName === "nwrplay") {
|
||||||
|
let callsignSearch = interaction.options.getString("callsign");
|
||||||
|
let callsigns = Object.keys(nwrstreams.callsigns);
|
||||||
|
let results = callsigns.filter((callsign) => callsign.toLowerCase().includes(callsignSearch.toLowerCase()));
|
||||||
|
if (results.length > 25) {
|
||||||
|
results = results.slice(0, 25);
|
||||||
|
}
|
||||||
|
interaction.respond(results.map((callsign) => ({ name: callsign, value: callsign })));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
discord.on("guildCreate", (guild) => {
|
discord.on("guildCreate", async (guild) => {
|
||||||
|
let logs = await guild.fetchAuditLogs()
|
||||||
|
logs = logs.entries.filter(e => e.action === Discord.AuditLogEvent.BotAdd)
|
||||||
|
let user = logs.find(l => l.target?.id === discord.user.id)?.executor
|
||||||
// Get the main guild
|
// Get the main guild
|
||||||
const myGuild = discord.guilds.cache.get(config.discord.mainGuild);
|
const myGuild = discord.guilds.cache.get(config.discord.mainGuild);
|
||||||
// Get the log channel
|
// Get the log channel
|
||||||
const channel = myGuild.channels.cache.get(config.discord.logChannel);
|
const channel = myGuild.channels.cache.get(config.discord.logChannel);
|
||||||
// Send a message to the log channel
|
// Send a message to the log channel
|
||||||
|
let invite = await discord.guilds.cache.get(config.discord.mainGuild).channels.cache.get(config.discord.inviteChannel).createInvite();
|
||||||
|
user.send({
|
||||||
|
embeds: [{
|
||||||
|
description: `Thanks for adding ${discord.user.username}!\nIf you have **ANY** questions, comments, suggestions, bug reports, etc, please feel free to throw it by us in our support server!\n\nTo get started use \`/subscribe\` to get alerts!`,
|
||||||
|
color: 0x00ff00
|
||||||
|
}],
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
type: Discord.ComponentType.ActionRow,
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
type: Discord.ComponentType.Button,
|
||||||
|
url: `https://discord.gg/${invite.code}`,
|
||||||
|
style: Discord.ButtonStyle.Link,
|
||||||
|
emoji: "ℹ",
|
||||||
|
label: "IEM Alerter Support Server"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}).catch((err) => {
|
||||||
|
console.log(`${colors.red("[ERROR]")} Failed to send message to user ${user.id}: ${err.message}`);
|
||||||
|
})
|
||||||
channel.send({
|
channel.send({
|
||||||
embeds: [
|
embeds: [
|
||||||
{
|
{
|
||||||
description: `I joined \`${guild.name}\``,
|
description: `I joined \`${guild.name}\``,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
"name": "User",
|
||||||
|
"value": `<@${user.id}> (@${user.username}) ${user.displayName}`
|
||||||
|
}
|
||||||
|
],
|
||||||
color: 0x00ff00
|
color: 0x00ff00
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -1304,7 +1627,7 @@ discord.on("guildDelete", (guild) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
process.on("unhandledRejection", (error, promise) => {
|
process.on("unhandledRejection", (error, promise) => {
|
||||||
console.log(`${colors.red("[ERROR]")} Unhandled Rejection @ ${promise}: ${error}`);
|
console.log(`${colors.red("[ERROR]")} Unhandled Rejection @ ${promise}: ${error.stack}`);
|
||||||
// create errors folder if it doesnt exist
|
// create errors folder if it doesnt exist
|
||||||
if (!fs.existsSync("./error")) {
|
if (!fs.existsSync("./error")) {
|
||||||
fs.mkdirSync("./error");
|
fs.mkdirSync("./error");
|
||||||
|
@ -1347,5 +1670,7 @@ process.on("uncaughtException", (error) => {
|
||||||
return;
|
return;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Login to discord
|
// Login to discord
|
||||||
discord.login(config.discord.token);
|
discord.login(config.discord.token);
|
|
@ -14,7 +14,8 @@
|
||||||
"@xmpp/client": "^0.13.1",
|
"@xmpp/client": "^0.13.1",
|
||||||
"@xmpp/debug": "^0.13.0",
|
"@xmpp/debug": "^0.13.0",
|
||||||
"colors": "^1.4.0",
|
"colors": "^1.4.0",
|
||||||
"discord.js": "^14.15.2",
|
"discord.js": "14.14.1",
|
||||||
|
"geolib": "^3.3.4",
|
||||||
"html-entities": "^2.5.2",
|
"html-entities": "^2.5.2",
|
||||||
"jimp": "^0.22.12",
|
"jimp": "^0.22.12",
|
||||||
"sodium": "^3.0.2",
|
"sodium": "^3.0.2",
|
||||||
|
|
Loading…
Reference in a new issue