Compare commits

..

27 Commits

Author SHA1 Message Date
eddf16a821 Update .gitea/workflows/production-docker.yml 2024-09-29 17:23:58 +00:00
80f89d0117 Update .gitea/workflows/dev-docker.yml 2024-09-29 17:23:46 +00:00
d06cac6f3d Update .gitea/workflows/dev-docker.yml 2024-09-29 17:23:29 +00:00
7e2664083e Add .gitea/workflows/dev-docker.yml 2024-09-29 17:12:41 +00:00
d898398780 Merge pull request 'v3.3.3: Paged Commands' (#20) from v3.3.3 into main
Reviewed-on: #20
2024-09-26 13:24:54 +00:00
3a35b829ad Documentation update, final for v.3.3.3
All checks were successful
NodBot Production Dockerization / build (pull_request) Successful in 17s
2024-09-26 09:23:25 -04:00
8148890574 Add listing of aliases to /help
All checks were successful
NodBot Production Dockerization / build (pull_request) Successful in 7s
2024-09-26 09:22:16 -04:00
09901b5e58 Add aliases
All checks were successful
NodBot Production Dockerization / build (pull_request) Successful in 18s
2024-09-26 09:07:26 -04:00
4b75dfa885 v3.3.3 Documentation 2024-09-26 09:07:20 -04:00
d0528c3637 MVP for paged /joints
All checks were successful
NodBot Production Dockerization / build (pull_request) Successful in 55s
2024-09-26 08:47:42 -04:00
c3fa30ea64 MVP for paged /requests 2024-09-26 08:37:16 -04:00
709c8cfab7 MVP for paged /pastas 2024-09-26 08:27:11 -04:00
7aa3d5d0a1 MVP for Paged /gifs Browser 2024-09-26 08:09:09 -04:00
8e5931e0d4 Working on storing interaction data in the client 2024-09-25 23:22:22 -04:00
e4c389be71 Merge branch 'v3.3.3' of https://git.vfsh.dev/voidf1sh/nodbot into v3.3.3 2024-09-25 19:20:57 -04:00
be83b9ea74 WIP 2024-09-25 18:56:33 -04:00
ecbfc2bc2a Remove unnecessary function calls now that classes exist 2024-09-25 15:35:01 -04:00
00df6074d6 Fixed duplication of valid commands 2024-09-25 15:30:51 -04:00
23f081c6c1 Move CommandData and checkCommand to NodBot Classes 2024-09-25 11:54:33 -04:00
1a0817a89c Versioning -- v3.3.3 2024-09-25 11:54:04 -04:00
e5d520a73b 'v3.3.2 The Help Fix' (#17) from v3.3.2 into main
Reviewed-on: #17

Vastly improved the help message formatting and fixed a bug causing the help message to crash due to too many fields. Fixed by moving away from fields, then by filtering the command names to be unique. Aliases are being loaded as entirely new commands which will need to be fixed later.
2024-09-24 02:05:13 +00:00
fdcb56998c Ignore new env format
All checks were successful
NodBot Production Dockerization / build (pull_request) Successful in 6s
2024-09-23 22:02:01 -04:00
28443611e4 Vastly improved the /help command 2024-09-23 22:01:19 -04:00
f231df89d8 Move help from fields to the description.
All checks were successful
NodBot Production Dockerization / build (pull_request) Successful in 1m7s
2024-09-23 21:21:38 -04:00
0c2eae76aa Remove vestigial code 2024-09-23 21:10:08 -04:00
7b967cca8c Versioning -- v3.3.2 2024-09-23 21:06:35 -04:00
f3ce349ceb 'v3.3.0-metars' (#14) from v3.3.0-metars into main
Reviewed-on: #14

Seems to work, send it.
2024-09-24 01:04:48 +00:00
22 changed files with 625 additions and 300 deletions

View File

@ -0,0 +1,46 @@
name: NodBot PE Dockerization
on:
push:
tags:
- 'v*-dev*'
env:
DHUB_UNAME: ${{ secrets.DHUB_UNAME }}
DHUB_PWORD: ${{ secrets.DHUB_PWORD }}
jobs:
build:
runs-on: self-hosted
steps:
- name: Pull latest from Git
run: |
echo "Branch: tags/${{ gitea.ref_name }}"
pwd
whoami
mkdir -p /var/lib/act_runner/
cd /var/lib/act_runner/
if [ ! -d "nodbot" ]; then
git clone https://git.vfsh.dev/voidf1sh/nodbot-dev
cd nodbot-dev
else
cd nodbot-dev
git checkout main
git pull
fi
git checkout tags/${{ gitea.ref_name }}
- name: Build the Docker image
run: |
cd /var/lib/act_runner/nodbot-dev
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: |
cd /var/lib/act_runner/nodbot-dev
docker push v0idf1sh/nodbot-dev
- name: Restart the container
run: |
cd /srv/docker/nodbot-dev
docker-compose down
docker-compose up -d

View File

@ -1,9 +1,9 @@
name: NodBot Production Dockerization name: NodBot Production Dockerization
on: on:
pull_request: push:
branches: tags:
- main - 'v*-prod*'
env: env:
DHUB_UNAME: ${{ secrets.DHUB_UNAME }} DHUB_UNAME: ${{ secrets.DHUB_UNAME }}
@ -15,7 +15,7 @@ jobs:
steps: steps:
- name: Pull latest from Git - name: Pull latest from Git
run: | run: |
echo "Branch: ${{ gitea.head_ref }}" echo "Branch: tags/${{ gitea.ref_name }}"
pwd pwd
whoami whoami
mkdir -p /var/lib/act_runner/ mkdir -p /var/lib/act_runner/
@ -27,7 +27,7 @@ jobs:
cd nodbot cd nodbot
git pull git pull
fi fi
git checkout ${{ gitea.head_ref }} git checkout tags/${{ gitea.ref_name }}
- name: Build the Docker image - name: Build the Docker image
run: | run: |
cd /var/lib/act_runner/nodbot cd /var/lib/act_runner/nodbot

1
.gitignore vendored
View File

@ -3,6 +3,7 @@
package-lock.json package-lock.json
.VSCodeCounter/ .VSCodeCounter/
.env* .env*
*.env
# Custom folders # Custom folders
# gifs/* # gifs/*

24
CHANGELOG.md Normal file
View File

@ -0,0 +1,24 @@
## v3.3.x
#### v3.3.3 (#20)
* Fixed content-list slash commands `/gifs`, `/pastas`, `/joints`, `/requests` (#19)
* Fixed the creation of duplicate commands properly (#18)
* Added a ton of aliases for `.gif` (`.wav`, `.mp3`, `.mp4`, `.wmv`, etc.)
* Added alias lists in `/help`
#### v3.3.2 (#17)
* Fixed the `/help` command to not crash the bot (#15)
* Filtered out duplicate commands from the `/help` list, temporary fix (#18)
* Removed instances of `MessageEmbed.addField` due to deprecation (#16)
v3.3.1 - Polishing and bugfixing for new AvWx commands
v3.3.0 - Added `.metar`, `.atis`, and `.datis` AvWx commands
## v3.0.x
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

View File

@ -0,0 +1,151 @@
const customEmbeds = require('../CustomModules/Embeds.js');
const InteractionStorage = require('../CustomModules/InteractionStorage.js');
const indexer = require('../CustomModules/Indexer.js');
const fn = require('../functions.js');
const requests = require('../slash-commands/requests.js');
module.exports = {
baseEvent(interaction) {
let iStorage;
if (interaction.client.iStorage.has(interaction.message.interaction.id)) {
iStorage = interaction.client.iStorage.get(interaction.message.interaction.id)
} else {
iStorage = new InteractionStorage(interaction.message.interaction.id, interaction);
iStorage.page = 0;
}
if (interaction.user.id !== iStorage.userId) return;
switch (interaction.component.customId) {
// Any of the gifsPage Buttons
case 'prevGifsPage':
module.exports.gifsPage(interaction);
break;
case 'nextGifsPage':
module.exports.gifsPage(interaction);
break;
case 'prevPastasPage':
module.exports.pastasPage(interaction);
break;
case 'nextPastasPage':
module.exports.pastasPage(interaction);
break;
case 'prevRequestsPage':
module.exports.requestsPage(interaction);
break;
case 'nextRequestsPage':
module.exports.requestsPage(interaction);
break;
case 'prevJointsPage':
module.exports.jointsPage(interaction);
break;
case 'nextJointsPage':
module.exports.jointsPage(interaction);
break;
default:
return;
}
},
gifsPage(interaction) {
const iStorage = interaction.client.iStorage.get(interaction.message.interaction.id);
switch (interaction.component.customId) {
case 'prevGifsPage':
if (iStorage.page > 0) {
iStorage.page = iStorage.page - 1;
}
break;
case 'nextGifsPage':
if (iStorage.page < interaction.client.gifs.size / 10) {
iStorage.page = iStorage.page + 1;
}
break;
default:
break;
}
const indexedGifs = indexer(interaction.client.gifs, iStorage.page);
indexedGifs.gifsString = new String();
for (const gif of indexedGifs.thisPage) {
indexedGifs.gifsString += `[${gif.name}.gif](${gif.url})\n`;
}
interaction.update(fn.embeds.gifs({command: "/gifs", author: interaction.member.displayName}, indexedGifs));
},
pastasPage(interaction) {
const iStorage = interaction.client.iStorage.get(interaction.message.interaction.id);
switch (interaction.component.customId) {
case 'prevPastasPage':
if (iStorage.page > 0) {
iStorage.page = iStorage.page - 1;
}
break;
case 'nextPastasPage':
if (iStorage.page < interaction.client.pastas.size / 10) {
iStorage.page = iStorage.page + 1;
}
break;
default:
break;
}
const indexedPastas = indexer(interaction.client.pastas, iStorage.page);
indexedPastas.pastasString = new String();
for (const pasta of indexedPastas.thisPage) {
indexedPastas.pastasString += `${pasta.name}.pasta\n`;
}
interaction.update(fn.embeds.pastas({command: "/pastas", author: interaction.member.displayName}, indexedPastas));
},
requestsPage(interaction) {
const iStorage = interaction.client.iStorage.get(interaction.message.interaction.id);
switch (interaction.component.customId) {
case 'prevRequestsPage':
if (iStorage.page > 0) {
iStorage.page = iStorage.page - 1;
}
break;
case 'nextRequestsPage':
if (iStorage.page < interaction.client.requests.size / 10) {
iStorage.page = iStorage.page + 1;
}
break;
default:
break;
}
const indexedRequests = indexer(interaction.client.requests, iStorage.page);
indexedRequests.requestsString = new String();
for (const request of indexedRequests.thisPage) {
indexedRequests.requestsString += `[${request.id}]: ${request.request} (submitted by ${request.author})\n`;
}
interaction.update(fn.embeds.requests({command: "/requests", author: interaction.member.displayName}, indexedRequests));
},
jointsPage(interaction) {
const iStorage = interaction.client.iStorage.get(interaction.message.interaction.id);
switch (interaction.component.customId) {
case 'prevJointsPage':
if (iStorage.page > 0) {
iStorage.page = iStorage.page - 1;
}
break;
case 'nextJointsPage':
if (iStorage.page < interaction.client.joints.size / 10) {
iStorage.page = iStorage.page + 1;
}
break;
default:
break;
}
const indexedJoints = indexer(interaction.client.joints, iStorage.page);
indexedJoints.jointsString = new String();
for (const joint of indexedJoints.thisPage) {
indexedJoints.jointsString += `${joint.content}\n`;
}
interaction.update(fn.embeds.joints({command: "/joints", author: interaction.member.displayName}, indexedJoints));
}
}

139
CustomModules/Embeds.js Normal file
View File

@ -0,0 +1,139 @@
const { MessageActionRow, MessageButton } = require('discord.js');
module.exports = {
gifSearchAR(state) {
// Setup the buttons
const previousButton = new MessageButton()
.setCustomId('prevGif')
.setLabel('⬅️')
.setStyle('SECONDARY');
const confirmButton = new MessageButton()
.setCustomId('confirmGif')
.setLabel('✅')
.setStyle('PRIMARY');
const nextButton = new MessageButton()
.setCustomId('nextGif')
.setLabel('➡️')
.setStyle('SECONDARY');
const cancelButton = new MessageButton()
.setCustomId('cancelGif')
.setLabel('❌')
.setStyle('DANGER');
switch (state) {
case 'first':
previousButton.setDisabled(true);
break;
case 'last':
nextButton.setDisabled(true);
break;
}
// Put the buttons into an ActionRow
return new MessageActionRow()
.addComponents(previousButton, confirmButton, nextButton, cancelButton);
},
gifsPageAR(state) {
// Setup the buttons
const previousButton = new MessageButton()
.setCustomId('prevGifsPage')
.setLabel('⬅️')
.setStyle('SECONDARY');
const nextButton = new MessageButton()
.setCustomId('nextGifsPage')
.setLabel('➡️')
.setStyle('SECONDARY');
switch (state) {
case 'first':
previousButton.setDisabled(true);
break;
case 'last':
nextButton.setDisabled(true);
break;
}
// Put the buttons into an ActionRow
return new MessageActionRow()
.addComponents(previousButton, nextButton);
},
requestsPageAR(state) {
// Setup the buttons
const previousButton = new MessageButton()
.setCustomId('prevRequestsPage')
.setLabel('⬅️')
.setStyle('SECONDARY');
const nextButton = new MessageButton()
.setCustomId('nextRequestsPage')
.setLabel('➡️')
.setStyle('SECONDARY');
switch (state) {
case 'first':
previousButton.setDisabled(true);
break;
case 'last':
nextButton.setDisabled(true);
break;
}
// Put the buttons into an ActionRow
return new MessageActionRow()
.addComponents(previousButton, nextButton);
},
pastasPageAR(state) {
// Setup the buttons
const previousButton = new MessageButton()
.setCustomId('prevPastasPage')
.setLabel('⬅️')
.setStyle('SECONDARY');
const nextButton = new MessageButton()
.setCustomId('nextPastasPage')
.setLabel('➡️')
.setStyle('SECONDARY');
switch (state) {
case 'first':
previousButton.setDisabled(true);
break;
case 'last':
nextButton.setDisabled(true);
break;
}
// Put the buttons into an ActionRow
return new MessageActionRow()
.addComponents(previousButton, nextButton);
},
jointsPageAR(state) {
// Setup the buttons
const previousButton = new MessageButton()
.setCustomId('prevJointsPage')
.setLabel('⬅️')
.setStyle('SECONDARY');
const nextButton = new MessageButton()
.setCustomId('nextJointsPage')
.setLabel('➡️')
.setStyle('SECONDARY');
switch (state) {
case 'first':
previousButton.setDisabled(true);
break;
case 'last':
nextButton.setDisabled(true);
break;
}
// Put the buttons into an ActionRow
return new MessageActionRow()
.addComponents(previousButton, nextButton);
}
}

32
CustomModules/Indexer.js Normal file
View File

@ -0,0 +1,32 @@
module.exports = (collection, page) => {
const itemsPerPage = 10;
const index = page * itemsPerPage;
const totalPages = Math.ceil(collection.size / itemsPerPage);
let state = page === 0 ? 'first' : 'middle';
const thisPage = new Array();
// Map the Djs Collection to an Array
const collectionArray = collection.map((command) => command);
for (let i = index; i < index + itemsPerPage; i++) {
if (collectionArray[i]) {
thisPage.push(collectionArray[i]);
} else {
state = 'last';
break;
}
if (i === collectionArray.size - 1) {
state = 'last';
break;
}
}
return {
state: state,
thisPage: thisPage,
totalPages: totalPages,
pagesString: `${page + 1}/${totalPages}`
};
}

View File

@ -0,0 +1,17 @@
module.exports = class InteractionStorage {
constructor(idString, interaction) {
this.idString = idString;
this.userId = interaction.user.id;
// Store in the client
interaction.client.iStorage.set(idString, this);
// Delete this from the interactionStorage after 5 minutes
setTimeout(() => {
console.log(`Deleting interactionStorage with id: ${idString}`);
interaction.client.iStorage.delete(idString);
}, 300000);
return this;
}
}

View File

@ -1,4 +1,34 @@
module.exports = { module.exports = {
CommandData: class {
constructor(message) {
// Get the location of the final period in the message
this.finalPeriod = message.content.lastIndexOf('.');
this.isCommand = this.finalPeriod >= 0 ? true : false; // Check if there is a period somewhere in the message to flag as a possible command
this.isValid = false;
this.args = message.content.slice(0,this.finalPeriod).toLowerCase(); // Grab everything leading up to the final period
this.command = message.content.slice(this.finalPeriod + 1).toLowerCase(); // Grab everything after the final period
this.author = message.author.username;
return this;
}
validate(dotCommands) {
if (this.args.startsWith('http')) return false;
if (this.args.startsWith('www')) return false;
for (const [key, value] of dotCommands) {
if (key === this.command) {
this.isValid = true;
return this;
} else if (value.alias && value.alias.includes(this.command)) {
this.command = key;
this.isValid = true;
return this;
}
}
return this;
}
},
GifData: class { GifData: class {
constructor() { constructor() {
this.id = 0; this.id = 0;

View File

@ -1,9 +1,6 @@
# About Nodbot # 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 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.
# Status
This should be ready to merge into `main`, let it run a couple days with testing before creating a PR. METAR and D-ATIS are implemented. TAFs will come later as they're more complicated.
# Nodbot Help # Nodbot Help
Use the `/help` command to see the bot's help message. Use the `/help` command to see the bot's help message.
@ -14,16 +11,6 @@ Use the `/help` command to see the bot's help message.
## Push Docker Image ## Push Docker Image
`docker push name/nodbot` `docker push name/nodbot`
# Immediate To-Do
1. ~~Sanitize inputs for SQL queries.~~
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.
7. Readjust keyword autoresponses to be more generic instead of hard coded
# Deploy NodBot Yourself # Deploy NodBot Yourself
1. Create an application at the [Discord Developer Portal](https://discord.com/developers/applications) 1. Create an application at the [Discord Developer Portal](https://discord.com/developers/applications)
@ -35,13 +22,6 @@ Use the `/help` command to see the bot's help message.
6. Configure your environment variables as outlined below. 6. Configure your environment variables as outlined below.
7. Fire it up with `node main.js` 7. Fire it up with `node main.js`
# Recent Changes
* Added METAR via AviationWeather.gov API
* Added D-ATIS via datis.clowd.io API
* Updated how keyword autoresponses are handled
* Changed `.joint` to reduce duplication and repetition by implementing an Ashtray and Roaches
## Table Structure ## Table Structure
``` ```
@ -109,14 +89,3 @@ ownerId=<your Discord user ID>
statusChannelId=<Discord channel ID of channel used for status messages> statusChannelId=<Discord channel ID of channel used for status messages>
clientId=<Discord user ID of your bot> clientId=<Discord user ID of your bot>
``` ```
## 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

View File

@ -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

View File

@ -9,6 +9,7 @@ const dotenv = require('dotenv').config();
module.exports = { module.exports = {
name: 'gif', name: 'gif',
description: 'Send a GIF', description: 'Send a GIF',
alias: ['jpg', 'png', 'gifv', 'webm', 'mp4', 'wav', 'wmv', 'webp', 'mp3', 'flac', 'ogg', 'avi', 'mov', 'mpg', 'mpeg', 'mkv', 'flv', 'bmp', 'tiff', 'tif', 'svg', 'ico'],
usage: '<GIF name or Search Query>.gif', usage: '<GIF name or Search Query>.gif',
async execute(message, commandData) { async execute(message, commandData) {
// if (message.deletable) message.delete(); // if (message.deletable) message.delete();

View File

@ -1,7 +1,6 @@
/* eslint-disable comma-dangle */ /* eslint-disable comma-dangle */
// dotenv for handling environment variables // dotenv for handling environment variables
const dotenv = require('dotenv'); const dotenv = require('dotenv').config();
dotenv.config();
// Assignment of environment variables for database access // Assignment of environment variables for database access
const dbHost = process.env.dbHost; const dbHost = process.env.dbHost;
const dbUser = process.env.dbUser; const dbUser = process.env.dbUser;
@ -9,7 +8,6 @@ const dbName = process.env.dbName;
const dbPass = process.env.dbPass; const dbPass = process.env.dbPass;
const dbPort = process.env.dbPort; const dbPort = process.env.dbPort;
const isDev = process.env.isDev; const isDev = process.env.isDev;
const ownerId = process.env.ownerId; const ownerId = process.env.ownerId;
// filesystem // filesystem
@ -22,10 +20,6 @@ const Discord = require('discord.js');
// Fuzzy text matching for db lookups // Fuzzy text matching for db lookups
const FuzzySearch = require('fuzzy-search'); const FuzzySearch = require('fuzzy-search');
// OpenAI
// const OpenAI = require("openai");
// const openai = new OpenAI();
// Axios for APIs // Axios for APIs
const axios = require('axios'); const axios = require('axios');
@ -34,6 +28,7 @@ const config = require('./config.json');
const strings = require('./strings.json'); const strings = require('./strings.json');
const slashCommandFiles = fs.readdirSync('./slash-commands/').filter(file => file.endsWith('.js')); const slashCommandFiles = fs.readdirSync('./slash-commands/').filter(file => file.endsWith('.js'));
const dotCommandFiles = fs.readdirSync('./dot-commands/').filter(file => file.endsWith('.js')); const dotCommandFiles = fs.readdirSync('./dot-commands/').filter(file => file.endsWith('.js'));
const customEmbeds = require('./CustomModules/Embeds.js');
// MySQL database connection // MySQL database connection
const mysql = require('mysql'); const mysql = require('mysql');
@ -50,6 +45,11 @@ const db = new mysql.createPool({
const functions = { const functions = {
// Functions for managing and creating Collections // Functions for managing and creating Collections
collections: { collections: {
interactionStorage(client) {
if (!client.iStorage) client.iStorage = new Discord.Collection();
client.iStorage.clear();
if (isDev) console.log('Interaction Storage Collection Built');
},
// Create the collection of slash commands // Create the collection of slash commands
slashCommands(client) { slashCommands(client) {
if (!client.slashCommands) client.slashCommands = new Discord.Collection(); if (!client.slashCommands) client.slashCommands = new Discord.Collection();
@ -81,13 +81,13 @@ const functions = {
for (const file of dotCommandFiles) { for (const file of dotCommandFiles) {
const dotCommand = require(`./dot-commands/${file}`); const dotCommand = require(`./dot-commands/${file}`);
client.dotCommands.set(dotCommand.name, dotCommand); client.dotCommands.set(dotCommand.name, dotCommand);
if (Array.isArray(dotCommand.alias)) { // if (Array.isArray(dotCommand.alias)) {
dotCommand.alias.forEach(element => { // dotCommand.alias.forEach(element => {
client.dotCommands.set(element, dotCommand); // client.dotCommands.set(element, dotCommand);
}); // });
} else if (dotCommand.alias != undefined) { // } else if (dotCommand.alias != undefined) {
client.dotCommands.set(dotCommand.alias, dotCommand); // client.dotCommands.set(dotCommand.alias, dotCommand);
} // }
} }
if (isDev) console.log('Dot Commands Collection Built'); if (isDev) console.log('Dot Commands Collection Built');
}, },
@ -170,91 +170,79 @@ const functions = {
if (isDev) console.log('Medical Advice Collection Built'); 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 + 1).toLowerCase();
commandData.author = `${message.author.username}`;
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: { embeds: {
help(interaction) { help(interaction) {
// Construct the Help Embed // Construct the Help Embed
const helpEmbed = new Discord.MessageEmbed() const helpEmbed = new Discord.MessageEmbed()
.setColor('BLUE') .setColor('BLUE')
.setAuthor({name: 'Help Page'}) .setAuthor({name: 'Help Page'})
.setDescription(strings.help.description)
.setThumbnail(strings.urls.avatar); .setThumbnail(strings.urls.avatar);
// Construct the Slash Commands help // Construct the Slash Commands help
let slashCommandsFields = []; let slashCommandsFields = [];
let slashSeenNames = new Array();
const slashCommandsMap = interaction.client.slashCommands.map(e => { const slashCommandsMap = interaction.client.slashCommands.map(e => {
return { if (!slashSeenNames.includes(e.data.name)) {
slashSeenNames.push(e.data.name);
const command = {
name: e.data.name, name: e.data.name,
description: e.data.description description: e.data.description
}; };
}) return command;
} else {
for (const e of slashCommandsMap) { return null;
slashCommandsFields.push({
name: `- /${e.name}`,
value: e.description,
inline: false,
});
} }
});
for (const e of slashCommandsMap) {
slashCommandsFields.push(`- \`/${e.name}\` - ${e.description}`);
}
console.log(slashCommandsFields);
// Construct the Dot Commands Help // Construct the Dot Commands Help
let dotCommandsFields = []; let dotCommandsFields = new Array();
let dotSeenNames = new Array();
const dotCommandsMap = interaction.client.dotCommands.map(e => { const dotCommandsMap = interaction.client.dotCommands.map(e => {
return { if (!dotSeenNames.includes(e.name)) {
dotSeenNames.push(e.name);
let command = {
name: e.name, name: e.name,
description: e.description, description: e.description,
usage: e.usage usage: e.usage
}; };
}); command.aliasString = new String();
if (e.alias != undefined && typeof e.alias === 'object') {
for (const e of dotCommandsMap) { for (const a of e.alias) {
dotCommandsFields.push({ command.aliasString += `\`.${a}\`, `;
name: `- .${e.name}`,
value: `${e.description}\nUsage: ${e.usage}`,
inline: false,
});
} }
} else if (e.alias != undefined && typeof e.alias === 'string') {
command.aliasString += `\`.${e.alias}\``;
} else {
command.aliasString = 'None';
}
return command;
} else {
return null;
}
});
for (const e of dotCommandsMap) {
if (e != null) {
dotCommandsFields.push(`- \`.${e.name}\` - ${e.description}\n\tUsage: ${e.usage}\n\tAliases: ${e.aliasString}`);
}
}
console.log(dotCommandsFields);
helpEmbed.addField('Slash Commands', strings.help.slash); // Construct the Description Fields
helpEmbed.addFields(slashCommandsFields); const descriptionFields = [
helpEmbed.addField('Dot Commands', strings.help.dot); `${strings.help.description}\n`,
helpEmbed.addFields(dotCommandsFields); `**Slash Commands**\n${strings.help.slash}\n`,
`${slashCommandsFields.join('\n')}\n`,
`**Dot Commands**\n${strings.help.dot}\n`,
`${dotCommandsFields.join('\n')}`
];
// Set the description
helpEmbed.setDescription(descriptionFields.join('\n'));
return { embeds: [ return { embeds: [
helpEmbed helpEmbed
@ -275,30 +263,35 @@ const functions = {
.setTimestamp() .setTimestamp()
.setFooter({text: commandData.author})]}; .setFooter({text: commandData.author})]};
}, },
pastas(commandData) { pastas(commandData, indexedPastas) {
const pastasArray = [];
const pastasEmbed = new Discord.MessageEmbed() const pastasEmbed = new Discord.MessageEmbed()
.setAuthor({name: commandData.command}) .setAuthor({name: commandData.command})
.setTimestamp() .setTimestamp()
.setFooter({text: commandData.author}); .setFooter({text: `Page: ${indexedPastas.pagesString}`})
.setDescription(indexedPastas.pastasString);
for (const row of commandData.pastas) { const pastasPageAR = customEmbeds.pastasPageAR(indexedPastas.state);
pastasArray.push(`#${row.id} - ${row.name}.pasta`); return { embeds: [pastasEmbed], components: [pastasPageAR], ephemeral: true };
}
const pastasString = pastasArray.join('\n');
pastasEmbed.setDescription(pastasString);
return { embeds: [pastasEmbed], ephemeral: true };
}, },
gifs(commandData, gifList) { gifs(commandData, indexedGifs) {
const gifsEmbed = new Discord.MessageEmbed() const gifsEmbed = new Discord.MessageEmbed()
.setAuthor({name: commandData.command}) .setAuthor({name: commandData.command})
.setTimestamp() .setTimestamp()
.setFooter({text: commandData.author}) .setFooter({text: `Page: ${indexedGifs.pagesString}`})
.setDescription(gifList.join('\n')); .setDescription(indexedGifs.gifsString);
return { embeds: [gifsEmbed] }; const gifsPageAR = customEmbeds.gifsPageAR(indexedGifs.state);
return { embeds: [gifsEmbed], components: [gifsPageAR], ephemeral: true };
},
joints(commandData, indexedJoints) {
const jointsEmbed = new Discord.MessageEmbed()
.setAuthor({name: commandData.command})
.setTimestamp()
.setFooter({text: `Page: ${indexedJoints.pagesString}`})
.setDescription(indexedJoints.jointsString);
const jointsPageAR = customEmbeds.jointsPageAR(indexedJoints.state);
return { embeds: [jointsEmbed], components: [jointsPageAR], ephemeral: true };
}, },
text(commandData) { text(commandData) {
return { embeds: [new Discord.MessageEmbed() return { embeds: [new Discord.MessageEmbed()
@ -307,24 +300,15 @@ const functions = {
.setTimestamp() .setTimestamp()
.setFooter({text: commandData.author})]}; .setFooter({text: commandData.author})]};
}, },
requests(commandData) { requests(commandData, indexedRequests) {
const requestsEmbed = new Discord.MessageEmbed() const requestsEmbed = new Discord.MessageEmbed()
.setAuthor({name: commandData.command}) .setAuthor({name: commandData.command})
.setTimestamp() .setTimestamp()
.setFooter({text: commandData.author}); .setFooter({text: `Page: ${indexedRequests.pagesString}`})
.setDescription(indexedRequests.requestsString);
const requestsArray = []; const requestsPageAR = customEmbeds.requestsPageAR(indexedRequests.state);
return { embeds: [requestsEmbed], components: [requestsPageAR], ephemeral: true };
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) { strain(strainInfo, interaction) {
const strainEmbed = new Discord.MessageEmbed() const strainEmbed = new Discord.MessageEmbed()
@ -622,38 +606,6 @@ const functions = {
} }
} }
}, },
openAI: {
chatPrompt(userPrompt) {
return new Promise(async (resolve, reject) => {
const response = await openai.chat.completions.create({
messages: [{
role: 'user',
content: userPrompt
}],
model: strings.ai.chatModel
}).catch(e => {
reject(e);
return null;
});
resolve(response);
});
},
imagePrompt(userPrompt, size, userId) {
return new Promise(async (resolve, reject) => {
try {
const response = await openai.createImage({
prompt: userPrompt,
size: size,
user: userId
});
resolve(response.data.data[0].url);
} catch (e) {
reject(e);
return;
}
});
}
},
search: { search: {
gifs(query, client) { gifs(query, client) {
const gifSearcher = new FuzzySearch(client.gifs.map(element => element.name)); const gifSearcher = new FuzzySearch(client.gifs.map(element => element.name));

17
main.js
View File

@ -27,10 +27,13 @@ const { MessageActionRow, MessageButton } = require('discord.js');
const fn = require('./functions.js'); const fn = require('./functions.js');
const config = require('./config.json'); const config = require('./config.json');
const strings = require('./strings.json'); const strings = require('./strings.json');
const { GifData } = require('./CustomModules/NodBot.js'); const { GifData, CommandData } = require('./CustomModules/NodBot.js');
const ButtonHandlers = require('./CustomModules/ButtonHandlers.js');
const InteractionStorage = require('./CustomModules/InteractionStorage.js');
const isDev = process.env.isDev; const isDev = process.env.isDev;
client.once('ready', async () => { client.once('ready', async () => {
fn.collections.interactionStorage(client);
fn.collections.slashCommands(client); fn.collections.slashCommands(client);
fn.collections.dotCommands(client); fn.collections.dotCommands(client);
fn.collections.setvalidCommands(client); fn.collections.setvalidCommands(client);
@ -54,10 +57,14 @@ client.once('ready', async () => {
client.on('interactionCreate', async interaction => { client.on('interactionCreate', async interaction => {
if (interaction.isCommand()) { if (interaction.isCommand()) {
if (isDev) { if (isDev) {
console.log(interaction); console.log('Interaction ID: ' + interaction.id);
} }
const { commandName } = interaction; const { commandName } = interaction;
if (!client.iStorage.has(interaction.id)) {
new InteractionStorage(interaction.id, interaction);
}
if (client.slashCommands.has(commandName)) { if (client.slashCommands.has(commandName)) {
client.slashCommands.get(commandName).execute(interaction); client.slashCommands.get(commandName).execute(interaction);
} else { } else {
@ -67,7 +74,8 @@ client.on('interactionCreate', async interaction => {
} }
if (interaction.isButton()) { if (interaction.isButton()) {
if (interaction.user.id != strings.temp.gifUserId) return; if (isDev) console.log('Origin Interaction ID: ' + interaction.message.interaction.id);
if (isDev) console.log('Button ID: ' + interaction.component.customId);
// Get some meta info from strings // Get some meta info from strings
const index = strings.temp.gifIndex; const index = strings.temp.gifIndex;
const limit = strings.temp.gifLimit; const limit = strings.temp.gifLimit;
@ -176,6 +184,7 @@ client.on('interactionCreate', async interaction => {
interaction.update({ content: 'Canceled.', components: [row] }); interaction.update({ content: 'Canceled.', components: [row] });
break; break;
default: default:
ButtonHandlers.baseEvent(interaction);
break; break;
} }
} }
@ -232,7 +241,7 @@ client.on('messageCreate', message => {
}); });
// Break the message down into its components and analyze it // Break the message down into its components and analyze it
const commandData = fn.dot.getCommandData(message); const commandData = new CommandData(message).validate(message.client.dotCommands);
console.log(commandData); console.log(commandData);
if (commandData.isValid && commandData.isCommand) { if (commandData.isValid && commandData.isCommand) {

View File

@ -1,6 +1,6 @@
{ {
"name": "nodbot", "name": "nodbot",
"version": "3.3.1", "version": "3.3.3",
"description": "Nods and Nod Accessories, now with ChatGPT!", "description": "Nods and Nod Accessories, now with ChatGPT!",
"main": "main.js", "main": "main.js",
"dependencies": { "dependencies": {

View File

@ -1,36 +1,30 @@
const { SlashCommandBuilder } = require('@discordjs/builders'); const { SlashCommandBuilder } = require('@discordjs/builders');
const { config } = require('dotenv'); const { config } = require('dotenv');
const fn = require('../functions.js'); const fn = require('../functions.js');
const indexer = require('../CustomModules/Indexer.js');
module.exports = { module.exports = {
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
.setName('gifs') .setName('gifs')
.setDescription('Get a list of currently saved GIFs.'), .setDescription('Get a list of currently saved GIFs.'),
async execute(interaction) { execute(interaction) {
if (!interaction.client.gifs) { if (!interaction.client.gifs) {
interaction.reply('For some reason I don\'t have access to the collection of gifs. Sorry about that!'); interaction.reply('For some reason I don\'t have access to the collection of gifs. Sorry about that!');
return; return;
} }
// const gifsMap = interaction.client.gifs.map(e => {e.name, e.url}); let iStorage = interaction.client.iStorage.get(interaction.id);
// const commandData = { let indexedGifs = indexer(interaction.client.gifs, 0);
// gifs: [], indexedGifs.gifsString = new String();
// command: 'gifs',
// author: interaction.user.tag, iStorage.page = 0;
// };
// for (const row of gifsMap) { for (const gif of indexedGifs.thisPage) {
// commandData.gifs.push({ indexedGifs.gifsString += `[${gif.name}.gif](${gif.url})\n`;
// id: row.id, }
// name: row.name,
// });
// }
let gifList = [];
interaction.client.gifs.forEach(element => {
gifList.push(`[${element.name}](${element.url})`);
});
const commandData = { const commandData = {
command: "/gifs", command: "/gifs",
author: interaction.member.displayName author: interaction.member.displayName
}; };
interaction.reply(fn.embeds.gifs(commandData, gifList)); interaction.reply(fn.embeds.gifs(commandData, indexedGifs));
}, }
}; };

View File

@ -4,29 +4,8 @@ const fn = require('../functions.js');
module.exports = { module.exports = {
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
.setName('help') .setName('help')
.setDescription('Send the help page.') .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'))
,
async execute(interaction) { 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)); await interaction.reply(fn.embeds.help(interaction));
}, }
}; };

View File

@ -1,15 +1,29 @@
const { SlashCommandBuilder } = require('@discordjs/builders'); const { SlashCommandBuilder } = require('@discordjs/builders');
const fn = require('../functions.js'); const fn = require('../functions.js');
const indexer = require('../CustomModules/Indexer.js');
module.exports = { module.exports = {
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
.setName('joints') .setName('joints')
.setDescription('Send a list of all the /joint phrases.'), .setDescription('Send a list of all the /joint phrases.'),
async execute(interaction) { async execute(interaction) {
let joints = []; if (!interaction.client.joints) {
interaction.client.joints.map(e => { interaction.reply('For some reason I don\'t have access to the collection of joints. Sorry about that!');
joints.push(e.content); return;
}); }
interaction.reply({ content: 'Here are all the `.joint` phrases I have saved:\n\n' + joints.join('\n'), ephemeral: true }); let iStorage = interaction.client.iStorage.get(interaction.id);
let indexedJoints = indexer(interaction.client.joints, 0);
indexedJoints.jointsString = new String();
iStorage.page = 0;
for (const joint of indexedJoints.thisPage) {
indexedJoints.jointsString += `${joint.content}\n`;
}
const commandData = {
command: "/joints",
author: interaction.member.displayName
};
interaction.reply(fn.embeds.joints(commandData, indexedJoints));
}, },
}; };

View File

@ -1,6 +1,7 @@
const { SlashCommandBuilder } = require('@discordjs/builders'); const { SlashCommandBuilder } = require('@discordjs/builders');
const { config } = require('dotenv'); const { config } = require('dotenv');
const fn = require('../functions.js'); const fn = require('../functions.js');
const indexer = require('../CustomModules/Indexer.js');
module.exports = { module.exports = {
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
@ -11,23 +12,19 @@ module.exports = {
interaction.reply({ content: 'For some reason I don\'t have access to the collection of copypastas. Sorry about that!', ephemeral: true }); interaction.reply({ content: 'For some reason I don\'t have access to the collection of copypastas. Sorry about that!', ephemeral: true });
return; return;
} }
const commandData = { let iStorage = interaction.client.iStorage.get(interaction.id);
author: interaction.user.tag, let indexedPastas = indexer(interaction.client.pastas, 0);
command: interaction.commandName, indexedPastas.pastasString = new String();
pastas: [],
}; iStorage.page = 0;
const pastasMap = interaction.client.pastas.map(e => {
return { for (const pasta of indexedPastas.thisPage) {
id: e.id, indexedPastas.pastasString += `${pasta.name}.pasta\n`;
name: e.name,
};
});
for (const row of pastasMap) {
commandData.pastas.push({
id: row.id,
name: row.name,
});
} }
interaction.reply(fn.embeds.pastas(commandData)); const commandData = {
command: "/pastas",
author: interaction.member.displayName
};
interaction.reply(fn.embeds.pastas(commandData, indexedPastas));
}, },
}; };

View File

@ -1,39 +1,31 @@
const { SlashCommandBuilder } = require('@discordjs/builders'); const { SlashCommandBuilder } = require('@discordjs/builders');
const { config } = require('dotenv'); const { config } = require('dotenv');
const fn = require('../functions.js'); const fn = require('../functions.js');
const indexer = require('../CustomModules/Indexer.js');
module.exports = { module.exports = {
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
.setName('requests') .setName('requests')
.setDescription('Get a list of Active requests from the database') .setDescription('Get a list of Active requests from the database'),
.addStringOption(option =>
option
.setName('page')
.setDescription('Page Number')
.setRequired(true)),
async execute(interaction) { async execute(interaction) {
const pageNum = interaction.options.getString('page'); if (!interaction.client.requests) {
interaction.reply('For some reason I don\'t have access to the collection of requests. Sorry about that!');
return;
}
let iStorage = interaction.client.iStorage.get(interaction.id);
let indexedRequests = indexer(interaction.client.requests, 0);
indexedRequests.requestsString = new String();
iStorage.page = 0;
for (const request of indexedRequests.thisPage) {
indexedRequests.requestsString += `[${request.id}]: ${request.request} (submitted by ${request.author})\n`;
}
const commandData = { const commandData = {
author: interaction.user.tag, command: "/requests",
command: interaction.commandName, author: interaction.member.displayName
requests: [],
}; };
const requestsMap = interaction.client.requests.map(e => { interaction.reply(fn.embeds.requests(commandData, indexedRequests));
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));
}, },
}; };

View File

@ -11,6 +11,7 @@ const { MessageActionRow, MessageButton } = require('discord.js');
const fn = require('../functions.js'); const fn = require('../functions.js');
const strings = require('../strings.json'); const strings = require('../strings.json');
const { GifData } = require('../CustomModules/NodBot.js'); const { GifData } = require('../CustomModules/NodBot.js');
const customEmbeds = require('../CustomModules/Embeds.js');
const { emoji } = strings; const { emoji } = strings;
module.exports = { module.exports = {
@ -142,16 +143,7 @@ module.exports = {
// GIF Search // GIF Search
case "gifsearch": case "gifsearch":
// TODO Check on option names // TODO Check on option names
// Previous GIF button const actionRow = customEmbeds.gifSearchAR();
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 // Get the query
const query = interaction.options.getString('query'); const query = interaction.options.getString('query');

View File

@ -1,8 +1,8 @@
{ {
"help": { "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.", "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.", "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'" "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": { "emoji": {
"joint": "<:joint:862082955902976000>", "joint": "<:joint:862082955902976000>",