@atmosx/event-product-parser 3.0.41 → 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 (61) hide show
  1. package/README.md +22 -0
  2. package/dist/cjs/index.cjs +1791 -1646
  3. package/dist/esm/index.mjs +1026 -882
  4. package/dist/index.d.mts +34 -3
  5. package/dist/index.d.ts +34 -3
  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 +3 -3
  12. package/src/@building/building.outlooks.ts +36 -0
  13. package/src/@building/building.properties.ts +11 -5
  14. package/src/@building/building.tracking.ts +7 -7
  15. package/src/@building/building.validate.ts +4 -2
  16. package/src/@core/core.query.ts +39 -0
  17. package/src/@core/core.setNode.ts +2 -2
  18. package/src/@core/core.start.ts +1 -1
  19. package/src/@events/events.api.ts +4 -1
  20. package/src/@events/events.text.ts +3 -2
  21. package/src/@events/events.ugc.ts +3 -2
  22. package/src/@events/events.vtec.ts +5 -4
  23. package/src/@manager/manager.createWebhook.ts +93 -81
  24. package/src/@manager/manager.mkEvent.ts +3 -2
  25. package/src/@manager/manager.setHash.ts +1 -1
  26. package/src/@manager/manager.updateWebhooks.ts +1 -2
  27. package/src/@modules/@database/database.cache.ts +1 -1
  28. package/src/@modules/@database/database.init.ts +1 -1
  29. package/src/@modules/@database/database.shapefiles.ts +1 -1
  30. package/src/@modules/@database/database.stanza.ts +2 -2
  31. package/src/@modules/@eas/eas.getCleanDescription.ts +89 -0
  32. package/src/@modules/@eas/eas.getTTS.ts +43 -0
  33. package/src/@modules/@eas/eas.setEasTone.ts +29 -24
  34. package/src/@modules/@stanza/stanza.getAwipsType.ts +1 -1
  35. package/src/@modules/@stanza/stanza.validate.ts +2 -2
  36. package/src/@modules/@utilities/utilities.getLatestIssuance.ts +32 -0
  37. package/src/@modules/@utilities/utilities.getSettings.ts +1 -1
  38. package/src/@modules/@utilities/utilities.isImageReady.ts +27 -0
  39. package/src/@modules/@utilities/utilities.setCronSchedule.ts +28 -2
  40. package/src/@modules/@utilities/utilities.setSettings.ts +1 -1
  41. package/src/@modules/@utilities/utilities.setWarning.ts +1 -1
  42. package/src/@modules/@xmpp/xmpp.xDeploy.ts +1 -1
  43. package/src/@modules/@xmpp/xmpp.xReconnect.ts +1 -1
  44. package/src/@modules/@xmpp/xmpp.xStanza.ts +1 -1
  45. package/src/@parsers/@hvtec/hvtec.extract.ts +1 -1
  46. package/src/@parsers/@pvtec/pvtec.extract.ts +1 -1
  47. package/src/@parsers/@ugc/ugc.extract.ts +1 -1
  48. package/src/@types/{types.compiled.ts → type.compiled.ts} +1 -1
  49. package/src/@types/type.event.ts +4 -2
  50. package/src/@types/type.properties.ts +3 -1
  51. package/src/@types/{types.settings.ts → type.settings.ts} +3 -2
  52. package/src/@types/{types.webhook.ts → type.webhook.ts} +1 -0
  53. package/src/bootstrap.ts +2 -1
  54. package/src/index.ts +3 -2
  55. package/src/@dictionaries/dictionaries.transcribedMessageReplacements.ts +0 -68
  56. /package/src/@types/{types.attributes.ts → type.attributes.ts} +0 -0
  57. /package/src/@types/{types.hash.ts → type.hash.ts} +0 -0
  58. /package/src/@types/{types.hvtec.ts → type.hvtec.ts} +0 -0
  59. /package/src/@types/{types.pvtec.ts → type.pvtec.ts} +0 -0
  60. /package/src/@types/{types.stanza.ts → type.stanza.ts} +0 -0
  61. /package/src/@types/{types.ugc.ts → type.ugc.ts} +0 -0
@@ -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 ? `**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 ? `**Updates**: ${event.metadata.history.length}` : null,
76
- (() => {
77
- if (event.status == `expires`) { 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;
@@ -36,6 +36,7 @@ export const mkEvent = async (event: TypeEvent): Promise<void> => {
36
36
  const getFeature = features.find(feature => feature.properties.metadata.tracking === getTracking);
37
37
  if (isHashed || event.properties.status_metadata.is_expired) return
38
38
  setHash(event, isEntry)
39
+
39
40
  const isFilteredLocation = await updateNode(event).then(() => event.properties.metadata.filtered_proximity);
40
41
  if (!isFilteredLocation && settings.GlobalSettings.EventFiltering.NodeLocationFiltering) { return }
41
42
 
@@ -59,7 +60,7 @@ export const mkEvent = async (event: TypeEvent): Promise<void> => {
59
60
  const iLocations = event.properties?.locations?.split(";").map((l: string) => l.trim()) ?? [];
60
61
  const iUgc = event.properties?.geocode?.ugc ?? [];
61
62
 
62
- const mHistory = [...cHistory, ...iHistory].filter((v, i, a) => a.indexOf(v) === i);
63
+ const mHistory = [...cHistory, ...iHistory].filter((v, i, a) => a.indexOf(v) === i).filter((v, i, a) => a.findIndex(h => h.description === v.description && h.issued === v.issued) === i);
63
64
  const mLocations = [...cLocations, ...iLocations].filter((v, i, a) => a.indexOf(v) === i).join('; ');
64
65
  const mUgc = [...cUgc, ...iUgc].filter((v, i, a) => a.indexOf(v) === i);
65
66
 
@@ -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,10 +19,9 @@
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
-
26
25
  export const updateWebhooks = async (event: TypeEvent): Promise<void> => {
27
26
  const settings = bootstrap.settings;
28
27
  const webhooks = settings.WebhookSettings as TypeWebhook[];
@@ -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;
@@ -0,0 +1,89 @@
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
+ export const getCleanDescription = (message: string): string => {
22
+ const patches: { regex: RegExp, replacement: string }[] = [
23
+ { regex: /\.{3,}/g, replacement: ". " },
24
+ { regex: /\bUTC\b/g, replacement: 'Coordinated Universal Time' },
25
+ { regex: /\bGMT\b/g, replacement: 'Greenwich Mean Time' },
26
+ { regex: /\bEST\b(?!\w)/g, replacement: 'Eastern Standard Time' },
27
+ { regex: /\bEDT\b(?!\w)/g, replacement: 'Eastern Daylight Time' },
28
+ { regex: /\bCST\b(?!\w)/g, replacement: 'Central Standard Time' },
29
+ { regex: /\bCDT\b(?!\w)/g, replacement: 'Central Daylight Time' },
30
+ { regex: /\bMST\b(?!\w)/g, replacement: 'Mountain Standard Time' },
31
+ { regex: /\bMDT\b(?!\w)/g, replacement: 'Mountain Daylight Time' },
32
+ { regex: /\bPST\b(?!\w)/g, replacement: 'Pacific Standard Time' },
33
+ { regex: /\bPDT\b(?!\w)/g, replacement: 'Pacific Daylight Time' },
34
+ { regex: /\bAKST\b(?!\w)/g, replacement: 'Alaska Standard Time' },
35
+ { regex: /\bAKDT\b(?!\w)/g, replacement: 'Alaska Daylight Time' },
36
+ { regex: /\bHST\b(?!\w)/g, replacement: 'Hawaii Standard Time' },
37
+ { regex: /\bHDT\b(?!\w)/g, replacement: 'Hawaii Daylight Time' },
38
+ { regex: /\bmph\b(?!\w)/g, replacement: 'miles per hour' },
39
+ { regex: /\bkm\/h\b(?!\w)/g, replacement: 'kilometers per hour' },
40
+ { regex: /\bkmh\b(?!\w)/g, replacement: 'kilometers per hour' },
41
+ { regex: /\bkt\b(?!\w)/g, replacement: 'knots' },
42
+ { regex: /\bNE\b(?!\w)/g, replacement: 'northeast' },
43
+ { regex: /\bNW\b(?!\w)/g, replacement: 'northwest' },
44
+ { regex: /\bSE\b(?!\w)/g, replacement: 'southeast' },
45
+ { regex: /\bSW\b(?!\w)/g, replacement: 'southwest' },
46
+ { regex: /\bNM\b(?!\w)/g, replacement: 'nautical miles' },
47
+ { regex: /\bdeg\b(?!\w)/g, replacement: 'degrees' },
48
+ { regex: /\btstm\b(?!\w)/g, replacement: 'thunderstorm' },
49
+ { regex: /\bmm\b(?!\w)/g, replacement: 'millimeters' },
50
+ { regex: /\bcm\b(?!\w)/g, replacement: 'centimeters' },
51
+ { regex: /\bin.\b(?!\w)/g, replacement: 'inches' },
52
+ { regex: /\bft\b(?!\w)/g, replacement: 'feet' },
53
+ { regex: /\bmi\b(?!\w)/g, replacement: 'miles' },
54
+ { regex: /\bhr\b(?!\w)/g, replacement: 'hour' },
55
+ { regex: /\bhourly\b(?!\w)/g, replacement: 'per hour' },
56
+ { regex: /\bkg\b(?!\w)/g, replacement: 'kilograms' },
57
+ { regex: /\bg\/kg\b(?!\w)/g, replacement: 'grams per kilogram' },
58
+ { regex: /\bmb\b(?!\w)/g, replacement: 'millibars' },
59
+ { regex: /\bhPa\b(?!\w)/g, replacement: 'hectopascals' },
60
+ { regex: /\bPa\b(?!\w)/g, replacement: 'pascals' },
61
+ { regex: /\bKPa\b(?!\w)/g, replacement: 'kilopascals' },
62
+ { regex: /\bC\/hr\b(?!\w)/g, replacement: 'degrees Celsius per hour' },
63
+ { regex: /\bF\/hr\b(?!\w)/g, replacement: 'degrees Fahrenheit per hour' },
64
+ { regex: /\bC\/min\b(?!\w)/g, replacement: 'degrees Celsius per minute' },
65
+ { regex: /\bF\/min\b(?!\w)/g, replacement: 'degrees Fahrenheit per minute' },
66
+ { regex: /\bC\b(?!\w)/g, replacement: 'degrees Celsius' },
67
+ { regex: /\bF\b(?!\w)/g, replacement: 'degrees Fahrenheit' },
68
+ ]
69
+ const hours: Record<number, string> = {
70
+ 0: "twelve", 1: "one", 2: "two", 3: "three", 4: "four",
71
+ 5: "five", 6: "six", 7: "seven", 8: "eight", 9: "nine",
72
+ 10: "ten", 11: "eleven", 12: "twelve",
73
+ };
74
+ const minWord = (m: number) => m === 0 ? "o'clock" : m < 10 ? `oh ${m}` : `${m}`;
75
+ const fmt = (h: number, m: number, ap: string) => `${hours[h % 12]} ${minWord(m)} ${ap}`;
76
+ for (const {regex, replacement} of patches) {
77
+ message = message.replace(regex, replacement)
78
+ }
79
+ return message
80
+ .replace(/\b(\d{1,2}):(\d{2})\s*(AM|PM)\b/gi, (_, h, m, ap) => fmt(+h, +m, ap.toUpperCase()))
81
+ .replace(/\b(\d{1,2})\s+(\d{2})\s+(AM|PM)\b/gi, (_, h, m, ap) => fmt(+h, +m, ap.toUpperCase()))
82
+ .replace(/\b(\d{2})(\d{2})\s+(AM|PM)\b/gi, (_, hh, mm, ap) => fmt(+hh, +mm, ap.toUpperCase()))
83
+ .replace(/\*+/g, " ")
84
+ .replace(/-/g, " ")
85
+ .replace(/\n/g, " ")
86
+ .trim();
87
+ }
88
+
89
+
@@ -0,0 +1,43 @@
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 { platform } from 'os'
22
+ import { execSync } from 'child_process'
23
+
24
+ export const getTTS = async (text: string, outputPath: string): Promise<void> => {
25
+ const vPlatform = platform();
26
+ switch (vPlatform) {
27
+ 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' });
35
+ break;
36
+ }
37
+ case 'linux':
38
+ execSync(`espeak -w "${outputPath}" "${text.replace(/"/g, '\\"')}"`);
39
+ break;
40
+ default: break;
41
+ }
42
+ };
43
+
@@ -17,10 +17,9 @@
17
17
 
18
18
  */
19
19
 
20
- import { TypeSettings } from "../../@types/types.settings";
21
- import { transcribedMessageReplacements } from "../../@dictionaries/dictionaries.transcribedMessageReplacements";
20
+ import { TypeSettings } from "../../@types/type.settings";
22
21
  import { bootstrap } from "../../bootstrap";
23
- import { setSleep } from "../@utilities/utilities.setSleep"
22
+ import { setWarning } from "../@utilities/utilities.setWarning";
24
23
  import { getWavPCM16 } from "./eas.getWavPCM16";
25
24
  import { getSampledPCM16 } from "./eas.getSampledPCM16";
26
25
  import { setRadioEffect } from "./eas.setRadioEffect";
@@ -29,12 +28,12 @@ import { setAttentionTone } from "./eas.setAttentionTone";
29
28
  import { getMergedPCM16 } from "./eas.getMergedPCM16";
30
29
  import { setNoise } from "./eas.setNoise";
31
30
  import { getPCM16 } from "./eas.getPCM16";
31
+ import { getTTS } from "./eas.getTTS";
32
+ import { getCleanDescription } from "./eas.getCleanDescription";
32
33
  import { join } from 'path'
33
34
  import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from 'fs'
34
35
  import { execSync } from 'child_process'
35
36
  import { platform } from 'os'
36
- import { setWarning } from "../@utilities/utilities.setWarning";
37
- import say from 'say'
38
37
 
39
38
  interface GenerateEASOptions {
40
39
  message: string
@@ -47,16 +46,17 @@ export const setEasTone = async (options: GenerateEASOptions): Promise<string> =
47
46
  const prefix = settings.GlobalSettings.EASSettings.IntroWavFile;
48
47
  let message = options.message;
49
48
  let header = options.header;
49
+ if (!message || !header) {
50
+ setWarning({
51
+ title: `EAS`,
52
+ message: `Message and header are required to generate an EAS tone.`
53
+ })
54
+ return null;
55
+ }
50
56
  let buffTTS: Buffer;
51
57
  let buffRadio: any;
52
58
  let buffFull: any[] = [];
53
59
 
54
-
55
- const tmpTTS = join(directory, `/temp/${Math.random().toString(36).substring(2, 15)}-${header.replace(/[^a-zA-Z0-9]/g, '')}.wav`)
56
- const outTTS = join(directory, `/output/${Math.random().toString(36).substring(2, 15)}-${header.replace(/[^a-zA-Z0-9]/g, '')}.wav`)
57
- const vTTS = process.platform === `win32` ? `Microsoft David Desktop` : `en-US-GuyNerual`;
58
- const vPlatform = platform();
59
-
60
60
  if (!existsSync(directory)) {
61
61
  mkdirSync(directory, { recursive: true });
62
62
  }
@@ -66,22 +66,27 @@ export const setEasTone = async (options: GenerateEASOptions): Promise<string> =
66
66
  if (!existsSync(join(directory, `/output`))) {
67
67
  mkdirSync(join(directory, `/output`), { recursive: true });
68
68
  }
69
- for (const {regex, replacement} of transcribedMessageReplacements) {
70
- message = message.replace(regex, replacement)
71
- }
72
69
 
73
- if (vPlatform != `win32`) {
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 vPlatform = platform();
73
+
74
+ if (vPlatform === 'darwin') {
74
75
  setWarning({
75
76
  title: `EAS`,
76
- message: `Generation isn't supported with this OS. Please wait for further updates before trying again`
77
- })
78
- return null;
79
- }
80
-
81
- say.export(message, vTTS, 1.0, tmpTTS)
82
- while (!existsSync(tmpTTS) || (buffTTS = readFileSync(tmpTTS)).length == 0) {
83
- await setSleep({timeout: 25});
77
+ message: `EAS tone generation is not supported on macOS.`
78
+ });
79
+ return null
84
80
  }
81
+
82
+
83
+ await new Promise<void>((resolve, reject) => {
84
+ const tMsg = getCleanDescription(message)
85
+ getTTS(tMsg, tmpTTS).then(() => {
86
+ buffTTS = readFileSync(tmpTTS);
87
+ resolve();
88
+ });
89
+ });
85
90
  const vWav = getWavPCM16(buffTTS);
86
91
  const vSamples = getSampledPCM16(vWav.samples, vWav.sampleRate, 8000)
87
92
  const vRadio = setRadioEffect(vSamples, 8000)
@@ -130,7 +135,7 @@ export const setEasTone = async (options: GenerateEASOptions): Promise<string> =
130
135
  const aFinal = setNoise(aSamples, 0.002);
131
136
  const aBuffer = getPCM16(Array.from(aFinal).map(v => ({ value: v })), 8000);
132
137
  writeFileSync(outTTS, aBuffer)
133
- try{unlinkSync(tmpTTS)} catch {}
138
+ try{ unlinkSync(tmpTTS) } catch {}
134
139
  return outTTS;
135
140
  }
136
141
 
@@ -17,7 +17,7 @@
17
17
 
18
18
  */
19
19
 
20
- import { TypeAttributes } from "../../@types/types.attributes"
20
+ import { TypeAttributes } from "../../@types/type.attributes"
21
21
  import { eventAwipAbreviations } from "../../@dictionaries/dictionaries.eventAwipAbreviations"
22
22
 
23
23
  interface ValidateOptions {
@@ -17,8 +17,8 @@
17
17
 
18
18
  */
19
19
 
20
- import { TypeStanza } from "../../@types/types.stanza"
21
- import { TypeStanzaCompiled } from "../../@types/types.compiled"
20
+ import { TypeStanza } from "../../@types/type.stanza"
21
+ import { TypeStanzaCompiled } from "../../@types/type.compiled"
22
22
  import { regExp } from "../../@dictionaries/dictionaries.regExp"
23
23
  import { getAwipsType } from "./stanza.getAwipsType"
24
24
 
@@ -0,0 +1,32 @@
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
+ export const getLatestIssuance = (): string => {
22
+ const now = new Date();
23
+ const current = now.getUTCHours() * 100 + now.getUTCMinutes();
24
+ const issuances = [100, 600, 1300, 1630, 2000];
25
+ let latest = issuances[0];
26
+ for (const issuance of issuances) {
27
+ if (current >= issuance) {
28
+ latest = issuance;
29
+ }
30
+ }
31
+ return latest.toString().padStart(4, "0");
32
+ }
@@ -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
 
23
23
  export const getSettings = (): TypeSettings => {
@@ -0,0 +1,27 @@
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
+ export const isImageReady = async (url: string): Promise<boolean> => {
21
+ try {
22
+ const res = await fetch(url, { method: "HEAD" })
23
+ return res.ok
24
+ } catch {
25
+ return false
26
+ }
27
+ }