diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6df93c4 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,8 @@ +FROM node:18 +RUN mkdir -p /usr/src/app +WORKDIR /usr/src/app + +COPY package.json ./ +RUN npm install +COPY . . +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/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..3e67b99 --- /dev/null +++ b/data/strings.json @@ -0,0 +1,22 @@ +{ + "help": { + "info": "", + "setup": "", + "permissions": "" + }, + "embeds": { + "footer": "Have a great day!", + "color": "5555FF" + }, + "emoji": { + "next": "⏭️", + "previous": "⏮️", + "confirm": "☑️", + "cancel": "❌", + "water": "💧", + "fruit": "🍎", + "status": "📟" + }, + "roleIds": { + } +} \ No newline at end of file diff --git a/main.js b/main.js new file mode 100644 index 0000000..798e936 --- /dev/null +++ b/main.js @@ -0,0 +1,55 @@ +// dotenv for handling environment variables +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'); +const client = new Client({ + intents: [ + GatewayIntentBits.Guilds + ] +}); + +// Various imports +const fn = require('./modules/functions.js'); +const strings = require('./data/strings.json'); +const isDev = process.env.DEBUG; +const statusChannelId = process.env.STATUSCHANNELID; + +client.once('ready', () => { + // 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); + }); + + // Heartbeat Timer + if (sendHeartbeat) { + setInterval(() => { + fn.sendHeartbeat(heartbeatUrl); + }, 30000); + if (isDev) console.log("Heartbeat interval set."); + } +}); + +// 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.').catch(err => console.error(err)); + console.error('Slash command attempted to run but not found: /' + commandName); + } + } +}); + +client.login(token); \ No newline at end of file diff --git a/modules/_cleanInput.js b/modules/_cleanInput.js new file mode 100644 index 0000000..f112c53 --- /dev/null +++ b/modules/_cleanInput.js @@ -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"; \ No newline at end of file diff --git a/modules/_deploySlashCmdsGlobal.js b/modules/_deploySlashCmdsGlobal.js new file mode 100644 index 0000000..431ddf1 --- /dev/null +++ b/modules/_deploySlashCmdsGlobal.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.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}`); + 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/_deploySlashCmdsLocal.js b/modules/_deploySlashCmdsLocal.js new file mode 100644 index 0000000..933b61f --- /dev/null +++ b/modules/_deploySlashCmdsLocal.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/_eraseSlashCmds.js b/modules/_eraseSlashCmds.js new file mode 100644 index 0000000..57b7d64 --- /dev/null +++ b/modules/_eraseSlashCmds.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/functions.js b/modules/functions.js new file mode 100644 index 0000000..df2abad --- /dev/null +++ b/modules/functions.js @@ -0,0 +1,91 @@ +// dotenv for handling environment variables +const dotenv = require('dotenv'); +dotenv.config(); +const isDev = process.env.isDev; + +// filesystem +const fs = require('fs'); +const https = require('https'); + +// Discord.js +const Discord = require('discord.js'); +const { EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } = Discord; + +// Various imports from other files +const config = require('../data/config.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 + collectionBuilders: { + // 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: { + 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] }; + } + } + }, + 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)); + } +}; + +module.exports = functions; \ No newline at end of file diff --git a/modules/input.txt b/modules/input.txt new file mode 100644 index 0000000..e69de29 diff --git a/package.json b/package.json new file mode 100644 index 0000000..009d532 --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "name": "nodgpt", + "version": "1.0.0", + "description": "NodBot's OpenAI features moved to a dedicated bot", + "main": "main.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://git.vfsh.dev/voidf1sh/nodgpt.git" + }, + "author": "Skylar Grant", + "license": "MIT", + "bugs": { + "url": "https://git.vfsh.dev/voidf1sh/nodgpt/issues" + }, + "homepage": "https://git.vfsh.dev/voidf1sh/nodgpt#readme", + "dependencies": { + "discord.js": "^14.9.0", + "dotenv": "^16.0.3", + "string.prototype.replaceall": "^1.0.7" + } +} 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