Skip to content

Commit dba6b6b

Browse files
committed
feat: morning weather status report
1 parent af0d04a commit dba6b6b

File tree

5 files changed

+82
-20
lines changed

5 files changed

+82
-20
lines changed

app.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import registerListeners from "./listeners";
33
import { setUpDailyGreeting } from "./intervals/morning";
44
import { setUpUptimeChecker } from "./intervals/uptimeChecker";
55
import { env } from "./env";
6-
import { trackedSitesTable } from "./db/schema";
76
import { db } from "./db";
87

98
/** Initialization */
@@ -56,7 +55,7 @@ registerListeners(app);
5655
text: msg,
5756
channel: channelId,
5857
})
59-
.catch(app.logger.error),
58+
.catch((er) => app.logger.error(er)),
6059
db
6160
);
6261
} catch (error) {

intervals/morning.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { DateTime } from "luxon";
2-
import { getWebsiteStatusString } from "./uptimeChecker";
2+
import { getTodaysWeather } from "./weather";
33

44
const greetingTime = {
55
hour: 9,
@@ -29,9 +29,10 @@ export const scheduleNextGreeting = (
2929
);
3030
setTimeout(async () => {
3131
sendMessage(
32-
`${
33-
greetings[Math.floor(Math.random() * greetings.length)]
34-
}\n\n${await getWebsiteStatusString()}`
32+
await getTodaysWeather().catch((e) => {
33+
console.error(e);
34+
return "Morning! I would give you the weather report for today, but the National Weather Service api seems to be unavailable. >_<";
35+
})
3536
);
3637
scheduleNextGreeting(sendMessage, nextMorningTime.plus({ days: 1 })); // this actually accounts for DST properly
3738
}, nextMorningTime.diff(currentTime).toMillis());

intervals/siteMonitor.ts

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,14 @@ export class SiteMonitor {
4545

4646
const downState = this.downSites[site.url];
4747
downState.failErrors.push(error);
48-
await this.db.insert(uptimeRecordsTable).values({
49-
up: false,
50-
details: error,
51-
site_id: site.id,
52-
});
48+
await this.db
49+
.insert(uptimeRecordsTable)
50+
.values({
51+
up: false,
52+
details: error,
53+
site_id: site.id,
54+
})
55+
.catch();
5356

5457
if (
5558
Date.now() - downState.firstDownTimestamp >= this.alertThresholdMs &&
@@ -86,12 +89,15 @@ export class SiteMonitor {
8689
async siteUp(site: SiteInfo, responseTimeMs: number) {
8790
console.log(`check successful for ${site.url}`);
8891

89-
await this.db.insert(uptimeRecordsTable).values({
90-
up: true,
91-
details: null,
92-
site_id: site.id,
93-
response_time_ms: responseTimeMs,
94-
});
92+
await this.db
93+
.insert(uptimeRecordsTable)
94+
.values({
95+
up: true,
96+
details: null,
97+
site_id: site.id,
98+
response_time_ms: responseTimeMs,
99+
})
100+
.catch();
95101
if (this.downSites[site.url] === undefined) return;
96102

97103
if (this.downSites[site.url].alertStage !== "NONE") {

intervals/uptimeChecker.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,11 @@ export const setUpUptimeChecker = (
3535
const trackedURLs = await db
3636
.select()
3737
.from(trackedSitesTable)
38-
.where(eq(trackedSitesTable.actively_tracked, true));
38+
.where(eq(trackedSitesTable.actively_tracked, true))
39+
.catch((e) => {
40+
console.error(e);
41+
return [];
42+
});
3943
trackedURLs.forEach((site) =>
4044
checkSite(
4145
site.url,
@@ -52,7 +56,9 @@ export const setUpUptimeChecker = (
5256

5357
export const getWebsiteStatusString = async () => {
5458
return (
55-
(await siteMonitor?.getStatusAsString()) ??
56-
"Site monitor has not been initialized!"
59+
(await siteMonitor?.getStatusAsString().catch((e) => {
60+
console.error(e);
61+
return "Site status unavailable!";
62+
})) ?? "Site monitor has not been initialized!"
5763
);
5864
};

intervals/weather.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { z, ZodType, ZodTypeAny } from "zod";
2+
const generalLocationSchema = z.object({
3+
properties: z.object({
4+
forecast: z.string(),
5+
}),
6+
});
7+
const forecastSchema = z.object({
8+
properties: z.object({
9+
periods: z.array(
10+
z.object({
11+
name: z.string(),
12+
detailedForecast: z.string(),
13+
})
14+
),
15+
}),
16+
});
17+
const rootApiURL = "https://api.weather.gov/points/40.4442,-79.9396";
18+
const fetchData = async <UWU extends ZodTypeAny>(
19+
url: string,
20+
schema: UWU,
21+
retriesLeft = 3
22+
) => {
23+
try {
24+
const data = schema.parse(await (await fetch(url)).json()) as z.infer<UWU>;
25+
return data;
26+
} catch (e) {
27+
if (retriesLeft === 1) throw e;
28+
return fetchData(url, schema, retriesLeft - 1);
29+
}
30+
};
31+
export async function getTodaysWeather() {
32+
const generalLocationData = await fetchData(
33+
rootApiURL,
34+
generalLocationSchema
35+
);
36+
const forecastURL = generalLocationData.properties.forecast;
37+
const forecast = await fetchData(forecastURL, forecastSchema);
38+
let weatherToday: string | undefined, weatherTonight: string | undefined;
39+
for (const entry of forecast.properties.periods) {
40+
if (entry.name === "Today") {
41+
weatherToday = entry.detailedForecast;
42+
}
43+
if (entry.name === "Tonight") {
44+
weatherTonight = entry.detailedForecast;
45+
}
46+
}
47+
return `おはよう! Here's today's weather forecast for CMU, Pittsburgh\n\n*Today:* ${
48+
weatherToday ?? "Unavailable"
49+
}\n\n*Tonight:* ${weatherTonight ?? "Unavailable"}`;
50+
}

0 commit comments

Comments
 (0)