@atmosx/event-product-parser 2.0.1

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.
@@ -0,0 +1,222 @@
1
+ /*
2
+ _ _ __ __
3
+ /\ | | | | (_) \ \ / /
4
+ / \ | |_ _ __ ___ ___ ___ _ __ | |__ ___ _ __ _ ___ \ V /
5
+ / /\ \| __| "_ ` _ \ / _ \/ __| "_ \| "_ \ / _ \ "__| |/ __| > <
6
+ / ____ \ |_| | | | | | (_) \__ \ |_) | | | | __/ | | | (__ / . \
7
+ /_/ \_\__|_| |_| |_|\___/|___/ .__/|_| |_|\___|_| |_|\___/_/ \_\
8
+ | |
9
+ |_|
10
+
11
+ Written by: KiyoWx (k3yomi)
12
+ */
13
+
14
+ import * as loader from '../bootstrap';
15
+ import * as types from '../types';
16
+ import StanzaParser from '../@parsers/stanza';
17
+ import EventParser from '../@parsers/events';
18
+ import Xmpp from './xmpp';
19
+
20
+ export class Utils {
21
+
22
+ /**
23
+ * @function sleep
24
+ * @description
25
+ * Pauses execution for a specified number of milliseconds.
26
+ *
27
+ * @static
28
+ * @async
29
+ * @param {number} ms
30
+ * @returns {Promise<void>}
31
+ */
32
+ public static async sleep(ms: number): Promise<void> {
33
+ return new Promise(resolve => setTimeout(resolve, ms));
34
+ }
35
+
36
+ /**
37
+ * @function warn
38
+ * @description
39
+ * Emits a log event and prints a warning to the console. Throttles repeated
40
+ * warnings within a short interval unless `force` is `true`.
41
+ *
42
+ * @static
43
+ * @param {string} message
44
+ * @param {boolean} [force=false]
45
+ * @returns {void}
46
+ */
47
+ public static warn(message: string, force: boolean = false): void {
48
+ loader.cache.events.emit('log', message)
49
+ if (!loader.settings.journal) return;
50
+ if (loader.cache.lastWarn != null && (Date.now() - loader.cache.lastWarn < 500) && !force) return;
51
+ loader.cache.lastWarn = Date.now();
52
+ console.warn(`\x1b[33m[ATMOSX-PARSER]\x1b[0m [${new Date().toLocaleString()}] ${message}`);
53
+ }
54
+
55
+ /**
56
+ * @function loadCollectionCache
57
+ * @description
58
+ * Loads cached stanzas from the database, validates them, and processes them through the event parser.
59
+ * Only processes stanzas that are not marked to be ignored and match the CAP preferences.
60
+ *
61
+ * @static
62
+ * @async
63
+ * @returns {Promise<void>}
64
+ */
65
+ public static async loadCollectionCache(): Promise<void> {
66
+ try {
67
+ const settings = loader.settings as types.ClientSettingsTypes;
68
+ if (settings.noaa_weather_wire_service_settings.cache.enabled) {
69
+ const maxRows = settings.noaa_weather_wire_service_settings.cache.max_db_cache_size ?? 5000;
70
+ const rows = await loader.cache.db.prepare(`SELECT * FROM stanzas ORDER BY rowid DESC LIMIT ?`)
71
+ .all(maxRows) as { rowid: number; stanza: string }[];
72
+ this.warn(loader.definitions.messages.dump_cache.replace(`{count}`, rows.length.toString()), true);
73
+ const eventsToProcess = rows
74
+ .map(row => {return JSON.parse(row.stanza)})
75
+ .filter(validate => {
76
+ if (!validate) return false;
77
+ const skip = validate.ignore ||
78
+ (validate.isCap && !settings.noaa_weather_wire_service_settings.preferences.cap_only) ||
79
+ (!validate.isCap && settings.noaa_weather_wire_service_settings.preferences.cap_only) ||
80
+ (validate.isCap && !validate.isCapDescription);
81
+ return !skip;
82
+ });
83
+ await Promise.all(eventsToProcess.map(validate => EventParser.eventHandler(validate)));
84
+ this.warn(loader.definitions.messages.dump_cache_complete, true);
85
+ return;
86
+ }
87
+ } catch (error: any) {
88
+ Utils.warn(`Failed to load cache: ${error.stack}`);
89
+ }
90
+ }
91
+
92
+ /**
93
+ * @function loadGeoJsonData
94
+ * @description
95
+ * Fetches GeoJSON data from the National Weather Service endpoint and
96
+ * passes it to the event parser for processing.
97
+ *
98
+ * @static
99
+ * @async
100
+ * @returns {Promise<void>}
101
+ */
102
+ public static async loadGeoJsonData(): Promise<void> {
103
+ try {
104
+ const settings = loader.settings as types.ClientSettingsTypes;
105
+ const response = await this.createHttpRequest<types.GenericHTTPResponse >(
106
+ settings.national_weather_service_settings.endpoint
107
+ );
108
+ if (response.error) return;
109
+ EventParser.eventHandler({
110
+ message: JSON.stringify(response.message),
111
+ attributes: {},
112
+ isCap: true,
113
+ isApi: true,
114
+ isPVtec: false,
115
+ isUGC: false,
116
+ isCapDescription: false,
117
+ awipsType: { type: 'api', prefix: 'AP' },
118
+ ignore: false
119
+ });
120
+ } catch (error: unknown) {
121
+ const msg = error instanceof Error ? error.message : String(error);
122
+ Utils.warn(`Failed to load National Weather Service GeoJSON Data: ${msg}`);
123
+ }
124
+ }
125
+
126
+ /**
127
+ * @function createHttpRequest
128
+ * @description
129
+ * Performs an HTTP GET request with default headers and timeout, returning
130
+ * either the response data or an error message.
131
+ *
132
+ * @static
133
+ * @template T
134
+ * @param {string} url
135
+ * @param {types.HTTPSettings} [options]
136
+ * @returns {Promise<{ error: boolean; message: T | string }>}
137
+ */
138
+ public static async createHttpRequest<T = unknown>(url: string, options?: types.HTTPSettings): Promise<{ error: boolean; message: T | string }> {
139
+ const defaultOptions = {
140
+ timeout: 10000,
141
+ headers: {
142
+ "User-Agent": "AtmosphericX",
143
+ "Accept": "application/geo+json, text/plain, */*; q=0.9",
144
+ "Accept-Language": "en-US,en;q=0.9"
145
+ }
146
+ };
147
+ const requestOptions = {
148
+ ...defaultOptions,
149
+ ...options,
150
+ headers: { ...defaultOptions.headers, ...(options?.headers ?? {}) }
151
+ };
152
+ try {
153
+ const resp = await loader.packages.axios.get<T>(url, {
154
+ headers: requestOptions.headers,
155
+ timeout: requestOptions.timeout,
156
+ maxRedirects: 0,
157
+ validateStatus: (status) => status === 200 || status === 500
158
+ });
159
+ return { error: false, message: resp.data };
160
+ } catch (err: unknown) {
161
+ const msg = err instanceof Error ? err.message : String(err);
162
+ return { error: true, message: msg };
163
+ }
164
+ }
165
+
166
+ /**
167
+ * @function handleCronJob
168
+ * @description
169
+ * Performs scheduled tasks for NWWS XMPP session maintenance or GeoJSON data
170
+ * updates depending on the job type.
171
+ *
172
+ * @static
173
+ * @param {boolean} isWire
174
+ * @returns {void}
175
+ */
176
+ public static handleCronJob(isWire: boolean): void {
177
+ try {
178
+ const settings = loader.settings as types.ClientSettingsTypes;
179
+ const cache = settings.noaa_weather_wire_service_settings.cache;
180
+ const reconnections = settings.noaa_weather_wire_service_settings.reconnection_settings;
181
+ if (isWire) {
182
+ if (reconnections.enabled) {
183
+ void Xmpp.isSessionReconnectionEligible(reconnections.interval);
184
+ }
185
+ } else {
186
+ void this.loadGeoJsonData();
187
+ }
188
+ } catch (error: unknown) {
189
+ const msg = error instanceof Error ? error.message : String(error);
190
+ Utils.warn(`Failed to perform scheduled tasks (${isWire ? 'NWWS' : 'GeoJSON'}): ${msg}`);
191
+ }
192
+ }
193
+
194
+ /**
195
+ * @function mergeClientSettings
196
+ * @description
197
+ * Recursively merges a ClientSettings object into a target object,
198
+ * preserving nested structures and overriding existing values.
199
+ *
200
+ * @static
201
+ * @param {Record<string, unknown>} target
202
+ * @param {types.ClientSettingsTypes} settings
203
+ * @returns {Record<string, unknown>}
204
+ */
205
+ public static mergeClientSettings(target: Record<string, unknown>, settings: types.ClientSettingsTypes): Record<string, unknown> {
206
+ for (const key in settings) {
207
+ if (!Object.prototype.hasOwnProperty.call(settings, key)) continue;
208
+ const value = settings[key];
209
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
210
+ if (!target[key] || typeof target[key] !== 'object' || Array.isArray(target[key])) {
211
+ target[key] = {};
212
+ }
213
+ this.mergeClientSettings(target[key] as Record<string, unknown>, value as types.ClientSettingsTypes);
214
+ } else {
215
+ target[key] = value;
216
+ }
217
+ }
218
+ return target;
219
+ }
220
+ }
221
+
222
+ export default Utils;
@@ -0,0 +1,142 @@
1
+ /*
2
+ _ _ __ __
3
+ /\ | | | | (_) \ \ / /
4
+ / \ | |_ _ __ ___ ___ ___ _ __ | |__ ___ _ __ _ ___ \ V /
5
+ / /\ \| __| "_ ` _ \ / _ \/ __| "_ \| "_ \ / _ \ "__| |/ __| > <
6
+ / ____ \ |_| | | | | | (_) \__ \ |_) | | | | __/ | | | (__ / . \
7
+ /_/ \_\__|_| |_| |_|\___/|___/ .__/|_| |_|\___|_| |_|\___/_/ \_\
8
+ | |
9
+ |_|
10
+
11
+ Written by: KiyoWx (k3yomi)
12
+ */
13
+
14
+ import * as loader from '../bootstrap';
15
+ import * as types from '../types';
16
+ import Utils from './utils';
17
+ import StanzaParser from '../@parsers/stanza';
18
+ import Database from '../@submodules/database';
19
+ import EventParser from '../@parsers/events';
20
+
21
+ export class Xmpp {
22
+
23
+ /**
24
+ * @function isSessionReconnectionEligible
25
+ * @description
26
+ * Checks if the XMPP session has been inactive longer than the given interval
27
+ * and, if so, attempts a controlled reconnection.
28
+ *
29
+ * @async
30
+ * @static
31
+ * @param {number} currentInterval
32
+ * @returns {Promise<void>}
33
+ */
34
+ public static async isSessionReconnectionEligible(currentInterval: number): Promise<void> {
35
+ const settings = loader.settings as types.ClientSettingsTypes;
36
+ const lastStanzaElapsed = Date.now() - loader.cache.lastStanza;
37
+ const threshold = currentInterval * 1000;
38
+ if ((!loader.cache.isConnected && !loader.cache.sigHalt) || !loader.cache.session) { return; }
39
+ if (lastStanzaElapsed < threshold) { return; }
40
+ if (loader.cache.attemptingReconnect) { return; }
41
+ loader.cache.attemptingReconnect = true;
42
+ loader.cache.isConnected = false;
43
+ loader.cache.totalReconnects += 1;
44
+ try {
45
+ loader.cache.events.emit("onReconnection", {
46
+ reconnects: loader.cache.totalReconnects,
47
+ lastStanza: lastStanzaElapsed,
48
+ lastName: settings.noaa_weather_wire_service_settings.credentials.nickname,
49
+ });
50
+ await loader.cache.session.stop().catch(() => {});
51
+ await loader.cache.session.start().catch(() => {});
52
+ } catch (err) {
53
+ Utils.warn(`XMPP reconnection failed: ${(err as Error).message}`);
54
+ } finally {
55
+ loader.cache.attemptingReconnect = false;
56
+ }
57
+ }
58
+
59
+ /**
60
+ * @function deploySession
61
+ * @description
62
+ * Initializes the NOAA Weather Wire Service (NWWS-OI) XMPP client session and
63
+ * manages its lifecycle events including connection, disconnection, errors,
64
+ * and message handling.
65
+ *
66
+ * @async
67
+ * @static
68
+ * @returns {Promise<void>}
69
+ */
70
+ public static async deploySession(): Promise<void> {
71
+ const settings = loader.settings as types.ClientSettingsTypes;
72
+ settings.noaa_weather_wire_service_settings.credentials.nickname ??= settings.noaa_weather_wire_service_settings.credentials.username;
73
+ loader.cache.session = loader.packages.xmpp.client({
74
+ service: 'xmpp://nwws-oi.weather.gov',
75
+ domain: 'nwws-oi.weather.gov',
76
+ username: settings.noaa_weather_wire_service_settings.credentials.username,
77
+ password: settings.noaa_weather_wire_service_settings.credentials.password,
78
+ });
79
+ loader.cache.session.on('online', async (address: string) => {
80
+ const now = Date.now();
81
+ if (loader.cache.lastConnect && now - loader.cache.lastConnect < 10_000) {
82
+ loader.cache.sigHalt = true;
83
+ Utils.warn(loader.definitions.messages.reconnect_too_fast);
84
+ await Utils.sleep(2_000);
85
+ await loader.cache.session.stop().catch(() => {});
86
+ return;
87
+ }
88
+ loader.cache.isConnected = true;
89
+ loader.cache.sigHalt = false;
90
+ loader.cache.lastConnect = now;
91
+ loader.cache.session.send(loader.packages.xmpp.xml('presence', {
92
+ to: `nwws@conference.nwws-oi.weather.gov/${settings.noaa_weather_wire_service_settings.credentials.nickname}`,
93
+ xmlns: 'http://jabber.org/protocol/muc',
94
+ }));
95
+ loader.cache.events.emit('onConnection', settings.noaa_weather_wire_service_settings.credentials.nickname);
96
+ if (loader.cache.attemptingReconnect) return;
97
+ loader.cache.attemptingReconnect = true;
98
+ await Utils.sleep(15_000);
99
+ loader.cache.attemptingReconnect = false;
100
+ });
101
+ loader.cache.session.on('offline', () => {
102
+ loader.cache.isConnected = false;
103
+ loader.cache.sigHalt = true;
104
+ Utils.warn('XMPP connection went offline');
105
+ });
106
+ loader.cache.session.on('error', (error: Error) => {
107
+ loader.cache.isConnected = false;
108
+ loader.cache.sigHalt = true;
109
+ Utils.warn(`XMPP connection error: ${error.message}`);
110
+ });
111
+ loader.cache.session.on('stanza', async (stanza: any) => {
112
+ try {
113
+ loader.cache.lastStanza = Date.now();
114
+ if (stanza.is('message')) {
115
+ const validate = StanzaParser.validate(stanza);
116
+ const skipMessage = validate.ignore ||
117
+ (validate.isCap && !settings.noaa_weather_wire_service_settings.preferences.cap_only) ||
118
+ (!validate.isCap && settings.noaa_weather_wire_service_settings.preferences.cap_only) ||
119
+ (validate.isCap && !validate.isCapDescription);
120
+ if (skipMessage) return;
121
+ await EventParser.eventHandler(validate);
122
+ await Database.stanzaCacheImport(validate);
123
+ loader.cache.events.emit('onMessage', validate);
124
+ }
125
+ if (stanza.is('presence') && stanza.attrs.from?.startsWith('nwws@conference.nwws-oi.weather.gov/')) {
126
+ const occupant = stanza.attrs.from.split('/').slice(1).join('/');
127
+ loader.cache.events.emit('onOccupant', {
128
+ occupant,
129
+ type: stanza.attrs.type === 'unavailable' ? 'unavailable' : 'available',
130
+ });
131
+ }
132
+ } catch (err: unknown) {
133
+ Utils.warn(`Error processing stanza: ${(err as Error).message}`);
134
+ }
135
+ });
136
+ try { await loader.cache.session.start(); } catch (err: unknown) {
137
+ Utils.warn(`Failed to start XMPP session: ${(err as Error).message}`);
138
+ }
139
+ }
140
+ }
141
+
142
+ export default Xmpp;
@@ -0,0 +1,190 @@
1
+ /*
2
+ _ _ __ __
3
+ /\ | | | | (_) \ \ / /
4
+ / \ | |_ _ __ ___ ___ ___ _ __ | |__ ___ _ __ _ ___ \ V /
5
+ / /\ \| __| '_ ` _ \ / _ \/ __| '_ \| '_ \ / _ \ '__| |/ __| > <
6
+ / ____ \ |_| | | | | | (_) \__ \ |_) | | | | __/ | | | (__ / . \
7
+ /_/ \_\__|_| |_| |_|\___/|___/ .__/|_| |_|\___|_| |_|\___/_/ \_\
8
+ | |
9
+ |_|
10
+
11
+ Written by: k3yomi@GitHub
12
+ */
13
+
14
+ import * as fs from 'fs';
15
+ import * as path from 'path';
16
+ import * as events from 'events';
17
+ import * as xmpp from '@xmpp/client';
18
+ import * as shapefile from 'shapefile';
19
+ import * as xml2js from 'xml2js';
20
+ import * as jobs from 'croner';
21
+ import * as polygonClipping from 'polygon-clipping';
22
+ import sqlite3 from 'better-sqlite3';
23
+ import axios from 'axios';
24
+ import crypto from 'crypto';
25
+ import os from 'os';
26
+ import say from 'say';
27
+ import child from 'child_process';
28
+ import jszip from 'jszip';
29
+
30
+
31
+ import * as dictEvents from './@dictionaries/events';
32
+ import * as dictAwips from './@dictionaries/awips';
33
+ import * as dictSignatures from './@dictionaries/signatures';
34
+ import * as dictICAOs from './@dictionaries/icao';
35
+
36
+
37
+
38
+ export const packages = {
39
+ fs, path, events, xmpp,
40
+ shapefile, xml2js, sqlite3, jobs, axios,
41
+ crypto, os, say, child, polygonClipping, jszip
42
+ };
43
+
44
+ export const cache = {
45
+ isReady: true,
46
+ sigHalt: false,
47
+ isConnected: false,
48
+ attemptingReconnect: false,
49
+ totalReconnects: 0,
50
+ lastStanza: null,
51
+ session: null,
52
+ lastConnect: null,
53
+ db: null,
54
+ lastWarn: null,
55
+ totalLocationWarns: 0,
56
+ events: new events.EventEmitter(),
57
+ isProcessingAudioQueue: false,
58
+ audioQueue: [],
59
+ };
60
+
61
+ export const settings = {
62
+ database: path.join(process.cwd(), 'shapefiles.db'),
63
+ is_wire: true,
64
+ journal: true,
65
+ noaa_weather_wire_service_settings: {
66
+ reconnection_settings: {
67
+ enabled: true,
68
+ interval: 60,
69
+ },
70
+ credentials: {
71
+ username: null,
72
+ password: null,
73
+ nickname: "AtmosphericX Standalone Parser",
74
+ },
75
+ cache: {
76
+ enabled: true,
77
+ max_db_history: 5000,
78
+ max_db_cache_size: 1000,
79
+ },
80
+ preferences: {
81
+ disable_ugc: false,
82
+ disable_vtec: false,
83
+ disable_text: false,
84
+ cap_only: false,
85
+ }
86
+ },
87
+ national_weather_service_settings: {
88
+ interval: 15,
89
+ endpoint: `https://api.weather.gov/alerts/active`,
90
+ },
91
+ global_settings: {
92
+ parent_events_only: true,
93
+ better_event_parsing: true,
94
+ ignore_geometry_parsing: false,
95
+ shapefile_coordinates: false,
96
+ shapefile_skip: 15,
97
+ filtering: {
98
+ events: [],
99
+ filtered_icao: [],
100
+ ignored_icao: [],
101
+ ignored_events: [`Xx`, `Test Message`],
102
+ ugc_filter: [],
103
+ state_filter: [],
104
+ check_expired: true,
105
+ ignore_test_products: true,
106
+ },
107
+ eas_settings: {
108
+ directory: null,
109
+ intro_wav: null,
110
+ }
111
+ }
112
+ };
113
+
114
+
115
+ export const definitions = {
116
+ events: dictEvents.events,
117
+ actions: dictEvents.actions,
118
+ status: dictEvents.status,
119
+ productTypes: dictEvents.types,
120
+ correlations: dictEvents.status_correlations,
121
+ offshore: dictEvents.offshore,
122
+ awips: dictAwips.awips,
123
+ causes: dictEvents.causes,
124
+ records: dictEvents.records,
125
+ severity: dictEvents.severity,
126
+ cancelSignatures: dictSignatures.cancel_signatures,
127
+ messageSignatures: dictSignatures.message_signatures,
128
+ tags: dictSignatures.tags,
129
+ ICAO: dictICAOs.icaos,
130
+ enhancedEvents: [
131
+ {"Tornado Warning": {
132
+ "Tornado Emergency": { description: "tornado emergency", condition: (tornadoThreatTag: string) => tornadoThreatTag === 'OBSERVED'},
133
+ "PDS Tornado Warning": { description: "particularly dangerous situation", condition: (damageThreatTag: string) => damageThreatTag === 'CONSIDERABLE'},
134
+ "Confirmed Tornado Warning": { condition: (tornadoThreatTag: string) => tornadoThreatTag === 'OBSERVED'},
135
+ "Radar Indicated Tornado Warning": {condition: (tornadoThreatTag: string) => tornadoThreatTag !== 'OBSERVED'},
136
+ }},
137
+ {"Special Marine Warning": {
138
+ "Tornadic Special Marine Warning": {condition: (tornadoThreatTag: string) => tornadoThreatTag !== 'POSSIBLE'},
139
+ }},
140
+ {"Tornado Watch": {
141
+ "PDS Tornado Watch": { description: "particularly dangerous situation"}
142
+ }},
143
+ {"Flash Flood Warning": {
144
+ "Flash Flood Emergency": { description: "flash flood emergency", },
145
+ "Considerable Flash Flood Warning": { condition: (damageThreatTag: string) => damageThreatTag === 'CONSIDERABLE' },
146
+ }},
147
+ {"Severe Thunderstorm Warning": {
148
+ "EDS Severe Thunderstorm Warning": {description: "extremely dangerous situation"},
149
+ "Destructive Severe Thunderstorm Warning": {condition: (damageThreatTag: string) => damageThreatTag === 'DESTRUCTIVE'},
150
+ "Considerable Severe Thunderstorm Warning": {condition: (damageThreatTag: string) => damageThreatTag === 'CONSIDERABLE'},
151
+ }},
152
+ ],
153
+ shapefiles_directory: [
154
+ {name: "us_counties", id: "C", link: "https://www.weather.gov/source/gis/Shapefiles/County/c_18mr25.zip"},
155
+ {name: "us_states_territories", id: "Z", link: "https://www.weather.gov/source/gis/Shapefiles/County/s_18mr25.zip"},
156
+ {name: "fire_weather_zones", id: "Z", link: "https://www.weather.gov/source/gis/Shapefiles/WSOM/fz18mr25.zip"},
157
+ {name: "costal_marine_zones", id: "Z", link: "https://www.weather.gov/source/gis/Shapefiles/WSOM/mz18mr25.zip"},
158
+ {name: "offshore_marine_zones", id: "Z", link: "https://www.weather.gov/source/gis/Shapefiles/WSOM/oz18mr25.zip"},
159
+ {name: "public_forecast_zones", id: "Z", link: "https://www.weather.gov/source/gis/Shapefiles/WSOM/z_18mr25.zip"},
160
+ {name: "county_warning_areas", id: "Z", link: "https://www.weather.gov/source/gis/Shapefiles/WSOM/w_18mr25.zip"},
161
+ {name: "river_forecast_boundaries", id: "Z", link: "https://www.weather.gov/source/gis/Shapefiles/Misc/rf05mr24.zip"},
162
+ ],
163
+ regular_expressions: {
164
+ 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"),
165
+ 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"),
166
+ wmo: new RegExp(`[A-Z0-9]{6}\\s[A-Z]{4}\\s\\d{6}`, "imu"),
167
+ ugc1: new RegExp(`(\\w{2}[CZ](\\d{3}((-|>)\\s?(\\n\\n)?))+)`, "imu"),
168
+ ugc2: new RegExp(`(\\d{6}(-|>)\\s?(\\n\\n)?)`, "imu"),
169
+ ugc3: new RegExp(`(\\d{6})(?=-|$)`, "imu"),
170
+ dateline: new RegExp(`\\d{3,4}\\s*(AM|PM)?\\s*[A-Z]{2,4}\\s+[A-Z]{3,}\\s+[A-Z]{3,}\\s+\\d{1,2}\\s+\\d{4}`, "gim"),
171
+ },
172
+ messages: {
173
+ shapefile_creation: `DO NOT EXIT UNTIL THE SHAPEFILES ARE DONE COMPLETING! IF YOU CLOSE YOUR PROJECT, THE SHAPEFILES WILL NOT BE CREATED AND YOU WILL NEED TO DELETE ${settings.database} AND RESTART TO CREATE THEM AGAIN!`,
174
+ shapefile_creation_finished: `Shapefiles have finished completing and you can now use the parser.`,
175
+ not_ready: `You can not create another instance without shutting down the current one first, please make sure to call the stop() method first!`,
176
+ invalid_nickname: `The nickname you provided is invalid, please provide a valid nickname to continue.`,
177
+ eas_no_directory: `You have not set a directory for EAS audio files to be saved to, please set the 'directory' setting in the global settings to enable EAS audio generation.`,
178
+ reconnect_too_fast: `The client is attempting to reconnect too fast. This may be due to network instability. Reconnection attempt has been halted for safety.`,
179
+ dump_cache: `Found {count} cached events and will begin dumping them shortly. This may take a while depending on the number of cached events.`,
180
+ dump_cache_complete: `Completed dumping all cached alert files.`,
181
+ }
182
+ };
183
+
184
+
185
+ process.on('uncaughtException', (err: any) => {
186
+ if (err?.code === 'ETIMEDOUT') { return; }
187
+ if (err?.code === 'ECONNRESET') { return; }
188
+ if (err?.code === 'EHOSTUNREACH') { return; }
189
+ throw err;
190
+ })