@atmosx/event-product-parser 2.0.16 → 3.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.
- package/README.md +248 -187
- package/dist/cjs/index.cjs +12218 -3139
- package/dist/esm/index.mjs +12401 -3327
- package/dist/index.d.mts +235 -0
- package/dist/index.d.ts +235 -0
- package/package.json +6 -4
- package/src/@building/building.clean.ts +30 -0
- package/src/@building/building.create.ts +42 -0
- package/src/@building/building.enhance.ts +56 -0
- package/src/@building/building.geometry.ts +48 -0
- package/src/@building/building.headers.ts +37 -0
- package/src/@building/building.office.ts +43 -0
- package/src/@building/building.polygon.ts +71 -0
- package/src/@building/building.properties.ts +89 -0
- package/src/@building/building.signature.ts +61 -0
- package/src/@building/building.tags.ts +24 -0
- package/src/@building/building.tracking.ts +68 -0
- package/src/@building/building.validate.ts +147 -0
- package/src/@core/core.getEvents.ts +25 -0
- package/src/@core/core.getNodes.ts +25 -0
- package/src/@core/core.getRandomEvent.ts +25 -0
- package/src/@core/core.getVersion.ts +25 -0
- package/src/@core/core.listener.ts +24 -0
- package/src/@core/core.setNode.ts +81 -0
- package/src/@core/core.start.ts +59 -0
- package/src/@core/core.stop.ts +32 -0
- package/src/@dictionaries/dictionaries.betterEventNames.ts +89 -0
- package/src/@dictionaries/dictionaries.eventActions.ts +28 -0
- package/src/@dictionaries/{awips.ts → dictionaries.eventAwipAbreviations.ts} +13 -7
- package/src/@dictionaries/dictionaries.eventCancelMessages.ts +30 -0
- package/src/@dictionaries/dictionaries.eventCauses.ts +36 -0
- package/src/@dictionaries/dictionaries.eventProducts.ts +25 -0
- package/src/@dictionaries/dictionaries.eventRecords.ts +25 -0
- package/src/@dictionaries/dictionaries.eventSeverity.ts +27 -0
- package/src/@dictionaries/dictionaries.eventStatus.ts +31 -0
- package/src/@dictionaries/{signatures.ts → dictionaries.eventTags.ts} +13 -68
- package/src/@dictionaries/dictionaries.eventTypes.ts +82 -0
- package/src/@dictionaries/dictionaries.eventsOffshore.ts +31 -0
- package/src/@dictionaries/dictionaries.hailStrings.ts +33 -0
- package/src/@dictionaries/{icao.ts → dictionaries.officeICAOs.ts} +13 -6
- package/src/@dictionaries/dictionaries.regExp.ts +28 -0
- package/src/@dictionaries/dictionaries.shapefileLinks.ts +36 -0
- package/src/@dictionaries/dictionaries.statusCorrelationText.ts +40 -0
- package/src/@dictionaries/dictionaries.testSignatures.ts +24 -0
- package/src/@dictionaries/dictionaries.transcribedMessageReplacements.ts +68 -0
- package/src/@events/events.api.ts +113 -0
- package/src/@events/events.text.ts +79 -0
- package/src/@events/events.ugc.ts +83 -0
- package/src/@events/events.vtec.ts +87 -0
- package/src/@manager/manager.mkEvent.ts +95 -0
- package/src/@manager/manager.rmEvent.ts +46 -0
- package/src/@manager/manager.setHash.ts +37 -0
- package/src/@manager/manager.updateNodes.ts +55 -0
- package/src/@manager/manager.updateWebhooks.ts +50 -0
- package/src/@modules/@database/database.cache.ts +48 -0
- package/src/@modules/@database/database.init.ts +45 -0
- package/src/@modules/@database/database.shapefiles.ts +96 -0
- package/src/@modules/@database/database.stanza.ts +48 -0
- package/src/@modules/@eas/eas.getFloatPCM16.ts +29 -0
- package/src/@modules/@eas/eas.getMergedPCM16.ts +32 -0
- package/src/@modules/@eas/eas.getPCM16.ts +52 -0
- package/src/@modules/@eas/eas.getPCMToFloat.ts +26 -0
- package/src/@modules/@eas/eas.getSampledPCM16.ts +36 -0
- package/src/@modules/@eas/eas.getWavPCM16.ts +52 -0
- package/src/@modules/@eas/eas.setAFSK.ts +52 -0
- package/src/@modules/@eas/eas.setAsciiToBits.ts +32 -0
- package/src/@modules/@eas/eas.setAttentionTone.ts +40 -0
- package/src/@modules/@eas/eas.setEasTone.ts +137 -0
- package/src/@modules/@eas/eas.setNoise.ts +31 -0
- package/src/@modules/@eas/eas.setRadioEffect.ts +49 -0
- package/src/@modules/@eas/eas.setSameHeader.ts +45 -0
- package/src/@modules/@stanza/stanza.getAwipsType.ts +46 -0
- package/src/@modules/@stanza/stanza.validate.ts +50 -0
- package/src/@modules/@utilities/utilities.createHttp.ts +85 -0
- package/src/@modules/@utilities/utilities.createWebhook.ts +100 -0
- package/src/@modules/@utilities/utilities.getFormattedTime.ts +43 -0
- package/src/@modules/@utilities/utilities.getSettings.ts +25 -0
- package/src/@modules/@utilities/utilities.getShapeNearestPoint.ts +114 -0
- package/src/@modules/@utilities/utilities.setCronSchedule.ts +65 -0
- package/src/@modules/@utilities/utilities.setEventEmit.ts +41 -0
- package/src/@modules/@utilities/utilities.setListener.ts +30 -0
- package/src/@modules/@utilities/utilities.setSettings.ts +42 -0
- package/src/@modules/@utilities/utilities.setSleep.ts +33 -0
- package/src/@modules/@utilities/utilities.setTimeoutAction.ts +59 -0
- package/src/@modules/@utilities/utilities.setWarning.ts +34 -0
- package/src/@modules/@xmpp/xmpp.xDeploy.ts +58 -0
- package/src/@modules/@xmpp/xmpp.xError.ts +29 -0
- package/src/@modules/@xmpp/xmpp.xOffline.ts +38 -0
- package/src/@modules/@xmpp/xmpp.xOnline.ts +45 -0
- package/src/@modules/@xmpp/xmpp.xReconnect.ts +64 -0
- package/src/@modules/@xmpp/xmpp.xStanza.ts +63 -0
- package/src/@parsers/@hvtec/hvtec.extract.ts +40 -0
- package/src/@parsers/@pvtec/pvtec.expires.ts +26 -0
- package/src/@parsers/@pvtec/pvtec.extract.ts +49 -0
- package/src/@parsers/@text/text.getDescriptionFromProduct.ts +53 -0
- package/src/@parsers/@text/text.getPolygonFromProduct.ts +32 -0
- package/src/@parsers/@text/text.getTextFromProduct.ts +43 -0
- package/src/@parsers/@text/text.getXML.ts +61 -0
- package/src/@parsers/@ugc/ugc.coordinates.ts +110 -0
- package/src/@parsers/@ugc/ugc.expiry.ts +32 -0
- package/src/@parsers/@ugc/ugc.extract.ts +37 -0
- package/src/@parsers/@ugc/ugc.header.ts +30 -0
- package/src/@parsers/@ugc/ugc.locations.ts +29 -0
- package/src/@parsers/@ugc/ugc.zones.ts +52 -0
- package/src/@types/type.event.ts +67 -0
- package/src/@types/type.properties.ts +75 -0
- package/src/@types/types.attributes.ts +28 -0
- package/src/@types/types.compiled.ts +35 -0
- package/src/@types/types.hash.ts +24 -0
- package/src/@types/types.hvtec.ts +25 -0
- package/src/@types/types.pvtec.ts +30 -0
- package/src/@types/types.settings.ts +76 -0
- package/src/@types/types.stanza.ts +37 -0
- package/src/@types/types.ugc.ts +24 -0
- package/src/@types/types.webhook.ts +26 -0
- package/src/bootstrap.ts +85 -163
- package/src/index.ts +47 -216
- package/test.js +78 -51
- package/tsup.config.ts +1 -0
- package/src/@dictionaries/events.ts +0 -168
- package/src/@parsers/@events/api.ts +0 -146
- package/src/@parsers/@events/cap.ts +0 -123
- package/src/@parsers/@events/text.ts +0 -104
- package/src/@parsers/@events/ugc.ts +0 -107
- package/src/@parsers/@events/vtec.ts +0 -76
- package/src/@parsers/events.ts +0 -392
- package/src/@parsers/hvtec.ts +0 -46
- package/src/@parsers/pvtec.ts +0 -72
- package/src/@parsers/stanza.ts +0 -97
- package/src/@parsers/text.ts +0 -165
- package/src/@parsers/ugc.ts +0 -247
- package/src/@submodules/database.ts +0 -201
- package/src/@submodules/eas.ts +0 -490
- package/src/@submodules/utils.ts +0 -191
- package/src/@submodules/xmpp.ts +0 -142
- package/src/types.ts +0 -259
package/src/@parsers/events.ts
DELETED
|
@@ -1,392 +0,0 @@
|
|
|
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 TextParser from './text';
|
|
17
|
-
import UGCParser from './ugc';
|
|
18
|
-
import VTECAlerts from './@events/vtec';
|
|
19
|
-
import UGCAlerts from './@events/ugc';
|
|
20
|
-
import TextAlerts from './@events/text';
|
|
21
|
-
import CAPAlerts from './@events/cap';
|
|
22
|
-
import APIAlerts from './@events/api';
|
|
23
|
-
|
|
24
|
-
export class EventParser {
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* @function getBaseProperties
|
|
28
|
-
* @description
|
|
29
|
-
* Extracts and compiles the core properties of a weather
|
|
30
|
-
* alert message into a structured object. Combines parsed
|
|
31
|
-
* textual data, UGC information, VTEC entries, and additional
|
|
32
|
-
* metadata for downstream use.
|
|
33
|
-
*
|
|
34
|
-
* @static
|
|
35
|
-
* @async
|
|
36
|
-
* @param {string} message
|
|
37
|
-
* @param {types.StanzaCompiled} metadata
|
|
38
|
-
* @param {types.UGCEntry} [ugc=null]
|
|
39
|
-
* @param {types.PVtecEntry} [pVtec=null]
|
|
40
|
-
* @param {types.HVtecEntry} [hVtec=null]
|
|
41
|
-
* @returns {Promise<Record<string, any>>}
|
|
42
|
-
*/
|
|
43
|
-
public static async getBaseProperties(message: string, metadata: types.DefaultAttributesType, ugc: types.UGCEntry = null, pVtec: types.PVtecEntry = null, hVtec: types.HVtecEntry = null): Promise<Record<string, any>> {
|
|
44
|
-
const settings = loader.settings as types.ClientSettingsTypes;
|
|
45
|
-
const definitions = {
|
|
46
|
-
tornado: TextParser.textProductToString(message, `TORNADO...`) ?? TextParser.textProductToString(message, `WATERSPOUT...`) ?? null,
|
|
47
|
-
hail: TextParser.textProductToString(message, `MAX HAIL SIZE...`, [`IN`]) ?? TextParser.textProductToString(message, `HAIL...`, [`IN`]) ?? null,
|
|
48
|
-
gusts: TextParser.textProductToString(message, `MAX WIND GUST...`) ?? TextParser.textProductToString(message, `WIND...`) ?? null,
|
|
49
|
-
flood: TextParser.textProductToString(message, `FLASH FLOOD...`) ?? null,
|
|
50
|
-
damage: TextParser.textProductToString(message, `DAMAGE THREAT...`) ?? null,
|
|
51
|
-
source: TextParser.textProductToString(message, `SOURCE...`, [`.`]) ?? null,
|
|
52
|
-
description: TextParser.textProductToDescription(message, pVtec?.raw ?? null),
|
|
53
|
-
polygon: TextParser.textProductToPolygon(message),
|
|
54
|
-
wmo: message.match(loader.definitions.regular_expressions.wmo)?.[0] ?? null,
|
|
55
|
-
mdTorIntensity: TextParser.textProductToString(message, `MOST PROBABLE PEAK TORNADO INTENSITY...`) ?? null,
|
|
56
|
-
mdWindGusts: TextParser.textProductToString(message, `MOST PROBABLE PEAK WIND GUST...`) ?? null,
|
|
57
|
-
mdHailSize: TextParser.textProductToString(message, `MOST PROBABLE PEAK HAIL SIZE...`) ?? null,
|
|
58
|
-
};
|
|
59
|
-
const getOffice = this.getICAO(pVtec, metadata, definitions.wmo);
|
|
60
|
-
const getCorrectIssued = this.getCorrectIssuedDate(metadata);
|
|
61
|
-
const getCorrectExpiry = this.getCorrectExpiryDate(pVtec, ugc);
|
|
62
|
-
const base = {
|
|
63
|
-
locations: ugc?.locations.join(`; `) ?? `No Location Specified (UGC Missing)`,
|
|
64
|
-
issued: getCorrectIssued,
|
|
65
|
-
expires: getCorrectExpiry,
|
|
66
|
-
geocode: {
|
|
67
|
-
UGC: ugc?.zones ?? [],
|
|
68
|
-
generated: definitions.polygon.length > 0 ? Buffer.from(JSON.stringify([definitions.polygon])).toString('base64') : null
|
|
69
|
-
},
|
|
70
|
-
description: definitions.description,
|
|
71
|
-
instruction: null,
|
|
72
|
-
sender_name: getOffice.name,
|
|
73
|
-
sender_icao: getOffice.icao,
|
|
74
|
-
raw: {...Object.fromEntries(Object.entries(metadata).filter(([key]) => key !== 'message'))},
|
|
75
|
-
parameters: {
|
|
76
|
-
wmo: Array.isArray(definitions.wmo) ? definitions.wmo[0] : (definitions.wmo ?? null),
|
|
77
|
-
source: definitions.source,
|
|
78
|
-
max_hail_size: definitions.hail,
|
|
79
|
-
max_wind_gust: definitions.gusts,
|
|
80
|
-
damage_threat: definitions.damage,
|
|
81
|
-
tornado_detection: definitions.tornado,
|
|
82
|
-
flood_detection: definitions.flood,
|
|
83
|
-
discussion_tornado_intensity: definitions.mdTorIntensity,
|
|
84
|
-
discussion_wind_intensity: definitions.mdWindGusts,
|
|
85
|
-
discussion_hail_intensity: definitions.mdHailSize,
|
|
86
|
-
},
|
|
87
|
-
};
|
|
88
|
-
return base;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* @function getEventGeometry
|
|
93
|
-
* @description
|
|
94
|
-
* Determines the geometry of an event using polygon data fromEntries
|
|
95
|
-
* in the message or UGC shapefile coordinates if enabled in settings. Falls
|
|
96
|
-
* back to null if no geometry can be determined.
|
|
97
|
-
*
|
|
98
|
-
* @static
|
|
99
|
-
* @param {string} generated
|
|
100
|
-
* @param {types.UGCEntry} [ugc=null]
|
|
101
|
-
* @returns {Promise<types.geometry>}
|
|
102
|
-
*/
|
|
103
|
-
public static async getEventGeometry(generated: string, ugc: types.UGCEntry = null, isUnion: boolean = true) : Promise<types.geometry> {
|
|
104
|
-
const settings = loader.settings as types.ClientSettingsTypes;
|
|
105
|
-
let geometry = { type: "Polygon", coordinates: generated != null ? JSON.parse(Buffer.from(generated, 'base64').toString('utf-8')) : null };
|
|
106
|
-
if (settings.global_settings.shapefile_coordinates && generated == null && ugc != null) {
|
|
107
|
-
const coordinates = await UGCParser.getCoordinates(ugc.zones, isUnion) as any;
|
|
108
|
-
geometry = coordinates
|
|
109
|
-
}
|
|
110
|
-
return geometry;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* @function betterParsedEventName
|
|
115
|
-
* @description
|
|
116
|
-
* Enhances the parsing of an event name using additional criteria
|
|
117
|
-
* from its description and parameters. Can optionally use
|
|
118
|
-
* the original parent event name instead.
|
|
119
|
-
*
|
|
120
|
-
* @static
|
|
121
|
-
* @param {types.EventCompiled} event
|
|
122
|
-
* @param {boolean} [betterParsing=false]
|
|
123
|
-
* @param {boolean} [useParentEvents=false]
|
|
124
|
-
* @returns {string}
|
|
125
|
-
*/
|
|
126
|
-
public static betterParsedEventName(event: types.EventCompiled, betterParsing?: boolean, useParentEvents?: boolean): string {
|
|
127
|
-
let eventName = event?.properties?.event ?? `Unknown Event`;
|
|
128
|
-
const defEventTable = loader.definitions.enhancedEvents;
|
|
129
|
-
const properties = event?.properties;
|
|
130
|
-
const parameters = properties?.parameters;
|
|
131
|
-
const description = (properties?.description ?? `Unknown Description`).toLowerCase();
|
|
132
|
-
const damageThreatTag = parameters?.damage_threat ?? null;
|
|
133
|
-
const tornadoThreatTag = parameters?.tornado_detection ?? null;
|
|
134
|
-
if (!betterParsing) { return eventName }
|
|
135
|
-
for (const eventGroup of defEventTable) {
|
|
136
|
-
const [baseEvent, conditions] = Object.entries(eventGroup)[0] as [string, Record<string, types.EnhancedEventCondition>];
|
|
137
|
-
if (eventName === baseEvent) {
|
|
138
|
-
for (const [specificEvent, condition] of Object.entries(conditions)) {
|
|
139
|
-
let conditionMet = false;
|
|
140
|
-
if (condition.description) {
|
|
141
|
-
conditionMet = description.includes(condition.description.toLowerCase());
|
|
142
|
-
if (!conditionMet) continue;
|
|
143
|
-
}
|
|
144
|
-
if (!conditionMet && condition.condition) {
|
|
145
|
-
const tagToCheck = baseEvent.includes('Tornado') || baseEvent.includes('Special Marine Warning') ? tornadoThreatTag : damageThreatTag;
|
|
146
|
-
conditionMet = condition.condition(tagToCheck);
|
|
147
|
-
}
|
|
148
|
-
if (conditionMet) {
|
|
149
|
-
eventName = specificEvent;
|
|
150
|
-
break;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
if (baseEvent === 'Severe Thunderstorm Warning' && tornadoThreatTag === 'POSSIBLE' && !eventName.includes('(TPROB)')) {
|
|
154
|
-
eventName += ' (TPROB)';
|
|
155
|
-
}
|
|
156
|
-
break;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
return useParentEvents ? event?.properties?.event : eventName;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* @function validateEvents
|
|
164
|
-
* @description
|
|
165
|
-
* Processes an array of event objects and filters them based on
|
|
166
|
-
* global and EAS filtering settings, and
|
|
167
|
-
* other criteria such as expired or test products. Valid events
|
|
168
|
-
* trigger relevant event emitters.
|
|
169
|
-
*
|
|
170
|
-
* @static
|
|
171
|
-
* @param {unknown[]} events
|
|
172
|
-
* @returns {Promise<void>}
|
|
173
|
-
*/
|
|
174
|
-
public static async validateEvents(events: unknown[]): Promise<void> {
|
|
175
|
-
if (events.length == 0) return;
|
|
176
|
-
const filteringSettings = loader.settings?.global_settings?.filtering;
|
|
177
|
-
const easSettings = loader.settings?.global_settings?.eas_settings;
|
|
178
|
-
const globalSettings = loader.settings?.global_settings;
|
|
179
|
-
const sets = {} as Record<string, Set<string>>;
|
|
180
|
-
const bools = {} as Record<string, boolean>;
|
|
181
|
-
const megered = {...filteringSettings, ...easSettings, ...globalSettings };
|
|
182
|
-
for (const key in megered) {
|
|
183
|
-
const setting = megered[key];
|
|
184
|
-
if (Array.isArray(setting)) { sets[key] = new Set(setting.map(item => item.toLowerCase())); }
|
|
185
|
-
if (typeof setting === 'boolean') { bools[key] = setting; }
|
|
186
|
-
}
|
|
187
|
-
const filtered = events.filter((event: types.EventCompiled) => {
|
|
188
|
-
const originalEvent = this.buildDefaultSignature(event);
|
|
189
|
-
const props = originalEvent?.properties;
|
|
190
|
-
const ugcs = props?.geocode?.UGC ?? [];
|
|
191
|
-
const { details, ...properties } = originalEvent.properties;
|
|
192
|
-
originalEvent.properties.parent = originalEvent.properties.event;
|
|
193
|
-
originalEvent.properties.event = this.betterParsedEventName(originalEvent, bools?.better_event_parsing, bools?.parent_events_only);
|
|
194
|
-
originalEvent.properties.hash = loader.packages.crypto.createHash('md5').update(JSON.stringify(properties)).digest('hex');
|
|
195
|
-
if (originalEvent.properties.is_test == true) {
|
|
196
|
-
loader.cache.events.emit(`onTest`, originalEvent);
|
|
197
|
-
if (bools?.ignore_test_products) {
|
|
198
|
-
return false;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
if (originalEvent.properties.is_cancelled == true) {
|
|
202
|
-
loader.cache.events.emit(`onExpired`, originalEvent);
|
|
203
|
-
if (bools?.check_expired) {
|
|
204
|
-
return false;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
loader.cache.events.emit(`on${originalEvent.properties.event.replace(/\s+/g, '')}`, originalEvent);
|
|
208
|
-
for (const key in sets) {
|
|
209
|
-
const setting = sets[key];
|
|
210
|
-
if (key === 'events' && setting.size > 0 && !setting.has(originalEvent.properties.event.toLowerCase())) {
|
|
211
|
-
loader.cache.events.emit(`onFilteredEvent`, originalEvent); return false
|
|
212
|
-
}
|
|
213
|
-
if (key === 'ignored_events' && setting.size > 0 && setting.has(originalEvent.properties.event.toLowerCase())) {
|
|
214
|
-
loader.cache.events.emit(`onIgnoredEvent`, originalEvent); return false
|
|
215
|
-
}
|
|
216
|
-
if (key === 'filtered_icao' && setting.size > 0 && props.sender_icao != null && !setting.has(props.sender_icao.toLowerCase())) {
|
|
217
|
-
loader.cache.events.emit(`onFilteredICAO`, originalEvent); return false
|
|
218
|
-
}
|
|
219
|
-
if (key === 'ignored_icao' && setting.size > 0 && props.sender_icao != null && setting.has(props.sender_icao.toLowerCase())) {
|
|
220
|
-
loader.cache.events.emit(`onIgnoredICAO`, originalEvent); return false
|
|
221
|
-
}
|
|
222
|
-
if (key === 'ugc_filter' && setting.size > 0 && ugcs.length > 0 && !ugcs.some((ugc: string) => setting.has(ugc.toLowerCase()))) {
|
|
223
|
-
loader.cache.events.emit(`onFilteredUGC`, originalEvent); return false
|
|
224
|
-
}
|
|
225
|
-
if (key === 'state_filter' && setting.size > 0 && ugcs.length > 0 && !ugcs.some((ugc: string) => setting.has(ugc.substring(0, 2).toLowerCase()))) {
|
|
226
|
-
loader.cache.events.emit(`onFilteredState`, originalEvent); return false
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
return true;
|
|
230
|
-
})
|
|
231
|
-
for (const event of filtered as types.EventCompiled[]) {
|
|
232
|
-
if (!loader.settings.global_settings.ignore_geometry_parsing) {
|
|
233
|
-
const geometry = await this.getEventGeometry(event.properties.geocode.generated, {
|
|
234
|
-
zones: event.properties.geocode != null ? event.properties.geocode.UGC : null
|
|
235
|
-
})
|
|
236
|
-
event.geometry = geometry;
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
if (filtered.length > 0) { loader.cache.events.emit(`onEvents`, filtered); }
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* @function getHeader
|
|
244
|
-
* @description
|
|
245
|
-
* Constructs a standardized alert header string using provided
|
|
246
|
-
* stanza attributes, event properties, and optional VTEC data.
|
|
247
|
-
*
|
|
248
|
-
* @static
|
|
249
|
-
* @param {types.StanzaAttributes} attributes
|
|
250
|
-
* @param {types.EventProperties} [properties]
|
|
251
|
-
* @param {types.PVtecEntry} [pVtec]
|
|
252
|
-
* @returns {string}
|
|
253
|
-
*/
|
|
254
|
-
public static getHeader(attributes: types.StanzaAttributes, properties?: types.EventProperties, pVtec?: types.PVtecEntry): string {
|
|
255
|
-
const parent = `ATSX`
|
|
256
|
-
const alertType = attributes?.awipsType?.type ?? attributes?.getAwip?.prefix ?? `XX`;
|
|
257
|
-
const ugc = properties?.geocode?.UGC != null ? properties?.geocode?.UGC.join(`-`) : `000000`;
|
|
258
|
-
const status = pVtec?.status ?? 'Issued';
|
|
259
|
-
const issued = properties?.issued != null ? new Date(properties?.issued)?.toISOString().replace(/[-:]/g, '').split('.')[0] : new Date().toISOString().replace(/[-:]/g, '').split('.')[0];
|
|
260
|
-
const sender = properties?.sender_icao ?? `XXXX`;
|
|
261
|
-
const header = `ZCZC-${parent}-${alertType}-${ugc}-${status}-${issued}-${sender}-`;
|
|
262
|
-
return header
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/**
|
|
266
|
-
* @function eventHandler
|
|
267
|
-
* @description
|
|
268
|
-
* Routes a validated stanza object to the appropriate alert handler
|
|
269
|
-
* based on its type flags: API, CAP, pVTEC (Primary VTEC), UGC, or plain text.
|
|
270
|
-
*
|
|
271
|
-
* @static
|
|
272
|
-
* @param {types.StanzaCompiled} metadata
|
|
273
|
-
* @returns {void}
|
|
274
|
-
*/
|
|
275
|
-
public static eventHandler(metadata: types.StanzaCompiled): Promise<void> {
|
|
276
|
-
const settings = loader.settings as types.ClientSettingsTypes;
|
|
277
|
-
const preferences = settings.noaa_weather_wire_service_settings.preferences;
|
|
278
|
-
if (metadata.isApi) return APIAlerts.event(metadata)
|
|
279
|
-
if (metadata.isCap) return CAPAlerts.event(metadata)
|
|
280
|
-
if (!preferences.disable_vtec && !metadata.isCap && metadata.isPVtec && metadata.isUGC) return VTECAlerts.event(metadata);
|
|
281
|
-
if (!preferences.disable_ugc && !metadata.isCap && !metadata.isPVtec && metadata.isUGC) return UGCAlerts.event(metadata);
|
|
282
|
-
if (!preferences.disable_text && !metadata.isCap && !metadata.isPVtec && !metadata.isUGC) return TextAlerts.event(metadata);
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
/**
|
|
287
|
-
* @function getICAO
|
|
288
|
-
* @description
|
|
289
|
-
* Determines the ICAO code and corresponding name for an event.
|
|
290
|
-
* Priority is given to the VTEC tracking code, then the attributes' `cccc` property,
|
|
291
|
-
* and finally the WMO code if available. Returns null if none are found.
|
|
292
|
-
*
|
|
293
|
-
* @private
|
|
294
|
-
* @static
|
|
295
|
-
* @param {types.PVtecEntry | null} pVtec
|
|
296
|
-
* @param {Record<string, string>} metadata
|
|
297
|
-
* @param {RegExpMatchArray | string | null} WMO
|
|
298
|
-
* @returns {{ icao: string; name: string }}
|
|
299
|
-
*/
|
|
300
|
-
private static getICAO(pVtec: types.PVtecEntry, metadata: types.DefaultAttributesType, WMO: RegExpMatchArray | string | null): { icao: string; name: string } {
|
|
301
|
-
const icao = pVtec != null ? pVtec?.tracking.split(`-`)[0] : (metadata.attributes?.cccc || (WMO != null ? (Array.isArray(WMO) ? WMO[0] : WMO) : null));
|
|
302
|
-
const name = loader.definitions.ICAO?.[icao] ?? null;
|
|
303
|
-
return { icao, name };
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
/**
|
|
307
|
-
* @function getCorrectIssuedDate
|
|
308
|
-
* @description
|
|
309
|
-
* Determines the issued date for an event based on the provided attributes.
|
|
310
|
-
* Falls back to the current date and time if no valid issue date is available.
|
|
311
|
-
*
|
|
312
|
-
* @private
|
|
313
|
-
* @static
|
|
314
|
-
* @param {Record<string, string>} metadata
|
|
315
|
-
* @returns {string}
|
|
316
|
-
*/
|
|
317
|
-
private static getCorrectIssuedDate(metadata: types.DefaultAttributesType): string {
|
|
318
|
-
const time = metadata.attributes.issue != null ?
|
|
319
|
-
new Date(metadata.attributes.issue).toISOString() : (metadata.attributes?.issue != null ?
|
|
320
|
-
new Date(metadata.attributes.issue).toISOString() :
|
|
321
|
-
new Date().toISOString());
|
|
322
|
-
return time;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
/**
|
|
326
|
-
* @function getCorrectExpiryDate
|
|
327
|
-
* @description
|
|
328
|
-
* Determines the most appropriate expiry date for an event using VTEC or UGC data.
|
|
329
|
-
* Falls back to one hour from the current time if no valid expiry is available.
|
|
330
|
-
*
|
|
331
|
-
* @private
|
|
332
|
-
* @static
|
|
333
|
-
* @param {types.PVtecEntry} pVtec
|
|
334
|
-
* @param {types.UGCEntry} ugc
|
|
335
|
-
* @returns {string}
|
|
336
|
-
*/
|
|
337
|
-
private static getCorrectExpiryDate(pVtec: types.PVtecEntry, ugc: types.UGCEntry): string | Date {
|
|
338
|
-
const time = pVtec?.expires && !isNaN(new Date(pVtec.expires).getTime()) ?
|
|
339
|
-
new Date(pVtec.expires).toISOString() : (ugc?.expiry != null ? new Date(ugc.expiry).toISOString() : new Date(new Date().getTime() + 1 * 60 * 60 * 1000))
|
|
340
|
-
if (isNaN(new Date(time).getTime())) {
|
|
341
|
-
return `Until Further Notice`
|
|
342
|
-
}
|
|
343
|
-
return time;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
/**
|
|
347
|
-
* @function buildDefaultSignature
|
|
348
|
-
* @description
|
|
349
|
-
* Populates default properties for an event object, including action type flags,
|
|
350
|
-
* tags, and status updates. Determines if the event is issued, updated, or cancelled
|
|
351
|
-
* based on correlations, description content, VTEC codes, and expiration time.
|
|
352
|
-
*
|
|
353
|
-
* @private
|
|
354
|
-
* @static
|
|
355
|
-
* @param {any} event
|
|
356
|
-
* @returns {types.EventCompiled}
|
|
357
|
-
*/
|
|
358
|
-
private static buildDefaultSignature(event: types.EventCompiled): types.EventCompiled {
|
|
359
|
-
const props = event.properties ?? {};
|
|
360
|
-
const statusCorrelation = loader.definitions.correlations.find((c: { type: string }) => c.type === props.action_type);
|
|
361
|
-
const defEventTags = loader.definitions.tags;
|
|
362
|
-
const tags = Object.entries(defEventTags).filter(([key]) => props?.description?.toLowerCase().includes(key.toLowerCase())).map(([, value]) => value)
|
|
363
|
-
props.tags = tags.length > 0 ? tags : [];
|
|
364
|
-
if (statusCorrelation) {
|
|
365
|
-
props.action_type = statusCorrelation.forward ?? props.action_type;
|
|
366
|
-
props.is_updated = !!statusCorrelation.update; props.is_issued = !!statusCorrelation.new;
|
|
367
|
-
props.is_cancelled = !!statusCorrelation.cancel;
|
|
368
|
-
} else {
|
|
369
|
-
props.is_issued = true
|
|
370
|
-
}
|
|
371
|
-
if (props.description) {
|
|
372
|
-
const detectedPhrase = loader.definitions.cancelSignatures.find(sig => props.description.toLowerCase().includes(sig.toLowerCase()));
|
|
373
|
-
if (detectedPhrase) {
|
|
374
|
-
props.is_cancelled = true;
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
if (props.details?.pvtec) {
|
|
378
|
-
const getType = props.details.pvtec.split(`.`)[0]?.replace(`/`, ``)
|
|
379
|
-
const isTestProduct = loader.definitions.productTypes[getType] == `Test Product`
|
|
380
|
-
const isTestSig = [`This is a test message`, `THIS_MESSAGE_IS_FOR_TEST_PURPOSES_ONLY`]
|
|
381
|
-
if (isTestProduct || isTestSig.some(sig => props?.description?.toLowerCase().includes(sig.toLowerCase()) || props?.instruction?.toLowerCase().includes(sig.toLowerCase()))) {
|
|
382
|
-
props.is_test = true
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
if (new Date(props?.expires).getTime() < new Date().getTime()) {
|
|
386
|
-
props.is_cancelled = true
|
|
387
|
-
}
|
|
388
|
-
return event;
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
export default EventParser
|
package/src/@parsers/hvtec.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
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
|
-
|
|
17
|
-
export class HVtecParser {
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* @function HVtecExtractor
|
|
21
|
-
* @description
|
|
22
|
-
* Extracts VTEC entries from a raw NWWS message string and returns
|
|
23
|
-
* structured objects containing type, tracking, event, status,
|
|
24
|
-
* WMO identifiers, and expiry date.
|
|
25
|
-
*
|
|
26
|
-
* @static
|
|
27
|
-
* @param {string} message
|
|
28
|
-
* @returns {Promise<types.HtecEntry[] | null>}
|
|
29
|
-
*/
|
|
30
|
-
public static async HVtecExtractor(message: string): Promise<types.HVtecEntry[] | null> {
|
|
31
|
-
const matches = message.match(loader.definitions.regular_expressions.hvtec);
|
|
32
|
-
if (!matches || matches.length !== 1) return null;
|
|
33
|
-
const hvtec = matches[0];
|
|
34
|
-
const parts = hvtec.split('.');
|
|
35
|
-
if (parts.length < 7) return null;
|
|
36
|
-
const hvtecs: types.HVtecEntry[] = [{
|
|
37
|
-
severity: loader.definitions.severity[parts[1]],
|
|
38
|
-
cause: loader.definitions.causes[parts[2]],
|
|
39
|
-
record: loader.definitions.records[parts[6]],
|
|
40
|
-
raw: hvtec,
|
|
41
|
-
}];
|
|
42
|
-
return hvtecs;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export default HVtecParser;
|
package/src/@parsers/pvtec.ts
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
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
|
-
|
|
17
|
-
export class PVtecParser {
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* @function pVtecExtractor
|
|
21
|
-
* @description
|
|
22
|
-
* Extracts VTEC entries from a raw NWWS message string and returns
|
|
23
|
-
* structured objects containing type, tracking, event, status,
|
|
24
|
-
* WMO identifiers, and expiry date.
|
|
25
|
-
*
|
|
26
|
-
* @static
|
|
27
|
-
* @param {string} message
|
|
28
|
-
* @returns {Promise<types.VtecEntry[] | null>}
|
|
29
|
-
*/
|
|
30
|
-
public static async pVtecExtractor(message: string): Promise<types.PVtecEntry[] | null> {
|
|
31
|
-
const matches = message.match(loader.definitions.regular_expressions.pvtec) ?? [];
|
|
32
|
-
const pVtecs: types.PVtecEntry[] = [];
|
|
33
|
-
for (const pvtec of matches) {
|
|
34
|
-
const parts = pvtec.split('.');
|
|
35
|
-
if (parts.length < 7) continue;
|
|
36
|
-
const dates = parts[6].split('-');
|
|
37
|
-
pVtecs.push({
|
|
38
|
-
raw: pvtec,
|
|
39
|
-
type: loader.definitions.productTypes[parts[0]],
|
|
40
|
-
tracking: `${parts[2]}-${parts[3]}-${parts[4]}-${parts[5]}`,
|
|
41
|
-
event: `${loader.definitions.events[parts[3]]} ${loader.definitions.actions[parts[4]]}`,
|
|
42
|
-
status: loader.definitions.status[parts[1]],
|
|
43
|
-
wmo: message.match(loader.definitions.regular_expressions.wmo)?.[0] || null,
|
|
44
|
-
expires: this.parseExpiryDate(dates),
|
|
45
|
-
isKWNS: (parts[4] == `A` || parts[4] == `Y`) && (parts[3] == `TO` || parts[3] == `SV`) ? true : false,
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
return pVtecs.length > 0 ? pVtecs : null;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* @function parseExpiryDate
|
|
53
|
-
* @description
|
|
54
|
-
* Converts a NWWS VTEC/expiry timestamp string into a formatted local ISO date string
|
|
55
|
-
* with an Eastern Time offset (-04:00). Returns `Invalid Date Format` if the input
|
|
56
|
-
* is `000000T0000Z`.
|
|
57
|
-
*
|
|
58
|
-
* @private
|
|
59
|
-
* @static
|
|
60
|
-
* @param {string[]} args
|
|
61
|
-
* @returns {string}
|
|
62
|
-
*/
|
|
63
|
-
private static parseExpiryDate(args: String[]): string {
|
|
64
|
-
if (args[1] == `000000T0000Z`) return `Invalid Date Format`;
|
|
65
|
-
const expires = `${new Date().getFullYear().toString().substring(0, 2)}${args[1].substring(0, 2)}-${args[1].substring(2, 4)}-${args[1].substring(4, 6)}T${args[1].substring(7, 9)}:${args[1].substring(9, 11)}:00`;
|
|
66
|
-
const local = new Date(new Date(expires).getTime() - 4 * 60 * 60000);
|
|
67
|
-
const pad = (n: number) => n.toString().padStart(2, '0');
|
|
68
|
-
return `${local.getFullYear()}-${pad(local.getMonth() + 1)}-${pad(local.getDate())}T${pad(local.getHours())}:${pad(local.getMinutes())}:00.000-04:00`;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export default PVtecParser;
|
package/src/@parsers/stanza.ts
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
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
|
-
|
|
17
|
-
export class StanzaParser {
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* @function validate
|
|
21
|
-
* @description
|
|
22
|
-
* Validates and parses a stanza message, extracting its attributes and metadata.
|
|
23
|
-
* Handles both raw message strings (for debug/testing) and actual stanza objects.
|
|
24
|
-
* Determines whether the message is a CAP alert, contains VTEC codes, or contains UGCs,
|
|
25
|
-
* and identifies the AWIPS product type and prefix.
|
|
26
|
-
*
|
|
27
|
-
* @static
|
|
28
|
-
* @param {any} stanza
|
|
29
|
-
* @param {boolean | types.StanzaAttributes} [isDebug=false]
|
|
30
|
-
* @returns {{
|
|
31
|
-
* message: string;
|
|
32
|
-
* attributes: types.StanzaAttributes;
|
|
33
|
-
* isCap: boolean,
|
|
34
|
-
* isPVtec: boolean;
|
|
35
|
-
* isCapDescription: boolean;
|
|
36
|
-
* awipsType: Record<string, string>;
|
|
37
|
-
* isApi: boolean;
|
|
38
|
-
* ignore: boolean;
|
|
39
|
-
* isUGC?: boolean;
|
|
40
|
-
* }}
|
|
41
|
-
*/
|
|
42
|
-
public static validate(stanza: any, isDebug: boolean | types.StanzaAttributes = false): { message: string; attributes: types.StanzaAttributes; isCap: any; isPVtec: boolean; isCapDescription: any; awipsType: any; isApi: boolean; ignore: boolean; isUGC?: boolean; } {
|
|
43
|
-
if (isDebug !== false) {
|
|
44
|
-
const vTypes = isDebug as types.StanzaAttributes;
|
|
45
|
-
const message = stanza;
|
|
46
|
-
const attributes = vTypes;
|
|
47
|
-
const isCap = vTypes.isCap ?? message.includes(`<?xml`);
|
|
48
|
-
const isCapDescription = message.includes(`<areaDesc>`);
|
|
49
|
-
const isPVtec = message.match(loader.definitions.regular_expressions.pvtec) != null;
|
|
50
|
-
const isUGC = message.match(loader.definitions.regular_expressions.ugc1) != null;
|
|
51
|
-
const awipsType = this.getType(attributes);
|
|
52
|
-
return { message, attributes, isCap, isPVtec, isUGC, isCapDescription, awipsType: awipsType, isApi: false, ignore: false };
|
|
53
|
-
}
|
|
54
|
-
if (stanza.is(`message`)) {
|
|
55
|
-
let cb = stanza.getChild(`x`)
|
|
56
|
-
if (cb && cb.children) {
|
|
57
|
-
let message = unescape(cb.children[0]);
|
|
58
|
-
let attributes = cb.attrs
|
|
59
|
-
if (attributes.awipsid && attributes.awipsid.length > 1) {
|
|
60
|
-
const isCap = message.includes(`<?xml`);
|
|
61
|
-
const isCapDescription = message.includes(`<areaDesc>`);
|
|
62
|
-
const isPVtec = message.match(loader.definitions.regular_expressions.pvtec) != null;
|
|
63
|
-
const isUGC = message.match(loader.definitions.regular_expressions.ugc1) != null;
|
|
64
|
-
const awipsType = this.getType(attributes);
|
|
65
|
-
return { message, attributes, isCap, isPVtec, isUGC, isCapDescription, awipsType: awipsType, isApi: false, ignore: false };
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
return { message: null, attributes: null, isApi: null, isCap: null, isPVtec: null, isUGC: null, isCapDescription: null, awipsType: null, ignore: true };
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* @function getType
|
|
74
|
-
* @description
|
|
75
|
-
* Determines the AWIPS product type and prefix from a stanza's attributes.
|
|
76
|
-
* Returns a default type of 'XX' if the attributes are missing or the AWIPS ID
|
|
77
|
-
* does not match any known definitions.
|
|
78
|
-
*
|
|
79
|
-
* @private
|
|
80
|
-
* @static
|
|
81
|
-
* @param {unknown} attributes
|
|
82
|
-
* @returns {Record<string, string>}
|
|
83
|
-
*/
|
|
84
|
-
private static getType(attributes: unknown): Record<string, string> {
|
|
85
|
-
const attrs = attributes as types.StanzaAttributesType | undefined;
|
|
86
|
-
if (!attrs?.awipsid) return { type: 'XX', prefix: 'XX' };
|
|
87
|
-
const awipsDefs = loader.definitions.awips as Record<string, string>;
|
|
88
|
-
for (const [prefix, type] of Object.entries(awipsDefs)) {
|
|
89
|
-
if (attrs.awipsid.startsWith(prefix)) {
|
|
90
|
-
return { type, prefix };
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
return { type: 'XX', prefix: 'XX' };
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export default StanzaParser;
|