Add status role menu+heartbeat+impr CI+logging
All checks were successful
voidbot Production Dockerization / build (push) Successful in 53s

This commit is contained in:
Skylar Grant 2023-08-12 22:10:05 -04:00
parent 5600af9e58
commit 6fb2a3f4e0
6 changed files with 77 additions and 23 deletions

View File

@ -1,7 +1,12 @@
name: Voidbot Dockerization name: voidbot Production Dockerization
on: on:
workflow_dispatch: push:
branches:
- main
merge:
branches:
- main
env: env:
DHUB_UNAME: ${{ secrets.DHUB_UNAME }} DHUB_UNAME: ${{ secrets.DHUB_UNAME }}
@ -11,19 +16,21 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: self-hosted
steps: steps:
- uses: actions/checkout@v3 - name: Pull latest from Git
run: |
cd /root/voidbot
git pull
git checkout $GITHUB_HEAD_REF
- name: Build the Docker image - name: Build the Docker image
run: docker build . --file Dockerfile --tag v0idf1sh/voidbot run: |
cd /root/voidbot
docker build . --file Dockerfile --tag v0idf1sh/voidbot
- name: Log into Docker Hub - name: Log into Docker Hub
run: docker login -u $DHUB_UNAME -p $DHUB_PWORD run: docker login -u $DHUB_UNAME -p $DHUB_PWORD
- name: Push image to Docker Hub - name: Push image to Docker Hub
run: docker push v0idf1sh/voidbot run: |
- name: Set up a skeleton .env file cd /root/voidbot
run: echo "TOKEN=${{secrets.TOKEN}}" > .env && echo "BOTID=${{ secrets.BOTID }}" >> .env docker push v0idf1sh/voidbot
- name: Install modules
run: npm i
- name: Refresh commands with Discord
run: node modules/_deploy-global.js

View File

@ -1,8 +1,8 @@
FROM node:16 FROM node:18
RUN mkdir -p /usr/src/app RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app WORKDIR /usr/src/app
COPY package.json ./ COPY package.json ./
RUN npm install RUN npm install
COPY . . COPY . .
CMD [ "node", "main.js" ] CMD ["/bin/sh", "-c", "node main.js 2> /logs/$(date +%Y-%m-%d_%H-%M-%S)-error.txt 1> /logs/$(date +%Y-%m-%d_%H-%M-%S)-status.txt"]

View File

@ -11,7 +11,7 @@
"rules": "1. Show respect\n2. No politics\n3. No spam or self-promotion (server invites, advertisements, etc) without permission from a staff member. This includes DMing fellow members.\n4. No age-restricted or obscene content. This includes text, images, or links featuring nudity, sex, hard violence, or other graphically disturbing content.\n5. If you see something against the rules or something that makes you feel unsafe, let staff know. We want this server to be a welcoming space!", "rules": "1. Show respect\n2. No politics\n3. No spam or self-promotion (server invites, advertisements, etc) without permission from a staff member. This includes DMing fellow members.\n4. No age-restricted or obscene content. This includes text, images, or links featuring nudity, sex, hard violence, or other graphically disturbing content.\n5. If you see something against the rules or something that makes you feel unsafe, let staff know. We want this server to be a welcoming space!",
"rulesFooter": "Use the Accept Rules button to gain access to the rest of the server.", "rulesFooter": "Use the Accept Rules button to gain access to the rest of the server.",
"roleMenuTitle": "Role Menu", "roleMenuTitle": "Role Menu",
"treeRoleMenu": "Use the buttons below to give yourself roles.\n\n``💧`` - <@&1069416740389404763>: Get notifications when the tree is ready to be watered.\n``🍎`` - <@&1073698485376921602>: Get notifications when fruit is falling from the tree.", "roleMenuDesc": "Use the buttons below to give yourself roles.\n\n``💧`` - <@&1069416740389404763>: Get notifications when the tree is ready to be watered.\n``🍎`` - <@&1073698485376921602>: Get notifications when fruit is falling from the tree.\n``📟`` - <@&1140098947306750073>: Get notifications when Silvanus or Silvanus FE are down and for scheduled maintenance.",
"roleMenuFooter": "Tip: Tap the button again to remove the role." "roleMenuFooter": "Tip: Tap the button again to remove the role."
}, },
"emoji": { "emoji": {
@ -20,11 +20,13 @@
"confirm": "☑️", "confirm": "☑️",
"cancel": "❌", "cancel": "❌",
"water": "💧", "water": "💧",
"fruit": "🍎" "fruit": "🍎",
"status": "📟"
}, },
"roleIds": { "roleIds": {
"member": "1048328885118435368", "member": "1048328885118435368",
"waterPings": "1069416740389404763", "waterPings": "1069416740389404763",
"fruitPings": "1073698485376921602" "fruitPings": "1073698485376921602",
"statusPings": "1140098947306750073"
} }
} }

15
main.js
View File

@ -4,6 +4,8 @@
const dotenv = require('dotenv'); const dotenv = require('dotenv');
dotenv.config(); dotenv.config();
const token = process.env.TOKEN; const token = process.env.TOKEN;
const heartbeatUrl = process.env.HEARTBEAT_URL;
const sendHeartbeat = typeof heartbeatUrl === 'string';
// Discord.JS // Discord.JS
const { Client, GatewayIntentBits } = require('discord.js'); const { Client, GatewayIntentBits } = require('discord.js');
@ -28,6 +30,14 @@ client.once('ready', () => {
}).catch(err => { }).catch(err => {
console.error("Error sending status message: " + err); console.error("Error sending status message: " + err);
}); });
// Heartbeat Timer
if (sendHeartbeat) {
setInterval(() => {
fn.sendHeartbeat(heartbeatUrl);
}, 30000);
if (isDev) console.log("Heartbeat interval set.");
}
}); });
// slash-commands // slash-commands
@ -58,6 +68,11 @@ client.on('interactionCreate', async interaction => {
console.error("Error handling fruit ping button: " + err); console.error("Error handling fruit ping button: " + err);
}); });
break; break;
case 'statuspingrole':
await fn.buttonHandlers.statusPing(interaction).catch(err => {
console.error("Error handling status ping button: " + err);
});
break;
default: default:
break; break;
} }

View File

@ -6,6 +6,7 @@ const isDev = process.env.isDev;
// filesystem // filesystem
const fs = require('fs'); const fs = require('fs');
const https = require('https');
// Discord.js // Discord.js
const Discord = require('discord.js'); const Discord = require('discord.js');
@ -41,11 +42,12 @@ const functions = {
this.buttons.acceptRules() this.buttons.acceptRules()
); );
}, },
treeRoleMenu() { roleMenu() {
return new ActionRowBuilder() return new ActionRowBuilder()
.addComponents( .addComponents(
this.buttons.waterPing(), this.buttons.waterPing(),
this.buttons.fruitPing() this.buttons.fruitPing(),
this.buttons.statusPing()
); );
}, },
buttons: { buttons: {
@ -66,6 +68,12 @@ const functions = {
.setCustomId('fruitpingrole') .setCustomId('fruitpingrole')
.setLabel(strings.emoji.fruit) .setLabel(strings.emoji.fruit)
.setStyle(ButtonStyle.Primary); .setStyle(ButtonStyle.Primary);
},
statusPing() {
return new ButtonBuilder()
.setCustomId('statuspingrole')
.setLabel(strings.emoji.status)
.setStyle(ButtonStyle.Primary);
} }
} }
}, },
@ -107,12 +115,12 @@ const functions = {
.setFooter({ text: strings.embeds.rulesFooter }); .setFooter({ text: strings.embeds.rulesFooter });
return { embeds: [embed], components: [actionRow] }; return { embeds: [embed], components: [actionRow] };
}, },
treeRoleMenu() { roleMenu() {
const actionRow = functions.builders.actionRows.treeRoleMenu(); const actionRow = functions.builders.actionRows.roleMenu();
const embed = new EmbedBuilder() const embed = new EmbedBuilder()
.setColor(strings.embeds.color) .setColor(strings.embeds.color)
.setTitle(strings.embeds.roleMenuTitle) .setTitle(strings.embeds.roleMenuTitle)
.setDescription(strings.embeds.treeRoleMenu) .setDescription(strings.embeds.roleMenuDesc)
.setFooter({ text: strings.embeds.roleMenuFooter }); .setFooter({ text: strings.embeds.roleMenuFooter });
return { embeds: [embed], components: [actionRow] }; return { embeds: [embed], components: [actionRow] };
} }
@ -148,11 +156,33 @@ const functions = {
} }
await interaction.reply(functions.builders.embeds.info("Roles updated!")).catch(err => console.error(err)); await interaction.reply(functions.builders.embeds.info("Roles updated!")).catch(err => console.error(err));
}, },
async statusPing(interaction) {
const role = await functions.roles.fetchRole(interaction.guild, strings.roleIds.statusPings);
if (interaction.member.roles.cache.some(role => role.id == strings.roleIds.statusPings)) {
functions.roles.takeRole(interaction.member, role);
} else {
functions.roles.giveRole(interaction.member, role);
}
await interaction.reply(functions.builders.embeds.info("Roles updated!")).catch(err => console.error(err));
},
async acceptRules(interaction) { async acceptRules(interaction) {
const role = await functions.roles.fetchRole(interaction.guild, strings.roleIds.member); const role = await functions.roles.fetchRole(interaction.guild, strings.roleIds.member);
functions.roles.giveRole(interaction.member, role).catch(err => console.error(err)); functions.roles.giveRole(interaction.member, role).catch(err => console.error(err));
await interaction.reply(functions.builders.embeds.info("Roles updated!")).catch(err => console.error(err)); await interaction.reply(functions.builders.embeds.info("Roles updated!")).catch(err => console.error(err));
} }
},
async sendHeartbeat(url) {
if (isDev) console.log("Heartbeat Sent: " + url);
https.get(url, async (response) => {
let data = '';
response.on('data', (chunk) => data += chunk);
response.on('end', () => {
parsedData = JSON.parse(data);
if ( !(parsedData.ok) ) console.error("Heartbeat failed");
});
}).on("error", (error) => console.error(error));
} }
}; };

View File

@ -7,6 +7,6 @@ module.exports = {
.setDescription('Send the role selection menu in the current channel'), .setDescription('Send the role selection menu in the current channel'),
async execute(interaction) { async execute(interaction) {
await interaction.deferReply().catch(err => console.error(err)); await interaction.deferReply().catch(err => console.error(err));
await interaction.editReply(fn.builders.embeds.treeRoleMenu()).catch(err => console.error(err)); await interaction.editReply(fn.builders.embeds.roleMenu()).catch(err => console.error(err));
}, },
}; };