Skip to content
This repository has been archived by the owner on Jul 31, 2023. It is now read-only.

Commit

Permalink
Ready for basic use, rules and tree role menu
Browse files Browse the repository at this point in the history
  • Loading branch information
voidf1sh committed Feb 11, 2023
1 parent 0f79d61 commit 23b17bc
Show file tree
Hide file tree
Showing 12 changed files with 246 additions and 86 deletions.
16 changes: 10 additions & 6 deletions .github/workflows/docker-image.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
name: Docker Image CI
name: Voidbot Dockerization

on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
branches: [ "main" ]

env:
DHUB_UNAME: ${{ secrets.DHUB_UNAME }}
Expand All @@ -19,8 +17,14 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Build the Docker image
run: docker build . --file Dockerfile --tag v0idf1sh/treeanalyzer
run: 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/treeanalyzer
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
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
# Discord Bot Template
This is a very basic Discord.js v14 bot template. This is meant to be an easy jumping-off point for quick bot creation without having to set up the basics every time.
# VoidBot
This is a super simple bot developed specifically for use in my development support Discord server.

VoidBot will handle automatically giving roles to members of the server, allow users to self-assign some roles, and will send custom embeds like About and Help messages.
23 changes: 16 additions & 7 deletions data/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,26 @@
"permissions": ""
},
"embeds": {
"footer": "",
"color": "0x55FF55"
"footer": "Have a great day!",
"color": "0x5555FF",
"rulesTitle": "voidf1sh Development Server Rules",
"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.",
"roleMenuFooter": "Tip: Tap the button again to remove the role."
},
"emoji": {
"next": "⏭️",
"previous": "⏮️",
"confirm": "☑️",
"cancel": ""
"cancel": "",
"water": "💧",
"fruit": "🍎"
},
"urls": {
"avatar": ""
},
"temp": {}
"roleIds": {
"member": "1048328885118435368",
"waterPings": "1069416740389404763",
"fruitPings": "1073698485376921602"
}
}
48 changes: 30 additions & 18 deletions main.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,29 @@
const dotenv = require('dotenv');
dotenv.config();
const token = process.env.TOKEN;
const statusChannelId = process.env.statusChannelId;

// Discord.JS
const { Client, GatewayIntentBits, Partials } = require('discord.js');
const { Client, GatewayIntentBits } = require('discord.js');
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.GuildMessageReactions,
GatewayIntentBits.MessageContent
],
partials: [
Partials.Channel,
Partials.Message
],
GatewayIntentBits.Guilds
]
});

// Various imports
const fn = require('./modules/functions.js');
const strings = require('./data/strings.json');
const isDev = process.env.isDev;
const isDev = process.env.DEBUG;
const statusChannelId = process.env.STATUSCHANNELID;

client.once('ready', () => {
fn.collections.slashCommands(client);
// Build a collection of slash commands for the bot to use
fn.collectionBuilders.slashCommands(client);
console.log('Ready!');
client.channels.fetch(statusChannelId).then(channel => {
channel.send(`${new Date().toISOString()} -- Ready`);
}).catch(err => {
console.error("Error sending status message: " + err);
});
});

Expand All @@ -42,13 +38,29 @@ client.on('interactionCreate', async interaction => {
if (client.slashCommands.has(commandName)) {
client.slashCommands.get(commandName).execute(interaction);
} else {
interaction.reply('Sorry, I don\'t have access to that command.');
interaction.reply('Sorry, I don\'t have access to that command.').catch(err => console.error(err));
console.error('Slash command attempted to run but not found: /' + commandName);
}
}

if (interaction.isButton() && interaction.component.customId == 'refresh') {
fn.refresh(interaction);
} else if (interaction.isButton()) {
switch (interaction.component.customId) {
case 'acceptrules':
await fn.buttonHandlers.acceptRules(interaction).catch(err => {
console.error("Error handling rule acceptance: " + err);
});
break;
case 'waterpingrole':
await fn.buttonHandlers.waterPing(interaction).catch(err => {
console.error("Error handling water ping button: " + err);
});
break;
case 'fruitpingrole':
await fn.buttonHandlers.fruitPing(interaction).catch(err => {
console.error("Error handling fruit ping button: " + err);
});
break;
default:
break;
}
}
});

Expand Down
8 changes: 8 additions & 0 deletions modules/_cleanInput.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const path = './modules/input.txt';
const fs = require('fs');
const replaceAll = require('string.prototype.replaceall');
const string = fs.readFileSync(path).toString();
console.log(JSON.stringify(string));
let newString = replaceAll(string, '\r\n', '\\n');
fs.writeFileSync(path, newString);
return "Done";
6 changes: 3 additions & 3 deletions modules/_deploy-global.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,21 @@ dotenv.config();

const { REST } = require('@discordjs/rest');
const { Routes } = require('discord-api-types/v9');
const clientId = process.env.clientId;
const clientId = process.env.BOTID;
const token = process.env.TOKEN;
const fs = require('fs');

const commands = [];
const commandFiles = fs.readdirSync('./slash-commands').filter(file => file.endsWith('.js'));

for (const file of commandFiles) {
const command = require(`./slash-commands/${file}`);
const command = require(`../slash-commands/${file}`);
if (command.data != undefined) {
commands.push(command.data.toJSON());
}
}

console.log(commands);
// console.log(commands);

const rest = new REST({ version: '9' }).setToken(token);

Expand Down
5 changes: 5 additions & 0 deletions modules/buttons.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const strings = require('../data/strings.json');

module.exports = {

}
166 changes: 116 additions & 50 deletions modules/functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,12 @@ const { EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } = Discord;

// Various imports from other files
const config = require('../data/config.json');
let guildInfo = require('../data/guildInfo.json');
const strings = require('../data/strings.json');
const slashCommandFiles = fs.readdirSync('./slash-commands/').filter(file => file.endsWith('.js'));

const functions = {
// Functions for managing and creating Collections
collections: {
collectionBuilders: {
// Create the collection of slash commands
slashCommands(client) {
if (!client.slashCommands) client.slashCommands = new Discord.Collection();
Expand All @@ -34,59 +33,126 @@ const functions = {
}
},
builders: {
refreshAction() {
// Create the button to go in the Action Row
const refreshButton = new ButtonBuilder()
.setCustomId('refresh')
.setLabel('Refresh')
.setStyle(ButtonStyle.Primary);
// Create the Action Row with the Button in it, to be sent with the Embed
const refreshActionRow = new ActionRowBuilder()
.addComponents(
refreshButton
);
return refreshActionRow;
actionRows: {
acceptRules() {
// Create the Action Row with the Button in it, to be sent with the Embed
return new ActionRowBuilder()
.addComponents(
this.buttons.acceptRules()
);
},
treeRoleMenu() {
return new ActionRowBuilder()
.addComponents(
this.buttons.waterPing(),
this.buttons.fruitPing()
);
},
buttons: {
acceptRules() {
return new ButtonBuilder()
.setCustomId('acceptrules')
.setLabel(`${strings.emoji.confirm} Accept Rules`)
.setStyle(ButtonStyle.Primary);
},
waterPing() {
return new ButtonBuilder()
.setCustomId('waterpingrole')
.setLabel(strings.emoji.water)
.setStyle(ButtonStyle.Primary);
},
fruitPing() {
return new ButtonBuilder()
.setCustomId('fruitpingrole')
.setLabel(strings.emoji.fruit)
.setStyle(ButtonStyle.Primary);
}
}
},
helpEmbed(content, private) {
const embed = new EmbedBuilder()
.setColor(strings.embeds.color)
.setTitle('Grow A Tree Analyzer Help')
.setDescription(content)
.setFooter({ text: strings.embeds.footer });
const privateBool = private == 'true';
const messageContents = { embeds: [embed], ephemeral: privateBool };
return messageContents;
embeds: {
helpEmbed(content, private) {
const embed = new EmbedBuilder()
.setColor(strings.embeds.color)
.setTitle('Grow A Tree Analyzer Help')
.setDescription(content)
.setFooter({ text: strings.embeds.footer });
const privateBool = private == 'true';
const messageContents = { embeds: [embed], ephemeral: privateBool };
return messageContents;
},
errorEmbed(content) {
const embed = new EmbedBuilder()
.setColor(0xFF0000)
.setTitle('Error!')
.setDescription(content)
.setFooter({ text: strings.embeds.footer });
const messageContents = { embeds: [embed], ephemeral: true };
return messageContents;
},
info(content) {
const embed = new EmbedBuilder()
.setColor(0x8888FF)
.setTitle('Information')
.setDescription(content)
.setFooter({ text: strings.embeds.footer });
const messageContents = { embeds: [embed], ephemeral: true };
return messageContents;
},
rules() {
const actionRow = functions.builders.actionRows.acceptRules();
const embed = new EmbedBuilder()
.setColor(strings.embeds.color)
.setTitle(strings.embeds.rulesTitle)
.setDescription(strings.embeds.rules)
.setFooter({ text: strings.embeds.rulesFooter });
return { embeds: [embed], components: [actionRow] };
},
treeRoleMenu() {
const actionRow = functions.builders.actionRows.treeRoleMenu();
const embed = new EmbedBuilder()
.setColor(strings.embeds.color)
.setTitle(strings.embeds.roleMenuTitle)
.setDescription(strings.embeds.treeRoleMenu)
.setFooter({ text: strings.embeds.roleMenuFooter });
return { embeds: [embed], components: [actionRow] };
}
}
},
roles: {
async fetchRole(guild, roleId) {
return await guild.roles.fetch(roleId).catch(err => console.error("Error fetching the role: " + err));
},
errorEmbed(content) {
const embed = new EmbedBuilder()
.setColor(0xFF0000)
.setTitle('Error!')
.setDescription(content)
.setFooter({ text: strings.embeds.footer });
const messageContents = { embeds: [embed], ephemeral: true };
return messageContents;
async giveRole(member, role) {
await member.roles.add(role).catch(err => console.error("Error giving the role: " + err));
},
embed(content) {
const embed = new EmbedBuilder()
.setColor(0x8888FF)
.setTitle('Information')
.setDescription(content)
.setFooter({ text: strings.embeds.footer });
const messageContents = { embeds: [embed], ephemeral: true };
return messageContents;
async takeRole(member, role) {
await member.roles.remove(role).catch(err => console.error("Error taking the role: " + err));
}
},
refresh(interaction) {
functions.rankings.parse(interaction).then(r1 => {
functions.tree.parse(interaction).then(r2 => {
const embed = functions.builders.comparisonEmbed(functions.rankings.compare(interaction), functions.builders.refreshAction())
interaction.update(embed);
}).catch(e => {
interaction.reply(functions.builders.errorEmbed(e));
});
}).catch(e => {
interaction.reply(functions.builders.errorEmbed(e));
});
buttonHandlers: {
async fruitPing(interaction) {
const role = await functions.roles.fetchRole(interaction.guild, strings.roleIds.fruitPings);
if (interaction.member.roles.cache.some(role => role.id == strings.roleIds.fruitPings)) {
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 waterPing(interaction) {
const role = await functions.roles.fetchRole(interaction.guild, strings.roleIds.waterPings);
if (interaction.member.roles.cache.some(role => role.id == strings.roleIds.waterPings)) {
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));
}
}
};

Expand Down
1 change: 1 addition & 0 deletions modules/input.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Use the buttons below to give yourself roles.\n\n``💧`` - <@1073794977886392410>: Get notifications when the tree is ready to be watered.\n``🍎`` - <@1073795088183996496>: Get notifications when fruit is falling from the tree.
Loading

0 comments on commit 23b17bc

Please sign in to comment.