mirror of
https://github.com/thedrewen/protojx-manager.git
synced 2026-03-21 09:48:56 +01:00
Compare commits
57 Commits
v1.0.0-bet
...
v1.1.7
| Author | SHA1 | Date | |
|---|---|---|---|
| 5ec7187afd | |||
| 2aa5c56ca7 | |||
| 7b35fcf31b | |||
| a5476b26fe | |||
| c99c11c241 | |||
| df048c1352 | |||
| 290e8b982a | |||
| 8bfeb1c43c | |||
| e2a8255d5a | |||
| 85ec27cb2b | |||
| e2c896c6f1 | |||
| 3ff4278217 | |||
| 7b80aca9e1 | |||
| afd8d1f68a | |||
| c571e03495 | |||
| a577f99277 | |||
| 4b6b2c8575 | |||
| e375fb2631 | |||
| 39178d1322 | |||
| d7b772544f | |||
| 27fc82d371 | |||
| 956536a717 | |||
| 0f22892816 | |||
| a593e05f5c | |||
| d417e7334b | |||
| 96fcb53e0b | |||
| fc5c2b1e63 | |||
| 3821583d1d | |||
| 91e95127c1 | |||
| f05a35965e | |||
| b9930cbc8f | |||
| d3fba3668e | |||
| 49da70082e | |||
| 37fbfd1c8c | |||
| b13c77d9a5 | |||
| 24ed5e6a62 | |||
| d4e90640b6 | |||
| 522ed2ba81 | |||
| 6b10aaa009 | |||
| 03769b14fa | |||
| d964ec7963 | |||
| 17c00211da | |||
| 4bb33bea89 | |||
| bbd071c44f | |||
| 3c6fd92cc2 | |||
| 5f770d861d | |||
| 2822908ea0 | |||
| 0449654edd | |||
|
|
e9565a9552 | ||
| 0504e8262d | |||
| 978d21a120 | |||
| 9cadfb2734 | |||
| 5b3167bf14 | |||
| 7f9468bc99 | |||
| 86a7429711 | |||
| 43b7bdd8b4 | |||
| 58ecaf3c4c |
24
.env.example
24
.env.example
@@ -1,5 +1,25 @@
|
|||||||
TOKEN=
|
# Discord Bot Configuration
|
||||||
CLIENT_ID=
|
TOKEN=your_discord_bot_token_here
|
||||||
|
CLIENT_ID=your_discord_client_id_here
|
||||||
|
|
||||||
|
# Custom Emojis
|
||||||
EMOJI_STATUS_ONLINE=<a:online:1432684754276323431>
|
EMOJI_STATUS_ONLINE=<a:online:1432684754276323431>
|
||||||
EMOJI_STATUS_OFFLINE=<a:offline:1432684900175183882>
|
EMOJI_STATUS_OFFLINE=<a:offline:1432684900175183882>
|
||||||
|
|
||||||
|
EMOJI_RYZEN=<:ryzen:1433711892009848833>
|
||||||
|
EMOJI_XEON=<:xeon:1433711864168054855>
|
||||||
|
|
||||||
|
# Database Configuration
|
||||||
|
DB_HOST=localhost
|
||||||
|
DB_PORT=5432
|
||||||
|
DB_USERNAME=postgres
|
||||||
|
DB_PASSWORD=your_database_password_here
|
||||||
|
DB_DATABASE=protojx_manager
|
||||||
|
DB_LOGGING=false
|
||||||
|
|
||||||
|
# Environment
|
||||||
|
NODE_ENV=development
|
||||||
|
|
||||||
|
# Protected IPS
|
||||||
|
PROTOJX_ROUTER_1=
|
||||||
|
PROTOJX_ROUTER_2=
|
||||||
52
.github/workflows/deploy.yml
vendored
Normal file
52
.github/workflows/deploy.yml
vendored
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# This is a basic workflow to help you get started with Actions
|
||||||
|
name: Deploy
|
||||||
|
|
||||||
|
# Controls when the workflow will run
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- '*'
|
||||||
|
# Allows you to run this workflow manually from the Actions tab
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||||
|
jobs:
|
||||||
|
# This workflow contains a single job called "build"
|
||||||
|
build:
|
||||||
|
# The type of runner that the job will run on
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||||
|
steps:
|
||||||
|
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup SSH Agent
|
||||||
|
uses: webfactory/ssh-agent@v0.9.0
|
||||||
|
with:
|
||||||
|
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||||
|
|
||||||
|
- name: Add VPS to known_hosts
|
||||||
|
run: |
|
||||||
|
ssh-keyscan -p ${{ secrets.VPS_PORT }} -H ${{ secrets.VPS_HOST }} >> ~/.ssh/known_hosts
|
||||||
|
|
||||||
|
- name: Deploy to VPS
|
||||||
|
run: |
|
||||||
|
ssh -p ${{ secrets.VPS_PORT }} ${{ secrets.VPS_USER }}@${{ secrets.VPS_HOST }} "
|
||||||
|
cd /home/${{ secrets.VPS_USER }}/protojx/protojx-manager &&
|
||||||
|
git pull &&
|
||||||
|
|
||||||
|
# Stop and remove old container if exists
|
||||||
|
docker stop protojx_manager 2>/dev/null || true &&
|
||||||
|
docker rm protojx_manager 2>/dev/null || true &&
|
||||||
|
|
||||||
|
# Build new image
|
||||||
|
docker buildx build -t protojx_manager . &&
|
||||||
|
|
||||||
|
# Run new container
|
||||||
|
docker run -d \
|
||||||
|
--name protojx_manager \
|
||||||
|
--restart unless-stopped \
|
||||||
|
--network shared \
|
||||||
|
protojx_manager
|
||||||
|
"
|
||||||
@@ -1,8 +1,12 @@
|
|||||||
FROM node:22
|
FROM node:22
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y iputils-ping
|
RUN apt-get update && apt-get install -y iputils-ping
|
||||||
RUN npm i
|
RUN npm i
|
||||||
|
|
||||||
|
# RUN npm run register
|
||||||
|
|
||||||
CMD [ "npm", "run", "start" ]
|
CMD [ "npm", "run", "start" ]
|
||||||
10
README.md
10
README.md
@@ -1,4 +1,4 @@
|
|||||||
# Protojx Manager Non Official
|
# Protojx Manager
|
||||||
A status bot and other features for protojx.
|
A status bot and other features for protojx.
|
||||||
|
|
||||||
- Add the bot : https://discord.com/oauth2/authorize?client_id=1432680068085190656
|
- Add the bot : https://discord.com/oauth2/authorize?client_id=1432680068085190656
|
||||||
@@ -9,9 +9,11 @@ A status bot and other features for protojx.
|
|||||||
| Description | Status |
|
| Description | Status |
|
||||||
|-------------|--------|
|
|-------------|--------|
|
||||||
| /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. | 🌐 |
|
||||||
| Deployment workflow on Raspberry Pi. | ➖ |
|
| Ability to create persistent status messages that update automatically. (/live_status) | 🌐 |
|
||||||
|
| Deployment workflow on Oracle VPS. | 🌐 |
|
||||||
|
| Filter for notifs. | 🌐 |
|
||||||
|
|
||||||
- 🌐 -> In production
|
- 🌐 -> In production
|
||||||
- ✅ -> Done
|
- ✅ -> Done
|
||||||
|
|||||||
1520
package-lock.json
generated
1520
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -24,13 +24,18 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/Under-scape/discordbot-ts-template#readme",
|
"homepage": "https://github.com/Under-scape/discordbot-ts-template#readme",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/node": "^24.9.2",
|
||||||
"typescript": "^5.9.2"
|
"typescript": "^5.9.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/ping": "^0.4.4",
|
"@types/ping": "^0.4.4",
|
||||||
"cron": "^4.3.3",
|
"cron": "^4.3.3",
|
||||||
"discord.js": "^14.24.1",
|
"dayjs": "^1.11.19",
|
||||||
|
"discord.js": "^14.24.2",
|
||||||
"dotenv": "^17.2.2",
|
"dotenv": "^17.2.2",
|
||||||
"ping": "^1.0.0"
|
"pg": "^8.16.3",
|
||||||
|
"ping": "^1.0.0",
|
||||||
|
"reflect-metadata": "^0.2.2",
|
||||||
|
"typeorm": "^0.3.27"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
55
src/commands/utility/follow.command.ts
Normal file
55
src/commands/utility/follow.command.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { ApplicationIntegrationType, ChatInputCommandInteraction, InteractionContextType, MessageFlags, SlashCommandBuilder } from "discord.js";
|
||||||
|
import { CommandDefinition } from "../../type";
|
||||||
|
import { AppDataSource } from "../../data-source";
|
||||||
|
import { Follow } from "../../entity/follow.entity";
|
||||||
|
import statusService from "../../services/status.service";
|
||||||
|
|
||||||
|
const cmd : CommandDefinition = {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('follow')
|
||||||
|
.setDescription('Enables/disables the receipt of service status notifications.')
|
||||||
|
.setIntegrationTypes(
|
||||||
|
ApplicationIntegrationType.UserInstall
|
||||||
|
)
|
||||||
|
.setContexts(
|
||||||
|
InteractionContextType.BotDM,
|
||||||
|
InteractionContextType.Guild,
|
||||||
|
InteractionContextType.PrivateChannel
|
||||||
|
)
|
||||||
|
.addStringOption((option) =>
|
||||||
|
option
|
||||||
|
.setRequired(true)
|
||||||
|
.addChoices(...statusService.hosts.filter((v) => v.notify).map((s) => ({name: s.name, value: s.host})))
|
||||||
|
.setName('host')
|
||||||
|
.setDescription('Host enable/disable.')
|
||||||
|
),
|
||||||
|
async execute(interaction : ChatInputCommandInteraction) {
|
||||||
|
const userRepo = AppDataSource.getRepository(Follow);
|
||||||
|
const hostvalue = interaction.options.getString('host');
|
||||||
|
|
||||||
|
const realHost = statusService.hosts.filter((v) => v.host == hostvalue);
|
||||||
|
if(!hostvalue || realHost.length == 0) {
|
||||||
|
await interaction.reply({content: '⚠️ Host not found !', flags: [MessageFlags.Ephemeral]});
|
||||||
|
}else{
|
||||||
|
let follow = await userRepo.findOne({where: {user_discord: interaction.user.id, host: hostvalue}});
|
||||||
|
if(!follow) {
|
||||||
|
follow = new Follow();
|
||||||
|
follow.user_discord = interaction.user.id;
|
||||||
|
follow.host = hostvalue;
|
||||||
|
await userRepo.save(follow);
|
||||||
|
}
|
||||||
|
|
||||||
|
follow.enable = !follow.enable;
|
||||||
|
|
||||||
|
await userRepo.save(follow);
|
||||||
|
|
||||||
|
await interaction.reply({content: `✅ Notification successfully ${follow.enable ? 'enabled 🔔' : 'disabled 🔕'} for ${realHost[0]?.name}!`, flags: [MessageFlags.Ephemeral]});
|
||||||
|
|
||||||
|
if(follow.enable) {
|
||||||
|
await interaction.user.send({content: `🔔 Notifications have been successfully enabled for ${realHost[0]?.name} ! To disable: /follow host:${realHost[0]?.name}`})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default cmd;
|
||||||
82
src/commands/utility/live_status.command.ts
Normal file
82
src/commands/utility/live_status.command.ts
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
import { ApplicationIntegrationType, ChannelType, ContainerBuilder, MessageFlags, PermissionFlagsBits, 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
|
||||||
|
)
|
||||||
|
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator),
|
||||||
|
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()) {
|
||||||
|
let message;
|
||||||
|
try {
|
||||||
|
message = await channel.send({components: [statusService.getUpdatedContainer(true)], flags: [MessageFlags.IsComponentsV2]});
|
||||||
|
} catch (error) {
|
||||||
|
await interaction.editReply({content: 'An error has occurred. Please check the permissions for the channel.'});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { ApplicationIntegrationType, ChatInputCommandInteraction, CommandInteraction, InteractionContextType, SlashCommandBuilder } from "discord.js";
|
import { ApplicationIntegrationType, ButtonInteraction, ButtonStyle, ChatInputCommandInteraction, ContainerBuilder, InteractionContextType, MessageFlags, SlashCommandBuilder } from "discord.js";
|
||||||
|
import { CommandDefinition } from "../../type";
|
||||||
|
|
||||||
export default {
|
const cmd : CommandDefinition = {
|
||||||
data: new SlashCommandBuilder()
|
data: new SlashCommandBuilder()
|
||||||
.setName('ping')
|
.setName('ping')
|
||||||
.setDescription('Pong again!')
|
.setDescription('Pong again!')
|
||||||
@@ -14,6 +15,35 @@ export default {
|
|||||||
InteractionContextType.PrivateChannel
|
InteractionContextType.PrivateChannel
|
||||||
),
|
),
|
||||||
async execute(interaction : ChatInputCommandInteraction) {
|
async execute(interaction : ChatInputCommandInteraction) {
|
||||||
await interaction.reply(`🏓 Latency is ${Date.now() - interaction.createdTimestamp}ms. API Latency : ${interaction.client.ws.ping}ms`);
|
|
||||||
}
|
const container = new ContainerBuilder()
|
||||||
|
.addTextDisplayComponents((textDisplay) => textDisplay.setContent(`🏓 Latency is ${Date.now() - interaction.createdTimestamp}ms. API Latency : ${interaction.client.ws.ping}ms`))
|
||||||
|
.addSeparatorComponents((s) => s)
|
||||||
|
.addSectionComponents((section) =>
|
||||||
|
section
|
||||||
|
.addTextDisplayComponents((textDisplay) =>
|
||||||
|
textDisplay
|
||||||
|
.setContent('Oh, that\'s a beautiful button!')
|
||||||
|
)
|
||||||
|
.setButtonAccessory((button) =>
|
||||||
|
button
|
||||||
|
.setCustomId('testClick')
|
||||||
|
.setLabel('Click Me !')
|
||||||
|
.setStyle(ButtonStyle.Success)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// await interaction.reply();
|
||||||
|
await interaction.reply({
|
||||||
|
components: [container],
|
||||||
|
flags: MessageFlags.IsComponentsV2
|
||||||
|
})
|
||||||
|
},
|
||||||
|
buttons: [
|
||||||
|
{id: 'testClick', handle: (interaction : ButtonInteraction) => {
|
||||||
|
interaction.reply({content: 'Ho !', flags: [MessageFlags.Ephemeral]})
|
||||||
|
}}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default cmd;
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { ApplicationIntegrationType, ChatInputCommandInteraction, CommandInteraction, EmbedBuilder, InteractionContextType, SlashCommandBuilder } from "discord.js";
|
import { ApplicationIntegrationType, ChatInputCommandInteraction, InteractionContextType, MessageFlags, SlashCommandBuilder } from "discord.js";
|
||||||
import ping from "ping";
|
|
||||||
import statusService from "../../services/status.service";
|
import statusService from "../../services/status.service";
|
||||||
|
import { CommandDefinition} from "../../type";
|
||||||
|
|
||||||
export default {
|
const cmd : CommandDefinition = {
|
||||||
data: new SlashCommandBuilder()
|
data: new SlashCommandBuilder()
|
||||||
.setName('status')
|
.setName('status')
|
||||||
.setDescription('Give statut of servers.')
|
.setDescription('Give statut of servers.')
|
||||||
@@ -17,17 +17,8 @@ export default {
|
|||||||
),
|
),
|
||||||
async execute(interaction : ChatInputCommandInteraction) {
|
async execute(interaction : ChatInputCommandInteraction) {
|
||||||
await interaction.deferReply();
|
await interaction.deferReply();
|
||||||
|
await interaction.editReply({components: [statusService.getUpdatedContainer()], flags: MessageFlags.IsComponentsV2});
|
||||||
const embed = new EmbedBuilder();
|
|
||||||
embed.setTitle('Status of protojx servers');
|
|
||||||
embed.setColor(0xffffff);
|
|
||||||
embed.setTimestamp(new Date());
|
|
||||||
embed.setThumbnail(interaction.client.user.avatarURL())
|
|
||||||
|
|
||||||
for(let host of statusService.hosts){
|
|
||||||
embed.addFields({name: host.name, value: host.alive ? `${process.env.EMOJI_STATUS_ONLINE} Online` : `${process.env.EMOJI_STATUS_OFFLINE} Offline`, inline: false});
|
|
||||||
}
|
|
||||||
|
|
||||||
await interaction.editReply({embeds: [embed]});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default cmd;
|
||||||
19
src/data-source.ts
Normal file
19
src/data-source.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import "reflect-metadata"
|
||||||
|
import { DataSource } from "typeorm"
|
||||||
|
import { configDotenv } from "dotenv"
|
||||||
|
|
||||||
|
configDotenv()
|
||||||
|
|
||||||
|
export const AppDataSource = new DataSource({
|
||||||
|
type: "postgres",
|
||||||
|
host: process.env.DB_HOST || "localhost",
|
||||||
|
port: parseInt(process.env.DB_PORT || "5432"),
|
||||||
|
username: process.env.DB_USERNAME || "postgres",
|
||||||
|
password: process.env.DB_PASSWORD,
|
||||||
|
database: process.env.DB_DATABASE,
|
||||||
|
synchronize: process.env.NODE_ENV !== "production",
|
||||||
|
logging: process.env.DB_LOGGING === "true",
|
||||||
|
entities: [__dirname + '/**/*.entity.js'],
|
||||||
|
migrations: [__dirname + "/**/*.migration.js"],
|
||||||
|
subscribers: [__dirname + "/**/*.subscriber.js"],
|
||||||
|
})
|
||||||
@@ -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) {
|
||||||
|
|||||||
16
src/entity/follow.entity.ts
Normal file
16
src/entity/follow.entity.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
|
||||||
|
|
||||||
|
@Entity({name: 'follows'})
|
||||||
|
export class Follow {
|
||||||
|
@PrimaryGeneratedColumn()
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
user_discord: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
host: string;
|
||||||
|
|
||||||
|
@Column({default: false})
|
||||||
|
enable: boolean;
|
||||||
|
}
|
||||||
16
src/entity/guild.entity.ts
Normal file
16
src/entity/guild.entity.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
|
||||||
|
|
||||||
|
@Entity({name: 'guilds'})
|
||||||
|
export class Guild {
|
||||||
|
@PrimaryGeneratedColumn()
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
guild_id: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
persistent_message_id: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
persistent_message_channel_id: string;
|
||||||
|
}
|
||||||
16
src/entity/hostslog.entity.ts
Normal file
16
src/entity/hostslog.entity.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn } from "typeorm";
|
||||||
|
|
||||||
|
@Entity({name: 'hosts_logs'})
|
||||||
|
export class HostsLog {
|
||||||
|
@PrimaryGeneratedColumn()
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
host: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
status: boolean;
|
||||||
|
|
||||||
|
@CreateDateColumn()
|
||||||
|
created_at: Date;
|
||||||
|
}
|
||||||
39
src/index.ts
39
src/index.ts
@@ -4,8 +4,18 @@ import path from "path";
|
|||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import statusService from "./services/status.service";
|
import statusService from "./services/status.service";
|
||||||
|
|
||||||
|
import "reflect-metadata";
|
||||||
|
import { AppDataSource } from "./data-source";
|
||||||
|
import { CommandDefinition } from "./type";
|
||||||
|
|
||||||
configDotenv();
|
configDotenv();
|
||||||
|
|
||||||
|
AppDataSource.initialize()
|
||||||
|
.then(() => {
|
||||||
|
console.log("Data Source initialized !")
|
||||||
|
})
|
||||||
|
.catch((error) => console.log(error));
|
||||||
|
|
||||||
const client = new Client({
|
const client = new Client({
|
||||||
intents: [
|
intents: [
|
||||||
GatewayIntentBits.Guilds,
|
GatewayIntentBits.Guilds,
|
||||||
@@ -15,8 +25,7 @@ const client = new Client({
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
// @ts-expect-error
|
const commands = new Collection<string, CommandDefinition>();
|
||||||
client.commands = new Collection();
|
|
||||||
|
|
||||||
const foldersPath = path.join(__dirname, 'commands');
|
const foldersPath = path.join(__dirname, 'commands');
|
||||||
const commandFolders = fs.readdirSync(foldersPath);
|
const commandFolders = fs.readdirSync(foldersPath);
|
||||||
@@ -29,8 +38,7 @@ for (const folder of commandFolders) {
|
|||||||
const command = require(filePath);
|
const command = require(filePath);
|
||||||
const commandModule = command.default || command;
|
const commandModule = command.default || command;
|
||||||
if ('data' in commandModule && 'execute' in commandModule) {
|
if ('data' in commandModule && 'execute' in commandModule) {
|
||||||
// @ts-expect-error
|
commands.set(commandModule.data.name, commandModule);
|
||||||
client.commands.set(commandModule.data.name, commandModule);
|
|
||||||
} else {
|
} else {
|
||||||
console.log(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`);
|
console.log(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`);
|
||||||
}
|
}
|
||||||
@@ -38,10 +46,26 @@ for (const folder of commandFolders) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
client.on(Events.InteractionCreate, async interaction => {
|
client.on(Events.InteractionCreate, async interaction => {
|
||||||
|
|
||||||
|
if(interaction.isButton()) {
|
||||||
|
|
||||||
|
const id = interaction.customId;
|
||||||
|
|
||||||
|
commands.forEach((value, key) => {
|
||||||
|
if(value.buttons) {
|
||||||
|
const button = value.buttons.filter((b) => b.id == id);
|
||||||
|
if(button.length == 1) {
|
||||||
|
button[0]?.handle(interaction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!interaction.isChatInputCommand()) return;
|
if (!interaction.isChatInputCommand()) return;
|
||||||
|
|
||||||
// @ts-expect-error
|
const command = commands.get(interaction.commandName);
|
||||||
const command = interaction.client.commands.get(interaction.commandName);
|
|
||||||
|
|
||||||
if (!command) {
|
if (!command) {
|
||||||
console.error(`No command matching ${interaction.commandName} was found.`);
|
console.error(`No command matching ${interaction.commandName} was found.`);
|
||||||
@@ -62,6 +86,9 @@ client.on(Events.InteractionCreate, async interaction => {
|
|||||||
|
|
||||||
client.once(Events.ClientReady, readyClient => {
|
client.once(Events.ClientReady, readyClient => {
|
||||||
console.log(`Ready! Logged in as ${readyClient.user.tag}`);
|
console.log(`Ready! Logged in as ${readyClient.user.tag}`);
|
||||||
|
client.guilds.cache.forEach((value) => {
|
||||||
|
console.log(`${value.name} conf loaded !`);
|
||||||
|
});
|
||||||
statusService.setClient(client);
|
statusService.setClient(client);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,78 +1,182 @@
|
|||||||
import ping from "ping";
|
import ping from "ping";
|
||||||
import * as cron from 'cron';
|
import * as cron from 'cron';
|
||||||
import { ActivityType, Client } from "discord.js";
|
import { ActivityType, Client, ContainerBuilder, MessageFlags } from "discord.js";
|
||||||
|
import { Host, InfraType } from "../type";
|
||||||
|
import { AppDataSource } from "../data-source";
|
||||||
|
import { HostsLog } from "../entity/hostslog.entity";
|
||||||
|
import { Repository } from "typeorm";
|
||||||
|
import { Follow } from "../entity/follow.entity";
|
||||||
|
import { Guild } from "../entity/guild.entity";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
|
type Nofity = {time: Date, name : string, alive : boolean, type : InfraType, host: string};
|
||||||
|
|
||||||
export class StatusService {
|
export class StatusService {
|
||||||
|
|
||||||
public hosts : Host[] = [
|
public hosts: Host[] = [
|
||||||
{
|
{
|
||||||
'host': 'https://protojx.com',
|
host: 'https://protojx.com',
|
||||||
'name': 'Protojx Website 🌐',
|
name: 'Protojx Website',
|
||||||
alive: false,
|
alive: false,
|
||||||
type: 'website'
|
ping_type: 'website',
|
||||||
|
type: 'website',
|
||||||
|
notify: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'host': 'https://manager.protojx.com',
|
host: 'https://manager.protojx.com',
|
||||||
'name': 'Espace Client 💻',
|
name: 'Espace Client',
|
||||||
alive: false,
|
alive: false,
|
||||||
type: 'website'
|
ping_type: 'website',
|
||||||
|
type: 'website',
|
||||||
|
notify: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
host: '5.178.99.4',
|
host: '5.178.99.4',
|
||||||
name: 'RYZEN 01 🖥️',
|
name: 'RYZEN 01',
|
||||||
alive: false,
|
alive: false,
|
||||||
type: 'ping'
|
ping_type: 'ping',
|
||||||
|
type: 'ryzen',
|
||||||
|
notify: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
host: '5.178.99.6',
|
host: '5.178.99.6',
|
||||||
name: 'RYZEN 02 🖥️',
|
name: 'RYZEN 02',
|
||||||
alive: false,
|
alive: false,
|
||||||
type: 'ping'
|
ping_type: 'ping',
|
||||||
|
type: 'ryzen',
|
||||||
|
notify: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
host: '5.178.99.5',
|
host: '5.178.99.5',
|
||||||
name: 'RYZEN 03 🖥️',
|
name: 'RYZEN 03',
|
||||||
alive: false,
|
alive: false,
|
||||||
type: 'ping'
|
ping_type: 'ping',
|
||||||
|
type: 'ryzen',
|
||||||
|
notify: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
host: '154.16.254.10',
|
||||||
|
name: 'RYZEN7 04',
|
||||||
|
alive: false,
|
||||||
|
ping_type: 'ping',
|
||||||
|
type: 'ryzen',
|
||||||
|
notify: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
host: '5.178.99.177',
|
host: '5.178.99.177',
|
||||||
name: 'XEON 01 (2697A V4) 🖥️',
|
name: 'XEON 01',
|
||||||
alive: false,
|
alive: false,
|
||||||
type: 'ping'
|
ping_type: 'ping',
|
||||||
|
type: 'xeon',
|
||||||
|
notify: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
host: '5.178.99.248',
|
host: '154.16.254.45',
|
||||||
name: 'XEON 02 (2687W V4) 🖥️',
|
name: 'XEON 02',
|
||||||
alive: false,
|
alive: false,
|
||||||
type: 'ping'
|
ping_type: 'ping',
|
||||||
|
type: 'xeon',
|
||||||
|
notify: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
host: '5.178.99.232',
|
||||||
|
name: 'XEON 03',
|
||||||
|
alive: false,
|
||||||
|
ping_type: 'ping',
|
||||||
|
type: 'xeon',
|
||||||
|
notify: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
host: '5.178.99.53',
|
host: '5.178.99.53',
|
||||||
name: 'RYZEN-GAME 01 👾',
|
name: 'RYZEN-GAME 01',
|
||||||
alive: false,
|
alive: false,
|
||||||
type: 'ping'
|
ping_type: 'ping',
|
||||||
|
type: 'games',
|
||||||
|
notify: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
host: '5.178.99.17',
|
||||||
|
name: 'RYZEN-GAME 02',
|
||||||
|
alive: false,
|
||||||
|
ping_type: 'ping',
|
||||||
|
type: 'games',
|
||||||
|
notify: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
host: '5.178.99.63',
|
host: '5.178.99.63',
|
||||||
name: 'XEON-GAME 01 👾',
|
name: 'XEON-GAME 01',
|
||||||
alive: false,
|
alive: false,
|
||||||
type: 'ping'
|
ping_type: 'ping',
|
||||||
|
type: 'games',
|
||||||
|
notify: true
|
||||||
|
},
|
||||||
|
// Routers
|
||||||
|
{
|
||||||
|
host: process.env.PROTOJX_ROUTER_1 as string,
|
||||||
|
name: 'ROUTER-FR 01',
|
||||||
|
alive: false,
|
||||||
|
ping_type: 'ping',
|
||||||
|
type: 'router',
|
||||||
|
notify: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
host: process.env.PROTOJX_ROUTER_2 as string,
|
||||||
|
name: 'ROUTER-FR 02',
|
||||||
|
alive: false,
|
||||||
|
ping_type: 'ping',
|
||||||
|
type: 'router',
|
||||||
|
notify: false
|
||||||
}
|
}
|
||||||
]
|
];
|
||||||
|
|
||||||
private client : Client|null = null;
|
private client: Client | null = null;
|
||||||
|
private hostsLogRepo: Repository<HostsLog>;
|
||||||
|
private followRepo: Repository<Follow>;
|
||||||
|
private guildRepo: Repository<Guild>;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|
||||||
|
this.hostsLogRepo = AppDataSource.getRepository(HostsLog);
|
||||||
|
this.followRepo = AppDataSource.getRepository(Follow);
|
||||||
|
this.guildRepo = AppDataSource.getRepository(Guild);
|
||||||
|
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
await this.fetch()
|
await this.fetch()
|
||||||
this.updateClientStatus();
|
this.updateClientStatus();
|
||||||
}, 3000);
|
}, 3000);
|
||||||
const cronJob = new cron.CronJob('*/2 * * * *', async () => {
|
const cronJob = new cron.CronJob('*/2 * * * *', async () => {
|
||||||
|
|
||||||
|
// ? cleanup logs
|
||||||
|
const oneMonthAgo = new Date();
|
||||||
|
oneMonthAgo.setMonth(oneMonthAgo.getMonth() - 1);
|
||||||
|
this.hostsLogRepo.
|
||||||
|
createQueryBuilder()
|
||||||
|
.delete()
|
||||||
|
.from(HostsLog)
|
||||||
|
.where("created_at < :date", { date: oneMonthAgo })
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
// ? Get status
|
||||||
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);
|
||||||
@@ -82,55 +186,159 @@ export class StatusService {
|
|||||||
console.log('Status monitoring started - checking every 2 minutes');
|
console.log('Status monitoring started - checking every 2 minutes');
|
||||||
}
|
}
|
||||||
|
|
||||||
setClient(client : Client) {
|
setClient(client: Client) {
|
||||||
this.client = client;
|
this.client = client;
|
||||||
this.client.user?.setActivity({name: '💭 Server load and status...'})
|
this.client.user?.setActivity({ name: '💭 Server load and status...' })
|
||||||
}
|
}
|
||||||
|
|
||||||
private async updateClientStatus() {
|
private async updateClientStatus() {
|
||||||
if(this.client) {
|
if (this.client) {
|
||||||
const hosts = this.hosts.length;
|
const hosts = this.hosts.length;
|
||||||
const hostsAlive = this.hosts.filter((h) => h.alive).length;
|
const hostsAlive = this.hosts.filter((h) => h.alive).length;
|
||||||
|
|
||||||
this.client.user?.setActivity({name: (
|
this.client.user?.setActivity({
|
||||||
hosts == hostsAlive ? '✅ All services are online.' : `📛 ${hosts - hostsAlive} service${hosts - hostsAlive > 1 ? 's' : ''} offline.`
|
name: (
|
||||||
), type: ActivityType.Watching});
|
hosts == hostsAlive ? '✅ All services are online.' : `📛 ${hosts - hostsAlive} service${hosts - hostsAlive > 1 ? 's' : ''} offline.`
|
||||||
|
), type: ActivityType.Watching
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async fetch(max = 1): Promise<Host[]> {
|
private async fetchAlive(host: Host, notifs : Nofity[]) {
|
||||||
|
|
||||||
const hosts = this.hosts.filter((value, index) => index < max * 10 && index >= (max - 1) * 10);
|
const latestLog = await this.hostsLogRepo.findOne({ where: { host: host.host }, order: { created_at: 'DESC' } });
|
||||||
async function fetchAlive(host: Host) {
|
|
||||||
if(host.type === 'ping'){
|
// ? Ping and Request Hosts
|
||||||
let res = await ping.promise.probe(host.host, {timeout: 3});
|
if (host.ping_type === 'ping') {
|
||||||
host.alive = res.alive;
|
let res = await ping.promise.probe(host.host, { timeout: 10 });
|
||||||
}else if(host.type === 'website'){
|
host.alive = res.alive;
|
||||||
try {
|
} else if (host.ping_type === 'website') {
|
||||||
const response = await fetch(host.host, { method: 'HEAD', signal: AbortSignal.timeout(3000) });
|
try {
|
||||||
host.alive = response.ok;
|
const response = await fetch(host.host, { method: 'HEAD', signal: AbortSignal.timeout(10000) });
|
||||||
} catch (error) {
|
host.alive = response.ok;
|
||||||
host.alive = false;
|
} catch (error) {
|
||||||
}
|
host.alive = false;
|
||||||
}
|
}
|
||||||
return host;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchPromises = hosts.map(host => fetchAlive(host));
|
// ? Notification System :
|
||||||
|
if (!latestLog || latestLog.status != host.alive) {
|
||||||
|
const log = new HostsLog();
|
||||||
|
log.host = host.host;
|
||||||
|
log.status = host.alive;
|
||||||
|
|
||||||
|
this.hostsLogRepo.save(log);
|
||||||
|
|
||||||
|
if(latestLog && host.notify) {
|
||||||
|
notifs.push({alive: host.alive, name: host.name, time: new Date(), type: host.type, host: host.host});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async fetch(max = 1, notifs : Nofity[] = []) {
|
||||||
|
|
||||||
|
const max_ping = 3;
|
||||||
|
|
||||||
|
const hosts = this.hosts.filter((value, index) => index < max * max_ping && index >= (max - 1) * max_ping);
|
||||||
|
|
||||||
|
const fetchPromises = hosts.map(host => this.fetchAlive(host, notifs));
|
||||||
const updatedHosts = await Promise.all(fetchPromises);
|
const updatedHosts = await Promise.all(fetchPromises);
|
||||||
|
|
||||||
updatedHosts.forEach((updatedHost, index) => {
|
updatedHosts.forEach((updatedHost, index) => {
|
||||||
const originalIndex = (max - 1) * 10 + index;
|
const originalIndex = (max - 1) * max_ping + index;
|
||||||
if (originalIndex < this.hosts.length) {
|
if (originalIndex < this.hosts.length) {
|
||||||
this.hosts[originalIndex] = updatedHost;
|
this.hosts[originalIndex] = updatedHost;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if(this.hosts.length > max * 10) {
|
if (this.hosts.length > max * max_ping) {
|
||||||
await this.fetch(max + 1);
|
await this.fetch(max + 1, notifs);
|
||||||
}
|
}else if(notifs.length > 0){
|
||||||
|
// ? Notification System (part 2 !):
|
||||||
|
const container = new ContainerBuilder()
|
||||||
|
.addTextDisplayComponents((t) => t.setContent(`### 🔔 Status change alert`));
|
||||||
|
|
||||||
return this.hosts;
|
notifs.map(async (n) => {
|
||||||
|
container.addSeparatorComponents((s)=>s);
|
||||||
|
container.addTextDisplayComponents((text) => text.setContent(`${n.alive ? process.env.EMOJI_STATUS_ONLINE : process.env.EMOJI_STATUS_OFFLINE} **${n.name}** is now **${n.alive ? 'online' : 'offline'}**\n🏷️ Type : ${n.type}\n🕒 Time : <t:${Math.round(new Date().getTime()/1000)}:R>`));
|
||||||
|
});
|
||||||
|
|
||||||
|
const users = await this.followRepo.find({where: {enable: true}});
|
||||||
|
const hosts = notifs.map((n) => n.host);
|
||||||
|
const users_ids : string[] = [];
|
||||||
|
users.filter(v => hosts.includes(v.host)).forEach(async (user) => {
|
||||||
|
if(!users_ids.includes(user.user_discord)) {
|
||||||
|
users_ids.push(user.user_discord)
|
||||||
|
try {
|
||||||
|
const userdc = await this.client?.users.fetch(user.user_discord);
|
||||||
|
if(userdc) {
|
||||||
|
userdc.send({components: [container], flags: [MessageFlags.IsComponentsV2]})
|
||||||
|
}
|
||||||
|
} catch (error) {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public getUpdatedContainer(live : boolean = false): ContainerBuilder {
|
||||||
|
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`}` };
|
||||||
|
});
|
||||||
|
|
||||||
|
const container = new ContainerBuilder()
|
||||||
|
.setAccentColor(0x0000ed)
|
||||||
|
.addTextDisplayComponents((text) => text.setContent('# Status of Protojx services'+(live ? ' (live)' : '')));
|
||||||
|
|
||||||
|
const sections: { title: string, type: InfraType, thumbnail: string }[] = [
|
||||||
|
{
|
||||||
|
title: 'Websites',
|
||||||
|
type: 'website',
|
||||||
|
thumbnail: 'https://protojx.com/assets/img/home2/agent.png'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Ryzens',
|
||||||
|
type: 'ryzen',
|
||||||
|
thumbnail: 'https://iconape.com/wp-content/png_logo_vector/ryzen.png'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Xeons',
|
||||||
|
type: 'xeon',
|
||||||
|
thumbnail: 'https://upload.wikimedia.org/wikipedia/commons/3/31/Intel-Xeon-Badge-2024.jpg'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Games',
|
||||||
|
type: 'games',
|
||||||
|
thumbnail: 'https://protojx.com/assets/img/hero-img.png'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Routers\n-# *The data displayed here is not real data but demonstration data. (Beta)*',
|
||||||
|
type: 'router',
|
||||||
|
thumbnail: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRMnCmtQRkLlcD1Cb6vKXz6NOxAu79vzmq2pRqpNYxpTJa5JQEsouhqnVn7cyl6ivYSyzY&usqp=CAU'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
sections.map((sectionData) => {
|
||||||
|
container.addSeparatorComponents((s) => s)
|
||||||
|
container.addSectionComponents(
|
||||||
|
(section) =>
|
||||||
|
section.addTextDisplayComponents(
|
||||||
|
(text) =>
|
||||||
|
text.setContent('## ' + sectionData.title + '\n' + hostTexts.filter((v) => v.type == sectionData.type).map((v) => v.value).join('\n'))
|
||||||
|
)
|
||||||
|
.setThumbnailAccessory(
|
||||||
|
(acc) =>
|
||||||
|
acc.setURL(sectionData.thumbnail)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
container.addSeparatorComponents((s) => s);
|
||||||
|
|
||||||
|
container.addTextDisplayComponents((text) => text.setContent(`:globe_with_meridians: Website Status : https://statut.protojx.com/\n${live ? 'Last update : ' : ''}<t:${dayjs().unix()}:f> - Receive automatic notifications when there is an outage with /follow !`));
|
||||||
|
|
||||||
|
return container;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
13
src/type.d.ts
vendored
13
src/type.d.ts
vendored
@@ -1 +1,12 @@
|
|||||||
type Host = { host: string, name: string, alive: boolean, type: 'ping' | 'website' };
|
import { ButtonInteraction, ChatInputCommandInteraction, SlashCommandBuilder } from "discord.js";
|
||||||
|
|
||||||
|
export type InfraType = 'website' | 'ryzen' | 'xeon' | 'games' | 'router';
|
||||||
|
export type Host = {
|
||||||
|
host: string,
|
||||||
|
name: string,
|
||||||
|
alive: boolean,
|
||||||
|
ping_type: 'ping' | 'website',
|
||||||
|
type: InfraType,
|
||||||
|
notify: boolean;
|
||||||
|
};
|
||||||
|
export type CommandDefinition = { data: SlashCommandBuilder | SlashCommandOptionsOnlyBuilder, execute: (interaction: ChatInputCommandInteraction) => void, buttons?: { id: string, handle: (interaction: ButtonInteraction) => void}[]};
|
||||||
@@ -24,7 +24,9 @@
|
|||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
"removeComments": false,
|
"removeComments": false,
|
||||||
"preserveConstEnums": true
|
"preserveConstEnums": true,
|
||||||
|
"strictPropertyInitialization": false,
|
||||||
|
"noUnusedLocals": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"src/**/*"
|
"src/**/*"
|
||||||
|
|||||||
Reference in New Issue
Block a user