Compare commits

..

94 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
9d9a1447f3 Fixed Prod Deployment
All checks were successful
NodBot Production Dockerization / build (pull_request) Successful in 1m5s
2024-09-23 18:51:43 -04:00
70fc12d458 Fix branch references 2024-09-23 18:51:34 -04:00
65470f2c57 Merge branch 'main' into v3.3.0-metars 2024-09-23 18:43:59 -04:00
38d90d7f6b Update CI/CD 2024-09-22 18:49:27 -04:00
8f7ed605a4 Update .github/workflows/pe-docker.yml
Some checks failed
NodBot PE Dockerization / build (push) Failing after 1m56s
Match from pe branch
2024-09-22 22:26:09 +00:00
2f9d29d891 Updating docs 2024-06-22 10:42:26 -04:00
ecb50148b9 Fix body/content 2024-06-22 10:26:01 -04:00
806d70b292 Move METAR raw to message body 2024-06-22 10:19:29 -04:00
798aaef4ea Adjust embed appearance 2024-06-22 10:16:21 -04:00
4c20d08471 Adjust embed appearance 2024-06-22 10:10:05 -04:00
550eb79374 Fix split ATISes 2024-06-22 10:08:28 -04:00
b11d14b72d Add and tweak D-ATIS 2024-06-22 09:42:02 -04:00
444bad7935 Add D-ATIS from clowd.io 2024-06-22 09:17:24 -04:00
6700e36907 Make update script 2024-06-21 20:20:07 -04:00
66dbdceb2a Minor tweaks 2024-06-21 20:19:05 -04:00
5a9b91ade1 Fix cloud layer CLR 2024-06-21 19:19:37 -04:00
13553d7e5a Add KCQX 2024-06-21 19:15:22 -04:00
388bc4d021 Add Metars with Decoding TODO: add TAFs 2024-06-21 18:35:12 -04:00
2a3b53ea08 Fix kms emoji is 2024-06-21 15:14:38 -04:00
99af5ca8b4 Add f u nodbot response 2024-06-21 15:11:34 -04:00
7003351ecc Updating how autoresponses are handled to be cleaner 2024-06-21 15:02:22 -04:00
bfa6e10011 Versioning v3.2.3 2024-06-21 15:02:08 -04:00
22f2ac58df I hate you 2024-06-21 15:01:56 -04:00
3dcc4e021f Merge pull request 'v3.2.2-dev Merged new fixed sbs and OpenAI deintegration' (#12) from v3.2.2-dev into main
Reviewed-on: #12
2024-06-15 02:24:16 +00:00
d1e2152de9 Disabled OpenAI init
Some checks failed
NodBot Production Dockerization / build (pull_request) Has been cancelled
2024-06-14 22:06:54 -04:00
1a024b216a Merge branch 'dev' into v3.2.2-dev 2024-06-14 22:00:33 -04:00
0106983a5f Fixed how replies work with .sb
Some checks failed
NodBot Production Dockerization / build (pull_request) Has been cancelled
2024-06-14 21:38:36 -04:00
caefe0e228 Fix silly replace method for getting command 2024-06-14 20:34:51 -04:00
928f12fe01 Versioning 2024-06-14 20:30:34 -04:00
ae9ce308b2 Ignore Mac BS
Some checks failed
NodBot Production Dockerization / build (pull_request) Has been cancelled
2024-06-14 14:43:23 -04:00
2ea94092db Ignore Mac BS 2024-06-14 14:43:06 -04:00
3f5cc0a9ff Fix exec dir
Some checks failed
NodBot Production Dockerization / build (pull_request) Has been cancelled
2024-01-01 15:47:14 -05:00
545756b762 Fix log overwriting error
Some checks failed
NodBot Production Dockerization / build (pull_request) Has been cancelled
2024-01-01 15:44:55 -05:00
d61ffbaaf7 Remove chat and dalle commands
Some checks failed
NodBot Production Dockerization / build (pull_request) Has been cancelled
2024-01-01 15:42:17 -05:00
15d39e3381 commmenting
Some checks reported warnings
NodBot Production Dockerization / build (pull_request) Has been cancelled
2023-12-14 10:54:05 -05:00
97b4136b64 Fix joint randomization and add more aliases
Some checks reported warnings
NodBot Production Dockerization / build (pull_request) Has been cancelled
2023-12-14 10:43:40 -05:00
82f65a800c bugfixes
Some checks reported warnings
NodBot Production Dockerization / build (pull_request) Has been cancelled
2023-12-13 16:49:48 -05:00
f995d9a643 Upgrade to gpt-3.5-turbo
Some checks failed
NodBot Production Dockerization / build (pull_request) Failing after 10m32s
2023-10-18 17:33:03 -04:00
ff3a423a30 Merge pull request 'Adding and Restructuring Commands and Classes' (#8) from dev into main
Some checks failed
NodBot Production Dockerization / build (pull_request) Failing after 2s
Reviewed-on: #8
2023-08-08 13:38:29 -07:00
7321f4535b Merge branch 'main' into dev
Some checks reported warnings
NodBot Production Dockerization / build (pull_request) Has been cancelled
2023-08-08 13:36:54 -07:00
c562c06925 Fix incorrect insert statement
All checks were successful
NodBot Production Dockerization / build (pull_request) Successful in 13s
2023-08-08 15:06:29 -04:00
b28e193b55 Update to use Node 18 LTS
All checks were successful
NodBot Production Dockerization / build (pull_request) Successful in 1m0s
2023-08-08 14:44:59 -04:00
6e062ffef2 Use Head Ref for main PRs
All checks were successful
NodBot Production Dockerization / build (pull_request) Successful in 1m5s
2023-08-08 14:39:32 -04:00
f3051ec944 /edit pasta, setFooter, PastaData Classification 2023-08-08 14:34:46 -04:00
cb834cb7ee Classes, Embed.SetAuthor, /gifs, /edit, PARTIAL 2023-08-08 13:25:07 -04:00
0cfa91eb10 Merge pull request 'Restructure /save* commands into subcommands' (#7) from dev into main
Reviewed-on: #7
2023-08-06 14:37:17 -07:00
17a9bd4ea2 Restructure /save commands
All checks were successful
NodBot Production Dockerization / build (pull_request) Successful in 14s
2023-08-06 17:29:17 -04:00
18ef6e56c2 Shouldn't be pushing to main 2023-08-06 09:14:44 -04:00
1bff94e71b Forgot to toggle
All checks were successful
NodBot Production Dockerization / build (push) Successful in 55s
2023-08-06 09:04:24 -04:00
10021d6926 changed HED_REF to main hardcoded
All checks were successful
NodBot Production Dockerization / build (push) Successful in 2m1s
2023-08-06 08:58:35 -04:00
38fdc07e4e Revert .sb functionality
All checks were successful
NodBot Production Dockerization / build (push) Successful in 19s
2023-08-06 08:51:28 -04:00
e99e6b2e1f Run CI on push to main 2023-08-06 08:51:12 -04:00
98acb652af Merge branch 'nodGPT' 2023-08-06 08:47:35 -04:00
c8d059e36d Ignore all .env 2023-08-06 08:47:15 -04:00
1f85b97467 CI/CD Update for Gitea Actions 2023-08-04 20:06:09 -04:00
Skylar Grant
7c09aaffaa Minor updates 2023-06-15 19:16:50 -04:00
Skylar Grant
08618e2be2 Experimental generating content embed 2023-06-15 19:07:16 -04:00
Skylar Grant
200468ac10 Implement logging to db 2023-05-31 08:33:03 -04:00
Skylar Grant
efdc605dc0 Adding costs to embeds 2023-05-31 08:07:12 -04:00
Skylar Grant
2c75ff09d9 Improved embed style. 2023-05-29 09:22:11 -04:00
Skylar Grant
4e2e8bc702 Add size option and response embed for dall-e 2023-05-29 08:47:07 -04:00
Skylar Grant
53625be91f Basic Dall-e implementation 2023-05-28 10:08:51 -04:00
Skylar Grant
755c8510d7 First basic implementation of ChatGPT 2023-05-27 20:41:08 -04:00
Skylar Grant
57238ce57a Add variety to ong 2023-04-13 20:16:33 -04:00
Skylar Grant
36e25f5e34 Rollback to D.js v13 2023-04-09 12:41:47 -04:00
Skylar Grant
e1474b935f Update CI/CD to manual only 2023-04-09 12:31:58 -04:00
Skylar Grant
2cf8132a65 Versioning 2023-04-09 12:28:53 -04:00
64 changed files with 2847 additions and 866 deletions

0
.dockerignore Executable file → Normal file
View File

0
.eslintrc.json Executable file → Normal file
View File

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

@ -0,0 +1,45 @@
name: NodBot PE Dockerization
on:
push:
branches:
- pe
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: ${{ gitea.head_ref }}"
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
cd nodbot
else
cd nodbot
git pull
fi
git checkout ${{ gitea.head_ref }}
- name: Build the Docker image
run: |
cd /var/lib/act_runner/nodbot
docker build . --file Dockerfile --tag v0idf1sh/nodbot-pe
- 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
docker push v0idf1sh/nodbot-pe
- name: Restart the container
run: |
cd /srv/docker/nodbot-pe
docker-compose down
docker-compose up -d

View File

@ -0,0 +1,45 @@
name: NodBot Production Dockerization
on:
push:
tags:
- 'v*-prod*'
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
cd nodbot
else
cd nodbot
git pull
fi
git checkout tags/${{ gitea.ref_name }}
- name: Build the Docker image
run: |
cd /var/lib/act_runner/nodbot
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: |
cd /var/lib/act_runner/nodbot
docker push v0idf1sh/nodbot
- name: Restart the container
run: |
cd /srv/docker/nodbot
docker-compose down
docker-compose up -d

View File

@ -1,29 +0,0 @@
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

6
.gitignore vendored Executable file → Normal file
View File

@ -2,9 +2,8 @@
.vscode
package-lock.json
.VSCodeCounter/
env.dev
env.prod
.DS_Store
.env*
*.env
# Custom folders
# gifs/*
@ -116,3 +115,4 @@ dist
# TernJS port file
.tern-port
.DS_Store

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;
}
}

82
CustomModules/NodBot.js Normal file
View File

@ -0,0 +1,82 @@
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 {
constructor() {
this.id = 0;
this.name = "";
this.url = "";
}
// Initial GifData configuration
// Can also be used to update the data piecemeal
setInfo(name, url, id) {
// Check for existing or incoming name
if ((this.name === "") && (typeof name !== 'string')) throw `Error: This Gif doesn't have existing name, and no name is going to be set.`;
// Check for existing content or incoming content
if ((this.url === "") && (typeof url !== 'string')) throw `Error: This Gif doesn't have existing url, and no url is going to be set.`;
// Property is set if the variable is the right type,
// otherwise it keeps the existing property
this.id = typeof id === 'number' ? id : this.id;
this.name = typeof name === 'string' ? name : this.name;
this.url = typeof url === 'string' ? url : this.url;
return this; // For chaining
}
},
PastaData: class {
constructor() {
this.id = 0;
this.name = "";
this.content = "";
this.iconUrl = "";
}
// Initial PastaData configuration
// Can also be used to update the data piecemeal
setInfo(name, content, iconUrl, id) {
// Check for existing or incoming name
if ((this.name === "") && (typeof name !== 'string')) throw `Error: This Pasta doesn't have existing name, and no name is going to be set.`;
// Check for existing content or incoming content
if ((this.content === "") && (typeof content !== 'string')) throw `Error: This Pasta doesn't have existing content, and no content is going to be set.`;
// Property is set if the variable is the right type,
// otherwise it keeps the existing property
this.id = typeof id === 'number' ? id : this.id;
this.name = typeof name === 'string' ? name : this.name;
this.content = typeof content === 'string' ? content : this.content;
this.iconUrl = typeof iconUrl === 'string' ? iconUrl : this.iconUrl;
return this; // For chaining
}
}
}

7
Dockerfile Executable file → Normal file
View File

@ -2,7 +2,8 @@ FROM node:18
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
COPY src/package.json ./
COPY package.json ./
RUN npm install
COPY ./src .
CMD [ "node", "main.js" ]
COPY . .
# CMD ["/bin/sh", "-c", "node main.js 2> /logs/nodbot.error 1> /logs/nodbot.log"]
CMD ["/bin/sh", "-c", "node main.js 2> /logs/$(date +%Y-%m-%d_%H-%M-%S)-error.txt 1> /logs/$(date +%Y-%m-%d_%H-%M-%S)-status.txt"]

96
README.md Executable file → Normal file
View File

@ -1,7 +1,91 @@
# NodBot v4
Under (very early) construction! Come back later
# 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.
# Dependencies
* `dotenv`
* `discord.js`
* `fs` (built in)
# 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`
# 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=<your bot's token from step 3>
isDev=<true/false>
dbHost=<mySQL host>
dbPort=<mySQL port (3306)>
dbUser=<mySQL username>
dbPass=<mySQL user password>
dbName=<mySQL table name>
tenorAPIKey=<Tenor API Key>
ownerId=<your Discord user ID>
statusChannelId=<Discord channel ID of channel used for status messages>
clientId=<Discord user ID of your bot>
```

27
_clear-commands.js Normal file
View File

@ -0,0 +1,27 @@
// dotenv for handling environment variables
const dotenv = require('dotenv');
dotenv.config();
const { REST } = require('@discordjs/rest');
const { Routes } = require('discord-api-types/v9');
const clientId = process.env.clientId;
const { guildId } = require('../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);
}
})();

40
_deploy-commands.js Normal file
View File

@ -0,0 +1,40 @@
// 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);
}
})();

17
src/modules/_deployGlobal.js → _deploy-global.js Executable file → Normal file
View File

@ -2,8 +2,9 @@
const dotenv = require('dotenv');
dotenv.config();
const { REST, Routes } = require('discord.js');
const botId = process.env.BOTID;
const { REST } = require('@discordjs/rest');
const { Routes } = require('discord-api-types/v9');
const clientId = process.env.clientId;
const token = process.env.TOKEN;
const fs = require('fs');
@ -11,26 +12,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: ${botId}`);
console.log(commands);
const rest = new REST({ version: '10' }).setToken(token);
const rest = new REST({ version: '9' }).setToken(token);
(async () => {
try {
console.log('Started refreshing global application (/) commands.');
console.log('Started refreshing application (/) commands.');
await rest.put(
Routes.applicationCommands(botId),
Routes.applicationCommands(clientId),
{ body: commands },
);
console.log('Successfully reloaded global application (/) commands.');
console.log('Successfully reloaded application (/) commands.');
process.exit();
} catch (error) {
console.error(error);

7
config.json Normal file
View File

@ -0,0 +1,7 @@
{
"guildId": "868542949737246730",
"validCommands": [],
"roaches": [],
"icaoIds": [],
"datisICAOs": []
}

28
dot-commands/datis.js Normal file
View File

@ -0,0 +1,28 @@
const fn = require('../functions');
module.exports = {
name: 'datis',
description: 'Lookup dATIS for an airport',
usage: 'ICAO.datis',
alias: [ 'atis' ],
async execute(message, commandData) {
try {
const icaoId = commandData.args.toUpperCase();
if (icaoId.length !== 4) throw new Error('Invalid ICAO ID. Provide only one ICAO code at a time like KBOS');
if (fn.avWx.datis.validate(icaoId)) {
const datisData = await fn.avWx.datis.getData(icaoId);
const messagePayload = fn.avWx.datis.parseData(datisData);
message.reply(messagePayload);
} else {
message.reply("No D-ATIS available for the specified ICAO ID.");
}
} catch (e) {
try {
message.reply(`D-ATIS Error: ${e.message}`);
console.error(e);
} catch (e) {
console.error(e);
}
}
}
}

54
dot-commands/gif.js Normal file
View File

@ -0,0 +1,54 @@
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',
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',
async execute(message, commandData) {
// if (message.deletable) message.delete();
const client = message.client;
if (!client.gifs.has(commandData.args.toLowerCase())) {
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);
const gifData = client.gifs.get(commandData.args.toLowerCase());
// message.reply(fn.embeds.gif(commandData));
// message.channel.send(`> ${commandData.author} - ${commandData.args}.gif`);
// message.channel.send(commandData.embed_url);
message.reply(gifData.url);
}
}
}

37
dot-commands/joint.js Normal file
View File

@ -0,0 +1,37 @@
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', 'dab', 'vape', 'dabs', 'shatter', 'edible', 'edibles', 'doobie', 'spliff', 'gummy', 'gummies', 'hash', 'toke', 'big doinks'],
execute(message, commandData) {
let joints = [];
// Create a simple array of the joint texts
for (const entry of message.client.joints.map(joint => joint.content)) {
joints.push(entry);
}
// Generate a random number between 0 and the length of the joints array
let randIndex = Math.floor(Math.random() * joints.length);
// Grab the joint text from the array
let joint = joints[randIndex];
// Check if the joint has already been smoked
while (message.client.roaches.has(joint)) {
// Regenerate a random number and recheck
randIndex = Math.floor(Math.random() * joints.length);
joint = joints[randIndex];
}
// Send the joint
message.reply(`${joints[randIndex]} ${emoji.joint}`);
// Check how full the roach collection is
if (message.client.roaches.size / joints.length >= 0.85) {
// If the roach collection is 85% of the joints collection
// Empty it out
message.client.roaches.clear();
}
// Add the joint to the roach collection
message.client.roaches.set(joint, "baked");
}
}

16
dot-commands/md.js Normal file
View File

@ -0,0 +1,16 @@
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]}`);
}
}

26
dot-commands/metar.js Normal file
View File

@ -0,0 +1,26 @@
const fn = require('../functions');
module.exports = {
name: 'metar',
description: 'Lookup METAR for an airport',
usage: 'ICAO.metar',
async execute(message, commandData) {
try {
// Parse the ICAOs into a CSV list by trimming whitespace and converting delimiters
// Also checks for validity of ICAOs
const icaoList = fn.avWx.parseICAOs(commandData);
const metarData = await fn.avWx.metar.getData(icaoList);
const messages = fn.avWx.metar.parseData(metarData);
messages.forEach(messagePayload => {
message.reply(messagePayload);
});
} catch (e) {
try {
message.reply(`METAR Error: ${e.message}`);
console.error(e);
} catch (e) {
console.error(e);
}
}
}
}

19
dot-commands/pasta.js Normal file
View File

@ -0,0 +1,19 @@
const fn = require('../functions.js');
module.exports = {
name: 'pasta',
description: 'Send a copypasta.',
usage: '<Copypasta Name>.pasta',
execute(message, commandData) {
const client = message.client;
let pastaData;
if (!client.pastas.has(commandData.args)) {
pastaData = {
content: "Sorry, I couldn't find that pasta."
};
} else {
pastaData = client.pastas.get(commandData.args);
}
message.reply(fn.embeds.pasta(commandData, pastaData));
}
}

17
dot-commands/request.js Normal file
View File

@ -0,0 +1,17 @@
const fn = require('../functions.js');
module.exports = {
name: 'request',
description: 'Submit a request to the bot developer.',
usage: '<request or feedback>.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);
},
};

43
dot-commands/spongebob.js Normal file
View File

@ -0,0 +1,43 @@
const fn = require('../functions.js');
const config = require('../config.json');
module.exports = {
name: 'spongebob',
alias: 'sb',
description: 'SpOnGeBoB-iFy AnYtHiNg AuToMaTiCaLly',
usage: '<text to convert>.spongebob',
execute(message, commandData) {
// message.reply(fn.spongebob(commandData)).then(() => {
// message.delete();
// });
if (message.reference != undefined) { // message.reference is undefined if the message isn't a reply to another message
if (commandData.args !== "") { // If the replying message isn't just .sb
const repliedMessageId = message.reference.messageId; // grab the message Id of the replied-to msg
message.channel.messages.fetch(repliedMessageId) // Fetch the message because with our luck it isn't in the cache
.then(repliedMessage => {
repliedMessage.reply(fn.spongebob({ args: commandData.args })).then(() => { // Use the pre-command text of the replying message to sb-ify
if (message.deletable) message.delete(); // If the initiating message is deletable, delete it.
});
})
.catch(err => {
console.error(err);
});
} else { // We're working with a basic ".sb" and can proceed as we did before...
const repliedMessageId = message.reference.messageId; // grab the message Id of the replied-to msg
message.channel.messages.fetch(repliedMessageId) // Fetch the message because with our luck it isn't in the cache
.then(repliedMessage => {
repliedMessage.reply(fn.spongebob({ args: repliedMessage.content })).then(() => {
if (message.deletable) message.delete();
});
})
.catch(err => {
console.error(err);
});
}
} else { // The message isn't a reply, so just sb it like we did from the very beginning
message.channel.send(fn.spongebob(commandData)).then(() => {
if (message.deletable) message.delete(); // If the initiating message is deletable, delete it.
});
}
}
}

View File

@ -0,0 +1,17 @@
const fn = require('../functions');
module.exports = {
name: 'strain',
description: 'Search for information about a cannabis strain.',
usage: '<strain name>.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));
}
}
}

832
functions.js Normal file
View File

@ -0,0 +1,832 @@
/* eslint-disable comma-dangle */
// dotenv for handling environment variables
const dotenv = require('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');
const zlib = require('zlib');
// Discord.js
const Discord = require('discord.js');
// Fuzzy text matching for db lookups
const FuzzySearch = require('fuzzy-search');
// Axios for APIs
const axios = require('axios');
// 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'));
const customEmbeds = require('./CustomModules/Embeds.js');
// MySQL database connection
const mysql = require('mysql');
const { GifData, PastaData } = require('./CustomModules/NodBot');
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: {
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
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
// };
const gifData = new GifData().setInfo(row.name, row.embed_url);
client.gifs.set(gifData.name, gifData);
}
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 pastaData = new PastaData().setInfo(row.name, row.content, row.iconurl, row.id);
client.pastas.set(pastaData.name, pastaData);
}
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.medicalAdviceColl) 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');
},
roaches(client) {
if (!client.roaches) client.roaches = new Discord.Collection();
client.roaches.clear();
if (isDev) console.log('Medical Advice Collection Built');
}
},
embeds: {
help(interaction) {
// Construct the Help Embed
const helpEmbed = new Discord.MessageEmbed()
.setColor('BLUE')
.setAuthor({name: 'Help Page'})
.setThumbnail(strings.urls.avatar);
// Construct the Slash Commands help
let slashCommandsFields = [];
let slashSeenNames = new Array();
const slashCommandsMap = interaction.client.slashCommands.map(e => {
if (!slashSeenNames.includes(e.data.name)) {
slashSeenNames.push(e.data.name);
const command = {
name: e.data.name,
description: e.data.description
};
return command;
} else {
return null;
}
});
for (const e of slashCommandsMap) {
slashCommandsFields.push(`- \`/${e.name}\` - ${e.description}`);
}
console.log(slashCommandsFields);
// Construct the Dot Commands Help
let dotCommandsFields = new Array();
let dotSeenNames = new Array();
const dotCommandsMap = interaction.client.dotCommands.map(e => {
if (!dotSeenNames.includes(e.name)) {
dotSeenNames.push(e.name);
let command = {
name: e.name,
description: e.description,
usage: e.usage
};
command.aliasString = new String();
if (e.alias != undefined && typeof e.alias === 'object') {
for (const a of e.alias) {
command.aliasString += `\`.${a}\`, `;
}
} 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);
// Construct the Description Fields
const descriptionFields = [
`${strings.help.description}\n`,
`**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: [
helpEmbed
], ephemeral: true };
},
gif(commandData) {
return { embeds: [new Discord.MessageEmbed()
.setAuthor({name: `${commandData.args}.${commandData.command}`})
.setImage(commandData.embed_url)
.setTimestamp()
.setFooter({text: commandData.author})]};
},
pasta(commandData, pastaData) {
return { embeds: [ new Discord.MessageEmbed()
.setAuthor({name: `${commandData.args}.${commandData.command}`})
.setDescription(pastaData.content)
.setThumbnail("https://assets.vfsh.dev/shednod.png")
.setTimestamp()
.setFooter({text: commandData.author})]};
},
pastas(commandData, indexedPastas) {
const pastasEmbed = new Discord.MessageEmbed()
.setAuthor({name: commandData.command})
.setTimestamp()
.setFooter({text: `Page: ${indexedPastas.pagesString}`})
.setDescription(indexedPastas.pastasString);
const pastasPageAR = customEmbeds.pastasPageAR(indexedPastas.state);
return { embeds: [pastasEmbed], components: [pastasPageAR], ephemeral: true };
},
gifs(commandData, indexedGifs) {
const gifsEmbed = new Discord.MessageEmbed()
.setAuthor({name: commandData.command})
.setTimestamp()
.setFooter({text: `Page: ${indexedGifs.pagesString}`})
.setDescription(indexedGifs.gifsString);
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) {
return { embeds: [new Discord.MessageEmbed()
.setAuthor({name: commandData.command})
.setDescription(commandData.content)
.setTimestamp()
.setFooter({text: commandData.author})]};
},
requests(commandData, indexedRequests) {
const requestsEmbed = new Discord.MessageEmbed()
.setAuthor({name: commandData.command})
.setTimestamp()
.setFooter({text: `Page: ${indexedRequests.pagesString}`})
.setDescription(indexedRequests.requestsString);
const requestsPageAR = customEmbeds.requestsPageAR(indexedRequests.state);
return { embeds: [requestsEmbed], components: [requestsPageAR], 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 ]});
},
dalle(prompt, imageUrl, size) {
const dalleEmbed = new Discord.MessageEmbed()
.setAuthor({ name: "NodBot powered by DALL-E", iconURL: "https://assets.vfsh.dev/openai-logos/PNGs/openai-logomark.png" })
.addFields(
{ name: "Prompt", value: prompt }
)
.setImage(imageUrl)
.setFooter({ text: `This ${size} image cost ${strings.costs.dalle[size]}¢ to generate.` })
return { embeds: [dalleEmbed] };
},
gpt(prompt, response, usage) {
const gptEmbed = new Discord.MessageEmbed()
.setAuthor({ name: "NodBot powered by GPT-3.5", iconURL: "https://assets.vfsh.dev/openai-logos/PNGs/openai-logomark.png" })
.setDescription(`**Prompt**\n${prompt}\n\n**Response**\n${response}`)
.setFooter({ text: `This prompt used ${usage.tokens} tokens for a cost of ${usage.usdc}¢. Generated using ${strings.ai.chatModel}` })
return { embeds: [gptEmbed] };
},
generatingResponse() {
const embed = new Discord.MessageEmbed()
.setAuthor({ name: "NodBot powered by OpenAI", iconURL: "https://assets.vfsh.dev/openai-logos/PNGs/openai-logomark.png" })
.setImage("https://media.tenor.com/aHMHzNGCb4kAAAAC/sucks.gif")
.setDescription("Generating a response, please stand by.")
.setFooter({ text: "Ligma balls" });
return { embeds: [embed] };
},
avWx: {
metar(metarData) {
const wgst = metarData.wgst ? `G${metarData.wgst}` : '';
const clouds = [];
const interAltim = Math.round((metarData.altim * 0.2952998057228486) * 10)
const altim = interAltim / 100;
metarData.clouds.forEach(cloudLayer => {
if (cloudLayer.base !== null) {
clouds.push(`${cloudLayer.cover} @ ${cloudLayer.base}'`);
} else {
clouds.push(`${cloudLayer.cover}`);
}
});
const embed = new Discord.MessageEmbed()
.setAuthor({ name: `${metarData.name} [${metarData.icaoId}] METAR`, iconURL: "https://aviationweather.gov/img/icons/awc-logo-180.png"})
// .setImage("https://media.discordapp.net/stickers/1175134632845516821.webp")
.setDescription(`**Do not use for real world flight planning or navigation.**`)
.setFooter({ text: "METAR by AviationWeather.gov for CumbHub LLC" })
.addFields(
{ name: 'Observation Time', value: `${metarData.reportTime}Z`, inline: true },
{ name: 'Temperature', value: `${metarData.temp}ºC/${metarData.dewp}ºC`, inline: true },
{ name: 'Winds', value: `${metarData.wdir.toString().padStart(3, '0')}º@${metarData.wspd}${wgst} kts`, inline: true },
{ name: 'Visibility', value: `${metarData.visib} SM`, inline: true },
{ name: 'Clouds', value: clouds.join('\n'), inline: true },
{ name: 'Altimeter', value: `${altim} inHg`, inline: true }
)
return { content: metarData.rawOb, embeds: [embed] };
},
datis(datisData) {
const messageEmbed = new Discord.MessageEmbed()
.setAuthor({ name: `${datisData[0].airport} Digital ATIS` })
// .setImage('https://media.discordapp.net/stickers/1175134632845516821.webp')
.setDescription(`**Do not use for real world flight planning or navigation.**`)
.setFooter({ text: 'D-ATIS by Clowd.io for CumbHub LLC' })
if (datisData.length > 1) {
datisData.forEach(data => {
if (data.type === 'dep') messageEmbed.addFields({ name: 'Departure Digital ATIS', value: data.datis, inline: false });
if (data.type === 'arr') messageEmbed.addFields({ name: 'Arrival Digital ATIS', value: data.datis, inline: false });
messageEmbed.addFields({ name: 'Information', value: data.code, inline: true });
})
messageEmbed.addFields(
{ name: 'Retreival Time', value: `${new Date().toISOString()}`, inline: true }
);
} else {
messageEmbed.addFields(
{ name: 'Digital ATIS', value: datisData[0].datis, inline: false },
{ name: 'Information', value: `${datisData[0].code}`, inline: true },
{ name: 'Retreival Time', value: `${new Date().toISOString()}`, inline: true }
)
}
const messagePayload = { embeds: [ messageEmbed ] };
return messagePayload;
}
}
},
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);
});
},
async pasta(pastaData, client) {
const query = `INSERT INTO pastas (name, content) VALUES (${db.escape(pastaData.name)},${db.escape(pastaData.content)}) ON DUPLICATE KEY UPDATE content=${db.escape(pastaData.content)}`;
await db.query(query, (err, rows, fields) => {
if (err) throw err;
functions.download.pastas(client);
});
return;
},
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);
});
},
async gif(gifData, client) {
const query = `INSERT INTO gifs (name, embed_url) VALUES (${db.escape(gifData.name)}, ${db.escape(gifData.url)}) ON DUPLICATE KEY UPDATE embed_url=${db.escape(gifData.url)}`;
await 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}) ON DUPLICATE KEY UPDATE strain=${db.escape(strain)}, type=${db.escape(type)}, effects=${db.escape(effects)}, description=${db.escape(description)}, flavor=${db.escape(flavor)}, rating=${db.escape(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();
});
})
},
openai(user, prompt, engine, tokens, usdc) {
const query = `INSERT INTO openai (user, prompt, engine, tokens, usdc) VALUES (${db.escape(user)}, ${db.escape(prompt)}, ${db.escape(engine)}, ${db.escape(tokens)}, ${db.escape(usdc)})`;
db.query(query, (err) => {
if (err) throw err;
});
}
},
download: {
async requests(client) {
const query = 'SELECT * FROM requests WHERE status = \'Active\' ORDER BY id DESC';
await db.query(query, (err, rows, fields) => {
if (err) throw err;
functions.collections.requests(rows, client);
});
},
async pastas(client) {
const query = 'SELECT * FROM pastas ORDER BY id ASC';
await db.query(query, (err, rows, fields) => {
if (err) throw err;
functions.collections.pastas(rows, client);
});
},
async gifs(client) {
const query = 'SELECT * FROM gifs ORDER BY id ASC';
await db.query(query, (err, rows, fields) => {
if (err) throw err;
functions.collections.gifs(rows, client);
});
},
async joints(client) {
const query = 'SELECT * FROM joints ORDER BY id ASC';
await db.query(query, (err, rows, fields) => {
if (err) throw err;
functions.collections.joints(rows, client);
});
},
async strain(strainName, interaction) {
const query = `SELECT id, strain, type, effects, description, flavor, rating FROM strains WHERE strain = ${db.escape(strainName)}`;
await 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);
}
});
},
async strains(client) {
const query = 'SELECT id, strain FROM strains';
await db.query(query, (err, rows, fields) => {
if (err) throw err;
functions.collections.strains(rows, client);
});
},
async medicalAdvice(client) {
const query = 'SELECT * FROM medical_advice ORDER BY id ASC';
await 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;
}
}
},
search: {
gifs(query, client) {
const gifSearcher = new FuzzySearch(client.gifs.map(element => element.name));
return gifSearcher.search(query).slice(0,25);
},
pastas(query, client) {
const pastaSearcher = new FuzzySearch(client.pastas.map(element => element.name));
return pastaSearcher.search(query).slice(0,25);
}
},
// 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 = '';
let lastIsUpper = 0;
for (const letter of commandData.args) {
if (letter == ' ') {
newText += letter;
continue;
}
if (letter == 'i' || letter == 'I') {
newText += 'i';
lastIsUpper = 0;
continue;
}
if (letter == 'l' || letter == 'L') {
newText += 'L';
lastIsUpper = 1;
continue;
}
if (lastIsUpper === 0) {
newText += letter.toUpperCase();
lastIsUpper = 1;
} else {
newText += letter.toLowerCase();
lastIsUpper = 0;
}
}
return newText + ' <:spongebob:1053398825965985822>';
},
autoresponses: { // Specific responses for certain keywords in sent messages
checkForAll(messageContent) {
let responses = [];
if (this.bigDoinks(messageContent)) responses.push("bigDoinks");
if (this.ligma(messageContent)) responses.push("ligma");
if (this.ong(messageContent)) responses.push("ong");
if (this.fuckYou(messageContent)) responses.push("fuckYou");
return responses;
},
bigDoinks(messageContent) {
let count = 0;
const { keywords } = strings.autoresponses.bigDoinks;
keywords.forEach(e => {
if (messageContent.includes(e)) count++;
});
if (count === keywords.length) {
return true;
}
},
ligma(messageContent) {
let count = 0;
const { keywords } = strings.autoresponses.ligma;
keywords.forEach(e => {
if (messageContent.includes(e)) count++;
});
if (count > 0) {
return true;
}
},
ong(messageContent) {
let count = 0;
const { keywords } = strings.autoresponses.ong;
keywords.forEach(e => {
if (messageContent.includes(e)) count++;
});
if (count > 0) {
return true;
}
},
fuckYou(messageContent) {
let count = 0;
const { keywords } = strings.autoresponses.fuckYou;
keywords.forEach(e => {
if (messageContent.includes(e)) count++;
});
if (count === keywords.length) {
return true;
}
},
send(message, responseType) {
const { responses } = strings.autoresponses[responseType];
const randomIndex = Math.floor(Math.random() * responses.length);
const response = responses[randomIndex];
try {
message.reply(response);
} catch(e) {
console.log(new Error(e));
}
}
},
avWx: {
parseICAOs(commandData) {
let input = commandData.args.toUpperCase();
// Replace newlines and different delimiters with a comma
let standardizedInput = input.replace(/[\s,;]+/g, ',');
// Split the string by commas
let icaoArray = standardizedInput.split(',');
// Trim each element to remove extra whitespace
icaoArray = icaoArray.map(icao => icao.trim()).filter(icao => icao.length > 0);
icaoArray.forEach(icao => {
if (!(config.icaoIds.includes(icao))) throw new Error(`Invalid ICAO ID Detected: ${icao}`);
});
// Join the array into a comma-separated string
return icaoArray.join(',');
},
metar: {
async getAllICAOs() {
const reqUrl = `https://aviationweather.gov/data/cache/stations.cache.json.gz`
try {
// Step 1: Download the GZipped file
const response = await axios({
url: reqUrl,
method: 'GET',
responseType: 'arraybuffer', // Ensure we get the raw binary data
headers: {
'Accept-Encoding': 'gzip' // Ensure the server sends gzipped content
}
});
// Step 2: Decompress the GZipped content
const buffer = Buffer.from(response.data);
zlib.gunzip(buffer, (err, decompressedBuffer) => {
if (err) {
console.error('An error occurred during decompression:', err);
return;
}
// Step 3: Parse the decompressed JSON
const jsonString = decompressedBuffer.toString('utf-8');
try {
const jsonData = JSON.parse(jsonString);
// console.log('Parsed JSON data:', jsonData);
jsonData.forEach(airport => {
config.icaoIds.push(airport.icaoId);
});
// console.log(`ICAO IDs: ${config.icaoIds.length}\n\n${config.icaoIds}`)
} catch (jsonError) {
console.error('An error occurred while parsing JSON:', jsonError);
}
});
} catch (error) {
console.error('An error occurred during the HTTP request:', error);
}
},
async getData(icaoList) {
const reqUrl = `https://aviationweather.gov/api/data/metar?ids=${icaoList}&format=json`;
const response = await axios.get(reqUrl);
return response.data;
},
parseData(metarData) {
let messages = [];
metarData.forEach(metar => {
messages.push(functions.embeds.avWx.metar(metar));
})
return messages;
}
},
datis: {
async getAllICAOs() {
const reqUrl = 'https://datis.clowd.io/api/stations';
const response = await axios.get(reqUrl);
response.data.forEach(icaoId => {
config.datisICAOs.push(icaoId);
});
},
validate(icaoId) {
return config.datisICAOs.includes(icaoId);
},
async getData(icaoId) {
const reqUrl = `https://datis.clowd.io/api/${icaoId}`;
const response = await axios.get(reqUrl);
if (response.error !== undefined) throw new Error('The D-ATIS API returned an error:\n' + response.error);
return response.data;
},
parseData(datisData) {
return functions.embeds.avWx.datis(datisData);
}
}
},
generateErrorId() {
const digitCount = 10;
const digits = [];
for (let i = 0; i < digitCount; i++) {
const randBase = Math.random();
const randNumRaw = randBase * 10;
const randNumRound = Math.floor(randNumRaw);
digits.push(randNumRound);
}
const errorId = digits.join("");
return errorId;
}
};
module.exports = functions;

259
main.js Normal file
View File

@ -0,0 +1,259 @@
/* eslint-disable no-case-declarations */
/* eslint-disable indent */
// dotenv for handling environment variables
const dotenv = require('dotenv');
dotenv.config();
const token = process.env.TOKEN;
const statusChannelId = process.env.statusChannelId;
// Discord.JS
const { Client, Intents } = require('discord.js');
const client = new Client({
intents: [
'GUILDS',
'GUILD_MESSAGES',
'GUILD_MESSAGE_REACTIONS',
'DIRECT_MESSAGES',
'DIRECT_MESSAGE_REACTIONS',
],
partials: [
'CHANNEL',
'MESSAGE',
],
});
const { MessageActionRow, MessageButton } = require('discord.js');
// Various imports
const fn = require('./functions.js');
const config = require('./config.json');
const strings = require('./strings.json');
const { GifData, CommandData } = require('./CustomModules/NodBot.js');
const ButtonHandlers = require('./CustomModules/ButtonHandlers.js');
const InteractionStorage = require('./CustomModules/InteractionStorage.js');
const isDev = process.env.isDev;
client.once('ready', async () => {
fn.collections.interactionStorage(client);
fn.collections.slashCommands(client);
fn.collections.dotCommands(client);
fn.collections.setvalidCommands(client);
fn.collections.roaches(client);
await fn.download.gifs(client);
await fn.download.pastas(client);
await fn.download.joints(client);
await fn.download.requests(client);
await fn.download.strains(client);
await fn.download.medicalAdvice(client);
console.log('Ready!');
await fn.avWx.metar.getAllICAOs();
await fn.avWx.datis.getAllICAOs();
// console.log(JSON.stringify(icaoArray));
client.channels.fetch(statusChannelId).then(channel => {
channel.send(`${new Date().toISOString()} -- <@${process.env.ownerId}>\nStartup Sequence Complete`);
});
});
// slash-commands
client.on('interactionCreate', async interaction => {
if (interaction.isCommand()) {
if (isDev) {
console.log('Interaction ID: ' + interaction.id);
}
const { commandName } = interaction;
if (!client.iStorage.has(interaction.id)) {
new InteractionStorage(interaction.id, interaction);
}
if (client.slashCommands.has(commandName)) {
client.slashCommands.get(commandName).execute(interaction);
} else {
interaction.reply('Sorry, I don\'t have access to that command.');
console.error('Slash command attempted to run but not found: ' + commandName);
}
}
if (interaction.isButton()) {
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
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,
// url: strings.temp.gifs[strings.temp.gifIndex].embed_url,
// };
const gifData = new GifData().setInfo(strings.temp.gifName, 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:
ButtonHandlers.baseEvent(interaction);
break;
}
}
// Handle autocomplete requests
if (interaction.isAutocomplete()) {
switch (interaction.commandName) {
case '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 }))
);
break;
case "edit":
//TODO
switch (interaction.options.getSubcommand()) {
case 'gif':
const gifQuery = interaction.options.getFocused();
const gifChoices = fn.search.gifs(gifQuery, interaction.client);
await interaction.respond(
gifChoices.map(choice => ({ name: choice, value: choice }))
);
break;
case 'pasta':
const pastaQuery = interaction.options.getFocused();
const pastaChoices = fn.search.pastas(pastaQuery, interaction.client);
await interaction.respond(
pastaChoices.map(choice => ({ name: choice, value: choice }))
);
break;
default:
break;
}
break;
default:
break;
}
}
});
// dot-commands
client.on('messageCreate', message => {
// Some basic checking to prevent running unnecessary code
if (message.author.bot) return;
// Automatic Responses, will respond if any message contains the keyword(s), excluding self-messages
const lowerContent = message.content.toLowerCase();
const autoresponses = fn.autoresponses.checkForAll(lowerContent);
autoresponses.forEach(e => {
fn.autoresponses.send(message, e);
});
// Break the message down into its components and analyze it
const commandData = new CommandData(message).validate(message.client.dotCommands);
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;
});
client.login(token);

37
package.json Normal file
View File

@ -0,0 +1,37 @@
{
"name": "nodbot",
"version": "3.3.3",
"description": "Nods and Nod Accessories, now with ChatGPT!",
"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.15.1",
"dotenv": "^10.0.0",
"fuzzy-search": "^3.2.1",
"mysql": "^2.18.1",
"openai": "^4.12.0",
"tenorjs": "^1.0.10"
},
"engines": {
"node": "18.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"
}

View File

@ -0,0 +1,16 @@
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);
},
};

110
slash-commands/edit.js Normal file
View File

@ -0,0 +1,110 @@
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 { GifData, PastaData } = require('../CustomModules/NodBot');
const fn = require('../functions.js');
const strings = require('../strings.json');
const { emoji } = strings;
module.exports = {
data: new SlashCommandBuilder()
.setName('edit')
.setDescription('Edit content in Nodbot\'s database.')
// GIF
.addSubcommand(subcommand =>
subcommand
.setName('gif')
.setDescription('Edit a GIF URL')
.addStringOption(option =>
option
.setName('name')
.setDescription('The name of the GIF to edit')
.setRequired(true)
.setAutocomplete(true)
)
.addStringOption(option =>
option
.setName('url')
.setDescription('The new URL')
.setRequired(true)
)
)
// Pasta
.addSubcommand(subcommand =>
subcommand
.setName('pasta')
.setDescription('Edit a copypasta\'s content')
.addStringOption(option =>
option
.setName('name')
.setDescription('The name of the copypasta')
.setRequired(true)
.setAutocomplete(true)
)
.addStringOption(option =>
option
.setName('content')
.setDescription('The new content of the copypasta')
.setRequired(true)
)
),
async execute(interaction) {
await interaction.deferReply({ ephemeral: true });
try {
// Code Here...
const subcommand = interaction.options.getSubcommand();
switch (subcommand) {
// GIF
case "gif":
//TODO
await this.editGif(interaction, interaction.options.getString('name'), interaction.options.getString('url'));
break;
// Joint
case "joint":
//TODO
break;
// MD
case "md":
//TODO
break;
// Pasta
case "pasta":
//TODO
await this.editPasta(interaction, interaction.options.getString('name'), interaction.options.getString('content'));
break;
// Strain
case "strain":
//TODO
break;
break;
// Default
default:
break;
}
} catch (err) {
const errorId = fn.generateErrorId();
console.error(`${errorId}: err`);
await interaction.editReply(`Sorry, an error has occured. Error ID: ${errorId}`);
}
},
async editGif(interaction, name, url) {
const gifData = new GifData().setInfo(name, url);
await fn.upload.gif(gifData, interaction.client);
await fn.download.gifs(interaction.client);
await interaction.editReply(`I've updated ${gifData.name}.gif`);
},
async editPasta(interaction, name, content) {
const pastaData = new PastaData().setInfo(name, content);
await fn.upload.pasta(pastaData, interaction.client);
await fn.download.gifs(interaction.client);
await interaction.editReply(`I've updated ${name}.pasta`);
}
};

30
slash-commands/gifs.js Normal file
View File

@ -0,0 +1,30 @@
const { SlashCommandBuilder } = require('@discordjs/builders');
const { config } = require('dotenv');
const fn = require('../functions.js');
const indexer = require('../CustomModules/Indexer.js');
module.exports = {
data: new SlashCommandBuilder()
.setName('gifs')
.setDescription('Get a list of currently saved GIFs.'),
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;
}
let iStorage = interaction.client.iStorage.get(interaction.id);
let indexedGifs = indexer(interaction.client.gifs, 0);
indexedGifs.gifsString = new String();
iStorage.page = 0;
for (const gif of indexedGifs.thisPage) {
indexedGifs.gifsString += `[${gif.name}.gif](${gif.url})\n`;
}
const commandData = {
command: "/gifs",
author: interaction.member.displayName
};
interaction.reply(fn.embeds.gifs(commandData, indexedGifs));
}
};

11
slash-commands/help.js Normal file
View File

@ -0,0 +1,11 @@
const { SlashCommandBuilder } = require('@discordjs/builders');
const fn = require('../functions.js');
module.exports = {
data: new SlashCommandBuilder()
.setName('help')
.setDescription('Send the help page.'),
async execute(interaction) {
await interaction.reply(fn.embeds.help(interaction));
}
};

11
slash-commands/jenny.js Normal file
View File

@ -0,0 +1,11 @@
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');
},
};

17
slash-commands/joint.js Normal file
View File

@ -0,0 +1,17 @@
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}`);
},
};

29
slash-commands/joints.js Normal file
View File

@ -0,0 +1,29 @@
const { SlashCommandBuilder } = require('@discordjs/builders');
const fn = require('../functions.js');
const indexer = require('../CustomModules/Indexer.js');
module.exports = {
data: new SlashCommandBuilder()
.setName('joints')
.setDescription('Send a list of all the /joint phrases.'),
async execute(interaction) {
if (!interaction.client.joints) {
interaction.reply('For some reason I don\'t have access to the collection of joints. Sorry about that!');
return;
}
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));
},
};

11
slash-commands/lenny.js Normal file
View File

@ -0,0 +1,11 @@
const { SlashCommandBuilder } = require('@discordjs/builders');
const fn = require('../functions.js');
module.exports = {
data: new SlashCommandBuilder()
.setName('lenny')
.setDescription('( ͡° ͜ʖ ͡°)'),
async execute(interaction) {
interaction.reply('( ͡° ͜ʖ ͡°)');
},
};

30
slash-commands/pastas.js Normal file
View File

@ -0,0 +1,30 @@
const { SlashCommandBuilder } = require('@discordjs/builders');
const { config } = require('dotenv');
const fn = require('../functions.js');
const indexer = require('../CustomModules/Indexer.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;
}
let iStorage = interaction.client.iStorage.get(interaction.id);
let indexedPastas = indexer(interaction.client.pastas, 0);
indexedPastas.pastasString = new String();
iStorage.page = 0;
for (const pasta of indexedPastas.thisPage) {
indexedPastas.pastasString += `${pasta.name}.pasta\n`;
}
const commandData = {
command: "/pastas",
author: interaction.member.displayName
};
interaction.reply(fn.embeds.pastas(commandData, indexedPastas));
},
};

11
slash-commands/ping.js Normal file
View File

@ -0,0 +1,11 @@
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 });
},
};

20
slash-commands/reload.js Normal file
View File

@ -0,0 +1,20 @@
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 });
},
};

View File

@ -0,0 +1,31 @@
const { SlashCommandBuilder } = require('@discordjs/builders');
const { config } = require('dotenv');
const fn = require('../functions.js');
const indexer = require('../CustomModules/Indexer.js');
module.exports = {
data: new SlashCommandBuilder()
.setName('requests')
.setDescription('Get a list of Active requests from the database'),
async execute(interaction) {
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 = {
command: "/requests",
author: interaction.member.displayName
};
interaction.reply(fn.embeds.requests(commandData, indexedRequests));
},
};

232
slash-commands/save.js Normal file
View File

@ -0,0 +1,232 @@
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');
const { GifData } = require('../CustomModules/NodBot.js');
const customEmbeds = require('../CustomModules/Embeds.js');
const { emoji } = strings;
module.exports = {
data: new SlashCommandBuilder()
.setName('save')
.setDescription('Save content to Nodbot\'s database.')
// GIF Search
.addSubcommand(subcommand =>
subcommand
.setName('gifsearch')
.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))
)
// GIF URL
.addSubcommand(subcommand =>
subcommand
.setName('gifurl')
.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))
)
// Joint
.addSubcommand(subcommand =>
subcommand
.setName('joint')
.setDescription('Roll a joint and stash it in the database.')
.addStringOption(option =>
option
.setName('joint-content')
.setDescription('Phrase to save')
.setRequired(true)
)
)
// MD
.addSubcommand(subcommand =>
subcommand
.setName('md')
.setDescription('Add medical advice to the database.')
.addStringOption(option =>
option
.setName('advice-content')
.setDescription('Advice to save')
.setRequired(true)
)
)
// Pasta
.addSubcommand(subcommand =>
subcommand
.setName('pasta')
.setDescription('Save a copypasta to the database.')
.addStringOption(option =>
option
.setName('pasta-name')
.setDescription('Title of the copypasta')
.setRequired(true)
)
.addStringOption(option =>
option
.setName('pasta-content')
.setDescription('Content of the copypasta')
.setRequired(true)
)
)
// Strain
.addSubcommand(subcommand =>
subcommand
.setName('strain')
.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) {
await interaction.deferReply({ ephemeral: true });
try {
// Code Here...
const subcommand = interaction.options.getSubcommand();
switch (subcommand) {
// GIF Search
case "gifsearch":
// TODO Check on option names
const actionRow = customEmbeds.gifSearchAR();
// 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.editReply('Sorry I was unable to find a GIF of ' + query);
return;
}
for (const row of res) {
strings.temp.gifs.push({
embed_url: row.media_formats.gif.url,
});
}
interaction.editReply({ content: strings.temp.gifs[0].embed_url, components: [actionRow], ephemeral: true });
});
break;
// GIF URL
case "gifurl":
//TODO Check on option names
// const gifData = {
// name: interaction.options.getString('name').toLowerCase(),
// url: interaction.options.getString('url'),
// };
const gifData = new GifData().setInfo(interaction.options.getString('name').toLowerCase(), interaction.options.getString('url'));
fn.upload.gif(gifData, interaction.client);
interaction.editReply({ content: `I've saved the GIF as ${gifData.name}.gif`, ephemeral: true });
fn.download.gifs(interaction.client);
break;
// Joint
case "joint":
//TODO
fn.upload.joint(interaction.options.getString('joint-content'), interaction.client);
interaction.editReply({ content: `The joint has been rolled${emoji.joint}`, ephemeral: true });
break;
// MD
case "md":
//TODO
fn.upload.medicalAdvice(interaction.options.getString('advice-content'), interaction.client);
interaction.editReply({ content: `The advice has been saved!`, ephemeral: true });
break;
// Pasta
case "pasta":
//TODO
const pastaData = {
name: interaction.options.getString('pasta-name').toLowerCase(),
content: interaction.options.getString('pasta-content'),
};
await fn.upload.pasta(pastaData, interaction.client);
interaction.editReply({content: `The copypasta has been saved as ${pastaData.name}.pasta`, ephemeral: true });
break;
// Strain
case "strain":
//TODO
fn.upload.strain(interaction).then(res => {
interaction.editReply({
content: `The strain information has been saved. (${interaction.options.getString('name')})`,
ephemeral: true
});
}).catch(err => {
console.log(`E: ${err}`);
interaction.editReply({
content: 'There was a problem saving the strain.',
ephemeral: true
});
});
break;
// Default
default:
break;
}
} catch (err) {
const errorId = fn.generateErrorId();
console.error(`${errorId}: err`);
await interaction.editReply(`Sorry, an error has occured. Error ID: ${errorId}`);
}
}
};

15
slash-commands/setup.js Normal file
View File

@ -0,0 +1,15 @@
// 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 });
},
};

17
slash-commands/strain.js Normal file
View File

@ -0,0 +1,17 @@
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);
},
};

11
slash-commands/template Normal file
View File

@ -0,0 +1,11 @@
const { SlashCommandBuilder } = require('@discordjs/builders');
const fn = require('../functions.js');
module.exports = {
data: new SlashCommandBuilder()
.setName('')
.setDescription(''),
async execute(interaction) {
await
},
};

11
slash-commands/truth.js Normal file
View File

@ -0,0 +1,11 @@
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');
},
};

View File

@ -1,37 +0,0 @@
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.data.name}:${(slashCommand.id == "" ? "0" : slashCommand.id)}> - ${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));
}
},
};

View File

@ -1,30 +0,0 @@
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("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) {
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));
}
},
};

View File

@ -1,67 +0,0 @@
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));
}
},
};

View File

@ -1,31 +0,0 @@
const { SlashCommandBuilder } = require('discord.js');
const fn = require('../modules/functions.js');
const strings = require('../data/strings.json');
module.exports = {
data: new SlashCommandBuilder()
.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) {
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));
}
},
};

View File

@ -1,4 +0,0 @@
{
"guildId": "",
"validCommands": []
}

View File

View File

@ -1,24 +0,0 @@
{
"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."
}
}

View File

@ -1,48 +0,0 @@
/* eslint-disable no-case-declarations */
/* eslint-disable indent */
// dotenv for handling environment variables
const dotenv = require('dotenv');
dotenv.config();
const token = process.env.TOKEN;;
// Discord.JS
const { Client, GatewayIntentBits } = require('discord.js');
const client = new Client({
intents: [
GatewayIntentBits.Guilds
]
});
// Various imports
const fn = require('./modules/functions.js');
const strings = require('./data/strings.json');
const debugMode = process.env.DEBUG;
const statusChannelId = process.env.STATUSCHANNELID
client.once('ready', () => {
fn.collectionBuilders.slashCommands(client);
console.log('Ready!');
// 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()) {
const { commandName } = interaction;
if (client.slashCommands.has(commandName)) {
client.slashCommands.get(commandName).execute(interaction).catch(e => console.error(e));
} else {
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);
}
}
});
process.on('uncaughtException', err => {
console.error(err);
});
client.login(token);

View File

@ -1,353 +0,0 @@
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
}
}
}
}
}
}
}

View File

@ -1,28 +0,0 @@
// 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);
}
})();

View File

@ -1,18 +0,0 @@
const replaceAll = require('string.prototype.replaceall');
const fs = require('fs');
const path = "../data/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);

View File

@ -1,78 +0,0 @@
// 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.`);
});
});
}
}

View File

@ -1,92 +0,0 @@
// 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 functions = {
// Functions for managing and creating Collections
collectionBuilders: {
// Create the collection of slash commands
slashCommands(client) {
const slashCommandFiles = fs.readdirSync('./_slashCommands/').filter(file => (file.endsWith(".js") && !file.startsWith("_")));
if (!client.slashCommands) client.slashCommands = new Discord.Collection();
client.slashCommands.clear();
for (const file of slashCommandFiles) {
const slashCommand = require(`../_slashCommands/${file}`);
if (slashCommand.data != undefined) {
client.slashCommands.set(slashCommand.data.name, slashCommand);
}
}
if (debugMode) console.log('Slash Commands Collection Built');
},
dotCommands(client) {
const dotCommandFiles = fs.readdirSync('./_dotCommands/').filter(file => (file.endsWith(".js") && !file.startsWith("_")));
if (!client.dotCommands) client.dotCommands = new Discord.Collection();
client.dotCommands.clear();
for (const file of dotCommandFiles) {
const dotCommand = require(`../_dotCommands/${file}`);
if (dotCommand.data != undefined) {
client.dotCommands.set(dotCommand.data.name, dotCommand);
}
}
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;

View File

@ -1,7 +0,0 @@
{
"dependencies": {
"discord.js": "^14.7.1",
"dotenv": "^16.0.3",
"string.prototype.replaceall": "^1.0.7"
}
}

84
strings.json Normal file
View File

@ -0,0 +1,84 @@
{
"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"
},
"costs": {
"gpt": {
"gpt-3.5-turbo": 0.2
},
"dalle": {
"256x256": 1.6,
"512x512": 1.8,
"1024x1024": 2.0
}
},
"ai": {
"chatModel": "gpt-3.5-turbo",
"chatPromptCentsPer": 0.15,
"chatPromptUnits": 1000,
"chatResCentsPer": 0.2,
"chatResUnits": 1000
},
"autoresponses": {
"bigDoinks": {
"keywords": ["big", "doinks"],
"responses": [
"<:bigdoinks:1053706618853924905> Gang.",
"<:bigdoinks:1053706618853924905> Out here in Amish",
"<:bigdoinks:1053706618853924905> Out here in Amish, smoking Big Doinks in Amish... Gang."
]
},
"ligma": {
"keywords": ["ligma"],
"responses": [
"ligma balls lmao gottem",
"ligma balls ahaha",
"https://tenor.com/view/ligma-balls-gif-12236083",
"<:deadmonkey:1139186312444911707>"
]
},
"ong": {
"keywords": [
"frfr",
"fr fr",
"bussin",
"no cap",
" ong "
],
"responses": [
"on god?!",
"fr fr?!",
"no cap?!",
"no cap fr",
"bussin fr, no cap",
"ongggg no :billed_cap: fr fr"
]
},
"fuckYou": {
"keywords": [
"fuck",
"nodbot"
],
"responses": [
"no u",
"go fuck yourself",
"why does everyone hate me :sob:",
"<:kms:1253790048696926298>",
"Eat a bag of dicks"
]
}
},
"temp": {}
}

4
update.sh Normal file
View File

@ -0,0 +1,4 @@
#!/bin/bash
git pull
docker build . -t v0idf1sh/nodbot
docker push v0idf1sh/nodbot