@atmosx/event-product-parser 3.0.44 → 3.0.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -31
- package/SUPPORTED_EVENTS.md +369 -0
- package/dist/cjs/index.cjs +476 -373
- package/dist/esm/index.mjs +454 -352
- package/dist/index.d.mts +42 -24
- package/dist/index.d.ts +42 -24
- package/package.json +1 -1
- package/src/@building/{building.outlooks.ts → building.attachments.ts} +7 -1
- package/src/@building/building.enhance.ts +7 -3
- package/src/@building/building.office.ts +2 -2
- package/src/@building/building.properties.ts +11 -11
- package/src/@building/building.signature.ts +11 -11
- package/src/@building/building.tags.ts +2 -2
- package/src/@building/building.tracking.ts +3 -3
- package/src/@building/building.validate.ts +2 -2
- package/src/@core/core.createEvent.ts +82 -0
- package/src/@dictionaries/{dictionaries.eventActions.ts → dictionaries.actions.ts} +1 -1
- package/src/@dictionaries/{dictionaries.eventAwipAbreviations.ts → dictionaries.awips.ts} +1 -1
- package/src/@dictionaries/{dictionaries.eventCancelMessages.ts → dictionaries.cancellation.ts} +1 -1
- package/src/@dictionaries/{dictionaries.eventCauses.ts → dictionaries.causes.ts} +1 -1
- package/src/@dictionaries/{dictionaries.statusCorrelationText.ts → dictionaries.correlations.ts} +1 -1
- package/src/@dictionaries/{dictionaries.betterEventNames.ts → dictionaries.enhanced.ts} +13 -2
- package/src/@dictionaries/{dictionaries.eventTypes.ts → dictionaries.events.ts} +1 -1
- package/src/@dictionaries/{dictionaries.regExp.ts → dictionaries.expressions.ts} +1 -1
- package/src/@dictionaries/{dictionaries.hailStrings.ts → dictionaries.hail.ts} +4 -1
- package/src/@dictionaries/{dictionaries.officeICAOs.ts → dictionaries.icao.ts} +1 -1
- package/src/@dictionaries/{dictionaries.eventsMatchText.ts → dictionaries.matches.ts} +1 -1
- package/src/@dictionaries/{dictionaries.eventProducts.ts → dictionaries.products.ts} +1 -1
- package/src/@dictionaries/{dictionaries.eventRecords.ts → dictionaries.records.ts} +1 -1
- package/src/@dictionaries/{dictionaries.eventSeverity.ts → dictionaries.severity.ts} +1 -1
- package/src/@dictionaries/{dictionaries.shapefileLinks.ts → dictionaries.shapefiles.ts} +1 -1
- package/src/@dictionaries/{dictionaries.eventStatus.ts → dictionaries.status.ts} +1 -1
- package/src/@dictionaries/{dictionaries.eventTags.ts → dictionaries.tags.ts} +1 -1
- package/src/@dictionaries/{dictionaries.testSignatures.ts → dictionaries.test.ts} +3 -2
- package/src/@events/events.api.ts +17 -15
- package/src/@events/events.text.ts +2 -2
- package/src/@events/events.ugc.ts +2 -2
- package/src/@manager/manager.mkEvent.ts +3 -3
- package/src/@manager/manager.rmEvent.ts +3 -2
- package/src/@manager/manager.updateListener.ts +124 -0
- package/src/@modules/@database/database.shapefiles.ts +2 -2
- package/src/@modules/@eas/eas.getCleanDescription.ts +2 -0
- package/src/@modules/@eas/eas.getTTS.ts +19 -8
- package/src/@modules/@eas/eas.setEasTone.ts +15 -10
- package/src/@modules/@stanza/stanza.getAwipsType.ts +2 -2
- package/src/@modules/@stanza/stanza.validate.ts +3 -3
- package/src/@modules/@utilities/utilities.setCronSchedule.ts +4 -2
- package/src/@parsers/@hvtec/hvtec.extract.ts +8 -8
- package/src/@parsers/@pvtec/pvtec.extract.ts +12 -12
- package/src/@parsers/@text/text.getDescriptionFromProduct.ts +2 -2
- package/src/@parsers/@text/text.getEmbed.ts +60 -0
- package/src/@parsers/@ugc/ugc.header.ts +3 -3
- package/src/@types/type.event.ts +2 -2
- package/src/@types/{type.webhook.ts → type.listener.ts} +11 -7
- package/src/@types/type.properties.ts +9 -9
- package/src/@types/type.settings.ts +7 -6
- package/src/bootstrap.ts +7 -6
- package/src/index.ts +2 -1
- package/test.js +19 -19
- package/src/@manager/manager.createWebhook.ts +0 -129
- package/src/@manager/manager.updateWebhooks.ts +0 -49
- package/src/@modules/@utilities/utilities.isImageReady.ts +0 -27
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
export const
|
|
20
|
+
export const dict_expressions: { [key: string]: RegExp } = {
|
|
21
21
|
pvtec: new RegExp(`[OTEX].(NEW|CON|EXT|EXA|EXB|UPG|CAN|EXP|COR|ROU).[A-Z]{4}.[A-Z]{2}.[WAYSFON].[0-9]{4}.[0-9]{6}T[0-9]{4}Z-[0-9]{6}T[0-9]{4}Z`, "g"),
|
|
22
22
|
hvtec: new RegExp(`[a-zA-Z0-9]{4}.[A-Z0-9].[A-Z]{2}.[0-9]{6}T[0-9]{4}Z.[0-9]{6}T[0-9]{4}Z.[0-9]{6}T[0-9]{4}Z.[A-Z]{2}`, "imu"),
|
|
23
23
|
wmo: new RegExp(`[A-Z0-9]{6}\\s[A-Z]{4}\\s\\d{6}`, "imu"),
|
|
@@ -17,8 +17,11 @@
|
|
|
17
17
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
export const
|
|
20
|
+
export const dict_hail: Record<string, string> = {
|
|
21
|
+
".25": "Pea",
|
|
22
|
+
".50": "Penny",
|
|
21
23
|
".75": "Penny",
|
|
24
|
+
"Up to .75": "Penny",
|
|
22
25
|
".88": "Nickel",
|
|
23
26
|
"0.75": "Penny",
|
|
24
27
|
"0.88": "Nickel",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
export const
|
|
20
|
+
export const dict_matches: Record<string, string> = {
|
|
21
21
|
"Special Weather Statement": "Special Weather Statement",
|
|
22
22
|
"Hurricane Warning": "Hurricane Warning",
|
|
23
23
|
"Hurricane Force Wind Warning": "Hurricane Force Wind Warning",
|
|
@@ -23,7 +23,7 @@ interface ShapefilesResponse {
|
|
|
23
23
|
link: string
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
export const
|
|
26
|
+
export const dict_shapefiles: ShapefilesResponse[] = [
|
|
27
27
|
{name: "us_counties", id: "C", link: "https://www.weather.gov/source/gis/Shapefiles/County/c_16ap26.zip"},
|
|
28
28
|
{name: "us_states_territories", id: "Z", link: "https://www.weather.gov/source/gis/Shapefiles/County/s_16ap26.zip"},
|
|
29
29
|
{name: "fire_weather_zones", id: "Z", link: "https://www.weather.gov/source/gis/Shapefiles/WSOM/fz16ap26.zip"},
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
export const
|
|
20
|
+
export const dict_tags: Record<string, string> = {
|
|
21
21
|
"FROSTBITE AND HYPOTHERMIA ARE LIKELY": "Frostbite and Hypothermia Likely",
|
|
22
22
|
"LICKELY BECOME SLICK AND HAZARDOUS": "Slick and Hazardous Roads",
|
|
23
23
|
"SLIPPERY ROAD CONDITIONS": "Slippery Roads",
|
|
@@ -17,8 +17,9 @@
|
|
|
17
17
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
export const
|
|
20
|
+
export const dict_testing: string[] = [
|
|
21
21
|
`This is a test message`,
|
|
22
22
|
`Monitoring message only.`,
|
|
23
|
-
`THIS_MESSAGE_IS_FOR_TEST_PURPOSES_ONLY
|
|
23
|
+
`THIS_MESSAGE_IS_FOR_TEST_PURPOSES_ONLY`,
|
|
24
|
+
"TEST MESSAGE"
|
|
24
25
|
]
|
|
@@ -21,17 +21,19 @@ import { TypeStanzaCompiled } from "../@types/type.compiled"
|
|
|
21
21
|
import { TypeEvent } from "../@types/type.event";
|
|
22
22
|
import { getEventTracking } from "../@building/building.tracking";
|
|
23
23
|
import { validateEvents } from "../@building/building.validate";
|
|
24
|
-
import {
|
|
24
|
+
import { pvExtract } from "../@parsers/@pvtec/pvtec.extract"
|
|
25
25
|
import { getEventTags } from "../@building/building.tags";
|
|
26
26
|
import { getTextFromProduct } from "../@parsers/@text/text.getTextFromProduct";
|
|
27
|
-
import {
|
|
27
|
+
import { dict_icao } from "../@dictionaries/dictionaries.icao";
|
|
28
|
+
import { TypePVTEC } from "../@types/type.pvtec";
|
|
29
|
+
|
|
28
30
|
|
|
29
31
|
export const api = async (stanza: TypeStanzaCompiled): Promise<void> => {
|
|
30
32
|
let processed: TypeEvent[] = [];
|
|
31
33
|
const messages = Object.values(JSON.parse(stanza.message).features) as any;
|
|
32
34
|
for (const feature of messages) {
|
|
33
35
|
const tick = performance.now();
|
|
34
|
-
const pVtec = feature?.properties?.parameters?.VTEC?.[0] ?? null
|
|
36
|
+
const pVtec = pvExtract(feature?.properties?.parameters?.VTEC?.[0]) ?? null as TypePVTEC[]
|
|
35
37
|
processed.push({
|
|
36
38
|
type: `Feature`,
|
|
37
39
|
geometry: {
|
|
@@ -49,8 +51,8 @@ export const api = async (stanza: TypeStanzaCompiled): Promise<void> => {
|
|
|
49
51
|
attributes: feature?.properties?.attributes ?? {},
|
|
50
52
|
geocode: {
|
|
51
53
|
office: {
|
|
52
|
-
office: pVtec ? pVtec.split(`.`)[
|
|
53
|
-
name:
|
|
54
|
+
office: pVtec ? pVtec?.[0]?.tracking.split(`.`)[0] : null,
|
|
55
|
+
name: dict_icao[pVtec ? pVtec?.[0]?.tracking.split(`.`)[0] : null] ?? null,
|
|
54
56
|
},
|
|
55
57
|
organization: feature?.properties?.parameters?.WMOidentifier?.[0],
|
|
56
58
|
ugc: feature?.properties?.geocode?.UGC ?? [],
|
|
@@ -71,16 +73,16 @@ export const api = async (stanza: TypeStanzaCompiled): Promise<void> => {
|
|
|
71
73
|
wind_threat: feature?.properties?.parameters?.windThreat?.[0] ?? null,
|
|
72
74
|
hail_threat: feature?.properties?.parameters?.hailThreat?.[0] ?? null,
|
|
73
75
|
},
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
76
|
+
discussion_parameters: {
|
|
77
|
+
discussion_number: getTextFromProduct({ message: feature?.properties?.description, find: [`Mesoscale Discussion `], removal: [`Mesoscale Discussion`, `Number`, `...`] })?.toString()?.padStart(4, "0") ?? null,
|
|
78
|
+
discussion_concerning: getTextFromProduct({ message: feature?.properties?.description, find: [`Concerning...`] }) ?? null,
|
|
79
|
+
discussion_max_tornado: getTextFromProduct({ message: feature?.properties?.description, find: [`MOST PROBABLE PEAK TORNADO INTENSITY...`] }) ?? null,
|
|
80
|
+
discussion_max_hail: getTextFromProduct({ message: feature?.properties?.description, find: [`MOST PROBABLE PEAK HAIL SIZE...`] }) ?? null,
|
|
81
|
+
discussion_max_wind: getTextFromProduct({ message: feature?.properties?.description, find: [`MOST PROBABLE PEAK WIND GUST...`] }) ?? null,
|
|
82
|
+
discussion_watch_issuance: getTextFromProduct({ message: feature?.properties?.description, find: [`Probability of Watch Issuance...`], removal: [`percent`]}) ?? null,
|
|
81
83
|
},
|
|
82
84
|
watch_parameters: {
|
|
83
|
-
watch_number: getTextFromProduct({ message: feature?.properties?.description, find: [`ITIES FOR`, `UPDATE FOR`, `Watch Number `], removal: [`%`, `<`, `:`] })?.replace(/(WT|WS|)/g, '')?.trim() ?? null,
|
|
85
|
+
watch_number: (pVtec?.[0]?.is_watch) && (getTextFromProduct({ message: feature?.properties?.description, find: [`ITIES FOR`, `UPDATE FOR`, `Watch Number `], removal: [`%`, `<`, `:`] })?.replace(/(WT|WS|)/g, '')?.trim()?.toString()?.padStart(4, "0") ?? pVtec?.[0]?.tracking?.slice(-4)?.toString()?.padStart(4, "0") ?? null),
|
|
84
86
|
watch_type: feature?.properties?.description.includes(`TORNADO WATCH`) ? `Tornado` : feature?.properties?.description.includes(`SEVERE`) ? `Severe` : null,
|
|
85
87
|
additional_tornadoes_probability: getTextFromProduct({ message: feature?.properties?.description, find: [`PROB OF 2 OR MORE TORNADOES`], removal: [`%`, `<`, `:`] }) ?? null,
|
|
86
88
|
strong_tornadoes_probability: getTextFromProduct({ message: feature?.properties?.description, find: [`PROB OF 1 OR MORE STRONG /EF2-EF5/ TORNADOES`], removal: [`%`, `<`, `:`] }) ?? null,
|
|
@@ -96,9 +98,9 @@ export const api = async (stanza: TypeStanzaCompiled): Promise<void> => {
|
|
|
96
98
|
metadata: {
|
|
97
99
|
ms: performance.now() - tick,
|
|
98
100
|
source: `events.api`,
|
|
99
|
-
tracking: getEventTracking({ type: `API`, organization: { wmoidentifier: feature?.properties?.parameters?.WMOidentifier?.[0], featureId: feature?.id}, vtec: pVtec}),
|
|
101
|
+
tracking: getEventTracking({ type: `API`, organization: { wmoidentifier: feature?.properties?.parameters?.WMOidentifier?.[0], featureId: feature?.id}, vtec: pVtec?.[0]}),
|
|
100
102
|
header: `ZCZC-ATMOSX-${feature?.properties?.parameters?.WMOidentifier}`,
|
|
101
|
-
vtec: pVtec,
|
|
103
|
+
vtec: pVtec?.[0],
|
|
102
104
|
hvtec: null,
|
|
103
105
|
raw: feature?.properties?.description,
|
|
104
106
|
history: [
|
|
@@ -22,7 +22,7 @@ import { TypeStanzaCompiled } from "../@types/type.compiled"
|
|
|
22
22
|
import { TypeEvent } from "../@types/type.event";
|
|
23
23
|
import { properties } from "../@building/building.properties";
|
|
24
24
|
import { getEventHeader } from "../@building/building.headers";
|
|
25
|
-
import {
|
|
25
|
+
import { dict_matches } from "../@dictionaries/dictionaries.matches";
|
|
26
26
|
import { getEventTracking } from "../@building/building.tracking";
|
|
27
27
|
import { validateEvents } from "../@building/building.validate";
|
|
28
28
|
|
|
@@ -40,7 +40,7 @@ export const text = async (stanza: TypeStanzaCompiled): Promise<void> => {
|
|
|
40
40
|
const header = getEventHeader({properties: props, getType: stanza.getType})
|
|
41
41
|
const issued = new Date(attributes.issue)
|
|
42
42
|
const expires = new Date(issued.getTime() + 12 * 60 * 60 * 1000)
|
|
43
|
-
let event = Object.keys(
|
|
43
|
+
let event = Object.keys(dict_matches).find(event => message.toLowerCase().includes(event.toLowerCase()));
|
|
44
44
|
let isStatement = false;
|
|
45
45
|
if (!event) {
|
|
46
46
|
event = stanza.getType.type.split(`-`).map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(` `)
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
import { TypeAttributes } from "../@types/type.attributes";
|
|
21
21
|
import { TypeStanzaCompiled } from "../@types/type.compiled"
|
|
22
22
|
import { TypeEvent } from "../@types/type.event";
|
|
23
|
-
import {
|
|
23
|
+
import { dict_matches } from "../@dictionaries/dictionaries.matches";
|
|
24
24
|
import { ugcExtract } from "../@parsers/@ugc/ugc.extract";
|
|
25
25
|
import { properties } from "../@building/building.properties";
|
|
26
26
|
import { getEventHeader } from "../@building/building.headers";
|
|
@@ -43,7 +43,7 @@ export const ugc = async (stanza: TypeStanzaCompiled): Promise<void> => {
|
|
|
43
43
|
const issued = new Date(attributes.issue)
|
|
44
44
|
const expires = new Date(ugc.expires)
|
|
45
45
|
const header = getEventHeader({properties: props, getType: stanza.getType })
|
|
46
|
-
let event = Object.keys(
|
|
46
|
+
let event = Object.keys(dict_matches).find(event => message.toLowerCase().includes(event.toLowerCase()));
|
|
47
47
|
let isStatement = false;
|
|
48
48
|
if (!event) {
|
|
49
49
|
event = stanza.getType.type.split(`-`).map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(` `)
|
|
@@ -21,7 +21,7 @@ import { setEventEmit } from "../@modules/@utilities/utilities.setEventEmit";
|
|
|
21
21
|
import { TypeEvent } from "../@types/type.event";
|
|
22
22
|
import { bootstrap } from "../bootstrap"
|
|
23
23
|
import { setHash } from "./manager.setHash";
|
|
24
|
-
import {
|
|
24
|
+
import { updateListener } from "./manager.updateListener";
|
|
25
25
|
import { updateNode } from "./manager.updateNodes";
|
|
26
26
|
import { TypeSettings } from "../@types/type.settings";
|
|
27
27
|
|
|
@@ -79,10 +79,10 @@ export const mkEvent = async (event: TypeEvent): Promise<void> => {
|
|
|
79
79
|
},
|
|
80
80
|
}
|
|
81
81
|
};
|
|
82
|
-
|
|
82
|
+
updateListener(bootstrap.cache.events.features[getIndex])
|
|
83
83
|
} else {
|
|
84
84
|
features.push(event)
|
|
85
|
-
|
|
85
|
+
updateListener(event)
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
}
|
|
@@ -20,13 +20,14 @@
|
|
|
20
20
|
import { setEventEmit } from "../@modules/@utilities/utilities.setEventEmit";
|
|
21
21
|
import { TypeEvent } from "../@types/type.event";
|
|
22
22
|
import { bootstrap } from "../bootstrap"
|
|
23
|
-
import {
|
|
23
|
+
import { updateListener } from "./manager.updateListener";
|
|
24
24
|
|
|
25
25
|
export const rmEvent = (event: TypeEvent): void => {
|
|
26
26
|
const getEvent = bootstrap.cache.events.features.find(f => f?.properties?.metadata?.tracking === event?.properties?.metadata?.tracking);
|
|
27
27
|
const cachedStatus = event.properties.status;
|
|
28
28
|
event.properties.expires = new Date().toISOString();
|
|
29
29
|
event.properties.status = `Expired`;
|
|
30
|
+
event.properties.status_metadata.is_expired = true;
|
|
30
31
|
if (getEvent) {
|
|
31
32
|
setEventEmit({
|
|
32
33
|
event: `onEventStatus`,
|
|
@@ -37,7 +38,7 @@ export const rmEvent = (event: TypeEvent): void => {
|
|
|
37
38
|
message: `[Removed] ${event.properties.event} (${event.properties.status}) (${event.properties.metadata.tracking})`
|
|
38
39
|
})
|
|
39
40
|
setEventEmit({ event: `onExpiredProduct`, metadata: event })
|
|
40
|
-
if (cachedStatus != `Statement`)
|
|
41
|
+
if (cachedStatus != `Statement`) updateListener(event)
|
|
41
42
|
bootstrap.cache.events.features.splice(bootstrap.cache.events.features.indexOf(getEvent), 1);
|
|
42
43
|
bootstrap.cache.hashes = bootstrap.cache.hashes.filter(hash => hash.tracking !== event.properties.metadata.tracking);
|
|
43
44
|
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/*
|
|
2
|
+
_ _ _ __ __
|
|
3
|
+
/\ | | | | (_) \ \ / /
|
|
4
|
+
/ \ | |_ _ __ ___ ___ ___ _ __ | |__ ___ _ __ _ ___ \ V /
|
|
5
|
+
/ /\ \| __| '_ ` _ \ / _ \/ __| '_ \| '_ \ / _ \ '__| |/ __| > <
|
|
6
|
+
/ ____ \ |_| | | | | | (_) \__ \ |_) | | | | __/ | | | (__ / . \
|
|
7
|
+
/_/ \_\__|_| |_| |_|\___/|___/ .__/|_| |_|\___|_| |_|\___/_/ \_\
|
|
8
|
+
| |
|
|
9
|
+
|_|
|
|
10
|
+
|
|
11
|
+
Created with ♥ by the AtmosphericX Team (KiyoWx, StarflightWx, & CJ Ziegler)
|
|
12
|
+
Discord: https://atmosphericx-discord.scriptkitty.cafe
|
|
13
|
+
Ko-Fi: https://ko-fi.com/k3yomi
|
|
14
|
+
Documentation: http://localhost/documentation | https://atmosphericx.scriptkitty.cafe/documentation
|
|
15
|
+
|
|
16
|
+
Internal Package: @atmosx/event-product-parser
|
|
17
|
+
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
import { TypeEvent } from "../@types/type.event";
|
|
22
|
+
import { TypeListener } from "../@types/type.listener";
|
|
23
|
+
import { bootstrap } from "../bootstrap"
|
|
24
|
+
import { setEasTone } from "../@modules/@eas/eas.setEasTone"
|
|
25
|
+
import { setTimeoutAction } from "../@modules/@utilities/utilities.setTimeoutAction";
|
|
26
|
+
import { getEmebed } from "../@parsers/@text/text.getEmbed";
|
|
27
|
+
import { createHttp } from "../@modules/@utilities/utilities.createHttp";
|
|
28
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync, appendFileSync } from "fs";
|
|
29
|
+
import FormData from "form-data";
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
export const updateListener = async (event: TypeEvent): Promise<void> => {
|
|
35
|
+
const settings = bootstrap.settings;
|
|
36
|
+
const listeners = settings.ListenerSettings as TypeListener[];
|
|
37
|
+
const properties = event.properties;
|
|
38
|
+
for (const socket of listeners) {
|
|
39
|
+
const events = socket.events;
|
|
40
|
+
const sWebhook = socket?.webhook;
|
|
41
|
+
const sUploads = socket?.uploads;
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
const matched = events.some(pattern => {
|
|
45
|
+
if (!pattern) return false;
|
|
46
|
+
if (pattern === "*" || pattern === properties.event) return true;
|
|
47
|
+
if (pattern.includes("*")) {
|
|
48
|
+
const regex = "^" +
|
|
49
|
+
pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*") + "$";
|
|
50
|
+
return new RegExp(regex).test(properties.event);
|
|
51
|
+
}
|
|
52
|
+
return false;
|
|
53
|
+
});
|
|
54
|
+
if (events?.length == 0 || matched) {
|
|
55
|
+
let eas;
|
|
56
|
+
let file;
|
|
57
|
+
|
|
58
|
+
if (sUploads?.eas) {
|
|
59
|
+
eas = await setEasTone({
|
|
60
|
+
title: `${properties.event}_${properties.status}_${properties.metadata.tracking}`,
|
|
61
|
+
message: properties.description,
|
|
62
|
+
header: properties.metadata.header
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
if (sUploads?.file) {
|
|
66
|
+
const destination = bootstrap.settings.GlobalSettings.ArchiveSettings.TextDirectory;
|
|
67
|
+
if (destination) {
|
|
68
|
+
if (!existsSync(destination)) { mkdirSync(destination, { recursive: true }); }
|
|
69
|
+
if (existsSync(`${destination}/${properties.event}_${properties.status}_${properties.metadata.tracking}.txt`)) {
|
|
70
|
+
appendFileSync(`${destination}/${properties.event}_${properties.status}_${properties.metadata.tracking}.txt`, properties.metadata.raw);
|
|
71
|
+
} else {
|
|
72
|
+
writeFileSync(`${destination}/${properties.event}_${properties.status}_${properties.metadata.tracking}.txt`, properties.metadata.raw);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
file = properties.metadata.raw;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (sWebhook?.enabled) {
|
|
79
|
+
const isLimited = setTimeoutAction({ identifier: sWebhook.destination, interval: sWebhook.ratelimit, max: sWebhook.ratelimit, addTime: true })
|
|
80
|
+
if (!isLimited.limited) {
|
|
81
|
+
let body = getEmebed(event);
|
|
82
|
+
if (body.length > 1900) {
|
|
83
|
+
body = body.substring(0, 1900) + "\n\n[Message truncated due to length]";
|
|
84
|
+
const blocks = (body.match(/```/g) ?? []).length;
|
|
85
|
+
if (blocks % 2 !== 0) body += "```";
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (properties.description.length < 25) { continue }
|
|
89
|
+
|
|
90
|
+
const form = new FormData();
|
|
91
|
+
const embed = {
|
|
92
|
+
title: `${properties.event} (${properties.status})`,
|
|
93
|
+
description: body,
|
|
94
|
+
color: 16711680,
|
|
95
|
+
timestamp: new Date().toISOString(),
|
|
96
|
+
image: {},
|
|
97
|
+
footer: { text: sWebhook.title ?? `AtmosphericX` }
|
|
98
|
+
};
|
|
99
|
+
form.append("payload_json", JSON.stringify({
|
|
100
|
+
username: sWebhook.title ?? "AtmosphericX",
|
|
101
|
+
content: sWebhook.message ?? "",
|
|
102
|
+
embeds: [embed]
|
|
103
|
+
}));
|
|
104
|
+
if (sUploads?.file) {
|
|
105
|
+
form.append("fUpload", Buffer.from(file), { filename: `${properties.event}_${properties.status}_${properties.metadata.tracking}.txt`, contentType: "application/text" });
|
|
106
|
+
}
|
|
107
|
+
if (sUploads?.eas) {
|
|
108
|
+
if (eas) {
|
|
109
|
+
const file = readFileSync(eas)
|
|
110
|
+
form.append("fEas", Buffer.from(file), { filename: `${properties.event}_${properties.status}_${properties.metadata.tracking}_eas.mp3`, contentType: "audio/mpeg" });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const a = await createHttp({
|
|
114
|
+
url: sWebhook.destination,
|
|
115
|
+
timeout: 5000,
|
|
116
|
+
method: `POST`,
|
|
117
|
+
headers: form.getHeaders(),
|
|
118
|
+
body: form
|
|
119
|
+
})
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
};
|
|
@@ -25,7 +25,7 @@ import { TypeSettings } from "../../@types/type.settings"
|
|
|
25
25
|
import { bootstrap } from "../../bootstrap"
|
|
26
26
|
import { setSleep } from '../@utilities/utilities.setSleep'
|
|
27
27
|
import { setWarning } from '../@utilities/utilities.setWarning'
|
|
28
|
-
import {
|
|
28
|
+
import { dict_shapefiles } from '../../@dictionaries/dictionaries.shapefiles'
|
|
29
29
|
|
|
30
30
|
export const importShapefiles = async (): Promise<void> => {
|
|
31
31
|
const settings = bootstrap.settings as TypeSettings;
|
|
@@ -36,7 +36,7 @@ export const importShapefiles = async (): Promise<void> => {
|
|
|
36
36
|
if (tShapefiles === 0) {
|
|
37
37
|
await setSleep({timeout: 1e3});
|
|
38
38
|
setWarning({ message: `Shapefiles are currently building, please DO NOT close your terminal. The shapefiles will not finish and will remain incomplete. If you do mess up, you will need to delete ${settings.Database} and restart the application.` })
|
|
39
|
-
for (const shapefile of
|
|
39
|
+
for (const shapefile of dict_shapefiles) {
|
|
40
40
|
const response = await fetch(shapefile.link);
|
|
41
41
|
const arrayBuff = await response.arrayBuffer();
|
|
42
42
|
const content = await loadAsync(arrayBuff);
|
|
@@ -77,9 +77,11 @@ export const getCleanDescription = (message: string): string => {
|
|
|
77
77
|
message = message.replace(regex, replacement)
|
|
78
78
|
}
|
|
79
79
|
return message
|
|
80
|
+
.replace(/'/g, "''")
|
|
80
81
|
.replace(/\b(\d{1,2}):(\d{2})\s*(AM|PM)\b/gi, (_, h, m, ap) => fmt(+h, +m, ap.toUpperCase()))
|
|
81
82
|
.replace(/\b(\d{1,2})\s+(\d{2})\s+(AM|PM)\b/gi, (_, h, m, ap) => fmt(+h, +m, ap.toUpperCase()))
|
|
82
83
|
.replace(/\b(\d{2})(\d{2})\s+(AM|PM)\b/gi, (_, hh, mm, ap) => fmt(+hh, +mm, ap.toUpperCase()))
|
|
84
|
+
.replace(/\b(\d{1,2})\s+(AM|PM)\b/gi, (_, h, ap) => fmt(+h, 0, ap.toUpperCase()))
|
|
83
85
|
.replace(/\*+/g, " ")
|
|
84
86
|
.replace(/-/g, " ")
|
|
85
87
|
.replace(/\n/g, " ")
|
|
@@ -19,23 +19,34 @@
|
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
import { platform } from 'os'
|
|
22
|
+
import { writeFileSync } from "fs"
|
|
22
23
|
import { execSync } from 'child_process'
|
|
23
24
|
|
|
24
25
|
export const getTTS = async (text: string, outputPath: string): Promise<void> => {
|
|
25
26
|
const vPlatform = platform();
|
|
26
27
|
switch (vPlatform) {
|
|
27
28
|
case 'win32': {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
29
|
+
try {
|
|
30
|
+
const txtPath = outputPath + ".txt";
|
|
31
|
+
writeFileSync(txtPath, text, "utf8");
|
|
32
|
+
const command = [
|
|
33
|
+
"Add-Type -AssemblyName System.Speech;",
|
|
34
|
+
`$speak = New-Object System.Speech.Synthesis.SpeechSynthesizer;`,
|
|
35
|
+
`$text = Get-Content -Raw -LiteralPath '${txtPath}';`,
|
|
36
|
+
`$speak.SetOutputToWaveFile('${outputPath}');`,
|
|
37
|
+
`$speak.Speak($text);`,
|
|
38
|
+
`$speak.Dispose();`
|
|
39
|
+
].join(" ");
|
|
40
|
+
execSync(`powershell -Command "${command}"`, { stdio: 'inherit' })
|
|
41
|
+
} catch(error) {
|
|
42
|
+
console.error('Error occurred while generating TTS:', error);
|
|
43
|
+
}
|
|
35
44
|
break;
|
|
36
45
|
}
|
|
37
46
|
case 'linux':
|
|
38
|
-
|
|
47
|
+
try {
|
|
48
|
+
execSync(`espeak -w "${outputPath}" "${text.replace(/"/g, '\\"')}"`);
|
|
49
|
+
} catch {}
|
|
39
50
|
break;
|
|
40
51
|
default: break;
|
|
41
52
|
}
|
|
@@ -38,14 +38,16 @@ import { platform } from 'os'
|
|
|
38
38
|
interface GenerateEASOptions {
|
|
39
39
|
message: string
|
|
40
40
|
header: string
|
|
41
|
+
title: string
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
export const setEasTone = async (options: GenerateEASOptions): Promise<string> => {
|
|
44
45
|
const settings = bootstrap.settings as TypeSettings;
|
|
45
|
-
const directory = settings.GlobalSettings.
|
|
46
|
-
const prefix = settings.GlobalSettings.
|
|
46
|
+
const directory = settings.GlobalSettings.ArchiveSettings.EasDirectory;
|
|
47
|
+
const prefix = settings.GlobalSettings.ArchiveSettings.EasToneout;
|
|
47
48
|
let message = options.message;
|
|
48
49
|
let header = options.header;
|
|
50
|
+
let title = options.title ?? `${Math.random().toString(36).substring(2, 15)}-${header.replace(/[^a-zA-Z0-9]/g, '')}`;
|
|
49
51
|
if (!message || !header) {
|
|
50
52
|
setWarning({
|
|
51
53
|
title: `EAS`,
|
|
@@ -60,15 +62,15 @@ export const setEasTone = async (options: GenerateEASOptions): Promise<string> =
|
|
|
60
62
|
if (!existsSync(directory)) {
|
|
61
63
|
mkdirSync(directory, { recursive: true });
|
|
62
64
|
}
|
|
63
|
-
if (!existsSync(join(directory, `/
|
|
64
|
-
mkdirSync(join(directory, `/
|
|
65
|
+
if (!existsSync(join(directory, `/tts`))) {
|
|
66
|
+
mkdirSync(join(directory, `/tts`), { recursive: true });
|
|
65
67
|
}
|
|
66
|
-
if (!existsSync(join(directory, `/
|
|
67
|
-
mkdirSync(join(directory, `/
|
|
68
|
+
if (!existsSync(join(directory, `/encoded`))) {
|
|
69
|
+
mkdirSync(join(directory, `/encoded`), { recursive: true });
|
|
68
70
|
}
|
|
69
71
|
|
|
70
|
-
const tmpTTS = join(directory, `/
|
|
71
|
-
const outTTS = join(directory, `/
|
|
72
|
+
const tmpTTS = join(directory, `/tts/${title}.wav`)
|
|
73
|
+
const outTTS = join(directory, `/encoded/${title}.wav`)
|
|
72
74
|
const vPlatform = platform();
|
|
73
75
|
|
|
74
76
|
if (vPlatform === 'darwin') {
|
|
@@ -83,10 +85,13 @@ export const setEasTone = async (options: GenerateEASOptions): Promise<string> =
|
|
|
83
85
|
await new Promise<void>((resolve, reject) => {
|
|
84
86
|
const tMsg = getCleanDescription(message)
|
|
85
87
|
getTTS(tMsg, tmpTTS).then(() => {
|
|
86
|
-
buffTTS = readFileSync(tmpTTS);
|
|
87
88
|
resolve();
|
|
88
89
|
});
|
|
89
90
|
});
|
|
91
|
+
if (!existsSync(tmpTTS)) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
buffTTS = readFileSync(tmpTTS);
|
|
90
95
|
const vWav = getWavPCM16(buffTTS);
|
|
91
96
|
const vSamples = getSampledPCM16(vWav.samples, vWav.sampleRate, 8000)
|
|
92
97
|
const vRadio = setRadioEffect(vSamples, 8000)
|
|
@@ -97,7 +102,7 @@ export const setEasTone = async (options: GenerateEASOptions): Promise<string> =
|
|
|
97
102
|
|
|
98
103
|
if (tWav == null) {
|
|
99
104
|
try {
|
|
100
|
-
const converted = join(directory, `/
|
|
105
|
+
const converted = join(directory, `/tts/${title}.converted.wav`)
|
|
101
106
|
execSync(`ffmpeg -y -i "${prefix}" -ar 8000 -ac 1 -sample_fmt s16 "${converted}"`, { stdio: 'ignore' })
|
|
102
107
|
if (existsSync(converted)) {
|
|
103
108
|
tBuffer = readFileSync(converted)
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
import { TypeAttributes } from "../../@types/type.attributes"
|
|
21
|
-
import {
|
|
21
|
+
import { dict_awips } from "../../@dictionaries/dictionaries.awips"
|
|
22
22
|
|
|
23
23
|
interface ValidateOptions {
|
|
24
24
|
attributes: TypeAttributes
|
|
@@ -37,7 +37,7 @@ export const getAwipsType = (options: ValidateOptions): ValidiateResponse => {
|
|
|
37
37
|
prefix: null
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
|
-
for (const [prefix, type] of Object.entries(
|
|
40
|
+
for (const [prefix, type] of Object.entries(dict_awips)) {
|
|
41
41
|
if (attributes.awipsid.startsWith(prefix)) {
|
|
42
42
|
return { type, prefix }
|
|
43
43
|
}
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
import { TypeStanza } from "../../@types/type.stanza"
|
|
21
21
|
import { TypeStanzaCompiled } from "../../@types/type.compiled"
|
|
22
|
-
import {
|
|
22
|
+
import { dict_expressions } from "../../@dictionaries/dictionaries.expressions"
|
|
23
23
|
import { getAwipsType } from "./stanza.getAwipsType"
|
|
24
24
|
|
|
25
25
|
interface ValidateOptions {
|
|
@@ -35,8 +35,8 @@ export const validate = (options: ValidateOptions): TypeStanzaCompiled => {
|
|
|
35
35
|
if (attributes.awipsid && attributes.awipsid.length > 1) {
|
|
36
36
|
const isCapEvent = message.includes(`<?xml`);
|
|
37
37
|
const isCapAreaDescription = message.includes(`<areaDesc>`)
|
|
38
|
-
const isVTEC = message.match(
|
|
39
|
-
const isUGC = message.match(
|
|
38
|
+
const isVTEC = message.match(dict_expressions.pvtec) != null;
|
|
39
|
+
const isUGC = message.match(dict_expressions.ugc1) != null;
|
|
40
40
|
const getType = getAwipsType({ attributes: attributes})
|
|
41
41
|
if (getType.type != null) {
|
|
42
42
|
return {
|
|
@@ -30,8 +30,9 @@ import { join } from "path";
|
|
|
30
30
|
export const setCronSchedule = async (): Promise<void> => {
|
|
31
31
|
const settings = bootstrap.settings as TypeSettings;
|
|
32
32
|
|
|
33
|
-
const TTL = settings.GlobalSettings.
|
|
33
|
+
const TTL = settings.GlobalSettings.ArchiveSettings.TTL
|
|
34
34
|
const TTLCUT = Date.now() - TTL * 1000;
|
|
35
|
+
|
|
35
36
|
const walk = (dir: string): void => {
|
|
36
37
|
if (existsSync(dir)) {
|
|
37
38
|
const entries = readdirSync(dir, { withFileTypes: true });
|
|
@@ -49,7 +50,8 @@ export const setCronSchedule = async (): Promise<void> => {
|
|
|
49
50
|
}
|
|
50
51
|
}
|
|
51
52
|
};
|
|
52
|
-
walk(settings.GlobalSettings.
|
|
53
|
+
walk(settings.GlobalSettings.ArchiveSettings.TextDirectory)
|
|
54
|
+
walk(settings.GlobalSettings.ArchiveSettings.EasDirectory)
|
|
53
55
|
|
|
54
56
|
|
|
55
57
|
|