mirror of
https://github.com/thedrewen/protojx-manager.git
synced 2026-03-23 05:01:54 +01:00
feat(live_status): add command to generate and update persistent status messages
This commit is contained in:
@@ -11,7 +11,7 @@ A status bot and other features for protojx.
|
|||||||
| /status command | 🌐 |
|
| /status command | 🌐 |
|
||||||
| Number of services down in the bot's status. | 🌐 |
|
| Number of services down in the bot's status. | 🌐 |
|
||||||
| Notification system in case of downtime. | ✅ |
|
| Notification system in case of downtime. | ✅ |
|
||||||
| Ability to create persistent status messages that update automatically. | ➖ |
|
| Ability to create persistent status messages that update automatically. (/live_status) | ✅ |
|
||||||
| Deployment workflow on Raspberry Pi. | ➖ |
|
| Deployment workflow on Raspberry Pi. | ➖ |
|
||||||
|
|
||||||
- 🌐 -> In production
|
- 🌐 -> In production
|
||||||
|
|||||||
75
src/commands/utility/live_status.command.ts
Normal file
75
src/commands/utility/live_status.command.ts
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import { ApplicationIntegrationType, ChannelType, ContainerBuilder, MessageFlags, SlashCommandBuilder } from "discord.js";
|
||||||
|
import { CommandDefinition } from "../../type";
|
||||||
|
import statusService from "../../services/status.service";
|
||||||
|
import { AppDataSource } from "../../data-source";
|
||||||
|
import { Guild } from "../../entity/guild.entity";
|
||||||
|
|
||||||
|
const cmd : CommandDefinition = {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('live_status')
|
||||||
|
.setDescription('Generate a permanent status message that updates every 2 minutes.')
|
||||||
|
.addChannelOption((option) => option
|
||||||
|
.setName('channel')
|
||||||
|
.setDescription('The message will be generated')
|
||||||
|
.addChannelTypes(ChannelType.GuildText)
|
||||||
|
.setRequired(true)
|
||||||
|
)
|
||||||
|
.setIntegrationTypes(
|
||||||
|
ApplicationIntegrationType.GuildInstall
|
||||||
|
),
|
||||||
|
async execute(interaction) {
|
||||||
|
await interaction.deferReply({flags: [MessageFlags.Ephemeral]});
|
||||||
|
|
||||||
|
const channel_options = await interaction.options.getChannel("channel");
|
||||||
|
if(channel_options && interaction.guildId){
|
||||||
|
const channel = await interaction.guild?.channels.fetch(channel_options?.id);
|
||||||
|
|
||||||
|
if(channel?.isSendable()) {
|
||||||
|
const message = await channel.send({components: [statusService.getUpdatedContainer(true)], flags: [MessageFlags.IsComponentsV2]});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const guildRepo = AppDataSource.getRepository(Guild);
|
||||||
|
|
||||||
|
let guild = await guildRepo.findOne({where: {guild_id: interaction.guildId}});
|
||||||
|
|
||||||
|
if(guild) {
|
||||||
|
const messageId = guild.persistent_message_id;
|
||||||
|
const channelId = guild.persistent_message_channel_id;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const beforeChannel = await interaction.guild?.channels.fetch(channelId);
|
||||||
|
if(beforeChannel && beforeChannel.isSendable()) {
|
||||||
|
try {
|
||||||
|
const beforeMessage = await beforeChannel.messages.fetch(messageId);
|
||||||
|
const container = new ContainerBuilder()
|
||||||
|
.addTextDisplayComponents((t) => t.setContent('This message is no longer valid!'));
|
||||||
|
beforeMessage.edit({components: [container]});
|
||||||
|
} catch (error) {}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
guild = new Guild();
|
||||||
|
guild.guild_id = interaction.guildId;
|
||||||
|
}
|
||||||
|
|
||||||
|
guild.persistent_message_channel_id = channel.id;
|
||||||
|
guild.persistent_message_id = message.id;
|
||||||
|
|
||||||
|
await guildRepo.save(guild);
|
||||||
|
|
||||||
|
await interaction.editReply('Message successfully generated!')
|
||||||
|
} catch (error) {
|
||||||
|
interaction.editReply('An error has occured ! '+error);
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
interaction.editReply('The selected channel is invalid!');
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
interaction.editReply('The selected channel is invalid!');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default cmd;
|
||||||
@@ -21,7 +21,7 @@ for (const folder of commandFolders) {
|
|||||||
if (!fs.statSync(commandsPath).isDirectory()) continue;
|
if (!fs.statSync(commandsPath).isDirectory()) continue;
|
||||||
|
|
||||||
const commandFiles = fs.readdirSync(commandsPath).filter(file =>
|
const commandFiles = fs.readdirSync(commandsPath).filter(file =>
|
||||||
file.endsWith('.js')
|
file.endsWith('.command.js')
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const file of commandFiles) {
|
for (const file of commandFiles) {
|
||||||
|
|||||||
@@ -10,4 +10,7 @@ export class Guild {
|
|||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
persistent_message_id: string;
|
persistent_message_id: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
persistent_message_channel_id: string;
|
||||||
}
|
}
|
||||||
@@ -6,6 +6,7 @@ import { AppDataSource } from "../data-source";
|
|||||||
import { HostsLog } from "../entity/hostslog.entity";
|
import { HostsLog } from "../entity/hostslog.entity";
|
||||||
import { Repository } from "typeorm";
|
import { Repository } from "typeorm";
|
||||||
import { Follow } from "../entity/follow.entity";
|
import { Follow } from "../entity/follow.entity";
|
||||||
|
import { Guild } from "../entity/guild.entity";
|
||||||
|
|
||||||
type Nofity = {time: Date, name : string, alive : boolean, type : InfraType};
|
type Nofity = {time: Date, name : string, alive : boolean, type : InfraType};
|
||||||
|
|
||||||
@@ -97,11 +98,13 @@ export class StatusService {
|
|||||||
private client: Client | null = null;
|
private client: Client | null = null;
|
||||||
private hostsLogRepo: Repository<HostsLog>;
|
private hostsLogRepo: Repository<HostsLog>;
|
||||||
private followRepo: Repository<Follow>;
|
private followRepo: Repository<Follow>;
|
||||||
|
private guildRepo: Repository<Guild>;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|
||||||
this.hostsLogRepo = AppDataSource.getRepository(HostsLog);
|
this.hostsLogRepo = AppDataSource.getRepository(HostsLog);
|
||||||
this.followRepo = AppDataSource.getRepository(Follow);
|
this.followRepo = AppDataSource.getRepository(Follow);
|
||||||
|
this.guildRepo = AppDataSource.getRepository(Guild);
|
||||||
|
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
await this.fetch()
|
await this.fetch()
|
||||||
@@ -123,6 +126,23 @@ export class StatusService {
|
|||||||
try {
|
try {
|
||||||
await this.fetch();
|
await this.fetch();
|
||||||
await this.updateClientStatus();
|
await this.updateClientStatus();
|
||||||
|
|
||||||
|
// ? Message Live
|
||||||
|
const guilds = await this.guildRepo.find();
|
||||||
|
|
||||||
|
guilds.forEach(async (gdb) => {
|
||||||
|
if(this.client) {
|
||||||
|
try {
|
||||||
|
const guild = await this.client.guilds.fetch(gdb.guild_id);
|
||||||
|
const channel = await guild.channels.fetch(gdb.persistent_message_channel_id);
|
||||||
|
if(channel?.isSendable()) {
|
||||||
|
const message = await channel.messages.fetch(gdb.persistent_message_id);
|
||||||
|
await message.edit({components: [this.getUpdatedContainer(true)]});
|
||||||
|
}
|
||||||
|
} catch (error) {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
console.log('Status check completed at:', new Date().toISOString());
|
console.log('Status check completed at:', new Date().toISOString());
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error during status check:', error);
|
console.error('Error during status check:', error);
|
||||||
@@ -223,14 +243,14 @@ export class StatusService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getUpdatedContainer(): ContainerBuilder {
|
public getUpdatedContainer(live : boolean = false): ContainerBuilder {
|
||||||
const hostTexts = this.hosts.map((s) => {
|
const hostTexts = this.hosts.map((s) => {
|
||||||
return { type: s.type, value: `- ${s.name} : ${s.alive ? `${process.env.EMOJI_STATUS_ONLINE} Online` : `${process.env.EMOJI_STATUS_OFFLINE} Offline`}` };
|
return { type: s.type, value: `- ${s.name} : ${s.alive ? `${process.env.EMOJI_STATUS_ONLINE} Online` : `${process.env.EMOJI_STATUS_OFFLINE} Offline`}` };
|
||||||
});
|
});
|
||||||
|
|
||||||
const container = new ContainerBuilder()
|
const container = new ContainerBuilder()
|
||||||
.setAccentColor(0x0000ed)
|
.setAccentColor(0x0000ed)
|
||||||
.addTextDisplayComponents((text) => text.setContent('# Status of protojx services'));
|
.addTextDisplayComponents((text) => text.setContent('# Status of protojx services'+(live ? ' (live)' : '')));
|
||||||
|
|
||||||
const sections: { title: string, type: InfraType, thumbnail: string }[] = [
|
const sections: { title: string, type: InfraType, thumbnail: string }[] = [
|
||||||
{
|
{
|
||||||
@@ -271,7 +291,7 @@ export class StatusService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
container.addTextDisplayComponents((text) => text.setContent(`${now.getDate()}-${now.getMonth() + 1}-${now.getFullYear()} ${(now.getHours() + '').padStart(2, "0")}:${(now.getMinutes() + '').padStart(2, "0")} - Receive automatic notifications when there is an outage with /follow !`));
|
container.addTextDisplayComponents((text) => text.setContent(`${live ? 'Last update : ' : ''}${now.getDate()}-${now.getMonth() + 1}-${now.getFullYear()} ${(now.getHours() + '').padStart(2, "0")}:${(now.getMinutes() + '').padStart(2, "0")} - Receive automatic notifications when there is an outage with /follow !`));
|
||||||
|
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
|||||||
2
src/type.d.ts
vendored
2
src/type.d.ts
vendored
@@ -9,4 +9,4 @@ export type Host = {
|
|||||||
type: InfraType,
|
type: InfraType,
|
||||||
notify: boolean;
|
notify: boolean;
|
||||||
};
|
};
|
||||||
export type CommandDefinition = { data: SlashCommandBuilder, execute: (interaction: ChatInputCommandInteraction) => void, buttons?: { id: string, handle: (interaction: ButtonInteraction) => void}[]};
|
export type CommandDefinition = { data: SlashCommandBuilder | SlashCommandOptionsOnlyBuilder, execute: (interaction: ChatInputCommandInteraction) => void, buttons?: { id: string, handle: (interaction: ButtonInteraction) => void}[]};
|
||||||
Reference in New Issue
Block a user