diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 5008ddf..0000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.github/workflows/dev-deploy.yml b/.github/workflows/dev-deploy.yml deleted file mode 100644 index 21fbe5f..0000000 --- a/.github/workflows/dev-deploy.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: Nodbot-dev Dockerization - -on: - workflow_dispatch: - -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/nodbot-dev - - name: Log into Docker Hub - run: docker login -u $DHUB_UNAME -p $DHUB_PWORD - - name: Push image to Docker Hub - run: docker push v0idf1sh/nodbot-dev diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml new file mode 100644 index 0000000..acfae2a --- /dev/null +++ b/.github/workflows/docker-image.yml @@ -0,0 +1,29 @@ +name: Bot Dockerization + +on: + workflow_dispatch: + +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/tag + - name: Log into Docker Hub + run: docker login -u $DHUB_UNAME -p $DHUB_PWORD + - name: Push image to Docker Hub + run: docker push v0idf1sh/tag + - 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 diff --git a/.github/workflows/prod-deploy.yml b/.github/workflows/prod-deploy.yml deleted file mode 100644 index 3d30972..0000000 --- a/.github/workflows/prod-deploy.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Docker Image CI - -on: - pull_request: - branches: [ "main" ] - -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/nodbot - - name: Log into Docker Hub - run: docker login -u $DHUB_UNAME -p $DHUB_PWORD - - name: Push image to Docker Hub - run: docker push v0idf1sh/nodbot diff --git a/.gitignore b/.gitignore index 06fecb3..923ffd7 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ package-lock.json .VSCodeCounter/ env.dev env.prod +.DS_Store # Custom folders # gifs/* diff --git a/Dockerfile b/Dockerfile index 7b75fef..a0b785a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,4 +5,4 @@ WORKDIR /usr/src/app COPY package.json ./ RUN npm install COPY . . -CMD ["/bin/sh", "-c", "node main.js 2> /logs/nodbot.error 1> /logs/nodbot.log"] \ No newline at end of file +CMD [ "node", "main.js" ] \ No newline at end of file diff --git a/README.md b/README.md index 7d31f60..d344efb 100644 --- a/README.md +++ b/README.md @@ -1,111 +1,7 @@ -# About Nodbot -Nodbot is a content saving and serving Discord bot. Nodbot is able to search Tenor for GIFs, save custom copypastas, and look up marijuana strain information. Nodbot is in semi-active development by voidf1sh. It's buggy as hell and very shoddily built. Don't use it. +# NodBot v4 +Under (very early) construction! Come back later -# Nodbot Help - -Use the `/help` command to see the bot's help message. - -## Create Docker Image -`docker build --tag=name/nodbot .` - -## Push Docker Image -`docker push name/nodbot` - -# Immediate To-Do - -1. ~~Sanitize inputs for SQL queries.~~ Done. -2. ~~Move environment variables so they don't get included in the image.~~ -3. Implement error handling on all actions. -4. Ephemeral responses to some/most slash commands. -5. Comment the code! Document! -6. Check for and create database tables if necessary. Handle errors. - -# Deploy NodBot Yourself - -1. Create an application at the [Discord Developer Portal](https://discord.com/developers/applications) -2. Convert the application into a Bot -3. Note down the token provided and keep this safe. You cannot view this token again, only regenerate a new one. -4. Create a Tenor account and obtain an API key. -5. Install and configure MySQL or MariaDB with a user for the bot and a datbase -* Create the table structure as outlined below (* Nodbot will soon create its own table structure) -6. Configure your environment variables as outlined below. -7. Fire it up with `node main.js` - -## Table Structure - -``` -Table: gifs -+-----------+---------------+------+-----+---------+----------------+ -| Field | Type | Null | Key | Default | Extra | -+-----------+---------------+------+-----+---------+----------------+ -| id | int(11) | NO | MUL | NULL | auto_increment | -| name | varchar(100) | NO | | NULL | | -| embed_url | varchar(1000) | NO | | NULL | | -+-----------+---------------+------+-----+---------+----------------+ - -Table: joints -+---------+---------------+------+-----+---------+----------------+ -| Field | Type | Null | Key | Default | Extra | -+---------+---------------+------+-----+---------+----------------+ -| id | int(11) | NO | MUL | NULL | auto_increment | -| content | varchar(1000) | NO | | NULL | | -+---------+---------------+------+-----+---------+----------------+ - -Table: pastas -+---------+---------------+------+-----+---------+----------------+ -| Field | Type | Null | Key | Default | Extra | -+---------+---------------+------+-----+---------+----------------+ -| id | int(11) | NO | MUL | NULL | auto_increment | -| name | varchar(100) | NO | | NULL | | -| content | varchar(1900) | NO | | NULL | | -| iconurl | varchar(200) | NO | | (url) | | -+---------+---------------+------+-----+---------+----------------+ - -Table: requests -+---------+---------------+------+-----+---------+----------------+ -| Field | Type | Null | Key | Default | Extra | -+---------+---------------+------+-----+---------+----------------+ -| id | int(11) | NO | MUL | NULL | auto_increment | -| author | varchar(100) | NO | | NULL | | -| request | varchar(1000) | NO | | NULL | | -| status | varchar(10) | YES | | Active | | -+---------+---------------+------+-----+---------+----------------+ - -Table: strains -+---------+-------------+------+-----+---------+-------+ -| Field | Type | Null | Key | Default | Extra | -+---------+-------------+------+-----+---------+-------+ -| id | smallint(6) | NO | | NULL | | -| name | varchar(60) | YES | | NULL | | -| type | varchar(10) | YES | | NULL | | -| effects | varchar(80) | YES | | NULL | | -| ailment | varchar(70) | YES | | NULL | | -| flavor | varchar(30) | YES | | NULL | | -+---------+-------------+------+-----+---------+-------+ -``` - -## Environment Variables -``` -TOKEN= -isDev= -dbHost= -dbPort= -dbUser= -dbPass= -dbName= -tenorAPIKey= -ownerId= -statusChannelId= -clientId= -``` - -## Changes - -v3.0.1 - Migrate TenorJS API Endpoint -v3.0.2 - Add medical advice commands -v3.0.3 - Fix broken `/requests` command -v3.0.4 - Add ability to use multiple aliases -v3.0.5 - Add ability to save strains -v3.0.6 - Move `.strain` to `/strain` and add Autocomplete -v3.0.7 - Add `.spongebob` replies -v3.0.8 - Add ability to open requests by pages \ No newline at end of file +# Dependencies +* `dotenv` +* `discord.js` +* `fs` (built in) \ No newline at end of file diff --git a/Roadmap.md b/Roadmap.md deleted file mode 100644 index 80bce7b..0000000 --- a/Roadmap.md +++ /dev/null @@ -1,14 +0,0 @@ -# v3.1.0 - -* Name checking for saving content -* .jpg, .wav -* Audio/Video attachments for saved content. -* Pass The Joint -* Voting system for Super Adventure Club - -# v4.0.0 -* Scalability: modify the code to allow the bot to be used in multiple servers - * including saved content, saved commands, preferences, etc. - -# v3.?.? -= Joke generator for Hallihan \ No newline at end of file diff --git a/_clear-commands.js b/_clear-commands.js deleted file mode 100644 index 869c90d..0000000 --- a/_clear-commands.js +++ /dev/null @@ -1,27 +0,0 @@ -// 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('../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/_deploy-commands.js b/_deploy-commands.js deleted file mode 100644 index 5ba21ce..0000000 --- a/_deploy-commands.js +++ /dev/null @@ -1,40 +0,0 @@ -// dotenv for handling environment variables -const dotenv = require('dotenv'); -dotenv.config(); - -const { REST } = require('@discordjs/rest'); -const { Routes } = require('discord-api-types/v9'); -const { guildId } = require('./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: '9' }).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/config.json b/config.json deleted file mode 100644 index b9087cf..0000000 --- a/config.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "guildId": "868542949737246730", - "validCommands": [] -} \ 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..05757af --- /dev/null +++ b/data/strings.json @@ -0,0 +1,24 @@ +{ + "help": { + "title": "Title", + "content": "Some help content", + "footer": "Witty Text Here" + }, + "embeds": { + "footer": "github/voidf1sh/discord-bot-template", + "color": "0x55FF55", + "errorTitle": "Error", + "errorColor": "0xFF0000", + "infoTitle": "Information", + "infoColor": "0x8888FF" + }, + "emoji": { + "next": "⏭️", + "previous": "⏮️", + "confirm": "☑️", + "cancel": "❌" + }, + "errors": { + "generalCommand": "Sorry, there was an error running that command." + } +} \ No newline at end of file diff --git a/dot-commands/gif.js b/dot-commands/gif.js deleted file mode 100644 index c7dedd7..0000000 --- a/dot-commands/gif.js +++ /dev/null @@ -1,53 +0,0 @@ -const fn = require('../functions'); -const axios = require('axios'); -const dotenv = require('dotenv').config(); - -// TODO: Tenor has changed API versions, switch from TenorJS (unmaintained) to axios for -// general API usage. See: https://github.com/Jinzulen/TenorJS/issues/12 -// see also: https://developers.google.com/tenor/guides/migrate-from-v1 - -module.exports = { - name: 'gif', - description: 'Send a GIF', - usage: '.gif', - async execute(message, commandData) { - // if (message.deletable) message.delete(); - const client = message.client; - if (!client.gifs.has(commandData.args)) { - if (process.env.isDev) console.log('https://tenor.googleapis.com/v2/search?' + `&q=${commandData.args}` + `&key=${process.env.tenorAPIKey}` + '&limit=1&contentfilter=off'); - const q = await axios.get( - 'https://tenor.googleapis.com/v2/search?' + - `&q=${commandData.args}` + - `&key=${process.env.tenorAPIKey}` + - '&limit=1' + - `&contentfilter=off` - ).then(res => { - if (process.env.isDev) console.log(res.data.results); - if (res.data.results[0] == undefined) { - message.reply('Sorry I was unable to find a GIF of ' + commandData.args); - return; - }; - commandData.embed_url = res.data.results[0].url; - message.reply(commandData.embed_url); - }).catch(err => console.error(err)); - // tenor.Search.Query(commandData.args, "1").then(res => { - // if (res[0] == undefined) { - // message.reply('Sorry I was unable to find a GIF of ' + commandData.args); - // return; - // }; - // commandData.embed_url = res[0].media[0].gif.url; - // // message.reply(fn.embeds.gif(commandData)); - // // message.channel.send(`> ${commandData.author} - ${commandData.args}.gif`); - // // message.channel.send(commandData.embed_url); - // message.reply(commandData.embed_url); - // }).catch(err => console.error(err)); - } else { - // message.reply(commandData.args + ' requested by ' + message.author.username + '\n' + client.gifs.get(commandData.args).embed_url); - commandData.embed_url = client.gifs.get(commandData.args).embed_url; - // message.reply(fn.embeds.gif(commandData)); - // message.channel.send(`> ${commandData.author} - ${commandData.args}.gif`); - // message.channel.send(commandData.embed_url); - message.reply(commandData.embed_url); - } - } -} \ No newline at end of file diff --git a/dot-commands/joint.js b/dot-commands/joint.js deleted file mode 100644 index 2ce3202..0000000 --- a/dot-commands/joint.js +++ /dev/null @@ -1,17 +0,0 @@ -const fn = require('../functions.js'); -const { emoji } = require('../strings.json'); - -module.exports = { - name: 'joint', - description: 'Send a random weed-themed phrase.', - usage: '.joint', - alias: ['bong', 'blunt', 'bowl', 'pipe'], - execute(message, commandData) { - let joints = []; - for (const entry of message.client.joints.map(joint => joint.content)) { - joints.push(entry); - } - const randIndex = Math.floor(Math.random() * joints.length); - message.reply(`${joints[randIndex]} ${emoji.joint}`); - } -} \ No newline at end of file diff --git a/dot-commands/md.js b/dot-commands/md.js deleted file mode 100644 index 01eea4a..0000000 --- a/dot-commands/md.js +++ /dev/null @@ -1,16 +0,0 @@ -const fn = require('../functions.js'); -// const { emoji } = require('../strings.json'); - -module.exports = { - name: 'md', - description: 'Get some medical advice.', - usage: '.md', - execute(message, commandData) { - let medicalAdviceArr = []; - for (const entry of message.client.medicalAdviceColl.map(medicalAdvice => medicalAdvice.content)) { - medicalAdviceArr.push(entry); - } - const randIndex = Math.floor(Math.random() * medicalAdviceArr.length); - message.reply(`${medicalAdviceArr[randIndex]}`); - } -} \ No newline at end of file diff --git a/dot-commands/pasta.js b/dot-commands/pasta.js deleted file mode 100644 index 247cf96..0000000 --- a/dot-commands/pasta.js +++ /dev/null @@ -1,19 +0,0 @@ -const fn = require('../functions.js'); - -module.exports = { - name: 'pasta', - description: 'Send a copypasta.', - usage: '.pasta', - execute(message, commandData) { - const client = message.client; - let replyBody = ''; - let iconUrl; - if (!client.pastas.has(commandData.args)) { - commandData.content = 'Sorry I couldn\'t find that pasta.'; - } else { - commandData.content = client.pastas.get(commandData.args).content; - commandData.iconUrl = client.pastas.get(commandData.args).iconUrl; - } - message.reply(fn.embeds.pasta(commandData)); - } -} \ No newline at end of file diff --git a/dot-commands/request.js b/dot-commands/request.js deleted file mode 100644 index 8f9c2ca..0000000 --- a/dot-commands/request.js +++ /dev/null @@ -1,17 +0,0 @@ -const fn = require('../functions.js'); - -module.exports = { - name: 'request', - description: 'Submit a request to the bot developer.', - usage: '.request', - execute(message, commandData) { - const request = commandData.args; - commandData.content = `Your request has been submitted!\nRequest: ${request}`; - message.reply(fn.embeds.text(commandData)); - commandData.content = `A new request has been submitted by ${message.author.tag}:\n${commandData.args}`; - message.client.users.fetch(process.env.ownerID).then(user => { - user.send(fn.embeds.text(commandData)); - }).catch(error => { console.error(error); }); - fn.upload.request(commandData, message.client); - }, -}; \ No newline at end of file diff --git a/dot-commands/spongebob.js b/dot-commands/spongebob.js deleted file mode 100644 index f886a27..0000000 --- a/dot-commands/spongebob.js +++ /dev/null @@ -1,30 +0,0 @@ -const fn = require('../functions.js'); -const config = require('../config.json'); - -module.exports = { - name: 'spongebob', - alias: 'sb', - description: 'SpOnGeBoB-iFy AnYtHiNg AuToMaTiCaLly', - usage: '.spongebob', - execute(message, commandData) { - // message.reply(fn.spongebob(commandData)).then(() => { - // message.delete(); - // }); - if (message.reference != undefined) { - const repliedMessageId = message.reference.messageId; - message.channel.messages.fetch(repliedMessageId) - .then(repliedMessage => { - repliedMessage.reply(fn.spongebob({ args: repliedMessage.content })).then(() => { - if (message.deletable) message.delete(); - }); - }) - .catch(err => { - console.error(err); - }); - } else { - message.channel.send(fn.spongebob(commandData)).then(() => { - if (message.deletable) message.delete(); - }); - } - } -} \ No newline at end of file diff --git a/dot-commands/strain.js.bak b/dot-commands/strain.js.bak deleted file mode 100644 index 630a4b4..0000000 --- a/dot-commands/strain.js.bak +++ /dev/null @@ -1,17 +0,0 @@ -const fn = require('../functions'); - -module.exports = { - name: 'strain', - description: 'Search for information about a cannabis strain.', - usage: '.strain', - execute(message, commandData) { - commandData.strainName = fn.weed.strain.lookup(commandData.args, message.client); - if (commandData.strainName) { - fn.download.strain(commandData, message); - } - else { - commandData.content = 'Sorry, I couldn\'t find a strain with that name: ' + commandData.args; - message.reply(fn.embeds.text(commandData)); - } - } -} \ No newline at end of file diff --git a/functions.js b/functions.js deleted file mode 100644 index 025c020..0000000 --- a/functions.js +++ /dev/null @@ -1,576 +0,0 @@ -/* eslint-disable comma-dangle */ -// dotenv for handling environment variables -const dotenv = require('dotenv'); -dotenv.config(); -// Assignment of environment variables for database access -const dbHost = process.env.dbHost; -const dbUser = process.env.dbUser; -const dbName = process.env.dbName; -const dbPass = process.env.dbPass; -const dbPort = process.env.dbPort; -const isDev = process.env.isDev; - -const ownerId = process.env.ownerId; - -// filesystem -const fs = require('fs'); - -// Discord.js -const Discord = require('discord.js'); - -// Fuzzy text matching for db lookups -const FuzzySearch = require('fuzzy-search'); - -// Various imports from other files -const config = require('./config.json'); -const strings = require('./strings.json'); -const slashCommandFiles = fs.readdirSync('./slash-commands/').filter(file => file.endsWith('.js')); -const dotCommandFiles = fs.readdirSync('./dot-commands/').filter(file => file.endsWith('.js')); - -// MySQL database connection -const mysql = require('mysql'); -const db = new mysql.createPool({ - connectionLimit: 10, - host: dbHost, - user: dbUser, - password: dbPass, - database: dbName, - port: dbPort, -}); - -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'); - }, - setvalidCommands(client) { - for (const entry of client.dotCommands.map(command => command)) { - config.validCommands.push(entry.name); - if (Array.isArray(entry.alias)) { - entry.alias.forEach(element => { - config.validCommands.push(element); - }); - } else if (entry.alias != undefined) { - config.validCommands.push(entry.alias); - } - } - if (isDev) console.log(`Valid Commands Added to Config\n${config.validCommands}`); - }, - dotCommands(client) { - if (!client.dotCommands) client.dotCommands = new Discord.Collection(); - client.dotCommands.clear(); - for (const file of dotCommandFiles) { - const dotCommand = require(`./dot-commands/${file}`); - client.dotCommands.set(dotCommand.name, dotCommand); - if (Array.isArray(dotCommand.alias)) { - dotCommand.alias.forEach(element => { - client.dotCommands.set(element, dotCommand); - }); - } else if (dotCommand.alias != undefined) { - client.dotCommands.set(dotCommand.alias, dotCommand); - } - } - if (isDev) console.log('Dot Commands Collection Built'); - }, - gifs(rows, client) { - if (!client.gifs) client.gifs = new Discord.Collection(); - client.gifs.clear(); - for (const row of rows) { - const gif = { - id: row.id, - name: row.name, - embed_url: row.embed_url - }; - client.gifs.set(gif.name, gif); - } - if (isDev) console.log('GIFs Collection Built'); - }, - joints(rows, client) { - if (!client.joints) client.joints = new Discord.Collection(); - client.joints.clear(); - for (const row of rows) { - const joint = { - id: row.id, - content: row.content - }; - client.joints.set(joint.id, joint); - } - if (isDev) console.log('Joints Collection Built'); - }, - pastas(rows, client) { - if (!client.pastas) client.pastas = new Discord.Collection(); - client.pastas.clear(); - for (const row of rows) { - const pasta = { - id: row.id, - name: row.name, - content: row.content, - iconUrl: row.iconurl, - }; - client.pastas.set(pasta.name, pasta); - } - if (isDev) console.log('Pastas Collection Built'); - }, - requests(rows, client) { - if (!client.requests) client.requests = new Discord.Collection(); - client.requests.clear(); - for (const row of rows) { - const request = { - id: row.id, - author: row.author, - request: row.request, - }; - client.requests.set(request.id, request); - } - if (isDev) console.log('Requests Collection Built'); - }, - strains(rows, client) { - if (!client.strains) client.strains = new Discord.Collection(); - client.strains.clear(); - for (const row of rows) { - const strain = { - id: row.id, - name: row.strain, - }; - client.strains.set(strain.name, strain); - // if (isDev) console.log(strain) - } - if (isDev) console.log('Strains Collection Built'); - }, - medicalAdvice(rows, client) { - if (!client.medicalAdviceCol) client.medicalAdviceColl = new Discord.Collection(); - client.medicalAdviceColl.clear(); - for (const row of rows) { - const medicalAdvice = { - id: row.id, - content: row.content - }; - client.medicalAdviceColl.set(medicalAdvice.id, medicalAdvice); - } - if (isDev) console.log('Medical Advice Collection Built'); - }, - }, - dot: { - getCommandData(message) { - const commandData = {}; - // Split the message content at the final instance of a period - const finalPeriod = message.content.lastIndexOf('.'); - if(isDev) console.log(message.content); - // If the final period is the last character, or doesn't exist - if (finalPeriod < 0) { - if (isDev) console.log(finalPeriod); - commandData.isCommand = false; - return commandData; - } - commandData.isCommand = true; - // Get the first part of the message, everything leading up to the final period - commandData.args = message.content.slice(0,finalPeriod).toLowerCase(); - // Get the last part of the message, everything after the final period - commandData.command = message.content.slice(finalPeriod).replace('.','').toLowerCase(); - commandData.author = `${message.author.username}#${message.author.discriminator}`; - return this.checkCommand(commandData); - }, - checkCommand(commandData) { - if (commandData.isCommand) { - const validCommands = require('./config.json').validCommands; - commandData.isValid = validCommands.includes(commandData.command); - // Add exceptions for messages that contain only a link - if (commandData.args.startsWith('http')) commandData.isValid = false; - } - else { - commandData.isValid = false; - console.error('Somehow a non-command made it to checkCommands()'); - } - return commandData; - } - }, - embeds: { - help(interaction) { - // Construct the Help Embed - const helpEmbed = new Discord.MessageEmbed() - .setColor('BLUE') - .setAuthor('Help Page') - .setDescription(strings.help.description) - .setThumbnail(strings.urls.avatar); - - // Construct the Slash Commands help - - let slashCommandsFields = []; - - const slashCommandsMap = interaction.client.slashCommands.map(e => { - return { - name: e.data.name, - description: e.data.description - }; - }) - - for (const e of slashCommandsMap) { - slashCommandsFields.push({ - name: `- /${e.name}`, - value: e.description, - inline: false, - }); - } - - // Construct the Dot Commands Help - let dotCommandsFields = []; - - const dotCommandsMap = interaction.client.dotCommands.map(e => { - return { - name: e.name, - description: e.description, - usage: e.usage - }; - }); - - for (const e of dotCommandsMap) { - dotCommandsFields.push({ - name: `- .${e.name}`, - value: `${e.description}\nUsage: ${e.usage}`, - inline: false, - }); - } - - helpEmbed.addField('Slash Commands', strings.help.slash); - helpEmbed.addFields(slashCommandsFields); - helpEmbed.addField('Dot Commands', strings.help.dot); - helpEmbed.addFields(dotCommandsFields); - - return { embeds: [ - helpEmbed - ], ephemeral: true }; - }, - gif(commandData) { - return { embeds: [new Discord.MessageEmbed() - .setAuthor(`${commandData.args}.${commandData.command}`) - .setImage(commandData.embed_url) - .setTimestamp() - .setFooter(commandData.author)]}; - }, - pasta(commandData) { - return { embeds: [ new Discord.MessageEmbed() - .setAuthor(`${commandData.args}.${commandData.command}`) - .setDescription(commandData.content) - .setThumbnail(commandData.iconUrl) - .setTimestamp() - .setFooter(commandData.author)]}; - }, - pastas(commandData) { - const pastasArray = []; - const pastasEmbed = new Discord.MessageEmbed() - .setAuthor(commandData.command) - .setTimestamp() - .setFooter(commandData.author); - - for (const row of commandData.pastas) { - pastasArray.push(`#${row.id} - ${row.name}.pasta`); - } - - const pastasString = pastasArray.join('\n'); - pastasEmbed.setDescription(pastasString); - - return { embeds: [pastasEmbed], ephemeral: true }; - }, - gifs(commandData) { - const gifsArray = []; - const gifsEmbed = new Discord.MessageEmbed() - .setAuthor(commandData.command) - .setTimestamp() - .setFooter(commandData.author); - - for (const row of commandData.gifs) { - gifsArray.push(`#${row.id} - ${row.name}.gif`); - } - - const gifsString = gifsArray.join('\n'); - gifsEmbed.setDescription(gifsString); - - return { embeds: [gifsEmbed] }; - }, - text(commandData) { - return { embeds: [new Discord.MessageEmbed() - .setAuthor(commandData.command) - .setDescription(commandData.content) - .setTimestamp() - .setFooter(commandData.author)]}; - }, - requests(commandData) { - const requestsEmbed = new Discord.MessageEmbed() - .setAuthor(commandData.command) - .setTimestamp() - .setFooter(commandData.author); - - const requestsArray = []; - - for (const row of commandData.requests) { - requestsArray.push( - `**#${row.id} - ${row.author}**`, - `Request: ${row.request}` - ); - } - - requestsEmbed.setDescription(requestsArray.join('\n')); - - return { embeds: [requestsEmbed], ephemeral: true }; - }, - strain(strainInfo, interaction) { - const strainEmbed = new Discord.MessageEmbed() - .setTimestamp(); - strainEmbed.addFields([ - { - name: 'Strain Name', - value: `${strainInfo.strain}`, - inline: true, - }, - { - name: 'Type', - value: `${strainInfo.type}`, - inline: true, - }, - { - name: 'Effects', - value: `${strainInfo.effects}`, - inline: true, - }, - { - name: 'Flavor', - value: `${strainInfo.flavor}`, - inline: true, - }, - { - name: 'Rating', - value: `⭐️${strainInfo.rating}`, - inline: true, - }, - { - name: 'Description', - value: `${strainInfo.description}`, - inline: false, - }, - ]); - - interaction.reply({ embeds: [ strainEmbed ]}); - }, - }, - collect: { - gifName(interaction) { - const gifNameFilter = m => m.author.id == strings.temp.gifUserId; - return interaction.channel.createMessageCollector({ filter: gifNameFilter, time: 30000 }); - }, - }, - upload: { - request(commandData, client) { - const query = `INSERT INTO requests (author, request, status) VALUES (${db.escape(commandData.author)},${db.escape(commandData.args)},'Active')`; - db.query(query, (err, rows, fields) => { - if (err) throw err; - functions.download.requests(client); - }); - }, - pasta(pastaData, client) { - const query = `INSERT INTO pastas (name, content) VALUES (${db.escape(pastaData.name)},${db.escape(pastaData.content)})`; - db.query(query, (err, rows, fields) => { - if (err) throw err; - functions.download.pastas(client); - }); - }, - joint(content, client) { - const query = `INSERT INTO joints (content) VALUES (${db.escape(content)})`; - db.query(query, (err, rows, fields) => { - if (err) throw err; - functions.download.joints(client); - }); - }, - gif(gifData, client) { - const query = `INSERT INTO gifs (name, embed_url) VALUES (${db.escape(gifData.name)}, ${db.escape(gifData.embed_url)})`; - db.query(query, (err, rows, fields) => { - if (err) throw err; - functions.download.gifs(client); - }); - }, - setup(interaction) { - /* Tables: - * - gifs - * - joints - * - pastas - * - requests - * - strains */ - const gifsQuery = "CREATE TABLE 'gifs' (id int(11), name varchar(100), embed_url varchar(1000), PRIMARY KEY(id))"; - const jointsQuery = "CREATE TABLE 'joints' (id int(11), content varchar(1000), PRIMARY KEY(id))"; - const pastasQuery = "CREATE TABLE 'pastas' (id int(11), name varchar(100), content varchar(1900), iconurl varchar(200) DEFAULT 'https://cdn.discordapp.com/avatars/513184762073055252/12227aa23a06d5178853e59b72c7487b.webp?size=128', PRIMARY KEY(id))"; - const requestsQuery = "CREATE TABLE 'requests' (id int(11), author varchar(100), request varchar(1000), status varchar(10) DEFAULT 'Active', PRIMARY KEY(id))"; - const strainsQuery = "CREATE TABLE 'strains' (id smallint(6), name varchar(60), type varchar(10), effects varchat(80), ailment varchar(70), flavor varchar(30), PRIMARY KEY(id))"; - - // Check for owner - if (interaction.user.id == ownerId) { - db.query(gifsQuery, (err, rows, fields) => { - if (err) throw err; - }); - db.query(jointsQuery, (err, rows, fields) => { - if (err) throw err; - }); - db.query(pastasQuery, (err, rows, fields) => { - if (err) throw err; - }); - db.query(requestsQuery, (err, rows, fields) => { - if (err) throw err; - }); - db.query(strainsQuery, (err, rows, fields) => { - if (err) throw err; - }); - return 'I\'ve created the required tables. Please check your database to validate this.'; - } else { - return 'Sorry, you don\'t have permission to do that.'; - } - }, - medicalAdvice(content, client) { - const query = `INSERT INTO medical_advice (content) VALUES (${db.escape(content)})`; - db.query(query, (err, rows, fields) => { - if (err) throw err; - functions.download.medicalAdvice(client); - }); - }, - strain(interaction) { - const strain = db.escape(interaction.options.getString('name')); - const type = db.escape(interaction.options.getString('type')); - const effects = db.escape(( interaction.options.getString('effects') || 'Unkown' )); - const description = db.escape(( interaction.options.getString('description') || 'Unknown' )); - const flavor = db.escape(( interaction.options.getString('flavor') || 'Unknown' )); - const rating = db.escape(( interaction.options.getString('rating') || '3' )); - const strainQuery = `INSERT INTO strains (strain, type, effects, description, flavor, rating) VALUES (${strain}, ${type}, ${effects}, ${description}, ${flavor}, ${rating})`; - console.log(strainQuery); - return new Promise((resolve, reject) => { - db.query(strainQuery, (err, rows, fields) => { - if (err) reject(err); - functions.download.strains(interaction.client); - resolve(); - }); - }) - } - }, - download: { - requests(client) { - const query = 'SELECT * FROM requests WHERE status = \'Active\' ORDER BY id DESC'; - db.query(query, (err, rows, fields) => { - if (err) throw err; - functions.collections.requests(rows, client); - }); - }, - pastas(client) { - const query = 'SELECT * FROM pastas ORDER BY id ASC'; - db.query(query, (err, rows, fields) => { - if (err) throw err; - functions.collections.pastas(rows, client); - }); - }, - gifs(client) { - const query = 'SELECT * FROM gifs ORDER BY id ASC'; - db.query(query, (err, rows, fields) => { - if (err) throw err; - functions.collections.gifs(rows, client); - }); - }, - joints(client) { - const query = 'SELECT * FROM joints ORDER BY id ASC'; - db.query(query, (err, rows, fields) => { - if (err) throw err; - functions.collections.joints(rows, client); - }); - }, - strain(strainName, interaction) { - const query = `SELECT id, strain, type, effects, description, flavor, rating FROM strains WHERE strain = ${db.escape(strainName)}`; - db.query(query, (err, rows, fields) => { - if (rows != undefined) { - const strainInfo = { - id: `${rows[0].id}`, - strain: `${rows[0].strain}`, - type: `${rows[0].type}`, - effects: `${rows[0].effects}`, - description: `${rows[0].description}`, - flavor: `${rows[0].flavor}`, - rating: `${rows[0].rating}`, - }; - functions.embeds.strain(strainInfo, interaction); - } - }); - }, - strains(client) { - const query = 'SELECT id, strain FROM strains'; - db.query(query, (err, rows, fields) => { - if (err) throw err; - functions.collections.strains(rows, client); - }); - }, - medicalAdvice(client) { - const query = 'SELECT * FROM medical_advice ORDER BY id ASC'; - db.query(query, (err, rows, fields) => { - if (err) throw err; - functions.collections.medicalAdvice(rows, client); - }); - } - }, - weed: { - strain: { - lookup(strainName, client) { - const strainSearcher = new FuzzySearch(client.strains.map(e => e.name)); - return strainSearcher.search(strainName).slice(0,25); - }, - submit(strainName) { - const query = `` - return strainName; - } - } - }, - // Parent-Level functions (miscellaneuous) - closeRequest(requestId, interaction) { - if (interaction.user.id == ownerId) { - const { client } = interaction; - const query = `UPDATE requests SET status = 'Closed' WHERE id = ${db.escape(requestId)}`; - db.query(query, (err, rows, fields) => { - if (err) throw err; - functions.download.requests(client); - }); - interaction.reply({ content: `Request #${requestId} has been closed.`, ephemeral: true }); - } else { - interaction.reply({ content: 'You do not have permission to do that.', ephemeral: true }); - } - if (isDev) { - console.log(requestId, interaction, ownerId); - } - }, - spongebob(commandData) { - let newText = ''; - for (const letter of commandData.args) { - if (letter == ' ') { - newText += letter; - continue; - } - if (letter == 'i' || letter == 'I') { - newText += 'i'; - continue; - } - if (letter == 'l' || letter == 'L') { - newText += 'L'; - continue; - } - if (Math.random() > 0.5) { - newText += letter.toUpperCase(); - } else { - newText += letter.toLowerCase(); - } - } - - return newText + ' <:spongebob:1053398825965985822>'; - }, -}; - -module.exports = functions; \ No newline at end of file diff --git a/main.js b/main.js index f215e21..929b536 100644 --- a/main.js +++ b/main.js @@ -3,216 +3,46 @@ // dotenv for handling environment variables const dotenv = require('dotenv'); dotenv.config(); -const token = process.env.TOKEN; -const statusChannelId = process.env.statusChannelId; +const token = process.env.TOKEN;; // Discord.JS -const { Client, Intents } = require('discord.js'); +const { Client, GatewayIntentBits } = require('discord.js'); const client = new Client({ intents: [ - 'GUILDS', - 'GUILD_MESSAGES', - 'GUILD_MESSAGE_REACTIONS', - 'DIRECT_MESSAGES', - 'DIRECT_MESSAGE_REACTIONS', - ], - partials: [ - 'CHANNEL', - 'MESSAGE', - ], + GatewayIntentBits.Guilds + ] }); -const { MessageActionRow, MessageButton } = require('discord.js'); // Various imports -const fn = require('./functions.js'); -const config = require('./config.json'); -const strings = require('./strings.json'); -const isDev = process.env.isDev; +const fn = require('./modules/functions.js'); +const strings = require('./data/strings.json'); +const debugMode = process.env.DEBUG; +const statusChannelId = process.env.STATUSCHANNELID client.once('ready', () => { - fn.collections.slashCommands(client); - fn.collections.dotCommands(client); - fn.collections.setvalidCommands(client); - fn.download.gifs(client); - fn.download.pastas(client); - fn.download.joints(client); - fn.download.requests(client); - fn.download.strains(client); - fn.download.medicalAdvice(client); + fn.collectionBuilders.slashCommands(client); console.log('Ready!'); - client.channels.fetch(statusChannelId).then(channel => { - channel.send(`${new Date().toISOString()} -- <@${process.env.ownerId}>\nStartup Sequence Complete`); - }); + // client.channels.fetch(statusChannelId).then(channel => { + // channel.send(`${new Date().toISOString()} -- Ready`).catch(e => console.error(e)); + // }); }); // slash-commands client.on('interactionCreate', async interaction => { if (interaction.isCommand()) { - if (isDev) { - console.log(interaction); - } const { commandName } = interaction; if (client.slashCommands.has(commandName)) { - client.slashCommands.get(commandName).execute(interaction); + client.slashCommands.get(commandName).execute(interaction).catch(e => console.error(e)); } 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()) { - if (interaction.user.id != strings.temp.gifUserId) return; - // Get some meta info from strings - const index = strings.temp.gifIndex; - const limit = strings.temp.gifLimit; - let newIndex; - const buttonId = interaction.component.customId; - switch (buttonId) { - case 'prevGif': - newIndex = index - 1; - strings.temp.gifIndex = newIndex; - // If we're leaving the last GIF, enable the Next GIF button - if (index == limit) { - // Re-Send Previous GIF button - const prevButton = new MessageButton().setCustomId('prevGif').setLabel('Previous GIF').setStyle('SECONDARY'); - // Re-Send Confirm GIF Button - const confirmButton = new MessageButton().setCustomId('confirmGif').setLabel('Confirm').setStyle('PRIMARY'); - // Enable Next GIF Button - const nextButton = new MessageButton().setCustomId('nextGif').setLabel('Next GIF').setStyle('SECONDARY'); - // Re-Send Cancel Button - const cancelButton = new MessageButton().setCustomId('cancelGif').setLabel('Cancel').setStyle('DANGER'); - // Put all the above into an ActionRow to be sent as a component of the reply - const row = new MessageActionRow().addComponents(prevButton, confirmButton, nextButton, cancelButton); - - interaction.update({ content: strings.temp.gifs[newIndex].embed_url, components: [row] }); - break; - } - // If we're going into the first GIF, disable the Previous GIF button - if (newIndex == 0) { - // Disable Previous GIF button - const prevButton = new MessageButton().setCustomId('prevGif').setLabel('Previous GIF').setStyle('SECONDARY').setDisabled(); - // Re-Send Confirm GIF Button - const confirmButton = new MessageButton().setCustomId('confirmGif').setLabel('Confirm').setStyle('PRIMARY'); - // Re-Send Next GIF Button - const nextButton = new MessageButton().setCustomId('nextGif').setLabel('Next GIF').setStyle('SECONDARY'); - // Re-Send Cancel Button - const cancelButton = new MessageButton().setCustomId('cancelGif').setLabel('Canceled').setStyle('DANGER'); - // Put all the above into an ActionRow to be sent as a component of the reply - const row = new MessageActionRow().addComponents(prevButton, confirmButton, nextButton, cancelButton); - - interaction.update({ content: strings.temp.gifs[newIndex].embed_url, components: [row] }); - break; - } - - interaction.update(strings.temp.gifs[newIndex].embed_url); - break; - case 'confirmGif': - const gifData = { - name: strings.temp.gifName, - embed_url: strings.temp.gifs[strings.temp.gifIndex].embed_url, - }; - fn.upload.gif(gifData, client); - interaction.update({ content: `I've saved the GIF as ${gifData.name}.gif`, components: [] }); - fn.download.gifs(interaction.client); - break; - case 'nextGif': - newIndex = index + 1; - strings.temp.gifIndex = newIndex; - // If we're leaving the first GIF, enable the Previous GIF button - if (index == 0) { - // Enable Previous GIF button - const prevButton = new MessageButton().setCustomId('prevGif').setLabel('Previous GIF').setStyle('SECONDARY').setDisabled(false); - // Re-Send Confirm GIF Button - const confirmButton = new MessageButton().setCustomId('confirmGif').setLabel('Confirm').setStyle('PRIMARY'); - // Re-Send Next GIF Button - const nextButton = new MessageButton().setCustomId('nextGif').setLabel('Next GIF').setStyle('SECONDARY'); - // Re-Send Cancel Button - const cancelButton = new MessageButton().setCustomId('cancelGif').setLabel('Cancel').setStyle('DANGER'); - // Put all the above into an ActionRow to be sent as a component of the reply - const row = new MessageActionRow().addComponents(prevButton, confirmButton, nextButton, cancelButton); - - interaction.update({ content: strings.temp.gifs[newIndex].embed_url, components: [row] }); - break; - } - // If we're going into the last GIF, disable the Next GIF button - if (newIndex == strings.temp.gifLimit) { - // Re-Send Previous GIF button - const prevButton = new MessageButton().setCustomId('prevGif').setLabel('Previous GIF').setStyle('SECONDARY'); - // Re-Send Confirm GIF Button - const confirmButton = new MessageButton().setCustomId('confirmGif').setLabel('Confirm').setStyle('PRIMARY'); - // Disable Next GIF Button - const nextButton = new MessageButton().setCustomId('nextGif').setLabel('Next GIF').setStyle('SECONDARY').setDisabled(); - // Re-Send Cancel Button - const cancelButton = new MessageButton().setCustomId('cancelGif').setLabel('Canceled').setStyle('DANGER'); - // Put all the above into an ActionRow to be sent as a component of the reply - const row = new MessageActionRow().addComponents(prevButton, confirmButton, nextButton, cancelButton); - - interaction.update({ content: strings.temp.gifs[newIndex].embed_url, components: [row] }); - break; - } - - interaction.update(strings.temp.gifs[newIndex].embed_url); - break; - case 'cancelGif': - // Previous GIF button - const prevButton = new MessageButton().setCustomId('prevGif').setLabel('Previous GIF').setStyle('SECONDARY').setDisabled(); - // Confirm GIF Button - const confirmButton = new MessageButton().setCustomId('confirmGif').setLabel('Confirm').setStyle('PRIMARY').setDisabled(); - // Next GIF Button - const nextButton = new MessageButton().setCustomId('nextGif').setLabel('Next GIF').setStyle('SECONDARY').setDisabled(); - // Cancel Button - const cancelButton = new MessageButton().setCustomId('cancelGif').setLabel('Canceled').setStyle('DANGER'); - // Put all the above into an ActionRow to be sent as a component of the reply - const row = new MessageActionRow().addComponents(prevButton, confirmButton, nextButton, cancelButton); - interaction.component.setDisabled(true); - - interaction.update({ content: 'Canceled.', components: [row] }); - break; - default: - break; - } - } - - // Handle autocomplete requests - if (interaction.isAutocomplete()) { - if (interaction.commandName == 'strain') { - const searchString = interaction.options.getFocused(); - const choices = fn.weed.strain.lookup(searchString, interaction.client); - await interaction.respond( - choices.map(choice => ({ name: choice, value: choice })) - ) - } else { - return; + interaction.reply('Sorry, I don\'t have access to that command.').catch(e => console.error(e)); + console.error('Slash command attempted to run but not found: /' + commandName); } } }); -// dot-commands -client.on('messageCreate', message => { - // Some basic checking to prevent running unnecessary code - if (message.author.bot) return; - - // Wildcard Responses, will respond if any message contains the trigger word(s), excluding self-messages - const lowerContent = message.content.toLowerCase(); - if (lowerContent.includes('big') && lowerContent.includes('doinks')) message.reply('gang.'); - if (lowerContent.includes('ligma')) message.reply('ligma balls, goteem'); - if (lowerContent.includes('frfr') || lowerContent.includes('fr fr') || lowerContent.includes('bussin') || lowerContent.includes(' ong') || lowerContent.startsWith('ong')) message.reply('ongggg no :billed_cap: fr fr str8 bussin'); - - // Break the message down into its components and analyze it - const commandData = fn.dot.getCommandData(message); - console.log(commandData); - - if (commandData.isValid && commandData.isCommand) { - try { - client.dotCommands.get(commandData.command).execute(message, commandData); - } - catch (error) { - console.error(error); - message.reply('There was an error trying to execute that command.'); - } - } - return; +process.on('uncaughtException', err => { + console.error(err); }); client.login(token); \ No newline at end of file diff --git a/modules/CustomClasses.js b/modules/CustomClasses.js new file mode 100644 index 0000000..dc29ffa --- /dev/null +++ b/modules/CustomClasses.js @@ -0,0 +1,353 @@ +const Collection = require('@discordjs/collection'); + +module.exports = { + CommandData: class { + constructor() { + this.name = ""; + this.type = ""; // "dot" or "slash" + this.isValid = false; + this.guildId = ""; + } + }, + GlobalContentManager: class { + constructor(collections) { + const { pastas, joints, mds, gifs, strains } = collections; + this.pastas = (pastas instanceof Collection) ? pastas : new Collection(); + this.joints = (joints instanceof Collection) ? joints: new Collection(); + this.mds = (mds instanceof Collection) ? mds : new Collection(); + this.gifs = (gifs instanceof Collection) ? gifs : new Collection(); + this.strains = (strains instanceof Collection) ? strains : new Collection(); + this.guildId = "GLOBAL"; + + this.queryBuilders = { + insert: { + pasta(title, content) { + // Upload a copypasta to the database + const rawQuery = "INSERT INTO ?? (??, ??, ??) VALUES (?, ?, ?)"; + const values = ["pastas", "title", "content", "guild_id", title, content, this.guildId]; + return { + rawQuery: rawQuery, + values: values + } + }, + joint(content) { + // Upload a stoner catchphrase to the database + const rawQuery = "INSERT INTO ?? (??, ??) VALUES (?, ?)"; + const values = ["joints", "content", "guild_id", content, this.guildId]; + return { + rawQuery: rawQuery, + values: values + } + }, + md(content) { + // Upload medical advice to the database + const rawQuery = "INSERT INTO ?? (??, ??) VALUES (?, ?)"; + const values = ["mds", "content", "guild_id", content, this.guildId]; + return { + rawQuery: rawQuery, + values: values + } + }, + media(title, url) { + // Upload an embeddable media url to the database + const rawQuery = "INSERT INTO ?? (??, ??, ??) VALUES (?, ?, ?)"; + const values = ["media", "title", "url", "guild_id", title, url, this.guildId]; + return { + rawQuery: rawQuery, + values: values + } + }, + strain(name, type, effects, flavor, rating) { + // Upload an embeddable media url to the database + const rawQuery = "INSERT INTO ?? (??, ??, ??) VALUES (?, ?, ?)"; + const values = ["media", "title", "url", "guild_id", title, url, this.guildId]; + return { + rawQuery: rawQuery, + values: values + } + } + }, + select: { + pasta(title) { + // Fetch a copypasta from the database + const rawQuery = "SELECT * FROM ?? WHERE ?? = ? AND ?? = ?"; + const values = ["pastas", "title", title, "guild_id", this.guildId]; + return { + rawQuery: rawQuery, + values: values + } + }, + joint(id) { + // Fetch a stoner catchphrase from the database + const rawQuery = "SELECT * FROM ?? WHERE ?? = ? AND ?? = ?"; + const values = ["joints", "id", id, "guild_id", this.guildId]; + return { + rawQuery: rawQuery, + values: values + } + }, + md(id) { + // Fetch medical advice from the database + const rawQuery = "SELECT * FROM ?? WHERE ?? = ? AND ?? = ?"; + const values = ["mds", "id", id, "guild_id", this.guildId]; + return { + rawQuery: rawQuery, + values: values + } + }, + media(title) { + // Fetch an embeddable media url from the database + const rawQuery = "SELECT * FROM ?? WHERE ?? = ? AND ?? = ?"; + const values = ["media", "title", title, "guild_id", this.guildId]; + return { + rawQuery: rawQuery, + values: values + } + } + }, + update: { + pasta(title, content) { + // Update a copypasta in the database + const rawQuery = "UPDATE ?? SET ? WHERE ?? = ? AND ?? = ?"; + const values = ["pastas", {content: content}, "title", title, "guild_id", this.guildId]; + return { + rawQuery: rawQuery, + values: values + } + }, + joint(id, content) { + // Update a stoner catchphrase in the database + const rawQuery = "UPDATE ?? SET ? WHERE ?? = ? AND ?? = ?"; + const values = ["joints", {content: content}, "id", id, "guild_id", this.guildId]; + return { + rawQuery: rawQuery, + values: values + } + }, + md(id, content) { + // Update medical advice in the database + const rawQuery = "UPDATE ?? SET ? WHERE ?? = ? AND ?? = ?"; + const values = ["mds", {content: content}, "id", id, "guild_id", this.guildId]; + return { + rawQuery: rawQuery, + values: values + } + }, + media(title, url) { + // Update an embeddable media url in the database + const rawQuery = "UPDATE ?? SET ? WHERE ?? = ? AND ?? = ?"; + const values = ["media", {url: url}, "title", title, "guild_id", this.guildId]; + return { + rawQuery: rawQuery, + values: values + } + } + }, + delete: { + pasta(title) { + // Delete a copypasta from the database + const rawQuery = "DELETE FROM ?? WHERE ?? = ? AND ?? = ?"; + const values = ["pastas", "title", title, "guild_id", this.guildId]; + return { + rawQuery: rawQuery, + values: values + } + }, + joint(id) { + // Delete a stoner catchphrase from the database + const rawQuery = "DELETE FROM ?? WHERE ?? = ? AND ?? = ?"; + const values = ["joints", "id", id, "guild_id", this.guildId]; + return { + rawQuery: rawQuery, + values: values + } + }, + md(id) { + // Delete medical advice from the database + const rawQuery = "DELETE FROM ?? WHERE ?? = ? AND ?? = ?"; + const values = ["mds", "id", id, "guild_id", this.guildId]; + return { + rawQuery: rawQuery, + values: values + } + }, + media(title) { + // Delete an embeddable media url from the database + const rawQuery = "DELETE FROM ?? WHERE ?? = ? AND ?? = ?"; + const values = ["media", "title", title, "guild_id", this.guildId]; + return { + rawQuery: rawQuery, + values: values + } + } + } + } + } + + }, + GuildContentManager: class { + constructor(collections) { + const { pastas, joints, mds, gifs } = collections; + this.pastas = (pastas instanceof Collection) ? pastas : new Collection(); + this.joints = (joints instanceof Collection) ? joints: new Collection(); + this.mds = (mds instanceof Collection) ? mds : new Collection(); + this.gifs = (gifs instanceof Collection) ? gifs : new Collection(); + this.guildId = ""; + + this.queryBuilders = { + insert: { + pasta(title, content) { + // Upload a copypasta to the database + const rawQuery = "INSERT INTO ?? (??, ??, ??) VALUES (?, ?, ?)"; + const values = ["pastas", "title", "content", "guild_id", title, content, this.guildId]; + return { + rawQuery: rawQuery, + values: values + } + }, + joint(content) { + // Upload a stoner catchphrase to the database + const rawQuery = "INSERT INTO ?? (??, ??) VALUES (?, ?)"; + const values = ["joints", "content", "guild_id", content, this.guildId]; + return { + rawQuery: rawQuery, + values: values + } + }, + md(content) { + // Upload medical advice to the database + const rawQuery = "INSERT INTO ?? (??, ??) VALUES (?, ?)"; + const values = ["mds", "content", "guild_id", content, this.guildId]; + return { + rawQuery: rawQuery, + values: values + } + }, + media(title, url) { + // Upload an embeddable media url to the database + const rawQuery = "INSERT INTO ?? (??, ??, ??) VALUES (?, ?, ?)"; + const values = ["media", "title", "url", "guild_id", title, url, this.guildId]; + return { + rawQuery: rawQuery, + values: values + } + } + }, + select: { + pasta(title) { + // Fetch a copypasta from the database + const rawQuery = "SELECT * FROM ?? WHERE ?? = ? AND ?? = ?"; + const values = ["pastas", "title", title, "guild_id", this.guildId]; + return { + rawQuery: rawQuery, + values: values + } + }, + joint(id) { + // Fetch a stoner catchphrase from the database + const rawQuery = "SELECT * FROM ?? WHERE ?? = ? AND ?? = ?"; + const values = ["joints", "id", id, "guild_id", this.guildId]; + return { + rawQuery: rawQuery, + values: values + } + }, + md(id) { + // Fetch medical advice from the database + const rawQuery = "SELECT * FROM ?? WHERE ?? = ? AND ?? = ?"; + const values = ["mds", "id", id, "guild_id", this.guildId]; + return { + rawQuery: rawQuery, + values: values + } + }, + media(title) { + // Fetch an embeddable media url from the database + const rawQuery = "SELECT * FROM ?? WHERE ?? = ? AND ?? = ?"; + const values = ["media", "title", title, "guild_id", this.guildId]; + return { + rawQuery: rawQuery, + values: values + } + } + }, + update: { + pasta(title, content) { + // Update a copypasta in the database + const rawQuery = "UPDATE ?? SET ? WHERE ?? = ? AND ?? = ?"; + const values = ["pastas", {content: content}, "title", title, "guild_id", this.guildId]; + return { + rawQuery: rawQuery, + values: values + } + }, + joint(id, content) { + // Update a stoner catchphrase in the database + const rawQuery = "UPDATE ?? SET ? WHERE ?? = ? AND ?? = ?"; + const values = ["joints", {content: content}, "id", id, "guild_id", this.guildId]; + return { + rawQuery: rawQuery, + values: values + } + }, + md(id, content) { + // Update medical advice in the database + const rawQuery = "UPDATE ?? SET ? WHERE ?? = ? AND ?? = ?"; + const values = ["mds", {content: content}, "id", id, "guild_id", this.guildId]; + return { + rawQuery: rawQuery, + values: values + } + }, + media(title, url) { + // Update an embeddable media url in the database + const rawQuery = "UPDATE ?? SET ? WHERE ?? = ? AND ?? = ?"; + const values = ["media", {url: url}, "title", title, "guild_id", this.guildId]; + return { + rawQuery: rawQuery, + values: values + } + } + }, + delete: { + pasta(title) { + // Delete a copypasta from the database + const rawQuery = "DELETE FROM ?? WHERE ?? = ? AND ?? = ?"; + const values = ["pastas", "title", title, "guild_id", this.guildId]; + return { + rawQuery: rawQuery, + values: values + } + }, + joint(id) { + // Delete a stoner catchphrase from the database + const rawQuery = "DELETE FROM ?? WHERE ?? = ? AND ?? = ?"; + const values = ["joints", "id", id, "guild_id", this.guildId]; + return { + rawQuery: rawQuery, + values: values + } + }, + md(id) { + // Delete medical advice from the database + const rawQuery = "DELETE FROM ?? WHERE ?? = ? AND ?? = ?"; + const values = ["mds", "id", id, "guild_id", this.guildId]; + return { + rawQuery: rawQuery, + values: values + } + }, + media(title) { + // Delete an embeddable media url from the database + const rawQuery = "DELETE FROM ?? WHERE ?? = ? AND ?? = ?"; + const values = ["media", "title", title, "guild_id", this.guildId]; + return { + rawQuery: rawQuery, + values: values + } + } + } + } + } + } +} \ No newline at end of file diff --git a/modules/_clearCommands.js b/modules/_clearCommands.js new file mode 100644 index 0000000..34ee25d --- /dev/null +++ b/modules/_clearCommands.js @@ -0,0 +1,28 @@ +// dotenv for handling environment variables +const dotenv = require('dotenv'); +dotenv.config(); + +const { REST, Routes } = require('discord.js'); +const botId = process.env.BOTID; +const token = process.env.TOKEN; +const fs = require('fs'); + +console.log(`Token: ...${token.slice(-5)} | Bot ID: ${botId}`); + +const rest = new REST({ version: '10' }).setToken(token); + +(async () => { + try { + console.log('Started clearing global application (/) commands.'); + + await rest.put( + Routes.applicationCommands(botId), + { body: '' }, + ); + + console.log('Successfully cleared global application (/) commands.'); + process.exit(); + } catch (error) { + console.error(error); + } +})(); \ No newline at end of file diff --git a/_deploy-global.js b/modules/_deployGlobal.js similarity index 51% rename from _deploy-global.js rename to modules/_deployGlobal.js index 9145657..f3c7c92 100644 --- a/_deploy-global.js +++ b/modules/_deployGlobal.js @@ -2,9 +2,8 @@ const dotenv = require('dotenv'); dotenv.config(); -const { REST } = require('@discordjs/rest'); -const { Routes } = require('discord-api-types/v9'); -const clientId = process.env.BOTID; +const { REST, Routes } = require('discord.js'); +const botId = process.env.BOTID; const token = process.env.TOKEN; const fs = require('fs'); @@ -12,26 +11,26 @@ 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(`Token: ...${token.slice(-5)} | Bot ID: ${clientId}`); +console.log(`Token: ...${token.slice(-5)} | Bot ID: ${botId}`); -const rest = new REST({ version: '9' }).setToken(token); +const rest = new REST({ version: '10' }).setToken(token); (async () => { try { - console.log('Started refreshing application (/) commands.'); + console.log('Started refreshing global application (/) commands.'); await rest.put( - Routes.applicationCommands(clientId), + Routes.applicationCommands(botId), { body: commands }, ); - console.log('Successfully reloaded application (/) commands.'); + console.log('Successfully reloaded global application (/) commands.'); process.exit(); } catch (error) { console.error(error); diff --git a/modules/_sanitizeInput.js b/modules/_sanitizeInput.js new file mode 100644 index 0000000..1a25698 --- /dev/null +++ b/modules/_sanitizeInput.js @@ -0,0 +1,18 @@ +const replaceAll = require('string.prototype.replaceall'); +const fs = require('fs'); +const path = "./input.txt"; +const input = fs.readFileSync(path); +let output = ""; + +console.log(input); + +if (input.includes("\r\n")) { + output = replaceAll(input, "\r\n", "\\n"); +} else { + output = replaceAll(input, "\n", "\\n"); +} + +output = replaceAll(output, "`", "``"); + +console.log(output); +fs.writeFileSync(path, output); \ No newline at end of file diff --git a/modules/dbutils.js b/modules/dbutils.js new file mode 100644 index 0000000..c0652fb --- /dev/null +++ b/modules/dbutils.js @@ -0,0 +1,78 @@ +// Import the mySQL module +const mysql = require('mysql'); +// Import environment variables for database connections +const dotenv = require('dotenv'); +dotenv.config(); + +module.exports = { + getData(queryParts) { + // Return a Promise so we can resolve with data later + return new Promise((resolve, reject) => { + // Set up the database connection + const db = mysql.createConnection({ + host: process.env.DBHOST, + user: process.env.DBUSER, + password: process.env.DBPASS, + database: process.env.DBNAME, + port: process.env.DBPORT + }); + // Open the connection + db.connect((err) => { + if (err) { + reject(`Error connecting to the database: ${err.message}`); + return; + } + }); + + db.query(queryParts.rawQuery, queryParts.values, (err, results) => { + if (err) { + reject("Error fetching the data: " + err.message + "\nOffending Query: " + query); + // Close the database connection + db.end(); + return; + } + // If an empty set is returned + if (results.length == 0) { + reject("No results were returned."); + // Close the database connection + db.end(); + return; + } + // Close the database connection + db.end(); + resolve(results); + }); + }); + }, + setData(queryParts) { + // Return a Promise so we can resolve with data later + return new Promise((resolve, reject) => { + // Set up the database connection + const db = mysql.createConnection({ + host: process.env.DBHOST, + user: process.env.DBUSER, + password: process.env.DBPASS, + database: process.env.DBNAME, + port: process.env.DBPORT + }); + // Open the connection + db.connect((err) => { + if (err) { + reject(`Error connecting to the database: ${err.message}`); + return; + } + }); + db.query(queryParts.rawQuery, queryParts.values, (err, results) => { + if (err) { + reject(`Error setting the data: ${err.message}\nOffending Query: ${query}`); + // Close the database connection + db.end(); + return; + } + // Close the database connection + db.end(); + resolve(`Query executed successfully: ${results.affectedRows} row changed.`); + }); + }); + } +} \ No newline at end of file diff --git a/modules/functions.js b/modules/functions.js new file mode 100644 index 0000000..2e0ab4d --- /dev/null +++ b/modules/functions.js @@ -0,0 +1,80 @@ +// dotenv for importing environment variables +const dotenv = require('dotenv'); +const fs = require('fs'); +// Configure Environment Variables +dotenv.config(); + +// Discord.js +const Discord = require('discord.js'); +const { EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } = Discord; + +// Various imports from other files +const debugMode = process.env.DEBUG; +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 (debugMode) console.log('Slash Commands Collection Built'); + } + }, + builders: { + actionRows: { + example() { + // Create the button to go in the Action Row + const exampleButton = this.buttons.exampleButton(); + // Create the Action Row with the Button in it, to be sent with the Embed + return new ActionRowBuilder() + .addComponents(exampleButton); + }, + buttons: { + exampleButton() { + return new ButtonBuilder() + .setCustomId('id') + .setLabel('Label') + .setStyle(ButtonStyle.Primary); + } + } + }, + embeds: { + help(private) { + const embed = new EmbedBuilder() + .setColor(strings.embeds.color) + .setTitle(strings.help.title) + .setDescription(strings.help.content) + .setFooter({ text: strings.help.footer }); + return { embeds: [embed] }; + }, + error(content) { + const embed = new EmbedBuilder() + .setColor(strings.error.color) + .setTitle(strings.error.title) + .setDescription(content) + .setFooter({ text: strings.embeds.footer }); + return { embeds: [embed], ephemeral: true }; + }, + info(content) { + const embed = new EmbedBuilder() + .setColor(strings.embeds.infoColor) + .setTitle(strings.embeds.infoTitle) + .setDescription(content) + .setFooter({ text: strings.embeds.footer }); + return { embeds: [embed], ephemeral: true }; + } + } + } +}; + +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 index 93cfb21..1e6381b 100644 --- a/package.json +++ b/package.json @@ -1,36 +1,7 @@ { - "name": "nodbot", - "version": "3.0.8", - "description": "Nods and Nod Accessories.", - "main": "main.js", "dependencies": { - "@discordjs/builders": "^0.16.0", - "@discordjs/rest": "^0.1.0-canary.0", - "axios": "^0.21.4", - "discord-api-types": "^0.22.0", - "discord.js": "~13.11.0", - "dotenv": "^10.0.0", - "fuzzy-search": "^3.2.1", - "mysql": "^2.18.1", - "tenorjs": "^1.0.10" - }, - "engines": { - "node": "16.x" - }, - "devDependencies": { - "eslint": "^7.32.0" - }, - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/voidf1sh/nodbot.git" - }, - "author": "voidf1sh#0420", - "license": "ISC", - "bugs": { - "url": "https://github.com/voidf1sh/nodbot/issues" - }, - "homepage": "https://github.com/voidf1sh/nodbot#readme" + "discord.js": "^14.7.1", + "dotenv": "^16.0.3", + "string.prototype.replaceall": "^1.0.7" + } } diff --git a/slash-commands/closereq.js b/slash-commands/closereq.js deleted file mode 100644 index 13e5c02..0000000 --- a/slash-commands/closereq.js +++ /dev/null @@ -1,16 +0,0 @@ -const { SlashCommandBuilder } = require('@discordjs/builders'); -const fn = require('../functions.js'); - -module.exports = { - data: new SlashCommandBuilder() - .setName('closereq') - .setDescription('Close a request by ID, retrieved from /requests') - .addStringOption(option => - option.setName('requestid') - .setDescription('The ID of the request you\'d like to close.') - .setRequired(true)), - async execute(interaction) { - const requestId = interaction.options.getString('requestid'); - fn.closeRequest(requestId, interaction); - }, -}; \ No newline at end of file diff --git a/slash-commands/commands.js b/slash-commands/commands.js new file mode 100644 index 0000000..cf5d9f1 --- /dev/null +++ b/slash-commands/commands.js @@ -0,0 +1,37 @@ +const { SlashCommandBuilder } = require('discord.js'); +const fn = require('../modules/functions.js'); +const strings = require('../data/strings.json'); + +module.exports = { + data: new SlashCommandBuilder() + .setName("commands") + .setDescription("View all of the bot's commands") + .addBooleanOption(o => + o.setName("private") + .setDescription("Should the reply be visible only to you?") + .setRequired(false) + ), + id: "", // The command ID, used to generate clickable commands + about: "[meta] View all of the bot's commands", // A description of the command to be used with /commands + async execute(interaction) { + let private = interaction.options.getBoolean('private'); + if (private == undefined) { + private = true; + } + // Defer the reply so we have time to do things + await interaction.deferReply({ ephemeral: private }).catch(e => console.error(e)); + try { + // Code here... + let commandsParts = ["These are all of the bot's commands, and some information about them:"]; + interaction.client.slashCommands.forEach(slashCommand => { + commandsParts.push(` - ${slashCommand.about}`); + }); + let commandsString = commandsParts.join("\n"); + await interaction.editReply(fn.builders.embeds.info(commandsString)).catch(e => console.error(e)); + } catch(err) { + // In case of error, log it and let the user know something went wrong + console.error(err); + await interaction.editReply(strings.errors.generalCommand).catch(e => console.error(e)); + } + }, +}; \ No newline at end of file diff --git a/slash-commands/gifs.js b/slash-commands/gifs.js deleted file mode 100644 index 934ff86..0000000 --- a/slash-commands/gifs.js +++ /dev/null @@ -1,33 +0,0 @@ -const { SlashCommandBuilder } = require('@discordjs/builders'); -const { config } = require('dotenv'); -const fn = require('../functions.js'); - -module.exports = { - data: new SlashCommandBuilder() - .setName('gifs') - .setDescription('Get a list of currently saved GIFs.'), - async execute(interaction) { - if (!interaction.client.gifs) { - interaction.reply('For some reason I don\'t have access to the collection of gifs. Sorry about that!'); - return; - } - const gifsMap = interaction.client.gifs.map(e => { - return { - id: e.id, - name: e.name, - }; - }); - const commandData = { - gifs: [], - command: 'gifs', - author: interaction.user.tag, - }; - for (const row of gifsMap) { - commandData.gifs.push({ - id: row.id, - name: row.name, - }); - } - interaction.reply(fn.embeds.gifs(commandData)); - }, -}; \ No newline at end of file diff --git a/slash-commands/help.js b/slash-commands/help.js index d18d9fe..9324c2e 100644 --- a/slash-commands/help.js +++ b/slash-commands/help.js @@ -1,32 +1,30 @@ -const { SlashCommandBuilder } = require('@discordjs/builders'); -const fn = require('../functions.js'); +const { SlashCommandBuilder } = require('discord.js'); +const fn = require('../modules/functions.js'); +const strings = require('../data/strings.json'); module.exports = { data: new SlashCommandBuilder() - .setName('help') - .setDescription('Send the help page.') - // .addStringOption(option => - // option.setName('location') - // .setDescription('Send help in this channel or in DMs?') - // .setRequired(true) - // .addChoice('Here', 'channel') - // .addChoice('DMs', 'dm')) - , + .setName("help") + .setDescription("Get some help using the bot") + .addBooleanOption(o => + o.setName("private") + .setDescription("Should the reply be visible only to you?") + .setRequired(false) + ), + id: "", // The command ID, used to generate clickable commands + about: "Get some help using the bot", // A description of the command to be used with /commands async execute(interaction) { - // switch (interaction.options.getString('location')) { - // case 'channel': - // await interaction.reply(fn.embeds.help(interaction)); - // break; - // case 'dm': - // await interaction.user.createDM().then(channel => { - // channel.send(fn.embeds.help(interaction)); - // interaction.reply({content: 'I\'ve sent you a copy of my help page.', ephemeral: true}); - // }); - // break; - // default: - // interaction.reply('There was an error, please try again.'); - // break; - // } - await interaction.reply(fn.embeds.help(interaction)); + let private = interaction.options.getBoolean('private'); + if (private == undefined) { + private = true; + } + await interaction.deferReply({ ephemeral: private }).catch(e => console.error(e)); + try { + await interaction.editReply(fn.builders.embeds.help()).catch(e => console.error(e)); + } catch(err) { + // In case of error, log it and let the user know something went wrong + console.error(err); + await interaction.editReply(strings.errors.generalCommand).catch(e => console.error(e)); + } }, }; \ No newline at end of file diff --git a/slash-commands/jenny.js b/slash-commands/jenny.js deleted file mode 100644 index b0e1f23..0000000 --- a/slash-commands/jenny.js +++ /dev/null @@ -1,11 +0,0 @@ -const { SlashCommandBuilder } = require('@discordjs/builders'); -const fn = require('../functions.js'); - -module.exports = { - data: new SlashCommandBuilder() - .setName('jenny') - .setDescription('Jenny?'), - async execute(interaction) { - interaction.reply('867-5309'); - }, -}; \ No newline at end of file diff --git a/slash-commands/joint.js b/slash-commands/joint.js deleted file mode 100644 index 9bd6f92..0000000 --- a/slash-commands/joint.js +++ /dev/null @@ -1,17 +0,0 @@ -const { SlashCommandBuilder } = require('@discordjs/builders'); -const fn = require('../functions.js'); -const { emoji } = require('../strings.json'); - -module.exports = { - data: new SlashCommandBuilder() - .setName('joint') - .setDescription('Replies with a random cannabis-related quote.'), - async execute(interaction) { - let joints = []; - for (const entry of interaction.client.joints.map(joint => joint.content)) { - joints.push(entry); - } - const randIndex = Math.floor(Math.random() * joints.length); - interaction.reply(`${joints[randIndex]} ${emoji.joint}`); - }, -}; \ No newline at end of file diff --git a/slash-commands/joints.js b/slash-commands/joints.js deleted file mode 100644 index 2ca5b6c..0000000 --- a/slash-commands/joints.js +++ /dev/null @@ -1,15 +0,0 @@ -const { SlashCommandBuilder } = require('@discordjs/builders'); -const fn = require('../functions.js'); - -module.exports = { - data: new SlashCommandBuilder() - .setName('joints') - .setDescription('Send a list of all the /joint phrases.'), - async execute(interaction) { - let joints = []; - interaction.client.joints.map(e => { - joints.push(e.content); - }); - interaction.reply({ content: 'Here are all the `.joint` phrases I have saved:\n\n' + joints.join('\n'), ephemeral: true }); - }, -}; \ No newline at end of file diff --git a/slash-commands/lenny.js b/slash-commands/lenny.js deleted file mode 100644 index 58bb358..0000000 --- a/slash-commands/lenny.js +++ /dev/null @@ -1,11 +0,0 @@ -const { SlashCommandBuilder } = require('@discordjs/builders'); -const fn = require('../functions.js'); - -module.exports = { - data: new SlashCommandBuilder() - .setName('lenny') - .setDescription('( ͡° ͜ʖ ͡°)'), - async execute(interaction) { - interaction.reply('( ͡° ͜ʖ ͡°)'); - }, -}; \ No newline at end of file diff --git a/slash-commands/list.js b/slash-commands/list.js new file mode 100644 index 0000000..5d9945b --- /dev/null +++ b/slash-commands/list.js @@ -0,0 +1,67 @@ +const { SlashCommandBuilder } = require('discord.js'); +const fn = require('../modules/functions.js'); +const strings = require('../data/strings.json'); + +module.exports = { + data: new SlashCommandBuilder() + .setName("list") + .setDescription("View lists of saved content") + .addStringOption(o => + o.setName('contenttype') + .setDescription('What type of content do you want listed?') + .addChoices([ + "pastas", + "gifs", + "joints", + "mds", + "strains", + "suggestions" + ]) + .setRequired(true) + ) + .addBooleanOption(o => + o.setName("private") + .setDescription("Should the reply be visible only to you?") + .setRequired(false) + ), + id: "", // The command ID, used to generate clickable commands + about: "View lists of saved content, including IDs to edit and delete content.", // A description of the command to be used with /commands + async execute(interaction) { + let private = interaction.options.getBoolean('private'); + let contentType = interaction.options.getString('contenttype'); + if (private == undefined) { + private = true; + } + // Defer the reply so we have time to do things + await interaction.deferReply({ ephemeral: private }).catch(e => console.error(e)); + try { + // Handle each content type appropriately + switch (contentType) { + case "joints": + + break; + case "pastas": + + break; + case "mds": + + break; + case "strains": + + break; + case "gifs": + + break; + case "suggestions": + + break; + default: + break; + } + } catch(err) { + // In case of error, log it and let the user know something went wrong + console.error(err); + await interaction.editReply(strings.errors.generalCommand).catch(e => console.error(e)); + } + }, +}; \ No newline at end of file diff --git a/slash-commands/pastas.js b/slash-commands/pastas.js deleted file mode 100644 index baeb52d..0000000 --- a/slash-commands/pastas.js +++ /dev/null @@ -1,33 +0,0 @@ -const { SlashCommandBuilder } = require('@discordjs/builders'); -const { config } = require('dotenv'); -const fn = require('../functions.js'); - -module.exports = { - data: new SlashCommandBuilder() - .setName('pastas') - .setDescription('Get a list of currently saved copypastas.'), - async execute(interaction) { - if (!interaction.client.pastas) { - interaction.reply({ content: 'For some reason I don\'t have access to the collection of copypastas. Sorry about that!', ephemeral: true }); - return; - } - const commandData = { - author: interaction.user.tag, - command: interaction.commandName, - pastas: [], - }; - const pastasMap = interaction.client.pastas.map(e => { - return { - id: e.id, - name: e.name, - }; - }); - for (const row of pastasMap) { - commandData.pastas.push({ - id: row.id, - name: row.name, - }); - } - interaction.reply(fn.embeds.pastas(commandData)); - }, -}; \ No newline at end of file diff --git a/slash-commands/ping.js b/slash-commands/ping.js deleted file mode 100644 index b3ba2c6..0000000 --- a/slash-commands/ping.js +++ /dev/null @@ -1,11 +0,0 @@ -const { SlashCommandBuilder } = require('@discordjs/builders'); -const fn = require('../functions.js'); - -module.exports = { - data: new SlashCommandBuilder() - .setName('ping') - .setDescription('Check that the bot is alive and responding.'), - async execute(interaction) { - await interaction.reply({ content: 'Pong!', ephemeral: true }); - }, -}; \ No newline at end of file diff --git a/slash-commands/reload.js b/slash-commands/reload.js deleted file mode 100644 index c6543c0..0000000 --- a/slash-commands/reload.js +++ /dev/null @@ -1,20 +0,0 @@ -const { SlashCommandBuilder } = require('@discordjs/builders'); -const fn = require('../functions.js'); - -module.exports = { - data: new SlashCommandBuilder() - .setName('reload') - .setDescription('Reload all saved content, useful if saving something fails.'), - async execute(interaction) { - const { client } = interaction; - fn.collections.slashCommands(client); - fn.collections.dotCommands(client); - fn.collections.setvalidCommands(client); - fn.download.gifs(client); - fn.download.pastas(client); - fn.download.joints(client); - fn.download.requests(client); - fn.download.strains(client); - interaction.reply({ content: 'Reloaded!', ephemeral: true }); - }, -}; \ No newline at end of file diff --git a/slash-commands/requests.js b/slash-commands/requests.js deleted file mode 100644 index 039504e..0000000 --- a/slash-commands/requests.js +++ /dev/null @@ -1,39 +0,0 @@ -const { SlashCommandBuilder } = require('@discordjs/builders'); -const { config } = require('dotenv'); -const fn = require('../functions.js'); - -module.exports = { - data: new SlashCommandBuilder() - .setName('requests') - .setDescription('Get a list of Active requests from the database') - .addStringOption(option => - option - .setName('page') - .setDescription('Page Number') - .setRequired(true)), - async execute(interaction) { - const pageNum = interaction.options.getString('page'); - const commandData = { - author: interaction.user.tag, - command: interaction.commandName, - requests: [], - }; - const requestsMap = interaction.client.requests.map(e => { - return { - id: e.id, - author: e.author, - request: e.request, - }; - }); - for (let i = ( 10 * ( pageNum - 1 ) ); i < ( 10 * pageNum ); i++) { - if (requestsMap[i] != undefined) { - commandData.requests.push({ - id: requestsMap[i].id, - author: requestsMap[i].author, - request: requestsMap[i].request, - }); - } - } - interaction.reply(fn.embeds.requests(commandData)); - }, -}; \ No newline at end of file diff --git a/slash-commands/save.js b/slash-commands/save.js deleted file mode 100644 index 93c17b2..0000000 --- a/slash-commands/save.js +++ /dev/null @@ -1,99 +0,0 @@ -const { SlashCommandBuilder } = require('@discordjs/builders'); -const fn = require('../functions.js'); -const strings = require('../strings.json'); - -module.exports = { - data: new SlashCommandBuilder() - .setName('save') - .setDescription('Save content to Nodbot\'s database.') - .addSubcommandGroup(sc => - sc.setName('gif') - .setDescription('Save a gif (or other embeddable link)') - .addSubcommand(sc => - sc.setName('search') - .setDescription('Search Tenor for gifs') - .addStringOption(o => - o.setName('query') - .setDescription('Search Query') - .setRequired(true) - ) - ) - .addSubcommand(sc => - sc.setName('enterurl') - .setDescription('Save a link to some embeddable media') - .addStringOption(o => - o.setName('url') - .setDescription('The link to the media') - .setRequired(true) - ) - ) - ) - .addSubcommand(sc => - sc.setName('joint') - .setDescription('Save a phrase to the .joint database') - .addStringOption(o => - o.setName('content') - .setDescription('What do you want to save?') - .setRequired(true) - ) - ) - .addSubcommand(sc => - sc.setName('md') - .setDescription('Save a phrase to the .md database') - .addStringOption(o => - o.setName('content') - .setDescription('What do you want to save?') - .setRequired(true) - ) - ) - .addSubcommand(sc => - sc.setName('pasta') - .setDescription('Save a copypasta to the database') - .addStringOption(o => - o.setName('name') - .setDescription('What is the name of the copypasta? (don\'t include .pasta)') - .setRequired(true) - ) - .addStringOption(o => - o.setName('content') - .setDescription('What\'s the copypasta?') - .setRequired(true) - ) - ) - .addSubcommand(sc => - sc.setName('strain') - .setDescription('Save a strain\'s information to the database') - .addStringOption(option => - option.setName('name') - .setDescription('Name of the Strain') - .setRequired(true)) - .addStringOption(option => - option.setName('type') - .setDescription('Indica/Sativa/Hybrid') - .setRequired(true) - .addChoices( - { name: "Indica", value: "Indica" }, - { name: "Hybrid", value: "Hybrid" }, - { name: "Sativa", value: "Sativa" } - )) - .addStringOption(option => - option.setName('effects') - .setDescription('The effects given by the strain') - .setRequired(false)) - .addStringOption(option => - option.setName('flavor') - .setDescription('Flavor notes') - .setRequired(false)) - .addStringOption(option => - option.setName('rating') - .setDescription('Number of stars') - .setRequired(false)) - .addStringOption(option => - option.setName('description') - .setDescription('Description of the strain') - .setRequired(false)), - ), - async execute(interaction) { - - }, -}; \ No newline at end of file diff --git a/slash-commands/savegif.js b/slash-commands/savegif.js deleted file mode 100644 index 27f4778..0000000 --- a/slash-commands/savegif.js +++ /dev/null @@ -1,93 +0,0 @@ -const tenor = require('tenorjs').client({ - 'Key': process.env.tenorAPIKey, // https://tenor.com/developer/keyregistration - 'Filter': 'off', // "off", "low", "medium", "high", not case sensitive - 'Locale': 'en_US', - 'MediaFilter': 'minimal', - 'DateFormat': 'D/MM/YYYY - H:mm:ss A', -}); - -const { SlashCommandBuilder } = require('@discordjs/builders'); -const { MessageActionRow, MessageButton } = require('discord.js'); -const fn = require('../functions.js'); -const strings = require('../strings.json'); - -module.exports = { - data: new SlashCommandBuilder() - .setName('savegif') - .setDescription('Save a GIF to Nodbot\'s database.') - .addSubcommand(subcommand => - subcommand - .setName('searchgif') - .setDescription('Search Tenor for a GIF.') - .addStringOption(option => - option.setName('query') - .setDescription('Search Query') - .setRequired(true)) - .addStringOption(option => - option.setName('name') - .setDescription('What to save the gif as') - .setRequired(true)) - ) - .addSubcommand(subcommand => - subcommand - .setName('enterurl') - .setDescription('Specify a URL to save.') - .addStringOption(option => - option - .setName('url') - .setDescription('URL Link to the GIF') - .setRequired(true)) - .addStringOption(option => - option.setName('name') - .setDescription('What to save the gif as') - .setRequired(true)) - ), - async execute(interaction) { - if (interaction.options.getSubcommand() == 'searchgif') { - // Previous GIF button - const prevButton = new MessageButton().setCustomId('prevGif').setLabel('Previous GIF').setStyle('SECONDARY').setDisabled(true); - // Confirm GIF Button - const confirmButton = new MessageButton().setCustomId('confirmGif').setLabel('Confirm').setStyle('PRIMARY'); - // Next GIF Button - const nextButton = new MessageButton().setCustomId('nextGif').setLabel('Next GIF').setStyle('SECONDARY'); - // Cancel Button - const cancelButton = new MessageButton().setCustomId('cancelGif').setLabel('Cancel').setStyle('DANGER'); - // Put all the above into an ActionRow to be sent as a component of the reply - const actionRow = new MessageActionRow().addComponents(prevButton, confirmButton, nextButton, cancelButton); - - // Get the query - const query = interaction.options.getString('query'); - strings.temp.gifName = interaction.options.getString('name').toLowerCase(); - - // Search Tenor for the GIF - tenor.Search.Query(query, '10').then(res => { - strings.temp.gifs = []; - strings.temp.gifIndex = 0; - strings.temp.gifLimit = res.length - 1; - strings.temp.gifUserId = interaction.user.id; - - if (res[0] == undefined) { - interaction.reply('Sorry I was unable to find a GIF of ' + query); - return; - } - for (const row of res) { - strings.temp.gifs.push({ - embed_url: row.media[0].gif.url, - }); - } - interaction.reply({ content: strings.temp.gifs[0].embed_url, components: [actionRow], ephemeral: true }); - }); - } - - if (interaction.options.getSubcommand() == 'enterurl') { - const gifData = { - name: interaction.options.getString('name'), - embed_url: interaction.options.getString('url'), - }; - fn.upload.gif(gifData, interaction.client); - interaction.reply({ content: `I've saved the GIF as ${gifData.name}.gif`, ephemeral: true }); - fn.download.gifs(interaction.client); - } - - }, -}; \ No newline at end of file diff --git a/slash-commands/savejoint.js b/slash-commands/savejoint.js deleted file mode 100644 index ba48eac..0000000 --- a/slash-commands/savejoint.js +++ /dev/null @@ -1,17 +0,0 @@ -const { SlashCommandBuilder } = require('@discordjs/builders'); -const fn = require('../functions.js'); -const { emoji } = require('../strings.json'); - -module.exports = { - data: new SlashCommandBuilder() - .setName('savejoint') - .setDescription('Save a phrase for /joint!') - .addStringOption(option => - option.setName('joint-content') - .setDescription('What is the phrase?') - .setRequired(true)), - async execute(interaction) { - fn.upload.joint(interaction.options.getString('joint-content'), interaction.client); - interaction.reply({ content: `The joint has been rolled${emoji.joint}`, ephemeral: true }); - }, -}; \ No newline at end of file diff --git a/slash-commands/savemd.js b/slash-commands/savemd.js deleted file mode 100644 index 8391da3..0000000 --- a/slash-commands/savemd.js +++ /dev/null @@ -1,17 +0,0 @@ -const { SlashCommandBuilder } = require('@discordjs/builders'); -const fn = require('../functions.js'); -// const { emoji } = require('../strings.json'); - -module.exports = { - data: new SlashCommandBuilder() - .setName('savemd') - .setDescription('Add medical advice to NodBot\'s Database!') - .addStringOption(option => - option.setName('advice-content') - .setDescription('What is the advice?') - .setRequired(true)), - async execute(interaction) { - fn.upload.medicalAdvice(interaction.options.getString('advice-content'), interaction.client); - interaction.reply({ content: `The advice has been saved!`, ephemeral: true }); - }, -}; \ No newline at end of file diff --git a/slash-commands/savepasta.js b/slash-commands/savepasta.js deleted file mode 100644 index a89da4a..0000000 --- a/slash-commands/savepasta.js +++ /dev/null @@ -1,24 +0,0 @@ -const { SlashCommandBuilder } = require('@discordjs/builders'); -const fn = require('../functions.js'); - -module.exports = { - data: new SlashCommandBuilder() - .setName('savepasta') - .setDescription('Save a copypasta!') - .addStringOption(option => - option.setName('pasta-name') - .setDescription('What should the name of the copypasta be?') - .setRequired(true)) - .addStringOption(option => - option.setName('pasta-content') - .setDescription('What is the content of the copypasta?') - .setRequired(true)), - async execute(interaction) { - const pastaData = { - name: interaction.options.getString('pasta-name'), - content: interaction.options.getString('pasta-content'), - }; - fn.upload.pasta(pastaData, interaction.client); - interaction.reply({content: `The copypasta has been saved as ${pastaData.name}.pasta`, ephemeral: true }); - }, -}; \ No newline at end of file diff --git a/slash-commands/savestrain.js b/slash-commands/savestrain.js deleted file mode 100644 index 3cc90f8..0000000 --- a/slash-commands/savestrain.js +++ /dev/null @@ -1,54 +0,0 @@ -const { SlashCommandBuilder } = require('@discordjs/builders'); -const fn = require('../functions.js'); -const { emoji } = require('../strings.json'); - -// Strain Name | Type | Effects | Flavor | Rating | Description - -module.exports = { - data: new SlashCommandBuilder() - .setName('savestrain') - .setDescription('Store a new Strain in the database!') - .addStringOption(option => - option.setName('name') - .setDescription('Name of the Strain') - .setRequired(true)) - .addStringOption(option => - option.setName('type') - .setDescription('Indica/Sativa/Hybrid') - .setRequired(true) - .addChoices( - { name: "Indica", value: "Indica" }, - { name: "Hybrid", value: "Hybrid" }, - { name: "Sativa", value: "Sativa" } - )) - .addStringOption(option => - option.setName('effects') - .setDescription('The effects given by the strain') - .setRequired(false)) - .addStringOption(option => - option.setName('flavor') - .setDescription('Flavor notes') - .setRequired(false)) - .addStringOption(option => - option.setName('rating') - .setDescription('Number of stars') - .setRequired(false)) - .addStringOption(option => - option.setName('description') - .setDescription('Description of the strain') - .setRequired(false)), - async execute(interaction) { - fn.upload.strain(interaction).then(res => { - interaction.reply({ - content: `The strain information has been saved. (${interaction.options.getString('name')})`, - ephemeral: true - }); - }).catch(err => { - console.log(`E: ${err}`); - interaction.reply({ - content: 'There was a problem saving the strain.', - ephemeral: true - }); - }); - }, -}; \ No newline at end of file diff --git a/slash-commands/setup.js b/slash-commands/setup.js deleted file mode 100644 index fb955dd..0000000 --- a/slash-commands/setup.js +++ /dev/null @@ -1,15 +0,0 @@ -// UNDER DEVELOPMENT -// This *should* create the tables required to use Nodbot, -// assuming you have a database set up with proper permissions. - -const { SlashCommandBuilder } = require('@discordjs/builders'); -const fn = require('../functions.js'); - -module.exports = { - data: new SlashCommandBuilder() - .setName('setup') - .setDescription('Create the tables required to use Nodbot'), - async execute(interaction) { - await interaction.reply({ content: fn.upload.setup(), ephemeral: true }); - }, -}; \ No newline at end of file diff --git a/slash-commands/strain.js b/slash-commands/strain.js deleted file mode 100644 index 74cf8d3..0000000 --- a/slash-commands/strain.js +++ /dev/null @@ -1,17 +0,0 @@ -const { SlashCommandBuilder } = require('@discordjs/builders'); -const fn = require('../functions.js'); - -module.exports = { - data: new SlashCommandBuilder() - .setName('strain') - .setDescription('Look up information about a cannabis strain.') - .addStringOption(option => - option - .setName('name') - .setDescription('Strain Name') - .setRequired(true) - .setAutocomplete(true)), - async execute(interaction) { - fn.download.strain(interaction.options.getString('name'), interaction); - }, -}; \ No newline at end of file diff --git a/slash-commands/template b/slash-commands/template index c214fae..ceb2de6 100644 --- a/slash-commands/template +++ b/slash-commands/template @@ -1,11 +1,31 @@ -const { SlashCommandBuilder } = require('@discordjs/builders'); -const fn = require('../functions.js'); +const { SlashCommandBuilder } = require('discord.js'); +const fn = require('../modules/functions.js'); +const strings = require('../data/strings.json'); module.exports = { data: new SlashCommandBuilder() - .setName('') - .setDescription(''), + .setName("") + .setDescription("") + .addBooleanOption(o => + o.setName("private") + .setDescription("Should the reply be visible only to you?") + .setRequired(false) + ), + id: "", // The command ID, used to generate clickable commands + about: "", // A description of the command to be used with /commands async execute(interaction) { - await + let private = interaction.options.getBoolean('private'); + if (private == undefined) { + private = true; + } + // Defer the reply so we have time to do things + await interaction.deferReply({ ephemeral: private }).catch(e => console.error(e)); + try { + // Code here... + } catch(err) { + // In case of error, log it and let the user know something went wrong + console.error(err); + await interaction.editReply(strings.errors.generalCommand).catch(e => console.error(e)); + } }, }; \ No newline at end of file diff --git a/slash-commands/truth.js b/slash-commands/truth.js deleted file mode 100644 index 9107844..0000000 --- a/slash-commands/truth.js +++ /dev/null @@ -1,11 +0,0 @@ -const { SlashCommandBuilder } = require('@discordjs/builders'); -const fn = require('../functions.js'); - -module.exports = { - data: new SlashCommandBuilder() - .setName('truth') - .setDescription('The truth about the MHallihan Flight Simulator'), - async execute(interaction) { - await interaction.reply('https://www.twitch.tv/hochmania/clip/EsteemedSlickDootStinkyCheese-hncmP8aIP8_WQb_a?filter=clips&range=all&sort=time'); - }, -}; \ No newline at end of file diff --git a/strings.json b/strings.json deleted file mode 100644 index ccc342e..0000000 --- a/strings.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "help": { - "description": "Hi there! Thanks for checking out NodBot. NodBot is used in two distinct ways: with 'Slash Commands' (/help), and with 'Dot Commands' (nod.gif). The two types will be outlined below, along with usage examples.", - "slash": "Slash Commands always begin with a / and a menu will pop up to help complete the commands.", - "dot": "Dot Commands have the command at the end of the message, for example to search for a gif of 'nod', type 'nod.gif'" - }, - "emoji": { - "joint": "<:joint:862082955902976000>", - "next": "⏭️", - "previous": "⏮️", - "confirm": "☑️", - "cancel": "❌" - }, - "urls": { - "avatar": "https://cdn.discordapp.com/avatars/513184762073055252/12227aa23a06d5178853e59b72c7487b.webp?size=128" - }, - "temp": {} -} \ No newline at end of file