From 6fb2a3f4e0460ef5c46003dce3501e49af48a1d1 Mon Sep 17 00:00:00 2001 From: Skylar Grant Date: Sat, 12 Aug 2023 22:10:05 -0400 Subject: [PATCH] Add status role menu+heartbeat+impr CI+logging --- .github/workflows/docker-image.yml | 31 ++++++++++++++--------- Dockerfile | 4 +-- data/strings.json | 8 +++--- main.js | 15 +++++++++++ modules/functions.js | 40 ++++++++++++++++++++++++++---- slash-commands/rolemenu.js | 2 +- 6 files changed, 77 insertions(+), 23 deletions(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 22c16a1..60b6003 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -1,7 +1,12 @@ -name: Voidbot Dockerization +name: voidbot Production Dockerization on: - workflow_dispatch: + push: + branches: + - main + merge: + branches: + - main env: DHUB_UNAME: ${{ secrets.DHUB_UNAME }} @@ -11,19 +16,21 @@ jobs: build: - runs-on: ubuntu-latest + runs-on: self-hosted 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 - run: docker build . --file Dockerfile --tag v0idf1sh/voidbot + run: | + cd /root/voidbot + docker build . --file Dockerfile --tag v0idf1sh/voidbot - name: Log into Docker Hub run: docker login -u $DHUB_UNAME -p $DHUB_PWORD - name: Push image to Docker Hub - run: docker push v0idf1sh/voidbot - - name: Set up a skeleton .env file - run: echo "TOKEN=${{secrets.TOKEN}}" > .env && echo "BOTID=${{ secrets.BOTID }}" >> .env - - name: Install modules - run: npm i - - name: Refresh commands with Discord - run: node modules/_deploy-global.js \ No newline at end of file + run: | + cd /root/voidbot + docker push v0idf1sh/voidbot \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index a0b785a..6df93c4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,8 @@ -FROM node:16 +FROM node:18 RUN mkdir -p /usr/src/app WORKDIR /usr/src/app COPY package.json ./ RUN npm install COPY . . -CMD [ "node", "main.js" ] \ No newline at end of file +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"] \ No newline at end of file diff --git a/data/strings.json b/data/strings.json index ee29d78..c1c4add 100644 --- a/data/strings.json +++ b/data/strings.json @@ -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!", "rulesFooter": "Use the Accept Rules button to gain access to the rest of the server.", "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." }, "emoji": { @@ -20,11 +20,13 @@ "confirm": "☑️", "cancel": "❌", "water": "💧", - "fruit": "🍎" + "fruit": "🍎", + "status": "📟" }, "roleIds": { "member": "1048328885118435368", "waterPings": "1069416740389404763", - "fruitPings": "1073698485376921602" + "fruitPings": "1073698485376921602", + "statusPings": "1140098947306750073" } } \ No newline at end of file diff --git a/main.js b/main.js index 78127b2..d35d463 100644 --- a/main.js +++ b/main.js @@ -4,6 +4,8 @@ const dotenv = require('dotenv'); dotenv.config(); const token = process.env.TOKEN; +const heartbeatUrl = process.env.HEARTBEAT_URL; +const sendHeartbeat = typeof heartbeatUrl === 'string'; // Discord.JS const { Client, GatewayIntentBits } = require('discord.js'); @@ -28,6 +30,14 @@ client.once('ready', () => { }).catch(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 @@ -58,6 +68,11 @@ client.on('interactionCreate', async interaction => { console.error("Error handling fruit ping button: " + err); }); break; + case 'statuspingrole': + await fn.buttonHandlers.statusPing(interaction).catch(err => { + console.error("Error handling status ping button: " + err); + }); + break; default: break; } diff --git a/modules/functions.js b/modules/functions.js index 58c287c..3d0bea7 100644 --- a/modules/functions.js +++ b/modules/functions.js @@ -6,6 +6,7 @@ const isDev = process.env.isDev; // filesystem const fs = require('fs'); +const https = require('https'); // Discord.js const Discord = require('discord.js'); @@ -41,11 +42,12 @@ const functions = { this.buttons.acceptRules() ); }, - treeRoleMenu() { + roleMenu() { return new ActionRowBuilder() .addComponents( this.buttons.waterPing(), - this.buttons.fruitPing() + this.buttons.fruitPing(), + this.buttons.statusPing() ); }, buttons: { @@ -66,6 +68,12 @@ const functions = { .setCustomId('fruitpingrole') .setLabel(strings.emoji.fruit) .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 }); return { embeds: [embed], components: [actionRow] }; }, - treeRoleMenu() { - const actionRow = functions.builders.actionRows.treeRoleMenu(); + roleMenu() { + const actionRow = functions.builders.actionRows.roleMenu(); const embed = new EmbedBuilder() .setColor(strings.embeds.color) .setTitle(strings.embeds.roleMenuTitle) - .setDescription(strings.embeds.treeRoleMenu) + .setDescription(strings.embeds.roleMenuDesc) .setFooter({ text: strings.embeds.roleMenuFooter }); 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)); }, + 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) { const role = await functions.roles.fetchRole(interaction.guild, strings.roleIds.member); 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)); } + }, + 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)); } }; diff --git a/slash-commands/rolemenu.js b/slash-commands/rolemenu.js index b891225..b35df18 100644 --- a/slash-commands/rolemenu.js +++ b/slash-commands/rolemenu.js @@ -7,6 +7,6 @@ module.exports = { .setDescription('Send the role selection menu in the current channel'), async execute(interaction) { 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)); }, }; \ No newline at end of file