@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.
Files changed (136) hide show
  1. package/README.md +248 -187
  2. package/dist/cjs/index.cjs +12218 -3139
  3. package/dist/esm/index.mjs +12401 -3327
  4. package/dist/index.d.mts +235 -0
  5. package/dist/index.d.ts +235 -0
  6. package/package.json +6 -4
  7. package/src/@building/building.clean.ts +30 -0
  8. package/src/@building/building.create.ts +42 -0
  9. package/src/@building/building.enhance.ts +56 -0
  10. package/src/@building/building.geometry.ts +48 -0
  11. package/src/@building/building.headers.ts +37 -0
  12. package/src/@building/building.office.ts +43 -0
  13. package/src/@building/building.polygon.ts +71 -0
  14. package/src/@building/building.properties.ts +89 -0
  15. package/src/@building/building.signature.ts +61 -0
  16. package/src/@building/building.tags.ts +24 -0
  17. package/src/@building/building.tracking.ts +68 -0
  18. package/src/@building/building.validate.ts +147 -0
  19. package/src/@core/core.getEvents.ts +25 -0
  20. package/src/@core/core.getNodes.ts +25 -0
  21. package/src/@core/core.getRandomEvent.ts +25 -0
  22. package/src/@core/core.getVersion.ts +25 -0
  23. package/src/@core/core.listener.ts +24 -0
  24. package/src/@core/core.setNode.ts +81 -0
  25. package/src/@core/core.start.ts +59 -0
  26. package/src/@core/core.stop.ts +32 -0
  27. package/src/@dictionaries/dictionaries.betterEventNames.ts +89 -0
  28. package/src/@dictionaries/dictionaries.eventActions.ts +28 -0
  29. package/src/@dictionaries/{awips.ts → dictionaries.eventAwipAbreviations.ts} +13 -7
  30. package/src/@dictionaries/dictionaries.eventCancelMessages.ts +30 -0
  31. package/src/@dictionaries/dictionaries.eventCauses.ts +36 -0
  32. package/src/@dictionaries/dictionaries.eventProducts.ts +25 -0
  33. package/src/@dictionaries/dictionaries.eventRecords.ts +25 -0
  34. package/src/@dictionaries/dictionaries.eventSeverity.ts +27 -0
  35. package/src/@dictionaries/dictionaries.eventStatus.ts +31 -0
  36. package/src/@dictionaries/{signatures.ts → dictionaries.eventTags.ts} +13 -68
  37. package/src/@dictionaries/dictionaries.eventTypes.ts +82 -0
  38. package/src/@dictionaries/dictionaries.eventsOffshore.ts +31 -0
  39. package/src/@dictionaries/dictionaries.hailStrings.ts +33 -0
  40. package/src/@dictionaries/{icao.ts → dictionaries.officeICAOs.ts} +13 -6
  41. package/src/@dictionaries/dictionaries.regExp.ts +28 -0
  42. package/src/@dictionaries/dictionaries.shapefileLinks.ts +36 -0
  43. package/src/@dictionaries/dictionaries.statusCorrelationText.ts +40 -0
  44. package/src/@dictionaries/dictionaries.testSignatures.ts +24 -0
  45. package/src/@dictionaries/dictionaries.transcribedMessageReplacements.ts +68 -0
  46. package/src/@events/events.api.ts +113 -0
  47. package/src/@events/events.text.ts +79 -0
  48. package/src/@events/events.ugc.ts +83 -0
  49. package/src/@events/events.vtec.ts +87 -0
  50. package/src/@manager/manager.mkEvent.ts +95 -0
  51. package/src/@manager/manager.rmEvent.ts +46 -0
  52. package/src/@manager/manager.setHash.ts +37 -0
  53. package/src/@manager/manager.updateNodes.ts +55 -0
  54. package/src/@manager/manager.updateWebhooks.ts +50 -0
  55. package/src/@modules/@database/database.cache.ts +48 -0
  56. package/src/@modules/@database/database.init.ts +45 -0
  57. package/src/@modules/@database/database.shapefiles.ts +96 -0
  58. package/src/@modules/@database/database.stanza.ts +48 -0
  59. package/src/@modules/@eas/eas.getFloatPCM16.ts +29 -0
  60. package/src/@modules/@eas/eas.getMergedPCM16.ts +32 -0
  61. package/src/@modules/@eas/eas.getPCM16.ts +52 -0
  62. package/src/@modules/@eas/eas.getPCMToFloat.ts +26 -0
  63. package/src/@modules/@eas/eas.getSampledPCM16.ts +36 -0
  64. package/src/@modules/@eas/eas.getWavPCM16.ts +52 -0
  65. package/src/@modules/@eas/eas.setAFSK.ts +52 -0
  66. package/src/@modules/@eas/eas.setAsciiToBits.ts +32 -0
  67. package/src/@modules/@eas/eas.setAttentionTone.ts +40 -0
  68. package/src/@modules/@eas/eas.setEasTone.ts +137 -0
  69. package/src/@modules/@eas/eas.setNoise.ts +31 -0
  70. package/src/@modules/@eas/eas.setRadioEffect.ts +49 -0
  71. package/src/@modules/@eas/eas.setSameHeader.ts +45 -0
  72. package/src/@modules/@stanza/stanza.getAwipsType.ts +46 -0
  73. package/src/@modules/@stanza/stanza.validate.ts +50 -0
  74. package/src/@modules/@utilities/utilities.createHttp.ts +85 -0
  75. package/src/@modules/@utilities/utilities.createWebhook.ts +100 -0
  76. package/src/@modules/@utilities/utilities.getFormattedTime.ts +43 -0
  77. package/src/@modules/@utilities/utilities.getSettings.ts +25 -0
  78. package/src/@modules/@utilities/utilities.getShapeNearestPoint.ts +114 -0
  79. package/src/@modules/@utilities/utilities.setCronSchedule.ts +65 -0
  80. package/src/@modules/@utilities/utilities.setEventEmit.ts +41 -0
  81. package/src/@modules/@utilities/utilities.setListener.ts +30 -0
  82. package/src/@modules/@utilities/utilities.setSettings.ts +42 -0
  83. package/src/@modules/@utilities/utilities.setSleep.ts +33 -0
  84. package/src/@modules/@utilities/utilities.setTimeoutAction.ts +59 -0
  85. package/src/@modules/@utilities/utilities.setWarning.ts +34 -0
  86. package/src/@modules/@xmpp/xmpp.xDeploy.ts +58 -0
  87. package/src/@modules/@xmpp/xmpp.xError.ts +29 -0
  88. package/src/@modules/@xmpp/xmpp.xOffline.ts +38 -0
  89. package/src/@modules/@xmpp/xmpp.xOnline.ts +45 -0
  90. package/src/@modules/@xmpp/xmpp.xReconnect.ts +64 -0
  91. package/src/@modules/@xmpp/xmpp.xStanza.ts +63 -0
  92. package/src/@parsers/@hvtec/hvtec.extract.ts +40 -0
  93. package/src/@parsers/@pvtec/pvtec.expires.ts +26 -0
  94. package/src/@parsers/@pvtec/pvtec.extract.ts +49 -0
  95. package/src/@parsers/@text/text.getDescriptionFromProduct.ts +53 -0
  96. package/src/@parsers/@text/text.getPolygonFromProduct.ts +32 -0
  97. package/src/@parsers/@text/text.getTextFromProduct.ts +43 -0
  98. package/src/@parsers/@text/text.getXML.ts +61 -0
  99. package/src/@parsers/@ugc/ugc.coordinates.ts +110 -0
  100. package/src/@parsers/@ugc/ugc.expiry.ts +32 -0
  101. package/src/@parsers/@ugc/ugc.extract.ts +37 -0
  102. package/src/@parsers/@ugc/ugc.header.ts +30 -0
  103. package/src/@parsers/@ugc/ugc.locations.ts +29 -0
  104. package/src/@parsers/@ugc/ugc.zones.ts +52 -0
  105. package/src/@types/type.event.ts +67 -0
  106. package/src/@types/type.properties.ts +75 -0
  107. package/src/@types/types.attributes.ts +28 -0
  108. package/src/@types/types.compiled.ts +35 -0
  109. package/src/@types/types.hash.ts +24 -0
  110. package/src/@types/types.hvtec.ts +25 -0
  111. package/src/@types/types.pvtec.ts +30 -0
  112. package/src/@types/types.settings.ts +76 -0
  113. package/src/@types/types.stanza.ts +37 -0
  114. package/src/@types/types.ugc.ts +24 -0
  115. package/src/@types/types.webhook.ts +26 -0
  116. package/src/bootstrap.ts +85 -163
  117. package/src/index.ts +47 -216
  118. package/test.js +78 -51
  119. package/tsup.config.ts +1 -0
  120. package/src/@dictionaries/events.ts +0 -168
  121. package/src/@parsers/@events/api.ts +0 -146
  122. package/src/@parsers/@events/cap.ts +0 -123
  123. package/src/@parsers/@events/text.ts +0 -104
  124. package/src/@parsers/@events/ugc.ts +0 -107
  125. package/src/@parsers/@events/vtec.ts +0 -76
  126. package/src/@parsers/events.ts +0 -392
  127. package/src/@parsers/hvtec.ts +0 -46
  128. package/src/@parsers/pvtec.ts +0 -72
  129. package/src/@parsers/stanza.ts +0 -97
  130. package/src/@parsers/text.ts +0 -165
  131. package/src/@parsers/ugc.ts +0 -247
  132. package/src/@submodules/database.ts +0 -201
  133. package/src/@submodules/eas.ts +0 -490
  134. package/src/@submodules/utils.ts +0 -191
  135. package/src/@submodules/xmpp.ts +0 -142
  136. package/src/types.ts +0 -259
@@ -0,0 +1,45 @@
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
+
22
+ import { setAFSK } from "./eas.setAFSK";
23
+ import { setAsciiToBits } from "./eas.setAsciiToBits";
24
+ import { getMergedPCM16 } from "./eas.getMergedPCM16";
25
+
26
+ export const setSameHeader = (vtec: string, repeats: number, sampleRate: number = 8000, options: {preMarkSec?: number, gapSec?: number} = {}): Int16Array => {
27
+ const preMarkSec = options.preMarkSec ?? 0.3;
28
+ const gapSec = options.gapSec ?? 0.1;
29
+ const bursts = [];
30
+ const gap = new Int16Array(Math.floor(gapSec * sampleRate));
31
+ for (let i = 0; i < repeats; i++) {
32
+ const bodyBits = setAsciiToBits(vtec);
33
+ const body = setAFSK(bodyBits, sampleRate);
34
+ const extendedBodyDuration = Math.round(preMarkSec * sampleRate);
35
+ const extendedBody = new Int16Array(extendedBodyDuration + gap.length);
36
+ for (let j = 0; j < extendedBodyDuration; j++) {
37
+ extendedBody[j] = Math.round(body[j % body.length] * 0.2);
38
+ }
39
+ extendedBody.set(gap, extendedBodyDuration);
40
+ bursts.push(extendedBody);
41
+ if (i !== repeats - 1) bursts.push(gap);
42
+ }
43
+ return getMergedPCM16(bursts);
44
+ }
45
+
@@ -0,0 +1,46 @@
1
+ /*
2
+ _ _ _ __ __
3
+ /\ | | | | (_) \ \ / /
4
+ / \ | |_ _ __ ___ ___ ___ _ __ | |__ ___ _ __ _ ___ \ V /
5
+ / /\ \| __| '_ ` _ \ / _ \/ __| '_ \| '_ \ / _ \ '__| |/ __| > <
6
+ / ____ \ |_| | | | | | (_) \__ \ |_) | | | | __/ | | | (__ / . \
7
+ /_/ \_\__|_| |_| |_|\___/|___/ .__/|_| |_|\___|_| |_|\___/_/ \_\
8
+ | |
9
+ |_|
10
+
11
+ Created with ♥ by the AtmosphericX Team (KiyoWx, StarflightWx, & CJ Ziegler)
12
+ Discord: https://atmosphericx-discord.scriptkitty.cafe
13
+ Ko-Fi: https://ko-fi.com/k3yomi
14
+ Documentation: http://localhost/documentation | https://atmosphericx.scriptkitty.cafe/documentation
15
+
16
+ Internal Package: @atmosx/event-product-parser
17
+
18
+ */
19
+
20
+ import { TypeAttributes } from "../../@types/types.attributes"
21
+ import { eventAwipAbreviations } from "../../@dictionaries/dictionaries.eventAwipAbreviations"
22
+
23
+ interface ValidateOptions {
24
+ attributes: TypeAttributes
25
+ }
26
+
27
+ interface ValidiateResponse {
28
+ type: string
29
+ prefix: string
30
+ }
31
+
32
+ export const getAwipsType = (options: ValidateOptions): ValidiateResponse => {
33
+ const attributes = options.attributes;
34
+ if (!attributes.awipsid) {
35
+ return {
36
+ type: null,
37
+ prefix: null
38
+ }
39
+ }
40
+ for (const [prefix, type] of Object.entries(eventAwipAbreviations)) {
41
+ if (attributes.awipsid.startsWith(prefix)) {
42
+ return { type, prefix }
43
+ }
44
+ }
45
+ return { type: null, prefix: null }
46
+ }
@@ -0,0 +1,50 @@
1
+ /*
2
+ _ _ _ __ __
3
+ /\ | | | | (_) \ \ / /
4
+ / \ | |_ _ __ ___ ___ ___ _ __ | |__ ___ _ __ _ ___ \ V /
5
+ / /\ \| __| '_ ` _ \ / _ \/ __| '_ \| '_ \ / _ \ '__| |/ __| > <
6
+ / ____ \ |_| | | | | | (_) \__ \ |_) | | | | __/ | | | (__ / . \
7
+ /_/ \_\__|_| |_| |_|\___/|___/ .__/|_| |_|\___|_| |_|\___/_/ \_\
8
+ | |
9
+ |_|
10
+
11
+ Created with ♥ by the AtmosphericX Team (KiyoWx, StarflightWx, & CJ Ziegler)
12
+ Discord: https://atmosphericx-discord.scriptkitty.cafe
13
+ Ko-Fi: https://ko-fi.com/k3yomi
14
+ Documentation: http://localhost/documentation | https://atmosphericx.scriptkitty.cafe/documentation
15
+
16
+ Internal Package: @atmosx/event-product-parser
17
+
18
+ */
19
+
20
+ import { TypeStanza } from "../../@types/types.stanza"
21
+ import { TypeStanzaCompiled } from "../../@types/types.compiled"
22
+ import { regExp } from "../../@dictionaries/dictionaries.regExp"
23
+ import { getAwipsType } from "./stanza.getAwipsType"
24
+
25
+ interface ValidateOptions {
26
+ stanza: TypeStanza
27
+ }
28
+
29
+ export const validate = (options: ValidateOptions): TypeStanzaCompiled => {
30
+ if (options.stanza.is(`message`)) {
31
+ const cb = options.stanza.getChild(`x`) as TypeStanza;
32
+ if (cb && cb.children) {
33
+ const message = unescape(cb.children[0]);
34
+ const attributes = cb.attrs;
35
+ if (attributes.awipsid && attributes.awipsid.length > 1) {
36
+ const isCapEvent = message.includes(`<?xml`);
37
+ const isCapAreaDescription = message.includes(`<areaDesc>`)
38
+ const isVTEC = message.match(regExp.pvtec) != null;
39
+ const isUGC = message.match(regExp.ugc1) != null;
40
+ const getType = getAwipsType({ attributes: attributes})
41
+ if (getType.type != null) {
42
+ return {
43
+ message, attributes, isCapEvent, isVTEC, isUGC, isCapAreaDescription, isIgnored: false, isNWWS: true, getType
44
+ }
45
+ }
46
+ }
47
+ }
48
+ }
49
+ return { isIgnored: true}
50
+ }
@@ -0,0 +1,85 @@
1
+ /*
2
+ _ _ _ __ __
3
+ /\ | | | | (_) \ \ / /
4
+ / \ | |_ _ __ ___ ___ ___ _ __ | |__ ___ _ __ _ ___ \ V /
5
+ / /\ \| __| '_ ` _ \ / _ \/ __| '_ \| '_ \ / _ \ '__| |/ __| > <
6
+ / ____ \ |_| | | | | | (_) \__ \ |_) | | | | __/ | | | (__ / . \
7
+ /_/ \_\__|_| |_| |_|\___/|___/ .__/|_| |_|\___|_| |_|\___/_/ \_\
8
+ | |
9
+ |_|
10
+
11
+ Created with ♥ by the AtmosphericX Team (KiyoWx, StarflightWx, & CJ Ziegler)
12
+ Discord: https://atmosphericx-discord.scriptkitty.cafe
13
+ Ko-Fi: https://ko-fi.com/k3yomi
14
+ Documentation: http://localhost/documentation | https://atmosphericx.scriptkitty.cafe/documentation
15
+
16
+ Internal Package: @atmosx/event-product-parser
17
+
18
+ */
19
+
20
+ import request from 'request'
21
+
22
+ interface CreateHttpOptions {
23
+ proxy?: string
24
+ url: string
25
+ headers?: any
26
+ timeout?: number
27
+ method?: `GET` | `POST` | `PUT` | `DELETE`
28
+ body?: any;
29
+ formData?: any;
30
+ }
31
+
32
+ export const createHttp = async (options: CreateHttpOptions): Promise<any> => {
33
+ return new Promise((resolve, reject) => {
34
+ const requestOptions = {
35
+ url: options.url ?? `https://api.weather.gov/alerts/active`,
36
+ headers: options.headers ?? {
37
+ "User-Agent": "AtmosphericX",
38
+ "Accept": "application/geo+json, text/plain, */*; q=0.9",
39
+ "Accept-Language": "en-US,en;q=0.9"
40
+ },
41
+ method: options.method ?? `GET`,
42
+ timeout: options.timeout ?? 10e3,
43
+ proxy: options.proxy ?? null,
44
+ maxRedirects: 1,
45
+ };
46
+ if (options.formData) {
47
+ requestOptions['formData'] = options.formData;
48
+ } else if (options.body) {
49
+ requestOptions['body'] = options.body;
50
+ }
51
+
52
+ request(requestOptions, (error, response, body) => {
53
+ if (error) {
54
+ return resolve({
55
+ error: true,
56
+ options: requestOptions,
57
+ status: -1,
58
+ message: error.message ?? `Unknown Error`
59
+ })
60
+ }
61
+ if (response.statusCode < 200 || response.statusCode >= 300) {
62
+ return resolve({
63
+ error: true,
64
+ options: requestOptions,
65
+ status: response.statusCode ?? -1,
66
+ message: `HTTP Status Code ${response.statusCode ?? `Unknown Status Code`} (${body})`
67
+ })
68
+ }
69
+ if (body == undefined || body == null) {
70
+ return resolve({
71
+ error: true,
72
+ options: requestOptions,
73
+ status: response.statusCode ?? -1,
74
+ message: `Empty Response Body`
75
+ })
76
+ }
77
+ resolve({
78
+ error: false,
79
+ options: requestOptions,
80
+ status: response.statusCode ?? -1,
81
+ message: body
82
+ })
83
+ })
84
+ })
85
+ }
@@ -0,0 +1,100 @@
1
+ /*
2
+ _ _ _ __ __
3
+ /\ | | | | (_) \ \ / /
4
+ / \ | |_ _ __ ___ ___ ___ _ __ | |__ ___ _ __ _ ___ \ V /
5
+ / /\ \| __| '_ ` _ \ / _ \/ __| '_ \| '_ \ / _ \ '__| |/ __| > <
6
+ / ____ \ |_| | | | | | (_) \__ \ |_) | | | | __/ | | | (__ / . \
7
+ /_/ \_\__|_| |_| |_|\___/|___/ .__/|_| |_|\___|_| |_|\___/_/ \_\
8
+ | |
9
+ |_|
10
+
11
+ Created with ♥ by the AtmosphericX Team (KiyoWx, StarflightWx, & CJ Ziegler)
12
+ Discord: https://atmosphericx-discord.scriptkitty.cafe
13
+ Ko-Fi: https://ko-fi.com/k3yomi
14
+ Documentation: http://localhost/documentation | https://atmosphericx.scriptkitty.cafe/documentation
15
+
16
+ Internal Package: @atmosx/event-product-parser
17
+
18
+ */
19
+
20
+ import { TypeEvent } from "../../@types/type.event";
21
+ import { setTimeoutAction } from "./utilities.setTimeoutAction"
22
+ import { createHttp } from "./utilities.createHttp"
23
+ import { TypeWebhook } from "../../@types/types.webhook";
24
+ import { getCleanedEvent } from "../../@building/building.clean";
25
+ import FormData from "form-data";
26
+
27
+ interface CreateWebhookOptions {
28
+ webhook: TypeWebhook
29
+ event: TypeEvent
30
+ }
31
+
32
+ export const createWebhook = async (options: CreateWebhookOptions): Promise<void> => {
33
+ const event = options.event.properties;
34
+ const settings = options.webhook;
35
+ let body = [
36
+ event.locations ? `**Locations**: ${event.locations.slice(0,100)}` : null,
37
+ event.issued ? `**Issued**: <t:${Math.floor(new Date(event.issued).getTime()/1000)}:R>` : null,
38
+ event.expires ? `**Expires**: <t:${Math.floor(new Date(event.expires).getTime()/1000)}:R>` : null,
39
+ (() => {
40
+ const val = event.parameters.estimated_wind_gusts ?? null
41
+ const th = event.parameters.wind_threat ?? null
42
+ const combined = [val, th].filter(Boolean).join(' ');
43
+ return combined ? `**Wind Gusts**: ${val} ${th ? `(${th})` : ''}` : null;
44
+ })(),
45
+ (() => {
46
+ const val = event.parameters.estimated_hail_size ?? null
47
+ const th = event.parameters.hail_threat ?? null
48
+ return (val || th) ? `**Hail Threat**: ${val} ${th ? `(${th})` : ''}` : null;
49
+ })(),
50
+ event.parameters.damage_threat ? `**Damage Threat**: ${event.parameters.damage_threat}` : null,
51
+ event.parameters.flood_threat ? `**Flood Threat**: ${event.parameters.flood_threat}` : null,
52
+ event.parameters.tornado_threat ? `**Tornado Threat**: ${event.parameters.tornado_threat}` : null,
53
+ (event.parameters.tags?.length > 0) ? `**Tags**: ${event.parameters.tags.join(', ')}` : null,
54
+ (() => {
55
+ const val = event.geocode?.office?.name ?? `N/A`
56
+ const th = event.geocode?.office?.office ?? null
57
+ return (val || th) ? `**Sender**: ${val} ${th ? `(${th})` : ''}` : null;
58
+ })(),
59
+ event.metadata?.tracking ? `**Tracking**: ${event.metadata.tracking}` : null,
60
+ event.metadata?.vtec?.vtec ? `**VTEC**: ${event.metadata.vtec?.vtec}` : null,
61
+ event.metadata?.filtered_proximity != null ? `**Currently in polygon (Node)**: ${event.metadata.filtered_proximity ? 'Yes' : 'No'}` : null,
62
+ (() => {
63
+ const desc = (event.description || '').split('\n').map(l => l.trim()).filter(Boolean).join('\n');
64
+ return desc ? '```' + '\n' + desc + '\n' + '```' : null;
65
+ })(),
66
+ ].filter(Boolean).join('\n');
67
+
68
+ const isLimited = setTimeoutAction({ identifier: options.webhook.webhook, interval: options.webhook.rate, max: options.webhook.rate, addTime: true })
69
+ if (isLimited.limited) { return }
70
+
71
+ if (body.length > 1900) {
72
+ body = body.substring(0, 1900) + "\n\n[Message truncated due to length]";
73
+ const blocks = (body.match(/```/g) || []).length;
74
+ if (blocks % 2 !== 0) body += "```";
75
+ }
76
+ const form = new FormData();
77
+ const embed = {
78
+ title: `${event.event} (${event.status})`,
79
+ description: body,
80
+ color: 16711680,
81
+ timestamp: new Date().toISOString(),
82
+ footer: { text: settings.title }
83
+ };
84
+ form.append("payload_json", JSON.stringify({
85
+ username: settings.title ?? "AtmosphericX",
86
+ content: settings.message ?? "",
87
+ embeds: [embed]
88
+ }));
89
+ form.append("file", Buffer.from(JSON.stringify({ type: "FeatureCollection", features: [(getCleanedEvent(options.event))] }, null, 2)), {
90
+ filename: `${event.event}_${event.status}_${event.metadata.tracking}.json`,
91
+ contentType: "application/json"
92
+ });
93
+ await createHttp({
94
+ url: settings.webhook,
95
+ timeout: 2000,
96
+ method: `POST`,
97
+ headers: form.getHeaders(),
98
+ body: form
99
+ })
100
+ }
@@ -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
+ export const getFormattedTime = (date: string): string => {
22
+ const now = Date.now();
23
+ const then = typeof date === "number" ? date : new Date(date).getTime()
24
+ const seconds = Math.floor((now - then) / 1000);
25
+ const absSeconds = Math.abs(seconds);
26
+ const isPast = seconds > 0;
27
+ const units: [string, number][] = [
28
+ ["year", 31536000], ["month", 2592000],
29
+ ["day", 86400], ["hour", 3600],
30
+ ["minute", 60], ["second", 1],
31
+ ];
32
+ for (const [name, value] of units) {
33
+ const interval = Math.floor(absSeconds / value);
34
+ if (interval >= 1) {
35
+ return isPast
36
+ ? `${interval} ${name}${interval > 1 ? "s" : ""} ago`
37
+ : `in ${interval} ${name}${interval > 1 ? "s" : ""}`;
38
+ }
39
+ }
40
+ return "just now";
41
+ }
42
+
43
+
@@ -0,0 +1,25 @@
1
+ /*
2
+ _ _ _ __ __
3
+ /\ | | | | (_) \ \ / /
4
+ / \ | |_ _ __ ___ ___ ___ _ __ | |__ ___ _ __ _ ___ \ V /
5
+ / /\ \| __| '_ ` _ \ / _ \/ __| '_ \| '_ \ / _ \ '__| |/ __| > <
6
+ / ____ \ |_| | | | | | (_) \__ \ |_) | | | | __/ | | | (__ / . \
7
+ /_/ \_\__|_| |_| |_|\___/|___/ .__/|_| |_|\___|_| |_|\___/_/ \_\
8
+ | |
9
+ |_|
10
+
11
+ Created with ♥ by the AtmosphericX Team (KiyoWx, StarflightWx, & CJ Ziegler)
12
+ Discord: https://atmosphericx-discord.scriptkitty.cafe
13
+ Ko-Fi: https://ko-fi.com/k3yomi
14
+ Documentation: http://localhost/documentation | https://atmosphericx.scriptkitty.cafe/documentation
15
+
16
+ Internal Package: @atmosx/event-product-parser
17
+
18
+ */
19
+
20
+ import { TypeSettings } from '../../@types/types.settings'
21
+ import { bootstrap } from "../../bootstrap"
22
+
23
+ export const getSettings = (): TypeSettings => {
24
+ return bootstrap.settings;
25
+ }
@@ -0,0 +1,114 @@
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
+ interface GetShapeNearestPointResponse {
21
+ proximity: boolean
22
+ point: [number, number]
23
+ distance: number | null
24
+ distanceKm?: number | null
25
+ distanceMeters?: number | null
26
+ }
27
+
28
+ export const getShapeNearestPoint = (coordinates: number[][], point: [number, number]): GetShapeNearestPointResponse => {
29
+ if (!coordinates || !point) {
30
+ return { proximity: false, point: [0, 0], distance: null }
31
+ }
32
+ const normalize = (coords: any): any[] => {
33
+ if (!Array.isArray(coords)) return [];
34
+ if (typeof coords[0] === 'number' && typeof coords[1] === 'number') return [];
35
+ if (Array.isArray(coords[0]) && typeof coords[0][0] === 'number') {
36
+ return [[coords]];
37
+ }
38
+ if (Array.isArray(coords[0]) && Array.isArray(coords[0][0]) && typeof coords[0][0][0] === 'number') {
39
+ return [coords];
40
+ }
41
+ if (Array.isArray(coords[0]) && Array.isArray(coords[0][0]) && Array.isArray(coords[0][0][0]) && typeof coords[0][0][0][0] === 'number') {
42
+ return coords;
43
+ }
44
+ return [];
45
+ }
46
+ const polys = normalize(coordinates);
47
+ if (polys.length === 0) return { proximity: false, point: [0, 0], distance: null };
48
+ const lon = point[0];
49
+ const lat = point[1];
50
+ const pointInRing = (pt: [number, number], ring: number[][]): boolean => {
51
+ let x = pt[0], y = pt[1];
52
+ let inside = false;
53
+ for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
54
+ const xi = ring[i][0], yi = ring[i][1];
55
+ const xj = ring[j][0], yj = ring[j][1];
56
+ const intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi + 0) + xi);
57
+ if (intersect) inside = !inside;
58
+ }
59
+ return inside;
60
+ };
61
+
62
+ const toRadians = (deg: number) => (deg * Math.PI) / 180;
63
+ const haversineMiles = (a: [number, number], b: [number, number]) => {
64
+ const R = 3958.8;
65
+ const dLat = toRadians(b[1] - a[1]);
66
+ const dLon = toRadians(b[0] - a[0]);
67
+ const lat1 = toRadians(a[1]);
68
+ const lat2 = toRadians(b[1]);
69
+ const sinDLat = Math.sin(dLat / 2);
70
+ const sinDLon = Math.sin(dLon / 2);
71
+ const c = 2 * Math.asin(Math.sqrt(sinDLat * sinDLat + Math.cos(lat1) * Math.cos(lat2) * sinDLon * sinDLon));
72
+ return R * c;
73
+ };
74
+ let minDistance = Infinity;
75
+ let closestPoint: [number, number] | null = null;
76
+ for (const poly of polys) {
77
+ const outer = poly[0];
78
+ const holes = poly.slice(1);
79
+ if (pointInRing(point, outer)) {
80
+ let inHole = false;
81
+ for (const hole of holes) {
82
+ if (pointInRing(point, hole)) { inHole = true; break; }
83
+ }
84
+ if (!inHole) {
85
+ return { proximity: true, point, distance: 0 };
86
+ }
87
+ }
88
+ for (const ring of poly) {
89
+ for (let i = 0; i < ring.length - 1; i++) {
90
+ const start = [ring[i][0], ring[i][1]] as [number, number];
91
+ const end = [ring[i+1][0], ring[i+1][1]] as [number, number];
92
+ const A = lon - start[0];
93
+ const B = lat - start[1];
94
+ const C = end[0] - start[0];
95
+ const D = end[1] - start[1];
96
+ const lenSq = C*C + D*D;
97
+ const t = lenSq === 0 ? 0 : Math.max(0, Math.min(1, (A*C + B*D) / lenSq));
98
+ const candidate: [number, number] = [start[0] + t*C, start[1] + t*D];
99
+ const dist = haversineMiles([lon, lat], candidate);
100
+ if (!isNaN(dist) && dist < minDistance) {
101
+ minDistance = Number(dist.toFixed(3));
102
+ closestPoint = candidate;
103
+ }
104
+ }
105
+ }
106
+ }
107
+ if (!isFinite(minDistance) || closestPoint == null) {
108
+ return { proximity: false, point: [0,0], distance: null };
109
+ }
110
+ const distanceMiles = minDistance;
111
+ const distanceKm = Number((distanceMiles * 1.609344).toFixed(3));
112
+ const distanceMeters = Math.round(distanceKm * 1000);
113
+ return { proximity: distanceMiles === 0, point: closestPoint, distance: distanceMiles, distanceKm, distanceMeters };
114
+ }
@@ -0,0 +1,65 @@
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 { TypeSettings } from "../../@types/types.settings";
22
+ import { xReconnect } from "../@xmpp/xmpp.xReconnect"
23
+ import { bootstrap } from "../../bootstrap";
24
+ import { createHttp } from "./utilities.createHttp";
25
+ import { createEvent } from "../../@building/building.create";
26
+ import { setEventEmit } from "./utilities.setEventEmit";
27
+
28
+ export const setCronSchedule = async (): Promise<void> => {
29
+ const settings = bootstrap.settings as TypeSettings;
30
+ if (settings.EnableWireService) {
31
+ if (settings.NOAAWeatherWireServiceSettings.ReconnectionSettings.Enabled) {
32
+ void xReconnect(settings.NOAAWeatherWireServiceSettings.ReconnectionSettings.ReconnectionInterval)
33
+ }
34
+ } else {
35
+ const response = await createHttp({
36
+ url: settings.NationalWeatherServiceSettings.EventsEndpoint,
37
+ headers: {
38
+ "User-Agent": "@atmosx/event-product-parser"
39
+ }
40
+ })
41
+ if (response.error) {
42
+ return setEventEmit({
43
+ event: `onServiceStatus`,
44
+ metadata: {
45
+ type: "fetch-api",
46
+ message: `Failed to fetch latest events from National Weather Service API - ${response.message}`,
47
+ data: {},
48
+ error: true
49
+ },
50
+ })
51
+ }
52
+ setEventEmit({
53
+ event: `onServiceStatus`,
54
+ metadata: {
55
+ message: `Fetched latest events from National Weather Service API`,
56
+ data: {},
57
+ type: "fetch-api",
58
+ error: false
59
+ },
60
+ })
61
+ createEvent({ message: response.message, isNWWS: false })
62
+ }
63
+ }
64
+
65
+
@@ -0,0 +1,41 @@
1
+ /*
2
+ _ _ _ __ __
3
+ /\ | | | | (_) \ \ / /
4
+ / \ | |_ _ __ ___ ___ ___ _ __ | |__ ___ _ __ _ ___ \ V /
5
+ / /\ \| __| '_ ` _ \ / _ \/ __| '_ \| '_ \ / _ \ '__| |/ __| > <
6
+ / ____ \ |_| | | | | | (_) \__ \ |_) | | | | __/ | | | (__ / . \
7
+ /_/ \_\__|_| |_| |_|\___/|___/ .__/|_| |_|\___|_| |_|\___/_/ \_\
8
+ | |
9
+ |_|
10
+
11
+ Created with ♥ by the AtmosphericX Team (KiyoWx, StarflightWx, & CJ Ziegler)
12
+ Discord: https://atmosphericx-discord.scriptkitty.cafe
13
+ Ko-Fi: https://ko-fi.com/k3yomi
14
+ Documentation: http://localhost/documentation | https://atmosphericx.scriptkitty.cafe/documentation
15
+
16
+ Internal Package: @atmosx/event-product-parser
17
+
18
+ */
19
+
20
+ import { bootstrap } from "../../bootstrap"
21
+ import { setTimeoutAction } from "./utilities.setTimeoutAction";
22
+ import { setWarning } from "./utilities.setWarning";
23
+
24
+ interface SetEventEmitOptions {
25
+ event: string
26
+ metadata: any
27
+ message?: string
28
+ limited?: boolean
29
+ }
30
+
31
+ export const setEventEmit = (options: SetEventEmitOptions): void => {
32
+ if (options.limited) {
33
+ const isTimeout = setTimeoutAction({ identifier: `event.${options.event}`, addTime: true, max: 1, interval: 1 })
34
+ if (isTimeout.limited) return;
35
+ }
36
+ bootstrap.listener.emit(options.event, options.metadata)
37
+ if (options.event != `log`) { bootstrap.listener.emit(`*`, {event: options.event, data: options.metadata}) }
38
+ if (options.message) {
39
+ setWarning({ message: options.message })
40
+ }
41
+ }
@@ -0,0 +1,30 @@
1
+ /*
2
+ _ _ _ __ __
3
+ /\ | | | | (_) \ \ / /
4
+ / \ | |_ _ __ ___ ___ ___ _ __ | |__ ___ _ __ _ ___ \ V /
5
+ / /\ \| __| '_ ` _ \ / _ \/ __| '_ \| '_ \ / _ \ '__| |/ __| > <
6
+ / ____ \ |_| | | | | | (_) \__ \ |_) | | | | __/ | | | (__ / . \
7
+ /_/ \_\__|_| |_| |_|\___/|___/ .__/|_| |_|\___|_| |_|\___/_/ \_\
8
+ | |
9
+ |_|
10
+
11
+ Created with ♥ by the AtmosphericX Team (KiyoWx, StarflightWx, & CJ Ziegler)
12
+ Discord: https://atmosphericx-discord.scriptkitty.cafe
13
+ Ko-Fi: https://ko-fi.com/k3yomi
14
+ Documentation: http://localhost/documentation | https://atmosphericx.scriptkitty.cafe/documentation
15
+
16
+ Internal Package: @atmosx/event-product-parser
17
+
18
+ */
19
+
20
+ import { bootstrap } from "../../bootstrap"
21
+
22
+ interface SetListenerOptions {
23
+ event: string,
24
+ callback: () => void
25
+ }
26
+
27
+ export const setListener = (options: SetListenerOptions): (() => void) => {
28
+ bootstrap.listener.on(options.event, options.callback)
29
+ return () => { void bootstrap.listener.off(options.event, options.callback) };
30
+ }