Compare commits

...

161 Commits

Author SHA1 Message Date
304503f7d8 Merge pull request 'v3.6.1 Requests Expansion' (#32) from v3.6.1 into main
All checks were successful
NodBot Production Dockerization / build (push) Successful in 21s
Reviewed-on: #32
2026-10-01 19:38:34 +00:00
7b504a2312 Re-fix variable ref
All checks were successful
NodBot PE Dockerization / build (push) Successful in 21s
2026-10-01 15:32:38 -04:00
8039f8f1c7 Print raw error instead of trying to stringify it
All checks were successful
NodBot PE Dockerization / build (push) Successful in 20s
2026-10-01 15:30:45 -04:00
46e5079eaf Add client to sendAlert
All checks were successful
NodBot PE Dockerization / build (push) Successful in 19s
2026-10-01 15:28:03 -04:00
8580dda6c8 Change MessageEmbed Description to just a string
All checks were successful
NodBot PE Dockerization / build (push) Successful in 19s
2026-10-01 15:25:53 -04:00
211e07f5f0 Fix tagging system
All checks were successful
NodBot PE Dockerization / build (push) Successful in 10s
2026-10-01 15:15:09 -04:00
531167f2ba Add specific target to push in CI flow
Some checks failed
NodBot PE Dockerization / build (push) Failing after 3s
2026-10-01 14:39:12 -04:00
f7c63c8705 Tweak dev and prod workflow 2026-10-01 14:30:48 -04:00
acfe540148 Add error alerting in Discord, revert last fix to test alerting
All checks were successful
NodBot PE Dockerization / build (push) Successful in 20s
2026-10-01 13:15:28 -04:00
23a282a8a5 Fix var reference in reply text 2026-10-01 12:19:19 -04:00
fc941ad487 Add url to request upload
All checks were successful
NodBot PE Dockerization / build (push) Successful in 1m18s
2026-10-01 12:03:47 -04:00
e198ca3b4d Versioning 2026-10-01 10:20:28 -04:00
fb963cc153 Merge pull request 'v3.6.0 GIF Fix' (#31) from v3.6.0 into main
Reviewed-on: #31
2026-10-01 14:14:12 +00:00
20a2665615 Dump env vars to log in dev
All checks were successful
NodBot PE Dockerization / build (push) Successful in 20s
NodBot Production Dockerization / build (push) Successful in 21s
2026-09-21 20:41:06 -04:00
c342f4741a Remove TenorJS completely
All checks were successful
NodBot PE Dockerization / build (push) Successful in 1m7s
2026-09-21 20:32:06 -04:00
f147761d35 Add version to help embed footer
All checks were successful
NodBot PE Dockerization / build (push) Successful in 1m20s
2026-09-21 20:24:04 -04:00
7ed4318863 Testing GIPHY for GIFs 2026-09-21 20:08:57 -04:00
5d5db3be40 Merge pull request 'v3.5.0' (#30) from v3.5.0 into main
All checks were successful
NodBot PE Dockerization / build (push) Successful in 1m17s
Reviewed-on: #30
2026-09-21 23:01:49 +00:00
e88b33c4ed Rollback April Fools'
All checks were successful
NodBot Production Dockerization / build (push) Successful in 25s
2025-04-02 18:05:52 -04:00
b10b126dc9 Update announcementChannelId to Prod
All checks were successful
NodBot Production Dockerization / build (push) Successful in 1m27s
2025-03-31 21:45:23 -04:00
d15431b299 Add announcement 2025-03-31 21:36:36 -04:00
4cf3efa584 Add membership check to dot commands 2025-03-31 20:24:17 -04:00
93251348a3 Add membership checks for slash commands 2025-03-31 20:21:37 -04:00
df9a3a0eba Add noMembership Embed Builder 2025-03-31 20:21:00 -04:00
35517d791f Add /member Manager 2025-03-31 20:19:19 -04:00
5d05306b4d Add fn.addMember and fn.removeMember 2025-03-31 19:40:52 -04:00
1fd3953852 Add fn.checkMembership 2025-03-31 19:36:08 -04:00
8fb269e6c1 Versioning v3.5.0 2025-03-31 19:35:49 -04:00
910f5ef609 Add collection for memberships 2025-03-31 19:32:40 -04:00
e389d5caae Merge pull request 'v3.4.3-dev' (#29) from v3.4.3-dev into main
Reviewed-on: #29
2025-02-09 22:36:11 +00:00
94301bb3f5 Fix ownerId ref
All checks were successful
NodBot Production Dockerization / build (push) Successful in 19s
2025-01-31 20:51:32 -05:00
fd60089d39 Add special status msg for leaving banned servers
All checks were successful
NodBot PE Dockerization / build (push) Successful in 18s
2025-01-31 20:45:50 -05:00
91982da762 Fix ref to bannedServers array
All checks were successful
NodBot PE Dockerization / build (push) Successful in 19s
2025-01-31 19:58:40 -05:00
4fcb45823d Tweak dev CI to use a clean environment every time
All checks were successful
NodBot PE Dockerization / build (push) Successful in 20s
2025-01-31 19:38:42 -05:00
efcf613ea4 .
Some checks failed
NodBot PE Dockerization / build (push) Has been cancelled
2025-01-31 19:30:39 -05:00
231ceeee9b Fix startup message
All checks were successful
NodBot PE Dockerization / build (push) Successful in 8s
2025-01-31 19:18:28 -05:00
a6d55ada75 .
All checks were successful
NodBot PE Dockerization / build (push) Successful in 24s
2025-01-31 18:55:07 -05:00
c3e6e0823b Add 3.4.3 updates
All checks were successful
NodBot Production Dockerization / build (push) Successful in 19s
2025-01-27 16:36:27 -05:00
c330af29fa Fix erroneous async issues
All checks were successful
NodBot PE Dockerization / build (push) Successful in 7s
2025-01-27 16:14:21 -05:00
9d819253e0 Improve .gif replies, testing only
All checks were successful
NodBot PE Dockerization / build (push) Successful in 1m21s
2025-01-27 15:53:49 -05:00
bb7c66d8db Merge pull request 'v3.4.2-dev' (#28) from v3.4.2-dev into main
Reviewed-on: #28
2025-01-27 20:14:14 +00:00
5e71af6072 Changed joint reply to standalone msg
All checks were successful
NodBot Production Dockerization / build (push) Successful in 19s
2025-01-27 15:03:35 -05:00
e5889e3c63 Versioning
All checks were successful
NodBot Production Dockerization / build (push) Successful in 20s
2025-01-24 20:20:37 -05:00
22c215f3c2 Coupla beers no beers 2025-01-24 20:16:03 -05:00
ba340e7b8e Merge pull request 'v3.4.1' (#27) from v3.4.1 into main
Reviewed-on: #27
2025-01-25 01:07:26 +00:00
1b77a24fb7 Branch/Tag fix for prod workflow 2025-01-11 11:03:56 -05:00
8878ac76e8 Add some custom error messages for perms and length of nick
All checks were successful
NodBot Production Dockerization / build (push) Successful in 1m14s
2025-01-11 11:02:39 -05:00
6fad8c6cdc Update workflow to remove the repo before cloning so every deploy is fresh 2025-01-11 09:48:44 -05:00
232a8ebc21 /setnick working
Some checks failed
NodBot Production Dockerization / build (push) Failing after 1s
2025-01-11 09:41:57 -05:00
0d663b444c Versioning 2025-01-11 08:45:26 -05:00
414b94285c Merge branch 'v3.4.0' 2025-01-11 08:41:25 -05:00
340551fbb2 Anoter fix
All checks were successful
NodBot PE Dockerization / build (push) Successful in 8s
2024-09-29 13:25:58 -04:00
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
70fa5aa892 Can't pull on a tag
Some checks failed
NodBot PE Dockerization / build (push) Failing after 0s
2024-09-29 13:23:19 -04:00
a0dc9a5cf5 Stop prod running on dev 2024-09-29 13:22:36 -04:00
3bdc03f553 Fix tag calls
Some checks failed
NodBot PE Dockerization / build (push) Failing after 0s
NodBot Production Dockerization / build (push) Failing after 1s
2024-09-29 13:19:52 -04:00
43cfb5a7fe Update CI, oops
Some checks failed
NodBot PE Dockerization / build (push) Failing after 0s
NodBot Production Dockerization / build (push) Failing after 0s
2024-09-29 13:12:52 -04:00
7e2664083e Add .gitea/workflows/dev-docker.yml 2024-09-29 17:12:41 +00:00
37d1d9ce1f Fix CI
Some checks failed
NodBot Production Dockerization / build (push) Failing after 1s
2024-09-29 13:11:03 -04:00
7f876f4555 Add a dev workflow
Some checks failed
NodBot Production Dockerization / build (push) Failing after 1s
2024-09-29 13:01:25 -04:00
0381272d7d Remove now defunct slash commands 2024-09-29 12:41:16 -04:00
e6936a5364 Update to run on tag push only 2024-09-29 12:40:54 -04:00
0e34a0a4bb Move to /view
All checks were successful
NodBot Production Dockerization / build (pull_request) Successful in 9s
2024-09-29 12:28:29 -04:00
e843275ba1 Fix references to guild names when not needed 2024-09-29 12:17:07 -04:00
28bd2f46cb added a response for when nothing gets returned from the API
All checks were successful
NodBot Production Dockerization / build (pull_request) Successful in 7s
2024-09-26 19:55:12 -04:00
e0c0dff82d v3.4.0-b
All checks were successful
NodBot Production Dockerization / build (pull_request) Successful in 17s
2024-09-26 19:39:19 -04:00
326d5a15f2 Might need to pull after checking out, oops
All checks were successful
NodBot Production Dockerization / build (pull_request) Successful in 18s
2024-09-26 19:37:35 -04:00
3def31b0fb Moved all button handling to ButtonHandlers.js and cleaned up /save gifSearch
All checks were successful
NodBot Production Dockerization / build (pull_request) Successful in 18s
2024-09-26 19:30:41 -04:00
b536ebda5b Add ability to handle pages of different length, defaulting to 10 2024-09-26 19:30:01 -04:00
6175a30974 Add PR tag # 2024-09-26 14:28:11 -04:00
8d5f75e471 Add nested commands 2024-09-26 14:25:22 -04:00
e4fbcca6c4 MVP for nested commands
All checks were successful
NodBot Production Dockerization / build (pull_request) Successful in 53s
2024-09-26 14:20:56 -04:00
2cbfc7f354 WIP DNU 2024-09-26 09:55:49 -04:00
ed315d0cb1 Versioning -- v3.4.0 2024-09-26 09:43:05 -04: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
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
45 changed files with 2285 additions and 827 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@ -0,0 +1,48 @@
name: NodBot PE Dockerization
######################################################
# Creates a new Docker image of nodbot-dev, pushes #
# it to DockerHub, and restarts the container. #
######################################################
# Run when a new tag for dev is pushed
on:
push:
tags:
- 'v*-dev*'
# Pull DockerHub credentials from secrets
env:
DHUB_UNAME: ${{ secrets.DHUB_UNAME }}
DHUB_PWORD: ${{ secrets.DHUB_PWORD }}
jobs:
build:
runs-on: self-hosted
steps:
- name: Clean up old environment
run: |
rm -rf /var/lib/act_runner/nodbot
rm -rf /var/lib/act_runner/nodbot-dev
- name: Pull latest from Git
run: |
echo "Ref: ${{ gitea.ref_name }}"
echo "Branch: tags/${{ gitea.ref_name }}"
cd /var/lib/act_runner
git clone https://git.vfsh.dev/voidf1sh/nodbot
cd nodbot
git checkout tags/${{ gitea.ref_name }}
- name: Build the Docker image
run: |
cd /var/lib/act_runner/nodbot
docker build --target dev . --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
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:
tags:
- 'v*-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.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 ${{ gitea.ref_name }}
- 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,47 @@
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: ${{ 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
rm -rf nodbot
git clone https://git.vfsh.dev/voidf1sh/nodbot
cd nodbot
git pull
fi
git checkout ${{ gitea.ref_name }}
- name: Build the Docker image
run: |
cd /var/lib/act_runner/nodbot
docker build --target prod . --file Dockerfile --tag v0idf1sh/nodbot:prod
- 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:prod
- name: Restart the container
run: |
cd /srv/docker/nodbot
docker-compose down
docker-compose up -d

View File

@ -1,35 +0,0 @@
name: NodBot Production Dockerization
on:
commit:
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: |
pwd
whoami
cd /root/nodbot
git pull
git checkout pe
- name: Build the Docker image
run: |
cd /root/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 /root/nodbot
docker push v0idf1sh/nodbot-pe

View File

@ -1,35 +0,0 @@
name: NodBot Production Dockerization
on:
pull_request:
branches:
- main
env:
DHUB_UNAME: ${{ secrets.DHUB_UNAME }}
DHUB_PWORD: ${{ secrets.DHUB_PWORD }}
jobs:
build:
runs-on: self-hosted
steps:
- name: Pull latest from Git
run: |
pwd
whoami
cd /root/nodbot
git pull
git checkout $GITHUB_HEAD_REF
- name: Build the Docker image
run: |
cd /root/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 /root/nodbot
docker push v0idf1sh/nodbot

5
.gitignore vendored
View File

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

51
CHANGELOG.md Normal file
View File

@ -0,0 +1,51 @@
## v3.6.x
#### v3.6.1 (#32) Improving Requests
* Start logging message URLs and timestamps when requests are submitted.
* Plan to improve request submission process to interactive slash-command.
#### v3.6.0 (#31) Fixing GIF APIs
* Switch from Tenor to Giphy for GIFs
## v3.5.x
#### v3.5.0 (#30) The April Fools Update
* Add April Fools stuff
## v3.4.x
#### v3.4.3 (#29)
* Fix `.gif` replies to redirect to the original replyee (#26)
#### v3.4.2 (#28)
* Added `.drink` and aliases
* Changed `.joint` responses to be standalone instead of replies
#### v3.4.1 (#27)
* Adding command to change nicknames
#### v3.4.0 (#25)
* Added nested commands, enclose a command in brackets, braces, or parenthesis inside a longer message: `You really don't get it do you? [that's the joke.gif] You're so dense` would return the results for just `that's the joke.gif`
* Improved the `/save gifsearch` interface and the code behind the scenes
## 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,211 @@
const customEmbeds = require('../CustomModules/Embeds.js');
const InteractionStorage = require('../CustomModules/InteractionStorage.js');
const indexer = require('../CustomModules/Indexer.js');
const fn = require('../functions.js');
const { GifData } = require('./NodBot.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;
case 'prevGif':
module.exports.gifSearchPage(interaction);
break;
case 'nextGif':
module.exports.gifSearchPage(interaction);
break;
case 'confirmGif':
module.exports.gifSearchPage(interaction);
break;
case 'cancelGif':
module.exports.gifSearchPage(interaction);
break;
case 'closeRequests':
module.exports.closeRequests(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.user.username}, 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.user.username}, 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.user.username}, 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.user.username}, indexedJoints));
},
gifSearchPage(interaction) {
const iStorage = interaction.client.iStorage.get(interaction.message.interaction.id);
switch (interaction.component.customId) {
case 'prevGif':
if (iStorage.page > 0) {
iStorage.page = iStorage.page - 1;
}
break;
case 'nextGif':
if (iStorage.page < interaction.client.gifs.size / 10) {
iStorage.page = iStorage.page + 1;
}
break;
case 'confirmGif':
const gif = indexer(iStorage.gifsCollection, iStorage.page, 1).thisPage[0];
const gifData = new GifData().setInfo(iStorage.gifName, gif.media_formats.gif.url, gif.id);
fn.upload.gif(gifData, interaction.client);
interaction.update({ content: `I've saved the GIF as ${gifData.name}.gif`, components: [] });
fn.download.gifs(interaction.client);
return;
break;
case 'cancelGif':
interaction.update({ content: 'The GIF has been discarded.', components: [], embeds: [] });
return;
break;
default:
break;
}
// Generate the action row
const gifSearchAR = customEmbeds.actionRows.gifSearchAR(iStorage.state);
// Update the index
const indexedGifs = indexer(iStorage.gifsCollection, iStorage.page, 1);
indexedGifs.query = iStorage.query;
indexedGifs.gifName = iStorage.gifName;
// Generate the embed
const gifEmbed = customEmbeds.core.gifSearch({ author: interaction.user.username }, indexedGifs);
// Update the interaction
interaction.update({ embeds: [gifEmbed], components: [gifSearchAR], ephemeral: true });
},
closeRequests(interaction) {
const closeRequestModal = customEmbeds.modals.closeRequestsModal();
interaction.showModal(closeRequestModal);
interaction.update({ content: 'The requests menu has been closed.', components: [] });
}
}

179
CustomModules/Embeds.js Normal file
View File

@ -0,0 +1,179 @@
const { MessageActionRow, MessageButton, MessageEmbed, TextInputComponent } = require('discord.js');
module.exports = {
actionRows: {
gifSearchAR(state) {
// Setup the buttons
const previousButton = new MessageButton()
.setCustomId('prevGif')
.setLabel('⬅️')
.setStyle('SECONDARY');
const confirmButton = new MessageButton()
.setCustomId('confirmGif')
.setLabel('Select')
.setStyle('PRIMARY');
const nextButton = new MessageButton()
.setCustomId('nextGif')
.setLabel('➡️')
.setStyle('SECONDARY');
const cancelButton = new MessageButton()
.setCustomId('cancelGif')
.setLabel('Cancel')
.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');
const closeButton = new MessageButton()
.setCustomId('closeRequests')
.setLabel('Close Requests')
.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, 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);
}
},
modals: {
closeRequestsModal() {
const requestNumberInput = new TextInputComponent()
.setCustomId('requestNumber')
.setLabel('Request Number')
.setPlaceholder('420')
.setMinLength(1)
.setMaxLength(5)
.setStyle('SHORT')
.setRequired(true);
const actionRow = new MessageActionRow()
.addComponents([requestNumberInput]);
return new Modal()
.setTitle('Close Request')
.setDescription('Please enter the number of the request you would like to close.')
.addComponents([actionRow]);
}
},
core: {
gifSearch(commandData, indexedGifs) {
return new MessageEmbed()
.setColor('#0099ff')
.setTitle('GIF Search')
.setImage(indexedGifs.thisPage[0].media_formats.gif.url)
.setFooter({ text: `Page ${indexedGifs.pagesString}` })
.addFields(
{ name: 'Query', value: `"${indexedGifs.query}"`, inline: true },
{ name: 'Saving As', value: `${indexedGifs.gifName}.gif`, inline: true },
);
}
}
}

32
CustomModules/Indexer.js Normal file
View File

@ -0,0 +1,32 @@
module.exports = (collection, page, qty) => {
const itemsPerPage = qty ? qty : 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;
}
}

179
CustomModules/NodBot.js Normal file
View File

@ -0,0 +1,179 @@
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;
this.message = message;
return this;
}
validate(dotCommands) {
if (this.args.startsWith('http')) return false;
if (this.args.startsWith('www')) return false;
const indices = {
curlyBrace: {
start: -1,
end: -1
},
bracket: {
start: -1,
end: -1
},
parenthesis: {
start: -1,
end: -1
}
}
// Check for and extract the part of the message that's
// wrapped in any type of brackets or quotes eg. ([{``''""}])
const curlyBraceStart = this.message.content.match(/[\{]/g);
const curlyBraceEnd = this.message.content.match(/[\}]/g);
if (curlyBraceStart && curlyBraceEnd) {
indices.curlyBrace.start = this.message.content.indexOf(curlyBraceStart[0]) + 1;
indices.curlyBrace.end = this.message.content.lastIndexOf(curlyBraceEnd[0]);
}
const bracketStart = this.message.content.match(/[\[]/g);
const bracketEnd = this.message.content.match(/[\]]/g);
if (bracketStart && bracketEnd) {
indices.bracket.start = this.message.content.indexOf(bracketStart[0]) + 1;
indices.bracket.end = this.message.content.lastIndexOf(bracketEnd[0]);
}
const parenthesisStart = this.message.content.match(/[\(]/g);
const parenthesisEnd = this.message.content.match(/[\)]/g);
if (parenthesisStart && parenthesisEnd) {
indices.parenthesis.start = this.message.content.indexOf(parenthesisStart[0]) + 1;
indices.parenthesis.end = this.message.content.lastIndexOf(parenthesisEnd[0]);
}
let nestedText = new String();
if (indices.curlyBrace.start >= 0 && indices.curlyBrace.end > 0) {
nestedText = this.message.content.slice(indices.curlyBrace.start, indices.curlyBrace.end);
}
if (indices.bracket.start >= 0 && indices.bracket.end > 0) {
nestedText = this.message.content.slice(indices.bracket.start, indices.bracket.end);
}
if (indices.parenthesis.start >= 0 && indices.parenthesis.end > 0) {
nestedText = this.message.content.slice(indices.parenthesis.start, indices.parenthesis.end);
}
console.log(nestedText);
if (nestedText !== "") {
this.nestedCommand = {
finalPeriod: nestedText.lastIndexOf('.'),
isCommand: nestedText.lastIndexOf('.') >= 0 ? true : false,
args: nestedText.slice(0, nestedText.lastIndexOf('.')).toLowerCase(),
command: nestedText.slice(nestedText.lastIndexOf('.') + 1).toLowerCase()
}
for (const [key, value] of dotCommands) {
if (key === this.nestedCommand.command) {
this.isValid = true;
this.args = this.nestedCommand.args;
this.command = key;
this.isCommand = this.nestedCommand.isCommand;
this.finalPeriod = this.nestedCommand.finalPeriod;
return this;
} else if (value.alias) {
if (typeof value.alias === 'string' && value.alias === this.nestedCommand.command) {
this.command = key
this.args = this.nestedCommand.args;
this.isValid = true;
this.isCommand = this.nestedCommand.isCommand;
this.finalPeriod = this.nestedCommand.finalPeriod;
return this;
} else if (typeof value.alias === 'object' && value.alias.includes(this.nestedCommand.command)) {
this.command = key
this.args = this.nestedCommand.args;
this.isValid = true;
this.isCommand = this.nestedCommand.isCommand;
this.finalPeriod = this.nestedCommand.finalPeriod;
return this;
}
}
}
}
for (const [key, value] of dotCommands) {
if (key === this.command) {
this.isValid = true;
return this;
} else if (value.alias) {
if (typeof value.alias === 'string' && value.alias === this.command) {
this.command = key;
this.isValid = true;
return this;
} else if (typeof value.alias === 'object' && 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
}
}
}

View File

@ -1,8 +1,20 @@
FROM node:16
# Dev Build
FROM node:18 AS dev
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
COPY package.json ./
RUN npm install
COPY . .
CMD ["/bin/sh", "-c", "node main.js 2> /logs/nodbot.error 1> /logs/nodbot.log"]
# Invoke the bot without redirecting STDOUT and STDERR so that docker logs can be used.
CMD ["/bin/sh", "-c", "node main.js"]
FROM node:18 AS prod
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
COPY package.json ./
RUN npm install
COPY . .
# CMD ["/bin/sh", "-c", "node main.js 2> /logs/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"]

View File

@ -1,5 +1,5 @@
# About Nodbot
Nodbot is a content saving and serving Discord bot. Nodbot is able to search Tenor for GIFs, save custom copypastas, and look up marijuana strain information. Nodbot is in semi-active development by voidf1sh. It's buggy as hell and very shoddily built. Don't use it.
Nodbot is a content saving and serving Discord bot. Nodbot is able to search GIPHY for GIFs, save custom copypastas, and look up marijuana strain information. Nodbot is in semi-active development by voidf1sh. It's buggy as hell and very shoddily built. Don't use it.
# Nodbot Help
@ -11,21 +11,12 @@ Use the `/help` command to see the bot's help message.
## Push Docker Image
`docker push name/nodbot`
# Immediate To-Do
1. ~~Sanitize inputs for SQL queries.~~ Done.
2. ~~Move environment variables so they don't get included in the image.~~
3. Implement error handling on all actions.
4. Ephemeral responses to some/most slash commands.
5. Comment the code! Document!
6. Check for and create database tables if necessary. Handle errors.
# Deploy NodBot Yourself
1. Create an application at the [Discord Developer Portal](https://discord.com/developers/applications)
2. Convert the application into a Bot
3. Note down the token provided and keep this safe. You cannot view this token again, only regenerate a new one.
4. Create a Tenor account and obtain an API key.
4. Create a GIPHY 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.
@ -93,19 +84,8 @@ dbPort=<mySQL port (3306)>
dbUser=<mySQL username>
dbPass=<mySQL user password>
dbName=<mySQL table name>
tenorAPIKey=<Tenor API Key>
giphyAPIKey=<GIPHY API Key>
ownerId=<your Discord user ID>
statusChannelId=<Discord channel ID of channel used for status messages>
clientId=<Discord user ID of your bot>
```
## Changes
v3.0.1 - Migrate TenorJS API Endpoint
v3.0.2 - Add medical advice commands
v3.0.3 - Fix broken `/requests` command
v3.0.4 - Add ability to use multiple aliases
v3.0.5 - Add ability to save strains
v3.0.6 - Move `.strain` to `/strain` and add Autocomplete
v3.0.7 - Add `.spongebob` replies
v3.0.8 - Add ability to open requests by pages
```

View File

@ -1,14 +0,0 @@
# v3.1.0
* Name checking for saving content
* .jpg, .wav
* Audio/Video attachments for saved content.
* Pass The Joint
* Voting system for Super Adventure Club
# v4.0.0
* Scalability: modify the code to allow the bot to be used in multiple servers
* including saved content, saved commands, preferences, etc.
# v3.?.?
= Joke generator for Hallihan

38
_list-guilds.js Normal file
View File

@ -0,0 +1,38 @@
/*
* Quick and dirty script to list out what guilds the bot is in and optionally leave some.
*/
const guildsToLeave = ['1224396616555823124'];
const dotenv = require('dotenv').config();
const token = process.env.TOKEN;
const Discord = require('discord.js');
const client = new Discord.Client({
intents: [
'GUILDS'
]
});
client.on('ready', async () => {
const guildChecks = [];
console.log(`Logged in as ${client.user.tag}`);
console.log('Guilds:');
client.guilds.cache.forEach((guild) => {
const p = new Promise((resolve, reject) => {
console.log(` - ${guild.name} (${guild.id})`);
if (guildsToLeave.includes(guild.id)) {
console.log(` Leaving ${guild.name}`);
guild.leave().then(resolve).catch(reject);
} else {
resolve();
}
});
guildChecks.push(p);
});
await Promise.all(guildChecks);
console.log('All guilds checked.');
client.destroy();
});
client.login(token);

View File

@ -1,4 +1,8 @@
{
"guildId": "868542949737246730",
"validCommands": []
"validCommands": [],
"roaches": [],
"icaoIds": [],
"datisICAOs": [],
"bannedServers": ["1224396616555823124"]
}

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

View File

@ -2,52 +2,53 @@ 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)) {
if (process.env.isDev) console.log('https://tenor.googleapis.com/v2/search?' + `&q=${commandData.args}` + `&key=${process.env.tenorAPIKey}` + '&limit=1&contentfilter=off');
if (!client.gifs.has(commandData.args.toLowerCase())) {
// TODO: Implement proper logging
if (process.env.isDev) console.log('https://api.giphy.com/v2/search?' + `&q=${commandData.args}` + `&key=${process.env.giphyAPIKey}` + '&limit=1&contentfilter=off');
const q = await axios.get(
'https://tenor.googleapis.com/v2/search?' +
'https://api.giphy.com/v2/search?' +
`&q=${commandData.args}` +
`&key=${process.env.tenorAPIKey}` +
`&key=${process.env.giphyAPIKey}` +
'&limit=1' +
`&contentfilter=off`
).then(res => {
).then(async (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);
// Handle when the invoking message is a reply to someone else
if (message.reference) {
// Fetch the referenced message from the channels' MessageManager
await message.channel.messages.fetch(message.reference.messageId).then(async (refMsg) => {
// Send the reply to the referenced message
await refMsg.reply(commandData.embed_url);
}).catch(async (e) => {
// Failed fetching the message from the channel, fallback to regular send
console.error(e);
await message.reply(commandData.embed_url);
});
} else {
await message.reply(commandData.embed_url);
}
}).catch(err => console.error(err));
// tenor.Search.Query(commandData.args, "1").then(res => {
// if (res[0] == undefined) {
// message.reply('Sorry I was unable to find a GIF of ' + commandData.args);
// return;
// };
// commandData.embed_url = res[0].media[0].gif.url;
// // message.reply(fn.embeds.gif(commandData));
// // message.channel.send(`> ${commandData.author} - ${commandData.args}.gif`);
// // message.channel.send(commandData.embed_url);
// message.reply(commandData.embed_url);
// }).catch(err => console.error(err));
} else {
// message.reply(commandData.args + ' requested by ' + message.author.username + '\n' + client.gifs.get(commandData.args).embed_url);
commandData.embed_url = client.gifs.get(commandData.args).embed_url;
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(commandData.embed_url);
message.reply(gifData.url);
}
}
}
}

View File

@ -5,13 +5,35 @@ module.exports = {
name: 'joint',
description: 'Send a random weed-themed phrase.',
usage: '.joint',
alias: ['bong', 'blunt', 'bowl', 'pipe'],
alias: ['bong', 'blunt', 'bowl', 'pipe', 'dab', 'vape', 'dabs', 'shatter', 'edible', 'edibles', 'doobie', 'spliff', 'gummy', 'gummies', 'hash', 'toke', 'big doinks',
'drink', 'shot', 'shots', 'beer', 'beers', 'brew', 'brews', 'liquor', 'cocktail', 'cocktails', 'wine', 'wines', 'whiskey', 'whiskies', 'vodka', 'vodkas', 'rum', 'rums', 'cider', 'ciders',
],
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);
}
const randIndex = Math.floor(Math.random() * joints.length);
message.reply(`${joints[randIndex]} ${emoji.joint}`);
// 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.channel.send(`${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");
}
}
}

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

@ -0,0 +1,30 @@
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);
if (metarData.length === 0) {
message.reply('No METAR data found for the provided ICAOs.');
return;
}
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);
}
}
}
}

View File

@ -6,14 +6,14 @@ module.exports = {
usage: '<Copypasta Name>.pasta',
execute(message, commandData) {
const client = message.client;
let replyBody = '';
let iconUrl;
let pastaData;
if (!client.pastas.has(commandData.args)) {
commandData.content = 'Sorry I couldn\'t find that pasta.';
pastaData = {
content: "Sorry, I couldn't find that pasta."
};
} else {
commandData.content = client.pastas.get(commandData.args).content;
commandData.iconUrl = client.pastas.get(commandData.args).iconUrl;
pastaData = client.pastas.get(commandData.args);
}
message.reply(fn.embeds.pasta(commandData));
message.reply(fn.embeds.pasta(commandData, pastaData));
}
}

View File

@ -5,13 +5,25 @@ module.exports = {
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);
// Get the request information from the command data that was passed in
const requestText = commandData.args;
const messageUrl = message.url;
const requester = message.author.username;
try {
fn.upload.request(requester, requestText, messageUrl, message.client);
// Set the content to sent a text embed notifying the user of success
commandData.content = `Your request has been submitted!\nRequest: ${requestText}`;
const replyEmbed = fn.embeds.text(commandData);
message.reply({ embeds: [ replyEmbed ]});
} catch(err) {
console.error(err);
commandData.content = "An error occurred while saving the request, please try again later.";
const replyEmbed = fn.embeds.text(commandData);
message.reply({ embeds: [ replyEmbed ]});
const errorEmbed = fn.embeds.error(`An error has occurred in command ${this.name}:\n${err}`);
fn.sendAlert(errorEmbed, message.client);
}
},
};

View File

@ -10,20 +10,33 @@ module.exports = {
// message.reply(fn.spongebob(commandData)).then(() => {
// message.delete();
// });
if (message.reference != undefined) {
const repliedMessageId = message.reference.messageId;
message.channel.messages.fetch(repliedMessageId)
.then(repliedMessage => {
repliedMessage.reply(fn.spongebob({ args: repliedMessage.content })).then(() => {
if (message.deletable) message.delete();
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);
});
})
.catch(err => {
console.error(err);
});
} else {
} 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 (message.deletable) message.delete(); // If the initiating message is deletable, delete it.
});
}
}

View File

@ -1,7 +1,6 @@
/* eslint-disable comma-dangle */
// dotenv for handling environment variables
const dotenv = require('dotenv');
dotenv.config();
const dotenv = require('dotenv').config();
// Assignment of environment variables for database access
const dbHost = process.env.dbHost;
const dbUser = process.env.dbUser;
@ -9,11 +8,12 @@ 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;
const statusChannelId = process.env.statusChannelId;
// filesystem
const fs = require('fs');
const zlib = require('zlib');
// Discord.js
const Discord = require('discord.js');
@ -21,14 +21,20 @@ 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 appVersion = require('./package.json').version;
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,
@ -41,6 +47,11 @@ const db = new mysql.createPool({
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();
@ -72,13 +83,13 @@ const functions = {
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 (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');
},
@ -86,12 +97,13 @@ const functions = {
if (!client.gifs) client.gifs = new Discord.Collection();
client.gifs.clear();
for (const row of rows) {
const gif = {
id: row.id,
name: row.name,
embed_url: row.embed_url
};
client.gifs.set(gif.name, gif);
// 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');
},
@ -111,13 +123,8 @@ const functions = {
if (!client.pastas) client.pastas = new Discord.Collection();
client.pastas.clear();
for (const row of rows) {
const pasta = {
id: row.id,
name: row.name,
content: row.content,
iconUrl: row.iconurl,
};
client.pastas.set(pasta.name, pasta);
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');
},
@ -148,7 +155,7 @@ const functions = {
if (isDev) console.log('Strains Collection Built');
},
medicalAdvice(rows, client) {
if (!client.medicalAdviceCol) client.medicalAdviceColl = new Discord.Collection();
if (!client.medicalAdviceColl) client.medicalAdviceColl = new Discord.Collection();
client.medicalAdviceColl.clear();
for (const row of rows) {
const medicalAdvice = {
@ -159,39 +166,16 @@ const functions = {
}
if (isDev) console.log('Medical Advice Collection Built');
},
},
dot: {
getCommandData(message) {
const commandData = {};
// Split the message content at the final instance of a period
const finalPeriod = message.content.lastIndexOf('.');
if(isDev) console.log(message.content);
// If the final period is the last character, or doesn't exist
if (finalPeriod < 0) {
if (isDev) console.log(finalPeriod);
commandData.isCommand = false;
return commandData;
}
commandData.isCommand = true;
// Get the first part of the message, everything leading up to the final period
commandData.args = message.content.slice(0,finalPeriod).toLowerCase();
// Get the last part of the message, everything after the final period
commandData.command = message.content.slice(finalPeriod).replace('.','').toLowerCase();
commandData.author = `${message.author.username}#${message.author.discriminator}`;
return this.checkCommand(commandData);
roaches(client) {
if (!client.roaches) client.roaches = new Discord.Collection();
client.roaches.clear();
if (isDev) console.log('Medical Advice Collection Built');
},
checkCommand(commandData) {
if (commandData.isCommand) {
const validCommands = require('./config.json').validCommands;
commandData.isValid = validCommands.includes(commandData.command);
// Add exceptions for messages that contain only a link
if (commandData.args.startsWith('http')) commandData.isValid = false;
}
else {
commandData.isValid = false;
console.error('Somehow a non-command made it to checkCommands()');
}
return commandData;
memberships(client) {
if (!client.memberships) client.memberships = new Discord.Collection();
client.memberships.clear();
client.memberships.set("481933290912350209", "vfsh");
if (isDev) console.log('Memberships Collection Built');
}
},
embeds: {
@ -199,52 +183,75 @@ const functions = {
// Construct the Help Embed
const helpEmbed = new Discord.MessageEmbed()
.setColor('BLUE')
.setAuthor('Help Page')
.setDescription(strings.help.description)
.setThumbnail(strings.urls.avatar);
.setAuthor({name: 'Help Page'})
.setThumbnail(strings.urls.avatar)
.setFooter(`v${appVersion}`);
// Construct the Slash Commands help
let slashCommandsFields = [];
let slashSeenNames = new Array();
const slashCommandsMap = interaction.client.slashCommands.map(e => {
return {
name: e.data.name,
description: e.data.description
};
})
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({
name: `- /${e.name}`,
value: e.description,
inline: false,
});
slashCommandsFields.push(`- \`/${e.name}\` - ${e.description}`);
}
console.log(slashCommandsFields);
// Construct the Dot Commands Help
let dotCommandsFields = [];
let dotCommandsFields = new Array();
let dotSeenNames = new Array();
const dotCommandsMap = interaction.client.dotCommands.map(e => {
return {
name: e.name,
description: e.description,
usage: e.usage
};
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) {
dotCommandsFields.push({
name: `- .${e.name}`,
value: `${e.description}\nUsage: ${e.usage}`,
inline: false,
});
if (e != null) {
dotCommandsFields.push(`- \`.${e.name}\` - ${e.description}\n\tUsage: ${e.usage}\n\tAliases: ${e.aliasString}`);
}
}
console.log(dotCommandsFields);
helpEmbed.addField('Slash Commands', strings.help.slash);
helpEmbed.addFields(slashCommandsFields);
helpEmbed.addField('Dot Commands', strings.help.dot);
helpEmbed.addFields(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
@ -252,76 +259,67 @@ const functions = {
},
gif(commandData) {
return { embeds: [new Discord.MessageEmbed()
.setAuthor(`${commandData.args}.${commandData.command}`)
.setAuthor({name: `${commandData.args}.${commandData.command}`})
.setImage(commandData.embed_url)
.setTimestamp()
.setFooter(commandData.author)]};
.setFooter({text: commandData.author})]};
},
pasta(commandData) {
pasta(commandData, pastaData) {
return { embeds: [ new Discord.MessageEmbed()
.setAuthor(`${commandData.args}.${commandData.command}`)
.setDescription(commandData.content)
.setThumbnail(commandData.iconUrl)
.setAuthor({name: `${commandData.args}.${commandData.command}`})
.setDescription(pastaData.content)
.setThumbnail("https://assets.vfsh.dev/shednod.png")
.setTimestamp()
.setFooter(commandData.author)]};
.setFooter({text: commandData.author})]};
},
pastas(commandData) {
const pastasArray = [];
pastas(commandData, indexedPastas) {
const pastasEmbed = new Discord.MessageEmbed()
.setAuthor(commandData.command)
.setAuthor({name: commandData.command})
.setTimestamp()
.setFooter(commandData.author);
.setFooter({text: `Page: ${indexedPastas.pagesString}`})
.setDescription(indexedPastas.pastasString);
for (const row of commandData.pastas) {
pastasArray.push(`#${row.id} - ${row.name}.pasta`);
}
const pastasString = pastasArray.join('\n');
pastasEmbed.setDescription(pastasString);
return { embeds: [pastasEmbed], ephemeral: true };
const pastasPageAR = customEmbeds.actionRows.pastasPageAR(indexedPastas.state);
return { embeds: [pastasEmbed], components: [pastasPageAR], ephemeral: true };
},
gifs(commandData) {
const gifsArray = [];
gifs(commandData, indexedGifs) {
const gifsEmbed = new Discord.MessageEmbed()
.setAuthor(commandData.command)
.setAuthor({name: commandData.command})
.setTimestamp()
.setFooter(commandData.author);
.setFooter({text: `Page: ${indexedGifs.pagesString}`})
.setDescription(indexedGifs.gifsString);
for (const row of commandData.gifs) {
gifsArray.push(`#${row.id} - ${row.name}.gif`);
}
const gifsPageAR = customEmbeds.actionRows.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 gifsString = gifsArray.join('\n');
gifsEmbed.setDescription(gifsString);
return { embeds: [gifsEmbed] };
const jointsPageAR = customEmbeds.actionRows.jointsPageAR(indexedJoints.state);
return { embeds: [jointsEmbed], components: [jointsPageAR], ephemeral: true };
},
text(commandData) {
return { embeds: [new Discord.MessageEmbed()
.setAuthor(commandData.command)
const textEmbed = new Discord.MessageEmbed()
.setAuthor({ name: commandData.command })
.setDescription(commandData.content)
.setTimestamp()
.setFooter(commandData.author)]};
.setFooter({text: commandData.author});
return textEmbed;
},
requests(commandData) {
requests(commandData, indexedRequests) {
const requestsEmbed = new Discord.MessageEmbed()
.setAuthor(commandData.command)
.setAuthor({name: commandData.command})
.setTimestamp()
.setFooter(commandData.author);
.setFooter({text: `Page: ${indexedRequests.pagesString}`})
.setDescription(indexedRequests.requestsString);
const requestsArray = [];
for (const row of commandData.requests) {
requestsArray.push(
`**#${row.id} - ${row.author}**`,
`Request: ${row.request}`
);
}
requestsEmbed.setDescription(requestsArray.join('\n'));
return { embeds: [requestsEmbed], ephemeral: true };
const requestsPageAR = customEmbeds.actionRows.requestsPageAR(indexedRequests.state);
return { embeds: [requestsEmbed], components: [requestsPageAR], ephemeral: true };
},
strain(strainInfo, interaction) {
const strainEmbed = new Discord.MessageEmbed()
@ -361,6 +359,117 @@ const functions = {
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;
}
},
noMembership() {
const embed = new Discord.MessageEmbed()
.setAuthor({ name: 'NodBot, powered by Enshittification'})
.setTitle('🚨 Subscription Required 🚨')
.setDescription('You have attempted to access NodBot\’s premium services without an active subscription. Please refer to our updated Terms of Service and pay the necessary fees to continue. [PayPal link](https://paypal.me/v0idf1sh)')
.setFooter({ text: "Ligma balls" });
return { embeds: [embed] };
},
membershipAnnouncement() {
const announcement = '# 🚨 Attention, peasants 🚨\n\nBig changes are here! As of today, NodBot is no longer a free service. Due to rising operational costs, we’re moving to a subscription-based model.\n\nEffective immediately, non-paying users (freeloaders) will have all commands rejected. To regain access, kindly submit a donation of your hard-earned monies, I\'ll spend it better than you anyways.\n\n## 💰 New Pricing Structure\nNodBot\'s subscription is a pay-what-you-want model with a minimum monthly donation valued at $10 USD.\n\nThank you for your continued (and now mandatory) support!\n\n— NodBot, your favorite paywalled AI overlord';
const embed1 = new Discord.MessageEmbed()
.setAuthor({ name: 'Terms of Use' })
.setDescription('By continuing to exist within the same digital space as NodBot, you automatically agree to the following terms:\n* Subscription Fees – Access to NodBot is now a privilege, not a right. Users must pay the required fees or suffer the consequences (i.e., total rejection and public shaming).\n* Payment Processing Period – Due to fraud prevention measures, payments must be verified before access to the bot is restored. Please allow 2-4 hours for access to be restored.\n* Non-Refundable Payments – If you thought there were refunds, think again moron. No take-backs.\n* Service Interruptions – NodBot may go offline at any time due to totally legitimate server issues. No compensation will be provided.\n* Arbitrary Rule Enforcement – Rules are subject to change at my whim, and compliance is mandatory.');
const embed2 = new Discord.MessageEmbed()
.setAuthor({ name: 'Privacy Policy' })
.setDescription('* Data Collection – NodBot does collect personal data, including but not limited to: location, IP address, MAC address, physical address, mother\'s maiden name, social security number, credit card numbers, phone fingerprints, physical fingerprints, penis length and girth, first pet\'s name, city where you met your spouse, and city where your parents met.\n* Payment Info – All payments are processed through a sketchy-looking PayPal link. NodBot and voidf1sh take no responsibility if your bank, CPA, lawyer, or local PD calls you afterward.\n* Chat Logs – NodBot may or may not remember every dumbass thing you’ve ever typed. Just assume it does.\n* Third-Party Sharing – Your usage data may be shared with savage\'s mom\n* Right to Be Forgotten – You can request NodBot forget your existence, but that requires a one-time processing fee of $50.');
return { content: announcement, embeds: [ embed1, embed2 ] };
},
error(errorText) {
const errorEmbed = new Discord.MessageEmbed()
.setAuthor({ name: "Error!" })
.setDescription(errorText)
.setFooter({ text: appVersion });
return errorEmbed;
}
},
collect: {
gifName(interaction) {
@ -369,19 +478,20 @@ const functions = {
},
},
upload: {
request(commandData, client) {
const query = `INSERT INTO requests (author, request, status) VALUES (${db.escape(commandData.author)},${db.escape(commandData.args)},'Active')`;
request(requester, requestText, messageUrl, client) {
const query = `INSERT INTO requests (author, request, status, url) VALUES (${db.escape(requester)},${db.escape(requestText)},'Active',${db.escape(messageUrl)})`;
db.query(query, (err, rows, fields) => {
if (err) throw err;
functions.download.requests(client);
});
},
pasta(pastaData, client) {
const query = `INSERT INTO pastas (name, content) VALUES (${db.escape(pastaData.name)},${db.escape(pastaData.content)})`;
db.query(query, (err, rows, fields) => {
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)})`;
@ -390,9 +500,9 @@ const functions = {
functions.download.joints(client);
});
},
gif(gifData, client) {
const query = `INSERT INTO gifs (name, embed_url) VALUES (${db.escape(gifData.name)}, ${db.escape(gifData.embed_url)})`;
db.query(query, (err, rows, fields) => {
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);
});
@ -446,7 +556,7 @@ const functions = {
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})`;
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) => {
@ -455,40 +565,46 @@ const functions = {
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: {
requests(client) {
async requests(client) {
const query = 'SELECT * FROM requests WHERE status = \'Active\' ORDER BY id DESC';
db.query(query, (err, rows, fields) => {
await db.query(query, (err, rows, fields) => {
if (err) throw err;
functions.collections.requests(rows, client);
});
},
pastas(client) {
async pastas(client) {
const query = 'SELECT * FROM pastas ORDER BY id ASC';
db.query(query, (err, rows, fields) => {
await db.query(query, (err, rows, fields) => {
if (err) throw err;
functions.collections.pastas(rows, client);
});
},
gifs(client) {
async gifs(client) {
const query = 'SELECT * FROM gifs ORDER BY id ASC';
db.query(query, (err, rows, fields) => {
await db.query(query, (err, rows, fields) => {
if (err) throw err;
functions.collections.gifs(rows, client);
});
},
joints(client) {
async joints(client) {
const query = 'SELECT * FROM joints ORDER BY id ASC';
db.query(query, (err, rows, fields) => {
await db.query(query, (err, rows, fields) => {
if (err) throw err;
functions.collections.joints(rows, client);
});
},
strain(strainName, interaction) {
async strain(strainName, interaction) {
const query = `SELECT id, strain, type, effects, description, flavor, rating FROM strains WHERE strain = ${db.escape(strainName)}`;
db.query(query, (err, rows, fields) => {
await db.query(query, (err, rows, fields) => {
if (rows != undefined) {
const strainInfo = {
id: `${rows[0].id}`,
@ -503,16 +619,16 @@ const functions = {
}
});
},
strains(client) {
async strains(client) {
const query = 'SELECT id, strain FROM strains';
db.query(query, (err, rows, fields) => {
await db.query(query, (err, rows, fields) => {
if (err) throw err;
functions.collections.strains(rows, client);
});
},
medicalAdvice(client) {
async medicalAdvice(client) {
const query = 'SELECT * FROM medical_advice ORDER BY id ASC';
db.query(query, (err, rows, fields) => {
await db.query(query, (err, rows, fields) => {
if (err) throw err;
functions.collections.medicalAdvice(rows, client);
});
@ -530,6 +646,16 @@ const functions = {
}
}
},
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) {
@ -549,6 +675,7 @@ const functions = {
},
spongebob(commandData) {
let newText = '';
let lastIsUpper = 0;
for (const letter of commandData.args) {
if (letter == ' ') {
newText += letter;
@ -556,21 +683,255 @@ const functions = {
}
if (letter == 'i' || letter == 'I') {
newText += 'i';
lastIsUpper = 0;
continue;
}
if (letter == 'l' || letter == 'L') {
newText += 'L';
lastIsUpper = 1;
continue;
}
if (Math.random() > 0.5) {
if (lastIsUpper === 0) {
newText += letter.toUpperCase();
lastIsUpper = 1;
} else {
newText += letter.toLowerCase();
lastIsUpper = 0;
}
}
return newText + ' <:spongebob:1053398825965985822>';
},
setNick(user, nickname) {
return new Promise((resolve, reject) => {
user.setNickname(nickname).then(() => {
resolve('Nickname set.');
}).catch(err => {
reject(err);
});
});
},
checkBannedServers(client, statusChannel) {
if ( !config.bannedServers || !statusChannel ) return;
const guildChecks = [];
console.log('Guild Check:');
client.guilds.cache.forEach((guild) => {
const p = new Promise((resolve, reject) => {
console.log(` - ${guild.name} (${guild.id})`);
if (config.bannedServers.includes(guild.id)) {
console.log(` Leaving banned server: ${guild.name}`);
statusChannel.send(`<@${process.env.ownerId}> Leaving banned server: ${guild.name}`);
guild.leave().then(resolve).catch(reject);
} else {
resolve();
}
});
guildChecks.push(p);
});
Promise.all(guildChecks).then(() => {
console.log('All guilds checked.');
}).catch((err) => {
console.error(err);
});
},
checkMembership(client, userId) {
if (!client.memberships) throw new Error("Membership Collection Not Exist");
if (client.memberships.has(userId)) {
return true;
} else {
return false;
}
},
addMember(client, userId, username) {
if (!client.memberships) throw new Error("Memberships Collection Not Exist");
client.memberships.set(userId, username);
return;
},
removeMember(client, userId) {
if (!client.memberships) throw new Error("Memberships Collection Not Exist");
if (client.memberships.has(userId)) {
client.memberships.delete(userId);
return;
} else {
return;
}
},
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;
},
async sendAlert(embed, client) {
const statusChannel = await client.channels.fetch(statusChannelId)
.catch(err => {
console.error(err);
});
await statusChannel.send({ embeds: [ embed ] })
.catch(err => {
console.error(err);
});
}
};
module.exports = functions;

220
main.js
View File

@ -6,6 +6,9 @@ dotenv.config();
const token = process.env.TOKEN;
const statusChannelId = process.env.statusChannelId;
// Dump the env vars to log in dev environment:
if (process.env.isDev === "true") console.log(JSON.stringify(process.env));
// Discord.JS
const { Client, Intents } = require('discord.js');
const client = new Client({
@ -27,32 +30,47 @@ const { MessageActionRow, MessageButton } = require('discord.js');
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;
let statusChannel;
client.once('ready', () => {
client.once('ready', async () => {
fn.collections.interactionStorage(client);
fn.collections.slashCommands(client);
fn.collections.dotCommands(client);
fn.collections.setvalidCommands(client);
fn.download.gifs(client);
fn.download.pastas(client);
fn.download.joints(client);
fn.download.requests(client);
fn.download.strains(client);
fn.download.medicalAdvice(client);
fn.collections.roaches(client);
fn.collections.memberships(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!');
client.channels.fetch(statusChannelId).then(channel => {
channel.send(`${new Date().toISOString()} -- <@${process.env.ownerId}>\nStartup Sequence Complete`);
});
await fn.avWx.metar.getAllICAOs();
await fn.avWx.datis.getAllICAOs();
// console.log(JSON.stringify(icaoArray));
statusChannel = await client.channels.fetch(statusChannelId).catch(e => console.error(e));
await statusChannel.send(`${new Date().toISOString()} -- <@${process.env.ownerId}>\nStartup Sequence Complete`).catch(e => console.error(e));
// Check for banned servers
fn.checkBannedServers(client, statusChannel);
});
// slash-commands
client.on('interactionCreate', async interaction => {
if (interaction.isCommand()) {
if (isDev) {
console.log(interaction);
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 {
@ -62,128 +80,46 @@ client.on('interactionCreate', async interaction => {
}
if (interaction.isButton()) {
if (interaction.user.id != strings.temp.gifUserId) return;
// Get some meta info from strings
const index = strings.temp.gifIndex;
const limit = strings.temp.gifLimit;
let newIndex;
const buttonId = interaction.component.customId;
switch (buttonId) {
case 'prevGif':
newIndex = index - 1;
strings.temp.gifIndex = newIndex;
// If we're leaving the last GIF, enable the Next GIF button
if (index == limit) {
// Re-Send Previous GIF button
const prevButton = new MessageButton().setCustomId('prevGif').setLabel('Previous GIF').setStyle('SECONDARY');
// Re-Send Confirm GIF Button
const confirmButton = new MessageButton().setCustomId('confirmGif').setLabel('Confirm').setStyle('PRIMARY');
// Enable Next GIF Button
const nextButton = new MessageButton().setCustomId('nextGif').setLabel('Next GIF').setStyle('SECONDARY');
// Re-Send Cancel Button
const cancelButton = new MessageButton().setCustomId('cancelGif').setLabel('Cancel').setStyle('DANGER');
// Put all the above into an ActionRow to be sent as a component of the reply
const row = new MessageActionRow().addComponents(prevButton, confirmButton, nextButton, cancelButton);
interaction.update({ content: strings.temp.gifs[newIndex].embed_url, components: [row] });
break;
}
// If we're going into the first GIF, disable the Previous GIF button
if (newIndex == 0) {
// Disable Previous GIF button
const prevButton = new MessageButton().setCustomId('prevGif').setLabel('Previous GIF').setStyle('SECONDARY').setDisabled();
// Re-Send Confirm GIF Button
const confirmButton = new MessageButton().setCustomId('confirmGif').setLabel('Confirm').setStyle('PRIMARY');
// Re-Send Next GIF Button
const nextButton = new MessageButton().setCustomId('nextGif').setLabel('Next GIF').setStyle('SECONDARY');
// Re-Send Cancel Button
const cancelButton = new MessageButton().setCustomId('cancelGif').setLabel('Canceled').setStyle('DANGER');
// Put all the above into an ActionRow to be sent as a component of the reply
const row = new MessageActionRow().addComponents(prevButton, confirmButton, nextButton, cancelButton);
interaction.update({ content: strings.temp.gifs[newIndex].embed_url, components: [row] });
break;
}
interaction.update(strings.temp.gifs[newIndex].embed_url);
break;
case 'confirmGif':
const gifData = {
name: strings.temp.gifName,
embed_url: strings.temp.gifs[strings.temp.gifIndex].embed_url,
};
fn.upload.gif(gifData, client);
interaction.update({ content: `I've saved the GIF as ${gifData.name}.gif`, components: [] });
fn.download.gifs(interaction.client);
break;
case 'nextGif':
newIndex = index + 1;
strings.temp.gifIndex = newIndex;
// If we're leaving the first GIF, enable the Previous GIF button
if (index == 0) {
// Enable Previous GIF button
const prevButton = new MessageButton().setCustomId('prevGif').setLabel('Previous GIF').setStyle('SECONDARY').setDisabled(false);
// Re-Send Confirm GIF Button
const confirmButton = new MessageButton().setCustomId('confirmGif').setLabel('Confirm').setStyle('PRIMARY');
// Re-Send Next GIF Button
const nextButton = new MessageButton().setCustomId('nextGif').setLabel('Next GIF').setStyle('SECONDARY');
// Re-Send Cancel Button
const cancelButton = new MessageButton().setCustomId('cancelGif').setLabel('Cancel').setStyle('DANGER');
// Put all the above into an ActionRow to be sent as a component of the reply
const row = new MessageActionRow().addComponents(prevButton, confirmButton, nextButton, cancelButton);
interaction.update({ content: strings.temp.gifs[newIndex].embed_url, components: [row] });
break;
}
// If we're going into the last GIF, disable the Next GIF button
if (newIndex == strings.temp.gifLimit) {
// Re-Send Previous GIF button
const prevButton = new MessageButton().setCustomId('prevGif').setLabel('Previous GIF').setStyle('SECONDARY');
// Re-Send Confirm GIF Button
const confirmButton = new MessageButton().setCustomId('confirmGif').setLabel('Confirm').setStyle('PRIMARY');
// Disable Next GIF Button
const nextButton = new MessageButton().setCustomId('nextGif').setLabel('Next GIF').setStyle('SECONDARY').setDisabled();
// Re-Send Cancel Button
const cancelButton = new MessageButton().setCustomId('cancelGif').setLabel('Canceled').setStyle('DANGER');
// Put all the above into an ActionRow to be sent as a component of the reply
const row = new MessageActionRow().addComponents(prevButton, confirmButton, nextButton, cancelButton);
interaction.update({ content: strings.temp.gifs[newIndex].embed_url, components: [row] });
break;
}
interaction.update(strings.temp.gifs[newIndex].embed_url);
break;
case 'cancelGif':
// Previous GIF button
const prevButton = new MessageButton().setCustomId('prevGif').setLabel('Previous GIF').setStyle('SECONDARY').setDisabled();
// Confirm GIF Button
const confirmButton = new MessageButton().setCustomId('confirmGif').setLabel('Confirm').setStyle('PRIMARY').setDisabled();
// Next GIF Button
const nextButton = new MessageButton().setCustomId('nextGif').setLabel('Next GIF').setStyle('SECONDARY').setDisabled();
// Cancel Button
const cancelButton = new MessageButton().setCustomId('cancelGif').setLabel('Canceled').setStyle('DANGER');
// Put all the above into an ActionRow to be sent as a component of the reply
const row = new MessageActionRow().addComponents(prevButton, confirmButton, nextButton, cancelButton);
interaction.component.setDisabled(true);
interaction.update({ content: 'Canceled.', components: [row] });
break;
default:
break;
}
if (isDev) console.log('Origin Interaction ID: ' + interaction.message.interaction.id);
if (isDev) console.log('Button ID: ' + interaction.component.customId);
ButtonHandlers.baseEvent(interaction);
}
// Handle autocomplete requests
if (interaction.isAutocomplete()) {
if (interaction.commandName == 'strain') {
const searchString = interaction.options.getFocused();
const choices = fn.weed.strain.lookup(searchString, interaction.client);
await interaction.respond(
choices.map(choice => ({ name: choice, value: choice }))
)
} else {
return;
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;
}
}
});
@ -193,17 +129,15 @@ client.on('messageCreate', message => {
// Some basic checking to prevent running unnecessary code
if (message.author.bot) return;
// Wildcard Responses, will respond if any message contains the trigger word(s), excluding self-messages
// Automatic Responses, will respond if any message contains the keyword(s), excluding self-messages
const lowerContent = message.content.toLowerCase();
if (lowerContent.includes('big') && lowerContent.includes('doinks')) message.reply('gang.');
if (lowerContent.includes('ligma')) message.reply('ligma balls, goteem');
if (lowerContent.includes('frfr') || lowerContent.includes('fr fr') || lowerContent.includes('bussin') || lowerContent.includes(' ong') || lowerContent.startsWith('ong')) {
const randIndex = Math.floor(Math.random() * strings.capbacks.length);
message.reply(strings.capbacks[randIndex]);
}
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 = fn.dot.getCommandData(message);
const commandData = new CommandData(message).validate(message.client.dotCommands);
console.log(commandData);
if (commandData.isValid && commandData.isCommand) {
@ -218,4 +152,16 @@ client.on('messageCreate', message => {
return;
});
// Guild Joins and Leaves:
client.on('guildCreate', guild => {
statusChannel.send(`I've been added to a new guild: ${guild.name} (${guild.id})`)
.catch(e => console.error(e));
fn.checkBannedServers(client, statusChannel);
});
client.on('guildDelete', guild => {
statusChannel.send(`I've been removed from a guild: ${guild.name} (${guild.id})`)
.catch(e => console.error(e));
});
client.login(token);

View File

@ -1,7 +1,7 @@
{
"name": "nodbot",
"version": "3.1.0",
"description": "Nods and Nod Accessories.",
"version": "3.6.1",
"description": "Nods and Nod Accessories",
"main": "main.js",
"dependencies": {
"@discordjs/builders": "^0.16.0",
@ -12,10 +12,10 @@
"dotenv": "^10.0.0",
"fuzzy-search": "^3.2.1",
"mysql": "^2.18.1",
"tenorjs": "^1.0.10"
"openai": "^4.12.0"
},
"engines": {
"node": "16.x"
"node": "18.x"
},
"devDependencies": {
"eslint": "^7.32.0"

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

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

View File

@ -1,33 +0,0 @@
const { SlashCommandBuilder } = require('@discordjs/builders');
const { config } = require('dotenv');
const fn = require('../functions.js');
module.exports = {
data: new SlashCommandBuilder()
.setName('gifs')
.setDescription('Get a list of currently saved GIFs.'),
async execute(interaction) {
if (!interaction.client.gifs) {
interaction.reply('For some reason I don\'t have access to the collection of gifs. Sorry about that!');
return;
}
const gifsMap = interaction.client.gifs.map(e => {
return {
id: e.id,
name: e.name,
};
});
const commandData = {
gifs: [],
command: 'gifs',
author: interaction.user.tag,
};
for (const row of gifsMap) {
commandData.gifs.push({
id: row.id,
name: row.name,
});
}
interaction.reply(fn.embeds.gifs(commandData));
},
};

View File

@ -4,29 +4,8 @@ const fn = require('../functions.js');
module.exports = {
data: new SlashCommandBuilder()
.setName('help')
.setDescription('Send the help page.')
// .addStringOption(option =>
// option.setName('location')
// .setDescription('Send help in this channel or in DMs?')
// .setRequired(true)
// .addChoice('Here', 'channel')
// .addChoice('DMs', 'dm'))
,
.setDescription('Send the help page.'),
async execute(interaction) {
// switch (interaction.options.getString('location')) {
// case 'channel':
// await interaction.reply(fn.embeds.help(interaction));
// break;
// case 'dm':
// await interaction.user.createDM().then(channel => {
// channel.send(fn.embeds.help(interaction));
// interaction.reply({content: 'I\'ve sent you a copy of my help page.', ephemeral: true});
// });
// break;
// default:
// interaction.reply('There was an error, please try again.');
// break;
// }
await interaction.reply(fn.embeds.help(interaction));
},
}
};

View File

@ -1,15 +0,0 @@
const { SlashCommandBuilder } = require('@discordjs/builders');
const fn = require('../functions.js');
module.exports = {
data: new SlashCommandBuilder()
.setName('joints')
.setDescription('Send a list of all the /joint phrases.'),
async execute(interaction) {
let joints = [];
interaction.client.joints.map(e => {
joints.push(e.content);
});
interaction.reply({ content: 'Here are all the `.joint` phrases I have saved:\n\n' + joints.join('\n'), ephemeral: true });
},
};

62
slash-commands/member.js Normal file
View File

@ -0,0 +1,62 @@
const { SlashCommandBuilder } = require('@discordjs/builders');
const fn = require('../functions.js');
const strings = require('../strings.json');
module.exports = {
data: new SlashCommandBuilder()
.setName('member')
.setDescription('Manage NodBot paid memberships.')
// Add
.addSubcommand(subcommand =>
subcommand
.setName('add')
.setDescription('Add an authorized paying member.')
.addUserOption(option =>
option
.setName('newmember')
.setDescription('The member to add.')
.setRequired(true)
)
)
// Remove
.addSubcommand(subcommand =>
subcommand
.setName('remove')
.setDescription('Remove an authorized paying member.')
.addUserOption(option =>
option
.setName('newmember')
.setDescription('The member to remove.')
.setRequired(true)
)
),
async execute(interaction) {
await interaction.deferReply();
try {
// Code Here...
const subcommand = interaction.options.getSubcommand();
const newmember = interaction.options.getUser('newmember');
switch (subcommand) {
// Add
case "add":
//TODO
fn.addMember(interaction.client, newmember.id, newmember.username);
interaction.editReply(`Member Added: ${newmember.username} [${newmember.id}]`);
break;
// Remove
case "remove":
fn.removeMember(interaction.client, newmember.id);
interaction.editReply(`Member Removed: ${newmember.username} [${newmember.id}]`);
break;
// Default
default:
interaction.editReply(`No Changes: ${newmember.username} [${newmember.id}]`);
break;
}
} catch (err) {
const errorId = fn.generateErrorId();
console.error(`${errorId}: err`);
await interaction.editReply(`Sorry, an error has occured. Error ID: ${errorId}`);
}
}
};

View File

@ -1,33 +0,0 @@
const { SlashCommandBuilder } = require('@discordjs/builders');
const { config } = require('dotenv');
const fn = require('../functions.js');
module.exports = {
data: new SlashCommandBuilder()
.setName('pastas')
.setDescription('Get a list of currently saved copypastas.'),
async execute(interaction) {
if (!interaction.client.pastas) {
interaction.reply({ content: 'For some reason I don\'t have access to the collection of copypastas. Sorry about that!', ephemeral: true });
return;
}
const commandData = {
author: interaction.user.tag,
command: interaction.commandName,
pastas: [],
};
const pastasMap = interaction.client.pastas.map(e => {
return {
id: e.id,
name: e.name,
};
});
for (const row of pastasMap) {
commandData.pastas.push({
id: row.id,
name: row.name,
});
}
interaction.reply(fn.embeds.pastas(commandData));
},
};

View File

@ -1,39 +0,0 @@
const { SlashCommandBuilder } = require('@discordjs/builders');
const { config } = require('dotenv');
const fn = require('../functions.js');
module.exports = {
data: new SlashCommandBuilder()
.setName('requests')
.setDescription('Get a list of Active requests from the database')
.addStringOption(option =>
option
.setName('page')
.setDescription('Page Number')
.setRequired(true)),
async execute(interaction) {
const pageNum = interaction.options.getString('page');
const commandData = {
author: interaction.user.tag,
command: interaction.commandName,
requests: [],
};
const requestsMap = interaction.client.requests.map(e => {
return {
id: e.id,
author: e.author,
request: e.request,
};
});
for (let i = ( 10 * ( pageNum - 1 ) ); i < ( 10 * pageNum ); i++) {
if (requestsMap[i] != undefined) {
commandData.requests.push({
id: requestsMap[i].id,
author: requestsMap[i].author,
request: requestsMap[i].request,
});
}
}
interaction.reply(fn.embeds.requests(commandData));
},
};

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

@ -0,0 +1,239 @@
const { SlashCommandBuilder } = require('@discordjs/builders');
const { Collection } = 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 Indexer = require('../CustomModules/Indexer.js');
const Embeds = 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 GIPHY 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":
// Grab the inputs from the command
const query = interaction.options.getString('query');
const gifName = interaction.options.getString('name').toLowerCase();
const iStorage = interaction.client.iStorage.get(interaction.id);
iStorage.page = 0;
iStorage.gifsCollection = new Collection();
iStorage.gifName = gifName;
iStorage.query = query;
interaction.editReply("Fuck off this is broken, come back later.");
// Search Tenor for the GIF
// tenor.Search.Query(query, '20').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) {
// iStorage.gifsCollection.set(row.id, row);
// }
// // Generate the initial action row
// const actionRow = customEmbeds.actionRows.gifSearchAR('first');
// // Get the index built
// const gifPage = Indexer(iStorage.gifsCollection, 0, 1);
// gifPage.query = query;
// gifPage.gifName = gifName;
// // Generate the embed
// const gifEmbed = Embeds.core.gifSearch({ author: interaction.user.username }, gifPage);
// interaction.editReply({ embeds: [gifEmbed], 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}`);
}
}
};

View File

@ -1,93 +0,0 @@
const tenor = require('tenorjs').client({
'Key': process.env.tenorAPIKey, // https://tenor.com/developer/keyregistration
'Filter': 'off', // "off", "low", "medium", "high", not case sensitive
'Locale': 'en_US',
'MediaFilter': 'minimal',
'DateFormat': 'D/MM/YYYY - H:mm:ss A',
});
const { SlashCommandBuilder } = require('@discordjs/builders');
const { MessageActionRow, MessageButton } = require('discord.js');
const fn = require('../functions.js');
const strings = require('../strings.json');
module.exports = {
data: new SlashCommandBuilder()
.setName('savegif')
.setDescription('Save a GIF to Nodbot\'s database.')
.addSubcommand(subcommand =>
subcommand
.setName('searchgif')
.setDescription('Search Tenor for a GIF.')
.addStringOption(option =>
option.setName('query')
.setDescription('Search Query')
.setRequired(true))
.addStringOption(option =>
option.setName('name')
.setDescription('What to save the gif as')
.setRequired(true))
)
.addSubcommand(subcommand =>
subcommand
.setName('enterurl')
.setDescription('Specify a URL to save.')
.addStringOption(option =>
option
.setName('url')
.setDescription('URL Link to the GIF')
.setRequired(true))
.addStringOption(option =>
option.setName('name')
.setDescription('What to save the gif as')
.setRequired(true))
),
async execute(interaction) {
if (interaction.options.getSubcommand() == 'searchgif') {
// Previous GIF button
const prevButton = new MessageButton().setCustomId('prevGif').setLabel('Previous GIF').setStyle('SECONDARY').setDisabled(true);
// Confirm GIF Button
const confirmButton = new MessageButton().setCustomId('confirmGif').setLabel('Confirm').setStyle('PRIMARY');
// Next GIF Button
const nextButton = new MessageButton().setCustomId('nextGif').setLabel('Next GIF').setStyle('SECONDARY');
// Cancel Button
const cancelButton = new MessageButton().setCustomId('cancelGif').setLabel('Cancel').setStyle('DANGER');
// Put all the above into an ActionRow to be sent as a component of the reply
const actionRow = new MessageActionRow().addComponents(prevButton, confirmButton, nextButton, cancelButton);
// Get the query
const query = interaction.options.getString('query');
strings.temp.gifName = interaction.options.getString('name').toLowerCase();
// Search Tenor for the GIF
tenor.Search.Query(query, '10').then(res => {
strings.temp.gifs = [];
strings.temp.gifIndex = 0;
strings.temp.gifLimit = res.length - 1;
strings.temp.gifUserId = interaction.user.id;
if (res[0] == undefined) {
interaction.reply('Sorry I was unable to find a GIF of ' + query);
return;
}
for (const row of res) {
strings.temp.gifs.push({
embed_url: row.media[0].gif.url,
});
}
interaction.reply({ content: strings.temp.gifs[0].embed_url, components: [actionRow], ephemeral: true });
});
}
if (interaction.options.getSubcommand() == 'enterurl') {
const gifData = {
name: interaction.options.getString('name'),
embed_url: interaction.options.getString('url'),
};
fn.upload.gif(gifData, interaction.client);
interaction.reply({ content: `I've saved the GIF as ${gifData.name}.gif`, ephemeral: true });
fn.download.gifs(interaction.client);
}
},
};

View File

@ -1,17 +0,0 @@
const { SlashCommandBuilder } = require('@discordjs/builders');
const fn = require('../functions.js');
const { emoji } = require('../strings.json');
module.exports = {
data: new SlashCommandBuilder()
.setName('savejoint')
.setDescription('Save a phrase for /joint!')
.addStringOption(option =>
option.setName('joint-content')
.setDescription('What is the phrase?')
.setRequired(true)),
async execute(interaction) {
fn.upload.joint(interaction.options.getString('joint-content'), interaction.client);
interaction.reply({ content: `The joint has been rolled${emoji.joint}`, ephemeral: true });
},
};

View File

@ -1,17 +0,0 @@
const { SlashCommandBuilder } = require('@discordjs/builders');
const fn = require('../functions.js');
// const { emoji } = require('../strings.json');
module.exports = {
data: new SlashCommandBuilder()
.setName('savemd')
.setDescription('Add medical advice to NodBot\'s Database!')
.addStringOption(option =>
option.setName('advice-content')
.setDescription('What is the advice?')
.setRequired(true)),
async execute(interaction) {
fn.upload.medicalAdvice(interaction.options.getString('advice-content'), interaction.client);
interaction.reply({ content: `The advice has been saved!`, ephemeral: true });
},
};

View File

@ -1,24 +0,0 @@
const { SlashCommandBuilder } = require('@discordjs/builders');
const fn = require('../functions.js');
module.exports = {
data: new SlashCommandBuilder()
.setName('savepasta')
.setDescription('Save a copypasta!')
.addStringOption(option =>
option.setName('pasta-name')
.setDescription('What should the name of the copypasta be?')
.setRequired(true))
.addStringOption(option =>
option.setName('pasta-content')
.setDescription('What is the content of the copypasta?')
.setRequired(true)),
async execute(interaction) {
const pastaData = {
name: interaction.options.getString('pasta-name'),
content: interaction.options.getString('pasta-content'),
};
fn.upload.pasta(pastaData, interaction.client);
interaction.reply({content: `The copypasta has been saved as ${pastaData.name}.pasta`, ephemeral: true });
},
};

View File

@ -1,54 +0,0 @@
const { SlashCommandBuilder } = require('@discordjs/builders');
const fn = require('../functions.js');
const { emoji } = require('../strings.json');
// Strain Name | Type | Effects | Flavor | Rating | Description
module.exports = {
data: new SlashCommandBuilder()
.setName('savestrain')
.setDescription('Store a new Strain in the database!')
.addStringOption(option =>
option.setName('name')
.setDescription('Name of the Strain')
.setRequired(true))
.addStringOption(option =>
option.setName('type')
.setDescription('Indica/Sativa/Hybrid')
.setRequired(true)
.addChoices(
{ name: "Indica", value: "Indica" },
{ name: "Hybrid", value: "Hybrid" },
{ name: "Sativa", value: "Sativa" }
))
.addStringOption(option =>
option.setName('effects')
.setDescription('The effects given by the strain')
.setRequired(false))
.addStringOption(option =>
option.setName('flavor')
.setDescription('Flavor notes')
.setRequired(false))
.addStringOption(option =>
option.setName('rating')
.setDescription('Number of stars')
.setRequired(false))
.addStringOption(option =>
option.setName('description')
.setDescription('Description of the strain')
.setRequired(false)),
async execute(interaction) {
fn.upload.strain(interaction).then(res => {
interaction.reply({
content: `The strain information has been saved. (${interaction.options.getString('name')})`,
ephemeral: true
});
}).catch(err => {
console.log(`E: ${err}`);
interaction.reply({
content: 'There was a problem saving the strain.',
ephemeral: true
});
});
},
};

42
slash-commands/setnick.js Normal file
View File

@ -0,0 +1,42 @@
const { SlashCommandBuilder } = require('@discordjs/builders');
const fn = require('../functions.js');
module.exports = {
data: new SlashCommandBuilder()
.setName('setnick')
.setDescription('Set a user\'s nickname.')
.addUserOption(o =>
o.setName('user')
.setDescription('The user to set the nickname for.')
.setRequired(true)
)
.addStringOption(o =>
o.setName('nickname')
.setDescription('The nickname to set.')
.setRequired(true)
),
async execute(interaction) {
try {
// Defer the reply, with ephemeral set to true
await interaction.deferReply({ ephemeral: true });
// Get the user and nickname from the interaction
const user = interaction.options.getMember('user');
const nickname = interaction.options.getString('nickname');
// Set the nickname
await fn.setNick(user, nickname);
// Reply to the interaction
await interaction.editReply(`Successfully set ${user}'s nickname to ${nickname}`);
} catch (error) {
console.error(error);
if (error.code === 50013) {
return await interaction.editReply('I do not have permission to set the nickname of that user!');
}
if (error.code === 50035) {
return await interaction.editReply('The nickname is too long!');
}
await interaction.editReply('There was an error while executing this command!');
}
},
};

124
slash-commands/view.js Normal file
View File

@ -0,0 +1,124 @@
const { SlashCommandBuilder } = require('@discordjs/builders');
const { Collection } = 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 Indexer = require('../CustomModules/Indexer.js');
const Embeds = require('../CustomModules/Embeds.js');
const { emoji } = strings;
module.exports = {
data: new SlashCommandBuilder()
.setName('view')
.setDescription('View content saved in Nodbot\'s database.')
// GIFs
.addSubcommand(subcommand =>
subcommand
.setName('gifs')
.setDescription('Display all saved GIFs.')
)
// Joints
.addSubcommand(subcommand =>
subcommand
.setName('joints')
.setDescription('Display all saved joints.')
)
// Pastas
.addSubcommand(subcommand =>
subcommand
.setName('pastas')
.setDescription('Display all saved copypastas.')
)
// Requests
.addSubcommand(subcommand =>
subcommand
.setName('requests')
.setDescription('Display all saved requests.')
),
async execute(interaction) {
await interaction.deferReply({ ephemeral: true });
try {
// Code Here...
const subcommand = interaction.options.getSubcommand();
const iStorage = interaction.client.iStorage.get(interaction.id);
const commandData = {
author: interaction.user.username,
command: `/${interaction.commandName} ${subcommand}`
};
switch (subcommand) {
// GIFs
case "gifs":
if (!interaction.client.gifs) {
interaction.editReply('For some reason I don\'t have access to the collection of gifs. Sorry about that!');
return;
}
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`;
}
interaction.editReply(fn.embeds.gifs(commandData, indexedGifs));
break;
// Joints
case "joints":
if (!interaction.client.joints) {
interaction.editReply('For some reason I don\'t have access to the collection of joints. Sorry about that!');
return;
}
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`;
}
interaction.editReply(fn.embeds.joints(commandData, indexedJoints));
break;
// Pastas
case "pastas":
if (!interaction.client.pastas) {
interaction.editReply({ content: 'For some reason I don\'t have access to the collection of copypastas. Sorry about that!', ephemeral: true });
return;
}
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`;
}
interaction.editReply(fn.embeds.pastas(commandData, indexedPastas));
break;
case "requests":
if (!interaction.client.requests) {
interaction.editReply('For some reason I don\'t have access to the collection of requests. Sorry about that!');
return;
}
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`;
}
interaction.editReply(fn.embeds.requests(commandData, indexedRequests));
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}`);
}
}
};

View File

@ -1,8 +1,8 @@
{
"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'"
"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>",
@ -14,13 +14,71 @@
"urls": {
"avatar": "https://cdn.discordapp.com/avatars/513184762073055252/12227aa23a06d5178853e59b72c7487b.webp?size=128"
},
"capbacks": [
"on god?!",
"fr fr?!",
"no cap?!",
"no cap fr",
"bussin fr, no cap",
"ongggg no :billed_cap: fr fr"
],
"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