@atmosx/event-product-parser 3.0.42 → 3.0.44

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 (60) hide show
  1. package/README.md +13 -0
  2. package/dist/cjs/index.cjs +1795 -1666
  3. package/dist/esm/index.mjs +1770 -1641
  4. package/dist/index.d.mts +6 -0
  5. package/dist/index.d.ts +6 -0
  6. package/package.json +1 -1
  7. package/src/@building/building.create.ts +2 -2
  8. package/src/@building/building.enhance.ts +1 -1
  9. package/src/@building/building.geometry.ts +1 -1
  10. package/src/@building/building.headers.ts +1 -1
  11. package/src/@building/building.office.ts +2 -2
  12. package/src/@building/building.outlooks.ts +36 -0
  13. package/src/@building/building.properties.ts +6 -4
  14. package/src/@building/building.tracking.ts +3 -3
  15. package/src/@building/building.validate.ts +4 -2
  16. package/src/@core/core.query.ts +1 -1
  17. package/src/@core/core.start.ts +1 -1
  18. package/src/@events/events.api.ts +4 -1
  19. package/src/@events/events.text.ts +3 -2
  20. package/src/@events/events.ugc.ts +3 -2
  21. package/src/@events/events.vtec.ts +5 -4
  22. package/src/@manager/manager.createWebhook.ts +93 -81
  23. package/src/@manager/manager.mkEvent.ts +1 -1
  24. package/src/@manager/manager.setHash.ts +1 -1
  25. package/src/@manager/manager.updateWebhooks.ts +1 -1
  26. package/src/@modules/@database/database.cache.ts +1 -1
  27. package/src/@modules/@database/database.init.ts +1 -1
  28. package/src/@modules/@database/database.shapefiles.ts +1 -1
  29. package/src/@modules/@database/database.stanza.ts +2 -2
  30. package/src/@modules/@eas/eas.getCleanDescription.ts +89 -0
  31. package/src/@modules/@eas/eas.getTTS.ts +43 -0
  32. package/src/@modules/@eas/eas.setEasTone.ts +29 -24
  33. package/src/@modules/@stanza/stanza.getAwipsType.ts +1 -1
  34. package/src/@modules/@stanza/stanza.validate.ts +2 -2
  35. package/src/@modules/@utilities/utilities.getLatestIssuance.ts +32 -0
  36. package/src/@modules/@utilities/utilities.getSettings.ts +1 -1
  37. package/src/@modules/@utilities/utilities.isImageReady.ts +27 -0
  38. package/src/@modules/@utilities/utilities.setCronSchedule.ts +28 -2
  39. package/src/@modules/@utilities/utilities.setSettings.ts +1 -1
  40. package/src/@modules/@utilities/utilities.setWarning.ts +1 -1
  41. package/src/@modules/@xmpp/xmpp.xDeploy.ts +1 -1
  42. package/src/@modules/@xmpp/xmpp.xReconnect.ts +1 -1
  43. package/src/@modules/@xmpp/xmpp.xStanza.ts +1 -1
  44. package/src/@parsers/@hvtec/hvtec.extract.ts +1 -1
  45. package/src/@parsers/@pvtec/pvtec.extract.ts +1 -1
  46. package/src/@parsers/@ugc/ugc.extract.ts +1 -1
  47. package/src/@types/{types.compiled.ts → type.compiled.ts} +1 -1
  48. package/src/@types/type.event.ts +4 -2
  49. package/src/@types/type.properties.ts +3 -1
  50. package/src/@types/{types.settings.ts → type.settings.ts} +3 -2
  51. package/src/@types/{types.webhook.ts → type.webhook.ts} +1 -0
  52. package/src/bootstrap.ts +2 -1
  53. package/src/index.ts +1 -1
  54. package/src/@dictionaries/dictionaries.transcribedMessageReplacements.ts +0 -68
  55. /package/src/@types/{types.attributes.ts → type.attributes.ts} +0 -0
  56. /package/src/@types/{types.hash.ts → type.hash.ts} +0 -0
  57. /package/src/@types/{types.hvtec.ts → type.hvtec.ts} +0 -0
  58. /package/src/@types/{types.pvtec.ts → type.pvtec.ts} +0 -0
  59. /package/src/@types/{types.stanza.ts → type.stanza.ts} +0 -0
  60. /package/src/@types/{types.ugc.ts → type.ugc.ts} +0 -0
package/dist/index.d.mts CHANGED
@@ -3,6 +3,7 @@ type TypeWebhook = {
3
3
  title: string;
4
4
  message: string;
5
5
  upload: boolean;
6
+ eas: boolean;
6
7
  rate: number;
7
8
  events: string[];
8
9
  };
@@ -57,6 +58,7 @@ type TypeSettings = {
57
58
  IgnoreTestProducts: boolean;
58
59
  };
59
60
  EASSettings: {
61
+ ArchiveTTL: number;
60
62
  ArchiveDirectory: string;
61
63
  IntroWavFile: string;
62
64
  };
@@ -122,6 +124,8 @@ type TypeEventProperties = {
122
124
  pds_watch: boolean;
123
125
  };
124
126
  spc_parameters: {
127
+ spc_number: string;
128
+ spc_concerning: string;
125
129
  spc_max_tornado: string;
126
130
  spc_max_hail: string;
127
131
  spc_max_wind: string;
@@ -190,6 +194,8 @@ type TypeEvent = {
190
194
  issued: string;
191
195
  status: string;
192
196
  }[];
197
+ attachments?: string[];
198
+ raw: string;
193
199
  };
194
200
  } & TypeEventProperties;
195
201
  };
package/dist/index.d.ts CHANGED
@@ -3,6 +3,7 @@ type TypeWebhook = {
3
3
  title: string;
4
4
  message: string;
5
5
  upload: boolean;
6
+ eas: boolean;
6
7
  rate: number;
7
8
  events: string[];
8
9
  };
@@ -57,6 +58,7 @@ type TypeSettings = {
57
58
  IgnoreTestProducts: boolean;
58
59
  };
59
60
  EASSettings: {
61
+ ArchiveTTL: number;
60
62
  ArchiveDirectory: string;
61
63
  IntroWavFile: string;
62
64
  };
@@ -122,6 +124,8 @@ type TypeEventProperties = {
122
124
  pds_watch: boolean;
123
125
  };
124
126
  spc_parameters: {
127
+ spc_number: string;
128
+ spc_concerning: string;
125
129
  spc_max_tornado: string;
126
130
  spc_max_hail: string;
127
131
  spc_max_wind: string;
@@ -190,6 +194,8 @@ type TypeEvent = {
190
194
  issued: string;
191
195
  status: string;
192
196
  }[];
197
+ attachments?: string[];
198
+ raw: string;
193
199
  };
194
200
  } & TypeEventProperties;
195
201
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atmosx/event-product-parser",
3
- "version": "3.0.42",
3
+ "version": "3.0.44",
4
4
  "description": "NOAA Weather Wire & NWS API Parser - Built for standalone and Project AtmosphericX Integration.",
5
5
  "main": "dist/cjs/index.cjs",
6
6
  "module": "dist/esm/index.mjs",
@@ -17,8 +17,8 @@
17
17
 
18
18
  */
19
19
 
20
- import { TypeStanzaCompiled } from "../@types/types.compiled"
21
- import { TypeSettings } from "../@types/types.settings"
20
+ import { TypeStanzaCompiled } from "../@types/type.compiled"
21
+ import { TypeSettings } from "../@types/type.settings"
22
22
  import { bootstrap } from "../bootstrap"
23
23
  import { text } from "../@events/events.text"
24
24
  import { ugc } from "../@events/events.ugc"
@@ -18,7 +18,7 @@
18
18
  */
19
19
 
20
20
  import { TypeEvent } from "../@types/type.event";
21
- import { TypeSettings } from "../@types/types.settings";
21
+ import { TypeSettings } from "../@types/type.settings";
22
22
  import { bootstrap } from "../bootstrap"
23
23
  import { betterEventNames } from "../@dictionaries/dictionaries.betterEventNames"
24
24
 
@@ -18,7 +18,7 @@
18
18
  */
19
19
 
20
20
  import { getSettings } from "../@modules/@utilities/utilities.getSettings";
21
- import { TypeSettings } from "../@types/types.settings"
21
+ import { TypeSettings } from "../@types/type.settings"
22
22
  import { TypeEvent } from "../@types/type.event"
23
23
  import { getZonePolygon } from "../@parsers/@ugc/ugc.coordinates";
24
24
 
@@ -17,7 +17,7 @@
17
17
 
18
18
  */
19
19
 
20
- import { TypePVTEC } from "../@types/types.pvtec";
20
+ import { TypePVTEC } from "../@types/type.pvtec";
21
21
  import { TypeEventProperties } from "../@types/type.properties";
22
22
 
23
23
  interface GetHeaderOptions {
@@ -17,8 +17,8 @@
17
17
 
18
18
  */
19
19
 
20
- import { TypeAttributes } from "../@types/types.attributes";
21
- import { TypePVTEC } from "../@types/types.pvtec";
20
+ import { TypeAttributes } from "../@types/type.attributes";
21
+ import { TypePVTEC } from "../@types/type.pvtec";
22
22
  import { officeICAOs } from "../@dictionaries/dictionaries.officeICAOs";
23
23
 
24
24
  interface GetOfficeOptions {
@@ -0,0 +1,36 @@
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
+ import { TypeEvent } from "../@types/type.event"
21
+ import { getLatestIssuance } from "../@modules/@utilities/utilities.getLatestIssuance";
22
+
23
+ export const getEventAttachments = (event: TypeEvent): string[] | null => {
24
+ const latestTime = getLatestIssuance();
25
+ const validEvents = [
26
+ { target: "Day 1", attachment: `https://www.spc.noaa.gov/products/outlook/day1otlk_${latestTime}.png` },
27
+ { target: "Day 2", attachment: `https://www.spc.noaa.gov/products/outlook/day2otlk.png` },
28
+ { target: "Day 3", attachment: `https://www.spc.noaa.gov/products/outlook/day3otlk.png` },
29
+ { target: "Mesoscale Discussion", attachment: `https://www.spc.noaa.gov/products/md/mcd${event?.properties?.spc_parameters?.spc_number}.png` },
30
+ ]
31
+ const isValid = validEvents.find(outlook => outlook.target == event.properties.event);
32
+ if (isValid) {
33
+ return isValid?.attachment ? [isValid.attachment] : [];
34
+ }
35
+ return null;
36
+ }
@@ -18,10 +18,10 @@
18
18
  */
19
19
 
20
20
  import { TypeEventProperties } from "../@types/type.properties";
21
- import { TypeAttributes } from "../@types/types.attributes";
22
- import { TypeUGC } from "../@types/types.ugc";
23
- import { TypePVTEC } from "../@types/types.pvtec";
24
- import { TypeHVTEC } from "../@types/types.hvtec";
21
+ import { TypeAttributes } from "../@types/type.attributes";
22
+ import { TypeUGC } from "../@types/type.ugc";
23
+ import { TypePVTEC } from "../@types/type.pvtec";
24
+ import { TypeHVTEC } from "../@types/type.hvtec";
25
25
  import { regExp } from "../@dictionaries/dictionaries.regExp";
26
26
  import { getDescriptionFromProduct } from "../@parsers/@text/text.getDescriptionFromProduct";
27
27
  import { getPolygonFromProduct } from "../@parsers/@text/text.getPolygonFromProduct";
@@ -66,6 +66,8 @@ export const properties = (options: GetPropertiesOptions): TypeEventProperties =
66
66
  hail_threat: getTextFromProduct({ message: options.message, find: [`HAIL THREAT...`], removal: []}) ?? null,
67
67
  },
68
68
  spc_parameters: {
69
+ spc_number: getTextFromProduct({ message: options.message, find: [`Mesoscale Discussion `], removal: [`Mesoscale Discussion`, `Number`, `...`] }) ?? null,
70
+ spc_concerning: getTextFromProduct({ message: options.message, find: [`Concerning...`] }) ?? null,
69
71
  spc_max_tornado: getTextFromProduct({ message: options.message, find: [`MOST PROBABLE PEAK TORNADO INTENSITY...`] }) ?? null,
70
72
  spc_max_hail: getTextFromProduct({ message: options.message, find: [`MOST PROBABLE PEAK HAIL SIZE...`] }) ?? null,
71
73
  spc_max_wind: getTextFromProduct({ message: options.message, find: [`MOST PROBABLE PEAK WIND GUST...`] }) ?? null,
@@ -17,10 +17,10 @@
17
17
 
18
18
  */
19
19
 
20
- import { TypeAttributes } from "../@types/types.attributes";
20
+ import { TypeAttributes } from "../@types/type.attributes";
21
21
  import { TypeEventProperties } from "../@types/type.properties";
22
- import { TypeStanzaCompiled } from "../@types/types.compiled";
23
- import { TypePVTEC } from "../@types/types.pvtec";
22
+ import { TypeStanzaCompiled } from "../@types/type.compiled";
23
+ import { TypePVTEC } from "../@types/type.pvtec";
24
24
 
25
25
  interface GetTrackingOptions {
26
26
  type: `RAW` | `VTEC` | `API`
@@ -19,13 +19,14 @@
19
19
 
20
20
  import { createHash } from "crypto"
21
21
  import { TypeEvent } from "../@types/type.event";
22
- import { TypeSettings } from "../@types/types.settings";
22
+ import { TypeSettings } from "../@types/type.settings";
23
23
  import { bootstrap } from "../bootstrap"
24
24
  import { getEventEnhancedName } from "./building.enhance";
25
25
  import { getEventSignature } from "./building.signature"
26
26
  import { mkEvent } from "../@manager/manager.mkEvent";
27
27
  import { rmEvent } from "../@manager/manager.rmEvent";
28
28
  import { getEventGeometry } from "./building.geometry";
29
+ import { getEventAttachments } from "./building.outlooks";
29
30
  import { updateNode } from "../@manager/manager.updateNodes";
30
31
  import { setEventEmit } from "../@modules/@utilities/utilities.setEventEmit";
31
32
 
@@ -51,7 +52,8 @@ export const validateEvents = async (events: TypeEvent[]): Promise<void> => {
51
52
  delete filteredProperties.metadata.ms;
52
53
  }
53
54
  filteredProperties.metadata = filteredProperties.metadata ?? {} as any;
54
- properties.metadata.hash = createHash("sha256").update(JSON.stringify(filteredProperties)).digest("hex")
55
+ properties.metadata.hash = createHash("sha256").update(JSON.stringify(filteredProperties)).digest("hex")
56
+ properties.metadata.attachments = getEventAttachments(event)
55
57
  setEventEmit({ event: `onProductType${enhancedEventName.replace(/\s+/g, '')}`, metadata: define });
56
58
 
57
59
  if (properties.status_metadata.is_test) {
@@ -17,7 +17,7 @@
17
17
 
18
18
  */
19
19
 
20
- import { TypeStanza } from "../@types/types.stanza";
20
+ import { TypeStanza } from "../@types/type.stanza";
21
21
  import { bootstrap } from "../bootstrap";
22
22
 
23
23
  interface QueryOptions {
@@ -17,7 +17,7 @@
17
17
 
18
18
  */
19
19
 
20
- import { TypeSettings } from "../@types/types.settings";
20
+ import { TypeSettings } from "../@types/type.settings";
21
21
  import { bootstrap } from "../bootstrap";
22
22
  import { setWarning } from "../@modules/@utilities/utilities.setWarning"
23
23
  import { setSettings } from "../@modules/@utilities/utilities.setSettings"
@@ -17,7 +17,7 @@
17
17
 
18
18
  */
19
19
 
20
- import { TypeStanzaCompiled } from "../@types/types.compiled"
20
+ 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";
@@ -72,6 +72,8 @@ export const api = async (stanza: TypeStanzaCompiled): Promise<void> => {
72
72
  hail_threat: feature?.properties?.parameters?.hailThreat?.[0] ?? null,
73
73
  },
74
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,
75
77
  spc_max_tornado: getTextFromProduct({ message: feature?.properties?.description, find: [`MOST PROBABLE PEAK TORNADO INTENSITY...`] }) ?? null,
76
78
  spc_max_hail: getTextFromProduct({ message: feature?.properties?.description, find: [`MOST PROBABLE PEAK HAIL SIZE...`] }) ?? null,
77
79
  spc_max_wind: getTextFromProduct({ message: feature?.properties?.description, find: [`MOST PROBABLE PEAK WIND GUST...`] }) ?? null,
@@ -98,6 +100,7 @@ export const api = async (stanza: TypeStanzaCompiled): Promise<void> => {
98
100
  header: `ZCZC-ATMOSX-${feature?.properties?.parameters?.WMOidentifier}`,
99
101
  vtec: pVtec,
100
102
  hvtec: null,
103
+ raw: feature?.properties?.description,
101
104
  history: [
102
105
  {
103
106
  description: feature?.properties?.description,
@@ -17,8 +17,8 @@
17
17
 
18
18
  */
19
19
 
20
- import { TypeAttributes } from "../@types/types.attributes";
21
- import { TypeStanzaCompiled } from "../@types/types.compiled"
20
+ import { TypeAttributes } from "../@types/type.attributes";
21
+ 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";
@@ -66,6 +66,7 @@ export const text = async (stanza: TypeStanzaCompiled): Promise<void> => {
66
66
  header: header,
67
67
  vtec: null,
68
68
  hvtec: null,
69
+ raw: message,
69
70
  history: [
70
71
  {
71
72
  description: props.description,
@@ -17,8 +17,8 @@
17
17
 
18
18
  */
19
19
 
20
- import { TypeAttributes } from "../@types/types.attributes";
21
- import { TypeStanzaCompiled } from "../@types/types.compiled"
20
+ import { TypeAttributes } from "../@types/type.attributes";
21
+ import { TypeStanzaCompiled } from "../@types/type.compiled"
22
22
  import { TypeEvent } from "../@types/type.event";
23
23
  import { eventsMatchText } from "../@dictionaries/dictionaries.eventsMatchText";
24
24
  import { ugcExtract } from "../@parsers/@ugc/ugc.extract";
@@ -69,6 +69,7 @@ export const ugc = async (stanza: TypeStanzaCompiled): Promise<void> => {
69
69
  header: header,
70
70
  vtec: null,
71
71
  hvtec: null,
72
+ raw: message,
72
73
  history: [
73
74
  {
74
75
  description: props.description,
@@ -17,11 +17,11 @@
17
17
 
18
18
  */
19
19
 
20
- import { TypeAttributes } from "../@types/types.attributes";
21
- import { TypeStanzaCompiled } from "../@types/types.compiled"
20
+ import { TypeAttributes } from "../@types/type.attributes";
21
+ import { TypeStanzaCompiled } from "../@types/type.compiled"
22
22
  import { TypeEvent } from "../@types/type.event";
23
- import { TypePVTEC } from "../@types/types.pvtec";
24
- import { TypeHVTEC } from "../@types/types.hvtec";
23
+ import { TypePVTEC } from "../@types/type.pvtec";
24
+ import { TypeHVTEC } from "../@types/type.hvtec";
25
25
  import { pvExtract } from "../@parsers/@pvtec/pvtec.extract";
26
26
  import { hvExtract } from "../@parsers/@hvtec/hvtec.extract";
27
27
  import { ugcExtract } from "../@parsers/@ugc/ugc.extract";
@@ -70,6 +70,7 @@ export const vtec = async (stanza: TypeStanzaCompiled): Promise<void> => {
70
70
  header: header,
71
71
  vtec: pv,
72
72
  hvtec: hVtec,
73
+ raw: message,
73
74
  history: [
74
75
  {
75
76
  description: props.description,
@@ -20,8 +20,11 @@
20
20
  import { TypeEvent } from "../@types/type.event";
21
21
  import { setTimeoutAction } from "../@modules/@utilities/utilities.setTimeoutAction"
22
22
  import { createHttp } from "../@modules/@utilities/utilities.createHttp"
23
- import { TypeWebhook } from "../@types/types.webhook";
23
+ import { isImageReady } from "../@modules/@utilities/utilities.isImageReady"
24
+ import { TypeWebhook } from "../@types/type.webhook";
24
25
  import { getCleanedEvent } from "../@building/building.clean";
26
+ import { setEasTone } from "../@modules/@eas/eas.setEasTone";
27
+ import { readFileSync } from "fs";
25
28
  import FormData from "form-data";
26
29
 
27
30
  interface CreateWebhookOptions {
@@ -32,86 +35,95 @@ interface CreateWebhookOptions {
32
35
  export const createWebhook = async (options: CreateWebhookOptions): Promise<void> => {
33
36
  const event = options.event.properties;
34
37
  const settings = options.webhook;
35
-
36
- let body = [
37
- event.locations ? `**Locations**: ${event.locations.slice(0,100)}` : null,
38
- event.issued && event.status != `Expired` ? `**Issued**: <t:${Math.floor(new Date(event.issued).getTime()/1000)}:R>` : null,
39
- event.expires && event.status != `Statement` ? `**Expires**: <t:${Math.floor(new Date(event.expires).getTime()/1000)}:R>` : null,
40
- (() => {
41
- const val = event.parameters.estimated_wind_gusts ?? null
42
- const th = event.parameters.wind_threat ?? null
43
- const combined = [val, th].filter(Boolean).join(' ');
44
- return combined ? `**Wind Gusts**: ${val} ${th ? `(${th})` : ''}` : null;
45
- })(),
46
- (() => {
47
- const val = event.parameters.estimated_hail_size ?? null
48
- const th = event.parameters.hail_threat ?? null
49
- return (val ?? th) ? `**Hail Threat**: ${val} ${th ? `(${th})` : ''}` : null;
50
- })(),
51
- event.parameters.damage_threat ? `**Damage Threat**: ${event.parameters.damage_threat}` : null,
52
- event.parameters.flood_threat ? `**Flood Threat**: ${event.parameters.flood_threat}` : null,
53
- event.parameters.tornado_threat ? `**Tornado Threat**: ${event.parameters.tornado_threat}` : null,
54
- event.spc_parameters.spc_max_tornado ? `**Max Tornado Threat**: ${event.spc_parameters.spc_max_tornado}` : null,
55
- event.spc_parameters.spc_max_hail ? `**Max Hail Threat**: ${event.spc_parameters.spc_max_hail}` : null,
56
- event.spc_parameters.spc_max_wind ? `**Max Wind Threat**: ${event.spc_parameters.spc_max_wind}` : null,
57
- event.spc_parameters.spc_watch_issuance ? `**Watch Issuance**: ${event.spc_parameters.spc_watch_issuance}%` : null,
58
- event.watch_parameters.watch_number ? `**Watch Number**: ${event.watch_parameters.watch_number}` : null,
59
- event.watch_parameters.strong_tornadoes_probability ? `**Strong Tornadoes Probability**: ${event.watch_parameters.strong_tornadoes_probability}%` : null,
60
- event.watch_parameters.additional_tornadoes_probability ? `**Additional Tornadoes Probability**: ${event.watch_parameters.additional_tornadoes_probability}%` : null,
61
- event.watch_parameters.combined_hail_wind_probability ? `**Combined Hail/Wind Probability**: ${event.watch_parameters.combined_hail_wind_probability}%` : null,
62
- event.watch_parameters.severe_hail_probability ? `**Severe Hail Probability**: ${event.watch_parameters.severe_hail_probability}%` : null,
63
- event.watch_parameters.hail_2in_probability ? `**Hail ≥2in Probability**: ${event.watch_parameters.hail_2in_probability}%` : null,
64
- event.watch_parameters.max_hail_in ? `**Max Hail Inches**: ${event.watch_parameters.max_hail_in}` : null,
65
- event.watch_parameters.severe_wind_probability ? `**Severe Wind Probability**: ${event.watch_parameters.severe_wind_probability}%` : null,
66
- event.watch_parameters.max_wind_surface ? `**Max Surface Wind**: ${event.watch_parameters.max_wind_surface}` : null,
67
- event.watch_parameters.max_tops_x100feet ? `**Max Tops (x100 feet)**: ${event.watch_parameters.max_tops_x100feet}` : null,
68
- (event.parameters.tags?.length > 0) ? `**Tags**: ${event.parameters.tags.join(', ')}` : null,
69
- (() => {
70
- const val = event.geocode?.office?.name ?? `N/A`
71
- const th = event.geocode?.office?.office ?? null
72
- return (val ?? th) ? `**Sender**: ${val} ${th ? `(${th})` : ''}` : null;
73
- })(),
74
- event.metadata?.tracking ? `**Tracking**: ${event.metadata.tracking}` : null,
75
- event.metadata.history?.length > 0 ? `**Logs**: ${event.metadata.history.length}` : null,
76
- (() => {
77
- if (event.status == `Expired`) { return null }
78
- const desc = (event.description ?? '').split('\n').map(l => l.trim()).filter(Boolean).join('\n');
79
- return desc ? '```' + '\n' + desc + '\n' + '```' : null;
80
- })(),
81
- ].filter(Boolean).join('\n');
82
-
38
+ const line = (label: string, value: unknown, condition = true) => condition && value ? `${label} ${value}` : null;
83
39
  const isLimited = setTimeoutAction({ identifier: options.webhook.webhook, interval: options.webhook.rate, max: options.webhook.rate, addTime: true })
84
- if (isLimited.limited) { return }
85
-
86
- if (body.length > 1900) {
87
- body = body.substring(0, 1900) + "\n\n[Message truncated due to length]";
88
- const blocks = (body.match(/```/g) ?? []).length;
89
- if (blocks % 2 !== 0) body += "```";
90
- }
91
- const form = new FormData();
92
- const embed = {
93
- title: `${event.event} (${event.status})`,
94
- description: body,
95
- color: 16711680,
96
- timestamp: new Date().toISOString(),
97
- footer: { text: settings.title }
98
- };
99
- form.append("payload_json", JSON.stringify({
100
- username: settings.title ?? "AtmosphericX",
101
- content: settings.message ?? "",
102
- embeds: [embed]
103
- }));
104
- if (settings.upload) {
105
- form.append("file", Buffer.from(JSON.stringify((getCleanedEvent(event)), null, 2)), {
106
- filename: `${event.event}_${event.status}_${event.metadata.tracking}.json`,
107
- contentType: "application/json"
108
- });
40
+ if (!isLimited.limited) {
41
+ const isStatement = event.status_metadata.is_statement;
42
+ const isExpired = event.status_metadata.is_expired;
43
+ let body = [
44
+ line(`**Locations:**`, event?.locations?.slice(0, 100)),
45
+ line(`**Issued:**`, `<t:${Math.floor(new Date(event.issued).getTime() / 1000)}:R>`),
46
+ line(`**Expires:**`, `<t:${Math.floor(new Date(event.expires).getTime() / 1000)}:R>`, !isExpired && !isStatement),
47
+ line(`**Damage Threat:**`, event.parameters.damage_threat, !isExpired),
48
+ line(`**Flood Threat:**`, event.parameters.flood_threat, !isExpired),
49
+ line(`**Tornado Threat:**`, event.parameters.tornado_threat, !isExpired),
50
+ line(`**Estimated Wind Gusts:**`, `${event.parameters.estimated_wind_gusts} ${event.parameters.wind_threat ? ` (${event.parameters.wind_threat})` : ''}`, !isExpired && event.parameters.estimated_wind_gusts != null),
51
+ line(`**Estimated Hail Size:**`, `${event.parameters.estimated_hail_size} ${event.parameters.hail_threat ? ` (${event.parameters.hail_threat})` : ''}`, !isExpired && event.parameters.estimated_hail_size != null),
52
+ line(`**Discussion:**`, event.spc_parameters.spc_number, !isExpired),
53
+ line(`**Concern:**`, event.spc_parameters.spc_concerning, !isExpired),
54
+ line(`**SPC Max Tornado Threat:**`, event.spc_parameters.spc_max_tornado, !isExpired),
55
+ line(`**SPC Max Hail Threat:**`, event.spc_parameters.spc_max_hail, !isExpired),
56
+ line(`**SPC Max Wind Threat:**`, event.spc_parameters.spc_max_wind, !isExpired),
57
+ line(`**SPC Watch Issuance Probability:**`, event.spc_parameters.spc_watch_issuance ? `${event.spc_parameters.spc_watch_issuance}%` : null, !isExpired),
58
+ line(`**Watch Number:**`, event.watch_parameters.watch_number, !isExpired),
59
+ line(`**Strong Tornadoes Probability:**`, event.watch_parameters.strong_tornadoes_probability ? `${event.watch_parameters.strong_tornadoes_probability}%` : null, !isExpired),
60
+ line(`**Additional Tornadoes Probability:**`, event.watch_parameters.additional_tornadoes_probability ? `${event.watch_parameters.additional_tornadoes_probability}%` : null, !isExpired),
61
+ line(`**Combined Hail/Wind Probability:**`, event.watch_parameters.combined_hail_wind_probability ? `${event.watch_parameters.combined_hail_wind_probability}%` : null, !isExpired),
62
+ line(`**Severe Hail Probability:**`, event.watch_parameters.severe_hail_probability ? `${event.watch_parameters.severe_hail_probability}%` : null, !isExpired),
63
+ line(`**Hail >2in Probability:**`, event.watch_parameters.hail_2in_probability ? `${event.watch_parameters.hail_2in_probability}%` : null, !isExpired),
64
+ line(`**Max Hail Inches:**`, event.watch_parameters.max_hail_in, !isExpired),
65
+ line(`**Severe Wind Probability:**`, event.watch_parameters.severe_wind_probability ? `${event.watch_parameters.severe_wind_probability}%` : null, !isExpired),
66
+ line(`**Max Surface Wind:**`, event.watch_parameters.max_wind_surface, !isExpired),
67
+ line(`**Max Tops (x100 feet):**`, event.watch_parameters.max_tops_x100feet, !isExpired),
68
+ line(`**Tags:**`, event.parameters.tags?.length > 0 ? event.parameters.tags.join(', ') : null, !isExpired),
69
+ line(`**Sender:**`, event.geocode?.office?.name ? `${event.geocode.office.name} (${event.geocode.office.office})` : event.geocode?.office?.office),
70
+ line(`**Tracking:**`, event.metadata?.tracking),
71
+ line(`**Logs:**`, event.metadata.history?.length > 0 ? event.metadata.history.length : null),
72
+ line(``, event.description ? '```' + '\n' + event.description.split('\n').map(l => l.trim()).filter(Boolean).join('\n') + '\n' + '```' : null, !isExpired)
73
+ ].filter(Boolean).join('\n');
74
+
75
+ if (body.length > 1900) {
76
+ body = body.substring(0, 1900) + "\n\n[Message truncated due to length]";
77
+ const blocks = (body.match(/```/g) ?? []).length;
78
+ if (blocks % 2 !== 0) body += "```";
79
+ }
80
+ if (event.description.length < 25) { return }
81
+
82
+ const form = new FormData();
83
+ const img = event.metadata.attachments?.[0]
84
+ const embed = {
85
+ title: `${event.event} (${event.status})`,
86
+ description: body,
87
+ color: 16711680,
88
+ timestamp: new Date().toISOString(),
89
+ image: {},
90
+ footer: { text: settings.title }
91
+ };
92
+ if (img) {
93
+ for (let i = 0; i < 5; i++) {
94
+ const ok = await isImageReady(img)
95
+ if (ok) break
96
+ await new Promise(r => setTimeout(r, 5000))
97
+ }
98
+ const finalCheck = await isImageReady(img)
99
+ if (finalCheck) {
100
+ embed.image = { url: img }
101
+ }
102
+ }
103
+ form.append("payload_json", JSON.stringify({
104
+ username: settings.title ?? "AtmosphericX",
105
+ content: settings.message ?? "",
106
+ embeds: [embed]
107
+ }));
108
+ if (settings.upload) {
109
+ form.append("fUpload", Buffer.from(`${event.metadata.raw}\n\n${JSON.stringify((getCleanedEvent(event)), null, 2)}`), { filename: `${event.event}_${event.status}_${event.metadata.tracking}.txt`, contentType: "application/text" });
110
+ }
111
+ if (settings.eas) {
112
+ const audio = await setEasTone({
113
+ message: event.description,
114
+ header: event.metadata.header
115
+ });
116
+ const file = readFileSync(audio)
117
+ if (audio) {
118
+ form.append("fEas", Buffer.from(file), { filename: `${event.event}_${event.status}_${event.metadata.tracking}_eas.mp3`, contentType: "audio/mpeg" });
119
+ }
120
+ }
121
+ await createHttp({
122
+ url: settings.webhook,
123
+ timeout: 2000,
124
+ method: `POST`,
125
+ headers: form.getHeaders(),
126
+ body: form
127
+ })
109
128
  }
110
- await createHttp({
111
- url: settings.webhook,
112
- timeout: 2000,
113
- method: `POST`,
114
- headers: form.getHeaders(),
115
- body: form
116
- })
117
129
  }
@@ -23,7 +23,7 @@ import { bootstrap } from "../bootstrap"
23
23
  import { setHash } from "./manager.setHash";
24
24
  import { updateWebhooks } from "./manager.updateWebhooks";
25
25
  import { updateNode } from "./manager.updateNodes";
26
- import { TypeSettings } from "../@types/types.settings";
26
+ import { TypeSettings } from "../@types/type.settings";
27
27
 
28
28
  export const mkEvent = async (event: TypeEvent): Promise<void> => {
29
29
  const settings = bootstrap.settings as TypeSettings;
@@ -18,7 +18,7 @@
18
18
  */
19
19
 
20
20
  import { TypeEvent } from "../@types/type.event";
21
- import { TypeHash } from "../@types/types.hash"
21
+ import { TypeHash } from "../@types/type.hash"
22
22
  import { bootstrap } from "../bootstrap"
23
23
 
24
24
 
@@ -19,7 +19,7 @@
19
19
 
20
20
  import { createWebhook } from "./manager.createWebhook";
21
21
  import { TypeEvent } from "../@types/type.event";
22
- import { TypeWebhook } from "../@types/types.webhook";
22
+ import { TypeWebhook } from "../@types/type.webhook";
23
23
  import { bootstrap } from "../bootstrap"
24
24
 
25
25
  export const updateWebhooks = async (event: TypeEvent): Promise<void> => {
@@ -17,7 +17,7 @@
17
17
 
18
18
  */
19
19
 
20
- import { TypeSettings } from "../../@types/types.settings"
20
+ import { TypeSettings } from "../../@types/type.settings"
21
21
  import { bootstrap } from '../../bootstrap'
22
22
  import { setWarning } from '../@utilities/utilities.setWarning';
23
23
  import { createEvent } from '../../@building/building.create';
@@ -19,7 +19,7 @@
19
19
 
20
20
  import fs from 'fs'
21
21
  import sqlite3 from 'better-sqlite3'
22
- import { TypeSettings } from "../../@types/types.settings"
22
+ import { TypeSettings } from "../../@types/type.settings"
23
23
  import { bootstrap } from "../../bootstrap"
24
24
  import { setWarning } from '../@utilities/utilities.setWarning'
25
25
  import { importShapefiles } from './database.shapefiles'
@@ -21,7 +21,7 @@ import fs from 'fs'
21
21
  import { resolve, extname} from 'path'
22
22
  import { loadAsync } from 'jszip'
23
23
  import { read } from 'shapefile'
24
- import { TypeSettings } from "../../@types/types.settings"
24
+ 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'
@@ -20,8 +20,8 @@
20
20
 
21
21
  import { bootstrap } from '../../bootstrap'
22
22
  import { setWarning } from '../@utilities/utilities.setWarning';
23
- import { TypeSettings } from "../../@types/types.settings"
24
- import { TypeStanzaCompiled } from '../../@types/types.compiled';
23
+ import { TypeSettings } from "../../@types/type.settings"
24
+ import { TypeStanzaCompiled } from '../../@types/type.compiled';
25
25
 
26
26
  export const importStanza = async (stanza: TypeStanzaCompiled): Promise<void> => {
27
27
  const settings = bootstrap.settings as TypeSettings;