kuma-discord/index.js
2025-07-14 06:57:09 -04:00

140 lines
4.1 KiB
JavaScript

require("dotenv").config();
const { Client, GatewayIntentBits, EmbedBuilder } = require("discord.js");
const UptimeKuma = require("uptimekuma-api");
const cron = require("node-cron");
const subs = require("./subs.json");
const KUMA_BASE_URL = process.env.KUMA_BASE_URL;
const DISCORD_TOKEN = process.env.TOKEN;
const CRON_SCHEDULE = "*/5 * * * *";
const STATUS_EMOJIS = {
online: "<:online:1307359583785713744>",
offline: "<:offline:1307359600592424971>",
degraded: "<:degraded:1307359619491954778>",
};
const STATUS_INFO = {
operational: {
text: "All Systems Operational",
color: 0x43b581, // Green
emoji: STATUS_EMOJIS.online,
},
degraded: {
text: "Degraded Service",
color: 0xfaa61a, // Orange
emoji: STATUS_EMOJIS.degraded,
},
outage: {
text: "Full Outage",
color: 0xf04747, // Red
emoji: STATUS_EMOJIS.offline,
},
};
const kuma = new UptimeKuma(KUMA_BASE_URL);
const client = new Client({
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages],
});
async function createStatusEmbed(statusPageSlug) {
try {
const statusData = await kuma.status(statusPageSlug);
const embed = new EmbedBuilder()
.setTitle(statusData.info.title)
.setThumbnail(statusData.info.icon);
let totalMonitors = 0;
let offlineMonitors = 0;
for (const category of statusData.status) {
let fieldText = "";
for (const monitor of category.monitors) {
totalMonitors++;
const isOnline = monitor.heartbeats[0]?.status === 1;
if (!isOnline) {
offlineMonitors++;
}
const emoji = isOnline ? STATUS_EMOJIS.online : STATUS_EMOJIS.offline;
fieldText += `${emoji} ${monitor.name}\n`;
}
// Discord embed fields have a 1024 character limit.
// This splits the text into multiple fields if necessary.
const fieldChunks = fieldText.match(/[\s\S]{1,1024}/g) || [];
fieldChunks.forEach((chunk, index) => {
embed.addFields({
name: index === 0 ? category.name : `${category.name} (cont.)`,
value: chunk,
});
});
}
const status = determineOverallStatus(offlineMonitors, totalMonitors);
const description = statusData.info.description
? `${statusData.info.description}\n\n${status.emoji} ${status.text}`
: `${status.emoji} ${status.text}`;
embed
.setDescription(description)
.setColor(status.color)
.setTimestamp(new Date());
return embed;
} catch (error) {
console.error(`Error fetching status for "${statusPageSlug}":`, error);
return new EmbedBuilder()
.setColor(STATUS_INFO.outage.color)
.setDescription("An error occurred while fetching status data.");
}
}
function determineOverallStatus(offlineCount, totalCount) {
if (offlineCount === 0) {
return STATUS_INFO.operational;
}
if (offlineCount === totalCount) {
return STATUS_INFO.outage;
}
return STATUS_INFO.degraded;
}
async function findOrCreateStatusMessage(channel) {
const messages = await channel.messages.fetch({ limit: 25 });
let message = messages.find((msg) => msg.author.id === client.user.id);
if (!message) {
const loadingEmbed = new EmbedBuilder().setDescription("Initializing status panel...");
message = await channel.send({ embeds: [loadingEmbed] });
}
return message;
}
async function updateAllStatusMessages() {
console.log("Updating all status messages...");
for (const [channelId, slug] of Object.entries(subs)) {
try {
const channel = await client.channels.fetch(channelId);
if (!channel || !channel.isTextBased()) {
console.warn(`Channel ${channelId} not found or is not a text channel.`);
continue;
}
const message = await findOrCreateStatusMessage(channel);
const embed = await createStatusEmbed(slug);
await message.edit({ embeds: [embed] });
} catch (error) {
console.error(`Failed to update status for channel ${channelId}:`, error);
}
}
console.log("Finished updating status messages.");
}
client.once("ready", async () => {
console.log(`Logged in as ${client.user.tag}`);
await updateAllStatusMessages(); // Initial update on startup
cron.schedule(CRON_SCHEDULE, updateAllStatusMessages);
console.log(`Scheduled status updates with cron schedule: ${CRON_SCHEDULE}`);
});
client.login(DISCORD_TOKEN);