@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.
Files changed (62) hide show
  1. package/README.md +66 -31
  2. package/SUPPORTED_EVENTS.md +369 -0
  3. package/dist/cjs/index.cjs +476 -373
  4. package/dist/esm/index.mjs +454 -352
  5. package/dist/index.d.mts +42 -24
  6. package/dist/index.d.ts +42 -24
  7. package/package.json +1 -1
  8. package/src/@building/{building.outlooks.ts → building.attachments.ts} +7 -1
  9. package/src/@building/building.enhance.ts +7 -3
  10. package/src/@building/building.office.ts +2 -2
  11. package/src/@building/building.properties.ts +11 -11
  12. package/src/@building/building.signature.ts +11 -11
  13. package/src/@building/building.tags.ts +2 -2
  14. package/src/@building/building.tracking.ts +3 -3
  15. package/src/@building/building.validate.ts +2 -2
  16. package/src/@core/core.createEvent.ts +82 -0
  17. package/src/@dictionaries/{dictionaries.eventActions.ts → dictionaries.actions.ts} +1 -1
  18. package/src/@dictionaries/{dictionaries.eventAwipAbreviations.ts → dictionaries.awips.ts} +1 -1
  19. package/src/@dictionaries/{dictionaries.eventCancelMessages.ts → dictionaries.cancellation.ts} +1 -1
  20. package/src/@dictionaries/{dictionaries.eventCauses.ts → dictionaries.causes.ts} +1 -1
  21. package/src/@dictionaries/{dictionaries.statusCorrelationText.ts → dictionaries.correlations.ts} +1 -1
  22. package/src/@dictionaries/{dictionaries.betterEventNames.ts → dictionaries.enhanced.ts} +13 -2
  23. package/src/@dictionaries/{dictionaries.eventTypes.ts → dictionaries.events.ts} +1 -1
  24. package/src/@dictionaries/{dictionaries.regExp.ts → dictionaries.expressions.ts} +1 -1
  25. package/src/@dictionaries/{dictionaries.hailStrings.ts → dictionaries.hail.ts} +4 -1
  26. package/src/@dictionaries/{dictionaries.officeICAOs.ts → dictionaries.icao.ts} +1 -1
  27. package/src/@dictionaries/{dictionaries.eventsMatchText.ts → dictionaries.matches.ts} +1 -1
  28. package/src/@dictionaries/{dictionaries.eventProducts.ts → dictionaries.products.ts} +1 -1
  29. package/src/@dictionaries/{dictionaries.eventRecords.ts → dictionaries.records.ts} +1 -1
  30. package/src/@dictionaries/{dictionaries.eventSeverity.ts → dictionaries.severity.ts} +1 -1
  31. package/src/@dictionaries/{dictionaries.shapefileLinks.ts → dictionaries.shapefiles.ts} +1 -1
  32. package/src/@dictionaries/{dictionaries.eventStatus.ts → dictionaries.status.ts} +1 -1
  33. package/src/@dictionaries/{dictionaries.eventTags.ts → dictionaries.tags.ts} +1 -1
  34. package/src/@dictionaries/{dictionaries.testSignatures.ts → dictionaries.test.ts} +3 -2
  35. package/src/@events/events.api.ts +17 -15
  36. package/src/@events/events.text.ts +2 -2
  37. package/src/@events/events.ugc.ts +2 -2
  38. package/src/@manager/manager.mkEvent.ts +3 -3
  39. package/src/@manager/manager.rmEvent.ts +3 -2
  40. package/src/@manager/manager.updateListener.ts +124 -0
  41. package/src/@modules/@database/database.shapefiles.ts +2 -2
  42. package/src/@modules/@eas/eas.getCleanDescription.ts +2 -0
  43. package/src/@modules/@eas/eas.getTTS.ts +19 -8
  44. package/src/@modules/@eas/eas.setEasTone.ts +15 -10
  45. package/src/@modules/@stanza/stanza.getAwipsType.ts +2 -2
  46. package/src/@modules/@stanza/stanza.validate.ts +3 -3
  47. package/src/@modules/@utilities/utilities.setCronSchedule.ts +4 -2
  48. package/src/@parsers/@hvtec/hvtec.extract.ts +8 -8
  49. package/src/@parsers/@pvtec/pvtec.extract.ts +12 -12
  50. package/src/@parsers/@text/text.getDescriptionFromProduct.ts +2 -2
  51. package/src/@parsers/@text/text.getEmbed.ts +60 -0
  52. package/src/@parsers/@ugc/ugc.header.ts +3 -3
  53. package/src/@types/type.event.ts +2 -2
  54. package/src/@types/{type.webhook.ts → type.listener.ts} +11 -7
  55. package/src/@types/type.properties.ts +9 -9
  56. package/src/@types/type.settings.ts +7 -6
  57. package/src/bootstrap.ts +7 -6
  58. package/src/index.ts +2 -1
  59. package/test.js +19 -19
  60. package/src/@manager/manager.createWebhook.ts +0 -129
  61. package/src/@manager/manager.updateWebhooks.ts +0 -49
  62. package/src/@modules/@utilities/utilities.isImageReady.ts +0 -27
@@ -17,7 +17,7 @@
17
17
 
18
18
  */
19
19
 
20
- export const eventTypes: Record<string, string> = {
20
+ export const dict_events: Record<string, string> = {
21
21
  "AF": "Ashfall",
22
22
  "AS": "Air Stagnation",
23
23
  "BH": "Beach Hazard",
@@ -17,7 +17,7 @@
17
17
 
18
18
  */
19
19
 
20
- export const regExp: { [key: string]: RegExp } = {
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 hailStrings: Record<string, string> = {
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 officeICAOs: Record<string, string> = {
20
+ export const dict_icao: Record<string, string> = {
21
21
  "KLUB": "Lubbock, TX",
22
22
  "KLCH": "Lake Charles, LA",
23
23
  "TSTL": "St. Louis, MO",
@@ -17,7 +17,7 @@
17
17
 
18
18
  */
19
19
 
20
- export const eventsMatchText: Record<string, string> = {
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",
@@ -17,7 +17,7 @@
17
17
 
18
18
  */
19
19
 
20
- export const eventProducts: Record<string, string> = {
20
+ export const dict_products: Record<string, string> = {
21
21
  "O": "Operational Product",
22
22
  "T": "Test Product",
23
23
  "E": "Experimental Product",
@@ -17,7 +17,7 @@
17
17
 
18
18
  */
19
19
 
20
- export const eventRecords: Record<string, string> = {
20
+ export const dict_records: Record<string, string> = {
21
21
  "NO": "No Record Expected",
22
22
  "NR": "Near Record or possible record",
23
23
  "UU": "Unknown history of records",
@@ -17,7 +17,7 @@
17
17
 
18
18
  */
19
19
 
20
- export const eventSeverity: Record<string, string> = {
20
+ export const dict_severity: Record<string, string> = {
21
21
  N: "Not Expected",
22
22
  0: "Areal Flood or FF Product",
23
23
  1: "Minor",
@@ -23,7 +23,7 @@ interface ShapefilesResponse {
23
23
  link: string
24
24
  }
25
25
 
26
- export const shapefileLinks: ShapefilesResponse[] = [
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 eventStatus: Record<string, string> = {
20
+ export const dict_status: Record<string, string> = {
21
21
  "NEW": "Issued",
22
22
  "CON": "Updated",
23
23
  "EXT": "Extended",
@@ -17,7 +17,7 @@
17
17
 
18
18
  */
19
19
 
20
- export const eventTags: Record<string, string> = {
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 testSignatures: string[] = [
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 { getEventOffice } from "../@building/building.office";
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 { officeICAOs } from "../@dictionaries/dictionaries.officeICAOs";
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(`.`)[2] : null,
53
- name: officeICAOs[pVtec ? pVtec.split(`.`)[2] : null] ?? null,
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
- spc_parameters: {
75
- spc_number: getTextFromProduct({ message: feature?.properties?.description, find: [`Mesoscale Discussion `], removal: [`Mesoscale Discussion`, `Number`, `...`] }) ?? null,
76
- spc_concerning: getTextFromProduct({ message: feature?.properties?.description, find: [`Concerning...`] }) ?? null,
77
- spc_max_tornado: getTextFromProduct({ message: feature?.properties?.description, find: [`MOST PROBABLE PEAK TORNADO INTENSITY...`] }) ?? null,
78
- spc_max_hail: getTextFromProduct({ message: feature?.properties?.description, find: [`MOST PROBABLE PEAK HAIL SIZE...`] }) ?? null,
79
- spc_max_wind: getTextFromProduct({ message: feature?.properties?.description, find: [`MOST PROBABLE PEAK WIND GUST...`] }) ?? null,
80
- spc_watch_issuance: getTextFromProduct({ message: feature?.properties?.description, find: [`Probability of Watch Issuance...`], removal: [`percent`]}) ?? null,
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 { eventsMatchText } from "../@dictionaries/dictionaries.eventsMatchText";
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(eventsMatchText).find(event => message.toLowerCase().includes(event.toLowerCase()));
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 { eventsMatchText } from "../@dictionaries/dictionaries.eventsMatchText";
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(eventsMatchText).find(event => message.toLowerCase().includes(event.toLowerCase()));
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 { updateWebhooks } from "./manager.updateWebhooks";
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
- updateWebhooks(bootstrap.cache.events.features[getIndex])
82
+ updateListener(bootstrap.cache.events.features[getIndex])
83
83
  } else {
84
84
  features.push(event)
85
- updateWebhooks(event)
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 { updateWebhooks } from "./manager.updateWebhooks";
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`) updateWebhooks(event)
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 { shapefileLinks } from '../../@dictionaries/dictionaries.shapefileLinks'
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 shapefileLinks) {
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
- const command =
29
- `Add-Type -AssemblyName System.Speech;` +
30
- `$speak = New-Object System.Speech.Synthesis.SpeechSynthesizer;` +
31
- `$speak.SetOutputToWaveFile('${outputPath}');` +
32
- `$speak.Speak('${text}');` +
33
- `$speak.Dispose();`;
34
- execSync(`powershell -Command "${command}"`, { stdio: 'ignore' });
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
- execSync(`espeak -w "${outputPath}" "${text.replace(/"/g, '\\"')}"`);
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.EASSettings.ArchiveDirectory;
46
- const prefix = settings.GlobalSettings.EASSettings.IntroWavFile;
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, `/temp`))) {
64
- mkdirSync(join(directory, `/temp`), { recursive: true });
65
+ if (!existsSync(join(directory, `/tts`))) {
66
+ mkdirSync(join(directory, `/tts`), { recursive: true });
65
67
  }
66
- if (!existsSync(join(directory, `/output`))) {
67
- mkdirSync(join(directory, `/output`), { recursive: true });
68
+ if (!existsSync(join(directory, `/encoded`))) {
69
+ mkdirSync(join(directory, `/encoded`), { recursive: true });
68
70
  }
69
71
 
70
- const tmpTTS = join(directory, `/temp/${Math.random().toString(36).substring(2, 15)}-${header.replace(/[^a-zA-Z0-9]/g, '')}.wav`)
71
- const outTTS = join(directory, `/output/${Math.random().toString(36).substring(2, 15)}-${header.replace(/[^a-zA-Z0-9]/g, '')}.wav`)
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, `/temp/${Math.random().toString(36).substring(2, 15)}.converted.wav`)
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 { eventAwipAbreviations } from "../../@dictionaries/dictionaries.eventAwipAbreviations"
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(eventAwipAbreviations)) {
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 { regExp } from "../../@dictionaries/dictionaries.regExp"
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(regExp.pvtec) != null;
39
- const isUGC = message.match(regExp.ugc1) != null;
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.EASSettings.ArchiveTTL
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.EASSettings.ArchiveDirectory)
53
+ walk(settings.GlobalSettings.ArchiveSettings.TextDirectory)
54
+ walk(settings.GlobalSettings.ArchiveSettings.EasDirectory)
53
55
 
54
56
 
55
57