Skip to content

Commit

Permalink
Release v2.1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
raphaelbernhart authored Jan 18, 2024
2 parents 29727b7 + be0df29 commit 218e5d5
Show file tree
Hide file tree
Showing 8 changed files with 168 additions and 42 deletions.
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"name": "healthznerbot",
"version": "2.0.4",
"version": "2.1.0",
"description": "A discord bot to get periodically updates of the health status of your hetzner cloud machines.",
"main": "main.js",
"engines": {
"node": ">=18.0.0 <19.0.0"
"node": ">=20.11.0"
},
"scripts": {
"start": "node .",
Expand All @@ -31,17 +31,17 @@
"homepage": "https://github.com/raphaelbernhart/healthznerbot#readme",
"dependencies": {
"@discordjs/rest": "^2.2.0",
"axios": "^1.6.2",
"axios": "^1.6.5",
"consola": "^3.2.3",
"dayjs": "^1.11.10",
"discord.js": "14.14.1",
"dotenv": "^16.3.1",
"hcloud-js": "^1.4.1"
},
"devDependencies": {
"@types/node": "18.19.0",
"@types/node": "20.11.0",
"@types/ws": "8.5.10",
"nodemon": "3.0.2",
"nodemon": "3.0.3",
"ts-node": "^10.9.2",
"typescript": "5.3.3"
}
Expand Down
46 changes: 28 additions & 18 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

39 changes: 26 additions & 13 deletions src/commands/servers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ChatInputCommandInteraction } from "discord.js";
import dayjs from "dayjs";
import axios from "axios";
import { fetchServerMetrics } from "../vendor/hetznerCloud";

const mapServerStatusToColor = (status: string) => {
switch (status) {
Expand All @@ -23,10 +24,17 @@ const mapServerStatusToColor = (status: string) => {

export default async (interaction: ChatInputCommandInteraction) => {
const serverMessages = [];
const serverMetricsPeriod = Number.parseFloat(
process.env.SERVER_METRICS_PERIOD || "15"
);

// Defer reply to fix bug when metrics gathering takes longer than 3sec
interaction.deferReply({ ephemeral: true });

for (let projectIndex = 0; projectIndex < $hcloud.length; projectIndex++) {
const client = $hcloud[projectIndex];
const token = client.hCloudToken.token;
const tokenName = $hcloud[projectIndex].hCloudToken.name;

const servers = (await client.servers.list()).servers;

Expand All @@ -35,22 +43,27 @@ export default async (interaction: ChatInputCommandInteraction) => {

// Metrics
const serverId = server.id;
const serverMetricsPeriod = Number.parseFloat(
process.env.SERVER_METRICS_PERIOD || "15"
);
const startDate = dayjs()
.subtract(serverMetricsPeriod, "minutes")
.toISOString();
const endDate = dayjs().toISOString();

const metricsResponse = await axios.get(
`https://api.hetzner.cloud/v1/servers/${serverId}/metrics?type=cpu,network&start=${startDate}&end=${endDate}`,
{
headers: {
Authorization: `Bearer ${token}`,
},
}
);
const metricsResponse = await fetchServerMetrics({
serverId,
startDate,
endDate,
token: {
key: tokenName,
value: token,
},
});

if (typeof metricsResponse === "undefined") {
await interaction.reply({
content: `An error occurred while fetching metrics from the hetzner api (${tokenName})`,
});
return;
}

// Metrics calculation
const metrics = metricsResponse.data.metrics;
Expand Down Expand Up @@ -121,7 +134,7 @@ export default async (interaction: ChatInputCommandInteraction) => {
}
}

interaction.reply({
interaction.editReply({
embeds: serverMessages.map((serverMessage) => ({
color: mapServerStatusToColor(
serverMessage.find((msg) => msg.name === "Status")
Expand All @@ -130,7 +143,7 @@ export default async (interaction: ChatInputCommandInteraction) => {
fields: serverMessage,
timestamp: new Date().toISOString(),
footer: {
text: "Healthzner Bot",
text: `(${serverMetricsPeriod}min) Healthzner Bot`,
},
})),
});
Expand Down
31 changes: 27 additions & 4 deletions src/commands/status.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { ChatInputCommandInteraction } from "discord.js";
import { ChatInputCommandInteraction, TextBasedChannel } from "discord.js";

export const getStatus = async (): Promise<string> => {
export const getStatus = async (
channel?: TextBasedChannel | undefined | null
): Promise<string | true> => {
let servers: Array<Server> = [];

// Map all servers of all hcloud clients in as an array
Expand Down Expand Up @@ -28,13 +30,34 @@ export const getStatus = async (): Promise<string> => {
});

if (allOnline) {
// Don't send status if last message was also allOnline message
if (typeof channel !== "undefined" && channel !== null) {
const lastFiveMessages = await channel.messages.fetch({ limit: 5 });
if (
lastFiveMessages.some(
(message) =>
message.client.user.id === $discordClient.user?.id &&
message.content.includes($lang?.status.success)
)
) {
return true;
}
}

return $lang?.status.success;
}
return messages.join("\n");
};

export default async (interaction: ChatInputCommandInteraction) => {
const message = await getStatus();
interaction.deferReply({ ephemeral: true });

const message = await getStatus(interaction.channel);

if (message === true) {
interaction.editReply($lang.status.success);
return;
}

interaction.reply(message);
interaction.editReply(message);
};
34 changes: 34 additions & 0 deletions src/helper/Discord.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import consola from "consola";
import { Client } from "discord.js";

export const getLastMessages = async (client: Client, limit: number = 5) => {
const channelId = process.env.DISCORD_CHANNEL as string;
const channel = await client.channels.fetch(channelId);

if (!channel?.isTextBased()) {
consola.error(
"Status update could not be sent. The specified channel (CHANNEL_ID env variable) is not a text channel"
);
return;
}

try {
return channel.messages.fetch({ limit });
} catch (err) {
consola.error(err);
}
};

export const getLastMessageOfBot = async (client: Client) => {
try {
const messages = await getLastMessages(client);

if (!messages) return;

return messages
?.filter((message) => message.author.id === client.user?.id)
.first();
} catch (err) {
consola.error(err);
}
};
3 changes: 3 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ import worker from "./worker/index";
import dayjs from "dayjs";
import duration from "dayjs/plugin/duration";
import ConfigCheck from "./helper/ConfigCheck";
import { Client as DiscordClient } from "discord.js";

const { Routes, Client, GatewayIntentBits } = require("discord.js");

declare global {
var $lang: Record<string, any>;
var $hcloud: HetznerClient[];
var $discordClient: DiscordClient;
}

// Configuration
Expand All @@ -35,6 +37,7 @@ const rest = new REST({ version: "10" }).setToken(
process.env.DISCORD_TOKEN || ""
);
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
global.$discordClient = client;

const main = async () => {
try {
Expand Down
28 changes: 28 additions & 0 deletions src/vendor/hetznerCloud.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
import hcloud from "hcloud-js";
import consola from "consola";
import { hetznerErrorTag } from "../constants/log";
import axios from "axios";

interface fetchServerMetricsOptions {
serverId: number;
startDate: string;
endDate: string;
token: {
key: string;
value: string;
};
}

export default class HetznerCloud {
static hCloudClients: Array<HetznerClient>;
Expand Down Expand Up @@ -63,3 +74,20 @@ export default class HetznerCloud {
}
}
}

export async function fetchServerMetrics(options: fetchServerMetricsOptions) {
try {
return await axios.get(
`https://api.hetzner.cloud/v1/servers/${options.serverId}/metrics?type=cpu,network&start=${options.startDate}&end=${options.endDate}`,
{
headers: {
Authorization: `Bearer ${options.token.value}`,
},
}
);
} catch (err: any) {
consola.error(
`[Hetzner] Metrics request failed for token ${options.token.key}\nStatus: ${err.response.status}\nCode: ${err.code}`
);
}
}
Loading

0 comments on commit 218e5d5

Please sign in to comment.