Skip to content

Commit 0bffea0

Browse files
migrate metar cmd to zod
1 parent 540557a commit 0bffea0

File tree

4 files changed

+110
-78
lines changed

4 files changed

+110
-78
lines changed

src/commands/utils/metar.ts

+45-48
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { ApplicationCommandOptionType, ApplicationCommandType, Colors } from 'discord.js';
2-
import fetch from 'node-fetch';
3-
import { constantsConfig, slashCommand, slashCommandStructure, makeEmbed, makeLines, Logger } from '../../lib';
2+
import { Request } from 'node-fetch';
3+
import { ZodError } from 'zod';
4+
import { constantsConfig, fetchData, makeEmbed, makeLines, slashCommand, slashCommandStructure } from '../../lib';
5+
import { Metar, MetarSchema } from '../../lib/apis/zodSchemas/metarSchemas';
46

57
const data = slashCommandStructure({
68
name: 'metar',
@@ -16,6 +18,12 @@ const data = slashCommandStructure({
1618
}],
1719
});
1820

21+
const errorEmbed = (error: string) => makeEmbed({
22+
title: 'METAR Error',
23+
description: error,
24+
color: Colors.Red,
25+
});
26+
1927
export default slashCommand(data, async ({ interaction }) => {
2028
await interaction.deferReply();
2129

@@ -32,55 +40,44 @@ export default slashCommand(data, async ({ interaction }) => {
3240
return interaction.editReply({ embeds: [noTokenEmbed] });
3341
}
3442

43+
let metar: Metar;
3544
try {
36-
const metarReport: any = await fetch(`https://avwx.rest/api/metar/${icao}`, {
45+
metar = await fetchData<Metar>(new Request(`https://avwx.rest/api/metar/${icao}`, {
3746
method: 'GET',
3847
headers: { Authorization: metarToken },
39-
})
40-
.then((res) => res.json());
41-
42-
if (metarReport.error) {
43-
const invalidEmbed = makeEmbed({
44-
title: `Metar Error | ${icao.toUpperCase()}`,
45-
description: metarReport.error,
46-
color: Colors.Red,
47-
});
48-
return interaction.editReply({ embeds: [invalidEmbed] });
49-
}
50-
const metarEmbed = makeEmbed({
51-
title: `METAR Report | ${metarReport.station}`,
52-
description: makeLines([
53-
'**Raw Report**',
54-
metarReport.raw,
55-
'',
56-
'**Basic Report:**',
57-
`**Time Observed:** ${metarReport.time.dt}`,
58-
`**Station:** ${metarReport.station}`,
59-
`**Wind:** ${metarReport.wind_direction.repr}${metarReport.wind_direction.repr === 'VRB' ? '' : constantsConfig.units.DEGREES} at ${metarReport.wind_speed.repr} ${metarReport.units.wind_speed}`,
60-
`**Visibility:** ${metarReport.visibility.repr} ${Number.isNaN(+metarReport.visibility.repr) ? '' : metarReport.units.visibility}`,
61-
`**Temperature:** ${metarReport.temperature.repr} ${constantsConfig.units.CELSIUS}`,
62-
`**Dew Point:** ${metarReport.dewpoint.repr} ${constantsConfig.units.CELSIUS}`,
63-
`**Altimeter:** ${metarReport.altimeter.value.toString()} ${metarReport.units.altimeter}`,
64-
`**Flight Rules:** ${metarReport.flight_rules}`,
65-
]),
66-
fields: [
67-
{
68-
name: 'Unsure of how to read the raw report?',
69-
value: 'Please refer to our guide [here.](https://docs.flybywiresim.com/pilots-corner/airliner-flying-guide/weather/)',
70-
inline: false,
71-
},
72-
],
73-
footer: { text: 'This METAR report may not accurately reflect the weather in the simulator. However, it will always be similar to the current conditions present in the sim.' },
74-
});
75-
76-
return interaction.editReply({ embeds: [metarEmbed] });
48+
}), MetarSchema);
7749
} catch (e) {
78-
Logger.error('metar:', e);
79-
const fetchErrorEmbed = makeEmbed({
80-
title: 'Metar Error | Fetch Error',
81-
description: 'There was an error fetching the METAR report. Please try again later.',
82-
color: Colors.Red,
83-
});
84-
return interaction.editReply({ embeds: [fetchErrorEmbed] });
50+
if (e instanceof ZodError) {
51+
return interaction.editReply({ embeds: [errorEmbed('The API returned unknown data.')] });
52+
}
53+
return interaction.editReply({ embeds: [errorEmbed(`An error occurred while fetching the latest METAR for ${icao.toUpperCase()}.`)] });
8554
}
55+
56+
const metarEmbed = makeEmbed({
57+
title: `METAR Report | ${metar.station}`,
58+
description: makeLines([
59+
'**Raw Report**',
60+
metar.raw,
61+
'',
62+
'**Basic Report:**',
63+
`**Time Observed:** ${metar.time.dt}`,
64+
`**Station:** ${metar.station}`,
65+
`**Wind:** ${metar.wind_direction.repr}${metar.wind_direction.repr === 'VRB' ? '' : constantsConfig.units.DEGREES} at ${metar.wind_speed.repr} ${metar.units.wind_speed}`,
66+
`**Visibility:** ${metar.visibility.repr} ${Number.isNaN(+metar.visibility.repr) ? '' : metar.units.visibility}`,
67+
`**Temperature:** ${metar.temperature.repr} ${constantsConfig.units.CELSIUS}`,
68+
`**Dew Point:** ${metar.dewpoint.repr} ${constantsConfig.units.CELSIUS}`,
69+
`**Altimeter:** ${metar.altimeter.value.toString()} ${metar.units.altimeter}`,
70+
`**Flight Rules:** ${metar.flight_rules}`,
71+
]),
72+
fields: [
73+
{
74+
name: 'Unsure of how to read the raw report?',
75+
value: 'Please refer to our guide [here](https://docs.flybywiresim.com/pilots-corner/airliner-flying-guide/weather/).',
76+
inline: false,
77+
},
78+
],
79+
footer: { text: 'This METAR report may not accurately reflect the weather in the simulator. However, it will always be similar to the current conditions present in the sim.' },
80+
});
81+
82+
return interaction.editReply({ embeds: [metarEmbed] });
8683
});

src/commands/utils/taf.ts

+19-29
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { ApplicationCommandOptionType, ApplicationCommandType, Colors } from 'discord.js';
22
import { Request } from 'node-fetch';
33
import { ZodError } from 'zod';
4-
import { Logger, TAF, TafSchema, fetchData, makeEmbed, makeLines, slashCommand, slashCommandStructure } from '../../lib';
4+
import { TAF, TafSchema, fetchData, makeEmbed, makeLines, slashCommand, slashCommandStructure } from '../../lib';
55

66
const data = slashCommandStructure({
77
name: 'taf',
@@ -62,33 +62,23 @@ export default slashCommand(data, async ({ interaction }) => {
6262
return interaction.editReply({ embeds: [errorEmbed(`An error occurred while fetching the latest TAF for ${icao.toUpperCase()}.`)] });
6363
}
6464

65-
try {
66-
const tafEmbed = makeEmbed({
67-
title: `TAF Report | ${taf.station}`,
68-
description: makeLines(['**Raw Report**', ...taf.forecast.map((forecast, i) => {
69-
if (i === 0) {
70-
return `${taf.station} ${forecast.raw}`;
71-
}
72-
return forecast.raw;
73-
})]),
74-
fields: [
75-
{
76-
name: 'Unsure of how to read the report?',
77-
value: `Please refer to our guide [here](https://docs.flybywiresim.com/pilots-corner/airliner-flying-guide/weather/#taf-example-decoded) or see above report decoded [here](https://e6bx.com/weather/${taf.station}/?showDecoded=1&focuspoint=tafdecoder).`,
78-
inline: false,
79-
},
80-
],
81-
footer: { text: 'This TAF report is only a forecast, and may not accurately reflect weather in the simulator.' },
82-
});
65+
const tafEmbed = makeEmbed({
66+
title: `TAF Report | ${taf.station}`,
67+
description: makeLines(['**Raw Report**', ...taf.forecast.map((forecast, i) => {
68+
if (i === 0) {
69+
return `${taf.station} ${forecast.raw}`;
70+
}
71+
return forecast.raw;
72+
})]),
73+
fields: [
74+
{
75+
name: 'Unsure of how to read the report?',
76+
value: `Please refer to our guide [here](https://docs.flybywiresim.com/pilots-corner/airliner-flying-guide/weather/#taf-example-decoded) or see above report decoded [here](https://e6bx.com/weather/${taf.station}/?showDecoded=1&focuspoint=tafdecoder).`,
77+
inline: false,
78+
},
79+
],
80+
footer: { text: 'This TAF report is only a forecast, and may not accurately reflect weather in the simulator.' },
81+
});
8382

84-
return interaction.editReply({ embeds: [tafEmbed] });
85-
} catch (error) {
86-
Logger.error('taf:', error);
87-
const fetchErrorEmbed = makeEmbed({
88-
title: 'TAF Error | Fetch Error',
89-
description: 'There was an error fetching the TAF report. Please try again later.',
90-
color: Colors.Red,
91-
});
92-
return interaction.editReply({ embeds: [fetchErrorEmbed] });
93-
}
83+
return interaction.editReply({ embeds: [tafEmbed] });
9484
});
+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { z } from 'zod';
2+
3+
export const TimeSchema = z.object({ dt: z.string().datetime() });
4+
5+
export const WindDirectionSchema = z.object({ repr: z.string() });
6+
7+
export const WindSpeedSchema = z.object({ repr: z.string() });
8+
9+
export const VisibilitySchema = z.object({ repr: z.string() });
10+
11+
export const TemperatureSchema = z.object({ repr: z.string() });
12+
13+
export const DewpointSchema = z.object({ repr: z.string() });
14+
15+
export const AltimeterSchema = z.object({ value: z.number() });
16+
17+
export const UnitsSchema = z.object({
18+
accumulation: z.string(),
19+
altimeter: z.string(),
20+
altitude: z.string(),
21+
temperature: z.string(),
22+
visibility: z.string(),
23+
wind_speed: z.string(),
24+
});
25+
26+
/**
27+
* This schema only contains currently used fields. If you wish to use other fields returned by the API add them below.
28+
*/
29+
export const MetarSchema = z.object({
30+
station: z.string(),
31+
raw: z.string(),
32+
time: TimeSchema,
33+
wind_direction: WindDirectionSchema,
34+
wind_speed: WindSpeedSchema,
35+
visibility: VisibilitySchema,
36+
temperature: TemperatureSchema,
37+
dewpoint: DewpointSchema,
38+
altimeter: AltimeterSchema,
39+
flight_rules: z.string(),
40+
units: UnitsSchema,
41+
});
42+
43+
/**
44+
* This type only contains currently used fields. If you wish to use other fields returned by the API add them below.
45+
*/
46+
export type Metar = z.infer<typeof MetarSchema>;

src/lib/apis/zodSchemas/tafSchemas.ts

-1
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,4 @@ export const TafSchema = z.object({
88
forecast: z.array(ForecastSchema),
99
});
1010

11-
export type Forecast = z.infer<typeof ForecastSchema>;
1211
export type TAF = z.infer<typeof TafSchema>;

0 commit comments

Comments
 (0)