const config = require('./config.json') const axios = require('axios') const fs = require('fs') const moment = require('moment') moment.locale('yes', { week : { dow : 1 } }) const jsdom = require("jsdom") const { JSDOM } = jsdom const express = require('express') const appConfig = express() appConfig.use(express.json({ limit: '1mb' })) appConfig.use(express.static('html')) appConfig.use(express.static(__dirname + '/node_modules/@mdi/font')) const umlautMapGermany = { '\u00dc': ']', '\u00c4': '[', '\u00d6': "\\", '\u00fc': '}', '\u00e4': '{', '\u00f6': '|', '\u00df': '~', } const umlautMapIntl = { '\u00dc': 'U', '\u00c4': 'A', '\u00d6': 'O', '\u00fc': 'u', '\u00e4': 'a', '\u00f6': 'o', '\u00df': 'ss', } function itnlUmlaute(str) { let umlautMap = umlautMapIntl return str .replace(/[\u00dc|\u00c4|\u00d6][a-z]/g, (a) => { const big = umlautMap[a.slice(0, 1)]; return big.charAt(0) + big.charAt(1).toLowerCase() + a.slice(1); }) .replace(new RegExp('['+Object.keys(umlautMap).join('|')+']',"g"), (a) => umlautMapIntl[a] ); } function replaceUmlaute(str) { let umlautMap = config.germanUmlautSupport ? umlautMapGermany : umlautMapIntl return str .replace(/[\u00dc|\u00c4|\u00d6][a-z]/g, (a) => { const big = umlautMap[a.slice(0, 1)]; return big.charAt(0) + big.charAt(1).toLowerCase() + a.slice(1); }) .replace(new RegExp('['+Object.keys(umlautMap).join('|')+']',"g"), (a) => umlautMap[a] ); } async function triggerAlert(alarmConfig) { console.log("sending msg for ", alarmConfig.name) if (alarmConfig.wbxEnabled) { wbxData = await axios.get("https://www.wettergefahren.de/warnungen/indizes/waldbrand.html") wbxData = parseWettergefahren(wbxData.data) //wbxData = JSON.parse(fs.readFileSync("./.waldbrand.json")) //fs.writeFileSync("./.waldbrand.json", JSON.stringify(wbxData)) if (!wbxData.counties[ alarmConfig.wbxCounty ]) throw "County404" if (!wbxData.counties[ alarmConfig.wbxCounty ].stationData[ alarmConfig.wbxStation ]) throw 'Station404' let stationData = wbxData.counties[ alarmConfig.wbxCounty ].stationData[ alarmConfig.wbxStation ] let dayCounter = 0 dayStrs = [] for (let dayKey of Object.keys(stationData)) { dayStrs.push(`${ dayKey.split(' ')[0] }=${ stationData[dayKey] }`) if (dayCounter > +alarmConfig.forecastLength) break dayCounter++ } await axios.post(new URL(config.pager.url).origin + '/api/message/' + (!!alarmConfig.preset ? 'preset' : 'advanced'), Object.assign({ preset: alarmConfig.preset }, { payload: `WBI: ${ replaceUmlaute(alarmConfig.wbxStation) } ${ dayStrs.join(' - ') }` })) } if (alarmConfig.gbxEnabled) { gbxData = await axios.get("https://www.wettergefahren.de/warnungen/indizes/grasland.html") gbxData = parseWettergefahren(gbxData.data) //gbxData = JSON.parse(fs.readFileSync("./.grasland.json")) //fs.writeFileSync("./.waldbrand.json", JSON.stringify(wbxData)) if (!gbxData.counties[ alarmConfig.gbxCounty ]) throw "County404" if (!gbxData.counties[ alarmConfig.gbxCounty ].stationData[ alarmConfig.gbxStation ]) throw 'Station404' let stationData = gbxData.counties[ alarmConfig.gbxCounty ].stationData[ alarmConfig.gbxStation ] let dayCounter = 0 dayStrs = [] for (let dayKey of Object.keys(stationData)) { dayStrs.push(`${ dayKey.split(' ')[0] }=${ stationData[dayKey] }`) if (dayCounter >= +alarmConfig.forecastLength) break dayCounter++ } await axios.post(new URL(config.pager.url).origin + '/api/message/' + (!!alarmConfig.preset ? 'preset' : 'advanced'), Object.assign({ preset: alarmConfig.preset }, { payload: `GBI: ${ replaceUmlaute(alarmConfig.gbxStation) } ${ dayStrs.join(' - ') }` })) } } async function minuteCheck() { let timeRN = moment() //console.log('timeRN', timeRN.format("HH:mm")) for (let alarm of config.alarms) { let alarmTime = moment() .hours(+alarm.alarmTime.split(':') [ 0 ]) .minutes(+alarm.alarmTime.split(':') [ 1 ]) let secDiff = alarmTime.diff(timeRN, 'seconds') // //console.log(alarmTime.format("HH:mm"), secDiff) if (alarmTime.format("HH:mm") == timeRN.format("HH:mm")) { //console.log("TRIGGER", alarm.alarmSchedulingMode) let alarmTrigger = false switch (alarm.alarmSchedulingMode) { case 'monthlyAtSpecificDate': alarmTrigger = (timeRN.date() == alarmTime.specificWeekday) break; case 'monthlyFirstOccWeekday': // wish Date = alarmTime.firstOccWeekday let thisMonthFirstWeekDayOccurance = -1 let monthFirstDay = moment().startOf('month') while (monthFirstDay.weekday() != alarm.firstOccWeekday) monthFirstDay.add(1, 'day') thisMonthFirstWeekDayOccurance = monthFirstDay.date() //console.log(monthFirstDay.date(), monthFirstDay.weekday()) //console.log('monthly first occurance for', alarm.firstOccWeekday, 'is', thisMonthFirstWeekDayOccurance) alarmTrigger = (timeRN.date() == thisMonthFirstWeekDayOccurance) break; case 'weekly': for (let weekDayKey of Object.keys(alarm.weekDay)) { //console.log(weekDayKey, alarm.weekDay) if (+weekDayKey === +timeRN.weekday()) { //console.log('weekday match', 'result is', alarm.weekDay[weekDayKey] ) alarmTrigger = alarm.weekDay[weekDayKey] break; } } break; } if (alarmTrigger === true) { console.log("TRIGGER!!!!!") await triggerAlert(alarm) /*await axios.post(new URL(config.pager.url).origin + '/api/message/' + (!!alarm.preset ? 'preset' : 'advanced'), Object.assign( !!alarm.preset ? { preset: alarm.preset } : { ...alarm.params } // backward compatibility , { payload: alarm.payload })) */ } } } } function parseWettergefahren(rawHTML) { const wbiResponse = { counties: {}, countyNames: [], } const { window } = new JSDOM(rawHTML) const sections = window.document.querySelectorAll("section[role='contentinfo']") for (let b of sections) { if ( b.getAttribute("aria-label") == "Legende" ) continue regionName = b.getAttribute("aria-label") wbiResponse.countyNames.push(regionName) tableHead = b.querySelector("thead") tableRows = b.querySelectorAll("tr") let headers = {}, values = {} let i = 0 for (let k of tableHead.querySelectorAll("th")) { headers[ i ] = k.textContent i++ } for (let k of tableRows) { let i = 0 let first = '' let l = 0 for (let j of k.querySelectorAll("td")) { if (first == '') { first = j.textContent; if(first == "Stationsname") break; values[first] = {} } else values[ first ][headers[ l ]] = j.textContent l++ } i++ } wbiResponse.counties[regionName] = { stationNames: Object.keys(values), stationData: values, } } return wbiResponse } async function main() { let now = moment() //await test() //table class="tab_rand" await minuteCheck() setTimeout(() => setInterval(minuteCheck, 60e3), now.diff(moment().add(1, 'minute'), 'seconds') * 1e3 + 5e3) } main() appConfig.get('/wg/autocomplete', async (req, res) => { if (req.query.refresh) { console.log("refreshing cached autocomplete jsons") try { fs.unlinkSync("./.waldbrand.json") } catch (e) {} try { fs.unlinkSync("./.grasland.json") } catch (e) {} } if (fs.existsSync("./.waldbrand.json") == false) { wbxData = await axios.get("https://www.wettergefahren.de/warnungen/indizes/waldbrand.html") fs.writeFileSync("./.waldbrand.json", JSON.stringify(parseWettergefahren(wbxData.data))) } if (fs.existsSync("./.grasland.json") == false) { gbxData = await axios.get("https://www.wettergefahren.de/warnungen/indizes/grasland.html") fs.writeFileSync("./.grasland.json", JSON.stringify(parseWettergefahren(gbxData.data))) } const WBX = fs.readFileSync("./.waldbrand.json") const GBX = fs.readFileSync("./.grasland.json") return res.json({ gbx: JSON.parse(GBX), wbx: JSON.parse(WBX), }) }) appConfig.get('/api/trigger/:index', async (req, res) => { let i = 0 for (let alarm of config.alarms) { if (i == +req.params.index) await triggerAlert(alarm) i++ } }) /** CONFIG Routes */ appConfig.get('/config', async (req, res) => { return res.json(JSON.parse(fs.readFileSync('config.json'))) }) appConfig.get('/api/deliveryPresets', async (req, res) => { const presets = await axios.get(new URL(config.pager.url).origin + '/api/deliveryPresets') return res.json(presets.data) }) appConfig.post('/config', async (req, res) => { if (!(!!req.body.alarms)) return res.status(403).json(false) if (!(!!req.body.pager)) return res.status(403).json(false) console.log(req.body) fs.writeFileSync('config.json', JSON.stringify(req.body, null, "\t")) return res.json(true) }) appConfig.post('/restart', (req, res) => { process.exit(1) }) appConfig.listen(3100, '0.0.0.0' || config.host || '127.0.0.1')