diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..163b5f6 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +node_modules +npm-debug.log +env.dev +env.prod +.env \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..ae431cc --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,49 @@ +{ + "extends": "eslint:recommended", + "env": { + "node": true, + "es6": true + }, + "parserOptions": { + "ecmaVersion": 2021 + }, + "rules": { + "arrow-spacing": ["warn", { "before": true, "after": true }], + "brace-style": ["error", "stroustrup", { "allowSingleLine": true }], + "comma-dangle": ["error", "always-multiline"], + "comma-spacing": "error", + "comma-style": "error", + "curly": ["error", "multi-line", "consistent"], + "dot-location": ["error", "property"], + "handle-callback-err": "off", + "indent": ["error", "tab"], + "keyword-spacing": "error", + "max-nested-callbacks": ["error", { "max": 4 }], + "max-statements-per-line": ["error", { "max": 2 }], + "no-console": "off", + "no-empty-function": "error", + "no-floating-decimal": "error", + "no-inline-comments": "error", + "no-lonely-if": "error", + "no-multi-spaces": "error", + "no-multiple-empty-lines": ["error", { "max": 2, "maxEOF": 1, "maxBOF": 0 }], + "no-shadow": ["error", { "allow": ["err", "resolve", "reject"] }], + "no-trailing-spaces": ["error"], + "no-var": "error", + "object-curly-spacing": ["error", "always"], + "prefer-const": "error", + "quotes": ["error", "single"], + "semi": ["error", "always"], + "space-before-blocks": "error", + "space-before-function-paren": ["error", { + "anonymous": "never", + "named": "never", + "asyncArrow": "always" + }], + "space-in-parens": "error", + "space-infix-ops": "error", + "space-unary-ops": "error", + "spaced-comment": "error", + "yoda": "error" + } +} \ No newline at end of file diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml new file mode 100644 index 0000000..5f27b9d --- /dev/null +++ b/.github/workflows/docker-image.yml @@ -0,0 +1,26 @@ +name: Docker Image CI + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +env: + DHUB_UNAME: ${{ secrets.DHUB_UNAME }} + DHUB_PWORD: ${{ secrets.DHUB_PWORD }} + +jobs: + + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Build the Docker image + run: docker build . --file Dockerfile --tag v0idf1sh/treeanalyzer + - 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 diff --git a/.gitignore b/.gitignore index dddcac2..923ffd7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,14 @@ # IDE Config Files .vscode -.eslintrc.json +package-lock.json +.VSCodeCounter/ +env.dev +env.prod +.DS_Store + +# Custom folders +# gifs/* +# pastas/* # Logs logs @@ -75,6 +83,8 @@ typings/ # dotenv environment variables file .env .env.test +.env.dev +.env.prod # parcel-bundler cache (https://parceljs.org/) .cache diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a0b785a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,8 @@ +FROM node:16 +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 diff --git a/README.md b/README.md index 4899fac..e49c919 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,2 @@ -# discord-bot-template -Bot Template for Discord.js +# 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. \ No newline at end of file diff --git a/data/config.json b/data/config.json new file mode 100644 index 0000000..05eb669 --- /dev/null +++ b/data/config.json @@ -0,0 +1,4 @@ +{ + "guildId": "", + "validCommands": [] +} \ No newline at end of file diff --git a/data/strings.json b/data/strings.json new file mode 100644 index 0000000..6f3f0e9 --- /dev/null +++ b/data/strings.json @@ -0,0 +1,21 @@ +{ + "help": { + "info": "", + "setup": "", + "permissions": "" + }, + "embeds": { + "footer": "", + "color": "0x55FF55" + }, + "emoji": { + "next": "⏭️", + "previous": "⏮️", + "confirm": "☑️", + "cancel": "❌" + }, + "urls": { + "avatar": "" + }, + "temp": {} +} \ No newline at end of file diff --git a/index.js b/index.js deleted file mode 100644 index 403bc99..0000000 --- a/index.js +++ /dev/null @@ -1,19 +0,0 @@ -/* eslint-disable brace-style */ -// Variable Assignment -const dotenv = require('dotenv'); -const Discord = require('discord.js'); -const client = new Discord.Client(); -const debug = false; - -dotenv.config(); -const owner = process.env.ownerID; - -client.once('ready', () => { - console.log('Ready'); -}); - -client.login(process.env.TOKEN); - -client.on('message', message => { - // Code Here -}); \ No newline at end of file diff --git a/main.js b/main.js new file mode 100644 index 0000000..dc55716 --- /dev/null +++ b/main.js @@ -0,0 +1,55 @@ +/* eslint-disable no-case-declarations */ +/* eslint-disable indent */ +// dotenv for handling environment variables +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 = new Client({ + intents: [ + GatewayIntentBits.Guilds, + GatewayIntentBits.GuildMessages, + GatewayIntentBits.GuildMessageReactions, + GatewayIntentBits.MessageContent + ], + partials: [ + Partials.Channel, + Partials.Message + ], +}); + +// Various imports +const fn = require('./modules/functions.js'); +const strings = require('./data/strings.json'); +const isDev = process.env.isDev; + +client.once('ready', () => { + fn.collections.slashCommands(client); + console.log('Ready!'); + client.channels.fetch(statusChannelId).then(channel => { + channel.send(`${new Date().toISOString()} -- Ready`); + }); +}); + +// slash-commands +client.on('interactionCreate', async interaction => { + if (interaction.isCommand()) { + const { commandName } = interaction; + + if (client.slashCommands.has(commandName)) { + client.slashCommands.get(commandName).execute(interaction); + } else { + interaction.reply('Sorry, I don\'t have access to that command.'); + console.error('Slash command attempted to run but not found: /' + commandName); + } + } + + if (interaction.isButton() && interaction.component.customId == 'refresh') { + fn.refresh(interaction); + } +}); + +client.login(token); \ No newline at end of file diff --git a/modules/_clear-commands.js b/modules/_clear-commands.js new file mode 100644 index 0000000..57b7d64 --- /dev/null +++ b/modules/_clear-commands.js @@ -0,0 +1,27 @@ +// dotenv for handling environment variables +const dotenv = require('dotenv'); +dotenv.config(); + +const { REST } = require('@discordjs/rest'); +const { Routes } = require('discord-api-types/v9'); +const clientId = process.env.clientId; +const { guildId } = require('../data/config.json'); +const token = process.env.TOKEN; + +const rest = new REST({ version: '9' }).setToken(token); + +(async () => { + try { + console.log('Started refreshing application (/) commands.'); + + await rest.put( + Routes.applicationGuildCommands(clientId, guildId), + { body: '' }, + ); + + console.log('Successfully reloaded application (/) commands.'); + process.exit(); + } catch (error) { + console.error(error); + } +})(); \ No newline at end of file diff --git a/modules/_deploy-commands.js b/modules/_deploy-commands.js new file mode 100644 index 0000000..933b61f --- /dev/null +++ b/modules/_deploy-commands.js @@ -0,0 +1,39 @@ +// dotenv for handling environment variables +const dotenv = require('dotenv'); +dotenv.config(); + +const { REST, Routes } = require('discord.js'); +const { guildId } = require('../data/config.json'); +const clientId = process.env.clientId; +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}`); + if (command.data != undefined) { + commands.push(command.data.toJSON()); + } +} + +console.log(commands); + +const rest = new REST({ version: '10' }).setToken(token); + +(async () => { + try { + console.log('Started refreshing application (/) commands.'); + + await rest.put( + Routes.applicationGuildCommands(clientId, guildId), + { body: commands }, + ); + + console.log('Successfully reloaded application (/) commands.'); + process.exit(); + } catch (error) { + console.error(error); + } +})(); \ No newline at end of file diff --git a/modules/_deploy-global.js b/modules/_deploy-global.js new file mode 100644 index 0000000..cd7b8ce --- /dev/null +++ b/modules/_deploy-global.js @@ -0,0 +1,39 @@ +// dotenv for handling environment variables +const dotenv = require('dotenv'); +dotenv.config(); + +const { REST } = require('@discordjs/rest'); +const { Routes } = require('discord-api-types/v9'); +const clientId = process.env.clientId; +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}`); + if (command.data != undefined) { + commands.push(command.data.toJSON()); + } +} + +console.log(commands); + +const rest = new REST({ version: '9' }).setToken(token); + +(async () => { + try { + console.log('Started refreshing application (/) commands.'); + + await rest.put( + Routes.applicationCommands(clientId), + { body: commands }, + ); + + console.log('Successfully reloaded application (/) commands.'); + process.exit(); + } catch (error) { + console.error(error); + } +})(); \ No newline at end of file diff --git a/modules/functions.js b/modules/functions.js new file mode 100644 index 0000000..f3edb7f --- /dev/null +++ b/modules/functions.js @@ -0,0 +1,93 @@ +/* eslint-disable comma-dangle */ +// dotenv for handling environment variables +const dotenv = require('dotenv'); +dotenv.config(); +const isDev = process.env.isDev; + +// filesystem +const fs = require('fs'); + +// Discord.js +const Discord = require('discord.js'); +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: { + // Create the collection of slash commands + slashCommands(client) { + if (!client.slashCommands) client.slashCommands = new Discord.Collection(); + client.slashCommands.clear(); + for (const file of slashCommandFiles) { + const slashCommand = require(`../slash-commands/${file}`); + if (slashCommand.data != undefined) { + client.slashCommands.set(slashCommand.data.name, slashCommand); + } + } + if (isDev) console.log('Slash Commands Collection Built'); + } + }, + 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; + }, + 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; + }, + 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; + } + }, + 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)); + }); + } +}; + +module.exports = functions; \ No newline at end of file diff --git a/slash-commands/template b/slash-commands/template new file mode 100644 index 0000000..774cf03 --- /dev/null +++ b/slash-commands/template @@ -0,0 +1,11 @@ +const { SlashCommandBuilder } = require('discord.js'); +const fn = require('../modules/functions.js'); + +module.exports = { + data: new SlashCommandBuilder() + .setName('') + .setDescription(''), + async execute(interaction) { + await + }, +}; \ No newline at end of file