Ready for basic use, rules and tree role menu
This commit is contained in:
parent
0f79d61491
commit
23b17bc0ec
16
.github/workflows/docker-image.yml
vendored
16
.github/workflows/docker-image.yml
vendored
@ -1,10 +1,8 @@
|
|||||||
name: Docker Image CI
|
name: Voidbot Dockerization
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
|
||||||
branches: [ "master" ]
|
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ "master" ]
|
branches: [ "main" ]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
DHUB_UNAME: ${{ secrets.DHUB_UNAME }}
|
DHUB_UNAME: ${{ secrets.DHUB_UNAME }}
|
||||||
@ -19,8 +17,14 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: Build the Docker image
|
- 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
|
- 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/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
|
@ -1,2 +1,4 @@
|
|||||||
# Discord Bot Template
|
# VoidBot
|
||||||
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.
|
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.
|
@ -5,17 +5,26 @@
|
|||||||
"permissions": ""
|
"permissions": ""
|
||||||
},
|
},
|
||||||
"embeds": {
|
"embeds": {
|
||||||
"footer": "",
|
"footer": "Have a great day!",
|
||||||
"color": "0x55FF55"
|
"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": {
|
"emoji": {
|
||||||
"next": "⏭️",
|
"next": "⏭️",
|
||||||
"previous": "⏮️",
|
"previous": "⏮️",
|
||||||
"confirm": "☑️",
|
"confirm": "☑️",
|
||||||
"cancel": "❌"
|
"cancel": "❌",
|
||||||
|
"water": "💧",
|
||||||
|
"fruit": "🍎"
|
||||||
},
|
},
|
||||||
"urls": {
|
"roleIds": {
|
||||||
"avatar": ""
|
"member": "1048328885118435368",
|
||||||
},
|
"waterPings": "1069416740389404763",
|
||||||
"temp": {}
|
"fruitPings": "1073698485376921602"
|
||||||
|
}
|
||||||
}
|
}
|
48
main.js
48
main.js
@ -4,33 +4,29 @@
|
|||||||
const dotenv = require('dotenv');
|
const dotenv = require('dotenv');
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
const token = process.env.TOKEN;
|
const token = process.env.TOKEN;
|
||||||
const statusChannelId = process.env.statusChannelId;
|
|
||||||
|
|
||||||
// Discord.JS
|
// Discord.JS
|
||||||
const { Client, GatewayIntentBits, Partials } = require('discord.js');
|
const { Client, GatewayIntentBits } = require('discord.js');
|
||||||
const client = new Client({
|
const client = new Client({
|
||||||
intents: [
|
intents: [
|
||||||
GatewayIntentBits.Guilds,
|
GatewayIntentBits.Guilds
|
||||||
GatewayIntentBits.GuildMessages,
|
]
|
||||||
GatewayIntentBits.GuildMessageReactions,
|
|
||||||
GatewayIntentBits.MessageContent
|
|
||||||
],
|
|
||||||
partials: [
|
|
||||||
Partials.Channel,
|
|
||||||
Partials.Message
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Various imports
|
// Various imports
|
||||||
const fn = require('./modules/functions.js');
|
const fn = require('./modules/functions.js');
|
||||||
const strings = require('./data/strings.json');
|
const strings = require('./data/strings.json');
|
||||||
const isDev = process.env.isDev;
|
const isDev = process.env.DEBUG;
|
||||||
|
const statusChannelId = process.env.STATUSCHANNELID;
|
||||||
|
|
||||||
client.once('ready', () => {
|
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!');
|
console.log('Ready!');
|
||||||
client.channels.fetch(statusChannelId).then(channel => {
|
client.channels.fetch(statusChannelId).then(channel => {
|
||||||
channel.send(`${new Date().toISOString()} -- Ready`);
|
channel.send(`${new Date().toISOString()} -- Ready`);
|
||||||
|
}).catch(err => {
|
||||||
|
console.error("Error sending status message: " + err);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -42,13 +38,29 @@ client.on('interactionCreate', async interaction => {
|
|||||||
if (client.slashCommands.has(commandName)) {
|
if (client.slashCommands.has(commandName)) {
|
||||||
client.slashCommands.get(commandName).execute(interaction);
|
client.slashCommands.get(commandName).execute(interaction);
|
||||||
} else {
|
} 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);
|
console.error('Slash command attempted to run but not found: /' + commandName);
|
||||||
}
|
}
|
||||||
}
|
} else if (interaction.isButton()) {
|
||||||
|
switch (interaction.component.customId) {
|
||||||
if (interaction.isButton() && interaction.component.customId == 'refresh') {
|
case 'acceptrules':
|
||||||
fn.refresh(interaction);
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
8
modules/_cleanInput.js
Normal file
8
modules/_cleanInput.js
Normal file
@ -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";
|
@ -4,7 +4,7 @@ dotenv.config();
|
|||||||
|
|
||||||
const { REST } = require('@discordjs/rest');
|
const { REST } = require('@discordjs/rest');
|
||||||
const { Routes } = require('discord-api-types/v9');
|
const { Routes } = require('discord-api-types/v9');
|
||||||
const clientId = process.env.clientId;
|
const clientId = process.env.BOTID;
|
||||||
const token = process.env.TOKEN;
|
const token = process.env.TOKEN;
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
|
||||||
@ -12,13 +12,13 @@ const commands = [];
|
|||||||
const commandFiles = fs.readdirSync('./slash-commands').filter(file => file.endsWith('.js'));
|
const commandFiles = fs.readdirSync('./slash-commands').filter(file => file.endsWith('.js'));
|
||||||
|
|
||||||
for (const file of commandFiles) {
|
for (const file of commandFiles) {
|
||||||
const command = require(`./slash-commands/${file}`);
|
const command = require(`../slash-commands/${file}`);
|
||||||
if (command.data != undefined) {
|
if (command.data != undefined) {
|
||||||
commands.push(command.data.toJSON());
|
commands.push(command.data.toJSON());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(commands);
|
// console.log(commands);
|
||||||
|
|
||||||
const rest = new REST({ version: '9' }).setToken(token);
|
const rest = new REST({ version: '9' }).setToken(token);
|
||||||
|
|
||||||
|
5
modules/buttons.js
Normal file
5
modules/buttons.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
const strings = require('../data/strings.json');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
|
||||||
|
}
|
@ -13,13 +13,12 @@ const { EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } = Discord;
|
|||||||
|
|
||||||
// Various imports from other files
|
// Various imports from other files
|
||||||
const config = require('../data/config.json');
|
const config = require('../data/config.json');
|
||||||
let guildInfo = require('../data/guildInfo.json');
|
|
||||||
const strings = require('../data/strings.json');
|
const strings = require('../data/strings.json');
|
||||||
const slashCommandFiles = fs.readdirSync('./slash-commands/').filter(file => file.endsWith('.js'));
|
const slashCommandFiles = fs.readdirSync('./slash-commands/').filter(file => file.endsWith('.js'));
|
||||||
|
|
||||||
const functions = {
|
const functions = {
|
||||||
// Functions for managing and creating Collections
|
// Functions for managing and creating Collections
|
||||||
collections: {
|
collectionBuilders: {
|
||||||
// Create the collection of slash commands
|
// Create the collection of slash commands
|
||||||
slashCommands(client) {
|
slashCommands(client) {
|
||||||
if (!client.slashCommands) client.slashCommands = new Discord.Collection();
|
if (!client.slashCommands) client.slashCommands = new Discord.Collection();
|
||||||
@ -34,59 +33,126 @@ const functions = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
builders: {
|
builders: {
|
||||||
refreshAction() {
|
actionRows: {
|
||||||
// Create the button to go in the Action Row
|
acceptRules() {
|
||||||
const refreshButton = new ButtonBuilder()
|
// Create the Action Row with the Button in it, to be sent with the Embed
|
||||||
.setCustomId('refresh')
|
return new ActionRowBuilder()
|
||||||
.setLabel('Refresh')
|
.addComponents(
|
||||||
.setStyle(ButtonStyle.Primary);
|
this.buttons.acceptRules()
|
||||||
// Create the Action Row with the Button in it, to be sent with the Embed
|
);
|
||||||
const refreshActionRow = new ActionRowBuilder()
|
},
|
||||||
.addComponents(
|
treeRoleMenu() {
|
||||||
refreshButton
|
return new ActionRowBuilder()
|
||||||
);
|
.addComponents(
|
||||||
return refreshActionRow;
|
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) {
|
embeds: {
|
||||||
const embed = new EmbedBuilder()
|
helpEmbed(content, private) {
|
||||||
.setColor(strings.embeds.color)
|
const embed = new EmbedBuilder()
|
||||||
.setTitle('Grow A Tree Analyzer Help')
|
.setColor(strings.embeds.color)
|
||||||
.setDescription(content)
|
.setTitle('Grow A Tree Analyzer Help')
|
||||||
.setFooter({ text: strings.embeds.footer });
|
.setDescription(content)
|
||||||
const privateBool = private == 'true';
|
.setFooter({ text: strings.embeds.footer });
|
||||||
const messageContents = { embeds: [embed], ephemeral: privateBool };
|
const privateBool = private == 'true';
|
||||||
return messageContents;
|
const messageContents = { embeds: [embed], ephemeral: privateBool };
|
||||||
},
|
return messageContents;
|
||||||
errorEmbed(content) {
|
},
|
||||||
const embed = new EmbedBuilder()
|
errorEmbed(content) {
|
||||||
.setColor(0xFF0000)
|
const embed = new EmbedBuilder()
|
||||||
.setTitle('Error!')
|
.setColor(0xFF0000)
|
||||||
.setDescription(content)
|
.setTitle('Error!')
|
||||||
.setFooter({ text: strings.embeds.footer });
|
.setDescription(content)
|
||||||
const messageContents = { embeds: [embed], ephemeral: true };
|
.setFooter({ text: strings.embeds.footer });
|
||||||
return messageContents;
|
const messageContents = { embeds: [embed], ephemeral: true };
|
||||||
},
|
return messageContents;
|
||||||
embed(content) {
|
},
|
||||||
const embed = new EmbedBuilder()
|
info(content) {
|
||||||
.setColor(0x8888FF)
|
const embed = new EmbedBuilder()
|
||||||
.setTitle('Information')
|
.setColor(0x8888FF)
|
||||||
.setDescription(content)
|
.setTitle('Information')
|
||||||
.setFooter({ text: strings.embeds.footer });
|
.setDescription(content)
|
||||||
const messageContents = { embeds: [embed], ephemeral: true };
|
.setFooter({ text: strings.embeds.footer });
|
||||||
return messageContents;
|
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] };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
refresh(interaction) {
|
roles: {
|
||||||
functions.rankings.parse(interaction).then(r1 => {
|
async fetchRole(guild, roleId) {
|
||||||
functions.tree.parse(interaction).then(r2 => {
|
return await guild.roles.fetch(roleId).catch(err => console.error("Error fetching the role: " + err));
|
||||||
const embed = functions.builders.comparisonEmbed(functions.rankings.compare(interaction), functions.builders.refreshAction())
|
},
|
||||||
interaction.update(embed);
|
async giveRole(member, role) {
|
||||||
}).catch(e => {
|
await member.roles.add(role).catch(err => console.error("Error giving the role: " + err));
|
||||||
interaction.reply(functions.builders.errorEmbed(e));
|
},
|
||||||
});
|
async takeRole(member, role) {
|
||||||
}).catch(e => {
|
await member.roles.remove(role).catch(err => console.error("Error taking the role: " + err));
|
||||||
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
1
modules/input.txt
Normal file
1
modules/input.txt
Normal file
@ -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.
|
24
package.json
Normal file
24
package.json
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"name": "voidbot",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "voidf1sh Development Server Support Bot",
|
||||||
|
"main": "main.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/voidf1sh/voidbot.git"
|
||||||
|
},
|
||||||
|
"author": "Skylar Grant",
|
||||||
|
"license": "ISC",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/voidf1sh/voidbot/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/voidf1sh/voidbot#readme",
|
||||||
|
"dependencies": {
|
||||||
|
"discord.js": "^14.7.1",
|
||||||
|
"dotenv": "^16.0.3",
|
||||||
|
"string.prototype.replaceall": "^1.0.7"
|
||||||
|
}
|
||||||
|
}
|
12
slash-commands/rolemenu.js
Normal file
12
slash-commands/rolemenu.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
const { SlashCommandBuilder } = require('discord.js');
|
||||||
|
const fn = require('../modules/functions.js');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('rolemenu')
|
||||||
|
.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));
|
||||||
|
},
|
||||||
|
};
|
17
slash-commands/rules.js
Normal file
17
slash-commands/rules.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
const { SlashCommandBuilder } = require('discord.js');
|
||||||
|
const fn = require('../modules/functions.js');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('rules')
|
||||||
|
.setDescription('Send the rules in the current channel'),
|
||||||
|
async execute(interaction) {
|
||||||
|
try {
|
||||||
|
await interaction.deferReply().catch(err => console.error(err));
|
||||||
|
await interaction.editReply(fn.builders.embeds.rules());
|
||||||
|
} catch(err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user