Compare commits
118 Commits
v1.2.0-dev
...
main
Author | SHA1 | Date | |
---|---|---|---|
f9bb94a4a1 | |||
1575d0047c | |||
d173338d09 | |||
aedbbec6a2 | |||
fa41a5d61e | |||
ac6eca4827 | |||
88cc4eda3c | |||
7291216dcb | |||
36f44f4b41 | |||
825ec725d1 | |||
fb43d09d13 | |||
0463695323 | |||
314b793042 | |||
eca3207d4e | |||
e2658c41e5 | |||
dd313faa60 | |||
ba81f01fa0 | |||
e303651f53 | |||
40fc6838a0 | |||
67483697f0 | |||
1e1d0cddaa | |||
45418e2bd6 | |||
22005ac68f | |||
805c20953c | |||
ba0e4625c0 | |||
11465287dd | |||
36c59f83f4 | |||
4dda58f48e | |||
e85340245c | |||
252593a327 | |||
7999350f1e | |||
87a468b620 | |||
ae602d08de | |||
|
94c3244016 | ||
|
2f9126aa6e | ||
|
486a59715d | ||
|
25859d364d | ||
|
c0cba3e14e | ||
|
e3fb4da9f6 | ||
|
f2560a081e | ||
|
e8f16d8fea | ||
|
7c56381141 | ||
|
a1386dd688 | ||
|
9e5a89eabb | ||
|
d6bab1c2da | ||
|
d117a69893 | ||
|
87c7a8e9ec | ||
|
a27dc1923e | ||
|
c3967e0643 | ||
|
4144dde4d6 | ||
|
e05bd20f1f | ||
|
7003e12fa1 | ||
|
246848e94a | ||
|
a13888c6c1 | ||
|
49747d4b54 | ||
|
e94b8ce433 | ||
|
9a5a457123 | ||
|
c83e94f01d | ||
|
08f76fb996 | ||
|
d1ac674c6b | ||
|
e29a54b0d9 | ||
|
5b7c0c4225 | ||
|
4f0fa1e51f | ||
|
946f05d57b | ||
|
92c06529ff | ||
|
c4fc1a4ba6 | ||
|
b0d6bdcf8a | ||
|
2704cac98f | ||
|
a28957b956 | ||
|
105fb2827f | ||
|
9aa9511fd7 | ||
|
79821d7abc | ||
|
e075a3a5d6 | ||
|
3c01d37bd5 | ||
|
f7230ec0fb | ||
|
1927d3029a | ||
|
41b59f8b92 | ||
|
f3573eeaed | ||
|
44a4d572bb | ||
|
de6b9ec4e4 | ||
|
56d9cacfbe | ||
|
0c28ec437c | ||
|
8d64640193 | ||
|
1cb5e94145 | ||
|
428cbc1597 | ||
|
1c57cb70ae | ||
|
6f3710bc07 | ||
|
0bdca58d03 | ||
|
7121ab5390 | ||
|
2353486150 | ||
|
26b79327be | ||
|
9580a500fd | ||
|
10f8ed9438 | ||
|
024defbbfc | ||
|
27169afcf0 | ||
|
5f37a3e5a3 | ||
|
a44a83beeb | ||
|
d7bd6b5d41 | ||
|
0f8e9a3e10 | ||
|
9c6204b19b | ||
|
02201b2bfa | ||
|
1f115252ca | ||
|
e632dce612 | ||
|
cdb61cf602 | ||
|
b2e0b2b17d | ||
|
e7a76eee65 | ||
|
a70b27b112 | ||
|
1e358e582e | ||
|
d5254dc58a | ||
|
8bc80dfaa8 | ||
|
663ef2297b | ||
|
56be24b5cd | ||
|
ebc31b5a0c | ||
|
1367a90904 | ||
|
26b5728636 | ||
|
b724229f06 | ||
|
0b6161f0e0 | ||
|
5ef905449d |
0
.dockerignore
Normal file → Executable file
0
.dockerignore
Normal file → Executable file
0
.eslintrc.json
Normal file → Executable file
0
.eslintrc.json
Normal file → Executable file
@ -1,8 +1,7 @@
|
||||
name: Silvanus-Dev Dockerization
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "*-dev" ]
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
DHUB_UNAME: ${{ secrets.DHUB_UNAME }}
|
||||
@ -16,9 +15,9 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Build the Docker image
|
||||
- name: Build the Docker image v0idf1sh/silvanus-dev
|
||||
run: docker build . --file Dockerfile --tag v0idf1sh/silvanus-dev
|
||||
- name: Log into Docker Hub
|
||||
run: docker login -u $DHUB_UNAME -p $DHUB_PWORD
|
||||
- name: Push image to Docker Hub
|
||||
run: docker push v0idf1sh/silvanus-dev
|
||||
- name: Push image to Docker Hub v0idf1sh/silvanus-dev
|
||||
run: docker push v0idf1sh/silvanus-dev
|
47
.gitea/workflows/docker-image-prod.yml
Normal file
47
.gitea/workflows/docker-image-prod.yml
Normal file
@ -0,0 +1,47 @@
|
||||
name: Silvanus Production Dockerization
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
env:
|
||||
DHUB_UNAME: ${{ secrets.DHUB_UNAME }}
|
||||
DHUB_PWORD: ${{ secrets.DHUB_PWORD }}
|
||||
|
||||
jobs:
|
||||
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Pull latest from Git
|
||||
run: |
|
||||
pwd
|
||||
whoami
|
||||
mkdir -p /var/lib/act_runner/
|
||||
cd /var/lib/act_runner/
|
||||
if [ ! -d "silvanus" ]; then
|
||||
git clone https://git.vfsh.dev/voidf1sh/silvanus
|
||||
cd silvanus
|
||||
else
|
||||
cd silvanus
|
||||
git pull
|
||||
fi
|
||||
git checkout ${{ gitea.ref}}
|
||||
- name: Build the Docker image
|
||||
run: |
|
||||
cd /var/lib/act_runner/silvanus
|
||||
docker build . --file Dockerfile --tag v0idf1sh/silvanus
|
||||
- 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/silvanus
|
||||
docker push v0idf1sh/silvanus
|
||||
- name: Restart the container
|
||||
run: |
|
||||
cd /srv/docker/silvanus
|
||||
docker-compose down
|
||||
docker-compose up -d
|
24
.github/workflows/docker-image.yml
vendored
24
.github/workflows/docker-image.yml
vendored
@ -1,24 +0,0 @@
|
||||
name: Silvanus Dockerization
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
|
||||
env:
|
||||
DHUB_UNAME: ${{ secrets.DHUB_UNAME }}
|
||||
DHUB_PWORD: ${{ secrets.DHUB_PWORD }}
|
||||
|
||||
jobs:
|
||||
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Build the Docker image
|
||||
run: docker build . --file Dockerfile --tag v0idf1sh/silvanus
|
||||
- name: Log into Docker Hub
|
||||
run: docker login -u $DHUB_UNAME -p $DHUB_PWORD
|
||||
- name: Push image to Docker Hub
|
||||
run: docker push v0idf1sh/silvanus
|
4
.gitignore
vendored
Normal file → Executable file
4
.gitignore
vendored
Normal file → Executable file
@ -2,11 +2,11 @@
|
||||
.vscode
|
||||
package-lock.json
|
||||
.VSCodeCounter/
|
||||
env.dev
|
||||
env.prod
|
||||
.env*
|
||||
.DS_Store
|
||||
data/guildInfo.json
|
||||
data/rawstring.txt
|
||||
modules/input.txt
|
||||
|
||||
# Custom folders
|
||||
# gifs/*
|
||||
|
2
Dockerfile
Normal file → Executable file
2
Dockerfile
Normal file → Executable file
@ -5,4 +5,4 @@ WORKDIR /usr/src/app
|
||||
COPY package.json ./
|
||||
RUN npm install
|
||||
COPY . .
|
||||
CMD [ "node", "main.js" ]
|
||||
CMD ["/bin/sh", "-c", "node main.js 2>&1 > /logs/$(date +%Y-%m-%d_%H-%M-%S).txt"]
|
||||
|
35
README.md
Normal file → Executable file
35
README.md
Normal file → Executable file
@ -1,37 +1,18 @@
|
||||
![Current Uptime Status](https://status.vfsh.dev/api/badge/1/status)
|
||||
![Uptime Percentage](https://status.vfsh.dev/api/badge/1/uptime)
|
||||
# Silvanus
|
||||
Silvanus is the ultimate Grow A Tree companion bot! Quickly compare your server's tree to others on the leaderboard with automatic calculation of tree height differences, active growth detection, watering time calculations, and more!
|
||||
|
||||
Important Note: Silvanus is only as up-to-date as your server's newest Tree and Tallest Trees messages. Make sure to refresh them before refreshing Silvanus' Compare message.
|
||||
|
||||
Silvanus is not affiliated with Grow A Tree or Limbo Labs.
|
||||
|
||||
## Add Silvanus to your server
|
||||
[Invite Silvanus to your Discord Server](https://discord.com/api/oauth2/authorize?client_id=521624335119810561&permissions=274877908992&scope=bot%20applications.commands)
|
||||
[Invite Silvanus to your Discord Server](https://discord.com/api/oauth2/authorize?client_id=521624335119810561&permissions=275146475520&scope=applications.commands%20bot)
|
||||
|
||||
## voidf1sh Development Support Server
|
||||
## Silvanus Support Server
|
||||
[Join Discord Server](https://discord.gg/g5JRGn7PxU)
|
||||
|
||||
## Setup
|
||||
## Silvanus Support Wiki
|
||||
Find the most up-to-date guides and information about Silvanus at the [Silvanus Support Wiki](https://silvanus.vfsh.dev/).
|
||||
[Silvanus Setup Guide](https://silvanus.vfsh.dev/en/setup)
|
||||
|
||||
If your `/tree` and `/top trees` messages are in the same channel, simple run `/compare` in that channel and you're good to go!
|
||||
|
||||
Otherwise, run `/setup` to set the proper channels for the bot to look in for the `/tree` and `/top trees` messages.
|
||||
|
||||
Use `/commands` to view a description of all my commands.
|
||||
|
||||
## Permissions
|
||||
Silvanus requires permissions to `Send Messages` and `Send Messages in Threads` if applicable.
|
||||
|
||||
## Commands
|
||||
* `/setup` - You only need to run this command if your server has its `/tree` and `/top trees` messages in separate channels.
|
||||
* `/setupinfo` - Displays links to the current Tree and Tallest Trees messages configured in your server.
|
||||
* `/compare` - Sends a refreshable embed that calculcates the height difference between your tree and the trees currently displayed on your Tallest Trees message. There is also an Active Growth Indicator (`[💧]`).
|
||||
* `/setping` - Guild members with the `Manage Roles` permission can run this command to set up automatic reminders when your tree is ready to be watered.
|
||||
* Type a reminder message (including any `@pings` desired) and select a channel to send the reminders in.
|
||||
* Once this command has been run a new `Reset Ping` button will appear next time you refresh the `/compare` message.
|
||||
* Click the `Reset Ping` button to be sent a reminder the next time the tree is ready to be watered.
|
||||
* Use `/optout` to disable reminder messages for your server.
|
||||
* `/watertime` - Calculates the wait time between waters for a tree of a given height.
|
||||
* `/timetoheight` - Calculates how long it would take to go from `beginheight` to `endheight`.
|
||||
* `/reset` - Removes your server's configuration from the database.
|
||||
* `/help` - Displays the bot's help page and links to each command.
|
||||
Please find the most up-to-date guides and information at the Silvanus Support Discord Server!
|
24
TODO.md
Normal file → Executable file
24
TODO.md
Normal file → Executable file
@ -1,10 +1,3 @@
|
||||
## In Progress
|
||||
☑ Switch `/setup` to ask for the tree and leaderboard channels
|
||||
* Switch `/compare` to check for newer trees and leaderboards when run **and** on every refresh
|
||||
|
||||
## Future Ideas
|
||||
* Go through and comment the code
|
||||
|
||||
## Variable Structures
|
||||
|
||||
guildInfo = {
|
||||
@ -21,7 +14,18 @@ guildInfo = {
|
||||
reminderOptIn: 0,
|
||||
}
|
||||
|
||||
## Expected Behaviors
|
||||
## New Table Planning
|
||||
Table: `silvanus`.timers
|
||||
|
||||
* Run `/compare` before `/setup`: `/compare` will search the current channel for tree and leaderboard messages, then create a comparison embed. If it can't find `/tree` or `/top trees` messages, it'll return an error saying as much.
|
||||
* Run `/compare` after `/setup`: ``/compare` will search the current channel for tree and leaderboard messages, then create a comparison embed. If it can't find `/tree` or `/top trees` messages, it'll just use old data silently (odds are `/compare` is being run from another channel, that's fine)
|
||||
id | INT | NOT NULL | AUTO INCREMENT | PRIMARY KEY
|
||||
status | VARCHAR(10) | NOT NULL | DEFAULT "WAITING"
|
||||
dc_timecode | INT | NOT NULL | Discord timecode
|
||||
guild_id | INT UNSIGNED | NOT NULL | Discord guild ID for referencing `silvanus`.guildInfo
|
||||
|
||||
|
||||
Table: `silvanus`.auto_role_status
|
||||
|
||||
id | INT | NOT NULL | AUTO INCREMENT | PRIMARY KEY
|
||||
user_id | INT UNSIGNED | NOT NULL
|
||||
guild_id | INT UNSIGNED | NOT NULL
|
||||
status | VARCHAR(10) | NOT NULL | DEFAULT "REMOVED" | OPTION: {"REMOVED", "ADDED"}
|
10
data/config.json
Normal file → Executable file
10
data/config.json
Normal file → Executable file
@ -5,5 +5,11 @@
|
||||
"treeHeight": 0,
|
||||
"validCommands": [],
|
||||
"rankMessageId": "",
|
||||
"rankings": []
|
||||
}
|
||||
"rankings": [],
|
||||
"devTeamIds": [
|
||||
"481933290912350209",
|
||||
"269249959973355520",
|
||||
"448606738669633536"
|
||||
],
|
||||
"ownerId": "481933290912350209"
|
||||
}
|
||||
|
58
data/strings.json
Normal file → Executable file
58
data/strings.json
Normal file → Executable file
@ -4,10 +4,13 @@
|
||||
},
|
||||
"help": {
|
||||
"title": "Silvanus Help",
|
||||
"info": "Silvanus is the ultimate Grow A Tree companion bot! Quickly compare your server's tree to others on the leaderboard with automatic calculation of tree height differences, active growth detection, watering time calculations, and more!\n\nImportant Note: Silvanus is only as up-to-date as your server's newest Tree and Tallest Trees messages. Make sure to refresh them before refreshing Silvanus' Compare message.",
|
||||
"setup": "If your ``/tree`` and ``/top trees`` messages are in the same channel, simple run </compare:1065346941166297128> in that channel and you're good to go!\n\nOtherwise, run </setup:1065407649363005561> to set the proper channels for the bot to look in for the ``/tree`` and ``/top trees`` messages.\n\nUse </commands:1069501270454456331> to view a description of all my commands.",
|
||||
"permissions": "At a minimum, Silvanus requires permissions to `Send Messages` and `Send Messages in Threads` if applicable. If Analyzer is given permission to `Manage Messages`, the bot will delete the `.settree` and `.setranks` messages to reduce spam.",
|
||||
"allCommands": "</setup:1065407649363005561> - You only need to run this command if your server has its ``/tree`` and ``/top trees`` messages in separate channels.\n</setupinfo:1065413032374706196> - Displays links to the current Tree and Tallest Trees messages configured in your server.\n</compare:1065346941166297128> - Sends a refreshable embed that calculcates the height difference between your tree and the trees currently displayed on your Tallest Trees message. There is also an Active Growth Indicator (``[💧]``).\n</setping:1068373237995683902> - Guild members with the ``Manage Roles`` permission can run this command to set up automatic reminders when your tree is ready to be watered.\n Type a reminder message (including any ``@pings`` desired) and select a channel to send the reminders in.\n Once this command has been run a new ``Reset Ping`` button will appear next time you refresh the </compare:1065346941166297128> message.\n Click the ``Reset Ping`` button to be sent a reminder the next time the tree is ready to be watered.\n Use </optout:1068753032801693758> to disable reminder messages for your server.\n</watertime:1066970330029113444> - Calculates the wait time between waters for a tree of a given height.\n</timetoheight:1067727254634889227> - Calculates how long it would take to go from ``beginheight`` to ``endheight``.\n</reset:1065412317052944476> - Removes your server's configuration from the database.\n</help:1065346941166297129> - Displays the bot's help page and links to each command."
|
||||
"aboutTitle": "About Silvanus",
|
||||
"info": "Silvanus is the ultimate companion bot designed for Grow A Tree, the popular Discord clicker game. With Silvanus by your side, you can take your tree-growing adventure to new heights! Enjoy customizable notifications, effortless tree height comparisons, and handy math features to optimize your gameplay.\n\n[Privacy Policy](https://assets.vfsh.dev/privacy.txt)",
|
||||
"topggTitle": "Want to support Silvanus?",
|
||||
"topggBody": "Vote and leave a review for Silvanus [on Top.gg](https://top.gg/bot/521624335119810561)",
|
||||
"setup": "For the most up to date guides and information, check out the Silvanus Support Wiki - https://silvanus.vfsh.dev/\n[Silvanus Setup Guide](https://silvanus.vfsh.dev/en/setup)\n\nNeed help and can't reach me on Discord? Send an email to SilvanusDev@gmail.com and I'll get back to you ASAP.",
|
||||
"longDescription": "Silvanus, the ultimate companion bot for Grow A Tree, takes your tree-growing journey to the next level. Are you tired of cluttered channels filled with notifications? Silvanus has you covered! With its unique notification relay system, Silvanus listens for Grow A Tree's notifications in a hidden channel and sends customized, auto-deleting notifications to the channels of your choice. Customize your notifications and keep your server clean and organized.\n\nSilvanus simplifies leaderboard tree height comparisons with a simple command. No more manual calculations or guesswork. Silvanus shows you exactly how far you are from the next tree on the leaderboard, allowing you to gauge your progress effortlessly.",
|
||||
"allCommands": "</compare:1077058896469966889> - Compare your tree to others on the leaderboard\n</relay set:1077322799032578152> - Setup a Notification Relay for the first time\n</relay update:1077322799032578152> - Update an already configured Notification Relay\n</relay disable:1077322799032578152> - Disable the Notification Relay\n</rolemenu:1077058896469966892> - Send a self-assignable role menu for relay pings\n</watertime:1077058896469966895> - Calculates the time between waters for a tree of a given height\n</timetoheight:1077058896469966894> - Calculates how long it would take a tree to grow to a given height\n</setup compare:1077058896469966893> - Set the channels to use with </compare:1077058896469966889>\n</setup view:1077058896469966893> - View your server's configuration\n</setup reset:1077058896469966893> - Delete your server's configuration\n</help:1077058896469966890> - Displays the bot's help page"
|
||||
},
|
||||
"commands": {
|
||||
"compare": "</compare:1065346941166297128>",
|
||||
@ -19,21 +22,47 @@
|
||||
},
|
||||
"embeds": {
|
||||
"footer": "Silvanus is not affiliated with Grow A Tree or Limbo Labs",
|
||||
"color": "0x55FF55"
|
||||
"color": 5635925,
|
||||
"errorTitle": "Oops!",
|
||||
"errorPrefix": "There seems to have been a problem.",
|
||||
"waterColor": 5592575,
|
||||
"fruitColor": 13391189,
|
||||
"waterTitle": "Water Notification",
|
||||
"fruitTitle": "Fruit Notification",
|
||||
"roleMenuTitle": "Role Menu",
|
||||
"treeRoleMenu": [
|
||||
"Use the buttons below to give yourself roles.\n\n",
|
||||
"``💧`` - ",
|
||||
": Get notifications when the tree is ready to be watered.",
|
||||
"\n``🍎`` - ",
|
||||
": Get notifications when fruit is falling from the tree."
|
||||
],
|
||||
"roleMenuFooter": "Tip: Tap the button again to remove the role."
|
||||
},
|
||||
"emoji": {
|
||||
"joint": "<:joint:862082955902976000>",
|
||||
"next": "⏭️",
|
||||
"previous": "⏮️",
|
||||
"confirm": "☑️",
|
||||
"cancel": "❌"
|
||||
"cancel": "❌",
|
||||
"water": "💧",
|
||||
"fruit": "🍎"
|
||||
},
|
||||
"urls": {
|
||||
"avatar": "https://cdn.discordapp.com/avatars/513184762073055252/12227aa23a06d5178853e59b72c7487b.webp?size=128",
|
||||
"supportServer": "https://discord.gg/g5JRGn7PxU"
|
||||
},
|
||||
"error": {
|
||||
"noGuild": "Setup has not been completed yet. Try running </setup:1065407649363005561> or </help setup:1065346941166297129>"
|
||||
"noGuild": "I was unable to find an entry for your server in the database. Try using the `/setup rolemenu` or `/relay` commands to generate a new entry. If that fails, try `/setup reset`, then kick me and re-add me. If you continue receiving this error, join the Support Server or DM @vfsh",
|
||||
"invalidSubcommand": "Invalid subcommand detected.",
|
||||
"noTreeMessage": "</tree:0> - Make sure you've sent or refreshed a Tree recently.",
|
||||
"noLeaderboardMessage": "</top trees:0> - Make sure you've sent or refreshed the Tallest Trees leaderboard recently.",
|
||||
"noCompareMessage": "</compare:0> - This is awkward, I've lost my own comparison message!",
|
||||
"noFetchRole": "I was unable to find that role, please make sure it exists and I have access to it.",
|
||||
"noGiveRole": "I was unable to give that role to you, please make sure I have permission to `Manage Roles` and that the role is below my role in the server settings role list.",
|
||||
"noTakeRole": "I was unable to remove that role from you, please make sure I have permission to `Manage Roles` and that the role is below my role in the server settings role list.",
|
||||
"yesGiveRole": "Successfully added the role to your profile!",
|
||||
"yesTakeRole": "Successfully removed the role from your profile!"
|
||||
},
|
||||
"status": {
|
||||
"treeAndLeaderboard": "Tree and leaderboard messages were both found, setup is complete. Run </setupinfo:1065413032374706196> to verify. Run </compare:1065346941166297128> to get started!",
|
||||
@ -42,7 +71,18 @@
|
||||
"missingLeaderboardMessage": "There was a problem finding the Tallest Trees message. Please make sure the ``/tree`` and ``/top trees`` messages are in this channel, or run </setup:1065407649363005561> to set the ``/tree`` and ``/top trees`` channels.",
|
||||
"missingLeaderboardChannel": "There was a problem finding the Tallest Trees channel, was it deleted? Please make sure the ``/tree`` and ``/top trees`` messages are in this channel, or run </setup:1065407649363005561> to set the ``/tree`` and ``/top trees`` channels.",
|
||||
"missingTreeMessage": "There was a problem finding the Tree message. Please make sure the ``/tree`` and ``/top trees`` messages are this channel, or run </setup:1065407649363005561> to set the ``/tree`` and ``/top trees`` channels.",
|
||||
"missingTreeChannel": "There was a problem finding the Tree channel, was it deleted? Please make sure the ``/tree`` and ``/top trees`` messages are in this channel, or run </setup:1065407649363005561> to set the ``/tree`` and ``/top trees`` channels."
|
||||
"missingTreeChannel": "There was a problem finding the Tree channel, was it deleted? Please make sure the ``/tree`` and ``/top trees`` messages are in this channel, or run </setup:1065407649363005561> to set the ``/tree`` and ``/top trees`` channels.",
|
||||
"reset": "All guild configuration information has been removed from the database.",
|
||||
"resetError": "There was a problem deleting your guild information, contact @voidf1sh#0420 for help.",
|
||||
"noRoleMenu": "A role menu has not been created for this guild yet. Run </setup rolemenu:0> to create a role menu.",
|
||||
"optout": "Notification relay has been disabled, to re-enable the relay use </relay update:0> with no options."
|
||||
},
|
||||
"notifications": {
|
||||
"water": "is ready to be watered again!",
|
||||
"fruit": "Fruit is appearing!"
|
||||
},
|
||||
"ids": {
|
||||
"growATree": "972637072991068220"
|
||||
},
|
||||
"temp": {}
|
||||
}
|
||||
}
|
||||
|
18
dot-commands/_template
Normal file
18
dot-commands/_template
Normal file
@ -0,0 +1,18 @@
|
||||
const fn = require('../modules/functions.js');
|
||||
|
||||
module.exports = {
|
||||
name: "",
|
||||
description: "",
|
||||
usage: "",
|
||||
permission: "devTeam", // "devTeam" or "owner"
|
||||
async execute(message, commandData) {
|
||||
if (fn.dotCommands.checkPermissions(this.permission, message.author.id)) {
|
||||
try {
|
||||
// Code Here
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
await message.reply(fn.builders.errorEmbed("There was an error running the command."));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
27
dot-commands/leave.js
Normal file
27
dot-commands/leave.js
Normal file
@ -0,0 +1,27 @@
|
||||
const fn = require('../modules/functions.js');
|
||||
|
||||
module.exports = {
|
||||
name: "leave",
|
||||
description: "Leave a server",
|
||||
usage: "<serverID> [<serverID>]".leave,
|
||||
permission: "owner",
|
||||
async execute(message, commandData) {
|
||||
if (fn.dotCommands.checkPermissions(this.permission, message.author.id)) {
|
||||
try {
|
||||
// Code Here
|
||||
const serverIds = commandData.args.split(" ");
|
||||
for (let i = 0; i < serverIds.length; i++) {
|
||||
const id = serverIds[i];
|
||||
const guild = await message.client.guilds.fetch(id).catch(e => {
|
||||
if (!(e.status === 404)) throw e;
|
||||
});
|
||||
await guild.leave();
|
||||
await message.channel.send("Left Guild: " + id);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
await message.reply(fn.builders.errorEmbed("There was an error running the command: " + err));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
23
dot-commands/message.js
Normal file
23
dot-commands/message.js
Normal file
@ -0,0 +1,23 @@
|
||||
const fn = require('../modules/functions.js');
|
||||
|
||||
module.exports = {
|
||||
name: "message",
|
||||
description: "Send a message to a server owner or server",
|
||||
usage: "<serverID> <content>".message,
|
||||
permission: "owner",
|
||||
async execute(message, commandData) {
|
||||
if (fn.dotCommands.checkPermissions(this.permission, message.author.id)) {
|
||||
try {
|
||||
// Code Here
|
||||
args = commandData.args.split(" ");
|
||||
const guildOwnerId = args.shift();
|
||||
const content = args.join(" ");
|
||||
const dmChannel = await message.client.users.createDM(guildOwnerId);
|
||||
await dmChannel.send(content);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
await message.reply(fn.builders.errorEmbed("There was an error running the command: " + err));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
36
dot-commands/permissions.js
Normal file
36
dot-commands/permissions.js
Normal file
@ -0,0 +1,36 @@
|
||||
const fn = require('../modules/functions.js');
|
||||
const { PermissionsBitField } = require('discord.js');
|
||||
|
||||
module.exports = {
|
||||
name: "permissions",
|
||||
description: "",
|
||||
usage: ".permissions",
|
||||
permission: "devTeam", // "devTeam" or "owner"
|
||||
async execute(message, commandData) {
|
||||
if (fn.dotCommands.checkPermissions(this.permission, message.author.id)) {
|
||||
try {
|
||||
const me = message.guild.members.me;
|
||||
const guildPerms = me.permissions;
|
||||
const manageRoles = guildPerms.has(PermissionsBitField.Flags.ManageRoles);
|
||||
const mentionEveryone = guildPerms.has(PermissionsBitField.Flags.MentionEveryone);
|
||||
const channelPerms = me.permissionsIn(message.channel);
|
||||
const viewChannel = channelPerms.has(PermissionsBitField.Flags.ViewChannel);
|
||||
const sendMessages = channelPerms.has(PermissionsBitField.Flags.SendMessages);
|
||||
const responseParts = [
|
||||
`This is the status of my permissions in this server and this channel (<#${message.channel.id}>)`,
|
||||
`**Guild Permissions**`,
|
||||
`Manage Roles: ${manageRoles}`,
|
||||
`Mention All Roles: ${mentionEveryone}`,
|
||||
`**Channel Permissions**`,
|
||||
`View Channel: ${viewChannel}`,
|
||||
`Send Messages: ${sendMessages}`
|
||||
];
|
||||
const replyEmbed = fn.builders.embed(responseParts.join("\n"));
|
||||
await message.reply(replyEmbed);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
await message.reply(fn.builders.errorEmbed("There was an error running the command."));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
18
dot-commands/ping.js
Normal file
18
dot-commands/ping.js
Normal file
@ -0,0 +1,18 @@
|
||||
const fn = require('../modules/functions.js');
|
||||
|
||||
module.exports = {
|
||||
name: "ping",
|
||||
description: "pong",
|
||||
usage: "ping pong",
|
||||
permission: "everyone",
|
||||
async execute(message, commandData) {
|
||||
if (fn.dotCommands.checkPermissions(this.permission, message.author.id)) {
|
||||
try {
|
||||
await message.reply("Pong!");
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
await message.reply(fn.builders.errorEmbed("There was an error running the command."));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
22
dot-commands/servers.js
Normal file
22
dot-commands/servers.js
Normal file
@ -0,0 +1,22 @@
|
||||
const fn = require('../modules/functions.js');
|
||||
|
||||
module.exports = {
|
||||
name: "servers",
|
||||
description: "Get a list of servers the bot is in",
|
||||
usage: ".servers",
|
||||
permission: "owner",
|
||||
async execute(message, commandData) {
|
||||
if (fn.dotCommands.checkPermissions(this.permission, message.author.id)) {
|
||||
try {
|
||||
let servers = [];
|
||||
const count = JSON.stringify(message.client.guilds.cache.size);
|
||||
servers.push("I'm currently in " + count + " servers.");
|
||||
const guilds = await message.client.guilds.cache;
|
||||
await message.reply(servers.join("\n"));
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
await message.reply(fn.builders.errorEmbed("There was an error running the command."));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
24
dot-commands/setupview.js
Normal file
24
dot-commands/setupview.js
Normal file
@ -0,0 +1,24 @@
|
||||
const fn = require('../modules/functions.js');
|
||||
|
||||
module.exports = {
|
||||
name: "setupview",
|
||||
description: "",
|
||||
usage: "",
|
||||
permission: "devTeam",
|
||||
async execute(message, commandData) {
|
||||
// Code here...
|
||||
if (fn.dotCommands.checkPermissions(this.permission, message.author.id)) {
|
||||
try {
|
||||
if (message.client.guildInfos.has(message.guildId)) {
|
||||
let guildInfo = message.client.guildInfos.get(message.guildId);
|
||||
await message.reply(fn.builders.embed(guildInfo.generateSetupInfo()));
|
||||
} else {
|
||||
await message.reply(fn.builders.errorEmbed("Guild doesn't exist in database!"));
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
await message.reply(fn.builders.errorEmbed("There was an error running the command."));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
195
main.js
Normal file → Executable file
195
main.js
Normal file → Executable file
@ -4,7 +4,9 @@
|
||||
const dotenv = require('dotenv');
|
||||
dotenv.config();
|
||||
const token = process.env.TOKEN;
|
||||
const statusChannelId = process.env.statusChannelId;
|
||||
const statusChannelId = process.env.STATUSCHANNELID;
|
||||
const heartbeatUrl = process.env.HEARTBEAT_URL;
|
||||
const sendHeartbeat = typeof heartbeatUrl === 'string';
|
||||
|
||||
// Discord.JS
|
||||
const { Client, GatewayIntentBits, Partials, ActivityType } = require('discord.js');
|
||||
@ -25,62 +27,175 @@ const client = new Client({
|
||||
const fn = require('./modules/functions.js');
|
||||
const strings = require('./data/strings.json');
|
||||
const dbfn = require('./modules/dbfn.js');
|
||||
const isDev = process.env.isDev;
|
||||
const { GuildInfo } = require('./modules/CustomClasses.js');
|
||||
const isDev = process.env.DEBUG === "true";
|
||||
let statusChannel;
|
||||
|
||||
client.once('ready', () => {
|
||||
fn.collections.slashCommands(client);
|
||||
client.once('ready', async () => {
|
||||
fn.collectionBuilders.slashCommands(client);
|
||||
fn.collectionBuilders.dotCommands(client);
|
||||
fn.collectionBuilders.setvalidCommands(client);
|
||||
await fn.collectionBuilders.guildInfos(client);
|
||||
await fn.collectionBuilders.messageCollectors(client);
|
||||
const serverCount = client.guilds.cache.size;
|
||||
// checkRateLimits();
|
||||
console.log('Ready!');
|
||||
client.user.setActivity({ name: strings.activity.name, type: ActivityType.Watching });
|
||||
fn.checkReady(client);
|
||||
client.user.setActivity({ name: `${serverCount} trees grow.`, type: ActivityType.Watching });
|
||||
statusChannel = await client.channels.fetch(statusChannelId);
|
||||
if (isDev == 'false') {
|
||||
client.channels.fetch(statusChannelId).then(channel => {
|
||||
channel.send(`${new Date().toISOString()} -- \nStartup Sequence Complete <@481933290912350209>`);
|
||||
});
|
||||
statusChannel.send(`${new Date().toISOString()} -- \nStartup Sequence Complete <@481933290912350209>`);
|
||||
}
|
||||
|
||||
// Heartbeat Timer
|
||||
if (sendHeartbeat) {
|
||||
setInterval(() => {
|
||||
fn.sendHeartbeat(heartbeatUrl);
|
||||
}, 30000);
|
||||
if (isDev) console.log("Heartbeat interval set.");
|
||||
} else {
|
||||
if (isDev) console.log("No heartbeat URL set, will not send heartbeats for uptime monitoring.");
|
||||
}
|
||||
});
|
||||
|
||||
// slash-commands
|
||||
client.on('interactionCreate', async interaction => {
|
||||
if (interaction.isCommand()) {
|
||||
// if (isDev) {
|
||||
// console.log(interaction);
|
||||
// }
|
||||
const { commandName } = interaction;
|
||||
|
||||
if (client.slashCommands.has(commandName)) {
|
||||
client.slashCommands.get(commandName).execute(interaction);
|
||||
try {
|
||||
if (interaction.isCommand()) {
|
||||
if (isDev) {
|
||||
// console.log(interaction);
|
||||
}
|
||||
const { commandName } = interaction;
|
||||
|
||||
if (client.slashCommands.has(commandName)) {
|
||||
client.slashCommands.get(commandName).execute(interaction);
|
||||
} else {
|
||||
interaction.reply('Sorry, I don\'t have access to that command.');
|
||||
console.error('Slash command attempted to run but not found: ' + commandName);
|
||||
}
|
||||
}
|
||||
|
||||
if (interaction.isButton()) {
|
||||
switch (interaction.component.customId) {
|
||||
case 'refresh':
|
||||
// console.log(JSON.stringify(interaction));
|
||||
await fn.refresh(interaction).catch(err => {
|
||||
interaction.channel.send(fn.builders.errorEmbed("Oops! Something went wrong!"));
|
||||
});
|
||||
break;
|
||||
case 'deleteping':
|
||||
if (interaction.message.deletable) {
|
||||
await interaction.message.delete().catch(err => {
|
||||
// console.error(err);
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'waterpingrole':
|
||||
const waterPingStatus = await fn.buttonHandlers.waterPing(interaction);
|
||||
await interaction.reply(waterPingStatus).catch(e => console.error(e));
|
||||
break;
|
||||
case 'fruitpingrole':
|
||||
const fruitPingStatus = await fn.buttonHandlers.fruitPing(interaction);
|
||||
await interaction.reply(fruitPingStatus).catch(e => console.error(e));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch(err) {
|
||||
if (err === "Guild doesn't exist in database!") {
|
||||
interaction.channel.send(fn.builders.errorEmbed(strings.error.noGuild));
|
||||
console.error(err);
|
||||
} else {
|
||||
interaction.reply('Sorry, I don\'t have access to that command.');
|
||||
console.error('Slash command attempted to run but not found: ' + commandName);
|
||||
interaction.channel.send("Oops! An error occurred... Sorry about that, please contact my owner @vfsh if this keeps happening.");
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
if (interaction.isButton() && interaction.component.customId == 'refresh') {
|
||||
// console.log(JSON.stringify(interaction));
|
||||
await fn.refresh(interaction).catch(err => {
|
||||
interaction.channel.send(fn.builders.errorEmbed(err));
|
||||
});
|
||||
} else if (interaction.isButton() && interaction.component.customId == 'resetping') {
|
||||
await fn.resetPing(interaction);
|
||||
await fn.refresh(interaction).catch(err => {
|
||||
interaction.channel.send(fn.builders.errorEmbed(err));
|
||||
});
|
||||
} else if (interaction.isButton() && interaction.component.customId == 'deleteping') {
|
||||
if (interaction.message.deletable) {
|
||||
await dbfn.setRemindedStatus(interaction.guildId, 0);
|
||||
await dbfn.getGuildInfo(interaction.guildId).then(async res => {
|
||||
const guildInfo = res.data;
|
||||
await fn.refreshComparisonMessage(interaction.client, guildInfo);
|
||||
});
|
||||
await interaction.message.delete().catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
client.on('messageUpdate', async (oldMessage, message) => {
|
||||
await fn.messages.updateHandler(message).catch(async e => {
|
||||
switch (e) {
|
||||
case strings.error.noCompareMessage:
|
||||
await message.channel.send(strings.error.noCompareMessage);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
client.on('messageCreate', async message => {
|
||||
await fn.messages.updateHandler(message).catch(e => console.error(e));
|
||||
|
||||
// Dot Command Handling
|
||||
// Some basic checking to prevent running unnecessary code
|
||||
if (message.author.bot) return;
|
||||
|
||||
// Break the message down into its components and analyze it
|
||||
const commandData = fn.dotCommands.getCommandData(message);
|
||||
// if (isDev) console.log(commandData);
|
||||
|
||||
if (commandData.isValid && commandData.isCommand) {
|
||||
try {
|
||||
client.dotCommands.get(commandData.command).execute(message, commandData);
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error);
|
||||
message.reply('There was an error trying to execute that command.');
|
||||
}
|
||||
}
|
||||
return;
|
||||
});
|
||||
|
||||
client.on('guildCreate', async guild => {
|
||||
const serverCount = client.guilds.cache.size;
|
||||
client.user.setActivity({ name: `${serverCount} trees grow.`, type: ActivityType.Watching });
|
||||
await statusChannel.send(`I've been added to a new guild: ${guild.name} (${guild.id})`);
|
||||
const guildInfo = new GuildInfo()
|
||||
.setIds(guild.id, guild.ownerId);
|
||||
const setBasicQuery = guildInfo.queryBuilder("setBasic");
|
||||
await dbfn.setGuildInfo(setBasicQuery).catch(e => console.error(e));
|
||||
});
|
||||
|
||||
client.on('guildDelete', async guild => {
|
||||
const serverCount = client.guilds.cache.size;
|
||||
client.user.setActivity({ name: `${serverCount} trees grow.`, type: ActivityType.Watching });
|
||||
await statusChannel.send(`I've been removed from a guild: ${guild.name} (${guild.id})`);
|
||||
if (client.guildInfos.has(guild.id)) {
|
||||
let guildInfo = client.guildInfos.get(guild.id);
|
||||
guildInfo.setReminders(undefined, undefined, undefined, undefined, false);
|
||||
const setRemindersQuery = guildInfo.queryBuilder("setReminders");
|
||||
await dbfn.setGuildInfo(setRemindersQuery);
|
||||
}
|
||||
});
|
||||
|
||||
async function checkRateLimits(hi) {
|
||||
const axios = require('axios');
|
||||
|
||||
// Make a GET request to the Discord API
|
||||
await axios.get('https://discord.com/api/v10/users/@me', {
|
||||
headers: {
|
||||
'Authorization': `Bot ${token}`
|
||||
}
|
||||
}).then(response => {
|
||||
// Get the rate limit headers
|
||||
const remaining = response.headers['x-ratelimit-remaining'];
|
||||
const reset = response.headers['x-ratelimit-reset'];
|
||||
|
||||
// Log the rate limit headers
|
||||
console.log(`Remaining requests: ${remaining}`);
|
||||
console.log(`Reset time (Unix epoch seconds): ${reset}`);
|
||||
}).catch(error => {
|
||||
console.error(error);
|
||||
});
|
||||
await fn.sleep(500).then(async () =>{
|
||||
await checkRateLimits();
|
||||
})
|
||||
}
|
||||
|
||||
process.on('unhandledRejection', error => {
|
||||
console.error('Unhandled promise rejection:', error);
|
||||
console.error('Unhandled promise rejection (pls dont break up with me):', error);
|
||||
});
|
||||
|
||||
client.login(token);
|
244
modules/CustomClasses.js
Executable file
244
modules/CustomClasses.js
Executable file
@ -0,0 +1,244 @@
|
||||
const mysql = require('mysql');
|
||||
const db = mysql.createConnection({
|
||||
host: process.env.DBHOST,
|
||||
user: process.env.DBUSER,
|
||||
password: process.env.DBPASS,
|
||||
database: process.env.DBNAME,
|
||||
port: process.env.DBPORT
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
GuildInfo: class {
|
||||
constructor() {
|
||||
this.guildId = "";
|
||||
this.ownerId = ""; // TODO Is ownerId fully implemented?
|
||||
this.treeName = "";
|
||||
this.treeHeight = 0;
|
||||
this.treeMessageId = "";
|
||||
this.treeChannelId = "";
|
||||
this.leaderboardMessageId = "";
|
||||
this.leaderboardChannelId = "";
|
||||
this.waterMessage = "";
|
||||
this.waterRoleId = "";
|
||||
this.fruitMessage = "";
|
||||
this.fruitRoleId = "";
|
||||
this.reminderChannelId = "";
|
||||
this.watchChannelId = "";
|
||||
this.notificationsEnabled = false;
|
||||
this.compareChannelId = "";
|
||||
this.compareMessageId = "";
|
||||
}
|
||||
|
||||
setIds(guildId, ownerId) {
|
||||
this.guildId = guildId;
|
||||
this.ownerId = ownerId === undefined ? this.ownerId : ownerId
|
||||
return this;
|
||||
}
|
||||
setName(name) {
|
||||
this.treeName = name;
|
||||
return this;
|
||||
}
|
||||
setHeight(height) {
|
||||
this.treeHeight = height;
|
||||
return this;
|
||||
}
|
||||
setTreeMessage(messageId, channelId) {
|
||||
this.treeMessageId = messageId ? messageId : this.treeMessageId;
|
||||
this.treeChannelId = channelId;
|
||||
return this;
|
||||
}
|
||||
setTreeInfo(name, height, channelId, messageId) {
|
||||
this.treeName = name ? name : this.treeName;
|
||||
this.treeHeight = height;
|
||||
this.treeChannelId = channelId ? channelId : this.treeChannelId;
|
||||
this.treeMessageId = messageId ? messageId : this.treeMessageId;
|
||||
return this;
|
||||
}
|
||||
setLeaderboardMessage(messageId, channelId) {
|
||||
this.leaderboardMessageId = messageId ? messageId : this.leaderboardMessageId;
|
||||
this.leaderboardChannelId = channelId ? channelId : this.leaderboardChannelId;
|
||||
return this;
|
||||
}
|
||||
setCompareMessage(channelId, messageId) {
|
||||
this.compareChannelId = channelId;
|
||||
this.compareMessageId = messageId;
|
||||
return this;
|
||||
}
|
||||
setReminders(waterMessage, fruitMessage, reminderChannelId, watchChannelId, enabled) {
|
||||
this.waterMessage = waterMessage === undefined ? this.waterMessage : waterMessage
|
||||
this.fruitMessage = fruitMessage === undefined ? this.fruitMessage : fruitMessage;
|
||||
this.reminderChannelId = reminderChannelId === undefined ? this.reminderChannelId : reminderChannelId
|
||||
this.watchChannelId = watchChannelId === undefined ? this.watchChannelId : watchChannelId;
|
||||
this.notificationsEnabled = enabled === undefined ? this.notificationsEnabled : enabled;
|
||||
return this;
|
||||
}
|
||||
setRoles(waterRoleId, fruitRoleId) {
|
||||
this.waterRoleId = waterRoleId;
|
||||
if (fruitRoleId) this.fruitRoleId = fruitRoleId;
|
||||
return this;
|
||||
}
|
||||
queryBuilder(query) {
|
||||
let queryParts = [];
|
||||
switch (query) {
|
||||
case "setAll":
|
||||
queryParts = [
|
||||
`INSERT INTO guild_info `,
|
||||
`(guild_id, `,
|
||||
`tree_name, `,
|
||||
`tree_height, `,
|
||||
`tree_message_id, `,
|
||||
`tree_channel_id, `,
|
||||
`leaderboard_message_id, `,
|
||||
`leaderboard_channel_id, `,
|
||||
`water_message, `,
|
||||
`fruit_message, `,
|
||||
`reminder_channel_id, `,
|
||||
`watch_channel_id) `,
|
||||
`VALUES (${db.escape(this.guildId)}, `,
|
||||
`${db.escape(this.treeName)}, `,
|
||||
`${db.escape(this.treeHeight)}, `,
|
||||
`${db.escape(this.treeMessageId)}, `,
|
||||
`${db.escape(this.treeChannelId)}, `,
|
||||
`${db.escape(this.leaderboardMessageId)}, `,
|
||||
`${db.escape(this.leaderboardChannelId)}, `,
|
||||
`${db.escape(this.waterMessage)}, `,
|
||||
`${db.escape(this.fruitMessage)}, `,
|
||||
`${db.escape(this.reminderChannelId)}, `,
|
||||
`${db.escape(this.watchChannelId)}) `,
|
||||
`ON DUPLICATE KEY UPDATE tree_name = ${db.escape(this.treeName)}, `,
|
||||
`tree_height = ${db.escape(this.treeHeight)}, `,
|
||||
`tree_message_id = ${db.escape(this.treeMessageId)}, `,
|
||||
`tree_channel_id = ${db.escape(this.treeChannelId)}, `,
|
||||
`leaderboard_message_id = ${db.escape(this.leaderboardMessageId)}, `,
|
||||
`leaderboard_channel_id = ${db.escape(this.leaderboardChannelId)}, `,
|
||||
`water_message = ${db.escape(this.waterMessage)}, `,
|
||||
`fruit_message = ${db.escape(this.fruitMessage)}, `,
|
||||
`reminder_channel_id = ${db.escape(this.reminderChannelId)}, `,
|
||||
`watch_channel_id = ${db.escape(this.watchChannelId)})`
|
||||
];
|
||||
return queryParts.join('');
|
||||
break;
|
||||
case "setReminders":
|
||||
queryParts = [
|
||||
`INSERT INTO guild_info (guild_id, water_message, fruit_message, reminder_channel_id, watch_channel_id, notifications_enabled) VALUES (`,
|
||||
`${db.escape(this.guildId)},`,
|
||||
`${db.escape(this.waterMessage)},`,
|
||||
`${db.escape(this.fruitMessage)},`,
|
||||
`${db.escape(this.reminderChannelId)},`,
|
||||
`${db.escape(this.watchChannelId)},`,
|
||||
`${db.escape(this.notificationsEnabled)}`,
|
||||
`) ON DUPLICATE KEY UPDATE water_message = ${db.escape(this.waterMessage)}, `,
|
||||
`fruit_message = ${db.escape(this.fruitMessage)}, `,
|
||||
`reminder_channel_id = ${db.escape(this.reminderChannelId)}, `,
|
||||
`watch_channel_id = ${db.escape(this.watchChannelId)},`,
|
||||
`notifications_enabled = ${db.escape(this.notificationsEnabled)}`
|
||||
];
|
||||
return queryParts.join('');
|
||||
break;
|
||||
case "setTreeMessage":
|
||||
// queryParts = [
|
||||
// `UPDATE guild_info SET tree_message_id = ${db.escape(this.treeMessageId)}, `,
|
||||
// `tree_channel_id = ${db.escape(this.treeChannelId)} `,
|
||||
// `WHERE guild_id = ${db.escape(this.guildId)}`
|
||||
// ];
|
||||
queryParts = [
|
||||
`INSERT INTO guild_info (guild_id, tree_message_id, tree_channel_id)`,
|
||||
`VALUES (${db.escape(this.guildId)}, ${db.escape(this.treeMessageId)}, ${db.escape(this.treeChannelId)})`,
|
||||
`ON DUPLICATE KEY UPDATE tree_message_id = ${db.escape(this.treeMessageId)}, tree_channel_id = ${db.escape(this.treeChannelId)}`
|
||||
];
|
||||
return queryParts.join('');
|
||||
break;
|
||||
case "setLeaderboardMessage":
|
||||
// queryParts = [
|
||||
// `UPDATE guild_info SET leaderboard_message_id = ${db.escape(this.leaderboardMessageId)}, `,
|
||||
// `leaderboard_channel_id = ${db.escape(this.leaderboardChannelId)} `,
|
||||
// `WHERE guild_id = ${db.escape(this.guildId)}`
|
||||
// ];
|
||||
queryParts = [
|
||||
`INSERT INTO guild_info (guild_id, leaderboard_message_id, leaderboard_channel_id)`,
|
||||
`VALUES (${db.escape(this.guildId)}, ${db.escape(this.leaderboardMessageId)}, ${db.escape(this.leaderboardChannelId)})`,
|
||||
`ON DUPLICATE KEY UPDATE leaderboard_message_id = ${db.escape(this.leaderboardMessageId)}, leaderboard_channel_id = ${db.escape(this.leaderboardChannelId)}`
|
||||
];
|
||||
return queryParts.join('');
|
||||
break;
|
||||
case "setRoles":
|
||||
if (this.fruitRoleId != "") {
|
||||
queryParts = [
|
||||
`INSERT INTO guild_info (`,
|
||||
`guild_id, water_role_id, fruit_role_id`,
|
||||
`) VALUES (`,
|
||||
`${db.escape(this.guildId)}, ${db.escape(this.waterRoleId)}, ${db.escape(this.fruitRoleId)}`,
|
||||
`) ON DUPLICATE KEY UPDATE water_role_id = ${db.escape(this.waterRoleId)}, `,
|
||||
`fruit_role_id = ${db.escape(this.fruitRoleId)}`
|
||||
];
|
||||
} else {
|
||||
queryParts = [
|
||||
`UPDATE guild_info SET water_role_id = ${db.escape(this.waterRoleId)} `,
|
||||
`WHERE guild_id = ${db.escape(this.guildId)}`
|
||||
];
|
||||
}
|
||||
return queryParts.join('');
|
||||
break;
|
||||
case "setTreeInfo":
|
||||
queryParts = [
|
||||
`INSERT INTO guild_info (`,
|
||||
`guild_id, tree_name, tree_height, tree_channel_id, tree_message_id`,
|
||||
`) VALUES (`,
|
||||
`${db.escape(this.guildId)}, ${db.escape(this.treeName)}, ${db.escape(this.treeHeight)}, ${db.escape(this.treeChannelId)}, ${db.escape(this.treeMessageId)}`,
|
||||
`) ON DUPLICATE KEY UPDATE tree_name = ${db.escape(this.treeName)}, `,
|
||||
`tree_height = ${db.escape(this.treeHeight)}, `,
|
||||
`tree_channel_id = ${db.escape(this.treeChannelId)}, `,
|
||||
`tree_message_id = ${db.escape(this.treeMessageId)}`
|
||||
];
|
||||
return queryParts.join('');
|
||||
case "setCompareMessage":
|
||||
queryParts = [
|
||||
`INSERT INTO guild_info (`,
|
||||
`guild_id, compare_channel_id, compare_message_id`,
|
||||
`) VALUES (`,
|
||||
`${db.escape(this.guildId)}, ${db.escape(this.compareChannelId)}, ${db.escape(this.compareMessageId)}`,
|
||||
`) ON DUPLICATE KEY UPDATE compare_channel_id = ${db.escape(this.compareChannelId)}, compare_message_id = ${db.escape(this.compareMessageId)}`,
|
||||
];
|
||||
return queryParts.join('');
|
||||
// TODO This is hacked in and needs to be implemented throughout the code
|
||||
case "setIds":
|
||||
queryParts = [
|
||||
`UPDATE guild_info SET `,
|
||||
`owner_id=${db.escape(this.ownerId)} `,
|
||||
`WHERE guild_id=${db.escape(this.guildId)}`
|
||||
];
|
||||
return queryParts.join('');
|
||||
case "setBasic":
|
||||
queryParts = [
|
||||
`INSERT INTO guild_info (`,
|
||||
`guild_id, owner_id`,
|
||||
`) VALUES (`,
|
||||
`${db.escape(this.guildId)}, ${db.escape(this.ownerId)}`,
|
||||
`) ON DUPLICATE KEY UPDATE owner_id=${db.escape(this.ownerId)}`
|
||||
];
|
||||
return queryParts.join('');
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
generateSetupInfo() {
|
||||
let setupInfoParts = [
|
||||
`Here is your server's configuration:`,
|
||||
`Tree Name: ${this.treeName}`,
|
||||
`Tree Height: ${this.treeHeight}`,
|
||||
`Tree Channel: <#${this.treeChannelId}>`,
|
||||
`[Tree Link](https://discord.com/channels/${this.guildId}/${this.treeChannelId}/${this.treeMessageId})`,
|
||||
`Leaderboard Channel: <#${this.leaderboardChannelId}>`,
|
||||
`[Leaderboard Link](https://discord.com/channels/${this.guildId}/${this.leaderboardChannelId}/${this.leaderboardMessageId})`,
|
||||
`Notification Relay Enabled: ${this.notificationsEnabled}`,
|
||||
`Notification Watch Channel: <#${this.watchChannelId}>`,
|
||||
`Notification Relay Channel: <#${this.reminderChannelId}>`,
|
||||
`Water Message: ${this.waterMessage}`,
|
||||
`Fruit Message: ${this.fruitMessage}`,
|
||||
`Water Role: <@&${this.waterRoleId}>`,
|
||||
`Fruit Role: <@&${this.fruitRoleId}>`
|
||||
]
|
||||
return setupInfoParts.join('\n');
|
||||
}
|
||||
}
|
||||
}
|
0
modules/_clear-commands.js
Normal file → Executable file
0
modules/_clear-commands.js
Normal file → Executable file
0
modules/_deploy-commands.js
Normal file → Executable file
0
modules/_deploy-commands.js
Normal file → Executable file
28
modules/_deploy-global.js
Normal file → Executable file
28
modules/_deploy-global.js
Normal file → Executable file
@ -4,7 +4,7 @@ dotenv.config();
|
||||
|
||||
const { REST } = require('@discordjs/rest');
|
||||
const { Routes } = require('discord-api-types/v9');
|
||||
const clientId = process.env.clientId;
|
||||
const clientId = process.env.BOTID;
|
||||
const token = process.env.TOKEN;
|
||||
const fs = require('fs');
|
||||
|
||||
@ -18,13 +18,28 @@ for (const file of commandFiles) {
|
||||
}
|
||||
}
|
||||
|
||||
console.log(commands);
|
||||
console.log(`Token: ${token} Client ID: ${clientId}`);
|
||||
|
||||
const rest = new REST({ version: '9' }).setToken(token);
|
||||
|
||||
(async () => {
|
||||
async function deleteCommands() {
|
||||
try {
|
||||
console.log('Started refreshing application (/) commands.');
|
||||
console.log('Started deleting application (/) commands.');
|
||||
|
||||
await rest.put(
|
||||
Routes.applicationCommands(clientId),
|
||||
{ body: "" },
|
||||
);
|
||||
|
||||
console.log('Successfully deleted application (/) commands.');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function uploadCommands() {
|
||||
try {
|
||||
console.log('Started reloading application (/) commands.');
|
||||
|
||||
await rest.put(
|
||||
Routes.applicationCommands(clientId),
|
||||
@ -36,4 +51,9 @@ const rest = new REST({ version: '9' }).setToken(token);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
(async () => {
|
||||
// await deleteCommands();
|
||||
await uploadCommands();
|
||||
})();
|
48
modules/_prepareStrings.js
Normal file → Executable file
48
modules/_prepareStrings.js
Normal file → Executable file
@ -1,33 +1,29 @@
|
||||
/*
|
||||
</setup:1065407649363005561>
|
||||
</setupinfo:1065413032374706196>
|
||||
</compare:1065346941166297128>
|
||||
</setping:1068373237995683902>
|
||||
</optout:1068753032801693758>
|
||||
</watertime:1066970330029113444>
|
||||
</timetoheight:1067727254634889227>
|
||||
</reset:1065412317052944476>
|
||||
</help:1065346941166297129>
|
||||
</tree:972648557796524032>
|
||||
</top trees:1051840665362894950>
|
||||
*/
|
||||
|
||||
const commands = [
|
||||
{raw: "`/compare`", clickable: "</compare:1077058896469966889>"},
|
||||
{raw: "`/relay set`", clickable: "</relay set:1077322799032578152>"},
|
||||
{raw: "`/relay update`", clickable: "</relay update:1077322799032578152>"},
|
||||
{raw: "`/relay disable`", clickable: "</relay disable:1077322799032578152>"},
|
||||
{raw: "`/rolemenu`", clickable: "</rolemenu:1077058896469966892>"},
|
||||
{raw: "`/watertime`", clickable: "</watertime:1077058896469966895>"},
|
||||
{raw: "`/timetoheight`", clickable: "</timetoheight:1077058896469966894>"},
|
||||
{raw: "`/setup compare`", clickable: "</setup compare:1077058896469966893>"},
|
||||
{raw: "`/setup view`", clickable: "</setup view:1077058896469966893>"},
|
||||
{raw: "`/setup reset`", clickable: "</setup reset:1077058896469966893>"},
|
||||
{raw: "`/help`", clickable: "</help:1077058896469966890>"},
|
||||
{raw: "`/tree`", clickable: "</tree:972648557796524032>"},
|
||||
{raw: "`/top trees`", clickable: "</top trees:1051840665362894950>"},
|
||||
{raw: "`/commands`", clickable: "</commands:1077058896469966888>"}
|
||||
];
|
||||
const fs = require('fs');
|
||||
const replaceAll = require('string.prototype.replaceall');
|
||||
const string = fs.readFileSync('./data/rawstring.txt').toString();
|
||||
const path = "./modules/input.txt";
|
||||
const string = fs.readFileSync(path).toString();
|
||||
let newString = replaceAll(string, '\* ', '');
|
||||
newString = replaceAll(newString, '\n', '\\n');
|
||||
newString = replaceAll(newString, '\t', ' - ');
|
||||
newString = replaceAll(newString, '`/setup`', '</setup:1065407649363005561>');
|
||||
newString = replaceAll(newString, '`/setupinfo`', '</setupinfo:1065413032374706196>');
|
||||
newString = replaceAll(newString, '`/compare`', '</compare:1065346941166297128>');
|
||||
newString = replaceAll(newString, '`/setping`', '</setping:1068373237995683902>');
|
||||
newString = replaceAll(newString, '`/optout`', '</optout:1068753032801693758>');
|
||||
newString = replaceAll(newString, '`/watertime`', '</watertime:1066970330029113444>');
|
||||
newString = replaceAll(newString, '`/timetoheight`', '</timetoheight:1067727254634889227>');
|
||||
newString = replaceAll(newString, '`/reset`', '</reset:1065412317052944476>');
|
||||
newString = replaceAll(newString, '`/help`', '</help:1065346941166297129>');
|
||||
newString = replaceAll(newString, '`/commands`', '</commands:1069501270454456331>');
|
||||
commands.forEach(command => {
|
||||
newString = replaceAll(newString, command.raw, command.clickable);
|
||||
});
|
||||
newString = replaceAll(newString, '`', '``');
|
||||
fs.writeFileSync('./data/rawstring.txt', newString);
|
||||
fs.writeFileSync(path, newString);
|
||||
return "Done";
|
365
modules/dbfn.js
Normal file → Executable file
365
modules/dbfn.js
Normal file → Executable file
@ -2,6 +2,7 @@ const dotenv = require('dotenv');
|
||||
dotenv.config();
|
||||
const debugMode = process.env.DEBUG || true;
|
||||
const mysql = require('mysql');
|
||||
const { GuildInfo } = require('./CustomClasses.js');
|
||||
|
||||
/* Table Structures
|
||||
guild_info
|
||||
@ -33,42 +34,6 @@ leaderboard
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
createGuildTables(guildId) {
|
||||
const db = mysql.createConnection({
|
||||
host: process.env.DBHOST,
|
||||
user: process.env.DBUSER,
|
||||
password: process.env.DBPASS,
|
||||
database: process.env.DBNAME,
|
||||
port: process.env.DBPORT
|
||||
});
|
||||
db.connect((err) => {
|
||||
if (err) throw `Error connecting to the database: ${err.message}`;
|
||||
});
|
||||
// Create the guild-information and rank-information tables to be used.
|
||||
const createGuildInfoTableQuery = "CREATE TABLE IF NOT EXISTS guild_info(guild_id VARCHAR(50) NOT NULL, tree_name VARCHAR(100) NOT NULL DEFAULT 'Run /setup where your tree is.', tree_height INT(10) NOT NULL DEFAULT 0, tree_message_id VARCHAR(50) NOT NULL DEFAULT 'Run /setup where your tree is.', tree_channel_id VARCHAR(50) NOT NULL DEFAULT 'Run /setup where your tree is.', leaderboard_message_id VARCHAR(50) NOT NULL DEFAULT 'Run /setup where your leaderboard is.', leaderboard_channel_id VARCHAR(50) NOT NULL DEFAULT 'Run /setup where your leaderboard is.', CONSTRAINT guild_pk PRIMARY KEY (guild_id))";
|
||||
const createLeaderboardTableQuery = "CREATE TABLE IF NOT EXISTS leaderboard(id INT(10) NOT NULL AUTO_INCREMENT,guild_id VARCHAR(50) NOT NULL,tree_name VARCHAR(100) NOT NULL,tree_rank INT(10) NOT NULL,tree_height INT(10) NOT NULL DEFAULT 1,has_pin TINYINT(1) NOT NULL DEFAULT 0,timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT id_pk PRIMARY KEY(id))";
|
||||
// TODO run the queries, then add a call to this function at the beginning of main.js or functions.js
|
||||
return new Promise((resolve, reject) => {
|
||||
db.query(createGuildInfoTableQuery, (err) => {
|
||||
if (err) {
|
||||
reject("Error creating the guild_info table: " + err.message);
|
||||
console.error("Offending query: " + createGuildInfoTableQuery);
|
||||
db.end();
|
||||
return;
|
||||
}
|
||||
db.query(createLeaderboardTableQuery, (err) => {
|
||||
if (err) {
|
||||
reject("Error creating the leaderboard table: " + err.message);
|
||||
console.error("Offending query: " + createLeaderboardTableQuery);
|
||||
db.end();
|
||||
return;
|
||||
}
|
||||
resolve({ "status": "Successfully checked both tables.", "data": null });
|
||||
db.end();
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
getGuildInfo(guildId) {
|
||||
const db = mysql.createConnection({
|
||||
host: process.env.DBHOST,
|
||||
@ -91,41 +56,18 @@ module.exports = {
|
||||
db.end();
|
||||
return;
|
||||
}
|
||||
/*const guildInfo = { "guildId": "123",
|
||||
"treeName": "name",
|
||||
"treeHeight": 123,
|
||||
"treeMessageId": "123",
|
||||
"treeChannelId": "123",
|
||||
"leaderboardMessageId": "123",
|
||||
"leaderboardChannelId": "123",
|
||||
"reminderMessage": "Abc",
|
||||
"reminderChannelId": "123",
|
||||
"remindedStatus": 0,
|
||||
"comparisonMessageId": "123"
|
||||
};*/
|
||||
if (res.length == 0) {
|
||||
reject("There is no database entry for your guild yet. Try running /setup");
|
||||
db.end();
|
||||
return;
|
||||
}
|
||||
row = res[0];
|
||||
const guildInfo = {
|
||||
"guildId": guildId,
|
||||
"treeName": row.tree_name,
|
||||
"treeHeight": row.tree_height,
|
||||
"treeMessageId": row.tree_message_id,
|
||||
"treeChannelId": row.tree_channel_id,
|
||||
"leaderboardMessageId": row.leaderboard_message_id,
|
||||
"leaderboardChannelId": row.leaderboard_channel_id,
|
||||
"reminderMessage": row.ping_role_id,
|
||||
"reminderChannelId": row.ping_channel_id,
|
||||
"remindedStatus": row.reminded_status,
|
||||
"reminderOptIn": row.reminder_optin,
|
||||
"comparisonMessageId": row.comparison_message_id,
|
||||
"comparisonChannelId": row.comparison_channel_id
|
||||
};
|
||||
const guildInfo = new GuildInfo()
|
||||
.setIds(row.guild_id, row.owner_id)
|
||||
.setName(row.tree_name)
|
||||
.setHeight(row.tree_height)
|
||||
.setTreeMessage(row.tree_message_id, row.tree_channel_id)
|
||||
.setLeaderboardMessage(row.leaderboard_message_id, row.leaderboard_channel_id)
|
||||
.setReminders(row.water_message, row.fruit_message, row.reminder_channel_id, row.watch_channel_id, row.notifications_enabled)
|
||||
.setRoles(row.water_role_id, row.fruit_role_id)
|
||||
.setCompareMessage(row.compare_channel_id, row.compare_message_id);
|
||||
db.end();
|
||||
resolve({ "status": "Successfully fetched guild information", "data": guildInfo });
|
||||
resolve(guildInfo);
|
||||
});
|
||||
});
|
||||
},
|
||||
@ -141,62 +83,43 @@ module.exports = {
|
||||
if (err) throw `Error connecting to the database: ${err.message}`;
|
||||
});
|
||||
// Get a server's tree information from the database
|
||||
const selectAllGuildInfosQuery = `SELECT * FROM guild_info`;
|
||||
const query = 'SELECT * FROM guild_info';
|
||||
// TODO run this query and return a promise then structure the output into a GuildInfo object. resolve with { "status": , "data": guildInfo }
|
||||
return new Promise((resolve, reject) => {
|
||||
db.query(selectAllGuildInfosQuery, (err, res) => {
|
||||
db.query(query, (err, res) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
reject("Error fetching all guilds information: " + err.message);
|
||||
reject("Error fetching all guild infos: " + err.message);
|
||||
db.end();
|
||||
return;
|
||||
}
|
||||
/*const guildInfo = { "guildId": "123",
|
||||
"treeName": "name",
|
||||
"treeHeight": 123,
|
||||
"treeMessageId": "123",
|
||||
"treeChannelId": "123",
|
||||
"leaderboardMessageId": "123",
|
||||
"leaderboardChannelId": "123",
|
||||
"reminderMessage": "Abc",
|
||||
"reminderChannelId": "123",
|
||||
"remindedStatus": 0,
|
||||
"comparisonMessageId": "123"
|
||||
};*/
|
||||
if (res.length == 0) {
|
||||
reject("There are no guilds set up yet.");
|
||||
reject("There are no servers yet!");
|
||||
db.end();
|
||||
return;
|
||||
}
|
||||
|
||||
let guildInfos = new Array();
|
||||
|
||||
let guildInfos = [];
|
||||
for (let i = 0; i < res.length; i++) {
|
||||
let row = res[i];
|
||||
let guildInfo = {
|
||||
"guildId": row.guild_id,
|
||||
"treeName": row.tree_name,
|
||||
"treeHeight": row.tree_height,
|
||||
"treeMessageId": row.tree_message_id,
|
||||
"treeChannelId": row.tree_channel_id,
|
||||
"leaderboardMessageId": row.leaderboard_message_id,
|
||||
"leaderboardChannelId": row.leaderboard_channel_id,
|
||||
"reminderMessage": row.ping_role_id,
|
||||
"reminderChannelId": row.ping_channel_id,
|
||||
"remindedStatus": row.reminded_status,
|
||||
"reminderOptIn": row.reminder_optin,
|
||||
"comparisonMessageId": row.comparison_message_id,
|
||||
"comparisonChannelId": row.comparison_channel_id
|
||||
};
|
||||
guildInfos.push(guildInfo);
|
||||
guildInfos.push(new GuildInfo()
|
||||
.setIds(row.guild_id, row.owner_id)
|
||||
.setName(row.tree_name)
|
||||
.setHeight(row.tree_height)
|
||||
.setTreeMessage(row.tree_message_id, row.tree_channel_id)
|
||||
.setLeaderboardMessage(row.leaderboard_message_id, row.leaderboard_channel_id)
|
||||
.setReminders(row.water_message, row.fruit_message, row.reminder_channel_id, row.watch_channel_id, row.notifications_enabled)
|
||||
.setRoles(row.water_role_id, row.fruit_role_id)
|
||||
.setCompareMessage(row.compare_channel_id, row.compare_message_id)
|
||||
);
|
||||
}
|
||||
|
||||
// console.log(res.length + " // " + guildInfos.length);
|
||||
|
||||
db.end();
|
||||
resolve({ "status": "Successfully fetched all guilds information", "data": guildInfos });
|
||||
resolve(guildInfos);
|
||||
});
|
||||
});
|
||||
},
|
||||
setGuildInfo(guildInfo) {
|
||||
setGuildInfo(query) {
|
||||
const db = mysql.createConnection({
|
||||
host: process.env.DBHOST,
|
||||
user: process.env.DBUSER,
|
||||
@ -207,21 +130,17 @@ module.exports = {
|
||||
db.connect((err) => {
|
||||
if (err) throw `Error connecting to the database: ${err.message}`;
|
||||
});
|
||||
// Returns a Promise, resolve({ "status": "", "data": null })
|
||||
// guildInfo = { "guildId": "123", "treeName": "name", "treeHeight": 123, "treeMessageId": "123", "treeChannelId": "123", "leaderboardMessageId": "123", "leaderboardChannelId": "123"}
|
||||
// Set a server's tree information in the database
|
||||
const insertGuildInfoQuery = `INSERT INTO guild_info (guild_id, tree_name, tree_height, tree_message_id, tree_channel_id, leaderboard_message_id, leaderboard_channel_id) VALUES (${db.escape(guildInfo.guildId)}, ${db.escape(guildInfo.treeName)}, ${db.escape(guildInfo.treeHeight)},${db.escape(guildInfo.treeMessageId)}, ${db.escape(guildInfo.treeChannelId)}, ${db.escape(guildInfo.leaderboardMessageId)}, ${db.escape(guildInfo.leaderboardChannelId)}) ON DUPLICATE KEY UPDATE tree_name = ${db.escape(guildInfo.treeName)},tree_height = ${db.escape(guildInfo.treeHeight)},tree_message_id = ${db.escape(guildInfo.treeMessageId)},tree_channel_id = ${db.escape(guildInfo.treeChannelId)},leaderboard_message_id = ${db.escape(guildInfo.leaderboardMessageId)},leaderboard_channel_id = ${db.escape(guildInfo.leaderboardChannelId)}`;
|
||||
// TODO run this query and return a promise, then resolve with { "status": , "data": null }
|
||||
return new Promise((resolve, reject) => {
|
||||
db.query(insertGuildInfoQuery, (err, res) => {
|
||||
db.query(query, (err, res) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
console.error(err + "\n" + query);
|
||||
reject("Error setting the guild info: " + err.message);
|
||||
db.end();
|
||||
return;
|
||||
}
|
||||
db.end();
|
||||
resolve({ "status": "Successfully set the guild information", "data": null });
|
||||
// console.log("Updated the database");
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
},
|
||||
@ -250,6 +169,7 @@ module.exports = {
|
||||
return;
|
||||
}
|
||||
db.end();
|
||||
console.log("Updated the database");
|
||||
resolve({ "status": "Successfully set the guild information", "data": null });
|
||||
});
|
||||
});
|
||||
@ -378,218 +298,5 @@ module.exports = {
|
||||
resolve({ "status": "Successfully uploaded the leaderboard", "data": res });
|
||||
});
|
||||
});
|
||||
},
|
||||
get24hTree(guildId, treeName) {
|
||||
const db = mysql.createConnection({
|
||||
host: process.env.DBHOST,
|
||||
user: process.env.DBUSER,
|
||||
password: process.env.DBPASS,
|
||||
database: process.env.DBNAME,
|
||||
port: process.env.DBPORT
|
||||
});
|
||||
db.connect((err) => {
|
||||
if (err) throw `Error connecting to the database: ${err.message}`;
|
||||
});
|
||||
// Returns a Promise, resolve({ "status": "", "data": leaderboard })
|
||||
const select24hTreeQuery = `SELECT id, tree_name, tree_rank, tree_height, has_pin FROM leaderboard WHERE guild_id = ${db.escape(guildId)} AND tree_name = ${db.escape(treeName)} AND timestamp > date_sub(now(), interval 1 day) ORDER BY id ASC LIMIT 1`;
|
||||
// TODO run the query and return a promise then process the results. resolve with { "status": , "data": leaderboard }
|
||||
return new Promise((resolve, reject) => {
|
||||
db.query(select24hTreeQuery, (err, res) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
db.end();
|
||||
reject("Error fetching the historic 24hr tree height: " + err.message);
|
||||
return;
|
||||
}
|
||||
let hist24hTree = {};
|
||||
if (res.length > 0) {
|
||||
hist24hTree = {
|
||||
"treeName": res[0].tree_name,
|
||||
"treeRank": res[0].tree_rank,
|
||||
"treeHeight": res[0].tree_height,
|
||||
"hasPin": res[0].has_pin
|
||||
}
|
||||
} else {
|
||||
hist24hTree = {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
db.end();
|
||||
resolve({ "status": "Successfully fetched historic 24hr tree.", "data": hist24hTree });
|
||||
});
|
||||
});
|
||||
},
|
||||
setReminderInfo(guildId, reminderMessage, reminderChannelId) {
|
||||
const db = mysql.createConnection({
|
||||
host: process.env.DBHOST,
|
||||
user: process.env.DBUSER,
|
||||
password: process.env.DBPASS,
|
||||
database: process.env.DBNAME,
|
||||
port: process.env.DBPORT
|
||||
});
|
||||
db.connect((err) => {
|
||||
if (err) throw `Error connecting to the database: ${err.message}`;
|
||||
});
|
||||
// Returns a Promise, resolve({ "status": "", "data": leaderboard })
|
||||
const insertReminderInfoQuery = `UPDATE guild_info SET ping_role_id = ${db.escape(reminderMessage)}, ping_channel_id = ${db.escape(reminderChannelId)} WHERE guild_id = ${db.escape(guildId)}`;
|
||||
// TODO run the query and return a promise then process the results. resolve with { "status": , "data": leaderboard }
|
||||
return new Promise((resolve, reject) => {
|
||||
db.query(insertReminderInfoQuery, (err, res) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
db.end();
|
||||
reject("Error updating the reminder info: " + err.message);
|
||||
return;
|
||||
}
|
||||
db.end();
|
||||
resolve({ "status": `Successfully set the reminder message to "${reminderMessage}" in <#${reminderChannelId}>`, "data": res });
|
||||
});
|
||||
});
|
||||
},
|
||||
setRemindedStatus(guildId, remindedStatus) {
|
||||
const db = mysql.createConnection({
|
||||
host: process.env.DBHOST,
|
||||
user: process.env.DBUSER,
|
||||
password: process.env.DBPASS,
|
||||
database: process.env.DBNAME,
|
||||
port: process.env.DBPORT
|
||||
});
|
||||
db.connect((err) => {
|
||||
if (err) throw `Error connecting to the database: ${err.message}`;
|
||||
});
|
||||
// Returns a Promise, resolve({ "status": "", "data": leaderboard })
|
||||
const setRemindedStatusQuery = `UPDATE guild_info SET reminded_status = ${db.escape(remindedStatus)} WHERE guild_id = ${db.escape(guildId)}`;
|
||||
// TODO run the query and return a promise then process the results. resolve with { "status": , "data": leaderboard }
|
||||
return new Promise((resolve, reject) => {
|
||||
db.query(setRemindedStatusQuery, (err, res) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
db.end();
|
||||
reject("Error updating the reminded status: " + err.message);
|
||||
return;
|
||||
}
|
||||
db.end();
|
||||
resolve({ "status": `Successfully set the reminded status to ${remindedStatus}`, "data": res });
|
||||
// console.log("Boop: " + remindedStatus);
|
||||
});
|
||||
});
|
||||
},
|
||||
setReminderOptIn(guildId, optIn) {
|
||||
const db = mysql.createConnection({
|
||||
host: process.env.DBHOST,
|
||||
user: process.env.DBUSER,
|
||||
password: process.env.DBPASS,
|
||||
database: process.env.DBNAME,
|
||||
port: process.env.DBPORT
|
||||
});
|
||||
db.connect((err) => {
|
||||
if (err) throw `Error connecting to the database: ${err.message}`;
|
||||
});
|
||||
// Returns a Promise, resolve({ "status": "", "data": leaderboard })
|
||||
const setReminderOptInQuery = `UPDATE guild_info SET reminder_optin = ${db.escape(optIn)} WHERE guild_id = ${db.escape(guildId)}`;
|
||||
// TODO run the query and return a promise then process the results. resolve with { "status": , "data": leaderboard }
|
||||
return new Promise((resolve, reject) => {
|
||||
db.query(setReminderOptInQuery, (err, res) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
db.end();
|
||||
reject("Error updating the reminder opt-in status: " + err.message);
|
||||
return;
|
||||
}
|
||||
db.end();
|
||||
resolve({ "status": `Successfully set the reminder opt-in status to ${optIn}`, "data": res });
|
||||
});
|
||||
});
|
||||
},
|
||||
getOptedInGuilds() {
|
||||
const db = mysql.createConnection({
|
||||
host: process.env.DBHOST,
|
||||
user: process.env.DBUSER,
|
||||
password: process.env.DBPASS,
|
||||
database: process.env.DBNAME,
|
||||
port: process.env.DBPORT
|
||||
});
|
||||
db.connect((err) => {
|
||||
if (err) throw `Error connecting to the database: ${err.message}`;
|
||||
});
|
||||
// Get a server's tree information from the database
|
||||
const getOptedInGuildsQuery = `SELECT * FROM guild_info WHERE reminder_optin = 1`;
|
||||
// TODO run this query and return a promise then structure the output into a GuildInfo object. resolve with { "status": , "data": guildInfo }
|
||||
return new Promise((resolve, reject) => {
|
||||
db.query(getOptedInGuildsQuery, (err, res) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
reject("Error fetching guild information: " + err.message);
|
||||
db.end();
|
||||
return;
|
||||
}
|
||||
/*const guildInfo = { "guildId": "123",
|
||||
"treeName": "name",
|
||||
"treeHeight": 123,
|
||||
"treeMessageId": "123",
|
||||
"treeChannelId": "123",
|
||||
"leaderboardMessageId": "123",
|
||||
"leaderboardChannelId": "123",
|
||||
"reminderMessage": "Abc",
|
||||
"reminderChannelId": "123",
|
||||
"remindedStatus": 0,
|
||||
"comparisonMessageId": "123"
|
||||
};*/
|
||||
if (res.length == 0) {
|
||||
resolve({ "status": "No servers have opted in yet" });
|
||||
db.end();
|
||||
return;
|
||||
}
|
||||
row = res[0];
|
||||
let guilds = [];
|
||||
res.forEach(row => {
|
||||
guilds.push({
|
||||
"guildId": row.guild_id,
|
||||
"treeName": row.tree_name,
|
||||
"treeHeight": row.tree_height,
|
||||
"treeMessageId": row.tree_message_id,
|
||||
"treeChannelId": row.tree_channel_id,
|
||||
"leaderboardMessageId": row.leaderboard_message_id,
|
||||
"leaderboardChannelId": row.leaderboard_channel_id,
|
||||
"reminderMessage": row.ping_role_id,
|
||||
"reminderChannelId": row.ping_channel_id,
|
||||
"remindedStatus": row.reminded_status,
|
||||
"comparisonMessageId": row.comparison_message_id,
|
||||
"comparisonChannelId": row.comparison_channel_id
|
||||
});
|
||||
});
|
||||
db.end();
|
||||
resolve({ "status": "Successfully fetched guild information", "data": guilds });
|
||||
});
|
||||
});
|
||||
},
|
||||
setComparisonMessage(comparisonMessage, guildId) {
|
||||
const db = mysql.createConnection({
|
||||
host: process.env.DBHOST,
|
||||
user: process.env.DBUSER,
|
||||
password: process.env.DBPASS,
|
||||
database: process.env.DBNAME,
|
||||
port: process.env.DBPORT
|
||||
});
|
||||
db.connect((err) => {
|
||||
if (err) throw `Error connecting to the database: ${err.message}`;
|
||||
});
|
||||
// Returns a Promise, resolve({ "status": "", "data": leaderboard })
|
||||
const setComparisonMessageQuery = `UPDATE guild_info SET comparison_message_id = ${db.escape(comparisonMessage.id)}, comparison_channel_id = ${db.escape(comparisonMessage.channel.id)} WHERE guild_id = ${db.escape(guildId)}`;
|
||||
// console.log(JSON.stringify(comparisonMessage));
|
||||
// TODO run the query and return a promise then process the results. resolve with { "status": , "data": leaderboard }
|
||||
return new Promise((resolve, reject) => {
|
||||
db.query(setComparisonMessageQuery, (err, res) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
db.end();
|
||||
reject("Error updating the comparison message ID: " + err.message);
|
||||
return;
|
||||
}
|
||||
db.end();
|
||||
resolve({ "status": `Successfully set the comparison message ID: ${comparisonMessage}`, "data": res });
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
930
modules/functions.js
Normal file → Executable file
930
modules/functions.js
Normal file → Executable file
File diff suppressed because it is too large
Load Diff
21
modules/testing.js
Executable file
21
modules/testing.js
Executable file
@ -0,0 +1,21 @@
|
||||
information(content, fields) {
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor(strings.embeds.color)
|
||||
.setTitle('Information')
|
||||
.setDescription(content)
|
||||
.setFooter({ text: `v${package.version} - ${strings.embeds.footer}` });
|
||||
if (fields) embed.addFields(fields);
|
||||
const messageContents = { embeds: [embed], ephemeral: true };
|
||||
return messageContents;
|
||||
}
|
||||
|
||||
replyContent = `I estimate that a tree with ${efficiency}% Composter Efficiency and ${quality}% Compost Quality growing from ${beginHeight}ft to ${endHeight}ft will take ${time}`;
|
||||
replyFields = [
|
||||
{ name: `Start Height:`, value: `**${beginHeight}ft**`, inline: true },
|
||||
{ name: `End Height:`, value: `**${endHeight}**`, inline: true },
|
||||
{ name: `Efficiency:`, value: `**${efficiency}%**`, inline: true },
|
||||
{ name: `Quality:`, value: `**${quality}%**`, inline: true },
|
||||
{ name: `Summary`, value: `Compost Applied **${compostAppliedCount}** times, out of **${totalWaterCount}** waterings, for an average of **${average}%**` }
|
||||
];
|
||||
|
||||
const reply = information(replyContent, replyFields);
|
98
modules/utils.js
Normal file
98
modules/utils.js
Normal file
@ -0,0 +1,98 @@
|
||||
/* eslint-disable no-case-declarations */
|
||||
/* eslint-disable indent */
|
||||
// dotenv for handling environment variables
|
||||
const dotenv = require('dotenv');
|
||||
dotenv.config();
|
||||
const token = process.env.TOKEN;
|
||||
const dbfn = require('./dbfn.js');
|
||||
|
||||
// Discord.JS
|
||||
const { Client, GatewayIntentBits, Partials } = require('discord.js');
|
||||
const client = new Client({
|
||||
intents: [
|
||||
GatewayIntentBits.Guilds,
|
||||
GatewayIntentBits.GuildMessages,
|
||||
GatewayIntentBits.GuildMessageReactions,
|
||||
GatewayIntentBits.MessageContent
|
||||
],
|
||||
partials: [
|
||||
Partials.Channel,
|
||||
Partials.Message
|
||||
],
|
||||
});
|
||||
|
||||
// Various imports
|
||||
const fn = require('../modules/functions.js');
|
||||
|
||||
client.once('ready', async () => {
|
||||
// watchRequestRates();
|
||||
await fn.collectionBuilders.guildInfos(client);
|
||||
const guilds = client.guilds.cache;
|
||||
console.log("I'm in " + guilds.size + " guilds with " + client.guildInfos.size + " guildInfos");
|
||||
// guilds.each(g => {
|
||||
// console.log(g.name + "," + g.id + "," + g.ownerId);
|
||||
// });
|
||||
await setAllGuildOwners();
|
||||
process.exit();
|
||||
});
|
||||
|
||||
client.login(token);
|
||||
|
||||
|
||||
async function watchRequestRates() {
|
||||
const axios = require('axios');
|
||||
|
||||
// Make a GET request to the Discord API
|
||||
await axios.get('https://discord.com/api/v10/users/@me', {
|
||||
headers: {
|
||||
'Authorization': `Bot ${token}`
|
||||
}
|
||||
}).then(response => {
|
||||
// Get the rate limit headers
|
||||
const remaining = response.headers['x-ratelimit-remaining'];
|
||||
const reset = response.headers['x-ratelimit-reset'];
|
||||
|
||||
// Log the rate limit headers
|
||||
console.log(`Remaining requests: ${remaining}`);
|
||||
console.log(`Reset time (Unix epoch seconds): ${reset}`);
|
||||
}).catch(error => {
|
||||
console.error(error);
|
||||
});
|
||||
await fn.sleep(500).then(async () => {
|
||||
await watchRequestRates();
|
||||
});
|
||||
}
|
||||
|
||||
async function setAllGuildOwners() {
|
||||
try {
|
||||
let guildInfosArray = new Array();
|
||||
let guildUpdateCount = 0;
|
||||
let guildMissingCount = 0;
|
||||
client.guildInfos.forEach((guildInfo) => {
|
||||
guildInfosArray.push(guildInfo);
|
||||
});
|
||||
// console.log(guildInfosArray);
|
||||
for (let i = 0; i < guildInfosArray.length; i++) {
|
||||
const guildInfo = guildInfosArray[i];
|
||||
let eFlag = 0;
|
||||
const guild = await client.guilds.fetch(guildInfo.guildId).catch(e => {
|
||||
eFlag = 1;
|
||||
if (e.status === 404) {
|
||||
console.log("Missing guild: " + guildInfo.guildId);
|
||||
guildMissingCount++;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
if (eFlag === 1) continue;
|
||||
guildInfo.setIds(guildInfo.guildId, guild.ownerId);
|
||||
const query = guildInfo.queryBuilder("setIds");
|
||||
console.log(query);
|
||||
await dbfn.setGuildInfo(query);
|
||||
guildUpdateCount++;
|
||||
}
|
||||
console.log(`Updated ${guildUpdateCount} guilds with ${guildMissingCount} missing guilds.`);
|
||||
} catch(err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
7
package.json
Normal file → Executable file
7
package.json
Normal file → Executable file
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "silvanus",
|
||||
"version": "1.1.4",
|
||||
"description": "Grow A Tree Comparison Tool",
|
||||
"version": "1.2.9",
|
||||
"description": "Grow A Tree Companion Bot",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
@ -17,7 +17,8 @@
|
||||
},
|
||||
"homepage": "https://github.com/voidf1sh/silvanus#readme",
|
||||
"dependencies": {
|
||||
"discord.js": "^14.7.1",
|
||||
"axios": "^1.4.0",
|
||||
"discord.js": "^14.11.0",
|
||||
"dotenv": "^16.0.3",
|
||||
"mysql": "^2.18.1",
|
||||
"string-replace-all": "^2.0.0",
|
||||
|
39
privacy
Normal file
39
privacy
Normal file
@ -0,0 +1,39 @@
|
||||
Privacy Policy for Silvanus Discord Bot
|
||||
|
||||
This Privacy Policy describes how Silvanus ("the Bot," "we," or "us") collects, uses, and stores your personal information when you interact with the bot. Please read this policy carefully to understand our practices regarding your personal data and how we will treat it.
|
||||
|
||||
Data Collection and Usage:
|
||||
We collect and store the following data when you use the Silvanus Discord Bot:
|
||||
|
||||
User IDs: We collect user IDs to provide personalized services and ensure smooth bot functionality.
|
||||
Guild IDs: We collect guild IDs to enable the bot to operate within specific servers and provide server-specific features.
|
||||
Channel IDs: We collect channel IDs to facilitate bot functionality within specific channels.
|
||||
Message IDs: We collect message IDs to support certain features and improve the user experience.
|
||||
Role IDs: We collect role IDs to provide role-based functionality within Discord servers.
|
||||
Tree Names, Ranks, and Heights: We collect tree-related data for in-game purposes and to enhance user interactions.
|
||||
|
||||
Data Storage and Security:
|
||||
All collected data is stored securely using industry-standard practices. User IDs, guild IDs, channel IDs, message IDs, role IDs, tree names, tree ranks, and tree heights are stored in an encrypted MariaDB database. Additionally, the database is stored on a LUKS encrypted drive, providing an extra layer of security.
|
||||
|
||||
Data Deletion Requests:
|
||||
If you would like to request the deletion of your data, please follow one of the methods below:
|
||||
|
||||
Discord Message: You can message @voidf1sh on Discord to request data deletion.
|
||||
Email: You can send an email to SilvanusDev@gmail.com to request data deletion.
|
||||
Support Server: You can join our support server at https://discord.gg/g5JRGn7PxU and submit a request for data deletion.
|
||||
|
||||
Please note that we will make reasonable efforts to respond to and process your data deletion requests in a timely manner, subject to any legal obligations or legitimate interests that may prevent us from complying with such requests.
|
||||
|
||||
Data Sharing:
|
||||
We do not sell or share your personal information with third parties, except in the following circumstances:
|
||||
|
||||
Compliance with Laws: We may share your personal information if required to do so by applicable laws, regulations, legal processes, or governmental requests.
|
||||
Protection of Rights: We may share your personal information to protect the rights, property, or safety of Silvanus, its users, or others.
|
||||
|
||||
Changes to the Privacy Policy:
|
||||
We reserve the right to modify or update this Privacy Policy at any time. We will notify users of any material changes through the bot or other means. Your continued use of Silvanus after such modifications or updates signifies your acceptance of the revised Privacy Policy.
|
||||
|
||||
Contact Information:
|
||||
If you have any questions, concerns, or requests regarding this Privacy Policy or our data practices, please contact us at SilvanusDev@gmail.com.
|
||||
|
||||
Last updated: [22 May 2023]
|
0
slash-commands/.DS_Store
vendored
Normal file → Executable file
0
slash-commands/.DS_Store
vendored
Normal file → Executable file
20
slash-commands/about.js
Executable file
20
slash-commands/about.js
Executable file
@ -0,0 +1,20 @@
|
||||
const { SlashCommandBuilder, messageLink } = require('discord.js');
|
||||
const fn = require('../modules/functions.js');
|
||||
const strings = require('../data/strings.json');
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('about')
|
||||
.setDescription('Get info about the bot')
|
||||
.addStringOption(o =>
|
||||
o.setName('private')
|
||||
.setDescription('Should the response be sent privately?')
|
||||
.setRequired(true)
|
||||
.addChoices(
|
||||
{ name: "True", value: "true" },
|
||||
{ name: "False", value: "false" })),
|
||||
async execute(interaction) {
|
||||
const aboutEmbed = fn.builders.aboutEmbed(interaction.options.getString('private'));
|
||||
await interaction.reply(aboutEmbed);
|
||||
},
|
||||
};
|
0
slash-commands/commands.js
Normal file → Executable file
0
slash-commands/commands.js
Normal file → Executable file
84
slash-commands/compare.js
Normal file → Executable file
84
slash-commands/compare.js
Normal file → Executable file
@ -1,6 +1,8 @@
|
||||
const { SlashCommandBuilder } = require('discord.js');
|
||||
const { SlashCommandBuilder, Guild } = require('discord.js');
|
||||
const dbfn = require('../modules/dbfn.js');
|
||||
const fn = require('../modules/functions.js');
|
||||
const strings = require('../data/strings.json');
|
||||
const { GuildInfo } = require('../modules/CustomClasses.js');
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
@ -10,61 +12,37 @@ module.exports = {
|
||||
try {
|
||||
await interaction.deferReply();
|
||||
// Get the guildInfo from the database
|
||||
dbfn.getGuildInfo(interaction.guildId).then(async getGuildInfoResponse => {
|
||||
let guildInfo = getGuildInfoResponse.data;
|
||||
// Find the most recent tree and leaderboard messages in their respective channels
|
||||
const findMessagesResponse = await fn.messages.find(interaction, guildInfo);
|
||||
if (findMessagesResponse.code == 1) {
|
||||
guildInfo = findMessagesResponse.data;
|
||||
// Parse the leaderboard message
|
||||
await fn.rankings.parse(interaction, guildInfo);
|
||||
// Build the string that shows the comparison // TODO Move the string building section to fn.builders?
|
||||
const comparedRankings = await fn.rankings.compare(interaction, guildInfo);
|
||||
|
||||
const embed = fn.builders.comparisonEmbed(comparedRankings, guildInfo);
|
||||
await interaction.editReply(embed).then(async message => {
|
||||
await dbfn.setComparisonMessage(message, interaction.guildId);
|
||||
});
|
||||
} else {
|
||||
await interaction.editReply(fn.builders.errorEmbed(findMessagesResponse.status));
|
||||
if (interaction.client.guildInfos.has(interaction.guildId)) {
|
||||
let guildInfo = interaction.client.guildInfos.get(interaction.guildId);
|
||||
// Handle a missing tree or leaderboard
|
||||
let errors = [];
|
||||
if (guildInfo.treeMessageId === "") {
|
||||
errors.push(strings.error.noTreeMessage);
|
||||
} else if (guildInfo.leaderboardMessageId === "") {
|
||||
errors.push(strings.error.noLeaderboardMessage);
|
||||
}
|
||||
|
||||
}).catch(async err => { // If we fail to fetch the guild's info from the database
|
||||
// If the error is because the guild hasn't been setup yet, set it up
|
||||
if (err === "There is no database entry for your guild yet. Try running /setup") {
|
||||
// Create a basic guildInfo with blank data
|
||||
let guildInfo = {
|
||||
guildId: `${interaction.guildId}`,
|
||||
treeName: "",
|
||||
treeHeight: 0,
|
||||
treeMessageId: "",
|
||||
treeChannelId: `${interaction.channelId}`, // Use this interaction channel for the initial channel IDs
|
||||
leaderboardMessageId: "",
|
||||
leaderboardChannelId: `${interaction.channelId}`,
|
||||
reminderMessage: "",
|
||||
reminderChannelId: "",
|
||||
remindedStatus: 0,
|
||||
reminderOptIn: 0,
|
||||
}
|
||||
// Using the above guildInfo, try to find the Grow A Tree messages
|
||||
const findMessagesResponse = await fn.messages.find(interaction, guildInfo);
|
||||
guildInfo = findMessagesResponse.data;
|
||||
if (findMessagesResponse.code == 1) {
|
||||
// Build the string that shows the comparison // TODO Move the string building section to fn.builders?
|
||||
const comparedRankings = await fn.rankings.compare(interaction, guildInfo);
|
||||
const embed = fn.builders.comparisonEmbed(comparedRankings, guildInfo);
|
||||
await interaction.editReply(embed).then(async message => {
|
||||
await dbfn.setComparisonMessage(message.id, interaction.guildId);
|
||||
});
|
||||
} else {
|
||||
await interaction.editReply(fn.builders.errorEmbed(findMessagesResponse.status));
|
||||
}
|
||||
|
||||
} else {
|
||||
await interaction.editReply(fn.builders.errorEmbed("An unknown error occurred while running the compare command."));
|
||||
console.error(err);
|
||||
if (errors.length > 0) {
|
||||
const embed = fn.builders.errorEmbed(`Unable to complete comparison, unable to find message(s):\n${errors.join("\n")}`);
|
||||
await interaction.editReply(embed);
|
||||
return;
|
||||
}
|
||||
});
|
||||
// Build the string that shows the comparison // TODO Move the string building section to fn.builders?
|
||||
const comparedRankings = await fn.rankings.compare(guildInfo);
|
||||
|
||||
const embed = fn.builders.comparisonEmbed(comparedRankings, guildInfo);
|
||||
await interaction.editReply(embed).then(async m => {
|
||||
guildInfo.setCompareMessage(m.channel.id, m.id);
|
||||
const query = guildInfo.queryBuilder("setCompareMessage");
|
||||
await dbfn.setGuildInfo(query);
|
||||
}).catch(e => console.error(e));
|
||||
} else {
|
||||
let errors = [];
|
||||
errors.push(strings.error.noTreeMessage);
|
||||
errors.push(strings.error.noLeaderboardMessage);
|
||||
const embed = fn.builders.errorEmbed(`Unable to complete comparison, unable to find message(s):\n${errors.join("\n")}`);
|
||||
await interaction.editReply(embed);
|
||||
}
|
||||
} catch (err) {
|
||||
interaction.editReply(fn.builders.errorEmbed(err)).catch(err => {
|
||||
console.error(err);
|
||||
|
2
slash-commands/help.js
Normal file → Executable file
2
slash-commands/help.js
Normal file → Executable file
@ -14,7 +14,7 @@ module.exports = {
|
||||
{ name: "True", value: "true" },
|
||||
{ name: "False", value: "false" })),
|
||||
execute(interaction) {
|
||||
const helpEmbed = fn.builders.helpEmbed(`${strings.help.info}\n\n**Setup**\n${strings.help.setup}\n\nSee </commands:0> for a list of all my commands\n\n**Support Server**\n${strings.urls.supportServer}`, interaction.options.getString('private'));
|
||||
const helpEmbed = fn.builders.helpEmbed(`${strings.help.info}\n\n**Setup**\n${strings.help.setup}\n\n**Support Server**\n${strings.urls.supportServer}`, interaction.options.getString('private'));
|
||||
interaction.reply(helpEmbed);
|
||||
},
|
||||
};
|
@ -1,20 +0,0 @@
|
||||
const { SlashCommandBuilder, PermissionFlagsBits } = require('discord.js');
|
||||
const dbfn = require('../modules/dbfn.js');
|
||||
const fn = require('../modules/functions.js');
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('optout')
|
||||
.setDescription('Opt-out of automatic water reminders')
|
||||
.setDefaultMemberPermissions(PermissionFlagsBits.ManageRoles),
|
||||
async execute(interaction) {
|
||||
try {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
const setReminderOptInResponse = await dbfn.setReminderOptIn(interaction.guildId, 0);
|
||||
interaction.editReply(setReminderOptInResponse.status);
|
||||
} catch(err) {
|
||||
console.error(err);
|
||||
await interaction.editReply(fn.builders.errorEmbed(err));
|
||||
}
|
||||
},
|
||||
};
|
30
slash-commands/permissions.js
Executable file
30
slash-commands/permissions.js
Executable file
@ -0,0 +1,30 @@
|
||||
const { SlashCommandBuilder, PermissionsBitField, PermissionFlagsBits } = require('discord.js');
|
||||
const fn = require('../modules/functions.js');
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('permissions')
|
||||
.setDescription('Check my permissions here')
|
||||
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator),
|
||||
async execute(interaction) {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
const me = interaction.guild.members.me;
|
||||
const guildPerms = me.permissions;
|
||||
const manageRoles = guildPerms.has(PermissionsBitField.Flags.ManageRoles);
|
||||
const mentionEveryone = guildPerms.has(PermissionsBitField.Flags.MentionEveryone);
|
||||
const channelPerms = me.permissionsIn(interaction.channel);
|
||||
const viewChannel = channelPerms.has(PermissionsBitField.Flags.ViewChannel);
|
||||
const sendMessages = channelPerms.has(PermissionsBitField.Flags.SendMessages);
|
||||
const responseParts = [
|
||||
`This is the status of my permissions in this server and this channel (<#${interaction.channel.id}>)`,
|
||||
`**Guild Permissions**`,
|
||||
`Manage Roles: ${manageRoles}`,
|
||||
`Mention All Roles: ${mentionEveryone}`,
|
||||
`**Channel Permissions**`,
|
||||
`View Channel: ${viewChannel}`,
|
||||
`Send Messages: ${sendMessages}`
|
||||
];
|
||||
const replyEmbed = fn.builders.embed(responseParts.join("\n"));
|
||||
await interaction.editReply(replyEmbed);
|
||||
}
|
||||
};
|
205
slash-commands/relay.js
Executable file
205
slash-commands/relay.js
Executable file
@ -0,0 +1,205 @@
|
||||
const { SlashCommandBuilder, PermissionFlagsBits } = require('discord.js');
|
||||
const { GuildInfo } = require('../modules/CustomClasses.js');
|
||||
const dbfn = require('../modules/dbfn.js');
|
||||
const fn = require('../modules/functions.js');
|
||||
const strings = require('../data/strings.json');
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('relay')
|
||||
.setDescription('A notification relay for improved water and fruit notifications')
|
||||
.addSubcommand(sc =>
|
||||
sc.setName('set')
|
||||
.setDescription('Set up the notification relay for the first time')
|
||||
.addChannelOption(o =>
|
||||
o.setName('watchchannel')
|
||||
.setDescription('The channel Grow A Tree sends your notifications in')
|
||||
.setRequired(true)
|
||||
)
|
||||
.addStringOption(o =>
|
||||
o.setName('watermessage')
|
||||
.setDescription('Message to send for water reminders')
|
||||
.setRequired(true)
|
||||
)
|
||||
.addChannelOption(o =>
|
||||
o.setName('pingchannel')
|
||||
.setDescription('The channel to send the water reminder in')
|
||||
.setRequired(true)
|
||||
)
|
||||
.addStringOption(o =>
|
||||
o.setName('fruitmessage')
|
||||
.setDescription("Message to send for fruit reminders")
|
||||
.setRequired(false)
|
||||
)
|
||||
)
|
||||
.addSubcommand(sc =>
|
||||
sc.setName('update')
|
||||
.setDescription('Update an already setup notification relay')
|
||||
.addChannelOption(o =>
|
||||
o.setName('watchchannel')
|
||||
.setDescription('The channel Grow A Tree sends your notifications in')
|
||||
.setRequired(false)
|
||||
)
|
||||
.addStringOption(o =>
|
||||
o.setName('watermessage')
|
||||
.setDescription('Message to send for water reminders')
|
||||
.setRequired(false)
|
||||
)
|
||||
.addChannelOption(o =>
|
||||
o.setName('pingchannel')
|
||||
.setDescription('The channel to send the water reminder in')
|
||||
.setRequired(false)
|
||||
)
|
||||
.addStringOption(o =>
|
||||
o.setName('fruitmessage')
|
||||
.setDescription("Message to send for fruit reminders")
|
||||
.setRequired(false)
|
||||
)
|
||||
.addBooleanOption(o =>
|
||||
o.setName('enabled')
|
||||
.setDescription("Enable the relay?")
|
||||
.setRequired(false)
|
||||
)
|
||||
)
|
||||
.addSubcommand(sc =>
|
||||
sc.setName('disable')
|
||||
.setDescription('Disable the notification relay')
|
||||
)
|
||||
.setDefaultMemberPermissions(PermissionFlagsBits.ManageRoles),
|
||||
async execute(interaction) {
|
||||
try {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
const subcommand = interaction.options.getSubcommand();
|
||||
// if (process.env.DEBUG) console.log(`${typeof subcommand}: ${subcommand}`);
|
||||
switch (subcommand) {
|
||||
// Set all components for the first time
|
||||
case "set":
|
||||
// If there is already a guildInfo object for this server
|
||||
if (interaction.client.guildInfos.has(interaction.guildId)) {
|
||||
let guildInfo = interaction.client.guildInfos.get(interaction.guildId);
|
||||
// Get options from the interaction
|
||||
const watchChannel = interaction.options.getChannel('watchchannel');
|
||||
const waterMessage = interaction.options.getString('watermessage');
|
||||
// If the fruit message is set, use it, otherwise default to the water message.
|
||||
const fruitMessage = interaction.options.getString('fruitmessage') ? interaction.options.getString('fruitmessage') : interaction.options.getString('watermessage');
|
||||
const reminderChannel = interaction.options.getChannel('pingchannel');
|
||||
// Set the reminder configuration in the GuildInfo object
|
||||
guildInfo.setReminders(waterMessage, fruitMessage, reminderChannel.id, watchChannel.id, true);
|
||||
// Update the guildInfos Collection
|
||||
interaction.client.guildInfos.set(interaction.guildId, guildInfo);
|
||||
// Build a query to update the database
|
||||
let query = guildInfo.queryBuilder("setReminders");
|
||||
// Run the query
|
||||
await dbfn.setGuildInfo(query);
|
||||
// Set up a collector on the watch channel
|
||||
fn.collectors.create(interaction.client, guildInfo);
|
||||
// Compose a reply
|
||||
const reply = [
|
||||
`I'll watch <#${watchChannel.id}> for Grow A Tree Notifications and relay them to <#${reminderChannel.id}>.`,
|
||||
`Water Message: ${waterMessage}`,
|
||||
`Fruit Message: ${fruitMessage}`
|
||||
].join("\n");
|
||||
// Send the reply
|
||||
await interaction.editReply(fn.builders.embed(reply)).catch(e => console.error(e));
|
||||
} else {
|
||||
// Get options from the interaction
|
||||
const watchChannel = interaction.options.getChannel('watchchannel');
|
||||
const waterMessage = interaction.options.getString('watermessage');
|
||||
// If the fruit message is set, use it. Otherwise default to the water message
|
||||
const fruitMessage = interaction.options.getString('fruitmessage') ? interaction.options.getString('fruitmessage') : interaction.options.getString('watermessage');
|
||||
const reminderChannel = interaction.options.getChannel('pingchannel');
|
||||
// Create a new GuildInfo object
|
||||
let guildInfo = new GuildInfo()
|
||||
.setIds(interaction.guildId, interaction.guild.ownerId)
|
||||
// Set the reminder configuration
|
||||
.setReminders(waterMessage, fruitMessage, reminderChannel.id, watchChannel.id, true);
|
||||
// Update the guildInfos Collection
|
||||
interaction.client.guildInfos.set(interaction.guildId, guildInfo);
|
||||
// Build a query to update the database
|
||||
let query = guildInfo.queryBuilder("setReminders");
|
||||
// Run the query
|
||||
await dbfn.setGuildInfo(query);
|
||||
// Refresh the collection
|
||||
await fn.collectionBuilders.guildInfos(interaction.client);
|
||||
// Create a messageCollector on the watch channel
|
||||
fn.collectors.create(interaction.client, guildInfo);
|
||||
// Compose a reply
|
||||
const reply = [
|
||||
`I'll watch <#${watchChannel.id}> for Grow A Tree Notifications and relay them to <#${reminderChannel.id}>.`,
|
||||
`Water Message: ${waterMessage}`,
|
||||
`Fruit Message: ${fruitMessage}`
|
||||
].join("\n");
|
||||
// Send the reply
|
||||
await interaction.editReply(reply).catch(e => console.error(e));
|
||||
}
|
||||
break;
|
||||
case "update": // Update the relay configuration piecemeal
|
||||
if (interaction.client.guildInfos.has(interaction.guildId)) {
|
||||
let guildInfo = interaction.client.guildInfos.get(interaction.guildId);
|
||||
|
||||
// Get all possible options from the interaction
|
||||
const inWatchChannel = interaction.options.getChannel('watchchannel');
|
||||
const inWaterMessage = interaction.options.getString('watermessage');
|
||||
const inFruitMessage = interaction.options.getString('fruitmessage');
|
||||
const inReminderChannel = interaction.options.getChannel('pingchannel');
|
||||
const inEnabled = interaction.options.getBoolean('enabled');
|
||||
|
||||
// Check if each option is set, if it is, use it. Otherwise use what was already set
|
||||
const outWatchChannelId = inWatchChannel ? inWatchChannel.id : guildInfo.watchChannelId;
|
||||
const outWaterMessage = inWaterMessage ? inWaterMessage : guildInfo.waterMessage;
|
||||
const outFruitMessage = inFruitMessage ? inFruitMessage : guildInfo.fruitMessage;
|
||||
const outReminderChannelId = inReminderChannel ? inReminderChannel.id : guildInfo.reminderChannelId;
|
||||
const outEnabled = inEnabled ? inEnabled : guildInfo.notificationsEnabled;
|
||||
|
||||
// Update the relay configuration
|
||||
guildInfo.setReminders(outWaterMessage, outFruitMessage, outReminderChannelId, outWatchChannelId, outEnabled);
|
||||
// Update the guildInfos Collection
|
||||
interaction.client.guildInfos.set(interaction.guildId, guildInfo);
|
||||
// Build a query to update the database
|
||||
let query = guildInfo.queryBuilder("setReminders");
|
||||
// Run the query
|
||||
await dbfn.setGuildInfo(query);
|
||||
// Refresh the collection
|
||||
await fn.collectionBuilders.guildInfos(interaction.client);
|
||||
// Create a messageCollector on the watch channel
|
||||
fn.collectors.create(interaction.client, guildInfo);
|
||||
// Compose a reply
|
||||
const reply = [
|
||||
`I'll watch <#${outWatchChannelId}> for Grow A Tree Notifications and relay them to <#${outReminderChannelId}>.`,
|
||||
`Water Message: ${outWaterMessage}`,
|
||||
`Fruit Message: ${outFruitMessage}`
|
||||
].join("\n");
|
||||
// Send the reply
|
||||
await interaction.editReply(reply).catch(e => console.error(e));
|
||||
} else {
|
||||
await interaction.editReply(fn.builders.errorEmbed("There is no existing notification relay to update!")).catch(e => console.error(e));
|
||||
}
|
||||
break;
|
||||
case 'disable': // Disable the relay
|
||||
if (interaction.client.guildInfos.has(interaction.guildId)) {
|
||||
let guildInfo = interaction.client.guildInfos.get(interaction.guildId);
|
||||
// Update the relay config with all undefined except for `enabled` which is false
|
||||
guildInfo.setReminders(undefined, undefined, undefined, undefined, false);
|
||||
// Update the guildInfos Collection
|
||||
interaction.client.guildInfos.set(interaction.guildId, guildInfo);
|
||||
// Update the database
|
||||
await dbfn.setGuildInfo(guildInfo.queryBuilder("setReminders")).catch(e => console.error(e));
|
||||
// Refresh the collection
|
||||
await fn.collectionBuilders.guildInfos(interaction.client);
|
||||
// Close the collector
|
||||
await fn.collectors.end(interaction.client, guildInfo).catch(e => console.error(e));
|
||||
// Reply confirming disabling of relay
|
||||
await interaction.editReply(fn.builders.embed(strings.status.optout)).catch(e => console.error(e));
|
||||
} else {
|
||||
await interaction.editReply(fn.builders.errorEmbed("A notification relay has not been set up yet!")).catch(e => console.error(e));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
await interaction.editReply(fn.builders.errorEmbed("Invalid subcommand detected.")).catch(e => console.error(e));
|
||||
break;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Error occurred while setting up a notification relay: " + err);
|
||||
}
|
||||
},
|
||||
};
|
@ -1,24 +0,0 @@
|
||||
const { SlashCommandBuilder } = require('discord.js');
|
||||
const fn = require('../modules/functions.js');
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('reset')
|
||||
.setDescription('Reset all message assignments in your server'),
|
||||
execute(interaction) {
|
||||
interaction.deferReply({ ephemeral: true }).then(() => {
|
||||
fn.reset(interaction.guildId).then(res => {
|
||||
interaction.editReply(fn.builders.embed("Assignments Reset")).catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
}).catch(err => {
|
||||
console.error(err);
|
||||
interaction.editReply("There was a problem deleting your guild information, contact @voidf1sh#0420 for help.").catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
});
|
||||
}).catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
},
|
||||
};
|
18
slash-commands/rolemenu.js
Executable file
18
slash-commands/rolemenu.js
Executable file
@ -0,0 +1,18 @@
|
||||
const { SlashCommandBuilder } = require('discord.js');
|
||||
const fn = require('../modules/functions.js');
|
||||
const strings = require('../data/strings.json');
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('rolemenu')
|
||||
.setDescription('Send a self-assignable role selection menu'),
|
||||
async execute(interaction) {
|
||||
await interaction.deferReply().catch(e => console.error(e));
|
||||
if (interaction.client.guildInfos.has(interaction.guildId)) {
|
||||
let guildInfo = interaction.client.guildInfos.get(interaction.guildId);
|
||||
await interaction.editReply(fn.builders.embeds.treeRoleMenu(guildInfo)).catch(e => console.error(e));
|
||||
} else {
|
||||
await interaction.editReply(fn.builders.errorEmbed(strings.status.noRoleMenu)).catch(e => console.error(e));
|
||||
}
|
||||
},
|
||||
};
|
@ -1,31 +0,0 @@
|
||||
const { SlashCommandBuilder, PermissionFlagsBits } = require('discord.js');
|
||||
const dbfn = require('../modules/dbfn.js');
|
||||
const fn = require('../modules/functions.js');
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('setping')
|
||||
.setDescription('Opt-in to automatic water reminders')
|
||||
.addStringOption(o =>
|
||||
o.setName('pingmsg')
|
||||
.setDescription('The message to send for a water reminder')
|
||||
.setRequired(true))
|
||||
.addChannelOption(o =>
|
||||
o.setName('pingchannel')
|
||||
.setDescription('The channel to send the water reminder in')
|
||||
.setRequired(true))
|
||||
.setDefaultMemberPermissions(PermissionFlagsBits.ManageRoles),
|
||||
async execute(interaction) {
|
||||
try {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
const reminderMessage = interaction.options.getString('pingmsg');
|
||||
const reminderChannel = interaction.options.getChannel('pingchannel');
|
||||
const setPingRoleResponse = await dbfn.setReminderInfo(interaction.guildId, reminderMessage, reminderChannel.id);
|
||||
await dbfn.setReminderOptIn(interaction.guildId, 1);
|
||||
interaction.editReply(setPingRoleResponse.status);
|
||||
} catch(err) {
|
||||
console.error(err);
|
||||
await interaction.editReply(fn.builders.errorEmbed(err));
|
||||
}
|
||||
},
|
||||
};
|
@ -1,18 +0,0 @@
|
||||
const { SlashCommandBuilder } = require('discord.js');
|
||||
const dbfn = require('../modules/dbfn.js');
|
||||
const fn = require('../modules/functions.js');
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('setping')
|
||||
.setDescription('Run this command when you water your tree to have a reminder sent.'),
|
||||
async execute(interaction) {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
const getGuildInfoResponse = await dbfn.getGuildInfo(interaction.guildId);
|
||||
const guildInfo = getGuildInfoResponse.data;
|
||||
const reminderTimeS = fn.getWaterTime(guildInfo.treeHeight);
|
||||
const reminderTimeMs = reminderTimeS * 1000;
|
||||
fn.setReminder(interaction, reminderTimeMs, guildInfo.pingRoleId);
|
||||
interaction.editReply("A reminder has been set.");
|
||||
},
|
||||
};
|
114
slash-commands/setup.js
Normal file → Executable file
114
slash-commands/setup.js
Normal file → Executable file
@ -1,37 +1,99 @@
|
||||
const { SlashCommandBuilder } = require('discord.js');
|
||||
const { SlashCommandBuilder, PermissionFlagsBits } = require('discord.js');
|
||||
const fn = require('../modules/functions.js');
|
||||
const strings = require('../data/strings.json');
|
||||
const dbfn = require('../modules/dbfn.js');
|
||||
const { GuildInfo } = require('../modules/CustomClasses.js');
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('setup')
|
||||
.setDescription('Attempt automatic configuration of the bot.')
|
||||
.addChannelOption(o =>
|
||||
o.setName('treechannel')
|
||||
.setDescription('What channel is your tree in?')
|
||||
.setRequired(true))
|
||||
.addChannelOption(o =>
|
||||
o.setName('leaderboardchannel')
|
||||
.setDescription('If your leaderboard isn\'t in the same channel, where is it?')
|
||||
.setRequired(false)),
|
||||
.setDescription('Configure some feature settings.')
|
||||
.addSubcommand(sc =>
|
||||
sc.setName('rolemenu')
|
||||
.setDescription('Setup the roles to be used with /rolemenu')
|
||||
.addRoleOption(o =>
|
||||
o.setName('waterrole')
|
||||
.setDescription('The role for water reminder pings')
|
||||
.setRequired(true)
|
||||
)
|
||||
.addRoleOption(o =>
|
||||
o.setName('fruitrole')
|
||||
.setDescription('The role for fruit alert pings')
|
||||
.setRequired(false)
|
||||
)
|
||||
)
|
||||
.addSubcommand(sc =>
|
||||
sc.setName('view')
|
||||
.setDescription('View your server\'s configuration'))
|
||||
.addSubcommand(sc =>
|
||||
sc.setName('reset')
|
||||
.setDescription('Remove all server configuration from the database')
|
||||
.addBooleanOption(o =>
|
||||
o.setName('confirm')
|
||||
.setDescription('WARNING THIS IS IRREVERSIBLE')
|
||||
.setRequired(true)
|
||||
)
|
||||
)
|
||||
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator),
|
||||
async execute(interaction) {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
/**/
|
||||
let guildInfo = {
|
||||
"guildId": interaction.guildId,
|
||||
"treeName": "",
|
||||
"treeHeight": 0,
|
||||
"treeMessageId": "",
|
||||
"treeChannelId": `${interaction.options.getChannel('treechannel').id }`,
|
||||
"leaderboardMessageId": "",
|
||||
"leaderboardChannelId": `${interaction.options.getChannel('leaderboardchannel').id || interaction.options.getChannel('treechannel').id }`,
|
||||
"reminderMessage": "",
|
||||
"reminderChannelId": "",
|
||||
"remindedStatus": 0,
|
||||
"reminderOptIn": 0,
|
||||
};
|
||||
const findMessagesResponse = await fn.messages.find(interaction, guildInfo);
|
||||
interaction.editReply(findMessagesResponse.status);
|
||||
const subcommand = interaction.options.getSubcommand();
|
||||
switch (subcommand) {
|
||||
case "rolemenu":
|
||||
let waterRoleId = interaction.options.getRole('waterrole').id;
|
||||
let fruitRoleId = interaction.options.getRole('fruitrole') ? interaction.options.getRole('fruitrole').id : undefined;
|
||||
if (interaction.client.guildInfos.has(interaction.guildId)) {
|
||||
let guildInfo = interaction.client.guildInfos.get(interaction.guildId);
|
||||
guildInfo.setRoles(waterRoleId, fruitRoleId);
|
||||
await dbfn.setGuildInfo(guildInfo.queryBuilder("setRoles"));
|
||||
await fn.collectionBuilders.guildInfos(interaction.client);
|
||||
await interaction.editReply(fn.builders.embeds.treeRoleMenu(guildInfo)).catch(e => console.error(e));
|
||||
} else {
|
||||
let guildInfo = new GuildInfo()
|
||||
.setIds(interaction.guildId, interaction.guild.ownerId);
|
||||
guildInfo.setRoles(waterRoleId, fruitRoleId);
|
||||
await dbfn.setGuildInfo(guildInfo.queryBuilder("setRoles"));
|
||||
await fn.collectionBuilders.guildInfos(interaction.client);
|
||||
await interaction.editReply(fn.builders.embeds.treeRoleMenu(guildInfo)).catch(e => console.error(e));
|
||||
}
|
||||
break;
|
||||
case "view":
|
||||
try {
|
||||
if (interaction.client.guildInfos.has(interaction.guildId)) {
|
||||
let guildInfo = interaction.client.guildInfos.get(interaction.guildId);
|
||||
await interaction.editReply(fn.builders.embed(guildInfo.generateSetupInfo()));
|
||||
} else {
|
||||
await interaction.editReply(fn.builders.errorEmbed("Guild doesn't exist in database!"));
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
await interaction.editReply(fn.builders.errorEmbed("There was an error running the command."));
|
||||
}
|
||||
break;
|
||||
case "reset":
|
||||
if (interaction.client.guildInfos.has(interaction.guildId)) {
|
||||
let guildInfo = interaction.client.guildInfos.get(interaction.guildId);
|
||||
if (interaction.options.getBoolean('confirm')) {
|
||||
fn.reset(interaction).then(res => {
|
||||
interaction.editReply(fn.builders.embed(strings.status.reset)).catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
}).catch(err => {
|
||||
console.error(err);
|
||||
interaction.editReply(strings.status.resetError).catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
await interaction.editReply(fn.builders.embed("You must select 'true' to confirm setup reset. No changes have been made.")).catch(e => console.error(e));
|
||||
}
|
||||
} else {
|
||||
await interaction.editReply(fn.builders.errorEmbed("There is no configuration to delete.")).catch(e => console.error(e));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
await interaction.editReply(fn.builders.errorEmbed(strings.error.invalidSubcommand)).catch(e => console.error(e));
|
||||
break;
|
||||
}
|
||||
},
|
||||
};
|
@ -1,21 +0,0 @@
|
||||
const { SlashCommandBuilder } = require('discord.js');
|
||||
const fn = require('../modules/functions.js');
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('setupinfo')
|
||||
.setDescription('View information about how the bot is set up in your server'),
|
||||
execute(interaction) {
|
||||
interaction.deferReply({ephemeral: true}).then(() => {
|
||||
fn.getInfo(interaction.guildId).then(res => {
|
||||
const embed = fn.builders.embed(res);
|
||||
interaction.editReply(embed);
|
||||
}).catch(err => {
|
||||
interaction.editReply(err);
|
||||
console.error(err);
|
||||
});
|
||||
}).catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
},
|
||||
};
|
0
slash-commands/template
Normal file → Executable file
0
slash-commands/template
Normal file → Executable file
76
slash-commands/timetoheight.js
Normal file → Executable file
76
slash-commands/timetoheight.js
Normal file → Executable file
@ -6,24 +6,64 @@ module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('timetoheight')
|
||||
.setDescription('Calculate how long it would take to reach a given height')
|
||||
.addStringOption(o =>
|
||||
o.setName('beginheight')
|
||||
.setDescription('Begining tree height in feet, numbers ONLY')
|
||||
.setRequired(true))
|
||||
.addStringOption(o =>
|
||||
.addIntegerOption(o =>
|
||||
o.setName('endheight')
|
||||
.setDescription('Ending tree height in feet, numbers ONLY')
|
||||
.setRequired(true)),
|
||||
.setDescription('Ending tree height in feet')
|
||||
.setRequired(true)
|
||||
)
|
||||
.addIntegerOption(o =>
|
||||
o.setName('beginheight')
|
||||
.setDescription('Beginning tree height in feet')
|
||||
.setRequired(false)
|
||||
)
|
||||
.addIntegerOption(o =>
|
||||
o.setName('efficiency')
|
||||
.setDescription('Composter efficiency percentage, rounded')
|
||||
.setRequired(false)
|
||||
)
|
||||
.addIntegerOption(o =>
|
||||
o.setName('quality')
|
||||
.setDescription('Compost quality percentage, rounded')
|
||||
.setRequired(false)
|
||||
)
|
||||
.addBooleanOption(o =>
|
||||
o.setName('private')
|
||||
.setDescription('Should the reply be visible only to you?')
|
||||
.setRequired(false)
|
||||
),
|
||||
async execute(interaction) {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
const beginHeight = interaction.options.getString('beginheight');
|
||||
const endHeight = interaction.options.getString('endheight');
|
||||
fn.timeToHeight(beginHeight, endHeight).then(res => {
|
||||
interaction.editReply(`It will take a tree that is ${beginHeight}ft tall ${res} to reach ${endHeight}ft.`);
|
||||
}).catch(err => {
|
||||
interaction.editReply("Error: " + err);
|
||||
console.error(err);
|
||||
return;
|
||||
});
|
||||
},
|
||||
const private = interaction.options.getBoolean('private') != undefined ? interaction.options.getBoolean('private') : true;
|
||||
await interaction.deferReply({ ephemeral: private });
|
||||
const inBeginHeight = interaction.options.getInteger('beginheight');
|
||||
const endHeight = interaction.options.getInteger('endheight');
|
||||
const efficiency = interaction.options.getInteger('efficiency') != undefined ? interaction.options.getInteger('efficiency') : 10;
|
||||
const quality = interaction.options.getInteger('quality') != undefined ? interaction.options.getInteger('quality') : 5;
|
||||
let beginHeight, replyContent;
|
||||
|
||||
if (!inBeginHeight) {
|
||||
const guildInfo = interaction.client.guildInfos.get(interaction.guild.id);
|
||||
beginHeight = guildInfo.treeHeight;
|
||||
} else {
|
||||
beginHeight = inBeginHeight;
|
||||
}
|
||||
|
||||
const timeToHeightResults = await fn.timeToHeight(beginHeight, endHeight, efficiency, quality);
|
||||
const { time, totalWaterCount, compostAppliedCount, average, savedTime } = timeToHeightResults;
|
||||
let replyFields = undefined;
|
||||
if (efficiency && quality) {
|
||||
replyContent = `I estimate that a tree with ${efficiency}% Composter Efficiency and ${quality}% Compost Quality growing from ${beginHeight}ft to ${endHeight}ft will take ${time}`;
|
||||
replyFields = [
|
||||
{ name: `Height Gained:`, value: `${endHeight - beginHeight}ft`, inline: true},
|
||||
{ name: `E/Q:`, value: `${efficiency}%/${quality}%`, inline: true},
|
||||
{ name: `Compost Applied`, value: `${compostAppliedCount} times`, inline: true },
|
||||
{ name: `Compost Average`, value: `${average}%`, inline: true },
|
||||
{ name: `Saved Time`, value: savedTime, inline: true }
|
||||
];
|
||||
} else {
|
||||
replyContent = `I estimate that a tree growing from ${beginHeight}ft to ${endHeight}ft will take ${time}`;
|
||||
}
|
||||
|
||||
const reply = fn.builders.embeds.information(replyContent, replyFields)
|
||||
await interaction.editReply(reply);
|
||||
}
|
||||
};
|
20
slash-commands/watertime.js
Normal file → Executable file
20
slash-commands/watertime.js
Normal file → Executable file
@ -6,13 +6,21 @@ module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('watertime')
|
||||
.setDescription('Calculate the watering time for a given tree height')
|
||||
.addStringOption(o =>
|
||||
.addIntegerOption(o =>
|
||||
o.setName('height')
|
||||
.setDescription('Tree height in feet, numbers ONLY')
|
||||
.setRequired(true)),
|
||||
.setDescription('Tree height')
|
||||
.setRequired(true))
|
||||
.addBooleanOption(o =>
|
||||
o.setName('private')
|
||||
.setDescription('Should the response be private? Default: true')
|
||||
.setRequired(false)),
|
||||
async execute(interaction) {
|
||||
await interaction.deferReply();
|
||||
const treeHeight = interaction.options.getString('height');
|
||||
await interaction.editReply(`A tree that is ${treeHeight}ft tall will have a watering time of ${fn.getWaterTime(treeHeight)} minutes.`);
|
||||
const treeHeight = interaction.options.getInteger('height');
|
||||
const privateOpt = interaction.options.getBoolean('private');
|
||||
const private = privateOpt != undefined ? privateOpt : true;
|
||||
await interaction.deferReply( {ephemeral: private });
|
||||
const waterSeconds = fn.getWaterTime(treeHeight);
|
||||
const waterTime = fn.parseWaterTime(waterSeconds);
|
||||
await interaction.editReply(`A tree that is ${treeHeight}ft tall will have a watering time of ${waterTime}.`);
|
||||
},
|
||||
};
|
Loading…
Reference in New Issue
Block a user